From 6cdc2ea18ce774960c55c91d679835c02e7a9086 Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 22 Feb 2020 16:33:00 -0300 Subject: [PATCH] reorder folders --- src/common/const.h | 771 +++++ src/common/crc.h | 52 + src/common/cvardef.h | 36 + src/common/entity_state.h | 193 ++ src/common/event_flags.h | 47 + src/common/in_buttons.h | 38 + src/common/nowin.h | 15 + src/common/studio_event.h | 29 + src/dlls/.gitignore | 3 + src/dlls/AI_BaseNPC_Schedule.cpp | 1392 ++++++++ src/dlls/Makefile | 56 + src/dlls/activity.h | 109 + src/dlls/activitymap.h | 97 + src/dlls/agrunt.cpp | 1100 +++++++ src/dlls/animating.cpp | 306 ++ src/dlls/animation.cpp | 521 +++ src/dlls/animation.h | 47 + src/dlls/apache.cpp | 977 ++++++ src/dlls/barney.cpp | 666 ++++ src/dlls/bigmomma.cpp | 1194 +++++++ src/dlls/bullsquid.cpp | 1174 +++++++ src/dlls/cdll_dll.h | 46 + src/dlls/cmbase.cpp | 306 ++ src/dlls/cmbase.h | 611 ++++ src/dlls/cmbasemonster.h | 1028 ++++++ src/dlls/cmflyingmonster.h | 53 + src/dlls/cmtalkmonster.h | 178 + src/dlls/combat.cpp | 1597 +++++++++ src/dlls/controller.cpp | 1359 ++++++++ src/dlls/decals.h | 75 + src/dlls/defaultai.cpp | 1084 +++++++ src/dlls/defaultai.h | 98 + src/dlls/dllapi.cpp | 1157 +++++++ src/dlls/doors.h | 33 + src/dlls/effects.cpp | 446 +++ src/dlls/effects.h | 203 ++ src/dlls/enginecallback.h | 158 + src/dlls/explode.h | 32 + src/dlls/extdll.h | 88 + src/dlls/flyingmonster.cpp | 281 ++ src/dlls/func_break.h | 70 + src/dlls/ggrenade.cpp | 466 +++ src/dlls/h_ai.cpp | 199 ++ src/dlls/h_export.cpp | 46 + src/dlls/hassassin.cpp | 909 ++++++ src/dlls/headcrab.cpp | 506 +++ src/dlls/hgrunt.cpp | 2102 ++++++++++++ src/dlls/hornet.cpp | 372 +++ src/dlls/hornet.h | 55 + src/dlls/houndeye.cpp | 1100 +++++++ src/dlls/islave.cpp | 752 +++++ src/dlls/monster_api.cpp | 146 + src/dlls/monster_config.cpp | 418 +++ src/dlls/monster_mm.def | 5 + src/dlls/monster_mm.dsp | 378 +++ src/dlls/monster_plugin.h | 57 + src/dlls/monsterevent.h | 34 + src/dlls/monsters.cpp | 2915 +++++++++++++++++ src/dlls/monsters.h | 180 ++ src/dlls/monsterstate.cpp | 214 ++ src/dlls/nodes.cpp | 3405 ++++++++++++++++++++ src/dlls/nodes.h | 374 +++ src/dlls/plane.h | 43 + src/dlls/schedule.h | 290 ++ src/dlls/scientist.cpp | 1013 ++++++ src/dlls/skill.cpp | 254 ++ src/dlls/skill.h | 103 + src/dlls/sound.cpp | 915 ++++++ src/dlls/squeakgrenade.cpp | 337 ++ src/dlls/subs.cpp | 470 +++ src/dlls/talkmonster.cpp | 1419 ++++++++ src/dlls/util.cpp | 1963 +++++++++++ src/dlls/util.h | 539 ++++ src/dlls/vector.h | 112 + src/dlls/weapons.cpp | 218 ++ src/dlls/weapons.h | 258 ++ src/dlls/zombie.cpp | 316 ++ src/engine/custom.h | 102 + src/engine/customentity.h | 38 + src/engine/edict.h | 29 + src/engine/eiface.h | 493 +++ src/engine/progdefs.h | 224 ++ src/engine/shake.h | 55 + src/engine/studio.h | 362 +++ src/metamod/.gitignore | 3 + src/metamod/Config.mak | 34 + src/metamod/Makefile | 460 +++ src/metamod/api_hook.cpp | 690 ++++ src/metamod/api_hook.h | 397 +++ src/metamod/api_info.cpp | 275 ++ src/metamod/api_info.h | 312 ++ src/metamod/build_all.sh | 5 + src/metamod/commands_meta.cpp | 513 +++ src/metamod/commands_meta.h | 83 + src/metamod/comp_dep.h | 97 + src/metamod/conf_meta.cpp | 240 ++ src/metamod/conf_meta.h | 100 + src/metamod/dllapi.cpp | 513 +++ src/metamod/dllapi.h | 121 + src/metamod/engine_api.cpp | 1130 +++++++ src/metamod/engine_api.h | 240 ++ src/metamod/engine_t.h | 82 + src/metamod/enginecallbacks.h | 78 + src/metamod/engineinfo.cpp | 390 +++ src/metamod/engineinfo.h | 263 ++ src/metamod/game_autodetect.cpp | 130 + src/metamod/game_autodetect.h | 43 + src/metamod/game_support.cpp | 331 ++ src/metamod/game_support.h | 56 + src/metamod/games.h | 140 + src/metamod/h_export.cpp | 108 + src/metamod/h_export.h | 49 + src/metamod/i386pe.merge | 205 ++ src/metamod/info_name.h | 56 + src/metamod/linkent.h | 109 + src/metamod/linkgame.cpp | 37 + src/metamod/linkplug.cpp | 42 + src/metamod/log_meta.cpp | 240 ++ src/metamod/log_meta.h | 116 + src/metamod/meta_api.h | 232 ++ src/metamod/meta_eiface.cpp | 724 +++++ src/metamod/meta_eiface.h | 480 +++ src/metamod/metamod.cpp | 459 +++ src/metamod/metamod.def | 5 + src/metamod/metamod.h | 256 ++ src/metamod/metamod.sln | 20 + src/metamod/metamod.vcproj | 489 +++ src/metamod/mhook.cpp | 0 src/metamod/mhook.h | 0 src/metamod/mlist.cpp | 901 ++++++ src/metamod/mlist.h | 94 + src/metamod/mm_pextensions.h | 116 + src/metamod/mplayer.cpp | 190 ++ src/metamod/mplayer.h | 91 + src/metamod/mplugin.cpp | 1561 +++++++++ src/metamod/mplugin.h | 234 ++ src/metamod/mqueue.cpp | 0 src/metamod/mqueue.h | 0 src/metamod/mreg.cpp | 532 +++ src/metamod/mreg.h | 191 ++ src/metamod/mutil.cpp | 395 +++ src/metamod/mutil.h | 108 + src/metamod/new_baseclass.h | 69 + src/metamod/osdep.cpp | 399 +++ src/metamod/osdep.h | 307 ++ src/metamod/osdep_detect_gamedll_linux.cpp | 341 ++ src/metamod/osdep_detect_gamedll_win32.cpp | 225 ++ src/metamod/osdep_linkent_linux.cpp | 234 ++ src/metamod/osdep_linkent_win32.cpp | 245 ++ src/metamod/osdep_p.cpp | 124 + src/metamod/osdep_p.h | 76 + src/metamod/plinfo.h | 85 + src/metamod/reg_support.cpp | 228 ++ src/metamod/reg_support.h | 49 + src/metamod/res_meta.rc | 76 + src/metamod/ret_type.h | 69 + src/metamod/sdk_util.cpp | 125 + src/metamod/sdk_util.h | 121 + src/metamod/studioapi.cpp | 38 + src/metamod/studioapi.h | 38 + src/metamod/support_meta.cpp | 121 + src/metamod/support_meta.h | 177 + src/metamod/thread_logparse.cpp | 0 src/metamod/thread_logparse.h | 0 src/metamod/tqueue.h | 114 + src/metamod/types_meta.h | 85 + src/metamod/vdate.cpp | 58 + src/metamod/vdate.h | 45 + src/metamod/vers_meta.h | 61 + src/pm_shared/pm_materials.h | 33 + src/utils/common/mathlib.h | 89 + 171 files changed, 60310 insertions(+) create mode 100644 src/common/const.h create mode 100644 src/common/crc.h create mode 100644 src/common/cvardef.h create mode 100644 src/common/entity_state.h create mode 100644 src/common/event_flags.h create mode 100644 src/common/in_buttons.h create mode 100644 src/common/nowin.h create mode 100644 src/common/studio_event.h create mode 100644 src/dlls/.gitignore create mode 100644 src/dlls/AI_BaseNPC_Schedule.cpp create mode 100644 src/dlls/Makefile create mode 100644 src/dlls/activity.h create mode 100644 src/dlls/activitymap.h create mode 100644 src/dlls/agrunt.cpp create mode 100644 src/dlls/animating.cpp create mode 100644 src/dlls/animation.cpp create mode 100644 src/dlls/animation.h create mode 100644 src/dlls/apache.cpp create mode 100644 src/dlls/barney.cpp create mode 100644 src/dlls/bigmomma.cpp create mode 100644 src/dlls/bullsquid.cpp create mode 100644 src/dlls/cdll_dll.h create mode 100644 src/dlls/cmbase.cpp create mode 100644 src/dlls/cmbase.h create mode 100644 src/dlls/cmbasemonster.h create mode 100644 src/dlls/cmflyingmonster.h create mode 100644 src/dlls/cmtalkmonster.h create mode 100644 src/dlls/combat.cpp create mode 100644 src/dlls/controller.cpp create mode 100644 src/dlls/decals.h create mode 100644 src/dlls/defaultai.cpp create mode 100644 src/dlls/defaultai.h create mode 100644 src/dlls/dllapi.cpp create mode 100644 src/dlls/doors.h create mode 100644 src/dlls/effects.cpp create mode 100644 src/dlls/effects.h create mode 100644 src/dlls/enginecallback.h create mode 100644 src/dlls/explode.h create mode 100644 src/dlls/extdll.h create mode 100644 src/dlls/flyingmonster.cpp create mode 100644 src/dlls/func_break.h create mode 100644 src/dlls/ggrenade.cpp create mode 100644 src/dlls/h_ai.cpp create mode 100644 src/dlls/h_export.cpp create mode 100644 src/dlls/hassassin.cpp create mode 100644 src/dlls/headcrab.cpp create mode 100644 src/dlls/hgrunt.cpp create mode 100644 src/dlls/hornet.cpp create mode 100644 src/dlls/hornet.h create mode 100644 src/dlls/houndeye.cpp create mode 100644 src/dlls/islave.cpp create mode 100644 src/dlls/monster_api.cpp create mode 100644 src/dlls/monster_config.cpp create mode 100644 src/dlls/monster_mm.def create mode 100644 src/dlls/monster_mm.dsp create mode 100644 src/dlls/monster_plugin.h create mode 100644 src/dlls/monsterevent.h create mode 100644 src/dlls/monsters.cpp create mode 100644 src/dlls/monsters.h create mode 100644 src/dlls/monsterstate.cpp create mode 100644 src/dlls/nodes.cpp create mode 100644 src/dlls/nodes.h create mode 100644 src/dlls/plane.h create mode 100644 src/dlls/schedule.h create mode 100644 src/dlls/scientist.cpp create mode 100644 src/dlls/skill.cpp create mode 100644 src/dlls/skill.h create mode 100644 src/dlls/sound.cpp create mode 100644 src/dlls/squeakgrenade.cpp create mode 100644 src/dlls/subs.cpp create mode 100644 src/dlls/talkmonster.cpp create mode 100644 src/dlls/util.cpp create mode 100644 src/dlls/util.h create mode 100644 src/dlls/vector.h create mode 100644 src/dlls/weapons.cpp create mode 100644 src/dlls/weapons.h create mode 100644 src/dlls/zombie.cpp create mode 100644 src/engine/custom.h create mode 100644 src/engine/customentity.h create mode 100644 src/engine/edict.h create mode 100644 src/engine/eiface.h create mode 100644 src/engine/progdefs.h create mode 100644 src/engine/shake.h create mode 100644 src/engine/studio.h create mode 100644 src/metamod/.gitignore create mode 100644 src/metamod/Config.mak create mode 100644 src/metamod/Makefile create mode 100644 src/metamod/api_hook.cpp create mode 100644 src/metamod/api_hook.h create mode 100644 src/metamod/api_info.cpp create mode 100644 src/metamod/api_info.h create mode 100644 src/metamod/build_all.sh create mode 100644 src/metamod/commands_meta.cpp create mode 100644 src/metamod/commands_meta.h create mode 100644 src/metamod/comp_dep.h create mode 100644 src/metamod/conf_meta.cpp create mode 100644 src/metamod/conf_meta.h create mode 100644 src/metamod/dllapi.cpp create mode 100644 src/metamod/dllapi.h create mode 100644 src/metamod/engine_api.cpp create mode 100644 src/metamod/engine_api.h create mode 100644 src/metamod/engine_t.h create mode 100644 src/metamod/enginecallbacks.h create mode 100644 src/metamod/engineinfo.cpp create mode 100644 src/metamod/engineinfo.h create mode 100644 src/metamod/game_autodetect.cpp create mode 100644 src/metamod/game_autodetect.h create mode 100644 src/metamod/game_support.cpp create mode 100644 src/metamod/game_support.h create mode 100644 src/metamod/games.h create mode 100644 src/metamod/h_export.cpp create mode 100644 src/metamod/h_export.h create mode 100644 src/metamod/i386pe.merge create mode 100644 src/metamod/info_name.h create mode 100644 src/metamod/linkent.h create mode 100644 src/metamod/linkgame.cpp create mode 100644 src/metamod/linkplug.cpp create mode 100644 src/metamod/log_meta.cpp create mode 100644 src/metamod/log_meta.h create mode 100644 src/metamod/meta_api.h create mode 100644 src/metamod/meta_eiface.cpp create mode 100644 src/metamod/meta_eiface.h create mode 100644 src/metamod/metamod.cpp create mode 100644 src/metamod/metamod.def create mode 100644 src/metamod/metamod.h create mode 100644 src/metamod/metamod.sln create mode 100644 src/metamod/metamod.vcproj create mode 100644 src/metamod/mhook.cpp create mode 100644 src/metamod/mhook.h create mode 100644 src/metamod/mlist.cpp create mode 100644 src/metamod/mlist.h create mode 100644 src/metamod/mm_pextensions.h create mode 100644 src/metamod/mplayer.cpp create mode 100644 src/metamod/mplayer.h create mode 100644 src/metamod/mplugin.cpp create mode 100644 src/metamod/mplugin.h create mode 100644 src/metamod/mqueue.cpp create mode 100644 src/metamod/mqueue.h create mode 100644 src/metamod/mreg.cpp create mode 100644 src/metamod/mreg.h create mode 100644 src/metamod/mutil.cpp create mode 100644 src/metamod/mutil.h create mode 100644 src/metamod/new_baseclass.h create mode 100644 src/metamod/osdep.cpp create mode 100644 src/metamod/osdep.h create mode 100644 src/metamod/osdep_detect_gamedll_linux.cpp create mode 100644 src/metamod/osdep_detect_gamedll_win32.cpp create mode 100644 src/metamod/osdep_linkent_linux.cpp create mode 100644 src/metamod/osdep_linkent_win32.cpp create mode 100644 src/metamod/osdep_p.cpp create mode 100644 src/metamod/osdep_p.h create mode 100644 src/metamod/plinfo.h create mode 100644 src/metamod/reg_support.cpp create mode 100644 src/metamod/reg_support.h create mode 100644 src/metamod/res_meta.rc create mode 100644 src/metamod/ret_type.h create mode 100644 src/metamod/sdk_util.cpp create mode 100644 src/metamod/sdk_util.h create mode 100644 src/metamod/studioapi.cpp create mode 100644 src/metamod/studioapi.h create mode 100644 src/metamod/support_meta.cpp create mode 100644 src/metamod/support_meta.h create mode 100644 src/metamod/thread_logparse.cpp create mode 100644 src/metamod/thread_logparse.h create mode 100644 src/metamod/tqueue.h create mode 100644 src/metamod/types_meta.h create mode 100644 src/metamod/vdate.cpp create mode 100644 src/metamod/vdate.h create mode 100644 src/metamod/vers_meta.h create mode 100644 src/pm_shared/pm_materials.h create mode 100644 src/utils/common/mathlib.h diff --git a/src/common/const.h b/src/common/const.h new file mode 100644 index 0000000..9db7d38 --- /dev/null +++ b/src/common/const.h @@ -0,0 +1,771 @@ +/*** +* +* 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. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CONST_H +#define CONST_H +// +// Constants shared by the engine and dlls +// This header file included by engine files and DLL files. +// Most came from server.h + +// edict->flags +#define FL_FLY (1<<0) // Changes the SV_Movestep() behavior to not need to be on ground +#define FL_SWIM (1<<1) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water) +#define FL_CONVEYOR (1<<2) +#define FL_CLIENT (1<<3) +#define FL_INWATER (1<<4) +#define FL_MONSTER (1<<5) +#define FL_GODMODE (1<<6) +#define FL_NOTARGET (1<<7) +#define FL_SKIPLOCALHOST (1<<8) // Don't send entity to local host, it's predicting this entity itself +#define FL_ONGROUND (1<<9) // At rest / on the ground +#define FL_PARTIALGROUND (1<<10) // not all corners are valid +#define FL_WATERJUMP (1<<11) // player jumping out of water +#define FL_FROZEN (1<<12) // Player is frozen for 3rd person camera +#define FL_FAKECLIENT (1<<13) // JAC: fake client, simulated server side; don't send network messages to them +#define FL_DUCKING (1<<14) // Player flag -- Player is fully crouched +#define FL_FLOAT (1<<15) // Apply floating force to this entity when in water +#define FL_GRAPHED (1<<16) // worldgraph has this ent listed as something that blocks a connection + +// UNDONE: Do we need these? +#define FL_IMMUNE_WATER (1<<17) +#define FL_IMMUNE_SLIME (1<<18) +#define FL_IMMUNE_LAVA (1<<19) + +#define FL_PROXY (1<<20) // This is a spectator proxy +#define FL_ALWAYSTHINK (1<<21) // Brush model flag -- call think every frame regardless of nextthink - ltime (for constantly changing velocity/path) +#define FL_BASEVELOCITY (1<<22) // Base velocity has been applied this frame (used to convert base velocity into momentum) +#define FL_MONSTERCLIP (1<<23) // Only collide in with monsters who have FL_MONSTERCLIP set +#define FL_ONTRAIN (1<<24) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction. +#define FL_WORLDBRUSH (1<<25) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something) +#define FL_SPECTATOR (1<<26) // This client is a spectator, don't run touch functions, etc. +#define FL_CUSTOMENTITY (1<<29) // This is a custom entity +#define FL_KILLME (1<<30) // This entity is marked for death -- This allows the engine to kill ents at the appropriate time +#define FL_DORMANT (1<<31) // Entity is dormant, no updates to client + + +// Goes into globalvars_t.trace_flags +#define FTRACE_SIMPLEBOX (1<<0) // Traceline with a simple box + + +// walkmove modes +#define WALKMOVE_NORMAL 0 // normal walkmove +#define WALKMOVE_WORLDONLY 1 // doesn't hit ANY entities, no matter what the solid type +#define WALKMOVE_CHECKONLY 2 // move, but don't touch triggers + +// edict->movetype values +#define MOVETYPE_NONE 0 // never moves +//#define MOVETYPE_ANGLENOCLIP 1 +//#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 // Player only - moving on the ground +#define MOVETYPE_STEP 4 // gravity, special edge handling -- monsters use this +#define MOVETYPE_FLY 5 // No gravity, but still collides with stuff +#define MOVETYPE_TOSS 6 // gravity/collisions +#define MOVETYPE_PUSH 7 // no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 // No gravity, no collisions, still do velocity/avelocity +#define MOVETYPE_FLYMISSILE 9 // extra size to monsters +#define MOVETYPE_BOUNCE 10 // Just like Toss, but reflect velocity when contacting surfaces +#define MOVETYPE_BOUNCEMISSILE 11 // bounce w/o gravity +#define MOVETYPE_FOLLOW 12 // track movement of aiment +#define MOVETYPE_PUSHSTEP 13 // BSP model that needs physics/world collisions (uses nearest hull for world collision) + +// edict->solid values +// NOTE: Some movetypes will cause collisions independent of SOLID_NOT/SOLID_TRIGGER when the entity moves +// SOLID only effects OTHER entities colliding with this one when they move - UGH! +#define SOLID_NOT 0 // no interaction with other objects +#define SOLID_TRIGGER 1 // touch on edge, but not blocking +#define SOLID_BBOX 2 // touch on edge, block +#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground +#define SOLID_BSP 4 // bsp clip, touch on edge, block + +// edict->deadflag values +#define DEAD_NO 0 // alive +#define DEAD_DYING 1 // playing death animation or still falling off of a ledge waiting to hit ground +#define DEAD_DEAD 2 // dead. lying still. +#define DEAD_RESPAWNABLE 3 +#define DEAD_DISCARDBODY 4 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// entity effects +#define EF_BRIGHTFIELD 1 // swirling cloud of particles +#define EF_MUZZLEFLASH 2 // single frame ELIGHT on entity attachment 0 +#define EF_BRIGHTLIGHT 4 // DLIGHT centered at entity origin +#define EF_DIMLIGHT 8 // player flashlight +#define EF_INVLIGHT 16 // get lighting from ceiling +#define EF_NOINTERP 32 // don't interpolate the next frame +#define EF_LIGHT 64 // rocket flare glow sprite +#define EF_NODRAW 128 // don't draw entity + +// entity flags +#define EFLAG_SLERP 1 // do studio interpolation of this entity + +// +// temp entity events +// +#define TE_BEAMPOINTS 0 // beam effect between two points +// coord coord coord (start position) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMENTPOINT 1 // beam effect between point and entity +// short (start entity) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_GUNSHOT 2 // particle effect plus ricochet sound +// coord coord coord (position) + +#define TE_EXPLOSION 3 // additive sprite, 2 dynamic lights, flickering particles, explosion sound, move vertically 8 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) +// byte (flags) +// +// The Explosion effect has some flags to control performance/aesthetic features: +#define TE_EXPLFLAG_NONE 0 // all flags clear makes default Half-Life explosion +#define TE_EXPLFLAG_NOADDITIVE 1 // sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite) +#define TE_EXPLFLAG_NODLIGHTS 2 // do not render dynamic lights +#define TE_EXPLFLAG_NOSOUND 4 // do not play client explosion sound +#define TE_EXPLFLAG_NOPARTICLES 8 // do not draw particles + + +#define TE_TAREXPLOSION 4 // Quake1 "tarbaby" explosion with sound +// coord coord coord (position) + +#define TE_SMOKE 5 // alphablend sprite, move vertically 30 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) + +#define TE_TRACER 6 // tracer effect from point to point +// coord, coord, coord (start) +// coord, coord, coord (end) + +#define TE_LIGHTNING 7 // TE_BEAMPOINTS with simplified parameters +// coord, coord, coord (start) +// coord, coord, coord (end) +// byte (life in 0.1's) +// byte (width in 0.1's) +// byte (amplitude in 0.01's) +// short (sprite model index) + +#define TE_BEAMENTS 8 +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_SPARKS 9 // 8 random tracers with gravity, ricochet sprite +// coord coord coord (position) + +#define TE_LAVASPLASH 10 // Quake1 lava splash +// coord coord coord (position) + +#define TE_TELEPORT 11 // Quake1 teleport splash +// coord coord coord (position) + +#define TE_EXPLOSION2 12 // Quake1 colormaped (base palette) particle explosion with sound +// coord coord coord (position) +// byte (starting color) +// byte (num colors) + +#define TE_BSPDECAL 13 // Decal from the .BSP file +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// short (texture index of precached decal texture name) +// short (entity index) +// [optional - only included if previous short is non-zero (not the world)] short (index of model of above entity) + +#define TE_IMPLOSION 14 // tracers moving toward a point +// coord, coord, coord (position) +// byte (radius) +// byte (count) +// byte (life in 0.1's) + +#define TE_SPRITETRAIL 15 // line of moving glow sprites with gravity, fadeout, and collisions +// coord, coord, coord (start) +// coord, coord, coord (end) +// short (sprite index) +// byte (count) +// byte (life in 0.1's) +// byte (scale in 0.1's) +// byte (velocity along vector in 10's) +// byte (randomness of velocity in 10's) + +#define TE_BEAM 16 // obsolete + +#define TE_SPRITE 17 // additive sprite, plays 1 cycle +// coord, coord, coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (brightness) + +#define TE_BEAMSPRITE 18 // A beam with a sprite at the end +// coord, coord, coord (start position) +// coord, coord, coord (end position) +// short (beam sprite index) +// short (end sprite index) + +#define TE_BEAMTORUS 19 // screen aligned beam ring, expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMDISK 20 // disk that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMCYLINDER 21 // cylinder that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMFOLLOW 22 // create a line of decaying beam segments until entity stops moving +// short (entity:attachment to follow) +// short (sprite index) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte,byte,byte (color) +// byte (brightness) + +#define TE_GLOWSPRITE 23 +// coord, coord, coord (pos) short (model index) byte (scale / 10) + +#define TE_BEAMRING 24 // connect a beam ring to two entities +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_STREAK_SPLASH 25 // oriented shower of tracers +// coord coord coord (start position) +// coord coord coord (direction vector) +// byte (color) +// short (count) +// short (base speed) +// short (ramdon velocity) + +#define TE_BEAMHOSE 26 // obsolete + +#define TE_DLIGHT 27 // dynamic light, effect world, minor entity effect +// coord, coord, coord (pos) +// byte (radius in 10's) +// byte byte byte (color) +// byte (brightness) +// byte (life in 10's) +// byte (decay rate in 10's) + +#define TE_ELIGHT 28 // point entity light, no world effect +// short (entity:attachment to follow) +// coord coord coord (initial position) +// coord (radius) +// byte byte byte (color) +// byte (life in 0.1's) +// coord (decay rate) + +#define TE_TEXTMESSAGE 29 +// short 1.2.13 x (-1 = center) +// short 1.2.13 y (-1 = center) +// byte Effect 0 = fade in/fade out + // 1 is flickery credits + // 2 is write out (training room) + +// 4 bytes r,g,b,a color1 (text color) +// 4 bytes r,g,b,a color2 (effect color) +// ushort 8.8 fadein time +// ushort 8.8 fadeout time +// ushort 8.8 hold time +// optional ushort 8.8 fxtime (time the highlight lags behing the leading text in effect 2) +// string text message (512 chars max sz string) +#define TE_LINE 30 +// coord, coord, coord startpos +// coord, coord, coord endpos +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_BOX 31 +// coord, coord, coord boxmins +// coord, coord, coord boxmaxs +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_KILLBEAM 99 // kill all beams attached to entity +// short (entity) + +#define TE_LARGEFUNNEL 100 +// coord coord coord (funnel position) +// short (sprite index) +// short (flags) + +#define TE_BLOODSTREAM 101 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_SHOWLINE 102 // line of particles every 5 units, dies in 30 seconds +// coord coord coord (start position) +// coord coord coord (end position) + +#define TE_BLOOD 103 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_DECAL 104 // Decal applied to a brush entity (not the world) +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) +// short (entity index) + +#define TE_FIZZ 105 // create alpha sprites inside of entity, float upwards +// short (entity) +// short (sprite index) +// byte (density) + +#define TE_MODEL 106 // create a moving model that bounces and makes a sound when it hits +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// angle (initial yaw) +// short (model index) +// byte (bounce sound type) +// byte (life in 0.1's) + +#define TE_EXPLODEMODEL 107 // spherical shower of models, picks from set +// coord, coord, coord (origin) +// coord (velocity) +// short (model index) +// short (count) +// byte (life in 0.1's) + +#define TE_BREAKMODEL 108 // box of models or sprites +// coord, coord, coord (position) +// coord, coord, coord (size) +// coord, coord, coord (velocity) +// byte (random velocity in 10's) +// short (sprite or model index) +// byte (count) +// byte (life in 0.1 secs) +// byte (flags) + +#define TE_GUNSHOTDECAL 109 // decal and ricochet sound +// coord, coord, coord (position) +// short (entity index???) +// byte (decal???) + +#define TE_SPRITE_SPRAY 110 // spay of alpha sprites +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (sprite index) +// byte (count) +// byte (speed) +// byte (noise) + +#define TE_ARMOR_RICOCHET 111 // quick spark sprite, client ricochet sound. +// coord, coord, coord (position) +// byte (scale in 0.1's) + +#define TE_PLAYERDECAL 112 // ??? +// byte (playerindex) +// coord, coord, coord (position) +// short (entity???) +// byte (decal number???) +// [optional] short (model index???) + +#define TE_BUBBLES 113 // create alpha sprites inside of box, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BUBBLETRAIL 114 // create alpha sprites along a line, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BLOODSPRITE 115 // spray of opaque sprite1's that fall, single sprite2 for 1..2 secs (this is a high-priority tent) +// coord, coord, coord (position) +// short (sprite1 index) +// short (sprite2 index) +// byte (color) +// byte (scale) + +#define TE_WORLDDECAL 116 // Decal applied to the world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) + +#define TE_WORLDDECALHIGH 117 // Decal (with texture index > 256) applied to world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) + +#define TE_DECALHIGH 118 // Same as TE_DECAL, but the texture index was greater than 256 +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) +// short (entity index) + +#define TE_PROJECTILE 119 // Makes a projectile (like a nail) (this is a high-priority tent) +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (modelindex) +// byte (life) +// byte (owner) projectile won't collide with owner (if owner == 0, projectile will hit any client). + +#define TE_SPRAY 120 // Throws a shower of sprites or models +// coord, coord, coord (position) +// coord, coord, coord (direction) +// short (modelindex) +// byte (count) +// byte (speed) +// byte (noise) +// byte (rendermode) + +#define TE_PLAYERSPRITES 121 // sprites emit from a player's bounding box (ONLY use for players!) +// byte (playernum) +// short (sprite modelindex) +// byte (count) +// byte (variance) (0 = no variance in size) (10 = 10% variance in size) + +#define TE_PARTICLEBURST 122 // very similar to lavasplash. +// coord (origin) +// short (radius) +// byte (particle color) +// byte (duration * 10) (will be randomized a bit) + +#define TE_FIREFIELD 123 // makes a field of fire. +// coord (origin) +// short (radius) (fire is made in a square around origin. -radius, -radius to radius, radius) +// short (modelindex) +// byte (count) +// byte (flags) +// byte (duration (in seconds) * 10) (will be randomized a bit) +// +// to keep network traffic low, this message has associated flags that fit into a byte: +#define TEFIRE_FLAG_ALLFLOAT 1 // all sprites will drift upwards as they animate +#define TEFIRE_FLAG_SOMEFLOAT 2 // some of the sprites will drift upwards. (50% chance) +#define TEFIRE_FLAG_LOOP 4 // if set, sprite plays at 15 fps, otherwise plays at whatever rate stretches the animation over the sprite's duration. +#define TEFIRE_FLAG_ALPHA 8 // if set, sprite is rendered alpha blended at 50% else, opaque +#define TEFIRE_FLAG_PLANAR 16 // if set, all fire sprites have same initial Z instead of randomly filling a cube. + +#define TE_PLAYERATTACHMENT 124 // attaches a TENT to a player (this is a high-priority tent) +// byte (entity index of player) +// coord (vertical offset) ( attachment origin.z = player origin.z + vertical offset ) +// short (model index) +// short (life * 10 ); + +#define TE_KILLPLAYERATTACHMENTS 125 // will expire all TENTS attached to a player. +// byte (entity index of player) + +#define TE_MULTIGUNSHOT 126 // much more compact shotgun message +// This message is used to make a client approximate a 'spray' of gunfire. +// Any weapon that fires more than one bullet per frame and fires in a bit of a spread is +// a good candidate for MULTIGUNSHOT use. (shotguns) +// +// NOTE: This effect makes the client do traces for each bullet, these client traces ignore +// entities that have studio models.Traces are 4096 long. +// +// coord (origin) +// coord (origin) +// coord (origin) +// coord (direction) +// coord (direction) +// coord (direction) +// coord (x noise * 100) +// coord (y noise * 100) +// byte (count) +// byte (bullethole decal texture index) + +#define TE_USERTRACER 127 // larger message than the standard tracer, but allows some customization. +// coord (origin) +// coord (origin) +// coord (origin) +// coord (velocity) +// coord (velocity) +// coord (velocity) +// byte ( life * 10 ) +// byte ( color ) this is an index into an array of color vectors in the engine. (0 - ) +// byte ( length * 10 ) + + + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_PVS 4 // Ents in PVS of org +#define MSG_PAS 5 // Ents in PAS of org +#define MSG_PVS_R 6 // Reliable to PVS +#define MSG_PAS_R 7 // Reliable to PAS +#define MSG_ONE_UNRELIABLE 8 // Send to one client, but don't put in reliable stream, put in unreliable datagram ( could be dropped ) +#define MSG_SPEC 9 // Sends to all spectator proxies + +// contents of a spot in the world +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +/* These additional contents constants are defined in bspfile.h +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 +*/ +#define CONTENTS_LADDER -16 + +#define CONTENT_EMPTY -1 +#define CONTENT_SOLID -2 +#define CONTENT_WATER -3 +#define CONTENT_SLIME -4 +#define CONTENT_LAVA -5 +#define CONTENT_SKY -6 + +// channels +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +#define CHAN_STREAM 5 // allocate stream channel from the static or dynamic area +#define CHAN_STATIC 6 // allocate channel from the static area +#define CHAN_NETWORKVOICE_BASE 7 // voice data coming across the network +#define CHAN_NETWORKVOICE_END 500 // network voice data reserves slots (CHAN_NETWORKVOICE_BASE through CHAN_NETWORKVOICE_END). + +// attenuation values +#define ATTN_NONE 0 +#define ATTN_NORM (float)0.8 +#define ATTN_IDLE (float)2 +#define ATTN_STATIC (float)1.25 + +// pitch values +#define PITCH_NORM 100 // non-pitch shifted +#define PITCH_LOW 95 // other values are possible - 0-255, where 255 is very high +#define PITCH_HIGH 120 + +// volume values +#define VOL_NORM 1.0 + +// plats +#define PLAT_LOW_TRIGGER 1 + +// Trains +#define SF_TRAIN_WAIT_RETRIGGER 1 +#define SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains + +// buttons +#ifndef IN_BUTTONS_H +#include "in_buttons.h" +#endif + +// Break Model Defines + +#define BREAK_TYPEMASK 0x4F +#define BREAK_GLASS 0x01 +#define BREAK_METAL 0x02 +#define BREAK_FLESH 0x04 +#define BREAK_WOOD 0x08 + +#define BREAK_SMOKE 0x10 +#define BREAK_TRANS 0x20 +#define BREAK_CONCRETE 0x40 +#define BREAK_2 0x80 + +// Colliding temp entity sounds + +#define BOUNCE_GLASS BREAK_GLASS +#define BOUNCE_METAL BREAK_METAL +#define BOUNCE_FLESH BREAK_FLESH +#define BOUNCE_WOOD BREAK_WOOD +#define BOUNCE_SHRAP 0x10 +#define BOUNCE_SHELL 0x20 +#define BOUNCE_CONCRETE BREAK_CONCRETE +#define BOUNCE_SHOTSHELL 0x80 + +// Temp entity bounce sound types +#define TE_BOUNCE_NULL 0 +#define TE_BOUNCE_SHELL 1 +#define TE_BOUNCE_SHOTSHELL 2 + +// Rendering constants +enum +{ + kRenderNormal, // src + kRenderTransColor, // c*a+dest*(1-a) + kRenderTransTexture, // src*a+dest*(1-a) + kRenderGlow, // src*a+dest -- No Z buffer checks + kRenderTransAlpha, // src*srca+dest*(1-srca) + kRenderTransAdd, // src*a+dest +}; + +enum +{ + kRenderFxNone = 0, + kRenderFxPulseSlow, + kRenderFxPulseFast, + kRenderFxPulseSlowWide, + kRenderFxPulseFastWide, + kRenderFxFadeSlow, + kRenderFxFadeFast, + kRenderFxSolidSlow, + kRenderFxSolidFast, + kRenderFxStrobeSlow, + kRenderFxStrobeFast, + kRenderFxStrobeFaster, + kRenderFxFlickerSlow, + kRenderFxFlickerFast, + kRenderFxNoDissipation, + kRenderFxDistort, // Distort/scale/translate flicker + kRenderFxHologram, // kRenderFxDistort + distance fade + kRenderFxDeadPlayer, // kRenderAmt is the player index + kRenderFxExplode, // Scale up really big! + kRenderFxGlowShell, // Glowing Shell + kRenderFxClampMinScale, // Keep this sprite from getting very small (SPRITES only!) +}; + + +typedef int func_t; +typedef int string_t; + +typedef unsigned char byte; +typedef unsigned short word; +#define _DEF_BYTE_ + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum {false, true} qboolean; +#else +typedef int qboolean; +#endif + +typedef struct +{ + byte r, g, b; +} color24; + +typedef struct +{ + unsigned r, g, b, a; +} colorVec; + +#ifdef _WIN32 +#pragma pack(push,2) +#endif + +typedef struct +{ + unsigned short r, g, b, a; +} PackedColorVec; + +#ifdef _WIN32 +#pragma pack(pop) +#endif +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +typedef struct edict_s edict_t; + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + plane_t plane; // surface normal at impact + edict_t *ent; // entity the surface is on + int hitgroup; // 0 == generic, non zero is specific body part +} trace_t; + +#endif + diff --git a/src/common/crc.h b/src/common/crc.h new file mode 100644 index 0000000..1c17d62 --- /dev/null +++ b/src/common/crc.h @@ -0,0 +1,52 @@ +/*** +* +* 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. +* +* 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. +* +****/ +/* crc.h */ +#ifndef CRC_H +#define CRC_H +#ifdef _WIN32 +#pragma once +#endif + +// MD5 Hash +typedef struct +{ + unsigned int buf[4]; + unsigned int bits[2]; + unsigned char in[64]; +} MD5Context_t; + + +typedef unsigned long CRC32_t; +void CRC32_Init(CRC32_t *pulCRC); +CRC32_t CRC32_Final(CRC32_t pulCRC); +void CRC32_ProcessBuffer(CRC32_t *pulCRC, void *p, int len); +void CRC32_ProcessByte(CRC32_t *pulCRC, unsigned char ch); +int CRC_File(CRC32_t *crcvalue, char *pszFileName); + +unsigned char COM_BlockSequenceCRCByte (unsigned char *base, int length, int sequence); + +void MD5Init(MD5Context_t *context); +void MD5Update(MD5Context_t *context, unsigned char const *buf, + unsigned int len); +void MD5Final(unsigned char digest[16], MD5Context_t *context); +void Transform(unsigned int buf[4], unsigned int const in[16]); + +int MD5_Hash_File(unsigned char digest[16], char *pszFileName, int bUsefopen, int bSeed, unsigned int seed[4]); +char *MD5_Print(unsigned char hash[16]); +int MD5_Hash_CachedFile(unsigned char digest[16], unsigned char *pCache, int nFileSize, int bSeed, unsigned int seed[4]); + +int CRC_MapFile(CRC32_t *crcvalue, char *pszFileName); + +#endif diff --git a/src/common/cvardef.h b/src/common/cvardef.h new file mode 100644 index 0000000..2e23406 --- /dev/null +++ b/src/common/cvardef.h @@ -0,0 +1,36 @@ +/*** +* +* 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. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CVARDEF_H +#define CVARDEF_H + +#define FCVAR_ARCHIVE (1<<0) // set to cause it to be saved to vars.rc +#define FCVAR_USERINFO (1<<1) // changes the client's info string +#define FCVAR_SERVER (1<<2) // notifies players when changed +#define FCVAR_EXTDLL (1<<3) // defined by external DLL +#define FCVAR_CLIENTDLL (1<<4) // defined by the client dll +#define FCVAR_PROTECTED (1<<5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value +#define FCVAR_SPONLY (1<<6) // This cvar cannot be changed by clients connected to a multiplayer server. +#define FCVAR_PRINTABLEONLY (1<<7) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ). +#define FCVAR_UNLOGGED (1<<8) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log + +typedef struct cvar_s +{ + char *name; + char *string; + int flags; + float value; + struct cvar_s *next; +} cvar_t; +#endif diff --git a/src/common/entity_state.h b/src/common/entity_state.h new file mode 100644 index 0000000..ff84655 --- /dev/null +++ b/src/common/entity_state.h @@ -0,0 +1,193 @@ +/*** +* +* 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. +* +* 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. +* +****/ +#if !defined( ENTITY_STATEH ) +#define ENTITY_STATEH +#ifdef _WIN32 +#pragma once +#endif + +// For entityType below +#define ENTITY_NORMAL (1<<0) +#define ENTITY_BEAM (1<<1) + +// Entity state is used for the baseline and for delta compression of a packet of +// entities that is sent to a client. +typedef struct entity_state_s entity_state_t; + +struct entity_state_s +{ +// Fields which are filled in by routines outside of delta compression + int entityType; + // Index into cl_entities array for this entity. + int number; + float msg_time; + + // Message number last time the player/entity state was updated. + int messagenum; + + // Fields which can be transitted and reconstructed over the network stream + vec3_t origin; + vec3_t angles; + + int modelindex; + int sequence; + float frame; + int colormap; + short skin; + short solid; + int effects; + float scale; + + byte eflags; + + // Render information + int rendermode; + int renderamt; + color24 rendercolor; + int renderfx; + + int movetype; + float animtime; + float framerate; + int body; + byte controller[4]; + byte blending[4]; + vec3_t velocity; + + // Send bbox down to client for use during prediction. + vec3_t mins; + vec3_t maxs; + + int aiment; + // If owned by a player, the index of that player ( for projectiles ). + int owner; + + // Friction, for prediction. + float friction; + // Gravity multiplier + float gravity; + +// PLAYER SPECIFIC + int team; + int playerclass; + int health; + qboolean spectator; + int weaponmodel; + int gaitsequence; + // If standing on conveyor, e.g. + vec3_t basevelocity; + // Use the crouched hull, or the regular player hull. + int usehull; + // Latched buttons last time state updated. + int oldbuttons; + // -1 = in air, else pmove entity number + int onground; + int iStepLeft; + // How fast we are falling + float flFallVelocity; + + float fov; + int weaponanim; + + // Parametric movement overrides + vec3_t startpos; + vec3_t endpos; + float impacttime; + float starttime; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +}; + +#include "pm_info.h" + +typedef struct clientdata_s +{ + vec3_t origin; + vec3_t velocity; + + int viewmodel; + vec3_t punchangle; + int flags; + int waterlevel; + int watertype; + vec3_t view_ofs; + float health; + + int bInDuck; + + int weapons; // remove? + + int flTimeStepSound; + int flDuckTime; + int flSwimTime; + int waterjumptime; + + float maxspeed; + + float fov; + int weaponanim; + + int m_iId; + int ammo_shells; + int ammo_nails; + int ammo_cells; + int ammo_rockets; + float m_flNextAttack; + + int tfstate; + + int pushmsec; + + int deadflag; + + char physinfo[ MAX_PHYSINFO_STRING ]; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} clientdata_t; + +#include "weaponinfo.h" + +typedef struct local_state_s +{ + entity_state_t playerstate; + clientdata_t client; + weapon_data_t weapondata[ 32 ]; +} local_state_t; + +#endif // !ENTITY_STATEH \ No newline at end of file diff --git a/src/common/event_flags.h b/src/common/event_flags.h new file mode 100644 index 0000000..8e489a1 --- /dev/null +++ b/src/common/event_flags.h @@ -0,0 +1,47 @@ +/*** +* +* 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. +* +* 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. +* +****/ +#if !defined( EVENT_FLAGSH ) +#define EVENT_FLAGSH +#ifdef _WIN32 +#pragma once +#endif + +// Skip local host for event send. +#define FEV_NOTHOST (1<<0) + +// Send the event reliably. You must specify the origin and angles and use +// PLAYBACK_EVENT_FULL for this to work correctly on the server for anything +// that depends on the event origin/angles. I.e., the origin/angles are not +// taken from the invoking edict for reliable events. +#define FEV_RELIABLE (1<<1) + +// Don't restrict to PAS/PVS, send this event to _everybody_ on the server ( useful for stopping CHAN_STATIC +// sounds started by client event when client is not in PVS anymore ( hwguy in TFC e.g. ). +#define FEV_GLOBAL (1<<2) + +// If this client already has one of these events in its queue, just update the event instead of sending it as a duplicate +// +#define FEV_UPDATE (1<<3) + +// Only send to entity specified as the invoker +#define FEV_HOSTONLY (1<<4) + +// Only send if the event was created on the server. +#define FEV_SERVER (1<<5) + +// Only issue event client side ( from shared code ) +#define FEV_CLIENT (1<<6) + +#endif \ No newline at end of file diff --git a/src/common/in_buttons.h b/src/common/in_buttons.h new file mode 100644 index 0000000..8cf40ec --- /dev/null +++ b/src/common/in_buttons.h @@ -0,0 +1,38 @@ +/*** +* +* 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. +* +* 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 IN_BUTTONS_H +#define IN_BUTTONS_H +#ifdef _WIN32 +#pragma once +#endif + +#define IN_ATTACK (1 << 0) +#define IN_JUMP (1 << 1) +#define IN_DUCK (1 << 2) +#define IN_FORWARD (1 << 3) +#define IN_BACK (1 << 4) +#define IN_USE (1 << 5) +#define IN_CANCEL (1 << 6) +#define IN_LEFT (1 << 7) +#define IN_RIGHT (1 << 8) +#define IN_MOVELEFT (1 << 9) +#define IN_MOVERIGHT (1 << 10) +#define IN_ATTACK2 (1 << 11) +#define IN_RUN (1 << 12) +#define IN_RELOAD (1 << 13) +#define IN_ALT1 (1 << 14) +#define IN_SCORE (1 << 15) // Used by client.dll for when scoreboard is held down + +#endif // IN_BUTTONS_H diff --git a/src/common/nowin.h b/src/common/nowin.h new file mode 100644 index 0000000..e9644c8 --- /dev/null +++ b/src/common/nowin.h @@ -0,0 +1,15 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef INC_NOWIN_H +#define INC_NOWIN_H +#ifndef _WIN32 + +#include + +#endif //!_WIN32 +#endif //INC_NOWIN_H \ No newline at end of file diff --git a/src/common/studio_event.h b/src/common/studio_event.h new file mode 100644 index 0000000..ba9d182 --- /dev/null +++ b/src/common/studio_event.h @@ -0,0 +1,29 @@ +/*** +* +* 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. +* +* 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. +* +****/ +#if !defined( STUDIO_EVENTH ) +#define STUDIO_EVENTH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct mstudioevent_s +{ + int frame; + int event; + int type; + char options[64]; +} mstudioevent_t; + +#endif // STUDIO_EVENTH diff --git a/src/dlls/.gitignore b/src/dlls/.gitignore new file mode 100644 index 0000000..d6ab6d4 --- /dev/null +++ b/src/dlls/.gitignore @@ -0,0 +1,3 @@ +msgs/ +opt.*/ +debug.*/ diff --git a/src/dlls/AI_BaseNPC_Schedule.cpp b/src/dlls/AI_BaseNPC_Schedule.cpp new file mode 100644 index 0000000..687a5a7 --- /dev/null +++ b/src/dlls/AI_BaseNPC_Schedule.cpp @@ -0,0 +1,1392 @@ +/*** +* +* Copyright (c) 1999, 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. +* +****/ +//========================================================= +// schedule.cpp - functions and data pertaining to the +// monsters' AI scheduling system. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "animation.h" +#include "nodes.h" +#include "defaultai.h" + +extern CGraph WorldGraph; + +//========================================================= +// FHaveSchedule - Returns TRUE if monster's m_pSchedule +// is anything other than NULL. +//========================================================= +BOOL CMBaseMonster :: FHaveSchedule( void ) +{ + if ( m_pSchedule == NULL ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// ClearSchedule - blanks out the caller's schedule pointer +// and index. +//========================================================= +void CMBaseMonster :: 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 CMBaseMonster :: 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 CMBaseMonster :: 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 _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 ( strcmp( STRING(pev->model), "models/hgrunt.mdl" ) == 0 ) + { + 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 CMBaseMonster :: 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 CMBaseMonster :: 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 CMBaseMonster :: 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. + ALERT ( at_aiconsole, "Schedule: %s Failed\n", m_pSchedule->pName ); + +/*jlb spark + Vector tmp = pev->origin; + tmp.z = pev->absmax.z + 16; + UTIL_Sparks( tmp ); +jlb */ + } +#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 CMBaseMonster :: 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 ( (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 = CMBaseMonster::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 CMBaseMonster :: 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: + { + edict_t *pTarget; + + if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ) + pTarget = m_hTargetEnt; + else + pTarget = m_hEnemy; + if ( pTarget ) + { + pev->ideal_yaw = UTIL_VecToYaw( pTarget->v.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())) ) + { + 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->v.origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->v.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(); + + if ( !BBoxFlat() ) + { + // a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will + // block the player on a slope or stairs, the corpse is made nonsolid. +// pev->solid = SOLID_NOT; + UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) ); + } + else // !!!HACKHACK - put monster in a thin, wide bounding box until we fix the solid type/bounding volume problem + UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + 1 ) ); + + if ( ShouldFadeOnDeath() ) + { + // this monster was created by a monstermaker... fade the corpse out. + SUB_StartFadeOut(); + } + } + 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; + } +} + +//========================================================= +// 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 CMBaseMonster :: 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 CMBaseMonster :: 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->v.origin, m_hEnemy->v.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->v.origin, m_hEnemy->v.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->v.origin, m_hEnemy->v.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 + { + edict_t *pEdict = m_hEnemy; + pevCover = VARS(pEdict); + } + + 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: + { + { + // 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->v.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->v.origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->v.origin; + if ( !MoveToTarget( ACT_WALK, 2 ) ) + TaskFail(); + } + break; + } + case TASK_RUN_TO_TARGET: + case TASK_WALK_TO_TARGET: + { + Activity newActivity; + + if ( (m_hTargetEnt->v.origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + if ( pTask->iTask == TASK_WALK_TO_TARGET ) + newActivity = ACT_WALK; + else + newActivity = ACT_RUN; + // This monster can't do this! + if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE ) + TaskComplete(); + else + { + if ( m_hTargetEnt == NULL || !MoveToTarget( newActivity, 2 ) ) + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to reach target!!!\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: + { + edict_t *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( BuildRoute ( pEnemy->v.origin, bits_MF_TO_ENEMY, pEnemy ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( pEnemy->v.origin, pEnemy->v.view_ofs, 0, (pEnemy->v.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: + { + edict_t *pPlayer = FIND_ENTITY_BY_CLASSNAME( 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_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: + { + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSCENT: + { + if ( m_hEnemy != NULL ) + { +/*jlb + m_hTargetEnt = m_hEnemy; + if ( MoveToTarget( m_movementActivity, 2 ) ) + { + TaskComplete(); + } + else +jlb*/ + { + // 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_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 *CMBaseMonster :: 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 *CMBaseMonster :: 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; + } + default: + { + ALERT ( at_aiconsole, "Invalid State for GetSchedule!\n" ); + break; + } + } + + return &slError[ 0 ]; +} diff --git a/src/dlls/Makefile b/src/dlls/Makefile new file mode 100644 index 0000000..da82e70 --- /dev/null +++ b/src/dlls/Makefile @@ -0,0 +1,56 @@ +CPP = gcc +BASEFLAGS = -Dstricmp=strcasecmp -Dstrcmpi=strcasecmp +CPPFLAGS = ${BASEFLAGS} -m386 -O2 -w -I. -I../engine -I../common -I../pm_shared -I../../metamod + +OBJ = \ + agrunt.o \ + AI_BaseNPC_Schedule.o \ + animating.o \ + animation.o \ + apache.o \ + barney.o \ + bigmomma.o \ + bullsquid.o \ + cmbase.o \ + combat.o \ + controller.o \ + defaultai.o \ + dllapi.o \ + effects.o \ + ggrenade.o \ + h_ai.o \ + h_export.o \ + hassassin.o \ + headcrab.o \ + hgrunt.o \ + hornet.o \ + houndeye.o \ + islave.o \ + monster_api.o \ + monster_config.o \ + monsters.o \ + monsterstate.o \ + nodes.o \ + scientist.o \ + skill.o \ + sound.o \ + squeakgrenade.o \ + subs.o \ + talkmonster.o \ + util.o \ + weapons.o \ + zombie.o + +monster_mm_i386.so: ${OBJ} + ${CPP} -fPIC -shared -o $@ ${OBJ} + +clean: + -rm -f *.o + -rm -f *.so + +%.o: %.cpp + ${CPP} ${CPPFLAGS} -c $< -o $@ + +%.o: %.c + ${CPP} ${CPPFLAGS} -c $< -o $@ + diff --git a/src/dlls/activity.h b/src/dlls/activity.h new file mode 100644 index 0000000..c091712 --- /dev/null +++ b/src/dlls/activity.h @@ -0,0 +1,109 @@ +/*** +* +* Copyright (c) 1996-2001, 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 ( doesn't HAVE to be a wall or on a wall ) + 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, +} Activity; + + +typedef struct { + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + + +#endif //ACTIVITY_H diff --git a/src/dlls/activitymap.h b/src/dlls/activitymap.h new file mode 100644 index 0000000..0748551 --- /dev/null +++ b/src/dlls/activitymap.h @@ -0,0 +1,97 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ + +#define _A( a ) { a, #a } + +activity_map_t activity_map[] = +{ +_A( ACT_IDLE ), +_A( ACT_GUARD ), +_A( ACT_WALK ), +_A( ACT_RUN ), +_A( ACT_FLY ), +_A( ACT_SWIM ), +_A( ACT_HOP ), +_A( ACT_LEAP ), +_A( ACT_FALL ), +_A( ACT_LAND ), +_A( ACT_STRAFE_LEFT ), +_A( ACT_STRAFE_RIGHT ), +_A( ACT_ROLL_LEFT ), +_A( ACT_ROLL_RIGHT ), +_A( ACT_TURN_LEFT ), +_A( ACT_TURN_RIGHT ), +_A( ACT_CROUCH ), +_A( ACT_CROUCHIDLE ), +_A( ACT_STAND ), +_A( ACT_USE ), +_A( ACT_SIGNAL1 ), +_A( ACT_SIGNAL2 ), +_A( ACT_SIGNAL3 ), +_A( ACT_TWITCH ), +_A( ACT_COWER ), +_A( ACT_SMALL_FLINCH ), +_A( ACT_BIG_FLINCH ), +_A( ACT_RANGE_ATTACK1 ), +_A( ACT_RANGE_ATTACK2 ), +_A( ACT_MELEE_ATTACK1 ), +_A( ACT_MELEE_ATTACK2 ), +_A( ACT_RELOAD ), +_A( ACT_ARM ), +_A( ACT_DISARM ), +_A( ACT_EAT ), +_A( ACT_DIESIMPLE ), +_A( ACT_DIEBACKWARD ), +_A( ACT_DIEFORWARD ), +_A( ACT_DIEVIOLENT ), +_A( ACT_BARNACLE_HIT ), +_A( ACT_BARNACLE_PULL ), +_A( ACT_BARNACLE_CHOMP ), +_A( ACT_BARNACLE_CHEW ), +_A( ACT_SLEEP ), +_A( ACT_INSPECT_FLOOR ), +_A( ACT_INSPECT_WALL ), +_A( ACT_IDLE_ANGRY ), +_A( ACT_WALK_HURT ), +_A( ACT_RUN_HURT ), +_A( ACT_HOVER ), +_A( ACT_GLIDE ), +_A( ACT_FLY_LEFT ), +_A( ACT_FLY_RIGHT ), +_A( ACT_DETECT_SCENT ), +_A( ACT_SNIFF ), +_A( ACT_BITE ), +_A( ACT_THREAT_DISPLAY ), +_A( ACT_FEAR_DISPLAY ), +_A( ACT_EXCITED ), +_A( ACT_SPECIAL_ATTACK1 ), +_A( ACT_SPECIAL_ATTACK2 ), +_A( ACT_COMBAT_IDLE ), +_A( ACT_WALK_SCARED ), +_A( ACT_RUN_SCARED ), +_A( ACT_VICTORY_DANCE ), +_A( ACT_DIE_HEADSHOT ), +_A( ACT_DIE_CHESTSHOT ), +_A( ACT_DIE_GUTSHOT ), +_A( ACT_DIE_BACKSHOT ), +_A( ACT_FLINCH_HEAD ), +_A( ACT_FLINCH_CHEST ), +_A( ACT_FLINCH_STOMACH ), +_A( ACT_FLINCH_LEFTARM ), +_A( ACT_FLINCH_RIGHTARM ), +_A( ACT_FLINCH_LEFTLEG ), +_A( ACT_FLINCH_RIGHTLEG ), +0, NULL +}; diff --git a/src/dlls/agrunt.cpp b/src/dlls/agrunt.cpp new file mode 100644 index 0000000..e5debdf --- /dev/null +++ b/src/dlls/agrunt.cpp @@ -0,0 +1,1100 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// Agrunt - Dominant, warlike alien grunt monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "weapons.h" +#include "hornet.h" +#include "skill.h" + + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_AGRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_AGRUNT_THREAT_DISPLAY, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_AGRUNT_SETUP_HIDE_ATTACK = LAST_COMMON_TASK + 1, + TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE, +}; + +int iAgruntMuzzleFlash; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define AGRUNT_AE_HORNET1 ( 1 ) +#define AGRUNT_AE_HORNET2 ( 2 ) +#define AGRUNT_AE_HORNET3 ( 3 ) +#define AGRUNT_AE_HORNET4 ( 4 ) +#define AGRUNT_AE_HORNET5 ( 5 ) +// some events are set up in the QC file that aren't recognized by the code yet. +#define AGRUNT_AE_PUNCH ( 6 ) +#define AGRUNT_AE_BITE ( 7 ) + +#define AGRUNT_AE_LEFT_FOOT ( 10 ) +#define AGRUNT_AE_RIGHT_FOOT ( 11 ) + +#define AGRUNT_AE_LEFT_PUNCH ( 12 ) +#define AGRUNT_AE_RIGHT_PUNCH ( 13 ) + +#define AGRUNT_MELEE_DIST 100 + +const char *CMAGrunt::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CMAGrunt::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CMAGrunt::pAttackSounds[] = +{ + "agrunt/ag_attack1.wav", + "agrunt/ag_attack2.wav", + "agrunt/ag_attack3.wav", +}; + +const char *CMAGrunt::pDieSounds[] = +{ + "agrunt/ag_die1.wav", + "agrunt/ag_die4.wav", + "agrunt/ag_die5.wav", +}; + +const char *CMAGrunt::pPainSounds[] = +{ + "agrunt/ag_pain1.wav", + "agrunt/ag_pain2.wav", + "agrunt/ag_pain3.wav", + "agrunt/ag_pain4.wav", + "agrunt/ag_pain5.wav", +}; + +const char *CMAGrunt::pIdleSounds[] = +{ + "agrunt/ag_idle1.wav", + "agrunt/ag_idle2.wav", + "agrunt/ag_idle3.wav", + "agrunt/ag_idle4.wav", +}; + +const char *CMAGrunt::pAlertSounds[] = +{ + "agrunt/ag_alert1.wav", + "agrunt/ag_alert3.wav", + "agrunt/ag_alert4.wav", + "agrunt/ag_alert5.wav", +}; + +//========================================================= +// IRelationship - overridden because Human Grunts are +// Alien Grunt's nemesis. +//========================================================= +int CMAGrunt::IRelationship ( CMBaseEntity *pTarget ) +{ + if ( strcmp(STRING(pTarget->pev->model), "models/hgrunt.mdl") == 0 ) + { + return R_NM; + } + + return CMBaseMonster :: IRelationship( pTarget ); +} + +//========================================================= +// ISoundMask +//========================================================= +int CMAGrunt :: ISoundMask ( void ) +{ + return 0; +} + +//========================================================= +// TraceAttack +//========================================================= +void CMAGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB))) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + Vector vecTracerDir = vecDir; + + vecTracerDir.x += RANDOM_FLOAT( -0.3, 0.3 ); + vecTracerDir.y += RANDOM_FLOAT( -0.3, 0.3 ); + vecTracerDir.z += RANDOM_FLOAT( -0.3, 0.3 ); + + vecTracerDir = vecTracerDir * -512; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, ptr->vecEndPos ); + WRITE_BYTE( TE_TRACER ); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( vecTracerDir.x ); + WRITE_COORD( vecTracerDir.y ); + WRITE_COORD( vecTracerDir.z ); + MESSAGE_END(); + } + + flDamage -= 20; + if (flDamage <= 0) + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + else + { + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + + AddMultiDamage( pevAttacker, this->edict(), flDamage, bitsDamageType ); +} + +//========================================================= +// StopTalking - won't speak again for 10-20 seconds. +//========================================================= +void CMAGrunt::StopTalking( void ) +{ + m_flNextWordTime = m_flNextSpeakTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10); +} + +//========================================================= +// ShouldSpeak - Should this agrunt be talking? +//========================================================= +BOOL CMAGrunt::ShouldSpeak( void ) +{ + if ( m_flNextSpeakTime > gpGlobals->time ) + { + // my time to talk is still in the future. + return FALSE; + } + + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) + { + // if gagged, don't talk outside of combat. + // if not going to talk because of this, put the talk time + // into the future a bit, so we don't talk immediately after + // going into combat + m_flNextSpeakTime = gpGlobals->time + 3; + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CMAGrunt :: PrescheduleThink ( void ) +{ + if ( ShouldSpeak() ) + { + if ( m_flNextWordTime < gpGlobals->time ) + { + int num = -1; + + do + { + num = RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1); + } while( num == m_iLastWord ); + + m_iLastWord = num; + + // play a new sound + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pIdleSounds[ num ], 1.0, ATTN_NORM ); + + // is this word our last? + if ( RANDOM_LONG( 1, 10 ) <= 1 ) + { + // stop talking. + StopTalking(); + } + else + { + m_flNextWordTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 1 ); + } + } + } +} + +//========================================================= +// DieSound +//========================================================= +void CMAGrunt :: DeathSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pDieSounds[RANDOM_LONG(0,ARRAYSIZE(pDieSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// AlertSound +//========================================================= +void CMAGrunt :: AlertSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pAlertSounds[RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// AttackSound +//========================================================= +void CMAGrunt :: AttackSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pAttackSounds[RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// PainSound +//========================================================= +void CMAGrunt :: PainSound ( void ) +{ + if ( m_flNextPainTime > gpGlobals->time ) + { + return; + } + + m_flNextPainTime = gpGlobals->time + 0.6; + + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pPainSounds[RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMAGrunt :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMAGrunt :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 110; + break; + default: ys = 100; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CMAGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case AGRUNT_AE_HORNET1: + case AGRUNT_AE_HORNET2: + case AGRUNT_AE_HORNET3: + case AGRUNT_AE_HORNET4: + case AGRUNT_AE_HORNET5: + { + // m_vecEnemyLKP should be center of enemy body + Vector vecArmPos, vecArmDir; + Vector vecDirToEnemy; + Vector angDir; + + if (HasConditions( bits_COND_SEE_ENEMY)) + { + vecDirToEnemy = ( ( m_vecEnemyLKP ) - pev->origin ); + angDir = UTIL_VecToAngles( vecDirToEnemy ); + vecDirToEnemy = vecDirToEnemy.Normalize(); + } + else + { + angDir = pev->angles; + UTIL_MakeAimVectors( angDir ); + vecDirToEnemy = gpGlobals->v_forward; + } + + pev->effects = EF_MUZZLEFLASH; + + // make angles +-180 + if (angDir.x > 180) + { + angDir.x = angDir.x - 360; + } + + SetBlending( 0, angDir.x ); + GetAttachment( 0, vecArmPos, vecArmDir ); + + vecArmPos = vecArmPos + vecDirToEnemy * 32; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecArmPos ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecArmPos.x ); // pos + WRITE_COORD( vecArmPos.y ); + WRITE_COORD( vecArmPos.z ); + WRITE_SHORT( iAgruntMuzzleFlash ); // model + WRITE_BYTE( 6 ); // size * 10 + WRITE_BYTE( 128 ); // brightness + MESSAGE_END(); + +//jlb CMBaseEntity *pHornet = CMBaseEntity::Create( "hornet", vecArmPos, UTIL_VecToAngles( vecDirToEnemy ), edict() ); + CMHornet *pHornet = CreateClassPtr((CMHornet *)NULL); + + if (pHornet != NULL) + { + pHornet->pev->origin = vecArmPos; + pHornet->pev->angles = UTIL_VecToAngles( vecDirToEnemy ); + pHornet->pev->owner = edict(); + + // Initialize these for entities who don't link to the world + pHornet->pev->absmin = pHornet->pev->origin - Vector(1,1,1); + pHornet->pev->absmax = pHornet->pev->origin + Vector(1,1,1); + + pHornet->Spawn(); + + UTIL_MakeVectors ( pHornet->pev->angles ); + pHornet->pev->velocity = gpGlobals->v_forward * 300; + + CMBaseMonster *pHornetMonster = pHornet->MyMonsterPointer(); + + if ( pHornetMonster ) + { + pHornetMonster->m_hEnemy = m_hEnemy; + } + } + } + break; + + case AGRUNT_AE_LEFT_FOOT: + switch (RANDOM_LONG(0,1)) + { + // left foot + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", 1, ATTN_NORM, 0, 70 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", 1, ATTN_NORM, 0, 70 ); break; + } + break; + case AGRUNT_AE_RIGHT_FOOT: + // right foot + switch (RANDOM_LONG(0,1)) + { + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", 1, ATTN_NORM, 0, 70 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", 1, ATTN_NORM, 0 ,70); break; + } + break; + + case AGRUNT_AE_LEFT_PUNCH: + { + edict_t *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); + + if ( pHurt ) + { + pHurt->v.punchangle.y = -25; + pHurt->v.punchangle.x = 8; + + // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. + if ( UTIL_IsPlayer(pHurt) ) + { + // this is a player. Knock him around. + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_right * 250; + } + + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + + if (UTIL_IsPlayer(pHurt)) + SpawnBlood(vecArmPos, BLOOD_COLOR_RED, 25); + else if (pHurt->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pHurt)); + SpawnBlood(vecArmPos, pMonster->BloodColor(), 25);// a little surface blood. + } + } + 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) ); + } + } + break; + + case AGRUNT_AE_RIGHT_PUNCH: + { + edict_t *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); + + if ( pHurt ) + { + pHurt->v.punchangle.y = 25; + pHurt->v.punchangle.x = 8; + + // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. + if ( UTIL_IsPlayer(pHurt) ) + { + // this is a player. Knock him around. + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_right * -250; + } + + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + + if (UTIL_IsPlayer(pHurt)) + SpawnBlood(vecArmPos, BLOOD_COLOR_RED, 25); + else if (pHurt->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pHurt)); + SpawnBlood(vecArmPos, pMonster->BloodColor(), 25);// a little surface blood. + } + } + 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) ); + } + } + break; + + default: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMAGrunt :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/agrunt.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.agruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = 0; + + m_HackedGunPos = Vector( 24, 64, 48 ); + + m_flNextSpeakTime = m_flNextWordTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10); + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMAGrunt :: Precache() +{ + int i; + + PRECACHE_MODEL("models/agrunt.mdl"); + + 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( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pDieSounds ); i++ ) + PRECACHE_SOUND((char *)pDieSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + + PRECACHE_SOUND( "hassault/hw_shoot1.wav" ); + + iAgruntMuzzleFlash = PRECACHE_MODEL( "sprites/muz4.spr" ); + + CMHornet hornet; + hornet.Precache(); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// Fail Schedule +//========================================================= +Task_t tlAGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slAGruntFail[] = +{ + { + tlAGruntFail, + ARRAYSIZE ( tlAGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AGrunt Fail" + }, +}; + +//========================================================= +// Combat Fail Schedule +//========================================================= +Task_t tlAGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slAGruntCombatFail[] = +{ + { + tlAGruntCombatFail, + ARRAYSIZE ( tlAGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AGrunt Combat Fail" + }, +}; + +//========================================================= +// 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 tlAGruntStandoff[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, +}; + +Schedule_t slAGruntStandoff[] = +{ + { + tlAGruntStandoff, + ARRAYSIZE ( tlAGruntStandoff ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + 0, + "Agrunt Standoff" + } +}; + +//========================================================= +// Suppress +//========================================================= +Task_t tlAGruntSuppressHornet[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slAGruntSuppress[] = +{ + { + tlAGruntSuppressHornet, + ARRAYSIZE ( tlAGruntSuppressHornet ), + 0, + 0, + "AGrunt Suppress Hornet", + }, +}; + +//========================================================= +// primary range attacks +//========================================================= +Task_t tlAGruntRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slAGruntRangeAttack1[] = +{ + { + tlAGruntRangeAttack1, + ARRAYSIZE ( tlAGruntRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE, + + 0, + "AGrunt Range Attack1" + }, +}; + + +Task_t tlAGruntHiddenRangeAttack1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_STANDOFF }, + { TASK_AGRUNT_SETUP_HIDE_ATTACK, 0 }, + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, 0 }, + { TASK_RANGE_ATTACK1_NOTURN, (float)0 }, +}; + +Schedule_t slAGruntHiddenRangeAttack[] = +{ + { + tlAGruntHiddenRangeAttack1, + ARRAYSIZE ( tlAGruntHiddenRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + 0, + "AGrunt Hidden Range Attack1" + }, +}; + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAGruntTakeCoverFromEnemy[] = +{ + { 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_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAGruntTakeCoverFromEnemy[] = +{ + { + tlAGruntTakeCoverFromEnemy, + ARRAYSIZE ( tlAGruntTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY, + 0, + "AGruntTakeCoverFromEnemy" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlAGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_AGRUNT_THREAT_DISPLAY }, + { TASK_WAIT, (float)0.2 }, + { TASK_AGRUNT_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_CROUCH }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, +}; + +Schedule_t slAGruntVictoryDance[] = +{ + { + tlAGruntVictoryDance, + ARRAYSIZE ( tlAGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "AGruntVictoryDance" + }, +}; + +//========================================================= +//========================================================= +Task_t tlAGruntThreatDisplay[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, +}; + +Schedule_t slAGruntThreatDisplay[] = +{ + { + tlAGruntThreatDisplay, + ARRAYSIZE ( tlAGruntThreatDisplay ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "AGruntThreatDisplay" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CMAGrunt ) +{ + slAGruntFail, + slAGruntCombatFail, + slAGruntStandoff, + slAGruntSuppress, + slAGruntRangeAttack1, + slAGruntHiddenRangeAttack, + slAGruntTakeCoverFromEnemy, + slAGruntVictoryDance, + slAGruntThreatDisplay, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMAGrunt, CMBaseMonster ); + +//========================================================= +// FCanCheckAttacks - this is overridden for alien grunts +// because they can use their smart weapons against unseen +// enemies. Base class doesn't attack anyone it can't see. +//========================================================= +BOOL CMAGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// CheckMeleeAttack1 - alien grunts zap the crap out of +// any enemy that gets too close. +//========================================================= +BOOL CMAGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( HasConditions ( bits_COND_SEE_ENEMY ) && flDist <= AGRUNT_MELEE_DIST && flDot >= 0.6 && m_hEnemy != NULL ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 +// +// !!!LATER - we may want to load balance this. Several +// tracelines are done, so we may not want to do this every +// server frame. Definitely not while firing. +//========================================================= +BOOL CMAGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( gpGlobals->time < m_flNextHornetAttackCheck ) + { + return m_fCanHornetAttack; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && flDist >= AGRUNT_MELEE_DIST && flDist <= 1024 && flDot >= 0.5 ) + { + TraceResult tr; + Vector vecArmPos, vecArmDir; + + // verify that a shot fired from the gun will hit the enemy before the world. + // !!!LATER - we may wish to do something different for projectile weapons as opposed to instant-hit + UTIL_MakeVectors( pev->angles ); + GetAttachment( 0, vecArmPos, vecArmDir ); +// UTIL_TraceLine( vecArmPos, vecArmPos + gpGlobals->v_forward * 256, ignore_monsters, ENT(pev), &tr); + UTIL_TraceLine( vecArmPos, UTIL_BodyTarget(m_hEnemy, vecArmPos), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 || (tr.pHit == m_hEnemy) ) + { + m_flNextHornetAttackCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); + m_fCanHornetAttack = TRUE; + return m_fCanHornetAttack; + } + } + + m_flNextHornetAttackCheck = gpGlobals->time + 0.2;// don't check for half second if this check wasn't successful + m_fCanHornetAttack = FALSE; + return m_fCanHornetAttack; +} + +//========================================================= +// StartTask +//========================================================= +void CMAGrunt :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 50, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "AGruntGetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + + case TASK_AGRUNT_SETUP_HIDE_ATTACK: + // alien grunt shoots hornets back out into the open from a concealed location. + // try to find a spot to throw that gives the smart weapon a good chance of finding the enemy. + // ideally, this spot is along a line that is perpendicular to a line drawn from the agrunt to the enemy. + + { + Vector vecCenter; + TraceResult tr; + BOOL fSkip; + + fSkip = FALSE; + vecCenter = Center(); + + UTIL_VecToAngles( m_vecEnemyLKP - pev->origin ); + + UTIL_TraceLine( Center() + gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin + gpGlobals->v_right * 128 ); + fSkip = TRUE; + TaskComplete(); + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() - gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin - gpGlobals->v_right * 128 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() + gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin + gpGlobals->v_right * 256 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() - gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin - gpGlobals->v_right * 256 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + TaskFail(); + } + } + break; + + default: + CMBaseMonster :: StartTask ( 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 *CMAGrunt :: GetSchedule ( void ) +{ + 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 CMBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType( SCHED_WAKE_ANGRY ); + } + + // zap player! + if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + AttackSound();// this is a total hack. Should be parto f the schedule + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // can attack + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + } + } + + return CMBaseMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CMAGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + return &slAGruntTakeCoverFromEnemy[ 0 ]; + break; + + case SCHED_RANGE_ATTACK1: + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + //normal attack + return &slAGruntRangeAttack1[ 0 ]; + } + else + { + // attack an unseen enemy + // return &slAGruntHiddenRangeAttack[ 0 ]; + return &slAGruntRangeAttack1[ 0 ]; + } + break; + + case SCHED_AGRUNT_THREAT_DISPLAY: + return &slAGruntThreatDisplay[ 0 ]; + break; + + case SCHED_AGRUNT_SUPPRESS: + return &slAGruntSuppress[ 0 ]; + break; + + case SCHED_STANDOFF: + return &slAGruntStandoff[ 0 ]; + break; + + case SCHED_VICTORY_DANCE: + return &slAGruntVictoryDance[ 0 ]; + break; + + case SCHED_FAIL: + // no fail schedule specified, so pick a good generic one. + { + if ( m_hEnemy != NULL ) + { + // I have an enemy + // !!!LATER - what if this enemy is really far away and i'm chasing him? + // this schedule will make me stop, face his last known position for 2 + // seconds, and then try to move again + return &slAGruntCombatFail[ 0 ]; + } + + return &slAGruntFail[ 0 ]; + } + break; + + } + + return CMBaseMonster :: GetScheduleOfType( Type ); +} + diff --git a/src/dlls/animating.cpp b/src/dlls/animating.cpp new file mode 100644 index 0000000..dff438e --- /dev/null +++ b/src/dlls/animating.cpp @@ -0,0 +1,306 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "animation.h" + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CMBaseAnimating :: 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; +} + +//========================================================= +// LookupActivity +//========================================================= +int CMBaseAnimating :: 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 CMBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivityHeaviest( pmodel, pev, activity ); +} + +//========================================================= +//========================================================= +int CMBaseAnimating :: LookupSequence ( const char *label ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupSequence( pmodel, label ); +} + + +//========================================================= +//========================================================= +void CMBaseAnimating :: 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 CMBaseAnimating :: GetSequenceFlags( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetSequenceFlags( pmodel, pev ); +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CMBaseAnimating :: 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 CMBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return SetController( pmodel, pev, iController, flValue ); +} + +//========================================================= +//========================================================= +void CMBaseAnimating :: 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 CMBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::SetBlending( pmodel, pev, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CMBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +void CMBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); +} + +//========================================================= +//========================================================= +int CMBaseAnimating :: 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 CMBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +void CMBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup, iValue ); +} + +int CMBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup ); +} + + +int CMBaseAnimating :: ExtractBbox( int sequence, float *mins, float *maxs ) +{ + return ::ExtractBbox( GET_MODEL_PTR( ENT(pev) ), sequence, mins, maxs ); +} + +//========================================================= +//========================================================= + +void CMBaseAnimating :: 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/src/dlls/animation.cpp b/src/dlls/animation.cpp new file mode 100644 index 0000000..83b138c --- /dev/null +++ b/src/dlls/animation.cpp @@ -0,0 +1,521 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "../common/nowin.h" + +typedef int BOOL; +#define TRUE 1 +#define FALSE 0 + +// hack into header files that we can ship +typedef int qboolean; +typedef unsigned char byte; +#include "../utils/common/mathlib.h" +#include "const.h" +#include "progdefs.h" +#include "edict.h" +#include "eiface.h" + +#include "studio.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#include "activitymap.h" + +#ifndef ANIMATION_H +#include "animation.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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr ) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if ( !pstudiohdr ) + { + ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" ); + return; + } + + VectorCopy ( pstudiohdr->eyeposition, vecEyePosition ); +} + +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_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 ) +{ + return 0; +} + + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + if ( index >= 0 ) + { + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (mstudioevent_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( (char *)(gpGlobals->pStringBase + ALLOC_STRING(pevent[i].options) ) ); + } + } + } +} + + + +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + int events = 0; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + pevent = (mstudioevent_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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + // find first controller that matches the index + for (int 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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return iGoalAnim; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_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" ); + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + if (iGroup > pstudiohdr->numbodyparts) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_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 ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + if (iGroup > pstudiohdr->numbodyparts) + return 0; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (pbodypart->nummodels <= 1) + return 0; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} \ No newline at end of file diff --git a/src/dlls/animation.h b/src/dlls/animation.h new file mode 100644 index 0000000..ec00ca4 --- /dev/null +++ b/src/dlls/animation.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2001, 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 ); + +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/src/dlls/apache.cpp b/src/dlls/apache.cpp new file mode 100644 index 0000000..6d3052d --- /dev/null +++ b/src/dlls/apache.cpp @@ -0,0 +1,977 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" +#include "explode.h" + + +#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix! +#define SF_NOWRECKAGE 0x08 + +void CMApache :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/apache.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.apacheHealth; + + m_flFieldOfView = -0.707; // 270 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0, 0xFF); + + InitBoneControllers(); + + if (pev->spawnflags & SF_WAITFORTRIGGER) + { + SetUse( StartupUse ); + } + else + { + SetThink( HuntThink ); + SetTouch( FlyTouch ); + pev->nextthink = gpGlobals->time + 1.0; + } + + m_iRockets = 10; + m_pBeam = NULL; + + m_pGoalEnt = NULL; + m_flGoalSpeed = 0.0f; + + m_flForce = 0.0f; + m_flNextRocket = 0.0f; + + Vector m_vecTarget = g_vecZero; + Vector m_posTarget = g_vecZero; + + Vector m_vecDesired = g_vecZero; + Vector m_posDesired = g_vecZero; + + Vector m_vecGoal = g_vecZero; + Vector m_angGun = g_vecZero; + + m_flLastSeen = 0.0f; + m_flPrevSeen = 0.0f; + + m_iSoundState = 0; +} + + +void CMApache::Precache( void ) +{ + 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" ); + + CMApacheHVR apache_rocket; + apache_rocket.Precache(); +} + + + +void CMApache::NullThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CMApache::StartupUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ) +{ + SetThink( HuntThink ); + SetTouch( FlyTouch ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse( NULL ); +} + +void CMApache :: 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( DyingThink ); + SetTouch( CrashTouch ); + pev->nextthink = gpGlobals->time + 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 CMApache :: DyingThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_flNextRocket > gpGlobals->time ) + { + if (g_sModelIndexFireball == 0) + g_sModelIndexFireball = PRECACHE_MODEL ("sprites/zerogxplode.spr"); // fireball + + // random explosions + MESSAGE_BEGIN( MSG_PVS, SVC_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(); + + if (g_sModelIndexSmoke == 0) + g_sModelIndexSmoke = PRECACHE_MODEL ("sprites/steam1.spr"); // smoke + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_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, SVC_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; + pev->nextthink = gpGlobals->time + 0.2; + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_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, SVC_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, SVC_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, SVC_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->spawnflags & SF_NOWRECKAGE) && */(pev->flags & FL_ONGROUND)) + { +/*jlb + CMBaseEntity *pWreckage = Create( "cycler_wreckage", pev->origin, pev->angles ); + // SET_MODEL( ENT(pWreckage->pev), STRING(pev->model) ); + UTIL_SetSize( pWreckage->pev, Vector( -200, -200, -128 ), Vector( 200, 200, -32 ) ); + pWreckage->pev->frame = pev->frame; + pWreckage->pev->sequence = pev->sequence; + pWreckage->pev->framerate = 0; + pWreckage->pev->dmgtime = gpGlobals->time + 5; +jlb*/ + } + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_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( SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + + +void CMApache::FlyTouch( edict_t *pOther ) +{ + // bounce if we hit something solid + if ( pOther->v.solid == SOLID_BSP) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + // UNDONE, do a real bounce + pev->velocity = pev->velocity + tr.vecPlaneNormal * (pev->velocity.Length() + 200); + } +} + + +void CMApache::CrashTouch( edict_t *pOther ) +{ + // only crash if we hit something solid + if ( pOther->v.solid == SOLID_BSP) + { + SetTouch( NULL ); + m_flNextRocket = gpGlobals->time; + pev->nextthink = gpGlobals->time; + } +} + + + +void CMApache :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + +void CMApache :: HuntThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 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->v.origin; + UTIL_MakeAimVectors( m_pGoalEnt->v.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 (UTIL_FVisible( m_hEnemy, this->edict() )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + m_posTarget = UTIL_Center( m_hEnemy ); + } + 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->v.target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->v.origin; + UTIL_MakeAimVectors( m_pGoalEnt->v.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 CMApache :: 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 + { + edict_t *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->v.velocity, (pPlayer->v.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 CMApache :: 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, SVC_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(); + +//jlb CMBaseEntity *pRocket = CMBaseEntity::Create( "hvr_rocket", vecSrc, pev->angles, edict() ); + CMApacheHVR *pRocket = CreateClassPtr((CMApacheHVR *)NULL); + + if (pRocket) + { + pRocket->pev->origin = vecSrc; + pRocket->pev->angles = pev->angles; + pRocket->pev->owner = edict(); + + // Initialize these for entities who don't link to the world + pRocket->pev->absmin = pRocket->pev->origin - Vector(1,1,1); + pRocket->pev->absmax = pRocket->pev->origin + Vector(1,1,1); + + pRocket->Spawn(); + + pRocket->pev->velocity = pev->velocity + gpGlobals->v_forward * 100; + } + + m_iRockets--; + + side = - side; +} + + + +BOOL CMApache :: 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_MONSTER_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->edict() ); + m_pBeam = NULL; + } + } + return FALSE; +} + + + +void CMApache :: ShowDamage( void ) +{ + if (m_iDoSmokePuff > 0 || RANDOM_LONG(0,99) > pev->health) + { + MESSAGE_BEGIN( MSG_PVS, SVC_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 CMApache :: 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 CMBaseEntity::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + + +void CMApache::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->edict(), 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 ); + } +} + + +void CMApacheHVR :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/HVR.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( IgniteThink ); + SetTouch( ExplodeTouch ); + + UTIL_MakeAimVectors( pev->angles ); + m_vecForward = gpGlobals->v_forward; + pev->gravity = 0.5; + + pev->nextthink = gpGlobals->time + 0.1; + + pev->dmg = 150; +} + + +void CMApacheHVR :: Precache( void ) +{ + PRECACHE_MODEL("models/HVR.mdl"); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + PRECACHE_SOUND ("weapons/rocket1.wav"); +} + + +void CMApacheHVR :: IgniteThink( void ) +{ + // pev->movetype = MOVETYPE_TOSS; + + // pev->movetype = MOVETYPE_FLY; + pev->effects |= EF_LIGHT; + + // make rocket sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 ); + + // rocket trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(m_iTrail ); // model + WRITE_BYTE( 15 ); // life + WRITE_BYTE( 5 ); // width + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + + MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS) + + // set to accelerate + SetThink( AccelerateThink ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CMApacheHVR :: AccelerateThink( void ) +{ + // check world boundaries + if (pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + UTIL_Remove( this->edict() ); + return; + } + + // accelerate + float flSpeed = pev->velocity.Length(); + if (flSpeed < 1800) + { + pev->velocity = pev->velocity + m_vecForward * 200; + } + + // re-aim + pev->angles = UTIL_VecToAngles( pev->velocity ); + + pev->nextthink = gpGlobals->time + 0.1; +} + + +#endif \ No newline at end of file diff --git a/src/dlls/barney.cpp b/src/dlls/barney.cpp new file mode 100644 index 0000000..c7a4650 --- /dev/null +++ b/src/dlls/barney.cpp @@ -0,0 +1,666 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "weapons.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 + + +//========================================================= +// 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, + 0, + "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, + 0, + "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, + + 0, + "IdleStand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CMBarney ) +{ + slBaFollow, + slBarneyEnemyDraw, + slBaFaceTarget, + slIdleBaStand, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CMBarney, CMTalkMonster ); + +void CMBarney :: StartTask( Task_t *pTask ) +{ + CMTalkMonster::StartTask( pTask ); +} + +void CMBarney :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + if (m_hEnemy != NULL && (UTIL_IsPlayer(m_hEnemy))) + { + pev->framerate = 1.5; + } + CMTalkMonster::RunTask( pTask ); + break; + default: + CMTalkMonster::RunTask( pTask ); + break; + } +} + + + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CMBarney :: ISoundMask ( void) +{ + return 0; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMBarney :: Classify ( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// ALertSound - barney says "Freeze!" +//========================================================= +void CMBarney :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + if ( FOkToSpeak() ) + { + 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 CMBarney :: 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 CMBarney :: 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 ); + + edict_t *pEnemy = m_hEnemy; + + Vector shootTarget = ( (UTIL_BodyTarget( pEnemy, shootOrigin ) - pEnemy->v.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) && (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 CMBarney :: 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; + + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, BULLET_MONSTER_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 ); + + // UNDONE: Reload? + m_cAmmoLoaded--;// take away a bullet! +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CMBarney :: 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 = BARNEY_BODY_GUNDRAWN; + m_fGunDrawn = TRUE; + break; + + case BARNEY_AE_HOLSTER: + // change bodygroup to replace gun in holster + pev->body = BARNEY_BODY_GUNHOLSTERED; + m_fGunDrawn = FALSE; + break; + + default: + CMTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CMBarney :: Spawn() +{ + Precache( ); + + // every new barney must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + 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; + pev->health = gSkillData.barneyHealth; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_FULL; + m_MonsterState = MONSTERSTATE_NONE; + + pev->body = 0; // gun in holster + m_fGunDrawn = FALSE; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMBarney :: Precache() +{ + 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"); + + CMTalkMonster::Precache(); +} + +// Init talk data +void CMBarney :: TalkInit() +{ + CMTalkMonster::TalkInit(); + + // scientists speach group names (group names are in sentences.txt) + + m_szGrp[TLK_ANSWER] = "BA_ANSWER"; + m_szGrp[TLK_QUESTION] = "BA_QUESTION"; + m_szGrp[TLK_IDLE] = "BA_IDLE"; + m_szGrp[TLK_STARE] = "BA_STARE"; + m_szGrp[TLK_USE] = "BA_OK"; + m_szGrp[TLK_UNUSE] = "BA_WAIT"; + 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; +} + + +static BOOL IsFacing( entvars_t *pevTest, const Vector &reference ) +{ + Vector vecDir = (reference - pevTest->origin); + vecDir.z = 0; + vecDir = vecDir.Normalize(); + Vector forward, angle; + angle = pevTest->v_angle; + 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; +} + + +int CMBarney :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // make sure friends talk about it if player hurts talkmonsters... + int ret = CMTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + if ( !IsAlive() || pev->deadflag == DEAD_DYING ) + return ret; + + if ( m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT) ) + { + // 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 ) && UTIL_IsPlayer(m_hEnemy) ) + { + Remember( bits_MEMORY_PROVOKED ); + } + } + + return ret; +} + + +//========================================================= +// PainSound +//========================================================= +void CMBarney :: 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 CMBarney :: 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 CMBarney::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; + } + + CMTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +void CMBarney::Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( pev->body < BARNEY_BODY_GUNGONE ) + {// drop the gun! + Vector vecGunPos; + Vector vecGunAngles; + + pev->body = BARNEY_BODY_GUNGONE; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + } + + SetUse( NULL ); + CMTalkMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +Schedule_t* CMBarney :: 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 = CMTalkMonster::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 = CMTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + { + // just look straight ahead. + return slIdleBaStand; + } + else + return psched; + } + + return CMTalkMonster::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 *CMBarney :: GetSchedule ( void ) +{ + if ( HasConditions( bits_COND_ENEMY_DEAD ) && FOkToSpeak() ) + { + 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 CMBaseMonster :: 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 ( !UTIL_IsAlive(m_hTargetEnt) ) + { + // 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 CMTalkMonster::GetSchedule(); +} + +MONSTERSTATE CMBarney :: GetIdealState ( void ) +{ + return CMTalkMonster::GetIdealState(); +} + + diff --git a/src/dlls/bigmomma.cpp b/src/dlls/bigmomma.cpp new file mode 100644 index 0000000..4f116eb --- /dev/null +++ b/src/dlls/bigmomma.cpp @@ -0,0 +1,1194 @@ +/*** +* +* Copyright (c) 1996-2001, 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 ) + +//========================================================= +// monster template +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "decals.h" + +#define SF_INFOBM_RUN 0x0001 +#define SF_INFOBM_WAIT 0x0002 + +// AI Nodes for Big Momma +class CMInfoBM : public CMPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData* pkvd ); + + // name in pev->targetname + // next in pev->target + // radius in pev->scale + // health in pev->health + // Reach target in pev->message + // Reach delay in pev->speed + // Reach sequence in pev->netname + + int m_preSequence; +}; + +void CMInfoBM::Spawn( void ) +{ +} + +void CMInfoBM::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachdelay")) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachtarget")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachsequence")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "presequence")) + { + m_preSequence = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CMPointEntity::KeyValue( pkvd ); +} + +//========================================================= +// Mortar shot entity +//========================================================= +class CMBMortar : public CMBaseEntity +{ +public: + void Spawn( void ); + + static CMBMortar *Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity ); + void Touch( edict_t *pOther ); + void EXPORT Animate( void ); + + int m_maxFrame; +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BIG_AE_STEP1 1 // Footstep left +#define BIG_AE_STEP2 2 // Footstep right +#define BIG_AE_STEP3 3 // Footstep back left +#define BIG_AE_STEP4 4 // Footstep back right +#define BIG_AE_SACK 5 // Sack slosh +#define BIG_AE_DEATHSOUND 6 // Death sound + +#define BIG_AE_MELEE_ATTACKBR 8 // Leg attack +#define BIG_AE_MELEE_ATTACKBL 9 // Leg attack +#define BIG_AE_MELEE_ATTACK1 10 // Leg attack +#define BIG_AE_MORTAR_ATTACK1 11 // Launch a mortar +#define BIG_AE_LAY_CRAB 12 // Lay a headcrab +#define BIG_AE_JUMP_FORWARD 13 // Jump up and forward +#define BIG_AE_SCREAM 14 // alert sound +#define BIG_AE_PAIN_SOUND 15 // pain sound +#define BIG_AE_ATTACK_SOUND 16 // attack sound +#define BIG_AE_BIRTH_SOUND 17 // birth sound +#define BIG_AE_EARLY_TARGET 50 // Fire target early + + + +// User defined conditions +#define bits_COND_NODE_SEQUENCE ( bits_COND_SPECIAL1 ) // pev->netname contains the name of a sequence to play + +// Attack distance constants +#define BIG_ATTACKDIST 170 +#define BIG_MORTARDIST 800 +#define BIG_MAXCHILDREN 20 // Max # of live headcrab children + + +#define bits_MEMORY_CHILDPAIR (bits_MEMORY_CUSTOM1) +#define bits_MEMORY_ADVANCE_NODE (bits_MEMORY_CUSTOM2) +#define bits_MEMORY_COMPLETED_NODE (bits_MEMORY_CUSTOM3) +#define bits_MEMORY_FIRED_NODE (bits_MEMORY_CUSTOM4) + +int gSpitSprite, gSpitDebrisSprite; +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ); +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ); + + +// UNDONE: +// +//#define BIG_CHILDCLASS "monster_babycrab" + +const char *CMBigMomma::pChildDieSounds[] = +{ + "gonarch/gon_childdie1.wav", + "gonarch/gon_childdie2.wav", + "gonarch/gon_childdie3.wav", +}; + +const char *CMBigMomma::pSackSounds[] = +{ + "gonarch/gon_sack1.wav", + "gonarch/gon_sack2.wav", + "gonarch/gon_sack3.wav", +}; + +const char *CMBigMomma::pDeathSounds[] = +{ + "gonarch/gon_die1.wav", +}; + +const char *CMBigMomma::pAttackSounds[] = +{ + "gonarch/gon_attack1.wav", + "gonarch/gon_attack2.wav", + "gonarch/gon_attack3.wav", +}; +const char *CMBigMomma::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CMBigMomma::pBirthSounds[] = +{ + "gonarch/gon_birth1.wav", + "gonarch/gon_birth2.wav", + "gonarch/gon_birth3.wav", +}; + +const char *CMBigMomma::pAlertSounds[] = +{ + "gonarch/gon_alert1.wav", + "gonarch/gon_alert2.wav", + "gonarch/gon_alert3.wav", +}; + +const char *CMBigMomma::pPainSounds[] = +{ + "gonarch/gon_pain2.wav", + "gonarch/gon_pain4.wav", + "gonarch/gon_pain5.wav", +}; + +const char *CMBigMomma::pFootSounds[] = +{ + "gonarch/gon_step1.wav", + "gonarch/gon_step2.wav", + "gonarch/gon_step3.wav", +}; + + +int CMBigMomma :: GetNodeSequence( void ) +{ + edict_t *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->v.netname; // netname holds node sequence + } + return 0; +} + +int CMBigMomma :: GetNodePresequence( void ) +{ + if (!m_hTargetEnt || (m_hTargetEnt->v.euser4 == NULL)) + return 0; + + CMInfoBM *pTarget = (CMInfoBM *)CMBaseEntity::Instance(m_hTargetEnt); + if ( pTarget ) + { + return pTarget->m_preSequence; + } + return 0; +} + +float CMBigMomma :: GetNodeDelay( void ) +{ + edict_t *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->v.speed; // Speed holds node delay + } + return 0; +} + +float CMBigMomma :: GetNodeRange( void ) +{ + edict_t *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->v.scale; // Scale holds node delay + } + return 1e6; +} + +float CMBigMomma :: GetNodeYaw( void ) +{ + edict_t *pTarget = m_hTargetEnt; + if ( pTarget ) + { + if ( pTarget->v.angles.y != 0 ) + return pTarget->v.angles.y; + } + return pev->angles.y; +} + +BOOL CMBigMomma :: CanLayCrab( void ) +{ + if ( m_crabTime < gpGlobals->time && m_crabCount < BIG_MAXCHILDREN ) + { + // Don't spawn crabs inside each other + Vector mins = pev->origin - Vector( 32, 32, 0 ); + Vector maxs = pev->origin + Vector( 32, 32, 0 ); + + edict_t *pList[2]; + int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != edict() ) // Don't hurt yourself! + return FALSE; + } + return TRUE; + } + + return FALSE; +} + +void CMBigMomma :: KeyValue( KeyValueData *pkvd ) +{ +#if 0 + if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else +#endif + CMBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMBigMomma :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMBigMomma :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 100; + break; + default: + ys = 90; + } + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CMBigMomma :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + case BIG_AE_MELEE_ATTACKBL: + case BIG_AE_MELEE_ATTACK1: + { + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + Vector center = pev->origin + forward * 128; + Vector mins = center - Vector( 64, 64, 0 ); + Vector maxs = center + Vector( 64, 64, 64 ); + + edict_t *pList[8]; + int count = UTIL_EntitiesInBox( pList, 8, mins, maxs, FL_MONSTER|FL_CLIENT ); + edict_t *pHurt = NULL; + + for ( int i = 0; i < count && !pHurt; i++ ) + { + if ( pList[i] != this->edict() ) + { + if ( pList[i]->v.owner != edict() ) + pHurt = pList[i]; + } + } + + if ( pHurt ) + { + if (UTIL_IsPlayer(pHurt)) + { + UTIL_TakeDamage( pHurt, pev, pev, gSkillData.bigmommaDmgSlash, DMG_CRUSH | DMG_SLASH ); + pHurt->v.punchangle.x = 15; + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + pHurt->v.velocity = pHurt->v.velocity + (forward * 150) + Vector(0,0,250) - (right * 200); + break; + + case BIG_AE_MELEE_ATTACKBL: + pHurt->v.velocity = pHurt->v.velocity + (forward * 150) + Vector(0,0,250) + (right * 200); + break; + + case BIG_AE_MELEE_ATTACK1: + pHurt->v.velocity = pHurt->v.velocity + (forward * 220) + Vector(0,0,200); + break; + } + + pHurt->v.flags &= ~FL_ONGROUND; + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else if (pHurt->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pHurt)); + + pMonster->TakeDamage( pev, pev, gSkillData.bigmommaDmgSlash, DMG_CRUSH | DMG_SLASH ); + pMonster->pev->punchangle.x = 15; + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + pMonster->pev->velocity = pMonster->pev->velocity + (forward * 150) + Vector(0,0,250) - (right * 200); + break; + + case BIG_AE_MELEE_ATTACKBL: + pMonster->pev->velocity = pMonster->pev->velocity + (forward * 150) + Vector(0,0,250) + (right * 200); + break; + + case BIG_AE_MELEE_ATTACK1: + pMonster->pev->velocity = pMonster->pev->velocity + (forward * 220) + Vector(0,0,200); + break; + } + + pMonster->pev->flags &= ~FL_ONGROUND; + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + } + break; + + case BIG_AE_SCREAM: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); + break; + + case BIG_AE_PAIN_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + break; + + case BIG_AE_ATTACK_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackSounds ); + break; + + case BIG_AE_BIRTH_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pBirthSounds ); + break; + + case BIG_AE_SACK: + if ( RANDOM_LONG(0,100) < 30 ) + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pSackSounds ); + break; + + case BIG_AE_DEATHSOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); + break; + + case BIG_AE_STEP1: // Footstep left + case BIG_AE_STEP3: // Footstep back left + EMIT_SOUND_ARRAY_DYN( CHAN_ITEM, pFootSounds ); + break; + + case BIG_AE_STEP4: // Footstep back right + case BIG_AE_STEP2: // Footstep right + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pFootSounds ); + break; + + case BIG_AE_MORTAR_ATTACK1: + LaunchMortar(); + break; + + case BIG_AE_LAY_CRAB: + LayHeadcrab(); + break; + + case BIG_AE_JUMP_FORWARD: + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (pev, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + pev->velocity = (gpGlobals->v_forward * 200) + gpGlobals->v_up * 500; + break; + + case BIG_AE_EARLY_TARGET: + { + edict_t *pTarget = m_hTargetEnt; + if ( pTarget && pTarget->v.message ) + FireTargets( STRING(pTarget->v.message), this->edict(), this->edict(), USE_TOGGLE, 0 ); + Remember( bits_MEMORY_FIRED_NODE ); + } + break; + + default: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +void CMBigMomma :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ +/* + if ( ptr->iHitgroup != 1 ) + { + // didn't hit the sack? + + 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 + } + else */ + if ( gpGlobals->time > m_painSoundTime ) + { + m_painSoundTime = gpGlobals->time + RANDOM_LONG(1, 3); + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + } + + CMBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +int CMBigMomma :: 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; + + if ( !HasMemory(bits_MEMORY_PATH_FINISHED) ) + { + if ( pev->health <= flDamage ) + { + pev->health = flDamage + 1; + Remember( bits_MEMORY_ADVANCE_NODE | bits_MEMORY_COMPLETED_NODE ); + ALERT( at_aiconsole, "BM: Finished node health!!!\n" ); + } + } + + return CMBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CMBigMomma :: LayHeadcrab( void ) +{ +// CMBaseEntity *pChild = CMBaseEntity::Create( BIG_CHILDCLASS, pev->origin, pev->angles, edict() ); + + CMBabyCrab *pChild = CreateClassPtr((CMBabyCrab *)NULL); + + if (pChild != NULL) + { + pChild->pev->origin = pev->origin; + pChild->pev->angles = pev->angles; + pChild->pev->owner = edict(); + + // Initialize these for entities who don't link to the world + pChild->pev->absmin = pChild->pev->origin - Vector(1,1,1); + pChild->pev->absmax = pChild->pev->origin + Vector(1,1,1); + + pChild->Spawn(); + + pChild->pev->spawnflags |= SF_MONSTER_FALL_TO_GROUND; + + // Is this the second crab in a pair? + if ( HasMemory( bits_MEMORY_CHILDPAIR ) ) + { + m_crabTime = gpGlobals->time + RANDOM_FLOAT( 5, 10 ); + Forget( bits_MEMORY_CHILDPAIR ); + } + else + { + m_crabTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 2.5 ); + Remember( bits_MEMORY_CHILDPAIR ); + } + + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,100), ignore_monsters, edict(), &tr); + UTIL_DecalTrace( &tr, DECAL_MOMMABIRTH ); + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBirthSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + m_crabCount++; + } +} + + + +void CMBigMomma::DeathNotice( entvars_t *pevChild ) +{ + if ( m_crabCount > 0 ) // Some babies may cross a transition, but we reset the count then + m_crabCount--; + if ( IsAlive() ) + { + // Make the "my baby's dead" noise! + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pChildDieSounds ); + } +} + + +void CMBigMomma::LaunchMortar( void ) +{ + m_mortarTime = gpGlobals->time + RANDOM_FLOAT( 2, 15 ); + + Vector startPos = pev->origin; + startPos.z += 180; + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pSackSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + CMBMortar *pBomb = CMBMortar::Shoot( edict(), startPos, pev->movedir ); + if (pBomb) + { + pBomb->pev->gravity = 1.0; + MortarSpray( startPos, Vector(0,0,1), gSpitSprite, 24 ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CMBigMomma :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/big_mom.mdl"); +// UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + UTIL_SetSize( pev, Vector( -64, -64, 0 ), Vector( 64, 64, 128 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = 150 * gSkillData.bigmommaHealthFactor; + pev->view_ofs = Vector ( 0, 0, 128 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.3;// 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 CMBigMomma :: Precache() +{ + PRECACHE_MODEL("models/big_mom.mdl"); + + PRECACHE_SOUND_ARRAY( pChildDieSounds ); + PRECACHE_SOUND_ARRAY( pSackSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pBirthSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pFootSounds ); + +// UTIL_PrecacheOther( BIG_CHILDCLASS ); + CMBabyCrab babycrab; + babycrab.Precache(); + + // TEMP: Squid + PRECACHE_MODEL("sprites/mommaspit.spr");// spit projectile. + gSpitSprite = PRECACHE_MODEL("sprites/mommaspout.spr");// client side spittle. + gSpitDebrisSprite = PRECACHE_MODEL("sprites/mommablob.spr" ); + + PRECACHE_SOUND( "bullchicken/bc_acid1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit2.wav" ); +} + + +void CMBigMomma::Activate( void ) +{ + if ( m_hTargetEnt == NULL ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Start 'er up +} + + +void CMBigMomma::NodeStart( int iszNextNode ) +{ + pev->netname = iszNextNode; + + edict_t *pTarget = NULL; + + if ( pev->netname ) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->netname) ); + + if ( !FNullEnt(pentTarget) ) + pTarget = pentTarget; + } + + + if ( !pTarget ) + { + ALERT( at_aiconsole, "BM: Finished the path!!\n" ); + Remember( bits_MEMORY_PATH_FINISHED ); + return; + } + Remember( bits_MEMORY_ON_PATH ); + m_hTargetEnt = pTarget; +} + + +void CMBigMomma::NodeReach( void ) +{ + edict_t *pTarget = m_hTargetEnt; + + Forget( bits_MEMORY_ADVANCE_NODE ); + + if ( !pTarget ) + return; + + if ( pTarget->v.health ) + pev->max_health = pev->health = pTarget->v.health * gSkillData.bigmommaHealthFactor; + + if ( !HasMemory( bits_MEMORY_FIRED_NODE ) ) + { + if ( pTarget->v.message ) + FireTargets( STRING(pTarget->v.message), this->edict(), this->edict(), USE_TOGGLE, 0 ); + } + Forget( bits_MEMORY_FIRED_NODE ); + + pev->netname = pTarget->v.target; + if ( pTarget->v.health == 0 ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Move on if no health at this node +} + + + // Slash +BOOL CMBigMomma::CheckMeleeAttack1( float flDot, float flDist ) +{ + if (flDot >= 0.7) + { + if ( flDist <= BIG_ATTACKDIST ) + return TRUE; + } + return FALSE; +} + + +// Lay a crab +BOOL CMBigMomma::CheckMeleeAttack2( float flDot, float flDist ) +{ + return CanLayCrab(); +} + + +// Mortar launch +BOOL CMBigMomma::CheckRangeAttack1( float flDot, float flDist ) +{ + if ( flDist <= BIG_MORTARDIST && m_mortarTime < gpGlobals->time ) + { + edict_t *pEnemy = m_hEnemy; + + if ( pEnemy ) + { + Vector startPos = pev->origin; + startPos.z += 180; + pev->movedir = VecCheckSplatToss( pev, startPos, UTIL_BodyTarget( pEnemy, pev->origin ), RANDOM_FLOAT( 150, 500 ) ); + if ( pev->movedir != g_vecZero ) + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +enum +{ + SCHED_BIG_NODE = LAST_COMMON_SCHEDULE + 1, + SCHED_NODE_FAIL, +}; + +enum +{ + TASK_MOVE_TO_NODE_RANGE = LAST_COMMON_TASK + 1, // Move within node range + TASK_FIND_NODE, // Find my next node + TASK_PLAY_NODE_PRESEQUENCE, // Play node pre-script + TASK_PLAY_NODE_SEQUENCE, // Play node script + TASK_PROCESS_NODE, // Fire targets, etc. + TASK_WAIT_NODE, // Wait at the node + TASK_NODE_DELAY, // Delay walking toward node for a bit. You've failed to get there + TASK_NODE_YAW, // Get the best facing direction for this node +}; + + +Task_t tlBigNode[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_NODE_FAIL }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_NODE, (float)0 }, // Find my next node + { TASK_PLAY_NODE_PRESEQUENCE,(float)0 }, // Play the pre-approach sequence if any + { TASK_MOVE_TO_NODE_RANGE, (float)0 }, // Move within node range + { TASK_STOP_MOVING, (float)0 }, + { TASK_NODE_YAW, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_NODE, (float)0 }, // Wait for node delay + { TASK_PLAY_NODE_SEQUENCE, (float)0 }, // Play the sequence if one exists + { TASK_PROCESS_NODE, (float)0 }, // Fire targets, etc. + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slBigNode[] = +{ + { + tlBigNode, + ARRAYSIZE ( tlBigNode ), + 0, + 0, + "Big Node" + }, +}; + + +Task_t tlNodeFail[] = +{ + { TASK_NODE_DELAY, (float)10 }, // Try to do something else for 10 seconds + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slNodeFail[] = +{ + { + tlNodeFail, + ARRAYSIZE ( tlNodeFail ), + 0, + 0, + "NodeFail" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CMBigMomma ) +{ + slBigNode, + slNodeFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMBigMomma, CMBaseMonster ); + + + + +Schedule_t *CMBigMomma::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_BIG_NODE: + return slBigNode; + break; + + case SCHED_NODE_FAIL: + return slNodeFail; + break; + } + + return CMBaseMonster::GetScheduleOfType( Type ); +} + + +BOOL CMBigMomma::ShouldGoToNode( void ) +{ + if ( HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( m_nodeTime < gpGlobals->time ) + return TRUE; + } + return FALSE; +} + + + +Schedule_t *CMBigMomma::GetSchedule( void ) +{ + if ( ShouldGoToNode() ) + { + return GetScheduleOfType( SCHED_BIG_NODE ); + } + + return CMBaseMonster::GetSchedule(); +} + + +void CMBigMomma::StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FIND_NODE: + { + edict_t *pTarget = m_hTargetEnt; + if ( !HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( pTarget ) + pev->netname = m_hTargetEnt->v.target; + } + NodeStart( pev->netname ); + TaskComplete(); + ALERT( at_aiconsole, "BM: Found node %s\n", STRING(pev->netname) ); + } + break; + + case TASK_NODE_DELAY: + m_nodeTime = gpGlobals->time + pTask->flData; + TaskComplete(); + ALERT( at_aiconsole, "BM: FAIL! Delay %.2f\n", pTask->flData ); + break; + + case TASK_PROCESS_NODE: + ALERT( at_aiconsole, "BM: Reached node %s\n", STRING(pev->netname) ); + NodeReach(); + TaskComplete(); + break; + + case TASK_PLAY_NODE_PRESEQUENCE: + case TASK_PLAY_NODE_SEQUENCE: + { + int sequence; + if ( pTask->iTask == TASK_PLAY_NODE_SEQUENCE ) + sequence = GetNodeSequence(); + else + sequence = GetNodePresequence(); + + ALERT( at_aiconsole, "BM: Playing node sequence %s\n", STRING(sequence) ); + if ( sequence ) + { + sequence = LookupSequence( STRING( sequence ) ); + if ( sequence != -1 ) + { + pev->sequence = sequence; + pev->frame = 0; + ResetSequenceInfo( ); + ALERT( at_aiconsole, "BM: Sequence %s\n", STRING(GetNodeSequence()) ); + return; + } + } + TaskComplete(); + } + break; + + case TASK_NODE_YAW: + pev->ideal_yaw = GetNodeYaw(); + TaskComplete(); + break; + + case TASK_WAIT_NODE: + m_flWait = gpGlobals->time + GetNodeDelay(); + if ( m_hTargetEnt->v.spawnflags & SF_INFOBM_WAIT ) + ALERT( at_aiconsole, "BM: Wait at node %s forever\n", STRING(pev->netname) ); + else + ALERT( at_aiconsole, "BM: Wait at node %s for %.2f\n", STRING(pev->netname), GetNodeDelay() ); + break; + + + case TASK_MOVE_TO_NODE_RANGE: + { + edict_t *pTarget = m_hTargetEnt; + if ( !pTarget ) + TaskFail(); + else + { + if ( (pTarget->v.origin - pev->origin).Length() < GetNodeRange() ) + TaskComplete(); + else + { + Activity act = ACT_WALK; + if ( pTarget->v.spawnflags & SF_INFOBM_RUN ) + act = ACT_RUN; + + m_vecMoveGoal = pTarget->v.origin; + if ( !MoveToTarget( act, 2 ) ) + { + TaskFail(); + } + } + } + } + ALERT( at_aiconsole, "BM: Moving to node %s\n", STRING(pev->netname) ); + + break; + + case TASK_MELEE_ATTACK1: + // Play an attack sound here + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAttackSounds), 1.0, ATTN_NORM, 0, PITCH_NORM ); + CMBaseMonster::StartTask( pTask ); + break; + + default: + CMBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CMBigMomma::RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_MOVE_TO_NODE_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( (distance < GetNodeRange()) || MovementIsComplete() ) + { + ALERT( at_aiconsole, "BM: Reached node!\n" ); + TaskComplete(); + RouteClear(); // Stop moving + } + } + } + + break; + + case TASK_WAIT_NODE: + if ( m_hTargetEnt != NULL && (m_hTargetEnt->v.spawnflags & SF_INFOBM_WAIT) ) + return; + + if ( gpGlobals->time > m_flWaitFinished ) + TaskComplete(); + ALERT( at_aiconsole, "BM: The WAIT is over!\n" ); + break; + + case TASK_PLAY_NODE_PRESEQUENCE: + case TASK_PLAY_NODE_SEQUENCE: + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + + default: + CMBaseMonster::RunTask( pTask ); + break; + } +} + + + +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = g_psv_gravity->value; + + // calculate the midpoint and apex of the 'triangle' + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,maxHeight), ignore_monsters, ENT(pev), &tr); + vecApex = tr.vecEndPos; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // Don't worry about actually hitting the target, this won't hurt us! + + // How high should the grenade travel (subtract 15 so the grenade doesn't hit the ceiling)? + float height = (vecApex.z - vecSpot1.z) - 15; + // How fast does the grenade need to travel to reach that height given gravity? + float speed = sqrt( 2 * flGravity * height ); + + // How much time does it take to get there? + float time = speed / flGravity; + vecGrenadeVel = (vecSpot2 - vecSpot1); + vecGrenadeVel.z = 0; + float distance = vecGrenadeVel.Length(); + + // Travel half the distance to the target in that time (apex is at the midpoint) + vecGrenadeVel = vecGrenadeVel * ( 0.5 / time ); + // Speed to offset gravity at the desired height + vecGrenadeVel.z = speed; + + return vecGrenadeVel; +} + + + + +// --------------------------------- +// +// Mortar +// +// --------------------------------- +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( position.x); // pos + WRITE_COORD( position.y); + WRITE_COORD( position.z); + WRITE_COORD( direction.x); // dir + WRITE_COORD( direction.y); + WRITE_COORD( direction.z); + WRITE_SHORT( spriteModel ); // model + WRITE_BYTE ( count ); // count + WRITE_BYTE ( 130 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); +} + + +// UNDONE: right now this is pretty much a copy of the squid spit with minor changes to the way it does damage +void CMBMortar:: Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->classname = MAKE_STRING( "bmortar" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/mommaspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + pev->dmgtime = gpGlobals->time + 0.4; +} + +void CMBMortar::Animate( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( gpGlobals->time > pev->dmgtime ) + { + pev->dmgtime = gpGlobals->time + 0.2; + MortarSpray( pev->origin, -pev->velocity.Normalize(), gSpitSprite, 3 ); + } + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +CMBMortar *CMBMortar::Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity ) +{ + CMBMortar *pSpit = CreateClassPtr( (CMBMortar *)NULL ); + if (pSpit) + { + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit->pev, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = pOwner; + pSpit->pev->scale = 2.5; + pSpit->SetThink ( Animate ); + pSpit->pev->nextthink = gpGlobals->time + 0.1; + } + return pSpit; +} + + +void CMBMortar::Touch( edict_t *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( UTIL_IsBSPModel(pOther) ) + { + + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_DecalTrace(&tr, DECAL_MOMMASPLAT); + } + else + { + tr.vecEndPos = pev->origin; + tr.vecPlaneNormal = -1 * pev->velocity.Normalize(); + } + // make some flecks + MortarSpray( tr.vecEndPos, tr.vecPlaneNormal, gSpitSprite, 24 ); + + entvars_t *pevOwner = NULL; + if ( pev->owner ) + pevOwner = VARS(pev->owner); + + RadiusDamage( pev->origin, pev, pevOwner, gSkillData.bigmommaDmgBlast, gSkillData.bigmommaRadiusBlast, CLASS_NONE, DMG_ACID ); + UTIL_Remove( this->edict() ); +} + +#endif diff --git a/src/dlls/bullsquid.cpp b/src/dlls/bullsquid.cpp new file mode 100644 index 0000000..02637e9 --- /dev/null +++ b/src/dlls/bullsquid.cpp @@ -0,0 +1,1174 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// bullsquid - big, spotty tentacle-mouthed meanie. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "nodes.h" +#include "effects.h" +#include "decals.h" + +#define SQUID_SPRINT_DIST 256 // how close the squid has to get before starting to sprint and refusing to swerve + +int iSquidSpitSprite; + + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_SQUID_HURTHOP = LAST_COMMON_SCHEDULE + 1, + SCHED_SQUID_SMELLFOOD, + SCHED_SQUID_SEECRAB, + SCHED_SQUID_EAT, + SCHED_SQUID_SNIFF_AND_EAT, + SCHED_SQUID_WALLOW, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_SQUID_HOPTURN = LAST_COMMON_TASK + 1, +}; + +//========================================================= +// Bullsquid's spit projectile +//========================================================= +class CSquidSpit : public CMBaseEntity +{ +public: + void Spawn( void ); + + static void Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + void SpitTouch( edict_t *pOther ); + void EXPORT Animate( void ); + + int m_maxFrame; +}; + +void CSquidSpit:: Spawn( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING( "squidspit" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/bigspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + +void CSquidSpit::Animate( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +void CSquidSpit::Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CSquidSpit *pSpit = CreateClassPtr( (CSquidSpit *)NULL ); + + if (pSpit == NULL) + return; + + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit->pev, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = ENT(pevOwner); + + pSpit->SetThink ( Animate ); + pSpit->pev->nextthink = gpGlobals->time + 0.1; + pSpit->SetTouch ( SpitTouch ); +} + +void CSquidSpit :: SpitTouch ( edict_t *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( !pOther->v.takedamage ) + { + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_DecalTrace(&tr, DECAL_SPIT1 + RANDOM_LONG(0,1)); + + // make some flecks + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( tr.vecEndPos.x); // pos + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_COORD( tr.vecPlaneNormal.x); // dir + WRITE_COORD( tr.vecPlaneNormal.y); + WRITE_COORD( tr.vecPlaneNormal.z); + WRITE_SHORT( iSquidSpitSprite ); // model + WRITE_BYTE ( 5 ); // count + WRITE_BYTE ( 30 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + } + else + { + if (UTIL_IsPlayer(pOther)) + UTIL_TakeDamage( pOther, pev, pev, gSkillData.bullsquidDmgSpit, DMG_GENERIC ); + else if (pOther->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + pMonster->TakeDamage ( pev, pev, gSkillData.bullsquidDmgSpit, DMG_GENERIC ); + } + } + + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BSQUID_AE_SPIT ( 1 ) +#define BSQUID_AE_BITE ( 2 ) +#define BSQUID_AE_BLINK ( 3 ) +#define BSQUID_AE_TAILWHIP ( 4 ) +#define BSQUID_AE_HOP ( 5 ) +#define BSQUID_AE_THROW ( 6 ) + + +//========================================================= +// IgnoreConditions +//========================================================= +int CMBullsquid::IgnoreConditions ( void ) +{ + int iIgnore = CMBaseMonster::IgnoreConditions(); + + if ( gpGlobals->time - m_flLastHurtTime <= 20 ) + { + // haven't been hurt in 20 seconds, so let the squid care about stink. + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + + if ( m_hEnemy != NULL ) + { + if ( strcmp(STRING(m_hEnemy->v.model), "models/headcrab.mdl") == 0 ) + { + // (Unless after a tasty headcrab) + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + } + + + return iIgnore; +} + +//========================================================= +// IRelationship - overridden for bullsquid so that it can +// be made to ignore its love of headcrabs for a while. +//========================================================= +int CMBullsquid::IRelationship ( CMBaseEntity *pTarget ) +{ + if ( gpGlobals->time - m_flLastHurtTime < 5 && (strcmp(STRING(pTarget->pev->model), "models/headcrab.mdl") == 0) ) + { + // if squid has been hurt in the last 5 seconds, and is getting relationship for a headcrab, + // tell squid to disregard crab. + return R_NO; + } + + return CMBaseMonster :: IRelationship ( pTarget ); +} + +//========================================================= +// TakeDamage - overridden for bullsquid so we can keep track +// of how much time has passed since it was last injured +//========================================================= +int CMBullsquid :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flDist; + Vector vecApex; + + // if the squid is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy, + // it will swerve. (whew). + if ( m_hEnemy != NULL && IsMoving() && pevAttacker == VARS((edict_t *)m_hEnemy) && gpGlobals->time - m_flLastHurtTime > 3 ) + { + flDist = ( pev->origin - m_hEnemy->v.origin ).Length2D(); + + if ( flDist > SQUID_SPRINT_DIST ) + { + flDist = ( pev->origin - m_Route[ m_iRouteIndex ].vecLocation ).Length2D();// reusing flDist. + + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist * 0.5, m_hEnemy, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR | bits_MF_DONT_SIMPLIFY ); + } + } + } + + if ( !strcmp(STRING(pev->model), "models/headcrab.mdl") == 0 ) + { + // don't forget about headcrabs if it was a headcrab that hurt the squid. + m_flLastHurtTime = gpGlobals->time; + } + + return CMBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CMBullsquid :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( IsMoving() && flDist >= 512 ) + { + // squid will far too far behind if he stops running to spit at this distance from the enemy. + return FALSE; + } + + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 && gpGlobals->time >= m_flNextSpitTime ) + { + if ( m_hEnemy != NULL ) + { + if ( fabs( pev->origin.z - m_hEnemy->v.origin.z ) > 256 ) + { + // don't try to spit at someone up really high or down really low. + return FALSE; + } + } + + if ( IsMoving() ) + { + // don't spit again for a long time, resume chasing enemy. + m_flNextSpitTime = gpGlobals->time + 5; + } + else + { + // not moving, so spit again pretty soon. + m_flNextSpitTime = gpGlobals->time + 0.5; + } + + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 - bullsquid is a big guy, so has a longer +// melee range than most monsters. This is the tailwhip attack +//========================================================= +BOOL CMBullsquid :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_hEnemy->v.health <= gSkillData.bullsquidDmgWhip && flDist <= 85 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 - bullsquid is a big guy, so has a longer +// melee range than most monsters. This is the bite attack. +// this attack will not be performed if the tailwhip attack +// is valid. +//========================================================= +BOOL CMBullsquid :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 85 && flDot >= 0.7 && !HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) // The player & bullsquid can be as much as their bboxes + { // apart (48 * sqrt(3)) and he can still attack (85 is a little more than 48*sqrt(3)) + return TRUE; + } + return FALSE; +} + +//========================================================= +// FValidateHintType +//========================================================= +BOOL CMBullsquid :: FValidateHintType ( short sHint ) +{ + int i; + + static short sSquidHints[] = + { + HINT_WORLD_HUMAN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sSquidHints ) ; i++ ) + { + if ( sSquidHints[ i ] == sHint ) + { + return TRUE; + } + } + + ALERT ( at_aiconsole, "Couldn't validate hint type" ); + return FALSE; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMBullsquid :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// IdleSound +//========================================================= +#define SQUID_ATTN_IDLE (float)1.5 +void CMBullsquid :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,4) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle1.wav", 1, SQUID_ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle2.wav", 1, SQUID_ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle3.wav", 1, SQUID_ATTN_IDLE ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle4.wav", 1, SQUID_ATTN_IDLE ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle5.wav", 1, SQUID_ATTN_IDLE ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CMBullsquid :: PainSound ( void ) +{ + int iPitch = RANDOM_LONG( 85, 120 ); + + switch ( RANDOM_LONG(0,3) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 2: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 3: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain4.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CMBullsquid :: AlertSound ( void ) +{ + int iPitch = RANDOM_LONG( 140, 160 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMBullsquid :: SetYawSpeed ( void ) +{ + int ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_WALK: ys = 90; break; + case ACT_RUN: ys = 90; break; + case ACT_IDLE: ys = 90; break; + case ACT_RANGE_ATTACK1: ys = 90; break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMBullsquid :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BSQUID_AE_SPIT: + { + if (m_hEnemy) + { + Vector vecSpitOffset; + Vector vecSpitDir; + + UTIL_MakeVectors ( pev->angles ); + + // !!!HACKHACK - the spot at which the spit originates (in front of the mouth) was measured in 3ds and hardcoded here. + // we should be able to read the position of bones at runtime for this info. + vecSpitOffset = ( gpGlobals->v_right * 8 + gpGlobals->v_forward * 37 + gpGlobals->v_up * 23 ); + vecSpitOffset = ( pev->origin + vecSpitOffset ); + vecSpitDir = ( ( m_hEnemy->v.origin + m_hEnemy->v.view_ofs ) - vecSpitOffset ).Normalize(); + + vecSpitDir.x += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.y += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.z += RANDOM_FLOAT( -0.05, 0 ); + + // do stuff for this event. + AttackSound(); + + // spew the spittle temporary ents. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpitOffset ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( vecSpitOffset.x); // pos + WRITE_COORD( vecSpitOffset.y); + WRITE_COORD( vecSpitOffset.z); + WRITE_COORD( vecSpitDir.x); // dir + WRITE_COORD( vecSpitDir.y); + WRITE_COORD( vecSpitDir.z); + WRITE_SHORT( iSquidSpitSprite ); // model + WRITE_BYTE ( 15 ); // count + WRITE_BYTE ( 210 ); // speed + WRITE_BYTE ( 25 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + + CSquidSpit::Shoot( pev, vecSpitOffset, vecSpitDir * 900 ); + } + } + break; + + case BSQUID_AE_BITE: + { + // SOUND HERE! + edict_t *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite, DMG_SLASH ); + + if ( pHurt ) + { + //pHurt->pev->punchangle.z = -15; + //pHurt->pev->punchangle.x = -45; + pHurt->v.velocity = pHurt->v.velocity - gpGlobals->v_forward * 100; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_up * 100; + } + } + break; + + case BSQUID_AE_TAILWHIP: + { + edict_t *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgWhip, DMG_CLUB | DMG_ALWAYSGIB ); + if ( pHurt ) + { + pHurt->v.punchangle.z = -20; + pHurt->v.punchangle.x = 20; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_right * 200; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_up * 100; + } + } + break; + + case BSQUID_AE_BLINK: + { + // close eye. + pev->skin = 1; + } + break; + + case BSQUID_AE_HOP: + { + float flGravity = g_psv_gravity->value; + + // throw the squid up into the air on this frame. + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + // jump into air for 0.8 (24/30) seconds +// pev->velocity.z += (0.875 * flGravity) * 0.5; + pev->velocity.z += (0.625 * flGravity) * 0.5; + } + break; + + case BSQUID_AE_THROW: + { + int iPitch; + + // squid throws its prey IF the prey is a client. + edict_t *pHurt = CheckTraceHullAttack( 70, 0, 0 ); + + if ( pHurt ) + { + // croonchy bite sound + iPitch = RANDOM_FLOAT( 90, 110 ); + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + + //pHurt->pev->punchangle.x = RANDOM_LONG(0,34) - 5; + //pHurt->pev->punchangle.z = RANDOM_LONG(0,49) - 25; + //pHurt->pev->punchangle.y = RANDOM_LONG(0,89) - 45; + + // screeshake transforms the viewmodel as well as the viewangle. No problems with seeing the ends of the viewmodels. + UTIL_ScreenShake( pHurt->v.origin, 25.0, 1.5, 0.7, 2 ); + + if ( UTIL_IsPlayer(pHurt) ) + { + UTIL_MakeVectors( pev->angles ); + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_forward * 300 + gpGlobals->v_up * 300; + } + } + } + break; + + default: + CMBaseMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CMBullsquid :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/bullsquid.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.bullsquidHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_flNextSpitTime = gpGlobals->time; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMBullsquid :: Precache() +{ + PRECACHE_MODEL("models/bullsquid.mdl"); + + PRECACHE_MODEL("sprites/bigspit.spr");// spit projectile. + + iSquidSpitSprite = PRECACHE_MODEL("sprites/tinyspit.spr");// client side spittle. + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + PRECACHE_SOUND("bullchicken/bc_attack2.wav"); + PRECACHE_SOUND("bullchicken/bc_attack3.wav"); + + PRECACHE_SOUND("bullchicken/bc_die1.wav"); + PRECACHE_SOUND("bullchicken/bc_die2.wav"); + PRECACHE_SOUND("bullchicken/bc_die3.wav"); + + PRECACHE_SOUND("bullchicken/bc_idle1.wav"); + PRECACHE_SOUND("bullchicken/bc_idle2.wav"); + PRECACHE_SOUND("bullchicken/bc_idle3.wav"); + PRECACHE_SOUND("bullchicken/bc_idle4.wav"); + PRECACHE_SOUND("bullchicken/bc_idle5.wav"); + + PRECACHE_SOUND("bullchicken/bc_pain1.wav"); + PRECACHE_SOUND("bullchicken/bc_pain2.wav"); + PRECACHE_SOUND("bullchicken/bc_pain3.wav"); + PRECACHE_SOUND("bullchicken/bc_pain4.wav"); + + PRECACHE_SOUND("bullchicken/bc_attackgrowl.wav"); + PRECACHE_SOUND("bullchicken/bc_attackgrowl2.wav"); + PRECACHE_SOUND("bullchicken/bc_attackgrowl3.wav"); + + PRECACHE_SOUND("bullchicken/bc_acid1.wav"); + + PRECACHE_SOUND("bullchicken/bc_bite2.wav"); + PRECACHE_SOUND("bullchicken/bc_bite3.wav"); + + PRECACHE_SOUND("bullchicken/bc_spithit1.wav"); + PRECACHE_SOUND("bullchicken/bc_spithit2.wav"); + +} + +//========================================================= +// DeathSound +//========================================================= +void CMBullsquid :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AttackSound +//========================================================= +void CMBullsquid :: AttackSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "bullchicken/bc_attack2.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "bullchicken/bc_attack3.wav", 1, ATTN_NORM ); + break; + } +} + + +//======================================================== +// RunAI - overridden for bullsquid because there are things +// that need to be checked every think. +//======================================================== +void CMBullsquid :: RunAI ( void ) +{ + // first, do base class stuff + CMBaseMonster :: RunAI(); + + if ( pev->skin != 0 ) + { + // close eye if it was open. + pev->skin = 0; + } + + if ( RANDOM_LONG(0,39) == 0 ) + { + pev->skin = 1; + } + + if ( m_hEnemy != NULL && m_Activity == ACT_RUN ) + { + // chasing enemy. Sprint for last bit + if ( (pev->origin - m_hEnemy->v.origin).Length2D() < SQUID_SPRINT_DIST ) + { + pev->framerate = 1.25; + } + } + +} + +//======================================================== +// AI Schedules Specific to this monster +//========================================================= + +// primary range attack +Task_t tlSquidRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSquidRangeAttack1[] = +{ + { + tlSquidRangeAttack1, + ARRAYSIZE ( tlSquidRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "Squid Range Attack1" + }, +}; + +// Chase enemy schedule +Task_t tlSquidChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 },// !!!OEM - this will stop nasty squid oscillation. + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slSquidChaseEnemy[] = +{ + { + tlSquidChaseEnemy1, + ARRAYSIZE ( tlSquidChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_SMELL_FOOD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + 0, + "Squid Chase Enemy" + }, +}; + +Task_t tlSquidHurtHop[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_SQUID_HOPTURN, (float)0 }, + { TASK_FACE_ENEMY, (float)0 },// in case squid didn't turn all the way in the air. +}; + +Schedule_t slSquidHurtHop[] = +{ + { + tlSquidHurtHop, + ARRAYSIZE ( tlSquidHurtHop ), + 0, + 0, + "SquidHurtHop" + } +}; + +Task_t tlSquidSeeCrab[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EXCITED }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slSquidSeeCrab[] = +{ + { + tlSquidSeeCrab, + ARRAYSIZE ( tlSquidSeeCrab ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "SquidSeeCrab" + } +}; + +// squid walks to something tasty and eats it. +Task_t tlSquidEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the food + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_EAT, (float)50 }, + { 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 slSquidEat[] = +{ + { + tlSquidEat, + ARRAYSIZE( tlSquidEat ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + 0, + "SquidEat" + } +}; + +// this is a bit different than just Eat. We use this schedule when the food is far away, occluded, or behind +// the squid. This schedule plays a sniff animation before going to the source of food. +Task_t tlSquidSniffAndEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the food + { TASK_PLAY_SEQUENCE, (float)ACT_DETECT_SCENT }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_EAT, (float)50 }, + { 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 slSquidSniffAndEat[] = +{ + { + tlSquidSniffAndEat, + ARRAYSIZE( tlSquidSniffAndEat ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + 0, + "SquidSniffAndEat" + } +}; + +// squid does this to stinky things. +Task_t tlSquidWallow[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the stinkiness + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_INSPECT_FLOOR}, + { TASK_EAT, (float)50 },// keeps squid from eating or sniffing anything else for a while. + { 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 slSquidWallow[] = +{ + { + tlSquidWallow, + ARRAYSIZE( tlSquidWallow ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + 0, + "SquidWallow" + } +}; + +DEFINE_CUSTOM_SCHEDULES( CMBullsquid ) +{ + slSquidRangeAttack1, + slSquidChaseEnemy, + slSquidHurtHop, + slSquidSeeCrab, + slSquidEat, + slSquidSniffAndEat, + slSquidWallow +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMBullsquid, CMBaseMonster ); + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CMBullsquid :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + { + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + return GetScheduleOfType ( SCHED_SQUID_HURTHOP ); + } + + 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 CMBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) && (m_hEnemy != NULL) ) + { + if (UTIL_IsPlayer(m_hEnemy)) + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + else if (m_hEnemy->v.euser4 != NULL) + { + edict_t *pEdict = m_hEnemy; + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEdict)); + + if ( IRelationship( pMonster ) == R_HT ) + { + // this means squid sees a headcrab! + return GetScheduleOfType ( SCHED_SQUID_SEECRAB ); + } + else + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + } + } + +/*jlb + if ( strcmp(STRING(m_hEnemy->v.model), "models/headcrab.mdl") == 0 ) + { + if ( !UTIL_FInViewCone ( m_hEnemy, ENT(pev), m_flFieldOfView ) || !UTIL_FVisible ( m_hEnemy, ENT(pev) ) ) + { + // scent is behind or occluded + return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_SQUID_EAT ); + } +jlb*/ + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK2 ); + } + + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + + break; + } + } + + return CMBaseMonster :: GetSchedule(); +} + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CMBullsquid :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + return &slSquidRangeAttack1[ 0 ]; + break; + case SCHED_SQUID_HURTHOP: + return &slSquidHurtHop[ 0 ]; + break; + case SCHED_SQUID_SEECRAB: + return &slSquidSeeCrab[ 0 ]; + break; + case SCHED_SQUID_EAT: + return &slSquidEat[ 0 ]; + break; + case SCHED_SQUID_SNIFF_AND_EAT: + return &slSquidSniffAndEat[ 0 ]; + break; + case SCHED_SQUID_WALLOW: + return &slSquidWallow[ 0 ]; + break; + case SCHED_CHASE_ENEMY: + return &slSquidChaseEnemy[ 0 ]; + break; + } + + return CMBaseMonster :: GetScheduleOfType ( Type ); +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. OVERRIDDEN for bullsquid because it needs to +// know explicitly when the last attempt to chase the enemy +// failed, since that impacts its attack choices. +//========================================================= +void CMBullsquid :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_MELEE_ATTACK2: + { + switch ( RANDOM_LONG ( 0, 2 ) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl3.wav", 1, ATTN_NORM ); + break; + } + + CMBaseMonster :: StartTask ( pTask ); + break; + } + case TASK_SQUID_HOPTURN: + { + SetActivity ( ACT_HOP ); + MakeIdealYaw ( m_vecEnemyLKP ); + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + if ( BuildRoute ( m_hEnemy->v.origin, bits_MF_TO_ENEMY, m_hEnemy ) ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + { + CMBaseMonster :: StartTask ( pTask ); + break; + } + } +} + +//========================================================= +// RunTask +//========================================================= +void CMBullsquid :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_SQUID_HOPTURN: + { + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CMBaseMonster :: RunTask( pTask ); + break; + } + } +} + + +//========================================================= +// GetIdealState - Overridden for Bullsquid to deal with +// the feature that makes it lose interest in headcrabs for +// a while if something injures it. +//========================================================= +MONSTERSTATE CMBullsquid :: 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_COMBAT: + /* + COMBAT goes to ALERT upon death of enemy + */ + { + if ( m_hEnemy != NULL && ( iConditions & bits_COND_LIGHT_DAMAGE || iConditions & bits_COND_HEAVY_DAMAGE ) && (strcmp(STRING(m_hEnemy->v.model), "models/headcrab.mdl") == 0) ) + { + // if the squid has a headcrab enemy and something hurts it, it's going to forget about the crab for a while. + m_hEnemy = NULL; + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + break; + } + } + + m_IdealMonsterState = CMBaseMonster :: GetIdealState(); + + return m_IdealMonsterState; +} + diff --git a/src/dlls/cdll_dll.h b/src/dlls/cdll_dll.h new file mode 100644 index 0000000..0acf496 --- /dev/null +++ b/src/dlls/cdll_dll.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_dll.h + +// this file is included by both the game-dll and the client-dll, + +#ifndef CDLL_DLL_H +#define CDLL_DLL_H + +#define MAX_WEAPONS 32 // ??? + +#define MAX_WEAPON_SLOTS 5 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // hard coded item types + +#define HIDEHUD_WEAPONS ( 1<<0 ) +#define HIDEHUD_FLASHLIGHT ( 1<<1 ) +#define HIDEHUD_ALL ( 1<<2 ) +#define HIDEHUD_HEALTH ( 1<<3 ) + +#define MAX_AMMO_TYPES 32 // ??? +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + + +#define WEAPON_SUIT 31 + +#endif \ No newline at end of file diff --git a/src/dlls/cmbase.cpp b/src/dlls/cmbase.cpp new file mode 100644 index 0000000..2d64075 --- /dev/null +++ b/src/dlls/cmbase.cpp @@ -0,0 +1,306 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "decals.h" + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern DLL_GLOBAL Vector g_vecAttackDir; + +edict_t * EHANDLE::Get( void ) +{ + if (m_pent) + { + if (m_pent->serialnumber == m_serialnumber) + return m_pent; + else + return NULL; + } + return NULL; +}; + +edict_t * EHANDLE::Set( edict_t *pent ) +{ + m_pent = pent; + if (pent) + m_serialnumber = m_pent->serialnumber; + return pent; +}; + + +EHANDLE :: operator edict_t *() +{ + return Get( ); +}; + + +edict_t * EHANDLE :: operator = (edict_t *pEntity) +{ + if (pEntity) + { + m_pent = pEntity; + if (m_pent) + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = NULL; + m_serialnumber = 0; + } + return pEntity; +} + + +edict_t * EHANDLE :: operator -> () +{ + return Get( ); +} + + +void *CMBaseEntity::operator new( size_t stAllocateBlock ) +{ + void *mem = ::operator new( stAllocateBlock ); + memset( mem, 0, stAllocateBlock ); + return mem; +} + + +edict_t *CMBaseEntity::CreateEntity(char *classname) +{ + int istr = MAKE_STRING(classname); + + edict_t *pent = CREATE_NAMED_ENTITY(istr); + + if ( FNullEnt( pent ) ) + return NULL; + + pev = VARS(pent); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + pev->flags = 0; + + m_pfnThink = NULL; + m_pfnTouch = NULL; + m_pfnUse = NULL; + m_pfnBlocked = NULL; + + pev->euser4 = (edict_t *)this; + + return pent; +} + +// give health +int CMBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) +{ + if (!pev->takedamage) + return 0; + +// heal + if ( pev->health >= pev->max_health ) + return 0; + + pev->health += flHealth; + + if (pev->health > pev->max_health) + pev->health = pev->max_health; + + return 1; +} + +// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH + +int CMBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + if (!pev->takedamage) + return 0; + + // UNDONE: some entity types may be immune or resistant to some bitsDamageType + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// save damage based on the target's armor level + +// figure momentum add (don't let hurt brushes or other triggers move player) + if ((!FNullEnt(pevInflictor)) && (pev->movetype == MOVETYPE_WALK || pev->movetype == MOVETYPE_STEP) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + + float flForce = flDamage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if (flForce > 1000.0) + flForce = 1000.0; + pev->velocity = pev->velocity + vecDir * flForce; + } + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + return 0; + } + + return 1; +} + + +void CMBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + UTIL_Remove( this->edict() ); +} + + +// Initialize absmin & absmax to the appropriate box +void SetObjectCollisionBox( entvars_t *pev ) +{ + if ( (pev->solid == SOLID_BSP) && + (pev->angles.x || pev->angles.y|| pev->angles.z) ) + { // expand for rotation + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( ((float *)pev->mins)[i]); + if (v > max) + max = v; + v = fabs( ((float *)pev->maxs)[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + ((float *)pev->absmin)[i] = ((float *)pev->origin)[i] - max; + ((float *)pev->absmax)[i] = ((float *)pev->origin)[i] + max; + } + } + else + { + pev->absmin = pev->origin + pev->mins; + pev->absmax = pev->origin + pev->maxs; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + + +void CMBaseEntity::SetObjectCollisionBox( void ) +{ + ::SetObjectCollisionBox( pev ); +} + + +int CMBaseEntity :: Intersects( CMBaseEntity *pOther ) +{ + if ( pOther->pev->absmin.x > pev->absmax.x || + pOther->pev->absmin.y > pev->absmax.y || + pOther->pev->absmin.z > pev->absmax.z || + pOther->pev->absmax.x < pev->absmin.x || + pOther->pev->absmax.y < pev->absmin.y || + pOther->pev->absmax.z < pev->absmin.z ) + return 0; + return 1; +} + +void CMBaseEntity :: MakeDormant( void ) +{ + SetBits( pev->flags, FL_DORMANT ); + + // Don't touch + pev->solid = SOLID_NOT; + // Don't move + pev->movetype = MOVETYPE_NONE; + // Don't draw + SetBits( pev->effects, EF_NODRAW ); + // Don't think + pev->nextthink = 0; + // Relink + UTIL_SetOrigin( pev, pev->origin ); +} + +int CMBaseEntity :: IsDormant( void ) +{ + return FBitSet( pev->flags, FL_DORMANT ); +} + +BOOL CMBaseEntity :: IsInWorld( void ) +{ + // position + if (pev->origin.x >= 4096) return FALSE; + if (pev->origin.y >= 4096) return FALSE; + if (pev->origin.z >= 4096) return FALSE; + if (pev->origin.x <= -4096) return FALSE; + if (pev->origin.y <= -4096) return FALSE; + if (pev->origin.z <= -4096) return FALSE; + // speed + if (pev->velocity.x >= 2000) return FALSE; + if (pev->velocity.y >= 2000) return FALSE; + if (pev->velocity.z >= 2000) return FALSE; + if (pev->velocity.x <= -2000) return FALSE; + if (pev->velocity.y <= -2000) return FALSE; + if (pev->velocity.z <= -2000) return FALSE; + + return TRUE; +} + +int CMBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) +{ + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) ) + return 0; + } + return 1; +} + + +int CMBaseEntity :: DamageDecal( int bitsDamageType ) +{ + if ( pev->rendermode == kRenderTransAlpha ) + return -1; + + if ( pev->rendermode != kRenderNormal ) + return DECAL_BPROOF1; + + return DECAL_GUNSHOT1 + RANDOM_LONG(0,4); +} + + diff --git a/src/dlls/cmbase.h b/src/dlls/cmbase.h new file mode 100644 index 0000000..191b1c3 --- /dev/null +++ b/src/dlls/cmbase.h @@ -0,0 +1,611 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +/* + +Class Hierachy + +CMBaseEntity + CMBaseDelay + CMBaseAnimating + CMBaseToggle + CMBaseMonster +*/ + +#include "monster_plugin.h" + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) +#define FCAP_CUSTOMSAVE 0x00000001 +#define FCAP_ACROSS_TRANSITION 0x00000002 // should transfer between transitions +#define FCAP_MUST_SPAWN 0x00000004 // Spawn after restore +#define FCAP_DONT_SAVE 0x80000000 // Don't save this +#define FCAP_IMPULSE_USE 0x00000008 // can be used by the player +#define FCAP_CONTINUOUS_USE 0x00000010 // can be used by the player +#define FCAP_ONOFF_USE 0x00000020 // can be used by the player +#define FCAP_DIRECTIONAL_USE 0x00000040 // Player sends +/- 1 when using (currently only tracktrains) +#define FCAP_MASTER 0x00000080 // Can be used to "master" other entities (like multisource) + +// UNDONE: This will ignore transition volumes (trigger_transition), but not the PVS!!! +#define FCAP_FORCE_TRANSITION 0x00000080 // ALWAYS goes across transitions + +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +// C functions for external declarations that call the appropriate C++ methods + +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT /* */ +#endif + +typedef enum { USE_OFF = 0, USE_ON = 1, USE_SET = 2, USE_TOGGLE = 3 } USE_TYPE; + +class CMBaseEntity; + +extern void FireTargets( const char *targetName, edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + +typedef void (CMBaseEntity::*BASEPTR)(void); +typedef void (CMBaseEntity::*ENTITYFUNCPTR)(CMBaseEntity *pOther ); +typedef void (CMBaseEntity::*USEPTR)( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + +// For CLASSIFY +#define CLASS_NONE 0 +#define CLASS_MACHINE 1 +#define CLASS_PLAYER 2 +#define CLASS_HUMAN_PASSIVE 3 +#define CLASS_HUMAN_MILITARY 4 +#define CLASS_ALIEN_MILITARY 5 +#define CLASS_ALIEN_PASSIVE 6 +#define CLASS_ALIEN_MONSTER 7 +#define CLASS_ALIEN_PREY 8 +#define CLASS_ALIEN_PREDATOR 9 +#define CLASS_INSECT 10 +#define CLASS_PLAYER_ALLY 11 +#define CLASS_PLAYER_BIOWEAPON 12 // hornets and snarks.launched by players +#define CLASS_ALIEN_BIOWEAPON 13 // hornets and snarks.launched by the alien menace +#define CLASS_BARNACLE 99 // special because no one pays attention to it, and it eats a wide cross-section of creatures. + +class CMBaseEntity; +class CMBaseMonster; + + +#define SF_NORESPAWN ( 1 << 30 )// !!!set this bit on guns and stuff that should never respawn. + +// +// EHANDLE. Safe way to point to edict_t who may die between frames +// +class EHANDLE +{ +private: + edict_t *m_pent; + int m_serialnumber; +public: + edict_t *Get( void ); + edict_t *Set( edict_t *pent ); + + operator edict_t *(); + + edict_t * operator = (edict_t *pEntity); + edict_t * operator ->(); +}; + + +template T * GetClassPtr( T *a ); + +// +// Base Entity. All entity types derive from this +// +class CMBaseEntity +{ +public: + // Constructor. Set engine to use C/C++ callback functions + // pointers to engine data + entvars_t *pev; // Don't need to save/restore this pointer, the engine resets it + + // path corners + edict_t *m_pGoalEnt;// path corner we are heading towards + + edict_t *m_edictList[100]; + int m_edictList_count; + + void *operator new( size_t stAllocateBlock ); + + virtual edict_t *CreateEntity(char *classname); + + // initialization functions + virtual void Spawn( void ) { return; } + virtual void Precache( void ) { return; } + virtual void KeyValue( KeyValueData* pkvd) { pkvd->fHandled = FALSE; } + virtual int ObjectCaps( void ) { return FCAP_ACROSS_TRANSITION; } + virtual void Activate( void ) {} + + // Setup the object->object collision box (pev->mins / pev->maxs is the object->world collision box) + virtual void SetObjectCollisionBox( void ); + +// Classify - returns the type of group (i.e, "houndeye", or "human military" so that monsters with different classnames +// still realize that they are teammates. (overridden for monsters that form groups) + virtual int Classify ( void ) { return CLASS_NONE; }; + virtual void DeathNotice ( entvars_t *pevChild ) {}// monster maker children use this to tell the monster maker that they have died. + + 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 TakeHealth( float flHealth, int bitsDamageType ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + virtual BOOL IsTriggered( CMBaseEntity *pActivator ) {return TRUE;} + virtual CMBaseMonster *MyMonsterPointer( void ) { return NULL;} + virtual int GetToggleState( void ) { return TS_AT_TOP; } + virtual void AddPoints( int score, BOOL bAllowNegativeScore ) {} + virtual void AddPointsToTeam( int score, BOOL bAllowNegativeScore ) {} + virtual float GetDelay( void ) { return 0; } + virtual int IsMoving( void ) { return pev->velocity != g_vecZero; } + virtual void OverrideReset( void ) {} + virtual int DamageDecal( int bitsDamageType ); + // This is ONLY used by the node graph to test movement through a door + virtual void SetToggleState( int state ) {} + virtual void StartSneaking( void ) {} + virtual void StopSneaking( void ) {} + virtual BOOL OnControls( entvars_t *pev ) { return FALSE; } + virtual BOOL IsSneaking( void ) { return FALSE; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsBSPModel( void ) { return pev->solid == SOLID_BSP || pev->movetype == MOVETYPE_PUSHSTEP; } + virtual BOOL ReflectGauss( void ) { return ( IsBSPModel() && !pev->takedamage ); } + virtual BOOL HasTarget( string_t targetname ) { return FStrEq(STRING(targetname), STRING(pev->targetname) ); } + virtual BOOL IsInWorld( void ); + virtual BOOL IsPlayer( void ) { return FALSE; } + virtual BOOL IsNetClient( void ) { return FALSE; } + virtual const char *TeamID( void ) { return ""; } + + +// virtual void SetActivator( CMBaseEntity *pActivator ) {} +//jlb virtual CMBaseEntity *GetNextTarget( void ); + + // fundamental callbacks + void (CMBaseEntity ::*m_pfnThink)(void); + void (CMBaseEntity ::*m_pfnTouch)( edict_t *pOther ); + void (CMBaseEntity ::*m_pfnUse)( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + void (CMBaseEntity ::*m_pfnBlocked)( edict_t *pOther ); + + virtual void Think( void ) { if (m_pfnThink) (this->*m_pfnThink)(); }; + virtual void Touch( edict_t *pOther ) { if (m_pfnTouch) (this->*m_pfnTouch)( pOther ); }; + virtual void Use( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ) + { + if (m_pfnUse) + (this->*m_pfnUse)( pActivator, pCaller, useType, value ); + } + virtual void Blocked( edict_t *pOther ) { if (m_pfnBlocked) (this->*m_pfnBlocked)( pOther ); }; + + void UpdateOnRemove( void ); + + // common member functions + void EXPORT SUB_Remove( void ); + void EXPORT SUB_DoNothing( void ); + void EXPORT SUB_StartFadeOut ( void ); + void EXPORT SUB_FadeOut ( void ); + void EXPORT SUB_CallUseToggle( void ) { this->Use( this->edict(), this->edict(), USE_TOGGLE, 0 ); } + int ShouldToggle( USE_TYPE useType, BOOL currentState ); + void FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL ); + Vector FireBulletsPlayer( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL, int shared_rand = 0 ); + + virtual CMBaseEntity *Respawn( void ) { return NULL; } + + void SUB_UseTargets( edict_t *pActivator, USE_TYPE useType, float value ); + // Do the bounding boxes of these two intersect? + int Intersects( CMBaseEntity *pOther ); + void MakeDormant( void ); + int IsDormant( void ); + BOOL IsLockedByMaster( void ) { return FALSE; } + + static CMBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + if ( pent->v.euser4 == NULL ) + return (CMBaseEntity *)NULL; + CMBaseEntity *pEnt = GetClassPtr((CMBaseEntity *)VARS(pent)); + return pEnt; + } + + static CMBaseEntity *Instance( entvars_t *pev ) { return Instance( ENT( pev ) ); } + static CMBaseEntity *Instance( int eoffset) { return Instance( ENT( eoffset) ); } + +/*jlb + CMBaseMonster *GetMonsterPointer( entvars_t *pevMonster ) + { + CMBaseEntity *pEntity = Instance( pevMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + CMBaseMonster *GetMonsterPointer( edict_t *pentMonster ) + { + CMBaseEntity *pEntity = Instance( pentMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } +jlb*/ + + // virtual functions used by a few classes + + // used by monsters that are created by the MonsterMaker + virtual void UpdateOwner( void ) { return; }; + + + // +//jlb static CMBaseEntity *Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner = NULL ); + + virtual BOOL FBecomeProne( void ) {return FALSE;}; + edict_t *edict() { return ENT( pev ); }; + EOFFSET eoffset( ) { return OFFSET( pev ); }; + int entindex( ) { return ENTINDEX( edict() ); }; + + virtual Vector Center( ) { return (pev->absmax + pev->absmin) * 0.5; }; // center point of entity + virtual Vector EyePosition( ) { return pev->origin + pev->view_ofs; }; // position of eyes + virtual Vector EarPosition( ) { return pev->origin + pev->view_ofs; }; // position of ears + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ); }; // position to shoot at + + virtual int Illumination( ) { return GETENTITYILLUM( ENT( pev ) ); }; + +// virtual BOOL FVisible ( edict_t *pEntity ); +// virtual BOOL FVisible ( const Vector &vecOrigin ); + + //We use this variables to store each ammo count. + int ammo_9mm; + int ammo_357; + int ammo_bolts; + int ammo_buckshot; + int ammo_rockets; + int ammo_uranium; + int ammo_hornets; + int ammo_argrens; + //Special stuff for grenades and satchels. + float m_flStartThrow; + float m_flReleaseThrow; + int m_chargeReady; + int m_fInAttack; + + enum EGON_FIRESTATE { FIRE_OFF, FIRE_CHARGE }; + int m_fireState; +}; + + + +// Ugly technique to override base member functions +// Normally it's illegal to cast a pointer to a member function of a derived class to a pointer to a +// member function of a base class. static_cast is a sleezy way around that problem. + +#define SetThink( a ) m_pfnThink = static_cast (a) +#define SetTouch( a ) m_pfnTouch = static_cast (a) +#define SetUse( a ) m_pfnUse = static_cast (a) +#define SetBlocked( a ) m_pfnBlocked = static_cast (a) + + +class CMPointEntity : public CMBaseEntity +{ +public: + void Spawn( void ); + virtual int ObjectCaps( void ) { return CMBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: +}; + + +typedef struct locksounds // sounds that doors and buttons make when locked/unlocked +{ + string_t sLockedSound; // sound a door makes when it's locked + string_t sLockedSentence; // sentence group played when door is locked + string_t sUnlockedSound; // sound a door makes when it's unlocked + string_t sUnlockedSentence; // sentence group played when door is unlocked + + int iLockedSentence; // which sentence in sentence group to play next + int iUnlockedSentence; // which sentence in sentence group to play next + + float flwaitSound; // time delay between playing consecutive 'locked/unlocked' sounds + float flwaitSentence; // time delay between playing consecutive sentences + BYTE bEOFLocked; // true if hit end of list of locked sentences + BYTE bEOFUnlocked; // true if hit end of list of unlocked sentences +} locksound_t; + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton); + + +// +// generic Delay entity. +// +class CMBaseDelay : public CMBaseEntity +{ +public: + float m_flDelay; + int m_iszKillTarget; + + virtual void KeyValue( KeyValueData* pkvd); + + // common member functions + void SUB_UseTargets( edict_t *pActivator, USE_TYPE useType, float value ); + void EXPORT DelayThink( void ); +}; + + +class CMBaseAnimating : public CMBaseDelay +{ +public: + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); // accumulate animation frame time from last time called until now + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + void ResetSequenceInfo ( ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); // Handle events that have happend since last time called up until X seconds into the future + 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 ); + void GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + 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 +}; + + +// +// generic Toggle entity. +// +#define SF_ITEM_USE_ONLY 256 // ITEM_USE_ONLY = BUTTON_USE_ONLY = DOOR_USE_ONLY!!! + +class CMBaseToggle : public CMBaseAnimating +{ +public: + void KeyValue( KeyValueData *pkvd ); + + TOGGLE_STATE m_toggle_state; + float m_flActivateFinished;//like attack_finished, but for doors + float m_flMoveDistance;// how far a door should slide or rotate + float m_flWait; + float m_flLip; + float m_flTWidth;// for plats + float m_flTLength;// for plats + + Vector m_vecPosition1; + Vector m_vecPosition2; + Vector m_vecAngle1; + Vector m_vecAngle2; + + int m_cTriggersLeft; // trigger_counter only, # of activations remaining + float m_flHeight; +//jlb EHANDLE m_hActivator; + void (CMBaseToggle::*m_pfnCallWhenMoveDone)(void); + Vector m_vecFinalDest; + Vector m_vecFinalAngle; + + int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does + + virtual int GetToggleState( void ) { return m_toggle_state; } + virtual float GetDelay( void ) { return m_flWait; } + + // common member functions + void LinearMove( Vector vecDest, float flSpeed ); + void EXPORT LinearMoveDone( void ); + void AngularMove( Vector vecDestAngle, float flSpeed ); + void EXPORT AngularMoveDone( void ); + BOOL IsLockedByMaster( void ); + + static float AxisValue( int flags, const Vector &angles ); + static void AxisDir( entvars_t *pev ); + static float AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ); + + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. +}; +#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + + +// people gib if their health is <= this at the time of death +#define GIB_HEALTH_VALUE -30 + +#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 + +#define bits_CAP_DUCK ( 1 << 0 )// crouch +#define bits_CAP_JUMP ( 1 << 1 )// jump/leap +#define bits_CAP_STRAFE ( 1 << 2 )// strafe ( walk/run sideways) +//??? #define bits_CAP_SQUAD ( 1 << 3 )// can form squads +#define bits_CAP_SWIM ( 1 << 4 )// proficiently navigate in water +#define bits_CAP_CLIMB ( 1 << 5 )// climb ladders/ropes +#define bits_CAP_USE ( 1 << 6 )// open doors/push buttons/pull levers +#define bits_CAP_HEAR ( 1 << 7 )// can hear forced sounds +#define bits_CAP_AUTO_DOORS ( 1 << 8 )// can trigger auto doors +#define bits_CAP_OPEN_DOORS ( 1 << 9 )// can open manual doors +#define bits_CAP_TURN_HEAD ( 1 << 10)// can turn head, always bone controller 0 + +#define bits_CAP_RANGE_ATTACK1 ( 1 << 11)// can do a range attack 1 +#define bits_CAP_RANGE_ATTACK2 ( 1 << 12)// can do a range attack 2 +#define bits_CAP_MELEE_ATTACK1 ( 1 << 13)// can do a melee attack 1 +#define bits_CAP_MELEE_ATTACK2 ( 1 << 14)// can do a melee attack 2 + +#define bits_CAP_FLY ( 1 << 15)// can fly, move all around + +#define bits_CAP_DOORS_GROUP (bits_CAP_USE | bits_CAP_AUTO_DOORS | bits_CAP_OPEN_DOORS) + +// used by suit voice to indicate damage sustained and repaired type to player + +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. +#define DMG_DROWN (1 << 14) // Drowning +// time-based damage +#define DMG_TIMEBASED (~(0x3fff)) // mask for time-based damage + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +// these are the damage types that are allowed to gib corpses +#define DMG_GIB_CORPSE ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) + +// these are the damage types that have client hud art +#define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// NOTE: tweak these values based on gameplay feedback: + +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + + +#define itbd_Paralyze 0 +#define itbd_NerveGas 1 +#define itbd_Poison 2 +#define itbd_Radiation 3 +#define itbd_DrownRecover 4 +#define itbd_Acid 5 +#define itbd_SlowBurn 6 +#define itbd_SlowFreeze 7 +#define CDMG_TIMEBASED 8 + +// when calling KILLED(), a value that governs gib behavior is expected to be +// one of these three values +#define GIB_NORMAL 0// gib if entity was overkilled +#define GIB_NEVER 1// never gib, no matter how much death damage is done ( freezing, etc ) +#define GIB_ALWAYS 2// always gib ( Houndeye Shock, Barnacle Bite ) + +class CMBaseMonster; + + +extern int GetMonsterIndex(void); + +// +// Converts a entvars_t * to a class pointer +// +template T * GetClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + if (pev == NULL) + return NULL; + + // get the private data + a = (T *)pev->euser4; + + return a; +} + +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity +// +template T * CreateClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + if (pev != NULL) + return NULL; // don't allocate if pointer already provided + + // allocate entity... + edict_t *temp_edict; + int edict_index; + int monster_index; + + if ((monster_index = GetMonsterIndex()) == -1) + { + (*g_engfuncs.pfnServerPrint)("[MONSTER] ERROR: No FREE Monster edicts in CreateClassPtr!\n" ); + return NULL; + } + + // allocate private data + a = new T; + + if ((temp_edict = a->CreateEntity("func_wall")) == NULL) + { + (*g_engfuncs.pfnServerPrint)("[MONSTER] ERROR: NULL Ent in CreateClassPtr!\n" ); + delete a; + return NULL; + } + + edict_index = (*g_engfuncs.pfnIndexOfEdict)(temp_edict); + + // store the class pointer to the edict pev... + pev = VARS(temp_edict); + a->pev = pev; + + // store the class pointer in the array here!!! + monsters[monster_index].monster_index = edict_index; + monsters[monster_index].monster_pent = temp_edict; + monsters[monster_index].respawn_index = -1; + monsters[monster_index].pMonster = (CMBaseMonster *)a; + + // get the private data + a = (T *)pev->euser4; + + return a; +} + diff --git a/src/dlls/cmbasemonster.h b/src/dlls/cmbasemonster.h new file mode 100644 index 0000000..7a95565 --- /dev/null +++ b/src/dlls/cmbasemonster.h @@ -0,0 +1,1028 @@ +/*** +* +* Copyright (c) 1996-2001, 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 + +#ifndef SCHEDULE_H +#include "schedule.h" +#endif + +#ifndef SKILL_H +#include "skill.h" +#endif + +// +// generic Monster +// +class CMBaseMonster : public CMBaseToggle +{ +private: + int m_afConditions; + +public: + + // 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 + + void KeyValue( KeyValueData *pkvd ); + +// monster use function + void EXPORT MonsterUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + void EXPORT CorpseUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + +// overrideable Monster member functions + + virtual int BloodColor( void ) { return m_bloodColor; } + + virtual CMBaseMonster *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); } + virtual BOOL ShouldFadeOnDeath( void ); + +// 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 ( CMBaseEntity *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 edict_t* BestVisibleEnemy ( void );// finds best visible enemy for attack + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ); + + virtual int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, edict_t *pTarget, float *pflDist );// check validity of a straight move through space + virtual void Move( float flInterval = 0.1 ); + virtual void MoveExecute( edict_t *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 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, CMBaseEntity *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 ( edict_t *pTarget, float flDist ); + virtual int CheckEnemy ( edict_t *pEnemy ); + void PushEnemy( edict_t *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 ); + void TaskFail( void ); + 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( edict_t *pTargetEnt ); + void AdvanceRoute ( float distance ); + virtual BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, edict_t *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, edict_t *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 ); + + 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 ); + + 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. + + edict_t *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); + BOOL FacingIdeal( void ); + + BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. + + 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 BOOL HasHumanGibs( void ); + virtual BOOL HasAlienGibs( void ); + virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled + + 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 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; } +}; + +// +// monster_plugin monsters +// + +#ifndef TALKMONSTER_H +#include "cmtalkmonster.h" +#endif + +#ifndef FLYINGMONSTER_H +#include "cmflyingmonster.h" +#endif + +#ifndef WEAPONS_H +#include "weapons.h" +#endif + +class CMHeadCrab : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void RunTask ( Task_t *pTask ); + void StartTask ( Task_t *pTask ); + void SetYawSpeed ( void ); + void EXPORT LeapTouch ( edict_t *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 gSkillData.headcrabDmgBite; } + 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[]; +}; + +class CMBabyCrab : public CMHeadCrab +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite * 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; } +}; + + +class CMZombie : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( 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 ); +}; + + +class CMSqueakGrenade : public CMGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify( void ); + void EXPORT SuperBounceTouch( edict_t *pOther ); + void EXPORT HuntThink( void ); + int BloodColor( void ) { return BLOOD_COLOR_YELLOW; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + static float m_flNextBounceSoundTime; + + // CMBaseEntity *m_pTarget; + float m_flDie; + Vector m_vecTarget; + float m_flNextHunt; + float m_flNextHit; + Vector m_posPrev; +}; + + +class CMBullsquid : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void IdleSound( void ); + void PainSound( void ); + void DeathSound( void ); + void AlertSound ( void ); + void AttackSound( void ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void RunAI( void ); + BOOL FValidateHintType ( short sHint ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + int IRelationship ( CMBaseEntity *pTarget ); + int IgnoreConditions ( void ); + MONSTERSTATE GetIdealState ( void ); + + CUSTOM_SCHEDULES; + + float m_flLastHurtTime;// we keep track of this, because if something hurts a squid, it will forget about its love of headcrabs for a while. + float m_flNextSpitTime;// last time the bullsquid used the spit attack. +}; + + +class CMHoundeye : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void SetYawSpeed ( void ); + void WarmUpSound ( void ); + void AlertSound( void ); + void DeathSound( void ); + void WarnSound( void ); + void PainSound( void ); + void IdleSound( void ); + void StartTask( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void SonicAttack( void ); + void PrescheduleThink( void ); + void SetActivity ( Activity NewActivity ); + void WriteBeamColor ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL FValidateHintType ( short sHint ); + BOOL FCanActiveIdle ( void ); + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *CMHoundeye :: GetSchedule( void ); + + CUSTOM_SCHEDULES; + + int m_iSpriteTexture; + BOOL m_fAsleep;// some houndeyes sleep in idle mode if this is set, the houndeye is lying down + BOOL m_fDontBlink;// don't try to open/close eye if this bit is set! + Vector m_vecPackCenter; // the center of the pack. The leader maintains this by averaging the origins of all pack members. +}; + + +class CMHGrunt : public CMTalkMonster +{ +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 ); + + edict_t *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 ( CMBaseEntity *pTarget ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES; + + // 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[]; +}; + + +class CMHAssassin : public CMBaseMonster +{ +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; + + 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; +}; + + +#define ISLAVE_MAX_BEAMS 8 + +class CMISlave : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + int Classify ( void ); + int IRelationship( CMBaseEntity *pTarget ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + 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); + + void DeathSound( void ); + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + void StartTask ( Task_t *pTask ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + CUSTOM_SCHEDULES; + + void ClearBeams( ); + void ArmBeam( int side ); + void WackBeam( int side, edict_t *pEntity ); + void ZapBeam( int side ); + void BeamGlow( void ); + + int m_iBravery; + + CMBeam *m_pBeam[ISLAVE_MAX_BEAMS]; + + int m_iBeams; + float m_flNextAttack; + + int m_voicePitch; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; +}; + + +class CMBarney : public CMTalkMonster +{ +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 ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + // 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 ); + + BOOL m_fGunDrawn; + float m_painTime; + float m_checkAttackTime; + BOOL m_lastAttackCheck; + + CUSTOM_SCHEDULES; +}; + + +class CMAGrunt : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -32, -32, 0 ); + pev->absmax = pev->origin + Vector( 32, 32, 85 ); + } + + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void StartTask ( Task_t *pTask ); + void AlertSound( void ); + void DeathSound ( void ); + void PainSound ( void ); + void AttackSound ( void ); + void PrescheduleThink ( void ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int IRelationship( CMBaseEntity *pTarget ); + void StopTalking ( void ); + BOOL ShouldSpeak( void ); + CUSTOM_SCHEDULES; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pAttackSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + + BOOL m_fCanHornetAttack; + float m_flNextHornetAttackCheck; + + float m_flNextPainTime; + + // three hacky fields for speech stuff. These don't really need to be saved. + float m_flNextSpeakTime; + float m_flNextWordTime; + int m_iLastWord; +}; + + +class CMScientist : public CMTalkMonster +{ +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 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( edict_t *pEnemy ); + + 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 ); + + CUSTOM_SCHEDULES; + +private: + float m_painTime; + float m_healTime; + float m_fearTime; +}; + + +class CMApache : public CMBaseMonster +{ +public: + 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( edict_t *pOther ); + void EXPORT CrashTouch( edict_t *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( edict_t *pActivator, edict_t *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; + CMBeam *m_pBeam; +}; + + +class CMApacheHVR : public CMGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT IgniteThink( void ); + void EXPORT AccelerateThink( void ); + + int m_iTrail; + Vector m_vecForward; +}; + + +class CMController : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunAI( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // balls + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // head + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // block, throw + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + CUSTOM_SCHEDULES; + + void Stop( void ); + void Move ( float flInterval ); + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, edict_t *pTarget, float *pflDist ); + void MoveExecute( edict_t *pTargetEnt, const Vector &vecDir, float flInterval ); + void SetActivity ( Activity NewActivity ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + int LookupFloat( ); + + float m_flNextFlinch; + + float m_flShootTime; + float m_flShootEnd; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + void DeathSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + CMSprite *m_pBall[2]; // hand balls + int m_iBall[2]; // how bright it should be + float m_iBallTime[2]; // when it should be that color + int m_iBallCurrent[2]; // current brightness + + Vector m_vecEstVelocity; + + Vector m_velocity; + int m_fInCombat; +}; + +//========================================================= +// Controller bouncy ball attack +//========================================================= +class CMControllerHeadBall : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT HuntThink( void ); + void EXPORT DieThink( void ); + void EXPORT BounceTouch( edict_t *pOther ); + void MovetoTarget( Vector vecTarget ); + void Crawl( void ); + int m_iTrail; + int m_flNextAttack; + Vector m_vecIdeal; + EHANDLE m_hOwner; +}; + +class CMControllerZapBall : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT AnimateThink( void ); + void EXPORT ExplodeTouch( edict_t *pOther ); + + EHANDLE m_hOwner; +}; + + +class CInfoBM; + +class CMBigMomma : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + void NodeStart( int iszNextNode ); + void NodeReach( void ); + BOOL ShouldGoToNode( void ); + + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void LayHeadcrab( void ); + + int GetNodeSequence( void ); + int GetNodePresequence( void ); + float GetNodeDelay( void ); + float GetNodeRange( void ); + float GetNodeYaw( void ); + + // Restart the crab count on each new level + void OverrideReset( void ) + { + m_crabCount = 0; + } + + void DeathNotice( entvars_t *pevChild ); + + BOOL CanLayCrab( void ); + + void LaunchMortar( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -95, -95, 0 ); + pev->absmax = pev->origin + Vector( 95, 95, 190 ); + } + + BOOL CheckMeleeAttack1( float flDot, float flDist ); // Slash + BOOL CheckMeleeAttack2( float flDot, float flDist ); // Lay a crab + BOOL CheckRangeAttack1( float flDot, float flDist ); // Mortar launch + + static const char *pChildDieSounds[]; + static const char *pSackSounds[]; + static const char *pDeathSounds[]; + static const char *pAttackSounds[]; + static const char *pAttackHitSounds[]; + static const char *pBirthSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pFootSounds[]; + + CUSTOM_SCHEDULES; + +private: + float m_nodeTime; + float m_crabTime; + float m_mortarTime; + float m_painSoundTime; + int m_crabCount; +}; + + +#endif // BASEMONSTER_H diff --git a/src/dlls/cmflyingmonster.h b/src/dlls/cmflyingmonster.h new file mode 100644 index 0000000..0f0b315 --- /dev/null +++ b/src/dlls/cmflyingmonster.h @@ -0,0 +1,53 @@ +/*** +* +* Copyright (c) 1996-2001, 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 CMFlyingMonster : public CMBaseMonster +{ +public: + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, edict_t *pTarget, float *pflDist );// check validity of a straight move through space + BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, edict_t *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( edict_t *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/src/dlls/cmtalkmonster.h b/src/dlls/cmtalkmonster.h new file mode 100644 index 0000000..e6f6135 --- /dev/null +++ b/src/dlls/cmtalkmonster.h @@ -0,0 +1,178 @@ +/*** +* +* Copyright (c) 1996-2001, 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_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 CMTalkMonster : public CMBaseMonster +{ +public: + void TalkInit( void ); + edict_t *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 TalkTouch( edict_t *pOther ); + void Killed( entvars_t *pevAttacker, int iGib ); + int IRelationship ( CMBaseEntity *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, edict_t *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 ); + edict_t *EnumFriends( edict_t *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 && UTIL_IsPlayer(m_hTargetEnt); } + void StopFollowing( BOOL clearSchedule ); + void StartFollowing( edict_t *pLeader ); + virtual void DeclineFollowing( void ) {} + void LimitFollowers( edict_t *pPlayer, int maxFollowers ); + + void EXPORT FollowerUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + + virtual void SetAnswerQuestion( edict_t *pSpeaker ); + virtual int FriendNumber( int arrayNumber ) { return arrayNumber; } + + 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) + + 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/src/dlls/combat.cpp b/src/dlls/combat.cpp new file mode 100644 index 0000000..f7adffc --- /dev/null +++ b/src/dlls/combat.cpp @@ -0,0 +1,1597 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "decals.h" +#include "animation.h" +#include "weapons.h" +#include "func_break.h" + +const Vector g_vecZero = Vector(0,0,0); +Vector g_vecAttackDir; +entvars_t *g_pevLastInflictor; + +#define HUMAN_GIB_COUNT 6 +#define ALIEN_GIB_COUNT 4 + + +// HACKHACK -- The gib velocity equations don't work +void CMGib :: LimitVelocity( void ) +{ + float length = pev->velocity.Length(); + + // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it + // in 3 separate places again, I'll just limit it here. + if ( length > 1500.0 ) + pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something +} + + +void CMGib :: SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) +{ + int i; + + for ( i = 0 ; i < cGibs ; i++ ) + { + CMGib *pGib = CreateClassPtr( (CMGib *)NULL ); + + if (pGib == NULL) // no free monster edicts left? + continue; + + pGib->Spawn( "models/stickygib.mdl" ); + pGib->pev->body = RANDOM_LONG(0,2); + + if ( pevVictim ) + { + pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); + + /* + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ); + */ + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.15, 0.15 ); + + pGib->pev->velocity = pGib->pev->velocity * 900; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 250, 400 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 250, 400 ); + + // copy owner's blood color + CMBaseMonster *pBlood = GetClassPtr((CMBaseMonster *)VARS(pevVictim)); + if (pBlood != NULL) + pGib->m_bloodColor = pBlood->BloodColor(); + else + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch ( StickyGibTouch ); + pGib->SetThink (NULL); + } + pGib->LimitVelocity(); + } +} + +void CMGib :: SpawnHeadGib( entvars_t *pevVictim ) +{ + CMGib *pGib = CreateClassPtr( (CMGib *)NULL ); + + if (pGib == NULL) // no free monster edicts left? + return; + + pGib->Spawn( "models/hgibs.mdl" );// throw one head + pGib->pev->body = 0; + + if ( pevVictim ) + { + pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pGib->edict() ); + + if ( RANDOM_LONG ( 0, 100 ) <= 5 && pentPlayer ) + { + // 5% chance head will be thrown at player's face. + entvars_t *pevPlayer; + + pevPlayer = VARS( pentPlayer ); + pGib->pev->velocity = ( ( pevPlayer->origin + pevPlayer->view_ofs ) - pGib->pev->origin ).Normalize() * 300; + pGib->pev->velocity.z += 100; + } + else + { + pGib->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + } + + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + CMBaseMonster *pBlood = GetClassPtr((CMBaseMonster *)VARS(pevVictim)); + if (pBlood != NULL) + pGib->m_bloodColor = pBlood->BloodColor(); + else + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + } + pGib->LimitVelocity(); +} + +void CMGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) +{ + int cSplat; + + for ( cSplat = 0 ; cSplat < cGibs ; cSplat++ ) + { + CMGib *pGib = CreateClassPtr( (CMGib *)NULL ); + + if (pGib == NULL) // no free monster edicts left? + continue; + + if ( human ) + { + // human pieces + pGib->Spawn( "models/hgibs.mdl" ); + pGib->pev->body = RANDOM_LONG(1,HUMAN_GIB_COUNT-1);// start at one to avoid throwing random amounts of skulls (0th gib) + } + else + { + // aliens + pGib->Spawn( "models/agibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,ALIEN_GIB_COUNT-1); + } + + if ( pevVictim ) + { + // spawn the gib somewhere in the monster's bounding volume + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.25, 0.25 ); + + pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT ( 300, 400 ); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + CMBaseMonster *pBlood = GetClassPtr((CMBaseMonster *)VARS(pevVictim)); + if (pBlood != NULL) + pGib->m_bloodColor = pBlood->BloodColor(); + else + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector( 0 , 0 , 0 ), Vector ( 0, 0, 0 ) ); + } + pGib->LimitVelocity(); + } +} + + +BOOL CMBaseMonster :: HasHumanGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_HUMAN_MILITARY || + myClass == CLASS_PLAYER_ALLY || + myClass == CLASS_HUMAN_PASSIVE || + myClass == CLASS_PLAYER ) + + return TRUE; + + return FALSE; +} + + +BOOL CMBaseMonster :: HasAlienGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_ALIEN_MILITARY || + myClass == CLASS_ALIEN_MONSTER || + myClass == CLASS_ALIEN_PASSIVE || + myClass == CLASS_INSECT || + myClass == CLASS_ALIEN_PREDATOR || + myClass == CLASS_ALIEN_PREY ) + + return TRUE; + + return FALSE; +} + + +void CMBaseMonster::FadeMonster( void ) +{ + StopAnimation(); + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->avelocity = g_vecZero; + pev->animtime = gpGlobals->time; + pev->effects |= EF_NOINTERP; + SUB_StartFadeOut(); +} + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CMBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + // only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 ) // Only the player will ever get here + { + CMGib::SpawnHeadGib( pev ); + CMGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs. + } + gibbed = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") != 0 ) // Should never get here, but someone might call it directly + { + CMGib::SpawnRandomGibs( pev, 4, 0 ); // Throw alien gibs + } + gibbed = TRUE; + } + + if ( !IsPlayer() ) + { + if ( gibbed ) + { + // don't remove players! + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + FadeMonster(); + } + } +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CMBaseMonster :: 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 CMBaseMonster :: 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 CMBaseMonster::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 CMBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + + +void CMBaseMonster::CallGibMonster( void ) +{ + BOOL fade = FALSE; + + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + fade = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") == 0 ) + fade = TRUE; + } + + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + if ( fade ) + { + FadeMonster(); + } + else + { + 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; + pev->fuser4 = pev->health; + } + + if ( ShouldFadeOnDeath() && !fade ) + UTIL_Remove(this->edict()); +} + + +/* +============ +Killed +============ +*/ +void CMBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + unsigned int cCount = 0; + BOOL fDone = FALSE; + + 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. +/*jlb monstermaker + CMBaseEntity *pOwner = CMBaseEntity::Instance(pev->owner); + if ( pOwner ) + { +//jlb it crashes here sometimes!!! + pOwner->DeathNotice( pev ); + } +jlb*/ + + 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; + pev->fuser4 = pev->health; + } + + //pev->enemy = ENT( pevAttacker );//why? (sjb) + + m_IdealMonsterState = MONSTERSTATE_DEAD; +} + +// +// fade out - slowly fades a entity out, then removes it. +// +// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! +// SET A FUTURE THINK AND A RENDERMODE!! +void CMBaseEntity :: SUB_StartFadeOut ( void ) +{ + if (pev->rendermode == kRenderNormal) + { + pev->renderamt = 255; + pev->rendermode = kRenderTransTexture; + } + + pev->solid = SOLID_NOT; + pev->avelocity = g_vecZero; + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( SUB_FadeOut ); +} + +void CMBaseEntity :: SUB_FadeOut ( void ) +{ + if ( pev->renderamt > 7 ) + { + pev->renderamt -= 7; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->renderamt = 0; + pev->nextthink = gpGlobals->time + 0.2; + SetThink ( SUB_Remove ); + } +} + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CMGib :: WaitTillLand ( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this->edict() ); + return; + } + + if ( pev->velocity == g_vecZero ) + { + SetThink (SUB_StartFadeOut); + pev->nextthink = gpGlobals->time + m_lifeTime; + } + else + { + // wait and check again in another half second. + pev->nextthink = gpGlobals->time + 0.5; + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CMGib :: BounceGibTouch ( edict_t *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + //if ( RANDOM_LONG(0,1) ) + // return;// don't bleed everytime + + if (pev->flags & FL_ONGROUND) + { + pev->velocity = pev->velocity * 0.9; + pev->angles.x = 0; + pev->angles.z = 0; + pev->avelocity.x = 0; + pev->avelocity.z = 0; + } + else + { + if ( m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + 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); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CMGib :: StickyGibTouch ( edict_t *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time + 5; + + if (!FStrEq(STRING(pOther->v.classname), "worldspawn")) + { + pev->nextthink = gpGlobals->time; + return; + } + + UTIL_TraceLine ( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + pev->velocity = tr.vecPlaneNormal * -1; + pev->angles = UTIL_VecToAngles ( pev->velocity ); + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; +} + +// +// Throw a chunk +// +void CMGib :: Spawn( const char *szGibModel ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->friction = 0.55; // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + pev->renderamt = 255; + pev->rendermode = kRenderNormal; + pev->renderfx = kRenderFxNone; + pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap + pev->classname = MAKE_STRING("gib"); + + SET_MODEL(ENT(pev), szGibModel); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->nextthink = gpGlobals->time + 4; + m_lifeTime = 10; + SetThink ( WaitTillLand ); + SetTouch ( BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +// take health +int CMBaseMonster :: 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 CMBaseEntity::TakeHealth(flHealth, 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 CMBaseMonster :: 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 )) + { + edict_t *pInflictor = ENT(pevInflictor); + if (pInflictor) + { + vecDir = ( UTIL_Center(pInflictor) - 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; + + if (pev->flags & FL_MONSTER) + pev->fuser4 = pev->health; + + // 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) ) + { + 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) + { + edict_t *pEdict = m_hEnemy; + if (m_hEnemy == NULL || pevInflictor == VARS(pEdict) || !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 CMBaseMonster :: 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 )) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pevInflictor)); + if (pMonster) + { + vecDir = ( pMonster->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; + pev->fuser4 = pev->health; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + pev->fuser4 = pev->health; + } + + return 1; +} + + +float CMBaseMonster :: 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; +} + +// +// 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 ) +{ + edict_t *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) == CONTENTS_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->v.takedamage != DAMAGE_NO ) + { + if (UTIL_IsPlayer(pEntity)) + { + // blast's don't tavel into or out of water + if (bInWater && pEntity->v.waterlevel == 0) + continue; + if (!bInWater && pEntity->v.waterlevel == 3) + continue; + + vecSpot = UTIL_BodyTarget( pEntity, vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity ) + {// 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( ); + UTIL_TraceAttack( pEntity, pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + UTIL_TakeDamage ( pEntity, pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + else if (pEntity->v.euser4 != NULL) + { + // UNDONE: this should check a damage mask, not an ignore + CMBaseEntity *pMonster = GetClassPtr((CMBaseEntity *)VARS(pEntity)); + + if ( iClassIgnore != CLASS_NONE && pMonster->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->v.waterlevel == 0) + continue; + if (!bInWater && pEntity->v.waterlevel == 3) + continue; + + vecSpot = pMonster->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity ) + {// 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( ); + pMonster->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pMonster->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } + } +} + + +void CMBaseMonster :: 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 CMBaseMonster :: 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 ); +} + + +//========================================================= +// 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. +//========================================================= +edict_t* CMBaseMonster :: 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 ) + { + edict_t *pEntity = tr.pHit; + + if ( iDamage > 0 ) + { + if (UTIL_IsPlayer(pEntity)) + UTIL_TakeDamage( pEntity, pev, pev, iDamage, iDmgType ); + else if (pEntity->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEntity)); + pMonster->TakeDamage( pev, pev, iDamage, iDmgType ); + } + } + + return pEntity; + } + + return NULL; +} + + +/* +================ +TraceAttack +================ +*/ +void CMBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this->edict(), flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + } +} + + +/* +//========================================================= +// TraceAttack +//========================================================= +void CMBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + ALERT ( at_console, "%d\n", ptr->iHitgroup ); + + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + } + } +} +*/ + +//========================================================= +// TraceAttack +//========================================================= +void CMBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + +/*jlb + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + flDamage *= gSkillData.monHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.monChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.monStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.monArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.monLeg; + break; + default: + break; + } +jlb*/ + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this->edict(), flDamage, bitsDamageType ); + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. + +This version is used by Monsters. +================ +*/ +void CMBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) +{ + static int tracerCount; + int tracer; + TraceResult tr; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for (ULONG iShot = 1; iShot <= cShots; iShot++) + { + // get circular gaussian spread + float x, y, z; + do { + x = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + y = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + + Vector vecDir = vecDirShooting + + x * vecSpread.x * vecRight + + y * vecSpread.y * vecUp; + Vector vecEnd; + + vecEnd = vecSrc + vecDir * flDistance; + UTIL_TraceLine(vecSrc, vecEnd, dont_ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + + tracer = 0; + if (iTracerFreq != 0 && (tracerCount++ % iTracerFreq) == 0) + { + Vector vecTracerSrc; + + if ( IsPlayer() ) + {// adjust tracer position for player + vecTracerSrc = vecSrc + Vector ( 0 , 0 , -4 ) + gpGlobals->v_right * 2 + gpGlobals->v_forward * 16; + } + else + { + vecTracerSrc = vecSrc; + } + + if ( iTracerFreq != 1 ) // guns that always trace also always decal + tracer = 1; + switch( iBulletType ) + { + case BULLET_MONSTER_MP5: + case BULLET_MONSTER_9MM: + case BULLET_MONSTER_12MM: + default: + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecTracerSrc ); + WRITE_BYTE( TE_TRACER ); + WRITE_COORD( vecTracerSrc.x ); + WRITE_COORD( vecTracerSrc.y ); + WRITE_COORD( vecTracerSrc.z ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + MESSAGE_END(); + break; + } + } + // do damage, paint decals + if (tr.flFraction != 1.0) + { + if (UTIL_IsPlayer(tr.pHit)) // is this a player? + { + edict_t *pPlayer = tr.pHit; + + if ( iDamage ) + { + UTIL_TraceAttack(pPlayer, pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ((iDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB) ); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + else switch(iBulletType) + { + default: + case BULLET_MONSTER_9MM: + UTIL_TraceAttack(pPlayer, pevAttacker, gSkillData.monDmg9MM, vecDir, &tr, DMG_BULLET); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + + break; + + case BULLET_MONSTER_MP5: + UTIL_TraceAttack(pPlayer, pevAttacker, gSkillData.monDmgMP5, vecDir, &tr, DMG_BULLET); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + + break; + + case BULLET_MONSTER_12MM: + UTIL_TraceAttack(pPlayer, pevAttacker, gSkillData.monDmg12MM, vecDir, &tr, DMG_BULLET); + if ( !tracer ) + { + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + break; + + case BULLET_NONE: // FIX + UTIL_TraceAttack(pPlayer, pevAttacker, 50, vecDir, &tr, DMG_CLUB); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + // only decal glass + if ( !FNullEnt(tr.pHit) && VARS(tr.pHit)->rendermode != 0) + { + UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG(0,2) ); + } + + break; + } + } + else if (tr.pHit->v.euser4 != NULL) + { + CMBaseEntity *pMonster = GetClassPtr((CMBaseMonster *)VARS(tr.pHit)); + + if ( iDamage ) + { + pMonster->TraceAttack(pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ((iDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB) ); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + else switch(iBulletType) + { + default: + case BULLET_MONSTER_9MM: + pMonster->TraceAttack(pevAttacker, gSkillData.monDmg9MM, vecDir, &tr, DMG_BULLET); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + + break; + + case BULLET_MONSTER_MP5: + pMonster->TraceAttack(pevAttacker, gSkillData.monDmgMP5, vecDir, &tr, DMG_BULLET); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + + break; + + case BULLET_MONSTER_12MM: + pMonster->TraceAttack(pevAttacker, gSkillData.monDmg12MM, vecDir, &tr, DMG_BULLET); + if ( !tracer ) + { + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + break; + + case BULLET_NONE: // FIX + pMonster->TraceAttack(pevAttacker, 50, vecDir, &tr, DMG_CLUB); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + // only decal glass + if ( !FNullEnt(tr.pHit) && VARS(tr.pHit)->rendermode != 0) + { + UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG(0,2) ); + } + + break; + } + } + } + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, (flDistance * tr.flFraction) / 64.0 ); + } + ApplyMultiDamage(pev, pevAttacker); +} + + +void CMBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (BloodColor() == DONT_BLEED) + return; + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + 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--; + } + } +*/ + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + 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); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +//========================================================= +void CMBaseMonster :: 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, SVC_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() ); + } + } +} diff --git a/src/dlls/controller.cpp b/src/dlls/controller.cpp new file mode 100644 index 0000000..11a7b0a --- /dev/null +++ b/src/dlls/controller.cpp @@ -0,0 +1,1359 @@ +/*** +* +* Copyright (c) 1996-2001, 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 ) + +//========================================================= +// CONTROLLER +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "effects.h" +#include "schedule.h" +#include "weapons.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define CONTROLLER_AE_HEAD_OPEN 1 +#define CONTROLLER_AE_BALL_SHOOT 2 +#define CONTROLLER_AE_SMALL_SHOOT 3 +#define CONTROLLER_AE_POWERUP_FULL 4 +#define CONTROLLER_AE_POWERUP_HALF 5 + +#define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs + +const char *CMController::pAttackSounds[] = +{ + "controller/con_attack1.wav", + "controller/con_attack2.wav", + "controller/con_attack3.wav", +}; + +const char *CMController::pIdleSounds[] = +{ + "controller/con_idle1.wav", + "controller/con_idle2.wav", + "controller/con_idle3.wav", + "controller/con_idle4.wav", + "controller/con_idle5.wav", +}; + +const char *CMController::pAlertSounds[] = +{ + "controller/con_alert1.wav", + "controller/con_alert2.wav", + "controller/con_alert3.wav", +}; + +const char *CMController::pPainSounds[] = +{ + "controller/con_pain1.wav", + "controller/con_pain2.wav", + "controller/con_pain3.wav", +}; + +const char *CMController::pDeathSounds[] = +{ + "controller/con_die1.wav", + "controller/con_die2.wav", +}; + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMController :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMController :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CMController :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CMBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CMController::Killed( entvars_t *pevAttacker, int iGib ) +{ + // shut off balls + /* + m_iBall[0] = 0; + m_iBallTime[0] = gpGlobals->time + 4.0; + m_iBall[1] = 0; + m_iBallTime[1] = gpGlobals->time + 4.0; + */ + + // fade balls + if (m_pBall[0]) + { + m_pBall[0]->SUB_StartFadeOut(); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + m_pBall[1]->SUB_StartFadeOut(); + m_pBall[1] = NULL; + } + + CMBaseMonster::Killed( pevAttacker, iGib ); +} + + +void CMController::GibMonster( void ) +{ + // delete balls + if (m_pBall[0]) + { + UTIL_Remove( m_pBall[0]->edict() ); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + UTIL_Remove( m_pBall[1]->edict() ); + m_pBall[1] = NULL; + } + CMBaseMonster::GibMonster( ); +} + + + + +void CMController :: PainSound( void ) +{ + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); +} + +void CMController :: AlertSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); +} + +void CMController :: IdleSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pIdleSounds ); +} + +void CMController :: AttackSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAttackSounds ); +} + +void CMController :: DeathSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMController :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case CONTROLLER_AE_HEAD_OPEN: + { + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( 1 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 20 ); // life * 10 + WRITE_COORD( -32 ); // decay + MESSAGE_END(); + + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + + } + break; + + case CONTROLLER_AE_BALL_SHOOT: + { + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( 32 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 32 ); // decay + MESSAGE_END(); + + CMControllerHeadBall *pBall = CreateClassPtr((CMControllerHeadBall *)NULL); + + if (pBall != NULL) + { + pBall->pev->origin = vecStart; + pBall->pev->angles = pev->angles; + pBall->pev->owner = edict(); + + // Initialize these for entities who don't link to the world + pBall->pev->absmin = pBall->pev->origin + Vector(-1,-1,-1); + pBall->pev->absmax = pBall->pev->origin + Vector(1,1,1); + + pBall->Spawn(); + + pBall->pev->velocity = Vector( 0, 0, 32 ); + pBall->m_hEnemy = m_hEnemy; + } + m_iBall[0] = 0; + m_iBall[1] = 0; + } + break; + + case CONTROLLER_AE_SMALL_SHOOT: + { + AttackSound( ); + m_flShootTime = gpGlobals->time; + m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_FULL: + { + m_iBall[0] = 255; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_HALF: + { + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 192; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + default: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMController :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/controller.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 )); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->flags |= FL_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.controllerHealth; + pev->view_ofs = Vector( 0, 0, -2 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_FULL;// 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 CMController :: Precache() +{ + PRECACHE_MODEL("models/controller.mdl"); + + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + + PRECACHE_MODEL( "sprites/xspark4.spr"); + + CMControllerZapBall ZapBall; + CMControllerHeadBall HeadBall; + + ZapBall.Precache(); + HeadBall.Precache(); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +// Chase enemy schedule +Task_t tlControllerChaseEnemy[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + +}; + +Schedule_t slControllerChaseEnemy[] = +{ + { + tlControllerChaseEnemy, + ARRAYSIZE ( tlControllerChaseEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_TASK_FAILED, + 0, + "ControllerChaseEnemy" + }, +}; + + + +Task_t tlControllerStrafe[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slControllerStrafe[] = +{ + { + tlControllerStrafe, + ARRAYSIZE ( tlControllerStrafe ), + bits_COND_NEW_ENEMY, + 0, + "ControllerStrafe" + }, +}; + + +Task_t tlControllerTakeCover[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slControllerTakeCover[] = +{ + { + tlControllerTakeCover, + ARRAYSIZE ( tlControllerTakeCover ), + bits_COND_NEW_ENEMY, + 0, + "ControllerTakeCover" + }, +}; + + +Task_t tlControllerFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slControllerFail[] = +{ + { + tlControllerFail, + ARRAYSIZE ( tlControllerFail ), + 0, + 0, + "ControllerFail" + }, +}; + + + +DEFINE_CUSTOM_SCHEDULES( CMController ) +{ + slControllerChaseEnemy, + slControllerStrafe, + slControllerTakeCover, + slControllerFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMController, CMBaseMonster ); + + + +//========================================================= +// StartTask +//========================================================= +void CMController :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + CMBaseMonster :: StartTask ( pTask ); + break; + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, pTask->flData, (m_vecEnemyLKP - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + edict_t *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if (BuildNearestRoute( pEnemy->v.origin, pEnemy->v.view_ofs, pTask->flData, (pEnemy->v.origin - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + CMBaseMonster :: StartTask ( pTask ); + break; + } +} + + +Vector Intersect( Vector vecSrc, Vector vecDst, Vector vecMove, float flSpeed ) +{ + Vector vecTo = vecDst - vecSrc; + + float a = DotProduct( vecMove, vecMove ) - flSpeed * flSpeed; + float b = 0 * DotProduct(vecTo, vecMove); // why does this work? + float c = DotProduct( vecTo, vecTo ); + + float t; + if (a == 0) + { + t = c / (flSpeed * flSpeed); + } + else + { + t = b * b - 4 * a * c; + t = sqrt( t ) / (2.0 * a); + float t1 = -b +t; + float t2 = -b -t; + + if (t1 < 0 || t2 < t1) + t = t2; + else + t = t1; + } + + // ALERT( at_console, "Intersect %f\n", t ); + + if (t < 0.1) + t = 0.1; + if (t > 10.0) + t = 10.0; + + Vector vecHit = vecTo + vecMove * t; + return vecHit.Normalize( ) * flSpeed; +} + + +int CMController::LookupFloat( ) +{ + if (m_velocity.Length( ) < 32.0) + { + return LookupSequence( "up" ); + } + + UTIL_MakeAimVectors( pev->angles ); + float x = DotProduct( gpGlobals->v_forward, m_velocity ); + float y = DotProduct( gpGlobals->v_right, m_velocity ); + float z = DotProduct( gpGlobals->v_up, m_velocity ); + + if (fabs(x) > fabs(y) && fabs(x) > fabs(z)) + { + if (x > 0) + return LookupSequence( "forward"); + else + return LookupSequence( "backward"); + } + else if (fabs(y) > fabs(z)) + { + if (y > 0) + return LookupSequence( "right"); + else + return LookupSequence( "left"); + } + else + { + if (z > 0) + return LookupSequence( "up"); + else + return LookupSequence( "down"); + } +} + + +//========================================================= +// RunTask +//========================================================= +void CMController :: RunTask ( Task_t *pTask ) +{ + + if (m_flShootEnd > gpGlobals->time) + { + Vector vecHand, vecAngle; + + GetAttachment( 2, vecHand, vecAngle ); + + while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) + { + Vector vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + Vector vecDir; + + if (m_hEnemy != NULL) + { + if (HasConditions( bits_COND_SEE_ENEMY )) + { + m_vecEstVelocity = m_vecEstVelocity * 0.5 + m_hEnemy->v.velocity * 0.5; + } + else + { + m_vecEstVelocity = m_vecEstVelocity * 0.8; + } + vecDir = Intersect( vecSrc, UTIL_BodyTarget( m_hEnemy, pev->origin ), m_vecEstVelocity, gSkillData.controllerSpeedBall ); + float delta = 0.03490; // +-2 degree + vecDir = vecDir + Vector( RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ) ) * gSkillData.controllerSpeedBall; + + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + + CMControllerZapBall *pBall = CreateClassPtr((CMControllerZapBall *)NULL); + + if (pBall != NULL) + { + pBall->pev->origin = vecSrc; + pBall->pev->angles = pev->angles; + pBall->pev->owner = edict(); + + // Initialize these for entities who don't link to the world + pBall->pev->absmin = pBall->pev->origin + Vector(-1,-1,-1); + pBall->pev->absmax = pBall->pev->origin + Vector(1,1,1); + + pBall->Spawn(); + + pBall->pev->velocity = vecDir; + } + } + m_flShootTime += 0.2; + } + + if (m_flShootTime > m_flShootEnd) + { + m_iBall[0] = 64; + m_iBallTime[0] = m_flShootEnd; + m_iBall[1] = 64; + m_iBallTime[1] = m_flShootEnd; + m_fInCombat = FALSE; + } + } + + switch ( pTask->iTask ) + { + case TASK_WAIT_FOR_MOVEMENT: + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + case TASK_WAIT_PVS: + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if (m_fSequenceFinished) + { + m_fInCombat = FALSE; + } + + CMBaseMonster :: RunTask ( pTask ); + + if (!m_fInCombat) + { + if (HasConditions ( bits_COND_CAN_RANGE_ATTACK1 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK1 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else if (HasConditions ( bits_COND_CAN_RANGE_ATTACK2 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK2 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else + { + int iFloat = LookupFloat( ); + if (m_fSequenceFinished || iFloat != pev->sequence) + { + pev->sequence = iFloat; + pev->frame = 0; + ResetSequenceInfo( ); + } + } + } + break; + default: + CMBaseMonster :: 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 *CMController :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + break; + + case MONSTERSTATE_ALERT: + break; + + case MONSTERSTATE_COMBAT: + { + Vector vecTmp = Intersect( Vector( 0, 0, 0 ), Vector( 100, 4, 7 ), Vector( 2, 10, -3 ), 20.0 ); + + // dead enemy + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + // m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + // m_iFrustration++; + } + } + break; + } + + return CMBaseMonster :: GetSchedule(); +} + + + +//========================================================= +//========================================================= +Schedule_t* CMController :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_CHASE_ENEMY: + return slControllerChaseEnemy; + case SCHED_RANGE_ATTACK1: + return slControllerStrafe; + case SCHED_RANGE_ATTACK2: + case SCHED_MELEE_ATTACK1: + case SCHED_MELEE_ATTACK2: + case SCHED_TAKE_COVER_FROM_ENEMY: + return slControllerTakeCover; + case SCHED_FAIL: + return slControllerFail; + } + + return CMBaseMonster :: GetScheduleOfType( Type ); +} + + + + + +//========================================================= +// CheckRangeAttack1 - shoot a bigass energy ball out of their head +// +//========================================================= +BOOL CMController :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 256 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CMController :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 64 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CMController :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + return FALSE; +} + + +void CMController :: SetActivity ( Activity NewActivity ) +{ + CMBaseMonster::SetActivity( NewActivity ); + + switch ( m_Activity) + { + case ACT_WALK: + m_flGroundSpeed = 100; + break; + default: + m_flGroundSpeed = 100; + break; + } +} + + + +//========================================================= +// RunAI +//========================================================= +void CMController :: RunAI( void ) +{ + CMBaseMonster :: RunAI(); + Vector vecStart, angleGun; + + if ( HasMemory( bits_MEMORY_KILLED ) ) + return; + + for (int i = 0; i < 2; i++) + { + if (m_pBall[i] == NULL) + { + m_pBall[i] = CMSprite::SpriteCreate( "sprites/xspark4.spr", pev->origin, TRUE ); + if (m_pBall[i] != NULL) + { + m_pBall[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pBall[i]->SetAttachment( edict(), (i + 3) ); + m_pBall[i]->SetScale( 1.0 ); + } + else + continue; + } + + float t = m_iBallTime[i] - gpGlobals->time; + if (t > 0.1) + t = 0.1 / t; + else + t = 1.0; + + m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t; + + m_pBall[i]->SetBrightness( m_iBallCurrent[i] ); + + GetAttachment( i + 2, vecStart, angleGun ); + UTIL_SetOrigin( m_pBall[i]->pev, vecStart ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * (i + 3) ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( m_iBallCurrent[i] / 8 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 5 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } +} + + +extern void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ); + +void CMController::Stop( void ) +{ + m_IdealActivity = GetStoppedActivity(); +} + + +#define DIST_TO_CHECK 200 +void CMController :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + float flMoveDist; + Vector vecDir; + Vector vecApex; + edict_t *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + 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, 0, 255 ); +#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; + + if (m_flGroundSpeed == 0) + { + m_flGroundSpeed = 100; + // TaskFail( ); + // return; + } + + flMoveDist = m_flGroundSpeed * flInterval; + + do + { + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + // 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 ) + { + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + edict_t *pBlocker = gpGlobals->trace_ent; + if (pBlocker != NULL) + { + Blocked( pBlocker ); + } + if ( pBlocker && m_moveWaitTime > 0 && UTIL_IsMoving(pBlocker) && !UTIL_IsPlayer(pBlocker) && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + // ALERT( at_aiconsole, "Move %s!!!\n", STRING( pBlocker->pev->classname ) ); + return; + } + } + 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(); + if ( m_moveWaitTime > 0 ) + { + FRefreshRoute(); + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime * 0.5; + } + else + { + TaskFail(); + ALERT( at_aiconsole, "Failed to move!\n" ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < flMoveDist) + { + MoveExecute( pTargetEnt, vecDir, flCheckDist / m_flGroundSpeed ); + + // ALERT( at_console, "%.02f\n", flInterval ); + AdvanceRoute( flWaypointDist ); + flMoveDist -= flCheckDist; + } + else + { + MoveExecute( pTargetEnt, vecDir, flMoveDist / m_flGroundSpeed ); + + if ( ShouldAdvanceRoute( flWaypointDist - flMoveDist ) ) + { + AdvanceRoute( flWaypointDist ); + } + flMoveDist = 0; + } + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } + } while (flMoveDist > 0 && flCheckDist > 0); + + // cut corner? + if (flWaypointDist < 128) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + + if (m_flGroundSpeed > 100) + m_flGroundSpeed -= 40; + } + else + { + if (m_flGroundSpeed < 400) + m_flGroundSpeed += 10; + } +} + + + +BOOL CMController:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= 32 ) + { + return TRUE; + } + + return FALSE; +} + + +int CMController :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, edict_t *pTarget, float *pflDist ) +{ + 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 == gpGlobals->trace_ent) ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +void CMController::MoveExecute( edict_t *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + // ALERT( at_console, "move %.4f %.4f %.4f : %f\n", vecDir.x, vecDir.y, vecDir.z, flInterval ); + + // float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + // UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flTotal, MOVE_STRAFE ); + + m_velocity = m_velocity * 0.8 + m_flGroundSpeed * vecDir * 0.2; + + UTIL_MoveToOrigin ( ENT(pev), pev->origin + m_velocity, m_velocity.Length() * flInterval, MOVE_STRAFE ); + +} + + +void CMControllerHeadBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/xspark4.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 2.0; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( HuntThink ); + SetTouch( BounceTouch ); + + m_vecIdeal = Vector( 0, 0, 0 ); + + pev->nextthink = gpGlobals->time + 0.1; + + m_hOwner = pev->owner; + pev->dmgtime = gpGlobals->time; +} + + +void CMControllerHeadBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/xspark1.spr"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CMControllerHeadBall :: HuntThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + pev->renderamt -= 5; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->renderamt / 16 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + + // check world boundaries + if (gpGlobals->time - pev->dmgtime > 5 || pev->renderamt < 64 || m_hEnemy == NULL || m_hOwner == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + SetTouch( NULL ); + UTIL_Remove( this->edict() ); + return; + } + + MovetoTarget( UTIL_Center( m_hEnemy ) ); + + if ((UTIL_Center(m_hEnemy) - pev->origin).Length() < 64) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, UTIL_Center(m_hEnemy), dont_ignore_monsters, ENT(pev), &tr ); + + if (tr.pHit != NULL && tr.pHit->v.takedamage) + { + ClearMultiDamage( ); + + if (UTIL_IsPlayer(tr.pHit)) + UTIL_TraceAttack( tr.pHit, VARS(m_hOwner), gSkillData.controllerDmgZap, pev->velocity, &tr, DMG_SHOCK ); + else if (tr.pHit->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(tr.pHit)); + pMonster->TraceAttack( VARS(m_hOwner), gSkillData.controllerDmgZap, pev->velocity, &tr, DMG_SHOCK ); + } + + ApplyMultiDamage( pev, VARS(m_hOwner) ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); + + m_flNextAttack = gpGlobals->time + 3.0; + + SetThink( DieThink ); + pev->nextthink = gpGlobals->time + 0.3; + } + + // Crawl( ); +} + + +void CMControllerHeadBall :: DieThink( void ) +{ + UTIL_Remove( this->edict() ); +} + + +void CMControllerHeadBall :: MovetoTarget( Vector vecTarget ) +{ + // accelerate + float flSpeed = m_vecIdeal.Length(); + if (flSpeed == 0) + { + m_vecIdeal = pev->velocity; + flSpeed = m_vecIdeal.Length(); + } + + if (flSpeed > 400) + { + m_vecIdeal = m_vecIdeal.Normalize( ) * 400; + } + m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 100; + pev->velocity = m_vecIdeal; +} + + + +void CMControllerHeadBall :: Crawl( void ) +{ + + Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); + Vector vecPnt = pev->origin + pev->velocity * 0.3 + vecAim * 64; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( vecPnt.x); + WRITE_COORD( vecPnt.y); + WRITE_COORD( vecPnt.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); +} + + +void CMControllerHeadBall::BounceTouch( edict_t *pOther ) +{ + Vector vecDir = m_vecIdeal.Normalize( ); + + TraceResult tr = UTIL_GetGlobalTrace( ); + + float n = -DotProduct(tr.vecPlaneNormal, vecDir); + + vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; + + m_vecIdeal = vecDir * m_vecIdeal.Length(); +} + + +void CMControllerZapBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/xspark4.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 0.5; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( AnimateThink ); + SetTouch( ExplodeTouch ); + + m_hOwner = pev->owner; + pev->dmgtime = gpGlobals->time; // keep track of when ball spawned + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CMControllerZapBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/xspark4.spr"); + // PRECACHE_SOUND("debris/zap4.wav"); + // PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CMControllerZapBall :: AnimateThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + pev->frame = ((int)pev->frame + 1) % 11; + + if (gpGlobals->time - pev->dmgtime > 5 || pev->velocity.Length() < 10) + { + SetTouch( NULL ); + UTIL_Remove( this->edict() ); + } +} + + +void CMControllerZapBall::ExplodeTouch( edict_t *pOther ) +{ + if (pOther->v.takedamage) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + entvars_t *pevOwner; + if (m_hOwner != NULL) + { + pevOwner = VARS(m_hOwner); + } + else + { + pevOwner = pev; + } + + ClearMultiDamage( ); + if (UTIL_IsPlayer(pOther)) + UTIL_TraceAttack(pOther, pevOwner, gSkillData.controllerDmgBall, pev->velocity.Normalize(), &tr, DMG_ENERGYBEAM ); + else if (pOther->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + pMonster->TraceAttack(pevOwner, gSkillData.controllerDmgBall, pev->velocity.Normalize(), &tr, DMG_ENERGYBEAM ); + } + ApplyMultiDamage( pevOwner, pevOwner ); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.3, ATTN_NORM, 0, RANDOM_LONG( 90, 99 ) ); + + } + + UTIL_Remove( this->edict() ); +} + + +#endif // !OEM && !HLDEMO diff --git a/src/dlls/decals.h b/src/dlls/decals.h new file mode 100644 index 0000000..e18ab71 --- /dev/null +++ b/src/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2001, 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 DECALS_H +#define DECALS_H + +// +// Dynamic Decals +// +enum decal_e +{ + DECAL_GUNSHOT1 = 0, + DECAL_GUNSHOT2, + DECAL_GUNSHOT3, + DECAL_GUNSHOT4, + DECAL_GUNSHOT5, + DECAL_LAMBDA1, + DECAL_LAMBDA2, + DECAL_LAMBDA3, + DECAL_LAMBDA4, + DECAL_LAMBDA5, + DECAL_LAMBDA6, + DECAL_SCORCH1, + DECAL_SCORCH2, + DECAL_BLOOD1, + DECAL_BLOOD2, + DECAL_BLOOD3, + DECAL_BLOOD4, + DECAL_BLOOD5, + DECAL_BLOOD6, + DECAL_YBLOOD1, + DECAL_YBLOOD2, + DECAL_YBLOOD3, + DECAL_YBLOOD4, + DECAL_YBLOOD5, + DECAL_YBLOOD6, + DECAL_GLASSBREAK1, + DECAL_GLASSBREAK2, + DECAL_GLASSBREAK3, + DECAL_BIGSHOT1, + DECAL_BIGSHOT2, + DECAL_BIGSHOT3, + DECAL_BIGSHOT4, + DECAL_BIGSHOT5, + DECAL_SPIT1, + DECAL_SPIT2, + DECAL_BPROOF1, // Bulletproof glass decal + DECAL_GARGSTOMP1, // Gargantua stomp crack + DECAL_SMALLSCORCH1, // Small scorch mark + DECAL_SMALLSCORCH2, // Small scorch mark + DECAL_SMALLSCORCH3, // Small scorch mark + DECAL_MOMMABIRTH, // Big momma birth splatter + DECAL_MOMMASPLAT, +}; + +typedef struct +{ + char *name; + int index; +} DLL_DECALLIST; + +extern DLL_DECALLIST gDecals[]; + +#endif // DECALS_H diff --git a/src/dlls/defaultai.cpp b/src/dlls/defaultai.cpp new file mode 100644 index 0000000..099c981 --- /dev/null +++ b/src/dlls/defaultai.cpp @@ -0,0 +1,1084 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "nodes.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, + 0, + "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, + 0, + "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, + 0, + "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, + + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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" + }, +}; + + +//========================================================= +// 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 *CMBaseMonster::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, + slCower, + slTakeCoverFromOrigin, + slTakeCoverFromBestSound, + slTakeCoverFromEnemy, + slFail +}; + +Schedule_t *CMBaseMonster::ScheduleFromName( const char *pName ) +{ + return ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) ); +} + + +Schedule_t *CMBaseMonster :: 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* CMBaseMonster :: GetScheduleOfType ( int Type ) +{ +// ALERT ( at_console, "Sched Type:%d\n", Type ); + switch ( Type ) + { + 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/src/dlls/defaultai.h b/src/dlls/defaultai.h new file mode 100644 index 0000000..32657ab --- /dev/null +++ b/src/dlls/defaultai.h @@ -0,0 +1,98 @@ +/*** +* +* Copyright (c) 1996-2001, 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/src/dlls/dllapi.cpp b/src/dlls/dllapi.cpp new file mode 100644 index 0000000..70c8251 --- /dev/null +++ b/src/dlls/dllapi.cpp @@ -0,0 +1,1157 @@ +// +// botman's monster - MetaMOD plugin +// +// dllapi.cpp +// + +/* + * This 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 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 code; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + + +#include "extdll.h" +#include "dllapi.h" +#include "meta_api.h" + +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "weapons.h" +#include "hornet.h" +#include "decals.h" +#include "shake.h" +#include "skill.h" + +extern globalvars_t *gpGlobals; +extern enginefuncs_t g_engfuncs; +extern gamedll_funcs_t *gpGamedllFuncs; + +extern cvar_t *dllapi_log; +extern cvar_t *monster_spawn; + +cvar_t *g_psv_gravity = NULL; + +DLL_DECALLIST gDecals[] = { + { "{shot1", -1 }, // DECAL_GUNSHOT1 + { "{shot2", -1 }, // DECAL_GUNSHOT2 + { "{shot3", -1 }, // DECAL_GUNSHOT3 + { "{shot4", -1 }, // DECAL_GUNSHOT4 + { "{shot5", -1 }, // DECAL_GUNSHOT5 + { "{lambda01", -1 }, // DECAL_LAMBDA1 + { "{lambda02", -1 }, // DECAL_LAMBDA2 + { "{lambda03", -1 }, // DECAL_LAMBDA3 + { "{lambda04", -1 }, // DECAL_LAMBDA4 + { "{lambda05", -1 }, // DECAL_LAMBDA5 + { "{lambda06", -1 }, // DECAL_LAMBDA6 + { "{scorch1", -1 }, // DECAL_SCORCH1 + { "{scorch2", -1 }, // DECAL_SCORCH2 + { "{blood1", -1 }, // DECAL_BLOOD1 + { "{blood2", -1 }, // DECAL_BLOOD2 + { "{blood3", -1 }, // DECAL_BLOOD3 + { "{blood4", -1 }, // DECAL_BLOOD4 + { "{blood5", -1 }, // DECAL_BLOOD5 + { "{blood6", -1 }, // DECAL_BLOOD6 + { "{yblood1", -1 }, // DECAL_YBLOOD1 + { "{yblood2", -1 }, // DECAL_YBLOOD2 + { "{yblood3", -1 }, // DECAL_YBLOOD3 + { "{yblood4", -1 }, // DECAL_YBLOOD4 + { "{yblood5", -1 }, // DECAL_YBLOOD5 + { "{yblood6", -1 }, // DECAL_YBLOOD6 + { "{break1", -1 }, // DECAL_GLASSBREAK1 + { "{break2", -1 }, // DECAL_GLASSBREAK2 + { "{break3", -1 }, // DECAL_GLASSBREAK3 + { "{bigshot1", -1 }, // DECAL_BIGSHOT1 + { "{bigshot2", -1 }, // DECAL_BIGSHOT2 + { "{bigshot3", -1 }, // DECAL_BIGSHOT3 + { "{bigshot4", -1 }, // DECAL_BIGSHOT4 + { "{bigshot5", -1 }, // DECAL_BIGSHOT5 + { "{spit1", -1 }, // DECAL_SPIT1 + { "{spit2", -1 }, // DECAL_SPIT2 + { "{bproof1", -1 }, // DECAL_BPROOF1 + { "{gargstomp", -1 }, // DECAL_GARGSTOMP1, // Gargantua stomp crack + { "{smscorch1", -1 }, // DECAL_SMALLSCORCH1, // Small scorch mark + { "{smscorch2", -1 }, // DECAL_SMALLSCORCH2, // Small scorch mark + { "{smscorch3", -1 }, // DECAL_SMALLSCORCH3, // Small scorch mark + { "{mommablob", -1 }, // DECAL_MOMMABIRTH // BM Birth spray + { "{mommablob", -1 }, // DECAL_MOMMASPLAT // BM Mortar spray?? need decal +}; + +monster_type_t monster_types[]= +{ + "agrunt", FALSE, + "apache", FALSE, + "barney", FALSE, + "bigmomma", FALSE, + "bullsquid", FALSE, + "controller", FALSE, + "hassassin", FALSE, + "headcrab", FALSE, + "hgrunt", FALSE, + "houndeye", FALSE, + "islave", FALSE, + "scientist", FALSE, + "snark", FALSE, + "zombie", FALSE, + "", FALSE}; + +monster_t monsters[MAX_MONSTER_ENTS]; +int monster_ents_used = 0; + +monster_spawnpoint_t monster_spawnpoint[MAX_MONSTERS]; +int monster_spawn_count = 0; + +float check_respawn_time; + +bool process_monster_cfg(void); +bool process_monster_precache_cfg(void); + + +int GetMonsterIndex(void) +{ + int monster_index = -1; + + for (int index = 0; index < MAX_MONSTER_ENTS; index++) + { + if (monsters[index].monster_pent == 0) + { + monster_index = index; + break; + } + } + + if (monster_index == -1) + return -1; + + if (monster_index >= monster_ents_used) + monster_ents_used = monster_index + 1; // monster index is 0 based + + return monster_index; +} + + +void FreeMonsterIndex(int index) +{ + int idx = monsters[index].respawn_index; + + if (idx != -1) + { + monster_spawnpoint[idx].need_to_respawn = TRUE; + monster_spawnpoint[idx].respawn_time = gpGlobals->time + + monster_spawnpoint[idx].delay; + } + + delete monsters[index].pMonster; + + monsters[index].monster_index = 0; + monsters[index].monster_pent = NULL; + monsters[index].killed = FALSE; + monsters[index].pMonster = NULL; + + if (index == monster_ents_used-1) + { + while (monsters[index].monster_index == 0) + { + index--; + monster_ents_used--; + if (monster_ents_used == 0) + break; + } + } +} + + +void Remove_Entity(edict_t *pEdict) +{ + for (int index = 0; index < monster_ents_used; index++) + { + if (monsters[index].monster_pent == pEdict) + { + FreeMonsterIndex(index); + break; + } + } + + REMOVE_ENTITY(pEdict); +} + + +void monster_unload(void) +{ + // the plugin is being unloaded, remove any currently spawned monster... + + for (int index = 0; index < MAX_MONSTER_ENTS; index++) + { + if (monsters[index].pMonster != NULL) + { + monsters[index].monster_pent->v.flags |= FL_KILLME; + + delete monsters[index].pMonster; + + monsters[index].monster_index = 0; + monsters[index].monster_pent = NULL; + monsters[index].killed = FALSE; + monsters[index].pMonster = NULL; + } + } +} + + +void check_monster_hurt(edict_t *pAttacker) +{ + int index; + + for (index = 0; index < monster_ents_used; index++) + { + if (monsters[index].monster_index) + { + edict_t *pent = (*g_engfuncs.pfnPEntityOfEntIndex)(monsters[index].monster_index); + + if (pent) + { + if (pent->v.health < pent->v.fuser4) + { + if (pent->v.takedamage != DAMAGE_NO) + { + TraceResult tr; + Vector vecSrc, vecSpot; + float distance, damage; + + // location of attacker and location of enemy... + vecSrc = pAttacker->v.origin + pAttacker->v.view_ofs; + vecSpot = pent->v.origin; + + // distance the blood can travel from the body... + distance = (vecSpot - vecSrc).Length() + 100.0f; + + // use aiming angles of attacker to trace blood splatter... + UTIL_MakeVectors(pAttacker->v.v_angle); + + // start just beyond the attacker's body... + vecSrc = vecSrc + gpGlobals->v_forward * 20; + vecSpot = vecSrc + gpGlobals->v_forward * distance; + + // trace a line ignoring enemies body... + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, pent, &tr ); + + damage = pent->v.fuser4 - pent->v.health; + + // restore previous health, then do the damage (again) + pent->v.health = pent->v.fuser4; + + ClearMultiDamage( ); + monsters[index].pMonster->TraceAttack( VARS(pAttacker), damage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, DMG_BULLET ); + ApplyMultiDamage( VARS(pAttacker), VARS(pAttacker) ); + } + + // save the new current health as previous health... + pent->v.fuser4 = pent->v.health; + } + } + else + { + // the entity no longer exists and we didn't catch it dying + FreeMonsterIndex(index); + } + } + } +} + + +void check_monster_dead(void) +{ + for (int index = 0; index < monster_ents_used; index++) + { + if (monsters[index].monster_index) + { + edict_t *pent = (*g_engfuncs.pfnPEntityOfEntIndex)(monsters[index].monster_index); + + if (pent) + { + if (pent->v.flags & FL_KILLME) // func_wall was "killed" + { + if (pent->v.flags & FL_MONSTER) // is this a monster? + { + if (monsters[index].killed == FALSE) + { + pent->v.flags &= ~FL_KILLME; // clear FL_KILLME bit + + pent->v.deadflag = DEAD_NO; // bring back to life + + monsters[index].pMonster->Killed(VARS(pent), 0); + monsters[index].killed = TRUE; + } + } + else // normal entity + { + FreeMonsterIndex(index); + } + } + } + else + { + FreeMonsterIndex(index); + } + } + } +} + + +bool spawn_monster(int monster_type, Vector origin, float angle, int respawn_index) +{ + int monster_index; + edict_t *monster_pent; + + if ((monster_index = GetMonsterIndex()) == -1) + { + META_CONS("[MONSTER] ERROR: No FREE Monster edicts!"); + LOG_MESSAGE(PLID, "ERROR: No FREE Monster edicts!"); + return TRUE; + } + + // was this monster NOT precached? + if (monster_types[monster_type].need_to_precache == FALSE) + { + char msg[256]; + + META_CONS("[MONSTER] ERROR: You can't spawn monster %s since it wasn't precached!", + monster_types[monster_type].name); + + META_CONS("[MONSTER] valid precached monster names are:"); + msg[0] = 0; + for (int index = 0; monster_types[index].name[0]; index++) + { + if (monster_types[index].need_to_precache == TRUE) + { + strcat(msg, monster_types[index].name); + strcat(msg, " "); + if (strlen(msg) > 60) + { + META_CONS("[MONSTER] %s", msg); + msg[0] = 0; + } + } + } + if (msg[0]) + META_CONS("[MONSTER] %s", msg); + + return TRUE; + } + + switch (monster_type) + { + case 0: monsters[monster_index].pMonster = CreateClassPtr((CMAGrunt *)NULL); + break; + case 1: monsters[monster_index].pMonster = CreateClassPtr((CMApache *)NULL); + break; + case 2: monsters[monster_index].pMonster = CreateClassPtr((CMBarney *)NULL); + break; + case 3: monsters[monster_index].pMonster = CreateClassPtr((CMBigMomma *)NULL); + break; + case 4: monsters[monster_index].pMonster = CreateClassPtr((CMBullsquid *)NULL); + break; + case 5: monsters[monster_index].pMonster = CreateClassPtr((CMController *)NULL); + break; + case 6: monsters[monster_index].pMonster = CreateClassPtr((CMHAssassin *)NULL); + break; + case 7: monsters[monster_index].pMonster = CreateClassPtr((CMHeadCrab *)NULL); + break; + case 8: monsters[monster_index].pMonster = CreateClassPtr((CMHGrunt *)NULL); + break; + case 9: monsters[monster_index].pMonster = CreateClassPtr((CMHoundeye *)NULL); + break; + case 10: monsters[monster_index].pMonster = CreateClassPtr((CMISlave *)NULL); + break; + case 11: monsters[monster_index].pMonster = CreateClassPtr((CMScientist *)NULL); + break; + case 12: monsters[monster_index].pMonster = CreateClassPtr((CMSqueakGrenade *)NULL); + break; + case 13: monsters[monster_index].pMonster = CreateClassPtr((CMZombie *)NULL); + break; + } + + if (monsters[monster_index].pMonster == NULL) + { + META_CONS("[MONSTER] ERROR: Error Creating Monster!" ); + LOG_MESSAGE(PLID, "ERROR: Error Creating Monster!"); + return TRUE; + } + + monsters[monster_index].respawn_index = respawn_index; + + monster_pent = ENT(monsters[monster_index].pMonster->pev); + monsters[monster_index].monster_pent = monster_pent; + + monsters[monster_index].monster_index = (*g_engfuncs.pfnIndexOfEdict)(monster_pent); + + monster_pent->v.origin = origin; + monster_pent->v.angles.y = angle; + + monsters[monster_index].pMonster->Spawn(); + + monster_pent->v.spawnflags = SF_MONSTER_FADECORPSE; + monster_pent->v.fuser4 = monster_pent->v.health; // save the original health + + return FALSE; +} + + +void check_respawn(void) +{ + int type_index; + int monster_type; + Vector origin; + float angle; + + if (!monster_spawn->value) + return; // monster_spawn is turned off, retry again later + + for (int index=0; index < monster_spawn_count; index++) + { + if (monster_spawnpoint[index].need_to_respawn && + (monster_spawnpoint[index].respawn_time <= gpGlobals->time)) + { + monster_spawnpoint[index].need_to_respawn = FALSE; + + type_index = RANDOM_LONG(0, monster_spawnpoint[index].monster_count-1); + + monster_type = monster_spawnpoint[index].monster[type_index]; + + origin = monster_spawnpoint[index].origin; + + angle = monster_spawnpoint[index].angle_min; + + if (angle != monster_spawnpoint[index].angle_max) + { + angle = RANDOM_FLOAT(angle, monster_spawnpoint[index].angle_max); + } + + if (spawn_monster(monster_type, origin, angle, index)) + { + // spawn_monster failed, retry again after delay... + monster_spawnpoint[index].need_to_respawn = TRUE; + monster_spawnpoint[index].respawn_time = gpGlobals->time + + monster_spawnpoint[index].delay; + } + } + } +} + + +DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood +DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; +DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot + +void world_precache(void) +{ + g_sModelIndexFireball = PRECACHE_MODEL ("sprites/zerogxplode.spr");// fireball + g_sModelIndexSmoke = PRECACHE_MODEL ("sprites/steam1.spr");// smoke + g_sModelIndexWExplosion = PRECACHE_MODEL ("sprites/WXplo1.spr");// underwater fireball + g_sModelIndexBubbles = PRECACHE_MODEL ("sprites/bubble.spr");//bubbles + g_sModelIndexBloodSpray = PRECACHE_MODEL ("sprites/bloodspray.spr"); // initial blood + g_sModelIndexBloodDrop = PRECACHE_MODEL ("sprites/blood.spr"); // splattered blood + + g_sModelIndexLaser = PRECACHE_MODEL( (char *)g_pModelNameLaser ); + g_sModelIndexLaserDot = PRECACHE_MODEL("sprites/laserdot.spr"); + + PRECACHE_MODEL ("models/w_grenade.mdl"); +} + + +void MonsterCommand(void) +{ + int index; + char msg[256]; + int monster_type = -1; + + if (CMD_ARGC() >= 3) + { + const char *parg1 = CMD_ARGV(1); + + // check for a valid monster name... + for (index = 0; monster_types[index].name[0]; index++) + { + if (strcmp(parg1, monster_types[index].name) == 0) + { + monster_type = index; + break; + } + } + + if (monster_type != -1) + { + // check for a valid player name or index... + const char *parg2 = CMD_ARGV(2); + int player_index = -1; + edict_t *pPlayer; + const char *player_name; + + if (*parg2 == '#') // player index + { + if (sscanf(&parg2[1], "%d", &player_index) != 1) + player_index = -1; + + if ((player_index < 1) || (player_index > gpGlobals->maxClients)) + { + META_CONS("[MONSTER] invalid player index! (%d to %d allowed)", + 1, gpGlobals->maxClients); + player_index = -1; + } + } + else + { + for (index = 1; index <= gpGlobals->maxClients; index++) + { + pPlayer = INDEXENT(index); + + if (pPlayer && !pPlayer->free) + { + if (stricmp(STRING(pPlayer->v.netname), parg2) == 0) + { + player_index = index; // found the matching player name + break; + } + } + } + + if (player_index == -1) + { + META_CONS("[MONSTER] can't find player named \"%s\"!", parg2); + return; + } + } + + if (player_index != -1) + { + pPlayer = INDEXENT(player_index); + + if ((pPlayer == NULL) || (pPlayer->free)) + { + META_CONS("[MONSTER] player index %d is not a valid player!", + player_index); + return; + } + + player_name = STRING(pPlayer->v.netname); + + if (player_name[0] == 0) + { + META_CONS("[MONSTER] player index %d is not a valid player!", + player_index); + return; + } + + if (!UTIL_IsAlive(pPlayer)) + { + META_CONS("[MONSTER] player \"%s\" is not alive or is an observer!", + player_name); + return; + } + + TraceResult tr; + Vector origin = pPlayer->v.origin; + Vector view_angle = pPlayer->v.v_angle; + Vector v_src, v_dest; + float monster_angle; + + // try to determine the best place to spawn the monster... + + view_angle.x = 0; // zero the pitch (level horizontally) + + UTIL_MakeVectors(view_angle); + + v_src = origin + Vector(0, 0, 20); // up a little bit + v_dest = v_src + gpGlobals->v_forward * 128; // in front of player + + UTIL_TraceHull(v_src, v_dest, dont_ignore_monsters, 1, pPlayer, &tr); + + if (tr.flFraction >= 1.0) + { + v_src = v_dest; + v_dest = v_dest + Vector(0, 0, -200); // down to ground + + // try to find the floor... + UTIL_TraceLine(v_src, v_dest, dont_ignore_monsters, pPlayer, &tr); + + if (tr.flFraction < 1.0) // hit something? + { + monster_angle = view_angle.y + 180.0f; // face the player + if (monster_angle > 360) + monster_angle -= 360; + if (monster_angle < 0) + monster_angle += 360; + + spawn_monster(monster_type, v_src, monster_angle, -1); + + return; + } + } + + v_src = origin + Vector(0, 0, 20); // up a little bit + // diagonally in front and to the left of the player... + v_dest = v_src + gpGlobals->v_forward * 90 + gpGlobals->v_right * -90; + + UTIL_TraceHull(v_src, v_dest, dont_ignore_monsters, 1, pPlayer, &tr); + + if (tr.flFraction >= 1.0) + { + v_src = v_dest; + v_dest = v_dest + Vector(0, 0, -200); // down to ground + + // try to find the floor... + UTIL_TraceLine(v_src, v_dest, dont_ignore_monsters, pPlayer, &tr); + + if (tr.flFraction < 1.0) // hit something? + { + monster_angle = view_angle.y - 135.0f; // face the player + if (monster_angle > 360) + monster_angle -= 360; + if (monster_angle < 0) + monster_angle += 360; + + spawn_monster(monster_type, v_src, monster_angle, -1); + + return; + } + } + + v_src = origin + Vector(0, 0, 20); // up a little bit + // diagonally in front and to the right of the player... + v_dest = v_src + gpGlobals->v_forward * 90 + gpGlobals->v_right * 90; + + UTIL_TraceHull(v_src, v_dest, dont_ignore_monsters, 1, pPlayer, &tr); + + if (tr.flFraction >= 1.0) + { + v_src = v_dest; + v_dest = v_dest + Vector(0, 0, -200); // down to ground + + // try to find the floor... + UTIL_TraceLine(v_src, v_dest, dont_ignore_monsters, pPlayer, &tr); + + if (tr.flFraction < 1.0) // hit something? + { + monster_angle = view_angle.y + 135.0f; // face the player + if (monster_angle > 360) + monster_angle -= 360; + if (monster_angle < 0) + monster_angle += 360; + + spawn_monster(monster_type, v_src, monster_angle, -1); + + return; + } + } + + v_src = origin + Vector(0, 0, 20); // up a little bit + v_dest = v_src + gpGlobals->v_right * 128; // to the right + + UTIL_TraceHull(v_src, v_dest, dont_ignore_monsters, 1, pPlayer, &tr); + + if (tr.flFraction >= 1.0) + { + v_src = v_dest; + v_dest = v_dest + Vector(0, 0, -200); // down to ground + + // try to find the floor... + UTIL_TraceLine(v_src, v_dest, dont_ignore_monsters, pPlayer, &tr); + + if (tr.flFraction < 1.0) // hit something? + { + monster_angle = view_angle.y + 90.0f; // face the player + if (monster_angle > 360) + monster_angle -= 360; + if (monster_angle < 0) + monster_angle += 360; + + spawn_monster(monster_type, v_src, monster_angle, -1); + + return; + } + } + + v_src = origin + Vector(0, 0, 20); // up a little bit + v_dest = v_src + gpGlobals->v_right * -128; // to the left + + UTIL_TraceHull(v_src, v_dest, dont_ignore_monsters, 1, pPlayer, &tr); + + if (tr.flFraction >= 1.0) + { + v_src = v_dest; + v_dest = v_dest + Vector(0, 0, -200); // down to ground + + // try to find the floor... + UTIL_TraceLine(v_src, v_dest, dont_ignore_monsters, pPlayer, &tr); + + if (tr.flFraction < 1.0) // hit something? + { + monster_angle = view_angle.y - 90.0f; // face the player + if (monster_angle > 360) + monster_angle -= 360; + if (monster_angle < 0) + monster_angle += 360; + + spawn_monster(monster_type, v_src, monster_angle, -1); + + return; + } + } + + v_src = origin + Vector(0, 0, 20); // up a little bit + v_dest = v_src + gpGlobals->v_forward * -128; // to the rear + + UTIL_TraceHull(v_src, v_dest, dont_ignore_monsters, 1, pPlayer, &tr); + + if (tr.flFraction >= 1.0) + { + v_src = v_dest; + v_dest = v_dest + Vector(0, 0, -200); // down to ground + + // try to find the floor... + UTIL_TraceLine(v_src, v_dest, dont_ignore_monsters, pPlayer, &tr); + + if (tr.flFraction < 1.0) // hit something? + { + monster_angle = view_angle.y; // face the player + if (monster_angle > 360) + monster_angle -= 360; + if (monster_angle < 0) + monster_angle += 360; + + spawn_monster(monster_type, v_src, monster_angle, -1); + + return; + } + } + + META_CONS("[MONSTER] there's no room to spawn a monster near player \"%s\"!", + player_name); + + return; + } + } + } + + META_CONS("[MONSTER] usage: monster monster_name player_name | #player_index"); + META_CONS("[MONSTER] valid monster_names are:"); + msg[0] = 0; + for (index = 0; monster_types[index].name[0]; index++) + { + strcat(msg, monster_types[index].name); + strcat(msg, " "); + if (strlen(msg) > 60) + { + META_CONS("[MONSTER] %s", msg); + msg[0] = 0; + } + } + if (msg[0]) + META_CONS("[MONSTER] %s", msg); +} + + +void mmGameDLLInit( void ) +{ + // one time initialization stuff here... + + RETURN_META(MRES_IGNORED); +} + + +int mmDispatchSpawn( edict_t *pent ) +{ + int index; + char *pClassname = (char *)STRING(pent->v.classname); + + if (strcmp(pClassname, "worldspawn") == 0) + { + // free any monster class memory not previously freed... + for (index = 0; index < MAX_MONSTER_ENTS; index++) + { + if (monsters[index].pMonster != NULL) + delete monsters[index].pMonster; + } + + // do level initialization stuff here... + + for (index = 0; monster_types[index].name[0]; index++) + monster_types[index].need_to_precache = FALSE; + + world_precache(); + + monster_spawn_count = 0; + + monster_skill_init(); + + process_monster_precache_cfg(); + + process_monster_cfg(); + + check_respawn_time = 0.0; + + for (index = 0; index < MAX_MONSTER_ENTS; index++) + { + monsters[index].monster_index = 0; + monsters[index].monster_pent = NULL; + monsters[index].killed = FALSE; // not killed yet + monsters[index].pMonster = NULL; + } + + monster_ents_used = 0; + + for (index = 0; index < ARRAYSIZE(gDecals); index++ ) + gDecals[index].index = DECAL_INDEX( gDecals[index].name ); + } + + // 0==Success, -1==Failure ? + RETURN_META_VALUE(MRES_IGNORED, 0); +} + +void mmDispatchThink( edict_t *pent ) +{ + for (int index=0; index < monster_ents_used; index++) + { + if (pent == monsters[index].monster_pent) + { + monsters[index].pMonster->Think(); + + check_monster_dead(); + + RETURN_META(MRES_SUPERCEDE); + } + } + + RETURN_META(MRES_IGNORED); +} + +void mmDispatchTouch( edict_t *pentTouched, edict_t *pentOther ) +{ + for (int index=0; index < monster_ents_used; index++) + { + if ((pentTouched != NULL) && + (pentTouched == monsters[index].monster_pent)) + { + monsters[index].pMonster->Touch(pentOther); + + check_monster_dead(); + + RETURN_META(MRES_SUPERCEDE); + } + } + + RETURN_META(MRES_IGNORED); +} + + +void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int index; + + CMAGrunt agrunt; + CMApache apache; + CMBarney barney; + CMBigMomma bigmomma; + CMBullsquid bullsquid; + CMController controller; + CMHAssassin hassassin; + CMHeadCrab headcrab; + CMHGrunt hgrunt; + CMHoundeye houndeye; + CMISlave islave; + CMScientist scientist; + CMSqueakGrenade snark; + CMZombie zombie; + + g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); + + (g_engfuncs.pfnAddServerCommand)("monster", MonsterCommand); + + for (index = 0; monster_types[index].name[0]; index++) + { + if (monster_types[index].need_to_precache) + { + if (dllapi_log->value) + { + LOG_MESSAGE(PLID, "Precaching %s models & sounds...", monster_types[index].name); + } + + switch (index) + { + case 0: + agrunt.Precache(); + break; + case 1: + apache.Precache(); + break; + case 2: + barney.Precache(); + break; + case 3: + bigmomma.Precache(); + break; + case 4: + bullsquid.Precache(); + break; + case 5: + controller.Precache(); + break; + case 6: + hassassin.Precache(); + break; + case 7: + headcrab.Precache(); + break; + case 8: + hgrunt.Precache(); + break; + case 9: + houndeye.Precache(); + break; + case 10: + islave.Precache(); + break; + case 11: + scientist.Precache(); + break; + case 12: + snark.Precache(); + break; + case 13: + zombie.Precache(); + break; + } + } + } + + for (index = 0; index < MAX_MONSTER_ENTS; index++) + { + monsters[index].monster_index = 0; + monsters[index].monster_pent = NULL; + monsters[index].killed = FALSE; // not killed yet + monsters[index].pMonster = NULL; + } + + monster_ents_used = 0; + + RETURN_META(MRES_IGNORED); +} + +void mmStartFrame( void ) +{ + if (check_respawn_time <= gpGlobals->time) + { + check_respawn_time = gpGlobals->time + 1.0; + + check_respawn(); + } + + RETURN_META(MRES_IGNORED); +} + +static DLL_FUNCTIONS gFunctionTable = +{ + mmGameDLLInit, //! pfnGameInit() Initialize the game (one-time call after loading of game .dll) + mmDispatchSpawn, //! pfnSpawn() + mmDispatchThink, //! pfnThink + NULL, // pfnUse + mmDispatchTouch, //! pfnTouch + NULL, // pfnBlocked + NULL, // pfnKeyValue + NULL, // pfnSave + NULL, // pfnRestore + NULL, // pfnSetAbsBox + + NULL, // pfnSaveWriteFields + NULL, // pfnSaveReadFields + + NULL, // pfnSaveGlobalState + NULL, // pfnRestoreGlobalState + NULL, // pfnResetGlobalState + + NULL, // pfnClientConnect + NULL, // pfnClientDisconnect + NULL, // pfnClientKill + NULL, // pfnClientPutInServer + NULL, // pfnClientCommand + NULL, // pfnClientUserInfoChanged + mmServerActivate, //! pfnServerActivate() (wd) Server is starting a new map + NULL, // pfnServerDeactivate + + NULL, // pfnPlayerPreThink + NULL, // pfnPlayerPostThink + + mmStartFrame, //! pfnStartFrame + NULL, // pfnParmsNewLevel + NULL, // pfnParmsChangeLevel + + NULL, // pfnGetGameDescription + NULL, // pfnPlayerCustomization + + NULL, // pfnSpectatorConnect + NULL, // pfnSpectatorDisconnect + NULL, // pfnSpectatorThink + + NULL, // pfnSys_Error + + NULL, // pfnPM_Move + NULL, // pfnPM_Init + NULL, // pfnPM_FindTextureType + + NULL, // pfnSetupVisibility + NULL, // pfnUpdateClientData + NULL, // pfnAddToFullPack + NULL, // pfnCreateBaseline + NULL, // pfnRegisterEncoders + NULL, // pfnGetWeaponData + NULL, // pfnCmdStart + NULL, // pfnCmdEnd + NULL, // pfnConnectionlessPacket + NULL, // pfnGetHullBounds + NULL, // pfnCreateInstancedBaselines + NULL, // pfnInconsistentFile + NULL, // pfnAllowLagCompensation +}; + + +C_DLLEXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if(!pFunctionTable) { + UTIL_LogPrintf("GetEntityAPI2 called with null pFunctionTable"); + return(FALSE); + } + else if(*interfaceVersion != INTERFACE_VERSION) { + UTIL_LogPrintf("GetEntityAPI2 version mismatch; requested=%d ours=%d", *interfaceVersion, INTERFACE_VERSION); + //! Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return(FALSE); + } + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return(TRUE); +} + + +void mmDispatchThink_Post( edict_t *pent ) +{ + check_monster_hurt(pent); + check_monster_dead(); + + + RETURN_META(MRES_IGNORED); +} + +void mmPlayerPostThink_Post( edict_t *pEntity ) +{ + check_monster_hurt(pEntity); + check_monster_dead(); + + + RETURN_META(MRES_IGNORED); +} + +static DLL_FUNCTIONS gFunctionTable_Post = +{ + NULL, // pfnGameInit() Initialize the game (one-time call after loading of game .dll) + NULL, // pfnSpawn() + mmDispatchThink_Post, //! pfnThink + NULL, // pfnUse + NULL, // pfnTouch + NULL, // pfnBlocked + NULL, // pfnKeyValue + NULL, // pfnSave + NULL, // pfnRestore + NULL, // pfnSetAbsBox + + NULL, // pfnSaveWriteFields + NULL, // pfnSaveReadFields + + NULL, // pfnSaveGlobalState + NULL, // pfnRestoreGlobalState + NULL, // pfnResetGlobalState + + NULL, // pfnClientConnect + NULL, // pfnClientDisconnect + NULL, // pfnClientKill + NULL, // pfnClientPutInServer + NULL, // pfnClientCommand + NULL, // pfnClientUserInfoChanged + NULL, // pfnServerActivate() (wd) Server is starting a new map + NULL, // pfnServerDeactivate + + NULL, // pfnPlayerPreThink + mmPlayerPostThink_Post, //! pfnPlayerPostThink + + NULL, // pfnStartFrame + NULL, // pfnParmsNewLevel + NULL, // pfnParmsChangeLevel + + NULL, // pfnGetGameDescription + NULL, // pfnPlayerCustomization + + NULL, // pfnSpectatorConnect + NULL, // pfnSpectatorDisconnect + NULL, // pfnSpectatorThink + + NULL, // pfnSys_Error + + NULL, // pfnPM_Move + NULL, // pfnPM_Init + NULL, // pfnPM_FindTextureType + + NULL, // pfnSetupVisibility + NULL, // pfnUpdateClientData + NULL, // pfnAddToFullPack + NULL, // pfnCreateBaseline + NULL, // pfnRegisterEncoders + NULL, // pfnGetWeaponData + NULL, // pfnCmdStart + NULL, // pfnCmdEnd + NULL, // pfnConnectionlessPacket + NULL, // pfnGetHullBounds + NULL, // pfnCreateInstancedBaselines + NULL, // pfnInconsistentFile + NULL, // pfnAllowLagCompensation +}; + +C_DLLEXPORT int GetEntityAPI2_Post( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if(!pFunctionTable) { + UTIL_LogPrintf("GetEntityAPI2_Post called with null pFunctionTable"); + return(FALSE); + } + else if(*interfaceVersion != INTERFACE_VERSION) { + UTIL_LogPrintf("GetEntityAPI2_Post version mismatch; requested=%d ours=%d", *interfaceVersion, INTERFACE_VERSION); + //! Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return(FALSE); + } + memcpy( pFunctionTable, &gFunctionTable_Post, sizeof( DLL_FUNCTIONS ) ); + return(TRUE); +} diff --git a/src/dlls/doors.h b/src/dlls/doors.h new file mode 100644 index 0000000..c9752c1 --- /dev/null +++ b/src/dlls/doors.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2001, 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 DOORS_H +#define DOORS_H + +// doors +#define SF_DOOR_ROTATE_Y 0 +#define SF_DOOR_START_OPEN 1 +#define SF_DOOR_ROTATE_BACKWARDS 2 +#define SF_DOOR_PASSABLE 8 +#define SF_DOOR_ONEWAY 16 +#define SF_DOOR_NO_AUTO_RETURN 32 +#define SF_DOOR_ROTATE_Z 64 +#define SF_DOOR_ROTATE_X 128 +#define SF_DOOR_USE_ONLY 256 // door must be opened by player's use button. +#define SF_DOOR_NOMONSTERS 512 // Monster can't open +#define SF_DOOR_SILENT 0x80000000 + + + +#endif //DOORS_H diff --git a/src/dlls/effects.cpp b/src/dlls/effects.cpp new file mode 100644 index 0000000..ad3ec2a --- /dev/null +++ b/src/dlls/effects.cpp @@ -0,0 +1,446 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "customentity.h" +#include "effects.h" +#include "weapons.h" +#include "decals.h" +#include "func_break.h" +#include "shake.h" + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired + +#define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. + + +// -------------------------------------------------- +// +// Beams +// +// -------------------------------------------------- + +void CMBeam::Spawn( void ) +{ + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); +} + +void CMBeam::Precache( void ) +{ + if ( pev->owner ) + SetStartEntity( ENTINDEX( pev->owner ) ); + if ( pev->aiment ) + SetEndEntity( ENTINDEX( pev->aiment ) ); +} + +void CMBeam::SetStartEntity( int entityIndex ) +{ + pev->sequence = (entityIndex & 0x0FFF) | ((pev->sequence&0xF000)<<12); + pev->owner = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + +void CMBeam::SetEndEntity( int entityIndex ) +{ + pev->skin = (entityIndex & 0x0FFF) | ((pev->skin&0xF000)<<12); + pev->aiment = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + + +// These don't take attachments into account +const Vector &CMBeam::GetStartPos( void ) +{ + if ( GetType() == BEAM_ENTS ) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetStartEntity() ); + return pent->v.origin; + } + return pev->origin; +} + + +const Vector &CMBeam::GetEndPos( void ) +{ + int type = GetType(); + if ( type == BEAM_POINTS || type == BEAM_HOSE ) + { + return pev->angles; + } + + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetEndEntity() ); + if ( pent ) + return pent->v.origin; + return pev->angles; +} + + +CMBeam *CMBeam::BeamCreate( const char *pSpriteName, int width ) +{ + // Create a new entity with CMBeam private data + CMBeam *pBeam = CreateClassPtr( (CMBeam *)NULL ); + + if (pBeam == NULL) + return NULL; + + pBeam->pev->classname = MAKE_STRING("beam"); + + pBeam->BeamInit( pSpriteName, width ); + + return pBeam; +} + + +void CMBeam::BeamInit( const char *pSpriteName, int width ) +{ + pev->flags |= FL_CUSTOMENTITY; + SetColor( 255, 255, 255 ); + SetBrightness( 255 ); + SetNoise( 0 ); + SetFrame( 0 ); + SetScrollRate( 0 ); + pev->model = MAKE_STRING( pSpriteName ); + SetTexture( PRECACHE_MODEL( (char *)pSpriteName ) ); + SetWidth( width ); + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; +} + + +void CMBeam::PointsInit( const Vector &start, const Vector &end ) +{ + SetType( BEAM_POINTS ); + SetStartPos( start ); + SetEndPos( end ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CMBeam::HoseInit( const Vector &start, const Vector &direction ) +{ + SetType( BEAM_HOSE ); + SetStartPos( start ); + SetEndPos( direction ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CMBeam::PointEntInit( const Vector &start, int endIndex ) +{ + SetType( BEAM_ENTPOINT ); + SetStartPos( start ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + +void CMBeam::EntsInit( int startIndex, int endIndex ) +{ + SetType( BEAM_ENTS ); + SetStartEntity( startIndex ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CMBeam::RelinkBeam( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->mins.x = min( startPos.x, endPos.x ); + pev->mins.y = min( startPos.y, endPos.y ); + pev->mins.z = min( startPos.z, endPos.z ); + pev->maxs.x = max( startPos.x, endPos.x ); + pev->maxs.y = max( startPos.y, endPos.y ); + pev->maxs.z = max( startPos.z, endPos.z ); + pev->mins = pev->mins - pev->origin; + pev->maxs = pev->maxs - pev->origin; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); +} + +#if 0 +void CMBeam::SetObjectCollisionBox( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->absmin.x = min( startPos.x, endPos.x ); + pev->absmin.y = min( startPos.y, endPos.y ); + pev->absmin.z = min( startPos.z, endPos.z ); + pev->absmax.x = max( startPos.x, endPos.x ); + pev->absmax.y = max( startPos.y, endPos.y ); + pev->absmax.z = max( startPos.z, endPos.z ); +} +#endif + + +void CMBeam::TriggerTouch( edict_t *pOther ) +{ + if ( pOther->v.flags & (FL_CLIENT | FL_MONSTER) ) + { + if ( pev->owner ) + { + if (pev->owner->v.euser4 != NULL) + { + CMBaseEntity *pOwner = GetClassPtr((CMBaseEntity *)VARS(pev->owner)); + pOwner->Use( pOther, this->edict(), USE_TOGGLE, 0 ); + } + } + ALERT( at_console, "Firing targets!!!\n" ); + } +} + + +edict_t *CMBeam::RandomTargetname( const char *szName ) +{ + int total = 0; + + edict_t *pEntity = NULL; + edict_t *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + +void CMBeam::DoSparks( const Vector &start, const Vector &end ) +{ + if ( pev->spawnflags & (SF_BEAM_SPARKSTART|SF_BEAM_SPARKEND) ) + { + if ( pev->spawnflags & SF_BEAM_SPARKSTART ) + { + UTIL_Sparks( start ); + } + if ( pev->spawnflags & SF_BEAM_SPARKEND ) + { + UTIL_Sparks( end ); + } + } +} + +void CMBeam::BeamDamage( TraceResult *ptr ) +{ + RelinkBeam(); + if ( ptr->flFraction != 1.0 && ptr->pHit != NULL ) + { + if (UTIL_IsPlayer(ptr->pHit)) + { + ClearMultiDamage(); + UTIL_TraceAttack(ptr->pHit, pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pev, pev ); + + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( UTIL_IsBSPModel(ptr->pHit) ) + UTIL_DecalTrace( ptr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4) ); + } + + } + else if (ptr->pHit->v.euser4 != NULL) + { + ClearMultiDamage(); + CMBaseEntity *pMonster = GetClassPtr((CMBaseEntity *)VARS(ptr->pHit)); + pMonster->TraceAttack( pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pev, pev ); + + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( pMonster->IsBSPModel() ) + UTIL_DecalTrace( ptr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4) ); + } + } + } + pev->dmgtime = gpGlobals->time; +} + + +void CMSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( pev->targetname && !(pev->spawnflags & SF_SPRITE_STARTON) ) + TurnOff(); + else + TurnOn(); + + // Worldcraft only sets y rotation, copy to Z + if ( pev->angles.y != 0 && pev->angles.z == 0 ) + { + pev->angles.z = pev->angles.y; + pev->angles.y = 0; + } +} + + +void CMSprite::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + + // Reset attachment after save/restore + if ( pev->aiment ) + SetAttachment( pev->aiment, pev->body ); + else + { + // Clear attachment + pev->skin = 0; + pev->body = 0; + } +} + + +void CMSprite::SpriteInit( const char *pSpriteName, const Vector &origin ) +{ + pev->model = MAKE_STRING(pSpriteName); + pev->origin = origin; + Spawn(); +} + +CMSprite *CMSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) +{ + CMSprite *pSprite = CreateClassPtr( (CMSprite *)NULL ); + pSprite->SpriteInit( pSpriteName, origin ); + pSprite->pev->classname = MAKE_STRING("env_sprite"); + pSprite->pev->solid = SOLID_NOT; + pSprite->pev->movetype = MOVETYPE_NOCLIP; + if ( animate ) + pSprite->TurnOn(); + + return pSprite; +} + + +void CMSprite::AnimateThink( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + +void CMSprite::AnimateUntilDead( void ) +{ + if ( gpGlobals->time > pev->dmgtime ) + UTIL_Remove(this->edict()); + else + { + AnimateThink(); + pev->nextthink = gpGlobals->time; + } +} + +void CMSprite::Expand( float scaleSpeed, float fadeSpeed ) +{ + pev->speed = scaleSpeed; + pev->health = fadeSpeed; + SetThink( ExpandThink ); + + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; +} + + +void CMSprite::ExpandThink( void ) +{ + float frametime = gpGlobals->time - m_lastTime; + pev->scale += pev->speed * frametime; + pev->renderamt -= pev->health * frametime; + if ( pev->renderamt <= 0 ) + { + pev->renderamt = 0; + UTIL_Remove( this->edict() ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; + } +} + + +void CMSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( pev->frame > m_maxFrame ) + { + if ( pev->spawnflags & SF_SPRITE_ONCE ) + { + TurnOff(); + } + else + { + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); + } + } +} + + +void CMSprite::TurnOff( void ) +{ + pev->effects = EF_NODRAW; + pev->nextthink = 0; +} + + +void CMSprite::TurnOn( void ) +{ + pev->effects = 0; + if ( (pev->framerate && m_maxFrame > 1.0) || (pev->spawnflags & SF_SPRITE_ONCE) ) + { + SetThink( AnimateThink ); + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; + } + pev->frame = 0; +} + + +void CMSprite::Use( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ) +{ + int on = pev->effects != EF_NODRAW; + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + { + TurnOff(); + } + else + { + TurnOn(); + } + } +} diff --git a/src/dlls/effects.h b/src/dlls/effects.h new file mode 100644 index 0000000..ec1d426 --- /dev/null +++ b/src/dlls/effects.h @@ -0,0 +1,203 @@ +/*** +* +* Copyright (c) 1996-2001, 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 EFFECTS_H +#define EFFECTS_H + +#define SF_BEAM_STARTON 0x0001 +#define SF_BEAM_TOGGLE 0x0002 +#define SF_BEAM_RANDOM 0x0004 +#define SF_BEAM_RING 0x0008 +#define SF_BEAM_SPARKSTART 0x0010 +#define SF_BEAM_SPARKEND 0x0020 +#define SF_BEAM_DECALS 0x0040 +#define SF_BEAM_SHADEIN 0x0080 +#define SF_BEAM_SHADEOUT 0x0100 +#define SF_BEAM_TEMPORARY 0x8000 + +#define SF_SPRITE_STARTON 0x0001 +#define SF_SPRITE_ONCE 0x0002 +#define SF_SPRITE_TEMPORARY 0x8000 + +class CMSprite : public CMPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_SPRITE_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CMBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + void EXPORT AnimateThink( void ); + void EXPORT ExpandThink( void ); + void Use( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + void Animate( float frames ); + void Expand( float scaleSpeed, float fadeSpeed ); + void SpriteInit( const char *pSpriteName, const Vector &origin ); + + inline void SetAttachment( edict_t *pEntity, int attachment ) + { + if ( pEntity ) + { + pev->skin = ENTINDEX(pEntity); + pev->body = attachment; + pev->aiment = pEntity; + pev->movetype = MOVETYPE_FOLLOW; + } + } + void TurnOff( void ); + void TurnOn( void ); + inline float Frames( void ) { return m_maxFrame; } + inline void SetTransparency( int rendermode, int r, int g, int b, int a, int fx ) + { + pev->rendermode = rendermode; + pev->rendercolor.x = r; + pev->rendercolor.y = g; + pev->rendercolor.z = b; + pev->renderamt = a; + pev->renderfx = fx; + } + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetScale( float scale ) { pev->scale = scale; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + + inline void AnimateAndDie( float framerate ) + { + SetThink(AnimateUntilDead); + pev->framerate = framerate; + pev->dmgtime = gpGlobals->time + (m_maxFrame / framerate); + pev->nextthink = gpGlobals->time; + } + + void EXPORT AnimateUntilDead( void ); + + static CMSprite *SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ); + +private: + + float m_lastTime; + float m_maxFrame; +}; + + +class CMBeam : public CMBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_BEAM_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CMBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + + void EXPORT TriggerTouch( edict_t *pOther ); + + // These functions are here to show the way beams are encoded as entities. + // Encoding beams as entities simplifies their management in the client/server architecture + inline void SetType( int type ) { pev->rendermode = (pev->rendermode & 0xF0) | (type&0x0F); } + inline void SetFlags( int flags ) { pev->rendermode = (pev->rendermode & 0x0F) | (flags&0xF0); } + inline void SetStartPos( const Vector& pos ) { pev->origin = pos; } + inline void SetEndPos( const Vector& pos ) { pev->angles = pos; } + void SetStartEntity( int entityIndex ); + void SetEndEntity( int entityIndex ); + + inline void SetStartAttachment( int attachment ) { pev->sequence = (pev->sequence & 0x0FFF) | ((attachment&0xF)<<12); } + inline void SetEndAttachment( int attachment ) { pev->skin = (pev->skin & 0x0FFF) | ((attachment&0xF)<<12); } + + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetWidth( int width ) { pev->scale = width; } + inline void SetNoise( int amplitude ) { pev->body = amplitude; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + inline void SetFrame( float frame ) { pev->frame = frame; } + inline void SetScrollRate( int speed ) { pev->animtime = speed; } + + inline int GetType( void ) { return pev->rendermode & 0x0F; } + inline int GetFlags( void ) { return pev->rendermode & 0xF0; } + inline int GetStartEntity( void ) { return pev->sequence & 0xFFF; } + inline int GetEndEntity( void ) { return pev->skin & 0xFFF; } + + const Vector &GetStartPos( void ); + const Vector &GetEndPos( void ); + + Vector Center( void ) { return (GetStartPos() + GetEndPos()) * 0.5; }; // center point of beam + + inline int GetTexture( void ) { return pev->modelindex; } + inline int GetWidth( void ) { return pev->scale; } + inline int GetNoise( void ) { return pev->body; } + // inline void GetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline int GetBrightness( void ) { return pev->renderamt; } + inline int GetFrame( void ) { return pev->frame; } + inline int GetScrollRate( void ) { return pev->animtime; } + + // Call after you change start/end positions + void RelinkBeam( void ); +// void SetObjectCollisionBox( void ); + + void DoSparks( const Vector &start, const Vector &end ); + edict_t *RandomTargetname( const char *szName ); + void BeamDamage( TraceResult *ptr ); + // Init after BeamCreate() + void BeamInit( const char *pSpriteName, int width ); + void PointsInit( const Vector &start, const Vector &end ); + void PointEntInit( const Vector &start, int endIndex ); + void EntsInit( int startIndex, int endIndex ); + void HoseInit( const Vector &start, const Vector &direction ); + + static CMBeam *BeamCreate( const char *pSpriteName, int width ); + + inline void LiveForTime( float time ) { SetThink(SUB_Remove); pev->nextthink = gpGlobals->time + time; } + inline void BeamDamageInstant( TraceResult *ptr, float damage ) + { + pev->dmg = damage; + pev->dmgtime = gpGlobals->time - 1; + BeamDamage(ptr); + } +}; + + +#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out +#define SF_MESSAGE_ALL 0x0002 // Send to all clients + + +class CMLaser : public CMBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void TurnOn( void ); + void TurnOff( void ); + int IsOn( void ); + + void FireAtPoint( TraceResult &point ); + + void EXPORT StrikeThink( void ); + void Use( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + + CMSprite *m_pSprite; + int m_iszSpriteName; + Vector m_firePosition; +}; + +#endif //EFFECTS_H diff --git a/src/dlls/enginecallback.h b/src/dlls/enginecallback.h new file mode 100644 index 0000000..31072fd --- /dev/null +++ b/src/dlls/enginecallback.h @@ -0,0 +1,158 @@ +/*** +* +* Copyright (c) 1996-2001, 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 ENGINECALLBACK_H +#define ENGINECALLBACK_H +#pragma once + +#include "event_flags.h" + +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define oldCHANGE_YAW (*g_engfuncs.pfnChangeYaw) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) +#define GETPLAYERWONID (*g_engfuncs.pfnGetPlayerWONId) + +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin = NULL, edict_t *ed = NULL ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ed); +} +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +inline void *GET_PRIVATE( edict_t *pent ) +{ + if ( pent ) + return pent->pvPrivateData; + return NULL; +} + +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +//#define STRING (*g_engfuncs.pfnSzFromIndex) +#define ALLOC_STRING (*g_engfuncs.pfnAllocString) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE_FOR_ME (*g_engfuncs.pfnLoadFileForMe) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer) + +#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent) +#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent) + +#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS) +#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS) + +#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility) + +#define DELTA_SET ( *g_engfuncs.pfnDeltaSetField ) +#define DELTA_UNSET ( *g_engfuncs.pfnDeltaUnsetField ) +#define DELTA_ADDENCODER ( *g_engfuncs.pfnDeltaAddEncoder ) +#define ENGINE_CURRENT_PLAYER ( *g_engfuncs.pfnGetCurrentPlayer ) + +#define ENGINE_CANSKIP ( *g_engfuncs.pfnCanSkipPlayer ) + +#define DELTA_FINDFIELD ( *g_engfuncs.pfnDeltaFindField ) +#define DELTA_SETBYINDEX ( *g_engfuncs.pfnDeltaSetFieldByIndex ) +#define DELTA_UNSETBYINDEX ( *g_engfuncs.pfnDeltaUnsetFieldByIndex ) + +#define ENGINE_GETPHYSINFO ( *g_engfuncs.pfnGetPhysicsInfoString ) + +#define ENGINE_SETGROUPMASK ( *g_engfuncs.pfnSetGroupMask ) + +#define ENGINE_INSTANCE_BASELINE ( *g_engfuncs.pfnCreateInstancedBaseline ) + +#define ENGINE_FORCE_UNMODIFIED ( *g_engfuncs.pfnForceUnmodified ) + +#define PLAYER_CNX_STATS ( *g_engfuncs.pfnGetPlayerStats ) + +#endif //ENGINECALLBACK_H \ No newline at end of file diff --git a/src/dlls/explode.h b/src/dlls/explode.h new file mode 100644 index 0000000..d0d1fd5 --- /dev/null +++ b/src/dlls/explode.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1996-2001, 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 EXPLODE_H +#define EXPLODE_H + + +#define SF_ENVEXPLOSION_NODAMAGE ( 1 << 0 ) // when set, ENV_EXPLOSION will not actually inflict damage +#define SF_ENVEXPLOSION_REPEATABLE ( 1 << 1 ) // can this entity be refired? +#define SF_ENVEXPLOSION_NOFIREBALL ( 1 << 2 ) // don't draw the fireball +#define SF_ENVEXPLOSION_NOSMOKE ( 1 << 3 ) // don't draw the smoke +#define SF_ENVEXPLOSION_NODECAL ( 1 << 4 ) // don't make a scorch mark +#define SF_ENVEXPLOSION_NOSPARKS ( 1 << 5 ) // don't make a scorch mark + +extern DLL_GLOBAL short g_sModelIndexFireball; +extern DLL_GLOBAL short g_sModelIndexSmoke; + + +extern void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ); + +#endif //EXPLODE_H diff --git a/src/dlls/extdll.h b/src/dlls/extdll.h new file mode 100644 index 0000000..cda0476 --- /dev/null +++ b/src/dlls/extdll.h @@ -0,0 +1,88 @@ +/*** +* +* Copyright (c) 1996-2001, 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 EXTDLL_H +#define EXTDLL_H + + +// +// Global header file for extension DLLs +// + +// Allow "DEBUG" in addition to default "_DEBUG" +#ifdef _DEBUG +#define DEBUG 1 +#endif + +// Silence certain warnings +#pragma warning(disable : 4244) // int or float down-conversion +#pragma warning(disable : 4305) // int or float data truncation +#pragma warning(disable : 4201) // nameless struct/union +#pragma warning(disable : 4514) // unreferenced inline function removed +#pragma warning(disable : 4100) // unreferenced formal parameter + +// Prevent tons of unused windows definitions +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) +typedef unsigned long ULONG; +typedef unsigned char BYTE; +typedef int BOOL; +#define MAX_PATH PATH_MAX +#include +#include +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define _vsnprintf(a,b,c,d) vsnprintf(a,b,c,d) +#endif +#endif //_WIN32 + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef int func_t; // +typedef int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +// Vector class +#include "vector.h" + +// Defining it as a (bogus) struct helps enforce type-checking +#define vec3_t Vector + +// Shared engine/DLL constants +#include "const.h" +#include "progdefs.h" +#include "edict.h" + +// Shared header describing protocol between engine and DLLs +#include "eiface.h" + +// Shared header between the client DLL and the game DLLs +#include "cdll_dll.h" + +#endif //EXTDLL_H diff --git a/src/dlls/flyingmonster.cpp b/src/dlls/flyingmonster.cpp new file mode 100644 index 0000000..e00208e --- /dev/null +++ b/src/dlls/flyingmonster.cpp @@ -0,0 +1,281 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" + +#define FLYING_AE_FLAP (8) +#define FLYING_AE_FLAPSOUND (9) + + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +int CMFlyingMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, edict_t *pTarget, float *pflDist ) +{ + // UNDONE: need to check more than the endpoint + if (FBitSet(pev->flags, FL_SWIM) && (UTIL_PointContents(vecEnd) != CONTENTS_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 == gpGlobals->trace_ent) ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +BOOL CMFlyingMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, edict_t *pTargetEnt, Vector *pApex ) +{ + return CMBaseMonster::FTriangulate( vecStart, vecEnd, flDist, pTargetEnt, pApex ); +} + + +Activity CMFlyingMonster :: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + + return ACT_HOVER; +} + + +void CMFlyingMonster :: 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 CMFlyingMonster :: 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 CMBaseMonster::ChangeYaw( speed ); +} + + +void CMFlyingMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_STEP; + ClearBits( pev->flags, FL_ONGROUND ); + pev->angles.z = 0; + pev->angles.x = 0; + CMBaseMonster::Killed( pevAttacker, iGib ); +} + + +void CMFlyingMonster :: 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: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CMFlyingMonster :: Move( float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + m_flGroundSpeed = m_flightSpeed; + CMBaseMonster::Move( flInterval ); +} + + +BOOL CMFlyingMonster:: 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 CMFlyingMonster::MoveExecute( edict_t *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 + CMBaseMonster::MoveExecute( pTargetEnt, vecDir, flInterval ); +} + + +float CMFlyingMonster::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 CMFlyingMonster::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 CMFlyingMonster::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/src/dlls/func_break.h b/src/dlls/func_break.h new file mode 100644 index 0000000..4694991 --- /dev/null +++ b/src/dlls/func_break.h @@ -0,0 +1,70 @@ +/*** +* +* Copyright (c) 1996-2001, 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 FUNC_BREAK_H +#define FUNC_BREAK_H + +typedef enum { expRandom, expDirected} Explosions; +typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matUnbreakableGlass, matRocks, matNone, matLastMaterial } Materials; + +#define NUM_SHARDS 6 // this many shards spawned when breakable objects break; + +class CMBreakable : public CMBaseDelay +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT BreakTouch( CMBaseEntity *pOther ); + void Use( CMBaseEntity *pActivator, CMBaseEntity *pCaller, USE_TYPE useType, float value ); + void DamageSound( void ); + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + // To spark when hit + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + BOOL IsBreakable( void ); + BOOL SparkWhenHit( void ); + + int DamageDecal( int bitsDamageType ); + + void EXPORT Die( void ); + virtual int ObjectCaps( void ) { return (CMBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + inline BOOL Explodable( void ) { return ExplosionMagnitude() > 0; } + inline int ExplosionMagnitude( void ) { return pev->impulse; } + inline void ExplosionSetMagnitude( int magnitude ) { pev->impulse = magnitude; } + + static void MaterialSoundPrecache( Materials precacheMaterial ); + static void MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ); + static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount ); + + static const char *pSoundsWood[]; + static const char *pSoundsFlesh[]; + static const char *pSoundsGlass[]; + static const char *pSoundsMetal[]; + static const char *pSoundsConcrete[]; + static const char *pSpawnObjects[]; + + Materials m_Material; + Explosions m_Explosion; + int m_idShard; + float m_angle; + int m_iszGibModel; + int m_iszSpawnObject; +}; + +#endif // FUNC_BREAK_H diff --git a/src/dlls/ggrenade.cpp b/src/dlls/ggrenade.cpp new file mode 100644 index 0000000..5de3c0e --- /dev/null +++ b/src/dlls/ggrenade.cpp @@ -0,0 +1,466 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +/* + +===== generic grenade.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "decals.h" +#include "explode.h" + + +//===================grenade + +// Grenades flagged with this will be triggered when the owner calls detonateSatchelCharges +#define SF_DETONATE 0x0001 + +// +// Grenade Explode +// +void CMGrenade::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CMGrenade::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( (pev->dmg - 50) * .60 ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + RadiusDamage ( pev, pevOwner, pev->dmg, CLASS_NONE, bitsDamageType ); + + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink( Smoke ); + pev->velocity = g_vecZero; + pev->nextthink = gpGlobals->time + 0.3; + +/*jlb + if (iContents != CONTENTS_WATER) + { + int sparkCount = RANDOM_LONG(0,3); + for ( int i = 0; i < sparkCount; i++ ) + Create( "spark_shower", pev->origin, pTrace->vecPlaneNormal, NULL ); + } +jlb*/ +} + + +void CMGrenade::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + else + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (pev->dmg - 50) * 0.80 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + UTIL_Remove( this->edict() ); +} + +void CMGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +// Timed grenade, this think is called when time runs out. +void CMGrenade::DetonateUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ) +{ + SetThink( Detonate ); + pev->nextthink = gpGlobals->time; +} + +void CMGrenade::PreDetonate( void ) +{ + SetThink( Detonate ); + pev->nextthink = gpGlobals->time + 1; +} + + +void CMGrenade::Detonate( void ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + + +// +// Contact grenade, explode when it touches something +// +void CMGrenade::ExplodeTouch( edict_t *pOther ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + pev->enemy = pOther; + + vecSpot = pev->origin - pev->velocity.Normalize() * 32; + UTIL_TraceLine( vecSpot, vecSpot + pev->velocity.Normalize() * 64, ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST ); +} + + +void CMGrenade::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this->edict() ); + return; + } + + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + } +} + + +void CMGrenade::BounceTouch( edict_t *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther == pev->owner ) + return; + + // only do damage if we're moving fairly fast + if (m_flNextAttack < gpGlobals->time && pev->velocity.Length() > 100) + { + entvars_t *pevOwner = VARS( pev->owner ); + if (pevOwner) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + ClearMultiDamage( ); + + if (UTIL_IsPlayer(pOther)) + UTIL_TraceAttack(pOther, pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB ); + else if (pOther->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + pMonster->TraceAttack(pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB ); + } + + ApplyMultiDamage( pev, pevOwner); + } + m_flNextAttack = gpGlobals->time + 1.0; // debounce + } + + Vector vecTestVelocity; + // pev->avelocity = Vector (300, 300, 300); + + // this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical + // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. + // trimming the Z velocity a bit seems to help quite a bit. + vecTestVelocity = pev->velocity; + vecTestVelocity.z *= 0.45; + + if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 ) + { + //ALERT( at_console, "Grenade Registered!: %f\n", vecTestVelocity.Length() ); + + // grenade is moving really slow. It's probably very close to where it will ultimately stop moving. + // go ahead and emit the danger sound. + + // register a radius louder than the explosion, so we make sure everyone gets out of the way + m_fRegisteredSound = TRUE; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.8; + + pev->sequence = RANDOM_LONG( 1, 1 ); + } + else + { + // play bounce sound + BounceSound(); + } + pev->framerate = pev->velocity.Length() / 200.0; + if (pev->framerate > 1.0) + pev->framerate = 1; + else if (pev->framerate < 0.5) + pev->framerate = 0; + +} + + + +void CMGrenade::SlideTouch( edict_t *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + + if (pev->velocity.x != 0 || pev->velocity.y != 0) + { + // maintain sliding sound + } + } + else + { + BounceSound(); + } +} + +void CMGrenade :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit1.wav", 0.25, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit2.wav", 0.25, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit3.wav", 0.25, ATTN_NORM); break; + } +} + +void CMGrenade :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this->edict() ); + return; + } + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink( Detonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CMGrenade:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "grenade" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/grenade.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 100; + m_fRegisteredSound = FALSE; +} + + +CMGrenade *CMGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CMGrenade *pGrenade = CreateClassPtr( (CMGrenade *)NULL ); + + if (pGrenade == NULL) // no free monster edicts left? + return NULL; + + pGrenade->Spawn(); + // contact grenades arc lower + pGrenade->pev->gravity = 0.5;// lower gravity since grenade is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles (pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pGrenade->SetThink( DangerSoundThink ); + pGrenade->pev->nextthink = gpGlobals->time; + + // Tumble in air + pGrenade->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pGrenade->SetTouch( ExplodeTouch ); + + pGrenade->pev->dmg = gSkillData.monDmgM203Grenade; + + return pGrenade; +} + + +CMGrenade * CMGrenade:: ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ) +{ + CMGrenade *pGrenade = CreateClassPtr( (CMGrenade *)NULL ); + + if (pGrenade == NULL) // no free monster edicts left? + return NULL; + + pGrenade->Spawn(); + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles(pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + pGrenade->SetTouch( BounceTouch ); // Bounce if touched + + // Take one second off of the desired detonation time and set the think to PreDetonate. PreDetonate + // will insert a DANGER sound into the world sound list and delay detonation for one second so that + // the grenade explodes after the exact amount of time specified in the call to ShootTimed(). + + pGrenade->pev->dmgtime = gpGlobals->time + time; + pGrenade->SetThink( TumbleThink ); + pGrenade->pev->nextthink = gpGlobals->time + 0.1; + if (time < 0.1) + { + pGrenade->pev->nextthink = gpGlobals->time; + pGrenade->pev->velocity = Vector( 0, 0, 0 ); + } + + pGrenade->pev->sequence = RANDOM_LONG( 3, 6 ); + pGrenade->pev->framerate = 1.0; + + // Tumble through the air + // pGrenade->pev->avelocity.x = -400; + + pGrenade->pev->gravity = 0.5; + pGrenade->pev->friction = 0.8; + + SET_MODEL(ENT(pGrenade->pev), "models/w_grenade.mdl"); + pGrenade->pev->dmg = 100; + + return pGrenade; +} + + +CMGrenade * CMGrenade :: ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CMGrenade *pGrenade = CreateClassPtr( (CMGrenade *)NULL ); + + if (pGrenade == NULL) // no free monster edicts left? + return NULL; + + pGrenade->pev->movetype = MOVETYPE_BOUNCE; + pGrenade->pev->classname = MAKE_STRING( "grenade" ); + + pGrenade->pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pGrenade->pev), "models/grenade.mdl"); // Change this to satchel charge model + + UTIL_SetSize(pGrenade->pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pGrenade->pev->dmg = 200; + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = g_vecZero; + pGrenade->pev->owner = ENT(pevOwner); + + // Detonate in "time" seconds + pGrenade->SetThink( SUB_DoNothing ); + pGrenade->SetUse( DetonateUse ); + pGrenade->SetTouch( SlideTouch ); + pGrenade->pev->spawnflags = SF_DETONATE; + + pGrenade->pev->friction = 0.9; + + return pGrenade; +} + +//======================end grenade + diff --git a/src/dlls/h_ai.cpp b/src/dlls/h_ai.cpp new file mode 100644 index 0000000..425f0e1 --- /dev/null +++ b/src/dlls/h_ai.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +/* + + h_ai.cpp - halflife specific ai code + +*/ + + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" + +#define NUM_LATERAL_CHECKS 13 // how many checks are made on each side of a monster looking for lateral cover +#define NUM_LATERAL_LOS_CHECKS 6 // how many checks are made on each side of a monster looking for lateral cover + +//float flRandom = RANDOM_FLOAT(0,1); + +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + +//========================================================= +// +// AI UTILITY FUNCTIONS +// +// !!!UNDONE - move CBaseMonster functions to monsters.cpp +//========================================================= + +//========================================================= +// 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 = g_psv_gravity->value * 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 = g_psv_gravity->value * 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; +} + + diff --git a/src/dlls/h_export.cpp b/src/dlls/h_export.cpp new file mode 100644 index 0000000..17c3de3 --- /dev/null +++ b/src/dlls/h_export.cpp @@ -0,0 +1,46 @@ +// +// botman's monster - MetaMOD plugin +// +// h_export.cpp +// + +/*** +* +* 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. +* +* 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. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "h_export.h" + +// From SDK dlls/h_export.cpp: + +//! Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +// Receive engine function table from engine. +// This appears to be the _first_ DLL routine called by the engine, so we +// do some setup operations here. +void WINAPI GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + diff --git a/src/dlls/hassassin.cpp b/src/dlls/hassassin.cpp new file mode 100644 index 0000000..e1685f6 --- /dev/null +++ b/src/dlls/hassassin.cpp @@ -0,0 +1,909 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "weapons.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) + + +//========================================================= +// DieSound +//========================================================= +void CMHAssassin :: DeathSound ( void ) +{ +} + +//========================================================= +// IdleSound +//========================================================= +void CMHAssassin :: IdleSound ( void ) +{ +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CMHAssassin :: ISoundMask ( void) +{ + return 0; +} + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMHAssassin :: Classify ( void ) +{ + return CLASS_HUMAN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMHAssassin :: 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 CMHAssassin :: Shoot ( void ) +{ + if (m_hEnemy == NULL) + { + 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_MONSTER_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 CMHAssassin :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ASSASSIN_AE_SHOOT1: + Shoot( ); + break; + case ASSASSIN_AE_TOSS1: + { + UTIL_MakeVectors( pev->angles ); + CMGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), 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; + pev->velocity = m_vecJumpVelocity; + m_flNextJump = gpGlobals->time + 3.0; + } + return; + default: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMHAssassin :: Spawn() +{ + Precache( ); + + 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; + pev->health = gSkillData.hassassinHealth; + 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 CMHAssassin :: Precache() +{ + PRECACHE_MODEL("models/hassassin.mdl"); + + PRECACHE_SOUND("weapons/pl_gun1.wav"); + PRECACHE_SOUND("weapons/pl_gun2.wav"); + + PRECACHE_SOUND("debris/beamstart1.wav"); + + m_iShell = PRECACHE_MODEL ("models/shell.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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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( CMHAssassin ) +{ + slAssassinFail, + slAssassinExposed, + slAssassinTakeCoverFromEnemy, + slAssassinTakeCoverFromEnemy2, + slAssassinTakeCoverFromBestSound, + slAssassinHide, + slAssassinHunt, + slAssassinJump, + slAssassinJumpAttack, + slAssassinJumpLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMHAssassin, CMBaseMonster ); + + +//========================================================= +// CheckMeleeAttack1 - jump like crazy if the enemy gets too close. +//========================================================= +BOOL CMHAssassin :: 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 = g_psv_gravity->value; + + 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 CMHAssassin :: 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, UTIL_BodyTarget(m_hEnemy, vecSrc), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1 || tr.pHit == m_hEnemy ) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close. +//========================================================= +BOOL CMHAssassin :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + m_fThrowGrenade = FALSE; + if ( !FBitSet ( m_hEnemy->v.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( ), UTIL_BodyTarget(m_hEnemy, g_vecZero), 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 CMHAssassin :: RunAI( void ) +{ + CMBaseMonster :: 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; + + 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 CMHAssassin :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK2: + if (!m_fThrowGrenade) + { + TaskComplete( ); + } + else + { + CMBaseMonster :: StartTask ( pTask ); + } + break; + case TASK_ASSASSIN_FALL_TO_GROUND: + break; + default: + CMBaseMonster :: StartTask ( pTask ); + break; + } +} + + +//========================================================= +// RunTask +//========================================================= +void CMHAssassin :: 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: + CMBaseMonster :: 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 *CMHAssassin :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + { + } + 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 CMBaseMonster :: 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_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 CMBaseMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CMHAssassin :: 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 CMBaseMonster :: GetScheduleOfType( Type ); +} + +#endif \ No newline at end of file diff --git a/src/dlls/headcrab.cpp b/src/dlls/headcrab.cpp new file mode 100644 index 0000000..911058c --- /dev/null +++ b/src/dlls/headcrab.cpp @@ -0,0 +1,506 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.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" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CMHeadCrab ) +{ + slHCRangeAttack1, + slHCRangeAttack1Fast, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMHeadCrab, CMBaseMonster ); + +const char *CMHeadCrab::pIdleSounds[] = +{ + "headcrab/hc_idle1.wav", + "headcrab/hc_idle2.wav", + "headcrab/hc_idle3.wav", +}; +const char *CMHeadCrab::pAlertSounds[] = +{ + "headcrab/hc_alert1.wav", +}; +const char *CMHeadCrab::pPainSounds[] = +{ + "headcrab/hc_pain1.wav", + "headcrab/hc_pain2.wav", + "headcrab/hc_pain3.wav", +}; +const char *CMHeadCrab::pAttackSounds[] = +{ + "headcrab/hc_attack1.wav", + "headcrab/hc_attack2.wav", + "headcrab/hc_attack3.wav", +}; + +const char *CMHeadCrab::pDeathSounds[] = +{ + "headcrab/hc_die1.wav", + "headcrab/hc_die2.wav", +}; + +const char *CMHeadCrab::pBiteSounds[] = +{ + "headcrab/hc_headbite.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMHeadCrab :: Classify ( void ) +{ + return 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 CMHeadCrab :: Center ( void ) +{ + return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 6 ); +} + + +Vector CMHeadCrab :: BodyTarget( const Vector &posSrc ) +{ + return Center( ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMHeadCrab :: 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 CMHeadCrab :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case HC_AE_JUMPATTACK: + { + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (pev, 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 = g_psv_gravity->value; + if (gravity <= 1) + gravity = 1; + + // How fast does the headcrab need to travel to reach that height given gravity? + float height = (m_hEnemy->v.origin.z + m_hEnemy->v.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->v.origin + m_hEnemy->v.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: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMHeadCrab :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/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; + pev->health = gSkillData.headcrabHealth; + 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 CMHeadCrab :: 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); + + PRECACHE_MODEL("models/headcrab.mdl"); +} + + +//========================================================= +// RunTask +//========================================================= +void CMHeadCrab :: 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: + { + CMBaseMonster :: RunTask(pTask); + } + } +} + +//========================================================= +// LeapTouch - this is the headcrab's touch function when it +// is in the air +//========================================================= +void CMHeadCrab :: LeapTouch ( edict_t *pOther ) +{ + if ( !pOther->v.takedamage ) + { + 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() ); + + if (UTIL_IsPlayer(pOther)) + UTIL_TakeDamage( pOther, pev, pev, GetDamageAmount(), DMG_SLASH ); + else if (pOther->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + pMonster->TakeDamage( pev, pev, GetDamageAmount(), DMG_SLASH ); + } + } + + SetTouch( NULL ); +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CMHeadCrab :: 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 CMHeadCrab :: 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 ( LeapTouch ); + break; + } + default: + { + CMBaseMonster :: StartTask( pTask ); + } + } +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CMHeadCrab :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist <= 256 && flDot >= 0.65 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CMHeadCrab :: 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 CMHeadCrab :: 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 CMBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// IdleSound +//========================================================= +#define CRAB_ATTN_IDLE (float)1.5 +void CMHeadCrab :: IdleSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CMHeadCrab :: AlertSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CMHeadCrab :: PainSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// DeathSound +//========================================================= +void CMHeadCrab :: DeathSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +Schedule_t* CMHeadCrab :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + { + return &slHCRangeAttack1[ 0 ]; + } + break; + } + + return CMBaseMonster::GetScheduleOfType( Type ); +} + + + +void CMBabyCrab :: Spawn( void ) +{ + CMHeadCrab::Spawn(); + 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 = gSkillData.headcrabHealth * 0.25; // less health than full grown +} + +void CMBabyCrab :: Precache( void ) +{ + PRECACHE_MODEL( "models/baby_headcrab.mdl" ); + CMHeadCrab::Precache(); +} + + +void CMBabyCrab :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 120; +} + + +BOOL CMBabyCrab :: 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* CMBabyCrab :: 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 CMHeadCrab::GetScheduleOfType( Type ); +} diff --git a/src/dlls/hgrunt.cpp b/src/dlls/hgrunt.cpp new file mode 100644 index 0000000..0749539 --- /dev/null +++ b/src/dlls/hgrunt.cpp @@ -0,0 +1,2102 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "weapons.h" +#include "cmtalkmonster.h" +#include "effects.h" +#include "customentity.h" + +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 ) + +const char *CMHGrunt::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 CMHGrunt :: 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 CMHGrunt::IRelationship ( CMBaseEntity *pTarget ) +{ + if (( strcmp(STRING(pTarget->pev->model), "models/agrunt.mdl") == 0 ) || + ( strcmp(STRING(pTarget->pev->model), "models/garg.mdl") == 0 )) + { + return R_NM; + } + + return CMBaseMonster::IRelationship( pTarget ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CMHGrunt :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + CMBaseMonster :: GibMonster(); +} + +//========================================================= +// ISoundMask - Overidden for human grunts because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CMHGrunt :: ISoundMask ( void ) +{ + return 0; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CMHGrunt :: FOkToSpeak( void ) +{ +// if someone else is talking, don't speak + if (gpGlobals->time <= CMTalkMonster::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 CMHGrunt :: JustSpoke( void ) +{ + CMTalkMonster::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 CMHGrunt :: PrescheduleThink ( void ) +{ + return; +} + +//========================================================= +// 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 CMHGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CMHGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if (m_hEnemy != NULL) + { + if (UTIL_IsPlayer(m_hEnemy)) + { + if ( flDist <= 64 && flDot >= 0.7) + { + return TRUE; + } + } + else if (m_hEnemy->v.euser4 != NULL) + { + edict_t *pEdict = m_hEnemy; + CMBaseMonster *pEnemy = GetClassPtr((CMBaseMonster *)VARS(pEdict)); + + 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 CMHGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 ) + { + TraceResult tr; + + if ( !UTIL_IsPlayer(m_hEnemy) && flDist <= 64 ) + { + // kick nonclients, 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, UTIL_BodyTarget(m_hEnemy, 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 CMHGrunt :: 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->v.flags, FL_ONGROUND ) && m_hEnemy->v.waterlevel == 0 && 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->v.origin.x, m_hEnemy->v.origin.y, m_hEnemy->v.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 + (UTIL_BodyTarget(m_hEnemy, pev->origin ) - m_hEnemy->v.origin); + // estimate position + if (HasConditions( bits_COND_SEE_ENEMY)) + vecTarget = vecTarget + ((vecTarget - pev->origin).Length() / gSkillData.hgruntGrenadeSpeed) * m_hEnemy->v.velocity; + } + + 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, gSkillData.hgruntGrenadeSpeed, 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 CMHGrunt :: 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; + } + CMBaseMonster::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 CMHGrunt :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Forget( bits_MEMORY_INCOVER ); + + return CMBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMHGrunt :: 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 CMHGrunt :: 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 CMHGrunt :: CheckAmmo ( void ) +{ + if ( m_cAmmoLoaded <= 0 ) + { + SetConditions(bits_COND_NO_AMMO_LOADED); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMHGrunt :: Classify ( void ) +{ + return CLASS_HUMAN_MILITARY; +} + +//========================================================= +//========================================================= +edict_t *CMHGrunt :: 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 ) + return tr.pHit; + + return NULL; +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CMHGrunt :: GetGunPosition( ) +{ + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 48 ); + } +} + +//========================================================= +// Shoot +//========================================================= +void CMHGrunt :: Shoot ( void ) +{ + if (m_hEnemy == 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_iBrassShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, BULLET_MONSTER_MP5 ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// Shoot +//========================================================= +void CMHGrunt :: Shotgun ( void ) +{ + if (m_hEnemy == 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(gSkillData.hgruntShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_PLAYER_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 ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMHGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + } + 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 ); + CMGrenade::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); + CMGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 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 ); + CMGrenade::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 )) + { + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + 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 + { + Shotgun( ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); + } + } + break; + + case HGRUNT_AE_BURST2: + case HGRUNT_AE_BURST3: + Shoot(); + break; + + case HGRUNT_AE_KICK: + { + edict_t *pHurt = Kick(); + + if ( pHurt ) + { + // SOUND HERE! + UTIL_MakeVectors( pev->angles ); + pHurt->v.punchangle.x = 15; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + if (UTIL_IsPlayer(pHurt)) + UTIL_TakeDamage( pHurt, pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); + else if (pHurt->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pHurt)); + pMonster->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, 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: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMHGrunt :: Spawn() +{ + Precache( ); + + 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; + pev->health = gSkillData.hgruntHealth; + m_flFieldOfView = VIEW_FIELD_FULL; // 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_TURN_HEAD | bits_CAP_DOORS_GROUP; + +//jlb 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 + switch(RANDOM_LONG(0, 2)) + { + case 0: + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + break; + case 1: + pev->weapons = HGRUNT_SHOTGUN; + break; + case 2: + pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + break; + } + } + + 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 + } + + CMTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMHGrunt :: Precache() +{ + PRECACHE_MODEL("models/hgrunt.mdl"); + + 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 = PRECACHE_MODEL ("models/shell.mdl");// brass shell + m_iShotgunShell = PRECACHE_MODEL ("models/shotgunshell.mdl"); +} + +//========================================================= +// start task +//========================================================= +void CMHGrunt :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_GRUNT_CHECK_FIRE: + 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 ); + CMBaseMonster ::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: + CMBaseMonster :: StartTask( pTask ); + if (pev->movetype == MOVETYPE_FLY) + { + m_IdealActivity = ACT_GLIDE; + } + break; + + default: + CMBaseMonster :: StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CMHGrunt :: 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: + { + CMBaseMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void CMHGrunt :: 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 CMHGrunt :: 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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "Repel Land" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CMHGrunt ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMHGrunt, CMBaseMonster ); + +//========================================================= +// SetActivity +//========================================================= +void CMHGrunt :: 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" ); + } + else + { + // get launch anim + iSequence = LookupSequence( "launchgrenade" ); + } + 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 *CMHGrunt :: 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 ); + } + } + + 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 CMBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + { + { + //!!!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) && UTIL_IsPlayer(m_hEnemy)) + // player + SENTENCEG_PlayRndSz( ENT(pev), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); +/*jlb + 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); +jlb*/ + 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 ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) ) + { + // 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 ) ) + { + //!!!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 + { + //!!!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 CMBaseMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CMHGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + { + if ( RANDOM_LONG(0,1) ) + { + return &slGruntTakeCover[ 0 ]; + } + else + { + return &slGruntGrenadeCover[ 0 ]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[ 0 ]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + 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: + { + return &slGruntVictoryDance[ 0 ]; + } + case SCHED_GRUNT_SUPPRESS: + { + if ( UTIL_IsPlayer(m_hEnemy) && 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 CMBaseMonster :: GetScheduleOfType ( Type ); + } + } +} diff --git a/src/dlls/hornet.cpp b/src/dlls/hornet.cpp new file mode 100644 index 0000000..7601659 --- /dev/null +++ b/src/dlls/hornet.cpp @@ -0,0 +1,372 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// Hornets +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "weapons.h" +#include "hornet.h" + + +int iHornetTrail; +int iHornetPuff; + +//========================================================= +// don't let hornets gib, ever. +//========================================================= +int CMHornet :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // filter these bits a little. + bitsDamageType &= ~ ( DMG_ALWAYSGIB ); + bitsDamageType |= DMG_NEVERGIB; + + return CMBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +//========================================================= +void CMHornet :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + pev->flags |= FL_MONSTER; + pev->health = 1;// weak! + + // hornets don't live as long in multiplayer + m_flStopAttack = gpGlobals->time + 3.5; + + m_flFieldOfView = 0.9; // +- 25 degrees + + if ( RANDOM_LONG ( 1, 5 ) <= 2 ) + { + m_iHornetType = HORNET_TYPE_RED; + m_flFlySpeed = HORNET_RED_SPEED; + } + else + { + m_iHornetType = HORNET_TYPE_ORANGE; + m_flFlySpeed = HORNET_ORANGE_SPEED; + } + + SET_MODEL(ENT( pev ), "models/hornet.mdl"); + UTIL_SetSize( pev, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) ); + + SetTouch( DieTouch ); + SetThink( StartTrack ); + + edict_t *pSoundEnt = pev->owner; + if ( !pSoundEnt ) + pSoundEnt = edict(); + + // no real owner, or owner isn't a client. + pev->dmg = gSkillData.monDmgHornet; + + pev->nextthink = gpGlobals->time + 0.1; + ResetSequenceInfo( ); +} + + +void CMHornet :: Precache() +{ + PRECACHE_MODEL("models/hornet.mdl"); + + PRECACHE_SOUND( "agrunt/ag_fire1.wav" ); + PRECACHE_SOUND( "agrunt/ag_fire2.wav" ); + PRECACHE_SOUND( "agrunt/ag_fire3.wav" ); + + PRECACHE_SOUND( "hornet/ag_buzz1.wav" ); + PRECACHE_SOUND( "hornet/ag_buzz2.wav" ); + PRECACHE_SOUND( "hornet/ag_buzz3.wav" ); + + PRECACHE_SOUND( "hornet/ag_hornethit1.wav" ); + PRECACHE_SOUND( "hornet/ag_hornethit2.wav" ); + PRECACHE_SOUND( "hornet/ag_hornethit3.wav" ); + + iHornetPuff = PRECACHE_MODEL( "sprites/muz1.spr" ); + iHornetTrail = PRECACHE_MODEL("sprites/laserbeam.spr"); +} + +//========================================================= +// hornets will never get mad at each other, no matter who the owner is. +//========================================================= +int CMHornet::IRelationship ( CMBaseEntity *pTarget ) +{ + if ( pTarget->pev->modelindex == pev->modelindex ) + { + return R_NO; + } + + return CMBaseMonster :: IRelationship( pTarget ); +} + +//========================================================= +// ID's Hornet as their owner +//========================================================= +int CMHornet::Classify ( void ) +{ + + if ( pev->owner && pev->owner->v.flags & FL_CLIENT) + { + return CLASS_PLAYER_BIOWEAPON; + } + + return CLASS_ALIEN_BIOWEAPON; +} + +//========================================================= +// StartTrack - starts a hornet out tracking its target +//========================================================= +void CMHornet :: StartTrack ( void ) +{ + IgniteTrail(); + + SetTouch( TrackTouch ); + SetThink( TrackTarget ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// StartDart - starts a hornet out just flying straight. +//========================================================= +void CMHornet :: StartDart ( void ) +{ + IgniteTrail(); + + SetTouch( DartTouch ); + + SetThink( SUB_Remove ); + pev->nextthink = gpGlobals->time + 4; +} + +void CMHornet::IgniteTrail( void ) +{ +/* + + ted's suggested trail colors: + +r161 +g25 +b97 + +r173 +g39 +b14 + +old colors + case HORNET_TYPE_RED: + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + break; + case HORNET_TYPE_ORANGE: + WRITE_BYTE( 0 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + break; + +*/ + + // trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT( entindex() ); // entity + WRITE_SHORT( iHornetTrail ); // model + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 2 ); // width + + switch ( m_iHornetType ) + { + case HORNET_TYPE_RED: + WRITE_BYTE( 179 ); // r, g, b + WRITE_BYTE( 39 ); // r, g, b + WRITE_BYTE( 14 ); // r, g, b + break; + case HORNET_TYPE_ORANGE: + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + break; + } + + WRITE_BYTE( 128 ); // brightness + + MESSAGE_END(); +} + +//========================================================= +// Hornet is flying, gently tracking target +//========================================================= +void CMHornet :: TrackTarget ( void ) +{ + Vector vecFlightDir; + Vector vecDirToEnemy; + float flDelta; + + StudioFrameAdvance( ); + + if (gpGlobals->time > m_flStopAttack) + { + SetTouch( NULL ); + SetThink( SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + // UNDONE: The player pointer should come back after returning from another level + if ( m_hEnemy == NULL ) + {// enemy is dead. + Look( 512 ); + m_hEnemy = BestVisibleEnemy( ); + } + + if ( m_hEnemy != NULL && UTIL_FVisible( m_hEnemy, ENT(pev) )) + { + m_vecEnemyLKP = UTIL_BodyTarget( m_hEnemy, pev->origin ); + } + else + { + m_vecEnemyLKP = m_vecEnemyLKP + pev->velocity * m_flFlySpeed * 0.1; + } + + vecDirToEnemy = ( m_vecEnemyLKP - pev->origin ).Normalize(); + + if (pev->velocity.Length() < 0.1) + vecFlightDir = vecDirToEnemy; + else + vecFlightDir = pev->velocity.Normalize(); + + // measure how far the turn is, the wider the turn, the slow we'll go this time. + flDelta = DotProduct ( vecFlightDir, vecDirToEnemy ); + + if ( flDelta < 0.5 ) + {// hafta turn wide again. play sound + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + } + } + + if ( flDelta <= 0 && m_iHornetType == HORNET_TYPE_RED ) + {// no flying backwards, but we don't want to invert this, cause we'd go fast when we have to turn REAL far. + flDelta = 0.25; + } + + pev->velocity = ( vecFlightDir + vecDirToEnemy).Normalize(); + + if ( pev->owner && (pev->owner->v.flags & FL_MONSTER) ) + { + // random pattern only applies to hornets fired by monsters, not players. + + pev->velocity.x += RANDOM_FLOAT ( -0.10, 0.10 );// scramble the flight dir a bit. + pev->velocity.y += RANDOM_FLOAT ( -0.10, 0.10 ); + pev->velocity.z += RANDOM_FLOAT ( -0.10, 0.10 ); + } + + switch ( m_iHornetType ) + { + case HORNET_TYPE_RED: + pev->velocity = pev->velocity * ( m_flFlySpeed * flDelta );// scale the dir by the ( speed * width of turn ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.3 ); + break; + case HORNET_TYPE_ORANGE: + pev->velocity = pev->velocity * m_flFlySpeed;// do not have to slow down to turn. + pev->nextthink = gpGlobals->time + 0.1;// fixed think time + break; + } + + pev->angles = UTIL_VecToAngles (pev->velocity); + + pev->solid = SOLID_BBOX; +} + +//========================================================= +// Tracking Hornet hit something +//========================================================= +void CMHornet :: TrackTouch ( edict_t *pOther ) +{ + if ( (pOther == pev->owner) || pOther->v.modelindex == pev->modelindex ) + {// bumped into the guy that shot it. + pev->solid = SOLID_NOT; + return; + } + + // is this NOT a player and IS a monster? + if (!UTIL_IsPlayer(pOther) && (pOther->v.euser4 != NULL)) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + + if ( IRelationship( pMonster ) <= R_NO ) + { + // hit something we don't want to hurt, so turn around. + + pev->velocity = pev->velocity.Normalize(); + + pev->velocity.x *= -1; + pev->velocity.y *= -1; + + pev->origin = pev->origin + pev->velocity * 4; // bounce the hornet off a bit. + pev->velocity = pev->velocity * m_flFlySpeed; + + return; + } + } + + DieTouch( pOther ); +} + +void CMHornet::DartTouch( edict_t *pOther ) +{ + DieTouch( pOther ); +} + +void CMHornet::DieTouch ( edict_t *pOther ) +{ + if ( pOther && pOther->v.takedamage ) + {// do the damage + + switch (RANDOM_LONG(0,2)) + {// buzz when you plug someone + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit3.wav", 1, ATTN_NORM); break; + } + + if (UTIL_IsPlayer(pOther)) + UTIL_TakeDamage( pOther, pev, VARS( pev->owner ), pev->dmg, DMG_BULLET ); + else if (pOther->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + pMonster->TakeDamage( pev, VARS( pev->owner ), pev->dmg, DMG_BULLET ); + } + } + + pev->modelindex = 0;// so will disappear for the 0.1 secs we wait until NEXTTHINK gets rid + pev->solid = SOLID_NOT; + + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time + 1;// stick around long enough for the sound to finish! +} + diff --git a/src/dlls/hornet.h b/src/dlls/hornet.h new file mode 100644 index 0000000..074f946 --- /dev/null +++ b/src/dlls/hornet.h @@ -0,0 +1,55 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// Hornets +//========================================================= + +//========================================================= +// Hornet Defines +//========================================================= +#define HORNET_TYPE_RED 0 +#define HORNET_TYPE_ORANGE 1 +#define HORNET_RED_SPEED (float)600 +#define HORNET_ORANGE_SPEED (float)800 +#define HORNET_BUZZ_VOLUME (float)0.8 + +extern int iHornetPuff; + +//========================================================= +// Hornet - this is the projectile that the Alien Grunt fires. +//========================================================= +class CMHornet : public CMBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + int IRelationship ( CMBaseEntity *pTarget ); + + void IgniteTrail( void ); + void EXPORT StartTrack ( void ); + void EXPORT StartDart ( void ); + void EXPORT TrackTarget ( void ); + void EXPORT TrackTouch ( edict_t *pOther ); + void EXPORT DartTouch( edict_t *pOther ); + void EXPORT DieTouch ( edict_t *pOther ); + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + float m_flStopAttack; + int m_iHornetType; + float m_flFlySpeed; +}; + diff --git a/src/dlls/houndeye.cpp b/src/dlls/houndeye.cpp new file mode 100644 index 0000000..b70f4e1 --- /dev/null +++ b/src/dlls/houndeye.cpp @@ -0,0 +1,1100 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// Houndeye - spooky sonic dog. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "nodes.h" + +extern CGraph WorldGraph; + +// houndeye does 20 points of damage spread over a sphere 384 units in diameter, and each additional +// squad member increases the BASE damage by 110%, per the spec. +#define HOUNDEYE_MAX_SQUAD_SIZE 4 +#define HOUNDEYE_MAX_ATTACK_RADIUS 384 +#define HOUNDEYE_SQUAD_BONUS (float)1.1 + +#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye + +#define HOUNDEYE_SOUND_STARTLE_VOLUME 128 // how loud a sound has to be to badly scare a sleeping houndeye + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_HOUND_CLOSE_EYE = LAST_COMMON_TASK + 1, + TASK_HOUND_OPEN_EYE, + TASK_HOUND_THREAT_DISPLAY, + TASK_HOUND_FALL_ASLEEP, + TASK_HOUND_WAKE_UP, + TASK_HOUND_HOP_BACK +}; + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_HOUND_AGITATED = LAST_COMMON_SCHEDULE + 1, + SCHED_HOUND_HOP_RETREAT, + SCHED_HOUND_FAIL, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HOUND_AE_WARN 1 +#define HOUND_AE_STARTATTACK 2 +#define HOUND_AE_THUMP 3 +#define HOUND_AE_ANGERSOUND1 4 +#define HOUND_AE_ANGERSOUND2 5 +#define HOUND_AE_HOPBACK 6 +#define HOUND_AE_CLOSE_EYE 7 + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMHoundeye :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// FValidateHintType +//========================================================= +BOOL CMHoundeye :: FValidateHintType ( short sHint ) +{ + int i; + + static short sHoundHints[] = + { + HINT_WORLD_MACHINERY, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sHoundHints ) ; i++ ) + { + if ( sHoundHints[ i ] == sHint ) + { + return TRUE; + } + } + + ALERT ( at_aiconsole, "Couldn't validate hint type" ); + return FALSE; +} + + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CMHoundeye :: FCanActiveIdle ( void ) +{ + return TRUE; +} + + +//========================================================= +// CheckRangeAttack1 - overridden for houndeyes so that they +// try to get within half of their max attack radius before +// attacking, so as to increase their chances of doing damage. +//========================================================= +BOOL CMHoundeye :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ) && flDot >= 0.3 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMHoundeye :: SetYawSpeed ( void ) +{ + int ys; + + ys = 90; + + switch ( m_Activity ) + { + case ACT_CROUCHIDLE://sleeping! + ys = 0; + break; + case ACT_IDLE: + ys = 60; + break; + case ACT_WALK: + ys = 90; + break; + case ACT_RUN: + ys = 90; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// SetActivity +//========================================================= +void CMHoundeye :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + if ( NewActivity == m_Activity ) + return; + + if ( m_MonsterState == MONSTERSTATE_COMBAT && NewActivity == ACT_IDLE && RANDOM_LONG(0,1) ) + { + // play pissed idle. + iSequence = LookupSequence( "madidle" ); + + 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; + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to the reset anim (if it's there) + pev->frame = 0; // FIX: frame counter shouldn't be reset when its the same activity as before + ResetSequenceInfo(); + SetYawSpeed(); + } + } + else + { + CMBaseMonster :: SetActivity ( NewActivity ); + } +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMHoundeye :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch ( pEvent->event ) + { + case HOUND_AE_WARN: + // do stuff for this event. + WarnSound(); + break; + + case HOUND_AE_STARTATTACK: + WarmUpSound(); + break; + + case HOUND_AE_HOPBACK: + { + float flGravity = g_psv_gravity->value; + + pev->flags &= ~FL_ONGROUND; + + pev->velocity = gpGlobals->v_forward * -200; + pev->velocity.z += (0.6 * flGravity) * 0.5; + + break; + } + + case HOUND_AE_THUMP: + // emit the shockwaves + SonicAttack(); + break; + + case HOUND_AE_ANGERSOUND1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM); + break; + + case HOUND_AE_ANGERSOUND2: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain1.wav", 1, ATTN_NORM); + break; + + case HOUND_AE_CLOSE_EYE: + if ( !m_fDontBlink ) + { + pev->skin = HOUNDEYE_EYE_FRAMES - 1; + } + break; + + default: + CMBaseMonster :: HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMHoundeye :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/houndeye.mdl"); + UTIL_SetSize(pev, Vector ( -16, -16, 0 ), Vector ( 16, 16, 36 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_YELLOW; + pev->effects = 0; + pev->health = gSkillData.houndeyeHealth; + 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; + m_fAsleep = FALSE; // everyone spawns awake + m_fDontBlink = FALSE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMHoundeye :: Precache() +{ + PRECACHE_MODEL("models/houndeye.mdl"); + + PRECACHE_SOUND("houndeye/he_alert1.wav"); + PRECACHE_SOUND("houndeye/he_alert2.wav"); + PRECACHE_SOUND("houndeye/he_alert3.wav"); + + PRECACHE_SOUND("houndeye/he_die1.wav"); + PRECACHE_SOUND("houndeye/he_die2.wav"); + PRECACHE_SOUND("houndeye/he_die3.wav"); + + PRECACHE_SOUND("houndeye/he_idle1.wav"); + PRECACHE_SOUND("houndeye/he_idle2.wav"); + PRECACHE_SOUND("houndeye/he_idle3.wav"); + + PRECACHE_SOUND("houndeye/he_hunt1.wav"); + PRECACHE_SOUND("houndeye/he_hunt2.wav"); + PRECACHE_SOUND("houndeye/he_hunt3.wav"); + + PRECACHE_SOUND("houndeye/he_pain1.wav"); + PRECACHE_SOUND("houndeye/he_pain3.wav"); + PRECACHE_SOUND("houndeye/he_pain4.wav"); + PRECACHE_SOUND("houndeye/he_pain5.wav"); + + PRECACHE_SOUND("houndeye/he_attack1.wav"); + PRECACHE_SOUND("houndeye/he_attack3.wav"); + + PRECACHE_SOUND("houndeye/he_blast1.wav"); + PRECACHE_SOUND("houndeye/he_blast2.wav"); + PRECACHE_SOUND("houndeye/he_blast3.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/shockwave.spr" ); +} + +//========================================================= +// IdleSound +//========================================================= +void CMHoundeye :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// IdleSound +//========================================================= +void CMHoundeye :: WarmUpSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack1.wav", 0.7, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack3.wav", 0.7, ATTN_NORM ); + break; + } +} + +//========================================================= +// WarnSound +//========================================================= +void CMHoundeye :: WarnSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CMHoundeye :: AlertSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CMHoundeye :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CMHoundeye :: PainSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain5.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// WriteBeamColor - writes a color vector to the network +// based on the size of the group. +//========================================================= +void CMHoundeye :: WriteBeamColor ( void ) +{ + BYTE bRed, bGreen, bBlue; + + // solo houndeye - weakest beam + bRed = 188; + bGreen = 220; + bBlue = 255; + + WRITE_BYTE( bRed ); + WRITE_BYTE( bGreen ); + WRITE_BYTE( bBlue ); +} + + +//========================================================= +// SonicAttack +//========================================================= +void CMHoundeye :: SonicAttack ( void ) +{ + float flAdjustedDamage; + float flDist; + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast3.wav", 1, ATTN_NORM); break; + } + + // blast circles + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16 + HOUNDEYE_MAX_ATTACK_RADIUS / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + + WriteBeamColor(); + + WRITE_BYTE( 255 ); //brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16 + ( HOUNDEYE_MAX_ATTACK_RADIUS / 2 ) / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + + WriteBeamColor(); + + WRITE_BYTE( 255 ); //brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + + edict_t *pEntity = NULL; + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL) + { + if ( pEntity->v.takedamage != DAMAGE_NO ) + { + if ( strcmp(STRING(pEntity->v.model), "models/houndeye.mdl") != 0 ) + {// houndeyes don't hurt other houndeyes with their attack + + // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise. + // This means that you must get out of the houndeye's attack range entirely to avoid damage. + // Calculate full damage first + + // solo + flAdjustedDamage = gSkillData.houndeyeDmgBlast; + + flDist = (pEntity->v.origin - pev->origin).Length(); + + flAdjustedDamage -= ( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ) * flAdjustedDamage; + + if ( !UTIL_FVisible( pEntity, this->edict() ) ) + { + if ( UTIL_IsPlayer(pEntity) ) + { + // if this entity is a client, and is not in full view, inflict half damage. We do this so that players still + // take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients + // so that monsters in other parts of the level don't take the damage and get pissed. + flAdjustedDamage *= 0.5; + } + else if ( !FClassnameIs( pEntity, "func_breakable" ) && !FClassnameIs( pEntity, "func_pushable" ) ) + { + // do not hurt nonclients through walls, but allow damage to be done to breakables + flAdjustedDamage = 0; + } + } + + //ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage ); + + if (flAdjustedDamage > 0 ) + { + if (UTIL_IsPlayer(pEntity)) + UTIL_TakeDamage( pEntity, pev, pev, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); + else if (pEntity->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEntity)); + pMonster->TakeDamage( pev, pev, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); + } + } + } + } + } +} + +//========================================================= +// start task +//========================================================= +void CMHoundeye :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_HOUND_FALL_ASLEEP: + { + m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!) + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_WAKE_UP: + { + m_fAsleep = FALSE; // signal that hound is standing again + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_OPEN_EYE: + { + m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_CLOSE_EYE: + { + pev->skin = 0; + m_fDontBlink = TRUE; // tell blink code to leave the eye alone. + break; + } + case TASK_HOUND_THREAT_DISPLAY: + { + m_IdealActivity = ACT_IDLE_ANGRY; + break; + } + case TASK_HOUND_HOP_BACK: + { + m_IdealActivity = ACT_LEAP; + break; + } + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_GUARD: + { + m_IdealActivity = ACT_GUARD; + break; + } + default: + { + CMBaseMonster :: StartTask(pTask); + break; + } + } +} + +//========================================================= +// RunTask +//========================================================= +void CMHoundeye :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_HOUND_THREAT_DISPLAY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + + break; + } + case TASK_HOUND_CLOSE_EYE: + { + if ( pev->skin < HOUNDEYE_EYE_FRAMES - 1 ) + { + pev->skin++; + } + break; + } + case TASK_HOUND_HOP_BACK: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + case TASK_SPECIAL_ATTACK1: + { + pev->skin = RANDOM_LONG(0, HOUNDEYE_EYE_FRAMES - 1); + + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + float life; + life = ((255 - pev->frame) / (pev->framerate * m_flFrameRate)); + if (life < 0.1) life = 0.1; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_IMPLOSION); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_BYTE( 50 * life + 100); // radius + WRITE_BYTE( pev->frame / 25.0 ); // count + WRITE_BYTE( life * 10 ); // life + MESSAGE_END(); + + if ( m_fSequenceFinished ) + { + SonicAttack(); + TaskComplete(); + } + + break; + } + default: + { + CMBaseMonster :: RunTask(pTask); + break; + } + } +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CMHoundeye::PrescheduleThink ( void ) +{ + // if the hound is mad and is running, make hunt noises. + if ( m_MonsterState == MONSTERSTATE_COMBAT && m_Activity == ACT_RUN && RANDOM_FLOAT( 0, 1 ) < 0.2 ) + { + WarnSound(); + } + + // at random, initiate a blink if not already blinking or sleeping + if ( !m_fDontBlink ) + { + if ( ( pev->skin == 0 ) && RANDOM_LONG(0,0x7F) == 0 ) + {// start blinking! + pev->skin = HOUNDEYE_EYE_FRAMES - 1; + } + else if ( pev->skin != 0 ) + {// already blinking + pev->skin--; + } + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlHoundGuardPack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GUARD, (float)0 }, +}; + +Schedule_t slHoundGuardPack[] = +{ + { + tlHoundGuardPack, + ARRAYSIZE ( tlHoundGuardPack ), + bits_COND_SEE_HATE | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + 0, + "GuardPack" + }, +}; + +// primary range attack +Task_t tlHoundYell1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_HOUND_AGITATED }, +}; + +Task_t tlHoundYell2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slHoundRangeAttack[] = +{ + { + tlHoundYell1, + ARRAYSIZE ( tlHoundYell1 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundRangeAttack1" + }, + { + tlHoundYell2, + ARRAYSIZE ( tlHoundYell2 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundRangeAttack2" + }, +}; + +// lie down and fall asleep +Task_t tlHoundSleep[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_RANDOM, (float)5 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_HOUND_FALL_ASLEEP, (float)0 }, + { TASK_WAIT_RANDOM, (float)25 }, + { TASK_HOUND_CLOSE_EYE, (float)0 }, + //{ TASK_WAIT, (float)10 }, + //{ TASK_WAIT_RANDOM, (float)10 }, +}; + +Schedule_t slHoundSleep[] = +{ + { + tlHoundSleep, + ARRAYSIZE ( tlHoundSleep ), + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY, + 0, + "Hound Sleep" + }, +}; + +// wake and stand up lazily +Task_t tlHoundWakeLazy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_HOUND_OPEN_EYE, (float)0 }, + { TASK_WAIT_RANDOM, (float)2.5 }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_HOUND_WAKE_UP, (float)0 }, +}; + +Schedule_t slHoundWakeLazy[] = +{ + { + tlHoundWakeLazy, + ARRAYSIZE ( tlHoundWakeLazy ), + 0, + 0, + "WakeLazy" + }, +}; + +// wake and stand up with great urgency! +Task_t tlHoundWakeUrgent[] = +{ + { TASK_HOUND_OPEN_EYE, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_HOUND_WAKE_UP, (float)0 }, +}; + +Schedule_t slHoundWakeUrgent[] = +{ + { + tlHoundWakeUrgent, + ARRAYSIZE ( tlHoundWakeUrgent ), + 0, + 0, + "WakeUrgent" + }, +}; + + +Task_t tlHoundSpecialAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SPECIAL_ATTACK1, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_IDLE_ANGRY }, +}; + +Schedule_t slHoundSpecialAttack1[] = +{ + { + tlHoundSpecialAttack1, + ARRAYSIZE ( tlHoundSpecialAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + + 0, + "Hound Special Attack1" + }, +}; + +Task_t tlHoundAgitated[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, +}; + +Schedule_t slHoundAgitated[] = +{ + { + tlHoundAgitated, + ARRAYSIZE ( tlHoundAgitated ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Hound Agitated" + }, +}; + +Task_t tlHoundHopRetreat[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_HOP_BACK, 0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slHoundHopRetreat[] = +{ + { + tlHoundHopRetreat, + ARRAYSIZE ( tlHoundHopRetreat ), + 0, + 0, + "Hound Hop Retreat" + }, +}; + +// hound fails in combat with client in the PVS +Task_t tlHoundCombatFailPVS[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slHoundCombatFailPVS[] = +{ + { + tlHoundCombatFailPVS, + ARRAYSIZE ( tlHoundCombatFailPVS ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundCombatFailPVS" + }, +}; + +// hound fails in combat with no client in the PVS. Don't keep peeping! +Task_t tlHoundCombatFailNoPVS[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_PVS, 0 }, +}; + +Schedule_t slHoundCombatFailNoPVS[] = +{ + { + tlHoundCombatFailNoPVS, + ARRAYSIZE ( tlHoundCombatFailNoPVS ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundCombatFailNoPVS" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CMHoundeye ) +{ + slHoundGuardPack, + slHoundRangeAttack, + &slHoundRangeAttack[ 1 ], + slHoundSleep, + slHoundWakeLazy, + slHoundWakeUrgent, + slHoundSpecialAttack1, + slHoundAgitated, + slHoundHopRetreat, + slHoundCombatFailPVS, + slHoundCombatFailNoPVS, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMHoundeye, CMBaseMonster ); + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CMHoundeye :: GetScheduleOfType ( int Type ) +{ + if ( m_fAsleep ) + { + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + // get up fast, to fight. + return &slHoundWakeUrgent[ 0 ]; + } + + else + { + // hound is waking up on its own + return &slHoundWakeLazy[ 0 ]; + } + } + switch ( Type ) + { + case SCHED_IDLE_STAND: + { + // we may want to sleep instead of stand! + if ( !m_fAsleep && RANDOM_LONG(0,29) < 1 ) + { + return &slHoundSleep[ 0 ]; + } + else + { + return CMBaseMonster :: GetScheduleOfType( Type ); + } + } + case SCHED_RANGE_ATTACK1: + { + return &slHoundRangeAttack[ 0 ]; + } + case SCHED_SPECIAL_ATTACK1: + { + return &slHoundSpecialAttack1[ 0 ]; + } + case SCHED_GUARD: + { + return &slHoundGuardPack[ 0 ]; + } + case SCHED_HOUND_AGITATED: + { + return &slHoundAgitated[ 0 ]; + } + case SCHED_HOUND_HOP_RETREAT: + { + return &slHoundHopRetreat[ 0 ]; + } + case SCHED_FAIL: + { + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + // client in PVS + return &slHoundCombatFailPVS[ 0 ]; + } + else + { + // client has taken off! + return &slHoundCombatFailNoPVS[ 0 ]; + } + } + else + { + return CMBaseMonster :: GetScheduleOfType ( Type ); + } + } + default: + { + return CMBaseMonster :: GetScheduleOfType ( Type ); + } + } +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CMHoundeye :: GetSchedule( void ) +{ + 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 CMBaseMonster :: GetSchedule(); + } + + if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + if ( RANDOM_FLOAT( 0 , 1 ) <= 0.4 ) + { + TraceResult tr; + UTIL_MakeVectors( pev->angles ); + UTIL_TraceHull( pev->origin, pev->origin + gpGlobals->v_forward * -128, dont_ignore_monsters, head_hull, ENT( pev ), &tr ); + + if ( tr.flFraction == 1.0 ) + { + // it's clear behind, so the hound will jump + return GetScheduleOfType ( SCHED_HOUND_HOP_RETREAT ); + } + } + + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + break; + } + } + + return CMBaseMonster :: GetSchedule(); +} diff --git a/src/dlls/islave.cpp b/src/dlls/islave.cpp new file mode 100644 index 0000000..376481f --- /dev/null +++ b/src/dlls/islave.cpp @@ -0,0 +1,752 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// Alien slave monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "effects.h" +#include "weapons.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ISLAVE_AE_CLAW ( 1 ) +#define ISLAVE_AE_CLAWRAKE ( 2 ) +#define ISLAVE_AE_ZAP_POWERUP ( 3 ) +#define ISLAVE_AE_ZAP_SHOOT ( 4 ) +#define ISLAVE_AE_ZAP_DONE ( 5 ) + + +const char *CMISlave::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CMISlave::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CMISlave::pPainSounds[] = +{ + "aslave/slv_pain1.wav", + "aslave/slv_pain2.wav", +}; + +const char *CMISlave::pDeathSounds[] = +{ + "aslave/slv_die1.wav", + "aslave/slv_die2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMISlave :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + + +int CMISlave::IRelationship( CMBaseEntity *pTarget ) +{ + if ( (pTarget->IsPlayer()) ) + if ( (pev->spawnflags & SF_MONSTER_WAIT_UNTIL_PROVOKED ) && ! (m_afMemory & bits_MEMORY_PROVOKED )) + return R_NO; + return CMBaseMonster::IRelationship( pTarget ); +} + + +//========================================================= +// ALertSound - scream +//========================================================= +void CMISlave :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + SENTENCEG_PlayRndSz(ENT(pev), "SLV_ALERT", 0.85, ATTN_NORM, 0, m_voicePitch); + } +} + +//========================================================= +// IdleSound +//========================================================= +void CMISlave :: IdleSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + SENTENCEG_PlayRndSz(ENT(pev), "SLV_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch); + } + +#if 0 + int side = RANDOM_LONG( 0, 1 ) * 2 - 1; + + ClearBeams( ); + ArmBeam( side ); + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + gpGlobals->v_right * 2 * side; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecSrc.x); // X + WRITE_COORD(vecSrc.y); // Y + WRITE_COORD(vecSrc.z); // Z + WRITE_BYTE( 8 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 96 ); // b + WRITE_BYTE( 10 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap1.wav", 1, ATTN_NORM, 0, 100 ); +#endif +} + +//========================================================= +// PainSound +//========================================================= +void CMISlave :: PainSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } +} + +//========================================================= +// DieSound +//========================================================= + +void CMISlave :: DeathSound( void ) +{ + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pDeathSounds[ RANDOM_LONG(0,ARRAYSIZE(pDeathSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); +} + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CMISlave :: ISoundMask ( void) +{ + return 0; +} + + +void CMISlave::Killed( entvars_t *pevAttacker, int iGib ) +{ + ClearBeams( ); + CMBaseMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMISlave :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_WALK: + ys = 50; + break; + case ACT_RUN: + ys = 70; + break; + case ACT_IDLE: + ys = 50; + break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CMISlave :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + switch( pEvent->event ) + { + case ISLAVE_AE_CLAW: + { + // SOUND HERE! + edict_t *pHurt = CheckTraceHullAttack( 70, gSkillData.slaveDmgClaw, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->v.flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->v.punchangle.z = -18; + pHurt->v.punchangle.x = 5; + } + // 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, m_voicePitch ); + } + 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, m_voicePitch ); + } + } + break; + + case ISLAVE_AE_CLAWRAKE: + { + edict_t *pHurt = CheckTraceHullAttack( 70, gSkillData.slaveDmgClawrake, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->v.flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->v.punchangle.z = -18; + pHurt->v.punchangle.x = 5; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case ISLAVE_AE_ZAP_POWERUP: + { + UTIL_MakeAimVectors( pev->angles ); + + if (m_iBeams == 0) + { + Vector vecSrc = pev->origin + gpGlobals->v_forward * 2; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecSrc.x); // X + WRITE_COORD(vecSrc.y); // Y + WRITE_COORD(vecSrc.z); // Z + WRITE_BYTE( 12 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 96 ); // b + WRITE_BYTE( 20 / pev->framerate ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + + } +/*jlb + if (m_hDead != NULL) + { + WackBeam( -1, m_hDead ); + WackBeam( 1, m_hDead ); + } + else +jlb*/ + { + ArmBeam( -1 ); + ArmBeam( 1 ); + BeamGlow( ); + } + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 100 + m_iBeams * 10 ); + pev->skin = m_iBeams / 2; + } + break; + + case ISLAVE_AE_ZAP_SHOOT: + { + ClearBeams( ); + +/*jlb + if (m_hDead != NULL) + { + Vector vecDest = m_hDead->v.origin + Vector( 0, 0, 38 ); + TraceResult trace; + UTIL_TraceHull( vecDest, vecDest, dont_ignore_monsters, human_hull, m_hDead, &trace ); + + if ( !trace.fStartSolid ) + { + CMBaseEntity *pNew = Create( "monster_alien_slave", m_hDead->pev->origin, m_hDead->pev->angles ); + CMBaseMonster *pNewMonster = pNew->MyMonsterPointer( ); + pNew->pev->spawnflags |= 1; + WackBeam( -1, pNew ); + WackBeam( 1, pNew ); + UTIL_Remove( m_hDead ); + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + break; + } + } +jlb*/ + ClearMultiDamage(); + + UTIL_MakeAimVectors( pev->angles ); + + ZapBeam( -1 ); + ZapBeam( 1 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + // STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); + + ApplyMultiDamage(pev, pev); + + m_flNextAttack = gpGlobals->time + RANDOM_FLOAT( 0.5, 4.0 ); + } + break; + + case ISLAVE_AE_ZAP_DONE: + { + ClearBeams( ); + } + break; + + default: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// CheckRangeAttack1 - normal beam attack +//========================================================= +BOOL CMISlave :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + return CMBaseMonster::CheckRangeAttack1( flDot, flDist ); +} + +//========================================================= +// CheckRangeAttack2 - check bravery and try to resurect dead comrades +//========================================================= +BOOL CMISlave :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + +//jlb m_hDead = NULL; + m_iBravery = 0; + + edict_t *pEntity = NULL; + while ((pEntity = UTIL_FindEntityByClassname( pEntity, "monster_alien_slave" )) != NULL) + { + TraceResult tr; + + UTIL_TraceLine( EyePosition( ), UTIL_EyePosition(pEntity), ignore_monsters, ENT(pev), &tr ); + if (tr.flFraction == 1.0 || tr.pHit == pEntity) + { + if (pEntity->v.deadflag == DEAD_DEAD) + { + float d = (pev->origin - pEntity->v.origin).Length(); + if (d < flDist) + { +//jlb m_hDead = pEntity; + flDist = d; + } + m_iBravery--; + } + else + { + m_iBravery++; + } + } + } +//jlb if (m_hDead != NULL) +//jlb return TRUE; +//jlb else + return FALSE; +} + + +//========================================================= +// StartTask +//========================================================= +void CMISlave :: StartTask ( Task_t *pTask ) +{ + ClearBeams( ); + + CMBaseMonster :: StartTask ( pTask ); +} + + +//========================================================= +// Spawn +//========================================================= +void CMISlave :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/islave.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; + pev->effects = 0; + pev->health = gSkillData.slaveHealth; + pev->view_ofs = Vector ( 0, 0, 64 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5; + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_RANGE_ATTACK2 | bits_CAP_DOORS_GROUP; + + m_voicePitch = RANDOM_LONG( 85, 110 ); + + for (int i = 0; i < ISLAVE_MAX_BEAMS; i++) + m_pBeam[i] = NULL; + + m_iBravery = 0; + m_flNextAttack = 0.0f; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMISlave :: Precache() +{ + int i; + + PRECACHE_MODEL("models/islave.mdl"); + PRECACHE_MODEL("sprites/lgtning.spr"); + PRECACHE_SOUND("debris/zap1.wav"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("hassault/hw_shoot1.wav"); + PRECACHE_SOUND("zombie/zo_pain2.wav"); + PRECACHE_SOUND("headcrab/hc_headbite.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + + 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( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pDeathSounds ); i++ ) + PRECACHE_SOUND((char *)pDeathSounds[i]); +} + + +//========================================================= +// TakeDamage - get provoked when injured +//========================================================= + +int CMISlave :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // don't slash one of your own + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pevAttacker)); + if (pMonster != NULL) + { + if ((bitsDamageType & DMG_SLASH) && pevAttacker && IRelationship( pMonster ) < R_DL) + return 0; + } + + m_afMemory |= bits_MEMORY_PROVOKED; + return CMBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +void CMISlave::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if (bitsDamageType & DMG_SHOCK) + return; + + CMBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +// primary range attack +Task_t tlSlaveAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSlaveAttack1[] = +{ + { + tlSlaveAttack1, + ARRAYSIZE ( tlSlaveAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + 0, + "Slave Range Attack1" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CMISlave ) +{ + slSlaveAttack1, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMISlave, CMBaseMonster ); + + +//========================================================= +//========================================================= +Schedule_t *CMISlave :: GetSchedule( void ) +{ + ClearBeams( ); + +/* + if (pev->spawnflags) + { + pev->spawnflags = 0; + return GetScheduleOfType( SCHED_RELOAD ); + } +*/ + + 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 CMBaseMonster :: GetSchedule(); + } + + if (pev->health < 20 || m_iBravery < 0) + { + if (!HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + m_failSchedule = SCHED_CHASE_ENEMY; + if (HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + // ALERT( at_console, "exposed\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } + } + break; + } + return CMBaseMonster::GetSchedule( ); +} + + +Schedule_t *CMISlave :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_FAIL: + if (HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + return CMBaseMonster :: GetScheduleOfType( SCHED_MELEE_ATTACK1 ); ; + } + break; + case SCHED_RANGE_ATTACK1: + return slSlaveAttack1; + case SCHED_RANGE_ATTACK2: + return slSlaveAttack1; + } + return CMBaseMonster :: GetScheduleOfType( Type ); +} + + +//========================================================= +// ArmBeam - small beam from arm to nearby geometry +//========================================================= + +void CMISlave :: ArmBeam( int side ) +{ + TraceResult tr; + float flDist = 1.0; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + gpGlobals->v_up * 36 + gpGlobals->v_right * side * 16 + gpGlobals->v_forward * 32; + + for (int i = 0; i < 3; i++) + { + Vector vecAim = gpGlobals->v_right * side * RANDOM_FLOAT( 0, 1 ) + gpGlobals->v_up * RANDOM_FLOAT( -1, 1 ); + TraceResult tr1; + UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 512, dont_ignore_monsters, ENT( pev ), &tr1); + if (flDist > tr1.flFraction) + { + tr = tr1; + flDist = tr.flFraction; + } + } + + // Couldn't find anything close enough + if ( flDist == 1.0 ) + return; + + DecalGunshot( &tr, BULLET_PLAYER_CROWBAR ); + + m_pBeam[m_iBeams] = CMBeam::BeamCreate( "sprites/lgtning.spr", 30 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + // m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetColor( 96, 128, 16 ); + m_pBeam[m_iBeams]->SetBrightness( 64 ); + m_pBeam[m_iBeams]->SetNoise( 80 ); + m_iBeams++; +} + + +//========================================================= +// BeamGlow - brighten all beams +//========================================================= +void CMISlave :: BeamGlow( ) +{ + int b = m_iBeams * 32; + if (b > 255) + b = 255; + + for (int i = 0; i < m_iBeams; i++) + { + if (m_pBeam[i]->GetBrightness() != 255) + { + m_pBeam[i]->SetBrightness( b ); + } + } +} + + +//========================================================= +// WackBeam - regenerate dead colleagues +//========================================================= +void CMISlave :: WackBeam( int side, edict_t *pEntity ) +{ + Vector vecDest; + float flDist = 1.0; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + if (pEntity == NULL) + return; + + m_pBeam[m_iBeams] = CMBeam::BeamCreate( "sprites/lgtning.spr", 30 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( UTIL_Center(pEntity), entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 80 ); + m_iBeams++; +} + +//========================================================= +// ZapBeam - heavy damage directly forward +//========================================================= +void CMISlave :: ZapBeam( int side ) +{ + Vector vecSrc, vecAim; + TraceResult tr; + edict_t *pEntity; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + vecSrc = pev->origin + gpGlobals->v_up * 36; + vecAim = ShootAtEnemy( vecSrc ); + float deflection = 0.01; + vecAim = vecAim + side * gpGlobals->v_right * RANDOM_FLOAT( 0, deflection ) + gpGlobals->v_up * RANDOM_FLOAT( -deflection, deflection ); + UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 1024, dont_ignore_monsters, ENT( pev ), &tr); + + m_pBeam[m_iBeams] = CMBeam::BeamCreate( "sprites/lgtning.spr", 50 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 20 ); + m_iBeams++; + + pEntity = tr.pHit; + if (pEntity != NULL && pEntity->v.takedamage) + { + if (UTIL_IsPlayer(pEntity)) + UTIL_TraceAttack( pEntity, pev, gSkillData.slaveDmgZap, vecAim, &tr, DMG_SHOCK ); + else if (pEntity->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEntity)); + pMonster->TraceAttack( pev, gSkillData.slaveDmgZap, vecAim, &tr, DMG_SHOCK ); + } + } + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); +} + + +//========================================================= +// ClearBeams - remove all beams +//========================================================= +void CMISlave :: ClearBeams( ) +{ + for (int i = 0; i < ISLAVE_MAX_BEAMS; i++) + { + if (m_pBeam[i]) + { + UTIL_Remove( m_pBeam[i]->edict() ); + m_pBeam[i] = NULL; + } + } + m_iBeams = 0; + pev->skin = 0; + + STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); +} diff --git a/src/dlls/monster_api.cpp b/src/dlls/monster_api.cpp new file mode 100644 index 0000000..f70dade --- /dev/null +++ b/src/dlls/monster_api.cpp @@ -0,0 +1,146 @@ +// +// botman's monster - MetaMOD plugin +// +// monster_api.cpp +// + +/* + * This 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 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 code; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include "extdll.h" +#include "meta_api.h" +#include "sdk_util.h" // UTIL_LogPrintf, etc + +// Must provide at least one of these.. +static META_FUNCTIONS gMetaFunctionTable = { + NULL, // pfnGetEntityAPI HL SDK; called before game DLL + NULL, // pfnGetEntityAPI_Post META; called after game DLL + GetEntityAPI2, // pfnGetEntityAPI2 HL SDK2; called before game DLL + GetEntityAPI2_Post, // pfnGetEntityAPI2_Post META; called after game DLL + NULL, // pfnGetNewDLLFunctions HL SDK2; called before game DLL + NULL, // pfnGetNewDLLFunctions_Post META; called after game DLL + NULL, // pfnGetEngineFunctions META; called before HL engine + NULL, // pfnGetEngineFunctions_Post META; called after HL engine +}; + +// Description of plugin +plugin_info_t Plugin_info = { + META_INTERFACE_VERSION, // interface version + "Monster", // name + "3.00.00", // version + "06/30/2002", // date + "botman ", // author + "http://planethalflife.com/botman/", // url + "MONSTER", // logtag + PT_CHANGELEVEL, // (when) loadable + PT_CHANGELEVEL, // (when) unloadable +}; + +char *VNAME=Plugin_info.name; +char *VVERSION=Plugin_info.version; +char *VDATE=Plugin_info.date; +char *VAUTHOR=Plugin_info.author; +char *VURL=Plugin_info.url; +char *VLOGTAG=Plugin_info.logtag; +char *COMPILE_TIME=__DATE__ ", " __TIME__; + +// Global vars from metamod: +meta_globals_t *gpMetaGlobals; // metamod globals +gamedll_funcs_t *gpGamedllFuncs; // gameDLL function tables +mutil_funcs_t *gpMetaUtilFuncs; // metamod utility functions + +cvar_t init_dllapi_log = {"monster_log", "0", FCVAR_EXTDLL, 0, NULL}; +cvar_t *dllapi_log = NULL; +cvar_t init_monster_spawn = {"monster_spawn", "1", FCVAR_EXTDLL, 0, NULL}; +cvar_t *monster_spawn = NULL; + + +// Metamod requesting info about this plugin: +// ifvers (given) interface_version metamod is using +// pPlugInfo (requested) struct with info about plugin +// pMetaUtilFuncs (given) table of utility functions provided by metamod +C_DLLEXPORT int Meta_Query(char *ifvers, plugin_info_t **pPlugInfo, + mutil_funcs_t *pMetaUtilFuncs) +{ + if(ifvers); // to satisfy gcc -Wunused + // Give metamod our plugin_info struct + *pPlugInfo=&Plugin_info; + // Get metamod utility function table. + gpMetaUtilFuncs=pMetaUtilFuncs; + return(TRUE); +} + +// Metamod attaching plugin to the server. +// now (given) current phase, ie during map, during changelevel, or at startup +// pFunctionTable (requested) table of function tables this plugin catches +// pMGlobals (given) global vars from metamod +// pGamedllFuncs (given) copy of function tables from game dll +C_DLLEXPORT int Meta_Attach(PLUG_LOADTIME now, META_FUNCTIONS *pFunctionTable, + meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) +{ + if(now); // to satisfy gcc -Wunused + if(!pMGlobals) { + LOG_ERROR(PLID, "Meta_Attach called with null pMGlobals"); + return(FALSE); + } + gpMetaGlobals=pMGlobals; + if(!pFunctionTable) { + LOG_ERROR(PLID, "Meta_Attach called with null pFunctionTable"); + return(FALSE); + } + memcpy(pFunctionTable, &gMetaFunctionTable, sizeof(META_FUNCTIONS)); + gpGamedllFuncs=pGamedllFuncs; + + LOG_MESSAGE(PLID, "%s v%s, %s", VNAME, VVERSION, VDATE); + LOG_MESSAGE(PLID, "by %s", VAUTHOR); + LOG_MESSAGE(PLID, " %s", VURL); + LOG_MESSAGE(PLID, "compiled: %s CDT", COMPILE_TIME); + + LOG_CONSOLE(PLID, "[%s] %s v%s, %s", VLOGTAG, VNAME, VVERSION, VDATE); + LOG_CONSOLE(PLID, "[%s] by %s", VLOGTAG, VAUTHOR); + + CVAR_REGISTER(&init_dllapi_log); + dllapi_log = CVAR_GET_POINTER("monster_log"); + + CVAR_REGISTER(&init_monster_spawn); + monster_spawn = CVAR_GET_POINTER("monster_spawn"); + + return(TRUE); +} + + +extern void monster_unload(void); + +// Metamod detaching plugin from the server. +// now (given) current phase, ie during map, etc +// reason (given) why detaching (refresh, console unload, forced unload, etc) +C_DLLEXPORT int Meta_Detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason) { + // remove all the monsters currently in the level... + monster_unload(); + return(TRUE); +} + diff --git a/src/dlls/monster_config.cpp b/src/dlls/monster_config.cpp new file mode 100644 index 0000000..7bd2399 --- /dev/null +++ b/src/dlls/monster_config.cpp @@ -0,0 +1,418 @@ +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#include "extdll.h" +#include "dllapi.h" +#include "meta_api.h" + +#include "monster_plugin.h" + +extern cvar_t *dllapi_log; + +extern monster_type_t monster_types[]; +extern int monster_spawn_count; + + +bool get_input(FILE *fp, char *input) +{ + char line[1024]; + int len, pos; + + while (!feof(fp)) + { + if (fgets(line, 1023, fp) != NULL) + { + len = strlen(line); + + if (len == 0) + continue; // skip any null lines + + // remove any trailing newline, carriage return or whitespace... + while ((line[len-1] == '\n') || (line[len-1] == '\r') || + isspace(line[len-1])) + { + line[len-1] = 0; + len--; + if (len == 0) + break; + } + + pos = 0; + + while (isspace(line[pos])) + pos++; // skip leading blanks + + if ((line[pos] == '/') && (line[pos+1] == '/')) + continue; // skip comment lines + + if (line[pos] == 0) + continue; // skip empty lines + + strcpy(input, &line[pos]); + return TRUE; + } + } + + return FALSE; // no input found +} + + +bool scan_monster_cfg(FILE *fp) +{ + char input[1024]; + bool origin, angle, delay, angle_min, angle_max, monster; + float x, y, z; + + while (get_input(fp, input)) + { + if (monster_spawn_count == MAX_MONSTERS) + continue; // skip if max monster spawn count reached + + if (input[0] == '{') + { + origin = angle = delay = angle_min = angle_max = monster = FALSE; + + monster_spawnpoint[monster_spawn_count].monster_count = 0; + + while (get_input(fp, input)) + { + if (input[0] == '}') + break; + + if (strncmp(input, "origin/", 7) == 0) + { + if (origin) + { + META_CONS("[MONSTER] ERROR: origin found twice: %s", input); + LOG_MESSAGE(PLID, "ERROR: origin found twice: %s", input); + return TRUE; // error + } + if (sscanf(&input[7], "%f %f %f", &x, &y, &z) != 3) + { + META_CONS("[MONSTER] ERROR: invalid origin: %s", input); + LOG_MESSAGE(PLID, "ERROR: invalid origin: %s", input); + return TRUE; // error + } + origin = TRUE; + monster_spawnpoint[monster_spawn_count].origin[0] = x; + monster_spawnpoint[monster_spawn_count].origin[1] = y; + monster_spawnpoint[monster_spawn_count].origin[2] = z; + } + else if (strncmp(input, "delay/", 6) == 0) + { + if (delay) + { + META_CONS("[MONSTER] ERROR: delay found twice: %s", input); + LOG_MESSAGE(PLID, "ERROR: delay found twice: %s", input); + return TRUE; // error + } + if (sscanf(&input[6], "%f", &x) != 1) + { + META_CONS("[MONSTER] ERROR: invalid delay: %s", input); + LOG_MESSAGE(PLID, "ERROR: invalid delay: %s", input); + return TRUE; // error + } + delay = TRUE; + monster_spawnpoint[monster_spawn_count].delay = x; + } + else if (strncmp(input, "angle/", 6) == 0) + { + if (angle) + { + META_CONS("[MONSTER] ERROR: angle found twice: %s", input); + LOG_MESSAGE(PLID, "ERROR: angle found twice: %s", input); + return TRUE; // error + } + if (angle_min || angle_max) + { + META_CONS("[MONSTER] ERROR: you can't specify angle AND angle_min or angle_max: %s", input); + LOG_MESSAGE(PLID, "ERROR: you can't specify angle AND angle_min or angle_max: %s", input); + return TRUE; // error + } + if (sscanf(&input[6], "%f", &x) != 1) + { + META_CONS("[MONSTER] ERROR: invalid angle: %s", input); + LOG_MESSAGE(PLID, "ERROR: invalid angle: %s", input); + return TRUE; // error + } + angle = TRUE; + monster_spawnpoint[monster_spawn_count].angle_min = x; + monster_spawnpoint[monster_spawn_count].angle_max = x; + } + else if (strncmp(input, "angle_min/", 10) == 0) + { + if (angle_min) + { + META_CONS("[MONSTER] ERROR: angle_min found twice: %s", input); + LOG_MESSAGE(PLID, "ERROR: angle_min found twice: %s", input); + return TRUE; // error + } + if (angle) + { + META_CONS("[MONSTER] ERROR: you can't specify angle AND angle_min or angle_max: %s", input); + LOG_MESSAGE(PLID, "ERROR: you can't specify angle AND angle_min or angle_max: %s", input); + return TRUE; // error + } + if (sscanf(&input[10], "%f", &x) != 1) + { + META_CONS("[MONSTER] ERROR: invalid angle_min: %s", input); + LOG_MESSAGE(PLID, "ERROR: invalid angle_min: %s", input); + return TRUE; // error + } + angle_min = TRUE; + monster_spawnpoint[monster_spawn_count].angle_min = x; + } + else if (strncmp(input, "angle_max/", 10) == 0) + { + if (angle_max) + { + META_CONS("[MONSTER] ERROR: angle_max found twice: %s", input); + LOG_MESSAGE(PLID, "ERROR: angle_max found twice: %s", input); + return TRUE; // error + } + if (angle) + { + META_CONS("[MONSTER] ERROR: you can't specify angle AND angle_min or angle_max: %s", input); + LOG_MESSAGE(PLID, "ERROR: you can't specify angle AND angle_min or angle_max: %s", input); + return TRUE; // error + } + if (sscanf(&input[10], "%f", &x) != 1) + { + META_CONS("[MONSTER] ERROR: invalid angle_max: %s", input); + LOG_MESSAGE(PLID, "ERROR: invalid angle_max: %s", input); + return TRUE; // error + } + angle_max = TRUE; + monster_spawnpoint[monster_spawn_count].angle_max = x; + } + else if (strncmp(input, "monster/", 8) == 0) + { + int index; + int count = monster_spawnpoint[monster_spawn_count].monster_count; + if (count < MAX_MONSTER_COUNT) + { + for (index=0; monster_types[index].name[0]; index++) + { + if (strcmp(&input[8], monster_types[index].name) == 0) + { + monster_spawnpoint[monster_spawn_count].monster[count] = index; + monster_spawnpoint[monster_spawn_count].monster_count++; + + monster_types[index].need_to_precache = TRUE; + break; + } + } + if (monster_types[index].name[0] == 0) + { + META_CONS("[MONSTER] ERROR: invalid monster name: %s", input); + LOG_MESSAGE(PLID, "ERROR: invalid monster name: %s", input); + return TRUE; + } + } + monster = TRUE; + } + else + { + META_CONS("[MONSTER] ERROR: unknown command: %s", input); + LOG_MESSAGE(PLID, "ERROR: unknown command: %s", input); + return TRUE; // error occurred + } + } + + // check for all necessary fields here... + if (!origin) + { + META_CONS("[MONSTER] ERROR: you didn't specify an origin!"); + LOG_MESSAGE(PLID, "ERROR: you didn't specify an origin!"); + return TRUE; + } + if (angle_min && !angle_max) + { + META_CONS("[MONSTER] ERROR: you specified angle_min but didn't specify angle_max!"); + LOG_MESSAGE(PLID, "ERROR: you specified angle_min but didn't specify angle_max!"); + return TRUE; + } + if (angle_max && !angle_min) + { + META_CONS("[MONSTER] ERROR: you specified angle_max but didn't specify angle_min!"); + LOG_MESSAGE(PLID, "ERROR: you specified angle_max but didn't specify angle_min!"); + return TRUE; + } + if (!monster) + { + META_CONS("[MONSTER] ERROR: No monster key found!"); + LOG_MESSAGE(PLID, "ERROR: No monster key found!"); + return TRUE; + } + + if (!delay) + monster_spawnpoint[monster_spawn_count].delay = 30; // 30 second default delay + + if (monster_spawnpoint[monster_spawn_count].delay < 1) + monster_spawnpoint[monster_spawn_count].delay = 1; // no negative or zero delay + + if (!angle && !angle_min && !angle_max) // no angle specified, use 0-359 as default + { + monster_spawnpoint[monster_spawn_count].angle_min = 0; + monster_spawnpoint[monster_spawn_count].angle_max = 359; + } + + if (monster_spawnpoint[monster_spawn_count].angle_min < 0) + monster_spawnpoint[monster_spawn_count].angle_min = 0; // no negative angles + + if (monster_spawnpoint[monster_spawn_count].angle_max > 359) + monster_spawnpoint[monster_spawn_count].angle_max = 359; // no angle > 359 degrees + + monster_spawnpoint[monster_spawn_count].respawn_time = gpGlobals->time + RANDOM_FLOAT(10.0, 20.0); + monster_spawnpoint[monster_spawn_count].need_to_respawn = TRUE; + + if (dllapi_log->value) + { + char name_monsters[256]; + + LOG_MESSAGE(PLID, "Added monster spawn at: %7.2f %7.2f %7.2f", + monster_spawnpoint[monster_spawn_count].origin.x, + monster_spawnpoint[monster_spawn_count].origin.y, + monster_spawnpoint[monster_spawn_count].origin.z); + LOG_MESSAGE(PLID, " with delay = %7.2f, angle_min = %7.2f, angle_max = %7.2f", + monster_spawnpoint[monster_spawn_count].delay, + monster_spawnpoint[monster_spawn_count].angle_min, + monster_spawnpoint[monster_spawn_count].angle_max); + name_monsters[0] = 0; + for (int i = 0; i < monster_spawnpoint[monster_spawn_count].monster_count; i++) + { + strcat(name_monsters, monster_types[monster_spawnpoint[monster_spawn_count].monster[i]].name); + strcat(name_monsters, " "); + } + LOG_MESSAGE(PLID, " monsters = %s", name_monsters); + } + + monster_spawn_count++; + } + } + + return FALSE; +} + + +bool process_monster_cfg(void) +{ + char game_dir[256]; + char filename[256]; + FILE *fp = NULL; + bool status = FALSE; // no error + + monster_spawn_count = 0; + + // find the directory name of the currently running MOD... + (*g_engfuncs.pfnGetGameDir)(game_dir); + + strcpy(filename, game_dir); +#ifdef __linux__ + strcat(filename, "/maps/"); +#else + strcat(filename, "\\maps\\"); +#endif + strcat(filename, STRING(gpGlobals->mapname)); + strcat(filename, "_monster.cfg"); + + // check if the map specific filename exists... + if (access(filename, 0) == 0) + { + if (dllapi_log->value) + { + META_CONS("[MONSTER] Processing config file=%s", filename); + LOG_MESSAGE(PLID, "Processing config file=%s", filename); + } + + if ((fp = fopen(filename, "r")) == NULL) + { + META_CONS("[MONSTER] ERROR: Could not open \"%s\"!", filename); + LOG_MESSAGE(PLID, "ERROR: Could not open \"%s\" file!", filename); + + return TRUE; // return bad status + } + + status = scan_monster_cfg(fp); + + fclose(fp); + } + + return status; +} + + +bool scan_monster_precache_cfg(FILE *fp) +{ + char input[1024]; + bool found; + + while (get_input(fp, input)) + { + found = FALSE; + + for (int index=0; monster_types[index].name[0]; index++) + { + if (strcmp(input, monster_types[index].name) == 0) + { + monster_types[index].need_to_precache = TRUE; + found = TRUE; + break; + } + } + + if (found == FALSE) + { + META_CONS("[MONSTER] ERROR: invalid precache monster name: %s", input); + LOG_MESSAGE(PLID, "ERROR: invalid precache monster name: %s", input); + } + } + + return FALSE; +} + + +bool process_monster_precache_cfg(void) +{ + char game_dir[256]; + char filename[256]; + FILE *fp = NULL; + bool status = FALSE; // no error + + // find the directory name of the currently running MOD... + (*g_engfuncs.pfnGetGameDir)(game_dir); + + strcpy(filename, game_dir); + strcat(filename, "/monster_precache.cfg"); + + // check if the map specific filename exists... + if (access(filename, 0) == 0) + { + if (dllapi_log->value) + { + META_CONS("[MONSTER] Processing config file=%s", filename); + LOG_MESSAGE(PLID, "Processing config file=%s", filename); + } + + if ((fp = fopen(filename, "r")) == NULL) + { + META_CONS("[MONSTER] ERROR: Could not open \"%s\"!", filename); + LOG_MESSAGE(PLID, "ERROR: Could not open \"%s\" file!", filename); + + return TRUE; // return bad status + } + + status = scan_monster_precache_cfg(fp); + + fclose(fp); + } + + return status; +} diff --git a/src/dlls/monster_mm.def b/src/dlls/monster_mm.def new file mode 100644 index 0000000..a8f0241 --- /dev/null +++ b/src/dlls/monster_mm.def @@ -0,0 +1,5 @@ +LIBRARY monster_mm +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/src/dlls/monster_mm.dsp b/src/dlls/monster_mm.dsp new file mode 100644 index 0000000..a62baa6 --- /dev/null +++ b/src/dlls/monster_mm.dsp @@ -0,0 +1,378 @@ +# Microsoft Developer Studio Project File - Name="monster_mm" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=monster_mm - 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 "monster_mm.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 "monster_mm.mak" CFG="monster_mm - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "monster_mm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "monster_mm - 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)" == "monster_mm - 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 "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "monster_mm_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\dlls" /I "..\common" /I "..\engine" /I "..\pm_shared" /I "..\..\metamod" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "monster_mm_EXPORTS" /D strcasecmp=stricmp /D strncasecmp=_strnicmp /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /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 +# ADD 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 /def:".\monster_mm.def" +# Begin Custom Build - Copying to DLL folder +TargetPath=.\Release\monster_mm.dll +TargetName=monster_mm +InputPath=.\Release\monster_mm.dll +SOURCE="$(InputPath)" + +"$(TargetName)" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetPath) D:\Half-Life\valve\dlls + copy $(TargetPath) D:\Half-Life\tfc\dlls + copy $(TargetPath) D:\Half-Life\cstrike\dlls + copy $(TargetPath) D:\Half-Life\dmc\dlls + copy $(TargetPath) D:\Half-Life\dod\dlls + copy $(TargetPath) D:\Half-Life\firearms\dlls + copy $(TargetPath) D:\Half-Life\frontline\dlls + +# End Custom Build + +!ELSEIF "$(CFG)" == "monster_mm - 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 "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "monster_mm_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "..\dlls" /I "..\common" /I "..\engine" /I "..\pm_shared" /I "..\..\metamod" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "monster_mm_EXPORTS" /D strcasecmp=stricmp /D strncasecmp=_strnicmp /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 0x409 /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 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 /def:".\monster_mm.def" /pdbtype:sept +# Begin Custom Build - Copying to DLL folder +TargetPath=.\Debug\monster_mm.dll +TargetName=monster_mm +InputPath=.\Debug\monster_mm.dll +SOURCE="$(InputPath)" + +"$(TargetName)" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetPath) D:\Half-Life\valve\dlls + copy $(TargetPath) D:\Half-Life\tfc\dlls + copy $(TargetPath) D:\Half-Life\cstrike\dlls + copy $(TargetPath) D:\Half-Life\dmc\dlls + copy $(TargetPath) D:\Half-Life\dod\dlls + copy $(TargetPath) D:\Half-Life\firearms\dlls + copy $(TargetPath) D:\Half-Life\frontline\dlls + copy $(TargetPath) D:\Half-Life\gearbox\dlls + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "monster_mm - Win32 Release" +# Name "monster_mm - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\agrunt.cpp +# End Source File +# Begin Source File + +SOURCE=.\AI_BaseNPC_Schedule.cpp +# End Source File +# Begin Source File + +SOURCE=.\animating.cpp +# End Source File +# Begin Source File + +SOURCE=.\animation.cpp +# End Source File +# Begin Source File + +SOURCE=.\apache.cpp +# End Source File +# Begin Source File + +SOURCE=.\barney.cpp +# End Source File +# Begin Source File + +SOURCE=.\bigmomma.cpp +# End Source File +# Begin Source File + +SOURCE=.\bullsquid.cpp +# End Source File +# Begin Source File + +SOURCE=.\cmbase.cpp +# End Source File +# Begin Source File + +SOURCE=.\combat.cpp +# End Source File +# Begin Source File + +SOURCE=.\controller.cpp +# End Source File +# Begin Source File + +SOURCE=.\defaultai.cpp +# End Source File +# Begin Source File + +SOURCE=.\dllapi.cpp +# End Source File +# Begin Source File + +SOURCE=.\effects.cpp +# End Source File +# Begin Source File + +SOURCE=.\flyingmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\ggrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_ai.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp +# End Source File +# Begin Source File + +SOURCE=.\hassassin.cpp +# End Source File +# Begin Source File + +SOURCE=.\headcrab.cpp +# End Source File +# Begin Source File + +SOURCE=.\hgrunt.cpp +# End Source File +# Begin Source File + +SOURCE=.\hornet.cpp +# End Source File +# Begin Source File + +SOURCE=.\houndeye.cpp +# End Source File +# Begin Source File + +SOURCE=.\islave.cpp +# End Source File +# Begin Source File + +SOURCE=.\monster_api.cpp +# End Source File +# Begin Source File + +SOURCE=.\monster_config.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsters.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsterstate.cpp +# End Source File +# Begin Source File + +SOURCE=.\nodes.cpp +# End Source File +# Begin Source File + +SOURCE=.\scientist.cpp +# End Source File +# Begin Source File + +SOURCE=.\skill.cpp +# End Source File +# Begin Source File + +SOURCE=.\sound.cpp +# End Source File +# Begin Source File + +SOURCE=.\squeakgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\subs.cpp +# End Source File +# Begin Source File + +SOURCE=.\talkmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=.\weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\zombie.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\activity.h +# End Source File +# Begin Source File + +SOURCE=.\animation.h +# End Source File +# Begin Source File + +SOURCE=.\cdll_dll.h +# End Source File +# Begin Source File + +SOURCE=.\cmbase.h +# End Source File +# Begin Source File + +SOURCE=.\cmbasemonster.h +# End Source File +# Begin Source File + +SOURCE=.\cmflyingmonster.h +# End Source File +# Begin Source File + +SOURCE=.\cmtalkmonster.h +# End Source File +# Begin Source File + +SOURCE=.\decals.h +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\effects.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\explode.h +# End Source File +# Begin Source File + +SOURCE=.\extdll.h +# End Source File +# Begin Source File + +SOURCE=.\func_break.h +# End Source File +# Begin Source File + +SOURCE=.\monster_plugin.h +# End Source File +# Begin Source File + +SOURCE=.\monsters.h +# End Source File +# Begin Source File + +SOURCE=.\nodes.h +# End Source File +# Begin Source File + +SOURCE=.\schedule.h +# End Source File +# Begin Source File + +SOURCE=.\skill.h +# End Source File +# Begin Source File + +SOURCE=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=.\weapons.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/src/dlls/monster_plugin.h b/src/dlls/monster_plugin.h new file mode 100644 index 0000000..9a6f0c9 --- /dev/null +++ b/src/dlls/monster_plugin.h @@ -0,0 +1,57 @@ +// +// monster_plugin.h +// + +#ifndef MONSTER_PLUGIN_H +#define MONSTER_PLUGIN_H + + +typedef struct +{ + char *name; + bool need_to_precache; +} monster_type_t; + + +class CMBaseMonster; + +typedef struct +{ + int monster_index; + edict_t *monster_pent; + bool killed; + int respawn_index; + CMBaseMonster *pMonster; +} monster_t; + +#define MAX_MONSTER_ENTS 200 + +extern monster_t monsters[MAX_MONSTER_ENTS]; + + +#define MAX_MONSTER_COUNT 20 + +typedef struct { + Vector origin; + float angle_min, angle_max; + float delay; + unsigned char monster[MAX_MONSTER_COUNT]; + int monster_count; + float respawn_time; + bool need_to_respawn; +} monster_spawnpoint_t; + +#define MAX_MONSTERS 100 +extern monster_spawnpoint_t monster_spawnpoint[MAX_MONSTERS]; + +extern DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +extern DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +extern DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +extern DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +extern DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood +extern DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +extern DLL_GLOBAL const char *g_pModelNameLaser; +extern DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot + +#endif diff --git a/src/dlls/monsterevent.h b/src/dlls/monsterevent.h new file mode 100644 index 0000000..0193b69 --- /dev/null +++ b/src/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1996-2001, 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 EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/src/dlls/monsters.cpp b/src/dlls/monsters.cpp new file mode 100644 index 0000000..e5666ba --- /dev/null +++ b/src/dlls/monsters.cpp @@ -0,0 +1,2915 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "weapons.h" +#include "decals.h" + +#define MONSTER_CUT_CORNER_DIST 8 // 8 means the monster's bounding box is contained without the box of the node in WC + + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +extern DLL_GLOBAL BOOL g_fDrawLines; + +extern CGraph WorldGraph;// the world node graph + + + +//========================================================= +// Eat - makes a monster full for a little while. +//========================================================= +void CMBaseMonster :: Eat ( float flFullDuration ) +{ + m_flHungryTime = gpGlobals->time + flFullDuration; +} + +//========================================================= +// FShouldEat - returns true if a monster is hungry. +//========================================================= +BOOL CMBaseMonster :: 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 CMBaseMonster :: 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 CMBaseMonster :: BarnacleVictimReleased ( void ) +{ + m_IdealMonsterState = MONSTERSTATE_IDLE; + + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_STEP; +} + + +//========================================================= +// FValidateHintType - tells use whether or not the monster cares +// about the type of Hint Node given +//========================================================= +BOOL CMBaseMonster :: 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 CMBaseMonster :: 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_edictList_count = 0; + + edict_t *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 ) ) + { + edict_t *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->edict() && + !FBitSet( pSightEnt->v.spawnflags, SF_MONSTER_PRISONER ) && + pSightEnt->v.health > 0 ) + { + // is this a player AND are they alive? + if (UTIL_IsPlayer(pSightEnt) && UTIL_IsAlive(pSightEnt)) + { + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen. + if ( UTIL_FInViewCone( pSightEnt, ENT(pev), m_flFieldOfView ) && + !FBitSet( pSightEnt->v.flags, FL_NOTARGET ) && UTIL_FVisible( pSightEnt, ENT(pev) ) ) + { + m_edictList[m_edictList_count] = pSightEnt; + m_edictList_count++; + + // if we see a client, remember that (mostly for scripted AI) + iSighted |= bits_COND_SEE_CLIENT; + + // is this monster NOT a scientist? + if (strcmp(STRING(pev->model), "models/scientist.mdl") != 0) + { + iSighted |= bits_COND_SEE_DISLIKE; + + 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; + } + } + } + } + else if (pSightEnt->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pSightEnt)); + + // 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( pMonster ) != R_NO && UTIL_FInViewCone( pSightEnt, ENT(pev), m_flFieldOfView ) && + !FBitSet( pSightEnt->v.flags, FL_NOTARGET ) && UTIL_FVisible( pSightEnt, ENT(pev) ) ) + { + m_edictList[m_edictList_count] = pSightEnt; + m_edictList_count++; + + if ( ENT(pMonster->pev) == 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 ( pMonster ) ) + { + 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(pMonster->pev->classname ) ); + break; + } + } + } + } + } + } + + SetConditions( iSighted ); +} + + +//========================================================= +// Monster Think - calls out to core AI functions and handles this +// monster's specific animation events +//========================================================= +void CMBaseMonster :: MonsterThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1;// 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 ); + } +#if _DEBUG + else + { + if ( !TaskIsRunning() && !TaskIsComplete() ) + ALERT( at_error, "Schedule stalled!!\n" ); + } +#endif +} + +//========================================================= +// CMBaseMonster - USE - will make a monster angry at whomever +// activated it. +//========================================================= +void CMBaseMonster :: MonsterUse ( edict_t *pActivator, edict_t *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 CMBaseMonster :: IgnoreConditions ( void ) +{ + int iIgnoreConditions = 0; + + if ( !FShouldEat() ) + { + // not hungry? Ignore food smell. + iIgnoreConditions |= bits_COND_SMELL_FOOD; + } + + return iIgnoreConditions; +} + +//========================================================= +// RouteClear - zeroes out the monster's route array and goal +//========================================================= +void CMBaseMonster :: 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 CMBaseMonster :: RouteNew ( void ) +{ + m_Route[ 0 ].iType = 0; + m_iRouteIndex = 0; +} + +//========================================================= +// FRouteClear - returns TRUE is the Route is cleared out +// ( invalid ) +//========================================================= +BOOL CMBaseMonster :: 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 CMBaseMonster :: FRefreshRoute ( void ) +{ + edict_t *pPathCorner; + int i; + BOOL returnCode; + + RouteNew(); + + returnCode = FALSE; + + switch( m_movementGoal ) + { + case MOVEGOAL_PATHCORNER: + { + // monster is on a path_corner loop + pPathCorner = m_pGoalEnt; + i = 0; + + while ( pPathCorner && i < ROUTE_SIZE ) + { + m_Route[ i ].iType = bits_MF_TO_PATHCORNER; + m_Route[ i ].vecLocation = pPathCorner->v.origin; + + pPathCorner = UTIL_GetNextTarget(pPathCorner); + + // 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->v.origin, bits_MF_TO_TARGETENT, m_hTargetEnt ); + } + break; + + case MOVEGOAL_NODE: + returnCode = FGetNodeRoute( m_vecMoveGoal ); +// if ( returnCode ) +// RouteSimplify( NULL ); + break; + } + + return returnCode; +} + + +BOOL CMBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_ENEMY; + return FRefreshRoute(); +} + + +BOOL CMBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_LOCATION; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +BOOL CMBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_TARGETENT; + return FRefreshRoute(); +} + + +BOOL CMBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_NODE; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +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 CMBaseMonster :: RouteSimplify( edict_t *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 CMBaseMonster :: FBecomeProne ( void ) +{ + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + m_IdealMonsterState = MONSTERSTATE_PRONE; + return TRUE; +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CMBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CMBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 512 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CMBaseMonster :: 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->v.flags, FL_ONGROUND ) ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 +//========================================================= +BOOL CMBaseMonster :: 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 CMBaseMonster :: CheckAttacks ( edict_t *pTarget, float flDist ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pTarget->v.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 CMBaseMonster :: 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 CMBaseMonster :: CheckEnemy ( edict_t *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 ( !UTIL_FVisible( pEnemy, ENT(pev) ) ) + { + ASSERT(!HasConditions(bits_COND_SEE_ENEMY)); + SetConditions( bits_COND_ENEMY_OCCLUDED ); + } + else + ClearConditions( bits_COND_ENEMY_OCCLUDED ); + + if ( !UTIL_IsAlive(pEnemy) ) + { + SetConditions ( bits_COND_ENEMY_DEAD ); + ClearConditions( bits_COND_SEE_ENEMY | bits_COND_ENEMY_OCCLUDED ); + return FALSE; + } + + Vector vecEnemyPos = pEnemy->v.origin; + // distance to enemy's origin + flDistToEnemy = ( vecEnemyPos - pev->origin ).Length(); + vecEnemyPos.z += pEnemy->v.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->v.size.z; + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + edict_t *pEnemyMonster; + + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->v.origin; + + pEnemyMonster = pEnemy; + + if ( pEnemyMonster ) + { + if ( UTIL_FInViewCone(pEnemyMonster, ENT(this->pev), m_flFieldOfView) ) + { + SetConditions ( bits_COND_ENEMY_FACING_ME ); + } + else + ClearConditions( bits_COND_ENEMY_FACING_ME ); + } + + if (pEnemy->v.velocity != Vector( 0, 0, 0)) + { + // trail the enemy a bit + m_vecEnemyLKP = m_vecEnemyLKP - pEnemy->v.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->v.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 CMBaseMonster :: PushEnemy( edict_t *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 CMBaseMonster :: 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 (UTIL_IsAlive(m_hOldEnemy[i])) // 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 CMBaseMonster :: 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 CMBaseMonster :: 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 CMBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, edict_t *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( pev, 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 == 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->v.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, SVC_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( pev, vecStartPos ); + + return iReturn; +} + + +//========================================================= +// AdvanceRoute - poorly named function that advances the +// m_iRouteIndex. If it goes beyond ROUTE_SIZE, the route +// is refreshed. +//========================================================= +void CMBaseMonster :: 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 = UTIL_GetNextTarget(m_pGoalEnt); + + // 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 CMBaseMonster :: 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 CMBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, edict_t *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, SVC_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 CMBaseMonster :: 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 CMBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, edict_t *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, SVC_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, SVC_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, SVC_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, SVC_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 CMBaseMonster :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + Vector vecDir; + Vector vecApex; + edict_t *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 ) + { + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + CMBaseMonster *pBlocker = GetClassPtr((CMBaseMonster *)VARS(gpGlobals->trace_ent)); + if (pBlocker) + { + Blocked( 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 + { +//jlb 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 CMBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= MONSTER_CUT_CORNER_DIST ) + { + // ALERT( at_console, "cut %f\n", flWaypointDist ); + return TRUE; + } + + return FALSE; +} + + +void CMBaseMonster::MoveExecute( edict_t *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.0f, 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 CMBaseMonster :: MonsterInit ( void ) +{ + // 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); + if ( pev->spawnflags & SF_MONSTER_HITMONSTERCLIP ) + pev->flags |= FL_MONSTERCLIP; + + ClearSchedule(); + RouteClear(); + InitBoneControllers( ); // FIX: should be done in Spawn + + m_iHintNode = NO_NODE; + + m_afMemory = MEMORY_CLEAR; + + m_hEnemy = NULL; + m_hTargetEnt = NULL; + + for (int i=0; i < MAX_OLD_ENEMIES; i++) + m_hOldEnemy[ i ] = NULL; + + m_flDistTooFar = 1024.0; + m_flDistLook = 2048.0; + + // set eye position + SetEyePosition(); + + SetThink( MonsterInitThink ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse ( MonsterUse ); +} + +//========================================================= +// MonsterInitThink - Calls StartMonster. Startmonster is +// virtual, but this function cannot be +//========================================================= +void CMBaseMonster :: MonsterInitThink ( void ) +{ + StartMonster(); +} + +//========================================================= +// StartMonster - final bit of initization before a monster +// is turned over to the AI. +//========================================================= +void CMBaseMonster :: 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. + if (!WALK_MOVE ( ENT(pev), 0, 0, WALKMOVE_NORMAL ) ) + { +//jlb ALERT(at_console, "Monster %s stuck in wall", STRING(pev->classname)); +//jlb stuck +//jlb pev->effects = EF_BRIGHTFIELD; + } + } + else + { + pev->flags &= ~FL_ONGROUND; + } + + if ( !FStringNull(pev->target) )// this monster has a target + { + // Find the monster's initial target entity, stash it + m_pGoalEnt = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING( pev->target ) ); + + if ( !m_pGoalEnt ) + { + ALERT(at_error, "ReadyMonster()--%s couldn't find target %s", STRING(pev->classname), STRING(pev->target)); + } + else + { + // Monster will start turning towards his destination + MakeIdealYaw ( m_pGoalEnt->v.origin ); + + // JAY: How important is this error message? Big Momma doesn't obey this rule, so I took it out. +#if 0 + // At this point, we expect only a path_corner as initial goal + if (!FClassnameIs( m_pGoalEnt->pev, "path_corner")) + { + ALERT(at_warning, "ReadyMonster--monster's initial goal '%s' is not a path_corner", STRING(pev->target)); + } +#endif + + // 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 ) ); + } + } + + //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 ( CallMonsterThink ); + pev->nextthink += 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 CMBaseMonster :: 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 CMBaseMonster::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 CMBaseMonster::IRelationship ( CMBaseEntity *pTarget ) +{ + static int iEnemy[14][14] = + { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 }, + /*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 } + }; + + return iEnemy[ Classify() ][ pTarget->Classify() ]; +} + +//========================================================= +// 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 CMBaseMonster :: 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, SVC_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 CMBaseMonster :: 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. +//========================================================= +edict_t *CMBaseMonster :: BestVisibleEnemy ( void ) +{ + int iNearest; + int iDist; + int iBestRelationship; + edict_t *pReturn; + edict_t *pEnt; + int edictList_index = 0; + + iNearest = 8192;// so first visible entity will become the closest. + + iBestRelationship = R_NO; + + pReturn = NULL; + + while (edictList_index < m_edictList_count) + { + pEnt = m_edictList[edictList_index]; + + if ( UTIL_IsPlayer(pEnt) ) + { + // it's a player... + iDist = ( pEnt->v.origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = R_NM; // player is always nemsis + pReturn = pEnt; + } + } + else if (pEnt->v.euser4 != NULL) + { + CMBaseMonster *pNextEnt = GetClassPtr((CMBaseMonster *)VARS(pEnt)); + 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 = pEnt; + } + 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 = pEnt; + } + } + } + } + + edictList_index++; + } + + 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 CMBaseMonster :: 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 CMBaseMonster::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 CMBaseMonster::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 CMBaseMonster::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 CMBaseMonster :: 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 CMBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + 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 CMBaseMonster :: 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 CMBaseMonster :: 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 CMBaseMonster :: 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 CMBaseMonster::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->v.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. " ); + } + + 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 CMBaseMonster :: 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 + { + CMBaseToggle::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 CMBaseMonster :: 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 && UTIL_IsPlayer(m_hEnemy) && 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: + break; + case AITRIGGER_HEARPLAYER: + break; + case AITRIGGER_HEARCOMBAT: + 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" ); + FireTargets( STRING( m_iszTriggerTarget ), this->edict(), this->edict(), USE_TOGGLE, 0 ); + m_iTriggerCondition = AITRIGGER_NONE; + return TRUE; + } + + 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 CMBaseMonster :: 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 CMBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) +{ + edict_t *pEnemy = m_hEnemy; + + if ( pEnemy ) + { + return ( (UTIL_BodyTarget(pEnemy, shootOrigin ) - pEnemy->v.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 CMBaseMonster :: FacingIdeal( void ) +{ + if ( fabs( FlYawDiff() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!! + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CMBaseMonster :: FCanActiveIdle ( void ) +{ + /* + if ( m_MonsterState == MONSTERSTATE_IDLE && m_IdealMonsterState == MONSTERSTATE_IDLE && !IsMoving() ) + { + return TRUE; + } + */ + return FALSE; +} + + +void CMBaseMonster::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 ); +//jlb else +//jlb SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, PITCH_NORM ); + } +} + + +void CMBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CMBaseEntity *pListener ) +{ + PlaySentence( pszSentence, duration, volume, attenuation ); +} + + +void CMBaseMonster::SentenceStop( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, "common/null.wav", 1.0, ATTN_IDLE ); +} + + +void CMBaseMonster::CorpseFallThink( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + SetThink ( NULL ); + + SetSequenceBox( ); + UTIL_SetOrigin( pev, pev->origin );// link into world. + } + else + pev->nextthink = gpGlobals->time + 0.1; +} + +// Call after animation/pose is set up +void CMBaseMonster :: 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( pev, pev->origin ); + + // Setup health counters, etc. + BecomeDead(); + SetThink( CorpseFallThink ); + pev->nextthink = gpGlobals->time + 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 CMBaseMonster :: 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 CMBaseMonster :: GetEnemy ( void ) +{ + edict_t *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->v.origin; + } + // if the new enemy has an owner, take that one as well +//jlb if (pNewEnemy->v.owner != NULL) +//jlb { +//jlb edict_t *pOwner = pNewEnemy->v.owner; +//jlb if ( pOwner && (pOwner->v.flags & FL_MONSTER) && IRelationship( pOwner ) != R_NO ) +//jlb PushEnemy( pOwner, m_vecEnemyLKP ); +//jlb } + } + } + } + + // do we see a player? Allies use this to set the m_hEnemy pointer... + if (HasConditions(bits_COND_SEE_CLIENT) && (m_hEnemy == NULL)) + { + m_hEnemy = BestVisibleEnemy(); + m_hTargetEnt = m_hEnemy; + m_vecEnemyLKP = m_hEnemy->v.origin; + } + + // 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 +} + + +BOOL CMBaseMonster :: ShouldFadeOnDeath( void ) +{ + // if flagged to fade out or I have an owner (I came from a monster spawner) + if ( (pev->spawnflags & SF_MONSTER_FADECORPSE) || !FNullEnt( pev->owner ) ) + return TRUE; + + return FALSE; +} + + +void CMBaseMonster :: TaskFail( void ) +{ + SetConditions(bits_COND_TASK_FAILED); +} + diff --git a/src/dlls/monsters.h b/src/dlls/monsters.h new file mode 100644 index 0000000..6f4fac8 --- /dev/null +++ b/src/dlls/monsters.h @@ -0,0 +1,180 @@ +/*** +* +* Copyright (c) 1996-2001, 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 + +*/ + +// 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_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_FADECORPSE 512 // Fade out corpse after death +#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" +*/ + +// +// A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. +// +class CMGib : public CMBaseEntity +{ +public: + void Spawn( const char *szGibModel ); + void EXPORT BounceGibTouch ( edict_t *pOther ); + void EXPORT StickyGibTouch ( edict_t *pOther ); + void EXPORT WaitTillLand( void ); + void LimitVelocity( void ); + + virtual int ObjectCaps( void ) { return (CMBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + static void SpawnHeadGib( entvars_t *pevVictim ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ); + static void SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ); + + int m_bloodColor; + int m_cBloodDecals; + int m_material; + float m_lifeTime; +}; + + +#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/src/dlls/monsterstate.cpp b/src/dlls/monsterstate.cpp new file mode 100644 index 0000000..4329f5d --- /dev/null +++ b/src/dlls/monsterstate.cpp @@ -0,0 +1,214 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// monsterstate.cpp - base class monster functions for +// controlling core AI. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" + +//========================================================= +// SetState +//========================================================= +void CMBaseMonster :: 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 ) && + ( strcmp(STRING(pev->model), "models/scientist.mdl") != 0)) + { + m_hEnemy = NULL;// not allowed to have an enemy anymore. + ALERT ( at_aiconsole, "Stripped\n" ); + } + break; + } + + m_MonsterState = State; + m_IdealMonsterState = State; +} + +//========================================================= +// RunAI +//========================================================= +void CMBaseMonster :: 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 ) ) + { + Look( m_flDistLook ); +//jlb 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 CMBaseMonster :: 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 ) + { + } + 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; + } + 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_DEAD: + m_IdealMonsterState = MONSTERSTATE_DEAD; + break; + } + + return m_IdealMonsterState; +} + diff --git a/src/dlls/nodes.cpp b/src/dlls/nodes.cpp new file mode 100644 index 0000000..19c63e0 --- /dev/null +++ b/src/dlls/nodes.cpp @@ -0,0 +1,3405 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "nodes.h" +#include "animation.h" +#include "doors.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 + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +CGraph WorldGraph; + +#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 ) +{ + edict_t *pentSearch; + edict_t *pentTrigger; + entvars_t *pevTrigger; + entvars_t *pevLinkEnt; + TraceResult tr; + + pevLinkEnt = pLink->m_pLinkEnt; + if ( !pevLinkEnt ) + return NULL; + + pentSearch = 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 ) + { + pentTrigger = FIND_ENTITY_BY_TARGET ( pentSearch, STRING( pevLinkEnt->targetname ) );// find the button or trigger + + if ( FNullEnt( pentTrigger ) ) + {// 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; + } + + pentSearch = pentTrigger; + pevTrigger = VARS( pentTrigger ); + + 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, NULL, &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; + 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_breakable + 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, NULL, &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, NULL, &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, SVC_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 CMBaseEntity *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 CMBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + { + if (pEntity->pev->waterlevel != 0) + { + 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. + // + for ( int 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, SVC_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, SVC_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 ULONG Hash(void *p, int len) +{ + CRC32_t ulCrc; + CRC32_INIT(&ulCrc); + CRC32_PROCESS_BUFFER(&ulCrc, p, len); + return CRC32_FINAL(ulCrc); +} + +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, CMBaseEntity *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, SVC_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, + NULL,//!!!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, + NULL,//!!!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 CMBaseMonster +{ + +public: + void Spawn( entvars_t *pevMasterNode ); + virtual int ObjectCaps( void ) { return CMBaseMonster :: 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; +}; + +//========================================================= +// 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 ( SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( DropDelay ); + pev->nextthink = gpGlobals->time + 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 ( VARS(pev), WorldGraph.m_pNodes[ 0 ].m_vecOrigin ); + + SetThink ( CallBuildNodeGraph ); + + pev->nextthink = gpGlobals->time + 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 + CMBaseEntity::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 = CreateClassPtr((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 ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//jlbextern BOOL gTouchDisabled; +void CTestHull::CallBuildNodeGraph( void ) +{ + // TOUCH HACK -- Don't allow this entity to call anyone's "touch" function +//jlb gTouchDisabled = TRUE; + BuildNodeGraph(); +//jlb 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 ( SUB_Remove );// no matter what happens, the hull gets rid of itself. + pev->nextthink = gpGlobals->time; + +// 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" ); + + 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) == CONTENTS_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, + NULL,//!!!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, + NULL,//!!!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 ( 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 ( pev, 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, SVC_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_FOR_ME(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]; + FILE *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; + } + + // make sure directories have been made + GET_GAME_DIR( szFilename ); + strcat( szFilename, "/maps" ); + CreateDirectory( szFilename, NULL ); + strcat( szFilename, "/graphs" ); + CreateDirectory( szFilename, NULL ); + + strcat( szFilename, "/" ); + strcat( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + file = fopen ( 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 + fwrite ( &iVersion, sizeof ( int ), 1, file ); + + // write the CGraph class + fwrite ( this, sizeof ( CGraph ), 1, file ); + + // write the nodes + fwrite ( m_pNodes, sizeof ( CNode ), m_cNodes, file ); + + // write the links + fwrite ( m_pLinkPool, sizeof ( CLink ), m_cLinks, file ); + + fwrite ( m_di, sizeof(DIST_INFO), m_cNodes, file ); + + // Write the route info. + // + if ( m_pRouteInfo && m_nRouteInfo ) + { + fwrite ( m_pRouteInfo, sizeof( char ), m_nRouteInfo, file ); + } + + if (m_pHashLinks && m_nHashLinks) + { + fwrite(m_pHashLinks, sizeof(short), m_nHashLinks, file); + } + fclose ( 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; + edict_t *pentLinkEnt; + + 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; + pentLinkEnt = FIND_ENTITY_BY_STRING( NULL, "model", name ); + + if ( FNullEnt ( pentLinkEnt ) ) + { + // 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 = VARS( pentLinkEnt ); + + 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; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 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; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 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. + // + for (int 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++; + for (int 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; + } + for (int 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. + // + // + for (int 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++) + { + for (int 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. + // + for (int 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) + { + for (int 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; + for (int 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); + for (int 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; +} diff --git a/src/dlls/nodes.h b/src/dlls/nodes.h new file mode 100644 index 0000000..66cbc6b --- /dev/null +++ b/src/dlls/nodes.h @@ -0,0 +1,374 @@ +/*** +* +* Copyright (c) 1996-2001, 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, CMBaseEntity *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 CMBaseEntity *pEntity ); // what hull the monster uses + int NodeType( const CMBaseEntity *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 CMBaseEntity +{ + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CMBaseEntity :: 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/src/dlls/plane.h b/src/dlls/plane.h new file mode 100644 index 0000000..10baeee --- /dev/null +++ b/src/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2001, 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 PLANE_H +#define PLANE_H + +//========================================================= +// Plane +//========================================================= +class CPlane +{ +public: + CPlane ( void ); + + //========================================================= + // InitializePlane - Takes a normal for the plane and a + // point on the plane and + //========================================================= + void InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ); + + //========================================================= + // PointInFront - determines whether the given vector is + // in front of the plane. + //========================================================= + BOOL PointInFront ( const Vector &vecPoint ); + + Vector m_vecNormal; + float m_flDist; + BOOL m_fInitialized; +}; + +#endif // PLANE_H diff --git a/src/dlls/schedule.h b/src/dlls/schedule.h new file mode 100644 index 0000000..0c09441 --- /dev/null +++ b/src/dlls/schedule.h @@ -0,0 +1,290 @@ +/*** +* +* Copyright (c) 1996-2001, 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_TARGET, + TASK_RUN_TO_TARGET, + 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_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_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/src/dlls/scientist.cpp b/src/dlls/scientist.cpp new file mode 100644 index 0000000..76e76d8 --- /dev/null +++ b/src/dlls/scientist.cpp @@ -0,0 +1,1013 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "animation.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 +//======================================================= + + +//========================================================= +// 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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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, + 0, + "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( CMScientist ) +{ + slFollow, + slFaceTarget, + slIdleSciStand, + slFear, + slScientistCover, + slScientistHide, + slScientistStartle, + slHeal, + slStopFollowing, + slSciPanic, + slFollowScared, + slFaceTargetScared, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CMScientist, CMTalkMonster ); + + +void CMScientist::DeclineFollowing( void ) +{ + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_POK", 2, VOL_NORM, ATTN_NORM ); +} + + +void CMScientist :: Scream( void ) +{ + if ( FOkToSpeak() ) + { + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + } +} + + +Activity CMScientist::GetStoppedActivity( void ) +{ + if ( m_hEnemy != NULL ) + return ACT_EXCITED; + return CMTalkMonster::GetStoppedActivity(); +} + + +void CMScientist :: 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 ( UTIL_IsPlayer(m_hEnemy) ) + 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->v.origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->v.origin; + if ( !MoveToTarget( ACT_WALK_SCARED, 0.5 ) ) + TaskFail(); + } + } + break; + + default: + CMTalkMonster::StartTask( pTask ); + break; + } +} + +void CMScientist :: 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->v.origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->v.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->v.origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + break; + default: + CMTalkMonster::RunTask( pTask ); + break; + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMScientist :: Classify ( void ) +{ + return CLASS_HUMAN_PASSIVE; +} + + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMScientist :: 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 CMScientist :: 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: + CMTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CMScientist :: Spawn( void ) +{ + Precache( ); + + // every new scientist must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + 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; + pev->health = gSkillData.scientistHealth; + 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; + + 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(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMScientist :: Precache( void ) +{ + 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"); + + CMTalkMonster::Precache(); +} + +// Init talk data +void CMScientist :: TalkInit() +{ + CMTalkMonster::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) + + m_szGrp[TLK_ANSWER] = "SC_ANSWER"; + m_szGrp[TLK_QUESTION] = "SC_QUESTION"; + m_szGrp[TLK_IDLE] = "SC_IDLE"; + m_szGrp[TLK_STARE] = "SC_STARE"; + m_szGrp[TLK_USE] = "SC_OK"; + m_szGrp[TLK_UNUSE] = "SC_WAIT"; + 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 % NUM_SCIENTIST_HEADS) + { + 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 CMScientist :: 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 CMTalkMonster::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 CMScientist :: ISoundMask ( void ) +{ + return 0; +} + +//========================================================= +// PainSound +//========================================================= +void CMScientist :: 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 CMScientist :: DeathSound ( void ) +{ + PainSound(); +} + + +void CMScientist::Killed( entvars_t *pevAttacker, int iGib ) +{ + SetUse( NULL ); + CMTalkMonster::Killed( pevAttacker, iGib ); +} + + +void CMScientist :: 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; + CMTalkMonster::SetActivity( newActivity ); +} + + +Schedule_t* CMScientist :: 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 = CMTalkMonster::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 = CMTalkMonster::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 CMTalkMonster::GetScheduleOfType( Type ); +} + +Schedule_t *CMScientist :: GetSchedule ( void ) +{ + // so we don't keep calling through the EHANDLE stuff + edict_t *pEnemy = m_hEnemy; + int relationship; + + switch( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( ( pEnemy ) && !UTIL_IsPlayer(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 ); + } + + // Nothing scary, just me and the player + if ( pEnemy != NULL ) + { + relationship = R_NO; + + if (UTIL_IsPlayer(pEnemy)) + relationship = R_AL; // allies + else if (pEnemy->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEnemy)); + relationship = IRelationship( pMonster ); + } + + // 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 ) // uses m_hTargetEnt + { + if ( CanHeal() ) // Heal opportunistically + return slHeal; + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } +//jlb + m_hEnemy = NULL; +//jlb + + 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 CMTalkMonster::GetSchedule(); +} + +MONSTERSTATE CMScientist :: GetIdealState ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if ( IsFollowing() ) + { + int relationship = R_NO; + + if (UTIL_IsPlayer(m_hEnemy)) + relationship = R_AL; // allies + else if (m_hEnemy->v.euser4 != NULL) + { + edict_t *pEdict = m_hEnemy; + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEdict)); + relationship = IRelationship( pMonster ); + } + + 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: + { + edict_t *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 CMTalkMonster::GetIdealState(); +} + + +BOOL CMScientist::CanHeal( void ) +{ + if ( (m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->v.health > (m_hTargetEnt->v.max_health * 0.5)) ) + return FALSE; + + return TRUE; +} + +void CMScientist::Heal( void ) +{ + if ( !CanHeal() ) + return; + + Vector target = m_hTargetEnt->v.origin - pev->origin; + if ( target.Length() > 100 ) + return; + + if (UTIL_IsPlayer(m_hTargetEnt)) + UTIL_TakeHealth( m_hTargetEnt, gSkillData.scientistHeal, DMG_GENERIC ); + else if (m_hTargetEnt->v.euser4 != NULL) + { + edict_t *pEdict = m_hTargetEnt; + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEdict)); + pMonster->TakeHealth( gSkillData.scientistHeal, DMG_GENERIC ); + } + + // Don't heal again for 1 minute + m_healTime = gpGlobals->time + 60; +} + +int CMScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 1, 2, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + +BOOL CMScientist :: DisregardEnemy( edict_t *pEnemy ) +{ + return !UTIL_IsAlive(pEnemy) || (gpGlobals->time - m_fearTime) > 15; +} + diff --git a/src/dlls/skill.cpp b/src/dlls/skill.cpp new file mode 100644 index 0000000..8169f08 --- /dev/null +++ b/src/dlls/skill.cpp @@ -0,0 +1,254 @@ + +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#include "extdll.h" +#include "dllapi.h" +#include "meta_api.h" +#include "skill.h" + +extern cvar_t *dllapi_log; + +skilldata_t gSkillData; + +struct skill_cfg_t +{ + char *name; + float *value; +}; + +skill_cfg_t skill_cfg[] = { + {"sk_agrunt_health", &gSkillData.agruntHealth}, + {"sk_agrunt_dmg_punch", &gSkillData.agruntDmgPunch}, + {"sk_apache_health", &gSkillData.apacheHealth}, + {"sk_barney_health", &gSkillData.barneyHealth}, + {"sk_bigmomma_health_factor", &gSkillData.bigmommaHealthFactor}, + {"sk_bigmomma_dmg_slash", &gSkillData.bigmommaDmgSlash}, + {"sk_bigmomma_dmg_blast", &gSkillData.bigmommaDmgBlast}, + {"sk_bigmomma_radius_blast", &gSkillData.bigmommaRadiusBlast}, + {"sk_bullsquid_health", &gSkillData.bullsquidHealth}, + {"sk_bullsquid_dmg_bite", &gSkillData.bullsquidDmgBite}, + {"sk_bullsquid_dmg_whip", &gSkillData.bullsquidDmgWhip}, + {"sk_bullsquid_dmg_spit", &gSkillData.bullsquidDmgSpit}, + {"sk_gargantua_health", &gSkillData.gargantuaHealth}, + {"sk_gargantua_dmg_slash", &gSkillData.gargantuaDmgSlash}, + {"sk_gargantua_dmg_fire", &gSkillData.gargantuaDmgFire}, + {"sk_gargantua_dmg_stomp", &gSkillData.gargantuaDmgStomp}, + {"sk_hassassin_health", &gSkillData.hassassinHealth}, + {"sk_headcrab_health", &gSkillData.headcrabHealth}, + {"sk_headcrab_dmg_bite", &gSkillData.headcrabDmgBite}, + {"sk_hgrunt_health", &gSkillData.hgruntHealth}, + {"sk_hgrunt_kick", &gSkillData.hgruntDmgKick}, + {"sk_hgrunt_pellets", &gSkillData.hgruntShotgunPellets}, + {"sk_hgrunt_gspeed", &gSkillData.hgruntGrenadeSpeed}, + {"sk_houndeye_health", &gSkillData.houndeyeHealth}, + {"sk_houndeye_dmg_blast", &gSkillData.houndeyeDmgBlast}, + {"sk_islave_health", &gSkillData.slaveHealth}, + {"sk_islave_dmg_claw", &gSkillData.slaveDmgClaw}, + {"sk_islave_dmg_clawrake", &gSkillData.slaveDmgClawrake}, + {"sk_islave_dmg_zap", &gSkillData.slaveDmgZap}, + {"sk_ichthyosaur_health", &gSkillData.ichthyosaurHealth}, + {"sk_ichthyosaur_shake", &gSkillData.ichthyosaurDmgShake}, + {"sk_leech_health", &gSkillData.leechHealth}, + {"sk_leech_dmg_bite", &gSkillData.leechDmgBite}, + {"sk_controller_health", &gSkillData.controllerHealth}, + {"sk_controller_dmgzap", &gSkillData.controllerDmgZap}, + {"sk_controller_speedball", &gSkillData.controllerSpeedBall}, + {"sk_controller_dmgball", &gSkillData.controllerDmgBall}, + {"sk_nihilanth_health", &gSkillData.nihilanthHealth}, + {"sk_nihilanth_zap", &gSkillData.nihilanthZap}, + {"sk_scientist_health", &gSkillData.scientistHealth}, + {"sk_scientist_heal", &gSkillData.scientistHeal}, + {"sk_snark_health", &gSkillData.snarkHealth}, + {"sk_snark_dmg_bite", &gSkillData.snarkDmgBite}, + {"sk_snark_dmg_pop", &gSkillData.snarkDmgPop}, + {"sk_zombie_health", &gSkillData.zombieHealth}, + {"sk_zombie_dmg_one_slash", &gSkillData.zombieDmgOneSlash}, + {"sk_zombie_dmg_both_slash", &gSkillData.zombieDmgBothSlash}, + {"sk_12mm_bullet", &gSkillData.monDmg9MM}, + {"sk_9mmAR_bullet", &gSkillData.monDmgMP5}, + {"sk_9mm_bullet", &gSkillData.monDmg12MM}, + {"sk_9mmAR_grenade", &gSkillData.monDmgM203Grenade}, + {"sk_hornet_dmg", &gSkillData.monDmgHornet}, + {"", NULL} +}; + +bool get_input(FILE *fp, char *input); + + +void scan_monster_skill(FILE *fp) +{ + char input[1024]; + int index, len, pos; + bool found; + + while (get_input(fp, input)) + { + index = 0; + found = FALSE; + + while (skill_cfg[index].name[0]) + { + len = strlen(skill_cfg[index].name); + if (strncmp(input, skill_cfg[index].name, len) == 0) + { + found = TRUE; + pos = len; + sscanf(&input[pos], "%f", skill_cfg[index].value); + + if (dllapi_log->value) + LOG_MESSAGE(PLID, "skill setting %s set to %f", + skill_cfg[index].name, *skill_cfg[index].value); + + break; + } + index++; + } + + if (!found) + { + META_CONS("[MONSTER] ERROR: unknown monster_skill.cfg item: %s", input); + LOG_MESSAGE(PLID, "ERROR: unknown monster_skill.cfg item: %s", input); + } + } +} + + +void monster_skill_init(void) +{ + char game_dir[256]; + char filename[256]; + FILE *fp = NULL; + + // Alien Grunt (agrunt) + gSkillData.agruntHealth = 90.0f; + gSkillData.agruntDmgPunch = 20.0f; + + // Apache (apache) + gSkillData.apacheHealth = 250.0f; + + // Barney (barney) + gSkillData.barneyHealth = 35.0f; + + // Big momma + gSkillData.bigmommaHealthFactor = 1.5f; + gSkillData.bigmommaDmgSlash = 60.0f; + gSkillData.bigmommaDmgBlast = 120.0f; + gSkillData.bigmommaRadiusBlast = 250.0f; + + // Bullsquid (bullsquid) + gSkillData.bullsquidHealth = 40.0f; + gSkillData.bullsquidDmgBite = 25.0f; + gSkillData.bullsquidDmgWhip = 35.0f; + gSkillData.bullsquidDmgSpit = 10.0f; + + // Gargantua + gSkillData.gargantuaHealth = 800.0f; + gSkillData.gargantuaDmgSlash = 30.0f; + gSkillData.gargantuaDmgFire = 5.0f; + gSkillData.gargantuaDmgStomp = 100.0f; + + // Hassassin (hassassin) + gSkillData.hassassinHealth = 50.0f; + + // Headcrab (headcrab) + gSkillData.headcrabHealth = 10.0f; + gSkillData.headcrabDmgBite = 10.0f; + + // Hgrunt (hgrunt) + gSkillData.hgruntHealth = 50.0f; + gSkillData.hgruntDmgKick = 10.0f; + gSkillData.hgruntShotgunPellets = 5.0f; + gSkillData.hgruntGrenadeSpeed = 600.0f; + + // Houndeye (houndeye) + gSkillData.houndeyeHealth = 20.0f; + gSkillData.houndeyeDmgBlast = 15.0f; + + // ISlave (islave) + gSkillData.slaveHealth = 30.0f; + gSkillData.slaveDmgClaw = 10.0f; + gSkillData.slaveDmgClawrake = 25.0f; + gSkillData.slaveDmgZap = 10.0f; + + // Icthyosaur + gSkillData.ichthyosaurHealth = 200.0f; + gSkillData.ichthyosaurDmgShake = 35.0f; + + // Leech + gSkillData.leechHealth = 2.0f; + gSkillData.leechDmgBite = 2.0f; + + // Controller + gSkillData.controllerHealth = 60.0f; + gSkillData.controllerDmgZap = 25.0f; + gSkillData.controllerSpeedBall = 800.0f; + gSkillData.controllerDmgBall = 4.0f; + + // Nihilanth + gSkillData.nihilanthHealth = 800.0f; + gSkillData.nihilanthZap = 30.0f; + + // Scientist (scientist) + gSkillData.scientistHealth = 20.0f; + gSkillData.scientistHeal = 25.0f; + + // Snark (snark) + gSkillData.snarkHealth = 2.0f; + gSkillData.snarkDmgBite = 10.0f; + gSkillData.snarkDmgPop = 5.0f; + + // Zombie (zombie) + gSkillData.zombieHealth = 50.0f; + gSkillData.zombieDmgOneSlash = 20.0f; + gSkillData.zombieDmgBothSlash = 40.0f; + + // MONSTER WEAPONS + gSkillData.monDmg9MM = 5.0f; + gSkillData.monDmgMP5 = 4.0f; + gSkillData.monDmg12MM = 10.0f; + gSkillData.monDmgM203Grenade = 100.0f; + + // HORNET + gSkillData.monDmgHornet = 5.0f; + + + // find the directory name of the currently running MOD... + (*g_engfuncs.pfnGetGameDir)(game_dir); + + strcpy(filename, game_dir); + strcat(filename, "/monster_skill.cfg"); + + // check if the map specific filename exists... + if (access(filename, 0) == 0) + { + if (dllapi_log->value) + { + META_CONS("[MONSTER] Processing monster skill file=%s", filename); + LOG_MESSAGE(PLID, "Processing monster skill file=%s", filename); + } + + if ((fp = fopen(filename, "r")) == NULL) + { + META_CONS("[MONSTER] ERROR: Could not open \"%s\"!", filename); + LOG_MESSAGE(PLID, "ERROR: Could not open \"%s\"!", filename); + } + + scan_monster_skill(fp); + + fclose(fp); + } + else + { + META_CONS("[MONSTER] ERROR: Could not find \"%s\" (default skill used)", filename); + LOG_MESSAGE(PLID, "ERROR: Could not find \"%s\" (default skill used)", filename); + } +} + diff --git a/src/dlls/skill.h b/src/dlls/skill.h new file mode 100644 index 0000000..2be1b96 --- /dev/null +++ b/src/dlls/skill.h @@ -0,0 +1,103 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// skill.h - skill level concerns +//========================================================= + +#ifndef SKILL_H +#define SKILL_H + +struct skilldata_t +{ +// Monster Health & Damage + float agruntHealth; + float agruntDmgPunch; + + float apacheHealth; + + float barneyHealth; + + float bigmommaHealthFactor; // Multiply each node's health by this + float bigmommaDmgSlash; // melee attack damage + float bigmommaDmgBlast; // mortar attack damage + float bigmommaRadiusBlast; // mortar attack radius + + float bullsquidHealth; + float bullsquidDmgBite; + float bullsquidDmgWhip; + float bullsquidDmgSpit; + + float gargantuaHealth; + float gargantuaDmgSlash; + float gargantuaDmgFire; + float gargantuaDmgStomp; + + float hassassinHealth; + + float headcrabHealth; + float headcrabDmgBite; + + float hgruntHealth; + float hgruntDmgKick; + float hgruntShotgunPellets; + float hgruntGrenadeSpeed; + + float houndeyeHealth; + float houndeyeDmgBlast; + + float slaveHealth; + float slaveDmgClaw; + float slaveDmgClawrake; + float slaveDmgZap; + + float ichthyosaurHealth; + float ichthyosaurDmgShake; + + float leechHealth; + float leechDmgBite; + + float controllerHealth; + float controllerDmgZap; + float controllerSpeedBall; + float controllerDmgBall; + + float nihilanthHealth; + float nihilanthZap; + + float scientistHealth; + float scientistHeal; + + float snarkHealth; + float snarkDmgBite; + float snarkDmgPop; + + float zombieHealth; + float zombieDmgOneSlash; + float zombieDmgBothSlash; + +// weapons shared by monsters + float monDmg9MM; + float monDmgMP5; + float monDmg12MM; + float monDmgM203Grenade; + + float monDmgHornet; +}; + +extern DLL_GLOBAL skilldata_t gSkillData; + +void monster_skill_init(void); + +#endif diff --git a/src/dlls/sound.cpp b/src/dlls/sound.cpp new file mode 100644 index 0000000..70d2963 --- /dev/null +++ b/src/dlls/sound.cpp @@ -0,0 +1,915 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "weapons.h" +#include "cmtalkmonster.h" +#include "pm_materials.h" + + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); + + +// ==================== GENERIC AMBIENT SOUND ====================================== + +// runtime pitch shift and volume fadein/out structure + +// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER +// SEE BELOW (in the typedescription for the class) +typedef struct dynpitchvol +{ + // NOTE: do not change the order of these parameters + // NOTE: unless you also change order of rgdpvpreset array elements! + int preset; + + int pitchrun; // pitch shift % when sound is running 0 - 255 + int pitchstart; // pitch shift % when sound stops or starts 0 - 255 + int spinup; // spinup time 0 - 100 + int spindown; // spindown time 0 - 100 + + int volrun; // volume change % when sound is running 0 - 10 + int volstart; // volume change % when sound stops or starts 0 - 10 + int fadein; // volume fade in time 0 - 100 + int fadeout; // volume fade out time 0 - 100 + + // Low Frequency Oscillator + int lfotype; // 0) off 1) square 2) triangle 3) random + int lforate; // 0 - 1000, how fast lfo osciallates + + int lfomodpitch; // 0-100 mod of current pitch. 0 is off. + int lfomodvol; // 0-100 mod of current volume. 0 is off. + + int cspinup; // each trigger hit increments counter and spinup pitch + + + int cspincount; + + int pitch; + int spinupsav; + int spindownsav; + int pitchfrac; + + int vol; + int fadeinsav; + int fadeoutsav; + int volfrac; + + int lfofrac; + int lfomult; + + +} dynpitchvol_t; + +#define CDPVPRESETMAX 27 + +// presets for runtime pitch and vol modulation of ambient sounds + +/*jlb +dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = +{ +// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup +{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, +{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, +{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, +{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} +}; +jlb*/ + + +// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== + +#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group + +// group of related sentences + +typedef struct sentenceg +{ + char szgroupname[CBSENTENCENAME_MAX]; + int count; + unsigned char rgblru[CSENTENCE_LRU_MAX]; + +} SENTENCEG; + +#define CSENTENCEG_MAX 200 // max number of sentence groups +// globals + +SENTENCEG rgsentenceg[CSENTENCEG_MAX]; +int fSentencesInit = FALSE; + +char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +int gcallsentences = 0; + +// randomize list of sentence name indices + +void USENTENCEG_InitLRU(unsigned char *plru, int count) +{ + int i, j, k; + unsigned char temp; + + if (!fSentencesInit) + return; + + if (count > CSENTENCE_LRU_MAX) + count = CSENTENCE_LRU_MAX; + + for (i = 0; i < count; i++) + plru[i] = (unsigned char) i; + + // randomize array + for (i = 0; i < (count * 4); i++) + { + j = RANDOM_LONG(0,count-1); + k = RANDOM_LONG(0,count-1); + temp = plru[j]; + plru[j] = plru[k]; + plru[k] = temp; + } +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset) +{ + char *szgroupname; + unsigned char count; + char sznum[8]; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + sprintf(sznum, "%d", ipick); + strcat(szfound, sznum); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int USENTENCEG_Pick(int isentenceg, char *szfound) +{ + char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + char sznum[8]; + unsigned char ipick; + int ffound = FALSE; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + plru = rgsentenceg[isentenceg].rgblru; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + USENTENCEG_InitLRU(plru, count); + else + { + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + sprintf(sznum, "%d", ipick); + strcat(szfound, sznum); + return ipick; + } + } + return -1; +} + +// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== + +// Given sentence group rootname (name without number suffix), +// get sentence group index (isentenceg). Returns -1 if no such name. + +int SENTENCEG_GetIndex(const char *szgroupname) +{ + int i; + + if (!fSentencesInit || !szgroupname) + return -1; + + // search rgsentenceg for match on szgroupname + + i = 0; + while (rgsentenceg[i].count) + { + if (!strcmp(szgroupname, rgsentenceg[i].szgroupname)) + return i; + i++; + } + + return -1; +} + +// given sentence group index, play random sentence for given entity. +// returns ipick - which sentence was picked to +// play from the group. Ipick is only needed if you plan on stopping +// the sound before playback is done (see SENTENCEG_Stop). + +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick > 0 && name) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipick; +} + +// same as above, but takes sentence group name instead of index + +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + { + ALERT( at_console, "No such sentence group %s\n", szgroupname ); + return -1; + } + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + + return ipick; +} + +// play sentences in sequential order from sentence group. Reset after last sentence. + +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int ipick, int freset) +{ + char name[64]; + int ipicknext; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + return -1; + + ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset); + if (ipicknext >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipicknext; +} + + +// for this entity, for the given sentence within the sentence group, stop +// the sentence. + +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick) +{ + char buffer[64]; + char sznum[8]; + + if (!fSentencesInit) + return; + + if (isentenceg < 0 || ipick < 0) + return; + + strcpy(buffer, "!"); + strcat(buffer, rgsentenceg[isentenceg].szgroupname); + sprintf(sznum, "%d", ipick); + strcat(buffer, sznum); + + STOP_SOUND(entity, CHAN_VOICE, buffer); +} + +// open sentences.txt, scan for groups, build rgsentenceg +// Should be called from world spawn, only works on the +// first call and is ignored subsequently. + +void SENTENCEG_Init() +{ + char buffer[512]; + char szgroup[64]; + int i, j; + int isentencegs; + + if (fSentencesInit) + return; + + memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX); + gcallsentences = 0; + + memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG)); + memset(buffer, 0, 512); + memset(szgroup, 0, 64); + isentencegs = -1; + + + int filePos = 0, fileSize; + byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL ) + { + // skip whitespace + i = 0; + while(buffer[i] && buffer[i] == ' ') + i++; + + if (!buffer[i]) + continue; + + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get sentence name + j = i; + while (buffer[j] && buffer[j] != ' ') + j++; + + if (!buffer[j]) + continue; + + if (gcallsentences > CVOXFILESENTENCEMAX) + { + ALERT (at_error, "Too many sentences in sentences.txt!\n"); + break; + } + + // null-terminate name and save in sentences array + buffer[j] = 0; + const char *pString = buffer + i; + + if ( strlen( pString ) >= CBSENTENCENAME_MAX ) + ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 ); + + strcpy( gszallsentencenames[gcallsentences++], pString ); + + j--; + if (j <= i) + continue; + if (!isdigit(buffer[j])) + continue; + + // cut out suffix numbers + while (j > i && isdigit(buffer[j])) + j--; + + if (j <= i) + continue; + + buffer[j+1] = 0; + + // if new name doesn't match previous group name, + // make a new group. + + if (strcmp(szgroup, &(buffer[i]))) + { + // name doesn't match with prev name, + // copy name into group, init count to 1 + isentencegs++; + if (isentencegs >= CSENTENCEG_MAX) + { + ALERT (at_error, "Too many sentence groups in sentences.txt!\n"); + break; + } + + strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i])); + rgsentenceg[isentencegs].count = 1; + + strcpy(szgroup, &(buffer[i])); + + continue; + } + else + { + //name matches with previous, increment group count + if (isentencegs >= 0) + rgsentenceg[isentencegs].count++; + } + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fSentencesInit = TRUE; + + // init lru lists + + i = 0; + + while (rgsentenceg[i].count && i < CSENTENCEG_MAX) + { + USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count); + i++; + } + +} + +// convert sentence (sample) name to !sentencenum, return !sentencenum + +int SENTENCEG_Lookup(const char *sample, char *sentencenum) +{ + char sznum[8]; + + int i; + // this is a sentence name; lookup sentence number + // and give to engine as string. + for (i = 0; i < gcallsentences; i++) + if (!stricmp(gszallsentencenames[i], sample+1)) + { + if (sentencenum) + { + strcpy(sentencenum, "!"); + sprintf(sznum, "%d", i); + strcat(sentencenum, sznum); + } + return i; + } + // sentence name not found! + return -1; +} + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch) +{ + if (sample && *sample == '!') + { + char name[32]; + if (SENTENCEG_Lookup(sample, name) >= 0) + EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch); + else + ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); + } + else + EMIT_SOUND_DYN2(entity, channel, sample, volume, attenuation, flags, pitch); +} + +// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + EMIT_SOUND_DYN(entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker + +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndI(entity, isentenceg, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in groupname + +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndSz(entity, groupname, fvol, ATTN_NORM, 0, pitch); +} + +// ===================== MATERIAL TYPE DETECTION, MAIN ROUTINES ======================== +// +// Used to detect the texture the player is standing on, map the +// texture name to a material type. Play footstep sound based +// on material type. + +int fTextureTypeInit = FALSE; + +#define CTEXTURESMAX 512 // max number of textures loaded + +//jlbint gcTextures = 0; +//jlbchar grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names +//jlbchar grgchTextureType[CTEXTURESMAX]; // parallel array of texture types + +// open materials.txt, get size, alloc space, +// save in array. Only works first time called, +// ignored on subsequent calls. + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) +{ + // Bullet-proofing + if ( !pMemFile || !pBuffer ) + return NULL; + + if ( filePos >= fileSize ) + return NULL; + + int i = filePos; + int last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if ( last - filePos > (bufferSize-1) ) + last = filePos + (bufferSize-1); + + int stop = 0; + + // Stop at the next newline (inclusive) or end of buffer + while ( i < last && !stop ) + { + if ( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + + // If we actually advanced the pointer, copy it over + if ( i != filePos ) + { + // We read in size bytes + int size = i - filePos; + // copy it out + memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size ); + + // If the buffer isn't full, terminate (this is always true) + if ( size < bufferSize ) + pBuffer[size] = 0; + + // Update file pointer + filePos = i; + return pBuffer; + } + + // No data read, bail + return NULL; +} + + +void TEXTURETYPE_Init() +{ +/*jlb + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + + if (fTextureTypeInit) + return; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); + + gcTextures = 0; + memset(buffer, 0, 512); + + pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/materials.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while (memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL && (gcTextures < CTEXTURESMAX)) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fTextureTypeInit = TRUE; +jlb*/ +} + +// given texture name, find texture type +// if not found, return type 'concrete' + +// NOTE: this routine should ONLY be called if the +// current texture under the player changes! + +char TEXTURETYPE_Find(char *name) +{ + // CONSIDER: pre-sort texture names and perform faster binary search here +/*jlb + for (int i = 0; i < gcTextures; i++) + { + if (!strnicmp(name, &(grgszTextureName[i][0]), CBTEXTURENAMEMAX-1)) + return (grgchTextureType[i]); + } +jlb*/ + return CHAR_TEX_CONCRETE; +} + +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play + +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType) +{ +// hit the world, try to play sound based on texture material type + + char chTextureType; + float fvol; + float fvolbar; + char szbuffer[64]; + const char *pTextureName; + float rgfl1[3]; + float rgfl2[3]; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + + CMBaseEntity *pEntity = CMBaseEntity::Instance(ptr->pHit); + + chTextureType = 0; + + if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + // hit body + chTextureType = CHAR_TEX_FLESH; + else + { + // hit world + + // find texture under strike, get material type + + // copy trace vector into array for trace_texture + + vecSrc.CopyToArray(rgfl1); + vecEnd.CopyToArray(rgfl2); + + // get texture from entity or world (world is ent(0)) + if (pEntity) + pTextureName = TRACE_TEXTURE( ENT(pEntity->pev), rgfl1, rgfl2 ); + else + pTextureName = TRACE_TEXTURE( ENT(0), rgfl1, rgfl2 ); + + if ( pTextureName ) + { + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + + // ALERT ( at_console, "texture hit: %s\n", szbuffer); + + // get texture type + chTextureType = TEXTURETYPE_Find(szbuffer); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + cnt = 3; + break; + case CHAR_TEX_FLESH: + if (iBulletType == BULLET_PLAYER_CROWBAR) + return 0.0; // crowbar already makes this sound + fvol = 1.0; fvolbar = 0.2; + rgsz[0] = "weapons/bullet_hit1.wav"; + rgsz[1] = "weapons/bullet_hit2.wav"; + fattn = 1.0; + cnt = 2; + break; + } + + // did we hit a breakable? + + if (pEntity && FClassnameIs(pEntity->pev, "func_breakable")) + { + // drop volumes, the object will already play a damaged sound + fvol /= 1.5; + fvolbar /= 2.0; + } + else if (chTextureType == CHAR_TEX_COMPUTER) + { + // play random spark if computer + + if ( ptr->flFraction != 1.0 && RANDOM_LONG(0,1)) + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100); break; + case 1: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100); break; + // case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + // case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + } + + // play material hit sound + UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0,cnt-1)], fvol, fattn, 0, 96 + RANDOM_LONG(0,0xf)); + //EMIT_SOUND_DYN( ENT(m_pPlayer->pev), CHAN_WEAPON, rgsz[RANDOM_LONG(0,cnt-1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG(0,0xf)); + + return fvolbar; +} + diff --git a/src/dlls/squeakgrenade.cpp b/src/dlls/squeakgrenade.cpp new file mode 100644 index 0000000..01fbc91 --- /dev/null +++ b/src/dlls/squeakgrenade.cpp @@ -0,0 +1,337 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" + +enum w_squeak_e { + WSQUEAK_IDLE1 = 0, + WSQUEAK_FIDGET, + WSQUEAK_JUMP, + WSQUEAK_RUN, +}; + +enum squeak_e { + SQUEAK_IDLE1 = 0, + SQUEAK_FIDGETFIT, + SQUEAK_FIDGETNIP, + SQUEAK_DOWN, + SQUEAK_UP, + SQUEAK_THROW +}; + +#ifndef CLIENT_DLL + + +float CMSqueakGrenade::m_flNextBounceSoundTime = 0; + +#define SQUEEK_DETONATE_DELAY 15.0 + +int CMSqueakGrenade :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +void CMSqueakGrenade :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_squeak.mdl"); + UTIL_SetSize(pev, Vector( -4, -4, 0), Vector(4, 4, 8)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( SuperBounceTouch ); + SetThink( HuntThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flNextHunt = gpGlobals->time + 1E6; + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.snarkHealth; + pev->gravity = 0.5; + pev->friction = 0.5; + + pev->dmg = gSkillData.snarkDmgPop; + + m_flDie = gpGlobals->time + SQUEEK_DETONATE_DELAY; + + m_flFieldOfView = 0; // 180 degrees + + m_flNextBounceSoundTime = gpGlobals->time;// reset each time a snark is spawned. + + pev->sequence = WSQUEAK_RUN; + ResetSequenceInfo( ); + + m_hEnemy = NULL; +} + +void CMSqueakGrenade::Precache( void ) +{ + PRECACHE_MODEL("models/w_squeak.mdl"); + PRECACHE_SOUND("squeek/sqk_blast1.wav"); + PRECACHE_SOUND("common/bodysplat.wav"); + PRECACHE_SOUND("squeek/sqk_die1.wav"); + PRECACHE_SOUND("squeek/sqk_hunt1.wav"); + PRECACHE_SOUND("squeek/sqk_hunt2.wav"); + PRECACHE_SOUND("squeek/sqk_hunt3.wav"); + PRECACHE_SOUND("squeek/sqk_deploy1.wav"); +} + + +void CMSqueakGrenade :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->model = iStringNull;// make invisible + SetThink( SUB_Remove ); + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + + // since squeak grenades never leave a body behind, clear out their takedamage now. + // Squeaks do a bit of radius damage when they pop, and that radius damage will + // continue to call this function unless we acknowledge the Squeak's death now. (sjb) + pev->takedamage = DAMAGE_NO; + + // play squeek blast + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "squeek/sqk_blast1.wav", 1, 0.5, 0, PITCH_NORM); + + UTIL_BloodDrips( pev->origin, g_vecZero, BloodColor(), 80 ); + + RadiusDamage ( pev, pev, pev->dmg, CLASS_NONE, DMG_BLAST ); + + CMBaseMonster :: Killed( pevAttacker, GIB_ALWAYS ); +} + +void CMSqueakGrenade :: GibMonster( void ) +{ + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + + +void CMSqueakGrenade::HuntThink( void ) +{ + // ALERT( at_console, "think\n" ); + + if (!IsInWorld()) + { + SetTouch( NULL ); + UTIL_Remove( this->edict() ); + return; + } + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + // explode when ready + if (gpGlobals->time >= m_flDie) + { + g_vecAttackDir = pev->velocity.Normalize( ); + pev->health = -1; + Killed( pev, 0 ); + return; + } + + // float + if (pev->waterlevel != 0) + { + if (pev->movetype == MOVETYPE_BOUNCE) + { + pev->movetype = MOVETYPE_FLY; + } + pev->velocity = pev->velocity * 0.9; + pev->velocity.z += 8.0; + } + else if (pev->movetype = MOVETYPE_FLY) + { + pev->movetype = MOVETYPE_BOUNCE; + } + + // return if not time to hunt + if (m_flNextHunt > gpGlobals->time) + return; + + m_flNextHunt = gpGlobals->time + 2.0; + + Vector vecDir; + TraceResult tr; + + Vector vecFlat = pev->velocity; + vecFlat.z = 0; + vecFlat = vecFlat.Normalize( ); + + UTIL_MakeVectors( pev->angles ); + + if (m_hEnemy == NULL || !UTIL_IsAlive(m_hEnemy)) + { + // find target, bounce a bit towards it. + Look( 512 ); + m_hEnemy = BestVisibleEnemy( ); + } + + // squeek if it's about time blow up + if ((m_flDie - gpGlobals->time <= 0.5) && (m_flDie - gpGlobals->time >= 0.3)) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_die1.wav", 1, ATTN_NORM, 0, 100 + RANDOM_LONG(0,0x3F)); + } + + // higher pitch as squeeker gets closer to detonation time + float flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / SQUEEK_DETONATE_DELAY); + if (flpitch < 80) + flpitch = 80; + + if (m_hEnemy != NULL) + { + if (UTIL_FVisible( m_hEnemy, ENT(pev) )) + { + vecDir = (m_hEnemy->v.origin + m_hEnemy->v.view_ofs) - pev->origin; + m_vecTarget = vecDir.Normalize( ); + } + + float flVel = pev->velocity.Length(); + float flAdj = 50.0 / (flVel + 10.0); + + if (flAdj > 1.2) + flAdj = 1.2; + + // ALERT( at_console, "think : enemy\n"); + + // ALERT( at_console, "%.0f %.2f %.2f %.2f\n", flVel, m_vecTarget.x, m_vecTarget.y, m_vecTarget.z ); + + pev->velocity = pev->velocity * flAdj + m_vecTarget * 300; + } + + if (pev->flags & FL_ONGROUND) + { + pev->avelocity = Vector( 0, 0, 0 ); + } + else + { + if (pev->avelocity == Vector( 0, 0, 0)) + { + pev->avelocity.x = RANDOM_FLOAT( -100, 100 ); + pev->avelocity.z = RANDOM_FLOAT( -100, 100 ); + } + } + + if ((pev->origin - m_posPrev).Length() < 1.0) + { + pev->velocity.x = RANDOM_FLOAT( -100, 100 ); + pev->velocity.y = RANDOM_FLOAT( -100, 100 ); + } + m_posPrev = pev->origin; + + pev->angles = UTIL_VecToAngles( pev->velocity ); + pev->angles.z = 0; + pev->angles.x = 0; +} + + +void CMSqueakGrenade::SuperBounceTouch( edict_t *pOther ) +{ + float flpitch; + + TraceResult tr = UTIL_GetGlobalTrace( ); + + // don't hit the guy that launched this grenade + if ( pev->owner && (pOther == pev->owner) ) + return; + + // at least until we've bounced once + pev->owner = NULL; + + pev->angles.x = 0; + pev->angles.z = 0; + + // avoid bouncing too much + if (m_flNextHit > gpGlobals->time) + return; + + // higher pitch as squeeker gets closer to detonation time + flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / SQUEEK_DETONATE_DELAY); + + if ( pOther->v.takedamage && m_flNextAttack < gpGlobals->time ) + { + // attack! + + // make sure it's me who has touched them + if (tr.pHit == pOther) + { + // and it's not another squeakgrenade + if (tr.pHit->v.modelindex != pev->modelindex) + { + // ALERT( at_console, "hit enemy\n"); + ClearMultiDamage( ); + + if (UTIL_IsPlayer(pOther)) + UTIL_TraceAttack(pOther, pev, gSkillData.snarkDmgBite, gpGlobals->v_forward, &tr, DMG_SLASH ); + else if (pOther->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + pMonster->TraceAttack(pev, gSkillData.snarkDmgBite, gpGlobals->v_forward, &tr, DMG_SLASH ); + } + + ApplyMultiDamage( pev, pev ); + + pev->dmg += gSkillData.snarkDmgPop; // add more explosion damage + // m_flDie += 2.0; // add more life + + // make bite sound + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "squeek/sqk_deploy1.wav", 1.0, ATTN_NORM, 0, (int)flpitch); + m_flNextAttack = gpGlobals->time + 0.5; + } + } + else + { + // ALERT( at_console, "been hit\n"); + } + } + + m_flNextHit = gpGlobals->time + 0.1; + m_flNextHunt = gpGlobals->time; + + // in multiplayer, we limit how often snarks can make their bounce sounds to prevent overflows. + if ( gpGlobals->time < m_flNextBounceSoundTime ) + { + // too soon! + return; + } + + if (!(pev->flags & FL_ONGROUND)) + { + // play bounce sound + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt1.wav", 1, ATTN_NORM, 0, (int)flpitch); + else if (flRndSound <= 0.66) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt2.wav", 1, ATTN_NORM, 0, (int)flpitch); + else + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt3.wav", 1, ATTN_NORM, 0, (int)flpitch); + } + + m_flNextBounceSoundTime = gpGlobals->time + 0.5;// half second. +} + +#endif + +#endif \ No newline at end of file diff --git a/src/dlls/subs.cpp b/src/dlls/subs.cpp new file mode 100644 index 0000000..a53db23 --- /dev/null +++ b/src/dlls/subs.cpp @@ -0,0 +1,470 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +/* + +===== subs.cpp ======================================================== + + frequently used global functions + +*/ + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "nodes.h" +#include "doors.h" + +extern CGraph WorldGraph; + +extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget); + +void Remove_Entity(edict_t *pEdict); + + +// Landmark class +void CMPointEntity :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +// UTIL_SetSize(pev, g_vecZero, g_vecZero); +} + + +// This updates global tables that need to know about entities being removed +void CMBaseEntity::UpdateOnRemove( void ) +{ + int i; + + if ( FBitSet( pev->flags, FL_GRAPHED ) ) + { + // this entity was a LinkEnt in the world node graph, so we must remove it from + // the graph since we are removing it from the world. + for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ ) + { + if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev ) + { + // if this link has a link ent which is the same ent that is removing itself, remove it! + WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL; + } + } + } +//jlb if ( pev->globalname ) +//jlb gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD ); +} + +// Convenient way to delay removing oneself +void CMBaseEntity :: SUB_Remove( void ) +{ + UpdateOnRemove(); + if (pev->health > 0) + { + // this situation can screw up monsters who can't tell their entity pointers are invalid. + pev->health = 0; + ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n"); + } + +// REMOVE_ENTITY(ENT(pev)); + Remove_Entity(ENT(pev)); +} + + +// Convenient way to explicitly do nothing (passed to functions that require a method) +void CMBaseEntity :: SUB_DoNothing( void ) +{ +} + + +void CMBaseDelay :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "delay")) + { + m_flDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "killtarget")) + { + m_iszKillTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CMBaseEntity::KeyValue( pkvd ); + } +} + + +/* +============================== +SUB_UseTargets + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function (if they have one) + +============================== +*/ +void CMBaseEntity :: SUB_UseTargets( edict_t *pActivator, USE_TYPE useType, float value ) +{ + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this->edict(), useType, value ); + } +} + + +void FireTargets( const char *targetName, edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ) +{ + edict_t *pentTarget = NULL; + if ( !targetName ) + return; + + ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); + + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, targetName); + if (FNullEnt(pentTarget)) + break; + + CMBaseEntity *pTarget = CMBaseEntity::Instance( pentTarget ); + if ( pTarget && !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); + pTarget->Use( pActivator, pCaller, useType, value ); + } + } +} + +void CMBaseDelay :: SUB_UseTargets( edict_t *pActivator, USE_TYPE useType, float value ) +{ + // + // exit immediatly if we don't have a target or kill target + // + if (FStringNull(pev->target) && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CMBaseDelay *pTemp = CreateClassPtr( (CMBaseDelay *)NULL); + + if (pTemp == NULL) + return; + + pTemp->pev->classname = MAKE_STRING("DelayedUse"); + + pTemp->pev->nextthink = gpGlobals->time + m_flDelay; + + pTemp->SetThink( DelayThink ); + + // Save the useType + pTemp->pev->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->pev->target = pev->target; + + // HACKHACK + // This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class + // but changing member variable hierarchy would break save/restore without some ugly code. + // This code is not as ugly as that code + if ( pActivator && UTIL_IsPlayer(pActivator) ) // If a player activates, then save it + { + pTemp->pev->owner = pActivator; + } + else + { + pTemp->pev->owner = NULL; + } + + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget ) + { + edict_t *pentKillTarget = NULL; + + ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_iszKillTarget) ); + while ( !FNullEnt(pentKillTarget) ) + { + UTIL_Remove( CMBaseEntity::Instance(pentKillTarget)->edict() ); + + ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING(m_iszKillTarget) ); + } + } + + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this->edict(), useType, value ); + } +} + + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void SetMovedir( entvars_t *pev ) +{ + if (pev->angles == Vector(0, -1, 0)) + { + pev->movedir = Vector(0, 0, 1); + } + else if (pev->angles == Vector(0, -2, 0)) + { + pev->movedir = Vector(0, 0, -1); + } + else + { + UTIL_MakeVectors(pev->angles); + pev->movedir = gpGlobals->v_forward; + } + + pev->angles = g_vecZero; +} + + + + +void CMBaseDelay::DelayThink( void ) +{ + edict_t *pActivator = NULL; + + if ( pev->owner != NULL ) // A player activated this on delay + { + pActivator = ENT(pev->owner); + } + // The use type is cached (and stashed) in pev->button + SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 ); +// REMOVE_ENTITY(ENT(pev)); + Remove_Entity(ENT(pev)); +} + + +void CMBaseToggle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_flMoveDistance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CMBaseDelay::KeyValue( pkvd ); +} + +/* +============= +LinearMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +=============== +*/ +void CMBaseToggle :: LinearMove( Vector vecDest, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); + + m_vecFinalDest = vecDest; + + // Already there? + if (vecDest == pev->origin) + { + LinearMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDest - pev->origin; + + // divide vector length by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to LinearMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( LinearMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->velocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After moving, set origin to exact final destination, call "move done" function +============ +*/ +void CMBaseToggle :: LinearMoveDone( void ) +{ + UTIL_SetOrigin(pev, m_vecFinalDest); + pev->velocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +BOOL CMBaseToggle :: IsLockedByMaster( void ) +{ + return FALSE; +} + +/* +============= +AngularMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +Just like LinearMove, but rotational. +=============== +*/ +void CMBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined"); + + m_vecFinalAngle = vecDestAngle; + + // Already there? + if (vecDestAngle == pev->angles) + { + AngularMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDestAngle - pev->angles; + + // divide by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to AngularMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( AngularMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After rotating, set angle to exact final angle, call "move done" function +============ +*/ +void CMBaseToggle :: AngularMoveDone( void ) +{ + pev->angles = m_vecFinalAngle; + pev->avelocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + + +float CMBaseToggle :: AxisValue( int flags, const Vector &angles ) +{ + if ( FBitSet(flags, SF_DOOR_ROTATE_Z) ) + return angles.z; + if ( FBitSet(flags, SF_DOOR_ROTATE_X) ) + return angles.x; + + return angles.y; +} + + +void CMBaseToggle :: AxisDir( entvars_t *pev ) +{ + if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) ) + pev->movedir = Vector ( 0, 0, 1 ); // around z-axis + else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) ) + pev->movedir = Vector ( 1, 0, 0 ); // around x-axis + else + pev->movedir = Vector ( 0, 1, 0 ); // around y-axis +} + + +float CMBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ) +{ + if ( FBitSet (flags, SF_DOOR_ROTATE_Z) ) + return angle1.z - angle2.z; + + if ( FBitSet (flags, SF_DOOR_ROTATE_X) ) + return angle1.x - angle2.x; + + return angle1.y - angle2.y; +} + + +/* +============= +FEntIsVisible + +returns TRUE if the passed entity is visible to caller, even if not infront () +============= +*/ + BOOL +FEntIsVisible( + 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); + + if (tr.fInOpen && tr.fInWater) + return FALSE; // sight line crossed contents + + if (tr.flFraction == 1) + return TRUE; + + return FALSE; + } + + diff --git a/src/dlls/talkmonster.cpp b/src/dlls/talkmonster.cpp new file mode 100644 index 0000000..0532a46 --- /dev/null +++ b/src/dlls/talkmonster.cpp @@ -0,0 +1,1419 @@ +/*** +* +* Copyright (c) 1996-2001, 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 "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "cmtalkmonster.h" +#include "defaultai.h" +#include "animation.h" + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= +float CMTalkMonster::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 + +// array of friend names +char *CMTalkMonster::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, + 0, + "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, + 0, + "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, + 0, + "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( CMTalkMonster ) +{ + slIdleResponse, + slIdleSpeak, + slIdleHello, + slIdleSpeakWait, + slIdleStopShooting, + slMoveAway, + slMoveAwayFollow, + slMoveAwayFail, + slTlkIdleWatchClient, + &slTlkIdleWatchClient[ 1 ], + slTlkIdleEyecontact, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMTalkMonster, CMBaseMonster ); + + +void CMTalkMonster :: SetActivity ( Activity newActivity ) +{ + if (newActivity == ACT_IDLE && IsTalking() ) + newActivity = ACT_SIGNAL3; + + if ( newActivity == ACT_SIGNAL3 && (LookupActivity ( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE)) + newActivity = ACT_IDLE; + + CMBaseMonster::SetActivity( newActivity ); +} + + +void CMTalkMonster :: 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->v.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.0f, 0.0f ) + pev->angles.y; + } + else + { + pev->ideal_yaw = max( yaw - 45.0f, 0.0f ) + 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; + CMBaseMonster::StartTask( pTask ); + break; + + default: + CMBaseMonster::StartTask( pTask ); + } +} + + +void CMTalkMonster :: 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 of nearest player + pPlayer = UTIL_FindNearestPlayer(this->edict(), m_flFieldOfView); + + 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 of nearest player + edict_t *pPlayer = UTIL_FindNearestPlayer(this->edict(), m_flFieldOfView); + + 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->v.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->v.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(); + } + } + + CMBaseMonster::RunTask( pTask ); + if (TaskIsComplete()) + IdleHeadTurn( pev->origin ); + break; + + default: + if (IsTalking() && m_hTalkTarget != NULL) + { + IdleHeadTurn( m_hTalkTarget->v.origin ); + } + else + { + SetBoneController( 0, 0 ); + } + CMBaseMonster::RunTask( pTask ); + } +} + + +void CMTalkMonster :: 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( ENT(pevAttacker), 0 ); + } + + m_hTargetEnt = NULL; + // Don't finish that sentence + StopTalking(); + SetUse( NULL ); + CMBaseMonster::Killed( pevAttacker, iGib ); +} + + + +edict_t *CMTalkMonster::EnumFriends( edict_t *pPrevious, int listNumber, BOOL bTrace ) +{ + edict_t *pFriend = pPrevious; + char *pszFriend; + TraceResult tr; + Vector vecCheck; + + pszFriend = m_szFriends[ FriendNumber(listNumber) ]; + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this->edict() || !UTIL_IsAlive(pFriend)) + // don't talk to self or dead people + continue; + if ( bTrace ) + { + vecCheck = pFriend->v.origin; + vecCheck.z = pFriend->v.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 CMTalkMonster::AlertFriends( void ) +{ +/*jlb + edict_t *pFriend = NULL; + int i; + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CMBaseMonster *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; + } + } + } +jlb*/ +} + + + +void CMTalkMonster::ShutUpFriends( void ) +{ +/*jlb + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CMBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + pMonster->SentenceStop(); + } + } + } +jlb*/ +} + + +// 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 CMTalkMonster::LimitFollowers( edict_t *pPlayer, int maxFollowers ) +{ +/*jlb + 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 )) + { + CMBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + if ( pMonster->m_hTargetEnt == pPlayer ) + { + count++; + if ( count > maxFollowers ) + pMonster->StopFollowing( TRUE ); + } + } + } + } +jlb*/ +} + + +float CMTalkMonster::TargetDistance( void ) +{ + // If we lose the player, or he dies, return a really large distance + if ( m_hTargetEnt == NULL || !UTIL_IsAlive(m_hEnemy) ) + return 1e6; + + return (m_hTargetEnt->v.origin - pev->origin).Length(); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMTalkMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + CMBaseMonster::HandleAnimEvent( pEvent ); +} + +// monsters derived from CMTalkMonster should call this in precache() + +void CMTalkMonster :: TalkInit( void ) +{ + // every new talking monster must reset this global, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + + CMTalkMonster::g_talkWaitTime = 0; + + m_hTalkTarget = NULL; + + m_voicePitch = 100; +} +//========================================================= +// FindNearestFriend +// Scan for nearest, visible friend. If fPlayer is true, look for +// nearest player +//========================================================= +edict_t *CMTalkMonster :: FindNearestFriend(BOOL fPlayer) +{ + edict_t *pFriend = NULL; + edict_t *pNearest = NULL; +/*jlb + 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; + + CMBaseMonster *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(); + } + } + } + } + } +jlb*/ + return pNearest; +} + +int CMTalkMonster :: GetVoicePitch( void ) +{ + return m_voicePitch + RANDOM_LONG(0,3); +} + + +void CMTalkMonster :: TalkTouch( edict_t *pOther ) +{ + // Did the player touch me? + if ( UTIL_IsPlayer(pOther) ) + { + // 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->v.velocity.x) + fabs(pOther->v.velocity.y); + if ( speed > 50 ) + { + SetConditions( bits_COND_CLIENT_PUSH ); + MakeIdealYaw( pOther->v.origin ); + } + } +} + + + +//========================================================= +// IdleRespond +// Respond to a previous question +//========================================================= +void CMTalkMonster :: IdleRespond( void ) +{ + int pitch = GetVoicePitch(); + + // play response + PlaySentence( m_szGrp[TLK_ANSWER], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); +} + +int CMTalkMonster :: 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 <= CMTalkMonster::g_talkWaitTime) + return FALSE; + + if ( pev->spawnflags & SF_MONSTER_GAG ) + 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()))) + return FALSE; + + // don't talk if you're in combat + if (m_hEnemy != NULL && UTIL_FVisible( m_hEnemy, this->edict() )) + return FALSE; + + return TRUE; +} + + +int CMTalkMonster::CanPlaySentence( BOOL fDisregardState ) +{ + if ( fDisregardState ) + return CMBaseMonster::CanPlaySentence( fDisregardState ); + return FOkToSpeak(); +} + +//========================================================= +// FIdleStare +//========================================================= +int CMTalkMonster :: 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 CMTalkMonster :: 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 + edict_t *pPlayer = FindNearestFriend(TRUE); + + if (pPlayer) + { + if (UTIL_FInViewCone(pPlayer, this->edict(), m_flFieldOfView) && UTIL_FVisible(pPlayer, this->edict())) + { + 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 CMTalkMonster :: 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 CMTalkMonster :: 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? + edict_t *pTarget = m_hTargetEnt; + + if ( pTarget != NULL ) + { + if ( UTIL_IsPlayer(pTarget) ) + { + if ( UTIL_IsAlive(pTarget) ) + { + m_hTalkTarget = m_hTargetEnt; + if (!FBitSet(m_bitsSaid, bit_saidDamageHeavy) && + (m_hTargetEnt->v.health <= m_hTargetEnt->v.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->v.health <= m_hTargetEnt->v.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->v.health <= m_hTargetEnt->v.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 + edict_t *pFriend = FindNearestFriend(FALSE); + + if (pFriend && !(UTIL_IsMoving(pFriend)) && (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 + CMTalkMonster *pTalkMonster = (CMTalkMonster *)pFriend; + m_hTalkTarget = pFriend; + pTalkMonster->SetAnswerQuestion( this->edict() ); // 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 ); + edict_t *pFriend = FindNearestFriend(TRUE); + + if ( pFriend ) + { + m_hTalkTarget = pFriend; + PlaySentence( szIdleGroup, duration, VOL_NORM, ATTN_IDLE ); + m_nSpeak++; + return TRUE; + } + } + + // didn't speak + Talk( 0 ); + CMTalkMonster::g_talkWaitTime = 0; + return FALSE; +} + +void CMTalkMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, edict_t *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 CMTalkMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( !pszSentence ) + return; + + Talk ( duration ); + + CMTalkMonster::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 CMTalkMonster :: 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 CMTalkMonster :: SetAnswerQuestion( edict_t *pSpeaker ) +{ + m_hTalkTarget = pSpeaker; +} + +int CMTalkMonster :: 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)) + { + edict_t *pFriend = FindNearestFriend(FALSE); + + if (pFriend && UTIL_IsAlive(pFriend)) + { + // only if not dead or dying! + CMTalkMonster *pTalkMonster = (CMTalkMonster *)pFriend; + pTalkMonster->ChangeSchedule( slIdleStopShooting ); + } + } + } + return CMBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +Schedule_t* CMTalkMonster :: 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() ); + //CMTalkMonster::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() ); + //CMTalkMonster::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 = UTIL_FindNearestPlayer(this->edict(), m_flFieldOfView); + + 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 CMTalkMonster::GetScheduleOfType, + // then check result and decide what to return ie: if sci gets back + // slIdleStand, return slIdleSciStand + } + break; + } + + return CMBaseMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// IsTalking - am I saying a sentence right now? +//========================================================= +BOOL CMTalkMonster :: IsTalking( void ) +{ + if ( m_flStopTalkTime > gpGlobals->time ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// If there's a player around, watch him. +//========================================================= +void CMTalkMonster :: PrescheduleThink ( void ) +{ + if ( !HasConditions ( bits_COND_SEE_CLIENT ) ) + { + SetConditions ( bits_COND_CLIENT_UNSEEN ); + } +} + +// try to smell something +void CMTalkMonster :: 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 CMTalkMonster::IRelationship( CMBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return R_HT; + return CMBaseMonster::IRelationship( pTarget ); +} + + +void CMTalkMonster::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 CMTalkMonster::StartFollowing( edict_t *pLeader ) +{ + 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(); +} + + +BOOL CMTalkMonster::CanFollow( void ) +{ + if ( !IsAlive() ) + return FALSE; + + return !IsFollowing(); +} + + +void CMTalkMonster :: FollowerUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ) +{ + // Don't allow use during a scripted_sentence + if ( m_useTime > gpGlobals->time ) + return; + + if ( pCaller != NULL && UTIL_IsPlayer(pCaller) ) + { + // Pre-disaster followers can't be used + if ( pev->spawnflags & SF_MONSTER_PREDISASTER ) + { + DeclineFollowing(); + } + else if ( CanFollow() ) + { + LimitFollowers( pCaller , 1 ); + + if ( m_afMemory & bits_MEMORY_PROVOKED ) + ALERT( at_console, "I'm not following you, you evil person!\n" ); + else + { + StartFollowing( pCaller ); + SetBits(m_bitsSaid, bit_saidHelloPlayer); // Don't say hi after you've started following + } + } + else + { + StopFollowing( TRUE ); + } + } +} + +void CMTalkMonster::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 + CMBaseMonster::KeyValue( pkvd ); +} + + +void CMTalkMonster::Precache( void ) +{ + if ( m_iszUse ) + m_szGrp[TLK_USE] = STRING( m_iszUse ); + if ( m_iszUnUse ) + m_szGrp[TLK_UNUSE] = STRING( m_iszUnUse ); +} + diff --git a/src/dlls/util.cpp b/src/dlls/util.cpp new file mode 100644 index 0000000..08b0c64 --- /dev/null +++ b/src/dlls/util.cpp @@ -0,0 +1,1963 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include +#include // va_start, etc +#include + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "shake.h" +#include "decals.h" +#include "weapons.h" +#include "monsters.h" + +typedef struct { + DLL_FUNCTIONS *dllapi_table; + NEW_DLL_FUNCTIONS *newapi_table; +} gamedll_funcs_t; + +extern gamedll_funcs_t *gpGamedllFuncs; + + +// Print to console. +void META_CONS(char *fmt, ...) { + va_list ap; + char buf[1024]; + unsigned int len; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + len=strlen(buf); + if(len < sizeof(buf)-2) // -1 null, -1 for newline + strcat(buf, "\n"); + else + buf[len-1] = '\n'; + + (*g_engfuncs.pfnServerPrint)(buf); +} + +float UTIL_WeaponTimeBase( void ) +{ +#if defined( CLIENT_WEAPONS ) + return 0.0; +#else + return gpGlobals->time; +#endif +} + +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 ); + } +} + +void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) +{ + pev->startpos = vecOrigin; + // Trace out line to end pos + TraceResult tr; + UTIL_MakeVectors( vecAngles ); + UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); + pev->endpos = tr.vecEndPos; + + // Now compute how long it will take based on current velocity + Vector vecTravel = pev->endpos - pev->startpos; + float travelTime = 0.0; + if ( pev->velocity.Length() > 0 ) + { + travelTime = vecTravel.Length() / pev->velocity.Length(); + } + pev->starttime = gpGlobals->time; + pev->impacttime = gpGlobals->time + travelTime; +} + +int g_groupmask = 0; +int g_groupop = 0; + +// Normal overrides +void UTIL_SetGroupTrace( int groupmask, int op ) +{ + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +void UTIL_UnsetGroupTrace( void ) +{ + g_groupmask = 0; + g_groupop = 0; + + ENGINE_SETGROUPMASK( 0, 0 ); +} + +// Smart version, it'll clean itself up when it pops off stack +UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) +{ + m_oldgroupmask = g_groupmask; + m_oldgroupop = g_groupop; + + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +UTIL_GroupTrace::~UTIL_GroupTrace( void ) +{ + g_groupmask = m_oldgroupmask; + g_groupop = m_oldgroupop; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + + +#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); + ALERT(at_console, szOut); + } +#endif // DEBUG + +// 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); +} + +// 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( edict_t **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + 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; + + pList[ count ] = pEdict; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( edict_t **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + 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; + + pList[ count ] = pEdict; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +edict_t *UTIL_FindEntityInSphere( edict_t *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity; + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return pentEntity; + + return NULL; +} + + +edict_t *UTIL_FindEntityByString( edict_t *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity; + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + if (!FNullEnt(pentEntity)) + return pentEntity; + + return NULL; +} + +edict_t *UTIL_FindEntityByClassname( edict_t *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +edict_t *UTIL_FindEntityByTargetname( edict_t *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + + +edict_t *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + edict_t *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + edict_t *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->v.origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a edict_t pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +edict_t *UTIL_PlayerByIndex( int playerIndex ) +{ + edict_t *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = 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); + +/*jlb + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + } + else +jlb*/ + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = value * scale; + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + +static short FixedSigned16( float value, float scale ) +{ + int output; + + output = value * scale; + + if ( output > 32767 ) + output = 32767; + + if ( output < -32768 ) + output = -32768; + + return (short)output; +} + +// 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? + +int gmsgShake = 0; + +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + int i; + float localAmplitude; + ScreenShake shake; + + shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed + shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + edict_t *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer || !(pPlayer->v.flags & FL_ONGROUND) ) // Don't shake if not onground + continue; + + localAmplitude = 0; + + if ( radius <= 0 ) + localAmplitude = amplitude; + else + { + Vector delta = center - pPlayer->v.origin; + float distance = delta.Length(); + + // Had to get rid of this falloff - it didn't work well + if ( distance < radius ) + localAmplitude = amplitude;//radius - distance; + } + if ( localAmplitude ) + { + shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed + + if (gmsgShake == 0) + gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); + + MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer ); // use the magic #1 for "one client" + + WRITE_SHORT( shake.amplitude ); // shake amount + WRITE_SHORT( shake.duration ); // shake lasts this long + WRITE_SHORT( shake.frequency ); // shake noise frequency + + MESSAGE_END(); + } + } +} + + + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); +} + + +void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed + fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed + fade.r = (int)color.x; + fade.g = (int)color.y; + fade.b = (int)color.z; + fade.a = alpha; + fade.fadeFlags = flags; +} + + +int gmsgFade = 0; + +void UTIL_ScreenFadeWrite( const ScreenFade &fade, edict_t *pEntity ) +{ + if ( !pEntity || !(pEntity->v.flags & FL_CLIENT) ) + return; + + if (gmsgFade == 0) + gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); + + MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity ); // use the magic #1 for "one client" + + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + + MESSAGE_END(); +} + + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + int i; + ScreenFade fade; + + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + edict_t *pPlayer = UTIL_PlayerByIndex( i ); + + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( edict_t *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( edict_t *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !(pEntity->v.flags & FL_CLIENT) ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity ); + 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++ ) + { + edict_t *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + + +int gmsgTextMsg = 0; +int gmsgSayText = 0; + +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + if (gmsgTextMsg) + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + + MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); + 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 ) +{ + if (gmsgTextMsg) + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + + MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, 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, edict_t *pEntity ) +{ + if ( !(pEntity->v.flags & FL_CLIENT) ) + return; + + if (gmsgSayText == 0) + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, edict_t *pEntity ) +{ + if (gmsgSayText == 0) + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + 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; +} + +int gmsgHudText = 0; + +void UTIL_ShowMessage( const char *pString, edict_t *pEntity ) +{ + if ( !pEntity || !(pEntity->v.flags & FL_CLIENT) ) + return; + + if (gmsgHudText == 0) + gmsgHudText = REG_USER_MSG( "HudText", -1 ); + + MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + int i; + + // loop through all players + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + edict_t *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_ShowMessage( pString, pPlayer ); + } +} + +// 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 ) +{ + TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( ) +{ + TraceResult tr; + + tr.fAllSolid = gpGlobals->trace_allsolid; + tr.fStartSolid = gpGlobals->trace_startsolid; + tr.fInOpen = gpGlobals->trace_inopen; + tr.fInWater = gpGlobals->trace_inwater; + 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.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_SetOrigin( entvars_t *pev, const Vector &vecOrigin ) +{ + SET_ORIGIN(ENT(pev), vecOrigin ); +} + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + + +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; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + return delta; +} + + +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_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, SVC_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; + + // scale up blood effect in multiplayer for better visibility + amount *= 2; + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, SVC_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) ); + } +} + + +bool IsBSPModel(edict_t *pEdict) +{ + return ((pEdict->v.solid == SOLID_BSP) || (pEdict->v.movetype == MOVETYPE_PUSHSTEP)); +} + + +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 ) + { + edict_t *pEntity = pTrace->pHit; + if ( pEntity && !IsBSPModel(pEntity) ) + 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, SVC_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, SVC_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, SVC_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, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); +} + + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_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(); +} + + +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; + } +} + + +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) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (UTIL_PointContents(midUp) == CONTENTS_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, SVC_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, SVC_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(); +} + + +void UTIL_Remove( edict_t *pEntity ) +{ + if ( !pEntity ) + return; + +//jlb pEntity->UpdateOnRemove(); + pEntity->v.flags |= FL_KILLME; + pEntity->v.targetname = 0; +} + + +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_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() ) ); +} + + +//========================================================= +// 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; +} + + +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return pevBModel->absmin + ( pevBModel->size * 0.5 ); +} + + +bool UTIL_IsAlive(entvars_t *pev) +{ + return ((pev->deadflag == DEAD_NO) && (pev->health > 0) && + ((pev->flags & FL_NOTARGET) == 0) && (pev->takedamage != 0)); +} + + +bool UTIL_IsAlive(edict_t *pEdict) +{ + return ((pEdict->v.deadflag == DEAD_NO) && (pEdict->v.health > 0) && + ((pEdict->v.flags & FL_NOTARGET) == 0) && (pEdict->v.takedamage != 0)); +} + + +bool UTIL_IsPlayer(edict_t *pEdict) +{ + return ((pEdict->v.flags & FL_CLIENT) == FL_CLIENT); +} + + +Vector UTIL_BodyTarget(edict_t *pEdict, Vector posSrc) +{ + if (pEdict->v.flags & FL_CLIENT) + return pEdict->v.origin + (pEdict->v.view_ofs * RANDOM_FLOAT(0.5, 1.1)); + else + return (pEdict->v.origin + ((pEdict->v.mins + pEdict->v.maxs) * 0.5)); +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target +//========================================================= +bool UTIL_FVisible ( edict_t *pEntity, edict_t *pLooker ) +{ + TraceResult tr; + Vector vecLookerOrigin; + Vector vecTargetOrigin; + + if (FBitSet( pEntity->v.flags, FL_NOTARGET )) + return FALSE; + + // don't look through water + if ((pLooker->v.waterlevel != 3 && pEntity->v.waterlevel == 3) + || (pLooker->v.waterlevel == 3 && pEntity->v.waterlevel == 0)) + return FALSE; + + vecLookerOrigin = pLooker->v.origin + pLooker->v.view_ofs;//look through the caller's 'eyes' + vecTargetOrigin = pEntity->v.origin + pEntity->v.view_ofs; + + UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, ignore_monsters, ignore_glass, pLooker, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target vector +//========================================================= +bool UTIL_FVisible ( const Vector &vecOrigin, edict_t *pLooker ) +{ + TraceResult tr; + Vector vecLookerOrigin; + + vecLookerOrigin = pLooker->v.origin + pLooker->v.view_ofs; + + UTIL_TraceLine(vecLookerOrigin, vecOrigin, ignore_monsters, ignore_glass, pLooker, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + + +//========================================================= +// 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 UTIL_FInViewCone ( edict_t *pEntity, edict_t *pLooker, float fov ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pLooker->v.angles ); + + vec2LOS = ( pEntity->v.origin - pLooker->v.origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > fov ) + { + 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 UTIL_FInViewCone ( Vector *pOrigin, edict_t *pLooker, float fov ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pLooker->v.angles ); + + vec2LOS = ( *pOrigin - pLooker->v.origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > fov ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +#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 gmsgDamage = 0; + +int UTIL_TakeDamage( edict_t *pEdict, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // note: This function should ONLY be used for players!!! + + int bitsDamage = bitsDamageType; + int ffound = TRUE; + int fTookDamage; + float flRatio; + float flBonus; + float flHealthPrev = pEdict->v.health; + + flBonus = ARMOR_BONUS; + flRatio = ARMOR_RATIO; + + if (!pEdict->v.takedamage) + return 0; + + if ( ( bitsDamageType & DMG_BLAST ) ) + { + // blasts damage armor more. + flBonus *= 2; + } + + // Already dead + if ( !UTIL_IsAlive(pEdict) ) + return 0; + + // Armor. + if (pEdict->v.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 > pEdict->v.armorvalue) + { + flArmor = pEdict->v.armorvalue; + flArmor *= (1/flBonus); + flNew = flDamage - flArmor; + pEdict->v.armorvalue = 0; + } + else + pEdict->v.armorvalue -= flArmor; + + flDamage = flNew; + } + + float flTake; + Vector vecDir; + + if ( pEdict->v.deadflag == DEAD_NO ) + { + // no pain sound during death animation. +//jlb PainSound();// "Ouch!" + } + + // WARNING the cast to INT is critical!!! The player could wind up with 0.5 health + flTake = (int)flDamage; + + // 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 )) + { + edict_t *pInflictor = ENT(pevInflictor); + + if (pInflictor) + { + vecDir = ( UTIL_Center(pInflictor) - Vector ( 0, 0, 10 ) - UTIL_Center(pEdict) ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + fTookDamage = flTake; + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pEdict->v.movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + float force = flDamage * ((32 * 32 * 72.0) / (pEdict->v.size.x * pEdict->v.size.y * pEdict->v.size.z)) * 5; + + if ( force > 1000.0) + force = 1000.0; + + pEdict->v.velocity = pEdict->v.velocity + vecDir * -force; + } + + // do the damage + pEdict->v.health -= flTake; + + if ( pEdict->v.health <= 0 ) + { + pEdict->v.health = 1; // can't suicide if already dead! + gpGamedllFuncs->dllapi_table->pfnClientKill(pEdict); + } + + // tell director about it + MESSAGE_BEGIN( MSG_SPEC, SVC_HLTV ); + WRITE_BYTE ( DRC_EVENT ); // take damage event + WRITE_SHORT( ENTINDEX(pEdict) ); // index number of primary entity + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity + WRITE_LONG( 5 ); // eventflags (priority and flags) + MESSAGE_END(); + + // 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 + + if (pEdict->v.health > 0) + { + pEdict->v.punchangle.x = -2; + + // only send down damage type that have hud art + int visibleDamageBits = bitsDamage & DMG_SHOWNHUD; + + if (gmsgDamage == 0) + gmsgDamage = REG_USER_MSG( "Damage", -1 ); + + MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, VARS(pEdict) ); + WRITE_BYTE( 0 ); + WRITE_BYTE( fTookDamage ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( pevInflictor->origin.x ); + WRITE_COORD( pevInflictor->origin.y ); + WRITE_COORD( pevInflictor->origin.z ); + MESSAGE_END(); + } + + return fTookDamage; +} + + +int UTIL_TakeHealth (edict_t *pEdict, float flHealth, int bitsDamageType) +{ + if (!pEdict->v.takedamage) + return 0; + +// heal + if ( pEdict->v.health >= pEdict->v.max_health ) + return 0; + + pEdict->v.health += flHealth; + + if (pEdict->v.health > pEdict->v.max_health) + pEdict->v.health = pEdict->v.max_health; + + return 1; +} + + +void UTIL_TraceBleed( edict_t *pEdict, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + // note: This function should ONLY be used for players!!! + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + int i; + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + 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, pEdict, &Bloodtr); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BLOOD_COLOR_RED ); + } + } +} + +void UTIL_TraceAttack( edict_t *pEdict, entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // note: This function should ONLY be used for players!!! + + if ( pEdict->v.takedamage ) + { + AddMultiDamage( pevAttacker, pEdict, flDamage, bitsDamageType ); + + SpawnBlood(ptr->vecEndPos, BLOOD_COLOR_RED, flDamage);// a little surface blood. + + UTIL_TraceBleed( pEdict, flDamage, vecDir, ptr, bitsDamageType ); + } +} + + +int UTIL_IsMoving(edict_t *pEdict) +{ + return (pEdict->v.velocity != g_vecZero); +} + + +Vector UTIL_EyePosition(edict_t *pEdict) +{ + return (pEdict->v.origin + pEdict->v.view_ofs); +} + + +Vector UTIL_Center(edict_t *pEdict) +{ + if (pEdict->v.flags & FL_CLIENT) + return pEdict->v.origin; + else + return (pEdict->v.origin + ((pEdict->v.mins + pEdict->v.maxs) * 0.5)); +} + + +edict_t *UTIL_GetNextTarget( edict_t *pEntity ) +{ + if ( FStringNull( pEntity->v.target ) ) + return NULL; + edict_t *pTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pEntity->v.target) ); + if ( FNullEnt(pTarget) ) + return NULL; + + return pTarget; +} + +edict_t *UTIL_FindNearestPlayer(edict_t *pEdict, float m_flFieldOfView) +{ + int playerIndex = 0; + float distance, nearest_distance; + edict_t *pNearestPlayer = NULL; + + nearest_distance = 9999.9f; + + while ( playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + + if ( pPlayerEdict && !pPlayerEdict->free ) + { + if (UTIL_IsPlayer(pPlayerEdict) && UTIL_IsAlive(pPlayerEdict)) + { + if (UTIL_FInViewCone( pPlayerEdict, pEdict, m_flFieldOfView ) && + !FBitSet( pPlayerEdict->v.flags, FL_NOTARGET ) && UTIL_FVisible( pPlayerEdict, pEdict ) ) + { + distance = (pPlayerEdict->v.origin - pEdict->v.origin).Length(); + if (distance < nearest_distance) + { + nearest_distance = distance; + pNearestPlayer = pPlayerEdict; + } + } + } + + } + + playerIndex++; + } + + return pNearestPlayer; +} + + +bool UTIL_IsBSPModel( edict_t *pent ) +{ + return (pent->v.solid == SOLID_BSP || pent->v.movetype == MOVETYPE_PUSHSTEP); +} diff --git a/src/dlls/util.h b/src/dlls/util.h new file mode 100644 index 0000000..0d2bb05 --- /dev/null +++ b/src/dlls/util.h @@ -0,0 +1,539 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +// +// Misc utility code +// +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ); // implementation later in this file + +extern globalvars_t *gpGlobals; +extern cvar_t *g_psv_gravity; + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) (const char *)(gpGlobals->pStringBase + (int)offset) +#define MAKE_STRING(str) ((int)str - (int)STRING(0)) + +inline edict_t *FIND_ENTITY_BY_CLASSNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "classname", pszName); +} + +inline edict_t *FIND_ENTITY_BY_TARGETNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "target", pszName); +} + +// Keeps clutter down a bit, when writing key-value pairs +#define WRITEKEY_INT(pf, szKeyName, iKeyValue) ENGINE_FPRINTF(pf, "\"%s\" \"%d\"\n", szKeyName, iKeyValue) +#define WRITEKEY_FLOAT(pf, szKeyName, flKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f\"\n", szKeyName, flKeyValue) +#define WRITEKEY_STRING(pf, szKeyName, szKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%s\"\n", szKeyName, szKeyValue) +#define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static +#define DLL_GLOBAL + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +typedef int BOOL; + +// In case this ever changes +#define M_PI 3.14159265358979323846 + +// Keeps clutter down a bit, when declaring external entity/global method prototypes +#define DECLARE_GLOBAL_METHOD(MethodName) extern void DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void DLLEXPORT funcname(void) + +// +// Conversion among the three types of "entity", including identity-conversions. +// +#ifdef DEBUG + extern edict_t *DBG_EntOfVars(const entvars_t *pev); + inline edict_t *ENT(const entvars_t *pev) { return DBG_EntOfVars(pev); } +#else + inline edict_t *ENT(const entvars_t *pev) { return pev->pContainingEntity; } +#endif +inline edict_t *ENT(edict_t *pent) { return pent; } +inline edict_t *ENT(EOFFSET eoffset) { return (*g_engfuncs.pfnPEntityOfEntOffset)(eoffset); } +inline EOFFSET OFFSET(EOFFSET eoffset) { return eoffset; } +inline EOFFSET OFFSET(const edict_t *pent) +{ +#if _DEBUG + if ( !pent ) + ALERT( at_error, "Bad ent in OFFSET()\n" ); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity)(pent); +} +inline EOFFSET OFFSET(entvars_t *pev) +{ +#if _DEBUG + if ( !pev ) + ALERT( at_error, "Bad pev in OFFSET()\n" ); +#endif + return OFFSET(ENT(pev)); +} +inline entvars_t *VARS(entvars_t *pev) { return pev; } + +inline entvars_t *VARS(edict_t *pent) +{ + if ( !pent ) + return NULL; + + return &pent->v; +} + +inline entvars_t* VARS(EOFFSET eoffset) { return VARS(ENT(eoffset)); } +inline int ENTINDEX(edict_t *pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); } +inline edict_t* INDEXENT( int iEdictNum ) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); } +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ENT(ent)); +} + +// Testing the three types of "entity" for nullity +#define eoNullEntity 0 +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)); } + +// Testing strings for nullity +#define iStringNull 0 +inline BOOL FStringNull(int iString) { return iString == iStringNull; } + +#define cchMapNameMost 32 + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// All monsters need this data +#define DONT_BLEED -1 +#define BLOOD_COLOR_RED (BYTE)247 +#define BLOOD_COLOR_YELLOW (BYTE)195 +#define BLOOD_COLOR_GREEN BLOOD_COLOR_YELLOW + +typedef enum +{ + + MONSTERSTATE_NONE = 0, + MONSTERSTATE_IDLE, + MONSTERSTATE_COMBAT, + MONSTERSTATE_ALERT, + MONSTERSTATE_HUNT, + MONSTERSTATE_PRONE, + MONSTERSTATE_SCRIPT, + MONSTERSTATE_PLAYDEAD, + MONSTERSTATE_DEAD + +} MONSTERSTATE; + + + +// Things that toggle (buttons/triggers/doors) need this +typedef enum + { + TS_AT_TOP, + TS_AT_BOTTOM, + TS_GOING_UP, + TS_GOING_DOWN + } TOGGLE_STATE; + +// Misc useful +inline BOOL FStrEq(const char*sz1, const char*sz2) + { return (strcmp(sz1, sz2) == 0); } +inline BOOL FClassnameIs(edict_t* pent, const char* szClassname) + { return FStrEq(STRING(VARS(pent)->classname), szClassname); } +inline BOOL FClassnameIs(entvars_t* pev, const char* szClassname) + { return FStrEq(STRING(pev->classname), szClassname); } + +// Misc. Prototypes +extern void UTIL_SetSize (entvars_t* pev, const Vector &vecMin, const Vector &vecMax); +extern float UTIL_VecToYaw (const Vector &vec); +extern Vector UTIL_VecToAngles (const Vector &vec); +extern float UTIL_AngleMod (float a); +extern float UTIL_AngleDiff ( float destAngle, float srcAngle ); + +extern edict_t *UTIL_FindEntityInSphere(edict_t *pStartEntity, const Vector &vecCenter, float flRadius); +extern edict_t *UTIL_FindEntityByString(edict_t *pStartEntity, const char *szKeyword, const char *szValue ); +extern edict_t *UTIL_FindEntityByClassname(edict_t *pStartEntity, const char *szName ); +extern edict_t *UTIL_FindEntityByTargetname(edict_t *pStartEntity, const char *szName ); +extern edict_t *UTIL_FindEntityGeneric(const char *szName, Vector &vecSrc, float flRadius ); + +// returns a edict_t pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +extern edict_t *UTIL_PlayerByIndex( int playerIndex ); + +#define UTIL_EntitiesInPVS(pent) (*g_engfuncs.pfnEntitiesInPVS)(pent) +extern void UTIL_MakeVectors (const Vector &vecAngles); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +extern int UTIL_MonstersInSphere( edict_t **pList, int listMax, const Vector ¢er, float radius ); +extern int UTIL_EntitiesInBox( edict_t **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ); + +inline void UTIL_MakeVectorsPrivate( const Vector &vecAngles, float *p_vForward, float *p_vRight, float *p_vUp ) +{ + g_engfuncs.pfnAngleVectors( vecAngles, p_vForward, p_vRight, p_vUp ); +} + +extern void UTIL_MakeAimVectors ( const Vector &vecAngles ); // like MakeVectors, but assumes pitch isn't inverted +extern void UTIL_MakeInvVectors ( const Vector &vec, globalvars_t *pgv ); + +extern void UTIL_SetOrigin ( entvars_t* pev, const Vector &vecOrigin ); +extern void UTIL_EmitAmbientSound ( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ); +extern void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +extern void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius ); +extern void UTIL_ScreenShakeAll ( const Vector ¢er, float amplitude, float frequency, float duration ); +extern void UTIL_ShowMessage ( const char *pString, edict_t *pPlayer ); +extern void UTIL_ShowMessageAll ( const char *pString ); +extern void UTIL_ScreenFadeAll ( const Vector &color, float fadeTime, float holdTime, int alpha, int flags ); +extern void UTIL_ScreenFade ( edict_t *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ); + +typedef enum { ignore_monsters=1, dont_ignore_monsters=0, missile=2 } IGNORE_MONSTERS; +typedef enum { ignore_glass=1, dont_ignore_glass=0 } IGNORE_GLASS; +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); +typedef enum { point_hull=0, human_hull=1, large_hull=2, head_hull=3 }; +extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); +extern TraceResult UTIL_GetGlobalTrace (void); +extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); +extern Vector UTIL_GetAimVector (edict_t* pent, float flSpeed); +extern int UTIL_PointContents (const Vector &vec); + +extern void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +extern void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ); +extern Vector UTIL_RandomBloodVector( void ); +extern BOOL UTIL_ShouldShowBlood( int bloodColor ); +extern void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ); +extern void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_Sparks( const Vector &position ); +extern void UTIL_Ricochet( const Vector &position, float scale ); +extern void UTIL_StringToVector( float *pVector, const char *pString ); +extern void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); +extern Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ); +extern float UTIL_Approach( float target, float value, float speed ); +extern float UTIL_ApproachAngle( float target, float value, float speed ); +extern float UTIL_AngleDistance( float next, float cur ); + +extern char *UTIL_VarArgs( char *format, ... ); +extern void UTIL_Remove( edict_t *pEntity ); +extern BOOL UTIL_IsValidEntity( edict_t *pent ); + +// Use for ease-in, ease-out style interpolation (accel/decel) +extern float UTIL_SplineFraction( float value, float scale ); + +// Search for water transition along a vertical line +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); +extern void UTIL_Bubbles( Vector mins, Vector maxs, int count ); +extern void UTIL_BubbleTrail( Vector from, Vector to, int count ); + +// prints a message to each client +extern void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); +inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UTIL_ClientPrintAll( HUD_PRINTCENTER, msg_name, param1, param2, param3, param4 ); +} + +// prints messages through the HUD +extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints a message to the HUD say (chat) +extern void UTIL_SayText( const char *pText, edict_t *pEntity ); +extern void UTIL_SayTextAll( const char *pText, edict_t *pEntity ); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + +// prints as transparent 'title' to the HUD +extern void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +extern void UTIL_HudMessage( edict_t *pEntity, const hudtextparms_t &textparms, const char *pMessage ); + +// for handy use with ClientPrint params +extern char *UTIL_dtos1( int d ); +extern char *UTIL_dtos2( int d ); +extern char *UTIL_dtos3( int d ); +extern char *UTIL_dtos4( int d ); + +// Writes message to console with timestamp and FragLog header. +extern void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonmonsters. +extern float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +extern void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +// Misc functions +extern void SetMovedir(entvars_t* pev); +extern int BuildChangeList( LEVELLIST *pLevelList, int maxList ); + +// +// How did I ever live without ASSERT? +// +#ifdef DEBUG +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage); +#define ASSERT(f) DBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction(f, #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +extern DLL_GLOBAL const Vector g_vecZero; + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// +#define LANGUAGE_ENGLISH 0 +#define LANGUAGE_GERMAN 1 +#define LANGUAGE_FRENCH 2 +#define LANGUAGE_BRITISH 3 + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1<<8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1<<5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1<<6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 28 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 +#define SVC_HLTV 50 + +// prxoy director stuff +#define DRC_EVENT 3 // informs the dircetor about ann important game event + +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_DRAMATIC (1<<5) + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2// players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4// only pushables can fire this trigger + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1// may only be broken by trigger +#define SF_BREAK_TOUCH 2// can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4// can be broken by a player standing on it +#define SF_BREAK_CROWBAR 256// instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + + +// Sound Utilities + +// sentence groups +#define CBSENTENCENAME_MAX 16 +#define CVOXFILESENTENCEMAX 1536 // max number of sentences in game. NOTE: this must match + // CVOXFILESENTENCEMAX in engine\sound.h!!! + +extern char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +extern int gcallsentences; + +int USENTENCEG_Pick(int isentenceg, char *szfound); +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset); +void USENTENCEG_InitLRU(unsigned char *plru, int count); + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int ipick, int freset); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample, char *sentencenum); + +void TEXTURETYPE_Init(); +char TEXTURETYPE_Find(char *name); +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType); + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch); + + +inline void EMIT_SOUND(edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +inline void STOP_SOUND(edict_t *entity, int channel, const char *sample) +{ + EMIT_SOUND_DYN(entity, channel, sample, 0, 0, SND_STOP, PITCH_NORM); +} + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample); +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg); +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname); + +#define PRECACHE_SOUND_ARRAY( a ) \ + { for (int i = 0; i < ARRAYSIZE( a ); i++ ) PRECACHE_SOUND((char *) a [i]); } + +#define EMIT_SOUND_ARRAY_DYN( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, ATTN_NORM, 0, RANDOM_LONG(95,105) ); + +#define RANDOM_SOUND_ARRAY( array ) (array) [ RANDOM_LONG(0,ARRAYSIZE( (array) )-1) ] + +#define PLAYBACK_EVENT( flags, who, index ) PLAYBACK_EVENT_FULL( flags, who, index, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +#define PLAYBACK_EVENT_DELAY( flags, who, index, delay ) PLAYBACK_EVENT_FULL( flags, who, index, delay, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +extern int g_groupmask; +extern int g_groupop; + +class UTIL_GroupTrace +{ +public: + UTIL_GroupTrace( int groupmask, int op ); + ~UTIL_GroupTrace( void ); + +private: + int m_oldgroupmask, m_oldgroupop; +}; + +void UTIL_SetGroupTrace( int groupmask, int op ); +void UTIL_UnsetGroupTrace( void ); + +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); + +float UTIL_WeaponTimeBase( void ); + +Vector VecBModelOrigin( entvars_t* pevBModel ); +bool UTIL_IsAlive(entvars_t *pev); +bool UTIL_IsAlive(edict_t *pEdict); +bool UTIL_IsPlayer(edict_t *pEdict); +Vector UTIL_BodyTarget(edict_t *pEdict, Vector posSrc); +bool UTIL_FVisible(edict_t *pEdict, edict_t *pLooker); +bool UTIL_FVisible ( const Vector &vecOrigin, edict_t *pLooker ); +bool UTIL_FInViewCone ( edict_t *pEntity, edict_t *pLooker, float fov ); +bool UTIL_FInViewCone ( Vector *pOrigin, edict_t *pLooker, float fov ); +int UTIL_TakeDamage( edict_t *pEdict, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); +int UTIL_TakeHealth (edict_t *pEdict, float flHealth, int bitsDamageType); +void UTIL_TraceBleed( edict_t *pEdict, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); +void UTIL_TraceAttack( edict_t *pEdict, entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); +int UTIL_IsMoving(edict_t *pEdict); +Vector UTIL_EyePosition(edict_t *pEdict); +Vector UTIL_Center(edict_t *pEdict); +edict_t *UTIL_GetNextTarget( edict_t *pEntity ); +edict_t *UTIL_FindNearestPlayer(edict_t *pEdict, float m_flFieldOfView); +bool UTIL_IsBSPModel( edict_t *pent ); diff --git a/src/dlls/vector.h b/src/dlls/vector.h new file mode 100644 index 0000000..cd5d735 --- /dev/null +++ b/src/dlls/vector.h @@ -0,0 +1,112 @@ +/*** +* +* Copyright (c) 1999, 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 VECTOR_H +#define VECTOR_H + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( 0, 0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + //inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + //inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +inline Vector operator*(float fl, const Vector& v) { return v * fl; } +inline float DotProduct(const Vector& a, const Vector& b) { return(a.x*b.x+a.y*b.y+a.z*b.z); } +inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } + + + +#endif \ No newline at end of file diff --git a/src/dlls/weapons.cpp b/src/dlls/weapons.cpp new file mode 100644 index 0000000..2d0bcb3 --- /dev/null +++ b/src/dlls/weapons.cpp @@ -0,0 +1,218 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +/* + +===== weapons.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "decals.h" + +extern CGraph WorldGraph; + +MULTIDAMAGE gMultiDamage; + +#define TRACER_FREQ 4 // Tracers fire every fourth bullet + + +/* +============================================================================== + +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; + + if (UTIL_IsPlayer(gMultiDamage.pEntity)) + UTIL_TakeDamage(gMultiDamage.pEntity, pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); + else if (gMultiDamage.pEntity->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(gMultiDamage.pEntity)); + pMonster->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); + } +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, edict_t *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; +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +int DamageDecal( CMBaseEntity *pEntity, int bitsDamageType ) +{ + if ( !pEntity ) + return (DECAL_GUNSHOT1 + RANDOM_LONG(0,4)); + + return pEntity->DamageDecal( bitsDamageType ); +} + +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 ) + { + CMBaseEntity *pEntity = NULL; + // Decal the wall with a gunshot + if ( !FNullEnt(pTrace->pHit) ) + pEntity = CMBaseEntity::Instance(pTrace->pHit); + + switch( iBulletType ) + { + case BULLET_PLAYER_9MM: + case BULLET_MONSTER_9MM: + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_PLAYER_BUCKSHOT: + case BULLET_PLAYER_357: + default: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_MONSTER_12MM: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_PLAYER_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, SVC_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(); +} + + +#if 0 +// UNDONE: This is no longer used? +void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE ( TE_EXPLODEMODEL ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + WRITE_COORD( speed ); + WRITE_SHORT( model ); + WRITE_SHORT( count ); + WRITE_BYTE ( 15 );// 1.5 seconds + MESSAGE_END(); +} +#endif + + +BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted ) +{ +#if defined( CLIENT_WEAPONS ) + if ( !isPredicted ) +#else + if ( 1 ) +#endif + { + return ( attack_time <= curtime ) ? TRUE : FALSE; + } + else + { + return ( attack_time <= 0.0 ) ? TRUE : FALSE; + } +} diff --git a/src/dlls/weapons.h b/src/dlls/weapons.h new file mode 100644 index 0000000..83701c7 --- /dev/null +++ b/src/dlls/weapons.h @@ -0,0 +1,258 @@ +/*** +* +* Copyright (c) 1996-2001, 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 WEAPONS_H +#define WEAPONS_H + +#include "effects.h" + +// Contact Grenade / Timed grenade / Satchel Charge +class CMGrenade : public CMBaseMonster +{ +public: + void Spawn( void ); + + typedef enum { SATCHEL_DETONATE = 0, SATCHEL_RELEASE } SATCHELCODE; + + static CMGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ); + static CMGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static CMGrenade *ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( edict_t *pOther ); + void EXPORT SlideTouch( edict_t *pOther ); + void EXPORT ExplodeTouch( edict_t *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + + +// constant items +#define ITEM_HEALTHKIT 1 +#define ITEM_ANTIDOTE 2 +#define ITEM_SECURITY 3 +#define ITEM_BATTERY 4 + +#define WEAPON_NONE 0 +#define WEAPON_CROWBAR 1 +#define WEAPON_GLOCK 2 +#define WEAPON_PYTHON 3 +#define WEAPON_MP5 4 +#define WEAPON_CHAINGUN 5 +#define WEAPON_CROSSBOW 6 +#define WEAPON_SHOTGUN 7 +#define WEAPON_RPG 8 +#define WEAPON_GAUSS 9 +#define WEAPON_EGON 10 +#define WEAPON_HORNETGUN 11 +#define WEAPON_HANDGRENADE 12 +#define WEAPON_TRIPMINE 13 +#define WEAPON_SATCHEL 14 +#define WEAPON_SNARK 15 + +#define WEAPON_ALLWEAPONS (~(1<yaw_speed = ys; +} + +int CMZombie :: 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 CMBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CMZombie :: 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 CMZombie :: 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 CMZombie :: 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 CMZombie :: 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 CMZombie :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ZOMBIE_AE_ATTACK_RIGHT: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + edict_t *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->v.flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->v.punchangle.z = -18; + pHurt->v.punchangle.x = 5; + pHurt->v.velocity = pHurt->v.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. + // ALERT( at_console, "Slash left!\n" ); + edict_t *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->v.flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->v.punchangle.z = 18; + pHurt->v.punchangle.x = 5; + pHurt->v.velocity = pHurt->v.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. + edict_t *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgBothSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->v.flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->v.punchangle.x = 5; + pHurt->v.velocity = pHurt->v.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; + + default: + CMBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMZombie :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/zombie.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; + pev->health = gSkillData.zombieHealth; + 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 CMZombie :: Precache() +{ + int i; + + PRECACHE_MODEL("models/zombie.mdl"); + + 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 CMZombie::IgnoreConditions ( void ) +{ + int iIgnore = CMBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK1)) + { +#if 0 + if (pev->health < 20) + iIgnore |= (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE); + else +#endif + 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; + +} \ No newline at end of file diff --git a/src/engine/custom.h b/src/engine/custom.h new file mode 100644 index 0000000..c7324a4 --- /dev/null +++ b/src/engine/custom.h @@ -0,0 +1,102 @@ +/*** +* +* Copyright (c) 1999, 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. +* +****/ +// Customization.h + +#ifndef CUSTOM_H +#define CUSTOM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "const.h" + +#define MAX_QPATH 64 // Must match value in quakedefs.h + +///////////////// +// Customization +// passed to pfnPlayerCustomization +// For automatic downloading. +typedef enum +{ + t_sound = 0, + t_skin, + t_model, + t_decal, + t_generic, + t_eventscript +} resourcetype_t; + +// Fake type for world +#define t_world 6 + +typedef struct +{ + int size; +} _resourceinfo_t; + +typedef struct resourceinfo_s +{ + _resourceinfo_t info[ 7 ]; +} resourceinfo_t; + +#define RES_FATALIFMISSING (1<<0) // Disconnect if we can't get this file. +#define RES_WASMISSING (1<<1) // Do we have the file locally, did we get it ok? +#define RES_CUSTOM (1<<2) // Is this resource one that corresponds to another player's customization + // or is it a server startup resource. +#define RES_REQUESTED (1<<3) // Already requested a download of this one +#define RES_PRECACHED (1<<4) // Already precached + +#include "crc.h" + +typedef struct resource_s +{ + char szFileName[MAX_QPATH]; // File name to download/precache. + resourcetype_t type; // t_sound, t_skin, t_model, t_decal. + int nIndex; // For t_decals + int nDownloadSize; // Size in Bytes if this must be downloaded. + unsigned char ucFlags; + +// For handling client to client resource propagation + unsigned char rgucMD5_hash[16]; // To determine if we already have it. + unsigned char playernum; // Which player index this resource is associated with, if it's a custom resource. + + unsigned char rguc_reserved[ 32 ]; // For future expansion + struct resource_s *pNext; // Next in chain. + struct resource_s *pPrev; +} resource_t; + +typedef struct customization_s +{ + qboolean bInUse; // Is this customization in use; + resource_t resource; // The resource_t for this customization + qboolean bTranslated; // Has the raw data been translated into a useable format? + // (e.g., raw decal .wad make into texture_t *) + int nUserData1; // Customization specific data + int nUserData2; // Customization specific data + void *pInfo; // Buffer that holds the data structure that references the data (e.g., the cachewad_t) + void *pBuffer; // Buffer that holds the data for the customization (the raw .wad data) + struct customization_s *pNext; // Next in chain +} customization_t; + +#define FCUST_FROMHPAK ( 1<<0 ) +#define FCUST_WIPEDATA ( 1<<1 ) +#define FCUST_IGNOREINIT ( 1<<2 ) + +void COM_ClearCustomizationList( struct customization_s *pHead, qboolean bCleanDecals); +qboolean COM_CreateCustomization( struct customization_s *pListHead, struct resource_s *pResource, int playernumber, int flags, + struct customization_s **pCustomization, int *nLumps ); +int COM_SizeofResourceList ( struct resource_s *pList, struct resourceinfo_s *ri ); + +#endif // CUSTOM_H diff --git a/src/engine/customentity.h b/src/engine/customentity.h new file mode 100644 index 0000000..3315e31 --- /dev/null +++ b/src/engine/customentity.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CUSTOMENTITY_H +#define CUSTOMENTITY_H + +// Custom Entities + +// Start/End Entity is encoded as 12 bits of entity index, and 4 bits of attachment (4:12) +#define BEAMENT_ENTITY(x) ((x)&0xFFF) +#define BEAMENT_ATTACHMENT(x) (((x)>>12)&0xF) + +// Beam types, encoded as a byte +enum +{ + BEAM_POINTS = 0, + BEAM_ENTPOINT, + BEAM_ENTS, + BEAM_HOSE, +}; + +#define BEAM_FSINE 0x10 +#define BEAM_FSOLID 0x20 +#define BEAM_FSHADEIN 0x40 +#define BEAM_FSHADEOUT 0x80 + +#endif //CUSTOMENTITY_H diff --git a/src/engine/edict.h b/src/engine/edict.h new file mode 100644 index 0000000..8676810 --- /dev/null +++ b/src/engine/edict.h @@ -0,0 +1,29 @@ +#if !defined EDICT_H +#define EDICT_H +#ifdef _WIN32 +#pragma once +#endif +#define MAX_ENT_LEAFS 48 + +#include "progdefs.h" + +struct edict_s +{ + qboolean free; + int serialnumber; + link_t area; // linked to a division node or leaf + + int headnode; // -1 to use normal leaf check + int num_leafs; + short leafnums[MAX_ENT_LEAFS]; + + float freetime; // sv.time when the object was freed + + void* pvPrivateData; // Alloced and freed by engine, used by DLLs + + entvars_t v; // C exported fields from progs + + // other fields from progs come immediately after +}; + +#endif diff --git a/src/engine/eiface.h b/src/engine/eiface.h new file mode 100644 index 0000000..5f7999e --- /dev/null +++ b/src/engine/eiface.h @@ -0,0 +1,493 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EIFACE_H +#define EIFACE_H + +#ifdef HLDEMO_BUILD +#define INTERFACE_VERSION 001 +#else // !HLDEMO_BUILD, i.e., regular version of HL +#define INTERFACE_VERSION 140 +#endif // !HLDEMO_BUILD + +#include +#include "custom.h" +#include "cvardef.h" +// +// Defines entity interface between engine and DLLs. +// This header file included by engine files and DLL files. +// +// Before including this header, DLLs must: +// include progdefs.h +// This is conveniently done for them in extdll.h +// + +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT /* */ +#endif + +typedef enum + { + at_notice, + at_console, // same as at_notice, but forces a ConPrintf, not a message box + at_aiconsole, // same as at_console, but only shown if developer level is 2! + at_warning, + at_error, + at_logged // Server print to console ( only in multiplayer games ). + } ALERT_TYPE; + +// 4-22-98 JOHN: added for use in pfnClientPrintf +typedef enum + { + print_console, + print_center, + print_chat, + } PRINT_TYPE; + +// For integrity checking of content on clients +typedef enum +{ + force_exactfile, // File on client must exactly match server's file + force_model_samebounds, // For model files only, the geometry must fit in the same bbox + force_model_specifybounds, // For model files only, the geometry must fit in the specified bbox +} FORCE_TYPE; + +// Returned by TraceLine +typedef struct + { + int fAllSolid; // if true, plane is not valid + int fStartSolid; // if true, the initial point was in a solid area + int fInOpen; + int fInWater; + float flFraction; // time completed, 1.0 = didn't hit anything + vec3_t vecEndPos; // final position + float flPlaneDist; + vec3_t vecPlaneNormal; // surface normal at impact + edict_t *pHit; // entity the surface is on + int iHitgroup; // 0 == generic, non zero is specific body part + } TraceResult; + +// CD audio status +typedef struct +{ + int fPlaying;// is sound playing right now? + int fWasPlaying;// if not, CD is paused if WasPlaying is true. + int fInitialized; + int fEnabled; + int fPlayLooping; + float cdvolume; + //BYTE remap[100]; + int fCDRom; + int fPlayTrack; +} CDStatus; + +#include "../common/crc.h" + +// Engine hands this to DLLs for functionality callbacks +typedef struct enginefuncs_s +{ + int (*pfnPrecacheModel) (char* s); + int (*pfnPrecacheSound) (char* s); + void (*pfnSetModel) (edict_t *e, const char *m); + int (*pfnModelIndex) (const char *m); + int (*pfnModelFrames) (int modelIndex); + void (*pfnSetSize) (edict_t *e, const float *rgflMin, const float *rgflMax); + void (*pfnChangeLevel) (char* s1, char* s2); + void (*pfnGetSpawnParms) (edict_t *ent); + void (*pfnSaveSpawnParms) (edict_t *ent); + float (*pfnVecToYaw) (const float *rgflVector); + void (*pfnVecToAngles) (const float *rgflVectorIn, float *rgflVectorOut); + void (*pfnMoveToOrigin) (edict_t *ent, const float *pflGoal, float dist, int iMoveType); + void (*pfnChangeYaw) (edict_t* ent); + void (*pfnChangePitch) (edict_t* ent); + edict_t* (*pfnFindEntityByString) (edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue); + int (*pfnGetEntityIllum) (edict_t* pEnt); + edict_t* (*pfnFindEntityInSphere) (edict_t *pEdictStartSearchAfter, const float *org, float rad); + edict_t* (*pfnFindClientInPVS) (edict_t *pEdict); + edict_t* (*pfnEntitiesInPVS) (edict_t *pplayer); + void (*pfnMakeVectors) (const float *rgflVector); + void (*pfnAngleVectors) (const float *rgflVector, float *forward, float *right, float *up); + edict_t* (*pfnCreateEntity) (void); + void (*pfnRemoveEntity) (edict_t* e); + edict_t* (*pfnCreateNamedEntity) (int className); + void (*pfnMakeStatic) (edict_t *ent); + int (*pfnEntIsOnFloor) (edict_t *e); + int (*pfnDropToFloor) (edict_t* e); + int (*pfnWalkMove) (edict_t *ent, float yaw, float dist, int iMode); + void (*pfnSetOrigin) (edict_t *e, const float *rgflOrigin); + void (*pfnEmitSound) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch); + void (*pfnEmitAmbientSound) (edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch); + void (*pfnTraceLine) (const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceToss) (edict_t* pent, edict_t* pentToIgnore, TraceResult *ptr); + int (*pfnTraceMonsterHull) (edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceHull) (const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceModel) (const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr); + const char *(*pfnTraceTexture) (edict_t *pTextureEntity, const float *v1, const float *v2 ); + void (*pfnTraceSphere) (const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnGetAimVector) (edict_t* ent, float speed, float *rgflReturn); + void (*pfnServerCommand) (char* str); + void (*pfnServerExecute) (void); + void (*pfnClientCommand) (edict_t* pEdict, char* szFmt, ...); + void (*pfnParticleEffect) (const float *org, const float *dir, float color, float count); + void (*pfnLightStyle) (int style, char* val); + int (*pfnDecalIndex) (const char *name); + int (*pfnPointContents) (const float *rgflVector); + void (*pfnMessageBegin) (int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); + void (*pfnMessageEnd) (void); + void (*pfnWriteByte) (int iValue); + void (*pfnWriteChar) (int iValue); + void (*pfnWriteShort) (int iValue); + void (*pfnWriteLong) (int iValue); + void (*pfnWriteAngle) (float flValue); + void (*pfnWriteCoord) (float flValue); + void (*pfnWriteString) (const char *sz); + void (*pfnWriteEntity) (int iValue); + void (*pfnCVarRegister) (cvar_t *pCvar); + float (*pfnCVarGetFloat) (const char *szVarName); + const char* (*pfnCVarGetString) (const char *szVarName); + void (*pfnCVarSetFloat) (const char *szVarName, float flValue); + void (*pfnCVarSetString) (const char *szVarName, const char *szValue); + void (*pfnAlertMessage) (ALERT_TYPE atype, char *szFmt, ...); + void (*pfnEngineFprintf) (FILE *pfile, char *szFmt, ...); + void* (*pfnPvAllocEntPrivateData) (edict_t *pEdict, long cb); + void* (*pfnPvEntPrivateData) (edict_t *pEdict); + void (*pfnFreeEntPrivateData) (edict_t *pEdict); + const char* (*pfnSzFromIndex) (int iString); + int (*pfnAllocString) (const char *szValue); + struct entvars_s* (*pfnGetVarsOfEnt) (edict_t *pEdict); + edict_t* (*pfnPEntityOfEntOffset) (int iEntOffset); + int (*pfnEntOffsetOfPEntity) (const edict_t *pEdict); + int (*pfnIndexOfEdict) (const edict_t *pEdict); + edict_t* (*pfnPEntityOfEntIndex) (int iEntIndex); + edict_t* (*pfnFindEntityByVars) (struct entvars_s* pvars); + void* (*pfnGetModelPtr) (edict_t* pEdict); + int (*pfnRegUserMsg) (const char *pszName, int iSize); + void (*pfnAnimationAutomove) (const edict_t* pEdict, float flTime); + void (*pfnGetBonePosition) (const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ); + unsigned long (*pfnFunctionFromName) ( const char *pName ); + const char *(*pfnNameForFunction) ( unsigned long function ); + void (*pfnClientPrintf) ( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ); // JOHN: engine callbacks so game DLL can print messages to individual clients + void (*pfnServerPrint) ( const char *szMsg ); + const char *(*pfnCmd_Args) ( void ); // these 3 added + const char *(*pfnCmd_Argv) ( int argc ); // so game DLL can easily + int (*pfnCmd_Argc) ( void ); // access client 'cmd' strings + void (*pfnGetAttachment) (const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ); + void (*pfnCRC32_Init) (CRC32_t *pulCRC); + void (*pfnCRC32_ProcessBuffer) (CRC32_t *pulCRC, void *p, int len); + void (*pfnCRC32_ProcessByte) (CRC32_t *pulCRC, unsigned char ch); + CRC32_t (*pfnCRC32_Final) (CRC32_t pulCRC); + long (*pfnRandomLong) (long lLow, long lHigh); + float (*pfnRandomFloat) (float flLow, float flHigh); + void (*pfnSetView) (const edict_t *pClient, const edict_t *pViewent ); + float (*pfnTime) ( void ); + void (*pfnCrosshairAngle) (const edict_t *pClient, float pitch, float yaw); + byte * (*pfnLoadFileForMe) (char *filename, int *pLength); + void (*pfnFreeFile) (void *buffer); + void (*pfnEndSection) (const char *pszSectionName); // trigger_endsection + int (*pfnCompareFileTime) (char *filename1, char *filename2, int *iCompare); + void (*pfnGetGameDir) (char *szGetGameDir); + void (*pfnCvar_RegisterVariable) (cvar_t *variable); + void (*pfnFadeClientVolume) (const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds); + void (*pfnSetClientMaxspeed) (const edict_t *pEdict, float fNewMaxspeed); + edict_t * (*pfnCreateFakeClient) (const char *netname); // returns NULL if fake client can't be created + void (*pfnRunPlayerMove) (edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec ); + int (*pfnNumberOfEntities) (void); + char* (*pfnGetInfoKeyBuffer) (edict_t *e); // passing in NULL gets the serverinfo + char* (*pfnInfoKeyValue) (char *infobuffer, char *key); + void (*pfnSetKeyValue) (char *infobuffer, char *key, char *value); + void (*pfnSetClientKeyValue) (int clientIndex, char *infobuffer, char *key, char *value); + int (*pfnIsMapValid) (char *filename); + void (*pfnStaticDecal) ( const float *origin, int decalIndex, int entityIndex, int modelIndex ); + int (*pfnPrecacheGeneric) (char* s); + int (*pfnGetPlayerUserId) (edict_t *e ); // returns the server assigned userid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + void (*pfnBuildSoundMsg) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); + int (*pfnIsDedicatedServer) (void);// is this a dedicated server? + cvar_t *(*pfnCVarGetPointer) (const char *szVarName); + unsigned int (*pfnGetPlayerWONId) (edict_t *e); // returns the server assigned WONid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + + // YWB 8/1/99 TFF Physics additions + void (*pfnInfo_RemoveKey) ( char *s, const char *key ); + const char *(*pfnGetPhysicsKeyValue) ( const edict_t *pClient, const char *key ); + void (*pfnSetPhysicsKeyValue) ( const edict_t *pClient, const char *key, const char *value ); + const char *(*pfnGetPhysicsInfoString) ( const edict_t *pClient ); + unsigned short (*pfnPrecacheEvent) ( int type, const char*psz ); + void (*pfnPlaybackEvent) ( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + unsigned char *(*pfnSetFatPVS) ( float *org ); + unsigned char *(*pfnSetFatPAS) ( float *org ); + + int (*pfnCheckVisibility ) ( const edict_t *entity, unsigned char *pset ); + + void (*pfnDeltaSetField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaUnsetField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaAddEncoder) ( char *name, void (*conditionalencode)( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) ); + int (*pfnGetCurrentPlayer) ( void ); + int (*pfnCanSkipPlayer) ( const edict_t *player ); + int (*pfnDeltaFindField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaSetFieldByIndex) ( struct delta_s *pFields, int fieldNumber ); + void (*pfnDeltaUnsetFieldByIndex)( struct delta_s *pFields, int fieldNumber ); + + void (*pfnSetGroupMask) ( int mask, int op ); + + int (*pfnCreateInstancedBaseline) ( int classname, struct entity_state_s *baseline ); + void (*pfnCvar_DirectSet) ( struct cvar_s *var, char *value ); + + // Forces the client and server to be running with the same version of the specified file + // ( e.g., a player model ). + // Calling this has no effect in single player + void (*pfnForceUnmodified) ( FORCE_TYPE type, float *mins, float *maxs, const char *filename ); + + void (*pfnGetPlayerStats) ( const edict_t *pClient, int *ping, int *packet_loss ); + + void (*pfnAddServerCommand) ( char *cmd_name, void (*function) (void) ); + + // For voice communications, set which clients hear eachother. + // NOTE: these functions take player entity indices (starting at 1). + qboolean (*pfnVoice_GetClientListening)(int iReceiver, int iSender); + qboolean (*pfnVoice_SetClientListening)(int iReceiver, int iSender, qboolean bListen); + + const char *(*pfnGetPlayerAuthId) ( edict_t *e ); + +} enginefuncs_t; +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 + +// Passed to pfnKeyValue +typedef struct KeyValueData_s +{ + char *szClassName; // in: entity classname + char *szKeyName; // in: name of key + char *szValue; // in: value of key + long fHandled; // out: DLL sets to true if key-value pair was understood +} KeyValueData; + + +typedef struct +{ + char mapName[ 32 ]; + char landmarkName[ 32 ]; + edict_t *pentLandmark; + vec3_t vecLandmarkOrigin; +} LEVELLIST; +#define MAX_LEVEL_CONNECTIONS 16 // These are encoded in the lower 16bits of ENTITYTABLE->flags + +typedef struct +{ + int id; // Ordinal ID of this entity (used for entity <--> pointer conversions) + edict_t *pent; // Pointer to the in-game entity + + int location; // Offset from the base data of this entity + int size; // Byte size of this entity's data + int flags; // This could be a short -- bit mask of transitions that this entity is in the PVS of + string_t classname; // entity class name + +} ENTITYTABLE; + +#define FENTTABLE_PLAYER 0x80000000 +#define FENTTABLE_REMOVED 0x40000000 +#define FENTTABLE_MOVEABLE 0x20000000 +#define FENTTABLE_GLOBAL 0x10000000 + +typedef struct saverestore_s SAVERESTOREDATA; + +#ifdef _WIN32 +typedef +#endif +struct saverestore_s +{ + char *pBaseData; // Start of all entity save data + char *pCurrentData; // Current buffer pointer for sequential access + int size; // Current data size + int bufferSize; // Total space for data + int tokenSize; // Size of the linear list of tokens + int tokenCount; // Number of elements in the pTokens table + char **pTokens; // Hash table of entity strings (sparse) + int currentIndex; // Holds a global entity table ID + int tableCount; // Number of elements in the entity table + int connectionCount;// Number of elements in the levelList[] + ENTITYTABLE *pTable; // Array of ENTITYTABLE elements (1 for each entity) + LEVELLIST levelList[ MAX_LEVEL_CONNECTIONS ]; // List of connections from this level + + // smooth transition + int fUseLandmark; + char szLandmarkName[20];// landmark we'll spawn near in next level + vec3_t vecLandmarkOffset;// for landmark transitions + float time; + char szCurrentMapName[32]; // To check global entities + +} +#ifdef _WIN32 +SAVERESTOREDATA +#endif +; + +typedef enum _fieldtypes +{ + FIELD_FLOAT = 0, // Any floating point value + FIELD_STRING, // A string ID (return from ALLOC_STRING) + FIELD_ENTITY, // An entity offset (EOFFSET) + FIELD_CLASSPTR, // CBaseEntity * + FIELD_EHANDLE, // Entity handle + FIELD_EVARS, // EVARS * + FIELD_EDICT, // edict_t *, or edict_t * (same thing) + FIELD_VECTOR, // Any vector + FIELD_POSITION_VECTOR, // A world coordinate (these are fixed up across level transitions automagically) + FIELD_POINTER, // Arbitrary data pointer... to be removed, use an array of FIELD_CHARACTER + FIELD_INTEGER, // Any integer or enum + FIELD_FUNCTION, // A class function pointer (Think, Use, etc) + FIELD_BOOLEAN, // boolean, implemented as an int, I may use this as a hint for compression + FIELD_SHORT, // 2 byte integer + FIELD_CHARACTER, // a byte + FIELD_TIME, // a floating point time (these are fixed up automatically too!) + FIELD_MODELNAME, // Engine string that is a model name (needs precache) + FIELD_SOUNDNAME, // Engine string that is a sound name (needs precache) + + FIELD_TYPECOUNT, // MUST BE LAST +} FIELDTYPE; + +#ifndef offsetof +#define offsetof(s,m) (size_t)&(((s *)0)->m) +#endif + +#define _FIELD(type,name,fieldtype,count,flags) { fieldtype, #name, offsetof(type, name), count, flags } +#define DEFINE_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, 0) +#define DEFINE_ARRAY(type,name,fieldtype,count) _FIELD(type, name, fieldtype, count, 0) +#define DEFINE_ENTITY_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, 0 ) +#define DEFINE_ENTITY_GLOBAL_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, FTYPEDESC_GLOBAL ) +#define DEFINE_GLOBAL_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, FTYPEDESC_GLOBAL ) + + +#define FTYPEDESC_GLOBAL 0x0001 // This field is masked for global entity save/restore + +typedef struct +{ + FIELDTYPE fieldType; + char *fieldName; + int fieldOffset; + short fieldSize; + short flags; +} TYPEDESCRIPTION; + +#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +typedef struct +{ + // Initialize/shutdown the game (one-time call after loading of game .dll ) + void (*pfnGameInit) ( void ); + int (*pfnSpawn) ( edict_t *pent ); + void (*pfnThink) ( edict_t *pent ); + void (*pfnUse) ( edict_t *pentUsed, edict_t *pentOther ); + void (*pfnTouch) ( edict_t *pentTouched, edict_t *pentOther ); + void (*pfnBlocked) ( edict_t *pentBlocked, edict_t *pentOther ); + void (*pfnKeyValue) ( edict_t *pentKeyvalue, KeyValueData *pkvd ); + void (*pfnSave) ( edict_t *pent, SAVERESTOREDATA *pSaveData ); + int (*pfnRestore) ( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); + void (*pfnSetAbsBox) ( edict_t *pent ); + + void (*pfnSaveWriteFields) ( SAVERESTOREDATA *, const char *, void *, TYPEDESCRIPTION *, int ); + void (*pfnSaveReadFields) ( SAVERESTOREDATA *, const char *, void *, TYPEDESCRIPTION *, int ); + + void (*pfnSaveGlobalState) ( SAVERESTOREDATA * ); + void (*pfnRestoreGlobalState) ( SAVERESTOREDATA * ); + void (*pfnResetGlobalState) ( void ); + + qboolean (*pfnClientConnect) ( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + + void (*pfnClientDisconnect) ( edict_t *pEntity ); + void (*pfnClientKill) ( edict_t *pEntity ); + void (*pfnClientPutInServer) ( edict_t *pEntity ); + void (*pfnClientCommand) ( edict_t *pEntity ); + void (*pfnClientUserInfoChanged)( edict_t *pEntity, char *infobuffer ); + + void (*pfnServerActivate) ( edict_t *pEdictList, int edictCount, int clientMax ); + void (*pfnServerDeactivate) ( void ); + + void (*pfnPlayerPreThink) ( edict_t *pEntity ); + void (*pfnPlayerPostThink) ( edict_t *pEntity ); + + void (*pfnStartFrame) ( void ); + void (*pfnParmsNewLevel) ( void ); + void (*pfnParmsChangeLevel) ( void ); + + // Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life + const char *(*pfnGetGameDescription)( void ); + + // Notify dll about a player customization. + void (*pfnPlayerCustomization) ( edict_t *pEntity, customization_t *pCustom ); + + // Spectator funcs + void (*pfnSpectatorConnect) ( edict_t *pEntity ); + void (*pfnSpectatorDisconnect) ( edict_t *pEntity ); + void (*pfnSpectatorThink) ( edict_t *pEntity ); + + // Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint. + void (*pfnSys_Error) ( const char *error_string ); + + void (*pfnPM_Move) ( struct playermove_s *ppmove, qboolean server ); + void (*pfnPM_Init) ( struct playermove_s *ppmove ); + char (*pfnPM_FindTextureType)( char *name ); + void (*pfnSetupVisibility)( struct edict_s *pViewEntity, struct edict_s *pClient, unsigned char **pvs, unsigned char **pas ); + void (*pfnUpdateClientData) ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); + int (*pfnAddToFullPack)( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); + void (*pfnCreateBaseline) ( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); + void (*pfnRegisterEncoders) ( void ); + int (*pfnGetWeaponData) ( struct edict_s *player, struct weapon_data_s *info ); + + void (*pfnCmdStart) ( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); + void (*pfnCmdEnd) ( const edict_t *player ); + + // Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + // size of the response_buffer, so you must zero it out if you choose not to respond. + int (*pfnConnectionlessPacket ) ( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + + // Enumerates player hulls. Returns 0 if the hull number doesn't exist, 1 otherwise + int (*pfnGetHullBounds) ( int hullnumber, float *mins, float *maxs ); + + // Create baselines for certain "unplaced" items. + void (*pfnCreateInstancedBaselines) ( void ); + + // One of the pfnForceUnmodified files failed the consistency check for the specified player + // Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) + int (*pfnInconsistentFile)( const struct edict_s *player, const char *filename, char *disconnect_message ); + + // The game .dll should return 1 if lag compensation should be allowed ( could also just set + // the sv_unlag cvar. + // Most games right now should return 0, until client-side weapon prediction code is written + // and tested for them. + int (*pfnAllowLagCompensation)( void ); +} DLL_FUNCTIONS; + +extern DLL_FUNCTIONS gEntityInterface; + +// Current version. +#define NEW_DLL_FUNCTIONS_VERSION 1 + +typedef struct +{ + // Called right before the object's memory is freed. + // Calls its destructor. + void (*pfnOnFreeEntPrivateData)(edict_t *pEnt); + void (*pfnGameShutdown)(void); + int (*pfnShouldCollide)( edict_t *pentTouched, edict_t *pentOther ); +} NEW_DLL_FUNCTIONS; +typedef int (*NEW_DLL_FUNCTIONS_FN)( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +// Pointers will be null if the game DLL doesn't support this API. +extern NEW_DLL_FUNCTIONS gNewDLLFunctions; + +typedef int (*APIFUNCTION)( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +typedef int (*APIFUNCTION2)( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +#endif EIFACE_H diff --git a/src/engine/progdefs.h b/src/engine/progdefs.h new file mode 100644 index 0000000..9e4a397 --- /dev/null +++ b/src/engine/progdefs.h @@ -0,0 +1,224 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PROGDEFS_H +#define PROGDEFS_H +#ifdef _WIN32 +#pragma once +#endif + +typedef struct +{ + float time; + float frametime; + float force_retouch; + string_t mapname; + string_t startspot; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float found_secrets; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + edict_t *trace_ent; + float trace_inopen; + float trace_inwater; + int trace_hitgroup; + int trace_flags; + int msg_entity; + int cdAudioTrack; + int maxClients; + int maxEntities; + const char *pStringBase; + + void *pSaveData; + vec3_t vecLandmarkOffset; +} globalvars_t; + + +typedef struct entvars_s +{ + string_t classname; + string_t globalname; + + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t basevelocity; + vec3_t clbasevelocity; // Base velocity that was passed in to server physics so + // client can predict conveyors correctly. Server zeroes it, so we need to store here, too. + vec3_t movedir; + + vec3_t angles; // Model angles + vec3_t avelocity; // angle velocity (degrees per second) + vec3_t punchangle; // auto-decaying view angle adjustment + vec3_t v_angle; // Viewing angle (player only) + + // For parametric entities + vec3_t endpos; + vec3_t startpos; + float impacttime; + float starttime; + + int fixangle; // 0:nothing, 1:force view angles, 2:add avelocity + float idealpitch; + float pitch_speed; + float ideal_yaw; + float yaw_speed; + + int modelindex; + string_t model; + + int viewmodel; // player's viewmodel + int weaponmodel; // what other players see + + vec3_t absmin; // BB max translated to world coord + vec3_t absmax; // BB max translated to world coord + vec3_t mins; // local BB min + vec3_t maxs; // local BB max + vec3_t size; // maxs - mins + + float ltime; + float nextthink; + + int movetype; + int solid; + + int skin; + int body; // sub-model selection for studiomodels + int effects; + + float gravity; // % of "normal" gravity + float friction; // inverse elasticity of MOVETYPE_BOUNCE + + int light_level; + + int sequence; // animation sequence + int gaitsequence; // movement animation sequence for player (0 for none) + float frame; // % playback position in animation sequences (0..255) + float animtime; // world time when frame was set + float framerate; // animation playback rate (-8x to 8x) + byte controller[4]; // bone controller setting (0..255) + byte blending[2]; // blending amount between sub-sequences (0..255) + + float scale; // sprite rendering scale (0..255) + + int rendermode; + float renderamt; + vec3_t rendercolor; + int renderfx; + + float health; + float frags; + int weapons; // bit mask for available weapons + float takedamage; + + int deadflag; + vec3_t view_ofs; // eye position + + int button; + int impulse; + + edict_t *chain; // Entity pointer when linked into a linked list + edict_t *dmg_inflictor; + edict_t *enemy; + edict_t *aiment; // entity pointer when MOVETYPE_FOLLOW + edict_t *owner; + edict_t *groundentity; + + int spawnflags; + int flags; + + int colormap; // lowbyte topcolor, highbyte bottomcolor + int team; + + float max_health; + float teleport_time; + float armortype; + float armorvalue; + int waterlevel; + int watertype; + + string_t target; + string_t targetname; + string_t netname; + string_t message; + + float dmg_take; + float dmg_save; + float dmg; + float dmgtime; + + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; + + float speed; + float air_finished; + float pain_finished; + float radsuit_finished; + + edict_t *pContainingEntity; + + int playerclass; + float maxspeed; + + float fov; + int weaponanim; + + int pushmsec; + + int bInDuck; + int flTimeStepSound; + int flSwimTime; + int flDuckTime; + int iStepLeft; + float flFallVelocity; + + int gamestate; + + int oldbuttons; + + int groupinfo; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + edict_t *euser1; + edict_t *euser2; + edict_t *euser3; + edict_t *euser4; +} entvars_t; + + +#endif // PROGDEFS_H \ No newline at end of file diff --git a/src/engine/shake.h b/src/engine/shake.h new file mode 100644 index 0000000..a451ef2 --- /dev/null +++ b/src/engine/shake.h @@ -0,0 +1,55 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SHAKE_H +#define SHAKE_H + +// Screen / View effects + +// screen shake +extern int gmsgShake; + +// This structure is sent over the net to describe a screen shake event +typedef struct +{ + unsigned short amplitude; // FIXED 4.12 amount of shake + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short frequency; // FIXED 8.8 noise frequency (low frequency is a jerk,high frequency is a rumble) +} ScreenShake; + +extern void V_ApplyShake( float *origin, float *angles, float factor ); +extern void V_CalcShake( void ); +extern int V_ScreenShake( const char *pszName, int iSize, void *pbuf ); +extern int V_ScreenFade( const char *pszName, int iSize, void *pbuf ); + + +// Fade in/out +extern int gmsgFade; + +#define FFADE_IN 0x0000 // Just here so we don't pass 0 into the function +#define FFADE_OUT 0x0001 // Fade out (not in) +#define FFADE_MODULATE 0x0002 // Modulate (don't blend) +#define FFADE_STAYOUT 0x0004 // ignores the duration, stays faded out until new ScreenFade message received + +// This structure is sent over the net to describe a screen fade event +typedef struct +{ + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short holdTime; // FIXED 4.12 seconds duration until reset (fade & hold) + short fadeFlags; // flags + byte r, g, b, a; // fade to color ( max alpha ) +} ScreenFade; + +#endif // SHAKE_H + diff --git a/src/engine/studio.h b/src/engine/studio.h new file mode 100644 index 0000000..b0b9960 --- /dev/null +++ b/src/engine/studio.h @@ -0,0 +1,362 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + + + +#ifndef _STUDIO_H_ +#define _STUDIO_H_ + +/* +============================================================================== + +STUDIO MODELS + +Studio models are position independent, so the cache manager can move them. +============================================================================== +*/ + + +#define MAXSTUDIOTRIANGLES 20000 // TODO: tune this +#define MAXSTUDIOVERTS 2048 // TODO: tune this +#define MAXSTUDIOSEQUENCES 256 // total animation sequences +#define MAXSTUDIOSKINS 100 // total textures +#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement +#define MAXSTUDIOBONES 128 // total bones actually used +#define MAXSTUDIOMODELS 32 // sub-models per model +#define MAXSTUDIOBODYPARTS 32 +#define MAXSTUDIOGROUPS 16 +#define MAXSTUDIOANIMATIONS 512 // per sequence +#define MAXSTUDIOMESHES 256 +#define MAXSTUDIOEVENTS 1024 +#define MAXSTUDIOPIVOTS 256 +#define MAXSTUDIOCONTROLLERS 8 + +typedef struct +{ + int id; + int version; + + char name[64]; + int length; + + vec3_t eyeposition; // ideal eye position + vec3_t min; // ideal movement hull size + vec3_t max; + + vec3_t bbmin; // clipping bounding box + vec3_t bbmax; + + int flags; + + int numbones; // bones + int boneindex; + + int numbonecontrollers; // bone controllers + int bonecontrollerindex; + + int numhitboxes; // complex bounding boxes + int hitboxindex; + + int numseq; // animation sequences + int seqindex; + + int numseqgroups; // demand loaded sequences + int seqgroupindex; + + int numtextures; // raw textures + int textureindex; + int texturedataindex; + + int numskinref; // replaceable textures + int numskinfamilies; + int skinindex; + + int numbodyparts; + int bodypartindex; + + int numattachments; // queryable attachable points + int attachmentindex; + + int soundtable; + int soundindex; + int soundgroups; + int soundgroupindex; + + int numtransitions; // animation node to animation node transition graph + int transitionindex; +} studiohdr_t; + +// header for demand loaded sequence group data +typedef struct +{ + int id; + int version; + + char name[64]; + int length; +} studioseqhdr_t; + +// bones +typedef struct +{ + char name[32]; // bone name for symbolic links + int parent; // parent bone + int flags; // ?? + int bonecontroller[6]; // bone controller index, -1 == none + float value[6]; // default DoF values + float scale[6]; // scale for delta DoF values +} mstudiobone_t; + + +// bone controllers +typedef struct +{ + int bone; // -1 == 0 + int type; // X, Y, Z, XR, YR, ZR, M + float start; + float end; + int rest; // byte index value at rest + int index; // 0-3 user set controller, 4 mouth +} mstudiobonecontroller_t; + +// intersection boxes +typedef struct +{ + int bone; + int group; // intersection group + vec3_t bbmin; // bounding box + vec3_t bbmax; +} mstudiobbox_t; + +#if !defined( CACHE_USER ) && !defined( QUAKEDEF_H ) +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; +} cache_user_t; +#endif + +// demand loaded sequence groups +typedef struct +{ + char label[32]; // textual name + char name[64]; // file name + cache_user_t cache; // cache index pointer + int data; // hack for group 0 +} mstudioseqgroup_t; + +// sequence descriptions +typedef struct +{ + char label[32]; // sequence label + + float fps; // frames per second + int flags; // looping/non-looping flags + + int activity; + int actweight; + + int numevents; + int eventindex; + + int numframes; // number of frames per sequence + + int numpivots; // number of foot pivots + int pivotindex; + + int motiontype; + int motionbone; + vec3_t linearmovement; + int automoveposindex; + int automoveangleindex; + + vec3_t bbmin; // per sequence bounding box + vec3_t bbmax; + + int numblends; + int animindex; // mstudioanim_t pointer relative to start of sequence group data + // [blend][bone][X, Y, Z, XR, YR, ZR] + + int blendtype[2]; // X, Y, Z, XR, YR, ZR + float blendstart[2]; // starting value + float blendend[2]; // ending value + int blendparent; + + int seqgroup; // sequence group for demand loading + + int entrynode; // transition node at entry + int exitnode; // transition node at exit + int nodeflags; // transition rules + + int nextseq; // auto advancing sequences +} mstudioseqdesc_t; + +// events +#include "studio_event.h" +/* +typedef struct +{ + int frame; + int event; + int type; + char options[64]; +} mstudioevent_t; +*/ + +// pivots +typedef struct +{ + vec3_t org; // pivot point + int start; + int end; +} mstudiopivot_t; + +// attachment +typedef struct +{ + char name[32]; + int type; + int bone; + vec3_t org; // attachment point + vec3_t vectors[3]; +} mstudioattachment_t; + +typedef struct +{ + unsigned short offset[6]; +} mstudioanim_t; + +// animation frames +typedef union +{ + struct { + byte valid; + byte total; + } num; + short value; +} mstudioanimvalue_t; + + + +// body part index +typedef struct +{ + char name[64]; + int nummodels; + int base; + int modelindex; // index into models array +} mstudiobodyparts_t; + + + +// skin info +typedef struct +{ + char name[64]; + int flags; + int width; + int height; + int index; +} mstudiotexture_t; + + +// skin families +// short index[skinfamilies][skinref] + +// studio models +typedef struct +{ + char name[64]; + + int type; + + float boundingradius; + + int nummesh; + int meshindex; + + int numverts; // number of unique vertices + int vertinfoindex; // vertex bone info + int vertindex; // vertex vec3_t + int numnorms; // number of unique surface normals + int norminfoindex; // normal bone info + int normindex; // normal vec3_t + + int numgroups; // deformation groups + int groupindex; +} mstudiomodel_t; + + +// vec3_t boundingbox[model][bone][2]; // complex intersection info + + +// meshes +typedef struct +{ + int numtris; + int triindex; + int skinref; + int numnorms; // per mesh normals + int normindex; // normal vec3_t +} mstudiomesh_t; + +// triangles +#if 0 +typedef struct +{ + short vertindex; // index into vertex array + short normindex; // index into normal array + short s,t; // s,t position on skin +} mstudiotrivert_t; +#endif + +// lighting options +#define STUDIO_NF_FLATSHADE 0x0001 +#define STUDIO_NF_CHROME 0x0002 +#define STUDIO_NF_FULLBRIGHT 0x0004 + +// motion flags +#define STUDIO_X 0x0001 +#define STUDIO_Y 0x0002 +#define STUDIO_Z 0x0004 +#define STUDIO_XR 0x0008 +#define STUDIO_YR 0x0010 +#define STUDIO_ZR 0x0020 +#define STUDIO_LX 0x0040 +#define STUDIO_LY 0x0080 +#define STUDIO_LZ 0x0100 +#define STUDIO_AX 0x0200 +#define STUDIO_AY 0x0400 +#define STUDIO_AZ 0x0800 +#define STUDIO_AXR 0x1000 +#define STUDIO_AYR 0x2000 +#define STUDIO_AZR 0x4000 +#define STUDIO_TYPES 0x7FFF +#define STUDIO_RLOOP 0x8000 // controller that wraps shortest distance + +// sequence flags +#define STUDIO_LOOPING 0x0001 + +// bone flags +#define STUDIO_HAS_NORMALS 0x0001 +#define STUDIO_HAS_VERTICES 0x0002 +#define STUDIO_HAS_BBOX 0x0004 +#define STUDIO_HAS_CHROME 0x0008 // if any of the textures have chrome on them + +#define RAD_TO_STUDIO (32768.0/M_PI) +#define STUDIO_TO_RAD (M_PI/32768.0) + +#endif diff --git a/src/metamod/.gitignore b/src/metamod/.gitignore new file mode 100644 index 0000000..d6ab6d4 --- /dev/null +++ b/src/metamod/.gitignore @@ -0,0 +1,3 @@ +msgs/ +opt.*/ +debug.*/ diff --git a/src/metamod/Config.mak b/src/metamod/Config.mak new file mode 100644 index 0000000..f16796e --- /dev/null +++ b/src/metamod/Config.mak @@ -0,0 +1,34 @@ +MODNAME = metamod + +#__METAMOD_BUILD__ for our special eiface.h +EXTRA_CFLAGS += -D__METAMOD_BUILD__ +#-DMETA_PERFMON + +SRCFILES = api_hook.cpp api_info.cpp commands_meta.cpp conf_meta.cpp \ + dllapi.cpp engine_api.cpp engineinfo.cpp game_support.cpp \ + game_autodetect.cpp h_export.cpp linkgame.cpp linkplug.cpp \ + log_meta.cpp meta_eiface.cpp metamod.cpp mlist.cpp mplayer.cpp \ + mplugin.cpp mqueue.cpp mreg.cpp mutil.cpp osdep.cpp \ + osdep_p.cpp reg_support.cpp sdk_util.cpp studioapi.cpp \ + support_meta.cpp thread_logparse.cpp vdate.cpp + +INFOFILES = info_name.h vers_meta.h +RESFILE = res_meta.rc + +ifeq "$(OS)" "linux" + SRCFILES+=osdep_linkent_linux.cpp osdep_detect_gamedll_linux.cpp + EXTRA_LINK+= +else + SRCFILES+=osdep_linkent_win32.cpp osdep_detect_gamedll_win32.cpp + EXTRA_LINK+=-Xlinker --script -Xlinker i386pe.merge +endif + +ifeq "$(OPT)" "opt" + EXTRA_CFLAGS += -D__INTERNALS_USE_REGPARAMS__ +endif + +ifeq "$(OPT)" "opt-fast" + EXTRA_CFLAGS += -D__INTERNALS_USE_REGPARAMS__ +endif + +#STLFILES = mreg.cpp diff --git a/src/metamod/Makefile b/src/metamod/Makefile new file mode 100644 index 0000000..0412aa3 --- /dev/null +++ b/src/metamod/Makefile @@ -0,0 +1,460 @@ +# vi: set ts=4 sw=4 : +# vim: set tw=75 : + +# MetaMod makefile +# Copyright (c) 2001-2003 Will Day +# +# based on the Valve SDK 2.1 Makefile as well as the Makefile +# in adminmod by Alfred Reynolds. +# +# From SDK 2.1 dlls/Makefile: +#! Half-Life StandardSDK 2.0 mp_i386.so Makefile for i386 Linux +#! April 2000 by Leon Hartwig (jehannum@planethalflife.com) + + +# NOTE: This is a generic Makefile for metamod and the bundled plugins, and +# is symlinked into each subdir. Per-module config statements are in +# Config.mak in each subdir. + +ifeq "$(shell uname | cut -d _ -f1)" "CYGWIN" + HOST=cygwin +endif + +ifdef COMSPEC + ifeq "$(HOST)" "cygwin" + ifeq "$(TARGET)" "win32" + OS=windows + else + OS=linux + endif + else + OS=windows + endif +else + OS=linux +endif + +############################################################################# +# CONFIGURATION +############################################################################# + +# TARGET amd64 disabled since Valve has dropped support for x86-64 server +#ifeq "$(TARGET)" "amd64" +# TARGETTYPE = amd64 +#else + TARGETTYPE = i386 +#endif + +# set paths for your environment +ifeq "$(OS)" "linux" + INST_DIR=$(HOME)/half-life/cstrike/dlls + TEST_DIR=$(HOME)/test/tfc/dlls + TST_DIR=$(HOME)/tmp +else ## windows + INST_DIR=/hlserver/tfc/dlls + TEST_DIR=/hlserver/tfc/dlls +endif + +DLLS_DIR=../dlls +SDKSRC=../hlsdk +METADIR=../metamod + +COMPILE_TZ=EET +## Developer overrides +ifeq "$(USER)" "jussi" + COMPILE_TZ=EET +endif + + +############################################################################# +# OS DEPENDENCIES +############################################################################# + +ifeq "$(OS)" "linux" + INSTALL=install -m 644 + LD_WINDLL= i686-w64-mingw32-dllwrap + OSTARGET=linux + LIBFILE=$(LIBFILE_LINUX) + TARGET_FILE=$(TARGET_LINUX) +else ## windows + INSTALL=cp + LD_WINDLL= i686-w64-mingw32-dllwrap + OSTARGET=win32 + LIBFILE=$(LIBFILE_WIN) + TARGET_FILE=$(TARGET_WIN) +endif + +CC_WIN=i686-w64-mingw32-gcc +RES_WIN=i686-w64-mingw32-windres + +ifeq "$(HOST)" "cygwin" + CC_WIN += -mno-cygwin + EXTRA_LINK += -L/lib/w32api +endif + +OBJDIR_LINUX_OPT=opt.linux_$(TARGETTYPE) +OBJDIR_LINUX_DBG=debug.linux_$(TARGETTYPE) +OBJDIR_WIN_OPT=opt.win32 +OBJDIR_WIN_DBG=debug.win32 + + +############################################################################# +# COMPILE OPTIONS - ARCHITECTURE AND OPTIMIZATIONS +############################################################################# + +ifeq "$(OS)" "linux" + GCCMAJ = $(shell $(CC) -dumpversion | sed -e 's/\.[0-9][0-9]*//' -e 's/\.[0-9][0-9]*//') + GCCMIN = $(shell $(CC) -dumpversion | sed -e 's/[0-9]\.//;s/\.[0-9]//' -e 's/\.[0-9][0-9]*//') +else + GCCMAJ = $(shell $(CC_WIN) -dumpversion | sed -e 's/\.[0-9][0-9]*//' -e 's/\.[0-9][0-9]*//') + GCCMIN = $(shell $(CC_WIN) -dumpversion | sed -e 's/[0-9]\.//;s/\.[0-9]//' -e 's/\.[0-9][0-9]*//') +endif + +GCCMAJ_GT_4 = $(shell if [ $(GCCMAJ) -gt 4 ]; then echo 1; else echo 0; fi) + +ifeq "$(HOST)" "cygwin" + ifeq "$(TARGETTYPE)" "amd64" + CC=gcc-linux-x86_64 + else + CC=gcc-linux + endif +else + ifeq "$(TARGETTYPE)" "amd64" + CC=gcc -m64 + else + CC=gcc -m32 + endif +endif + +MCPU=-mcpu + +ifeq "$(GCCMAJ)" "3" + ifeq "$(GCCMIN)" "4" + MCPU=-mtune + endif +endif +ifeq "$(GCCMAJ)" "4" + MCPU=-mtune +endif +ifeq "$(GCCMAJ_GT_4)" "1" + MCPU=-mtune +endif + +ifeq "$(OS)" "linux" + CC_DEP=$(CC) +else + CC_DEP=$(CC_WIN) +endif + +# Note! About gcc optimization levels. +# There is four optimization levels: +# -O0 No optimizations. +# -O1 Optimize for smaller size. +# -O2 Optimize for speed without increasing size (alot). +# -O3 Optimize for speed, can result much greater filesize. +# Levels higher -O3 (-O6 for example) is threaded as -O3. +# See differences at "http://gcc.gnu.org/" (look for link to 'gcc manual'). + +# original safe optimization, from valve Makefile +#CCOPT = -O2 -ffast-math -funroll-loops \ +# -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ +# -malign-jumps=2 -malign-functions=2 + +# safe optimization, adapted from adminmod Makefile +#CCOPT = -m486 -O6 -ffast-math -funroll-loops \ +# -fexpensive-optimizations -malign-loops=2 -malign-jumps=2 \ +# -malign-functions=2 -Wall + +# full optimization, adapted from adminmod Makefile +# "WONT WORK WITH omit-frame-pointer"? +# - disable (unneeded) C++ exceptions and rtti code to save some space ? + +CCOPT = $(CCO) $(CCOPT_ARCH) -fno-exceptions -fno-rtti + +# optimization level; overridden for certain problematic files +CCO = -O2 -fomit-frame-pointer -funsafe-math-optimizations +CCO += -flto -fvisibility=hidden + +# architecture tuning by target type +ifeq "$(TARGETTYPE)" "amd64" + CCOPT_ARCH = +else + CCOPT_ARCH = -march=i686 $(MCPU)=generic -msse -msse2 +endif + +# debugging; halt on warnings +CCDEBUG+= -ggdb3 + + +############################################################################# +# COMPILE SETUP +############################################################################# + +SRCDIR=. +INCLUDEDIRS+=-I$(SRCDIR) -I$(METADIR) -I$(SDKSRC)/engine -I$(SDKSRC)/common -I$(SDKSRC)/pm_shared -I$(SDKSRC)/dlls -I$(SDKSRC) +FILES_ALL = *.cpp *.h [A-Z]* *.rc + +CFLAGS=-Wall -Wno-unknown-pragmas -Wno-attributes -Wno-write-strings + +CFLAGS+=-std=gnu++98 -Wno-c++11-compat + +#CFLAGS += -DTEST + +ifeq "$(OPT)" "opt-fast" + ODEF = -DOPT_TYPE="\"optimized+meta_debug-disabled\"" + CFLAGS := $(CCOPT) $(CFLAGS) $(ODEF) -D__BUILD_FAST_METAMOD__ + OBJDIR_LINUX = $(OBJDIR_LINUX_OPT) + OBJDIR_WIN = $(OBJDIR_WIN_OPT) +else #other +ifeq "$(OPT)" "opt" + ODEF = -DOPT_TYPE="\"optimized\"" + CFLAGS := $(CCOPT) $(CFLAGS) $(ODEF) + OBJDIR_LINUX = $(OBJDIR_LINUX_OPT) + OBJDIR_WIN = $(OBJDIR_WIN_OPT) +else # debug + ODEF = -DOPT_TYPE="\"debugging\"" + CFLAGS := $(CCDEBUG) $(CFLAGS) $(ODEF) + OBJDIR_LINUX = $(OBJDIR_LINUX_DBG) + OBJDIR_WIN = $(OBJDIR_WIN_DBG) + DLLS_DIR := $(DLLS_DIR)/debug +endif +endif + +ifeq "$(OS)" "linux" + OBJDIR = $(OBJDIR_LINUX) +else + OBJDIR = $(OBJDIR_WIN) +endif + +include Config.mak +# any local CFLAGS from Config.mak +CFLAGS += $(EXTRA_CFLAGS) + +# provide timezone info +$(OBJDIR_LINUX)/vdate.o $(OBJDIR_WIN)/vdate.o: CFLAGS += -DCOMPILE_TZ=\"$(COMPILE_TZ)\" + +# ignore complaints from SDK files like cbase.h +#$(OBJDIR_LINUX)/sdk_util.o $(OBJDIR_WIN)/sdk_util.o: CFLAGS += -Wno-unused + +# ignore complaints from STL headers +STLOBJ = $(STLFILES:%.cpp=$(OBJDIR_LINUX)/%.o) +STLOBJ += $(STLFILES:%.cpp=$(OBJDIR_WIN)/%.o) +#$(OBJDIR_LINUX)/mreg.o $(OBJDIR_WIN)/mreg.o: CFLAGS += -Wno-effc++ +$(STLOBJ): FILTER= 2>&1 | ../tools/stlfilter +$(STLOBJ): CFLAGS += -Wno-error + +# these files seem to create "Internal compiler error" errors under mingw +# when using -O6 +#$(OBJDIR_WIN)/engine_api.o: CCO = -O5 +#$(OBJDIR_WIN)/dllapi_api.o: CCO = -O5 + +############################################################################# +# BUILDING LINUX SO +############################################################################# + +# linux .so compile commands +DO_CC_LINUX=$(CC) $(CFLAGS) -fPIC $(INCLUDEDIRS) -o $@ -c $< $(FILTER) +LINK_LINUX=$(CC) $(CFLAGS) -shared -ldl -lm -static-libgcc $(EXTRA_LINK) $(OBJ_LINUX) -o $@ + +# sort by date +#SRCFILES := $(shell ls -t $(SRCFILES)) + +# linux object files +OBJ_LINUX := $(SRCFILES:%.cpp=$(OBJDIR_LINUX)/%.o) + +# compiling linux object files +$(OBJDIR_LINUX)/%.o: $(SRCDIR)/%.cpp + $(DO_CC_LINUX) + +# linux .so target file +LIBFILE_LINUX = $(MODNAME).so +TARGET_LINUX = $(OBJDIR_LINUX)/$(LIBFILE_LINUX) + + +############################################################################# +# BUILDING WINDOWS DLL +############################################################################# + +# windows .dll compile commands +DO_CC_WIN=$(CC_WIN) $(CFLAGS) $(INCLUDEDIRS) -o $@ -c $< +DO_RES_WIN=$(RES_WIN) '$(ODEF)' --include-dir . --include-dir ../metamod -i $< -O coff -o $@ +#LINK_WIN=$(LD_WINDLL) -k -mwindows --add-stdcall-alias --def metamod.def -o $@ $(OBJ_WIN) +#LINK_WIN=$(LD_WINDLL) -A -k -mwindows --export-all-symbols -o $@ $(OBJ_WIN) +#LINK_WIN=$(LD_WINDLL) -mwindows --add-stdcall-alias $(OBJ_WIN) $(RES_OBJ_WIN) -lstdc++ -s -o $@ +LINK_WIN=$(CC_WIN) $(CFLAGS) -mdll -Xlinker --add-stdcall-alias $(EXTRA_LINK) $(OBJ_WIN) $(RES_OBJ_WIN) -s -o $@ + +# windows object files +OBJ_WIN := $(SRCFILES:%.cpp=$(OBJDIR_WIN)/%.o) +RES_OBJ_WIN := $(RESFILE:%.rc=$(OBJDIR_WIN)/%.o) + +# compiling windows object files +$(OBJDIR_WIN)/%.o: $(SRCDIR)/%.cpp + $(DO_CC_WIN) + +# compiling windows resource file +$(OBJDIR_WIN)/%.o: $(SRCDIR)/%.rc $(INFOFILES) + $(DO_RES_WIN) + +# windows .dll target file +LIBFILE_WIN = $(MODNAME).dll +TARGET_WIN = $(OBJDIR_WIN)/$(LIBFILE_WIN) + + +############################################################################# +# OVERRIDES +############################################################################# + +ifeq "$(PLATFORM)" "linux-only" + LIBFILE_WIN = + TARGET_WIN = +endif +ifeq "$(PLATFORM)" "win32-only" + LIBFILE_LINUX = + TARGET_LINUX = + OSTARGET=win32 + LIBFILE=$(LIBFILE_WIN) + TARGET_FILE=$(TARGET_WIN) +endif + + +############################################################################# +# BUILD RULES +############################################################################# + +default: $(TARGET_FILE) + +all: do_dll_linux do_dll_win32 + +opt: + $(MAKE) default OPT=opt + +linux: do_dll_linux +win32: do_dll_win32 + +linux_opt: + $(MAKE) linux OPT=opt +win32_opt: + $(MAKE) win32 OPT=opt + +$(TARGET_LINUX): msgs/debug msgs/warning msgs/log msgs/error $(OBJDIR_LINUX) $(OBJ_LINUX) + $(LINK_LINUX) + +# for plugins, recompile meta_api.cpp if info_name.h changed +$(OBJDIR_LINUX)/meta_api.o $(OBJDIR_WIN)/meta_api.o: info_name.h + +$(TARGET_WIN): msgs/debug msgs/warning msgs/log msgs/error $(OBJDIR_WIN) $(OBJ_WIN) $(RES_OBJ_WIN) + $(LINK_WIN) + +$(OBJDIR_LINUX) $(OBJDIR_WIN) msgs: + mkdir $@ + +# make sure to recompile vdate.c for each link +$(OBJDIR_LINUX)/vdate.o $(OBJDIR_WIN)/vdate.o : $(SRCFILES) *.h + +domsgs: msgs/debug msgs/log msgs/error msgs/warning + +msgs/debug: $(SRCFILES) *.h msgs + egrep "DEBUG\([0-9]" $(SRCFILES) *.h | sed "s/:[ ]*/ /" | sort -k2,2 > $@ + +msgs/log: $(SRCFILES) *.h msgs + egrep "META_LOG\(" $(SRCFILES) *.h | sed "s/:[ ]*/ /" | sort > $@ + +msgs/error: $(SRCFILES) *.h msgs + egrep "META_ERROR\(" $(SRCFILES) *.h | sed "s/:[ ]*/ /" | sort > $@ + +msgs/warning: $(SRCFILES) *.h msgs + egrep "META_WARNING\(" $(SRCFILES) *.h | sed "s/:[ ]*/ /" | sort > $@ + +tags: .tags .htags +ctags: .tags +htags: .htags + +.tags: $(SRCFILES) *.h + -ctags -a $? + +.htags: *.h + -htags $? + +retags: + -rm -f .tags .htags + ctags -f .tags `find $(SDKSRC) -name '*.h'` + ctags -f .tags -a `find $(METADIR) -name old -prune -o -name '*.h' -print` + ctags -f .tags -a $(SRCFILES) + htags -R $(SDKSRC) + htags `find $(METADIR) -name old -prune -o -name '*.h' -print` + htags *.h + +clean: clean_$(OSTARGET) + +clean_linux: + test -n "$(OBJDIR_LINUX)" + -rm -f $(OBJDIR_LINUX)/* + +clean_win32: + test -n "$(OBJDIR_WIN)" + -rm -f $(OBJDIR_WIN)/* + +cleanall_linux: + $(MAKE) clean_linux + $(MAKE) clean_linux OPT=opt + $(MAKE) clean_linux TARGET=amd64 + $(MAKE) clean_linux TARGET=amd64 OPT=opt + +cleanall_win32: + $(MAKE) clean_win32 + $(MAKE) clean_win32 OPT=opt + +cleanall: cleanall_linux cleanall_win32 + +dll_linux dll_win32: + $(MAKE) do_$@ + $(MAKE) do_$@ OPT=opt + +do_dll_linux: $(DLLS_DIR) $(DLLS_DIR)/$(LIBFILE_LINUX) +do_dll_win32: $(DLLS_DIR) $(DLLS_DIR)/$(LIBFILE_WIN) + +$(DLLS_DIR): + mkdir $(DLLS_DIR) + +$(DLLS_DIR)/$(LIBFILE_LINUX): $(TARGET_LINUX) + $(INSTALL) $+ $@ + +$(DLLS_DIR)/$(LIBFILE_WIN): $(TARGET_WIN) + $(INSTALL) $+ $@ + +dlls: dll_linux dll_win32 +rmdlls: + -rm -f ../dlls/*.* ../dlls/debug/*.* + +spotless: cleanall + -rmdir $(OBJDIR_LINUX) $(OBJDIR_WIN) +# -rm -f .snap $(GENERATED) + +distclean: spotless + +install: $(INST_DIR)/$(LIBFILE) +test: $(TEST_DIR)/$(LIBFILE) +test_opt: + $(MAKE) test OPT=opt + +$(INST_DIR)/$(LIBFILE) $(TEST_DIR)/$(LIBFILE): $(TARGET_FILE) + $(INSTALL) $< $@ + +tst: $(TST_DIR)/$(LIBFILE_WIN) + +$(TST_DIR)/$(LIBFILE_WIN): $(TARGET_WIN) + cp $< $@ + +snap: .snap + +.snap: $(FILES_ALL) + mkdir -p snapshots + tar zcvf snapshots/`date '+%m%d-%H%M'`.tgz $(FILES_ALL) + touch .snap + +depend: $(OBJDIR)/Rules.depend + +$(OBJDIR)/Rules.depend: Makefile $(SRCFILES) $(OBJDIR) + $(CC_DEP) -MM $(INCLUDEDIRS) $(SRCFILES) | sed "s;\(^[^ ]*\):\(.*\);$(OBJDIR)/\1: Makefile Config.mak \2;" > $@ + +include $(OBJDIR)/Rules.depend diff --git a/src/metamod/api_hook.cpp b/src/metamod/api_hook.cpp new file mode 100644 index 0000000..d2e41f0 --- /dev/null +++ b/src/metamod/api_hook.cpp @@ -0,0 +1,690 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // offsetof +#include + +#include "ret_type.h" +#include "types_meta.h" +#include "api_info.h" +#include "api_hook.h" +#include "mplugin.h" +#include "metamod.h" +#include "osdep.h" //unlikely + +// getting pointer with table index is faster than with if-else +static const void ** api_tables[3] = { + (const void**)&Engine.funcs, + (const void**)&GameDLL.funcs.dllapi_table, + (const void**)&GameDLL.funcs.newapi_table +}; + +static const void ** api_info_tables[3] = { + (const void**)&engine_info, + (const void**)&dllapi_info, + (const void**)&newapi_info +}; + +// Safety check for metamod-bot-plugin bugfix. +// engine_api->pfnRunPlayerMove calls dllapi-functions before it returns. +// This causes problems with bots running as metamod plugins, because +// metamod assumed that PublicMetaGlobals is free to be used. +// With call_count we can fix this by backuping up PublicMetaGlobals if +// it's already being used. +static unsigned int call_count = 0; + +// get function pointer from api table by function pointer offset +inline void * DLLINTERNAL get_api_function(const void * api_table, unsigned int func_offset) { + return(*(void**)((unsigned long)api_table + func_offset)); +} + +// get data pointer from api_info table by function offset +inline const api_info_t * DLLINTERNAL get_api_info(enum_api_t api, unsigned int api_info_offset) { + return((const api_info_t *)((unsigned long)api_info_tables[api] + api_info_offset)); +} + +// simplified 'void' version of main hook function +void DLLINTERNAL main_hook_function_void(unsigned int api_info_offset, enum_api_t api, unsigned int func_offset, const void * packed_args) { + const api_info_t *api_info; + int i; + META_RES mres, status, prev_mres; + MPlugin *iplug; + void *pfn_routine; + int loglevel; + const void *api_table; + meta_globals_t backup_meta_globals[1]; + + //passing offset from api wrapper function makes code faster/smaller + api_info = get_api_info(api, api_info_offset); + + //Fix bug with metamod-bot-plugins. + if(unlikely(call_count++>0)) { + //Backup PublicMetaGlobals. + backup_meta_globals[0] = PublicMetaGlobals; + } + + //Setup + loglevel=api_info->loglevel; + mres=MRES_UNSET; + status=MRES_UNSET; + prev_mres=MRES_UNSET; + pfn_routine=NULL; + + //Pre plugin functions + prev_mres=MRES_UNSET; + for(i=0; likely(i < Plugins->endlist); i++) { + iplug=&Plugins->plist[i]; + + if(unlikely(iplug->status != PL_RUNNING)) + continue; + + api_table = iplug->get_api_table(api); + if(likely(!api_table)) { + //plugin doesn't provide this api table + continue; + } + + pfn_routine=get_api_function(api_table, func_offset); + if(likely(!pfn_routine)) { + //plugin doesn't provide this function + continue; + } + + // initialize PublicMetaGlobals + PublicMetaGlobals.mres = MRES_UNSET; + PublicMetaGlobals.prev_mres = prev_mres; + PublicMetaGlobals.status = status; + + // call plugin + META_DEBUG(loglevel, ("Calling %s:%s()", iplug->file, api_info->name)); + api_info->api_caller(pfn_routine, packed_args); + API_UNPAUSE_TSC_TRACKING(); + + // plugin's result code + mres=PublicMetaGlobals.mres; + if(unlikely(mres > status)) + status = mres; + + // save this for successive plugins to see + prev_mres = mres; + + if(unlikely(mres==MRES_UNSET)) + META_WARNING("Plugin didn't set meta_result: %s:%s()", iplug->file, api_info->name); + } + + call_count--; + + //Api call + if(likely(status!=MRES_SUPERCEDE)) { + //get api table + api_table = *api_tables[api]; + + if(likely(api_table)) { + pfn_routine = get_api_function(api_table, func_offset); + if(likely(pfn_routine)) { + META_DEBUG(loglevel, ("Calling %s:%s()", (api==e_api_engine)?"engine":GameDLL.file, api_info->name)); + api_info->api_caller(pfn_routine, packed_args); + API_UNPAUSE_TSC_TRACKING(); + } else { + // don't complain for NULL routines in NEW_DLL_FUNCTIONS + if(unlikely(api != e_api_newapi)) + META_WARNING("Couldn't find api call: %s:%s", (api==e_api_engine)?"engine":GameDLL.file, api_info->name); + status=MRES_UNSET; + } + } else { + // don't complain for NULL NEW_DLL_FUNCTIONS-table + if(unlikely(api != e_api_newapi)) + META_DEBUG(loglevel, ("No api table defined for api call: %s:%s", (api==e_api_engine)?"engine":GameDLL.file, api_info->name)); + status=MRES_UNSET; + } + } else + META_DEBUG(loglevel, ("Skipped (supercede) %s:%s()", (api==e_api_engine)?"engine":GameDLL.file, api_info->name)); + + call_count++; + + //Post plugin functions + prev_mres=MRES_UNSET; + for(i=0; likely(i < Plugins->endlist); i++) { + iplug=&Plugins->plist[i]; + + if(unlikely(iplug->status != PL_RUNNING)) + continue; + + api_table = iplug->get_api_post_table(api); + if(likely(!api_table)) { + //plugin doesn't provide this api table + continue; + } + + pfn_routine=get_api_function(api_table, func_offset); + if(likely(!pfn_routine)) { + //plugin doesn't provide this function + continue; + } + + // initialize PublicMetaGlobals + PublicMetaGlobals.mres = MRES_UNSET; + PublicMetaGlobals.prev_mres = prev_mres; + PublicMetaGlobals.status = status; + + // call plugin + META_DEBUG(loglevel, ("Calling %s:%s_Post()", iplug->file, api_info->name)); + api_info->api_caller(pfn_routine, packed_args); + API_UNPAUSE_TSC_TRACKING(); + + // plugin's result code + mres=PublicMetaGlobals.mres; + if(unlikely(mres > status)) + status = mres; + + // save this for successive plugins to see + prev_mres = mres; + + if(unlikely(mres==MRES_UNSET)) + META_WARNING("Plugin didn't set meta_result: %s:%s_Post()", iplug->file, api_info->name); + else if(unlikely(mres==MRES_SUPERCEDE)) + META_WARNING("MRES_SUPERCEDE not valid in Post functions: %s:%s_Post()", iplug->file, api_info->name); + } + + if(unlikely(--call_count>0)) { + //Restore backup + PublicMetaGlobals = backup_meta_globals[0]; + } +} + +// full return typed version of main hook function +void * DLLINTERNAL main_hook_function(const class_ret_t ret_init, unsigned int api_info_offset, enum_api_t api, unsigned int func_offset, const void * packed_args) { + const api_info_t *api_info; + int i; + META_RES mres, status, prev_mres; + MPlugin *iplug; + void *pfn_routine; + int loglevel; + const void *api_table; + meta_globals_t backup_meta_globals[1]; + + //passing offset from api wrapper function makes code faster/smaller + api_info = get_api_info(api, api_info_offset); + + //Fix bug with metamod-bot-plugins. + if(unlikely(call_count++>0)) { + //Backup PublicMetaGlobals. + backup_meta_globals[0] = PublicMetaGlobals; + } + + //Return class setup + class_ret_t dllret=ret_init; + class_ret_t override_ret=ret_init; + class_ret_t pub_override_ret=ret_init; + class_ret_t orig_ret=ret_init; + class_ret_t pub_orig_ret=ret_init; + + //Setup + loglevel=api_info->loglevel; + mres=MRES_UNSET; + status=MRES_UNSET; + prev_mres=MRES_UNSET; + pfn_routine=NULL; + + //Pre plugin functions + prev_mres=MRES_UNSET; + for(i=0; likely(i < Plugins->endlist); i++) { + iplug=&Plugins->plist[i]; + + if(unlikely(iplug->status != PL_RUNNING)) + continue; + + api_table = iplug->get_api_table(api); + if(likely(!api_table)) { + //plugin doesn't provide this api table + continue; + } + + pfn_routine=get_api_function(api_table, func_offset); + if(likely(!pfn_routine)) { + //plugin doesn't provide this function + continue; + } + + // initialize PublicMetaGlobals + PublicMetaGlobals.mres = MRES_UNSET; + PublicMetaGlobals.prev_mres = prev_mres; + PublicMetaGlobals.status = status; + pub_orig_ret = orig_ret; + PublicMetaGlobals.orig_ret = pub_orig_ret.getptr(); + if(unlikely(status==MRES_SUPERCEDE)) { + pub_override_ret = override_ret; + PublicMetaGlobals.override_ret = pub_override_ret.getptr(); + } + + // call plugin + META_DEBUG(loglevel, ("Calling %s:%s()", iplug->file, api_info->name)); + dllret = class_ret_t(api_info->api_caller(pfn_routine, packed_args)); + API_UNPAUSE_TSC_TRACKING(); + + // plugin's result code + mres=PublicMetaGlobals.mres; + if(unlikely(mres > status)) + status = mres; + + // save this for successive plugins to see + prev_mres = mres; + + if(unlikely(mres==MRES_SUPERCEDE)) { + pub_override_ret = dllret; + override_ret = dllret; + } + else if(unlikely(mres==MRES_UNSET)) { + META_WARNING("Plugin didn't set meta_result: %s:%s()", iplug->file, api_info->name); + } + } + + call_count--; + + //Api call + if(likely(status!=MRES_SUPERCEDE)) { + //get api table + api_table = *api_tables[api]; + + if(likely(api_table)) { + pfn_routine = get_api_function(api_table, func_offset); + if(likely(pfn_routine)) { + META_DEBUG(loglevel, ("Calling %s:%s()", (api==e_api_engine)?"engine":GameDLL.file, api_info->name)); + dllret = class_ret_t(api_info->api_caller(pfn_routine, packed_args)); + API_UNPAUSE_TSC_TRACKING(); + orig_ret = dllret; + } else { + // don't complain for NULL routines in NEW_DLL_FUNCTIONS + if(unlikely(api != e_api_newapi)) + META_WARNING("Couldn't find api call: %s:%s", (api==e_api_engine)?"engine":GameDLL.file, api_info->name); + status=MRES_UNSET; + } + } else { + // don't complain for NULL NEW_DLL_FUNCTIONS-table + if(unlikely(api != e_api_newapi)) + META_DEBUG(loglevel, ("No api table defined for api call: %s:%s", (api==e_api_engine)?"engine":GameDLL.file, api_info->name)); + status=MRES_UNSET; + } + } else { + META_DEBUG(loglevel, ("Skipped (supercede) %s:%s()", (api==e_api_engine)?"engine":GameDLL.file, api_info->name)); + orig_ret = override_ret; + pub_orig_ret = override_ret; + PublicMetaGlobals.orig_ret = pub_orig_ret.getptr(); + } + + call_count++; + + //Pre plugin functions + prev_mres=MRES_UNSET; + for(i=0; likely(i < Plugins->endlist); i++) { + iplug=&Plugins->plist[i]; + + if(unlikely(iplug->status != PL_RUNNING)) + continue; + + api_table = iplug->get_api_post_table(api); + if(likely(!api_table)) { + //plugin doesn't provide this api table + continue; + } + + pfn_routine=get_api_function(api_table, func_offset); + if(likely(!pfn_routine)) { + //plugin doesn't provide this function + continue; + } + + // initialize PublicMetaGlobals + PublicMetaGlobals.mres = MRES_UNSET; + PublicMetaGlobals.prev_mres = prev_mres; + PublicMetaGlobals.status = status; + pub_orig_ret = orig_ret; + PublicMetaGlobals.orig_ret = pub_orig_ret.getptr(); + if(unlikely(status==MRES_OVERRIDE)) { + pub_override_ret = override_ret; + PublicMetaGlobals.override_ret = pub_override_ret.getptr(); + } + + // call plugin + META_DEBUG(loglevel, ("Calling %s:%s_Post()", iplug->file, api_info->name)); + dllret = class_ret_t(api_info->api_caller(pfn_routine, packed_args)); + API_UNPAUSE_TSC_TRACKING(); + + // plugin's result code + mres=PublicMetaGlobals.mres; + if(unlikely(mres > status)) + status = mres; + + // save this for successive plugins to see + prev_mres = mres; + + if(unlikely(mres==MRES_OVERRIDE)) { + pub_override_ret = dllret; + override_ret = dllret; + } + else if(unlikely(mres==MRES_UNSET)) { + META_WARNING("Plugin didn't set meta_result: %s:%s_Post()", iplug->file, api_info->name); + } + else if(unlikely(mres==MRES_SUPERCEDE)) { + META_WARNING("MRES_SUPERCEDE not valid in Post functions: %s:%s_Post()", iplug->file, api_info->name); + } + } + + if(unlikely(--call_count>0)) { + //Restore backup + PublicMetaGlobals = backup_meta_globals[0]; + } + + //return value is passed through ret_init! + if(likely(status!=MRES_OVERRIDE)) { + return(*(void**)orig_ret.getptr()); + } else { + META_DEBUG(loglevel, ("Returning (override) %s()", api_info->name)); + return(*(void**)override_ret.getptr()); + } +} + +// +// Macros for creating api caller functions +// +#define BEGIN_API_CALLER_FUNC(ret_type, args_type_code) \ + void * DLLINTERNAL _COMBINE4(api_caller_, ret_type, _args_, args_type_code)(const void * func, const void * packed_args) { \ + _COMBINE2(pack_args_type_, args_type_code) * p ATTRIBUTE(unused)= (_COMBINE2(pack_args_type_, args_type_code) *)packed_args; +#define END_API_CALLER_FUNC(ret_t, args_t, args) \ + API_PAUSE_TSC_TRACKING(); \ + return(*(void **)class_ret_t((*(( ret_t (*) args_t )func)) args).getptr()); \ + } +#define END_API_CALLER_FUNC_void(args_t, args) \ + API_PAUSE_TSC_TRACKING(); \ + return((*(( void* (*) args_t )func)) args); \ + } + +// +// API function callers. +// + +//- +BEGIN_API_CALLER_FUNC(void, ipV) +END_API_CALLER_FUNC_void( (int, const void*, ...), (p->i1, p->p1, p->str) ) + +//- +BEGIN_API_CALLER_FUNC(void, 2pV) +END_API_CALLER_FUNC_void( (const void*, const void*, ...), (p->p1, p->p2, p->str) ) + +//- +BEGIN_API_CALLER_FUNC(void, void) +END_API_CALLER_FUNC_void( (void), () ) + +BEGIN_API_CALLER_FUNC(ptr, void) +END_API_CALLER_FUNC(void*, (void), () ) + +BEGIN_API_CALLER_FUNC(int, void) +END_API_CALLER_FUNC(int, (void), () ) + +BEGIN_API_CALLER_FUNC(float, void) +END_API_CALLER_FUNC(float, (void), () ) + +//- +BEGIN_API_CALLER_FUNC(float, 2f) +END_API_CALLER_FUNC( float, (float, float), (p->f1, p->f2) ) + +//- +BEGIN_API_CALLER_FUNC(void, 2i) +END_API_CALLER_FUNC_void( (int, int), (p->i1, p->i2) ); + +BEGIN_API_CALLER_FUNC(int, 2i) +END_API_CALLER_FUNC(int, (int, int), (p->i1, p->i2) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2i2p) +END_API_CALLER_FUNC_void( (int, int, const void*, const void*), (p->i1, p->i2, p->p1, p->p2) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2i2pi2p) +END_API_CALLER_FUNC_void( (int, int, const void*, const void*, int, const void*, const void*), (p->i1, p->i2, p->p1, p->p2, p->i3, p->p3, p->p4) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2p) +END_API_CALLER_FUNC_void( (const void*, const void*), (p->p1, p->p2) ); + +BEGIN_API_CALLER_FUNC(ptr, 2p) +END_API_CALLER_FUNC(void*, (const void*, const void*), (p->p1, p->p2) ); + +BEGIN_API_CALLER_FUNC(int, 2p) +END_API_CALLER_FUNC(int, (const void*, const void*), (p->p1, p->p2) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2p2f) +END_API_CALLER_FUNC_void( (const void*, const void*, float, float), (p->p1, p->p2, p->f1, p->f2) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2p2i2p) +END_API_CALLER_FUNC_void( (const void*, const void*, int, int, const void*, const void*), (p->p1, p->p2, p->i1, p->i2, p->p3, p->p4) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2p3fus2uc) +END_API_CALLER_FUNC_void( (const void*, const void*, float, float, float, unsigned short, unsigned char, unsigned char), (p->p1, p->p2, p->f1, p->f2, p->f3, p->us1, p->uc1, p->uc2) ); + +//- +BEGIN_API_CALLER_FUNC(ptr, 2pf) +END_API_CALLER_FUNC(void*, (const void*, const void*, float), (p->p1, p->p2, p->f1) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2pfi) +END_API_CALLER_FUNC_void( (const void*, const void*, float, int), (p->p1, p->p2, p->f1, p->i1) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2pi) +END_API_CALLER_FUNC_void( (const void*, const void*, int), (p->p1, p->p2, p->i1) ); + +BEGIN_API_CALLER_FUNC(int, 2pi) +END_API_CALLER_FUNC(int, (const void*, const void*, int), (p->p1, p->p2, p->i1) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2pui) +END_API_CALLER_FUNC_void( (const void*, const void*, unsigned int), (p->p1, p->p2, p->ui1) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2pi2p) +END_API_CALLER_FUNC_void( (const void*, const void*, int, const void*, const void*), (p->p1, p->p2, p->i1, p->p3, p->p4) ); + +//- +BEGIN_API_CALLER_FUNC(void, 2pif2p) +END_API_CALLER_FUNC_void( (const void*, const void*, int, float, const void*, const void*), (p->p1, p->p2, p->i1, p->f1, p->p3, p->p4) ); + +//- +BEGIN_API_CALLER_FUNC(int, 3i) +END_API_CALLER_FUNC(int, (int, int, int), (p->i1, p->i2, p->i3) ); + +//- +BEGIN_API_CALLER_FUNC(void, 3p) +END_API_CALLER_FUNC_void( (const void*, const void*, const void*), (p->p1, p->p2, p->p3) ); + +BEGIN_API_CALLER_FUNC(ptr, 3p) +END_API_CALLER_FUNC(void*, (const void*, const void*, const void*), (p->p1, p->p2, p->p3) ); + +BEGIN_API_CALLER_FUNC(int, 3p) +END_API_CALLER_FUNC(int, (const void*, const void*, const void*), (p->p1, p->p2, p->p3) ); + +//- +BEGIN_API_CALLER_FUNC(void, 3p2f2i) +END_API_CALLER_FUNC_void( (const void*, const void*, const void*, float, float, int, int), (p->p1, p->p2, p->p3, p->f1, p->f2, p->i1, p->i2) ); + +//- +BEGIN_API_CALLER_FUNC(int, 3pi2p) +END_API_CALLER_FUNC(int, (const void*, const void*, const void*, int, const void*, const void*), (p->p1, p->p2, p->p3, p->i1, p->p4, p->p5) ); + +//- +BEGIN_API_CALLER_FUNC(void, 4p) +END_API_CALLER_FUNC_void( (const void*, const void*, const void*, const void*), (p->p1, p->p2, p->p3, p->p4) ); + +BEGIN_API_CALLER_FUNC(int, 4p) +END_API_CALLER_FUNC(int, (const void*, const void*, const void*, const void*), (p->p1, p->p2, p->p3, p->p4) ); + +//- +BEGIN_API_CALLER_FUNC(void, 4pi) +END_API_CALLER_FUNC_void( (const void*, const void*, const void*, const void*, int), (p->p1, p->p2, p->p3, p->p4, p->i1) ); + +BEGIN_API_CALLER_FUNC(int, 4pi) +END_API_CALLER_FUNC(int, (const void*, const void*, const void*, const void*, int), (p->p1, p->p2, p->p3, p->p4, p->i1) ); + +//- +BEGIN_API_CALLER_FUNC(void, f) +END_API_CALLER_FUNC_void( (float), (p->f1) ); + +//- +BEGIN_API_CALLER_FUNC(void, i) +END_API_CALLER_FUNC_void( (int), (p->i1) ); + +BEGIN_API_CALLER_FUNC(int, i) +END_API_CALLER_FUNC(int, (int), (p->i1) ); + +BEGIN_API_CALLER_FUNC(ptr, i) +END_API_CALLER_FUNC(void*, (int), (p->i1) ); + +BEGIN_API_CALLER_FUNC(uint, ui) +END_API_CALLER_FUNC(unsigned int, (unsigned int), (p->ui1) ); + +BEGIN_API_CALLER_FUNC(ptr, ui) +END_API_CALLER_FUNC(void*, (unsigned int), (p->ui1) ); + +//- +BEGIN_API_CALLER_FUNC(ulong, ul) +END_API_CALLER_FUNC(unsigned long, (unsigned long), (p->ul1) ); + +//- +BEGIN_API_CALLER_FUNC(void, i2p) +END_API_CALLER_FUNC_void( (int, const void*, const void*), (p->i1, p->p1, p->p2) ); + +BEGIN_API_CALLER_FUNC(int, i2p) +END_API_CALLER_FUNC(int, (int, const void*, const void*), (p->i1, p->p1, p->p2) ); + +//- +BEGIN_API_CALLER_FUNC(void, i3p) +END_API_CALLER_FUNC_void( (int, const void*, const void*, const void*), (p->i1, p->p1, p->p2, p->p3) ); + +//- +BEGIN_API_CALLER_FUNC(void, ip) +END_API_CALLER_FUNC_void( (int, const void*), (p->i1, p->p1) ); + +BEGIN_API_CALLER_FUNC(ushort, ip) +END_API_CALLER_FUNC( unsigned short, (int, const void*), (p->i1, p->p1) ); + +BEGIN_API_CALLER_FUNC(int, ip) +END_API_CALLER_FUNC( int, (int, const void*), (p->i1, p->p1) ); + +//- +BEGIN_API_CALLER_FUNC(void, ipusf2p2f4i) +END_API_CALLER_FUNC_void( (int, const void*, unsigned short, float, const void*, const void*, float, float, int, int, int, int), (p->i1, p->p1, p->us1, p->f1, p->p2, p->p3, p->f2, p->f3, p->i2, p->i3, p->i4, p->i5) ); + +//- +BEGIN_API_CALLER_FUNC(void, p) +END_API_CALLER_FUNC_void( (const void*), (p->p1) ); + +BEGIN_API_CALLER_FUNC(ptr, p) +END_API_CALLER_FUNC(void*, (const void*), (p->p1) ); + +BEGIN_API_CALLER_FUNC(char, p) +END_API_CALLER_FUNC(char, (const void*), (p->p1) ); + +BEGIN_API_CALLER_FUNC(int, p) +END_API_CALLER_FUNC(int, (const void*), (p->p1) ); + +BEGIN_API_CALLER_FUNC(uint, p) +END_API_CALLER_FUNC(unsigned int, (const void*), (p->p1) ); + +BEGIN_API_CALLER_FUNC(float, p) +END_API_CALLER_FUNC(float, (const void*), (p->p1) ); + +//- +BEGIN_API_CALLER_FUNC(void, p2f) +END_API_CALLER_FUNC_void( (const void*, float, float), (p->p1, p->f1, p->f2) ); + +//- +BEGIN_API_CALLER_FUNC(int, p2fi) +END_API_CALLER_FUNC(int, (const void*, float, float, int), (p->p1, p->f1, p->f2, p->i1) ); + +//- +BEGIN_API_CALLER_FUNC(void, p2i) +END_API_CALLER_FUNC_void( (const void*, int, int), (p->p1, p->i1, p->i2) ); + +//- +BEGIN_API_CALLER_FUNC(void, p3i) +END_API_CALLER_FUNC_void( (const void*, int, int, int), (p->p1, p->i1, p->i2, p->i3) ); + +//- +BEGIN_API_CALLER_FUNC(void, p4i) +END_API_CALLER_FUNC_void( (const void*, int, int, int, int), (p->p1, p->i1, p->i2, p->i3, p->i4) ); + +//- +BEGIN_API_CALLER_FUNC(void, puc) +END_API_CALLER_FUNC_void( (const void*, unsigned char), (p->p1, p->uc1) ); + +//- +BEGIN_API_CALLER_FUNC(void, pf) +END_API_CALLER_FUNC_void( (const void*, float), (p->p1, p->f1) ); + +//- +BEGIN_API_CALLER_FUNC(void, pfp) +END_API_CALLER_FUNC_void( (const void*, float, const void*), (p->p1, p->f1, p->p2) ); + +//- +BEGIN_API_CALLER_FUNC(void, pi) +END_API_CALLER_FUNC_void( (const void*, int), (p->p1, p->i1) ); + +BEGIN_API_CALLER_FUNC(ptr, pi) +END_API_CALLER_FUNC(void*, (const void*, int), (p->p1, p->i1) ); + +BEGIN_API_CALLER_FUNC(int, pi) +END_API_CALLER_FUNC(int, (const void*, int), (p->p1, p->i1) ); + +//- +BEGIN_API_CALLER_FUNC(void, pi2p) +END_API_CALLER_FUNC_void( (const void*, int, const void*, const void*), (p->p1, p->i1, p->p2, p->p3) ); + +//- +BEGIN_API_CALLER_FUNC(int, pi2p2ip) +END_API_CALLER_FUNC(int, (const void*, int, const void*, const void*, int, int, const void*), (p->p1, p->i1, p->p2, p->p3, p->i2, p->i3, p->p4) ); + +//- +BEGIN_API_CALLER_FUNC(void, pip) +END_API_CALLER_FUNC_void( (const void*, int, const void*), (p->p1, p->i1, p->p2) ); + +BEGIN_API_CALLER_FUNC(ptr, pip) +END_API_CALLER_FUNC(void*, (const void*, int, const void*), (p->p1, p->i1, p->p2) ); + +//- +BEGIN_API_CALLER_FUNC(void, pip2f2i) +END_API_CALLER_FUNC_void( (const void*, int, const void*, float, float, int, int), (p->p1, p->i1, p->p2, p->f1, p->f2, p->i2, p->i3) ); + +//- +BEGIN_API_CALLER_FUNC(void, pip2f4i2p) +END_API_CALLER_FUNC_void( (const void*, int, const void*, float, float, int, int, int, int, const void*, const void*), (p->p1, p->i1, p->p2, p->f1, p->f2, p->i2, p->i3, p->i4, p->i5, p->p3, p->p4) ); diff --git a/src/metamod/api_hook.h b/src/metamod/api_hook.h new file mode 100644 index 0000000..bdebf36 --- /dev/null +++ b/src/metamod/api_hook.h @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ +#ifndef API_HOOK_H +#define API_HOOK_H + +#include "ret_type.h" +#include "api_info.h" +#include "meta_api.h" +#include "osdep.h" //OPEN_ARGS + +// Compine 4 parts for single name +#define _COMBINE4(w,x,y,z) w##x##y##z +#define _COMBINE2(x,y) x##y + +// simplified 'void' version of main hook function +void DLLINTERNAL main_hook_function_void(unsigned int api_info_offset, enum_api_t api, unsigned int func_offset, const void * packed_args); + +// full return typed version of main hook function +void * DLLINTERNAL main_hook_function(const class_ret_t ret_init, unsigned int api_info_offset, enum_api_t api, unsigned int func_offset, const void * packed_args); + +// +// API function args structures/classes +// +#define API_PACK_ARGS(type, args) \ + _COMBINE2(pack_args_type_, type) packed_args args; + +#define PACK_ARGS_CLASS_HEADER(type, constructor_args) \ + class _COMBINE2(pack_args_type_, type) : public class_metamod_new { \ + public: inline _COMBINE2(pack_args_type_, type) constructor_args + +#define PACK_ARGS_END }; + +#define VOID_ARG 0 + +PACK_ARGS_CLASS_HEADER(void, (int)) {}; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(i, (int _i1)): i1(_i1) {}; + int i1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2i, (int _i1, int _i2)): i1(_i1), i2(_i2) {}; + int i1,i2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(3i, (int _i1, int _i2, int _i3)): i1(_i1), i2(_i2), i3(_i3) {}; + int i1,i2,i3; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(ui, (unsigned int _ui1)): ui1(_ui1) {}; + unsigned int ui1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(ul, (unsigned long _ul1)): ul1(_ul1) {}; + unsigned long ul1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(f, (float _f1)): f1(_f1) {}; + float f1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2f, (float _f1, float _f2)): f1(_f1), f2(_f2) {}; + float f1,f2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(p, (const void *_p1)): p1(_p1) {}; + const void *p1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2p, (const void *_p1, const void *_p2)): p1(_p1), p2(_p2) {}; + const void *p1,*p2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(3p, (const void *_p1, const void *_p2, const void *_p3)): p1(_p1), p2(_p2), p3(_p3) {}; + const void *p1,*p2,*p3; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(4p, (const void *_p1, const void *_p2, const void *_p3, const void *_p4)): p1(_p1), p2(_p2), p3(_p3), p4(_p4) {}; + const void *p1,*p2,*p3,*p4; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2pV, (const void *_p1, const void *_p2, const void *_str)): p1(_p1), p2(_p2), str(_str) {}; + const void *p1,*p2,*str; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(ipV, (int _i1, const void *_p1, const void *_str)): i1(_i1), p1(_p1), str(_str) {}; + int i1; + const void *p1,*str; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2i2p, (int _i1, int _i2, const void *_p1, const void *_p2)): i1(_i1), i2(_i2), p1(_p1), p2(_p2) {}; + int i1,i2; + const void *p1,*p2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2p2f, (const void *_p1, const void *_p2, float _f1, float _f2)): p1(_p1), p2(_p2), f1(_f1), f2(_f2) {}; + const void *p1,*p2; + float f1,f2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2p2i2p, (const void *_p1, const void *_p2, int _i1, int _i2, const void *_p3, const void *_p4)): p1(_p1), p2(_p2), i1(_i1), i2(_i2), p3(_p3), p4(_p4) {}; + const void *p1,*p2; + int i1,i2; + const void *p3,*p4; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2p3fus2uc, (const void *_p1, const void *_p2, float _f1, float _f2, float _f3, unsigned short _us1, unsigned char _uc1, unsigned char _uc2)): p1(_p1), p2(_p2), f1(_f1), f2(_f2), f3(_f3), us1(_us1), uc1(_uc1), uc2(_uc2) {}; + const void *p1,*p2; + float f1,f2,f3; + unsigned int us1; + unsigned int uc1,uc2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2pf, (const void *_p1, const void *_p2, float _f1)): p1(_p1), p2(_p2), f1(_f1) {}; + const void *p1,*p2; + float f1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2pfi, (const void *_p1, const void *_p2, float _f1, int _i1)): p1(_p1), p2(_p2), f1(_f1), i1(_i1) {}; + const void *p1,*p2; + float f1; + int i1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2pi, (const void *_p1, const void *_p2, int _i1)): p1(_p1), p2(_p2), i1(_i1) {}; + const void *p1,*p2; + int i1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2pi2p, (const void *_p1, const void *_p2, int _i1, const void *_p3, const void *_p4)): p1(_p1), p2(_p2), i1(_i1), p3(_p3), p4(_p4) {}; + const void *p1,*p2; + int i1; + const void *p3,*p4; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2pif2p, (const void *_p1, const void *_p2, int _i1, float _f1, const void *_p3, const void *_p4)): p1(_p1), p2(_p2), i1(_i1), f1(_f1), p3(_p3), p4(_p4) {}; + const void *p1,*p2; + int i1; + float f1; + const void *p3,*p4; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(3p2f2i, (const void *_p1, const void *_p2, const void *_p3, float _f1, float _f2, int _i1, int _i2)): p1(_p1), p2(_p2), p3(_p3), f1(_f1), f2(_f2), i1(_i1), i2(_i2) {}; + const void *p1,*p2,*p3; + float f1,f2; + int i1,i2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(3pi2p, (const void *_p1, const void *_p2, const void *_p3, int _i1, const void *_p4, const void *_p5)): p1(_p1), p2(_p2), p3(_p3), i1(_i1), p4(_p4), p5(_p5) {}; + const void *p1,*p2,*p3; + int i1; + const void *p4,*p5; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(i3p, (int _i1, const void *_p1, const void *_p2, const void *_p3)): i1(_i1), p1(_p1), p2(_p2), p3(_p3) {}; + int i1; + const void *p1,*p2,*p3; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(ip, (int _i1, const void *_p1)): i1(_i1), p1(_p1) {}; + int i1; + const void *p1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(ipusf2p2f4i, (int _i1, const void *_p1, unsigned short _us1, float _f1, const void *_p2, const void *_p3, float _f2, float _f3, int _i2, int _i3, int _i4, int _i5)): i1(_i1), p1(_p1), us1(_us1), f1(_f1), p2(_p2), p3(_p3), f2(_f2), f3(_f3), i2(_i2), i3(_i3), i4(_i4), i5(_i5) {}; + int i1; + const void *p1; + unsigned int us1; + float f1; + const void *p2,*p3; + float f2,f3; + int i2,i3,i4,i5; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(3pi, (const void *_p1, const void *_p2, const void *_p3, int _i1)): p1(_p1), p2(_p2), p3(_p3), i1(_i1) {}; + const void *p1,*p2,*p3; + int i1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(4pi, (const void *_p1, const void *_p2, const void *_p3, const void *_p4, int _i1)): p1(_p1), p2(_p2), p3(_p3), p4(_p4), i1(_i1) {}; + const void *p1,*p2,*p3,*p4; + int i1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(pf, (const void *_p1, float _f1)): p1(_p1), f1(_f1) {}; + const void *p1; + float f1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(pfp, (const void *_p1, float _f1, const void *_p2)): p1(_p1), f1(_f1), p2(_p2) {}; + const void *p1; + float f1; + const void *p2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(pi, (const void *_p1, int _i1)): p1(_p1), i1(_i1) {}; + const void *p1; + int i1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(pi2p, (const void *_p1, int _i1, const void *_p2, const void *_p3)): p1(_p1), i1(_i1), p2(_p2), p3(_p3) {}; + const void *p1; + int i1; + const void *p2, *p3; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(pip, (const void *_p1, int _i1, const void *_p2)): p1(_p1), i1(_i1), p2(_p2) {}; + const void *p1; + int i1; + const void *p2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(pip2f2i, (const void *_p1, int _i1, const void *_p2, float _f1, float _f2, int _i2, int _i3)): p1(_p1), i1(_i1), p2(_p2), f1(_f1), f2(_f2), i2(_i2), i3(_i3) {}; + const void *p1; + int i1; + const void *p2; + float f1,f2; + int i2,i3; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(pip2f4i2p, (const void *_p1, int _i1, const void *_p2, float _f1, float _f2, int _i2, int _i3, int _i4, int _i5, const void *_p3, const void *_p4)): p1(_p1), i1(_i1), p2(_p2), f1(_f1), f2(_f2), i2(_i2), i3(_i3), i4(_i4), i5(_i5), p3(_p3), p4(_p4) {}; + const void *p1; + int i1; + const void *p2; + float f1,f2; + int i2,i3,i4,i5; + const void *p3,*p4; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(puc, (const void *_p1, unsigned char _uc1)): p1(_p1), uc1(_uc1) {}; + const void *p1; + unsigned int uc1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2i2pi2p, (int _i1, int _i2, const void *_p1, const void *_p2, int _i3, const void *_p3, const void *_p4)): i1(_i1), i2(_i2), p1(_p1), p2(_p2), i3(_i3), p3(_p3), p4(_p4) {}; + int i1,i2; + const void *p1,*p2; + int i3; + const void *p3,*p4; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(2pui, (const void *_p1, const void *_p2, unsigned int _ui1)): p1(_p1), p2(_p2), ui1(_ui1) {}; + const void *p1,*p2; + unsigned int ui1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(i2p, (int _i1, const void *_p1, const void *_p2)): i1(_i1), p1(_p1), p2(_p2) {}; + int i1; + const void *p1,*p2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(p2f, (const void *_p1, float _f1, float _f2)): p1(_p1), f1(_f1), f2(_f2) {}; + const void *p1; + float f1,f2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(p2fi, (const void *_p1, float _f1, float _f2, int _i1)): p1(_p1), f1(_f1), f2(_f2), i1(_i1) {}; + const void *p1; + float f1,f2; + int i1; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(p2i, (const void *_p1, int _i1, int _i2)): p1(_p1), i1(_i1), i2(_i2) {}; + const void *p1; + int i1,i2; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(p3i, (const void *_p1, int _i1, int _i2, int _i3)): p1(_p1), i1(_i1), i2(_i2), i3(_i3) {}; + const void *p1; + int i1,i2,i3; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(p4i, (const void *_p1, int _i1, int _i2, int _i3, int _i4)): p1(_p1), i1(_i1), i2(_i2), i3(_i3), i4(_i4) {}; + const void *p1; + int i1,i2,i3,i4; +PACK_ARGS_END + +PACK_ARGS_CLASS_HEADER(pi2p2ip, (const void *_p1, int _i1, const void *_p2, const void *_p3, int _i2, int _i3, const void *_p4)): p1(_p1), i1(_i1), p2(_p2), p3(_p3), i2(_i2), i3(_i3), p4(_p4) {}; + const void *p1; + int i1; + const void *p2,*p3; + int i2,i3; + const void *p4; +PACK_ARGS_END + +// +// API function callers. +// +#ifdef __METAMOD_BUILD__ + #define EXTERN_API_CALLER_FUNCTION(ret_type, args_code) \ + void * DLLINTERNAL _COMBINE4(api_caller_, ret_type, _args_, args_code)(const void * func, const void * packed_args) +#else + #define EXTERN_API_CALLER_FUNCTION(ret_type, args_code) \ + static const api_caller_func_t _COMBINE4(api_caller_, ret_type, _args_, args_code) DLLHIDDEN = (api_caller_func_t)0 +#endif + +EXTERN_API_CALLER_FUNCTION(void, ipV); +EXTERN_API_CALLER_FUNCTION(void, 2pV); +EXTERN_API_CALLER_FUNCTION(void, void); +EXTERN_API_CALLER_FUNCTION(ptr, void); +EXTERN_API_CALLER_FUNCTION(int, void); +EXTERN_API_CALLER_FUNCTION(float, void); +EXTERN_API_CALLER_FUNCTION(float, 2f); +EXTERN_API_CALLER_FUNCTION(void, 2i); +EXTERN_API_CALLER_FUNCTION(int, 2i); +EXTERN_API_CALLER_FUNCTION(void, 2i2p); +EXTERN_API_CALLER_FUNCTION(void, 2i2pi2p); +EXTERN_API_CALLER_FUNCTION(void, 2p); +EXTERN_API_CALLER_FUNCTION(ptr, 2p); +EXTERN_API_CALLER_FUNCTION(int, 2p); +EXTERN_API_CALLER_FUNCTION(void, 2p2f); +EXTERN_API_CALLER_FUNCTION(void, 2p2i2p); +EXTERN_API_CALLER_FUNCTION(void, 2p3fus2uc); +EXTERN_API_CALLER_FUNCTION(ptr, 2pf); +EXTERN_API_CALLER_FUNCTION(void, 2pfi); +EXTERN_API_CALLER_FUNCTION(void, 2pi); +EXTERN_API_CALLER_FUNCTION(int, 2pi); +EXTERN_API_CALLER_FUNCTION(void, 2pui); +EXTERN_API_CALLER_FUNCTION(void, 2pi2p); +EXTERN_API_CALLER_FUNCTION(void, 2pif2p); +EXTERN_API_CALLER_FUNCTION(int, 3i); +EXTERN_API_CALLER_FUNCTION(void, 3p); +EXTERN_API_CALLER_FUNCTION(ptr, 3p); +EXTERN_API_CALLER_FUNCTION(int, 3p); +EXTERN_API_CALLER_FUNCTION(void, 3p2f2i); +EXTERN_API_CALLER_FUNCTION(int, 3pi2p); +EXTERN_API_CALLER_FUNCTION(void, 4p); +EXTERN_API_CALLER_FUNCTION(int, 4p); +EXTERN_API_CALLER_FUNCTION(void, 4pi); +EXTERN_API_CALLER_FUNCTION(int, 4pi); +EXTERN_API_CALLER_FUNCTION(void, f); +EXTERN_API_CALLER_FUNCTION(void, i); +EXTERN_API_CALLER_FUNCTION(ptr, i); +EXTERN_API_CALLER_FUNCTION(int, i); +EXTERN_API_CALLER_FUNCTION(ptr, ui); +EXTERN_API_CALLER_FUNCTION(uint, ui); +EXTERN_API_CALLER_FUNCTION(ulong, ul); +EXTERN_API_CALLER_FUNCTION(void, i2p); +EXTERN_API_CALLER_FUNCTION(int, i2p); +EXTERN_API_CALLER_FUNCTION(void, i3p); +EXTERN_API_CALLER_FUNCTION(void, ip); +EXTERN_API_CALLER_FUNCTION(ushort, ip); +EXTERN_API_CALLER_FUNCTION(int, ip); +EXTERN_API_CALLER_FUNCTION(void, ipusf2p2f4i); +EXTERN_API_CALLER_FUNCTION(void, p); +EXTERN_API_CALLER_FUNCTION(ptr, p); +EXTERN_API_CALLER_FUNCTION(char, p); +EXTERN_API_CALLER_FUNCTION(int, p); +EXTERN_API_CALLER_FUNCTION(uint, p); +EXTERN_API_CALLER_FUNCTION(float, p); +EXTERN_API_CALLER_FUNCTION(void, p2f); +EXTERN_API_CALLER_FUNCTION(int, p2fi); +EXTERN_API_CALLER_FUNCTION(void, p2i); +EXTERN_API_CALLER_FUNCTION(void, p3i); +EXTERN_API_CALLER_FUNCTION(void, p4i); +EXTERN_API_CALLER_FUNCTION(void, puc); +EXTERN_API_CALLER_FUNCTION(void, pf); +EXTERN_API_CALLER_FUNCTION(void, pfp); +EXTERN_API_CALLER_FUNCTION(void, pi); +EXTERN_API_CALLER_FUNCTION(ptr, pi); +EXTERN_API_CALLER_FUNCTION(int, pi); +EXTERN_API_CALLER_FUNCTION(void, pi2p); +EXTERN_API_CALLER_FUNCTION(int, pi2p2ip); +EXTERN_API_CALLER_FUNCTION(void, pip); +EXTERN_API_CALLER_FUNCTION(ptr, pip); +EXTERN_API_CALLER_FUNCTION(void, pip2f2i); +EXTERN_API_CALLER_FUNCTION(void, pip2f4i2p); + +#endif /*API_HOOK_H*/ diff --git a/src/metamod/api_info.cpp b/src/metamod/api_info.cpp new file mode 100644 index 0000000..7f139db --- /dev/null +++ b/src/metamod/api_info.cpp @@ -0,0 +1,275 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// api_info.cpp - info for api routines + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // always + +#include "api_info.h" // me +#include "api_hook.h" + +// trace flag, loglevel, name +const dllapi_info_t dllapi_info = { + { mFALSE, 3, api_caller_void_args_void, "GameDLLInit" }, // pfnGameInit + { mFALSE, 10, api_caller_int_args_p, "DispatchSpawn" }, // pfnSpawn + { mFALSE, 16, api_caller_void_args_p, "DispatchThink" }, // pfnThink + { mFALSE, 9, api_caller_void_args_2p, "DispatchUse" }, // pfnUse + { mFALSE, 11, api_caller_void_args_2p, "DispatchTouch" }, // pfnTouch + { mFALSE, 9, api_caller_void_args_2p, "DispatchBlocked" }, // pfnBlocked + { mFALSE, 10, api_caller_void_args_2p, "DispatchKeyValue" }, // pfnKeyValue + { mFALSE, 9, api_caller_void_args_2p, "DispatchSave" }, // pfnSave + { mFALSE, 9, api_caller_int_args_2pi, "DispatchRestore" }, // pfnRestore + { mFALSE, 20, api_caller_void_args_p, "DispatchObjectCollsionBox" }, // pfnSetAbsBox + { mFALSE, 9, api_caller_void_args_4pi, "SaveWriteFields" }, // pfnSaveWriteFields + { mFALSE, 9, api_caller_void_args_4pi, "SaveReadFields" }, // pfnSaveReadFields + { mFALSE, 9, api_caller_void_args_p, "SaveGlobalState" }, // pfnSaveGlobalState + { mFALSE, 9, api_caller_void_args_p, "RestoreGlobalState" }, // pfnRestoreGlobalState + { mFALSE, 9, api_caller_void_args_void, "ResetGlobalState" }, // pfnResetGlobalState + { mFALSE, 3, api_caller_int_args_4p, "ClientConnect" }, // pfnClientConnect + { mFALSE, 3, api_caller_void_args_p, "ClientDisconnect" }, // pfnClientDisconnect + { mFALSE, 3, api_caller_void_args_p, "ClientKill" }, // pfnClientKill + { mFALSE, 3, api_caller_void_args_p, "ClientPutInServer" }, // pfnClientPutInServer + { mFALSE, 9, api_caller_void_args_p, "ClientCommand" }, // pfnClientCommand + { mFALSE, 11, api_caller_void_args_2p, "ClientUserInfoChanged" }, // pfnClientUserInfoChanged + { mFALSE, 3, api_caller_void_args_p2i, "ServerActivate" }, // pfnServerActivate + { mFALSE, 3, api_caller_void_args_void, "ServerDeactivate" }, // pfnServerDeactivate + { mFALSE, 14, api_caller_void_args_p, "PlayerPreThink" }, // pfnPlayerPreThink + { mFALSE, 14, api_caller_void_args_p, "PlayerPostThink" }, // pfnPlayerPostThink + { mFALSE, 18, api_caller_void_args_void, "StartFrame" }, // pfnStartFrame + { mFALSE, 9, api_caller_void_args_void, "ParmsNewLevel" }, // pfnParmsNewLevel + { mFALSE, 9, api_caller_void_args_void, "ParmsChangeLevel" }, // pfnParmsChangeLevel + { mFALSE, 9, api_caller_ptr_args_void, "GetGameDescription" }, // pfnGetGameDescription + { mFALSE, 9, api_caller_void_args_2p, "PlayerCustomization" }, // pfnPlayerCustomization + { mFALSE, 9, api_caller_void_args_p, "SpectatorConnect" }, // pfnSpectatorConnect + { mFALSE, 9, api_caller_void_args_p, "SpectatorDisconnect" }, // pfnSpectatorDisconnect + { mFALSE, 9, api_caller_void_args_p, "SpectatorThink" }, // pfnSpectatorThink + { mFALSE, 3, api_caller_void_args_p, "Sys_Error" }, // pfnSys_Error + { mFALSE, 13, api_caller_void_args_pi, "PM_Move" }, // pfnPM_Move + { mFALSE, 9, api_caller_void_args_p, "PM_Init" }, // pfnPM_Init + { mFALSE, 9, api_caller_char_args_p, "PM_FindTextureType" }, // pfnPM_FindTextureType + { mFALSE, 12, api_caller_void_args_4p, "SetupVisibility" }, // pfnSetupVisibility + { mFALSE, 12, api_caller_void_args_pip, "UpdateClientData" }, // pfnUpdateClientData + { mFALSE, 16, api_caller_int_args_pi2p2ip, "AddToFullPack" }, // pfnAddToFullPack + { mFALSE, 9, api_caller_void_args_2i2pi2p, "CreateBaseline" }, // pfnCreateBaseline + { mFALSE, 9, api_caller_void_args_void, "RegisterEncoders" }, // pfnRegisterEncoders + { mFALSE, 9, api_caller_int_args_2p, "GetWeaponData" }, // pfnGetWeaponData + { mFALSE, 15, api_caller_void_args_2pui, "CmdStart" }, // pfnCmdStart + { mFALSE, 15, api_caller_void_args_p, "CmdEnd" }, // pfnCmdEnd + { mFALSE, 9, api_caller_int_args_4p, "ConnectionlessPacket" }, // pfnConnectionlessPacket + { mFALSE, 9, api_caller_int_args_i2p, "GetHullBounds" }, // pfnGetHullBounds + { mFALSE, 9, api_caller_void_args_void, "CreateInstancedBaselines" }, // pfnCreateInstancedBaselines + { mFALSE, 3, api_caller_int_args_3p, "InconsistentFile" }, // pfnInconsistentFile + { mFALSE, 20, api_caller_int_args_void, "AllowLagCompensation" }, // pfnAllowLagCompensation + { mFALSE, 0, NULL, NULL }, +}; + +const newapi_info_t newapi_info = { + { mFALSE, 16, api_caller_void_args_p, "OnFreeEntPrivateData" }, // pfnOnFreeEntPrivateData + { mFALSE, 3, api_caller_void_args_void, "GameShutdown" }, // pfnGameShutdown + { mFALSE, 14, api_caller_int_args_2p, "ShouldCollide" }, // pfnShouldCollide + // Added 2005/08/11 (no SDK update): + { mFALSE, 3, api_caller_void_args_2p, "CvarValue" }, // pfnCvarValue + // Added 2005/11/21 (no SDK update): + { mFALSE, 3, api_caller_void_args_pi2p, "CvarValue2" }, // pfnCvarValue2 + { mFALSE, 0, NULL, NULL }, +}; + +const engine_info_t engine_info = { + { mFALSE, 13, api_caller_int_args_p, "PrecacheModel" }, // pfnPrecacheModel + { mFALSE, 13, api_caller_int_args_p, "PrecacheSound" }, // pfnPrecacheSound + { mFALSE, 18, api_caller_void_args_2p, "SetModel" }, // pfnSetModel + { mFALSE, 34, api_caller_int_args_p, "ModelIndex" }, // pfnModelIndex + { mFALSE, 10, api_caller_int_args_i, "ModelFrames" }, // pfnModelFrames + { mFALSE, 14, api_caller_void_args_3p, "SetSize" }, // pfnSetSize + { mFALSE, 9, api_caller_void_args_2p, "ChangeLevel" }, // pfnChangeLevel + { mFALSE, 9, api_caller_void_args_p, "GetSpawnParms" }, // pfnGetSpawnParms + { mFALSE, 9, api_caller_void_args_p, "SaveSpawnParms" }, // pfnSaveSpawnParms + { mFALSE, 9, api_caller_float_args_p, "VecToYaw" }, // pfnVecToYaw + { mFALSE, 14, api_caller_void_args_2p, "VecToAngles" }, // pfnVecToAngles + { mFALSE, 9, api_caller_void_args_2pfi, "MoveToOrigin" }, // pfnMoveToOrigin + { mFALSE, 9, api_caller_void_args_p, "ChangeYaw" }, // pfnChangeYaw + { mFALSE, 9, api_caller_void_args_p, "ChangePitch" }, // pfnChangePitch + { mFALSE, 32, api_caller_ptr_args_3p, "FindEntityByString" }, // pfnFindEntityByString + { mFALSE, 9, api_caller_int_args_p, "GetEntityIllum" }, // pfnGetEntityIllum + { mFALSE, 9, api_caller_ptr_args_2pf, "FindEntityInSphere" }, // pfnFindEntityInSphere + { mFALSE, 19, api_caller_ptr_args_p, "FindClientInPVS" }, // pfnFindClientInPVS + { mFALSE, 9, api_caller_ptr_args_p, "EntitiesInPVS" }, // pfnEntitiesInPVS + { mFALSE, 40, api_caller_void_args_p, "MakeVectors" }, // pfnMakeVectors + { mFALSE, 9, api_caller_void_args_4p, "AngleVectors" }, // pfnAngleVectors + { mFALSE, 13, api_caller_ptr_args_void, "CreateEntity" }, // pfnCreateEntity + { mFALSE, 13, api_caller_void_args_p, "RemoveEntity" }, // pfnRemoveEntity + { mFALSE, 13, api_caller_ptr_args_i, "CreateNamedEntity" }, // pfnCreateNamedEntity + { mFALSE, 9, api_caller_void_args_p, "MakeStatic" }, // pfnMakeStatic + { mFALSE, 9, api_caller_int_args_p, "EntIsOnFloor" }, // pfnEntIsOnFloor + { mFALSE, 9, api_caller_int_args_p, "DropToFloor" }, // pfnDropToFloor + { mFALSE, 9, api_caller_int_args_p2fi, "WalkMove" }, // pfnWalkMove + { mFALSE, 14, api_caller_void_args_2p, "SetOrigin" }, // pfnSetOrigin + { mFALSE, 12, api_caller_void_args_pip2f2i, "EmitSound" }, // pfnEmitSound + { mFALSE, 12, api_caller_void_args_3p2f2i, "EmitAmbientSound" }, // pfnEmitAmbientSound + { mFALSE, 20, api_caller_void_args_2pi2p, "TraceLine" }, // pfnTraceLine + { mFALSE, 9, api_caller_void_args_3p, "TraceToss" }, // pfnTraceToss + { mFALSE, 9, api_caller_int_args_3pi2p, "TraceMonsterHull" }, // pfnTraceMonsterHull + { mFALSE, 9, api_caller_void_args_2p2i2p, "TraceHull" }, // pfnTraceHull + { mFALSE, 9, api_caller_void_args_2pi2p, "TraceModel" }, // pfnTraceModel + { mFALSE, 15, api_caller_ptr_args_3p, "TraceTexture" }, // pfnTraceTexture // CS: when moving + { mFALSE, 9, api_caller_void_args_2pif2p, "TraceSphere" }, // pfnTraceSphere + { mFALSE, 9, api_caller_void_args_pfp, "GetAimVector" }, // pfnGetAimVector + { mFALSE, 9, api_caller_void_args_p, "ServerCommand" }, // pfnServerCommand + { mFALSE, 9, api_caller_void_args_void, "ServerExecute" }, // pfnServerExecute + { mFALSE, 11, api_caller_void_args_2pV, "engClientCommand" }, // pfnClientCommand // d'oh, ClientCommand in dllapi too + { mFALSE, 9, api_caller_void_args_2p2f, "ParticleEffect" }, // pfnParticleEffect + { mFALSE, 9, api_caller_void_args_ip, "LightStyle" }, // pfnLightStyle + { mFALSE, 9, api_caller_int_args_p, "DecalIndex" }, // pfnDecalIndex + { mFALSE, 15, api_caller_int_args_p, "PointContents" }, // pfnPointContents // CS: when moving + { mFALSE, 22, api_caller_void_args_2i2p, "MessageBegin" }, // pfnMessageBegin + { mFALSE, 22, api_caller_void_args_void, "MessageEnd" }, // pfnMessageEnd + { mFALSE, 30, api_caller_void_args_i, "WriteByte" }, // pfnWriteByte + { mFALSE, 23, api_caller_void_args_i, "WriteChar" }, // pfnWriteChar + { mFALSE, 24, api_caller_void_args_i, "WriteShort" }, // pfnWriteShort + { mFALSE, 23, api_caller_void_args_i, "WriteLong" }, // pfnWriteLong + { mFALSE, 23, api_caller_void_args_f, "WriteAngle" }, // pfnWriteAngle + { mFALSE, 23, api_caller_void_args_f, "WriteCoord" }, // pfnWriteCoord + { mFALSE, 25, api_caller_void_args_p, "WriteString" }, // pfnWriteString + { mFALSE, 23, api_caller_void_args_i, "WriteEntity" }, // pfnWriteEntity + { mFALSE, 9, api_caller_void_args_p, "CVarRegister" }, // pfnCVarRegister + { mFALSE, 21, api_caller_float_args_p, "CVarGetFloat" }, // pfnCVarGetFloat + { mFALSE, 9, api_caller_ptr_args_p, "CVarGetString" }, // pfnCVarGetString + { mFALSE, 10, api_caller_void_args_pf, "CVarSetFloat" }, // pfnCVarSetFloat + { mFALSE, 9, api_caller_void_args_2p, "CVarSetString" }, // pfnCVarSetString + { mFALSE, 15, api_caller_void_args_ipV, "AlertMessage" }, // pfnAlertMessage + { mFALSE, 17, api_caller_void_args_2pV, "EngineFprintf" }, // pfnEngineFprintf + { mFALSE, 14, api_caller_ptr_args_pi, "PvAllocEntPrivateData" }, // pfnPvAllocEntPrivateData + { mFALSE, 9, api_caller_ptr_args_p, "PvEntPrivateData" }, // pfnPvEntPrivateData + { mFALSE, 9, api_caller_void_args_p, "FreeEntPrivateData" }, // pfnFreeEntPrivateData + { mFALSE, 9, api_caller_ptr_args_i, "SzFromIndex" }, // pfnSzFromIndex + { mFALSE, 10, api_caller_int_args_p, "AllocString" }, // pfnAllocString + { mFALSE, 9, api_caller_ptr_args_p, "GetVarsOfEnt" }, // pfnGetVarsOfEnt + { mFALSE, 14, api_caller_ptr_args_i, "PEntityOfEntOffset" }, // pfnPEntityOfEntOffset + { mFALSE, 19, api_caller_int_args_p, "EntOffsetOfPEntity" }, // pfnEntOffsetOfPEntity + { mFALSE, 14, api_caller_int_args_p, "IndexOfEdict" }, // pfnIndexOfEdict + { mFALSE, 17, api_caller_ptr_args_i, "PEntityOfEntIndex" }, // pfnPEntityOfEntIndex + { mFALSE, 9, api_caller_ptr_args_p, "FindEntityByVars" }, // pfnFindEntityByVars + { mFALSE, 14, api_caller_ptr_args_p, "GetModelPtr" }, // pfnGetModelPtr + { mFALSE, 9, api_caller_int_args_pi, "RegUserMsg" }, // pfnRegUserMsg + { mFALSE, 9, api_caller_void_args_pf, "AnimationAutomove" }, // pfnAnimationAutomove + { mFALSE, 9, api_caller_void_args_pi2p, "GetBonePosition" }, // pfnGetBonePosition + { mFALSE, 9, api_caller_uint_args_p, "FunctionFromName" }, // pfnFunctionFromName + { mFALSE, 9, api_caller_ptr_args_ui, "NameForFunction" }, // pfnNameForFunction + { mFALSE, 9, api_caller_void_args_pip, "ClientPrintf" }, // pfnClientPrintf + { mFALSE, 9, api_caller_void_args_p, "ServerPrint" }, // pfnServerPrint + { mFALSE, 13, api_caller_ptr_args_void, "Cmd_Args" }, // pfnCmd_Args + { mFALSE, 13, api_caller_ptr_args_i, "Cmd_Argv" }, // pfnCmd_Argv + { mFALSE, 13, api_caller_int_args_void, "Cmd_Argc" }, // pfnCmd_Argc + { mFALSE, 9, api_caller_void_args_pi2p, "GetAttachment" }, // pfnGetAttachment + { mFALSE, 9, api_caller_void_args_p, "CRC32_Init" }, // pfnCRC32_Init + { mFALSE, 9, api_caller_void_args_2pi, "CRC32_ProcessBuffer" }, // pfnCRC32_ProcessBuffer + { mFALSE, 9, api_caller_void_args_puc, "CRC32_ProcessByte" }, // pfnCRC32_ProcessByte + { mFALSE, 9, api_caller_ulong_args_ul, "CRC32_Final" }, // pfnCRC32_Final + { mFALSE, 16, api_caller_int_args_2i, "RandomLong" }, // pfnRandomLong + { mFALSE, 14, api_caller_float_args_2f, "RandomFloat" }, // pfnRandomFloat // CS: when firing + { mFALSE, 14, api_caller_void_args_2p, "SetView" }, // pfnSetView + { mFALSE, 9, api_caller_float_args_void, "Time" }, // pfnTime + { mFALSE, 9, api_caller_void_args_p2f, "CrosshairAngle" }, // pfnCrosshairAngle + { mFALSE, 10, api_caller_ptr_args_2p, "LoadFileForMe" }, // pfnLoadFileForMe + { mFALSE, 10, api_caller_void_args_p, "FreeFile" }, // pfnFreeFile + { mFALSE, 9, api_caller_void_args_p, "EndSection" }, // pfnEndSection + { mFALSE, 9, api_caller_int_args_3p, "CompareFileTime" }, // pfnCompareFileTime + { mFALSE, 9, api_caller_void_args_p, "GetGameDir" }, // pfnGetGameDir + { mFALSE, 9, api_caller_void_args_p, "Cvar_RegisterVariable" }, // pfnCvar_RegisterVariable + { mFALSE, 9, api_caller_void_args_p4i, "FadeClientVolume" }, // pfnFadeClientVolume + { mFALSE, 14, api_caller_void_args_pf, "SetClientMaxspeed" }, // pfnSetClientMaxspeed + { mFALSE, 9, api_caller_ptr_args_p, "CreateFakeClient" }, // pfnCreateFakeClient + { mFALSE, 9, api_caller_void_args_2p3fus2uc, "RunPlayerMove" }, // pfnRunPlayerMove + { mFALSE, 9, api_caller_int_args_void, "NumberOfEntities" }, // pfnNumberOfEntities + { mFALSE, 17, api_caller_ptr_args_p, "GetInfoKeyBuffer" }, // pfnGetInfoKeyBuffer + { mFALSE, 13, api_caller_ptr_args_2p, "InfoKeyValue" }, // pfnInfoKeyValue + { mFALSE, 9, api_caller_void_args_3p, "SetKeyValue" }, // pfnSetKeyValue + { mFALSE, 12, api_caller_void_args_i3p, "SetClientKeyValue" }, // pfnSetClientKeyValue + { mFALSE, 9, api_caller_int_args_p, "IsMapValid" }, // pfnIsMapValid + { mFALSE, 9, api_caller_void_args_p3i, "StaticDecal" }, // pfnStaticDecal + { mFALSE, 9, api_caller_int_args_p, "PrecacheGeneric" }, // pfnPrecacheGeneric + { mFALSE, 10, api_caller_int_args_p, "GetPlayerUserId" }, // pfnGetPlayerUserId + { mFALSE, 9, api_caller_void_args_pip2f4i2p, "BuildSoundMsg" }, // pfnBuildSoundMsg + { mFALSE, 9, api_caller_int_args_void, "IsDedicatedServer" }, // pfnIsDedicatedServer + { mFALSE, 9, api_caller_ptr_args_p, "CVarGetPointer" }, // pfnCVarGetPointer + { mFALSE, 9, api_caller_uint_args_p, "GetPlayerWONId" }, // pfnGetPlayerWONId + { mFALSE, 9, api_caller_void_args_2p, "Info_RemoveKey" }, // pfnInfo_RemoveKey + { mFALSE, 15, api_caller_ptr_args_2p, "GetPhysicsKeyValue" }, // pfnGetPhysicsKeyValue + { mFALSE, 14, api_caller_void_args_3p, "SetPhysicsKeyValue" }, // pfnSetPhysicsKeyValue + { mFALSE, 15, api_caller_ptr_args_p, "GetPhysicsInfoString" }, // pfnGetPhysicsInfoString + { mFALSE, 13, api_caller_ushort_args_ip, "PrecacheEvent" }, // pfnPrecacheEvent + { mFALSE, 9, api_caller_void_args_ipusf2p2f4i,"PlaybackEvent" }, // pfnPlaybackEvent + { mFALSE, 31, api_caller_ptr_args_p, "SetFatPVS" }, // pfnSetFatPVS + { mFALSE, 31, api_caller_ptr_args_p, "SetFatPAS" }, // pfnSetFatPAS + { mFALSE, 50, api_caller_int_args_2p, "CheckVisibility" }, // pfnCheckVisibility + { mFALSE, 37, api_caller_void_args_2p, "DeltaSetField" }, // pfnDeltaSetField + { mFALSE, 38, api_caller_void_args_2p, "DeltaUnsetField" }, // pfnDeltaUnsetField + { mFALSE, 9, api_caller_void_args_2p, "DeltaAddEncoder" }, // pfnDeltaAddEncoder + { mFALSE, 45, api_caller_int_args_void, "GetCurrentPlayer" }, // pfnGetCurrentPlayer + { mFALSE, 14, api_caller_int_args_p, "CanSkipPlayer" }, // pfnCanSkipPlayer + { mFALSE, 9, api_caller_int_args_2p, "DeltaFindField" }, // pfnDeltaFindField + { mFALSE, 37, api_caller_void_args_pi, "DeltaSetFieldByIndex" }, // pfnDeltaSetFieldByIndex + { mFALSE, 38, api_caller_void_args_pi, "DeltaUnsetFieldByIndex" }, // pfnDeltaUnsetFieldByIndex + { mFALSE, 9, api_caller_void_args_2i, "SetGroupMask" }, // pfnSetGroupMask + { mFALSE, 9, api_caller_int_args_ip, "engCreateInstancedBaseline" }, // pfnCreateInstancedBaseline // d'oh, CreateInstancedBaseline in dllapi too + { mFALSE, 9, api_caller_void_args_2p, "Cvar_DirectSet" }, // pfnCvar_DirectSet + { mFALSE, 9, api_caller_void_args_i3p, "ForceUnmodified" }, // pfnForceUnmodified + { mFALSE, 9, api_caller_void_args_3p, "GetPlayerStats" }, // pfnGetPlayerStats + { mFALSE, 3, api_caller_void_args_2p, "AddServerCommand" }, // pfnAddServerCommand + // Added in SDK 2.2: + { mFALSE, 9, api_caller_int_args_2i, "Voice_GetClientListening" }, // Voice_GetClientListening + { mFALSE, 9, api_caller_int_args_3i, "Voice_SetClientListening" }, // Voice_SetClientListening + // Added for HL 1109 (no SDK update): + { mFALSE, 9, api_caller_ptr_args_p, "GetPlayerAuthId" }, // pfnGetPlayerAuthId + // Added 2003/11/10 (no SDK update): + { mFALSE, 30, api_caller_ptr_args_2p, "SequenceGet" }, // pfnSequenceGet + { mFALSE, 30, api_caller_ptr_args_pip, "SequencePickSentence" }, // pfnSequencePickSentence + { mFALSE, 30, api_caller_int_args_p, "GetFileSize" }, // pfnGetFileSize + { mFALSE, 30, api_caller_uint_args_p, "GetApproxWavePlayLen" }, // pfnGetApproxWavePlayLen + { mFALSE, 30, api_caller_int_args_void, "IsCareerMatch" }, // pfnIsCareerMatch + { mFALSE, 30, api_caller_int_args_p, "GetLocalizedStringLength" }, // pfnGetLocalizedStringLength + { mFALSE, 30, api_caller_void_args_i, "RegisterTutorMessageShown" }, // pfnRegisterTutorMessageShown + { mFALSE, 30, api_caller_int_args_i, "GetTimesTutorMessageShown" }, // pfnGetTimesTutorMessageShown + { mFALSE, 30, api_caller_void_args_pi, "ProcessTutorMessageDecayBuffer" }, // pfnProcessTutorMessageDecayBuffer + { mFALSE, 30, api_caller_void_args_pi, "ConstructTutorMessageDecayBuffer" }, // pfnConstructTutorMessageDecayBuffer + { mFALSE, 9, api_caller_void_args_void, "ResetTutorMessageDecayData" }, // pfnResetTutorMessageDecayData + // Added 2005/08/11 (no SDK update): + { mFALSE, 3, api_caller_void_args_2p, "QueryClientCvarValue" }, // pfnQueryClientCvarValue + // Added 2005/11/21 (no SDK update): + { mFALSE, 3, api_caller_void_args_2pi, "QueryClientCvarValue2" }, // pfnQueryClientCvarValue2 + // Added 2009-06-17 (no SDK update): + { mFALSE, 8, api_caller_int_args_2p, "EngCheckParm" }, // pfnEngCheckParm + // end + { mFALSE, 0, NULL, NULL }, +}; diff --git a/src/metamod/api_info.h b/src/metamod/api_info.h new file mode 100644 index 0000000..9c1f590 --- /dev/null +++ b/src/metamod/api_info.h @@ -0,0 +1,312 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// api_info.h - structures to store info about api routines + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef API_INFO_H +#define API_INFO_H + +#include "comp_dep.h" +#include "types_meta.h" // mBOOL +#include "ret_type.h" + + +#define P_PRE 0 // plugin function called before gamedll +#define P_POST 1 // plugin function called after gamedll + + +// API selector +typedef enum enum_api_t { + e_api_engine = 0, + e_api_dllapi = 1, + e_api_newapi = 2, +} enum_api_t; + +// API caller function prototype +typedef void * (DLLINTERNAL_NOVIS * api_caller_func_t)(const void * func, const void * packed_args); + + +typedef struct api_info_s { + mBOOL trace; // if true, log info about this function + int loglevel; // level at which to log info about this function + api_caller_func_t api_caller; // argument format/type for single-main-hook-function optimization + const char *name; // string representation of function name +} api_info_t; + + +// DLL api functions +typedef struct dllapi_info_s { + api_info_t pfnGameInit; + api_info_t pfnSpawn; + api_info_t pfnThink; + api_info_t pfnUse; + api_info_t pfnTouch; + api_info_t pfnBlocked; + api_info_t pfnKeyValue; + api_info_t pfnSave; + api_info_t pfnRestore; + api_info_t pfnSetAbsBox; + api_info_t pfnSaveWriteFields; + api_info_t pfnSaveReadFields; + api_info_t pfnSaveGlobalState; + api_info_t pfnRestoreGlobalState; + api_info_t pfnResetGlobalState; + api_info_t pfnClientConnect; + api_info_t pfnClientDisconnect; + api_info_t pfnClientKill; + api_info_t pfnClientPutInServer; + api_info_t pfnClientCommand; + api_info_t pfnClientUserInfoChanged; + api_info_t pfnServerActivate; + api_info_t pfnServerDeactivate; + api_info_t pfnPlayerPreThink; + api_info_t pfnPlayerPostThink; + api_info_t pfnStartFrame; + api_info_t pfnParmsNewLevel; + api_info_t pfnParmsChangeLevel; + api_info_t pfnGetGameDescription; + api_info_t pfnPlayerCustomization; + api_info_t pfnSpectatorConnect; + api_info_t pfnSpectatorDisconnect; + api_info_t pfnSpectatorThink; + api_info_t pfnSys_Error; + api_info_t pfnPM_Move; + api_info_t pfnPM_Init; + api_info_t pfnPM_FindTextureType; + api_info_t pfnSetupVisibility; + api_info_t pfnUpdateClientData; + api_info_t pfnAddToFullPack; + api_info_t pfnCreateBaseline; + api_info_t pfnRegisterEncoders; + api_info_t pfnGetWeaponData; + api_info_t pfnCmdStart; + api_info_t pfnCmdEnd; + api_info_t pfnConnectionlessPacket; + api_info_t pfnGetHullBounds; + api_info_t pfnCreateInstancedBaselines; + api_info_t pfnInconsistentFile; + api_info_t pfnAllowLagCompensation; + api_info_t END; +} dllapi_info_t; + + +// "New" api functions +typedef struct newapi_info_s { + api_info_t pfnOnFreeEntPrivateData; + api_info_t pfnGameShutdown; + api_info_t pfnShouldCollide; + // Added 2005/08/11 (no SDK update): + api_info_t pfnCvarValue; + // Added 2005/11/21 (no SDK update): + api_info_t pfnCvarValue2; + api_info_t END; +} newapi_info_t; + + +// Engine functions +typedef struct engine_info_s { + api_info_t pfnPrecacheModel; + api_info_t pfnPrecacheSound; + api_info_t pfnSetModel; + api_info_t pfnModelIndex; + api_info_t pfnModelFrames; + api_info_t pfnSetSize; + api_info_t pfnChangeLevel; + api_info_t pfnGetSpawnParms; + api_info_t pfnSaveSpawnParms; + api_info_t pfnVecToYaw; + api_info_t pfnVecToAngles; + api_info_t pfnMoveToOrigin; + api_info_t pfnChangeYaw; + api_info_t pfnChangePitch; + api_info_t pfnFindEntityByString; + api_info_t pfnGetEntityIllum; + api_info_t pfnFindEntityInSphere; + api_info_t pfnFindClientInPVS; + api_info_t pfnEntitiesInPVS; + api_info_t pfnMakeVectors; + api_info_t pfnAngleVectors; + api_info_t pfnCreateEntity; + api_info_t pfnRemoveEntity; + api_info_t pfnCreateNamedEntity; + api_info_t pfnMakeStatic; + api_info_t pfnEntIsOnFloor; + api_info_t pfnDropToFloor; + api_info_t pfnWalkMove; + api_info_t pfnSetOrigin; + api_info_t pfnEmitSound; + api_info_t pfnEmitAmbientSound; + api_info_t pfnTraceLine; + api_info_t pfnTraceToss; + api_info_t pfnTraceMonsterHull; + api_info_t pfnTraceHull; + api_info_t pfnTraceModel; + api_info_t pfnTraceTexture; + api_info_t pfnTraceSphere; + api_info_t pfnGetAimVector; + api_info_t pfnServerCommand; + api_info_t pfnServerExecute; + api_info_t pfnClientCommand; + api_info_t pfnParticleEffect; + api_info_t pfnLightStyle; + api_info_t pfnDecalIndex; + api_info_t pfnPointContents; + api_info_t pfnMessageBegin; + api_info_t pfnMessageEnd; + api_info_t pfnWriteByte; + api_info_t pfnWriteChar; + api_info_t pfnWriteShort; + api_info_t pfnWriteLong; + api_info_t pfnWriteAngle; + api_info_t pfnWriteCoord; + api_info_t pfnWriteString; + api_info_t pfnWriteEntity; + api_info_t pfnCVarRegister; + api_info_t pfnCVarGetFloat; + api_info_t pfnCVarGetString; + api_info_t pfnCVarSetFloat; + api_info_t pfnCVarSetString; + api_info_t pfnAlertMessage; + api_info_t pfnEngineFprintf; + api_info_t pfnPvAllocEntPrivateData; + api_info_t pfnPvEntPrivateData; + api_info_t pfnFreeEntPrivateData; + api_info_t pfnSzFromIndex; + api_info_t pfnAllocString; + api_info_t pfnGetVarsOfEnt; + api_info_t pfnPEntityOfEntOffset; + api_info_t pfnEntOffsetOfPEntity; + api_info_t pfnIndexOfEdict; + api_info_t pfnPEntityOfEntIndex; + api_info_t pfnFindEntityByVars; + api_info_t pfnGetModelPtr; + api_info_t pfnRegUserMsg; + api_info_t pfnAnimationAutomove; + api_info_t pfnGetBonePosition; + api_info_t pfnFunctionFromName; + api_info_t pfnNameForFunction; + api_info_t pfnClientPrintf; + api_info_t pfnServerPrint; + api_info_t pfnCmd_Args; + api_info_t pfnCmd_Argv; + api_info_t pfnCmd_Argc; + api_info_t pfnGetAttachment; + api_info_t pfnCRC32_Init; + api_info_t pfnCRC32_ProcessBuffer; + api_info_t pfnCRC32_ProcessByte; + api_info_t pfnCRC32_Final; + api_info_t pfnRandomLong; + api_info_t pfnRandomFloat; + api_info_t pfnSetView; + api_info_t pfnTime; + api_info_t pfnCrosshairAngle; + api_info_t pfnLoadFileForMe; + api_info_t pfnFreeFile; + api_info_t pfnEndSection; + api_info_t pfnCompareFileTime; + api_info_t pfnGetGameDir; + api_info_t pfnCvar_RegisterVariable; + api_info_t pfnFadeClientVolume; + api_info_t pfnSetClientMaxspeed; + api_info_t pfnCreateFakeClient; + api_info_t pfnRunPlayerMove; + api_info_t pfnNumberOfEntities; + api_info_t pfnGetInfoKeyBuffer; + api_info_t pfnInfoKeyValue; + api_info_t pfnSetKeyValue; + api_info_t pfnSetClientKeyValue; + api_info_t pfnIsMapValid; + api_info_t pfnStaticDecal; + api_info_t pfnPrecacheGeneric; + api_info_t pfnGetPlayerUserId; + api_info_t pfnBuildSoundMsg; + api_info_t pfnIsDedicatedServer; + api_info_t pfnCVarGetPointer; + api_info_t pfnGetPlayerWONId; + api_info_t pfnInfo_RemoveKey; + api_info_t pfnGetPhysicsKeyValue; + api_info_t pfnSetPhysicsKeyValue; + api_info_t pfnGetPhysicsInfoString; + api_info_t pfnPrecacheEvent; + api_info_t pfnPlaybackEvent; + api_info_t pfnSetFatPVS; + api_info_t pfnSetFatPAS; + api_info_t pfnCheckVisibility; + api_info_t pfnDeltaSetField; + api_info_t pfnDeltaUnsetField; + api_info_t pfnDeltaAddEncoder; + api_info_t pfnGetCurrentPlayer; + api_info_t pfnCanSkipPlayer; + api_info_t pfnDeltaFindField; + api_info_t pfnDeltaSetFieldByIndex; + api_info_t pfnDeltaUnsetFieldByIndex; + api_info_t pfnSetGroupMask; + api_info_t pfnCreateInstancedBaseline; + api_info_t pfnCvar_DirectSet; + api_info_t pfnForceUnmodified; + api_info_t pfnGetPlayerStats; + api_info_t pfnAddServerCommand; + // Added in SDK 2.2: + api_info_t pfnVoice_GetClientListening; + api_info_t pfnVoice_SetClientListening; + // Added for HL 1109 (no SDK update): + api_info_t pfnGetPlayerAuthId; + // Added 2003/11/10 (no SDK update): + api_info_t pfnSequenceGet; + api_info_t pfnSequencePickSentence; + api_info_t pfnGetFileSize; + api_info_t pfnGetApproxWavePlayLen; + api_info_t pfnIsCareerMatch; + api_info_t pfnGetLocalizedStringLength; + api_info_t pfnRegisterTutorMessageShown; + api_info_t pfnGetTimesTutorMessageShown; + api_info_t pfnProcessTutorMessageDecayBuffer; + api_info_t pfnConstructTutorMessageDecayBuffer; + api_info_t pfnResetTutorMessageDecayData; + // Added 2005/08/11 (no SDK update): + api_info_t pfnQueryClientCvarValue; + // Added 2005/11/21 (no SDK update): + api_info_t pfnQueryClientCvarValue2; + // Added 2009/06/17 (no SDK update): + api_info_t pfnEngCheckParm; + // end + api_info_t END; +} engine_info_t; + + +extern const dllapi_info_t dllapi_info DLLHIDDEN; +extern const newapi_info_t newapi_info DLLHIDDEN; +extern const engine_info_t engine_info DLLHIDDEN; + +#endif /* API_INFO_H */ diff --git a/src/metamod/build_all.sh b/src/metamod/build_all.sh new file mode 100644 index 0000000..cdf8768 --- /dev/null +++ b/src/metamod/build_all.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +make HOST=cygwin TARGET=win32 OPT=opt +make HOST=cygwin OPT=opt +make HOST=cygwin TARGET=amd64 OPT=opt diff --git a/src/metamod/commands_meta.cpp b/src/metamod/commands_meta.cpp new file mode 100644 index 0000000..8e182b1 --- /dev/null +++ b/src/metamod/commands_meta.cpp @@ -0,0 +1,513 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// commands_meta.cpp - implementation of various console commands + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // strtol() + +#include // always + +#include "commands_meta.h" // me +#include "metamod.h" // Plugins, etc +#include "log_meta.h" // META_CONS, etc +#include "info_name.h" // VNAME, etc +#include "vdate.h" // COMPILE_TIME, COMPILE_TZONE + + +#ifdef META_PERFMON + +long double total_tsc=0; +unsigned long long count_tsc=0; +unsigned long long active_tsc=0; +unsigned long long min_tsc=0; + +void DLLINTERNAL cmd_meta_tsc(void) { + if(!count_tsc) + return; + + META_CONS(" "); + META_CONS(" count_tsc: %.0f", (double)count_tsc); + META_CONS(" mean_tsc: %.1f", (double)(total_tsc / count_tsc)); + META_CONS(" min_tsc: %.0f", (double)min_tsc); +} + +void DLLINTERNAL cmd_meta_reset_tsc(void) { + total_tsc=0; + count_tsc=0; + min_tsc=0; +} +#endif /*META_PERFMON*/ + +// Register commands and cvars. +void DLLINTERNAL meta_register_cmdcvar() { + CVAR_REGISTER(&meta_debug); + CVAR_REGISTER(&meta_version); + + meta_debug_value = (int)meta_debug.value; + + REG_SVR_COMMAND("meta", svr_meta); +} + +// Parse "meta" console command. +void DLLHIDDEN svr_meta(void) { + const char *cmd; + cmd=CMD_ARGV(1); + // arguments: none + if(!strcasecmp(cmd, "version")) + cmd_meta_version(); + else if(!strcasecmp(cmd, "gpl")) + cmd_meta_gpl(); + else if(!strcasecmp(cmd, "refresh")) + cmd_meta_refresh(); + else if(!strcasecmp(cmd, "list")) + cmd_meta_pluginlist(); + else if(!strcasecmp(cmd, "cmds")) + cmd_meta_cmdlist(); + else if(!strcasecmp(cmd, "cvars")) + cmd_meta_cvarlist(); + else if(!strcasecmp(cmd, "game")) + cmd_meta_game(); + else if(!strcasecmp(cmd, "config")) + cmd_meta_config(); + // arguments: existing plugin(s) + else if(!strcasecmp(cmd, "pause")) + cmd_doplug(PC_PAUSE); + else if(!strcasecmp(cmd, "unpause")) + cmd_doplug(PC_UNPAUSE); + else if(!strcasecmp(cmd, "unload")) + cmd_doplug(PC_UNLOAD); + else if(!strcasecmp(cmd, "force_unload")) + cmd_doplug(PC_FORCE_UNLOAD); + else if(!strcasecmp(cmd, "reload")) + cmd_doplug(PC_RELOAD); + else if(!strcasecmp(cmd, "retry")) + cmd_doplug(PC_RETRY); + else if(!strcasecmp(cmd, "clear")) + cmd_doplug(PC_CLEAR); + else if(!strcasecmp(cmd, "info")) + cmd_doplug(PC_INFO); + else if(!strcasecmp(cmd, "require")) + cmd_doplug(PC_REQUIRE); + // arguments: filename, description + else if(!strcasecmp(cmd, "load")) + cmd_meta_load(); +#ifdef META_PERFMON + else if(!strcasecmp(cmd, "tsc")) + cmd_meta_tsc(); + else if(!strcasecmp(cmd, "reset_tsc")) + cmd_meta_reset_tsc(); +#endif /*META_PERFMON*/ + // unrecognized + else { + META_CONS("Unrecognized meta command: %s", cmd); + cmd_meta_usage(); + return; + } +} + +// Parse "meta" client command. +void DLLINTERNAL client_meta(edict_t *pEntity) { + const char *cmd; + cmd=CMD_ARGV(1); + META_LOG("ClientCommand 'meta %s' from player '%s'", + CMD_ARGS(), STRING(pEntity->v.netname)); + // arguments: none + if(strmatch(cmd, "version")) + client_meta_version(pEntity); + else if(strmatch(cmd, "list")) + client_meta_pluginlist(pEntity); + else if(strmatch(cmd, "aybabtu")) + client_meta_aybabtu(pEntity); + // unrecognized + else { + META_CLIENT(pEntity, "Unrecognized meta command: %s", cmd); + client_meta_usage(pEntity); + return; + } +} + +// Print usage for "meta" console command. +void DLLINTERNAL cmd_meta_usage(void) { + META_CONS("usage: meta []"); + META_CONS("valid commands are:"); + META_CONS(" version - display metamod version info"); + META_CONS(" game - display gamedll info"); + META_CONS(" list - list plugins currently loaded"); + META_CONS(" cmds - list console cmds registered by plugins"); + META_CONS(" cvars - list cvars registered by plugins"); + META_CONS(" refresh - load/unload any new/deleted/updated plugins"); + META_CONS(" config - show config info loaded from config.ini"); + META_CONS(" load - find and load a plugin with the given name"); + META_CONS(" unload - unload a loaded plugin"); + META_CONS(" reload - unload a plugin and load it again"); + META_CONS(" info - show all information about a plugin"); + META_CONS(" pause - pause a loaded, running plugin"); + META_CONS(" unpause - unpause a previously paused plugin"); + META_CONS(" retry - retry a plugin that previously failed its action"); + META_CONS(" clear - clear a failed plugin from the list"); + META_CONS(" force_unload - forcibly unload a loaded plugin"); + META_CONS(" require - exit server if plugin not loaded/running"); +} + +// Print usage for "meta" client command. +void DLLINTERNAL client_meta_usage(edict_t *pEntity) { + META_CLIENT(pEntity, "usage: meta []"); + META_CLIENT(pEntity, "valid commands are:"); + META_CLIENT(pEntity, " version - display metamod version info"); + META_CLIENT(pEntity, " list - list plugins currently loaded"); +} + +// "meta aybabtu" client command. +void DLLINTERNAL client_meta_aybabtu(edict_t *pEntity) { + META_CLIENT(pEntity, "%s", "All Your Base Are Belong To Us"); +} + +// "meta version" console command. +void DLLINTERNAL cmd_meta_version(void) { + if(CMD_ARGC() != 2) { + META_CONS("usage: meta version"); + return; + } + META_CONS("%s v%s %s (%s)", VNAME, VVERSION, VDATE, META_INTERFACE_VERSION); + META_CONS("by %s", VAUTHOR); + META_CONS(" %s", VURL); + META_CONS(" Patch: %s v%d", VPATCH_NAME, VPATCH_IVERSION); + META_CONS(" by %s", VPATCH_AUTHOR); + META_CONS(" %s", VPATCH_WEBSITE); + META_CONS("compiled: %s %s (%s)", COMPILE_TIME, COMPILE_TZONE, OPT_TYPE); +} + +// "meta version" client command. +void DLLINTERNAL client_meta_version(edict_t *pEntity) { + if(CMD_ARGC() != 2) { + META_CLIENT(pEntity, "usage: meta version"); + return; + } + META_CLIENT(pEntity, "%s v%s %s (%s)", VNAME, VVERSION, VDATE, META_INTERFACE_VERSION); + META_CLIENT(pEntity, "by %s", VAUTHOR); + META_CLIENT(pEntity, " %s", VURL); + META_CLIENT(pEntity, " Patch: %s v%d", VPATCH_NAME, VPATCH_IVERSION); + META_CLIENT(pEntity, " by %s", VPATCH_AUTHOR); + META_CLIENT(pEntity, " %s", VPATCH_WEBSITE); + META_CLIENT(pEntity, "compiled: %s %s (%s)", COMPILE_TIME, COMPILE_TZONE, OPT_TYPE); + META_CLIENT(pEntity, "ifvers: %s", META_INTERFACE_VERSION); +} + +// "meta gpl" console command. +void DLLINTERNAL cmd_meta_gpl(void) { + META_CONS("%s version %s %s", VNAME, VVERSION, VDATE); + META_CONS("Copyright (c) 2001-%s %s", COPYRIGHT_YEAR, VAUTHOR); + META_CONS(""); + META_CONS(" %s is free software; you can redistribute it and/or", VNAME); + META_CONS(" modify it under the terms of the GNU General Public License"); + META_CONS(" as published by the Free Software Foundation; either"); + META_CONS(" version 2 of the License, or (at your option) any later"); + META_CONS(" version."); + META_CONS(" "); + META_CONS(" %s is distributed in the hope that it will be useful,", VNAME); + META_CONS(" but WITHOUT ANY WARRANTY; without even the implied warranty"); + META_CONS(" of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."); + META_CONS(" See the GNU General Public License for more details."); + META_CONS(" "); + META_CONS(" You should have received a copy of the GNU General Public"); + META_CONS(" License along with Metamod; if not, write to the Free"); + META_CONS(" Software Foundation, Inc., 59 Temple Place, Suite 330,"); + META_CONS(" Boston, MA 02111-1307 USA"); + META_CONS(" "); + META_CONS(" In addition, as a special exception, the author gives"); + META_CONS(" permission to link the code of this program with the"); + META_CONS(" Half-Life Game Engine (\"HL Engine\") and Modified Game"); + META_CONS(" Libraries (\"MODs\") developed by Valve, L.L.C (\"Valve\")."); + META_CONS(" You must obey the GNU General Public License in all"); + META_CONS(" respects for all of the code used other than the HL Engine"); + META_CONS(" and MODs from Valve. If you modify this file, you may"); + META_CONS(" extend this exception to your version of the file, but you"); + META_CONS(" are not obligated to do so. If you do not wish to do so,"); + META_CONS(" delete this exception statement from your version."); +} + +// "meta game" console command. +void DLLINTERNAL cmd_meta_game(void) { + if(CMD_ARGC() != 2) { + META_CONS("usage: meta game"); + return; + } + META_CONS("GameDLL info:"); + META_CONS(" name: %s", GameDLL.name); + META_CONS(" desc: %s", GameDLL.desc); + META_CONS(" gamedir: %s", GameDLL.gamedir); + META_CONS(" dll file: %s", GameDLL.file); + META_CONS("dll pathname: %s", GameDLL.pathname); + RegMsgs->show(); +} + +// "meta refresh" console command. +void DLLINTERNAL cmd_meta_refresh(void) { + if(CMD_ARGC() != 2) { + META_CONS("usage: meta refresh"); + return; + } + META_LOG("Refreshing the plugins on demand..."); + if(Plugins->refresh(PT_ANYTIME) != mTRUE) { + META_LOG("Refresh failed."); + } +} + +// "meta list" console command. +void DLLINTERNAL cmd_meta_pluginlist(void) { + if(CMD_ARGC() != 2) { + META_CONS("usage: meta list"); + return; + } + Plugins->show(); +} + +// "meta list" client command. +void DLLINTERNAL client_meta_pluginlist(edict_t *pEntity) { + if(CMD_ARGC() != 2) { + META_CLIENT(pEntity, "usage: meta list"); + return; + } + Plugins->show_client(pEntity); +} + +// "meta cmds" console command. +void DLLINTERNAL cmd_meta_cmdlist(void) { + if(CMD_ARGC() != 2) { + META_CONS("usage: meta cmds"); + return; + } + RegCmds->show(); +} + +// "meta cvars" console command. +void DLLINTERNAL cmd_meta_cvarlist(void) { + if(CMD_ARGC() != 2) { + META_CONS("usage: meta cvars"); + return; + } + RegCvars->show(); +} + +// "meta config" console command. +void DLLINTERNAL cmd_meta_config(void) { + if(CMD_ARGC() != 2) { + META_CONS("usage: meta cvars"); + return; + } + Config->show(); +} + +// gamedir/filename +// gamedir/dlls/filename +// +// dir/mm_file +// dir/file +// +// path +// path_mm +// path_MM +// path.so, path.dll +// path_i386.so, path_i486.so, etc + +// "meta load" console command. +void DLLINTERNAL cmd_meta_load(void) { + int argc; + const char *args; + argc=CMD_ARGC(); + if(argc < 3) { + META_CONS("usage: meta load []"); + META_CONS(" where is an identifier used to locate the plugin file."); + META_CONS(" The system will look for a number of files based on this name, including:"); + META_CONS(" name"); +#ifdef linux + META_CONS(" name.so"); + META_CONS(" name_mm.so"); + META_CONS(" name_MM.so"); + META_CONS(" mm_name.so"); +#ifdef __x86_64__ + META_CONS(" name_amd64.so"); + META_CONS(" name_x86_64.so"); +#else + META_CONS(" name_i386.so"); + META_CONS(" name_i686.so"); +#endif +#elif defined(_WIN32) + META_CONS(" name.dll"); + META_CONS(" name_mm.dll"); + META_CONS(" mm_name.dll"); +#endif /* linux */ + META_CONS(" in a number of directories, including:"); + META_CONS(" "); + META_CONS(" /dlls"); + META_CONS(" "); + return; + } + args=CMD_ARGS(); + // cmd_addload() handles all the feedback to the console.. + Plugins->cmd_addload(args); +} + +// Handle various console commands that refer to a known/loaded plugin. +void DLLINTERNAL cmd_doplug(PLUG_CMD pcmd) { + int i=0, argc; + const char *cmd, *arg; + MPlugin *findp; + + argc=CMD_ARGC(); + cmd=CMD_ARGV(1); + if(argc < 3) { + META_CONS("usage: meta %s [ ...]", cmd); + META_CONS(" where can be either the plugin index #"); + META_CONS(" or a non-ambiguous prefix string matching name, desc, file, or logtag"); + return; + } + // i=2 to skip first arg, as that's the "cmd" + for(i=2; i < argc; i++) { + int pindex; + char *endptr; + + arg=CMD_ARGV(i); + + // try to match plugin id first + pindex = strtol(arg, &endptr, 10); + if(*arg && !*endptr) + findp=Plugins->find(pindex); + // else try to match some string (prefix) + else + findp=Plugins->find_match(arg); + + // Require that: + // - specified plugin was found in the list of current plugins + // - plugin successfully loaded and began running + // Otherwise, print error and exit. + if(pcmd==PC_REQUIRE) { + if(findp && findp->status >= PL_RUNNING) { + META_DEBUG(3, ("Required plugin '%s' found loaded and running.", + arg)); + return; + } + // Output to both places, because we don't want the admin + // to miss this.. + if(!findp && meta_errno == ME_NOTUNIQ) { + META_ERROR("Unique match for required plugin '%s' was not found! Exiting.", arg); + META_CONS("\nERROR: Unique match for required plugin '%s' was not found! Exiting.\n", arg); + } + else if(!findp) { + META_ERROR("Required plugin '%s' was not found! Exiting.", + arg); + META_CONS("\nERROR: Required plugin '%s' was not found! Exiting.\n", + arg); + } + else { + META_ERROR("Required plugin '%s' did not load successfully! (status=%s) Exiting.", arg, findp->str_status(ST_SIMPLE)); + META_CONS("\nERROR: Required plugin '%s' did not load successfully! (status=%s) Exiting.\n", arg, findp->str_status(ST_SIMPLE)); + } + // Allow chance to read the message, before any window closes. + do_exit(1); + } + + if(!findp) { + if(meta_errno == ME_NOTUNIQ) + META_CONS("Couldn't find unique plugin matching '%s'", arg); + else + META_CONS("Couldn't find plugin matching '%s'", arg); + return; + } + + if(pcmd==PC_PAUSE) { + if(findp->pause()) + META_CONS("Paused plugin '%s'", findp->desc); + else + META_CONS("Pause failed for plugin '%s'", findp->desc); + } + else if(pcmd==PC_UNPAUSE) { + if(findp->unpause()) + META_CONS("Unpaused plugin '%s'", findp->desc); + else + META_CONS("Unpause failed for plugin '%s'", findp->desc); + } + else if(pcmd==PC_UNLOAD) { + findp->action=PA_UNLOAD; + if(findp->unload(PT_ANYTIME, PNL_COMMAND, PNL_COMMAND)) { + META_CONS("Unloaded plugin '%s'", findp->desc); + Plugins->show(); + } + else if(meta_errno == ME_DELAYED) + META_CONS("Unload delayed for plugin '%s'", findp->desc); + else + META_CONS("Unload failed for plugin '%s'", findp->desc); + } + else if(pcmd==PC_FORCE_UNLOAD) { + findp->action=PA_UNLOAD; + if(findp->unload(PT_ANYTIME, PNL_CMD_FORCED, PNL_CMD_FORCED)) { + META_CONS("Forced unload plugin '%s'", findp->desc); + Plugins->show(); + } + else + META_CONS("Forced unload failed for plugin '%s'", findp->desc); + } + else if(pcmd==PC_RELOAD) { + findp->action=PA_RELOAD; + if(findp->reload(PT_ANYTIME, PNL_COMMAND)) + META_CONS("Reloaded plugin '%s'", findp->desc); + else if(meta_errno == ME_DELAYED) + META_CONS("Reload delayed for plugin '%s'", findp->desc); + else if(meta_errno == ME_NOTALLOWED) + META_CONS("Reload not allowed for plugin '%s' now, only allowed %s", findp->desc, findp->str_loadable(SL_ALLOWED)); + else + META_CONS("Reload failed for plugin '%s'", findp->desc); + } + else if(pcmd==PC_RETRY) { + if(findp->retry(PT_ANYTIME, PNL_COMMAND)) + META_CONS("Retry succeeded for plugin '%s'", findp->desc); + else + META_CONS("Retry failed for plugin '%s'", findp->desc); + } + else if(pcmd==PC_CLEAR) { + if(findp->clear()) { + META_CONS("Cleared failed plugin '%s' from list", findp->desc); + Plugins->show(); + } + else + META_CONS("Clear failed for plugin '%s'", findp->desc); + } + else if(pcmd==PC_INFO) + findp->show(); + else { + META_WARNING("Unexpected plug_cmd: %d", pcmd); + META_CONS("Command failed; see log"); + } + } +} diff --git a/src/metamod/commands_meta.h b/src/metamod/commands_meta.h new file mode 100644 index 0000000..4001c8e --- /dev/null +++ b/src/metamod/commands_meta.h @@ -0,0 +1,83 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// commands_meta.h - prototypes for console commands + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef COMMANDS_META_H +#define COMMANDS_META_H + +#include "types_meta.h" // mBOOL +#include "comp_dep.h" + +// Flags to use for meta_cmd_doplug(), to operate on existing plugins; note +// "load" operates on a non-existing plugin thus isn't included here. +typedef enum { + PC_NULL = 0, + PC_PAUSE, // pause the plugin + PC_UNPAUSE, // unpause the plugin + PC_UNLOAD, // unload the plugin + PC_RELOAD, // unload the plugin and load it again + PC_RETRY, // retry a failed operation (usually load/attach) + PC_INFO, // show all info about the plugin + PC_CLEAR, // remove a failed plugin from the list + PC_FORCE_UNLOAD, // forcibly unload the plugin + PC_REQUIRE, // require that this plugin is loaded/running +} PLUG_CMD; + +void DLLINTERNAL meta_register_cmdcvar(); + +void DLLHIDDEN svr_meta(void); // only hidden because called from outside! + +void DLLINTERNAL cmd_meta_usage(void); +void DLLINTERNAL cmd_meta_version(void); +void DLLINTERNAL cmd_meta_gpl(void); + +void DLLINTERNAL cmd_meta_game(void); +void DLLINTERNAL cmd_meta_refresh(void); +void DLLINTERNAL cmd_meta_load(void); + +void DLLINTERNAL cmd_meta_pluginlist(void); +void DLLINTERNAL cmd_meta_cmdlist(void); +void DLLINTERNAL cmd_meta_cvarlist(void); +void DLLINTERNAL cmd_meta_config(void); + +void DLLINTERNAL cmd_doplug(PLUG_CMD pcmd); + +void DLLINTERNAL client_meta(edict_t *pEntity); +void DLLINTERNAL client_meta_usage(edict_t *pEntity); +void DLLINTERNAL client_meta_version(edict_t *pEntity); +void DLLINTERNAL client_meta_pluginlist(edict_t *pEntity); +void DLLINTERNAL client_meta_aybabtu(edict_t *pEntity); + +#endif /* COMMANDS_META_H */ diff --git a/src/metamod/comp_dep.h b/src/metamod/comp_dep.h new file mode 100644 index 0000000..92c585e --- /dev/null +++ b/src/metamod/comp_dep.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef COMP_DEP_H +#define COMP_DEP_H + +#define DECLSPEC(kw) +#if defined (_WIN32) && defined (_MSC_VER) + #define ATTRIBUTE(kw) +#else + #define ATTRIBUTE(kw) __attribute__((kw)) +#endif +#define MM_CDECL + +// We use these macros to hide our internal globals from being exported +// on ELF .so +#if defined(__GNUC__) && !defined(_WIN32) && __GNUC__ >= 3 && __GNUC_MINOR__ >= 3 + // Hidden data/function. + #define DLLHIDDEN __attribute__((visibility("hidden"))) + // Hidden internal function. + #if defined(__x86_64__) || defined(__amd64__) + #define DLLINTERNAL __attribute__((visibility("internal"))) + #define DLLINTERNAL_NOVIS + #else + #ifdef __INTERNALS_USE_REGPARAMS__ + #define DLLINTERNAL __attribute__((visibility("internal"), regparm(3))) + #define DLLINTERNAL_NOVIS __attribute__((regparm(3))) + #else + #define DLLINTERNAL __attribute__((visibility("internal"))) + #define DLLINTERNAL_NOVIS + #endif + #endif +#else + #define DLLHIDDEN + #if defined (_WIN32) && defined (_MSC_VER) + #define DLLINTERNAL_NOVIS + #define DLLINTERNAL + #else + #ifdef __INTERNALS_USE_REGPARAMS__ + #define DLLINTERNAL_NOVIS __attribute__((regparm(3))) + #define DLLINTERNAL DLLINTERNAL_NOVIS + #else + #define DLLINTERNAL_NOVIS + #define DLLINTERNAL + #endif + #endif //defined WIN32 +#endif + +#if defined (_WIN32) && defined (_MSC_VER) + // On x86 va_list is just a pointer. + #define va_copy(dst,src) ((dst)=(src)) +#else + // Some systems that do not supply va_copy have __va_copy instead, since + // that was the name used in the draft proposal. + #if !defined(__GNUC__) || __GNUC__ < 3 + #define va_copy __va_copy + #endif +#endif + +// Manual branch optimization for GCC 3.0.0 and newer +#if !defined(__GNUC__) || __GNUC__ < 3 + #define likely(x) (x) + #define unlikely(x) (x) +#else + #define likely(x) __builtin_expect((long int)(x), true) + #define unlikely(x) __builtin_expect((long int)(x), false) +#endif + +#endif /*COMP_DEP_H*/ diff --git a/src/metamod/conf_meta.cpp b/src/metamod/conf_meta.cpp new file mode 100644 index 0000000..167c1ad --- /dev/null +++ b/src/metamod/conf_meta.cpp @@ -0,0 +1,240 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// conf_meta.cpp - configfile reading routines + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // FILE, +#include // atoi +#include // isdigit + +#include // always + +#include "conf_meta.h" // me +#include "support_meta.h" // strmatch +#include "osdep.h" // strtok, + +MConfig::MConfig(void) + : list(NULL), filename(NULL), debuglevel(0), gamedll(NULL), + plugins_file(NULL), exec_cfg(NULL) +{ +} + +// Initialize default values from the stored options struct. Has to happen +// _after_ constructor, so that all the fields are allocated (d'oh). +void DLLINTERNAL MConfig::init(option_t *global_options) { + option_t *optp; + list=global_options; + for(optp=list; optp->name; optp++) + set(optp, optp->init); +} + +option_t * DLLINTERNAL MConfig::find(const char *lookup) { + option_t *optp; + + for(optp=list; optp->name && !strmatch(optp->name, lookup); optp++); + if(optp->name) + return(optp); + else + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +mBOOL DLLINTERNAL MConfig::set(const char *key, const char *value) { + option_t *optp; + optp=find(key); + if(optp) + return(set(optp, value)); + else + RETURN_ERRNO(mFALSE, ME_NOTFOUND); +} + +mBOOL DLLINTERNAL MConfig::set(option_t *setp, const char *setstr) { + char pathbuf[PATH_MAX]; + int *optval = (int *) setp->dest; + char **optstr = (char **) setp->dest; + // cvar_t *optcvar = (cvar_t *) setp->dest; + // SETOPT_FN optcmd = (SETOPT_FN) setp->dest; + + if(!setstr) + return(mTRUE); + + switch(setp->type) { + case CF_INT: + if(!isdigit(setstr[0])) { + META_WARNING("option '%s' invalid format '%s'", setp->name, setstr); + RETURN_ERRNO(mFALSE, ME_FORMAT); + } + *optval=atoi(setstr); + META_DEBUG(3, ("set config int: %s = %d", setp->name, *optval)); + break; + case CF_BOOL: + if(strcasematch(setstr, "true") + || strcasematch(setstr, "yes") + || strmatch(setstr, "1")) + { + *optval=1; + } + else if(strcasematch(setstr, "false") + || strcasematch(setstr, "no") + || strmatch(setstr, "0")) + { + *optval=0; + } + else { + META_WARNING("option '%s' invalid format '%s'", setp->name, setstr); + RETURN_ERRNO(mFALSE, ME_FORMAT); + } + META_DEBUG(3, ("set config bool: %s = %s", setp->name, *optval ? "true" : "false")); + break; + case CF_STR: + if(*optstr) + free(*optstr); + *optstr=strdup(setstr); + META_DEBUG(3, ("set config string: %s = %s", setp->name, *optstr)); + break; + case CF_PATH: + if(*optstr) + free(*optstr); + full_gamedir_path(setstr, pathbuf); + *optstr=strdup(pathbuf); + META_DEBUG(3, ("set config path: %s = %s", setp->name, *optstr)); + break; +#if 0 + case CF_CVAR: + CVAR_SET_STRING(optcvar->name, setstr); + META_DEBUG(3, ("set config cvar: %s = %s", optcvar->name, setstr)); + break; + case CF_CMD: + optcmd(setp->name, setstr); + META_DEBUG(3, ("set config command: %s, %s", optcvar->name, setstr)); + break; +#endif + default: + META_WARNING("unrecognized config type '%d'", setp->type); + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + return(mTRUE); +} + +mBOOL DLLINTERNAL MConfig::load(const char *fn) { + FILE *fp; + char loadfile[PATH_MAX]; + char line[MAX_CONF_LEN]; + char *optname, *optval; + option_t *optp; + int ln; + + // Make full pathname (from gamedir if relative, collapse "..", + // backslashes, etc). + full_gamedir_path(fn, loadfile); + + fp=fopen(loadfile, "r"); + if(!fp) { + META_WARNING("unable to open config file '%s': %s", loadfile, + strerror(errno)); + RETURN_ERRNO(mFALSE, ME_NOFILE); + } + + META_DEBUG(2, ("Loading from config file: %s", loadfile)); + for(ln=1; !feof(fp) && fgets(line, sizeof(line), fp); ln++) { + if(line[0]=='#') + continue; + if(line[0]==';') + continue; + if(strnmatch(line, "//", 2)) + continue; + if(!(optname=strtok(line, " \t\r\n"))) { + META_WARNING("'%s' line %d: bad config format: missing option", + loadfile, ln); + continue; + } + if(!(optval=strtok(NULL, "\r\n"))) { + META_WARNING("'%s' line %d: bad config format: missing value", + loadfile, ln); + continue; + } + + if(!(optp=find(optname))) { + META_WARNING("'%s' line %d: unknown option name '%s'", + loadfile, ln, optname); + continue; + } + + if(!set(optp, optval)) { + META_WARNING("'%s' line %d: unable to set option '%s' value '%s'", + loadfile, ln, optname, optval); + continue; + } + } + filename=strdup(loadfile); + fclose(fp); + return(mTRUE); +} + +void DLLINTERNAL MConfig::show(void) { + option_t *optp; + if(filename) + META_CONS("%s and %s:", "Config options from localinfo", filename); + else + META_CONS("%s:", "Config options from localinfo"); + for(optp=list; optp->name; optp++) { + int *optval = (int *) optp->dest; + char **optstr = (char **) optp->dest; + // cvar_t *optcvar = (cvar_t *) optp->dest; + // SETOPT_FN optcmd = (SETOPT_FN) optp->dest; + switch(optp->type) { + case CF_INT: + META_CONS(" %-20s\t%d\n", optp->name, *optval); + break; + case CF_BOOL: + META_CONS(" %-20s\t%s\n", optp->name, + *optval ? "true" : "false"); + break; + case CF_STR: + case CF_PATH: + META_CONS(" %-20s\t%s\n", optp->name, + *optstr ? *optstr : ""); + break; +#if 0 + case CF_CVAR: + META_CONS(" %-20s\tstores in: %s\n", optp->name, optcvar->name); + break; + case CF_CMD: + META_CONS(" %-20s\tparsed by: %d\n", optp->name, (int) optcmd); + break; +#endif + case CF_NONE: + break; + } + } +} diff --git a/src/metamod/conf_meta.h b/src/metamod/conf_meta.h new file mode 100644 index 0000000..bf751ab --- /dev/null +++ b/src/metamod/conf_meta.h @@ -0,0 +1,100 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// conf_meta.h - configfile reading + +// Modeled after mutt/init.[ch]. + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef CONF_META_H +#define CONF_META_H + +#include "types_meta.h" // mBOOL +#include "new_baseclass.h" +#include "comp_dep.h" + +// Max length of line in config file. +#define MAX_CONF_LEN 1024 + +// Supported config value-types. +typedef enum { + CF_NONE=0, + CF_INT, + CF_BOOL, + CF_STR, + CF_PATH, +#if 0 + CF_CVAR, + CF_CMD, +#endif +} cf_type_t; + +//typedef mBOOL (*SETOPT_FN) (char *key, char *value); + +typedef struct option_s { + char *name; // option name + cf_type_t type; // option type + void *dest; // addr of destination variable, or handler function + char *init; // initial value, as a string, just as config file would +} option_t; + +class MConfig : public class_metamod_new { + private: + // data + option_t *list; + char *filename; + // functions + option_t * DLLINTERNAL find(const char *lookup); + mBOOL DLLINTERNAL set(option_t *setp, const char *value); + // Private; to satisfy -Weffc++ "has pointer data members but does + // not override" copy/assignment constructor. + void operator=(const MConfig &src); + MConfig(const MConfig &src); + public: + // contructor + MConfig(void) DLLINTERNAL; + // data + int debuglevel; // to use for meta_debug + char *gamedll; // string if specified in config.ini + char *plugins_file; // ie metamod.ini, plugins.ini + char *exec_cfg; // ie metaexec.cfg, exec.cfg + int autodetect; // autodetection of gamedll (Metamod-All-Support patch) + int clientmeta; // control 'meta' client-command + // functions + void DLLINTERNAL init(option_t *global_options); + mBOOL DLLINTERNAL load(const char *filename); + mBOOL DLLINTERNAL set(const char *key, const char *value); + void DLLINTERNAL show(void); +}; + +#endif /* CONF_META_H */ diff --git a/src/metamod/dllapi.cpp b/src/metamod/dllapi.cpp new file mode 100644 index 0000000..1c7013f --- /dev/null +++ b/src/metamod/dllapi.cpp @@ -0,0 +1,513 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// dllapi.cpp - implementation of Half-Life DLL routines + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // offsetof + +#include // always + +#include "dllapi.h" // me +#include "metamod.h" // SETUP_API_CALLS, etc +#include "api_info.h" // dllapi_info, etc +#include "commands_meta.h" // client_meta, etc +#include "log_meta.h" // META_ERROR, etc +#include "api_hook.h" + + +// Original DLL routines, functions returning "void". +#define META_DLLAPI_HANDLE_void(FN_TYPE, pfnName, pack_args_type, pfn_args) \ + API_START_TSC_TRACKING(); \ + API_PACK_ARGS(pack_args_type, pfn_args); \ + main_hook_function_void(offsetof(dllapi_info_t, pfnName), e_api_dllapi, offsetof(DLL_FUNCTIONS, pfnName), &packed_args); \ + API_END_TSC_TRACKING() + +// Original DLL routines, functions returning an actual value. +#define META_DLLAPI_HANDLE(ret_t, ret_init, FN_TYPE, pfnName, pack_args_type, pfn_args) \ + API_START_TSC_TRACKING(); \ + API_PACK_ARGS(pack_args_type, pfn_args); \ + class_ret_t ret_val(main_hook_function(class_ret_t((ret_t)ret_init), offsetof(dllapi_info_t, pfnName), e_api_dllapi, offsetof(DLL_FUNCTIONS, pfnName), &packed_args)); \ + API_END_TSC_TRACKING() + +// The "new" api routines (just 3 right now), functions returning "void". +#define META_NEWAPI_HANDLE_void(FN_TYPE, pfnName, pack_args_type, pfn_args) \ + API_START_TSC_TRACKING(); \ + API_PACK_ARGS(pack_args_type, pfn_args); \ + main_hook_function_void(offsetof(newapi_info_t, pfnName), e_api_newapi, offsetof(NEW_DLL_FUNCTIONS, pfnName), &packed_args); \ + API_END_TSC_TRACKING() + +// The "new" api routines (just 3 right now), functions returning an actual value. +#define META_NEWAPI_HANDLE(ret_t, ret_init, FN_TYPE, pfnName, pack_args_type, pfn_args) \ + API_START_TSC_TRACKING(); \ + API_PACK_ARGS(pack_args_type, pfn_args); \ + class_ret_t ret_val(main_hook_function(class_ret_t((ret_t)ret_init), offsetof(newapi_info_t, pfnName), e_api_newapi, offsetof(NEW_DLL_FUNCTIONS, pfnName), &packed_args)); \ + API_END_TSC_TRACKING() + + +// From SDK dlls/game.cpp: +static void mm_GameDLLInit(void) { + META_DLLAPI_HANDLE_void(FN_GAMEINIT, pfnGameInit, void, (VOID_ARG)); + RETURN_API_void(); +} + +// From SDK dlls/cbase.cpp: +static int mm_DispatchSpawn(edict_t *pent) { + // 0==Success, -1==Failure ? + META_DLLAPI_HANDLE(int, 0, FN_DISPATCHSPAWN, pfnSpawn, p, (pent)); + RETURN_API(int); +} +static void mm_DispatchThink(edict_t *pent) { + META_DLLAPI_HANDLE_void(FN_DISPATCHTHINK, pfnThink, p, (pent)); + RETURN_API_void(); +} +static void mm_DispatchUse(edict_t *pentUsed, edict_t *pentOther) { + META_DLLAPI_HANDLE_void(FN_DISPATCHUSE, pfnUse, 2p, (pentUsed, pentOther)); + RETURN_API_void(); +} +static void mm_DispatchTouch(edict_t *pentTouched, edict_t *pentOther) { + META_DLLAPI_HANDLE_void(FN_DISPATCHTOUCH, pfnTouch, 2p, (pentTouched, pentOther)); + RETURN_API_void(); +} +static void mm_DispatchBlocked(edict_t *pentBlocked, edict_t *pentOther) { + META_DLLAPI_HANDLE_void(FN_DISPATCHBLOCKED, pfnBlocked, 2p, (pentBlocked, pentOther)); + RETURN_API_void(); +} +static void mm_DispatchKeyValue(edict_t *pentKeyvalue, KeyValueData *pkvd) { + META_DLLAPI_HANDLE_void(FN_DISPATCHKEYVALUE, pfnKeyValue, 2p, (pentKeyvalue, pkvd)); + RETURN_API_void(); +} +static void mm_DispatchSave(edict_t *pent, SAVERESTOREDATA *pSaveData) { + META_DLLAPI_HANDLE_void(FN_DISPATCHSAVE, pfnSave, 2p, (pent, pSaveData)); + RETURN_API_void(); +} +static int mm_DispatchRestore(edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity) { + // 0==Success, -1==Failure ? + META_DLLAPI_HANDLE(int, 0, FN_DISPATCHRESTORE, pfnRestore, 2pi, (pent, pSaveData, globalEntity)); + RETURN_API(int); +} +static void mm_DispatchObjectCollsionBox(edict_t *pent) { + META_DLLAPI_HANDLE_void(FN_DISPATCHOBJECTCOLLISIONBOX, pfnSetAbsBox, p, (pent)); + RETURN_API_void(); +} +static void mm_SaveWriteFields(SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount) { + META_DLLAPI_HANDLE_void(FN_SAVEWRITEFIELDS, pfnSaveWriteFields, 4pi, (pSaveData, pname, pBaseData, pFields, fieldCount)); + RETURN_API_void(); +} +static void mm_SaveReadFields(SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount) { + META_DLLAPI_HANDLE_void(FN_SAVEREADFIELDS, pfnSaveReadFields, 4pi, (pSaveData, pname, pBaseData, pFields, fieldCount)); + RETURN_API_void(); +} + +// From SDK dlls/world.cpp: +static void mm_SaveGlobalState(SAVERESTOREDATA *pSaveData) { + META_DLLAPI_HANDLE_void(FN_SAVEGLOBALSTATE, pfnSaveGlobalState, p, (pSaveData)); + RETURN_API_void(); +} +static void mm_RestoreGlobalState(SAVERESTOREDATA *pSaveData) { + META_DLLAPI_HANDLE_void(FN_RESTOREGLOBALSTATE, pfnRestoreGlobalState, p, (pSaveData)); + RETURN_API_void(); +} +static void mm_ResetGlobalState(void) { + META_DLLAPI_HANDLE_void(FN_RESETGLOBALSTATE, pfnResetGlobalState, void, (VOID_ARG)); + RETURN_API_void(); +} + +// From SDK dlls/client.cpp: +static qboolean mm_ClientConnect(edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[128]) { + g_Players.clear_player_cvar_query(pEntity); + META_DLLAPI_HANDLE(qboolean, TRUE, FN_CLIENTCONNECT, pfnClientConnect, 4p, (pEntity, pszName, pszAddress, szRejectReason)); + RETURN_API(qboolean); +} +static void mm_ClientDisconnect(edict_t *pEntity) { + g_Players.clear_player_cvar_query(pEntity); + META_DLLAPI_HANDLE_void(FN_CLIENTDISCONNECT, pfnClientDisconnect, p, (pEntity)); + RETURN_API_void(); +} +static void mm_ClientKill(edict_t *pEntity) { + META_DLLAPI_HANDLE_void(FN_CLIENTKILL, pfnClientKill, p, (pEntity)); + RETURN_API_void(); +} +static void mm_ClientPutInServer(edict_t *pEntity) { + META_DLLAPI_HANDLE_void(FN_CLIENTPUTINSERVER, pfnClientPutInServer, p, (pEntity)); + RETURN_API_void(); +} +static void mm_ClientCommand(edict_t *pEntity) { + if(Config->clientmeta && strmatch(CMD_ARGV(0), "meta")) { + client_meta(pEntity); + } + META_DLLAPI_HANDLE_void(FN_CLIENTCOMMAND, pfnClientCommand, p, (pEntity)); + RETURN_API_void(); +} +static void mm_ClientUserInfoChanged(edict_t *pEntity, char *infobuffer) { + META_DLLAPI_HANDLE_void(FN_CLIENTUSERINFOCHANGED, pfnClientUserInfoChanged, 2p, (pEntity, infobuffer)); + RETURN_API_void(); +} +static void mm_ServerActivate(edict_t *pEdictList, int edictCount, int clientMax) { + META_DLLAPI_HANDLE_void(FN_SERVERACTIVATE, pfnServerActivate, p2i, (pEdictList, edictCount, clientMax)); + RETURN_API_void(); +} +static void mm_ServerDeactivate(void) { + META_DLLAPI_HANDLE_void(FN_SERVERDEACTIVATE, pfnServerDeactivate, void, (VOID_ARG)); + // Update loaded plugins. Look for new plugins in inifile, as well as + // any plugins waiting for a changelevel to load. + // + // This is done in ServerDeactivate rather than Activate, as the latter + // isn't actually the first routine to be called on a new map. In + // particular DispatchKeyValue and DispatchSpawn are called before + // Activate, and we want any newly loaded plugins to be able to catch + // these. + // + // So, we do this from Deactivate, which is the _last_ routine called + // from the previous map. It's also called right before shutdown, + // which means whenever hlds quits, it'll reload the plugins just + // before it exits, which is rather silly, but oh well. + Plugins->refresh(PT_CHANGELEVEL); + Plugins->unpause_all(); + // Plugins->retry_all(PT_CHANGELEVEL); + g_Players.clear_all_cvar_queries(); + requestid_counter = 0; + RETURN_API_void(); +} +static void mm_PlayerPreThink(edict_t *pEntity) { + META_DLLAPI_HANDLE_void(FN_PLAYERPRETHINK, pfnPlayerPreThink, p, (pEntity)); + RETURN_API_void(); +} +static void mm_PlayerPostThink(edict_t *pEntity) { + META_DLLAPI_HANDLE_void(FN_PLAYERPOSTTHINK, pfnPlayerPostThink, p, (pEntity)); + RETURN_API_void(); +} +static void mm_StartFrame(void) { + meta_debug_value = (int)meta_debug.value; + + META_DLLAPI_HANDLE_void(FN_STARTFRAME, pfnStartFrame, void, (VOID_ARG)); + RETURN_API_void(); +} +static void mm_ParmsNewLevel(void) { + META_DLLAPI_HANDLE_void(FN_PARMSNEWLEVEL, pfnParmsNewLevel, void, (VOID_ARG)); + RETURN_API_void(); +} +static void mm_ParmsChangeLevel(void) { + META_DLLAPI_HANDLE_void(FN_PARMSCHANGELEVEL, pfnParmsChangeLevel, void, (VOID_ARG)); + RETURN_API_void(); +} +static const char *mm_GetGameDescription(void) { + META_DLLAPI_HANDLE(const char *, NULL, FN_GETGAMEDESCRIPTION, pfnGetGameDescription, void, (VOID_ARG)); + RETURN_API(const char *); +} +static void mm_PlayerCustomization(edict_t *pEntity, customization_t *pCust) { + META_DLLAPI_HANDLE_void(FN_PLAYERCUSTOMIZATION, pfnPlayerCustomization, 2p, (pEntity, pCust)); + RETURN_API_void(); +} +static void mm_SpectatorConnect(edict_t *pEntity) { + META_DLLAPI_HANDLE_void(FN_SPECTATORCONNECT, pfnSpectatorConnect, p, (pEntity)); + RETURN_API_void(); +} +static void mm_SpectatorDisconnect(edict_t *pEntity) { + META_DLLAPI_HANDLE_void(FN_SPECTATORDISCONNECT, pfnSpectatorDisconnect, p, (pEntity)); + RETURN_API_void(); +} +static void mm_SpectatorThink(edict_t *pEntity) { + META_DLLAPI_HANDLE_void(FN_SPECTATORTHINK, pfnSpectatorThink, p, (pEntity)); + RETURN_API_void(); +} +static void mm_Sys_Error(const char *error_string) { + META_DLLAPI_HANDLE_void(FN_SYS_ERROR, pfnSys_Error, p, (error_string)); + RETURN_API_void(); +} + +// From SDK pm_shared/pm_shared.c: +static void mm_PM_Move (struct playermove_s *ppmove, int server) { + META_DLLAPI_HANDLE_void(FN_PM_MOVE, pfnPM_Move, pi, (ppmove, server)); + RETURN_API_void(); +} +static void mm_PM_Init(struct playermove_s *ppmove) { + META_DLLAPI_HANDLE_void(FN_PM_INIT, pfnPM_Init, p, (ppmove)); + RETURN_API_void(); +} +static char mm_PM_FindTextureType(char *name) { + META_DLLAPI_HANDLE(char, '\0', FN_PM_FINDTEXTURETYPE, pfnPM_FindTextureType, p, (name)); + RETURN_API(char); +} + +// From SDK dlls/client.cpp: +static void mm_SetupVisibility(edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas) { + META_DLLAPI_HANDLE_void(FN_SETUPVISIBILITY, pfnSetupVisibility, 4p, (pViewEntity, pClient, pvs, pas)); + RETURN_API_void(); +} +static void mm_UpdateClientData (const struct edict_s *ent, int sendweapons, struct clientdata_s *cd) { + META_DLLAPI_HANDLE_void(FN_UPDATECLIENTDATA, pfnUpdateClientData, pip, (ent, sendweapons, cd)); + RETURN_API_void(); +} +static int mm_AddToFullPack(struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet) { + META_DLLAPI_HANDLE(int, 0, FN_ADDTOFULLPACK, pfnAddToFullPack, pi2p2ip, (state, e, ent, host, hostflags, player, pSet)); + RETURN_API(int); +} +static void mm_CreateBaseline(int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs) { + META_DLLAPI_HANDLE_void(FN_CREATEBASELINE, pfnCreateBaseline, 2i2pi2p, (player, eindex, baseline, entity, playermodelindex, (float*)player_mins, (float*)player_maxs)); + RETURN_API_void(); +} +static void mm_RegisterEncoders(void) { + META_DLLAPI_HANDLE_void(FN_REGISTERENCODERS, pfnRegisterEncoders, void, (VOID_ARG)); + RETURN_API_void(); +} +static int mm_GetWeaponData(struct edict_s *player, struct weapon_data_s *info) { + META_DLLAPI_HANDLE(int, 0, FN_GETWEAPONDATA, pfnGetWeaponData, 2p, (player, info)); + RETURN_API(int); +} +static void mm_CmdStart(const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed) { + META_DLLAPI_HANDLE_void(FN_CMDSTART, pfnCmdStart, 2pui, (player, cmd, random_seed)); + RETURN_API_void(); +} +static void mm_CmdEnd (const edict_t *player) { + META_DLLAPI_HANDLE_void(FN_CMDEND, pfnCmdEnd, p, (player)); + RETURN_API_void(); +} +static int mm_ConnectionlessPacket(const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size) { + META_DLLAPI_HANDLE(int, 0, FN_CONNECTIONLESSPACKET, pfnConnectionlessPacket, 4p, (net_from, args, response_buffer, response_buffer_size)); + RETURN_API(int); +} +static int mm_GetHullBounds(int hullnumber, float *mins, float *maxs) { + META_DLLAPI_HANDLE(int, 0, FN_GETHULLBOUNDS, pfnGetHullBounds, i2p, (hullnumber, mins, maxs)); + RETURN_API(int); +} +static void mm_CreateInstancedBaselines (void) { + META_DLLAPI_HANDLE_void(FN_CREATEINSTANCEDBASELINES, pfnCreateInstancedBaselines, void, (VOID_ARG)); + RETURN_API_void(); +} +static int mm_InconsistentFile(const edict_t *player, const char *filename, char *disconnect_message) { + META_DLLAPI_HANDLE(int, 0, FN_INCONSISTENTFILE, pfnInconsistentFile, 3p, (player, filename, disconnect_message)); + RETURN_API(int); +} +static int mm_AllowLagCompensation(void) { + META_DLLAPI_HANDLE(int, 0, FN_ALLOWLAGCOMPENSATION, pfnAllowLagCompensation, void, (VOID_ARG)); + RETURN_API(int); +} + + +// New API functions +// From SDK ? +static void mm_OnFreeEntPrivateData(edict_t *pEnt) { + META_NEWAPI_HANDLE_void(FN_ONFREEENTPRIVATEDATA, pfnOnFreeEntPrivateData, p, (pEnt)); + RETURN_API_void(); +} +static void mm_GameShutdown(void) { + META_NEWAPI_HANDLE_void(FN_GAMESHUTDOWN, pfnGameShutdown, void, (VOID_ARG)); + RETURN_API_void(); +} +static int mm_ShouldCollide(edict_t *pentTouched, edict_t *pentOther) { + META_NEWAPI_HANDLE(int, 1, FN_SHOULDCOLLIDE, pfnShouldCollide, 2p, (pentTouched, pentOther)); + RETURN_API(int); +} +// Added 2005/08/11 (no SDK update): +static void mm_CvarValue(const edict_t *pEnt, const char *value) { + g_Players.clear_player_cvar_query(pEnt); + META_NEWAPI_HANDLE_void(FN_CVARVALUE, pfnCvarValue, 2p, (pEnt, value)); + + RETURN_API_void(); +} +// Added 2005/11/21 (no SDK update): +static void mm_CvarValue2(const edict_t *pEnt, int requestID, const char *cvarName, const char *value) { + META_NEWAPI_HANDLE_void(FN_CVARVALUE2, pfnCvarValue2, pi2p, (pEnt, requestID, cvarName, value)); + + RETURN_API_void(); +} + + +// From SDK dlls/cbase.cpp: +// "(wd)" indicates my comments on the functions +static DLL_FUNCTIONS gFunctionTable = +{ + mm_GameDLLInit, //! pfnGameInit() Initialize the game (one-time call after loading of game .dll) + mm_DispatchSpawn, //! pfnSpawn() + mm_DispatchThink, //! pfnThink() + mm_DispatchUse, //! pfnUse() + mm_DispatchTouch, //! pfnTouch() + mm_DispatchBlocked, //! pfnBlocked() + mm_DispatchKeyValue, //! pfnKeyValue() + mm_DispatchSave, //! pfnSave() + mm_DispatchRestore, //! pfnRestore() + mm_DispatchObjectCollsionBox, //! pfnSetAbsBox() + + mm_SaveWriteFields, //! pfnSaveWriteFields() + mm_SaveReadFields, //! pfnSaveReadFields() + + mm_SaveGlobalState, //! pfnSaveGlobalState() + mm_RestoreGlobalState, //! pfnRestoreGlobalState() + mm_ResetGlobalState, //! pfnResetGlobalState() + + mm_ClientConnect, //! pfnClientConnect() (wd) Client has connected + mm_ClientDisconnect, //! pfnClientDisconnect() (wd) Player has left the game + mm_ClientKill, //! pfnClientKill() (wd) Player has typed "kill" + mm_ClientPutInServer, //! pfnClientPutInServer() (wd) Client is entering the game + mm_ClientCommand, //! pfnClientCommand() (wd) Player has sent a command (typed, or from a bind) + mm_ClientUserInfoChanged, //! pfnClientUserInfoChanged() (wd) Client has updated their setinfo structure + mm_ServerActivate, //! pfnServerActivate() (wd) Server is starting a new map + mm_ServerDeactivate, //! pfnServerDeactivate() (wd) Server is leaving the map (shutdown, or changelevel); SDK2 + + mm_PlayerPreThink, //! pfnPlayerPreThink() + mm_PlayerPostThink, //! pfnPlayerPostThink() + + mm_StartFrame, //! pfnStartFrame() + mm_ParmsNewLevel, //! pfnParmsNewLevel() + mm_ParmsChangeLevel, //! pfnParmsChangeLevel() + + mm_GetGameDescription, //! pfnGetGameDescription() Returns string describing current .dll. E.g. "TeamFotrress 2", "Half-Life" + mm_PlayerCustomization, //! pfnPlayerCustomization() Notifies .dll of new customization for player. + + mm_SpectatorConnect, //! pfnSpectatorConnect() Called when spectator joins server + mm_SpectatorDisconnect, //! pfnSpectatorDisconnect() Called when spectator leaves the server + mm_SpectatorThink, //! pfnSpectatorThink() Called when spectator sends a command packet (usercmd_t) + + mm_Sys_Error, //! pfnSys_Error() Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint. SDK2 + + mm_PM_Move, //! pfnPM_Move() (wd) SDK2 + mm_PM_Init, //! pfnPM_Init() Server version of player movement initialization; (wd) SDK2 + mm_PM_FindTextureType, //! pfnPM_FindTextureType() (wd) SDK2 + + mm_SetupVisibility, //! pfnSetupVisibility() Set up PVS and PAS for networking for this client; (wd) SDK2 + mm_UpdateClientData, //! pfnUpdateClientData() Set up data sent only to specific client; (wd) SDK2 + mm_AddToFullPack, //! pfnAddToFullPack() (wd) SDK2 + mm_CreateBaseline, //! pfnCreateBaseline() Tweak entity baseline for network encoding, allows setup of player baselines, too.; (wd) SDK2 + mm_RegisterEncoders, //! pfnRegisterEncoders() Callbacks for network encoding; (wd) SDK2 + mm_GetWeaponData, //! pfnGetWeaponData() (wd) SDK2 + mm_CmdStart, //! pfnCmdStart() (wd) SDK2 + mm_CmdEnd, //! pfnCmdEnd() (wd) SDK2 + mm_ConnectionlessPacket, //! pfnConnectionlessPacket() (wd) SDK2 + mm_GetHullBounds, //! pfnGetHullBounds() (wd) SDK2 + mm_CreateInstancedBaselines, //! pfnCreateInstancedBaselines() (wd) SDK2 + mm_InconsistentFile, //! pfnInconsistentFile() (wd) SDK2 + mm_AllowLagCompensation, //! pfnAllowLagCompensation() (wd) SDK2 +}; + +DLL_FUNCTIONS *g_pHookedDllFunctions = &gFunctionTable; + +// It's not clear what the difference is between GetAPI and GetAPI2; they +// both appear to return the exact same function table. +// +// Only one of them appears to be ever called, though. If the DLL provides +// GetAPI2, the engine/hlds will call that, and will not call GetAPI. If +// the engine couldn't find GetAPI2 in the DLL, it appears to fall back to +// GetAPI. +// +// So, GetAPI2 appears to replace GetAPI, and appears to have been added +// with SDK 2.0. My best guess is that, with the new SDK, interface +// version checking became important, and without the int ptr used in +// GetAPI2, the engine can't find out the version of the DLL via GetAPI. +// +// It's unclear whether a DLL coded under SDK2 needs to provide the older +// GetAPI or not.. + +C_DLLEXPORT int GetEntityAPI(DLL_FUNCTIONS *pFunctionTable, int interfaceVersion) +{ + META_DEBUG(3, ("called: GetEntityAPI; version=%d", interfaceVersion)); + if(!pFunctionTable || metamod_not_loaded) { + META_WARNING("GetEntityAPI called with null pFunctionTable"); + return(FALSE); + } + else if(interfaceVersion != INTERFACE_VERSION) { + META_WARNING("GetEntityAPI version mismatch; requested=%d ours=%d", interfaceVersion, INTERFACE_VERSION); + return(FALSE); + } + memcpy(pFunctionTable, &gFunctionTable, sizeof(DLL_FUNCTIONS)); + return(TRUE); +} + +C_DLLEXPORT int GetEntityAPI2(DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion) +{ + META_DEBUG(3, ("called: GetEntityAPI2; version=%d", *interfaceVersion)); + if(!pFunctionTable || metamod_not_loaded) { + META_WARNING("GetEntityAPI2 called with null pFunctionTable"); + return(FALSE); + } + else if(*interfaceVersion != INTERFACE_VERSION) { + META_WARNING("GetEntityAPI2 version mismatch; requested=%d ours=%d", *interfaceVersion, INTERFACE_VERSION); + //! Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return(FALSE); + } + memcpy(pFunctionTable, &gFunctionTable, sizeof(DLL_FUNCTIONS)); + return(TRUE); +} + + +// I could find _no_ documentation or examples for the intended use of +// NEW_DLL_FUNCTIONS. I wouldn't have even _known_ about the +// GetNewDLLFunctions() function except for the reference in Adminmod.. It +// appears to be new with SDK 2.0. +// +// Obviously, it seems to provide additional functions to the engine, but +// it's unclear why a new table and interface were added, rather than +// appending new functions to the GetAPI table/interface. +// +// Interestingly, it appears to be called by the engine _before_ GetAPI. + +static meta_new_dll_functions_t sNewFunctionTable( + &mm_OnFreeEntPrivateData, //! pfnOnFreeEntPrivateData() Called right before the object's memory is freed. Calls its destructor. + &mm_GameShutdown, //! pfnGameShutdown() + &mm_ShouldCollide, //! pfnShouldCollide() + // Added 2005/08/11 (no SDK update): + &mm_CvarValue, //! pfnCvarValue() + // Added 2005/11/21 (no SDK update): + &mm_CvarValue2 //! pfnCvarValue2() +); + +NEW_DLL_FUNCTIONS *g_pHookedNewDllFunctions = &sNewFunctionTable; + +C_DLLEXPORT int GetNewDLLFunctions(NEW_DLL_FUNCTIONS *pNewFunctionTable, int *interfaceVersion) +{ + META_DEBUG(6, ("called: GetNewDLLFunctions; version=%d", *interfaceVersion)); +#if 0 // ~dvander - but then you can't use cvar querying on many mods... + // Don't provide these functions to engine if gamedll doesn't provide + // them. Otherwise, we're in the position of having to provide answers + // we can't necessarily provide (for instance, ShouldCollide())... + if(!GameDLL.funcs.newapi_table) + return(FALSE); +#endif + + if(!pNewFunctionTable) { + META_ERROR("GetNewDLLFunctions called with null pNewFunctionTable"); + return(FALSE); + } + else if(*interfaceVersion != NEW_DLL_FUNCTIONS_VERSION) { + META_ERROR("GetNewDLLFunctions version mismatch; requested=%d ours=%d", *interfaceVersion, NEW_DLL_FUNCTIONS_VERSION); + //! Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = NEW_DLL_FUNCTIONS_VERSION; + return(FALSE); + } + + sNewFunctionTable.copy_to(pNewFunctionTable); + + + return(TRUE); +} diff --git a/src/metamod/dllapi.h b/src/metamod/dllapi.h new file mode 100644 index 0000000..f04d542 --- /dev/null +++ b/src/metamod/dllapi.h @@ -0,0 +1,121 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// dllapi.h - prototypes and typedefs for Half-Life DLL API routines + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef DLLAPI_H +#define DLLAPI_H + +#include "sdk_util.h" // BOOL +#include "osdep.h" // DLLEXPORT, etc + +// Typedefs for these are provided in SDK engine/eiface.h, but I didn't +// like the names (APIFUNCTION, APIFUNCTION2, NEW_DLL_FUNCTIONS_FN). +typedef int (*GETENTITYAPI_FN) (DLL_FUNCTIONS *pFunctionTable, int interfaceVersion); +typedef int (*GETENTITYAPI2_FN) (DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion); +typedef int (*GETNEWDLLFUNCTIONS_FN) (NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion); + +// From SDK dlls/cbase.h: +C_DLLEXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +C_DLLEXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +// No example in SDK.. +// From Adminmod dll.cpp: +C_DLLEXPORT int GetNewDLLFunctions( NEW_DLL_FUNCTIONS *pNewFunctionTable, int *interfaceVersion ); + +// Typedefs for the above functions: + +typedef void (*FN_GAMEINIT) ( void ); +typedef int (*FN_DISPATCHSPAWN) ( edict_t *pent ); +typedef void (*FN_DISPATCHTHINK) ( edict_t *pent ); +typedef void (*FN_DISPATCHUSE) ( edict_t *pentUsed, edict_t *pentOther ); +typedef void (*FN_DISPATCHTOUCH) ( edict_t *pentTouched, edict_t *pentOther ); +typedef void (*FN_DISPATCHBLOCKED) ( edict_t *pentBlocked, edict_t *pentOther ); +typedef void (*FN_DISPATCHKEYVALUE) ( edict_t *pentKeyvalue, KeyValueData *pkvd ); +typedef void (*FN_DISPATCHSAVE) ( edict_t *pent, SAVERESTOREDATA *pSaveData ); +typedef int (*FN_DISPATCHRESTORE) ( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); +typedef void (*FN_DISPATCHOBJECTCOLLISIONBOX) ( edict_t *pent ); +typedef void (*FN_SAVEWRITEFIELDS) ( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +typedef void (*FN_SAVEREADFIELDS) ( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +typedef void (*FN_SAVEGLOBALSTATE) ( SAVERESTOREDATA *pSaveData ); +typedef void (*FN_RESTOREGLOBALSTATE) ( SAVERESTOREDATA *pSaveData ); +typedef void (*FN_RESETGLOBALSTATE) ( void ); + +typedef qboolean (*FN_CLIENTCONNECT) ( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[128]); +typedef void (*FN_CLIENTDISCONNECT) ( edict_t *pEntity ); +typedef void (*FN_CLIENTKILL) ( edict_t *pEntity ); +typedef void (*FN_CLIENTPUTINSERVER) ( edict_t *pEntity ); +typedef void (*FN_CLIENTCOMMAND) ( edict_t *pEntity ); +typedef void (*FN_CLIENTUSERINFOCHANGED) ( edict_t *pEntity, char *infobuffer ); +typedef void (*FN_SERVERACTIVATE) ( edict_t *pEdictList, int edictCount, int clientMax ); +typedef void (*FN_SERVERDEACTIVATE) ( void ); +typedef void (*FN_PLAYERPRETHINK) ( edict_t *pEntity ); +typedef void (*FN_PLAYERPOSTTHINK) ( edict_t *pEntity ); +typedef void (*FN_STARTFRAME) ( void ); +typedef void (*FN_PARMSNEWLEVEL) ( void ); +typedef void (*FN_PARMSCHANGELEVEL) ( void ); +typedef const char *(*FN_GETGAMEDESCRIPTION) ( void ); +typedef void (*FN_PLAYERCUSTOMIZATION) ( edict_t *pEntity, customization_t *pCust ); +typedef void (*FN_SPECTATORCONNECT) ( edict_t *pEntity ); +typedef void (*FN_SPECTATORDISCONNECT) ( edict_t *pEntity ); +typedef void (*FN_SPECTATORTHINK) ( edict_t *pEntity ); +typedef void (*FN_SYS_ERROR) ( const char *error_string ); + +typedef void (*FN_PM_MOVE) ( struct playermove_s *ppmove, int server ); +typedef void (*FN_PM_INIT) ( struct playermove_s *ppmove ); +typedef char (*FN_PM_FINDTEXTURETYPE) ( char *name ); + +typedef void (*FN_SETUPVISIBILITY) ( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ); +typedef void (*FN_UPDATECLIENTDATA) ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); +typedef int (*FN_ADDTOFULLPACK) ( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); +typedef void (*FN_CREATEBASELINE) ( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); +typedef void (*FN_REGISTERENCODERS) ( void ); +typedef int (*FN_GETWEAPONDATA) ( struct edict_s *player, struct weapon_data_s *info ); +typedef void (*FN_CMDSTART) ( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); +typedef void (*FN_CMDEND) ( const edict_t *player ); +typedef int (*FN_CONNECTIONLESSPACKET) ( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +typedef int (*FN_GETHULLBOUNDS) ( int hullnumber, float *mins, float *maxs ); +typedef void (*FN_CREATEINSTANCEDBASELINES) ( void ); +typedef int (*FN_INCONSISTENTFILE) ( const edict_t *player, const char *filename, char *disconnect_message ); +typedef int (*FN_ALLOWLAGCOMPENSATION) ( void ); + +typedef void (*FN_ONFREEENTPRIVATEDATA) (edict_t *pEnt); +typedef void (*FN_GAMESHUTDOWN) (void); +typedef int (*FN_SHOULDCOLLIDE) (edict_t *pentTouched, edict_t *pentOther); +// Added 2005/08/11 (no SDK update): +typedef void (*FN_CVARVALUE)(const edict_t *pEnt, const char *value); +// Added 2005/11/21 (no SDK update): +typedef void (*FN_CVARVALUE2)(const edict_t *pEnt, int requestID, const char *cvarName, const char *value); + +#endif /* DLLAPI_H */ diff --git a/src/metamod/engine_api.cpp b/src/metamod/engine_api.cpp new file mode 100644 index 0000000..f51866d --- /dev/null +++ b/src/metamod/engine_api.cpp @@ -0,0 +1,1130 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// engine_api.cpp - implementation of Half-Life engine functions + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // offsetof +#include // vsnprintf, etc +#include // va_start, etc +#include // alloca, etc + +#include // always + +#include "engine_api.h" // me +#include "metamod.h" // SETUP_API_CALLS, etc +#include "thread_logparse.h" // LogQueue, etc +#include "api_info.h" // dllapi_info, etc +#include "log_meta.h" // META_ERROR, etc +#include "osdep.h" // win32 vsnprintf, etc +#include "api_hook.h" + + +// Engine routines, functions returning "void". +#define META_ENGINE_HANDLE_void(FN_TYPE, pfnName, pack_args_type, pfn_args) \ + API_START_TSC_TRACKING(); \ + API_PACK_ARGS(pack_args_type, pfn_args); \ + main_hook_function_void(offsetof(engine_info_t, pfnName), e_api_engine, offsetof(enginefuncs_t, pfnName), &packed_args); \ + API_END_TSC_TRACKING() + +// Engine routines, functions returning an actual value. +#define META_ENGINE_HANDLE(ret_t, ret_init, FN_TYPE, pfnName, pack_args_type, pfn_args) \ + API_START_TSC_TRACKING(); \ + API_PACK_ARGS(pack_args_type, pfn_args); \ + class_ret_t ret_val(main_hook_function(class_ret_t((ret_t)ret_init), offsetof(engine_info_t, pfnName), e_api_engine, offsetof(enginefuncs_t, pfnName), &packed_args)); \ + API_END_TSC_TRACKING() + +// For varargs functions +#ifndef DO_NOT_FIX_VARARG_ENGINE_API_WARPERS + #define MAKE_FORMATED_STRING(szFmt) \ + char strbuf[MAX_STRBUF_LEN]; \ + char * buf=strbuf; \ + { \ + int len; \ + va_list vargs; \ + va_start(vargs, szFmt); \ + len = safe_vsnprintf(strbuf, sizeof(strbuf), szFmt, vargs); \ + va_end(vargs); \ + if((unsigned)len >= sizeof(strbuf)) { \ + buf = (char *)malloc(len + 1); \ + if(buf) { \ + va_start(vargs, szFmt); \ + safevoid_vsnprintf(buf, len + 1, szFmt, vargs); \ + va_end(vargs); \ + } else { \ + buf=strbuf; \ + } \ + } \ + } + #define CLEAN_FORMATED_STRING() \ + if(buf != strbuf) \ + free(buf); +#else + #define MAKE_FORMATED_STRING(szFmt) \ + char buf[MAX_STRBUF_LEN]; \ + va_list ap; \ + va_start(ap, szFmt); \ + safevoid_vsnprintf(buf, sizeof(buf), szFmt, ap); \ + va_end(ap); + + #define CLEAN_FORMATED_STRING() +#endif + +// Engine routines, printf-style functions returning "void". +#define META_ENGINE_HANDLE_void_varargs(FN_TYPE, pfnName, pack_args_type, pfn_arg, fmt_arg) \ + MAKE_FORMATED_STRING(fmt_arg); \ + API_START_TSC_TRACKING(); \ + META_DEBUG(engine_info.pfnName.loglevel, ("In %s: fmt=%s", engine_info.pfnName.name, fmt_arg)); \ + API_PACK_ARGS(pack_args_type, (pfn_arg, "%s", buf)); \ + main_hook_function_void(offsetof(engine_info_t, pfnName), e_api_engine, offsetof(enginefuncs_t, pfnName), &packed_args); \ + API_END_TSC_TRACKING() \ + CLEAN_FORMATED_STRING() + +// Engine routines, printf-style functions returning an actual value. +#define META_ENGINE_HANDLE_varargs(ret_t, ret_init, FN_TYPE, pfnName, pack_args_type, pfn_arg, fmt_arg) \ + MAKE_FORMATED_STRING(fmt_arg); \ + API_START_TSC_TRACKING(); \ + META_DEBUG(engine_info.pfnName.loglevel, ("In %s: fmt=%s", engine_info.pfnName.name, fmt_arg)); \ + API_PACK_ARGS(pack_args_type, (pfn_arg, "%s", buf)); \ + class_ret_t ret_val(main_hook_function(class_ret_t((ret_t)ret_init), offsetof(engine_info_t, pfnName), e_api_engine, offsetof(enginefuncs_t, pfnName), &packed_args)); \ + API_END_TSC_TRACKING() \ + CLEAN_FORMATED_STRING() + + +static int mm_PrecacheModel(char *s) { + META_ENGINE_HANDLE(int, 0, FN_PRECACHEMODEL, pfnPrecacheModel, p, (s)); + RETURN_API(int) +} +static int mm_PrecacheSound(char *s) { + META_ENGINE_HANDLE(int, 0, FN_PRECACHESOUND, pfnPrecacheSound, p, (s)); + RETURN_API(int) +} +static void mm_SetModel(edict_t *e, const char *m) { + META_ENGINE_HANDLE_void(FN_SETMODEL, pfnSetModel, 2p, (e, m)); + RETURN_API_void() +} +static int mm_ModelIndex(const char *m) { + META_ENGINE_HANDLE(int, 0, FN_MODELINDEX, pfnModelIndex, p, (m)); + RETURN_API(int) +} +static int mm_ModelFrames(int modelIndex) { + META_ENGINE_HANDLE(int, 0, FN_MODELFRAMES, pfnModelFrames, i, (modelIndex)); + RETURN_API(int) +} + +static void mm_SetSize(edict_t *e, const float *rgflMin, const float *rgflMax) { + META_ENGINE_HANDLE_void(FN_SETSIZE, pfnSetSize, 3p, (e, rgflMin, rgflMax)); + RETURN_API_void() +} +static void mm_ChangeLevel(char *s1, char *s2) { + META_ENGINE_HANDLE_void(FN_CHANGELEVEL, pfnChangeLevel, 2p, (s1, s2)); + RETURN_API_void() +} +static void mm_GetSpawnParms(edict_t *ent) { + META_ENGINE_HANDLE_void(FN_GETSPAWNPARMS, pfnGetSpawnParms, p, (ent)); + RETURN_API_void() +} +static void mm_SaveSpawnParms(edict_t *ent) { + META_ENGINE_HANDLE_void(FN_SAVESPAWNPARMS, pfnSaveSpawnParms, p, (ent)); + RETURN_API_void() +} + +static float mm_VecToYaw(const float *rgflVector) { + META_ENGINE_HANDLE(float, 0.0, FN_VECTOYAW, pfnVecToYaw, p, (rgflVector)); + RETURN_API(float) +} +static void mm_VecToAngles(const float *rgflVectorIn, float *rgflVectorOut) { + META_ENGINE_HANDLE_void(FN_VECTOANGLES, pfnVecToAngles, 2p, (rgflVectorIn, rgflVectorOut)); + RETURN_API_void() +} +static void mm_MoveToOrigin(edict_t *ent, const float *pflGoal, float dist, int iMoveType) { + META_ENGINE_HANDLE_void(FN_MOVETOORIGIN, pfnMoveToOrigin, 2pfi, (ent, pflGoal, dist, iMoveType)); + RETURN_API_void() +} +static void mm_ChangeYaw(edict_t *ent) { + META_ENGINE_HANDLE_void(FN_CHANGEYAW, pfnChangeYaw, p, (ent)); + RETURN_API_void() +} +static void mm_ChangePitch(edict_t *ent) { + META_ENGINE_HANDLE_void(FN_CHANGEPITCH, pfnChangePitch, p, (ent)); + RETURN_API_void() +} + +static edict_t *mm_FindEntityByString(edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_FINDENTITYBYSTRING, pfnFindEntityByString, 3p, (pEdictStartSearchAfter, pszField, pszValue)); + RETURN_API(edict_t *) +} +static int mm_GetEntityIllum(edict_t *pEnt) { + META_ENGINE_HANDLE(int, 0, FN_GETENTITYILLUM, pfnGetEntityIllum, p, (pEnt)); + RETURN_API(int) +} +static edict_t *mm_FindEntityInSphere(edict_t *pEdictStartSearchAfter, const float *org, float rad) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_FINDENTITYINSPHERE, pfnFindEntityInSphere, 2pf, (pEdictStartSearchAfter, org, rad)); + RETURN_API(edict_t *) +} +static edict_t *mm_FindClientInPVS(edict_t *pEdict) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_FINDCLIENTINPVS, pfnFindClientInPVS, p, (pEdict)); + RETURN_API(edict_t *) +} +static edict_t *mm_EntitiesInPVS(edict_t *pplayer) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_ENTITIESINPVS, pfnEntitiesInPVS, p, (pplayer)); + RETURN_API(edict_t *) +} + +static void mm_MakeVectors(const float *rgflVector) { + META_ENGINE_HANDLE_void(FN_MAKEVECTORS, pfnMakeVectors, p, (rgflVector)); + RETURN_API_void() +} +static void mm_AngleVectors(const float *rgflVector, float *forward, float *right, float *up) { + META_ENGINE_HANDLE_void(FN_ANGLEVECTORS, pfnAngleVectors, 4p, (rgflVector, forward, right, up)); + RETURN_API_void() +} + +static edict_t *mm_CreateEntity(void) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_CREATEENTITY, pfnCreateEntity, void, (VOID_ARG)); + RETURN_API(edict_t *) +} +static void mm_RemoveEntity(edict_t *e) { + META_ENGINE_HANDLE_void(FN_REMOVEENTITY, pfnRemoveEntity, p, (e)); + RETURN_API_void() +} +static edict_t *mm_CreateNamedEntity(int className) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_CREATENAMEDENTITY, pfnCreateNamedEntity, i, (className)); + RETURN_API(edict_t *) +} + +static void mm_MakeStatic(edict_t *ent) { + META_ENGINE_HANDLE_void(FN_MAKESTATIC, pfnMakeStatic, p, (ent)); + RETURN_API_void() +} +static int mm_EntIsOnFloor(edict_t *e) { + META_ENGINE_HANDLE(int, 0, FN_ENTISONFLOOR, pfnEntIsOnFloor, p, (e)); + RETURN_API(int) +} +static int mm_DropToFloor(edict_t *e) { + META_ENGINE_HANDLE(int, 0, FN_DROPTOFLOOR, pfnDropToFloor, p, (e)); + RETURN_API(int) +} + +static int mm_WalkMove(edict_t *ent, float yaw, float dist, int iMode) { + META_ENGINE_HANDLE(int, 0, FN_WALKMOVE, pfnWalkMove, p2fi, (ent, yaw, dist, iMode)); + RETURN_API(int) +} +static void mm_SetOrigin(edict_t *e, const float *rgflOrigin) { + META_ENGINE_HANDLE_void(FN_SETORIGIN, pfnSetOrigin, 2p, (e, rgflOrigin)); + RETURN_API_void() +} + +static void mm_EmitSound(edict_t *entity, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch) { + META_ENGINE_HANDLE_void(FN_EMITSOUND, pfnEmitSound, pip2f2i, (entity, channel, sample, volume, attenuation, fFlags, pitch)); + RETURN_API_void() +} +static void mm_EmitAmbientSound(edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch) { + META_ENGINE_HANDLE_void(FN_EMITAMBIENTSOUND, pfnEmitAmbientSound, 3p2f2i, (entity, pos, samp, vol, attenuation, fFlags, pitch)); + RETURN_API_void() +} + +static void mm_TraceLine(const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr) { + META_ENGINE_HANDLE_void(FN_TRACELINE, pfnTraceLine, 2pi2p, (v1, v2, fNoMonsters, pentToSkip, ptr)); + RETURN_API_void() +} +static void mm_TraceToss(edict_t *pent, edict_t *pentToIgnore, TraceResult *ptr) { + META_ENGINE_HANDLE_void(FN_TRACETOSS, pfnTraceToss, 3p, (pent, pentToIgnore, ptr)); + RETURN_API_void() +} +static int mm_TraceMonsterHull(edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr) { + META_ENGINE_HANDLE(int, 0, FN_TRACEMONSTERHULL, pfnTraceMonsterHull, 3pi2p, (pEdict, v1, v2, fNoMonsters, pentToSkip, ptr)); + RETURN_API(int) +} +static void mm_TraceHull(const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr) { + META_ENGINE_HANDLE_void(FN_TRACEHULL, pfnTraceHull, 2p2i2p, (v1, v2, fNoMonsters, hullNumber, pentToSkip, ptr)); + RETURN_API_void() +} +static void mm_TraceModel(const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr) { + META_ENGINE_HANDLE_void(FN_TRACEMODEL, pfnTraceModel, 2pi2p, (v1, v2, hullNumber, pent, ptr)); + RETURN_API_void() +} +static const char *mm_TraceTexture(edict_t *pTextureEntity, const float *v1, const float *v2 ) { + META_ENGINE_HANDLE(const char *, NULL, FN_TRACETEXTURE, pfnTraceTexture, 3p, (pTextureEntity, v1, v2)); + RETURN_API(const char *) +} +static void mm_TraceSphere(const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr) { + META_ENGINE_HANDLE_void(FN_TRACESPHERE, pfnTraceSphere, 2pif2p, (v1, v2, fNoMonsters, radius, pentToSkip, ptr)); + RETURN_API_void() +} +static void mm_GetAimVector(edict_t *ent, float speed, float *rgflReturn) { + META_ENGINE_HANDLE_void(FN_GETAIMVECTOR, pfnGetAimVector, pfp, (ent, speed, rgflReturn)); + RETURN_API_void() +} + +static void mm_ServerCommand(char *str) { + META_ENGINE_HANDLE_void(FN_SERVERCOMMAND, pfnServerCommand, p, (str)); + RETURN_API_void() +} +static void mm_ServerExecute(void) { + META_ENGINE_HANDLE_void(FN_SERVEREXECUTE, pfnServerExecute, void, (VOID_ARG)); + RETURN_API_void() +} +static void mm_engClientCommand(edict_t *pEdict, char *szFmt, ...) { + META_ENGINE_HANDLE_void_varargs(FN_CLIENTCOMMAND_ENG, pfnClientCommand, 2pV, pEdict, szFmt); + RETURN_API_void() +} + +static void mm_ParticleEffect(const float *org, const float *dir, float color, float count) { + META_ENGINE_HANDLE_void(FN_PARTICLEEFFECT, pfnParticleEffect, 2p2f, (org, dir, color, count)); + RETURN_API_void() +} +static void mm_LightStyle(int style, char *val) { + META_ENGINE_HANDLE_void(FN_LIGHTSTYLE, pfnLightStyle, ip, (style, val)); + RETURN_API_void() +} +static int mm_DecalIndex(const char *name) { + META_ENGINE_HANDLE(int, 0, FN_DECALINDEX, pfnDecalIndex, p, (name)); + RETURN_API(int) +} +static int mm_PointContents(const float *rgflVector) { + META_ENGINE_HANDLE(int, 0, FN_POINTCONTENTS, pfnPointContents, p, (rgflVector)); + RETURN_API(int) +} + +static void mm_MessageBegin(int msg_dest, int msg_type, const float *pOrigin, edict_t *ed) { + META_ENGINE_HANDLE_void(FN_MESSAGEBEGIN, pfnMessageBegin, 2i2p, (msg_dest, msg_type, pOrigin, ed)); + RETURN_API_void() +} +static void mm_MessageEnd(void) { + META_ENGINE_HANDLE_void(FN_MESSAGEEND, pfnMessageEnd, void, (VOID_ARG)); + RETURN_API_void() +} + +static void mm_WriteByte(int iValue) { + META_ENGINE_HANDLE_void(FN_WRITEBYTE, pfnWriteByte, i, (iValue)); + RETURN_API_void() +} +static void mm_WriteChar(int iValue) { + META_ENGINE_HANDLE_void(FN_WRITECHAR, pfnWriteChar, i, (iValue)); + RETURN_API_void() +} +static void mm_WriteShort(int iValue) { + META_ENGINE_HANDLE_void(FN_WRITESHORT, pfnWriteShort, i, (iValue)); + RETURN_API_void() +} +static void mm_WriteLong(int iValue) { + META_ENGINE_HANDLE_void(FN_WRITELONG, pfnWriteLong, i, (iValue)); + RETURN_API_void() +} +static void mm_WriteAngle(float flValue) { + META_ENGINE_HANDLE_void(FN_WRITEANGLE, pfnWriteAngle, f, (flValue)); + RETURN_API_void() +} +static void mm_WriteCoord(float flValue) { + META_ENGINE_HANDLE_void(FN_WRITECOORD, pfnWriteCoord, f, (flValue)); + RETURN_API_void() +} +static void mm_WriteString(const char *sz) { + META_ENGINE_HANDLE_void(FN_WRITESTRING, pfnWriteString, p, (sz)); + RETURN_API_void() +} +static void mm_WriteEntity(int iValue) { + META_ENGINE_HANDLE_void(FN_WRITEENTITY, pfnWriteEntity, i, (iValue)); + RETURN_API_void() +} + +static void mm_CVarRegister(cvar_t *pCvar) { + META_ENGINE_HANDLE_void(FN_CVARREGISTER, pfnCVarRegister, p, (pCvar)); + RETURN_API_void() +} +static float mm_CVarGetFloat(const char *szVarName) { + META_ENGINE_HANDLE(float, 0.0, FN_CVARGETFLOAT, pfnCVarGetFloat, p, (szVarName)); + RETURN_API(float) +} +static const char *mm_CVarGetString(const char *szVarName) { + META_ENGINE_HANDLE(const char *, NULL, FN_CVARGETSTRING, pfnCVarGetString, p, (szVarName)); + RETURN_API(const char *) +} +static void mm_CVarSetFloat(const char *szVarName, float flValue) { + META_ENGINE_HANDLE_void(FN_CVARSETFLOAT, pfnCVarSetFloat, pf, (szVarName, flValue)); + + meta_debug_value = (int)meta_debug.value; + + RETURN_API_void() +} +static void mm_CVarSetString(const char *szVarName, const char *szValue) { + META_ENGINE_HANDLE_void(FN_CVARSETSTRING, pfnCVarSetString, 2p, (szVarName, szValue)); + + meta_debug_value = (int)meta_debug.value; + + RETURN_API_void() +} + +static void mm_AlertMessage(ALERT_TYPE atype, char *szFmt, ...) { + META_ENGINE_HANDLE_void_varargs(FN_ALERTMESSAGE, pfnAlertMessage, ipV, atype, szFmt); + RETURN_API_void() +} +#ifdef HLSDK_3_2_OLD_EIFACE +static void mm_EngineFprintf(FILE *pfile, char *szFmt, ...) { +#else +static void mm_EngineFprintf(void *pfile, char *szFmt, ...) { +#endif + META_ENGINE_HANDLE_void_varargs(FN_ENGINEFPRINTF, pfnEngineFprintf, 2pV, pfile, szFmt); + RETURN_API_void() +} + +#ifdef HLSDK_3_2_OLD_EIFACE +static void *mm_PvAllocEntPrivateData(edict_t *pEdict, long cb) { +#else +static void *mm_PvAllocEntPrivateData(edict_t *pEdict, int32 cb) { +#endif + META_ENGINE_HANDLE(void *, NULL, FN_PVALLOCENTPRIVATEDATA, pfnPvAllocEntPrivateData, pi, (pEdict, cb)); + RETURN_API(void *) +} +static void *mm_PvEntPrivateData(edict_t *pEdict) { + META_ENGINE_HANDLE(void *, NULL, FN_PVENTPRIVATEDATA, pfnPvEntPrivateData, p, (pEdict)); + RETURN_API(void *) +} +static void mm_FreeEntPrivateData(edict_t *pEdict) { + META_ENGINE_HANDLE_void(FN_FREEENTPRIVATEDATA, pfnFreeEntPrivateData, p, (pEdict)); + RETURN_API_void() +} + +static const char *mm_SzFromIndex(int iString) { + META_ENGINE_HANDLE(const char *, NULL, FN_SZFROMINDEX, pfnSzFromIndex, i, (iString)); + RETURN_API(const char *) +} +static int mm_AllocString(const char *szValue) { + META_ENGINE_HANDLE(int, 0, FN_ALLOCSTRING, pfnAllocString, p, (szValue)); + RETURN_API(int) +} + +static struct entvars_s *mm_GetVarsOfEnt(edict_t *pEdict) { + META_ENGINE_HANDLE(struct entvars_s *, NULL, FN_GETVARSOFENT, pfnGetVarsOfEnt, p, (pEdict)); + RETURN_API(struct entvars_s *) +} +static edict_t *mm_PEntityOfEntOffset(int iEntOffset) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_PENTITYOFENTOFFSET, pfnPEntityOfEntOffset, i, (iEntOffset)); + RETURN_API(edict_t *) +} +static int mm_EntOffsetOfPEntity(const edict_t *pEdict) { + META_ENGINE_HANDLE(int, 0, FN_ENTOFFSETOFPENTITY, pfnEntOffsetOfPEntity, p, (pEdict)); + RETURN_API(int) +} +static int mm_IndexOfEdict(const edict_t *pEdict) { + META_ENGINE_HANDLE(int, 0, FN_INDEXOFEDICT, pfnIndexOfEdict, p, (pEdict)); + RETURN_API(int) +} +static edict_t *mm_PEntityOfEntIndex(int iEntIndex) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_PENTITYOFENTINDEX, pfnPEntityOfEntIndex, i, (iEntIndex)); + RETURN_API(edict_t *) +} +static edict_t *mm_FindEntityByVars(struct entvars_s *pvars) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_FINDENTITYBYVARS, pfnFindEntityByVars, p, (pvars)); + RETURN_API(edict_t *) +} +static void *mm_GetModelPtr(edict_t *pEdict) { + META_ENGINE_HANDLE(void *, NULL, FN_GETMODELPTR, pfnGetModelPtr, p, (pEdict)); + RETURN_API(void *) +} + +static int mm_RegUserMsg(const char *pszName, int iSize) { + int imsgid; + MRegMsg *nmsg=NULL; + META_ENGINE_HANDLE(int, 0, FN_REGUSERMSG, pfnRegUserMsg, pi, (pszName, iSize)); + // Expand the macro, since we need to do extra work. + /// RETURN_API(int) + imsgid = GET_RET_CLASS(ret_val, int); + + // Add the msgid, name, and size to our saved list, if we haven't + // already. + nmsg=RegMsgs->find(imsgid); + if(nmsg) { + if(FStrEq(pszName, nmsg->name)) + // This name/msgid pair was already registered. + META_DEBUG(3, ("user message registered again: name=%s, msgid=%d", pszName, imsgid)); + else + // This msgid was previously used by a different message name. + META_WARNING("user message id reused: msgid=%d, oldname=%s, newname=%s", imsgid, nmsg->name, pszName); + } + else + RegMsgs->add(pszName, imsgid, iSize); + return(imsgid); +} + +static void mm_AnimationAutomove(const edict_t *pEdict, float flTime) { + META_ENGINE_HANDLE_void(FN_ANIMATIONAUTOMOVE, pfnAnimationAutomove, pf, (pEdict, flTime)); + RETURN_API_void() +} +static void mm_GetBonePosition(const edict_t *pEdict, int iBone, float *rgflOrigin, float *rgflAngles ) { + META_ENGINE_HANDLE_void(FN_GETBONEPOSITION, pfnGetBonePosition, pi2p, (pEdict, iBone, rgflOrigin, rgflAngles)); + RETURN_API_void() +} + +#ifdef HLSDK_3_2_OLD_EIFACE +static unsigned long mm_FunctionFromName( const char *pName ) { + META_ENGINE_HANDLE(unsigned long, 0, FN_FUNCTIONFROMNAME, pfnFunctionFromName, p, (pName)); + RETURN_API(unsigned long) +} +#else +static uint32 mm_FunctionFromName( const char *pName ) { + META_ENGINE_HANDLE(uint32, 0, FN_FUNCTIONFROMNAME, pfnFunctionFromName, p, (pName)); + RETURN_API(uint32) +} +#endif +#ifdef HLSDK_3_2_OLD_EIFACE +static const char *mm_NameForFunction( unsigned long function ) { +#else +static const char *mm_NameForFunction( uint32 function ) { +#endif + META_ENGINE_HANDLE(const char *, NULL, FN_NAMEFORFUNCTION, pfnNameForFunction, ui, (function)); + RETURN_API(const char *) +} + +//! JOHN: engine callbacks so game DLL can print messages to individual clients +static void mm_ClientPrintf( edict_t *pEdict, PRINT_TYPE ptype, const char *szMsg ) { + META_ENGINE_HANDLE_void(FN_CLIENTPRINTF, pfnClientPrintf, pip, (pEdict, ptype, szMsg)); + RETURN_API_void() +} +static void mm_ServerPrint( const char *szMsg ) { + META_ENGINE_HANDLE_void(FN_SERVERPRINT, pfnServerPrint, p, (szMsg)); + RETURN_API_void() +} + +//! these 3 added so game DLL can easily access client 'cmd' strings +static const char *mm_Cmd_Args( void ) { + META_ENGINE_HANDLE(const char *, NULL, FN_CMD_ARGS, pfnCmd_Args, void, (VOID_ARG)); + RETURN_API(const char *) +} +static const char *mm_Cmd_Argv( int argc ) { + META_ENGINE_HANDLE(const char *, NULL, FN_CMD_ARGV, pfnCmd_Argv, i, (argc)); + RETURN_API(const char *) +} +static int mm_Cmd_Argc( void ) { + META_ENGINE_HANDLE(int, 0, FN_CMD_ARGC, pfnCmd_Argc, void, (VOID_ARG)); + RETURN_API(int) +} + +static void mm_GetAttachment(const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ) { + META_ENGINE_HANDLE_void(FN_GETATTACHMENT, pfnGetAttachment, pi2p, (pEdict, iAttachment, rgflOrigin, rgflAngles)); + RETURN_API_void() +} + +static void mm_CRC32_Init(CRC32_t *pulCRC) { + META_ENGINE_HANDLE_void(FN_CRC32_INIT, pfnCRC32_Init, p, (pulCRC)); + RETURN_API_void() +} +static void mm_CRC32_ProcessBuffer(CRC32_t *pulCRC, void *p, int len) { + META_ENGINE_HANDLE_void(FN_CRC32_PROCESSBUFFER, pfnCRC32_ProcessBuffer, 2pi, (pulCRC, p, len)); + RETURN_API_void() +} +static void mm_CRC32_ProcessByte(CRC32_t *pulCRC, unsigned char ch) { + META_ENGINE_HANDLE_void(FN_CRC32_PROCESSBYTE, pfnCRC32_ProcessByte, puc, (pulCRC, ch)); + RETURN_API_void() +} +static CRC32_t mm_CRC32_Final(CRC32_t pulCRC) { + META_ENGINE_HANDLE(CRC32_t, 0, FN_CRC32_FINAL, pfnCRC32_Final, ul, (pulCRC)); + RETURN_API(CRC32_t) +} + +#ifdef HLSDK_3_2_OLD_EIFACE +static long mm_RandomLong(long lLow, long lHigh) { + META_ENGINE_HANDLE(long, 0, FN_RANDOMLONG, pfnRandomLong, 2i, (lLow, lHigh)); + RETURN_API(long) +} +#else +static int32 mm_RandomLong(int32 lLow, int32 lHigh) { + META_ENGINE_HANDLE(int32, 0, FN_RANDOMLONG, pfnRandomLong, 2i, (lLow, lHigh)); + RETURN_API(int32) +} +#endif +static float mm_RandomFloat(float flLow, float flHigh) { + META_ENGINE_HANDLE(float, 0.0, FN_RANDOMFLOAT, pfnRandomFloat, 2f, (flLow, flHigh)); + RETURN_API(float) +} + +static void mm_SetView(const edict_t *pClient, const edict_t *pViewent ) { + META_ENGINE_HANDLE_void(FN_SETVIEW, pfnSetView, 2p, (pClient, pViewent)); + RETURN_API_void() +} +static float mm_Time( void ) { + META_ENGINE_HANDLE(float, 0.0, FN_TIME, pfnTime, void, (VOID_ARG)); + RETURN_API(float) +} +static void mm_CrosshairAngle(const edict_t *pClient, float pitch, float yaw) { + META_ENGINE_HANDLE_void(FN_CROSSHAIRANGLE, pfnCrosshairAngle, p2f, (pClient, pitch, yaw)); + RETURN_API_void() +} + +static byte * mm_LoadFileForMe(char *filename, int *pLength) { + META_ENGINE_HANDLE(byte *, NULL, FN_LOADFILEFORME, pfnLoadFileForMe, 2p, (filename, pLength)); + RETURN_API(byte *) +} +static void mm_FreeFile(void *buffer) { + META_ENGINE_HANDLE_void(FN_FREEFILE, pfnFreeFile, p, (buffer)); + RETURN_API_void() +} + +//! trigger_endsection +static void mm_EndSection(const char *pszSectionName) { + META_ENGINE_HANDLE_void(FN_ENDSECTION, pfnEndSection, p, (pszSectionName)); + RETURN_API_void() +} +static int mm_CompareFileTime(char *filename1, char *filename2, int *iCompare) { + META_ENGINE_HANDLE(int, 0, FN_COMPAREFILETIME, pfnCompareFileTime, 3p, (filename1, filename2, iCompare)); + RETURN_API(int) +} +static void mm_GetGameDir(char *szGetGameDir) { + META_ENGINE_HANDLE_void(FN_GETGAMEDIR, pfnGetGameDir, p, (szGetGameDir)); + RETURN_API_void() +} +static void mm_Cvar_RegisterVariable(cvar_t *variable) { + META_ENGINE_HANDLE_void(FN_CVAR_REGISTERVARIABLE, pfnCvar_RegisterVariable, p, (variable)); + RETURN_API_void() +} +static void mm_FadeClientVolume(const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds) { + META_ENGINE_HANDLE_void(FN_FADECLIENTVOLUME, pfnFadeClientVolume, p4i, (pEdict, fadePercent, fadeOutSeconds, holdTime, fadeInSeconds)); + RETURN_API_void() +} +static void mm_SetClientMaxspeed(const edict_t *pEdict, float fNewMaxspeed) { + META_ENGINE_HANDLE_void(FN_SETCLIENTMAXSPEED, pfnSetClientMaxspeed, pf, (pEdict, fNewMaxspeed)); + RETURN_API_void() +} +//! returns NULL if fake client can't be created +static edict_t * mm_CreateFakeClient(const char *netname) { + META_ENGINE_HANDLE(edict_t *, NULL, FN_CREATEFAKECLIENT, pfnCreateFakeClient, p, (netname)); + RETURN_API(edict_t *) +} +static void mm_RunPlayerMove(edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec ) { + META_ENGINE_HANDLE_void(FN_RUNPLAYERMOVE, pfnRunPlayerMove, 2p3fus2uc, (fakeclient, viewangles, forwardmove, sidemove, upmove, buttons, impulse, msec)); + RETURN_API_void() +} +static int mm_NumberOfEntities(void) { + META_ENGINE_HANDLE(int, 0, FN_NUMBEROFENTITIES, pfnNumberOfEntities, void, (VOID_ARG)); + RETURN_API(int) +} + +//! passing in NULL gets the serverinfo +static char *mm_GetInfoKeyBuffer(edict_t *e) { + META_ENGINE_HANDLE(char *, NULL, FN_GETINFOKEYBUFFER, pfnGetInfoKeyBuffer, p, (e)); + RETURN_API(char *) +} +static char *mm_InfoKeyValue(char *infobuffer, char *key) { + META_ENGINE_HANDLE(char *, NULL, FN_INFOKEYVALUE, pfnInfoKeyValue, 2p, (infobuffer, key)); + RETURN_API(char *) +} +static void mm_SetKeyValue(char *infobuffer, char *key, char *value) { + META_ENGINE_HANDLE_void(FN_SETKEYVALUE, pfnSetKeyValue, 3p, (infobuffer, key, value)); + RETURN_API_void() +} +static void mm_SetClientKeyValue(int clientIndex, char *infobuffer, char *key, char *value) { + META_ENGINE_HANDLE_void(FN_SETCLIENTKEYVALUE, pfnSetClientKeyValue, i3p, (clientIndex, infobuffer, key, value)); + RETURN_API_void() +} + +static int mm_IsMapValid(char *filename) { + META_ENGINE_HANDLE(int, 0, FN_ISMAPVALID, pfnIsMapValid, p, (filename)); + RETURN_API(int) +} +static void mm_StaticDecal( const float *origin, int decalIndex, int entityIndex, int modelIndex ) { + META_ENGINE_HANDLE_void(FN_STATICDECAL, pfnStaticDecal, p3i, (origin, decalIndex, entityIndex, modelIndex)); + RETURN_API_void() +} +static int mm_PrecacheGeneric(char *s) { + META_ENGINE_HANDLE(int, 0, FN_PRECACHEGENERIC, pfnPrecacheGeneric, p, (s)); + RETURN_API(int) +} +//! returns the server assigned userid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients +static int mm_GetPlayerUserId(edict_t *e ) { + META_ENGINE_HANDLE(int, 0, FN_GETPLAYERUSERID, pfnGetPlayerUserId, p, (e)); + RETURN_API(int) +} +static void mm_BuildSoundMsg(edict_t *entity, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed) +{ + META_ENGINE_HANDLE_void(FN_BUILDSOUNDMSG, pfnBuildSoundMsg, pip2f4i2p, (entity, channel, sample, volume, attenuation, fFlags, pitch, msg_dest, msg_type, pOrigin, ed)); + RETURN_API_void() +} +//! is this a dedicated server? +static int mm_IsDedicatedServer(void) { + META_ENGINE_HANDLE(int, 0, FN_ISDEDICATEDSERVER, pfnIsDedicatedServer, void, (VOID_ARG)); + RETURN_API(int) +} +static cvar_t *mm_CVarGetPointer(const char *szVarName) { + META_ENGINE_HANDLE(cvar_t *, NULL, FN_CVARGETPOINTER, pfnCVarGetPointer, p, (szVarName)); + RETURN_API(cvar_t *) +} +//! returns the server assigned WONid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients +static unsigned int mm_GetPlayerWONId(edict_t *e) { + META_ENGINE_HANDLE(unsigned int, 0, FN_GETPLAYERWONID, pfnGetPlayerWONId, p, (e)); + RETURN_API(unsigned int) +} + +//! YWB 8/1/99 TFF Physics additions +static void mm_Info_RemoveKey( char *s, const char *key ) { + META_ENGINE_HANDLE_void(FN_INFO_REMOVEKEY, pfnInfo_RemoveKey, 2p, (s, key)); + RETURN_API_void() +} +static const char *mm_GetPhysicsKeyValue( const edict_t *pClient, const char *key ) { + META_ENGINE_HANDLE(const char *, NULL, FN_GETPHYSICSKEYVALUE, pfnGetPhysicsKeyValue, 2p, (pClient, key)); + RETURN_API(const char *) +} +static void mm_SetPhysicsKeyValue( const edict_t *pClient, const char *key, const char *value ) { + META_ENGINE_HANDLE_void(FN_SETPHYSICSKEYVALUE, pfnSetPhysicsKeyValue, 3p, (pClient, key, value)); + RETURN_API_void() +} +static const char *mm_GetPhysicsInfoString( const edict_t *pClient ) { + META_ENGINE_HANDLE(const char *, NULL, FN_GETPHYSICSINFOSTRING, pfnGetPhysicsInfoString, p, (pClient)); + RETURN_API(const char *) +} +static unsigned short mm_PrecacheEvent( int type, const char *psz ) { + META_ENGINE_HANDLE(unsigned short, 0, FN_PRECACHEEVENT, pfnPrecacheEvent, ip, (type, psz)); + RETURN_API(unsigned short) +} +static void mm_PlaybackEvent( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) { + META_ENGINE_HANDLE_void(FN_PLAYBACKEVENT, pfnPlaybackEvent, ipusf2p2f4i, (flags, pInvoker, eventindex, delay, origin, angles, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2)); + RETURN_API_void() +} + +static unsigned char *mm_SetFatPVS( float *org ) { + META_ENGINE_HANDLE(unsigned char *, 0, FN_SETFATPVS, pfnSetFatPVS, p, (org)); + RETURN_API(unsigned char *) +} +static unsigned char *mm_SetFatPAS( float *org ) { + META_ENGINE_HANDLE(unsigned char *, 0, FN_SETFATPAS, pfnSetFatPAS, p, (org)); + RETURN_API(unsigned char *) +} + +static int mm_CheckVisibility( const edict_t *entity, unsigned char *pset ) { + META_ENGINE_HANDLE(int, 0, FN_CHECKVISIBILITY, pfnCheckVisibility, 2p, (entity, pset)); + RETURN_API(int) +} + +static void mm_DeltaSetField( struct delta_s *pFields, const char *fieldname ) { + META_ENGINE_HANDLE_void(FN_DELTASETFIELD, pfnDeltaSetField, 2p, (pFields, fieldname)); + RETURN_API_void() +} +static void mm_DeltaUnsetField( struct delta_s *pFields, const char *fieldname ) { + META_ENGINE_HANDLE_void(FN_DELTAUNSETFIELD, pfnDeltaUnsetField, 2p, (pFields, fieldname)); + RETURN_API_void() +} +static void mm_DeltaAddEncoder( char *name, void (*conditionalencode)( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) ) { + META_ENGINE_HANDLE_void(FN_DELTAADDENCODER, pfnDeltaAddEncoder, 2p, (name, (void*)conditionalencode)); + RETURN_API_void() +} +static int mm_GetCurrentPlayer( void ) { + META_ENGINE_HANDLE(int, 0, FN_GETCURRENTPLAYER, pfnGetCurrentPlayer, void, (VOID_ARG)); + RETURN_API(int) +} +static int mm_CanSkipPlayer( const edict_t *player ) { + META_ENGINE_HANDLE(int, 0, FN_CANSKIPPLAYER, pfnCanSkipPlayer, p, (player)); + RETURN_API(int) +} +static int mm_DeltaFindField( struct delta_s *pFields, const char *fieldname ) { + META_ENGINE_HANDLE(int, 0, FN_DELTAFINDFIELD, pfnDeltaFindField, 2p, (pFields, fieldname)); + RETURN_API(int) +} +static void mm_DeltaSetFieldByIndex( struct delta_s *pFields, int fieldNumber ) { + META_ENGINE_HANDLE_void(FN_DELTASETFIELDBYINDEX, pfnDeltaSetFieldByIndex, pi, (pFields, fieldNumber)); + RETURN_API_void() +} +static void mm_DeltaUnsetFieldByIndex( struct delta_s *pFields, int fieldNumber ) { + META_ENGINE_HANDLE_void(FN_DELTAUNSETFIELDBYINDEX, pfnDeltaUnsetFieldByIndex, pi, (pFields, fieldNumber)); + RETURN_API_void() +} + +static void mm_SetGroupMask( int mask, int op ) { + META_ENGINE_HANDLE_void(FN_SETGROUPMASK, pfnSetGroupMask, 2i, (mask, op)); + RETURN_API_void() +} + +static int mm_engCreateInstancedBaseline( int classname, struct entity_state_s *baseline ) { + META_ENGINE_HANDLE(int, 0, FN_CREATEINSTANCEDBASELINE, pfnCreateInstancedBaseline, ip, (classname, baseline)); + RETURN_API(int) +} +static void mm_Cvar_DirectSet( struct cvar_s *var, char *value ) { + META_ENGINE_HANDLE_void(FN_CVAR_DIRECTSET, pfnCvar_DirectSet, 2p, (var, value)); + + meta_debug_value = (int)meta_debug.value; + + RETURN_API_void() +} + +//! Forces the client and server to be running with the same version of the specified file +//!( e.g., a player model ). +//! Calling this has no effect in single player +static void mm_ForceUnmodified( FORCE_TYPE type, float *mins, float *maxs, const char *filename ) { + META_ENGINE_HANDLE_void(FN_FORCEUNMODIFIED, pfnForceUnmodified, i3p, (type, mins, maxs, filename)); + RETURN_API_void() +} + +static void mm_GetPlayerStats( const edict_t *pClient, int *ping, int *packet_loss ) { + META_ENGINE_HANDLE_void(FN_GETPLAYERSTATS, pfnGetPlayerStats, 3p, (pClient, ping, packet_loss)); + RETURN_API_void() +} + +static void mm_AddServerCommand( char *cmd_name, void (*function) (void) ) { + META_ENGINE_HANDLE_void(FN_ADDSERVERCOMMAND, pfnAddServerCommand, 2p, (cmd_name, (void*)function)); + RETURN_API_void() +} + +// Added in SDK 2.2: + +//! For voice communications, set which clients hear eachother. +//! NOTE: these functions take player entity indices (starting at 1). +static qboolean mm_Voice_GetClientListening(int iReceiver, int iSender) { + META_ENGINE_HANDLE(qboolean, false, FN_VOICE_GETCLIENTLISTENING, pfnVoice_GetClientListening, 2i, (iReceiver, iSender)); + RETURN_API(qboolean) +} +static qboolean mm_Voice_SetClientListening(int iReceiver, int iSender, qboolean bListen) { + META_ENGINE_HANDLE(qboolean, false, FN_VOICE_SETCLIENTLISTENING, pfnVoice_SetClientListening, 3i, (iReceiver, iSender, bListen)); + RETURN_API(qboolean) +} + +// Added for HL 1109 (no SDK update): + +static const char *mm_GetPlayerAuthId(edict_t *e) { + META_ENGINE_HANDLE(const char *, NULL, FN_GETPLAYERAUTHID, pfnGetPlayerAuthId, p, (e)); + RETURN_API(const char *) +} + +// Added 2003/11/10 (no SDK update): + +static sequenceEntry_s *mm_SequenceGet(const char *fileName, const char *entryName) { + META_ENGINE_HANDLE(sequenceEntry_s *, NULL, FN_SEQUENCEGET, pfnSequenceGet, 2p, (fileName, entryName)); + RETURN_API(sequenceEntry_s *) +} + +static sentenceEntry_s *mm_SequencePickSentence(const char *groupName, int pickMethod, int *picked) { + META_ENGINE_HANDLE(sentenceEntry_s *, NULL, FN_SEQUENCEPICKSENTENCE, pfnSequencePickSentence, pip, (groupName, pickMethod, picked)); + RETURN_API(sentenceEntry_s *) +} + +static int mm_GetFileSize(char *filename) { + META_ENGINE_HANDLE(int, 0, FN_GETFILESIZE, pfnGetFileSize, p, (filename)); + RETURN_API(int) +} + +static unsigned int mm_GetApproxWavePlayLen(const char *filepath) { + META_ENGINE_HANDLE(unsigned int, 0, FN_GETAPPROXWAVEPLAYLEN, pfnGetApproxWavePlayLen, p, (filepath)); + RETURN_API(unsigned int) +} + +static int mm_IsCareerMatch(void) { + META_ENGINE_HANDLE(int, 0, FN_ISCAREERMATCH, pfnIsCareerMatch, void, (VOID_ARG)); + RETURN_API(int) +} + +static int mm_GetLocalizedStringLength(const char *label) { + META_ENGINE_HANDLE(int, 0, FN_GETLOCALIZEDSTRINGLENGTH, pfnGetLocalizedStringLength, p, (label)); + RETURN_API(int) +} + +static void mm_RegisterTutorMessageShown(int mid) { + META_ENGINE_HANDLE_void(FN_REGISTERTUTORMESSAGESHOWN, pfnRegisterTutorMessageShown, i, (mid)); + RETURN_API_void() +} + +static int mm_GetTimesTutorMessageShown(int mid) { + META_ENGINE_HANDLE(int, 0, FN_GETTIMESTUTORMESSAGESHOWN, pfnGetTimesTutorMessageShown, i, (mid)); + RETURN_API(int) +} + +static void mm_ProcessTutorMessageDecayBuffer(int *buffer, int bufferLength) { + META_ENGINE_HANDLE_void(FN_PROCESSTUTORMESSAGEDECAYBUFFER, pfnProcessTutorMessageDecayBuffer, pi, (buffer, bufferLength)); + RETURN_API_void() +} + +static void mm_ConstructTutorMessageDecayBuffer(int *buffer, int bufferLength) { + META_ENGINE_HANDLE_void(FN_CONSTRUCTTUTORMESSAGEDECAYBUFFER, pfnConstructTutorMessageDecayBuffer, pi, (buffer, bufferLength)); + RETURN_API_void() +} + +static void mm_ResetTutorMessageDecayData(void) { + META_ENGINE_HANDLE_void(FN_RESETTUTORMESSAGEDECAYDATA, pfnResetTutorMessageDecayData, void, (VOID_ARG)); + RETURN_API_void() +} + +// Added 2005/08/11 (no SDK update): +static void mm_QueryClientCvarValue(const edict_t *player, const char *cvarName) { + static mBOOL s_check = mFALSE; + + //Engine version didn't change when this API was added. Check if the pointer is valid. + if (!s_check && g_engfuncs.pfnQueryClientCvarValue && + !IS_VALID_PTR((void * )g_engfuncs.pfnQueryClientCvarValue)) { + g_engfuncs.pfnQueryClientCvarValue = NULL; + s_check = mTRUE; + } + + META_ENGINE_HANDLE_void(FN_QUERYCLIENTCVARVALUE, pfnQueryClientCvarValue, 2p, (player, cvarName)); + RETURN_API_void() +} + +// Added 2005/11/21 (no SDK update): +static void mm_QueryClientCvarValue2(const edict_t *player, const char *cvarName, int requestID) { + static mBOOL s_check = mFALSE; + + //Engine version didn't change when this API was added. Check if the pointer is valid. + if (!s_check && g_engfuncs.pfnQueryClientCvarValue2 && + !IS_VALID_PTR((void * )g_engfuncs.pfnQueryClientCvarValue2)) { + g_engfuncs.pfnQueryClientCvarValue2 = NULL; + s_check = mTRUE; + } + + META_ENGINE_HANDLE_void(FN_QUERYCLIENTCVARVALUE2, pfnQueryClientCvarValue2, 2pi, (player, cvarName, requestID)); + RETURN_API_void() +} + +// Added 2009/06/19 (no SDK update): +static int mm_EngCheckParm(const char *pchCmdLineToken, char **pchNextVal) { + static mBOOL s_check = mFALSE; + + //Engine version didn't change when this API was added. Check if the pointer is valid. + if (!s_check && g_engfuncs.pfnEngCheckParm && + !IS_VALID_PTR((void * )g_engfuncs.pfnEngCheckParm)) { + g_engfuncs.pfnEngCheckParm = NULL; + s_check = mTRUE; + } + + META_ENGINE_HANDLE(int, 0, FN_ENGCHECKPARM, pfnEngCheckParm, 2p, (pchCmdLineToken, pchNextVal)); + RETURN_API(int) +} + +meta_enginefuncs_t meta_engfuncs ( + &mm_PrecacheModel, // pfnPrecacheModel() + &mm_PrecacheSound, // pfnPrecacheSound() + &mm_SetModel, // pfnSetModel() + &mm_ModelIndex, // pfnModelIndex() + &mm_ModelFrames, // pfnModelFrames() + + &mm_SetSize, // pfnSetSize() + &mm_ChangeLevel, // pfnChangeLevel() + &mm_GetSpawnParms, // pfnGetSpawnParms() + &mm_SaveSpawnParms, // pfnSaveSpawnParms() + + &mm_VecToYaw, // pfnVecToYaw() + &mm_VecToAngles, // pfnVecToAngles() + &mm_MoveToOrigin, // pfnMoveToOrigin() + &mm_ChangeYaw, // pfnChangeYaw() + &mm_ChangePitch, // pfnChangePitch() + + &mm_FindEntityByString, // pfnFindEntityByString() + &mm_GetEntityIllum, // pfnGetEntityIllum() + &mm_FindEntityInSphere, // pfnFindEntityInSphere() + &mm_FindClientInPVS, // pfnFindClientInPVS() + &mm_EntitiesInPVS, // pfnEntitiesInPVS() + + &mm_MakeVectors, // pfnMakeVectors() + &mm_AngleVectors, // pfnAngleVectors() + + &mm_CreateEntity, // pfnCreateEntity() + &mm_RemoveEntity, // pfnRemoveEntity() + &mm_CreateNamedEntity, // pfnCreateNamedEntity() + + &mm_MakeStatic, // pfnMakeStatic() + &mm_EntIsOnFloor, // pfnEntIsOnFloor() + &mm_DropToFloor, // pfnDropToFloor() + + &mm_WalkMove, // pfnWalkMove() + &mm_SetOrigin, // pfnSetOrigin() + + &mm_EmitSound, // pfnEmitSound() + &mm_EmitAmbientSound, // pfnEmitAmbientSound() + + &mm_TraceLine, // pfnTraceLine() + &mm_TraceToss, // pfnTraceToss() + &mm_TraceMonsterHull, // pfnTraceMonsterHull() + &mm_TraceHull, // pfnTraceHull() + &mm_TraceModel, // pfnTraceModel() + &mm_TraceTexture, // pfnTraceTexture() + &mm_TraceSphere, // pfnTraceSphere() + &mm_GetAimVector, // pfnGetAimVector() + + &mm_ServerCommand, // pfnServerCommand() + &mm_ServerExecute, // pfnServerExecute() + &mm_engClientCommand, // pfnClientCommand() // D'oh, ClientCommand in dllapi too. + + &mm_ParticleEffect, // pfnParticleEffect() + &mm_LightStyle, // pfnLightStyle() + &mm_DecalIndex, // pfnDecalIndex() + &mm_PointContents, // pfnPointContents() + + &mm_MessageBegin, // pfnMessageBegin() + &mm_MessageEnd, // pfnMessageEnd() + + &mm_WriteByte, // pfnWriteByte() + &mm_WriteChar, // pfnWriteChar() + &mm_WriteShort, // pfnWriteShort() + &mm_WriteLong, // pfnWriteLong() + &mm_WriteAngle, // pfnWriteAngle() + &mm_WriteCoord, // pfnWriteCoord() + &mm_WriteString, // pfnWriteString() + &mm_WriteEntity, // pfnWriteEntity() + + &mm_CVarRegister, // pfnCVarRegister() + &mm_CVarGetFloat, // pfnCVarGetFloat() + &mm_CVarGetString, // pfnCVarGetString() + &mm_CVarSetFloat, // pfnCVarSetFloat() + &mm_CVarSetString, // pfnCVarSetString() + + &mm_AlertMessage, // pfnAlertMessage() + &mm_EngineFprintf, // pfnEngineFprintf() + + &mm_PvAllocEntPrivateData, // pfnPvAllocEntPrivateData() + &mm_PvEntPrivateData, // pfnPvEntPrivateData() + &mm_FreeEntPrivateData, // pfnFreeEntPrivateData() + + &mm_SzFromIndex, // pfnSzFromIndex() + &mm_AllocString, // pfnAllocString() + + &mm_GetVarsOfEnt, // pfnGetVarsOfEnt() + &mm_PEntityOfEntOffset, // pfnPEntityOfEntOffset() + &mm_EntOffsetOfPEntity, // pfnEntOffsetOfPEntity() + &mm_IndexOfEdict, // pfnIndexOfEdict() + &mm_PEntityOfEntIndex, // pfnPEntityOfEntIndex() + &mm_FindEntityByVars, // pfnFindEntityByVars() + &mm_GetModelPtr, // pfnGetModelPtr() + + &mm_RegUserMsg, // pfnRegUserMsg() + + &mm_AnimationAutomove, // pfnAnimationAutomove() + &mm_GetBonePosition, // pfnGetBonePosition() + + &mm_FunctionFromName, // pfnFunctionFromName() + &mm_NameForFunction, // pfnNameForFunction() + + &mm_ClientPrintf, // pfnClientPrintf() //! JOHN: engine callbacks so game DLL can print messages to individual clients + &mm_ServerPrint, // pfnServerPrint() + + &mm_Cmd_Args, // pfnCmd_Args() //! these 3 added + &mm_Cmd_Argv, // pfnCmd_Argv() //! so game DLL can easily + &mm_Cmd_Argc, // pfnCmd_Argc() //! access client 'cmd' strings + + &mm_GetAttachment, // pfnGetAttachment() + + &mm_CRC32_Init, // pfnCRC32_Init() + &mm_CRC32_ProcessBuffer, // pfnCRC32_ProcessBuffer() + &mm_CRC32_ProcessByte, // pfnCRC32_ProcessByte() + &mm_CRC32_Final, // pfnCRC32_Final() + + &mm_RandomLong, // pfnRandomLong() + &mm_RandomFloat, // pfnRandomFloat() + + &mm_SetView, // pfnSetView() + &mm_Time, // pfnTime() + &mm_CrosshairAngle, // pfnCrosshairAngle() + + &mm_LoadFileForMe, // pfnLoadFileForMe() + &mm_FreeFile, // pfnFreeFile() + + &mm_EndSection, // pfnEndSection() //! trigger_endsection + &mm_CompareFileTime, // pfnCompareFileTime() + &mm_GetGameDir, // pfnGetGameDir() + &mm_Cvar_RegisterVariable, // pfnCvar_RegisterVariable() + &mm_FadeClientVolume, // pfnFadeClientVolume() + &mm_SetClientMaxspeed, // pfnSetClientMaxspeed() + &mm_CreateFakeClient, // pfnCreateFakeClient() //! returns NULL if fake client can't be created + &mm_RunPlayerMove, // pfnRunPlayerMove() + &mm_NumberOfEntities, // pfnNumberOfEntities() + + &mm_GetInfoKeyBuffer, // pfnGetInfoKeyBuffer() //! passing in NULL gets the serverinfo + &mm_InfoKeyValue, // pfnInfoKeyValue() + &mm_SetKeyValue, // pfnSetKeyValue() + &mm_SetClientKeyValue, // pfnSetClientKeyValue() + + &mm_IsMapValid, // pfnIsMapValid() + &mm_StaticDecal, // pfnStaticDecal() + &mm_PrecacheGeneric, // pfnPrecacheGeneric() + &mm_GetPlayerUserId, // pfnGetPlayerUserId() //! returns the server assigned userid for this player. + &mm_BuildSoundMsg, // pfnBuildSoundMsg() + &mm_IsDedicatedServer, // pfnIsDedicatedServer() //! is this a dedicated server? + &mm_CVarGetPointer, // pfnCVarGetPointer() + &mm_GetPlayerWONId, // pfnGetPlayerWONId() //! returns the server assigned WONid for this player. + + //! YWB 8/1/99 TFF Physics additions + &mm_Info_RemoveKey, // pfnInfo_RemoveKey() + &mm_GetPhysicsKeyValue, // pfnGetPhysicsKeyValue() + &mm_SetPhysicsKeyValue, // pfnSetPhysicsKeyValue() + &mm_GetPhysicsInfoString, // pfnGetPhysicsInfoString() + &mm_PrecacheEvent, // pfnPrecacheEvent() + &mm_PlaybackEvent, // pfnPlaybackEvent() + + &mm_SetFatPVS, // pfnSetFatPVS() + &mm_SetFatPAS, // pfnSetFatPAS() + + &mm_CheckVisibility, // pfnCheckVisibility() + + &mm_DeltaSetField, // pfnDeltaSetField() + &mm_DeltaUnsetField, // pfnDeltaUnsetField() + &mm_DeltaAddEncoder, // pfnDeltaAddEncoder() + &mm_GetCurrentPlayer, // pfnGetCurrentPlayer() + &mm_CanSkipPlayer, // pfnCanSkipPlayer() + &mm_DeltaFindField, // pfnDeltaFindField() + &mm_DeltaSetFieldByIndex, // pfnDeltaSetFieldByIndex() + &mm_DeltaUnsetFieldByIndex, // pfnDeltaUnsetFieldByIndex() + + &mm_SetGroupMask, // pfnSetGroupMask() + + &mm_engCreateInstancedBaseline, // pfnCreateInstancedBaseline() // D'oh, CreateInstancedBaseline in dllapi too. + &mm_Cvar_DirectSet, // pfnCvar_DirectSet() + + &mm_ForceUnmodified, // pfnForceUnmodified() + + &mm_GetPlayerStats, // pfnGetPlayerStats() + + &mm_AddServerCommand, // pfnAddServerCommand() + + // Added in SDK 2l2: + &mm_Voice_GetClientListening, // pfnVoice_GetClientListening() + &mm_Voice_SetClientListening, // pfnVoice_SetClientListening() + + // Added for HL 1109 (no SDK update): + &mm_GetPlayerAuthId, // pfnGetPlayerAuthId() + + // Added 2003/11/10 (no SDK update): + &mm_SequenceGet, // pfnSequenceGet() + &mm_SequencePickSentence, // pfnSequencePickSentence() + &mm_GetFileSize, // pfnGetFileSize() + &mm_GetApproxWavePlayLen, // pfnGetApproxWavePlayLen() + &mm_IsCareerMatch, // pfnIsCareerMatch() + &mm_GetLocalizedStringLength, // pfnGetLocalizedStringLength() + &mm_RegisterTutorMessageShown, // pfnRegisterTutorMessageShown() + &mm_GetTimesTutorMessageShown, // pfnGetTimesTutorMessageShown() + &mm_ProcessTutorMessageDecayBuffer, // pfnProcessTutorMessageDecayBuffer() + &mm_ConstructTutorMessageDecayBuffer, // pfnConstructTutorMessageDecayBuffer() + &mm_ResetTutorMessageDecayData, // pfnResetTutorMessageDecayData() + + // Added 2005/08/11 (no SDK update): + &mm_QueryClientCvarValue, // pfnQueryClientCvarValue() + + // Added 2005/11/21 (no SDK update): + &mm_QueryClientCvarValue2, // pfnQueryClientCvarValue2() + + // Added 2009/06/17 (no SDK update): + &mm_EngCheckParm // pfnEngCheckParm() +); diff --git a/src/metamod/engine_api.h b/src/metamod/engine_api.h new file mode 100644 index 0000000..151e253 --- /dev/null +++ b/src/metamod/engine_api.h @@ -0,0 +1,240 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// engine_api.h - prototypes and typedefs for Half-Life engine functions + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef ENGINE_API_H +#define ENGINE_API_H + +#include "comp_dep.h" + +// Plugin's GetEngineFunctions, called by metamod. +typedef int (*GET_ENGINE_FUNCTIONS_FN) (enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion); + +// According to SDK engine/eiface.h: +//! enginefuncs_t +//! ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 +#define ENGINE_INTERFACE_VERSION 138 + +// Protect against other projects which use this include file but use the +// normal enginefuncs_t type for their meta_engfuncs. +#ifdef __METAMOD_BUILD__ +# include "meta_eiface.h" // meta_enginefuncs_t +extern meta_enginefuncs_t meta_engfuncs DLLHIDDEN; +#else +extern enginefuncs_t meta_engfuncs DLLHIDDEN; +#endif + +// Typedefs for the above functions: + +typedef int (*FN_PRECACHEMODEL) (char* s); +typedef int (*FN_PRECACHESOUND) (char* s); +typedef void (*FN_SETMODEL) (edict_t *e, const char *m); +typedef int (*FN_MODELINDEX) (const char *m); +typedef int (*FN_MODELFRAMES) (int modelIndex); +typedef void (*FN_SETSIZE) (edict_t *e, const float *rgflMin, const float *rgflMax); +typedef void (*FN_CHANGELEVEL) (char *s1, char *s2); +typedef void (*FN_GETSPAWNPARMS) (edict_t *ent); +typedef void (*FN_SAVESPAWNPARMS) (edict_t *ent); +typedef float (*FN_VECTOYAW) (const float *rgflVector); +typedef void (*FN_VECTOANGLES) (const float *rgflVectorIn, float *rgflVectorOut); +typedef void (*FN_MOVETOORIGIN) (edict_t *ent, const float *pflGoal, float dist, int iMoveType); +typedef void (*FN_CHANGEYAW) (edict_t *ent); +typedef void (*FN_CHANGEPITCH) (edict_t *ent); +typedef edict_t * (*FN_FINDENTITYBYSTRING) (edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue); +typedef int (*FN_GETENTITYILLUM) (edict_t *pEnt); +typedef edict_t * (*FN_FINDENTITYINSPHERE) (edict_t *pEdictStartSearchAfter, const float *org, float rad); +typedef edict_t * (*FN_FINDCLIENTINPVS) (edict_t *pEdict); +typedef edict_t * (*FN_ENTITIESINPVS) (edict_t *pplayer); +typedef void (*FN_MAKEVECTORS) (const float *rgflVector); +typedef void (*FN_ANGLEVECTORS) (const float *rgflVector, float *forward, float *right, float *up); +typedef edict_t * (*FN_CREATEENTITY) (void); +typedef void (*FN_REMOVEENTITY) (edict_t *e); +typedef edict_t * (*FN_CREATENAMEDENTITY) (int className); +typedef void (*FN_MAKESTATIC) (edict_t *ent); +typedef int (*FN_ENTISONFLOOR) (edict_t *e); +typedef int (*FN_DROPTOFLOOR) (edict_t *e); +typedef int (*FN_WALKMOVE) (edict_t *ent, float yaw, float dist, int iMode); +typedef void (*FN_SETORIGIN) (edict_t *e, const float *rgflOrigin); +typedef void (*FN_EMITSOUND) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch); +typedef void (*FN_EMITAMBIENTSOUND) (edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch); +typedef void (*FN_TRACELINE) (const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); +typedef void (*FN_TRACETOSS) (edict_t *pent, edict_t *pentToIgnore, TraceResult *ptr); +typedef int (*FN_TRACEMONSTERHULL) (edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); +typedef void (*FN_TRACEHULL) (const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr); +typedef void (*FN_TRACEMODEL) (const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr); +typedef const char * (*FN_TRACETEXTURE) (edict_t *pTextureEntity, const float *v1, const float *v2 ); +typedef void (*FN_TRACESPHERE) (const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr); +typedef void (*FN_GETAIMVECTOR) (edict_t *ent, float speed, float *rgflReturn); +typedef void (*FN_SERVERCOMMAND) (char *str); +typedef void (*FN_SERVEREXECUTE) (void); +typedef void (*FN_CLIENTCOMMAND_ENG) (edict_t *pEdict, char *szFmt, ...); +typedef void (*FN_PARTICLEEFFECT) (const float *org, const float *dir, float color, float count); +typedef void (*FN_LIGHTSTYLE) (int style, char *val); +typedef int (*FN_DECALINDEX) (const char *name); +typedef int (*FN_POINTCONTENTS) (const float *rgflVector); +typedef void (*FN_MESSAGEBEGIN) (int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); +typedef void (*FN_MESSAGEEND) (void); +typedef void (*FN_WRITEBYTE) (int iValue); +typedef void (*FN_WRITECHAR) (int iValue); +typedef void (*FN_WRITESHORT) (int iValue); +typedef void (*FN_WRITELONG) (int iValue); +typedef void (*FN_WRITEANGLE) (float flValue); +typedef void (*FN_WRITECOORD) (float flValue); +typedef void (*FN_WRITESTRING) (const char *sz); +typedef void (*FN_WRITEENTITY) (int iValue); +typedef void (*FN_CVARREGISTER) (cvar_t *pCvar); +typedef float (*FN_CVARGETFLOAT) (const char *szVarName); +typedef const char * (*FN_CVARGETSTRING) (const char *szVarName); +typedef void (*FN_CVARSETFLOAT) (const char *szVarName, float flValue); +typedef void (*FN_CVARSETSTRING) (const char *szVarName, const char *szValue); +typedef void (*FN_ALERTMESSAGE) (ALERT_TYPE atype, char *szFmt, ...); +#ifdef HLSDK_3_2_OLD_EIFACE +typedef void (*FN_ENGINEFPRINTF) (FILE *pfile, char *szFmt, ...); +typedef void * (*FN_PVALLOCENTPRIVATEDATA) (edict_t *pEdict, long cb); +#else +typedef void (*FN_ENGINEFPRINTF) (void *pfile, char *szFmt, ...); +typedef void * (*FN_PVALLOCENTPRIVATEDATA) (edict_t *pEdict, int32 cb); +#endif +typedef void * (*FN_PVENTPRIVATEDATA) (edict_t *pEdict); +typedef void (*FN_FREEENTPRIVATEDATA) (edict_t *pEdict); +typedef const char * (*FN_SZFROMINDEX) (int iString); +typedef int (*FN_ALLOCSTRING) (const char *szValue); +typedef struct entvars_s * (*FN_GETVARSOFENT) (edict_t *pEdict); +typedef edict_t * (*FN_PENTITYOFENTOFFSET) (int iEntOffset); +typedef int (*FN_ENTOFFSETOFPENTITY) (const edict_t *pEdict); +typedef int (*FN_INDEXOFEDICT) (const edict_t *pEdict); +typedef edict_t * (*FN_PENTITYOFENTINDEX) (int iEntIndex); +typedef edict_t * (*FN_FINDENTITYBYVARS) (struct entvars_s *pvars); +typedef void * (*FN_GETMODELPTR) (edict_t *pEdict); +typedef int (*FN_REGUSERMSG) (const char *pszName, int iSize); +typedef void (*FN_ANIMATIONAUTOMOVE) (const edict_t *pEdict, float flTime); +typedef void (*FN_GETBONEPOSITION) (const edict_t *pEdict, int iBone, float *rgflOrigin, float *rgflAngles ); +#ifdef HLSDK_3_2_OLD_EIFACE +typedef unsigned long (*FN_FUNCTIONFROMNAME) ( const char *pName ); +typedef const char * (*FN_NAMEFORFUNCTION) ( unsigned long function ); +#else +typedef uint32 (*FN_FUNCTIONFROMNAME) ( const char *pName ); +typedef const char * (*FN_NAMEFORFUNCTION) ( uint32 function ); +#endif +typedef void (*FN_CLIENTPRINTF) ( edict_t *pEdict, PRINT_TYPE ptype, const char *szMsg ); +typedef void (*FN_SERVERPRINT) ( const char *szMsg ); +typedef const char * (*FN_CMD_ARGS) ( void ); +typedef const char * (*FN_CMD_ARGV) ( int argc ); +typedef int (*FN_CMD_ARGC) ( void ); +typedef void (*FN_GETATTACHMENT) (const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ); +typedef void (*FN_CRC32_INIT) (CRC32_t *pulCRC); +typedef void (*FN_CRC32_PROCESSBUFFER) (CRC32_t *pulCRC, void *p, int len); +typedef void (*FN_CRC32_PROCESSBYTE) (CRC32_t *pulCRC, unsigned char ch); +typedef CRC32_t (*FN_CRC32_FINAL) (CRC32_t pulCRC); +#ifdef HLSDK_3_2_OLD_EIFACE +typedef long (*FN_RANDOMLONG) (long lLow, long lHigh); +#else +typedef int32 (*FN_RANDOMLONG) (int32 lLow, int32 lHigh); +#endif +typedef float (*FN_RANDOMFLOAT) (float flLow, float flHigh); +typedef void (*FN_SETVIEW) (const edict_t *pClient, const edict_t *pViewent ); +typedef float (*FN_TIME) ( void ); +typedef void (*FN_CROSSHAIRANGLE) (const edict_t *pClient, float pitch, float yaw); +typedef byte * (*FN_LOADFILEFORME) (char *filename, int *pLength); +typedef void (*FN_FREEFILE) (void *buffer); +typedef void (*FN_ENDSECTION) (const char *pszSectionName); +typedef int (*FN_COMPAREFILETIME) (char *filename1, char *filename2, int *iCompare); +typedef void (*FN_GETGAMEDIR) (char *szGetGameDir); +typedef void (*FN_CVAR_REGISTERVARIABLE) (cvar_t *variable); +typedef void (*FN_FADECLIENTVOLUME) (const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds); +typedef void (*FN_SETCLIENTMAXSPEED) (const edict_t *pEdict, float fNewMaxspeed); +typedef edict_t * (*FN_CREATEFAKECLIENT) (const char *netname); +typedef void (*FN_RUNPLAYERMOVE) (edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec ); +typedef int (*FN_NUMBEROFENTITIES) (void); +typedef char * (*FN_GETINFOKEYBUFFER) (edict_t *e); +typedef char * (*FN_INFOKEYVALUE) (char *infobuffer, char *key); +typedef void (*FN_SETKEYVALUE) (char *infobuffer, char *key, char *value); +typedef void (*FN_SETCLIENTKEYVALUE) (int clientIndex, char *infobuffer, char *key, char *value); +typedef int (*FN_ISMAPVALID) (char *filename); +typedef void (*FN_STATICDECAL) ( const float *origin, int decalIndex, int entityIndex, int modelIndex ); +typedef int (*FN_PRECACHEGENERIC) (char *s); +typedef int (*FN_GETPLAYERUSERID) (edict_t *e ); +typedef void (*FN_BUILDSOUNDMSG) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); +typedef int (*FN_ISDEDICATEDSERVER) (void); +typedef cvar_t * (*FN_CVARGETPOINTER) (const char *szVarName); +typedef unsigned int (*FN_GETPLAYERWONID) (edict_t *e); +typedef void (*FN_INFO_REMOVEKEY) ( char *s, const char *key ); +typedef const char * (*FN_GETPHYSICSKEYVALUE) ( const edict_t *pClient, const char *key ); +typedef void (*FN_SETPHYSICSKEYVALUE) ( const edict_t *pClient, const char *key, const char *value ); +typedef const char * (*FN_GETPHYSICSINFOSTRING) ( const edict_t *pClient ); +typedef unsigned short (*FN_PRECACHEEVENT) ( int type, const char *psz ); +typedef void (*FN_PLAYBACKEVENT) ( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +typedef unsigned char * (*FN_SETFATPVS) ( float *org ); +typedef unsigned char * (*FN_SETFATPAS) ( float *org ); +typedef int (*FN_CHECKVISIBILITY) ( const edict_t *entity, unsigned char *pset ); +typedef void (*FN_DELTASETFIELD) ( struct delta_s *pFields, const char *fieldname ); +typedef void (*FN_DELTAUNSETFIELD) ( struct delta_s *pFields, const char *fieldname ); +typedef void (*FN_DELTAADDENCODER) ( char *name, void (*conditionalencode)( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) ); +typedef int (*FN_GETCURRENTPLAYER) ( void ); +typedef int (*FN_CANSKIPPLAYER) ( const edict_t *player ); +typedef int (*FN_DELTAFINDFIELD) ( struct delta_s *pFields, const char *fieldname ); +typedef void (*FN_DELTASETFIELDBYINDEX) ( struct delta_s *pFields, int fieldNumber ); +typedef void (*FN_DELTAUNSETFIELDBYINDEX) ( struct delta_s *pFields, int fieldNumber ); +typedef void (*FN_SETGROUPMASK) ( int mask, int op ); +typedef int (*FN_CREATEINSTANCEDBASELINE) ( int classname, struct entity_state_s *baseline ); +typedef void (*FN_CVAR_DIRECTSET) ( struct cvar_s *var, char *value ); +typedef void (*FN_FORCEUNMODIFIED) ( FORCE_TYPE type, float *mins, float *maxs, const char *filename ); +typedef void (*FN_GETPLAYERSTATS) ( const edict_t *pClient, int *ping, int *packet_loss ); +typedef void (*FN_ADDSERVERCOMMAND) ( char *cmd_name, void (*function) (void) ); +// Added in SDK 2.2: +typedef qboolean (*FN_VOICE_GETCLIENTLISTENING) (int iReceiver, int iSender); +typedef qboolean (*FN_VOICE_SETCLIENTLISTENING) (int iReceiver, int iSender, qboolean bListen); +// Added for HL 1109 (no SDK update): +typedef const char * (*FN_GETPLAYERAUTHID) (edict_t *e); +// Added 2003/11/10 (no SDK update): +typedef sequenceEntry_s * (*FN_SEQUENCEGET) (const char* fileName, const char* entryName); +typedef sentenceEntry_s * (*FN_SEQUENCEPICKSENTENCE) (const char* groupName, int pickMethod, int *picked); +typedef int (*FN_GETFILESIZE) (char *filename); +typedef unsigned int (*FN_GETAPPROXWAVEPLAYLEN) (const char *filepath); +typedef int (*FN_ISCAREERMATCH) (void); +typedef int (*FN_GETLOCALIZEDSTRINGLENGTH) (const char *label); +typedef void (*FN_REGISTERTUTORMESSAGESHOWN) (int mid); +typedef int (*FN_GETTIMESTUTORMESSAGESHOWN) (int mid); +typedef void (*FN_PROCESSTUTORMESSAGEDECAYBUFFER) (int *buffer, int bufferLength); +typedef void (*FN_CONSTRUCTTUTORMESSAGEDECAYBUFFER) (int *buffer, int bufferLength); +typedef void (*FN_RESETTUTORMESSAGEDECAYDATA) (void); +// Added 2005/08/11 (no SDK update): +typedef void (*FN_QUERYCLIENTCVARVALUE) ( const edict_t *player, const char *cvarName ); +// Added 2005/11/21 (no SDK update): +typedef void (*FN_QUERYCLIENTCVARVALUE2) ( const edict_t *player, const char *cvarName, int requestID ); +// Added 2009/06/17 (no SDK update): +typedef void (*FN_ENGCHECKPARM) ( const char *pchCmdLineToken, char **pchNextVal ); + +#endif /* ENGINE_API_H */ diff --git a/src/metamod/engine_t.h b/src/metamod/engine_t.h new file mode 100644 index 0000000..f3ff86b --- /dev/null +++ b/src/metamod/engine_t.h @@ -0,0 +1,82 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// engine_t.h - The engine_t type + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef MM_ENGINE_T_H +#define MM_ENGINE_T_H + +#include "eiface.h" // engfuncs_t, globalvars_t +#include "engineinfo.h" // EngineInfo +#include "comp_dep.h" +#include "osdep.h" //unlikely, OPEN_ARGS + +// Our structure for storing engine references. +struct engine_t { + engine_t() DLLINTERNAL; + engine_t(const engine_t&) DLLINTERNAL; + engine_t& operator=(const engine_t&) DLLINTERNAL; + + enginefuncs_t *funcs; // engine funcs + globalvars_t *globals; // engine globals + enginefuncs_t *pl_funcs; // "modified" eng funcs we give to plugins + EngineInfo info; // some special info elements +}; + +inline engine_t::engine_t() + : funcs(NULL), globals(NULL), pl_funcs(NULL), info() +{ +} + + +inline engine_t::engine_t(const engine_t& _rhs) + : funcs(_rhs.funcs), globals(_rhs.globals), pl_funcs(_rhs.pl_funcs), info(_rhs.info) +{ +} + + +inline engine_t& engine_t::operator=(const engine_t& _rhs) +{ + funcs = _rhs.funcs; + globals = _rhs.globals; + pl_funcs = _rhs.pl_funcs; + info = _rhs.info; + return *this; +} + + +extern engine_t Engine DLLHIDDEN; + +#endif /* MM_ENGINE_T_H */ + diff --git a/src/metamod/enginecallbacks.h b/src/metamod/enginecallbacks.h new file mode 100644 index 0000000..fc1760f --- /dev/null +++ b/src/metamod/enginecallbacks.h @@ -0,0 +1,78 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// enginecallbacks.h - wrapper for + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef MM_ENGINECALLBACKS_H +#define MM_ENGINECALLBACKS_H + +// This file is a wrapper around the SDK's enginecallback.h file. We need +// this because we use a different type for the global object g_engfuncs, +// which is still compatible with the enginefuncs_t that the SDK +// uses. +// This is only done for files that belong to Metamod, not other projects +// like plugins that use this file, or others that include it, too. +// Since we don't have a clean seperation of include files right now we +// "hack" our way around that by using a flag METAMOD_CORE which is set +// when compiling Metamod proper. + +#ifdef __METAMOD_BUILD__ +# include "meta_eiface.h" // HL_enginefuncs_t + +// Use a #define to bend the enginefuncs_t type to our HL_enginefuncs_t +// type instead as we now use that for the global object g_engfuncs. +# define enginefuncs_t HL_enginefuncs_t +#endif /* METAMOD_CORE */ + +#include // ALERT, etc + +#ifdef __METAMOD_BUILD__ +# undef enginefuncs_t +#endif /* METAMOD_CORE */ + +// Also, create some additional macros for engine callback functions, which +// weren't in SDK dlls/enginecallbacks.h but probably should have been. + +#define GET_INFOKEYBUFFER (*g_engfuncs.pfnGetInfoKeyBuffer) +#define INFOKEY_VALUE (*g_engfuncs.pfnInfoKeyValue) +#define SET_CLIENT_KEYVALUE (*g_engfuncs.pfnSetClientKeyValue) +#define REG_SVR_COMMAND (*g_engfuncs.pfnAddServerCommand) +#define SERVER_PRINT (*g_engfuncs.pfnServerPrint) +#define SET_SERVER_KEYVALUE (*g_engfuncs.pfnSetKeyValue) +#define QUERY_CLIENT_CVAR_VALUE (*g_engfuncs.pfnQueryClientCvarValue) +#define QUERY_CLIENT_CVAR_VALUE2 (*g_engfuncs.pfnQueryClientCvarValue2) + + +#endif /* MM_ENGINECALLBACKS_H */ + diff --git a/src/metamod/engineinfo.cpp b/src/metamod/engineinfo.cpp new file mode 100644 index 0000000..f9e575e --- /dev/null +++ b/src/metamod/engineinfo.cpp @@ -0,0 +1,390 @@ + +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// engineinfo.cpp - get info about HL engine, like file used +// and test segment range +// + + +#ifdef _WIN32 + // Don't include winspool.h; clashes with SERVER_EXECUTE from engine +# define _WINSPOOL_H +# include +# include // Header structures +#else +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +# include // dladdr() +# include // ElfW(Phdr/Ehdr) macros. + // _DYNAMIC, r_debug, link_map, etc. +#endif /* _WIN32 */ + +#include // strlen(), strrchr(), strcmp() +#include // printf() + +#include "engineinfo.h" // me +#include "log_meta.h" // META_DEV() + + +// Current mask for checking engine function addresses +// in VAC protected engine dlls on Win32. This is gonna fail +// on Win64. +const unsigned long c_VacDllEngineFuncsRangeMask = 0xFFF00000; +const unsigned long c_VacDllEngineFuncsRangeMark = 0x01D00000; +void* const c_VacDllEngineFuncsRangeStart = (void*)0x01D00000; +void* const c_VacDllEngineFuncsRangeEnd = (void*)0x01E00000; + + +bool DLLINTERNAL EngineInfo::check_for_engine_module( const char* _pName ) +{ + const char* pC; + size_t size; + + if ( NULL == _pName ) return false; + +#ifdef _WIN32 + + // The engine module is either sw.dll or hw.dll or swds.dll + pC = strrchr( _pName, '.' ); + + pC -= 2; + if ( 0 != strcmp(pC, "sw.dll") && 0 != strcmp(pC, "hw.dll") ) { + pC -= 2; + if ( 0 != strcmp(pC, "swds.dll") ) return false; + } + + HMODULE hModule = GetModuleHandle( pC ); + if ( NULL == hModule ) { + return false; + } + + // Ok, we found the string sw(ds).dll, thus we deduct that this is the + // name of the engine's shared object. We copy the string for future + // reference and return successfully. + size = 0; + while ( *pC != '.' && size < c_EngineInfo__typeLen-1 ) { + m_type[size++] = *pC++; + } + m_type[size] = '\0'; + +#else /* _WIN32 */ + + const char* pType; + + + size = strlen( _pName ); + if ( size < 11 ) { + // Forget it, this string is too short to even be 'engine_.so', so + // it can't the name of the engine shared library. + return false; + } + + // Start parsing at the end. Since we know that the string is at least + // 11 characters long we can safely do our parsing without being afraid + // of leaving the string. + pC = _pName + size -1; + + // First we see if the string ends in '.so' + if ( *pC-- != 'o' || *pC-- != 's' || *pC-- != '.' ) return false; + + // Now find the '_' which would be the seperator between 'engine' and + // the architecture. + while ( *pC != '_' && pC != _pName ) --pC; + if ( pC == _pName ) return false; + + // We are at the '_', thus the architecture identifier must start at + // the next character. + pType = pC +1; + + // Now we walk further back and check if we find the string 'engine' + // backwards. + --pC; + if ( *pC-- != 'e' || *pC-- != 'n' || *pC-- != 'i' || + *pC-- != 'g' || *pC-- != 'n' || *pC != 'e' ) { + return false; + } + + // Ok, we found the string engine_*.so, thus we deduct that this is the + // name of the engine's shared object. We copy the architecture string + // for future reference and return successfully. + size = 0; + while ( *pType != '.' && size < c_EngineInfo__typeLen-1 ) { + m_type[size++] = *pType++; + } + m_type[size] = '\0'; + + +#endif /* _WIN32 */ + + + return true; +} + + +#ifdef _WIN32 + +int DLLINTERNAL EngineInfo::nthdr_module_name( void ) +{ + PIMAGE_DOS_HEADER pDosHeader; + PIMAGE_NT_HEADERS pNTHeader; + unsigned char* pBaseAddr; + const char* pName = "sw.dll"; + + if ( ! check_for_engine_module(pName) ) { + pName = "hw.dll"; + if ( ! check_for_engine_module(pName) ) { + pName = "swds.dll"; + if ( ! check_for_engine_module(pName) ) { + return MODULE_NAME_NOTFOUND; + } + } + } + + // Get the module handle for the engine dll we found. + // This is also the modules base address. + HMODULE hModule = GetModuleHandle( pName ); + + pBaseAddr = (unsigned char*)hModule; + + + // Check if we find a DOS header + pDosHeader = (PIMAGE_DOS_HEADER)hModule; + if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { + return INVALID_DOS_SIGN; + } + + // Check if we find a PE header + pNTHeader = (PIMAGE_NT_HEADERS)(pBaseAddr + pDosHeader->e_lfanew); + if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) { + return INVALID_NT_SIGN; + } + + set_code_range( pBaseAddr, pNTHeader ); + return 0; +} + + +int DLLINTERNAL EngineInfo::vac_pe_approx( enginefuncs_t* _pFuncs ) +{ + if ( NULL == _pFuncs ) return INVALID_ARG; + + // There is really no good and easy way to do this. Right now what + // we do is assume that Steam listenservers will normally be + // up-to-date and hence have all function pointers set correctly. + // So we just check for some anomality and then set some approximated + // values. + + // The known addresses at the time of writing all start with 0x01D and + // this lie in the range between 0x01D00000 and 0x01E00000. What we do + // is check all functions pointers that are known to be good to start + // with 0x01D. If that is the case then we know that the loading address + // of the engine functions hasn't changed and we assume the above stated + // range to be the range of valid engine function addresses. + // If we find functions outside this range we can't determine a valid + // range and have to just give up. + + unsigned long* pengfuncs = (unsigned long*)_pFuncs; + unsigned int i, invals = 0; + for ( i = 0, pengfuncs += i; i < 140; i++, pengfuncs++ ) { + if ( ((*pengfuncs) & c_VacDllEngineFuncsRangeMask) != c_VacDllEngineFuncsRangeMark ) { + invals++; + break; + } + } + + if ( invals > 0 ) { + META_DEV( "Found %d engine functions out of range 0x%08lx. " + "Unable to determine valid engine code address range.", + invals, c_VacDllEngineFuncsRangeMark ); + + strncpy( m_type, "vacdll+?", c_EngineInfo__typeLen ); + m_state = STATE_INVALID; + return STATE_INVALID; + } + + m_codeStart = c_VacDllEngineFuncsRangeStart; + m_codeEnd = c_VacDllEngineFuncsRangeEnd; + + strncpy( m_type, "vacdll", c_EngineInfo__typeLen ); + + m_state = STATE_VALID; + + return 0; +} + + +void DLLINTERNAL EngineInfo::set_code_range( unsigned char* _pBase, PIMAGE_NT_HEADERS _pNThdr ) +{ + m_codeStart = _pBase + _pNThdr->OptionalHeader.BaseOfCode; + m_codeEnd = _pBase + _pNThdr->OptionalHeader.BaseOfCode + _pNThdr->OptionalHeader.SizeOfCode; + + m_state = STATE_VALID; +} + + +#else /* _WIN32 */ + + +int DLLINTERNAL EngineInfo::phdr_elfhdr( void* _pElfHdr ) +{ + ElfW(Ehdr)* pEhdr = (ElfW(Ehdr)*)_pElfHdr; + ElfW(Phdr)* pPhdr; + + if ( NULL == _pElfHdr ) return INVALID_ARG; + + if ( pEhdr->e_ident[0] == 0x7f + && pEhdr->e_ident[1] == 'E' + && pEhdr->e_ident[2] == 'L' + && pEhdr->e_ident[3] == 'F' ) { + + // Looking good, we found an ELF signature. + // Let's see if the rest of the data at this address would fit an ELH header, too. + if ( (pEhdr->e_ident[EI_CLASS] == ELFCLASS32 //ELFW(CLASS) + || pEhdr->e_ident[EI_CLASS] == ELFCLASS64) + + && (pEhdr->e_ident[EI_DATA] == ELFDATA2LSB + || pEhdr->e_ident[EI_DATA] == ELFDATA2MSB) + + && pEhdr->e_type == ET_DYN ) { + + // Ok, we believe that this is a shared object's ELF header. + // Let's find the program header for the segment that includes + // the text section and return it. + + pPhdr = (ElfW(Phdr) *) ((char *) pEhdr + pEhdr->e_phoff); + for ( int i = 0; i < pEhdr->e_phnum; i++, pPhdr++ ) { + + if ( PT_LOAD == pPhdr->p_type + && (pPhdr->p_flags & PF_R) + && (pPhdr->p_flags & PF_X) ) { + + // The text section gets loaded and has read and + // execute flags set. So this must be it. + set_code_range( pEhdr, pPhdr ); + return 0; + } + } + } + } + + return HEADER_NOTFOUND; +} + + + + +int DLLINTERNAL EngineInfo::phdr_dladdr( void* _pMem ) +{ + Dl_info info; + + if ( 0 != dladdr(_pMem, &info) ) { + // Check if this is the engine module + if ( check_for_engine_module(info.dli_fname) ) { + return phdr_elfhdr( info.dli_fbase ); + } + } + + return NOTFOUND; +} + + +int DLLINTERNAL EngineInfo::phdr_r_debug( void ) +{ + ElfW(Dyn)* pDyn; + struct r_debug* pr_debug; + struct link_map* pMap; + + + // Search if we have a DT_DEBUG symbol in our DYNAMIC segment, which + // ought to be set to the r_debug structure's address. + for (pDyn = _DYNAMIC; pDyn->d_tag != DT_NULL; ++pDyn) { + if (pDyn->d_tag == DT_DEBUG) { + pr_debug = (struct r_debug *) pDyn->d_un.d_ptr; + break; + } + } + + if ( DT_NULL == pDyn->d_tag ) { + } + else if ( NULL == pr_debug ) { + } + else { + pMap = pr_debug->r_map; + + // Walk to the start of the list + while ( pMap->l_prev != NULL ) pMap = pMap->l_prev; + do { + if ( check_for_engine_module(pMap->l_name) ) { + return phdr_elfhdr( (void*)pMap->l_addr ); + } + + pMap = pMap->l_next; + } while ( NULL != pMap ); + + } + + return NOTFOUND; +} + +void DLLINTERNAL EngineInfo::set_code_range( void* _pBase, ElfW(Phdr)* _pPhdr ) +{ + unsigned char* pBase = (unsigned char*)_pBase; + m_codeStart = pBase + _pPhdr->p_vaddr; + m_codeEnd = pBase + _pPhdr->p_vaddr + _pPhdr->p_memsz; + + m_state = STATE_VALID; +} + + +#endif /* _WIN32 */ + + +int DLLINTERNAL EngineInfo::initialise( enginefuncs_t* _pFuncs ) +{ + int ret = 0; + + // Have to do this only once. + if ( NULL != m_codeStart ) { + return 0; + } + +#ifdef _WIN32 + + ret = nthdr_module_name(); + if ( MODULE_NAME_NOTFOUND == ret && (! _pFuncs->pfnIsDedicatedServer()) ) { + // We could not find the engine dll by name and we are running on + // a listen server. This usually means that that we are dealing with + // a VAC protected engine dll. No other way than approximating the + // range of valid engine function pointers in this case. + ret = vac_pe_approx( _pFuncs ); + } + +#else /* _WIN32 */ + + // If we have no reference pointer to start from we can only try to use + // the r_debug symbol. + if ( NULL == _pFuncs ) { + ret = phdr_r_debug(); + } + + // If we have a refererence pointer we try to use it first. + if ( 0 != phdr_dladdr(_pFuncs) ) { + ret = phdr_r_debug(); + } + +#endif /* _WIN32 */ + + if ( 0 != ret ) { + META_DEV( "Unable to determine engine code address range!" ); + } + else { + META_DEV( "Set engine code range: start address = %p, end address = %p", + m_codeStart, m_codeEnd ); + } + + return 0; +} + + diff --git a/src/metamod/engineinfo.h b/src/metamod/engineinfo.h new file mode 100644 index 0000000..af5eae9 --- /dev/null +++ b/src/metamod/engineinfo.h @@ -0,0 +1,263 @@ + +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// engineinfo.h - info about HL engine, like file used and +// text segment range +// + +#ifndef MM_ENGINEINFO_H +#define MM_ENGINEINFO_H + + + +#ifdef _WIN32 +typedef void* MemAddr; +#else +# include // ElfW(Addr/Phdr) macros +typedef void* MemAddr; +#endif /* _WIN32 */ + +#include "extdll.h" // eiface.h: enginefuncs_t + +#include "comp_dep.h" +#include "osdep.h" //unlikely, OPEN_ARGS +#include "new_baseclass.h" + + +// What we return in is_valid_code_pointer() when the EngineInfo object is +// in an INVALID state, i.e. no code address range could be determined. +static const bool c_DefaultReturnOnInvalidState = true; + +static const int c_EngineInfo__typeLen = 10; + + +class EngineInfo : public class_metamod_new +{ + private: + // data : + + MemAddr m_codeStart; + MemAddr m_codeEnd; + + // State is either NULL when not yet initialised, + // VALID if a code range could be determined + // or INVALID when no valid range for code addresses + // could be determined. + char m_state; + + // Type of engine dso/dll used. + // For Linux this specifies the architecture, i.e. 'i486', 'i686', + // 'amd', 'amd64' etc. + // For Windows this is either 'sw' or 'hw' or 'swds' depending on + // the server type. + char m_type[c_EngineInfo__typeLen]; + + // functions : + + + // Check if string is valid name of engine dso/dll. + bool DLLINTERNAL check_for_engine_module( const char* pName ); + +#ifdef _WIN32 + + // Set info using the PE header found by module name. + // Returns 0 on success, error code on failure. + int DLLINTERNAL nthdr_module_name( void ); + + int DLLINTERNAL vac_pe_approx( enginefuncs_t* pFuncs ); + + // Set code segment start and end from PEheader. The base + // address, that relative addresses are based on, is passed in + // pBase. + void DLLINTERNAL set_code_range( unsigned char* pBase, PIMAGE_NT_HEADERS pNThdr ); + +#else + + // Set info using the Programheader found via r_debug struct. + // Returns 0 on success, error code on failure. + int DLLINTERNAL phdr_r_debug( void ); + // Set info using the Programheader found with reference address + // via dladdr(). Returns 0 on success, error code on failure. + int DLLINTERNAL phdr_dladdr( void* pMem ); + // Set info using the Programheader found via ELF header passed as + // pElfHdr. Return 0 on success, error code on failure. + int DLLINTERNAL phdr_elfhdr( void* pElfHdr ); + // Set code segment start and end from Programheader. The base + // address, that relative addresses are based on, is passed in + // pBase. + void DLLINTERNAL set_code_range( void* pBase, ElfW(Phdr)* pPhdr ); + +#endif /* _WIN32 */ + + public: + // codes : + + enum { + STATE_NULL = 0, + STATE_VALID, + STATE_INVALID, + + MODULE_NAME_NOTFOUND = 5, + INVALID_DOS_SIGN, + INVALID_NT_SIGN, + INVALID_ARG, + HEADER_NOTFOUND, + NOTFOUND + }; + + + // functions : + + EngineInfo() DLLINTERNAL; + EngineInfo& operator=( const EngineInfo& ) DLLINTERNAL; + EngineInfo( const EngineInfo& ) DLLINTERNAL; + + const char* DLLINTERNAL type( void ); + + // Initilaise object, determining the bounds of the code segment of + // the HL engine shared object. + int DLLINTERNAL initialise( enginefuncs_t* pFuncs = NULL ); + + // Test if pMem is within bounds of the code segment. + bool DLLINTERNAL is_valid_code_pointer( void* pMem ); + + // Overloaded versions of above test to keep the ugly pointer + // conversion stuff in here. + bool DLLINTERNAL is_valid_code_pointer( const char* (*fp) (edict_t*) ); + bool DLLINTERNAL is_valid_code_pointer( sequenceEntry_s* (*fp) (const char*, const char*) ); + bool DLLINTERNAL is_valid_code_pointer( sentenceEntry_s* (*fp) (const char*, int, int*) ); + bool DLLINTERNAL is_valid_code_pointer( int (*fp) (char*) ); + bool DLLINTERNAL is_valid_code_pointer( unsigned int (*fp) (const char*) ); + bool DLLINTERNAL is_valid_code_pointer( int (*fp) (void) ); + bool DLLINTERNAL is_valid_code_pointer( int (*fp) (const char*) ); + bool DLLINTERNAL is_valid_code_pointer( void (*fp) (int) ); + bool DLLINTERNAL is_valid_code_pointer( int (*fp) (int) ); + bool DLLINTERNAL is_valid_code_pointer( void (*fp) (int*, int) ); + bool DLLINTERNAL is_valid_code_pointer( void (*fp) (void) ); + bool DLLINTERNAL is_valid_code_pointer( void (*fp) (const edict_t*, const char*) ); + bool DLLINTERNAL is_valid_code_pointer( void (*fp) (const edict_t*, const char*, int) ); + bool DLLINTERNAL is_valid_code_pointer( int (*fp) (const char *, char**) ); +}; + + + +// We probably should run an initialisation here without a reference +// pointer so that the object has valid info in any case. +inline EngineInfo::EngineInfo() : + m_codeStart(NULL), + m_codeEnd(NULL), + m_state(STATE_NULL) +{ + m_type[0] = '\0'; +} + + +inline EngineInfo::EngineInfo( const EngineInfo& _rhs) : + m_codeStart(_rhs.m_codeStart), + m_codeEnd(_rhs.m_codeEnd), + m_state(STATE_NULL) +{ + memcpy( m_type, _rhs.m_type, c_EngineInfo__typeLen ); +} + + +inline EngineInfo& EngineInfo::operator=( const EngineInfo& _rhs) +{ + m_state = _rhs.m_state; + m_codeStart = _rhs.m_codeStart; + m_codeEnd = _rhs.m_codeEnd; + memcpy( m_type, _rhs.m_type, c_EngineInfo__typeLen ); + return *this; +} + + +inline const char* EngineInfo::type( void ) +{ + return m_type; +} + +inline bool EngineInfo::is_valid_code_pointer( void* _pMem ) +{ + if ( STATE_INVALID == m_state ) { + return c_DefaultReturnOnInvalidState; + } + if ( NULL != _pMem && m_codeStart <= _pMem && _pMem <= m_codeEnd ) { + return true; + } + + return false; +} + +inline bool EngineInfo::is_valid_code_pointer( const char* (*_fp) (edict_t*) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( sequenceEntry_s* (*_fp) (const char*, const char*) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( sentenceEntry_s* (*_fp) (const char*, int, int*) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( int (*_fp) (char*) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( unsigned int (*_fp) (const char*) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( int (*_fp) (void) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( int (*_fp) (const char*) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( void (*_fp) (int) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( int (*_fp) (int) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( void (*_fp) (int*, int) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( void (*_fp) (void) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( void (*_fp) (const edict_t*, const char*) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( void (*_fp) (const edict_t*, const char*, int) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +inline bool EngineInfo::is_valid_code_pointer( int (*_fp) (const char*, char**) ) +{ + return is_valid_code_pointer( (void*)_fp ); +} + +#endif /* MM_ENGINEINFO_H */ + diff --git a/src/metamod/game_autodetect.cpp b/src/metamod/game_autodetect.cpp new file mode 100644 index 0000000..2093a5c --- /dev/null +++ b/src/metamod/game_autodetect.cpp @@ -0,0 +1,130 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// autodetect.cpp - GameDLL search and autodetection. + +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // always +#include "osdep_p.h" // is_gamedll, ... +#include "game_autodetect.h" // me +#include "support_meta.h" // full_gamedir_path, + + +// Search gamedir/dlls/*.dll for gamedlls +//TODO: add META_DEBUG +const char * DLLINTERNAL autodetect_gamedll(const gamedll_t *gamedll, const char *knownfn) +{ + static char buf[256]; + char dllpath[256]; + char fnpath[256]; + DIR *dir; + struct dirent *ent; + unsigned int fn_len; + + // Generate dllpath + safevoid_snprintf(buf, sizeof(buf), "%s/dlls", gamedll->gamedir); + if(!full_gamedir_path(buf, dllpath)) { + //whine & return + META_WARNING("GameDLL-Autodetection: Directory '%s' doesn't exist.", buf); + return(0); + } + + // Generate knownfn path + safevoid_snprintf(fnpath, sizeof(fnpath), "%s/%s", dllpath, knownfn); + + // Check if knownfn exists and is valid gamedll + if(is_gamedll(fnpath)) { + // knownfn exists and is loadable gamedll, return 0. + return(0); + } + + // Open directory + if(!(dir = opendir(dllpath))) { + //whine & return + META_WARNING("GameDLL-Autodetection: Couldn't open directory '%s'.", dllpath); + return(0); + } + + while((ent = readdir(dir)) != 0) { + fn_len = strlen(ent->d_name); + + if(fn_len <= strlen(PLATFORM_DLEXT)) { + // Filename is too short + continue; + } + + // Compare end of filename with PLATFORM_DLEXT + if(!strcasematch(&ent->d_name[fn_len - strlen(PLATFORM_DLEXT)], PLATFORM_DLEXT)) { + // File isn't dll + continue; + } + + // Exclude all metamods + if(strncasematch(ent->d_name, "metamod", strlen("metamod"))) { + continue; + } + + // Exclude all bots + STRNCPY(buf, ent->d_name, sizeof(buf)); + strlwr(buf); + if(strstr(buf, "bot.")) { + continue; + } +#ifdef linux + //bot_iX86.so, bot_amd64.so, bot_x86_64.so + if(strstr(buf, "bot_i") || strstr(buf, "bot_amd64.so") || strstr(buf, "bot_x86")) { + continue; + } +#endif + + // Generate full path + safevoid_snprintf(fnpath, sizeof(fnpath), "%s/%s", dllpath, ent->d_name); + + // Check if dll is gamedll + if(is_gamedll(fnpath)) { + META_DEBUG(8, ("is_gamedll(%s): ok.", fnpath)); + //gamedll detected + STRNCPY(buf, ent->d_name, sizeof(buf)); + closedir(dir); + return(buf); + } + META_DEBUG(8, ("is_gamedll(%s): failed.", fnpath)); + } + + //not found + META_WARNING("GameDLL-Autodetection: Couldn't find gamedll in '%s'.", dllpath); + closedir(dir); + + return(0); +} + diff --git a/src/metamod/game_autodetect.h b/src/metamod/game_autodetect.h new file mode 100644 index 0000000..a16b762 --- /dev/null +++ b/src/metamod/game_autodetect.h @@ -0,0 +1,43 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// autodetect.h - GameDLL search and autodetection. + +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef GAME_AUTODETECT_H +#define GAME_AUTODETECT_H + +#include "metamod.h" +const char * DLLINTERNAL autodetect_gamedll(const gamedll_t *gamedll, const char *knownfn); + +#endif /*GAME_AUTODETECT_H*/ diff --git a/src/metamod/game_support.cpp b/src/metamod/game_support.cpp new file mode 100644 index 0000000..28977f3 --- /dev/null +++ b/src/metamod/game_support.cpp @@ -0,0 +1,331 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// game_support.cpp - info to recognize different HL mod "games" + +/* + * Copyright (c) 2001-2013 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ +#include // open, write + +#include // always + +#include "game_support.h" // me +#include "log_meta.h" // META_LOG, etc +#include "types_meta.h" // mBOOL +#include "osdep.h" // win32 snprintf, etc +#include "game_autodetect.h" // autodetect_gamedll +#include "support_meta.h" // MIN + +// Adapted from adminmod h_export.cpp: +//! this structure contains a list of supported mods and their dlls names +//! To add support for another mod add an entry here, and add all the +//! exported entities to link_func.cpp +const game_modlist_t known_games = { + // name/gamedir linux_so win_dll desc + // + // Previously enumerated in this sourcefile, the list is now kept in a + // separate file, generated based on game information stored in a + // convenient db. + // +#include "games.h" + // End of list terminator: + {NULL, NULL, NULL, NULL} +}; + +// Find a modinfo corresponding to the given game name. +const game_modinfo_t * DLLINTERNAL lookup_game(const char *name) { + const game_modinfo_t *imod; + int i; + for(i=0; known_games[i].name; i++) { + imod=&known_games[i]; + if(strcasematch(imod->name, name)) + return(imod); + } + // no match found + return(NULL); +} + +// Installs gamedll from Steam cache +mBOOL DLLINTERNAL install_gamedll(char *from, const char *to) { + int length_in; + int length_out; + + if(!from) + return mFALSE; + if(!to) + to = from; + + byte* cachefile = LOAD_FILE_FOR_ME(from, &length_in); + + // If the file seems to exist in the cache. + if(cachefile) { + int fd=open(to, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); + + if(fd < 0) { + META_DEBUG(3, ("Installing gamedll from cache: Failed to create file %s: %s", to, strerror(errno)) ); + FREE_FILE(cachefile); + return(mFALSE); + } + + length_out=write(fd, cachefile, length_in); + FREE_FILE(cachefile); + close(fd); + + // Writing the file was not successfull + if(length_out != length_in) { + META_DEBUG(3,("Installing gamedll from chache: Failed to write all %d bytes to file, only %d written: %s", length_in, length_out, strerror(errno))); + // Let's not leave a mess but clean up nicely. + if(length_out >= 0) + unlink(to); + + return(mFALSE); + } + + META_LOG("Installed gamedll %s from cache.", to); + } else { + META_DEBUG(3, ("Failed to install gamedll from cache: file %s not found in cache.", from) ); + return(mFALSE); + } + + return(mTRUE); +} + +// Set all the fields in the gamedll struct, - based either on an entry in +// known_games matching the current gamedir, or on one specified manually +// by the server admin. +// +// meta_errno values: +// - ME_NOTFOUND couldn't recognize game +mBOOL DLLINTERNAL setup_gamedll(gamedll_t *gamedll) { +#ifdef __x86_64__ + static char fixname_amd64[NAME_MAX]; // pointer is given outside function +#endif + static char override_desc_buf[NAME_MAX]; // pointer is given outside function + static char autodetect_desc_buf[NAME_MAX]; // pointer is given outside function + char install_path[NAME_MAX]; + const game_modinfo_t *known; + char *cp, *strippedfn; + const char *autofn = 0, *knownfn=0, *usedfn = 0; + int override=0; + + // Check for old-style "metagame.ini" file and complain. + if(valid_gamedir_file(OLD_GAMEDLL_TXT)) + META_WARNING("File '%s' is no longer supported; instead, specify override gamedll in %s or with '+localinfo mm_gamedll '", OLD_GAMEDLL_TXT, CONFIG_INI); + // First, look for a known game, based on gamedir. + if((known=lookup_game(gamedll->name))) { +#ifdef _WIN32 + knownfn=known->win_dll; +#elif defined(linux) + knownfn=known->linux_so; + #ifdef __x86_64__ + //AMD64: convert _i386.so to _amd64.so + if((cp = strstr(knownfn, "_i386.so")) || + (cp = strstr(knownfn, "_i486.so")) || + (cp = strstr(knownfn, "_i586.so")) || + (cp = strstr(knownfn, "_i686.so"))) { + //make sure that it's the ending that has "_iX86.so" + if(cp[strlen("_i386.so")] == 0) { + STRNCPY(fixname_amd64, known->linux_so, + MIN(((size_t)cp - (size_t)knownfn) + 1, + sizeof(fixname_amd64))); + strncat(fixname_amd64, "_amd64.so", sizeof(fixname_amd64)); + + knownfn=fixname_amd64; + } + } + #endif /*__x86_64__*/ +#else +#error "OS unrecognized" +#endif /* _WIN32 */ + + // Do this before autodetecting gamedll from "dlls/*.dll" + if(!Config->gamedll) { +#ifdef linux + // The engine changed game dll lookup behaviour in that it strips + // anything after the last '_' from the name and tries to load the + // resulting name. The DSO names were changed and do not have the + // '_i386' part in them anymore, so cs_i386.so became cs.so. We + // have to adapt to that and try to load the DSO name without the + // '_*' part first, to see if we have a new version file available. + char temp_str[NAME_MAX]; + + STRNCPY(temp_str, knownfn, sizeof(temp_str)); + strippedfn = temp_str; + + char *loc = strrchr(strippedfn, '_'); + + // A small safety net here: make sure that we are dealing with + // a file name at least four characters long and ending in + // '.so'. This way we can be sure that we can safely overwrite + // anything from the '_' on with '.so'. + int size = 0; + const char *ext; + if(0 != loc) { + size = strlen(strippedfn); + ext = strippedfn + (size - 3); + } + + if(0 != loc && size > 3 && 0 == strcasecmp(ext, ".so")) { + strcpy(loc, ".so"); + META_DEBUG(4, ("Checking for new version game DLL name '%s'.\n", strippedfn) ); + + // Again, as above, I abuse the real_pathname member to store the full pathname + // and the pathname member to store the relative name to pass it to the + // install_gamedll function to save stack space. They are going + // to get overwritten later on, so that's ok. + safevoid_snprintf(gamedll->pathname, sizeof(gamedll->pathname), "dlls/%s", + strippedfn); + // Check if the gamedll file exists. If not, try to install it from + // the cache. + mBOOL ok = mTRUE; + if(!valid_gamedir_file(gamedll->pathname)) { + safevoid_snprintf(gamedll->real_pathname, sizeof(gamedll->real_pathname), "%s/dlls/%s", + gamedll->gamedir, strippedfn); + ok = install_gamedll(gamedll->pathname, gamedll->real_pathname); + } + if(ok) + usedfn = strippedfn; + } + else { + META_DEBUG(4, ("Known game DLL name does not qualify for checking for a stripped version, skipping: '%s'.\n", + strippedfn) ); + } +#endif /* linux */ + // If no file to be used was found, try the old known DLL file + // name. + if (0 == usedfn) { + META_DEBUG(4, ("Checking for old version game DLL name '%s'.\n", knownfn) ); + safevoid_snprintf(gamedll->pathname, sizeof(gamedll->pathname), "dlls/%s", knownfn); + // Check if the gamedll file exists. If not, try to install it from + // the cache. + if(!valid_gamedir_file(gamedll->pathname)) { + safevoid_snprintf(gamedll->real_pathname, sizeof(gamedll->real_pathname), "%s/dlls/%s", + gamedll->gamedir, knownfn); + install_gamedll(gamedll->pathname, gamedll->real_pathname); + } + } else { + knownfn = usedfn; + } + } + } + + // Then, autodetect gamedlls in "gamedir/dlls/" + // autodetect_gamedll returns 0 if knownfn exists and is valid gamedll. + if(Config->autodetect && (autofn=autodetect_gamedll(gamedll, knownfn))) { + // If knownfn is set and autodetect_gamedll returns non-null + // then knownfn doesn't exists and we should use autodetected + // dll instead. + if(knownfn) { + // Whine loud about fact that known-list dll doesn't exists! + //META_LOG(plapla); + knownfn = autofn; + } + } + + // Neither override nor known-list nor auto-detect found a gamedll. + if(!known && !Config->gamedll && !autofn) + RETURN_ERRNO(mFALSE, ME_NOTFOUND); + + // Use override-dll if specified. + if(Config->gamedll) { + STRNCPY(gamedll->pathname, Config->gamedll, + sizeof(gamedll->pathname)); + override=1; + + // If the path is relative, the gamedll file will be missing and + // it might be found in the cache file. + if(!is_absolute_path(gamedll->pathname)) { + safevoid_snprintf(install_path, sizeof(install_path), + "%s/%s", gamedll->gamedir, gamedll->pathname); + // If we could successfully install the gamedll from the cache we + // rectify the pathname to be a full pathname. + if(install_gamedll(gamedll->pathname, install_path)) + STRNCPY(gamedll->pathname, install_path, sizeof(gamedll->pathname)); + } + } + // Else use Known-list dll. + else if(known) { + safevoid_snprintf(gamedll->pathname, sizeof(gamedll->pathname), "%s/dlls/%s", + gamedll->gamedir, knownfn); + } + // Else use Autodetect dll. + else { + safevoid_snprintf(gamedll->pathname, sizeof(gamedll->pathname), "%s/dlls/%s", + gamedll->gamedir, autofn); + } + + // get filename from pathname + cp=strrchr(gamedll->pathname, '/'); + if(cp) + cp++; + else + cp=gamedll->pathname; + gamedll->file=cp; + + // If found, store also the supposed "real" dll path based on the + // gamedir, in case it differs from the "override" dll path. + if(known && override) + safevoid_snprintf(gamedll->real_pathname, sizeof(gamedll->real_pathname), + "%s/dlls/%s", gamedll->gamedir, knownfn); + else if(known && autofn) + safevoid_snprintf(gamedll->real_pathname, sizeof(gamedll->real_pathname), + "%s/dlls/%s", gamedll->gamedir, knownfn); + else // !known or (!override and !autofn) + STRNCPY(gamedll->real_pathname, gamedll->pathname, + sizeof(gamedll->real_pathname)); + + if(override) { + // generate a desc + safevoid_snprintf(override_desc_buf, sizeof(override_desc_buf), "%s (override)", gamedll->file); + gamedll->desc=override_desc_buf; + // log result + META_LOG("Overriding game '%s' with dllfile '%s'", gamedll->name, gamedll->file); + } + else if(known && autofn) { + // dll in known-list doesn't exists but we found new one with autodetect. + + // generate a desc + safevoid_snprintf(autodetect_desc_buf, sizeof(autodetect_desc_buf), "%s (autodetect-override)", gamedll->file); + gamedll->desc=autodetect_desc_buf; + META_LOG("Recognized game '%s'; Autodetection override; using dllfile '%s'", gamedll->name, gamedll->file); + } + else if(autofn) { + // generate a desc + safevoid_snprintf(autodetect_desc_buf, sizeof(autodetect_desc_buf), "%s (autodetect)", gamedll->file); + gamedll->desc=autodetect_desc_buf; + META_LOG("Autodetected game '%s'; using dllfile '%s'", gamedll->name, gamedll->file); + } + else if(known) { + gamedll->desc=known->desc; + META_LOG("Recognized game '%s'; using dllfile '%s'", gamedll->name, gamedll->file); + } + return(mTRUE); +} diff --git a/src/metamod/game_support.h b/src/metamod/game_support.h new file mode 100644 index 0000000..61583cc --- /dev/null +++ b/src/metamod/game_support.h @@ -0,0 +1,56 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// game_support.h - structures for supporting different HL mod "games" + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef GAME_SUPPORT_H +#define GAME_SUPPORT_H + +#include "types_meta.h" // mBOOL +#include "metamod.h" // gamedll_t + +// Information we have about each game/mod DLL. +typedef struct game_modinfo_s { + const char *name; // name (the game dir) + const char *linux_so; // filename of linux shared lib + const char *win_dll; // filename of win32 dll + const char *desc; // our long-name description +} game_modinfo_t; + +typedef game_modinfo_t game_modlist_t[]; + +const DLLINTERNAL game_modinfo_t *lookup_game(const char *name); +mBOOL DLLINTERNAL setup_gamedll(gamedll_t *gamedll); + +#endif /* GAME_SUPPORT_H */ diff --git a/src/metamod/games.h b/src/metamod/games.h new file mode 100644 index 0000000..d4c5e61 --- /dev/null +++ b/src/metamod/games.h @@ -0,0 +1,140 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// games.h - list of supported game mods and their data + +/* + * Copyright (c) 2001-2013 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +// This list is now kept in a separate file to facilitate generating the +// list from game data stored in a convenient db. + +#if defined(__x86_64__) || defined(__amd64__) +# define MODARCH "_amd64" +#else +# define MODARCH "_i386" +#endif + + {"action", "ahl"MODARCH".so", "ahl.dll", "Action Half-Life"}, + {"ag", "ag"MODARCH".so", "ag.dll", "Adrenaline Gamer Steam"}, + {"ag3", "hl"MODARCH".so", "hl.dll", "Adrenalinegamer 3.x"}, + {"aghl", "ag"MODARCH".so", "ag.dll", "Adrenalinegamer 4.x"}, + {"arg", "arg"MODARCH".so", "hl.dll", "Arg!"}, + {"asheep", "hl"MODARCH".so", "hl.dll", "Azure Sheep"}, + {"hcfrenzy", "hcfrenzy.so", "hcfrenzy.dll", "Headcrab Frenzy" }, + {"bdef", "../cl_dlls/server.so", "../cl_dlls/server.dll", "Base Defense [Modification]" }, + {"bdef", "server.so", "server.dll", "Base Defense [Steam Version]" }, + {"bg", "bg"MODARCH".so", "bg.dll", "The Battle Grounds"}, + {"bhl", "none", "bhl.dll", "Brutal Half-Life" }, + {"bot", "bot"MODARCH".so", "bot.dll", "Bot"}, + {"brainbread", "bb"MODARCH".so", "bb.dll", "BrainBread"}, + {"bumpercars", "hl"MODARCH".so", "hl.dll", "Bumper Cars"}, + {"buzzybots", "bb"MODARCH".so", "bb.dll", "BuzzyBots"}, + {"ckf3", "none", "mp.dll", "Chicken Fortress 3" }, + {"cs13", "cs"MODARCH".so", "mp.dll", "Counter-Strike 1.3"}, + {"cstrike", "cs"MODARCH".so", "mp.dll", "Counter-Strike"}, + {"csv15", "cs"MODARCH".so", "mp.dll", "CS 1.5 for Steam"}, + {"czero", "cs"MODARCH".so", "mp.dll", "Counter-Strike:Condition Zero"}, + {"dcrisis", "dc"MODARCH".so", "dc.dll", "Desert Crisis"}, + {"dmc", "dmc"MODARCH".so", "dmc.dll", "Deathmatch Classic"}, + {"dod", "dod"MODARCH".so", "dod.dll", "Day of Defeat"}, + {"dpb", "pb.i386.so", "pb.dll", "Digital Paintball"}, + {"dragonmodz", "hl"MODARCH".so", "mp.dll", "Dragon Mod Z"}, + {"esf", "hl"MODARCH".so", "hl.dll", "Earth's Special Forces"}, + {"existence", "ex"MODARCH".so", "existence.dll", "Existence"}, + {"firearms", "fa"MODARCH".so", "firearms.dll", "Firearms"}, + {"firearms25", "fa"MODARCH".so", "firearms.dll", "Retro Firearms"}, + {"freeze", "mp"MODARCH".so", "mp.dll", "Freeze"}, + {"frontline", "front"MODARCH".so", "frontline.dll", "Frontline Force"}, + {"gangstawars", "gangsta"MODARCH".so", "gwars27.dll", "Gangsta Wars"}, + {"gangwars", "mp"MODARCH".so", "mp.dll", "Gangwars"}, + {"gearbox", "opfor"MODARCH".so", "opfor.dll", "Opposing Force"}, + {"globalwarfare", "gw"MODARCH".so", "mp.dll", "Global Warfare"}, + {"goldeneye", "golden"MODARCH".so", "mp.dll", "Goldeneye"}, + {"hl15we", "hl"MODARCH".so", "hl.dll", "Half-Life 1.5: Weapon Edition"}, + {"HLAinGOLDSrc", "none", "hl.dll", "Half-Life Alpha in GOLDSrc"}, + {"hlrally", "hlr"MODARCH".so", "hlrally.dll", "HL-Rally"}, + {"holywars", "hl"MODARCH".so", "holywars.dll", "Holy Wars"}, + {"hostileintent", "hl"MODARCH".so", "hl.dll", "Hostile Intent"}, + {"ios", "ios"MODARCH".so", "ios.dll", "International Online Soccer"}, + {"judgedm", "judge"MODARCH".so", "mp.dll", "Judgement"}, + {"kanonball", "hl"MODARCH".so", "kanonball.dll", "Kanonball"}, + {"monkeystrike", "ms"MODARCH".so", "monkey.dll", "Monkeystrike"}, + {"MorbidPR", "morbid"MODARCH".so", "morbid.dll", "Morbid Inclination"}, + {"movein", "hl"MODARCH".so", "hl.dll", "Move In!"}, + {"msc", "none", "ms.dll", "Master Sword Continued" }, + {"ns", "ns"MODARCH".so", "ns.dll", "Natural Selection"}, + {"nsp", "ns"MODARCH".so", "ns.dll", "Natural Selection Beta"}, + {"oel", "hl"MODARCH".so", "hl.dll", "OeL Half-Life"}, + {"og", "og"MODARCH".so", "og.dll", "Over Ground"}, + {"ol", "ol"MODARCH".so", "hl.dll", "Outlawsmod"}, + {"ops1942", "spirit"MODARCH".so", "spirit.dll", "Operations 1942"}, + {"osjb", "osjb"MODARCH".so", "jail.dll", "Open-Source Jailbreak"}, + {"outbreak", "none", "hl.dll", "Out Break"}, + {"oz", "mp"MODARCH".so", "mp.dll", "Oz Deathmatch"}, + {"paintball", "pb"MODARCH".so", "mp.dll", "Paintball"}, + {"penemy", "pe"MODARCH".so", "pe.dll", "Public Enemy"}, + {"phineas", "phineas"MODARCH".so", "phineas.dll", "Phineas Bot"}, + {"ponreturn", "ponr"MODARCH".so", "mp.dll", "Point of No Return"}, + {"pvk", "hl"MODARCH".so", "hl.dll", "Pirates, Vikings and Knights"}, + {"rc2", "rc2"MODARCH".so", "rc2.dll", "Rocket Crowbar 2"}, + {"recbb2", "recb"MODARCH".so", "recb.dll", "Resident Evil : Cold Blood" }, + {"retrocs", "rcs"MODARCH".so", "rcs.dll", "Retro Counter-Strike"}, + {"rewolf", "hl"MODARCH".so", "gunman.dll", "Gunman Chronicles"}, + {"ricochet", "ricochet"MODARCH".so", "mp.dll", "Ricochet"}, + {"rockcrowbar", "rc"MODARCH".so", "rc.dll", "Rocket Crowbar"}, + {"rspecies", "hl"MODARCH".so", "hl.dll", "Rival Species"}, + {"scihunt", "shunt.so", "shunt.dll", "Scientist Hunt"}, + {"sdm", "sdmmod"MODARCH".so", "sdmmod.dll", "Special Death Match"}, + {"Ship", "ship"MODARCH".so", "ship.dll", "The Ship"}, + {"si", "si"MODARCH".so", "si.dll", "Science & Industry"}, + {"snow", "snow"MODARCH".so", "snow.dll", "Snow-War"}, + {"stargatetc", "hl"MODARCH".so", "hl.dll", "StargateTC"}, + {"svencoop", "hl"MODARCH".so", "hl.dll", "Sven Coop [Modification]" }, + {"svencoop", "server.so", "server.dll", "Sven Coop [Steam Version]" }, + {"swarm", "swarm"MODARCH".so", "swarm.dll", "Swarm"}, + {"tfc", "tfc"MODARCH".so", "tfc.dll", "Team Fortress Classic"}, + {"thewastes", "thewastes"MODARCH".so", "thewastes.dll", "The Wastes"}, + {"timeless", "pt"MODARCH".so", "timeless.dll", "Project Timeless"}, + {"tod", "hl"MODARCH".so", "hl.dll", "Tour of Duty"}, + {"trainhunters", "th"MODARCH".so", "th.dll", "Train Hunters"}, + {"trevenge", "trevenge.so", "trevenge.dll", "The Terrorist Revenge"}, + {"TS", "ts"MODARCH".so", "mp.dll", "The Specialists"}, + {"tt", "tt"MODARCH".so", "tt.dll", "The Trenches"}, + {"underworld", "uw"MODARCH".so", "uw.dll", "Underworld Bloodline"}, + {"valve", "hl"MODARCH".so", "hl.dll", "Half-Life Deathmatch"}, + {"vs", "vs"MODARCH".so", "mp.dll", "VampireSlayer"}, + {"wantedhl", "hl"MODARCH".so", "wanted.dll", "Wanted!"}, + {"wasteland", "whl_linux.so", "mp.dll", "Wasteland"}, + {"weapon_wars", "ww"MODARCH".so", "hl.dll", "Weapon Wars"}, + {"wizwars", "mp"MODARCH".so", "hl.dll", "Wizard Wars"}, + {"wormshl", "wormshl_i586.so", "wormshl.dll", "WormsHL"}, + {"zp", "none", "mp.dll", "Zombie Panic"}, diff --git a/src/metamod/h_export.cpp b/src/metamod/h_export.cpp new file mode 100644 index 0000000..82699b3 --- /dev/null +++ b/src/metamod/h_export.cpp @@ -0,0 +1,108 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// h_export.cpp - main exported DLL functionality + +// From SDK dlls/h_export.cpp: + +/*** +* +* 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. +* +* 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. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + + +#include // always +#include + +#include "h_export.h" // me +#include "metamod.h" // engine_t, etc +#include "log_meta.h" // META_DEV, etc +#include "osdep_p.h" // get_module_handle_of_memptr + +// From SDK dlls/h_export.cpp: + +#ifdef _WIN32 +//! Required DLL entry point +// The above SDK comment indicates this routine is required, but the MSDN +// documentation indicates it's actually optional. We keep it, though, for +// completeness. +// Note! 'extern "C"' needed for mingw compile. +extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) { + metamod_handle = hinstDLL; + } + else if (fdwReason == DLL_PROCESS_DETACH) { + /* nothing */ + } + return(TRUE); +} +#elif defined(linux) +// Linux routines to correspond to ATTACH and DETACH cases above. These +// aren't required by linux, but are included here for completeness, and +// just in case we come across a need to do something at dll load or +// unload. +void _init(void) { + // called before dlopen() returns +} +void _fini(void) { + // called before dlclose() returns +} +#endif + +// Fixed MSVC compiling, by Nikolay "The Storm" Baklicharov. +#if defined(_WIN32) && !defined(__GNUC__) && defined (_MSC_VER) + #pragma comment(linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8,@1") + #pragma comment(linker, "/SECTION:.data,RW") +#endif + +//! Holds engine functionality callbacks +HL_enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; +engine_t Engine; + +// Receive engine function table from engine. +// +// This appears to be the _first_ DLL routine called by the engine, so this +// is where we hook to load all the other DLLs (game, plugins, etc), which +// is actually all done in meta_startup(). +C_DLLEXPORT void WINAPI GiveFnptrsToDll(enginefuncs_t *pengfuncsFromEngine, + globalvars_t *pGlobals) +{ +#ifdef linux + metamod_handle = get_module_handle_of_memptr((void*)&g_engfuncs); +#endif + gpGlobals = pGlobals; + Engine.funcs = &g_engfuncs; + Engine.globals = pGlobals; + Engine.info.initialise(pengfuncsFromEngine); + + g_engfuncs.initialise_interface(pengfuncsFromEngine); + // NOTE! Have to call logging function _after_ initialising g_engfuncs, so + // that g_engfuncs.pfnAlertMessage() can be resolved properly, heh. :) + META_DEV("called: GiveFnptrsToDll"); + + // Load plugins, load game dll. + if(!metamod_startup()) { + metamod_not_loaded = 1; + } + + return; +} diff --git a/src/metamod/h_export.h b/src/metamod/h_export.h new file mode 100644 index 0000000..494d8e0 --- /dev/null +++ b/src/metamod/h_export.h @@ -0,0 +1,49 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// h_export.h - prototypes for h_export.cpp + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef H_EXPORT_H +#define H_EXPORT_H + +#include "osdep.h" // DLLEXPORT, WINAPI, etc + +// Our GiveFnptrsToDll, called by engine. +typedef void (WINAPI *GIVE_ENGINE_FUNCTIONS_FN) (enginefuncs_t + *pengfuncsFromEngine, globalvars_t *pGlobals); + +C_DLLEXPORT void WINAPI GiveFnptrsToDll(enginefuncs_t *pengfuncsFromEngine, + globalvars_t *pGlobals); + +#endif /* H_EXPORT_H */ diff --git a/src/metamod/i386pe.merge b/src/metamod/i386pe.merge new file mode 100644 index 0000000..19ceced --- /dev/null +++ b/src/metamod/i386pe.merge @@ -0,0 +1,205 @@ +/* Special ld script needed, because HL engine does some PE section checking on loaded gamedlls. */ +/* These checks fail and segfault HL process if default ld script is used! */ +OUTPUT_FORMAT(pei-i386) +SEARCH_DIR("/mingw/mingw32/lib"); SEARCH_DIR("/mingw/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib"); +ENTRY(_mainCRTStartup) +SECTIONS +{ + .text __image_base__ + __section_alignment__ : + { + *(.init) + *(.text) + *(SORT(.text$*)) + *(.glue_7t) + *(.glue_7) + ___CTOR_LIST__ = .; __CTOR_LIST__ = . ; + LONG (-1); + *(EXCLUDE_FILE (*crtend.o) .ctors); + *(.ctor); + *(SORT(.ctors.*)); + *crtend.o (.ctors); + LONG (0); + ___DTOR_LIST__ = .; __DTOR_LIST__ = . ; + LONG (-1); + *(EXCLUDE_FILE (*crtend.o) .dtors); + *(.dtor); + *(SORT(.dtors.*)); + *crtend.o (.dtors); + LONG (0); + *(.fini) + /* ??? Why is .gcc_exc here? */ + *(.gcc_exc) + PROVIDE (etext = .); + *(.gcc_except_table) + /*} + .rdata BLOCK(__section_alignment__) : + {*/ + *(.rdata) + *(SORT(.rdata$*)) + } + /* The Cygwin32 library uses a section to avoid copying certain data + on fork. This used to be named ".data". The linker used + to include this between __data_start__ and __data_end__, but that + breaks building the cygwin32 dll. Instead, we name the section + ".data_cygwin_nocopy" and explictly include it after __data_end__. */ + .data BLOCK(__section_alignment__) : + { + __data_start__ = . ; + *(.data) + *(.data2) + *(SORT(.data$*)) + __data_end__ = . ; + *(.data_cygwin_nocopy) + *(.eh_frame) + ___RUNTIME_PSEUDO_RELOC_LIST__ = .; + __RUNTIME_PSEUDO_RELOC_LIST__ = .; + *(.rdata_runtime_pseudo_reloc) + ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .; + __RUNTIME_PSEUDO_RELOC_LIST_END__ = .; + /*} + .bss BLOCK(__section_alignment__) : + {*/ + __bss_start__ = . ; + *(.bss) + *(COMMON) + __bss_end__ = . ; + } + .edata BLOCK(__section_alignment__) : + { + *(.edata) + } + .idata BLOCK(__section_alignment__) : + { + /* This cannot currently be handled with grouped sections. + See pe.em:sort_sections. */ + SORT(*)(.idata$2) + SORT(*)(.idata$3) + /* These zeroes mark the end of the import list. */ + LONG (0); LONG (0); LONG (0); LONG (0); LONG (0); + SORT(*)(.idata$4) + SORT(*)(.idata$5) + SORT(*)(.idata$6) + SORT(*)(.idata$7) + } + /DISCARD/ : + { + *(.debug$S) + *(.debug$T) + *(.debug$F) + *(.drectve) + } + .CRT BLOCK(__section_alignment__) : + { + ___crt_xc_start__ = . ; + *(SORT(.CRT$XC*)) /* C initialization */ + ___crt_xc_end__ = . ; + ___crt_xi_start__ = . ; + *(SORT(.CRT$XI*)) /* C++ initialization */ + ___crt_xi_end__ = . ; + ___crt_xl_start__ = . ; + *(SORT(.CRT$XL*)) /* TLS callbacks */ + /* ___crt_xl_end__ is defined in the TLS Directory support code */ + ___crt_xp_start__ = . ; + *(SORT(.CRT$XP*)) /* Pre-termination */ + ___crt_xp_end__ = . ; + ___crt_xt_start__ = . ; + *(SORT(.CRT$XT*)) /* Termination */ + ___crt_xt_end__ = . ; + } + .tls BLOCK(__section_alignment__) : + { + ___tls_start__ = . ; + *(.tls) + *(.tls$) + *(SORT(.tls$*)) + ___tls_end__ = . ; + } + .endjunk BLOCK(__section_alignment__) : + { + /* end is deprecated, don't use it */ + PROVIDE (end = .); + PROVIDE ( _end = .); + __end__ = .; + } + .rsrc BLOCK(__section_alignment__) : + { + *(.rsrc) + *(SORT(.rsrc$*)) + } + .reloc BLOCK(__section_alignment__) : + { + *(.reloc) + } + .stab BLOCK(__section_alignment__) (NOLOAD) : + { + *(.stab) + } + .stabstr BLOCK(__section_alignment__) (NOLOAD) : + { + *(.stabstr) + } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section. Unlike other targets that fake this by putting the + section VMA at 0, the PE format will not allow it. */ + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_aranges) + } + .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_pubnames) + } + /* DWARF 2. */ + .debug_info BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_info) *(.gnu.linkonce.wi.*) + } + .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_abbrev) + } + .debug_line BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_line) + } + .debug_frame BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_frame) + } + .debug_str BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_str) + } + .debug_loc BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_loc) + } + .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_macinfo) + } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_weaknames) + } + .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_funcnames) + } + .debug_typenames BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_typenames) + } + .debug_varnames BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_varnames) + } + /* DWARF 3. */ + .debug_ranges BLOCK(__section_alignment__) (NOLOAD) : + { + *(.debug_ranges) + } +} diff --git a/src/metamod/info_name.h b/src/metamod/info_name.h new file mode 100644 index 0000000..9df3ae1 --- /dev/null +++ b/src/metamod/info_name.h @@ -0,0 +1,56 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// info_name.h - name, desc, author, etc + +/* + * Copyright (c) 2001-2013 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef INFO_NAME_H +#define INFO_NAME_H + +#include "vers_meta.h" // VDATE, VVERSION, etc + +#define VNAME "Metamod" +#define VAUTHOR "Will Day" +#define VURL "http://www.metamod.org/" + +#define COPYRIGHT_YEAR "2013" + +// Various strings for the Windows DLL Resources in res_meta.rc +#define RC_COMMENTS "Metamod-P is enhanced version of Metamod. Metamod allows running multiple mod-like plugin DLLs, to add functionality or change the behavior of the running HLDS game mod. See " VURL +#define RC_DESC "Metamod-P Half-Life MOD DLL" +#define RC_FILENAME "METAMOD.DLL" +#define RC_INTERNAL "METAMOD-P" +#define RC_COPYRIGHT "Copyright© 2001-" COPYRIGHT_YEAR " Will Day; 2004-" VPATCH_COPYRIGHT_YEAR " Jussi Kivilinna; GPL licensed" +#define RC_LICENSE "Licensed under the GNU General Public License" + +#endif /* INFO_NAME_H */ diff --git a/src/metamod/linkent.h b/src/metamod/linkent.h new file mode 100644 index 0000000..f61eeaa --- /dev/null +++ b/src/metamod/linkent.h @@ -0,0 +1,109 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// linkent.h - export entities from mod "games" back to the HL engine + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef LINK_ENT_H +#define LINK_ENT_H + +#include // always + +#include "osdep.h" // DLLEXPORT, etc +#include "metamod.h" // GameDLL, etc +#include "mlist.h" // MPluginList::find_match, etc +#include "mplugin.h" // MPlugin::info, etc +#include "log_meta.h" // META_DEBUG, etc + + +//Initializes replacement code +int DLLINTERNAL init_linkent_replacement(DLHANDLE moduleMetamod, DLHANDLE moduleGame); + + +// Comments from SDK dlls/util.h: +//! This is the glue that hooks .MAP entity class names to our CPP classes. +//! The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress(). +//! The function is used to intialize / allocate the object for the entity. + +// Adapted from LINK_ENTITY_TO_FUNC in adminmod linkfunc.cpp. + +typedef void (*ENTITY_FN) (entvars_t *); + + +// For now, we have to explicitly export functions for plugin entities, +// just as for gamedll entities. Ideally, this could be generalized in +// some manner, so that plugins can declare and use their own entities +// without having them explicitly supported by metamod, but I don't know +// yet if that is actually possible. +// +// LINK_ENTITY_TO_PLUGIN +// - if plugin not loaded & running, return +// - plugin has to be set loadable=startup only, else log error, return +// - (plugin loaded) if func missing, return +// - (plugin loaded) if func not found, dlsym +// - (plugin loaded) if func still not found, set missing, return +// - (plugin loaded, func found) call func +#define LINK_ENTITY_TO_PLUGIN(entityName, pluginName) \ + C_DLLEXPORT void entityName(entvars_t *pev); \ + void entityName(entvars_t *pev) { \ + static ENTITY_FN pfnEntity = NULL; \ + static int missing=0; \ + char *entStr; \ + MPlugin *findp; \ + entStr = STRINGIZE(entityName, 0); \ + if(missing) \ + return; \ + if(!pfnEntity) { \ + if(!(findp=Plugins->find_match(pluginName))) { \ + META_WARNING("Couldn't find loaded plugin '%s' for plugin entity '%s'", pluginName, entStr); \ + missing=1; \ + return; \ + } \ + if(findp->info && findp->info->loadable != PT_STARTUP) { \ + META_WARNING("Can't link entity '%s' for plugin '%s'; loadable != startup: %s", entStr, pluginName, findp->str_loadable()); \ + missing=1; \ + return; \ + } \ + META_DEBUG(9, ("Looking up plugin entity '%s'", entStr)); \ + pfnEntity = (ENTITY_FN) DLSYM(findp->handle, entStr); \ + } \ + if(!pfnEntity) { \ + META_WARNING("Couldn't find plugin entity '%s' in plugin DLL '%s'", entStr, findp->file); \ + missing=1; \ + return; \ + } \ + META_DEBUG(8, ("Linking plugin entity '%s'", entStr)); \ + (*pfnEntity)(pev); \ + } + +#endif /* LINK_ENT_H */ diff --git a/src/metamod/linkgame.cpp b/src/metamod/linkgame.cpp new file mode 100644 index 0000000..897aec7 --- /dev/null +++ b/src/metamod/linkgame.cpp @@ -0,0 +1,37 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// linkgame.cpp - export entities from mod "games" back to the HL engine + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +//linkents not needed on this version of metamod diff --git a/src/metamod/linkplug.cpp b/src/metamod/linkplug.cpp new file mode 100644 index 0000000..c967e90 --- /dev/null +++ b/src/metamod/linkplug.cpp @@ -0,0 +1,42 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// linkplug.cpp - export entities from plugins back to the HL engine + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // always + +#include "linkent.h" // LINK_ENTITY_TO_PLUGIN + +// Entity lists for plugins +LINK_ENTITY_TO_PLUGIN(adminmod_timer, "adminmod"); diff --git a/src/metamod/log_meta.cpp b/src/metamod/log_meta.cpp new file mode 100644 index 0000000..6801695 --- /dev/null +++ b/src/metamod/log_meta.cpp @@ -0,0 +1,240 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// log_mega.cpp - logging routines + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // vsnprintf, etc +#include // va_start, etc + +#include // always +#include "enginecallbacks.h" // ALERT, etc + +#include "sdk_util.h" // SERVER_PRINT, etc +#include "log_meta.h" // me +#include "osdep.h" // win32 vsnprintf, etc +#include "support_meta.h" // MAX + +cvar_t meta_debug = {"meta_debug", "0", FCVAR_EXTDLL, 0, NULL}; + +int meta_debug_value = 0; //meta_debug_value is converted from float(meta_debug.value) to int on every frame + +enum MLOG_SERVICE { + mlsCONS = 1, + mlsDEV, + mlsIWEL, + mlsCLIENT +}; + +static void buffered_ALERT(MLOG_SERVICE service, ALERT_TYPE atype, const char *prefix, const char *fmt, va_list ap); + +// Print to console. +void DLLINTERNAL META_CONS(const char *fmt, ...) { + va_list ap; + char buf[MAX_LOGMSG_LEN]; + unsigned int len; + + va_start(ap, fmt); + safevoid_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + len=strlen(buf); + if(len < sizeof(buf)-2) { // -1 null, -1 for newline + buf[len+0] = '\n'; + buf[len+1] = 0; + } + else + buf[len-1] = '\n'; + + SERVER_PRINT(buf); +} + +// Log developer-level messages (obsoleted). +static const char *const prefixDEV = "[META] dev:"; +void DLLINTERNAL META_DEV(const char *fmt, ...) { + va_list ap; + int dev; + + if(NULL != g_engfuncs.pfnCVarGetFloat) { + dev=(int) CVAR_GET_FLOAT("developer"); + if(dev==0) return; + } + + va_start(ap, fmt); + buffered_ALERT(mlsDEV, at_logged, prefixDEV, fmt, ap); + va_end(ap); +} + +// Log infos. +static const char *const prefixINFO = "[META] INFO:"; +void DLLINTERNAL META_INFO(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + buffered_ALERT(mlsIWEL, at_logged, prefixINFO, fmt, ap); + va_end(ap); +} + +// Log warnings. +static const char *const prefixWARNING = "[META] WARNING:"; +void DLLINTERNAL META_WARNING(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + buffered_ALERT(mlsIWEL, at_logged, prefixWARNING, fmt, ap); + va_end(ap); +} + +// Log errors. +static const char *const prefixERROR = "[META] ERROR:"; +void DLLINTERNAL META_ERROR(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + buffered_ALERT(mlsIWEL, at_logged, prefixERROR, fmt, ap); + va_end(ap); +} + +// Normal log messages. +static const char *const prefixLOG = "[META]"; +void DLLINTERNAL META_LOG(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + buffered_ALERT(mlsIWEL, at_logged, prefixLOG, fmt, ap); + va_end(ap); +} + +// Print to client. +void DLLINTERNAL META_CLIENT(edict_t *pEntity, const char *fmt, ...) { + va_list ap; + char buf[MAX_CLIENTMSG_LEN]; + unsigned int len; + + va_start(ap, fmt); + safevoid_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + len=strlen(buf); + if(len < sizeof(buf)-2) { // -1 null, -1 for newline + buf[len+0] = '\n'; + buf[len+1] = 0; + } + else + buf[len-1] = '\n'; + + CLIENT_PRINTF(pEntity, print_console, buf); +} + +#ifndef __BUILD_FAST_METAMOD__ + +static int debug_level; + +void DLLINTERNAL META_DEBUG_SET_LEVEL(int level) { + debug_level = level; +} + +void DLLINTERNAL META_DO_DEBUG(const char *fmt, ...) { + char meta_debug_str[1024]; + va_list ap; + + va_start(ap, fmt); + safevoid_vsnprintf(meta_debug_str, sizeof(meta_debug_str), fmt, ap); + va_end(ap); + + ALERT(at_logged, "[META] (debug:%d) %s\n", debug_level, meta_debug_str); +} + +#endif /*!__BUILD_FAST_METAMOD__*/ + +class BufferedMessage : public class_metamod_new { +public: + MLOG_SERVICE service; + ALERT_TYPE atype; + const char *prefix; + char buf[MAX_LOGMSG_LEN]; + BufferedMessage *next; +}; + +static BufferedMessage *messageQueueStart = NULL; +static BufferedMessage *messageQueueEnd = NULL; + +static void buffered_ALERT(MLOG_SERVICE service, ALERT_TYPE atype, const char *prefix, const char *fmt, va_list ap) { + char buf[MAX_LOGMSG_LEN]; + BufferedMessage *msg; + + if (NULL != g_engfuncs.pfnAlertMessage) { + vsnprintf(buf, sizeof(buf), fmt, ap); + ALERT(atype, "%s %s\n", prefix, buf); + return; + } + + // Engine AlertMessage function not available. Buffer message. + msg = new BufferedMessage; + if (NULL == msg) { + // though luck, gonna lose this message + return; + } + msg->service = service; + msg->atype = atype; + msg->prefix = prefix; + vsnprintf(msg->buf, sizeof(buf), fmt, ap); + msg->next = NULL; + + if (NULL == messageQueueEnd) { + messageQueueStart = messageQueueEnd = msg; + } else { + messageQueueEnd->next = msg; + messageQueueEnd = msg; + } +} + + +// Flushes the message queue, printing messages to the respective +// service. This function doesn't check anymore if the g_engfuncs +// jumptable is set. Don't call it if it isn't set. +void DLLINTERNAL flush_ALERT_buffer(void) { + BufferedMessage *msg = messageQueueStart; + int dev = (int) CVAR_GET_FLOAT("developer"); + + while (NULL != msg) { + if(msg->service == mlsDEV && dev==0) { + ; + } else { + ALERT(msg->atype, "b>%s %s\n", msg->prefix, msg->buf); + } + messageQueueStart = messageQueueStart->next; + delete msg; + msg = messageQueueStart; + } + + messageQueueStart = messageQueueEnd = NULL; +} diff --git a/src/metamod/log_meta.h b/src/metamod/log_meta.h new file mode 100644 index 0000000..3b0225f --- /dev/null +++ b/src/metamod/log_meta.h @@ -0,0 +1,116 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// log_meta.h - functions & macros for logging + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef LOG_META_H +#define LOG_META_H + +#include "comp_dep.h" +#include "osdep.h" //unlikely, OPEN_ARGS + +// Debug logging. +// +// This is done as a macro, rather than a function. This way, you can add +// DEBUG statements all over, without worrying about performance +// implications. If the debugging level is set low, all those statements +// will only generate a simple float/int compare each; if we were to use a +// function instead of a macro, it would end up wasting a lot of cpu cycles +// calling/returning from the function every time. With a fair number of +// DEBUG statements, or if they're placed in frequently excuted code, the +// overhead of the wasted function calls could significantly impact server +// performance. +// +// For this reason, we also compare directly to the float value of the +// cvar, rather than calling CVAR_GET_FLOAT() and thus generating a string +// compare for each DEBUG statement. +// +// Called as: +// META_DEBUG(3, ("return code: %d", ret)); +// +// Note the double parens, and the missing parens around "args" in the +// macro itself. Note also the "do..while(0)" loop wrapping the +// statements, so they become a single statement when expanded, necessary +// for times when it might be called as a single-statement result of an +// else (or other flow control). +// +// Yes, it's all a bit of a hack. +// +// Using meta_debug_value instead of meta_debug.value. +// meta_debug_value is preconverted int-value of meta_debug.value. +// Reason for this optimization: Integer compare is much faster than float compare. +// i686 has fast float compare, but since we want to have i386 binary, we use this. + +#ifdef __BUILD_FAST_METAMOD__ + #define META_DEBUG(level, args) do { break; } while(0) +#else + #define META_DEBUG(level, args) \ + do { \ + if(unlikely(meta_debug_value >= level)) { \ + META_DEBUG_SET_LEVEL(level); \ + META_DO_DEBUG args; \ + } \ + } while(0) +#endif + +// max buffer size for printed messages +#define MAX_LOGMSG_LEN 1024 + +// max buffer size for client messages +#define MAX_CLIENTMSG_LEN 128 + +extern cvar_t meta_debug DLLHIDDEN; +extern int meta_debug_value DLLHIDDEN; + +// META_DEV provides debug logging via the cvar "developer" (when set to 1) +// and uses a function call rather than a macro as it's really intended to +// be used only during startup, before meta_debug has been set from reading +// server.cfg. +// NOTE: META_DEV has now been mostly obsoleted in the code. + +void DLLINTERNAL META_CONS(const char *fmt, ...); +void DLLINTERNAL META_DEV(const char *fmt, ...); +void DLLINTERNAL META_INFO(const char *fmt, ...); +void DLLINTERNAL META_WARNING(const char *fmt, ...); +void DLLINTERNAL META_ERROR(const char *fmt, ...); +void DLLINTERNAL META_LOG(const char *fmt, ...); +void DLLINTERNAL META_CLIENT(edict_t *pEntity, const char *fmt, ...); +#ifndef __BUILD_FAST_METAMOD__ + void DLLINTERNAL META_DEBUG_SET_LEVEL(int level); + void DLLINTERNAL META_DO_DEBUG(const char *fmt, ...); +#endif + +void DLLINTERNAL flush_ALERT_buffer(void); + +#endif /* LOG_META_H */ diff --git a/src/metamod/meta_api.h b/src/metamod/meta_api.h new file mode 100644 index 0000000..4480b06 --- /dev/null +++ b/src/metamod/meta_api.h @@ -0,0 +1,232 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// meta_api.h - description of metamod's DLL interface + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef META_API_H +#define META_API_H + +#include "comp_dep.h" +#include "dllapi.h" // GETENTITYAPI_FN, etc +#include "engine_api.h" // GET_ENGINE_FUNCTIONS_FN, etc +#include "plinfo.h" // plugin_info_t, etc +#include "osdep.h" // DLLEXPORT, etc +#include "mutil.h" + +// Version consists of "major:minor", two separate integer numbers. +// Version 1 original +// Version 2 added plugin_info_t **pinfo +// Version 3 init split into query and attach, added detach +// Version 4 added (PLUG_LOADTIME now) to attach +// Version 5 changed mm ifvers int* to string, moved pl ifvers to info +// Version 5:1 added link support for entity "adminmod_timer" (adminmod) +// Version 5:2 added gamedll_funcs to meta_attach() [v1.0-rc2] +// Version 5:3 added mutil_funcs to meta_query() [v1.05] +// Version 5:4 added centersay utility functions [v1.06] +// Version 5:5 added Meta_Init to plugin API [v1.08] +// Version 5:6 added CallGameEntity utility function [v1.09] +// Version 5:7 added GetUserMsgID, GetUserMsgName util funcs [v1.11] +// Version 5:8 added GetPluginPath [v1.11] +// Version 5:9 added GetGameInfo [v1.14] +// Version 5:10 added GINFO_REALDLL_FULLPATH for GetGameInfo [v1.17] +// Version 5:11 added plugin loading and unloading API [v1.18] +// Version 5:12 added IS_QUERYING_CLIENT_CVAR to mutils [v1.18] +// Version 5:13 added MAKE_REQUESTID and GET_HOOK_TABLES to mutils [v1.19] +#define META_INTERFACE_VERSION "5:13" + +// Flags returned by a plugin's api function. +// NOTE: order is crucial, as greater/less comparisons are made. +typedef enum { + MRES_UNSET = 0, + MRES_IGNORED, // plugin didn't take any action + MRES_HANDLED, // plugin did something, but real function should still be called + MRES_OVERRIDE, // call real function, but use my return value + MRES_SUPERCEDE, // skip real function; use my return value +} META_RES; + +// Variables provided to plugins. +typedef struct meta_globals_s { + META_RES mres; // writable; plugin's return flag + META_RES prev_mres; // readable; return flag of the previous plugin called + META_RES status; // readable; "highest" return flag so far + void *orig_ret; // readable; return value from "real" function + void *override_ret; // readable; return value from overriding/superceding plugin +} meta_globals_t; + +extern meta_globals_t *gpMetaGlobals DLLHIDDEN; +#define SET_META_RESULT(result) gpMetaGlobals->mres=result +#define RETURN_META(result) \ + do { gpMetaGlobals->mres=result; return; } while(0) +#define RETURN_META_VALUE(result, value) \ + do { gpMetaGlobals->mres=result; return(value); } while(0) +#define META_RESULT_STATUS gpMetaGlobals->status +#define META_RESULT_PREVIOUS gpMetaGlobals->prev_mres +#define META_RESULT_ORIG_RET(type) *(type *)gpMetaGlobals->orig_ret +#define META_RESULT_OVERRIDE_RET(type) *(type *)gpMetaGlobals->override_ret + +// Table of getapi functions, retrieved from each plugin. +typedef struct { + GETENTITYAPI_FN pfnGetEntityAPI; + GETENTITYAPI_FN pfnGetEntityAPI_Post; + GETENTITYAPI2_FN pfnGetEntityAPI2; + GETENTITYAPI2_FN pfnGetEntityAPI2_Post; + GETNEWDLLFUNCTIONS_FN pfnGetNewDLLFunctions; + GETNEWDLLFUNCTIONS_FN pfnGetNewDLLFunctions_Post; + GET_ENGINE_FUNCTIONS_FN pfnGetEngineFunctions; + GET_ENGINE_FUNCTIONS_FN pfnGetEngineFunctions_Post; +} META_FUNCTIONS; + +// Pair of function tables provided by game DLL. +typedef struct { + DLL_FUNCTIONS *dllapi_table; + NEW_DLL_FUNCTIONS *newapi_table; +} gamedll_funcs_t; + +// Declared in plugin; referenced in macros. +extern gamedll_funcs_t *gpGamedllFuncs DLLHIDDEN; +extern mutil_funcs_t *gpMetaUtilFuncs DLLHIDDEN; + +// Tell the dll that we'll be loading it as a metamod plugin, in case it +// needs to do something special prior to the standard query/attach +// procedure. In particular, this will allow for DLL's that can be used as +// both standalone DLL's and metamod plugins. (optional; not required in +// plugin) +C_DLLEXPORT void Meta_Init(void); +typedef void (*META_INIT_FN) (void); + +// Get info about plugin, compare meta_interface versions, provide meta +// utility callback functions. +C_DLLEXPORT int Meta_Query(char *interfaceVersion, + plugin_info_t **plinfo, + mutil_funcs_t *pMetaUtilFuncs); +typedef int (*META_QUERY_FN) (char *interfaceVersion, + plugin_info_t **plinfo, + mutil_funcs_t *pMetaUtilFuncs); + +// Attach the plugin to the API; get the table of getapi functions; give +// meta_globals and gamedll_funcs. +C_DLLEXPORT int Meta_Attach(PLUG_LOADTIME now, + META_FUNCTIONS *pFunctionTable, + meta_globals_t *pMGlobals, + gamedll_funcs_t *pGamedllFuncs); +typedef int (*META_ATTACH_FN) (PLUG_LOADTIME now, + META_FUNCTIONS *pFunctionTable, + meta_globals_t *pMGlobals, + gamedll_funcs_t *pGamedllFuncs); + +// Detach the plugin; tell why and when. +C_DLLEXPORT int Meta_Detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason); +typedef int (*META_DETACH_FN) (PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + +// Standard HL SDK interface function prototypes. +C_DLLEXPORT int GetEntityAPI_Post(DLL_FUNCTIONS *pFunctionTable, + int interfaceVersion ); +C_DLLEXPORT int GetEntityAPI2_Post(DLL_FUNCTIONS *pFunctionTable, + int *interfaceVersion ); + +// Additional SDK-like interface function prototypes. +C_DLLEXPORT int GetNewDLLFunctions_Post(NEW_DLL_FUNCTIONS *pNewFunctionTable, + int *interfaceVersion ); +C_DLLEXPORT int GetEngineFunctions(enginefuncs_t *pengfuncsFromEngine, + int *interfaceVersion); +C_DLLEXPORT int GetEngineFunctions_Post(enginefuncs_t *pengfuncsFromEngine, + int *interfaceVersion); + +// Convenience macros for accessing GameDLL functions. Note: these talk +// _directly_ to the gamedll, and are not multiplexed through Metamod to +// the other plugins. + +// DLL API functions: +#define MDLL_FUNC gpGamedllFuncs->dllapi_table + +#define MDLL_GameDLLInit MDLL_FUNC->pfnGameInit +#define MDLL_Spawn MDLL_FUNC->pfnSpawn +#define MDLL_Think MDLL_FUNC->pfnThink +#define MDLL_Use MDLL_FUNC->pfnUse +#define MDLL_Touch MDLL_FUNC->pfnTouch +#define MDLL_Blocked MDLL_FUNC->pfnBlocked +#define MDLL_KeyValue MDLL_FUNC->pfnKeyValue +#define MDLL_Save MDLL_FUNC->pfnSave +#define MDLL_Restore MDLL_FUNC->pfnRestore +#define MDLL_ObjectCollsionBox MDLL_FUNC->pfnAbsBox +#define MDLL_SaveWriteFields MDLL_FUNC->pfnSaveWriteFields +#define MDLL_SaveReadFields MDLL_FUNC->pfnSaveReadFields +#define MDLL_SaveGlobalState MDLL_FUNC->pfnSaveGlobalState +#define MDLL_RestoreGlobalState MDLL_FUNC->pfnRestoreGlobalState +#define MDLL_ResetGlobalState MDLL_FUNC->pfnResetGlobalState +#define MDLL_ClientConnect MDLL_FUNC->pfnClientConnect +#define MDLL_ClientDisconnect MDLL_FUNC->pfnClientDisconnect +#define MDLL_ClientKill MDLL_FUNC->pfnClientKill +#define MDLL_ClientPutInServer MDLL_FUNC->pfnClientPutInServer +#define MDLL_ClientCommand MDLL_FUNC->pfnClientCommand +#define MDLL_ClientUserInfoChanged MDLL_FUNC->pfnClientUserInfoChanged +#define MDLL_ServerActivate MDLL_FUNC->pfnServerActivate +#define MDLL_ServerDeactivate MDLL_FUNC->pfnServerDeactivate +#define MDLL_PlayerPreThink MDLL_FUNC->pfnPlayerPreThink +#define MDLL_PlayerPostThink MDLL_FUNC->pfnPlayerPostThink +#define MDLL_StartFrame MDLL_FUNC->pfnStartFrame +#define MDLL_ParmsNewLevel MDLL_FUNC->pfnParmsNewLevel +#define MDLL_ParmsChangeLevel MDLL_FUNC->pfnParmsChangeLevel +#define MDLL_GetGameDescription MDLL_FUNC->pfnGetGameDescription +#define MDLL_PlayerCustomization MDLL_FUNC->pfnPlayerCustomization +#define MDLL_SpectatorConnect MDLL_FUNC->pfnSpectatorConnect +#define MDLL_SpectatorDisconnect MDLL_FUNC->pfnSpectatorDisconnect +#define MDLL_SpectatorThink MDLL_FUNC->pfnSpectatorThink +#define MDLL_Sys_Error MDLL_FUNC->pfnSys_Error +#define MDLL_PM_Move MDLL_FUNC->pfnPM_Move +#define MDLL_PM_Init MDLL_FUNC->pfnPM_Init +#define MDLL_PM_FindTextureType MDLL_FUNC->pfnPM_FindTextureType +#define MDLL_SetupVisibility MDLL_FUNC->pfnSetupVisibility +#define MDLL_UpdateClientData MDLL_FUNC->pfnUpdateClientData +#define MDLL_AddToFullPack MDLL_FUNC->pfnAddToFullPack +#define MDLL_CreateBaseline MDLL_FUNC->pfnCreateBaseline +#define MDLL_RegisterEncoders MDLL_FUNC->pfnRegisterEncoders +#define MDLL_GetWeaponData MDLL_FUNC->pfnGetWeaponData +#define MDLL_CmdStart MDLL_FUNC->pfnCmdStart +#define MDLL_CmdEnd MDLL_FUNC->pfnCmdEnd +#define MDLL_ConnectionlessPacket MDLL_FUNC->pfnConnectionlessPacket +#define MDLL_GetHullBounds MDLL_FUNC->pfnGetHullBounds +#define MDLL_CreateInstancedBaselines MDLL_FUNC->pfnCreateInstancedBaselines +#define MDLL_InconsistentFile MDLL_FUNC->pfnInconsistentFile +#define MDLL_AllowLagCompensation MDLL_FUNC->pfnAllowLagCompensation + +// NEW API functions: +#define MNEW_FUNC gpGamedllFuncs->newapi_table + +#define MNEW_OnFreeEntPrivateData MNEW_FUNC->pfnOnFreeEntPrivateData +#define MNEW_GameShutdown MNEW_FUNC->pfnGameShutdown +#define MNEW_ShouldCollide MNEW_FUNC->pfnShouldCollide +#define MNEW_CvarValue MNEW_FUNC->pfnCvarValue + +#endif /* META_API_H */ diff --git a/src/metamod/meta_eiface.cpp b/src/metamod/meta_eiface.cpp new file mode 100644 index 0000000..5328b94 --- /dev/null +++ b/src/metamod/meta_eiface.cpp @@ -0,0 +1,724 @@ + +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// meta_eiface.cpp - wrapper for engine/dll interface + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // always + +#include // fprintf() +#include // exit() +#include // memset(), memcpy() + +#include "meta_eiface.h" // me +#include "dllapi.h" // FN_CVARVALUE, FN_CVARVALUE2 +#include "engine_t.h" // Engine + + +// ------------------------------------------------------------------------ +// meta_new_dll_functions_t +// ------------------------------------------------------------------------ + +// static member initialisation +int meta_new_dll_functions_t::sm_version DLLHIDDEN = 0; + + +meta_new_dll_functions_t::meta_new_dll_functions_t( + void (*_pfnOnFreeEntPrivateData) (edict_t*), + void (*_pfnGameShutdown) (void), + int (*_pfnShouldCollide) (edict_t*, edict_t*), + void (*_pfnCvarValue) (const edict_t*, const char*), + void (*_pfnCvarValue2) (const edict_t*, int, const char*, const char*) + ) +{ + pfnOnFreeEntPrivateData = _pfnOnFreeEntPrivateData; + pfnGameShutdown = _pfnGameShutdown; + pfnShouldCollide = _pfnShouldCollide; + pfnCvarValue = _pfnCvarValue; + pfnCvarValue2 = _pfnCvarValue2; + + memset( dummies, 0, sizeof(pdummyfunc) * c_NumDummies ); +} + + + +void DLLINTERNAL meta_new_dll_functions_t::copy_to( NEW_DLL_FUNCTIONS *_pFuncs ) +{ + // This is where the magic happens. We check what version of the + // NEW_DLL_FUNCTIONS interface the engine has and calculate the size of + // that interface. Then we only copy the function pointers present in + // that version over to the receiver, so that we do not overwrite his + // memory with functions that he doesn't know of in his copy of the + // struct. + size_t size = get_size(); + + if ( 0 == size ) { + // Ok, this is a real problem and should *not* happen. + // We try to work with NEW_DLL_FUNCTIONS without knowing what + // interface the attached engine uses. This means that the classes + // defined herein are not used in the way they are meant to because + // someone forgot to first create a HL_enginefuncs_t object and + // initialise it with the pointers passed from the engine. + // We treat this as a major developer error and bluntly exit the + // whole process, assuming that this will never happen on a + // production server as it should have been caught by the developer + // during testing. + // + // We use a printf() to complain since we do not know if we have + // already attached to the engine and can use its alerting + // functions. This should be augemnted with a windows version + // popping open a message box. + fprintf( stderr, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" ); + fprintf( stderr, "ERROR: INTERNAL ERROR.\n" ); + fprintf( stderr, " Attempt to use meta_new_dll_functions_t without initialised engine interface version!\n" ); + fprintf( stderr, " %s at %d\n", __FILE__, __LINE__ ); + exit(1); + } + + memcpy( _pFuncs, this, size ); +} + + + + +int DLLINTERNAL meta_new_dll_functions_t::determine_interface_version( void ) +{ + // If the meta_enginefuncs_t::version is 0, i.e. has not yet been + // determined, that is a problem and an error. We should probably throw + // a fit here or something. + // For now we just return 0 and leave it to the caller to complain. + if (meta_enginefuncs_t::version() == 0) return 0; + + // The default version is 1. + sm_version = 1; + + // With the enginefuncs interface version 156 the function + // pfnCvarValue() was added, which we call version 2. + if (meta_enginefuncs_t::version() >= 156) sm_version = 2; + + // With the enginefuncs interface version 157 the function + // pfnCvarValue2() was added, which we call version 3. + if (meta_enginefuncs_t::version() >= 157) sm_version = 3; + + return sm_version; +} + + +size_t DLLINTERNAL meta_new_dll_functions_t::get_size( int _version ) +{ + size_t size = sizeof(NEW_DLL_FUNCTIONS); + + if ( 0 == _version ) { + // Use the current engine's interface version + _version = version(); + + // Error: meta_enginefuncs_t::version probably not yet set up. + if ( 0 == _version ) return 0; + } + + switch( _version ) { + case 1: + // Version 1 is missing all functions from CvarValue() on. + size -= sizeof(FN_CVARVALUE); + case 2: + // Version 2 is missing all functions from CvarValue2() on. + size -= sizeof(FN_CVARVALUE2); + } + + return size; +} + + + + +// -------------------------------------------------------------- +// meta_enginefuncs_t +// -------------------------------------------------------------- + +// static member initialisation +int meta_enginefuncs_t::sm_version = 0; + + + +meta_enginefuncs_t::meta_enginefuncs_t( + int (*_pfnPrecacheModel) (char*), + int (*_pfnPrecacheSound) (char*), + void (*_pfnSetModel) (edict_t*, const char*), + int (*_pfnModelIndex) (const char*), + int (*_pfnModelFrames) (int), + void (*_pfnSetSize) (edict_t*, const float*, const float*), + void (*_pfnChangeLevel) (char*, char*), + void (*_pfnGetSpawnParms) (edict_t*), + void (*_pfnSaveSpawnParms) (edict_t*), + float (*_pfnVecToYaw) (const float*), + void (*_pfnVecToAngles) (const float*, float*), + void (*_pfnMoveToOrigin) (edict_t*, const float*, float, int), + void (*_pfnChangeYaw) (edict_t*), + void (*_pfnChangePitch) (edict_t*), + edict_t* (*_pfnFindEntityByString) (edict_t*, const char*, const char*), + int (*_pfnGetEntityIllum) (edict_t*), + edict_t* (*_pfnFindEntityInSphere) (edict_t*, const float*, float), + edict_t* (*_pfnFindClientInPVS) (edict_t*), + edict_t* (*_pfnEntitiesInPVS) (edict_t*), + void (*_pfnMakeVectors) (const float*), + void (*_pfnAngleVectors) (const float*, float*, float*, float*), + edict_t* (*_pfnCreateEntity) (void), + void (*_pfnRemoveEntity) (edict_t*), + edict_t* (*_pfnCreateNamedEntity) (int), + void (*_pfnMakeStatic) (edict_t*), + int (*_pfnEntIsOnFloor) (edict_t*), + int (*_pfnDropToFloor) (edict_t*), + int (*_pfnWalkMove) (edict_t*, float, float, int), + void (*_pfnSetOrigin) (edict_t*, const float*), + void (*_pfnEmitSound) (edict_t*, int, const char*, float, float, int, int), + void (*_pfnEmitAmbientSound) (edict_t*, float*, const char*, float, float, int, int), + void (*_pfnTraceLine) (const float*, const float*, int, edict_t*, TraceResult*), + void (*_pfnTraceToss) (edict_t*, edict_t*, TraceResult*), + int (*_pfnTraceMonsterHull) (edict_t*, const float*, const float*, int, edict_t*, TraceResult*), + void (*_pfnTraceHull) (const float*, const float*, int, int, edict_t*, TraceResult*), + void (*_pfnTraceModel) (const float*, const float*, int, edict_t*, TraceResult*), + const char* (*_pfnTraceTexture) (edict_t*, const float*, const float*), + void (*_pfnTraceSphere) (const float*, const float*, int, float, edict_t*, TraceResult*), + void (*_pfnGetAimVector) (edict_t*, float, float*), + void (*_pfnServerCommand) (char*), + void (*_pfnServerExecute) (void), + void (*_pfnClientCommand) (edict_t*, char*, ...), + void (*_pfnParticleEffect) (const float*, const float*, float, float), + void (*_pfnLightStyle) (int, char*), + int (*_pfnDecalIndex) (const char*), + int (*_pfnPointContents) (const float*), + void (*_pfnMessageBegin) (int, int, const float*, edict_t*), + void (*_pfnMessageEnd) (void), + void (*_pfnWriteByte) (int), + void (*_pfnWriteChar) (int), + void (*_pfnWriteShort) (int), + void (*_pfnWriteLong) (int), + void (*_pfnWriteAngle) (float), + void (*_pfnWriteCoord) (float), + void (*_pfnWriteString) (const char*), + void (*_pfnWriteEntity) (int), + void (*_pfnCVarRegister) (cvar_t*), + float (*_pfnCVarGetFloat) (const char*), + const char* (*_pfnCVarGetString) (const char*), + void (*_pfnCVarSetFloat) (const char*, float), + void (*_pfnCVarSetString) (const char*, const char*), + void (*_pfnAlertMessage) (ALERT_TYPE, char*, ...), + void (*_pfnEngineFprintf) (void*, char*, ...), + void* (*_pfnPvAllocEntPrivateData) (edict_t*, int32), + void* (*_pfnPvEntPrivateData) (edict_t*), + void (*_pfnFreeEntPrivateData) (edict_t*), + const char* (*_pfnSzFromIndex) (int), + int (*_pfnAllocString) (const char*), + struct entvars_s*(*_pfnGetVarsOfEnt) (edict_t*), + edict_t* (*_pfnPEntityOfEntOffset) (int), + int (*_pfnEntOffsetOfPEntity) (const edict_t*), + int (*_pfnIndexOfEdict) (const edict_t*), + edict_t* (*_pfnPEntityOfEntIndex) (int), + edict_t* (*_pfnFindEntityByVars) (struct entvars_s*), + void* (*_pfnGetModelPtr) (edict_t*), + int (*_pfnRegUserMsg) (const char*, int), + void (*_pfnAnimationAutomove) (const edict_t*, float), + void (*_pfnGetBonePosition) (const edict_t*, int, float*, float* ), + uint32 (*_pfnFunctionFromName) (const char*), + const char* (*_pfnNameForFunction) (uint32), + void (*_pfnClientPrintf) (edict_t*, PRINT_TYPE, const char*), + void (*_pfnServerPrint) (const char*), + const char* (*_pfnCmd_Args) (void), + const char* (*_pfnCmd_Argv) (int argc), + int (*_pfnCmd_Argc) (void), + void (*_pfnGetAttachment) (const edict_t*, int, float*, float*), + void (*_pfnCRC32_Init) (CRC32_t*), + void (*_pfnCRC32_ProcessBuffer) (CRC32_t*, void*, int), + void (*_pfnCRC32_ProcessByte) (CRC32_t*, unsigned char), + CRC32_t (*_pfnCRC32_Final) (CRC32_t), + int32 (*_pfnRandomLong) (int32, int32), + float (*_pfnRandomFloat) (float, float), + void (*_pfnSetView) (const edict_t*, const edict_t*), + float (*_pfnTime) (void), + void (*_pfnCrosshairAngle) (const edict_t*, float, float), + byte* (*_pfnLoadFileForMe) (char*, int*), + void (*_pfnFreeFile) (void*), + void (*_pfnEndSection) (const char*), + int (*_pfnCompareFileTime) (char*, char*, int*), + void (*_pfnGetGameDir) (char*), + void (*_pfnCvar_RegisterVariable) (cvar_t*), + void (*_pfnFadeClientVolume) (const edict_t*, int, int, int, int), + void (*_pfnSetClientMaxspeed) (const edict_t*, float), + edict_t* (*_pfnCreateFakeClient) (const char*), + void (*_pfnRunPlayerMove) (edict_t*, const float*, float, float, float, unsigned short, byte, byte), + int (*_pfnNumberOfEntities) (void), + char* (*_pfnGetInfoKeyBuffer) (edict_t*), + char* (*_pfnInfoKeyValue) (char*, char*), + void (*_pfnSetKeyValue) (char*, char*, char*), + void (*_pfnSetClientKeyValue) (int, char*, char*, char*), + int (*_pfnIsMapValid) (char*), + void (*_pfnStaticDecal) (const float*, int, int, int), + int (*_pfnPrecacheGeneric) (char*), + int (*_pfnGetPlayerUserId) (edict_t*), + void (*_pfnBuildSoundMsg) (edict_t*, int, const char*, float, float, int, int, int, int, const float*, edict_t*), + int (*_pfnIsDedicatedServer) (void), + cvar_t* (*_pfnCVarGetPointer) (const char*), + unsigned int (*_pfnGetPlayerWONId) (edict_t*), + void (*_pfnInfo_RemoveKey) (char*, const char*), + const char* (*_pfnGetPhysicsKeyValue) (const edict_t*, const char*), + void (*_pfnSetPhysicsKeyValue) (const edict_t*, const char*, const char*), + const char* (*_pfnGetPhysicsInfoString) (const edict_t*), + unsigned short (*_pfnPrecacheEvent) (int, const char*), + void (*_pfnPlaybackEvent) (int, const edict_t*, unsigned short, float, float*, float*, float, float, int, int, int, int), + unsigned char* (*_pfnSetFatPVS) (float*), + unsigned char* (*_pfnSetFatPAS) (float*), + int (*_pfnCheckVisibility) (const edict_t*, unsigned char*), + void (*_pfnDeltaSetField) (struct delta_s*, const char*), + void (*_pfnDeltaUnsetField) (struct delta_s*, const char*), + void (*_pfnDeltaAddEncoder) (char*, void (*)(struct delta_s*, const unsigned char*, const unsigned char*)), + int (*_pfnGetCurrentPlayer) (void), + int (*_pfnCanSkipPlayer) (const edict_t*), + int (*_pfnDeltaFindField) (struct delta_s*, const char*), + void (*_pfnDeltaSetFieldByIndex) (struct delta_s*, int), + void (*_pfnDeltaUnsetFieldByIndex) (struct delta_s*, int), + void (*_pfnSetGroupMask) (int, int), + int (*_pfnCreateInstancedBaseline) (int, struct entity_state_s*), + void (*_pfnCvar_DirectSet) (struct cvar_s*, char*), + void (*_pfnForceUnmodified) (FORCE_TYPE, float*, float*, const char*), + void (*_pfnGetPlayerStats) (const edict_t*, int*, int*), + void (*_pfnAddServerCommand) (char*, void (*) (void)), + qboolean (*_pfnVoice_GetClientListening) (int, int), + qboolean (*_pfnVoice_SetClientListening) (int, int, qboolean), + const char* (*_pfnGetPlayerAuthId) (edict_t*), + sequenceEntry_s* (*_pfnSequenceGet) (const char*, const char*), + sentenceEntry_s* (*_pfnSequencePickSentence) (const char*, int, int*), + int (*_pfnGetFileSize) (char*), + unsigned int (*_pfnGetApproxWavePlayLen) (const char*), + int (*_pfnIsCareerMatch) (void), + int (*_pfnGetLocalizedStringLength) (const char*), + void (*_pfnRegisterTutorMessageShown) (int), + int (*_pfnGetTimesTutorMessageShown) (int), + void (*_pfnProcessTutorMessageDecayBuffer) (int*, int), + void (*_pfnConstructTutorMessageDecayBuffer)(int*, int), + void (*_pfnResetTutorMessageDecayData) (void), + void (*_pfnQueryClientCvarValue) (const edict_t*, const char*), + void (*_pfnQueryClientCvarValue2) (const edict_t*, const char*, int), + int (*_pfnEngCheckParm) (const char*, char**) + ) +{ + pfnPrecacheModel = _pfnPrecacheModel; + pfnPrecacheSound = _pfnPrecacheSound; + pfnSetModel = _pfnSetModel; + pfnModelIndex = _pfnModelIndex; + pfnModelFrames = _pfnModelFrames; + pfnSetSize = _pfnSetSize; + pfnChangeLevel = _pfnChangeLevel; + pfnGetSpawnParms = _pfnGetSpawnParms; + pfnSaveSpawnParms = _pfnSaveSpawnParms; + pfnVecToYaw = _pfnVecToYaw; + pfnVecToAngles = _pfnVecToAngles; + pfnMoveToOrigin = _pfnMoveToOrigin; + pfnChangeYaw = _pfnChangeYaw; + pfnChangePitch = _pfnChangePitch; + pfnFindEntityByString = _pfnFindEntityByString; + pfnGetEntityIllum = _pfnGetEntityIllum; + pfnFindEntityInSphere = _pfnFindEntityInSphere; + pfnFindClientInPVS = _pfnFindClientInPVS; + pfnEntitiesInPVS = _pfnEntitiesInPVS; + pfnMakeVectors = _pfnMakeVectors; + pfnAngleVectors = _pfnAngleVectors; + pfnCreateEntity = _pfnCreateEntity; + pfnRemoveEntity = _pfnRemoveEntity; + pfnCreateNamedEntity = _pfnCreateNamedEntity; + pfnMakeStatic = _pfnMakeStatic; + pfnEntIsOnFloor = _pfnEntIsOnFloor; + pfnDropToFloor = _pfnDropToFloor; + pfnWalkMove = _pfnWalkMove; + pfnSetOrigin = _pfnSetOrigin; + pfnEmitSound = _pfnEmitSound; + pfnEmitAmbientSound = _pfnEmitAmbientSound; + pfnTraceLine = _pfnTraceLine; + pfnTraceToss = _pfnTraceToss; + pfnTraceMonsterHull = _pfnTraceMonsterHull; + pfnTraceHull = _pfnTraceHull; + pfnTraceModel = _pfnTraceModel; + pfnTraceTexture = _pfnTraceTexture; + pfnTraceSphere = _pfnTraceSphere; + pfnGetAimVector = _pfnGetAimVector; + pfnServerCommand = _pfnServerCommand; + pfnServerExecute = _pfnServerExecute; + pfnClientCommand = _pfnClientCommand; + pfnParticleEffect = _pfnParticleEffect; + pfnLightStyle = _pfnLightStyle; + pfnDecalIndex = _pfnDecalIndex; + pfnPointContents = _pfnPointContents; + pfnMessageBegin = _pfnMessageBegin; + pfnMessageEnd = _pfnMessageEnd; + pfnWriteByte = _pfnWriteByte; + pfnWriteChar = _pfnWriteChar; + pfnWriteShort = _pfnWriteShort; + pfnWriteLong = _pfnWriteLong; + pfnWriteAngle = _pfnWriteAngle; + pfnWriteCoord = _pfnWriteCoord; + pfnWriteString = _pfnWriteString; + pfnWriteEntity = _pfnWriteEntity; + pfnCVarRegister = _pfnCVarRegister; + pfnCVarGetFloat = _pfnCVarGetFloat; + pfnCVarGetString = _pfnCVarGetString; + pfnCVarSetFloat = _pfnCVarSetFloat; + pfnCVarSetString = _pfnCVarSetString; + pfnAlertMessage = _pfnAlertMessage; + pfnEngineFprintf = _pfnEngineFprintf; + pfnPvAllocEntPrivateData = _pfnPvAllocEntPrivateData; + pfnPvEntPrivateData = _pfnPvEntPrivateData; + pfnFreeEntPrivateData = _pfnFreeEntPrivateData; + pfnSzFromIndex = _pfnSzFromIndex; + pfnAllocString = _pfnAllocString; + pfnGetVarsOfEnt = _pfnGetVarsOfEnt; + pfnPEntityOfEntOffset = _pfnPEntityOfEntOffset; + pfnEntOffsetOfPEntity = _pfnEntOffsetOfPEntity; + pfnIndexOfEdict = _pfnIndexOfEdict; + pfnPEntityOfEntIndex = _pfnPEntityOfEntIndex; + pfnFindEntityByVars = _pfnFindEntityByVars; + pfnGetModelPtr = _pfnGetModelPtr; + pfnRegUserMsg = _pfnRegUserMsg; + pfnAnimationAutomove = _pfnAnimationAutomove; + pfnGetBonePosition = _pfnGetBonePosition; + pfnFunctionFromName = _pfnFunctionFromName; + pfnNameForFunction = _pfnNameForFunction; + pfnClientPrintf = _pfnClientPrintf; + pfnServerPrint = _pfnServerPrint; + pfnCmd_Args = _pfnCmd_Args; + pfnCmd_Argv = _pfnCmd_Argv; + pfnCmd_Argc = _pfnCmd_Argc; + pfnGetAttachment = _pfnGetAttachment; + pfnCRC32_Init = _pfnCRC32_Init; + pfnCRC32_ProcessBuffer = _pfnCRC32_ProcessBuffer; + pfnCRC32_ProcessByte = _pfnCRC32_ProcessByte; + pfnCRC32_Final = _pfnCRC32_Final; + pfnRandomLong = _pfnRandomLong; + pfnRandomFloat = _pfnRandomFloat; + pfnSetView = _pfnSetView; + pfnTime = _pfnTime; + pfnCrosshairAngle = _pfnCrosshairAngle; + pfnLoadFileForMe = _pfnLoadFileForMe; + pfnFreeFile = _pfnFreeFile; + pfnEndSection = _pfnEndSection; + pfnCompareFileTime = _pfnCompareFileTime; + pfnGetGameDir = _pfnGetGameDir; + pfnCvar_RegisterVariable = _pfnCvar_RegisterVariable; + pfnFadeClientVolume = _pfnFadeClientVolume; + pfnSetClientMaxspeed = _pfnSetClientMaxspeed; + pfnCreateFakeClient = _pfnCreateFakeClient; + pfnRunPlayerMove = _pfnRunPlayerMove; + pfnNumberOfEntities = _pfnNumberOfEntities; + pfnGetInfoKeyBuffer = _pfnGetInfoKeyBuffer; + pfnInfoKeyValue = _pfnInfoKeyValue; + pfnSetKeyValue = _pfnSetKeyValue; + pfnSetClientKeyValue = _pfnSetClientKeyValue; + pfnIsMapValid = _pfnIsMapValid; + pfnStaticDecal = _pfnStaticDecal; + pfnPrecacheGeneric = _pfnPrecacheGeneric; + pfnGetPlayerUserId = _pfnGetPlayerUserId; + pfnBuildSoundMsg = _pfnBuildSoundMsg; + pfnIsDedicatedServer = _pfnIsDedicatedServer; + pfnCVarGetPointer = _pfnCVarGetPointer; + pfnGetPlayerWONId = _pfnGetPlayerWONId; + pfnInfo_RemoveKey = _pfnInfo_RemoveKey; + pfnGetPhysicsKeyValue = _pfnGetPhysicsKeyValue; + pfnSetPhysicsKeyValue = _pfnSetPhysicsKeyValue; + pfnGetPhysicsInfoString = _pfnGetPhysicsInfoString; + pfnPrecacheEvent = _pfnPrecacheEvent; + pfnPlaybackEvent = _pfnPlaybackEvent; + pfnSetFatPVS = _pfnSetFatPVS; + pfnSetFatPAS = _pfnSetFatPAS; + pfnCheckVisibility = _pfnCheckVisibility; + pfnDeltaSetField = _pfnDeltaSetField; + pfnDeltaUnsetField = _pfnDeltaUnsetField; + pfnDeltaAddEncoder = _pfnDeltaAddEncoder; + pfnGetCurrentPlayer = _pfnGetCurrentPlayer; + pfnCanSkipPlayer = _pfnCanSkipPlayer; + pfnDeltaFindField = _pfnDeltaFindField; + pfnDeltaSetFieldByIndex = _pfnDeltaSetFieldByIndex; + pfnDeltaUnsetFieldByIndex = _pfnDeltaUnsetFieldByIndex; + pfnSetGroupMask = _pfnSetGroupMask; + pfnCreateInstancedBaseline = _pfnCreateInstancedBaseline; + pfnCvar_DirectSet = _pfnCvar_DirectSet; + pfnForceUnmodified = _pfnForceUnmodified; + pfnGetPlayerStats = _pfnGetPlayerStats; + pfnAddServerCommand = _pfnAddServerCommand; + pfnVoice_GetClientListening = _pfnVoice_GetClientListening; + pfnVoice_SetClientListening = _pfnVoice_SetClientListening; + pfnGetPlayerAuthId = _pfnGetPlayerAuthId; + pfnSequenceGet = _pfnSequenceGet; + pfnSequencePickSentence = _pfnSequencePickSentence; + pfnGetFileSize = _pfnGetFileSize; + pfnGetApproxWavePlayLen = _pfnGetApproxWavePlayLen; + pfnIsCareerMatch = _pfnIsCareerMatch; + pfnGetLocalizedStringLength = _pfnGetLocalizedStringLength; + pfnRegisterTutorMessageShown = _pfnRegisterTutorMessageShown; + pfnGetTimesTutorMessageShown = _pfnGetTimesTutorMessageShown; + pfnProcessTutorMessageDecayBuffer = _pfnProcessTutorMessageDecayBuffer; + pfnConstructTutorMessageDecayBuffer = _pfnConstructTutorMessageDecayBuffer; + pfnResetTutorMessageDecayData = _pfnResetTutorMessageDecayData; + pfnQueryClientCvarValue = _pfnQueryClientCvarValue; + pfnQueryClientCvarValue2 = _pfnQueryClientCvarValue2; + pfnEngCheckParm = _pfnEngCheckParm; + + memset( extra_functions, 0, sizeof(extra_functions)); + + memset( dummies, 0, sizeof(pdummyfunc) * c_NumDummies ); +} + + + +// ----------------------------------------------------------------- +// HL_enginefuncs +// ----------------------------------------------------------------- + +void HL_enginefuncs_t::initialise_interface( enginefuncs_t *_pFuncs ) +{ + set_from( _pFuncs ); + + // Now the pfnAlertMessage is available and we trust it to be a valid + // pointer, so flush the message buffer. + flush_ALERT_buffer(); + + determine_engine_interface_version(); + fixup_engine_interface(); +} + + + +// The following part (i.e. the end) of the enginefuncs_t struct is +// used to determine the engine interface version since it is the one +// that changed since SDK 212 engines. We call this the "signature" of +// the enginefuncs interface. +// +// Default version is 138. That's what the SDK says. +// +// 144: const char *(*pfnGetPlayerAuthId) ( edict_t *e ); +// +// // PSV: Added for CZ training map +// // const char *(*pfnKeyNameForBinding) ( const char* pBinding ); +// +// sequenceEntry_s* (*pfnSequenceGet) ( const char* fileName, const char* entryName ); +// sentenceEntry_s* (*pfnSequencePickSentence) ( const char* groupName, int pickMethod, int *picked ); +// +// // LH: Give access to filesize via filesystem +// 147: int (*pfnGetFileSize) ( char *filename ); +// +// unsigned int (*pfnGetApproxWavePlayLen) (const char *filepath); +// // MDC: Added for CZ career-mode +// int (*pfnIsCareerMatch) ( void ); +// +// // BGC: return the number of characters of the localized string referenced by using "label" +// int (*pfnGetLocalizedStringLength) (const char *label); +// +// // BGC: added to facilitate persistent storage of tutor message decay values for +// // different career game profiles. Also needs to persist regardless of mp.dll being +// // destroyed and recreated. +// void (*pfnRegisterTutorMessageShown) (int mid); +// int (*pfnGetTimesTutorMessageShown) (int mid); +// void (*pfnProcessTutorMessageDecayBuffer) (int *buffer, int bufferLength); +// void (*pfnConstructTutorMessageDecayBuffer) (int *buffer, int bufferLength); +// 155: void (*pfnResetTutorMessageDecayData) ( void ); + +// 156: void (*pfnQueryClientCvarValue) ( const edict_t *player, const char *cvarName ); +// 157: void (*pfnQueryClientCvarValue2) ( const edict_t *player, const char *cvarName, int requestID ); +// 158: int (*pfnEngCheckParm) ( const char *pchCmdLineToke, char **pchNextValue ); + +void HL_enginefuncs_t::determine_engine_interface_version( void ) +{ + // We only need to do this once. + if (0 != sm_version) { + return; + } + + // Test every pointer that is part of the signature if it is a valid + // pointer. If it is not, we set it explicitly to NULL. + if ( ! Engine.info.is_valid_code_pointer(pfnGetPlayerAuthId) ) { + pfnGetPlayerAuthId = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnSequenceGet) ) { + pfnSequenceGet = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnSequencePickSentence) ) { + pfnSequencePickSentence = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnGetFileSize) ) { + pfnGetFileSize = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnGetApproxWavePlayLen) ) { + pfnGetApproxWavePlayLen = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnIsCareerMatch) ) { + pfnIsCareerMatch = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnGetLocalizedStringLength) ) { + pfnGetLocalizedStringLength = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnRegisterTutorMessageShown) ) { + pfnRegisterTutorMessageShown = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnGetTimesTutorMessageShown) ) { + pfnGetTimesTutorMessageShown = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnProcessTutorMessageDecayBuffer) ) { + pfnProcessTutorMessageDecayBuffer = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnConstructTutorMessageDecayBuffer) ) { + pfnConstructTutorMessageDecayBuffer = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnResetTutorMessageDecayData) ) { + pfnResetTutorMessageDecayData = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnQueryClientCvarValue) ) { + pfnQueryClientCvarValue = NULL; + } + if ( ! Engine.info.is_valid_code_pointer(pfnQueryClientCvarValue2) ) { + pfnQueryClientCvarValue2 = NULL; + } + + // Now begins our heuristic, where we try to determine the engine + // interface version. + // As alluded to above we are currently only interested, and thus + // only detect, versions 144, 147, 156 or 157 (defined by us). + + // The minimal default is 138. + sm_version = 138; + + // If GetPlayerAuthId() is present, it is at least 144, + // otherwise leave it at the default 138. + // This may give incorrect results for *really* old engine versions, + // i.e. pre 1.1.0.8. We live with that risk. No one uses them anymore. + // Really. + if ( pfnGetPlayerAuthId == NULL ) { + return; + } + sm_version = 144; + + // The two function pointers for pfnSequenceGet() and + // pfnSequencePickSentence() are only valid in a few engine versions + // and are set to NULL in most other version, so they don't get + // checked. + + // If pfnGetFileSize() is present, it is at least 147, + // otherwise leave it at the so far determined value. + if ( pfnGetFileSize == NULL ) { + return; + } + sm_version = 147; + + // Now it gets a bit fuzzy. If all of the functions following GetFileSize() + // but before QueryClientCvarValue() are valid, it is at least 155. + // If even one of them is NULL, then our version can't be higher than 147, + // so use 147. Actually, it could be that there exist engine + // versions where one of them is NULL but the interface is still at + // least 155. If such an engine is found in use, adaptions need to be + // made. + // (Yes, I know this could be done with a little hacky for() loop. We + // don't need to do hacky here.) + int cntInvals = 0; + if ( pfnGetApproxWavePlayLen == NULL ) cntInvals++; + if ( pfnIsCareerMatch == NULL ) cntInvals++; + if ( pfnGetLocalizedStringLength == NULL ) cntInvals++; + if ( pfnRegisterTutorMessageShown == NULL ) cntInvals++; + if ( pfnGetTimesTutorMessageShown == NULL ) cntInvals++; + if ( pfnProcessTutorMessageDecayBuffer == NULL ) cntInvals++; + if ( pfnConstructTutorMessageDecayBuffer == NULL ) cntInvals++; + if ( pfnResetTutorMessageDecayData == NULL ) cntInvals++; + + if ( cntInvals > 0 ) { + return; + } + sm_version = 155; + + // All functions up to QueryClientCvarValue() are valid. + // If QueryClientCvarValue() is not valid, leave it at the so far + // determined version. Otherwise the version is at least 156. + if ( pfnQueryClientCvarValue == NULL) { + return; + } + sm_version = 156; + + // All functions up to QueryClientCvarValue2() are valid. + // If QueryClientCvarValue2() is not valid, leave it at the so far + // determined version. Otherwise the version is at least 157. + if ( pfnQueryClientCvarValue2 == NULL) { + return; + } + sm_version = 157; + + // All functions up to EngCheckParm() are valid. + // If EngCheckParm() is not valid, leave it at the so far determined + // version. Otherwise the version is at least 158. + if ( pfnEngCheckParm == NULL) { + return; + } + sm_version = 158; +} + + + + + +void HL_enginefuncs_t::fixup_engine_interface( void ) +{ + // This function will make sure that all function pointers that aren't + // valid are set to NULL, depending on the engine interface version. + // Sometimes a pointer has a valid value although the function doesn't + // exist in the interface version. + + switch ( version() ) { + case 138: + pfnGetPlayerAuthId = NULL; + case 144: + pfnSequenceGet = NULL; + pfnSequencePickSentence = NULL; + pfnGetFileSize = NULL; + case 147: + pfnGetApproxWavePlayLen = NULL; + pfnIsCareerMatch = NULL; + pfnGetLocalizedStringLength = NULL; + pfnRegisterTutorMessageShown = NULL; + pfnGetTimesTutorMessageShown = NULL; + pfnProcessTutorMessageDecayBuffer = NULL; + pfnConstructTutorMessageDecayBuffer = NULL; + pfnResetTutorMessageDecayData = NULL; + case 155: + pfnQueryClientCvarValue = NULL; + case 156: + pfnQueryClientCvarValue2 = NULL; + case 157: + pfnEngCheckParm = NULL; + } +} + + diff --git a/src/metamod/meta_eiface.h b/src/metamod/meta_eiface.h new file mode 100644 index 0000000..08a6a7a --- /dev/null +++ b/src/metamod/meta_eiface.h @@ -0,0 +1,480 @@ + +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// meta_eiface.h - wrapper for engine/dll interface + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef MM_META_EIFACE_H +#define MM_META_EIFACE_H + +#include // NEW_DLL_FUNCTIONS, enginefuncs_t +#include // memset() + +#include "comp_dep.h" +#include "osdep.h" //unlikely, OPEN_ARGS + + +// We use our own versions of the engine/dll interface structs. We add a +// few dummy entries to the end and set them to 0. That way we are +// protected from updates to the HL SDK adding new functions which would +// cause a) the game dll copying arbitrary values from us and b) the game +// dll overwriting our memory when using an old Metamod with a new game +// dll. + +const int c_NumDummies = 5; +typedef void (*pdummyfunc)(void); + + +// -------------------------------------------------------------------- +// meta_new_dll_functions_t +// -------------------------------------------------------------------- + +struct meta_new_dll_functions_t : public NEW_DLL_FUNCTIONS { + // Array of five dummy function pointers. Must be filled with NULL. + pdummyfunc dummies[c_NumDummies]; + + // functions : + meta_new_dll_functions_t() DLLINTERNAL; + + meta_new_dll_functions_t( + void (*pfnOnFreeEntPrivateData) (edict_t*), + void (*pfnGameShutdown) (void), + int (*pfnShouldCollide) (edict_t*, edict_t*), + void (*pfnCvarValue) (const edict_t*, const char*), + void (*pfnCvarValue2) (const edict_t*, int, const char*, const char*) + ) DLLINTERNAL; + + meta_new_dll_functions_t( const meta_new_dll_functions_t& ) DLLINTERNAL; + meta_new_dll_functions_t& operator=( const meta_new_dll_functions_t& ) DLLINTERNAL; + + // Fill this object with pointers copied from a NEW_DLL_FUNCTIONS struct. + void DLLINTERNAL set_from( NEW_DLL_FUNCTIONS* pFuncs ); + + // Copy the pointers from this object to a NEW_DLL_FUNCTIONS struct. + void DLLINTERNAL copy_to( NEW_DLL_FUNCTIONS* pFuncs ); + + // return the engine's version of NEW_DLL_FUNCTIONS + int DLLINTERNAL version( void ); + + private: + + // data : + + // The NEW_DLL_FUNCTIONS struct also changed, but the version + // number did not change. That begs the question why to have + // it versioned in the first place, but whaddaya know. + // While the official version is left at 1, we internally + // calculate a different version of the engine's NEW_DLL_FUNCTIONS + // struct since we know that the engine lies to us about the + // version that it uses. + // + // The default version is 1. + // + // With the enginefuncs interface version 156 the function + // pfnCvarValue() was added, which we call version 2. + // + // With the enginefuncs interface version 157 the function + // pfnCvarValue2() was added, which we call version 3. + // + // If Valve ever decides to change the version of the + // NEW_DLL_FUNCTIONS interface in the future (haha), + // we are in trouble and will need to change our + // internal versions. + + static int sm_version; + + // functions : + + // Calculates our idea of the engine's version of the + // NEW_DLL_FUNCTIONS interface. Stores this version for future + // reference in m_version and returns it. + int DLLINTERNAL determine_interface_version( void ); + + // Comfort function to determine the size of the NEW_DLL_FUNCTIONS + // struct for the different versions. + // If passed a version number other than 0, the size for that + // specific version is returned. + // If passed 0 as version number (default) the size for the version + // that was determined to be the version of the currently connected + // engine's interface. Should that version have not yet been + // determined (via the enginefuncs_t interface), 0 is returned to + // indicated this error state. + size_t DLLINTERNAL get_size( int version = 0 ); +}; + + +// Inline functions + +inline meta_new_dll_functions_t::meta_new_dll_functions_t() +{ + memset( this, 0, sizeof(meta_new_dll_functions_t) ); +} + + +inline meta_new_dll_functions_t::meta_new_dll_functions_t( const meta_new_dll_functions_t& _rhs ) +{ + memcpy( this, &_rhs, sizeof(NEW_DLL_FUNCTIONS) ); + memset( dummies, 0, sizeof(pdummyfunc) * c_NumDummies ); +} + + +inline meta_new_dll_functions_t& meta_new_dll_functions_t::operator=( const meta_new_dll_functions_t& _rhs) +{ + memcpy( this, &_rhs, sizeof(NEW_DLL_FUNCTIONS) ); + return *this; +} + + +inline void meta_new_dll_functions_t::set_from( NEW_DLL_FUNCTIONS* _pFuncs ) +{ + memcpy( this, _pFuncs, sizeof(NEW_DLL_FUNCTIONS) ); +} + + +inline int meta_new_dll_functions_t::version( void ) +{ + return sm_version ? sm_version : determine_interface_version(); +} + + + +// No meta version of DLL_FUNCTIONS because that won't be changing anymore. + + + +// -------------------------------------------------------------------- +// meta_enginefuncs_t +// -------------------------------------------------------------------- + + +struct meta_enginefuncs_t : public enginefuncs_t { + // data : + + // Array of five dummy function pointers. Must be filled with NULL. + pdummyfunc dummies[c_NumDummies]; + + // functions : + meta_enginefuncs_t() DLLINTERNAL; + + // Spawn of the devil + meta_enginefuncs_t( + int (*_pfnPrecacheModel) (char*), + int (*_pfnPrecacheSound) (char*), + void (*_pfnSetModel) (edict_t*, const char*), + int (*_pfnModelIndex) (const char*), + int (*_pfnModelFrames) (int), + void (*_pfnSetSize) (edict_t*, const float*, const float*), + void (*_pfnChangeLevel) (char*, char*), + void (*_pfnGetSpawnParms) (edict_t*), + void (*_pfnSaveSpawnParms) (edict_t*), + float (*_pfnVecToYaw) (const float*), + void (*_pfnVecToAngles) (const float*, float*), + void (*_pfnMoveToOrigin) (edict_t*, const float*, float, int), + void (*_pfnChangeYaw) (edict_t*), + void (*_pfnChangePitch) (edict_t*), + edict_t* (*_pfnFindEntityByString) (edict_t*, const char*, const char*), + int (*_pfnGetEntityIllum) (edict_t*), + edict_t* (*_pfnFindEntityInSphere) (edict_t*, const float*, float), + edict_t* (*_pfnFindClientInPVS) (edict_t*), + edict_t* (*_pfnEntitiesInPVS) (edict_t*), + void (*_pfnMakeVectors) (const float*), + void (*_pfnAngleVectors) (const float*, float*, float*, float*), + edict_t* (*_pfnCreateEntity) (void), + void (*_pfnRemoveEntity) (edict_t*), + edict_t* (*_pfnCreateNamedEntity) (int), + void (*_pfnMakeStatic) (edict_t*), + int (*_pfnEntIsOnFloor) (edict_t*), + int (*_pfnDropToFloor) (edict_t*), + int (*_pfnWalkMove) (edict_t*, float, float, int), + void (*_pfnSetOrigin) (edict_t*, const float*), + void (*_pfnEmitSound) (edict_t*, int, const char*, float, float, int, int), + void (*_pfnEmitAmbientSound) (edict_t*, float*, const char*, float, float, int, int), + void (*_pfnTraceLine) (const float*, const float*, int, edict_t*, TraceResult*), + void (*_pfnTraceToss) (edict_t*, edict_t*, TraceResult*), + int (*_pfnTraceMonsterHull) (edict_t*, const float*, const float*, int, edict_t*, TraceResult*), + void (*_pfnTraceHull) (const float*, const float*, int, int, edict_t*, TraceResult*), + void (*_pfnTraceModel) (const float*, const float*, int, edict_t*, TraceResult*), + const char* (*_pfnTraceTexture) (edict_t*, const float*, const float*), + void (*_pfnTraceSphere) (const float*, const float*, int, float, edict_t*, TraceResult*), + void (*_pfnGetAimVector) (edict_t*, float, float*), + void (*_pfnServerCommand) (char*), + void (*_pfnServerExecute) (void), + void (*_pfnClientCommand) (edict_t*, char*, ...), + void (*_pfnParticleEffect) (const float*, const float*, float, float), + void (*_pfnLightStyle) (int, char*), + int (*_pfnDecalIndex) (const char*), + int (*_pfnPointContents) (const float*), + void (*_pfnMessageBegin) (int, int, const float*, edict_t*), + void (*_pfnMessageEnd) (void), + void (*_pfnWriteByte) (int), + void (*_pfnWriteChar) (int), + void (*_pfnWriteShort) (int), + void (*_pfnWriteLong) (int), + void (*_pfnWriteAngle) (float), + void (*_pfnWriteCoord) (float), + void (*_pfnWriteString) (const char*), + void (*_pfnWriteEntity) (int), + void (*_pfnCVarRegister) (cvar_t*), + float (*_pfnCVarGetFloat) (const char*), + const char* (*_pfnCVarGetString) (const char*), + void (*_pfnCVarSetFloat) (const char*, float), + void (*_pfnCVarSetString) (const char*, const char*), + void (*_pfnAlertMessage) (ALERT_TYPE, char*, ...), + void (*_pfnEngineFprintf) (void*, char*, ...), + void* (*_pfnPvAllocEntPrivateData) (edict_t*, int32), + void* (*_pfnPvEntPrivateData) (edict_t*), + void (*_pfnFreeEntPrivateData) (edict_t*), + const char* (*_pfnSzFromIndex) (int), + int (*_pfnAllocString) (const char*), + struct entvars_s*(*_pfnGetVarsOfEnt) (edict_t*), + edict_t* (*_pfnPEntityOfEntOffset) (int), + int (*_pfnEntOffsetOfPEntity) (const edict_t*), + int (*_pfnIndexOfEdict) (const edict_t*), + edict_t* (*_pfnPEntityOfEntIndex) (int), + edict_t* (*_pfnFindEntityByVars) (struct entvars_s*), + void* (*_pfnGetModelPtr) (edict_t*), + int (*_pfnRegUserMsg) (const char*, int), + void (*_pfnAnimationAutomove) (const edict_t*, float), + void (*_pfnGetBonePosition) (const edict_t*, int, float*, float* ), + uint32 (*_pfnFunctionFromName) (const char*), + const char* (*_pfnNameForFunction) (uint32), + void (*_pfnClientPrintf) (edict_t*, PRINT_TYPE, const char*), + void (*_pfnServerPrint) (const char*), + const char* (*_pfnCmd_Args) (void), + const char* (*_pfnCmd_Argv) (int argc), + int (*_pfnCmd_Argc) (void), + void (*_pfnGetAttachment) (const edict_t*, int, float*, float*), + void (*_pfnCRC32_Init) (CRC32_t*), + void (*_pfnCRC32_ProcessBuffer) (CRC32_t*, void*, int), + void (*_pfnCRC32_ProcessByte) (CRC32_t*, unsigned char), + CRC32_t (*_pfnCRC32_Final) (CRC32_t), + int32 (*_pfnRandomLong) (int32, int32), + float (*_pfnRandomFloat) (float, float), + void (*_pfnSetView) (const edict_t*, const edict_t*), + float (*_pfnTime) (void), + void (*_pfnCrosshairAngle) (const edict_t*, float, float), + byte* (*_pfnLoadFileForMe) (char*, int*), + void (*_pfnFreeFile) (void*), + void (*_pfnEndSection) (const char*), + int (*_pfnCompareFileTime) (char*, char*, int*), + void (*_pfnGetGameDir) (char*), + void (*_pfnCvar_RegisterVariable) (cvar_t*), + void (*_pfnFadeClientVolume) (const edict_t*, int, int, int, int), + void (*_pfnSetClientMaxspeed) (const edict_t*, float), + edict_t* (*_pfnCreateFakeClient) (const char*), + void (*_pfnRunPlayerMove) (edict_t*, const float*, float, float, float, unsigned short, byte, byte), + int (*_pfnNumberOfEntities) (void), + char* (*_pfnGetInfoKeyBuffer) (edict_t*), + char* (*_pfnInfoKeyValue) (char*, char*), + void (*_pfnSetKeyValue) (char*, char*, char*), + void (*_pfnSetClientKeyValue) (int, char*, char*, char*), + int (*_pfnIsMapValid) (char*), + void (*_pfnStaticDecal) (const float*, int, int, int), + int (*_pfnPrecacheGeneric) (char*), + int (*_pfnGetPlayerUserId) (edict_t*), + void (*_pfnBuildSoundMsg) (edict_t*, int, const char*, float, float, int, int, int, int, const float*, edict_t*), + int (*_pfnIsDedicatedServer) (void), + cvar_t* (*_pfnCVarGetPointer) (const char*), + unsigned int (*_pfnGetPlayerWONId) (edict_t*), + void (*_pfnInfo_RemoveKey) (char*, const char*), + const char* (*_pfnGetPhysicsKeyValue) (const edict_t*, const char*), + void (*_pfnSetPhysicsKeyValue) (const edict_t*, const char*, const char*), + const char* (*_pfnGetPhysicsInfoString) (const edict_t*), + unsigned short (*_pfnPrecacheEvent) (int, const char*), + void (*_pfnPlaybackEvent) (int, const edict_t*, unsigned short, float, float*, float*, float, float, int, int, int, int), + unsigned char* (*_pfnSetFatPVS) (float*), + unsigned char* (*_pfnSetFatPAS) (float*), + int (*_pfnCheckVisibility) (const edict_t*, unsigned char*), + void (*_pfnDeltaSetField) (struct delta_s*, const char*), + void (*_pfnDeltaUnsetField) (struct delta_s*, const char*), + void (*_pfnDeltaAddEncoder) (char*, void (*)(struct delta_s*, const unsigned char*, const unsigned char*)), + int (*_pfnGetCurrentPlayer) (void), + int (*_pfnCanSkipPlayer) (const edict_t*), + int (*_pfnDeltaFindField) (struct delta_s*, const char*), + void (*_pfnDeltaSetFieldByIndex) (struct delta_s*, int), + void (*_pfnDeltaUnsetFieldByIndex) (struct delta_s*, int), + void (*_pfnSetGroupMask) (int, int), + int (*_pfnCreateInstancedBaseline) (int, struct entity_state_s*), + void (*_pfnCvar_DirectSet) (struct cvar_s*, char*), + void (*_pfnForceUnmodified) (FORCE_TYPE, float*, float*, const char*), + void (*_pfnGetPlayerStats) (const edict_t*, int*, int*), + void (*_pfnAddServerCommand) (char*, void (*) (void)), + qboolean (*_pfnVoice_GetClientListening) (int, int), + qboolean (*_pfnVoice_SetClientListening) (int, int, qboolean), + const char* (*_pfnGetPlayerAuthId) (edict_t*), + sequenceEntry_s* (*_pfnSequenceGet) (const char*, const char*), + sentenceEntry_s* (*_pfnSequencePickSentence) (const char*, int, int*), + int (*_pfnGetFileSize) (char*), + unsigned int (*_pfnGetApproxWavePlayLen) (const char*), + int (*_pfnIsCareerMatch) (void), + int (*_pfnGetLocalizedStringLength) (const char*), + void (*_pfnRegisterTutorMessageShown) (int), + int (*_pfnGetTimesTutorMessageShown) (int), + void (*_pfnProcessTutorMessageDecayBuffer) (int*, int), + void (*_pfnConstructTutorMessageDecayBuffer)(int*, int), + void (*_pfnResetTutorMessageDecayData) (void), + void (*_pfnQueryClientCvarValue) (const edict_t*, const char*), + void (*_pfnQueryClientCvarValue2) (const edict_t*, const char*, int), + int (*_pfnEngCheckParm) (const char *, char**) + ) DLLINTERNAL; + + meta_enginefuncs_t( const meta_enginefuncs_t& ) DLLINTERNAL; + meta_enginefuncs_t& operator=( const meta_enginefuncs_t& ) DLLINTERNAL; + + // Fill this object with pointers copied from an enginefuncs_t struct. + void DLLINTERNAL set_from( enginefuncs_t *pFuncs ); + + // Copy the pointers from this object to an enginefuncs_t struct. + void DLLINTERNAL copy_to( enginefuncs_t *pFuncs ); + + // return the engine interface version + static int DLLINTERNAL version( void ); + + protected: + + // data : + + // The version of the engine functions interface. It is frozen at 138. But no one knows + // when that was and what it looked like then. So we simply interprete it as the + // number of functions that the enginefuncs struct contains. + // + // That means we get gaps inbetween versions and right now we can detect only + // about five different versions anyway, but that suffices for the current itches + // to get scratched. + // + // The default is hence 138. + // A value of 0 means "not yet determined". + // Other possible versions currently detectable: + // 144: engine versions after 1.1.0.9 build 1996 + // 147: engine versions after build 2384 with pfnGetFileSize() + // 155: all versions between build 2384 and the one + // including pfnQueryClientCvarValue() + // 156: includes pfnQueryClientCvarValue() + // 157: includes pfnQueryClientCvarValue2() + // 158: includes pfnEngCheckParm() + static int sm_version DLLHIDDEN; + +}; + +// +// Inline functions +// + +inline meta_enginefuncs_t::meta_enginefuncs_t() +{ + memset( this, 0, sizeof(meta_enginefuncs_t) ); +} + + +inline meta_enginefuncs_t::meta_enginefuncs_t( const meta_enginefuncs_t& _rhs ) +{ + memcpy( this, &_rhs, sizeof(enginefuncs_t) ); + memset( dummies, 0, sizeof(pdummyfunc) * c_NumDummies ); +} + + +inline meta_enginefuncs_t& meta_enginefuncs_t::operator=( const meta_enginefuncs_t& _rhs) +{ + memcpy( this, &_rhs, sizeof(enginefuncs_t) ); + return *this; +} + + +inline void meta_enginefuncs_t::set_from( enginefuncs_t* _pFuncs ) +{ + memcpy( this, _pFuncs, sizeof(enginefuncs_t) ); +} + + +inline void meta_enginefuncs_t::copy_to( enginefuncs_t* _pFuncs ) +{ + memcpy( _pFuncs, this, sizeof(enginefuncs_t) ); +} + + +inline int meta_enginefuncs_t::version( void ) +{ + return sm_version; +} + + + +// -------------------------------------------------------------------- +// HL_enginefuncs_t +// -------------------------------------------------------------------- + +// +// This is a specialisation of the meta_enginefuncs_t struct which is only +// used for the initial copy of the engine functions, i.e. those we get +// passed from the HL engine right at the beginning. +// This specialisation does some extra initialisation when getting set up +// like calculating the engine interface version and fixing up any invalid +// pointers. +// Since there is only one master copy of engine functions this could be +// implemented as a singleton. This is left as an option for later. +// +struct HL_enginefuncs_t : public meta_enginefuncs_t { + + // functions : + HL_enginefuncs_t() DLLINTERNAL; + + // Fill this object with pointers copied from an enginefuncs_t struct + // and fixup the interface. + // For this class this happens in the GiveFptrsToDll() function + // with the pointers passed from the HL engine. + void initialise_interface( enginefuncs_t *pFuncs ) DLLINTERNAL; + + private: + + // functions : + + // Moving copy_to() and set_from() to the private space. + void DLLINTERNAL set_from( enginefuncs_t *pFuncs ) { meta_enginefuncs_t::set_from( pFuncs ); }; + void DLLINTERNAL copy_to( enginefuncs_t *pFuncs ) { meta_enginefuncs_t::copy_to( pFuncs ); }; + + // Determine the version of the engine interface from the + // enginefuncs signature. + void DLLINTERNAL determine_engine_interface_version( void ); + + // Fixup the enginefuncs pointers according to the determined + // version as some pointers may be invalid. + void DLLINTERNAL fixup_engine_interface( void ); +}; + + +inline HL_enginefuncs_t :: HL_enginefuncs_t() : meta_enginefuncs_t() { }; + + +#endif /* META_EIFACE_H */ + diff --git a/src/metamod/metamod.cpp b/src/metamod/metamod.cpp new file mode 100644 index 0000000..70e8c6a --- /dev/null +++ b/src/metamod/metamod.cpp @@ -0,0 +1,459 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// metamod.cpp - (main) implementation of metamod operations + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // malloc, etc +#include // errno, etc + +#include // always +#include "enginecallbacks.h" // GET_GAME_DIR, etc + +#include "metamod.h" // me +#include "h_export.h" // GIVE_ENGINE_FUNCTIONS_FN, etc +#include "mreg.h" // class mCmdList, etc +#include "meta_api.h" // meta_globals_t, etc +#include "mutil.h" // mutil_funcs_t, etc +#include "osdep.h" // DLOPEN, getcwd, is_absolute_path, +#include "reg_support.h" // meta_AddServerCommand, etc +#include "game_support.h" // lookup_game, etc +#include "commands_meta.h" // meta_register_cmdcvar, etc +#include "thread_logparse.h" // logparse_handle, etc +#include "support_meta.h" // valid_gamedir_file, etc +#include "log_meta.h" // META_LOG, etc +#include "types_meta.h" // mBOOL +#include "info_name.h" // VNAME, etc +#include "vdate.h" // COMPILE_TIME, etc +#include "linkent.h" + +cvar_t meta_version = {"metamod_version", VVERSION, FCVAR_SERVER, 0, NULL}; + +MConfig static_config; +MConfig *Config=&static_config; +option_t global_options[] = { + { "debuglevel", CF_INT, &Config->debuglevel, "0" }, + { "gamedll", CF_PATH, &Config->gamedll, NULL }, + { "plugins_file", CF_PATH, &Config->plugins_file, PLUGINS_INI }, + { "exec_cfg", CF_STR, &Config->exec_cfg, EXEC_CFG }, + { "autodetect", CF_BOOL, &Config->autodetect, "yes" }, + { "clientmeta", CF_BOOL, &Config->clientmeta, "yes" }, + // list terminator + { NULL, CF_NONE, NULL, NULL } +}; + +gamedll_t GameDLL; + +meta_globals_t PublicMetaGlobals; +meta_globals_t PrivateMetaGlobals; + +meta_enginefuncs_t g_plugin_engfuncs; + +MPluginList *Plugins; +MRegCmdList *RegCmds; +MRegCvarList *RegCvars; +MRegMsgList *RegMsgs; + +MPlayerList g_Players; +int requestid_counter = 0; + +DLHANDLE metamod_handle; +int metamod_not_loaded = 0; + + +// Very first metamod function that's run. +// Do startup operations... +int DLLINTERNAL metamod_startup(void) { + char *cp, *mmfile=NULL, *cfile=NULL; + + META_CONS(" "); + META_CONS(" %s version %s Copyright (c) 2001-%s %s", VNAME, VVERSION, COPYRIGHT_YEAR, VAUTHOR); + META_CONS(" Patch: %s v%d Copyright (c) 2004-%s %s", VPATCH_NAME, VPATCH_IVERSION, VPATCH_COPYRIGHT_YEAR, VPATCH_AUTHOR); + META_CONS(" %s comes with ABSOLUTELY NO WARRANTY; for details type `meta gpl'.", VNAME); + META_CONS(" This is free software, and you are welcome to redistribute it"); + META_CONS(" under certain conditions; type `meta gpl' for details."); + META_CONS(" "); + + META_LOG("%s v%s %s", VNAME, VVERSION, VDATE); + META_LOG("by %s", VAUTHOR); + META_LOG(" %s", VURL); + META_LOG(" Patch: %s v%d", VPATCH_NAME, VPATCH_IVERSION); + META_LOG(" by %s", VPATCH_AUTHOR); + META_LOG(" %s", VPATCH_WEBSITE); + META_LOG("compiled: %s %s (%s)", COMPILE_TIME, COMPILE_TZONE, OPT_TYPE); + + // If running with "+developer", allow an opportunity to break in with + // a debugger. + if((int)CVAR_GET_FLOAT("developer") != 0) + sleep(1); + + // Get gamedir, very early on, because it seems we need it all over the + // place here at the start. + if(!meta_init_gamedll()) { + META_ERROR("Failure to init game DLL; exiting..."); + return(0); + } + + // Register various console commands and cvars. + // Can I do these here, rather than waiting for GameDLLInit() ? + // Looks like it works okay.. + meta_register_cmdcvar(); + { + //dirty hacks + int vers[4] = {RC_VERS_DWORD}; + char mvers[32]; + + if(vers[2]==0) + safevoid_snprintf(mvers, sizeof(mvers), "%d.%dp%d", vers[0], vers[1], vers[3]); + else + safevoid_snprintf(mvers, sizeof(mvers), "%d.%d.%dp%d", vers[0], vers[1], vers[2], vers[3]); + + CVAR_SET_STRING(meta_version.name, mvers); + } + + // Set a slight debug level for developer mode, if debug level not + // already set. + if((int)CVAR_GET_FLOAT("developer") != 0 && (int)meta_debug.value == 0) { + CVAR_SET_FLOAT("meta_debug", (float)(meta_debug_value = 3)); + } + + // Init default values + Config->init(global_options); + // Find config file + cfile=CONFIG_INI; + if((cp=LOCALINFO("mm_configfile")) && *cp != '\0') { + META_LOG("Configfile specified via localinfo: %s", cp); + if(valid_gamedir_file(cp)) + cfile=cp; + else + META_WARNING("Empty/missing config.ini file: %s; falling back to %s", + cp, cfile); + } + // Load config file + if(valid_gamedir_file(cfile)) + Config->load(cfile); + else + META_DEBUG(2, ("No config.ini file found: %s", CONFIG_INI)); + + // Now, override config options with localinfo commandline options. + if((cp=LOCALINFO("mm_debug")) && *cp != '\0') { + META_LOG("Debuglevel specified via localinfo: %s", cp); + Config->set("debuglevel", cp); + } + if((cp=LOCALINFO("mm_gamedll")) && *cp != '\0') { + META_LOG("Gamedll specified via localinfo: %s", cp); + Config->set("gamedll", cp); + } + if((cp=LOCALINFO("mm_pluginsfile")) && *cp != '\0') { + META_LOG("Pluginsfile specified via localinfo: %s", cp); + Config->set("plugins_file", cp); + } + if((cp=LOCALINFO("mm_execcfg")) && *cp != '\0') { + META_LOG("Execcfg specified via localinfo: %s", cp); + Config->set("exec_cfg", cp); + } + if((cp=LOCALINFO("mm_autodetect")) && *cp != '\0') { + META_LOG("Autodetect specified via localinfo: %s", cp); + Config->set("autodetect", cp); + } + if((cp=LOCALINFO("mm_clientmeta")) && *cp != '\0') { + META_LOG("Clientmeta specified via localinfo: %s", cp); + Config->set("clientmeta", cp); + } + + + // Check for an initial debug level, since cfg files don't get exec'd + // until later. + if(Config->debuglevel != 0) { + CVAR_SET_FLOAT("meta_debug", (float)(meta_debug_value = Config->debuglevel)); + } + + // Prepare for registered commands from plugins. + RegCmds = new MRegCmdList(); + RegCvars = new MRegCvarList(); + + // Prepare for registered user messages from gamedll. + RegMsgs = new MRegMsgList(); + + // Copy, and store pointer in Engine struct. Yes, we could just store + // the actual engine_t struct in Engine, but then it wouldn't be a + // pointer to match the other g_engfuncs. + g_plugin_engfuncs.set_from(Engine.funcs); + Engine.pl_funcs=&g_plugin_engfuncs; + // substitute our special versions of various commands + Engine.pl_funcs->pfnAddServerCommand = meta_AddServerCommand; + Engine.pl_funcs->pfnCVarRegister = meta_CVarRegister; + Engine.pl_funcs->pfnCvar_RegisterVariable = meta_CVarRegister; + Engine.pl_funcs->pfnRegUserMsg = meta_RegUserMsg; + if(IS_VALID_PTR((void*)Engine.pl_funcs->pfnQueryClientCvarValue)) + Engine.pl_funcs->pfnQueryClientCvarValue = meta_QueryClientCvarValue; + else + Engine.pl_funcs->pfnQueryClientCvarValue = NULL; + if(!IS_VALID_PTR((void*)Engine.pl_funcs->pfnQueryClientCvarValue2)) + Engine.pl_funcs->pfnQueryClientCvarValue2 = NULL; + + // Before, we loaded plugins before loading the game DLL, so that if no + // plugins caught engine functions, we could pass engine funcs straight + // to game dll, rather than acting as intermediary. (Should perform + // better, right?) + // + // But since a plugin can be loaded at any time, we have to go ahead + // and catch the engine funcs regardless. Also, we want to give each + // plugin a copy of the gameDLL's api tables, in case they need to call + // API functions directly. + // + // Thus, load gameDLL first, then plugins. + // + // However, we have to init the Plugins object first, because if the + // gamedll calls engine functions during GiveFnptrsToDll (like hpb_bot + // does) then it needs to be non-null so META_ENGINE_HANDLE won't crash. + // + // However, having replaced valid_file with valid_gamedir_file, we need + // to at least initialize the gameDLL to include the gamedir, before + // looking for plugins.ini. + // + // In fact, we need gamedir even earlier, so moved up above. + + // Fall back to old plugins filename, if configured one isn't found. + mmfile=PLUGINS_INI; + if(!valid_gamedir_file(PLUGINS_INI) && valid_gamedir_file(OLD_PLUGINS_INI)) + mmfile=OLD_PLUGINS_INI; + if(valid_gamedir_file(Config->plugins_file)) + mmfile=Config->plugins_file; + else + META_WARNING("Plugins file is empty/missing: %s; falling back to %s", + Config->plugins_file, mmfile); + + Plugins = new MPluginList(mmfile); + + if(!meta_load_gamedll()) { + META_ERROR("Failure to load game DLL; exiting..."); + return(0); + } + if(!Plugins->load()) { + META_WARNING("Failure to load plugins..."); + // Exit on failure here? Dunno... + } + + // Allow for commands to metamod plugins at startup. Autoexec.cfg is + // read too early, and server.cfg is read too late. + // + // Only attempt load if the file appears to exist and be non-empty, to + // avoid confusing users with "couldn't exec exec.cfg" console + // messages. + if(valid_gamedir_file(Config->exec_cfg)) + mmfile=Config->exec_cfg; + else if(valid_gamedir_file(OLD_EXEC_CFG)) + mmfile=OLD_EXEC_CFG; + else + mmfile=NULL; + + if(mmfile) { + if(mmfile[0]=='/') + META_WARNING("Cannot exec absolute pathnames: %s", mmfile); + else { + char cmd[NAME_MAX]; + META_LOG("Exec'ing metamod exec.cfg: %s...", mmfile); + safevoid_snprintf(cmd, sizeof(cmd), "exec %s\n", mmfile); + SERVER_COMMAND(cmd); + } + } + + return(1); +} + +// Set initial GameDLL fields (name, gamedir). +// meta_errno values: +// - ME_NULLRESULT getcwd failed +mBOOL DLLINTERNAL meta_init_gamedll(void) { + char gamedir[PATH_MAX]; + char *cp; + + memset(&GameDLL, 0, sizeof(GameDLL)); + + GET_GAME_DIR(gamedir); + normalize_pathname(gamedir); + // + // As of 1.1.1.1, the engine routine GET_GAME_DIR no longer returns a + // full-pathname, but rather just the path specified as the argument to + // "-game". + // + // However, since we have to work properly under both the new version + // as well as previous versions, we have to support both possibilities. + // + // Note: the code has always assumed the server op wouldn't do: + // hlds -game other/firearms + // + if(is_absolute_path(gamedir)) { + // Old style; GET_GAME_DIR returned full pathname. Copy this into + // our gamedir, and truncate to get the game name. + // (note check for both linux and win32 full pathname.) + STRNCPY(GameDLL.gamedir, gamedir, sizeof(GameDLL.gamedir)); + cp=strrchr(gamedir, '/') + 1; + STRNCPY(GameDLL.name, cp, sizeof(GameDLL.name)); + } + else { + // New style; GET_GAME_DIR returned game name. Copy this into our + // game name, and prepend the current working directory. + char buf[PATH_MAX]; + if(!getcwd(buf, sizeof(buf))) { + META_WARNING("dll: Couldn't get cwd; %s", strerror(errno)); + RETURN_ERRNO(mFALSE, ME_NULLRESULT); + } + safevoid_snprintf(GameDLL.gamedir, sizeof(GameDLL.gamedir), + "%s/%s", buf, gamedir); + STRNCPY(GameDLL.name, gamedir, sizeof(GameDLL.name)); + } + + META_DEBUG(3, ("Game: %s", GameDLL.name)); + + return(mTRUE); +} + +// Load game DLL. +// meta_errno values: +// - ME_DLOPEN couldn't dlopen game dll file +// - ME_DLMISSING couldn't find required routine in game dll +// (GiveFnptrsToDll, GetEntityAPI, GetEntityAPI2) +mBOOL DLLINTERNAL meta_load_gamedll(void) { + int iface_vers; + int found=0; + + GIVE_ENGINE_FUNCTIONS_FN pfn_give_engfuncs; + GETNEWDLLFUNCTIONS_FN pfn_getapinew; + GETENTITYAPI2_FN pfn_getapi2; + GETENTITYAPI_FN pfn_getapi; + + if(!setup_gamedll(&GameDLL)) { + META_WARNING("dll: Unrecognized game: %s", GameDLL.name); + // meta_errno should be already set in lookup_game() + return(mFALSE); + } + + // open the game DLL + if(!(GameDLL.handle=DLOPEN(GameDLL.pathname))) { + META_WARNING("dll: Couldn't load game DLL %s: %s", GameDLL.pathname, + DLERROR()); + RETURN_ERRNO(mFALSE, ME_DLOPEN); + } + + // Used to only pass our table of engine funcs if a loaded plugin + // wanted to catch one of the functions, but now that plugins are + // dynamically loadable at any time, we have to always pass our table, + // so that any plugin loaded later can catch what they need to. + if((pfn_give_engfuncs = (GIVE_ENGINE_FUNCTIONS_FN) DLSYM(GameDLL.handle, "GiveFnptrsToDll"))) + { + pfn_give_engfuncs(&meta_engfuncs, gpGlobals); + META_DEBUG(3, ("dll: Game '%s': Called GiveFnptrsToDll", + GameDLL.name)); + + //activate linkent-replacement after give_engfuncs so that if game dll is + //plugin too and uses same method we get combined export table of plugin + //and game dll + if(!init_linkent_replacement(metamod_handle, GameDLL.handle)) { + META_WARNING("dll: Couldn't load linkent replacement for game DLL"); + RETURN_ERRNO(mFALSE, ME_DLERROR); + } + } + else { + META_WARNING("dll: Couldn't find GiveFnptrsToDll() in game DLL '%s': %s", + GameDLL.name, DLERROR()); + RETURN_ERRNO(mFALSE, ME_DLMISSING); + } + + // Yes...another macro. +#define GET_FUNC_TABLE_FROM_GAME(gamedll, pfnGetFuncs, STR_GetFuncs, struct_field, API_TYPE, TABLE_TYPE, vers_pass, vers_int, vers_want, gotit) \ + if((pfnGetFuncs = (API_TYPE) DLSYM(gamedll.handle, STR_GetFuncs))) { \ + gamedll.funcs.struct_field = (TABLE_TYPE*) calloc(1, sizeof(TABLE_TYPE)); \ + if(!gamedll.funcs.struct_field) {\ + META_WARNING("malloc failed for gamedll struct_field: %s", STR_GetFuncs); \ + } \ + else if(pfnGetFuncs(gamedll.funcs.struct_field, vers_pass)) { \ + META_DEBUG(3, ("dll: Game '%s': Found %s", gamedll.name, STR_GetFuncs)); \ + gotit=1; \ + } \ + else { \ + META_WARNING("dll: Failure calling %s in game '%s'", STR_GetFuncs, gamedll.name); \ + free(gamedll.funcs.struct_field); \ + gamedll.funcs.struct_field=NULL; \ + if(vers_int != vers_want) { \ + META_WARNING("dll: Interface version didn't match; we wanted %d, they had %d", vers_want, vers_int); \ + /* reproduce error from engine */ \ + META_CONS("=================="); \ + META_CONS("Game DLL version mismatch"); \ + META_CONS("DLL version is %d, engine version is %d", vers_int, vers_want); \ + if(vers_int > vers_want) \ + META_CONS("Engine appears to be outdated, check for updates"); \ + else \ + META_CONS("The game DLL for %s appears to be outdated, check for updates", GameDLL.name); \ + META_CONS("=================="); \ + ALERT(at_error, "Exiting...\n"); \ + } \ + } \ + } \ + else { \ + META_DEBUG(5, ("dll: Game '%s': No %s", gamedll.name, STR_GetFuncs)); \ + gamedll.funcs.struct_field=NULL; \ + } + + // Look for API-NEW interface in Game dll. We do this before API2/API, because + // that's what the engine appears to do.. + iface_vers=NEW_DLL_FUNCTIONS_VERSION; + GET_FUNC_TABLE_FROM_GAME(GameDLL, pfn_getapinew, "GetNewDLLFunctions", newapi_table, + GETNEWDLLFUNCTIONS_FN, meta_new_dll_functions_t, + &iface_vers, iface_vers, NEW_DLL_FUNCTIONS_VERSION, found); + + // Look for API2 interface in plugin; preferred over API-1. + found=0; + iface_vers=INTERFACE_VERSION; + GET_FUNC_TABLE_FROM_GAME(GameDLL, pfn_getapi2, "GetEntityAPI2", dllapi_table, + GETENTITYAPI2_FN, DLL_FUNCTIONS, + &iface_vers, iface_vers, INTERFACE_VERSION, found); + + // Look for API-1 in plugin, if API2 interface wasn't found. + if(!found) { + found=0; + GET_FUNC_TABLE_FROM_GAME(GameDLL, pfn_getapi, "GetEntityAPI", dllapi_table, + GETENTITYAPI_FN, DLL_FUNCTIONS, + INTERFACE_VERSION, INTERFACE_VERSION, INTERFACE_VERSION, found); + } + + // If didn't find either, return failure. + if(!found) { + META_WARNING("dll: Couldn't find either GetEntityAPI nor GetEntityAPI2 in game DLL '%s'", GameDLL.name); + RETURN_ERRNO(mFALSE, ME_DLMISSING); + } + + META_LOG("Game DLL for '%s' loaded successfully", GameDLL.desc); + return(mTRUE); +} diff --git a/src/metamod/metamod.def b/src/metamod/metamod.def new file mode 100644 index 0000000..b62aafb --- /dev/null +++ b/src/metamod/metamod.def @@ -0,0 +1,5 @@ +LIBRARY metamod +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/src/metamod/metamod.h b/src/metamod/metamod.h new file mode 100644 index 0000000..1b699c9 --- /dev/null +++ b/src/metamod/metamod.h @@ -0,0 +1,256 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// metamod.h - (main) description of metamod operations + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef METAMOD_H +#define METAMOD_H + +#include "comp_dep.h" +#include "meta_api.h" // META_RES, etc +#include "mlist.h" // MPluginList, etc +#include "mreg.h" // MRegCmdList, etc +#include "conf_meta.h" // MConfig +#include "osdep.h" // NAME_MAX, etc +#include "types_meta.h" // mBOOL +#include "mplayer.h" // MPlayerList +#include "meta_eiface.h" // HL_enginefuncs_t, meta_enginefuncs_t +#include "engine_t.h" // engine_t, Engine + +// file that lists plugins to load at startup +#define PLUGINS_INI "addons/metamod/plugins.ini" +#define OLD_PLUGINS_INI "metamod.ini" + +// file that contains commands to metamod plugins at startup +#define EXEC_CFG "addons/metamod/exec.cfg" +#define OLD_EXEC_CFG "metaexec.cfg" + +// previously, file that contained path for an override-gamedll +#define OLD_GAMEDLL_TXT "metagame.ini" + +// generic config file +#define CONFIG_INI "addons/metamod/config.ini" + +// metamod module handle +extern DLHANDLE metamod_handle DLLHIDDEN; + +// cvar to contain version +extern cvar_t meta_version DLLHIDDEN; + +// Info about the game dll/mod. +typedef struct gamedll_s { + char name[NAME_MAX]; // ie "cstrike" (from gamedir) + const char *desc; // ie "Counter-Strike" + char gamedir[PATH_MAX]; // ie "/home/willday/half-life/cstrike" + char pathname[PATH_MAX]; // ie "/home/willday/half-life/cstrike/dlls/cs_i386.so" + char const *file; // ie "cs_i386.so" + char real_pathname[PATH_MAX]; // in case pathname overridden by bot, etc + DLHANDLE handle; + gamedll_funcs_t funcs; // dllapi_table, newapi_table +} gamedll_t; +extern gamedll_t GameDLL DLLHIDDEN; + +// SDK variables for storing engine funcs and globals. +extern HL_enginefuncs_t g_engfuncs DLLHIDDEN; +extern globalvars_t *gpGlobals DLLHIDDEN; + +// Our modified version of the engine funcs, to give to plugins. +extern meta_enginefuncs_t g_plugin_engfuncs DLLHIDDEN; + +// Config structure. +extern MConfig *Config DLLHIDDEN; + +// List of plugins loaded/opened/running. +extern MPluginList *Plugins DLLHIDDEN; + +// List of command functions registered by plugins. +extern MRegCmdList *RegCmds DLLHIDDEN; + +// List of cvar structures registered by plugins. +extern MRegCvarList *RegCvars DLLHIDDEN; + +// List of user messages registered by gamedll. +extern MRegMsgList *RegMsgs DLLHIDDEN; + +// Data provided to plugins. +// Separate copies to prevent plugins from modifying "readable" parts. +// See meta_api.h for meta_globals_t structure. +extern meta_globals_t PublicMetaGlobals DLLHIDDEN; +extern meta_globals_t PrivateMetaGlobals DLLHIDDEN; + +// hook function tables +extern DLL_FUNCTIONS *g_pHookedDllFunctions DLLHIDDEN; +extern NEW_DLL_FUNCTIONS *g_pHookedNewDllFunctions DLLHIDDEN; + +extern int metamod_not_loaded DLLHIDDEN; + +// Holds cached player info, right now only things for querying cvars +// Max players is always 32, small enough that we can use a static array +extern MPlayerList g_Players DLLHIDDEN; + +extern int requestid_counter DLLHIDDEN; + +int DLLINTERNAL metamod_startup(void); + +mBOOL DLLINTERNAL meta_init_gamedll(void); +mBOOL DLLINTERNAL meta_load_gamedll(void); + +// ===== lotsa macros... ====================================================== + +// These are the meat of the metamod processing, and are as ugly as (or +// uglier) than they look. This is done via macros, because of the varying +// parameter types (int, void, edict_t*, etc) as well as varying +// function-pointer types and different api tables (dllapi, newapi, +// engine), which just can't be passed to a function. And, since the +// operation is similar for each api call, I didn't want to keep +// duplicating code all over the place. Thus the ugly macros. +// +// The basic operation is, for each api call: +// - iterate through list of plugins +// - for each plugin, if it provides this api call, then call the +// function in the plugin +// - call the "real" function (in the game dll, or from the engine) +// - for each plugin, check for a "post" version of the function, and call +// if present +// +// +// Also, for any api call, each plugin has the opportunity to replace the +// real routine, in two ways: +// - prevent the real routine from being called ("supercede") +// - allow the real routine to be called, but change the value that's +// returned ("override") +// +// Thus after each plugin is called, its META_RETURN flag is checked, and +// action taken appropriately. Note that supercede/override only affects +// the _real_ routine; other plugins will still be called. +// +// In addition to the SUPERCEDE and OVERRIDE flags, there are two +// additional flags a plugin can return: +// - HANDLED ("I did something here") +// - IGNORED ("I didn't really do anything") +// +// These aren't used by metamod itself, but could be used by plugins to +// get an idea if a previous plugin did anything. +// +// +// The 5 basic macros are: +// SETUP +// CALL_PLUGIN +// CALL_GAME and CALL_ENGINE +// RETURN +// +// These 5 are actually used to build second level macros for each api type +// (dllapi, newapi, engine), with the CALL_PLUGIN macro being used twice +// (before and after). Altogether, they end up expanding to approx 150 +// lines of code for _each_ api call. Ack, ugly indeed. +// +// However, due to some functions returning 'void', and others returning an +// actual value, I had to have separate macros for the two, since I +// couldn't seem to generalize the two into a form that the compiler would +// accept. Thus there are "_void" versions of the 5 macros; these are +// listed first. + +// ===== macros for void-returning functions ================================== + +// return (void) +#define RETURN_API_void() \ + return; + +// ===== macros for type-returning functions ================================== + +// return a value +#define RETURN_API(ret_t) \ + {return(GET_RET_CLASS(ret_val, ret_t));} + +// ===== end macros =========================================================== + +#ifdef META_PERFMON + +// ============================================================================ +// Api-hook performance monitoring +// ============================================================================ + +extern long double total_tsc DLLHIDDEN; +extern unsigned long long count_tsc DLLHIDDEN; +extern unsigned long long active_tsc DLLHIDDEN; +extern unsigned long long min_tsc DLLHIDDEN; + +inline unsigned long long DLLINTERNAL GET_TSC(void) { + union { struct { unsigned int eax, edx; } split; unsigned long long full; } tsc; +#ifdef __GNUC__ + __asm__ __volatile__("rdtsc":"=a"(tsc.split.eax), "=d"(tsc.split.edx)); +#else + __asm + { + rdtsc + mov tsc.split.eax, eax + mov tsc.split.edx, edx + } +#endif + return(tsc.full); +} + +#define API_START_TSC_TRACKING() \ + active_tsc = GET_TSC() + +#define API_PAUSE_TSC_TRACKING() \ + total_tsc += GET_TSC() - active_tsc + +#define API_UNPAUSE_TSC_TRACKING() \ + active_tsc = GET_TSC() + +#define API_END_TSC_TRACKING() { \ + unsigned long long run_tsc = GET_TSC() - active_tsc; \ + total_tsc += run_tsc; \ + count_tsc++; \ + if(min_tsc == 0 || run_tsc < min_tsc) \ + min_tsc = run_tsc; \ + } + +// ===== end ================================================================== + +#else + +// ===== performance monitor disabled ========================================= + +#define API_START_TSC_TRACKING() +#define API_PAUSE_TSC_TRACKING() +#define API_UNPAUSE_TSC_TRACKING() +#define API_END_TSC_TRACKING() + +// ===== end ================================================================== + +#endif /*META_PERFMON*/ + +#endif /* METAMOD_H */ diff --git a/src/metamod/metamod.sln b/src/metamod/metamod.sln new file mode 100644 index 0000000..fec7321 --- /dev/null +++ b/src/metamod/metamod.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "metamod", "metamod.vcproj", "{02832A39-E902-46B7-8D47-911C37CF41B0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {02832A39-E902-46B7-8D47-911C37CF41B0}.Debug|Win32.ActiveCfg = Debug|Win32 + {02832A39-E902-46B7-8D47-911C37CF41B0}.Debug|Win32.Build.0 = Debug|Win32 + {02832A39-E902-46B7-8D47-911C37CF41B0}.Release|Win32.ActiveCfg = Release|Win32 + {02832A39-E902-46B7-8D47-911C37CF41B0}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/metamod/metamod.vcproj b/src/metamod/metamod.vcproj new file mode 100644 index 0000000..f03d2b8 --- /dev/null +++ b/src/metamod/metamod.vcproj @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/metamod/mhook.cpp b/src/metamod/mhook.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/metamod/mhook.h b/src/metamod/mhook.h new file mode 100644 index 0000000..e69de29 diff --git a/src/metamod/mlist.cpp b/src/metamod/mlist.cpp new file mode 100644 index 0000000..9399428 --- /dev/null +++ b/src/metamod/mlist.cpp @@ -0,0 +1,901 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mlist.cpp - functions for list of plugins (class MPluginList) + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // errno, etc + +#include // always + +#include "mlist.h" // me +#include "mplugin.h" // class MPlugin +#include "plinfo.h" // plid_t, etc +#include "commands_meta.h" // cmd_meta_pluginlist, etc +#include "metamod.h" // GameDLL, etc +#include "types_meta.h" // mBOOL +#include "log_meta.h" // META_LOG, etc +#include "osdep.h" // win32 snprintf, normalize_pathname, +#include "osdep_p.h" + +// Constructor +MPluginList::MPluginList(const char *ifile) + : size(MAX_PLUGINS), endlist(0) +{ + int i; + // store filename of ini file + STRNCPY(inifile, ifile, sizeof(inifile)); + // initialize array + for(i=0; i < size; i++) { + //reset to empty + plist[i].index=i+1; + reset_plugin(&plist[i]); + } + endlist=0; +} + +// Resets plugin to empty +void DLLINTERNAL MPluginList::reset_plugin(MPlugin *pl_find) { + int i; + + //calculate index + i = pl_find - &plist[0]; + + //free any pointers first + pl_find->free_api_pointers(); + + //set zero + memset(pl_find, 0, sizeof(*pl_find)); + + pl_find->index=i+1; // 1-based +} + +// Find a plugin based on the plugin index #. +// meta_errno values: +// - ME_ARGUMENT invalid pindex +// - ME_NOTFOUND couldn't find a matching plugin +MPlugin * DLLINTERNAL MPluginList::find(int pindex) { + MPlugin *pfound; + if(pindex <= 0) + RETURN_ERRNO(NULL, ME_ARGUMENT); + pfound=&plist[pindex-1]; + if(pfound->status < PL_VALID) + RETURN_ERRNO(NULL, ME_NOTFOUND); + else + return(pfound); +} + +// Find a plugin based on the plugin handle. +// meta_errno values: +// - ME_ARGUMENT invalid pindex +// - ME_NOTFOUND couldn't find a matching plugin +MPlugin * DLLINTERNAL MPluginList::find(DLHANDLE handle) { + int i; + + if(!handle) + RETURN_ERRNO(NULL, ME_ARGUMENT); + for(i=0; i < endlist; i++) { + if(plist[i].status < PL_VALID) + continue; + if(plist[i].handle == handle) + return(&plist[i]); + } + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// Clear source_plugin_index on all matching plugins +void DLLINTERNAL MPluginList::clear_source_plugin_index(int source_index) { + int i; + + if(source_index <= 0) + return; + + for(i=0; i < endlist; i++) { + if(plist[i].status < PL_VALID) + continue; + if(plist[i].source_plugin_index == source_index) + plist[i].source_plugin_index = -1; + } +} + +// Find if any plugin has been loaded by plugin 'source_index' +mBOOL DLLINTERNAL MPluginList::found_child_plugins(int source_index) { + int i; + + if(source_index <= 0) + return(mFALSE); + + for(i=0; i < endlist; i++) { + if(plist[i].status < PL_VALID) + continue; + if(plist[i].source_plugin_index == source_index) + return(mTRUE); + } + + return(mFALSE); +} + +// Try make endlist lower (called after plugin unload) +void DLLINTERNAL MPluginList::trim_list(void) { + int i,n; + + if(endlist <= 0) + return; + + for(i=0,n=0; i < endlist; i++) { + if(plist[i].status == PL_EMPTY) + continue; + n=i+1; + } + + if(n < endlist) + endlist=n; +} + +// Find a plugin with the given plid. +// meta_errno values: +// - ME_ARGUMENT null plid_t +// - ME_NOTFOUND couldn't find a matching plugin +MPlugin * DLLINTERNAL MPluginList::find(plid_t id) { + int i; + + if(!id) + RETURN_ERRNO(NULL, ME_ARGUMENT); + for(i=0; i < endlist; i++) { + if(plist[i].status < PL_VALID) + continue; + if(plist[i].info == id) + return(&plist[i]); + } + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// Find a plugin with the given pathname. +// meta_errno values: +// - ME_ARGUMENT null path +// - ME_NOTFOUND couldn't find a matching plugin +MPlugin * DLLINTERNAL MPluginList::find(const char *findpath) { + int i; + + if(!findpath) + RETURN_ERRNO(NULL, ME_ARGUMENT); + META_DEBUG(8, ("Looking for loaded plugin with dlfnamepath: %s", findpath)); + for(i=0; i < endlist; i++) { + META_DEBUG(9, ("Looking at: plugin %s loadedpath: %s", plist[i].file, plist[i].pathname)); + if(plist[i].status < PL_VALID) + continue; + if(strmatch(plist[i].pathname, findpath)) { + META_DEBUG(8, ("Found loaded plugin %s", plist[i].file)); + return(&plist[i]); + } + } + META_DEBUG(8, ("No loaded plugin found with path: %s", findpath)); + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// Find a plugin that uses the given memory location. +// meta_errno values: +// - ME_ARGUMENT null memptr +// - ME_NOTFOUND couldn't find a matching plugin +// - errno's from DLFNAME() +MPlugin * DLLINTERNAL MPluginList::find_memloc(void *memptr) { +#ifdef linux + const char *dlfile; + + if(!memptr) + RETURN_ERRNO(NULL, ME_ARGUMENT); + if(!(dlfile=DLFNAME(memptr))) { + META_DEBUG(8, ("DLFNAME failed to find memloc %d", memptr)); + // meta_errno should be already set in DLFNAME + return(NULL); + } + + return(find(dlfile)); +#else + DLHANDLE dlhandle; + + if(!memptr) + RETURN_ERRNO(NULL, ME_ARGUMENT); + if(!(dlhandle=get_module_handle_of_memptr(memptr))) { + META_DEBUG(8, ("DLFNAME failed to find memloc %d", memptr)); + RETURN_ERRNO(NULL, ME_NOTFOUND); + } + + return(find(dlhandle)); +#endif +} + +// Find a plugin with non-ambiguous prefix string matching desc, file, +// name, or logtag. +// meta_errno values: +// - ME_ARGUMENT null prefix +// - ME_NOTFOUND couldn't find a matching plugin +// - ME_NOTUNIQ found multiple matches; no unique match +MPlugin * DLLINTERNAL MPluginList::find_match(const char *prefix) { + int i, len; + MPlugin *iplug, *pfound; + char buf[NAME_MAX]; + + if(!prefix) + RETURN_ERRNO(NULL, ME_ARGUMENT); + pfound=NULL; + len=strlen(prefix); + safevoid_snprintf(buf, sizeof(buf), "mm_%s", prefix); + for(i=0; i < endlist; i++) { + iplug=&plist[i]; + if(iplug->status < PL_VALID) + continue; + if(iplug->info && strncasecmp(iplug->info->name, prefix, len) == 0) { + if(pfound) + RETURN_ERRNO(NULL, ME_NOTUNIQ); + pfound=iplug; + continue; + } + else if(strncasecmp(iplug->desc, prefix, len) == 0) { + if(pfound) + RETURN_ERRNO(NULL, ME_NOTUNIQ); + pfound=iplug; + continue; + } + else if(strncasecmp(iplug->file, prefix, len) == 0) { + if(pfound) + RETURN_ERRNO(NULL, ME_NOTUNIQ); + pfound=iplug; + continue; + } + else if(strncasecmp(iplug->file, buf, strlen(buf)) == 0) { + if(pfound) + RETURN_ERRNO(NULL, ME_NOTUNIQ); + pfound=iplug; + continue; + } + else if(iplug->info + && strncasecmp(iplug->info->logtag, prefix, len) == 0) + { + if(pfound) + RETURN_ERRNO(NULL, ME_NOTUNIQ); + pfound=iplug; + continue; + } + } + + if(pfound) + return(pfound); + + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// Find a plugin with same file, logtag, desc or significant +// prefix of file. Uses the platform_match() method of MPlugin. +// meta_errno values: +// - ME_ARGUMENT null prefix +// - ME_NOTFOUND couldn't find a matching plugin +MPlugin * DLLINTERNAL MPluginList::find_match(MPlugin *pmatch) { + int i; + MPlugin *iplug, *pfound; + if(!pmatch) + RETURN_ERRNO(NULL, ME_ARGUMENT); + pfound=NULL; + for(i=0; i < endlist; i++) { + iplug=&plist[i]; + if(pmatch->platform_match(iplug)) { + pfound=iplug; + break; + } + } + + if(pfound) + return(pfound); + + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// Add a plugin to the list. +// meta_errno values: +// - ME_MAXREACHED reached max plugins +MPlugin * DLLINTERNAL MPluginList::add(MPlugin *padd) { + int i; + MPlugin *iplug; + + // Find either: + // - a slot in the list that's not being used + // - the end of the list + for(i=0; i < endlist && plist[i].status != PL_EMPTY; i++); + + // couldn't find a slot to use + if(i==size) { + META_WARNING("Couldn't add plugin '%s' to list; reached max plugins (%d)", + padd->file, i); + RETURN_ERRNO(NULL, ME_MAXREACHED); + } + + // if we found the end of the list, advance end marker + if(i==endlist) + endlist++; + iplug = &plist[i]; + + // copy filename into this free slot + STRNCPY(iplug->filename, padd->filename, sizeof(iplug->filename)); + // Copy file offset ptr. + // Can't just copy ptr, as it points to offset in padd, which will go + // away; need to point to corresponding offset in iplug. + iplug->file = iplug->filename + (padd->file - padd->filename); + // copy description + STRNCPY(iplug->desc, padd->desc, sizeof(iplug->desc)); + // copy pathname + STRNCPY(iplug->pathname, padd->pathname, sizeof(iplug->pathname)); + // copy source + iplug->source=padd->source; + // copy loader-plugin + iplug->source_plugin_index=padd->source_plugin_index; + // copy status + iplug->status=padd->status; + + return(iplug); +} + + +// Read plugins.ini at server startup. +// meta_errno values: +// - ME_NOFILE ini file missing or empty +mBOOL DLLINTERNAL MPluginList::ini_startup() { + FILE *fp; + char line[MAX_STRBUF_LEN]; + int n, ln; + MPlugin *pmatch; + + if(!valid_gamedir_file(inifile)) { + META_WARNING("ini: Metamod plugins file empty or missing: %s", inifile); + RETURN_ERRNO(mFALSE, ME_NOFILE); + } + full_gamedir_path(inifile, inifile); + + fp=fopen(inifile, "r"); + if(!fp) { + META_WARNING("ini: Unable to open plugins file '%s': %s", inifile, + strerror(errno)); + RETURN_ERRNO(mFALSE, ME_NOFILE); + } + + META_LOG("ini: Begin reading plugins list: %s", inifile); + for(n=0, ln=1; !feof(fp) && fgets(line, sizeof(line), fp) && n < size; ln++) { + // Remove line terminations. + char *cp; + if((cp=strrchr(line, '\r'))) + *cp='\0'; + if((cp=strrchr(line, '\n'))) + *cp='\0'; + // Parse directly into next entry in array + if(!plist[n].ini_parseline(line)) { + if(meta_errno==ME_FORMAT) + META_WARNING("ini: Skipping malformed line %d of %s", ln, + inifile); + continue; + } + // Check for a duplicate - an existing entry with this pathname. + if(find(plist[n].pathname)) { + // Should we check platform specific level here? + META_INFO("ini: Skipping duplicate plugin, line %d of %s: %s", + ln, inifile, plist[n].pathname); + continue; + } + // Check for a matching platform with different platform specifics + // level. + if(NULL != (pmatch=find_match(&plist[n]))) { + if(pmatch->pfspecific >= plist[n].pfspecific) { + META_DEBUG(1, ("ini: Skipping plugin, line %d of %s: plugin with higher platform specific level already exists. (%d >= %d)", + ln, inifile, pmatch->pfspecific, plist[n].pfspecific)); + continue; + } + META_DEBUG(1, ("ini: Plugin in line %d overrides existing plugin with lower platform specific level %d, ours %d", + ln, pmatch->pfspecific, plist[n].pfspecific)); + //reset to empty + reset_plugin(pmatch); + } + plist[n].action=PA_LOAD; + META_LOG("ini: Read plugin config for: %s", plist[n].desc); + n++; + endlist=n; // mark end of list + } + META_LOG("ini: Finished reading plugins list: %s; Found %d plugins to load", + inifile, n); + + fclose(fp); + if(!n) { + META_WARNING("ini: Warning; no plugins found to load?"); + } + return(mTRUE); +} + +// Re-read plugins.ini looking for added/deleted/changed plugins. +// meta_errno values: +// - ME_NOFILE ini file missing or empty +mBOOL DLLINTERNAL MPluginList::ini_refresh() { + FILE *fp; + char line[MAX_STRBUF_LEN]; + int n, ln; + MPlugin pl_temp; + MPlugin *pl_found, *pl_added; + + fp=fopen(inifile, "r"); + if(!fp) { + META_WARNING("ini: Unable to open plugins file '%s': %s", inifile, + strerror(errno)); + RETURN_ERRNO(mFALSE, ME_NOFILE); + } + + META_LOG("ini: Begin re-reading plugins list: %s", inifile); + for(n=0, ln=1; !feof(fp) && fgets(line, sizeof(line), fp) && n < size; ln++) + { + // Remove line terminations. + char *cp; + if((cp=strrchr(line, '\r'))) + *cp='\0'; + if((cp=strrchr(line, '\n'))) + *cp='\0'; + // Parse into a temp plugin + memset(&pl_temp, 0, sizeof(pl_temp)); + if(!pl_temp.ini_parseline(line)) { + if(meta_errno==ME_FORMAT) + META_WARNING("ini: Skipping malformed line %d of %s", + ln, inifile); + continue; + } + // Try to find plugin with this pathname in the current list of + // plugins. + if(!(pl_found=find(pl_temp.pathname))) { + // Check for a matching platform with higher platform specifics + // level. + if(NULL != (pl_found=find_match(&pl_temp))) { + if(pl_found->pfspecific >= pl_temp.pfspecific) { + META_DEBUG(1, ("ini: Skipping plugin, line %d of %s: plugin with higher platform specific level already exists. (%d >= %d)", + ln, inifile, pl_found->pfspecific, pl_temp.pfspecific)); + continue; + } + if(PA_LOAD == pl_found->action) { + META_DEBUG(1, ("ini: Plugin in line %d overrides loading of plugin with lower platform specific level %d, ours %d", + ln, pl_found->pfspecific, pl_temp.pfspecific)); + //reset to empty + reset_plugin(pl_found); + } + else { + META_DEBUG(1, ("ini: Plugin in line %d should override existing plugin with lower platform specific level %d, ours %d. Unable to comply.", + ln, pl_found->pfspecific, pl_temp.pfspecific)); + continue; + } + } + // new plugin; add to list + if((pl_added=add(&pl_temp))) { + // try to load this plugin at the next opportunity + pl_added->action=PA_LOAD; + } + else + // error details logged in add() + continue; + } + else { + // This plugin is already in the current list of plugins. + // Pathname already matches. Recopy desc, if specified in + // plugins.ini. + if(pl_temp.desc[0] != '<') + STRNCPY(pl_found->desc, pl_temp.desc, sizeof(pl_found->desc)); + + // Check the file to see if it looks like it's been modified + // since we last loaded it. + if(!pl_found->newer_file()) { + if(meta_errno==ME_NOFILE) { + META_WARNING("ini: Skipping plugin, couldn't stat file '%s': %s", + pl_found->pathname, strerror(errno)); + continue; + } + else { + // File hasn't been updated. + // Keep plugin (don't let refresh() unload it). + pl_found->action=PA_KEEP; + } + } + // Newer file on disk. + else if(pl_found->status >= PL_OPENED) { + META_DEBUG(2, ("ini: Plugin '%s' has newer file on disk", pl_found->desc)); + pl_found->action=PA_RELOAD; + } + else + META_WARNING("ini: Plugin '%s' has newer file, but unexpected status (%s)", + pl_found->desc, pl_found->str_status()); + } + if(NULL != pl_found) { + META_LOG("ini: Read plugin config for: %s", pl_found->desc); + } + else { + META_LOG("ini: Read plugin config for: %s", pl_temp.desc); + } + n++; + } + META_LOG("ini: Finished reading plugins list: %s; Found %d plugins", inifile, n); + + fclose(fp); + if(!n) { + META_WARNING("ini: Warning; no plugins found to load?"); + } + return(mTRUE); +} + +// Load a plugin from plugin request. +// meta_errno values: +// - errno's from resolve() +// - ME_ALREADY this plugin already loaded +// - errno's from add() +// - errno's from load() +MPlugin * DLLINTERNAL MPluginList::plugin_addload(plid_t plid, const char *fname, PLUG_LOADTIME now) { + MPlugin pl_temp; + MPlugin *pl_found, *pl_added, *pl_loader; + + // Find loader plugin + if(!(pl_loader=find(plid))) { + // Couldn't find a matching file on disk + META_DEBUG(1, ("Couldn't find plugin that gave this loading request!")); + // meta_errno should be already set in resolve() + RETURN_ERRNO(NULL, ME_BADREQ); + } + + memset(&pl_temp, 0, sizeof(pl_temp)); + + // copy filename + if(!pl_temp.plugin_parseline(fname, pl_loader->index)) { + // parse_plugin_load doesn't return mFALSE. + RETURN_ERRNO(NULL, ME_NOTFOUND); + } + + // resolve given path into a file; accepts various "shortcut" + // pathnames. + if(pl_temp.resolve() != mTRUE) { + // Couldn't find a matching file on disk + META_DEBUG(1, ("Couldn't resolve given path into a file: %s", pl_temp.file)); + // meta_errno should be already set in resolve() + RETURN_ERRNO(NULL, ME_NOTFOUND); + } + + // Try to find plugin with this pathname in the current list of + // plugins. + if((pl_found=find(pl_temp.pathname))) { + // Already in list + META_DEBUG(1, ("Plugin '%s' already in current list; file=%s desc='%s'", + pl_temp.file, pl_found->file, pl_found->desc)); + RETURN_ERRNO(NULL, ME_ALREADY); + } + // new plugin; add to list + if(!(pl_added=add(&pl_temp))) { + META_DEBUG(1, ("Couldn't add plugin '%s' to list; see log", pl_temp.desc)); + // meta_errno should be already set in add() + return(NULL); + } + + // try to load new plugin (setting 'must load now') + pl_added->action=PA_LOAD; + if(!pl_added->load(now)) { + // load failed + if(meta_errno==ME_NOTALLOWED || meta_errno==ME_DELAYED) { + META_DEBUG(1, ("Plugin '%s' couldn't attach; only allowed %s", + pl_added->desc, pl_added->str_loadable(SL_ALLOWED))); + pl_added->clear(); + } + else if(pl_added->status == PL_OPENED) + META_DEBUG(1, ("Opened plugin '%s', but failed to attach; see log", pl_added->desc)); + else + META_DEBUG(1, ("Couldn't load plugin '%s'; see log", pl_added->desc)); + // meta_errno should be already set in load() + return(NULL); + } + META_DEBUG(1, ("Loaded plugin '%s' successfully", pl_added->desc)); + meta_errno = ME_NOERROR; + return(pl_added); +} + +// Load a plugin from a console command. +// meta_errno values: +// - errno's from cmd_parseline() +// - errno's from resolve() +// - ME_ALREADY this plugin already loaded +// - errno's from add() +// - errno's from load() +mBOOL DLLINTERNAL MPluginList::cmd_addload(const char *args) { + MPlugin pl_temp; + MPlugin *pl_found, *pl_added; + + memset(&pl_temp, 0, sizeof(pl_temp)); + + // XXX move back to comands_meta ? + + // parse into a temp plugin + if(pl_temp.cmd_parseline(args) != mTRUE) { + META_CONS("Couldn't parse 'meta load' arguments: %s", args); + // meta_errno should be already set in cmd_parseline() + return(mFALSE); + } + + // resolve given path into a file; accepts various "shortcut" + // pathnames. + if(pl_temp.resolve() != mTRUE) { + // Couldn't find a matching file on disk + META_CONS("Couldn't resolve given path into a file: %s", + pl_temp.file); + // meta_errno should be already set in resolve() + return(mFALSE); + } + + // Try to find plugin with this pathname in the current list of + // plugins. + if((pl_found=find(pl_temp.pathname))) { + // Already in list + META_CONS("Plugin '%s' already in current list; file=%s desc='%s'", + pl_temp.file, pl_found->file, pl_found->desc); + RETURN_ERRNO(mFALSE, ME_ALREADY); + } + // new plugin; add to list + if(!(pl_added=add(&pl_temp))) { + META_CONS("Couldn't add plugin '%s' to list; see log", pl_temp.desc); + // meta_errno should be already set in add() + return(mFALSE); + } + + // try to load new plugin + pl_added->action=PA_LOAD; + if(!pl_added->load(PT_ANYTIME)) { + // load failed + if(meta_errno==ME_DELAYED) + META_CONS("Loaded plugin '%s', but will wait to become active, %s", + pl_added->desc, pl_added->str_loadable(SL_ALLOWED)); + else if(meta_errno==ME_NOTALLOWED) { + META_CONS("Plugin '%s' couldn't attach; only allowed %s", + pl_added->desc, pl_added->str_loadable(SL_ALLOWED)); + pl_added->clear(); + } + else if(pl_added->status == PL_OPENED) + META_CONS("Opened plugin '%s', but failed to attach; see log", pl_added->desc); + else + META_CONS("Couldn't load plugin '%s'; see log", pl_added->desc); + show(); + // meta_errno should be already set in load() + return(mFALSE); + } + META_CONS("Loaded plugin '%s' successfully", pl_added->desc); + show(); + return(mTRUE); +} + +// Load plugins at startup. +// meta_errno values: +// - errno's from ini_startup() +mBOOL DLLINTERNAL MPluginList::load() { + int i, n; + + if(!ini_startup()) { + META_WARNING("Problem loading plugins.ini: %s", inifile); + // meta_errno should be already set in ini_startup() + return(mFALSE); + } + + META_LOG("dll: Loading plugins..."); + for(i=0, n=0; i < endlist; i++) { + if(plist[i].status < PL_VALID) + continue; + if(plist[i].load(PT_STARTUP) == mTRUE) + n++; + else + // all plugins should be loadable at startup... + META_WARNING("dll: Failed to load plugin '%s'", plist[i].file); + } + META_LOG("dll: Finished loading %d plugins", n); + return(mTRUE); +} + +// Update list of loaded plugins from ini file, and load any new/changed plugins. +// meta_errno values: +// - errno's from ini_refresh() +mBOOL DLLINTERNAL MPluginList::refresh(PLUG_LOADTIME now) { + int i, ndone=0, nkept=0, nloaded=0, nunloaded=0, nreloaded=0, ndelayed=0; + MPlugin *iplug; + + if(!ini_refresh()) { + META_WARNING("dll: Problem reloading plugins.ini: %s", inifile); + // meta_errno should be already set in ini_refresh() + return(mFALSE); + } + + META_LOG("dll: Updating plugins..."); + for(i=0; i < endlist; i++) { + iplug=&plist[i]; + if(iplug->status < PL_VALID) + continue; + switch(iplug->action) { + case PA_KEEP: + META_DEBUG(1, ("Keeping plugin '%s'", iplug->desc)); + iplug->action=PA_NONE; + nkept++; + break; + case PA_LOAD: + META_DEBUG(1, ("Loading plugin '%s'", iplug->desc)); + if(iplug->load(now)) + nloaded++; + else if(meta_errno==ME_DELAYED) + ndelayed++; + break; + case PA_RELOAD: + META_DEBUG(1, ("Reloading plugin '%s'", iplug->desc)); + if(iplug->reload(now, PNL_FILE_NEWER)) + nreloaded++; + else if(meta_errno==ME_DELAYED) + ndelayed++; + break; + case PA_NONE: + // If previously loaded from ini, but apparently removed from new ini. + if(iplug->source==PS_INI && iplug->status >= PL_RUNNING) { + META_DEBUG(1, ("Unloading plugin '%s'", iplug->desc)); + iplug->action=PA_UNLOAD; + if(iplug->unload(now, PNL_INI_DELETED, PNL_INI_DELETED)) + nunloaded++; + else if(meta_errno==ME_DELAYED) + ndelayed++; + } + break; + case PA_ATTACH: + // Previously requested attach, but was delayed? + META_DEBUG(1, ("Retrying attach plugin '%s'", iplug->desc)); + if(iplug->retry(now, PNL_DELAYED)) + nloaded++; + else if(meta_errno==ME_DELAYED) + ndelayed++; + break; + case PA_UNLOAD: + // Previously requested unload, but was delayed? + META_DEBUG(1, ("Retrying unload plugin '%s'", iplug->desc)); + if(iplug->retry(now, PNL_DELAYED)) + nunloaded++; + else if(meta_errno==ME_DELAYED) + ndelayed++; + break; + case PA_NULL: + META_WARNING("dll: Unexpected action for plugin '%s': '%s'", iplug->desc, iplug->str_action()); + break; + default: + META_WARNING("dll: Unrecognized action for plugin '%s': '%s'", iplug->desc, iplug->str_action()); + break; + } + ndone++; + } + META_LOG("dll: Finished updating %d plugins; kept %d, loaded %d, unloaded %d, reloaded %d, delayed %d", + ndone, nkept, nloaded, nunloaded, nreloaded, ndelayed); + return(mTRUE); +} + +// Re-enable any plugins currently paused. +// meta_errno values: +// - none +void DLLINTERNAL MPluginList::unpause_all(void) { + int i; + MPlugin *iplug; + for(i=0; i < endlist; i++) { + iplug=&plist[i]; + if(iplug->status==PL_PAUSED) + iplug->unpause(); + } +} + +// Retry any pending actions on plugins, for instance load/unload delayed +// until changelevel. +// meta_errno values: +// - none +void DLLINTERNAL MPluginList::retry_all(PLUG_LOADTIME now) { + int i; + MPlugin *iplug; + for(i=0; i < endlist; i++) { + iplug=&plist[i]; + if(iplug->action != PA_NONE) + iplug->retry(now, PNL_DELAYED); + } +} + +// List plugins and information about them in a formatted table. +// meta_errno values: +// - none +void DLLINTERNAL MPluginList::show(int source_index) { + int i, n=0, r=0; + MPlugin *pl; + char desc[15+1], file[16+1], vers[7+1]; // plus 1 for term null + + if(source_index <= 0) + META_CONS("Currently loaded plugins:"); + else + META_CONS("Child plugins:"); + + META_CONS(" %*s %-*s %-4s %-4s %-*s v%-*s %-*s %-5s %-5s", + WIDTH_MAX_PLUGINS, "", + sizeof(desc)-1, "description", + "stat", "pend", + sizeof(file)-1, "file", sizeof(vers)-1, "ers", + 2+WIDTH_MAX_PLUGINS, "src", + "load ", "unlod"); + + for(i=0; i < endlist; i++) { + pl=&plist[i]; + if(pl->status < PL_VALID) + continue; + if(source_index > 0 && pl->source_plugin_index != source_index) + continue; + STRNCPY(desc, pl->desc, sizeof(desc)); + STRNCPY(file, pl->file, sizeof(file)); + if(pl->info && pl->info->version) + STRNCPY(vers, pl->info->version, sizeof(vers)); + else + STRNCPY(vers, " -", sizeof(vers)); + META_CONS(" [%*d] %-*s %-4s %-4s %-*s v%-*s %-*s %-5s %-5s", + WIDTH_MAX_PLUGINS, pl->index, + sizeof(desc)-1, desc, + pl->str_status(ST_SHOW), pl->str_action(SA_SHOW), + sizeof(file)-1, file, sizeof(vers)-1, vers, + 2+WIDTH_MAX_PLUGINS, pl->str_source(SO_SHOW), + pl->str_loadable(SL_SHOW), pl->str_unloadable(SL_SHOW)); + if(pl->status == PL_RUNNING) + r++; + n++; + } + + META_CONS("%d plugins, %d running", n, r); +} + +// List plugins and information to Player/client entity. Differs from the +// "meta list" console command in that: +// - Shows only "running" plugins, skipping any failed or paused plugins. +// - Limited info about each plugin, mostly the "public" info (name, author, +// etc). +// meta_errno values: +// - none +void DLLINTERNAL MPluginList::show_client(edict_t *pEntity) { + int i, n=0; + MPlugin *pl; + META_CLIENT(pEntity, "Currently running plugins:"); + for(i=0; i < endlist; i++) { + pl=&plist[i]; + if(pl->status != PL_RUNNING || !pl->info) + continue; + n++; + META_CLIENT(pEntity, " [%3d] %s, v%s, %s, by %s, see %s", + n, + pl->info->name ? pl->info->name : "", + pl->info->version ? pl->info->version : "", + pl->info->date ? pl->info->date : "<../../..>", + pl->info->author ? pl->info->author : "", + pl->info->url ? pl->info->url : ""); + } + META_CLIENT(pEntity, "%d plugins", n); +} diff --git a/src/metamod/mlist.h b/src/metamod/mlist.h new file mode 100644 index 0000000..c1597c2 --- /dev/null +++ b/src/metamod/mlist.h @@ -0,0 +1,94 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mlist.h - class and constants to describe a list of plugins + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef MLIST_H +#define MLIST_H + +#include "types_meta.h" // mBOOL +#include "mplugin.h" // class MPlugin +#include "plinfo.h" // plid_t, etc +#include "new_baseclass.h" + +// Max number of plugins we can manage. This is an arbitrary, fixed number, +// for convenience. It would probably be better to dynamically grow the +// list as needed, but we do this for now. +#define MAX_PLUGINS 50 +// Width required to printf above MAX, for show() functions. +#define WIDTH_MAX_PLUGINS 2 + + +// A list of plugins. +class MPluginList : public class_metamod_new { + public: + // data: + MPlugin plist[MAX_PLUGINS]; // array of plugins + int size; // size of list, ie MAX_PLUGINS + int endlist; // index of last used entry + char inifile[PATH_MAX]; // full pathname + + // constructor: + MPluginList(const char *ifile) DLLINTERNAL; + + // functions: + void DLLINTERNAL reset_plugin(MPlugin *pl_find); + MPlugin * DLLINTERNAL find(int pindex); // find by index + MPlugin * DLLINTERNAL find(const char *findpath); // find by pathname + MPlugin * DLLINTERNAL find(plid_t id); // find by plid_t + MPlugin * DLLINTERNAL find(DLHANDLE handle); // find by handle + MPlugin * DLLINTERNAL find_memloc(void *memptr); // find by memory location + MPlugin * DLLINTERNAL find_match(const char *prefix); // find by partial prefix match + MPlugin * DLLINTERNAL find_match(MPlugin *pmatch); // find by platform_match() + MPlugin * DLLINTERNAL add(MPlugin *padd); + + mBOOL DLLINTERNAL found_child_plugins(int source_index); + void DLLINTERNAL clear_source_plugin_index(int source_index); + void DLLINTERNAL trim_list(void); + + mBOOL DLLINTERNAL ini_startup(void); // read inifile at startup + mBOOL DLLINTERNAL ini_refresh(void); // re-read inifile + mBOOL DLLINTERNAL cmd_addload(const char *args); // load from console command + MPlugin * DLLINTERNAL plugin_addload(plid_t plid, const char *fname, PLUG_LOADTIME now); //load from plugin + + mBOOL DLLINTERNAL load(void); // load the list, at startup + mBOOL DLLINTERNAL refresh(PLUG_LOADTIME now); // update from re-read inifile + void DLLINTERNAL unpause_all(void); // unpause any paused plugins + void DLLINTERNAL retry_all(PLUG_LOADTIME now); // retry any pending plugin actions + void DLLINTERNAL show(int source_index); // list plugins to console + void DLLINTERNAL show(void) { show(-1); }; // list plugins to console + void DLLINTERNAL show_client(edict_t *pEntity); // list plugins to player client +}; + +#endif /* MLIST_H */ diff --git a/src/metamod/mm_pextensions.h b/src/metamod/mm_pextensions.h new file mode 100644 index 0000000..2a0329e --- /dev/null +++ b/src/metamod/mm_pextensions.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef MM_PEXTENSIONS_H +#define MM_PEXTENSIONS_H + +#include "plinfo.h" // plid_t +#include "meta_api.h" // PLUG_LOADTIME +/* + + How to use: + 1. Add new export function 'Meta_PExtGiveFnptrs' to your plugin file. + 'Meta_PExtGiveFnptrs' will be called right after 'Meta_Query' call. + 2. Meta_PExtGiveFnptrs is called with interface version 'META_PEXT_VERSION' + and pointer to extension function table. + 3. Meta_PExtGiveFnptrs should return plugin's interface version. + 4. !NOTE! Metamod will not stop loading plugin even if plugin returns + interface version greater than current. Plugin should disable itself in + this kind of situation. + + Example: + #include "mm_pextensions.h" + + pextension_funcs_t *gpMetaPExtFuncs; + + int Meta_PExtGiveFnptrs(int interfaceVersion, pextension_funcs_t *pMetaPExtFuncs) { + if(interfaceVersion < META_PEXT_VERSION) { + LOG_DEVELOPER(PLID, "Error! Metamod is too old, please update!"); + gpMetaPExtFuncs = NULL; + + return(META_PEXT_VERSION); + } + + gpMetaPExtFuncs = pMetaPExtFuncs; + + return(META_PEXT_VERSION); + } + + Callback functions: + - int PEXT_LOAD_PLUGIN_BY_NAME(PLID, const char *cmdline, PLUG_LOADTIME now, void **plugin_handle); + Parses 'cmdline' as metamod would parse 'meta load ' and loads found + plugin. If 'plugin_handle' is set, metamod writes module handle of loaded + plugin at it. + Returns zero on success. + For error codes see 'META_ERRNO' in 'types_meta.h'. + + - int PEXT_UNLOAD_PLUGIN_BY_NAME(PLID, const char *cmdline, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + Parses 'cmdline' as metamod would parse 'meta unload ' and + unloads found plugin. + Returns zero on success. + For error codes see 'META_ERRNO' in 'types_meta.h'. + + - int PEXT_UNLOAD_PLUGIN_BY_HANDLE(PLID, void *plugin_handle, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + Unloads plugin with 'plugin_handle'. + Returns zero on success. + For error codes see 'META_ERRNO' in 'types_meta.h'. + + !NOTE! Plugin cannot unload itself! +*/ + +// Interface version +// 1: first version. Used in p13 +// 2: Complete remake (p14): +// pfnLoadMetaPluginByName +// pfnUnloadMetaPluginByName +// pfnUnloadMetaPluginByHandle +// v2 is locked now. Don't modify old functions. If you add new functions, increase META_PEXT_VERSION. +#define META_PEXT_VERSION 2 + +// Meta PExtension Function table type. +typedef struct pextension_funcs_s { + int (*pfnLoadMetaPluginByName)(plid_t plid, const char *cmdline, PLUG_LOADTIME now, void **plugin_handle); + int (*pfnUnloadMetaPluginByName)(plid_t plid, const char *cmdline, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + int (*pfnUnloadMetaPluginByHandle)(plid_t plid, void *plugin_handle, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); +} pextension_funcs_t; + +// Convenience macros for MetaPExtension functions. +#define PEXT_LOAD_PLUGIN_BY_NAME (*gpMetaPExtFuncs->pfnLoadMetaPluginByName) +#define PEXT_UNLOAD_PLUGIN_BY_NAME (*gpMetaPExtFuncs->pfnUnloadMetaPluginByName) +#define PEXT_UNLOAD_PLUGIN_BY_HANDLE (*gpMetaPExtFuncs->pfnUnloadMetaPluginByHandle) + +// Give plugin extension function table. +C_DLLEXPORT int Meta_PExtGiveFnptrs(int interfaceVersion, + pextension_funcs_t *pMetaPExtFuncs); +typedef int (*META_GIVE_PEXT_FUNCTIONS_FN) (int interfaceVersion, + pextension_funcs_t *pMetaPExtFuncs); + +#endif /* MM_PEXTENSIONS_H */ diff --git a/src/metamod/mplayer.cpp b/src/metamod/mplayer.cpp new file mode 100644 index 0000000..a88eee9 --- /dev/null +++ b/src/metamod/mplayer.cpp @@ -0,0 +1,190 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mplayer.cpp - methods of individual player (class MPlayer) and +// list of players (class MPlayerList). + +/* + * Copyright (c) 2005-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // strdup() + +#include // always + +#include "mplayer.h" // me +#include "sdk_util.h" // ENTINDEX() +#include "metamod.h" // gpGlobals + + +// Constructor +MPlayer::MPlayer() + : isQueried(mFALSE), + cvarName(NULL) +{ +} + + +// Destructor +MPlayer::~MPlayer() +{ + if(cvarName) { + free(cvarName); + } +} + + +// Copy constructor +MPlayer::MPlayer(const MPlayer& rhs) + : isQueried(rhs.isQueried), + cvarName(NULL) +{ + if(rhs.cvarName) { + cvarName = strdup(rhs.cvarName); + } +} + + +// Assignment operator +MPlayer& DLLINTERNAL MPlayer::operator=(const MPlayer& rhs) +{ + isQueried = rhs.isQueried; + + if(cvarName) { + free(cvarName); + } + + cvarName = NULL; + + if(rhs.cvarName) { + cvarName = strdup(rhs.cvarName); + } + + return *this; +} + + +// Mark a player as querying a client cvar and stores the cvar name +// meta_errno values: +// - ME_ARGUMENT cvar is NULL +void DLLINTERNAL MPlayer::set_cvar_query(const char *cvar) +{ + // Do not allow NULL as queried cvar since we use this as + // return value in is_querying_cvar as indication if a + // client cvar is queried. + if(!cvar) { + meta_errno = ME_ARGUMENT; + return; + } + + isQueried = mTRUE; + if(cvarName) { + free(cvarName); + } + + cvarName = strdup(cvar); +} + + +// Unmark player as querying a client cvar +void DLLINTERNAL MPlayer::clear_cvar_query(const char* /*cvar*/) +{ + isQueried = mFALSE; + if(cvarName) { + free(cvarName); + cvarName = NULL; + } +} + + +// Check if a client cvar is queried for this player +// Returns NULL if not +// or the name of the cvar. +const char * DLLINTERNAL MPlayer::is_querying_cvar(void) +{ + if(isQueried) { + return(cvarName); + } + + return(NULL); +} + + + +// Mark a player as querying a client cvar and stores the cvar name +// meta_errno values: +// - ME_ARGUMENT cvar is NULL +void DLLINTERNAL MPlayerList::set_player_cvar_query(const edict_t *pEntity, const char *cvar) +{ + int indx = ENTINDEX(const_cast(pEntity)); + + if(indx < 1 || indx >= MPlayerList::NUM_SLOTS) + return; //maybe output a message? + + players[indx].set_cvar_query(cvar); +} + + +// Unmark player as querying a client cvar +void DLLINTERNAL MPlayerList::clear_player_cvar_query(const edict_t *pEntity, const char *cvar) +{ + int indx = ENTINDEX(const_cast(pEntity)); + + if(indx < 1 || indx >= MPlayerList::NUM_SLOTS) + return; //maybe output a message? + + players[indx].clear_cvar_query(cvar); +} + + +void DLLINTERNAL MPlayerList::clear_all_cvar_queries(void) +{ + for(int indx=1; indx < MPlayerList::NUM_SLOTS; ++indx) { + players[indx].clear_cvar_query(); + } +} + + + +// Check if a client cvar is queried for this player +// Returns NULL if not +// or the name of the cvar. +// meta_errno values: +// - ME_NOTFOUND invalid entity +const char* DLLINTERNAL MPlayerList::is_querying_cvar(const edict_t *pEntity) +{ + int indx = ENTINDEX(const_cast(pEntity)); + + if(indx < 1 || indx > gpGlobals->maxClients) { + RETURN_ERRNO(NULL, ME_NOTFOUND); + } + + return(players[indx].is_querying_cvar()); +} diff --git a/src/metamod/mplayer.h b/src/metamod/mplayer.h new file mode 100644 index 0000000..5e6352f --- /dev/null +++ b/src/metamod/mplayer.h @@ -0,0 +1,91 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mplayer.h - class to keep info about a player and a class listing all +// players + +/* + * Copyright (c) 2005-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef INCLUDE_METAMOD_PLAYER_H +#define INCLUDE_METAMOD_PLAYER_H + +#include "plinfo.h" // plugin_info_t, etc +#include "mutil.h" // query_callback_t +#include "types_meta.h" // mBOOL +#include "new_baseclass.h" // class_metamod_new + + +// Numbers of players limit set by the engine +#define MAX_PLAYERS 32 + + +// Info on an individual player +class MPlayer : public class_metamod_new +{ +private: + mBOOL isQueried; // is this player currently queried for a cvar value + char *cvarName; // name of the cvar if getting queried + + MPlayer (const MPlayer&) DLLINTERNAL; + MPlayer& operator=(const MPlayer&) DLLINTERNAL; + + +public: + MPlayer() DLLINTERNAL; + ~MPlayer() DLLINTERNAL; + void DLLINTERNAL set_cvar_query(const char *cvar); // mark this player as querying a client cvar + void DLLINTERNAL clear_cvar_query(const char *cvar=NULL); // unmark this player as querying a client cvar + const char *DLLINTERNAL is_querying_cvar(void); // check if a player is querying a cvar. returns + // NULL if not or the name of the cvar +}; + + + +// A list of players. The number of max players is fixed and small enough +// to use an array. +class MPlayerList +{ +private: + enum { NUM_SLOTS = MAX_PLAYERS + 1 }; + + MPlayer players[NUM_SLOTS]; // array of players + + +public: + void DLLINTERNAL set_player_cvar_query(const edict_t *pEntity, const char *cvar); + void DLLINTERNAL clear_player_cvar_query(const edict_t *pEntity, const char *cvar=NULL); + void DLLINTERNAL clear_all_cvar_queries(void); + const char *DLLINTERNAL is_querying_cvar(const edict_t *pEntity); +}; + + +#endif /* INCLUDE_METAMOD_PLAYER_H */ diff --git a/src/metamod/mplugin.cpp b/src/metamod/mplugin.cpp new file mode 100644 index 0000000..b204e14 --- /dev/null +++ b/src/metamod/mplugin.cpp @@ -0,0 +1,1561 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mplugin.cpp - functions for individual plugin (class MPlugin) + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // errno, etc +#include // malloc, etc +#include // stat +#include // stat + +#include // always + +#include "mplugin.h" // me +#include "metamod.h" // GameDLL, etc +#include "mreg.h" // MRegCmdList::show(int), etc +#include "mhook.h" // class MHook +#include "h_export.h" // GIVE_ENGINE_FUNCTIONS_FN, etc +#include "dllapi.h" // FN_GAMEINIT, etc +#include "support_meta.h" // full_gamedir_path, +#include "types_meta.h" // mBOOL +#include "log_meta.h" // logging functions, etc +#include "osdep.h" // win32 snprintf, is_absolute_path, +#include "mm_pextensions.h" + + +// Parse a line from plugins.ini into a plugin. +// meta_errno values: +// - ME_COMMENT ignored commented line +// - ME_FORMAT invalid line format +// - ME_OSNOTSUP plugin is not for this OS +mBOOL DLLINTERNAL MPlugin::ini_parseline(const char *line) { + char *token; + char *ptr_token; + char *cp; + char *tmp_line; + + // + tmp_line=strdup(line); + if(!tmp_line) + RETURN_ERRNO(mFALSE, ME_NOMEM); + + // skip whitespace at start of line + while(*tmp_line==' ' || *tmp_line=='\t') tmp_line++; + + // remove whitespace at end of line + cp = tmp_line + strlen(tmp_line) -1; + while(*cp==' ' || *cp=='\t') *cp--='\0'; + + // skip empty lines + if(tmp_line[0]=='\0') { + META_DEBUG(7, ("ini: Ignoring empty line: %s", tmp_line)); + free(tmp_line); + RETURN_ERRNO(mFALSE, ME_BLANK); + } + + if(tmp_line[0]=='#' || tmp_line[0]==';' || strstr(tmp_line, "//")==tmp_line) { + META_DEBUG(7, ("ini: Ignoring commented line: %s", tmp_line)); + free(tmp_line); + RETURN_ERRNO(mFALSE, ME_COMMENT); + } + + // grab platform ("win32" or "linux") + token=strtok_r(tmp_line, " \t", &ptr_token); + if(!token) { + free(tmp_line); + RETURN_ERRNO(mFALSE, ME_FORMAT); + } + if(strcasecmp(token, PLATFORM) == 0) { + pfspecific=0; + } else if(strcasecmp(token, PLATFORM_SPC) == 0) { + pfspecific=1; + } else { + // plugin is not for this OS + META_DEBUG(7, ("ini: Ignoring entry for %s", token)); + free(tmp_line); + RETURN_ERRNO(mFALSE, ME_OSNOTSUP); + } + + // grab filename + token=strtok_r(NULL, " \t\r\n", &ptr_token); + if(!token) { + free(tmp_line); + RETURN_ERRNO(mFALSE, ME_FORMAT); + } + STRNCPY(filename, token, sizeof(filename)); + normalize_pathname(filename); + // Store name of just the actual _file_, without dir components. + cp=strrchr(filename, '/'); + if(cp) + file=cp+1; + else + file=filename; + + // Grab description. + // Just get the the rest of the line, minus line-termination. + token=strtok_r(NULL, "\n\r", &ptr_token); + if(token) { + token=token+strspn(token, " \t"); // skip whitespace + STRNCPY(desc, token, sizeof(desc)); + } + else { + // If no description is specified, temporarily use plugin file, + // until plugin can be queried, and desc replaced with info->name. + safevoid_snprintf(desc, sizeof(desc), "<%s>", file); + } + + // Make full pathname (from gamedir if relative, remove "..", + // backslashes, etc). + full_gamedir_path(filename, pathname); + + source=PS_INI; + status=PL_VALID; + + free(tmp_line); + return(mTRUE); +} + +// Parse a line from console "load" command into a plugin. +// meta_errno values: +// - ME_FORMAT invalid line format +mBOOL DLLINTERNAL MPlugin::cmd_parseline(const char *line) { + char buf[NAME_MAX + PATH_MAX + MAX_DESC_LEN]; + char *token; + char *ptr_token; + char *cp; + + STRNCPY(buf, line, sizeof(buf)); + + // remove "load" + token=strtok_r(buf, " \t", &ptr_token); + if(!token) + RETURN_ERRNO(mFALSE, ME_FORMAT); + + // grab filename + token=strtok_r(NULL, " \t", &ptr_token); + if(!token) + RETURN_ERRNO(mFALSE, ME_FORMAT); + STRNCPY(filename, token, sizeof(filename)); + normalize_pathname(filename); + // store name of just the actual _file_, without dir components + cp=strrchr(filename, '/'); + if(cp) + file=cp+1; + else + file=filename; + + // Grab description. + // Specify no delimiter chars, as we just want the rest of the line. + token=strtok_r(NULL, "", &ptr_token); + if(token) { + token=token+strspn(token, " \t"); // skip whitespace + STRNCPY(desc, token, sizeof(desc)); + } + else { + // if no description is specified, temporarily use plugin file, + // until plugin can be queried, and desc replaced with info->name. + safevoid_snprintf(desc, sizeof(desc), "<%s>", file); + } + + // Make full pathname (from gamedir if relative, remove "..", + // backslashes, etc). + full_gamedir_path(filename, pathname); + + source=PS_CMD; + status=PL_VALID; + return(mTRUE); +} + +// Parse a filename string from PEXT_LOAD_PLUGIN_BY_* function into a plugin. +// meta_errno values: +mBOOL DLLINTERNAL MPlugin::plugin_parseline(const char *fname, int loader_index) { + char *cp; + + source_plugin_index = loader_index; + + // + STRNCPY(filename, fname, sizeof(filename)); + normalize_pathname(filename); + // store name of just the actual _file_, without dir components + cp=strrchr(filename, '/'); + if(cp) + file=cp+1; + else + file=filename; + + // Grab description. + // Temporarily use plugin file, until plugin can be queried, and desc replaced with info->name. + safevoid_snprintf(desc, sizeof(desc), "<%s>", file); + + // Make full pathname (from gamedir if relative, remove "..", + // backslashes, etc). + full_gamedir_path(filename, pathname); + + source=PS_PLUGIN; + status=PL_VALID; + return(mTRUE); +} + +// Make sure this plugin has the necessary minimal information. +// meta_errno values: +// - ME_ARGUMENT missing necessary fields in plugin +mBOOL DLLINTERNAL MPlugin::check_input(void) { + // doublecheck our input/state + if(status < PL_VALID) { + META_WARNING("dll: Tried to operate on plugin[%d] with a non-valid status (%d)", index, str_status()); + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + if(!file || !file[0]) { + META_WARNING("dll: Tried to operate on plugin[%d] with an empty file", + index); + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + if(!filename[0]) { + META_WARNING("dll: Tried to operate on plugin[%d] with an empty filename", + index); + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + if(!pathname[0]) { + META_WARNING("dll: Tried to operate on plugin[%d] with an empty pathname", + index); + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + if(!desc[0]) { + // if no description is specified, temporarily use plugin file, + // until plugin can be queried, and desc replaced with info->name. + safevoid_snprintf(desc, sizeof(desc), "<%s>", file); + } + return(mTRUE); +} + +// Try to resolve a plugin's filename as a (possibly partial) path to an +// actual filename on disk, to facilitate easier console load-command +// arguments. Uses resolve_dirs, resolve_prefix, and resolve_suffix below. +// Example paths that it tries: +// filename +// Gamedir/filename.dll, Gamedir/filename.so +// Gamedir/filename_i386.so +// Gamedir/dlls/mm_filename_i386.so +// Gamedir/dlls/filename_mm_i386.so +// Gamedir/dlls/filename_MM_i386.so +// meta_errno values: +// - ME_NOTFOUND couldn't find a matching file for the partial name +// - errno's from check_input() +mBOOL DLLINTERNAL MPlugin::resolve(void) { + char *found; + char *cp; + int len; + if(!check_input()) { + // details logged, meta_errno set in check_input() + return(mFALSE); + } + if(is_absolute_path(filename)) + found=resolve_prefix(filename); + else + found=resolve_dirs(filename); + + if(!found) { + META_DEBUG(2, ("Couldn't resolve '%s' to file", filename)); + RETURN_ERRNO(mFALSE, ME_NOTFOUND); + } + META_DEBUG(2, ("Resolved '%s' to file '%s'", filename, found)); + // store pathname: the resolved path (should be absolute) + STRNCPY(pathname, found, sizeof(pathname)); + // store file: the name of the file (without dir) + cp=strrchr(pathname, '/'); + if(cp) + file=cp+1; + else + file=pathname; + // store pathname: the gamedir relative path, or an absolute path + len=strlen(GameDLL.gamedir); + if(strncasecmp(pathname, GameDLL.gamedir, len) == 0) + STRNCPY(filename, pathname+len+1, sizeof(filename)); + else + STRNCPY(filename, pathname, sizeof(filename)); + + return(mTRUE); +} + +// For the given path, tries to find file in several possible +// directories. +// Try: +// GAMEDIR/filename +// GAMEDIR/dlls/filename +// meta_errno values: +// - none +char * DLLINTERNAL MPlugin::resolve_dirs(const char *path) { + struct stat st; + static char buf[PATH_MAX]; + char *found; + + safevoid_snprintf(buf, sizeof(buf), "%s/%s", GameDLL.gamedir, path); + // try this path + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + // try other file prefixes in this path + if((found=resolve_prefix(buf))) + return(found); + + safevoid_snprintf(buf, sizeof(buf), "%s/dlls/%s", GameDLL.gamedir, path); + // try this path + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + // try other file prefixes for this path + if((found=resolve_prefix(buf))) + return(found); + + return(NULL); +} + +// For the given path, tries several possible filename prefixes. +// Try: +// dir/mm_file +// dir/file +// meta_errno values: +// - none +char * DLLINTERNAL MPlugin::resolve_prefix(const char *path) { + struct stat st; + char *cp, *fname; + char dname[PATH_MAX]; + static char buf[PATH_MAX]; + char *found; + + // try "mm_" prefix FIRST. + // split into dirname and filename + STRNCPY(dname, path, sizeof(dname)); + cp=strrchr(dname, '/'); + if(cp) { + *cp='\0'; + fname=cp+1; + safevoid_snprintf(buf, sizeof(buf), "%s/mm_%s", dname, fname); + } + else { + // no directory in given path + safevoid_snprintf(buf, sizeof(buf), "mm_%s", path); + } + // try this path + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + // try other suffixes for this path + if((found=resolve_suffix(buf))) + return(found); + + // try other suffixes for the original path + if((found=resolve_suffix(path))) + return(found); + + return(NULL); +} + +// For the given path, tries several different filename suffixes. +// Try: +// path +// path_mm +// path_MM +// path.so (linux), path.dll (win32) +// path_i386.so, path_i486.so, etc (if linux) +// meta_errno values: +// - none +char * DLLINTERNAL MPlugin::resolve_suffix(const char *path) { + struct stat st; + static char buf[PATH_MAX]; + static char tmpbuf[PATH_MAX]; + char *found; + + // Hmm, recursion. + if(!strstr(path, "_mm")) { + safevoid_snprintf(buf, sizeof(buf), "%s_mm", path); + memcpy(tmpbuf,buf,sizeof(tmpbuf)); + found=resolve_suffix(tmpbuf); + if(found) + return(found); + } + + if(!strstr(path, "_MM")) { + safevoid_snprintf(buf, sizeof(buf), "%s_MM", path); + memcpy(tmpbuf,buf,sizeof(tmpbuf)); + found=resolve_suffix(tmpbuf); + if(found) + return(found); + } + +#ifdef _WIN32 + safevoid_snprintf(buf, sizeof(buf), "%s.dll", path); +#elif defined(linux) + safevoid_snprintf(buf, sizeof(buf), "%s.so", path); +#else +#error "OS unrecognized" +#endif /* _WIN32 */ + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + +#ifdef linux +#ifdef __x86_64__ + safevoid_snprintf(buf, sizeof(buf), "%s_amd64.so", path); + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + safevoid_snprintf(buf, sizeof(buf), "%s_x86_64.so", path); + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + safevoid_snprintf(buf, sizeof(buf), "%s_x86-64.so", path); + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); +#else + safevoid_snprintf(buf, sizeof(buf), "%s_i386.so", path); + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + safevoid_snprintf(buf, sizeof(buf), "%s_i486.so", path); + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + safevoid_snprintf(buf, sizeof(buf), "%s_i586.so", path); + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); + safevoid_snprintf(buf, sizeof(buf), "%s_i686.so", path); + if(stat(buf, &st) == 0 && S_ISREG(st.st_mode)) + return(buf); +#endif /* !__x86_64__ */ +#endif /* linux */ + + return(NULL); +} + + +// Check if a passed string starts with a known platform postfix. +// It does not check beyond the period in order to work for both +// Linux and Win32. +mBOOL DLLINTERNAL MPlugin::is_platform_postfix(const char *pf) { + typedef struct { const char * postfix; size_t len; } postfix_t; + static const postfix_t postfixes[] = { + {"_i386.", 6}, + {"_i686.", 6}, + {"_amd64.", 7}, + {"_i486.", 6}, + {"_i586.", 6}, + {"_x86_64.", 8}, + {"_x86-64.", 8}, + {0,0}, + }; + + if(!pf) + return(mFALSE); + + for(const postfix_t * plist = postfixes; plist->postfix; plist++) { + if(!mm_strncmp(pf, plist->postfix, plist->len)) + return(mTRUE); + } + + return(mFALSE); +} + + +// Check if a given plugin is the same but possibly for a +// different platform. A match is considered to be found if +// 1. the filename without the path is the same, or +// 2a. for an attached plugin the logtag is the same, or +// 2b. the description is the same, or +// 3. a significant part of the filename is the same. +// A significant part of a plugin name is currently defined to: +// the part up to a known platform postfix as determined by +// the is_platform_postfix() function (see above), or +// the part up to the last dot, if one exists. +// meta_errno values: +// - none +mBOOL DLLINTERNAL MPlugin::platform_match(MPlugin* other) { + char *end, *other_end; + int prefixlen; + + if(status < PL_VALID || other->status < PL_VALID) + return(mFALSE); + + if(mm_strcmp(file, other->file) == 0) + return(mTRUE); + + if(status >= PL_OPENED && other->status >= PL_OPENED && + mm_strcmp(info->logtag, other->info->logtag) == 0) + return(mTRUE); + + if(*desc != '\0' && strcasecmp(desc,other->desc) == 0) + return(mTRUE); + + end=strrchr(file, '_'); + if(end == NULL || !is_platform_postfix(end)) + end=strrchr(file, '.'); + + other_end=strrchr(other->file, '_'); + if(other_end == NULL || !is_platform_postfix(other_end)) + other_end=strrchr(other->file, '.'); + + if(end == NULL || other_end == NULL) + return(mFALSE); + + prefixlen=end-file; + if((other_end-other->file) != prefixlen) + return(mFALSE); + + if(mm_strncmp(file,other->file, prefixlen) == 0) + return(mTRUE); + + return(mFALSE); +} + +// Load a plugin; query, check allowed time, attach. +// meta_errno values: +// - ME_ARGUMENT missing necessary fields in plugin +// - ME_ALREADY this plugin already loaded +// - ME_BADREQ plugin not marked for load +// - ME_DELAYED load request is delayed (till changelevel?) +// - ME_NOTALLOWED plugin not loadable after startup +// - errno's from query() +// - errno's from attach() +// - errno's from check_input() +mBOOL DLLINTERNAL MPlugin::load(PLUG_LOADTIME now) { + if(!check_input()) { + // details logged, meta_errno set in check_input() + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + if(status >= PL_RUNNING) { + META_WARNING("dll: Not loading plugin '%s'; already loaded (status=%s)", + desc, str_status()); + RETURN_ERRNO(mFALSE, ME_ALREADY); + } + if(action != PA_LOAD && action != PA_ATTACH && action != PA_RELOAD) { + META_WARNING("dll: Not loading plugin '%s'; not marked for load (action=%s)", desc, str_action()); + RETURN_ERRNO(mFALSE, ME_BADREQ); + } + + if(status <= PL_OPENED) { + // query plugin; open file and get info about it + if(!query()) { + META_WARNING("dll: Skipping plugin '%s'; couldn't query", desc); + if(meta_errno != ME_DLOPEN) { + if(DLCLOSE(handle) != 0) { + META_WARNING("dll: Couldn't close plugin file '%s': %s", + file, DLERROR()); + } + else + handle=NULL; + } + status=PL_BADFILE; + info=NULL; // prevent crash + // meta_errno should be already set in query() + return(mFALSE); + } + status=PL_OPENED; + } + + // are we allowed to attach this plugin at this time? + if(info->loadable < now) { + if(info->loadable > PT_STARTUP) { + // will try to attach again at next opportunity + META_DEBUG(2, ("dll: Delaying load plugin '%s'; can't attach now: allowed=%s; now=%s", + desc, str_loadable(), str_loadtime(now, SL_SIMPLE))); + RETURN_ERRNO(mFALSE, ME_DELAYED); + } + else { + META_DEBUG(2, ("dll: Failed load plugin '%s'; can't attach now: allowed=%s; now=%s", + desc, str_loadable(), str_loadtime(now, SL_SIMPLE))); + // don't try to attach again later + action=PA_NONE; + RETURN_ERRNO(mFALSE, ME_NOTALLOWED); + } + } + + // attach plugin; get function tables + if(attach(now) != mTRUE) { + META_WARNING("dll: Failed to attach plugin '%s'", desc); + // Note we don't dlclose() here, since we're returning PL_FAILED, + // which implies that it's been dlopened and queried successfully. + // Doing so causes crashes, because things like "meta list" try to + // look at *info, which is in the DLL memory space and unaccessible + // (segfault) after dlclosed. + status=PL_FAILED; + // meta_errno should be already set in attach() + return(mFALSE); + } + + status=PL_RUNNING; + action=PA_NONE; + + // If not loading at server startup, then need to call plugin's + // GameInit, since we've passed that. + if(now != PT_STARTUP) { + FN_GAMEINIT pfn_gameinit=NULL; + if(tables.dllapi && (pfn_gameinit=tables.dllapi->pfnGameInit)) + pfn_gameinit(); + } + // If loading during map, then I'd like to call plugin's + // ServerActivate, since we've passed that. However, I'm not sure what + // arguments to give it... So, we'll just have to say for the + // moment that plugins that are loadable during a map can't need to + // hook ServerActivate. + + META_LOG("dll: Loaded plugin '%s': %s v%s %s, %s", desc, info->name, + info->version, info->date, info->author); + return(mTRUE); +} + +// Query a plugin: +// - dlopen() the file, store the handle +// - dlsym() and call: +// Meta_Init (if present) - tell dll it'll be used as a metamod plugin +// GiveFnptrsToDll - give engine function ptrs +// Meta_Query - say "hi" and get info about plugin +// meta_errno values: +// - ME_DLOPEN dlopen/loadlibrary failed; see dlerror() for details +// - ME_DLMISSING couldn't find a query() or giveFuncs() in plugin +// - ME_DLERROR plugin query() returned error +// - ME_NULLDATA info struct from query() was null +mBOOL DLLINTERNAL MPlugin::query(void) { + int plugin_pext_version; + META_GIVE_PEXT_FUNCTIONS_FN pfn_give_pext_funcs; + META_INIT_FN pfn_init; + GIVE_ENGINE_FUNCTIONS_FN pfn_give_engfuncs; + META_QUERY_FN pfn_query; + + // open the plugin DLL + if(!(handle=DLOPEN(pathname))) { + META_WARNING("dll: Failed query plugin '%s'; Couldn't open file '%s': %s", + desc, pathname, DLERROR()); + RETURN_ERRNO(mFALSE, ME_DLOPEN); + } + + // First, we check to see if they have a Meta_Query. We would normally + // dlsym this just prior to calling it, after having called + // GiveFnptrsToDll, but things like standalone-DLL bots do all their + // startup in GiveFnptrsToDll, and trying to load them as metamod + // plugins creates all sorts of crashes. So, we do a trivial check + // first to see if the DLL looks like a metamod plugin before + // proceeding with the normal order. Note that we still have to call + // GiveFnptrsToDll before Meta_Query, because the latter typically uses + // engine functions like AlertMessage, which have to be passed along via + // GiveFnptrsToDll. + pfn_query = (META_QUERY_FN) DLSYM(handle, "Meta_Query"); + if(!pfn_query) { + META_WARNING("dll: Failed query plugin '%s'; Couldn't find Meta_Query(): %s", desc, DLERROR()); + // caller will dlclose() + RETURN_ERRNO(mFALSE, ME_DLMISSING); + } + + // Call Meta_Init, if present. This is an optional plugin routine to + // allow plugin to do any special initializing or other processing + // prior to the standard query/attach procedure. + // + // In particular, this should allow for DLL's that can operate as both + // a standalone dll AND a metamod plugin. This routine has to be + // called _before_ GiveFnptrsToDll, since the dll will usually do all + // its startup processing in GiveFn, and has to know at that point + // whether it needs to do just mm plugin startup, or all the extra + // startup needed for a standalone DLL. + // + // This passes nothing and returns nothing, and the routine in the + // plugin can NOT use any Engine functions, as they haven't been + // provided yet (done next, in GiveFnptrsToDll). + pfn_init = (META_INIT_FN) DLSYM(handle, "Meta_Init"); + if(pfn_init) { + pfn_init(); + META_DEBUG(6, ("dll: Plugin '%s': Called Meta_Init()", desc)); + } + else { + META_DEBUG(5, ("dll: no Meta_Init present in plugin '%s'", desc)); + // don't return; not an error + } + + // pass on engine function table and globals to plugin + if(!(pfn_give_engfuncs = (GIVE_ENGINE_FUNCTIONS_FN) DLSYM(handle, "GiveFnptrsToDll"))) { + // META_WARNING("dll: Couldn't find GiveFnptrsToDll() in plugin '%s': %s", desc, DLERROR()); + META_WARNING("dll: Failed query plugin '%s'; Couldn't find GiveFnptrsToDll(): %s", desc, DLERROR()); + // caller will dlclose() + RETURN_ERRNO(mFALSE, ME_DLMISSING); + } + pfn_give_engfuncs(Engine.pl_funcs, Engine.globals); + META_DEBUG(6, ("dll: Plugin '%s': Called GiveFnptrsToDll()", desc)); + + // Call plugin's Meta_Query(), to pass our meta interface version, and get + // plugin's info structure. + meta_errno=ME_NOERROR; + info=NULL; + // Make a copy of the meta_util function table for each plugin, for the + // same reason. + memcpy(&mutil_funcs, &MetaUtilFunctions, sizeof(mutil_funcs)); + + if(pfn_query(META_INTERFACE_VERSION, &info, &mutil_funcs) != TRUE) { + META_WARNING("dll: Failed query plugin '%s'; Meta_Query returned error", + desc); + meta_errno=ME_DLERROR; + } + else { + META_DEBUG(6, ("dll: Plugin '%s': Called Meta_Query() successfully", + desc)); + } + + // Check for interface differences... Generally, a difference in major + // version will be incompatible, and a plugin that expects a later + // minor version will be incompatible (it's expecting things that this + // Metamod won't be supplying). Plugins that use an older minor + // version will still work, as backward-compability within major + // version is expected (forward-compatibility is not). + // + // Note, this check is done regardless of whether meta_query returns an + // error. + if(info && !FStrEq(info->ifvers, META_INTERFACE_VERSION)) { + int mmajor=0, mminor=0, pmajor=0, pminor=0; + META_DEBUG(3, ("dll: Note: Plugin '%s' interface version didn't match; expected %s, found %s", desc, META_INTERFACE_VERSION, info->ifvers)); + sscanf(META_INTERFACE_VERSION, "%d:%d", &mmajor, &mminor); + sscanf(info->ifvers, "%d:%d", &pmajor, &pminor); + // If plugin has later interface version, it's incompatible + // (update metamod). + if(pmajor > mmajor || (pmajor==mmajor && pminor > mminor)) { + META_WARNING("dll: Plugin '%s' requires a newer version of Metamod (Metamod needs at least interface %s not the current %s)", desc, info->ifvers, META_INTERFACE_VERSION); + meta_errno=ME_IFVERSION; + } + // If plugin has older major interface version, it's incompatible + // (update plugin). + else if(pmajor < mmajor) { + META_WARNING("dll: Plugin '%s' is out of date and incompatible with this version of Metamod; please find a newer version of the plugin (plugin needs at least interface %s not the current %s)", desc, META_INTERFACE_VERSION, info->ifvers); + meta_errno=ME_IFVERSION; + } + // Plugin has identical major, with older minor. This is supposed to be + // backwards compatible, so we warn, but we still accept it. + else if(pmajor==mmajor && pminor < mminor) + META_LOG("dll: Note: plugin '%s' is using an older interface version (%s), not the latest interface version (%s); there might be an updated version of the plugin", desc, info->ifvers, META_INTERFACE_VERSION); + else + META_LOG("dll: Plugin '%s': unexpected version comparision; metavers=%s, mmajor=%d, mminor=%d; plugvers=%s, pmajor=%d, pminor=%d", + desc, META_INTERFACE_VERSION, mmajor, mminor, info->ifvers, pmajor, pminor); + } + + if(meta_errno == ME_IFVERSION) { + META_WARNING("dll: Rejected plugin '%s' due to interface version incompatibility (mm=%s, pl=%s)", desc, META_INTERFACE_VERSION, info->ifvers); + // meta_errno is set already above + // caller will dlclose() + return(mFALSE); + } + else if(meta_errno != ME_NOERROR) { + // some other error, already logged + return(mFALSE); + } + + if(!info) { + META_WARNING("dll: Failed query plugin '%s'; Empty info structure", desc); + // caller will dlclose() + RETURN_ERRNO(mFALSE, ME_NULLRESULT); + } + // Replace temporary desc with plugin's internal name. + if(desc[0] == '<') + STRNCPY(desc, info->name, sizeof(desc)); + + // + // Give plugin the p-series extension function table. + // Check for version differences! + // + if(NULL!=(pfn_give_pext_funcs=(META_GIVE_PEXT_FUNCTIONS_FN) DLSYM(handle, "Meta_PExtGiveFnptrs"))) { + plugin_pext_version = (*pfn_give_pext_funcs)(META_PEXT_VERSION, (pextension_funcs_t*)(&(mutil_funcs.pfnLoadPlugin))); + + //if plugin is newer, we got incompatibility problem! + if(plugin_pext_version > META_PEXT_VERSION) { + META_WARNING("dll: Plugin '%s' requires a newer version of Metamod-P (extension interface needs to be at least %d not the current %d)", + desc, + plugin_pext_version, + META_PEXT_VERSION); + } + } + + META_DEBUG(6, ("dll: Plugin '%s': Query successful", desc)); + + return(mTRUE); +} + +// Attach a plugin: +// - dlsym() and call: +// Meta_Attach - get table of api tables, give meta_globals +// - if provided by plugin, call various "Get" function pointers, +// and store resulting function tables: +// GetEntityAPI (std) +// GetEntityAPI2 (std sdk2) +// GetNewDLLFunctions (std sdk2) +// +// GetEntityAPI_Post (meta) +// GetEntityAPI2_Post (meta) +// GetNewDLLFunctions_Post (meta) +// +// GetEngineFunctions (meta) +// GetEngineFunctions_Post (meta) +// meta_errno values: +// - ME_DLMISSING couldn't find meta_attach() in plugin +// - ME_DLERROR plugin attach() returned error +// - ME_NOMEM failed malloc +mBOOL DLLINTERNAL MPlugin::attach(PLUG_LOADTIME now) { + int ret; + int iface_vers; + + META_ATTACH_FN pfn_attach; + META_FUNCTIONS meta_table; + + // Make copy of gameDLL's function tables for each plugin, so we don't + // risk the plugins screwing with the tables everyone uses. + if(!gamedll_funcs.dllapi_table) { + gamedll_funcs.dllapi_table = (DLL_FUNCTIONS *) calloc(1, sizeof(DLL_FUNCTIONS)); + if(!gamedll_funcs.dllapi_table) { + META_WARNING("dll: Failed attach plugin '%s': Failed malloc() for dllapi_table"); + RETURN_ERRNO(mFALSE, ME_NOMEM); + } + if(GameDLL.funcs.dllapi_table) + memcpy(gamedll_funcs.dllapi_table, GameDLL.funcs.dllapi_table, sizeof(DLL_FUNCTIONS)); + else + memset(gamedll_funcs.dllapi_table, 0, sizeof(DLL_FUNCTIONS)); + } + if(!gamedll_funcs.newapi_table) { + gamedll_funcs.newapi_table = (NEW_DLL_FUNCTIONS *) calloc(1, sizeof(NEW_DLL_FUNCTIONS)); + if(!gamedll_funcs.newapi_table) { + META_WARNING("dll: Failed attach plugin '%s': Failed malloc() for newapi_table"); + RETURN_ERRNO(mFALSE, ME_NOMEM); + } + if(GameDLL.funcs.newapi_table) + memcpy(gamedll_funcs.newapi_table, GameDLL.funcs.newapi_table, sizeof(NEW_DLL_FUNCTIONS)); + else + memset(gamedll_funcs.newapi_table, 0, sizeof(NEW_DLL_FUNCTIONS)); + } + if(!(pfn_attach = (META_ATTACH_FN) DLSYM(handle, "Meta_Attach"))) { + META_WARNING("dll: Failed attach plugin '%s': Couldn't find Meta_Attach(): %s", desc, DLERROR()); + // caller will dlclose() + RETURN_ERRNO(mFALSE, ME_DLMISSING); + } + + memset(&meta_table, 0, sizeof(meta_table)); + // get table of function tables, + // give public meta globals + ret=pfn_attach(now, &meta_table, &PublicMetaGlobals, &gamedll_funcs); + if(ret != TRUE) { + META_WARNING("dll: Failed attach plugin '%s': Error from Meta_Attach(): %d", desc, ret); + // caller will dlclose() + RETURN_ERRNO(mFALSE, ME_DLERROR); + } + META_DEBUG(6, ("dll: Plugin '%s': Called Meta_Attach() successfully", desc)); + + // Rather than duplicate code, we use another ugly macro. Again, + // a function isn't an option since we have varying types. +#define GET_FUNC_TABLE_FROM_PLUGIN(pfnGetFuncs, STR_GetFuncs, struct_field, API_TYPE, TABLE_TYPE, TABLE_SIZE, vers_pass, vers_int, vers_want) \ + if(meta_table.pfnGetFuncs) { \ + if(!struct_field) { \ + struct_field = (TABLE_TYPE*)calloc(1, TABLE_SIZE); \ + } else { \ + memset(struct_field, 0, TABLE_SIZE); \ + } \ + if(meta_table.pfnGetFuncs(struct_field, vers_pass)) { \ + META_DEBUG(3, ("dll: Plugin '%s': Found %s", desc, STR_GetFuncs)); \ + } \ + else { \ + META_WARNING("dll: Failure calling %s in plugin '%s'", STR_GetFuncs, desc); \ + if(vers_int != vers_want) \ + META_WARNING("dll: Interface version didn't match; expected %d, found %d", vers_want, vers_int); \ + } \ + } \ + else { \ + META_DEBUG(5, ("dll: Plugin '%s': No %s", desc, STR_GetFuncs)); \ + if(struct_field) \ + free(struct_field); \ + struct_field=NULL; \ + } + + // Look for API-NEW interface in plugin. We do this before API2/API, because + // that's what the engine appears to do.. + iface_vers=NEW_DLL_FUNCTIONS_VERSION; + GET_FUNC_TABLE_FROM_PLUGIN(pfnGetNewDLLFunctions, + "GetNewDLLFunctions", tables.newapi, + NEW_DLL_FUNCTIONS_FN, NEW_DLL_FUNCTIONS, sizeof(NEW_DLL_FUNCTIONS), + &iface_vers, iface_vers, NEW_DLL_FUNCTIONS_VERSION); + iface_vers=NEW_DLL_FUNCTIONS_VERSION; + GET_FUNC_TABLE_FROM_PLUGIN(pfnGetNewDLLFunctions_Post, + "GetNewDLLFunctions_Post", post_tables.newapi, + NEW_DLL_FUNCTIONS_FN, NEW_DLL_FUNCTIONS, sizeof(NEW_DLL_FUNCTIONS), + &iface_vers, iface_vers, NEW_DLL_FUNCTIONS_VERSION); + + // Look for API2 interface in plugin; preferred over API-1. + iface_vers=INTERFACE_VERSION; + GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEntityAPI2, + "GetEntityAPI2", tables.dllapi, + APIFUNCTION2, DLL_FUNCTIONS, sizeof(DLL_FUNCTIONS), + &iface_vers, iface_vers, INTERFACE_VERSION); + iface_vers=INTERFACE_VERSION; + GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEntityAPI2_Post, + "GetEntityAPI2_Post", post_tables.dllapi, + APIFUNCTION2, DLL_FUNCTIONS, sizeof(DLL_FUNCTIONS), + &iface_vers, iface_vers, INTERFACE_VERSION); + + // Look for old-style API in plugin, if API2 interface wasn't found. + if(!tables.dllapi && !post_tables.dllapi) { + GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEntityAPI, + "GetEntityAPI", tables.dllapi, + APIFUNCTION, DLL_FUNCTIONS, sizeof(DLL_FUNCTIONS), + INTERFACE_VERSION, INTERFACE_VERSION, INTERFACE_VERSION); + GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEntityAPI_Post, + "GetEntityAPI_Post", post_tables.dllapi, + APIFUNCTION, DLL_FUNCTIONS, sizeof(DLL_FUNCTIONS), + INTERFACE_VERSION, INTERFACE_VERSION, INTERFACE_VERSION); + } + + // Look for Engine interface. + iface_vers=ENGINE_INTERFACE_VERSION; + GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEngineFunctions, + "GetEngineFunctions", tables.engine, + GET_ENGINE_FUNCTIONS_FN, enginefuncs_t, (sizeof(enginefuncs_t) - sizeof(((enginefuncs_t*)0)->extra_functions)), + &iface_vers, iface_vers, ENGINE_INTERFACE_VERSION); + iface_vers=ENGINE_INTERFACE_VERSION; + GET_FUNC_TABLE_FROM_PLUGIN(pfnGetEngineFunctions_Post, + "GetEngineFunctions_Post", post_tables.engine, + GET_ENGINE_FUNCTIONS_FN, enginefuncs_t, (sizeof(enginefuncs_t) - sizeof(((enginefuncs_t*)0)->extra_functions)), + &iface_vers, iface_vers, ENGINE_INTERFACE_VERSION); + + if(!tables.dllapi && !post_tables.dllapi + && !tables.newapi && !post_tables.newapi + && !tables.engine && !post_tables.engine) + { + META_LOG("dll: Plugin '%s' isn't catching _any_ functions ??", desc); + } + + time_loaded=time(NULL); + return(mTRUE); +} + +// Unload a plugin from plugin request +// meta_errno values: +// - errno's from unload() +mBOOL DLLINTERNAL MPlugin::plugin_unload(plid_t plid, PLUG_LOADTIME now, PL_UNLOAD_REASON reason) { + PLUG_ACTION old_action; + MPlugin * pl_unloader; + + // try find unloader + if(!(pl_unloader=Plugins->find(plid))) { + META_WARNING("dll: Not unloading plugin '%s'; plugin that requested unload is not found.", desc); + RETURN_ERRNO(mFALSE, ME_BADREQ); + } + // self check. Do not allow plugin to unload itself! + else if(pl_unloader->index == index) { + META_WARNING("dll: Not unloading plugin '%s'; Plugin tried to unload itself.", desc); + RETURN_ERRNO(mFALSE, ME_UNLOAD_SELF); + } + // safe check. Do not allow active unloader to be unloaded! + else if(is_unloader) { + META_WARNING("dll: Not unloading plugin '%s'; Plugin is unloading plugin that tried to unload it.", desc); + RETURN_ERRNO(mFALSE, ME_UNLOAD_UNLOADER); + } + else { + unloader_index=pl_unloader->index; + } + + // block unloader from being unloaded by other plugin + pl_unloader->is_unloader = mTRUE; + + // try unload + old_action=action; + action=PA_UNLOAD; + if(unload(now, reason, (reason==PNL_CMD_FORCED) ? PNL_PLG_FORCED : PNL_PLUGIN)) { + META_DEBUG(1,("Unloaded plugin '%s'", desc)); + pl_unloader->is_unloader = mFALSE; + return(mTRUE); + } + + pl_unloader->is_unloader = mFALSE; + + // Cannot unload plugin now. Don't set delayed mode. + if(meta_errno==ME_DELAYED) { + action=old_action; + meta_errno = ME_NOTALLOWED; + META_DEBUG(2, ("dll: Failed unload plugin '%s'; can't detach now: allowed=%s; now=%s", desc, str_unloadable(), str_loadtime(PT_ANYTIME, SL_SIMPLE))); + } + + return(mFALSE); +} + +// Unload a plugin. Check time, detach. +// meta_errno values: +// - ME_ARGUMENT missing necessary fields in plugin +// - ME_ALREADY this plugin already unloaded +// - ME_BADREQ plugin not marked for unload +// - ME_DELAYED unload request is delayed (till changelevel?) +// - ME_NOTALLOWED plugin not unloadable after startup +// - errno's from check_input() +mBOOL DLLINTERNAL MPlugin::unload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason, PL_UNLOAD_REASON real_reason) { + if(!check_input()) { + // details logged, meta_errno set in check_input() + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + if(status < PL_RUNNING) { + if(reason != PNL_CMD_FORCED && reason != PNL_RELOAD) { + META_WARNING("dll: Not unloading plugin '%s'; already unloaded (status=%s)", desc, str_status()); + RETURN_ERRNO(mFALSE, ME_ALREADY); + } + } + if(action != PA_UNLOAD && action != PA_RELOAD) { + META_WARNING("dll: Not unloading plugin '%s'; not marked for unload (action=%s)", desc, str_action()); + RETURN_ERRNO(mFALSE, ME_BADREQ); + } + + // Are we allowed to detach this plugin at this time? + // If forcing unload, we disregard when plugin wants to be unloaded. + if(info && info->unloadable < now) { + if(reason == PNL_CMD_FORCED) { + META_DEBUG(2, ("dll: Forced unload plugin '%s' overriding allowed times: allowed=%s; now=%s", desc, str_unloadable(), str_loadtime(now, SL_SIMPLE))); + } + else { + if(info->unloadable > PT_STARTUP) { + META_DEBUG(2, ("dll: Delaying unload plugin '%s'; can't detach now: allowed=%s; now=%s", desc, str_unloadable(), str_loadtime(now, SL_SIMPLE))); + // caller should give message to user + // try to unload again at next opportunity + RETURN_ERRNO(mFALSE, ME_DELAYED); + } + else { + META_DEBUG(2, ("dll: Failed unload plugin '%s'; can't detach now: allowed=%s; now=%s", desc, str_unloadable(), str_loadtime(now, SL_SIMPLE))); + // don't try to unload again later + action=PA_NONE; + RETURN_ERRNO(mFALSE, ME_NOTALLOWED); + } + } + } + + // If unloading during map, then I'd like to call plugin's + // ServerDeactivate. However, I don't want to do this until I start + // calling ServerActivate when loading during map, since the SDK + // indicates these two routines should match call for call. + + // detach plugin + if(!detach(now, reason)) { + if(reason == PNL_RELOAD) { + META_DEBUG(2, ("dll: Reload plugin '%s' overriding failed detach", desc)); + } + else if(reason == PNL_CMD_FORCED) { + META_DEBUG(2, ("dll: Forced unload plugin '%s' overriding failed detach", desc)); + } + else { + META_WARNING("dll: Failed to detach plugin '%s'; ", desc); + // meta_errno should be already set in detach() + return(mFALSE); + } + } + + // successful detach, or forced unload + + // clear source_plugin_index for all plugins that this plugin has loaded + Plugins->clear_source_plugin_index(index); + + // Unmark registered commands for this plugin (by index number). + RegCmds->disable(index); + // Unmark registered cvars for this plugin (by index number). + RegCvars->disable(index); + + // Close the file. Note: after this, attempts to reference any memory + // locations in the file will produce a segfault. + if(DLCLOSE(handle) != 0) { + // If DLL cannot be closed, OS is badly broken or we are giving invalid handle. + // So we don't return here but instead remove plugin from our listings. + META_WARNING("dll: Couldn't dlclose plugin file '%s': %s", file, DLERROR()); + } + handle=NULL; + + if(action==PA_UNLOAD) { + status=PL_EMPTY; + clear(); + } + else if(action==PA_RELOAD) { + status=PL_VALID; + action=PA_LOAD; + clear(); + } + META_LOG("dll: Unloaded plugin '%s' for reason '%s'", desc, str_reason(reason, real_reason)); + return(mTRUE); +} + +// Inform plugin we're going to unload it. +// meta_errno values: +// - +// - ME_DLMISSING couldn't find meta_detach() in plugin +// - ME_DLERROR plugin detach() returned error +mBOOL DLLINTERNAL MPlugin::detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason) { + int ret; + META_DETACH_FN pfn_detach; + + // If we have no handle, i.e. no dll loaded, we return true because the + // dll is obviously detached. We shouldn't call DLSYM() with a NULL + // handle since this will DLSYM() ourself. + if(!handle) + return(mTRUE); + + if(!(pfn_detach = (META_DETACH_FN) DLSYM(handle, "Meta_Detach"))) { + META_WARNING("dll: Error detach plugin '%s': Couldn't find Meta_Detach(): %s", desc, DLERROR()); + // caller will dlclose() + RETURN_ERRNO(mFALSE, ME_DLMISSING); + } + + ret=pfn_detach(now, reason); + if(ret != TRUE) { + META_WARNING("dll: Failed detach plugin '%s': Error from Meta_Detach(): %d", desc, ret); + RETURN_ERRNO(mFALSE, ME_DLERROR); + } + META_DEBUG(6, ("dll: Plugin '%s': Called Meta_Detach() successfully", desc)); + return(mTRUE); +} + +// Reload a plugin; unload and load again. +// meta_errno values: +// - ME_NOTALLOWED plugin not loadable after startup +// - errno's from check_input() +// - errno's from unload() +// - errno's from load() +mBOOL DLLINTERNAL MPlugin::reload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason) { + if(!check_input()) { + // details logged, meta_errno set in check_input() + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + + // Are we allowed to load this plugin at this time? + // If we cannot load the plugin after unloading it, we keep it. + if(info && info->loadable < now) { + if(info->loadable > PT_STARTUP) { + META_DEBUG(2, ("dll: Delaying reload plugin '%s'; would not be able to reattach now: allowed=%s; now=%s", desc, str_loadable(), str_loadtime(now, SL_SIMPLE))); + // caller should give message to user + // try to reload again at next opportunity + RETURN_ERRNO(mFALSE, ME_DELAYED); + } + else { + META_DEBUG(2, ("dll: Failed reload plugin '%s'; would not be able to reattach now: allowed=%s; now=%s", desc, str_loadable(), str_loadtime(now, SL_SIMPLE))); + // don't try to reload again later + action=PA_NONE; + RETURN_ERRNO(mFALSE, ME_NOTALLOWED); + } + } + + //this is to fix unloading + if(status < PL_RUNNING) { + META_WARNING("dll: Plugin '%s' isn't running; Forcing unload plugin for reloading", desc); + reason = PNL_RELOAD; + } + if(!unload(now, reason, reason)) { + META_WARNING("dll: Failed to unload plugin '%s' for reloading", desc); + // meta_errno should be set already in unload() + return(mFALSE); + } + if(!load(now)) { + META_WARNING("dll: Failed to reload plugin '%s' after unloading", desc); + // meta_errno should be set already in load() + return(mFALSE); + } + return(mTRUE); +} + +// Pause a plugin; temporarily disabled for API routines. +// meta_errno values: +// - ME_ALREADY this plugin already paused +// - ME_BADREQ can't pause; not running +// - ME_NOTALLOWED plugin doesn't want to be paused +mBOOL MPlugin::pause(void) { + if(status == PL_PAUSED) { + META_WARNING("Not pausing plugin '%s'; already paused", desc); + RETURN_ERRNO(mFALSE, ME_ALREADY); + } + if(status != PL_RUNNING) { + META_WARNING("Cannot pause plugin '%s'; not currently running (status=%s)", desc, str_status()); + RETURN_ERRNO(mFALSE, ME_BADREQ); + } + + // are we allowed to pause this plugin? + if(info->unloadable < PT_ANYPAUSE) { + META_WARNING("Cannot pause plugin '%s'; not allowed by plugin (allowed=%s)", desc, str_unloadable()); + action=PA_NONE; + RETURN_ERRNO(mFALSE, ME_NOTALLOWED); + } + + status=PL_PAUSED; + META_LOG("Paused plugin '%s'", desc); + return(mTRUE); +} + +// Unpause a plugin. +// meta_errno values: +// - ME_BADREQ can't unpause; not paused +mBOOL DLLINTERNAL MPlugin::unpause(void) { + if(status != PL_PAUSED) { + META_WARNING("Cannot unpause plugin '%s'; not currently paused (status=%s)", desc, str_status()); + RETURN_ERRNO(mFALSE, ME_BADREQ); + } + status=PL_RUNNING; + META_LOG("Unpaused plugin '%s'", desc); + return(mTRUE); +} + +// Retry pending action, presumably from a previous failure. +// meta_errno values: +// - ME_BADREQ no pending action +// - errno's from load() +// - errno's from unload() +// - errno's from reload() +mBOOL DLLINTERNAL MPlugin::retry(PLUG_LOADTIME now, PL_UNLOAD_REASON reason) { + if(action==PA_LOAD) + return(load(now)); + else if(action==PA_ATTACH) + return(load(now)); + else if(action==PA_UNLOAD) + return(unload(now, reason, reason)); + else if(action==PA_RELOAD) + return(reload(now, reason)); + else { + META_WARNING("No pending action to retry for plugin '%s'; (status=%s, action=%s)", desc, str_status(), str_action()); + RETURN_ERRNO(mFALSE, ME_BADREQ); + } +} + +// +void DLLINTERNAL MPlugin::free_api_pointers(void) { + if(gamedll_funcs.dllapi_table) + free(gamedll_funcs.dllapi_table); + if(gamedll_funcs.newapi_table) + free(gamedll_funcs.newapi_table); + + if(tables.dllapi) + free(tables.dllapi); + if(post_tables.dllapi) + free(post_tables.dllapi); + if(tables.newapi) + free(tables.newapi); + if(post_tables.newapi) + free(post_tables.newapi); + if(tables.engine) + free(tables.engine); + if(post_tables.engine) + free(post_tables.engine); +} + +// Clear a plugin (it failed a previous action and should be +// removed from the list, or it's being unloaded). +// meta_errno values: +// - ME_BADREQ not marked for clearing +// - ME_DLERROR failed to dlclose +mBOOL DLLINTERNAL MPlugin::clear(void) { + if(status != PL_FAILED && status != PL_BADFILE + && status != PL_EMPTY && status != PL_OPENED) { + META_WARNING("Cannot clear plugin '%s'; not marked as failed, empty, or open (status=%s)", desc, str_status()); + RETURN_ERRNO(mFALSE, ME_BADREQ); + } + // If file is open, close the file. Note: after this, attempts to + // reference any memory locations in the file will produce a segfault. + if(handle && DLCLOSE(handle) != 0) { + META_WARNING("dll: Couldn't close plugin file '%s': %s", file, DLERROR()); + status=PL_FAILED; + RETURN_ERRNO(mFALSE, ME_DLERROR); + } + handle=NULL; + + free_api_pointers(); + + status=PL_EMPTY; + action=PA_NULL; + handle=NULL; + info=NULL; + time_loaded=0; + gamedll_funcs.dllapi_table=NULL; + gamedll_funcs.newapi_table=NULL; + memset(&tables, 0, sizeof(tables)); + memset(&post_tables, 0, sizeof(post_tables)); + + Plugins->trim_list(); + + return(mTRUE); +} + +// List information about plugin to console. +void DLLINTERNAL MPlugin::show(void) { + char *cp, *tstr; + int n, width; + width=13; + META_CONS("%*s: %s", width, "name", info ? info->name : "(nil)"); + META_CONS("%*s: %s", width, "desc", desc); + META_CONS("%*s: %s", width, "status", str_status()); + META_CONS("%*s: %s", width, "action", str_action()); + META_CONS("%*s: %s", width, "filename", filename); + META_CONS("%*s: %s", width, "file", file); + META_CONS("%*s: %s", width, "pathname", pathname); + META_CONS("%*s: %d", width, "index", index); + META_CONS("%*s: %s", width, "source", str_source()); + META_CONS("%*s: %s", width, "loadable", str_loadable(SL_ALLOWED)); + META_CONS("%*s: %s", width, "unloadable", str_unloadable(SL_ALLOWED)); + META_CONS("%*s: %s", width, "version", info ? info->version : "(nil)"); + META_CONS("%*s: %s", width, "date", info ? info->date : "(nil)"); + META_CONS("%*s: %s", width, "author", info ? info->author : "(nil)"); + META_CONS("%*s: %s", width, "url", info ? info->url : "(nil)"); + META_CONS("%*s: %s", width, "logtag", info ? info->logtag : "(nil)"); + META_CONS("%*s: %s", width, "ifvers", info ? info->ifvers : "(nil)"); + // ctime() includes newline at EOL + tstr=ctime(&time_loaded); + if((cp=strchr(tstr, '\n'))) + *cp='\0'; + META_CONS("%*s: %s", width, "last loaded", tstr); + // XXX show file time ? + + if(tables.dllapi) { + META_CONS("DLLAPI functions:"); + SHOW_DEF_DLLAPI(tables.dllapi," ", ""); + META_CONS("%d functions (dllapi)", n); + } + else + META_CONS("No DLLAPI functions."); + if(post_tables.dllapi) { + META_CONS("DLLAPI-Post functions:"); + SHOW_DEF_DLLAPI(post_tables.dllapi, " ", "_Post"); + META_CONS("%d functions (dllapi post)", n); + } + else + META_CONS("No DLLAPI-Post functions."); + + if(tables.newapi) { + META_CONS("NEWAPI functions:"); + SHOW_DEF_NEWAPI(tables.newapi, " ", ""); + META_CONS("%d functions (newapi)", n); + } + else + META_CONS("No NEWAPI functions."); + if(post_tables.newapi) { + META_CONS("NEWAPI-Post functions:"); + SHOW_DEF_NEWAPI(post_tables.newapi, " ", "_Post"); + META_CONS("%d functions (newapi post)", n); + } + else + META_CONS("No NEWAPI-Post functions."); + + if(tables.engine) { + META_CONS("Engine functions:"); + SHOW_DEF_ENGINE(tables.engine, " ", ""); + META_CONS("%d functions (engine)", n); + } + else + META_CONS("No Engine functions."); + if(post_tables.engine) { + META_CONS("Engine-Post functions:"); + SHOW_DEF_ENGINE(post_tables.engine, " ", "_Post"); + META_CONS("%d functions (engine post)", n); + } + else + META_CONS("No Engine-Post functions."); + RegCmds->show(index); + RegCvars->show(index); + + if(Plugins->found_child_plugins(index)) + Plugins->show(index); + else + META_CONS("No child plugins."); +} + +// Check whether the file on disk looks like it's been updated since we +// last loaded the plugin. +// meta_errno values: +// - ME_NOFILE couldn't find file +// - ME_NOERROR no error; false indicates file not newer +mBOOL DLLINTERNAL MPlugin::newer_file(void) { + struct stat st; + time_t file_time; + + if(stat(pathname, &st) != 0) + RETURN_ERRNO(mFALSE, ME_NOFILE); + file_time=st.st_ctime > st.st_mtime ? st.st_ctime : st.st_mtime; + META_DEBUG(5, ("newer_file? file=%s; load=%d, file=%d; ctime=%d, mtime=%d", + file, time_loaded, file_time, st.st_ctime, st.st_mtime)); + if(file_time > time_loaded) + return(mTRUE); + else + RETURN_ERRNO(mFALSE, ME_NOERROR); +} + +// Return a string describing status of plugin. +// SIMPLE is the default. +// SHOW is max 4 chars, for "show" output. +// meta_errno values: +// - none +const char * DLLINTERNAL MPlugin::str_status(STR_STATUS fmt) { + switch(status) { + case PL_EMPTY: + if(fmt==ST_SHOW) return("empt"); + else return("empty"); + case PL_VALID: + if(fmt==ST_SHOW) return("info"); + else return("valid"); + case PL_BADFILE: + if(fmt==ST_SHOW) return("badf"); + else return("badfile"); + case PL_OPENED: + if(fmt==ST_SHOW) return("open"); + else return("opened"); + case PL_FAILED: + if(fmt==ST_SHOW) return("fail"); + else return("failed"); + case PL_RUNNING: + if(fmt==ST_SHOW) return("RUN"); + else return("running"); + case PL_PAUSED: + if(fmt==ST_SHOW) return("PAUS"); + else return("paused"); + default: + if(fmt==ST_SHOW) return(META_UTIL_VarArgs("UNK%d", status)); + return(META_UTIL_VarArgs("unknown (%d)", status)); + } +} + +// Return a string (one word) describing requested action for plugin. +// SIMPLE is the default. +// SHOW is max 4 chars, for "show" output. +// meta_errno values: +// - none +const char * DLLINTERNAL MPlugin::str_action(STR_ACTION fmt) { + switch(action) { + case PA_NULL: + if(fmt==SA_SHOW) return("NULL"); + else return("null"); + case PA_NONE: + if(fmt==SA_SHOW) return(" - "); + else return("none"); + case PA_KEEP: + if(fmt==SA_SHOW) return("keep"); + else return("keep"); + case PA_LOAD: + if(fmt==SA_SHOW) return("load"); + else return("load"); + case PA_ATTACH: + if(fmt==SA_SHOW) return("atch"); + else return("attach"); + case PA_UNLOAD: + if(fmt==SA_SHOW) return("unld"); + else return("unload"); + case PA_RELOAD: + if(fmt==SA_SHOW) return("relo"); + else return("reload"); + default: + if(fmt==SA_SHOW) return(META_UTIL_VarArgs("UNK%d", action)); + else return(META_UTIL_VarArgs("unknown (%d)", action)); + } +} + +// Return a string describing given plugin loadtime. +// SIMPLE is the default. +// SHOW is max 3 chars, for "show" output. +// ALLOWED is in terms of when it's allowed to load/unload. +// NOW is to describe current situation of load/unload attempt. +// meta_errno values: +// - none +const char * DLLINTERNAL MPlugin::str_loadtime(PLUG_LOADTIME ptime, STR_LOADTIME fmt) { + switch(ptime) { + case PT_NEVER: + if(fmt==SL_SHOW) return("Never"); + else return("never"); + case PT_STARTUP: + if(fmt==SL_SHOW) return("Start"); + else if(fmt==SL_ALLOWED) return("at server startup"); + else if(fmt==SL_NOW) return("during server startup"); + else return("startup"); + case PT_CHANGELEVEL: + if(fmt==SL_SHOW) return("Chlvl"); + else if(fmt==SL_ALLOWED) return("at changelevel"); + else if(fmt==SL_NOW) return("during changelevel"); + else return("changelevel"); + case PT_ANYTIME: + if(fmt==SL_SHOW) return("ANY"); + else if(fmt==SL_ALLOWED) return("at any time"); + else if(fmt==SL_NOW) return("during map"); + else return("anytime"); + case PT_ANYPAUSE: + if(fmt==SL_SHOW) return("Pause"); + else if(fmt==SL_ALLOWED) return("at any time, and pausable"); + else if(fmt==SL_NOW) return("for requested pause"); + else return("pausable"); + default: + if(fmt==SL_SHOW) return(META_UTIL_VarArgs("UNK-%d", ptime)); + else return(META_UTIL_VarArgs("unknown (%d)", ptime)); + } +} + +// Return a string describing why a plugin is to be unloaded. +// meta_errno values: +// - none +const char * DLLINTERNAL MPlugin::str_reason(PL_UNLOAD_REASON preason, PL_UNLOAD_REASON preal_reason) { + char buf[128]; + + if(preason == PNL_PLUGIN) + preason = PNL_NULL; + if(preason == PNL_PLG_FORCED) + preason = PNL_NULL; + + switch(preal_reason) { + case PNL_NULL: + return("null"); + case PNL_INI_DELETED: + return("deleted from ini file"); + case PNL_FILE_NEWER: + return("file on disk is newer"); + case PNL_COMMAND: + return("server command"); + case PNL_CMD_FORCED: + return("forced by server command"); + case PNL_PLUGIN: + STRNCPY(buf, str_reason(PNL_NULL, preason), sizeof(buf)); + return(META_UTIL_VarArgs("%s (request from plugin[%d])", buf, unloader_index)); + case PNL_PLG_FORCED: + STRNCPY(buf, str_reason(PNL_NULL, preason), sizeof(buf)); + return(META_UTIL_VarArgs("%s (forced request from plugin[%d])", buf, unloader_index)); + case PNL_RELOAD: + return("reloading"); + default: + return(META_UTIL_VarArgs("unknown (%d)", preal_reason)); + } +} + +// Return a string describing how the plugin was loaded. +// meta_errno values: +// - none +const char * DLLINTERNAL MPlugin::str_source(STR_SOURCE fmt) { + switch(source) { + case PS_INI: + if(fmt==SO_SHOW) return("ini"); + else return("ini file"); + case PS_CMD: + if(fmt==SO_SHOW) return("cmd"); + else return("console command"); + case PS_PLUGIN: + if(source_plugin_index <= 0) { + if(fmt==SO_SHOW) return("plUN"); + else return("unloaded plugin"); + } else { + if(fmt==SO_SHOW) return(META_UTIL_VarArgs("pl%d", source_plugin_index)); + else return(META_UTIL_VarArgs("plugin [%d]", source_plugin_index)); + } + default: + if(fmt==SO_SHOW) return(META_UTIL_VarArgs("UNK%d", source)); + else return(META_UTIL_VarArgs("unknown (%d)", source)); + } +} diff --git a/src/metamod/mplugin.h b/src/metamod/mplugin.h new file mode 100644 index 0000000..087058f --- /dev/null +++ b/src/metamod/mplugin.h @@ -0,0 +1,234 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mplugin.h - class and types to describe an individual plugin + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef MPLUGIN_H +#define MPLUGIN_H + +#include // time_t, etc +#include // malloc, etc + +#include // DLL_FUNCTIONS, etc + +#include "types_meta.h" // mBOOL +#include "meta_api.h" // GETENTITYAPI_FN, etc +#include "api_info.h" // dllapi_info, etc +#include "support_meta.h" // MAX_DESC_LEN +#include "osdep.h" +#include "new_baseclass.h" + + +// Flags to indicate current "load" state of plugin. +// NOTE: order is important, as greater/less comparisons are made. +typedef enum { + PL_EMPTY = 0, // empty slot + PL_VALID, // has valid info in it + PL_BADFILE, // nonexistent file (open failed), + // or not a valid plugin file (query failed) + PL_OPENED, // dlopened and queried + PL_FAILED, // opened, but failed to attach or unattach + PL_RUNNING, // attached and running + PL_PAUSED, // attached but paused +} PLUG_STATUS; + +// Action to take for plugin at next opportunity. +typedef enum { + PA_NULL = 0, + PA_NONE, // no action needed right now + PA_KEEP, // keep, after ini refresh + PA_LOAD, // load (dlopen, query) and try to attach + PA_ATTACH, // attach + PA_UNLOAD, // unload (detach, dlclose) + PA_RELOAD, // unload and load again +} PLUG_ACTION; + +// Flags to indicate from where the plugin was loaded. +typedef enum { + PS_INI = 0, // was loaded from the plugins.ini + PS_CMD, // was loaded via a server command + PS_PLUGIN, // was loaded by other plugin +} PLOAD_SOURCE; + +// Flags for how to word description of plugin loadtime. +typedef enum { + SL_SIMPLE = 0, // single word + SL_SHOW, // for "show" output, 5 chars + SL_ALLOWED, // when plugin is allowed to load/unload + SL_NOW, // current situation +} STR_LOADTIME; + +// Flags for how to format description of status. +typedef enum { + ST_SIMPLE = 0, // single word + ST_SHOW, // for "show" output, 4 chars +} STR_STATUS; + +// Flags for how to format description of action. +typedef enum { + SA_SIMPLE = 0, // single word + SA_SHOW, // for "show" output, 4 chars +} STR_ACTION; + +// Flags for how to format description of source. +typedef enum { + SO_SIMPLE = 0, // two words + SO_SHOW, // for "list" output, 3 chars +} STR_SOURCE; + +// api table list +typedef struct { + enginefuncs_t *engine; + DLL_FUNCTIONS *dllapi; + NEW_DLL_FUNCTIONS *newapi; +} api_tables_t; + +// An individual plugin. +class MPlugin : public class_metamod_new { + public: + // data: + // reordered for faster api_hook.cpp functions + PLUG_STATUS status; // current status of plugin (loaded, etc) + api_tables_t tables; + api_tables_t post_tables; + + inline DLLINTERNAL void * get_api_table(enum_api_t api) { + return(((void**)&tables)[api]); + } + inline DLLINTERNAL void * get_api_post_table(enum_api_t api) { + return(((void**)&post_tables)[api]); + } + + int index; // 1-based + int pfspecific; // level of specific platform affinity, used during load time + PLUG_ACTION action; // what to do with plugin (load, unload, etc) + PLOAD_SOURCE source; // source of the request to load the plugin + int source_plugin_index; // index of plugin that loaded this plugin. -1 means source plugin has been unloaded. + int unloader_index; + mBOOL is_unloader; // fix to prevent other plugins unload active unloader. + + DLHANDLE handle; // handle for dlopen, dlsym, etc + plugin_info_t *info; // information plugin provides about itself + time_t time_loaded; // when plugin was loaded + + char filename[PATH_MAX]; // ie "dlls/mm_test_i386.so", from inifile + char *file; // ie "mm_test_i386.so", ptr from filename + char desc[MAX_DESC_LEN]; // ie "Test metamod plugin", from inifile + char pathname[PATH_MAX]; // UNIQUE, ie "/home/willday/half-life/cstrike/dlls/mm_test_i386.so", built with GameDLL.gamedir + + // functions: + mBOOL DLLINTERNAL ini_parseline(const char *line); // parse line from inifile + mBOOL DLLINTERNAL cmd_parseline(const char *line); // parse from console command + mBOOL DLLINTERNAL plugin_parseline(const char *fname, int loader_index); // parse from plugin + mBOOL DLLINTERNAL check_input(void); + + mBOOL DLLINTERNAL resolve(void); // find a matching file on disk + char * DLLINTERNAL resolve_dirs(const char *path); + char * DLLINTERNAL resolve_prefix(const char *path); + char * DLLINTERNAL resolve_suffix(const char *path); + static mBOOL DLLINTERNAL is_platform_postfix(const char *pf); + + mBOOL DLLINTERNAL platform_match(MPlugin* plugin); + + mBOOL DLLINTERNAL load(PLUG_LOADTIME now); + mBOOL DLLINTERNAL unload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason, PL_UNLOAD_REASON real_reason); + mBOOL DLLINTERNAL reload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + mBOOL DLLINTERNAL pause(void); + mBOOL DLLINTERNAL unpause(void); + mBOOL DLLINTERNAL retry(PLUG_LOADTIME now, PL_UNLOAD_REASON reason); // if previously failed + void DLLINTERNAL free_api_pointers(void); + mBOOL DLLINTERNAL clear(void); + mBOOL DLLINTERNAL plugin_unload(plid_t plid, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); // other plugin unloading + void DLLINTERNAL show(void); // print info about plugin to console + + mBOOL DLLINTERNAL newer_file(void); // check for newer file on disk + + // output string functions + const char * DLLINTERNAL str_status(STR_STATUS fmt); + const char * DLLINTERNAL str_action(STR_ACTION fmt); + const char * DLLINTERNAL str_source(STR_SOURCE fmt); + + const char * DLLINTERNAL str_reason(PL_UNLOAD_REASON preason, PL_UNLOAD_REASON preal_reason); + const char * DLLINTERNAL str_loadtime(PLUG_LOADTIME pallow, STR_LOADTIME fmt); + + inline const char * DLLINTERNAL str_status(void) { return(str_status(ST_SIMPLE)); }; + inline const char * DLLINTERNAL str_action(void) { return(str_action(SA_SIMPLE)); }; + inline const char * DLLINTERNAL str_source(void) { return(str_source(SO_SIMPLE)); }; + + inline const char * DLLINTERNAL str_loadable(void) { + return(info?str_loadtime(info->loadable, SL_SIMPLE):" -"); + }; + inline const char * DLLINTERNAL str_unloadable(void) { + return(info?str_loadtime(info->unloadable, SL_SIMPLE):" -"); + }; + inline const char * DLLINTERNAL str_loadable(STR_LOADTIME fmt) { + return(info?str_loadtime(info->loadable, fmt):" -"); + }; + inline const char * DLLINTERNAL str_unloadable(STR_LOADTIME fmt) { + return(info?str_loadtime(info->unloadable, fmt):" -"); + }; + private: + mBOOL DLLINTERNAL query(void); + mBOOL DLLINTERNAL attach(PLUG_LOADTIME now); + mBOOL DLLINTERNAL detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + + gamedll_funcs_t gamedll_funcs; + mutil_funcs_t mutil_funcs; +}; + +// Macros used by MPlugin::show(), to list the functions that the plugin +// catches. +#define SHOW_DEF_API(api_info, api_table, pre_str, post_str) \ + n=0; \ + { \ + const api_info_t * ainfo = (const api_info_t *)&api_info; \ + const void ** table = (const void **)api_table; \ + for(int i = 0; &ainfo[i] < &api_info.END; i++) { \ + if(table[i]) { \ + META_CONS("%s%s%s", pre_str, ainfo[i].name, post_str); \ + n++; \ + } \ + } \ + } + +#define SHOW_DEF_DLLAPI(api_table, pre_str, post_str) \ + SHOW_DEF_API(dllapi_info, api_table, pre_str, post_str) + +#define SHOW_DEF_NEWAPI(api_table, pre_str, post_str) \ + SHOW_DEF_API(newapi_info, api_table, pre_str, post_str) + +#define SHOW_DEF_ENGINE(api_table, pre_str, post_str) \ + SHOW_DEF_API(engine_info, api_table, pre_str, post_str) + +#endif /* MPLUGIN_H */ diff --git a/src/metamod/mqueue.cpp b/src/metamod/mqueue.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/metamod/mqueue.h b/src/metamod/mqueue.h new file mode 100644 index 0000000..e69de29 diff --git a/src/metamod/mreg.cpp b/src/metamod/mreg.cpp new file mode 100644 index 0000000..cbc8136 --- /dev/null +++ b/src/metamod/mreg.cpp @@ -0,0 +1,532 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mreg.cpp - functions for registered items (classes MRegCmd, MRegCmdList) + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifdef linux +// enable extra routines in system header files, like strsignal +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +#endif /* linux */ + +#include // strsignal, strdup, etc +#include // strerror, etc + +#include // always + +#include "mreg.h" // me +#include "metamod.h" // Plugins, etc +#include "mlist.h" // class MPluginList +#include "mplugin.h" // class MPlugin +#include "types_meta.h" // mBOOL +#include "log_meta.h" // META_LOG, etc +#include "osdep.h" // os_safe_call, etc + + +///// class MRegCmd: + +// Init values. It would probably be more "proper" to use containers and +// constructors, rather than arrays and init-functions. +void DLLINTERNAL MRegCmd::init(int idx) +{ + index = idx; + name = NULL; + pfnCmd = NULL; + plugid = 0; + status = RG_INVALID; +} + +// Try to call the function. Relies on OS-specific routine to attempt +// calling the function without generating a segfault from an unloaded +// plugin DLL. +// meta_errno values: +// - ME_BADREQ function disabled/invalid +// - ME_ARGUMENT function pointer is null +mBOOL DLLINTERNAL MRegCmd::call(void) { + mBOOL ret; + + // can we expect to call this function? + if(status != RG_VALID) + RETURN_ERRNO(mFALSE, ME_BADREQ); + if(!pfnCmd) + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + + // try to call this function + ret=os_safe_call(pfnCmd); + if(!ret) { + META_DEBUG(4, ("Plugin reg_cmd '%s' called after unloaded; removed from list", name)); + status=RG_INVALID; + pfnCmd=NULL; + // NOTE: we can't free the malloc'd space for the name, as that + // would just re-introduce the segfault problem.. + } + // meta_errno (if failed) is set already in os_safe_call() + return(ret); +} + + +///// class MRegCmdList: + +// Constructor +MRegCmdList::MRegCmdList(void) + : mlist(0), size(REG_CMD_GROWSIZE), endlist(0) +{ + int i; + mlist = (MRegCmd *) calloc(1, size * sizeof(MRegCmd)); + // initialize array + for(i=0; i < size; i++) + mlist[i].init(i+1); // 1-based index + endlist=0; +} + +// Try to find a registered function with the given name. +// meta_errno values: +// - ME_NOTFOUND couldn't find a matching function +MRegCmd * DLLINTERNAL MRegCmdList::find(const char *findname) { + int i; + for(i=0; i < endlist; i++) { + if(!strcasecmp(mlist[i].name, findname)) + return(&mlist[i]); + } + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// Add the given name to the list and return the instance. This only +// writes the "name" to the new cmd; other fields are writtin by caller +// (meta_AddServerCommand). +// meta_errno values: +// - ME_NOMEM couldn't realloc or malloc for various parts +MRegCmd * DLLINTERNAL MRegCmdList::add(const char *addname) { + MRegCmd *icmd; + + if(endlist==size) { + // grow array + MRegCmd *temp; + int i, newsize; + newsize=size+REG_CMD_GROWSIZE; + META_DEBUG(6, ("Growing reg cmd list from %d to %d", size, newsize)); + temp = (MRegCmd *) realloc(mlist, newsize*sizeof(MRegCmd)); + if(!temp) { + META_WARNING("Couldn't grow registered command list to %d for '%s': %s", newsize, addname, strerror(errno)); + RETURN_ERRNO(NULL, ME_NOMEM); + } + mlist=temp; + size=newsize; + // initialize new (unused) entries + for(i=endlist; iname=strdup(addname); + if(!icmd->name) { + META_WARNING("Couldn't strdup for adding reg cmd name '%s': %s", + addname, strerror(errno)); + RETURN_ERRNO(NULL, ME_NOMEM); + } + endlist++; + + return(icmd); +} + +// Disable any functions belonging to the given plugin (by index id). +void DLLINTERNAL MRegCmdList::disable(int plugin_id) { + int i; + for(i=0; i < size; i++) { + if(mlist[i].plugid == plugin_id) + mlist[i].status = RG_INVALID; + } +} + +// List all the registered commands. +void DLLINTERNAL MRegCmdList::show(void) { + int i, n=0, a=0; + MRegCmd *icmd; + MPlugin *iplug; + char bplug[18+1]; // +1 for term null + + META_CONS("Registered plugin commands:"); + META_CONS(" %*s %-*s %-s", + WIDTH_MAX_REG, "", + sizeof(bplug)-1, "plugin", "command"); + + for(i=0; i < endlist; i++) { + icmd = &mlist[i]; + + if(icmd->status==RG_VALID) { + iplug=Plugins->find(icmd->plugid); + + if(iplug) + STRNCPY(bplug, iplug->desc, sizeof(bplug)); + else + STRNCPY(bplug, "(unknown)", sizeof(bplug)); + } + else + STRNCPY(bplug, "(unloaded)", sizeof(bplug)); + + META_CONS(" [%*d] %-*s %-s", + WIDTH_MAX_REG, icmd->index, + sizeof(bplug)-1, bplug, + icmd->name); + + if(icmd->status==RG_VALID) + a++; + + n++; + } + + META_CONS("%d commands, %d available (%d allocated)", n, a, size); +} + +// List all the registered commands for the given plugin id. +void DLLINTERNAL MRegCmdList::show(int plugin_id) { + int i, n=0; + MRegCmd *icmd; + + /* + // If OS doesn't support DLFNAME, then we can't know what the plugin's + // registered cvars are. + DLFNAME(NULL); + if(meta_errno==ME_OSNOTSUP) { + META_CONS("Registered commands: unknown (can't get info under this OS)"); + return; + } + */ + + META_CONS("Registered commands:"); + for(i=0; i < endlist; i++) { + icmd = &mlist[i]; + if(icmd->plugid != plugin_id) + continue; + META_CONS(" %s", icmd->name); + n++; + } + META_CONS("%d commands", n); +} + + +///// class MRegCvar: + +// Init values. It would probably be more "proper" to use containers and +// constructors, rather than arrays and init-functions. +void DLLINTERNAL MRegCvar::init(int idx) +{ + index = idx; + data = NULL; + plugid = 0; + status = RG_INVALID; +} + +// Set the cvar, copying values from given cvar. +// meta_errno values: +// - ME_ARGUMENT given cvar doesn't match this cvar +mBOOL DLLINTERNAL MRegCvar::set(cvar_t *src) { + if(strcasecmp(src->name, data->name)) { + META_WARNING("Tried to set cvar with mismatched name; src=%s dst=%s", + src->name, data->name); + RETURN_ERRNO(mFALSE, ME_ARGUMENT); + } + // Would like to free() existing string, but can't tell where it was + // allocated... + data->string = strdup(src->string); + data->flags = src->flags; + data->value = src->value; + data->next = src->next; + return(mTRUE); +} + + +///// class MRegCvarList: + +// Constructor +MRegCvarList::MRegCvarList(void) + : vlist(0), size(REG_CVAR_GROWSIZE), endlist(0) +{ + int i; + vlist = (MRegCvar *) calloc(1, size * sizeof(MRegCvar)); + // initialize array + for(i=0; i < size; i++) + vlist[i].init(i+1); // 1-based + endlist=0; +} + +// Add the given cvar name to the list and return the instance. This only +// writes the "name" to the new cvar; other fields are written with +// cvar::set(). +// meta_errno values: +// - ME_NOMEM couldn't alloc or realloc for various parts +MRegCvar * DLLINTERNAL MRegCvarList::add(const char *addname) { + MRegCvar *icvar; + + if(endlist==size) { + // grow array + MRegCvar *temp; + int i, newsize; + newsize=size+REG_CVAR_GROWSIZE; + META_DEBUG(6, ("Growing reg cvar list from %d to %d", size, newsize)); + temp = (MRegCvar *) realloc(vlist, newsize*sizeof(MRegCvar)); + if(!temp) { + META_WARNING("Couldn't grow registered cvar list to %d for '%s'; %s", newsize, addname, strerror(errno)); + RETURN_ERRNO(NULL, ME_NOMEM); + } + vlist=temp; + size=newsize; + // initialize new (unused) entries + for(i=endlist; idata = (cvar_t *) calloc(1, sizeof(cvar_t)); + if(!icvar->data) { + META_WARNING("Couldn't malloc cvar for adding reg cvar name '%s': %s", + addname, strerror(errno)); + RETURN_ERRNO(NULL, ME_NOMEM); + } + icvar->data->name=strdup(addname); + if(!icvar->data->name) { + META_WARNING("Couldn't strdup for adding reg cvar name '%s': %s", + addname, strerror(errno)); + RETURN_ERRNO(NULL, ME_NOMEM); + } + endlist++; + + return(icvar); +} + +// Try to find a registered cvar with the given name. +// meta_errno values: +// - ME_NOTFOUND couldn't find a matching cvar +MRegCvar * DLLINTERNAL MRegCvarList::find(const char *findname) { + int i; + for(i=0; i < endlist; i++) { + if(!strcasecmp(vlist[i].data->name, findname)) + return(&vlist[i]); + } + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// Disable any cvars belonging to the given plugin (by index id). +void DLLINTERNAL MRegCvarList::disable(int plugin_id) { + int i; + MRegCvar *icvar; + for(i=0; i < size; i++) { + icvar=&vlist[i]; + if(icvar->plugid == plugin_id) { + icvar->status = RG_INVALID; + icvar->plugid = 0; + // Decided not to do this, in order to keep pre-existing values + // after a plugin reload. + // CVAR_SET_STRING(icvar->data->name, "[metamod: cvar invalid; plugin unloaded]"); + } + } +} + +// List all the registered cvars. +void DLLINTERNAL MRegCvarList::show(void) { + int i, n=0, a=0; + MRegCvar *icvar; + MPlugin *iplug; + char bplug[13+1], bname[20+1], bval[15+1]; // +1 for term null + + META_CONS("Registered plugin cvars:"); + META_CONS(" %*s %-*s %-*s %*s %s", + WIDTH_MAX_REG, "", + sizeof(bplug)-1, "plugin", + sizeof(bname)-1, "cvar", + sizeof(bval)-1, "float value", + "string value"); + + for(i=0; i < endlist; i++) { + icvar = &vlist[i]; + if(icvar->status==RG_VALID) { + iplug=Plugins->find(icvar->plugid); + if(iplug) + STRNCPY(bplug, iplug->desc, sizeof(bplug)); + else + STRNCPY(bplug, "(unknown)", sizeof(bplug)); + } + else + STRNCPY(bplug, "(unloaded)", sizeof(bplug)); + STRNCPY(bname, icvar->data->name, sizeof(bname)); + safevoid_snprintf(bval, sizeof(bval), "%f", icvar->data->value); + META_CONS(" [%*d] %-*s %-*s %*s %s", + WIDTH_MAX_REG, icvar->index, + sizeof(bplug)-1, bplug, + sizeof(bname)-1, bname, + sizeof(bval)-1, bval, + icvar->data->string); + if(icvar->status==RG_VALID) + a++; + n++; + } + META_CONS("%d cvars, %d available (%d allocated)", n, a, size); +} + +// List the registered cvars for the given plugin id. +void DLLINTERNAL MRegCvarList::show(int plugin_id) { + int i, n=0; + MRegCvar *icvar; + char bname[30+1], bval[15+1]; // +1 for term null + + /* + // If OS doesn't support DLFNAME, then we can't know what the plugin's + // registered cvars are. + DLFNAME(NULL); + if(meta_errno==ME_OSNOTSUP) { + META_CONS("Registered cvars: unknown (can't get info under this OS)"); + return; + } + */ + + META_CONS("%-*s %*s %s", + sizeof(bname)-1, "Registered cvars:", + sizeof(bval)-1, "float value", + "string value"); + for(i=0; i < endlist; i++) { + icvar = &vlist[i]; + if(icvar->plugid != plugin_id) + continue; + STRNCPY(bname, icvar->data->name, sizeof(bname)); + safevoid_snprintf(bval, sizeof(bval), "%f", icvar->data->value); + META_CONS(" %-*s %*s %s", + sizeof(bname)-1, bname, + sizeof(bval)-1, bval, + icvar->data->string); + n++; + } + META_CONS("%d cvars", n); +} + + +///// class MRegMsgList: + +// Constructor +MRegMsgList::MRegMsgList(void) + : size(MAX_REG_MSGS), endlist(0) +{ + int i; + // initialize array + memset(mlist, 0, sizeof(mlist)); + for(i=0; i < size; i++) { + mlist[i].index=i+1; // 1-based + } + endlist=0; +} + +// Add the given user msg the list and return the instance. +// meta_errno values: +// - ME_MAXREACHED reached max number of msgs allowed +MRegMsg * DLLINTERNAL MRegMsgList::add(const char *addname, int addmsgid, int addsize) { + MRegMsg *imsg; + + if(endlist==size) { + // all slots used + META_ERROR("Couldn't add registered msg '%s' to list; reached max msgs (%d)", + addname, size); + RETURN_ERRNO(NULL, ME_MAXREACHED); + } + + imsg = &mlist[endlist]; + endlist++; + + // Copy msg data into empty slot. + // Note: 'addname' assumed to be a constant string allocated in the + // gamedll. + imsg->name=addname; + imsg->msgid=addmsgid; + imsg->size=addsize; + + return(imsg); +} + +// Try to find a registered msg with the given name. +// meta_errno values: +// - ME_NOTFOUND couldn't find a matching cvar +MRegMsg * DLLINTERNAL MRegMsgList::find(const char *findname) { + int i; + for(i=0; i < endlist; i++) { + if(!mm_strcmp(mlist[i].name, findname)) + return(&mlist[i]); + } + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// Try to find a registered msg with the given msgid. +// meta_errno values: +// - ME_NOTFOUND couldn't find a matching cvar +MRegMsg * DLLINTERNAL MRegMsgList::find(int findmsgid) { + int i; + for(i=0; i < endlist; i++) { + if(mlist[i].msgid == findmsgid) + return(&mlist[i]); + } + RETURN_ERRNO(NULL, ME_NOTFOUND); +} + +// List the registered usermsgs for the gamedll. +void DLLINTERNAL MRegMsgList::show(void) { + int i, n=0; + MRegMsg *imsg; + char bname[25+1]; // +1 for term null + + META_CONS("%-*s %5s %5s", + sizeof(bname)-1, "Game registered user msgs:", "msgid", "size"); + for(i=0; i < endlist; i++) { + imsg = &mlist[i]; + STRNCPY(bname, imsg->name, sizeof(bname)); + META_CONS(" %-*s %3d %3d", + sizeof(bname)-1, bname, + imsg->msgid, + imsg->size); + n++; + } + META_CONS("%d game user msgs", n); +} diff --git a/src/metamod/mreg.h b/src/metamod/mreg.h new file mode 100644 index 0000000..704dbfd --- /dev/null +++ b/src/metamod/mreg.h @@ -0,0 +1,191 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mreg.h - description of registered items (classes MRegCmd, MRegCmdList) + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef MREG_H +#define MREG_H + +#include "types_meta.h" // mBOOL +#include "comp_dep.h" // +#include "new_baseclass.h" + +// Number of entries to add to reglists when they need to grow. Typically +// more cvars than commands, so we grow them at different increments. +#define REG_CMD_GROWSIZE 32 +#define REG_CVAR_GROWSIZE 64 + +// Width required to printf a Reg*List index number, for show() functions. +// This used to correspond to the number of digits in MAX_REG, which was a +// fixed, compile-time limit. However, now that the reg lists are grown +// dynamically, this has less strong correspondance to list sizes. So for +// the moment, it reflects what one might normally expect to be the max +// width needed to print an index number; 4 allows 9999 (which is a damn +// lot, if you ask me). +#define WIDTH_MAX_REG 4 + +// Max number of registered user msgs we can manage. +#define MAX_REG_MSGS 256 + +// Max number of clients on server +#define MAX_CLIENTS_CONNECTED 32 + +// Flags to indicate if given cvar or func is part of a loaded plugin. +typedef enum { + RG_INVALID, + RG_VALID, +} REG_STATUS; + +// Pointer to function registered by AddServerCommand. +typedef void (*REG_CMD_FN) (void); + + +// An individual registered function/command. +class MRegCmd : public class_metamod_new { + friend class MRegCmdList; + private: + // data: + int index; // 1-based + public: + char *name; // space is malloc'd + REG_CMD_FN pfnCmd; // pointer to the function + int plugid; // index id of corresponding plugin + REG_STATUS status; // whether corresponding plugin is loaded + // functions: + void DLLINTERNAL init(int idx); // init values, as not using constructors + mBOOL DLLINTERNAL call(void); // try to call the function +}; + + +// A list of registered commands. +class MRegCmdList : public class_metamod_new { + private: + // data: + MRegCmd *mlist; // malloc'd array of registered commands + int size; // current size of list + int endlist; // index of last used entry + // Private; to satisfy -Weffc++ "has pointer data members but does + // not override" copy/assignment constructor. + void operator=(const MRegCmdList &src); + MRegCmdList(const MRegCmdList &src); + + public: + // constructor: + MRegCmdList(void) DLLINTERNAL; + + // functions: + MRegCmd * DLLINTERNAL find(const char *findname); // find by MRegCmd->name + MRegCmd * DLLINTERNAL add(const char *addname); + void DLLINTERNAL disable(int plugin_id); // change status to Invalid + void DLLINTERNAL show(void); // list all funcs to console + void DLLINTERNAL show(int plugin_id); // list given plugin's funcs to console +}; + + + +// An individual registered cvar. +class MRegCvar : public class_metamod_new { + friend class MRegCvarList; + private: + // data: + int index; // 1-based + public: + cvar_t *data; // actual cvar structure, malloc'd + int plugid; // index id of corresponding plugin + REG_STATUS status; // whether corresponding plugin is loaded + // functions: + void DLLINTERNAL init(int idx); // init values, as not using constructors + mBOOL DLLINTERNAL set(cvar_t *src); +}; + + +// A list of registered cvars. +class MRegCvarList : public class_metamod_new { + private: + // data: + MRegCvar *vlist; // malloc'd array of registered cvars + int size; // size of list, ie MAX_REG_CVARS + int endlist; // index of last used entry + // Private; to satisfy -Weffc++ "has pointer data members but does + // not override" copy/assignment constructor. + void operator=(const MRegCvarList &src); + MRegCvarList(const MRegCvarList &src); + + public: + // constructor: + MRegCvarList(void) DLLINTERNAL; + + // functions: + MRegCvar * DLLINTERNAL add(const char *addname); + MRegCvar * DLLINTERNAL find(const char *findname); // find by MRegCvar->data.name + void DLLINTERNAL disable(int plugin_id); // change status to Invalid + void DLLINTERNAL show(void); // list all cvars to console + void DLLINTERNAL show(int plugin_id); // list given plugin's cvars to console +}; + + + +// An individual registered user msg, from gamedll. +class MRegMsg : public class_metamod_new { + friend class MRegMsgList; + private: + // data: + int index; // 1-based + public: + const char *name; // name, assumed constant string in gamedll + int msgid; // msgid, assigned by engine + int size; // size, if given by gamedll +}; + + +// A list of registered user msgs. +class MRegMsgList : public class_metamod_new { + private: + // data: + MRegMsg mlist[MAX_REG_MSGS]; // array of registered msgs + int size; // size of list, ie MAX_REG_MSGS + int endlist; // index of last used entry + + public: + // constructor: + MRegMsgList(void) DLLINTERNAL; + + // functions: + MRegMsg * DLLINTERNAL add(const char *addname, int addmsgid, int addsize); + MRegMsg * DLLINTERNAL find(const char *findname); + MRegMsg * DLLINTERNAL find(int findmsgid); + void DLLINTERNAL show(void); // list all msgs to console +}; + +#endif /* MREG_H */ diff --git a/src/metamod/mutil.cpp b/src/metamod/mutil.cpp new file mode 100644 index 0000000..b773bd3 --- /dev/null +++ b/src/metamod/mutil.cpp @@ -0,0 +1,395 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mutil.cpp - utility functions to provide to plugins + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // vsnprintf(), etc +#include // vs_start(), etc +#include // strtol() + +#include // always + +#include "meta_api.h" // +#include "mutil.h" // me +#include "mhook.h" // class MHookList, etc +#include "linkent.h" // ENTITY_FN, etc +#include "metamod.h" // Hooks, etc +#include "types_meta.h" // mBOOL +#include "osdep.h" // win32 vsnprintf, etc +#include "sdk_util.h" // ALERT, etc + +static hudtextparms_t default_csay_tparms = { + -1, 0.25, // x, y + 2, // effect + 0, 255, 0, 0, // r, g, b, a1 + 0, 0, 0, 0, // r2, g2, b2, a2 + 0, 0, 10, 10, // fadein, fadeout, hold, fxtime + 1 // channel +}; + +// Log to console; newline added. +static void mutil_LogConsole(plid_t /* plid */, const char *fmt, ...) { + va_list ap; + char buf[MAX_LOGMSG_LEN]; + unsigned int len; + + va_start(ap, fmt); + safevoid_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + // end msg with newline + len=strlen(buf); + if(len < sizeof(buf)-2) // -1 null, -1 for newline + strcat(buf, "\n"); + else + buf[len-1] = '\n'; + + SERVER_PRINT(buf); +} + +// Log regular message to logs; newline added. +static void mutil_LogMessage(plid_t plid, const char *fmt, ...) { + va_list ap; + char buf[MAX_LOGMSG_LEN]; + plugin_info_t *plinfo; + + plinfo=(plugin_info_t *)plid; + va_start(ap, fmt); + safevoid_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + ALERT(at_logged, "[%s] %s\n", plinfo->logtag, buf); +} + +// Log an error message to logs; newline added. +static void mutil_LogError(plid_t plid, const char *fmt, ...) { + va_list ap; + char buf[MAX_LOGMSG_LEN]; + plugin_info_t *plinfo; + + plinfo=(plugin_info_t *)plid; + va_start(ap, fmt); + safevoid_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + ALERT(at_logged, "[%s] ERROR: %s\n", plinfo->logtag, buf); +} + +// Log a message only if cvar "developer" set; newline added. +static void mutil_LogDeveloper(plid_t plid, const char *fmt, ...) { + va_list ap; + char buf[MAX_LOGMSG_LEN]; + plugin_info_t *plinfo; + + if((int)CVAR_GET_FLOAT("developer") == 0) + return; + + plinfo=(plugin_info_t *)plid; + va_start(ap, fmt); + safevoid_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + ALERT(at_logged, "[%s] dev: %s\n", plinfo->logtag, buf); +} + +// Print a center-message, with text parameters and varargs. Provides +// functionality to the above center_say interfaces. +static void mutil_CenterSayVarargs(plid_t plid, hudtextparms_t tparms, + const char *fmt, va_list ap) +{ + char buf[MAX_LOGMSG_LEN]; + int n; + edict_t *pEntity; + + safevoid_vsnprintf(buf, sizeof(buf), fmt, ap); + + mutil_LogMessage(plid, "(centersay) %s", buf); + for(n=1; n <= gpGlobals->maxClients; n++) { + pEntity=INDEXENT(n); + META_UTIL_HudMessage(pEntity, tparms, buf); + } +} + +// Print message on center of all player's screens. Uses default text +// parameters (color green, 10 second fade-in). +static void mutil_CenterSay(plid_t plid, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mutil_CenterSayVarargs(plid, default_csay_tparms, fmt, ap); + va_end(ap); +} + +// Print a center-message, with given text parameters. +static void mutil_CenterSayParms(plid_t plid, hudtextparms_t tparms, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mutil_CenterSayVarargs(plid, tparms, fmt, ap); + va_end(ap); +} + +// Allow plugins to call the entity functions in the GameDLL. In +// particular, calling "player()" as needed by most Bots. Suggested by +// Jussi Kivilinna. +static qboolean mutil_CallGameEntity(plid_t plid, const char *entStr, entvars_t *pev) { + plugin_info_t *plinfo; + ENTITY_FN pfnEntity; + + plinfo=(plugin_info_t *)plid; + META_DEBUG(8, ("Looking up game entity '%s' for plugin '%s'", entStr, + plinfo->name)); + pfnEntity = (ENTITY_FN) DLSYM(GameDLL.handle, entStr); + if(!pfnEntity) { + META_WARNING("Couldn't find game entity '%s' in game DLL '%s' for plugin '%s'", entStr, GameDLL.name, plinfo->name); + return(false); + } + META_DEBUG(7, ("Calling game entity '%s' for plugin '%s'", entStr, + plinfo->name)); + (*pfnEntity)(pev); + return(true); +} + +// Find a usermsg, registered by the gamedll, with the corresponding +// msgname, and return remaining info about it (msgid, size). +static int mutil_GetUserMsgID(plid_t plid, const char *msgname, int *size) { + plugin_info_t *plinfo; + MRegMsg *umsg; + + plinfo=(plugin_info_t *)plid; + META_DEBUG(8, ("Looking up usermsg name '%s' for plugin '%s'", msgname, + plinfo->name)); + umsg=RegMsgs->find(msgname); + if(umsg) { + if(size) + *size=umsg->size; + return(umsg->msgid); + } + else + return(0); +} + +// Find a usermsg, registered by the gamedll, with the corresponding +// msgid, and return remaining info about it (msgname, size). +static const char *mutil_GetUserMsgName(plid_t plid, int msgid, int *size) { + plugin_info_t *plinfo; + MRegMsg *umsg; + + plinfo=(plugin_info_t *)plid; + META_DEBUG(8, ("Looking up usermsg id '%d' for plugin '%s'", msgid, + plinfo->name)); + // Guess names for any built-in Engine messages mentioned in the SDK; + // from dlls/util.h. + if(msgid < 64) { + switch(msgid) { + case SVC_TEMPENTITY: + if(size) *size=-1; + return("tempentity?"); + case SVC_INTERMISSION: + if(size) *size=-1; + return("intermission?"); + case SVC_CDTRACK: + if(size) *size=-1; + return("cdtrack?"); + case SVC_WEAPONANIM: + if(size) *size=-1; + return("weaponanim?"); + case SVC_ROOMTYPE: + if(size) *size=-1; + return("roomtype?"); + case SVC_DIRECTOR: + if(size) *size=-1; + return("director?"); + } + } + umsg=RegMsgs->find(msgid); + if(umsg) { + if(size) + *size=umsg->size; + // 'name' is assumed to be a constant string, allocated in the + // gamedll. + return(umsg->name); + } + else + return(NULL); +} + +// Return the full path of the plugin's loaded dll/so file. +static const char *mutil_GetPluginPath(plid_t plid) { + static char buf[PATH_MAX]; + MPlugin *plug; + + plug=Plugins->find(plid); + if(!plug) { + META_WARNING("GetPluginPath: couldn't find plugin '%s'", + plid->name); + return(NULL); + } + STRNCPY(buf, plug->pathname, sizeof(buf)); + return(buf); +} + +// Return various string-based info about the game/MOD/gamedll. +static const char *mutil_GetGameInfo(plid_t plid, ginfo_t type) { + static char buf[MAX_STRBUF_LEN]; + const char *cp; + switch(type) { + case GINFO_NAME: + cp=GameDLL.name; + break; + case GINFO_DESC: + cp=GameDLL.desc; + break; + case GINFO_GAMEDIR: + cp=GameDLL.gamedir; + break; + case GINFO_DLL_FULLPATH: + cp=GameDLL.pathname; + break; + case GINFO_DLL_FILENAME: + cp=GameDLL.file; + break; + case GINFO_REALDLL_FULLPATH: + cp=GameDLL.real_pathname; + break; + default: + META_WARNING("GetGameInfo: invalid request '%d' from plugin '%s'", + type, plid->name); + return(NULL); + } + STRNCPY(buf, cp, sizeof(buf)); + return(buf); +} + +static int mutil_LoadMetaPlugin(plid_t plid, const char *fname, PLUG_LOADTIME now, void **plugin_handle) +{ + MPlugin *pl_loaded; + + if(NULL == fname) { + return(ME_ARGUMENT); + } + + meta_errno = ME_NOERROR; + if(!(pl_loaded=Plugins->plugin_addload(plid, fname, now))) { + if(plugin_handle) + *plugin_handle = NULL; + return(meta_errno); + } else { + if(plugin_handle) + *plugin_handle = (void*)pl_loaded->handle; + return(0); + } +} + +static int mutil_UnloadMetaPlugin(plid_t plid, const char *fname, PLUG_LOADTIME now, PL_UNLOAD_REASON reason) +{ + MPlugin *findp = NULL; + int pindex; + char* endptr; + + if(NULL == fname) { + return(ME_ARGUMENT); + } + + pindex = strtol(fname, &endptr, 10); + if(*fname != '\0' && *endptr == '\0') + findp = Plugins->find(pindex); + else + findp = Plugins->find_match(fname); + + if(!findp) + return(meta_errno); + + meta_errno = ME_NOERROR; + + if(findp->plugin_unload(plid, now, reason)) + return(0); + + return(meta_errno); +} + +static int mutil_UnloadMetaPluginByHandle(plid_t plid, void *plugin_handle, PLUG_LOADTIME now, PL_UNLOAD_REASON reason) +{ + MPlugin *findp; + + if(NULL == plugin_handle) { + return(ME_ARGUMENT); + } + + if(!(findp=Plugins->find((DLHANDLE)plugin_handle))) + return(ME_NOTFOUND); + + meta_errno = ME_NOERROR; + + if(findp->plugin_unload(plid, now, reason)) + return(0); + + return(meta_errno); +} + +// Check if player is being queried for cvar +static const char * mutil_IsQueryingClientCvar(plid_t /*plid*/, const edict_t *player) { + return(g_Players.is_querying_cvar(player)); +} + +// +static int mutil_MakeRequestID(plid_t /*plid*/) { + return(abs(0xbeef<<16) + (++requestid_counter)); +} + +// +static void mutil_GetHookTables(plid_t plid, enginefuncs_t **peng, DLL_FUNCTIONS **pdll, NEW_DLL_FUNCTIONS **pnewdll) { + if (peng) + *peng = &meta_engfuncs; + if (pdll) + *pdll = g_pHookedDllFunctions; + if (pnewdll) + *pnewdll = g_pHookedNewDllFunctions; +} + +// Meta Utility Function table. +mutil_funcs_t MetaUtilFunctions = { + mutil_LogConsole, // pfnLogConsole + mutil_LogMessage, // pfnLogMessage + mutil_LogError, // pfnLogError + mutil_LogDeveloper, // pfnLogDeveloper + mutil_CenterSay, // pfnCenterSay + mutil_CenterSayParms, // pfnCenterSayParms + mutil_CenterSayVarargs, // pfnCenterSayVarargs + mutil_CallGameEntity, // pfnCallGameEntity + mutil_GetUserMsgID, // pfnGetUserMsgID + mutil_GetUserMsgName, // pfnGetUserMsgName + mutil_GetPluginPath, // pfnGetPluginPath + mutil_GetGameInfo, // pfnGetGameInfo + mutil_LoadMetaPlugin, // pfnLoadPlugin + mutil_UnloadMetaPlugin, // pfnUnloadPlugin + mutil_UnloadMetaPluginByHandle, // pfnUnloadPluginByHandle + mutil_IsQueryingClientCvar, // pfnIsQueryingClientCvar + mutil_MakeRequestID, // pfnMakeRequestID + mutil_GetHookTables, // pfnGetHookTables +}; diff --git a/src/metamod/mutil.h b/src/metamod/mutil.h new file mode 100644 index 0000000..eeeaab6 --- /dev/null +++ b/src/metamod/mutil.h @@ -0,0 +1,108 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// mutil.h - prototypes for utility functions to provide to plugins + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef MUTIL_H +#define MUTIL_H + +#include "comp_dep.h" +#include "plinfo.h" // plugin_info_t, etc +#include "mhook.h" // game_event_t, etc +#include "sdk_util.h" // hudtextparms_t, etc + +// max buffer size for printed messages +#define MAX_LOGMSG_LEN 1024 + +// For GetGameInfo: +typedef enum { + GINFO_NAME = 0, + GINFO_DESC, + GINFO_GAMEDIR, + GINFO_DLL_FULLPATH, + GINFO_DLL_FILENAME, + GINFO_REALDLL_FULLPATH, +} ginfo_t; + +// Meta Utility Function table type. +typedef struct meta_util_funcs_s { + void (*pfnLogConsole) (plid_t plid, const char *fmt, ...); + void (*pfnLogMessage) (plid_t plid, const char *fmt, ...); + void (*pfnLogError) (plid_t plid, const char *fmt, ...); + void (*pfnLogDeveloper) (plid_t plid, const char *fmt, ...); + void (*pfnCenterSay) (plid_t plid, const char *fmt, ...); + void (*pfnCenterSayParms) (plid_t plid, hudtextparms_t tparms, + const char *fmt, ...); + void (*pfnCenterSayVarargs) (plid_t plid, hudtextparms_t tparms, + const char *fmt, va_list ap); + qboolean (*pfnCallGameEntity) (plid_t plid, const char *entStr, + entvars_t *pev); + int (*pfnGetUserMsgID) (plid_t plid, const char *msgname, int *size); + const char *(*pfnGetUserMsgName) (plid_t plid, int msgid, int *size); + const char *(*pfnGetPluginPath) (plid_t plid); + const char *(*pfnGetGameInfo) (plid_t plid, ginfo_t tag); + + int (*pfnLoadPlugin)(plid_t plid, const char *cmdline, PLUG_LOADTIME now, void **plugin_handle); + int (*pfnUnloadPlugin)(plid_t plid, const char *cmdline, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + int (*pfnUnloadPluginByHandle)(plid_t plid, void *plugin_handle, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + + const char *(*pfnIsQueryingClientCvar) (plid_t plid, const edict_t *player); + + int (*pfnMakeRequestID) (plid_t plid); + + void (*pfnGetHookTables) (plid_t plid, enginefuncs_t **peng, DLL_FUNCTIONS **pdll, NEW_DLL_FUNCTIONS **pnewdll); +} mutil_funcs_t; +extern mutil_funcs_t MetaUtilFunctions DLLHIDDEN; + +// Convenience macros for MetaUtil functions +#define LOG_CONSOLE (*gpMetaUtilFuncs->pfnLogConsole) +#define LOG_MESSAGE (*gpMetaUtilFuncs->pfnLogMessage) +#define LOG_ERROR (*gpMetaUtilFuncs->pfnLogError) +#define LOG_DEVELOPER (*gpMetaUtilFuncs->pfnLogDeveloper) +#define CENTER_SAY (*gpMetaUtilFuncs->pfnCenterSay) +#define CENTER_SAY_PARMS (*gpMetaUtilFuncs->pfnCenterSayParms) +#define CENTER_SAY_VARARGS (*gpMetaUtilFuncs->pfnCenterSayVarargs) +#define CALL_GAME_ENTITY (*gpMetaUtilFuncs->pfnCallGameEntity) +#define GET_USER_MSG_ID (*gpMetaUtilFuncs->pfnGetUserMsgID) +#define GET_USER_MSG_NAME (*gpMetaUtilFuncs->pfnGetUserMsgName) +#define GET_PLUGIN_PATH (*gpMetaUtilFuncs->pfnGetPluginPath) +#define GET_GAME_INFO (*gpMetaUtilFuncs->pfnGetGameInfo) +#define LOAD_PLUGIN (*gpMetaUtilFuncs->pfnLoadPlugin) +#define UNLOAD_PLUGIN (*gpMetaUtilFuncs->pfnUnloadPlugin) +#define UNLOAD_PLUGIN_BY_HANDLE (*gpMetaUtilFuncs->pfnUnloadPluginByHandle) +#define IS_QUERYING_CLIENT_CVAR (*gpMetaUtilFuncs->pfnIsQueryingClientCvar) +#define MAKE_REQUESTID (*gpMetaUtilFuncs->pfnMakeRequestID) +#define GET_HOOK_TABLES (*gpMetaUtilFuncs->pfnGetHookTables) + +#endif /* MUTIL_H */ diff --git a/src/metamod/new_baseclass.h b/src/metamod/new_baseclass.h new file mode 100644 index 0000000..28707fc --- /dev/null +++ b/src/metamod/new_baseclass.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ +#ifndef METAMOD_NEW_BASECLASS_H +#define METAMOD_NEW_BASECLASS_H + +#include + +#include "comp_dep.h" + +//new/delete operators with malloc/free to remove need for libstdc++ + +class class_metamod_new { +public: + // Construction + class_metamod_new(void) { }; + + // Operators + inline void * operator new(size_t size) { + if(size==0) + return(calloc(1, 1)); + return(calloc(1, size)); + } + + inline void * operator new[](size_t size) { + if(size==0) + return(calloc(1, 1)); + return(calloc(1, size)); + } + + inline void operator delete(void *ptr) { + if(ptr) + free(ptr); + } + + inline void operator delete[](void *ptr) { + if(ptr) + free(ptr); + } +}; + +#endif /*METAMOD_NEW_BASECLASS_H*/ diff --git a/src/metamod/osdep.cpp b/src/metamod/osdep.cpp new file mode 100644 index 0000000..1f0ade2 --- /dev/null +++ b/src/metamod/osdep.cpp @@ -0,0 +1,399 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// osdep.cpp - routines for operating system differences + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifdef linux +// enable extra routines in system header files, like dladdr +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +#include // dlopen, dladdr, etc +#endif /* linux */ + +#include // strpbrk, etc + +#include // always + +#include "osdep.h" // me +#include "mreg.h" // RG_VALID, etc +#include "log_meta.h" // META_ERROR, etc +#include "types_meta.h" // mBOOL +#include "support_meta.h" // MAX_STRBUF_LEN +#include "limits.h" // INT_MAX + + +mBOOL dlclose_handle_invalid; + +#ifdef _WIN32 +// Since windows doesn't provide a verison of strtok_r(), we include one +// here. This may or may not operate exactly like strtok_r(), but does +// what we need it it do. +char * DLLINTERNAL my_strtok_r(char *s, const char *delim, char **ptrptr) { + char *begin=NULL; + char *end=NULL; + char *rest=NULL; + if(s) + begin=s; + else + begin=*ptrptr; + if(!begin) + return(NULL); + end=strpbrk(begin, delim); + if(end) { + *end='\0'; + rest=end+1; + *ptrptr=rest+strspn(rest, delim); + } + else + *ptrptr=NULL; + return(begin); +} +#endif /* _WIN32 */ + + +#ifdef linux +char * DLLINTERNAL my_strlwr(char *s) { + char *c; + if(!s) + return(0); + for(c=s;*c;c++) + *c = tolower(*c); + return(s); +} +#endif + + +#ifndef DO_NOT_FIX_VARARG_ENGINE_API_WARPERS +// Microsoft's msvcrt.dll:vsnprintf is buggy and so is vsnprintf on some glibc versions. +// We use wrapper function to fix bugs. +// from: http://sourceforge.net/tracker/index.php?func=detail&aid=1083721&group_id=2435&atid=102435 +int DLLINTERNAL safe_vsnprintf(char* s, size_t n, const char *format, va_list src_ap) { + va_list ap; + int res; + char *tmpbuf; + size_t bufsize = n; + + if(s && n>0) + s[0]=0; + + // If the format string is empty, nothing to do. + if(!format || !*format) + return(0); + + // The supplied count may be big enough. Try to use the library + // vsnprintf, fixing up the case where the library function + // neglects to terminate with '/0'. + if(n > 0) + { + // A NULL destination will cause a segfault with vsnprintf. + // if n > 0. Nor do we want to copy our tmpbuf to NULL later. + if(!s) + return(-1); + + va_copy(ap, src_ap); + res = vsnprintf(s, n, format, ap); + va_end(ap); + + if(res > 0) { + if((unsigned)res == n) + s[res - 1] = 0; + return(res); + } + + // If n is already larger than INT_MAX, increasing it won't + // help. + if(n > INT_MAX) + return(-1); + + // Try a larger buffer. + bufsize *= 2; + } + + if(bufsize < 1024) + bufsize = 1024; + + tmpbuf = (char *)malloc(bufsize * sizeof(char)); + if(!tmpbuf) + return(-1); + + va_copy(ap, src_ap); + res = vsnprintf(tmpbuf, bufsize, format, ap); + va_end(ap); + + // The test for bufsize limit is probably not necesary + // with 2GB address space limit, since, in practice, malloc will + // fail well before INT_MAX. + while(res < 0 && bufsize <= INT_MAX) { + char * newbuf; + + bufsize *= 2; + newbuf = (char*)realloc(tmpbuf, bufsize * sizeof(char)); + + if(!newbuf) + break; + + tmpbuf = newbuf; + + va_copy(ap, src_ap); + res = vsnprintf(tmpbuf, bufsize, format, ap); + va_end(ap); + } + + if(res > 0 && n > 0) { + if(n > (unsigned)res) + memcpy(s, tmpbuf, (res + 1) * sizeof (char)); + else { + memcpy(s, tmpbuf, (n - 1) * sizeof (char)); + s[n - 1] = 0; + } + } + + free(tmpbuf); + return(res); +} + +int DLLINTERNAL safe_snprintf(char* s, size_t n, const char* format, ...) { + int res; + va_list ap; + + va_start(ap, format); + res = safe_vsnprintf(s, n, format, ap); + va_end(ap); + + return(res); +} +#endif + +void DLLINTERNAL safevoid_vsnprintf(char* s, size_t n, const char *format, va_list ap) { + int res; + + if(!s || n <= 0) + return; + + // If the format string is empty, nothing to do. + if(!format || !*format) { + s[0]=0; + return; + } + + res = vsnprintf(s, n, format, ap); + + // w32api returns -1 on too long write, glibc returns number of bytes it could have written if there were enough space + // w32api doesn't write null at all, some buggy glibc don't either + if(res < 0 || (size_t)res >= n) + s[n-1]=0; +} + +void DLLINTERNAL safevoid_snprintf(char* s, size_t n, const char* format, ...) { + va_list ap; + + va_start(ap, format); + safevoid_vsnprintf(s, n, format, ap); + va_end(ap); +} + + +#ifdef _WIN32 +// Windows doesn't provide a functon analagous to dlerr() that returns a +// string describing the error, so we include one here, as exampled at: +// http://msdn.microsoft.com/library/en-us/debug/errors_0sdh.asp +// except without FORMAT_MESSAGE_ALLOCATE_BUFFER, since we use a local +// static buffer. +char * DLLINTERNAL str_GetLastError(void) { + static char buf[MAX_STRBUF_LEN]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //! Default language + (LPTSTR) &buf, MAX_STRBUF_LEN-1, NULL); + return(buf); +} +#endif /* _WIN32 */ + + +// Find the filename of the DLL/shared-lib where the given memory location +// exists. +#ifdef linux +// Errno values: +// - ME_NOTFOUND couldn't find a sharedlib that contains memory location +const char * DLLINTERNAL DLFNAME(void *memptr) { + Dl_info dli; + memset(&dli, 0, sizeof(dli)); + if(dladdr(memptr, &dli)) + return(dli.dli_fname); + else + RETURN_ERRNO(NULL, ME_NOTFOUND); +} +#elif defined(_WIN32) +// Implementation for win32 provided by Jussi Kivilinna : +// +// 1. Get memory location info on memptr with VirtualQuery. +// 2. Check if memory location info is valid and use MBI.AllocationBase +// as module start point. +// 3. Get module file name with GetModuleFileName. +// +// Simple and should work pretty much same way as 'dladdr' in linux. +// VirtualQuery and GetModuleFileName work even with win32s. +// +// Note that GetModuleFileName returns longfilenames rather than 8.3. +// +// Note also, the returned filename is local static storage, and should be +// copied by caller if it needs to keep it around. +// +// Also note, normalize_pathname() should really be done by the caller, but +// is done here to preserve "const char *" return consistent with linux +// version. +// +// Errno values: +// - ME_NOTFOUND couldn't find a DLL that contains memory location +const char * DLLINTERNAL DLFNAME(void *memptr) { + MEMORY_BASIC_INFORMATION MBI; + static char fname[PATH_MAX]; + + memset(fname, 0, sizeof(fname)); + + if(!VirtualQuery(memptr, &MBI, sizeof(MBI))) + RETURN_ERRNO(NULL, ME_NOTFOUND); + if(MBI.State != MEM_COMMIT) + RETURN_ERRNO(NULL, ME_NOTFOUND); + if(!MBI.AllocationBase) + RETURN_ERRNO(NULL, ME_NOTFOUND); + + // MSDN indicates that GetModuleFileName will leave string + // null-terminated, even if it's truncated because buffer is too small. + if(!GetModuleFileNameA((HMODULE)MBI.AllocationBase, fname, sizeof(fname)-1)) + RETURN_ERRNO(NULL, ME_NOTFOUND); + if(!fname[0]) + RETURN_ERRNO(NULL, ME_NOTFOUND); + + normalize_pathname(fname); + return(fname); +} +#endif /* _WIN32 */ + + +#ifdef _WIN32 +// Normalize/standardize a pathname. +// - For win32, this involves: +// - Turning backslashes (\) into slashes (/), so that config files and +// Metamod internal code can be simpler and just use slashes (/). +// - Turning upper/mixed case into lowercase, since windows is +// non-case-sensitive. +// - For linux, this requires no work, as paths uses slashes (/) natively, +// and pathnames are case-sensitive. +void DLLINTERNAL normalize_pathname(char *path) { + char *cp; + + META_DEBUG(8, ("normalize: %s", path)); + for(cp=path; *cp; cp++) { + /*if(isupper(*cp))*/ + *cp=tolower(*cp); + + if(*cp=='\\') + *cp='/'; + } + META_DEBUG(8, ("normalized: %s", path)); +} + +// Buffer pointed to by resolved_name is assumed to be able to store a +// string of PATH_MAX length. +char * DLLINTERNAL realpath(const char *file_name, char *resolved_name) { + int ret; + ret=GetFullPathNameA(file_name, PATH_MAX, resolved_name, NULL); + if(ret > PATH_MAX) { + errno=ENAMETOOLONG; + return(NULL); + } + else if(ret > 0) { + HANDLE handle; + WIN32_FIND_DATAA find_data; + handle=FindFirstFileA(resolved_name, &find_data); + if(INVALID_HANDLE_VALUE == handle) { + errno=ENOENT; + return(NULL); + } + FindClose(handle); + normalize_pathname(resolved_name); + return(resolved_name); + } + else + return(NULL); +} +#endif /*_WIN32*/ + + +// Determine whether the given memory location is valid (ie whether we +// should expect to be able to reference strings or functions at this +// location without segfaulting). +#ifdef linux +// Simulate this with dladdr. I'm not convinced this will be as generally +// applicable as the native windows routine below, but it should do what +// we need it for in this particular situation. +// meta_errno values: +// - ME_NOTFOUND couldn't find a matching sharedlib for this ptr +mBOOL DLLINTERNAL IS_VALID_PTR(void *memptr) { + Dl_info dli; + memset(&dli, 0, sizeof(dli)); + if(dladdr(memptr, &dli)) + return(mTRUE); + else + RETURN_ERRNO(mFALSE, ME_NOTFOUND); +} +#elif defined(_WIN32) +// Use the native windows routine IsBadCodePtr. +// meta_errno values: +// - ME_BADMEMPTR not a valid memory pointer +mBOOL DLLINTERNAL IS_VALID_PTR(void *memptr) { + if(IsBadCodePtr((FARPROC) memptr)) + RETURN_ERRNO(mFALSE, ME_BADMEMPTR); + else + return(mTRUE); +} +#endif /* _WIN32 */ + +// This used to be OS-dependent, as it used a SEGV signal handler under +// linux, but that was removed because (a) it masked legitimate segfaults +// in plugin commands and produced confusing output ("plugin has been +// unloaded", when really it segfaultd), and (b) wasn't necessary since +// IS_VALID_PTR() should cover the situation. +mBOOL DLLINTERNAL os_safe_call(REG_CMD_FN pfn) { + // try and see if this is a valid memory location + if(!IS_VALID_PTR((void *) pfn)) + // meta_errno should be already set in is_valid_ptr() + return(mFALSE); + + pfn(); + return(mTRUE); +} + diff --git a/src/metamod/osdep.h b/src/metamod/osdep.h new file mode 100644 index 0000000..ac7c5b7 --- /dev/null +++ b/src/metamod/osdep.h @@ -0,0 +1,307 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// osdep.h - operating system dependencies + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef OSDEP_H +#define OSDEP_H + +#include // strerror() +#include // isupper, tolower +#include // errno + +// Various differences between WIN32 and Linux. + +#include "comp_dep.h" +#include "types_meta.h" // mBOOL +#include "mreg.h" // REG_CMD_FN, etc +#include "log_meta.h" // LOG_ERROR, etc + +// String describing platform/DLL-type, for matching lines in plugins.ini. +#ifdef linux + #define PLATFORM "linux" +# if defined(__x86_64__) || defined(__amd64__) + #define PLATFORM_SPC "lin64" +# else + #define PLATFORM_SPC "lin32" +# endif + #define PLATFORM_DLEXT ".so" +#elif defined(_WIN32) + #define PLATFORM "mswin" + #define PLATFORM_SPC "win32" + #define PLATFORM_DLEXT ".dll" +#else /* unknown */ + #error "OS unrecognized" +#endif /* unknown */ + +// Macro for function-exporting from DLL.. +// from SDK dlls/cbase.h: +//! C functions for external declarations that call the appropriate C++ methods + +// Windows uses "__declspec(dllexport)" to mark functions in the DLL that +// should be visible/callable externally. +// +// It also apparently requires WINAPI for GiveFnptrsToDll(). +// +// See doc/notes_windows_coding for more information.. + +// Attributes to specify an "exported" function, visible from outside the +// DLL. +#undef DLLEXPORT +#ifdef _WIN32 + #define DLLEXPORT __declspec(dllexport) __attribute__ ((externally_visible)) + // WINAPI should be provided in the windows compiler headers. + // It's usually defined to something like "__stdcall". +#elif defined(linux) + #define DLLEXPORT __attribute__ ((visibility ("default"), externally_visible)) + #define WINAPI /* */ +#endif /* linux */ + +// Simplified macro for declaring/defining exported DLL functions. They +// need to be 'extern "C"' so that the C++ compiler enforces parameter +// type-matching, rather than considering routines with mis-matched +// arguments/types to be overloaded functions... +// +// AFAIK, this is os-independent, but it's included here in osdep.h where +// DLLEXPORT is defined, for convenience. +#define C_DLLEXPORT extern "C" DLLEXPORT + +// Special version that fixes vsnprintf bugs. +#ifndef DO_NOT_FIX_VARARG_ENGINE_API_WARPERS +int DLLINTERNAL safe_vsnprintf(char* s, size_t n, const char *format, va_list ap); +int DLLINTERNAL safe_snprintf(char* s, size_t n, const char* format, ...); +#endif +void DLLINTERNAL safevoid_vsnprintf(char* s, size_t n, const char *format, va_list ap); +void DLLINTERNAL safevoid_snprintf(char* s, size_t n, const char* format, ...); + +// Functions & types for DLL open/close/etc operations. +extern mBOOL dlclose_handle_invalid DLLHIDDEN; +#ifdef linux + #include + typedef void* DLHANDLE; + typedef void* DLFUNC; + inline DLHANDLE DLLINTERNAL DLOPEN(const char *filename) { + return(dlopen(filename, RTLD_NOW)); + } + inline DLFUNC DLLINTERNAL DLSYM(DLHANDLE handle, const char *string) { + return(dlsym(handle, string)); + } + //dlclose crashes if handle is null. + inline int DLLINTERNAL DLCLOSE(DLHANDLE handle) { + if(!handle) { + dlclose_handle_invalid = mTRUE; + return(1); + } + + dlclose_handle_invalid = mFALSE; + return(dlclose(handle)); + } + inline const char * DLLINTERNAL DLERROR(void) { + if(dlclose_handle_invalid) + return("Invalid handle."); + return(dlerror()); + } +#elif defined(_WIN32) + typedef HINSTANCE DLHANDLE; + typedef FARPROC DLFUNC; + inline DLHANDLE DLLINTERNAL DLOPEN(const char *filename) { + return(LoadLibraryA(filename)); + } + inline DLFUNC DLLINTERNAL DLSYM(DLHANDLE handle, const char *string) { + return(GetProcAddress(handle, string)); + } + inline int DLLINTERNAL DLCLOSE(DLHANDLE handle) { + if(!handle) { + dlclose_handle_invalid = mTRUE; + return(1); + } + + dlclose_handle_invalid = mFALSE; + + // NOTE: Windows FreeLibrary returns success=nonzero, fail=zero, + // which is the opposite of the unix convention, thus the '!'. + return(!FreeLibrary(handle)); + } + // Windows doesn't provide a function corresponding to dlerror(), so + // we make our own. + char * DLLINTERNAL str_GetLastError(void); + inline const char * DLLINTERNAL DLERROR(void) { + if(dlclose_handle_invalid) + return("Invalid handle."); + return(str_GetLastError()); + } +#endif /* _WIN32 */ +const char * DLLINTERNAL DLFNAME(void *memptr); +mBOOL DLLINTERNAL IS_VALID_PTR(void *memptr); + + +// Attempt to call the given function pointer, without segfaulting. +mBOOL DLLINTERNAL os_safe_call(REG_CMD_FN pfn); + + +// Windows doesn't have an strtok_r() routine, so we write our own. +#ifdef _WIN32 + #define strtok_r(s, delim, ptrptr) my_strtok_r(s, delim, ptrptr) + char * DLLINTERNAL my_strtok_r(char *s, const char *delim, char **ptrptr); +#endif /* _WIN32 */ + + +// Linux doesn't have an strlwr() routine, so we write our own. +#ifdef linux + #define strlwr(s) my_strlwr(s) + char * DLLINTERNAL my_strlwr(char *s); +#endif /* _WIN32 */ + + +// Set filename and pathname maximum lengths. Note some windows compilers +// provide a which is incomplete and/or causes problems; see +// doc/windows_notes.txt for more information. +// +// Note that both OS's include room for null-termination: +// linux: "# chars in a path name including nul" +// win32: "note that the sizes include space for 0-terminator" +#ifdef linux + #include +#elif defined(_WIN32) + #include + #define NAME_MAX _MAX_FNAME + #ifndef PATH_MAX + #define PATH_MAX _MAX_PATH + #endif +#endif /* _WIN32 */ + +// Various other windows routine differences. +#ifdef linux + #include // sleep + #ifndef O_BINARY + #define O_BINARY 0 + #endif +#elif defined(_WIN32) + #include + #include + + #define sleep(x) Sleep(x*1000) + + // Fixed MSVC compiling, by Nikolay "The Storm" Baklicharov. + #if defined(__GNUC__) || defined (_MSC_VER) && _MSC_VER >= 1400 + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define unlink _unlink + #define strlwr _strlwr + #define strdup _strdup + #define strcasecmp _stricmp + #define strncasecmp _strnicmp + #define getcwd _getcwd + #define open _open + #define read _read + #define write _write + #define close _close + #endif /* GCC or MSVC 8.0+ */ +#endif /* _WIN32 */ + +#if !defined WIN32 && !defined _MSC_VER +#include // getcwd +#endif + +#include +#ifndef S_ISREG + // Linux gcc defines this; earlier mingw didn't, later mingw does; + // MSVC doesn't seem to. + #define S_ISREG(m) ((m) & S_IFREG) +#endif /* not S_ISREG */ +#ifdef _WIN32 + // The following two are defined in mingw but not in MSVC + #ifndef S_IRUSR + #define S_IRUSR _S_IREAD + #endif + #ifndef S_IWUSR + #define S_IWUSR _S_IWRITE + #endif + + // The following two are defined neither in mingw nor in MSVC + #ifndef S_IRGRP + #define S_IRGRP S_IRUSR + #endif + #ifndef S_IWGRP + #define S_IWGRP S_IWUSR + #endif +#endif /* _WIN32 */ + +// Normalize/standardize a pathname. +// - For win32, this involves: +// - Turning backslashes (\) into slashes (/), so that config files and +// Metamod internal code can be simpler and just use slashes (/). +// - Turning upper/mixed case into lowercase, since windows is +// non-case-sensitive. +// - For linux, this requires no work, as paths uses slashes (/) natively, +// and pathnames are case-sensitive. +#ifdef linux +#define normalize_pathname(a) +#elif defined(_WIN32) +void DLLINTERNAL normalize_pathname(char *path); +#endif /* _WIN32 */ + +// Indicate if pathname appears to be an absolute-path. Under linux this +// is a leading slash (/). Under win32, this can be: +// - a drive-letter path (ie "D:blah" or "C:\blah") +// - a toplevel path (ie "\blah") +// - a UNC network address (ie "\\srv1\blah"). +// Also, handle both native and normalized pathnames. +inline mBOOL DLLINTERNAL is_absolute_path(const char *path) { + if(path[0]=='/') return(mTRUE); +#ifdef _WIN32 + if(path[1]==':') return(mTRUE); + if(path[0]=='\\') return(mTRUE); +#endif /* _WIN32 */ + return(mFALSE); +} + +#ifdef _WIN32 +// Buffer pointed to by resolved_name is assumed to be able to store a +// string of PATH_MAX length. +char * DLLINTERNAL realpath(const char *file_name, char *resolved_name); +#endif /* _WIN32 */ + +// Generic "error string" from a recent OS call. For linux, this is based +// on errno. For win32, it's based on GetLastError. +inline const char * DLLINTERNAL str_os_error(void) { +#ifdef linux + return(strerror(errno)); +#elif defined(_WIN32) + return(str_GetLastError()); +#endif /* _WIN32 */ +} + + +#endif /* OSDEP_H */ diff --git a/src/metamod/osdep_detect_gamedll_linux.cpp b/src/metamod/osdep_detect_gamedll_linux.cpp new file mode 100644 index 0000000..a90bf95 --- /dev/null +++ b/src/metamod/osdep_detect_gamedll_linux.cpp @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +// enable extra routines in system header files, like dladdr +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +#include // dlopen, dladdr, etc +#include // sigaction, etc +#include // sigsetjmp, longjmp, etc +#include // mmap, munmap, mprotect, etc +#include +#include + +#include // always + +#include "osdep_p.h" // me +#include "support_meta.h" // STRNCPY + + +// On linux manually search for exports from dynamic library file. +// --Jussi Kivilinna +static jmp_buf signal_jmp_buf; + +// Signal handler for is_gamedll() +static void signal_handler_sigsegv(int) { + /* + * GLIBC 2.11+ intercept longjmp with __longjmp_chk. However we want + * binary compability with older versions of GLIBC. + */ +#ifdef __amd64__ + __asm__ volatile(".symver __longjmp_chk,longjmp@GLIBC_2.2.5" + : + :"r"(&signal_jmp_buf) + :"memory"); +#else + __asm__ volatile(".symver __longjmp_chk,longjmp@GLIBC_2.0" + : + :"r"(&signal_jmp_buf) + :"memory"); +#endif /*__amd64__*/ + longjmp(signal_jmp_buf, 1); +} + +#define invalid_elf_ptr(x) (((unsigned long)&(x)) > file_end - 1) +#define invalid_elf_offset(x) (((unsigned long)(x)) > filesize - 1) +#define elf_error_exit() \ + do { \ + sigaction(SIGSEGV, &oldaction, 0); \ + META_DEBUG(3, ("is_gamedll(%s): Invalid ELF.", filename)); \ + munmap(ehdr, filesize); \ + return(mFALSE); \ + } while(0) + +mBOOL DLLINTERNAL is_gamedll(const char *filename) { + // When these are not static there are some mysterious hidden bugs that I can't find/solve. + // So this is simple workaround. + static struct sigaction action; + static struct sigaction oldaction; + static ElfW(Ehdr) * ehdr = 0; + static ElfW(Shdr) * shdr = 0; + static ElfW(Sym) * symtab = 0; + static char * strtab = 0; + static FILE * pf = 0; + static unsigned long filesize = 0; + static unsigned long strtab_size = 0; + static unsigned long nsyms = 0; + static unsigned long i = 0; + static unsigned long file_end = 0; + static char * funcname = 0; + static int has_GiveFnptrsToDll = 0; + static int has_GetEntityAPI2 = 0; + static int has_GetEntityAPI = 0; + + ehdr = 0; + shdr = 0; + symtab = 0; + strtab = 0; + pf = 0; + filesize = 0; + file_end = 0; + strtab_size = 0; + nsyms = 0; + i = 0; + funcname = 0; + has_GiveFnptrsToDll = 0; + has_GetEntityAPI2 = 0; + has_GetEntityAPI = 0; + + // Try open file and get filesize + if((pf = fopen(filename, "rb"))) { + fseek(pf, 0, SEEK_END); + filesize = ftell(pf); + fseek(pf, 0, SEEK_SET); + } else { + META_DEBUG(3, ("is_gamedll(%s): Failed, cannot fopen() file.", filename)); + + return(mFALSE); + } + + // Check that filesize is atleast size of ELF header! + if(filesize < sizeof(ElfW(Ehdr))) { +#ifdef __x86_64__ + META_DEBUG(3, ("is_gamedll(%s): Failed, file is too small to be ELF64. [%i < %i]", filename, filesize, sizeof(ElfW(Ehdr)))); +#else + META_DEBUG(3, ("is_gamedll(%s): Failed, file is too small to be ELF32. [%i < %i]", filename, filesize, sizeof(ElfW(Ehdr)))); +#endif + fclose(pf); + + return(mFALSE); + } + + // mmap library for easy reading + ehdr = (ElfW(Ehdr) *)mmap(0, filesize, PROT_READ|PROT_WRITE, MAP_PRIVATE, fileno(pf), 0); + file_end = (unsigned long)ehdr + filesize; + + // not needed anymore + fclose(pf); + + // check if mmap was successful + if(!ehdr || (void*)ehdr==(void*)-1) { + META_DEBUG(3, ("is_gamedll(%s): Failed, mmap() [0x%x]", filename, ehdr)); + + return(mFALSE); + } + + //In case that ELF file is incomplete (because bad upload etc), we protect memory-mapping access with signal-handler + if(!setjmp(signal_jmp_buf)) { + memset(&action, 0, sizeof(struct sigaction)); + memset(&oldaction, 0, sizeof(struct sigaction)); + + // Not returning from signal, set SIGSEGV handler. + action.sa_handler = signal_handler_sigsegv; + action.sa_flags = SA_RESETHAND | SA_NODEFER; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, &oldaction); + } else { + // Reset signal handler. + sigaction(SIGSEGV, &oldaction, 0); + + META_DEBUG(3, ("is_gamedll(%s): Failed, signal SIGSEGV.", filename)); + + munmap(ehdr, filesize); + + return(mFALSE); + } + + if(mm_strncmp((char *)ehdr, ELFMAG, SELFMAG) != 0 || ehdr->e_ident[EI_VERSION] != EV_CURRENT) { + // Reset signal handler. + sigaction(SIGSEGV, &oldaction, 0); + + META_DEBUG(3, ("is_gamedll(%s): Failed, file isn't ELF (%02x%02x%02x%02x:%x).", filename, + ((char *)ehdr)[0], ((char *)ehdr)[1], ((char *)ehdr)[2], ((char *)ehdr)[3], ehdr->e_ident[EI_VERSION])); + + munmap(ehdr, filesize); + + return(mFALSE); + } + +#ifdef __x86_64__ + // check if x86_64-shared-library + if(ehdr->e_ident[EI_CLASS] != ELFCLASS64 || ehdr->e_type != ET_DYN || ehdr->e_machine != EM_X86_64) { + // Reset signal handler. + sigaction(SIGSEGV, &oldaction, 0); + + META_DEBUG(3, ("is_gamedll(%s): Failed, ELF isn't for target:x86_64. [%x:%x:%x]", filename, + ehdr->e_ident[EI_CLASS], ehdr->e_type, ehdr->e_machine)); + + munmap(ehdr, filesize); + + return(mFALSE); + } +#else + // check if x86-shared-library + if(ehdr->e_ident[EI_CLASS] != ELFCLASS32 || ehdr->e_type != ET_DYN || ehdr->e_machine != EM_386) { + // Reset signal handler. + sigaction(SIGSEGV, &oldaction, 0); + + META_DEBUG(3, ("is_gamedll(%s): Failed, ELF isn't for target:i386. [%x:%x:%x]", filename, + ehdr->e_ident[EI_CLASS], ehdr->e_type, ehdr->e_machine)); + + munmap(ehdr, filesize); + + return(mFALSE); + } +#endif + + //Get symtab and strtab info + shdr = (ElfW(Shdr) *)((char *)ehdr + ehdr->e_shoff); + if(invalid_elf_ptr(shdr[ehdr->e_shnum])) + elf_error_exit(); + + for(i = 0; i < ehdr->e_shnum; i++) { + // searching for dynamic linker symbol table + if(shdr[i].sh_type == SHT_DYNSYM) { + if(invalid_elf_offset(shdr[i].sh_offset) || + invalid_elf_ptr(shdr[shdr[i].sh_link]) || + invalid_elf_offset(shdr[shdr[i].sh_link].sh_offset) || + invalid_elf_ptr(strtab[strtab_size]) || + invalid_elf_ptr(symtab[nsyms])) + elf_error_exit(); + + symtab = (ElfW(Sym) *)((char *)ehdr + shdr[i].sh_offset); + strtab = (char *)((char *)ehdr + shdr[shdr[i].sh_link].sh_offset); + strtab_size = shdr[shdr[i].sh_link].sh_size; + nsyms = shdr[i].sh_size / shdr[i].sh_entsize; + + break; + } + } + + if(!symtab) { + //Another method for finding symtab + for(i = 0; i < ehdr->e_shnum; i++) { + if(shdr[i].sh_type == SHT_SYMTAB) { + if(invalid_elf_offset(shdr[i].sh_offset) || + invalid_elf_ptr(shdr[shdr[i].sh_link]) || + invalid_elf_offset(shdr[shdr[i].sh_link].sh_offset) || + invalid_elf_ptr(strtab[strtab_size]) || + invalid_elf_ptr(symtab[nsyms])) + elf_error_exit(); + + symtab = (ElfW(Sym) *)((char *)ehdr + shdr[i].sh_offset); + strtab = (char *)((char *)ehdr + shdr[shdr[i].sh_link].sh_offset); + strtab_size = shdr[shdr[i].sh_link].sh_size; + nsyms = shdr[i].sh_size / shdr[i].sh_entsize; + + break; + } + } + } + + if(!symtab) { + // Reset signal handler. + sigaction(SIGSEGV, &oldaction, 0); + + META_DEBUG(3, ("is_gamedll(%s): Failed, couldn't locate symtab.", filename)); + + munmap(ehdr, filesize); + + return(mFALSE); + } + + //Search symbols for exports + for(i = 0; i < nsyms; i++) { +#ifdef __x86_64__ + // Export? + if(ELF64_ST_TYPE(symtab[i].st_info) != STT_FUNC || ELF64_ST_BIND(symtab[i].st_info) != STB_GLOBAL) + continue; +#else + // Export? + if(ELF32_ST_TYPE(symtab[i].st_info) != STT_FUNC || ELF32_ST_BIND(symtab[i].st_info) != STB_GLOBAL) + continue; +#endif + + // string outside strtab? + if(symtab[i].st_name <= 0 || symtab[i].st_name >= strtab_size) + continue; + + funcname = &strtab[symtab[i].st_name]; + + // Check + // Fast check for 'G' first + if(funcname[0] == 'G') { + // Collect export information + if(!has_GiveFnptrsToDll) + has_GiveFnptrsToDll = strmatch(funcname, "GiveFnptrsToDll"); + if(!has_GetEntityAPI2) + has_GetEntityAPI2 = strmatch(funcname, "GetEntityAPI2"); + if(!has_GetEntityAPI) + has_GetEntityAPI = strmatch(funcname, "GetEntityAPI"); + } + // Check if metamod plugin + else if(funcname[0] == 'M') { + if(strmatch(funcname, "Meta_Init") || + strmatch(funcname, "Meta_Query") || + strmatch(funcname, "Meta_Attach") || + strmatch(funcname, "Meta_Detach")) { + // Metamod plugin.. is not gamedll + META_DEBUG(5, ("is_gamedll(%s): Detected Metamod plugin, library exports [%s].", filename, funcname)); + + // Reset signal handler. + sigaction(SIGSEGV, &oldaction, 0); + + munmap(ehdr, filesize); + + return(mFALSE); + } + } + } + + // Check if gamedll + if(has_GiveFnptrsToDll && (has_GetEntityAPI2 || has_GetEntityAPI)) { + // This is gamedll! + META_DEBUG(5, ("is_gamedll(%s): Detected GameDLL.", filename)); + + // Reset signal handler. + sigaction(SIGSEGV, &oldaction, 0); + + munmap(ehdr, filesize); + + return(mTRUE); + } else { + META_DEBUG(5, ("is_gamedll(%s): Library isn't GameDLL.", filename)); + } + + // Reset signal handler. + sigaction(SIGSEGV, &oldaction, 0); + + munmap(ehdr, filesize); + + return(mFALSE); +} diff --git a/src/metamod/osdep_detect_gamedll_win32.cpp b/src/metamod/osdep_detect_gamedll_win32.cpp new file mode 100644 index 0000000..59e6e02 --- /dev/null +++ b/src/metamod/osdep_detect_gamedll_win32.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // always + +#include "osdep_p.h" // me +#include "support_meta.h" // STRNCPY + +//check for invalid handle values +#define is_invalid_handle(X) ((X)==0 || (X)==INVALID_HANDLE_VALUE) + +//relative virtual address to virtual address +#define rva_to_va(base, rva) ((unsigned long)base + (unsigned long)rva) +//virtual address to relative virtual address +#define va_to_rva(base, va) ((unsigned long)va - (unsigned long)base) + +// +static unsigned long DLLINTERNAL_NOVIS va_to_mapaddr(void * mapview, IMAGE_SECTION_HEADER * sections, int num_sects, unsigned long vaddr) { + for(int i = 0; i < num_sects; i++) + if(vaddr >= sections[i].VirtualAddress && vaddr < sections[i].VirtualAddress + sections[i].SizeOfRawData) + return(rva_to_va(mapview, (vaddr - sections[i].VirtualAddress + sections[i].PointerToRawData))); + + return(0); +} + +// Checks module signatures and return ntheaders pointer for valid module +static IMAGE_NT_HEADERS * DLLINTERNAL_NOVIS get_ntheaders(void * mapview) { + union { + unsigned long mem; + IMAGE_DOS_HEADER * dos; + IMAGE_NT_HEADERS * pe; + } mem; + + //Check if valid dos header + mem.mem = (unsigned long)mapview; + if(IsBadReadPtr(mem.dos, sizeof(*mem.dos)) || mem.dos->e_magic != IMAGE_DOS_SIGNATURE) + return(0); + + //Get and check pe header + mem.mem = rva_to_va(mapview, mem.dos->e_lfanew); + if(IsBadReadPtr(mem.pe, sizeof(*mem.pe)) || mem.pe->Signature != IMAGE_NT_SIGNATURE) + return(0); + + return(mem.pe); +} + +// Returns export table for valid module +static IMAGE_EXPORT_DIRECTORY * DLLINTERNAL_NOVIS get_export_table(void * mapview, IMAGE_NT_HEADERS * ntheaders, IMAGE_SECTION_HEADER * sections, int num_sects) { + union { + unsigned long mem; + void * pvoid; + IMAGE_DOS_HEADER * dos; + IMAGE_NT_HEADERS * pe; + IMAGE_EXPORT_DIRECTORY * export_dir; + } mem; + + mem.pe = ntheaders; + + //Check for exports + if(!mem.pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) + return(0); + + mem.mem = va_to_mapaddr(mapview, sections, num_sects, mem.pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + if(IsBadReadPtr(mem.export_dir, sizeof(*mem.export_dir))) + return(0); + + return(mem.export_dir); +} + +mBOOL DLLINTERNAL is_gamedll(const char *filename) { + HANDLE hFile; + HANDLE hMap; + void * mapview; + IMAGE_NT_HEADERS * ntheaders; + IMAGE_SECTION_HEADER * sections; + int num_sects; + IMAGE_EXPORT_DIRECTORY * exports; + + int has_GiveFnptrsToDll = 0; + int has_GetEntityAPI2 = 0; + int has_GetEntityAPI = 0; + + // Try open file for read + hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if(is_invalid_handle(hFile)) { + META_DEBUG(3, ("is_gamedll(%s): CreateFile() failed.", filename)); + return(mFALSE); + } + + // + hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if(is_invalid_handle(hMap)) { + META_DEBUG(3, ("is_gamedll(%s): CreateFileMapping() failed.", filename)); + CloseHandle(hFile); + return(mFALSE); + } + + // + mapview = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); + if(!mapview) { + META_DEBUG(3, ("is_gamedll(%s): MapViewOfFile() failed.", filename)); + CloseHandle(hMap); + CloseHandle(hFile); + return(mFALSE); + } + + ntheaders = get_ntheaders(mapview); + if(!ntheaders) { + META_DEBUG(3, ("is_gamedll(%s): get_ntheaders() failed.", filename)); + UnmapViewOfFile(mapview); + CloseHandle(hMap); + CloseHandle(hFile); + return(mFALSE); + } + + //Sections for va_to_mapaddr function + sections = IMAGE_FIRST_SECTION(ntheaders); + num_sects = ntheaders->FileHeader.NumberOfSections; + if(IsBadReadPtr(sections, num_sects * sizeof(IMAGE_SECTION_HEADER))) { + META_DEBUG(3, ("is_gamedll(%s): IMAGE_FIRST_SECTION() failed.", filename)); + UnmapViewOfFile(mapview); + CloseHandle(hMap); + CloseHandle(hFile); + return(mFALSE); + } + + // + exports = get_export_table(mapview, ntheaders, sections, num_sects); + if(!exports) { + META_DEBUG(3, ("is_gamedll(%s): get_export_table() failed.", filename)); + UnmapViewOfFile(mapview); + CloseHandle(hMap); + CloseHandle(hFile); + return(mFALSE); + } + + // + unsigned long * names = (unsigned long *)va_to_mapaddr(mapview, sections, num_sects, exports->AddressOfNames); + if(IsBadReadPtr(names, exports->NumberOfNames * sizeof(unsigned long))) { + META_DEBUG(3, ("is_gamedll(%s): Pointer to exported function names is invalid.", filename)); + UnmapViewOfFile(mapview); + CloseHandle(hMap); + CloseHandle(hFile); + return(mFALSE); + } + + for(unsigned int i = 0; i < exports->NumberOfNames; i++) { + //get function name with valid address + char * funcname = (char *)va_to_mapaddr(mapview, sections, num_sects, names[i]); + if(IsBadStringPtrA(funcname, 128)) + continue; + + // Check + // Fast check for 'G' first + if(funcname[0] == 'G') { + // Collect export information + if(!has_GiveFnptrsToDll) + has_GiveFnptrsToDll = strmatch(funcname, "GiveFnptrsToDll"); + if(!has_GetEntityAPI2) + has_GetEntityAPI2 = strmatch(funcname, "GetEntityAPI2"); + if(!has_GetEntityAPI) + has_GetEntityAPI = strmatch(funcname, "GetEntityAPI"); + } + // Check if metamod plugin + else if(funcname[0] == 'M') { + if(strmatch(funcname, "Meta_Init") || + strmatch(funcname, "Meta_Query") || + strmatch(funcname, "Meta_Attach") || + strmatch(funcname, "Meta_Detach")) { + // Metamod plugin.. is not gamedll + META_DEBUG(5, ("is_gamedll(%s): Detected Metamod plugin, library exports [%s].", filename, funcname)); + + UnmapViewOfFile(mapview); + CloseHandle(hMap); + CloseHandle(hFile); + + return(mFALSE); + } + } + } + + UnmapViewOfFile(mapview); + CloseHandle(hMap); + CloseHandle(hFile); + + // Check if gamedll + if(has_GiveFnptrsToDll && (has_GetEntityAPI2 || has_GetEntityAPI)) { + // This is gamedll! + META_DEBUG(5, ("is_gamedll(%s): Detected GameDLL.", filename)); + + return(mTRUE); + } else { + META_DEBUG(5, ("is_gamedll(%s): Library isn't GameDLL.", filename)); + + return(mFALSE); + } +} diff --git a/src/metamod/osdep_linkent_linux.cpp b/src/metamod/osdep_linkent_linux.cpp new file mode 100644 index 0000000..328ba9c --- /dev/null +++ b/src/metamod/osdep_linkent_linux.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // always + +#ifndef __USE_GNU +#define __USE_GNU +#endif + +#include +#include +#define PAGE_SIZE 4096UL +#define PAGE_MASK (~(PAGE_SIZE-1)) +#define PAGE_ALIGN(addr) (((addr)+PAGE_SIZE-1)&PAGE_MASK) +#include +#include + +#include "osdep.h" +#include "osdep_p.h" +#include "log_meta.h" // META_LOG, etc +#include "support_meta.h" + +// +// Linux code for dynamic linkents +// -- by Jussi Kivilinna +// + +//opcode, e9, + sizeof pointer +#define BYTES_SIZE (1 + sizeof(void*)) + +typedef void * (*dlsym_func)(void * module, const char * funcname); + +static void * gamedll_module_handle = 0; +static void * metamod_module_handle = 0; + +//pointer to original dlsym +static dlsym_func dlsym_original; + +//contains jmp to replacement_dlsym @dlsym_original +static unsigned char dlsym_new_bytes[BYTES_SIZE]; + +//contains original bytes of dlsym +static unsigned char dlsym_old_bytes[BYTES_SIZE]; + +//Mutex for our protection +static pthread_mutex_t mutex_replacement_dlsym = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + +//constructs new jmp forwarder +inline void construct_jmp_instruction(void *x, void *place, void* target) +{ + ((unsigned char *)x)[0] = 0xe9; + *(unsigned long *)((char *)x + 1) = (unsigned long)target - ((unsigned long)place + 5); +} + +//checks if pointer x points to jump forwarder +inline bool is_code_trampoline_jmp_opcode(void *x) +{ + return (((unsigned char *)x)[0] == 0xff || ((unsigned char *)x)[1] == 0x25); +} + +//extracts pointer from "jmp dword ptr[pointer]" +inline void * extract_function_pointer_from_trampoline_jmp(void *x) +{ + return (**(void***)((char *)(x) + 2)); +} + +// +//restores old dlsym +// +inline void DLLINTERNAL restore_original_dlsym(void) +{ + //Copy old dlsym bytes back + memcpy((void*)dlsym_original, dlsym_old_bytes, BYTES_SIZE); +} + +// +//resets new dlsym +// +inline void DLLINTERNAL reset_dlsym_hook(void) +{ + //Copy new dlsym bytes back + memcpy((void*)dlsym_original, dlsym_new_bytes, BYTES_SIZE); +} + +// +// Replacement dlsym function +// +static void * __replacement_dlsym(void * module, const char * funcname) +{ + //these are needed in case dlsym calls dlsym, default one doesn't do + //it but some LD_PRELOADed library that hooks dlsym might actually + //do so. + static int is_original_restored = 0; + int was_original_restored = is_original_restored; + + //Lock before modifing original dlsym + pthread_mutex_lock(&mutex_replacement_dlsym); + + //restore old dlsym + if(!is_original_restored) + { + restore_original_dlsym(); + + is_original_restored = 1; + } + + //check if we should hook this call + if(module != metamod_module_handle || !metamod_module_handle || !gamedll_module_handle) + { + //no metamod/gamedll module? should we remove hook now? + void * retval = dlsym_original(module, funcname); + + if(metamod_module_handle && gamedll_module_handle) + { + if(!was_original_restored) + { + //reset dlsym hook + reset_dlsym_hook(); + + is_original_restored = 0; + } + } + else + { + //no metamod/gamedll module? should we remove hook now by not reseting it back? + } + + //unlock + pthread_mutex_unlock(&mutex_replacement_dlsym); + + return(retval); + } + + //dlsym on metamod module + void * func = dlsym_original(module, funcname); + + if(!func) + { + //function not in metamod module, try gamedll + func = dlsym_original(gamedll_module_handle, funcname); + } + + if(!was_original_restored) + { + //reset dlsym hook + reset_dlsym_hook(); + + is_original_restored = 0; + } + + //unlock + pthread_mutex_unlock(&mutex_replacement_dlsym); + + return(func); +} + +// +// Initialize +// +int DLLINTERNAL init_linkent_replacement(DLHANDLE MetamodHandle, DLHANDLE GameDllHandle) +{ + metamod_module_handle = MetamodHandle; + gamedll_module_handle = GameDllHandle; + + // dlsym is already known to be pointing to valid function, we loaded gamedll using it earlier! + void * sym_ptr = (void*)&dlsym; + while(is_code_trampoline_jmp_opcode(sym_ptr)) { + sym_ptr = extract_function_pointer_from_trampoline_jmp(sym_ptr); + } + + dlsym_original = (dlsym_func)sym_ptr; + + //Backup old bytes of "dlsym" function + memcpy(dlsym_old_bytes, (void*)dlsym_original, BYTES_SIZE); + + //Construct new bytes: "jmp offset[replacement_sendto] @ sendto_original" + construct_jmp_instruction((void*)&dlsym_new_bytes[0], (void*)dlsym_original, (void*)&__replacement_dlsym); + + //Check if bytes overlap page border. + unsigned long start_of_page = PAGE_ALIGN((long)dlsym_original) - PAGE_SIZE; + unsigned long size_of_pages = 0; + + if((unsigned long)dlsym_original + BYTES_SIZE > PAGE_ALIGN((unsigned long)dlsym_original)) + { + //bytes are located on two pages + size_of_pages = PAGE_SIZE*2; + } + else + { + //bytes are located entirely on one page. + size_of_pages = PAGE_SIZE; + } + + //Remove PROT_READ restriction + if(mprotect((void*)start_of_page, size_of_pages, PROT_READ|PROT_WRITE|PROT_EXEC)) + { + META_ERROR("Couldn't initialize dynamic linkents, mprotect failed: %i. Exiting...", errno); + return(0); + } + + //Write our own jmp-forwarder on "dlsym" + reset_dlsym_hook(); + + //done + return(1); +} diff --git a/src/metamod/osdep_linkent_win32.cpp b/src/metamod/osdep_linkent_win32.cpp new file mode 100644 index 0000000..5c64aa4 --- /dev/null +++ b/src/metamod/osdep_linkent_win32.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // always +#include "osdep.h" + +#include "log_meta.h" // META_LOG, etc +#include "support_meta.h" + + +// +// Win32 code for dynamic linkents +// -- by Jussi Kivilinna +// + +// +// Reads metamod.dll and game.dll function export tables and combines theim to +// single table that replaces metamod.dll's original table. +// + +typedef struct sort_names_s { + unsigned long name; + unsigned short nameOrdinal; +} sort_names_t; + +//relative virtual address to virtual address +#define rva_to_va(base, rva) ((unsigned long)base + (unsigned long)rva) +//virtual address to relative virtual address +#define va_to_rva(base, va) ((unsigned long)va - (unsigned long)base) + +// +// Checks module signatures and return ntheaders pointer for valid module +// +static IMAGE_NT_HEADERS * DLLINTERNAL_NOVIS get_ntheaders(HMODULE module) +{ + union { + unsigned long mem; + IMAGE_DOS_HEADER * dos; + IMAGE_NT_HEADERS * pe; + } mem; + + //Check if valid dos header + mem.mem = (unsigned long)module; + if(IsBadReadPtr(mem.dos, sizeof(*mem.dos)) || mem.dos->e_magic != IMAGE_DOS_SIGNATURE) + return(0); + + //Get and check pe header + mem.mem = rva_to_va(module, mem.dos->e_lfanew); + if(IsBadReadPtr(mem.pe, sizeof(*mem.pe)) || mem.pe->Signature != IMAGE_NT_SIGNATURE) + return(0); + + return(mem.pe); +} + +// +// Returns export table for valid module +// +static IMAGE_EXPORT_DIRECTORY * DLLINTERNAL_NOVIS get_export_table(HMODULE module) +{ + union { + unsigned long mem; + void * pvoid; + IMAGE_DOS_HEADER * dos; + IMAGE_NT_HEADERS * pe; + IMAGE_EXPORT_DIRECTORY * export_dir; + } mem; + + //Check module + mem.pe = get_ntheaders(module); + if(!mem.pe) + return(0); + + //Check for exports + if(!mem.pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) + return(0); + + mem.mem = rva_to_va(module, mem.pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + if(IsBadReadPtr(mem.export_dir, sizeof(*mem.export_dir))) + return(0); + + return(mem.export_dir); +} + +// +// Sort function for qsort +// +static int sort_names_list(const sort_names_t * A, const sort_names_t * B) +{ + const char * str_A = (const char *)A->name; + const char * str_B = (const char *)B->name; + + return(mm_strcmp(str_A, str_B)); +} + +// +// Combines moduleMM and moduleGame export tables and replaces moduleMM table with new one +// +static int DLLINTERNAL_NOVIS combine_module_export_tables(HMODULE moduleMM, HMODULE moduleGame) +{ + IMAGE_EXPORT_DIRECTORY * exportMM; + IMAGE_EXPORT_DIRECTORY * exportGame; + + unsigned long newNumberOfFunctions; + unsigned long newNumberOfNames; + unsigned long * newFunctions; + unsigned long * newNames; + unsigned short * newNameOrdinals; + sort_names_t * newSort; + + unsigned long i; + unsigned long u; + unsigned long funcCount; + unsigned long nameCount; + unsigned long listFix; + + //Get export tables + exportMM = get_export_table(moduleMM); + exportGame = get_export_table(moduleGame); + if(!exportMM || !exportGame) + { + META_ERROR("Couldn't initialize dynamic linkents, exportMM: %i, exportGame: %i. Exiting...", exportMM, exportGame); + return(0); + } + + //setup new export table + newNumberOfFunctions = exportMM->NumberOfFunctions + exportGame->NumberOfFunctions; + newNumberOfNames = exportMM->NumberOfNames + exportGame->NumberOfNames; + + //alloc lists + *(void**)&newFunctions = calloc(1, newNumberOfFunctions * sizeof(*newFunctions)); + *(void**)&newSort = calloc(1, newNumberOfNames * sizeof(*newSort)); + + //copy moduleMM to new export + for(funcCount = 0; funcCount < exportMM->NumberOfFunctions; funcCount++) + newFunctions[funcCount] = rva_to_va(moduleMM, ((unsigned long*)rva_to_va(moduleMM, exportMM->AddressOfFunctions))[funcCount]); + for(nameCount = 0; nameCount < exportMM->NumberOfNames; nameCount++) + { + //fix name address + newSort[nameCount].name = rva_to_va(moduleMM, ((unsigned long*)rva_to_va(moduleMM, exportMM->AddressOfNames))[nameCount]); + //ordinal is index to function list + newSort[nameCount].nameOrdinal = ((unsigned short *)rva_to_va(moduleMM, exportMM->AddressOfNameOrdinals))[nameCount]; + } + + //copy moduleGame to new export + for(i = 0; i < exportGame->NumberOfFunctions; i++) + newFunctions[funcCount + i] = rva_to_va(moduleGame, ((unsigned long*)rva_to_va(moduleGame, exportGame->AddressOfFunctions))[i]); + for(i = 0, listFix = 0; i < exportGame->NumberOfNames; i++) + { + const char * name = (const char *)rva_to_va(moduleGame, ((unsigned long*)rva_to_va(moduleGame, exportGame->AddressOfNames))[i]); + //Check if name already in the list + for(u = 0; u < nameCount; u++) + { + if(!strcasecmp(name, (const char*)newSort[u].name)) + { + listFix -= 1; + break; + } + } + if(u < nameCount) //already in the list.. skip + continue; + + newSort[nameCount + i + listFix].name = (unsigned long)name; + newSort[nameCount + i + listFix].nameOrdinal = (unsigned short)funcCount + ((unsigned short *)rva_to_va(moduleGame, exportGame->AddressOfNameOrdinals))[i]; + } + + //set new number + newNumberOfNames = nameCount + i + listFix; + + //sort names list + qsort(newSort, newNumberOfNames, sizeof(*newSort), (int(*)(const void*, const void*))&sort_names_list); + + //make newNames and newNameOrdinals lists (VirtualAlloc so we dont waste heap memory to stuff that isn't freed) + *(void**)&newNames = VirtualAlloc(0, newNumberOfNames * sizeof(*newNames), MEM_COMMIT, PAGE_READWRITE); + *(void**)&newNameOrdinals = VirtualAlloc(0, newNumberOfNames * sizeof(*newNameOrdinals), MEM_COMMIT, PAGE_READWRITE); + + for(i = 0; i < newNumberOfNames; i++) + { + newNames[i] = newSort[i].name; + newNameOrdinals[i] = newSort[i].nameOrdinal; + } + + free(newSort); + + //translate VAs to RVAs + for(i = 0; i < newNumberOfFunctions; i++) + newFunctions[i] = va_to_rva(moduleMM, newFunctions[i]); + for(i = 0; i < newNumberOfNames; i++) + { + newNames[i] = va_to_rva(moduleMM, newNames[i]); + newNameOrdinals[i] = (unsigned short)va_to_rva(moduleMM, newNameOrdinals[i]); + } + + DWORD OldProtect; + if(!VirtualProtect(exportMM, sizeof(*exportMM), PAGE_READWRITE, &OldProtect)) + { + META_ERROR("Couldn't initialize dynamic linkents, VirtualProtect failed: %i. Exiting...", GetLastError()); + return(0); + } + + exportMM->Base = 1; + exportMM->NumberOfFunctions = newNumberOfFunctions; + exportMM->NumberOfNames = newNumberOfNames; + *(unsigned long*)&(exportMM->AddressOfFunctions) = va_to_rva(moduleMM, newFunctions); + *(unsigned long*)&(exportMM->AddressOfNames) = va_to_rva(moduleMM, newNames); + *(unsigned long*)&(exportMM->AddressOfNameOrdinals) = va_to_rva(moduleMM, newNameOrdinals); + + VirtualProtect(exportMM, sizeof(*exportMM), OldProtect, &OldProtect); + return(1); +} + +// +// ... +// +int DLLINTERNAL init_linkent_replacement(DLHANDLE moduleMetamod, DLHANDLE moduleGame) +{ + return(combine_module_export_tables(moduleMetamod, moduleGame)); +} diff --git a/src/metamod/osdep_p.cpp b/src/metamod/osdep_p.cpp new file mode 100644 index 0000000..9bbb5ae --- /dev/null +++ b/src/metamod/osdep_p.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifdef linux +// enable extra routines in system header files, like dladdr +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +#include // dlopen, dladdr, etc +#endif /* linux */ + +#include // always + +#include "osdep_p.h" // me +#include "support_meta.h" // STRNCPY + + +#ifdef _WIN32 +// MSVC doesn't provide "dirent.h" header. These functions wrap opendir/readdir/closedir +// functions to FindFirst/FindNext/FindClose win32api-functions. +DIR * DLLINTERNAL my_opendir(const char *path) +{ + char search_path[MAX_PATH]; + DIR *dir; + + // Add wildcards to path + safevoid_snprintf(search_path, sizeof(search_path), "%s\\*.*", path); + + // Get memory for new DIR object + dir = (DIR*)calloc(1, sizeof(DIR)); + + // Start searching + dir->handle = FindFirstFileA(search_path, &dir->find_data); + if(dir->handle == INVALID_HANDLE_VALUE) { + free(dir); + return(0); + } + + // Found file + dir->not_found = 0; + + return(dir); +} + +struct dirent * DLLINTERNAL my_readdir(DIR *dir) +{ + // If not found stop + if(!dir || dir->not_found) + return(0); + + // Set filename + STRNCPY(dir->ent.d_name, dir->find_data.cFileName, sizeof(dir->ent.d_name)); + + // Search next + dir->not_found = !FindNextFileA(dir->handle, &dir->find_data); + + return(&dir->ent); +} + +void DLLINTERNAL my_closedir(DIR *dir) +{ + if(!dir) + return; + + FindClose(dir->handle); + free(dir); +} +#endif /* _WIN32 */ + +//get module handle of memptr +#ifdef linux +DLHANDLE DLLINTERNAL get_module_handle_of_memptr(void * memptr) +{ + Dl_info dli; + memset(&dli, 0, sizeof(dli)); + + if(dladdr(memptr, &dli)) + return(dlopen(dli.dli_fname, RTLD_NOW)); + else + return((void*)0); +} +#else +DLHANDLE DLLINTERNAL get_module_handle_of_memptr(void * memptr) +{ + MEMORY_BASIC_INFORMATION MBI; + + if(!VirtualQuery(memptr, &MBI, sizeof(MBI))) + return(NULL); + if(MBI.State != MEM_COMMIT) + return(NULL); + if(!MBI.AllocationBase) + return(NULL); + + return((DLHANDLE)MBI.AllocationBase); +} +#endif /* linux */ diff --git a/src/metamod/osdep_p.h b/src/metamod/osdep_p.h new file mode 100644 index 0000000..e265d36 --- /dev/null +++ b/src/metamod/osdep_p.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef OSDEP_P_H +#define OSDEP_P_H + +#include "types_meta.h" // mBOOL +#include "osdep.h" // PATH_MAX + +// Checks if file is hlsdk api game dll +// (osdep_detect_gamedll_linux.cpp and osdep_detect_gamedll_win32.cpp) +// --Jussi Kivilinna +mBOOL DLLINTERNAL is_gamedll(const char *filename); + +// MSVC doesn't provide opendir/readdir/closedir, so we write our own. +// --Jussi Kivilinna +#ifdef _WIN32 + struct my_dirent { + char d_name[PATH_MAX]; + }; + typedef struct { + HANDLE handle; + WIN32_FIND_DATAA find_data; + struct my_dirent ent; + int not_found; + } my_DIR; + + #define dirent my_dirent + #define DIR my_DIR + + DIR * DLLINTERNAL my_opendir(const char *); + struct dirent * DLLINTERNAL my_readdir(DIR *); + void DLLINTERNAL my_closedir(DIR *); + + #define opendir(x) my_opendir(x) + #define readdir(x) my_readdir(x) + #define closedir(x) my_closedir(x) +#else + #include +#endif /* _WIN32 */ + +DLHANDLE DLLINTERNAL get_module_handle_of_memptr(void * memptr); + +#ifdef linux + void * DLLINTERNAL get_dlsym_pointer(void); +#endif + +#endif /* OSDEP_P_H */ diff --git a/src/metamod/plinfo.h b/src/metamod/plinfo.h new file mode 100644 index 0000000..1036db8 --- /dev/null +++ b/src/metamod/plinfo.h @@ -0,0 +1,85 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// plinfo.h - typedefs for plugin info structure + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef PLINFO_H +#define PLINFO_H + +#include "comp_dep.h" + +// Flags for plugin to indicate when it can be be loaded/unloaded. +// NOTE: order is crucial, as greater/less comparisons are made. +typedef enum { + PT_NEVER = 0, + PT_STARTUP, // should only be loaded/unloaded at initial hlds execution + PT_CHANGELEVEL, // can be loaded/unloaded between maps + PT_ANYTIME, // can be loaded/unloaded at any time + PT_ANYPAUSE, // can be loaded/unloaded at any time, and can be "paused" during a map +} PLUG_LOADTIME; + +// Flags to indicate why the plugin is being unloaded. +typedef enum { + PNL_NULL = 0, + PNL_INI_DELETED, // was deleted from plugins.ini + PNL_FILE_NEWER, // file on disk is newer than last load + PNL_COMMAND, // requested by server/console command + PNL_CMD_FORCED, // forced by server/console command + PNL_DELAYED, // delayed from previous request; can't tell origin +//only used for 'real_reason' on MPlugin::unload() + PNL_PLUGIN, // requested by plugin function call + PNL_PLG_FORCED, // forced by plugin function call +//only used internally for 'meta reload' + PNL_RELOAD, // forced unload by reload() +} PL_UNLOAD_REASON; + +// Information plugin provides about itself. +typedef struct { + char *ifvers; // meta_interface version + char *name; // full name of plugin + char *version; // version + char *date; // date + char *author; // author name/email + char *url; // URL + char *logtag; // log message prefix (unused right now) + PLUG_LOADTIME loadable; // when loadable + PLUG_LOADTIME unloadable; // when unloadable +} plugin_info_t; +extern plugin_info_t Plugin_info DLLHIDDEN; + +// Plugin identifier, passed to all Meta Utility Functions. +typedef plugin_info_t* plid_t; +#define PLID &Plugin_info + +#endif /* PLINFO_H */ diff --git a/src/metamod/reg_support.cpp b/src/metamod/reg_support.cpp new file mode 100644 index 0000000..9549bc7 --- /dev/null +++ b/src/metamod/reg_support.cpp @@ -0,0 +1,228 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// reg_support.cpp - support for things "registered" by plugins (console +// cmds, cvars, msgs, etc) + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifdef linux +// enable extra routines in system header files, like strsignal +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +#endif /* linux */ + +#include // strsignal, etc + +#include // always +#include "sdk_util.h" // REG_SVR_COMMAND, etc + +#include "reg_support.h" // me +#include "metamod.h" // RegCmds, g_Players, etc +#include "log_meta.h" // META_ERROR, etc + +// "Register" support. +// +// This code is necessary to support the different "register" engine +// functions that can be called by plugins, so that the plugins can be +// safely unloaded without incurring segmentation faults. +// +// The problem was that, when a plugin called, say: +// REG_SVR_COMMAND("stub_version", svr_stub_version); +// +// the engine stored the address to the "stub_version" string, as well as, +// of course, the svr_stub_version() function pointer. If the plugin was +// unloaded, and dlclose()'d, the memory locations for both the string and +// function became invalid and unavailable. If the engine tried to +// strcmp() against the string, or tried to call the function pointer, it +// generated a segfault and killed the process. +// +// The solution used here is to create strings and functions allocated +// locally, in metamod.dll, which won't become unavailable or invalid due +// to a dlclose(). These local copies are passed to the engine instead of +// those allocated in the plugin DLLs. Thus, when a plugin is later +// unloaded and dlclose'd, the engine can still reference the memory +// locations of registered strings and functions, without segfaulting. +// +// Now, what happens with a registered string or function after a plugin is +// unloaded not quite as nice as I'd like. +// +// Console commands will print a message along the lines of: +// [metamod: command '%s' unavailable; plugin unloaded] +// +// Cvars though, have their value unaffected. This can be convenient when +// reloading a plugin, as the cvars keep their old values. However it can +// also be confusing, as there is no indication when viewing/setting cvars +// from the console that they no longer have an effect. +// +// Also note, the console commands for listing registered cmds and cvars +// will try to show the name of the associated plugin. + + +// Generic command handler, passed to the engine for any AddServerCommand +// calls made by the plugin. It finds the appropriate plugin function +// pointer to call based on CMD_ARGV(0). +void DLLHIDDEN meta_command_handler(void) { + MRegCmd *icmd; + const char *cmd; + + META_DEBUG(5, ("called: meta_command_handler; arg0=%s args='%s'", CMD_ARGV(0), CMD_ARGS())); + cmd=CMD_ARGV(0); + if(!cmd) { + META_WARNING("Null command name in meta_command_handler() ??"); + return; + } + + icmd=RegCmds->find(cmd); + if(!icmd) { + META_WARNING("Couldn't find registered plugin command: %s", cmd); + return; + } + if(icmd->call() != mTRUE) + META_CONS("[metamod: command '%s' unavailable; plugin unloaded]", cmd); +} + + +// Replacement for engine routine AddServerCommand; called by plugins. +// Rather then handing the engine the plugin's command string and function +// pointer (both of which are allocated in the plugin DLL), this hands the +// engine a command string and function pointer allocated locally (in the +// metamod DLL). +// +// The string handed to the engine is just a strdup() of the plugin's +// string. The function pointer handed to the engine is actually a pointer +// to a generic command-handler function (see above). +void DLLHIDDEN meta_AddServerCommand(char *cmd_name, void (*function) (void)) { + MPlugin *iplug=NULL; + MRegCmd *icmd=NULL; + + META_DEBUG(4, ("called: meta_AddServerCommand; cmd_name=%s, function=%d", cmd_name, function)); + + // try to find which plugin is registering this command + if(!(iplug=Plugins->find_memloc((void *)function))) { + // if this isn't supported on this OS, don't log an error + if(meta_errno != ME_OSNOTSUP) + META_WARNING("Failed to find memloc for regcmd '%s'", cmd_name); + } + + // See if this command was previously registered, ie a "reloaded" plugin. + icmd=RegCmds->find(cmd_name); + if(!icmd) { + // If not found, add. + icmd=RegCmds->add(cmd_name); + if(!icmd) { + // error details logged in add() + return; + } + // Only register if not previously registered.. + REG_SVR_COMMAND(icmd->name, meta_command_handler); + } + + icmd->pfnCmd=function; + icmd->status=RG_VALID; + // Store which plugin this is for, if we know. We can use '0' for + // unknown plugin, since plugin index starts at 1. + if(iplug) + icmd->plugid = iplug->index; + else + icmd->plugid = 0; +} + + +// Replacement for engine routine CVarRegister; called by plugins. Rather +// then handing the engine the plugin's cvar structure (which is allocated +// in the plugin DLL), this hands the engine a cvar structure allocated +// locally (in the metamod DLL). +// +// The cvar handed to the engine is globally allocated in the metamod.dll; +// the "name" and "string" fields are strdup()'s of the plugin's strings. +// Note that, once this is done, the cvar_t allocated in the plugin is no +// longer used for _anything_. As long as everything sets/gets the cvar +// values via the engine functions, this will work fine. If the plugin +// code tries to _directly_ read/set the fields of its own cvar structures, +// it will fail to work properly. +void DLLHIDDEN meta_CVarRegister(cvar_t *pCvar) { + MPlugin *iplug=NULL; + MRegCvar *icvar=NULL; + + META_DEBUG(4, ("called: meta_CVarRegister; name=%s", pCvar->name)); + + // try to find which plugin is registering this cvar + if(!(iplug=Plugins->find_memloc((void *)pCvar))) { + // if this isn't supported on this OS, don't log an error + if(meta_errno != ME_OSNOTSUP) + // Note: if cvar_t was malloc'd by the plugin, we can't + // determine the calling plugin. Thus, this becomes a Debug + // rather than Error message. + META_DEBUG(1, ("Failed to find memloc for regcvar '%s'", + pCvar->name)); + } + + // See if this cvar was previously registered, ie a "reloaded" plugin. + icvar=RegCvars->find(pCvar->name); + if(!icvar) { + // If not found, add. + icvar=RegCvars->add(pCvar->name); + if(!icvar) { + // error details logged in add() + return; + } + // Reset to given value + icvar->set(pCvar); + CVAR_REGISTER(icvar->data); + } + // Note: if not a new cvar, then we don't set the values, and just keep + // the pre-existing value. + + icvar->status=RG_VALID; + // Store which plugin this is for, if we know. Use '0' for unknown + // plugin, as plugin index starts at 1. + if(iplug) + icvar->plugid = iplug->index; + else + icvar->plugid = 0; +} + + +// Replacement for engine routine RegUserMsg; called by plugins. +int DLLHIDDEN meta_RegUserMsg(const char *pszName, int iSize) { + return(REG_USER_MSG(strdup(pszName), iSize)); +} + + +// Intercept and record queries +void DLLHIDDEN meta_QueryClientCvarValue(const edict_t *player, const char *cvarName) { + g_Players.set_player_cvar_query(player, cvarName); + + (*g_engfuncs.pfnQueryClientCvarValue)(player, cvarName); +} diff --git a/src/metamod/reg_support.h b/src/metamod/reg_support.h new file mode 100644 index 0000000..a4ec38a --- /dev/null +++ b/src/metamod/reg_support.h @@ -0,0 +1,49 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// reg_support.h - functions for "registered" cmd/cvar/msg support + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef REG_SUPPORT_H +#define REG_SUPPORT_H + +#include "mreg.h" // REG_CMD_FN, etc + +// these are only 'hidden' because called from outside (plugins and engine) +void DLLHIDDEN meta_command_handler(void); +void DLLHIDDEN meta_AddServerCommand(char *cmd_name, REG_CMD_FN function); +void DLLHIDDEN meta_CVarRegister(cvar_t *pCvar); +int DLLHIDDEN meta_RegUserMsg(const char *pszName, int iSize); +void DLLHIDDEN meta_QueryClientCvarValue(const edict_t *player, const char *cvarName); + +#endif /* REG_SUPPORT_H */ diff --git a/src/metamod/res_meta.rc b/src/metamod/res_meta.rc new file mode 100644 index 0000000..ad952c0 --- /dev/null +++ b/src/metamod/res_meta.rc @@ -0,0 +1,76 @@ +// vi: set ts=4 sw=4 ft=c : +// vim: set tw=75 : + +/* + * Copyright (c) 2001-2003 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +// see: +// http://msdn.microsoft.com/library/psdk/winui/rc_7x2d.htm + +#include +#include "info_name.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION RC_VERS_DWORD + PRODUCTVERSION RC_VERS_DWORD + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", RC_COMMENTS "\0" + VALUE "CompanyName", VAUTHOR "\0" + VALUE "FileDescription", RC_DESC "\0" + VALUE "FileVersion", VVERSION "\0" + VALUE "InternalName", RC_INTERNAL "\0" + VALUE "LegalCopyright", RC_COPYRIGHT "\0" + VALUE "License", RC_LICENSE "\0" + VALUE "OriginalFilename", RC_FILENAME "\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", VNAME "\0" + VALUE "ProductVersion", VVERSION "\0" + VALUE "SpecialBuild", OPT_TYPE "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/metamod/ret_type.h b/src/metamod/ret_type.h new file mode 100644 index 0000000..e246752 --- /dev/null +++ b/src/metamod/ret_type.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ +#ifndef RET_TYPE_H +#define RET_TYPE_H + +#include "new_baseclass.h" + +class class_ret_t : public class_metamod_new { +public: + // Construction + inline class_ret_t(void) { }; + inline class_ret_t(float f) { data.f = f; }; + inline class_ret_t(void * p) { data.p = p; }; + inline class_ret_t(const char * pc) { data.pc = pc; }; + inline class_ret_t(int i) { data.i = i; }; + inline class_ret_t(short s) { data.i = s; }; + inline class_ret_t(char c) { data.i = c; }; + inline class_ret_t(unsigned int ui) { data.ui = ui; }; + inline class_ret_t(unsigned long ui) { data.ui = ui; }; + inline class_ret_t(unsigned short us) { data.ui = us; }; + inline class_ret_t(unsigned char uc) { data.ui = uc; }; + + // Reading/Writing + inline void * getptr(void) { return(&data); }; + + #define SET_RET_CLASS(ret,type,x) \ + *(type*)((ret).getptr()) = (type)(x) + #define GET_RET_CLASS(ret,type) \ + (*(type*)((ret).getptr())) +private: + //Data (select data size of largest type) (x86: 32bit, x86_64: 64bit) + union { + void * p; + const char * pc; + float f; + long i; + unsigned long ui; + } data; +}; + +#endif /*RET_TYPE_H*/ diff --git a/src/metamod/sdk_util.cpp b/src/metamod/sdk_util.cpp new file mode 100644 index 0000000..10d7600 --- /dev/null +++ b/src/metamod/sdk_util.cpp @@ -0,0 +1,125 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// sdk_util.cpp - utility routines from HL SDK util.cpp + +// Selected portions of dlls/util.cpp from SDK 2.1. +// Functions copied from there as needed... +// And modified to avoid buffer overflows (argh). +// Also modified to remove dependency on CBaseEntity class. + +/*** +* +* 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. +* +* 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. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include +#include "sdk_util.h" + +#include // for strncpy(), etc + +#include "osdep.h" // win32 vsnprintf, etc + +const char * DLLINTERNAL META_UTIL_VarArgs(const char *format, ...) +{ + va_list argptr; + static char string[4096]; + + va_start(argptr, format); + safevoid_vsnprintf(string, sizeof(string), format, argptr); + va_end(argptr); + + return(string); +} + +short DLLINTERNAL FixedSigned16(float value, float scale) +{ + int output; + + output = (int)(value * scale); + + if(output > 32767) + output = 32767; + + if(output < -32768) + output = -32768; + + return((short)output); +} + +unsigned short DLLINTERNAL FixedUnsigned16(float value, float scale) +{ + int output; + + output = (int)(value * scale); + if(output < 0) + output = 0; + if(output > 0xFFFF) + output = 0xFFFF; + + return((unsigned short)output); +} + + +void DLLINTERNAL META_UTIL_HudMessage(edict_t *pEntity, const hudtextparms_t &textparms, const char *pMessage) +{ + if(fast_FNullEnt(pEntity) || pEntity->free) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity ); + 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(); +} + + diff --git a/src/metamod/sdk_util.h b/src/metamod/sdk_util.h new file mode 100644 index 0000000..48a5025 --- /dev/null +++ b/src/metamod/sdk_util.h @@ -0,0 +1,121 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// sdk_util.h - wrapper & extension of util.h from HL SDK + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +// Wrap util.h from SDK with ifndef/endif, to avoid problems from multiple +// inclusions. Dunno why Valve didn't do that in util.h themselves.. + +#ifndef SDK_UTIL_H +#define SDK_UTIL_H + +// We're not including the DBG_EntOfVars and DBG_AssertFunction routines +// mentioned in the SDK util.h, so we're going to unset DEBUG here so that +// we don't get "unresolved symbol" errors. +#ifdef DEBUG +#undef DEBUG +#endif /* DEBUG */ + +#include "enginecallbacks.h" +#include "comp_dep.h" +#include + + +// Also, create some additional macros for engine callback functions, which +// weren't in SDK dlls/enginecallbacks.h but probably should have been. + +#define GET_INFOKEYBUFFER (*g_engfuncs.pfnGetInfoKeyBuffer) +#define INFOKEY_VALUE (*g_engfuncs.pfnInfoKeyValue) +#define SET_CLIENT_KEYVALUE (*g_engfuncs.pfnSetClientKeyValue) +#define REG_SVR_COMMAND (*g_engfuncs.pfnAddServerCommand) +#define SERVER_PRINT (*g_engfuncs.pfnServerPrint) +#define SET_SERVER_KEYVALUE (*g_engfuncs.pfnSetKeyValue) +#define QUERY_CLIENT_CVAR_VALUE (*g_engfuncs.pfnQueryClientCvarValue) +#define QUERY_CLIENT_CVAR_VALUE2 (*g_engfuncs.pfnQueryClientCvarValue2) + +// Add overloaded ENTINDEX() version for const edict_t pointer. +// The pfnIndexOfEdict() function takes a const edict_t pointer +// as parameter anyway, so there is no reason why ENTINDEX() +// shouldn't. +inline int ENTINDEX(const edict_t *pEdict) { + return((*g_engfuncs.pfnIndexOfEdict)(pEdict)); +} + + +// Also, create some nice inlines for engine callback combos. + +// Get a setinfo value from a player entity. +inline char * DLLINTERNAL ENTITY_KEYVALUE(edict_t *entity, char *key) { + return(INFOKEY_VALUE(GET_INFOKEYBUFFER(entity), key)); +} + +// Set a setinfo value for a player entity. +inline void DLLINTERNAL ENTITY_SET_KEYVALUE(edict_t *entity, char *key, char *value) { + SET_CLIENT_KEYVALUE(ENTINDEX(entity), GET_INFOKEYBUFFER(entity), key, value); +} + +// Get a "serverinfo" value. +inline char * DLLINTERNAL SERVERINFO(char *key) { + return(ENTITY_KEYVALUE(INDEXENT(0), key)); +} + +// Set a "serverinfo" value. +inline void DLLINTERNAL SET_SERVERINFO(char *key, char *value) { + SET_SERVER_KEYVALUE(GET_INFOKEYBUFFER(INDEXENT(0)), key, value); +} + +// Get a "localinfo" value. +inline char * DLLINTERNAL LOCALINFO(char *key) { + return(ENTITY_KEYVALUE(NULL, key)); +} + +// Set a "localinfo" value. +inline void DLLINTERNAL SET_LOCALINFO(char *key, char *value) { + SET_SERVER_KEYVALUE(GET_INFOKEYBUFFER(NULL), key, value); +} + +inline int DLLINTERNAL fast_FNullEnt(const edict_t* pent) { + return(!pent || !(*g_engfuncs.pfnEntOffsetOfPEntity)(pent)); +} + +// Our slightly modified version, using an edict_t pointer instead of a +// CBaseEntity pointer. (was in 1.17p1, included in 1.17.1) +void DLLINTERNAL META_UTIL_HudMessage(edict_t *pEntity, const hudtextparms_t &textparms, const char *pMessage); + +const char * DLLINTERNAL META_UTIL_VarArgs(const char *format, ...); + +short DLLINTERNAL FixedSigned16(float value, float scale); +unsigned short DLLINTERNAL FixedUnsigned16(float value, float scale); + +#endif /* SDK_UTIL_H */ diff --git a/src/metamod/studioapi.cpp b/src/metamod/studioapi.cpp new file mode 100644 index 0000000..6c2ee4a --- /dev/null +++ b/src/metamod/studioapi.cpp @@ -0,0 +1,38 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// studio.cpp - player model blending interfaces + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +//Jussi Kivilinna: This forwarder function to gamedll is not needed anymore, dynamic +// linkent code handles this too! diff --git a/src/metamod/studioapi.h b/src/metamod/studioapi.h new file mode 100644 index 0000000..6c2ee4a --- /dev/null +++ b/src/metamod/studioapi.h @@ -0,0 +1,38 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// studio.cpp - player model blending interfaces + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +//Jussi Kivilinna: This forwarder function to gamedll is not needed anymore, dynamic +// linkent code handles this too! diff --git a/src/metamod/support_meta.cpp b/src/metamod/support_meta.cpp new file mode 100644 index 0000000..f6b09d0 --- /dev/null +++ b/src/metamod/support_meta.cpp @@ -0,0 +1,121 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// support_meta.cpp - generic support routines + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include // always + +#include "metamod.h" // GameDLL +#include "support_meta.h" // me +#include "osdep.h" // sleep, etc + +META_ERRNO meta_errno; + +void DLLINTERNAL do_exit(int exitval) { + sleep(3); + exit(exitval); +} + +// Checks for a non-empty file, relative to the gamedir if necessary. +// Formerly used LOAD_FILE_FOR_ME, which provided a simple way to check for +// a file under the gamedir, but which would _also_ look in the sibling +// "valve" directory, thus sometimes finding files that weren't desired. +// Also, formerly named just "valid_file". +// +// Special-case-recognize "/dev/null" as a valid file. +int DLLINTERNAL valid_gamedir_file(const char *path) { + char buf[PATH_MAX]; + struct stat st; + int ret, reg, size; + + if(!path) + return(FALSE); + + if(strmatch(path, "/dev/null")) + return(TRUE); + + if(is_absolute_path(path)) + STRNCPY(buf, path, sizeof(buf)); + else + safevoid_snprintf(buf, sizeof(buf), "%s/%s", GameDLL.gamedir, path); + + ret=stat(buf, &st); + if(ret != 0) { + META_DEBUG(5, ("Unable to stat '%s': %s", buf, strerror(errno))); + return(FALSE); + } + + reg=S_ISREG(st.st_mode); + if(!reg) { + META_DEBUG(5, ("Not a regular file: %s", buf)); + return(FALSE); + } + + size=st.st_size; + if(!size) { + META_DEBUG(5, ("Empty file: %s", buf)); + return(FALSE); + } + + if(ret==0 && reg && size) + return(TRUE); + else + return(FALSE); +} + +// Turns path into a full path: +// - if not absolute, prepends gamedir +// - calls realpath() to collapse ".." and such +// - calls normalize_pathname() to fix backslashes, etc +// +// Much like realpath, buffer pointed to by fullpath is assumed to be +// able to store a string of PATH_MAX length. +char * DLLINTERNAL full_gamedir_path(const char *path, char *fullpath) { + char buf[PATH_MAX]; + + // Build pathname from filename, plus gamedir if relative path. + if(is_absolute_path(path)) + STRNCPY(buf, path, sizeof(buf)); + else + safevoid_snprintf(buf, sizeof(buf), "%s/%s", GameDLL.gamedir, path); + // Remove relative path components, if possible. + if(!realpath(buf, fullpath)) { + META_DEBUG(4, ("Unable to get realpath for '%s': %s", buf, + str_os_error())); + STRNCPY(fullpath, path, PATH_MAX); + } + // Replace backslashes, etc. + normalize_pathname(fullpath); + return(fullpath); +} diff --git a/src/metamod/support_meta.h b/src/metamod/support_meta.h new file mode 100644 index 0000000..3b9028d --- /dev/null +++ b/src/metamod/support_meta.h @@ -0,0 +1,177 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// support_meta.h - generic support macros + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef SUPPORT_META_H +#define SUPPORT_META_H + +#include // strcpy(), strncat() +#include // stat +#include // stat + +#include "osdep.h" // strcasecmp, S_ISREG, +#include "enginecallbacks.h" // LOAD_FILE_FOR_ME, etc + +void DLLINTERNAL do_exit(int exitval); + +//use pointer to avoid inlining of strcmp +inline int DLLINTERNAL mm_strcmp(const char *s1, const char *s2) { +#if 0 + int (*__strcmp)(const char*, const char*) = &strcmp; + return((*__strcmp)(s1, s2)); +#else + return(strcmp(s1,s2)); +#endif +} + +//use pointer to avoid inlining of strncmp +inline int DLLINTERNAL mm_strncmp(const char *s1, const char *s2, size_t n) { +#if 0 + int (*__strncmp)(const char*, const char*, size_t) = &strncmp; + return((*__strncmp)(s1, s2, n)); +#else + return(strncmp(s1,s2,n)); +#endif +} + +// Unlike snprintf(), strncpy() doesn't necessarily null-terminate the +// target. It appears the former function reasonably considers the given +// size to be "max size of target string" (including the null-terminator), +// whereas strncpy() strangely considers the given size to be "total number +// of bytes to copy". Note strncpy() _always_ writes n bytes, whereas +// snprintf() writes a _max_ of n bytes (incl the NULL). If strncpy() +// needs to write extra bytes to reach n, it uses NULLs, so the target +// _can_ be null-terminated, but only if the source string would have fit +// anyway - in which case why not just use strcpy() instead? +// +// Thus, it appears strncpy() is not only unsafe, it's also inefficient, +// and seemingly no better than plain strcpy() anyway. +// +// With this logic, strncpy() doesn't appear to be much of a "str" function +// at all, IMHO. +// +// Strncat works better, although it considers the given size to be "number +// of bytes to append", and doesn't include the null-terminator in that +// count. Thus, we can use it for what we want to do, by setting the +// target to zero-length (NULL in first byte), and copying n-1 bytes +// (leaving room for the null-termiator). +// +// Why does it have to be soo haaard... + +// Also note, some kind of wrapper is necessary to group the two +// statements into one, for use in situations like non-braced else +// statements. + +// Technique 1: use "do..while": +#if 0 +#define STRNCPY(dst, src, size) \ + do { strcpy(dst, "\0"); strncat(dst, src, size-1); } while(0) +#endif + +// Technique 2: use parens and commas: +#if 0 +#define STRNCPY(dst, src, size) \ + (strcpy(dst, "\0"), strncat(dst, src, size-1)) +#endif + +// Technique 3: use inline +inline char * DLLINTERNAL STRNCPY(char *dst, const char *src, int size) { + return(strncat(&(*dst = 0), src, size-1)); +} + +// Renamed string functions to be clearer. +inline int DLLINTERNAL strmatch(const char *s1, const char *s2) { + if(likely(s1) && likely(s2)) + return(!mm_strcmp(s1, s2)); + else + return(0); +} +inline int DLLINTERNAL strnmatch(const char *s1, const char *s2, size_t n) { + if(likely(s1) && likely(s2)) + return(!mm_strncmp(s1, s2, n)); + else + return(0); +} +inline int DLLINTERNAL strcasematch(const char *s1, const char *s2) { + if(likely(s1) && likely(s2)) + return(!strcasecmp(s1, s2)); + else + return(0); +} +inline int DLLINTERNAL strncasematch(const char *s1, const char *s2, size_t n) { + if(likely(s1) && likely(s2)) + return(!strncasecmp(s1, s2, n)); + else + return(0); +} + +inline int DLLINTERNAL old_valid_file(char *path) { + char *cp; + int len, ret; + cp = (char *)LOAD_FILE_FOR_ME(path, &len); + if(cp && len) + ret=1; + else + ret=0; + FREE_FILE(cp); + return(ret); +} +int DLLINTERNAL valid_gamedir_file(const char *path); +char * DLLINTERNAL full_gamedir_path(const char *path, char *fullpath); + +// Turn a variable/function name into the corresponding string, optionally +// stripping off the leading "len" characters. Useful for things like +// turning 'pfnClientCommand' into "ClientCommand" so we don't have to +// specify strings used for all the debugging/log messages. +#define STRINGIZE(name, len) #name+len + + +// Max description length for plugins.ini and other places. +#define MAX_DESC_LEN 256 + + +// For various character string buffers. +#define MAX_STRBUF_LEN 1024 + + +// Smallest of two +#define MIN(x, y) (((x)<(y))?(x):(y)) + + +// Greatest of two +#define MAX(x, y) (((x)>(y))?(x):(y)) + + +#endif /* SUPPORT_META_H */ diff --git a/src/metamod/thread_logparse.cpp b/src/metamod/thread_logparse.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/metamod/thread_logparse.h b/src/metamod/thread_logparse.h new file mode 100644 index 0000000..e69de29 diff --git a/src/metamod/tqueue.h b/src/metamod/tqueue.h new file mode 100644 index 0000000..cc9c721 --- /dev/null +++ b/src/metamod/tqueue.h @@ -0,0 +1,114 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// tqueue.h - template classes for Queue and QItem + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ +/* +#ifndef TQUEUE_H +#define TQUEUE_H + +#include "new_baseclass.h" + +// Forward declarations. +template class Queue; + +// Template for Queue. +template class Queue : public class_metamod_new { + private: + // private copy/assign constructors: + Queue(const Queue &src); + void operator=(const Queue &src); + protected: + // structs: + class QItem : public class_metamod_new { + private: + // private copy/assign constructors: + QItem(const QItem &src); + void operator=(const QItem &src); + public: + qdata_t *data; + QItem *next; + QItem(void) :data(NULL), next(NULL) { }; + QItem(qdata_t *dnew) :data(dnew), next(NULL) { }; + }; + // data: + int size; + QItem *front; + QItem *end; + public: + // constructor: + Queue(void) :size(0), front(NULL), end(NULL) {}; + // functions: + void push(qdata_t *qadd); + qdata_t * pop(void); +}; + + +///// Template Queue: + +// Push onto the queue (at end). +template inline void Queue::push(qdata_t *qadd) { + QItem *qnew = new QItem(qadd); + + if(size==0) + front=qnew; + else + end->next=qnew; + + end=qnew; + + size++; +} + +// Pop from queue (from front). Wait for an item to actually be available +// on the queue (block until there's something there). +template inline qdata_t* Queue::pop(void) { + QItem *qtmp; + qdata_t *ret; + + if(size==0) + return(NULL); + + qtmp=front; + + ret=front->data; + front=front->next; + + delete qtmp; + + size--; + + return(ret); +} + +#endif*/ /* TQUEUE_H */ diff --git a/src/metamod/types_meta.h b/src/metamod/types_meta.h new file mode 100644 index 0000000..4e8ff11 --- /dev/null +++ b/src/metamod/types_meta.h @@ -0,0 +1,85 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// types_meta.h - common internal type, etc definitions + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef TYPES_META_H +#define TYPES_META_H + +#include "comp_dep.h" + + +// Our own boolean type, for stricter type matching. +typedef enum mBOOL { + mFALSE = 0, + mTRUE, +} mBOOL; + +// Like C's errno, for our various functions; describes causes of failure +// or mFALSE returns. +typedef enum { + ME_NOERROR = 0, + ME_FORMAT, // invalid format + ME_COMMENT, // ignored comment + ME_BLANK, // ignored blank (empty) line + ME_ALREADY, // request had already been done + ME_DELAYED, // request is delayed + ME_NOTALLOWED, // request not allowed + ME_SKIPPED, // request is being skipped for whatever reason + ME_BADREQ, // invalid request for this + ME_ARGUMENT, // invalid arguments + ME_NULLRESULT, // resulting data was empty or null + ME_MAXREACHED, // reached max/limit + ME_NOTUNIQ, // not unique (ambigious match) + ME_NOTFOUND, // in find operation, match not found + ME_NOFILE, // file empty or missing + ME_NOMEM, // malloc failed + ME_BADMEMPTR, // invalid memory address + ME_OSNOTSUP, // OS doesn't support this operation + ME_DLOPEN, // failed to open shared lib/dll + ME_DLMISSING, // symbol missing in lib/dll + ME_DLERROR, // some other error encountered calling functions from dll + ME_IFVERSION, // incompatible interface version + ME_UNLOAD_UNLOADER, // tried to unload unloader + ME_UNLOAD_SELF, // tried to unload self +} META_ERRNO; +extern META_ERRNO meta_errno DLLHIDDEN; + +#define RETURN_ERRNO(retval, errval) \ + do { meta_errno=errval; return(retval); } while(0) + +#define RETURN_LOGERR_ERRNO(errargs, retval, errval) \ + do { META_ERROR errargs ; meta_errno=errval; return(retval); } while(0) + +#endif /* TYPES_META_H */ diff --git a/src/metamod/vdate.cpp b/src/metamod/vdate.cpp new file mode 100644 index 0000000..ed61313 --- /dev/null +++ b/src/metamod/vdate.cpp @@ -0,0 +1,58 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// vdate.cpp - compile-time version date + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include "info_name.h" // for VNAME, VVERSION, etc +#include "vdate.h" + + +// Grab date/time of compile. The Makefile is set up to recompile this +// module before each link, so that this will always indicate the time the +// library was compiled and linked. + +// This is in a separate file from vers_*, so it can be generically used by +// multiple projects. + +char const *COMPILE_TIME=__DATE__ ", " __TIME__; + +#ifndef COMPILE_TZ + #define COMPILE_TZ "EET" +#endif + +char const *COMPILE_TZONE = COMPILE_TZ; + +// Include a string for /usr/bin/ident. + +char const *vstring="\n$Pg: " VNAME " -- " VVERSION " | " __DATE__ " - " __TIME__ " $\n"; diff --git a/src/metamod/vdate.h b/src/metamod/vdate.h new file mode 100644 index 0000000..6783c63 --- /dev/null +++ b/src/metamod/vdate.h @@ -0,0 +1,45 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// vdate.h - compile-time version date + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef VDATE_H +#define VDATE_H + +#include "comp_dep.h" + +extern char const *COMPILE_TIME DLLHIDDEN; +extern char const *COMPILE_TZONE DLLHIDDEN; + +#endif /* VDATE_H */ diff --git a/src/metamod/vers_meta.h b/src/metamod/vers_meta.h new file mode 100644 index 0000000..96beea8 --- /dev/null +++ b/src/metamod/vers_meta.h @@ -0,0 +1,61 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// vers_meta.h - version info, intended to be common among DLLs distributed +// with metamod. + +/* + * Copyright (c) 2001-2013 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#ifndef VERS_META_H +#define VERS_META_H + +#ifndef OPT_TYPE + #define OPT_TYPE "default" +#endif /* not OPT_TYPE */ + + +#define VDATE "2018/02/11" +#define VPATCH_COPYRIGHT_YEAR "2018" +#define VMETA_VERSION "1.21" + +#define VPATCH_NAME "Metamod-P (mm-p)" +#define VPATCH_IVERSION 38 +#define VPATCH_VERSION "38" +#define VPATCH_AUTHOR "Jussi Kivilinna" +#define VPATCH_WEBSITE "http://metamod-p.sourceforge.net/" + +#define VVERSION VMETA_VERSION "p" VPATCH_VERSION +#define RC_VERS_DWORD 1,21,0,VPATCH_IVERSION // Version Windows DLL Resources in res_meta.rc + + + +#endif /* VERS_META_H */ diff --git a/src/pm_shared/pm_materials.h b/src/pm_shared/pm_materials.h new file mode 100644 index 0000000..bc49c4d --- /dev/null +++ b/src/pm_shared/pm_materials.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ +#if !defined( PM_MATERIALSH ) +#define PM_MATERIALSH +#pragma once + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#endif // !PM_MATERIALSH diff --git a/src/utils/common/mathlib.h b/src/utils/common/mathlib.h new file mode 100644 index 0000000..209f9ef --- /dev/null +++ b/src/utils/common/mathlib.h @@ -0,0 +1,89 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +****/ + +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec3_t[3]; // x,y,z +typedef vec_t vec4_t[4]; // x,y,z,w + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#define Q_PI 3.14159265358979323846 + +extern vec3_t vec3_origin; + +// Use this definition globally +#define ON_EPSILON 0.01 +#define EQUAL_EPSILON 0.001 + +int VectorCompare (vec3_t v1, vec3_t v2); + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define VectorFill(a,b) { (a)[0]=(b); (a)[1]=(b); (a)[2]=(b);} +#define VectorAvg(a) ( ( (a)[0] + (a)[1] + (a)[2] ) / 3 ) +#define VectorSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];} +#define VectorAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];} +#define VectorCopy(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];} +#define VectorScale(a,b,c) {(c)[0]=(b)*(a)[0];(c)[1]=(b)*(a)[1];(c)[2]=(b)*(a)[2];} + +vec_t Q_rint (vec_t in); +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); +void _VectorScale (vec3_t v, vec_t scale, vec3_t out); + +double VectorLength(vec3_t v); + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc); + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); +void VectorInverse (vec3_t v); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); + +void AngleMatrix (const vec3_t angles, float matrix[3][4] ); +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ); +void R_ConcatTransforms (const float in1[3][4], const float in2[3][4], float out[3][4]); + +void VectorIRotate (const vec3_t in1, const float in2[3][4], vec3_t out); +void VectorRotate (const vec3_t in1, const float in2[3][4], vec3_t out); + +void VectorTransform (const vec3_t in1, const float in2[3][4], vec3_t out); + +void AngleQuaternion( const vec3_t angles, vec4_t quaternion ); +void QuaternionMatrix( const vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( const vec4_t p, vec4_t q, float t, vec4_t qt ); + + +#ifdef __cplusplus +} +#endif + +#endif