From 4c2b8fd6c4fbadd84ea62a590e108cc9aefcec41 Mon Sep 17 00:00:00 2001 From: Scott Ehlert Date: Mon, 15 Sep 2008 01:33:59 -0500 Subject: [PATCH] Modified SDK for GCC 4.1 --- Everything_SDK-2003.sln | 22 +- Everything_SDK-2005.sln | 44 +- choreoobjects/choreoactor.cpp | 293 + choreoobjects/choreochannel.cpp | 562 + choreoobjects/choreoevent.cpp | 4204 +++ choreoobjects/choreoobjects-2003.vcproj | 161 + choreoobjects/choreoobjects-2005.vcproj | 213 + choreoobjects/choreoscene.cpp | 3987 +++ cl_dll/client_hl2-2003.vcproj | 121 +- cl_dll/client_hl2-2005.vcproj | 146 +- cl_dll/client_hl2mp-2003.vcproj | 241 +- cl_dll/client_hl2mp-2005.vcproj | 340 +- cl_dll/client_scratch-2003.vcproj | 283 +- cl_dll/client_scratch-2005.vcproj | 246 +- cl_dll/game_controls/SpectatorGUI.cpp | 2 +- cl_dll/hl2_hud/hud_autoaim.cpp | 2 +- dlls/AI_ResponseSystem.cpp | 6140 ++-- dlls/CRagdollMagnet.h | 90 +- dlls/EffectsServer.cpp | 2 +- dlls/EntityFlame.cpp | 4 +- dlls/EnvBeam.cpp | 4 +- dlls/EnvShake.cpp | 2 + dlls/SkyCamera.cpp | 2 + dlls/TemplateEntities.cpp | 2 +- dlls/ai_baseactor.cpp | 33 +- dlls/ai_baseactor.h | 4 +- dlls/ai_basenpc.cpp | 27339 ++++++++-------- dlls/ai_basenpc_physicsflyer.cpp | 2 - dlls/ai_basenpc_schedule.cpp | 8 +- dlls/ai_behavior.h | 16 +- dlls/ai_behavior_assault.cpp | 6 +- dlls/ai_behavior_follow.cpp | 2 +- dlls/ai_behavior_passenger.cpp | 6 +- dlls/ai_behavior_rappel.cpp | 2 +- dlls/ai_behavior_standoff.cpp | 2 +- dlls/ai_blended_movement.cpp | 8 +- dlls/ai_blended_movement.h | 6 +- dlls/ai_default.cpp | 2 + dlls/ai_dynamiclink.h | 242 +- dlls/ai_eventresponse.h | 94 +- dlls/ai_hint.cpp | 10 +- dlls/ai_hull.cpp | 2 +- dlls/ai_hull.h | 150 +- dlls/ai_looktarget.h | 94 +- dlls/ai_memory.cpp | 1284 +- dlls/ai_memory.h | 1 + dlls/ai_motor.cpp | 10 +- dlls/ai_motor.h | 1 + dlls/ai_moveprobe.h | 1 + dlls/ai_moveshoot.cpp | 4 + dlls/ai_movesolver.cpp | 8 +- dlls/ai_namespaces.cpp | 8 +- dlls/ai_navigator.cpp | 13 +- dlls/ai_navigator.h | 12 +- dlls/ai_network.cpp | 4 +- dlls/ai_networkmanager.cpp | 2 +- dlls/ai_pathfinder.cpp | 10 +- dlls/ai_planesolver.cpp | 2 +- dlls/ai_planesolver.h | 4 +- dlls/ai_playerally.cpp | 6 +- dlls/ai_schedule.cpp | 4 +- dlls/ai_schedule.h | 400 +- dlls/ai_scriptconditions.h | 498 +- dlls/ai_senses.cpp | 9 +- dlls/ai_speech.cpp | 4 +- dlls/ai_speech.h | 28 +- dlls/ai_squad.cpp | 2 +- dlls/ai_tacticalservices.cpp | 2 +- dlls/ai_trackpather.cpp | 3368 +- dlls/baseanimating.cpp | 4 +- dlls/basebludgeonweapon.h | 114 +- dlls/basecombatcharacter.cpp | 4 +- dlls/basecombatweapon.cpp | 2 +- dlls/baseentity.cpp | 18 +- dlls/baseentity.h | 10 +- dlls/baseflex.cpp | 7 +- dlls/basegrenade_concussion.cpp | 2 +- dlls/basetempentity.h | 130 +- dlls/baseviewmodel.h | 34 +- dlls/bitstring.h | 62 +- dlls/bmodels.cpp | 2 +- dlls/buttons.cpp | 2970 +- dlls/cbase.cpp | 15 + dlls/client.cpp | 4 +- dlls/controlentities.cpp | 2 +- dlls/cplane.cpp | 142 +- dlls/cplane.h | 88 +- dlls/cterrainmorph.cpp | 2 +- dlls/doors.cpp | 6 +- dlls/effects.cpp | 10 +- dlls/entityblocker.cpp | 152 +- dlls/entitylist.cpp | 2 +- dlls/env_entity_maker.cpp | 4 +- dlls/env_zoom.h | 26 +- dlls/envmicrophone.cpp | 2 +- .../ai_behavior_passenger_companion.cpp | 2 +- .../episodic/ai_behavior_passenger_zombie.cpp | 2 +- dlls/episodic/grenade_hopwire.cpp | 2 +- dlls/episodic/grenade_hopwire.h | 2 +- dlls/episodic/prop_coreball.cpp | 266 +- dlls/event_tempentity_tester.cpp | 250 +- dlls/event_tempentity_tester.h | 80 +- dlls/explode.cpp | 2 +- dlls/fire.cpp | 4 +- dlls/fire_smoke.cpp | 390 +- dlls/fogcontroller.cpp | 2 +- dlls/fourwheelvehiclephysics.cpp | 2 +- dlls/fourwheelvehiclephysics.h | 2 +- dlls/func_break.cpp | 19 +- dlls/func_break.h | 2 +- dlls/func_breakablesurf.cpp | 8 +- dlls/func_ladder_endpoint.cpp | 136 +- dlls/game_ui.cpp | 4 +- dlls/gameinterface.cpp | 5181 ++- dlls/genericactor.cpp | 2 +- dlls/genericmonster.cpp | 2 +- dlls/hl2_dll/ai_behavior_functank.cpp | 6 +- dlls/hl2_dll/ai_behavior_functank.h | 240 +- dlls/hl2_dll/ai_behavior_police.cpp | 6 +- dlls/hl2_dll/ai_goal_police.cpp | 2 +- dlls/hl2_dll/ai_interactions.h | 160 +- dlls/hl2_dll/ai_spotlight.cpp | 4 +- dlls/hl2_dll/antlion_dust.h | 64 +- dlls/hl2_dll/antlion_maker.cpp | 14 +- dlls/hl2_dll/basehlcombatweapon.cpp | 2 +- dlls/hl2_dll/cbasehelicopter.cpp | 4 +- dlls/hl2_dll/cbasespriteprojectile.h | 2 +- dlls/hl2_dll/combine_mine.cpp | 4 +- dlls/hl2_dll/energy_wave.h | 74 +- dlls/hl2_dll/env_alyxemp.cpp | 2 +- dlls/hl2_dll/env_headcrabcanister.cpp | 4 +- dlls/hl2_dll/env_starfield.cpp | 214 +- dlls/hl2_dll/func_recharge.cpp | 20 +- dlls/hl2_dll/func_tank.cpp | 28 +- dlls/hl2_dll/func_tank.h | 682 +- dlls/hl2_dll/grenade_ar2.cpp | 2 +- dlls/hl2_dll/grenade_bugbait.cpp | 2 +- dlls/hl2_dll/grenade_frag.cpp | 784 +- dlls/hl2_dll/grenade_homer.cpp | 2 +- dlls/hl2_dll/hl2_client.cpp | 2 +- dlls/hl2_dll/hl2_player.cpp | 6930 ++-- dlls/hl2_dll/hl2_triggers.cpp | 6 +- dlls/hl2_dll/item_ammo.cpp | 2 +- dlls/hl2_dll/item_dynamic_resupply.cpp | 16 +- dlls/hl2_dll/item_healthkit.cpp | 12 +- dlls/hl2_dll/npc_BaseZombie.cpp | 22 +- dlls/hl2_dll/npc_BaseZombie.h | 2 +- dlls/hl2_dll/npc_PoisonZombie.cpp | 2 +- dlls/hl2_dll/npc_alyx_episodic.cpp | 10 +- dlls/hl2_dll/npc_antlion.cpp | 8 +- dlls/hl2_dll/npc_antlionguard.cpp | 2 +- dlls/hl2_dll/npc_attackchopper.cpp | 10 +- dlls/hl2_dll/npc_barnacle.cpp | 6 +- dlls/hl2_dll/npc_breen.cpp | 2 +- dlls/hl2_dll/npc_citizen17.cpp | 46 +- dlls/hl2_dll/npc_combine_episodic.cpp | 14 +- dlls/hl2_dll/npc_combinedropship.cpp | 11 +- dlls/hl2_dll/npc_combinegunship.cpp | 10 +- dlls/hl2_dll/npc_combines.cpp | 14 +- dlls/hl2_dll/npc_cranedriver.cpp | 3 + dlls/hl2_dll/npc_crow.cpp | 3 + dlls/hl2_dll/npc_eli.cpp | 2 +- dlls/hl2_dll/npc_fastzombie.cpp | 5 +- dlls/hl2_dll/npc_gman.cpp | 2 +- dlls/hl2_dll/npc_headcrab.cpp | 7 +- dlls/hl2_dll/npc_kleiner.cpp | 2 +- dlls/hl2_dll/npc_launcher.cpp | 2 +- dlls/hl2_dll/npc_manhack.cpp | 6 +- dlls/hl2_dll/npc_metropolice.cpp | 25 +- dlls/hl2_dll/npc_mossman.cpp | 2 +- dlls/hl2_dll/npc_playercompanion.cpp | 2 +- dlls/hl2_dll/npc_rollermine.cpp | 11 +- dlls/hl2_dll/npc_scanner.cpp | 13 +- dlls/hl2_dll/npc_stalker.cpp | 5 +- dlls/hl2_dll/npc_strider.cpp | 8 +- dlls/hl2_dll/npc_turret_ceiling.cpp | 2254 +- dlls/hl2_dll/npc_turret_floor.cpp | 1 + dlls/hl2_dll/npc_vortigaunt.cpp | 5 +- dlls/hl2_dll/prop_combine_ball.cpp | 2 +- dlls/hl2_dll/prop_thumper.cpp | 2 +- dlls/hl2_dll/proto_sniper.cpp | 4 +- dlls/hl2_dll/rotorwash.cpp | 192 +- dlls/hl2_dll/vehicle_airboat.cpp | 13 +- dlls/hl2_dll/vehicle_apc.cpp | 4 +- dlls/hl2_dll/vehicle_jeep.cpp | 4 +- dlls/hl2_dll/vehicle_prisoner_pod.cpp | 4 +- dlls/hl2_dll/weapon_357.cpp | 4 +- dlls/hl2_dll/weapon_ar2.cpp | 8 +- dlls/hl2_dll/weapon_brickbat.cpp | 6 +- dlls/hl2_dll/weapon_bugbait.cpp | 2 +- dlls/hl2_dll/weapon_cguard.cpp | 4 +- dlls/hl2_dll/weapon_citizenpackage.cpp | 1 + dlls/hl2_dll/weapon_citizenpackage.h | 62 +- dlls/hl2_dll/weapon_crossbow.cpp | 10 +- dlls/hl2_dll/weapon_crowbar.cpp | 2 +- dlls/hl2_dll/weapon_physcannon.cpp | 8936 ++--- dlls/hl2_dll/weapon_pistol.cpp | 8 +- dlls/hl2_dll/weapon_rpg.cpp | 16 +- dlls/hl2_dll/weapon_shotgun.cpp | 20 +- dlls/hl2_dll/weapon_smg1.cpp | 8 +- dlls/hl2_dll/weapon_stunstick.cpp | 2 +- dlls/hl2_dll/weapon_thumper.cpp | 4 +- dlls/hl2mp_dll/grenade_satchel.cpp | 2 +- dlls/hl2mp_dll/grenade_tripmine.cpp | 4 +- dlls/hl2mp_dll/hl2mp_bot_temp.cpp | 3 +- dlls/hl2mp_dll/hl2mp_bot_temp.h | 37 +- dlls/hl2mp_dll/hl2mp_cvars.cpp | 64 +- dlls/hl2mp_dll/hl2mp_cvars.h | 40 +- dlls/hl2mp_dll/hl2mp_gameinterface.cpp | 128 +- dlls/hl2mp_dll/hl2mp_gameinterface.h | 40 +- dlls/hl2mp_dll/hl2mp_player.cpp | 3230 +- dlls/hltvdirector.cpp | 12 +- dlls/item_world.cpp | 2 + dlls/lightglow.cpp | 1 + dlls/lights.cpp | 2 +- dlls/logicentities.cpp | 8 +- dlls/movement.cpp | 4 +- dlls/nav.h | 7 + dlls/nav_area.cpp | 22 +- dlls/nav_colors.h | 138 +- dlls/nav_file.cpp | 6 +- dlls/nav_generate.cpp | 15 +- dlls/nav_ladder.cpp | 2 + dlls/nav_mesh.cpp | 2 +- dlls/npc_talker.cpp | 4 +- dlls/npc_talker.h | 2 +- dlls/npc_vehicledriver.cpp | 2 + dlls/physics.cpp | 8 +- dlls/physics_cannister.cpp | 2 +- dlls/physics_impact_damage.cpp | 2 +- dlls/physics_main.cpp | 6 +- dlls/physics_prop_ragdoll.cpp | 2 +- dlls/physobj.cpp | 2 +- dlls/player.cpp | 16265 +++++---- dlls/player_lagcompensation.cpp | 2 +- dlls/player_resource.cpp | 2 +- dlls/playerinfomanager.cpp | 268 +- dlls/playerinfomanager.h | 64 +- dlls/point_spotlight.cpp | 4 +- dlls/props.cpp | 18 +- dlls/recipientfilter.cpp | 746 +- dlls/sceneentity.cpp | 16 +- dlls/sceneentity.h | 92 +- dlls/scripted.cpp | 10 +- dlls/sdk/sdk_bot_temp.cpp | 2 +- dlls/sdk/sdk_env_sparkler.cpp | 208 +- dlls/sdk/sdk_logicalentity.cpp | 132 +- dlls/sdk/sdk_vehicle_jeep.cpp | 4 +- dlls/sendproxy.cpp | 7 +- dlls/server_hl2-2003.vcproj | 66 +- dlls/server_hl2-2005.vcproj | 140 +- dlls/server_hl2mp-2003.vcproj | 236 +- dlls/server_hl2mp-2005.vcproj | 362 +- dlls/server_scratch-2003.vcproj | 184 +- dlls/server_scratch-2005.vcproj | 306 +- dlls/sound.cpp | 8 +- dlls/soundent.cpp | 2 +- dlls/soundscape.cpp | 4 +- dlls/soundscape_system.cpp | 600 +- dlls/tanktrain.cpp | 2 +- dlls/te.h | 34 +- dlls/te_basebeam.h | 118 +- dlls/te_beamentpoint.cpp | 308 +- dlls/te_beaments.cpp | 268 +- dlls/te_beamfollow.cpp | 224 +- dlls/te_beamlaser.cpp | 254 +- dlls/te_beampoints.cpp | 288 +- dlls/te_beamring.cpp | 270 +- dlls/te_beamringpoint.cpp | 248 +- dlls/te_beamspline.cpp | 268 +- dlls/te_bloodsprite.cpp | 302 +- dlls/te_bloodstream.cpp | 270 +- dlls/te_breakmodel.cpp | 292 +- dlls/te_bspdecal.cpp | 250 +- dlls/te_bubbles.cpp | 274 +- dlls/te_bubbletrail.cpp | 280 +- dlls/te_decal.cpp | 262 +- dlls/te_dynamiclight.cpp | 288 +- dlls/te_explosion.cpp | 264 +- dlls/te_fizz.cpp | 224 +- dlls/te_footprintdecal.cpp | 182 +- dlls/te_glowsprite.cpp | 260 +- dlls/te_impact.cpp | 194 +- dlls/te_killplayerattachments.cpp | 184 +- dlls/te_largefunnel.cpp | 206 +- dlls/te_muzzleflash.cpp | 198 +- dlls/te_particlesystem.cpp | 4 - dlls/te_physicsprop.cpp | 266 +- dlls/te_playerdecal.cpp | 248 +- dlls/te_projecteddecal.cpp | 244 +- dlls/te_showline.cpp | 214 +- dlls/te_smoke.cpp | 224 +- dlls/te_sprite.cpp | 242 +- dlls/te_spritespray.cpp | 272 +- dlls/te_worlddecal.cpp | 234 +- dlls/triggers.cpp | 9 +- dlls/util.cpp | 9 +- dlls/util.h | 1239 +- dlls/variant_t.h | 8 +- dlls/vehicle_base.cpp | 5 +- dlls/vehicle_baseserver.cpp | 14 +- dlls/vehicle_choreo_generic.cpp | 1835 +- dlls/wcedit.h | 72 +- dlls/world.cpp | 8 +- game_shared/ModelSoundsCache.cpp | 434 +- game_shared/SoundEmitterSystem.cpp | 4 +- game_shared/Sprite.cpp | 6 +- game_shared/activitylist.h | 2 +- game_shared/ammodef.cpp | 6 +- game_shared/ammodef.h | 206 +- game_shared/animation.cpp | 2 + game_shared/basecombatweapon_shared.cpp | 2 +- game_shared/baseentity_shared.cpp | 5 +- game_shared/basegrenade_shared.cpp | 4 +- game_shared/baseplayer_shared.cpp | 6 +- game_shared/beam_flags.h | 74 +- game_shared/beam_shared.cpp | 6 +- game_shared/debugoverlay_shared.cpp | 2 +- game_shared/effect_color_tables.h | 10 +- game_shared/env_wind_shared.cpp | 2 +- game_shared/eventlist.cpp | 465 +- game_shared/func_ladder.cpp | 4 +- game_shared/gameeventdefs.h | 42 +- game_shared/gamemovement.cpp | 6 +- game_shared/gamestats/ep1_gamestats.cpp | 394 +- game_shared/gamestats/ep1_gamestats.h | 4 +- game_shared/gamestringpool.cpp | 154 +- game_shared/hl2/basehlcombatweapon_shared.cpp | 840 +- game_shared/hl2/hl2_gamerules.cpp | 4 +- game_shared/hl2/hl2_usermessages.cpp | 84 +- game_shared/hl2/survival_gamerules.cpp | 544 +- game_shared/hl2mp/hl2mp_gamerules.cpp | 2474 +- game_shared/hl2mp/hl2mp_gamerules.h | 334 +- game_shared/hl2mp/hl2mp_player_shared.cpp | 1146 +- game_shared/hl2mp/hl2mp_player_shared.h | 1 + game_shared/hl2mp/weapon_crowbar.cpp | 522 +- game_shared/hl2mp/weapon_hl2mpbase.cpp | 4 +- .../hl2mp/weapon_hl2mpbasebasebludgeon.cpp | 1 + .../hl2mp/weapon_hl2mpbasebasebludgeon.h | 132 +- .../weapon_hl2mpbasehlmpcombatweapon.cpp | 832 +- game_shared/hl2mp/weapon_physcannon.cpp | 6 +- game_shared/hl2mp/weapon_rpg.cpp | 4 +- game_shared/hl2mp/weapon_stunstick.cpp | 1792 +- game_shared/in_buttons.h | 80 +- game_shared/mapdata_shared.h | 66 +- game_shared/mapentities_shared.cpp | 2 +- game_shared/physics_main_shared.cpp | 2 +- game_shared/predictableid.cpp | 10 +- game_shared/predicted_viewmodel.cpp | 142 +- game_shared/props_shared.cpp | 10 +- game_shared/rumble_shared.h | 130 +- game_shared/saverestore.cpp | 6758 ++-- game_shared/sceneentity_shared.cpp | 258 +- .../sdk/sdk_basegrenade_projectile.cpp | 2 +- game_shared/sdk/sdk_gamerules.cpp | 2 +- game_shared/sdk/sdk_playeranimstate.cpp | 2 +- game_shared/sdk/weapon_sdkbase.cpp | 326 +- game_shared/sheetsimulator.cpp | 4 +- game_shared/soundenvelope.cpp | 8 +- game_shared/usercmd.cpp | 6 +- game_shared/voice_gamemgr.cpp | 8 +- game_shared/weapon_parse.cpp | 6 +- lib-vc7/public/choreoobjects.lib | Bin 918478 -> 931142 bytes lib-vc7/public/vmpi.lib | Bin 3381440 -> 1252456 bytes lib/public/choreoobjects.lib | Bin 2129102 -> 2237702 bytes lib/public/tier2.lib | Bin 202946 -> 74790 bytes lib/public/vmpi.lib | Bin 3381440 -> 1252020 bytes linux_sdk/Makefile | 66 +- linux_sdk/Makefile.choreoobjects | 45 + linux_sdk/Makefile.game | 8 +- linux_sdk/Makefile.mathlib | 45 + linux_sdk/Makefile.plugin | 16 +- linux_sdk/Makefile.tier1 | 66 + linux_sdk/Makefile.vcpm | 25 +- .../stdshaders/stdshader_dx8-2003.vcproj | 25 +- .../stdshaders/stdshader_dx8-2005.vcproj | 19 +- .../stdshaders/stdshader_dx9-2003.vcproj | 23 +- .../stdshaders/stdshader_dx9-2005.vcproj | 16 +- mathlib/lightdesc.cpp | 10 + mathlib/mathlib_base.cpp | 33 +- notes.txt | 18 + public/IceKey.cpp | 3 +- public/ScratchPadUtils.h | 1 + .../isoundemittersystembase.h | 12 +- .../isoundemittersystembasev1.h | 2 +- public/UnicodeFileHelpers.cpp | 480 +- public/UtlCachedFileData.h | 6 +- public/anorms.cpp | 362 +- public/bitmap/imageformat.h | 6 +- public/blockingudpsocket.cpp | 316 +- public/blockingudpsocket.h | 78 +- public/bone_setup.cpp | 9931 +++--- public/bspfile.h | 8 +- public/builddisp.cpp | 5993 ++-- public/builddisp.h | 2864 +- public/bumpvects.cpp | 138 +- public/chunkfile.cpp | 1990 +- public/chunkfile.h | 394 +- public/collisionutils.cpp | 5330 +-- public/collisionutils.h | 696 +- public/coordsize.h | 184 +- public/datacache/idatacache.h | 2 + public/datamap.h | 6 +- public/datamodel/idatamodel.h | 4 +- public/disp_powerinfo.cpp | 1160 +- public/dispcoll.cpp | 3988 +-- public/dispcoll.h | 498 +- public/dlight.h | 168 +- public/dt_recv.cpp | 811 +- public/dt_send.cpp | 2 +- public/dt_shared.cpp | 226 +- public/dt_utlvector_common.cpp | 3 +- public/dt_utlvector_recv.h | 120 +- public/eiface.h | 23 +- public/engine/iserverplugin.h | 29 +- public/engine/ivmodelinfo.h | 242 +- public/filesystem_init.cpp | 2288 +- public/iclient.h | 2 +- public/igameevents.h | 385 +- public/ihltv.h | 96 +- public/inetchannel.h | 1 - public/inetchannelinfo.h | 142 +- public/inetmessage.h | 2 - public/inetmsghandler.h | 3 +- public/interpolatortypes.cpp | 880 +- public/kevvaluescompiler.cpp | 872 +- public/keyframe/keyframe.cpp | 3 + public/keyvaluescompiler.h | 374 +- public/loadcmdline.cpp | 246 +- public/materialsystem/shader_vcs_version.h | 127 +- public/mathlib/math_base.h | 19 +- public/mathlib/ssemath.h | 803 +- public/measure_section.cpp | 542 +- public/measure_section.h | 254 +- public/model_types.h | 74 +- public/networkvar.h | 11 +- public/resourcetags.cpp | 56 +- public/riff.cpp | 1010 +- public/scratchpad3d.cpp | 2 +- public/sentence.cpp | 3134 +- public/shattersurfacetypes.h | 42 +- public/string_t.h | 4 +- public/studio.h | 12 +- public/studio_generic_io.cpp | 312 +- public/tier0/basetypes.h | 3 +- public/tier0/commonmacros.h | 92 +- public/tier0/dbg.h | 20 +- public/tier0/fasttimer.h | 4 +- public/tier0/icommandline.h | 112 +- public/tier0/l2cache.h | 92 +- public/tier0/memalloc.h | 2 +- public/tier0/memdbgon.h | 6 +- public/tier0/memoverride-vc7.cpp | 1156 +- public/tier0/platform.h | 22 +- public/tier0/threadtools.h | 13 +- public/tier0/vprof.h | 5 +- public/tier0/wchartypes.h | 5 +- public/tier1/UtlSortVector.h | 42 +- public/tier1/bitbuf.h | 3 +- public/tier1/interface.h | 412 +- public/tier1/jobthread.h | 4 +- public/tier1/stringpool.h | 2 +- public/tier1/tier1.h | 8 +- public/tier1/tokenreader.h | 4 +- public/tier1/utlfixedlinkedlist.h | 2 +- public/tier1/utlfixedmemory.h | 7 +- public/tier1/utllinkedlist.h | 1818 +- public/tier1/utlmemory.h | 12 +- public/tier1/utlmultilist.h | 1458 +- public/tier1/utlrbtree.h | 20 +- public/tools/bonelist.cpp | 144 +- public/trace.h | 138 +- public/vector.h | 35 +- public/vector2d.h | 2 - public/vector4d.h | 2 - public/vgui_controls/MessageMap.h | 16 +- public/vmatrix.cpp | 2 + public/vphysics/collision_set.h | 40 +- public/worldsize.h | 84 +- tier1/KeyValues.cpp | 14 +- tier1/NetAdr.cpp | 2 +- tier1/bitbuf.cpp | 5 +- tier1/byteswap.cpp | 2 +- tier1/characterset.cpp | 2 +- tier1/generichash.cpp | 7 + tier1/mempool.cpp | 2 +- tier1/memstack.cpp | 14 +- tier1/strtools.cpp | 34 +- tier1/utlbuffer.cpp | 22 +- tier1/utlsymbol.cpp | 10 +- utils/demoinfo/demoinfo-2003.vcproj | 52 +- utils/demoinfo/demoinfo-2005.vcproj | 112 +- utils/glview/glview-2003.vcproj | 58 +- utils/glview/glview-2005.vcproj | 17 +- utils/height2normal/height2normal-2003.vcproj | 24 +- utils/height2normal/height2normal-2005.vcproj | 18 +- utils/hlmv/controlpanel.cpp | 10 +- utils/hlmv/hlmv-2005.vcproj | 5 +- utils/hlmv/studio_utils.cpp | 2 +- utils/hlmv/viewersettings.cpp | 6 +- utils/motionmapper/motionmapper-2003.vcproj | 20 +- utils/motionmapper/motionmapper-2005.vcproj | 17 +- .../phonemeextractor-2003.vcproj | 71 +- .../phonemeextractor-2005.vcproj | 240 +- utils/qc_eyes/QC_Eyes-2003.vcproj | 25 +- utils/qc_eyes/QC_Eyes-2005.vcproj | 17 +- utils/qc_eyes/QC_Eyes.cpp | 11 - .../serverplugin_sample/serverplugin_bot.cpp | 2 +- .../serverplugin_empty-2003.vcproj | 16 +- .../serverplugin_empty-2005.vcproj | 13 +- .../serverplugin_empty.cpp | 2 +- utils/studiomdl/simplify.cpp | 2 +- utils/studiomdl/studiomdl-2003.vcproj | 28 +- utils/studiomdl/studiomdl-2005.vcproj | 17 +- utils/studiomdl/studiomdl.cpp | 2 +- utils/tgadiff/tgadiff-2003.vcproj | 24 +- utils/tgadiff/tgadiff-2005.vcproj | 13 +- utils/vbsp/vbsp-2003.vcproj | 36 +- utils/vbsp/vbsp-2005.vcproj | 18 +- utils/vice/vice-2003.vcproj | 31 +- utils/vice/vice-2005.vcproj | 22 +- utils/vice/vice.cpp | 6 +- utils/vprojtomake/makefilecreator.cpp | 558 +- utils/vprojtomake/vcprojconvert.cpp | 28 +- utils/vprojtomake/vprojtomake-2003.vcproj | 50 +- utils/vprojtomake/vprojtomake-2005.vcproj | 126 +- utils/vrad/vrad-2003.vcproj | 36 +- utils/vrad/vrad-2005.vcproj | 16 +- utils/vrad_launcher/vrad_launcher-2003.vcproj | 38 +- utils/vrad_launcher/vrad_launcher-2005.vcproj | 109 +- utils/vtf2tga/vtf2tga-2003.vcproj | 28 +- utils/vtf2tga/vtf2tga-2005.vcproj | 18 +- utils/vtfdiff/vtfdiff-2003.vcproj | 32 +- utils/vtfdiff/vtfdiff-2005.vcproj | 23 +- utils/vvis/mpivis.cpp | 2 +- utils/vvis/vvis-2003.vcproj | 90 +- utils/vvis/vvis-2005.vcproj | 17 +- utils/vvis_launcher/vvis_launcher-2003.vcproj | 40 +- utils/vvis_launcher/vvis_launcher-2005.vcproj | 115 +- utils/xwad/goldsrc_standin.h | 8 + utils/xwad/wadlib.h | 8 + utils/xwad/xwad-2003.vcproj | 56 +- utils/xwad/xwad-2005.vcproj | 48 +- 543 files changed, 100067 insertions(+), 91817 deletions(-) create mode 100644 choreoobjects/choreoactor.cpp create mode 100644 choreoobjects/choreochannel.cpp create mode 100644 choreoobjects/choreoevent.cpp create mode 100644 choreoobjects/choreoobjects-2003.vcproj create mode 100644 choreoobjects/choreoobjects-2005.vcproj create mode 100644 choreoobjects/choreoscene.cpp create mode 100644 linux_sdk/Makefile.choreoobjects create mode 100644 linux_sdk/Makefile.mathlib create mode 100644 linux_sdk/Makefile.tier1 create mode 100644 notes.txt diff --git a/Everything_SDK-2003.sln b/Everything_SDK-2003.sln index 7909ae42..17e23ecb 100644 --- a/Everything_SDK-2003.sln +++ b/Everything_SDK-2003.sln @@ -110,14 +110,20 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tier1", "tier1\tier1-2003.v ProjectSection(ProjectDependencies) = postProject EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "phonemeextractor", "utils\phonemeextractor\phonemeextractor-2003.vcproj", "{8A35F55B-B5C2-47A0-8C4A-5857A8E20385}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "choreoobjects", "choreoobjects\choreoobjects-2003.vcproj", "{E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlmv", "utils\hlmv\hlmv-2003.vcproj", "{F3704DBF-8055-413C-B027-399E5DBCA4DD}" ProjectSection(ProjectDependencies) = postProject {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlmv", "utils\hlmv\hlmv-2003.vcproj", "{F3704DBF-8055-413C-B027-399E5DBCA4DD}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "phonemeextractor", "utils\phonemeextractor\phonemeextractor-2003.vcproj", "{8A35F55B-B5C2-47A0-8C4A-5857A8E20385}" ProjectSection(ProjectDependencies) = postProject + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Global @@ -212,14 +218,18 @@ Global {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Debug.Build.0 = Debug|Win32 {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Release.ActiveCfg = Release|Win32 {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Release.Build.0 = Release|Win32 - {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug.ActiveCfg = Debug|Win32 - {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug.Build.0 = Debug|Win32 - {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release.ActiveCfg = Release|Win32 - {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release.Build.0 = Release|Win32 + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Debug.ActiveCfg = Debug|Win32 + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Debug.Build.0 = Debug|Win32 + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Release.ActiveCfg = Release|Win32 + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Release.Build.0 = Release|Win32 {F3704DBF-8055-413C-B027-399E5DBCA4DD}.Debug.ActiveCfg = Debug|Win32 {F3704DBF-8055-413C-B027-399E5DBCA4DD}.Debug.Build.0 = Debug|Win32 {F3704DBF-8055-413C-B027-399E5DBCA4DD}.Release.ActiveCfg = Release|Win32 {F3704DBF-8055-413C-B027-399E5DBCA4DD}.Release.Build.0 = Release|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug.ActiveCfg = Debug|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug.Build.0 = Debug|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release.ActiveCfg = Release|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release.Build.0 = Release|Win32 EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution EndGlobalSection diff --git a/Everything_SDK-2005.sln b/Everything_SDK-2005.sln index 18e77466..cfea827d 100644 --- a/Everything_SDK-2005.sln +++ b/Everything_SDK-2005.sln @@ -2,38 +2,38 @@ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glview", "utils\glview\glview-2005.vcproj", "{BD1604CA-F401-4C4B-821C-251F5AE157FE}" ProjectSection(ProjectDependencies) = postProject - {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "height2normal", "utils\height2normal\height2normal-2005.vcproj", "{0FDD99E4-130F-493C-8202-4C0236CC47EA}" ProjectSection(ProjectDependencies) = postProject - {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "serverplugin_empty", "utils\serverplugin_sample\serverplugin_empty-2005.vcproj", "{B6572CBE-7C7F-4FFF-94DE-AFBBBAB11C64}" ProjectSection(ProjectDependencies) = postProject - {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "studiomdl", "utils\studiomdl\studiomdl-2005.vcproj", "{AE28536E-885A-41BF-99A4-41A7E424B869}" ProjectSection(ProjectDependencies) = postProject - {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tgadiff", "utils\tgadiff\tgadiff-2005.vcproj", "{0CE0AF8A-A977-4538-9D63-BCB76DE1BAC6}" ProjectSection(ProjectDependencies) = postProject - {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vbsp", "utils\vbsp\vbsp-2005.vcproj", "{B78B6271-B19A-4CF6-926E-40B643548E23}" ProjectSection(ProjectDependencies) = postProject - {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vice", "utils\vice\vice-2005.vcproj", "{3CE6E7A9-89EC-4304-8D72-5B602B4DBD09}" @@ -48,8 +48,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vprojtomake", "utils\vprojt EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vrad", "utils\vrad\vrad-2005.vcproj", "{FC0F5DE3-F09F-4EF6-98F9-BA762FFF268D}" ProjectSection(ProjectDependencies) = postProject - {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vrad_launcher", "utils\vrad_launcher\vrad_launcher-2005.vcproj", "{914F19DF-64EC-4E7D-8B01-76477BF06479}" @@ -59,20 +59,20 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vrad_launcher", "utils\vrad EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vtf2tga", "utils\vtf2tga\vtf2tga-2005.vcproj", "{2A1F656C-053C-46A4-AE33-304C1D865E0C}" ProjectSection(ProjectDependencies) = postProject - {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vtfdiff", "utils\vtfdiff\vtfdiff-2005.vcproj", "{0A368DE7-D34A-48D3-B517-996BFF2D0D5D}" ProjectSection(ProjectDependencies) = postProject - {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vvis", "utils\vvis\vvis-2005.vcproj", "{5B065E70-6EE0-4B47-BA64-113D2F81220D}" ProjectSection(ProjectDependencies) = postProject - {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vvis_launcher", "utils\vvis_launcher\vvis_launcher-2005.vcproj", "{4C0B9915-E8FF-4089-8927-FC934BFC1E4A}" @@ -91,8 +91,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "demoinfo", "utils\demoinfo\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "motionmapper", "utils\motionmapper\motionmapper-2005.vcproj", "{A882FC08-8B92-4D4F-89BF-75BCEC2BAE11}" ProjectSection(ProjectDependencies) = postProject - {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QC_Eyes", "utils\qc_eyes\QC_Eyes-2005.vcproj", "{D373436F-7DBF-468B-A3E4-601FB8556544}" @@ -101,14 +101,20 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mathlib", "mathlib\mathlib- EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tier1", "tier1\tier1-2005.vcproj", "{E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "choreoobjects", "choreoobjects\choreoobjects-2005.vcproj", "{0A940BE3-0C70-4D6F-A9F8-C39B483E3796}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlmv", "utils\hlmv\hlmv-2005.vcproj", "{F3704DBF-8055-413C-B027-399E5DBCA4DD}" + ProjectSection(ProjectDependencies) = postProject + {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} + {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} + EndProjectSection +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "phonemeextractor", "utils\phonemeextractor\phonemeextractor-2005.vcproj", "{8A35F55B-B5C2-47A0-8C4A-5857A8E20385}" ProjectSection(ProjectDependencies) = postProject {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720} {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} = {E1DA9FB8-FB4C-4B14-91A6-98BCED6B9720} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlmv", "utils\hlmv\hlmv-2005.vcproj", "{F3704DBF-8055-413C-B027-399E5DBCA4DD}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -199,14 +205,18 @@ Global {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Debug|Win32.Build.0 = Debug|Win32 {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Release|Win32.ActiveCfg = Release|Win32 {E1DA8DB8-FB4C-4B14-91A6-98BCED6B9720}.Release|Win32.Build.0 = Release|Win32 - {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug|Win32.ActiveCfg = Debug|Win32 - {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug|Win32.Build.0 = Debug|Win32 - {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release|Win32.ActiveCfg = Release|Win32 - {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release|Win32.Build.0 = Release|Win32 + {0A940BE3-0C70-4D6F-A9F8-C39B483E3796}.Debug|Win32.ActiveCfg = Debug|Win32 + {0A940BE3-0C70-4D6F-A9F8-C39B483E3796}.Debug|Win32.Build.0 = Debug|Win32 + {0A940BE3-0C70-4D6F-A9F8-C39B483E3796}.Release|Win32.ActiveCfg = Release|Win32 + {0A940BE3-0C70-4D6F-A9F8-C39B483E3796}.Release|Win32.Build.0 = Release|Win32 {F3704DBF-8055-413C-B027-399E5DBCA4DD}.Debug|Win32.ActiveCfg = Debug|Win32 {F3704DBF-8055-413C-B027-399E5DBCA4DD}.Debug|Win32.Build.0 = Debug|Win32 {F3704DBF-8055-413C-B027-399E5DBCA4DD}.Release|Win32.ActiveCfg = Release|Win32 {F3704DBF-8055-413C-B027-399E5DBCA4DD}.Release|Win32.Build.0 = Release|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug|Win32.ActiveCfg = Debug|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Debug|Win32.Build.0 = Debug|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release|Win32.ActiveCfg = Release|Win32 + {8A35F55B-B5C2-47A0-8C4A-5857A8E20385}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/choreoobjects/choreoactor.cpp b/choreoobjects/choreoactor.cpp new file mode 100644 index 00000000..cc63861a --- /dev/null +++ b/choreoobjects/choreoactor.cpp @@ -0,0 +1,293 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include +#include +#include +#include "choreoactor.h" +#include "choreochannel.h" +#include "choreoscene.h" +#include "tier1/utlbuffer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoActor::CChoreoActor( void ) +{ + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CChoreoActor::CChoreoActor( const char *name ) +{ + Init(); + SetName( name ); +} + +//----------------------------------------------------------------------------- +// Purpose: // Assignment +// Input : src - +// Output : CChoreoActor& +//----------------------------------------------------------------------------- +CChoreoActor& CChoreoActor::operator=( const CChoreoActor& src ) +{ + m_bActive = src.m_bActive; + + Q_strncpy( m_szName, src.m_szName, sizeof( m_szName ) ); + Q_strncpy( m_szFacePoserModelName, src.m_szFacePoserModelName, sizeof( m_szFacePoserModelName ) ); + + for ( int i = 0; i < src.m_Channels.Size(); i++ ) + { + CChoreoChannel *c = src.m_Channels[ i ]; + CChoreoChannel *newChannel = new CChoreoChannel(); + newChannel->SetActor( this ); + *newChannel = *c; + AddChannel( newChannel ); + } + + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoActor::Init( void ) +{ + m_szName[ 0 ] = 0; + m_szFacePoserModelName[ 0 ] = 0; + m_bActive = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CChoreoActor::SetName( const char *name ) +{ + assert( strlen( name ) < MAX_ACTOR_NAME ); + Q_strncpy( m_szName, name, sizeof( m_szName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoActor::GetName( void ) +{ + return m_szName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoActor::GetNumChannels( void ) +{ + return m_Channels.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : channel - +// Output : CChoreoChannel +//----------------------------------------------------------------------------- +CChoreoChannel *CChoreoActor::GetChannel( int channel ) +{ + if ( channel < 0 || channel >= m_Channels.Size() ) + { + return NULL; + } + + return m_Channels[ channel ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoActor::AddChannel( CChoreoChannel *channel ) +{ + m_Channels.AddToTail( channel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoActor::RemoveChannel( CChoreoChannel *channel ) +{ + int idx = FindChannelIndex( channel ); + if ( idx == -1 ) + return; + + m_Channels.Remove( idx ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoActor::RemoveAllChannels() +{ + m_Channels.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : c1 - +// c2 - +//----------------------------------------------------------------------------- +void CChoreoActor::SwapChannels( int c1, int c2 ) +{ + CChoreoChannel *temp; + + temp = m_Channels[ c1 ]; + m_Channels[ c1 ] = m_Channels[ c2 ]; + m_Channels[ c2 ] = temp; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoActor::FindChannelIndex( CChoreoChannel *channel ) +{ + for ( int i = 0; i < m_Channels.Size(); i++ ) + { + if ( channel == m_Channels[ i ] ) + { + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CChoreoActor::SetFacePoserModelName( const char *name ) +{ + Q_strncpy( m_szFacePoserModelName, name, sizeof( m_szFacePoserModelName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +const char *CChoreoActor::GetFacePoserModelName( void ) const +{ + return m_szFacePoserModelName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : active - +//----------------------------------------------------------------------------- +void CChoreoActor::SetActive( bool active ) +{ + m_bActive = active; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoActor::GetActive( void ) const +{ + return m_bActive; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoActor::MarkForSaveAll( bool mark ) +{ + SetMarkedForSave( mark ); + + int c = GetNumChannels(); + for ( int i = 0; i < c; i++ ) + { + CChoreoChannel *channel = GetChannel( i ); + channel->MarkForSaveAll( mark ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : CChoreoChannel +//----------------------------------------------------------------------------- +CChoreoChannel *CChoreoActor::FindChannel( const char *name ) +{ + int c = GetNumChannels(); + for ( int i = 0; i < c; i++ ) + { + CChoreoChannel *channel = GetChannel( i ); + if ( !Q_stricmp( channel->GetName(), name ) ) + return channel; + } + + return NULL; +} + +void CChoreoActor::SaveToBuffer( CUtlBuffer& buf, CChoreoScene *pScene ) +{ + int i, c; + buf.PutString( GetName() ); + + c = GetNumChannels(); + buf.PutShort( c ); + for ( i = 0; i < c; i++ ) + { + CChoreoChannel *channel = GetChannel( i ); + Assert( channel ); + channel->SaveToBuffer( buf, pScene ); + } + + /* + if ( Q_strlen( a->GetFacePoserModelName() ) > 0 ) + { + FilePrintf( buf, level + 1, "faceposermodel \"%s\"\n", a->GetFacePoserModelName() ); + } + */ + buf.PutChar( GetActive() ? 1 : 0 ); +} + +bool CChoreoActor::RestoreFromBuffer( CUtlBuffer& buf, CChoreoScene *pScene ) +{ + char sz[ 256 ]; + buf.GetString( sz, sizeof( sz ) ); + + SetName( sz ); + + int i; + int c = buf.GetShort(); + for ( i = 0; i < c; i++ ) + { + CChoreoChannel *channel = pScene->AllocChannel(); + Assert( channel ); + if ( channel->RestoreFromBuffer( buf, pScene, this ) ) + { + AddChannel( channel ); + channel->SetActor( this ); + continue; + } + + return false; + } + + SetActive( buf.GetChar() == 1 ? true : false ); + + return true; +} + diff --git a/choreoobjects/choreochannel.cpp b/choreoobjects/choreochannel.cpp new file mode 100644 index 00000000..fac0e4fd --- /dev/null +++ b/choreoobjects/choreochannel.cpp @@ -0,0 +1,562 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include +#include +#include +#include "choreochannel.h" +#include "choreoevent.h" +#include "choreoscene.h" +#include "utlrbtree.h" +#include "tier1/utlbuffer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoChannel::CChoreoChannel( void ) +{ + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CChoreoChannel::CChoreoChannel(const char *name ) +{ + Init(); + SetName( name ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Assignment +// Input : src - +//----------------------------------------------------------------------------- +CChoreoChannel& CChoreoChannel::operator=( const CChoreoChannel& src ) +{ + m_bActive = src.m_bActive; + Q_strncpy( m_szName, src.m_szName, sizeof( m_szName ) ); + for ( int i = 0; i < src.m_Events.Size(); i++ ) + { + CChoreoEvent *e = src.m_Events[ i ]; + CChoreoEvent *newEvent = new CChoreoEvent( e->GetScene() ); + *newEvent = *e; + AddEvent( newEvent ); + newEvent->SetChannel( this ); + newEvent->SetActor( m_pActor ); + } + + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CChoreoChannel::SetName( const char *name ) +{ + assert( Q_strlen( name ) < MAX_CHANNEL_NAME ); + Q_strncpy( m_szName, name, sizeof( m_szName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoChannel::GetName( void ) +{ + return m_szName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoChannel::GetNumEvents( void ) +{ + return m_Events.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : event - +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoChannel::GetEvent( int event ) +{ + if ( event < 0 || event >= m_Events.Size() ) + { + return NULL; + } + + return m_Events[ event ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoChannel::AddEvent( CChoreoEvent *event ) +{ + m_Events.AddToTail( event ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoChannel::RemoveEvent( CChoreoEvent *event ) +{ + int idx = FindEventIndex( event ); + if ( idx == -1 ) + return; + + m_Events.Remove( idx ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoChannel::RemoveAllEvents() +{ + m_Events.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoChannel::FindEventIndex( CChoreoEvent *event ) +{ + for ( int i = 0; i < m_Events.Size(); i++ ) + { + if ( event == m_Events[ i ] ) + { + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoChannel::Init( void ) +{ + m_szName[ 0 ] = 0; + SetActor( NULL ); + m_bActive = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoActor +//----------------------------------------------------------------------------- +CChoreoActor *CChoreoChannel::GetActor( void ) +{ + return m_pActor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoChannel::SetActor( CChoreoActor *actor ) +{ + m_pActor = actor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : active - +//----------------------------------------------------------------------------- +void CChoreoChannel::SetActive( bool active ) +{ + m_bActive = active; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoChannel::GetActive( void ) const +{ + return m_bActive; +} + +static bool ChoreEventStartTimeLessFunc( CChoreoEvent * const &p1, CChoreoEvent * const &p2 ) +{ + CChoreoEvent *e1; + CChoreoEvent *e2; + + e1 = const_cast< CChoreoEvent * >( p1 ); + e2 = const_cast< CChoreoEvent * >( p2 ); + + return e1->GetStartTime() < e2->GetStartTime(); +} + +void CChoreoChannel::ReconcileGestureTimes() +{ + // Sort gesture events within channel by starting time + CUtlRBTree< CChoreoEvent * > sortedGestures( 0, 0, ChoreEventStartTimeLessFunc ); + int i; + // Sort items + int c = GetNumEvents(); + for ( i = 0; i < c; i++ ) + { + CChoreoEvent *e = GetEvent( i ); + Assert( e ); + if ( e->GetType() != CChoreoEvent::GESTURE ) + continue; + + sortedGestures.Insert( e ); + } + + // Now walk list of gestures + if ( !sortedGestures.Count() ) + return; + + CChoreoEvent *previous = NULL; + + for ( i = sortedGestures.FirstInorder(); i != sortedGestures.InvalidIndex(); i = sortedGestures.NextInorder( i ) ) + { + CChoreoEvent *event = sortedGestures[ i ]; + + if ( !previous ) + { + // event->SetStartTime( 0.0f ); + } + else if ( previous->GetSyncToFollowingGesture() ) + { + // TODO: ask the sequence for what tags to match + + CEventAbsoluteTag *pEntryTag = event->FindEntryTag( CChoreoEvent::PLAYBACK ); + CEventAbsoluteTag *pExitTag = previous->FindExitTag( CChoreoEvent::PLAYBACK ); + + if (pEntryTag && pExitTag) + { + float entryTime = pEntryTag->GetAbsoluteTime( ); + + // get current decay rate of previous gesture + float duration = previous->GetDuration(); + float decayTime = (1.0 - pExitTag->GetPercentage()) * duration; + + // adjust the previous gestures end time to current apex + existing decay rate + previous->RescaleGestureTimes( previous->GetStartTime(), entryTime + decayTime ); + previous->SetEndTime( entryTime + decayTime ); + + // set the previous gestures end tag to the current apex + pExitTag->SetAbsoluteTime( entryTime ); + + event->PreventTagOverlap( ); + previous->PreventTagOverlap( ); + } + // BUG: Tracker 3298: ywb 1/31/04 + // I think this fixes the issue with abutting past NULL gestures on paste: + // Here's the bug report: + // ------------------------- + // When copying and pasteing posture and gesture clips in face poser the beginings of the clips stretch + // to the begining of the scene even if there is a null gesture in place at the begining. + // ------------------------- + /* + else if ( pEntryTag && !Q_stricmp( previous->GetName(), "NULL" ) ) + { + // If the previous was a null event, then do a bit of fixup + event->SetStartTime( previous->GetEndTime() ); + + event->PreventTagOverlap( ); + } + */ + + // The previous event decays from it's end dispaly end time to the current event's display start time + // The next event starts just after the display end time of the previous event + } + + previous = event; + } + + if ( previous ) + { + CChoreoScene *scene = previous->GetScene(); + if ( scene ) + { + // HACK: Could probably do better by allowing user to drag the blue "end time" bar + //float finish = scene->FindStopTime(); + //previous->RescaleGestureTimes( previous->GetStartTime(), finish ); + //previous->SetEndTime( finish ); + } + } + + /* + c = 0; + for ( i = sortedGestures.FirstInorder(); i != sortedGestures.InvalidIndex(); i = sortedGestures.NextInorder( i ) ) + { + CChoreoEvent *event = sortedGestures[ i ]; + + Msg( "event %i start %f disp %f dispend %f end %f\n", + c + 1, + event->GetStartTime( CChoreoEvent::SIMULATION ), + event->GetStartTime( CChoreoEvent::DISPLAY ), + event->GetEndTime( CChoreoEvent::DISPLAY ), + event->GetEndTime( CChoreoEvent::SIMULATION ) + ); + c++; + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoChannel::MarkForSaveAll( bool mark ) +{ + SetMarkedForSave( mark ); + + int c = GetNumEvents(); + for ( int i = 0; i < c; i++ ) + { + CChoreoEvent *e = GetEvent( i ); + e->SetMarkedForSave( mark ); + } +} + + +struct EventGroup +{ + EventGroup() : + timeSortedEvents( 0, 0, ChoreEventStartTimeLessFunc ) + { + } + + EventGroup( const EventGroup& src ) + : + timeSortedEvents( 0, 0, ChoreEventStartTimeLessFunc ) + { + timeSortedEvents.RemoveAll(); + int i = src.timeSortedEvents.FirstInorder(); + while ( i != src.timeSortedEvents.InvalidIndex() ) + { + timeSortedEvents.Insert( src.timeSortedEvents[ i ] ); + + i = src.timeSortedEvents.NextInorder( i ); + } + } + + EventGroup & operator=( const EventGroup& src ) + { + if ( this == &src ) + return *this; + + timeSortedEvents.RemoveAll(); + int i = src.timeSortedEvents.FirstInorder(); + while ( i != src.timeSortedEvents.InvalidIndex() ) + { + timeSortedEvents.Insert( src.timeSortedEvents[ i ] ); + + i = src.timeSortedEvents.NextInorder( i ); + } + return *this; + } + + CUtlRBTree< CChoreoEvent * > timeSortedEvents; +}; + +// Compute master/slave, count, endtime info for close captioning data +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoChannel::ReconcileCloseCaption() +{ + // Create a dictionary based on the combined token name + CUtlDict< EventGroup, int > validSpeakEventsGroupedByName; + + int i; + // Sort items + int c = GetNumEvents(); + for ( i = 0; i < c; i++ ) + { + CChoreoEvent *e = GetEvent( i ); + Assert( e ); + if ( e->GetType() != CChoreoEvent::SPEAK ) + continue; + + CChoreoEvent::CLOSECAPTION type; + + type = e->GetCloseCaptionType(); + if ( type == CChoreoEvent::CC_DISABLED ) + { + e->SetUsingCombinedFile( false ); + e->SetRequiredCombinedChecksum( 0 ); + e->SetNumSlaves( 0 ); + e->SetLastSlaveEndTime( 0.0f ); + continue; + } + + char const *name = e->GetCloseCaptionToken(); + if ( !name || !name[0] ) + { + // Fixup invalid slave tag + if ( type == CChoreoEvent::CC_SLAVE ) + { + e->SetCloseCaptionType( CChoreoEvent::CC_MASTER ); + e->SetUsingCombinedFile( false ); + e->SetRequiredCombinedChecksum( 0 ); + e->SetNumSlaves( 0 ); + e->SetLastSlaveEndTime( 0.0f ); + } + continue; + } + + int idx = validSpeakEventsGroupedByName.Find( name ); + if ( idx == validSpeakEventsGroupedByName.InvalidIndex() ) + { + EventGroup eg; + eg.timeSortedEvents.Insert( e ); + validSpeakEventsGroupedByName.Insert( name, eg ); + } + else + { + EventGroup & eg = validSpeakEventsGroupedByName[ idx ]; + eg.timeSortedEvents.Insert( e ); + } + } + + c = validSpeakEventsGroupedByName.Count(); + // Now walk list of events by group + if ( !c ) + { + return; + } + + for ( i = 0; i < c; ++i ) + { + EventGroup & eg = validSpeakEventsGroupedByName[ i ]; + int sortedEventInGroup = eg.timeSortedEvents.Count(); + // If there's only one, just mark it valid + if ( sortedEventInGroup <= 1 ) + { + CChoreoEvent *e = eg.timeSortedEvents[ 0 ]; + Assert( e ); + // Make sure it's the master + e->SetCloseCaptionType( CChoreoEvent::CC_MASTER ); + // Since it's by itself, can't be using "combined" file + e->SetUsingCombinedFile( false ); + e->SetRequiredCombinedChecksum( 0 ); + e->SetNumSlaves( 0 ); + e->SetLastSlaveEndTime( 0.0f ); + continue; + } + + // Okay, read them back in of start time + int j = eg.timeSortedEvents.FirstInorder(); + CChoreoEvent *master = NULL; + while ( j != eg.timeSortedEvents.InvalidIndex() ) + { + CChoreoEvent *e = eg.timeSortedEvents[ j ]; + if ( !master ) + { + master = e; + e->SetCloseCaptionType( CChoreoEvent::CC_MASTER ); + //e->SetUsingCombinedFile( true ); + e->SetRequiredCombinedChecksum( 0 ); + e->SetNumSlaves( sortedEventInGroup - 1 ); + e->SetLastSlaveEndTime( e->GetEndTime() ); + } + else + { + // Keep bumping out the end time + master->SetLastSlaveEndTime( e->GetEndTime() ); + e->SetCloseCaptionType( CChoreoEvent::CC_SLAVE ); + e->SetUsingCombinedFile( master->IsUsingCombinedFile() ); + e->SetRequiredCombinedChecksum( 0 ); + e->SetLastSlaveEndTime( 0.0f ); + } + + j = eg.timeSortedEvents.NextInorder( j ); + } + } +} + +bool CChoreoChannel::GetSortedCombinedEventList( char const *cctoken, CUtlRBTree< CChoreoEvent * >& events ) +{ + events.RemoveAll(); + + int i; + // Sort items + int c = GetNumEvents(); + for ( i = 0; i < c; i++ ) + { + CChoreoEvent *e = GetEvent( i ); + Assert( e ); + if ( e->GetType() != CChoreoEvent::SPEAK ) + continue; + + if ( e->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED ) + continue; + + // A master with no slaves is not a combined event + if ( e->GetCloseCaptionType() == CChoreoEvent::CC_MASTER && + e->GetNumSlaves() == 0 ) + continue; + + char const *token = e->GetCloseCaptionToken(); + if ( Q_stricmp( token, cctoken ) ) + continue; + + events.Insert( e ); + } + + return ( events.Count() > 0 ) ? true : false; +} + +void CChoreoChannel::SaveToBuffer( CUtlBuffer& buf, CChoreoScene *pScene ) +{ + buf.PutString( GetName() ); + int c = GetNumEvents(); + Assert( c <= 65535 ); + + buf.PutShort( c ); + for ( int i = 0; i < c; i++ ) + { + CChoreoEvent *e = GetEvent( i ); + Assert( e ); + e->SaveToBuffer( buf, pScene ); + } + + buf.PutChar( GetActive() ? 1 : 0 ); +} + +bool CChoreoChannel::RestoreFromBuffer( CUtlBuffer& buf, CChoreoScene *pScene, CChoreoActor *pActor ) +{ + char sz[ 256 ]; + buf.GetString( sz, sizeof( sz ) ); + SetName( sz ); + + int numEvents = (int)buf.GetShort(); + for ( int i = 0 ; i < numEvents; ++i ) + { + CChoreoEvent *e = pScene->AllocEvent(); + if ( e->RestoreFromBuffer( buf, pScene ) ) + { + AddEvent( e ); + e->SetChannel( this ); + e->SetActor( pActor ); + continue; + } + return false; + } + + SetActive( buf.GetChar() == 1 ? true : false ); + + return true; +} diff --git a/choreoobjects/choreoevent.cpp b/choreoobjects/choreoevent.cpp new file mode 100644 index 00000000..3cc1c217 --- /dev/null +++ b/choreoobjects/choreoevent.cpp @@ -0,0 +1,4204 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier0/dbg.h" +#include +#include +#include +#include "choreoevent.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "minmax.h" +#include "mathlib.h" +#include "vstdlib/strtools.h" +#include "choreoscene.h" +#include "ichoreoeventcallback.h" +#include "tier1/utlbuffer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +int CChoreoEvent::s_nGlobalID = 1; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *owner - +// *name - +// percentage - +//----------------------------------------------------------------------------- +CEventRelativeTag::CEventRelativeTag( CChoreoEvent *owner, const char *name, float percentage ) +{ + Assert( owner ); + Assert( name ); + Assert( percentage >= 0.0f ); + Assert( percentage <= 1.0f ); + + m_Name = name; + m_flPercentage = percentage; + m_pOwner = owner; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : src - +//----------------------------------------------------------------------------- +CEventRelativeTag::CEventRelativeTag( const CEventRelativeTag& src ) +{ + m_Name = src.m_Name; + m_flPercentage = src.m_flPercentage; + m_pOwner = src.m_pOwner; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CEventRelativeTag::GetName( void ) +{ + return m_Name.c_str(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CEventRelativeTag::GetPercentage( void ) +{ + return m_flPercentage; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : percentage - +//----------------------------------------------------------------------------- +void CEventRelativeTag::SetPercentage( float percentage ) +{ + m_flPercentage = percentage; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CEventRelativeTag::GetOwner( void ) +{ + return m_pOwner; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CEventRelativeTag::SetOwner( CChoreoEvent *event ) +{ + m_pOwner = event; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the corrected time based on the owner's length and start time +// Output : float +//----------------------------------------------------------------------------- +float CEventRelativeTag::GetStartTime( void ) +{ + Assert( m_pOwner ); + if ( !m_pOwner ) + { + return 0.0f; + } + + float ownerstart = m_pOwner->GetStartTime(); + float ownerduration = m_pOwner->GetDuration(); + + return ( ownerstart + ownerduration * m_flPercentage ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *owner - +// *name - +// percentage - +//----------------------------------------------------------------------------- +CFlexTimingTag::CFlexTimingTag( CChoreoEvent *owner, const char *name, float percentage, bool locked ) +: BaseClass( owner, name, percentage ) +{ + m_bLocked = locked; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : src - +//----------------------------------------------------------------------------- +CFlexTimingTag::CFlexTimingTag( const CFlexTimingTag& src ) +: BaseClass( src ) +{ + m_bLocked = src.m_bLocked; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFlexTimingTag::GetLocked( void ) +{ + return m_bLocked; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : locked - +//----------------------------------------------------------------------------- +void CFlexTimingTag::SetLocked( bool locked ) +{ + m_bLocked = locked; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *owner - +// *name - +// percentage - +//----------------------------------------------------------------------------- +CEventAbsoluteTag::CEventAbsoluteTag( CChoreoEvent *owner, const char *name, float t ) +{ + Assert( owner ); + Assert( name ); + Assert( t >= 0.0f ); + + m_Name = name; + m_flPercentage = t; + m_pOwner = owner; + m_bLocked = false; + m_bLinear = false; + m_bEntry = false; + m_bExit = false; + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : src - +//----------------------------------------------------------------------------- +CEventAbsoluteTag::CEventAbsoluteTag( const CEventAbsoluteTag& src ) +{ + m_Name = src.m_Name; + m_flPercentage = src.m_flPercentage; + m_pOwner = src.m_pOwner; + m_bLocked = src.m_bLocked; + m_bLinear = src.m_bLinear; + m_bEntry = src.m_bEntry; + m_bExit = src.m_bExit; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CEventAbsoluteTag::GetName( void ) +{ + return m_Name.c_str(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CEventAbsoluteTag::GetPercentage( void ) +{ + return m_flPercentage; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : percentage - +//----------------------------------------------------------------------------- +void CEventAbsoluteTag::SetPercentage( float percentage ) +{ + m_flPercentage = percentage; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CEventAbsoluteTag::GetEventTime( void ) +{ + Assert( m_pOwner ); + if ( !m_pOwner ) + { + return 0.0f; + } + + float ownerduration = m_pOwner->GetDuration(); + + return (m_flPercentage * ownerduration); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : percentage - +//----------------------------------------------------------------------------- +void CEventAbsoluteTag::SetEventTime( float t ) +{ + Assert( m_pOwner ); + if ( !m_pOwner ) + { + return; + } + + float ownerduration = m_pOwner->GetDuration(); + + m_flPercentage = (t / ownerduration); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CEventAbsoluteTag::GetAbsoluteTime( void ) +{ + Assert( m_pOwner ); + if ( !m_pOwner ) + { + return 0.0f; + } + + float ownerstart = m_pOwner->GetStartTime(); + float ownerduration = m_pOwner->GetDuration(); + + return (ownerstart + m_flPercentage * ownerduration); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : percentage - +//----------------------------------------------------------------------------- +void CEventAbsoluteTag::SetAbsoluteTime( float t ) +{ + Assert( m_pOwner ); + if ( !m_pOwner ) + { + return; + } + + float ownerstart = m_pOwner->GetStartTime(); + float ownerduration = m_pOwner->GetDuration(); + + m_flPercentage = (t - ownerstart) / ownerduration; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CEventAbsoluteTag::GetOwner( void ) +{ + return m_pOwner; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CEventAbsoluteTag::SetOwner( CChoreoEvent *event ) +{ + m_pOwner = event; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CEventAbsoluteTag::SetLocked( bool bLocked ) +{ + m_bLocked = bLocked; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +bool CEventAbsoluteTag::GetLocked( void ) +{ + return m_bLocked; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CEventAbsoluteTag::SetLinear( bool bLinear ) +{ + m_bLinear = bLinear; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +bool CEventAbsoluteTag::GetLinear( void ) +{ + return m_bLinear; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CEventAbsoluteTag::SetEntry( bool bEntry ) +{ + m_bEntry = bEntry; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +bool CEventAbsoluteTag::GetEntry( void ) +{ + return m_bEntry; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CEventAbsoluteTag::SetExit( bool bExit ) +{ + m_bExit = bExit; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +bool CEventAbsoluteTag::GetExit( void ) +{ + return m_bExit; +} + + + + +// FLEX ANIMATIONS +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : *event - +//----------------------------------------------------------------------------- +CFlexAnimationTrack::CFlexAnimationTrack( CChoreoEvent *event ) +{ + m_pEvent = event; + m_pControllerName = NULL; + m_bActive = false; + m_bCombo = false; + m_bServerSide = false; + m_nFlexControllerIndex[ 0 ] = m_nFlexControllerIndex[ 1 ] = -1; + m_nFlexControllerIndexRaw[ 0 ] = m_nFlexControllerIndexRaw[ 1 ] = -1; + + // base track has range, combo is always 0..1 + m_flMin = 0.0f; + m_flMax = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : src - +//----------------------------------------------------------------------------- +CFlexAnimationTrack::CFlexAnimationTrack( const CFlexAnimationTrack* src ) +{ + m_pControllerName = NULL; + SetFlexControllerName( src->m_pControllerName ? src->m_pControllerName : "" ); + + m_bActive = src->m_bActive; + m_bCombo = src->m_bCombo; + m_bServerSide = src->m_bServerSide; + + for ( int t = 0; t < 2; t++ ) + { + m_Samples[ t ].Purge(); + for ( int i = 0 ;i < src->m_Samples[ t ].Size(); i++ ) + { + CExpressionSample s = src->m_Samples[ t ][ i ]; + m_Samples[ t ].AddToTail( s ); + } + } + + for ( int side = 0; side < 2; side++ ) + { + m_nFlexControllerIndex[ side ] = src->m_nFlexControllerIndex[ side ]; + m_nFlexControllerIndexRaw[ side ] = src->m_nFlexControllerIndexRaw[ side ]; + } + + m_flMin = src->m_flMin; + m_flMax = src->m_flMax; + + m_EdgeInfo[ 0 ] = src->m_EdgeInfo[ 0 ]; + m_EdgeInfo[ 1 ] = src->m_EdgeInfo[ 1 ]; + + m_pEvent = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFlexAnimationTrack::~CFlexAnimationTrack( void ) +{ + delete[] m_pControllerName; + + for ( int t = 0; t < 2; t++ ) + { + m_Samples[ t ].Purge(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetEvent( CChoreoEvent *event ) +{ + m_pEvent = event; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::Clear( void ) +{ + for ( int t = 0; t < 2; t++ ) + { + m_Samples[ t ].RemoveAll(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::RemoveSample( int index, int type /*=0*/ ) +{ + Assert( type == 0 || type == 1 ); + + m_Samples[ type ].Remove( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetFlexControllerName( const char *name ) +{ + delete[] m_pControllerName; + size_t len = Q_strlen( name ) + 1; + m_pControllerName = new char[ len ]; + Q_strncpy( m_pControllerName, name, len ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +const char *CFlexAnimationTrack::GetFlexControllerName( void ) +{ + return m_pControllerName ? m_pControllerName : ""; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CFlexAnimationTrack::GetNumSamples( int type /*=0*/ ) +{ + Assert( type == 0 || type == 1 ); + + return m_Samples[ type ].Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : CExpressionSample +//----------------------------------------------------------------------------- +CExpressionSample *CFlexAnimationTrack::GetSample( int index, int type /*=0*/ ) +{ + Assert( type == 0 || type == 1 ); + + if ( index < 0 || index >= GetNumSamples( type ) ) + return NULL; + return &m_Samples[ type ][ index ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFlexAnimationTrack::IsTrackActive( void ) +{ + return m_bActive; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : active - +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetTrackActive( bool active ) +{ + m_bActive = active; +} + +void CFlexAnimationTrack::SetEdgeInfo( bool leftEdge, int curveType, float zero ) +{ + int idx = leftEdge ? 0 : 1; + m_EdgeInfo[ idx ].m_CurveType = curveType; + m_EdgeInfo[ idx ].m_flZeroPos = zero; +} + +void CFlexAnimationTrack::GetEdgeInfo( bool leftEdge, int& curveType, float& zero ) const +{ + int idx = leftEdge ? 0 : 1; + curveType = m_EdgeInfo[ idx ].m_CurveType; + zero = m_EdgeInfo[ idx ].m_flZeroPos; +} + +void CFlexAnimationTrack::SetEdgeActive( bool leftEdge, bool state ) +{ + int idx = leftEdge ? 0 : 1; + m_EdgeInfo[ idx ].m_bActive = state; +} + +bool CFlexAnimationTrack::IsEdgeActive( bool leftEdge ) const +{ + int idx = leftEdge ? 0 : 1; + return m_EdgeInfo[ idx ].m_bActive; +} + +int CFlexAnimationTrack::GetEdgeCurveType( bool leftEdge ) const +{ + if ( !IsEdgeActive( leftEdge ) ) + { + return CURVE_DEFAULT; + } + + int idx = leftEdge ? 0 : 1; + return m_EdgeInfo[ idx ].m_CurveType; +} + +float CFlexAnimationTrack::GetEdgeZeroValue( bool leftEdge ) const +{ + if ( !IsEdgeActive( leftEdge ) ) + { + return 0.0f; + } + + int idx = leftEdge ? 0 : 1; + return m_EdgeInfo[ idx ].m_flZeroPos; +} + +float CFlexAnimationTrack::GetDefaultEdgeZeroPos() const +{ + float zero = 0.0f; + if ( m_flMin != m_flMax ) + { + zero = ( 0.0f - m_flMin ) / ( m_flMax - m_flMin ); + } + return zero; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CFlexAnimationTrack::GetZeroValue( int type, bool leftSide ) +{ + // Stereo track is always clamped to 0.5 and doesn't care about l/r settings + if ( type == 1 ) + { + return 0.5f; + } + + if ( IsEdgeActive( leftSide ) ) + { + return GetEdgeZeroValue( leftSide ); + } + + return GetDefaultEdgeZeroPos(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : number - +// Output : CExpressionSample +//----------------------------------------------------------------------------- +CExpressionSample *CFlexAnimationTrack::GetBoundedSample( int number, bool& bClamped, int type /*=0*/ ) +{ + Assert( type == 0 || type == 1 ); + + if ( number < 0 ) + { + // Search for two samples which span time f + static CExpressionSample nullstart; + nullstart.time = 0.0f; + nullstart.value = GetZeroValue( type, true ); + if ( type == 0 ) + { + nullstart.SetCurveType( GetEdgeCurveType( true ) ); + } + else + { + nullstart.SetCurveType( CURVE_DEFAULT ); + } + bClamped = true; + return &nullstart; + } + else if ( number >= GetNumSamples( type ) ) + { + static CExpressionSample nullend; + nullend.time = m_pEvent->GetDuration(); + nullend.value = GetZeroValue( type, false ); + if ( type == 0 ) + { + nullend.SetCurveType( GetEdgeCurveType( false ) ); + } + else + { + nullend.SetCurveType( CURVE_DEFAULT ); + } + bClamped = true; + return &nullend; + } + + bClamped = false; + return GetSample( number, type ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// type - +// Output : float +//----------------------------------------------------------------------------- +float CFlexAnimationTrack::GetIntensityInternal( float time, int type ) +{ + Assert( type == 0 || type == 1 ); + + float retval = 0.0f; + + // find samples that span the time + if ( !m_pEvent || !m_pEvent->HasEndTime() || time < m_pEvent->GetStartTime() ) + { + retval = GetZeroValue( type, true );; + } + else if ( time > m_pEvent->GetEndTime() ) + { + retval = GetZeroValue( type, false );; + } + else + { + float elapsed = time - m_pEvent->GetStartTime(); + retval = GetFracIntensity( elapsed, type ); + } + + // scale + if (type == 0 && m_flMin != m_flMax) + { + retval = retval * (m_flMax - m_flMin) + m_flMin; + } + return retval; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// type - +// Output : float +//----------------------------------------------------------------------------- +float CFlexAnimationTrack::GetFracIntensity( float time, int type ) +{ + float zeroValueLeft = GetZeroValue( type, true ); + + Assert( type == 0 || type == 1 ); + + // find samples that span the time + if ( !m_pEvent || !m_pEvent->HasEndTime() ) + return zeroValueLeft; + + int rampCount = GetNumSamples( type ); + if ( rampCount < 1 ) + { + return zeroValueLeft; + } + + CExpressionSample *esStart = NULL; + CExpressionSample *esEnd = NULL; + + // do binary search for sample in time period + int j = max( rampCount / 2, 1 ); + int i = j; + while ( i > -2 && i < rampCount + 1 ) + { + bool dummy; + esStart = GetBoundedSample( i, dummy, type ); + esEnd = GetBoundedSample( i + 1, dummy, type ); + + j = max( j / 2, 1 ); + if ( time < esStart->time) + { + i -= j; + } + else if ( time > esEnd->time) + { + i += j; + } + else + { + if ( time == esEnd->time ) + { + ++i; + esStart = GetBoundedSample( i, dummy, type ); + esEnd = GetBoundedSample( i + 1, dummy, type ); + } + break; + } + } + + if (!esStart) + { + return zeroValueLeft; + } + + int prev = i - 1; + int next = i + 2; + + prev = max( -1, prev ); + next = min( next, rampCount ); + + bool clamp[ 2 ]; + CExpressionSample *esPre = GetBoundedSample( prev, clamp[ 0 ], type ); + CExpressionSample *esNext = GetBoundedSample( next, clamp[ 1 ], type ); + + float dt = esEnd->time - esStart->time; + + Vector vPre( esPre->time, esPre->value, 0 ); + Vector vStart( esStart->time, esStart->value, 0 ); + Vector vEnd( esEnd->time, esEnd->value, 0 ); + Vector vNext( esNext->time, esNext->value, 0 ); + + float f2 = 0.0f; + if ( dt > 0.0f ) + { + f2 = ( time - esStart->time ) / ( dt ); + } + f2 = clamp( f2, 0.0f, 1.0f ); + + Vector vOut; + int dummy; + int earlypart, laterpart; + + // Not holding out value of previous curve... + Interpolator_CurveInterpolatorsForType( esStart->GetCurveType(), dummy, earlypart ); + Interpolator_CurveInterpolatorsForType( esEnd->GetCurveType(), laterpart, dummy ); + + if ( earlypart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + VectorLerp( vStart, vEnd, f2, vOut ); + vOut.y = vStart.y; + } + else if ( laterpart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + VectorLerp( vStart, vEnd, f2, vOut ); + vOut.y = vEnd.y; + } + else + { + bool sameCurveType = earlypart == laterpart ? true : false; + if ( sameCurveType ) + { + Interpolator_CurveInterpolate( laterpart, vPre, vStart, vEnd, vNext, f2, vOut ); + } + else // curves differ, sigh + { + Vector vOut1, vOut2; + + Interpolator_CurveInterpolate( earlypart, vPre, vStart, vEnd, vNext, f2, vOut1 ); + Interpolator_CurveInterpolate( laterpart, vPre, vStart, vEnd, vNext, f2, vOut2 ); + + VectorLerp( vOut1, vOut2, f2, vOut ); + } + } + + float retval = clamp( vOut.y, 0.0f, 1.0f ); + return retval; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// Output : float +//----------------------------------------------------------------------------- +float CFlexAnimationTrack::GetSampleIntensity( float time ) +{ + return GetIntensityInternal( time, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// Output : float +//----------------------------------------------------------------------------- +float CFlexAnimationTrack::GetBalanceIntensity( float time ) +{ + if ( IsComboType() ) + { + return GetIntensityInternal( time, 1 ); + } + + return 1.0f; +} + +// For a given time, computes 0->1 intensity value for the slider +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// Output : float +//----------------------------------------------------------------------------- +float CFlexAnimationTrack::GetIntensity( float time, int side ) +{ + float mag = GetSampleIntensity( time ); + + float scale = 1.0f; + + if ( IsComboType() ) + { + float balance = GetBalanceIntensity( time ); + + // Asking for left but balance is to right, then fall off as we go + // further right + if ( side == 0 && balance > 0.5f ) + { + scale = (1.0f - balance ) / 0.5f; + } + // Asking for right, but balance is left, fall off as we go left. + else if ( side == 1 && balance < 0.5f ) + { + scale = ( balance / 0.5f ); + } + } + + return mag * scale; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// value - +//----------------------------------------------------------------------------- +CExpressionSample *CFlexAnimationTrack::AddSample( float time, float value, int type /*=0*/ ) +{ + Assert( type == 0 || type == 1 ); + + CExpressionSample sample; + sample.time = time; + sample.value = value; + sample.selected = false; + + int idx = m_Samples[ type ].AddToTail( sample ); + + // Resort( type ); + return &m_Samples[ type ][ idx ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::Resort( int type /*=0*/ ) +{ + Assert( type == 0 || type == 1 ); + + for ( int i = 0; i < m_Samples[ type ].Size(); i++ ) + { + for ( int j = i + 1; j < m_Samples[ type ].Size(); j++ ) + { + CExpressionSample src = m_Samples[ type ][ i ]; + CExpressionSample dest = m_Samples[ type ][ j ]; + + if ( src.time > dest.time ) + { + m_Samples[ type ][ i ] = dest; + m_Samples[ type ][ j ] = src; + } + } + } + + // Make sure nothing is out of range + RemoveOutOfRangeSamples( 0 ); + RemoveOutOfRangeSamples( 1 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CFlexAnimationTrack::GetEvent( void ) +{ + return m_pEvent; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : side - +// Output : int +//----------------------------------------------------------------------------- +int CFlexAnimationTrack::GetFlexControllerIndex( int side /*= 0*/ ) +{ + Assert( side == 0 || side == 1 ); + + if ( IsComboType() ) + { + return m_nFlexControllerIndex[ side ]; + } + + return m_nFlexControllerIndex[ 0 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : side - +// Output : int +//----------------------------------------------------------------------------- +int CFlexAnimationTrack::GetRawFlexControllerIndex( int side /*= 0*/ ) +{ + Assert( side == 0 || side == 1 ); + + if ( IsComboType() ) + { + return m_nFlexControllerIndexRaw[ side ]; + } + + return m_nFlexControllerIndexRaw[ 0 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// side - +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetFlexControllerIndex( int raw, int index, int side /*= 0*/ ) +{ + Assert( side == 0 || side == 1 ); + + m_nFlexControllerIndex[ side ] = index; + // Model specific + m_nFlexControllerIndexRaw[ side ] = raw; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : combo - +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetComboType( bool combo ) +{ + m_bCombo = combo; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFlexAnimationTrack::IsComboType( void ) +{ + return m_bCombo; +} + +//----------------------------------------------------------------------------- +// Purpose: True if this should be simulated on the server side always +// Input : state - +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetServerSide( bool state ) +{ + m_bServerSide = state; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFlexAnimationTrack::IsServerSide() const +{ + return m_bServerSide; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetMin( float value ) +{ + m_flMin = value; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetMax( float value ) +{ + m_flMax = value; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CFlexAnimationTrack::GetMin( int type ) +{ + if (type == 0) + return m_flMin; + else + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CFlexAnimationTrack::GetMax( int type ) +{ + if (type == 0) + return m_flMax; + else + return 1.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFlexAnimationTrack::IsInverted( void ) +{ + if (m_bInverted) + return true; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::SetInverted( bool isInverted ) +{ + m_bInverted = isInverted; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +void CFlexAnimationTrack::RemoveOutOfRangeSamples( int type ) +{ + Assert( m_pEvent ); + if ( !m_pEvent ) + return; + + Assert( m_pEvent->HasEndTime() ); + float duration = m_pEvent->GetDuration(); + + int c = m_Samples[ type ].Size(); + for ( int i = c-1; i >= 0; i-- ) + { + CExpressionSample src = m_Samples[ type ][ i ]; + if ( src.time < 0 || + src.time > duration ) + { + m_Samples[ type ].Remove( i ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoEvent::CChoreoEvent( CChoreoScene *scene ) +{ + Init( scene ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// *name - +//----------------------------------------------------------------------------- +CChoreoEvent::CChoreoEvent( CChoreoScene *scene, EVENTTYPE type, const char *name ) +{ + Init( scene ); + SetType( type ); + SetName( name ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// *name - +// *param - +//----------------------------------------------------------------------------- +CChoreoEvent::CChoreoEvent( CChoreoScene *scene, EVENTTYPE type, const char *name, const char *param ) +{ + Init( scene ); + SetType( type ); + SetName( name ); + SetParameters( param ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoEvent::~CChoreoEvent( void ) +{ + RemoveAllTracks(); + ClearEventDependencies(); + delete m_pSubScene; +} + +//----------------------------------------------------------------------------- +// Purpose: Assignment +// Input : src - +// Output : CChoreoEvent& +//----------------------------------------------------------------------------- +CChoreoEvent& CChoreoEvent::operator=( const CChoreoEvent& src ) +{ + MEM_ALLOC_CREDIT(); + + // Copy global id when copying entity + m_nGlobalID = src.m_nGlobalID; + + m_pActor = NULL; + m_pChannel = NULL; + + m_fType = src.m_fType; + m_Name = src.m_Name; + m_Parameters = src.m_Parameters; + m_Parameters2= src.m_Parameters2; + m_flStartTime = src.m_flStartTime; + m_flEndTime = src.m_flEndTime; + + m_bFixedLength = src.m_bFixedLength; + m_flGestureSequenceDuration = src.m_flGestureSequenceDuration; + m_bResumeCondition = src.m_bResumeCondition; + m_bLockBodyFacing = src.m_bLockBodyFacing; + m_flDistanceToTarget = src.m_flDistanceToTarget; + m_bForceShortMovement = src.m_bForceShortMovement; + m_bUsesTag = src.m_bUsesTag; + m_TagName = src.m_TagName; + m_TagWavName = src.m_TagWavName; + + ClearAllRelativeTags(); + ClearAllTimingTags(); + int t; + for ( t = 0; t < NUM_ABS_TAG_TYPES; t++ ) + { + ClearAllAbsoluteTags( (AbsTagType)t ); + } + + int i; + for ( i = 0; i < src.m_RelativeTags.Size(); i++ ) + { + CEventRelativeTag newtag( src.m_RelativeTags[ i ] ); + newtag.SetOwner( this ); + m_RelativeTags.AddToTail( newtag ); + } + + for ( i = 0; i < src.m_TimingTags.Size(); i++ ) + { + CFlexTimingTag newtag( src.m_TimingTags[ i ] ); + newtag.SetOwner( this ); + m_TimingTags.AddToTail( newtag ); + } + for ( t = 0; t < NUM_ABS_TAG_TYPES; t++ ) + { + for ( i = 0; i < src.m_AbsoluteTags[ t ].Size(); i++ ) + { + CEventAbsoluteTag newtag( src.m_AbsoluteTags[ t ][ i ] ); + newtag.SetOwner( this ); + m_AbsoluteTags[ t ].AddToTail( newtag ); + } + } + + RemoveAllTracks(); + + for ( i = 0 ; i < src.m_FlexAnimationTracks.Size(); i++ ) + { + CFlexAnimationTrack *newtrack = new CFlexAnimationTrack( src.m_FlexAnimationTracks[ i ] ); + newtrack->SetEvent( this ); + m_FlexAnimationTracks.AddToTail( newtrack ); + } + + m_bTrackLookupSet = src.m_bTrackLookupSet; + + // FIXME: Use a safe handle? + //m_pSubScene = src.m_pSubScene; + + m_bProcessing = src.m_bProcessing; + m_pMixer = src.m_pMixer; + + m_pScene = src.m_pScene; + + m_nPitch = src.m_nPitch; + m_nYaw = src.m_nYaw; + + m_nNumLoops = src.m_nNumLoops; + m_nLoopsRemaining = src.m_nLoopsRemaining; + + // Copy ramp over + m_Ramp.RemoveAll(); + for ( i = 0; i < src.m_Ramp.Count(); i++ ) + { + CExpressionSample sample = src.m_Ramp[ i ]; + AddRamp( sample.time, sample.value, sample.selected ); + } + + m_RampEdgeInfo[ 0 ] = src.m_RampEdgeInfo[ 0 ]; + m_RampEdgeInfo[ 1 ] = src.m_RampEdgeInfo[ 1 ]; + + m_ccType = src.m_ccType; + m_CCToken = src.m_CCToken; + m_bUsingCombinedSoundFile = src.m_bUsingCombinedSoundFile; + m_uRequiredCombinedChecksum = src.m_uRequiredCombinedChecksum; + m_nNumSlaves = src.m_nNumSlaves; + m_flLastSlaveEndTime = src.m_flLastSlaveEndTime; + m_bCCTokenValid = src.m_bCCTokenValid; + m_bCombinedUsingGenderToken = src.m_bCombinedUsingGenderToken; + + m_bSuppressCaptionAttenuation = src.m_bSuppressCaptionAttenuation; + + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::Init( CChoreoScene *scene ) +{ + m_nGlobalID = s_nGlobalID++; + + m_fType = UNSPECIFIED; + m_Name.clear(); + m_Parameters.clear(); + m_Parameters2.clear(); + + m_flStartTime = 0.0f; + m_flEndTime = -1.0f; + + m_pActor = NULL; + m_pChannel = NULL; + m_pScene = scene; + + m_bFixedLength = false; + m_bResumeCondition = false; + SetUsingRelativeTag( false, 0, 0 ); + + m_bTrackLookupSet = false; + + m_bLockBodyFacing = false; + m_flDistanceToTarget = 0.0f; + m_bForceShortMovement = false; + m_bSyncToFollowingGesture = false; + + m_pSubScene = NULL; + m_bProcessing = false; + m_pMixer = NULL; + m_flGestureSequenceDuration = 0.0f; + + m_nPitch = m_nYaw = 0; + + m_nNumLoops = -1; + m_nLoopsRemaining = 0; + + // Close captioning/localization support + m_CCToken.clear(); + m_ccType = CC_MASTER; + m_bUsingCombinedSoundFile = false; + m_uRequiredCombinedChecksum = 0; + m_nNumSlaves = 0; + m_flLastSlaveEndTime = 0.0f; + m_bCCTokenValid = false; + m_bCombinedUsingGenderToken = false; + m_bSuppressCaptionAttenuation = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +CChoreoEvent::EVENTTYPE CChoreoEvent::GetType( void ) +{ + return (EVENTTYPE)m_fType; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetType( EVENTTYPE type ) +{ + m_fType = type; + + if ( m_fType == SPEAK || + m_fType == SUBSCENE ) + { + m_bFixedLength = true; + } + else + { + m_bFixedLength = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetName( const char *name ) +{ + m_Name = name; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEvent::GetName( void ) +{ + return m_Name.c_str(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *param - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetParameters( const char *param ) +{ + m_Parameters = param; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEvent::GetParameters( void ) +{ + return m_Parameters.c_str(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *param - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetParameters2( const char *param ) +{ + int iLength = strlen( param ); + m_Parameters2 = param; + + // HACK: Remove trailing " " until faceposer is fixed + if ( iLength > 0 ) + { + if ( param[iLength-1] == ' ' ) + { + m_Parameters2.erase(iLength-1); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEvent::GetParameters2( void ) +{ + return m_Parameters2.c_str(); +} + +//----------------------------------------------------------------------------- +// Purpose: debugging description +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEvent::GetDescription( void ) +{ + static char description[ 256 ]; + + description[ 0 ] = 0; + + if ( !GetActor() ) + { + Q_snprintf( description,sizeof(description), "global %s", m_Name.c_str() ); + } + else + { + Assert( m_pChannel ); + Q_snprintf( description,sizeof(description), "%s : %s : %s -- %s \"%s\"", m_pActor->GetName(), m_pChannel->GetName(), GetName(), NameForType( GetType() ), GetParameters() ); + if ( GetType() == EXPRESSION ) + { + char sz[ 256 ]; + + Q_snprintf( sz,sizeof(sz), " \"%s\"", GetParameters2() ); + Q_strncat( description, sz, sizeof(description), COPY_ALL_CHARACTERS ); + } + } + + return description; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : starttime - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetStartTime( float starttime ) +{ + m_flStartTime = starttime; + if ( m_flEndTime != -1.0f ) + { + if ( m_flEndTime < m_flStartTime ) + { + m_flEndTime = m_flStartTime; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::GetStartTime( ) +{ + return m_flStartTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : endtime - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetEndTime( float endtime ) +{ + bool changed = m_flEndTime != endtime; + + m_flEndTime = endtime; + + if ( endtime != -1.0f ) + { + if ( m_flEndTime < m_flStartTime ) + { + m_flEndTime = m_flStartTime; + } + + if ( changed ) + { + OnEndTimeChanged(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::GetEndTime( ) +{ + return m_flEndTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoEvent::HasEndTime( void ) +{ + return m_flEndTime != -1.0f ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::GetCompletion( float time ) +{ + float t = (time - GetStartTime()) / (GetEndTime() - GetStartTime()); + + if (t < 0.0f) + return 0.0f; + else if (t > 1.0f) + return 1.0f; + + return t; +} + +// ICurveDataAccessor method +bool CChoreoEvent::CurveHasEndTime() +{ + return HasEndTime(); +} + +// ICurveDataAccessor method +int CChoreoEvent::CurveGetSampleCount() +{ + return GetRampCount(); +} + +// ICurveDataAccessor method +CExpressionSample *CChoreoEvent::CurveGetBoundedSample( int idx, bool& bClamped ) +{ + return GetBoundedRamp( idx, bClamped ); +} + +int CChoreoEvent::GetDefaultCurveType() +{ + return CURVE_CATMULL_ROM_TO_CATMULL_ROM; +} + +// Static method!!! +float CChoreoEvent::GetRampIntensity( ICurveDataAccessor *data, float time ) +{ + float zeroValue = 0.0f; + + // find samples that span the time + if ( !data->CurveHasEndTime() ) + { + return zeroValue; + } + + int rampCount = data->CurveGetSampleCount(); + if ( rampCount < 1 ) + { + // Full intensity + return 1.0f; + } + + CExpressionSample *esStart = NULL; + CExpressionSample *esEnd = NULL; + + // do binary search for sample in time period + int j = max( rampCount / 2, 1 ); + int i = j; + while ( i > -2 && i < rampCount + 1 ) + { + bool dummy; + esStart = data->CurveGetBoundedSample( i, dummy ); + esEnd = data->CurveGetBoundedSample( i + 1, dummy ); + + j = max( j / 2, 1 ); + if ( time < esStart->time) + { + i -= j; + } + else if ( time > esEnd->time) + { + i += j; + } + else + { + break; + } + } + + if (!esStart) + { + return 1.0f; + } + + int prev = i - 1; + int next = i + 2; + + prev = max( -1, prev ); + next = min( next, rampCount ); + + bool clamp[ 2 ]; + CExpressionSample *esPre = data->CurveGetBoundedSample( prev, clamp[ 0 ] ); + CExpressionSample *esNext = data->CurveGetBoundedSample( next, clamp[ 1 ] ); + + float dt = esEnd->time - esStart->time; + + Vector vPre( esPre->time, esPre->value, 0 ); + Vector vStart( esStart->time, esStart->value, 0 ); + Vector vEnd( esEnd->time, esEnd->value, 0 ); + Vector vNext( esNext->time, esNext->value, 0 ); + + if ( clamp[ 0 ] ) + { + vPre.x = vStart.x; + } + + if ( clamp[ 1 ] ) + { + vNext.x = vEnd.x; + } + + float f2 = 0.0f; + if ( dt > 0.0f ) + { + f2 = ( time - esStart->time ) / ( dt ); + } + f2 = clamp( f2, 0.0f, 1.0f ); + + Vector vOut; + int dummy; + int earlypart, laterpart; + + int startCurve = esStart->GetCurveType(); + int endCurve = esEnd->GetCurveType(); + + if ( startCurve == CURVE_DEFAULT ) + { + startCurve = data->GetDefaultCurveType(); + } + if ( endCurve == CURVE_DEFAULT ) + { + endCurve = data->GetDefaultCurveType(); + } + + // Not holding out value of previous curve... + Interpolator_CurveInterpolatorsForType( startCurve, dummy, earlypart ); + Interpolator_CurveInterpolatorsForType( endCurve, laterpart, dummy ); + + if ( earlypart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + VectorLerp( vStart, vEnd, f2, vOut ); + vOut.y = vStart.y; + } + else if ( laterpart == INTERPOLATE_HOLD ) + { + // Hold "out" of previous sample (can cause a discontinuity) + VectorLerp( vStart, vEnd, f2, vOut ); + vOut.y = vEnd.y; + } + else + { + bool sameCurveType = earlypart == laterpart ? true : false; + if ( sameCurveType ) + { + Interpolator_CurveInterpolate( laterpart, vPre, vStart, vEnd, vNext, f2, vOut ); + } + else // curves differ, sigh + { + Vector vOut1, vOut2; + + Interpolator_CurveInterpolate( earlypart, vPre, vStart, vEnd, vNext, f2, vOut1 ); + Interpolator_CurveInterpolate( laterpart, vPre, vStart, vEnd, vNext, f2, vOut2 ); + + VectorLerp( vOut1, vOut2, f2, vOut ); + } + } + + float retval = clamp( vOut.y, 0.0f, 1.0f ); + return retval; +} + +//----------------------------------------------------------------------------- +// Purpose: Get intensity for event, bounded by scene global intensity +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::GetIntensity( ICurveDataAccessor *data, float scenetime ) +{ + float global_intensity = 1.0f; + if ( m_pScene ) + { + global_intensity = m_pScene->GetSceneRampIntensity( scenetime ); + } + else + { + Assert( 0 ); + } + + float event_intensity = _GetIntensity( data, scenetime ); + + return global_intensity * event_intensity; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::_GetIntensity( ICurveDataAccessor *data, float scenetime ) +{ + // Convert to event local time + float time = scenetime - GetStartTime(); + return GetRampIntensity( data, time ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void CChoreoEvent::OffsetStartTime( float dt ) +{ + SetStartTime( GetStartTime() + dt ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void CChoreoEvent::OffsetEndTime( float dt ) +{ + if ( HasEndTime() ) + { + SetEndTime( GetEndTime() + dt ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void CChoreoEvent::OffsetTime( float dt ) +{ + if ( HasEndTime() ) + { + m_flEndTime += dt; + } + m_flStartTime += dt; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetActor( CChoreoActor *actor ) +{ + m_pActor = actor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoActor +//----------------------------------------------------------------------------- +CChoreoActor *CChoreoEvent::GetActor( void ) +{ + return m_pActor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetChannel( CChoreoChannel *channel ) +{ + m_pChannel = channel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoChannel +//----------------------------------------------------------------------------- +CChoreoChannel *CChoreoEvent::GetChannel( void ) +{ + return m_pChannel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetSubScene( CChoreoScene *scene ) +{ + m_pSubScene = scene; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoScene +//----------------------------------------------------------------------------- +CChoreoScene *CChoreoEvent::GetSubScene( void ) +{ + return m_pSubScene; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct EventNameMap_t +{ + CChoreoEvent::EVENTTYPE type; + char const *name; +}; + +static EventNameMap_t g_NameMap[] = +{ + { CChoreoEvent::UNSPECIFIED, "unspecified" }, // error condition!!! + { CChoreoEvent::SECTION, "section" }, + { CChoreoEvent::EXPRESSION, "expression" }, + { CChoreoEvent::LOOKAT, "lookat" }, + { CChoreoEvent::MOVETO, "moveto" }, + { CChoreoEvent::SPEAK, "speak" }, + { CChoreoEvent::GESTURE, "gesture" }, + { CChoreoEvent::SEQUENCE, "sequence" }, + { CChoreoEvent::FACE, "face" }, + { CChoreoEvent::FIRETRIGGER, "firetrigger" }, + { CChoreoEvent::FLEXANIMATION, "flexanimation" }, + { CChoreoEvent::SUBSCENE, "subscene" }, + { CChoreoEvent::LOOP, "loop" }, + { CChoreoEvent::INTERRUPT, "interrupt" }, + { CChoreoEvent::STOPPOINT, "stoppoint" }, + { CChoreoEvent::PERMIT_RESPONSES, "permitresponses" }, + { CChoreoEvent::GENERIC, "generic" }, +}; + +//----------------------------------------------------------------------------- +// Purpose: A simple class to verify the names data above at runtime +//----------------------------------------------------------------------------- +class CCheckEventNames +{ +public: + CCheckEventNames() + { + if ( ARRAYSIZE( g_NameMap ) != CChoreoEvent::NUM_TYPES ) + { + Error( "g_NameMap contains %i entries, CChoreoEvent::NUM_TYPES == %i!", + ARRAYSIZE( g_NameMap ), CChoreoEvent::NUM_TYPES ); + } + for ( int i = 0; i < CChoreoEvent::NUM_TYPES; ++i ) + { + if ( !g_NameMap[ i ].name ) + { + Error( "g_NameMap: Event type at %i has NULL name string!", i ); + } + + if ( (CChoreoEvent::EVENTTYPE)(i) == g_NameMap[ i ].type ) + continue; + + Error( "g_NameMap: Event type at %i has wrong value (%i)!", + i, (int)g_NameMap[ i ].type ); + } + } +}; +static CCheckEventNames g_CheckNamesSingleton; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : int +//----------------------------------------------------------------------------- +CChoreoEvent::EVENTTYPE CChoreoEvent::TypeForName( const char *name ) +{ + for ( int i = 0; i < NUM_TYPES; ++i ) + { + EventNameMap_t *slot = &g_NameMap[ i ]; + if ( !Q_stricmp( name, slot->name ) ) + return slot->type; + } + + Assert( !"CChoreoEvent::TypeForName failed!!!" ); + return UNSPECIFIED; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEvent::NameForType( EVENTTYPE type ) +{ + int i = (int)type; + if ( i < 0 || i >= NUM_TYPES ) + { + Assert( "!CChoreoEvent::NameForType: bogus type!" ); + // returns "unspecified!!!"; + return g_NameMap[ 0 ].name; + } + + return g_NameMap[ i ].name; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CCNameMap_t +{ + CChoreoEvent::CLOSECAPTION type; + char const *name; +}; + +static CCNameMap_t g_CCNameMap[] = +{ + { CChoreoEvent::CC_MASTER, "cc_master" }, // error condition!!! + { CChoreoEvent::CC_SLAVE, "cc_slave" }, + { CChoreoEvent::CC_DISABLED, "cc_disabled" }, +}; + +//----------------------------------------------------------------------------- +// Purpose: A simple class to verify the names data above at runtime +//----------------------------------------------------------------------------- +class CCheckCCNames +{ +public: + CCheckCCNames() + { + if ( ARRAYSIZE( g_CCNameMap ) != CChoreoEvent::NUM_CC_TYPES ) + { + Error( "g_CCNameMap contains %i entries, CChoreoEvent::NUM_CC_TYPES == %i!", + ARRAYSIZE( g_CCNameMap ), CChoreoEvent::NUM_CC_TYPES ); + } + for ( int i = 0; i < CChoreoEvent::NUM_CC_TYPES; ++i ) + { + if ( !g_CCNameMap[ i ].name ) + { + Error( "g_NameMap: CC type at %i has NULL name string!", i ); + } + + if ( (CChoreoEvent::CLOSECAPTION)(i) == g_CCNameMap[ i ].type ) + continue; + + Error( "g_CCNameMap: Event type at %i has wrong value (%i)!", + i, (int)g_CCNameMap[ i ].type ); + } + } +}; +static CCheckCCNames g_CheckCCNamesSingleton; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : CLOSECAPTION +//----------------------------------------------------------------------------- +CChoreoEvent::CLOSECAPTION CChoreoEvent::CCTypeForName( const char *name ) +{ + for ( int i = 0; i < NUM_CC_TYPES; ++i ) + { + CCNameMap_t *slot = &g_CCNameMap[ i ]; + if ( !Q_stricmp( name, slot->name ) ) + return slot->type; + } + + Assert( !"CChoreoEvent::TypeForName failed!!!" ); + return CC_MASTER; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEvent::NameForCCType( CLOSECAPTION type ) +{ + int i = (int)type; + if ( i < 0 || i >= NUM_CC_TYPES ) + { + Assert( "!CChoreoEvent::NameForType: bogus type!" ); + // returns "unspecified!!!"; + return g_CCNameMap[ 0 ].name; + } + + return g_CCNameMap[ i ].name; +} + +//----------------------------------------------------------------------------- +// Purpose: Is the event something that can be sized ( a wave file, e.g. ) +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoEvent::IsFixedLength( void ) +{ + return m_bFixedLength; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : isfixedlength - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetFixedLength( bool isfixedlength ) +{ + m_bFixedLength = isfixedlength; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : resumecondition - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetResumeCondition( bool resumecondition ) +{ + m_bResumeCondition = resumecondition; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoEvent::IsResumeCondition( void ) +{ + return m_bResumeCondition; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : lockbodyfacing - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetLockBodyFacing( bool lockbodyfacing ) +{ + m_bLockBodyFacing = lockbodyfacing; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoEvent::IsLockBodyFacing( void ) +{ + return m_bLockBodyFacing; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : distancetotarget - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetDistanceToTarget( float distancetotarget ) +{ + m_flDistanceToTarget = distancetotarget; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns ideal distance to target +//----------------------------------------------------------------------------- +float CChoreoEvent::GetDistanceToTarget( void ) +{ + return m_flDistanceToTarget; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set if small (sub-1/2 bbox) movements are forced +//----------------------------------------------------------------------------- + +void CChoreoEvent::SetForceShortMovement( bool bForceShortMovement ) +{ + m_bForceShortMovement = bForceShortMovement; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : get if small (sub-1/2 bbox) movements are forced +//----------------------------------------------------------------------------- + +bool CChoreoEvent::GetForceShortMovement( void ) +{ + return m_bForceShortMovement; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set if the gesture should sync its exit tag with the following gestures entry tag +//----------------------------------------------------------------------------- + +void CChoreoEvent::SetSyncToFollowingGesture( bool bSyncToFollowingGesture ) +{ + m_bSyncToFollowingGesture = bSyncToFollowingGesture; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : get if the gesture should sync its exit tag with the following gestures entry tag +//----------------------------------------------------------------------------- + +bool CChoreoEvent::GetSyncToFollowingGesture( void ) +{ + return m_bSyncToFollowingGesture; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::GetDuration( void ) +{ + if ( HasEndTime() ) + { + return GetEndTime() - GetStartTime(); + } + + return 0.0f; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::ClearAllRelativeTags( void ) +{ + m_RelativeTags.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoEvent::GetNumRelativeTags( void ) +{ + return m_RelativeTags.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : tagnum - +// Output : CEventRelativeTag +//----------------------------------------------------------------------------- +CEventRelativeTag *CChoreoEvent::GetRelativeTag( int tagnum ) +{ + Assert( tagnum >= 0 && tagnum < m_RelativeTags.Size() ); + return &m_RelativeTags[ tagnum ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tagname - +// percentage - +//----------------------------------------------------------------------------- +void CChoreoEvent::AddRelativeTag( const char *tagname, float percentage ) +{ + CEventRelativeTag rt( this, tagname, percentage ); + m_RelativeTags.AddToTail( rt ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tagname - +//----------------------------------------------------------------------------- +void CChoreoEvent::RemoveRelativeTag( const char *tagname ) +{ + for ( int i = 0; i < m_RelativeTags.Size(); i++ ) + { + CEventRelativeTag *prt = &m_RelativeTags[ i ]; + if ( !prt ) + continue; + + if ( !stricmp( prt->GetName(), tagname ) ) + { + m_RelativeTags.Remove( i ); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tagname - +// Output : CEventRelativeTag * +//----------------------------------------------------------------------------- +CEventRelativeTag * CChoreoEvent::FindRelativeTag( const char *tagname ) +{ + for ( int i = 0; i < m_RelativeTags.Size(); i++ ) + { + CEventRelativeTag *prt = &m_RelativeTags[ i ]; + if ( !prt ) + continue; + + if ( !stricmp( prt->GetName(), tagname ) ) + { + return prt; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoEvent::IsUsingRelativeTag( void ) +{ + return m_bUsesTag; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : usetag - +// 0 - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetUsingRelativeTag( bool usetag, const char *tagname /*= 0*/, + const char *wavname /* = 0 */ ) +{ + m_bUsesTag = usetag; + if ( tagname ) + { + m_TagName = tagname; + } + else + { + m_TagName.clear(); + } + if ( wavname ) + { + m_TagWavName = wavname; + } + else + { + m_TagWavName.clear(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEvent::GetRelativeTagName( void ) +{ + return m_TagName.c_str(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEvent::GetRelativeWavName( void ) +{ + return m_TagWavName.c_str(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::ClearAllTimingTags( void ) +{ + m_TimingTags.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoEvent::GetNumTimingTags( void ) +{ + return m_TimingTags.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : tagnum - +// Output : CEventRelativeTag +//----------------------------------------------------------------------------- +CFlexTimingTag *CChoreoEvent::GetTimingTag( int tagnum ) +{ + Assert( tagnum >= 0 && tagnum < m_TimingTags.Size() ); + return &m_TimingTags[ tagnum ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tagname - +// percentage - +//----------------------------------------------------------------------------- +void CChoreoEvent::AddTimingTag( const char *tagname, float percentage, bool locked ) +{ + CFlexTimingTag tt( this, tagname, percentage, locked ); + m_TimingTags.AddToTail( tt ); + + // Sort tags + CFlexTimingTag temp( (CChoreoEvent *)0x1, "", 0.0f, false ); + + // ugly bubble sort + for ( int i = 0; i < m_TimingTags.Size(); i++ ) + { + for ( int j = i + 1; j < m_TimingTags.Size(); j++ ) + { + CFlexTimingTag *t1 = &m_TimingTags[ i ]; + CFlexTimingTag *t2 = &m_TimingTags[ j ]; + + if ( t1->GetPercentage() > t2->GetPercentage() ) + { + temp = *t1; + *t1 = *t2; + *t2 = temp; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tagname - +//----------------------------------------------------------------------------- +void CChoreoEvent::RemoveTimingTag( const char *tagname ) +{ + for ( int i = 0; i < m_TimingTags.Size(); i++ ) + { + CFlexTimingTag *ptt = &m_TimingTags[ i ]; + if ( !ptt ) + continue; + + if ( !stricmp( ptt->GetName(), tagname ) ) + { + m_TimingTags.Remove( i ); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tagname - +// Output : CEventRelativeTag * +//----------------------------------------------------------------------------- +CFlexTimingTag * CChoreoEvent::FindTimingTag( const char *tagname ) +{ + for ( int i = 0; i < m_TimingTags.Size(); i++ ) + { + CFlexTimingTag *ptt = &m_TimingTags[ i ]; + if ( !ptt ) + continue; + + if ( !stricmp( ptt->GetName(), tagname ) ) + { + return ptt; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::OnEndTimeChanged( void ) +{ + int c = GetNumFlexAnimationTracks(); + for ( int i = 0; i < c; i++ ) + { + CFlexAnimationTrack *track = GetFlexAnimationTrack( i ); + Assert( track ); + if ( !track ) + continue; + + track->Resort( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoEvent::GetNumFlexAnimationTracks( void ) +{ + return m_FlexAnimationTracks.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : CFlexAnimationTrack +//----------------------------------------------------------------------------- +CFlexAnimationTrack *CChoreoEvent::GetFlexAnimationTrack( int index ) +{ + if ( index < 0 || index >= GetNumFlexAnimationTracks() ) + return NULL; + return m_FlexAnimationTracks[ index ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *controllername - +// Output : CFlexAnimationTrack +//----------------------------------------------------------------------------- +CFlexAnimationTrack *CChoreoEvent::AddTrack( const char *controllername ) +{ + CFlexAnimationTrack *newTrack = new CFlexAnimationTrack( this ); + newTrack->SetFlexControllerName( controllername ); + + m_FlexAnimationTracks.AddToTail( newTrack ); + + return newTrack; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +//----------------------------------------------------------------------------- +void CChoreoEvent::RemoveTrack( int index ) +{ + CFlexAnimationTrack *track = GetFlexAnimationTrack( index ); + if ( !track ) + return; + + m_FlexAnimationTracks.Remove( index ); + delete track; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::RemoveAllTracks( void ) +{ + while ( GetNumFlexAnimationTracks() > 0 ) + { + RemoveTrack( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *controllername - +// Output : CFlexAnimationTrack +//----------------------------------------------------------------------------- +CFlexAnimationTrack *CChoreoEvent::FindTrack( const char *controllername ) +{ + for ( int i = 0; i < GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *t = GetFlexAnimationTrack( i ); + if ( t && !stricmp( t->GetFlexControllerName(), controllername ) ) + { + return t; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoEvent::GetTrackLookupSet( void ) +{ + return m_bTrackLookupSet; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetTrackLookupSet( bool set ) +{ + m_bTrackLookupSet = set; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoEvent::IsProcessing( void ) const +{ + return m_bProcessing; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *cb - +// t - +//----------------------------------------------------------------------------- +void CChoreoEvent::StartProcessing( IChoreoEventCallback *cb, CChoreoScene *scene, float t ) +{ + Assert( !m_bProcessing ); + m_bProcessing = true; + if ( cb ) + { + cb->StartEvent( t, scene, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *cb - +// t - +//----------------------------------------------------------------------------- +void CChoreoEvent::ContinueProcessing( IChoreoEventCallback *cb, CChoreoScene *scene, float t ) +{ + Assert( m_bProcessing ); + if ( cb ) + { + cb->ProcessEvent( t, scene, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *cb - +// t - +//----------------------------------------------------------------------------- +void CChoreoEvent::StopProcessing( IChoreoEventCallback *cb, CChoreoScene *scene, float t ) +{ + Assert( m_bProcessing ); + if ( cb ) + { + cb->EndEvent( t, scene, this ); + } + m_bProcessing = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *cb - +// t - +//----------------------------------------------------------------------------- +bool CChoreoEvent::CheckProcessing( IChoreoEventCallback *cb, CChoreoScene *scene, float t ) +{ + //Assert( !m_bProcessing ); + if ( cb ) + { + return cb->CheckEvent( t, scene, this ); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::ResetProcessing( void ) +{ + if ( GetType() == LOOP ) + { + m_nLoopsRemaining = m_nNumLoops; + } + + m_bProcessing = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mixer - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetMixer( CAudioMixer *mixer ) +{ + m_pMixer = mixer; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CAudioMixer +//----------------------------------------------------------------------------- +CAudioMixer *CChoreoEvent::GetMixer( void ) const +{ + return m_pMixer; +} + +// Snap to scene framerate +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::SnapTimes() +{ + if ( HasEndTime() && !IsFixedLength() ) + { + m_flEndTime = SnapTime( m_flEndTime ); + } + float oldstart = m_flStartTime; + m_flStartTime = SnapTime( m_flStartTime ); + + // Don't snap end time for fixed length events, just set based on new start time + if ( IsFixedLength() ) + { + float dt = m_flStartTime - oldstart; + m_flEndTime += dt; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::SnapTime( float t ) +{ + CChoreoScene *scene = GetScene(); + if ( !scene) + { + Assert( 0 ); + return t; + } + + return scene->SnapTime( t ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoScene +//----------------------------------------------------------------------------- +CChoreoScene *CChoreoEvent::GetScene( void ) +{ + return m_pScene; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetScene( CChoreoScene *scene ) +{ + m_pScene = scene; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +// Output : char const +//----------------------------------------------------------------------------- +const char *CChoreoEvent::NameForAbsoluteTagType( AbsTagType t ) +{ + switch ( t ) + { + case PLAYBACK: + return "playback_time"; + case ORIGINAL: + return "shifted_time"; + default: + break; + } + + return "AbsTagType(unknown)"; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : AbsTagType +//----------------------------------------------------------------------------- +CChoreoEvent::AbsTagType CChoreoEvent::TypeForAbsoluteTagName( const char *name ) +{ + if ( !Q_strcasecmp( name, "playback_time" ) ) + { + return PLAYBACK; + } + else if ( !Q_strcasecmp( name, "shifted_time" ) ) + { + return ORIGINAL; + } + + return (AbsTagType)-1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +//----------------------------------------------------------------------------- +void CChoreoEvent::ClearAllAbsoluteTags( AbsTagType type ) +{ + m_AbsoluteTags[ type ].Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoEvent::GetNumAbsoluteTags( AbsTagType type ) +{ + return m_AbsoluteTags[ type ].Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// tagnum - +// Output : CEventAbsoluteTag +//----------------------------------------------------------------------------- +CEventAbsoluteTag *CChoreoEvent::GetAbsoluteTag( AbsTagType type, int tagnum ) +{ + Assert( tagnum >= 0 && tagnum < m_AbsoluteTags[ type ].Size() ); + return &m_AbsoluteTags[ type ][ tagnum ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// *tagname - +// Output : CEventAbsoluteTag +//----------------------------------------------------------------------------- +CEventAbsoluteTag *CChoreoEvent::FindAbsoluteTag( AbsTagType type, const char *tagname ) +{ + for ( int i = 0; i < m_AbsoluteTags[ type ].Size(); i++ ) + { + CEventAbsoluteTag *ptag = &m_AbsoluteTags[ type ][ i ]; + if ( !ptag ) + continue; + + if ( !stricmp( ptag->GetName(), tagname ) ) + { + return ptag; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// *tagname - +// t - +//----------------------------------------------------------------------------- +void CChoreoEvent::AddAbsoluteTag( AbsTagType type, const char *tagname, float t ) +{ + CEventAbsoluteTag at( this, tagname, t ); + m_AbsoluteTags[ type ].AddToTail( at ); + + // Sort tags + CEventAbsoluteTag temp( (CChoreoEvent *)0x1, "", 0.0f ); + + // ugly bubble sort + for ( int i = 0; i < m_AbsoluteTags[ type ].Size(); i++ ) + { + for ( int j = i + 1; j < m_AbsoluteTags[ type ].Size(); j++ ) + { + CEventAbsoluteTag *t1 = &m_AbsoluteTags[ type ][ i ]; + CEventAbsoluteTag *t2 = &m_AbsoluteTags[ type ][ j ]; + + if ( t1->GetPercentage() > t2->GetPercentage() ) + { + temp = *t1; + *t1 = *t2; + *t2 = temp; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// *tagname - +//----------------------------------------------------------------------------- +void CChoreoEvent::RemoveAbsoluteTag( AbsTagType type, const char *tagname ) +{ + for ( int i = 0; i < m_AbsoluteTags[ type ].Size(); i++ ) + { + CEventAbsoluteTag *ptag = &m_AbsoluteTags[ type ][ i ]; + if ( !ptag ) + continue; + + if ( !stricmp( ptag->GetName(), tagname ) ) + { + m_AbsoluteTags[ type ].Remove( i ); + return; + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: makes sure tags in PLAYBACK are in the same order as ORIGINAL +// Input : +// Output : true if they were in order, false if it has to reorder them +//----------------------------------------------------------------------------- +bool CChoreoEvent::VerifyTagOrder( ) +{ + bool bInOrder = true; + + // Sort tags + CEventAbsoluteTag temp( (CChoreoEvent *)0x1, "", 0.0f ); + + for ( int i = 0; i < m_AbsoluteTags[ CChoreoEvent::ORIGINAL ].Size(); i++ ) + { + CEventAbsoluteTag *ptag = &m_AbsoluteTags[ CChoreoEvent::ORIGINAL ][ i ]; + if ( !ptag ) + continue; + + CEventAbsoluteTag *t1 = &m_AbsoluteTags[ CChoreoEvent::PLAYBACK ][ i ]; + + if ( stricmp( ptag->GetName(), t1->GetName() ) == 0) + continue; + + bInOrder = false; + for ( int j = i + 1; j < m_AbsoluteTags[ CChoreoEvent::PLAYBACK ].Size(); j++ ) + { + CEventAbsoluteTag *t2 = &m_AbsoluteTags[ CChoreoEvent::PLAYBACK ][ j ]; + + if ( stricmp( ptag->GetName(), t2->GetName() ) == 0 ) + { + temp = *t1; + *t1 = *t2; + *t2 = temp; + break; + } + } + } + return bInOrder; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// *tagname - +//----------------------------------------------------------------------------- + +float CChoreoEvent::GetBoundedAbsoluteTagPercentage( AbsTagType type, int tagnum ) +{ + if ( tagnum <= -2 ) + { + /* + if (GetNumAbsoluteTags( type ) >= 1) + { + CEventAbsoluteTag *tag = GetAbsoluteTag( type, 0 ); + Assert( tag ); + return -tag->GetTime(); + } + */ + return 0.0f; // -0.5f; + } + else if ( tagnum == -1 ) + { + return 0.0f; + } + else if ( tagnum == GetNumAbsoluteTags( type ) ) + { + return 1.0; + } + else if ( tagnum > GetNumAbsoluteTags( type ) ) + { + /* + if (GetNumAbsoluteTags( type ) >= 1) + { + CEventAbsoluteTag *tag = GetAbsoluteTag( type, tagnum - 2 ); + Assert( tag ); + return 2.0 - tag->GetTime(); + } + */ + return 1.0; // 1.5; + } + + /* + { + float duration = GetDuration(); + + if ( type == SHIFTED ) + { + float seqduration; + GetGestureSequenceDuration( seqduration ); + return seqduration; + } + return duration; + } + */ + + CEventAbsoluteTag *tag = GetAbsoluteTag( type, tagnum ); + Assert( tag ); + return tag->GetPercentage(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::GetOriginalPercentageFromPlaybackPercentage( float t ) +{ + Assert( GetType() == GESTURE ); + if ( GetType() != GESTURE ) + return t; + + int count = GetNumAbsoluteTags( PLAYBACK ); + + if ( count != GetNumAbsoluteTags( ORIGINAL ) ) + { + return t; + } + + if ( count <= 0 ) + { + return t; + } + + if ( t <= 0.0f ) + return 0.0f; + + float s = 0.0f, n = 0.0f; + + // find what tags this is between + int i; + for ( i = -1 ; i < count; i++ ) + { + s = GetBoundedAbsoluteTagPercentage( PLAYBACK, i ); + n = GetBoundedAbsoluteTagPercentage( PLAYBACK, i + 1 ); + + if ( t >= s && t <= n ) + { + break; + } + } + + int prev = i - 1; + int start = i; + int end = i + 1; + int next = i + 2; + + prev = max( -2, prev ); + start = max( -1, start ); + end = min( end, count ); + next = min( next, count + 1 ); + + CEventAbsoluteTag *pStartTag = NULL; + CEventAbsoluteTag *pEndTag = NULL; + + // check for linear portion of lookup + if (start >= 0 && start < count) + { + pStartTag = GetAbsoluteTag( PLAYBACK, start ); + } + if (end >= 0 && end < count) + { + pEndTag = GetAbsoluteTag( PLAYBACK, end ); + } + + if (pStartTag && pEndTag) + { + if (pStartTag->GetLinear() && pEndTag->GetLinear()) + { + CEventAbsoluteTag *pOrigStartTag = GetAbsoluteTag( ORIGINAL, start ); + CEventAbsoluteTag *pOrigEndTag = GetAbsoluteTag( ORIGINAL, end ); + + if (pOrigStartTag && pOrigEndTag) + { + s = ( t - pStartTag->GetPercentage() ) / (pEndTag->GetPercentage() - pStartTag->GetPercentage()); + return (1 - s) * pOrigStartTag->GetPercentage() + s * pOrigEndTag->GetPercentage(); + } + } + } + + float dt = n - s; + + Vector vPre( GetBoundedAbsoluteTagPercentage( PLAYBACK, prev ), GetBoundedAbsoluteTagPercentage( ORIGINAL, prev ), 0 ); + Vector vStart( GetBoundedAbsoluteTagPercentage( PLAYBACK, start ), GetBoundedAbsoluteTagPercentage( ORIGINAL, start ), 0 ); + Vector vEnd( GetBoundedAbsoluteTagPercentage( PLAYBACK, end ), GetBoundedAbsoluteTagPercentage( ORIGINAL, end ), 0 ); + Vector vNext( GetBoundedAbsoluteTagPercentage( PLAYBACK, next ), GetBoundedAbsoluteTagPercentage( ORIGINAL, next ), 0 ); + + // simulate sections of either side of "linear" portion of ramp as linear slope + if (pStartTag && pStartTag->GetLinear()) + { + vPre.Init( vStart.x - (vEnd.x - vStart.x), vStart.y - (vEnd.y - vStart.y), 0 ); + } + + if (pEndTag && pEndTag->GetLinear()) + { + vNext.Init( vEnd.x + (vEnd.x - vStart.x), vEnd.y + (vEnd.y - vStart.y), 0 ); + } + + + float f2 = 0.0f; + if ( dt > 0.0f ) + { + f2 = ( t - s ) / ( dt ); + } + f2 = clamp( f2, 0.0f, 1.0f ); + + Vector vOut; + Catmull_Rom_Spline_NormalizeX( + vPre, + vStart, + vEnd, + vNext, + f2, + vOut ); + + return vOut.y; + + /* + float duration; + GetGestureSequenceDuration( duration ); + + float retval = clamp( vOut.y, 0.0f, duration ); + return retval; + */ +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +// Output : float +//----------------------------------------------------------------------------- +float CChoreoEvent::GetPlaybackPercentageFromOriginalPercentage( float t ) +{ + Assert( GetType() == GESTURE ); + if ( GetType() != GESTURE ) + return t; + + int count = GetNumAbsoluteTags( PLAYBACK ); + + if ( count != GetNumAbsoluteTags( ORIGINAL ) ) + { + return t; + } + + if ( count <= 0 ) + { + return t; + } + + if ( t <= 0.0f ) + return 0.0f; + + float s = 0.0f, n = 0.0f; + + // find what tags this is between + int i; + for ( i = -1 ; i < count; i++ ) + { + s = GetBoundedAbsoluteTagPercentage( PLAYBACK, i ); + n = GetBoundedAbsoluteTagPercentage( PLAYBACK, i + 1 ); + + if ( t >= s && t <= n ) + { + break; + } + } + + int prev = i - 1; + int start = i; + int end = i + 1; + int next = i + 2; + + prev = max( -2, prev ); + start = max( -1, start ); + end = min( end, count ); + next = min( next, count + 1 ); + + CEventAbsoluteTag *pStartTag = NULL; + CEventAbsoluteTag *pEndTag = NULL; + + // check for linear portion of lookup + if (start >= 0 && start < count) + { + pStartTag = GetAbsoluteTag( ORIGINAL, start ); + } + if (end >= 0 && end < count) + { + pEndTag = GetAbsoluteTag( ORIGINAL, end ); + } + + // check for linear portion of lookup + if (pStartTag && pEndTag) + { + if (pStartTag->GetLinear() && pEndTag->GetLinear()) + { + CEventAbsoluteTag *pPlaybackStartTag = GetAbsoluteTag( PLAYBACK, start ); + CEventAbsoluteTag *pPlaybackEndTag = GetAbsoluteTag( PLAYBACK, end ); + + if (pPlaybackStartTag && pPlaybackEndTag) + { + s = ( t - pStartTag->GetPercentage() ) / (pEndTag->GetPercentage() - pStartTag->GetPercentage()); + return (1 - s) * pPlaybackStartTag->GetPercentage() + s * pPlaybackEndTag->GetPercentage(); + } + } + } + + float dt = n - s; + + Vector vPre( GetBoundedAbsoluteTagPercentage( ORIGINAL, prev ), GetBoundedAbsoluteTagPercentage( PLAYBACK, prev ), 0 ); + Vector vStart( GetBoundedAbsoluteTagPercentage( ORIGINAL, start ), GetBoundedAbsoluteTagPercentage( PLAYBACK, start ), 0 ); + Vector vEnd( GetBoundedAbsoluteTagPercentage( ORIGINAL, end ), GetBoundedAbsoluteTagPercentage( PLAYBACK, end ), 0 ); + Vector vNext( GetBoundedAbsoluteTagPercentage( ORIGINAL, next ), GetBoundedAbsoluteTagPercentage( PLAYBACK, next ), 0 ); + + // simulate sections of either side of "linear" portion of ramp as linear slope + if (pStartTag && pStartTag->GetLinear()) + { + vPre.Init( vStart.x - (vEnd.x - vStart.x), vStart.y - (vEnd.y - vStart.y), 0 ); + } + + if (pEndTag && pEndTag->GetLinear()) + { + vNext.Init( vEnd.x + (vEnd.x - vStart.x), vEnd.y + (vEnd.y - vStart.y), 0 ); + } + + float f2 = 0.0f; + if ( dt > 0.0f ) + { + f2 = ( t - s ) / ( dt ); + } + f2 = clamp( f2, 0.0f, 1.0f ); + + Vector vOut; + Catmull_Rom_Spline_NormalizeX( + vPre, + vStart, + vEnd, + vNext, + f2, + vOut ); + + return vOut.y; + + /* + float duration; + GetGestureSequenceDuration( duration ); + + float retval = clamp( vOut.y, 0.0f, duration ); + return retval; + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : duration - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetGestureSequenceDuration( float duration ) +{ + m_flGestureSequenceDuration = duration; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : duration - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoEvent::GetGestureSequenceDuration( float& duration ) +{ + bool valid = m_flGestureSequenceDuration != 0.0f; + + if ( !valid ) + { + duration = GetDuration(); + } + else + { + duration = m_flGestureSequenceDuration; + } + + return valid; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pitch - +//----------------------------------------------------------------------------- +int CChoreoEvent::GetPitch( void ) const +{ + return m_nPitch; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pitch - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetPitch( int pitch ) +{ + m_nPitch = pitch; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : yaw - +//----------------------------------------------------------------------------- +int CChoreoEvent::GetYaw( void ) const +{ + return m_nYaw; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : yaw - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetYaw( int yaw ) +{ + m_nYaw = yaw; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +// -1 - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetLoopCount( int numloops ) +{ + Assert( GetType() == LOOP ); + // Never below -1 + m_nNumLoops = max( numloops, -1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoEvent::GetNumLoopsRemaining( void ) +{ + Assert( GetType() == LOOP ); + + return m_nLoopsRemaining; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : loops - +//----------------------------------------------------------------------------- +void CChoreoEvent::SetNumLoopsRemaining( int loops ) +{ + Assert( GetType() == LOOP ); + + m_nLoopsRemaining = loops; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoEvent::GetLoopCount( void ) +{ + Assert( GetType() == LOOP ); + return m_nNumLoops; +} + +EdgeInfo_t *CChoreoEvent::GetRampEdgeInfo( int idx ) +{ + return &m_RampEdgeInfo[ idx ]; +} + +int CChoreoEvent::GetRampCount( void ) +{ + return m_Ramp.Count(); +} + +CExpressionSample *CChoreoEvent::GetRamp( int index ) +{ + if ( index < 0 || index >= GetRampCount() ) + return NULL; + + return &m_Ramp[ index ]; +} + +CExpressionSample *CChoreoEvent::AddRamp( float time, float value, bool selected ) +{ + CExpressionSample sample; + + sample.time = time; + sample.value = value; + sample.selected = selected; + + int idx = m_Ramp.AddToTail( sample ); + return &m_Ramp[ idx ]; +} + +void CChoreoEvent::DeleteRamp( int index ) +{ + if ( index < 0 || index >= GetRampCount() ) + return; + + m_Ramp.Remove( index ); +} + +void CChoreoEvent::ClearRamp( void ) +{ + m_Ramp.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::ResortRamp( void ) +{ + for ( int i = 0; i < m_Ramp.Size(); i++ ) + { + for ( int j = i + 1; j < m_Ramp.Size(); j++ ) + { + CExpressionSample src = m_Ramp[ i ]; + CExpressionSample dest = m_Ramp[ j ]; + + if ( src.time > dest.time ) + { + m_Ramp[ i ] = dest; + m_Ramp[ j ] = src; + } + } + } + + RemoveOutOfRangeRampSamples(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : number - +// Output : CExpressionSample +//----------------------------------------------------------------------------- +CExpressionSample *CChoreoEvent::GetBoundedRamp( int number, bool& bClamped ) +{ + // Search for two samples which span time f + if ( number < 0 ) + { + static CExpressionSample nullstart; + nullstart.time = 0.0f; + nullstart.value = RampGetEdgeZeroValue( true ); + nullstart.SetCurveType( RampGetEdgeCurveType( true ) ); + bClamped = true; + return &nullstart; + } + else if ( number >= GetRampCount() ) + { + static CExpressionSample nullend; + nullend.time = GetDuration(); + nullend.value = RampGetEdgeZeroValue( false ); + nullend.SetCurveType( RampGetEdgeCurveType( false ) ); + bClamped = true; + return &nullend; + } + + bClamped = false; + return GetRamp( number ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::RemoveOutOfRangeRampSamples( void ) +{ + float duration = GetDuration(); + + int c = GetRampCount(); + for ( int i = c-1; i >= 0; i-- ) + { + CExpressionSample src = m_Ramp[ i ]; + if ( src.time < 0 || + src.time > duration + 0.01 ) + { + m_Ramp.Remove( i ); + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::RescaleGestureTimes( float newstart, float newend ) +{ + if ( GetType() != CChoreoEvent::GESTURE ) + return; + + // Did it actually change + if ( newstart == GetStartTime() && + newend == GetEndTime() ) + { + return; + } + + float newduration = newend - newstart; + + float dt = 0.0f; + //If the end is moving, leave tags stay where they are (dt == 0.0f) + if ( newstart != GetStartTime() ) + { + // Otherwise, if the new start is later, then tags need to be shifted backwards + dt -= ( newstart - GetStartTime() ); + } + + int i; + int count = GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ); + for ( i = 0; i < count; i++ ) + { + CEventAbsoluteTag *tag = GetAbsoluteTag( CChoreoEvent::PLAYBACK, i ); + float tagtime = tag->GetPercentage() * GetDuration(); + + tagtime += dt; + + tagtime = clamp( tagtime / newduration, 0.0f, 1.0f ); + + tag->SetPercentage( tagtime ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Make sure tags aren't co-located or out of order +//----------------------------------------------------------------------------- +bool CChoreoEvent::PreventTagOverlap( void ) +{ + bool bHadOverlap = false; + + // FIXME: limit to single frame? + float minDp = 0.01; + + float minP = 1.00; + + int count = GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ); + for ( int i = count - 1; i >= 0; i-- ) + { + CEventAbsoluteTag *tag = GetAbsoluteTag( CChoreoEvent::PLAYBACK, i ); + + if (tag->GetPercentage() > minP) + { + tag->SetPercentage( minP ); + + minDp = min( 0.01, minP / (i + 1) ); + bHadOverlap = true; + } + else + { + minP = tag->GetPercentage(); + } + minP = max( minP - minDp, 0 ); + } + + return bHadOverlap; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// Output : CEventAbsoluteTag +//----------------------------------------------------------------------------- +CEventAbsoluteTag *CChoreoEvent::FindEntryTag( AbsTagType type ) +{ + for ( int i = 0; i < m_AbsoluteTags[ type ].Size(); i++ ) + { + CEventAbsoluteTag *ptag = &m_AbsoluteTags[ type ][ i ]; + if ( !ptag ) + continue; + + if ( ptag->GetEntry() ) + { + return ptag; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// Output : CEventAbsoluteTag +//----------------------------------------------------------------------------- +CEventAbsoluteTag *CChoreoEvent::FindExitTag( AbsTagType type ) +{ + for ( int i = 0; i < m_AbsoluteTags[ type ].Size(); i++ ) + { + CEventAbsoluteTag *ptag = &m_AbsoluteTags[ type ][ i ]; + if ( !ptag ) + continue; + + if ( ptag->GetExit() ) + { + return ptag; + } + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *style - +// maxlen - +//----------------------------------------------------------------------------- +void CChoreoEvent::GetMovementStyle( char *style, int maxlen ) +{ + Assert( GetType() == MOVETO ); + + style[0] = 0; + + const char *in = m_Parameters2.c_str(); + char *out = style; + + while ( *in && *in != '\0' && *in != ' ' ) + { + if ( out - style >= maxlen - 1 ) + break; + *out++ = *in++; + } + + *out = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *style - +// maxlen - +//----------------------------------------------------------------------------- +void CChoreoEvent::GetDistanceStyle( char *style, int maxlen ) +{ + Assert( GetType() == MOVETO ); + + style[0]= 0; + + const char *in = Q_strstr( m_Parameters2.c_str(), " " ); + if ( !in ) + return; + + in++; + char *out = style; + + while ( *in && *in != '\0' ) + { + if ( out - style >= maxlen - 1 ) + break; + *out++ = *in++; + } + + *out = 0; +} + +void CChoreoEvent::SetCloseCaptionType( CLOSECAPTION type ) +{ + Assert( m_fType == SPEAK ); + m_ccType = type; +} + +CChoreoEvent::CLOSECAPTION CChoreoEvent::GetCloseCaptionType() const +{ + Assert( m_fType == SPEAK ); + return (CLOSECAPTION)m_ccType; +} + +void CChoreoEvent::SetCloseCaptionToken( char const *token ) +{ + Assert( m_fType == SPEAK ); + Assert( token ); + m_CCToken = token; +} + +char const *CChoreoEvent::GetCloseCaptionToken() const +{ + Assert( m_fType == SPEAK ); + return m_CCToken.c_str(); +} + +bool CChoreoEvent::GetPlaybackCloseCaptionToken( char *dest, int destlen ) +{ + dest[0] = 0; + + Assert( m_fType == SPEAK ); + + switch ( m_ccType ) + { + default: + case CC_DISABLED: + { + return false; + } + case CC_SLAVE: + { + // If it's a slave, then only disable if we're not using the combined wave + if ( IsUsingCombinedFile() ) + { + return false; + } + + if ( m_CCToken[ 0 ] != 0 ) + { + Q_strncpy( dest, m_CCToken.c_str(), destlen ); + } + else + { + Q_strncpy( dest, m_Parameters.c_str(), destlen ); + } + return true; + } + case CC_MASTER: + { + // Always use the override if we're the master, otherwise always use the default + // parameter + if ( m_CCToken[ 0 ] != 0 ) + { + Q_strncpy( dest, m_CCToken.c_str(), destlen ); + } + else + { + Q_strncpy( dest, m_Parameters.c_str(), destlen ); + } + return true; + } + } + + return false; +} + + +void CChoreoEvent::SetUsingCombinedFile( bool isusing ) +{ + Assert( m_fType == SPEAK ); + m_bUsingCombinedSoundFile = isusing; +} + +bool CChoreoEvent::IsUsingCombinedFile() const +{ + Assert( m_fType == SPEAK ); + return m_bUsingCombinedSoundFile; +} + +void CChoreoEvent::SetRequiredCombinedChecksum( unsigned int checksum ) +{ + Assert( m_fType == SPEAK ); + m_uRequiredCombinedChecksum = checksum; +} + +unsigned int CChoreoEvent::GetRequiredCombinedChecksum() +{ + Assert( m_fType == SPEAK ); + return m_uRequiredCombinedChecksum; +} + +void CChoreoEvent::SetNumSlaves( int num ) +{ + Assert( m_fType == SPEAK ); + Assert( num >= 0 ); + m_nNumSlaves = num; +} + +int CChoreoEvent::GetNumSlaves() const +{ + Assert( m_fType == SPEAK ); + return m_nNumSlaves; +} + +void CChoreoEvent::SetLastSlaveEndTime( float t ) +{ + Assert( m_fType == SPEAK ); + m_flLastSlaveEndTime = t; +} + +float CChoreoEvent::GetLastSlaveEndTime() const +{ + Assert( m_fType == SPEAK ); + return m_flLastSlaveEndTime; +} + +void CChoreoEvent::SetCloseCaptionTokenValid( bool valid ) +{ + Assert( m_fType == SPEAK ); + m_bCCTokenValid = valid; +} + +bool CChoreoEvent::GetCloseCaptionTokenValid() const +{ + Assert( m_fType == SPEAK ); + return m_bCCTokenValid; +} + + +//----------------------------------------------------------------------------- +// Purpose: Removes characters which can't appear in windows filenames +// Input : *in - +// *dest - +// destlen - +// Output : static void +//----------------------------------------------------------------------------- +static void CleanupTokenName( char const *in, char *dest, int destlen ) +{ + char *out = dest; + while ( *in && ( out - dest ) < destlen ) + { + if ( isalnum( *in ) || // lowercase, uppercase, digits and underscore are valid + *in == '_' ) + { + *out++ = *in; + } + else + { + *out++ = '_'; // Put underscores in for bogus characters + } + in++; + } + *out = 0; +} + +bool CChoreoEvent::ComputeCombinedBaseFileName( char *dest, int destlen, bool creategenderwildcard ) +{ + if ( m_fType != SPEAK ) + return false; + + if ( m_ccType != CC_MASTER ) + return false; + + if ( GetNumSlaves() == 0 ) + return false; + + if ( !m_pScene ) + return false; + + char vcdpath[ 512 ]; + char cleanedtoken[ MAX_CCTOKEN_STRING ]; + CleanupTokenName( m_CCToken.c_str(), cleanedtoken, sizeof( cleanedtoken ) ); + + if ( Q_strlen( cleanedtoken ) <= 0 ) + return false; + + Q_strncpy( vcdpath, m_pScene->GetFilename(), sizeof( vcdpath ) ); + Q_StripFilename( vcdpath ); + Q_FixSlashes( vcdpath, '/' ); + + char *pvcd = vcdpath; + + char *offset = Q_strstr( vcdpath, "scenes" ); + if ( offset ) + { + pvcd = offset + 6; + if ( *pvcd == '/' ) + { + ++pvcd; + } + } + + size_t len = Q_strlen( pvcd ); + + if ( len > 0 && ( len + 1 ) < ( sizeof( vcdpath ) - 1 ) ) + { + pvcd[ len ] = '/'; + pvcd[ len + 1 ] = 0; + } + + Assert( !Q_strstr( pvcd, ":" ) ); + + if ( creategenderwildcard ) + { + Q_snprintf( dest, destlen, "sound/combined/%s%s_$gender.wav", pvcd, cleanedtoken ); + } + else + { + Q_snprintf( dest, destlen, "sound/combined/%s%s.wav", pvcd, cleanedtoken ); + } + return true; +} + +bool CChoreoEvent::IsCombinedUsingGenderToken() const +{ + return m_bCombinedUsingGenderToken; +} + +void CChoreoEvent::SetCombinedUsingGenderToken( bool using_gender ) +{ + m_bCombinedUsingGenderToken = using_gender; +} + + +int CChoreoEvent::ValidateCombinedFile() +{ + + return 0; +} + +bool CChoreoEvent::IsSuppressingCaptionAttenuation() const +{ + return m_bSuppressCaptionAttenuation; +} + +void CChoreoEvent::SetSuppressingCaptionAttenuation( bool suppress ) +{ + m_bSuppressCaptionAttenuation = suppress; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEvent::ClearEventDependencies() +{ + m_Dependencies.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *other - +//----------------------------------------------------------------------------- +void CChoreoEvent::AddEventDependency( CChoreoEvent *other ) +{ + if ( m_Dependencies.Find( other ) == m_Dependencies.InvalidIndex() ) + { + m_Dependencies.AddToTail( other ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : list - +//----------------------------------------------------------------------------- +void CChoreoEvent::GetEventDependencies( CUtlVector< CChoreoEvent * >& list ) +{ + int c = m_Dependencies.Count(); + for ( int i = 0; i < c; ++i ) + { + list.AddToTail( m_Dependencies[ i ] ); + } +} + +void CChoreoEvent::RampSetEdgeInfo( bool leftEdge, int curveType, float zero ) +{ + int idx = leftEdge ? 0 : 1; + m_RampEdgeInfo[ idx ].m_CurveType = curveType; + m_RampEdgeInfo[ idx ].m_flZeroPos = zero; +} + +void CChoreoEvent::RampGetEdgeInfo( bool leftEdge, int& curveType, float& zero ) const +{ + int idx = leftEdge ? 0 : 1; + curveType = m_RampEdgeInfo[ idx ].m_CurveType; + zero = m_RampEdgeInfo[ idx ].m_flZeroPos; +} + +void CChoreoEvent::RampSetEdgeActive( bool leftEdge, bool state ) +{ + int idx = leftEdge ? 0 : 1; + m_RampEdgeInfo[ idx ].m_bActive = state; +} + +bool CChoreoEvent::RampIsEdgeActive( bool leftEdge ) const +{ + int idx = leftEdge ? 0 : 1; + return m_RampEdgeInfo[ idx ].m_bActive; +} + +int CChoreoEvent::RampGetEdgeCurveType( bool leftEdge ) const +{ + if ( !RampIsEdgeActive( leftEdge ) ) + { + return CURVE_DEFAULT; + } + + int idx = leftEdge ? 0 : 1; + return m_RampEdgeInfo[ idx ].m_CurveType; +} + +float CChoreoEvent::RampGetEdgeZeroValue( bool leftEdge ) const +{ + if ( !RampIsEdgeActive( leftEdge ) ) + { + return 0.0f; + } + + int idx = leftEdge ? 0 : 1; + return m_RampEdgeInfo[ idx ].m_flZeroPos; +} + + +void CChoreoEvent::SaveToBuffer( CUtlBuffer& buf, CChoreoScene *pScene ) +{ + buf.PutChar( GetType() ); + buf.PutString( GetName() ); + + float st, et; + st = GetStartTime(); + et = GetEndTime(); + + buf.PutFloat( st ); + buf.PutFloat( et ); + + buf.PutString( GetParameters() ); + buf.PutString( GetParameters2() ); + + SaveRampToBuffer( buf ); + + buf.PutChar( IsResumeCondition() ? 1 : 0 ); + buf.PutChar( IsLockBodyFacing() ? 1 : 0 ); + buf.PutFloat( GetDistanceToTarget() ); + buf.PutChar( IsFixedLength() ? 1 : 0 ); + + buf.PutShort( GetNumRelativeTags() ); + + for ( int t = 0; t < GetNumRelativeTags(); t++ ) + { + CEventRelativeTag *rt = GetRelativeTag( t ); + Assert( rt ); + buf.PutString( rt->GetName() ); + buf.PutFloat( rt->GetPercentage() ); + } + + buf.PutShort( GetNumTimingTags() ); + + for ( int t = 0; t < GetNumTimingTags(); t++ ) + { + CFlexTimingTag *tt = GetTimingTag( t ); + Assert( tt ); + buf.PutString( tt->GetName() ); + buf.PutFloat( tt->GetPercentage() ); + // Don't save locked state, it's only used by the editor tt->GetLocked() + } + + int tagtype; + for ( tagtype = 0; tagtype < CChoreoEvent::NUM_ABS_TAG_TYPES; tagtype++ ) + { + int num = GetNumAbsoluteTags( (CChoreoEvent::AbsTagType)tagtype ); + buf.PutShort( num ); + for ( int i = 0; i < num ; ++i ) + { + CEventAbsoluteTag *abstag = GetAbsoluteTag( (CChoreoEvent::AbsTagType)tagtype, i ); + Assert( abstag ); + buf.PutString( abstag->GetName() ); + buf.PutFloat( abstag->GetPercentage() ); + } + } + + if ( GetType() == CChoreoEvent::GESTURE ) + { + float duration; + if ( GetGestureSequenceDuration( duration ) ) + { + buf.PutFloat( duration ); + } + else + { + buf.PutFloat( -1.0f ); + } + } + + buf.PutChar( IsUsingRelativeTag() ? 1 : 0 ); + if ( IsUsingRelativeTag() ) + { + buf.PutString( GetRelativeTagName() ); + buf.PutString( GetRelativeWavName() ); + } + + SaveFlexAnimationsToBuffer( buf ); + + if ( GetType() == LOOP ) + { + buf.PutChar( GetLoopCount() ); + } + + if ( GetType() == CChoreoEvent::SPEAK ) + { + buf.PutChar( GetCloseCaptionType() ); + buf.PutString( GetCloseCaptionToken() ); + int flags = 0; + + if ( GetCloseCaptionType() != CChoreoEvent::CC_DISABLED && + IsUsingCombinedFile() ) + { + flags |= ( 1<<0 ); + } + if ( IsCombinedUsingGenderToken() ) + { + flags |= ( 1<<1 ); + } + if ( IsSuppressingCaptionAttenuation() ) + { + flags |= ( 1<<2 ); + } + + buf.PutChar( flags ); + } +} + +bool CChoreoEvent::RestoreFromBuffer( CUtlBuffer& buf, CChoreoScene *pScene ) +{ + SetType( (EVENTTYPE)buf.GetChar() ); + char sz[ 256 ]; + buf.GetString( sz, sizeof( sz ) ); + SetName( sz ); + + SetStartTime( buf.GetFloat() ); + SetEndTime( buf.GetFloat() ); + + char params[ 2048 ]; + buf.GetString( params, sizeof( params ) ); + SetParameters( params ); + buf.GetString( params, sizeof( params ) ); + SetParameters2( params ); + + if ( !RestoreRampFromBuffer( buf ) ) + return false; + + SetResumeCondition( buf.GetChar() == 1 ? true : false ); + SetLockBodyFacing( buf.GetChar() == 1 ? true : false ); + SetDistanceToTarget( buf.GetFloat() ); + SetFixedLength( buf.GetChar() == 1 ? true : false ); + + int numRelTags = buf.GetShort(); + for ( int i = 0 ;i < numRelTags; ++i ) + { + char tagName[ 256 ]; + buf.GetString( tagName, sizeof( tagName ) ); + float percentage = buf.GetFloat(); + AddRelativeTag( tagName, percentage ); + } + + int numTimingTags = buf.GetShort(); + for ( int i = 0 ;i < numTimingTags; ++i ) + { + char tagName[ 256 ]; + buf.GetString( tagName, sizeof( tagName ) ); + float percentage = buf.GetFloat(); + // Don't parse locked state, only used by editors + AddTimingTag( tagName, percentage, false ); + } + + int tagtype; + for ( tagtype = 0; tagtype < CChoreoEvent::NUM_ABS_TAG_TYPES; tagtype++ ) + { + int num = buf.GetShort(); + for ( int i = 0 ; i < num ; ++i ) + { + char tagName[ 256 ]; + buf.GetString( tagName, sizeof( tagName ) ); + float percentage = buf.GetFloat(); + // Don't parse locked state, only used by editors + AddAbsoluteTag( (CChoreoEvent::AbsTagType)tagtype, tagName, percentage ); + } + } + + if ( GetType() == CChoreoEvent::GESTURE ) + { + float duration = buf.GetFloat(); + if ( duration != -1 ) + { + SetGestureSequenceDuration( duration ); + } + } + + if ( buf.GetChar() == 1 ) + { + char tagname[ 256 ]; + char wavname[ 256 ]; + buf.GetString( tagname, sizeof( tagname ) ); + buf.GetString( wavname, sizeof( wavname ) ); + + SetUsingRelativeTag( true, tagname, wavname ); + } + + if ( !RestoreFlexAnimationsFromBuffer( buf ) ) + return false; + + if ( GetType() == LOOP ) + { + SetLoopCount( buf.GetChar() ); + } + + if ( GetType() == CChoreoEvent::SPEAK ) + { + SetCloseCaptionType( (CLOSECAPTION)buf.GetChar() ); + char cctoken[ 256 ]; + buf.GetString( cctoken, sizeof( cctoken ) ); + SetCloseCaptionToken( cctoken ); + int flags = buf.GetChar(); + if ( flags & ( 1<<0 ) ) + { + SetUsingCombinedFile( true ); + } + if ( flags & ( 1<<1 ) ) + { + SetCombinedUsingGenderToken( true ); + } + if ( flags & ( 1<<2 ) ) + { + SetSuppressingCaptionAttenuation( true ); + } + } + + return true; +} + +void CChoreoEvent::SaveRampToBuffer( CUtlBuffer& buf ) +{ + int c = GetRampCount(); + buf.PutInt( c ); + if ( c <= 0 ) + return; + + for ( int i = 0; i < c; i++ ) + { + CExpressionSample *sample = GetRamp( i ); + buf.PutFloat( sample->time ); + buf.PutFloat( sample->value ); + } +} + +bool CChoreoEvent::RestoreRampFromBuffer( CUtlBuffer& buf ) +{ + int c = buf.GetInt(); + for ( int i = 0; i < c; i++ ) + { + float t, v; + t = buf.GetFloat(); + v = buf.GetFloat(); + + AddRamp( t, v, false ); + } + + return true; +} + +void CChoreoEvent::SaveFlexAnimationsToBuffer( CUtlBuffer& buf ) +{ + buf.PutShort( GetNumFlexAnimationTracks() ); + for ( int i = 0; i < GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = GetFlexAnimationTrack( i ); + + buf.PutString( track->GetFlexControllerName() ); + buf.PutChar( track->IsTrackActive() ? 1 : 0 ); + buf.PutChar( track->IsComboType() ? 1 : 0 ); + buf.PutFloat( track->GetMin() ); + buf.PutFloat( track->GetMax() ); + + buf.PutShort( track->GetNumSamples( 0 ) ); + for ( int j = 0 ; j < track->GetNumSamples( 0 ) ; j++ ) + { + CExpressionSample *s = track->GetSample( j, 0 ); + if ( !s ) + continue; + + buf.PutFloat( s->time ); + buf.PutFloat( s->value ); + } + + // Write out combo samples + if ( track->IsComboType() ) + { + buf.PutShort( track->GetNumSamples( 1 ) ); + for ( int j = 0 ; j < track->GetNumSamples( 1) ; j++ ) + { + CExpressionSample *s = track->GetSample( j, 1 ); + if ( !s ) + continue; + + buf.PutFloat( s->time ); + buf.PutFloat( s->value ); + } + } + } +} + +bool CChoreoEvent::RestoreFlexAnimationsFromBuffer( CUtlBuffer& buf ) +{ + int numTracks = buf.GetShort(); + + for ( int i = 0; i < numTracks; i++ ) + { + char name[ 256 ]; + buf.GetString( name, sizeof( name ) ); + + CFlexAnimationTrack *track = AddTrack( name ); + track->SetTrackActive( buf.GetChar() == 1 ? true : false ); + track->SetComboType( buf.GetChar() == 1 ? true : false ); + track->SetMin( buf.GetFloat() ); + track->SetMax( buf.GetFloat() ); + + int s = buf.GetShort(); + for ( int j = 0; j < s; ++j ) + { + float t, v; + t = buf.GetFloat(); + v = buf.GetFloat(); + + track->AddSample( t, v, 0 ); + } + + if ( track->IsComboType() ) + { + int s = buf.GetShort(); + for ( int j = 0; j < s; ++j ) + { + float t, v; + t = buf.GetFloat(); + v = buf.GetFloat(); + + track->AddSample( t, v, 1 ); + } + } + } + + return true; +} diff --git a/choreoobjects/choreoobjects-2003.vcproj b/choreoobjects/choreoobjects-2003.vcproj new file mode 100644 index 00000000..277a2cc3 --- /dev/null +++ b/choreoobjects/choreoobjects-2003.vcproj @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/choreoobjects/choreoobjects-2005.vcproj b/choreoobjects/choreoobjects-2005.vcproj new file mode 100644 index 00000000..7571eb70 --- /dev/null +++ b/choreoobjects/choreoobjects-2005.vcproj @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/choreoobjects/choreoscene.cpp b/choreoobjects/choreoscene.cpp new file mode 100644 index 00000000..2498d2cb --- /dev/null +++ b/choreoobjects/choreoscene.cpp @@ -0,0 +1,3987 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + + +#if defined(_WIN32) && !defined(_XBOX) +#include +#endif + +#include "basetypes.h" +#include +#include "choreoscene.h" +#include "choreoevent.h" +#include "choreochannel.h" +#include "choreoactor.h" +#include "ichoreoeventcallback.h" +#include "iscenetokenprocessor.h" +#include "utlbuffer.h" +#include "filesystem.h" +#include "utlrbtree.h" +#include "mathlib.h" +#include "vstdlib/strtools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IFileSystem *SceneFileSystem(); + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) +#endif + +// Let scene linger for 1/4 second so blends can finish +#define SCENE_LINGER_TIME 0.25f + +// The engine turns this to true in dlls/sceneentity.cpp at bool SceneCacheInit()!!! +bool CChoreoScene::s_bEditingDisabled = false; + +//----------------------------------------------------------------------------- +// Purpose: Debug printout +// Input : level - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoScene::choreoprintf( int level, const char *fmt, ... ) +{ + char string[ 2048 ]; + va_list argptr; + va_start( argptr, fmt ); + Q_vsnprintf( string, sizeof(string), fmt, argptr ); + va_end( argptr ); + + while ( level-- > 0 ) + { + if (m_pfnPrint ) + { + (*m_pfnPrint)( " " ); + } + else + { + printf( " " ); + } + Msg( " " ); + } + + if ( m_pfnPrint ) + { + (*m_pfnPrint)( string ); + } + else + { + printf( string ); + } + + Msg( "%s", string ); +} + +//----------------------------------------------------------------------------- +// Purpose: Creates scene from a file +// Input : *filename - +// *pfn - +// Output : CChoreoScene +//----------------------------------------------------------------------------- +CChoreoScene *ChoreoLoadScene +( + char const *filename, + IChoreoEventCallback *callback, + ISceneTokenProcessor *tokenizer, + void ( *pfn ) ( const char *fmt, ... ) +) +{ + MEM_ALLOC_CREDIT_CLASS(); + CChoreoScene *scene = new CChoreoScene( callback ); + Assert( scene ); + scene->ParseFromBuffer( filename, tokenizer ); + scene->SetPrintFunc( pfn ); + return scene; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoScene::CChoreoScene( IChoreoEventCallback *callback ) +{ + Init( callback ); +} + + +//----------------------------------------------------------------------------- +// Purpose: // Assignment +// Input : src - +// Output : CChoreoScene& +//----------------------------------------------------------------------------- +CChoreoScene& CChoreoScene::operator=( const CChoreoScene& src ) +{ + Init( src.m_pIChoreoEventCallback ); + + // Delete existing + int i; + for ( i = 0; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + Assert( a ); + delete a; + } + + m_Actors.RemoveAll(); + + for ( i = 0; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + Assert( e ); + delete e; + } + + m_Events.RemoveAll(); + + for ( i = 0 ; i < m_Channels.Size(); i++ ) + { + CChoreoChannel *c = m_Channels[ i ]; + Assert( c ); + delete c; + } + + m_Channels.RemoveAll(); + + m_pTokenizer = src.m_pTokenizer; + + m_flCurrentTime = src.m_flCurrentTime; + m_flStartTime = src.m_flStartTime; + m_flEndTime = src.m_flEndTime; + m_flSoundSystemLatency = src.m_flSoundSystemLatency; + m_pfnPrint = src.m_pfnPrint; + m_flLastActiveTime = src.m_flLastActiveTime; + m_pTokenizer = src.m_pTokenizer; + m_bSubScene = src.m_bSubScene; + m_nSceneFPS = src.m_nSceneFPS; + m_bUseFrameSnap = src.m_bUseFrameSnap; + + // Now copy the object tree + // First copy the global events + + for ( i = 0; i < src.m_Events.Size(); i++ ) + { + CChoreoEvent *event = src.m_Events[ i ]; + if ( event->GetActor() == NULL ) + { + MEM_ALLOC_CREDIT(); + + // Copy it + CChoreoEvent *newEvent = AllocEvent(); + *newEvent = *event; + } + } + + // Finally, push actors, channels, events onto global stacks + for ( i = 0; i < src.m_Actors.Size(); i++ ) + { + CChoreoActor *actor = src.m_Actors[ i ]; + CChoreoActor *newActor = AllocActor(); + *newActor = *actor; + + for ( int j = 0; j < newActor->GetNumChannels() ; j++ ) + { + CChoreoChannel *ch = newActor->GetChannel( j ); + m_Channels.AddToTail( ch ); + + for ( int k = 0; k < ch->GetNumEvents(); k++ ) + { + CChoreoEvent *ev = ch->GetEvent( k ); + m_Events.AddToTail( ev ); + ev->SetScene( this ); + } + } + } + + Q_strncpy( m_szMapname, src.m_szMapname, sizeof( m_szMapname ) ); + + // Copy ramp over + m_SceneRamp.RemoveAll(); + for ( i = 0; i < src.m_SceneRamp.Count(); i++ ) + { + CExpressionSample sample = src.m_SceneRamp[ i ]; + AddSceneRamp( sample.time, sample.value, sample.selected ); + } + m_SceneRampEdgeInfo[ 0 ] = src.m_SceneRampEdgeInfo[ 0 ]; + m_SceneRampEdgeInfo[ 1 ] = src.m_SceneRampEdgeInfo[ 1 ]; + + m_TimeZoomLookup.RemoveAll(); + for ( i = 0; i < (int)src.m_TimeZoomLookup.Count(); i++ ) + { + m_TimeZoomLookup.Insert( src.m_TimeZoomLookup.GetElementName( i ), src.m_TimeZoomLookup[ i ] ); + } + + Q_strncpy( m_szFileName, src.m_szFileName, sizeof( m_szFileName ) ); + + m_nLastPauseEvent = src.m_nLastPauseEvent; + m_flPrecomputedStopTime = src.m_flPrecomputedStopTime; + + m_bitvecHasEventOfType = src.m_bitvecHasEventOfType; + + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::Init( IChoreoEventCallback *callback ) +{ + m_flPrecomputedStopTime = 0.0f; + m_pTokenizer = NULL; + m_szMapname[ 0 ] = 0; + + m_flCurrentTime = 0.0f; + m_flStartTime = 0.0f; + m_flEndTime = 0.0f; + m_flSoundSystemLatency = 0.0f; + m_pfnPrint = NULL; + m_flLastActiveTime = 0.0f; + m_flEarliestTime = 0.0f; + m_flLatestTime = 0.0f; + m_nActiveEvents = 0; + + m_pIChoreoEventCallback = callback; + + m_bSubScene = false; + m_nSceneFPS = DEFAULT_SCENE_FPS; + m_bUseFrameSnap = false; + m_szFileName[0] = 0; + + m_bIsBackground = false; + m_bitvecHasEventOfType.ClearAll(); + m_nLastPauseEvent = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Destroy objects and queues +//----------------------------------------------------------------------------- +CChoreoScene::~CChoreoScene( void ) +{ + int i; + for ( i = 0; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + Assert( a ); + delete a; + } + + m_Actors.RemoveAll(); + + for ( i = 0; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + Assert( e ); + delete e; + } + + m_Events.RemoveAll(); + + for ( i = 0 ; i < m_Channels.Size(); i++ ) + { + CChoreoChannel *c = m_Channels[ i ]; + Assert( c ); + delete c; + } + + m_Channels.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *callback - +//----------------------------------------------------------------------------- +void CChoreoScene::SetEventCallbackInterface( IChoreoEventCallback *callback ) +{ + m_pIChoreoEventCallback = callback; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : level - +// *e - +//----------------------------------------------------------------------------- +void CChoreoScene::PrintEvent( int level, CChoreoEvent *e ) +{ + choreoprintf( level, "event %s \"%s\"\n", CChoreoEvent::NameForType( e->GetType() ), e->GetName() ); + choreoprintf( level, "{\n" ); + choreoprintf( level + 1, "time %f %f\n", e->GetStartTime(), e->GetEndTime() ); + choreoprintf( level + 1, "param \"%s\"\n", e->GetParameters() ); + if ( strlen( e->GetParameters2() ) > 0 ) + { + choreoprintf( level + 1, "param2 \"%s\"\n", e->GetParameters2() ); + } + choreoprintf( level, "}\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : level - +// *c - +//----------------------------------------------------------------------------- +void CChoreoScene::PrintChannel( int level, CChoreoChannel *c ) +{ + choreoprintf( level, "channel \"%s\"\n", c->GetName() ); + choreoprintf( level, "{\n" ); + + for ( int i = 0; i < c->GetNumEvents(); i++ ) + { + CChoreoEvent *e = c->GetEvent( i ); + if ( e ) + { + PrintEvent( level + 1, e ); + } + } + + choreoprintf( level, "}\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : level - +// *a - +//----------------------------------------------------------------------------- +void CChoreoScene::PrintActor( int level, CChoreoActor *a ) +{ + choreoprintf( level, "actor \"%s\"\n", a->GetName() ); + choreoprintf( level, "{\n" ); + + for ( int i = 0; i < a->GetNumChannels(); i++ ) + { + CChoreoChannel *c = a->GetChannel( i ); + if ( c ) + { + PrintChannel( level + 1, c ); + } + } + + choreoprintf( level, "}\n\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::Print( void ) +{ + // Look for events that don't have actor/channel set + int i; + + for ( i = 0 ; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + if ( e->GetActor() ) + continue; + + PrintEvent( 0, e ); + } + + for ( i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + PrintActor( 0, a ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: prints if m_pfnPrint is active +// Output : +//----------------------------------------------------------------------------- + +void CChoreoScene::SceneMsg( const char *pFormat, ... ) +{ + char string[ 2048 ]; + va_list argptr; + va_start( argptr, pFormat ); + Q_vsnprintf( string, sizeof(string), pFormat, argptr ); + va_end( argptr ); + + if ( m_pfnPrint ) + { + (*m_pfnPrint)( string ); + } + else + { + Msg( "%s", string ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoScene::AllocEvent( void ) +{ + MEM_ALLOC_CREDIT_CLASS(); + CChoreoEvent *e = new CChoreoEvent( this ); + Assert( e ); + m_Events.AddToTail( e ); + return e; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoChannel +//----------------------------------------------------------------------------- +CChoreoChannel *CChoreoScene::AllocChannel( void ) +{ + MEM_ALLOC_CREDIT_CLASS(); + CChoreoChannel *c = new CChoreoChannel(); + Assert( c ); + m_Channels.AddToTail( c ); + return c; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoActor +//----------------------------------------------------------------------------- +CChoreoActor *CChoreoScene::AllocActor( void ) +{ + MEM_ALLOC_CREDIT_CLASS(); + CChoreoActor *a = new CChoreoActor; + Assert( a ); + m_Actors.AddToTail( a ); + return a; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : CChoreoActor +//----------------------------------------------------------------------------- +CChoreoActor *CChoreoScene::FindActor( const char *name ) +{ + for ( int i = 0; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + if ( !Q_stricmp( a->GetName(), name ) ) + return a; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoScene::GetNumEvents( void ) +{ + return m_Events.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : event - +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoScene::GetEvent( int event ) +{ + if ( event < 0 || event >= m_Events.Size() ) + return NULL; + + return m_Events[ event ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoScene::GetNumActors( void ) +{ + return m_Actors.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : actor - +// Output : CChoreoActor +//----------------------------------------------------------------------------- +CChoreoActor *CChoreoScene::GetActor( int actor ) +{ + if ( actor < 0 || actor >= GetNumActors() ) + return NULL; + return m_Actors[ actor ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoScene::GetNumChannels( void ) +{ + return m_Channels.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : channel - +// Output : CChoreoChannel +//----------------------------------------------------------------------------- +CChoreoChannel *CChoreoScene::GetChannel( int channel ) +{ + if ( channel < 0 || channel >= GetNumChannels() ) + return NULL; + return m_Channels[ channel ]; +} + +void CChoreoScene::ParseRamp( ISceneTokenProcessor *tokenizer, CChoreoEvent *e ) +{ + e->ClearRamp(); + + tokenizer->GetToken( true ); + + if ( !Q_stricmp( tokenizer->CurrentToken(), "leftedge" ) ) + { + ParseEdgeInfo( tokenizer, e->GetRampEdgeInfo( 0 ) ); + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "rightedge" ) ) + { + ParseEdgeInfo( tokenizer, e->GetRampEdgeInfo( 1 ) ); + } + + if ( stricmp( tokenizer->CurrentToken(), "{" ) ) + tokenizer->Error( "expecting {\n" ); + + while ( 1 ) + { + // Parse until } + tokenizer->GetToken( true ); + + if ( strlen( tokenizer->CurrentToken() ) <= 0 ) + { + tokenizer->Error( "expecting ramp data\n" ); + break; + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "}" ) ) + break; + + CUtlVector< CExpressionSample > samples; + + float time = (float)atof( tokenizer->CurrentToken() ); + tokenizer->GetToken( false ); + float value = (float)atof( tokenizer->CurrentToken() ); + + // Add to counter + int idx = samples.AddToTail(); + CExpressionSample *s = &samples[ idx ]; + + s->time = time; + s->value = value; + + // If there are more tokens on this line, then it's a new format curve name + if ( tokenizer->TokenAvailable() ) + { + tokenizer->GetToken( false ); + int curveType = Interpolator_CurveTypeForName( tokenizer->CurrentToken() ); + s->SetCurveType( curveType ); + } + + if ( samples.Size() >= 1 ) + { + for ( int i = 0; i < samples.Size(); i++ ) + { + CExpressionSample sample = samples[ i ]; + + CExpressionSample *newSample = e->AddRamp( sample.time, sample.value, false ); + newSample->SetCurveType( sample.GetCurveType() ); + } + } + } + + e->ResortRamp(); +} + +void CChoreoScene::ParseSceneRamp( ISceneTokenProcessor *tokenizer, CChoreoScene *scene ) +{ + scene->ClearSceneRamp(); + + tokenizer->GetToken( true ); + + if ( !Q_stricmp( tokenizer->CurrentToken(), "leftedge" ) ) + { + ParseEdgeInfo( tokenizer, scene->GetSceneRampEdgeInfo( 0 ) ); + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "rightedge" ) ) + { + ParseEdgeInfo( tokenizer, scene->GetSceneRampEdgeInfo( 1 ) ); + } + + if ( stricmp( tokenizer->CurrentToken(), "{" ) ) + tokenizer->Error( "expecting {\n" ); + + while ( 1 ) + { + // Parse until } + tokenizer->GetToken( true ); + + if ( strlen( tokenizer->CurrentToken() ) <= 0 ) + { + tokenizer->Error( "expecting ramp data\n" ); + break; + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "}" ) ) + break; + + CUtlVector< CExpressionSample > samples; + + float time = (float)atof( tokenizer->CurrentToken() ); + tokenizer->GetToken( false ); + float value = (float)atof( tokenizer->CurrentToken() ); + + // Add to counter + int idx = samples.AddToTail(); + CExpressionSample *s = &samples[ idx ]; + + s->time = time; + s->value = value; + + // If there are more tokens on this line, then it's a new format curve name + if ( tokenizer->TokenAvailable() ) + { + tokenizer->GetToken( false ); + int curveType = Interpolator_CurveTypeForName( tokenizer->CurrentToken() ); + s->SetCurveType( curveType ); + } + + if ( samples.Size() >= 1 ) + { + for ( int i = 0; i < samples.Size(); i++ ) + { + CExpressionSample sample = samples[ i ]; + + CExpressionSample *newSample = scene->AddSceneRamp( sample.time, sample.value, false ); + newSample->SetCurveType( sample.GetCurveType() ); + } + } + } + + scene->ResortSceneRamp(); +} + +//----------------------------------------------------------------------------- +// Purpose: Helper for restoring edge info +// Input : *edgeinfo - +//----------------------------------------------------------------------------- +void CChoreoScene::ParseEdgeInfo( ISceneTokenProcessor *tokenizer, EdgeInfo_t *edgeinfo ) +{ + Assert( edgeinfo ); + Assert( tokenizer ); + + tokenizer->GetToken( false ); + edgeinfo->m_bActive = true; + edgeinfo->m_CurveType = Interpolator_CurveTypeForName( tokenizer->CurrentToken() ); + tokenizer->GetToken( false ); + edgeinfo->m_flZeroPos = atof( tokenizer->CurrentToken() ); + tokenizer->GetToken( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tokenizer - +// *e - +//----------------------------------------------------------------------------- +void CChoreoScene::ParseFlexAnimations( ISceneTokenProcessor *tokenizer, CChoreoEvent *e, bool removeold /*= true*/ ) +{ + Assert( e ); + + if ( removeold ) + { + // Make sure there's nothing already there... + e->RemoveAllTracks(); + // Make it re-index + e->SetTrackLookupSet( false ); + } + + // BACKWARD COMPATABILITY + // in the old system the samples were 0.0 to 1.0 mapped to endtime - starttime + // if samples_use_time is true, then samples are actually offsets of time from starttime + bool samples_use_realtime = false; + // Parse tags between { } + // + tokenizer->GetToken( true ); + + Assert( e->HasEndTime() ); + + float endtime = e->GetEndTime(); + float starttime = e->GetStartTime(); + float event_time = endtime - starttime; + + // Is it the new file format? + if ( !Q_stricmp( tokenizer->CurrentToken(), "samples_use_time" ) ) + { + samples_use_realtime = true; + tokenizer->GetToken( true ); + } + + if ( stricmp( tokenizer->CurrentToken(), "{" ) ) + tokenizer->Error( "expecting {\n" ); + + while ( 1 ) + { + // Parse until } + tokenizer->GetToken( true ); + + if ( strlen( tokenizer->CurrentToken() ) <= 0 ) + { + tokenizer->Error( "expecting flex animation data\n" ); + break; + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "}" ) ) + break; + + char flexcontroller[ CFlexAnimationTrack::MAX_CONTROLLER_NAME ]; + Q_strncpy( flexcontroller, tokenizer->CurrentToken(), sizeof( flexcontroller ) ); + + // Animations default to active + bool active = true; + bool combo = false; + float range_min = 0.0f; + float range_max = 1.0f; + tokenizer->GetToken( true ); + + EdgeInfo_t edgeinfo[ 2 ]; + + if ( !Q_stricmp( tokenizer->CurrentToken(), "disabled" ) ) + { + active = false; + tokenizer->GetToken( true ); + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "combo" ) ) + { + combo = true; + tokenizer->GetToken( true ); + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "range" ) ) + { + tokenizer->GetToken( false ); + range_min = atof( tokenizer->CurrentToken() ); + tokenizer->GetToken( false ); + range_max = atof( tokenizer->CurrentToken() ); + tokenizer->GetToken( true ); + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "leftedge" ) ) + { + ParseEdgeInfo( tokenizer, &edgeinfo[ 0 ] ); + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "rightedge" ) ) + { + ParseEdgeInfo( tokenizer, &edgeinfo[ 1 ] ); + } + + CUtlVector< CExpressionSample > samples[2]; + + for ( int samplecount = 0; samplecount < ( combo ? 2 : 1 ); samplecount++ ) + { + if ( stricmp( tokenizer->CurrentToken(), "{" ) ) + { + tokenizer->Error( "expecting {\n" ); + } + + while ( 1 ) + { + tokenizer->GetToken( true ); + + if ( strlen( tokenizer->CurrentToken() ) <= 0 ) + { + tokenizer->Error( "expecting flex animation data\n" ); + break; + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "}" ) ) + break; + + float time = (float)atof( tokenizer->CurrentToken() ); + tokenizer->GetToken( false ); + float value = (float)atof( tokenizer->CurrentToken() ); + + // Add to counter + int idx = samples[ samplecount ].AddToTail(); + + CExpressionSample *s = &samples[ samplecount ][ idx ]; + + if ( samples_use_realtime ) + { + s->time = time; + } + else + { + // Time is an old style fraction (0 to 1) map into real time + s->time = time * event_time; + } + + s->value = value; + + // If there are more tokens on this line, then it's a new format curve name + if ( tokenizer->TokenAvailable() ) + { + tokenizer->GetToken( false ); + int curveType = Interpolator_CurveTypeForName( tokenizer->CurrentToken() ); + s->SetCurveType( curveType ); + } + } + + if ( combo && samplecount == 0 ) + { + tokenizer->GetToken( true ); + } + } + + if ( active || samples[ 0 ].Size() >= 1 ) + { + // Add it in + CFlexAnimationTrack *track = e->AddTrack( flexcontroller ); + Assert( track ); + track->SetTrackActive( active ); + track->SetComboType( combo ); + + track->SetMin( range_min ); + track->SetMax( range_max ); + + for ( int t = 0; t < ( combo ? 2 : 1 ); t++ ) + { + for ( int i = 0; i < samples[ t ].Size(); i++ ) + { + CExpressionSample *sample = &samples[ t ][ i ]; + + CExpressionSample *added = track->AddSample( sample->time, sample->value, t ); + Assert( added ); + added->SetCurveType( sample->GetCurveType() ); + } + } + + for ( int edge = 0; edge < 2; ++edge ) + { + if ( !edgeinfo[ edge ].m_bActive ) + continue; + + track->SetEdgeActive( edge == 0 ? true : false, true ); + track->SetEdgeInfo( edge == 0 ? true : false, edgeinfo[ edge ].m_CurveType, edgeinfo[ edge ].m_flZeroPos ); + } + + track->Resort( 0 ); + track->Resort( 1 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *channel - +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoScene::ParseEvent( CChoreoActor *actor, CChoreoChannel *channel ) +{ + // For conversion of old style attack/sustain/decay ramps + bool hadramp = false; + float attack = 1.0f, sustain = 1.0f, decay = 1.0f; + + CChoreoEvent *e; + { + MEM_ALLOC_CREDIT(); + e = AllocEvent(); + } + + MEM_ALLOC_CREDIT(); + + Assert( e ); + + // read event type + m_pTokenizer->GetToken( false ); + + e->SetType( CChoreoEvent::TypeForName( m_pTokenizer->CurrentToken() ) ); + + m_pTokenizer->GetToken( false ); + e->SetName( m_pTokenizer->CurrentToken() ); + + m_pTokenizer->GetToken( true ); + if ( stricmp( m_pTokenizer->CurrentToken(), "{" ) ) + m_pTokenizer->Error( "expecting {\n" ); + + while ( 1 ) + { + m_pTokenizer->GetToken( true ); + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "}" ) ) + break; + + if ( strlen( m_pTokenizer->CurrentToken() ) <= 0 ) + { + m_pTokenizer->Error( "expecting more tokens!" ); + break; + } + + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "time" ) ) + { + float start, end = 1.0f; + + m_pTokenizer->GetToken( false ); + start = (float)atof( m_pTokenizer->CurrentToken() ); + if ( m_pTokenizer->TokenAvailable() ) + { + m_pTokenizer->GetToken( false ); + end = (float)atof( m_pTokenizer->CurrentToken() ); + } + + e->SetStartTime( start ); + e->SetEndTime( end ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "ramp" ) ) + { + hadramp = true; + + m_pTokenizer->GetToken( false ); + attack = (float)atof( m_pTokenizer->CurrentToken() ); + if ( m_pTokenizer->TokenAvailable() ) + { + m_pTokenizer->GetToken( false ); + sustain = (float)atof( m_pTokenizer->CurrentToken() ); + } + if ( m_pTokenizer->TokenAvailable() ) + { + m_pTokenizer->GetToken( false ); + decay = (float)atof( m_pTokenizer->CurrentToken() ); + } + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "param" ) ) + { + m_pTokenizer->GetToken( false ); + + e->SetParameters( m_pTokenizer->CurrentToken() ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "param2" ) ) + { + m_pTokenizer->GetToken( false ); + + e->SetParameters2( m_pTokenizer->CurrentToken() ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "pitch" ) ) + { + m_pTokenizer->GetToken( false ); + e->SetPitch( atoi( m_pTokenizer->CurrentToken() ) ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "yaw" ) ) + { + m_pTokenizer->GetToken( false ); + e->SetYaw( atoi( m_pTokenizer->CurrentToken() ) ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "loopcount" ) ) + { + m_pTokenizer->GetToken( false ); + e->SetLoopCount( atoi( m_pTokenizer->CurrentToken() ) ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "resumecondition" ) ) + { + e->SetResumeCondition( true ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "fixedlength" ) ) + { + e->SetFixedLength( true ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "lockbodyfacing" ) ) + { + e->SetLockBodyFacing( true ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "distancetotarget" ) ) + { + m_pTokenizer->GetToken( false ); + e->SetDistanceToTarget( atof( m_pTokenizer->CurrentToken() ) ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "forceshortmovement" ) ) + { + e->SetForceShortMovement( true ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "synctofollowinggesture" ) ) + { + e->SetSyncToFollowingGesture( true ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "tags" ) ) + { + // Parse tags between { } + // + m_pTokenizer->GetToken( true ); + if ( stricmp( m_pTokenizer->CurrentToken(), "{" ) ) + m_pTokenizer->Error( "expecting {\n" ); + + while ( 1 ) + { + // Parse until } + m_pTokenizer->GetToken( true ); + + if ( strlen( m_pTokenizer->CurrentToken() ) <= 0 ) + { + m_pTokenizer->Error( "expecting relative tag\n" ); + break; + } + + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "}" ) ) + break; + + char tagname[ CEventRelativeTag::MAX_EVENTTAG_LENGTH ]; + float percentage; + + Q_strncpy( tagname, m_pTokenizer->CurrentToken(), sizeof( tagname ) ); + m_pTokenizer->GetToken( false ); + percentage = (float)atof( m_pTokenizer->CurrentToken() ); + + e->AddRelativeTag( tagname, percentage ); + } + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "sequenceduration" ) ) + { + float duration = 0.0f; + + m_pTokenizer->GetToken( false ); + duration = (float)atof( m_pTokenizer->CurrentToken() ); + + e->SetGestureSequenceDuration( duration ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "absolutetags" ) ) + { + m_pTokenizer->GetToken( true ); + CChoreoEvent::AbsTagType tagtype; + + tagtype = CChoreoEvent::TypeForAbsoluteTagName( m_pTokenizer->CurrentToken() ); + + if ( tagtype == (CChoreoEvent::AbsTagType) -1 ) + { + m_pTokenizer->Error( "expecting valid tag type!!!" ); + } + + // Parse tags between { } + // + m_pTokenizer->GetToken( true ); + if ( stricmp( m_pTokenizer->CurrentToken(), "{" ) ) + m_pTokenizer->Error( "expecting {\n" ); + + while ( 1 ) + { + // Parse until } + m_pTokenizer->GetToken( true ); + + if ( strlen( m_pTokenizer->CurrentToken() ) <= 0 ) + { + m_pTokenizer->Error( "expecting relative tag\n" ); + break; + } + + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "}" ) ) + break; + + char tagname[ CFlexTimingTag::MAX_EVENTTAG_LENGTH ]; + float t; + + Q_strncpy( tagname, m_pTokenizer->CurrentToken(), sizeof( tagname ) ); + m_pTokenizer->GetToken( false ); + t = (float)atof( m_pTokenizer->CurrentToken() ); + + e->AddAbsoluteTag( tagtype, tagname, t ); + } + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "flextimingtags" ) ) + { + // Parse tags between { } + // + m_pTokenizer->GetToken( true ); + if ( stricmp( m_pTokenizer->CurrentToken(), "{" ) ) + m_pTokenizer->Error( "expecting {\n" ); + + while ( 1 ) + { + // Parse until } + m_pTokenizer->GetToken( true ); + + if ( strlen( m_pTokenizer->CurrentToken() ) <= 0 ) + { + m_pTokenizer->Error( "expecting relative tag\n" ); + break; + } + + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "}" ) ) + break; + + char tagname[ CFlexTimingTag::MAX_EVENTTAG_LENGTH ]; + float percentage; + bool locked; + + Q_strncpy( tagname, m_pTokenizer->CurrentToken(), sizeof( tagname ) ); + m_pTokenizer->GetToken( false ); + percentage = (float)atof( m_pTokenizer->CurrentToken() ); + + m_pTokenizer->GetToken( false ); + locked = atoi( m_pTokenizer->CurrentToken() ) ? true : false; + + e->AddTimingTag( tagname, percentage, locked ); + } + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "relativetag" ) ) + { + char tagname[ CChoreoEvent::MAX_TAGNAME_STRING ]; + char wavname[ CChoreoEvent::MAX_TAGNAME_STRING ]; + + m_pTokenizer->GetToken( false ); + Q_strncpy( tagname, m_pTokenizer->CurrentToken(), sizeof( tagname ) ); + m_pTokenizer->GetToken( false ); + Q_strncpy( wavname, m_pTokenizer->CurrentToken(), sizeof( wavname ) ); + + e->SetUsingRelativeTag( true, tagname, wavname ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "flexanimations" ) ) + { + ParseFlexAnimations( m_pTokenizer, e ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "event_ramp" ) ) + { + ParseRamp( m_pTokenizer, e ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "cctype" ) ) + { + m_pTokenizer->GetToken( false ); + e->SetCloseCaptionType( CChoreoEvent::CCTypeForName( m_pTokenizer->CurrentToken() ) ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "cctoken" ) ) + { + m_pTokenizer->GetToken( false ); + e->SetCloseCaptionToken( m_pTokenizer->CurrentToken() ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "cc_usingcombinedfile" ) ) + { + e->SetUsingCombinedFile( true ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "cc_combinedusesgender" ) ) + { + e->SetCombinedUsingGenderToken( true ); + } + else if( !Q_stricmp( m_pTokenizer->CurrentToken(), "cc_noattenuate" ) ) + { + e->SetSuppressingCaptionAttenuation( true ); + } + } + + if ( channel ) + { + channel->AddEvent( e ); + } + + e->SetActor( actor ); + e->SetChannel( channel ); + + // It had old sytle ramp and none of the new style stuff + // Convert it + if ( hadramp && !e->GetRampCount() ) + { + // Only retrofit if something was changed by user + if ( attack != 1.0f || + sustain != 1.0f || + decay != 1.0f ) + { + float attacktime = ( 1.0f - attack ) * e->GetDuration(); + float decaytime = decay * e->GetDuration(); + float midpoint = ( attacktime + decaytime ) * 0.5f; + + e->AddRamp( attacktime, sustain, false ); + e->AddRamp( midpoint, sustain, false ); + e->AddRamp( decaytime, sustain, false ); + e->ResortRamp(); + } + } + + return e; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoActor +//----------------------------------------------------------------------------- +CChoreoActor *CChoreoScene::ParseActor( void ) +{ + CChoreoActor *a = AllocActor(); + Assert( a ); + + m_pTokenizer->GetToken( false ); + a->SetName( m_pTokenizer->CurrentToken() ); + + m_pTokenizer->GetToken( true ); + if ( stricmp( m_pTokenizer->CurrentToken(), "{" ) ) + m_pTokenizer->Error( "expecting {" ); + + // Parse channels + while ( 1 ) + { + m_pTokenizer->GetToken( true ); + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "}" ) ) + break; + + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "channel" ) ) + { + ParseChannel( a ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "faceposermodel" ) ) + { + ParseFacePoserModel( a ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "active" ) ) + { + m_pTokenizer->GetToken( true ); + a->SetActive( atoi( m_pTokenizer->CurrentToken() ) ? true : false ); + } + else + { + m_pTokenizer->Error( "expecting channel got %s\n", m_pTokenizer->CurrentToken() ); + } + } + + return a; +} + +//----------------------------------------------------------------------------- +// Output : char const +//----------------------------------------------------------------------------- +const char *CChoreoScene::GetMapname( void ) +{ + return m_szMapname; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CChoreoScene::SetMapname( const char *name ) +{ + Q_strncpy( m_szMapname, name, sizeof( m_szMapname ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::ParseMapname( void ) +{ + m_szMapname[ 0 ] = 0; + + m_pTokenizer->GetToken( true ); + Q_strncpy( m_szMapname, m_pTokenizer->CurrentToken(), sizeof( m_szMapname ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::ParseFPS( void ) +{ + m_pTokenizer->GetToken( true ); + m_nSceneFPS = atoi( m_pTokenizer->CurrentToken() ); + // Clamp to valid range + m_nSceneFPS = clamp( m_nSceneFPS, MIN_SCENE_FPS, MAX_SCENE_FPS); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::ParseSnap( void ) +{ + m_pTokenizer->GetToken( true ); + m_bUseFrameSnap = !Q_stricmp( m_pTokenizer->CurrentToken(), "on" ) ? true : false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoScene::ParseFacePoserModel( CChoreoActor *actor ) +{ + m_pTokenizer->GetToken( true ); + actor->SetFacePoserModelName( m_pTokenizer->CurrentToken() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// Output : CChoreoChannel +//----------------------------------------------------------------------------- +CChoreoChannel *CChoreoScene::ParseChannel( CChoreoActor *actor ) +{ + CChoreoChannel *c = AllocChannel(); + Assert( c ); + + m_pTokenizer->GetToken( false ); + c->SetName( m_pTokenizer->CurrentToken() ); + + m_pTokenizer->GetToken( true ); + if ( stricmp( m_pTokenizer->CurrentToken(), "{" ) ) + m_pTokenizer->Error( "expecting {" ); + + // Parse channels + while ( 1 ) + { + m_pTokenizer->GetToken( true ); + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "}" ) ) + break; + + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "event" ) ) + { + ParseEvent( actor, c ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "active" ) ) + { + m_pTokenizer->GetToken( true ); + c->SetActive( atoi( m_pTokenizer->CurrentToken() ) ? true : false ); + } + else + { + m_pTokenizer->Error( "expecting event got %s\n", m_pTokenizer->CurrentToken() ); + } + } + + Assert( actor ); + if ( actor ) + { + actor->AddChannel( c ); + c->SetActor( actor ); + } + + return c; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoScene::ParseFromBuffer( char const *filenae, ISceneTokenProcessor *tokenizer ) +{ + Q_strncpy( m_szFileName, filenae, sizeof( m_szFileName ) ); + + m_pTokenizer = tokenizer; + + while ( 1 ) + { + if ( !m_pTokenizer->GetToken( true ) ) + { + break; + } + + if ( strlen( m_pTokenizer->CurrentToken() ) <= 0 ) + break; + + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "event" ) ) + { + ParseEvent( NULL, NULL ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "actor" ) ) + { + ParseActor(); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "mapname" ) ) + { + ParseMapname(); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "fps" ) ) + { + ParseFPS(); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "snap" ) ) + { + ParseSnap(); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "scene_ramp" ) ) + { + ParseSceneRamp( m_pTokenizer, this ); + } + else if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "scalesettings" ) ) + { + ParseScaleSettings( m_pTokenizer, this ); + } + else + { + m_pTokenizer->Error( "unexpected token %s\n", m_pTokenizer->CurrentToken() ); + break; + } + } + + // Fixup time tags + ReconcileTags(); + + ReconcileGestureTimes(); + + ReconcileCloseCaption(); + + InternalDetermineEventTypes(); + + if ( CChoreoScene::s_bEditingDisabled ) + { + m_flPrecomputedStopTime = FindStopTime(); + } + + return true; +} + +void CChoreoScene::RemoveEventsExceptTypes( int* typeList, int count ) +{ + int i; + for ( i = 0 ; i < m_Actors.Count(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + int num = c->GetNumEvents(); + for ( int k = num - 1 ; k >= 0; --k ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + bool found = false; + for ( int idx = 0; idx < count; ++idx ) + { + if ( e->GetType() == ( CChoreoEvent::EVENTTYPE )typeList[ idx ] ) + { + found = true; + break; + } + } + + if ( !found ) + { + c->RemoveEvent( e ); + DeleteReferencedObjects( e ); + } + } + } + } + + // Remvoe non-matching global events, too + for ( i = m_Events.Count() - 1 ; i >= 0; --i ) + { + CChoreoEvent *e = m_Events[ i ]; + + // This was already dealt with above... + if ( e->GetActor() ) + continue; + + bool found = false; + for ( int idx = 0; idx < count; ++idx ) + { + if ( e->GetType() == ( CChoreoEvent::EVENTTYPE )typeList[ idx ] ) + { + found = true; + break; + } + } + + if ( !found ) + { + DeleteReferencedObjects( e ); + } + } +} + +void CChoreoScene::InternalDetermineEventTypes() +{ + m_bitvecHasEventOfType.ClearAll(); + + for ( int i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0 ; k < c->GetNumEvents(); k++ ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + m_bitvecHasEventOfType.Set( e->GetType(), true ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoScene::FindStopTime( void ) +{ + if ( m_flPrecomputedStopTime != 0.0f ) + { + return m_flPrecomputedStopTime; + } + + float lasttime = 0.0f; + + int c = m_Events.Count(); + for ( int i = 0; i < c ; i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + Assert( e ); + + float checktime = e->HasEndTime() ? e->GetEndTime() : e->GetStartTime(); + if ( checktime > lasttime ) + { + lasttime = checktime; + } + } + + return lasttime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fp - +// level - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoScene::FilePrintf( CUtlBuffer& buf, int level, const char *fmt, ... ) +{ + va_list argptr; + va_start( argptr, fmt ); + + while ( level-- > 0 ) + { + buf.Printf( " " ); + } + + buf.VaPrintf( fmt, argptr ); + va_end( argptr ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fp - +//----------------------------------------------------------------------------- +void CChoreoScene::FileSaveHeader( CUtlBuffer& buf ) +{ + FilePrintf( buf, 0, "// Choreo version 1\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mark - +//----------------------------------------------------------------------------- +void CChoreoScene::MarkForSaveAll( bool mark ) +{ + int i; + + // Mark global events + for ( i = 0 ; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + if ( e->GetActor() ) + continue; + + e->SetMarkedForSave( mark ); + } + + // Recursively mark everything else + for ( i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + a->MarkForSaveAll( mark ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoScene::ExportMarkedToFile( const char *filename ) +{ + // Create a serialization buffer + CUtlBuffer buf( 0, 0, true ); + FileSaveHeader( buf ); + + // Look for events that don't have actor/channel set + int i; + for ( i = 0 ; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + if ( e->GetActor() ) + continue; + + FileSaveEvent( buf, 0, e ); + } + + for ( i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + FileSaveActor( buf, 0, a ); + } + + // Write it out baby + FileHandle_t fh = SceneFileSystem()->Open( filename, "wt" ); + if (fh) + { + SceneFileSystem()->Write( buf.Base(), buf.TellPut(), fh ); + SceneFileSystem()->Close(fh); + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +bool CChoreoScene::SaveToFile( const char *filename ) +{ + // Create a serialization buffer + CUtlBuffer buf( 0, 0, true ); + FileSaveHeader( buf ); + + MarkForSaveAll( true ); + + // Look for events that don't have actor/channel set + int i; + for ( i = 0 ; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + if ( e->GetActor() ) + continue; + + FileSaveEvent( buf, 0, e ); + } + + for ( i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + FileSaveActor( buf, 0, a ); + } + + if ( m_szMapname[ 0 ] ) + { + FilePrintf( buf, 0, "mapname \"%s\"\n", m_szMapname ); + } + + FileSaveSceneRamp( buf, 0, this ); + FileSaveScaleSettings( buf, 0, this ); + + FilePrintf( buf, 0, "fps %i\n", m_nSceneFPS ); + FilePrintf( buf, 0, "snap %s\n", m_bUseFrameSnap ? "on" : "off" ); + + // Write it out baby + FileHandle_t fh = SceneFileSystem()->Open( filename, "wt" ); + if (fh) + { + SceneFileSystem()->Write( buf.Base(), buf.TellPut(), fh ); + SceneFileSystem()->Close(fh); + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +// level - +// *e - +//----------------------------------------------------------------------------- +void CChoreoScene::FileSaveRamp( CUtlBuffer& buf, int level, CChoreoEvent *e ) +{ + // Nothing to save? + int c = e->GetRampCount(); + if ( c <= 0 && + !e->RampIsEdgeActive( true ) && + !e->RampIsEdgeActive( false ) ) + return; + + char line[ 1024 ]; + Q_strncpy( line, "event_ramp ", sizeof( line ) ); + + if ( e->RampIsEdgeActive( true ) || e->RampIsEdgeActive( false ) ) + { + if ( e->RampIsEdgeActive( true ) ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "leftedge %s %.3f ", Interpolator_NameForCurveType( e->RampGetEdgeCurveType( true ), false ), e->RampGetEdgeZeroValue( true ) ); + Q_strncat( line, sz, sizeof( line ), COPY_ALL_CHARACTERS ); + } + if ( e->RampIsEdgeActive( false ) ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "rightedge %s %.3f", Interpolator_NameForCurveType( e->RampGetEdgeCurveType( false ), false ), e->RampGetEdgeZeroValue( false ) ); + Q_strncat( line, sz, sizeof( line ), COPY_ALL_CHARACTERS ); + } + } + + FilePrintf( buf, level, "%s\n", line ); + FilePrintf( buf, level, "{\n" ); + + for ( int i = 0; i < c; i++ ) + { + CExpressionSample *sample = e->GetRamp( i ); + if ( sample->GetCurveType() != CURVE_DEFAULT ) + { + FilePrintf( buf, level + 1, "%.4f %.4f \"%s\"\n", + sample->time, + sample->value, + Interpolator_NameForCurveType( sample->GetCurveType(), false ) ); + } + else + { + FilePrintf( buf, level + 1, "%.4f %.4f\n", + sample->time, + sample->value ); + } + } + + FilePrintf( buf, level, "}\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +// level - +// *e - +//----------------------------------------------------------------------------- +void CChoreoScene::FileSaveSceneRamp( CUtlBuffer& buf, int level, CChoreoScene *scene ) +{ + // Nothing to save? + int c = scene->GetSceneRampCount(); + if ( c <= 0 && + !scene->SceneRampIsEdgeActive( true ) && + !scene->SceneRampIsEdgeActive( false ) ) + return; + + char line[ 1024 ]; + Q_strncpy( line, "scene_ramp ", sizeof( line ) ); + + if ( scene->SceneRampIsEdgeActive( true ) || scene->SceneRampIsEdgeActive( false ) ) + { + if ( scene->SceneRampIsEdgeActive( true ) ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "leftedge %s %.3f ", Interpolator_NameForCurveType( scene->SceneRampGetEdgeCurveType( true ), false ), scene->SceneRampGetEdgeZeroValue( true ) ); + Q_strncat( line, sz, sizeof( line ), COPY_ALL_CHARACTERS ); + } + if ( scene->SceneRampIsEdgeActive( false ) ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ),"rightedge %s %.3f", Interpolator_NameForCurveType( scene->SceneRampGetEdgeCurveType( false ), false ), scene->SceneRampGetEdgeZeroValue( false ) ); + Q_strncat( line, sz, sizeof( line ), COPY_ALL_CHARACTERS ); + } + } + + FilePrintf( buf, level, "%s\n", line ); + FilePrintf( buf, level, "{\n" ); + + for ( int i = 0; i < c; i++ ) + { + CExpressionSample *sample = scene->GetSceneRamp( i ); + if ( sample->GetCurveType() != CURVE_DEFAULT ) + { + FilePrintf( buf, level + 1, "%.4f %.4f \"%s\"\n", + sample->time, + sample->value, + Interpolator_NameForCurveType( sample->GetCurveType(), false ) ); + } + else + { + FilePrintf( buf, level + 1, "%.4f %.4f\n", + sample->time, + sample->value ); + } + } + + FilePrintf( buf, level, "}\n" ); +} + +void CChoreoScene::FileSaveScaleSettings( CUtlBuffer& buf, int level, CChoreoScene *scene ) +{ + // Nothing to save? + int c = scene->m_TimeZoomLookup.Count(); + if ( c <= 0 ) + return; + + FilePrintf( buf, level, "scalesettings\n" ); + FilePrintf( buf, level, "{\n" ); + + for ( int i = 0; i < c; i++ ) + { + int value = scene->m_TimeZoomLookup[ i ]; + + FilePrintf( buf, level + 1, "\"%s\" \"%i\"\n", + scene->m_TimeZoomLookup.GetElementName( i ), + value ); + } + + FilePrintf( buf, level, "}\n" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +// level - +// *track - +//----------------------------------------------------------------------------- +void CChoreoScene::FileSaveFlexAnimationTrack( CUtlBuffer& buf, int level, CFlexAnimationTrack *track ) +{ + if ( !track ) + return; + + if ( !track->IsTrackActive() && track->GetNumSamples() <= 0 ) + return; + + char line[ 1024 ]; + Q_snprintf( line, sizeof( line ), "\"%s\" ", track->GetFlexControllerName() ); + if ( !track->IsTrackActive() ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "disabled " ); + Q_strncat( line, sz, sizeof( line ), COPY_ALL_CHARACTERS ); + } + if ( track->IsComboType() ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "combo " ); + Q_strncat( line, sz, sizeof( line ), COPY_ALL_CHARACTERS ); + } + if ( track->GetMin() != 0.0f || track->GetMax() != 1.0f) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "range %.1f %.1f ", track->GetMin(), track->GetMax() ); + Q_strncat( line, sz, sizeof( line ), COPY_ALL_CHARACTERS ); + } + if ( track->IsEdgeActive( true ) || track->IsEdgeActive( false ) ) + { + char edgestr[ 512 ]; + edgestr[ 0 ] = 0; + + if ( track->IsEdgeActive( true ) ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "leftedge %s %.3f ", Interpolator_NameForCurveType( track->GetEdgeCurveType( true ), false ), track->GetEdgeZeroValue( true ) ); + Q_strncat( edgestr, sz, sizeof( edgestr ), COPY_ALL_CHARACTERS ); + } + if ( track->IsEdgeActive( false ) ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "rightedge %s %.3f ", Interpolator_NameForCurveType( track->GetEdgeCurveType( false ), false ), track->GetEdgeZeroValue( false ) ); + Q_strncat( edgestr, sz, sizeof( edgestr ), COPY_ALL_CHARACTERS ); + } + + Q_strncat( line, edgestr, sizeof( line ), COPY_ALL_CHARACTERS ); + } + + + FilePrintf( buf, level + 2, "%s\n", line ); + + // Write out samples + FilePrintf( buf, level + 2, "{\n" ); + + for ( int j = 0 ; j < track->GetNumSamples( 0 ) ; j++ ) + { + CExpressionSample *s = track->GetSample( j, 0 ); + if ( !s ) + continue; + + if ( s->GetCurveType() != CURVE_DEFAULT ) + { + FilePrintf( buf, level + 3, "%.4f %.4f \"%s\"\n", + s->time, + s->value, + Interpolator_NameForCurveType( s->GetCurveType(), false ) ); + } + else + { + FilePrintf( buf, level + 3, "%.4f %.4f\n", + s->time, + s->value ); + } + } + + FilePrintf( buf, level + 2, "}\n" ); + + // Write out combo samples + if ( track->IsComboType() ) + { + FilePrintf( buf, level + 2, "{\n" ); + + for ( int j = 0 ; j < track->GetNumSamples( 1) ; j++ ) + { + CExpressionSample *s = track->GetSample( j, 1 ); + if ( !s ) + continue; + + if ( s->GetCurveType() != CURVE_DEFAULT ) + { + FilePrintf( buf, level + 3, "%.4f %.4f \"%s\"\n", + s->time, + s->value, + Interpolator_NameForCurveType( s->GetCurveType(), false ) ); + } + else + { + FilePrintf( buf, level + 3, "%.4f %.4f\n", + s->time, + s->value ); + } + } + + FilePrintf( buf, level + 2, "}\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +// level - +// *e - +//----------------------------------------------------------------------------- +void CChoreoScene::FileSaveFlexAnimations( CUtlBuffer& buf, int level, CChoreoEvent *e ) +{ + // Nothing to save + if ( e->GetNumFlexAnimationTracks() <= 0 ) + return; + + FilePrintf( buf, level + 1, "flexanimations samples_use_time\n" ); + FilePrintf( buf, level + 1, "{\n" ); + + for ( int i = 0; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + FileSaveFlexAnimationTrack( buf, level, track ); + } + + FilePrintf( buf, level + 1, "}\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fp - +// level - +// *e - +//----------------------------------------------------------------------------- +void CChoreoScene::FileSaveEvent( CUtlBuffer& buf, int level, CChoreoEvent *e ) +{ + if ( !e->IsMarkedForSave() ) + return; + + FilePrintf( buf, level, "event %s \"%s\"\n", CChoreoEvent::NameForType( e->GetType() ), e->GetName() ); + FilePrintf( buf, level, "{\n" ); + + float st, et; + st = e->GetStartTime(); + et = e->GetEndTime(); + + FilePrintf( buf, level + 1, "time %f %f\n", st, et ); + FilePrintf( buf, level + 1, "param \"%s\"\n", e->GetParameters() ); + if ( strlen( e->GetParameters2() ) > 0 ) + { + FilePrintf( buf, level + 1, "param2 \"%s\"\n", e->GetParameters2() ); + } + if ( e->GetRampCount() > 0 ) + { + FileSaveRamp( buf, level + 1, e ); + } + if ( e->GetPitch() != 0 ) + { + FilePrintf( buf, level + 1, "pitch \"%i\"\n", e->GetPitch() ); + } + if ( e->GetYaw() != 0 ) + { + FilePrintf( buf, level + 1, "yaw \"%i\"\n", e->GetYaw() ); + } + if ( e->IsResumeCondition() ) + { + FilePrintf( buf, level + 1, "resumecondition\n" ); + } + if ( e->IsLockBodyFacing() ) + { + FilePrintf( buf, level + 1, "lockbodyfacing\n" ); + } + if ( e->GetDistanceToTarget() > 0.0f ) + { + FilePrintf( buf, level + 1, "distancetotarget %.2f\n", e->GetDistanceToTarget() ); + } + if ( e->GetForceShortMovement() ) + { + FilePrintf( buf, level + 1, "forceshortmovement\n" ); + } + if ( e->GetSyncToFollowingGesture() ) + { + FilePrintf( buf, level + 1, "synctofollowinggesture\n" ); + } + if ( e->IsFixedLength() ) + { + FilePrintf( buf, level + 1, "fixedlength\n" ); + } + if ( e->GetNumRelativeTags() > 0 ) + { + FilePrintf( buf, level + 1, "tags\n" ); + FilePrintf( buf, level + 1, "{\n" ); + for ( int t = 0; t < e->GetNumRelativeTags(); t++ ) + { + CEventRelativeTag *rt = e->GetRelativeTag( t ); + Assert( rt ); + FilePrintf( buf, level + 2, "\"%s\" %f\n", rt->GetName(), rt->GetPercentage() ); + } + FilePrintf( buf, level + 1, "}\n" ); + } + if ( e->GetNumTimingTags() > 0 ) + { + FilePrintf( buf, level + 1, "flextimingtags\n" ); + FilePrintf( buf, level + 1, "{\n" ); + for ( int t = 0; t < e->GetNumTimingTags(); t++ ) + { + CFlexTimingTag *tt = e->GetTimingTag( t ); + Assert( tt ); + FilePrintf( buf, level + 2, "\"%s\" %f %i\n", tt->GetName(), tt->GetPercentage(), tt->GetLocked() ? 1 : 0 ); + } + FilePrintf( buf, level + 1, "}\n" ); + } + int tagtype; + for ( tagtype = 0; tagtype < CChoreoEvent::NUM_ABS_TAG_TYPES; tagtype++ ) + { + if ( e->GetNumAbsoluteTags( (CChoreoEvent::AbsTagType)tagtype ) > 0 ) + { + FilePrintf( buf, level + 1, "absolutetags %s\n", CChoreoEvent::NameForAbsoluteTagType( (CChoreoEvent::AbsTagType)tagtype ) ); + FilePrintf( buf, level + 1, "{\n" ); + for ( int t = 0; t < e->GetNumAbsoluteTags( (CChoreoEvent::AbsTagType)tagtype ); t++ ) + { + CEventAbsoluteTag *abstag = e->GetAbsoluteTag( (CChoreoEvent::AbsTagType)tagtype, t ); + Assert( abstag ); + FilePrintf( buf, level + 2, "\"%s\" %f\n", abstag->GetName(), abstag->GetPercentage() ); + } + FilePrintf( buf, level + 1, "}\n" ); + } + } + + if ( e->GetType() == CChoreoEvent::GESTURE ) + { + float duration; + if ( e->GetGestureSequenceDuration( duration ) ) + { + FilePrintf( buf, level + 1, "sequenceduration %f\n", duration ); + } + } + + if ( e->IsUsingRelativeTag() ) + { + FilePrintf( buf, level + 1, "relativetag \"%s\" \"%s\"\n", + e->GetRelativeTagName(), e->GetRelativeWavName() ); + } + + if ( e->GetNumFlexAnimationTracks() > 0 ) + { + FileSaveFlexAnimations( buf, level, e ); + } + + if ( e->GetType() == CChoreoEvent::LOOP ) + { + FilePrintf( buf, level + 1, "loopcount \"%i\"\n", e->GetLoopCount() ); + } + + if ( e->GetType() == CChoreoEvent::SPEAK ) + { + FilePrintf( buf, level + 1, "cctype \"%s\"\n", CChoreoEvent::NameForCCType( e->GetCloseCaptionType() ) ); + FilePrintf( buf, level + 1, "cctoken \"%s\"\n", e->GetCloseCaptionToken() ); + if ( e->GetCloseCaptionType() != CChoreoEvent::CC_DISABLED && + e->IsUsingCombinedFile() ) + { + FilePrintf( buf, level + 1, "cc_usingcombinedfile\n" ); + } + if ( e->IsCombinedUsingGenderToken() ) + { + FilePrintf( buf, level + 1, "cc_combinedusesgender\n" ); + } + if ( e->IsSuppressingCaptionAttenuation() ) + { + FilePrintf( buf, level + 1, "cc_noattenuate\n" ); + } + } + + FilePrintf( buf, level, "}\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fp - +// level - +// *c - +//----------------------------------------------------------------------------- +void CChoreoScene::FileSaveChannel( CUtlBuffer& buf, int level, CChoreoChannel *c ) +{ + if ( !c->IsMarkedForSave() ) + return; + + FilePrintf( buf, level, "channel \"%s\"\n", c->GetName() ); + FilePrintf( buf, level, "{\n" ); + + for ( int i = 0; i < c->GetNumEvents(); i++ ) + { + CChoreoEvent *e = c->GetEvent( i ); + if ( e ) + { + FileSaveEvent( buf, level + 1, e ); + } + } + + if ( !c->GetActive() ) + { + // Only write out inactive + FilePrintf( buf, level + 1, "active \"0\"\n" ); + } + + FilePrintf( buf, level, "}\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fp - +// level - +// *a - +//----------------------------------------------------------------------------- +void CChoreoScene::FileSaveActor( CUtlBuffer& buf, int level, CChoreoActor *a ) +{ + if ( !a->IsMarkedForSave() ) + return; + + FilePrintf( buf, level, "actor \"%s\"\n", a->GetName() ); + FilePrintf( buf, level, "{\n" ); + + for ( int i = 0; i < a->GetNumChannels(); i++ ) + { + CChoreoChannel *c = a->GetChannel( i ); + if ( c ) + { + FileSaveChannel( buf, level + 1, c ); + } + } + + if ( Q_strlen( a->GetFacePoserModelName() ) > 0 ) + { + FilePrintf( buf, level + 1, "faceposermodel \"%s\"\n", a->GetFacePoserModelName() ); + } + + if ( !a->GetActive() ) + { + // Only write out inactive + FilePrintf( buf, level + 1, "active \"0\"\n" ); + } + + FilePrintf( buf, level, "}\n\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoScene::FindAdjustedStartTime( void ) +{ + float earliest_time = 0.0f; + + CChoreoEvent *e; + + for ( int i = 0; i < m_Events.Size(); i++ ) + { + e = m_Events[ i ]; + + float starttime = e->GetStartTime(); + + // If it's a wav file, pre-queue the starting time by the sound system's + // current latency + if ( e->GetType() == CChoreoEvent::SPEAK ) + { + starttime -= m_flSoundSystemLatency; + } + + if ( starttime < earliest_time ) + { + earliest_time = starttime; + } + } + + return earliest_time; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoScene::FindAdjustedEndTime( void ) +{ + float latest_time = 0.0f; + + CChoreoEvent *e; + + for ( int i = 0; i < m_Events.Size(); i++ ) + { + e = m_Events[ i ]; + + float endtime = e->GetStartTime(); + if ( e->HasEndTime() ) + { + endtime = e->GetEndTime(); + } + + // If it's a wav file, pre-queue the starting time by the sound system's + // current latency + if ( e->GetType() == CChoreoEvent::SPEAK ) + { + endtime += m_flSoundSystemLatency; + } + + if ( endtime > latest_time ) + { + latest_time = endtime; + } + } + + return latest_time; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::ResetSimulation( bool forward /*= true*/, float starttime /*= 0.0f*/, float endtime /*= 0.0f*/ ) +{ + CChoreoEvent *e; + + m_ActiveResumeConditions.RemoveAll(); + m_ResumeConditions.RemoveAll(); + m_PauseEvents.RemoveAll(); + + // Put all items into the pending queue + for ( int i = 0; i < m_Events.Size(); i++ ) + { + e = m_Events[ i ]; + e->ResetProcessing(); + + if ( e->GetType() == CChoreoEvent::SECTION ) + { + m_PauseEvents.AddToTail( e ); + continue; + } + + if ( e->IsResumeCondition() ) + { + m_ResumeConditions.AddToTail( e ); + continue; + } + } + + // Find earliest adjusted start time + m_flEarliestTime = FindAdjustedStartTime(); + m_flLatestTime = FindAdjustedEndTime(); + + m_flCurrentTime = forward ? m_flEarliestTime : m_flLatestTime; + + // choreoprintf( 0, "Start time %f\n", m_flCurrentTime ); + + m_flLastActiveTime = 0.0f; + m_nActiveEvents = m_Events.Size(); + + m_flStartTime = starttime; + m_flEndTime = endtime; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CChoreoScene::CheckEventCompletion( void ) +{ + CChoreoEvent *e; + + bool bAllCompleted = true; + // check all items in the active pending queue + for ( int i = 0; i < m_ActiveResumeConditions.Size(); i++ ) + { + e = m_ActiveResumeConditions[ i ]; + + bAllCompleted = bAllCompleted && e->CheckProcessing( m_pIChoreoEventCallback, this, m_flCurrentTime ); + } + return bAllCompleted; +} + + + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoScene::SimulationFinished( void ) +{ + // Scene's linger for a little bit to allow things to settle + // check for events that are still active... + + if ( m_flCurrentTime > m_flLatestTime ) + { + if ( m_nActiveEvents != 0 ) + { + return false; + } + + return true; + } + if ( m_flCurrentTime < m_flEarliestTime ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoScene::FindPauseBetweenTimes( float starttime, float endtime ) +{ + CChoreoEvent *e; + + // Iterate through all events in the scene + for ( int i = 0; i < m_PauseEvents.Size(); i++ ) + { + e = m_PauseEvents[ i ]; + if ( !e ) + continue; + + Assert( e->GetType() == CChoreoEvent::SECTION ); + + int time_is = IsTimeInRange( e->GetStartTime(), starttime, endtime ); + if ( IN_RANGE != time_is ) + continue; + + // Found a pause in between start and end time + return e; + } + + // No pause inside the specified time span + return NULL; +} + +int CChoreoScene::IsTimeInRange( float t, float starttime, float endtime ) +{ + if ( t > endtime ) + { + return AFTER_RANGE; + } + else if ( t < starttime ) + { + return BEFORE_RANGE; + } + + return IN_RANGE; +} + +int CChoreoScene::EventThink( CChoreoEvent *e, float frame_start_time, float frame_end_time, bool playing_forward, PROCESSING_TYPE& disposition ) +{ + disposition = PROCESSING_TYPE_IGNORE; + int iret = 0; + + bool hasend = e->HasEndTime(); + float starttime, endtime; + + starttime = e->GetStartTime(); + endtime = hasend ? e->GetEndTime() : e->GetStartTime(); + + if ( !playing_forward ) + { + // Swap intervals + float temp = frame_start_time; + frame_start_time = frame_end_time; + frame_end_time = temp; + } + + bool suppressed = false; + + // Special processing + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + // If it's a wav file, pre-queue the starting/endtime time by the sound system's + // current latency + { + if ( playing_forward ) + { + starttime -= m_flSoundSystemLatency; + + + // Search for pause condition in between the original time and the + // adjusted start time, but make sure that the pause event hasn't already triggered... + CChoreoEvent *pauseEvent = FindPauseBetweenTimes( starttime, starttime + m_flSoundSystemLatency ); + if ( pauseEvent && + ( frame_start_time <= pauseEvent->GetStartTime() ) ) + { + pauseEvent->AddEventDependency( e ); + + suppressed = true; + } + } + /* + else + // Don't bother if playing backward!!! + { + endtime += m_flSoundSystemLatency; + + // Search for pause condition in between the original time and the + // adjusted start time + CChoreoEvent *pauseEvent = FindPauseBetweenTimes( endtime - m_flSoundSystemLatency, endtime ); + if ( pauseEvent ) + { + pauseEvent->AddEventDependency( e ); + + suppressed = true; + } + } + */ + } + break; + case CChoreoEvent::SUBSCENE: + { + if ( IsSubScene() ) + { + suppressed = true; + } + } + break; + } + + if ( suppressed ) + { + if ( e->IsProcessing() ) + { + disposition = PROCESSING_TYPE_STOP; + } + return iret; + } + + int where_is_event; + + if ( e->IsProcessing() ) + { + where_is_event = IsTimeInRange( frame_start_time, starttime, endtime ); + if ( IN_RANGE == where_is_event ) + { + disposition = PROCESSING_TYPE_CONTINUE; + iret = 1; + } + else + { + disposition = PROCESSING_TYPE_STOP; + } + } + else + { + + // Is the event supposed to be active at this time + where_is_event = IsTimeInRange( frame_start_time, starttime, endtime ); + + if ( IN_RANGE == where_is_event ) + { + if ( e->IsResumeCondition() ) + { + disposition = PROCESSING_TYPE_START_RESUMECONDITION; + } + else + { + disposition = PROCESSING_TYPE_START; + } + iret = 1; + } + // See if it's a single fire event which should occur during this frame + else if ( !hasend ) + { + where_is_event = IsTimeInRange( starttime, frame_start_time, frame_end_time ); + if ( IN_RANGE == where_is_event ) + { + disposition = PROCESSING_TYPE_START; + iret = 1; + } + + } + } + + return iret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &e0 - +// &e1 - +// Output : static bool +//----------------------------------------------------------------------------- +bool CChoreoScene::EventLess( const CChoreoScene::ActiveList &al0, const CChoreoScene::ActiveList &al1 ) +{ + CChoreoEvent *event0, *event1; + event0 = const_cast< CChoreoEvent * >( al0.e ); + event1 = const_cast< CChoreoEvent * >( al1.e ); + + if ( event0->GetStartTime() < event1->GetStartTime() ) + { + return true; + } + + if ( event0->GetStartTime() > event1->GetStartTime() ) + { + return false; + } + + // Check for end time overlap + if ( event0->HasEndTime() && event1->HasEndTime() ) + { + if ( event0->GetEndTime() > event1->GetEndTime() ) + return true; + else if ( event0->GetEndTime() < event1->GetEndTime() ) + return false; + } + + CChoreoActor *a0, *a1; + a0 = event0->GetActor(); + a1 = event1->GetActor(); + + // Start time equal, go to order in channel + if ( !a0 || !a1 || a0 != a1 ) + { + return strcmp( event0->GetName(), event1->GetName() ) == -1; + } + + CChoreoChannel *c0 = event0->GetChannel(); + CChoreoChannel *c1 = event1->GetChannel(); + + if ( !c0 || !c1 || c0 != c1 ) + { + return strcmp( event0->GetName(), event1->GetName() ) == -1; + } + + // Go by slot within channel + int index0 = a0->FindChannelIndex( c0 ); + int index1 = a1->FindChannelIndex( c1 ); + + return ( index0 < index1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::ClearPauseEventDependencies() +{ + int c = m_PauseEvents.Count(); + for ( int i = 0 ; i < c; ++i ) + { + CChoreoEvent *pause = m_PauseEvents[ i ]; + Assert( pause ); + pause->ClearEventDependencies(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pauseEvent - +// *suppressed - +//----------------------------------------------------------------------------- +void CChoreoScene::AddPauseEventDependency( CChoreoEvent *pauseEvent, CChoreoEvent *suppressed ) +{ + Assert( pauseEvent ); + Assert( pauseEvent != suppressed ); + pauseEvent->AddEventDependency( suppressed ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void CChoreoScene::Think( float curtime ) +{ + CChoreoEvent *e; + + float oldt = m_flCurrentTime; + float dt = curtime - oldt; + + bool playing_forward = ( dt >= 0.0f ) ? true : false; + + m_nActiveEvents = 0; + + ClearPauseEventDependencies(); + + CUtlRBTree< ActiveList, int > pending(0,0,EventLess); + + // Iterate through all events in the scene + int i; + for ( i = 0; i < m_Events.Size(); i++ ) + { + e = m_Events[ i ]; + if ( !e ) + continue; + + PROCESSING_TYPE disposition; + m_nActiveEvents += EventThink( e, m_flCurrentTime, curtime, playing_forward, disposition ); + + if ( disposition != PROCESSING_TYPE_IGNORE ) + { + + ActiveList entry; + + entry.e = e; + entry.pt = disposition; + + pending.Insert( entry ); + } + } + + // Events are sorted start time and then by channel and actor slot or by name if those aren't equal + bool dump = false; + + i = pending.FirstInorder(); + while ( i != pending.InvalidIndex() ) + { + ActiveList *entry = &pending[ i ]; + + Assert( entry->e ); + + if ( dump ) + { + Msg( "%f == %s starting at %f (actor %p channel %p)\n", + m_flCurrentTime, entry->e->GetName(), entry->e->GetStartTime(), + entry->e->GetActor(), entry->e->GetChannel() ); + } + + switch ( entry->pt ) + { + default: + case PROCESSING_TYPE_IGNORE: + { + Assert( 0 ); + } + break; + case PROCESSING_TYPE_START: + case PROCESSING_TYPE_START_RESUMECONDITION: + { + entry->e->StartProcessing( m_pIChoreoEventCallback, this, m_flCurrentTime ); + + if ( entry->pt == PROCESSING_TYPE_START_RESUMECONDITION ) + { + Assert( entry->e->IsResumeCondition() ); + m_ActiveResumeConditions.AddToTail( entry->e ); + } + + // This event can "pause" the scene, so we need to remember who "paused" the scene so that + // when we resume we can resume any suppressed events dependent on this pauser... + if ( entry->e->GetType() == CChoreoEvent::SECTION ) + { + // So this event should be in the pauseevents list, otherwise this'll be -1 + m_nLastPauseEvent = m_PauseEvents.Find( entry->e ); + } + } + break; + case PROCESSING_TYPE_CONTINUE: + { + entry->e->ContinueProcessing( m_pIChoreoEventCallback, this, m_flCurrentTime ); + } + break; + case PROCESSING_TYPE_STOP: + { + entry->e->StopProcessing( m_pIChoreoEventCallback, this, m_flCurrentTime ); + } + break; + } + + i = pending.NextInorder( i ); + } + + if ( dump ) + { + Msg( "\n" ); + } + + // If a Process call slams this time, don't override it!!! + if ( oldt == m_flCurrentTime ) + { + m_flCurrentTime = curtime; + } + + // Still processing? + if ( m_nActiveEvents ) + { + m_flLastActiveTime = m_flCurrentTime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoScene::GetTime( void ) +{ + return m_flCurrentTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +//----------------------------------------------------------------------------- +void CChoreoScene::SetTime( float t ) +{ + m_flCurrentTime = t; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +//----------------------------------------------------------------------------- +void CChoreoScene::LoopToTime( float t ) +{ + m_flCurrentTime = t; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pfn - +//----------------------------------------------------------------------------- +void CChoreoScene::SetPrintFunc( void ( *pfn ) ( const char *fmt, ... ) ) +{ + m_pfnPrint = pfn; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoScene::RemoveActor( CChoreoActor *actor ) +{ + int idx = FindActorIndex( actor ); + if ( idx == -1 ) + return; + + m_Actors.Remove( idx ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoScene::FindActorIndex( CChoreoActor *actor ) +{ + for ( int i = 0; i < m_Actors.Size(); i++ ) + { + if ( actor == m_Actors[ i ] ) + { + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : a1 - +// a2 - +//----------------------------------------------------------------------------- +void CChoreoScene::SwapActors( int a1, int a2 ) +{ + CChoreoActor *temp; + + temp = m_Actors[ a1 ]; + m_Actors[ a1 ] = m_Actors[ a2 ]; + m_Actors[ a2 ] = temp; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoScene::DeleteReferencedObjects( CChoreoActor *actor ) +{ + for ( int i = 0; i < actor->GetNumChannels(); i++ ) + { + CChoreoChannel *channel = actor->GetChannel( i ); + actor->RemoveChannel( channel ); + + DeleteReferencedObjects( channel ); + } + + DestroyActor( actor ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoScene::DeleteReferencedObjects( CChoreoChannel *channel ) +{ + for ( int i = 0; i < channel->GetNumEvents(); i++ ) + { + CChoreoEvent *event = channel->GetEvent( i ); + channel->RemoveEvent( event ); + + DeleteReferencedObjects( event ); + } + + DestroyChannel( channel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoScene::DeleteReferencedObjects( CChoreoEvent *event ) +{ + int idx = m_PauseEvents.Find( event ); + if ( idx != m_PauseEvents.InvalidIndex() ) + { + m_PauseEvents.Remove( idx ); + } + // Events don't reference anything lower + DestroyEvent( event ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoScene::DestroyActor( CChoreoActor *actor ) +{ + int size = m_Actors.Size(); + for ( int i = size - 1; i >= 0; i-- ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( a == actor ) + { + m_Actors.Remove( i ); + } + } + + delete actor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoScene::DestroyChannel( CChoreoChannel *channel ) +{ + int size = m_Channels.Size(); + for ( int i = size - 1; i >= 0; i-- ) + { + CChoreoChannel *c = m_Channels[ i ]; + if ( c == channel ) + { + m_Channels.Remove( i ); + } + } + + delete channel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoScene::DestroyEvent( CChoreoEvent *event ) +{ + int size = m_Events.Size(); + for ( int i = size - 1; i >= 0; i-- ) + { + CChoreoEvent *e = m_Events[ i ]; + if ( e == event ) + { + m_Events.Remove( i ); + } + } + + delete event; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::ResumeSimulation( void ) +{ + // If the thing that paused us was a SECTION pause event, then this will be set + if ( m_nLastPauseEvent >= 0 && + m_nLastPauseEvent < m_PauseEvents.Count() ) + { + // Start any suppressed dependencies immediately, should only be .wav files!!! + // These are .wav files which are placed at or just after the SECTION pause event + // in the .vcd, but due to the user's sound system latency, they would have triggered before the + // pause (we pre-queue sounds). Since we suppressed that, we need to unsupress / start these sounds + // now that the SECTION pause is being resumed from + CUtlVector< CChoreoEvent * > deps; + CChoreoEvent *pauseEvent = m_PauseEvents[ m_nLastPauseEvent ]; + Assert( pauseEvent ); + + // Sanity check ( this should be about 1 tick usually 15 msec) + float timeSincePaused = m_flCurrentTime - pauseEvent->GetStartTime(); + if ( fabs( timeSincePaused ) > 1.0f ) + { + Assert( !"Resume simulation with unexpected pause event" ); + } + + // Snag any sounds which were suppressed by this issue + pauseEvent->GetEventDependencies( deps ); + for ( int j = 0; j < deps.Count(); ++j ) + { + CChoreoEvent *startEvent = deps[ j ]; + Assert( startEvent ); + // Start them now. Yes, they won't pre-queue, but it's better than totally skipping the sound!!! + startEvent->StartProcessing( m_pIChoreoEventCallback, this, m_flCurrentTime ); + } + } + + // Reset section pause signal + m_nLastPauseEvent = -1; + + m_ActiveResumeConditions.RemoveAll(); +} + +// Sound system needs to have sounds pre-queued by this much time +void CChoreoScene::SetSoundFileStartupLatency( float time ) +{ + Assert( time >= 0 ); + m_flSoundSystemLatency = time; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +//----------------------------------------------------------------------------- +void CChoreoScene::GetSceneTimes( float& start, float& end ) +{ + start = m_flStartTime; + end = m_flEndTime; +} + +//----------------------------------------------------------------------------- +// Purpose: Do housekeeping on times that are relative to tags +//----------------------------------------------------------------------------- +void CChoreoScene::ReconcileTags( void ) +{ + for ( int i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0 ; k < c->GetNumEvents(); k++ ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( !e->IsUsingRelativeTag() ) + continue; + + CEventRelativeTag *tag = FindTagByName( + e->GetRelativeWavName(), + e->GetRelativeTagName() ); + + if ( tag ) + { + // Determine correct starting time based on tag + float starttime = tag->GetStartTime(); + + // Figure out delta + float dt = starttime - e->GetStartTime(); + + // Fix up start and possible end time + e->OffsetTime( dt ); + } + else + { + // The tag was missing!!! unflag it + choreoprintf( 0, "Event %s was missing tag %s for wav %s\n", + e->GetName(), e->GetRelativeWavName(), e->GetRelativeTagName() ); + + e->SetUsingRelativeTag( false, "", "" ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *wavname - +// *name - +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoScene::FindTargetingEvent( const char *wavname, const char *name ) +{ + for ( int i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0 ; k < c->GetNumEvents(); k++ ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( !e->IsUsingRelativeTag() ) + continue; + + if ( stricmp( wavname, e->GetRelativeWavName() ) ) + continue; + + if ( stricmp( name, e->GetRelativeTagName() ) ) + continue; + + return e; + } + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *wavname - +// *name - +// Output : CEventRelativeTag +//----------------------------------------------------------------------------- +CEventRelativeTag *CChoreoScene::FindTagByName( const char *wavname, const char *name ) +{ + for ( int i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0 ; k < c->GetNumEvents(); k++ ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( e->GetType() != CChoreoEvent::SPEAK ) + continue; + + // Search for tag by name + if ( !strstr( e->GetParameters(), wavname ) ) + continue; + + CEventRelativeTag *tag = e->FindRelativeTag( name ); + if ( !tag ) + continue; + + return tag; + } + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void CChoreoScene::ExportEvents( const char *filename, CUtlVector< CChoreoEvent * >& events ) +{ + if ( events.Size() <= 0 ) + return; + + // Create a serialization buffer + CUtlBuffer buf( 0, 0, true ); + FilePrintf( buf, 0, "// Choreo version 1: <%i> Exported Events\n", events.Size() ); + + // Save out the selected events. + int i; + for ( i = 0 ; i < events.Size(); i++ ) + { + CChoreoEvent *e = events[ i ]; + if ( !e->GetActor() ) + continue; + + FileSaveEvent( buf, 0, e ); + } + + // Write it out baby + FileHandle_t fh = SceneFileSystem()->Open( filename, "wt" ); + if (fh) + { + SceneFileSystem()->Write( buf.Base(), buf.TellPut(), fh ); + SceneFileSystem()->Close(fh); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *channel - +// starttime - +//----------------------------------------------------------------------------- +void CChoreoScene::ImportEvents( ISceneTokenProcessor *tokenizer, CChoreoActor *actor, CChoreoChannel *channel ) +{ + m_pTokenizer = tokenizer; + + while ( 1 ) + { + if ( !m_pTokenizer->GetToken( true ) ) + { + break; + } + + if ( strlen( m_pTokenizer->CurrentToken() ) <= 0 ) + break; + + if ( !Q_stricmp( m_pTokenizer->CurrentToken(), "event" ) ) + { + ParseEvent( actor, channel ); + } + else + { + m_pTokenizer->Error( "unexpected token %s\n", m_pTokenizer->CurrentToken() ); + break; + } + } + + // Fixup time tags + ReconcileTags(); +} + +void CChoreoScene::SetSubScene( bool sub ) +{ + m_bSubScene = sub; +} + +bool CChoreoScene::IsSubScene( void ) const +{ + return m_bSubScene; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoScene::GetSceneFPS( void ) const +{ + return m_nSceneFPS; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : fps - +//----------------------------------------------------------------------------- +void CChoreoScene::SetSceneFPS( int fps ) +{ + m_nSceneFPS = fps; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoScene::IsUsingFrameSnap( void ) const +{ + return m_bUseFrameSnap; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : snap - +//----------------------------------------------------------------------------- +void CChoreoScene::SetUsingFrameSnap( bool snap ) +{ + m_bUseFrameSnap = snap; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +// Output : float +//----------------------------------------------------------------------------- +float CChoreoScene::SnapTime( float t ) +{ + if ( !IsUsingFrameSnap() ) + return t; + + float fps = (float)GetSceneFPS(); + Assert( fps > 0 ); + + int itime = (int)( t * fps + 0.5f ); + + t = (float)itime / fps; + + // FIXME: If FPS is set and "using grid", snap to proper fractional time value + return t; +} + +EdgeInfo_t *CChoreoScene::GetSceneRampEdgeInfo( int idx ) +{ + return &m_SceneRampEdgeInfo[ idx ]; +} + +float CChoreoScene::GetSceneRampIntensity( float time ) +{ + return CChoreoEvent::GetRampIntensity( this, time ); +} + +int CChoreoScene::GetSceneRampCount( void ) +{ + return m_SceneRamp.Count(); +} + +CExpressionSample *CChoreoScene::GetSceneRamp( int index ) +{ + if ( index < 0 || index >= GetSceneRampCount() ) + return NULL; + + return &m_SceneRamp[ index ]; +} + +CExpressionSample *CChoreoScene::AddSceneRamp( float time, float value, bool selected ) +{ + CExpressionSample sample; + + sample.time = time; + sample.value = value; + sample.selected = selected; + + int idx = m_SceneRamp.AddToTail( sample ); + return &m_SceneRamp[ idx ]; +} + +void CChoreoScene::DeleteSceneRamp( int index ) +{ + if ( index < 0 || index >= GetSceneRampCount() ) + return; + + m_SceneRamp.Remove( index ); +} + +void CChoreoScene::ClearSceneRamp( void ) +{ + m_SceneRamp.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::ResortSceneRamp( void ) +{ + for ( int i = 0; i < m_SceneRamp.Size(); i++ ) + { + for ( int j = i + 1; j < m_SceneRamp.Size(); j++ ) + { + CExpressionSample src = m_SceneRamp[ i ]; + CExpressionSample dest = m_SceneRamp[ j ]; + + if ( src.time > dest.time ) + { + m_SceneRamp[ i ] = dest; + m_SceneRamp[ j ] = src; + } + } + } + + RemoveOutOfRangeSceneRampSamples(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : number - +// Output : CExpressionSample +//----------------------------------------------------------------------------- +CExpressionSample *CChoreoScene::GetBoundedSceneRamp( int number, bool& bClamped ) +{ + if ( number < 0 ) + { + // Search for two samples which span time f + static CExpressionSample nullstart; + nullstart.time = 0.0f; + nullstart.value = SceneRampGetEdgeZeroValue( true ); + nullstart.SetCurveType( SceneRampGetEdgeCurveType( true ) ); + bClamped = true; + return &nullstart; + } + else if ( number >= GetSceneRampCount() ) + { + static CExpressionSample nullend; + nullend.time = FindStopTime(); + nullend.value = SceneRampGetEdgeZeroValue( false ); + nullend.SetCurveType( SceneRampGetEdgeCurveType( false ) ); + bClamped = true; + return &nullend; + } + + bClamped = false; + return GetSceneRamp( number ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::RemoveOutOfRangeSceneRampSamples( void ) +{ + float duration = FindStopTime(); + + int c = GetSceneRampCount(); + for ( int i = c-1; i >= 0; i-- ) + { + CExpressionSample src = m_SceneRamp[ i ]; + if ( src.time < 0 || + src.time > duration + 0.01 ) + { + m_SceneRamp.Remove( i ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::ReconcileGestureTimes() +{ + for ( int i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + c->ReconcileGestureTimes(); + } + } +} + +int CChoreoScene::TimeZoomFirst() +{ + return m_TimeZoomLookup.First(); +} + +int CChoreoScene::TimeZoomNext( int i ) +{ + return m_TimeZoomLookup.Next( i ); +} +int CChoreoScene::TimeZoomInvalid() const +{ + return m_TimeZoomLookup.InvalidIndex(); +} +char const *CChoreoScene::TimeZoomName( int i ) +{ + return m_TimeZoomLookup.GetElementName( i ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tool - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoScene::GetTimeZoom( char const *tool ) +{ + // If not present add it + int idx = m_TimeZoomLookup.Find( tool ); + if ( idx == m_TimeZoomLookup.InvalidIndex() ) + { + idx = m_TimeZoomLookup.Insert( tool, 100 ); + } + + return m_TimeZoomLookup[ idx ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tool - +// tz - +//----------------------------------------------------------------------------- +void CChoreoScene::SetTimeZoom( char const *tool, int tz ) +{ + // If not present add it + int idx = m_TimeZoomLookup.Find( tool ); + if ( idx == m_TimeZoomLookup.InvalidIndex() ) + { + idx = m_TimeZoomLookup.Insert( tool, 100 ); + } + + m_TimeZoomLookup[ idx ] = tz; +} + +void CChoreoScene::ParseScaleSettings( ISceneTokenProcessor *tokenizer, CChoreoScene *scene ) +{ + tokenizer->GetToken( true ); + + if ( stricmp( tokenizer->CurrentToken(), "{" ) ) + tokenizer->Error( "expecting {\n" ); + + while ( 1 ) + { + // Parse until } + tokenizer->GetToken( true ); + + if ( strlen( tokenizer->CurrentToken() ) <= 0 ) + { + tokenizer->Error( "expecting scalesettings data\n" ); + break; + } + + if ( !Q_stricmp( tokenizer->CurrentToken(), "}" ) ) + break; + + char tool[ 256 ]; + Q_strncpy( tool, tokenizer->CurrentToken(), sizeof( tool ) ); + + tokenizer->GetToken( false ); + + int tz = Q_atoi( tokenizer->CurrentToken() ); + if ( tz <= 0 ) + tz = 100; + + scene->SetTimeZoom( tool, tz ); + } +} + +// Merges two .vcd's together +bool CChoreoScene::Merge( CChoreoScene *other ) +{ + int acount = 0; + int ccount = 0; + int ecount = 0; + + // Look for events that don't have actor/channel set + int i; + for ( i = 0 ; i < other->m_Events.Size(); i++ ) + { + CChoreoEvent *e = other->m_Events[ i ]; + if ( e->GetActor() ) + continue; + + MEM_ALLOC_CREDIT(); + // Make a copy of the other event and add it to this scene + CChoreoEvent *newEvent = AllocEvent(); + *newEvent = *e; + newEvent->SetScene( this ); + ecount++; + } + + for ( i = 0 ; i < other->m_Actors.Size(); i++ ) + { + CChoreoActor *a = other->m_Actors[ i ]; + + // See if that actor already exists + bool newActor = false; + CChoreoActor *destActor = FindActor( a->GetName() ); + if ( !destActor ) + { + newActor = true; + destActor = AllocActor(); + *destActor = *a; + destActor->RemoveAllChannels(); + acount++; + } + + // Now we have a destination actor, work on channels + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *ch = a->GetChannel( j ); + + bool newChannel = false; + CChoreoChannel *destChannel = NULL; + destChannel = destActor->FindChannel( ch->GetName() ); + if ( !destChannel ) + { + destChannel = AllocChannel(); + *destChannel = *ch; + destChannel->RemoveAllEvents(); + newChannel = true; + ccount++; + } + + if ( newChannel ) + { + destActor->AddChannel( destChannel ); + destChannel->SetActor( destActor ); + } + + // Now we have a destination channel, work on events themselves + for ( int k = 0 ; k < ch->GetNumEvents(); k++ ) + { + CChoreoEvent *e = ch->GetEvent( k ); + + // Just import them wholesale, no checking + MEM_ALLOC_CREDIT(); + CChoreoEvent *newEvent = AllocEvent(); + *newEvent = *e; + newEvent->SetScene( this ); + + destChannel->AddEvent( newEvent ); + + newEvent->SetChannel( destChannel ); + newEvent->SetActor( destActor ); + + ecount++; + } + } + } + + Msg( "Merged in (%i) actors, (%i) channels, and (%i) events\n", + acount, ccount, ecount ); + + return ( ecount || acount || ccount ); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates master/slave status info per channel +//----------------------------------------------------------------------------- +void CChoreoScene::ReconcileCloseCaption() +{ + for ( int i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + c->ReconcileCloseCaption(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +char const *CChoreoScene::GetFilename() const +{ + return m_szFileName; +} + + +void CChoreoScene::SetFileName( char const *fn ) +{ + Q_strncpy( m_szFileName, fn, sizeof( m_szFileName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this scene has speech events that haven't played yet +//----------------------------------------------------------------------------- +bool CChoreoScene::HasUnplayedSpeech() +{ + for ( int i = 0; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + if ( e->GetType() == CChoreoEvent::SPEAK ) + { + // Have we played it yet? + if ( m_flCurrentTime < e->GetStartTime() ) + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this scene has flex animation events that are playing +//----------------------------------------------------------------------------- +bool CChoreoScene::HasFlexAnimation() +{ + for ( int i = 0; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + if ( e->GetType() == CChoreoEvent::FLEXANIMATION ) + { + // Have we played it yet? + if ( m_flCurrentTime >= e->GetStartTime() && m_flCurrentTime <= e->GetEndTime() ) + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoScene::SetBackground( bool bIsBackground ) +{ + m_bIsBackground = bIsBackground; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CChoreoScene::IsBackground( ) +{ + return m_bIsBackground; +} + +bool CChoreoScene::HasEventsOfType( CChoreoEvent::EVENTTYPE type ) const +{ + return m_bitvecHasEventOfType.IsBitSet( type ); +} + +void CChoreoScene::SceneRampSetEdgeInfo( bool leftEdge, int curveType, float zero ) +{ + int idx = leftEdge ? 0 : 1; + m_SceneRampEdgeInfo[ idx ].m_CurveType = curveType; + m_SceneRampEdgeInfo[ idx ].m_flZeroPos = zero; +} + +void CChoreoScene::SceneRampGetEdgeInfo( bool leftEdge, int& curveType, float& zero ) const +{ + int idx = leftEdge ? 0 : 1; + curveType = m_SceneRampEdgeInfo[ idx ].m_CurveType; + zero = m_SceneRampEdgeInfo[ idx ].m_flZeroPos; +} + +void CChoreoScene::SceneRampSetEdgeActive( bool leftEdge, bool state ) +{ + int idx = leftEdge ? 0 : 1; + m_SceneRampEdgeInfo[ idx ].m_bActive = state; +} + +bool CChoreoScene::SceneRampIsEdgeActive( bool leftEdge ) const +{ + int idx = leftEdge ? 0 : 1; + return m_SceneRampEdgeInfo[ idx ].m_bActive; +} + +int CChoreoScene::SceneRampGetEdgeCurveType( bool leftEdge ) const +{ + if ( !SceneRampIsEdgeActive( leftEdge ) ) + { + return CURVE_DEFAULT; + } + + int idx = leftEdge ? 0 : 1; + return m_SceneRampEdgeInfo[ idx ].m_CurveType; +} + +float CChoreoScene::SceneRampGetEdgeZeroValue( bool leftEdge ) const +{ + if ( !SceneRampIsEdgeActive( leftEdge ) ) + { + return 0.0f; + } + + int idx = leftEdge ? 0 : 1; + return m_SceneRampEdgeInfo[ idx ].m_flZeroPos; +} + +// ICurveDataAccessor method +bool CChoreoScene::CurveHasEndTime() +{ + return true; +} + +// ICurveDataAccessor method +int CChoreoScene::CurveGetSampleCount() +{ + return GetSceneRampCount(); +} + +// ICurveDataAccessor method +CExpressionSample *CChoreoScene::CurveGetBoundedSample( int idx, bool& bClamped ) +{ + return GetBoundedSceneRamp( idx, bClamped ); +} + +int CChoreoScene::GetDefaultCurveType() +{ + return CURVE_CATMULL_ROM_TO_CATMULL_ROM; +} + +#define SCENE_BINARY_VERSION 0x01 +#define SCENE_TAG MAKEID( 'x', 'v', 'c', 'd' ) + +bool CChoreoScene::SaveBinary( char const *pszBinaryFileName, char const *pPathID, unsigned int nTextVersionCRC ) +{ + bool bret = false; + CUtlBuffer buf; + + SaveToBuffer( buf, nTextVersionCRC ); + + if ( SceneFileSystem()->FileExists( pszBinaryFileName, pPathID ) && + !SceneFileSystem()->IsFileWritable( pszBinaryFileName, pPathID ) ) + { + Warning( "Forcing '%s' to be writable!!!\n", pszBinaryFileName ); + SceneFileSystem()->SetFileWritable( pszBinaryFileName, true, pPathID ); + } + + FileHandle_t fh = SceneFileSystem()->Open( pszBinaryFileName, "wb", pPathID ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + SceneFileSystem()->Write( buf.Base(), buf.TellPut(), fh ); + + // pad to 512 byte sector size + int align = (buf.TellPut() + 511) & ~511; + int padLength = align - buf.TellPut(); + char padByte = 0; + for ( int i=0; iWrite( &padByte, 1, fh ); + } + + SceneFileSystem()->Close( fh ); + + // Success + bret = true; + } + else + { + Warning( "Unable to open '%s' for writing!!!\n", pszBinaryFileName ); + } + + return bret; +} + +void CChoreoScene::SaveToBuffer( CUtlBuffer& buf, unsigned int nTextVersionCRC ) +{ + buf.PutInt( SCENE_TAG ); + buf.PutChar( SCENE_BINARY_VERSION ); + buf.PutInt( nTextVersionCRC ); + + CUtlVector< CChoreoEvent * > eventList; + + // Look for events that don't have actor/channel set + int i; + for ( i = 0 ; i < m_Events.Size(); i++ ) + { + CChoreoEvent *e = m_Events[ i ]; + if ( e->GetActor() ) + continue; + + eventList.AddToTail( e ); + } + + int c = eventList.Count(); + buf.PutShort( c ); + for ( i = 0; i < c; ++i ) + { + CChoreoEvent *e = eventList[ i ]; + e->SaveToBuffer( buf, this ); + } + + // Now serialize the actors themselves + CUtlVector< CChoreoActor * > actorList; + for ( i = 0 ; i < m_Actors.Size(); i++ ) + { + CChoreoActor *a = m_Actors[ i ]; + if ( !a ) + continue; + + actorList.AddToTail( a ); + } + + c = actorList.Count(); + buf.PutShort( c ); + for ( i = 0; i < c; ++i ) + { + CChoreoActor *a = actorList[ i ]; + a->SaveToBuffer( buf, this ); + } + + /* + // compiled version strips out map name, only used by editor + if ( m_szMapname[ 0 ] ) + { + FilePrintf( buf, 0, "mapname \"%s\"\n", m_szMapname ); + } + */ + + SaveSceneRampToBuffer( buf ); + + /* + // compiled version strips out scale settings fps and snap, only used by editor + FileSaveScaleSettings( buf, 0, this ); + FilePrintf( buf, 0, "fps %i\n", m_nSceneFPS ); + FilePrintf( buf, 0, "snap %s\n", m_bUseFrameSnap ? "on" : "off" ); + */ +} + +//----------------------------------------------------------------------------- +// Purpose: Static method to extract just the CRC from a binary .xcd file +// Input : buf - +// crc - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoScene::GetCRCFromBuffer( CUtlBuffer& buf, unsigned int& crc ) +{ + bool bret = false; + + int pos = buf.TellGet(); + + int tag = buf.GetInt(); + if ( tag == SCENE_TAG ) + { + byte ver = buf.GetChar(); + if ( ver == SCENE_BINARY_VERSION ) + { + bret = true; + crc = (unsigned int)buf.GetInt(); + } + } + + buf.SeekGet( CUtlBuffer::SEEK_HEAD, pos ); + + return bret; +} + +bool CChoreoScene::RestoreFromBuffer( CUtlBuffer& buf, char const *filename ) +{ + Q_strncpy( m_szFileName, filename, sizeof( m_szFileName ) ); + + int tag = buf.GetInt(); + if ( tag != SCENE_TAG ) + return false; + + byte ver = buf.GetChar(); + if ( ver != SCENE_BINARY_VERSION ) + return false; + + // Skip the CRC + buf.GetInt(); + + int i; + int eventCount = buf.GetShort(); + for ( i = 0; i < eventCount; ++i ) + { + MEM_ALLOC_CREDIT(); + CChoreoEvent *e = AllocEvent(); + Assert( e ); + + if ( e->RestoreFromBuffer( buf, this ) ) + { + continue; + } + + return false; + } + + int actorCount = buf.GetShort(); + for ( i = 0; i < actorCount; ++i ) + { + CChoreoActor *a = AllocActor(); + Assert( a ); + if ( a->RestoreFromBuffer( buf, this ) ) + { + continue; + } + + return false; + } + + if ( !ParseSceneRampFromBuffer( buf ) ) + { + return false; + } + +// FIXME: Are these ever needed on restore? +// ReconcileTags(); +// ReconcileGestureTimes(); + + ReconcileCloseCaption(); + + if ( CChoreoScene::s_bEditingDisabled ) + { + m_flPrecomputedStopTime = FindStopTime(); + } + + return true; +} + +void CChoreoScene::SaveSceneRampToBuffer( CUtlBuffer& buf ) +{ + // Nothing to save? + int c = GetSceneRampCount(); + buf.PutInt( c ); + if ( c <= 0 ) + return; + + for ( int i = 0; i < c; i++ ) + { + CExpressionSample *sample = GetSceneRamp( i ); + buf.PutFloat( sample->time ); + buf.PutFloat( sample->value ); + } +} + +bool CChoreoScene::ParseSceneRampFromBuffer( CUtlBuffer& buf ) +{ + int c = buf.GetInt(); + if ( c <= 0 ) + return true; + + for ( int i = 0; i < c; i++ ) + { + float t = buf.GetFloat(); + float v = buf.GetFloat(); + + AddSceneRamp( t, v, false ); + } + + return true; +} + diff --git a/cl_dll/client_hl2-2003.vcproj b/cl_dll/client_hl2-2003.vcproj index f0686a4b..42c31ff2 100644 --- a/cl_dll/client_hl2-2003.vcproj +++ b/cl_dll/client_hl2-2003.vcproj @@ -55,7 +55,7 @@ copy "$(TargetDir)"client.dll "..\..\game\hl2\bin" LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBC,LIBCD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="TRUE" @@ -147,7 +147,7 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMTD,LIBCD,LIBC" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="TRUE" @@ -507,66 +507,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2721,10 +2661,10 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. RelativePath="..\public\vgui\IImage.h"> + RelativePath="iinput.h"> + RelativePath="..\public\vgui\IInput.h"> @@ -2964,10 +2904,10 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. RelativePath="..\public\tier1\mempool.h"> + RelativePath="..\public\vgui_controls\Menu.h"> + RelativePath="menu.h"> @@ -3531,27 +3471,34 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/cl_dll/client_hl2-2005.vcproj b/cl_dll/client_hl2-2005.vcproj index 58bd306c..6eecabd0 100644 --- a/cl_dll/client_hl2-2005.vcproj +++ b/cl_dll/client_hl2-2005.vcproj @@ -92,7 +92,7 @@ SuppressStartupBanner="true" AdditionalLibraryDirectories="..\lib\public" IgnoreAllDefaultLibraries="false" - IgnoreDefaultLibraryNames="LIBC,LIBCD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" GenerateDebugInformation="true" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="true" @@ -208,7 +208,7 @@ LinkIncremental="1" SuppressStartupBanner="true" AdditionalLibraryDirectories="..\lib\public" - IgnoreDefaultLibraryNames="LIBC,LIBCD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="true" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="true" @@ -670,86 +670,6 @@ RelativePath="cdll_util.cpp" > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -4684,34 +4604,42 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/cl_dll/client_hl2mp-2003.vcproj b/cl_dll/client_hl2mp-2003.vcproj index 6da63cbc..f8f4da3f 100644 --- a/cl_dll/client_hl2mp-2003.vcproj +++ b/cl_dll/client_hl2mp-2003.vcproj @@ -26,7 +26,7 @@ AdditionalIncludeDirectories=".\hl2mp\UI,.\hl2mp,..\game_shared\hl2mp,.\hl2_hud,.\hl2_hud\elements,../game_shared/hl2,.,.\..\Public,.\..\Public\tier1,.\..\vgui2\include,.\..\vgui2\controls,../game_shared,.\game_controls" PreprocessorDefinitions="HL2_EPISODIC;HL2MP;HL2_CLIENT_DLL;_DEBUG;fopen=dont_use_fopen;NO_STRING_T;CLIENT_DLL;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" ExceptionHandling="FALSE" - RuntimeLibrary="5" + RuntimeLibrary="1" ForceConformanceInForLoopScope="TRUE" RuntimeTypeInfo="TRUE" UsePrecompiledHeader="3" @@ -55,7 +55,7 @@ copy "$(TargetDir)"client.dll "..\..\game\hl2mp\bin" LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMTD,LIBC,LIBCMT" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="TRUE" @@ -114,7 +114,7 @@ copy "$(TargetDir)"client.dll "..\..\game\hl2mp\bin" PreprocessorDefinitions="HL2_EPISODIC;HL2MP;HL2_CLIENT_DLL;NDEBUG;NO_STRING_T;CLIENT_DLL;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" StringPooling="TRUE" ExceptionHandling="FALSE" - RuntimeLibrary="4" + RuntimeLibrary="0" BufferSecurityCheck="FALSE" EnableFunctionLevelLinking="TRUE" ForceConformanceInForLoopScope="TRUE" @@ -147,7 +147,7 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="libcd.lib,LIBCD,LIBCMT,libcmtd" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="TRUE" @@ -507,66 +507,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2144,16 +2084,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - @@ -2169,80 +2099,30 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2252,47 +2132,15 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - - - - - @@ -2306,18 +2154,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. Filter=""> - - - - - - @@ -2348,18 +2184,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - @@ -2990,10 +2814,10 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. RelativePath="..\public\vgui\IImage.h"> + RelativePath="..\public\vgui\IInput.h"> + RelativePath="iinput.h"> @@ -3236,10 +3060,10 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. RelativePath="..\public\tier1\mempool.h"> + RelativePath="..\public\vgui_controls\Menu.h"> + RelativePath="menu.h"> @@ -3793,27 +3617,34 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/cl_dll/client_hl2mp-2005.vcproj b/cl_dll/client_hl2mp-2005.vcproj index b10ebd41..aec97236 100644 --- a/cl_dll/client_hl2mp-2005.vcproj +++ b/cl_dll/client_hl2mp-2005.vcproj @@ -203,7 +203,7 @@ LinkIncremental="1" SuppressStartupBanner="true" AdditionalLibraryDirectories="..\lib\public" - IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="true" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="true" @@ -665,86 +665,6 @@ RelativePath="cdll_util.cpp" > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2836,20 +2756,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3965,11 +3725,11 @@ > - - - - - - - - - + - - - - - - - - - - - + - - - + - - - + + + + + + + + + + diff --git a/cl_dll/client_scratch-2003.vcproj b/cl_dll/client_scratch-2003.vcproj index be3352e2..07231198 100644 --- a/cl_dll/client_scratch-2003.vcproj +++ b/cl_dll/client_scratch-2003.vcproj @@ -56,7 +56,7 @@ copy "$(TargetDir)"client.dll "..\..\game\sdksample\bin" LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMT,LIBC" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="TRUE" @@ -149,7 +149,7 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="libcd.lib,LIBCD,LIBCMT,libcmtd" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="TRUE" @@ -506,66 +506,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1859,79 +1799,27 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1941,16 +1829,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - @@ -1963,16 +1841,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - @@ -1982,16 +1850,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - @@ -2013,32 +1871,12 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - @@ -2048,80 +1886,30 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2131,16 +1919,6 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - @@ -2820,10 +2598,10 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. RelativePath="..\public\vgui\IImage.h"> + RelativePath="iinput.h"> + RelativePath="..\public\vgui\IInput.h"> @@ -3066,10 +2844,10 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. RelativePath="..\public\tier1\mempool.h"> + RelativePath="menu.h"> + RelativePath="..\public\vgui_controls\Menu.h"> @@ -3635,27 +3413,34 @@ if exist "$(TargetDir)"client.pdb copy "$(TargetDir)"client. - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/cl_dll/client_scratch-2005.vcproj b/cl_dll/client_scratch-2005.vcproj index d556aa2f..f6f6bd78 100644 --- a/cl_dll/client_scratch-2005.vcproj +++ b/cl_dll/client_scratch-2005.vcproj @@ -89,7 +89,7 @@ LinkIncremental="2" SuppressStartupBanner="true" AdditionalLibraryDirectories="..\lib\public" - IgnoreDefaultLibraryNames="LIBC,LIBCD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" GenerateDebugInformation="true" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="true" @@ -205,7 +205,7 @@ LinkIncremental="1" SuppressStartupBanner="true" AdditionalLibraryDirectories="..\lib\public" - IgnoreDefaultLibraryNames="LIBC,LIBCD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="true" ProgramDatabaseFile="$(IntDir)/client.pdb" GenerateMapFile="true" @@ -663,86 +663,6 @@ RelativePath="cdll_util.cpp" > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2502,7 +2422,7 @@ > @@ -4827,88 +4747,96 @@ - - - - - - - - - + - - - - - - - - - + - - - + - - - - - + + + + + + + - - - + - - - + + + + + + + + + + + + + + + + + + diff --git a/cl_dll/game_controls/SpectatorGUI.cpp b/cl_dll/game_controls/SpectatorGUI.cpp index 11e4e9c4..af9792c0 100644 --- a/cl_dll/game_controls/SpectatorGUI.cpp +++ b/cl_dll/game_controls/SpectatorGUI.cpp @@ -505,7 +505,7 @@ void CSpectatorGUI::Update() wchar_t playerText[ 80 ], playerName[ 64 ], health[ 10 ]; wcscpy( playerText, L"Unable to find #Spec_PlayerItem*" ); - memset( playerName, 0x0, sizeof( playerName ) * sizeof( wchar_t ) ); + memset( playerName, 0x0, sizeof( playerName ) ); localize()->ConvertANSIToUnicode( UTIL_SafeName(gr->GetPlayerName( playernum )), playerName, sizeof( playerName ) ); int iHealth = gr->GetHealth( playernum ); diff --git a/cl_dll/hl2_hud/hud_autoaim.cpp b/cl_dll/hl2_hud/hud_autoaim.cpp index 5f7f97ea..68a9e203 100644 --- a/cl_dll/hl2_hud/hud_autoaim.cpp +++ b/cl_dll/hl2_hud/hud_autoaim.cpp @@ -199,7 +199,7 @@ void CHUDAutoAim::OnThink() QAngle targetangles; QAngle delta; - float dist = (pLocalPlayer->m_HL2Local.m_vecAutoAimPoint - pLocalPlayer->GetAbsOrigin()).Length(); +// float dist = (pLocalPlayer->m_HL2Local.m_vecAutoAimPoint - pLocalPlayer->GetAbsOrigin()).Length(); engine->GetViewAngles( viewangles ); diff --git a/dlls/AI_ResponseSystem.cpp b/dlls/AI_ResponseSystem.cpp index 6b153dc8..67911a7f 100644 --- a/dlls/AI_ResponseSystem.cpp +++ b/dlls/AI_ResponseSystem.cpp @@ -1,3070 +1,3070 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - - -#include "cbase.h" -#include "SoundEmitterSystem/isoundemittersystembase.h" -#include "AI_ResponseSystem.h" -#include "igamesystem.h" -#include "AI_Criteria.h" -#include -#include "filesystem.h" -#include "utldict.h" -#include "ai_speech.h" -#include "vstdlib/ICommandLine.h" -#include -#include "sceneentity.h" -#include "isaverestore.h" -#include "utlbuffer.h" -#include "stringpool.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring). If set to 3, it will only show response success/failure for npc_selected NPCs." ); -ConVar rr_debugrule( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); -ConVar rr_dumpresponses( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); - -static CUtlSymbolTable g_RS; - -inline static char *CopyString( const char *in ) -{ - if ( !in ) - return NULL; - - int len = Q_strlen( in ); - char *out = new char[ len + 1 ]; - Q_memcpy( out, in, len ); - out[ len ] = 0; - return out; -} - -#pragma pack(1) -class Matcher -{ -public: - Matcher() - { - valid = false; - isnumeric = false; - notequal = false; - usemin = false; - minequals = false; - usemax = false; - maxequals = false; - maxval = 0.0f; - minval = 0.0f; - - token = UTL_INVAL_SYMBOL; - rawtoken = UTL_INVAL_SYMBOL; - } - - void Describe( void ) - { - if ( !valid ) - { - DevMsg( " invalid!\n" ); - return; - } - char sz[ 128 ]; - - sz[ 0] = 0; - int minmaxcount = 0; - if ( usemin ) - { - Q_snprintf( sz, sizeof( sz ), ">%s%.3f", minequals ? "=" : "", minval ); - minmaxcount++; - } - if ( usemax ) - { - char sz2[ 128 ]; - Q_snprintf( sz2, sizeof( sz2 ), "<%s%.3f", maxequals ? "=" : "", maxval ); - - if ( minmaxcount > 0 ) - { - Q_strncat( sz, " and ", sizeof( sz ), COPY_ALL_CHARACTERS ); - } - Q_strncat( sz, sz2, sizeof( sz ), COPY_ALL_CHARACTERS ); - minmaxcount++; - } - - if ( minmaxcount >= 1 ) - { - DevMsg( " matcher: %s\n", sz ); - return; - } - - if ( notequal ) - { - DevMsg( " matcher: !=%s\n", GetToken() ); - return; - } - - DevMsg( " matcher: ==%s\n", GetToken() ); - } - - float maxval; - float minval; - - bool valid : 1; //1 - bool isnumeric : 1; //2 - bool notequal : 1; //3 - bool usemin : 1; //4 - bool minequals : 1; //5 - bool usemax : 1; //6 - bool maxequals : 1; //7 - - void SetToken( char const *s ) - { - token = g_RS.AddString( s ); - } - - char const *GetToken() - { - if ( token.IsValid() ) - { - return g_RS.String( token ); - } - return ""; - } - void SetRaw( char const *raw ) - { - rawtoken = g_RS.AddString( raw ); - } - char const *GetRaw() - { - if ( rawtoken.IsValid() ) - { - return g_RS.String( rawtoken ); - } - return ""; - } - -private: - CUtlSymbol token; - CUtlSymbol rawtoken; -}; - -struct Response -{ - DECLARE_SIMPLE_DATADESC(); - - Response() - { - type = RESPONSE_NONE; - value = NULL; - weight.SetFloat( 1.0f ); - depletioncount = 0; - first = false; - last = false; - } - - Response( const Response& src ) - { - weight = src.weight; - type = src.type; - value = CopyString( src.value ); - depletioncount = src.depletioncount; - first = src.first; - last = src.last; - } - - Response& operator =( const Response& src ) - { - if ( this == &src ) - return *this; - weight = src.weight; - type = src.type; - value = CopyString( src.value ); - depletioncount = src.depletioncount; - first = src.first; - last = src.last; - return *this; - } - - ~Response() - { - delete[] value; - } - - ResponseType_t GetType() { return (ResponseType_t)type; } - - char *value; // fixed up value spot // 4 - float16 weight; // 6 - - byte depletioncount; // 7 - byte type : 6; // 8 - byte first : 1; // - byte last : 1; // -}; - -struct ResponseGroup -{ - DECLARE_SIMPLE_DATADESC(); - - ResponseGroup() - { - // By default visit all nodes before repeating - m_bSequential = false; - m_bNoRepeat = false; - m_bEnabled = true; - m_nCurrentIndex = 0; - m_bDepleteBeforeRepeat = true; - m_nDepletionCount = 1; - m_bHasFirst = false; - m_bHasLast = false; - } - - ResponseGroup( const ResponseGroup& src ) - { - int c = src.group.Count(); - for ( int i = 0; i < c; i++ ) - { - group.AddToTail( src.group[ i ] ); - } - - rp = src.rp; - m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; - m_nDepletionCount = src.m_nDepletionCount; - m_bHasFirst = src.m_bHasFirst; - m_bHasLast = src.m_bHasLast; - m_bSequential = src.m_bSequential; - m_bNoRepeat = src.m_bNoRepeat; - m_bEnabled = src.m_bEnabled; - m_nCurrentIndex = src.m_nCurrentIndex; - } - - ResponseGroup& operator=( const ResponseGroup& src ) - { - if ( this == &src ) - return *this; - int c = src.group.Count(); - for ( int i = 0; i < c; i++ ) - { - group.AddToTail( src.group[ i ] ); - } - - rp = src.rp; - m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; - m_nDepletionCount = src.m_nDepletionCount; - m_bHasFirst = src.m_bHasFirst; - m_bHasLast = src.m_bHasLast; - m_bSequential = src.m_bSequential; - m_bNoRepeat = src.m_bNoRepeat; - m_bEnabled = src.m_bEnabled; - m_nCurrentIndex = src.m_nCurrentIndex; - return *this; - } - - bool HasUndepletedChoices() const - { - if ( !m_bDepleteBeforeRepeat ) - return true; - - int c = group.Count(); - for ( int i = 0; i < c; i++ ) - { - if ( group[ i ].depletioncount != m_nDepletionCount ) - return true; - } - - return false; - } - - void MarkResponseUsed( int idx ) - { - if ( !m_bDepleteBeforeRepeat ) - return; - - if ( idx < 0 || idx >= group.Count() ) - { - Assert( 0 ); - return; - } - - group[ idx ].depletioncount = m_nDepletionCount; - } - - void ResetDepletionCount() - { - if ( !m_bDepleteBeforeRepeat ) - return; - ++m_nDepletionCount; - } - - void Reset() - { - ResetDepletionCount(); - SetEnabled( true ); - SetCurrentIndex( 0 ); - m_nDepletionCount = 1; - - for ( int i = 0; i < group.Count(); ++i ) - { - group[ i ].depletioncount = 0; - } - } - - bool HasUndepletedFirst( int& index ) - { - index = -1; - - if ( !m_bDepleteBeforeRepeat ) - return false; - - int c = group.Count(); - for ( int i = 0; i < c; i++ ) - { - Response *r = &group[ i ]; - - if ( ( r->depletioncount != m_nDepletionCount ) && r->first ) - { - index = i; - return true; - } - } - - return false; - } - - bool HasUndepletedLast( int& index ) - { - index = -1; - - if ( !m_bDepleteBeforeRepeat ) - return false; - - int c = group.Count(); - for ( int i = 0; i < c; i++ ) - { - Response *r = &group[ i ]; - - if ( ( r->depletioncount != m_nDepletionCount ) && r->last ) - { - index = i; - return true; - } - } - - return false; - } - - bool ShouldCheckRepeats() const { return m_bDepleteBeforeRepeat; } - int GetDepletionCount() const { return m_nDepletionCount; } - - bool IsSequential() const { return m_bSequential; } - void SetSequential( bool seq ) { m_bSequential = seq; } - - bool IsNoRepeat() const { return m_bNoRepeat; } - void SetNoRepeat( bool norepeat ) { m_bNoRepeat = norepeat; } - - bool IsEnabled() const { return m_bEnabled; } - void SetEnabled( bool enabled ) { m_bEnabled = enabled; } - - int GetCurrentIndex() const { return m_nCurrentIndex; } - void SetCurrentIndex( byte idx ) { m_nCurrentIndex = idx; } - - CUtlVector< Response > group; - - AI_ResponseParams rp; - - bool m_bEnabled; - - byte m_nCurrentIndex; - // Invalidation counter - byte m_nDepletionCount; - - // Use all slots before repeating any - bool m_bDepleteBeforeRepeat : 1; - bool m_bHasFirst : 1; - bool m_bHasLast : 1; - bool m_bSequential : 1; - bool m_bNoRepeat : 1; - -}; - -struct Criteria -{ - Criteria() - { - name = NULL; - value = NULL; - weight.SetFloat( 1.0f ); - required = false; - } - Criteria& operator =(const Criteria& src ) - { - if ( this == &src ) - return *this; - - name = CopyString( src.name ); - value = CopyString( src.value ); - weight = src.weight; - required = src.required; - - matcher = src.matcher; - - int c = src.subcriteria.Count(); - for ( int i = 0; i < c; i++ ) - { - subcriteria.AddToTail( src.subcriteria[ i ] ); - } - - return *this; - } - Criteria(const Criteria& src ) - { - name = CopyString( src.name ); - value = CopyString( src.value ); - weight = src.weight; - required = src.required; - - matcher = src.matcher; - - int c = src.subcriteria.Count(); - for ( int i = 0; i < c; i++ ) - { - subcriteria.AddToTail( src.subcriteria[ i ] ); - } - } - ~Criteria() - { - delete[] name; - delete[] value; - } - - bool IsSubCriteriaType() const - { - return ( subcriteria.Count() > 0 ) ? true : false; - } - - char *name; - char *value; - float16 weight; - bool required; - - Matcher matcher; - - // Indices into sub criteria - CUtlVector< unsigned short > subcriteria; -}; - -struct Rule -{ - Rule() - { - m_bMatchOnce = false; - m_bEnabled = true; - m_szContext = NULL; - } - - Rule& operator =( const Rule& src ) - { - if ( this == &src ) - return *this; - - int i; - int c; - - c = src.m_Criteria.Count(); - for ( i = 0; i < c; i++ ) - { - m_Criteria.AddToTail( src.m_Criteria[ i ] ); - } - - c = src.m_Responses.Count(); - for ( i = 0; i < c; i++ ) - { - m_Responses.AddToTail( src.m_Responses[ i ] ); - } - - SetContext( src.m_szContext ); - m_bMatchOnce = src.m_bMatchOnce; - m_bEnabled = src.m_bEnabled; - return *this; - } - - Rule( const Rule& src ) - { - int i; - int c; - - c = src.m_Criteria.Count(); - for ( i = 0; i < c; i++ ) - { - m_Criteria.AddToTail( src.m_Criteria[ i ] ); - } - - c = src.m_Responses.Count(); - for ( i = 0; i < c; i++ ) - { - m_Responses.AddToTail( src.m_Responses[ i ] ); - } - - SetContext( src.m_szContext ); - m_bMatchOnce = src.m_bMatchOnce; - m_bEnabled = src.m_bEnabled; - } - - ~Rule() - { - delete[] m_szContext; - } - - void SetContext( const char *context ) - { - delete[] m_szContext; - m_szContext = CopyString( context ); - } - - const char *GetContext( void ) const { return m_szContext; } - - bool IsEnabled() const { return m_bEnabled; } - void Disable() { m_bEnabled = false; } - bool IsMatchOnce() const { return m_bMatchOnce; } - - // Indices into underlying criteria and response dictionaries - CUtlVector< unsigned short > m_Criteria; - CUtlVector< unsigned short> m_Responses; - - char *m_szContext; - - bool m_bMatchOnce : 1; - bool m_bEnabled : 1; -}; -#pragma pack() - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -abstract_class CResponseSystem : public IResponseSystem -{ -public: - CResponseSystem(); - ~CResponseSystem(); - - // IResponseSystem - virtual bool FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter = NULL ); - virtual void GetAllResponses( CUtlVector *pResponses ); - - virtual void Release() = 0; - - virtual void DumpRules(); - - virtual void Precache(); - - virtual void PrecacheResponses( bool bEnable ) - { - m_bPrecache = bEnable; - } - - bool ShouldPrecache() { return m_bPrecache; } - - void Clear(); - -protected: - - virtual const char *GetScriptFile( void ) = 0; - void LoadRuleSet( const char *setname ); - - void ResetResponseGroups(); -public: - - - - -private: - - struct Enumeration - { - float value; - }; - - struct ResponseSearchResult - { - ResponseSearchResult() - { - group = NULL; - action = NULL; - } - - ResponseGroup *group; - Response *action; - }; - - inline bool ParseToken( void ) - { - if ( m_bUnget ) - { - m_bUnget = false; - return true; - } - if ( m_ScriptStack.Count() <= 0 ) - { - Assert( 0 ); - return false; - } - - m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); - m_ScriptStack[ 0 ].tokencount++; - return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; - } - - inline void Unget() - { - m_bUnget = true; - } - - inline bool TokenWaiting( void ) - { - if ( m_ScriptStack.Count() <= 0 ) - { - Assert( 0 ); - return false; - } - - const char *p = m_ScriptStack[ 0 ].currenttoken; - - if ( !p ) - { - Error( "AI_ResponseSystem: Unxpected TokenWaiting() with NULL buffer in %s", m_ScriptStack[ 0 ].name ); - return false; - } - - - while ( *p && *p!='\n') - { - // Special handler for // comment blocks - if ( *p == '/' && *(p+1) == '/' ) - return false; - - if ( !isspace( *p ) || isalnum( *p ) ) - return true; - - p++; - } - - return false; - } - - void ParseOneResponse( const char *responseGroupName, ResponseGroup& group ); - - void ParseInclude( CStringPool &includedFiles ); - void ParseResponse( void ); - void ParseCriterion( void ); - void ParseRule( void ); - void ParseEnumeration( void ); - - int ParseOneCriterion( const char *criterionName ); - - bool Compare( const char *setValue, Criteria *c, bool verbose = false ); - bool CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose = false ); - void ComputeMatcher( Criteria *c, Matcher& matcher ); - void ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ); - float LookupEnumeration( const char *name, bool& found ); - - int FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose ); - - float ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose = false ); - float RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ); - float ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose = false ); - bool GetBestResponse( ResponseSearchResult& result, Rule *rule, bool verbose = false, IResponseFilter *pFilter = NULL ); - bool ResolveResponse( ResponseSearchResult& result, int depth, const char *name, bool verbose = false, IResponseFilter *pFilter = NULL ); - int SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ); - void DescribeResponseGroup( ResponseGroup *group, int selected, int depth ); - void DebugPrint( int depth, const char *fmt, ... ); - - void LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles ); - -// void TouchReferencedScenes(); - - void GetCurrentScript( char *buf, size_t buflen ); - int GetCurrentToken() const; - void SetCurrentScript( const char *script ); - bool IsRootCommand(); - - void PushScript( const char *scriptfile, unsigned char *buffer ); - void PopScript(void); - - void ResponseWarning( const char *fmt, ... ); - - CUtlDict< ResponseGroup, short > m_Responses; - CUtlDict< Criteria, short > m_Criteria; - CUtlDict< Rule, short > m_Rules; - CUtlDict< Enumeration, short > m_Enumerations; - - char token[ 1204 ]; - - bool m_bUnget; - bool m_bPrecache; - - struct ScriptEntry - { - unsigned char *buffer; - FileNameHandle_t name; - const char *currenttoken; - int tokencount; - }; - - CUtlVector< ScriptEntry > m_ScriptStack; - - friend class CDefaultResponseSystemSaveRestoreBlockHandler; - friend class CResponseSystemSaveRestoreOps; -}; - -BEGIN_SIMPLE_DATADESC( Response ) - // DEFINE_FIELD( type, FIELD_INTEGER ), - // DEFINE_ARRAY( value, FIELD_CHARACTER ), - // DEFINE_FIELD( weight, FIELD_FLOAT ), - DEFINE_FIELD( depletioncount, FIELD_CHARACTER ), - // DEFINE_FIELD( first, FIELD_BOOLEAN ), - // DEFINE_FIELD( last, FIELD_BOOLEAN ), -END_DATADESC() - -BEGIN_SIMPLE_DATADESC( ResponseGroup ) - // DEFINE_FIELD( group, FIELD_UTLVECTOR ), - // DEFINE_FIELD( rp, FIELD_EMBEDDED ), - // DEFINE_FIELD( m_bDepleteBeforeRepeat, FIELD_BOOLEAN ), - DEFINE_FIELD( m_nDepletionCount, FIELD_CHARACTER ), - // DEFINE_FIELD( m_bHasFirst, FIELD_BOOLEAN ), - // DEFINE_FIELD( m_bHasLast, FIELD_BOOLEAN ), - // DEFINE_FIELD( m_bSequential, FIELD_BOOLEAN ), - // DEFINE_FIELD( m_bNoRepeat, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), - DEFINE_FIELD( m_nCurrentIndex, FIELD_CHARACTER ), -END_DATADESC() - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CResponseSystem::CResponseSystem() -{ - token[0] = 0; - m_bUnget = false; - m_bPrecache = true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CResponseSystem::~CResponseSystem() -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : char const -//----------------------------------------------------------------------------- -void CResponseSystem::GetCurrentScript( char *buf, size_t buflen ) -{ - Assert( buf ); - buf[ 0 ] = 0; - if ( m_ScriptStack.Count() <= 0 ) - return; - - if ( filesystem->String( m_ScriptStack[ 0 ].name, buf, buflen ) ) - { - return; - } - buf[ 0 ] = 0; -} - -void CResponseSystem::PushScript( const char *scriptfile, unsigned char *buffer ) -{ - ScriptEntry e; - e.name = filesystem->FindOrAddFileName( scriptfile ); - e.buffer = buffer; - e.currenttoken = (char *)e.buffer; - e.tokencount = 0; - m_ScriptStack.AddToHead( e ); -} - -void CResponseSystem::PopScript(void) -{ - Assert( m_ScriptStack.Count() >= 1 ); - if ( m_ScriptStack.Count() <= 0 ) - return; - - m_ScriptStack.Remove( 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::Clear() -{ - m_Responses.RemoveAll(); - m_Criteria.RemoveAll(); - m_Rules.RemoveAll(); - m_Enumerations.RemoveAll(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -// found - -// Output : float -//----------------------------------------------------------------------------- -float CResponseSystem::LookupEnumeration( const char *name, bool& found ) -{ - int idx = m_Enumerations.Find( name ); - if ( idx == m_Enumerations.InvalidIndex() ) - { - found = false; - return 0.0f; - } - - - found = true; - return m_Enumerations[ idx ].value; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : matcher - -//----------------------------------------------------------------------------- -void CResponseSystem::ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ) -{ - if ( rawtoken[0] != '[' ) - { - Q_strncpy( token, rawtoken, bufsize ); - return; - } - - // Now lookup enumeration - bool found = false; - float f = LookupEnumeration( rawtoken, found ); - if ( !found ) - { - Q_strncpy( token, rawtoken, bufsize ); - ResponseWarning( "No such enumeration '%s'\n", token ); - return; - } - - Q_snprintf( token, bufsize, "%f", f ); -} - - -static bool AppearsToBeANumber( char const *token ) -{ - if ( atof( token ) != 0.0f ) - return true; - - char const *p = token; - while ( *p ) - { - if ( *p != '0' ) - return false; - - p++; - } - - return true; -} - -void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) -{ - const char *s = c->value; - if ( !s ) - { - matcher.valid = false; - return; - } - - const char *in = s; - - char token[ 128 ]; - char rawtoken[ 128 ]; - - token[ 0 ] = 0; - rawtoken[ 0 ] = 0; - - int n = 0; - - bool gt = false; - bool lt = false; - bool eq = false; - bool nt = false; - - bool done = false; - while ( !done ) - { - switch( *in ) - { - case '>': - { - gt = true; - Assert( !lt ); // Can't be both - } - break; - case '<': - { - lt = true; - Assert( !gt ); // Can't be both - } - break; - case '=': - { - eq = true; - } - break; - case ',': - case '\0': - { - rawtoken[ n ] = 0; - n = 0; - - // Convert raw token to real token in case token is an enumerated type specifier - ResolveToken( matcher, token, sizeof( token ), rawtoken ); - - // Fill in first data set - if ( gt ) - { - matcher.usemin = true; - matcher.minequals = eq; - matcher.minval = (float)atof( token ); - - matcher.isnumeric = true; - } - else if ( lt ) - { - matcher.usemax = true; - matcher.maxequals = eq; - matcher.maxval = (float)atof( token ); - - matcher.isnumeric = true; - } - else - { - if ( *in == ',' ) - { - // If there's a comma, this better have been a less than or a gt key - Assert( 0 ); - } - - matcher.notequal = nt; - - matcher.isnumeric = AppearsToBeANumber( token ); - } - - gt = lt = eq = nt = false; - - if ( !(*in) ) - { - done = true; - } - } - break; - case '!': - nt = true; - break; - default: - rawtoken[ n++ ] = *in; - break; - } - - in++; - } - - matcher.SetToken( token ); - matcher.SetRaw( rawtoken ); - matcher.valid = true; -} - -bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose /*=false*/ ) -{ - if ( !m.valid ) - return false; - - float v = (float)atof( setValue ); - if ( setValue[0] == '[' ) - { - bool found = false; - v = LookupEnumeration( setValue, found ); - } - - int minmaxcount = 0; - - if ( m.usemin ) - { - if ( m.minequals ) - { - if ( v < m.minval ) - return false; - } - else - { - if ( v <= m.minval ) - return false; - } - - ++minmaxcount; - } - - if ( m.usemax ) - { - if ( m.maxequals ) - { - if ( v > m.maxval ) - return false; - } - else - { - if ( v >= m.maxval ) - return false; - } - - ++minmaxcount; - } - - // Had one or both criteria and met them - if ( minmaxcount >= 1 ) - { - return true; - } - - if ( m.notequal ) - { - if ( m.isnumeric ) - { - if ( v == (float)atof( m.GetToken() ) ) - return false; - } - else - { - if ( !Q_stricmp( setValue, m.GetToken() ) ) - return false; - } - - return true; - } - - if ( m.isnumeric ) - { - // If the setValue is "", the NPC doesn't have the key at all, - // in which case we shouldn't match "0". - if ( !setValue || !setValue[0] ) - return false; - - return v == (float)atof( m.GetToken() ); - } - - return !Q_stricmp( setValue, m.GetToken() ) ? true : false; -} - -bool CResponseSystem::Compare( const char *setValue, Criteria *c, bool verbose /*= false*/ ) -{ - Assert( c ); - Assert( setValue ); - - bool bret = CompareUsingMatcher( setValue, c->matcher, verbose ); - - if ( verbose ) - { - DevMsg( "'%20s' vs. '%20s' = ", setValue, c->value ); - - { - //DevMsg( "\n" ); - //m.Describe(); - } - } - return bret; -} - -float CResponseSystem::RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ) -{ - float score = 0.0f; - int subcount = parent->subcriteria.Count(); - for ( int i = 0; i < subcount; i++ ) - { - int icriterion = parent->subcriteria[ i ]; - - bool excludesubrule = false; - if (verbose) - { - DevMsg( "\n" ); - } - score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, excludesubrule, verbose ); - } - - exclude = ( parent->required && score == 0.0f ) ? true : false; - - return score * parent->weight.GetFloat(); -} - -float CResponseSystem::ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose /*=false*/ ) -{ - Criteria *c = &m_Criteria[ icriterion ]; - - if ( c->IsSubCriteriaType() ) - { - return RecursiveScoreSubcriteriaAgainstRule( set, c, exclude, verbose ); - } - - if ( verbose ) - { - DevMsg( " criterion '%25s':'%15s' ", m_Criteria.GetElementName( icriterion ), c->name ); - } - - exclude = false; - - float score = 0.0f; - - const char *actualValue = ""; - - int found = set.FindCriterionIndex( c->name ); - if ( found != -1 ) - { - actualValue = set.GetValue( found ); - if ( !actualValue ) - { - Assert( 0 ); - return score; - } - } - - Assert( actualValue ); - - if ( Compare( actualValue, c, verbose ) ) - { - float w = set.GetWeight( found ); - score = w * c->weight.GetFloat(); - - if ( verbose ) - { - DevMsg( "matched, weight %4.2f (s %4.2f x c %4.2f)", - score, w, c->weight ); - } - } - else - { - if ( c->required ) - { - exclude = true; - if ( verbose ) - { - DevMsg( "failed (+exclude rule)" ); - } - } - else - { - if ( verbose ) - { - DevMsg( "failed" ); - } - } - } - - return score; -} - -float CResponseSystem::ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose /*=false*/ ) -{ - Rule *rule = &m_Rules[ irule ]; - float score = 0.0f; - - bool bBeingWatched = false; - - // See if we're trying to debug this rule - const char *pszText = rr_debugrule.GetString(); - if ( pszText && pszText[0] && !Q_stricmp( pszText, m_Rules.GetElementName( irule ) ) ) - { - bBeingWatched = true; - } - - if ( !rule->IsEnabled() ) - { - if ( bBeingWatched ) - { - DevMsg("Rule '%s' is disabled.\n" ); - } - return 0.0f; - } - - if ( bBeingWatched ) - { - verbose = true; - } - - if ( verbose ) - { - DevMsg( "Scoring rule '%s' (%i)\n{\n", m_Rules.GetElementName( irule ), irule+1 ); - } - - // Iterate set criteria - int count = rule->m_Criteria.Count(); - int i; - for ( i = 0; i < count; i++ ) - { - int icriterion = rule->m_Criteria[ i ]; - - bool exclude = false; - score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, exclude, verbose ); - - if ( verbose ) - { - DevMsg( ", score %4.2f\n", score ); - } - - if ( exclude ) - { - score = 0.0f; - break; - } - } - - if ( verbose ) - { - DevMsg( "}\n" ); - } - - return score; -} - -void CResponseSystem::DebugPrint( int depth, const char *fmt, ... ) -{ - int indentchars = 3 * depth; - char *indent = (char *)_alloca( indentchars + 1); - indent[ indentchars ] = 0; - while ( --indentchars >= 0 ) - { - indent[ indentchars ] = ' '; - } - - // Dump text to debugging console. - va_list argptr; - char szText[1024]; - - va_start (argptr, fmt); - Q_vsnprintf (szText, sizeof( szText ), fmt, argptr); - va_end (argptr); - - DevMsg( "%s%s", indent, szText ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::ResetResponseGroups() -{ - int i; - int c = m_Responses.Count(); - for ( i = 0; i < c; i++ ) - { - m_Responses[ i ].Reset(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *g - -// Output : int -//----------------------------------------------------------------------------- -int CResponseSystem::SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ) -{ - int c = g->group.Count(); - if ( !c ) - { - Assert( !"Expecting response group with >= 1 elements" ); - return -1; - } - - int i; - - // Fake depletion of unavailable choices - CUtlVector fakedDepletes; - if ( pFilter && g->ShouldCheckRepeats() ) - { - for ( i = 0; i < c; i++ ) - { - Response *r = &g->group[ i ]; - if ( r->depletioncount != g->GetDepletionCount() && !pFilter->IsValidResponse( r->GetType(), r->value ) ) - { - fakedDepletes.AddToTail( i ); - g->MarkResponseUsed( i ); - } - } - } - - if ( !g->HasUndepletedChoices() ) - { - g->ResetDepletionCount(); - - if ( pFilter && g->ShouldCheckRepeats() ) - { - fakedDepletes.RemoveAll(); - for ( i = 0; i < c; i++ ) - { - Response *r = &g->group[ i ]; - if ( !pFilter->IsValidResponse( r->GetType(), r->value ) ) - { - fakedDepletes.AddToTail( i ); - g->MarkResponseUsed( i ); - } - } - } - - if ( !g->HasUndepletedChoices() ) - return -1; - - // Disable the group if we looped through all the way - if ( g->IsNoRepeat() ) - { - g->SetEnabled( false ); - return -1; - } - } - - bool checkrepeats = g->ShouldCheckRepeats(); - int depletioncount = g->GetDepletionCount(); - - float totalweight = 0.0f; - int slot = -1; - - if ( checkrepeats ) - { - int check= -1; - // Snag the first slot right away - if ( g->HasUndepletedFirst( check ) && check != -1 ) - { - slot = check; - } - - if ( slot == -1 && g->HasUndepletedLast( check ) && check != -1 ) - { - // If this is the only undepleted one, use it now - for ( i = 0; i < c; i++ ) - { - Response *r = &g->group[ i ]; - if ( checkrepeats && - ( r->depletioncount == depletioncount ) ) - { - continue; - } - - if ( r->last ) - { - Assert( i == check ); - continue; - } - - // There's still another undepleted entry - break; - } - - // No more undepleted so use the r->last slot - if ( i >= c ) - { - slot = check; - } - } - } - - if ( slot == -1 ) - { - for ( i = 0; i < c; i++ ) - { - Response *r = &g->group[ i ]; - if ( checkrepeats && - ( r->depletioncount == depletioncount ) ) - { - continue; - } - - // Always skip last entry here since we will deal with it above - if ( checkrepeats && r->last ) - continue; - - int prevSlot = slot; - - if ( !totalweight ) - { - slot = i; - } - - // Always assume very first slot will match - totalweight += r->weight.GetFloat(); - if ( !totalweight || random->RandomFloat(0,totalweight) < r->weight.GetFloat() ) - { - slot = i; - } - - if ( !checkrepeats && slot != prevSlot && pFilter && !pFilter->IsValidResponse( r->GetType(), r->value ) ) - { - slot = prevSlot; - totalweight -= r->weight.GetFloat(); - } - } - } - - if ( slot != -1 ) - g->MarkResponseUsed( slot ); - - // Revert fake depletion of unavailable choices - if ( pFilter && g->ShouldCheckRepeats() ) - { - for ( i = 0; i < fakedDepletes.Count(); i++ ) - { - g->group[ fakedDepletes[ i ] ].depletioncount = 0;; - } - } - - return slot; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : searchResult - -// depth - -// *name - -// verbose - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CResponseSystem::ResolveResponse( ResponseSearchResult& searchResult, int depth, const char *name, bool verbose /*= false*/, IResponseFilter *pFilter ) -{ - int responseIndex = m_Responses.Find( name ); - if ( responseIndex == m_Responses.InvalidIndex() ) - return false; - - ResponseGroup *g = &m_Responses[ responseIndex ]; - // Group has been disabled - if ( !g->IsEnabled() ) - return false; - - int c = g->group.Count(); - if ( !c ) - return false; - - int idx = 0; - - if ( g->IsSequential() ) - { - // See if next index is valid - int initialIndex = g->GetCurrentIndex(); - bool bFoundValid = false; - - do - { - idx = g->GetCurrentIndex(); - g->SetCurrentIndex( idx + 1 ); - if ( idx >= c ) - { - if ( g->IsNoRepeat() ) - { - g->SetEnabled( false ); - return false; - } - idx = 0; - g->SetCurrentIndex( 0 ); - } - - if ( !pFilter || pFilter->IsValidResponse( g->group[idx].GetType(), g->group[idx].value ) ) - { - bFoundValid = true; - break; - } - - } while ( g->GetCurrentIndex() != initialIndex ); - - if ( !bFoundValid ) - return false; - } - else - { - idx = SelectWeightedResponseFromResponseGroup( g, pFilter ); - if ( idx < 0 ) - return false; - } - - if ( verbose ) - { - DebugPrint( depth, "%s\n", m_Responses.GetElementName( responseIndex ) ); - DebugPrint( depth, "{\n" ); - DescribeResponseGroup( g, idx, depth ); - } - - bool bret = true; - - Response *result = &g->group[ idx ]; - if ( result->type == RESPONSE_RESPONSE ) - { - // Recurse - bret = ResolveResponse( searchResult, depth + 1, result->value, verbose, pFilter ); - } - else - { - searchResult.action = result; - searchResult.group = g; - } - - if( verbose ) - { - DebugPrint( depth, "}\n" ); - } - - return bret; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *group - -// selected - -// depth - -//----------------------------------------------------------------------------- -void CResponseSystem::DescribeResponseGroup( ResponseGroup *group, int selected, int depth ) -{ - int c = group->group.Count(); - - for ( int i = 0; i < c ; i++ ) - { - Response *r = &group->group[ i ]; - DebugPrint( depth + 1, "%s%20s : %40s %5.3f\n", - i == selected ? "-> " : " ", - AI_Response::DescribeResponse( r->GetType() ), - r->value, - r->weight ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *rule - -// Output : CResponseSystem::Response -//----------------------------------------------------------------------------- -bool CResponseSystem::GetBestResponse( ResponseSearchResult& searchResult, Rule *rule, bool verbose /*=false*/, IResponseFilter *pFilter ) -{ - int c = rule->m_Responses.Count(); - if ( !c ) - return false; - - int index = random->RandomInt( 0, c - 1 ); - int groupIndex = rule->m_Responses[ index ]; - - ResponseGroup *g = &m_Responses[ groupIndex ]; - - // Group has been disabled - if ( !g->IsEnabled() ) - return false; - - int count = g->group.Count(); - if ( !count ) - return false; - - int responseIndex = 0; - - if ( g->IsSequential() ) - { - // See if next index is valid - int initialIndex = g->GetCurrentIndex(); - bool bFoundValid = false; - - do - { - responseIndex = g->GetCurrentIndex(); - g->SetCurrentIndex( responseIndex + 1 ); - if ( responseIndex >= count ) - { - if ( g->IsNoRepeat() ) - { - g->SetEnabled( false ); - return false; - } - responseIndex = 0; - g->SetCurrentIndex( 0 ); - } - - if ( !pFilter || pFilter->IsValidResponse( g->group[responseIndex].GetType(), g->group[responseIndex].value ) ) - { - bFoundValid = true; - break; - } - - } while ( g->GetCurrentIndex() != initialIndex ); - - if ( !bFoundValid ) - return false; - } - else - { - responseIndex = SelectWeightedResponseFromResponseGroup( g, pFilter ); - if ( responseIndex < 0 ) - return false; - } - - - Response *r = &g->group[ responseIndex ]; - - int depth = 0; - - if ( verbose ) - { - DebugPrint( depth, "%s\n", m_Responses.GetElementName( groupIndex ) ); - DebugPrint( depth, "{\n" ); - - DescribeResponseGroup( g, responseIndex, depth ); - } - - bool bret = true; - - if ( r->type == RESPONSE_RESPONSE ) - { - bret = ResolveResponse( searchResult, depth + 1, r->value, verbose, pFilter ); - } - else - { - searchResult.action = r; - searchResult.group = g; - } - - if ( verbose ) - { - DebugPrint( depth, "}\n" ); - } - - return bret; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : set - -// verbose - -// Output : int -//----------------------------------------------------------------------------- -int CResponseSystem::FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose ) -{ - CUtlVector< int > bestrules; - float bestscore = 0.001f; - - int c = m_Rules.Count(); - int i; - for ( i = 0; i < c; i++ ) - { - float score = ScoreCriteriaAgainstRule( set, i, verbose ); - // Check equals so that we keep track of all matching rules - if ( score >= bestscore ) - { - // Reset bucket - if( score != bestscore ) - { - bestscore = score; - bestrules.RemoveAll(); - } - - // Add to bucket - bestrules.AddToTail( i ); - } - } - - int bestCount = bestrules.Count(); - if ( bestCount <= 0 ) - return -1; - - if ( bestCount == 1 ) - return bestrules[ 0 ]; - - // Randomly pick one of the tied matching rules - int idx = random->RandomInt( 0, bestCount - 1 ); - if ( verbose ) - { - DevMsg( "Found %i matching rules, selecting slot %i\n", bestCount, idx ); - } - return bestrules[ idx ]; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : set - -// Output : AI_Response -//----------------------------------------------------------------------------- -bool CResponseSystem::FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter ) -{ - bool valid = false; - - int iDbgResponse = rr_debugresponses.GetInt(); - bool showRules = ( iDbgResponse == 2 ); - bool showResult = ( iDbgResponse == 1 || iDbgResponse == 2 ); - - // Look for match - int bestRule = FindBestMatchingRule( set, showRules ); - - ResponseType_t responseType = RESPONSE_NONE; - AI_ResponseParams rp; - - char ruleName[ 128 ]; - char responseName[ 128 ]; - const char *context; - ruleName[ 0 ] = 0; - responseName[ 0 ] = 0; - context = NULL; - if ( bestRule != -1 ) - { - Rule *r = &m_Rules[ bestRule ]; - - ResponseSearchResult result; - if ( GetBestResponse( result, r, showResult, pFilter ) ) - { - Q_strncpy( responseName, result.action->value, sizeof( responseName ) ); - responseType = result.action->GetType(); - rp = result.group->rp; - } - - Q_strncpy( ruleName, m_Rules.GetElementName( bestRule ), sizeof( ruleName ) ); - - // Disable the rule if it only allows for matching one time - if ( r->IsMatchOnce() ) - { - r->Disable(); - } - context = r->GetContext(); - - valid = true; - } - - response.Init( responseType, responseName, set, rp, ruleName, context ); - - if ( showResult ) - { - if ( valid ) - { - // Rescore the winner and dump to console - ScoreCriteriaAgainstRule( set, bestRule, true ); - } - - if ( valid || showRules ) - { - // Describe the response, too - response.Describe(); - } - } - - return valid; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CResponseSystem::GetAllResponses( CUtlVector *pResponses ) -{ - for ( int i = 0; i < (int)m_Responses.Count(); i++ ) - { - ResponseGroup &group = m_Responses[i]; - - for ( int j = 0; j < group.group.Count(); j++) - { - Response &response = group.group[j]; - if ( response.type != RESPONSE_RESPONSE ) - { - AI_Response *pResponse = new AI_Response; - pResponse->Init( response.GetType(), response.value, AI_CriteriaSet(), group.rp, NULL, NULL ); - pResponses->AddToTail(pResponse); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::Precache() -{ - // enumerate and mark all the scripts so we know they're referenced - for ( int i = 0; i < (int)m_Responses.Count(); i++ ) - { - ResponseGroup &group = m_Responses[i]; - - for ( int j = 0; j < group.group.Count(); j++) - { - Response &response = group.group[j]; - switch ( response.type ) - { - default: - break; - case RESPONSE_SCENE: - { - // fixup $gender references - char file[_MAX_PATH]; - Q_strncpy( file, response.value, sizeof(file) ); - char *gender = strstr( file, "$gender" ); - if ( gender ) - { - // replace with male & female - const char *postGender = gender + strlen("$gender"); - *gender = 0; - char genderFile[_MAX_PATH]; - // male - Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); - - PrecacheInstancedScene( genderFile ); - - Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); - - PrecacheInstancedScene( genderFile ); - } - else - { - PrecacheInstancedScene( file ); - } - } - break; - case RESPONSE_SPEAK: - { - CBaseEntity::PrecacheScriptSound( response.value ); - } - break; - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Does any necessary resource allocation for resources references in the script file -//----------------------------------------------------------------------------- -/* -I think this is obsolete in view of having to precache all of the scenes, etc. -void CResponseSystem::TouchReferencedScenes() -{ - if (CommandLine()->CheckParm("-makereslists")) - { - // enumerate and mark all the scripts so we know they're referenced - for ( int i = 0; i < (int)m_Responses.Count(); i++ ) - { - ResponseGroup &group = m_Responses[i]; - - for ( int j = 0; j < group.group.Count(); j++) - { - Response &response = group.group[j]; - if (response.type == RESPONSE_SCENE) - { - // fixup $gender references - char file[_MAX_PATH]; - Q_strncpy( file, response.value, sizeof(file) ); - char *gender = strstr( file, "$gender" ); - if ( gender ) - { - // replace with male & female - const char *postGender = gender + strlen("$gender"); - *gender = 0; - char genderFile[_MAX_PATH]; - // male - Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); - FileHandle_t f = filesystem->Open(genderFile, "rb"); - if (f) - { - filesystem->Close(f); - } - // female - Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); - f = filesystem->Open(genderFile, "rb"); - if (f) - { - filesystem->Close(f); - } - } - else - { - // just force the file open and closed so the filesystem can log it - FileHandle_t f = filesystem->Open(file, "rb"); - if (f) - { - filesystem->Close(f); - } - } - } - } - } - } -} -*/ - -void CResponseSystem::ParseInclude( CStringPool &includedFiles ) -{ - char includefile[ 256 ]; - ParseToken(); - Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); - - // check if the file is already included - if ( includedFiles.Find( includefile ) != NULL ) - { - return; - } - - // Try and load it - CUtlBuffer buf; - if ( !filesystem->ReadFile( includefile, "GAME", buf ) ) - { - DevMsg( "Unable to load #included script %s\n", includefile ); - return; - } - - LoadFromBuffer( includefile, (const char *)buf.PeekGet(), includedFiles ); -} - -void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles ) -{ - includedFiles.Allocate( scriptfile ); - PushScript( scriptfile, (unsigned char * )buffer ); - - if( rr_dumpresponses.GetBool() ) - { - DevMsg("Reading: %s\n", scriptfile ); - } - - while ( 1 ) - { - ParseToken(); - if ( !token[0] ) - { - break; - } - - if ( !Q_stricmp( token, "#include" ) ) - { - ParseInclude( includedFiles ); - } - else if ( !Q_stricmp( token, "response" ) ) - { - ParseResponse(); - } - else if ( !Q_stricmp( token, "criterion" ) || - !Q_stricmp( token, "criteria" ) ) - { - ParseCriterion(); - } - else if ( !Q_stricmp( token, "rule" ) ) - { - ParseRule(); - } - else if ( !Q_stricmp( token, "enumeration" ) ) - { - ParseEnumeration(); - } - else - { - int byteoffset = m_ScriptStack[ 0 ].currenttoken - (const char *)m_ScriptStack[ 0 ].buffer; - - Error( "CResponseSystem::LoadFromBuffer: Unknown entry type '%s', expecting 'response', 'criterion', 'enumeration' or 'rules' in file %s(offset:%i)\n", - token, scriptfile, byteoffset ); - break; - } - } - - if ( m_ScriptStack.Count() == 1 ) - { - char cur[ 256 ]; - GetCurrentScript( cur, sizeof( cur ) ); - DevMsg( 1, "CResponseSystem: %s (%i rules, %i criteria, and %i responses)\n", - cur, m_Rules.Count(), m_Criteria.Count(), m_Responses.Count() ); - - if( rr_dumpresponses.GetBool() ) - { - DumpRules(); - } - } - - PopScript(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::LoadRuleSet( const char *basescript ) -{ - int length = 0; - unsigned char *buffer = (unsigned char *)UTIL_LoadFileForMe( basescript, &length ); - if ( length <= 0 || !buffer ) - { - DevMsg( 1, "CResponseSystem: failed to load %s\n", basescript ); - return; - } - - CStringPool includedFiles; - - LoadFromBuffer( basescript, (const char *)buffer, includedFiles ); - - UTIL_FreeFile( buffer ); - - Assert( m_ScriptStack.Count() == 0 ); - - //TouchReferencedScenes(); -} - -static ResponseType_t ComputeResponseType( const char *s ) -{ - if ( !Q_stricmp( s, "scene" ) ) - { - return RESPONSE_SCENE; - } - else if ( !Q_stricmp( s, "sentence" ) ) - { - return RESPONSE_SENTENCE; - } - else if ( !Q_stricmp( s, "speak" ) ) - { - return RESPONSE_SPEAK; - } - else if ( !Q_stricmp( s, "response" ) ) - { - return RESPONSE_RESPONSE; - } - else if ( !Q_stricmp( s, "print" ) ) - { - return RESPONSE_PRINT; - } - - return RESPONSE_NONE; -} - -void CResponseSystem::ParseOneResponse( const char *responseGroupName, ResponseGroup& group ) -{ - Response newResponse; - newResponse.weight.SetFloat( 1.0f ); - AI_ResponseParams *rp = &group.rp; - - newResponse.type = ComputeResponseType( token ); - if ( RESPONSE_NONE == newResponse.type ) - { - ResponseWarning( "response entry '%s' with unknown response type '%s'\n", responseGroupName, token ); - return; - } - - ParseToken(); - newResponse.value = CopyString( token ); - - while ( TokenWaiting() ) - { - ParseToken(); - if ( !Q_stricmp( token, "weight" ) ) - { - ParseToken(); - newResponse.weight.SetFloat( (float)atof( token ) ); - continue; - } - - if ( !Q_stricmp( token, "nodelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.start = 0; - rp->delay.range = 0; - continue; - } - - if ( !Q_stricmp( token, "defaultdelay" ) ) - { - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.start = AIS_DEF_MIN_DELAY; - rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); - continue; - } - - if ( !Q_stricmp( token, "delay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "speakonce" ) ) - { - rp->flags |= AI_ResponseParams::RG_SPEAKONCE; - continue; - } - - if ( !Q_stricmp( token, "noscene" ) ) - { - rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; - continue; - } - - if ( !Q_stricmp( token, "stop_on_nonidle" ) ) - { - rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; - continue; - } - - if ( !Q_stricmp( token, "odds" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_ODDS; - rp->odds = clamp( atoi( token ), 0, 100 ); - continue; - } - - if ( !Q_stricmp( token, "respeakdelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; - rp->respeakdelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "weapondelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; - rp->weapondelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "soundlevel" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; - rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); - continue; - } - - if ( !Q_stricmp( token, "displayfirst" ) ) - { - newResponse.first = true; - group.m_bHasFirst = true; - continue; - } - - if ( !Q_stricmp( token, "displaylast" ) ) - { - newResponse.last = true; - group.m_bHasLast= true; - continue; - } - } - - group.group.AddToTail( newResponse ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CResponseSystem::IsRootCommand() -{ - if ( !Q_stricmp( token, "#include" ) ) - return true; - if ( !Q_stricmp( token, "response" ) ) - return true; - if ( !Q_stricmp( token, "enumeration" ) ) - return true; - if ( !Q_stricmp( token, "criteria" ) ) - return true; - if ( !Q_stricmp( token, "criterion" ) ) - return true; - if ( !Q_stricmp( token, "rule" ) ) - return true; - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *kv - -//----------------------------------------------------------------------------- -void CResponseSystem::ParseResponse( void ) -{ - // Should have groupname at start - char responseGroupName[ 128 ]; - - ResponseGroup newGroup; - AI_ResponseParams *rp = &newGroup.rp; - - // Response Group Name - ParseToken(); - Q_strncpy( responseGroupName, token, sizeof( responseGroupName ) ); - - while ( 1 ) - { - ParseToken(); - - // Oops, part of next definition - if( IsRootCommand() ) - { - Unget(); - break; - } - - if ( !Q_stricmp( token, "{" ) ) - { - while ( 1 ) - { - ParseToken(); - if ( !Q_stricmp( token, "}" ) ) - break; - - if ( !Q_stricmp( token, "permitrepeats" ) ) - { - newGroup.m_bDepleteBeforeRepeat = false; - continue; - } - else if ( !Q_stricmp( token, "sequential" ) ) - { - newGroup.SetSequential( true ); - continue; - } - else if ( !Q_stricmp( token, "norepeat" ) ) - { - newGroup.SetNoRepeat( true ); - continue; - } - - ParseOneResponse( responseGroupName, newGroup ); - } - break; - } - - if ( !Q_stricmp( token, "nodelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.start = 0; - rp->delay.range = 0; - continue; - } - - if ( !Q_stricmp( token, "defaultdelay" ) ) - { - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.start = AIS_DEF_MIN_DELAY; - rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); - continue; - } - - if ( !Q_stricmp( token, "delay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "speakonce" ) ) - { - rp->flags |= AI_ResponseParams::RG_SPEAKONCE; - continue; - } - - if ( !Q_stricmp( token, "noscene" ) ) - { - rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; - continue; - } - - if ( !Q_stricmp( token, "stop_on_nonidle" ) ) - { - rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; - continue; - } - - if ( !Q_stricmp( token, "odds" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_ODDS; - rp->odds = clamp( atoi( token ), 0, 100 ); - continue; - } - - if ( !Q_stricmp( token, "respeakdelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; - rp->respeakdelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "weapondelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; - rp->weapondelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "soundlevel" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; - rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); - continue; - } - - ParseOneResponse( responseGroupName, newGroup ); - } - - m_Responses.Insert( responseGroupName, newGroup ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *criterion - -//----------------------------------------------------------------------------- -int CResponseSystem::ParseOneCriterion( const char *criterionName ) -{ - char key[ 128 ]; - char value[ 128 ]; - - Criteria newCriterion; - - bool gotbody = false; - - while ( TokenWaiting() || !gotbody ) - { - ParseToken(); - - // Oops, part of next definition - if( IsRootCommand() ) - { - Unget(); - break; - } - - if ( !Q_stricmp( token, "{" ) ) - { - gotbody = true; - - while ( 1 ) - { - ParseToken(); - if ( !Q_stricmp( token, "}" ) ) - break; - - // Look up subcriteria index - int idx = m_Criteria.Find( token ); - if ( idx != m_Criteria.InvalidIndex() ) - { - newCriterion.subcriteria.AddToTail( idx ); - } - else - { - ResponseWarning( "Skipping unrecongized subcriterion '%s' in '%s'\n", token, criterionName ); - } - } - continue; - } - else if ( !Q_stricmp( token, "required" ) ) - { - newCriterion.required = true; - } - else if ( !Q_stricmp( token, "weight" ) ) - { - ParseToken(); - newCriterion.weight.SetFloat( (float)atof( token ) ); - } - else - { - Assert( newCriterion.subcriteria.Count() == 0 ); - - // Assume it's the math info for a non-subcriteria resposne - Q_strncpy( key, token, sizeof( key ) ); - ParseToken(); - Q_strncpy( value, token, sizeof( value ) ); - - newCriterion.name = CopyString( key ); - newCriterion.value = CopyString( value ); - - gotbody = true; - } - } - - if ( !newCriterion.IsSubCriteriaType() ) - { - ComputeMatcher( &newCriterion, newCriterion.matcher ); - } - - if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() ) - { - ResponseWarning( "Multiple definitions for criteria '%s'\n", criterionName ); - return m_Criteria.InvalidIndex(); - } - - int idx = m_Criteria.Insert( criterionName, newCriterion ); - return idx; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *kv - -//----------------------------------------------------------------------------- -void CResponseSystem::ParseCriterion( void ) -{ - // Should have groupname at start - char criterionName[ 128 ]; - ParseToken(); - Q_strncpy( criterionName, token, sizeof( criterionName ) ); - - ParseOneCriterion( criterionName ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *kv - -//----------------------------------------------------------------------------- -void CResponseSystem::ParseEnumeration( void ) -{ - char enumerationName[ 128 ]; - ParseToken(); - Q_strncpy( enumerationName, token, sizeof( enumerationName ) ); - - ParseToken(); - if ( Q_stricmp( token, "{" ) ) - { - ResponseWarning( "Expecting '{' in enumeration '%s', got '%s'\n", enumerationName, token ); - return; - } - - while ( 1 ) - { - ParseToken(); - if ( !Q_stricmp( token, "}" ) ) - break; - - if ( Q_strlen( token ) <= 0 ) - { - ResponseWarning( "Expecting more tokens in enumeration '%s'\n", enumerationName ); - break; - } - - char key[ 128 ]; - - Q_strncpy( key, token, sizeof( key ) ); - ParseToken(); - float value = (float)atof( token ); - - char sz[ 128 ]; - Q_snprintf( sz, sizeof( sz ), "[%s::%s]", enumerationName, key ); - Q_strlower( sz ); - - Enumeration newEnum; - newEnum.value = value; - - if ( m_Enumerations.Find( sz ) == m_Enumerations.InvalidIndex() ) - { - m_Enumerations.Insert( sz, newEnum ); - } - else - { - ResponseWarning( "Ignoring duplication enumeration '%s'\n", sz ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *kv - -//----------------------------------------------------------------------------- -void CResponseSystem::ParseRule( void ) -{ - static int instancedCriteria = 0; - - char ruleName[ 128 ]; - ParseToken(); - Q_strncpy( ruleName, token, sizeof( ruleName ) ); - - ParseToken(); - if ( Q_stricmp( token, "{" ) ) - { - ResponseWarning( "Expecting '{' in rule '%s', got '%s'\n", ruleName, token ); - return; - } - - // entries are "criteria", "response" or an in-line criteria to instance - Rule newRule; - - char sz[ 128 ]; - - bool validRule = true; - while ( 1 ) - { - ParseToken(); - if ( !Q_stricmp( token, "}" ) ) - { - break; - } - - if ( Q_strlen( token ) <= 0 ) - { - ResponseWarning( "Expecting more tokens in rule '%s'\n", ruleName ); - break; - } - - if ( !Q_stricmp( token, "matchonce" ) ) - { - newRule.m_bMatchOnce = true; - continue; - } - - if ( !Q_stricmp( token, "applyContext" ) ) - { - ParseToken(); - if ( newRule.GetContext() == NULL ) - { - newRule.SetContext( token ); - } - else - { - CFmtStrN<1024> newContext( "%s,%s", newRule.GetContext(), token ); - newRule.SetContext( newContext ); - } - continue; - } - - if ( !Q_stricmp( token, "response" ) ) - { - // Read them until we run out. - while ( TokenWaiting() ) - { - ParseToken(); - int idx = m_Responses.Find( token ); - if ( idx != m_Responses.InvalidIndex() ) - { - MEM_ALLOC_CREDIT(); - newRule.m_Responses.AddToTail( idx ); - } - else - { - validRule = false; - ResponseWarning( "No such response '%s' for rule '%s'\n", token, ruleName ); - } - } - continue; - } - - if ( !Q_stricmp( token, "criteria" ) || - !Q_stricmp( token, "criterion" ) ) - { - // Read them until we run out. - while ( TokenWaiting() ) - { - ParseToken(); - - int idx = m_Criteria.Find( token ); - if ( idx != m_Criteria.InvalidIndex() ) - { - MEM_ALLOC_CREDIT(); - newRule.m_Criteria.AddToTail( idx ); - } - else - { - validRule = false; - ResponseWarning( "No such criterion '%s' for rule '%s'\n", token, ruleName ); - } - } - continue; - } - - // It's an inline criteria, generate a name and parse it in - Q_snprintf( sz, sizeof( sz ), "[%s%03i]", ruleName, ++instancedCriteria ); - Unget(); - int idx = ParseOneCriterion( sz ); - if ( idx != m_Criteria.InvalidIndex() ) - { - newRule.m_Criteria.AddToTail( idx ); - } - } - - if ( validRule ) - { - m_Rules.Insert( ruleName, newRule ); - } - else - { - DevMsg( "Discarded rule %s\n", ruleName ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CResponseSystem::GetCurrentToken() const -{ - if ( m_ScriptStack.Count() <= 0 ) - return -1; - - return m_ScriptStack[ 0 ].tokencount; -} - - -void CResponseSystem::ResponseWarning( const char *fmt, ... ) -{ - va_list argptr; -#ifndef _XBOX - static char string[1024]; -#else - char string[1024]; -#endif - - va_start (argptr, fmt); - Q_vsnprintf(string, sizeof(string), fmt,argptr); - va_end (argptr); - - char cur[ 256 ]; - GetCurrentScript( cur, sizeof( cur ) ); - DevMsg( 1, "%s(token %i) : %s", cur, GetCurrentToken(), string ); -} - - -//----------------------------------------------------------------------------- -// Purpose: A special purpose response system associated with a custom entity -//----------------------------------------------------------------------------- -class CInstancedResponseSystem : public CResponseSystem -{ - typedef CResponseSystem BaseClass; - -public: - CInstancedResponseSystem( const char *scriptfile ) : - m_pszScriptFile( 0 ) - { - Assert( scriptfile ); - - int len = Q_strlen( scriptfile ) + 1; - m_pszScriptFile = new char[ len ]; - Assert( m_pszScriptFile ); - Q_strncpy( m_pszScriptFile, scriptfile, len ); - } - - ~CInstancedResponseSystem() - { - delete[] m_pszScriptFile; - } - virtual const char *GetScriptFile( void ) - { - Assert( m_pszScriptFile ); - return m_pszScriptFile; - } - - // CAutoGameSystem - virtual bool Init() - { - const char *basescript = GetScriptFile(); - LoadRuleSet( basescript ); - return true; - } - - virtual void LevelInitPostEntity() - { - ResetResponseGroups(); - } - - virtual void Release() - { - Clear(); - delete this; - } -private: - - char *m_pszScriptFile; -}; - -//----------------------------------------------------------------------------- -// Purpose: The default response system for expressive AIs -//----------------------------------------------------------------------------- -class CDefaultResponseSystem : public CResponseSystem, public CAutoGameSystem -{ - typedef CAutoGameSystem BaseClass; - -public: - CDefaultResponseSystem() : CAutoGameSystem( "CDefaultResponseSystem" ) - { - } - - virtual const char *GetScriptFile( void ) - { - return "scripts/talker/response_rules.txt"; - } - - // CAutoServerSystem - virtual bool Init(); - virtual void Shutdown(); - - virtual void LevelInitPostEntity() - { - } - - virtual void Release() - { - Assert( 0 ); - } - - void AddInstancedResponseSystem( const char *scriptfile, CInstancedResponseSystem *sys ) - { - m_InstancedSystems.Insert( scriptfile, sys ); - } - - CInstancedResponseSystem *FindResponseSystem( const char *scriptfile ) - { - int idx = m_InstancedSystems.Find( scriptfile ); - if ( idx == m_InstancedSystems.InvalidIndex() ) - return NULL; - return m_InstancedSystems[ idx ]; - } - - IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) - { - CInstancedResponseSystem *sys = ( CInstancedResponseSystem * )FindResponseSystem( scriptfile ); - if ( !sys ) - { - sys = new CInstancedResponseSystem( scriptfile ); - if ( !sys ) - { - Error( "Failed to load response system data from %s", scriptfile ); - } - - if ( !sys->Init() ) - { - Error( "CInstancedResponseSystem: Failed to init response system from %s!", scriptfile ); - } - - AddInstancedResponseSystem( scriptfile, sys ); - } - - sys->Precache(); - - return ( IResponseSystem * )sys; - } - - virtual void LevelInitPreEntity() - { - // This will precache the default system - // All user installed systems are init'd by PrecacheCustomResponseSystem which will call sys->Precache() on the ones being used - - // FIXME: This is SLOW the first time you run the engine (can take 3 - 10 seconds!!!) - if ( ShouldPrecache() ) - { - Precache(); - } - - ResetResponseGroups(); - } - - void ReloadAllResponseSystems() - { - Clear(); - Init(); - - int c = m_InstancedSystems.Count(); - for ( int i = c - 1 ; i >= 0; i-- ) - { - CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; - sys->Clear(); - sys->Init(); - } - - } - -private: - - void ClearInstanced() - { - int c = m_InstancedSystems.Count(); - for ( int i = c - 1 ; i >= 0; i-- ) - { - CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; - sys->Release(); - } - m_InstancedSystems.RemoveAll(); - } - - CUtlDict< CInstancedResponseSystem *, int > m_InstancedSystems; -}; - -static CDefaultResponseSystem defaultresponsesytem; -IResponseSystem *g_pResponseSystem = &defaultresponsesytem; - -CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." ) -{ - defaultresponsesytem.ReloadAllResponseSystems(); -} - -static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1; - -// note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed -// -class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler -{ -public: - const char *GetBlockName() - { - return "ResponseSystem"; - } - - void WriteSaveHeaders( ISave *pSave ) - { - pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION ); - } - - void ReadRestoreHeaders( IRestore *pRestore ) - { - // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. - short version; - pRestore->ReadShort( &version ); - m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION ); - } - - void Save( ISave *pSave ) - { - CDefaultResponseSystem& rs = defaultresponsesytem; - - int count = rs.m_Responses.Count(); - pSave->WriteInt( &count ); - for ( int i = 0; i < count; ++i ) - { - pSave->StartBlock( "ResponseGroup" ); - - pSave->WriteString( rs.m_Responses.GetElementName( i ) ); - const ResponseGroup *group = &rs.m_Responses[ i ]; - pSave->WriteAll( group ); - - short groupCount = group->group.Count(); - pSave->WriteShort( &groupCount ); - for ( int j = 0; j < groupCount; ++j ) - { - const Response *response = &group->group[ j ]; - pSave->StartBlock( "Response" ); - pSave->WriteString( response->value ); - pSave->WriteAll( response ); - pSave->EndBlock(); - } - - pSave->EndBlock(); - } - } - - void Restore( IRestore *pRestore, bool createPlayers ) - { - if ( !m_fDoLoad ) - return; - - CDefaultResponseSystem& rs = defaultresponsesytem; - - int count = pRestore->ReadInt(); - for ( int i = 0; i < count; ++i ) - { - char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; - pRestore->StartBlock( szResponseGroupBlockName ); - if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) - { - - char groupname[ 256 ]; - pRestore->ReadString( groupname, sizeof( groupname ), 0 ); - - // Try and find it - int idx = rs.m_Responses.Find( groupname ); - if ( idx != rs.m_Responses.InvalidIndex() ) - { - ResponseGroup *group = &rs.m_Responses[ idx ]; - pRestore->ReadAll( group ); - - short groupCount = pRestore->ReadShort(); - for ( int j = 0; j < groupCount; ++j ) - { - char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; - - char responsename[ 256 ]; - pRestore->StartBlock( szResponseBlockName ); - if ( !Q_stricmp( szResponseBlockName, "Response" ) ) - { - pRestore->ReadString( responsename, sizeof( responsename ), 0 ); - - // Find it by name - int ri; - for ( ri = 0; ri < group->group.Count(); ++ri ) - { - Response *response = &group->group[ ri ]; - if ( !Q_stricmp( response->value, responsename ) ) - { - break; - } - } - - if ( ri < group->group.Count() ) - { - Response *response = &group->group[ ri ]; - pRestore->ReadAll( response ); - } - } - - pRestore->EndBlock(); - } - } - } - - pRestore->EndBlock(); - } - } -private: - - bool m_fDoLoad; - -} g_DefaultResponseSystemSaveRestoreBlockHandler; - -ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler() -{ - return &g_DefaultResponseSystemSaveRestoreBlockHandler; -} - -//----------------------------------------------------------------------------- -// CResponseSystemSaveRestoreOps -// -// Purpose: Handles save and load for instanced response systems... -// -// BUGBUG: This will save the same response system to file multiple times for "shared" response systems and -// therefore it'll restore the same data onto the same pointer N times on reload (probably benign for now, but we could -// write code to save/restore the instanced ones by filename in the block handler above maybe? -//----------------------------------------------------------------------------- - -class CResponseSystemSaveRestoreOps : public CDefSaveRestoreOps -{ -public: - - virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) - { - CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; - if ( !pRS || pRS == &defaultresponsesytem ) - return; - - int count = pRS->m_Responses.Count(); - pSave->WriteInt( &count ); - for ( int i = 0; i < count; ++i ) - { - pSave->StartBlock( "ResponseGroup" ); - - pSave->WriteString( pRS->m_Responses.GetElementName( i ) ); - const ResponseGroup *group = &pRS->m_Responses[ i ]; - pSave->WriteAll( group ); - - short groupCount = group->group.Count(); - pSave->WriteShort( &groupCount ); - for ( int j = 0; j < groupCount; ++j ) - { - const Response *response = &group->group[ j ]; - pSave->StartBlock( "Response" ); - pSave->WriteString( response->value ); - pSave->WriteAll( response ); - pSave->EndBlock(); - } - - pSave->EndBlock(); - } - } - - virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) - { - CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; - if ( !pRS || pRS == &defaultresponsesytem ) - return; - - int count = pRestore->ReadInt(); - for ( int i = 0; i < count; ++i ) - { - char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; - pRestore->StartBlock( szResponseGroupBlockName ); - if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) - { - - char groupname[ 256 ]; - pRestore->ReadString( groupname, sizeof( groupname ), 0 ); - - // Try and find it - int idx = pRS->m_Responses.Find( groupname ); - if ( idx != pRS->m_Responses.InvalidIndex() ) - { - ResponseGroup *group = &pRS->m_Responses[ idx ]; - pRestore->ReadAll( group ); - - short groupCount = pRestore->ReadShort(); - for ( int j = 0; j < groupCount; ++j ) - { - char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; - - char responsename[ 256 ]; - pRestore->StartBlock( szResponseBlockName ); - if ( !Q_stricmp( szResponseBlockName, "Response" ) ) - { - pRestore->ReadString( responsename, sizeof( responsename ), 0 ); - - // Find it by name - int ri; - for ( ri = 0; ri < group->group.Count(); ++ri ) - { - Response *response = &group->group[ ri ]; - if ( !Q_stricmp( response->value, responsename ) ) - { - break; - } - } - - if ( ri < group->group.Count() ) - { - Response *response = &group->group[ ri ]; - pRestore->ReadAll( response ); - } - } - - pRestore->EndBlock(); - } - } - } - - pRestore->EndBlock(); - } - } - -} g_ResponseSystemSaveRestoreOps; - -ISaveRestoreOps *responseSystemSaveRestoreOps = &g_ResponseSystemSaveRestoreOps; - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CDefaultResponseSystem::Init() -{ -/* - Warning( "sizeof( Response ) == %d\n", sizeof( Response ) ); - Warning( "sizeof( ResponseGroup ) == %d\n", sizeof( ResponseGroup ) ); - Warning( "sizeof( Criteria ) == %d\n", sizeof( Criteria ) ); - Warning( "sizeof( AI_ResponseParams ) == %d\n", sizeof( AI_ResponseParams ) ); -*/ - const char *basescript = GetScriptFile(); - - LoadRuleSet( basescript ); - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDefaultResponseSystem::Shutdown() -{ - // Wipe instanced versions - ClearInstanced(); - - // Clear outselves - Clear(); - // IServerSystem chain - BaseClass::Shutdown(); -} - -//----------------------------------------------------------------------------- -// Purpose: Instance a custom response system -// Input : *scriptfile - -// Output : IResponseSystem -//----------------------------------------------------------------------------- -IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) -{ - return defaultresponsesytem.PrecacheCustomResponseSystem( scriptfile ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CResponseSystem::DumpRules() -{ - int c = m_Rules.Count(); - int i; - - for ( i = 0; i < c; i++ ) - { - Msg("%s\n", m_Rules.GetElementName( i ) ); - } -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "AI_ResponseSystem.h" +#include "igamesystem.h" +#include "AI_Criteria.h" +#include +#include "filesystem.h" +#include "utldict.h" +#include "ai_speech.h" +#include "vstdlib/ICommandLine.h" +#include +#include "sceneentity.h" +#include "isaverestore.h" +#include "utlbuffer.h" +#include "stringpool.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring). If set to 3, it will only show response success/failure for npc_selected NPCs." ); +ConVar rr_debugrule( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); +ConVar rr_dumpresponses( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); + +static CUtlSymbolTable g_RS; + +inline static char *CopyString( const char *in ) +{ + if ( !in ) + return NULL; + + int len = Q_strlen( in ); + char *out = new char[ len + 1 ]; + Q_memcpy( out, in, len ); + out[ len ] = 0; + return out; +} + +#pragma pack(1) +class Matcher +{ +public: + Matcher() + { + valid = false; + isnumeric = false; + notequal = false; + usemin = false; + minequals = false; + usemax = false; + maxequals = false; + maxval = 0.0f; + minval = 0.0f; + + token = UTL_INVAL_SYMBOL; + rawtoken = UTL_INVAL_SYMBOL; + } + + void Describe( void ) + { + if ( !valid ) + { + DevMsg( " invalid!\n" ); + return; + } + char sz[ 128 ]; + + sz[ 0] = 0; + int minmaxcount = 0; + if ( usemin ) + { + Q_snprintf( sz, sizeof( sz ), ">%s%.3f", minequals ? "=" : "", minval ); + minmaxcount++; + } + if ( usemax ) + { + char sz2[ 128 ]; + Q_snprintf( sz2, sizeof( sz2 ), "<%s%.3f", maxequals ? "=" : "", maxval ); + + if ( minmaxcount > 0 ) + { + Q_strncat( sz, " and ", sizeof( sz ), COPY_ALL_CHARACTERS ); + } + Q_strncat( sz, sz2, sizeof( sz ), COPY_ALL_CHARACTERS ); + minmaxcount++; + } + + if ( minmaxcount >= 1 ) + { + DevMsg( " matcher: %s\n", sz ); + return; + } + + if ( notequal ) + { + DevMsg( " matcher: !=%s\n", GetToken() ); + return; + } + + DevMsg( " matcher: ==%s\n", GetToken() ); + } + + float maxval; + float minval; + + bool valid : 1; //1 + bool isnumeric : 1; //2 + bool notequal : 1; //3 + bool usemin : 1; //4 + bool minequals : 1; //5 + bool usemax : 1; //6 + bool maxequals : 1; //7 + + void SetToken( char const *s ) + { + token = g_RS.AddString( s ); + } + + char const *GetToken() + { + if ( token.IsValid() ) + { + return g_RS.String( token ); + } + return ""; + } + void SetRaw( char const *raw ) + { + rawtoken = g_RS.AddString( raw ); + } + char const *GetRaw() + { + if ( rawtoken.IsValid() ) + { + return g_RS.String( rawtoken ); + } + return ""; + } + +private: + CUtlSymbol token; + CUtlSymbol rawtoken; +}; + +struct Response +{ + DECLARE_SIMPLE_DATADESC(); + + Response() + { + type = RESPONSE_NONE; + value = NULL; + weight.SetFloat( 1.0f ); + depletioncount = 0; + first = false; + last = false; + } + + Response( const Response& src ) + { + weight = src.weight; + type = src.type; + value = CopyString( src.value ); + depletioncount = src.depletioncount; + first = src.first; + last = src.last; + } + + Response& operator =( const Response& src ) + { + if ( this == &src ) + return *this; + weight = src.weight; + type = src.type; + value = CopyString( src.value ); + depletioncount = src.depletioncount; + first = src.first; + last = src.last; + return *this; + } + + ~Response() + { + delete[] value; + } + + ResponseType_t GetType() { return (ResponseType_t)type; } + + char *value; // fixed up value spot // 4 + float16 weight; // 6 + + byte depletioncount; // 7 + byte type : 6; // 8 + byte first : 1; // + byte last : 1; // +}; + +struct ResponseGroup +{ + DECLARE_SIMPLE_DATADESC(); + + ResponseGroup() + { + // By default visit all nodes before repeating + m_bSequential = false; + m_bNoRepeat = false; + m_bEnabled = true; + m_nCurrentIndex = 0; + m_bDepleteBeforeRepeat = true; + m_nDepletionCount = 1; + m_bHasFirst = false; + m_bHasLast = false; + } + + ResponseGroup( const ResponseGroup& src ) + { + int c = src.group.Count(); + for ( int i = 0; i < c; i++ ) + { + group.AddToTail( src.group[ i ] ); + } + + rp = src.rp; + m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; + m_nDepletionCount = src.m_nDepletionCount; + m_bHasFirst = src.m_bHasFirst; + m_bHasLast = src.m_bHasLast; + m_bSequential = src.m_bSequential; + m_bNoRepeat = src.m_bNoRepeat; + m_bEnabled = src.m_bEnabled; + m_nCurrentIndex = src.m_nCurrentIndex; + } + + ResponseGroup& operator=( const ResponseGroup& src ) + { + if ( this == &src ) + return *this; + int c = src.group.Count(); + for ( int i = 0; i < c; i++ ) + { + group.AddToTail( src.group[ i ] ); + } + + rp = src.rp; + m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; + m_nDepletionCount = src.m_nDepletionCount; + m_bHasFirst = src.m_bHasFirst; + m_bHasLast = src.m_bHasLast; + m_bSequential = src.m_bSequential; + m_bNoRepeat = src.m_bNoRepeat; + m_bEnabled = src.m_bEnabled; + m_nCurrentIndex = src.m_nCurrentIndex; + return *this; + } + + bool HasUndepletedChoices() const + { + if ( !m_bDepleteBeforeRepeat ) + return true; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + if ( group[ i ].depletioncount != m_nDepletionCount ) + return true; + } + + return false; + } + + void MarkResponseUsed( int idx ) + { + if ( !m_bDepleteBeforeRepeat ) + return; + + if ( idx < 0 || idx >= group.Count() ) + { + Assert( 0 ); + return; + } + + group[ idx ].depletioncount = m_nDepletionCount; + } + + void ResetDepletionCount() + { + if ( !m_bDepleteBeforeRepeat ) + return; + ++m_nDepletionCount; + } + + void Reset() + { + ResetDepletionCount(); + SetEnabled( true ); + SetCurrentIndex( 0 ); + m_nDepletionCount = 1; + + for ( int i = 0; i < group.Count(); ++i ) + { + group[ i ].depletioncount = 0; + } + } + + bool HasUndepletedFirst( int& index ) + { + index = -1; + + if ( !m_bDepleteBeforeRepeat ) + return false; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + Response *r = &group[ i ]; + + if ( ( r->depletioncount != m_nDepletionCount ) && r->first ) + { + index = i; + return true; + } + } + + return false; + } + + bool HasUndepletedLast( int& index ) + { + index = -1; + + if ( !m_bDepleteBeforeRepeat ) + return false; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + Response *r = &group[ i ]; + + if ( ( r->depletioncount != m_nDepletionCount ) && r->last ) + { + index = i; + return true; + } + } + + return false; + } + + bool ShouldCheckRepeats() const { return m_bDepleteBeforeRepeat; } + int GetDepletionCount() const { return m_nDepletionCount; } + + bool IsSequential() const { return m_bSequential; } + void SetSequential( bool seq ) { m_bSequential = seq; } + + bool IsNoRepeat() const { return m_bNoRepeat; } + void SetNoRepeat( bool norepeat ) { m_bNoRepeat = norepeat; } + + bool IsEnabled() const { return m_bEnabled; } + void SetEnabled( bool enabled ) { m_bEnabled = enabled; } + + int GetCurrentIndex() const { return m_nCurrentIndex; } + void SetCurrentIndex( byte idx ) { m_nCurrentIndex = idx; } + + CUtlVector< Response > group; + + AI_ResponseParams rp; + + bool m_bEnabled; + + byte m_nCurrentIndex; + // Invalidation counter + byte m_nDepletionCount; + + // Use all slots before repeating any + bool m_bDepleteBeforeRepeat : 1; + bool m_bHasFirst : 1; + bool m_bHasLast : 1; + bool m_bSequential : 1; + bool m_bNoRepeat : 1; + +}; + +struct Criteria +{ + Criteria() + { + name = NULL; + value = NULL; + weight.SetFloat( 1.0f ); + required = false; + } + Criteria& operator =(const Criteria& src ) + { + if ( this == &src ) + return *this; + + name = CopyString( src.name ); + value = CopyString( src.value ); + weight = src.weight; + required = src.required; + + matcher = src.matcher; + + int c = src.subcriteria.Count(); + for ( int i = 0; i < c; i++ ) + { + subcriteria.AddToTail( src.subcriteria[ i ] ); + } + + return *this; + } + Criteria(const Criteria& src ) + { + name = CopyString( src.name ); + value = CopyString( src.value ); + weight = src.weight; + required = src.required; + + matcher = src.matcher; + + int c = src.subcriteria.Count(); + for ( int i = 0; i < c; i++ ) + { + subcriteria.AddToTail( src.subcriteria[ i ] ); + } + } + ~Criteria() + { + delete[] name; + delete[] value; + } + + bool IsSubCriteriaType() const + { + return ( subcriteria.Count() > 0 ) ? true : false; + } + + char *name; + char *value; + float16 weight; + bool required; + + Matcher matcher; + + // Indices into sub criteria + CUtlVector< unsigned short > subcriteria; +}; + +struct Rule +{ + Rule() + { + m_bMatchOnce = false; + m_bEnabled = true; + m_szContext = NULL; + } + + Rule& operator =( const Rule& src ) + { + if ( this == &src ) + return *this; + + int i; + int c; + + c = src.m_Criteria.Count(); + for ( i = 0; i < c; i++ ) + { + m_Criteria.AddToTail( src.m_Criteria[ i ] ); + } + + c = src.m_Responses.Count(); + for ( i = 0; i < c; i++ ) + { + m_Responses.AddToTail( src.m_Responses[ i ] ); + } + + SetContext( src.m_szContext ); + m_bMatchOnce = src.m_bMatchOnce; + m_bEnabled = src.m_bEnabled; + return *this; + } + + Rule( const Rule& src ) + { + int i; + int c; + + c = src.m_Criteria.Count(); + for ( i = 0; i < c; i++ ) + { + m_Criteria.AddToTail( src.m_Criteria[ i ] ); + } + + c = src.m_Responses.Count(); + for ( i = 0; i < c; i++ ) + { + m_Responses.AddToTail( src.m_Responses[ i ] ); + } + + SetContext( src.m_szContext ); + m_bMatchOnce = src.m_bMatchOnce; + m_bEnabled = src.m_bEnabled; + } + + ~Rule() + { + delete[] m_szContext; + } + + void SetContext( const char *context ) + { + delete[] m_szContext; + m_szContext = CopyString( context ); + } + + const char *GetContext( void ) const { return m_szContext; } + + bool IsEnabled() const { return m_bEnabled; } + void Disable() { m_bEnabled = false; } + bool IsMatchOnce() const { return m_bMatchOnce; } + + // Indices into underlying criteria and response dictionaries + CUtlVector< unsigned short > m_Criteria; + CUtlVector< unsigned short> m_Responses; + + char *m_szContext; + + bool m_bMatchOnce : 1; + bool m_bEnabled : 1; +}; +#pragma pack() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +abstract_class CResponseSystem : public IResponseSystem +{ +public: + CResponseSystem(); + ~CResponseSystem(); + + // IResponseSystem + virtual bool FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter = NULL ); + virtual void GetAllResponses( CUtlVector *pResponses ); + + virtual void Release() = 0; + + virtual void DumpRules(); + + virtual void Precache(); + + virtual void PrecacheResponses( bool bEnable ) + { + m_bPrecache = bEnable; + } + + bool ShouldPrecache() { return m_bPrecache; } + + void Clear(); + +protected: + + virtual const char *GetScriptFile( void ) = 0; + void LoadRuleSet( const char *setname ); + + void ResetResponseGroups(); +public: + + + + +private: + + struct Enumeration + { + float value; + }; + + struct ResponseSearchResult + { + ResponseSearchResult() + { + group = NULL; + action = NULL; + } + + ResponseGroup *group; + Response *action; + }; + + inline bool ParseToken( void ) + { + if ( m_bUnget ) + { + m_bUnget = false; + return true; + } + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } + + inline void Unget() + { + m_bUnget = true; + } + + inline bool TokenWaiting( void ) + { + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + const char *p = m_ScriptStack[ 0 ].currenttoken; + + if ( !p ) + { + Error( "AI_ResponseSystem: Unxpected TokenWaiting() with NULL buffer in %s", m_ScriptStack[ 0 ].name ); + return false; + } + + + while ( *p && *p!='\n') + { + // Special handler for // comment blocks + if ( *p == '/' && *(p+1) == '/' ) + return false; + + if ( !isspace( *p ) || isalnum( *p ) ) + return true; + + p++; + } + + return false; + } + + void ParseOneResponse( const char *responseGroupName, ResponseGroup& group ); + + void ParseInclude( CStringPool &includedFiles ); + void ParseResponse( void ); + void ParseCriterion( void ); + void ParseRule( void ); + void ParseEnumeration( void ); + + int ParseOneCriterion( const char *criterionName ); + + bool Compare( const char *setValue, Criteria *c, bool verbose = false ); + bool CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose = false ); + void ComputeMatcher( Criteria *c, Matcher& matcher ); + void ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ); + float LookupEnumeration( const char *name, bool& found ); + + int FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose ); + + float ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose = false ); + float RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ); + float ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose = false ); + bool GetBestResponse( ResponseSearchResult& result, Rule *rule, bool verbose = false, IResponseFilter *pFilter = NULL ); + bool ResolveResponse( ResponseSearchResult& result, int depth, const char *name, bool verbose = false, IResponseFilter *pFilter = NULL ); + int SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ); + void DescribeResponseGroup( ResponseGroup *group, int selected, int depth ); + void DebugPrint( int depth, const char *fmt, ... ); + + void LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles ); + +// void TouchReferencedScenes(); + + void GetCurrentScript( char *buf, size_t buflen ); + int GetCurrentToken() const; + void SetCurrentScript( const char *script ); + bool IsRootCommand(); + + void PushScript( const char *scriptfile, unsigned char *buffer ); + void PopScript(void); + + void ResponseWarning( const char *fmt, ... ); + + CUtlDict< ResponseGroup, short > m_Responses; + CUtlDict< Criteria, short > m_Criteria; + CUtlDict< Rule, short > m_Rules; + CUtlDict< Enumeration, short > m_Enumerations; + + char token[ 1204 ]; + + bool m_bUnget; + bool m_bPrecache; + + struct ScriptEntry + { + unsigned char *buffer; + FileNameHandle_t name; + const char *currenttoken; + int tokencount; + }; + + CUtlVector< ScriptEntry > m_ScriptStack; + + friend class CDefaultResponseSystemSaveRestoreBlockHandler; + friend class CResponseSystemSaveRestoreOps; +}; + +BEGIN_SIMPLE_DATADESC( Response ) + // DEFINE_FIELD( type, FIELD_INTEGER ), + // DEFINE_ARRAY( value, FIELD_CHARACTER ), + // DEFINE_FIELD( weight, FIELD_FLOAT ), + DEFINE_FIELD( depletioncount, FIELD_CHARACTER ), + // DEFINE_FIELD( first, FIELD_BOOLEAN ), + // DEFINE_FIELD( last, FIELD_BOOLEAN ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( ResponseGroup ) + // DEFINE_FIELD( group, FIELD_UTLVECTOR ), + // DEFINE_FIELD( rp, FIELD_EMBEDDED ), + // DEFINE_FIELD( m_bDepleteBeforeRepeat, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nDepletionCount, FIELD_CHARACTER ), + // DEFINE_FIELD( m_bHasFirst, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bHasLast, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bSequential, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bNoRepeat, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nCurrentIndex, FIELD_CHARACTER ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResponseSystem::CResponseSystem() +{ + token[0] = 0; + m_bUnget = false; + m_bPrecache = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResponseSystem::~CResponseSystem() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CResponseSystem::GetCurrentScript( char *buf, size_t buflen ) +{ + Assert( buf ); + buf[ 0 ] = 0; + if ( m_ScriptStack.Count() <= 0 ) + return; + + if ( filesystem->String( m_ScriptStack[ 0 ].name, buf, buflen ) ) + { + return; + } + buf[ 0 ] = 0; +} + +void CResponseSystem::PushScript( const char *scriptfile, unsigned char *buffer ) +{ + ScriptEntry e; + e.name = filesystem->FindOrAddFileName( scriptfile ); + e.buffer = buffer; + e.currenttoken = (char *)e.buffer; + e.tokencount = 0; + m_ScriptStack.AddToHead( e ); +} + +void CResponseSystem::PopScript(void) +{ + Assert( m_ScriptStack.Count() >= 1 ); + if ( m_ScriptStack.Count() <= 0 ) + return; + + m_ScriptStack.Remove( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::Clear() +{ + m_Responses.RemoveAll(); + m_Criteria.RemoveAll(); + m_Rules.RemoveAll(); + m_Enumerations.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// found - +// Output : float +//----------------------------------------------------------------------------- +float CResponseSystem::LookupEnumeration( const char *name, bool& found ) +{ + int idx = m_Enumerations.Find( name ); + if ( idx == m_Enumerations.InvalidIndex() ) + { + found = false; + return 0.0f; + } + + + found = true; + return m_Enumerations[ idx ].value; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : matcher - +//----------------------------------------------------------------------------- +void CResponseSystem::ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ) +{ + if ( rawtoken[0] != '[' ) + { + Q_strncpy( token, rawtoken, bufsize ); + return; + } + + // Now lookup enumeration + bool found = false; + float f = LookupEnumeration( rawtoken, found ); + if ( !found ) + { + Q_strncpy( token, rawtoken, bufsize ); + ResponseWarning( "No such enumeration '%s'\n", token ); + return; + } + + Q_snprintf( token, bufsize, "%f", f ); +} + + +static bool AppearsToBeANumber( char const *token ) +{ + if ( atof( token ) != 0.0f ) + return true; + + char const *p = token; + while ( *p ) + { + if ( *p != '0' ) + return false; + + p++; + } + + return true; +} + +void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) +{ + const char *s = c->value; + if ( !s ) + { + matcher.valid = false; + return; + } + + const char *in = s; + + char token[ 128 ]; + char rawtoken[ 128 ]; + + token[ 0 ] = 0; + rawtoken[ 0 ] = 0; + + int n = 0; + + bool gt = false; + bool lt = false; + bool eq = false; + bool nt = false; + + bool done = false; + while ( !done ) + { + switch( *in ) + { + case '>': + { + gt = true; + Assert( !lt ); // Can't be both + } + break; + case '<': + { + lt = true; + Assert( !gt ); // Can't be both + } + break; + case '=': + { + eq = true; + } + break; + case ',': + case '\0': + { + rawtoken[ n ] = 0; + n = 0; + + // Convert raw token to real token in case token is an enumerated type specifier + ResolveToken( matcher, token, sizeof( token ), rawtoken ); + + // Fill in first data set + if ( gt ) + { + matcher.usemin = true; + matcher.minequals = eq; + matcher.minval = (float)atof( token ); + + matcher.isnumeric = true; + } + else if ( lt ) + { + matcher.usemax = true; + matcher.maxequals = eq; + matcher.maxval = (float)atof( token ); + + matcher.isnumeric = true; + } + else + { + if ( *in == ',' ) + { + // If there's a comma, this better have been a less than or a gt key + Assert( 0 ); + } + + matcher.notequal = nt; + + matcher.isnumeric = AppearsToBeANumber( token ); + } + + gt = lt = eq = nt = false; + + if ( !(*in) ) + { + done = true; + } + } + break; + case '!': + nt = true; + break; + default: + rawtoken[ n++ ] = *in; + break; + } + + in++; + } + + matcher.SetToken( token ); + matcher.SetRaw( rawtoken ); + matcher.valid = true; +} + +bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose /*=false*/ ) +{ + if ( !m.valid ) + return false; + + float v = (float)atof( setValue ); + if ( setValue[0] == '[' ) + { + bool found = false; + v = LookupEnumeration( setValue, found ); + } + + int minmaxcount = 0; + + if ( m.usemin ) + { + if ( m.minequals ) + { + if ( v < m.minval ) + return false; + } + else + { + if ( v <= m.minval ) + return false; + } + + ++minmaxcount; + } + + if ( m.usemax ) + { + if ( m.maxequals ) + { + if ( v > m.maxval ) + return false; + } + else + { + if ( v >= m.maxval ) + return false; + } + + ++minmaxcount; + } + + // Had one or both criteria and met them + if ( minmaxcount >= 1 ) + { + return true; + } + + if ( m.notequal ) + { + if ( m.isnumeric ) + { + if ( v == (float)atof( m.GetToken() ) ) + return false; + } + else + { + if ( !Q_stricmp( setValue, m.GetToken() ) ) + return false; + } + + return true; + } + + if ( m.isnumeric ) + { + // If the setValue is "", the NPC doesn't have the key at all, + // in which case we shouldn't match "0". + if ( !setValue || !setValue[0] ) + return false; + + return v == (float)atof( m.GetToken() ); + } + + return !Q_stricmp( setValue, m.GetToken() ) ? true : false; +} + +bool CResponseSystem::Compare( const char *setValue, Criteria *c, bool verbose /*= false*/ ) +{ + Assert( c ); + Assert( setValue ); + + bool bret = CompareUsingMatcher( setValue, c->matcher, verbose ); + + if ( verbose ) + { + DevMsg( "'%20s' vs. '%20s' = ", setValue, c->value ); + + { + //DevMsg( "\n" ); + //m.Describe(); + } + } + return bret; +} + +float CResponseSystem::RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ) +{ + float score = 0.0f; + int subcount = parent->subcriteria.Count(); + for ( int i = 0; i < subcount; i++ ) + { + int icriterion = parent->subcriteria[ i ]; + + bool excludesubrule = false; + if (verbose) + { + DevMsg( "\n" ); + } + score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, excludesubrule, verbose ); + } + + exclude = ( parent->required && score == 0.0f ) ? true : false; + + return score * parent->weight.GetFloat(); +} + +float CResponseSystem::ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose /*=false*/ ) +{ + Criteria *c = &m_Criteria[ icriterion ]; + + if ( c->IsSubCriteriaType() ) + { + return RecursiveScoreSubcriteriaAgainstRule( set, c, exclude, verbose ); + } + + if ( verbose ) + { + DevMsg( " criterion '%25s':'%15s' ", m_Criteria.GetElementName( icriterion ), c->name ); + } + + exclude = false; + + float score = 0.0f; + + const char *actualValue = ""; + + int found = set.FindCriterionIndex( c->name ); + if ( found != -1 ) + { + actualValue = set.GetValue( found ); + if ( !actualValue ) + { + Assert( 0 ); + return score; + } + } + + Assert( actualValue ); + + if ( Compare( actualValue, c, verbose ) ) + { + float w = set.GetWeight( found ); + score = w * c->weight.GetFloat(); + + if ( verbose ) + { + DevMsg( "matched, weight %4.2f (s %4.2f x c %4.2f)", + score, w, c->weight.GetFloat() ); + } + } + else + { + if ( c->required ) + { + exclude = true; + if ( verbose ) + { + DevMsg( "failed (+exclude rule)" ); + } + } + else + { + if ( verbose ) + { + DevMsg( "failed" ); + } + } + } + + return score; +} + +float CResponseSystem::ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose /*=false*/ ) +{ + Rule *rule = &m_Rules[ irule ]; + float score = 0.0f; + + bool bBeingWatched = false; + + // See if we're trying to debug this rule + const char *pszText = rr_debugrule.GetString(); + if ( pszText && pszText[0] && !Q_stricmp( pszText, m_Rules.GetElementName( irule ) ) ) + { + bBeingWatched = true; + } + + if ( !rule->IsEnabled() ) + { + if ( bBeingWatched ) + { + DevMsg("Rule '%s' is disabled.\n" ); + } + return 0.0f; + } + + if ( bBeingWatched ) + { + verbose = true; + } + + if ( verbose ) + { + DevMsg( "Scoring rule '%s' (%i)\n{\n", m_Rules.GetElementName( irule ), irule+1 ); + } + + // Iterate set criteria + int count = rule->m_Criteria.Count(); + int i; + for ( i = 0; i < count; i++ ) + { + int icriterion = rule->m_Criteria[ i ]; + + bool exclude = false; + score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, exclude, verbose ); + + if ( verbose ) + { + DevMsg( ", score %4.2f\n", score ); + } + + if ( exclude ) + { + score = 0.0f; + break; + } + } + + if ( verbose ) + { + DevMsg( "}\n" ); + } + + return score; +} + +void CResponseSystem::DebugPrint( int depth, const char *fmt, ... ) +{ + int indentchars = 3 * depth; + char *indent = (char *)_alloca( indentchars + 1); + indent[ indentchars ] = 0; + while ( --indentchars >= 0 ) + { + indent[ indentchars ] = ' '; + } + + // Dump text to debugging console. + va_list argptr; + char szText[1024]; + + va_start (argptr, fmt); + Q_vsnprintf (szText, sizeof( szText ), fmt, argptr); + va_end (argptr); + + DevMsg( "%s%s", indent, szText ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::ResetResponseGroups() +{ + int i; + int c = m_Responses.Count(); + for ( i = 0; i < c; i++ ) + { + m_Responses[ i ].Reset(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *g - +// Output : int +//----------------------------------------------------------------------------- +int CResponseSystem::SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ) +{ + int c = g->group.Count(); + if ( !c ) + { + Assert( !"Expecting response group with >= 1 elements" ); + return -1; + } + + int i; + + // Fake depletion of unavailable choices + CUtlVector fakedDepletes; + if ( pFilter && g->ShouldCheckRepeats() ) + { + for ( i = 0; i < c; i++ ) + { + Response *r = &g->group[ i ]; + if ( r->depletioncount != g->GetDepletionCount() && !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + fakedDepletes.AddToTail( i ); + g->MarkResponseUsed( i ); + } + } + } + + if ( !g->HasUndepletedChoices() ) + { + g->ResetDepletionCount(); + + if ( pFilter && g->ShouldCheckRepeats() ) + { + fakedDepletes.RemoveAll(); + for ( i = 0; i < c; i++ ) + { + Response *r = &g->group[ i ]; + if ( !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + fakedDepletes.AddToTail( i ); + g->MarkResponseUsed( i ); + } + } + } + + if ( !g->HasUndepletedChoices() ) + return -1; + + // Disable the group if we looped through all the way + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); + return -1; + } + } + + bool checkrepeats = g->ShouldCheckRepeats(); + int depletioncount = g->GetDepletionCount(); + + float totalweight = 0.0f; + int slot = -1; + + if ( checkrepeats ) + { + int check= -1; + // Snag the first slot right away + if ( g->HasUndepletedFirst( check ) && check != -1 ) + { + slot = check; + } + + if ( slot == -1 && g->HasUndepletedLast( check ) && check != -1 ) + { + // If this is the only undepleted one, use it now + for ( i = 0; i < c; i++ ) + { + Response *r = &g->group[ i ]; + if ( checkrepeats && + ( r->depletioncount == depletioncount ) ) + { + continue; + } + + if ( r->last ) + { + Assert( i == check ); + continue; + } + + // There's still another undepleted entry + break; + } + + // No more undepleted so use the r->last slot + if ( i >= c ) + { + slot = check; + } + } + } + + if ( slot == -1 ) + { + for ( i = 0; i < c; i++ ) + { + Response *r = &g->group[ i ]; + if ( checkrepeats && + ( r->depletioncount == depletioncount ) ) + { + continue; + } + + // Always skip last entry here since we will deal with it above + if ( checkrepeats && r->last ) + continue; + + int prevSlot = slot; + + if ( !totalweight ) + { + slot = i; + } + + // Always assume very first slot will match + totalweight += r->weight.GetFloat(); + if ( !totalweight || random->RandomFloat(0,totalweight) < r->weight.GetFloat() ) + { + slot = i; + } + + if ( !checkrepeats && slot != prevSlot && pFilter && !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + slot = prevSlot; + totalweight -= r->weight.GetFloat(); + } + } + } + + if ( slot != -1 ) + g->MarkResponseUsed( slot ); + + // Revert fake depletion of unavailable choices + if ( pFilter && g->ShouldCheckRepeats() ) + { + for ( i = 0; i < fakedDepletes.Count(); i++ ) + { + g->group[ fakedDepletes[ i ] ].depletioncount = 0;; + } + } + + return slot; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : searchResult - +// depth - +// *name - +// verbose - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CResponseSystem::ResolveResponse( ResponseSearchResult& searchResult, int depth, const char *name, bool verbose /*= false*/, IResponseFilter *pFilter ) +{ + int responseIndex = m_Responses.Find( name ); + if ( responseIndex == m_Responses.InvalidIndex() ) + return false; + + ResponseGroup *g = &m_Responses[ responseIndex ]; + // Group has been disabled + if ( !g->IsEnabled() ) + return false; + + int c = g->group.Count(); + if ( !c ) + return false; + + int idx = 0; + + if ( g->IsSequential() ) + { + // See if next index is valid + int initialIndex = g->GetCurrentIndex(); + bool bFoundValid = false; + + do + { + idx = g->GetCurrentIndex(); + g->SetCurrentIndex( idx + 1 ); + if ( idx >= c ) + { + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); + return false; + } + idx = 0; + g->SetCurrentIndex( 0 ); + } + + if ( !pFilter || pFilter->IsValidResponse( g->group[idx].GetType(), g->group[idx].value ) ) + { + bFoundValid = true; + break; + } + + } while ( g->GetCurrentIndex() != initialIndex ); + + if ( !bFoundValid ) + return false; + } + else + { + idx = SelectWeightedResponseFromResponseGroup( g, pFilter ); + if ( idx < 0 ) + return false; + } + + if ( verbose ) + { + DebugPrint( depth, "%s\n", m_Responses.GetElementName( responseIndex ) ); + DebugPrint( depth, "{\n" ); + DescribeResponseGroup( g, idx, depth ); + } + + bool bret = true; + + Response *result = &g->group[ idx ]; + if ( result->type == RESPONSE_RESPONSE ) + { + // Recurse + bret = ResolveResponse( searchResult, depth + 1, result->value, verbose, pFilter ); + } + else + { + searchResult.action = result; + searchResult.group = g; + } + + if( verbose ) + { + DebugPrint( depth, "}\n" ); + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *group - +// selected - +// depth - +//----------------------------------------------------------------------------- +void CResponseSystem::DescribeResponseGroup( ResponseGroup *group, int selected, int depth ) +{ + int c = group->group.Count(); + + for ( int i = 0; i < c ; i++ ) + { + Response *r = &group->group[ i ]; + DebugPrint( depth + 1, "%s%20s : %40s %5.3f\n", + i == selected ? "-> " : " ", + AI_Response::DescribeResponse( r->GetType() ), + r->value, + r->weight.GetFloat() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *rule - +// Output : CResponseSystem::Response +//----------------------------------------------------------------------------- +bool CResponseSystem::GetBestResponse( ResponseSearchResult& searchResult, Rule *rule, bool verbose /*=false*/, IResponseFilter *pFilter ) +{ + int c = rule->m_Responses.Count(); + if ( !c ) + return false; + + int index = random->RandomInt( 0, c - 1 ); + int groupIndex = rule->m_Responses[ index ]; + + ResponseGroup *g = &m_Responses[ groupIndex ]; + + // Group has been disabled + if ( !g->IsEnabled() ) + return false; + + int count = g->group.Count(); + if ( !count ) + return false; + + int responseIndex = 0; + + if ( g->IsSequential() ) + { + // See if next index is valid + int initialIndex = g->GetCurrentIndex(); + bool bFoundValid = false; + + do + { + responseIndex = g->GetCurrentIndex(); + g->SetCurrentIndex( responseIndex + 1 ); + if ( responseIndex >= count ) + { + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); + return false; + } + responseIndex = 0; + g->SetCurrentIndex( 0 ); + } + + if ( !pFilter || pFilter->IsValidResponse( g->group[responseIndex].GetType(), g->group[responseIndex].value ) ) + { + bFoundValid = true; + break; + } + + } while ( g->GetCurrentIndex() != initialIndex ); + + if ( !bFoundValid ) + return false; + } + else + { + responseIndex = SelectWeightedResponseFromResponseGroup( g, pFilter ); + if ( responseIndex < 0 ) + return false; + } + + + Response *r = &g->group[ responseIndex ]; + + int depth = 0; + + if ( verbose ) + { + DebugPrint( depth, "%s\n", m_Responses.GetElementName( groupIndex ) ); + DebugPrint( depth, "{\n" ); + + DescribeResponseGroup( g, responseIndex, depth ); + } + + bool bret = true; + + if ( r->type == RESPONSE_RESPONSE ) + { + bret = ResolveResponse( searchResult, depth + 1, r->value, verbose, pFilter ); + } + else + { + searchResult.action = r; + searchResult.group = g; + } + + if ( verbose ) + { + DebugPrint( depth, "}\n" ); + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// verbose - +// Output : int +//----------------------------------------------------------------------------- +int CResponseSystem::FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose ) +{ + CUtlVector< int > bestrules; + float bestscore = 0.001f; + + int c = m_Rules.Count(); + int i; + for ( i = 0; i < c; i++ ) + { + float score = ScoreCriteriaAgainstRule( set, i, verbose ); + // Check equals so that we keep track of all matching rules + if ( score >= bestscore ) + { + // Reset bucket + if( score != bestscore ) + { + bestscore = score; + bestrules.RemoveAll(); + } + + // Add to bucket + bestrules.AddToTail( i ); + } + } + + int bestCount = bestrules.Count(); + if ( bestCount <= 0 ) + return -1; + + if ( bestCount == 1 ) + return bestrules[ 0 ]; + + // Randomly pick one of the tied matching rules + int idx = random->RandomInt( 0, bestCount - 1 ); + if ( verbose ) + { + DevMsg( "Found %i matching rules, selecting slot %i\n", bestCount, idx ); + } + return bestrules[ idx ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// Output : AI_Response +//----------------------------------------------------------------------------- +bool CResponseSystem::FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter ) +{ + bool valid = false; + + int iDbgResponse = rr_debugresponses.GetInt(); + bool showRules = ( iDbgResponse == 2 ); + bool showResult = ( iDbgResponse == 1 || iDbgResponse == 2 ); + + // Look for match + int bestRule = FindBestMatchingRule( set, showRules ); + + ResponseType_t responseType = RESPONSE_NONE; + AI_ResponseParams rp; + + char ruleName[ 128 ]; + char responseName[ 128 ]; + const char *context; + ruleName[ 0 ] = 0; + responseName[ 0 ] = 0; + context = NULL; + if ( bestRule != -1 ) + { + Rule *r = &m_Rules[ bestRule ]; + + ResponseSearchResult result; + if ( GetBestResponse( result, r, showResult, pFilter ) ) + { + Q_strncpy( responseName, result.action->value, sizeof( responseName ) ); + responseType = result.action->GetType(); + rp = result.group->rp; + } + + Q_strncpy( ruleName, m_Rules.GetElementName( bestRule ), sizeof( ruleName ) ); + + // Disable the rule if it only allows for matching one time + if ( r->IsMatchOnce() ) + { + r->Disable(); + } + context = r->GetContext(); + + valid = true; + } + + response.Init( responseType, responseName, set, rp, ruleName, context ); + + if ( showResult ) + { + if ( valid ) + { + // Rescore the winner and dump to console + ScoreCriteriaAgainstRule( set, bestRule, true ); + } + + if ( valid || showRules ) + { + // Describe the response, too + response.Describe(); + } + } + + return valid; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::GetAllResponses( CUtlVector *pResponses ) +{ + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + Response &response = group.group[j]; + if ( response.type != RESPONSE_RESPONSE ) + { + AI_Response *pResponse = new AI_Response; + pResponse->Init( response.GetType(), response.value, AI_CriteriaSet(), group.rp, NULL, NULL ); + pResponses->AddToTail(pResponse); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::Precache() +{ + // enumerate and mark all the scripts so we know they're referenced + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + Response &response = group.group[j]; + switch ( response.type ) + { + default: + break; + case RESPONSE_SCENE: + { + // fixup $gender references + char file[_MAX_PATH]; + Q_strncpy( file, response.value, sizeof(file) ); + char *gender = strstr( file, "$gender" ); + if ( gender ) + { + // replace with male & female + const char *postGender = gender + strlen("$gender"); + *gender = 0; + char genderFile[_MAX_PATH]; + // male + Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); + + PrecacheInstancedScene( genderFile ); + + Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); + + PrecacheInstancedScene( genderFile ); + } + else + { + PrecacheInstancedScene( file ); + } + } + break; + case RESPONSE_SPEAK: + { + CBaseEntity::PrecacheScriptSound( response.value ); + } + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Does any necessary resource allocation for resources references in the script file +//----------------------------------------------------------------------------- +/* +I think this is obsolete in view of having to precache all of the scenes, etc. +void CResponseSystem::TouchReferencedScenes() +{ + if (CommandLine()->CheckParm("-makereslists")) + { + // enumerate and mark all the scripts so we know they're referenced + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + Response &response = group.group[j]; + if (response.type == RESPONSE_SCENE) + { + // fixup $gender references + char file[_MAX_PATH]; + Q_strncpy( file, response.value, sizeof(file) ); + char *gender = strstr( file, "$gender" ); + if ( gender ) + { + // replace with male & female + const char *postGender = gender + strlen("$gender"); + *gender = 0; + char genderFile[_MAX_PATH]; + // male + Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); + FileHandle_t f = filesystem->Open(genderFile, "rb"); + if (f) + { + filesystem->Close(f); + } + // female + Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); + f = filesystem->Open(genderFile, "rb"); + if (f) + { + filesystem->Close(f); + } + } + else + { + // just force the file open and closed so the filesystem can log it + FileHandle_t f = filesystem->Open(file, "rb"); + if (f) + { + filesystem->Close(f); + } + } + } + } + } + } +} +*/ + +void CResponseSystem::ParseInclude( CStringPool &includedFiles ) +{ + char includefile[ 256 ]; + ParseToken(); + Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); + + // check if the file is already included + if ( includedFiles.Find( includefile ) != NULL ) + { + return; + } + + // Try and load it + CUtlBuffer buf; + if ( !filesystem->ReadFile( includefile, "GAME", buf ) ) + { + DevMsg( "Unable to load #included script %s\n", includefile ); + return; + } + + LoadFromBuffer( includefile, (const char *)buf.PeekGet(), includedFiles ); +} + +void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles ) +{ + includedFiles.Allocate( scriptfile ); + PushScript( scriptfile, (unsigned char * )buffer ); + + if( rr_dumpresponses.GetBool() ) + { + DevMsg("Reading: %s\n", scriptfile ); + } + + while ( 1 ) + { + ParseToken(); + if ( !token[0] ) + { + break; + } + + if ( !Q_stricmp( token, "#include" ) ) + { + ParseInclude( includedFiles ); + } + else if ( !Q_stricmp( token, "response" ) ) + { + ParseResponse(); + } + else if ( !Q_stricmp( token, "criterion" ) || + !Q_stricmp( token, "criteria" ) ) + { + ParseCriterion(); + } + else if ( !Q_stricmp( token, "rule" ) ) + { + ParseRule(); + } + else if ( !Q_stricmp( token, "enumeration" ) ) + { + ParseEnumeration(); + } + else + { + int byteoffset = m_ScriptStack[ 0 ].currenttoken - (const char *)m_ScriptStack[ 0 ].buffer; + + Error( "CResponseSystem::LoadFromBuffer: Unknown entry type '%s', expecting 'response', 'criterion', 'enumeration' or 'rules' in file %s(offset:%i)\n", + token, scriptfile, byteoffset ); + break; + } + } + + if ( m_ScriptStack.Count() == 1 ) + { + char cur[ 256 ]; + GetCurrentScript( cur, sizeof( cur ) ); + DevMsg( 1, "CResponseSystem: %s (%i rules, %i criteria, and %i responses)\n", + cur, m_Rules.Count(), m_Criteria.Count(), m_Responses.Count() ); + + if( rr_dumpresponses.GetBool() ) + { + DumpRules(); + } + } + + PopScript(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::LoadRuleSet( const char *basescript ) +{ + int length = 0; + unsigned char *buffer = (unsigned char *)UTIL_LoadFileForMe( basescript, &length ); + if ( length <= 0 || !buffer ) + { + DevMsg( 1, "CResponseSystem: failed to load %s\n", basescript ); + return; + } + + CStringPool includedFiles; + + LoadFromBuffer( basescript, (const char *)buffer, includedFiles ); + + UTIL_FreeFile( buffer ); + + Assert( m_ScriptStack.Count() == 0 ); + + //TouchReferencedScenes(); +} + +static ResponseType_t ComputeResponseType( const char *s ) +{ + if ( !Q_stricmp( s, "scene" ) ) + { + return RESPONSE_SCENE; + } + else if ( !Q_stricmp( s, "sentence" ) ) + { + return RESPONSE_SENTENCE; + } + else if ( !Q_stricmp( s, "speak" ) ) + { + return RESPONSE_SPEAK; + } + else if ( !Q_stricmp( s, "response" ) ) + { + return RESPONSE_RESPONSE; + } + else if ( !Q_stricmp( s, "print" ) ) + { + return RESPONSE_PRINT; + } + + return RESPONSE_NONE; +} + +void CResponseSystem::ParseOneResponse( const char *responseGroupName, ResponseGroup& group ) +{ + Response newResponse; + newResponse.weight.SetFloat( 1.0f ); + AI_ResponseParams *rp = &group.rp; + + newResponse.type = ComputeResponseType( token ); + if ( RESPONSE_NONE == newResponse.type ) + { + ResponseWarning( "response entry '%s' with unknown response type '%s'\n", responseGroupName, token ); + return; + } + + ParseToken(); + newResponse.value = CopyString( token ); + + while ( TokenWaiting() ) + { + ParseToken(); + if ( !Q_stricmp( token, "weight" ) ) + { + ParseToken(); + newResponse.weight.SetFloat( (float)atof( token ) ); + continue; + } + + if ( !Q_stricmp( token, "nodelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = 0; + rp->delay.range = 0; + continue; + } + + if ( !Q_stricmp( token, "defaultdelay" ) ) + { + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = AIS_DEF_MIN_DELAY; + rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); + continue; + } + + if ( !Q_stricmp( token, "delay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "speakonce" ) ) + { + rp->flags |= AI_ResponseParams::RG_SPEAKONCE; + continue; + } + + if ( !Q_stricmp( token, "noscene" ) ) + { + rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; + continue; + } + + if ( !Q_stricmp( token, "stop_on_nonidle" ) ) + { + rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; + continue; + } + + if ( !Q_stricmp( token, "odds" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_ODDS; + rp->odds = clamp( atoi( token ), 0, 100 ); + continue; + } + + if ( !Q_stricmp( token, "respeakdelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; + rp->respeakdelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "weapondelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; + rp->weapondelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "soundlevel" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; + rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); + continue; + } + + if ( !Q_stricmp( token, "displayfirst" ) ) + { + newResponse.first = true; + group.m_bHasFirst = true; + continue; + } + + if ( !Q_stricmp( token, "displaylast" ) ) + { + newResponse.last = true; + group.m_bHasLast= true; + continue; + } + } + + group.group.AddToTail( newResponse ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CResponseSystem::IsRootCommand() +{ + if ( !Q_stricmp( token, "#include" ) ) + return true; + if ( !Q_stricmp( token, "response" ) ) + return true; + if ( !Q_stricmp( token, "enumeration" ) ) + return true; + if ( !Q_stricmp( token, "criteria" ) ) + return true; + if ( !Q_stricmp( token, "criterion" ) ) + return true; + if ( !Q_stricmp( token, "rule" ) ) + return true; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseResponse( void ) +{ + // Should have groupname at start + char responseGroupName[ 128 ]; + + ResponseGroup newGroup; + AI_ResponseParams *rp = &newGroup.rp; + + // Response Group Name + ParseToken(); + Q_strncpy( responseGroupName, token, sizeof( responseGroupName ) ); + + while ( 1 ) + { + ParseToken(); + + // Oops, part of next definition + if( IsRootCommand() ) + { + Unget(); + break; + } + + if ( !Q_stricmp( token, "{" ) ) + { + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + if ( !Q_stricmp( token, "permitrepeats" ) ) + { + newGroup.m_bDepleteBeforeRepeat = false; + continue; + } + else if ( !Q_stricmp( token, "sequential" ) ) + { + newGroup.SetSequential( true ); + continue; + } + else if ( !Q_stricmp( token, "norepeat" ) ) + { + newGroup.SetNoRepeat( true ); + continue; + } + + ParseOneResponse( responseGroupName, newGroup ); + } + break; + } + + if ( !Q_stricmp( token, "nodelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = 0; + rp->delay.range = 0; + continue; + } + + if ( !Q_stricmp( token, "defaultdelay" ) ) + { + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = AIS_DEF_MIN_DELAY; + rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); + continue; + } + + if ( !Q_stricmp( token, "delay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "speakonce" ) ) + { + rp->flags |= AI_ResponseParams::RG_SPEAKONCE; + continue; + } + + if ( !Q_stricmp( token, "noscene" ) ) + { + rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; + continue; + } + + if ( !Q_stricmp( token, "stop_on_nonidle" ) ) + { + rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; + continue; + } + + if ( !Q_stricmp( token, "odds" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_ODDS; + rp->odds = clamp( atoi( token ), 0, 100 ); + continue; + } + + if ( !Q_stricmp( token, "respeakdelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; + rp->respeakdelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "weapondelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; + rp->weapondelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "soundlevel" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; + rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); + continue; + } + + ParseOneResponse( responseGroupName, newGroup ); + } + + m_Responses.Insert( responseGroupName, newGroup ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *criterion - +//----------------------------------------------------------------------------- +int CResponseSystem::ParseOneCriterion( const char *criterionName ) +{ + char key[ 128 ]; + char value[ 128 ]; + + Criteria newCriterion; + + bool gotbody = false; + + while ( TokenWaiting() || !gotbody ) + { + ParseToken(); + + // Oops, part of next definition + if( IsRootCommand() ) + { + Unget(); + break; + } + + if ( !Q_stricmp( token, "{" ) ) + { + gotbody = true; + + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + // Look up subcriteria index + int idx = m_Criteria.Find( token ); + if ( idx != m_Criteria.InvalidIndex() ) + { + newCriterion.subcriteria.AddToTail( idx ); + } + else + { + ResponseWarning( "Skipping unrecongized subcriterion '%s' in '%s'\n", token, criterionName ); + } + } + continue; + } + else if ( !Q_stricmp( token, "required" ) ) + { + newCriterion.required = true; + } + else if ( !Q_stricmp( token, "weight" ) ) + { + ParseToken(); + newCriterion.weight.SetFloat( (float)atof( token ) ); + } + else + { + Assert( newCriterion.subcriteria.Count() == 0 ); + + // Assume it's the math info for a non-subcriteria resposne + Q_strncpy( key, token, sizeof( key ) ); + ParseToken(); + Q_strncpy( value, token, sizeof( value ) ); + + newCriterion.name = CopyString( key ); + newCriterion.value = CopyString( value ); + + gotbody = true; + } + } + + if ( !newCriterion.IsSubCriteriaType() ) + { + ComputeMatcher( &newCriterion, newCriterion.matcher ); + } + + if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() ) + { + ResponseWarning( "Multiple definitions for criteria '%s'\n", criterionName ); + return m_Criteria.InvalidIndex(); + } + + int idx = m_Criteria.Insert( criterionName, newCriterion ); + return idx; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseCriterion( void ) +{ + // Should have groupname at start + char criterionName[ 128 ]; + ParseToken(); + Q_strncpy( criterionName, token, sizeof( criterionName ) ); + + ParseOneCriterion( criterionName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseEnumeration( void ) +{ + char enumerationName[ 128 ]; + ParseToken(); + Q_strncpy( enumerationName, token, sizeof( enumerationName ) ); + + ParseToken(); + if ( Q_stricmp( token, "{" ) ) + { + ResponseWarning( "Expecting '{' in enumeration '%s', got '%s'\n", enumerationName, token ); + return; + } + + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + if ( Q_strlen( token ) <= 0 ) + { + ResponseWarning( "Expecting more tokens in enumeration '%s'\n", enumerationName ); + break; + } + + char key[ 128 ]; + + Q_strncpy( key, token, sizeof( key ) ); + ParseToken(); + float value = (float)atof( token ); + + char sz[ 128 ]; + Q_snprintf( sz, sizeof( sz ), "[%s::%s]", enumerationName, key ); + Q_strlower( sz ); + + Enumeration newEnum; + newEnum.value = value; + + if ( m_Enumerations.Find( sz ) == m_Enumerations.InvalidIndex() ) + { + m_Enumerations.Insert( sz, newEnum ); + } + else + { + ResponseWarning( "Ignoring duplication enumeration '%s'\n", sz ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseRule( void ) +{ + static int instancedCriteria = 0; + + char ruleName[ 128 ]; + ParseToken(); + Q_strncpy( ruleName, token, sizeof( ruleName ) ); + + ParseToken(); + if ( Q_stricmp( token, "{" ) ) + { + ResponseWarning( "Expecting '{' in rule '%s', got '%s'\n", ruleName, token ); + return; + } + + // entries are "criteria", "response" or an in-line criteria to instance + Rule newRule; + + char sz[ 128 ]; + + bool validRule = true; + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + if ( Q_strlen( token ) <= 0 ) + { + ResponseWarning( "Expecting more tokens in rule '%s'\n", ruleName ); + break; + } + + if ( !Q_stricmp( token, "matchonce" ) ) + { + newRule.m_bMatchOnce = true; + continue; + } + + if ( !Q_stricmp( token, "applyContext" ) ) + { + ParseToken(); + if ( newRule.GetContext() == NULL ) + { + newRule.SetContext( token ); + } + else + { + CFmtStrN<1024> newContext( "%s,%s", newRule.GetContext(), token ); + newRule.SetContext( newContext ); + } + continue; + } + + if ( !Q_stricmp( token, "response" ) ) + { + // Read them until we run out. + while ( TokenWaiting() ) + { + ParseToken(); + int idx = m_Responses.Find( token ); + if ( idx != m_Responses.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + newRule.m_Responses.AddToTail( idx ); + } + else + { + validRule = false; + ResponseWarning( "No such response '%s' for rule '%s'\n", token, ruleName ); + } + } + continue; + } + + if ( !Q_stricmp( token, "criteria" ) || + !Q_stricmp( token, "criterion" ) ) + { + // Read them until we run out. + while ( TokenWaiting() ) + { + ParseToken(); + + int idx = m_Criteria.Find( token ); + if ( idx != m_Criteria.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + newRule.m_Criteria.AddToTail( idx ); + } + else + { + validRule = false; + ResponseWarning( "No such criterion '%s' for rule '%s'\n", token, ruleName ); + } + } + continue; + } + + // It's an inline criteria, generate a name and parse it in + Q_snprintf( sz, sizeof( sz ), "[%s%03i]", ruleName, ++instancedCriteria ); + Unget(); + int idx = ParseOneCriterion( sz ); + if ( idx != m_Criteria.InvalidIndex() ) + { + newRule.m_Criteria.AddToTail( idx ); + } + } + + if ( validRule ) + { + m_Rules.Insert( ruleName, newRule ); + } + else + { + DevMsg( "Discarded rule %s\n", ruleName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CResponseSystem::GetCurrentToken() const +{ + if ( m_ScriptStack.Count() <= 0 ) + return -1; + + return m_ScriptStack[ 0 ].tokencount; +} + + +void CResponseSystem::ResponseWarning( const char *fmt, ... ) +{ + va_list argptr; +#ifndef _XBOX + static char string[1024]; +#else + char string[1024]; +#endif + + va_start (argptr, fmt); + Q_vsnprintf(string, sizeof(string), fmt,argptr); + va_end (argptr); + + char cur[ 256 ]; + GetCurrentScript( cur, sizeof( cur ) ); + DevMsg( 1, "%s(token %i) : %s", cur, GetCurrentToken(), string ); +} + + +//----------------------------------------------------------------------------- +// Purpose: A special purpose response system associated with a custom entity +//----------------------------------------------------------------------------- +class CInstancedResponseSystem : public CResponseSystem +{ + typedef CResponseSystem BaseClass; + +public: + CInstancedResponseSystem( const char *scriptfile ) : + m_pszScriptFile( 0 ) + { + Assert( scriptfile ); + + int len = Q_strlen( scriptfile ) + 1; + m_pszScriptFile = new char[ len ]; + Assert( m_pszScriptFile ); + Q_strncpy( m_pszScriptFile, scriptfile, len ); + } + + ~CInstancedResponseSystem() + { + delete[] m_pszScriptFile; + } + virtual const char *GetScriptFile( void ) + { + Assert( m_pszScriptFile ); + return m_pszScriptFile; + } + + // CAutoGameSystem + virtual bool Init() + { + const char *basescript = GetScriptFile(); + LoadRuleSet( basescript ); + return true; + } + + virtual void LevelInitPostEntity() + { + ResetResponseGroups(); + } + + virtual void Release() + { + Clear(); + delete this; + } +private: + + char *m_pszScriptFile; +}; + +//----------------------------------------------------------------------------- +// Purpose: The default response system for expressive AIs +//----------------------------------------------------------------------------- +class CDefaultResponseSystem : public CResponseSystem, public CAutoGameSystem +{ + typedef CAutoGameSystem BaseClass; + +public: + CDefaultResponseSystem() : CAutoGameSystem( "CDefaultResponseSystem" ) + { + } + + virtual const char *GetScriptFile( void ) + { + return "scripts/talker/response_rules.txt"; + } + + // CAutoServerSystem + virtual bool Init(); + virtual void Shutdown(); + + virtual void LevelInitPostEntity() + { + } + + virtual void Release() + { + Assert( 0 ); + } + + void AddInstancedResponseSystem( const char *scriptfile, CInstancedResponseSystem *sys ) + { + m_InstancedSystems.Insert( scriptfile, sys ); + } + + CInstancedResponseSystem *FindResponseSystem( const char *scriptfile ) + { + int idx = m_InstancedSystems.Find( scriptfile ); + if ( idx == m_InstancedSystems.InvalidIndex() ) + return NULL; + return m_InstancedSystems[ idx ]; + } + + IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) + { + CInstancedResponseSystem *sys = ( CInstancedResponseSystem * )FindResponseSystem( scriptfile ); + if ( !sys ) + { + sys = new CInstancedResponseSystem( scriptfile ); + if ( !sys ) + { + Error( "Failed to load response system data from %s", scriptfile ); + } + + if ( !sys->Init() ) + { + Error( "CInstancedResponseSystem: Failed to init response system from %s!", scriptfile ); + } + + AddInstancedResponseSystem( scriptfile, sys ); + } + + sys->Precache(); + + return ( IResponseSystem * )sys; + } + + virtual void LevelInitPreEntity() + { + // This will precache the default system + // All user installed systems are init'd by PrecacheCustomResponseSystem which will call sys->Precache() on the ones being used + + // FIXME: This is SLOW the first time you run the engine (can take 3 - 10 seconds!!!) + if ( ShouldPrecache() ) + { + Precache(); + } + + ResetResponseGroups(); + } + + void ReloadAllResponseSystems() + { + Clear(); + Init(); + + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; + sys->Clear(); + sys->Init(); + } + + } + +private: + + void ClearInstanced() + { + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; + sys->Release(); + } + m_InstancedSystems.RemoveAll(); + } + + CUtlDict< CInstancedResponseSystem *, int > m_InstancedSystems; +}; + +static CDefaultResponseSystem defaultresponsesytem; +IResponseSystem *g_pResponseSystem = &defaultresponsesytem; + +CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." ) +{ + defaultresponsesytem.ReloadAllResponseSystems(); +} + +static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1; + +// note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed +// +class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler +{ +public: + const char *GetBlockName() + { + return "ResponseSystem"; + } + + void WriteSaveHeaders( ISave *pSave ) + { + pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION ); + } + + void ReadRestoreHeaders( IRestore *pRestore ) + { + // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. + short version; + pRestore->ReadShort( &version ); + m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION ); + } + + void Save( ISave *pSave ) + { + CDefaultResponseSystem& rs = defaultresponsesytem; + + int count = rs.m_Responses.Count(); + pSave->WriteInt( &count ); + for ( int i = 0; i < count; ++i ) + { + pSave->StartBlock( "ResponseGroup" ); + + pSave->WriteString( rs.m_Responses.GetElementName( i ) ); + const ResponseGroup *group = &rs.m_Responses[ i ]; + pSave->WriteAll( group ); + + short groupCount = group->group.Count(); + pSave->WriteShort( &groupCount ); + for ( int j = 0; j < groupCount; ++j ) + { + const Response *response = &group->group[ j ]; + pSave->StartBlock( "Response" ); + pSave->WriteString( response->value ); + pSave->WriteAll( response ); + pSave->EndBlock(); + } + + pSave->EndBlock(); + } + } + + void Restore( IRestore *pRestore, bool createPlayers ) + { + if ( !m_fDoLoad ) + return; + + CDefaultResponseSystem& rs = defaultresponsesytem; + + int count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szResponseGroupBlockName ); + if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) + { + + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = rs.m_Responses.Find( groupname ); + if ( idx != rs.m_Responses.InvalidIndex() ) + { + ResponseGroup *group = &rs.m_Responses[ idx ]; + pRestore->ReadAll( group ); + + short groupCount = pRestore->ReadShort(); + for ( int j = 0; j < groupCount; ++j ) + { + char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; + + char responsename[ 256 ]; + pRestore->StartBlock( szResponseBlockName ); + if ( !Q_stricmp( szResponseBlockName, "Response" ) ) + { + pRestore->ReadString( responsename, sizeof( responsename ), 0 ); + + // Find it by name + int ri; + for ( ri = 0; ri < group->group.Count(); ++ri ) + { + Response *response = &group->group[ ri ]; + if ( !Q_stricmp( response->value, responsename ) ) + { + break; + } + } + + if ( ri < group->group.Count() ) + { + Response *response = &group->group[ ri ]; + pRestore->ReadAll( response ); + } + } + + pRestore->EndBlock(); + } + } + } + + pRestore->EndBlock(); + } + } +private: + + bool m_fDoLoad; + +} g_DefaultResponseSystemSaveRestoreBlockHandler; + +ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler() +{ + return &g_DefaultResponseSystemSaveRestoreBlockHandler; +} + +//----------------------------------------------------------------------------- +// CResponseSystemSaveRestoreOps +// +// Purpose: Handles save and load for instanced response systems... +// +// BUGBUG: This will save the same response system to file multiple times for "shared" response systems and +// therefore it'll restore the same data onto the same pointer N times on reload (probably benign for now, but we could +// write code to save/restore the instanced ones by filename in the block handler above maybe? +//----------------------------------------------------------------------------- + +class CResponseSystemSaveRestoreOps : public CDefSaveRestoreOps +{ +public: + + virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) + { + CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; + if ( !pRS || pRS == &defaultresponsesytem ) + return; + + int count = pRS->m_Responses.Count(); + pSave->WriteInt( &count ); + for ( int i = 0; i < count; ++i ) + { + pSave->StartBlock( "ResponseGroup" ); + + pSave->WriteString( pRS->m_Responses.GetElementName( i ) ); + const ResponseGroup *group = &pRS->m_Responses[ i ]; + pSave->WriteAll( group ); + + short groupCount = group->group.Count(); + pSave->WriteShort( &groupCount ); + for ( int j = 0; j < groupCount; ++j ) + { + const Response *response = &group->group[ j ]; + pSave->StartBlock( "Response" ); + pSave->WriteString( response->value ); + pSave->WriteAll( response ); + pSave->EndBlock(); + } + + pSave->EndBlock(); + } + } + + virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) + { + CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; + if ( !pRS || pRS == &defaultresponsesytem ) + return; + + int count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szResponseGroupBlockName ); + if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) + { + + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = pRS->m_Responses.Find( groupname ); + if ( idx != pRS->m_Responses.InvalidIndex() ) + { + ResponseGroup *group = &pRS->m_Responses[ idx ]; + pRestore->ReadAll( group ); + + short groupCount = pRestore->ReadShort(); + for ( int j = 0; j < groupCount; ++j ) + { + char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; + + char responsename[ 256 ]; + pRestore->StartBlock( szResponseBlockName ); + if ( !Q_stricmp( szResponseBlockName, "Response" ) ) + { + pRestore->ReadString( responsename, sizeof( responsename ), 0 ); + + // Find it by name + int ri; + for ( ri = 0; ri < group->group.Count(); ++ri ) + { + Response *response = &group->group[ ri ]; + if ( !Q_stricmp( response->value, responsename ) ) + { + break; + } + } + + if ( ri < group->group.Count() ) + { + Response *response = &group->group[ ri ]; + pRestore->ReadAll( response ); + } + } + + pRestore->EndBlock(); + } + } + } + + pRestore->EndBlock(); + } + } + +} g_ResponseSystemSaveRestoreOps; + +ISaveRestoreOps *responseSystemSaveRestoreOps = &g_ResponseSystemSaveRestoreOps; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CDefaultResponseSystem::Init() +{ +/* + Warning( "sizeof( Response ) == %d\n", sizeof( Response ) ); + Warning( "sizeof( ResponseGroup ) == %d\n", sizeof( ResponseGroup ) ); + Warning( "sizeof( Criteria ) == %d\n", sizeof( Criteria ) ); + Warning( "sizeof( AI_ResponseParams ) == %d\n", sizeof( AI_ResponseParams ) ); +*/ + const char *basescript = GetScriptFile(); + + LoadRuleSet( basescript ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDefaultResponseSystem::Shutdown() +{ + // Wipe instanced versions + ClearInstanced(); + + // Clear outselves + Clear(); + // IServerSystem chain + BaseClass::Shutdown(); +} + +//----------------------------------------------------------------------------- +// Purpose: Instance a custom response system +// Input : *scriptfile - +// Output : IResponseSystem +//----------------------------------------------------------------------------- +IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) +{ + return defaultresponsesytem.PrecacheCustomResponseSystem( scriptfile ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::DumpRules() +{ + int c = m_Rules.Count(); + int i; + + for ( i = 0; i < c; i++ ) + { + Msg("%s\n", m_Rules.GetElementName( i ) ); + } +} diff --git a/dlls/CRagdollMagnet.h b/dlls/CRagdollMagnet.h index 87f20b0d..673a96ae 100644 --- a/dlls/CRagdollMagnet.h +++ b/dlls/CRagdollMagnet.h @@ -1,45 +1,45 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Used to influence the initial force for a dying NPC's ragdoll. -// Passive entity. Just represents position in the world, radius, force -// -// $NoKeywords: $ -//=============================================================================// -#pragma once - -#ifndef CRAGDOLLMAGNET_H -#define CRAGDOLLMAGNET_H - -#define SF_RAGDOLLMAGNET_BAR 0x00000002 // this is a bar magnet. - -class CRagdollMagnet : public CPointEntity -{ -public: - DECLARE_CLASS( CRagdollMagnet, CPointEntity ); - DECLARE_DATADESC(); - - Vector GetForceVector( CBaseEntity *pNPC ); - float GetRadius( void ) { return m_radius; } - Vector GetAxisVector( void ) { return m_axis - GetAbsOrigin(); } - float DistToPoint( const Vector &vecPoint ); - - bool IsEnabled( void ) { return !m_bDisabled; } - - int IsBarMagnet( void ) { return (m_spawnflags & SF_RAGDOLLMAGNET_BAR); } - - static CRagdollMagnet *FindBestMagnet( CBaseEntity *pNPC ); - - void Enable( bool bEnable ) { m_bDisabled = !bEnable; } - - // Inputs - void InputEnable( inputdata_t &inputdata ); - void InputDisable( inputdata_t &inputdata ); - -private: - bool m_bDisabled; - float m_radius; - float m_force; - Vector m_axis; -}; - -#endif //CRAGDOLLMAGNET_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Used to influence the initial force for a dying NPC's ragdoll. +// Passive entity. Just represents position in the world, radius, force +// +// $NoKeywords: $ +//=============================================================================// +#pragma once + +#ifndef CRAGDOLLMAGNET_H +#define CRAGDOLLMAGNET_H + +#define SF_RAGDOLLMAGNET_BAR 0x00000002 // this is a bar magnet. + +class CRagdollMagnet : public CPointEntity +{ +public: + DECLARE_CLASS( CRagdollMagnet, CPointEntity ); + DECLARE_DATADESC(); + + Vector GetForceVector( CBaseEntity *pNPC ); + float GetRadius( void ) { return m_radius; } + Vector GetAxisVector( void ) { return m_axis - GetAbsOrigin(); } + float DistToPoint( const Vector &vecPoint ); + + bool IsEnabled( void ) { return !m_bDisabled; } + + int IsBarMagnet( void ) { return (m_spawnflags & SF_RAGDOLLMAGNET_BAR); } + + static CRagdollMagnet *FindBestMagnet( CBaseEntity *pNPC ); + + void Enable( bool bEnable ) { m_bDisabled = !bEnable; } + + // Inputs + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + +private: + bool m_bDisabled; + float m_radius; + float m_force; + Vector m_axis; +}; + +#endif //CRAGDOLLMAGNET_H diff --git a/dlls/EffectsServer.cpp b/dlls/EffectsServer.cpp index 9c6b2744..1ad6f3a1 100644 --- a/dlls/EffectsServer.cpp +++ b/dlls/EffectsServer.cpp @@ -124,7 +124,7 @@ void CEffectsServer::Smoke( const Vector &origin, int mModel, float flScale, flo CPVSFilter filter( origin ); if ( !SuppressTE( filter ) ) { - te->Smoke( filter, 0.0, &origin, mModel, flScale * 0.1f, flFramerate ); + te->Smoke( filter, 0.0, &origin, mModel, flScale * 0.1f, static_cast(flFramerate) ); } } diff --git a/dlls/EntityFlame.cpp b/dlls/EntityFlame.cpp index d105ae11..9d496ce0 100644 --- a/dlls/EntityFlame.cpp +++ b/dlls/EntityFlame.cpp @@ -274,8 +274,8 @@ void CEntityFlame::FlameThink( void ) { const float ENTITYFLAME_MOVE_AWAY_DIST = 24.0f; // Make a sound near my origin, and up a little higher (in case I'm on the ground, so NPC's still hear it) - CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin(), ENTITYFLAME_MOVE_AWAY_DIST, 0.1f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); - CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin() + Vector( 0, 0, 48.0f ), ENTITYFLAME_MOVE_AWAY_DIST, 0.1f, this, SOUNDENT_CHANNEL_REPEATING ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin(), static_cast(ENTITYFLAME_MOVE_AWAY_DIST), 0.1f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin() + Vector( 0, 0, 48.0f ), static_cast(ENTITYFLAME_MOVE_AWAY_DIST), 0.1f, this, SOUNDENT_CHANNEL_REPEATING ); } } else diff --git a/dlls/EnvBeam.cpp b/dlls/EnvBeam.cpp index a4b7f277..6e32fc74 100644 --- a/dlls/EnvBeam.cpp +++ b/dlls/EnvBeam.cpp @@ -381,7 +381,7 @@ void CEnvBeam::Strike( void ) if ( pStart == NULL || pEnd == NULL ) return; - m_speed = clamp( m_speed, 0, MAX_BEAM_SCROLLSPEED ); + m_speed = static_cast(clamp( m_speed, 0, MAX_BEAM_SCROLLSPEED )); int pointStart = IsStaticPointEntity( pStart ); int pointEnd = IsStaticPointEntity( pEnd ); @@ -710,7 +710,7 @@ void CEnvBeam::BeamUpdateVars( void ) m_nNumBeamEnts = 2; - m_speed = clamp( m_speed, 0, MAX_BEAM_SCROLLSPEED ); + m_speed = static_cast(clamp( m_speed, 0, MAX_BEAM_SCROLLSPEED )); // NOTE: If the end entity is the beam itself (and the start entity // isn't *also* the beam itself, we've got problems. This is a problem diff --git a/dlls/EnvShake.cpp b/dlls/EnvShake.cpp index 679fe9eb..d59e73d9 100644 --- a/dlls/EnvShake.cpp +++ b/dlls/EnvShake.cpp @@ -245,6 +245,8 @@ void CEnvShake::ApplyShake( ShakeCommand_t command ) case SHAKE_FREQUENCY: m_pShakeController->WakeObjects(); break; + default: + break; } } } diff --git a/dlls/SkyCamera.cpp b/dlls/SkyCamera.cpp index 843b7bd6..4fcc295e 100644 --- a/dlls/SkyCamera.cpp +++ b/dlls/SkyCamera.cpp @@ -61,6 +61,7 @@ END_DATADESC() //----------------------------------------------------------------------------- // List of maps in HL2 that we must apply our skybox fog fixup hack to //----------------------------------------------------------------------------- +#ifdef HL2_DLL static const char *s_pBogusFogMaps[] = { "d1_canals_01", @@ -84,6 +85,7 @@ static const char *s_pBogusFogMaps[] = "d3_citadel_01", NULL }; +#endif //----------------------------------------------------------------------------- // Constructor, destructor diff --git a/dlls/TemplateEntities.cpp b/dlls/TemplateEntities.cpp index 1c85bcea..db98532e 100644 --- a/dlls/TemplateEntities.cpp +++ b/dlls/TemplateEntities.cpp @@ -309,7 +309,7 @@ void Templates_StartUniqueInstance( void ) g_iCurrentTemplateInstance++; // Make sure there's enough room to fit it into the string - int iMax = pow(10.0f, (int)((strlen(ENTITYIO_FIXUP_STRING) - 1))); // -1 for the & + int iMax = static_cast(pow(10.0f, (int)((strlen(ENTITYIO_FIXUP_STRING) - 1)))); // -1 for the & if ( g_iCurrentTemplateInstance >= iMax ) { // We won't hit this. diff --git a/dlls/ai_baseactor.cpp b/dlls/ai_baseactor.cpp index e0a2169e..d42e25b6 100644 --- a/dlls/ai_baseactor.cpp +++ b/dlls/ai_baseactor.cpp @@ -629,29 +629,22 @@ bool CAI_BaseActor::CheckSceneEventCompletion( CSceneEventInfo *info, float curr Assert( info->m_pScene ); Assert( info->m_pEvent ); - switch ( event->GetType() ) + if (event->GetType() == CChoreoEvent::GENERIC) { - case CChoreoEvent::GENERIC: + if (info->m_nType == SCENE_AI_HOLSTER || info->m_nType == SCENE_AI_UNHOLSTER) { - switch( info->m_nType) + if (info->m_iLayer == -1) { - case SCENE_AI_HOLSTER: - case SCENE_AI_UNHOLSTER: - { - if (info->m_iLayer == -1) - { - return true; - } - float preload = event->GetEndTime() - currenttime; - if (preload < 0) - { - return true; - } - float t = (1.0 - GetLayerCycle( info->m_iLayer )) * SequenceDuration( GetLayerSequence( info->m_iLayer ) ); - - return (t <= preload); - } + return true; } + float preload = event->GetEndTime() - currenttime; + if (preload < 0) + { + return true; + } + float t = (1.0 - GetLayerCycle( info->m_iLayer )) * SequenceDuration( GetLayerSequence( info->m_iLayer ) ); + + return (t <= preload); } } @@ -1665,6 +1658,8 @@ void CAI_BaseActor::PlayExpressionForState( NPC_STATE state ) SetExpression( STRING(m_iszDeathExpression) ); } break; + default: + break; } } diff --git a/dlls/ai_baseactor.h b/dlls/ai_baseactor.h index 66720edd..a94ce0ec 100644 --- a/dlls/ai_baseactor.h +++ b/dlls/ai_baseactor.h @@ -81,11 +81,11 @@ public: m_flBlinktime( 0 ), m_hLookTarget( NULL ), m_iszExpressionScene( NULL_STRING ), + m_iszExpressionOverride( NULL_STRING ), m_iszIdleExpression( NULL_STRING ), m_iszAlertExpression( NULL_STRING ), m_iszCombatExpression( NULL_STRING ), - m_iszDeathExpression( NULL_STRING ), - m_iszExpressionOverride( NULL_STRING ) + m_iszDeathExpression( NULL_STRING ) { memset( m_flextarget, 0, 64 * sizeof( m_flextarget[0] ) ); } diff --git a/dlls/ai_basenpc.cpp b/dlls/ai_basenpc.cpp index a4015e72..f3caf305 100644 --- a/dlls/ai_basenpc.cpp +++ b/dlls/ai_basenpc.cpp @@ -1,13658 +1,13681 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" - -#include "ai_basenpc.h" -#include "fmtstr.h" -#include "activitylist.h" -#include "animation.h" -#include "basecombatweapon.h" -#include "soundent.h" -#include "decals.h" -#include "entitylist.h" -#include "eventqueue.h" -#include "entityapi.h" -#include "bitstring.h" -#include "gamerules.h" // For g_pGameRules -#include "scripted.h" -#include "worldsize.h" -#include "game.h" -#include "shot_manipulator.h" - -#ifdef HL2_DLL -#include "ai_interactions.h" -#include "hl2_gamerules.h" -#endif // HL2_DLL - -#include "ai_network.h" -#include "ai_networkmanager.h" -#include "ai_pathfinder.h" -#include "ai_node.h" -#include "ai_default.h" -#include "ai_schedule.h" -#include "ai_task.h" -#include "ai_hull.h" -#include "ai_moveprobe.h" -#include "ai_hint.h" -#include "ai_navigator.h" -#include "ai_senses.h" -#include "ai_squadslot.h" -#include "ai_memory.h" -#include "ai_squad.h" -#include "ai_localnavigator.h" -#include "ai_tacticalservices.h" -#include "ai_behavior.h" -#include "ai_dynamiclink.h" -#include "AI_Criteria.h" -#include "basegrenade_shared.h" -#include "ammodef.h" -#include "player.h" -#include "sceneentity.h" -#include "ndebugoverlay.h" -#include "mathlib.h" -#include "bone_setup.h" -#include "IEffects.h" -#include "vstdlib/random.h" -#include "engine/IEngineSound.h" -#include "vstdlib/strtools.h" -#include "doors.h" -#include "BasePropDoor.h" -#include "saverestore_utlvector.h" -#include "npcevent.h" -#include "movevars_shared.h" -#include "te_effect_dispatch.h" -#include "globals.h" -#include "saverestore_bitstring.h" -#include "checksum_crc.h" -#include "iservervehicle.h" -#include "filters.h" -#ifdef HL2_DLL -#include "npc_bullseye.h" -#include "hl2_player.h" -#include "weapon_physcannon.h" -#endif -#include "waterbullet.h" -#include "in_buttons.h" -#include "eventlist.h" -#include "globalstate.h" -#include "physics_prop_ragdoll.h" -#include "vphysics/friction.h" -#include "physics_npc_solver.h" -#include "tier0/vcrmode.h" -#include "death_pose.h" -#include "datacache/imdlcache.h" -#ifdef HL2_EPISODIC -#include "npc_alyx_episodic.h" -#endif - -#include "env_debughistory.h" -#include "collisionutils.h" - -extern ConVar sk_healthkit; - -// dvs: for opening doors -- these should probably not be here -#include "ai_route.h" -#include "ai_waypoint.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//#define DEBUG_LOOK - -bool RagdollManager_SaveImportant( CAI_BaseNPC *pNPC ); - -#define MIN_PHYSICS_FLINCH_DAMAGE 5.0f - -#define NPC_GRENADE_FEAR_DIST 200 -#define MAX_GLASS_PENETRATION_DEPTH 16.0f - -#define FINDNAMEDENTITY_MAX_ENTITIES 32 // max number of entities to be considered for random entity selection in FindNamedEntity - -extern bool g_fDrawLines; -extern short g_sModelIndexLaser; // holds the index for the laser beam -extern short g_sModelIndexLaserDot; // holds the index for the laser beam dot - -// Debugging tools -ConVar ai_no_select_box( "ai_no_select_box", "0" ); - -ConVar ai_show_think_tolerance( "ai_show_think_tolerance", "0" ); -ConVar ai_debug_think_ticks( "ai_debug_think_ticks", "0" ); -ConVar ai_debug_doors( "ai_debug_doors", "0" ); -ConVar ai_debug_enemies( "ai_debug_enemies", "0" ); - -ConVar ai_rebalance_thinks( "ai_rebalance_thinks", "1" ); -ConVar ai_use_efficiency( "ai_use_efficiency", "1" ); -ConVar ai_use_frame_think_limits( "ai_use_frame_think_limits", "1" ); -ConVar ai_default_efficient( "ai_default_efficient", ( IsXbox() ) ? "1" : "0" ); -ConVar ai_efficiency_override( "ai_efficiency_override", "0" ); -ConVar ai_debug_efficiency( "ai_debug_efficiency", "0" ); -ConVar ai_debug_dyninteractions( "ai_debug_dyninteractions", "0", FCVAR_NONE, "Debug the NPC dynamic interaction system." ); -ConVar ai_frametime_limit( "ai_frametime_limit", "50", FCVAR_NONE, "frametime limit for min efficiency AIE_NORMAL (in sec's)." ); - -ConVar ai_use_think_optimizations( "ai_use_think_optimizations", "1" ); - -ConVar ai_test_moveprobe_ignoresmall( "ai_test_moveprobe_ignoresmall", "0" ); - -#ifndef _RETAIL -#define ShouldUseEfficiency() ( ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool() ) -#define ShouldUseFrameThinkLimits() ( ai_use_think_optimizations.GetBool() && ai_use_frame_think_limits.GetBool() ) -#define ShouldRebalanceThinks() ( ai_use_think_optimizations.GetBool() && ai_rebalance_thinks.GetBool() ) -#define ShouldDefaultEfficient() ( ai_use_think_optimizations.GetBool() && ai_default_efficient.GetBool() ) -#else -#define ShouldUseEfficiency() ( true ) -#define ShouldUseFrameThinkLimits() ( true ) -#define ShouldRebalanceThinks() ( true ) -#define ShouldDefaultEfficient() ( true ) -#endif - -#ifndef _RETAIL -#define DbgEnemyMsg if ( !ai_debug_enemies.GetBool() ) ; else DevMsg -#else -#define DbgEnemyMsg if ( 0 ) ; else DevMsg -#endif - -#ifdef DEBUG_AI_FRAME_THINK_LIMITS -#define DbgFrameLimitMsg DevMsg -#else -#define DbgFrameLimitMsg (void) -#endif - -// NPC damage adjusters -ConVar sk_npc_head( "sk_npc_head","2" ); -ConVar sk_npc_chest( "sk_npc_chest","1" ); -ConVar sk_npc_stomach( "sk_npc_stomach","1" ); -ConVar sk_npc_arm( "sk_npc_arm","1" ); -ConVar sk_npc_leg( "sk_npc_leg","1" ); -ConVar showhitlocation( "showhitlocation", "0" ); - -// Squad debugging -ConVar ai_debug_squads( "ai_debug_squads", "0" ); -ConVar ai_debug_loners( "ai_debug_loners", "0" ); - -// Shoot trajectory -ConVar ai_lead_time( "ai_lead_time","0.0" ); -ConVar ai_shot_stats( "ai_shot_stats", "0" ); -ConVar ai_shot_stats_term( "ai_shot_stats_term", "1000" ); -ConVar ai_shot_bias( "ai_shot_bias", "1.0" ); - -ConVar ai_spread_defocused_cone_multiplier( "ai_spread_defocused_cone_multiplier","3.0" ); -ConVar ai_spread_cone_focus_time( "ai_spread_cone_focus_time","0.6" ); -ConVar ai_spread_pattern_focus_time( "ai_spread_pattern_focus_time","0.8" ); - -ConVar ai_reaction_delay_idle( "ai_reaction_delay_idle","0.3" ); -ConVar ai_reaction_delay_alert( "ai_reaction_delay_alert", "0.1" ); - -//----------------------------------------------------------------------------- -// -// Crude frame timings -// - -CFastTimer g_AIRunTimer; -CFastTimer g_AIPostRunTimer; -CFastTimer g_AIMoveTimer; - -CFastTimer g_AIConditionsTimer; -CFastTimer g_AIPrescheduleThinkTimer; -CFastTimer g_AIMaintainScheduleTimer; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -CAI_Manager g_AI_Manager; - -//------------------------------------- - -CAI_Manager::CAI_Manager() -{ - m_AIs.EnsureCapacity( MAX_AIS ); -} - -//------------------------------------- - -CAI_BaseNPC **CAI_Manager::AccessAIs() -{ - if (m_AIs.Count()) - return &m_AIs[0]; - return NULL; -} - -//------------------------------------- - -int CAI_Manager::NumAIs() -{ - return m_AIs.Count(); -} - -//------------------------------------- - -void CAI_Manager::AddAI( CAI_BaseNPC *pAI ) -{ - m_AIs.AddToTail( pAI ); -} - -//------------------------------------- - -void CAI_Manager::RemoveAI( CAI_BaseNPC *pAI ) -{ - int i = m_AIs.Find( pAI ); - - if ( i != -1 ) - m_AIs.FastRemove( i ); -} - - -//----------------------------------------------------------------------------- - -// ================================================================ -// Init static data -// ================================================================ -int CAI_BaseNPC::m_nDebugBits = 0; -CAI_BaseNPC* CAI_BaseNPC::m_pDebugNPC = NULL; -int CAI_BaseNPC::m_nDebugPauseIndex = -1; - -CAI_ClassScheduleIdSpace CAI_BaseNPC::gm_ClassScheduleIdSpace( true ); -CAI_GlobalScheduleNamespace CAI_BaseNPC::gm_SchedulingSymbols; -CAI_LocalIdSpace CAI_BaseNPC::gm_SquadSlotIdSpace( true ); - -string_t CAI_BaseNPC::gm_iszPlayerSquad; - -int CAI_BaseNPC::gm_iNextThinkRebalanceTick; -float CAI_BaseNPC::gm_flTimeLastSpawn; -int CAI_BaseNPC::gm_nSpawnedThisFrame; - -CSimpleSimTimer CAI_BaseNPC::m_AnyUpdateEnemyPosTimer; - -// ================================================================ -// Class Methods -// ================================================================ - -//----------------------------------------------------------------------------- -// Purpose: Static debug function to clear schedules for all NPCS -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ClearAllSchedules(void) -{ - CAI_BaseNPC *npc = gEntList.NextEntByClass( (CAI_BaseNPC *)NULL ); - - while (npc) - { - npc->ClearSchedule(); - npc->GetNavigator()->ClearGoal(); - npc = gEntList.NextEntByClass(npc); - } -} - -// ============================================================================== - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::Event_Gibbed( const CTakeDamageInfo &info ) -{ - bool gibbed = CorpseGib( info ); - - if ( gibbed ) - { - // don't remove players! - UTIL_Remove( this ); - SetThink( NULL ); //We're going away, so don't think anymore. - } - else - { - CorpseFade(); - } - - return gibbed; -} - -//========================================================= -// GetFlinchActivity - determines the best type of flinch -// anim to play. -//========================================================= -Activity CAI_BaseNPC::GetFlinchActivity( bool bHeavyDamage, bool bGesture ) -{ - Activity flinchActivity; - - switch ( LastHitGroup() ) - { - // pick a region-specific flinch - case HITGROUP_HEAD: - flinchActivity = bGesture ? ACT_GESTURE_FLINCH_HEAD : ACT_FLINCH_HEAD; - break; - case HITGROUP_STOMACH: - flinchActivity = bGesture ? ACT_GESTURE_FLINCH_STOMACH : ACT_FLINCH_STOMACH; - break; - case HITGROUP_LEFTARM: - flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTARM : ACT_FLINCH_LEFTARM; - break; - case HITGROUP_RIGHTARM: - flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTARM : ACT_FLINCH_RIGHTARM; - break; - case HITGROUP_LEFTLEG: - flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTLEG : ACT_FLINCH_LEFTLEG; - break; - case HITGROUP_RIGHTLEG: - flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTLEG : ACT_FLINCH_RIGHTLEG; - break; - case HITGROUP_CHEST: - flinchActivity = bGesture ? ACT_GESTURE_FLINCH_CHEST : ACT_FLINCH_CHEST; - break; - case HITGROUP_GEAR: - case HITGROUP_GENERIC: - default: - // just get a generic flinch. - if ( bHeavyDamage ) - { - flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH; - } - else - { - flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH; - } - break; - } - - // do we have a sequence for the ideal activity? - if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) - { - if ( bHeavyDamage ) - { - flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH; - - // If we fail at finding a big flinch, resort to a small one - if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) - { - flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH; - } - } - else - { - flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH; - } - } - - return flinchActivity; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput ) -{ - if ( !m_bDidDeathCleanup ) - { - m_bDidDeathCleanup = true; - - if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine ) - { - // bail out of this script here - m_hCine->CancelScript(); - // now keep going with the death code - } - - if ( GetHintNode() ) - { - GetHintNode()->Unlock(); - SetHintNode( NULL ); - } - - if( bFireDeathOutput ) - { - m_OnDeath.FireOutput( pCulprit, this ); - } - - // Vacate any strategy slot I might have - VacateStrategySlot(); - - // Remove from squad if in one - if (m_pSquad) - { - // If I'm in idle it means that I didn't see who killed me - // and my squad is still in idle state. Tell squad we have - // an enemy to wake them up and put the enemy position at - // my death position - if ( m_NPCState == NPC_STATE_IDLE && pCulprit) - { - // If we already have some danger memory, don't do this cheat - if ( GetEnemies()->GetDangerMemory() == NULL ) - { - UpdateEnemyMemory( pCulprit, GetAbsOrigin() ); - } - } - - // Remove from squad - m_pSquad->RemoveFromSquad(this, true); - m_pSquad = NULL; - } - - RemoveActorFromScriptedScenes( this, false /*all scenes*/ ); - } - else - DevMsg( "Unexpected double-death-cleanup\n" ); -} - -void CAI_BaseNPC::SelectDeathPose( const CTakeDamageInfo &info ) -{ - if ( !GetModelPtr() || (info.GetDamageType() & DMG_PREVENT_PHYSICS_FORCE) ) - return; - - if ( ShouldPickADeathPose() == false ) - return; - - Activity aActivity = ACT_INVALID; - int iDeathFrame = 0; - - SelectDeathPoseActivityAndFrame( this, info, LastHitGroup(), aActivity, iDeathFrame ); - if ( aActivity == ACT_INVALID ) - { - SetDeathPose( ACT_INVALID ); - SetDeathPoseFrame( 0 ); - return; - } - - SetDeathPose( SelectWeightedSequence( aActivity ) ); - SetDeathPoseFrame( iDeathFrame ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::Event_Killed( const CTakeDamageInfo &info ) -{ - if (IsCurSchedule(SCHED_NPC_FREEZE)) - { - // We're frozen; don't die. - return; - } - - Wake( false ); - - //Adrian: Select a death pose to extrapolate the ragdoll's velocity. - SelectDeathPose( info ); - - m_lifeState = LIFE_DYING; - - CleanupOnDeath( info.GetAttacker() ); - - StopLoopingSounds(); - DeathSound( info ); - - if ( ( GetFlags() & FL_NPC ) && ( ShouldGib( info ) == false ) ) - { - SetTouch( NULL ); - } - - BaseClass::Event_Killed( info ); - - - if ( m_bFadeCorpse ) - { - m_bImportanRagdoll = RagdollManager_SaveImportant( this ); - } - - // Make sure this condition is fired too (OnTakeDamage breaks out before this happens on death) - SetCondition( COND_LIGHT_DAMAGE ); - SetIdealState( NPC_STATE_DEAD ); - - // Some characters rely on getting a state transition, even to death. - // zombies, for instance. When a character becomes a ragdoll, their - // server entity ceases to think, so we have to set the dead state here - // because the AI code isn't going to pick up the change on the next think - // for us. - - // Adrian - Only set this if we are going to become a ragdoll. We still want to - // select SCHED_DIE or do something special when this NPC dies and we wont - // catch the change of state if we set this to whatever the ideal state is. - if ( CanBecomeRagdoll() || IsRagdoll() ) - SetState( NPC_STATE_DEAD ); - - // If the remove-no-ragdoll flag is set in the damage type, we're being - // told to remove ourselves immediately on death. This is used when something - // else has some special reason for us to vanish instead of creating a ragdoll. - // i.e. The barnacle does this because it's already got a ragdoll for us. - if ( info.GetDamageType() & DMG_REMOVENORAGDOLL ) - { - if ( !IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) ) - { - // Go away - RemoveDeferred(); - } - } -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner ) -{ - BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner ); - -#ifdef HL2_EPISODIC - CBasePlayer *pPlayer = AI_GetSinglePlayer(); - if ( pPlayer->IRelationType( this ) != D_LI ) - { - CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx(); - - if ( alyx ) - { - alyx->EnemyIgnited( this ); - } - } -#endif -} - -//----------------------------------------------------------------------------- - -ConVar ai_block_damage( "ai_block_damage","0" ); - -bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info ) -{ - if ( ai_block_damage.GetBool() ) - return false; - // FIXME: hook a friendly damage filter to the npc instead? - if ( (CapabilitiesGet() & bits_CAP_FRIENDLY_DMG_IMMUNE) && info.GetAttacker() && info.GetAttacker() != this ) - { - // check attackers relationship with me - CBaseCombatCharacter *npcEnemy = info.GetAttacker()->MyCombatCharacterPointer(); - bool bHitByVehicle = false; - if ( !npcEnemy ) - { - if ( info.GetAttacker()->GetServerVehicle() ) - { - bHitByVehicle = true; - } - } - - if ( bHitByVehicle || (npcEnemy && npcEnemy->IRelationType( this ) == D_LI) ) - { - m_fNoDamageDecal = true; - - if ( npcEnemy && npcEnemy->IsPlayer() ) - { - m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); - // This also counts as being harmed by player's squad. - m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); - } - - return false; - } - } - - if ( !BaseClass::PassesDamageFilter( info ) ) - { - m_fNoDamageDecal = true; - return false; - } - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -int CAI_BaseNPC::OnTakeDamage_Alive( const CTakeDamageInfo &info ) -{ - Forget( bits_MEMORY_INCOVER ); - - if ( !BaseClass::OnTakeDamage_Alive( info ) ) - return 0; - - if ( GetSleepState() == AISS_WAITING_FOR_THREAT ) - Wake(); - - // NOTE: This must happen after the base class is called; we need to reduce - // health before the pain sound, since some NPCs use the final health - // level as a modifier to determine which pain sound to use. - - // REVISIT: Combine soldiers shoot each other a lot and then talk about it - // this improves that case a bunch, but it seems kind of harsh. - if ( !m_pSquad || !m_pSquad->SquadIsMember( info.GetAttacker() ) ) - { - PainSound( info );// "Ouch!" - } - - // See if we're running a dynamic interaction that should break when I am damaged. - if ( IsActiveDynamicInteraction() ) - { - ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction(); - if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_DAMAGE ) - { - // Can only break when we're in the action anim - if ( m_hCine->IsPlayingAction() ) - { - m_hCine->StopActionLoop( true ); - } - } - } - - // If we're not allowed to die, refuse to die - // Allow my interaction partner to kill me though - if ( m_iHealth <= 0 && HasInteractionCantDie() && info.GetAttacker() != m_hInteractionPartner ) - { - m_iHealth = 1; - } - -#if 0 - // HACKHACK Don't kill npcs in a script. Let them break their scripts first - // THIS is a Half-Life 1 hack that's not cutting the mustard in the scripts - // that have been authored for Half-Life 2 thus far. (sjb) - if ( m_NPCState == NPC_STATE_SCRIPT ) - { - SetCondition( COND_LIGHT_DAMAGE ); - } -#endif - - // ----------------------------------- - // Fire outputs - // ----------------------------------- - if ( m_flLastDamageTime != gpGlobals->curtime ) - { - // only fire once per frame - m_OnDamaged.FireOutput( info.GetAttacker(), this); - - if( info.GetAttacker()->IsPlayer() ) - { - m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); - - // This also counts as being harmed by player's squad. - m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); - } - else - { - // See if the person that injured me is an NPC. - CAI_BaseNPC *pAttacker = dynamic_cast( info.GetAttacker() ); - CBasePlayer *pPlayer = AI_GetSinglePlayer(); - - if( pAttacker && pAttacker->IsAlive() && pPlayer ) - { - if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() ) - { - m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); - } - } - } - } - - if( (info.GetDamageType() & DMG_CRUSH) && !(info.GetDamageType() & DMG_PHYSGUN) && info.GetDamage() >= MIN_PHYSICS_FLINCH_DAMAGE ) - { - SetCondition( COND_PHYSICS_DAMAGE ); - } - - if ( m_iHealth <= ( m_iMaxHealth / 2 ) ) - { - m_OnHalfHealth.FireOutput( info.GetAttacker(), this ); - } - - // react to the damage (get mad) - if ( ( (GetFlags() & FL_NPC) == 0 ) || !info.GetAttacker() ) - return 1; - - // If the attacker was an NPC or client update my position memory - if ( info.GetAttacker()->GetFlags() & (FL_NPC | FL_CLIENT) ) - { - // ------------------------------------------------------------------ - // DO NOT CHANGE THIS CODE W/O CONSULTING - // Only update information about my attacker I don't see my attacker - // ------------------------------------------------------------------ - if ( !FInViewCone( info.GetAttacker() ) || !FVisible( info.GetAttacker() ) ) - { - // ------------------------------------------------------------- - // If I have an inflictor (enemy / grenade) update memory with - // position of inflictor, otherwise update with an position - // estimate for where the attack came from - // ------------------------------------------------------ - Vector vAttackPos; - if (info.GetInflictor()) - { - vAttackPos = info.GetInflictor()->GetAbsOrigin(); - } - else - { - vAttackPos = (GetAbsOrigin() + ( g_vecAttackDir * 64 )); - } - - - // ---------------------------------------------------------------- - // If I already have an enemy, assume that the attack - // came from the enemy and update my enemy's position - // unless I already know about the attacker or I can see my enemy - // ---------------------------------------------------------------- - if ( GetEnemy() != NULL && - !GetEnemies()->HasMemory( info.GetAttacker() ) && - !HasCondition(COND_SEE_ENEMY) ) - { - UpdateEnemyMemory(GetEnemy(), vAttackPos, GetEnemy()); - } - // ---------------------------------------------------------------- - // If I already know about this enemy, update his position - // ---------------------------------------------------------------- - else if (GetEnemies()->HasMemory( info.GetAttacker() )) - { - UpdateEnemyMemory(info.GetAttacker(), vAttackPos); - } - // ----------------------------------------------------------------- - // Otherwise just note the position, but don't add enemy to my list - // ----------------------------------------------------------------- - else - { - UpdateEnemyMemory(NULL, vAttackPos); - } - } - - // add pain to the conditions - if ( IsLightDamage( info ) ) - { - SetCondition( COND_LIGHT_DAMAGE ); - } - if ( IsHeavyDamage( info ) ) - { - SetCondition( COND_HEAVY_DAMAGE ); - } - - ForceGatherConditions(); - - // Keep track of how much consecutive damage I have recieved - if ((gpGlobals->curtime - m_flLastDamageTime) < 1.0) - { - m_flSumDamage += info.GetDamage(); - } - else - { - m_flSumDamage = info.GetDamage(); - } - m_flLastDamageTime = gpGlobals->curtime; - if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() ) - m_flLastPlayerDamageTime = gpGlobals->curtime; - GetEnemies()->OnTookDamageFrom( info.GetAttacker() ); - - if (m_flSumDamage > m_iMaxHealth*0.3) - { - SetCondition(COND_REPEATED_DAMAGE); - } - - NotifyFriendsOfDamage( info.GetAttacker() ); - } - - // --------------------------------------------------------------- - // Insert a combat sound so that nearby NPCs know I've been hit - // --------------------------------------------------------------- - CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1024, 0.5, this, SOUNDENT_CHANNEL_INJURY ); - - return 1; -} - - -//========================================================= -// OnTakeDamage_Dying - takedamage function called when a npc's -// corpse is damaged. -//========================================================= -int CAI_BaseNPC::OnTakeDamage_Dying( const CTakeDamageInfo &info ) -{ - if ( info.GetDamageType() & DMG_PLASMA ) - { - if ( m_takedamage != DAMAGE_EVENTS_ONLY ) - { - m_iHealth -= info.GetDamage(); - - if (m_iHealth < -500) - { - UTIL_Remove(this); - } - } - } - return BaseClass::OnTakeDamage_Dying( info ); -} - -//========================================================= -// OnTakeDamage_Dead - takedamage function called when a npc's -// corpse is damaged. -//========================================================= -int CAI_BaseNPC::OnTakeDamage_Dead( const CTakeDamageInfo &info ) -{ - 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 = vec3_origin; - if ( info.GetInflictor() ) - { - vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter(); - VectorNormalize( vecDir ); - g_vecAttackDir = vecDir; - } - -#if 0// turn this back on when the bounding box issues are resolved. - - SetGroundEntity( NULL ); - GetLocalOrigin().z += 1; - - // let the damage scoot the corpse around a bit. - if ( info.GetInflictor() && (info.GetAttacker()->GetSolid() != SOLID_TRIGGER) ) - { - ApplyAbsVelocityImpulse( 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 ( info.GetDamageType() & DMG_GIB_CORPSE ) - { - // Accumulate corpse gibbing damage, so you can gib with multiple hits - if ( m_takedamage != DAMAGE_EVENTS_ONLY ) - { - m_iHealth -= info.GetDamage() * 0.1; - } - } - - if ( info.GetDamageType() & DMG_PLASMA ) - { - if ( m_takedamage != DAMAGE_EVENTS_ONLY ) - { - m_iHealth -= info.GetDamage(); - - if (m_iHealth < -500) - { - UTIL_Remove(this); - } - } - } - - return 1; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CAI_BaseNPC::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity ) -{ - CAI_BaseNPC *pAttacker = pAttackerEntity->MyNPCPointer(); - if ( pAttacker ) - { - const Vector &origin = GetAbsOrigin(); - for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) - { - const float NEAR_Z = 10*12; - const float NEAR_XY_SQ = Square( 50*12 ); - CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i]; - if ( pNpc && pNpc != this ) - { - const Vector &originNpc = pNpc->GetAbsOrigin(); - if ( fabsf( originNpc.z - origin.z ) < NEAR_Z ) - { - if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ ) - { - if ( pNpc->GetSquad() == GetSquad() || IRelationType( pNpc ) == D_LI ) - pNpc->OnFriendDamaged( this, pAttacker ); - } - } - } - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity *pAttacker ) -{ - if ( GetSleepState() != AISS_WAITING_FOR_INPUT ) - { - float distSqToThreat = ( GetAbsOrigin() - pAttacker->GetAbsOrigin() ).LengthSqr(); - - if ( GetSleepState() != AISS_AWAKE && distSqToThreat < Square( 20 * 12 ) ) - Wake(); - - if ( distSqToThreat < Square( 50 * 12 ) ) - ForceGatherConditions(); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsLightDamage( const CTakeDamageInfo &info ) -{ - // ALL nonzero damage is light damage! Mask off COND_LIGHT_DAMAGE if you want to ignore light damage. - return ( info.GetDamage() > 0 ); -} - -bool CAI_BaseNPC::IsHeavyDamage( const CTakeDamageInfo &info ) -{ - return ( info.GetDamage() > 20 ); -} - -void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, int iClassIgnore, CBaseEntity *pEntityIgnore ) -{ - RadiusDamage( info, GetAbsOrigin(), info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore ); -} - - -void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrc, int iClassIgnore, CBaseEntity *pEntityIgnore ) -{ - RadiusDamage( info, vecSrc, info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore ); -} - - -//----------------------------------------------------------------------------- -// Set the contents types that are solid by default to all NPCs -//----------------------------------------------------------------------------- -unsigned int CAI_BaseNPC::PhysicsSolidMaskForEntity( void ) const -{ - return MASK_NPCSOLID; -} - - -//========================================================= - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::DecalTrace( trace_t *pTrace, char const *decalName ) -{ - if ( m_fNoDamageDecal ) - { - m_fNoDamageDecal = false; - // @Note (toml 04-23-03): e3, don't decal face on damage if still alive - return; - } - BaseClass::DecalTrace( pTrace, decalName ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName ) -{ - if ( m_fNoDamageDecal ) - { - m_fNoDamageDecal = false; - // @Note (toml 04-23-03): e3, don't decal face on damage if still alive - return; - } - BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName ); -} - -//--------------------------------------------------------- -// Return the number by which to multiply incoming damage -// based on the hitgroup it hits. This exists mainly so -// that individual NPC's can have more or less resistance -// to damage done to certain hitgroups. -//--------------------------------------------------------- -float CAI_BaseNPC::GetHitgroupDamageMultiplier( int iHitGroup, const CTakeDamageInfo &info ) -{ - switch( iHitGroup ) - { - case HITGROUP_GENERIC: - return 1.0f; - - case HITGROUP_HEAD: - return sk_npc_head.GetFloat(); - - case HITGROUP_CHEST: - return sk_npc_chest.GetFloat(); - - case HITGROUP_STOMACH: - return sk_npc_stomach.GetFloat(); - - case HITGROUP_LEFTARM: - case HITGROUP_RIGHTARM: - return sk_npc_arm.GetFloat(); - - case HITGROUP_LEFTLEG: - case HITGROUP_RIGHTLEG: - return sk_npc_leg.GetFloat(); - - default: - return 1.0f; - } -} - -//========================================================= -// TraceAttack -//========================================================= -void CAI_BaseNPC::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) -{ - m_fNoDamageDecal = false; - if ( m_takedamage == DAMAGE_NO ) - return; - - CTakeDamageInfo subInfo = info; - - SetLastHitGroup( ptr->hitgroup ); - m_nForceBone = ptr->physicsbone; // save this bone for physics forces - - Assert( m_nForceBone > -255 && m_nForceBone < 256 ); - - bool bDebug = showhitlocation.GetBool(); - - switch ( ptr->hitgroup ) - { - case HITGROUP_GENERIC: - if( bDebug ) DevMsg("Hit Location: Generic\n"); - break; - - // hit gear, react but don't bleed - case HITGROUP_GEAR: - subInfo.SetDamage( 0.01 ); - ptr->hitgroup = HITGROUP_GENERIC; - if( bDebug ) DevMsg("Hit Location: Gear\n"); - break; - - case HITGROUP_HEAD: - subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); - if( bDebug ) DevMsg("Hit Location: Head\n"); - break; - - case HITGROUP_CHEST: - subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); - if( bDebug ) DevMsg("Hit Location: Chest\n"); - break; - - case HITGROUP_STOMACH: - subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); - if( bDebug ) DevMsg("Hit Location: Stomach\n"); - break; - - case HITGROUP_LEFTARM: - case HITGROUP_RIGHTARM: - subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); - if( bDebug ) DevMsg("Hit Location: Left/Right Arm\n"); - break - ; - case HITGROUP_LEFTLEG: - case HITGROUP_RIGHTLEG: - subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); - if( bDebug ) DevMsg("Hit Location: Left/Right Leg\n"); - break; - - default: - if( bDebug ) DevMsg("Hit Location: UNKNOWN\n"); - break; - } - - if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK ) ) - { - if( !IsPlayer() || ( IsPlayer() && g_pGameRules->IsMultiplayer() ) ) - { - // NPC's always bleed. Players only bleed in multiplayer. - SpawnBlood( ptr->endpos, vecDir, BloodColor(), subInfo.GetDamage() );// a little surface blood. - } - - TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() ); - - if ( ptr->hitgroup == HITGROUP_HEAD && m_iHealth - subInfo.GetDamage() > 0 ) - { - m_fNoDamageDecal = true; - } - } - - // Airboat gun will impart major force if it's about to kill him.... - if ( info.GetDamageType() & DMG_AIRBOAT ) - { - if ( subInfo.GetDamage() >= GetHealth() ) - { - float flMagnitude = subInfo.GetDamageForce().Length(); - if ( (flMagnitude != 0.0f) && (flMagnitude < 400.0f * 65.0f) ) - { - subInfo.ScaleDamageForce( 400.0f * 65.0f / flMagnitude ); - } - } - } - - if( info.GetInflictor() ) - { - subInfo.SetInflictor( info.GetInflictor() ); - } - else - { - subInfo.SetInflictor( info.GetAttacker() ); - } - - AddMultiDamage( subInfo, this ); -} - -//----------------------------------------------------------------------------- -// Purpose: Checks if point is in spread angle between source and target Pos -// Used to prevent friendly fire -// Input : Source of attack, target position, spread angle -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::PointInSpread( CBaseCombatCharacter *pCheckEntity, const Vector &sourcePos, const Vector &targetPos, const Vector &testPoint, float flSpread, float maxDistOffCenter ) -{ - float distOffLine = CalcDistanceToLine2D( testPoint.AsVector2D(), sourcePos.AsVector2D(), targetPos.AsVector2D() ); - if ( distOffLine < maxDistOffCenter ) - { - Vector toTarget = targetPos - sourcePos; - float distTarget = VectorNormalize(toTarget); - - Vector toTest = testPoint - sourcePos; - float distTest = VectorNormalize(toTest); - // Only reject if target is on other side - if (distTarget > distTest) - { - toTarget.z = 0.0; - toTest.z = 0.0; - - float dotProduct = DotProduct(toTarget,toTest); - if (dotProduct > flSpread) - { - return true; - } - else if( dotProduct > 0.0f ) - { - // If this guy is in front, do the hull/line test: - if( pCheckEntity ) - { - float flBBoxDist = NAI_Hull::Width( pCheckEntity->GetHullType() ); - flBBoxDist *= 1.414f; // sqrt(2) - - // !!!BUGBUG - this 2d check will stop a citizen shooting at a gunship or strider - // if another citizen is between them, even though the strider or gunship may be - // high up in the air (sjb) - distOffLine = CalcDistanceToLine( testPoint, sourcePos, targetPos ); - if( distOffLine < flBBoxDist ) - { - return true; - } - } - } - } - } - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Checks if player is in spread angle between source and target Pos -// Used to prevent friendly fire -// Input : Source of attack, target position, spread angle -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::PlayerInSpread( const Vector &sourcePos, const Vector &targetPos, float flSpread, float maxDistOffCenter, bool ignoreHatedPlayers ) -{ - // loop through all players - for (int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) != D_HT ) ) - { - if ( PointInSpread( pPlayer, sourcePos, targetPos, pPlayer->WorldSpaceCenter(), flSpread, maxDistOffCenter ) ) - return true; - } - } - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Checks if player is in range of given location. Used by NPCs -// to prevent friendly fire -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseEntity *CAI_BaseNPC::PlayerInRange( const Vector &vecLocation, float flDist ) -{ - // loop through all players - for (int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if (pPlayer && (vecLocation - pPlayer->WorldSpaceCenter() ).Length2D() <= flDist) - { - return pPlayer; - } - } - return NULL; -} - - -#define BULLET_WIZZDIST 80.0 -#define SLOPE ( -1.0 / BULLET_WIZZDIST ) - -void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer ) -{ - CBasePlayer *pPlayer; - Vector vecBulletPath; - Vector vecPlayerPath; - Vector vecBulletDir; - Vector vecNearestPoint; - float flDist; - float flBulletDist; - - vecBulletPath = vecEndPos - vecSrc; - vecBulletDir = vecBulletPath; - VectorNormalize(vecBulletDir); - - // see how near this bullet passed by player in a single player game - // for multiplayer, we need to go through the list of clients. - for (int i = 1; i <= gpGlobals->maxClients; i++ ) - { - pPlayer = UTIL_PlayerByIndex( i ); - - if ( !pPlayer ) - continue; - - // Don't hear one's own bullets - if( pPlayer->edict() == pShooter ) - continue; - - vecPlayerPath = pPlayer->EarPosition() - vecSrc; - flDist = DotProduct( vecPlayerPath, vecBulletDir ); - vecNearestPoint = vecSrc + vecBulletDir * flDist; - // FIXME: minus m_vecViewOffset? - flBulletDist = ( vecNearestPoint - pPlayer->EarPosition() ).Length(); - } -} - -//----------------------------------------------------------------------------- -// Hits triggers with raycasts -//----------------------------------------------------------------------------- -class CTriggerTraceEnum : public IEntityEnumerator -{ -public: - CTriggerTraceEnum( Ray_t *pRay, const CTakeDamageInfo &info, const Vector& dir, int contentsMask ) : - m_info( info ), m_VecDir(dir), m_ContentsMask(contentsMask), m_pRay(pRay) - { - } - - virtual bool EnumEntity( IHandleEntity *pHandleEntity ) - { - trace_t tr; - - CBaseEntity *pEnt = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); - - // Done to avoid hitting an entity that's both solid & a trigger. - if ( pEnt->IsSolid() ) - return true; - - enginetrace->ClipRayToEntity( *m_pRay, m_ContentsMask, pHandleEntity, &tr ); - if (tr.fraction < 1.0f) - { - pEnt->DispatchTraceAttack( m_info, m_VecDir, &tr ); - ApplyMultiDamage(); - } - - return true; - } - -private: - Vector m_VecDir; - int m_ContentsMask; - Ray_t *m_pRay; - CTakeDamageInfo m_info; -}; - -void CBaseEntity::TraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir ) -{ - Ray_t ray; - ray.Init( start, end ); - - CTriggerTraceEnum triggerTraceEnum( &ray, info, dir, MASK_SHOT ); - enginetrace->EnumerateEntities( ray, true, &triggerTraceEnum ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const char -//----------------------------------------------------------------------------- -const char *CAI_BaseNPC::GetTracerType( void ) -{ - if ( GetActiveWeapon() ) - { - return GetActiveWeapon()->GetTracerType(); - } - - return BaseClass::GetTracerType(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &vecTracerSrc - -// &tr - -// iTracerType - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ) -{ - if ( GetActiveWeapon() ) - { - GetActiveWeapon()->MakeTracer( vecTracerSrc, tr, iTracerType ); - return; - } - - BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::FireBullets( const FireBulletsInfo_t &info ) -{ -#ifdef HL2_DLL - // If we're shooting at a bullseye, become perfectly accurate if the bullseye demands it - if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE ) - { - CNPC_Bullseye *pBullseye = dynamic_cast(GetEnemy()); - if ( pBullseye && pBullseye->UsePerfectAccuracy() ) - { - FireBulletsInfo_t accurateInfo = info; - accurateInfo.m_vecSpread = vec3_origin; - BaseClass::FireBullets( accurateInfo ); - return; - } - } -#endif - - BaseClass::FireBullets( info ); -} - - -//----------------------------------------------------------------------------- -// Shot statistics -//----------------------------------------------------------------------------- -void CBaseEntity::UpdateShotStatistics( const trace_t &tr ) -{ - if ( ai_shot_stats.GetBool() ) - { - CAI_BaseNPC *pNpc = MyNPCPointer(); - if ( pNpc ) - { - pNpc->m_TotalShots++; - if ( tr.m_pEnt == pNpc->GetEnemy() ) - { - pNpc->m_TotalHits++; - } - } - } -} - - -//----------------------------------------------------------------------------- -// Handle shot entering water -//----------------------------------------------------------------------------- -void CBaseEntity::HandleShotImpactingGlass( const FireBulletsInfo_t &info, - const trace_t &tr, const Vector &vecDir, ITraceFilter *pTraceFilter ) -{ - // Move through the glass until we're at the other side - Vector testPos = tr.endpos + ( vecDir * MAX_GLASS_PENETRATION_DEPTH ); - - CEffectData data; - - data.m_vNormal = tr.plane.normal; - data.m_vOrigin = tr.endpos; - - DispatchEffect( "GlassImpact", data ); - - trace_t penetrationTrace; - - // Re-trace as if the bullet had passed right through - UTIL_TraceLine( testPos, tr.endpos, MASK_SHOT, pTraceFilter, &penetrationTrace ); - - // See if we found the surface again - if ( penetrationTrace.startsolid || tr.fraction == 0.0f || penetrationTrace.fraction == 1.0f ) - return; - - //FIXME: This is technically frustrating MultiDamage, but multiple shots hitting multiple targets in one call - // would do exactly the same anyway... - - // Impact the other side (will look like an exit effect) - DoImpactEffect( penetrationTrace, GetAmmoDef()->DamageType(info.m_iAmmoType) ); - - data.m_vNormal = penetrationTrace.plane.normal; - data.m_vOrigin = penetrationTrace.endpos; - - DispatchEffect( "GlassImpact", data ); - - // Refire the round, as if starting from behind the glass - FireBulletsInfo_t behindGlassInfo; - behindGlassInfo.m_iShots = 1; - behindGlassInfo.m_vecSrc = penetrationTrace.endpos; - behindGlassInfo.m_vecDirShooting = vecDir; - behindGlassInfo.m_vecSpread = vec3_origin; - behindGlassInfo.m_flDistance = info.m_flDistance*( 1.0f - tr.fraction ); - behindGlassInfo.m_iAmmoType = info.m_iAmmoType; - behindGlassInfo.m_iTracerFreq = info.m_iTracerFreq; - behindGlassInfo.m_iDamage = info.m_iDamage; - behindGlassInfo.m_pAttacker = info.m_pAttacker ? info.m_pAttacker : this; - behindGlassInfo.m_nFlags = info.m_nFlags; - - FireBullets( behindGlassInfo ); -} - - -//----------------------------------------------------------------------------- -// Computes the tracer start position -//----------------------------------------------------------------------------- -#define SHOT_UNDERWATER_BUBBLE_DIST 400 - -void CBaseEntity::CreateBubbleTrailTracer( const Vector &vecShotSrc, const Vector &vecShotEnd, const Vector &vecShotDir ) -{ - int nBubbles; - Vector vecBubbleEnd; - float flLengthSqr = vecShotSrc.DistToSqr( vecShotEnd ); - if ( flLengthSqr > SHOT_UNDERWATER_BUBBLE_DIST * SHOT_UNDERWATER_BUBBLE_DIST ) - { - VectorMA( vecShotSrc, SHOT_UNDERWATER_BUBBLE_DIST, vecShotDir, vecBubbleEnd ); - nBubbles = WATER_BULLET_BUBBLES_PER_INCH * SHOT_UNDERWATER_BUBBLE_DIST; - } - else - { - float flLength = sqrt(flLengthSqr) - 0.1f; - nBubbles = WATER_BULLET_BUBBLES_PER_INCH * flLength; - VectorMA( vecShotSrc, flLength, vecShotDir, vecBubbleEnd ); - } - - Vector vecTracerSrc; - ComputeTracerStartPosition( vecShotSrc, &vecTracerSrc ); - UTIL_BubbleTrail( vecTracerSrc, vecBubbleEnd, nBubbles ); -} - - -//========================================================= -//========================================================= -void CAI_BaseNPC::MakeDamageBloodDecal ( int cCount, float flNoise, trace_t *ptr, Vector vecDir ) -{ - // make blood decal on the wall! - trace_t Bloodtr; - Vector vecTraceDir; - int i; - - if ( !IsAlive() ) - { - // dealing with a dead npc. - if ( m_iMaxHealth <= 0 ) - { - // no blood decal for a npc that has already decalled its limit. - return; - } - else - { - m_iMaxHealth -= 1; - } - } - - for ( i = 0 ; i < cCount ; i++ ) - { - vecTraceDir = vecDir; - - vecTraceDir.x += random->RandomFloat( -flNoise, flNoise ); - vecTraceDir.y += random->RandomFloat( -flNoise, flNoise ); - vecTraceDir.z += random->RandomFloat( -flNoise, flNoise ); - - AI_TraceLine( ptr->endpos, ptr->endpos + vecTraceDir * 172, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &Bloodtr); - - if ( Bloodtr.fraction != 1.0 ) - { - UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &tr - -// nDamageType - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::DoImpactEffect( trace_t &tr, int nDamageType ) -{ - if ( GetActiveWeapon() != NULL ) - { - GetActiveWeapon()->DoImpactEffect( tr, nDamageType ); - return; - } - - BaseClass::DoImpactEffect( tr, nDamageType ); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -#define InterruptFromCondition( iCondition ) \ - AI_RemapFromGlobal( ( AI_IdIsLocal( iCondition ) ? GetClassScheduleIdSpace()->ConditionLocalToGlobal( iCondition ) : iCondition ) ) - -void CAI_BaseNPC::SetCondition( int iCondition ) -{ - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return; - } - - m_Conditions.SetBit( interrupt ); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -bool CAI_BaseNPC::HasCondition( int iCondition ) -{ - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return false; - } - - bool bReturn = m_Conditions.GetBit(interrupt); - return (bReturn); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -bool CAI_BaseNPC::HasCondition( int iCondition, bool bUseIgnoreConditions ) -{ - if ( bUseIgnoreConditions ) - return HasCondition( iCondition ); - - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return false; - } - - bool bReturn = m_ConditionsPreIgnore.GetBit(interrupt); - return (bReturn); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -void CAI_BaseNPC::ClearCondition( int iCondition ) -{ - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return; - } - - m_Conditions.ClearBit(interrupt); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -void CAI_BaseNPC::ClearConditions( int *pConditions, int nConditions ) -{ - for ( int i = 0; i < nConditions; ++i ) - { - int iCondition = pConditions[i]; - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - continue; - } - - m_Conditions.ClearBit( interrupt ); - } -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -void CAI_BaseNPC::SetIgnoreConditions( int *pConditions, int nConditions ) -{ - for ( int i = 0; i < nConditions; ++i ) - { - int iCondition = pConditions[i]; - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - continue; - } - - m_InverseIgnoreConditions.ClearBit( interrupt ); // clear means ignore - } -} - -void CAI_BaseNPC::ClearIgnoreConditions( int *pConditions, int nConditions ) -{ - for ( int i = 0; i < nConditions; ++i ) - { - int iCondition = pConditions[i]; - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - continue; - } - - m_InverseIgnoreConditions.SetBit( interrupt ); // set means don't ignore - } -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -bool CAI_BaseNPC::HasInterruptCondition( int iCondition ) -{ - if( !GetCurSchedule() ) - { - return false; - } - - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return false; - } - return ( m_Conditions.GetBit( interrupt ) && GetCurSchedule()->HasInterrupt( interrupt ) ); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -bool CAI_BaseNPC::ConditionInterruptsCurSchedule( int iCondition ) -{ - if( !GetCurSchedule() ) - { - return false; - } - - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return false; - } - return ( GetCurSchedule()->HasInterrupt( interrupt ) ); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -bool CAI_BaseNPC::ConditionInterruptsSchedule( int localScheduleID, int iCondition ) -{ - CAI_Schedule *pSchedule = GetSchedule( localScheduleID ); - if ( !pSchedule ) - return false; - - int interrupt = InterruptFromCondition( iCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return false; - } - return ( pSchedule->HasInterrupt( interrupt ) ); -} - - -//----------------------------------------------------------------------------- -// Returns whether we currently have any interrupt conditions that would -// interrupt the given schedule. -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::HasConditionsToInterruptSchedule( int nLocalScheduleID ) -{ - CAI_Schedule *pSchedule = GetSchedule( nLocalScheduleID ); - if ( !pSchedule ) - return false; - - CAI_ScheduleBits bitsMask; - pSchedule->GetInterruptMask( &bitsMask ); - - CAI_ScheduleBits bitsOut; - AccessConditionBits().And( bitsMask, &bitsOut ); - - return !bitsOut.IsAllClear(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsCustomInterruptConditionSet( int nCondition ) -{ - int interrupt = InterruptFromCondition( nCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return false; - } - - return m_CustomInterruptConditions.GetBit( interrupt ); -} - -//----------------------------------------------------------------------------- -// Purpose: Sets a flag in the custom interrupt flags, translating the condition -// to the proper global space, if necessary -//----------------------------------------------------------------------------- -void CAI_BaseNPC::SetCustomInterruptCondition( int nCondition ) -{ - int interrupt = InterruptFromCondition( nCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return; - } - - m_CustomInterruptConditions.SetBit( interrupt ); -} - -//----------------------------------------------------------------------------- -// Purpose: Clears a flag in the custom interrupt flags, translating the condition -// to the proper global space, if necessary -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ClearCustomInterruptCondition( int nCondition ) -{ - int interrupt = InterruptFromCondition( nCondition ); - - if ( interrupt == -1 ) - { - Assert(0); - return; - } - - m_CustomInterruptConditions.ClearBit( interrupt ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Clears all the custom interrupt flags. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ClearCustomInterruptConditions() -{ - m_CustomInterruptConditions.ClearAllBits(); -} - - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::SetDistLook( float flDistLook ) -{ - m_pSenses->SetDistLook( flDistLook ); -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::QueryHearSound( CSound *pSound ) -{ - if ( pSound->SoundContext() & SOUND_CONTEXT_COMBINE_ONLY ) - return false; - - if ( pSound->SoundContext() & SOUND_CONTEXT_ALLIES_ONLY ) - { - if ( !IsPlayerAlly() ) - return false; - } - - if ( pSound->IsSoundType( SOUND_PLAYER ) && GetState() == NPC_STATE_IDLE && !FVisible( pSound->GetSoundReactOrigin() ) ) - { - // NPC's that are IDLE should disregard player movement sounds if they can't see them. - // This does not affect them hearing the player's weapon. - // !!!BUGBUG - this probably makes NPC's not hear doors opening, because doors opening put SOUND_PLAYER - // in the world, but the door's model will block the FVisible() trace and this code will then - // deduce that the sound can not be heard - return false; - } - - // Disregard footsteps from our own class type - if ( pSound->IsSoundType( SOUND_COMBAT ) && pSound->SoundChannel() == SOUNDENT_CHANNEL_NPC_FOOTSTEP ) - { - if ( pSound->m_hOwner && pSound->m_hOwner->ClassMatches( m_iClassname ) ) - return false; - } - - if( ShouldIgnoreSound( pSound ) ) - return false; - - return true; -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC ) -{ - if ( bOnlyHateOrFearIfNPC && pEntity->IsNPC() ) - { - Disposition_t disposition = IRelationType( pEntity ); - return ( disposition == D_HT || disposition == D_FR ); - } - return true; -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::OnLooked( int iDistance ) -{ - // DON'T let visibility information from last frame sit around! - static int conditionsToClear[] = - { - COND_SEE_HATE, - COND_SEE_DISLIKE, - COND_SEE_ENEMY, - COND_SEE_FEAR, - COND_SEE_NEMESIS, - COND_SEE_PLAYER, - COND_LOST_PLAYER, - COND_ENEMY_WENT_NULL, - }; - - bool bHadSeePlayer = HasCondition(COND_SEE_PLAYER); - - ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); - - AISightIter_t iter; - CBaseEntity *pSightEnt; - - pSightEnt = GetSenses()->GetFirstSeenEntity( &iter ); - - while( pSightEnt ) - { - if ( pSightEnt->IsPlayer() ) - { - // if we see a client, remember that (mostly for scripted AI) - SetCondition(COND_SEE_PLAYER); - m_flLastSawPlayerTime = gpGlobals->curtime; - } - - Disposition_t relation = IRelationType( 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 ( relation != D_NU ) - { - if ( pSightEnt == GetEnemy() ) - { - // we know this ent is visible, so if it also happens to be our enemy, store that now. - SetCondition(COND_SEE_ENEMY); - } - - // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when - // we see npcs other than the Enemy. - switch ( relation ) - { - case D_HT: - { - int priority = IRelationPriority( pSightEnt ); - if (priority < 0) - { - SetCondition(COND_SEE_DISLIKE); - } - else if (priority > 10) - { - SetCondition(COND_SEE_NEMESIS); - } - else - { - SetCondition(COND_SEE_HATE); - } - UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin()); - break; - - } - case D_FR: - UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin()); - SetCondition(COND_SEE_FEAR); - break; - case D_LI: - case D_NU: - break; - default: - DevWarning( 2, "%s can't assess %s\n", GetClassname(), pSightEnt->GetClassname() ); - break; - } - } - - pSightEnt = GetSenses()->GetNextSeenEntity( &iter ); - } - - // Did we lose the player? - if ( bHadSeePlayer && !HasCondition(COND_SEE_PLAYER) ) - { - SetCondition(COND_LOST_PLAYER); - } -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::OnListened() -{ - AISoundIter_t iter; - - CSound *pCurrentSound; - - static int conditionsToClear[] = - { - COND_HEAR_DANGER, - COND_HEAR_COMBAT, - COND_HEAR_WORLD, - COND_HEAR_PLAYER, - COND_HEAR_THUMPER, - COND_HEAR_BUGBAIT, - COND_HEAR_PHYSICS_DANGER, - COND_HEAR_BULLET_IMPACT, - COND_HEAR_MOVE_AWAY, - - COND_NO_HEAR_DANGER, - - COND_SMELL, - }; - - ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); - - pCurrentSound = GetSenses()->GetFirstHeardSound( &iter ); - - while ( pCurrentSound ) - { - // the npc cares about this sound, and it's close enough to hear. - int condition = COND_NONE; - - if ( pCurrentSound->FIsSound() ) - { - // this is an audible sound. - switch( pCurrentSound->SoundTypeNoContext() ) - { - case SOUND_DANGER: - if( gpGlobals->curtime > m_flIgnoreDangerSoundsUntil) - condition = COND_HEAR_DANGER; - break; - - case SOUND_THUMPER: condition = COND_HEAR_THUMPER; break; - case SOUND_BUGBAIT: condition = COND_HEAR_BUGBAIT; break; - case SOUND_COMBAT: - if ( pCurrentSound->SoundChannel() == SOUNDENT_CHANNEL_SPOOKY_NOISE ) - { - condition = COND_HEAR_SPOOKY; - } - else - { - condition = COND_HEAR_COMBAT; - } - break; - - case SOUND_WORLD: condition = COND_HEAR_WORLD; break; - case SOUND_PLAYER: condition = COND_HEAR_PLAYER; break; - case SOUND_BULLET_IMPACT: condition = COND_HEAR_BULLET_IMPACT; break; - case SOUND_PHYSICS_DANGER: condition = COND_HEAR_PHYSICS_DANGER; break; - case SOUND_DANGER_SNIPERONLY:/* silence warning */ break; - case SOUND_MOVE_AWAY: condition = COND_HEAR_MOVE_AWAY; break; - case SOUND_PLAYER_VEHICLE: condition = COND_HEAR_PLAYER; break; - - default: - DevMsg( "**ERROR: NPC %s hearing sound of unknown type %d!\n", GetClassname(), pCurrentSound->SoundType() ); - break; - } - } - else - { - // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent - condition = COND_SMELL; - } - - if ( condition != COND_NONE ) - { - SetCondition( condition ); - } - - pCurrentSound = GetSenses()->GetNextHeardSound( &iter ); - } - - if( !HasCondition( COND_HEAR_DANGER ) ) - { - SetCondition( COND_NO_HEAR_DANGER ); - } - - // Sound outputs - if ( HasCondition( COND_HEAR_WORLD ) ) - { - m_OnHearWorld.FireOutput(this, this); - } - - if ( HasCondition( COND_HEAR_PLAYER ) ) - { - m_OnHearPlayer.FireOutput(this, this); - } - - if ( HasCondition( COND_HEAR_COMBAT ) || - HasCondition( COND_HEAR_BULLET_IMPACT ) || - HasCondition( COND_HEAR_DANGER ) ) - { - m_OnHearCombat.FireOutput(this, this); - } -} - -//========================================================= -// FValidateHintType - tells use whether or not the npc cares -// about the type of Hint Node given -//========================================================= -bool CAI_BaseNPC::FValidateHintType ( CAI_Hint *pHint ) -{ - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Override in subclasses to associate specific hint types -// with activities -//----------------------------------------------------------------------------- -Activity CAI_BaseNPC::GetHintActivity( short sHintType, Activity HintsActivity ) -{ - if ( HintsActivity != ACT_INVALID ) - return HintsActivity; - - return ACT_IDLE; -} - -//----------------------------------------------------------------------------- -// Purpose: Override in subclasses to give specific hint types delays -// before they can be used again -// Input : -// Output : -//----------------------------------------------------------------------------- -float CAI_BaseNPC::GetHintDelay( short sHintType ) -{ - return 0; -} - -//----------------------------------------------------------------------------- -// Purpose: Return incoming grenade if spotted -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseGrenade* CAI_BaseNPC::IncomingGrenade(void) -{ - int iDist; - - AIEnemiesIter_t iter; - for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) - { - CBaseGrenade* pBG = dynamic_cast((CBaseEntity*)pEMemory->hEnemy); - - // Make sure this memory is for a grenade and grenade is not dead - if (!pBG || pBG->m_lifeState == LIFE_DEAD) - continue; - - // Make sure it's visible - if (!FVisible(pBG)) - continue; - - // Check if it's near me - iDist = ( pBG->GetAbsOrigin() - GetAbsOrigin() ).Length(); - if ( iDist <= NPC_GRENADE_FEAR_DIST ) - return pBG; - - // Check if it's headed towards me - Vector vGrenadeDir = GetAbsOrigin() - pBG->GetAbsOrigin(); - Vector vGrenadeVel; - pBG->GetVelocity( &vGrenadeVel, NULL ); - VectorNormalize(vGrenadeDir); - VectorNormalize(vGrenadeVel); - float flDotPr = DotProduct(vGrenadeDir, vGrenadeVel); - if (flDotPr > 0.85) - return pBG; - } - return NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: Check my physical state with the environment -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::TryRestoreHull(void) -{ - if ( IsUsingSmallHull() && GetCurSchedule() ) - { - trace_t tr; - Vector vUpBit = GetAbsOrigin(); - vUpBit.z += 1; - - AI_TraceHull( GetAbsOrigin(), vUpBit, GetHullMins(), - GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); - if ( !tr.startsolid && (tr.fraction == 1.0) ) - { - SetHullSizeNormal(); - } - } -} - -//========================================================= -//========================================================= -int CAI_BaseNPC::GetSoundInterests( void ) -{ - return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_PLAYER_VEHICLE | - SOUND_BULLET_IMPACT; -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -int CAI_BaseNPC::GetSoundPriority( CSound *pSound ) -{ - int iSoundTypeNoContext = pSound->SoundTypeNoContext(); - int iSoundContext = pSound->SoundContext(); - - if( iSoundTypeNoContext & SOUND_DANGER ) - { - return SOUND_PRIORITY_HIGHEST; - } - - if( iSoundTypeNoContext & SOUND_COMBAT ) - { - if( iSoundContext & SOUND_CONTEXT_EXPLOSION ) - { - return SOUND_PRIORITY_VERY_HIGH; - } - - return SOUND_PRIORITY_HIGH; - } - - return SOUND_PRIORITY_NORMAL; -} - -//--------------------------------------------------------- -// Return the loudest sound of this type in the sound list -//--------------------------------------------------------- -CSound *CAI_BaseNPC::GetLoudestSoundOfType( int iType ) -{ - return CSoundEnt::GetLoudestSoundOfType( iType, EarPosition() ); -} - -//========================================================= -// GetBestSound - returns a pointer to the sound the npc -// should react to. Right now responds only to nearest sound. -//========================================================= -CSound* CAI_BaseNPC::GetBestSound( int validTypes ) -{ - if ( m_pLockedBestSound->m_iType != SOUND_NONE ) - return m_pLockedBestSound; - CSound *pResult = GetSenses()->GetClosestSound( false, validTypes ); - if ( pResult == NULL) - DevMsg( "Warning: NULL Return from GetBestSound\n" ); // condition previously set now no longer true. Have seen this when play too many sounds... - return pResult; -} - -//========================================================= -// PBestScent - returns a pointer to the scent the npc -// should react to. Right now responds only to nearest scent -//========================================================= -CSound* CAI_BaseNPC::GetBestScent( void ) -{ - CSound *pResult = GetSenses()->GetClosestSound( true ); - if ( pResult == NULL) - DevMsg("Warning: NULL Return from GetBestScent\n" ); - return pResult; -} - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::LockBestSound() -{ - UnlockBestSound(); - CSound *pBestSound = GetBestSound(); - if ( pBestSound ) - *m_pLockedBestSound = *pBestSound; -} - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::UnlockBestSound() -{ - if ( m_pLockedBestSound->m_iType != SOUND_NONE ) - { - m_pLockedBestSound->m_iType = SOUND_NONE; - OnListened(); // reset hearing conditions - } -} - -//----------------------------------------------------------------------------- -// Purpose: Return true if the specified sound is visible. Handles sounds generated by entities correctly. -// Input : *pSound - -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::SoundIsVisible( CSound *pSound ) -{ - CBaseEntity *pBlocker = NULL; - if ( !FVisible( pSound->GetSoundReactOrigin(), MASK_OPAQUE, &pBlocker ) ) - { - // Is the blocker the sound owner? - if ( pBlocker && pBlocker == pSound->m_hOwner ) - return true; - - return false; - } - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns true if target is in legal range of eye movements -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::ValidEyeTarget(const Vector &lookTargetPos) -{ - Vector vHeadDir = HeadDirection3D( ); - Vector lookTargetDir = lookTargetPos - EyePosition(); - VectorNormalize(lookTargetDir); - - // Only look if it doesn't crank my eyeballs too far - float dotPr = DotProduct(lookTargetDir, vHeadDir); - if (dotPr > 0.7) - { - return true; - } - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Integrate head turn over time -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::SetHeadDirection( const Vector &vTargetPos, float flInterval) -{ - if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD)) - return; - -#ifdef DEBUG_LOOK - // Draw line in body, head, and eye directions - Vector vEyePos = EyePosition(); - Vector vHeadDir; - HeadDirection3D(&vHeadDir); - Vector vBodyDir; - BodyDirection2D(&vBodyDir); - - //UNDONE <> - // currently eye dir just returns head dir, so use vTargetPos for now - //Vector vEyeDir; w - //EyeDirection3D(&vEyeDir); - NDebugOverlay::Line( vEyePos, vEyePos+(50*vHeadDir), 255, 0, 0, false, 0.1 ); - NDebugOverlay::Line( vEyePos, vEyePos+(50*vBodyDir), 0, 255, 0, false, 0.1 ); - NDebugOverlay::Line( vEyePos, vTargetPos, 0, 0, 255, false, 0.1 ); -#endif - - //-------------------------------------- - // Set head yaw - //-------------------------------------- - float flDesiredYaw = VecToYaw(vTargetPos - GetLocalOrigin()) - GetLocalAngles().y; - if (flDesiredYaw > 180) - flDesiredYaw -= 360; - if (flDesiredYaw < -180) - flDesiredYaw += 360; - - float iRate = 0.8; - - // Make frame rate independent - float timeToUse = flInterval; - while (timeToUse > 0) - { - m_flHeadYaw = (iRate * m_flHeadYaw) + (1-iRate)*flDesiredYaw; - timeToUse -= 0.1; - } - if (m_flHeadYaw > 360) m_flHeadYaw = 0; - - m_flHeadYaw = SetBoneController( 0, m_flHeadYaw ); - - - //-------------------------------------- - // Set head pitch - //-------------------------------------- - Vector vEyePosition = EyePosition(); - float fTargetDist = (vTargetPos - vEyePosition).Length(); - float fVertDist = vTargetPos.z - vEyePosition.z; - float flDesiredPitch = -RAD2DEG(atan(fVertDist/fTargetDist)); - - // Make frame rate independent - timeToUse = flInterval; - while (timeToUse > 0) - { - m_flHeadPitch = (iRate * m_flHeadPitch) + (1-iRate)*flDesiredPitch; - timeToUse -= 0.1; - } - if (m_flHeadPitch > 360) m_flHeadPitch = 0; - - SetBoneController( 1, m_flHeadPitch ); - -} - -//------------------------------------------------------------------------------ -// Purpose : Calculate the NPC's eye direction in 2D world space -// Input : -// Output : -//------------------------------------------------------------------------------ -Vector CAI_BaseNPC::EyeDirection2D( void ) -{ - // UNDONE - // For now just return head direction.... - return HeadDirection2D( ); -} - -//------------------------------------------------------------------------------ -// Purpose : Calculate the NPC's eye direction in 2D world space -// Input : -// Output : -//------------------------------------------------------------------------------ -Vector CAI_BaseNPC::EyeDirection3D( void ) -{ - // UNDONE //<> - // For now just return head direction.... - return HeadDirection3D( ); -} - -//------------------------------------------------------------------------------ -// Purpose : Calculate the NPC's head direction in 2D world space -// Input : -// Output : -//------------------------------------------------------------------------------ -Vector CAI_BaseNPC::HeadDirection2D( void ) -{ - // UNDONE <> - // This does not account for head rotations in the animation, - // only those done via bone controllers - - // Head yaw is in local cooridnate so it must be added to the body's yaw - QAngle bodyAngles = BodyAngles(); - float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y; - - // Convert head yaw into facing direction - return UTIL_YawToVector( flWorldHeadYaw ); -} - -//------------------------------------------------------------------------------ -// Purpose : Calculate the NPC's head direction in 3D world space -// Input : -// Output : -//------------------------------------------------------------------------------ -Vector CAI_BaseNPC::HeadDirection3D( void ) -{ - Vector vHeadDirection; - - // UNDONE <> - // This does not account for head rotations in the animation, - // only those done via bone controllers - - // Head yaw is in local cooridnate so it must be added to the body's yaw - QAngle bodyAngles = BodyAngles(); - float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y; - - // Convert head yaw into facing direction - AngleVectors( QAngle( m_flHeadPitch, flWorldHeadYaw, 0), &vHeadDirection ); - return vHeadDirection; -} - - -//----------------------------------------------------------------------------- -// Purpose: Look at other NPCs and clients from time to time -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseEntity *CAI_BaseNPC::EyeLookTarget( void ) -{ - if (m_flNextEyeLookTime < gpGlobals->curtime) - { - CBaseEntity* pBestEntity = NULL; - float fBestDist = MAX_COORD_RANGE; - float fTestDist; - - CBaseEntity *pEntity = NULL; - - for ( CEntitySphereQuery sphere( GetAbsOrigin(), 1024, 0 ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) - { - if (pEntity == this) - { - continue; - } - CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); - if (pNPC || (pEntity->GetFlags() & FL_CLIENT)) - { - fTestDist = (GetAbsOrigin() - pEntity->EyePosition()).Length(); - if (fTestDist < fBestDist) - { - if (ValidEyeTarget(pEntity->EyePosition())) - { - fBestDist = fTestDist; - pBestEntity = pEntity; - } - } - } - } - if (pBestEntity) - { - m_flNextEyeLookTime = gpGlobals->curtime + random->RandomInt(1,5); - m_hEyeLookTarget = pBestEntity; - } - } - return m_hEyeLookTarget; -} - - -//----------------------------------------------------------------------------- -// Purpose: Set direction that the NPC aiming their gun -// returns true is the passed Vector is in -// the caller's forward aim cone. The dot product is performed -// in 2d, making the view cone infinitely tall. By default, the -// callers aim cone is assumed to be very narrow -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::FInAimCone( const Vector &vecSpot ) -{ - Vector los = ( vecSpot - GetAbsOrigin() ); - - // do this in 2D - los.z = 0; - VectorNormalize( los ); - - Vector facingDir = BodyDirection2D( ); - - float flDot = DotProduct( los, facingDir ); - - if (CapabilitiesGet() & bits_CAP_AIM_GUN) - { - // FIXME: query current animation for ranges - return ( flDot > DOT_30DEGREE ); - } - - if ( flDot > 0.994 )//!!!BUGBUG - magic number same as FacingIdeal(), what is this? - return true; - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Set direction that the NPC aiming their gun -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::SetAim( const Vector &aimDir ) -{ - QAngle angDir; - VectorAngles( aimDir, angDir ); - - float curPitch = GetPoseParameter( "aim_pitch" ); - float curYaw = GetPoseParameter( "aim_yaw" ); - - float newPitch; - float newYaw; - - if( GetEnemy() ) - { - // clamp and dampen movement - newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch ); - - float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y ); - // float flNewTargetYaw = UTIL_ApproachAngle( flRelativeYaw, curYaw, 20 ); - // float newYaw = curYaw + 0.8 * UTIL_AngleDiff( flNewTargetYaw, curYaw ); - newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw ); - } - else - { - // Sweep your weapon more slowly if you're not fighting someone - newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch ); - - float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y ); - newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw ); - } - - newPitch = AngleNormalize( newPitch ); - newYaw = AngleNormalize( newYaw ); - - SetPoseParameter( "aim_pitch", newPitch ); - SetPoseParameter( "aim_yaw", newYaw ); - - // Msg("yaw %.0f (%.0f %.0f)\n", newYaw, angDir.y, GetAbsAngles().y ); - - // Calculate our interaction yaw. - // If we've got a small adjustment off our abs yaw, use that. - // Otherwise, use our abs yaw. - if ( fabs(newYaw) < 20 ) - { - m_flInteractionYaw = angDir.y; - } - else - { - m_flInteractionYaw = GetAbsAngles().y; - } -} - - -void CAI_BaseNPC::RelaxAim( ) -{ - float curPitch = GetPoseParameter( "aim_pitch" ); - float curYaw = GetPoseParameter( "aim_yaw" ); - - // dampen existing aim - float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) ); - float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) ); - - SetPoseParameter( "aim_pitch", newPitch ); - SetPoseParameter( "aim_yaw", newYaw ); - // DevMsg("relax aim %.0f %0.f\n", newPitch, newYaw ); -} - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::AimGun() -{ - if (GetEnemy()) - { - Vector vecShootOrigin; - - vecShootOrigin = Weapon_ShootPosition(); - Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false ); - - SetAim( vecShootDir ); - } - else - { - RelaxAim( ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Set direction that the NPC is looking -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::MaintainLookTargets ( float flInterval ) -{ - // -------------------------------------------------------- - // Try to look at enemy if I have one - // -------------------------------------------------------- - if ((CBaseEntity*)GetEnemy()) - { - if ( ValidEyeTarget(GetEnemy()->EyePosition()) ) - { - SetHeadDirection(GetEnemy()->EyePosition(),flInterval); - SetViewtarget( GetEnemy()->EyePosition() ); - return; - } - } - -#if 0 - // -------------------------------------------------------- - // First check if I've been assigned to look at an entity - // -------------------------------------------------------- - CBaseEntity *lookTarget = EyeLookTarget(); - if (lookTarget && ValidEyeTarget(lookTarget->EyePosition())) - { - SetHeadDirection(lookTarget->EyePosition(),flInterval); - SetViewtarget( lookTarget->EyePosition() ); - return; - } -#endif - - // -------------------------------------------------------- - // If I'm moving, look at my target position - // -------------------------------------------------------- - if (GetNavigator()->IsGoalActive() && ValidEyeTarget(GetNavigator()->GetCurWaypointPos())) - { - SetHeadDirection(GetNavigator()->GetCurWaypointPos(),flInterval); - SetViewtarget( GetNavigator()->GetCurWaypointPos() ); - return; - } - - - // ------------------------------------- - // If I hear a combat sounds look there - // ------------------------------------- - if ( HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER) ) - { - CSound *pSound = GetBestSound(); - - if ( pSound && pSound->IsSoundType(SOUND_COMBAT | SOUND_DANGER) ) - { - if (ValidEyeTarget( pSound->GetSoundOrigin() )) - { - SetHeadDirection(pSound->GetSoundOrigin(),flInterval); - SetViewtarget( pSound->GetSoundOrigin() ); - return; - } - } - } - - // ------------------------------------- - // Otherwise just look around randomly - // ------------------------------------- - - // Check that look target position is still valid - if (m_flNextEyeLookTime > gpGlobals->curtime) - { - if (!ValidEyeTarget(m_vEyeLookTarget)) - { - // Force choosing of new look target - m_flNextEyeLookTime = 0; - } - } - - if (m_flNextEyeLookTime < gpGlobals->curtime) - { - Vector vBodyDir = BodyDirection2D( ); - - /* - Vector lookSpread = Vector(0.82,0.82,0.22); - float x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); - float y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); - float z = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); - - QAngle angles; - VectorAngles( vBodyDir, angles ); - Vector forward, right, up; - AngleVectors( angles, &forward, &right, &up ); - - Vector vecDir = vBodyDir + (x * lookSpread.x * forward) + (y * lookSpread.y * right) + (z * lookSpread.z * up); - float lookDist = random->RandomInt(50,2000); - m_vEyeLookTarget = EyePosition() + lookDist*vecDir; - */ - m_vEyeLookTarget = EyePosition() + 500*vBodyDir; - m_flNextEyeLookTime = gpGlobals->curtime + 0.5; // random->RandomInt(1,5); - } - SetHeadDirection(m_vEyeLookTarget,flInterval); - - // ---------------------------------------------------- - // Integrate eye turn in frame rate independent manner - // ---------------------------------------------------- - float timeToUse = flInterval; - while (timeToUse > 0) - { - m_vCurEyeTarget = ((1 - m_flEyeIntegRate) * m_vCurEyeTarget + m_flEyeIntegRate * m_vEyeLookTarget); - timeToUse -= 0.1; - } - SetViewtarget( m_vCurEyeTarget ); -} - - -//----------------------------------------------------------------------------- -// Let the motor deal with turning presentation issues -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::MaintainTurnActivity( ) -{ - AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainTurnActivity ); - GetMotor()->MaintainTurnActivity( ); -} - - -//----------------------------------------------------------------------------- -// Here's where all motion occurs -//----------------------------------------------------------------------------- -void CAI_BaseNPC::PerformMovement() -{ - // don't bother to move if the npc isn't alive - if (!IsAlive()) - return; - - AI_PROFILE_SCOPE(CAI_BaseNPC_PerformMovement); - g_AIMoveTimer.Start(); - - float flInterval = ( m_flTimeLastMovement != FLT_MAX ) ? gpGlobals->curtime - m_flTimeLastMovement : 0.1; - - m_pNavigator->Move( ROUND_TO_TICKS( flInterval ) ); - m_flTimeLastMovement = gpGlobals->curtime; - - g_AIMoveTimer.End(); - -} - -//----------------------------------------------------------------------------- -// Updates to npc after movement is completed -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::PostMovement() -{ - AI_PROFILE_SCOPE( CAI_BaseNPC_PostMovement ); - - InvalidateBoneCache(); - - if ( GetModelPtr() && GetModelPtr()->SequencesAvailable() ) - { - float flInterval = GetAnimTimeInterval(); - - if ( CapabilitiesGet() & bits_CAP_AIM_GUN ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_PM_AimGun ); - AimGun(); - } - - // set look targets for npcs with animated faces - if ( CapabilitiesGet() & bits_CAP_ANIMATEDFACE ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainLookTargets ); - MaintainLookTargets(flInterval); - } - - } - - - { - AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainTurnActivity ); - // update turning as needed - MaintainTurnActivity( ); - } -} - - -//----------------------------------------------------------------------------- - -float g_AINextDisabledMessageTime; -extern bool IsInCommentaryMode( void ); - -bool CAI_BaseNPC::PreThink( void ) -{ - // ---------------------------------------------------------- - // Skip AI if its been disabled or networks haven't been - // loaded, and put a warning message on the screen - // - // Don't do this if the convar wants it hidden - // ---------------------------------------------------------- - if ( (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI || !g_pAINetworkManager->NetworksLoaded()) ) - { - if ( gpGlobals->curtime >= g_AINextDisabledMessageTime && !IsInCommentaryMode() ) - { - g_AINextDisabledMessageTime = gpGlobals->curtime + 0.5f; - - hudtextparms_s tTextParam; - tTextParam.x = 0.7; - tTextParam.y = 0.65; - tTextParam.effect = 0; - tTextParam.r1 = 255; - tTextParam.g1 = 255; - tTextParam.b1 = 255; - tTextParam.a1 = 255; - tTextParam.r2 = 255; - tTextParam.g2 = 255; - tTextParam.b2 = 255; - tTextParam.a2 = 255; - tTextParam.fadeinTime = 0; - tTextParam.fadeoutTime = 0; - tTextParam.holdTime = 0.6; - tTextParam.fxTime = 0; - tTextParam.channel = 1; - UTIL_HudMessageAll( tTextParam, "A.I. Disabled...\n" ); - } - SetActivity( ACT_IDLE ); - return false; - } - - // -------------------------------------------------------- - // If debug stepping - // -------------------------------------------------------- - if (CAI_BaseNPC::m_nDebugBits & bits_debugStepAI) - { - if (m_nDebugCurIndex >= CAI_BaseNPC::m_nDebugPauseIndex) - { - if (!GetNavigator()->IsGoalActive()) - { - m_flPlaybackRate = 0; - } - return false; - } - else - { - m_flPlaybackRate = 1; - } - } - - if ( m_hOpeningDoor.Get() && AIIsDebuggingDoors( this ) ) - { - NDebugOverlay::Line( EyePosition(), m_hOpeningDoor->WorldSpaceCenter(), 255, 255, 255, false, .1 ); - } - - return true; -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::RunAnimation( void ) -{ - VPROF_BUDGET( "CAI_BaseNPC_RunAnimation", VPROF_BUDGETGROUP_SERVER_ANIM ); - - if ( !GetModelPtr() ) - return; - - float flInterval = GetAnimTimeInterval(); - - StudioFrameAdvance( ); // animate - - if ((CAI_BaseNPC::m_nDebugBits & bits_debugStepAI) && !GetNavigator()->IsGoalActive()) - { - flInterval = 0; - } - - // 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_NPCState != NPC_STATE_SCRIPT && m_NPCState != NPC_STATE_DEAD && m_Activity == ACT_IDLE && IsActivityFinished() ) - { - int iSequence; - - // FIXME: this doesn't reissue a translation, so if the idle activity translation changes over time, it'll never get reset - if ( SequenceLoops() ) - { - // animation does loop, which means we're playing subtle idle. Might need to fidget. - iSequence = SelectWeightedSequence ( m_translatedActivity ); - } - 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 = SelectHeaviestSequence ( m_translatedActivity ); - } - if ( iSequence != ACTIVITY_NOT_AVAILABLE ) - { - ResetSequence( iSequence ); // Set to new anim (if it's there) - - //Adrian: Basically everywhere else in the AI code this variable gets set to whatever our sequence is. - //But here it doesn't and not setting it causes any animation set through here to be stomped by the - //ideal sequence before it has a chance of playing out (since there's code that reselects the ideal - //sequence if it doesn't match the current one). - if ( hl2_episodic.GetBool() ) - { - m_nIdealSequence = iSequence; - } - } - } - - DispatchAnimEvents( this ); -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::PostRun( void ) -{ - AI_PROFILE_SCOPE(CAI_BaseNPC_PostRun); - - g_AIPostRunTimer.Start(); - - if ( !IsMoving() ) - { - if ( GetIdealActivity() == ACT_WALK || - GetIdealActivity() == ACT_RUN || - GetIdealActivity() == ACT_WALK_AIM || - GetIdealActivity() == ACT_RUN_AIM ) - { - PostRunStopMoving(); - } - } - - RunAnimation(); - - // update slave items (weapons) - Weapon_FrameUpdate(); - - g_AIPostRunTimer.End(); -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::PostRunStopMoving() -{ - DbgNavMsg1( this, "NPC %s failed to stop properly, slamming activity\n", GetDebugName() ); - if ( !GetNavigator()->SetGoalFromStoppingPath() ) - SetIdealActivity( GetStoppedActivity() ); // This is to prevent running in place -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::ShouldAlwaysThink() -{ - // @TODO (toml 07-08-03): This needs to be beefed up. - // There are simple cases already seen where an AI can briefly leave - // the PVS while navigating to the player. Perhaps should incorporate a heuristic taking into - // account mode, enemy, last time saw player, player range etc. For example, if enemy is player, - // and player is within 100 feet, and saw the player within the past 15 seconds, keep running... - return HasSpawnFlags(SF_NPC_ALWAYSTHINK); -} - - -bool CAI_BaseNPC::ShouldPlayerAvoid( void ) -{ - if ( GetState() == NPC_STATE_SCRIPT ) - return true; - - if ( IsInAScript() ) - return true; - - if ( IsInLockedScene() == true ) - return true; - - if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) ) - return true; - - return false; -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::UpdateEfficiency( bool bInPVS ) -{ - // Sleeping NPCs always dormant - if ( GetSleepState() != AISS_AWAKE ) - { - SetEfficiency( AIE_DORMANT ); - return; - } - - m_bInChoreo = ( GetState() == NPC_STATE_SCRIPT || IsCurSchedule( SCHED_SCENE_GENERIC, false ) ); - - if ( !ShouldUseEfficiency() ) - { - SetEfficiency( AIE_NORMAL ); - SetMoveEfficiency( AIME_NORMAL ); - return; - } - - //--------------------------------- - - CBasePlayer *pPlayer = AI_GetSinglePlayer(); - static Vector vPlayerEyePosition; - static Vector vPlayerForward; - static int iPrevFrame = -1; - if ( gpGlobals->framecount != iPrevFrame ) - { - iPrevFrame = gpGlobals->framecount; - if ( pPlayer ) - { - pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL ); - } - } - - Vector vToNPC = GetAbsOrigin() - vPlayerEyePosition; - float playerDist = VectorNormalize( vToNPC ); - bool bPlayerFacing; - - bool bClientPVSExpanded = UTIL_ClientPVSIsExpanded(); - - if ( pPlayer ) - { - bPlayerFacing = ( bClientPVSExpanded || ( bInPVS && vPlayerForward.Dot( vToNPC ) > 0 ) ); - } - else - { - playerDist = 0; - bPlayerFacing = true; - } - - //--------------------------------- - - bool bInVisibilityPVS = ( bClientPVSExpanded && UTIL_FindClientInVisibilityPVS( edict() ) != NULL ); - - //--------------------------------- - - if ( ( bInPVS && ( bPlayerFacing || playerDist < 25*12 ) ) || bClientPVSExpanded ) - { - SetMoveEfficiency( AIME_NORMAL ); - } - else - { - SetMoveEfficiency( AIME_EFFICIENT ); - } - - //--------------------------------- - - if ( !IsRetail() && ai_efficiency_override.GetInt() > AIE_NORMAL && ai_efficiency_override.GetInt() <= AIE_DORMANT ) - { - SetEfficiency( (AI_Efficiency_t)ai_efficiency_override.GetInt() ); - return; - } - - //--------------------------------- - - // Some conditions will always force normal - if ( gpGlobals->curtime - GetLastAttackTime() < .15 ) - { - SetEfficiency( AIE_NORMAL ); - return; - } - - bool bFramerateOk = ( gpGlobals->frametime < ai_frametime_limit.GetFloat() ); - - if ( m_bForceConditionsGather || - gpGlobals->curtime - GetLastAttackTime() < .2 || - gpGlobals->curtime - m_flLastDamageTime < .2 || - ( GetState() < NPC_STATE_IDLE || GetState() > NPC_STATE_SCRIPT ) || - ( ( bInPVS || bInVisibilityPVS ) && - ( ( GetTask() && !TaskIsRunning() ) || - GetTaskInterrupt() > 0 || - m_bInChoreo ) ) ) - { - SetEfficiency( ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT ); - return; - } - - AI_Efficiency_t minEfficiency; - - if ( !ShouldDefaultEfficient() ) - { - minEfficiency = ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT; - } - else - { - minEfficiency = ( bFramerateOk ) ? AIE_EFFICIENT : AIE_VERY_EFFICIENT; - } - - // Stay normal if there's any chance of a relevant danger sound - bool bPotentialDanger = false; - - if ( GetSoundInterests() & SOUND_DANGER ) - { - int iSound = CSoundEnt::ActiveList(); - - while ( iSound != SOUNDLIST_EMPTY ) - { - CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); - - float hearingSensitivity = HearingSensitivity(); - Vector vEarPosition = EarPosition(); - - if ( pCurrentSound && (SOUND_DANGER & pCurrentSound->SoundType()) ) - { - float flHearDistanceSq = pCurrentSound->Volume() * hearingSensitivity; - flHearDistanceSq *= flHearDistanceSq; - if ( pCurrentSound->GetSoundOrigin().DistToSqr( vEarPosition ) <= flHearDistanceSq ) - { - bPotentialDanger = true; - break; - } - } - - iSound = pCurrentSound->NextSound(); - } - } - - if ( bPotentialDanger ) - { - SetEfficiency( minEfficiency ); - return; - } - - //--------------------------------- - - if ( !pPlayer ) - { - // No heuristic currently for dedicated servers - SetEfficiency( minEfficiency ); - return; - } - - enum - { - NEAR, - MID, - FAR - }; - - int range; - if ( bInPVS ) - { - if ( playerDist < 15*12 ) - { - SetEfficiency( minEfficiency ); - return; - } - - range = ( playerDist < 50*12 ) ? NEAR : - ( playerDist < 200*12 ) ? MID : FAR; - } - else - { - range = ( playerDist < 25*12 ) ? NEAR : - ( playerDist < 100*12 ) ? MID : FAR; - } - - // Efficiency mappings - int state = GetState(); - if (state == NPC_STATE_SCRIPT ) // Treat script as alert. Already confirmed not in PVS - state = NPC_STATE_ALERT; - - static AI_Efficiency_t mappings[] = - { - // Idle - // In PVS - // Facing - AIE_NORMAL, - AIE_EFFICIENT, - AIE_EFFICIENT, - // Not facing - AIE_EFFICIENT, - AIE_EFFICIENT, - AIE_VERY_EFFICIENT, - // Not in PVS - AIE_VERY_EFFICIENT, - AIE_SUPER_EFFICIENT, - AIE_SUPER_EFFICIENT, - // Alert - // In PVS - // Facing - AIE_NORMAL, - AIE_EFFICIENT, - AIE_EFFICIENT, - // Not facing - AIE_NORMAL, - AIE_EFFICIENT, - AIE_EFFICIENT, - // Not in PVS - AIE_EFFICIENT, - AIE_VERY_EFFICIENT, - AIE_SUPER_EFFICIENT, - // Combat - // In PVS - // Facing - AIE_NORMAL, - AIE_NORMAL, - AIE_EFFICIENT, - // Not facing - AIE_NORMAL, - AIE_EFFICIENT, - AIE_EFFICIENT, - // Not in PVS - AIE_NORMAL, - AIE_EFFICIENT, - AIE_VERY_EFFICIENT, - }; - - static const int stateBase[] = { 0, 9, 18 }; - const int NOT_FACING_OFFSET = 3; - const int NO_PVS_OFFSET = 6; - - int iStateOffset = stateBase[state - NPC_STATE_IDLE] ; - int iFacingOffset = (!bInPVS || bPlayerFacing) ? 0 : NOT_FACING_OFFSET; - int iPVSOffset = (bInPVS) ? 0 : NO_PVS_OFFSET; - int iMapping = iStateOffset + iPVSOffset + iFacingOffset + range; - - Assert( iMapping < ARRAYSIZE( mappings ) ); - - AI_Efficiency_t efficiency = mappings[iMapping]; - - //--------------------------------- - - AI_Efficiency_t maxEfficiency = AIE_SUPER_EFFICIENT; - if ( bInVisibilityPVS && state >= NPC_STATE_ALERT ) - { - maxEfficiency = AIE_EFFICIENT; - } - else if ( bInVisibilityPVS || HasCondition( COND_SEE_PLAYER ) ) - { - maxEfficiency = AIE_VERY_EFFICIENT; - } - - //--------------------------------- - - SetEfficiency( clamp( efficiency, minEfficiency, maxEfficiency ) ); -} - - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::UpdateSleepState( bool bInPVS ) -{ - if ( GetSleepState() > AISS_AWAKE ) - { - CBasePlayer *pLocalPlayer = AI_GetSinglePlayer(); - if ( !pLocalPlayer ) - { - if ( gpGlobals->maxClients > 1 ) - { - Wake(); - } - else - { - Warning( "CAI_BaseNPC::UpdateSleepState called with NULL pLocalPlayer\n" ); - } - return; - } - - if ( m_flWakeRadius > .1 && !(pLocalPlayer->GetFlags() & FL_NOTARGET) && ( pLocalPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() <= Square(m_flWakeRadius) ) - Wake(); - else if ( GetSleepState() == AISS_WAITING_FOR_PVS ) - { - if ( bInPVS ) - Wake(); - } - else if ( GetSleepState() == AISS_WAITING_FOR_THREAT ) - { - if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) - Wake(); - else - { - if ( bInPVS ) - { - for (int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - if ( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) && pPlayer->FVisible( this ) ) - Wake(); - } - } - - // Should check for visible danger sounds - if ( (GetSoundInterests() & SOUND_DANGER) && !(HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN)) ) - { - int iSound = CSoundEnt::ActiveList(); - - while ( iSound != SOUNDLIST_EMPTY ) - { - CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); - Assert( pCurrentSound ); - - if ( (pCurrentSound->SoundType() & SOUND_DANGER) && - GetSenses()->CanHearSound( pCurrentSound ) && - SoundIsVisible( pCurrentSound )) - { - Wake(); - break; - } - - iSound = pCurrentSound->NextSound(); - } - } - } - } - } - else - { - // NPC is awake - // Don't let an NPC sleep if they're running a script! - if( !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT ) - { - if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS) ) - { - if( !HasCondition(COND_IN_PVS) ) - { - SetSleepState( AISS_WAITING_FOR_PVS ); - Sleep(); - } - } - if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS) ) - { - if( HasCondition(COND_IN_PVS) ) - { - // OK, we're in the player's PVS. Now switch to regular old AUTO_PVS sleep rules. - AddSleepFlags(AI_SLEEP_FLAG_AUTO_PVS); - RemoveSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS); - } - } - } - } -} - -//----------------------------------------------------------------------------- - -struct AIRebalanceInfo_t -{ - CAI_BaseNPC * pNPC; - int iNextThinkTick; - bool bInPVS; - float dotPlayer; - float distPlayer; -}; - -int __cdecl ThinkRebalanceCompare( const AIRebalanceInfo_t *pLeft, const AIRebalanceInfo_t *pRight ) -{ - int base = pLeft->iNextThinkTick - pRight->iNextThinkTick; - if ( base != 0 ) - return base; - - if ( !pLeft->bInPVS && !pRight->bInPVS ) - return 0; - - if ( !pLeft->bInPVS ) - return 1; - - if ( !pRight->bInPVS ) - return -1; - - if ( pLeft->dotPlayer < 0 && pRight->dotPlayer < 0 ) - return 0; - - if ( pLeft->dotPlayer < 0 ) - return 1; - - if ( pRight->dotPlayer < 0 ) - return -1; - - const float NEAR_PLAYER = 50*12; - - if ( pLeft->distPlayer < NEAR_PLAYER && pRight->distPlayer >= NEAR_PLAYER ) - return -1; - - if ( pRight->distPlayer < NEAR_PLAYER && pLeft->distPlayer >= NEAR_PLAYER ) - return 1; - - if ( pLeft->dotPlayer > pRight->dotPlayer ) - return -1; - - if ( pLeft->dotPlayer < pRight->dotPlayer ) - return 1; - - return 0; -} - -inline bool CAI_BaseNPC::CanThinkRebalance() -{ - if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink ) - { - return false; - } - - if ( m_bInChoreo ) - { - return false; - } - - if ( m_NPCState == NPC_STATE_DEAD ) - { - return false; - } - - if ( GetSleepState() != AISS_AWAKE ) - { - return false; - } - - if ( !m_bUsingStandardThinkTime /*&& m_iFrameBlocked == -1 */ ) - { - return false; - } - - return true; -} - -void CAI_BaseNPC::RebalanceThinks() -{ - bool bDebugThinkTicks = ai_debug_think_ticks.GetBool(); - if ( bDebugThinkTicks ) - { - static int iPrevTick; - static int nThinksInTick; - static int nRebalanceableThinksInTick; - - if ( gpGlobals->tickcount != iPrevTick ) - { - DevMsg( "NPC per tick is %d [%d] (tick %d, frame %d)\n", nRebalanceableThinksInTick, nThinksInTick, iPrevTick, gpGlobals->framecount ); - iPrevTick = gpGlobals->tickcount; - nThinksInTick = 0; - nRebalanceableThinksInTick = 0; - } - nThinksInTick++; - if ( CanThinkRebalance() ) - nRebalanceableThinksInTick++; - } - - if ( ShouldRebalanceThinks() && gpGlobals->tickcount >= gm_iNextThinkRebalanceTick ) - { - AI_PROFILE_SCOPE(AI_Think_Rebalance ); - - static CUtlVector rebalanceCandidates( 16, 64 ); - gm_iNextThinkRebalanceTick = gpGlobals->tickcount + TIME_TO_TICKS( random->RandomFloat( 3, 5) ); - - int i; - - CBasePlayer *pPlayer = AI_GetSinglePlayer(); - Vector vPlayerForward; - Vector vPlayerEyePosition; - - vPlayerForward.Init(); - vPlayerEyePosition.Init(); - - if ( pPlayer ) - { - pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL ); - } - - int iTicksPer10Hz = TIME_TO_TICKS( .1 ); - int iMinTickRebalance = gpGlobals->tickcount - 1; // -1 needed for alternate ticks - int iMaxTickRebalance = gpGlobals->tickcount + iTicksPer10Hz; - - for ( i = 0; i < g_AI_Manager.NumAIs(); i++ ) - { - CAI_BaseNPC *pCandidate = g_AI_Manager.AccessAIs()[i]; - if ( pCandidate->CanThinkRebalance() && - ( pCandidate->GetNextThinkTick() >= iMinTickRebalance && - pCandidate->GetNextThinkTick() < iMaxTickRebalance ) ) - { - int iInfo = rebalanceCandidates.AddToTail(); - - rebalanceCandidates[iInfo].pNPC = pCandidate; - rebalanceCandidates[iInfo].iNextThinkTick = pCandidate->GetNextThinkTick(); - - if ( pCandidate->IsFlaggedEfficient() ) - { - rebalanceCandidates[iInfo].bInPVS = false; - } - else if ( pPlayer ) - { - Vector vToCandidate = pCandidate->EyePosition() - vPlayerEyePosition; - rebalanceCandidates[iInfo].bInPVS = ( UTIL_FindClientInPVS( pCandidate->edict() ) != NULL ); - rebalanceCandidates[iInfo].distPlayer = VectorNormalize( vToCandidate ); - rebalanceCandidates[iInfo].dotPlayer = vPlayerForward.Dot( vToCandidate ); - } - else - { - rebalanceCandidates[iInfo].bInPVS = true; - rebalanceCandidates[iInfo].dotPlayer = 1; - rebalanceCandidates[iInfo].distPlayer = 0; - } - } - else if ( bDebugThinkTicks ) - DevMsg( " Ignoring %d\n", pCandidate->GetNextThinkTick() ); - } - - if ( rebalanceCandidates.Count() ) - { - rebalanceCandidates.Sort( ThinkRebalanceCompare ); - - int iMaxThinkersPerTick = ceil( (float)( rebalanceCandidates.Count() + 1 ) / (float)iTicksPer10Hz ); // +1 to account for "this" - - int iCurTickDistributing = min( gpGlobals->tickcount, rebalanceCandidates[0].iNextThinkTick ); - int iRemainingThinksToDistribute = iMaxThinkersPerTick - 1; // Start with one fewer first time because "this" is - // always gets a slot in the current tick to avoid complications - // in the calculation of "last think" - - if ( bDebugThinkTicks ) - { - DevMsg( "Rebalance %d!\n", rebalanceCandidates.Count() + 1 ); - DevMsg( " Distributing %d\n", iCurTickDistributing ); - } - - for ( i = 0; i < rebalanceCandidates.Count(); i++ ) - { - if ( iRemainingThinksToDistribute == 0 || rebalanceCandidates[i].iNextThinkTick > iCurTickDistributing ) - { - if ( rebalanceCandidates[i].iNextThinkTick <= iCurTickDistributing ) - { - iCurTickDistributing = iCurTickDistributing + 1; - } - else - { - iCurTickDistributing = rebalanceCandidates[i].iNextThinkTick; - } - - if ( bDebugThinkTicks ) - DevMsg( " Distributing %d\n", iCurTickDistributing ); - - iRemainingThinksToDistribute = iMaxThinkersPerTick; - } - - if ( rebalanceCandidates[i].pNPC->GetNextThinkTick() != iCurTickDistributing ) - { - if ( bDebugThinkTicks ) - DevMsg( " Bumping %d to %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick(), iCurTickDistributing ); - - rebalanceCandidates[i].pNPC->SetNextThink( TICKS_TO_TIME( iCurTickDistributing ) ); - } - else if ( bDebugThinkTicks ) - { - DevMsg( " Leaving %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick() ); - } - - iRemainingThinksToDistribute--; - } - } - - rebalanceCandidates.RemoveAll(); - - if ( bDebugThinkTicks ) - { - DevMsg( "New distribution is:\n"); - for ( i = 0; i < g_AI_Manager.NumAIs(); i++ ) - { - DevMsg( " %d\n", g_AI_Manager.AccessAIs()[i]->GetNextThinkTick() ); - } - } - - Assert( GetNextThinkTick() == TICK_NEVER_THINK ); // never change this objects tick - } -} - -static float g_NpcTimeThisFrame; -static float g_StartTimeCurThink; - -bool CAI_BaseNPC::PreNPCThink() -{ - static int iPrevFrame = -1; - static float frameTimeLimit = FLT_MAX; - static const ConVar *pHostTimescale; - - if ( frameTimeLimit == FLT_MAX ) - { - pHostTimescale = cvar->FindVar( "host_timescale" ); - } - - bool bUseThinkLimits = ( !m_bInChoreo && ShouldUseFrameThinkLimits() ); - -#ifdef _DEBUG - const float NPC_THINK_LIMIT = 30.0 / 1000.0; -#else - const float NPC_THINK_LIMIT = ( !IsXbox() ) ? (10.0 / 1000.0) : (12.5 / 1000.0); -#endif - - g_StartTimeCurThink = 0; - - if ( bUseThinkLimits && VCRGetMode() == VCR_Disabled ) - { - if ( m_iFrameBlocked == gpGlobals->framecount ) - { - DbgFrameLimitMsg( "Stalled %d (%d)\n", this, gpGlobals->framecount ); - SetNextThink( gpGlobals->curtime ); - return false; - } - else if ( gpGlobals->framecount != iPrevFrame ) - { - DbgFrameLimitMsg( "--- FRAME: %d (%d)\n", this, gpGlobals->framecount ); - float timescale = pHostTimescale->GetFloat(); - if ( timescale < 1 ) - timescale = 1; - - iPrevFrame = gpGlobals->framecount; - frameTimeLimit = NPC_THINK_LIMIT * timescale; - g_NpcTimeThisFrame = 0; - } - else - { - if ( g_NpcTimeThisFrame > NPC_THINK_LIMIT ) - { - float timeSinceLastRealThink = gpGlobals->curtime - m_flLastRealThinkTime; - // Don't bump anyone more that a quarter second - if ( timeSinceLastRealThink <= .25 ) - { - DbgFrameLimitMsg( "Bumped %d (%d)\n", this, gpGlobals->framecount ); - m_iFrameBlocked = gpGlobals->framecount; - SetNextThink( gpGlobals->curtime ); - return false; - } - else - { - DbgFrameLimitMsg( "(Over %d )\n", this ); - } - } - } - - DbgFrameLimitMsg( "Running %d (%d)\n", this, gpGlobals->framecount ); - g_StartTimeCurThink = engine->Time(); - - m_iFrameBlocked = -1; - m_nLastThinkTick = TIME_TO_TICKS( m_flLastRealThinkTime ); - } - - return true; -} - -void CAI_BaseNPC::PostNPCThink( void ) -{ - if ( g_StartTimeCurThink != 0.0 && VCRGetMode() == VCR_Disabled ) - { - g_NpcTimeThisFrame += engine->Time() - g_StartTimeCurThink; - } -} - -void CAI_BaseNPC::CallNPCThink( void ) -{ - RebalanceThinks(); - - //--------------------------------- - - m_bUsingStandardThinkTime = false; - - //--------------------------------- - - if ( !PreNPCThink() ) - { - return; - } - - // reduce cache queries by locking model in memory - MDLCACHE_CRITICAL_SECTION(); - - this->NPCThink(); - - m_flLastRealThinkTime = gpGlobals->curtime; - - PostNPCThink(); -} - -bool NPC_CheckBrushExclude( CBaseEntity *pEntity, CBaseEntity *pBrush ) -{ - CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); - - if ( pNPC ) - { - return pNPC->GetMoveProbe()->ShouldBrushBeIgnored( pBrush ); - } - - return false; -} - -class CTraceFilterPlayerAvoidance : public CTraceFilterEntitiesOnly -{ -public: - CTraceFilterPlayerAvoidance( const CBaseEntity *pEntity ) { m_pIgnore = pEntity; } - - virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) - { - CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); - - if ( m_pIgnore == pEntity ) - return false; - - if ( pEntity->IsPlayer() ) - return true; - - return false; - } -private: - - const CBaseEntity *m_pIgnore; -}; - -void CAI_BaseNPC::GetPlayerAvoidBounds( Vector *pMins, Vector *pMaxs ) -{ - *pMins = WorldAlignMins(); - *pMaxs = WorldAlignMaxs(); -} - -ConVar ai_debug_avoidancebounds( "ai_debug_avoidancebounds", "0" ); - -void CAI_BaseNPC::SetPlayerAvoidState( void ) -{ - bool bShouldPlayerAvoid = false; - Vector vNothing; - - GetSequenceLinearMotion( GetSequence(), &vNothing ); - bool bIsMoving = ( IsMoving() || ( vNothing != vec3_origin ) ); - - //If we are coming out of a script, check if we are stuck inside the player. - if ( m_bPerformAvoidance || ( ShouldPlayerAvoid() && bIsMoving ) ) - { - trace_t trace; - Vector vMins, vMaxs; - - GetPlayerAvoidBounds( &vMins, &vMaxs ); - - CBasePlayer *pLocalPlayer = AI_GetSinglePlayer(); - - if ( pLocalPlayer ) - { - bShouldPlayerAvoid = IsBoxIntersectingBox( GetAbsOrigin() + vMins, GetAbsOrigin() + vMaxs, - pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMins(), pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMaxs() ); - } - - if ( ai_debug_avoidancebounds.GetBool() ) - { - int iRed = ( bShouldPlayerAvoid == true ) ? 255 : 0; - - NDebugOverlay::Box( GetAbsOrigin(), vMins, vMaxs, iRed, 0, 255, 64, 0.1 ); - } - } - - m_bPlayerAvoidState = ShouldPlayerAvoid(); - m_bPerformAvoidance = bShouldPlayerAvoid; - - if ( GetCollisionGroup() == COLLISION_GROUP_NPC || GetCollisionGroup() == COLLISION_GROUP_NPC_ACTOR ) - { - if ( m_bPerformAvoidance == true ) - { - SetCollisionGroup( COLLISION_GROUP_NPC_ACTOR ); - } - else - { - SetCollisionGroup( COLLISION_GROUP_NPC ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Enables player avoidance when the player's vphysics shadow penetrates our vphysics shadow. This can -// happen when the player is hit by a combine ball, which pushes them into an adjacent npc. Subclasses should -// override this if it causes problems, but in general this will solve cases of the player getting stuck in -// the NPC from being pushed. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::PlayerPenetratingVPhysics( void ) -{ - m_bPerformAvoidance = true; -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::CheckPVSCondition() -{ - bool bInPVS = ( UTIL_FindClientInPVS( edict() ) != NULL ) || (UTIL_ClientPVSIsExpanded() && UTIL_FindClientInVisibilityPVS( edict() )); - - if ( bInPVS ) - SetCondition( COND_IN_PVS ); - else - ClearCondition( COND_IN_PVS ); - - return bInPVS; -} - - -//----------------------------------------------------------------------------- -// NPC Think - calls out to core AI functions and handles this -// npc's specific animation events -// - -void CAI_BaseNPC::NPCThink( void ) -{ - if ( m_bCheckContacts ) - { - CheckPhysicsContacts(); - } - - Assert( !(m_NPCState == NPC_STATE_DEAD && m_lifeState == LIFE_ALIVE) ); - - //--------------------------------- - - SetNextThink( TICK_NEVER_THINK ); - - //--------------------------------- - - bool bInPVS = CheckPVSCondition(); - - //--------------------------------- - - UpdateSleepState( bInPVS ); - - //--------------------------------- - bool bRanDecision = false; - - if ( GetEfficiency() < AIE_DORMANT && GetSleepState() == AISS_AWAKE ) - { - static CFastTimer timer; - float thinkLimit = ai_show_think_tolerance.GetFloat(); - - if ( thinkLimit > 0 ) - timer.Start(); - - if ( g_pAINetworkManager && g_pAINetworkManager->IsInitialized() ) - { - VPROF_BUDGET( "NPCs", VPROF_BUDGETGROUP_NPCS ); - - AI_PROFILE_SCOPE_BEGIN_( GetClassScheduleIdSpace()->GetClassName() ); // need to use a string stable from map load to map load - - SetPlayerAvoidState(); - - if ( PreThink() ) - { - if ( m_flNextDecisionTime <= gpGlobals->curtime ) - { - bRanDecision = true; - m_ScheduleState.bTaskRanAutomovement = false; - m_ScheduleState.bTaskUpdatedYaw = false; - RunAI(); - } - else - { - if ( m_ScheduleState.bTaskRanAutomovement ) - AutoMovement(); - if ( m_ScheduleState.bTaskUpdatedYaw ) - GetMotor()->UpdateYaw(); - } - - PostRun(); - - PerformMovement(); - - m_bIsMoving = IsMoving(); - - PostMovement(); - - SetSimulationTime( gpGlobals->curtime ); - } - else - m_flTimeLastMovement = FLT_MAX; - - AI_PROFILE_SCOPE_END(); - } - - if ( thinkLimit > 0 ) - { - timer.End(); - - float thinkTime = g_AIRunTimer.GetDuration().GetMillisecondsF(); - - if ( thinkTime > thinkLimit ) - { - int color = (int)RemapVal( thinkTime, thinkLimit, thinkLimit * 3, 96.0, 255.0 ); - if ( color > 255 ) - color = 255; - else if ( color < 96 ) - color = 96; - - Vector right; - Vector vecPoint; - - vecPoint = EyePosition() + Vector( 0, 0, 12 ); - GetVectors( NULL, &right, NULL ); - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), color, 0, 0, false , 1.0 ); - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) + right * 16, color, 0, 0, false , 1.0 ); - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) - right * 16, color, 0, 0, false , 1.0 ); - } - } - } - - m_bUsingStandardThinkTime = ( GetNextThinkTick() == TICK_NEVER_THINK ); - - UpdateEfficiency( bInPVS ); - - if ( m_bUsingStandardThinkTime ) - { - static const char *ppszEfficiencies[] = - { - "AIE_NORMAL", - "AIE_EFFICIENT", - "AIE_VERY_EFFICIENT", - "AIE_SUPER_EFFICIENT", - "AIE_DORMANT", - }; - - static const char *ppszMoveEfficiencies[] = - { - "AIME_NORMAL", - "AIME_EFFICIENT", - }; - - if ( ai_debug_efficiency.GetBool() ) - DevMsg( this, "Eff: %s, Move: %s\n", ppszEfficiencies[GetEfficiency()], ppszMoveEfficiencies[GetMoveEfficiency()] ); - - static float g_DecisionIntervals[] = - { - .1, // AIE_NORMAL - .2, // AIE_EFFICIENT - .4, // AIE_VERY_EFFICIENT - .6, // AIE_SUPER_EFFICIENT - }; - - if ( bRanDecision ) - { - m_flNextDecisionTime = gpGlobals->curtime + g_DecisionIntervals[GetEfficiency()]; - } - - if ( GetMoveEfficiency() == AIME_NORMAL || GetEfficiency() == AIE_NORMAL ) - { - SetNextThink( gpGlobals->curtime + .1 ); - } - else - { - SetNextThink( gpGlobals->curtime + .2 ); - } - } - else - { - m_flNextDecisionTime = 0; - } -} - -//========================================================= -// CAI_BaseNPC - USE - will make a npc angry at whomever -// activated it. -//========================================================= -void CAI_BaseNPC::NPCUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - return; - - // Can't +USE NPCs running scripts - if ( GetState() == NPC_STATE_SCRIPT ) - return; - - if ( IsInAScript() ) - return; - - SetIdealState( NPC_STATE_ALERT ); -} - -//----------------------------------------------------------------------------- -// Purpose: Virtual function that allows us to have any npc ignore a set of -// shared conditions. -// -//----------------------------------------------------------------------------- -void CAI_BaseNPC::RemoveIgnoredConditions ( void ) -{ - m_ConditionsPreIgnore = m_Conditions; - m_Conditions.And( m_InverseIgnoreConditions, &m_Conditions ); - - if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine ) - m_hCine->RemoveIgnoredConditions(); -} - -//========================================================= -// RangeAttack1Conditions -//========================================================= -int CAI_BaseNPC::RangeAttack1Conditions ( float flDot, float flDist ) -{ - if ( flDist < 64) - { - return COND_TOO_CLOSE_TO_ATTACK; - } - else if (flDist > 784) - { - return COND_TOO_FAR_TO_ATTACK; - } - else if (flDot < 0.5) - { - return COND_NOT_FACING_ATTACK; - } - - return COND_CAN_RANGE_ATTACK1; -} - -//========================================================= -// RangeAttack2Conditions -//========================================================= -int CAI_BaseNPC::RangeAttack2Conditions ( float flDot, float flDist ) -{ - if ( flDist < 64) - { - return COND_TOO_CLOSE_TO_ATTACK; - } - else if (flDist > 512) - { - return COND_TOO_FAR_TO_ATTACK; - } - else if (flDot < 0.5) - { - return COND_NOT_FACING_ATTACK; - } - - return COND_CAN_RANGE_ATTACK2; -} - -//========================================================= -// MeleeAttack1Conditions -//========================================================= -int CAI_BaseNPC::MeleeAttack1Conditions ( float flDot, float flDist ) -{ - if ( flDist > 64) - { - return COND_TOO_FAR_TO_ATTACK; - } - else if (flDot < 0.7) - { - return 0; - } - else if (GetEnemy() == NULL) - { - return 0; - } - - // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) - if ( GetEnemy()->GetFlags() & FL_ONGROUND ) - { - return COND_CAN_MELEE_ATTACK1; - } - return 0; -} - -//========================================================= -// MeleeAttack2Conditions -//========================================================= -int CAI_BaseNPC::MeleeAttack2Conditions ( float flDot, float flDist ) -{ - if ( flDist > 64) - { - return COND_TOO_FAR_TO_ATTACK; - } - else if (flDot < 0.7) - { - return 0; - } - return COND_CAN_MELEE_ATTACK2; -} - -// Get capability mask -int CAI_BaseNPC::CapabilitiesGet( void ) const -{ - int capability = m_afCapability; - if ( GetActiveWeapon() ) - { - capability |= GetActiveWeapon()->CapabilitiesGet(); - } - return capability; -} - -// Set capability mask -int CAI_BaseNPC::CapabilitiesAdd( int capability ) -{ - m_afCapability |= capability; - - return m_afCapability; -} - -// Set capability mask -int CAI_BaseNPC::CapabilitiesRemove( int capability ) -{ - m_afCapability &= ~capability; - - return m_afCapability; -} - -// Clear capability mask -void CAI_BaseNPC::CapabilitiesClear( void ) -{ - m_afCapability = 0; -} - - -//========================================================= -// ClearAttacks - clear out all attack conditions -//========================================================= -void CAI_BaseNPC::ClearAttackConditions( ) -{ - // Clear all attack conditions - ClearCondition( COND_CAN_RANGE_ATTACK1 ); - ClearCondition( COND_CAN_RANGE_ATTACK2 ); - ClearCondition( COND_CAN_MELEE_ATTACK1 ); - ClearCondition( COND_CAN_MELEE_ATTACK2 ); - ClearCondition( COND_WEAPON_HAS_LOS ); - ClearCondition( COND_WEAPON_BLOCKED_BY_FRIEND ); - ClearCondition( COND_WEAPON_PLAYER_IN_SPREAD ); // Player in shooting direction - ClearCondition( COND_WEAPON_PLAYER_NEAR_TARGET ); // Player near shooting position - ClearCondition( COND_WEAPON_SIGHT_OCCLUDED ); -} - -//========================================================= -// GatherAttackConditions - sets all of the bits for attacks that the -// npc is capable of carrying out on the passed entity. -//========================================================= - -void CAI_BaseNPC::GatherAttackConditions( CBaseEntity *pTarget, float flDist ) -{ - AI_PROFILE_SCOPE(CAI_BaseNPC_GatherAttackConditions); - - Vector vecLOS = ( pTarget->GetAbsOrigin() - GetAbsOrigin() ); - vecLOS.z = 0; - VectorNormalize( vecLOS ); - - Vector vBodyDir = BodyDirection2D( ); - float flDot = DotProduct( vecLOS, vBodyDir ); - - // we know the enemy is in front now. We'll find which attacks the npc is capable of by - // checking for corresponding Activities in the model file, then do the simple checks to validate - // those attack types. - - int capability; - Vector targetPos; - bool bWeaponHasLOS; - int condition; - - capability = CapabilitiesGet(); - - // Clear all attack conditions - AI_PROFILE_SCOPE_BEGIN( CAI_BaseNPC_GatherAttackConditions_PrimaryWeaponLOS ); - - // @TODO (toml 06-15-03): There are simple cases where - // the upper torso of the enemy is visible, and the NPC is at an angle below - // them, but the above test fails because BodyTarget returns the center of - // the target. This needs some better handling/closer evaluation - - // Try the eyes first, as likely to succeed (because can see or else wouldn't be here) thus reducing - // the odds of the need for a second trace - ClearAttackConditions(); - targetPos = pTarget->EyePosition(); - bWeaponHasLOS = WeaponLOSCondition( GetAbsOrigin(), targetPos, true ); - - AI_PROFILE_SCOPE_END(); - - if ( !bWeaponHasLOS ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_SecondaryWeaponLOS ); - ClearAttackConditions( ); - targetPos = pTarget->BodyTarget( GetAbsOrigin() ); - bWeaponHasLOS = WeaponLOSCondition( GetAbsOrigin(), targetPos, true ); - } - else - { - SetCondition( COND_WEAPON_HAS_LOS ); - } - - bool bWeaponIsReady = (GetActiveWeapon() && !IsWeaponStateChanging()); - - // FIXME: move this out of here - if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK1) && bWeaponIsReady ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack1Condition ); - - condition = GetActiveWeapon()->WeaponRangeAttack1Condition(flDot, flDist); - - if ( condition == COND_NOT_FACING_ATTACK && FInAimCone( targetPos ) ) - DevMsg( "Warning: COND_NOT_FACING_ATTACK set but FInAimCone is true\n" ); - - if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS) - { - SetCondition(condition); - } - } - else if ( capability & bits_CAP_INNATE_RANGE_ATTACK1 ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack1Condition ); - - condition = RangeAttack1Conditions( flDot, flDist ); - if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS) - { - SetCondition(condition); - } - } - - if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK2) && bWeaponIsReady && ( GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK2 ) ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack2Condition ); - - condition = GetActiveWeapon()->WeaponRangeAttack2Condition(flDot, flDist); - if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS) - { - SetCondition(condition); - } - } - else if ( capability & bits_CAP_INNATE_RANGE_ATTACK2 ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack2Condition ); - - condition = RangeAttack2Conditions( flDot, flDist ); - if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS) - { - SetCondition(condition); - } - } - - if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK1) && bWeaponIsReady) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack1Condition ); - SetCondition(GetActiveWeapon()->WeaponMeleeAttack1Condition(flDot, flDist)); - } - else if ( capability & bits_CAP_INNATE_MELEE_ATTACK1 ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack1Condition ); - SetCondition(MeleeAttack1Conditions ( flDot, flDist )); - } - - if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK2) && bWeaponIsReady) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack2Condition ); - SetCondition(GetActiveWeapon()->WeaponMeleeAttack2Condition(flDot, flDist)); - } - else if ( capability & bits_CAP_INNATE_MELEE_ATTACK2 ) - { - AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack2Condition ); - SetCondition(MeleeAttack2Conditions ( flDot, flDist )); - } - - // ----------------------------------------------------------------- - // If any attacks are possible clear attack specific bits - // ----------------------------------------------------------------- - if (HasCondition(COND_CAN_RANGE_ATTACK2) || - HasCondition(COND_CAN_RANGE_ATTACK1) || - HasCondition(COND_CAN_MELEE_ATTACK2) || - HasCondition(COND_CAN_MELEE_ATTACK1) ) - { - ClearCondition(COND_TOO_CLOSE_TO_ATTACK); - ClearCondition(COND_TOO_FAR_TO_ATTACK); - ClearCondition(COND_WEAPON_BLOCKED_BY_FRIEND); - } -} - - -//========================================================= -// SetState -//========================================================= -void CAI_BaseNPC::SetState( NPC_STATE State ) -{ - NPC_STATE OldState; - - OldState = m_NPCState; - - if ( State != m_NPCState ) - { - m_flLastStateChangeTime = gpGlobals->curtime; - } - - switch( State ) - { - // Drop enemy pointers when going to idle - case NPC_STATE_IDLE: - - if ( GetEnemy() != NULL ) - { - SetEnemy( NULL ); // not allowed to have an enemy anymore. - DevMsg( 2, "Stripped\n" ); - } - break; - } - - bool fNotifyChange = false; - - if( m_NPCState != State ) - { - // Don't notify if we're changing to a state we're already in! - fNotifyChange = true; - } - - m_NPCState = State; - SetIdealState( State ); - - // Notify the character that its state has changed. - if( fNotifyChange ) - { - OnStateChange( OldState, m_NPCState ); - } -} - -bool CAI_BaseNPC::WokeThisTick() const -{ - return m_nWakeTick == gpGlobals->tickcount ? true : false; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CAI_BaseNPC::Wake( bool bFireOutput ) -{ - if ( GetSleepState() != AISS_AWAKE ) - { - m_nWakeTick = gpGlobals->tickcount; - SetSleepState( AISS_AWAKE ); - RemoveEffects( EF_NODRAW ); - if ( bFireOutput ) - m_OnWake.FireOutput( this, this ); - - if ( m_bWakeSquad && GetSquad() ) - { - AISquadIter_t iter; - for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) ) - { - if ( pSquadMember->IsAlive() && pSquadMember != this ) - { - pSquadMember->m_bWakeSquad = false; - pSquadMember->Wake(); - } - } - - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CAI_BaseNPC::Sleep() -{ - // Don't render. - AddEffects( EF_NODRAW ); - - if( GetState() == NPC_STATE_SCRIPT ) - { - Warning( "%s put to sleep while in Scripted state!\n"); - } - - VacateStrategySlot(); - - // Slam my schedule. - SetSchedule( SCHED_SLEEP ); - - m_OnSleep.FireOutput( this, this ); -} - -//----------------------------------------------------------------------------- -// Sets all sensing-related conditions -//----------------------------------------------------------------------------- -void CAI_BaseNPC::PerformSensing( void ) -{ - GetSenses()->PerformSensing(); -} - - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::ClearSenseConditions( void ) -{ - static int conditionsToClear[] = - { - COND_SEE_HATE, - COND_SEE_DISLIKE, - COND_SEE_ENEMY, - COND_SEE_FEAR, - COND_SEE_NEMESIS, - COND_SEE_PLAYER, - COND_HEAR_DANGER, - COND_HEAR_COMBAT, - COND_HEAR_WORLD, - COND_HEAR_PLAYER, - COND_HEAR_THUMPER, - COND_HEAR_BUGBAIT, - COND_HEAR_PHYSICS_DANGER, - COND_HEAR_MOVE_AWAY, - COND_SMELL, - }; - - ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::CheckOnGround( void ) -{ - bool bScriptedWait = ( IsCurSchedule( SCHED_WAIT_FOR_SCRIPT ) || ( m_hCine && m_scriptState == CAI_BaseNPC::SCRIPT_WAIT ) ); - if ( !bScriptedWait && !HasCondition( COND_FLOATING_OFF_GROUND ) ) - { - // parented objects are never floating - if (GetMoveParent() != NULL) - return; - - if ( ( GetNavType() == NAV_GROUND ) && ( GetMoveType() != MOVETYPE_VPHYSICS ) && ( GetMoveType() != MOVETYPE_NONE ) ) - { - if ( m_CheckOnGroundTimer.Expired() ) - { - m_CheckOnGroundTimer.Set(0.5); - - // check a shrunk box centered around the foot - Vector maxs = WorldAlignMaxs(); - Vector mins = WorldAlignMins(); - - if ( mins != maxs ) // some NPCs have no hull, so mins == maxs == vec3_origin - { - maxs -= Vector( 0.0f, 0.0f, 0.2f ); - - Vector vecStart = GetAbsOrigin() + Vector( 0, 0, .1f ); - Vector vecDown = GetAbsOrigin(); - vecDown.z -= 4.0; - - trace_t trace; - m_pMoveProbe->TraceHull( vecStart, vecDown, mins, maxs, MASK_NPCSOLID, &trace ); - - if (trace.fraction == 1.0) - { - SetCondition( COND_FLOATING_OFF_GROUND ); - SetGroundEntity( NULL ); - } - else - { - if ( trace.startsolid && trace.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS && - trace.m_pEnt->VPhysicsGetObject() && trace.m_pEnt->VPhysicsGetObject()->GetMass() < VPHYSICS_LARGE_OBJECT_MASS ) - { - // stuck inside a small physics object? - m_CheckOnGroundTimer.Set(0.1f); - NPCPhysics_CreateSolver( this, trace.m_pEnt, true, 0.25f ); - if ( VPhysicsGetObject() ) - { - VPhysicsGetObject()->RecheckContactPoints(); - } - } - // Check to see if someone changed the ground on us... - if ( trace.m_pEnt && trace.m_pEnt != GetGroundEntity() ) - { - SetGroundEntity( trace.m_pEnt ); - } - } - } - } - } - } - else - { - // parented objects are never floating - if ( bScriptedWait || GetMoveParent() != NULL || (GetFlags() & FL_ONGROUND ) || GetNavType() != NAV_GROUND ) - { - ClearCondition( COND_FLOATING_OFF_GROUND ); - } - } - -} - -void CAI_BaseNPC::NotifyPushMove() -{ - // don't recheck ground when I'm being push-moved - m_CheckOnGroundTimer.Set( 0.5f ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::CanFlinch( void ) -{ - if ( IsCurSchedule( SCHED_BIG_FLINCH ) ) - return false; - - if ( m_flNextFlinchTime >= gpGlobals->curtime ) - return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CheckFlinches( void ) -{ - // If we're currently flinching, don't allow gesture flinches to be overlaid - if ( IsCurSchedule( SCHED_BIG_FLINCH ) ) - { - ClearCondition( COND_LIGHT_DAMAGE ); - ClearCondition( COND_HEAVY_DAMAGE ); - } - - // If we've taken heavy damage, try to do a full schedule flinch - if ( HasCondition(COND_HEAVY_DAMAGE) ) - { - // If we've already flinched recently, gesture flinch instead. - if ( HasMemory(bits_MEMORY_FLINCHED) ) - { - // Clear the heavy damage condition so we don't interrupt schedules - // when we play a gesture flinch because we recently did a full flinch. - // Prevents the player from stun-locking enemies, even though they don't full flinch. - ClearCondition( COND_HEAVY_DAMAGE ); - } - else if ( !HasInterruptCondition(COND_HEAVY_DAMAGE) ) - { - // If we have taken heavy damage, but the current schedule doesn't - // break on that, resort to just playing a gesture flinch. - PlayFlinchGesture(); - } - - // Otherwise, do nothing. The heavy damage will interrupt our schedule and we'll flinch. - } - else if ( HasCondition( COND_LIGHT_DAMAGE ) ) - { - // If we have taken light damage play gesture flinches - PlayFlinchGesture(); - } - - // If it's been a while since we did a full flinch, forget that we flinched so we'll flinch fully again - if ( HasMemory(bits_MEMORY_FLINCHED) && gpGlobals->curtime > m_flNextFlinchTime ) - { - Forget(bits_MEMORY_FLINCHED); - } -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::GatherConditions( void ) -{ - m_bConditionsGathered = true; - g_AIConditionsTimer.Start(); - - if ( m_NPCState != NPC_STATE_NONE && m_NPCState != NPC_STATE_DEAD ) - { - if ( FacingIdeal() ) - Forget( bits_MEMORY_TURNING ); - - bool bForcedGather = m_bForceConditionsGather; - m_bForceConditionsGather = false; - - if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink ) - { - if ( UTIL_FindClientInPVS( edict() ) != NULL ) - SetCondition( COND_IN_PVS ); - else - ClearCondition( COND_IN_PVS ); - } - - // Sample the environment. Do this unconditionally if there is a player in this - // npc's PVS. NPCs in COMBAT state are allowed to simulate when there is no player in - // the same PVS. This is so that any fights in progress will continue even if the player leaves the PVS. - if ( !IsFlaggedEfficient() && - ( bForcedGather || - HasCondition( COND_IN_PVS ) || - ShouldAlwaysThink() || - m_NPCState == NPC_STATE_COMBAT ) ) - { - CheckOnGround(); - - if ( ShouldPlayIdleSound() ) - { - AI_PROFILE_SCOPE(CAI_BaseNPC_IdleSound); - IdleSound(); - } - - PerformSensing(); - - GetEnemies()->RefreshMemories(); - ChooseEnemy(); - - // Check to see if there is a better weapon available - if (Weapon_IsBetterAvailable()) - { - SetCondition(COND_BETTER_WEAPON_AVAILABLE); - } - - if ( GetCurSchedule() && - ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT) && - GetEnemy() && - !HasCondition( COND_NEW_ENEMY ) && - GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY ) ) - { - // @Note (toml 05-05-04): There seems to be a case where an NPC can not respond - // to COND_NEW_ENEMY. Only evidence right now is save - // games after the fact, so for now, just patching it up - DevMsg( 2, "Had to force COND_NEW_ENEMY\n" ); - SetCondition(COND_NEW_ENEMY); - } - } - else - { - // if not done, can have problems if leave PVS in same frame heard/saw things, - // since only PerformSensing clears conditions - ClearSenseConditions(); - } - - // do these calculations if npc has an enemy. - if ( GetEnemy() != NULL ) - { - if ( !IsFlaggedEfficient() ) - { - GatherEnemyConditions( GetEnemy() ); - m_flLastEnemyTime = gpGlobals->curtime; - } - else - { - SetEnemy( NULL ); - } - } - - // do these calculations if npc has a target - if ( GetTarget() != NULL ) - { - CheckTarget( GetTarget() ); - } - - CheckAmmo(); - - CheckFlinches(); - - CheckSquad(); - } - else - ClearCondition( COND_IN_PVS ); - - RemoveIgnoredConditions(); - - g_AIConditionsTimer.End(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::PrescheduleThink( void ) -{ -#ifdef HL2_EPISODIC - CheckForScriptedNPCInteractions(); -#endif - - // If we use weapons, and our desired weapon state is not the current, fix it - if( (CapabilitiesGet() & bits_CAP_USE_WEAPONS) && (m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) ) - { - if ( IsAlive() && !IsInAScript() ) - { - if ( !IsCurSchedule( SCHED_MELEE_ATTACK1, false ) && !IsCurSchedule( SCHED_MELEE_ATTACK2, false ) && - !IsCurSchedule( SCHED_RANGE_ATTACK1, false ) && !IsCurSchedule( SCHED_RANGE_ATTACK2, false ) ) - { - if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) - { - HolsterWeapon(); - } - else if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED ) - { - UnholsterWeapon(); - } - } - } - else - { - // Throw away the request - m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; - } - } -} - -//----------------------------------------------------------------------------- -// Main entry point for processing AI -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::RunAI( void ) -{ - AI_PROFILE_SCOPE(CAI_BaseNPC_RunAI); - g_AIRunTimer.Start(); - - if( ai_debug_squads.GetBool() ) - { - if( IsInSquad() && GetSquad() && !CAI_Squad::IsSilentMember(this ) && ( GetSquad()->IsLeader( this ) || GetSquad()->NumMembers() == 1 ) ) - { - AISquadIter_t iter; - CAI_Squad *pSquad = GetSquad(); - - Vector right; - Vector vecPoint; - - vecPoint = EyePosition() + Vector( 0, 0, 12 ); - GetVectors( NULL, &right, NULL ); - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 0, 255, 0, false , 0.1 ); - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 0, 255, 0, false , 0.1 ); - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 0, 255, 0, false , 0.1 ); - - for ( CAI_BaseNPC *pSquadMember = pSquad->GetFirstMember( &iter, false ); pSquadMember; pSquadMember = pSquad->GetNextMember( &iter, false ) ) - { - if ( pSquadMember != this ) - NDebugOverlay::Line( EyePosition(), pSquadMember->EyePosition(), 0, - CAI_Squad::IsSilentMember(pSquadMember) ? 127 : 255, 0, false , 0.1 ); - } - } - } - - if( ai_debug_loners.GetBool() && !IsInSquad() && AI_IsSinglePlayer() ) - { - Vector right; - Vector vecPoint; - - vecPoint = EyePosition() + Vector( 0, 0, 12 ); - - UTIL_GetLocalPlayer()->GetVectors( NULL, &right, NULL ); - - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 255, 0, 0, false , 0.1 ); - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 255, 0, 0, false , 0.1 ); - NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 255, 0, 0, false , 0.1 ); - } - -#ifdef _DEBUG - m_bSelected = ( (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) != 0 ); -#endif - - m_bConditionsGathered = false; - m_bSkippedChooseEnemy = false; - - if ( g_pDeveloper->GetInt() && !GetNavigator()->IsOnNetwork() ) - { - AddTimedOverlay( "NPC w/no reachable nodes!", 5 ); - } - - AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_RunAI_GatherConditions); - GatherConditions(); - AI_PROFILE_SCOPE_END(); - - if ( !m_bConditionsGathered ) - m_bConditionsGathered = true; // derived class didn't call to base - - TryRestoreHull(); - - g_AIPrescheduleThinkTimer.Start(); - - AI_PROFILE_SCOPE_BEGIN(CAI_RunAI_PrescheduleThink); - PrescheduleThink(); - AI_PROFILE_SCOPE_END(); - - g_AIPrescheduleThinkTimer.End(); - - MaintainSchedule(); - - PostscheduleThink(); - - ClearTransientConditions(); - - g_AIRunTimer.End(); -} - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ClearTransientConditions() -{ - // if the npc didn't use these conditions during the above call to MaintainSchedule() - // we throw them out cause we don't want them sitting around through the lifespan of a schedule - // that doesn't use them. - ClearCondition( COND_LIGHT_DAMAGE ); - ClearCondition( COND_HEAVY_DAMAGE ); - ClearCondition( COND_PHYSICS_DAMAGE ); - ClearCondition( COND_PLAYER_PUSHING ); -} - - -//----------------------------------------------------------------------------- -// Selecting the idle ideal state -//----------------------------------------------------------------------------- -NPC_STATE CAI_BaseNPC::SelectIdleIdealState() -{ - // 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 - if ( HasCondition(COND_NEW_ENEMY) || - HasCondition(COND_SEE_ENEMY) ) - { - // new enemy! This means an idle npc has seen someone it dislikes, or - // that a npc in combat has found a more suitable target to attack - return NPC_STATE_COMBAT; - } - - // Set our ideal yaw if we've taken damage - if ( HasCondition(COND_LIGHT_DAMAGE) || - HasCondition(COND_HEAVY_DAMAGE) || - (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) ) - { - Vector vecEnemyLKP; - - // Fill in where we're trying to look - if ( GetEnemy() ) - { - vecEnemyLKP = GetEnemyLKP(); - } - else - { - if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) ) - { - vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY ); - } - else - { - // Don't have an enemy, so face the direction the last attack came from (don't face north) - vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 ); - } - } - - // Set the ideal - GetMotor()->SetIdealYawToTarget( vecEnemyLKP ); - - return NPC_STATE_ALERT; - } - - if ( HasCondition(COND_HEAR_DANGER) || - HasCondition(COND_HEAR_COMBAT) || - HasCondition(COND_HEAR_WORLD) || - HasCondition(COND_HEAR_PLAYER) || - HasCondition(COND_HEAR_THUMPER) || - HasCondition(COND_HEAR_BULLET_IMPACT) ) - { - // Interrupted by a sound. So make our ideal yaw the - // source of the sound! - CSound *pSound; - - pSound = GetBestSound(); - Assert( pSound != NULL ); - if ( pSound ) - { - // BRJ 1/7/04: This code used to set the ideal yaw. - // It's really side-effecty to set the yaw here. - // That is now done by the FACE_BESTSOUND schedule. - // Revert this change if it causes problems. - GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() ); - if ( pSound->IsSoundType( SOUND_COMBAT | SOUND_DANGER | SOUND_BULLET_IMPACT ) ) - { - return NPC_STATE_ALERT; - } - } - } - - if ( HasInterruptCondition(COND_SMELL) ) - { - return NPC_STATE_ALERT; - } - - return NPC_STATE_INVALID; -} - - -//----------------------------------------------------------------------------- -// Selecting the alert ideal state -//----------------------------------------------------------------------------- -NPC_STATE CAI_BaseNPC::SelectAlertIdealState() -{ - // ALERT goes to IDLE upon becoming bored - // ALERT goes to COMBAT upon sighting an enemy - if ( HasCondition(COND_NEW_ENEMY) || - HasCondition(COND_SEE_ENEMY) || - GetEnemy() != NULL ) - { - return NPC_STATE_COMBAT; - } - - // Set our ideal yaw if we've taken damage - if ( HasCondition(COND_LIGHT_DAMAGE) || - HasCondition(COND_HEAVY_DAMAGE) || - (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) ) - { - Vector vecEnemyLKP; - - // Fill in where we're trying to look - if ( GetEnemy() ) - { - vecEnemyLKP = GetEnemyLKP(); - } - else - { - if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) ) - { - vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY ); - } - else - { - // Don't have an enemy, so face the direction the last attack came from (don't face north) - vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 ); - } - } - - // Set the ideal - GetMotor()->SetIdealYawToTarget( vecEnemyLKP ); - - return NPC_STATE_ALERT; - } - - if ( HasCondition(COND_HEAR_DANGER) || - HasCondition(COND_HEAR_COMBAT) ) - { - CSound *pSound = GetBestSound(); - AssertOnce( pSound != NULL ); - - if ( pSound ) - { - GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() ); - } - - return NPC_STATE_ALERT; - } - - if ( ShouldGoToIdleState() ) - { - return NPC_STATE_IDLE; - } - - return NPC_STATE_INVALID; -} - - -//----------------------------------------------------------------------------- -// Selecting the alert ideal state -//----------------------------------------------------------------------------- -NPC_STATE CAI_BaseNPC::SelectScriptIdealState() -{ - if ( HasCondition(COND_TASK_FAILED) || - HasCondition(COND_LIGHT_DAMAGE) || - HasCondition(COND_HEAVY_DAMAGE) ) - { - ExitScriptedSequence(); // This will set the ideal state - } - - if ( m_IdealNPCState == NPC_STATE_IDLE ) - { - // Exiting a script. Select the ideal state assuming we were idle now. - m_NPCState = NPC_STATE_IDLE; - NPC_STATE eIdealState = SelectIdealState(); - m_NPCState = NPC_STATE_SCRIPT; - return eIdealState; - } - - return NPC_STATE_INVALID; -} - - -//----------------------------------------------------------------------------- -// Purpose: Surveys the Conditions information available and finds the best -// new state for a npc. -// -// NOTICE the CAI_BaseNPC implementation of this function does not care about -// private conditions! -// -// Output : NPC_STATE - the suggested ideal state based on current conditions. -//----------------------------------------------------------------------------- -NPC_STATE CAI_BaseNPC::SelectIdealState( void ) -{ - // dvs: FIXME: lots of side effecty code in here!! this function should ONLY return an ideal state! - - // --------------------------- - // Do some squad stuff first - // --------------------------- - if (m_pSquad) - { - switch( m_NPCState ) - { - case NPC_STATE_IDLE: - case NPC_STATE_ALERT: - if ( HasCondition ( COND_NEW_ENEMY ) ) - { - m_pSquad->SquadNewEnemy( GetEnemy() ); - } - break; - } - } - - // --------------------------- - // Set ideal state - // --------------------------- - switch ( m_NPCState ) - { - case NPC_STATE_IDLE: - { - NPC_STATE nState = SelectIdleIdealState(); - if ( nState != NPC_STATE_INVALID ) - return nState; - } - break; - - case NPC_STATE_ALERT: - { - NPC_STATE nState = SelectAlertIdealState(); - if ( nState != NPC_STATE_INVALID ) - return nState; - } - break; - - case NPC_STATE_COMBAT: - // COMBAT goes to ALERT upon death of enemy - { - if ( GetEnemy() == NULL ) - { - DevWarning( 2, "***Combat state with no enemy!\n" ); - return NPC_STATE_ALERT; - } - break; - } - case NPC_STATE_SCRIPT: - { - NPC_STATE nState = SelectScriptIdealState(); - if ( nState != NPC_STATE_INVALID ) - return nState; - } - break; - - case NPC_STATE_DEAD: - return NPC_STATE_DEAD; - } - - // The best ideal state is the current ideal state. - return m_IdealNPCState; -} - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ -void CAI_BaseNPC::GiveWeapon( string_t iszWeaponName ) -{ - CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(iszWeaponName) ); - if ( !pWeapon ) - { - Warning( "Couldn't create weapon %s to give NPC %s.\n", STRING(iszWeaponName), STRING(GetEntityName()) ); - return; - } - - // If I have a weapon already, drop it - if ( GetActiveWeapon() ) - { - Weapon_Drop( GetActiveWeapon() ); - } - - // If I have a name, make my weapon match it with "_weapon" appended - if ( GetEntityName() != NULL_STRING ) - { - pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", GetEntityName())) ); - } - - Weapon_Equip( pWeapon ); - - // Handle this case - OnGivenWeapon( pWeapon ); -} - -//----------------------------------------------------------------------------- -// Rather specific function that tells us if an NPC is in the process of -// moving to a weapon with the intent to pick it up. -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsMovingToPickupWeapon() -{ - return IsCurSchedule( SCHED_NEW_WEAPON ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::ShouldLookForBetterWeapon() -{ - if( m_flNextWeaponSearchTime > gpGlobals->curtime ) - return false; - - if( !(CapabilitiesGet() & bits_CAP_USE_WEAPONS) ) - return false; - - // Already armed and currently fighting. Don't try to upgrade. - if( GetActiveWeapon() && m_NPCState == NPC_STATE_COMBAT ) - return false; - - if( IsMovingToPickupWeapon() ) - return false; - - if( !IsPlayerAlly() && GetActiveWeapon() ) - return false; - - if( IsInAScript() ) - return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Check if a better weapon is available. -// For now check if there is a weapon and I don't have one. In -// the future -// UNDONE: actually rate the weapons based on there strength -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::Weapon_IsBetterAvailable() -{ - if( m_iszPendingWeapon != NULL_STRING ) - { - // Some weapon is reserved for us. - return true; - } - - if( ShouldLookForBetterWeapon() ) - { - if( GetActiveWeapon() ) - { - m_flNextWeaponSearchTime = gpGlobals->curtime + 2; - } - else - { - if( IsInPlayerSquad() ) - { - // Look for a weapon frequently. - m_flNextWeaponSearchTime = gpGlobals->curtime + 1; - } - else - { - m_flNextWeaponSearchTime = gpGlobals->curtime + 2; - } - } - - if ( Weapon_FindUsable( WEAPON_SEARCH_DELTA ) ) - { - return true; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns true is weapon has a line of sight. If bSetConditions is -// true, also sets LOC conditions -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ) -{ -#if 0 - // @TODO (toml 03-07-04): this code might be better (not tested) - Vector vecLocalRelativePosition; - VectorITransform( npcOwner->Weapon_ShootPosition(), npcOwner->EntityToWorldTransform(), vecLocalRelativePosition ); - - // Compute desired test transform - - // Compute desired x axis - Vector xaxis; - VectorSubtract( targetPos, ownerPos, xaxis ); - - // FIXME: Insert angle test here? - float flAngle = acos( xaxis.z / xaxis.Length() ); - - xaxis.z = 0.0f; - float flLength = VectorNormalize( xaxis ); - if ( flLength < 1e-3 ) - return false; - - Vector yaxis( -xaxis.y, xaxis.x, 0.0f ); - - matrix3x4_t losTestToWorld; - MatrixInitialize( losTestToWorld, ownerPos, xaxis, yaxis, zaxis ); - - Vector barrelPos; - VectorTransform( vecLocalRelativePosition, losTestToWorld, barrelPos ); - -#endif - - bool bHaveLOS; - - if (GetActiveWeapon()) - { - bHaveLOS = GetActiveWeapon()->WeaponLOSCondition(ownerPos, targetPos, bSetConditions); - } - else if (CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1) - { - bHaveLOS = InnateWeaponLOSCondition(ownerPos, targetPos, bSetConditions); - } - else - { - if (bSetConditions) - { - SetCondition( COND_NO_WEAPON ); - } - bHaveLOS = false; - } - // ------------------------------------------- - // Check for friendly fire with the player - // ------------------------------------------- - if ( CapabilitiesGet() & ( bits_CAP_NO_HIT_PLAYER | bits_CAP_NO_HIT_SQUADMATES ) ) - { - float spread = 0.92; - if ( GetActiveWeapon() ) - { - Vector vSpread = GetAttackSpread( GetActiveWeapon() ); - if ( vSpread.x > VECTOR_CONE_15DEGREES.x ) - spread = TableCos(asin(vSpread.x)); - else // too much error because using point not box - spread = 0.99145; // "15 degrees" - } - if (CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) - { - // Check shoot direction relative to player - if (PlayerInSpread( ownerPos, targetPos, spread, 8*12 )) - { - if (bSetConditions) - { - SetCondition( COND_WEAPON_PLAYER_IN_SPREAD ); - } - bHaveLOS = false; - } - /* For grenades etc. check that player is clear? - // Check player position also - if (PlayerInRange( targetPos, 100 )) - { - SetCondition( COND_WEAPON_PLAYER_NEAR_TARGET ); - } - */ - } - - if ( bHaveLOS ) - { - if ( (CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && m_pSquad && GetEnemy() ) - { - if ( IsSquadmateInSpread( ownerPos, targetPos, spread, 8*12 ) ) - bHaveLOS = false; - } - } - } - return bHaveLOS; -} - -//----------------------------------------------------------------------------- -// Purpose: Check the innate weapon LOS for an owner at an arbitrary position -// If bSetConditions is true, LOS related conditions will also be set -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ) -{ - // -------------------- - // Check for occlusion - // -------------------- - // Base class version assumes innate weapon position is at eye level - Vector barrelPos = ownerPos + GetViewOffset(); - trace_t tr; - AI_TraceLine( barrelPos, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); - - if ( tr.fraction == 1.0 ) - { - return true; - } - - CBaseEntity *pHitEntity = tr.m_pEnt; - - // Translate a hit vehicle into its passenger if found - if ( GetEnemy() != NULL ) - { - CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer(); - if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() ) - { - // Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is - // Also, check to see if the owner of the entity is the vehicle, in which case it's valid too. - // This catches vehicles that use bone followers. - CBaseEntity *pVehicleEnt = pCCEnemy->GetVehicleEntity(); - if ( pHitEntity == pVehicleEnt || pHitEntity->GetOwnerEntity() == pVehicleEnt ) - return true; - } - } - - if ( pHitEntity == GetEnemy() ) - { - return true; - } - else if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() ) - { - if (IRelationType( pHitEntity ) == D_HT) - { - return true; - } - else if (bSetConditions) - { - SetCondition(COND_WEAPON_BLOCKED_BY_FRIEND); - } - } - else if (bSetConditions) - { - SetCondition(COND_WEAPON_SIGHT_OCCLUDED); - SetEnemyOccluder(tr.m_pEnt); - } - - return false; -} - -//========================================================= -// CanCheckAttacks - prequalifies a npc to do more fine -// checking of potential attacks. -//========================================================= -bool CAI_BaseNPC::FCanCheckAttacks( void ) -{ - // Not allowed to check attacks while climbing or jumping - // Otherwise schedule is interrupted while on ladder/etc - // which is NOT a legal place to attack from - if ( GetNavType() == NAV_CLIMB || GetNavType() == NAV_JUMP ) - return false; - - if ( HasCondition(COND_SEE_ENEMY) && !HasCondition( COND_ENEMY_TOO_FAR)) - { - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Return dist. to enemy (closest of origin/head/feet) -// Input : -// Output : -//----------------------------------------------------------------------------- -float CAI_BaseNPC::EnemyDistance( CBaseEntity *pEnemy ) -{ - Vector enemyDelta = pEnemy->WorldSpaceCenter() - WorldSpaceCenter(); - - // NOTE: We ignore rotation for computing height. Assume it isn't an effect - // we care about, so we simply use OBBSize().z for height. - // Otherwise you'd do this: - // pEnemy->CollisionProp()->WorldSpaceSurroundingBounds( &enemyMins, &enemyMaxs ); - // float enemyHeight = enemyMaxs.z - enemyMins.z; - - float enemyHeight = pEnemy->CollisionProp()->OBBSize().z; - float myHeight = CollisionProp()->OBBSize().z; - - // max distance our centers can be apart with the boxes still overlapping - float flMaxZDist = ( enemyHeight + myHeight ) * 0.5f; - - // see if the enemy is closer to my head, feet or in between - if ( enemyDelta.z > flMaxZDist ) - { - // enemy feet above my head, compute distance from my head to his feet - enemyDelta.z -= flMaxZDist; - } - else if ( enemyDelta.z < -flMaxZDist ) - { - // enemy head below my feet, return distance between my feet and his head - enemyDelta.z += flMaxZDist; - } - else - { - // boxes overlap in Z, no delta - enemyDelta.z = 0; - } - - return enemyDelta.Length(); -} - -//----------------------------------------------------------------------------- - -float CAI_BaseNPC::GetReactionDelay( CBaseEntity *pEnemy ) -{ - return ( m_NPCState == NPC_STATE_ALERT || m_NPCState == NPC_STATE_COMBAT ) ? - ai_reaction_delay_alert.GetFloat() : - ai_reaction_delay_idle.GetFloat(); -} - -//----------------------------------------------------------------------------- -// Purpose: Update information on my enemy -// Input : -// Output : Returns true is new enemy, false is known enemy -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer ) -{ - bool firstHand = ( pInformer == NULL || pInformer == this ); - - AI_PROFILE_SCOPE(CAI_BaseNPC_UpdateEnemyMemory); - - if ( GetEnemies() ) - { - // If the was eluding me and allow the NPC to play a sound - if (GetEnemies()->HasEludedMe(pEnemy)) - { - FoundEnemySound(); - } - float reactionDelay = ( !pInformer || pInformer == this ) ? GetReactionDelay( pEnemy ) : 0.0; - bool result = GetEnemies()->UpdateMemory(GetNavigator()->GetNetwork(), pEnemy, position, reactionDelay, firstHand); - - if ( !firstHand && pEnemy && result && GetState() == NPC_STATE_IDLE ) // if it's a new potential enemy - ForceDecisionThink(); - - if ( firstHand && pEnemy && m_pSquad ) - { - m_pSquad->UpdateEnemyMemory( this, pEnemy, position ); - } - return result; - } - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Remembers the thing my enemy is hiding behind. Called when either -// COND_ENEMY_OCCLUDED or COND_WEAPON_SIGHT_OCCLUDED is set. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::SetEnemyOccluder(CBaseEntity *pBlocker) -{ - m_hEnemyOccluder = pBlocker; -} - - -//----------------------------------------------------------------------------- -// Purpose: Gets the thing my enemy is hiding behind (assuming they are hiding). -//----------------------------------------------------------------------------- -CBaseEntity *CAI_BaseNPC::GetEnemyOccluder(void) -{ - return m_hEnemyOccluder.Get(); -} - - -//----------------------------------------------------------------------------- -// Purpose: part of the Condition collection process -// gets and stores data and conditions pertaining to a npc's -// enemy. -// @TODO (toml 07-27-03): this should become subservient to the senses. right -// now, it yields different result -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::GatherEnemyConditions( CBaseEntity *pEnemy ) -{ - AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions); - - ClearCondition( COND_ENEMY_FACING_ME ); - ClearCondition( COND_BEHIND_ENEMY ); - - // --------------------------- - // Set visibility conditions - // --------------------------- - if ( HasCondition( COND_NEW_ENEMY ) || GetSenses()->GetTimeLastUpdate( GetEnemy() ) == gpGlobals->curtime ) - { - AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_Visibility); - - ClearCondition( COND_HAVE_ENEMY_LOS ); - ClearCondition( COND_ENEMY_OCCLUDED ); - - CBaseEntity *pBlocker = NULL; - SetEnemyOccluder(NULL); - - bool bSensesDidSee = GetSenses()->DidSeeEntity( pEnemy ); - - if ( !bSensesDidSee && ( ( EnemyDistance( pEnemy ) >= GetSenses()->GetDistLook() ) || !FVisible( pEnemy, MASK_OPAQUE, &pBlocker ) ) ) - { - // No LOS to enemy - SetEnemyOccluder(pBlocker); - SetCondition( COND_ENEMY_OCCLUDED ); - ClearCondition( COND_SEE_ENEMY ); - - if (HasMemory( bits_MEMORY_HAD_LOS )) - { - AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs); - // Send output event - if (GetEnemy()->IsPlayer()) - { - m_OnLostPlayerLOS.FireOutput( GetEnemy(), this ); - } - m_OnLostEnemyLOS.FireOutput( GetEnemy(), this ); - } - Forget( bits_MEMORY_HAD_LOS ); - } - else - { - // Have LOS but may not be in view cone - SetCondition( COND_HAVE_ENEMY_LOS ); - - if ( bSensesDidSee ) - { - // Have LOS and in view cone - SetCondition( COND_SEE_ENEMY ); - } - else - { - ClearCondition( COND_SEE_ENEMY ); - } - - if (!HasMemory( bits_MEMORY_HAD_LOS )) - { - AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs); - // Send output event - EHANDLE hEnemy; - hEnemy.Set( GetEnemy() ); - - if (GetEnemy()->IsPlayer()) - { - m_OnFoundPlayer.Set(hEnemy, this, this); - m_OnFoundEnemy.Set(hEnemy, this, this); - } - else - { - m_OnFoundEnemy.Set(hEnemy, this, this); - } - } - Remember( bits_MEMORY_HAD_LOS ); - } - - AI_PROFILE_SCOPE_END(); - } - - // ------------------- - // If enemy is dead - // ------------------- - if ( !pEnemy->IsAlive() ) - { - SetCondition( COND_ENEMY_DEAD ); - ClearCondition( COND_SEE_ENEMY ); - ClearCondition( COND_ENEMY_OCCLUDED ); - return; - } - - float flDistToEnemy = EnemyDistance(pEnemy); - - AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_SeeEnemy); - - if ( HasCondition( COND_SEE_ENEMY ) ) - { - // Trail the enemy a bit if he's moving - if (pEnemy->GetSmoothedVelocity() != vec3_origin) - { - Vector vTrailPos = pEnemy->GetAbsOrigin() - pEnemy->GetSmoothedVelocity() * random->RandomFloat( -0.05, 0 ); - UpdateEnemyMemory(pEnemy,vTrailPos); - } - else - { - UpdateEnemyMemory(pEnemy,pEnemy->GetAbsOrigin()); - } - - // If it's not an NPC, assume it can't see me - if ( pEnemy->MyCombatCharacterPointer() && pEnemy->MyCombatCharacterPointer()->FInViewCone ( this ) ) - { - SetCondition ( COND_ENEMY_FACING_ME ); - ClearCondition ( COND_BEHIND_ENEMY ); - } - else - { - ClearCondition( COND_ENEMY_FACING_ME ); - SetCondition ( COND_BEHIND_ENEMY ); - } - } - else if ( (!HasCondition(COND_ENEMY_OCCLUDED) && !HasCondition(COND_SEE_ENEMY)) && ( flDistToEnemy <= 256 ) ) - { - // if the enemy is not occluded, and unseen, that means it is behind or beside the npc. - // if the enemy is near enough the npc, we go ahead and let the npc know where the - // enemy is. Send the enemy in as the informer so this knowledge will be regarded as - // secondhand so that the NPC doesn't - UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), pEnemy ); - } - - AI_PROFILE_SCOPE_END(); - - float tooFar = m_flDistTooFar; - if ( GetActiveWeapon() && HasCondition(COND_SEE_ENEMY) ) - { - tooFar = max( m_flDistTooFar, GetActiveWeapon()->m_fMaxRange1 ); - } - - if ( flDistToEnemy >= tooFar ) - { - // enemy is very far away from npc - SetCondition( COND_ENEMY_TOO_FAR ); - } - else - { - ClearCondition( COND_ENEMY_TOO_FAR ); - } - - if ( FCanCheckAttacks() ) - { - // This may also call SetEnemyOccluder! - GatherAttackConditions( GetEnemy(), flDistToEnemy ); - } - else - { - ClearAttackConditions(); - } - - // If my enemy has moved significantly, or if the enemy has changed update my path - UpdateEnemyPos(); - - // If my target entity has moved significantly, update my path - // This is an odd place to put this, but where else should it go? - UpdateTargetPos(); - - // ---------------------------------------------------------------------------- - // Check if enemy is reachable via the node graph unless I'm not on a network - // ---------------------------------------------------------------------------- - if (GetNavigator()->IsOnNetwork()) - { - // Note that unreachablity times out - if (IsUnreachable(GetEnemy())) - { - SetCondition(COND_ENEMY_UNREACHABLE); - } - } - - //----------------------------------------------------------------------- - // If I haven't seen the enemy in a while he may have eluded me - //----------------------------------------------------------------------- - if (gpGlobals->curtime - GetEnemyLastTimeSeen() > 8) - { - //----------------------------------------------------------------------- - // I'm at last known position at enemy isn't in sight then has eluded me - // ---------------------------------------------------------------------- - Vector flEnemyLKP = GetEnemyLKP(); - if (((flEnemyLKP - GetAbsOrigin()).Length2D() < 48) && - !HasCondition(COND_SEE_ENEMY)) - { - MarkEnemyAsEluded(); - } - //------------------------------------------------------------------- - // If enemy isn't reachable, I can see last known position and enemy - // isn't there, then he has eluded me - // ------------------------------------------------------------------ - if (!HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_UNREACHABLE)) - { - if ( !FVisible( flEnemyLKP ) ) - { - MarkEnemyAsEluded(); - } - } - } -} - - -//----------------------------------------------------------------------------- -// In the case of goaltype enemy, update the goal position -//----------------------------------------------------------------------------- -float CAI_BaseNPC::GetGoalRepathTolerance( CBaseEntity *pGoalEnt, GoalType_t type, const Vector &curGoal, const Vector &curTargetPos ) -{ - float distToGoal = ( GetAbsOrigin() - curTargetPos ).Length() - GetNavigator()->GetArrivalDistance(); - float distMoved1Sec = GetSmoothedVelocity().Length(); - float result = 120; // FIXME: why 120? - - if (distMoved1Sec > 0.0) - { - float t = distToGoal / distMoved1Sec; - - result = clamp( 120 * t, 0, 120 ); - // Msg("t %.2f : d %.0f (%.0f)\n", t, result, distMoved1Sec ); - } - - if ( !pGoalEnt->IsPlayer() ) - result *= 1.20; - - return result; -} - -//----------------------------------------------------------------------------- -// In the case of goaltype enemy, update the goal position -//----------------------------------------------------------------------------- -void CAI_BaseNPC::UpdateEnemyPos() -{ - // Don't perform path recomputations during a climb or a jump - if ( !GetNavigator()->IsInterruptable() ) - return; - - if ( m_AnyUpdateEnemyPosTimer.Expired() && m_UpdateEnemyPosTimer.Expired() ) - { - // FIXME: does GetGoalRepathTolerance() limit re-routing enough to remove this? - // m_UpdateEnemyPosTimer.Set( 0.5, 1.0 ); - - // If my enemy has moved significantly, or if the enemy has changed update my path - if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY ) - { - if (m_hEnemy != GetNavigator()->GetGoalTarget()) - { - GetNavigator()->SetGoalTarget( m_hEnemy, vec3_origin ); - } - else - { - Vector vEnemyLKP = GetEnemyLKP(); - TranslateNavGoal( GetEnemy(), vEnemyLKP ); - float tolerance = GetGoalRepathTolerance( GetEnemy(), GOALTYPE_ENEMY, GetNavigator()->GetGoalPos(), vEnemyLKP); - if ( (GetNavigator()->GetGoalPos() - vEnemyLKP).Length() > tolerance ) - { - // FIXME: when fleeing crowds, won't this severely limit the effectiveness of each individual? Shouldn't this be a mutex that's held for some period so that at least one attacker is effective? - m_AnyUpdateEnemyPosTimer.Set( 0.1 ); // FIXME: what's a reasonable interval? - if ( !GetNavigator()->RefindPathToGoal( false ) ) - { - TaskFail( FAIL_NO_ROUTE ); - } - } - } - } - } -} - - -//----------------------------------------------------------------------------- -// In the case of goaltype targetent, update the goal position -//----------------------------------------------------------------------------- -void CAI_BaseNPC::UpdateTargetPos() -{ - // BRJ 10/7/02 - // FIXME: make this check time based instead of distance based! - - // Don't perform path recomputations during a climb or a jump - if ( !GetNavigator()->IsInterruptable() ) - return; - - // If my target entity has moved significantly, or has changed, update my path - // This is an odd place to put this, but where else should it go? - if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT ) - { - if (m_hTargetEnt != GetNavigator()->GetGoalTarget()) - { - GetNavigator()->SetGoalTarget( m_hTargetEnt, vec3_origin ); - } - else if ( GetNavigator()->GetGoalFlags() & AIN_UPDATE_TARGET_POS ) - { - if ( GetTarget() == NULL || (GetNavigator()->GetGoalPos() - GetTarget()->GetAbsOrigin()).Length() > GetGoalRepathTolerance( GetTarget(), GOALTYPE_TARGETENT, GetNavigator()->GetGoalPos(), GetTarget()->GetAbsOrigin()) ) - { - if ( !GetNavigator()->RefindPathToGoal( false ) ) - { - TaskFail( FAIL_NO_ROUTE ); - } - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: part of the Condition collection process -// gets and stores data and conditions pertaining to a npc's -// enemy. -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CheckTarget( CBaseEntity *pTarget ) -{ - AI_PROFILE_SCOPE(CAI_Enemies_CheckTarget); - - ClearCondition ( COND_HAVE_TARGET_LOS ); - ClearCondition ( COND_TARGET_OCCLUDED ); - - // --------------------------- - // Set visibility conditions - // --------------------------- - if ( ( EnemyDistance( pTarget ) >= GetSenses()->GetDistLook() ) || !FVisible( pTarget ) ) - { - // No LOS to target - SetCondition( COND_TARGET_OCCLUDED ); - } - else - { - // Have LOS (may not be in view cone) - SetCondition( COND_HAVE_TARGET_LOS ); - } - - UpdateTargetPos(); -} - -//----------------------------------------------------------------------------- -// Purpose: Creates a bullseye of limited lifespan at the provided position -// Input : vecOrigin - Where to create the bullseye -// duration - The lifespan of the bullseye -// Output : A BaseNPC pointer to the bullseye -// -// NOTES : It is the caller's responsibility to set up relationships with -// this bullseye! -//----------------------------------------------------------------------------- -CAI_BaseNPC *CAI_BaseNPC::CreateCustomTarget( const Vector &vecOrigin, float duration ) -{ -#ifdef HL2_DLL - CNPC_Bullseye *pTarget = (CNPC_Bullseye*)CreateEntityByName( "npc_bullseye" ); - - ASSERT( pTarget != NULL ); - - // Build a nonsolid bullseye and place it in the desired location - // The bullseye must take damage or the SetHealth 0 call will not be able - pTarget->AddSpawnFlags( SF_BULLSEYE_NONSOLID ); - pTarget->SetAbsOrigin( vecOrigin ); - pTarget->Spawn(); - - // Set it up to remove itself - variant_t value; - value.SetFloat(0); - g_EventQueue.AddEvent( pTarget, "SetHealth", value, 2.0, this, this ); - - return pTarget; -#else - return NULL; -#endif// HL2_DLL -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : eNewActivity - -// Output : Activity -//----------------------------------------------------------------------------- -Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity ) -{ - Assert( eNewActivity != ACT_INVALID ); - - if (eNewActivity == ACT_RANGE_ATTACK1) - { - if ( IsCrouching() ) - { - eNewActivity = ACT_RANGE_ATTACK1_LOW; - } - } - else if (eNewActivity == ACT_RELOAD) - { - if (IsCrouching()) - { - eNewActivity = ACT_RELOAD_LOW; - } - } - else if ( eNewActivity == ACT_IDLE ) - { - if ( IsCrouching() ) - { - eNewActivity = ACT_CROUCHIDLE; - } - } - - if (CapabilitiesGet() & bits_CAP_DUCK) - { - if (eNewActivity == ACT_RELOAD) - { - return GetReloadActivity(GetHintNode()); - } - else if ((eNewActivity == ACT_COVER ) || - (eNewActivity == ACT_IDLE && HasMemory(bits_MEMORY_INCOVER))) - { - Activity nCoverActivity = GetCoverActivity(GetHintNode()); - // --------------------------------------------------------------- - // Some NPCs don't have a cover activity defined so just use idle - // --------------------------------------------------------------- - if (SelectWeightedSequence( nCoverActivity ) == ACTIVITY_NOT_AVAILABLE) - { - nCoverActivity = ACT_IDLE; - } - - return nCoverActivity; - } - } - return eNewActivity; -} - - -//----------------------------------------------------------------------------- - -Activity CAI_BaseNPC::TranslateActivity( Activity idealActivity, Activity *pIdealWeaponActivity ) -{ - const int MAX_TRIES = 5; - int count = 0; - - bool bIdealWeaponRequired = false; - Activity idealWeaponActivity; - Activity baseTranslation; - bool bWeaponRequired = false; - Activity weaponTranslation; - Activity last; - Activity current; - - idealWeaponActivity = Weapon_TranslateActivity( idealActivity, &bIdealWeaponRequired ); - if ( pIdealWeaponActivity ) - *pIdealWeaponActivity = idealWeaponActivity; - - baseTranslation = idealActivity; - weaponTranslation = idealActivity; - last = idealActivity; - while ( count++ < MAX_TRIES ) - { - current = NPC_TranslateActivity( last ); - if ( current != last ) - baseTranslation = current; - - weaponTranslation = Weapon_TranslateActivity( current, &bWeaponRequired ); - - if ( weaponTranslation == last ) - break; - - last = weaponTranslation; - } - AssertMsg( count < MAX_TRIES, "Circular activity translation!" ); - - if ( last == ACT_SCRIPT_CUSTOM_MOVE ) - return ACT_SCRIPT_CUSTOM_MOVE; - - if ( HaveSequenceForActivity( weaponTranslation ) ) - return weaponTranslation; - - if ( bWeaponRequired ) - { - // only complain about an activity once - static CUtlVector< Activity > sUniqueActivities; - - if (!sUniqueActivities.Find( weaponTranslation)) - { - // FIXME: warning - DevWarning( "%s missing activity \"%s\" needed by weapon\"%s\"\n", - GetClassname(), GetActivityName( weaponTranslation ), GetActiveWeapon()->GetClassname() ); - - sUniqueActivities.AddToTail( weaponTranslation ); - } - } - - if ( baseTranslation != weaponTranslation && HaveSequenceForActivity( baseTranslation ) ) - return baseTranslation; - - if ( idealWeaponActivity != baseTranslation && HaveSequenceForActivity( idealWeaponActivity ) ) - return idealActivity; - - if ( idealActivity != idealWeaponActivity && HaveSequenceForActivity( idealActivity ) ) - return idealActivity; - - Assert( !HaveSequenceForActivity( idealActivity ) ); - if ( idealActivity == ACT_RUN ) - { - idealActivity = ACT_WALK; - } - else if ( idealActivity == ACT_WALK ) - { - idealActivity = ACT_RUN; - } - - return idealActivity; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : NewActivity - -// iSequence - -// translatedActivity - -// weaponActivity - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ResolveActivityToSequence(Activity NewActivity, int &iSequence, Activity &translatedActivity, Activity &weaponActivity) -{ - AI_PROFILE_SCOPE( CAI_BaseNPC_ResolveActivityToSequence ); - - iSequence = ACTIVITY_NOT_AVAILABLE; - - translatedActivity = TranslateActivity( NewActivity, &weaponActivity ); - - if ( ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE ) ) - { - iSequence = GetScriptCustomMoveSequence(); - } - else - { - iSequence = SelectWeightedSequence( translatedActivity ); - - if ( iSequence == ACTIVITY_NOT_AVAILABLE ) - { - static CAI_BaseNPC *pLastWarn; - static Activity lastWarnActivity; - static float timeLastWarn; - - if ( ( pLastWarn != this && lastWarnActivity != translatedActivity ) || gpGlobals->curtime - timeLastWarn > 5.0 ) - { - DevWarning( "%s has no sequence for act:%s\n", GetClassname(), ActivityList_NameForIndex(translatedActivity) ); - pLastWarn = this; - lastWarnActivity = translatedActivity; - timeLastWarn = gpGlobals->curtime; - } - - if ( translatedActivity == ACT_RUN ) - { - translatedActivity = ACT_WALK; - iSequence = SelectWeightedSequence( translatedActivity ); - } - } - } - - if ( iSequence == ACT_INVALID ) - { - // Abject failure. Use sequence zero. - iSequence = 0; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : NewActivity - -// iSequence - -// translatedActivity - -// weaponActivity - -//----------------------------------------------------------------------------- -extern ConVar ai_sequence_debug; - -void CAI_BaseNPC::SetActivityAndSequence(Activity NewActivity, int iSequence, Activity translatedActivity, Activity weaponActivity) -{ - m_translatedActivity = translatedActivity; - - if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) - { - DevMsg("SetActivityAndSequence : %s: %s:%s -> %s:%s / %s:%s\n", GetClassname(), - GetActivityName(GetActivity()), GetSequenceName(GetSequence()), - GetActivityName(NewActivity), GetSequenceName(iSequence), - GetActivityName(translatedActivity), GetActivityName(weaponActivity) ); - - } - - // Set to the desired anim, or default anim if the desired is not present - if ( iSequence > ACTIVITY_NOT_AVAILABLE ) - { - if ( GetSequence() != iSequence || !SequenceLoops() ) - { - // - // Don't reset frame between movement phased animations - if (!IsActivityMovementPhased( m_Activity ) || - !IsActivityMovementPhased( NewActivity )) - { - SetCycle( 0 ); - } - } - - ResetSequence( iSequence ); - Weapon_SetActivity( weaponActivity, SequenceDuration( iSequence ) ); - } - else - { - // Not available try to get default anim - ResetSequence( 0 ); - } - - // Set the view position based on the current activity - SetViewOffset( EyeOffset(m_translatedActivity) ); - - if (m_Activity != NewActivity) - { - OnChangeActivity(NewActivity); - } - - // NOTE: We DO NOT write the translated activity here. - // This is to abstract the activity translation from the AI code. - // As far as the code is concerned, a translation is merely a new set of sequences - // that should be regarded as the activity in question. - - // Go ahead and set this so it doesn't keep trying when the anim is not present - m_Activity = NewActivity; - - // this cannot be called until m_Activity stores NewActivity! - GetMotor()->RecalculateYawSpeed(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Sets the activity to the desired activity immediately, skipping any -// transition sequences. -// Input : NewActivity - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::SetActivity( Activity NewActivity ) -{ - // If I'm already doing the NewActivity I can bail. - // FIXME: Should this be based on the current translated activity and ideal translated activity (calculated below)? - // The old code only cared about the logical activity, not translated. - - if (m_Activity == NewActivity) - { - return; - } - - // Don't do this if I'm playing a transition, unless it's ACT_RESET. - if ( NewActivity != ACT_RESET && m_Activity == ACT_TRANSITION && m_IdealActivity != ACT_DO_NOT_DISTURB ) - { - return; - } - - if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) - { - DevMsg("SetActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity)); - } - - if ( !GetModelPtr() ) - return; - - // In case someone calls this with something other than the ideal activity. - m_IdealActivity = NewActivity; - - // Resolve to ideals and apply directly, skipping transitions. - ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); - - //DevMsg("%s: SLAM %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence)); - - SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); -} - - -//----------------------------------------------------------------------------- -// Purpose: Sets the activity that we would like to transition toward. -// Input : NewActivity - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::SetIdealActivity( Activity NewActivity ) -{ - // ignore if it's an ACT_TRANSITION, it means somewhere we're setting IdealActivity with a bogus intermediate value - if (NewActivity == ACT_TRANSITION) - { - Assert( 0 ); - return; - } - - if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) - { - DevMsg("SetIdealActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity)); - } - - - if (NewActivity == ACT_RESET) - { - // They probably meant to call SetActivity(ACT_RESET)... we'll fix it for them. - SetActivity(ACT_RESET); - return; - } - - m_IdealActivity = NewActivity; - - if( NewActivity == ACT_DO_NOT_DISTURB ) - { - // Don't resolve anything! Leave it the way the user has it right now. - return; - } - - if ( !GetModelPtr() ) - return; - - // Perform translation in case we need to change sequences within a single activity, - // such as between a standing idle and a crouching idle. - ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); -} - - -//----------------------------------------------------------------------------- -// Purpose: Moves toward the ideal activity through any transition sequences. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::AdvanceToIdealActivity(void) -{ - // If there is a transition sequence between the current sequence and the ideal sequence... - int nNextSequence = FindTransitionSequence(GetSequence(), m_nIdealSequence, NULL); - if (nNextSequence != -1) - { - // We found a transition sequence or possibly went straight to - // the ideal sequence. - if (nNextSequence != m_nIdealSequence) - { -// DevMsg("%s: TRANSITION %s -> %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(nNextSequence), GetSequenceName(m_nIdealSequence)); - - Activity eWeaponActivity = ACT_TRANSITION; - Activity eTranslatedActivity = ACT_TRANSITION; - - // Figure out if the transition sequence has an associated activity that - // we can use for our weapon. Do activity translation also. - Activity eTransitionActivity = GetSequenceActivity(nNextSequence); - if (eTransitionActivity != ACT_INVALID) - { - int nDiscard; - ResolveActivityToSequence(eTransitionActivity, nDiscard, eTranslatedActivity, eWeaponActivity); - } - - // Set activity and sequence to the transition stuff. Set the activity to ACT_TRANSITION - // so we know we're in a transition. - SetActivityAndSequence(ACT_TRANSITION, nNextSequence, eTranslatedActivity, eWeaponActivity); - } - else - { - //DevMsg("%s: IDEAL %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence)); - - // Set activity and sequence to the ideal stuff that was set up in MaintainActivity. - SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); - } - } - // Else go straight there to the ideal activity. - else - { - //DevMsg("%s: Unable to get from sequence %s to %s!\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence)); - SetActivity(m_IdealActivity); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Tries to achieve our ideal animation state, playing any transition -// sequences that we need to play to get there. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::MaintainActivity(void) -{ - AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainActivity ); - - if ( m_lifeState == LIFE_DEAD ) - { - // Don't maintain activities if we're daid. - // Blame Speyrer - return; - } - - if ((GetState() == NPC_STATE_SCRIPT)) - { - // HACK: finish any transitions we might be playing before we yield control to the script - if (GetActivity() != ACT_TRANSITION) - { - // Our animation state is being controlled by a script. - return; - } - } - - if( m_IdealActivity == ACT_DO_NOT_DISTURB || !GetModelPtr() ) - { - return; - } - - // We may have work to do if we aren't playing our ideal activity OR if we - // aren't playing our ideal sequence. - if ((GetActivity() != m_IdealActivity) || (GetSequence() != m_nIdealSequence)) - { - if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) - { - DevMsg("MaintainActivity %s : %s:%s -> %s:%s\n", GetClassname(), - GetActivityName(GetActivity()), GetSequenceName(GetSequence()), - GetActivityName(m_IdealActivity), GetSequenceName(m_nIdealSequence)); - } - - bool bAdvance = false; - - // If we're in a transition activity, see if we are done with the transition. - if (GetActivity() == ACT_TRANSITION) - { - // If the current sequence is finished, try to go to the next one - // closer to our ideal sequence. - if (IsSequenceFinished()) - { - bAdvance = true; - } - // Else a transition sequence is in progress, do nothing. - } - // Else get a specific sequence for the activity and try to transition to that. - else - { - // Save off a target sequence and translated activities to apply when we finish - // playing all the transitions and finally arrive at our ideal activity. - ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); - bAdvance = true; - } - - if (bAdvance) - { - // Try to go to the next sequence closer to our ideal sequence. - AdvanceToIdealActivity(); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Returns true if our ideal activity has finished playing. -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsActivityFinished( void ) -{ - return (IsSequenceFinished() && (GetSequence() == m_nIdealSequence)); -} - -//----------------------------------------------------------------------------- -// Purpose: Checks to see if the activity is one of the standard phase-matched movement activities -// Input : activity -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsActivityMovementPhased( Activity activity ) -{ - switch( activity ) - { - case ACT_WALK: - case ACT_WALK_AIM: - case ACT_WALK_CROUCH: - case ACT_WALK_CROUCH_AIM: - case ACT_RUN: - case ACT_RUN_AIM: - case ACT_RUN_CROUCH: - case ACT_RUN_CROUCH_AIM: - case ACT_RUN_PROTECTED: - return true; - } - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OnChangeActivity( Activity eNewActivity ) -{ - if ( eNewActivity == ACT_RUN || - eNewActivity == ACT_RUN_AIM || - eNewActivity == ACT_WALK ) - { - Stand(); - } -} - -//========================================================= -// SetSequenceByName -//========================================================= -void CAI_BaseNPC::SetSequenceByName( char *szSequence ) -{ - int iSequence = LookupSequence( szSequence ); - - if ( iSequence > ACTIVITY_NOT_AVAILABLE ) - SetSequenceById( iSequence ); - else - { - DevWarning( 2, "%s has no sequence to match request\n", GetClassname(), szSequence ); - SetSequence( 0 ); // Set to the reset anim (if it's there) - } -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::SetSequenceById( int iSequence ) -{ - // Set to the desired anim, or default anim if the desired is not present - if ( iSequence > ACTIVITY_NOT_AVAILABLE ) - { - if ( GetSequence() != iSequence || !SequenceLoops() ) - { - SetCycle( 0 ); - } - - ResetSequence( iSequence ); // Set to the reset anim (if it's there) - GetMotor()->RecalculateYawSpeed(); - } - else - { - // Not available try to get default anim - DevWarning( 2, "%s invalid sequence requested\n", GetClassname() ); - SetSequence( 0 ); // Set to the reset anim (if it's there) - } -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the target entity -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseEntity *CAI_BaseNPC::GetNavTargetEntity(void) -{ - if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY ) - return m_hEnemy; - else if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT ) - return m_hTargetEnt; - return NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: returns zero if the caller can jump from -// vecStart to vecEnd ignoring collisions with pTarget -// -// if the throw fails, returns the distance -// that can be travelled before an obstacle is hit -//----------------------------------------------------------------------------- -#include "ai_initutils.h" -//#define _THROWDEBUG -float CAI_BaseNPC::ThrowLimit( const Vector &vecStart, - const Vector &vecEnd, - float fGravity, - float fArcSize, - const Vector &mins, - const Vector &maxs, - CBaseEntity *pTarget, - Vector *jumpVel, - CBaseEntity **pBlocker) -{ - // Get my jump velocity - Vector rawJumpVel = CalcThrowVelocity(vecStart, vecEnd, fGravity, fArcSize); - *jumpVel = rawJumpVel; - Vector vecFrom = vecStart; - - // Calculate the total time of the jump minus a tiny fraction - float jumpTime = (vecStart - vecEnd).Length2D()/rawJumpVel.Length2D(); - float timeStep = jumpTime / 10.0; - - Vector gravity = Vector(0,0,fGravity); - - // this loop takes single steps to the goal. - for (float flTime = 0 ; flTime < jumpTime-0.1 ; flTime += timeStep ) - { - // Calculate my position after the time step (average velocity over this time step) - Vector nextPos = vecFrom + (rawJumpVel - 0.5 * gravity * timeStep) * timeStep; - - // If last time step make next position the target position - if ((flTime + timeStep) > jumpTime) - { - nextPos = vecEnd; - } - - trace_t tr; - AI_TraceHull( vecFrom, nextPos, mins, maxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); - - if (tr.startsolid || tr.fraction < 1.0) - { - CBaseEntity *pEntity = tr.m_pEnt; - - // If we hit the target we are good to go! - if (pEntity == pTarget) - { - return 0; - } - -#ifdef _THROWDEBUG - NDebugOverlay::Line( vecFrom, nextPos, 255, 0, 0, true, 1.0 ); -#endif - // ---------------------------------------------------------- - // If blocked by an npc remember - // ---------------------------------------------------------- - *pBlocker = pEntity; - - // Return distance sucessfully traveled before block encountered - return ((tr.endpos - vecStart).Length()); - } -#ifdef _THROWDEBUG - else - { - NDebugOverlay::Line( vecFrom, nextPos, 255, 255, 255, true, 1.0 ); - } -#endif - - - rawJumpVel = rawJumpVel - gravity * timeStep; - vecFrom = nextPos; - } - return 0; -} - - - -//----------------------------------------------------------------------------- -// Purpose: Called to initialize or re-initialize the vphysics hull when the size -// of the NPC changes -//----------------------------------------------------------------------------- -void CAI_BaseNPC::SetupVPhysicsHull() -{ - if ( GetMoveType() == MOVETYPE_VPHYSICS || GetMoveType() == MOVETYPE_NONE ) - return; - - if ( VPhysicsGetObject() ) - { - // Disable collisions to get - VPhysicsGetObject()->EnableCollisions(false); - VPhysicsDestroyObject(); - } - VPhysicsInitShadow( true, false ); - IPhysicsObject *pPhysObj = VPhysicsGetObject(); - if ( pPhysObj ) - { - float mass = Studio_GetMass(GetModelPtr()); - if ( mass > 0 ) - { - pPhysObj->SetMass( mass ); - } -#if _DEBUG - else - { - DevMsg("Warning: %s has no physical mass\n", STRING(GetModelName())); - } -#endif - IPhysicsShadowController *pController = pPhysObj->GetShadowController(); - float avgsize = (WorldAlignSize().x + WorldAlignSize().y) * 0.5; - pController->SetTeleportDistance( avgsize * 0.5 ); - m_bCheckContacts = true; - } -} - - -// Check for problematic physics objects resting on this NPC. -// They can screw up his navigation, so attach a controller to -// help separate the NPC & physics when you encounter these. -ConVar ai_auto_contact_solver( "ai_auto_contact_solver", "1" ); -void CAI_BaseNPC::CheckPhysicsContacts() -{ - if ( gpGlobals->frametime <= 0.0f || !ai_auto_contact_solver.GetBool() ) - return; - - m_bCheckContacts = false; - if ( GetMoveType() == MOVETYPE_STEP && VPhysicsGetObject()) - { - IPhysicsObject *pPhysics = VPhysicsGetObject(); - IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot(); - CBaseEntity *pGroundEntity = GetGroundEntity(); - float heightCheck = GetAbsOrigin().z + GetHullMaxs().z; - Vector npcVel; - pPhysics->GetVelocity( &npcVel, NULL ); - CBaseEntity *pOtherEntity = NULL; - bool createSolver = false; - float solverTime = 0.0f; - while ( pSnapshot->IsValid() ) - { - IPhysicsObject *pOther = pSnapshot->GetObject(1); - pOtherEntity = static_cast(pOther->GetGameData()); - - if ( pOtherEntity && pGroundEntity != pOtherEntity ) - { - float otherMass = PhysGetEntityMass(pOtherEntity); - - if ( pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() && - otherMass < VPHYSICS_LARGE_OBJECT_MASS ) - { - Assert( !NPCPhysics_SolverExists(this, pOtherEntity) ); - m_bCheckContacts = true; - Vector vel, point; - pOther->GetVelocity( &vel, NULL ); - pSnapshot->GetContactPoint( point ); - - // compare the relative velocity - vel -= npcVel; - - // slow moving object probably won't clear itself. - // Either set ignore, or disable collisions entirely - if ( vel.LengthSqr() < 5.0f*5.0f ) - { - float topdist = fabs(point.z-heightCheck); - // 4 seconds to ignore this for nav - solverTime = 4.0f; - if ( topdist < 2.0f ) - { - // Resting on my head so disable collisions for a bit - solverTime = 0.5f; // UNDONE: Tune - if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) - { - // player is being a monkey - solverTime = 0.25f; - } - - //Msg("Dropping %s from %s\n", pOtherEntity->GetClassname(), GetClassname() ); - createSolver = true; - break; - } - } - } - } - pSnapshot->NextFrictionData(); - } - pPhysics->DestroyFrictionSnapshot( pSnapshot ); - if ( createSolver ) - { - // turn collisions back on once we've been separated for enough time - NPCPhysics_CreateSolver( this, pOtherEntity, true, solverTime ); - pPhysics->RecheckContactPoints(); - } - } -} - -void CAI_BaseNPC::StartTouch( CBaseEntity *pOther ) -{ - BaseClass::StartTouch(pOther); - - if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) - { - m_bCheckContacts = true; - } -} - -//----------------------------------------------------------------------------- -// Purpose: To be called instead of UTIL_SetSize, so pathfinding hull -// and actual hull agree -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::SetHullSizeNormal( bool force ) -{ - if ( m_fIsUsingSmallHull || force ) - { - UTIL_SetSize(this, GetHullMins(),GetHullMaxs()); - m_fIsUsingSmallHull = false; - if ( VPhysicsGetObject() ) - { - SetupVPhysicsHull(); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: To be called instead of UTIL_SetSize, so pathfinding hull -// and actual hull agree -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::SetHullSizeSmall( bool force ) -{ - if ( !m_fIsUsingSmallHull || force ) - { - UTIL_SetSize(this, NAI_Hull::SmallMins(GetHullType()),NAI_Hull::SmallMaxs(GetHullType())); - m_fIsUsingSmallHull = true; - if ( VPhysicsGetObject() ) - { - SetupVPhysicsHull(); - } - } - return true; -} - -//----------------------------------------------------------------------------- -// Checks to see that the nav hull is valid for the NPC -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsNavHullValid() const -{ - Assert( GetSolid() != SOLID_BSP ); - - Vector hullMin = GetHullMins(); - Vector hullMax = GetHullMaxs(); - Vector vecMins, vecMaxs; - if ( GetSolid() == SOLID_BBOX ) - { - vecMins = WorldAlignMins(); - vecMaxs = WorldAlignMaxs(); - } - else if ( GetSolid() == SOLID_VPHYSICS ) - { - Assert( VPhysicsGetObject() ); - const CPhysCollide *pPhysCollide = VPhysicsGetObject()->GetCollide(); - physcollision->CollideGetAABB( vecMins, vecMaxs, pPhysCollide, GetAbsOrigin(), GetAbsAngles() ); - vecMins -= GetAbsOrigin(); - vecMaxs -= GetAbsOrigin(); - } - else - { - vecMins = hullMin; - vecMaxs = hullMax; - } - - if ( (hullMin.x > vecMins.x) || (hullMax.x < vecMaxs.x) || - (hullMin.y > vecMins.y) || (hullMax.y < vecMaxs.y) || - (hullMin.z > vecMins.z) || (hullMax.z < vecMaxs.z) ) - { - return false; - } - - return true; -} - - -//========================================================= -// NPCInit - after a npc 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 npc spawns. Any -// initialization that should take place for all npcs -// goes here. -//========================================================= -void CAI_BaseNPC::NPCInit ( void ) -{ - if (!g_pGameRules->FAllowNPCs()) - { - UTIL_Remove( this ); - return; - } - - if( IsWaitingToRappel() ) - { - // If this guy's supposed to rappel, keep him from - // falling to the ground when he spawns. - AddFlag( FL_FLY ); - } - -#ifdef _DEBUG - // Make sure that the bounding box is appropriate for the hull size... - // FIXME: We can't test vphysics objects because NPCInit occurs before VPhysics is set up - if ( GetSolid() != SOLID_VPHYSICS && !IsSolidFlagSet(FSOLID_NOT_SOLID) ) - { - if ( !IsNavHullValid() ) - { - Warning("NPC Entity %s (%d) has a bounding box which extends outside its nav box!\n", - STRING(m_iClassname), entindex() ); - } - } -#endif - - // Set fields common to all npcs - AddFlag( FL_AIMTARGET | FL_NPC ); - AddSolidFlags( FSOLID_NOT_STANDABLE ); - - m_flOriginalYaw = GetAbsAngles().y; - - SetBlocksLOS( false ); - - SetGravity(1.0); // Don't change - m_takedamage = DAMAGE_YES; - GetMotor()->SetIdealYaw( GetLocalAngles().y ); - m_iMaxHealth = m_iHealth; - m_lifeState = LIFE_ALIVE; - SetIdealState( NPC_STATE_IDLE );// Assume npc will be idle, until proven otherwise - SetIdealActivity( ACT_IDLE ); - SetActivity( ACT_IDLE ); - -#ifdef HL1_DLL - SetDeathPose( ACT_INVALID ); -#endif - - ClearCommandGoal(); - - ClearSchedule(); - GetNavigator()->ClearGoal(); - InitBoneControllers( ); // FIX: should be done in Spawn - if ( GetModelPtr() ) - { - ResetActivityIndexes(); - ResetEventIndexes(); - } - - SetHintNode( NULL ); - - m_afMemory = MEMORY_CLEAR; - - SetEnemy( NULL ); - - m_flDistTooFar = 1024.0; - SetDistLook( 2048.0 ); - - if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) ) - { - m_flDistTooFar = 1e9f; - SetDistLook( 6000.0 ); - } - - // Clear conditions - m_Conditions.ClearAllBits(); - - // set eye position - SetDefaultEyeOffset(); - - // Only give weapon of allowed to have one - if (CapabilitiesGet() & bits_CAP_USE_WEAPONS) - { // Does this npc spawn with a weapon - if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0")) - { - CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(m_spawnEquipment) ); - if ( pWeapon ) - { - // If I have a name, make my weapon match it with "_weapon" appended - if ( GetEntityName() != NULL_STRING ) - { - pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", GetEntityName())) ); - } - Weapon_Equip( pWeapon ); - } - } - } - - // Robin: Removed this, since it stomps the weapon's settings, and it's stomped - // by OnUpdateShotRegulator() as soon as they finish firing the first time. - //GetShotRegulator()->SetParameters( 2, 6, 0.3f, 0.8f ); - - SetUse ( &CAI_BaseNPC::NPCUse ); - - // NOTE: Can't call NPC Init Think directly... logic changed about - // what time it is when worldspawn happens.. - - // We must put off the rest of our initialization - // until we're sure everything else has had a chance to spawn. Otherwise - // we may try to reference entities that haven't spawned yet.(sjb) - SetThink( &CAI_BaseNPC::NPCInitThink ); - SetNextThink( gpGlobals->curtime + 0.01f ); - - ForceGatherConditions(); - - // HACKHACK: set up a pre idle animation - // NOTE: Must do this before CreateVPhysics() so bone followers have the correct initial positions. - if ( HasSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT ) ) - { - const char *pStartSequence = CAI_ScriptedSequence::GetSpawnPreIdleSequenceForScript( this ); - if ( pStartSequence ) - { - SetSequence( LookupSequence( pStartSequence ) ); - } - } - - CreateVPhysics(); - - if ( HasSpawnFlags( SF_NPC_START_EFFICIENT ) ) - { - SetEfficiency( AIE_EFFICIENT ); - } - - m_bFadeCorpse = ShouldFadeOnDeath(); - - m_GiveUpOnDeadEnemyTimer.Set( 0.75, 2.0 ); - - m_flTimeLastMovement = FLT_MAX; - - m_flIgnoreDangerSoundsUntil = 0; - - SetDeathPose( ACT_INVALID ); - SetDeathPoseFrame( 0 ); - - m_EnemiesSerialNumber = -1; -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::CreateVPhysics() -{ - if ( IsAlive() && !VPhysicsGetObject() ) - { - SetupVPhysicsHull(); - } - return true; -} - - -//----------------------------------------------------------------------------- -// Set up the shot regulator based on the equipped weapon -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OnUpdateShotRegulator( ) -{ - CBaseCombatWeapon *pWeapon = GetActiveWeapon(); - if ( !pWeapon ) - return; - - // Default values - m_ShotRegulator.SetBurstInterval( pWeapon->GetFireRate(), pWeapon->GetFireRate() ); - m_ShotRegulator.SetBurstShotCountRange( pWeapon->GetMinBurst(), pWeapon->GetMaxBurst() ); - m_ShotRegulator.SetRestInterval( pWeapon->GetMinRestTime(), pWeapon->GetMaxRestTime() ); - - // Let the behavior have a whack at it. - if ( GetRunningBehavior() ) - { - GetRunningBehavior()->OnUpdateShotRegulator(); - } -} - - -//----------------------------------------------------------------------------- -// Set up the shot regulator based on the equipped weapon -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon ) -{ - BaseClass::OnChangeActiveWeapon( pOldWeapon, pNewWeapon ); - - // Shot regulator code - if ( pNewWeapon ) - { - OnUpdateShotRegulator(); - m_ShotRegulator.Reset( true ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Tests to see if NPC can holster their weapon (if animation exists to holster weapon) -// Output : true if holster weapon animation exists -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::CanHolsterWeapon( void ) -{ - int seq = SelectWeightedSequence( ACT_DISARM ); - return (seq >= 0); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CAI_BaseNPC::HolsterWeapon( void ) -{ - if ( IsWeaponHolstered() ) - return -1; - - int iHolsterGesture = FindGestureLayer( ACT_DISARM ); - if ( iHolsterGesture != -1 ) - return iHolsterGesture; - - int iLayer = AddGesture( ACT_DISARM, true ); - //iLayer = AddGesture( ACT_GESTURE_DISARM, true ); - - if (iLayer != -1) - { - // Prevent firing during the holster / unholster - float flDuration = GetLayerDuration( iLayer ); - m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 ); - - if( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) - { - m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING_DESTROY; - } - else - { - m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING; - } - - // Make sure we don't try to reload while we're holstering - ClearCondition(COND_LOW_PRIMARY_AMMO); - ClearCondition(COND_NO_PRIMARY_AMMO); - ClearCondition(COND_NO_SECONDARY_AMMO); - } - - return iLayer; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CAI_BaseNPC::UnholsterWeapon( void ) -{ - if ( !IsWeaponHolstered() ) - return -1; - - int iHolsterGesture = FindGestureLayer( ACT_ARM ); - if ( iHolsterGesture != -1 ) - return iHolsterGesture; - - // Deploy the first weapon you can find - for (int i = 0; i < WeaponCount(); i++) - { - if ( GetWeapon( i )) - { - SetActiveWeapon( GetWeapon(i) ); - - int iLayer = AddGesture( ACT_ARM, true ); - //iLayer = AddGesture( ACT_GESTURE_ARM, true ); - - if (iLayer != -1) - { - // Prevent firing during the holster / unholster - float flDuration = GetLayerDuration( iLayer ); - m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 ); - - m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING; - } - - // Refill the clip - if ( GetActiveWeapon()->UsesClipsForAmmo1() ) - { - GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); - } - - // Make sure we don't try to reload while we're unholstering - ClearCondition(COND_LOW_PRIMARY_AMMO); - ClearCondition(COND_NO_PRIMARY_AMMO); - ClearCondition(COND_NO_SECONDARY_AMMO); - - return iLayer; - } - } - - return -1; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputHolsterWeapon( inputdata_t &inputdata ) -{ - m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputHolsterAndDestroyWeapon( inputdata_t &inputdata ) -{ - m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED_DESTROYED; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputUnholsterWeapon( inputdata_t &inputdata ) -{ - m_iDesiredWeaponState = DESIREDWEAPONSTATE_UNHOLSTERED; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsWeaponHolstered( void ) -{ - if( !GetActiveWeapon() ) - return true; - - if( GetActiveWeapon()->IsEffectActive(EF_NODRAW) ) - return true; - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsWeaponStateChanging( void ) -{ - return ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING || m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY ); -} - -//----------------------------------------------------------------------------- -// Set up the shot regulator based on the equipped weapon -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OnRangeAttack1() -{ - SetLastAttackTime( gpGlobals->curtime ); - - // Houston, there is a problem! - AssertOnce( GetShotRegulator()->ShouldShoot() ); - - m_ShotRegulator.OnFiredWeapon(); - if ( m_ShotRegulator.IsInRestInterval() ) - { - OnUpdateShotRegulator(); - } - - SetNextAttack( m_ShotRegulator.NextShotTime() ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Initialze the relationship table from the keyvalues -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InitRelationshipTable(void) -{ - AddRelationship( STRING( m_RelationshipString ), NULL ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::AddRelationship( const char *pszRelationship, CBaseEntity *pActivator ) -{ - // Parse the keyvalue data - char parseString[1000]; - Q_strncpy(parseString, pszRelationship, sizeof(parseString)); - - // Look for an entity string - char *entityString = strtok(parseString," "); - while (entityString) - { - // Get the disposition - char *dispositionString = strtok(NULL," "); - Disposition_t disposition = D_NU; - if ( dispositionString ) - { - if (!stricmp(dispositionString,"D_HT")) - { - disposition = D_HT; - } - else if (!stricmp(dispositionString,"D_FR")) - { - disposition = D_FR; - } - else if (!stricmp(dispositionString,"D_LI")) - { - disposition = D_LI; - } - else if (!stricmp(dispositionString,"D_NU")) - { - disposition = D_NU; - } - else - { - disposition = D_NU; - Warning( "***ERROR***\nBad relationship type (%s) to unknown entity (%s)!\n", dispositionString,entityString ); - Assert( 0 ); - return; - } - } - else - { - Warning("Can't parse relationship info (%s)\n", pszRelationship ); - Assert(0); - return; - } - - // Get the priority - char *priorityString = strtok(NULL," "); - int priority = ( priorityString ) ? atoi(priorityString) : DEF_RELATIONSHIP_PRIORITY; - - bool bFoundEntity = false; - - // Try to get pointer to an entity of this name - CBaseEntity *entity = gEntList.FindEntityByName( NULL, entityString ); - while( entity ) - { - // make sure you catch all entities of this name. - bFoundEntity = true; - AddEntityRelationship(entity, disposition, priority ); - entity = gEntList.FindEntityByName( entity, entityString ); - } - - if( !bFoundEntity ) - { - // Need special condition for player as we can only have one - if (!stricmp("player", entityString) || !stricmp("!player", entityString)) - { - AddClassRelationship( CLASS_PLAYER, disposition, priority ); - } - // Otherwise try to create one too see if a valid classname and get class type - else - { - // HACKHACK: - CBaseEntity *pEntity = CanCreateEntityClass( entityString ) ? CreateEntityByName( entityString ) : NULL; - if (pEntity) - { - AddClassRelationship( pEntity->Classify(), disposition, priority ); - UTIL_RemoveImmediate(pEntity); - } - else - { - DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString ); - } - } - } - // Check for another entity in the list - entityString = strtok(NULL," "); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CAI_BaseNPC::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority ) -{ -#if 0 - ForceGatherConditions(); -#endif - BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CAI_BaseNPC::AddClassRelationship( Class_T nClass, Disposition_t nDisposition, int nPriority ) -{ -#if 0 - ForceGatherConditions(); -#endif - BaseClass::AddClassRelationship( nClass, nDisposition, nPriority ); -} - -//========================================================= -// NPCInitThink - Calls StartNPC. Startnpc is -// virtual, but this function cannot be -//========================================================= -void CAI_BaseNPC::NPCInitThink ( void ) -{ - // Initialize the relationship table - InitRelationshipTable(); - - StartNPC(); - - PostNPCInit(); - - if( GetSleepState() == AISS_AUTO_PVS ) - { - // This code is a bit wonky, but it makes it easier for level designers to - // select this option in Hammer. So we set a sleep flag to indicate the choice, - // and then set the sleep state to awake (normal) - AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS ); - SetSleepState( AISS_AWAKE ); - } - - if( GetSleepState() == AISS_AUTO_PVS_AFTER_PVS ) - { - AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS ); - SetSleepState( AISS_AWAKE ); - } - - if ( GetSleepState() > AISS_AWAKE ) - { - Sleep(); - } - - m_flLastRealThinkTime = gpGlobals->curtime; -} - -//========================================================= -// StartNPC - final bit of initization before a npc -// is turned over to the AI. -//========================================================= -void CAI_BaseNPC::StartNPC( void ) -{ - // Raise npc off the floor one unit, then drop to floor - if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) && - !(CapabilitiesGet() & bits_CAP_MOVE_FLY) && - !HasSpawnFlags( SF_NPC_FALL_TO_GROUND ) && !IsWaitingToRappel() && !GetMoveParent() ) - { - Vector origin = GetLocalOrigin(); - - if (!GetMoveProbe()->FloorPoint( origin + Vector(0, 0, 0.1), MASK_NPCSOLID, 0, -2048, &origin )) - { - Warning( "NPC %s stuck in wall--level design error at (%.2f %.2f %.2f)\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); - if ( g_pDeveloper->GetInt() > 1 ) - { - m_debugOverlays |= OVERLAY_BBOX_BIT; - } - } - - SetLocalOrigin( origin ); - } - else - { - SetGroundEntity( NULL ); - } - - if ( m_target != NULL_STRING )// this npc has a target - { - // Find the npc's initial target entity, stash it - SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) ); - - if ( !GetGoalEnt() ) - { - Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target)); - } - else - { - StartTargetHandling( GetGoalEnt() ); - } - } - - //SetState ( m_IdealNPCState ); - //SetActivity ( m_IdealActivity ); - - InitSquad(); - - //--------------------------------- - // - // Spread think times of simultaneously spawned NPCs so that they don't all happen at the same time - // - // Think distribution based on spawn order is: - // - // Tick offset Think time Spawn order - // 0 0 1 - // 1 0.015 13 - // 2 0.03 5 - // 3 0.045 9 - // 4 0.06 18 - // 5 0.075 3 - // 6 0.09 15 - // 7 0.105 11 - // 8 0.12 7 - // 9 0.135 17 - // 10 0.15 2 - // 11 0.165 14 - // 12 0.18 6 - // 13 0.195 19 - // 14 0.21 10 - // 15 0.225 4 - // 16 0.24 16 - // 17 0.255 12 - // 18 0.27 8 - // 19 0.285 20 - - - // If this NPC is spawning late in the game, just push through the rest of the initialization - // start thinking right now. Some spread is added to handle triggered spawns that bring - // a bunch of NPCs into the level - SetThink ( &CAI_BaseNPC::CallNPCThink ); - - if ( gm_flTimeLastSpawn != gpGlobals->curtime ) - { - gm_nSpawnedThisFrame = 0; - gm_flTimeLastSpawn = gpGlobals->curtime; - } - - static const float nextThinkTimes[20] = - { - .0, .150, .075, .225, .030, .180, .120, .270, .045, .210, .105, .255, .015, .165, .090, .240, .135, .060, .195, .285 - }; - - SetNextThink( gpGlobals->curtime + nextThinkTimes[gm_nSpawnedThisFrame % 20] ); - - gm_nSpawnedThisFrame++; - - //--------------------------------- - - m_ScriptArrivalActivity = AIN_DEF_ACTIVITY; - m_strScriptArrivalSequence = NULL_STRING; - - if ( HasSpawnFlags(SF_NPC_WAIT_FOR_SCRIPT) ) - { - SetState( NPC_STATE_IDLE ); - m_Activity = m_IdealActivity; - m_nIdealSequence = GetSequence(); - SetSchedule( SCHED_WAIT_FOR_SCRIPT ); - } -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::StartTargetHandling( CBaseEntity *pTargetEnt ) -{ - // set the npc up to walk a path corner path. - // !!!BUGBUG - this is a minor bit of a hack. - // JAYJAY - - // NPC will start turning towards his destination - bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY); - AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pTargetEnt->GetAbsOrigin(), - bIsFlying ? ACT_FLY : ACT_WALK, - AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST); - - SetState( NPC_STATE_IDLE ); - SetSchedule( SCHED_IDLE_WALK ); - - if ( !GetNavigator()->SetGoal( goal ) ) - { - DevWarning( 2, "Can't Create Route!\n" ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Connect my memory to the squad's -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::InitSquad( void ) -{ - // ------------------------------------------------------- - // If I form squads add me to a squad - // ------------------------------------------------------- - if (!m_pSquad && ( CapabilitiesGet() & bits_CAP_SQUAD )) - { - if ( !m_SquadName ) - { - DevMsg(2, "Found %s that isn't in a squad\n",GetClassname()); - } - else - { - m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName); - } - } - - return ( m_pSquad != NULL ); -} - -//----------------------------------------------------------------------------- -// Purpose: Get the memory for this NPC -//----------------------------------------------------------------------------- -CAI_Enemies *CAI_BaseNPC::GetEnemies( void ) -{ - return m_pEnemies; -} - -//----------------------------------------------------------------------------- -// Purpose: Remove this NPC's memory -//----------------------------------------------------------------------------- -void CAI_BaseNPC::RemoveMemory( void ) -{ - delete m_pEnemies; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::TaskComplete( bool fIgnoreSetFailedCondition ) -{ - EndTaskOverlay(); - - // Handy thing to use for debugging - //if (IsCurSchedule(SCHED_PUT_HERE) && - // GetTask()->iTask == TASK_PUT_HERE) - //{ - // int put_breakpoint_here = 5; - //} - - if ( fIgnoreSetFailedCondition || !HasCondition(COND_TASK_FAILED) ) - { - SetTaskStatus( TASKSTATUS_COMPLETE ); - } -} - -void CAI_BaseNPC::TaskMovementComplete( void ) -{ - switch( GetTaskStatus() ) - { - case TASKSTATUS_NEW: - case TASKSTATUS_RUN_MOVE_AND_TASK: - SetTaskStatus( TASKSTATUS_RUN_TASK ); - break; - - case TASKSTATUS_RUN_MOVE: - TaskComplete(); - break; - - case TASKSTATUS_RUN_TASK: - // FIXME: find out how to safely restart movement - //Warning( "Movement completed twice!\n" ); - //Assert( 0 ); - break; - - case TASKSTATUS_COMPLETE: - break; - } - - // JAY: Put this back in. - // UNDONE: Figure out how much of the timestep was consumed by movement - // this frame and restart the movement/schedule engine if necessary - if ( m_scriptState != SCRIPT_CUSTOM_MOVE_TO_MARK ) - { - SetIdealActivity( GetStoppedActivity() ); - } - - // Advance past the last node (in case there is some event at this node) - if ( GetNavigator()->IsGoalActive() ) - { - GetNavigator()->AdvancePath(); - } - - // Now clear the path, it's done. - GetNavigator()->ClearGoal(); - - OnMovementComplete(); -} - - -int CAI_BaseNPC::TaskIsRunning( void ) -{ - if ( GetTaskStatus() != TASKSTATUS_COMPLETE && - GetTaskStatus() != TASKSTATUS_RUN_MOVE ) - return 1; - - return 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::TaskFail( AI_TaskFailureCode_t code ) -{ - EndTaskOverlay(); - - // Handy tool for debugging - //if (IsCurSchedule(SCHED_PUT_NAME_HERE)) - //{ - // int put_breakpoint_here = 5; - //} - - // If in developer mode save the fail text for debug output - if (g_pDeveloper->GetInt()) - { - m_failText = TaskFailureToString( code ); - - m_interuptSchedule = NULL; - m_failedSchedule = GetCurSchedule(); - - if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT) - { - DevMsg(this, AIMF_IGNORE_SELECTED, " TaskFail -> %s\n", m_failText ); - } - - ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): TaskFail -> %s\n", GetDebugName(), entindex(), m_failText ) ); - - //AddTimedOverlay( fail_text, 5); - } - - m_ScheduleState.taskFailureCode = code; - SetCondition(COND_TASK_FAILED); - Forget( bits_MEMORY_TURNING ); -} - -//------------------------------------------------------------------------------ -// Purpose : Remember that this entity wasn't reachable -// Input : -// Output : -//------------------------------------------------------------------------------ -void CAI_BaseNPC::RememberUnreachable(CBaseEntity *pEntity) -{ - if ( pEntity == GetEnemy() ) - { - ForceChooseNewEnemy(); - } - - const float NPC_UNREACHABLE_TIMEOUT = 3; - // Only add to list if not already on it - for (int i=m_UnreachableEnts.Size()-1;i>=0;i--) - { - // If record already exists just update mark time - if (pEntity == m_UnreachableEnts[i].hUnreachableEnt) - { - m_UnreachableEnts[i].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT; - m_UnreachableEnts[i].vLocationWhenUnreachable = pEntity->GetAbsOrigin(); - return; - } - } - - // Add new unreachabe entity to list - int nNewIndex = m_UnreachableEnts.AddToTail(); - m_UnreachableEnts[nNewIndex].hUnreachableEnt = pEntity; - m_UnreachableEnts[nNewIndex].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT; - m_UnreachableEnts[nNewIndex].vLocationWhenUnreachable = pEntity->GetAbsOrigin(); -} - -//------------------------------------------------------------------------------ -// Purpose : Returns true is entity was remembered as unreachable. -// After a time delay reachability is checked -// Input : -// Output : -//------------------------------------------------------------------------------ -bool CAI_BaseNPC::IsUnreachable(CBaseEntity *pEntity) -{ - float UNREACHABLE_DIST_TOLERANCE_SQ = (120*120); - - // Note that it's ok to remove elements while I'm iterating - // as long as I iterate backwards and remove them using FastRemove - for (int i=m_UnreachableEnts.Size()-1;i>=0;i--) - { - // Remove any dead elements - if (m_UnreachableEnts[i].hUnreachableEnt == NULL) - { - m_UnreachableEnts.FastRemove(i); - } - else if (pEntity == m_UnreachableEnts[i].hUnreachableEnt) - { - // Test for reachablility on any elements that have timed out - if ( gpGlobals->curtime > m_UnreachableEnts[i].fExpireTime || - pEntity->GetAbsOrigin().DistToSqr(m_UnreachableEnts[i].vLocationWhenUnreachable) > UNREACHABLE_DIST_TOLERANCE_SQ) - { - m_UnreachableEnts.FastRemove(i); - return false; - } - return true; - } - } - return false; -} - -bool CAI_BaseNPC::IsValidEnemy( CBaseEntity *pEnemy ) -{ - CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer(); - if ( pEnemyNPC && pEnemyNPC->CanBeAnEnemyOf( this ) == false ) - return false; - - // Test our enemy filter - if ( m_hEnemyFilter.Get()!= NULL && m_hEnemyFilter->PassesFilter( this, pEnemy ) == false ) - return false; - - return true; -} - - -bool CAI_BaseNPC::CanBeAnEnemyOf( CBaseEntity *pEnemy ) -{ - if ( GetSleepState() > AISS_WAITING_FOR_THREAT ) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Picks best enemy from my list of enemies -// Prefers reachable enemies over enemies that are unreachable, -// regardless of priority. For enemies that are both reachable or -// unreachable picks by priority. If priority is the same, picks -// by distance. -// Input : -// Output : -//----------------------------------------------------------------------------- - -CBaseEntity *CAI_BaseNPC::BestEnemy( void ) -{ - AI_PROFILE_SCOPE( CAI_BaseNPC_BestEnemy ); - // TODO - may want to consider distance, attack types, back turned, etc. - - CBaseEntity* pBestEnemy = NULL; - int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE;// so first visible entity will become the closest. - int iBestPriority = -1000; - bool bBestUnreachable = true; // Forces initial check - ThreeState_t fBestSeen = TRS_NONE; - ThreeState_t fBestVisible = TRS_NONE; - int iDistSq; - bool bUnreachable = false; - - AIEnemiesIter_t iter; - - DbgEnemyMsg( this, "BestEnemy() {\n" ); - - for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) - { - CBaseEntity *pEnemy = pEMemory->hEnemy; - - if (!pEnemy || !pEnemy->IsAlive()) - { - if ( pEnemy ) - DbgEnemyMsg( this, " %s rejected: dead\n", pEnemy->GetDebugName() ); - continue; - } - - if ( (pEnemy->GetFlags() & FL_NOTARGET) ) - { - DbgEnemyMsg( this, " %s rejected: no target\n", pEnemy->GetDebugName() ); - continue; - } - - // UNDONE: Move relationship checks into IsValidEnemy? - Disposition_t relation = IRelationType( pEnemy ); - if ( (relation != D_HT && relation != D_FR) ) - { - DbgEnemyMsg( this, " %s rejected: no hate/fear\n", pEnemy->GetDebugName() ); - continue; - } - - if ( m_flAcceptableTimeSeenEnemy > 0.0 && pEMemory->timeLastSeen < m_flAcceptableTimeSeenEnemy ) - { - DbgEnemyMsg( this, " %s rejected: old\n", pEnemy->GetDebugName() ); - continue; - } - - if ( pEMemory->timeValidEnemy > gpGlobals->curtime ) - { - DbgEnemyMsg( this, " %s rejected: not yet valid\n", pEnemy->GetDebugName() ); - continue; - } - - // Skip enemies that have eluded me to prevent infinite loops - if ( pEMemory->bEludedMe ) - { - DbgEnemyMsg( this, " %s rejected: eluded\n", pEnemy->GetDebugName() ); - continue; - } - - // Skip enemies I fear that I've never seen. (usually seen through an enemy finder) - if ( relation == D_FR && !pEMemory->bUnforgettable && pEMemory->timeFirstSeen == AI_INVALID_TIME ) - { - DbgEnemyMsg( this, " %s rejected: feared, but never seen\n", pEnemy->GetDebugName() ); - continue; - } - - if ( !IsValidEnemy( pEnemy ) ) - { - DbgEnemyMsg( this, " %s rejected: not valid\n", pEnemy->GetDebugName() ); - continue; - } - - // establish the reachability of this enemy - bUnreachable = IsUnreachable(pEnemy); - - // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority - if (!bBestUnreachable && bUnreachable) - { - DbgEnemyMsg( this, " %s rejected: unreachable\n", pEnemy->GetDebugName() ); - continue; - } - - // If best is unreachable and current is reachable, always pick the current regardless of priority - if (bBestUnreachable && !bUnreachable) - { - DbgEnemyMsg( this, " %s accepted (1)\n", pEnemy->GetDebugName() ); - if ( pBestEnemy ) - DbgEnemyMsg( this, " (%s displaced)\n", pBestEnemy->GetDebugName() ); - - iBestPriority = IRelationPriority ( pEnemy ); - iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); - pBestEnemy = pEnemy; - bBestUnreachable = bUnreachable; - fBestSeen = TRS_NONE; - fBestVisible = TRS_NONE; - } - // If both are unreachable or both are reachable, chose enemy based on priority and distance - else if ( IRelationPriority( pEnemy ) > iBestPriority ) - { - DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() ); - if ( pBestEnemy ) - DbgEnemyMsg( this, " (%s displaced due to priority, %d > %d )\n", pBestEnemy->GetDebugName(), IRelationPriority( pEnemy ), iBestPriority ); - // 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. - iBestPriority = IRelationPriority ( pEnemy ); - iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); - pBestEnemy = pEnemy; - bBestUnreachable = bUnreachable; - fBestSeen = TRS_NONE; - fBestVisible = TRS_NONE; - } - else if ( IRelationPriority( pEnemy ) == iBestPriority ) - { - // 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. - iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); - - bool bAcceptCurrent = false; - bool bCloser = ( iDistSq < iBestDistSq ); - ThreeState_t fCurSeen = TRS_NONE; - ThreeState_t fCurVisible = TRS_NONE; - - // The following code is constructed in such a verbose manner to - // ensure the expensive calls only occur if absolutely needed - - // If current is farther, and best has previously been confirmed as seen or visible, move on - if ( !bCloser) - { - if ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE ) - { - DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() ); - continue; - } - } - - // If current is closer, and best has previously been confirmed as not seen and not visible, take it - if ( bCloser) - { - if ( fBestSeen == TRS_FALSE && fBestVisible == TRS_FALSE ) - { - bAcceptCurrent = true; - } - } - - if ( !bAcceptCurrent ) - { - // If current is closer, and seen, take it - if ( bCloser ) - { - fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE; - - bAcceptCurrent = ( fCurSeen == TRS_TRUE ); - } - } - - if ( !bAcceptCurrent ) - { - // If current is farther, and best is seen, move on - if ( !bCloser ) - { - if ( fBestSeen == TRS_NONE ) - { - fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE; - } - - if ( fBestSeen == TRS_TRUE ) - { - DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() ); - continue; - } - } - - // At this point, need to start performing expensive tests - if ( bCloser && fBestVisible == TRS_NONE ) - { - // Perform shortest FVisible - fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE; - - bAcceptCurrent = ( fCurVisible == TRS_TRUE ); - } - - // Alas, must do the most expensive comparison - if ( !bAcceptCurrent ) - { - if ( fBestSeen == TRS_NONE ) - { - fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE; - } - - if ( fBestVisible == TRS_NONE ) - { - fBestVisible = ( ( EnemyDistance( pBestEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE; - } - - if ( fCurSeen == TRS_NONE ) - { - fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE; - } - - if ( fCurVisible == TRS_NONE ) - { - fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE; - } - - bool bBestSeenOrVisible = ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE ); - bool bCurSeenOrVisible = ( fCurSeen == TRS_TRUE || fCurVisible == TRS_TRUE ); - - if ( !bCloser) - { - if ( bBestSeenOrVisible ) - { - DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() ); - continue; - } - else if ( !bCurSeenOrVisible ) - { - DbgEnemyMsg( this, " %s rejected: current is closer and neither is seen\n", pEnemy->GetDebugName() ); - continue; - } - } - else // Closer - { - if ( !bCurSeenOrVisible && bBestSeenOrVisible ) - { - DbgEnemyMsg( this, " %s rejected: current is father but seen\n", pEnemy->GetDebugName() ); - continue; - } - } - } - } - - DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() ); - if ( pBestEnemy ) - DbgEnemyMsg( this, " (%s displaced due to distance/visibility)\n", pBestEnemy->GetDebugName() ); - fBestSeen = fCurSeen; - fBestVisible = fCurVisible; - iBestDistSq = iDistSq; - iBestPriority = IRelationPriority ( pEnemy ); - pBestEnemy = pEnemy; - bBestUnreachable = bUnreachable; - } - else - DbgEnemyMsg( this, " %s rejected: lower priority\n", pEnemy->GetDebugName() ); - } - - DbgEnemyMsg( this, "} == %s\n", pBestEnemy->GetDebugName() ); - - return pBestEnemy; -} - -//----------------------------------------------------------------------------- -// Purpose: Given a node returns the appropriate reload activity -// Input : -// Output : -//----------------------------------------------------------------------------- -Activity CAI_BaseNPC::GetReloadActivity( CAI_Hint* pHint ) -{ - Activity nReloadActivity = ACT_RELOAD; - - if (pHint && GetEnemy()!=NULL) - { - switch (pHint->HintType()) - { - case HINT_TACTICAL_COVER_LOW: - case HINT_TACTICAL_COVER_MED: - { - if (SelectWeightedSequence( ACT_RELOAD_LOW ) != ACTIVITY_NOT_AVAILABLE) - { - Vector vEyePos = GetAbsOrigin() + EyeOffset(ACT_RELOAD_LOW); - // Check if this location will block the threat's line of sight to me - trace_t tr; - AI_TraceLOS( vEyePos, GetEnemy()->EyePosition(), this, &tr ); - if (tr.fraction != 1.0) - { - nReloadActivity = ACT_RELOAD_LOW; - } - } - break; - } - } - } - return nReloadActivity; -} - -//----------------------------------------------------------------------------- -// Purpose: Given a node returns the appropriate cover activity -// Input : -// Output : -//----------------------------------------------------------------------------- -Activity CAI_BaseNPC::GetCoverActivity( CAI_Hint *pHint ) -{ - Activity nCoverActivity = ACT_INVALID; - - // --------------------------------------------------------------- - // Check if hint node specifies different cover type - // --------------------------------------------------------------- - if (pHint) - { - switch (pHint->HintType()) - { - case HINT_TACTICAL_COVER_MED: - { - nCoverActivity = ACT_COVER_MED; - break; - } - case HINT_TACTICAL_COVER_LOW: - { - nCoverActivity = ACT_COVER_LOW; - break; - } - } - } - - if ( nCoverActivity == ACT_INVALID ) - nCoverActivity = ACT_COVER; - - return nCoverActivity; -} - -//========================================================= -// CalcIdealYaw - gets a yaw value for the caller that would -// face the supplied vector. Value is stuffed into the npc's -// ideal_yaw -//========================================================= -float CAI_BaseNPC::CalcIdealYaw( const Vector &vecTarget ) -{ - Vector vecProjection; - - // strafing npc needs to face 90 degrees away from its goal - if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_LEFT ) - { - vecProjection.x = -vecTarget.y; - vecProjection.y = vecTarget.x; - - return UTIL_VecToYaw( vecProjection - GetLocalOrigin() ); - } - else if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_RIGHT ) - { - vecProjection.x = vecTarget.y; - vecProjection.y = vecTarget.x; - - return UTIL_VecToYaw( vecProjection - GetLocalOrigin() ); - } - else - { - return UTIL_VecToYaw ( vecTarget - GetLocalOrigin() ); - } -} - -//========================================================= -// SetEyePosition -// -// queries the npc's model for $eyeposition and copies -// that vector to the npc's m_vDefaultEyeOffset and m_vecViewOffset -// -//========================================================= -void CAI_BaseNPC::SetDefaultEyeOffset ( void ) -{ - if ( GetModelPtr() ) - { - GetEyePosition( GetModelPtr(), m_vDefaultEyeOffset ); - - if ( m_vDefaultEyeOffset == vec3_origin ) - { - if ( Classify() != CLASS_NONE ) - { - DevMsg( "WARNING: %s(%s) has no eye offset in .qc!\n", GetClassname(), STRING(GetModelName()) ); - } - VectorAdd( WorldAlignMins(), WorldAlignMaxs(), m_vDefaultEyeOffset ); - m_vDefaultEyeOffset *= 0.75; - } - } - else - m_vDefaultEyeOffset = vec3_origin; - - SetViewOffset( m_vDefaultEyeOffset ); - -} - -//------------------------------------------------------------------------------ -// Purpose : Returns eye offset for an NPC for the given activity -// Input : -// Output : -//------------------------------------------------------------------------------ -Vector CAI_BaseNPC::EyeOffset( Activity nActivity ) -{ - if ( CapabilitiesGet() & bits_CAP_DUCK ) - { - if ( IsCrouchedActivity( nActivity ) ) - return GetCrouchEyeOffset(); - } - - // if the hint doesn't tell anything, assume current state - if ( IsCrouching() ) - return GetCrouchEyeOffset(); - - return m_vDefaultEyeOffset; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Vector -//----------------------------------------------------------------------------- -Vector CAI_BaseNPC::EyePosition( void ) -{ - if ( IsCrouching() ) - return GetAbsOrigin() + GetCrouchEyeOffset(); - - return BaseClass::EyePosition(); -} - -//------------------------------------------------------------------------------ -// Purpose : -// Input : -// Output : -//------------------------------------------------------------------------------ -void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) -{ - // UNDONE: Share this code into CBaseAnimating as appropriate? - switch( pEvent->event ) - { - case SCRIPT_EVENT_DEAD: - if ( m_NPCState == NPC_STATE_SCRIPT ) - { - m_lifeState = LIFE_DYING; - // Kill me now! (and fade out when CineCleanup() is called) -#if _DEBUG - DevMsg( 2, "Death event: %s\n", GetClassname() ); -#endif - m_iHealth = 0; - } -#if _DEBUG - else - DevWarning( 2, "INVALID death event:%s\n", GetClassname() ); -#endif - break; - case SCRIPT_EVENT_NOT_DEAD: - if ( m_NPCState == NPC_STATE_SCRIPT ) - { - m_lifeState = LIFE_ALIVE; - // This is for life/death sequences where the player can determine whether a character is dead or alive after the script - m_iHealth = m_iMaxHealth; - } - break; - - case SCRIPT_EVENT_SOUND: // Play a named wave file - { - EmitSound( pEvent->options ); - } - break; - - case SCRIPT_EVENT_SOUND_VOICE: - { - EmitSound( pEvent->options ); - } - break; - - case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time - if (random->RandomInt(0,2) == 0) - break; - // fall through... - case SCRIPT_EVENT_SENTENCE: // Play a named sentence group - SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, SNDLVL_TALKING, 0, 100 ); - break; - - case SCRIPT_EVENT_FIREEVENT: - { - // - // Fire a script event. The number of the script event to fire is in the options string. - // - if ( m_hCine != NULL ) - { - m_hCine->FireScriptEvent( atoi( pEvent->options ) ); - } - else - { - // FIXME: look so see if it's playing a vcd and fire those instead - // AssertOnce( 0 ); - } - break; - } - case SCRIPT_EVENT_FIRE_INPUT: - { - variant_t emptyVariant; - this->AcceptInput( pEvent->options, this, this, emptyVariant, 0 ); - break; - } - - case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on - if ( m_hCine ) - m_hCine->AllowInterrupt( false ); - break; - - case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now - if ( m_hCine ) - m_hCine->AllowInterrupt( true ); - break; - -#if 0 - case SCRIPT_EVENT_INAIR: // Don't engine->DropToFloor() - case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to - break; -#endif - case SCRIPT_EVENT_BODYGROUPON: - case SCRIPT_EVENT_BODYGROUPOFF: - case SCRIPT_EVENT_BODYGROUPTEMP: - DevMsg( "Bodygroup!\n" ); - break; - - case AE_NPC_ATTACK_BROADCAST: - break; - - case NPC_EVENT_BODYDROP_HEAVY: - if ( GetFlags() & FL_ONGROUND ) - { - EmitSound( "AI_BaseNPC.BodyDrop_Heavy" ); - } - break; - - case NPC_EVENT_BODYDROP_LIGHT: - if ( GetFlags() & FL_ONGROUND ) - { - EmitSound( "AI_BaseNPC.BodyDrop_Light" ); - } - break; - - case NPC_EVENT_SWISHSOUND: - { - // NO NPC may use this anim event unless that npc's precache precaches this sound!!! - EmitSound( "AI_BaseNPC.SwishSound" ); - break; - } - - - case NPC_EVENT_180TURN: - { - //DevMsg( "Turned!\n" ); - SetIdealActivity( ACT_IDLE ); - Forget( bits_MEMORY_TURNING ); - SetBoneController( 0, GetLocalAngles().y ); - AddEffects( EF_NOINTERP ); - break; - } - - case NPC_EVENT_ITEM_PICKUP: - { - CBaseEntity *pPickup = NULL; - - // - // Figure out what we're supposed to pick up. - // - if ( pEvent->options && strlen( pEvent->options ) > 0 ) - { - // Pick up the weapon or item that was specified in the anim event. - pPickup = gEntList.FindEntityGenericNearest( pEvent->options, GetAbsOrigin(), 256, this ); - } - else - { - // Pick up the weapon or item that was found earlier and cached in our target pointer. - pPickup = GetTarget(); - } - - // Make sure we found something to pick up. - if ( !pPickup ) - { - TaskFail("Item no longer available!\n"); - break; - } - - // Make sure the item hasn't moved. - float flDist = ( pPickup->WorldSpaceCenter() - GetAbsOrigin() ).Length2D(); - if ( flDist > ITEM_PICKUP_TOLERANCE ) - { - TaskFail("Item has moved!\n"); - break; - } - - CBaseCombatWeapon *pWeapon = dynamic_cast( pPickup ); - if ( pWeapon ) - { - // Picking up a weapon. - CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); - if ( pOwner ) - { - TaskFail( "Weapon in use by someone else" ); - } - else if ( !pWeapon ) - { - TaskFail( "Weapon doesn't exist" ); - } - else if (!Weapon_CanUse( pWeapon )) - { - TaskFail( "Can't use this weapon type" ); - } - else - { - PickupWeapon( pWeapon ); - TaskComplete(); - break; - } - } - else - { - // Picking up an item. - PickupItem( pPickup ); - TaskComplete(); - } - - break; - } - - case NPC_EVENT_WEAPON_SET_SEQUENCE_NUMBER: - { - CBaseCombatWeapon *pWeapon = GetActiveWeapon(); - if ((pWeapon) && (pEvent->options)) - { - int nSequence = atoi(pEvent->options); - if (nSequence != -1) - { - pWeapon->ResetSequence(nSequence); - } - } - break; - } - - case NPC_EVENT_WEAPON_SET_SEQUENCE_NAME: - { - CBaseCombatWeapon *pWeapon = GetActiveWeapon(); - if ((pWeapon) && (pEvent->options)) - { - int nSequence = pWeapon->LookupSequence(pEvent->options); - if (nSequence != -1) - { - pWeapon->ResetSequence(nSequence); - } - } - break; - } - - case NPC_EVENT_WEAPON_SET_ACTIVITY: - { - CBaseCombatWeapon *pWeapon = GetActiveWeapon(); - if ((pWeapon) && (pEvent->options)) - { - Activity act = (Activity)pWeapon->LookupActivity(pEvent->options); - if (act != ACT_INVALID) - { - // FIXME: where should the duration come from? normally it would come from the current sequence - Weapon_SetActivity(act, 0); - } - } - break; - } - - case NPC_EVENT_WEAPON_DROP: - { - // - // Drop our active weapon (or throw it at the specified target entity). - // - CBaseEntity *pTarget = NULL; - if (pEvent->options) - { - pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this); - } - - if (pTarget) - { - Vector vecTargetPos = pTarget->WorldSpaceCenter(); - Weapon_Drop(GetActiveWeapon(), &vecTargetPos); - } - else - { - Weapon_Drop(GetActiveWeapon()); - } - - break; - } - - case EVENT_WEAPON_RELOAD: - { - if ( GetActiveWeapon() ) - { - GetActiveWeapon()->WeaponSound( RELOAD_NPC ); - GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); - ClearCondition(COND_LOW_PRIMARY_AMMO); - ClearCondition(COND_NO_PRIMARY_AMMO); - ClearCondition(COND_NO_SECONDARY_AMMO); - } - break; - } - - case EVENT_WEAPON_RELOAD_SOUND: - { - if ( GetActiveWeapon() ) - { - GetActiveWeapon()->WeaponSound( RELOAD_NPC ); - } - break; - } - - case EVENT_WEAPON_RELOAD_FILL_CLIP: - { - if ( GetActiveWeapon() ) - { - GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); - ClearCondition(COND_LOW_PRIMARY_AMMO); - ClearCondition(COND_NO_PRIMARY_AMMO); - ClearCondition(COND_NO_SECONDARY_AMMO); - } - break; - } - - case NPC_EVENT_LEFTFOOT: - case NPC_EVENT_RIGHTFOOT: - // For right now, do nothing. All functionality for this lives in individual npcs. - break; - - case NPC_EVENT_OPEN_DOOR: - { - CBasePropDoor *pDoor = (CBasePropDoor *)(CBaseEntity *)GetNavigator()->GetPath()->GetCurWaypoint()->GetEHandleData(); - if (pDoor != NULL) - { - OpenPropDoorNow( pDoor ); - } - - break; - } - - default: - if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_SERVER)) - { - if (pEvent->event == AE_NPC_HOLSTER) - { - // Cache off the weapon. - CBaseCombatWeapon *pWeapon = GetActiveWeapon(); - - Assert( pWeapon != NULL ); - - GetActiveWeapon()->Holster(); - SetActiveWeapon( NULL ); - - //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now that we don't have a weapon out. - GetNavigator()->SetArrivalSequence( ACT_INVALID ); - - if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY ) - { - // Get rid of it! - UTIL_Remove( pWeapon ); - } - - if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE ) - { - m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; - m_Activity = ACT_RESET; - } - - return; - } - else if (pEvent->event == AE_NPC_DRAW) - { - if (GetActiveWeapon()) - { - GetActiveWeapon()->Deploy(); - - //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now. - GetNavigator()->SetArrivalSequence( ACT_INVALID ); - - if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE ) - { - m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; - m_Activity = ACT_RESET; - } - } - return; - } - else if ( pEvent->event == AE_NPC_BODYDROP_HEAVY ) - { - if ( GetFlags() & FL_ONGROUND ) - { - EmitSound( "AI_BaseNPC.BodyDrop_Heavy" ); - } - return; - } - else if ( pEvent->event == AE_NPC_LEFTFOOT || pEvent->event == AE_NPC_RIGHTFOOT ) - { - return; - } - else if ( pEvent->event == AE_NPC_RAGDOLL ) - { - // Convert to ragdoll immediately - BecomeRagdollOnClient( vec3_origin ); - return; - } - else if ( pEvent->event == AE_NPC_ADDGESTURE ) - { - Activity act = ( Activity )LookupActivity( pEvent->options ); - if (act != ACT_INVALID) - { - act = TranslateActivity( act ); - if (act != ACT_INVALID) - { - AddGesture( act ); - } - } - return; - } - else if ( pEvent->event == AE_NPC_RESTARTGESTURE ) - { - Activity act = ( Activity )LookupActivity( pEvent->options ); - if (act != ACT_INVALID) - { - act = TranslateActivity( act ); - if (act != ACT_INVALID) - { - RestartGesture( act ); - } - } - return; - } - else if ( pEvent->event == AE_NPC_WEAPON_DROP ) - { - // Drop our active weapon (or throw it at the specified target entity). - CBaseEntity *pTarget = NULL; - if (pEvent->options) - { - pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this); - } - - if (pTarget) - { - Vector vecTargetPos = pTarget->WorldSpaceCenter(); - Weapon_Drop(GetActiveWeapon(), &vecTargetPos); - } - else - { - Weapon_Drop(GetActiveWeapon()); - } - return; - } - else if ( pEvent->event == AE_NPC_WEAPON_SET_ACTIVITY ) - { - CBaseCombatWeapon *pWeapon = GetActiveWeapon(); - if ((pWeapon) && (pEvent->options)) - { - Activity act = (Activity)pWeapon->LookupActivity(pEvent->options); - if (act == ACT_INVALID) - { - // Try and translate it - act = Weapon_TranslateActivity( (Activity)CAI_BaseNPC::GetActivityID(pEvent->options), false ); - } - - if (act != ACT_INVALID) - { - // FIXME: where should the duration come from? normally it would come from the current sequence - Weapon_SetActivity(act, 0); - } - } - return; - } - else if ( pEvent->event == AE_NPC_SET_INTERACTION_CANTDIE ) - { - SetInteractionCantDie( (atoi(pEvent->options) != 0) ); - return; - } - else if ( pEvent->event == AE_NPC_HURT_INTERACTION_PARTNER ) - { - // If we're currently interacting with an enemy, hurt them/me - if ( m_hInteractionPartner ) - { - CAI_BaseNPC *pTarget = NULL; - CAI_BaseNPC *pAttacker = NULL; - if ( pEvent->options ) - { - char szEventOptions[128]; - Q_strncpy( szEventOptions, pEvent->options, sizeof(szEventOptions) ); - char *pszParam = strtok( szEventOptions, " " ); - if ( pszParam ) - { - if ( !Q_strncmp( pszParam, "ME", 2 ) ) - { - pTarget = this; - pAttacker = m_hInteractionPartner; - } - else if ( !Q_strncmp( pszParam, "THEM", 4 ) ) - { - pAttacker = this; - pTarget = m_hInteractionPartner; - } - - pszParam = strtok(NULL," "); - if ( pAttacker && pTarget && pszParam ) - { - int iDamage = atoi( pszParam ); - if ( iDamage ) - { - // We've got a target, and damage. Now hurt them. - CTakeDamageInfo info; - info.SetDamage( iDamage ); - info.SetAttacker( pAttacker ); - info.SetInflictor( pAttacker ); - info.SetDamageType( DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE ); - pTarget->TakeDamage( info ); - return; - } - } - } - } - - // Bad data. Explain how to use this anim event. - const char *pName = EventList_NameForIndex( pEvent->event ); - DevWarning( 1, "Bad %s format. Should be: { AE_NPC_HURT_INTERACTION_PARTNER \" \" }\n", pName ); - return; - } - - DevWarning( "%s received AE_NPC_HURT_INTERACTION_PARTNER anim event, but it's not interacting with anything.\n", GetDebugName() ); - return; - } - } - - // FIXME: why doesn't this code pass unhandled events down to its parent? - // Came from my weapon? - //Adrian I'll clean this up once the old event system is phased out. - if ( pEvent->pSource != this || ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM && pEvent->type & AE_TYPE_WEAPON ) || (pEvent->event >= EVENT_WEAPON && pEvent->event <= EVENT_WEAPON_LAST) ) - { - Weapon_HandleAnimEvent( pEvent ); - } - else - { - BaseClass::HandleAnimEvent( pEvent ); - } - break; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Override base class to add display of routes -// Input : -// Output : Current text offset from the top -//----------------------------------------------------------------------------- -void CAI_BaseNPC::DrawDebugGeometryOverlays(void) -{ - // Handy for debug - //NDebugOverlay::Cross3D(EyePosition(),Vector(-2,-2,-2),Vector(2,2,2),0,255,0,true); - - // ------------------------------ - // Remove me if requested - // ------------------------------ - if (m_debugOverlays & OVERLAY_NPC_ZAP_BIT) - { - VacateStrategySlot(); - Weapon_Drop( GetActiveWeapon() ); - m_iHealth = 0; - SetThink( &CAI_BaseNPC::SUB_Remove ); - } - - // ------------------------------ - // properly kill an NPC. - // ------------------------------ - if (m_debugOverlays & OVERLAY_NPC_KILL_BIT) - { - CTakeDamageInfo info; - - info.SetDamage( m_iHealth ); - info.SetAttacker( this ); - info.SetInflictor( ( AI_IsSinglePlayer() ) ? (CBaseEntity *)AI_GetSinglePlayer() : (CBaseEntity *)this ); - info.SetDamageType( DMG_GENERIC ); - - m_debugOverlays &= ~OVERLAY_NPC_KILL_BIT; - TakeDamage( info ); - return; - } - - - // ------------------------------ - // Draw route if requested - // ------------------------------ - if ((m_debugOverlays & OVERLAY_NPC_ROUTE_BIT)) - { - GetNavigator()->DrawDebugRouteOverlay(); - if ( IsMoving() ) - { - float yaw = GetMotor()->GetIdealYaw(); - Vector vecYaw = UTIL_YawToVector(yaw); - NDebugOverlay::Line(WorldSpaceCenter(),WorldSpaceCenter() + vecYaw * GetHullWidth() * .5,255,255,255,true,0.0); - } - } - - if (!(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) && (IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN))) - { - NDebugOverlay::Box(m_vecLastPosition, Vector(-5,-5,-5),Vector(5,5,5), 255, 0, 255, 0, 0); - NDebugOverlay::HorzArrow( GetAbsOrigin(), m_vecLastPosition, 16, 255, 0, 255, 64, true, 0 ); - } - - // ------------------------------ - // Draw red box around if selected - // ------------------------------ - if ((m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) && !ai_no_select_box.GetBool()) - { - NDebugOverlay::EntityBounds(this, 255, 0, 0, 20, 0); - } - - // ------------------------------ - // Draw nearest node if selected - // ------------------------------ - if ((m_debugOverlays & OVERLAY_NPC_NEAREST_BIT)) - { - int iNodeID = GetPathfinder()->NearestNodeToNPC(); - if (iNodeID != NO_NODE) - { - NDebugOverlay::Box(GetNavigator()->GetNetwork()->AccessNodes()[iNodeID]->GetPosition(GetHullType()), Vector(-10,-10,-10),Vector(10,10,10), 255, 255, 255, 0, 0); - } - } - - // ------------------------------ - // Draw viewcone if selected - // ------------------------------ - if ((m_debugOverlays & OVERLAY_NPC_VIEWCONE_BIT)) - { - float flViewRange = acos(m_flFieldOfView); - Vector vEyeDir = EyeDirection2D( ); - Vector vLeftDir, vRightDir; - float fSin, fCos; - SinCos( flViewRange, &fSin, &fCos ); - - vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin; - vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos; - vLeftDir.z = vEyeDir.z; - fSin = sin(-flViewRange); - fCos = cos(-flViewRange); - vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin; - vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos; - vRightDir.z = vEyeDir.z; - - // Visualize it - NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vLeftDir * 200 ), 64, 255, 0, 0, 50, false, 0 ); - NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vRightDir * 200 ), 64, 255, 0, 0, 50, false, 0 ); - NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vEyeDir * 100 ), 8, 0, 255, 0, 50, false, 0 ); - NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 ); - } - - // ---------------------------------------------- - // Draw the relationships for this NPC to others - // ---------------------------------------------- - if ( m_debugOverlays & OVERLAY_NPC_RELATION_BIT ) - { - // Show the relationships to entities around us - int r = 0; - int g = 0; - int b = 0; - - int nRelationship; - CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); - - // Rate all NPCs - for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) - { - if ( ppAIs[i] == NULL || ppAIs[i] == this ) - continue; - - // Get our relation to the target - nRelationship = IRelationType( ppAIs[i] ); - - // Get the color for the arrow - UTIL_GetDebugColorForRelationship( nRelationship, r, g, b ); - - // Draw an arrow - NDebugOverlay::HorzArrow( GetAbsOrigin(), ppAIs[i]->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f ); - } - - // Also include all players - for ( int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - if ( pPlayer == NULL ) - continue; - - // Get our relation to the target - nRelationship = IRelationType( pPlayer ); - - // Get the color for the arrow - UTIL_GetDebugColorForRelationship( nRelationship, r, g, b ); - - // Draw an arrow - NDebugOverlay::HorzArrow( GetAbsOrigin(), pPlayer->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f ); - } - } - - // ------------------------------ - // Draw enemies if selected - // ------------------------------ - if ((m_debugOverlays & OVERLAY_NPC_ENEMIES_BIT)) - { - AIEnemiesIter_t iter; - for( AI_EnemyInfo_t *eMemory = GetEnemies()->GetFirst(&iter); eMemory != NULL; eMemory = GetEnemies()->GetNext(&iter) ) - { - if (eMemory->hEnemy) - { - CBaseCombatCharacter *npcEnemy = (eMemory->hEnemy)->MyCombatCharacterPointer(); - if (npcEnemy) - { - float r,g,b; - char debugText[255]; - debugText[0] = NULL; - - if (npcEnemy == GetEnemy()) - { - Q_strncat(debugText,"Current Enemy", sizeof( debugText ), COPY_ALL_CHARACTERS ); - } - else if (npcEnemy == GetTarget()) - { - Q_strncat(debugText,"Current Target", sizeof( debugText ), COPY_ALL_CHARACTERS ); - } - else - { - Q_strncat(debugText,"Other Memory", sizeof( debugText ), COPY_ALL_CHARACTERS ); - } - if (IsUnreachable(npcEnemy)) - { - Q_strncat(debugText," (Unreachable)", sizeof( debugText ), COPY_ALL_CHARACTERS ); - } - if (eMemory->bEludedMe) - { - Q_strncat(debugText," (Eluded)", sizeof( debugText ), COPY_ALL_CHARACTERS ); - } - // Unreachable enemy drawn in green - if (IsUnreachable(npcEnemy)) - { - r = 0; - g = 255; - b = 0; - } - // Eluded enemy drawn in blue - else if (eMemory->bEludedMe) - { - r = 0; - g = 0; - b = 255; - } - // Current enemy drawn in red - else if (npcEnemy == GetEnemy()) - { - r = 255; - g = 0; - b = 0; - } - // Current traget drawn in magenta - else if (npcEnemy == GetTarget()) - { - r = 255; - g = 0; - b = 255; - } - // All other enemies drawn in pink - else - { - r = 255; - g = 100; - b = 100; - } - - - Vector drawPos = eMemory->vLastKnownLocation; - NDebugOverlay::Text( drawPos, debugText, false, 0.0 ); - - // If has a line on the player draw cross slightly in front so player can see - if (npcEnemy->IsPlayer() && - (eMemory->vLastKnownLocation - npcEnemy->GetAbsOrigin()).Length()<10 ) - { - Vector vEnemyFacing = npcEnemy->BodyDirection2D( ); - Vector eyePos = npcEnemy->EyePosition() + vEnemyFacing*10.0; - Vector upVec = Vector(0,0,2); - Vector sideVec; - CrossProduct( vEnemyFacing, upVec, sideVec); - NDebugOverlay::Line(eyePos+sideVec+upVec, eyePos-sideVec-upVec, r,g,b, false,0); - NDebugOverlay::Line(eyePos+sideVec-upVec, eyePos-sideVec+upVec, r,g,b, false,0); - - NDebugOverlay::Text( eyePos, debugText, false, 0.0 ); - } - else - { - NDebugOverlay::Cross3D(drawPos,NAI_Hull::Mins(npcEnemy->GetHullType()),NAI_Hull::Maxs(npcEnemy->GetHullType()),r,g,b,false,0); - } - } - } - } - } - - // ---------------------------------------------- - // Draw line to target and enemy entity if exist - // ---------------------------------------------- - if ((m_debugOverlays & OVERLAY_NPC_FOCUS_BIT)) - { - if (GetEnemy() != NULL) - { - NDebugOverlay::Line(EyePosition(),GetEnemy()->EyePosition(),255,0,0,true,0.0); - } - if (GetTarget() != NULL) - { - NDebugOverlay::Line(EyePosition(),GetTarget()->EyePosition(),0,0,255,true,0.0); - } - } - - - GetPathfinder()->DrawDebugGeometryOverlays(m_debugOverlays); - - CBaseEntity::DrawDebugGeometryOverlays(); -} - -//----------------------------------------------------------------------------- -// Purpose: Draw any debug text overlays -// Input : -// Output : Current text offset from the top -//----------------------------------------------------------------------------- -int CAI_BaseNPC::DrawDebugTextOverlays(void) -{ - int text_offset = 0; - - // --------------------- - // Print Baseclass text - // --------------------- - text_offset = BaseClass::DrawDebugTextOverlays(); - - if (m_debugOverlays & OVERLAY_NPC_SQUAD_BIT) - { - // Print health - char tempstr[512]; - Q_snprintf(tempstr,sizeof(tempstr),"Health: %i",m_iHealth); - EntityText(text_offset,tempstr,0); - text_offset++; - - // Print squad name - Q_strncpy(tempstr,"Squad: ",sizeof(tempstr)); - if (m_pSquad) - { - Q_strncat(tempstr,m_pSquad->GetName(),sizeof(tempstr), COPY_ALL_CHARACTERS); - - if( m_pSquad->GetLeader() == this ) - { - Q_strncat(tempstr," (LEADER)",sizeof(tempstr), COPY_ALL_CHARACTERS); - } - - Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS); - } - else - { - Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS); - } - EntityText(text_offset,tempstr,0); - text_offset++; - - // Print enemy name - Q_strncpy(tempstr,"Enemy: ",sizeof(tempstr)); - if (GetEnemy()) - { - if (GetEnemy()->GetEntityName() != NULL_STRING) - { - Q_strncat(tempstr,STRING(GetEnemy()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS); - Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS); - } - else - { - Q_strncat(tempstr,STRING(GetEnemy()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS); - Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS); - } - } - else - { - Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS); - } - EntityText(text_offset,tempstr,0); - text_offset++; - - // Print slot - Q_snprintf(tempstr,sizeof(tempstr),"Slot: %s \n", - SquadSlotName(m_iMySquadSlot)); - EntityText(text_offset,tempstr,0); - text_offset++; - - } - - if (m_debugOverlays & OVERLAY_TEXT_BIT) - { - char tempstr[512]; - // -------------- - // Print Health - // -------------- - Q_snprintf(tempstr,sizeof(tempstr),"Health: %i (DACC:%1.2f)",m_iHealth, GetDamageAccumulator() ); - EntityText(text_offset,tempstr,0); - text_offset++; - - // -------------- - // Print State - // -------------- - static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" }; - if ( (int)m_NPCState < ARRAYSIZE(pStateNames) ) - { - Q_snprintf(tempstr,sizeof(tempstr),"Stat: %s, ", pStateNames[m_NPCState] ); - EntityText(text_offset,tempstr,0); - text_offset++; - } - - // ----------------- - // Start Scripting? - // ----------------- - if( IsInAScript() ) - { - Q_snprintf(tempstr,sizeof(tempstr),"STARTSCRIPTING" ); - EntityText(text_offset,tempstr,0); - text_offset++; - } - - // ----------------- - // Print MotionType - // ----------------- - int navTypeIndex = (int)GetNavType() + 1; - static const char *pMoveNames[] = { "None", "Ground", "Jump", "Fly", "Climb" }; - Assert( navTypeIndex >= 0 && navTypeIndex < ARRAYSIZE(pMoveNames) ); - if ( navTypeIndex < ARRAYSIZE(pMoveNames) ) - { - Q_snprintf(tempstr,sizeof(tempstr),"Move: %s, ", pMoveNames[navTypeIndex] ); - EntityText(text_offset,tempstr,0); - text_offset++; - } - - // -------------- - // Print Schedule - // -------------- - if ( GetCurSchedule() ) - { - CAI_BehaviorBase *pBehavior = GetRunningBehavior(); - if ( pBehavior ) - { - Q_snprintf(tempstr,sizeof(tempstr),"Behv: %s, ", pBehavior->GetName() ); - EntityText(text_offset,tempstr,0); - text_offset++; - } - - const char *pName = NULL; - pName = GetCurSchedule()->GetName(); - if ( !pName ) - { - pName = "Unknown"; - } - Q_snprintf(tempstr,sizeof(tempstr),"Schd: %s, ", pName ); - EntityText(text_offset,tempstr,0); - text_offset++; - - if (m_debugOverlays & OVERLAY_NPC_TASK_BIT) - { - for (int i = 0 ; i < GetCurSchedule()->NumTasks(); i++) - { - Q_snprintf(tempstr,sizeof(tempstr),"%s%s%s%s", - ((i==0) ? "Task:":" "), - ((i==GetScheduleCurTaskIndex()) ? "->" :" "), - TaskName(GetCurSchedule()->GetTaskList()[i].iTask), - ((i==GetScheduleCurTaskIndex()) ? "<-" :"")); - - EntityText(text_offset,tempstr,0); - text_offset++; - } - } - else - { - const Task_t *pTask = GetTask(); - if ( pTask ) - { - Q_snprintf(tempstr,sizeof(tempstr),"Task: %s (#%d), ", TaskName(pTask->iTask), GetScheduleCurTaskIndex() ); - } - else - { - Q_strncpy(tempstr,"Task: None",sizeof(tempstr)); - } - EntityText(text_offset,tempstr,0); - text_offset++; - } - } - - // -------------- - // Print Acitivity - // -------------- - if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID && m_Activity != ACT_RESET) - { - Activity iActivity = TranslateActivity( m_Activity ); - - Activity iIdealActivity = Weapon_TranslateActivity( m_IdealActivity ); - iIdealActivity = NPC_TranslateActivity( iIdealActivity ); - - const char *pszActivity = GetActivityName( iActivity ); - const char *pszIdealActivity = GetActivityName( iIdealActivity ); - const char *pszRootActivity = GetActivityName( m_Activity ); - - Q_snprintf(tempstr,sizeof(tempstr),"Actv: %s (%s) [%s]\n", pszActivity, pszIdealActivity, pszRootActivity ); - } - else if (m_Activity == ACT_RESET) - { - Q_strncpy(tempstr,"Actv: RESET",sizeof(tempstr) ); - } - else - { - Q_strncpy(tempstr,"Actv: INVALID", sizeof(tempstr) ); - } - EntityText(text_offset,tempstr,0); - text_offset++; - - // - // Print all the current conditions. - // - if (m_debugOverlays & OVERLAY_NPC_CONDITIONS_BIT) - { - bool bHasConditions = false; - for (int i = 0; i < MAX_CONDITIONS; i++) - { - if (m_Conditions.GetBit(i)) - { - Q_snprintf(tempstr, sizeof(tempstr), "Cond: %s\n", ConditionName(AI_RemapToGlobal(i))); - EntityText(text_offset, tempstr, 0); - text_offset++; - bHasConditions = true; - } - } - if (!bHasConditions) - { - Q_snprintf(tempstr,sizeof(tempstr),"(no conditions)",m_iHealth); - EntityText(text_offset,tempstr,0); - text_offset++; - } - } - - if ( GetFlags() & FL_FLY ) - { - EntityText(text_offset,"HAS FL_FLY",0); - text_offset++; - } - - // -------------- - // Print Interrupte - // -------------- - if (m_interuptSchedule) - { - const char *pName = NULL; - pName = m_interuptSchedule->GetName(); - if ( !pName ) - { - pName = "Unknown"; - } - - Q_snprintf(tempstr,sizeof(tempstr),"Intr: %s (%s)\n", pName, m_interruptText ); - EntityText(text_offset,tempstr,0); - text_offset++; - } - - // -------------- - // Print Failure - // -------------- - if (m_failedSchedule) - { - const char *pName = NULL; - pName = m_failedSchedule->GetName(); - if ( !pName ) - { - pName = "Unknown"; - } - Q_snprintf(tempstr,sizeof(tempstr),"Fail: %s (%s)\n", pName,m_failText ); - EntityText(text_offset,tempstr,0); - text_offset++; - } - - - // ------------------------------- - // Print any important condtions - // ------------------------------- - if (HasCondition(COND_ENEMY_TOO_FAR)) - { - EntityText(text_offset,"Enemy too far to attack",0); - text_offset++; - } - if ( GetAbsVelocity() != vec3_origin || GetLocalAngularVelocity() != vec3_angle ) - { - char tmp[512]; - Q_snprintf( tmp, sizeof(tmp), "Vel %.1f %.1f %.1f Ang: %.1f %.1f %.1f\n", - GetAbsVelocity().x, GetAbsVelocity().y, GetAbsVelocity().z, - GetLocalAngularVelocity().x, GetLocalAngularVelocity().y, GetLocalAngularVelocity().z ); - EntityText(text_offset,tmp,0); - text_offset++; - } - - // ------------------------------- - // Print shot accuracy - // ------------------------------- - if ( m_LastShootAccuracy != -1 && ai_shot_stats.GetBool() ) - { - CFmtStr msg; - EntityText(text_offset,msg.sprintf("Cur Accuracy: %.1f", m_LastShootAccuracy),0); - text_offset++; - if ( m_TotalShots ) - { - EntityText(text_offset,msg.sprintf("Act Accuracy: %.1f", ((float)m_TotalHits/(float)m_TotalShots)*100.0),0); - text_offset++; - } - - if ( GetActiveWeapon() && GetEnemy() ) - { - Vector curSpread = GetAttackSpread(GetActiveWeapon(), GetEnemy()); - float curCone = RAD2DEG(asin(curSpread.x)) * 2; - float bias = GetSpreadBias( GetActiveWeapon(), GetEnemy()); - EntityText(text_offset,msg.sprintf("Cone %.1f, Bias %.2f", curCone, bias),0); - text_offset++; - } - } - - if ( GetGoalEnt() && GetNavigator()->GetGoalType() == GOALTYPE_PATHCORNER ) - { - Q_strncpy(tempstr,"Pathcorner/goal ent: ",sizeof(tempstr)); - if (GetGoalEnt()->GetEntityName() != NULL_STRING) - { - Q_strncat(tempstr,STRING(GetGoalEnt()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS); - } - else - { - Q_strncat(tempstr,STRING(GetGoalEnt()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS); - } - EntityText(text_offset, tempstr, 0); - text_offset++; - } - - if ( VPhysicsGetObject() ) - { - vphysics_objectstress_t stressOut; - CalculateObjectStress( VPhysicsGetObject(), this, &stressOut ); - Q_snprintf(tempstr, sizeof(tempstr),"Stress: %.2f", stressOut.receivedStress ); - EntityText(text_offset, tempstr, 0); - text_offset++; - } - if ( m_pSquad ) - { - if( m_pSquad->IsLeader(this) ) - { - Q_snprintf(tempstr, sizeof(tempstr),"**Squad Leader**" ); - EntityText(text_offset, tempstr, 0); - text_offset++; - } - - Q_snprintf(tempstr, sizeof(tempstr), "SquadSlot:%s", GetSquadSlotDebugName( GetMyStrategySlot() ) ); - EntityText(text_offset, tempstr, 0); - text_offset++; - } - } - return text_offset; -} - - -//----------------------------------------------------------------------------- -// Purpose: Displays information in the console about the state of this npc. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ReportAIState( void ) -{ - static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" }; - - DevMsg( "%s: ", GetClassname() ); - if ( (int)m_NPCState < ARRAYSIZE(pStateNames) ) - DevMsg( "State: %s, ", pStateNames[m_NPCState] ); - - if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID ) - { - const char *pszActivity = GetActivityName(m_Activity); - const char *pszIdealActivity = GetActivityName(m_IdealActivity); - - DevMsg( "Activity: %s - Ideal Activity: %s\n", pszActivity, pszIdealActivity ); - } - - if ( GetCurSchedule() ) - { - const char *pName = NULL; - pName = GetCurSchedule()->GetName(); - if ( !pName ) - pName = "Unknown"; - DevMsg( "Schedule %s, ", pName ); - const Task_t *pTask = GetTask(); - if ( pTask ) - DevMsg( "Task %d (#%d), ", pTask->iTask, GetScheduleCurTaskIndex() ); - } - else - DevMsg( "No Schedule, " ); - - if ( GetEnemy() != NULL ) - { - g_pEffects->Sparks( GetEnemy()->GetAbsOrigin() + Vector( 0, 0, 64 ) ); - DevMsg( "\nEnemy is %s", GetEnemy()->GetClassname() ); - } - else - DevMsg( "No enemy " ); - - if ( IsMoving() ) - { - DevMsg( " Moving " ); - if ( m_flMoveWaitFinished > gpGlobals->curtime ) - DevMsg( ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->curtime ); - else if ( m_IdealActivity == GetStoppedActivity() ) - DevMsg( ": In stopped anim. " ); - } - - DevMsg( "Leader." ); - - DevMsg( "\n" ); - DevMsg( "Yaw speed:%3.1f,Health: %3d\n", GetMotor()->GetYawSpeed(), m_iHealth ); - - if ( GetGroundEntity() ) - { - DevMsg( "Groundent:%s\n\n", GetGroundEntity()->GetClassname() ); - } - else - { - DevMsg( "Groundent: NULL\n\n" ); - } -} - -//----------------------------------------------------------------------------- - -ConVar ai_report_task_timings_on_limit( "ai_report_task_timings_on_limit", "0", FCVAR_ARCHIVE ); -ConVar ai_think_limit_label( "ai_think_limit_label", "0", FCVAR_ARCHIVE ); - -void CAI_BaseNPC::ReportOverThinkLimit( float time ) -{ - DevMsg( "%s thinking for %.02fms!!! (%s); r%.2f (c%.2f, pst%.2f, ms%.2f), p-r%.2f, m%.2f\n", - GetDebugName(), time, GetCurSchedule()->GetName(), - g_AIRunTimer.GetDuration().GetMillisecondsF(), - g_AIConditionsTimer.GetDuration().GetMillisecondsF(), - g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF(), - g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF(), - g_AIPostRunTimer.GetDuration().GetMillisecondsF(), - g_AIMoveTimer.GetDuration().GetMillisecondsF() ); - - if (ai_think_limit_label.GetBool()) - { - Vector tmp; - CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &tmp ); - tmp.z += 16; - - float max = -1; - const char *pszMax = "unknown"; - - if ( g_AIConditionsTimer.GetDuration().GetMillisecondsF() > max ) - { - max = g_AIConditionsTimer.GetDuration().GetMillisecondsF(); - pszMax = "Conditions"; - } - if ( g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF() > max ) - { - max = g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF(); - pszMax = "Pre-think"; - } - if ( g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF() > max ) - { - max = g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF(); - pszMax = "Schedule"; - } - if ( g_AIPostRunTimer.GetDuration().GetMillisecondsF() > max ) - { - max = g_AIPostRunTimer.GetDuration().GetMillisecondsF(); - pszMax = "Post-run"; - } - if ( g_AIMoveTimer.GetDuration().GetMillisecondsF() > max ) - { - max = g_AIMoveTimer.GetDuration().GetMillisecondsF(); - pszMax = "Move"; - } - NDebugOverlay::Text( tmp, (char*)(const char *)CFmtStr( "Slow %.1f, %s %.1f ", time, pszMax, max ), false, 1 ); - } - - if ( ai_report_task_timings_on_limit.GetBool() ) - DumpTaskTimings(); -} - -//----------------------------------------------------------------------------- -// Purpose: Returns whether or not this npc can play the scripted sequence or AI -// sequence that is trying to possess it. If DisregardState is set, the npc -// will be sucked into the script no matter what state it is in. ONLY -// Scripted AI ents should allow this. -// Input : fDisregardNPCState - -// interruptLevel - -// eMode - If the function returns true, eMode will be one of the following values: -// CAN_PLAY_NOW -// CAN_PLAY_ENQUEUED -// Output : -//----------------------------------------------------------------------------- -CanPlaySequence_t CAI_BaseNPC::CanPlaySequence( bool fDisregardNPCState, int interruptLevel ) -{ - CanPlaySequence_t eReturn = CAN_PLAY_NOW; - - if ( m_hCine ) - { - if ( !m_hCine->CanEnqueueAfter() ) - { - // npc is already running one scripted sequence and has an important script to play next - return CANNOT_PLAY; - } - - eReturn = CAN_PLAY_ENQUEUED; - } - - if ( !IsAlive() ) - { - // npc is dead! - return CANNOT_PLAY; - } - - if ( fDisregardNPCState ) - { - // ok to go, no matter what the npc state. (scripted AI) - - // No clue as to how to proced if they're climbing or jumping - // Assert( GetNavType() != NAV_JUMP && GetNavType() != NAV_CLIMB ); - - return eReturn; - } - - if ( m_NPCState == NPC_STATE_NONE || m_NPCState == NPC_STATE_IDLE || m_IdealNPCState == NPC_STATE_IDLE ) - { - // ok to go, but only in these states - return eReturn; - } - - if ( m_NPCState == NPC_STATE_ALERT && interruptLevel >= SS_INTERRUPT_BY_NAME ) - { - return eReturn; - } - - // unknown situation - return CANNOT_PLAY; -} - - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::SetHintGroup( string_t newGroup, bool bHintGroupNavLimiting ) -{ - string_t oldGroup = m_strHintGroup; - m_strHintGroup = newGroup; - m_bHintGroupNavLimiting = bHintGroupNavLimiting; - - if ( oldGroup != newGroup ) - OnChangeHintGroup( oldGroup, newGroup ); - -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -Vector CAI_BaseNPC::GetShootEnemyDir( const Vector &shootOrigin, bool bNoisy ) -{ - CBaseEntity *pEnemy = GetEnemy(); - - if ( pEnemy ) - { - Vector vecEnemyLKP = GetEnemyLKP(); - Vector retval = (pEnemy->BodyTarget( shootOrigin, bNoisy ) - pEnemy->GetAbsOrigin()) + vecEnemyLKP - shootOrigin; - VectorNormalize( retval ); - return retval; - } - else - { - Vector forward; - AngleVectors( GetLocalAngles(), &forward ); - return forward; - } -} - -//----------------------------------------------------------------------------- -// Simulates many times and reports statistical accuracy. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CollectShotStats( const Vector &vecShootOrigin, const Vector &vecShootDir ) -{ -#ifdef HL2_DLL - if( ai_shot_stats.GetBool() != 0 && GetEnemy()->IsPlayer() ) - { - int iterations = ai_shot_stats_term.GetInt(); - int iHits = 0; - Vector testDir = vecShootDir; - - CShotManipulator manipulator( testDir ); - - for( int i = 0 ; i < iterations ; i++ ) - { - // Apply appropriate accuracy. - manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) ); - Vector shotDir = manipulator.GetResult(); - - Vector vecEnd = vecShootOrigin + shotDir * 8192; - - trace_t tr; - AI_TraceLine( vecShootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); - - if( tr.m_pEnt && tr.m_pEnt == GetEnemy() ) - { - iHits++; - } - Vector vecProjectedPosition = GetActualShootPosition( vecShootOrigin ); - Vector testDir = vecProjectedPosition - vecShootOrigin; - VectorNormalize( testDir ); - manipulator.SetShootDir( testDir ); - } - - float flHitPercent = ((float)iHits / (float)iterations) * 100.0; - m_LastShootAccuracy = flHitPercent; - //DevMsg("Shots:%d Hits:%d Percentage:%.1f\n", iterations, iHits, flHitPercent); - } - else - { - m_LastShootAccuracy = -1; - } -#endif -} - -#ifdef HL2_DLL -//----------------------------------------------------------------------------- -// Purpose: Return the actual position the NPC wants to fire at when it's trying -// to hit it's current enemy. -//----------------------------------------------------------------------------- -Vector CAI_BaseNPC::GetActualShootPosition( const Vector &shootOrigin ) -{ - // Project the target's location into the future. - Vector vecEnemyLKP = GetEnemyLKP(); - Vector vecTargetPosition = (GetEnemy()->BodyTarget( shootOrigin ) - GetEnemy()->GetAbsOrigin()) + vecEnemyLKP; - - // lead for some fraction of a second. - return (vecTargetPosition + ( GetEnemy()->GetSmoothedVelocity() * ai_lead_time.GetFloat() )); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -float CAI_BaseNPC::GetSpreadBias( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) -{ - float bias = BaseClass::GetSpreadBias( pWeapon, pTarget ); - AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget ); - if ( ai_shot_bias.GetFloat() != 1.0 ) - bias = ai_shot_bias.GetFloat(); - if ( pEnemyInfo ) - { - float timeToFocus = ai_spread_pattern_focus_time.GetFloat(); - if ( timeToFocus > 0.0 ) - { - float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy; - if ( timeSinceValidEnemy < 0.0f ) - { - timeSinceValidEnemy = 0.0f; - } - float timeSinceReacquire = gpGlobals->curtime - pEnemyInfo->timeLastReacquired; - if ( timeSinceValidEnemy < timeToFocus ) - { - float scale = timeSinceValidEnemy / timeToFocus; - Assert( scale >= 0.0 && scale <= 1.0 ); - bias *= scale; - } - else if ( timeSinceReacquire < timeToFocus ) // handled seperately as might be tuning seperately - { - float scale = timeSinceReacquire / timeToFocus; - Assert( scale >= 0.0 && scale <= 1.0 ); - bias *= scale; - } - - } - } - return bias; -} - -//----------------------------------------------------------------------------- -Vector CAI_BaseNPC::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) -{ - Vector baseResult = BaseClass::GetAttackSpread( pWeapon, pTarget ); - - AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget ); - if ( pEnemyInfo ) - { - float timeToFocus = ai_spread_cone_focus_time.GetFloat(); - if ( timeToFocus > 0.0 ) - { - float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy; - if ( timeSinceValidEnemy < 0 ) - timeSinceValidEnemy = 0; - if ( timeSinceValidEnemy < timeToFocus ) - { - float coneMultiplier = ai_spread_defocused_cone_multiplier.GetFloat(); - if ( coneMultiplier > 1.0 ) - { - float scale = 1.0 - timeSinceValidEnemy / timeToFocus; - Assert( scale >= 0.0 && scale <= 1.0 ); - float multiplier = ( (coneMultiplier - 1.0) * scale ) + 1.0; - baseResult *= multiplier; - } - } - } - } - return baseResult; -} - -//----------------------------------------------------------------------------- -// Similar to calling GetShootEnemyDir, but returns the exact trajectory to -// fire the bullet along, after calculating for target speed, location, -// concealment, etc. -// -// Ultimately, this code results in the shooter aiming his weapon somewhere ahead of -// a moving target. Since bullet traces are instant, aiming ahead of a target -// will result in more misses than hits. This is designed to provide targets with -// a bonus when moving perpendicular to the shooter's line of sight. -// -// Do not confuse this with leading a target in an effort to more easily hit it. -// -// This code PENALIZES a target for moving directly along the shooter's line of sight. -// -// This code REWARDS a target for moving perpendicular to the shooter's line of sight. -//----------------------------------------------------------------------------- -Vector CAI_BaseNPC::GetActualShootTrajectory( const Vector &shootOrigin ) -{ - if( !GetEnemy() ) - return GetShootEnemyDir( shootOrigin ); - - // If we're above water shooting at a player underwater, bias some of the shots forward of - // the player so that they see the cool bubble trails in the water ahead of them. - if (GetEnemy()->IsPlayer() && (GetWaterLevel() != 3) && (GetEnemy()->GetWaterLevel() == 3)) - { -#if 1 - if (random->RandomInt(0, 4) < 3) - { - Vector vecEnemyForward; - GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL ); - vecEnemyForward.z = 0; - - // Lead up to a second ahead of them unless they are moving backwards. - Vector vecEnemyVelocity = GetEnemy()->GetSmoothedVelocity(); - VectorNormalize( vecEnemyVelocity ); - float flVelocityScale = vecEnemyForward.Dot( vecEnemyVelocity ); - if ( flVelocityScale < 0.0f ) - { - flVelocityScale = 0.0f; - } - - Vector vecAimPos = GetEnemy()->EyePosition() + ( 48.0f * vecEnemyForward ) + (flVelocityScale * GetEnemy()->GetSmoothedVelocity() ); - //NDebugOverlay::Cross3D(vecAimPos, Vector(-16,-16,-16), Vector(16,16,16), 255, 255, 0, true, 1.0f ); - - //vecAimPos.z = UTIL_WaterLevel( vecAimPos, vecAimPos.z, vecAimPos.z + 400.0f ); - //NDebugOverlay::Cross3D(vecAimPos, Vector(-32,-32,-32), Vector(32,32,32), 255, 0, 0, true, 1.0f ); - - Vector vecShotDir = vecAimPos - shootOrigin; - VectorNormalize( vecShotDir ); - return vecShotDir; - } -#else - if (random->RandomInt(0, 4) < 3) - { - // Aim at a point a few feet in front of the player's eyes - Vector vecEnemyForward; - GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL ); - - Vector vecAimPos = GetEnemy()->EyePosition() + (120.0f * vecEnemyForward ); - - Vector vecShotDir = vecAimPos - shootOrigin; - VectorNormalize( vecShotDir ); - - CShotManipulator manipulator( vecShotDir ); - manipulator.ApplySpread( VECTOR_CONE_10DEGREES, 1 ); - vecShotDir = manipulator.GetResult(); - - return vecShotDir; - } -#endif - } - - Vector vecProjectedPosition = GetActualShootPosition( shootOrigin ); - - Vector shotDir = vecProjectedPosition - shootOrigin; - VectorNormalize( shotDir ); - - CollectShotStats( shootOrigin, shotDir ); - - // NOW we have a shoot direction. Where a 100% accurate bullet should go. - // Modify it by weapon proficiency. - // construct a manipulator - CShotManipulator manipulator( shotDir ); - - // Apply appropriate accuracy. - bool bUsePerfectAccuracy = false; - if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE ) - { - CNPC_Bullseye *pBullseye = dynamic_cast(GetEnemy()); - if ( pBullseye && pBullseye->UsePerfectAccuracy() ) - { - bUsePerfectAccuracy = true; - } - } - - if ( !bUsePerfectAccuracy ) - { - manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) ); - shotDir = manipulator.GetResult(); - } - - // Look for an opportunity to make misses hit interesting things. - CBaseCombatCharacter *pEnemy; - - pEnemy = GetEnemy()->MyCombatCharacterPointer(); - - if( pEnemy && pEnemy->ShouldShootMissTarget( this ) ) - { - Vector vecEnd = shootOrigin + shotDir * 8192; - trace_t tr; - - AI_TraceLine(shootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); - - if( tr.fraction != 1.0 && tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO ) - { - // Hit something we can harm. Just shoot it. - return manipulator.GetResult(); - } - - // Find something interesting around the enemy to shoot instead of just missing. - CBaseEntity *pMissTarget = pEnemy->FindMissTarget(); - - if( pMissTarget ) - { - shotDir = pMissTarget->WorldSpaceCenter() - shootOrigin; - VectorNormalize( shotDir ); - } - } - - return shotDir; -} -#endif HL2_DLL - -//----------------------------------------------------------------------------- - -Vector CAI_BaseNPC::BodyTarget( const Vector &posSrc, bool bNoisy ) -{ - Vector low = WorldSpaceCenter() - ( WorldSpaceCenter() - GetAbsOrigin() ) * .25; - Vector high = EyePosition(); - Vector delta = high - low; - Vector result; - if ( bNoisy ) - { - // bell curve - float rand1 = random->RandomFloat( 0.0, 0.5 ); - float rand2 = random->RandomFloat( 0.0, 0.5 ); - result = low + delta * rand1 + delta * rand2; - } - else - result = low + delta * 0.5; - - return result; -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::ShouldMoveAndShoot() -{ - return ( ( CapabilitiesGet() & bits_CAP_MOVE_SHOOT ) != 0 ); -} - - -//========================================================= -// FacingIdeal - tells us if a npc 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 CAI_BaseNPC::FacingIdeal( void ) -{ - if ( fabs( GetMotor()->DeltaIdealYaw() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!! - { - return true; - } - - return false; -} - -//--------------------------------- - -void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp ) -{ - GetMotor()->AddFacingTarget( pTarget, flImportance, flDuration, flRamp ); -} - -void CAI_BaseNPC::AddFacingTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) -{ - GetMotor()->AddFacingTarget( vecPosition, flImportance, flDuration, flRamp ); -} - -void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) -{ - GetMotor()->AddFacingTarget( pTarget, vecPosition, flImportance, flDuration, flRamp ); -} - -float CAI_BaseNPC::GetFacingDirection( Vector &vecDir ) -{ - return (GetMotor()->GetFacingDirection( vecDir )); -} - - -//--------------------------------- - - -int CAI_BaseNPC::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener ) -{ - int sentenceIndex = -1; - - if ( pszSentence && IsAlive() ) - { - - if ( pszSentence[0] == '!' ) - { - sentenceIndex = SENTENCEG_Lookup( pszSentence ); - CPASAttenuationFilter filter( this, soundlevel ); - CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, volume, soundlevel, 0, PITCH_NORM ); - } - else - { - sentenceIndex = SENTENCEG_PlayRndSz( edict(), pszSentence, volume, soundlevel, 0, PITCH_NORM ); - } - } - - return sentenceIndex; -} - - -int CAI_BaseNPC::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener ) -{ - return PlaySentence( pszSentence, delay, volume, soundlevel, pListener ); -} - - -void CAI_BaseNPC::SentenceStop( void ) -{ - EmitSound( "AI_BaseNPC.SentenceStop" ); -} - - - - -//----------------------------------------------------------------------------- -// Purpose: Play a one-shot scene -// Input : -// Output : -//----------------------------------------------------------------------------- -float CAI_BaseNPC::PlayScene( const char *pszScene, float flDelay, AI_Response *response ) -{ - return InstancedScriptedScene( this, pszScene, NULL, flDelay, false, response ); -} - -//----------------------------------------------------------------------------- -// Purpose: Generate a one-shot scene in memory with one track which is to play the named sound on the actor -// Input : *soundname - -// Output : float -//----------------------------------------------------------------------------- -float CAI_BaseNPC::PlayAutoGeneratedSoundScene( const char *soundname ) -{ - return InstancedAutoGeneratedSoundScene( this, soundname ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseEntity *CAI_BaseNPC::FindNamedEntity( const char *name, IEntityFindFilter *pFilter ) -{ - if ( !stricmp( name, "!player" )) - { - return ( CBaseEntity * )AI_GetSinglePlayer(); - } - else if ( !stricmp( name, "!enemy" ) ) - { - if (GetEnemy() != NULL) - return GetEnemy(); - } - else if ( !stricmp( name, "!self" ) || !stricmp( name, "!target1" ) ) - { - return this; - } - else if ( !stricmp( name, "!nearestfriend" ) || !stricmp( name, "!friend" ) ) - { - // FIXME: look at CBaseEntity *CNPCSimpleTalker::FindNearestFriend(bool fPlayer) - // punt for now - return ( CBaseEntity * )AI_GetSinglePlayer(); - } - else if (!stricmp( name, "self" )) - { - static int selfwarningcount = 0; - - // fix the vcd, the reserved names have changed - if ( ++selfwarningcount < 5 ) - { - DevMsg( "ERROR: \"self\" is no longer used, use \"!self\" in vcd instead!\n" ); - } - return this; - } - else if ( !stricmp( name, "Player" )) - { - static int playerwarningcount = 0; - if ( ++playerwarningcount < 5 ) - { - DevMsg( "ERROR: \"player\" is no longer used, use \"!player\" in vcd instead!\n" ); - } - return ( CBaseEntity * )AI_GetSinglePlayer(); - } - else - { - // search for up to 32 entities with the same name and choose one randomly - CBaseEntity *entityList[ FINDNAMEDENTITY_MAX_ENTITIES ]; - CBaseEntity *entity; - int iCount; - - entity = NULL; - for( iCount = 0; iCount < FINDNAMEDENTITY_MAX_ENTITIES; iCount++ ) - { - entity = gEntList.FindEntityByName( entity, name, NULL, NULL, NULL, pFilter ); - if ( !entity ) - { - break; - } - entityList[ iCount ] = entity; - } - - if ( iCount > 0 ) - { - int index = RandomInt( 0, iCount - 1 ); - entity = entityList[ index ]; - return entity; - } - } - - return NULL; -} - - -void CAI_BaseNPC::CorpseFallThink( void ) -{ - if ( GetFlags() & FL_ONGROUND ) - { - SetThink ( NULL ); - - SetSequenceBox( ); - } - else - { - SetNextThink( gpGlobals->curtime + 0.1f ); - } -} - -// Call after animation/pose is set up -void CAI_BaseNPC::NPCInitDead( void ) -{ - InitBoneControllers(); - - RemoveSolidFlags( FSOLID_NOT_SOLID ); - - // so he'll fall to ground - SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); - - SetCycle( 0 ); - ResetSequenceInfo( ); - m_flPlaybackRate = 0; - - // Copy health - m_iMaxHealth = m_iHealth; - m_lifeState = LIFE_DEAD; - - UTIL_SetSize(this, vec3_origin, vec3_origin ); - - // Setup health counters, etc. - SetThink( &CAI_BaseNPC::CorpseFallThink ); - - SetNextThink( gpGlobals->curtime + 0.5f ); -} - -//========================================================= -// BBoxIsFlat - check to see if the npc's bounding box -// is lying flat on a surface (traces from all four corners -// are same length.) -//========================================================= -bool CAI_BaseNPC::BBoxFlat ( void ) -{ - trace_t tr; - Vector vecPoint; - float flXSize, flYSize; - float flLength; - float flLength2; - - flXSize = WorldAlignSize().x / 2; - flYSize = WorldAlignSize().y / 2; - - vecPoint.x = GetAbsOrigin().x + flXSize; - vecPoint.y = GetAbsOrigin().y + flYSize; - vecPoint.z = GetAbsOrigin().z; - - AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); - flLength = (vecPoint - tr.endpos).Length(); - - vecPoint.x = GetAbsOrigin().x - flXSize; - vecPoint.y = GetAbsOrigin().y - flYSize; - - AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); - flLength2 = (vecPoint - tr.endpos).Length(); - if ( flLength2 > flLength ) - { - return false; - } - flLength = flLength2; - - vecPoint.x = GetAbsOrigin().x - flXSize; - vecPoint.y = GetAbsOrigin().y + flYSize; - AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); - flLength2 = (vecPoint - tr.endpos).Length(); - if ( flLength2 > flLength ) - { - return false; - } - flLength = flLength2; - - vecPoint.x = GetAbsOrigin().x + flXSize; - vecPoint.y = GetAbsOrigin().y - flYSize; - AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); - flLength2 = (vecPoint - tr.endpos).Length(); - if ( flLength2 > flLength ) - { - return false; - } - flLength = flLength2; - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEnemy - -// bSetCondNewEnemy - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::SetEnemy( CBaseEntity *pEnemy, bool bSetCondNewEnemy ) -{ - if (m_hEnemy != pEnemy) - { - ClearAttackConditions( ); - VacateStrategySlot(); - m_GiveUpOnDeadEnemyTimer.Stop(); - - // If we've just found a new enemy, set the condition - if ( pEnemy && bSetCondNewEnemy ) - { - SetCondition( COND_NEW_ENEMY ); - } - } - - // Assert( (pEnemy == NULL) || (m_NPCState == NPC_STATE_COMBAT) ); - - m_hEnemy = pEnemy; - m_flTimeEnemyAcquired = gpGlobals->curtime; - - m_LastShootAccuracy = -1; - m_TotalShots = 0; - m_TotalHits = 0; - - if ( !pEnemy ) - ClearCondition( COND_NEW_ENEMY ); -} - -const Vector &CAI_BaseNPC::GetEnemyLKP() const -{ - return (const_cast(this))->GetEnemies()->LastKnownPosition( GetEnemy() ); -} - -float CAI_BaseNPC::GetEnemyLastTimeSeen() const -{ - return (const_cast(this))->GetEnemies()->LastTimeSeen( GetEnemy() ); -} - -void CAI_BaseNPC::MarkEnemyAsEluded() -{ - GetEnemies()->MarkAsEluded( GetEnemy() ); -} - -void CAI_BaseNPC::ClearEnemyMemory() -{ - GetEnemies()->ClearMemory( GetEnemy() ); -} - -bool CAI_BaseNPC::EnemyHasEludedMe() const -{ - return (const_cast(this))->GetEnemies()->HasEludedMe( GetEnemy() ); -} - -void CAI_BaseNPC::SetTarget( CBaseEntity *pTarget ) -{ - m_hTargetEnt = pTarget; -} - - -//========================================================= -// Choose Enemy - tries to find the best suitable enemy for the npc. -//========================================================= - -bool CAI_BaseNPC::ShouldChooseNewEnemy() -{ - CBaseEntity *pEnemy = GetEnemy(); - if ( pEnemy ) - { - if ( GetEnemies()->GetSerialNumber() != m_EnemiesSerialNumber ) - { - return true; - } - - m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber(); - - if ( EnemyHasEludedMe() || (IRelationType( pEnemy ) != D_HT && IRelationType( pEnemy ) != D_FR) || !IsValidEnemy( pEnemy ) ) - { - DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (1)\n" ); - return true; - } - if ( HasCondition(COND_SEE_HATE) || HasCondition(COND_SEE_DISLIKE) || HasCondition(COND_SEE_NEMESIS) || HasCondition(COND_SEE_FEAR) ) - { - DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (2)\n" ); - return true; - } - if ( !pEnemy->IsAlive() ) - { - if ( m_GiveUpOnDeadEnemyTimer.IsRunning() ) - { - if ( m_GiveUpOnDeadEnemyTimer.Expired() ) - { - DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (3)\n" ); - return true; - } - } - else - m_GiveUpOnDeadEnemyTimer.Start(); - } - - AI_EnemyInfo_t *pInfo = GetEnemies()->Find( pEnemy ); - - if ( m_FailChooseEnemyTimer.Expired() ) - { - m_FailChooseEnemyTimer.Set( 1.5 ); - if ( HasCondition( COND_TASK_FAILED ) || - ( pInfo && ( pInfo->timeAtFirstHand == AI_INVALID_TIME || gpGlobals->curtime - pInfo->timeLastSeen > 10 ) ) ) - { - return true; - } - } - - if ( pInfo && pInfo->timeValidEnemy < gpGlobals->curtime ) - { - DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> false\n" ); - return false; - } - } - - DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (4)\n" ); - m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber(); - - return true; -} - -//------------------------------------- - -bool CAI_BaseNPC::ChooseEnemy( void ) -{ - AI_PROFILE_SCOPE(CAI_Enemies_ChooseEnemy); - - DbgEnemyMsg( this, "ChooseEnemy() {\n" ); - - //--------------------------------- - // - // Gather initial conditions - // - - CBaseEntity *pInitialEnemy = GetEnemy(); - CBaseEntity *pChosenEnemy = pInitialEnemy; - - // Use memory bits in case enemy pointer altered outside this function, (e.g., ehandle goes NULL) - bool fHadEnemy = ( HasMemory( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER ) ); - bool fEnemyWasPlayer = HasMemory( bits_MEMORY_HAD_PLAYER ); - bool fEnemyWentNull = ( fHadEnemy && !pInitialEnemy ); - bool fEnemyEluded = ( fEnemyWentNull || ( pInitialEnemy && GetEnemies()->HasEludedMe( pInitialEnemy ) ) ); - - //--------------------------------- - // - // Establish suitability of choosing a new enemy - // - - bool fHaveCondNewEnemy; - bool fHaveCondLostEnemy; - - if ( GetCurSchedule() && !FScheduleDone() ) - { - Assert( InterruptFromCondition( COND_NEW_ENEMY ) == COND_NEW_ENEMY && InterruptFromCondition( COND_LOST_ENEMY ) == COND_LOST_ENEMY ); - fHaveCondNewEnemy = GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY ); - fHaveCondLostEnemy = GetCurSchedule()->HasInterrupt( COND_LOST_ENEMY ); - - // See if they've been added as a custom interrupt - if ( !fHaveCondNewEnemy ) - { - fHaveCondNewEnemy = IsCustomInterruptConditionSet( COND_NEW_ENEMY ); - } - if ( !fHaveCondLostEnemy ) - { - fHaveCondLostEnemy = IsCustomInterruptConditionSet( COND_LOST_ENEMY ); - } - } - else - { - fHaveCondNewEnemy = true; // not having a schedule is the same as being interruptable by any condition - fHaveCondLostEnemy = true; - } - - if ( !fEnemyWentNull ) - { - if ( !fHaveCondNewEnemy && !( fHaveCondLostEnemy && fEnemyEluded ) ) - { - // DO NOT mess with the npc's enemy pointer unless the schedule the npc is currently - // running will be interrupted by COND_NEW_ENEMY or COND_LOST_ENEMY. This will - // eliminate the problem of npcs 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. - m_bSkippedChooseEnemy = true; - - DbgEnemyMsg( this, "Skipped enemy selection due to schedule restriction\n" ); - DbgEnemyMsg( this, "}\n" ); - return ( pChosenEnemy != NULL ); - } - } - else if ( !fHaveCondNewEnemy && !fHaveCondLostEnemy && GetCurSchedule() ) - { - DevMsg( 2, "WARNING: AI enemy went NULL but schedule (%s) is not interested\n", GetCurSchedule()->GetName() ); - } - - m_bSkippedChooseEnemy = false; - - //--------------------------------- - // - // Select a target - // - - if ( ShouldChooseNewEnemy() ) - { - pChosenEnemy = BestEnemy(); - } - - //--------------------------------- - // - // React to result of selection - // - - bool fChangingEnemy = ( pChosenEnemy != pInitialEnemy ); - - if ( fChangingEnemy || fEnemyWentNull ) - { - DbgEnemyMsg( this, "Enemy changed from %s to %s\n", pInitialEnemy->GetDebugName(), pChosenEnemy->GetDebugName() ); - Forget( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER ); - - // Did our old enemy snuff it? - if ( pInitialEnemy && !pInitialEnemy->IsAlive() ) - { - SetCondition( COND_ENEMY_DEAD ); - } - - SetEnemy( pChosenEnemy ); - - if ( fHadEnemy ) - { - // Vacate any strategy slot on old enemy - VacateStrategySlot(); - - // Force output event for establishing LOS - Forget( bits_MEMORY_HAD_LOS ); - // m_flLastAttackTime = 0; - } - - if ( !pChosenEnemy ) - { - // Don't break on enemies going null if they've been killed - if ( !HasCondition(COND_ENEMY_DEAD) ) - { - SetCondition( COND_ENEMY_WENT_NULL ); - } - - if ( fEnemyEluded ) - { - SetCondition( COND_LOST_ENEMY ); - LostEnemySound(); - } - - if ( fEnemyWasPlayer ) - { - m_OnLostPlayer.FireOutput( pInitialEnemy, this ); - } - m_OnLostEnemy.FireOutput( pInitialEnemy, this); - } - else - { - Remember( ( pChosenEnemy->IsPlayer() ) ? bits_MEMORY_HAD_PLAYER : bits_MEMORY_HAD_ENEMY ); - } - } - - //--------------------------------- - - return ( pChosenEnemy != NULL ); -} - - -//========================================================= -void CAI_BaseNPC::PickupWeapon( CBaseCombatWeapon *pWeapon ) -{ - pWeapon->OnPickedUp( this ); - Weapon_Equip( pWeapon ); - m_iszPendingWeapon = NULL_STRING; -} - -//========================================================= -// DropItem - dead npc drops named item -//========================================================= -CBaseEntity *CAI_BaseNPC::DropItem ( char *pszItemName, Vector vecPos, QAngle vecAng ) -{ - if ( !pszItemName ) - { - DevMsg( "DropItem() - No item name!\n" ); - return NULL; - } - - CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, this ); - - if ( pItem ) - { - if ( g_pGameRules->IsAllowedToSpawn( pItem ) == false ) - { - UTIL_Remove( pItem ); - return NULL; - } - - IPhysicsObject *pPhys = pItem->VPhysicsGetObject(); - - if ( pPhys ) - { - // Add an extra push in a random direction - Vector vel = RandomVector( -64.0f, 64.0f ); - AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f ); - - vel[2] = 0.0f; - pPhys->AddVelocity( &vel, &angImp ); - } - else - { - // do we want this behavior to be default?! (sjb) - pItem->ApplyAbsVelocityImpulse( GetAbsVelocity() ); - pItem->ApplyLocalAngularVelocityImpulse( AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 ) ); - } - - return pItem; - } - else - { - DevMsg( "DropItem() - Didn't create!\n" ); - return NULL; - } - -} - -bool CAI_BaseNPC::ShouldFadeOnDeath( void ) -{ - if ( g_RagdollLVManager.IsLowViolence() ) - { - return true; - } - else - { - // if flagged to fade out - return HasSpawnFlags(SF_NPC_FADE_CORPSE); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Indicates whether or not this npc should play an idle sound now. -// -// -// Output : Returns true if yes, false if no. -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::ShouldPlayIdleSound( void ) -{ - if ( ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT ) && - random->RandomInt(0,99) == 0 && !HasSpawnFlags(SF_NPC_GAG) ) - { - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Make a sound that other AI's can hear, to broadcast our presence -// Input : volume (radius) of the sound. -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::MakeAIFootstepSound( float volume, float duration ) -{ - CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), volume, duration, this, SOUNDENT_CHANNEL_NPC_FOOTSTEP ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::FOkToMakeSound( int soundPriority ) -{ - // ask the squad to filter sounds if I'm in one - if ( m_pSquad ) - { - if ( !m_pSquad->FOkToMakeSound( soundPriority ) ) - return false; - } - else - { - // otherwise, check my own sound timer - // Am I making uninterruptable sound? - if (gpGlobals->curtime <= m_flSoundWaitTime) - { - if ( soundPriority <= m_nSoundPriority ) - return false; - } - } - - // no talking outside of combat if gagged. - if ( HasSpawnFlags(SF_NPC_GAG) && ( m_NPCState != NPC_STATE_COMBAT ) ) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -void CAI_BaseNPC::JustMadeSound( int soundPriority, float flSoundLength ) -{ - m_flSoundWaitTime = gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0); - m_nSoundPriority = soundPriority; - - if (m_pSquad) - { - m_pSquad->JustMadeSound( soundPriority, gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0) ); - } -} - -Activity CAI_BaseNPC::GetStoppedActivity( void ) -{ - if (GetNavigator()->IsGoalActive()) - { - Activity activity = GetNavigator()->GetArrivalActivity(); - - if (activity > ACT_RESET) - { - return activity; - } - } - - return ACT_IDLE; -} - - -//========================================================= -//========================================================= -void CAI_BaseNPC::OnScheduleChange ( void ) -{ - EndTaskOverlay(); - - m_pNavigator->OnScheduleChange(); - - m_flMoveWaitFinished = 0; - - VacateStrategySlot(); - - // If I still have have a route, clear it - // FIXME: Routes should only be cleared inside of tasks (kenb) - GetNavigator()->ClearGoal(); - - UnlockBestSound(); - - // If I locked a hint node clear it - if ( HasMemory(bits_MEMORY_LOCKED_HINT) && GetHintNode() != NULL) - { - float hintDelay = GetHintDelay(GetHintNode()->HintType()); - GetHintNode()->Unlock(hintDelay); - SetHintNode( NULL ); - } -} - - - -CBaseCombatCharacter* CAI_BaseNPC::GetEnemyCombatCharacterPointer() -{ - if ( GetEnemy() == NULL ) - return NULL; - - return GetEnemy()->MyCombatCharacterPointer(); -} - - -// Global Savedata for npc -// -// This should be an exact copy of the var's in the header. Fields -// that aren't save/restored are commented out - -BEGIN_DATADESC( CAI_BaseNPC ) - - // m_pSchedule (reacquired on restore) - DEFINE_EMBEDDED( m_ScheduleState ), - DEFINE_FIELD( m_IdealSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules - DEFINE_FIELD( m_failSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules - DEFINE_FIELD( m_bUsingStandardThinkTime, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flLastRealThinkTime, FIELD_TIME ), - // m_iFrameBlocked (not saved) - // m_bInChoreo (not saved) - // m_bDoPostRestoreRefindPath (not saved) - // gm_flTimeLastSpawn (static) - // gm_nSpawnedThisFrame (static) - // m_Conditions (custom save) - // m_CustomInterruptConditions (custom save) - // m_ConditionsPreIgnore (custom save) - // m_InverseIgnoreConditions (custom save) - DEFINE_FIELD( m_bForceConditionsGather, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bConditionsGathered, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bSkippedChooseEnemy, FIELD_BOOLEAN ), - DEFINE_FIELD( m_NPCState, FIELD_INTEGER ), - DEFINE_FIELD( m_IdealNPCState, FIELD_INTEGER ), - DEFINE_FIELD( m_flLastStateChangeTime, FIELD_TIME ), - DEFINE_FIELD( m_Efficiency, FIELD_INTEGER ), - DEFINE_FIELD( m_MoveEfficiency, FIELD_INTEGER ), - DEFINE_FIELD( m_flNextDecisionTime, FIELD_TIME ), - DEFINE_KEYFIELD( m_SleepState, FIELD_INTEGER, "sleepstate" ), - DEFINE_FIELD( m_SleepFlags, FIELD_INTEGER ), - DEFINE_KEYFIELD( m_flWakeRadius, FIELD_FLOAT, "wakeradius" ), - DEFINE_KEYFIELD( m_bWakeSquad, FIELD_BOOLEAN, "wakesquad" ), - DEFINE_FIELD( m_nWakeTick, FIELD_TICK ), - - DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ), - DEFINE_CUSTOM_FIELD( m_translatedActivity, ActivityDataOps() ), - DEFINE_CUSTOM_FIELD( m_IdealActivity, ActivityDataOps() ), - DEFINE_CUSTOM_FIELD( m_IdealTranslatedActivity, ActivityDataOps() ), - DEFINE_CUSTOM_FIELD( m_IdealWeaponActivity, ActivityDataOps() ), - - DEFINE_FIELD( m_nIdealSequence, FIELD_INTEGER ), - DEFINE_EMBEDDEDBYREF( m_pSenses ), - DEFINE_EMBEDDEDBYREF( m_pLockedBestSound ), - DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ), - DEFINE_FIELD( m_flTimeEnemyAcquired, FIELD_TIME ), - DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ), - DEFINE_EMBEDDED( m_GiveUpOnDeadEnemyTimer ), - DEFINE_EMBEDDED( m_FailChooseEnemyTimer ), - DEFINE_FIELD( m_EnemiesSerialNumber, FIELD_INTEGER ), - DEFINE_FIELD( m_flAcceptableTimeSeenEnemy, FIELD_TIME ), - DEFINE_EMBEDDED( m_UpdateEnemyPosTimer ), - // m_flTimeAnyUpdateEnemyPos (static) - DEFINE_FIELD( m_vecCommandGoal, FIELD_VECTOR ), - DEFINE_EMBEDDED( m_CommandMoveMonitor ), - DEFINE_FIELD( m_flSoundWaitTime, FIELD_TIME ), - DEFINE_FIELD( m_nSoundPriority, FIELD_INTEGER ), - DEFINE_FIELD( m_flIgnoreDangerSoundsUntil, FIELD_TIME ), - DEFINE_FIELD( m_afCapability, FIELD_INTEGER ), - DEFINE_FIELD( m_flMoveWaitFinished, FIELD_TIME ), - DEFINE_FIELD( m_hOpeningDoor, FIELD_EHANDLE ), - DEFINE_EMBEDDEDBYREF( m_pNavigator ), - DEFINE_EMBEDDEDBYREF( m_pLocalNavigator ), - DEFINE_EMBEDDEDBYREF( m_pPathfinder ), - DEFINE_EMBEDDEDBYREF( m_pMoveProbe ), - DEFINE_EMBEDDEDBYREF( m_pMotor ), - DEFINE_UTLVECTOR(m_UnreachableEnts, FIELD_EMBEDDED), - DEFINE_FIELD( m_hInteractionPartner, FIELD_EHANDLE ), - DEFINE_FIELD( m_hLastInteractionTestTarget, FIELD_EHANDLE ), - DEFINE_FIELD( m_hForcedInteractionPartner, FIELD_EHANDLE ), - DEFINE_FIELD( m_vecForcedWorldPosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_bCannotDieDuringInteraction, FIELD_BOOLEAN ), - DEFINE_FIELD( m_iInteractionState, FIELD_INTEGER ), - DEFINE_FIELD( m_iInteractionPlaying, FIELD_INTEGER ), - DEFINE_UTLVECTOR(m_ScriptedInteractions,FIELD_EMBEDDED), - DEFINE_FIELD( m_flInteractionYaw, FIELD_FLOAT ), - DEFINE_EMBEDDED( m_CheckOnGroundTimer ), - DEFINE_FIELD( m_vDefaultEyeOffset, FIELD_VECTOR ), - DEFINE_FIELD( m_flNextEyeLookTime, FIELD_TIME ), - DEFINE_FIELD( m_flEyeIntegRate, FIELD_FLOAT ), - DEFINE_FIELD( m_vEyeLookTarget, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vCurEyeTarget, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_hEyeLookTarget, FIELD_EHANDLE ), - DEFINE_FIELD( m_flHeadYaw, FIELD_FLOAT ), - DEFINE_FIELD( m_flHeadPitch, FIELD_FLOAT ), - DEFINE_FIELD( m_flOriginalYaw, FIELD_FLOAT ), - DEFINE_FIELD( m_bInAScript, FIELD_BOOLEAN ), - DEFINE_FIELD( m_scriptState, FIELD_INTEGER ), - DEFINE_FIELD( m_hCine, FIELD_EHANDLE ), - DEFINE_CUSTOM_FIELD( m_ScriptArrivalActivity, ActivityDataOps() ), - DEFINE_FIELD( m_strScriptArrivalSequence, FIELD_STRING ), - DEFINE_FIELD( m_flSceneTime, FIELD_TIME ), - DEFINE_FIELD( m_iszSceneCustomMoveSeq, FIELD_STRING ), - // m_pEnemies Saved specially in ai_saverestore.cpp - DEFINE_FIELD( m_afMemory, FIELD_INTEGER ), - DEFINE_FIELD( m_hEnemyOccluder, FIELD_EHANDLE ), - DEFINE_FIELD( m_flSumDamage, FIELD_FLOAT ), - DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ), - DEFINE_FIELD( m_flLastPlayerDamageTime, FIELD_TIME ), - DEFINE_FIELD( m_flLastSawPlayerTime, FIELD_TIME ), - DEFINE_FIELD( m_flLastAttackTime, FIELD_TIME ), - DEFINE_FIELD( m_flLastEnemyTime, FIELD_TIME ), - DEFINE_FIELD( m_flNextWeaponSearchTime, FIELD_TIME ), - DEFINE_FIELD( m_iszPendingWeapon, FIELD_STRING ), - DEFINE_EMBEDDED( m_ShotRegulator ), - DEFINE_FIELD( m_iDesiredWeaponState, FIELD_INTEGER ), - // m_pSquad Saved specially in ai_saverestore.cpp - DEFINE_KEYFIELD(m_SquadName, FIELD_STRING, "squadname" ), - DEFINE_FIELD( m_iMySquadSlot, FIELD_INTEGER ), - DEFINE_KEYFIELD( m_strHintGroup, FIELD_STRING, "hintgroup" ), - DEFINE_KEYFIELD( m_bHintGroupNavLimiting, FIELD_BOOLEAN, "hintlimiting" ), - DEFINE_EMBEDDEDBYREF( m_pTacticalServices ), - DEFINE_FIELD( m_flWaitFinished, FIELD_TIME ), - DEFINE_FIELD( m_flNextFlinchTime, FIELD_TIME ), - DEFINE_FIELD( m_flNextDodgeTime, FIELD_TIME ), - DEFINE_EMBEDDED( m_MoveAndShootOverlay ), - DEFINE_FIELD( m_vecLastPosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vSavePosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vInterruptSavePosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_pHintNode, FIELD_EHANDLE), - DEFINE_FIELD( m_cAmmoLoaded, FIELD_INTEGER ), - DEFINE_FIELD( m_flDistTooFar, FIELD_FLOAT ), - DEFINE_FIELD( m_hGoalEnt, FIELD_EHANDLE ), - DEFINE_FIELD( m_flTimeLastMovement, FIELD_TIME ), - DEFINE_KEYFIELD(m_spawnEquipment, FIELD_STRING, "additionalequipment" ), - DEFINE_FIELD( m_fNoDamageDecal, FIELD_BOOLEAN ), - DEFINE_FIELD( m_hStoredPathTarget, FIELD_EHANDLE ), - DEFINE_FIELD( m_vecStoredPathGoal, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_nStoredPathType, FIELD_INTEGER ), - DEFINE_FIELD( m_fStoredPathFlags, FIELD_INTEGER ), - DEFINE_FIELD( m_bDidDeathCleanup, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bCrouchDesired, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bForceCrouch, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bIsCrouching, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bPerformAvoidance, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bIsMoving, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bFadeCorpse, FIELD_BOOLEAN ), - DEFINE_FIELD( m_iDeathPose, FIELD_INTEGER ), - DEFINE_FIELD( m_iDeathFrame, FIELD_INTEGER ), - DEFINE_FIELD( m_bCheckContacts, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bSpeedModActive, FIELD_BOOLEAN ), - DEFINE_FIELD( m_iSpeedModRadius, FIELD_INTEGER ), - DEFINE_FIELD( m_iSpeedModSpeed, FIELD_INTEGER ), - DEFINE_FIELD( m_hEnemyFilter, FIELD_EHANDLE ), - DEFINE_KEYFIELD( m_iszEnemyFilterName, FIELD_STRING, "enemyfilter" ), - DEFINE_FIELD( m_bImportanRagdoll, FIELD_BOOLEAN ), - - // m_fIsUsingSmallHull TODO -- This needs more consideration than simple save/load - // m_failText DEBUG - // m_interruptText DEBUG - // m_failedSchedule DEBUG - // m_interuptSchedule DEBUG - // m_nDebugCurIndex DEBUG - - // m_LastShootAccuracy DEBUG - // m_RecentShotAccuracy DEBUG - // m_TotalShots DEBUG - // m_TotalHits DEBUG - // m_bSelected DEBUG - // m_TimeLastShotMark DEBUG - - - // Outputs - DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ), - DEFINE_OUTPUT( m_OnDeath, "OnDeath" ), - DEFINE_OUTPUT( m_OnHalfHealth, "OnHalfHealth" ), - DEFINE_OUTPUT( m_OnFoundEnemy, "OnFoundEnemy" ), - DEFINE_OUTPUT( m_OnLostEnemyLOS, "OnLostEnemyLOS" ), - DEFINE_OUTPUT( m_OnLostEnemy, "OnLostEnemy" ), - DEFINE_OUTPUT( m_OnFoundPlayer, "OnFoundPlayer" ), - DEFINE_OUTPUT( m_OnLostPlayerLOS, "OnLostPlayerLOS" ), - DEFINE_OUTPUT( m_OnLostPlayer, "OnLostPlayer" ), - DEFINE_OUTPUT( m_OnHearWorld, "OnHearWorld" ), - DEFINE_OUTPUT( m_OnHearPlayer, "OnHearPlayer" ), - DEFINE_OUTPUT( m_OnHearCombat, "OnHearCombat" ), - DEFINE_OUTPUT( m_OnDamagedByPlayer, "OnDamagedByPlayer" ), - DEFINE_OUTPUT( m_OnDamagedByPlayerSquad, "OnDamagedByPlayerSquad" ), - DEFINE_OUTPUT( m_OnDenyCommanderUse, "OnDenyCommanderUse" ), - DEFINE_OUTPUT( m_OnRappelTouchdown, "OnRappelTouchdown" ), - DEFINE_OUTPUT( m_OnWake, "OnWake" ), - DEFINE_OUTPUT( m_OnSleep, "OnSleep" ), - DEFINE_OUTPUT( m_OnForcedInteractionAborted, "OnForcedInteractionAborted" ), - DEFINE_OUTPUT( m_OnForcedInteractionFinished, "OnForcedInteractionFinished" ), - - // Inputs - DEFINE_INPUTFUNC( FIELD_STRING, "SetRelationship", InputSetRelationship ), - DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), - DEFINE_INPUTFUNC( FIELD_VOID, "BeginRappel", InputBeginRappel ), - DEFINE_INPUTFUNC( FIELD_STRING, "SetSquad", InputSetSquad ), - DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ), - DEFINE_INPUTFUNC( FIELD_STRING, "ForgetEntity", InputForgetEntity ), - DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreDangerSounds", InputIgnoreDangerSounds ), - DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ), - DEFINE_INPUTFUNC( FIELD_VOID, "StartScripting", InputStartScripting ), - DEFINE_INPUTFUNC( FIELD_VOID, "StopScripting", InputStopScripting ), - DEFINE_INPUTFUNC( FIELD_VOID, "GagEnable", InputGagEnable ), - DEFINE_INPUTFUNC( FIELD_VOID, "GagDisable", InputGagDisable ), - DEFINE_INPUTFUNC( FIELD_VOID, "InsideTransition", InputInsideTransition ), - DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ), - DEFINE_INPUTFUNC( FIELD_VOID, "ActivateSpeedModifier", InputActivateSpeedModifier ), - DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpeedModifier", InputDisableSpeedModifier ), - DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModRadius", InputSetSpeedModifierRadius ), - DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModSpeed", InputSetSpeedModifierSpeed ), - DEFINE_INPUTFUNC( FIELD_VOID, "HolsterWeapon", InputHolsterWeapon ), - DEFINE_INPUTFUNC( FIELD_VOID, "HolsterAndDestroyWeapon", InputHolsterAndDestroyWeapon ), - DEFINE_INPUTFUNC( FIELD_VOID, "UnholsterWeapon", InputUnholsterWeapon ), - DEFINE_INPUTFUNC( FIELD_STRING, "ForceInteractionWithNPC", InputForceInteractionWithNPC ), - DEFINE_INPUTFUNC( FIELD_STRING, "UpdateEnemyMemory", InputUpdateEnemyMemory ), - - // Function pointers - DEFINE_USEFUNC( NPCUse ), - DEFINE_THINKFUNC( CallNPCThink ), - DEFINE_THINKFUNC( CorpseFallThink ), - DEFINE_THINKFUNC( NPCInitThink ), - -END_DATADESC() - -BEGIN_SIMPLE_DATADESC( AIScheduleState_t ) - DEFINE_FIELD( iCurTask, FIELD_INTEGER ), - DEFINE_FIELD( fTaskStatus, FIELD_INTEGER ), - DEFINE_FIELD( timeStarted, FIELD_TIME ), - DEFINE_FIELD( timeCurTaskStarted, FIELD_TIME ), - DEFINE_FIELD( taskFailureCode, FIELD_INTEGER ), - DEFINE_FIELD( iTaskInterrupt, FIELD_INTEGER ), - DEFINE_FIELD( bTaskRanAutomovement, FIELD_BOOLEAN ), - DEFINE_FIELD( bTaskUpdatedYaw, FIELD_BOOLEAN ), -END_DATADESC() - - -IMPLEMENT_SERVERCLASS_ST( CAI_BaseNPC, DT_AI_BaseNPC ) - SendPropInt( SENDINFO( m_lifeState ), 3, SPROP_UNSIGNED ), - SendPropBool( SENDINFO( m_bPerformAvoidance ) ), - SendPropBool( SENDINFO( m_bIsMoving ) ), - SendPropBool( SENDINFO( m_bFadeCorpse ) ), - SendPropInt( SENDINFO( m_iDeathPose ), ANIMATION_SEQUENCE_BITS ), - SendPropInt( SENDINFO( m_iDeathFrame ), 5 ), - SendPropBool( SENDINFO( m_bSpeedModActive ) ), - SendPropInt( SENDINFO( m_iSpeedModRadius ) ), - SendPropInt( SENDINFO( m_iSpeedModSpeed ) ), - SendPropBool( SENDINFO( m_bImportanRagdoll ) ), -END_SEND_TABLE() - -//------------------------------------- - -BEGIN_SIMPLE_DATADESC( UnreachableEnt_t ) - - DEFINE_FIELD( hUnreachableEnt, FIELD_EHANDLE ), - DEFINE_FIELD( fExpireTime, FIELD_TIME ), - DEFINE_FIELD( vLocationWhenUnreachable, FIELD_POSITION_VECTOR ), - -END_DATADESC() - -//------------------------------------- - -BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_Phases_t ) -DEFINE_FIELD( iszSequence, FIELD_STRING ), -DEFINE_FIELD( iActivity, FIELD_INTEGER ), -END_DATADESC() - -//------------------------------------- - -BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t ) - DEFINE_FIELD( iszInteractionName, FIELD_STRING ), - DEFINE_FIELD( iFlags, FIELD_INTEGER ), - DEFINE_FIELD( iTriggerMethod, FIELD_INTEGER ), - DEFINE_FIELD( iLoopBreakTriggerMethod, FIELD_INTEGER ), - DEFINE_FIELD( vecRelativeOrigin, FIELD_VECTOR ), - DEFINE_FIELD( angRelativeAngles, FIELD_VECTOR ), - DEFINE_FIELD( vecRelativeVelocity, FIELD_VECTOR ), - DEFINE_FIELD( flDelay, FIELD_FLOAT ), - DEFINE_FIELD( flDistSqr, FIELD_FLOAT ), - DEFINE_FIELD( iszMyWeapon, FIELD_STRING ), - DEFINE_FIELD( iszTheirWeapon, FIELD_STRING ), - DEFINE_EMBEDDED_ARRAY( sPhases, SNPCINT_NUM_PHASES ), - DEFINE_FIELD( matDesiredLocalToWorld, FIELD_VMATRIX ), - DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ), - DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ), -END_DATADESC() - -//------------------------------------- - -void CAI_BaseNPC::PostConstructor( const char *szClassname ) -{ - BaseClass::PostConstructor( szClassname ); - CreateComponents(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::Activate( void ) -{ - BaseClass::Activate(); - - if ( GetModelPtr() ) - { - ParseScriptedNPCInteractions(); - } - - // Get a handle to my enemy filter entity if there is one. - if ( m_iszEnemyFilterName != NULL_STRING ) - { - CBaseEntity *pFilter = gEntList.FindEntityByName( NULL, m_iszEnemyFilterName ); - if ( pFilter != NULL ) - { - m_hEnemyFilter = dynamic_cast(pFilter); - } - } -} - -void CAI_BaseNPC::Precache( void ) -{ - gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls - - if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0") ) - { - UTIL_PrecacheOther( STRING(m_spawnEquipment) ); - } - - // Make sure schedules are loaded for this NPC type - if (!LoadedSchedules()) - { - DevMsg("ERROR: Rejecting spawn of %s as error in NPC's schedules.\n",GetDebugName()); - UTIL_Remove(this); - return; - } - - PrecacheScriptSound( "AI_BaseNPC.SwishSound" ); - PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Heavy" ); - PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Light" ); - PrecacheScriptSound( "AI_BaseNPC.SentenceStop" ); - - BaseClass::Precache(); -} - - -//----------------------------------------------------------------------------- - -const short AI_EXTENDED_SAVE_HEADER_VERSION = 5; -const short AI_EXTENDED_SAVE_HEADER_RESET_VERSION = 3; - -const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS = 2; -const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP = 3; -const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE = 4; -const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE = 5; - -struct AIExtendedSaveHeader_t -{ - AIExtendedSaveHeader_t() - : version(AI_EXTENDED_SAVE_HEADER_VERSION), - flags(0), - scheduleCrc(0) - { - szSchedule[0] = 0; - szIdealSchedule[0] = 0; - szFailSchedule[0] = 0; - szSequence[0] = 0; - } - - short version; - unsigned flags; - char szSchedule[128]; - CRC32_t scheduleCrc; - char szIdealSchedule[128]; - char szFailSchedule[128]; - char szSequence[128]; - - DECLARE_SIMPLE_DATADESC(); -}; - -enum AIExtendedSaveHeaderFlags_t -{ - AIESH_HAD_ENEMY = 0x01, - AIESH_HAD_TARGET = 0x02, - AIESH_HAD_NAVGOAL = 0x04, -}; - -//------------------------------------- - -BEGIN_SIMPLE_DATADESC( AIExtendedSaveHeader_t ) - DEFINE_FIELD( version, FIELD_SHORT ), - DEFINE_FIELD( flags, FIELD_INTEGER ), - DEFINE_AUTO_ARRAY( szSchedule, FIELD_CHARACTER ), - DEFINE_FIELD( scheduleCrc, FIELD_INTEGER ), - DEFINE_AUTO_ARRAY( szIdealSchedule, FIELD_CHARACTER ), - DEFINE_AUTO_ARRAY( szFailSchedule, FIELD_CHARACTER ), - DEFINE_AUTO_ARRAY( szSequence, FIELD_CHARACTER ), -END_DATADESC() - -//------------------------------------- - -int CAI_BaseNPC::Save( ISave &save ) -{ - AIExtendedSaveHeader_t saveHeader; - - if ( GetEnemy() ) - saveHeader.flags |= AIESH_HAD_ENEMY; - if ( GetTarget() ) - saveHeader.flags |= AIESH_HAD_TARGET; - if ( GetNavigator()->IsGoalActive() ) - saveHeader.flags |= AIESH_HAD_NAVGOAL; - - if ( m_pSchedule ) - { - const char *pszSchedule = m_pSchedule->GetName(); - - Assert( Q_strlen( pszSchedule ) < sizeof( saveHeader.szSchedule ) - 1 ); - Q_strncpy( saveHeader.szSchedule, pszSchedule, sizeof( saveHeader.szSchedule ) ); - - CRC32_Init( &saveHeader.scheduleCrc ); - CRC32_ProcessBuffer( &saveHeader.scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) ); - CRC32_Final( &saveHeader.scheduleCrc ); - } - else - { - saveHeader.szSchedule[0] = 0; - saveHeader.scheduleCrc = 0; - } - - int idealSchedule = GetGlobalScheduleId( m_IdealSchedule ); - - if ( idealSchedule != -1 && idealSchedule != AI_RemapToGlobal( SCHED_NONE ) && idealSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) ) - { - CAI_Schedule *pIdealSchedule = GetSchedule( m_IdealSchedule ); - if ( pIdealSchedule ) - { - const char *pszIdealSchedule = pIdealSchedule->GetName(); - Assert( Q_strlen( pszIdealSchedule ) < sizeof( saveHeader.szIdealSchedule ) - 1 ); - Q_strncpy( saveHeader.szIdealSchedule, pszIdealSchedule, sizeof( saveHeader.szIdealSchedule ) ); - } - } - - int failSchedule = GetGlobalScheduleId( m_failSchedule ); - if ( failSchedule != -1 && failSchedule != AI_RemapToGlobal( SCHED_NONE ) && failSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) ) - { - CAI_Schedule *pFailSchedule = GetSchedule( m_failSchedule ); - if ( pFailSchedule ) - { - const char *pszFailSchedule = pFailSchedule->GetName(); - Assert( Q_strlen( pszFailSchedule ) < sizeof( saveHeader.szFailSchedule ) - 1 ); - Q_strncpy( saveHeader.szFailSchedule, pszFailSchedule, sizeof( saveHeader.szFailSchedule ) ); - } - } - - if ( GetSequence() != ACT_INVALID && GetModelPtr() ) - { - const char *pszSequenceName = GetSequenceName( GetSequence() ); - if ( pszSequenceName && *pszSequenceName ) - { - Assert( Q_strlen( pszSequenceName ) < sizeof( saveHeader.szSequence ) - 1 ); - Q_strncpy( saveHeader.szSequence, pszSequenceName, sizeof(saveHeader.szSequence) ); - } - } - - save.WriteAll( &saveHeader ); - - save.StartBlock(); - SaveConditions( save, m_Conditions ); - SaveConditions( save, m_CustomInterruptConditions ); - SaveConditions( save, m_ConditionsPreIgnore ); - CAI_ScheduleBits ignoreConditions; - m_InverseIgnoreConditions.Not( &ignoreConditions ); - SaveConditions( save, ignoreConditions ); - save.EndBlock(); - - save.StartBlock(); - GetNavigator()->Save( save ); - save.EndBlock(); - - return BaseClass::Save(save); -} - -//------------------------------------- - -void CAI_BaseNPC::DiscardScheduleState() -{ - // We don't save/restore routes yet - GetNavigator()->ClearGoal(); - - // We don't save/restore schedules yet - ClearSchedule(); - - // Reset animation - m_Activity = ACT_RESET; - - // If we don't have an enemy, clear conditions like see enemy, etc. - if ( GetEnemy() == NULL ) - { - m_Conditions.ClearAllBits(); - } - - // went across a transition and lost my m_hCine - bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL ); - if ( bLostScript ) - { - // UNDONE: Do something better here? - // for now, just go back to idle and let the AI figure out what to do. - SetState( NPC_STATE_IDLE ); - SetIdealState( NPC_STATE_IDLE ); - DevMsg(1, "Scripted Sequence stripped on level transition for %s\n", GetDebugName() ); - } -} - -//------------------------------------- - -void CAI_BaseNPC::OnRestore() -{ - gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls - - if ( m_bDoPostRestoreRefindPath && CAI_NetworkManager::NetworksLoaded() ) - { - CAI_DynamicLink::InitDynamicLinks(); - if ( !GetNavigator()->RefindPathToGoal( false ) ) - DiscardScheduleState(); - } - else - { - GetNavigator()->ClearGoal(); - } - BaseClass::OnRestore(); - m_bCheckContacts = true; -} - - -//------------------------------------- - -int CAI_BaseNPC::Restore( IRestore &restore ) -{ - AIExtendedSaveHeader_t saveHeader; - restore.ReadAll( &saveHeader ); - - if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP ) - { - if ( saveHeader.szIdealSchedule[0] ) - { - CAI_Schedule *pIdealSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szIdealSchedule ); - m_IdealSchedule = ( pIdealSchedule ) ? pIdealSchedule->GetId() : SCHED_NONE; - } - - if ( saveHeader.szFailSchedule[0] ) - { - CAI_Schedule *pFailSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szFailSchedule ); - m_failSchedule = ( pFailSchedule ) ? pFailSchedule->GetId() : SCHED_NONE; - } - } - - if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS ) - { - restore.StartBlock(); - RestoreConditions( restore, &m_Conditions ); - RestoreConditions( restore, &m_CustomInterruptConditions ); - RestoreConditions( restore, &m_ConditionsPreIgnore ); - CAI_ScheduleBits ignoreConditions; - RestoreConditions( restore, &ignoreConditions ); - ignoreConditions.Not( &m_InverseIgnoreConditions ); - restore.EndBlock(); - } - - if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE ) - { - restore.StartBlock(); - GetNavigator()->Restore( restore ); - restore.EndBlock(); - } - - // do a normal restore - int status = BaseClass::Restore(restore); - if ( !status ) - return 0; - - bool bLostSequence = false; - if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE && saveHeader.szSequence[0] && GetModelPtr() ) - { - SetSequence( LookupSequence( saveHeader.szSequence ) ); - if ( GetSequence() == ACT_INVALID ) - { - DevMsg( this, AIMF_IGNORE_SELECTED, "Discarding missing sequence %s on load.\n", saveHeader.szSequence ); - SetSequence( 0 ); - bLostSequence = true; - } - - Assert( IsValidSequence( GetSequence() ) ); - } - - bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL ); - bool bDiscardScheduleState = ( bLostScript || - bLostSequence || - saveHeader.szSchedule[0] == 0 || - saveHeader.version < AI_EXTENDED_SAVE_HEADER_RESET_VERSION || - ( (saveHeader.flags & AIESH_HAD_ENEMY) && !GetEnemy() ) || - ( (saveHeader.flags & AIESH_HAD_TARGET) && !GetTarget() ) ); - - if ( m_ScheduleState.taskFailureCode >= NUM_FAIL_CODES ) - m_ScheduleState.taskFailureCode = FAIL_NO_TARGET; // must have been a string, gotta punt - - if ( !bDiscardScheduleState ) - { - m_pSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szSchedule ); - if ( m_pSchedule ) - { - CRC32_t scheduleCrc; - CRC32_Init( &scheduleCrc ); - CRC32_ProcessBuffer( &scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) ); - CRC32_Final( &scheduleCrc ); - - if ( scheduleCrc != saveHeader.scheduleCrc ) - { - m_pSchedule = NULL; - } - } - } - - if ( !m_pSchedule ) - bDiscardScheduleState = true; - - if ( !bDiscardScheduleState ) - m_bDoPostRestoreRefindPath = ( ( saveHeader.flags & AIESH_HAD_NAVGOAL) != 0 ); - else - { - m_bDoPostRestoreRefindPath = false; - DiscardScheduleState(); - } - - return status; -} - -//------------------------------------- - -void CAI_BaseNPC::SaveConditions( ISave &save, const CAI_ScheduleBits &conditions ) -{ - for (int i = 0; i < MAX_CONDITIONS; i++) - { - if (conditions.GetBit(i)) - { - const char *pszConditionName = ConditionName(AI_RemapToGlobal(i)); - if ( !pszConditionName ) - break; - save.WriteString( pszConditionName ); - } - } - save.WriteString( "" ); -} - -//------------------------------------- - -void CAI_BaseNPC::RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions ) -{ - pConditions->ClearAllBits(); - char szCondition[256]; - for (;;) - { - restore.ReadString( szCondition, sizeof(szCondition), 0 ); - if ( !szCondition[0] ) - break; - int iCondition = GetSchedulingSymbols()->ConditionSymbolToId( szCondition ); - if ( iCondition != -1 ) - pConditions->SetBit( AI_RemapFromGlobal( iCondition ) ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::KeyValue( const char *szKeyName, const char *szValue ) -{ - bool bResult = BaseClass::KeyValue( szKeyName, szValue ); - - if( !bResult ) - { - // Defer unhandled Keys to behaviors - CAI_BehaviorBase **ppBehaviors = AccessBehaviors(); - - for ( int i = 0; i < NumBehaviors(); i++ ) - { - if( ppBehaviors[ i ]->KeyValue( szKeyName, szValue ) ) - { - return true; - } - } - } - - return bResult; -} - -//----------------------------------------------------------------------------- -// Purpose: Debug function to make this NPC freeze in place (or unfreeze). -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ToggleFreeze(void) -{ - if (!IsCurSchedule(SCHED_NPC_FREEZE)) - { - // Freeze them. - SetCondition(COND_NPC_FREEZE); - SetMoveType(MOVETYPE_NONE); - SetGravity(0); - SetLocalAngularVelocity(vec3_angle); - SetAbsVelocity( vec3_origin ); - } - else - { - // Unfreeze them. - SetCondition(COND_NPC_UNFREEZE); - m_Activity = ACT_RESET; - - // BUGBUG: this might not be the correct movetype! - SetMoveType( MOVETYPE_STEP ); - - // Doesn't restore gravity to the original value, but who cares? - SetGravity(1); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Written by subclasses macro to load schedules -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::LoadSchedules(void) -{ - return true; -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::LoadedSchedules(void) -{ - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Constructor -// Input : -// Output : -//----------------------------------------------------------------------------- -CAI_BaseNPC::CAI_BaseNPC(void) - : m_UnreachableEnts( 0, 4 ) -{ - m_pMotor = NULL; - m_pMoveProbe = NULL; - m_pNavigator = NULL; - m_pSenses = NULL; - m_pPathfinder = NULL; - m_pLocalNavigator = NULL; - - m_pSchedule = NULL; - m_IdealSchedule = SCHED_NONE; - -#ifdef _DEBUG - // necessary since in debug, we initialize vectors to NAN for debugging - m_vecLastPosition.Init(); - m_vSavePosition.Init(); - m_vEyeLookTarget.Init(); - m_vCurEyeTarget.Init(); - m_vDefaultEyeOffset.Init(); - -#endif - m_bDidDeathCleanup = false; - - m_afCapability = 0; // Make sure this is cleared in the base class - - SetHullType(HULL_HUMAN); // Give human hull by default, subclasses should override - - m_iMySquadSlot = SQUAD_SLOT_NONE; - m_flSumDamage = 0; - m_flLastDamageTime = 0; - m_flLastAttackTime = 0; - m_flSoundWaitTime = 0; - m_flNextEyeLookTime = 0; - m_flHeadYaw = 0; - m_flHeadPitch = 0; - m_spawnEquipment = NULL_STRING; - m_pEnemies = new CAI_Enemies; - m_flEyeIntegRate = 0.95; - SetTarget( NULL ); - - m_pSquad = NULL; - - m_flMoveWaitFinished = 0; - - m_fIsUsingSmallHull = true; - - m_bHintGroupNavLimiting = false; - - m_fNoDamageDecal = false; - - SetInAScript( false ); - - m_pLockedBestSound = new CSound; - m_pLockedBestSound->m_iType = SOUND_NONE; - - // ---------------------------- - // Debugging fields - // ---------------------------- - m_interruptText = NULL; - m_failText = NULL; - m_failedSchedule = NULL; - m_interuptSchedule = NULL; - m_nDebugPauseIndex = 0; - - g_AI_Manager.AddAI( this ); - - if ( g_AI_Manager.NumAIs() == 1 ) - { - m_AnyUpdateEnemyPosTimer.Force(); - gm_flTimeLastSpawn = -1; - gm_nSpawnedThisFrame = 0; - gm_iNextThinkRebalanceTick = 0; - } - - m_iFrameBlocked = -1; - m_bInChoreo = true; // assume so until call to UpdateEfficiency() - - SetCollisionGroup( COLLISION_GROUP_NPC ); -} - -//----------------------------------------------------------------------------- -// Purpose: Destructor -// Input : -// Output : -//----------------------------------------------------------------------------- -CAI_BaseNPC::~CAI_BaseNPC(void) -{ - g_AI_Manager.RemoveAI( this ); - - delete m_pLockedBestSound; - - RemoveMemory(); - - delete m_pPathfinder; - delete m_pNavigator; - delete m_pMotor; - delete m_pLocalNavigator; - delete m_pMoveProbe; - delete m_pSenses; - delete m_pTacticalServices; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::UpdateOnRemove(void) -{ - if ( !m_bDidDeathCleanup ) - { - if ( m_NPCState == NPC_STATE_DEAD ) - DevMsg( "May not have cleaned up on NPC death\n"); - - CleanupOnDeath( NULL, false ); - } - - // Chain at end to mimic destructor unwind order - BaseClass::UpdateOnRemove(); -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::CreateComponents() -{ - m_pSenses = CreateSenses(); - if ( !m_pSenses ) - return false; - - m_pMotor = CreateMotor(); - if ( !m_pMotor ) - return false; - - m_pLocalNavigator = CreateLocalNavigator(); - if ( !m_pLocalNavigator ) - return false; - - m_pMoveProbe = CreateMoveProbe(); - if ( !m_pMoveProbe ) - return false; - - m_pNavigator = CreateNavigator(); - if ( !m_pNavigator ) - return false; - - m_pPathfinder = CreatePathfinder(); - if ( !m_pPathfinder ) - return false; - - m_pTacticalServices = CreateTacticalServices(); - if ( !m_pTacticalServices ) - return false; - - m_MoveAndShootOverlay.SetOuter( this ); - - m_pMotor->Init( m_pLocalNavigator ); - m_pLocalNavigator->Init( m_pNavigator ); - m_pNavigator->Init( g_pBigAINet ); - m_pPathfinder->Init( g_pBigAINet ); - m_pTacticalServices->Init( g_pBigAINet ); - - return true; -} - -//----------------------------------------------------------------------------- - -CAI_Senses *CAI_BaseNPC::CreateSenses() -{ - CAI_Senses *pSenses = new CAI_Senses; - pSenses->SetOuter( this ); - return pSenses; -} - -//----------------------------------------------------------------------------- - -CAI_Motor *CAI_BaseNPC::CreateMotor() -{ - return new CAI_Motor( this ); -} - -//----------------------------------------------------------------------------- - -CAI_MoveProbe *CAI_BaseNPC::CreateMoveProbe() -{ - return new CAI_MoveProbe( this ); -} - -//----------------------------------------------------------------------------- - -CAI_LocalNavigator *CAI_BaseNPC::CreateLocalNavigator() -{ - return new CAI_LocalNavigator( this ); -} - -//----------------------------------------------------------------------------- - -CAI_TacticalServices *CAI_BaseNPC::CreateTacticalServices() -{ - return new CAI_TacticalServices( this ); -} - -//----------------------------------------------------------------------------- - -CAI_Navigator *CAI_BaseNPC::CreateNavigator() -{ - return new CAI_Navigator( this ); -} - -//----------------------------------------------------------------------------- - -CAI_Pathfinder *CAI_BaseNPC::CreatePathfinder() -{ - return new CAI_Pathfinder( this ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputSetRelationship( inputdata_t &inputdata ) -{ - AddRelationship( inputdata.value.String(), inputdata.pActivator ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputSetHealth( inputdata_t &inputdata ) -{ - int iNewHealth = inputdata.value.Int(); - int iDelta = abs(GetHealth() - iNewHealth); - if ( iNewHealth > GetHealth() ) - { - TakeHealth( iDelta, DMG_GENERIC ); - } - else if ( iNewHealth < GetHealth() ) - { - TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputBeginRappel( inputdata_t &inputdata ) -{ - BeginRappel(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputSetSquad( inputdata_t &inputdata ) -{ - if ( !( CapabilitiesGet() & bits_CAP_SQUAD ) ) - { - Warning("SetSquad Input received for NPC %s, but that NPC can't use squads.\n", GetDebugName() ); - return; - } - - m_SquadName = inputdata.value.StringID(); - - // Removing from squad? - if ( m_SquadName == NULL_STRING ) - { - if ( m_pSquad ) - { - m_pSquad->RemoveFromSquad(this, true); - m_pSquad = NULL; - } - } - else - { - m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputWake( inputdata_t &inputdata ) -{ - Wake(); - - // Check if we have a path to follow. This is normally done in StartNPC, - // but putting the NPC to sleep will cancel it, so we have to do it again. - if ( m_target != NULL_STRING )// this npc has a target - { - // Find the npc's initial target entity, stash it - SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) ); - - if ( !GetGoalEnt() ) - { - Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target)); - } - else - { - StartTargetHandling( GetGoalEnt() ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputForgetEntity( inputdata_t &inputdata ) -{ - const char *pszEntityToForget = inputdata.value.String(); - - if ( g_pDeveloper->GetInt() && pszEntityToForget[strlen( pszEntityToForget ) - 1] == '*' ) - DevMsg( "InputForgetEntity does not support wildcards\n" ); - - CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, pszEntityToForget ); - if ( pEntity ) - { - if ( GetEnemy() == pEntity ) - { - SetEnemy( NULL ); - SetIdealState( NPC_STATE_ALERT ); - } - GetEnemies()->ClearMemory( pEntity ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputIgnoreDangerSounds( inputdata_t &inputdata ) -{ - // Default is 10 seconds. - float flDelay = 10.0f; - - if( inputdata.value.Float() > 0.0f ) - { - flDelay = inputdata.value.Float(); - } - - m_flIgnoreDangerSoundsUntil = gpGlobals->curtime + flDelay; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputUpdateEnemyMemory( inputdata_t &inputdata ) -{ - const char *pszEnemy = inputdata.value.String(); - CBaseEntity *pEnemy = gEntList.FindEntityByName( NULL, pszEnemy ); - - if( pEnemy ) - { - UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), this ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputOutsideTransition( inputdata_t &inputdata ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: Called when this NPC transitions to another level with the player -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputInsideTransition( inputdata_t &inputdata ) -{ - CleanupScriptsOnTeleport( true ); - - // If we're inside a vcd, tell it to stop - if ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) ) - { - RemoveActorFromScriptedScenes( this, false ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CleanupScriptsOnTeleport( bool bEnrouteAsWell ) -{ - // If I'm running a scripted sequence, I need to clean up - if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine ) - { - if ( !bEnrouteAsWell ) - { - // Don't cancel scripts when they're teleporting an NPC - // to the script due to CINE_MOVETO_TELEPORT. - if ( m_hCine->IsTeleportingDueToMoveTo() ) - return; - } - - m_hCine->ScriptEntityCancel( m_hCine, true ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) -{ -#ifdef HL2_DLL - if ( interactionType == g_interactionBarnacleVictimGrab ) - { - // Make the victim stop thinking so they're as good as dead without - // shocking the system by destroying the entity. - StopLoopingSounds(); - BarnacleDeathSound(); - SetThink( NULL ); - - // Gag the NPC so they won't talk anymore - AddSpawnFlags( SF_NPC_GAG ); - - // Drop any weapon they're holding - if ( GetActiveWeapon() ) - { - Weapon_Drop( GetActiveWeapon() ); - } - - return true; - } -#endif // HL2_DLL - - return BaseClass::HandleInteraction( interactionType, data, sourceEnt ); -} - -CAI_BaseNPC *CAI_BaseNPC::GetInteractionPartner( void ) -{ - if ( m_hInteractionPartner == NULL ) - return NULL; - - return m_hInteractionPartner->MyNPCPointer(); -} - -//----------------------------------------------------------------------------- -// Purpose: Called when exiting a scripted sequence. -// Output : Returns true if alive, false if dead. -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::ExitScriptedSequence( ) -{ - if ( m_lifeState == LIFE_DYING ) - { - // is this legal? - // BUGBUG -- This doesn't call Killed() - SetIdealState( NPC_STATE_DEAD ); - return false; - } - - if (m_hCine) - { - m_hCine->CancelScript( ); - } - - return true; -} - - -bool CAI_BaseNPC::CineCleanup() -{ - CAI_ScriptedSequence *pOldCine = m_hCine; - - bool bDestroyCine = false; - if ( IsRunningDynamicInteraction() ) - { - bDestroyCine = true; - - // Re-enable physics collisions between me & the other NPC - if ( m_hInteractionPartner ) - { - PhysEnableEntityCollisions( this, m_hInteractionPartner ); - //Msg("%s(%s) enabled collisions with %s(%s) at %0.2f\n", GetClassName(), GetDebugName(), m_hInteractionPartner->GetClassName(), m_hInteractionPartner->GetDebugName(), gpGlobals->curtime ); - } - - if ( m_hForcedInteractionPartner ) - { - // We've finished a forced interaction. Let the mapmaker know. - m_OnForcedInteractionFinished.FireOutput( this, this ); - } - - // Clear interaction partner, because we're not running a scripted sequence anymore - m_hInteractionPartner = NULL; - CleanupForcedInteraction(); - } - - // am I linked to a cinematic? - if (m_hCine) - { - // okay, reset me to what it thought I was before - m_hCine->SetTarget( NULL ); - // NOTE that this will have had EF_NODRAW removed in script.dll when it's cached off - SetEffects( m_hCine->m_saved_effects ); - } - else - { - // arg, punt - AddSolidFlags( FSOLID_NOT_STANDABLE ); - } - m_hCine = NULL; - SetTarget( NULL ); - SetGoalEnt( NULL ); - if (m_lifeState == LIFE_DYING) - { - // last frame of death animation? - m_iHealth = 0; - AddSolidFlags( FSOLID_NOT_SOLID ); - SetState( NPC_STATE_DEAD ); - m_lifeState = LIFE_DEAD; - UTIL_SetSize( this, WorldAlignMins(), Vector(WorldAlignMaxs().x, WorldAlignMaxs().y, WorldAlignMins().z + 2) ); - - if ( pOldCine && pOldCine->HasSpawnFlags( SF_SCRIPT_LEAVECORPSE ) ) - { - SetUse( NULL ); // BUGBUG -- This doesn't call Killed() - SetThink( NULL ); // This will probably break some stuff - SetTouch( NULL ); - } - else - SUB_StartFadeOut(); // SetThink( SUB_DoNothing ); - - - //Not becoming a ragdoll, so set the NOINTERP flag on. - if ( CanBecomeRagdoll() == false ) - { - StopAnimation(); - AddEffects( EF_NOINTERP ); // Don't interpolate either, assume the corpse is positioned in its final resting place - } - - SetMoveType( MOVETYPE_NONE ); - return false; - } - - // If we actually played a sequence - if ( pOldCine && pOldCine->m_iszPlay != NULL_STRING && pOldCine->PlayedSequence() ) - { - if ( !pOldCine->HasSpawnFlags(SF_SCRIPT_NOSCRIPTMOVEMENT) ) - { - // reset position - Vector new_origin; - QAngle new_angle; - GetBonePosition( 0, new_origin, new_angle ); - - // Figure out how far they have moved - // We can't really solve this problem because we can't query the movement of the origin relative - // to the sequence. We can get the root bone's position as we do here, but there are - // cases where the root bone is in a different relative position to the entity's origin - // before/after the sequence plays. So we are stuck doing this: - - // !!!HACKHACK: Float the origin up and drop to floor because some sequences have - // irregular motion that can't be properly accounted for. - - // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. - Vector oldOrigin = GetLocalOrigin(); - - // UNDONE: ugly hack. Don't move NPC if they don't "seem" to move - // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly - // being set, so animations that really do move won't be caught. - if ((oldOrigin - new_origin).Length2D() < 8.0) - new_origin = oldOrigin; - - Vector origin = GetLocalOrigin(); - - origin.x = new_origin.x; - origin.y = new_origin.y; - origin.z += 1; - - if ( GetFlags() & FL_FLY ) - { - origin.z = new_origin.z; - SetLocalOrigin( origin ); - } - else - { - SetLocalOrigin( origin ); - - int drop = UTIL_DropToFloor( this, MASK_NPCSOLID ); - - // Origin in solid? Set to org at the end of the sequence - if ( drop < 0 ) - { - SetLocalOrigin( oldOrigin ); - } - else if ( drop == 0 ) // Hanging in air? - { - Vector origin = GetLocalOrigin(); - origin.z = new_origin.z; - SetLocalOrigin( origin ); - SetGroundEntity( NULL ); - } - } - - origin = GetLocalOrigin(); - - // teleport if it's a non-trivial distance - if ((oldOrigin - origin).Length() > 8.0) - { - // Call teleport to notify - Teleport( &origin, NULL, NULL ); - SetLocalOrigin( origin ); - AddEffects( EF_NOINTERP ); - } - - if ( m_iHealth <= 0 ) - { - // Dropping out because he got killed - SetIdealState( NPC_STATE_DEAD ); - SetCondition( COND_LIGHT_DAMAGE ); - m_lifeState = LIFE_DYING; - } - } - - // We should have some animation to put these guys in, but for now it's idle. - // Due to NOINTERP above, there won't be any blending between this anim & the sequence - m_Activity = ACT_RESET; - } - - // set them back into a normal state - if ( m_iHealth > 0 ) - { - SetIdealState( NPC_STATE_IDLE ); - } - else - { - // Dropping out because he got killed - SetIdealState( NPC_STATE_DEAD ); - SetCondition( COND_LIGHT_DAMAGE ); - } - - // SetAnimation( m_NPCState ); - CLEARBITS(m_spawnflags, SF_NPC_WAIT_FOR_SCRIPT ); - - if ( bDestroyCine ) - { - UTIL_Remove( pOldCine ); - } - - return true; -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ) -{ - CleanupScriptsOnTeleport( false ); - - BaseClass::Teleport( newPosition, newAngles, newVelocity ); -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::FindSpotForNPCInRadius( Vector *pResult, const Vector &vStartPos, CAI_BaseNPC *pNPC, float radius, bool bOutOfPlayerViewcone ) -{ - CBasePlayer *pPlayer = AI_GetSinglePlayer(); - QAngle fan; - - fan.x = 0; - fan.z = 0; - - for( fan.y = 0 ; fan.y < 360 ; fan.y += 18.0 ) - { - Vector vecTest; - Vector vecDir; - - AngleVectors( fan, &vecDir ); - - vecTest = vStartPos + vecDir * radius; - - if ( bOutOfPlayerViewcone && pPlayer && !pPlayer->FInViewCone( vecTest ) ) - continue; - - trace_t tr; - - UTIL_TraceLine( vecTest, vecTest - Vector( 0, 0, 8192 ), MASK_SHOT, pNPC, COLLISION_GROUP_NONE, &tr ); - if( tr.fraction == 1.0 ) - { - continue; - } - - UTIL_TraceHull( tr.endpos, - tr.endpos + Vector( 0, 0, 10 ), - pNPC->GetHullMins(), - pNPC->GetHullMaxs(), - MASK_NPCSOLID, - pNPC, - COLLISION_GROUP_NONE, - &tr ); - - if( tr.fraction == 1.0 && pNPC->GetMoveProbe()->CheckStandPosition( tr.endpos, MASK_NPCSOLID ) ) - { - *pResult = tr.endpos; - return true; - } - } - return false; -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::IsNavigationUrgent() -{ - // return true if the navigation is for something that can't react well to failure - if ( IsCurSchedule( SCHED_SCRIPTED_WALK, false ) || - IsCurSchedule( SCHED_SCRIPTED_RUN, false ) || - IsCurSchedule( SCHED_SCRIPTED_CUSTOM_MOVE, false ) || - ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) && IsInLockedScene() ) ) - { - return true; - } - return false; -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::ShouldFailNav( bool bMovementFailed ) -{ - // Robin: Only fail movement. Fail route building so that we'll keep trying to build routes. - // Otherwise, we can can get stuck inside a movement task without a route. - if ( bMovementFailed && IsNavigationUrgent()) - { - return false; - } - return true; -} - -Navigation_t CAI_BaseNPC::GetNavType() const -{ - return m_pNavigator->GetNavType(); -} - -void CAI_BaseNPC::SetNavType( Navigation_t navType ) -{ - m_pNavigator->SetNavType( navType ); -} - -//----------------------------------------------------------------------------- -// NPCs can override this to tweak with how costly particular movements are -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost ) -{ - // We have nothing to say on the matter, but derived classes might - return false; -} - -bool CAI_BaseNPC::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval ) -{ - return false; -} - -bool CAI_BaseNPC::OverrideMove( float flInterval ) -{ - return false; -} - - -//========================================================= -// VecToYaw - turns a directional vector into a yaw value -// that points down that vector. -//========================================================= -float CAI_BaseNPC::VecToYaw( const Vector &vecDir ) -{ - if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0) - return GetLocalAngles().y; - - return UTIL_VecToYaw( vecDir ); -} - -//----------------------------------------------------------------------------- -// Inherited from IAI_MotorMovementServices -//----------------------------------------------------------------------------- -float CAI_BaseNPC::CalcYawSpeed( void ) -{ - // Negative values are invalud - return -1.0f; -} - -bool CAI_BaseNPC::OnCalcBaseMove( AILocalMoveGoal_t *pMoveGoal, - float distClear, - AIMoveResult_t *pResult ) -{ - if ( pMoveGoal->directTrace.pObstruction ) - { - CBasePropDoor *pPropDoor = dynamic_cast( pMoveGoal->directTrace.pObstruction ); - if ( pPropDoor && OnUpcomingPropDoor( pMoveGoal, pPropDoor, distClear, pResult ) ) - { - return true; - } - } - - return false; -} - - -bool CAI_BaseNPC::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal, - float distClear, - AIMoveResult_t *pResult ) -{ - if ( pMoveGoal->directTrace.pObstruction ) - { - CBaseDoor *pDoor = dynamic_cast( pMoveGoal->directTrace.pObstruction ); - if ( pDoor && OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) ) - { - return true; - } - } - - return false; -} - - -bool CAI_BaseNPC::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, - CBaseDoor *pDoor, - float distClear, - AIMoveResult_t *pResult ) -{ - if ( pMoveGoal->maxDist < distClear ) - return false; - - // By default, NPCs don't know how to open doors - if ( pDoor->m_toggle_state == TS_AT_BOTTOM || pDoor->m_toggle_state == TS_GOING_DOWN ) - { - if ( distClear < 0.1 ) - { - *pResult = AIMR_BLOCKED_ENTITY; - } - else - { - pMoveGoal->maxDist = distClear; - *pResult = AIMR_OK; - } - - return true; - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pMoveGoal - -// pDoor - -// distClear - -// default - -// spawn - -// oldorg - -// pfPosition - -// neworg - -// Output : Returns true if movement is solved, false otherwise. -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal, - CBasePropDoor *pDoor, - float distClear, - AIMoveResult_t *pResult ) -{ - if ( (pMoveGoal->flags & AILMG_TARGET_IS_GOAL) && pMoveGoal->maxDist < distClear ) - return false; - - if ( pMoveGoal->maxDist + GetHullWidth() * .25 < distClear ) - return false; - - if (pDoor == m_hOpeningDoor) - { - if ( pDoor->IsNPCOpening( this ) ) - { - // We're in the process of opening the door, don't be blocked by it. - pMoveGoal->maxDist = distClear; - *pResult = AIMR_OK; - return true; - } - m_hOpeningDoor = NULL; - } - - if ((CapabilitiesGet() & bits_CAP_DOORS_GROUP) && !pDoor->IsDoorLocked() && (pDoor->IsDoorClosed() || pDoor->IsDoorClosing())) - { - AI_Waypoint_t *pOpenDoorRoute = NULL; - - opendata_t opendata; - pDoor->GetNPCOpenData(this, opendata); - - // dvs: FIXME: local route might not be sufficient - pOpenDoorRoute = GetPathfinder()->BuildLocalRoute( - GetLocalOrigin(), - opendata.vecStandPos, - NULL, - bits_WP_TO_DOOR | bits_WP_DONT_SIMPLIFY, - NO_NODE, - bits_BUILD_GROUND | bits_BUILD_IGNORE_NPCS, - 0.0); - - if ( pOpenDoorRoute ) - { - if ( AIIsDebuggingDoors(this) ) - { - NDebugOverlay::Cross3D(opendata.vecStandPos + Vector(0,0,1), 32, 255, 255, 255, false, 1.0 ); - Msg( "Opening door!\n" ); - } - - // Attach the door to the waypoint so we open it when we get there. - // dvs: FIXME: this is kind of bullshit, I need to find the exact waypoint to open the door - // should I just walk the path until I find it? - pOpenDoorRoute->m_hData = pDoor; - - GetNavigator()->GetPath()->PrependWaypoints( pOpenDoorRoute ); - - m_hOpeningDoor = pDoor; - pMoveGoal->maxDist = distClear; - *pResult = AIMR_CHANGE_TYPE; - - return true; - } - else - AIDoorDebugMsg( this, "Failed create door route!\n" ); - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Called by the navigator to initiate the opening of a prop_door -// that is in our way. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor ) -{ - // dvs: not quite working, disabled for now. - //opendata_t opendata; - //pDoor->GetNPCOpenData(this, opendata); - // - //if (HaveSequenceForActivity(opendata.eActivity)) - //{ - // SetIdealActivity(opendata.eActivity); - //} - //else - { - // We don't have an appropriate sequence, just open the door magically. - OpenPropDoorNow( pDoor ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Called when we are trying to open a prop_door and it's time to start -// the door moving. This is called either in response to an anim event -// or as a fallback when we don't have an appropriate open activity. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OpenPropDoorNow( CBasePropDoor *pDoor ) -{ - // Start the door moving. - pDoor->NPCOpenDoor(this); - - // Wait for the door to finish opening before trying to move through the doorway. - m_flMoveWaitFinished = gpGlobals->curtime + pDoor->GetOpenInterval(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Called when the door we were trying to open becomes fully open. -// Input : pDoor - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OnDoorFullyOpen(CBasePropDoor *pDoor) -{ - // We're done with the door. - m_hOpeningDoor = NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: Called when the door we were trying to open becomes blocked before opening. -// Input : pDoor - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::OnDoorBlocked(CBasePropDoor *pDoor) -{ - // dvs: FIXME: do something so that we don't loop forever trying to open this door - // not clearing out the door handle will cause the NPC to invalidate the connection - // We're done with the door. - //m_hOpeningDoor = NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: Template NPCs are marked as templates by the level designer. They -// do not spawn, but their keyvalues are saved for use by a template -// spawner. -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsTemplate( void ) -{ - return HasSpawnFlags( SF_NPC_TEMPLATE ); -} - - - -//----------------------------------------------------------------------------- -// -// Movement code for walking + flying -// -//----------------------------------------------------------------------------- -int CAI_BaseNPC::FlyMove( const Vector& pfPosition, unsigned int mask ) -{ - Vector oldorg, neworg; - trace_t trace; - - // try the move - VectorCopy( GetAbsOrigin(), oldorg ); - VectorAdd( oldorg, pfPosition, neworg ); - UTIL_TraceEntity( this, oldorg, neworg, mask, &trace ); - if (trace.fraction == 1) - { - if ( (GetFlags() & FL_SWIM) && enginetrace->GetPointContents(trace.endpos) == CONTENTS_EMPTY ) - return false; // swim monster left water - - SetAbsOrigin( trace.endpos ); - PhysicsTouchTriggers(); - return true; - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : ent - -// Dir - Normalized direction vector for movement. -// dist - Distance along 'Dir' to move. -// iMode - -// Output : Returns nonzero on success, zero on failure. -//----------------------------------------------------------------------------- -int CAI_BaseNPC::WalkMove( const Vector& vecPosition, unsigned int mask ) -{ - if ( GetFlags() & (FL_FLY | FL_SWIM) ) - { - return FlyMove( vecPosition, mask ); - } - - if ( (GetFlags() & FL_ONGROUND) == 0 ) - { - return 0; - } - - trace_t trace; - Vector oldorg, neworg, end; - Vector move( vecPosition[0], vecPosition[1], 0.0f ); - VectorCopy( GetAbsOrigin(), oldorg ); - VectorAdd( oldorg, move, neworg ); - - // push down from a step height above the wished position - float flStepSize = sv_stepsize.GetFloat(); - neworg[2] += flStepSize; - VectorCopy(neworg, end); - end[2] -= flStepSize*2; - - UTIL_TraceEntity( this, neworg, end, mask, &trace ); - if ( trace.allsolid ) - return false; - - if (trace.startsolid) - { - neworg[2] -= flStepSize; - UTIL_TraceEntity( this, neworg, end, mask, &trace ); - if ( trace.allsolid || trace.startsolid ) - return false; - } - - if (trace.fraction == 1) - { - // if monster had the ground pulled out, go ahead and fall - if ( GetFlags() & FL_PARTIALGROUND ) - { - SetAbsOrigin( oldorg + move ); - PhysicsTouchTriggers(); - SetGroundEntity( NULL ); - return true; - } - - return false; // walked off an edge - } - - // check point traces down for dangling corners - SetAbsOrigin( trace.endpos ); - - if (UTIL_CheckBottom( this, NULL, flStepSize ) == 0) - { - if ( GetFlags() & FL_PARTIALGROUND ) - { - // entity had floor mostly pulled out from underneath it - // and is trying to correct - PhysicsTouchTriggers(); - return true; - } - - // Reset to original position - SetAbsOrigin( oldorg ); - return false; - } - - if ( GetFlags() & FL_PARTIALGROUND ) - { - // Con_Printf ("back on ground\n"); - RemoveFlag( FL_PARTIALGROUND ); - } - - // the move is ok - SetGroundEntity( trace.m_pEnt ); - PhysicsTouchTriggers(); - return true; -} - -//----------------------------------------------------------------------------- - -static void AIMsgGuts( CAI_BaseNPC *pAI, unsigned flags, const char *pszMsg ) -{ - int len = strlen( pszMsg ); - const char *pszFmt2 = NULL; - - if ( len && pszMsg[len-1] == '\n' ) - { - (const_cast(pszMsg))[len-1] = 0; - pszFmt2 = "%s (%s: %d/%s) [%d]\n"; - } - else - pszFmt2 = "%s (%s: %d/%s) [%d]"; - - DevMsg( pszFmt2, - pszMsg, - pAI->GetClassname(), - pAI->entindex(), - ( pAI->GetEntityName() == NULL_STRING ) ? "" : STRING(pAI->GetEntityName()), - gpGlobals->tickcount ); -} - -void DevMsg( CAI_BaseNPC *pAI, unsigned flags, const char *pszFormat, ... ) -{ - if ( (flags & AIMF_IGNORE_SELECTED) || (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) ) - { - AIMsgGuts( pAI, flags, CFmtStr( &pszFormat ) ); - } -} - -//----------------------------------------------------------------------------- - -void DevMsg( CAI_BaseNPC *pAI, const char *pszFormat, ... ) -{ - if ( (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) ) - { - AIMsgGuts( pAI, 0, CFmtStr( &pszFormat ) ); - } -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::IsPlayerAlly( CBasePlayer *pPlayer ) -{ - if ( pPlayer == NULL ) - { - // in multiplayer mode we need a valid pPlayer - // or override this virtual function - if ( !AI_IsSinglePlayer() ) - return false; - - // NULL means single player mode - pPlayer = UTIL_GetLocalPlayer(); - } - - return ( !pPlayer || IRelationType( pPlayer ) == D_LI ); -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::SetCommandGoal( const Vector &vecGoal ) -{ - m_vecCommandGoal = vecGoal; - m_CommandMoveMonitor.ClearMark(); -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::ClearCommandGoal() -{ - m_vecCommandGoal = vec3_invalid; - m_CommandMoveMonitor.ClearMark(); -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::IsInPlayerSquad() const -{ - return ( m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() && !CAI_Squad::IsSilentMember(this) ); -} - - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::CanBeUsedAsAFriend( void ) -{ - if ( IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN) ) - return false; - return true; -} - -//----------------------------------------------------------------------------- - -Vector CAI_BaseNPC::GetSmoothedVelocity( void ) -{ - if( GetNavType() == NAV_GROUND || GetNavType() == NAV_FLY ) - { - return m_pMotor->GetCurVel(); - } - - return BaseClass::GetSmoothedVelocity(); -} - - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition ) -{ - trace_t tr; - - // By default, we ignore the viewer (me) when determining cover positions - CTraceFilterLOS filter( NULL, COLLISION_GROUP_NONE, this ); - - // If I'm trying to find cover from the player, and the player is in a vehicle, - // ignore the vehicle for the purpose of determining line of sight. - CBaseEntity *pEnemy = GetEnemy(); - if ( pEnemy ) - { - // Hack to see if our threat position is our enemy - bool bThreatPosIsEnemy = ( (vecThreat - GetEnemy()->EyePosition()).LengthSqr() < 0.1f ); - if ( bThreatPosIsEnemy ) - { - CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer(); - if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() ) - { - // Ignore the vehicle - filter.SetPassEntity( pCCEnemy->GetVehicleEntity() ); - } - - if ( !filter.GetPassEntity() ) - { - filter.SetPassEntity( pEnemy ); - } - } - } - - AI_TraceLOS( vecThreat, vecPosition, this, &tr, &filter ); - - if( tr.fraction != 1.0 && hl2_episodic.GetBool() ) - { - if( tr.m_pEnt->m_iClassname == m_iClassname ) - { - // Don't hide behind buddies! - return false; - } - } - - return (tr.fraction != 1.0); -} - -//----------------------------------------------------------------------------- - -float CAI_BaseNPC::SetWait( float minWait, float maxWait ) -{ - int minThinks = Ceil2Int( minWait * 10 ); - - if ( maxWait == 0.0 ) - { - m_flWaitFinished = gpGlobals->curtime + ( 0.1 * minThinks ); - } - else - { - if ( minThinks == 0 ) // random 0..n is almost certain to not return 0 - minThinks = 1; - int maxThinks = Ceil2Int( maxWait * 10 ); - - m_flWaitFinished = gpGlobals->curtime + ( 0.1 * random->RandomInt( minThinks, maxThinks ) ); - } - return m_flWaitFinished; -} - -//----------------------------------------------------------------------------- - -void CAI_BaseNPC::ClearWait() -{ - m_flWaitFinished = FLT_MAX; -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::IsWaitFinished() -{ - return ( gpGlobals->curtime >= m_flWaitFinished ); -} - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::IsWaitSet() -{ - return ( m_flWaitFinished != FLT_MAX ); -} - -void CAI_BaseNPC::TestPlayerPushing( CBaseEntity *pEntity ) -{ - if ( HasSpawnFlags( SF_NPC_NO_PLAYER_PUSHAWAY ) ) - return; - - // Heuristic for determining if the player is pushing me away - CBasePlayer *pPlayer = ToBasePlayer( pEntity ); - if ( pPlayer && !( pPlayer->GetFlags() & FL_NOTARGET ) ) - { - if ( (pPlayer->m_nButtons & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT)) || - pPlayer->GetAbsVelocity().AsVector2D().LengthSqr() > 50*50 ) - { - SetCondition( COND_PLAYER_PUSHING ); - Vector vecPush = GetAbsOrigin() - pPlayer->GetAbsOrigin(); - VectorNormalize( vecPush ); - CascadePlayerPush( vecPush, pPlayer->WorldSpaceCenter() ); - } - } -} - -void CAI_BaseNPC::CascadePlayerPush( const Vector &push, const Vector &pushOrigin ) -{ - // - // Try to push any friends that are in the way. - // - float hullWidth = GetHullWidth(); - const Vector & origin = GetAbsOrigin(); - const Vector2D &origin2D = origin.AsVector2D(); - - const float MIN_Z_TO_TRANSMIT = GetHullHeight() * 0.5 + 0.1; - const float DIST_REQD_TO_TRANSMIT_PUSH_SQ = Square( hullWidth * 5 + 0.1 ); - const float DIST_FROM_PUSH_VECTOR_REQD_SQ = Square( hullWidth + 0.1 ); - - Vector2D pushTestPoint = vec2_invalid; - - for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) - { - CAI_BaseNPC *pOther = g_AI_Manager.AccessAIs()[i]; - if ( pOther != this && pOther->IRelationType(this) == D_LI && !pOther->HasCondition( COND_PLAYER_PUSHING ) ) - { - const Vector &friendOrigin = pOther->GetAbsOrigin(); - if ( fabsf( friendOrigin.z - origin.z ) < MIN_Z_TO_TRANSMIT && - ( friendOrigin.AsVector2D() - origin.AsVector2D() ).LengthSqr() < DIST_REQD_TO_TRANSMIT_PUSH_SQ ) - { - if ( pushTestPoint == vec2_invalid ) - { - pushTestPoint = origin2D - pushOrigin.AsVector2D(); - // No normalize, since it wants to just be a big number and we can't be less that a hull away - pushTestPoint *= 2000; - pushTestPoint += origin2D; - - } - float t; - float distSq = CalcDistanceSqrToLine2D( friendOrigin.AsVector2D(), origin2D, pushTestPoint, &t ); - if ( t > 0 && distSq < DIST_FROM_PUSH_VECTOR_REQD_SQ ) - { - pOther->SetCondition( COND_PLAYER_PUSHING ); - } - } - } - } -} - - -//----------------------------------------------------------------------------- -// Break into pieces! -//----------------------------------------------------------------------------- -void CAI_BaseNPC::Break( CBaseEntity *pBreaker ) -{ - m_takedamage = DAMAGE_NO; - - Vector velocity; - AngularImpulse angVelocity; - IPhysicsObject *pPhysics = VPhysicsGetObject(); - Vector origin; - QAngle angles; - AddSolidFlags( FSOLID_NOT_SOLID ); - if ( pPhysics ) - { - pPhysics->GetVelocity( &velocity, &angVelocity ); - pPhysics->GetPosition( &origin, &angles ); - pPhysics->RecheckCollisionFilter(); - } - else - { - velocity = GetAbsVelocity(); - QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity ); - origin = GetAbsOrigin(); - angles = GetAbsAngles(); - } - - breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity ); - params.impactEnergyScale = m_impactEnergyScale; - params.defCollisionGroup = GetCollisionGroup(); - if ( params.defCollisionGroup == COLLISION_GROUP_NONE ) - { - // don't automatically make anything COLLISION_GROUP_NONE or it will - // collide with debris being ejected by breaking - params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE; - } - - // no damage/damage force? set a burst of 100 for some movement - params.defBurstScale = 100;//pDamageInfo ? 0 : 100; - PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, false ); - - UTIL_Remove(this); -} - - -//----------------------------------------------------------------------------- -// Purpose: Input handler for breaking the breakable immediately. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputBreak( inputdata_t &inputdata ) -{ - Break( inputdata.pActivator ); -} - - -//----------------------------------------------------------------------------- - -bool CAI_BaseNPC::FindNearestValidGoalPos( const Vector &vTestPoint, Vector *pResult ) -{ - AIMoveTrace_t moveTrace; - Vector vCandidate = vec3_invalid; - if ( GetNavigator()->CanFitAtPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) ) - { - if ( GetMoveProbe()->CheckStandPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) ) - { - vCandidate = vTestPoint; - } - } - - if ( vCandidate == vec3_invalid ) - { - int iNearestNode = GetPathfinder()->NearestNodeToPoint( vTestPoint ); - if ( iNearestNode != NO_NODE ) - { - GetMoveProbe()->MoveLimit( NAV_GROUND, - g_pBigAINet->GetNodePosition(GetHullType(), iNearestNode ), - vTestPoint, - MASK_SOLID_BRUSHONLY, - NULL, - 0, - &moveTrace ); - if ( ( moveTrace.vEndPosition - vTestPoint ).Length2DSqr() < Square( GetHullWidth() * 3.0 ) && - GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY ) ) - { - vCandidate = moveTrace.vEndPosition; - } - } - } - - if ( vCandidate != vec3_invalid ) - { - AI_Waypoint_t *pPathToPoint = GetPathfinder()->BuildRoute( GetAbsOrigin(), vCandidate, AI_GetSinglePlayer(), 5*12, NAV_NONE, true ); - if ( pPathToPoint ) - { - GetPathfinder()->UnlockRouteNodes( pPathToPoint ); - CAI_Path tempPath; - tempPath.SetWaypoints( pPathToPoint ); // path object will delete waypoints - } - else - vCandidate = vec3_invalid; - } - - if ( vCandidate == vec3_invalid ) - { - GetMoveProbe()->MoveLimit( NAV_GROUND, - GetAbsOrigin(), - vTestPoint, - MASK_SOLID_BRUSHONLY, - NULL, - 0, - &moveTrace ); - vCandidate = moveTrace.vEndPosition; - } - - if ( vCandidate == vec3_invalid ) - return false; - - *pResult = vCandidate; - return true; -} - -//--------------------------------------------------------- -// Pass a direction to get how far an NPC would see if facing -// that direction. Pass nothing to get the length of the NPC's -// current line of sight. -//--------------------------------------------------------- -float CAI_BaseNPC::LineOfSightDist( const Vector &vecDir, float zEye ) -{ - Vector testDir; - if( vecDir == vec3_invalid ) - { - testDir = EyeDirection3D(); - } - else - { - testDir = vecDir; - } - - if ( zEye == FLT_MAX ) - zEye = EyePosition().z; - - trace_t tr; - // Need to center trace so don't get erratic results based on orientation - Vector testPos( GetAbsOrigin().x, GetAbsOrigin().y, zEye ); - AI_TraceLOS( testPos, testPos + testDir * MAX_COORD_RANGE, this, &tr ); - return (tr.startpos - tr.endpos ).Length(); -} - -ConVar ai_LOS_mode( "ai_LOS_mode", "0", FCVAR_REPLICATED ); - -//----------------------------------------------------------------------------- -// Purpose: Use this to perform AI tracelines that are trying to determine LOS between points. -// LOS checks between entities should use FVisible. -//----------------------------------------------------------------------------- -void AI_TraceLOS( const Vector& vecAbsStart, const Vector& vecAbsEnd, CBaseEntity *pLooker, trace_t *ptr, ITraceFilter *pFilter ) -{ - AI_PROFILE_SCOPE( AI_TraceLOS ); - - if ( ai_LOS_mode.GetBool() ) - { - // Don't use LOS tracefilter - UTIL_TraceLine( vecAbsStart, vecAbsEnd, MASK_OPAQUE, pLooker, COLLISION_GROUP_NONE, ptr ); - return; - } - - // Use the custom LOS trace filter - CTraceFilterLOS traceFilter( pLooker, COLLISION_GROUP_NONE ); - if ( !pFilter ) - pFilter = &traceFilter; - AI_TraceLine( vecAbsStart, vecAbsEnd, MASK_OPAQUE_AND_NPCS, pFilter, ptr ); -} - -void CAI_BaseNPC::InputSetSpeedModifierRadius( inputdata_t &inputdata ) -{ - m_iSpeedModRadius = inputdata.value.Int(); - m_iSpeedModRadius *= m_iSpeedModRadius; -} -void CAI_BaseNPC::InputSetSpeedModifierSpeed( inputdata_t &inputdata ) -{ - m_iSpeedModSpeed = inputdata.value.Int(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsAllowedToDodge( void ) -{ - // Can't do it if I'm not available - if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT ) - return false; - - return ( m_flNextDodgeTime <= gpGlobals->curtime ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ParseScriptedNPCInteractions( void ) -{ - // Already parsed them? - if ( m_ScriptedInteractions.Count() ) - return; - - // Parse the model's key values and find any dynamic interactions - KeyValues *modelKeyValues = new KeyValues(""); - if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) ) - { - // Do we have a dynamic interactions section? - KeyValues *pkvInteractions = modelKeyValues->FindKey("dynamic_interactions"); - if ( pkvInteractions ) - { - KeyValues *pkvNode = pkvInteractions->GetFirstSubKey(); - while ( pkvNode ) - { - ScriptedNPCInteraction_t sInteraction; - sInteraction.iszInteractionName = AllocPooledString( pkvNode->GetName() ); - - // Trigger method - const char *pszTrigger = pkvNode->GetString( "trigger", NULL ); - if ( pszTrigger ) - { - if ( !Q_strncmp( pszTrigger, "auto_in_combat", 14) ) - { - sInteraction.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT; - } - } - - // Loop Break trigger method - pszTrigger = pkvNode->GetString( "loop_break_trigger", NULL ); - if ( pszTrigger ) - { - char szTrigger[256]; - Q_strncpy( szTrigger, pszTrigger, sizeof(szTrigger) ); - char *pszParam = strtok( szTrigger, " " ); - while (pszParam) - { - if ( !Q_strncmp( pszParam, "on_damage", 9) ) - { - sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE; - } - if ( !Q_strncmp( pszParam, "on_flashlight_illum", 19) ) - { - sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM; - } - - pszParam = strtok(NULL," "); - } - } - - // Origin - const char *pszOrigin = pkvNode->GetString( "origin_relative", "0 0 0" ); - UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), pszOrigin ); - - // Angles - const char *pszAngles = pkvNode->GetString( "angles_relative", NULL ); - if ( pszAngles ) - { - sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES; - UTIL_StringToVector( sInteraction.angRelativeAngles.Base(), pszAngles ); - } - - // Velocity - const char *pszVelocity = pkvNode->GetString( "velocity_relative", NULL ); - if ( pszVelocity ) - { - sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY; - UTIL_StringToVector( sInteraction.vecRelativeVelocity.Base(), pszVelocity ); - } - - // Entry Sequence - const char *pszSequence = pkvNode->GetString( "entry_sequence", NULL ); - if ( pszSequence ) - { - sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString( pszSequence ); - } - // Entry Activity - const char *pszActivity = pkvNode->GetString( "entry_activity", NULL ); - if ( pszActivity ) - { - sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID( pszActivity ); - } - - // Sequence - pszSequence = pkvNode->GetString( "sequence", NULL ); - if ( pszSequence ) - { - sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( pszSequence ); - } - // Activity - pszActivity = pkvNode->GetString( "activity", NULL ); - if ( pszActivity ) - { - sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID( pszActivity ); - } - - // Exit Sequence - pszSequence = pkvNode->GetString( "exit_sequence", NULL ); - if ( pszSequence ) - { - sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString( pszSequence ); - } - // Exit Activity - pszActivity = pkvNode->GetString( "exit_activity", NULL ); - if ( pszActivity ) - { - sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID( pszActivity ); - } - - // Delay - sInteraction.flDelay = pkvNode->GetFloat( "delay", 10.0 ); - - // Delta - sInteraction.flDistSqr = pkvNode->GetFloat( "origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST) ); - - // Loop? - if ( pkvNode->GetFloat( "loop_in_action", 0 ) ) - { - sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION; - } - - // Needs a weapon? - const char *pszNeedsWeapon = pkvNode->GetString( "needs_weapon", NULL ); - if ( pszNeedsWeapon ) - { - if ( !Q_strncmp( pszNeedsWeapon, "ME", 2 ) ) - { - sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; - } - else if ( !Q_strncmp( pszNeedsWeapon, "THEM", 4 ) ) - { - sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; - } - else if ( !Q_strncmp( pszNeedsWeapon, "BOTH", 4 ) ) - { - sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; - sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; - } - } - - // Specific weapon types - const char *pszWeaponName = pkvNode->GetString( "weapon_mine", NULL ); - if ( pszWeaponName ) - { - sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; - sInteraction.iszMyWeapon = AllocPooledString( pszWeaponName ); - } - pszWeaponName = pkvNode->GetString( "weapon_theirs", NULL ); - if ( pszWeaponName ) - { - sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; - sInteraction.iszTheirWeapon = AllocPooledString( pszWeaponName ); - } - - // Add it to the list - AddScriptedNPCInteraction( &sInteraction ); - - // Move to next interaction - pkvNode = pkvNode->GetNextKey(); - } - } - } - - modelKeyValues->deleteThis(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction ) -{ - int nNewIndex = m_ScriptedInteractions.AddToTail(); - - if ( ai_debug_dyninteractions.GetBool() ) - { - Msg("%s(%s): Added dynamic interaction: %s\n", GetClassName(), GetDebugName(), STRING(pInteraction->iszInteractionName) ); - } - - // Copy the interaction over - ScriptedNPCInteraction_t *pNewInt = &(m_ScriptedInteractions[nNewIndex]); - memcpy( pNewInt, pInteraction, sizeof(ScriptedNPCInteraction_t) ); - - // Calculate the local to world matrix - m_ScriptedInteractions[nNewIndex].matDesiredLocalToWorld.SetupMatrixOrgAngles( pInteraction->vecRelativeOrigin, pInteraction->angRelativeAngles ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase ) -{ - if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID ) - { - int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity ); - return GetSequenceName( iSequence ); - } - - if ( pInteraction->sPhases[iPhase].iszSequence != NULL_STRING ) - return STRING(pInteraction->sPhases[iPhase].iszSequence); - - return NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive ) -{ - m_hInteractionPartner = pOtherNPC; - if ( bActive ) - { - m_iInteractionState = NPCINT_RUNNING_ACTIVE; - } - else - { - m_iInteractionState = NPCINT_RUNNING_PARTNER; - } - m_bCannotDieDuringInteraction = true; - - // Force the NPC into an idle schedule so they don't move. - // NOTE: We must set SCHED_IDLE_STAND directly, to prevent derived NPC - // classes from translating the idle stand schedule away to do something bad. - SetSchedule( GetSchedule(SCHED_IDLE_STAND) ); - - // Prepare the NPC for the script. Setting this allows the scripted sequences - // that we're about to create to immediately grab & use this NPC right away. - // This prevents the NPC from being able to make any schedule decisions - // before the DSS gets underway. - m_scriptState = SCRIPT_PLAYING; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles ) -{ - variant_t emptyVariant; - - StartRunningInteraction( pOtherNPC, true ); - if ( pOtherNPC ) - { - pOtherNPC->StartRunningInteraction( this, false ); - - //Msg("%s(%s) disabled collisions with %s(%s) at %0.2f\n", GetClassName(), GetDebugName(), pOtherNPC->GetClassName(), pOtherNPC->GetDebugName(), gpGlobals->curtime ); - PhysDisableEntityCollisions( this, pOtherNPC ); - } - - // Determine which sequences we're going to use - const char *pszEntrySequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_ENTRY ); - const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE ); - const char *pszExitSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_EXIT ); - - // Debug - if ( ai_debug_dyninteractions.GetBool() ) - { - if ( pOtherNPC ) - { - Msg("%s(%s) starting dynamic interaction \"%s\" with %s(%s).\n", GetClassName(), GetDebugName(), STRING(pInteraction->iszInteractionName), pOtherNPC->GetClassName(), pOtherNPC->GetDebugName() ); - if ( pszEntrySequence ) - { - Msg( " - Entry sequence: %s\n", pszEntrySequence ); - } - Msg( " - Core sequence: %s\n", pszSequence ); - if ( pszExitSequence ) - { - Msg( " - Exit sequence: %s\n", pszExitSequence ); - } - } - } - - // Create a scripted sequence name that's guaranteed to be unique - char szSSName[256]; - if ( pOtherNPC ) - { - Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d%s%d", GetDebugName(), entindex(), pOtherNPC->GetDebugName(), pOtherNPC->entindex() ); - } - else - { - Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d", GetDebugName(), entindex() ); - } - string_t iszSSName = AllocPooledString(szSSName); - - // Setup next attempt - pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2); - - // Spawn a scripted sequence for this NPC to play the interaction anim - CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" ); - pMySequence->KeyValue( "m_iszEntry", pszEntrySequence ); - pMySequence->KeyValue( "m_iszPlay", pszSequence ); - pMySequence->KeyValue( "m_iszPostIdle", pszExitSequence ); - pMySequence->KeyValue( "m_fMoveTo", "5" ); - pMySequence->SetAbsOrigin( GetAbsOrigin() ); - - QAngle angDesired = GetAbsAngles(); - angDesired[YAW] = m_flInteractionYaw; - - pMySequence->SetAbsAngles( angDesired ); - pMySequence->ForceSetTargetEntity( this, true ); - pMySequence->SetName( iszSSName ); - pMySequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE ); - pMySequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 ); - pMySequence->SetSynchPostIdles( true ); - if ( ai_debug_dyninteractions.GetBool() ) - { - pMySequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT; - } - - // Spawn the matching scripted sequence for the other NPC - CAI_ScriptedSequence *pTheirSequence = NULL; - if ( pOtherNPC ) - { - pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" ); - pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence ); - pTheirSequence->KeyValue( "m_iszPlay", pszSequence ); - pTheirSequence->KeyValue( "m_iszPostIdle", pszExitSequence ); - pTheirSequence->KeyValue( "m_fMoveTo", "5" ); - pTheirSequence->SetAbsOrigin( vecOtherOrigin ); - pTheirSequence->SetAbsAngles( angOtherAngles ); - pTheirSequence->ForceSetTargetEntity( pOtherNPC, true ); - pTheirSequence->SetName( iszSSName ); - pTheirSequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE ); - pTheirSequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 ); - pTheirSequence->SetSynchPostIdles( true ); - if ( ai_debug_dyninteractions.GetBool() ) - { - pTheirSequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT; - } - - // Tell their sequence to keep their position relative to me - pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld ); - } - - // Spawn both sequences at once - pMySequence->Spawn(); - if ( pTheirSequence ) - { - pTheirSequence->Spawn(); - } - - // Call activate on both sequences at once - pMySequence->Activate(); - if ( pTheirSequence ) - { - pTheirSequence->Activate(); - } - - // Setup the outputs for both sequences. The first kills them both when it finishes - pMySequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); - if ( pszExitSequence ) - { - pMySequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); - if ( pTheirSequence ) - { - pTheirSequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); - } - } - else - { - pMySequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); - if ( pTheirSequence ) - { - pTheirSequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); - } - } - if ( pTheirSequence ) - { - pTheirSequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); - } - - // Tell both sequences to start - pMySequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 ); - if ( pTheirSequence ) - { - pTheirSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced ) -{ - if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT ) - return false; - - if ( !IsAlive() ) - return false; - - if ( IsOnFire() ) - return false; - - if ( IsCrouching() ) - return false; - - // Not while running scripted sequences - if ( m_hCine ) - return false; - - if ( bForced ) - { - if ( !m_hForcedInteractionPartner ) - return false; - } - else - { - if ( m_hForcedInteractionPartner || m_hInteractionPartner ) - return false; - if ( IsInAScript() || !HasCondition(COND_IN_PVS) ) - return false; - if ( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_MOVE_AWAY) ) - return false; - - // Default AI prevents interactions while melee attacking, but not ranged attacking - if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) ) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CheckForScriptedNPCInteractions( void ) -{ - // Are we being forced to interact with another NPC? If so, do that - if ( m_hForcedInteractionPartner ) - { - CheckForcedNPCInteractions(); - return; - } - - // Otherwise, see if we can interaction with our enemy - if ( !m_ScriptedInteractions.Count() || !GetEnemy() ) - return; - - CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer(); - - if( !pNPC ) - return; - - // Recalculate interaction capability whenever we switch enemies - if ( m_hLastInteractionTestTarget != GetEnemy() ) - { - m_hLastInteractionTestTarget = GetEnemy(); - - CalculateValidEnemyInteractions(); - } - - // First, make sure I'm in a state where I can do this - if ( !CanRunAScriptedNPCInteraction() ) - return; - if ( pNPC && !pNPC->CanRunAScriptedNPCInteraction() ) - return; - - for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) - { - ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i]; - - if ( !pInteraction->bValidOnCurrentEnemy ) - continue; - if ( pInteraction->flNextAttemptTime > gpGlobals->curtime ) - continue; - - Vector vecOrigin; - QAngle angAngles; - if ( !InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) ) - continue; - - m_iInteractionPlaying = i; - StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Calculate all the valid dynamic interactions we can perform with our current enemy -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CalculateValidEnemyInteractions( void ) -{ - CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer(); - if ( !pNPC ) - return; - - bool bDebug = (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT && ai_debug_dyninteractions.GetBool()); - if ( bDebug ) - { - Msg("%s(%s): Computing valid interactions with %s(%s)\n", GetClassName(), GetDebugName(), pNPC->GetClassName(), pNPC->GetDebugName() ); - } - - bool bFound = false; - for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) - { - ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i]; - pInteraction->bValidOnCurrentEnemy = false; - - // If the trigger method of the interaction isn't the one we're after, we're done - if ( pInteraction->iTriggerMethod != SNPCINT_AUTOMATIC_IN_COMBAT ) - continue; - - if ( !pNPC->GetModelPtr() ) - continue; - - // If we have a damage filter that prevents us hurting the enemy, - // don't interact with him, since most interactions kill the enemy. - // Create a fake damage info to test it with. - CTakeDamageInfo tempinfo( this, this, vec3_origin, vec3_origin, 1.0, DMG_BULLET ); - if ( !pNPC->PassesDamageFilter( tempinfo ) ) - continue; - - // Check the weapon requirements for the interaction - if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_ME ) - { - if ( !GetActiveWeapon()) - continue; - - // Check the specific weapon type - if ( pInteraction->iszMyWeapon != NULL_STRING && GetActiveWeapon()->m_iClassname != pInteraction->iszMyWeapon ) - continue; - } - if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_THEM ) - { - if ( !pNPC->GetActiveWeapon() ) - continue; - - // Check the specific weapon type - if ( pInteraction->iszTheirWeapon != NULL_STRING && pNPC->GetActiveWeapon()->m_iClassname != pInteraction->iszTheirWeapon ) - continue; - } - - // Script needs the other NPC, so make sure they're not dead - if ( !pNPC->IsAlive() ) - continue; - - // Use sequence? or activity? - if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID ) - { - // Resolve the activity to a sequence, and make sure our enemy has it - const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE ); - if ( !pszSequence ) - continue; - if ( pNPC->LookupSequence( pszSequence ) == -1 ) - continue; - } - else - { - if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 ) - continue; - } - - pInteraction->bValidOnCurrentEnemy = true; - bFound = true; - - if ( bDebug ) - { - Msg(" Found: %s\n", STRING(pInteraction->iszInteractionName) ); - } - } - - if ( bDebug && !bFound ) - { - Msg(" No valid interactions found.\n"); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CheckForcedNPCInteractions( void ) -{ - // If we don't have an interaction, we're waiting for our partner to start it. Do nothing. - if ( m_iInteractionPlaying == NPCINT_NONE ) - return; - - CAI_BaseNPC *pNPC = m_hForcedInteractionPartner->MyNPCPointer(); - - // First, make sure both NPCs are able to do this - if ( !CanRunAScriptedNPCInteraction( true ) || !pNPC->CanRunAScriptedNPCInteraction( true ) ) - { - // If we were still moving to our target, abort. - if ( m_iInteractionState == NPCINT_MOVING_TO_MARK ) - { - if ( m_hForcedInteractionPartner ) - { - // We've aborted a forced interaction. Let the mapmaker know. - m_OnForcedInteractionAborted.FireOutput( this, this ); - } - - CleanupForcedInteraction(); - pNPC->CleanupForcedInteraction(); - } - return; - } - - // Check to see if we can start our interaction. If we can, dance. - ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[m_iInteractionPlaying]; - Vector vecOrigin; - QAngle angAngles; - if ( !InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) ) - return;; - - StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles ) -{ - // Get a matrix that'll convert from my local interaction space to world space - VMatrix matMeToWorld, matLocalToWorld; - QAngle angMyCurrent = GetAbsAngles(); - angMyCurrent[YAW] = m_flInteractionYaw; - matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angMyCurrent ); - MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld ); - - // Get the desired NPC position in worldspace - vecOrigin = matLocalToWorld.GetTranslation(); - MatrixToAngles( matLocalToWorld, angAngles ); - - bool bDebug = ai_debug_dyninteractions.GetBool(); - if ( bDebug ) - { - NDebugOverlay::Axis( vecOrigin, angAngles, 20, true, 0.1 ); - } - - // Determine whether or not the enemy is on the target - float flDistSqr = (vecOrigin - pOtherNPC->GetAbsOrigin()).LengthSqr(); - if ( flDistSqr > pInteraction->flDistSqr ) - { - if ( bDebug ) - { - if ( m_debugOverlays & OVERLAY_NPC_SELECTED_BIT || pOtherNPC->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) - { - if ( ai_debug_dyninteractions.GetFloat() == 2 ) - { - Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr, - pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z ); - } - } - } - return false; - } - - if ( bDebug ) - { - Msg("DYNINT: (%s) testing interaction \"%s\"\n", GetDebugName(), STRING(pInteraction->iszInteractionName) ); - Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); - Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr, - pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z ); - - if ( pOtherNPC ) - { - float flOtherSpeed = pOtherNPC->GetSequenceGroundSpeed( pOtherNPC->GetSequence() ); - Msg(" %s Speed: %.2f\n", pOtherNPC->GetSequenceName( pOtherNPC->GetSequence() ), flOtherSpeed); - } - } - - // Angle check, if we're supposed to - if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES ) - { - QAngle angEnemyAngles = pOtherNPC->GetAbsAngles(); - bool bMatches = true; - for ( int ang = 0; ang < 3; ang++ ) - { - float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] ); - if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF ) - { - bMatches = false; - break; - } - } - if ( !bMatches ) - return false; - - if ( bDebug ) - { - Msg(" %s angle matched: (%0.2f %0.2f %0.2f), desired (%0.2f, %0.2f, %0.2f)\n", GetDebugName(), - anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) ); - } - } - - // TODO: Velocity check, if we're supposed to - if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY ) - { - - } - - // Valid so far. Now check to make sure there's nothing in the way. - // This isn't a very good method of checking, but it's cheap and rules out the problems we're seeing so far. - // If we start getting interactions that start a fair distance apart, we're going to need to do more work here. - trace_t tr; - AI_TraceLine( EyePosition(), pOtherNPC->EyePosition(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr); - if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC ) - { - if ( bDebug ) - { - Msg( " %s Interaction was blocked.\n", GetDebugName() ); - NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 ); - NDebugOverlay::Line( pOtherNPC->EyePosition(), tr.endpos, 255,0,0, true, 1.0 ); - } - return false; - } - - if ( bDebug ) - { - NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 ); - } - - // Do a knee-level trace to find low physics objects - Vector vecMyKnee, vecOtherKnee; - CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecMyKnee ); - pOtherNPC->CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecOtherKnee ); - AI_TraceLine( vecMyKnee, vecOtherKnee, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr); - if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC ) - { - if ( bDebug ) - { - Msg( " %s Interaction was blocked.\n", GetDebugName() ); - NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 ); - NDebugOverlay::Line( vecOtherKnee, tr.endpos, 255,0,0, true, 1.0 ); - } - return false; - } - - if ( bDebug ) - { - NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 ); - } - - // Finally, make sure the NPC can actually fit at the interaction position - // This solves problems with NPCs who are a few units or so above the - // interaction point, and would sink into the ground when playing the anim. - CTraceFilterSkipTwoEntities traceFilter( pOtherNPC, this, COLLISION_GROUP_NONE ); - AI_TraceHull( vecOrigin, vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr ); - if ( tr.startsolid ) - { - if ( bDebug ) - { - NDebugOverlay::Box( vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), 255,0,0, true, 1.0 ); - } - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Return true if this NPC cannot die because it's in an interaction -// and the flag has been set by the animation. -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::HasInteractionCantDie( void ) -{ - return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::InputForceInteractionWithNPC( inputdata_t &inputdata ) -{ - // Get the interaction name & target - char parseString[255]; - Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString)); - - // First, the target's name - char *pszParam = strtok(parseString," "); - if ( !pszParam || !pszParam[0] ) - { - Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC \n", GetClassname(), GetDebugName(), inputdata.value.String() ); - return; - } - // Find the target - CBaseEntity *pTarget = FindNamedEntity( pszParam ); - if ( !pTarget ) - { - Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find entity named: %s\n", GetClassname(), GetDebugName(), pszParam ); - return; - } - CAI_BaseNPC *pNPC = pTarget->MyNPCPointer(); - if ( !pNPC || !pNPC->GetModelPtr() ) - { - Warning("%s(%s) received ForceInteractionWithNPC input, but entity named %s cannot run dynamic interactions.\n", GetClassname(), GetDebugName(), pszParam ); - return; - } - - // Second, the interaction name - pszParam = strtok(NULL," "); - if ( !pszParam || !pszParam[0] ) - { - Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC \n", GetClassname(), GetDebugName(), inputdata.value.String() ); - return; - } - - // Find the interaction from the name, and ensure it's one that the target NPC can play - int iInteraction = -1; - for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) - { - if ( Q_strncmp( pszParam, STRING(m_ScriptedInteractions[i].iszInteractionName), strlen(pszParam) ) ) - continue; - - // Use sequence? or activity? - if ( m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID ) - { - if ( !pNPC->HaveSequenceForActivity( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity ) ) - { - // Other NPC may have all the matching sequences, but just without the activity specified. - // Lets find a single sequence for us, and ensure they have a matching one. - int iMySeq = SelectWeightedSequence( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity ); - if ( pNPC->LookupSequence( GetSequenceName(iMySeq) ) == -1 ) - continue; - } - } - else - { - if ( pNPC->LookupSequence( STRING(m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 ) - continue; - } - - iInteraction = i; - break; - } - - if ( iInteraction == -1 ) - { - Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find an interaction named %s that entity named %s could run.\n", GetClassname(), GetDebugName(), pszParam, pNPC->GetDebugName() ); - return; - } - - // Found both pieces of data, lets dance. - StartForcedInteraction( pNPC, iInteraction ); - pNPC->StartForcedInteraction( this, NPCINT_NONE ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction ) -{ - m_hForcedInteractionPartner = pNPC; - ClearSchedule(); - - m_iInteractionPlaying = iInteraction; - m_iInteractionState = NPCINT_MOVING_TO_MARK; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CleanupForcedInteraction( void ) -{ - m_hForcedInteractionPartner = NULL; - m_iInteractionPlaying = NPCINT_NONE; - m_iInteractionState = NPCINT_NOT_RUNNING; -} - -//----------------------------------------------------------------------------- -// Purpose: Calculate a position to move to so that I can interact with my -// target NPC. -// -// FIXME: THIS ONLY WORKS FOR INTERACTIONS THAT REQUIRE THE TARGET -// NPC TO BE SOME DISTANCE DIRECTLY IN FRONT OF ME. -//----------------------------------------------------------------------------- -void CAI_BaseNPC::CalculateForcedInteractionPosition( void ) -{ - if ( m_iInteractionPlaying == NPCINT_NONE ) - return; - - ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction(); - - // Pretend I was facing the target, and extrapolate from that the position I should be at - Vector vecToTarget = m_hForcedInteractionPartner->GetAbsOrigin() - GetAbsOrigin(); - VectorNormalize( vecToTarget ); - QAngle angToTarget; - VectorAngles( vecToTarget, angToTarget ); - - // Get the desired position in worldspace, relative to the target - VMatrix matMeToWorld, matLocalToWorld; - matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angToTarget ); - MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld ); - - Vector vecOrigin = GetAbsOrigin() - matLocalToWorld.GetTranslation(); - m_vecForcedWorldPosition = m_hForcedInteractionPartner->GetAbsOrigin() + vecOrigin; - - //NDebugOverlay::Axis( m_vecForcedWorldPosition, angToTarget, 20, true, 3.0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pPlayer - -//----------------------------------------------------------------------------- -void CAI_BaseNPC::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot ) -{ -#ifdef HL2_DLL - if ( IsActiveDynamicInteraction() ) - { - ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction(); - if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM ) - { - // Only do this in alyx darkness mode - if ( HL2GameRules()->IsAlyxInDarknessMode() ) - { - // Can only break when we're in the action anim - if ( m_hCine->IsPlayingAction() ) - { - m_hCine->StopActionLoop( true ); - } - } - } - } -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::ModifyOrAppendCriteria( AI_CriteriaSet& set ) -{ - BaseClass::ModifyOrAppendCriteria( set ); - - // Append time since seen player - if ( m_flLastSawPlayerTime ) - { - set.AppendCriteria( "timesinceseenplayer", UTIL_VarArgs( "%f", gpGlobals->curtime - m_flLastSawPlayerTime ) ); - } - else - { - set.AppendCriteria( "timesinceseenplayer", "-1" ); - } - - // Append distance to my enemy - if ( GetEnemy() ) - { - set.AppendCriteria( "distancetoenemy", UTIL_VarArgs( "%f", EnemyDistance(GetEnemy()) ) ); - } - else - { - set.AppendCriteria( "distancetoenemy", "-1" ); - } -} - -//----------------------------------------------------------------------------- -// If I were crouching at my current location, could I shoot this target? -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::CouldShootIfCrouching( CBaseEntity *pTarget ) -{ - bool bWasStanding = !IsCrouching(); - Crouch(); - - Vector vecTarget; - if (GetActiveWeapon()) - { - vecTarget = pTarget->BodyTarget( GetActiveWeapon()->GetLocalOrigin() ); - } - else - { - vecTarget = pTarget->BodyTarget( GetLocalOrigin() ); - } - - bool bResult = WeaponLOSCondition( GetLocalOrigin(), vecTarget, false ); - - if ( bWasStanding ) - { - Stand(); - } - - return bResult; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsCrouchedActivity( Activity activity ) -{ - Activity realActivity = TranslateActivity(activity); - - switch ( realActivity ) - { - case ACT_RELOAD_LOW: - case ACT_COVER_LOW: - case ACT_COVER_PISTOL_LOW: - case ACT_COVER_SMG1_LOW: - case ACT_RELOAD_SMG1_LOW: - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Get shoot position of BCC at an arbitrary position -//----------------------------------------------------------------------------- -Vector CAI_BaseNPC::Weapon_ShootPosition( void ) -{ - Vector right; - GetVectors( NULL, &right, NULL ); - - bool bStanding = !IsCrouching(); - if ( bStanding && (CapabilitiesGet() & bits_CAP_DUCK) ) - { - if ( IsCrouchedActivity( GetActivity() ) ) - { - bStanding = false; - } - } - - if ( !bStanding ) - return (GetAbsOrigin() + GetCrouchGunOffset() + right * 8); - - return BaseClass::Weapon_ShootPosition(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity ) -{ - if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) - { - if ( ai_test_moveprobe_ignoresmall.GetBool() && IsNavigationUrgent() ) - { - IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject(); - - if ( pPhysics->IsMoveable() && pPhysics->GetMass() < 40.0 ) - return false; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::Crouch( void ) -{ - m_bIsCrouching = true; - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::IsCrouching( void ) -{ - return ( (CapabilitiesGet() & bits_CAP_DUCK) && m_bIsCrouching ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CAI_BaseNPC::Stand( void ) -{ - if ( m_bForceCrouch ) - return false; - - m_bIsCrouching = false; - DesireStand(); - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_BaseNPC::DesireCrouch( void ) -{ - m_bCrouchDesired = true; -} - -bool CAI_BaseNPC::IsInChoreo() const -{ - return m_bInChoreo; -} +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" + +#include "ai_basenpc.h" +#include "fmtstr.h" +#include "activitylist.h" +#include "animation.h" +#include "basecombatweapon.h" +#include "soundent.h" +#include "decals.h" +#include "entitylist.h" +#include "eventqueue.h" +#include "entityapi.h" +#include "bitstring.h" +#include "gamerules.h" // For g_pGameRules +#include "scripted.h" +#include "worldsize.h" +#include "game.h" +#include "shot_manipulator.h" + +#ifdef HL2_DLL +#include "ai_interactions.h" +#include "hl2_gamerules.h" +#endif // HL2_DLL + +#include "ai_network.h" +#include "ai_networkmanager.h" +#include "ai_pathfinder.h" +#include "ai_node.h" +#include "ai_default.h" +#include "ai_schedule.h" +#include "ai_task.h" +#include "ai_hull.h" +#include "ai_moveprobe.h" +#include "ai_hint.h" +#include "ai_navigator.h" +#include "ai_senses.h" +#include "ai_squadslot.h" +#include "ai_memory.h" +#include "ai_squad.h" +#include "ai_localnavigator.h" +#include "ai_tacticalservices.h" +#include "ai_behavior.h" +#include "ai_dynamiclink.h" +#include "AI_Criteria.h" +#include "basegrenade_shared.h" +#include "ammodef.h" +#include "player.h" +#include "sceneentity.h" +#include "ndebugoverlay.h" +#include "mathlib.h" +#include "bone_setup.h" +#include "IEffects.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "vstdlib/strtools.h" +#include "doors.h" +#include "BasePropDoor.h" +#include "saverestore_utlvector.h" +#include "npcevent.h" +#include "movevars_shared.h" +#include "te_effect_dispatch.h" +#include "globals.h" +#include "saverestore_bitstring.h" +#include "checksum_crc.h" +#include "iservervehicle.h" +#include "filters.h" +#ifdef HL2_DLL +#include "npc_bullseye.h" +#include "hl2_player.h" +#include "weapon_physcannon.h" +#endif +#include "waterbullet.h" +#include "in_buttons.h" +#include "eventlist.h" +#include "globalstate.h" +#include "physics_prop_ragdoll.h" +#include "vphysics/friction.h" +#include "physics_npc_solver.h" +#include "tier0/vcrmode.h" +#include "death_pose.h" +#include "datacache/imdlcache.h" +#ifdef HL2_EPISODIC +#include "npc_alyx_episodic.h" +#endif + +#include "env_debughistory.h" +#include "collisionutils.h" + +extern ConVar sk_healthkit; + +// dvs: for opening doors -- these should probably not be here +#include "ai_route.h" +#include "ai_waypoint.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//#define DEBUG_LOOK + +bool RagdollManager_SaveImportant( CAI_BaseNPC *pNPC ); + +#define MIN_PHYSICS_FLINCH_DAMAGE 5.0f + +#define NPC_GRENADE_FEAR_DIST 200 +#define MAX_GLASS_PENETRATION_DEPTH 16.0f + +#define FINDNAMEDENTITY_MAX_ENTITIES 32 // max number of entities to be considered for random entity selection in FindNamedEntity + +extern bool g_fDrawLines; +extern short g_sModelIndexLaser; // holds the index for the laser beam +extern short g_sModelIndexLaserDot; // holds the index for the laser beam dot + +// Debugging tools +ConVar ai_no_select_box( "ai_no_select_box", "0" ); + +ConVar ai_show_think_tolerance( "ai_show_think_tolerance", "0" ); +ConVar ai_debug_think_ticks( "ai_debug_think_ticks", "0" ); +ConVar ai_debug_doors( "ai_debug_doors", "0" ); +ConVar ai_debug_enemies( "ai_debug_enemies", "0" ); + +ConVar ai_rebalance_thinks( "ai_rebalance_thinks", "1" ); +ConVar ai_use_efficiency( "ai_use_efficiency", "1" ); +ConVar ai_use_frame_think_limits( "ai_use_frame_think_limits", "1" ); +ConVar ai_default_efficient( "ai_default_efficient", ( IsXbox() ) ? "1" : "0" ); +ConVar ai_efficiency_override( "ai_efficiency_override", "0" ); +ConVar ai_debug_efficiency( "ai_debug_efficiency", "0" ); +ConVar ai_debug_dyninteractions( "ai_debug_dyninteractions", "0", FCVAR_NONE, "Debug the NPC dynamic interaction system." ); +ConVar ai_frametime_limit( "ai_frametime_limit", "50", FCVAR_NONE, "frametime limit for min efficiency AIE_NORMAL (in sec's)." ); + +ConVar ai_use_think_optimizations( "ai_use_think_optimizations", "1" ); + +ConVar ai_test_moveprobe_ignoresmall( "ai_test_moveprobe_ignoresmall", "0" ); + +#ifndef _RETAIL +#define ShouldUseEfficiency() ( ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool() ) +#define ShouldUseFrameThinkLimits() ( ai_use_think_optimizations.GetBool() && ai_use_frame_think_limits.GetBool() ) +#define ShouldRebalanceThinks() ( ai_use_think_optimizations.GetBool() && ai_rebalance_thinks.GetBool() ) +#define ShouldDefaultEfficient() ( ai_use_think_optimizations.GetBool() && ai_default_efficient.GetBool() ) +#else +#define ShouldUseEfficiency() ( true ) +#define ShouldUseFrameThinkLimits() ( true ) +#define ShouldRebalanceThinks() ( true ) +#define ShouldDefaultEfficient() ( true ) +#endif + +#ifndef _RETAIL +#define DbgEnemyMsg if ( !ai_debug_enemies.GetBool() ) ; else DevMsg +#else +#define DbgEnemyMsg DevMsg +#endif + +#ifdef DEBUG_AI_FRAME_THINK_LIMITS +#define DbgFrameLimitMsg DevMsg +#else +#if defined __GNUC__ || (defined _MSC_VER && _MSC_VER >= 1400) +#define DbgFrameLimitMsg(...) +#else +#define DbgFrameLimitMsg (void) +#endif +#endif + +// NPC damage adjusters +ConVar sk_npc_head( "sk_npc_head","2" ); +ConVar sk_npc_chest( "sk_npc_chest","1" ); +ConVar sk_npc_stomach( "sk_npc_stomach","1" ); +ConVar sk_npc_arm( "sk_npc_arm","1" ); +ConVar sk_npc_leg( "sk_npc_leg","1" ); +ConVar showhitlocation( "showhitlocation", "0" ); + +// Squad debugging +ConVar ai_debug_squads( "ai_debug_squads", "0" ); +ConVar ai_debug_loners( "ai_debug_loners", "0" ); + +// Shoot trajectory +ConVar ai_lead_time( "ai_lead_time","0.0" ); +ConVar ai_shot_stats( "ai_shot_stats", "0" ); +ConVar ai_shot_stats_term( "ai_shot_stats_term", "1000" ); +ConVar ai_shot_bias( "ai_shot_bias", "1.0" ); + +ConVar ai_spread_defocused_cone_multiplier( "ai_spread_defocused_cone_multiplier","3.0" ); +ConVar ai_spread_cone_focus_time( "ai_spread_cone_focus_time","0.6" ); +ConVar ai_spread_pattern_focus_time( "ai_spread_pattern_focus_time","0.8" ); + +ConVar ai_reaction_delay_idle( "ai_reaction_delay_idle","0.3" ); +ConVar ai_reaction_delay_alert( "ai_reaction_delay_alert", "0.1" ); + +//----------------------------------------------------------------------------- +// +// Crude frame timings +// + +CFastTimer g_AIRunTimer; +CFastTimer g_AIPostRunTimer; +CFastTimer g_AIMoveTimer; + +CFastTimer g_AIConditionsTimer; +CFastTimer g_AIPrescheduleThinkTimer; +CFastTimer g_AIMaintainScheduleTimer; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +CAI_Manager g_AI_Manager; + +//------------------------------------- + +CAI_Manager::CAI_Manager() +{ + m_AIs.EnsureCapacity( MAX_AIS ); +} + +//------------------------------------- + +CAI_BaseNPC **CAI_Manager::AccessAIs() +{ + if (m_AIs.Count()) + return &m_AIs[0]; + return NULL; +} + +//------------------------------------- + +int CAI_Manager::NumAIs() +{ + return m_AIs.Count(); +} + +//------------------------------------- + +void CAI_Manager::AddAI( CAI_BaseNPC *pAI ) +{ + m_AIs.AddToTail( pAI ); +} + +//------------------------------------- + +void CAI_Manager::RemoveAI( CAI_BaseNPC *pAI ) +{ + int i = m_AIs.Find( pAI ); + + if ( i != -1 ) + m_AIs.FastRemove( i ); +} + + +//----------------------------------------------------------------------------- + +// ================================================================ +// Init static data +// ================================================================ +int CAI_BaseNPC::m_nDebugBits = 0; +CAI_BaseNPC* CAI_BaseNPC::m_pDebugNPC = NULL; +int CAI_BaseNPC::m_nDebugPauseIndex = -1; + +CAI_ClassScheduleIdSpace CAI_BaseNPC::gm_ClassScheduleIdSpace( true ); +CAI_GlobalScheduleNamespace CAI_BaseNPC::gm_SchedulingSymbols; +CAI_LocalIdSpace CAI_BaseNPC::gm_SquadSlotIdSpace( true ); + +string_t CAI_BaseNPC::gm_iszPlayerSquad; + +int CAI_BaseNPC::gm_iNextThinkRebalanceTick; +float CAI_BaseNPC::gm_flTimeLastSpawn; +int CAI_BaseNPC::gm_nSpawnedThisFrame; + +CSimpleSimTimer CAI_BaseNPC::m_AnyUpdateEnemyPosTimer; + +// ================================================================ +// Class Methods +// ================================================================ + +//----------------------------------------------------------------------------- +// Purpose: Static debug function to clear schedules for all NPCS +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ClearAllSchedules(void) +{ + CAI_BaseNPC *npc = gEntList.NextEntByClass( (CAI_BaseNPC *)NULL ); + + while (npc) + { + npc->ClearSchedule(); + npc->GetNavigator()->ClearGoal(); + npc = gEntList.NextEntByClass(npc); + } +} + +// ============================================================================== + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::Event_Gibbed( const CTakeDamageInfo &info ) +{ + bool gibbed = CorpseGib( info ); + + if ( gibbed ) + { + // don't remove players! + UTIL_Remove( this ); + SetThink( NULL ); //We're going away, so don't think anymore. + } + else + { + CorpseFade(); + } + + return gibbed; +} + +//========================================================= +// GetFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CAI_BaseNPC::GetFlinchActivity( bool bHeavyDamage, bool bGesture ) +{ + Activity flinchActivity; + + switch ( LastHitGroup() ) + { + // pick a region-specific flinch + case HITGROUP_HEAD: + flinchActivity = bGesture ? ACT_GESTURE_FLINCH_HEAD : ACT_FLINCH_HEAD; + break; + case HITGROUP_STOMACH: + flinchActivity = bGesture ? ACT_GESTURE_FLINCH_STOMACH : ACT_FLINCH_STOMACH; + break; + case HITGROUP_LEFTARM: + flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTARM : ACT_FLINCH_LEFTARM; + break; + case HITGROUP_RIGHTARM: + flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTARM : ACT_FLINCH_RIGHTARM; + break; + case HITGROUP_LEFTLEG: + flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTLEG : ACT_FLINCH_LEFTLEG; + break; + case HITGROUP_RIGHTLEG: + flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTLEG : ACT_FLINCH_RIGHTLEG; + break; + case HITGROUP_CHEST: + flinchActivity = bGesture ? ACT_GESTURE_FLINCH_CHEST : ACT_FLINCH_CHEST; + break; + case HITGROUP_GEAR: + case HITGROUP_GENERIC: + default: + // just get a generic flinch. + if ( bHeavyDamage ) + { + flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH; + } + else + { + flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH; + } + break; + } + + // do we have a sequence for the ideal activity? + if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + if ( bHeavyDamage ) + { + flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH; + + // If we fail at finding a big flinch, resort to a small one + if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH; + } + } + else + { + flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH; + } + } + + return flinchActivity; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput ) +{ + if ( !m_bDidDeathCleanup ) + { + m_bDidDeathCleanup = true; + + if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine ) + { + // bail out of this script here + m_hCine->CancelScript(); + // now keep going with the death code + } + + if ( GetHintNode() ) + { + GetHintNode()->Unlock(); + SetHintNode( NULL ); + } + + if( bFireDeathOutput ) + { + m_OnDeath.FireOutput( pCulprit, this ); + } + + // Vacate any strategy slot I might have + VacateStrategySlot(); + + // Remove from squad if in one + if (m_pSquad) + { + // If I'm in idle it means that I didn't see who killed me + // and my squad is still in idle state. Tell squad we have + // an enemy to wake them up and put the enemy position at + // my death position + if ( m_NPCState == NPC_STATE_IDLE && pCulprit) + { + // If we already have some danger memory, don't do this cheat + if ( GetEnemies()->GetDangerMemory() == NULL ) + { + UpdateEnemyMemory( pCulprit, GetAbsOrigin() ); + } + } + + // Remove from squad + m_pSquad->RemoveFromSquad(this, true); + m_pSquad = NULL; + } + + RemoveActorFromScriptedScenes( this, false /*all scenes*/ ); + } + else + DevMsg( "Unexpected double-death-cleanup\n" ); +} + +void CAI_BaseNPC::SelectDeathPose( const CTakeDamageInfo &info ) +{ + if ( !GetModelPtr() || (info.GetDamageType() & DMG_PREVENT_PHYSICS_FORCE) ) + return; + + if ( ShouldPickADeathPose() == false ) + return; + + Activity aActivity = ACT_INVALID; + int iDeathFrame = 0; + + SelectDeathPoseActivityAndFrame( this, info, LastHitGroup(), aActivity, iDeathFrame ); + if ( aActivity == ACT_INVALID ) + { + SetDeathPose( ACT_INVALID ); + SetDeathPoseFrame( 0 ); + return; + } + + SetDeathPose( SelectWeightedSequence( aActivity ) ); + SetDeathPoseFrame( iDeathFrame ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::Event_Killed( const CTakeDamageInfo &info ) +{ + if (IsCurSchedule(SCHED_NPC_FREEZE)) + { + // We're frozen; don't die. + return; + } + + Wake( false ); + + //Adrian: Select a death pose to extrapolate the ragdoll's velocity. + SelectDeathPose( info ); + + m_lifeState = LIFE_DYING; + + CleanupOnDeath( info.GetAttacker() ); + + StopLoopingSounds(); + DeathSound( info ); + + if ( ( GetFlags() & FL_NPC ) && ( ShouldGib( info ) == false ) ) + { + SetTouch( NULL ); + } + + BaseClass::Event_Killed( info ); + + + if ( m_bFadeCorpse ) + { + m_bImportanRagdoll = RagdollManager_SaveImportant( this ); + } + + // Make sure this condition is fired too (OnTakeDamage breaks out before this happens on death) + SetCondition( COND_LIGHT_DAMAGE ); + SetIdealState( NPC_STATE_DEAD ); + + // Some characters rely on getting a state transition, even to death. + // zombies, for instance. When a character becomes a ragdoll, their + // server entity ceases to think, so we have to set the dead state here + // because the AI code isn't going to pick up the change on the next think + // for us. + + // Adrian - Only set this if we are going to become a ragdoll. We still want to + // select SCHED_DIE or do something special when this NPC dies and we wont + // catch the change of state if we set this to whatever the ideal state is. + if ( CanBecomeRagdoll() || IsRagdoll() ) + SetState( NPC_STATE_DEAD ); + + // If the remove-no-ragdoll flag is set in the damage type, we're being + // told to remove ourselves immediately on death. This is used when something + // else has some special reason for us to vanish instead of creating a ragdoll. + // i.e. The barnacle does this because it's already got a ragdoll for us. + if ( info.GetDamageType() & DMG_REMOVENORAGDOLL ) + { + if ( !IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) ) + { + // Go away + RemoveDeferred(); + } + } +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner ) +{ + BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner ); + +#ifdef HL2_EPISODIC + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if ( pPlayer->IRelationType( this ) != D_LI ) + { + CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx(); + + if ( alyx ) + { + alyx->EnemyIgnited( this ); + } + } +#endif +} + +//----------------------------------------------------------------------------- + +ConVar ai_block_damage( "ai_block_damage","0" ); + +bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info ) +{ + if ( ai_block_damage.GetBool() ) + return false; + // FIXME: hook a friendly damage filter to the npc instead? + if ( (CapabilitiesGet() & bits_CAP_FRIENDLY_DMG_IMMUNE) && info.GetAttacker() && info.GetAttacker() != this ) + { + // check attackers relationship with me + CBaseCombatCharacter *npcEnemy = info.GetAttacker()->MyCombatCharacterPointer(); + bool bHitByVehicle = false; + if ( !npcEnemy ) + { + if ( info.GetAttacker()->GetServerVehicle() ) + { + bHitByVehicle = true; + } + } + + if ( bHitByVehicle || (npcEnemy && npcEnemy->IRelationType( this ) == D_LI) ) + { + m_fNoDamageDecal = true; + + if ( npcEnemy && npcEnemy->IsPlayer() ) + { + m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); + // This also counts as being harmed by player's squad. + m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + } + + return false; + } + } + + if ( !BaseClass::PassesDamageFilter( info ) ) + { + m_fNoDamageDecal = true; + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +int CAI_BaseNPC::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + Forget( bits_MEMORY_INCOVER ); + + if ( !BaseClass::OnTakeDamage_Alive( info ) ) + return 0; + + if ( GetSleepState() == AISS_WAITING_FOR_THREAT ) + Wake(); + + // NOTE: This must happen after the base class is called; we need to reduce + // health before the pain sound, since some NPCs use the final health + // level as a modifier to determine which pain sound to use. + + // REVISIT: Combine soldiers shoot each other a lot and then talk about it + // this improves that case a bunch, but it seems kind of harsh. + if ( !m_pSquad || !m_pSquad->SquadIsMember( info.GetAttacker() ) ) + { + PainSound( info );// "Ouch!" + } + + // See if we're running a dynamic interaction that should break when I am damaged. + if ( IsActiveDynamicInteraction() ) + { + ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction(); + if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_DAMAGE ) + { + // Can only break when we're in the action anim + if ( m_hCine->IsPlayingAction() ) + { + m_hCine->StopActionLoop( true ); + } + } + } + + // If we're not allowed to die, refuse to die + // Allow my interaction partner to kill me though + if ( m_iHealth <= 0 && HasInteractionCantDie() && info.GetAttacker() != m_hInteractionPartner ) + { + m_iHealth = 1; + } + +#if 0 + // HACKHACK Don't kill npcs in a script. Let them break their scripts first + // THIS is a Half-Life 1 hack that's not cutting the mustard in the scripts + // that have been authored for Half-Life 2 thus far. (sjb) + if ( m_NPCState == NPC_STATE_SCRIPT ) + { + SetCondition( COND_LIGHT_DAMAGE ); + } +#endif + + // ----------------------------------- + // Fire outputs + // ----------------------------------- + if ( m_flLastDamageTime != gpGlobals->curtime ) + { + // only fire once per frame + m_OnDamaged.FireOutput( info.GetAttacker(), this); + + if( info.GetAttacker()->IsPlayer() ) + { + m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); + + // This also counts as being harmed by player's squad. + m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + } + else + { + // See if the person that injured me is an NPC. + CAI_BaseNPC *pAttacker = dynamic_cast( info.GetAttacker() ); + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + + if( pAttacker && pAttacker->IsAlive() && pPlayer ) + { + if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() ) + { + m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + } + } + } + } + + if( (info.GetDamageType() & DMG_CRUSH) && !(info.GetDamageType() & DMG_PHYSGUN) && info.GetDamage() >= MIN_PHYSICS_FLINCH_DAMAGE ) + { + SetCondition( COND_PHYSICS_DAMAGE ); + } + + if ( m_iHealth <= ( m_iMaxHealth / 2 ) ) + { + m_OnHalfHealth.FireOutput( info.GetAttacker(), this ); + } + + // react to the damage (get mad) + if ( ( (GetFlags() & FL_NPC) == 0 ) || !info.GetAttacker() ) + return 1; + + // If the attacker was an NPC or client update my position memory + if ( info.GetAttacker()->GetFlags() & (FL_NPC | FL_CLIENT) ) + { + // ------------------------------------------------------------------ + // DO NOT CHANGE THIS CODE W/O CONSULTING + // Only update information about my attacker I don't see my attacker + // ------------------------------------------------------------------ + if ( !FInViewCone( info.GetAttacker() ) || !FVisible( info.GetAttacker() ) ) + { + // ------------------------------------------------------------- + // If I have an inflictor (enemy / grenade) update memory with + // position of inflictor, otherwise update with an position + // estimate for where the attack came from + // ------------------------------------------------------ + Vector vAttackPos; + if (info.GetInflictor()) + { + vAttackPos = info.GetInflictor()->GetAbsOrigin(); + } + else + { + vAttackPos = (GetAbsOrigin() + ( g_vecAttackDir * 64 )); + } + + + // ---------------------------------------------------------------- + // If I already have an enemy, assume that the attack + // came from the enemy and update my enemy's position + // unless I already know about the attacker or I can see my enemy + // ---------------------------------------------------------------- + if ( GetEnemy() != NULL && + !GetEnemies()->HasMemory( info.GetAttacker() ) && + !HasCondition(COND_SEE_ENEMY) ) + { + UpdateEnemyMemory(GetEnemy(), vAttackPos, GetEnemy()); + } + // ---------------------------------------------------------------- + // If I already know about this enemy, update his position + // ---------------------------------------------------------------- + else if (GetEnemies()->HasMemory( info.GetAttacker() )) + { + UpdateEnemyMemory(info.GetAttacker(), vAttackPos); + } + // ----------------------------------------------------------------- + // Otherwise just note the position, but don't add enemy to my list + // ----------------------------------------------------------------- + else + { + UpdateEnemyMemory(NULL, vAttackPos); + } + } + + // add pain to the conditions + if ( IsLightDamage( info ) ) + { + SetCondition( COND_LIGHT_DAMAGE ); + } + if ( IsHeavyDamage( info ) ) + { + SetCondition( COND_HEAVY_DAMAGE ); + } + + ForceGatherConditions(); + + // Keep track of how much consecutive damage I have recieved + if ((gpGlobals->curtime - m_flLastDamageTime) < 1.0) + { + m_flSumDamage += info.GetDamage(); + } + else + { + m_flSumDamage = info.GetDamage(); + } + m_flLastDamageTime = gpGlobals->curtime; + if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() ) + m_flLastPlayerDamageTime = gpGlobals->curtime; + GetEnemies()->OnTookDamageFrom( info.GetAttacker() ); + + if (m_flSumDamage > m_iMaxHealth*0.3) + { + SetCondition(COND_REPEATED_DAMAGE); + } + + NotifyFriendsOfDamage( info.GetAttacker() ); + } + + // --------------------------------------------------------------- + // Insert a combat sound so that nearby NPCs know I've been hit + // --------------------------------------------------------------- + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1024, 0.5, this, SOUNDENT_CHANNEL_INJURY ); + + return 1; +} + + +//========================================================= +// OnTakeDamage_Dying - takedamage function called when a npc's +// corpse is damaged. +//========================================================= +int CAI_BaseNPC::OnTakeDamage_Dying( const CTakeDamageInfo &info ) +{ + if ( info.GetDamageType() & DMG_PLASMA ) + { + if ( m_takedamage != DAMAGE_EVENTS_ONLY ) + { + m_iHealth -= info.GetDamage(); + + if (m_iHealth < -500) + { + UTIL_Remove(this); + } + } + } + return BaseClass::OnTakeDamage_Dying( info ); +} + +//========================================================= +// OnTakeDamage_Dead - takedamage function called when a npc's +// corpse is damaged. +//========================================================= +int CAI_BaseNPC::OnTakeDamage_Dead( const CTakeDamageInfo &info ) +{ + 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 = vec3_origin; + if ( info.GetInflictor() ) + { + vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter(); + VectorNormalize( vecDir ); + g_vecAttackDir = vecDir; + } + +#if 0// turn this back on when the bounding box issues are resolved. + + SetGroundEntity( NULL ); + GetLocalOrigin().z += 1; + + // let the damage scoot the corpse around a bit. + if ( info.GetInflictor() && (info.GetAttacker()->GetSolid() != SOLID_TRIGGER) ) + { + ApplyAbsVelocityImpulse( 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 ( info.GetDamageType() & DMG_GIB_CORPSE ) + { + // Accumulate corpse gibbing damage, so you can gib with multiple hits + if ( m_takedamage != DAMAGE_EVENTS_ONLY ) + { + m_iHealth -= info.GetDamage() * 0.1; + } + } + + if ( info.GetDamageType() & DMG_PLASMA ) + { + if ( m_takedamage != DAMAGE_EVENTS_ONLY ) + { + m_iHealth -= info.GetDamage(); + + if (m_iHealth < -500) + { + UTIL_Remove(this); + } + } + } + + return 1; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity ) +{ + CAI_BaseNPC *pAttacker = pAttackerEntity->MyNPCPointer(); + if ( pAttacker ) + { + const Vector &origin = GetAbsOrigin(); + for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + const float NEAR_Z = 10*12; + const float NEAR_XY_SQ = Square( 50*12 ); + CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i]; + if ( pNpc && pNpc != this ) + { + const Vector &originNpc = pNpc->GetAbsOrigin(); + if ( fabsf( originNpc.z - origin.z ) < NEAR_Z ) + { + if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ ) + { + if ( pNpc->GetSquad() == GetSquad() || IRelationType( pNpc ) == D_LI ) + pNpc->OnFriendDamaged( this, pAttacker ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity *pAttacker ) +{ + if ( GetSleepState() != AISS_WAITING_FOR_INPUT ) + { + float distSqToThreat = ( GetAbsOrigin() - pAttacker->GetAbsOrigin() ).LengthSqr(); + + if ( GetSleepState() != AISS_AWAKE && distSqToThreat < Square( 20 * 12 ) ) + Wake(); + + if ( distSqToThreat < Square( 50 * 12 ) ) + ForceGatherConditions(); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsLightDamage( const CTakeDamageInfo &info ) +{ + // ALL nonzero damage is light damage! Mask off COND_LIGHT_DAMAGE if you want to ignore light damage. + return ( info.GetDamage() > 0 ); +} + +bool CAI_BaseNPC::IsHeavyDamage( const CTakeDamageInfo &info ) +{ + return ( info.GetDamage() > 20 ); +} + +void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, int iClassIgnore, CBaseEntity *pEntityIgnore ) +{ + RadiusDamage( info, GetAbsOrigin(), info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore ); +} + + +void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrc, int iClassIgnore, CBaseEntity *pEntityIgnore ) +{ + RadiusDamage( info, vecSrc, info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore ); +} + + +//----------------------------------------------------------------------------- +// Set the contents types that are solid by default to all NPCs +//----------------------------------------------------------------------------- +unsigned int CAI_BaseNPC::PhysicsSolidMaskForEntity( void ) const +{ + return MASK_NPCSOLID; +} + + +//========================================================= + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::DecalTrace( trace_t *pTrace, char const *decalName ) +{ + if ( m_fNoDamageDecal ) + { + m_fNoDamageDecal = false; + // @Note (toml 04-23-03): e3, don't decal face on damage if still alive + return; + } + BaseClass::DecalTrace( pTrace, decalName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName ) +{ + if ( m_fNoDamageDecal ) + { + m_fNoDamageDecal = false; + // @Note (toml 04-23-03): e3, don't decal face on damage if still alive + return; + } + BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName ); +} + +//--------------------------------------------------------- +// Return the number by which to multiply incoming damage +// based on the hitgroup it hits. This exists mainly so +// that individual NPC's can have more or less resistance +// to damage done to certain hitgroups. +//--------------------------------------------------------- +float CAI_BaseNPC::GetHitgroupDamageMultiplier( int iHitGroup, const CTakeDamageInfo &info ) +{ + switch( iHitGroup ) + { + case HITGROUP_GENERIC: + return 1.0f; + + case HITGROUP_HEAD: + return sk_npc_head.GetFloat(); + + case HITGROUP_CHEST: + return sk_npc_chest.GetFloat(); + + case HITGROUP_STOMACH: + return sk_npc_stomach.GetFloat(); + + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + return sk_npc_arm.GetFloat(); + + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + return sk_npc_leg.GetFloat(); + + default: + return 1.0f; + } +} + +//========================================================= +// TraceAttack +//========================================================= +void CAI_BaseNPC::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) +{ + m_fNoDamageDecal = false; + if ( m_takedamage == DAMAGE_NO ) + return; + + CTakeDamageInfo subInfo = info; + + SetLastHitGroup( ptr->hitgroup ); + m_nForceBone = ptr->physicsbone; // save this bone for physics forces + + Assert( m_nForceBone > -255 && m_nForceBone < 256 ); + + bool bDebug = showhitlocation.GetBool(); + + switch ( ptr->hitgroup ) + { + case HITGROUP_GENERIC: + if( bDebug ) DevMsg("Hit Location: Generic\n"); + break; + + // hit gear, react but don't bleed + case HITGROUP_GEAR: + subInfo.SetDamage( 0.01 ); + ptr->hitgroup = HITGROUP_GENERIC; + if( bDebug ) DevMsg("Hit Location: Gear\n"); + break; + + case HITGROUP_HEAD: + subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); + if( bDebug ) DevMsg("Hit Location: Head\n"); + break; + + case HITGROUP_CHEST: + subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); + if( bDebug ) DevMsg("Hit Location: Chest\n"); + break; + + case HITGROUP_STOMACH: + subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); + if( bDebug ) DevMsg("Hit Location: Stomach\n"); + break; + + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); + if( bDebug ) DevMsg("Hit Location: Left/Right Arm\n"); + break + ; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) ); + if( bDebug ) DevMsg("Hit Location: Left/Right Leg\n"); + break; + + default: + if( bDebug ) DevMsg("Hit Location: UNKNOWN\n"); + break; + } + + if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK ) ) + { + if( !IsPlayer() || ( IsPlayer() && g_pGameRules->IsMultiplayer() ) ) + { + // NPC's always bleed. Players only bleed in multiplayer. + SpawnBlood( ptr->endpos, vecDir, BloodColor(), subInfo.GetDamage() );// a little surface blood. + } + + TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() ); + + if ( ptr->hitgroup == HITGROUP_HEAD && m_iHealth - subInfo.GetDamage() > 0 ) + { + m_fNoDamageDecal = true; + } + } + + // Airboat gun will impart major force if it's about to kill him.... + if ( info.GetDamageType() & DMG_AIRBOAT ) + { + if ( subInfo.GetDamage() >= GetHealth() ) + { + float flMagnitude = subInfo.GetDamageForce().Length(); + if ( (flMagnitude != 0.0f) && (flMagnitude < 400.0f * 65.0f) ) + { + subInfo.ScaleDamageForce( 400.0f * 65.0f / flMagnitude ); + } + } + } + + if( info.GetInflictor() ) + { + subInfo.SetInflictor( info.GetInflictor() ); + } + else + { + subInfo.SetInflictor( info.GetAttacker() ); + } + + AddMultiDamage( subInfo, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Checks if point is in spread angle between source and target Pos +// Used to prevent friendly fire +// Input : Source of attack, target position, spread angle +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::PointInSpread( CBaseCombatCharacter *pCheckEntity, const Vector &sourcePos, const Vector &targetPos, const Vector &testPoint, float flSpread, float maxDistOffCenter ) +{ + float distOffLine = CalcDistanceToLine2D( testPoint.AsVector2D(), sourcePos.AsVector2D(), targetPos.AsVector2D() ); + if ( distOffLine < maxDistOffCenter ) + { + Vector toTarget = targetPos - sourcePos; + float distTarget = VectorNormalize(toTarget); + + Vector toTest = testPoint - sourcePos; + float distTest = VectorNormalize(toTest); + // Only reject if target is on other side + if (distTarget > distTest) + { + toTarget.z = 0.0; + toTest.z = 0.0; + + float dotProduct = DotProduct(toTarget,toTest); + if (dotProduct > flSpread) + { + return true; + } + else if( dotProduct > 0.0f ) + { + // If this guy is in front, do the hull/line test: + if( pCheckEntity ) + { + float flBBoxDist = NAI_Hull::Width( pCheckEntity->GetHullType() ); + flBBoxDist *= 1.414f; // sqrt(2) + + // !!!BUGBUG - this 2d check will stop a citizen shooting at a gunship or strider + // if another citizen is between them, even though the strider or gunship may be + // high up in the air (sjb) + distOffLine = CalcDistanceToLine( testPoint, sourcePos, targetPos ); + if( distOffLine < flBBoxDist ) + { + return true; + } + } + } + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Checks if player is in spread angle between source and target Pos +// Used to prevent friendly fire +// Input : Source of attack, target position, spread angle +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::PlayerInSpread( const Vector &sourcePos, const Vector &targetPos, float flSpread, float maxDistOffCenter, bool ignoreHatedPlayers ) +{ + // loop through all players + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) != D_HT ) ) + { + if ( PointInSpread( pPlayer, sourcePos, targetPos, pPlayer->WorldSpaceCenter(), flSpread, maxDistOffCenter ) ) + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Checks if player is in range of given location. Used by NPCs +// to prevent friendly fire +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *CAI_BaseNPC::PlayerInRange( const Vector &vecLocation, float flDist ) +{ + // loop through all players + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if (pPlayer && (vecLocation - pPlayer->WorldSpaceCenter() ).Length2D() <= flDist) + { + return pPlayer; + } + } + return NULL; +} + + +#define BULLET_WIZZDIST 80.0 +#define SLOPE ( -1.0 / BULLET_WIZZDIST ) + +void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer ) +{ + CBasePlayer *pPlayer; + Vector vecBulletPath; + Vector vecPlayerPath; + Vector vecBulletDir; + Vector vecNearestPoint; + float flDist; + float flBulletDist; + + vecBulletPath = vecEndPos - vecSrc; + vecBulletDir = vecBulletPath; + VectorNormalize(vecBulletDir); + + // see how near this bullet passed by player in a single player game + // for multiplayer, we need to go through the list of clients. + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + // Don't hear one's own bullets + if( pPlayer->edict() == pShooter ) + continue; + + vecPlayerPath = pPlayer->EarPosition() - vecSrc; + flDist = DotProduct( vecPlayerPath, vecBulletDir ); + vecNearestPoint = vecSrc + vecBulletDir * flDist; + // FIXME: minus m_vecViewOffset? + flBulletDist = ( vecNearestPoint - pPlayer->EarPosition() ).Length(); + } +} + +//----------------------------------------------------------------------------- +// Hits triggers with raycasts +//----------------------------------------------------------------------------- +class CTriggerTraceEnum : public IEntityEnumerator +{ +public: + CTriggerTraceEnum( Ray_t *pRay, const CTakeDamageInfo &info, const Vector& dir, int contentsMask ) : + m_info( info ), m_VecDir(dir), m_ContentsMask(contentsMask), m_pRay(pRay) + { + } + + virtual bool EnumEntity( IHandleEntity *pHandleEntity ) + { + trace_t tr; + + CBaseEntity *pEnt = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); + + // Done to avoid hitting an entity that's both solid & a trigger. + if ( pEnt->IsSolid() ) + return true; + + enginetrace->ClipRayToEntity( *m_pRay, m_ContentsMask, pHandleEntity, &tr ); + if (tr.fraction < 1.0f) + { + pEnt->DispatchTraceAttack( m_info, m_VecDir, &tr ); + ApplyMultiDamage(); + } + + return true; + } + +private: + CTakeDamageInfo m_info; + Vector m_VecDir; + int m_ContentsMask; + Ray_t *m_pRay; +}; + +void CBaseEntity::TraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir ) +{ + Ray_t ray; + ray.Init( start, end ); + + CTriggerTraceEnum triggerTraceEnum( &ray, info, dir, MASK_SHOT ); + enginetrace->EnumerateEntities( ray, true, &triggerTraceEnum ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CAI_BaseNPC::GetTracerType( void ) +{ + if ( GetActiveWeapon() ) + { + return GetActiveWeapon()->GetTracerType(); + } + + return BaseClass::GetTracerType(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecTracerSrc - +// &tr - +// iTracerType - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ) +{ + if ( GetActiveWeapon() ) + { + GetActiveWeapon()->MakeTracer( vecTracerSrc, tr, iTracerType ); + return; + } + + BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::FireBullets( const FireBulletsInfo_t &info ) +{ +#ifdef HL2_DLL + // If we're shooting at a bullseye, become perfectly accurate if the bullseye demands it + if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE ) + { + CNPC_Bullseye *pBullseye = dynamic_cast(GetEnemy()); + if ( pBullseye && pBullseye->UsePerfectAccuracy() ) + { + FireBulletsInfo_t accurateInfo = info; + accurateInfo.m_vecSpread = vec3_origin; + BaseClass::FireBullets( accurateInfo ); + return; + } + } +#endif + + BaseClass::FireBullets( info ); +} + + +//----------------------------------------------------------------------------- +// Shot statistics +//----------------------------------------------------------------------------- +void CBaseEntity::UpdateShotStatistics( const trace_t &tr ) +{ + if ( ai_shot_stats.GetBool() ) + { + CAI_BaseNPC *pNpc = MyNPCPointer(); + if ( pNpc ) + { + pNpc->m_TotalShots++; + if ( tr.m_pEnt == pNpc->GetEnemy() ) + { + pNpc->m_TotalHits++; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Handle shot entering water +//----------------------------------------------------------------------------- +void CBaseEntity::HandleShotImpactingGlass( const FireBulletsInfo_t &info, + const trace_t &tr, const Vector &vecDir, ITraceFilter *pTraceFilter ) +{ + // Move through the glass until we're at the other side + Vector testPos = tr.endpos + ( vecDir * MAX_GLASS_PENETRATION_DEPTH ); + + CEffectData data; + + data.m_vNormal = tr.plane.normal; + data.m_vOrigin = tr.endpos; + + DispatchEffect( "GlassImpact", data ); + + trace_t penetrationTrace; + + // Re-trace as if the bullet had passed right through + UTIL_TraceLine( testPos, tr.endpos, MASK_SHOT, pTraceFilter, &penetrationTrace ); + + // See if we found the surface again + if ( penetrationTrace.startsolid || tr.fraction == 0.0f || penetrationTrace.fraction == 1.0f ) + return; + + //FIXME: This is technically frustrating MultiDamage, but multiple shots hitting multiple targets in one call + // would do exactly the same anyway... + + // Impact the other side (will look like an exit effect) + DoImpactEffect( penetrationTrace, GetAmmoDef()->DamageType(info.m_iAmmoType) ); + + data.m_vNormal = penetrationTrace.plane.normal; + data.m_vOrigin = penetrationTrace.endpos; + + DispatchEffect( "GlassImpact", data ); + + // Refire the round, as if starting from behind the glass + FireBulletsInfo_t behindGlassInfo; + behindGlassInfo.m_iShots = 1; + behindGlassInfo.m_vecSrc = penetrationTrace.endpos; + behindGlassInfo.m_vecDirShooting = vecDir; + behindGlassInfo.m_vecSpread = vec3_origin; + behindGlassInfo.m_flDistance = info.m_flDistance*( 1.0f - tr.fraction ); + behindGlassInfo.m_iAmmoType = info.m_iAmmoType; + behindGlassInfo.m_iTracerFreq = info.m_iTracerFreq; + behindGlassInfo.m_iDamage = info.m_iDamage; + behindGlassInfo.m_pAttacker = info.m_pAttacker ? info.m_pAttacker : this; + behindGlassInfo.m_nFlags = info.m_nFlags; + + FireBullets( behindGlassInfo ); +} + + +//----------------------------------------------------------------------------- +// Computes the tracer start position +//----------------------------------------------------------------------------- +#define SHOT_UNDERWATER_BUBBLE_DIST 400 + +void CBaseEntity::CreateBubbleTrailTracer( const Vector &vecShotSrc, const Vector &vecShotEnd, const Vector &vecShotDir ) +{ + int nBubbles; + Vector vecBubbleEnd; + float flLengthSqr = vecShotSrc.DistToSqr( vecShotEnd ); + if ( flLengthSqr > SHOT_UNDERWATER_BUBBLE_DIST * SHOT_UNDERWATER_BUBBLE_DIST ) + { + VectorMA( vecShotSrc, SHOT_UNDERWATER_BUBBLE_DIST, vecShotDir, vecBubbleEnd ); + nBubbles = static_cast(WATER_BULLET_BUBBLES_PER_INCH * SHOT_UNDERWATER_BUBBLE_DIST); + } + else + { + float flLength = sqrt(flLengthSqr) - 0.1f; + nBubbles = static_cast(WATER_BULLET_BUBBLES_PER_INCH * flLength); + VectorMA( vecShotSrc, flLength, vecShotDir, vecBubbleEnd ); + } + + Vector vecTracerSrc; + ComputeTracerStartPosition( vecShotSrc, &vecTracerSrc ); + UTIL_BubbleTrail( vecTracerSrc, vecBubbleEnd, nBubbles ); +} + + +//========================================================= +//========================================================= +void CAI_BaseNPC::MakeDamageBloodDecal ( int cCount, float flNoise, trace_t *ptr, Vector vecDir ) +{ + // make blood decal on the wall! + trace_t Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead npc. + if ( m_iMaxHealth <= 0 ) + { + // no blood decal for a npc that has already decalled its limit. + return; + } + else + { + m_iMaxHealth -= 1; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += random->RandomFloat( -flNoise, flNoise ); + vecTraceDir.y += random->RandomFloat( -flNoise, flNoise ); + vecTraceDir.z += random->RandomFloat( -flNoise, flNoise ); + + AI_TraceLine( ptr->endpos, ptr->endpos + vecTraceDir * 172, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &Bloodtr); + + if ( Bloodtr.fraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &tr - +// nDamageType - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::DoImpactEffect( trace_t &tr, int nDamageType ) +{ + if ( GetActiveWeapon() != NULL ) + { + GetActiveWeapon()->DoImpactEffect( tr, nDamageType ); + return; + } + + BaseClass::DoImpactEffect( tr, nDamageType ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +#define InterruptFromCondition( iCondition ) \ + AI_RemapFromGlobal( ( AI_IdIsLocal( iCondition ) ? GetClassScheduleIdSpace()->ConditionLocalToGlobal( iCondition ) : iCondition ) ) + +void CAI_BaseNPC::SetCondition( int iCondition ) +{ + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return; + } + + m_Conditions.SetBit( interrupt ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CAI_BaseNPC::HasCondition( int iCondition ) +{ + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return false; + } + + bool bReturn = m_Conditions.GetBit(interrupt); + return (bReturn); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CAI_BaseNPC::HasCondition( int iCondition, bool bUseIgnoreConditions ) +{ + if ( bUseIgnoreConditions ) + return HasCondition( iCondition ); + + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return false; + } + + bool bReturn = m_ConditionsPreIgnore.GetBit(interrupt); + return (bReturn); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CAI_BaseNPC::ClearCondition( int iCondition ) +{ + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return; + } + + m_Conditions.ClearBit(interrupt); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CAI_BaseNPC::ClearConditions( int *pConditions, int nConditions ) +{ + for ( int i = 0; i < nConditions; ++i ) + { + int iCondition = pConditions[i]; + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + continue; + } + + m_Conditions.ClearBit( interrupt ); + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CAI_BaseNPC::SetIgnoreConditions( int *pConditions, int nConditions ) +{ + for ( int i = 0; i < nConditions; ++i ) + { + int iCondition = pConditions[i]; + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + continue; + } + + m_InverseIgnoreConditions.ClearBit( interrupt ); // clear means ignore + } +} + +void CAI_BaseNPC::ClearIgnoreConditions( int *pConditions, int nConditions ) +{ + for ( int i = 0; i < nConditions; ++i ) + { + int iCondition = pConditions[i]; + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + continue; + } + + m_InverseIgnoreConditions.SetBit( interrupt ); // set means don't ignore + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CAI_BaseNPC::HasInterruptCondition( int iCondition ) +{ + if( !GetCurSchedule() ) + { + return false; + } + + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return false; + } + return ( m_Conditions.GetBit( interrupt ) && GetCurSchedule()->HasInterrupt( interrupt ) ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CAI_BaseNPC::ConditionInterruptsCurSchedule( int iCondition ) +{ + if( !GetCurSchedule() ) + { + return false; + } + + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return false; + } + return ( GetCurSchedule()->HasInterrupt( interrupt ) ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CAI_BaseNPC::ConditionInterruptsSchedule( int localScheduleID, int iCondition ) +{ + CAI_Schedule *pSchedule = GetSchedule( localScheduleID ); + if ( !pSchedule ) + return false; + + int interrupt = InterruptFromCondition( iCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return false; + } + return ( pSchedule->HasInterrupt( interrupt ) ); +} + + +//----------------------------------------------------------------------------- +// Returns whether we currently have any interrupt conditions that would +// interrupt the given schedule. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::HasConditionsToInterruptSchedule( int nLocalScheduleID ) +{ + CAI_Schedule *pSchedule = GetSchedule( nLocalScheduleID ); + if ( !pSchedule ) + return false; + + CAI_ScheduleBits bitsMask; + pSchedule->GetInterruptMask( &bitsMask ); + + CAI_ScheduleBits bitsOut; + AccessConditionBits().And( bitsMask, &bitsOut ); + + return !bitsOut.IsAllClear(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsCustomInterruptConditionSet( int nCondition ) +{ + int interrupt = InterruptFromCondition( nCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return false; + } + + return m_CustomInterruptConditions.GetBit( interrupt ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets a flag in the custom interrupt flags, translating the condition +// to the proper global space, if necessary +//----------------------------------------------------------------------------- +void CAI_BaseNPC::SetCustomInterruptCondition( int nCondition ) +{ + int interrupt = InterruptFromCondition( nCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return; + } + + m_CustomInterruptConditions.SetBit( interrupt ); +} + +//----------------------------------------------------------------------------- +// Purpose: Clears a flag in the custom interrupt flags, translating the condition +// to the proper global space, if necessary +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ClearCustomInterruptCondition( int nCondition ) +{ + int interrupt = InterruptFromCondition( nCondition ); + + if ( interrupt == -1 ) + { + Assert(0); + return; + } + + m_CustomInterruptConditions.ClearBit( interrupt ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Clears all the custom interrupt flags. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ClearCustomInterruptConditions() +{ + m_CustomInterruptConditions.ClearAllBits(); +} + + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::SetDistLook( float flDistLook ) +{ + m_pSenses->SetDistLook( flDistLook ); +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::QueryHearSound( CSound *pSound ) +{ + if ( pSound->SoundContext() & SOUND_CONTEXT_COMBINE_ONLY ) + return false; + + if ( pSound->SoundContext() & SOUND_CONTEXT_ALLIES_ONLY ) + { + if ( !IsPlayerAlly() ) + return false; + } + + if ( pSound->IsSoundType( SOUND_PLAYER ) && GetState() == NPC_STATE_IDLE && !FVisible( pSound->GetSoundReactOrigin() ) ) + { + // NPC's that are IDLE should disregard player movement sounds if they can't see them. + // This does not affect them hearing the player's weapon. + // !!!BUGBUG - this probably makes NPC's not hear doors opening, because doors opening put SOUND_PLAYER + // in the world, but the door's model will block the FVisible() trace and this code will then + // deduce that the sound can not be heard + return false; + } + + // Disregard footsteps from our own class type + if ( pSound->IsSoundType( SOUND_COMBAT ) && pSound->SoundChannel() == SOUNDENT_CHANNEL_NPC_FOOTSTEP ) + { + if ( pSound->m_hOwner && pSound->m_hOwner->ClassMatches( m_iClassname ) ) + return false; + } + + if( ShouldIgnoreSound( pSound ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC ) +{ + if ( bOnlyHateOrFearIfNPC && pEntity->IsNPC() ) + { + Disposition_t disposition = IRelationType( pEntity ); + return ( disposition == D_HT || disposition == D_FR ); + } + return true; +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::OnLooked( int iDistance ) +{ + // DON'T let visibility information from last frame sit around! + static int conditionsToClear[] = + { + COND_SEE_HATE, + COND_SEE_DISLIKE, + COND_SEE_ENEMY, + COND_SEE_FEAR, + COND_SEE_NEMESIS, + COND_SEE_PLAYER, + COND_LOST_PLAYER, + COND_ENEMY_WENT_NULL, + }; + + bool bHadSeePlayer = HasCondition(COND_SEE_PLAYER); + + ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); + + AISightIter_t iter; + CBaseEntity *pSightEnt; + + pSightEnt = GetSenses()->GetFirstSeenEntity( &iter ); + + while( pSightEnt ) + { + if ( pSightEnt->IsPlayer() ) + { + // if we see a client, remember that (mostly for scripted AI) + SetCondition(COND_SEE_PLAYER); + m_flLastSawPlayerTime = gpGlobals->curtime; + } + + Disposition_t relation = IRelationType( 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 ( relation != D_NU ) + { + if ( pSightEnt == GetEnemy() ) + { + // we know this ent is visible, so if it also happens to be our enemy, store that now. + SetCondition(COND_SEE_ENEMY); + } + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see npcs other than the Enemy. + switch ( relation ) + { + case D_HT: + { + int priority = IRelationPriority( pSightEnt ); + if (priority < 0) + { + SetCondition(COND_SEE_DISLIKE); + } + else if (priority > 10) + { + SetCondition(COND_SEE_NEMESIS); + } + else + { + SetCondition(COND_SEE_HATE); + } + UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin()); + break; + + } + case D_FR: + UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin()); + SetCondition(COND_SEE_FEAR); + break; + case D_LI: + case D_NU: + break; + default: + DevWarning( 2, "%s can't assess %s\n", GetClassname(), pSightEnt->GetClassname() ); + break; + } + } + + pSightEnt = GetSenses()->GetNextSeenEntity( &iter ); + } + + // Did we lose the player? + if ( bHadSeePlayer && !HasCondition(COND_SEE_PLAYER) ) + { + SetCondition(COND_LOST_PLAYER); + } +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::OnListened() +{ + AISoundIter_t iter; + + CSound *pCurrentSound; + + static int conditionsToClear[] = + { + COND_HEAR_DANGER, + COND_HEAR_COMBAT, + COND_HEAR_WORLD, + COND_HEAR_PLAYER, + COND_HEAR_THUMPER, + COND_HEAR_BUGBAIT, + COND_HEAR_PHYSICS_DANGER, + COND_HEAR_BULLET_IMPACT, + COND_HEAR_MOVE_AWAY, + + COND_NO_HEAR_DANGER, + + COND_SMELL, + }; + + ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); + + pCurrentSound = GetSenses()->GetFirstHeardSound( &iter ); + + while ( pCurrentSound ) + { + // the npc cares about this sound, and it's close enough to hear. + int condition = COND_NONE; + + if ( pCurrentSound->FIsSound() ) + { + // this is an audible sound. + switch( pCurrentSound->SoundTypeNoContext() ) + { + case SOUND_DANGER: + if( gpGlobals->curtime > m_flIgnoreDangerSoundsUntil) + condition = COND_HEAR_DANGER; + break; + + case SOUND_THUMPER: condition = COND_HEAR_THUMPER; break; + case SOUND_BUGBAIT: condition = COND_HEAR_BUGBAIT; break; + case SOUND_COMBAT: + if ( pCurrentSound->SoundChannel() == SOUNDENT_CHANNEL_SPOOKY_NOISE ) + { + condition = COND_HEAR_SPOOKY; + } + else + { + condition = COND_HEAR_COMBAT; + } + break; + + case SOUND_WORLD: condition = COND_HEAR_WORLD; break; + case SOUND_PLAYER: condition = COND_HEAR_PLAYER; break; + case SOUND_BULLET_IMPACT: condition = COND_HEAR_BULLET_IMPACT; break; + case SOUND_PHYSICS_DANGER: condition = COND_HEAR_PHYSICS_DANGER; break; + case SOUND_DANGER_SNIPERONLY:/* silence warning */ break; + case SOUND_MOVE_AWAY: condition = COND_HEAR_MOVE_AWAY; break; + case SOUND_PLAYER_VEHICLE: condition = COND_HEAR_PLAYER; break; + + default: + DevMsg( "**ERROR: NPC %s hearing sound of unknown type %d!\n", GetClassname(), pCurrentSound->SoundType() ); + break; + } + } + else + { + // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent + condition = COND_SMELL; + } + + if ( condition != COND_NONE ) + { + SetCondition( condition ); + } + + pCurrentSound = GetSenses()->GetNextHeardSound( &iter ); + } + + if( !HasCondition( COND_HEAR_DANGER ) ) + { + SetCondition( COND_NO_HEAR_DANGER ); + } + + // Sound outputs + if ( HasCondition( COND_HEAR_WORLD ) ) + { + m_OnHearWorld.FireOutput(this, this); + } + + if ( HasCondition( COND_HEAR_PLAYER ) ) + { + m_OnHearPlayer.FireOutput(this, this); + } + + if ( HasCondition( COND_HEAR_COMBAT ) || + HasCondition( COND_HEAR_BULLET_IMPACT ) || + HasCondition( COND_HEAR_DANGER ) ) + { + m_OnHearCombat.FireOutput(this, this); + } +} + +//========================================================= +// FValidateHintType - tells use whether or not the npc cares +// about the type of Hint Node given +//========================================================= +bool CAI_BaseNPC::FValidateHintType ( CAI_Hint *pHint ) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Override in subclasses to associate specific hint types +// with activities +//----------------------------------------------------------------------------- +Activity CAI_BaseNPC::GetHintActivity( short sHintType, Activity HintsActivity ) +{ + if ( HintsActivity != ACT_INVALID ) + return HintsActivity; + + return ACT_IDLE; +} + +//----------------------------------------------------------------------------- +// Purpose: Override in subclasses to give specific hint types delays +// before they can be used again +// Input : +// Output : +//----------------------------------------------------------------------------- +float CAI_BaseNPC::GetHintDelay( short sHintType ) +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Return incoming grenade if spotted +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseGrenade* CAI_BaseNPC::IncomingGrenade(void) +{ + int iDist; + + AIEnemiesIter_t iter; + for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) + { + CBaseGrenade* pBG = dynamic_cast((CBaseEntity*)pEMemory->hEnemy); + + // Make sure this memory is for a grenade and grenade is not dead + if (!pBG || pBG->m_lifeState == LIFE_DEAD) + continue; + + // Make sure it's visible + if (!FVisible(pBG)) + continue; + + // Check if it's near me + iDist = static_cast(( pBG->GetAbsOrigin() - GetAbsOrigin() ).Length()); + if ( iDist <= NPC_GRENADE_FEAR_DIST ) + return pBG; + + // Check if it's headed towards me + Vector vGrenadeDir = GetAbsOrigin() - pBG->GetAbsOrigin(); + Vector vGrenadeVel; + pBG->GetVelocity( &vGrenadeVel, NULL ); + VectorNormalize(vGrenadeDir); + VectorNormalize(vGrenadeVel); + float flDotPr = DotProduct(vGrenadeDir, vGrenadeVel); + if (flDotPr > 0.85) + return pBG; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Check my physical state with the environment +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::TryRestoreHull(void) +{ + if ( IsUsingSmallHull() && GetCurSchedule() ) + { + trace_t tr; + Vector vUpBit = GetAbsOrigin(); + vUpBit.z += 1; + + AI_TraceHull( GetAbsOrigin(), vUpBit, GetHullMins(), + GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + if ( !tr.startsolid && (tr.fraction == 1.0) ) + { + SetHullSizeNormal(); + } + } +} + +//========================================================= +//========================================================= +int CAI_BaseNPC::GetSoundInterests( void ) +{ + return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_PLAYER_VEHICLE | + SOUND_BULLET_IMPACT; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CAI_BaseNPC::GetSoundPriority( CSound *pSound ) +{ + int iSoundTypeNoContext = pSound->SoundTypeNoContext(); + int iSoundContext = pSound->SoundContext(); + + if( iSoundTypeNoContext & SOUND_DANGER ) + { + return SOUND_PRIORITY_HIGHEST; + } + + if( iSoundTypeNoContext & SOUND_COMBAT ) + { + if( iSoundContext & SOUND_CONTEXT_EXPLOSION ) + { + return SOUND_PRIORITY_VERY_HIGH; + } + + return SOUND_PRIORITY_HIGH; + } + + return SOUND_PRIORITY_NORMAL; +} + +//--------------------------------------------------------- +// Return the loudest sound of this type in the sound list +//--------------------------------------------------------- +CSound *CAI_BaseNPC::GetLoudestSoundOfType( int iType ) +{ + return CSoundEnt::GetLoudestSoundOfType( iType, EarPosition() ); +} + +//========================================================= +// GetBestSound - returns a pointer to the sound the npc +// should react to. Right now responds only to nearest sound. +//========================================================= +CSound* CAI_BaseNPC::GetBestSound( int validTypes ) +{ + if ( m_pLockedBestSound->m_iType != SOUND_NONE ) + return m_pLockedBestSound; + CSound *pResult = GetSenses()->GetClosestSound( false, validTypes ); + if ( pResult == NULL) + DevMsg( "Warning: NULL Return from GetBestSound\n" ); // condition previously set now no longer true. Have seen this when play too many sounds... + return pResult; +} + +//========================================================= +// PBestScent - returns a pointer to the scent the npc +// should react to. Right now responds only to nearest scent +//========================================================= +CSound* CAI_BaseNPC::GetBestScent( void ) +{ + CSound *pResult = GetSenses()->GetClosestSound( true ); + if ( pResult == NULL) + DevMsg("Warning: NULL Return from GetBestScent\n" ); + return pResult; +} + +//----------------------------------------------------------------------------- +void CAI_BaseNPC::LockBestSound() +{ + UnlockBestSound(); + CSound *pBestSound = GetBestSound(); + if ( pBestSound ) + *m_pLockedBestSound = *pBestSound; +} + +//----------------------------------------------------------------------------- +void CAI_BaseNPC::UnlockBestSound() +{ + if ( m_pLockedBestSound->m_iType != SOUND_NONE ) + { + m_pLockedBestSound->m_iType = SOUND_NONE; + OnListened(); // reset hearing conditions + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the specified sound is visible. Handles sounds generated by entities correctly. +// Input : *pSound - +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::SoundIsVisible( CSound *pSound ) +{ + CBaseEntity *pBlocker = NULL; + if ( !FVisible( pSound->GetSoundReactOrigin(), MASK_OPAQUE, &pBlocker ) ) + { + // Is the blocker the sound owner? + if ( pBlocker && pBlocker == pSound->m_hOwner ) + return true; + + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if target is in legal range of eye movements +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::ValidEyeTarget(const Vector &lookTargetPos) +{ + Vector vHeadDir = HeadDirection3D( ); + Vector lookTargetDir = lookTargetPos - EyePosition(); + VectorNormalize(lookTargetDir); + + // Only look if it doesn't crank my eyeballs too far + float dotPr = DotProduct(lookTargetDir, vHeadDir); + if (dotPr > 0.7) + { + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Integrate head turn over time +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::SetHeadDirection( const Vector &vTargetPos, float flInterval) +{ + if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD)) + return; + +#ifdef DEBUG_LOOK + // Draw line in body, head, and eye directions + Vector vEyePos = EyePosition(); + Vector vHeadDir; + HeadDirection3D(&vHeadDir); + Vector vBodyDir; + BodyDirection2D(&vBodyDir); + + //UNDONE <> + // currently eye dir just returns head dir, so use vTargetPos for now + //Vector vEyeDir; w + //EyeDirection3D(&vEyeDir); + NDebugOverlay::Line( vEyePos, vEyePos+(50*vHeadDir), 255, 0, 0, false, 0.1 ); + NDebugOverlay::Line( vEyePos, vEyePos+(50*vBodyDir), 0, 255, 0, false, 0.1 ); + NDebugOverlay::Line( vEyePos, vTargetPos, 0, 0, 255, false, 0.1 ); +#endif + + //-------------------------------------- + // Set head yaw + //-------------------------------------- + float flDesiredYaw = VecToYaw(vTargetPos - GetLocalOrigin()) - GetLocalAngles().y; + if (flDesiredYaw > 180) + flDesiredYaw -= 360; + if (flDesiredYaw < -180) + flDesiredYaw += 360; + + float iRate = 0.8; + + // Make frame rate independent + float timeToUse = flInterval; + while (timeToUse > 0) + { + m_flHeadYaw = (iRate * m_flHeadYaw) + (1-iRate)*flDesiredYaw; + timeToUse -= 0.1; + } + if (m_flHeadYaw > 360) m_flHeadYaw = 0; + + m_flHeadYaw = SetBoneController( 0, m_flHeadYaw ); + + + //-------------------------------------- + // Set head pitch + //-------------------------------------- + Vector vEyePosition = EyePosition(); + float fTargetDist = (vTargetPos - vEyePosition).Length(); + float fVertDist = vTargetPos.z - vEyePosition.z; + float flDesiredPitch = -RAD2DEG(atan(fVertDist/fTargetDist)); + + // Make frame rate independent + timeToUse = flInterval; + while (timeToUse > 0) + { + m_flHeadPitch = (iRate * m_flHeadPitch) + (1-iRate)*flDesiredPitch; + timeToUse -= 0.1; + } + if (m_flHeadPitch > 360) m_flHeadPitch = 0; + + SetBoneController( 1, m_flHeadPitch ); + +} + +//------------------------------------------------------------------------------ +// Purpose : Calculate the NPC's eye direction in 2D world space +// Input : +// Output : +//------------------------------------------------------------------------------ +Vector CAI_BaseNPC::EyeDirection2D( void ) +{ + // UNDONE + // For now just return head direction.... + return HeadDirection2D( ); +} + +//------------------------------------------------------------------------------ +// Purpose : Calculate the NPC's eye direction in 2D world space +// Input : +// Output : +//------------------------------------------------------------------------------ +Vector CAI_BaseNPC::EyeDirection3D( void ) +{ + // UNDONE //<> + // For now just return head direction.... + return HeadDirection3D( ); +} + +//------------------------------------------------------------------------------ +// Purpose : Calculate the NPC's head direction in 2D world space +// Input : +// Output : +//------------------------------------------------------------------------------ +Vector CAI_BaseNPC::HeadDirection2D( void ) +{ + // UNDONE <> + // This does not account for head rotations in the animation, + // only those done via bone controllers + + // Head yaw is in local cooridnate so it must be added to the body's yaw + QAngle bodyAngles = BodyAngles(); + float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y; + + // Convert head yaw into facing direction + return UTIL_YawToVector( flWorldHeadYaw ); +} + +//------------------------------------------------------------------------------ +// Purpose : Calculate the NPC's head direction in 3D world space +// Input : +// Output : +//------------------------------------------------------------------------------ +Vector CAI_BaseNPC::HeadDirection3D( void ) +{ + Vector vHeadDirection; + + // UNDONE <> + // This does not account for head rotations in the animation, + // only those done via bone controllers + + // Head yaw is in local cooridnate so it must be added to the body's yaw + QAngle bodyAngles = BodyAngles(); + float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y; + + // Convert head yaw into facing direction + AngleVectors( QAngle( m_flHeadPitch, flWorldHeadYaw, 0), &vHeadDirection ); + return vHeadDirection; +} + + +//----------------------------------------------------------------------------- +// Purpose: Look at other NPCs and clients from time to time +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *CAI_BaseNPC::EyeLookTarget( void ) +{ + if (m_flNextEyeLookTime < gpGlobals->curtime) + { + CBaseEntity* pBestEntity = NULL; + float fBestDist = MAX_COORD_RANGE; + float fTestDist; + + CBaseEntity *pEntity = NULL; + + for ( CEntitySphereQuery sphere( GetAbsOrigin(), 1024, 0 ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) + { + if (pEntity == this) + { + continue; + } + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if (pNPC || (pEntity->GetFlags() & FL_CLIENT)) + { + fTestDist = (GetAbsOrigin() - pEntity->EyePosition()).Length(); + if (fTestDist < fBestDist) + { + if (ValidEyeTarget(pEntity->EyePosition())) + { + fBestDist = fTestDist; + pBestEntity = pEntity; + } + } + } + } + if (pBestEntity) + { + m_flNextEyeLookTime = gpGlobals->curtime + random->RandomInt(1,5); + m_hEyeLookTarget = pBestEntity; + } + } + return m_hEyeLookTarget; +} + + +//----------------------------------------------------------------------------- +// Purpose: Set direction that the NPC aiming their gun +// returns true is the passed Vector is in +// the caller's forward aim cone. The dot product is performed +// in 2d, making the view cone infinitely tall. By default, the +// callers aim cone is assumed to be very narrow +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::FInAimCone( const Vector &vecSpot ) +{ + Vector los = ( vecSpot - GetAbsOrigin() ); + + // do this in 2D + los.z = 0; + VectorNormalize( los ); + + Vector facingDir = BodyDirection2D( ); + + float flDot = DotProduct( los, facingDir ); + + if (CapabilitiesGet() & bits_CAP_AIM_GUN) + { + // FIXME: query current animation for ranges + return ( flDot > DOT_30DEGREE ); + } + + if ( flDot > 0.994 )//!!!BUGBUG - magic number same as FacingIdeal(), what is this? + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Set direction that the NPC aiming their gun +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::SetAim( const Vector &aimDir ) +{ + QAngle angDir; + VectorAngles( aimDir, angDir ); + + float curPitch = GetPoseParameter( "aim_pitch" ); + float curYaw = GetPoseParameter( "aim_yaw" ); + + float newPitch; + float newYaw; + + if( GetEnemy() ) + { + // clamp and dampen movement + newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch ); + + float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y ); + // float flNewTargetYaw = UTIL_ApproachAngle( flRelativeYaw, curYaw, 20 ); + // float newYaw = curYaw + 0.8 * UTIL_AngleDiff( flNewTargetYaw, curYaw ); + newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw ); + } + else + { + // Sweep your weapon more slowly if you're not fighting someone + newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch ); + + float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y ); + newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw ); + } + + newPitch = AngleNormalize( newPitch ); + newYaw = AngleNormalize( newYaw ); + + SetPoseParameter( "aim_pitch", newPitch ); + SetPoseParameter( "aim_yaw", newYaw ); + + // Msg("yaw %.0f (%.0f %.0f)\n", newYaw, angDir.y, GetAbsAngles().y ); + + // Calculate our interaction yaw. + // If we've got a small adjustment off our abs yaw, use that. + // Otherwise, use our abs yaw. + if ( fabs(newYaw) < 20 ) + { + m_flInteractionYaw = angDir.y; + } + else + { + m_flInteractionYaw = GetAbsAngles().y; + } +} + + +void CAI_BaseNPC::RelaxAim( ) +{ + float curPitch = GetPoseParameter( "aim_pitch" ); + float curYaw = GetPoseParameter( "aim_yaw" ); + + // dampen existing aim + float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) ); + float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) ); + + SetPoseParameter( "aim_pitch", newPitch ); + SetPoseParameter( "aim_yaw", newYaw ); + // DevMsg("relax aim %.0f %0.f\n", newPitch, newYaw ); +} + +//----------------------------------------------------------------------------- +void CAI_BaseNPC::AimGun() +{ + if (GetEnemy()) + { + Vector vecShootOrigin; + + vecShootOrigin = Weapon_ShootPosition(); + Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false ); + + SetAim( vecShootDir ); + } + else + { + RelaxAim( ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set direction that the NPC is looking +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::MaintainLookTargets ( float flInterval ) +{ + // -------------------------------------------------------- + // Try to look at enemy if I have one + // -------------------------------------------------------- + if ((CBaseEntity*)GetEnemy()) + { + if ( ValidEyeTarget(GetEnemy()->EyePosition()) ) + { + SetHeadDirection(GetEnemy()->EyePosition(),flInterval); + SetViewtarget( GetEnemy()->EyePosition() ); + return; + } + } + +#if 0 + // -------------------------------------------------------- + // First check if I've been assigned to look at an entity + // -------------------------------------------------------- + CBaseEntity *lookTarget = EyeLookTarget(); + if (lookTarget && ValidEyeTarget(lookTarget->EyePosition())) + { + SetHeadDirection(lookTarget->EyePosition(),flInterval); + SetViewtarget( lookTarget->EyePosition() ); + return; + } +#endif + + // -------------------------------------------------------- + // If I'm moving, look at my target position + // -------------------------------------------------------- + if (GetNavigator()->IsGoalActive() && ValidEyeTarget(GetNavigator()->GetCurWaypointPos())) + { + SetHeadDirection(GetNavigator()->GetCurWaypointPos(),flInterval); + SetViewtarget( GetNavigator()->GetCurWaypointPos() ); + return; + } + + + // ------------------------------------- + // If I hear a combat sounds look there + // ------------------------------------- + if ( HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER) ) + { + CSound *pSound = GetBestSound(); + + if ( pSound && pSound->IsSoundType(SOUND_COMBAT | SOUND_DANGER) ) + { + if (ValidEyeTarget( pSound->GetSoundOrigin() )) + { + SetHeadDirection(pSound->GetSoundOrigin(),flInterval); + SetViewtarget( pSound->GetSoundOrigin() ); + return; + } + } + } + + // ------------------------------------- + // Otherwise just look around randomly + // ------------------------------------- + + // Check that look target position is still valid + if (m_flNextEyeLookTime > gpGlobals->curtime) + { + if (!ValidEyeTarget(m_vEyeLookTarget)) + { + // Force choosing of new look target + m_flNextEyeLookTime = 0; + } + } + + if (m_flNextEyeLookTime < gpGlobals->curtime) + { + Vector vBodyDir = BodyDirection2D( ); + + /* + Vector lookSpread = Vector(0.82,0.82,0.22); + float x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + float y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + float z = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + + QAngle angles; + VectorAngles( vBodyDir, angles ); + Vector forward, right, up; + AngleVectors( angles, &forward, &right, &up ); + + Vector vecDir = vBodyDir + (x * lookSpread.x * forward) + (y * lookSpread.y * right) + (z * lookSpread.z * up); + float lookDist = random->RandomInt(50,2000); + m_vEyeLookTarget = EyePosition() + lookDist*vecDir; + */ + m_vEyeLookTarget = EyePosition() + 500*vBodyDir; + m_flNextEyeLookTime = gpGlobals->curtime + 0.5; // random->RandomInt(1,5); + } + SetHeadDirection(m_vEyeLookTarget,flInterval); + + // ---------------------------------------------------- + // Integrate eye turn in frame rate independent manner + // ---------------------------------------------------- + float timeToUse = flInterval; + while (timeToUse > 0) + { + m_vCurEyeTarget = ((1 - m_flEyeIntegRate) * m_vCurEyeTarget + m_flEyeIntegRate * m_vEyeLookTarget); + timeToUse -= 0.1; + } + SetViewtarget( m_vCurEyeTarget ); +} + + +//----------------------------------------------------------------------------- +// Let the motor deal with turning presentation issues +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::MaintainTurnActivity( ) +{ + AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainTurnActivity ); + GetMotor()->MaintainTurnActivity( ); +} + + +//----------------------------------------------------------------------------- +// Here's where all motion occurs +//----------------------------------------------------------------------------- +void CAI_BaseNPC::PerformMovement() +{ + // don't bother to move if the npc isn't alive + if (!IsAlive()) + return; + + AI_PROFILE_SCOPE(CAI_BaseNPC_PerformMovement); + g_AIMoveTimer.Start(); + + float flInterval = ( m_flTimeLastMovement != FLT_MAX ) ? gpGlobals->curtime - m_flTimeLastMovement : 0.1; + + m_pNavigator->Move( ROUND_TO_TICKS( flInterval ) ); + m_flTimeLastMovement = gpGlobals->curtime; + + g_AIMoveTimer.End(); + +} + +//----------------------------------------------------------------------------- +// Updates to npc after movement is completed +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::PostMovement() +{ + AI_PROFILE_SCOPE( CAI_BaseNPC_PostMovement ); + + InvalidateBoneCache(); + + if ( GetModelPtr() && GetModelPtr()->SequencesAvailable() ) + { + float flInterval = GetAnimTimeInterval(); + + if ( CapabilitiesGet() & bits_CAP_AIM_GUN ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_PM_AimGun ); + AimGun(); + } + + // set look targets for npcs with animated faces + if ( CapabilitiesGet() & bits_CAP_ANIMATEDFACE ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainLookTargets ); + MaintainLookTargets(flInterval); + } + + } + + + { + AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainTurnActivity ); + // update turning as needed + MaintainTurnActivity( ); + } +} + + +//----------------------------------------------------------------------------- + +float g_AINextDisabledMessageTime; +extern bool IsInCommentaryMode( void ); + +bool CAI_BaseNPC::PreThink( void ) +{ + // ---------------------------------------------------------- + // Skip AI if its been disabled or networks haven't been + // loaded, and put a warning message on the screen + // + // Don't do this if the convar wants it hidden + // ---------------------------------------------------------- + if ( (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI || !g_pAINetworkManager->NetworksLoaded()) ) + { + if ( gpGlobals->curtime >= g_AINextDisabledMessageTime && !IsInCommentaryMode() ) + { + g_AINextDisabledMessageTime = gpGlobals->curtime + 0.5f; + + hudtextparms_s tTextParam; + tTextParam.x = 0.7; + tTextParam.y = 0.65; + tTextParam.effect = 0; + tTextParam.r1 = 255; + tTextParam.g1 = 255; + tTextParam.b1 = 255; + tTextParam.a1 = 255; + tTextParam.r2 = 255; + tTextParam.g2 = 255; + tTextParam.b2 = 255; + tTextParam.a2 = 255; + tTextParam.fadeinTime = 0; + tTextParam.fadeoutTime = 0; + tTextParam.holdTime = 0.6; + tTextParam.fxTime = 0; + tTextParam.channel = 1; + UTIL_HudMessageAll( tTextParam, "A.I. Disabled...\n" ); + } + SetActivity( ACT_IDLE ); + return false; + } + + // -------------------------------------------------------- + // If debug stepping + // -------------------------------------------------------- + if (CAI_BaseNPC::m_nDebugBits & bits_debugStepAI) + { + if (m_nDebugCurIndex >= CAI_BaseNPC::m_nDebugPauseIndex) + { + if (!GetNavigator()->IsGoalActive()) + { + m_flPlaybackRate = 0; + } + return false; + } + else + { + m_flPlaybackRate = 1; + } + } + + if ( m_hOpeningDoor.Get() && AIIsDebuggingDoors( this ) ) + { + NDebugOverlay::Line( EyePosition(), m_hOpeningDoor->WorldSpaceCenter(), 255, 255, 255, false, .1 ); + } + + return true; +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::RunAnimation( void ) +{ + VPROF_BUDGET( "CAI_BaseNPC_RunAnimation", VPROF_BUDGETGROUP_SERVER_ANIM ); + + if ( !GetModelPtr() ) + return; + + float flInterval = GetAnimTimeInterval(); + + StudioFrameAdvance( ); // animate + + if ((CAI_BaseNPC::m_nDebugBits & bits_debugStepAI) && !GetNavigator()->IsGoalActive()) + { + flInterval = 0; + } + + // 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_NPCState != NPC_STATE_SCRIPT && m_NPCState != NPC_STATE_DEAD && m_Activity == ACT_IDLE && IsActivityFinished() ) + { + int iSequence; + + // FIXME: this doesn't reissue a translation, so if the idle activity translation changes over time, it'll never get reset + if ( SequenceLoops() ) + { + // animation does loop, which means we're playing subtle idle. Might need to fidget. + iSequence = SelectWeightedSequence ( m_translatedActivity ); + } + 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 = SelectHeaviestSequence ( m_translatedActivity ); + } + if ( iSequence != ACTIVITY_NOT_AVAILABLE ) + { + ResetSequence( iSequence ); // Set to new anim (if it's there) + + //Adrian: Basically everywhere else in the AI code this variable gets set to whatever our sequence is. + //But here it doesn't and not setting it causes any animation set through here to be stomped by the + //ideal sequence before it has a chance of playing out (since there's code that reselects the ideal + //sequence if it doesn't match the current one). + if ( hl2_episodic.GetBool() ) + { + m_nIdealSequence = iSequence; + } + } + } + + DispatchAnimEvents( this ); +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::PostRun( void ) +{ + AI_PROFILE_SCOPE(CAI_BaseNPC_PostRun); + + g_AIPostRunTimer.Start(); + + if ( !IsMoving() ) + { + if ( GetIdealActivity() == ACT_WALK || + GetIdealActivity() == ACT_RUN || + GetIdealActivity() == ACT_WALK_AIM || + GetIdealActivity() == ACT_RUN_AIM ) + { + PostRunStopMoving(); + } + } + + RunAnimation(); + + // update slave items (weapons) + Weapon_FrameUpdate(); + + g_AIPostRunTimer.End(); +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::PostRunStopMoving() +{ + DbgNavMsg1( this, "NPC %s failed to stop properly, slamming activity\n", GetDebugName() ); + if ( !GetNavigator()->SetGoalFromStoppingPath() ) + SetIdealActivity( GetStoppedActivity() ); // This is to prevent running in place +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::ShouldAlwaysThink() +{ + // @TODO (toml 07-08-03): This needs to be beefed up. + // There are simple cases already seen where an AI can briefly leave + // the PVS while navigating to the player. Perhaps should incorporate a heuristic taking into + // account mode, enemy, last time saw player, player range etc. For example, if enemy is player, + // and player is within 100 feet, and saw the player within the past 15 seconds, keep running... + return HasSpawnFlags(SF_NPC_ALWAYSTHINK); +} + + +bool CAI_BaseNPC::ShouldPlayerAvoid( void ) +{ + if ( GetState() == NPC_STATE_SCRIPT ) + return true; + + if ( IsInAScript() ) + return true; + + if ( IsInLockedScene() == true ) + return true; + + if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::UpdateEfficiency( bool bInPVS ) +{ + // Sleeping NPCs always dormant + if ( GetSleepState() != AISS_AWAKE ) + { + SetEfficiency( AIE_DORMANT ); + return; + } + + m_bInChoreo = ( GetState() == NPC_STATE_SCRIPT || IsCurSchedule( SCHED_SCENE_GENERIC, false ) ); + + if ( !ShouldUseEfficiency() ) + { + SetEfficiency( AIE_NORMAL ); + SetMoveEfficiency( AIME_NORMAL ); + return; + } + + //--------------------------------- + + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + static Vector vPlayerEyePosition; + static Vector vPlayerForward; + static int iPrevFrame = -1; + if ( gpGlobals->framecount != iPrevFrame ) + { + iPrevFrame = gpGlobals->framecount; + if ( pPlayer ) + { + pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL ); + } + } + + Vector vToNPC = GetAbsOrigin() - vPlayerEyePosition; + float playerDist = VectorNormalize( vToNPC ); + bool bPlayerFacing; + + bool bClientPVSExpanded = UTIL_ClientPVSIsExpanded(); + + if ( pPlayer ) + { + bPlayerFacing = ( bClientPVSExpanded || ( bInPVS && vPlayerForward.Dot( vToNPC ) > 0 ) ); + } + else + { + playerDist = 0; + bPlayerFacing = true; + } + + //--------------------------------- + + bool bInVisibilityPVS = ( bClientPVSExpanded && UTIL_FindClientInVisibilityPVS( edict() ) != NULL ); + + //--------------------------------- + + if ( ( bInPVS && ( bPlayerFacing || playerDist < 25*12 ) ) || bClientPVSExpanded ) + { + SetMoveEfficiency( AIME_NORMAL ); + } + else + { + SetMoveEfficiency( AIME_EFFICIENT ); + } + + //--------------------------------- + + if ( !IsRetail() && ai_efficiency_override.GetInt() > AIE_NORMAL && ai_efficiency_override.GetInt() <= AIE_DORMANT ) + { + SetEfficiency( (AI_Efficiency_t)ai_efficiency_override.GetInt() ); + return; + } + + //--------------------------------- + + // Some conditions will always force normal + if ( gpGlobals->curtime - GetLastAttackTime() < .15 ) + { + SetEfficiency( AIE_NORMAL ); + return; + } + + bool bFramerateOk = ( gpGlobals->frametime < ai_frametime_limit.GetFloat() ); + + if ( m_bForceConditionsGather || + gpGlobals->curtime - GetLastAttackTime() < .2 || + gpGlobals->curtime - m_flLastDamageTime < .2 || + ( GetState() < NPC_STATE_IDLE || GetState() > NPC_STATE_SCRIPT ) || + ( ( bInPVS || bInVisibilityPVS ) && + ( ( GetTask() && !TaskIsRunning() ) || + GetTaskInterrupt() > 0 || + m_bInChoreo ) ) ) + { + SetEfficiency( ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT ); + return; + } + + AI_Efficiency_t minEfficiency; + + if ( !ShouldDefaultEfficient() ) + { + minEfficiency = ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT; + } + else + { + minEfficiency = ( bFramerateOk ) ? AIE_EFFICIENT : AIE_VERY_EFFICIENT; + } + + // Stay normal if there's any chance of a relevant danger sound + bool bPotentialDanger = false; + + if ( GetSoundInterests() & SOUND_DANGER ) + { + int iSound = CSoundEnt::ActiveList(); + + while ( iSound != SOUNDLIST_EMPTY ) + { + CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); + + float hearingSensitivity = HearingSensitivity(); + Vector vEarPosition = EarPosition(); + + if ( pCurrentSound && (SOUND_DANGER & pCurrentSound->SoundType()) ) + { + float flHearDistanceSq = pCurrentSound->Volume() * hearingSensitivity; + flHearDistanceSq *= flHearDistanceSq; + if ( pCurrentSound->GetSoundOrigin().DistToSqr( vEarPosition ) <= flHearDistanceSq ) + { + bPotentialDanger = true; + break; + } + } + + iSound = pCurrentSound->NextSound(); + } + } + + if ( bPotentialDanger ) + { + SetEfficiency( minEfficiency ); + return; + } + + //--------------------------------- + + if ( !pPlayer ) + { + // No heuristic currently for dedicated servers + SetEfficiency( minEfficiency ); + return; + } + + enum + { + NEAR, + MID, + FAR + }; + + int range; + if ( bInPVS ) + { + if ( playerDist < 15*12 ) + { + SetEfficiency( minEfficiency ); + return; + } + + range = ( playerDist < 50*12 ) ? NEAR : + ( playerDist < 200*12 ) ? MID : FAR; + } + else + { + range = ( playerDist < 25*12 ) ? NEAR : + ( playerDist < 100*12 ) ? MID : FAR; + } + + // Efficiency mappings + int state = GetState(); + if (state == NPC_STATE_SCRIPT ) // Treat script as alert. Already confirmed not in PVS + state = NPC_STATE_ALERT; + + static AI_Efficiency_t mappings[] = + { + // Idle + // In PVS + // Facing + AIE_NORMAL, + AIE_EFFICIENT, + AIE_EFFICIENT, + // Not facing + AIE_EFFICIENT, + AIE_EFFICIENT, + AIE_VERY_EFFICIENT, + // Not in PVS + AIE_VERY_EFFICIENT, + AIE_SUPER_EFFICIENT, + AIE_SUPER_EFFICIENT, + // Alert + // In PVS + // Facing + AIE_NORMAL, + AIE_EFFICIENT, + AIE_EFFICIENT, + // Not facing + AIE_NORMAL, + AIE_EFFICIENT, + AIE_EFFICIENT, + // Not in PVS + AIE_EFFICIENT, + AIE_VERY_EFFICIENT, + AIE_SUPER_EFFICIENT, + // Combat + // In PVS + // Facing + AIE_NORMAL, + AIE_NORMAL, + AIE_EFFICIENT, + // Not facing + AIE_NORMAL, + AIE_EFFICIENT, + AIE_EFFICIENT, + // Not in PVS + AIE_NORMAL, + AIE_EFFICIENT, + AIE_VERY_EFFICIENT, + }; + + static const int stateBase[] = { 0, 9, 18 }; + const int NOT_FACING_OFFSET = 3; + const int NO_PVS_OFFSET = 6; + + int iStateOffset = stateBase[state - NPC_STATE_IDLE] ; + int iFacingOffset = (!bInPVS || bPlayerFacing) ? 0 : NOT_FACING_OFFSET; + int iPVSOffset = (bInPVS) ? 0 : NO_PVS_OFFSET; + int iMapping = iStateOffset + iPVSOffset + iFacingOffset + range; + + Assert( iMapping < static_cast(ARRAYSIZE( mappings )) ); + + AI_Efficiency_t efficiency = mappings[iMapping]; + + //--------------------------------- + + AI_Efficiency_t maxEfficiency = AIE_SUPER_EFFICIENT; + if ( bInVisibilityPVS && state >= NPC_STATE_ALERT ) + { + maxEfficiency = AIE_EFFICIENT; + } + else if ( bInVisibilityPVS || HasCondition( COND_SEE_PLAYER ) ) + { + maxEfficiency = AIE_VERY_EFFICIENT; + } + + //--------------------------------- + + SetEfficiency( clamp( efficiency, minEfficiency, maxEfficiency ) ); +} + + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::UpdateSleepState( bool bInPVS ) +{ + if ( GetSleepState() > AISS_AWAKE ) + { + CBasePlayer *pLocalPlayer = AI_GetSinglePlayer(); + if ( !pLocalPlayer ) + { + if ( gpGlobals->maxClients > 1 ) + { + Wake(); + } + else + { + Warning( "CAI_BaseNPC::UpdateSleepState called with NULL pLocalPlayer\n" ); + } + return; + } + + if ( m_flWakeRadius > .1 && !(pLocalPlayer->GetFlags() & FL_NOTARGET) && ( pLocalPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() <= Square(m_flWakeRadius) ) + Wake(); + else if ( GetSleepState() == AISS_WAITING_FOR_PVS ) + { + if ( bInPVS ) + Wake(); + } + else if ( GetSleepState() == AISS_WAITING_FOR_THREAT ) + { + if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) + Wake(); + else + { + if ( bInPVS ) + { + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) && pPlayer->FVisible( this ) ) + Wake(); + } + } + + // Should check for visible danger sounds + if ( (GetSoundInterests() & SOUND_DANGER) && !(HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN)) ) + { + int iSound = CSoundEnt::ActiveList(); + + while ( iSound != SOUNDLIST_EMPTY ) + { + CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); + Assert( pCurrentSound ); + + if ( (pCurrentSound->SoundType() & SOUND_DANGER) && + GetSenses()->CanHearSound( pCurrentSound ) && + SoundIsVisible( pCurrentSound )) + { + Wake(); + break; + } + + iSound = pCurrentSound->NextSound(); + } + } + } + } + } + else + { + // NPC is awake + // Don't let an NPC sleep if they're running a script! + if( !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT ) + { + if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS) ) + { + if( !HasCondition(COND_IN_PVS) ) + { + SetSleepState( AISS_WAITING_FOR_PVS ); + Sleep(); + } + } + if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS) ) + { + if( HasCondition(COND_IN_PVS) ) + { + // OK, we're in the player's PVS. Now switch to regular old AUTO_PVS sleep rules. + AddSleepFlags(AI_SLEEP_FLAG_AUTO_PVS); + RemoveSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS); + } + } + } + } +} + +//----------------------------------------------------------------------------- + +struct AIRebalanceInfo_t +{ + CAI_BaseNPC * pNPC; + int iNextThinkTick; + bool bInPVS; + float dotPlayer; + float distPlayer; +}; + +int __cdecl ThinkRebalanceCompare( const AIRebalanceInfo_t *pLeft, const AIRebalanceInfo_t *pRight ) +{ + int base = pLeft->iNextThinkTick - pRight->iNextThinkTick; + if ( base != 0 ) + return base; + + if ( !pLeft->bInPVS && !pRight->bInPVS ) + return 0; + + if ( !pLeft->bInPVS ) + return 1; + + if ( !pRight->bInPVS ) + return -1; + + if ( pLeft->dotPlayer < 0 && pRight->dotPlayer < 0 ) + return 0; + + if ( pLeft->dotPlayer < 0 ) + return 1; + + if ( pRight->dotPlayer < 0 ) + return -1; + + const float NEAR_PLAYER = 50*12; + + if ( pLeft->distPlayer < NEAR_PLAYER && pRight->distPlayer >= NEAR_PLAYER ) + return -1; + + if ( pRight->distPlayer < NEAR_PLAYER && pLeft->distPlayer >= NEAR_PLAYER ) + return 1; + + if ( pLeft->dotPlayer > pRight->dotPlayer ) + return -1; + + if ( pLeft->dotPlayer < pRight->dotPlayer ) + return 1; + + return 0; +} + +inline bool CAI_BaseNPC::CanThinkRebalance() +{ + if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink ) + { + return false; + } + + if ( m_bInChoreo ) + { + return false; + } + + if ( m_NPCState == NPC_STATE_DEAD ) + { + return false; + } + + if ( GetSleepState() != AISS_AWAKE ) + { + return false; + } + + if ( !m_bUsingStandardThinkTime /*&& m_iFrameBlocked == -1 */ ) + { + return false; + } + + return true; +} + +void CAI_BaseNPC::RebalanceThinks() +{ + bool bDebugThinkTicks = ai_debug_think_ticks.GetBool(); + if ( bDebugThinkTicks ) + { + static int iPrevTick; + static int nThinksInTick; + static int nRebalanceableThinksInTick; + + if ( gpGlobals->tickcount != iPrevTick ) + { + DevMsg( "NPC per tick is %d [%d] (tick %d, frame %d)\n", nRebalanceableThinksInTick, nThinksInTick, iPrevTick, gpGlobals->framecount ); + iPrevTick = gpGlobals->tickcount; + nThinksInTick = 0; + nRebalanceableThinksInTick = 0; + } + nThinksInTick++; + if ( CanThinkRebalance() ) + nRebalanceableThinksInTick++; + } + + if ( ShouldRebalanceThinks() && gpGlobals->tickcount >= gm_iNextThinkRebalanceTick ) + { + AI_PROFILE_SCOPE(AI_Think_Rebalance ); + + static CUtlVector rebalanceCandidates( 16, 64 ); + gm_iNextThinkRebalanceTick = gpGlobals->tickcount + TIME_TO_TICKS( random->RandomFloat( 3, 5) ); + + int i; + + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + Vector vPlayerForward; + Vector vPlayerEyePosition; + + vPlayerForward.Init(); + vPlayerEyePosition.Init(); + + if ( pPlayer ) + { + pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL ); + } + + int iTicksPer10Hz = TIME_TO_TICKS( .1 ); + int iMinTickRebalance = gpGlobals->tickcount - 1; // -1 needed for alternate ticks + int iMaxTickRebalance = gpGlobals->tickcount + iTicksPer10Hz; + + for ( i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + CAI_BaseNPC *pCandidate = g_AI_Manager.AccessAIs()[i]; + if ( pCandidate->CanThinkRebalance() && + ( pCandidate->GetNextThinkTick() >= iMinTickRebalance && + pCandidate->GetNextThinkTick() < iMaxTickRebalance ) ) + { + int iInfo = rebalanceCandidates.AddToTail(); + + rebalanceCandidates[iInfo].pNPC = pCandidate; + rebalanceCandidates[iInfo].iNextThinkTick = pCandidate->GetNextThinkTick(); + + if ( pCandidate->IsFlaggedEfficient() ) + { + rebalanceCandidates[iInfo].bInPVS = false; + } + else if ( pPlayer ) + { + Vector vToCandidate = pCandidate->EyePosition() - vPlayerEyePosition; + rebalanceCandidates[iInfo].bInPVS = ( UTIL_FindClientInPVS( pCandidate->edict() ) != NULL ); + rebalanceCandidates[iInfo].distPlayer = VectorNormalize( vToCandidate ); + rebalanceCandidates[iInfo].dotPlayer = vPlayerForward.Dot( vToCandidate ); + } + else + { + rebalanceCandidates[iInfo].bInPVS = true; + rebalanceCandidates[iInfo].dotPlayer = 1; + rebalanceCandidates[iInfo].distPlayer = 0; + } + } + else if ( bDebugThinkTicks ) + DevMsg( " Ignoring %d\n", pCandidate->GetNextThinkTick() ); + } + + if ( rebalanceCandidates.Count() ) + { + rebalanceCandidates.Sort( ThinkRebalanceCompare ); + + int iMaxThinkersPerTick = static_cast(ceil( (float)( rebalanceCandidates.Count() + 1 ) / (float)iTicksPer10Hz )); // +1 to account for "this" + + int iCurTickDistributing = min( gpGlobals->tickcount, rebalanceCandidates[0].iNextThinkTick ); + int iRemainingThinksToDistribute = iMaxThinkersPerTick - 1; // Start with one fewer first time because "this" is + // always gets a slot in the current tick to avoid complications + // in the calculation of "last think" + + if ( bDebugThinkTicks ) + { + DevMsg( "Rebalance %d!\n", rebalanceCandidates.Count() + 1 ); + DevMsg( " Distributing %d\n", iCurTickDistributing ); + } + + for ( i = 0; i < rebalanceCandidates.Count(); i++ ) + { + if ( iRemainingThinksToDistribute == 0 || rebalanceCandidates[i].iNextThinkTick > iCurTickDistributing ) + { + if ( rebalanceCandidates[i].iNextThinkTick <= iCurTickDistributing ) + { + iCurTickDistributing = iCurTickDistributing + 1; + } + else + { + iCurTickDistributing = rebalanceCandidates[i].iNextThinkTick; + } + + if ( bDebugThinkTicks ) + DevMsg( " Distributing %d\n", iCurTickDistributing ); + + iRemainingThinksToDistribute = iMaxThinkersPerTick; + } + + if ( rebalanceCandidates[i].pNPC->GetNextThinkTick() != iCurTickDistributing ) + { + if ( bDebugThinkTicks ) + DevMsg( " Bumping %d to %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick(), iCurTickDistributing ); + + rebalanceCandidates[i].pNPC->SetNextThink( TICKS_TO_TIME( iCurTickDistributing ) ); + } + else if ( bDebugThinkTicks ) + { + DevMsg( " Leaving %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick() ); + } + + iRemainingThinksToDistribute--; + } + } + + rebalanceCandidates.RemoveAll(); + + if ( bDebugThinkTicks ) + { + DevMsg( "New distribution is:\n"); + for ( i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + DevMsg( " %d\n", g_AI_Manager.AccessAIs()[i]->GetNextThinkTick() ); + } + } + + Assert( GetNextThinkTick() == TICK_NEVER_THINK ); // never change this objects tick + } +} + +static float g_NpcTimeThisFrame; +static float g_StartTimeCurThink; + +bool CAI_BaseNPC::PreNPCThink() +{ + static int iPrevFrame = -1; + static float frameTimeLimit = FLT_MAX; + static const ConVar *pHostTimescale; + + if ( frameTimeLimit == FLT_MAX ) + { + pHostTimescale = cvar->FindVar( "host_timescale" ); + } + + bool bUseThinkLimits = ( !m_bInChoreo && ShouldUseFrameThinkLimits() ); + +#ifdef _DEBUG + const float NPC_THINK_LIMIT = 30.0 / 1000.0; +#else + const float NPC_THINK_LIMIT = ( !IsXbox() ) ? (10.0 / 1000.0) : (12.5 / 1000.0); +#endif + + g_StartTimeCurThink = 0; + + if ( bUseThinkLimits && VCRGetMode() == VCR_Disabled ) + { + if ( m_iFrameBlocked == gpGlobals->framecount ) + { + DbgFrameLimitMsg( "Stalled %d (%d)\n", this, gpGlobals->framecount ); + SetNextThink( gpGlobals->curtime ); + return false; + } + else if ( gpGlobals->framecount != iPrevFrame ) + { + DbgFrameLimitMsg( "--- FRAME: %d (%d)\n", this, gpGlobals->framecount ); + float timescale = pHostTimescale->GetFloat(); + if ( timescale < 1 ) + timescale = 1; + + iPrevFrame = gpGlobals->framecount; + frameTimeLimit = NPC_THINK_LIMIT * timescale; + g_NpcTimeThisFrame = 0; + } + else + { + if ( g_NpcTimeThisFrame > NPC_THINK_LIMIT ) + { + float timeSinceLastRealThink = gpGlobals->curtime - m_flLastRealThinkTime; + // Don't bump anyone more that a quarter second + if ( timeSinceLastRealThink <= .25 ) + { + DbgFrameLimitMsg( "Bumped %d (%d)\n", this, gpGlobals->framecount ); + m_iFrameBlocked = gpGlobals->framecount; + SetNextThink( gpGlobals->curtime ); + return false; + } + else + { + DbgFrameLimitMsg( "(Over %d )\n", this ); + } + } + } + + DbgFrameLimitMsg( "Running %d (%d)\n", this, gpGlobals->framecount ); + g_StartTimeCurThink = engine->Time(); + + m_iFrameBlocked = -1; + m_nLastThinkTick = TIME_TO_TICKS( m_flLastRealThinkTime ); + } + + return true; +} + +void CAI_BaseNPC::PostNPCThink( void ) +{ + if ( g_StartTimeCurThink != 0.0 && VCRGetMode() == VCR_Disabled ) + { + g_NpcTimeThisFrame += engine->Time() - g_StartTimeCurThink; + } +} + +void CAI_BaseNPC::CallNPCThink( void ) +{ + RebalanceThinks(); + + //--------------------------------- + + m_bUsingStandardThinkTime = false; + + //--------------------------------- + + if ( !PreNPCThink() ) + { + return; + } + + // reduce cache queries by locking model in memory + MDLCACHE_CRITICAL_SECTION(); + + this->NPCThink(); + + m_flLastRealThinkTime = gpGlobals->curtime; + + PostNPCThink(); +} + +bool NPC_CheckBrushExclude( CBaseEntity *pEntity, CBaseEntity *pBrush ) +{ + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + + if ( pNPC ) + { + return pNPC->GetMoveProbe()->ShouldBrushBeIgnored( pBrush ); + } + + return false; +} + +class CTraceFilterPlayerAvoidance : public CTraceFilterEntitiesOnly +{ +public: + CTraceFilterPlayerAvoidance( const CBaseEntity *pEntity ) { m_pIgnore = pEntity; } + + virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + + if ( m_pIgnore == pEntity ) + return false; + + if ( pEntity->IsPlayer() ) + return true; + + return false; + } +private: + + const CBaseEntity *m_pIgnore; +}; + +void CAI_BaseNPC::GetPlayerAvoidBounds( Vector *pMins, Vector *pMaxs ) +{ + *pMins = WorldAlignMins(); + *pMaxs = WorldAlignMaxs(); +} + +ConVar ai_debug_avoidancebounds( "ai_debug_avoidancebounds", "0" ); + +void CAI_BaseNPC::SetPlayerAvoidState( void ) +{ + bool bShouldPlayerAvoid = false; + Vector vNothing; + + GetSequenceLinearMotion( GetSequence(), &vNothing ); + bool bIsMoving = ( IsMoving() || ( vNothing != vec3_origin ) ); + + //If we are coming out of a script, check if we are stuck inside the player. + if ( m_bPerformAvoidance || ( ShouldPlayerAvoid() && bIsMoving ) ) + { + trace_t trace; + Vector vMins, vMaxs; + + GetPlayerAvoidBounds( &vMins, &vMaxs ); + + CBasePlayer *pLocalPlayer = AI_GetSinglePlayer(); + + if ( pLocalPlayer ) + { + bShouldPlayerAvoid = IsBoxIntersectingBox( GetAbsOrigin() + vMins, GetAbsOrigin() + vMaxs, + pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMins(), pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMaxs() ); + } + + if ( ai_debug_avoidancebounds.GetBool() ) + { + int iRed = ( bShouldPlayerAvoid == true ) ? 255 : 0; + + NDebugOverlay::Box( GetAbsOrigin(), vMins, vMaxs, iRed, 0, 255, 64, 0.1 ); + } + } + + m_bPlayerAvoidState = ShouldPlayerAvoid(); + m_bPerformAvoidance = bShouldPlayerAvoid; + + if ( GetCollisionGroup() == COLLISION_GROUP_NPC || GetCollisionGroup() == COLLISION_GROUP_NPC_ACTOR ) + { + if ( m_bPerformAvoidance == true ) + { + SetCollisionGroup( COLLISION_GROUP_NPC_ACTOR ); + } + else + { + SetCollisionGroup( COLLISION_GROUP_NPC ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Enables player avoidance when the player's vphysics shadow penetrates our vphysics shadow. This can +// happen when the player is hit by a combine ball, which pushes them into an adjacent npc. Subclasses should +// override this if it causes problems, but in general this will solve cases of the player getting stuck in +// the NPC from being pushed. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::PlayerPenetratingVPhysics( void ) +{ + m_bPerformAvoidance = true; +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::CheckPVSCondition() +{ + bool bInPVS = ( UTIL_FindClientInPVS( edict() ) != NULL ) || (UTIL_ClientPVSIsExpanded() && UTIL_FindClientInVisibilityPVS( edict() )); + + if ( bInPVS ) + SetCondition( COND_IN_PVS ); + else + ClearCondition( COND_IN_PVS ); + + return bInPVS; +} + + +//----------------------------------------------------------------------------- +// NPC Think - calls out to core AI functions and handles this +// npc's specific animation events +// + +void CAI_BaseNPC::NPCThink( void ) +{ + if ( m_bCheckContacts ) + { + CheckPhysicsContacts(); + } + + Assert( !(m_NPCState == NPC_STATE_DEAD && m_lifeState == LIFE_ALIVE) ); + + //--------------------------------- + + SetNextThink( TICK_NEVER_THINK ); + + //--------------------------------- + + bool bInPVS = CheckPVSCondition(); + + //--------------------------------- + + UpdateSleepState( bInPVS ); + + //--------------------------------- + bool bRanDecision = false; + + if ( GetEfficiency() < AIE_DORMANT && GetSleepState() == AISS_AWAKE ) + { + static CFastTimer timer; + float thinkLimit = ai_show_think_tolerance.GetFloat(); + + if ( thinkLimit > 0 ) + timer.Start(); + + if ( g_pAINetworkManager && g_pAINetworkManager->IsInitialized() ) + { + VPROF_BUDGET( "NPCs", VPROF_BUDGETGROUP_NPCS ); + + AI_PROFILE_SCOPE_BEGIN_( GetClassScheduleIdSpace()->GetClassName() ); // need to use a string stable from map load to map load + + SetPlayerAvoidState(); + + if ( PreThink() ) + { + if ( m_flNextDecisionTime <= gpGlobals->curtime ) + { + bRanDecision = true; + m_ScheduleState.bTaskRanAutomovement = false; + m_ScheduleState.bTaskUpdatedYaw = false; + RunAI(); + } + else + { + if ( m_ScheduleState.bTaskRanAutomovement ) + AutoMovement(); + if ( m_ScheduleState.bTaskUpdatedYaw ) + GetMotor()->UpdateYaw(); + } + + PostRun(); + + PerformMovement(); + + m_bIsMoving = IsMoving(); + + PostMovement(); + + SetSimulationTime( gpGlobals->curtime ); + } + else + m_flTimeLastMovement = FLT_MAX; + + AI_PROFILE_SCOPE_END(); + } + + if ( thinkLimit > 0 ) + { + timer.End(); + + float thinkTime = g_AIRunTimer.GetDuration().GetMillisecondsF(); + + if ( thinkTime > thinkLimit ) + { + int color = (int)RemapVal( thinkTime, thinkLimit, thinkLimit * 3, 96.0, 255.0 ); + if ( color > 255 ) + color = 255; + else if ( color < 96 ) + color = 96; + + Vector right; + Vector vecPoint; + + vecPoint = EyePosition() + Vector( 0, 0, 12 ); + GetVectors( NULL, &right, NULL ); + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), color, 0, 0, false , 1.0 ); + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) + right * 16, color, 0, 0, false , 1.0 ); + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) - right * 16, color, 0, 0, false , 1.0 ); + } + } + } + + m_bUsingStandardThinkTime = ( GetNextThinkTick() == TICK_NEVER_THINK ); + + UpdateEfficiency( bInPVS ); + + if ( m_bUsingStandardThinkTime ) + { + static const char *ppszEfficiencies[] = + { + "AIE_NORMAL", + "AIE_EFFICIENT", + "AIE_VERY_EFFICIENT", + "AIE_SUPER_EFFICIENT", + "AIE_DORMANT", + }; + + static const char *ppszMoveEfficiencies[] = + { + "AIME_NORMAL", + "AIME_EFFICIENT", + }; + + if ( ai_debug_efficiency.GetBool() ) + DevMsg( this, "Eff: %s, Move: %s\n", ppszEfficiencies[GetEfficiency()], ppszMoveEfficiencies[GetMoveEfficiency()] ); + + static float g_DecisionIntervals[] = + { + .1, // AIE_NORMAL + .2, // AIE_EFFICIENT + .4, // AIE_VERY_EFFICIENT + .6, // AIE_SUPER_EFFICIENT + }; + + if ( bRanDecision ) + { + m_flNextDecisionTime = gpGlobals->curtime + g_DecisionIntervals[GetEfficiency()]; + } + + if ( GetMoveEfficiency() == AIME_NORMAL || GetEfficiency() == AIE_NORMAL ) + { + SetNextThink( gpGlobals->curtime + .1 ); + } + else + { + SetNextThink( gpGlobals->curtime + .2 ); + } + } + else + { + m_flNextDecisionTime = 0; + } +} + +//========================================================= +// CAI_BaseNPC - USE - will make a npc angry at whomever +// activated it. +//========================================================= +void CAI_BaseNPC::NPCUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + return; + + // Can't +USE NPCs running scripts + if ( GetState() == NPC_STATE_SCRIPT ) + return; + + if ( IsInAScript() ) + return; + + SetIdealState( NPC_STATE_ALERT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Virtual function that allows us to have any npc ignore a set of +// shared conditions. +// +//----------------------------------------------------------------------------- +void CAI_BaseNPC::RemoveIgnoredConditions ( void ) +{ + m_ConditionsPreIgnore = m_Conditions; + m_Conditions.And( m_InverseIgnoreConditions, &m_Conditions ); + + if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine ) + m_hCine->RemoveIgnoredConditions(); +} + +//========================================================= +// RangeAttack1Conditions +//========================================================= +int CAI_BaseNPC::RangeAttack1Conditions ( float flDot, float flDist ) +{ + if ( flDist < 64) + { + return COND_TOO_CLOSE_TO_ATTACK; + } + else if (flDist > 784) + { + return COND_TOO_FAR_TO_ATTACK; + } + else if (flDot < 0.5) + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_RANGE_ATTACK1; +} + +//========================================================= +// RangeAttack2Conditions +//========================================================= +int CAI_BaseNPC::RangeAttack2Conditions ( float flDot, float flDist ) +{ + if ( flDist < 64) + { + return COND_TOO_CLOSE_TO_ATTACK; + } + else if (flDist > 512) + { + return COND_TOO_FAR_TO_ATTACK; + } + else if (flDot < 0.5) + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_RANGE_ATTACK2; +} + +//========================================================= +// MeleeAttack1Conditions +//========================================================= +int CAI_BaseNPC::MeleeAttack1Conditions ( float flDot, float flDist ) +{ + if ( flDist > 64) + { + return COND_TOO_FAR_TO_ATTACK; + } + else if (flDot < 0.7) + { + return 0; + } + else if (GetEnemy() == NULL) + { + return 0; + } + + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + if ( GetEnemy()->GetFlags() & FL_ONGROUND ) + { + return COND_CAN_MELEE_ATTACK1; + } + return 0; +} + +//========================================================= +// MeleeAttack2Conditions +//========================================================= +int CAI_BaseNPC::MeleeAttack2Conditions ( float flDot, float flDist ) +{ + if ( flDist > 64) + { + return COND_TOO_FAR_TO_ATTACK; + } + else if (flDot < 0.7) + { + return 0; + } + return COND_CAN_MELEE_ATTACK2; +} + +// Get capability mask +int CAI_BaseNPC::CapabilitiesGet( void ) const +{ + int capability = m_afCapability; + if ( GetActiveWeapon() ) + { + capability |= GetActiveWeapon()->CapabilitiesGet(); + } + return capability; +} + +// Set capability mask +int CAI_BaseNPC::CapabilitiesAdd( int capability ) +{ + m_afCapability |= capability; + + return m_afCapability; +} + +// Set capability mask +int CAI_BaseNPC::CapabilitiesRemove( int capability ) +{ + m_afCapability &= ~capability; + + return m_afCapability; +} + +// Clear capability mask +void CAI_BaseNPC::CapabilitiesClear( void ) +{ + m_afCapability = 0; +} + + +//========================================================= +// ClearAttacks - clear out all attack conditions +//========================================================= +void CAI_BaseNPC::ClearAttackConditions( ) +{ + // Clear all attack conditions + ClearCondition( COND_CAN_RANGE_ATTACK1 ); + ClearCondition( COND_CAN_RANGE_ATTACK2 ); + ClearCondition( COND_CAN_MELEE_ATTACK1 ); + ClearCondition( COND_CAN_MELEE_ATTACK2 ); + ClearCondition( COND_WEAPON_HAS_LOS ); + ClearCondition( COND_WEAPON_BLOCKED_BY_FRIEND ); + ClearCondition( COND_WEAPON_PLAYER_IN_SPREAD ); // Player in shooting direction + ClearCondition( COND_WEAPON_PLAYER_NEAR_TARGET ); // Player near shooting position + ClearCondition( COND_WEAPON_SIGHT_OCCLUDED ); +} + +//========================================================= +// GatherAttackConditions - sets all of the bits for attacks that the +// npc is capable of carrying out on the passed entity. +//========================================================= + +void CAI_BaseNPC::GatherAttackConditions( CBaseEntity *pTarget, float flDist ) +{ + AI_PROFILE_SCOPE(CAI_BaseNPC_GatherAttackConditions); + + Vector vecLOS = ( pTarget->GetAbsOrigin() - GetAbsOrigin() ); + vecLOS.z = 0; + VectorNormalize( vecLOS ); + + Vector vBodyDir = BodyDirection2D( ); + float flDot = DotProduct( vecLOS, vBodyDir ); + + // we know the enemy is in front now. We'll find which attacks the npc is capable of by + // checking for corresponding Activities in the model file, then do the simple checks to validate + // those attack types. + + int capability; + Vector targetPos; + bool bWeaponHasLOS; + int condition; + + capability = CapabilitiesGet(); + + // Clear all attack conditions + AI_PROFILE_SCOPE_BEGIN( CAI_BaseNPC_GatherAttackConditions_PrimaryWeaponLOS ); + + // @TODO (toml 06-15-03): There are simple cases where + // the upper torso of the enemy is visible, and the NPC is at an angle below + // them, but the above test fails because BodyTarget returns the center of + // the target. This needs some better handling/closer evaluation + + // Try the eyes first, as likely to succeed (because can see or else wouldn't be here) thus reducing + // the odds of the need for a second trace + ClearAttackConditions(); + targetPos = pTarget->EyePosition(); + bWeaponHasLOS = WeaponLOSCondition( GetAbsOrigin(), targetPos, true ); + + AI_PROFILE_SCOPE_END(); + + if ( !bWeaponHasLOS ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_SecondaryWeaponLOS ); + ClearAttackConditions( ); + targetPos = pTarget->BodyTarget( GetAbsOrigin() ); + bWeaponHasLOS = WeaponLOSCondition( GetAbsOrigin(), targetPos, true ); + } + else + { + SetCondition( COND_WEAPON_HAS_LOS ); + } + + bool bWeaponIsReady = (GetActiveWeapon() && !IsWeaponStateChanging()); + + // FIXME: move this out of here + if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK1) && bWeaponIsReady ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack1Condition ); + + condition = GetActiveWeapon()->WeaponRangeAttack1Condition(flDot, flDist); + + if ( condition == COND_NOT_FACING_ATTACK && FInAimCone( targetPos ) ) + DevMsg( "Warning: COND_NOT_FACING_ATTACK set but FInAimCone is true\n" ); + + if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS) + { + SetCondition(condition); + } + } + else if ( capability & bits_CAP_INNATE_RANGE_ATTACK1 ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack1Condition ); + + condition = RangeAttack1Conditions( flDot, flDist ); + if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS) + { + SetCondition(condition); + } + } + + if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK2) && bWeaponIsReady && ( GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK2 ) ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack2Condition ); + + condition = GetActiveWeapon()->WeaponRangeAttack2Condition(flDot, flDist); + if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS) + { + SetCondition(condition); + } + } + else if ( capability & bits_CAP_INNATE_RANGE_ATTACK2 ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack2Condition ); + + condition = RangeAttack2Conditions( flDot, flDist ); + if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS) + { + SetCondition(condition); + } + } + + if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK1) && bWeaponIsReady) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack1Condition ); + SetCondition(GetActiveWeapon()->WeaponMeleeAttack1Condition(flDot, flDist)); + } + else if ( capability & bits_CAP_INNATE_MELEE_ATTACK1 ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack1Condition ); + SetCondition(MeleeAttack1Conditions ( flDot, flDist )); + } + + if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK2) && bWeaponIsReady) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack2Condition ); + SetCondition(GetActiveWeapon()->WeaponMeleeAttack2Condition(flDot, flDist)); + } + else if ( capability & bits_CAP_INNATE_MELEE_ATTACK2 ) + { + AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack2Condition ); + SetCondition(MeleeAttack2Conditions ( flDot, flDist )); + } + + // ----------------------------------------------------------------- + // If any attacks are possible clear attack specific bits + // ----------------------------------------------------------------- + if (HasCondition(COND_CAN_RANGE_ATTACK2) || + HasCondition(COND_CAN_RANGE_ATTACK1) || + HasCondition(COND_CAN_MELEE_ATTACK2) || + HasCondition(COND_CAN_MELEE_ATTACK1) ) + { + ClearCondition(COND_TOO_CLOSE_TO_ATTACK); + ClearCondition(COND_TOO_FAR_TO_ATTACK); + ClearCondition(COND_WEAPON_BLOCKED_BY_FRIEND); + } +} + + +//========================================================= +// SetState +//========================================================= +void CAI_BaseNPC::SetState( NPC_STATE State ) +{ + NPC_STATE OldState; + + OldState = m_NPCState; + + if ( State != m_NPCState ) + { + m_flLastStateChangeTime = gpGlobals->curtime; + } + + switch( State ) + { + // Drop enemy pointers when going to idle + case NPC_STATE_IDLE: + + if ( GetEnemy() != NULL ) + { + SetEnemy( NULL ); // not allowed to have an enemy anymore. + DevMsg( 2, "Stripped\n" ); + } + break; + + default: + break; + } + + bool fNotifyChange = false; + + if( m_NPCState != State ) + { + // Don't notify if we're changing to a state we're already in! + fNotifyChange = true; + } + + m_NPCState = State; + SetIdealState( State ); + + // Notify the character that its state has changed. + if( fNotifyChange ) + { + OnStateChange( OldState, m_NPCState ); + } +} + +bool CAI_BaseNPC::WokeThisTick() const +{ + return m_nWakeTick == gpGlobals->tickcount ? true : false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::Wake( bool bFireOutput ) +{ + if ( GetSleepState() != AISS_AWAKE ) + { + m_nWakeTick = gpGlobals->tickcount; + SetSleepState( AISS_AWAKE ); + RemoveEffects( EF_NODRAW ); + if ( bFireOutput ) + m_OnWake.FireOutput( this, this ); + + if ( m_bWakeSquad && GetSquad() ) + { + AISquadIter_t iter; + for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) ) + { + if ( pSquadMember->IsAlive() && pSquadMember != this ) + { + pSquadMember->m_bWakeSquad = false; + pSquadMember->Wake(); + } + } + + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::Sleep() +{ + // Don't render. + AddEffects( EF_NODRAW ); + + if( GetState() == NPC_STATE_SCRIPT ) + { + Warning( "%s put to sleep while in Scripted state!\n"); + } + + VacateStrategySlot(); + + // Slam my schedule. + SetSchedule( SCHED_SLEEP ); + + m_OnSleep.FireOutput( this, this ); +} + +//----------------------------------------------------------------------------- +// Sets all sensing-related conditions +//----------------------------------------------------------------------------- +void CAI_BaseNPC::PerformSensing( void ) +{ + GetSenses()->PerformSensing(); +} + + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::ClearSenseConditions( void ) +{ + static int conditionsToClear[] = + { + COND_SEE_HATE, + COND_SEE_DISLIKE, + COND_SEE_ENEMY, + COND_SEE_FEAR, + COND_SEE_NEMESIS, + COND_SEE_PLAYER, + COND_HEAR_DANGER, + COND_HEAR_COMBAT, + COND_HEAR_WORLD, + COND_HEAR_PLAYER, + COND_HEAR_THUMPER, + COND_HEAR_BUGBAIT, + COND_HEAR_PHYSICS_DANGER, + COND_HEAR_MOVE_AWAY, + COND_SMELL, + }; + + ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::CheckOnGround( void ) +{ + bool bScriptedWait = ( IsCurSchedule( SCHED_WAIT_FOR_SCRIPT ) || ( m_hCine && m_scriptState == CAI_BaseNPC::SCRIPT_WAIT ) ); + if ( !bScriptedWait && !HasCondition( COND_FLOATING_OFF_GROUND ) ) + { + // parented objects are never floating + if (GetMoveParent() != NULL) + return; + + if ( ( GetNavType() == NAV_GROUND ) && ( GetMoveType() != MOVETYPE_VPHYSICS ) && ( GetMoveType() != MOVETYPE_NONE ) ) + { + if ( m_CheckOnGroundTimer.Expired() ) + { + m_CheckOnGroundTimer.Set(0.5); + + // check a shrunk box centered around the foot + Vector maxs = WorldAlignMaxs(); + Vector mins = WorldAlignMins(); + + if ( mins != maxs ) // some NPCs have no hull, so mins == maxs == vec3_origin + { + maxs -= Vector( 0.0f, 0.0f, 0.2f ); + + Vector vecStart = GetAbsOrigin() + Vector( 0, 0, .1f ); + Vector vecDown = GetAbsOrigin(); + vecDown.z -= 4.0; + + trace_t trace; + m_pMoveProbe->TraceHull( vecStart, vecDown, mins, maxs, MASK_NPCSOLID, &trace ); + + if (trace.fraction == 1.0) + { + SetCondition( COND_FLOATING_OFF_GROUND ); + SetGroundEntity( NULL ); + } + else + { + if ( trace.startsolid && trace.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS && + trace.m_pEnt->VPhysicsGetObject() && trace.m_pEnt->VPhysicsGetObject()->GetMass() < VPHYSICS_LARGE_OBJECT_MASS ) + { + // stuck inside a small physics object? + m_CheckOnGroundTimer.Set(0.1f); + NPCPhysics_CreateSolver( this, trace.m_pEnt, true, 0.25f ); + if ( VPhysicsGetObject() ) + { + VPhysicsGetObject()->RecheckContactPoints(); + } + } + // Check to see if someone changed the ground on us... + if ( trace.m_pEnt && trace.m_pEnt != GetGroundEntity() ) + { + SetGroundEntity( trace.m_pEnt ); + } + } + } + } + } + } + else + { + // parented objects are never floating + if ( bScriptedWait || GetMoveParent() != NULL || (GetFlags() & FL_ONGROUND ) || GetNavType() != NAV_GROUND ) + { + ClearCondition( COND_FLOATING_OFF_GROUND ); + } + } + +} + +void CAI_BaseNPC::NotifyPushMove() +{ + // don't recheck ground when I'm being push-moved + m_CheckOnGroundTimer.Set( 0.5f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::CanFlinch( void ) +{ + if ( IsCurSchedule( SCHED_BIG_FLINCH ) ) + return false; + + if ( m_flNextFlinchTime >= gpGlobals->curtime ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CheckFlinches( void ) +{ + // If we're currently flinching, don't allow gesture flinches to be overlaid + if ( IsCurSchedule( SCHED_BIG_FLINCH ) ) + { + ClearCondition( COND_LIGHT_DAMAGE ); + ClearCondition( COND_HEAVY_DAMAGE ); + } + + // If we've taken heavy damage, try to do a full schedule flinch + if ( HasCondition(COND_HEAVY_DAMAGE) ) + { + // If we've already flinched recently, gesture flinch instead. + if ( HasMemory(bits_MEMORY_FLINCHED) ) + { + // Clear the heavy damage condition so we don't interrupt schedules + // when we play a gesture flinch because we recently did a full flinch. + // Prevents the player from stun-locking enemies, even though they don't full flinch. + ClearCondition( COND_HEAVY_DAMAGE ); + } + else if ( !HasInterruptCondition(COND_HEAVY_DAMAGE) ) + { + // If we have taken heavy damage, but the current schedule doesn't + // break on that, resort to just playing a gesture flinch. + PlayFlinchGesture(); + } + + // Otherwise, do nothing. The heavy damage will interrupt our schedule and we'll flinch. + } + else if ( HasCondition( COND_LIGHT_DAMAGE ) ) + { + // If we have taken light damage play gesture flinches + PlayFlinchGesture(); + } + + // If it's been a while since we did a full flinch, forget that we flinched so we'll flinch fully again + if ( HasMemory(bits_MEMORY_FLINCHED) && gpGlobals->curtime > m_flNextFlinchTime ) + { + Forget(bits_MEMORY_FLINCHED); + } +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::GatherConditions( void ) +{ + m_bConditionsGathered = true; + g_AIConditionsTimer.Start(); + + if ( m_NPCState != NPC_STATE_NONE && m_NPCState != NPC_STATE_DEAD ) + { + if ( FacingIdeal() ) + Forget( bits_MEMORY_TURNING ); + + bool bForcedGather = m_bForceConditionsGather; + m_bForceConditionsGather = false; + + if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink ) + { + if ( UTIL_FindClientInPVS( edict() ) != NULL ) + SetCondition( COND_IN_PVS ); + else + ClearCondition( COND_IN_PVS ); + } + + // Sample the environment. Do this unconditionally if there is a player in this + // npc's PVS. NPCs in COMBAT state are allowed to simulate when there is no player in + // the same PVS. This is so that any fights in progress will continue even if the player leaves the PVS. + if ( !IsFlaggedEfficient() && + ( bForcedGather || + HasCondition( COND_IN_PVS ) || + ShouldAlwaysThink() || + m_NPCState == NPC_STATE_COMBAT ) ) + { + CheckOnGround(); + + if ( ShouldPlayIdleSound() ) + { + AI_PROFILE_SCOPE(CAI_BaseNPC_IdleSound); + IdleSound(); + } + + PerformSensing(); + + GetEnemies()->RefreshMemories(); + ChooseEnemy(); + + // Check to see if there is a better weapon available + if (Weapon_IsBetterAvailable()) + { + SetCondition(COND_BETTER_WEAPON_AVAILABLE); + } + + if ( GetCurSchedule() && + ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT) && + GetEnemy() && + !HasCondition( COND_NEW_ENEMY ) && + GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY ) ) + { + // @Note (toml 05-05-04): There seems to be a case where an NPC can not respond + // to COND_NEW_ENEMY. Only evidence right now is save + // games after the fact, so for now, just patching it up + DevMsg( 2, "Had to force COND_NEW_ENEMY\n" ); + SetCondition(COND_NEW_ENEMY); + } + } + else + { + // if not done, can have problems if leave PVS in same frame heard/saw things, + // since only PerformSensing clears conditions + ClearSenseConditions(); + } + + // do these calculations if npc has an enemy. + if ( GetEnemy() != NULL ) + { + if ( !IsFlaggedEfficient() ) + { + GatherEnemyConditions( GetEnemy() ); + m_flLastEnemyTime = gpGlobals->curtime; + } + else + { + SetEnemy( NULL ); + } + } + + // do these calculations if npc has a target + if ( GetTarget() != NULL ) + { + CheckTarget( GetTarget() ); + } + + CheckAmmo(); + + CheckFlinches(); + + CheckSquad(); + } + else + ClearCondition( COND_IN_PVS ); + + RemoveIgnoredConditions(); + + g_AIConditionsTimer.End(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::PrescheduleThink( void ) +{ +#ifdef HL2_EPISODIC + CheckForScriptedNPCInteractions(); +#endif + + // If we use weapons, and our desired weapon state is not the current, fix it + if( (CapabilitiesGet() & bits_CAP_USE_WEAPONS) && (m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) ) + { + if ( IsAlive() && !IsInAScript() ) + { + if ( !IsCurSchedule( SCHED_MELEE_ATTACK1, false ) && !IsCurSchedule( SCHED_MELEE_ATTACK2, false ) && + !IsCurSchedule( SCHED_RANGE_ATTACK1, false ) && !IsCurSchedule( SCHED_RANGE_ATTACK2, false ) ) + { + if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) + { + HolsterWeapon(); + } + else if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED ) + { + UnholsterWeapon(); + } + } + } + else + { + // Throw away the request + m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; + } + } +} + +//----------------------------------------------------------------------------- +// Main entry point for processing AI +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::RunAI( void ) +{ + AI_PROFILE_SCOPE(CAI_BaseNPC_RunAI); + g_AIRunTimer.Start(); + + if( ai_debug_squads.GetBool() ) + { + if( IsInSquad() && GetSquad() && !CAI_Squad::IsSilentMember(this ) && ( GetSquad()->IsLeader( this ) || GetSquad()->NumMembers() == 1 ) ) + { + AISquadIter_t iter; + CAI_Squad *pSquad = GetSquad(); + + Vector right; + Vector vecPoint; + + vecPoint = EyePosition() + Vector( 0, 0, 12 ); + GetVectors( NULL, &right, NULL ); + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 0, 255, 0, false , 0.1 ); + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 0, 255, 0, false , 0.1 ); + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 0, 255, 0, false , 0.1 ); + + for ( CAI_BaseNPC *pSquadMember = pSquad->GetFirstMember( &iter, false ); pSquadMember; pSquadMember = pSquad->GetNextMember( &iter, false ) ) + { + if ( pSquadMember != this ) + NDebugOverlay::Line( EyePosition(), pSquadMember->EyePosition(), 0, + CAI_Squad::IsSilentMember(pSquadMember) ? 127 : 255, 0, false , 0.1 ); + } + } + } + + if( ai_debug_loners.GetBool() && !IsInSquad() && AI_IsSinglePlayer() ) + { + Vector right; + Vector vecPoint; + + vecPoint = EyePosition() + Vector( 0, 0, 12 ); + + UTIL_GetLocalPlayer()->GetVectors( NULL, &right, NULL ); + + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 255, 0, 0, false , 0.1 ); + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 255, 0, 0, false , 0.1 ); + NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 255, 0, 0, false , 0.1 ); + } + +#ifdef _DEBUG + m_bSelected = ( (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) != 0 ); +#endif + + m_bConditionsGathered = false; + m_bSkippedChooseEnemy = false; + + if ( g_pDeveloper->GetInt() && !GetNavigator()->IsOnNetwork() ) + { + AddTimedOverlay( "NPC w/no reachable nodes!", 5 ); + } + + AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_RunAI_GatherConditions); + GatherConditions(); + AI_PROFILE_SCOPE_END(); + + if ( !m_bConditionsGathered ) + m_bConditionsGathered = true; // derived class didn't call to base + + TryRestoreHull(); + + g_AIPrescheduleThinkTimer.Start(); + + AI_PROFILE_SCOPE_BEGIN(CAI_RunAI_PrescheduleThink); + PrescheduleThink(); + AI_PROFILE_SCOPE_END(); + + g_AIPrescheduleThinkTimer.End(); + + MaintainSchedule(); + + PostscheduleThink(); + + ClearTransientConditions(); + + g_AIRunTimer.End(); +} + +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ClearTransientConditions() +{ + // if the npc didn't use these conditions during the above call to MaintainSchedule() + // we throw them out cause we don't want them sitting around through the lifespan of a schedule + // that doesn't use them. + ClearCondition( COND_LIGHT_DAMAGE ); + ClearCondition( COND_HEAVY_DAMAGE ); + ClearCondition( COND_PHYSICS_DAMAGE ); + ClearCondition( COND_PLAYER_PUSHING ); +} + + +//----------------------------------------------------------------------------- +// Selecting the idle ideal state +//----------------------------------------------------------------------------- +NPC_STATE CAI_BaseNPC::SelectIdleIdealState() +{ + // 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 + if ( HasCondition(COND_NEW_ENEMY) || + HasCondition(COND_SEE_ENEMY) ) + { + // new enemy! This means an idle npc has seen someone it dislikes, or + // that a npc in combat has found a more suitable target to attack + return NPC_STATE_COMBAT; + } + + // Set our ideal yaw if we've taken damage + if ( HasCondition(COND_LIGHT_DAMAGE) || + HasCondition(COND_HEAVY_DAMAGE) || + (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) ) + { + Vector vecEnemyLKP; + + // Fill in where we're trying to look + if ( GetEnemy() ) + { + vecEnemyLKP = GetEnemyLKP(); + } + else + { + if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) ) + { + vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY ); + } + else + { + // Don't have an enemy, so face the direction the last attack came from (don't face north) + vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 ); + } + } + + // Set the ideal + GetMotor()->SetIdealYawToTarget( vecEnemyLKP ); + + return NPC_STATE_ALERT; + } + + if ( HasCondition(COND_HEAR_DANGER) || + HasCondition(COND_HEAR_COMBAT) || + HasCondition(COND_HEAR_WORLD) || + HasCondition(COND_HEAR_PLAYER) || + HasCondition(COND_HEAR_THUMPER) || + HasCondition(COND_HEAR_BULLET_IMPACT) ) + { + // Interrupted by a sound. So make our ideal yaw the + // source of the sound! + CSound *pSound; + + pSound = GetBestSound(); + Assert( pSound != NULL ); + if ( pSound ) + { + // BRJ 1/7/04: This code used to set the ideal yaw. + // It's really side-effecty to set the yaw here. + // That is now done by the FACE_BESTSOUND schedule. + // Revert this change if it causes problems. + GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() ); + if ( pSound->IsSoundType( SOUND_COMBAT | SOUND_DANGER | SOUND_BULLET_IMPACT ) ) + { + return NPC_STATE_ALERT; + } + } + } + + if ( HasInterruptCondition(COND_SMELL) ) + { + return NPC_STATE_ALERT; + } + + return NPC_STATE_INVALID; +} + + +//----------------------------------------------------------------------------- +// Selecting the alert ideal state +//----------------------------------------------------------------------------- +NPC_STATE CAI_BaseNPC::SelectAlertIdealState() +{ + // ALERT goes to IDLE upon becoming bored + // ALERT goes to COMBAT upon sighting an enemy + if ( HasCondition(COND_NEW_ENEMY) || + HasCondition(COND_SEE_ENEMY) || + GetEnemy() != NULL ) + { + return NPC_STATE_COMBAT; + } + + // Set our ideal yaw if we've taken damage + if ( HasCondition(COND_LIGHT_DAMAGE) || + HasCondition(COND_HEAVY_DAMAGE) || + (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) ) + { + Vector vecEnemyLKP; + + // Fill in where we're trying to look + if ( GetEnemy() ) + { + vecEnemyLKP = GetEnemyLKP(); + } + else + { + if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) ) + { + vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY ); + } + else + { + // Don't have an enemy, so face the direction the last attack came from (don't face north) + vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 ); + } + } + + // Set the ideal + GetMotor()->SetIdealYawToTarget( vecEnemyLKP ); + + return NPC_STATE_ALERT; + } + + if ( HasCondition(COND_HEAR_DANGER) || + HasCondition(COND_HEAR_COMBAT) ) + { + CSound *pSound = GetBestSound(); + AssertOnce( pSound != NULL ); + + if ( pSound ) + { + GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() ); + } + + return NPC_STATE_ALERT; + } + + if ( ShouldGoToIdleState() ) + { + return NPC_STATE_IDLE; + } + + return NPC_STATE_INVALID; +} + + +//----------------------------------------------------------------------------- +// Selecting the alert ideal state +//----------------------------------------------------------------------------- +NPC_STATE CAI_BaseNPC::SelectScriptIdealState() +{ + if ( HasCondition(COND_TASK_FAILED) || + HasCondition(COND_LIGHT_DAMAGE) || + HasCondition(COND_HEAVY_DAMAGE) ) + { + ExitScriptedSequence(); // This will set the ideal state + } + + if ( m_IdealNPCState == NPC_STATE_IDLE ) + { + // Exiting a script. Select the ideal state assuming we were idle now. + m_NPCState = NPC_STATE_IDLE; + NPC_STATE eIdealState = SelectIdealState(); + m_NPCState = NPC_STATE_SCRIPT; + return eIdealState; + } + + return NPC_STATE_INVALID; +} + + +//----------------------------------------------------------------------------- +// Purpose: Surveys the Conditions information available and finds the best +// new state for a npc. +// +// NOTICE the CAI_BaseNPC implementation of this function does not care about +// private conditions! +// +// Output : NPC_STATE - the suggested ideal state based on current conditions. +//----------------------------------------------------------------------------- +NPC_STATE CAI_BaseNPC::SelectIdealState( void ) +{ + // dvs: FIXME: lots of side effecty code in here!! this function should ONLY return an ideal state! + + // --------------------------- + // Do some squad stuff first + // --------------------------- + if (m_pSquad) + { + switch( m_NPCState ) + { + case NPC_STATE_IDLE: + case NPC_STATE_ALERT: + if ( HasCondition ( COND_NEW_ENEMY ) ) + { + m_pSquad->SquadNewEnemy( GetEnemy() ); + } + break; + default: + break; + } + } + + // --------------------------- + // Set ideal state + // --------------------------- + switch ( m_NPCState ) + { + case NPC_STATE_IDLE: + { + NPC_STATE nState = SelectIdleIdealState(); + if ( nState != NPC_STATE_INVALID ) + return nState; + } + break; + + case NPC_STATE_ALERT: + { + NPC_STATE nState = SelectAlertIdealState(); + if ( nState != NPC_STATE_INVALID ) + return nState; + } + break; + + case NPC_STATE_COMBAT: + // COMBAT goes to ALERT upon death of enemy + { + if ( GetEnemy() == NULL ) + { + DevWarning( 2, "***Combat state with no enemy!\n" ); + return NPC_STATE_ALERT; + } + break; + } + case NPC_STATE_SCRIPT: + { + NPC_STATE nState = SelectScriptIdealState(); + if ( nState != NPC_STATE_INVALID ) + return nState; + } + break; + + case NPC_STATE_DEAD: + return NPC_STATE_DEAD; + break; + + default: + break; + } + + // The best ideal state is the current ideal state. + return m_IdealNPCState; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CAI_BaseNPC::GiveWeapon( string_t iszWeaponName ) +{ + CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(iszWeaponName) ); + if ( !pWeapon ) + { + Warning( "Couldn't create weapon %s to give NPC %s.\n", STRING(iszWeaponName), STRING(GetEntityName()) ); + return; + } + + // If I have a weapon already, drop it + if ( GetActiveWeapon() ) + { + Weapon_Drop( GetActiveWeapon() ); + } + + // If I have a name, make my weapon match it with "_weapon" appended + if ( GetEntityName() != NULL_STRING ) + { + pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", STRING(GetEntityName()))) ); + } + + Weapon_Equip( pWeapon ); + + // Handle this case + OnGivenWeapon( pWeapon ); +} + +//----------------------------------------------------------------------------- +// Rather specific function that tells us if an NPC is in the process of +// moving to a weapon with the intent to pick it up. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsMovingToPickupWeapon() +{ + return IsCurSchedule( SCHED_NEW_WEAPON ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::ShouldLookForBetterWeapon() +{ + if( m_flNextWeaponSearchTime > gpGlobals->curtime ) + return false; + + if( !(CapabilitiesGet() & bits_CAP_USE_WEAPONS) ) + return false; + + // Already armed and currently fighting. Don't try to upgrade. + if( GetActiveWeapon() && m_NPCState == NPC_STATE_COMBAT ) + return false; + + if( IsMovingToPickupWeapon() ) + return false; + + if( !IsPlayerAlly() && GetActiveWeapon() ) + return false; + + if( IsInAScript() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Check if a better weapon is available. +// For now check if there is a weapon and I don't have one. In +// the future +// UNDONE: actually rate the weapons based on there strength +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::Weapon_IsBetterAvailable() +{ + if( m_iszPendingWeapon != NULL_STRING ) + { + // Some weapon is reserved for us. + return true; + } + + if( ShouldLookForBetterWeapon() ) + { + if( GetActiveWeapon() ) + { + m_flNextWeaponSearchTime = gpGlobals->curtime + 2; + } + else + { + if( IsInPlayerSquad() ) + { + // Look for a weapon frequently. + m_flNextWeaponSearchTime = gpGlobals->curtime + 1; + } + else + { + m_flNextWeaponSearchTime = gpGlobals->curtime + 2; + } + } + + if ( Weapon_FindUsable( WEAPON_SEARCH_DELTA ) ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true is weapon has a line of sight. If bSetConditions is +// true, also sets LOC conditions +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ) +{ +#if 0 + // @TODO (toml 03-07-04): this code might be better (not tested) + Vector vecLocalRelativePosition; + VectorITransform( npcOwner->Weapon_ShootPosition(), npcOwner->EntityToWorldTransform(), vecLocalRelativePosition ); + + // Compute desired test transform + + // Compute desired x axis + Vector xaxis; + VectorSubtract( targetPos, ownerPos, xaxis ); + + // FIXME: Insert angle test here? + float flAngle = acos( xaxis.z / xaxis.Length() ); + + xaxis.z = 0.0f; + float flLength = VectorNormalize( xaxis ); + if ( flLength < 1e-3 ) + return false; + + Vector yaxis( -xaxis.y, xaxis.x, 0.0f ); + + matrix3x4_t losTestToWorld; + MatrixInitialize( losTestToWorld, ownerPos, xaxis, yaxis, zaxis ); + + Vector barrelPos; + VectorTransform( vecLocalRelativePosition, losTestToWorld, barrelPos ); + +#endif + + bool bHaveLOS; + + if (GetActiveWeapon()) + { + bHaveLOS = GetActiveWeapon()->WeaponLOSCondition(ownerPos, targetPos, bSetConditions); + } + else if (CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1) + { + bHaveLOS = InnateWeaponLOSCondition(ownerPos, targetPos, bSetConditions); + } + else + { + if (bSetConditions) + { + SetCondition( COND_NO_WEAPON ); + } + bHaveLOS = false; + } + // ------------------------------------------- + // Check for friendly fire with the player + // ------------------------------------------- + if ( CapabilitiesGet() & ( bits_CAP_NO_HIT_PLAYER | bits_CAP_NO_HIT_SQUADMATES ) ) + { + float spread = 0.92; + if ( GetActiveWeapon() ) + { + Vector vSpread = GetAttackSpread( GetActiveWeapon() ); + if ( vSpread.x > VECTOR_CONE_15DEGREES.x ) + spread = TableCos(asin(vSpread.x)); + else // too much error because using point not box + spread = 0.99145; // "15 degrees" + } + if (CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) + { + // Check shoot direction relative to player + if (PlayerInSpread( ownerPos, targetPos, spread, 8*12 )) + { + if (bSetConditions) + { + SetCondition( COND_WEAPON_PLAYER_IN_SPREAD ); + } + bHaveLOS = false; + } + /* For grenades etc. check that player is clear? + // Check player position also + if (PlayerInRange( targetPos, 100 )) + { + SetCondition( COND_WEAPON_PLAYER_NEAR_TARGET ); + } + */ + } + + if ( bHaveLOS ) + { + if ( (CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && m_pSquad && GetEnemy() ) + { + if ( IsSquadmateInSpread( ownerPos, targetPos, spread, 8*12 ) ) + bHaveLOS = false; + } + } + } + return bHaveLOS; +} + +//----------------------------------------------------------------------------- +// Purpose: Check the innate weapon LOS for an owner at an arbitrary position +// If bSetConditions is true, LOS related conditions will also be set +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ) +{ + // -------------------- + // Check for occlusion + // -------------------- + // Base class version assumes innate weapon position is at eye level + Vector barrelPos = ownerPos + GetViewOffset(); + trace_t tr; + AI_TraceLine( barrelPos, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); + + if ( tr.fraction == 1.0 ) + { + return true; + } + + CBaseEntity *pHitEntity = tr.m_pEnt; + + // Translate a hit vehicle into its passenger if found + if ( GetEnemy() != NULL ) + { + CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer(); + if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() ) + { + // Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is + // Also, check to see if the owner of the entity is the vehicle, in which case it's valid too. + // This catches vehicles that use bone followers. + CBaseEntity *pVehicleEnt = pCCEnemy->GetVehicleEntity(); + if ( pHitEntity == pVehicleEnt || pHitEntity->GetOwnerEntity() == pVehicleEnt ) + return true; + } + } + + if ( pHitEntity == GetEnemy() ) + { + return true; + } + else if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() ) + { + if (IRelationType( pHitEntity ) == D_HT) + { + return true; + } + else if (bSetConditions) + { + SetCondition(COND_WEAPON_BLOCKED_BY_FRIEND); + } + } + else if (bSetConditions) + { + SetCondition(COND_WEAPON_SIGHT_OCCLUDED); + SetEnemyOccluder(tr.m_pEnt); + } + + return false; +} + +//========================================================= +// CanCheckAttacks - prequalifies a npc to do more fine +// checking of potential attacks. +//========================================================= +bool CAI_BaseNPC::FCanCheckAttacks( void ) +{ + // Not allowed to check attacks while climbing or jumping + // Otherwise schedule is interrupted while on ladder/etc + // which is NOT a legal place to attack from + if ( GetNavType() == NAV_CLIMB || GetNavType() == NAV_JUMP ) + return false; + + if ( HasCondition(COND_SEE_ENEMY) && !HasCondition( COND_ENEMY_TOO_FAR)) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Return dist. to enemy (closest of origin/head/feet) +// Input : +// Output : +//----------------------------------------------------------------------------- +float CAI_BaseNPC::EnemyDistance( CBaseEntity *pEnemy ) +{ + Vector enemyDelta = pEnemy->WorldSpaceCenter() - WorldSpaceCenter(); + + // NOTE: We ignore rotation for computing height. Assume it isn't an effect + // we care about, so we simply use OBBSize().z for height. + // Otherwise you'd do this: + // pEnemy->CollisionProp()->WorldSpaceSurroundingBounds( &enemyMins, &enemyMaxs ); + // float enemyHeight = enemyMaxs.z - enemyMins.z; + + float enemyHeight = pEnemy->CollisionProp()->OBBSize().z; + float myHeight = CollisionProp()->OBBSize().z; + + // max distance our centers can be apart with the boxes still overlapping + float flMaxZDist = ( enemyHeight + myHeight ) * 0.5f; + + // see if the enemy is closer to my head, feet or in between + if ( enemyDelta.z > flMaxZDist ) + { + // enemy feet above my head, compute distance from my head to his feet + enemyDelta.z -= flMaxZDist; + } + else if ( enemyDelta.z < -flMaxZDist ) + { + // enemy head below my feet, return distance between my feet and his head + enemyDelta.z += flMaxZDist; + } + else + { + // boxes overlap in Z, no delta + enemyDelta.z = 0; + } + + return enemyDelta.Length(); +} + +//----------------------------------------------------------------------------- + +float CAI_BaseNPC::GetReactionDelay( CBaseEntity *pEnemy ) +{ + return ( m_NPCState == NPC_STATE_ALERT || m_NPCState == NPC_STATE_COMBAT ) ? + ai_reaction_delay_alert.GetFloat() : + ai_reaction_delay_idle.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Purpose: Update information on my enemy +// Input : +// Output : Returns true is new enemy, false is known enemy +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer ) +{ + bool firstHand = ( pInformer == NULL || pInformer == this ); + + AI_PROFILE_SCOPE(CAI_BaseNPC_UpdateEnemyMemory); + + if ( GetEnemies() ) + { + // If the was eluding me and allow the NPC to play a sound + if (GetEnemies()->HasEludedMe(pEnemy)) + { + FoundEnemySound(); + } + float reactionDelay = ( !pInformer || pInformer == this ) ? GetReactionDelay( pEnemy ) : 0.0; + bool result = GetEnemies()->UpdateMemory(GetNavigator()->GetNetwork(), pEnemy, position, reactionDelay, firstHand); + + if ( !firstHand && pEnemy && result && GetState() == NPC_STATE_IDLE ) // if it's a new potential enemy + ForceDecisionThink(); + + if ( firstHand && pEnemy && m_pSquad ) + { + m_pSquad->UpdateEnemyMemory( this, pEnemy, position ); + } + return result; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Remembers the thing my enemy is hiding behind. Called when either +// COND_ENEMY_OCCLUDED or COND_WEAPON_SIGHT_OCCLUDED is set. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::SetEnemyOccluder(CBaseEntity *pBlocker) +{ + m_hEnemyOccluder = pBlocker; +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets the thing my enemy is hiding behind (assuming they are hiding). +//----------------------------------------------------------------------------- +CBaseEntity *CAI_BaseNPC::GetEnemyOccluder(void) +{ + return m_hEnemyOccluder.Get(); +} + + +//----------------------------------------------------------------------------- +// Purpose: part of the Condition collection process +// gets and stores data and conditions pertaining to a npc's +// enemy. +// @TODO (toml 07-27-03): this should become subservient to the senses. right +// now, it yields different result +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::GatherEnemyConditions( CBaseEntity *pEnemy ) +{ + AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions); + + ClearCondition( COND_ENEMY_FACING_ME ); + ClearCondition( COND_BEHIND_ENEMY ); + + // --------------------------- + // Set visibility conditions + // --------------------------- + if ( HasCondition( COND_NEW_ENEMY ) || GetSenses()->GetTimeLastUpdate( GetEnemy() ) == gpGlobals->curtime ) + { + AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_Visibility); + + ClearCondition( COND_HAVE_ENEMY_LOS ); + ClearCondition( COND_ENEMY_OCCLUDED ); + + CBaseEntity *pBlocker = NULL; + SetEnemyOccluder(NULL); + + bool bSensesDidSee = GetSenses()->DidSeeEntity( pEnemy ); + + if ( !bSensesDidSee && ( ( EnemyDistance( pEnemy ) >= GetSenses()->GetDistLook() ) || !FVisible( pEnemy, MASK_OPAQUE, &pBlocker ) ) ) + { + // No LOS to enemy + SetEnemyOccluder(pBlocker); + SetCondition( COND_ENEMY_OCCLUDED ); + ClearCondition( COND_SEE_ENEMY ); + + if (HasMemory( bits_MEMORY_HAD_LOS )) + { + AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs); + // Send output event + if (GetEnemy()->IsPlayer()) + { + m_OnLostPlayerLOS.FireOutput( GetEnemy(), this ); + } + m_OnLostEnemyLOS.FireOutput( GetEnemy(), this ); + } + Forget( bits_MEMORY_HAD_LOS ); + } + else + { + // Have LOS but may not be in view cone + SetCondition( COND_HAVE_ENEMY_LOS ); + + if ( bSensesDidSee ) + { + // Have LOS and in view cone + SetCondition( COND_SEE_ENEMY ); + } + else + { + ClearCondition( COND_SEE_ENEMY ); + } + + if (!HasMemory( bits_MEMORY_HAD_LOS )) + { + AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs); + // Send output event + EHANDLE hEnemy; + hEnemy.Set( GetEnemy() ); + + if (GetEnemy()->IsPlayer()) + { + m_OnFoundPlayer.Set(hEnemy, this, this); + m_OnFoundEnemy.Set(hEnemy, this, this); + } + else + { + m_OnFoundEnemy.Set(hEnemy, this, this); + } + } + Remember( bits_MEMORY_HAD_LOS ); + } + + AI_PROFILE_SCOPE_END(); + } + + // ------------------- + // If enemy is dead + // ------------------- + if ( !pEnemy->IsAlive() ) + { + SetCondition( COND_ENEMY_DEAD ); + ClearCondition( COND_SEE_ENEMY ); + ClearCondition( COND_ENEMY_OCCLUDED ); + return; + } + + float flDistToEnemy = EnemyDistance(pEnemy); + + AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_SeeEnemy); + + if ( HasCondition( COND_SEE_ENEMY ) ) + { + // Trail the enemy a bit if he's moving + if (pEnemy->GetSmoothedVelocity() != vec3_origin) + { + Vector vTrailPos = pEnemy->GetAbsOrigin() - pEnemy->GetSmoothedVelocity() * random->RandomFloat( -0.05, 0 ); + UpdateEnemyMemory(pEnemy,vTrailPos); + } + else + { + UpdateEnemyMemory(pEnemy,pEnemy->GetAbsOrigin()); + } + + // If it's not an NPC, assume it can't see me + if ( pEnemy->MyCombatCharacterPointer() && pEnemy->MyCombatCharacterPointer()->FInViewCone ( this ) ) + { + SetCondition ( COND_ENEMY_FACING_ME ); + ClearCondition ( COND_BEHIND_ENEMY ); + } + else + { + ClearCondition( COND_ENEMY_FACING_ME ); + SetCondition ( COND_BEHIND_ENEMY ); + } + } + else if ( (!HasCondition(COND_ENEMY_OCCLUDED) && !HasCondition(COND_SEE_ENEMY)) && ( flDistToEnemy <= 256 ) ) + { + // if the enemy is not occluded, and unseen, that means it is behind or beside the npc. + // if the enemy is near enough the npc, we go ahead and let the npc know where the + // enemy is. Send the enemy in as the informer so this knowledge will be regarded as + // secondhand so that the NPC doesn't + UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), pEnemy ); + } + + AI_PROFILE_SCOPE_END(); + + float tooFar = m_flDistTooFar; + if ( GetActiveWeapon() && HasCondition(COND_SEE_ENEMY) ) + { + tooFar = max( m_flDistTooFar, GetActiveWeapon()->m_fMaxRange1 ); + } + + if ( flDistToEnemy >= tooFar ) + { + // enemy is very far away from npc + SetCondition( COND_ENEMY_TOO_FAR ); + } + else + { + ClearCondition( COND_ENEMY_TOO_FAR ); + } + + if ( FCanCheckAttacks() ) + { + // This may also call SetEnemyOccluder! + GatherAttackConditions( GetEnemy(), flDistToEnemy ); + } + else + { + ClearAttackConditions(); + } + + // If my enemy has moved significantly, or if the enemy has changed update my path + UpdateEnemyPos(); + + // If my target entity has moved significantly, update my path + // This is an odd place to put this, but where else should it go? + UpdateTargetPos(); + + // ---------------------------------------------------------------------------- + // Check if enemy is reachable via the node graph unless I'm not on a network + // ---------------------------------------------------------------------------- + if (GetNavigator()->IsOnNetwork()) + { + // Note that unreachablity times out + if (IsUnreachable(GetEnemy())) + { + SetCondition(COND_ENEMY_UNREACHABLE); + } + } + + //----------------------------------------------------------------------- + // If I haven't seen the enemy in a while he may have eluded me + //----------------------------------------------------------------------- + if (gpGlobals->curtime - GetEnemyLastTimeSeen() > 8) + { + //----------------------------------------------------------------------- + // I'm at last known position at enemy isn't in sight then has eluded me + // ---------------------------------------------------------------------- + Vector flEnemyLKP = GetEnemyLKP(); + if (((flEnemyLKP - GetAbsOrigin()).Length2D() < 48) && + !HasCondition(COND_SEE_ENEMY)) + { + MarkEnemyAsEluded(); + } + //------------------------------------------------------------------- + // If enemy isn't reachable, I can see last known position and enemy + // isn't there, then he has eluded me + // ------------------------------------------------------------------ + if (!HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_UNREACHABLE)) + { + if ( !FVisible( flEnemyLKP ) ) + { + MarkEnemyAsEluded(); + } + } + } +} + + +//----------------------------------------------------------------------------- +// In the case of goaltype enemy, update the goal position +//----------------------------------------------------------------------------- +float CAI_BaseNPC::GetGoalRepathTolerance( CBaseEntity *pGoalEnt, GoalType_t type, const Vector &curGoal, const Vector &curTargetPos ) +{ + float distToGoal = ( GetAbsOrigin() - curTargetPos ).Length() - GetNavigator()->GetArrivalDistance(); + float distMoved1Sec = GetSmoothedVelocity().Length(); + float result = 120; // FIXME: why 120? + + if (distMoved1Sec > 0.0) + { + float t = distToGoal / distMoved1Sec; + + result = clamp( 120 * t, 0, 120 ); + // Msg("t %.2f : d %.0f (%.0f)\n", t, result, distMoved1Sec ); + } + + if ( !pGoalEnt->IsPlayer() ) + result *= 1.20; + + return result; +} + +//----------------------------------------------------------------------------- +// In the case of goaltype enemy, update the goal position +//----------------------------------------------------------------------------- +void CAI_BaseNPC::UpdateEnemyPos() +{ + // Don't perform path recomputations during a climb or a jump + if ( !GetNavigator()->IsInterruptable() ) + return; + + if ( m_AnyUpdateEnemyPosTimer.Expired() && m_UpdateEnemyPosTimer.Expired() ) + { + // FIXME: does GetGoalRepathTolerance() limit re-routing enough to remove this? + // m_UpdateEnemyPosTimer.Set( 0.5, 1.0 ); + + // If my enemy has moved significantly, or if the enemy has changed update my path + if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY ) + { + if (m_hEnemy != GetNavigator()->GetGoalTarget()) + { + GetNavigator()->SetGoalTarget( m_hEnemy, vec3_origin ); + } + else + { + Vector vEnemyLKP = GetEnemyLKP(); + TranslateNavGoal( GetEnemy(), vEnemyLKP ); + float tolerance = GetGoalRepathTolerance( GetEnemy(), GOALTYPE_ENEMY, GetNavigator()->GetGoalPos(), vEnemyLKP); + if ( (GetNavigator()->GetGoalPos() - vEnemyLKP).Length() > tolerance ) + { + // FIXME: when fleeing crowds, won't this severely limit the effectiveness of each individual? Shouldn't this be a mutex that's held for some period so that at least one attacker is effective? + m_AnyUpdateEnemyPosTimer.Set( 0.1 ); // FIXME: what's a reasonable interval? + if ( !GetNavigator()->RefindPathToGoal( false ) ) + { + TaskFail( FAIL_NO_ROUTE ); + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// In the case of goaltype targetent, update the goal position +//----------------------------------------------------------------------------- +void CAI_BaseNPC::UpdateTargetPos() +{ + // BRJ 10/7/02 + // FIXME: make this check time based instead of distance based! + + // Don't perform path recomputations during a climb or a jump + if ( !GetNavigator()->IsInterruptable() ) + return; + + // If my target entity has moved significantly, or has changed, update my path + // This is an odd place to put this, but where else should it go? + if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT ) + { + if (m_hTargetEnt != GetNavigator()->GetGoalTarget()) + { + GetNavigator()->SetGoalTarget( m_hTargetEnt, vec3_origin ); + } + else if ( GetNavigator()->GetGoalFlags() & AIN_UPDATE_TARGET_POS ) + { + if ( GetTarget() == NULL || (GetNavigator()->GetGoalPos() - GetTarget()->GetAbsOrigin()).Length() > GetGoalRepathTolerance( GetTarget(), GOALTYPE_TARGETENT, GetNavigator()->GetGoalPos(), GetTarget()->GetAbsOrigin()) ) + { + if ( !GetNavigator()->RefindPathToGoal( false ) ) + { + TaskFail( FAIL_NO_ROUTE ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: part of the Condition collection process +// gets and stores data and conditions pertaining to a npc's +// enemy. +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CheckTarget( CBaseEntity *pTarget ) +{ + AI_PROFILE_SCOPE(CAI_Enemies_CheckTarget); + + ClearCondition ( COND_HAVE_TARGET_LOS ); + ClearCondition ( COND_TARGET_OCCLUDED ); + + // --------------------------- + // Set visibility conditions + // --------------------------- + if ( ( EnemyDistance( pTarget ) >= GetSenses()->GetDistLook() ) || !FVisible( pTarget ) ) + { + // No LOS to target + SetCondition( COND_TARGET_OCCLUDED ); + } + else + { + // Have LOS (may not be in view cone) + SetCondition( COND_HAVE_TARGET_LOS ); + } + + UpdateTargetPos(); +} + +//----------------------------------------------------------------------------- +// Purpose: Creates a bullseye of limited lifespan at the provided position +// Input : vecOrigin - Where to create the bullseye +// duration - The lifespan of the bullseye +// Output : A BaseNPC pointer to the bullseye +// +// NOTES : It is the caller's responsibility to set up relationships with +// this bullseye! +//----------------------------------------------------------------------------- +CAI_BaseNPC *CAI_BaseNPC::CreateCustomTarget( const Vector &vecOrigin, float duration ) +{ +#ifdef HL2_DLL + CNPC_Bullseye *pTarget = (CNPC_Bullseye*)CreateEntityByName( "npc_bullseye" ); + + ASSERT( pTarget != NULL ); + + // Build a nonsolid bullseye and place it in the desired location + // The bullseye must take damage or the SetHealth 0 call will not be able + pTarget->AddSpawnFlags( SF_BULLSEYE_NONSOLID ); + pTarget->SetAbsOrigin( vecOrigin ); + pTarget->Spawn(); + + // Set it up to remove itself + variant_t value; + value.SetFloat(0); + g_EventQueue.AddEvent( pTarget, "SetHealth", value, 2.0, this, this ); + + return pTarget; +#else + return NULL; +#endif// HL2_DLL +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : eNewActivity - +// Output : Activity +//----------------------------------------------------------------------------- +Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity ) +{ + Assert( eNewActivity != ACT_INVALID ); + + if (eNewActivity == ACT_RANGE_ATTACK1) + { + if ( IsCrouching() ) + { + eNewActivity = ACT_RANGE_ATTACK1_LOW; + } + } + else if (eNewActivity == ACT_RELOAD) + { + if (IsCrouching()) + { + eNewActivity = ACT_RELOAD_LOW; + } + } + else if ( eNewActivity == ACT_IDLE ) + { + if ( IsCrouching() ) + { + eNewActivity = ACT_CROUCHIDLE; + } + } + + if (CapabilitiesGet() & bits_CAP_DUCK) + { + if (eNewActivity == ACT_RELOAD) + { + return GetReloadActivity(GetHintNode()); + } + else if ((eNewActivity == ACT_COVER ) || + (eNewActivity == ACT_IDLE && HasMemory(bits_MEMORY_INCOVER))) + { + Activity nCoverActivity = GetCoverActivity(GetHintNode()); + // --------------------------------------------------------------- + // Some NPCs don't have a cover activity defined so just use idle + // --------------------------------------------------------------- + if (SelectWeightedSequence( nCoverActivity ) == ACTIVITY_NOT_AVAILABLE) + { + nCoverActivity = ACT_IDLE; + } + + return nCoverActivity; + } + } + return eNewActivity; +} + + +//----------------------------------------------------------------------------- + +Activity CAI_BaseNPC::TranslateActivity( Activity idealActivity, Activity *pIdealWeaponActivity ) +{ + const int MAX_TRIES = 5; + int count = 0; + + bool bIdealWeaponRequired = false; + Activity idealWeaponActivity; + Activity baseTranslation; + bool bWeaponRequired = false; + Activity weaponTranslation; + Activity last; + Activity current; + + idealWeaponActivity = Weapon_TranslateActivity( idealActivity, &bIdealWeaponRequired ); + if ( pIdealWeaponActivity ) + *pIdealWeaponActivity = idealWeaponActivity; + + baseTranslation = idealActivity; + weaponTranslation = idealActivity; + last = idealActivity; + while ( count++ < MAX_TRIES ) + { + current = NPC_TranslateActivity( last ); + if ( current != last ) + baseTranslation = current; + + weaponTranslation = Weapon_TranslateActivity( current, &bWeaponRequired ); + + if ( weaponTranslation == last ) + break; + + last = weaponTranslation; + } + AssertMsg( count < MAX_TRIES, "Circular activity translation!" ); + + if ( last == ACT_SCRIPT_CUSTOM_MOVE ) + return ACT_SCRIPT_CUSTOM_MOVE; + + if ( HaveSequenceForActivity( weaponTranslation ) ) + return weaponTranslation; + + if ( bWeaponRequired ) + { + // only complain about an activity once + static CUtlVector< Activity > sUniqueActivities; + + if (!sUniqueActivities.Find( weaponTranslation)) + { + // FIXME: warning + DevWarning( "%s missing activity \"%s\" needed by weapon\"%s\"\n", + GetClassname(), GetActivityName( weaponTranslation ), GetActiveWeapon()->GetClassname() ); + + sUniqueActivities.AddToTail( weaponTranslation ); + } + } + + if ( baseTranslation != weaponTranslation && HaveSequenceForActivity( baseTranslation ) ) + return baseTranslation; + + if ( idealWeaponActivity != baseTranslation && HaveSequenceForActivity( idealWeaponActivity ) ) + return idealActivity; + + if ( idealActivity != idealWeaponActivity && HaveSequenceForActivity( idealActivity ) ) + return idealActivity; + + Assert( !HaveSequenceForActivity( idealActivity ) ); + if ( idealActivity == ACT_RUN ) + { + idealActivity = ACT_WALK; + } + else if ( idealActivity == ACT_WALK ) + { + idealActivity = ACT_RUN; + } + + return idealActivity; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : NewActivity - +// iSequence - +// translatedActivity - +// weaponActivity - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ResolveActivityToSequence(Activity NewActivity, int &iSequence, Activity &translatedActivity, Activity &weaponActivity) +{ + AI_PROFILE_SCOPE( CAI_BaseNPC_ResolveActivityToSequence ); + + iSequence = ACTIVITY_NOT_AVAILABLE; + + translatedActivity = TranslateActivity( NewActivity, &weaponActivity ); + + if ( ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE ) ) + { + iSequence = GetScriptCustomMoveSequence(); + } + else + { + iSequence = SelectWeightedSequence( translatedActivity ); + + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + static CAI_BaseNPC *pLastWarn; + static Activity lastWarnActivity; + static float timeLastWarn; + + if ( ( pLastWarn != this && lastWarnActivity != translatedActivity ) || gpGlobals->curtime - timeLastWarn > 5.0 ) + { + DevWarning( "%s has no sequence for act:%s\n", GetClassname(), ActivityList_NameForIndex(translatedActivity) ); + pLastWarn = this; + lastWarnActivity = translatedActivity; + timeLastWarn = gpGlobals->curtime; + } + + if ( translatedActivity == ACT_RUN ) + { + translatedActivity = ACT_WALK; + iSequence = SelectWeightedSequence( translatedActivity ); + } + } + } + + if ( iSequence == ACT_INVALID ) + { + // Abject failure. Use sequence zero. + iSequence = 0; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : NewActivity - +// iSequence - +// translatedActivity - +// weaponActivity - +//----------------------------------------------------------------------------- +extern ConVar ai_sequence_debug; + +void CAI_BaseNPC::SetActivityAndSequence(Activity NewActivity, int iSequence, Activity translatedActivity, Activity weaponActivity) +{ + m_translatedActivity = translatedActivity; + + if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) + { + DevMsg("SetActivityAndSequence : %s: %s:%s -> %s:%s / %s:%s\n", GetClassname(), + GetActivityName(GetActivity()), GetSequenceName(GetSequence()), + GetActivityName(NewActivity), GetSequenceName(iSequence), + GetActivityName(translatedActivity), GetActivityName(weaponActivity) ); + + } + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( GetSequence() != iSequence || !SequenceLoops() ) + { + // + // Don't reset frame between movement phased animations + if (!IsActivityMovementPhased( m_Activity ) || + !IsActivityMovementPhased( NewActivity )) + { + SetCycle( 0 ); + } + } + + ResetSequence( iSequence ); + Weapon_SetActivity( weaponActivity, SequenceDuration( iSequence ) ); + } + else + { + // Not available try to get default anim + ResetSequence( 0 ); + } + + // Set the view position based on the current activity + SetViewOffset( EyeOffset(m_translatedActivity) ); + + if (m_Activity != NewActivity) + { + OnChangeActivity(NewActivity); + } + + // NOTE: We DO NOT write the translated activity here. + // This is to abstract the activity translation from the AI code. + // As far as the code is concerned, a translation is merely a new set of sequences + // that should be regarded as the activity in question. + + // Go ahead and set this so it doesn't keep trying when the anim is not present + m_Activity = NewActivity; + + // this cannot be called until m_Activity stores NewActivity! + GetMotor()->RecalculateYawSpeed(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the activity to the desired activity immediately, skipping any +// transition sequences. +// Input : NewActivity - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::SetActivity( Activity NewActivity ) +{ + // If I'm already doing the NewActivity I can bail. + // FIXME: Should this be based on the current translated activity and ideal translated activity (calculated below)? + // The old code only cared about the logical activity, not translated. + + if (m_Activity == NewActivity) + { + return; + } + + // Don't do this if I'm playing a transition, unless it's ACT_RESET. + if ( NewActivity != ACT_RESET && m_Activity == ACT_TRANSITION && m_IdealActivity != ACT_DO_NOT_DISTURB ) + { + return; + } + + if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) + { + DevMsg("SetActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity)); + } + + if ( !GetModelPtr() ) + return; + + // In case someone calls this with something other than the ideal activity. + m_IdealActivity = NewActivity; + + // Resolve to ideals and apply directly, skipping transitions. + ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); + + //DevMsg("%s: SLAM %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence)); + + SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the activity that we would like to transition toward. +// Input : NewActivity - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::SetIdealActivity( Activity NewActivity ) +{ + // ignore if it's an ACT_TRANSITION, it means somewhere we're setting IdealActivity with a bogus intermediate value + if (NewActivity == ACT_TRANSITION) + { + Assert( 0 ); + return; + } + + if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) + { + DevMsg("SetIdealActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity)); + } + + + if (NewActivity == ACT_RESET) + { + // They probably meant to call SetActivity(ACT_RESET)... we'll fix it for them. + SetActivity(ACT_RESET); + return; + } + + m_IdealActivity = NewActivity; + + if( NewActivity == ACT_DO_NOT_DISTURB ) + { + // Don't resolve anything! Leave it the way the user has it right now. + return; + } + + if ( !GetModelPtr() ) + return; + + // Perform translation in case we need to change sequences within a single activity, + // such as between a standing idle and a crouching idle. + ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); +} + + +//----------------------------------------------------------------------------- +// Purpose: Moves toward the ideal activity through any transition sequences. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::AdvanceToIdealActivity(void) +{ + // If there is a transition sequence between the current sequence and the ideal sequence... + int nNextSequence = FindTransitionSequence(GetSequence(), m_nIdealSequence, NULL); + if (nNextSequence != -1) + { + // We found a transition sequence or possibly went straight to + // the ideal sequence. + if (nNextSequence != m_nIdealSequence) + { +// DevMsg("%s: TRANSITION %s -> %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(nNextSequence), GetSequenceName(m_nIdealSequence)); + + Activity eWeaponActivity = ACT_TRANSITION; + Activity eTranslatedActivity = ACT_TRANSITION; + + // Figure out if the transition sequence has an associated activity that + // we can use for our weapon. Do activity translation also. + Activity eTransitionActivity = GetSequenceActivity(nNextSequence); + if (eTransitionActivity != ACT_INVALID) + { + int nDiscard; + ResolveActivityToSequence(eTransitionActivity, nDiscard, eTranslatedActivity, eWeaponActivity); + } + + // Set activity and sequence to the transition stuff. Set the activity to ACT_TRANSITION + // so we know we're in a transition. + SetActivityAndSequence(ACT_TRANSITION, nNextSequence, eTranslatedActivity, eWeaponActivity); + } + else + { + //DevMsg("%s: IDEAL %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence)); + + // Set activity and sequence to the ideal stuff that was set up in MaintainActivity. + SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); + } + } + // Else go straight there to the ideal activity. + else + { + //DevMsg("%s: Unable to get from sequence %s to %s!\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence)); + SetActivity(m_IdealActivity); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Tries to achieve our ideal animation state, playing any transition +// sequences that we need to play to get there. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::MaintainActivity(void) +{ + AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainActivity ); + + if ( m_lifeState == LIFE_DEAD ) + { + // Don't maintain activities if we're daid. + // Blame Speyrer + return; + } + + if ((GetState() == NPC_STATE_SCRIPT)) + { + // HACK: finish any transitions we might be playing before we yield control to the script + if (GetActivity() != ACT_TRANSITION) + { + // Our animation state is being controlled by a script. + return; + } + } + + if( m_IdealActivity == ACT_DO_NOT_DISTURB || !GetModelPtr() ) + { + return; + } + + // We may have work to do if we aren't playing our ideal activity OR if we + // aren't playing our ideal sequence. + if ((GetActivity() != m_IdealActivity) || (GetSequence() != m_nIdealSequence)) + { + if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) + { + DevMsg("MaintainActivity %s : %s:%s -> %s:%s\n", GetClassname(), + GetActivityName(GetActivity()), GetSequenceName(GetSequence()), + GetActivityName(m_IdealActivity), GetSequenceName(m_nIdealSequence)); + } + + bool bAdvance = false; + + // If we're in a transition activity, see if we are done with the transition. + if (GetActivity() == ACT_TRANSITION) + { + // If the current sequence is finished, try to go to the next one + // closer to our ideal sequence. + if (IsSequenceFinished()) + { + bAdvance = true; + } + // Else a transition sequence is in progress, do nothing. + } + // Else get a specific sequence for the activity and try to transition to that. + else + { + // Save off a target sequence and translated activities to apply when we finish + // playing all the transitions and finally arrive at our ideal activity. + ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); + bAdvance = true; + } + + if (bAdvance) + { + // Try to go to the next sequence closer to our ideal sequence. + AdvanceToIdealActivity(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns true if our ideal activity has finished playing. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsActivityFinished( void ) +{ + return (IsSequenceFinished() && (GetSequence() == m_nIdealSequence)); +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if the activity is one of the standard phase-matched movement activities +// Input : activity +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsActivityMovementPhased( Activity activity ) +{ + switch( activity ) + { + case ACT_WALK: + case ACT_WALK_AIM: + case ACT_WALK_CROUCH: + case ACT_WALK_CROUCH_AIM: + case ACT_RUN: + case ACT_RUN_AIM: + case ACT_RUN_CROUCH: + case ACT_RUN_CROUCH_AIM: + case ACT_RUN_PROTECTED: + return true; + break; + default: + break; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OnChangeActivity( Activity eNewActivity ) +{ + if ( eNewActivity == ACT_RUN || + eNewActivity == ACT_RUN_AIM || + eNewActivity == ACT_WALK ) + { + Stand(); + } +} + +//========================================================= +// SetSequenceByName +//========================================================= +void CAI_BaseNPC::SetSequenceByName( char *szSequence ) +{ + int iSequence = LookupSequence( szSequence ); + + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + SetSequenceById( iSequence ); + else + { + DevWarning( 2, "%s has no sequence to match request\n", GetClassname(), szSequence ); + SetSequence( 0 ); // Set to the reset anim (if it's there) + } +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::SetSequenceById( int iSequence ) +{ + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( GetSequence() != iSequence || !SequenceLoops() ) + { + SetCycle( 0 ); + } + + ResetSequence( iSequence ); // Set to the reset anim (if it's there) + GetMotor()->RecalculateYawSpeed(); + } + else + { + // Not available try to get default anim + DevWarning( 2, "%s invalid sequence requested\n", GetClassname() ); + SetSequence( 0 ); // Set to the reset anim (if it's there) + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the target entity +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *CAI_BaseNPC::GetNavTargetEntity(void) +{ + if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY ) + return m_hEnemy; + else if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT ) + return m_hTargetEnt; + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns zero if the caller can jump from +// vecStart to vecEnd ignoring collisions with pTarget +// +// if the throw fails, returns the distance +// that can be travelled before an obstacle is hit +//----------------------------------------------------------------------------- +#include "ai_initutils.h" +//#define _THROWDEBUG +float CAI_BaseNPC::ThrowLimit( const Vector &vecStart, + const Vector &vecEnd, + float fGravity, + float fArcSize, + const Vector &mins, + const Vector &maxs, + CBaseEntity *pTarget, + Vector *jumpVel, + CBaseEntity **pBlocker) +{ + // Get my jump velocity + Vector rawJumpVel = CalcThrowVelocity(vecStart, vecEnd, fGravity, fArcSize); + *jumpVel = rawJumpVel; + Vector vecFrom = vecStart; + + // Calculate the total time of the jump minus a tiny fraction + float jumpTime = (vecStart - vecEnd).Length2D()/rawJumpVel.Length2D(); + float timeStep = jumpTime / 10.0; + + Vector gravity = Vector(0,0,fGravity); + + // this loop takes single steps to the goal. + for (float flTime = 0 ; flTime < jumpTime-0.1 ; flTime += timeStep ) + { + // Calculate my position after the time step (average velocity over this time step) + Vector nextPos = vecFrom + (rawJumpVel - 0.5 * gravity * timeStep) * timeStep; + + // If last time step make next position the target position + if ((flTime + timeStep) > jumpTime) + { + nextPos = vecEnd; + } + + trace_t tr; + AI_TraceHull( vecFrom, nextPos, mins, maxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + + if (tr.startsolid || tr.fraction < 1.0) + { + CBaseEntity *pEntity = tr.m_pEnt; + + // If we hit the target we are good to go! + if (pEntity == pTarget) + { + return 0; + } + +#ifdef _THROWDEBUG + NDebugOverlay::Line( vecFrom, nextPos, 255, 0, 0, true, 1.0 ); +#endif + // ---------------------------------------------------------- + // If blocked by an npc remember + // ---------------------------------------------------------- + *pBlocker = pEntity; + + // Return distance sucessfully traveled before block encountered + return ((tr.endpos - vecStart).Length()); + } +#ifdef _THROWDEBUG + else + { + NDebugOverlay::Line( vecFrom, nextPos, 255, 255, 255, true, 1.0 ); + } +#endif + + + rawJumpVel = rawJumpVel - gravity * timeStep; + vecFrom = nextPos; + } + return 0; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Called to initialize or re-initialize the vphysics hull when the size +// of the NPC changes +//----------------------------------------------------------------------------- +void CAI_BaseNPC::SetupVPhysicsHull() +{ + if ( GetMoveType() == MOVETYPE_VPHYSICS || GetMoveType() == MOVETYPE_NONE ) + return; + + if ( VPhysicsGetObject() ) + { + // Disable collisions to get + VPhysicsGetObject()->EnableCollisions(false); + VPhysicsDestroyObject(); + } + VPhysicsInitShadow( true, false ); + IPhysicsObject *pPhysObj = VPhysicsGetObject(); + if ( pPhysObj ) + { + float mass = Studio_GetMass(GetModelPtr()); + if ( mass > 0 ) + { + pPhysObj->SetMass( mass ); + } +#if _DEBUG + else + { + DevMsg("Warning: %s has no physical mass\n", STRING(GetModelName())); + } +#endif + IPhysicsShadowController *pController = pPhysObj->GetShadowController(); + float avgsize = (WorldAlignSize().x + WorldAlignSize().y) * 0.5; + pController->SetTeleportDistance( avgsize * 0.5 ); + m_bCheckContacts = true; + } +} + + +// Check for problematic physics objects resting on this NPC. +// They can screw up his navigation, so attach a controller to +// help separate the NPC & physics when you encounter these. +ConVar ai_auto_contact_solver( "ai_auto_contact_solver", "1" ); +void CAI_BaseNPC::CheckPhysicsContacts() +{ + if ( gpGlobals->frametime <= 0.0f || !ai_auto_contact_solver.GetBool() ) + return; + + m_bCheckContacts = false; + if ( GetMoveType() == MOVETYPE_STEP && VPhysicsGetObject()) + { + IPhysicsObject *pPhysics = VPhysicsGetObject(); + IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot(); + CBaseEntity *pGroundEntity = GetGroundEntity(); + float heightCheck = GetAbsOrigin().z + GetHullMaxs().z; + Vector npcVel; + pPhysics->GetVelocity( &npcVel, NULL ); + CBaseEntity *pOtherEntity = NULL; + bool createSolver = false; + float solverTime = 0.0f; + while ( pSnapshot->IsValid() ) + { + IPhysicsObject *pOther = pSnapshot->GetObject(1); + pOtherEntity = static_cast(pOther->GetGameData()); + + if ( pOtherEntity && pGroundEntity != pOtherEntity ) + { + float otherMass = PhysGetEntityMass(pOtherEntity); + + if ( pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() && + otherMass < VPHYSICS_LARGE_OBJECT_MASS ) + { + Assert( !NPCPhysics_SolverExists(this, pOtherEntity) ); + m_bCheckContacts = true; + Vector vel, point; + pOther->GetVelocity( &vel, NULL ); + pSnapshot->GetContactPoint( point ); + + // compare the relative velocity + vel -= npcVel; + + // slow moving object probably won't clear itself. + // Either set ignore, or disable collisions entirely + if ( vel.LengthSqr() < 5.0f*5.0f ) + { + float topdist = fabs(point.z-heightCheck); + // 4 seconds to ignore this for nav + solverTime = 4.0f; + if ( topdist < 2.0f ) + { + // Resting on my head so disable collisions for a bit + solverTime = 0.5f; // UNDONE: Tune + if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) + { + // player is being a monkey + solverTime = 0.25f; + } + + //Msg("Dropping %s from %s\n", pOtherEntity->GetClassname(), GetClassname() ); + createSolver = true; + break; + } + } + } + } + pSnapshot->NextFrictionData(); + } + pPhysics->DestroyFrictionSnapshot( pSnapshot ); + if ( createSolver ) + { + // turn collisions back on once we've been separated for enough time + NPCPhysics_CreateSolver( this, pOtherEntity, true, solverTime ); + pPhysics->RecheckContactPoints(); + } + } +} + +void CAI_BaseNPC::StartTouch( CBaseEntity *pOther ) +{ + BaseClass::StartTouch(pOther); + + if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) + { + m_bCheckContacts = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: To be called instead of UTIL_SetSize, so pathfinding hull +// and actual hull agree +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::SetHullSizeNormal( bool force ) +{ + if ( m_fIsUsingSmallHull || force ) + { + UTIL_SetSize(this, GetHullMins(),GetHullMaxs()); + m_fIsUsingSmallHull = false; + if ( VPhysicsGetObject() ) + { + SetupVPhysicsHull(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: To be called instead of UTIL_SetSize, so pathfinding hull +// and actual hull agree +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::SetHullSizeSmall( bool force ) +{ + if ( !m_fIsUsingSmallHull || force ) + { + UTIL_SetSize(this, NAI_Hull::SmallMins(GetHullType()),NAI_Hull::SmallMaxs(GetHullType())); + m_fIsUsingSmallHull = true; + if ( VPhysicsGetObject() ) + { + SetupVPhysicsHull(); + } + } + return true; +} + +//----------------------------------------------------------------------------- +// Checks to see that the nav hull is valid for the NPC +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsNavHullValid() const +{ + Assert( GetSolid() != SOLID_BSP ); + + Vector hullMin = GetHullMins(); + Vector hullMax = GetHullMaxs(); + Vector vecMins, vecMaxs; + if ( GetSolid() == SOLID_BBOX ) + { + vecMins = WorldAlignMins(); + vecMaxs = WorldAlignMaxs(); + } + else if ( GetSolid() == SOLID_VPHYSICS ) + { + Assert( VPhysicsGetObject() ); + const CPhysCollide *pPhysCollide = VPhysicsGetObject()->GetCollide(); + physcollision->CollideGetAABB( vecMins, vecMaxs, pPhysCollide, GetAbsOrigin(), GetAbsAngles() ); + vecMins -= GetAbsOrigin(); + vecMaxs -= GetAbsOrigin(); + } + else + { + vecMins = hullMin; + vecMaxs = hullMax; + } + + if ( (hullMin.x > vecMins.x) || (hullMax.x < vecMaxs.x) || + (hullMin.y > vecMins.y) || (hullMax.y < vecMaxs.y) || + (hullMin.z > vecMins.z) || (hullMax.z < vecMaxs.z) ) + { + return false; + } + + return true; +} + + +//========================================================= +// NPCInit - after a npc 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 npc spawns. Any +// initialization that should take place for all npcs +// goes here. +//========================================================= +void CAI_BaseNPC::NPCInit ( void ) +{ + if (!g_pGameRules->FAllowNPCs()) + { + UTIL_Remove( this ); + return; + } + + if( IsWaitingToRappel() ) + { + // If this guy's supposed to rappel, keep him from + // falling to the ground when he spawns. + AddFlag( FL_FLY ); + } + +#ifdef _DEBUG + // Make sure that the bounding box is appropriate for the hull size... + // FIXME: We can't test vphysics objects because NPCInit occurs before VPhysics is set up + if ( GetSolid() != SOLID_VPHYSICS && !IsSolidFlagSet(FSOLID_NOT_SOLID) ) + { + if ( !IsNavHullValid() ) + { + Warning("NPC Entity %s (%d) has a bounding box which extends outside its nav box!\n", + STRING(m_iClassname), entindex() ); + } + } +#endif + + // Set fields common to all npcs + AddFlag( FL_AIMTARGET | FL_NPC ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + + m_flOriginalYaw = GetAbsAngles().y; + + SetBlocksLOS( false ); + + SetGravity(1.0); // Don't change + m_takedamage = DAMAGE_YES; + GetMotor()->SetIdealYaw( GetLocalAngles().y ); + m_iMaxHealth = m_iHealth; + m_lifeState = LIFE_ALIVE; + SetIdealState( NPC_STATE_IDLE );// Assume npc will be idle, until proven otherwise + SetIdealActivity( ACT_IDLE ); + SetActivity( ACT_IDLE ); + +#ifdef HL1_DLL + SetDeathPose( ACT_INVALID ); +#endif + + ClearCommandGoal(); + + ClearSchedule(); + GetNavigator()->ClearGoal(); + InitBoneControllers( ); // FIX: should be done in Spawn + if ( GetModelPtr() ) + { + ResetActivityIndexes(); + ResetEventIndexes(); + } + + SetHintNode( NULL ); + + m_afMemory = MEMORY_CLEAR; + + SetEnemy( NULL ); + + m_flDistTooFar = 1024.0; + SetDistLook( 2048.0 ); + + if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) ) + { + m_flDistTooFar = 1e9f; + SetDistLook( 6000.0 ); + } + + // Clear conditions + m_Conditions.ClearAllBits(); + + // set eye position + SetDefaultEyeOffset(); + + // Only give weapon of allowed to have one + if (CapabilitiesGet() & bits_CAP_USE_WEAPONS) + { // Does this npc spawn with a weapon + if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0")) + { + CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(m_spawnEquipment) ); + if ( pWeapon ) + { + // If I have a name, make my weapon match it with "_weapon" appended + if ( GetEntityName() != NULL_STRING ) + { + pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", STRING(GetEntityName()))) ); + } + Weapon_Equip( pWeapon ); + } + } + } + + // Robin: Removed this, since it stomps the weapon's settings, and it's stomped + // by OnUpdateShotRegulator() as soon as they finish firing the first time. + //GetShotRegulator()->SetParameters( 2, 6, 0.3f, 0.8f ); + + SetUse ( &CAI_BaseNPC::NPCUse ); + + // NOTE: Can't call NPC Init Think directly... logic changed about + // what time it is when worldspawn happens.. + + // We must put off the rest of our initialization + // until we're sure everything else has had a chance to spawn. Otherwise + // we may try to reference entities that haven't spawned yet.(sjb) + SetThink( &CAI_BaseNPC::NPCInitThink ); + SetNextThink( gpGlobals->curtime + 0.01f ); + + ForceGatherConditions(); + + // HACKHACK: set up a pre idle animation + // NOTE: Must do this before CreateVPhysics() so bone followers have the correct initial positions. + if ( HasSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT ) ) + { + const char *pStartSequence = CAI_ScriptedSequence::GetSpawnPreIdleSequenceForScript( this ); + if ( pStartSequence ) + { + SetSequence( LookupSequence( pStartSequence ) ); + } + } + + CreateVPhysics(); + + if ( HasSpawnFlags( SF_NPC_START_EFFICIENT ) ) + { + SetEfficiency( AIE_EFFICIENT ); + } + + m_bFadeCorpse = ShouldFadeOnDeath(); + + m_GiveUpOnDeadEnemyTimer.Set( 0.75, 2.0 ); + + m_flTimeLastMovement = FLT_MAX; + + m_flIgnoreDangerSoundsUntil = 0; + + SetDeathPose( ACT_INVALID ); + SetDeathPoseFrame( 0 ); + + m_EnemiesSerialNumber = -1; +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::CreateVPhysics() +{ + if ( IsAlive() && !VPhysicsGetObject() ) + { + SetupVPhysicsHull(); + } + return true; +} + + +//----------------------------------------------------------------------------- +// Set up the shot regulator based on the equipped weapon +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OnUpdateShotRegulator( ) +{ + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( !pWeapon ) + return; + + // Default values + m_ShotRegulator.SetBurstInterval( pWeapon->GetFireRate(), pWeapon->GetFireRate() ); + m_ShotRegulator.SetBurstShotCountRange( pWeapon->GetMinBurst(), pWeapon->GetMaxBurst() ); + m_ShotRegulator.SetRestInterval( pWeapon->GetMinRestTime(), pWeapon->GetMaxRestTime() ); + + // Let the behavior have a whack at it. + if ( GetRunningBehavior() ) + { + GetRunningBehavior()->OnUpdateShotRegulator(); + } +} + + +//----------------------------------------------------------------------------- +// Set up the shot regulator based on the equipped weapon +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon ) +{ + BaseClass::OnChangeActiveWeapon( pOldWeapon, pNewWeapon ); + + // Shot regulator code + if ( pNewWeapon ) + { + OnUpdateShotRegulator(); + m_ShotRegulator.Reset( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tests to see if NPC can holster their weapon (if animation exists to holster weapon) +// Output : true if holster weapon animation exists +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::CanHolsterWeapon( void ) +{ + int seq = SelectWeightedSequence( ACT_DISARM ); + return (seq >= 0); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CAI_BaseNPC::HolsterWeapon( void ) +{ + if ( IsWeaponHolstered() ) + return -1; + + int iHolsterGesture = FindGestureLayer( ACT_DISARM ); + if ( iHolsterGesture != -1 ) + return iHolsterGesture; + + int iLayer = AddGesture( ACT_DISARM, true ); + //iLayer = AddGesture( ACT_GESTURE_DISARM, true ); + + if (iLayer != -1) + { + // Prevent firing during the holster / unholster + float flDuration = GetLayerDuration( iLayer ); + m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 ); + + if( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) + { + m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING_DESTROY; + } + else + { + m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING; + } + + // Make sure we don't try to reload while we're holstering + ClearCondition(COND_LOW_PRIMARY_AMMO); + ClearCondition(COND_NO_PRIMARY_AMMO); + ClearCondition(COND_NO_SECONDARY_AMMO); + } + + return iLayer; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CAI_BaseNPC::UnholsterWeapon( void ) +{ + if ( !IsWeaponHolstered() ) + return -1; + + int iHolsterGesture = FindGestureLayer( ACT_ARM ); + if ( iHolsterGesture != -1 ) + return iHolsterGesture; + + // Deploy the first weapon you can find + for (int i = 0; i < WeaponCount(); i++) + { + if ( GetWeapon( i )) + { + SetActiveWeapon( GetWeapon(i) ); + + int iLayer = AddGesture( ACT_ARM, true ); + //iLayer = AddGesture( ACT_GESTURE_ARM, true ); + + if (iLayer != -1) + { + // Prevent firing during the holster / unholster + float flDuration = GetLayerDuration( iLayer ); + m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 ); + + m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING; + } + + // Refill the clip + if ( GetActiveWeapon()->UsesClipsForAmmo1() ) + { + GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); + } + + // Make sure we don't try to reload while we're unholstering + ClearCondition(COND_LOW_PRIMARY_AMMO); + ClearCondition(COND_NO_PRIMARY_AMMO); + ClearCondition(COND_NO_SECONDARY_AMMO); + + return iLayer; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputHolsterWeapon( inputdata_t &inputdata ) +{ + m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputHolsterAndDestroyWeapon( inputdata_t &inputdata ) +{ + m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED_DESTROYED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputUnholsterWeapon( inputdata_t &inputdata ) +{ + m_iDesiredWeaponState = DESIREDWEAPONSTATE_UNHOLSTERED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsWeaponHolstered( void ) +{ + if( !GetActiveWeapon() ) + return true; + + if( GetActiveWeapon()->IsEffectActive(EF_NODRAW) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsWeaponStateChanging( void ) +{ + return ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING || m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY ); +} + +//----------------------------------------------------------------------------- +// Set up the shot regulator based on the equipped weapon +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OnRangeAttack1() +{ + SetLastAttackTime( gpGlobals->curtime ); + + // Houston, there is a problem! + AssertOnce( GetShotRegulator()->ShouldShoot() ); + + m_ShotRegulator.OnFiredWeapon(); + if ( m_ShotRegulator.IsInRestInterval() ) + { + OnUpdateShotRegulator(); + } + + SetNextAttack( m_ShotRegulator.NextShotTime() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Initialze the relationship table from the keyvalues +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InitRelationshipTable(void) +{ + AddRelationship( STRING( m_RelationshipString ), NULL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::AddRelationship( const char *pszRelationship, CBaseEntity *pActivator ) +{ + // Parse the keyvalue data + char parseString[1000]; + Q_strncpy(parseString, pszRelationship, sizeof(parseString)); + + // Look for an entity string + char *entityString = strtok(parseString," "); + while (entityString) + { + // Get the disposition + char *dispositionString = strtok(NULL," "); + Disposition_t disposition = D_NU; + if ( dispositionString ) + { + if (!stricmp(dispositionString,"D_HT")) + { + disposition = D_HT; + } + else if (!stricmp(dispositionString,"D_FR")) + { + disposition = D_FR; + } + else if (!stricmp(dispositionString,"D_LI")) + { + disposition = D_LI; + } + else if (!stricmp(dispositionString,"D_NU")) + { + disposition = D_NU; + } + else + { + disposition = D_NU; + Warning( "***ERROR***\nBad relationship type (%s) to unknown entity (%s)!\n", dispositionString,entityString ); + Assert( 0 ); + return; + } + } + else + { + Warning("Can't parse relationship info (%s)\n", pszRelationship ); + Assert(0); + return; + } + + // Get the priority + char *priorityString = strtok(NULL," "); + int priority = ( priorityString ) ? atoi(priorityString) : DEF_RELATIONSHIP_PRIORITY; + + bool bFoundEntity = false; + + // Try to get pointer to an entity of this name + CBaseEntity *entity = gEntList.FindEntityByName( NULL, entityString ); + while( entity ) + { + // make sure you catch all entities of this name. + bFoundEntity = true; + AddEntityRelationship(entity, disposition, priority ); + entity = gEntList.FindEntityByName( entity, entityString ); + } + + if( !bFoundEntity ) + { + // Need special condition for player as we can only have one + if (!stricmp("player", entityString) || !stricmp("!player", entityString)) + { + AddClassRelationship( CLASS_PLAYER, disposition, priority ); + } + // Otherwise try to create one too see if a valid classname and get class type + else + { + // HACKHACK: + CBaseEntity *pEntity = CanCreateEntityClass( entityString ) ? CreateEntityByName( entityString ) : NULL; + if (pEntity) + { + AddClassRelationship( pEntity->Classify(), disposition, priority ); + UTIL_RemoveImmediate(pEntity); + } + else + { + DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString ); + } + } + } + // Check for another entity in the list + entityString = strtok(NULL," "); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority ) +{ +#if 0 + ForceGatherConditions(); +#endif + BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::AddClassRelationship( Class_T nClass, Disposition_t nDisposition, int nPriority ) +{ +#if 0 + ForceGatherConditions(); +#endif + BaseClass::AddClassRelationship( nClass, nDisposition, nPriority ); +} + +//========================================================= +// NPCInitThink - Calls StartNPC. Startnpc is +// virtual, but this function cannot be +//========================================================= +void CAI_BaseNPC::NPCInitThink ( void ) +{ + // Initialize the relationship table + InitRelationshipTable(); + + StartNPC(); + + PostNPCInit(); + + if( GetSleepState() == AISS_AUTO_PVS ) + { + // This code is a bit wonky, but it makes it easier for level designers to + // select this option in Hammer. So we set a sleep flag to indicate the choice, + // and then set the sleep state to awake (normal) + AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS ); + SetSleepState( AISS_AWAKE ); + } + + if( GetSleepState() == AISS_AUTO_PVS_AFTER_PVS ) + { + AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS ); + SetSleepState( AISS_AWAKE ); + } + + if ( GetSleepState() > AISS_AWAKE ) + { + Sleep(); + } + + m_flLastRealThinkTime = gpGlobals->curtime; +} + +//========================================================= +// StartNPC - final bit of initization before a npc +// is turned over to the AI. +//========================================================= +void CAI_BaseNPC::StartNPC( void ) +{ + // Raise npc off the floor one unit, then drop to floor + if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) && + !(CapabilitiesGet() & bits_CAP_MOVE_FLY) && + !HasSpawnFlags( SF_NPC_FALL_TO_GROUND ) && !IsWaitingToRappel() && !GetMoveParent() ) + { + Vector origin = GetLocalOrigin(); + + if (!GetMoveProbe()->FloorPoint( origin + Vector(0, 0, 0.1), MASK_NPCSOLID, 0, -2048, &origin )) + { + Warning( "NPC %s stuck in wall--level design error at (%.2f %.2f %.2f)\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); + if ( g_pDeveloper->GetInt() > 1 ) + { + m_debugOverlays |= OVERLAY_BBOX_BIT; + } + } + + SetLocalOrigin( origin ); + } + else + { + SetGroundEntity( NULL ); + } + + if ( m_target != NULL_STRING )// this npc has a target + { + // Find the npc's initial target entity, stash it + SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) ); + + if ( !GetGoalEnt() ) + { + Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target)); + } + else + { + StartTargetHandling( GetGoalEnt() ); + } + } + + //SetState ( m_IdealNPCState ); + //SetActivity ( m_IdealActivity ); + + InitSquad(); + + //--------------------------------- + // + // Spread think times of simultaneously spawned NPCs so that they don't all happen at the same time + // + // Think distribution based on spawn order is: + // + // Tick offset Think time Spawn order + // 0 0 1 + // 1 0.015 13 + // 2 0.03 5 + // 3 0.045 9 + // 4 0.06 18 + // 5 0.075 3 + // 6 0.09 15 + // 7 0.105 11 + // 8 0.12 7 + // 9 0.135 17 + // 10 0.15 2 + // 11 0.165 14 + // 12 0.18 6 + // 13 0.195 19 + // 14 0.21 10 + // 15 0.225 4 + // 16 0.24 16 + // 17 0.255 12 + // 18 0.27 8 + // 19 0.285 20 + + + // If this NPC is spawning late in the game, just push through the rest of the initialization + // start thinking right now. Some spread is added to handle triggered spawns that bring + // a bunch of NPCs into the level + SetThink ( &CAI_BaseNPC::CallNPCThink ); + + if ( gm_flTimeLastSpawn != gpGlobals->curtime ) + { + gm_nSpawnedThisFrame = 0; + gm_flTimeLastSpawn = gpGlobals->curtime; + } + + static const float nextThinkTimes[20] = + { + .0, .150, .075, .225, .030, .180, .120, .270, .045, .210, .105, .255, .015, .165, .090, .240, .135, .060, .195, .285 + }; + + SetNextThink( gpGlobals->curtime + nextThinkTimes[gm_nSpawnedThisFrame % 20] ); + + gm_nSpawnedThisFrame++; + + //--------------------------------- + + m_ScriptArrivalActivity = AIN_DEF_ACTIVITY; + m_strScriptArrivalSequence = NULL_STRING; + + if ( HasSpawnFlags(SF_NPC_WAIT_FOR_SCRIPT) ) + { + SetState( NPC_STATE_IDLE ); + m_Activity = m_IdealActivity; + m_nIdealSequence = GetSequence(); + SetSchedule( SCHED_WAIT_FOR_SCRIPT ); + } +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::StartTargetHandling( CBaseEntity *pTargetEnt ) +{ + // set the npc up to walk a path corner path. + // !!!BUGBUG - this is a minor bit of a hack. + // JAYJAY + + // NPC will start turning towards his destination + bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY); + AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pTargetEnt->GetAbsOrigin(), + bIsFlying ? ACT_FLY : ACT_WALK, + AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST); + + SetState( NPC_STATE_IDLE ); + SetSchedule( SCHED_IDLE_WALK ); + + if ( !GetNavigator()->SetGoal( goal ) ) + { + DevWarning( 2, "Can't Create Route!\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Connect my memory to the squad's +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::InitSquad( void ) +{ + // ------------------------------------------------------- + // If I form squads add me to a squad + // ------------------------------------------------------- + if (!m_pSquad && ( CapabilitiesGet() & bits_CAP_SQUAD )) + { + if ( !m_SquadName ) + { + DevMsg(2, "Found %s that isn't in a squad\n",GetClassname()); + } + else + { + m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName); + } + } + + return ( m_pSquad != NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the memory for this NPC +//----------------------------------------------------------------------------- +CAI_Enemies *CAI_BaseNPC::GetEnemies( void ) +{ + return m_pEnemies; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove this NPC's memory +//----------------------------------------------------------------------------- +void CAI_BaseNPC::RemoveMemory( void ) +{ + delete m_pEnemies; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::TaskComplete( bool fIgnoreSetFailedCondition ) +{ + EndTaskOverlay(); + + // Handy thing to use for debugging + //if (IsCurSchedule(SCHED_PUT_HERE) && + // GetTask()->iTask == TASK_PUT_HERE) + //{ + // int put_breakpoint_here = 5; + //} + + if ( fIgnoreSetFailedCondition || !HasCondition(COND_TASK_FAILED) ) + { + SetTaskStatus( TASKSTATUS_COMPLETE ); + } +} + +void CAI_BaseNPC::TaskMovementComplete( void ) +{ + switch( GetTaskStatus() ) + { + case TASKSTATUS_NEW: + case TASKSTATUS_RUN_MOVE_AND_TASK: + SetTaskStatus( TASKSTATUS_RUN_TASK ); + break; + + case TASKSTATUS_RUN_MOVE: + TaskComplete(); + break; + + case TASKSTATUS_RUN_TASK: + // FIXME: find out how to safely restart movement + //Warning( "Movement completed twice!\n" ); + //Assert( 0 ); + break; + + case TASKSTATUS_COMPLETE: + break; + } + + // JAY: Put this back in. + // UNDONE: Figure out how much of the timestep was consumed by movement + // this frame and restart the movement/schedule engine if necessary + if ( m_scriptState != SCRIPT_CUSTOM_MOVE_TO_MARK ) + { + SetIdealActivity( GetStoppedActivity() ); + } + + // Advance past the last node (in case there is some event at this node) + if ( GetNavigator()->IsGoalActive() ) + { + GetNavigator()->AdvancePath(); + } + + // Now clear the path, it's done. + GetNavigator()->ClearGoal(); + + OnMovementComplete(); +} + + +int CAI_BaseNPC::TaskIsRunning( void ) +{ + if ( GetTaskStatus() != TASKSTATUS_COMPLETE && + GetTaskStatus() != TASKSTATUS_RUN_MOVE ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::TaskFail( AI_TaskFailureCode_t code ) +{ + EndTaskOverlay(); + + // Handy tool for debugging + //if (IsCurSchedule(SCHED_PUT_NAME_HERE)) + //{ + // int put_breakpoint_here = 5; + //} + + // If in developer mode save the fail text for debug output + if (g_pDeveloper->GetInt()) + { + m_failText = TaskFailureToString( code ); + + m_interuptSchedule = NULL; + m_failedSchedule = GetCurSchedule(); + + if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT) + { + DevMsg(this, AIMF_IGNORE_SELECTED, " TaskFail -> %s\n", m_failText ); + } + + ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): TaskFail -> %s\n", GetDebugName(), entindex(), m_failText ) ); + + //AddTimedOverlay( fail_text, 5); + } + + m_ScheduleState.taskFailureCode = code; + SetCondition(COND_TASK_FAILED); + Forget( bits_MEMORY_TURNING ); +} + +//------------------------------------------------------------------------------ +// Purpose : Remember that this entity wasn't reachable +// Input : +// Output : +//------------------------------------------------------------------------------ +void CAI_BaseNPC::RememberUnreachable(CBaseEntity *pEntity) +{ + if ( pEntity == GetEnemy() ) + { + ForceChooseNewEnemy(); + } + + const float NPC_UNREACHABLE_TIMEOUT = 3; + // Only add to list if not already on it + for (int i=m_UnreachableEnts.Size()-1;i>=0;i--) + { + // If record already exists just update mark time + if (pEntity == m_UnreachableEnts[i].hUnreachableEnt) + { + m_UnreachableEnts[i].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT; + m_UnreachableEnts[i].vLocationWhenUnreachable = pEntity->GetAbsOrigin(); + return; + } + } + + // Add new unreachabe entity to list + int nNewIndex = m_UnreachableEnts.AddToTail(); + m_UnreachableEnts[nNewIndex].hUnreachableEnt = pEntity; + m_UnreachableEnts[nNewIndex].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT; + m_UnreachableEnts[nNewIndex].vLocationWhenUnreachable = pEntity->GetAbsOrigin(); +} + +//------------------------------------------------------------------------------ +// Purpose : Returns true is entity was remembered as unreachable. +// After a time delay reachability is checked +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CAI_BaseNPC::IsUnreachable(CBaseEntity *pEntity) +{ + float UNREACHABLE_DIST_TOLERANCE_SQ = (120*120); + + // Note that it's ok to remove elements while I'm iterating + // as long as I iterate backwards and remove them using FastRemove + for (int i=m_UnreachableEnts.Size()-1;i>=0;i--) + { + // Remove any dead elements + if (m_UnreachableEnts[i].hUnreachableEnt == NULL) + { + m_UnreachableEnts.FastRemove(i); + } + else if (pEntity == m_UnreachableEnts[i].hUnreachableEnt) + { + // Test for reachablility on any elements that have timed out + if ( gpGlobals->curtime > m_UnreachableEnts[i].fExpireTime || + pEntity->GetAbsOrigin().DistToSqr(m_UnreachableEnts[i].vLocationWhenUnreachable) > UNREACHABLE_DIST_TOLERANCE_SQ) + { + m_UnreachableEnts.FastRemove(i); + return false; + } + return true; + } + } + return false; +} + +bool CAI_BaseNPC::IsValidEnemy( CBaseEntity *pEnemy ) +{ + CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer(); + if ( pEnemyNPC && pEnemyNPC->CanBeAnEnemyOf( this ) == false ) + return false; + + // Test our enemy filter + if ( m_hEnemyFilter.Get()!= NULL && m_hEnemyFilter->PassesFilter( this, pEnemy ) == false ) + return false; + + return true; +} + + +bool CAI_BaseNPC::CanBeAnEnemyOf( CBaseEntity *pEnemy ) +{ + if ( GetSleepState() > AISS_WAITING_FOR_THREAT ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Picks best enemy from my list of enemies +// Prefers reachable enemies over enemies that are unreachable, +// regardless of priority. For enemies that are both reachable or +// unreachable picks by priority. If priority is the same, picks +// by distance. +// Input : +// Output : +//----------------------------------------------------------------------------- + +CBaseEntity *CAI_BaseNPC::BestEnemy( void ) +{ + AI_PROFILE_SCOPE( CAI_BaseNPC_BestEnemy ); + // TODO - may want to consider distance, attack types, back turned, etc. + + CBaseEntity* pBestEnemy = NULL; + int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE;// so first visible entity will become the closest. + int iBestPriority = -1000; + bool bBestUnreachable = true; // Forces initial check + ThreeState_t fBestSeen = TRS_NONE; + ThreeState_t fBestVisible = TRS_NONE; + int iDistSq; + bool bUnreachable = false; + + AIEnemiesIter_t iter; + + DbgEnemyMsg( this, "BestEnemy() {\n" ); + + for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) + { + CBaseEntity *pEnemy = pEMemory->hEnemy; + + if (!pEnemy || !pEnemy->IsAlive()) + { + if ( pEnemy ) + DbgEnemyMsg( this, " %s rejected: dead\n", pEnemy->GetDebugName() ); + continue; + } + + if ( (pEnemy->GetFlags() & FL_NOTARGET) ) + { + DbgEnemyMsg( this, " %s rejected: no target\n", pEnemy->GetDebugName() ); + continue; + } + + // UNDONE: Move relationship checks into IsValidEnemy? + Disposition_t relation = IRelationType( pEnemy ); + if ( (relation != D_HT && relation != D_FR) ) + { + DbgEnemyMsg( this, " %s rejected: no hate/fear\n", pEnemy->GetDebugName() ); + continue; + } + + if ( m_flAcceptableTimeSeenEnemy > 0.0 && pEMemory->timeLastSeen < m_flAcceptableTimeSeenEnemy ) + { + DbgEnemyMsg( this, " %s rejected: old\n", pEnemy->GetDebugName() ); + continue; + } + + if ( pEMemory->timeValidEnemy > gpGlobals->curtime ) + { + DbgEnemyMsg( this, " %s rejected: not yet valid\n", pEnemy->GetDebugName() ); + continue; + } + + // Skip enemies that have eluded me to prevent infinite loops + if ( pEMemory->bEludedMe ) + { + DbgEnemyMsg( this, " %s rejected: eluded\n", pEnemy->GetDebugName() ); + continue; + } + + // Skip enemies I fear that I've never seen. (usually seen through an enemy finder) + if ( relation == D_FR && !pEMemory->bUnforgettable && pEMemory->timeFirstSeen == AI_INVALID_TIME ) + { + DbgEnemyMsg( this, " %s rejected: feared, but never seen\n", pEnemy->GetDebugName() ); + continue; + } + + if ( !IsValidEnemy( pEnemy ) ) + { + DbgEnemyMsg( this, " %s rejected: not valid\n", pEnemy->GetDebugName() ); + continue; + } + + // establish the reachability of this enemy + bUnreachable = IsUnreachable(pEnemy); + + // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority + if (!bBestUnreachable && bUnreachable) + { + DbgEnemyMsg( this, " %s rejected: unreachable\n", pEnemy->GetDebugName() ); + continue; + } + + // If best is unreachable and current is reachable, always pick the current regardless of priority + if (bBestUnreachable && !bUnreachable) + { + DbgEnemyMsg( this, " %s accepted (1)\n", pEnemy->GetDebugName() ); + if ( pBestEnemy ) + DbgEnemyMsg( this, " (%s displaced)\n", pBestEnemy->GetDebugName() ); + + iBestPriority = IRelationPriority ( pEnemy ); + iBestDistSq = static_cast((pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); + pBestEnemy = pEnemy; + bBestUnreachable = bUnreachable; + fBestSeen = TRS_NONE; + fBestVisible = TRS_NONE; + } + // If both are unreachable or both are reachable, chose enemy based on priority and distance + else if ( IRelationPriority( pEnemy ) > iBestPriority ) + { + DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() ); + if ( pBestEnemy ) + DbgEnemyMsg( this, " (%s displaced due to priority, %d > %d )\n", pBestEnemy->GetDebugName(), IRelationPriority( pEnemy ), iBestPriority ); + // 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. + iBestPriority = IRelationPriority ( pEnemy ); + iBestDistSq = static_cast(( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); + pBestEnemy = pEnemy; + bBestUnreachable = bUnreachable; + fBestSeen = TRS_NONE; + fBestVisible = TRS_NONE; + } + else if ( IRelationPriority( pEnemy ) == iBestPriority ) + { + // 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. + iDistSq = static_cast(( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); + + bool bAcceptCurrent = false; + bool bCloser = ( iDistSq < iBestDistSq ); + ThreeState_t fCurSeen = TRS_NONE; + ThreeState_t fCurVisible = TRS_NONE; + + // The following code is constructed in such a verbose manner to + // ensure the expensive calls only occur if absolutely needed + + // If current is farther, and best has previously been confirmed as seen or visible, move on + if ( !bCloser) + { + if ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE ) + { + DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() ); + continue; + } + } + + // If current is closer, and best has previously been confirmed as not seen and not visible, take it + if ( bCloser) + { + if ( fBestSeen == TRS_FALSE && fBestVisible == TRS_FALSE ) + { + bAcceptCurrent = true; + } + } + + if ( !bAcceptCurrent ) + { + // If current is closer, and seen, take it + if ( bCloser ) + { + fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE; + + bAcceptCurrent = ( fCurSeen == TRS_TRUE ); + } + } + + if ( !bAcceptCurrent ) + { + // If current is farther, and best is seen, move on + if ( !bCloser ) + { + if ( fBestSeen == TRS_NONE ) + { + fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE; + } + + if ( fBestSeen == TRS_TRUE ) + { + DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() ); + continue; + } + } + + // At this point, need to start performing expensive tests + if ( bCloser && fBestVisible == TRS_NONE ) + { + // Perform shortest FVisible + fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE; + + bAcceptCurrent = ( fCurVisible == TRS_TRUE ); + } + + // Alas, must do the most expensive comparison + if ( !bAcceptCurrent ) + { + if ( fBestSeen == TRS_NONE ) + { + fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE; + } + + if ( fBestVisible == TRS_NONE ) + { + fBestVisible = ( ( EnemyDistance( pBestEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE; + } + + if ( fCurSeen == TRS_NONE ) + { + fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE; + } + + if ( fCurVisible == TRS_NONE ) + { + fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE; + } + + bool bBestSeenOrVisible = ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE ); + bool bCurSeenOrVisible = ( fCurSeen == TRS_TRUE || fCurVisible == TRS_TRUE ); + + if ( !bCloser) + { + if ( bBestSeenOrVisible ) + { + DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() ); + continue; + } + else if ( !bCurSeenOrVisible ) + { + DbgEnemyMsg( this, " %s rejected: current is closer and neither is seen\n", pEnemy->GetDebugName() ); + continue; + } + } + else // Closer + { + if ( !bCurSeenOrVisible && bBestSeenOrVisible ) + { + DbgEnemyMsg( this, " %s rejected: current is father but seen\n", pEnemy->GetDebugName() ); + continue; + } + } + } + } + + DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() ); + if ( pBestEnemy ) + DbgEnemyMsg( this, " (%s displaced due to distance/visibility)\n", pBestEnemy->GetDebugName() ); + fBestSeen = fCurSeen; + fBestVisible = fCurVisible; + iBestDistSq = iDistSq; + iBestPriority = IRelationPriority ( pEnemy ); + pBestEnemy = pEnemy; + bBestUnreachable = bUnreachable; + } + else + DbgEnemyMsg( this, " %s rejected: lower priority\n", pEnemy->GetDebugName() ); + } + + DbgEnemyMsg( this, "} == %s\n", pBestEnemy->GetDebugName() ); + + return pBestEnemy; +} + +//----------------------------------------------------------------------------- +// Purpose: Given a node returns the appropriate reload activity +// Input : +// Output : +//----------------------------------------------------------------------------- +Activity CAI_BaseNPC::GetReloadActivity( CAI_Hint* pHint ) +{ + Activity nReloadActivity = ACT_RELOAD; + + if (pHint && GetEnemy()!=NULL) + { + switch (pHint->HintType()) + { + case HINT_TACTICAL_COVER_LOW: + case HINT_TACTICAL_COVER_MED: + { + if (SelectWeightedSequence( ACT_RELOAD_LOW ) != ACTIVITY_NOT_AVAILABLE) + { + Vector vEyePos = GetAbsOrigin() + EyeOffset(ACT_RELOAD_LOW); + // Check if this location will block the threat's line of sight to me + trace_t tr; + AI_TraceLOS( vEyePos, GetEnemy()->EyePosition(), this, &tr ); + if (tr.fraction != 1.0) + { + nReloadActivity = ACT_RELOAD_LOW; + } + } + break; + } + default: + break; + } + } + return nReloadActivity; +} + +//----------------------------------------------------------------------------- +// Purpose: Given a node returns the appropriate cover activity +// Input : +// Output : +//----------------------------------------------------------------------------- +Activity CAI_BaseNPC::GetCoverActivity( CAI_Hint *pHint ) +{ + Activity nCoverActivity = ACT_INVALID; + + // --------------------------------------------------------------- + // Check if hint node specifies different cover type + // --------------------------------------------------------------- + if (pHint) + { + switch (pHint->HintType()) + { + case HINT_TACTICAL_COVER_MED: + { + nCoverActivity = ACT_COVER_MED; + break; + } + case HINT_TACTICAL_COVER_LOW: + { + nCoverActivity = ACT_COVER_LOW; + break; + } + default: + break; + } + } + + if ( nCoverActivity == ACT_INVALID ) + nCoverActivity = ACT_COVER; + + return nCoverActivity; +} + +//========================================================= +// CalcIdealYaw - gets a yaw value for the caller that would +// face the supplied vector. Value is stuffed into the npc's +// ideal_yaw +//========================================================= +float CAI_BaseNPC::CalcIdealYaw( const Vector &vecTarget ) +{ + Vector vecProjection; + + // strafing npc needs to face 90 degrees away from its goal + if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_LEFT ) + { + vecProjection.x = -vecTarget.y; + vecProjection.y = vecTarget.x; + + return UTIL_VecToYaw( vecProjection - GetLocalOrigin() ); + } + else if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_RIGHT ) + { + vecProjection.x = vecTarget.y; + vecProjection.y = vecTarget.x; + + return UTIL_VecToYaw( vecProjection - GetLocalOrigin() ); + } + else + { + return UTIL_VecToYaw ( vecTarget - GetLocalOrigin() ); + } +} + +//========================================================= +// SetEyePosition +// +// queries the npc's model for $eyeposition and copies +// that vector to the npc's m_vDefaultEyeOffset and m_vecViewOffset +// +//========================================================= +void CAI_BaseNPC::SetDefaultEyeOffset ( void ) +{ + if ( GetModelPtr() ) + { + GetEyePosition( GetModelPtr(), m_vDefaultEyeOffset ); + + if ( m_vDefaultEyeOffset == vec3_origin ) + { + if ( Classify() != CLASS_NONE ) + { + DevMsg( "WARNING: %s(%s) has no eye offset in .qc!\n", GetClassname(), STRING(GetModelName()) ); + } + VectorAdd( WorldAlignMins(), WorldAlignMaxs(), m_vDefaultEyeOffset ); + m_vDefaultEyeOffset *= 0.75; + } + } + else + m_vDefaultEyeOffset = vec3_origin; + + SetViewOffset( m_vDefaultEyeOffset ); + +} + +//------------------------------------------------------------------------------ +// Purpose : Returns eye offset for an NPC for the given activity +// Input : +// Output : +//------------------------------------------------------------------------------ +Vector CAI_BaseNPC::EyeOffset( Activity nActivity ) +{ + if ( CapabilitiesGet() & bits_CAP_DUCK ) + { + if ( IsCrouchedActivity( nActivity ) ) + return GetCrouchEyeOffset(); + } + + // if the hint doesn't tell anything, assume current state + if ( IsCrouching() ) + return GetCrouchEyeOffset(); + + return m_vDefaultEyeOffset; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Vector +//----------------------------------------------------------------------------- +Vector CAI_BaseNPC::EyePosition( void ) +{ + if ( IsCrouching() ) + return GetAbsOrigin() + GetCrouchEyeOffset(); + + return BaseClass::EyePosition(); +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) +{ + // UNDONE: Share this code into CBaseAnimating as appropriate? + switch( pEvent->event ) + { + case SCRIPT_EVENT_DEAD: + if ( m_NPCState == NPC_STATE_SCRIPT ) + { + m_lifeState = LIFE_DYING; + // Kill me now! (and fade out when CineCleanup() is called) +#if _DEBUG + DevMsg( 2, "Death event: %s\n", GetClassname() ); +#endif + m_iHealth = 0; + } +#if _DEBUG + else + DevWarning( 2, "INVALID death event:%s\n", GetClassname() ); +#endif + break; + case SCRIPT_EVENT_NOT_DEAD: + if ( m_NPCState == NPC_STATE_SCRIPT ) + { + m_lifeState = LIFE_ALIVE; + // This is for life/death sequences where the player can determine whether a character is dead or alive after the script + m_iHealth = m_iMaxHealth; + } + break; + + case SCRIPT_EVENT_SOUND: // Play a named wave file + { + EmitSound( pEvent->options ); + } + break; + + case SCRIPT_EVENT_SOUND_VOICE: + { + EmitSound( pEvent->options ); + } + break; + + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time + if (random->RandomInt(0,2) == 0) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, SNDLVL_TALKING, 0, 100 ); + break; + + case SCRIPT_EVENT_FIREEVENT: + { + // + // Fire a script event. The number of the script event to fire is in the options string. + // + if ( m_hCine != NULL ) + { + m_hCine->FireScriptEvent( atoi( pEvent->options ) ); + } + else + { + // FIXME: look so see if it's playing a vcd and fire those instead + // AssertOnce( 0 ); + } + break; + } + case SCRIPT_EVENT_FIRE_INPUT: + { + variant_t emptyVariant; + this->AcceptInput( pEvent->options, this, this, emptyVariant, 0 ); + break; + } + + case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on + if ( m_hCine ) + m_hCine->AllowInterrupt( false ); + break; + + case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now + if ( m_hCine ) + m_hCine->AllowInterrupt( true ); + break; + +#if 0 + case SCRIPT_EVENT_INAIR: // Don't engine->DropToFloor() + case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to + break; +#endif + case SCRIPT_EVENT_BODYGROUPON: + case SCRIPT_EVENT_BODYGROUPOFF: + case SCRIPT_EVENT_BODYGROUPTEMP: + DevMsg( "Bodygroup!\n" ); + break; + + case AE_NPC_ATTACK_BROADCAST: + break; + + case NPC_EVENT_BODYDROP_HEAVY: + if ( GetFlags() & FL_ONGROUND ) + { + EmitSound( "AI_BaseNPC.BodyDrop_Heavy" ); + } + break; + + case NPC_EVENT_BODYDROP_LIGHT: + if ( GetFlags() & FL_ONGROUND ) + { + EmitSound( "AI_BaseNPC.BodyDrop_Light" ); + } + break; + + case NPC_EVENT_SWISHSOUND: + { + // NO NPC may use this anim event unless that npc's precache precaches this sound!!! + EmitSound( "AI_BaseNPC.SwishSound" ); + break; + } + + + case NPC_EVENT_180TURN: + { + //DevMsg( "Turned!\n" ); + SetIdealActivity( ACT_IDLE ); + Forget( bits_MEMORY_TURNING ); + SetBoneController( 0, GetLocalAngles().y ); + AddEffects( EF_NOINTERP ); + break; + } + + case NPC_EVENT_ITEM_PICKUP: + { + CBaseEntity *pPickup = NULL; + + // + // Figure out what we're supposed to pick up. + // + if ( pEvent->options && strlen( pEvent->options ) > 0 ) + { + // Pick up the weapon or item that was specified in the anim event. + pPickup = gEntList.FindEntityGenericNearest( pEvent->options, GetAbsOrigin(), 256, this ); + } + else + { + // Pick up the weapon or item that was found earlier and cached in our target pointer. + pPickup = GetTarget(); + } + + // Make sure we found something to pick up. + if ( !pPickup ) + { + TaskFail("Item no longer available!\n"); + break; + } + + // Make sure the item hasn't moved. + float flDist = ( pPickup->WorldSpaceCenter() - GetAbsOrigin() ).Length2D(); + if ( flDist > ITEM_PICKUP_TOLERANCE ) + { + TaskFail("Item has moved!\n"); + break; + } + + CBaseCombatWeapon *pWeapon = dynamic_cast( pPickup ); + if ( pWeapon ) + { + // Picking up a weapon. + CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); + if ( pOwner ) + { + TaskFail( "Weapon in use by someone else" ); + } + else if ( !pWeapon ) + { + TaskFail( "Weapon doesn't exist" ); + } + else if (!Weapon_CanUse( pWeapon )) + { + TaskFail( "Can't use this weapon type" ); + } + else + { + PickupWeapon( pWeapon ); + TaskComplete(); + break; + } + } + else + { + // Picking up an item. + PickupItem( pPickup ); + TaskComplete(); + } + + break; + } + + case NPC_EVENT_WEAPON_SET_SEQUENCE_NUMBER: + { + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ((pWeapon) && (pEvent->options)) + { + int nSequence = atoi(pEvent->options); + if (nSequence != -1) + { + pWeapon->ResetSequence(nSequence); + } + } + break; + } + + case NPC_EVENT_WEAPON_SET_SEQUENCE_NAME: + { + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ((pWeapon) && (pEvent->options)) + { + int nSequence = pWeapon->LookupSequence(pEvent->options); + if (nSequence != -1) + { + pWeapon->ResetSequence(nSequence); + } + } + break; + } + + case NPC_EVENT_WEAPON_SET_ACTIVITY: + { + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ((pWeapon) && (pEvent->options)) + { + Activity act = (Activity)pWeapon->LookupActivity(pEvent->options); + if (act != ACT_INVALID) + { + // FIXME: where should the duration come from? normally it would come from the current sequence + Weapon_SetActivity(act, 0); + } + } + break; + } + + case NPC_EVENT_WEAPON_DROP: + { + // + // Drop our active weapon (or throw it at the specified target entity). + // + CBaseEntity *pTarget = NULL; + if (pEvent->options) + { + pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this); + } + + if (pTarget) + { + Vector vecTargetPos = pTarget->WorldSpaceCenter(); + Weapon_Drop(GetActiveWeapon(), &vecTargetPos); + } + else + { + Weapon_Drop(GetActiveWeapon()); + } + + break; + } + + case EVENT_WEAPON_RELOAD: + { + if ( GetActiveWeapon() ) + { + GetActiveWeapon()->WeaponSound( RELOAD_NPC ); + GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); + ClearCondition(COND_LOW_PRIMARY_AMMO); + ClearCondition(COND_NO_PRIMARY_AMMO); + ClearCondition(COND_NO_SECONDARY_AMMO); + } + break; + } + + case EVENT_WEAPON_RELOAD_SOUND: + { + if ( GetActiveWeapon() ) + { + GetActiveWeapon()->WeaponSound( RELOAD_NPC ); + } + break; + } + + case EVENT_WEAPON_RELOAD_FILL_CLIP: + { + if ( GetActiveWeapon() ) + { + GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); + ClearCondition(COND_LOW_PRIMARY_AMMO); + ClearCondition(COND_NO_PRIMARY_AMMO); + ClearCondition(COND_NO_SECONDARY_AMMO); + } + break; + } + + case NPC_EVENT_LEFTFOOT: + case NPC_EVENT_RIGHTFOOT: + // For right now, do nothing. All functionality for this lives in individual npcs. + break; + + case NPC_EVENT_OPEN_DOOR: + { + CBasePropDoor *pDoor = (CBasePropDoor *)(CBaseEntity *)GetNavigator()->GetPath()->GetCurWaypoint()->GetEHandleData(); + if (pDoor != NULL) + { + OpenPropDoorNow( pDoor ); + } + + break; + } + + default: + if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_SERVER)) + { + if (pEvent->event == AE_NPC_HOLSTER) + { + // Cache off the weapon. + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + + Assert( pWeapon != NULL ); + + GetActiveWeapon()->Holster(); + SetActiveWeapon( NULL ); + + //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now that we don't have a weapon out. + GetNavigator()->SetArrivalSequence( ACT_INVALID ); + + if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY ) + { + // Get rid of it! + UTIL_Remove( pWeapon ); + } + + if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE ) + { + m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; + m_Activity = ACT_RESET; + } + + return; + } + else if (pEvent->event == AE_NPC_DRAW) + { + if (GetActiveWeapon()) + { + GetActiveWeapon()->Deploy(); + + //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now. + GetNavigator()->SetArrivalSequence( ACT_INVALID ); + + if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE ) + { + m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; + m_Activity = ACT_RESET; + } + } + return; + } + else if ( pEvent->event == AE_NPC_BODYDROP_HEAVY ) + { + if ( GetFlags() & FL_ONGROUND ) + { + EmitSound( "AI_BaseNPC.BodyDrop_Heavy" ); + } + return; + } + else if ( pEvent->event == AE_NPC_LEFTFOOT || pEvent->event == AE_NPC_RIGHTFOOT ) + { + return; + } + else if ( pEvent->event == AE_NPC_RAGDOLL ) + { + // Convert to ragdoll immediately + BecomeRagdollOnClient( vec3_origin ); + return; + } + else if ( pEvent->event == AE_NPC_ADDGESTURE ) + { + Activity act = ( Activity )LookupActivity( pEvent->options ); + if (act != ACT_INVALID) + { + act = TranslateActivity( act ); + if (act != ACT_INVALID) + { + AddGesture( act ); + } + } + return; + } + else if ( pEvent->event == AE_NPC_RESTARTGESTURE ) + { + Activity act = ( Activity )LookupActivity( pEvent->options ); + if (act != ACT_INVALID) + { + act = TranslateActivity( act ); + if (act != ACT_INVALID) + { + RestartGesture( act ); + } + } + return; + } + else if ( pEvent->event == AE_NPC_WEAPON_DROP ) + { + // Drop our active weapon (or throw it at the specified target entity). + CBaseEntity *pTarget = NULL; + if (pEvent->options) + { + pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this); + } + + if (pTarget) + { + Vector vecTargetPos = pTarget->WorldSpaceCenter(); + Weapon_Drop(GetActiveWeapon(), &vecTargetPos); + } + else + { + Weapon_Drop(GetActiveWeapon()); + } + return; + } + else if ( pEvent->event == AE_NPC_WEAPON_SET_ACTIVITY ) + { + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ((pWeapon) && (pEvent->options)) + { + Activity act = (Activity)pWeapon->LookupActivity(pEvent->options); + if (act == ACT_INVALID) + { + // Try and translate it + act = Weapon_TranslateActivity( (Activity)CAI_BaseNPC::GetActivityID(pEvent->options), false ); + } + + if (act != ACT_INVALID) + { + // FIXME: where should the duration come from? normally it would come from the current sequence + Weapon_SetActivity(act, 0); + } + } + return; + } + else if ( pEvent->event == AE_NPC_SET_INTERACTION_CANTDIE ) + { + SetInteractionCantDie( (atoi(pEvent->options) != 0) ); + return; + } + else if ( pEvent->event == AE_NPC_HURT_INTERACTION_PARTNER ) + { + // If we're currently interacting with an enemy, hurt them/me + if ( m_hInteractionPartner ) + { + CAI_BaseNPC *pTarget = NULL; + CAI_BaseNPC *pAttacker = NULL; + if ( pEvent->options ) + { + char szEventOptions[128]; + Q_strncpy( szEventOptions, pEvent->options, sizeof(szEventOptions) ); + char *pszParam = strtok( szEventOptions, " " ); + if ( pszParam ) + { + if ( !Q_strncmp( pszParam, "ME", 2 ) ) + { + pTarget = this; + pAttacker = m_hInteractionPartner; + } + else if ( !Q_strncmp( pszParam, "THEM", 4 ) ) + { + pAttacker = this; + pTarget = m_hInteractionPartner; + } + + pszParam = strtok(NULL," "); + if ( pAttacker && pTarget && pszParam ) + { + int iDamage = atoi( pszParam ); + if ( iDamage ) + { + // We've got a target, and damage. Now hurt them. + CTakeDamageInfo info; + info.SetDamage( iDamage ); + info.SetAttacker( pAttacker ); + info.SetInflictor( pAttacker ); + info.SetDamageType( DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE ); + pTarget->TakeDamage( info ); + return; + } + } + } + } + + // Bad data. Explain how to use this anim event. + const char *pName = EventList_NameForIndex( pEvent->event ); + DevWarning( 1, "Bad %s format. Should be: { AE_NPC_HURT_INTERACTION_PARTNER \" \" }\n", pName ); + return; + } + + DevWarning( "%s received AE_NPC_HURT_INTERACTION_PARTNER anim event, but it's not interacting with anything.\n", GetDebugName() ); + return; + } + } + + // FIXME: why doesn't this code pass unhandled events down to its parent? + // Came from my weapon? + //Adrian I'll clean this up once the old event system is phased out. + if ( pEvent->pSource != this || ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM && pEvent->type & AE_TYPE_WEAPON ) || (pEvent->event >= EVENT_WEAPON && pEvent->event <= EVENT_WEAPON_LAST) ) + { + Weapon_HandleAnimEvent( pEvent ); + } + else + { + BaseClass::HandleAnimEvent( pEvent ); + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Override base class to add display of routes +// Input : +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +void CAI_BaseNPC::DrawDebugGeometryOverlays(void) +{ + // Handy for debug + //NDebugOverlay::Cross3D(EyePosition(),Vector(-2,-2,-2),Vector(2,2,2),0,255,0,true); + + // ------------------------------ + // Remove me if requested + // ------------------------------ + if (m_debugOverlays & OVERLAY_NPC_ZAP_BIT) + { + VacateStrategySlot(); + Weapon_Drop( GetActiveWeapon() ); + m_iHealth = 0; + SetThink( &CAI_BaseNPC::SUB_Remove ); + } + + // ------------------------------ + // properly kill an NPC. + // ------------------------------ + if (m_debugOverlays & OVERLAY_NPC_KILL_BIT) + { + CTakeDamageInfo info; + + info.SetDamage( m_iHealth ); + info.SetAttacker( this ); + info.SetInflictor( ( AI_IsSinglePlayer() ) ? (CBaseEntity *)AI_GetSinglePlayer() : (CBaseEntity *)this ); + info.SetDamageType( DMG_GENERIC ); + + m_debugOverlays &= ~OVERLAY_NPC_KILL_BIT; + TakeDamage( info ); + return; + } + + + // ------------------------------ + // Draw route if requested + // ------------------------------ + if ((m_debugOverlays & OVERLAY_NPC_ROUTE_BIT)) + { + GetNavigator()->DrawDebugRouteOverlay(); + if ( IsMoving() ) + { + float yaw = GetMotor()->GetIdealYaw(); + Vector vecYaw = UTIL_YawToVector(yaw); + NDebugOverlay::Line(WorldSpaceCenter(),WorldSpaceCenter() + vecYaw * GetHullWidth() * .5,255,255,255,true,0.0); + } + } + + if (!(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) && (IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN))) + { + NDebugOverlay::Box(m_vecLastPosition, Vector(-5,-5,-5),Vector(5,5,5), 255, 0, 255, 0, 0); + NDebugOverlay::HorzArrow( GetAbsOrigin(), m_vecLastPosition, 16, 255, 0, 255, 64, true, 0 ); + } + + // ------------------------------ + // Draw red box around if selected + // ------------------------------ + if ((m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) && !ai_no_select_box.GetBool()) + { + NDebugOverlay::EntityBounds(this, 255, 0, 0, 20, 0); + } + + // ------------------------------ + // Draw nearest node if selected + // ------------------------------ + if ((m_debugOverlays & OVERLAY_NPC_NEAREST_BIT)) + { + int iNodeID = GetPathfinder()->NearestNodeToNPC(); + if (iNodeID != NO_NODE) + { + NDebugOverlay::Box(GetNavigator()->GetNetwork()->AccessNodes()[iNodeID]->GetPosition(GetHullType()), Vector(-10,-10,-10),Vector(10,10,10), 255, 255, 255, 0, 0); + } + } + + // ------------------------------ + // Draw viewcone if selected + // ------------------------------ + if ((m_debugOverlays & OVERLAY_NPC_VIEWCONE_BIT)) + { + float flViewRange = acos(m_flFieldOfView); + Vector vEyeDir = EyeDirection2D( ); + Vector vLeftDir, vRightDir; + float fSin, fCos; + SinCos( flViewRange, &fSin, &fCos ); + + vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin; + vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos; + vLeftDir.z = vEyeDir.z; + fSin = sin(-flViewRange); + fCos = cos(-flViewRange); + vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin; + vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos; + vRightDir.z = vEyeDir.z; + + // Visualize it + NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vLeftDir * 200 ), 64, 255, 0, 0, 50, false, 0 ); + NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vRightDir * 200 ), 64, 255, 0, 0, 50, false, 0 ); + NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vEyeDir * 100 ), 8, 0, 255, 0, 50, false, 0 ); + NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 ); + } + + // ---------------------------------------------- + // Draw the relationships for this NPC to others + // ---------------------------------------------- + if ( m_debugOverlays & OVERLAY_NPC_RELATION_BIT ) + { + // Show the relationships to entities around us + int r = 0; + int g = 0; + int b = 0; + + int nRelationship; + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + + // Rate all NPCs + for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + if ( ppAIs[i] == NULL || ppAIs[i] == this ) + continue; + + // Get our relation to the target + nRelationship = IRelationType( ppAIs[i] ); + + // Get the color for the arrow + UTIL_GetDebugColorForRelationship( nRelationship, r, g, b ); + + // Draw an arrow + NDebugOverlay::HorzArrow( GetAbsOrigin(), ppAIs[i]->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f ); + } + + // Also include all players + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer == NULL ) + continue; + + // Get our relation to the target + nRelationship = IRelationType( pPlayer ); + + // Get the color for the arrow + UTIL_GetDebugColorForRelationship( nRelationship, r, g, b ); + + // Draw an arrow + NDebugOverlay::HorzArrow( GetAbsOrigin(), pPlayer->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f ); + } + } + + // ------------------------------ + // Draw enemies if selected + // ------------------------------ + if ((m_debugOverlays & OVERLAY_NPC_ENEMIES_BIT)) + { + AIEnemiesIter_t iter; + for( AI_EnemyInfo_t *eMemory = GetEnemies()->GetFirst(&iter); eMemory != NULL; eMemory = GetEnemies()->GetNext(&iter) ) + { + if (eMemory->hEnemy) + { + CBaseCombatCharacter *npcEnemy = (eMemory->hEnemy)->MyCombatCharacterPointer(); + if (npcEnemy) + { + int r,g,b; + char debugText[255]; + debugText[0] = 0; + + if (npcEnemy == GetEnemy()) + { + Q_strncat(debugText,"Current Enemy", sizeof( debugText ), COPY_ALL_CHARACTERS ); + } + else if (npcEnemy == GetTarget()) + { + Q_strncat(debugText,"Current Target", sizeof( debugText ), COPY_ALL_CHARACTERS ); + } + else + { + Q_strncat(debugText,"Other Memory", sizeof( debugText ), COPY_ALL_CHARACTERS ); + } + if (IsUnreachable(npcEnemy)) + { + Q_strncat(debugText," (Unreachable)", sizeof( debugText ), COPY_ALL_CHARACTERS ); + } + if (eMemory->bEludedMe) + { + Q_strncat(debugText," (Eluded)", sizeof( debugText ), COPY_ALL_CHARACTERS ); + } + // Unreachable enemy drawn in green + if (IsUnreachable(npcEnemy)) + { + r = 0; + g = 255; + b = 0; + } + // Eluded enemy drawn in blue + else if (eMemory->bEludedMe) + { + r = 0; + g = 0; + b = 255; + } + // Current enemy drawn in red + else if (npcEnemy == GetEnemy()) + { + r = 255; + g = 0; + b = 0; + } + // Current traget drawn in magenta + else if (npcEnemy == GetTarget()) + { + r = 255; + g = 0; + b = 255; + } + // All other enemies drawn in pink + else + { + r = 255; + g = 100; + b = 100; + } + + + Vector drawPos = eMemory->vLastKnownLocation; + NDebugOverlay::Text( drawPos, debugText, false, 0.0 ); + + // If has a line on the player draw cross slightly in front so player can see + if (npcEnemy->IsPlayer() && + (eMemory->vLastKnownLocation - npcEnemy->GetAbsOrigin()).Length()<10 ) + { + Vector vEnemyFacing = npcEnemy->BodyDirection2D( ); + Vector eyePos = npcEnemy->EyePosition() + vEnemyFacing*10.0; + Vector upVec = Vector(0,0,2); + Vector sideVec; + CrossProduct( vEnemyFacing, upVec, sideVec); + NDebugOverlay::Line(eyePos+sideVec+upVec, eyePos-sideVec-upVec, r, g, b, false, 0); + NDebugOverlay::Line(eyePos+sideVec-upVec, eyePos-sideVec+upVec, r, g, b, false, 0); + + NDebugOverlay::Text( eyePos, debugText, false, 0.0 ); + } + else + { + NDebugOverlay::Cross3D(drawPos,NAI_Hull::Mins(npcEnemy->GetHullType()),NAI_Hull::Maxs(npcEnemy->GetHullType()),r,g,b,false,0); + } + } + } + } + } + + // ---------------------------------------------- + // Draw line to target and enemy entity if exist + // ---------------------------------------------- + if ((m_debugOverlays & OVERLAY_NPC_FOCUS_BIT)) + { + if (GetEnemy() != NULL) + { + NDebugOverlay::Line(EyePosition(),GetEnemy()->EyePosition(),255,0,0,true,0.0); + } + if (GetTarget() != NULL) + { + NDebugOverlay::Line(EyePosition(),GetTarget()->EyePosition(),0,0,255,true,0.0); + } + } + + + GetPathfinder()->DrawDebugGeometryOverlays(m_debugOverlays); + + CBaseEntity::DrawDebugGeometryOverlays(); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Input : +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CAI_BaseNPC::DrawDebugTextOverlays(void) +{ + int text_offset = 0; + + // --------------------- + // Print Baseclass text + // --------------------- + text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_NPC_SQUAD_BIT) + { + // Print health + char tempstr[512]; + Q_snprintf(tempstr,sizeof(tempstr),"Health: %i",m_iHealth); + EntityText(text_offset,tempstr,0); + text_offset++; + + // Print squad name + Q_strncpy(tempstr,"Squad: ",sizeof(tempstr)); + if (m_pSquad) + { + Q_strncat(tempstr,m_pSquad->GetName(),sizeof(tempstr), COPY_ALL_CHARACTERS); + + if( m_pSquad->GetLeader() == this ) + { + Q_strncat(tempstr," (LEADER)",sizeof(tempstr), COPY_ALL_CHARACTERS); + } + + Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS); + } + else + { + Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + // Print enemy name + Q_strncpy(tempstr,"Enemy: ",sizeof(tempstr)); + if (GetEnemy()) + { + if (GetEnemy()->GetEntityName() != NULL_STRING) + { + Q_strncat(tempstr,STRING(GetEnemy()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS); + Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS); + } + else + { + Q_strncat(tempstr,STRING(GetEnemy()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS); + Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS); + } + } + else + { + Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + // Print slot + Q_snprintf(tempstr,sizeof(tempstr),"Slot: %s \n", + SquadSlotName(m_iMySquadSlot)); + EntityText(text_offset,tempstr,0); + text_offset++; + + } + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + // -------------- + // Print Health + // -------------- + Q_snprintf(tempstr,sizeof(tempstr),"Health: %i (DACC:%1.2f)",m_iHealth, GetDamageAccumulator() ); + EntityText(text_offset,tempstr,0); + text_offset++; + + // -------------- + // Print State + // -------------- + static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" }; + if ( static_cast(m_NPCState) < ARRAYSIZE(pStateNames) ) + { + Q_snprintf(tempstr,sizeof(tempstr),"Stat: %s, ", pStateNames[m_NPCState] ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + // ----------------- + // Start Scripting? + // ----------------- + if( IsInAScript() ) + { + Q_snprintf(tempstr,sizeof(tempstr),"STARTSCRIPTING" ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + // ----------------- + // Print MotionType + // ----------------- + int navTypeIndex = (int)GetNavType() + 1; + static const char *pMoveNames[] = { "None", "Ground", "Jump", "Fly", "Climb" }; + Assert( navTypeIndex >= 0 && navTypeIndex < static_cast(ARRAYSIZE(pMoveNames)) ); + if ( static_cast(navTypeIndex) < ARRAYSIZE(pMoveNames) ) + { + Q_snprintf(tempstr,sizeof(tempstr),"Move: %s, ", pMoveNames[navTypeIndex] ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + // -------------- + // Print Schedule + // -------------- + if ( GetCurSchedule() ) + { + CAI_BehaviorBase *pBehavior = GetRunningBehavior(); + if ( pBehavior ) + { + Q_snprintf(tempstr,sizeof(tempstr),"Behv: %s, ", pBehavior->GetName() ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + const char *pName = NULL; + pName = GetCurSchedule()->GetName(); + if ( !pName ) + { + pName = "Unknown"; + } + Q_snprintf(tempstr,sizeof(tempstr),"Schd: %s, ", pName ); + EntityText(text_offset,tempstr,0); + text_offset++; + + if (m_debugOverlays & OVERLAY_NPC_TASK_BIT) + { + for (int i = 0 ; i < GetCurSchedule()->NumTasks(); i++) + { + Q_snprintf(tempstr,sizeof(tempstr),"%s%s%s%s", + ((i==0) ? "Task:":" "), + ((i==GetScheduleCurTaskIndex()) ? "->" :" "), + TaskName(GetCurSchedule()->GetTaskList()[i].iTask), + ((i==GetScheduleCurTaskIndex()) ? "<-" :"")); + + EntityText(text_offset,tempstr,0); + text_offset++; + } + } + else + { + const Task_t *pTask = GetTask(); + if ( pTask ) + { + Q_snprintf(tempstr,sizeof(tempstr),"Task: %s (#%d), ", TaskName(pTask->iTask), GetScheduleCurTaskIndex() ); + } + else + { + Q_strncpy(tempstr,"Task: None",sizeof(tempstr)); + } + EntityText(text_offset,tempstr,0); + text_offset++; + } + } + + // -------------- + // Print Acitivity + // -------------- + if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID && m_Activity != ACT_RESET) + { + Activity iActivity = TranslateActivity( m_Activity ); + + Activity iIdealActivity = Weapon_TranslateActivity( m_IdealActivity ); + iIdealActivity = NPC_TranslateActivity( iIdealActivity ); + + const char *pszActivity = GetActivityName( iActivity ); + const char *pszIdealActivity = GetActivityName( iIdealActivity ); + const char *pszRootActivity = GetActivityName( m_Activity ); + + Q_snprintf(tempstr,sizeof(tempstr),"Actv: %s (%s) [%s]\n", pszActivity, pszIdealActivity, pszRootActivity ); + } + else if (m_Activity == ACT_RESET) + { + Q_strncpy(tempstr,"Actv: RESET",sizeof(tempstr) ); + } + else + { + Q_strncpy(tempstr,"Actv: INVALID", sizeof(tempstr) ); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + // + // Print all the current conditions. + // + if (m_debugOverlays & OVERLAY_NPC_CONDITIONS_BIT) + { + bool bHasConditions = false; + for (int i = 0; i < MAX_CONDITIONS; i++) + { + if (m_Conditions.GetBit(i)) + { + Q_snprintf(tempstr, sizeof(tempstr), "Cond: %s\n", ConditionName(AI_RemapToGlobal(i))); + EntityText(text_offset, tempstr, 0); + text_offset++; + bHasConditions = true; + } + } + if (!bHasConditions) + { + Q_snprintf(tempstr,sizeof(tempstr),"(no conditions)",m_iHealth); + EntityText(text_offset,tempstr,0); + text_offset++; + } + } + + if ( GetFlags() & FL_FLY ) + { + EntityText(text_offset,"HAS FL_FLY",0); + text_offset++; + } + + // -------------- + // Print Interrupte + // -------------- + if (m_interuptSchedule) + { + const char *pName = NULL; + pName = m_interuptSchedule->GetName(); + if ( !pName ) + { + pName = "Unknown"; + } + + Q_snprintf(tempstr,sizeof(tempstr),"Intr: %s (%s)\n", pName, m_interruptText ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + // -------------- + // Print Failure + // -------------- + if (m_failedSchedule) + { + const char *pName = NULL; + pName = m_failedSchedule->GetName(); + if ( !pName ) + { + pName = "Unknown"; + } + Q_snprintf(tempstr,sizeof(tempstr),"Fail: %s (%s)\n", pName,m_failText ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + + // ------------------------------- + // Print any important condtions + // ------------------------------- + if (HasCondition(COND_ENEMY_TOO_FAR)) + { + EntityText(text_offset,"Enemy too far to attack",0); + text_offset++; + } + if ( GetAbsVelocity() != vec3_origin || GetLocalAngularVelocity() != vec3_angle ) + { + char tmp[512]; + Q_snprintf( tmp, sizeof(tmp), "Vel %.1f %.1f %.1f Ang: %.1f %.1f %.1f\n", + GetAbsVelocity().x, GetAbsVelocity().y, GetAbsVelocity().z, + GetLocalAngularVelocity().x, GetLocalAngularVelocity().y, GetLocalAngularVelocity().z ); + EntityText(text_offset,tmp,0); + text_offset++; + } + + // ------------------------------- + // Print shot accuracy + // ------------------------------- + if ( m_LastShootAccuracy != -1 && ai_shot_stats.GetBool() ) + { + CFmtStr msg; + EntityText(text_offset,msg.sprintf("Cur Accuracy: %.1f", m_LastShootAccuracy),0); + text_offset++; + if ( m_TotalShots ) + { + EntityText(text_offset,msg.sprintf("Act Accuracy: %.1f", ((float)m_TotalHits/(float)m_TotalShots)*100.0),0); + text_offset++; + } + + if ( GetActiveWeapon() && GetEnemy() ) + { + Vector curSpread = GetAttackSpread(GetActiveWeapon(), GetEnemy()); + float curCone = RAD2DEG(asin(curSpread.x)) * 2; + float bias = GetSpreadBias( GetActiveWeapon(), GetEnemy()); + EntityText(text_offset,msg.sprintf("Cone %.1f, Bias %.2f", curCone, bias),0); + text_offset++; + } + } + + if ( GetGoalEnt() && GetNavigator()->GetGoalType() == GOALTYPE_PATHCORNER ) + { + Q_strncpy(tempstr,"Pathcorner/goal ent: ",sizeof(tempstr)); + if (GetGoalEnt()->GetEntityName() != NULL_STRING) + { + Q_strncat(tempstr,STRING(GetGoalEnt()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS); + } + else + { + Q_strncat(tempstr,STRING(GetGoalEnt()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS); + } + EntityText(text_offset, tempstr, 0); + text_offset++; + } + + if ( VPhysicsGetObject() ) + { + vphysics_objectstress_t stressOut; + CalculateObjectStress( VPhysicsGetObject(), this, &stressOut ); + Q_snprintf(tempstr, sizeof(tempstr),"Stress: %.2f", stressOut.receivedStress ); + EntityText(text_offset, tempstr, 0); + text_offset++; + } + if ( m_pSquad ) + { + if( m_pSquad->IsLeader(this) ) + { + Q_snprintf(tempstr, sizeof(tempstr),"**Squad Leader**" ); + EntityText(text_offset, tempstr, 0); + text_offset++; + } + + Q_snprintf(tempstr, sizeof(tempstr), "SquadSlot:%s", GetSquadSlotDebugName( GetMyStrategySlot() ) ); + EntityText(text_offset, tempstr, 0); + text_offset++; + } + } + return text_offset; +} + + +//----------------------------------------------------------------------------- +// Purpose: Displays information in the console about the state of this npc. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ReportAIState( void ) +{ + static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" }; + + DevMsg( "%s: ", GetClassname() ); + if ( static_cast(m_NPCState) < ARRAYSIZE(pStateNames) ) + DevMsg( "State: %s, ", pStateNames[m_NPCState] ); + + if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID ) + { + const char *pszActivity = GetActivityName(m_Activity); + const char *pszIdealActivity = GetActivityName(m_IdealActivity); + + DevMsg( "Activity: %s - Ideal Activity: %s\n", pszActivity, pszIdealActivity ); + } + + if ( GetCurSchedule() ) + { + const char *pName = NULL; + pName = GetCurSchedule()->GetName(); + if ( !pName ) + pName = "Unknown"; + DevMsg( "Schedule %s, ", pName ); + const Task_t *pTask = GetTask(); + if ( pTask ) + DevMsg( "Task %d (#%d), ", pTask->iTask, GetScheduleCurTaskIndex() ); + } + else + DevMsg( "No Schedule, " ); + + if ( GetEnemy() != NULL ) + { + g_pEffects->Sparks( GetEnemy()->GetAbsOrigin() + Vector( 0, 0, 64 ) ); + DevMsg( "\nEnemy is %s", GetEnemy()->GetClassname() ); + } + else + DevMsg( "No enemy " ); + + if ( IsMoving() ) + { + DevMsg( " Moving " ); + if ( m_flMoveWaitFinished > gpGlobals->curtime ) + DevMsg( ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->curtime ); + else if ( m_IdealActivity == GetStoppedActivity() ) + DevMsg( ": In stopped anim. " ); + } + + DevMsg( "Leader." ); + + DevMsg( "\n" ); + DevMsg( "Yaw speed:%3.1f,Health: %3d\n", GetMotor()->GetYawSpeed(), m_iHealth ); + + if ( GetGroundEntity() ) + { + DevMsg( "Groundent:%s\n\n", GetGroundEntity()->GetClassname() ); + } + else + { + DevMsg( "Groundent: NULL\n\n" ); + } +} + +//----------------------------------------------------------------------------- + +ConVar ai_report_task_timings_on_limit( "ai_report_task_timings_on_limit", "0", FCVAR_ARCHIVE ); +ConVar ai_think_limit_label( "ai_think_limit_label", "0", FCVAR_ARCHIVE ); + +void CAI_BaseNPC::ReportOverThinkLimit( float time ) +{ + DevMsg( "%s thinking for %.02fms!!! (%s); r%.2f (c%.2f, pst%.2f, ms%.2f), p-r%.2f, m%.2f\n", + GetDebugName(), time, GetCurSchedule()->GetName(), + g_AIRunTimer.GetDuration().GetMillisecondsF(), + g_AIConditionsTimer.GetDuration().GetMillisecondsF(), + g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF(), + g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF(), + g_AIPostRunTimer.GetDuration().GetMillisecondsF(), + g_AIMoveTimer.GetDuration().GetMillisecondsF() ); + + if (ai_think_limit_label.GetBool()) + { + Vector tmp; + CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &tmp ); + tmp.z += 16; + + float max = -1; + const char *pszMax = "unknown"; + + if ( g_AIConditionsTimer.GetDuration().GetMillisecondsF() > max ) + { + max = g_AIConditionsTimer.GetDuration().GetMillisecondsF(); + pszMax = "Conditions"; + } + if ( g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF() > max ) + { + max = g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF(); + pszMax = "Pre-think"; + } + if ( g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF() > max ) + { + max = g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF(); + pszMax = "Schedule"; + } + if ( g_AIPostRunTimer.GetDuration().GetMillisecondsF() > max ) + { + max = g_AIPostRunTimer.GetDuration().GetMillisecondsF(); + pszMax = "Post-run"; + } + if ( g_AIMoveTimer.GetDuration().GetMillisecondsF() > max ) + { + max = g_AIMoveTimer.GetDuration().GetMillisecondsF(); + pszMax = "Move"; + } + NDebugOverlay::Text( tmp, (char*)(const char *)CFmtStr( "Slow %.1f, %s %.1f ", time, pszMax, max ), false, 1 ); + } + + if ( ai_report_task_timings_on_limit.GetBool() ) + DumpTaskTimings(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether or not this npc can play the scripted sequence or AI +// sequence that is trying to possess it. If DisregardState is set, the npc +// will be sucked into the script no matter what state it is in. ONLY +// Scripted AI ents should allow this. +// Input : fDisregardNPCState - +// interruptLevel - +// eMode - If the function returns true, eMode will be one of the following values: +// CAN_PLAY_NOW +// CAN_PLAY_ENQUEUED +// Output : +//----------------------------------------------------------------------------- +CanPlaySequence_t CAI_BaseNPC::CanPlaySequence( bool fDisregardNPCState, int interruptLevel ) +{ + CanPlaySequence_t eReturn = CAN_PLAY_NOW; + + if ( m_hCine ) + { + if ( !m_hCine->CanEnqueueAfter() ) + { + // npc is already running one scripted sequence and has an important script to play next + return CANNOT_PLAY; + } + + eReturn = CAN_PLAY_ENQUEUED; + } + + if ( !IsAlive() ) + { + // npc is dead! + return CANNOT_PLAY; + } + + if ( fDisregardNPCState ) + { + // ok to go, no matter what the npc state. (scripted AI) + + // No clue as to how to proced if they're climbing or jumping + // Assert( GetNavType() != NAV_JUMP && GetNavType() != NAV_CLIMB ); + + return eReturn; + } + + if ( m_NPCState == NPC_STATE_NONE || m_NPCState == NPC_STATE_IDLE || m_IdealNPCState == NPC_STATE_IDLE ) + { + // ok to go, but only in these states + return eReturn; + } + + if ( m_NPCState == NPC_STATE_ALERT && interruptLevel >= SS_INTERRUPT_BY_NAME ) + { + return eReturn; + } + + // unknown situation + return CANNOT_PLAY; +} + + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::SetHintGroup( string_t newGroup, bool bHintGroupNavLimiting ) +{ + string_t oldGroup = m_strHintGroup; + m_strHintGroup = newGroup; + m_bHintGroupNavLimiting = bHintGroupNavLimiting; + + if ( oldGroup != newGroup ) + OnChangeHintGroup( oldGroup, newGroup ); + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +Vector CAI_BaseNPC::GetShootEnemyDir( const Vector &shootOrigin, bool bNoisy ) +{ + CBaseEntity *pEnemy = GetEnemy(); + + if ( pEnemy ) + { + Vector vecEnemyLKP = GetEnemyLKP(); + Vector retval = (pEnemy->BodyTarget( shootOrigin, bNoisy ) - pEnemy->GetAbsOrigin()) + vecEnemyLKP - shootOrigin; + VectorNormalize( retval ); + return retval; + } + else + { + Vector forward; + AngleVectors( GetLocalAngles(), &forward ); + return forward; + } +} + +//----------------------------------------------------------------------------- +// Simulates many times and reports statistical accuracy. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CollectShotStats( const Vector &vecShootOrigin, const Vector &vecShootDir ) +{ +#ifdef HL2_DLL + if( ai_shot_stats.GetBool() != 0 && GetEnemy()->IsPlayer() ) + { + int iterations = ai_shot_stats_term.GetInt(); + int iHits = 0; + Vector testDir = vecShootDir; + + CShotManipulator manipulator( testDir ); + + for( int i = 0 ; i < iterations ; i++ ) + { + // Apply appropriate accuracy. + manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) ); + Vector shotDir = manipulator.GetResult(); + + Vector vecEnd = vecShootOrigin + shotDir * 8192; + + trace_t tr; + AI_TraceLine( vecShootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); + + if( tr.m_pEnt && tr.m_pEnt == GetEnemy() ) + { + iHits++; + } + Vector vecProjectedPosition = GetActualShootPosition( vecShootOrigin ); + Vector testDir = vecProjectedPosition - vecShootOrigin; + VectorNormalize( testDir ); + manipulator.SetShootDir( testDir ); + } + + float flHitPercent = ((float)iHits / (float)iterations) * 100.0; + m_LastShootAccuracy = flHitPercent; + //DevMsg("Shots:%d Hits:%d Percentage:%.1f\n", iterations, iHits, flHitPercent); + } + else + { + m_LastShootAccuracy = -1; + } +#endif +} + +#ifdef HL2_DLL +//----------------------------------------------------------------------------- +// Purpose: Return the actual position the NPC wants to fire at when it's trying +// to hit it's current enemy. +//----------------------------------------------------------------------------- +Vector CAI_BaseNPC::GetActualShootPosition( const Vector &shootOrigin ) +{ + // Project the target's location into the future. + Vector vecEnemyLKP = GetEnemyLKP(); + Vector vecTargetPosition = (GetEnemy()->BodyTarget( shootOrigin ) - GetEnemy()->GetAbsOrigin()) + vecEnemyLKP; + + // lead for some fraction of a second. + return (vecTargetPosition + ( GetEnemy()->GetSmoothedVelocity() * ai_lead_time.GetFloat() )); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CAI_BaseNPC::GetSpreadBias( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) +{ + float bias = BaseClass::GetSpreadBias( pWeapon, pTarget ); + AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget ); + if ( ai_shot_bias.GetFloat() != 1.0 ) + bias = ai_shot_bias.GetFloat(); + if ( pEnemyInfo ) + { + float timeToFocus = ai_spread_pattern_focus_time.GetFloat(); + if ( timeToFocus > 0.0 ) + { + float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy; + if ( timeSinceValidEnemy < 0.0f ) + { + timeSinceValidEnemy = 0.0f; + } + float timeSinceReacquire = gpGlobals->curtime - pEnemyInfo->timeLastReacquired; + if ( timeSinceValidEnemy < timeToFocus ) + { + float scale = timeSinceValidEnemy / timeToFocus; + Assert( scale >= 0.0 && scale <= 1.0 ); + bias *= scale; + } + else if ( timeSinceReacquire < timeToFocus ) // handled seperately as might be tuning seperately + { + float scale = timeSinceReacquire / timeToFocus; + Assert( scale >= 0.0 && scale <= 1.0 ); + bias *= scale; + } + + } + } + return bias; +} + +//----------------------------------------------------------------------------- +Vector CAI_BaseNPC::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) +{ + Vector baseResult = BaseClass::GetAttackSpread( pWeapon, pTarget ); + + AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget ); + if ( pEnemyInfo ) + { + float timeToFocus = ai_spread_cone_focus_time.GetFloat(); + if ( timeToFocus > 0.0 ) + { + float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy; + if ( timeSinceValidEnemy < 0 ) + timeSinceValidEnemy = 0; + if ( timeSinceValidEnemy < timeToFocus ) + { + float coneMultiplier = ai_spread_defocused_cone_multiplier.GetFloat(); + if ( coneMultiplier > 1.0 ) + { + float scale = 1.0 - timeSinceValidEnemy / timeToFocus; + Assert( scale >= 0.0 && scale <= 1.0 ); + float multiplier = ( (coneMultiplier - 1.0) * scale ) + 1.0; + baseResult *= multiplier; + } + } + } + } + return baseResult; +} + +//----------------------------------------------------------------------------- +// Similar to calling GetShootEnemyDir, but returns the exact trajectory to +// fire the bullet along, after calculating for target speed, location, +// concealment, etc. +// +// Ultimately, this code results in the shooter aiming his weapon somewhere ahead of +// a moving target. Since bullet traces are instant, aiming ahead of a target +// will result in more misses than hits. This is designed to provide targets with +// a bonus when moving perpendicular to the shooter's line of sight. +// +// Do not confuse this with leading a target in an effort to more easily hit it. +// +// This code PENALIZES a target for moving directly along the shooter's line of sight. +// +// This code REWARDS a target for moving perpendicular to the shooter's line of sight. +//----------------------------------------------------------------------------- +Vector CAI_BaseNPC::GetActualShootTrajectory( const Vector &shootOrigin ) +{ + if( !GetEnemy() ) + return GetShootEnemyDir( shootOrigin ); + + // If we're above water shooting at a player underwater, bias some of the shots forward of + // the player so that they see the cool bubble trails in the water ahead of them. + if (GetEnemy()->IsPlayer() && (GetWaterLevel() != 3) && (GetEnemy()->GetWaterLevel() == 3)) + { +#if 1 + if (random->RandomInt(0, 4) < 3) + { + Vector vecEnemyForward; + GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL ); + vecEnemyForward.z = 0; + + // Lead up to a second ahead of them unless they are moving backwards. + Vector vecEnemyVelocity = GetEnemy()->GetSmoothedVelocity(); + VectorNormalize( vecEnemyVelocity ); + float flVelocityScale = vecEnemyForward.Dot( vecEnemyVelocity ); + if ( flVelocityScale < 0.0f ) + { + flVelocityScale = 0.0f; + } + + Vector vecAimPos = GetEnemy()->EyePosition() + ( 48.0f * vecEnemyForward ) + (flVelocityScale * GetEnemy()->GetSmoothedVelocity() ); + //NDebugOverlay::Cross3D(vecAimPos, Vector(-16,-16,-16), Vector(16,16,16), 255, 255, 0, true, 1.0f ); + + //vecAimPos.z = UTIL_WaterLevel( vecAimPos, vecAimPos.z, vecAimPos.z + 400.0f ); + //NDebugOverlay::Cross3D(vecAimPos, Vector(-32,-32,-32), Vector(32,32,32), 255, 0, 0, true, 1.0f ); + + Vector vecShotDir = vecAimPos - shootOrigin; + VectorNormalize( vecShotDir ); + return vecShotDir; + } +#else + if (random->RandomInt(0, 4) < 3) + { + // Aim at a point a few feet in front of the player's eyes + Vector vecEnemyForward; + GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL ); + + Vector vecAimPos = GetEnemy()->EyePosition() + (120.0f * vecEnemyForward ); + + Vector vecShotDir = vecAimPos - shootOrigin; + VectorNormalize( vecShotDir ); + + CShotManipulator manipulator( vecShotDir ); + manipulator.ApplySpread( VECTOR_CONE_10DEGREES, 1 ); + vecShotDir = manipulator.GetResult(); + + return vecShotDir; + } +#endif + } + + Vector vecProjectedPosition = GetActualShootPosition( shootOrigin ); + + Vector shotDir = vecProjectedPosition - shootOrigin; + VectorNormalize( shotDir ); + + CollectShotStats( shootOrigin, shotDir ); + + // NOW we have a shoot direction. Where a 100% accurate bullet should go. + // Modify it by weapon proficiency. + // construct a manipulator + CShotManipulator manipulator( shotDir ); + + // Apply appropriate accuracy. + bool bUsePerfectAccuracy = false; + if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE ) + { + CNPC_Bullseye *pBullseye = dynamic_cast(GetEnemy()); + if ( pBullseye && pBullseye->UsePerfectAccuracy() ) + { + bUsePerfectAccuracy = true; + } + } + + if ( !bUsePerfectAccuracy ) + { + manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) ); + shotDir = manipulator.GetResult(); + } + + // Look for an opportunity to make misses hit interesting things. + CBaseCombatCharacter *pEnemy; + + pEnemy = GetEnemy()->MyCombatCharacterPointer(); + + if( pEnemy && pEnemy->ShouldShootMissTarget( this ) ) + { + Vector vecEnd = shootOrigin + shotDir * 8192; + trace_t tr; + + AI_TraceLine(shootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); + + if( tr.fraction != 1.0 && tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO ) + { + // Hit something we can harm. Just shoot it. + return manipulator.GetResult(); + } + + // Find something interesting around the enemy to shoot instead of just missing. + CBaseEntity *pMissTarget = pEnemy->FindMissTarget(); + + if( pMissTarget ) + { + shotDir = pMissTarget->WorldSpaceCenter() - shootOrigin; + VectorNormalize( shotDir ); + } + } + + return shotDir; +} +#endif // HL2_DLL + +//----------------------------------------------------------------------------- + +Vector CAI_BaseNPC::BodyTarget( const Vector &posSrc, bool bNoisy ) +{ + Vector low = WorldSpaceCenter() - ( WorldSpaceCenter() - GetAbsOrigin() ) * .25; + Vector high = EyePosition(); + Vector delta = high - low; + Vector result; + if ( bNoisy ) + { + // bell curve + float rand1 = random->RandomFloat( 0.0, 0.5 ); + float rand2 = random->RandomFloat( 0.0, 0.5 ); + result = low + delta * rand1 + delta * rand2; + } + else + result = low + delta * 0.5; + + return result; +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::ShouldMoveAndShoot() +{ + return ( ( CapabilitiesGet() & bits_CAP_MOVE_SHOOT ) != 0 ); +} + + +//========================================================= +// FacingIdeal - tells us if a npc 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 CAI_BaseNPC::FacingIdeal( void ) +{ + if ( fabs( GetMotor()->DeltaIdealYaw() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!! + { + return true; + } + + return false; +} + +//--------------------------------- + +void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp ) +{ + GetMotor()->AddFacingTarget( pTarget, flImportance, flDuration, flRamp ); +} + +void CAI_BaseNPC::AddFacingTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) +{ + GetMotor()->AddFacingTarget( vecPosition, flImportance, flDuration, flRamp ); +} + +void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) +{ + GetMotor()->AddFacingTarget( pTarget, vecPosition, flImportance, flDuration, flRamp ); +} + +float CAI_BaseNPC::GetFacingDirection( Vector &vecDir ) +{ + return (GetMotor()->GetFacingDirection( vecDir )); +} + + +//--------------------------------- + + +int CAI_BaseNPC::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener ) +{ + int sentenceIndex = -1; + + if ( pszSentence && IsAlive() ) + { + + if ( pszSentence[0] == '!' ) + { + sentenceIndex = SENTENCEG_Lookup( pszSentence ); + CPASAttenuationFilter filter( this, soundlevel ); + CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, volume, soundlevel, 0, PITCH_NORM ); + } + else + { + sentenceIndex = SENTENCEG_PlayRndSz( edict(), pszSentence, volume, soundlevel, 0, PITCH_NORM ); + } + } + + return sentenceIndex; +} + + +int CAI_BaseNPC::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener ) +{ + return PlaySentence( pszSentence, delay, volume, soundlevel, pListener ); +} + + +void CAI_BaseNPC::SentenceStop( void ) +{ + EmitSound( "AI_BaseNPC.SentenceStop" ); +} + + + + +//----------------------------------------------------------------------------- +// Purpose: Play a one-shot scene +// Input : +// Output : +//----------------------------------------------------------------------------- +float CAI_BaseNPC::PlayScene( const char *pszScene, float flDelay, AI_Response *response ) +{ + return InstancedScriptedScene( this, pszScene, NULL, flDelay, false, response ); +} + +//----------------------------------------------------------------------------- +// Purpose: Generate a one-shot scene in memory with one track which is to play the named sound on the actor +// Input : *soundname - +// Output : float +//----------------------------------------------------------------------------- +float CAI_BaseNPC::PlayAutoGeneratedSoundScene( const char *soundname ) +{ + return InstancedAutoGeneratedSoundScene( this, soundname ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *CAI_BaseNPC::FindNamedEntity( const char *name, IEntityFindFilter *pFilter ) +{ + if ( !stricmp( name, "!player" )) + { + return ( CBaseEntity * )AI_GetSinglePlayer(); + } + else if ( !stricmp( name, "!enemy" ) ) + { + if (GetEnemy() != NULL) + return GetEnemy(); + } + else if ( !stricmp( name, "!self" ) || !stricmp( name, "!target1" ) ) + { + return this; + } + else if ( !stricmp( name, "!nearestfriend" ) || !stricmp( name, "!friend" ) ) + { + // FIXME: look at CBaseEntity *CNPCSimpleTalker::FindNearestFriend(bool fPlayer) + // punt for now + return ( CBaseEntity * )AI_GetSinglePlayer(); + } + else if (!stricmp( name, "self" )) + { + static int selfwarningcount = 0; + + // fix the vcd, the reserved names have changed + if ( ++selfwarningcount < 5 ) + { + DevMsg( "ERROR: \"self\" is no longer used, use \"!self\" in vcd instead!\n" ); + } + return this; + } + else if ( !stricmp( name, "Player" )) + { + static int playerwarningcount = 0; + if ( ++playerwarningcount < 5 ) + { + DevMsg( "ERROR: \"player\" is no longer used, use \"!player\" in vcd instead!\n" ); + } + return ( CBaseEntity * )AI_GetSinglePlayer(); + } + else + { + // search for up to 32 entities with the same name and choose one randomly + CBaseEntity *entityList[ FINDNAMEDENTITY_MAX_ENTITIES ]; + CBaseEntity *entity; + int iCount; + + entity = NULL; + for( iCount = 0; iCount < FINDNAMEDENTITY_MAX_ENTITIES; iCount++ ) + { + entity = gEntList.FindEntityByName( entity, name, NULL, NULL, NULL, pFilter ); + if ( !entity ) + { + break; + } + entityList[ iCount ] = entity; + } + + if ( iCount > 0 ) + { + int index = RandomInt( 0, iCount - 1 ); + entity = entityList[ index ]; + return entity; + } + } + + return NULL; +} + + +void CAI_BaseNPC::CorpseFallThink( void ) +{ + if ( GetFlags() & FL_ONGROUND ) + { + SetThink ( NULL ); + + SetSequenceBox( ); + } + else + { + SetNextThink( gpGlobals->curtime + 0.1f ); + } +} + +// Call after animation/pose is set up +void CAI_BaseNPC::NPCInitDead( void ) +{ + InitBoneControllers(); + + RemoveSolidFlags( FSOLID_NOT_SOLID ); + + // so he'll fall to ground + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + + SetCycle( 0 ); + ResetSequenceInfo( ); + m_flPlaybackRate = 0; + + // Copy health + m_iMaxHealth = m_iHealth; + m_lifeState = LIFE_DEAD; + + UTIL_SetSize(this, vec3_origin, vec3_origin ); + + // Setup health counters, etc. + SetThink( &CAI_BaseNPC::CorpseFallThink ); + + SetNextThink( gpGlobals->curtime + 0.5f ); +} + +//========================================================= +// BBoxIsFlat - check to see if the npc's bounding box +// is lying flat on a surface (traces from all four corners +// are same length.) +//========================================================= +bool CAI_BaseNPC::BBoxFlat ( void ) +{ + trace_t tr; + Vector vecPoint; + float flXSize, flYSize; + float flLength; + float flLength2; + + flXSize = WorldAlignSize().x / 2; + flYSize = WorldAlignSize().y / 2; + + vecPoint.x = GetAbsOrigin().x + flXSize; + vecPoint.y = GetAbsOrigin().y + flYSize; + vecPoint.z = GetAbsOrigin().z; + + AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + flLength = (vecPoint - tr.endpos).Length(); + + vecPoint.x = GetAbsOrigin().x - flXSize; + vecPoint.y = GetAbsOrigin().y - flYSize; + + AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + flLength2 = (vecPoint - tr.endpos).Length(); + if ( flLength2 > flLength ) + { + return false; + } + flLength = flLength2; + + vecPoint.x = GetAbsOrigin().x - flXSize; + vecPoint.y = GetAbsOrigin().y + flYSize; + AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + flLength2 = (vecPoint - tr.endpos).Length(); + if ( flLength2 > flLength ) + { + return false; + } + flLength = flLength2; + + vecPoint.x = GetAbsOrigin().x + flXSize; + vecPoint.y = GetAbsOrigin().y - flYSize; + AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + flLength2 = (vecPoint - tr.endpos).Length(); + if ( flLength2 > flLength ) + { + return false; + } + flLength = flLength2; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEnemy - +// bSetCondNewEnemy - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::SetEnemy( CBaseEntity *pEnemy, bool bSetCondNewEnemy ) +{ + if (m_hEnemy != pEnemy) + { + ClearAttackConditions( ); + VacateStrategySlot(); + m_GiveUpOnDeadEnemyTimer.Stop(); + + // If we've just found a new enemy, set the condition + if ( pEnemy && bSetCondNewEnemy ) + { + SetCondition( COND_NEW_ENEMY ); + } + } + + // Assert( (pEnemy == NULL) || (m_NPCState == NPC_STATE_COMBAT) ); + + m_hEnemy = pEnemy; + m_flTimeEnemyAcquired = gpGlobals->curtime; + + m_LastShootAccuracy = -1; + m_TotalShots = 0; + m_TotalHits = 0; + + if ( !pEnemy ) + ClearCondition( COND_NEW_ENEMY ); +} + +const Vector &CAI_BaseNPC::GetEnemyLKP() const +{ + return (const_cast(this))->GetEnemies()->LastKnownPosition( GetEnemy() ); +} + +float CAI_BaseNPC::GetEnemyLastTimeSeen() const +{ + return (const_cast(this))->GetEnemies()->LastTimeSeen( GetEnemy() ); +} + +void CAI_BaseNPC::MarkEnemyAsEluded() +{ + GetEnemies()->MarkAsEluded( GetEnemy() ); +} + +void CAI_BaseNPC::ClearEnemyMemory() +{ + GetEnemies()->ClearMemory( GetEnemy() ); +} + +bool CAI_BaseNPC::EnemyHasEludedMe() const +{ + return (const_cast(this))->GetEnemies()->HasEludedMe( GetEnemy() ); +} + +void CAI_BaseNPC::SetTarget( CBaseEntity *pTarget ) +{ + m_hTargetEnt = pTarget; +} + + +//========================================================= +// Choose Enemy - tries to find the best suitable enemy for the npc. +//========================================================= + +bool CAI_BaseNPC::ShouldChooseNewEnemy() +{ + CBaseEntity *pEnemy = GetEnemy(); + if ( pEnemy ) + { + if ( GetEnemies()->GetSerialNumber() != m_EnemiesSerialNumber ) + { + return true; + } + + m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber(); + + if ( EnemyHasEludedMe() || (IRelationType( pEnemy ) != D_HT && IRelationType( pEnemy ) != D_FR) || !IsValidEnemy( pEnemy ) ) + { + DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (1)\n" ); + return true; + } + if ( HasCondition(COND_SEE_HATE) || HasCondition(COND_SEE_DISLIKE) || HasCondition(COND_SEE_NEMESIS) || HasCondition(COND_SEE_FEAR) ) + { + DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (2)\n" ); + return true; + } + if ( !pEnemy->IsAlive() ) + { + if ( m_GiveUpOnDeadEnemyTimer.IsRunning() ) + { + if ( m_GiveUpOnDeadEnemyTimer.Expired() ) + { + DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (3)\n" ); + return true; + } + } + else + m_GiveUpOnDeadEnemyTimer.Start(); + } + + AI_EnemyInfo_t *pInfo = GetEnemies()->Find( pEnemy ); + + if ( m_FailChooseEnemyTimer.Expired() ) + { + m_FailChooseEnemyTimer.Set( 1.5 ); + if ( HasCondition( COND_TASK_FAILED ) || + ( pInfo && ( pInfo->timeAtFirstHand == AI_INVALID_TIME || gpGlobals->curtime - pInfo->timeLastSeen > 10 ) ) ) + { + return true; + } + } + + if ( pInfo && pInfo->timeValidEnemy < gpGlobals->curtime ) + { + DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> false\n" ); + return false; + } + } + + DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (4)\n" ); + m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber(); + + return true; +} + +//------------------------------------- + +bool CAI_BaseNPC::ChooseEnemy( void ) +{ + AI_PROFILE_SCOPE(CAI_Enemies_ChooseEnemy); + + DbgEnemyMsg( this, "ChooseEnemy() {\n" ); + + //--------------------------------- + // + // Gather initial conditions + // + + CBaseEntity *pInitialEnemy = GetEnemy(); + CBaseEntity *pChosenEnemy = pInitialEnemy; + + // Use memory bits in case enemy pointer altered outside this function, (e.g., ehandle goes NULL) + bool fHadEnemy = ( HasMemory( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER ) ); + bool fEnemyWasPlayer = HasMemory( bits_MEMORY_HAD_PLAYER ); + bool fEnemyWentNull = ( fHadEnemy && !pInitialEnemy ); + bool fEnemyEluded = ( fEnemyWentNull || ( pInitialEnemy && GetEnemies()->HasEludedMe( pInitialEnemy ) ) ); + + //--------------------------------- + // + // Establish suitability of choosing a new enemy + // + + bool fHaveCondNewEnemy; + bool fHaveCondLostEnemy; + + if ( GetCurSchedule() && !FScheduleDone() ) + { + Assert( InterruptFromCondition( COND_NEW_ENEMY ) == COND_NEW_ENEMY && InterruptFromCondition( COND_LOST_ENEMY ) == COND_LOST_ENEMY ); + fHaveCondNewEnemy = GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY ); + fHaveCondLostEnemy = GetCurSchedule()->HasInterrupt( COND_LOST_ENEMY ); + + // See if they've been added as a custom interrupt + if ( !fHaveCondNewEnemy ) + { + fHaveCondNewEnemy = IsCustomInterruptConditionSet( COND_NEW_ENEMY ); + } + if ( !fHaveCondLostEnemy ) + { + fHaveCondLostEnemy = IsCustomInterruptConditionSet( COND_LOST_ENEMY ); + } + } + else + { + fHaveCondNewEnemy = true; // not having a schedule is the same as being interruptable by any condition + fHaveCondLostEnemy = true; + } + + if ( !fEnemyWentNull ) + { + if ( !fHaveCondNewEnemy && !( fHaveCondLostEnemy && fEnemyEluded ) ) + { + // DO NOT mess with the npc's enemy pointer unless the schedule the npc is currently + // running will be interrupted by COND_NEW_ENEMY or COND_LOST_ENEMY. This will + // eliminate the problem of npcs 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. + m_bSkippedChooseEnemy = true; + + DbgEnemyMsg( this, "Skipped enemy selection due to schedule restriction\n" ); + DbgEnemyMsg( this, "}\n" ); + return ( pChosenEnemy != NULL ); + } + } + else if ( !fHaveCondNewEnemy && !fHaveCondLostEnemy && GetCurSchedule() ) + { + DevMsg( 2, "WARNING: AI enemy went NULL but schedule (%s) is not interested\n", GetCurSchedule()->GetName() ); + } + + m_bSkippedChooseEnemy = false; + + //--------------------------------- + // + // Select a target + // + + if ( ShouldChooseNewEnemy() ) + { + pChosenEnemy = BestEnemy(); + } + + //--------------------------------- + // + // React to result of selection + // + + bool fChangingEnemy = ( pChosenEnemy != pInitialEnemy ); + + if ( fChangingEnemy || fEnemyWentNull ) + { + DbgEnemyMsg( this, "Enemy changed from %s to %s\n", pInitialEnemy->GetDebugName(), pChosenEnemy->GetDebugName() ); + Forget( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER ); + + // Did our old enemy snuff it? + if ( pInitialEnemy && !pInitialEnemy->IsAlive() ) + { + SetCondition( COND_ENEMY_DEAD ); + } + + SetEnemy( pChosenEnemy ); + + if ( fHadEnemy ) + { + // Vacate any strategy slot on old enemy + VacateStrategySlot(); + + // Force output event for establishing LOS + Forget( bits_MEMORY_HAD_LOS ); + // m_flLastAttackTime = 0; + } + + if ( !pChosenEnemy ) + { + // Don't break on enemies going null if they've been killed + if ( !HasCondition(COND_ENEMY_DEAD) ) + { + SetCondition( COND_ENEMY_WENT_NULL ); + } + + if ( fEnemyEluded ) + { + SetCondition( COND_LOST_ENEMY ); + LostEnemySound(); + } + + if ( fEnemyWasPlayer ) + { + m_OnLostPlayer.FireOutput( pInitialEnemy, this ); + } + m_OnLostEnemy.FireOutput( pInitialEnemy, this); + } + else + { + Remember( ( pChosenEnemy->IsPlayer() ) ? bits_MEMORY_HAD_PLAYER : bits_MEMORY_HAD_ENEMY ); + } + } + + //--------------------------------- + + return ( pChosenEnemy != NULL ); +} + + +//========================================================= +void CAI_BaseNPC::PickupWeapon( CBaseCombatWeapon *pWeapon ) +{ + pWeapon->OnPickedUp( this ); + Weapon_Equip( pWeapon ); + m_iszPendingWeapon = NULL_STRING; +} + +//========================================================= +// DropItem - dead npc drops named item +//========================================================= +CBaseEntity *CAI_BaseNPC::DropItem ( char *pszItemName, Vector vecPos, QAngle vecAng ) +{ + if ( !pszItemName ) + { + DevMsg( "DropItem() - No item name!\n" ); + return NULL; + } + + CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, this ); + + if ( pItem ) + { + if ( g_pGameRules->IsAllowedToSpawn( pItem ) == false ) + { + UTIL_Remove( pItem ); + return NULL; + } + + IPhysicsObject *pPhys = pItem->VPhysicsGetObject(); + + if ( pPhys ) + { + // Add an extra push in a random direction + Vector vel = RandomVector( -64.0f, 64.0f ); + AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f ); + + vel[2] = 0.0f; + pPhys->AddVelocity( &vel, &angImp ); + } + else + { + // do we want this behavior to be default?! (sjb) + pItem->ApplyAbsVelocityImpulse( GetAbsVelocity() ); + pItem->ApplyLocalAngularVelocityImpulse( AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 ) ); + } + + return pItem; + } + else + { + DevMsg( "DropItem() - Didn't create!\n" ); + return NULL; + } + +} + +bool CAI_BaseNPC::ShouldFadeOnDeath( void ) +{ + if ( g_RagdollLVManager.IsLowViolence() ) + { + return true; + } + else + { + // if flagged to fade out + return HasSpawnFlags(SF_NPC_FADE_CORPSE); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Indicates whether or not this npc should play an idle sound now. +// +// +// Output : Returns true if yes, false if no. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::ShouldPlayIdleSound( void ) +{ + if ( ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT ) && + random->RandomInt(0,99) == 0 && !HasSpawnFlags(SF_NPC_GAG) ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Make a sound that other AI's can hear, to broadcast our presence +// Input : volume (radius) of the sound. +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::MakeAIFootstepSound( float volume, float duration ) +{ + CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), static_cast(volume), duration, this, SOUNDENT_CHANNEL_NPC_FOOTSTEP ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::FOkToMakeSound( int soundPriority ) +{ + // ask the squad to filter sounds if I'm in one + if ( m_pSquad ) + { + if ( !m_pSquad->FOkToMakeSound( soundPriority ) ) + return false; + } + else + { + // otherwise, check my own sound timer + // Am I making uninterruptable sound? + if (gpGlobals->curtime <= m_flSoundWaitTime) + { + if ( soundPriority <= m_nSoundPriority ) + return false; + } + } + + // no talking outside of combat if gagged. + if ( HasSpawnFlags(SF_NPC_GAG) && ( m_NPCState != NPC_STATE_COMBAT ) ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CAI_BaseNPC::JustMadeSound( int soundPriority, float flSoundLength ) +{ + m_flSoundWaitTime = gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0); + m_nSoundPriority = soundPriority; + + if (m_pSquad) + { + m_pSquad->JustMadeSound( soundPriority, gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0) ); + } +} + +Activity CAI_BaseNPC::GetStoppedActivity( void ) +{ + if (GetNavigator()->IsGoalActive()) + { + Activity activity = GetNavigator()->GetArrivalActivity(); + + if (activity > ACT_RESET) + { + return activity; + } + } + + return ACT_IDLE; +} + + +//========================================================= +//========================================================= +void CAI_BaseNPC::OnScheduleChange ( void ) +{ + EndTaskOverlay(); + + m_pNavigator->OnScheduleChange(); + + m_flMoveWaitFinished = 0; + + VacateStrategySlot(); + + // If I still have have a route, clear it + // FIXME: Routes should only be cleared inside of tasks (kenb) + GetNavigator()->ClearGoal(); + + UnlockBestSound(); + + // If I locked a hint node clear it + if ( HasMemory(bits_MEMORY_LOCKED_HINT) && GetHintNode() != NULL) + { + float hintDelay = GetHintDelay(GetHintNode()->HintType()); + GetHintNode()->Unlock(hintDelay); + SetHintNode( NULL ); + } +} + + + +CBaseCombatCharacter* CAI_BaseNPC::GetEnemyCombatCharacterPointer() +{ + if ( GetEnemy() == NULL ) + return NULL; + + return GetEnemy()->MyCombatCharacterPointer(); +} + + +// Global Savedata for npc +// +// This should be an exact copy of the var's in the header. Fields +// that aren't save/restored are commented out + +BEGIN_DATADESC( CAI_BaseNPC ) + + // m_pSchedule (reacquired on restore) + DEFINE_EMBEDDED( m_ScheduleState ), + DEFINE_FIELD( m_IdealSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules + DEFINE_FIELD( m_failSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules + DEFINE_FIELD( m_bUsingStandardThinkTime, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flLastRealThinkTime, FIELD_TIME ), + // m_iFrameBlocked (not saved) + // m_bInChoreo (not saved) + // m_bDoPostRestoreRefindPath (not saved) + // gm_flTimeLastSpawn (static) + // gm_nSpawnedThisFrame (static) + // m_Conditions (custom save) + // m_CustomInterruptConditions (custom save) + // m_ConditionsPreIgnore (custom save) + // m_InverseIgnoreConditions (custom save) + DEFINE_FIELD( m_bForceConditionsGather, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bConditionsGathered, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bSkippedChooseEnemy, FIELD_BOOLEAN ), + DEFINE_FIELD( m_NPCState, FIELD_INTEGER ), + DEFINE_FIELD( m_IdealNPCState, FIELD_INTEGER ), + DEFINE_FIELD( m_flLastStateChangeTime, FIELD_TIME ), + DEFINE_FIELD( m_Efficiency, FIELD_INTEGER ), + DEFINE_FIELD( m_MoveEfficiency, FIELD_INTEGER ), + DEFINE_FIELD( m_flNextDecisionTime, FIELD_TIME ), + DEFINE_KEYFIELD( m_SleepState, FIELD_INTEGER, "sleepstate" ), + DEFINE_FIELD( m_SleepFlags, FIELD_INTEGER ), + DEFINE_KEYFIELD( m_flWakeRadius, FIELD_FLOAT, "wakeradius" ), + DEFINE_KEYFIELD( m_bWakeSquad, FIELD_BOOLEAN, "wakesquad" ), + DEFINE_FIELD( m_nWakeTick, FIELD_TICK ), + + DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ), + DEFINE_CUSTOM_FIELD( m_translatedActivity, ActivityDataOps() ), + DEFINE_CUSTOM_FIELD( m_IdealActivity, ActivityDataOps() ), + DEFINE_CUSTOM_FIELD( m_IdealTranslatedActivity, ActivityDataOps() ), + DEFINE_CUSTOM_FIELD( m_IdealWeaponActivity, ActivityDataOps() ), + + DEFINE_FIELD( m_nIdealSequence, FIELD_INTEGER ), + DEFINE_EMBEDDEDBYREF( m_pSenses ), + DEFINE_EMBEDDEDBYREF( m_pLockedBestSound ), + DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ), + DEFINE_FIELD( m_flTimeEnemyAcquired, FIELD_TIME ), + DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_EMBEDDED( m_GiveUpOnDeadEnemyTimer ), + DEFINE_EMBEDDED( m_FailChooseEnemyTimer ), + DEFINE_FIELD( m_EnemiesSerialNumber, FIELD_INTEGER ), + DEFINE_FIELD( m_flAcceptableTimeSeenEnemy, FIELD_TIME ), + DEFINE_EMBEDDED( m_UpdateEnemyPosTimer ), + // m_flTimeAnyUpdateEnemyPos (static) + DEFINE_FIELD( m_vecCommandGoal, FIELD_VECTOR ), + DEFINE_EMBEDDED( m_CommandMoveMonitor ), + DEFINE_FIELD( m_flSoundWaitTime, FIELD_TIME ), + DEFINE_FIELD( m_nSoundPriority, FIELD_INTEGER ), + DEFINE_FIELD( m_flIgnoreDangerSoundsUntil, FIELD_TIME ), + DEFINE_FIELD( m_afCapability, FIELD_INTEGER ), + DEFINE_FIELD( m_flMoveWaitFinished, FIELD_TIME ), + DEFINE_FIELD( m_hOpeningDoor, FIELD_EHANDLE ), + DEFINE_EMBEDDEDBYREF( m_pNavigator ), + DEFINE_EMBEDDEDBYREF( m_pLocalNavigator ), + DEFINE_EMBEDDEDBYREF( m_pPathfinder ), + DEFINE_EMBEDDEDBYREF( m_pMoveProbe ), + DEFINE_EMBEDDEDBYREF( m_pMotor ), + DEFINE_UTLVECTOR(m_UnreachableEnts, FIELD_EMBEDDED), + DEFINE_FIELD( m_hInteractionPartner, FIELD_EHANDLE ), + DEFINE_FIELD( m_hLastInteractionTestTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_hForcedInteractionPartner, FIELD_EHANDLE ), + DEFINE_FIELD( m_vecForcedWorldPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_bCannotDieDuringInteraction, FIELD_BOOLEAN ), + DEFINE_FIELD( m_iInteractionState, FIELD_INTEGER ), + DEFINE_FIELD( m_iInteractionPlaying, FIELD_INTEGER ), + DEFINE_UTLVECTOR(m_ScriptedInteractions,FIELD_EMBEDDED), + DEFINE_FIELD( m_flInteractionYaw, FIELD_FLOAT ), + DEFINE_EMBEDDED( m_CheckOnGroundTimer ), + DEFINE_FIELD( m_vDefaultEyeOffset, FIELD_VECTOR ), + DEFINE_FIELD( m_flNextEyeLookTime, FIELD_TIME ), + DEFINE_FIELD( m_flEyeIntegRate, FIELD_FLOAT ), + DEFINE_FIELD( m_vEyeLookTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vCurEyeTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_hEyeLookTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_flHeadYaw, FIELD_FLOAT ), + DEFINE_FIELD( m_flHeadPitch, FIELD_FLOAT ), + DEFINE_FIELD( m_flOriginalYaw, FIELD_FLOAT ), + DEFINE_FIELD( m_bInAScript, FIELD_BOOLEAN ), + DEFINE_FIELD( m_scriptState, FIELD_INTEGER ), + DEFINE_FIELD( m_hCine, FIELD_EHANDLE ), + DEFINE_CUSTOM_FIELD( m_ScriptArrivalActivity, ActivityDataOps() ), + DEFINE_FIELD( m_strScriptArrivalSequence, FIELD_STRING ), + DEFINE_FIELD( m_flSceneTime, FIELD_TIME ), + DEFINE_FIELD( m_iszSceneCustomMoveSeq, FIELD_STRING ), + // m_pEnemies Saved specially in ai_saverestore.cpp + DEFINE_FIELD( m_afMemory, FIELD_INTEGER ), + DEFINE_FIELD( m_hEnemyOccluder, FIELD_EHANDLE ), + DEFINE_FIELD( m_flSumDamage, FIELD_FLOAT ), + DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ), + DEFINE_FIELD( m_flLastPlayerDamageTime, FIELD_TIME ), + DEFINE_FIELD( m_flLastSawPlayerTime, FIELD_TIME ), + DEFINE_FIELD( m_flLastAttackTime, FIELD_TIME ), + DEFINE_FIELD( m_flLastEnemyTime, FIELD_TIME ), + DEFINE_FIELD( m_flNextWeaponSearchTime, FIELD_TIME ), + DEFINE_FIELD( m_iszPendingWeapon, FIELD_STRING ), + DEFINE_EMBEDDED( m_ShotRegulator ), + DEFINE_FIELD( m_iDesiredWeaponState, FIELD_INTEGER ), + // m_pSquad Saved specially in ai_saverestore.cpp + DEFINE_KEYFIELD(m_SquadName, FIELD_STRING, "squadname" ), + DEFINE_FIELD( m_iMySquadSlot, FIELD_INTEGER ), + DEFINE_KEYFIELD( m_strHintGroup, FIELD_STRING, "hintgroup" ), + DEFINE_KEYFIELD( m_bHintGroupNavLimiting, FIELD_BOOLEAN, "hintlimiting" ), + DEFINE_EMBEDDEDBYREF( m_pTacticalServices ), + DEFINE_FIELD( m_flWaitFinished, FIELD_TIME ), + DEFINE_FIELD( m_flNextFlinchTime, FIELD_TIME ), + DEFINE_FIELD( m_flNextDodgeTime, FIELD_TIME ), + DEFINE_EMBEDDED( m_MoveAndShootOverlay ), + DEFINE_FIELD( m_vecLastPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vSavePosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vInterruptSavePosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_pHintNode, FIELD_EHANDLE), + DEFINE_FIELD( m_cAmmoLoaded, FIELD_INTEGER ), + DEFINE_FIELD( m_flDistTooFar, FIELD_FLOAT ), + DEFINE_FIELD( m_hGoalEnt, FIELD_EHANDLE ), + DEFINE_FIELD( m_flTimeLastMovement, FIELD_TIME ), + DEFINE_KEYFIELD(m_spawnEquipment, FIELD_STRING, "additionalequipment" ), + DEFINE_FIELD( m_fNoDamageDecal, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hStoredPathTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_vecStoredPathGoal, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_nStoredPathType, FIELD_INTEGER ), + DEFINE_FIELD( m_fStoredPathFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_bDidDeathCleanup, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bCrouchDesired, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bForceCrouch, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bIsCrouching, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bPerformAvoidance, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bIsMoving, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bFadeCorpse, FIELD_BOOLEAN ), + DEFINE_FIELD( m_iDeathPose, FIELD_INTEGER ), + DEFINE_FIELD( m_iDeathFrame, FIELD_INTEGER ), + DEFINE_FIELD( m_bCheckContacts, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bSpeedModActive, FIELD_BOOLEAN ), + DEFINE_FIELD( m_iSpeedModRadius, FIELD_INTEGER ), + DEFINE_FIELD( m_iSpeedModSpeed, FIELD_INTEGER ), + DEFINE_FIELD( m_hEnemyFilter, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iszEnemyFilterName, FIELD_STRING, "enemyfilter" ), + DEFINE_FIELD( m_bImportanRagdoll, FIELD_BOOLEAN ), + + // m_fIsUsingSmallHull TODO -- This needs more consideration than simple save/load + // m_failText DEBUG + // m_interruptText DEBUG + // m_failedSchedule DEBUG + // m_interuptSchedule DEBUG + // m_nDebugCurIndex DEBUG + + // m_LastShootAccuracy DEBUG + // m_RecentShotAccuracy DEBUG + // m_TotalShots DEBUG + // m_TotalHits DEBUG + // m_bSelected DEBUG + // m_TimeLastShotMark DEBUG + + + // Outputs + DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ), + DEFINE_OUTPUT( m_OnDeath, "OnDeath" ), + DEFINE_OUTPUT( m_OnHalfHealth, "OnHalfHealth" ), + DEFINE_OUTPUT( m_OnFoundEnemy, "OnFoundEnemy" ), + DEFINE_OUTPUT( m_OnLostEnemyLOS, "OnLostEnemyLOS" ), + DEFINE_OUTPUT( m_OnLostEnemy, "OnLostEnemy" ), + DEFINE_OUTPUT( m_OnFoundPlayer, "OnFoundPlayer" ), + DEFINE_OUTPUT( m_OnLostPlayerLOS, "OnLostPlayerLOS" ), + DEFINE_OUTPUT( m_OnLostPlayer, "OnLostPlayer" ), + DEFINE_OUTPUT( m_OnHearWorld, "OnHearWorld" ), + DEFINE_OUTPUT( m_OnHearPlayer, "OnHearPlayer" ), + DEFINE_OUTPUT( m_OnHearCombat, "OnHearCombat" ), + DEFINE_OUTPUT( m_OnDamagedByPlayer, "OnDamagedByPlayer" ), + DEFINE_OUTPUT( m_OnDamagedByPlayerSquad, "OnDamagedByPlayerSquad" ), + DEFINE_OUTPUT( m_OnDenyCommanderUse, "OnDenyCommanderUse" ), + DEFINE_OUTPUT( m_OnRappelTouchdown, "OnRappelTouchdown" ), + DEFINE_OUTPUT( m_OnWake, "OnWake" ), + DEFINE_OUTPUT( m_OnSleep, "OnSleep" ), + DEFINE_OUTPUT( m_OnForcedInteractionAborted, "OnForcedInteractionAborted" ), + DEFINE_OUTPUT( m_OnForcedInteractionFinished, "OnForcedInteractionFinished" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_STRING, "SetRelationship", InputSetRelationship ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), + DEFINE_INPUTFUNC( FIELD_VOID, "BeginRappel", InputBeginRappel ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetSquad", InputSetSquad ), + DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ), + DEFINE_INPUTFUNC( FIELD_STRING, "ForgetEntity", InputForgetEntity ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreDangerSounds", InputIgnoreDangerSounds ), + DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartScripting", InputStartScripting ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopScripting", InputStopScripting ), + DEFINE_INPUTFUNC( FIELD_VOID, "GagEnable", InputGagEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "GagDisable", InputGagDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "InsideTransition", InputInsideTransition ), + DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ), + DEFINE_INPUTFUNC( FIELD_VOID, "ActivateSpeedModifier", InputActivateSpeedModifier ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpeedModifier", InputDisableSpeedModifier ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModRadius", InputSetSpeedModifierRadius ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModSpeed", InputSetSpeedModifierSpeed ), + DEFINE_INPUTFUNC( FIELD_VOID, "HolsterWeapon", InputHolsterWeapon ), + DEFINE_INPUTFUNC( FIELD_VOID, "HolsterAndDestroyWeapon", InputHolsterAndDestroyWeapon ), + DEFINE_INPUTFUNC( FIELD_VOID, "UnholsterWeapon", InputUnholsterWeapon ), + DEFINE_INPUTFUNC( FIELD_STRING, "ForceInteractionWithNPC", InputForceInteractionWithNPC ), + DEFINE_INPUTFUNC( FIELD_STRING, "UpdateEnemyMemory", InputUpdateEnemyMemory ), + + // Function pointers + DEFINE_USEFUNC( NPCUse ), + DEFINE_THINKFUNC( CallNPCThink ), + DEFINE_THINKFUNC( CorpseFallThink ), + DEFINE_THINKFUNC( NPCInitThink ), + +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( AIScheduleState_t ) + DEFINE_FIELD( iCurTask, FIELD_INTEGER ), + DEFINE_FIELD( fTaskStatus, FIELD_INTEGER ), + DEFINE_FIELD( timeStarted, FIELD_TIME ), + DEFINE_FIELD( timeCurTaskStarted, FIELD_TIME ), + DEFINE_FIELD( taskFailureCode, FIELD_INTEGER ), + DEFINE_FIELD( iTaskInterrupt, FIELD_INTEGER ), + DEFINE_FIELD( bTaskRanAutomovement, FIELD_BOOLEAN ), + DEFINE_FIELD( bTaskUpdatedYaw, FIELD_BOOLEAN ), +END_DATADESC() + + +IMPLEMENT_SERVERCLASS_ST( CAI_BaseNPC, DT_AI_BaseNPC ) + SendPropInt( SENDINFO( m_lifeState ), 3, SPROP_UNSIGNED ), + SendPropBool( SENDINFO( m_bPerformAvoidance ) ), + SendPropBool( SENDINFO( m_bIsMoving ) ), + SendPropBool( SENDINFO( m_bFadeCorpse ) ), + SendPropInt( SENDINFO( m_iDeathPose ), ANIMATION_SEQUENCE_BITS ), + SendPropInt( SENDINFO( m_iDeathFrame ), 5 ), + SendPropBool( SENDINFO( m_bSpeedModActive ) ), + SendPropInt( SENDINFO( m_iSpeedModRadius ) ), + SendPropInt( SENDINFO( m_iSpeedModSpeed ) ), + SendPropBool( SENDINFO( m_bImportanRagdoll ) ), +END_SEND_TABLE() + +//------------------------------------- + +BEGIN_SIMPLE_DATADESC( UnreachableEnt_t ) + + DEFINE_FIELD( hUnreachableEnt, FIELD_EHANDLE ), + DEFINE_FIELD( fExpireTime, FIELD_TIME ), + DEFINE_FIELD( vLocationWhenUnreachable, FIELD_POSITION_VECTOR ), + +END_DATADESC() + +//------------------------------------- + +BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_Phases_t ) +DEFINE_FIELD( iszSequence, FIELD_STRING ), +DEFINE_FIELD( iActivity, FIELD_INTEGER ), +END_DATADESC() + +//------------------------------------- + +BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t ) + DEFINE_FIELD( iszInteractionName, FIELD_STRING ), + DEFINE_FIELD( iFlags, FIELD_INTEGER ), + DEFINE_FIELD( iTriggerMethod, FIELD_INTEGER ), + DEFINE_FIELD( iLoopBreakTriggerMethod, FIELD_INTEGER ), + DEFINE_FIELD( vecRelativeOrigin, FIELD_VECTOR ), + DEFINE_FIELD( angRelativeAngles, FIELD_VECTOR ), + DEFINE_FIELD( vecRelativeVelocity, FIELD_VECTOR ), + DEFINE_FIELD( flDelay, FIELD_FLOAT ), + DEFINE_FIELD( flDistSqr, FIELD_FLOAT ), + DEFINE_FIELD( iszMyWeapon, FIELD_STRING ), + DEFINE_FIELD( iszTheirWeapon, FIELD_STRING ), + DEFINE_EMBEDDED_ARRAY( sPhases, SNPCINT_NUM_PHASES ), + DEFINE_FIELD( matDesiredLocalToWorld, FIELD_VMATRIX ), + DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ), + DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ), +END_DATADESC() + +//------------------------------------- + +void CAI_BaseNPC::PostConstructor( const char *szClassname ) +{ + BaseClass::PostConstructor( szClassname ); + CreateComponents(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::Activate( void ) +{ + BaseClass::Activate(); + + if ( GetModelPtr() ) + { + ParseScriptedNPCInteractions(); + } + + // Get a handle to my enemy filter entity if there is one. + if ( m_iszEnemyFilterName != NULL_STRING ) + { + CBaseEntity *pFilter = gEntList.FindEntityByName( NULL, m_iszEnemyFilterName ); + if ( pFilter != NULL ) + { + m_hEnemyFilter = dynamic_cast(pFilter); + } + } +} + +void CAI_BaseNPC::Precache( void ) +{ + gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls + + if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0") ) + { + UTIL_PrecacheOther( STRING(m_spawnEquipment) ); + } + + // Make sure schedules are loaded for this NPC type + if (!LoadedSchedules()) + { + DevMsg("ERROR: Rejecting spawn of %s as error in NPC's schedules.\n",GetDebugName()); + UTIL_Remove(this); + return; + } + + PrecacheScriptSound( "AI_BaseNPC.SwishSound" ); + PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Heavy" ); + PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Light" ); + PrecacheScriptSound( "AI_BaseNPC.SentenceStop" ); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- + +const short AI_EXTENDED_SAVE_HEADER_VERSION = 5; +const short AI_EXTENDED_SAVE_HEADER_RESET_VERSION = 3; + +const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS = 2; +const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP = 3; +const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE = 4; +const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE = 5; + +struct AIExtendedSaveHeader_t +{ + AIExtendedSaveHeader_t() + : version(AI_EXTENDED_SAVE_HEADER_VERSION), + flags(0), + scheduleCrc(0) + { + szSchedule[0] = 0; + szIdealSchedule[0] = 0; + szFailSchedule[0] = 0; + szSequence[0] = 0; + } + + short version; + unsigned flags; + char szSchedule[128]; + CRC32_t scheduleCrc; + char szIdealSchedule[128]; + char szFailSchedule[128]; + char szSequence[128]; + + DECLARE_SIMPLE_DATADESC(); +}; + +enum AIExtendedSaveHeaderFlags_t +{ + AIESH_HAD_ENEMY = 0x01, + AIESH_HAD_TARGET = 0x02, + AIESH_HAD_NAVGOAL = 0x04, +}; + +//------------------------------------- + +BEGIN_SIMPLE_DATADESC( AIExtendedSaveHeader_t ) + DEFINE_FIELD( version, FIELD_SHORT ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( szSchedule, FIELD_CHARACTER ), + DEFINE_FIELD( scheduleCrc, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( szIdealSchedule, FIELD_CHARACTER ), + DEFINE_AUTO_ARRAY( szFailSchedule, FIELD_CHARACTER ), + DEFINE_AUTO_ARRAY( szSequence, FIELD_CHARACTER ), +END_DATADESC() + +//------------------------------------- + +int CAI_BaseNPC::Save( ISave &save ) +{ + AIExtendedSaveHeader_t saveHeader; + + if ( GetEnemy() ) + saveHeader.flags |= AIESH_HAD_ENEMY; + if ( GetTarget() ) + saveHeader.flags |= AIESH_HAD_TARGET; + if ( GetNavigator()->IsGoalActive() ) + saveHeader.flags |= AIESH_HAD_NAVGOAL; + + if ( m_pSchedule ) + { + const char *pszSchedule = m_pSchedule->GetName(); + + Assert( static_cast(Q_strlen( pszSchedule )) < sizeof( saveHeader.szSchedule ) - 1 ); + Q_strncpy( saveHeader.szSchedule, pszSchedule, sizeof( saveHeader.szSchedule ) ); + + CRC32_Init( &saveHeader.scheduleCrc ); + CRC32_ProcessBuffer( &saveHeader.scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) ); + CRC32_Final( &saveHeader.scheduleCrc ); + } + else + { + saveHeader.szSchedule[0] = 0; + saveHeader.scheduleCrc = 0; + } + + int idealSchedule = GetGlobalScheduleId( m_IdealSchedule ); + + if ( idealSchedule != -1 && idealSchedule != AI_RemapToGlobal( SCHED_NONE ) && idealSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) ) + { + CAI_Schedule *pIdealSchedule = GetSchedule( m_IdealSchedule ); + if ( pIdealSchedule ) + { + const char *pszIdealSchedule = pIdealSchedule->GetName(); + Assert( static_cast(Q_strlen( pszIdealSchedule )) < sizeof( saveHeader.szIdealSchedule ) - 1 ); + Q_strncpy( saveHeader.szIdealSchedule, pszIdealSchedule, sizeof( saveHeader.szIdealSchedule ) ); + } + } + + int failSchedule = GetGlobalScheduleId( m_failSchedule ); + if ( failSchedule != -1 && failSchedule != AI_RemapToGlobal( SCHED_NONE ) && failSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) ) + { + CAI_Schedule *pFailSchedule = GetSchedule( m_failSchedule ); + if ( pFailSchedule ) + { + const char *pszFailSchedule = pFailSchedule->GetName(); + Assert( static_cast(Q_strlen( pszFailSchedule )) < sizeof( saveHeader.szFailSchedule ) - 1 ); + Q_strncpy( saveHeader.szFailSchedule, pszFailSchedule, sizeof( saveHeader.szFailSchedule ) ); + } + } + + if ( GetSequence() != ACT_INVALID && GetModelPtr() ) + { + const char *pszSequenceName = GetSequenceName( GetSequence() ); + if ( pszSequenceName && *pszSequenceName ) + { + Assert( static_cast(Q_strlen( pszSequenceName )) < sizeof( saveHeader.szSequence ) - 1 ); + Q_strncpy( saveHeader.szSequence, pszSequenceName, sizeof(saveHeader.szSequence) ); + } + } + + save.WriteAll( &saveHeader ); + + save.StartBlock(); + SaveConditions( save, m_Conditions ); + SaveConditions( save, m_CustomInterruptConditions ); + SaveConditions( save, m_ConditionsPreIgnore ); + CAI_ScheduleBits ignoreConditions; + m_InverseIgnoreConditions.Not( &ignoreConditions ); + SaveConditions( save, ignoreConditions ); + save.EndBlock(); + + save.StartBlock(); + GetNavigator()->Save( save ); + save.EndBlock(); + + return BaseClass::Save(save); +} + +//------------------------------------- + +void CAI_BaseNPC::DiscardScheduleState() +{ + // We don't save/restore routes yet + GetNavigator()->ClearGoal(); + + // We don't save/restore schedules yet + ClearSchedule(); + + // Reset animation + m_Activity = ACT_RESET; + + // If we don't have an enemy, clear conditions like see enemy, etc. + if ( GetEnemy() == NULL ) + { + m_Conditions.ClearAllBits(); + } + + // went across a transition and lost my m_hCine + bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL ); + if ( bLostScript ) + { + // UNDONE: Do something better here? + // for now, just go back to idle and let the AI figure out what to do. + SetState( NPC_STATE_IDLE ); + SetIdealState( NPC_STATE_IDLE ); + DevMsg(1, "Scripted Sequence stripped on level transition for %s\n", GetDebugName() ); + } +} + +//------------------------------------- + +void CAI_BaseNPC::OnRestore() +{ + gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls + + if ( m_bDoPostRestoreRefindPath && CAI_NetworkManager::NetworksLoaded() ) + { + CAI_DynamicLink::InitDynamicLinks(); + if ( !GetNavigator()->RefindPathToGoal( false ) ) + DiscardScheduleState(); + } + else + { + GetNavigator()->ClearGoal(); + } + BaseClass::OnRestore(); + m_bCheckContacts = true; +} + + +//------------------------------------- + +int CAI_BaseNPC::Restore( IRestore &restore ) +{ + AIExtendedSaveHeader_t saveHeader; + restore.ReadAll( &saveHeader ); + + if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP ) + { + if ( saveHeader.szIdealSchedule[0] ) + { + CAI_Schedule *pIdealSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szIdealSchedule ); + m_IdealSchedule = ( pIdealSchedule ) ? pIdealSchedule->GetId() : SCHED_NONE; + } + + if ( saveHeader.szFailSchedule[0] ) + { + CAI_Schedule *pFailSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szFailSchedule ); + m_failSchedule = ( pFailSchedule ) ? pFailSchedule->GetId() : SCHED_NONE; + } + } + + if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS ) + { + restore.StartBlock(); + RestoreConditions( restore, &m_Conditions ); + RestoreConditions( restore, &m_CustomInterruptConditions ); + RestoreConditions( restore, &m_ConditionsPreIgnore ); + CAI_ScheduleBits ignoreConditions; + RestoreConditions( restore, &ignoreConditions ); + ignoreConditions.Not( &m_InverseIgnoreConditions ); + restore.EndBlock(); + } + + if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE ) + { + restore.StartBlock(); + GetNavigator()->Restore( restore ); + restore.EndBlock(); + } + + // do a normal restore + int status = BaseClass::Restore(restore); + if ( !status ) + return 0; + + bool bLostSequence = false; + if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE && saveHeader.szSequence[0] && GetModelPtr() ) + { + SetSequence( LookupSequence( saveHeader.szSequence ) ); + if ( GetSequence() == ACT_INVALID ) + { + DevMsg( this, AIMF_IGNORE_SELECTED, "Discarding missing sequence %s on load.\n", saveHeader.szSequence ); + SetSequence( 0 ); + bLostSequence = true; + } + + Assert( IsValidSequence( GetSequence() ) ); + } + + bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL ); + bool bDiscardScheduleState = ( bLostScript || + bLostSequence || + saveHeader.szSchedule[0] == 0 || + saveHeader.version < AI_EXTENDED_SAVE_HEADER_RESET_VERSION || + ( (saveHeader.flags & AIESH_HAD_ENEMY) && !GetEnemy() ) || + ( (saveHeader.flags & AIESH_HAD_TARGET) && !GetTarget() ) ); + + if ( m_ScheduleState.taskFailureCode >= NUM_FAIL_CODES ) + m_ScheduleState.taskFailureCode = FAIL_NO_TARGET; // must have been a string, gotta punt + + if ( !bDiscardScheduleState ) + { + m_pSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szSchedule ); + if ( m_pSchedule ) + { + CRC32_t scheduleCrc; + CRC32_Init( &scheduleCrc ); + CRC32_ProcessBuffer( &scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) ); + CRC32_Final( &scheduleCrc ); + + if ( scheduleCrc != saveHeader.scheduleCrc ) + { + m_pSchedule = NULL; + } + } + } + + if ( !m_pSchedule ) + bDiscardScheduleState = true; + + if ( !bDiscardScheduleState ) + m_bDoPostRestoreRefindPath = ( ( saveHeader.flags & AIESH_HAD_NAVGOAL) != 0 ); + else + { + m_bDoPostRestoreRefindPath = false; + DiscardScheduleState(); + } + + return status; +} + +//------------------------------------- + +void CAI_BaseNPC::SaveConditions( ISave &save, const CAI_ScheduleBits &conditions ) +{ + for (int i = 0; i < MAX_CONDITIONS; i++) + { + if (conditions.GetBit(i)) + { + const char *pszConditionName = ConditionName(AI_RemapToGlobal(i)); + if ( !pszConditionName ) + break; + save.WriteString( pszConditionName ); + } + } + save.WriteString( "" ); +} + +//------------------------------------- + +void CAI_BaseNPC::RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions ) +{ + pConditions->ClearAllBits(); + char szCondition[256]; + for (;;) + { + restore.ReadString( szCondition, sizeof(szCondition), 0 ); + if ( !szCondition[0] ) + break; + int iCondition = GetSchedulingSymbols()->ConditionSymbolToId( szCondition ); + if ( iCondition != -1 ) + pConditions->SetBit( AI_RemapFromGlobal( iCondition ) ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::KeyValue( const char *szKeyName, const char *szValue ) +{ + bool bResult = BaseClass::KeyValue( szKeyName, szValue ); + + if( !bResult ) + { + // Defer unhandled Keys to behaviors + CAI_BehaviorBase **ppBehaviors = AccessBehaviors(); + + for ( int i = 0; i < NumBehaviors(); i++ ) + { + if( ppBehaviors[ i ]->KeyValue( szKeyName, szValue ) ) + { + return true; + } + } + } + + return bResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Debug function to make this NPC freeze in place (or unfreeze). +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ToggleFreeze(void) +{ + if (!IsCurSchedule(SCHED_NPC_FREEZE)) + { + // Freeze them. + SetCondition(COND_NPC_FREEZE); + SetMoveType(MOVETYPE_NONE); + SetGravity(0); + SetLocalAngularVelocity(vec3_angle); + SetAbsVelocity( vec3_origin ); + } + else + { + // Unfreeze them. + SetCondition(COND_NPC_UNFREEZE); + m_Activity = ACT_RESET; + + // BUGBUG: this might not be the correct movetype! + SetMoveType( MOVETYPE_STEP ); + + // Doesn't restore gravity to the original value, but who cares? + SetGravity(1); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Written by subclasses macro to load schedules +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::LoadSchedules(void) +{ + return true; +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::LoadedSchedules(void) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : +// Output : +//----------------------------------------------------------------------------- +CAI_BaseNPC::CAI_BaseNPC(void) + : m_UnreachableEnts( 0, 4 ) +{ + m_pMotor = NULL; + m_pMoveProbe = NULL; + m_pNavigator = NULL; + m_pSenses = NULL; + m_pPathfinder = NULL; + m_pLocalNavigator = NULL; + + m_pSchedule = NULL; + m_IdealSchedule = SCHED_NONE; + +#ifdef _DEBUG + // necessary since in debug, we initialize vectors to NAN for debugging + m_vecLastPosition.Init(); + m_vSavePosition.Init(); + m_vEyeLookTarget.Init(); + m_vCurEyeTarget.Init(); + m_vDefaultEyeOffset.Init(); + +#endif + m_bDidDeathCleanup = false; + + m_afCapability = 0; // Make sure this is cleared in the base class + + SetHullType(HULL_HUMAN); // Give human hull by default, subclasses should override + + m_iMySquadSlot = SQUAD_SLOT_NONE; + m_flSumDamage = 0; + m_flLastDamageTime = 0; + m_flLastAttackTime = 0; + m_flSoundWaitTime = 0; + m_flNextEyeLookTime = 0; + m_flHeadYaw = 0; + m_flHeadPitch = 0; + m_spawnEquipment = NULL_STRING; + m_pEnemies = new CAI_Enemies; + m_flEyeIntegRate = 0.95; + SetTarget( NULL ); + + m_pSquad = NULL; + + m_flMoveWaitFinished = 0; + + m_fIsUsingSmallHull = true; + + m_bHintGroupNavLimiting = false; + + m_fNoDamageDecal = false; + + SetInAScript( false ); + + m_pLockedBestSound = new CSound; + m_pLockedBestSound->m_iType = SOUND_NONE; + + // ---------------------------- + // Debugging fields + // ---------------------------- + m_interruptText = NULL; + m_failText = NULL; + m_failedSchedule = NULL; + m_interuptSchedule = NULL; + m_nDebugPauseIndex = 0; + + g_AI_Manager.AddAI( this ); + + if ( g_AI_Manager.NumAIs() == 1 ) + { + m_AnyUpdateEnemyPosTimer.Force(); + gm_flTimeLastSpawn = -1; + gm_nSpawnedThisFrame = 0; + gm_iNextThinkRebalanceTick = 0; + } + + m_iFrameBlocked = -1; + m_bInChoreo = true; // assume so until call to UpdateEfficiency() + + SetCollisionGroup( COLLISION_GROUP_NPC ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +// Input : +// Output : +//----------------------------------------------------------------------------- +CAI_BaseNPC::~CAI_BaseNPC(void) +{ + g_AI_Manager.RemoveAI( this ); + + delete m_pLockedBestSound; + + RemoveMemory(); + + delete m_pPathfinder; + delete m_pNavigator; + delete m_pMotor; + delete m_pLocalNavigator; + delete m_pMoveProbe; + delete m_pSenses; + delete m_pTacticalServices; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::UpdateOnRemove(void) +{ + if ( !m_bDidDeathCleanup ) + { + if ( m_NPCState == NPC_STATE_DEAD ) + DevMsg( "May not have cleaned up on NPC death\n"); + + CleanupOnDeath( NULL, false ); + } + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::CreateComponents() +{ + m_pSenses = CreateSenses(); + if ( !m_pSenses ) + return false; + + m_pMotor = CreateMotor(); + if ( !m_pMotor ) + return false; + + m_pLocalNavigator = CreateLocalNavigator(); + if ( !m_pLocalNavigator ) + return false; + + m_pMoveProbe = CreateMoveProbe(); + if ( !m_pMoveProbe ) + return false; + + m_pNavigator = CreateNavigator(); + if ( !m_pNavigator ) + return false; + + m_pPathfinder = CreatePathfinder(); + if ( !m_pPathfinder ) + return false; + + m_pTacticalServices = CreateTacticalServices(); + if ( !m_pTacticalServices ) + return false; + + m_MoveAndShootOverlay.SetOuter( this ); + + m_pMotor->Init( m_pLocalNavigator ); + m_pLocalNavigator->Init( m_pNavigator ); + m_pNavigator->Init( g_pBigAINet ); + m_pPathfinder->Init( g_pBigAINet ); + m_pTacticalServices->Init( g_pBigAINet ); + + return true; +} + +//----------------------------------------------------------------------------- + +CAI_Senses *CAI_BaseNPC::CreateSenses() +{ + CAI_Senses *pSenses = new CAI_Senses; + pSenses->SetOuter( this ); + return pSenses; +} + +//----------------------------------------------------------------------------- + +CAI_Motor *CAI_BaseNPC::CreateMotor() +{ + return new CAI_Motor( this ); +} + +//----------------------------------------------------------------------------- + +CAI_MoveProbe *CAI_BaseNPC::CreateMoveProbe() +{ + return new CAI_MoveProbe( this ); +} + +//----------------------------------------------------------------------------- + +CAI_LocalNavigator *CAI_BaseNPC::CreateLocalNavigator() +{ + return new CAI_LocalNavigator( this ); +} + +//----------------------------------------------------------------------------- + +CAI_TacticalServices *CAI_BaseNPC::CreateTacticalServices() +{ + return new CAI_TacticalServices( this ); +} + +//----------------------------------------------------------------------------- + +CAI_Navigator *CAI_BaseNPC::CreateNavigator() +{ + return new CAI_Navigator( this ); +} + +//----------------------------------------------------------------------------- + +CAI_Pathfinder *CAI_BaseNPC::CreatePathfinder() +{ + return new CAI_Pathfinder( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputSetRelationship( inputdata_t &inputdata ) +{ + AddRelationship( inputdata.value.String(), inputdata.pActivator ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputSetHealth( inputdata_t &inputdata ) +{ + int iNewHealth = inputdata.value.Int(); + int iDelta = abs(GetHealth() - iNewHealth); + if ( iNewHealth > GetHealth() ) + { + TakeHealth( iDelta, DMG_GENERIC ); + } + else if ( iNewHealth < GetHealth() ) + { + TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputBeginRappel( inputdata_t &inputdata ) +{ + BeginRappel(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputSetSquad( inputdata_t &inputdata ) +{ + if ( !( CapabilitiesGet() & bits_CAP_SQUAD ) ) + { + Warning("SetSquad Input received for NPC %s, but that NPC can't use squads.\n", GetDebugName() ); + return; + } + + m_SquadName = inputdata.value.StringID(); + + // Removing from squad? + if ( m_SquadName == NULL_STRING ) + { + if ( m_pSquad ) + { + m_pSquad->RemoveFromSquad(this, true); + m_pSquad = NULL; + } + } + else + { + m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputWake( inputdata_t &inputdata ) +{ + Wake(); + + // Check if we have a path to follow. This is normally done in StartNPC, + // but putting the NPC to sleep will cancel it, so we have to do it again. + if ( m_target != NULL_STRING )// this npc has a target + { + // Find the npc's initial target entity, stash it + SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) ); + + if ( !GetGoalEnt() ) + { + Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target)); + } + else + { + StartTargetHandling( GetGoalEnt() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputForgetEntity( inputdata_t &inputdata ) +{ + const char *pszEntityToForget = inputdata.value.String(); + + if ( g_pDeveloper->GetInt() && pszEntityToForget[strlen( pszEntityToForget ) - 1] == '*' ) + DevMsg( "InputForgetEntity does not support wildcards\n" ); + + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, pszEntityToForget ); + if ( pEntity ) + { + if ( GetEnemy() == pEntity ) + { + SetEnemy( NULL ); + SetIdealState( NPC_STATE_ALERT ); + } + GetEnemies()->ClearMemory( pEntity ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputIgnoreDangerSounds( inputdata_t &inputdata ) +{ + // Default is 10 seconds. + float flDelay = 10.0f; + + if( inputdata.value.Float() > 0.0f ) + { + flDelay = inputdata.value.Float(); + } + + m_flIgnoreDangerSoundsUntil = gpGlobals->curtime + flDelay; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputUpdateEnemyMemory( inputdata_t &inputdata ) +{ + const char *pszEnemy = inputdata.value.String(); + CBaseEntity *pEnemy = gEntList.FindEntityByName( NULL, pszEnemy ); + + if( pEnemy ) + { + UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputOutsideTransition( inputdata_t &inputdata ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Called when this NPC transitions to another level with the player +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputInsideTransition( inputdata_t &inputdata ) +{ + CleanupScriptsOnTeleport( true ); + + // If we're inside a vcd, tell it to stop + if ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) ) + { + RemoveActorFromScriptedScenes( this, false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CleanupScriptsOnTeleport( bool bEnrouteAsWell ) +{ + // If I'm running a scripted sequence, I need to clean up + if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine ) + { + if ( !bEnrouteAsWell ) + { + // Don't cancel scripts when they're teleporting an NPC + // to the script due to CINE_MOVETO_TELEPORT. + if ( m_hCine->IsTeleportingDueToMoveTo() ) + return; + } + + m_hCine->ScriptEntityCancel( m_hCine, true ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) +{ +#ifdef HL2_DLL + if ( interactionType == g_interactionBarnacleVictimGrab ) + { + // Make the victim stop thinking so they're as good as dead without + // shocking the system by destroying the entity. + StopLoopingSounds(); + BarnacleDeathSound(); + SetThink( NULL ); + + // Gag the NPC so they won't talk anymore + AddSpawnFlags( SF_NPC_GAG ); + + // Drop any weapon they're holding + if ( GetActiveWeapon() ) + { + Weapon_Drop( GetActiveWeapon() ); + } + + return true; + } +#endif // HL2_DLL + + return BaseClass::HandleInteraction( interactionType, data, sourceEnt ); +} + +CAI_BaseNPC *CAI_BaseNPC::GetInteractionPartner( void ) +{ + if ( m_hInteractionPartner == NULL ) + return NULL; + + return m_hInteractionPartner->MyNPCPointer(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when exiting a scripted sequence. +// Output : Returns true if alive, false if dead. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::ExitScriptedSequence( ) +{ + if ( m_lifeState == LIFE_DYING ) + { + // is this legal? + // BUGBUG -- This doesn't call Killed() + SetIdealState( NPC_STATE_DEAD ); + return false; + } + + if (m_hCine) + { + m_hCine->CancelScript( ); + } + + return true; +} + + +bool CAI_BaseNPC::CineCleanup() +{ + CAI_ScriptedSequence *pOldCine = m_hCine; + + bool bDestroyCine = false; + if ( IsRunningDynamicInteraction() ) + { + bDestroyCine = true; + + // Re-enable physics collisions between me & the other NPC + if ( m_hInteractionPartner ) + { + PhysEnableEntityCollisions( this, m_hInteractionPartner ); + //Msg("%s(%s) enabled collisions with %s(%s) at %0.2f\n", GetClassName(), GetDebugName(), m_hInteractionPartner->GetClassName(), m_hInteractionPartner->GetDebugName(), gpGlobals->curtime ); + } + + if ( m_hForcedInteractionPartner ) + { + // We've finished a forced interaction. Let the mapmaker know. + m_OnForcedInteractionFinished.FireOutput( this, this ); + } + + // Clear interaction partner, because we're not running a scripted sequence anymore + m_hInteractionPartner = NULL; + CleanupForcedInteraction(); + } + + // am I linked to a cinematic? + if (m_hCine) + { + // okay, reset me to what it thought I was before + m_hCine->SetTarget( NULL ); + // NOTE that this will have had EF_NODRAW removed in script.dll when it's cached off + SetEffects( m_hCine->m_saved_effects ); + } + else + { + // arg, punt + AddSolidFlags( FSOLID_NOT_STANDABLE ); + } + m_hCine = NULL; + SetTarget( NULL ); + SetGoalEnt( NULL ); + if (m_lifeState == LIFE_DYING) + { + // last frame of death animation? + m_iHealth = 0; + AddSolidFlags( FSOLID_NOT_SOLID ); + SetState( NPC_STATE_DEAD ); + m_lifeState = LIFE_DEAD; + UTIL_SetSize( this, WorldAlignMins(), Vector(WorldAlignMaxs().x, WorldAlignMaxs().y, WorldAlignMins().z + 2) ); + + if ( pOldCine && pOldCine->HasSpawnFlags( SF_SCRIPT_LEAVECORPSE ) ) + { + SetUse( NULL ); // BUGBUG -- This doesn't call Killed() + SetThink( NULL ); // This will probably break some stuff + SetTouch( NULL ); + } + else + SUB_StartFadeOut(); // SetThink( SUB_DoNothing ); + + + //Not becoming a ragdoll, so set the NOINTERP flag on. + if ( CanBecomeRagdoll() == false ) + { + StopAnimation(); + AddEffects( EF_NOINTERP ); // Don't interpolate either, assume the corpse is positioned in its final resting place + } + + SetMoveType( MOVETYPE_NONE ); + return false; + } + + // If we actually played a sequence + if ( pOldCine && pOldCine->m_iszPlay != NULL_STRING && pOldCine->PlayedSequence() ) + { + if ( !pOldCine->HasSpawnFlags(SF_SCRIPT_NOSCRIPTMOVEMENT) ) + { + // reset position + Vector new_origin; + QAngle new_angle; + GetBonePosition( 0, new_origin, new_angle ); + + // Figure out how far they have moved + // We can't really solve this problem because we can't query the movement of the origin relative + // to the sequence. We can get the root bone's position as we do here, but there are + // cases where the root bone is in a different relative position to the entity's origin + // before/after the sequence plays. So we are stuck doing this: + + // !!!HACKHACK: Float the origin up and drop to floor because some sequences have + // irregular motion that can't be properly accounted for. + + // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. + Vector oldOrigin = GetLocalOrigin(); + + // UNDONE: ugly hack. Don't move NPC if they don't "seem" to move + // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly + // being set, so animations that really do move won't be caught. + if ((oldOrigin - new_origin).Length2D() < 8.0) + new_origin = oldOrigin; + + Vector origin = GetLocalOrigin(); + + origin.x = new_origin.x; + origin.y = new_origin.y; + origin.z += 1; + + if ( GetFlags() & FL_FLY ) + { + origin.z = new_origin.z; + SetLocalOrigin( origin ); + } + else + { + SetLocalOrigin( origin ); + + int drop = UTIL_DropToFloor( this, MASK_NPCSOLID ); + + // Origin in solid? Set to org at the end of the sequence + if ( drop < 0 ) + { + SetLocalOrigin( oldOrigin ); + } + else if ( drop == 0 ) // Hanging in air? + { + Vector origin = GetLocalOrigin(); + origin.z = new_origin.z; + SetLocalOrigin( origin ); + SetGroundEntity( NULL ); + } + } + + origin = GetLocalOrigin(); + + // teleport if it's a non-trivial distance + if ((oldOrigin - origin).Length() > 8.0) + { + // Call teleport to notify + Teleport( &origin, NULL, NULL ); + SetLocalOrigin( origin ); + AddEffects( EF_NOINTERP ); + } + + if ( m_iHealth <= 0 ) + { + // Dropping out because he got killed + SetIdealState( NPC_STATE_DEAD ); + SetCondition( COND_LIGHT_DAMAGE ); + m_lifeState = LIFE_DYING; + } + } + + // We should have some animation to put these guys in, but for now it's idle. + // Due to NOINTERP above, there won't be any blending between this anim & the sequence + m_Activity = ACT_RESET; + } + + // set them back into a normal state + if ( m_iHealth > 0 ) + { + SetIdealState( NPC_STATE_IDLE ); + } + else + { + // Dropping out because he got killed + SetIdealState( NPC_STATE_DEAD ); + SetCondition( COND_LIGHT_DAMAGE ); + } + + // SetAnimation( m_NPCState ); + CLEARBITS(m_spawnflags, SF_NPC_WAIT_FOR_SCRIPT ); + + if ( bDestroyCine ) + { + UTIL_Remove( pOldCine ); + } + + return true; +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ) +{ + CleanupScriptsOnTeleport( false ); + + BaseClass::Teleport( newPosition, newAngles, newVelocity ); +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::FindSpotForNPCInRadius( Vector *pResult, const Vector &vStartPos, CAI_BaseNPC *pNPC, float radius, bool bOutOfPlayerViewcone ) +{ + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + QAngle fan; + + fan.x = 0; + fan.z = 0; + + for( fan.y = 0 ; fan.y < 360 ; fan.y += 18.0 ) + { + Vector vecTest; + Vector vecDir; + + AngleVectors( fan, &vecDir ); + + vecTest = vStartPos + vecDir * radius; + + if ( bOutOfPlayerViewcone && pPlayer && !pPlayer->FInViewCone( vecTest ) ) + continue; + + trace_t tr; + + UTIL_TraceLine( vecTest, vecTest - Vector( 0, 0, 8192 ), MASK_SHOT, pNPC, COLLISION_GROUP_NONE, &tr ); + if( tr.fraction == 1.0 ) + { + continue; + } + + UTIL_TraceHull( tr.endpos, + tr.endpos + Vector( 0, 0, 10 ), + pNPC->GetHullMins(), + pNPC->GetHullMaxs(), + MASK_NPCSOLID, + pNPC, + COLLISION_GROUP_NONE, + &tr ); + + if( tr.fraction == 1.0 && pNPC->GetMoveProbe()->CheckStandPosition( tr.endpos, MASK_NPCSOLID ) ) + { + *pResult = tr.endpos; + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::IsNavigationUrgent() +{ + // return true if the navigation is for something that can't react well to failure + if ( IsCurSchedule( SCHED_SCRIPTED_WALK, false ) || + IsCurSchedule( SCHED_SCRIPTED_RUN, false ) || + IsCurSchedule( SCHED_SCRIPTED_CUSTOM_MOVE, false ) || + ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) && IsInLockedScene() ) ) + { + return true; + } + return false; +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::ShouldFailNav( bool bMovementFailed ) +{ + // Robin: Only fail movement. Fail route building so that we'll keep trying to build routes. + // Otherwise, we can can get stuck inside a movement task without a route. + if ( bMovementFailed && IsNavigationUrgent()) + { + return false; + } + return true; +} + +Navigation_t CAI_BaseNPC::GetNavType() const +{ + return m_pNavigator->GetNavType(); +} + +void CAI_BaseNPC::SetNavType( Navigation_t navType ) +{ + m_pNavigator->SetNavType( navType ); +} + +//----------------------------------------------------------------------------- +// NPCs can override this to tweak with how costly particular movements are +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost ) +{ + // We have nothing to say on the matter, but derived classes might + return false; +} + +bool CAI_BaseNPC::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval ) +{ + return false; +} + +bool CAI_BaseNPC::OverrideMove( float flInterval ) +{ + return false; +} + + +//========================================================= +// VecToYaw - turns a directional vector into a yaw value +// that points down that vector. +//========================================================= +float CAI_BaseNPC::VecToYaw( const Vector &vecDir ) +{ + if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0) + return GetLocalAngles().y; + + return UTIL_VecToYaw( vecDir ); +} + +//----------------------------------------------------------------------------- +// Inherited from IAI_MotorMovementServices +//----------------------------------------------------------------------------- +float CAI_BaseNPC::CalcYawSpeed( void ) +{ + // Negative values are invalud + return -1.0f; +} + +bool CAI_BaseNPC::OnCalcBaseMove( AILocalMoveGoal_t *pMoveGoal, + float distClear, + AIMoveResult_t *pResult ) +{ + if ( pMoveGoal->directTrace.pObstruction ) + { + CBasePropDoor *pPropDoor = dynamic_cast( pMoveGoal->directTrace.pObstruction ); + if ( pPropDoor && OnUpcomingPropDoor( pMoveGoal, pPropDoor, distClear, pResult ) ) + { + return true; + } + } + + return false; +} + + +bool CAI_BaseNPC::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal, + float distClear, + AIMoveResult_t *pResult ) +{ + if ( pMoveGoal->directTrace.pObstruction ) + { + CBaseDoor *pDoor = dynamic_cast( pMoveGoal->directTrace.pObstruction ); + if ( pDoor && OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) ) + { + return true; + } + } + + return false; +} + + +bool CAI_BaseNPC::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, + CBaseDoor *pDoor, + float distClear, + AIMoveResult_t *pResult ) +{ + if ( pMoveGoal->maxDist < distClear ) + return false; + + // By default, NPCs don't know how to open doors + if ( pDoor->m_toggle_state == TS_AT_BOTTOM || pDoor->m_toggle_state == TS_GOING_DOWN ) + { + if ( distClear < 0.1 ) + { + *pResult = AIMR_BLOCKED_ENTITY; + } + else + { + pMoveGoal->maxDist = distClear; + *pResult = AIMR_OK; + } + + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pMoveGoal - +// pDoor - +// distClear - +// default - +// spawn - +// oldorg - +// pfPosition - +// neworg - +// Output : Returns true if movement is solved, false otherwise. +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal, + CBasePropDoor *pDoor, + float distClear, + AIMoveResult_t *pResult ) +{ + if ( (pMoveGoal->flags & AILMG_TARGET_IS_GOAL) && pMoveGoal->maxDist < distClear ) + return false; + + if ( pMoveGoal->maxDist + GetHullWidth() * .25 < distClear ) + return false; + + if (pDoor == m_hOpeningDoor) + { + if ( pDoor->IsNPCOpening( this ) ) + { + // We're in the process of opening the door, don't be blocked by it. + pMoveGoal->maxDist = distClear; + *pResult = AIMR_OK; + return true; + } + m_hOpeningDoor = NULL; + } + + if ((CapabilitiesGet() & bits_CAP_DOORS_GROUP) && !pDoor->IsDoorLocked() && (pDoor->IsDoorClosed() || pDoor->IsDoorClosing())) + { + AI_Waypoint_t *pOpenDoorRoute = NULL; + + opendata_t opendata; + pDoor->GetNPCOpenData(this, opendata); + + // dvs: FIXME: local route might not be sufficient + pOpenDoorRoute = GetPathfinder()->BuildLocalRoute( + GetLocalOrigin(), + opendata.vecStandPos, + NULL, + bits_WP_TO_DOOR | bits_WP_DONT_SIMPLIFY, + NO_NODE, + bits_BUILD_GROUND | bits_BUILD_IGNORE_NPCS, + 0.0); + + if ( pOpenDoorRoute ) + { + if ( AIIsDebuggingDoors(this) ) + { + NDebugOverlay::Cross3D(opendata.vecStandPos + Vector(0,0,1), 32, 255, 255, 255, false, 1.0 ); + Msg( "Opening door!\n" ); + } + + // Attach the door to the waypoint so we open it when we get there. + // dvs: FIXME: this is kind of bullshit, I need to find the exact waypoint to open the door + // should I just walk the path until I find it? + pOpenDoorRoute->m_hData = pDoor; + + GetNavigator()->GetPath()->PrependWaypoints( pOpenDoorRoute ); + + m_hOpeningDoor = pDoor; + pMoveGoal->maxDist = distClear; + *pResult = AIMR_CHANGE_TYPE; + + return true; + } + else + AIDoorDebugMsg( this, "Failed create door route!\n" ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Called by the navigator to initiate the opening of a prop_door +// that is in our way. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor ) +{ + // dvs: not quite working, disabled for now. + //opendata_t opendata; + //pDoor->GetNPCOpenData(this, opendata); + // + //if (HaveSequenceForActivity(opendata.eActivity)) + //{ + // SetIdealActivity(opendata.eActivity); + //} + //else + { + // We don't have an appropriate sequence, just open the door magically. + OpenPropDoorNow( pDoor ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when we are trying to open a prop_door and it's time to start +// the door moving. This is called either in response to an anim event +// or as a fallback when we don't have an appropriate open activity. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OpenPropDoorNow( CBasePropDoor *pDoor ) +{ + // Start the door moving. + pDoor->NPCOpenDoor(this); + + // Wait for the door to finish opening before trying to move through the doorway. + m_flMoveWaitFinished = gpGlobals->curtime + pDoor->GetOpenInterval(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when the door we were trying to open becomes fully open. +// Input : pDoor - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OnDoorFullyOpen(CBasePropDoor *pDoor) +{ + // We're done with the door. + m_hOpeningDoor = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when the door we were trying to open becomes blocked before opening. +// Input : pDoor - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::OnDoorBlocked(CBasePropDoor *pDoor) +{ + // dvs: FIXME: do something so that we don't loop forever trying to open this door + // not clearing out the door handle will cause the NPC to invalidate the connection + // We're done with the door. + //m_hOpeningDoor = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Template NPCs are marked as templates by the level designer. They +// do not spawn, but their keyvalues are saved for use by a template +// spawner. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsTemplate( void ) +{ + return HasSpawnFlags( SF_NPC_TEMPLATE ); +} + + + +//----------------------------------------------------------------------------- +// +// Movement code for walking + flying +// +//----------------------------------------------------------------------------- +int CAI_BaseNPC::FlyMove( const Vector& pfPosition, unsigned int mask ) +{ + Vector oldorg, neworg; + trace_t trace; + + // try the move + VectorCopy( GetAbsOrigin(), oldorg ); + VectorAdd( oldorg, pfPosition, neworg ); + UTIL_TraceEntity( this, oldorg, neworg, mask, &trace ); + if (trace.fraction == 1) + { + if ( (GetFlags() & FL_SWIM) && enginetrace->GetPointContents(trace.endpos) == CONTENTS_EMPTY ) + return false; // swim monster left water + + SetAbsOrigin( trace.endpos ); + PhysicsTouchTriggers(); + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ent - +// Dir - Normalized direction vector for movement. +// dist - Distance along 'Dir' to move. +// iMode - +// Output : Returns nonzero on success, zero on failure. +//----------------------------------------------------------------------------- +int CAI_BaseNPC::WalkMove( const Vector& vecPosition, unsigned int mask ) +{ + if ( GetFlags() & (FL_FLY | FL_SWIM) ) + { + return FlyMove( vecPosition, mask ); + } + + if ( (GetFlags() & FL_ONGROUND) == 0 ) + { + return 0; + } + + trace_t trace; + Vector oldorg, neworg, end; + Vector move( vecPosition[0], vecPosition[1], 0.0f ); + VectorCopy( GetAbsOrigin(), oldorg ); + VectorAdd( oldorg, move, neworg ); + + // push down from a step height above the wished position + float flStepSize = sv_stepsize.GetFloat(); + neworg[2] += flStepSize; + VectorCopy(neworg, end); + end[2] -= flStepSize*2; + + UTIL_TraceEntity( this, neworg, end, mask, &trace ); + if ( trace.allsolid ) + return false; + + if (trace.startsolid) + { + neworg[2] -= flStepSize; + UTIL_TraceEntity( this, neworg, end, mask, &trace ); + if ( trace.allsolid || trace.startsolid ) + return false; + } + + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( GetFlags() & FL_PARTIALGROUND ) + { + SetAbsOrigin( oldorg + move ); + PhysicsTouchTriggers(); + SetGroundEntity( NULL ); + return true; + } + + return false; // walked off an edge + } + + // check point traces down for dangling corners + SetAbsOrigin( trace.endpos ); + + if (UTIL_CheckBottom( this, NULL, flStepSize ) == 0) + { + if ( GetFlags() & FL_PARTIALGROUND ) + { + // entity had floor mostly pulled out from underneath it + // and is trying to correct + PhysicsTouchTriggers(); + return true; + } + + // Reset to original position + SetAbsOrigin( oldorg ); + return false; + } + + if ( GetFlags() & FL_PARTIALGROUND ) + { + // Con_Printf ("back on ground\n"); + RemoveFlag( FL_PARTIALGROUND ); + } + + // the move is ok + SetGroundEntity( trace.m_pEnt ); + PhysicsTouchTriggers(); + return true; +} + +//----------------------------------------------------------------------------- + +static void AIMsgGuts( CAI_BaseNPC *pAI, unsigned flags, const char *pszMsg ) +{ + int len = strlen( pszMsg ); + const char *pszFmt2 = NULL; + + if ( len && pszMsg[len-1] == '\n' ) + { + (const_cast(pszMsg))[len-1] = 0; + pszFmt2 = "%s (%s: %d/%s) [%d]\n"; + } + else + pszFmt2 = "%s (%s: %d/%s) [%d]"; + + DevMsg( pszFmt2, + pszMsg, + pAI->GetClassname(), + pAI->entindex(), + ( pAI->GetEntityName() == NULL_STRING ) ? "" : STRING(pAI->GetEntityName()), + gpGlobals->tickcount ); +} + +void DevMsg( CAI_BaseNPC *pAI, unsigned flags, const char *pszFormat, ... ) +{ + if ( (flags & AIMF_IGNORE_SELECTED) || (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) ) + { + AIMsgGuts( pAI, flags, CFmtStr( &pszFormat ) ); + } +} + +//----------------------------------------------------------------------------- + +void DevMsg( CAI_BaseNPC *pAI, const char *pszFormat, ... ) +{ + if ( (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) ) + { + AIMsgGuts( pAI, 0, CFmtStr( &pszFormat ) ); + } +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::IsPlayerAlly( CBasePlayer *pPlayer ) +{ + if ( pPlayer == NULL ) + { + // in multiplayer mode we need a valid pPlayer + // or override this virtual function + if ( !AI_IsSinglePlayer() ) + return false; + + // NULL means single player mode + pPlayer = UTIL_GetLocalPlayer(); + } + + return ( !pPlayer || IRelationType( pPlayer ) == D_LI ); +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::SetCommandGoal( const Vector &vecGoal ) +{ + m_vecCommandGoal = vecGoal; + m_CommandMoveMonitor.ClearMark(); +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::ClearCommandGoal() +{ + m_vecCommandGoal = vec3_invalid; + m_CommandMoveMonitor.ClearMark(); +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::IsInPlayerSquad() const +{ + return ( m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() && !CAI_Squad::IsSilentMember(this) ); +} + + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::CanBeUsedAsAFriend( void ) +{ + if ( IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN) ) + return false; + return true; +} + +//----------------------------------------------------------------------------- + +Vector CAI_BaseNPC::GetSmoothedVelocity( void ) +{ + if( GetNavType() == NAV_GROUND || GetNavType() == NAV_FLY ) + { + return m_pMotor->GetCurVel(); + } + + return BaseClass::GetSmoothedVelocity(); +} + + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition ) +{ + trace_t tr; + + // By default, we ignore the viewer (me) when determining cover positions + CTraceFilterLOS filter( NULL, COLLISION_GROUP_NONE, this ); + + // If I'm trying to find cover from the player, and the player is in a vehicle, + // ignore the vehicle for the purpose of determining line of sight. + CBaseEntity *pEnemy = GetEnemy(); + if ( pEnemy ) + { + // Hack to see if our threat position is our enemy + bool bThreatPosIsEnemy = ( (vecThreat - GetEnemy()->EyePosition()).LengthSqr() < 0.1f ); + if ( bThreatPosIsEnemy ) + { + CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer(); + if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() ) + { + // Ignore the vehicle + filter.SetPassEntity( pCCEnemy->GetVehicleEntity() ); + } + + if ( !filter.GetPassEntity() ) + { + filter.SetPassEntity( pEnemy ); + } + } + } + + AI_TraceLOS( vecThreat, vecPosition, this, &tr, &filter ); + + if( tr.fraction != 1.0 && hl2_episodic.GetBool() ) + { + if( tr.m_pEnt->m_iClassname == m_iClassname ) + { + // Don't hide behind buddies! + return false; + } + } + + return (tr.fraction != 1.0); +} + +//----------------------------------------------------------------------------- + +float CAI_BaseNPC::SetWait( float minWait, float maxWait ) +{ + int minThinks = Ceil2Int( minWait * 10 ); + + if ( maxWait == 0.0 ) + { + m_flWaitFinished = gpGlobals->curtime + ( 0.1 * minThinks ); + } + else + { + if ( minThinks == 0 ) // random 0..n is almost certain to not return 0 + minThinks = 1; + int maxThinks = Ceil2Int( maxWait * 10 ); + + m_flWaitFinished = gpGlobals->curtime + ( 0.1 * random->RandomInt( minThinks, maxThinks ) ); + } + return m_flWaitFinished; +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::ClearWait() +{ + m_flWaitFinished = FLT_MAX; +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::IsWaitFinished() +{ + return ( gpGlobals->curtime >= m_flWaitFinished ); +} + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::IsWaitSet() +{ + return ( m_flWaitFinished != FLT_MAX ); +} + +void CAI_BaseNPC::TestPlayerPushing( CBaseEntity *pEntity ) +{ + if ( HasSpawnFlags( SF_NPC_NO_PLAYER_PUSHAWAY ) ) + return; + + // Heuristic for determining if the player is pushing me away + CBasePlayer *pPlayer = ToBasePlayer( pEntity ); + if ( pPlayer && !( pPlayer->GetFlags() & FL_NOTARGET ) ) + { + if ( (pPlayer->m_nButtons & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT)) || + pPlayer->GetAbsVelocity().AsVector2D().LengthSqr() > 50*50 ) + { + SetCondition( COND_PLAYER_PUSHING ); + Vector vecPush = GetAbsOrigin() - pPlayer->GetAbsOrigin(); + VectorNormalize( vecPush ); + CascadePlayerPush( vecPush, pPlayer->WorldSpaceCenter() ); + } + } +} + +void CAI_BaseNPC::CascadePlayerPush( const Vector &push, const Vector &pushOrigin ) +{ + // + // Try to push any friends that are in the way. + // + float hullWidth = GetHullWidth(); + const Vector & origin = GetAbsOrigin(); + const Vector2D &origin2D = origin.AsVector2D(); + + const float MIN_Z_TO_TRANSMIT = GetHullHeight() * 0.5 + 0.1; + const float DIST_REQD_TO_TRANSMIT_PUSH_SQ = Square( hullWidth * 5 + 0.1 ); + const float DIST_FROM_PUSH_VECTOR_REQD_SQ = Square( hullWidth + 0.1 ); + + Vector2D pushTestPoint = vec2_invalid; + + for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + CAI_BaseNPC *pOther = g_AI_Manager.AccessAIs()[i]; + if ( pOther != this && pOther->IRelationType(this) == D_LI && !pOther->HasCondition( COND_PLAYER_PUSHING ) ) + { + const Vector &friendOrigin = pOther->GetAbsOrigin(); + if ( fabsf( friendOrigin.z - origin.z ) < MIN_Z_TO_TRANSMIT && + ( friendOrigin.AsVector2D() - origin.AsVector2D() ).LengthSqr() < DIST_REQD_TO_TRANSMIT_PUSH_SQ ) + { + if ( pushTestPoint == vec2_invalid ) + { + pushTestPoint = origin2D - pushOrigin.AsVector2D(); + // No normalize, since it wants to just be a big number and we can't be less that a hull away + pushTestPoint *= 2000; + pushTestPoint += origin2D; + + } + float t; + float distSq = CalcDistanceSqrToLine2D( friendOrigin.AsVector2D(), origin2D, pushTestPoint, &t ); + if ( t > 0 && distSq < DIST_FROM_PUSH_VECTOR_REQD_SQ ) + { + pOther->SetCondition( COND_PLAYER_PUSHING ); + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Break into pieces! +//----------------------------------------------------------------------------- +void CAI_BaseNPC::Break( CBaseEntity *pBreaker ) +{ + m_takedamage = DAMAGE_NO; + + Vector velocity; + AngularImpulse angVelocity; + IPhysicsObject *pPhysics = VPhysicsGetObject(); + Vector origin; + QAngle angles; + AddSolidFlags( FSOLID_NOT_SOLID ); + if ( pPhysics ) + { + pPhysics->GetVelocity( &velocity, &angVelocity ); + pPhysics->GetPosition( &origin, &angles ); + pPhysics->RecheckCollisionFilter(); + } + else + { + velocity = GetAbsVelocity(); + QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity ); + origin = GetAbsOrigin(); + angles = GetAbsAngles(); + } + + breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity ); + params.impactEnergyScale = m_impactEnergyScale; + params.defCollisionGroup = GetCollisionGroup(); + if ( params.defCollisionGroup == COLLISION_GROUP_NONE ) + { + // don't automatically make anything COLLISION_GROUP_NONE or it will + // collide with debris being ejected by breaking + params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE; + } + + // no damage/damage force? set a burst of 100 for some movement + params.defBurstScale = 100;//pDamageInfo ? 0 : 100; + PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, false ); + + UTIL_Remove(this); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for breaking the breakable immediately. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputBreak( inputdata_t &inputdata ) +{ + Break( inputdata.pActivator ); +} + + +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::FindNearestValidGoalPos( const Vector &vTestPoint, Vector *pResult ) +{ + AIMoveTrace_t moveTrace; + Vector vCandidate = vec3_invalid; + if ( GetNavigator()->CanFitAtPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) ) + { + if ( GetMoveProbe()->CheckStandPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) ) + { + vCandidate = vTestPoint; + } + } + + if ( vCandidate == vec3_invalid ) + { + int iNearestNode = GetPathfinder()->NearestNodeToPoint( vTestPoint ); + if ( iNearestNode != NO_NODE ) + { + GetMoveProbe()->MoveLimit( NAV_GROUND, + g_pBigAINet->GetNodePosition(GetHullType(), iNearestNode ), + vTestPoint, + MASK_SOLID_BRUSHONLY, + NULL, + 0, + &moveTrace ); + if ( ( moveTrace.vEndPosition - vTestPoint ).Length2DSqr() < Square( GetHullWidth() * 3.0 ) && + GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY ) ) + { + vCandidate = moveTrace.vEndPosition; + } + } + } + + if ( vCandidate != vec3_invalid ) + { + AI_Waypoint_t *pPathToPoint = GetPathfinder()->BuildRoute( GetAbsOrigin(), vCandidate, AI_GetSinglePlayer(), 5*12, NAV_NONE, true ); + if ( pPathToPoint ) + { + GetPathfinder()->UnlockRouteNodes( pPathToPoint ); + CAI_Path tempPath; + tempPath.SetWaypoints( pPathToPoint ); // path object will delete waypoints + } + else + vCandidate = vec3_invalid; + } + + if ( vCandidate == vec3_invalid ) + { + GetMoveProbe()->MoveLimit( NAV_GROUND, + GetAbsOrigin(), + vTestPoint, + MASK_SOLID_BRUSHONLY, + NULL, + 0, + &moveTrace ); + vCandidate = moveTrace.vEndPosition; + } + + if ( vCandidate == vec3_invalid ) + return false; + + *pResult = vCandidate; + return true; +} + +//--------------------------------------------------------- +// Pass a direction to get how far an NPC would see if facing +// that direction. Pass nothing to get the length of the NPC's +// current line of sight. +//--------------------------------------------------------- +float CAI_BaseNPC::LineOfSightDist( const Vector &vecDir, float zEye ) +{ + Vector testDir; + if( vecDir == vec3_invalid ) + { + testDir = EyeDirection3D(); + } + else + { + testDir = vecDir; + } + + if ( zEye == FLT_MAX ) + zEye = EyePosition().z; + + trace_t tr; + // Need to center trace so don't get erratic results based on orientation + Vector testPos( GetAbsOrigin().x, GetAbsOrigin().y, zEye ); + AI_TraceLOS( testPos, testPos + testDir * MAX_COORD_RANGE, this, &tr ); + return (tr.startpos - tr.endpos ).Length(); +} + +ConVar ai_LOS_mode( "ai_LOS_mode", "0", FCVAR_REPLICATED ); + +//----------------------------------------------------------------------------- +// Purpose: Use this to perform AI tracelines that are trying to determine LOS between points. +// LOS checks between entities should use FVisible. +//----------------------------------------------------------------------------- +void AI_TraceLOS( const Vector& vecAbsStart, const Vector& vecAbsEnd, CBaseEntity *pLooker, trace_t *ptr, ITraceFilter *pFilter ) +{ + AI_PROFILE_SCOPE( AI_TraceLOS ); + + if ( ai_LOS_mode.GetBool() ) + { + // Don't use LOS tracefilter + UTIL_TraceLine( vecAbsStart, vecAbsEnd, MASK_OPAQUE, pLooker, COLLISION_GROUP_NONE, ptr ); + return; + } + + // Use the custom LOS trace filter + CTraceFilterLOS traceFilter( pLooker, COLLISION_GROUP_NONE ); + if ( !pFilter ) + pFilter = &traceFilter; + AI_TraceLine( vecAbsStart, vecAbsEnd, MASK_OPAQUE_AND_NPCS, pFilter, ptr ); +} + +void CAI_BaseNPC::InputSetSpeedModifierRadius( inputdata_t &inputdata ) +{ + m_iSpeedModRadius = inputdata.value.Int(); + m_iSpeedModRadius *= m_iSpeedModRadius; +} +void CAI_BaseNPC::InputSetSpeedModifierSpeed( inputdata_t &inputdata ) +{ + m_iSpeedModSpeed = inputdata.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsAllowedToDodge( void ) +{ + // Can't do it if I'm not available + if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT ) + return false; + + return ( m_flNextDodgeTime <= gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ParseScriptedNPCInteractions( void ) +{ + // Already parsed them? + if ( m_ScriptedInteractions.Count() ) + return; + + // Parse the model's key values and find any dynamic interactions + KeyValues *modelKeyValues = new KeyValues(""); + if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) ) + { + // Do we have a dynamic interactions section? + KeyValues *pkvInteractions = modelKeyValues->FindKey("dynamic_interactions"); + if ( pkvInteractions ) + { + KeyValues *pkvNode = pkvInteractions->GetFirstSubKey(); + while ( pkvNode ) + { + ScriptedNPCInteraction_t sInteraction; + sInteraction.iszInteractionName = AllocPooledString( pkvNode->GetName() ); + + // Trigger method + const char *pszTrigger = pkvNode->GetString( "trigger", NULL ); + if ( pszTrigger ) + { + if ( !Q_strncmp( pszTrigger, "auto_in_combat", 14) ) + { + sInteraction.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT; + } + } + + // Loop Break trigger method + pszTrigger = pkvNode->GetString( "loop_break_trigger", NULL ); + if ( pszTrigger ) + { + char szTrigger[256]; + Q_strncpy( szTrigger, pszTrigger, sizeof(szTrigger) ); + char *pszParam = strtok( szTrigger, " " ); + while (pszParam) + { + if ( !Q_strncmp( pszParam, "on_damage", 9) ) + { + sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE; + } + if ( !Q_strncmp( pszParam, "on_flashlight_illum", 19) ) + { + sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM; + } + + pszParam = strtok(NULL," "); + } + } + + // Origin + const char *pszOrigin = pkvNode->GetString( "origin_relative", "0 0 0" ); + UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), pszOrigin ); + + // Angles + const char *pszAngles = pkvNode->GetString( "angles_relative", NULL ); + if ( pszAngles ) + { + sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES; + UTIL_StringToVector( sInteraction.angRelativeAngles.Base(), pszAngles ); + } + + // Velocity + const char *pszVelocity = pkvNode->GetString( "velocity_relative", NULL ); + if ( pszVelocity ) + { + sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY; + UTIL_StringToVector( sInteraction.vecRelativeVelocity.Base(), pszVelocity ); + } + + // Entry Sequence + const char *pszSequence = pkvNode->GetString( "entry_sequence", NULL ); + if ( pszSequence ) + { + sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString( pszSequence ); + } + // Entry Activity + const char *pszActivity = pkvNode->GetString( "entry_activity", NULL ); + if ( pszActivity ) + { + sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID( pszActivity ); + } + + // Sequence + pszSequence = pkvNode->GetString( "sequence", NULL ); + if ( pszSequence ) + { + sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( pszSequence ); + } + // Activity + pszActivity = pkvNode->GetString( "activity", NULL ); + if ( pszActivity ) + { + sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID( pszActivity ); + } + + // Exit Sequence + pszSequence = pkvNode->GetString( "exit_sequence", NULL ); + if ( pszSequence ) + { + sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString( pszSequence ); + } + // Exit Activity + pszActivity = pkvNode->GetString( "exit_activity", NULL ); + if ( pszActivity ) + { + sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID( pszActivity ); + } + + // Delay + sInteraction.flDelay = pkvNode->GetFloat( "delay", 10.0 ); + + // Delta + sInteraction.flDistSqr = pkvNode->GetFloat( "origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST) ); + + // Loop? + if ( pkvNode->GetFloat( "loop_in_action", 0 ) ) + { + sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION; + } + + // Needs a weapon? + const char *pszNeedsWeapon = pkvNode->GetString( "needs_weapon", NULL ); + if ( pszNeedsWeapon ) + { + if ( !Q_strncmp( pszNeedsWeapon, "ME", 2 ) ) + { + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; + } + else if ( !Q_strncmp( pszNeedsWeapon, "THEM", 4 ) ) + { + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; + } + else if ( !Q_strncmp( pszNeedsWeapon, "BOTH", 4 ) ) + { + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; + } + } + + // Specific weapon types + const char *pszWeaponName = pkvNode->GetString( "weapon_mine", NULL ); + if ( pszWeaponName ) + { + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; + sInteraction.iszMyWeapon = AllocPooledString( pszWeaponName ); + } + pszWeaponName = pkvNode->GetString( "weapon_theirs", NULL ); + if ( pszWeaponName ) + { + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; + sInteraction.iszTheirWeapon = AllocPooledString( pszWeaponName ); + } + + // Add it to the list + AddScriptedNPCInteraction( &sInteraction ); + + // Move to next interaction + pkvNode = pkvNode->GetNextKey(); + } + } + } + + modelKeyValues->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction ) +{ + int nNewIndex = m_ScriptedInteractions.AddToTail(); + + if ( ai_debug_dyninteractions.GetBool() ) + { + Msg("%s(%s): Added dynamic interaction: %s\n", GetClassName(), GetDebugName(), STRING(pInteraction->iszInteractionName) ); + } + + // Copy the interaction over + ScriptedNPCInteraction_t *pNewInt = &(m_ScriptedInteractions[nNewIndex]); + memcpy( pNewInt, pInteraction, sizeof(ScriptedNPCInteraction_t) ); + + // Calculate the local to world matrix + m_ScriptedInteractions[nNewIndex].matDesiredLocalToWorld.SetupMatrixOrgAngles( pInteraction->vecRelativeOrigin, pInteraction->angRelativeAngles ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase ) +{ + if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID ) + { + int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity ); + return GetSequenceName( iSequence ); + } + + if ( pInteraction->sPhases[iPhase].iszSequence != NULL_STRING ) + return STRING(pInteraction->sPhases[iPhase].iszSequence); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive ) +{ + m_hInteractionPartner = pOtherNPC; + if ( bActive ) + { + m_iInteractionState = NPCINT_RUNNING_ACTIVE; + } + else + { + m_iInteractionState = NPCINT_RUNNING_PARTNER; + } + m_bCannotDieDuringInteraction = true; + + // Force the NPC into an idle schedule so they don't move. + // NOTE: We must set SCHED_IDLE_STAND directly, to prevent derived NPC + // classes from translating the idle stand schedule away to do something bad. + SetSchedule( GetSchedule(SCHED_IDLE_STAND) ); + + // Prepare the NPC for the script. Setting this allows the scripted sequences + // that we're about to create to immediately grab & use this NPC right away. + // This prevents the NPC from being able to make any schedule decisions + // before the DSS gets underway. + m_scriptState = SCRIPT_PLAYING; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles ) +{ + variant_t emptyVariant; + + StartRunningInteraction( pOtherNPC, true ); + if ( pOtherNPC ) + { + pOtherNPC->StartRunningInteraction( this, false ); + + //Msg("%s(%s) disabled collisions with %s(%s) at %0.2f\n", GetClassName(), GetDebugName(), pOtherNPC->GetClassName(), pOtherNPC->GetDebugName(), gpGlobals->curtime ); + PhysDisableEntityCollisions( this, pOtherNPC ); + } + + // Determine which sequences we're going to use + const char *pszEntrySequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_ENTRY ); + const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE ); + const char *pszExitSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_EXIT ); + + // Debug + if ( ai_debug_dyninteractions.GetBool() ) + { + if ( pOtherNPC ) + { + Msg("%s(%s) starting dynamic interaction \"%s\" with %s(%s).\n", GetClassName(), GetDebugName(), STRING(pInteraction->iszInteractionName), pOtherNPC->GetClassName(), pOtherNPC->GetDebugName() ); + if ( pszEntrySequence ) + { + Msg( " - Entry sequence: %s\n", pszEntrySequence ); + } + Msg( " - Core sequence: %s\n", pszSequence ); + if ( pszExitSequence ) + { + Msg( " - Exit sequence: %s\n", pszExitSequence ); + } + } + } + + // Create a scripted sequence name that's guaranteed to be unique + char szSSName[256]; + if ( pOtherNPC ) + { + Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d%s%d", GetDebugName(), entindex(), pOtherNPC->GetDebugName(), pOtherNPC->entindex() ); + } + else + { + Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d", GetDebugName(), entindex() ); + } + string_t iszSSName = AllocPooledString(szSSName); + + // Setup next attempt + pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2); + + // Spawn a scripted sequence for this NPC to play the interaction anim + CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" ); + pMySequence->KeyValue( "m_iszEntry", pszEntrySequence ); + pMySequence->KeyValue( "m_iszPlay", pszSequence ); + pMySequence->KeyValue( "m_iszPostIdle", pszExitSequence ); + pMySequence->KeyValue( "m_fMoveTo", "5" ); + pMySequence->SetAbsOrigin( GetAbsOrigin() ); + + QAngle angDesired = GetAbsAngles(); + angDesired[YAW] = m_flInteractionYaw; + + pMySequence->SetAbsAngles( angDesired ); + pMySequence->ForceSetTargetEntity( this, true ); + pMySequence->SetName( iszSSName ); + pMySequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE ); + pMySequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 ); + pMySequence->SetSynchPostIdles( true ); + if ( ai_debug_dyninteractions.GetBool() ) + { + pMySequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT; + } + + // Spawn the matching scripted sequence for the other NPC + CAI_ScriptedSequence *pTheirSequence = NULL; + if ( pOtherNPC ) + { + pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" ); + pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence ); + pTheirSequence->KeyValue( "m_iszPlay", pszSequence ); + pTheirSequence->KeyValue( "m_iszPostIdle", pszExitSequence ); + pTheirSequence->KeyValue( "m_fMoveTo", "5" ); + pTheirSequence->SetAbsOrigin( vecOtherOrigin ); + pTheirSequence->SetAbsAngles( angOtherAngles ); + pTheirSequence->ForceSetTargetEntity( pOtherNPC, true ); + pTheirSequence->SetName( iszSSName ); + pTheirSequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE ); + pTheirSequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 ); + pTheirSequence->SetSynchPostIdles( true ); + if ( ai_debug_dyninteractions.GetBool() ) + { + pTheirSequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT; + } + + // Tell their sequence to keep their position relative to me + pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld ); + } + + // Spawn both sequences at once + pMySequence->Spawn(); + if ( pTheirSequence ) + { + pTheirSequence->Spawn(); + } + + // Call activate on both sequences at once + pMySequence->Activate(); + if ( pTheirSequence ) + { + pTheirSequence->Activate(); + } + + // Setup the outputs for both sequences. The first kills them both when it finishes + pMySequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); + if ( pszExitSequence ) + { + pMySequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); + if ( pTheirSequence ) + { + pTheirSequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); + } + } + else + { + pMySequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); + if ( pTheirSequence ) + { + pTheirSequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); + } + } + if ( pTheirSequence ) + { + pTheirSequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) ); + } + + // Tell both sequences to start + pMySequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 ); + if ( pTheirSequence ) + { + pTheirSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced ) +{ + if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT ) + return false; + + if ( !IsAlive() ) + return false; + + if ( IsOnFire() ) + return false; + + if ( IsCrouching() ) + return false; + + // Not while running scripted sequences + if ( m_hCine ) + return false; + + if ( bForced ) + { + if ( !m_hForcedInteractionPartner ) + return false; + } + else + { + if ( m_hForcedInteractionPartner || m_hInteractionPartner ) + return false; + if ( IsInAScript() || !HasCondition(COND_IN_PVS) ) + return false; + if ( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_MOVE_AWAY) ) + return false; + + // Default AI prevents interactions while melee attacking, but not ranged attacking + if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CheckForScriptedNPCInteractions( void ) +{ + // Are we being forced to interact with another NPC? If so, do that + if ( m_hForcedInteractionPartner ) + { + CheckForcedNPCInteractions(); + return; + } + + // Otherwise, see if we can interaction with our enemy + if ( !m_ScriptedInteractions.Count() || !GetEnemy() ) + return; + + CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer(); + + if( !pNPC ) + return; + + // Recalculate interaction capability whenever we switch enemies + if ( m_hLastInteractionTestTarget != GetEnemy() ) + { + m_hLastInteractionTestTarget = GetEnemy(); + + CalculateValidEnemyInteractions(); + } + + // First, make sure I'm in a state where I can do this + if ( !CanRunAScriptedNPCInteraction() ) + return; + if ( pNPC && !pNPC->CanRunAScriptedNPCInteraction() ) + return; + + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i]; + + if ( !pInteraction->bValidOnCurrentEnemy ) + continue; + if ( pInteraction->flNextAttemptTime > gpGlobals->curtime ) + continue; + + Vector vecOrigin; + QAngle angAngles; + if ( !InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) ) + continue; + + m_iInteractionPlaying = i; + StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate all the valid dynamic interactions we can perform with our current enemy +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CalculateValidEnemyInteractions( void ) +{ + CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer(); + if ( !pNPC ) + return; + + bool bDebug = (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT && ai_debug_dyninteractions.GetBool()); + if ( bDebug ) + { + Msg("%s(%s): Computing valid interactions with %s(%s)\n", GetClassName(), GetDebugName(), pNPC->GetClassName(), pNPC->GetDebugName() ); + } + + bool bFound = false; + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i]; + pInteraction->bValidOnCurrentEnemy = false; + + // If the trigger method of the interaction isn't the one we're after, we're done + if ( pInteraction->iTriggerMethod != SNPCINT_AUTOMATIC_IN_COMBAT ) + continue; + + if ( !pNPC->GetModelPtr() ) + continue; + + // If we have a damage filter that prevents us hurting the enemy, + // don't interact with him, since most interactions kill the enemy. + // Create a fake damage info to test it with. + CTakeDamageInfo tempinfo( this, this, vec3_origin, vec3_origin, 1.0, DMG_BULLET ); + if ( !pNPC->PassesDamageFilter( tempinfo ) ) + continue; + + // Check the weapon requirements for the interaction + if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_ME ) + { + if ( !GetActiveWeapon()) + continue; + + // Check the specific weapon type + if ( pInteraction->iszMyWeapon != NULL_STRING && GetActiveWeapon()->m_iClassname != pInteraction->iszMyWeapon ) + continue; + } + if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_THEM ) + { + if ( !pNPC->GetActiveWeapon() ) + continue; + + // Check the specific weapon type + if ( pInteraction->iszTheirWeapon != NULL_STRING && pNPC->GetActiveWeapon()->m_iClassname != pInteraction->iszTheirWeapon ) + continue; + } + + // Script needs the other NPC, so make sure they're not dead + if ( !pNPC->IsAlive() ) + continue; + + // Use sequence? or activity? + if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID ) + { + // Resolve the activity to a sequence, and make sure our enemy has it + const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE ); + if ( !pszSequence ) + continue; + if ( pNPC->LookupSequence( pszSequence ) == -1 ) + continue; + } + else + { + if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 ) + continue; + } + + pInteraction->bValidOnCurrentEnemy = true; + bFound = true; + + if ( bDebug ) + { + Msg(" Found: %s\n", STRING(pInteraction->iszInteractionName) ); + } + } + + if ( bDebug && !bFound ) + { + Msg(" No valid interactions found.\n"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CheckForcedNPCInteractions( void ) +{ + // If we don't have an interaction, we're waiting for our partner to start it. Do nothing. + if ( m_iInteractionPlaying == NPCINT_NONE ) + return; + + CAI_BaseNPC *pNPC = m_hForcedInteractionPartner->MyNPCPointer(); + + // First, make sure both NPCs are able to do this + if ( !CanRunAScriptedNPCInteraction( true ) || !pNPC->CanRunAScriptedNPCInteraction( true ) ) + { + // If we were still moving to our target, abort. + if ( m_iInteractionState == NPCINT_MOVING_TO_MARK ) + { + if ( m_hForcedInteractionPartner ) + { + // We've aborted a forced interaction. Let the mapmaker know. + m_OnForcedInteractionAborted.FireOutput( this, this ); + } + + CleanupForcedInteraction(); + pNPC->CleanupForcedInteraction(); + } + return; + } + + // Check to see if we can start our interaction. If we can, dance. + ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[m_iInteractionPlaying]; + Vector vecOrigin; + QAngle angAngles; + if ( !InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) ) + return;; + + StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles ) +{ + // Get a matrix that'll convert from my local interaction space to world space + VMatrix matMeToWorld, matLocalToWorld; + QAngle angMyCurrent = GetAbsAngles(); + angMyCurrent[YAW] = m_flInteractionYaw; + matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angMyCurrent ); + MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld ); + + // Get the desired NPC position in worldspace + vecOrigin = matLocalToWorld.GetTranslation(); + MatrixToAngles( matLocalToWorld, angAngles ); + + bool bDebug = ai_debug_dyninteractions.GetBool(); + if ( bDebug ) + { + NDebugOverlay::Axis( vecOrigin, angAngles, 20, true, 0.1 ); + } + + // Determine whether or not the enemy is on the target + float flDistSqr = (vecOrigin - pOtherNPC->GetAbsOrigin()).LengthSqr(); + if ( flDistSqr > pInteraction->flDistSqr ) + { + if ( bDebug ) + { + if ( m_debugOverlays & OVERLAY_NPC_SELECTED_BIT || pOtherNPC->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) + { + if ( ai_debug_dyninteractions.GetFloat() == 2 ) + { + Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr, + pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z ); + } + } + } + return false; + } + + if ( bDebug ) + { + Msg("DYNINT: (%s) testing interaction \"%s\"\n", GetDebugName(), STRING(pInteraction->iszInteractionName) ); + Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); + Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr, + pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z ); + + if ( pOtherNPC ) + { + float flOtherSpeed = pOtherNPC->GetSequenceGroundSpeed( pOtherNPC->GetSequence() ); + Msg(" %s Speed: %.2f\n", pOtherNPC->GetSequenceName( pOtherNPC->GetSequence() ), flOtherSpeed); + } + } + + // Angle check, if we're supposed to + if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES ) + { + QAngle angEnemyAngles = pOtherNPC->GetAbsAngles(); + bool bMatches = true; + for ( int ang = 0; ang < 3; ang++ ) + { + float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] ); + if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF ) + { + bMatches = false; + break; + } + } + if ( !bMatches ) + return false; + + if ( bDebug ) + { + Msg(" %s angle matched: (%0.2f %0.2f %0.2f), desired (%0.2f, %0.2f, %0.2f)\n", GetDebugName(), + anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) ); + } + } + + // TODO: Velocity check, if we're supposed to + if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY ) + { + + } + + // Valid so far. Now check to make sure there's nothing in the way. + // This isn't a very good method of checking, but it's cheap and rules out the problems we're seeing so far. + // If we start getting interactions that start a fair distance apart, we're going to need to do more work here. + trace_t tr; + AI_TraceLine( EyePosition(), pOtherNPC->EyePosition(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr); + if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC ) + { + if ( bDebug ) + { + Msg( " %s Interaction was blocked.\n", GetDebugName() ); + NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 ); + NDebugOverlay::Line( pOtherNPC->EyePosition(), tr.endpos, 255,0,0, true, 1.0 ); + } + return false; + } + + if ( bDebug ) + { + NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 ); + } + + // Do a knee-level trace to find low physics objects + Vector vecMyKnee, vecOtherKnee; + CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecMyKnee ); + pOtherNPC->CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecOtherKnee ); + AI_TraceLine( vecMyKnee, vecOtherKnee, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr); + if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC ) + { + if ( bDebug ) + { + Msg( " %s Interaction was blocked.\n", GetDebugName() ); + NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 ); + NDebugOverlay::Line( vecOtherKnee, tr.endpos, 255,0,0, true, 1.0 ); + } + return false; + } + + if ( bDebug ) + { + NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 ); + } + + // Finally, make sure the NPC can actually fit at the interaction position + // This solves problems with NPCs who are a few units or so above the + // interaction point, and would sink into the ground when playing the anim. + CTraceFilterSkipTwoEntities traceFilter( pOtherNPC, this, COLLISION_GROUP_NONE ); + AI_TraceHull( vecOrigin, vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr ); + if ( tr.startsolid ) + { + if ( bDebug ) + { + NDebugOverlay::Box( vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), 255,0,0, true, 1.0 ); + } + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this NPC cannot die because it's in an interaction +// and the flag has been set by the animation. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::HasInteractionCantDie( void ) +{ + return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputForceInteractionWithNPC( inputdata_t &inputdata ) +{ + // Get the interaction name & target + char parseString[255]; + Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString)); + + // First, the target's name + char *pszParam = strtok(parseString," "); + if ( !pszParam || !pszParam[0] ) + { + Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC \n", GetClassname(), GetDebugName(), inputdata.value.String() ); + return; + } + // Find the target + CBaseEntity *pTarget = FindNamedEntity( pszParam ); + if ( !pTarget ) + { + Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find entity named: %s\n", GetClassname(), GetDebugName(), pszParam ); + return; + } + CAI_BaseNPC *pNPC = pTarget->MyNPCPointer(); + if ( !pNPC || !pNPC->GetModelPtr() ) + { + Warning("%s(%s) received ForceInteractionWithNPC input, but entity named %s cannot run dynamic interactions.\n", GetClassname(), GetDebugName(), pszParam ); + return; + } + + // Second, the interaction name + pszParam = strtok(NULL," "); + if ( !pszParam || !pszParam[0] ) + { + Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC \n", GetClassname(), GetDebugName(), inputdata.value.String() ); + return; + } + + // Find the interaction from the name, and ensure it's one that the target NPC can play + int iInteraction = -1; + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + if ( Q_strncmp( pszParam, STRING(m_ScriptedInteractions[i].iszInteractionName), strlen(pszParam) ) ) + continue; + + // Use sequence? or activity? + if ( m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID ) + { + if ( !pNPC->HaveSequenceForActivity( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity ) ) + { + // Other NPC may have all the matching sequences, but just without the activity specified. + // Lets find a single sequence for us, and ensure they have a matching one. + int iMySeq = SelectWeightedSequence( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity ); + if ( pNPC->LookupSequence( GetSequenceName(iMySeq) ) == -1 ) + continue; + } + } + else + { + if ( pNPC->LookupSequence( STRING(m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 ) + continue; + } + + iInteraction = i; + break; + } + + if ( iInteraction == -1 ) + { + Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find an interaction named %s that entity named %s could run.\n", GetClassname(), GetDebugName(), pszParam, pNPC->GetDebugName() ); + return; + } + + // Found both pieces of data, lets dance. + StartForcedInteraction( pNPC, iInteraction ); + pNPC->StartForcedInteraction( this, NPCINT_NONE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction ) +{ + m_hForcedInteractionPartner = pNPC; + ClearSchedule(); + + m_iInteractionPlaying = iInteraction; + m_iInteractionState = NPCINT_MOVING_TO_MARK; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CleanupForcedInteraction( void ) +{ + m_hForcedInteractionPartner = NULL; + m_iInteractionPlaying = NPCINT_NONE; + m_iInteractionState = NPCINT_NOT_RUNNING; +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate a position to move to so that I can interact with my +// target NPC. +// +// FIXME: THIS ONLY WORKS FOR INTERACTIONS THAT REQUIRE THE TARGET +// NPC TO BE SOME DISTANCE DIRECTLY IN FRONT OF ME. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::CalculateForcedInteractionPosition( void ) +{ + if ( m_iInteractionPlaying == NPCINT_NONE ) + return; + + ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction(); + + // Pretend I was facing the target, and extrapolate from that the position I should be at + Vector vecToTarget = m_hForcedInteractionPartner->GetAbsOrigin() - GetAbsOrigin(); + VectorNormalize( vecToTarget ); + QAngle angToTarget; + VectorAngles( vecToTarget, angToTarget ); + + // Get the desired position in worldspace, relative to the target + VMatrix matMeToWorld, matLocalToWorld; + matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angToTarget ); + MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld ); + + Vector vecOrigin = GetAbsOrigin() - matLocalToWorld.GetTranslation(); + m_vecForcedWorldPosition = m_hForcedInteractionPartner->GetAbsOrigin() + vecOrigin; + + //NDebugOverlay::Axis( m_vecForcedWorldPosition, angToTarget, 20, true, 3.0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +//----------------------------------------------------------------------------- +void CAI_BaseNPC::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot ) +{ +#ifdef HL2_DLL + if ( IsActiveDynamicInteraction() ) + { + ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction(); + if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM ) + { + // Only do this in alyx darkness mode + if ( HL2GameRules()->IsAlyxInDarknessMode() ) + { + // Can only break when we're in the action anim + if ( m_hCine->IsPlayingAction() ) + { + m_hCine->StopActionLoop( true ); + } + } + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ModifyOrAppendCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + + // Append time since seen player + if ( m_flLastSawPlayerTime ) + { + set.AppendCriteria( "timesinceseenplayer", UTIL_VarArgs( "%f", gpGlobals->curtime - m_flLastSawPlayerTime ) ); + } + else + { + set.AppendCriteria( "timesinceseenplayer", "-1" ); + } + + // Append distance to my enemy + if ( GetEnemy() ) + { + set.AppendCriteria( "distancetoenemy", UTIL_VarArgs( "%f", EnemyDistance(GetEnemy()) ) ); + } + else + { + set.AppendCriteria( "distancetoenemy", "-1" ); + } +} + +//----------------------------------------------------------------------------- +// If I were crouching at my current location, could I shoot this target? +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::CouldShootIfCrouching( CBaseEntity *pTarget ) +{ + bool bWasStanding = !IsCrouching(); + Crouch(); + + Vector vecTarget; + if (GetActiveWeapon()) + { + vecTarget = pTarget->BodyTarget( GetActiveWeapon()->GetLocalOrigin() ); + } + else + { + vecTarget = pTarget->BodyTarget( GetLocalOrigin() ); + } + + bool bResult = WeaponLOSCondition( GetLocalOrigin(), vecTarget, false ); + + if ( bWasStanding ) + { + Stand(); + } + + return bResult; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsCrouchedActivity( Activity activity ) +{ + Activity realActivity = TranslateActivity(activity); + + switch ( realActivity ) + { + case ACT_RELOAD_LOW: + case ACT_COVER_LOW: + case ACT_COVER_PISTOL_LOW: + case ACT_COVER_SMG1_LOW: + case ACT_RELOAD_SMG1_LOW: + return true; + break; + default: + break; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Get shoot position of BCC at an arbitrary position +//----------------------------------------------------------------------------- +Vector CAI_BaseNPC::Weapon_ShootPosition( void ) +{ + Vector right; + GetVectors( NULL, &right, NULL ); + + bool bStanding = !IsCrouching(); + if ( bStanding && (CapabilitiesGet() & bits_CAP_DUCK) ) + { + if ( IsCrouchedActivity( GetActivity() ) ) + { + bStanding = false; + } + } + + if ( !bStanding ) + return (GetAbsOrigin() + GetCrouchGunOffset() + right * 8); + + return BaseClass::Weapon_ShootPosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity ) +{ + if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) + { + if ( ai_test_moveprobe_ignoresmall.GetBool() && IsNavigationUrgent() ) + { + IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject(); + + if ( pPhysics->IsMoveable() && pPhysics->GetMass() < 40.0 ) + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::Crouch( void ) +{ + m_bIsCrouching = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::IsCrouching( void ) +{ + return ( (CapabilitiesGet() & bits_CAP_DUCK) && m_bIsCrouching ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::Stand( void ) +{ + if ( m_bForceCrouch ) + return false; + + m_bIsCrouching = false; + DesireStand(); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::DesireCrouch( void ) +{ + m_bCrouchDesired = true; +} + +bool CAI_BaseNPC::IsInChoreo() const +{ + return m_bInChoreo; +} diff --git a/dlls/ai_basenpc_physicsflyer.cpp b/dlls/ai_basenpc_physicsflyer.cpp index d26ab945..2b8230b4 100644 --- a/dlls/ai_basenpc_physicsflyer.cpp +++ b/dlls/ai_basenpc_physicsflyer.cpp @@ -336,8 +336,6 @@ void CAI_BasePhysicsFlyingBot::TranslateNavGoal( CBaseEntity *pTarget, Vector &c //----------------------------------------------------------------------------- IMotionEvent::simresult_e CAI_BasePhysicsFlyingBot::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) { - static int count; - IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); // Assert( pPhysicsObject ); if (!pPhysicsObject) diff --git a/dlls/ai_basenpc_schedule.cpp b/dlls/ai_basenpc_schedule.cpp index cccb5701..04f66130 100644 --- a/dlls/ai_basenpc_schedule.cpp +++ b/dlls/ai_basenpc_schedule.cpp @@ -1132,7 +1132,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) { if (!GetHintNode()) { - SetHintNode( CAI_HintManager::FindHint( this, HINT_NONE, pTask->flTaskData, 2000 ) ); + SetHintNode( CAI_HintManager::FindHint( this, HINT_NONE, static_cast(pTask->flTaskData), 2000 ) ); } if (GetHintNode()) { @@ -1330,7 +1330,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) break; case TASK_SET_SCHEDULE: - if ( !SetSchedule( pTask->flTaskData ) ) + if ( !SetSchedule( static_cast(pTask->flTaskData) ) ) TaskFail(FAIL_SCHEDULE_NOT_FOUND); break; @@ -2537,7 +2537,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) } case TASK_SPEAK_SENTENCE: { - SpeakSentence(pTask->flTaskData); + SpeakSentence(static_cast(pTask->flTaskData)); TaskComplete(); break; } @@ -2842,7 +2842,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) { int iMinDist, iMaxDist, iParameter; - iParameter = pTask->flTaskData; + iParameter = static_cast(pTask->flTaskData); iMinDist = iParameter / 10000; iMaxDist = iParameter - (iMinDist * 10000); diff --git a/dlls/ai_behavior.h b/dlls/ai_behavior.h index 851c2afa..113dd856 100644 --- a/dlls/ai_behavior.h +++ b/dlls/ai_behavior.h @@ -15,11 +15,15 @@ #include "networkvar.h" #ifdef DEBUG +#ifdef _MSC_VER #pragma warning(push) +#endif #include +#ifdef _MSC_VER #pragma warning(pop) #pragma warning(disable:4290) #endif +#endif #if defined( _WIN32 ) #pragma once @@ -262,28 +266,28 @@ public: { if ( condition >= ID_SPACE_OFFSET && condition < ID_SPACE_OFFSET + 10000 ) // it's local to us condition = GetClassScheduleIdSpace()->ConditionLocalToGlobal( condition ); - GetOuter()->SetCondition( condition ); + this->GetOuter()->SetCondition( condition ); } bool HasCondition( int condition ) { if ( condition >= ID_SPACE_OFFSET && condition < ID_SPACE_OFFSET + 10000 ) // it's local to us condition = GetClassScheduleIdSpace()->ConditionLocalToGlobal( condition ); - return GetOuter()->HasCondition( condition ); + return this->GetOuter()->HasCondition( condition ); } bool HasInterruptCondition( int condition ) { if ( condition >= ID_SPACE_OFFSET && condition < ID_SPACE_OFFSET + 10000 ) // it's local to us condition = GetClassScheduleIdSpace()->ConditionLocalToGlobal( condition ); - return GetOuter()->HasInterruptCondition( condition ); + return this->GetOuter()->HasInterruptCondition( condition ); } void ClearCondition( int condition ) { if ( condition >= ID_SPACE_OFFSET && condition < ID_SPACE_OFFSET + 10000 ) // it's local to us condition = GetClassScheduleIdSpace()->ConditionLocalToGlobal( condition ); - GetOuter()->ClearCondition( condition ); + this->GetOuter()->ClearCondition( condition ); } protected: @@ -298,7 +302,7 @@ protected: } virtual CAI_ClassScheduleIdSpace *GetClassScheduleIdSpace() { - return GetOuter()->GetClassScheduleIdSpace(); + return this->GetOuter()->GetClassScheduleIdSpace(); } static CAI_ClassScheduleIdSpace &AccessClassScheduleIdSpaceDirect() @@ -968,7 +972,7 @@ inline void CAI_BehaviorHost::ChangeBehaviorTo( CAI_BehaviorBase *pNew if ( pOldBehavior ) { pOldBehavior->EndScheduleSelection(); - VacateStrategySlot(); + this->VacateStrategySlot(); } OnChangeRunningBehavior( pOldBehavior, pNewBehavior ); diff --git a/dlls/ai_behavior_assault.cpp b/dlls/ai_behavior_assault.cpp index bcff8f9b..6dae7492 100644 --- a/dlls/ai_behavior_assault.cpp +++ b/dlls/ai_behavior_assault.cpp @@ -169,7 +169,7 @@ void CAI_AssaultBehavior::ClearAssaultPoint( void ) } else { - DevMsg("**ERROR: Can't find next assault point: %s\n", m_hAssaultPoint->m_NextAssaultPointName ); + DevMsg("**ERROR: Can't find next assault point: %s\n", STRING(m_hAssaultPoint->m_NextAssaultPointName) ); // Bomb out of assault behavior. m_AssaultCue = CUE_NO_ASSAULT; @@ -785,7 +785,7 @@ void CAI_AssaultBehavior::SetParameters( string_t rallypointname, AssaultCue_t a if( !pBest ) { - DevMsg("%s Didn't find a best rally point!\n", GetOuter()->GetEntityName() ); + DevMsg("%s Didn't find a best rally point!\n", STRING(GetOuter()->GetEntityName()) ); return; } @@ -887,6 +887,8 @@ bool CAI_AssaultBehavior::PollAssaultCue( void ) // Player told me to go, so go! return m_ReceivedAssaultCue == CUE_COMMANDER; break; + default: + break; } return false; diff --git a/dlls/ai_behavior_follow.cpp b/dlls/ai_behavior_follow.cpp index 7f8dfba2..7a8a4fca 100644 --- a/dlls/ai_behavior_follow.cpp +++ b/dlls/ai_behavior_follow.cpp @@ -2289,7 +2289,7 @@ AI_FollowFormation_t *AIGetFormation( AI_Formations_t formation ) { if ( formation < 0 ) formation = (AI_Formations_t)0; - else if ( formation >= ARRAYSIZE( g_AI_Formations ) ) + else if ( static_cast(formation) >= ARRAYSIZE( g_AI_Formations ) ) formation = (AI_Formations_t)(ARRAYSIZE( g_AI_Formations ) - 1 ); return g_AI_Formations[formation]; diff --git a/dlls/ai_behavior_passenger.cpp b/dlls/ai_behavior_passenger.cpp index b593db5c..82340bed 100644 --- a/dlls/ai_behavior_passenger.cpp +++ b/dlls/ai_behavior_passenger.cpp @@ -51,10 +51,10 @@ END_DATADESC(); //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- -CAI_PassengerBehavior::CAI_PassengerBehavior( void ) : -m_bEnabled( false ), -m_hVehicle( NULL ), +CAI_PassengerBehavior::CAI_PassengerBehavior( void ) : m_PassengerState( PASSENGER_STATE_OUTSIDE ), +m_hVehicle( NULL ), +m_bEnabled( false ), m_PassengerIntent( PASSENGER_INTENT_NONE ), m_nTransitionSequence( -1 ) { diff --git a/dlls/ai_behavior_rappel.cpp b/dlls/ai_behavior_rappel.cpp index 727ec639..6120fbbb 100644 --- a/dlls/ai_behavior_rappel.cpp +++ b/dlls/ai_behavior_rappel.cpp @@ -59,7 +59,7 @@ void CRopeAnchor::Spawn() flDist = fabs( GetOwnerEntity()->GetAbsOrigin().z - GetAbsOrigin().z ); } - m_hRope = CRopeKeyframe::CreateWithSecondPointDetached( this, -1, flDist, RAPPEL_ROPE_WIDTH, "cable/cable.vmt", 5, true ); + m_hRope = CRopeKeyframe::CreateWithSecondPointDetached( this, -1, static_cast(flDist), RAPPEL_ROPE_WIDTH, "cable/cable.vmt", 5, true ); ASSERT( m_hRope != NULL ); diff --git a/dlls/ai_behavior_standoff.cpp b/dlls/ai_behavior_standoff.cpp index 84361449..2037f840 100644 --- a/dlls/ai_behavior_standoff.cpp +++ b/dlls/ai_behavior_standoff.cpp @@ -1110,7 +1110,7 @@ void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap() CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon(); const char *pszWeaponClass = ( pWeapon ) ? pWeapon->GetClassname() : ""; - for ( int i = 0; i < ARRAYSIZE(mappings); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(mappings); i++ ) { if ( !mappings[i].pszWeapon || stricmp( mappings[i].pszWeapon, pszWeaponClass ) == 0 ) { diff --git a/dlls/ai_blended_movement.cpp b/dlls/ai_blended_movement.cpp index 87fe135a..87810768 100644 --- a/dlls/ai_blended_movement.cpp +++ b/dlls/ai_blended_movement.cpp @@ -961,7 +961,7 @@ int CAI_BlendedMotor::BuildInsertNode( int i, float flTime ) Assert( flTime > 0.0 ); - for (i; i < m_scriptTurn.Count() - 1; i++) + for (; i < m_scriptTurn.Count() - 1; i++) { if (m_scriptTurn[i].flTime < flTime) { @@ -1203,12 +1203,8 @@ void CAI_BlendedMotor::BuildVelocityScript( const AILocalMoveGoal_t &move ) script.flMaxVelocity = 0; } break; - /* - case NAV_FLY: - // FIXME: can there be a NAV_GROUND -> NAV_FLY transition? - script.flMaxVelocity = 0; + default: break; - */ } } else diff --git a/dlls/ai_blended_movement.h b/dlls/ai_blended_movement.h index dbb8d17a..a02611a9 100644 --- a/dlls/ai_blended_movement.h +++ b/dlls/ai_blended_movement.h @@ -212,8 +212,8 @@ class CAI_BlendingHost : public BASE_NPC { DECLARE_CLASS_NOFRIEND( CAI_BlendingHost, BASE_NPC ); public: - const CAI_BlendedMotor *GetBlendedMotor() const { return assert_cast(GetMotor()); } - CAI_BlendedMotor * GetBlendedMotor() { return assert_cast(GetMotor()); } + const CAI_BlendedMotor *GetBlendedMotor() const { return assert_cast(this->GetMotor()); } + CAI_BlendedMotor * GetBlendedMotor() { return assert_cast(this->GetMotor()); } CAI_Motor *CreateMotor() { @@ -230,7 +230,7 @@ public: float MaxYawSpeed( void ) { - float override = GetBlendedMotor()->OverrideMaxYawSpeed( GetActivity() ); + float override = GetBlendedMotor()->OverrideMaxYawSpeed( this->GetActivity() ); if ( override != -1 ) return override; return BaseClass::MaxYawSpeed(); diff --git a/dlls/ai_default.cpp b/dlls/ai_default.cpp index bba29993..2d3e7cb2 100644 --- a/dlls/ai_default.cpp +++ b/dlls/ai_default.cpp @@ -329,6 +329,8 @@ int CAI_BaseNPC::TranslateSchedule( int scheduleType ) return SCHED_ALERT_WALK; case NPC_STATE_COMBAT: return SCHED_COMBAT_WALK; + default: + break; } } break; diff --git a/dlls/ai_dynamiclink.h b/dlls/ai_dynamiclink.h index 01ad7fa0..9ab21fb4 100644 --- a/dlls/ai_dynamiclink.h +++ b/dlls/ai_dynamiclink.h @@ -1,121 +1,121 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: A link that can be turned on and off. Unlike normal links -// dyanimc links must be entities so they and receive messages. -// They update the state of the actual links. Allows us to save -// a lot of memory by not making all links into entities -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef AI_DYNAMICLINK_H -#define AI_DYNAMICLINK_H -#pragma once - -enum DynamicLinkState_t -{ - LINK_OFF = 0, - LINK_ON = 1, -}; - -class CAI_Link; - -//============================================================================= -// >> CAI_DynanicLink -//============================================================================= -class CAI_DynamicLink : public CServerOnlyEntity -{ - DECLARE_CLASS( CAI_DynamicLink, CServerOnlyEntity ); -public: - static void InitDynamicLinks(void); - static void ResetDynamicLinks(void); - static void PurgeDynamicLinks(void); - static void GenerateControllerLinks(); - - static bool gm_bInitialized; - - static CAI_DynamicLink* GetDynamicLink(int nSrcID, int nDstID); - - static CAI_DynamicLink* m_pAllDynamicLinks; // A linked list of all dynamic link - CAI_DynamicLink* m_pNextDynamicLink; // The next dynamic link in the list of dynamic links - - int m_nSrcEditID; // the node that 'owns' this link - int m_nDestEditID; // the node on the other end of the link. - - int m_nSrcID; // the node that 'owns' this link - int m_nDestID; // the node on the other end of the link. - DynamicLinkState_t m_nLinkState; // - string_t m_strAllowUse; - - bool m_bFixedUpIds; - bool m_bNotSaved; - int m_nLinkType; - - void SetLinkState( void ); - bool IsLinkValid( void ); - - CAI_Link * FindLink(); - - int ObjectCaps(); - - // ---------------- - // Inputs - // ---------------- - void InputTurnOn( inputdata_t &inputdata ); - void InputTurnOff( inputdata_t &inputdata ); - - DECLARE_DATADESC(); - - CAI_DynamicLink(); - ~CAI_DynamicLink(); -}; - -//============================================================================= -// >> CAI_DynanicLinkVolume -//============================================================================= -class CAI_DynamicLinkController : public CServerOnlyEntity -{ - DECLARE_CLASS( CAI_DynamicLinkController, CServerOnlyEntity ); -public: - void GenerateLinksFromVolume(); - - // ---------------- - // Inputs - // ---------------- - void InputTurnOn( inputdata_t &inputdata ); - void InputTurnOff( inputdata_t &inputdata ); - - CUtlVector< CHandle > m_ControlledLinks; - DynamicLinkState_t m_nLinkState; // - string_t m_strAllowUse; - - DECLARE_DATADESC(); -}; - -//============================================================================= -//============================================================================= -class CAI_RadialLinkController : public CBaseEntity -{ - DECLARE_CLASS( CAI_RadialLinkController, CBaseEntity ); - -public: - void Spawn(); - void Activate(); - void PollMotionThink(); - void ModifyNodeLinks( bool bMakeStale ); - -public: - float m_flRadius; - Vector m_vecAtRestOrigin; - bool m_bAtRest; - - DECLARE_DATADESC(); -}; - -#endif // AI_DYNAMICLINK_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: A link that can be turned on and off. Unlike normal links +// dyanimc links must be entities so they and receive messages. +// They update the state of the actual links. Allows us to save +// a lot of memory by not making all links into entities +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef AI_DYNAMICLINK_H +#define AI_DYNAMICLINK_H +#pragma once + +enum DynamicLinkState_t +{ + LINK_OFF = 0, + LINK_ON = 1, +}; + +class CAI_Link; + +//============================================================================= +// >> CAI_DynanicLink +//============================================================================= +class CAI_DynamicLink : public CServerOnlyEntity +{ + DECLARE_CLASS( CAI_DynamicLink, CServerOnlyEntity ); +public: + static void InitDynamicLinks(void); + static void ResetDynamicLinks(void); + static void PurgeDynamicLinks(void); + static void GenerateControllerLinks(); + + static bool gm_bInitialized; + + static CAI_DynamicLink* GetDynamicLink(int nSrcID, int nDstID); + + static CAI_DynamicLink* m_pAllDynamicLinks; // A linked list of all dynamic link + CAI_DynamicLink* m_pNextDynamicLink; // The next dynamic link in the list of dynamic links + + int m_nSrcEditID; // the node that 'owns' this link + int m_nDestEditID; // the node on the other end of the link. + + int m_nSrcID; // the node that 'owns' this link + int m_nDestID; // the node on the other end of the link. + DynamicLinkState_t m_nLinkState; // + string_t m_strAllowUse; + + bool m_bFixedUpIds; + bool m_bNotSaved; + int m_nLinkType; + + void SetLinkState( void ); + bool IsLinkValid( void ); + + CAI_Link * FindLink(); + + int ObjectCaps(); + + // ---------------- + // Inputs + // ---------------- + void InputTurnOn( inputdata_t &inputdata ); + void InputTurnOff( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + + CAI_DynamicLink(); + ~CAI_DynamicLink(); +}; + +//============================================================================= +// >> CAI_DynanicLinkVolume +//============================================================================= +class CAI_DynamicLinkController : public CServerOnlyEntity +{ + DECLARE_CLASS( CAI_DynamicLinkController, CServerOnlyEntity ); +public: + void GenerateLinksFromVolume(); + + // ---------------- + // Inputs + // ---------------- + void InputTurnOn( inputdata_t &inputdata ); + void InputTurnOff( inputdata_t &inputdata ); + + CUtlVector< CHandle > m_ControlledLinks; + DynamicLinkState_t m_nLinkState; // + string_t m_strAllowUse; + + DECLARE_DATADESC(); +}; + +//============================================================================= +//============================================================================= +class CAI_RadialLinkController : public CBaseEntity +{ + DECLARE_CLASS( CAI_RadialLinkController, CBaseEntity ); + +public: + void Spawn(); + void Activate(); + void PollMotionThink(); + void ModifyNodeLinks( bool bMakeStale ); + +public: + float m_flRadius; + Vector m_vecAtRestOrigin; + bool m_bAtRest; + + DECLARE_DATADESC(); +}; + +#endif // AI_DYNAMICLINK_H diff --git a/dlls/ai_eventresponse.h b/dlls/ai_eventresponse.h index 590ce001..f5091cc5 100644 --- a/dlls/ai_eventresponse.h +++ b/dlls/ai_eventresponse.h @@ -1,47 +1,47 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: AI system that makes NPCs verbally respond to game events -// -//=============================================================================// - -#ifndef AI_EVENTRESPONSE_H -#define AI_EVENTRESPONSE_H - -#include "utldict.h" - -#define NPCEVENTRESPONSE_DISTANCE_SQR (768 * 768) // Maximum distance for responding to NPCs -#define NPCEVENTRESPONSE_REFIRE_TIME 15.0 // Time after giving a response before giving any more -#define NPCEVENTRESPONSE_GIVEUP_TIME 4.0 // Time after a response trigger was fired before discarding it without responding - -//----------------------------------------------------------------------------- -// Purpose: AI system that makes NPCs verbally respond to game events -//----------------------------------------------------------------------------- -class CNPCEventResponseSystem : public CAutoGameSystemPerFrame -{ -public: - CNPCEventResponseSystem( char const *name ) : CAutoGameSystemPerFrame( name ) - { - } - - void LevelInitPreEntity(); - void FrameUpdatePreEntityThink(); - void TriggerEvent( const char *pResponse, bool bForce = false ); - -private: - float m_flNextEventPoll; - - struct storedevent_t - { - float flEventTime; - float flNextResponseTime; - bool bForce; - bool bPreventExpiration; - }; - - typedef CUtlDict< storedevent_t, int > EventMap; - EventMap m_ActiveEvents; -}; - -CNPCEventResponseSystem *NPCEventResponse(); - -#endif // AI_EVENTRESPONSE_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: AI system that makes NPCs verbally respond to game events +// +//=============================================================================// + +#ifndef AI_EVENTRESPONSE_H +#define AI_EVENTRESPONSE_H + +#include "utldict.h" + +#define NPCEVENTRESPONSE_DISTANCE_SQR (768 * 768) // Maximum distance for responding to NPCs +#define NPCEVENTRESPONSE_REFIRE_TIME 15.0 // Time after giving a response before giving any more +#define NPCEVENTRESPONSE_GIVEUP_TIME 4.0 // Time after a response trigger was fired before discarding it without responding + +//----------------------------------------------------------------------------- +// Purpose: AI system that makes NPCs verbally respond to game events +//----------------------------------------------------------------------------- +class CNPCEventResponseSystem : public CAutoGameSystemPerFrame +{ +public: + CNPCEventResponseSystem( char const *name ) : CAutoGameSystemPerFrame( name ) + { + } + + void LevelInitPreEntity(); + void FrameUpdatePreEntityThink(); + void TriggerEvent( const char *pResponse, bool bForce = false ); + +private: + float m_flNextEventPoll; + + struct storedevent_t + { + float flEventTime; + float flNextResponseTime; + bool bForce; + bool bPreventExpiration; + }; + + typedef CUtlDict< storedevent_t, int > EventMap; + EventMap m_ActiveEvents; +}; + +CNPCEventResponseSystem *NPCEventResponse(); + +#endif // AI_EVENTRESPONSE_H diff --git a/dlls/ai_hint.cpp b/dlls/ai_hint.cpp index dc555c70..f2f9d84c 100644 --- a/dlls/ai_hint.cpp +++ b/dlls/ai_hint.cpp @@ -311,7 +311,7 @@ int CAI_HintManager::FindAllHints( CAI_BaseNPC *pNPC, const Vector &position, co // If we have no hints, bail int c = CAI_HintManager::gm_AllHints.Count(); if ( !c ) - return NULL; + return 0; // Remove the nearest flag. It makes now sense with random. bool hadNearest = hintCriteria.HasFlag( bits_HINT_NODE_NEAREST ); @@ -682,7 +682,7 @@ int CAI_HintManager::GetFlags( const char *token ) char *lowercase = (char *)_alloca( len + 1 ); Q_strncpy( lowercase, token, len+1 ); - strlwr( lowercase ); + Q_strlower( lowercase ); if ( strstr( "none", lowercase ) ) { @@ -1003,6 +1003,8 @@ bool CAI_Hint::IsViewable(void) { case HINT_WORLD_VISUALLY_INTERESTING: return true; + default: + break; } return false; @@ -1551,7 +1553,7 @@ hinttypedescs_t g_pszHintDescriptions[] = //----------------------------------------------------------------------------- const char *GetHintTypeDescription( Hint_e iHintType ) { - for ( int i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ ) { if ( g_pszHintDescriptions[i].iType == iHintType ) return g_pszHintDescriptions[i].pszDesc; @@ -1581,7 +1583,7 @@ void CC_ai_drop_hint( void ) { Msg("Invalid hint type specified. Format: ai_drop_hint \nValid hint types:\n"); - for ( int i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ ) { Msg("%d : %s\n", g_pszHintDescriptions[i].iType, g_pszHintDescriptions[i].pszDesc ); } diff --git a/dlls/ai_hull.cpp b/dlls/ai_hull.cpp index 0ea99010..febc3f2e 100644 --- a/dlls/ai_hull.cpp +++ b/dlls/ai_hull.cpp @@ -11,7 +11,7 @@ struct ai_hull_t { ai_hull_t( int bit, const char *pName, const Vector &_mins, const Vector &_maxs, const Vector &_smallMins, const Vector &_smallMaxs ) - : hullBit( bit ), mins( _mins ), maxs( _maxs ), smallMins( _smallMins ), smallMaxs( _smallMaxs ), name( pName ) {} + : hullBit( bit ), name( pName ), mins( _mins ), maxs( _maxs ), smallMins( _smallMins ), smallMaxs( _smallMaxs ) {} int hullBit; const char* name; diff --git a/dlls/ai_hull.h b/dlls/ai_hull.h index 1173d62a..fa53e3b3 100644 --- a/dlls/ai_hull.h +++ b/dlls/ai_hull.h @@ -1,75 +1,75 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -//=============================================================================// - -#ifndef AI_HULL_H -#define AI_HULL_H -#pragma once - -class Vector; -//========================================================= -// Link Properties. These hulls must correspond to the hulls -// in AI_Hull.cpp! -//========================================================= -enum Hull_t -{ - HULL_HUMAN, // Combine, Stalker, Zombie... - HULL_SMALL_CENTERED, // Scanner - HULL_WIDE_HUMAN, // Vortigaunt - HULL_TINY, // Headcrab - HULL_WIDE_SHORT, // Bullsquid - HULL_MEDIUM, // Cremator - HULL_TINY_CENTERED, // Manhack - HULL_LARGE, // Antlion Guard - HULL_LARGE_CENTERED, // Mortar Synth - HULL_MEDIUM_TALL, // Ministrider -//-------------------------------------------- - NUM_HULLS, - HULL_NONE // No Hull (appears after num hulls as we don't want to count it) -}; - -enum Hull_Bits_t -{ - bits_HUMAN_HULL = 0x00000001, - bits_SMALL_CENTERED_HULL = 0x00000002, - bits_WIDE_HUMAN_HULL = 0x00000004, - bits_TINY_HULL = 0x00000008, - bits_WIDE_SHORT_HULL = 0x00000010, - bits_MEDIUM_HULL = 0x00000020, - bits_TINY_CENTERED_HULL = 0x00000040, - bits_LARGE_HULL = 0x00000080, - bits_LARGE_CENTERED_HULL = 0x00000100, - bits_MEDIUM_TALL_HULL = 0x00000200, - bits_HULL_BITS_MASK = 0x000002ff, -}; - -inline int HullToBit( Hull_t hull ) -{ - return ( 1 << hull ); -} - - - -//============================================================================= -// >> CAI_Hull -//============================================================================= -namespace NAI_Hull -{ - const Vector &Mins(int id); - const Vector &Maxs(int id); - - const Vector &SmallMins(int id); - const Vector &SmallMaxs(int id); - - float Length(int id); - float Width(int id); - float Height(int id); - - int Bits(int id); - - const char* Name(int id); - - Hull_t LookupId(const char *szName); -}; - -#endif // AI_HULL_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +//=============================================================================// + +#ifndef AI_HULL_H +#define AI_HULL_H +#pragma once + +class Vector; +//========================================================= +// Link Properties. These hulls must correspond to the hulls +// in AI_Hull.cpp! +//========================================================= +enum Hull_t +{ + HULL_HUMAN, // Combine, Stalker, Zombie... + HULL_SMALL_CENTERED, // Scanner + HULL_WIDE_HUMAN, // Vortigaunt + HULL_TINY, // Headcrab + HULL_WIDE_SHORT, // Bullsquid + HULL_MEDIUM, // Cremator + HULL_TINY_CENTERED, // Manhack + HULL_LARGE, // Antlion Guard + HULL_LARGE_CENTERED, // Mortar Synth + HULL_MEDIUM_TALL, // Ministrider +//-------------------------------------------- + NUM_HULLS, + HULL_NONE // No Hull (appears after num hulls as we don't want to count it) +}; + +enum Hull_Bits_t +{ + bits_HUMAN_HULL = 0x00000001, + bits_SMALL_CENTERED_HULL = 0x00000002, + bits_WIDE_HUMAN_HULL = 0x00000004, + bits_TINY_HULL = 0x00000008, + bits_WIDE_SHORT_HULL = 0x00000010, + bits_MEDIUM_HULL = 0x00000020, + bits_TINY_CENTERED_HULL = 0x00000040, + bits_LARGE_HULL = 0x00000080, + bits_LARGE_CENTERED_HULL = 0x00000100, + bits_MEDIUM_TALL_HULL = 0x00000200, + bits_HULL_BITS_MASK = 0x000002ff, +}; + +inline int HullToBit( Hull_t hull ) +{ + return ( 1 << hull ); +} + + + +//============================================================================= +// >> CAI_Hull +//============================================================================= +namespace NAI_Hull +{ + const Vector &Mins(int id); + const Vector &Maxs(int id); + + const Vector &SmallMins(int id); + const Vector &SmallMaxs(int id); + + float Length(int id); + float Width(int id); + float Height(int id); + + int Bits(int id); + + const char* Name(int id); + + Hull_t LookupId(const char *szName); +}; + +#endif // AI_HULL_H diff --git a/dlls/ai_looktarget.h b/dlls/ai_looktarget.h index eff2c3e7..d4093c90 100644 --- a/dlls/ai_looktarget.h +++ b/dlls/ai_looktarget.h @@ -1,47 +1,47 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// -#pragma once -#ifndef AI_LOOKTARGET_H -#define AI_LOOKTARGET_H - -#define SF_LOOKTARGET_ONLYONCE 0x00000001 - -//============================================================================= -//============================================================================= -class CAI_LookTarget : public CPointEntity -{ -public: - DECLARE_CLASS( CAI_LookTarget, CPointEntity ); - DECLARE_DATADESC(); - - CAI_LookTarget() { m_flTimeNextAvailable = -1; } - - // Debugging - int DrawDebugTextOverlays(void); - - // Accessors & Availability - bool IsEligible( CBaseEntity *pLooker ); - bool IsEnabled() { return !m_bDisabled; } - bool IsAvailable() { return (gpGlobals->curtime > m_flTimeNextAvailable); } - void Reserve( float flDuration ); - - // Searching - static CAI_LookTarget *GetFirstLookTarget(); - static CAI_LookTarget *GetNextLookTarget( CAI_LookTarget *pCurrentTarget ); - - int m_iContext; - int m_iPriority; - - void Enable() { m_bDisabled = false; } - void Disable() { m_bDisabled = true; } - -private: - bool m_bDisabled; - float m_flTimeNextAvailable; - float m_flMaxDist; -}; - -#endif//AI_LOOKTARGET_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#pragma once +#ifndef AI_LOOKTARGET_H +#define AI_LOOKTARGET_H + +#define SF_LOOKTARGET_ONLYONCE 0x00000001 + +//============================================================================= +//============================================================================= +class CAI_LookTarget : public CPointEntity +{ +public: + DECLARE_CLASS( CAI_LookTarget, CPointEntity ); + DECLARE_DATADESC(); + + CAI_LookTarget() { m_flTimeNextAvailable = -1; } + + // Debugging + int DrawDebugTextOverlays(void); + + // Accessors & Availability + bool IsEligible( CBaseEntity *pLooker ); + bool IsEnabled() { return !m_bDisabled; } + bool IsAvailable() { return (gpGlobals->curtime > m_flTimeNextAvailable); } + void Reserve( float flDuration ); + + // Searching + static CAI_LookTarget *GetFirstLookTarget(); + static CAI_LookTarget *GetNextLookTarget( CAI_LookTarget *pCurrentTarget ); + + int m_iContext; + int m_iPriority; + + void Enable() { m_bDisabled = false; } + void Disable() { m_bDisabled = true; } + +private: + bool m_bDisabled; + float m_flTimeNextAvailable; + float m_flMaxDist; +}; + +#endif//AI_LOOKTARGET_H diff --git a/dlls/ai_memory.cpp b/dlls/ai_memory.cpp index 2e4898d1..8f1f912c 100644 --- a/dlls/ai_memory.cpp +++ b/dlls/ai_memory.cpp @@ -1,642 +1,642 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: An NPC's memory of potential enemies -// -//=============================================================================// - -#include "cbase.h" -#include "isaverestore.h" -#include "ai_debug.h" -#include "ai_memory.h" -#include "ai_basenpc.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define EMEMORY_POOL_SIZE 64 -#define AI_FREE_KNOWLEDGE_DURATION 1.75 - -//----------------------------------------------------------------------------- -// AI_EnemyInfo_t -// -//----------------------------------------------------------------------------- - -DEFINE_FIXEDSIZE_ALLOCATOR( AI_EnemyInfo_t, EMEMORY_POOL_SIZE, CMemoryPool::GROW_FAST ); - -//----------------------------------------------------------------------------- - -AI_EnemyInfo_t::AI_EnemyInfo_t(void) -{ - hEnemy = NULL; - vLastKnownLocation = vec3_origin; - vLastSeenLocation = vec3_origin; - timeLastSeen = 0; - timeFirstSeen = 0; - timeLastReacquired = 0; - timeValidEnemy = 0; - timeLastReceivedDamageFrom = 0; - timeAtFirstHand = AI_INVALID_TIME; - bDangerMemory = 0; - bEludedMe = 0; - bUnforgettable = 0; - bMobbedMe = 0; -} - - -//----------------------------------------------------------------------------- -// CAI_EnemiesListSaveRestoreOps -// -// Purpose: Handles save and load for enemy memories -// -//----------------------------------------------------------------------------- - -class CAI_EnemiesListSaveRestoreOps : public CDefSaveRestoreOps -{ -public: - CAI_EnemiesListSaveRestoreOps() - { - } - - virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) - { - CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; - - int nMemories = pMemMap->Count(); - pSave->WriteInt( &nMemories ); - - for ( CAI_Enemies::CMemMap::IndexType_t i = pMemMap->FirstInorder(); i != pMemMap->InvalidIndex(); i = pMemMap->NextInorder( i ) ) - { - pSave->WriteAll( (*pMemMap)[i] ); - } - } - - virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) - { - CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; - Assert( pMemMap->Count() == 0 ); - - int nMemories = pRestore->ReadInt(); - - while ( nMemories-- ) - { - AI_EnemyInfo_t *pAddMemory = new AI_EnemyInfo_t; - - pRestore->ReadAll( pAddMemory ); - - if ( pAddMemory->hEnemy != NULL ) - { - pMemMap->Insert( pAddMemory->hEnemy, pAddMemory ); - } - else - delete pAddMemory; - } - } - - virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) - { - CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; - - for ( CAI_Enemies::CMemMap::IndexType_t i = pMemMap->FirstInorder(); i != pMemMap->InvalidIndex(); i = pMemMap->NextInorder( i ) ) - { - delete (*pMemMap)[i]; - } - - pMemMap->RemoveAll(); - } - - virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) - { - CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; - return ( pMemMap->Count() == 0 ); - } - -} g_AI_MemoryListSaveRestoreOps; - -//----------------------------------------------------------------------------- -// CAI_Enemies -// -// Purpose: Stores a set of AI_EnemyInfo_t's -// -//----------------------------------------------------------------------------- - -BEGIN_SIMPLE_DATADESC( CAI_Enemies ) - - DEFINE_CUSTOM_FIELD( m_Map, &g_AI_MemoryListSaveRestoreOps ), - DEFINE_FIELD( m_flFreeKnowledgeDuration, FIELD_FLOAT ), - DEFINE_FIELD( m_flEnemyDiscardTime, FIELD_FLOAT ), - DEFINE_FIELD( m_vecDefaultLKP, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vecDefaultLSP, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_serial, FIELD_INTEGER ), - -END_DATADESC() - -BEGIN_SIMPLE_DATADESC( AI_EnemyInfo_t ) - DEFINE_FIELD( vLastKnownLocation, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( vLastSeenLocation, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( hEnemy, FIELD_EHANDLE ), - DEFINE_FIELD( timeLastSeen, FIELD_TIME ), - DEFINE_FIELD( timeFirstSeen, FIELD_TIME ), - DEFINE_FIELD( timeLastReacquired, FIELD_TIME ), - DEFINE_FIELD( timeValidEnemy, FIELD_TIME ), - DEFINE_FIELD( timeLastReceivedDamageFrom, FIELD_TIME ), - DEFINE_FIELD( timeAtFirstHand, FIELD_TIME ), - DEFINE_FIELD( bDangerMemory, FIELD_BOOLEAN ), - DEFINE_FIELD( bEludedMe, FIELD_BOOLEAN ), - DEFINE_FIELD( bUnforgettable, FIELD_BOOLEAN ), - DEFINE_FIELD( bMobbedMe, FIELD_BOOLEAN ), - // NOT SAVED nextEMemory -END_DATADESC() - -//----------------------------------------------------------------------------- - -CAI_Enemies::CAI_Enemies(void) -{ - m_flFreeKnowledgeDuration = AI_FREE_KNOWLEDGE_DURATION; - m_flEnemyDiscardTime = AI_DEF_ENEMY_DISCARD_TIME; - m_vecDefaultLKP = vec3_invalid; - m_vecDefaultLSP = vec3_invalid; - m_serial = 0; - SetDefLessFunc( m_Map ); -} - - -//----------------------------------------------------------------------------- - -CAI_Enemies::~CAI_Enemies() -{ - for ( CMemMap::IndexType_t i = m_Map.FirstInorder(); i != m_Map.InvalidIndex(); i = m_Map.NextInorder( i ) ) - { - delete m_Map[i]; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Purges any dead enemies from memory -//----------------------------------------------------------------------------- - -AI_EnemyInfo_t *CAI_Enemies::GetFirst( AIEnemiesIter_t *pIter ) -{ - CMemMap::IndexType_t i = m_Map.FirstInorder(); - *pIter = (AIEnemiesIter_t)(unsigned)i; - - if ( i == m_Map.InvalidIndex() ) - return NULL; - - if ( m_Map[i]->hEnemy == NULL ) - return GetNext( pIter ); - - return m_Map[i]; -} - -//----------------------------------------------------------------------------- - -AI_EnemyInfo_t *CAI_Enemies::GetNext( AIEnemiesIter_t *pIter ) -{ - CMemMap::IndexType_t i = (CMemMap::IndexType_t)((unsigned)(*pIter)); - - if ( i == m_Map.InvalidIndex() ) - return NULL; - - i = m_Map.NextInorder( i ); - *pIter = (AIEnemiesIter_t)(unsigned)i; - if ( i == m_Map.InvalidIndex() ) - return NULL; - - if ( m_Map[i]->hEnemy == NULL ) - return GetNext( pIter ); - - return m_Map[i]; -} - -//----------------------------------------------------------------------------- - -AI_EnemyInfo_t *CAI_Enemies::Find( CBaseEntity *pEntity, bool bTryDangerMemory ) -{ - if ( pEntity == AI_UNKNOWN_ENEMY ) - pEntity = NULL; - - CMemMap::IndexType_t i = m_Map.Find( pEntity ); - if ( i == m_Map.InvalidIndex() ) - { - if ( !bTryDangerMemory || ( i = m_Map.Find( NULL ) ) == m_Map.InvalidIndex() ) - return NULL; - Assert(m_Map[i]->bDangerMemory == true); - } - return m_Map[i]; -} - - -//----------------------------------------------------------------------------- - -AI_EnemyInfo_t *CAI_Enemies::GetDangerMemory() -{ - CMemMap::IndexType_t i = m_Map.Find( NULL ); - if ( i == m_Map.InvalidIndex() ) - return NULL; - Assert(m_Map[i]->bDangerMemory == true); - return m_Map[i]; -} - -//----------------------------------------------------------------------------- - -bool CAI_Enemies::ShouldDiscardMemory( AI_EnemyInfo_t *pMemory ) -{ - CBaseEntity *pEnemy = pMemory->hEnemy; - - if ( pEnemy ) - { - CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer(); - if ( pEnemyNPC && pEnemyNPC->GetState() == NPC_STATE_DEAD ) - return true; - } - else - { - if ( !pMemory->bDangerMemory ) - return true; - } - - if ( !pMemory->bUnforgettable && - gpGlobals->curtime > pMemory->timeLastSeen + m_flEnemyDiscardTime ) - { - return true; - } - - return false; -} - - -//----------------------------------------------------------------------------- - -void CAI_Enemies::RefreshMemories(void) -{ - AI_PROFILE_SCOPE(CAI_Enemies_RefreshMemories); - - // ------------------- - // Check each record - // ------------------- - - CMemMap::IndexType_t i = m_Map.FirstInorder(); - while ( i != m_Map.InvalidIndex() ) - { - AI_EnemyInfo_t *pMemory = m_Map[i]; - - CMemMap::IndexType_t iNext = m_Map.NextInorder( i ); // save so can remove - if ( ShouldDiscardMemory( pMemory ) ) - { - delete pMemory; - m_Map.RemoveAt(i); - } - else if ( pMemory->hEnemy ) - { - if ( gpGlobals->curtime <= pMemory->timeLastSeen + m_flFreeKnowledgeDuration ) - { - // Free knowledge is ignored if the target has notarget on - if ( !(pMemory->hEnemy->GetFlags() & FL_NOTARGET) ) - { - pMemory->vLastKnownLocation = pMemory->hEnemy->GetAbsOrigin(); - } - } - - if ( gpGlobals->curtime <= pMemory->timeLastSeen ) - { - pMemory->vLastSeenLocation = pMemory->hEnemy->GetAbsOrigin(); - } - } - i = iNext; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Updates information about our enemies -// Output : Returns true if new enemy, false if already know of enemy -//----------------------------------------------------------------------------- - -bool CAI_Enemies::UpdateMemory(CAI_Network* pAINet, CBaseEntity *pEnemy, const Vector &vPosition, float reactionDelay, bool firstHand ) -{ - if ( pEnemy == AI_UNKNOWN_ENEMY ) - pEnemy = NULL; - - const float DIST_TRIGGER_REACQUIRE_SQ = Square(20.0 * 12.0); - const float TIME_TRIGGER_REACQUIRE = 4.0; - const float MIN_DIST_TIME_TRIGGER_REACQUIRE_SQ = Square(4.0 * 12.0); - - AI_EnemyInfo_t *pMemory = Find( pEnemy ); - // ------------------------------------------- - // Otherwise just update my own - // ------------------------------------------- - // Update enemy information - if ( pMemory ) - { - Assert(pEnemy || pMemory->bDangerMemory == true); - - if ( firstHand ) - pMemory->timeLastSeen = gpGlobals->curtime; - pMemory->bEludedMe = false; - - float deltaDist = (pMemory->vLastKnownLocation - vPosition).LengthSqr(); - - if (deltaDist>DIST_TRIGGER_REACQUIRE_SQ || ( deltaDist>MIN_DIST_TIME_TRIGGER_REACQUIRE_SQ && ( gpGlobals->curtime - pMemory->timeLastSeen ) > TIME_TRIGGER_REACQUIRE ) ) - { - pMemory->timeLastReacquired = gpGlobals->curtime; - } - - // Only update if the enemy has moved - if (deltaDist>Square(12.0)) - { - pMemory->vLastKnownLocation = vPosition; - - } - - // Update the time at which we first saw him firsthand - if ( firstHand && pMemory->timeAtFirstHand == AI_INVALID_TIME ) - { - pMemory->timeAtFirstHand = gpGlobals->curtime; - } - - return false; - } - - // If not on my list of enemies add it - AI_EnemyInfo_t *pAddMemory = new AI_EnemyInfo_t; - pAddMemory->vLastKnownLocation = vPosition; - - if ( firstHand ) - { - pAddMemory->timeLastReacquired = pAddMemory->timeFirstSeen = pAddMemory->timeLastSeen = pAddMemory->timeAtFirstHand = gpGlobals->curtime; - } - else - { - // Block free knowledge - pAddMemory->timeLastReacquired = pAddMemory->timeFirstSeen = pAddMemory->timeLastSeen = ( gpGlobals->curtime - (m_flFreeKnowledgeDuration + 0.01) ); - pAddMemory->timeAtFirstHand = AI_INVALID_TIME; - } - - if ( reactionDelay > 0.0 ) - pAddMemory->timeValidEnemy = gpGlobals->curtime + reactionDelay; - - pAddMemory->bEludedMe = false; - - // I'm either remembering a postion of an enmey of just a danger position - pAddMemory->hEnemy = pEnemy; - pAddMemory->bDangerMemory = ( pEnemy == NULL ); - - // add to the list - m_Map.Insert( pEnemy, pAddMemory ); - m_serial++; - - return true; -} - -//------------------------------------------------------------------------------ -// Purpose : Returns true if this enemy is part of my memory -//------------------------------------------------------------------------------ -void CAI_Enemies::OnTookDamageFrom( CBaseEntity *pEnemy ) -{ - AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); - if ( pMemory ) - pMemory->timeLastReceivedDamageFrom = gpGlobals->curtime; -} - -//------------------------------------------------------------------------------ -// Purpose : Returns true if this enemy is part of my memory -//------------------------------------------------------------------------------ -bool CAI_Enemies::HasMemory( CBaseEntity *pEnemy ) -{ - return ( Find( pEnemy ) != NULL ); -} - -//----------------------------------------------------------------------------- -// Purpose: Clear information about our enemy -//----------------------------------------------------------------------------- -void CAI_Enemies::ClearMemory(CBaseEntity *pEnemy) -{ - CMemMap::IndexType_t i = m_Map.Find( pEnemy ); - if ( i != m_Map.InvalidIndex() ) - { - delete m_Map[i]; - m_Map.RemoveAt( i ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Notes that the given enemy has eluded me -//----------------------------------------------------------------------------- -void CAI_Enemies::MarkAsEluded( CBaseEntity *pEnemy ) -{ - AI_EnemyInfo_t *pMemory = Find( pEnemy ); - if ( pMemory ) - { - pMemory->bEludedMe = true; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Returns last known posiiton of given enemy -//----------------------------------------------------------------------------- -const Vector &CAI_Enemies::LastKnownPosition( CBaseEntity *pEnemy ) -{ - AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); - if ( pMemory ) - { - m_vecDefaultLKP = pMemory->vLastKnownLocation; - } - else - { - DevWarning( 2,"Asking LastKnownPosition for enemy that's not in my memory!!\n"); - } - return m_vecDefaultLKP; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the last position the enemy was SEEN at. This will always be -// different than LastKnownPosition() when the enemy is out of sight, because -// the last KNOWN position will be updated for a number of seconds after the -// player disappears. -//----------------------------------------------------------------------------- -const Vector &CAI_Enemies::LastSeenPosition( CBaseEntity *pEnemy ) -{ - AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); - if ( pMemory ) - { - m_vecDefaultLSP = pMemory->vLastSeenLocation; - } - else - { - DevWarning( 2,"Asking LastSeenPosition for enemy that's not in my memory!!\n"); - } - return m_vecDefaultLSP; -} - -float CAI_Enemies::TimeLastReacquired( CBaseEntity *pEnemy ) -{ - // I've never seen something that doesn't exist - if (!pEnemy) - return 0; - - AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); - if ( pMemory ) - return pMemory->timeLastReacquired; - - if ( pEnemy != AI_UNKNOWN_ENEMY ) - DevWarning( 2,"Asking TimeLastReacquired for enemy that's not in my memory!!\n"); - return AI_INVALID_TIME; -} - -//----------------------------------------------------------------------------- -// Purpose: Sets position to the last known position of an enemy. If enemy -// was not found returns last memory of danger position if it exists -// Output : Returns false is no position is known -//----------------------------------------------------------------------------- -float CAI_Enemies::LastTimeSeen( CBaseEntity *pEnemy, bool bCheckDangerMemory /*= true*/ ) -{ - // I've never seen something that doesn't exist - if (!pEnemy) - return 0; - - AI_EnemyInfo_t *pMemory = Find( pEnemy, bCheckDangerMemory ); - if ( pMemory ) - return pMemory->timeLastSeen; - - if ( pEnemy != AI_UNKNOWN_ENEMY ) - DevWarning( 2,"Asking LastTimeSeen for enemy that's not in my memory!!\n"); - return AI_INVALID_TIME; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the time at which the enemy was first seen. -// Output : Returns false is no position is known -//----------------------------------------------------------------------------- -float CAI_Enemies::FirstTimeSeen( CBaseEntity *pEnemy) -{ - // I've never seen something that doesn't exist - if (!pEnemy) - return 0; - - AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); - if ( pMemory ) - return pMemory->timeFirstSeen; - - if ( pEnemy != AI_UNKNOWN_ENEMY ) - DevWarning( 2,"Asking FirstTimeSeen for enemy that's not in my memory!!\n"); - return AI_INVALID_TIME; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEnemy - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CAI_Enemies::HasFreeKnowledgeOf( CBaseEntity *pEnemy ) -{ - // I've never seen something that doesn't exist - if (!pEnemy) - return 0; - - AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); - if ( pMemory ) - { - float flFreeKnowledgeTime = pMemory->timeLastSeen + m_flFreeKnowledgeDuration; - return ( gpGlobals->curtime < flFreeKnowledgeTime ); - } - - if ( pEnemy != AI_UNKNOWN_ENEMY ) - DevWarning( 2,"Asking HasFreeKnowledgeOf for enemy that's not in my memory!!\n"); - return AI_INVALID_TIME; -} - -//----------------------------------------------------------------------------- -float CAI_Enemies::LastTimeTookDamageFrom( CBaseEntity *pEnemy) -{ - // I've never seen something that doesn't exist - if (!pEnemy) - return 0; - - AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); - if ( pMemory ) - return pMemory->timeLastReceivedDamageFrom; - - if ( pEnemy != AI_UNKNOWN_ENEMY ) - DevWarning( 2,"Asking LastTimeTookDamageFrom for enemy that's not in my memory!!\n"); - return AI_INVALID_TIME; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the time at which the enemy was first seen firsthand -// Input : *pEnemy - -// Output : float -//----------------------------------------------------------------------------- -float CAI_Enemies::TimeAtFirstHand( CBaseEntity *pEnemy ) -{ - // I've never seen something that doesn't exist - if (!pEnemy) - return 0; - - AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); - if ( pMemory ) - return pMemory->timeAtFirstHand; - - if ( pEnemy != AI_UNKNOWN_ENEMY ) - DevWarning( 2,"Asking TimeAtFirstHand for enemy that's not in my memory!!\n"); - return AI_INVALID_TIME; -} - -//----------------------------------------------------------------------------- -// Purpose: Sets position to the last known position of an enemy. If enemy -// was not found returns last memory of danger position if it exists -// Output : Returns false is no position is known -//----------------------------------------------------------------------------- -bool CAI_Enemies::HasEludedMe( CBaseEntity *pEnemy ) -{ - AI_EnemyInfo_t *pMemory = Find( pEnemy ); - if ( pMemory ) - return pMemory->bEludedMe; - return false; -} - -void CAI_Enemies::SetTimeValidEnemy( CBaseEntity *pEnemy, float flTime ) -{ - AI_EnemyInfo_t *pMemory = Find( pEnemy ); - if ( pMemory ) - pMemory->timeValidEnemy = flTime; -} - -//----------------------------------------------------------------------------- -void CAI_Enemies::SetUnforgettable( CBaseEntity *pEnemy, bool bUnforgettable ) -{ - AI_EnemyInfo_t *pMemory = Find( pEnemy ); - if ( pMemory ) - pMemory->bUnforgettable = bUnforgettable; -} - -//----------------------------------------------------------------------------- -void CAI_Enemies::SetMobbedMe( CBaseEntity *pEnemy, bool bMobbedMe ) -{ - AI_EnemyInfo_t *pMemory = Find( pEnemy ); - if ( pMemory ) - pMemory->bMobbedMe = bMobbedMe; -} - -//----------------------------------------------------------------------------- - -void CAI_Enemies::SetFreeKnowledgeDuration( float flDuration ) -{ - m_flFreeKnowledgeDuration = flDuration; - - // If your free knowledge time is greater than your discard time, - // you'll forget about secondhand enemies passed to you by squadmates - // as soon as you're given them. - Assert( m_flFreeKnowledgeDuration < m_flEnemyDiscardTime ); -} - -//----------------------------------------------------------------------------- - -void CAI_Enemies::SetEnemyDiscardTime( float flTime ) -{ - m_flEnemyDiscardTime = flTime; - - // If your free knowledge time is greater than your discard time, - // you'll forget about secondhand enemies passed to you by squadmates - // as soon as you're given them. - Assert( m_flFreeKnowledgeDuration < m_flEnemyDiscardTime ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: An NPC's memory of potential enemies +// +//=============================================================================// + +#include "cbase.h" +#include "isaverestore.h" +#include "ai_debug.h" +#include "ai_memory.h" +#include "ai_basenpc.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define EMEMORY_POOL_SIZE 64 +#define AI_FREE_KNOWLEDGE_DURATION 1.75 + +//----------------------------------------------------------------------------- +// AI_EnemyInfo_t +// +//----------------------------------------------------------------------------- + +DEFINE_FIXEDSIZE_ALLOCATOR( AI_EnemyInfo_t, EMEMORY_POOL_SIZE, CMemoryPool::GROW_FAST ); + +//----------------------------------------------------------------------------- + +AI_EnemyInfo_t::AI_EnemyInfo_t(void) +{ + hEnemy = NULL; + vLastKnownLocation = vec3_origin; + vLastSeenLocation = vec3_origin; + timeLastSeen = 0; + timeFirstSeen = 0; + timeLastReacquired = 0; + timeValidEnemy = 0; + timeLastReceivedDamageFrom = 0; + timeAtFirstHand = AI_INVALID_TIME; + bDangerMemory = 0; + bEludedMe = 0; + bUnforgettable = 0; + bMobbedMe = 0; +} + + +//----------------------------------------------------------------------------- +// CAI_EnemiesListSaveRestoreOps +// +// Purpose: Handles save and load for enemy memories +// +//----------------------------------------------------------------------------- + +class CAI_EnemiesListSaveRestoreOps : public CDefSaveRestoreOps +{ +public: + CAI_EnemiesListSaveRestoreOps() + { + } + + virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) + { + CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; + + int nMemories = pMemMap->Count(); + pSave->WriteInt( &nMemories ); + + for ( CAI_Enemies::CMemMap::IndexType_t i = pMemMap->FirstInorder(); i != pMemMap->InvalidIndex(); i = pMemMap->NextInorder( i ) ) + { + pSave->WriteAll( (*pMemMap)[i] ); + } + } + + virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) + { + CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; + Assert( pMemMap->Count() == 0 ); + + int nMemories = pRestore->ReadInt(); + + while ( nMemories-- ) + { + AI_EnemyInfo_t *pAddMemory = new AI_EnemyInfo_t; + + pRestore->ReadAll( pAddMemory ); + + if ( pAddMemory->hEnemy != NULL ) + { + pMemMap->Insert( pAddMemory->hEnemy, pAddMemory ); + } + else + delete pAddMemory; + } + } + + virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) + { + CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; + + for ( CAI_Enemies::CMemMap::IndexType_t i = pMemMap->FirstInorder(); i != pMemMap->InvalidIndex(); i = pMemMap->NextInorder( i ) ) + { + delete (*pMemMap)[i]; + } + + pMemMap->RemoveAll(); + } + + virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) + { + CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; + return ( pMemMap->Count() == 0 ); + } + +} g_AI_MemoryListSaveRestoreOps; + +//----------------------------------------------------------------------------- +// CAI_Enemies +// +// Purpose: Stores a set of AI_EnemyInfo_t's +// +//----------------------------------------------------------------------------- + +BEGIN_SIMPLE_DATADESC( CAI_Enemies ) + + DEFINE_CUSTOM_FIELD( m_Map, &g_AI_MemoryListSaveRestoreOps ), + DEFINE_FIELD( m_flFreeKnowledgeDuration, FIELD_FLOAT ), + DEFINE_FIELD( m_flEnemyDiscardTime, FIELD_FLOAT ), + DEFINE_FIELD( m_vecDefaultLKP, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecDefaultLSP, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_serial, FIELD_INTEGER ), + +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( AI_EnemyInfo_t ) + DEFINE_FIELD( vLastKnownLocation, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( vLastSeenLocation, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( hEnemy, FIELD_EHANDLE ), + DEFINE_FIELD( timeLastSeen, FIELD_TIME ), + DEFINE_FIELD( timeFirstSeen, FIELD_TIME ), + DEFINE_FIELD( timeLastReacquired, FIELD_TIME ), + DEFINE_FIELD( timeValidEnemy, FIELD_TIME ), + DEFINE_FIELD( timeLastReceivedDamageFrom, FIELD_TIME ), + DEFINE_FIELD( timeAtFirstHand, FIELD_TIME ), + DEFINE_FIELD( bDangerMemory, FIELD_BOOLEAN ), + DEFINE_FIELD( bEludedMe, FIELD_BOOLEAN ), + DEFINE_FIELD( bUnforgettable, FIELD_BOOLEAN ), + DEFINE_FIELD( bMobbedMe, FIELD_BOOLEAN ), + // NOT SAVED nextEMemory +END_DATADESC() + +//----------------------------------------------------------------------------- + +CAI_Enemies::CAI_Enemies(void) +{ + m_flFreeKnowledgeDuration = AI_FREE_KNOWLEDGE_DURATION; + m_flEnemyDiscardTime = AI_DEF_ENEMY_DISCARD_TIME; + m_vecDefaultLKP = vec3_invalid; + m_vecDefaultLSP = vec3_invalid; + m_serial = 0; + SetDefLessFunc( m_Map ); +} + + +//----------------------------------------------------------------------------- + +CAI_Enemies::~CAI_Enemies() +{ + for ( CMemMap::IndexType_t i = m_Map.FirstInorder(); i != m_Map.InvalidIndex(); i = m_Map.NextInorder( i ) ) + { + delete m_Map[i]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Purges any dead enemies from memory +//----------------------------------------------------------------------------- + +AI_EnemyInfo_t *CAI_Enemies::GetFirst( AIEnemiesIter_t *pIter ) +{ + CMemMap::IndexType_t i = m_Map.FirstInorder(); + *pIter = (AIEnemiesIter_t)(unsigned)i; + + if ( i == m_Map.InvalidIndex() ) + return NULL; + + if ( m_Map[i]->hEnemy == NULL ) + return GetNext( pIter ); + + return m_Map[i]; +} + +//----------------------------------------------------------------------------- + +AI_EnemyInfo_t *CAI_Enemies::GetNext( AIEnemiesIter_t *pIter ) +{ + CMemMap::IndexType_t i = (CMemMap::IndexType_t)((unsigned)(*pIter)); + + if ( i == m_Map.InvalidIndex() ) + return NULL; + + i = m_Map.NextInorder( i ); + *pIter = (AIEnemiesIter_t)(unsigned)i; + if ( i == m_Map.InvalidIndex() ) + return NULL; + + if ( m_Map[i]->hEnemy == NULL ) + return GetNext( pIter ); + + return m_Map[i]; +} + +//----------------------------------------------------------------------------- + +AI_EnemyInfo_t *CAI_Enemies::Find( CBaseEntity *pEntity, bool bTryDangerMemory ) +{ + if ( pEntity == AI_UNKNOWN_ENEMY ) + pEntity = NULL; + + CMemMap::IndexType_t i = m_Map.Find( pEntity ); + if ( i == m_Map.InvalidIndex() ) + { + if ( !bTryDangerMemory || ( i = m_Map.Find( NULL ) ) == m_Map.InvalidIndex() ) + return NULL; + Assert(m_Map[i]->bDangerMemory == true); + } + return m_Map[i]; +} + + +//----------------------------------------------------------------------------- + +AI_EnemyInfo_t *CAI_Enemies::GetDangerMemory() +{ + CMemMap::IndexType_t i = m_Map.Find( NULL ); + if ( i == m_Map.InvalidIndex() ) + return NULL; + Assert(m_Map[i]->bDangerMemory == true); + return m_Map[i]; +} + +//----------------------------------------------------------------------------- + +bool CAI_Enemies::ShouldDiscardMemory( AI_EnemyInfo_t *pMemory ) +{ + CBaseEntity *pEnemy = pMemory->hEnemy; + + if ( pEnemy ) + { + CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer(); + if ( pEnemyNPC && pEnemyNPC->GetState() == NPC_STATE_DEAD ) + return true; + } + else + { + if ( !pMemory->bDangerMemory ) + return true; + } + + if ( !pMemory->bUnforgettable && + gpGlobals->curtime > pMemory->timeLastSeen + m_flEnemyDiscardTime ) + { + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- + +void CAI_Enemies::RefreshMemories(void) +{ + AI_PROFILE_SCOPE(CAI_Enemies_RefreshMemories); + + // ------------------- + // Check each record + // ------------------- + + CMemMap::IndexType_t i = m_Map.FirstInorder(); + while ( i != m_Map.InvalidIndex() ) + { + AI_EnemyInfo_t *pMemory = m_Map[i]; + + CMemMap::IndexType_t iNext = m_Map.NextInorder( i ); // save so can remove + if ( ShouldDiscardMemory( pMemory ) ) + { + delete pMemory; + m_Map.RemoveAt(i); + } + else if ( pMemory->hEnemy ) + { + if ( gpGlobals->curtime <= pMemory->timeLastSeen + m_flFreeKnowledgeDuration ) + { + // Free knowledge is ignored if the target has notarget on + if ( !(pMemory->hEnemy->GetFlags() & FL_NOTARGET) ) + { + pMemory->vLastKnownLocation = pMemory->hEnemy->GetAbsOrigin(); + } + } + + if ( gpGlobals->curtime <= pMemory->timeLastSeen ) + { + pMemory->vLastSeenLocation = pMemory->hEnemy->GetAbsOrigin(); + } + } + i = iNext; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates information about our enemies +// Output : Returns true if new enemy, false if already know of enemy +//----------------------------------------------------------------------------- + +bool CAI_Enemies::UpdateMemory(CAI_Network* pAINet, CBaseEntity *pEnemy, const Vector &vPosition, float reactionDelay, bool firstHand ) +{ + if ( pEnemy == AI_UNKNOWN_ENEMY ) + pEnemy = NULL; + + const float DIST_TRIGGER_REACQUIRE_SQ = Square(20.0 * 12.0); + const float TIME_TRIGGER_REACQUIRE = 4.0; + const float MIN_DIST_TIME_TRIGGER_REACQUIRE_SQ = Square(4.0 * 12.0); + + AI_EnemyInfo_t *pMemory = Find( pEnemy ); + // ------------------------------------------- + // Otherwise just update my own + // ------------------------------------------- + // Update enemy information + if ( pMemory ) + { + Assert(pEnemy || pMemory->bDangerMemory == true); + + if ( firstHand ) + pMemory->timeLastSeen = gpGlobals->curtime; + pMemory->bEludedMe = false; + + float deltaDist = (pMemory->vLastKnownLocation - vPosition).LengthSqr(); + + if (deltaDist>DIST_TRIGGER_REACQUIRE_SQ || ( deltaDist>MIN_DIST_TIME_TRIGGER_REACQUIRE_SQ && ( gpGlobals->curtime - pMemory->timeLastSeen ) > TIME_TRIGGER_REACQUIRE ) ) + { + pMemory->timeLastReacquired = gpGlobals->curtime; + } + + // Only update if the enemy has moved + if (deltaDist>Square(12.0)) + { + pMemory->vLastKnownLocation = vPosition; + + } + + // Update the time at which we first saw him firsthand + if ( firstHand && pMemory->timeAtFirstHand == AI_INVALID_TIME ) + { + pMemory->timeAtFirstHand = gpGlobals->curtime; + } + + return false; + } + + // If not on my list of enemies add it + AI_EnemyInfo_t *pAddMemory = new AI_EnemyInfo_t; + pAddMemory->vLastKnownLocation = vPosition; + + if ( firstHand ) + { + pAddMemory->timeLastReacquired = pAddMemory->timeFirstSeen = pAddMemory->timeLastSeen = pAddMemory->timeAtFirstHand = gpGlobals->curtime; + } + else + { + // Block free knowledge + pAddMemory->timeLastReacquired = pAddMemory->timeFirstSeen = pAddMemory->timeLastSeen = ( gpGlobals->curtime - (m_flFreeKnowledgeDuration + 0.01) ); + pAddMemory->timeAtFirstHand = AI_INVALID_TIME; + } + + if ( reactionDelay > 0.0 ) + pAddMemory->timeValidEnemy = gpGlobals->curtime + reactionDelay; + + pAddMemory->bEludedMe = false; + + // I'm either remembering a postion of an enmey of just a danger position + pAddMemory->hEnemy = pEnemy; + pAddMemory->bDangerMemory = ( pEnemy == NULL ); + + // add to the list + m_Map.Insert( pEnemy, pAddMemory ); + m_serial++; + + return true; +} + +//------------------------------------------------------------------------------ +// Purpose : Returns true if this enemy is part of my memory +//------------------------------------------------------------------------------ +void CAI_Enemies::OnTookDamageFrom( CBaseEntity *pEnemy ) +{ + AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); + if ( pMemory ) + pMemory->timeLastReceivedDamageFrom = gpGlobals->curtime; +} + +//------------------------------------------------------------------------------ +// Purpose : Returns true if this enemy is part of my memory +//------------------------------------------------------------------------------ +bool CAI_Enemies::HasMemory( CBaseEntity *pEnemy ) +{ + return ( Find( pEnemy ) != NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Clear information about our enemy +//----------------------------------------------------------------------------- +void CAI_Enemies::ClearMemory(CBaseEntity *pEnemy) +{ + CMemMap::IndexType_t i = m_Map.Find( pEnemy ); + if ( i != m_Map.InvalidIndex() ) + { + delete m_Map[i]; + m_Map.RemoveAt( i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Notes that the given enemy has eluded me +//----------------------------------------------------------------------------- +void CAI_Enemies::MarkAsEluded( CBaseEntity *pEnemy ) +{ + AI_EnemyInfo_t *pMemory = Find( pEnemy ); + if ( pMemory ) + { + pMemory->bEludedMe = true; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns last known posiiton of given enemy +//----------------------------------------------------------------------------- +const Vector &CAI_Enemies::LastKnownPosition( CBaseEntity *pEnemy ) +{ + AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); + if ( pMemory ) + { + m_vecDefaultLKP = pMemory->vLastKnownLocation; + } + else + { + DevWarning( 2,"Asking LastKnownPosition for enemy that's not in my memory!!\n"); + } + return m_vecDefaultLKP; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the last position the enemy was SEEN at. This will always be +// different than LastKnownPosition() when the enemy is out of sight, because +// the last KNOWN position will be updated for a number of seconds after the +// player disappears. +//----------------------------------------------------------------------------- +const Vector &CAI_Enemies::LastSeenPosition( CBaseEntity *pEnemy ) +{ + AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); + if ( pMemory ) + { + m_vecDefaultLSP = pMemory->vLastSeenLocation; + } + else + { + DevWarning( 2,"Asking LastSeenPosition for enemy that's not in my memory!!\n"); + } + return m_vecDefaultLSP; +} + +float CAI_Enemies::TimeLastReacquired( CBaseEntity *pEnemy ) +{ + // I've never seen something that doesn't exist + if (!pEnemy) + return 0; + + AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); + if ( pMemory ) + return pMemory->timeLastReacquired; + + if ( pEnemy != AI_UNKNOWN_ENEMY ) + DevWarning( 2,"Asking TimeLastReacquired for enemy that's not in my memory!!\n"); + return AI_INVALID_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets position to the last known position of an enemy. If enemy +// was not found returns last memory of danger position if it exists +// Output : Returns false is no position is known +//----------------------------------------------------------------------------- +float CAI_Enemies::LastTimeSeen( CBaseEntity *pEnemy, bool bCheckDangerMemory /*= true*/ ) +{ + // I've never seen something that doesn't exist + if (!pEnemy) + return 0; + + AI_EnemyInfo_t *pMemory = Find( pEnemy, bCheckDangerMemory ); + if ( pMemory ) + return pMemory->timeLastSeen; + + if ( pEnemy != AI_UNKNOWN_ENEMY ) + DevWarning( 2,"Asking LastTimeSeen for enemy that's not in my memory!!\n"); + return AI_INVALID_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the time at which the enemy was first seen. +// Output : Returns false is no position is known +//----------------------------------------------------------------------------- +float CAI_Enemies::FirstTimeSeen( CBaseEntity *pEnemy) +{ + // I've never seen something that doesn't exist + if (!pEnemy) + return 0; + + AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); + if ( pMemory ) + return pMemory->timeFirstSeen; + + if ( pEnemy != AI_UNKNOWN_ENEMY ) + DevWarning( 2,"Asking FirstTimeSeen for enemy that's not in my memory!!\n"); + return AI_INVALID_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEnemy - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_Enemies::HasFreeKnowledgeOf( CBaseEntity *pEnemy ) +{ + // I've never seen something that doesn't exist + if (!pEnemy) + return 0; + + AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); + if ( pMemory ) + { + float flFreeKnowledgeTime = pMemory->timeLastSeen + m_flFreeKnowledgeDuration; + return ( gpGlobals->curtime < flFreeKnowledgeTime ); + } + + if ( pEnemy != AI_UNKNOWN_ENEMY ) + DevWarning( 2,"Asking HasFreeKnowledgeOf for enemy that's not in my memory!!\n"); + return AI_INVALID_TIME; +} + +//----------------------------------------------------------------------------- +float CAI_Enemies::LastTimeTookDamageFrom( CBaseEntity *pEnemy) +{ + // I've never seen something that doesn't exist + if (!pEnemy) + return 0; + + AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); + if ( pMemory ) + return pMemory->timeLastReceivedDamageFrom; + + if ( pEnemy != AI_UNKNOWN_ENEMY ) + DevWarning( 2,"Asking LastTimeTookDamageFrom for enemy that's not in my memory!!\n"); + return AI_INVALID_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the time at which the enemy was first seen firsthand +// Input : *pEnemy - +// Output : float +//----------------------------------------------------------------------------- +float CAI_Enemies::TimeAtFirstHand( CBaseEntity *pEnemy ) +{ + // I've never seen something that doesn't exist + if (!pEnemy) + return 0; + + AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); + if ( pMemory ) + return pMemory->timeAtFirstHand; + + if ( pEnemy != AI_UNKNOWN_ENEMY ) + DevWarning( 2,"Asking TimeAtFirstHand for enemy that's not in my memory!!\n"); + return AI_INVALID_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets position to the last known position of an enemy. If enemy +// was not found returns last memory of danger position if it exists +// Output : Returns false is no position is known +//----------------------------------------------------------------------------- +bool CAI_Enemies::HasEludedMe( CBaseEntity *pEnemy ) +{ + AI_EnemyInfo_t *pMemory = Find( pEnemy ); + if ( pMemory ) + return pMemory->bEludedMe; + return false; +} + +void CAI_Enemies::SetTimeValidEnemy( CBaseEntity *pEnemy, float flTime ) +{ + AI_EnemyInfo_t *pMemory = Find( pEnemy ); + if ( pMemory ) + pMemory->timeValidEnemy = flTime; +} + +//----------------------------------------------------------------------------- +void CAI_Enemies::SetUnforgettable( CBaseEntity *pEnemy, bool bUnforgettable ) +{ + AI_EnemyInfo_t *pMemory = Find( pEnemy ); + if ( pMemory ) + pMemory->bUnforgettable = bUnforgettable; +} + +//----------------------------------------------------------------------------- +void CAI_Enemies::SetMobbedMe( CBaseEntity *pEnemy, bool bMobbedMe ) +{ + AI_EnemyInfo_t *pMemory = Find( pEnemy ); + if ( pMemory ) + pMemory->bMobbedMe = bMobbedMe; +} + +//----------------------------------------------------------------------------- + +void CAI_Enemies::SetFreeKnowledgeDuration( float flDuration ) +{ + m_flFreeKnowledgeDuration = flDuration; + + // If your free knowledge time is greater than your discard time, + // you'll forget about secondhand enemies passed to you by squadmates + // as soon as you're given them. + Assert( m_flFreeKnowledgeDuration < m_flEnemyDiscardTime ); +} + +//----------------------------------------------------------------------------- + +void CAI_Enemies::SetEnemyDiscardTime( float flTime ) +{ + m_flEnemyDiscardTime = flTime; + + // If your free knowledge time is greater than your discard time, + // you'll forget about secondhand enemies passed to you by squadmates + // as soon as you're given them. + Assert( m_flFreeKnowledgeDuration < m_flEnemyDiscardTime ); +} diff --git a/dlls/ai_memory.h b/dlls/ai_memory.h index be67523e..7c3dc3c2 100644 --- a/dlls/ai_memory.h +++ b/dlls/ai_memory.h @@ -118,3 +118,4 @@ private: //----------------------------------------------------------------------------- #endif // AI_MEMORY_H + diff --git a/dlls/ai_motor.cpp b/dlls/ai_motor.cpp index dc359925..e769116c 100644 --- a/dlls/ai_motor.cpp +++ b/dlls/ai_motor.cpp @@ -34,14 +34,19 @@ void DebugNoteMovementFailure() } // a place to put breakpoints +#ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4189) +#endif + AIMoveResult_t DbgResult( AIMoveResult_t result ) { +#ifdef _MSC_VER if ( result < AIMR_OK ) { int breakHere = 1; } +#endif switch ( result ) { @@ -750,7 +755,7 @@ void CAI_Motor::UpdateYaw( int yawSpeed ) float ideal, current, newYaw; if ( yawSpeed == -1 ) - yawSpeed = GetYawSpeed(); + yawSpeed = static_cast(GetYawSpeed()); // NOTE: GetIdealYaw() will never exactly be reached because UTIL_AngleMod // also truncates the angle to 16 bits of resolution. So lets truncate it here. @@ -908,7 +913,7 @@ AIMoveResult_t CAI_Motor::MoveNormalExecute( const AILocalMoveGoal_t &move ) AIMR_BLOCKED_WORLD, // AIM_PARTIAL_HIT_WORLD AIMR_BLOCKED_WORLD, // AIM_PARTIAL_HIT_TARGET }; - Assert( ARRAYSIZE( moveResults ) == AIM_NUM_RESULTS && fMotorResult >= 0 && fMotorResult <= ARRAYSIZE( moveResults ) ); + Assert( (AIMotorMoveResult_t)ARRAYSIZE( moveResults ) == AIM_NUM_RESULTS && fMotorResult >= 0 && fMotorResult <= (AIMotorMoveResult_t)ARRAYSIZE( moveResults ) ); AIMoveResult_t result = moveResults[fMotorResult]; @@ -1015,4 +1020,3 @@ void CAI_Motor::SetMoveType( MoveType_t val, MoveCollide_t moveCollide ) } //============================================================================= - diff --git a/dlls/ai_motor.h b/dlls/ai_motor.h index 7adfbad6..7e088abc 100644 --- a/dlls/ai_motor.h +++ b/dlls/ai_motor.h @@ -220,3 +220,4 @@ public: //============================================================================= #endif // AI_MOTOR_H + diff --git a/dlls/ai_moveprobe.h b/dlls/ai_moveprobe.h index 829abba3..0d66da36 100644 --- a/dlls/ai_moveprobe.h +++ b/dlls/ai_moveprobe.h @@ -154,3 +154,4 @@ inline bool CAI_MoveProbe::TestGroundMove( const Vector &vecActualStart, const V } #endif // AI_MOVEPROBE_H + diff --git a/dlls/ai_moveshoot.cpp b/dlls/ai_moveshoot.cpp index 001cb639..47064c34 100644 --- a/dlls/ai_moveshoot.cpp +++ b/dlls/ai_moveshoot.cpp @@ -138,6 +138,8 @@ void CAI_MoveAndShootOverlay::UpdateMoveShootActivity( bool bMoveAimAtEnemy ) case ACT_RUN: newActivity = ACT_RUN_AIM; break; + default: + break; } } else @@ -150,6 +152,8 @@ void CAI_MoveAndShootOverlay::UpdateMoveShootActivity( bool bMoveAimAtEnemy ) case ACT_RUN_AIM: newActivity = ACT_RUN; break; + default: + break; } } diff --git a/dlls/ai_movesolver.cpp b/dlls/ai_movesolver.cpp index f231f097..7b641075 100644 --- a/dlls/ai_movesolver.cpp +++ b/dlls/ai_movesolver.cpp @@ -114,7 +114,7 @@ bool CAI_MoveSolver::Solve( const AI_MoveSuggestion_t *pSuggestions, int nSugges AI_MoveSuggestion_t *pHighSuggestion; }; - Solution_t solutions[NUM_SOLUTIONS] = { 0 }; + Solution_t solutions[NUM_SOLUTIONS] = { {0.0f, 0.0f, NULL} }; //--------------------------------- @@ -133,15 +133,15 @@ bool CAI_MoveSolver::Solve( const AI_MoveSuggestion_t *pSuggestions, int nSugges // Convert arc values to solution indices relative to right post. Right is angle down, left is angle up. float halfSpan = current.arc.span * 0.5; - int center = round( ( halfSpan * NUM_SOLUTIONS ) / 360 ); - int left = ( current.arc.span * NUM_SOLUTIONS ) / 360; + int center = static_cast(round( ( halfSpan * NUM_SOLUTIONS ) / 360 )); + int left = static_cast(( current.arc.span * NUM_SOLUTIONS ) / 360); float angRight = current.arc.center - halfSpan; if (angRight < 0.0) angRight += 360; - int base = ( angRight * NUM_SOLUTIONS ) / 360; + int base = static_cast(( angRight * NUM_SOLUTIONS ) / 360); // Sweep from left to right, summing the bias. For positive suggestions, // the bias is further weighted to favor the center of the arc. diff --git a/dlls/ai_namespaces.cpp b/dlls/ai_namespaces.cpp index 7e8b6a9d..b58835ed 100644 --- a/dlls/ai_namespaces.cpp +++ b/dlls/ai_namespaces.cpp @@ -90,12 +90,12 @@ int CAI_GlobalNamespace::NextGlobalBase() const // CAI_LocalIdSpace::CAI_LocalIdSpace( bool fIsRoot ) - : m_pGlobalNamespace( NULL ), - m_pParentIDSpace( NULL ), - m_globalBase( (fIsRoot) ? 0 : -1 ), + : m_globalBase( (fIsRoot) ? 0 : -1 ), m_localBase( (fIsRoot) ? 0 : MAX_STRING_INDEX ), m_localTop( -1 ), - m_globalTop( -1 ) + m_globalTop( -1 ), + m_pParentIDSpace( NULL ), + m_pGlobalNamespace( NULL ) { }; diff --git a/dlls/ai_navigator.cpp b/dlls/ai_navigator.cpp index 39ba2fba..24526fe2 100644 --- a/dlls/ai_navigator.cpp +++ b/dlls/ai_navigator.cpp @@ -1116,7 +1116,6 @@ float CAI_Navigator::GetPathTimeToGoal() AI_PathNode_t CAI_Navigator::GetNearestNode() { - COMPILE_TIME_ASSERT( (int)AIN_NO_NODE == NO_NODE ); return (AI_PathNode_t)( GetPathfinder()->NearestNodeToNPC() ); } @@ -2159,6 +2158,8 @@ bool CAI_Navigator::PreMove() GetMotor()->MoveJumpStop(); break; } + default: + break; } SetNavType( NAV_GROUND ); @@ -2364,6 +2365,8 @@ bool CAI_Navigator::Move( float flInterval ) case NAV_FLY: OnMoveBlocked( &moveResult ); break; + default: + break; } break; } @@ -3625,6 +3628,8 @@ bool CAI_Navigator::DoFindPath( void ) } } break; + default: + break; } return returnCode; @@ -3927,18 +3932,18 @@ void CAI_Navigator::DrawDebugRouteOverlay(void) if (waypoint) { Vector RGB = GetRouteColor(waypoint->NavType(), waypoint->Flags()); - NDebugOverlay::Line(GetLocalOrigin(), waypoint->GetPos(), RGB[0],RGB[1],RGB[2], true,0); + NDebugOverlay::Line(GetLocalOrigin(), waypoint->GetPos(), static_cast(RGB[0]),static_cast(RGB[1]),static_cast(RGB[2]), true,0); } while (waypoint) { Vector RGB = GetWaypointColor(waypoint->NavType()); - NDebugOverlay::Box(waypoint->GetPos(), Vector(-3,-3,-3),Vector(3,3,3), RGB[0],RGB[1],RGB[2], true,0); + NDebugOverlay::Box(waypoint->GetPos(), Vector(-3,-3,-3),Vector(3,3,3), static_cast(RGB[0]),static_cast(RGB[1]),static_cast(RGB[2]), true,0); if (waypoint->GetNext()) { Vector RGB = GetRouteColor(waypoint->GetNext()->NavType(), waypoint->GetNext()->Flags()); - NDebugOverlay::Line(waypoint->GetPos(), waypoint->GetNext()->GetPos(),RGB[0],RGB[1],RGB[2], true,0); + NDebugOverlay::Line(waypoint->GetPos(), waypoint->GetNext()->GetPos(),static_cast(RGB[0]),static_cast(RGB[1]),static_cast(RGB[2]), true,0); } waypoint = waypoint->GetNext(); } diff --git a/dlls/ai_navigator.h b/dlls/ai_navigator.h index 04541d5c..4432b834 100644 --- a/dlls/ai_navigator.h +++ b/dlls/ai_navigator.h @@ -185,7 +185,7 @@ struct AI_NavGoal_t CBaseEntity * pTarget = AIN_DEF_TARGET); //---------------------------------- - + // What type of goal is this GoalType_t type; @@ -195,10 +195,6 @@ struct AI_NavGoal_t // The activity to use, or none if a previosly set activity should be used Activity activity; - - // The predicted activity used after arrival - Activity arrivalActivity; - int arrivalSequence; // The tolerance of success, or none if a previosly set tolerance should be used float tolerance; @@ -209,9 +205,13 @@ struct AI_NavGoal_t // Optional flags specifying unsigned flags; - + // The target of the navigation, primarily used to ignore the entity in hull and line traces CBaseEntity * pTarget; + + // The predicted activity used after arrival + Activity arrivalActivity; + int arrivalSequence; }; //------------------------------------- diff --git a/dlls/ai_network.cpp b/dlls/ai_network.cpp index d95a30e5..9ad9c757 100644 --- a/dlls/ai_network.cpp +++ b/dlls/ai_network.cpp @@ -47,13 +47,13 @@ public: class CNodeFilter : public INodeListFilter { public: - CNodeFilter( CAI_BaseNPC *pNPC, const Vector &pos ) : m_pNPC(pNPC), m_pos(pos) + CNodeFilter( CAI_BaseNPC *pNPC, const Vector &pos ) : m_pos(pos), m_pNPC(pNPC) { if ( m_pNPC ) m_capabilities = m_pNPC->CapabilitiesGet(); } - CNodeFilter( const Vector &pos ) : m_pNPC(NULL), m_pos(pos) + CNodeFilter( const Vector &pos ) : m_pos(pos), m_pNPC(NULL) { } diff --git a/dlls/ai_networkmanager.cpp b/dlls/ai_networkmanager.cpp index aea26199..29c9a6a8 100644 --- a/dlls/ai_networkmanager.cpp +++ b/dlls/ai_networkmanager.cpp @@ -2345,7 +2345,7 @@ void CAI_NetworkBuilder::InitClimbNodePosition(CAI_Network *pNetwork, CAI_Node * { float floorZ = GetFloorZ(origin); // FIXME: don't use this - if (abs(pNode->GetOrigin().z - floorZ) < 36) + if (abs(static_cast(pNode->GetOrigin().z - floorZ)) < 36) { CAI_Node *new_node = pNetwork->AddNode( pNode->GetOrigin(), pNode->m_flYaw ); new_node->m_pHint = NULL; diff --git a/dlls/ai_pathfinder.cpp b/dlls/ai_pathfinder.cpp index c9ca207a..c30890c8 100644 --- a/dlls/ai_pathfinder.cpp +++ b/dlls/ai_pathfinder.cpp @@ -1241,7 +1241,7 @@ AI_Waypoint_t *CAI_Pathfinder::BuildRadialRoute( const Vector &vStartPos, const vNextPos.y += flRadius * sin( flCurAngle ); // Build a route from the last position to the current one - pNextRoute = BuildLocalRoute( vLastPos, vNextPos, NULL, NULL, NO_NODE, fRouteBits, goalTolerance); + pNextRoute = BuildLocalRoute( vLastPos, vNextPos, NULL, 0, NO_NODE, fRouteBits, goalTolerance); // If we can't find a route, we failed if ( pNextRoute == NULL ) @@ -1270,7 +1270,7 @@ AI_Waypoint_t *CAI_Pathfinder::BuildRadialRoute( const Vector &vStartPos, const } // Append a path to the final position - pLastRoute = BuildLocalRoute( vLastPos, vGoalPos, NULL, NULL, NO_NODE, bAirRoute ? bits_BUILD_FLY : bits_BUILD_GROUND, goalTolerance ); + pLastRoute = BuildLocalRoute( vLastPos, vGoalPos, NULL, 0, NO_NODE, bAirRoute ? bits_BUILD_FLY : bits_BUILD_GROUND, goalTolerance ); if ( pLastRoute == NULL ) return NULL; @@ -1811,9 +1811,9 @@ void CAI_Pathfinder::CTriDebugOverlay::FadeTriOverlayLines(void) { for (int i=0;ir *= 0.5; - m_debugTriOverlayLine[i]->g *= 0.5; - m_debugTriOverlayLine[i]->b *= 0.5; + m_debugTriOverlayLine[i]->r = static_cast(m_debugTriOverlayLine[i]->r * 0.5); + m_debugTriOverlayLine[i]->g = static_cast(m_debugTriOverlayLine[i]->g * 0.5); + m_debugTriOverlayLine[i]->b = static_cast(m_debugTriOverlayLine[i]->b * 0.5); } } } diff --git a/dlls/ai_planesolver.cpp b/dlls/ai_planesolver.cpp index 8062cded..d70dad20 100644 --- a/dlls/ai_planesolver.cpp +++ b/dlls/ai_planesolver.cpp @@ -60,8 +60,8 @@ inline float cube( float f ) CAI_PlaneSolver::CAI_PlaneSolver( CAI_BaseNPC *pNpc ) : m_pNpc( pNpc ), - m_fSolvedPrev( false ), m_PrevTarget( FLT_MAX, FLT_MAX, FLT_MAX ), + m_fSolvedPrev( false ), m_PrevSolution( 0 ), m_ClosestHaveBeenToCurrent( FLT_MAX ), m_TimeLastProgress( FLT_MAX ), diff --git a/dlls/ai_planesolver.h b/dlls/ai_planesolver.h index 0f61c066..da134518 100644 --- a/dlls/ai_planesolver.h +++ b/dlls/ai_planesolver.h @@ -137,8 +137,8 @@ private: CircleObstacles_t( const Vector ¢er, float radius, CBaseEntity *pEntity, AI_MoveSuggType_t type ) : center(center), radius(radius), - hEntity(pEntity), - type(type) + type(type), + hEntity(pEntity) { } diff --git a/dlls/ai_playerally.cpp b/dlls/ai_playerally.cpp index bd7164d5..bf9292cf 100644 --- a/dlls/ai_playerally.cpp +++ b/dlls/ai_playerally.cpp @@ -133,7 +133,7 @@ static CUtlMap g_ConceptInfoMap; static void InitConcepts( void ) { g_ConceptInfoMap.SetLessFunc( CaselessStringLessThan ); - for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ ) { g_ConceptInfoMap.Insert( g_ConceptInfos[i].concept, &g_ConceptInfos[i] ); } @@ -158,7 +158,7 @@ void CAI_AllySpeechManager::Spawn() InitConcepts(); } - for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ ) m_ConceptTimers.Insert( AllocPooledString( g_ConceptInfos[i].concept ), CSimpleSimTimer() ); } @@ -1512,7 +1512,7 @@ bool CAI_PlayerAlly::ShouldSpeakRandom( AIConcept_t concept, int iChance ) if ( flModifier < 0.001 ) return false; - iChance = floor( (float)iChance / flModifier ); + iChance = static_cast(floor( (float)iChance / flModifier )); } } diff --git a/dlls/ai_schedule.cpp b/dlls/ai_schedule.cpp index 9edefc4a..eb84b89a 100644 --- a/dlls/ai_schedule.cpp +++ b/dlls/ai_schedule.cpp @@ -239,7 +239,7 @@ bool CAI_SchedulesManager::LoadSchedulesFromBuffer( const char *prefix, char *pS int taskNum = 0; pfile = engine->ParseFile(pfile, token, sizeof( token ) ); - while ((token[0]!=NULL) && (stricmp("Interrupts",token))) + while ((token[0] != 0) && (stricmp("Interrupts",token))) { // Convert generic ID to sub-class specific enum int taskID = CAI_BaseNPC::GetTaskID(token); @@ -477,7 +477,7 @@ bool CAI_SchedulesManager::LoadSchedulesFromBuffer( const char *prefix, char *pS // Now read in the interrupts // ========================== pfile = engine->ParseFile(pfile, token, sizeof( token ) ); - while ((token[0]!=NULL) && (stricmp("Schedule",token))) + while ((token[0] != 0) && (stricmp("Schedule",token))) { // Convert generic ID to sub-class specific enum int condID = CAI_BaseNPC::GetConditionID(token); diff --git a/dlls/ai_schedule.h b/dlls/ai_schedule.h index d6567792..85528e33 100644 --- a/dlls/ai_schedule.h +++ b/dlls/ai_schedule.h @@ -1,200 +1,200 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: A schedule -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#include "bitstring.h" - -#ifndef AI_SCHEDULE_H -#define AI_SCHEDULE_H - -#pragma once - -class CStringRegistry; -class CAI_ClassScheduleIdSpace; -class CAI_BaseNPC; - -struct Task_t; - -#ifndef MAX_CONDITIONS -#define MAX_CONDITIONS 32*8 -#endif -typedef CFixedBitString CAI_ScheduleBits; - -//================================================== -// goalType_t -//================================================== - -enum goalType_t -{ - GOAL_NONE = -1, - GOAL_ENEMY, //Our current enemy's position - GOAL_TARGET, //Our current target's position - GOAL_ENEMY_LKP, //Our current enemy's last known position - GOAL_SAVED_POSITION, //Our saved position -}; - -//================================================== -// pathType_t -//================================================== - -enum pathType_t -{ - PATH_NONE = -1, - PATH_TRAVEL, //Path that will take us to the goal - PATH_LOS, //Path that gives us line of sight to our goal - //PATH_FLANK, //Path that will take us to a flanking position of our goal - //PATH_FLANK_LOS, //Path that will take us to within line of sight to the flanking position of our goal - PATH_COVER, //Path that will give us cover from our goal - //PATH_COVER_LOS, //Path that will give us line of sight to cover from our goal -}; - -//============================================================================= -// >> CAI_Schedule -//============================================================================= - -class CAI_Schedule; - -class CAI_SchedulesManager -{ -public: - CAI_SchedulesManager() - { - allSchedules = NULL; - m_CurLoadSig = 0; // Note when schedules reset - } - - int GetScheduleLoadSignature() { return m_CurLoadSig; } - CAI_Schedule* GetScheduleFromID( int schedID ); // Function to return schedule from linked list - CAI_Schedule* GetScheduleByName( const char *name ); - - bool LoadAllSchedules(void); - - bool LoadSchedules( const char* prefix, CAI_ClassScheduleIdSpace *pIdSpace ); - bool LoadSchedulesFromBuffer( const char *prefix, char *pfile, CAI_ClassScheduleIdSpace *pIdSpace ); - -private: - friend class CAI_SystemHook; - - int m_CurLoadSig; // Note when schedules reset - CAI_Schedule* allSchedules; // A linked list of all schedules - - CAI_Schedule * CreateSchedule(char *name, int schedule_id); - - void CreateStringRegistries( void ); - void DestroyStringRegistries( void ); - void DeleteAllSchedules(void); - - //static bool LoadSchedules( char* prefix, int taskIDOffset, int taskENOffset, - // int schedIDOffset, int schedENOffset, - // int condIDOffset, int condENOffset); - - // parsing helpers - int GetStateID(const char *state_name); - int GetMemoryID(const char *memory_name); - int GetPathID( const char *token ); - int GetGoalID( const char *token ); - -}; - -extern CAI_SchedulesManager g_AI_SchedulesManager; - -class CAI_Schedule -{ -// --------- -// Static -// --------- -// --------- -public: - int GetId() const - { - return m_iScheduleID; - } - - const Task_t *GetTaskList() const - { - return m_pTaskList; - } - - int NumTasks() const - { - return m_iNumTasks; - } - - void GetInterruptMask( CAI_ScheduleBits *pBits ) const - { - m_InterruptMask.Copy( pBits ); - } - - bool HasInterrupt( int condition ) const - { - return m_InterruptMask.GetBit( condition ); - } - - const char *GetName() const - { - return m_pName; - } - -private: - friend class CAI_SchedulesManager; - - int m_iScheduleID; // The id number of this schedule - - Task_t *m_pTaskList; - int m_iNumTasks; - - CAI_ScheduleBits m_InterruptMask; // a bit mask of conditions that can interrupt this schedule - char *m_pName; - - CAI_Schedule *nextSchedule; // The next schedule in the list of schedules - - CAI_Schedule(char *name,int schedule_id, CAI_Schedule *pNext); - ~CAI_Schedule( void ); -}; - -//----------------------------------------------------------------------------- -// -// In-memory schedules -// - -#define AI_DEFINE_SCHEDULE( name, text ) \ - const char * g_psz##name = \ - "\n Schedule" \ - "\n " #name \ - text \ - "\n" - - -#define AI_LOAD_SCHEDULE( classname, name ) \ - do \ - { \ - extern const char * g_psz##name; \ - if ( classname::gm_SchedLoadStatus.fValid ) \ - { \ - classname::gm_SchedLoadStatus.fValid = g_AI_SchedulesManager.LoadSchedulesFromBuffer( #classname,(char *)g_psz##name,&classname::gm_ClassScheduleIdSpace ); \ - } \ - } while (false) - - -// For loading default schedules in memory (see ai_default.cpp) -#define AI_LOAD_DEF_SCHEDULE( classname, name ) \ - do \ - { \ - extern const char * g_psz##name; \ - if (!g_AI_SchedulesManager.LoadSchedulesFromBuffer( #classname,(char *)g_psz##name,&classname::gm_ClassScheduleIdSpace )) \ - return false; \ - } while (false) - - -//----------------------------------------------------------------------------- - -#endif // AI_SCHEDULE_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: A schedule +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "bitstring.h" + +#ifndef AI_SCHEDULE_H +#define AI_SCHEDULE_H + +#pragma once + +class CStringRegistry; +class CAI_ClassScheduleIdSpace; +class CAI_BaseNPC; + +struct Task_t; + +#ifndef MAX_CONDITIONS +#define MAX_CONDITIONS 32*8 +#endif +typedef CFixedBitString CAI_ScheduleBits; + +//================================================== +// goalType_t +//================================================== + +enum goalType_t +{ + GOAL_NONE = -1, + GOAL_ENEMY, //Our current enemy's position + GOAL_TARGET, //Our current target's position + GOAL_ENEMY_LKP, //Our current enemy's last known position + GOAL_SAVED_POSITION, //Our saved position +}; + +//================================================== +// pathType_t +//================================================== + +enum pathType_t +{ + PATH_NONE = -1, + PATH_TRAVEL, //Path that will take us to the goal + PATH_LOS, //Path that gives us line of sight to our goal + //PATH_FLANK, //Path that will take us to a flanking position of our goal + //PATH_FLANK_LOS, //Path that will take us to within line of sight to the flanking position of our goal + PATH_COVER, //Path that will give us cover from our goal + //PATH_COVER_LOS, //Path that will give us line of sight to cover from our goal +}; + +//============================================================================= +// >> CAI_Schedule +//============================================================================= + +class CAI_Schedule; + +class CAI_SchedulesManager +{ +public: + CAI_SchedulesManager() + { + allSchedules = NULL; + m_CurLoadSig = 0; // Note when schedules reset + } + + int GetScheduleLoadSignature() { return m_CurLoadSig; } + CAI_Schedule* GetScheduleFromID( int schedID ); // Function to return schedule from linked list + CAI_Schedule* GetScheduleByName( const char *name ); + + bool LoadAllSchedules(void); + + bool LoadSchedules( const char* prefix, CAI_ClassScheduleIdSpace *pIdSpace ); + bool LoadSchedulesFromBuffer( const char *prefix, char *pfile, CAI_ClassScheduleIdSpace *pIdSpace ); + +private: + friend class CAI_SystemHook; + + int m_CurLoadSig; // Note when schedules reset + CAI_Schedule* allSchedules; // A linked list of all schedules + + CAI_Schedule * CreateSchedule(char *name, int schedule_id); + + void CreateStringRegistries( void ); + void DestroyStringRegistries( void ); + void DeleteAllSchedules(void); + + //static bool LoadSchedules( char* prefix, int taskIDOffset, int taskENOffset, + // int schedIDOffset, int schedENOffset, + // int condIDOffset, int condENOffset); + + // parsing helpers + int GetStateID(const char *state_name); + int GetMemoryID(const char *memory_name); + int GetPathID( const char *token ); + int GetGoalID( const char *token ); + +}; + +extern CAI_SchedulesManager g_AI_SchedulesManager; + +class CAI_Schedule +{ +// --------- +// Static +// --------- +// --------- +public: + int GetId() const + { + return m_iScheduleID; + } + + const Task_t *GetTaskList() const + { + return m_pTaskList; + } + + int NumTasks() const + { + return m_iNumTasks; + } + + void GetInterruptMask( CAI_ScheduleBits *pBits ) const + { + m_InterruptMask.Copy( pBits ); + } + + bool HasInterrupt( int condition ) const + { + return m_InterruptMask.GetBit( condition ); + } + + const char *GetName() const + { + return m_pName; + } + +private: + friend class CAI_SchedulesManager; + + int m_iScheduleID; // The id number of this schedule + + Task_t *m_pTaskList; + int m_iNumTasks; + + CAI_ScheduleBits m_InterruptMask; // a bit mask of conditions that can interrupt this schedule + char *m_pName; + + CAI_Schedule *nextSchedule; // The next schedule in the list of schedules + + CAI_Schedule(char *name,int schedule_id, CAI_Schedule *pNext); + ~CAI_Schedule( void ); +}; + +//----------------------------------------------------------------------------- +// +// In-memory schedules +// + +#define AI_DEFINE_SCHEDULE( name, text ) \ + const char * g_psz##name = \ + "\n Schedule" \ + "\n " #name \ + text \ + "\n" + + +#define AI_LOAD_SCHEDULE( classname, name ) \ + do \ + { \ + extern const char * g_psz##name; \ + if ( classname::gm_SchedLoadStatus.fValid ) \ + { \ + classname::gm_SchedLoadStatus.fValid = g_AI_SchedulesManager.LoadSchedulesFromBuffer( #classname,(char *)g_psz##name,&classname::gm_ClassScheduleIdSpace ); \ + } \ + } while (false) + + +// For loading default schedules in memory (see ai_default.cpp) +#define AI_LOAD_DEF_SCHEDULE( classname, name ) \ + do \ + { \ + extern const char * g_psz##name; \ + if (!g_AI_SchedulesManager.LoadSchedulesFromBuffer( #classname,(char *)g_psz##name,&classname::gm_ClassScheduleIdSpace )) \ + return false; \ + } while (false) + + +//----------------------------------------------------------------------------- + +#endif // AI_SCHEDULE_H diff --git a/dlls/ai_scriptconditions.h b/dlls/ai_scriptconditions.h index 8290de51..168d8a74 100644 --- a/dlls/ai_scriptconditions.h +++ b/dlls/ai_scriptconditions.h @@ -1,249 +1,249 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef AI_SCRIPTCONDITIONS_H -#define AI_SCRIPTCONDITIONS_H - -#include "baseentity.h" -#include "entityoutput.h" -#include "simtimer.h" -#include "ai_npcstate.h" - -#if defined( _WIN32 ) -#pragma once -#endif - -//----------------------------------------------------------------------------- - -class CAI_ProxTester -{ -public: - CAI_ProxTester() - : m_distSq( 0 ), - m_fInside( false ) - { - } - - void Init( float dist ) - { - m_fInside = ( dist > 0 ); - m_distSq = dist * dist; - } - - bool Check( CBaseEntity *pEntity1, CBaseEntity *pEntity2 ) - { - if ( m_distSq != 0 ) - { - float distSq = ( pEntity1->GetAbsOrigin() - pEntity2->GetAbsOrigin() ).LengthSqr(); - bool fInside = ( distSq < m_distSq ); - - return ( m_fInside == fInside ); - } - return true; - } - - DECLARE_SIMPLE_DATADESC(); - -private: - - float m_distSq; - bool m_fInside; -}; - -//----------------------------------------------------------------------------- -class CAI_ScriptConditionsElement -{ -public: - - DECLARE_SIMPLE_DATADESC(); - - void SetActor( CBaseEntity *pEntity ) { m_hActor = pEntity; } - CBaseEntity *GetActor( void ){ return m_hActor.Get(); } - - void SetTimer( CSimTimer timer ) { m_Timer = timer; } - CSimTimer *GetTimer( void ) { return &m_Timer; } - - void SetTimeOut( CSimTimer timeout) { m_Timeout = timeout; } - CSimTimer *GetTimeOut( void ) { return &m_Timeout; } - -private: - EHANDLE m_hActor; - CSimTimer m_Timer; - CSimTimer m_Timeout; -}; - -//----------------------------------------------------------------------------- -// class CAI_ScriptConditions -// -// Purpose: Watches a set of conditions relative to a given NPC, and when they -// are all satisfied, fires the relevant output -//----------------------------------------------------------------------------- - -class CAI_ScriptConditions : public CBaseEntity, public IEntityListener -{ - DECLARE_CLASS( CAI_ScriptConditions, CBaseEntity ); - -public: - CAI_ScriptConditions() - : m_fDisabled( true ), - m_flRequiredTime( 0 ), - m_fMinState( NPC_STATE_IDLE ), - m_fMaxState( NPC_STATE_IDLE ), - m_fScriptStatus( TRS_NONE ), - m_fActorSeePlayer( TRS_NONE ), - m_flPlayerActorProximity( 0 ), - m_flPlayerActorFOV( -1 ), - m_fPlayerActorLOS( TRS_NONE ), - m_fActorSeeTarget( TRS_NONE ), - m_flActorTargetProximity( 0 ), - m_flPlayerTargetProximity( 0 ), - m_flPlayerTargetFOV( 0 ), - m_fPlayerTargetLOS( TRS_NONE ), - m_fPlayerBlockingActor( TRS_NONE ), - m_flMinTimeout( 0 ), - m_flMaxTimeout( 0 ), - m_fActorInPVS( TRS_NONE ) - { -#ifndef HL2_EPISODIC - m_hActor = NULL; -#endif - } - -private: - void Spawn(); - void Activate(); - - void EvaluationThink(); - - void Enable(); - void Disable(); - - void SetThinkTime() { SetNextThink( gpGlobals->curtime + 0.250 ); } - - // Evaluators - struct EvalArgs_t - { - CBaseEntity *pActor; - CBaseEntity *pPlayer; - CBaseEntity *pTarget; - }; - - bool EvalState( const EvalArgs_t &args ); - bool EvalActorSeePlayer( const EvalArgs_t &args ); - bool EvalPlayerActorLook( const EvalArgs_t &args ); - bool EvalPlayerTargetLook( const EvalArgs_t &args ); - bool EvalPlayerActorProximity( const EvalArgs_t &args ); - bool EvalPlayerTargetProximity( const EvalArgs_t &args ); - bool EvalActorTargetProximity( const EvalArgs_t &args ); - bool EvalActorSeeTarget( const EvalArgs_t &args ); - bool EvalPlayerActorLOS( const EvalArgs_t &args ); - bool EvalPlayerTargetLOS( const EvalArgs_t &args ); - bool EvalPlayerBlockingActor( const EvalArgs_t &args ); - bool EvalActorInPVS( const EvalArgs_t &args ); - - void OnEntitySpawned( CBaseEntity *pEntity ); - - int AddNewElement( CBaseEntity *pActor ); - - bool ActorInList( CBaseEntity *pActor ); - void UpdateOnRemove( void ); - - // Input handlers - void InputEnable( inputdata_t &inputdata ); - void InputDisable( inputdata_t &inputdata ); - - // Output handlers - COutputEvent m_OnConditionsSatisfied; - COutputEvent m_OnConditionsTimeout; - COutputEvent m_NoValidActors; - - //--------------------------------- - -#ifndef HL2_EPISODIC - CBaseEntity *GetActor() { return m_hActor.Get(); } -#endif - CBaseEntity *GetPlayer() { return UTIL_GetLocalPlayer(); } - - //--------------------------------- - - // @Note (toml 07-17-02): At some point, it may be desireable to switch to using function objects instead of functions. Probably - // if support for NPCs addiing custom conditions becomes necessary - typedef bool (CAI_ScriptConditions::*EvaluationFunc_t)( const EvalArgs_t &args ); - - struct EvaluatorInfo_t - { - EvaluationFunc_t pfnEvaluator; - const char *pszName; - }; - - static EvaluatorInfo_t gm_Evaluators[]; - - //--------------------------------- - // Evaluation helpers - - static bool IsInFOV( CBaseEntity *pViewer, CBaseEntity *pViewed, float fov, bool bTrueCone ); - static bool PlayerHasLineOfSight( CBaseEntity *pViewer, CBaseEntity *pViewed, bool fNot ); - static bool ActorInPlayersPVS( CBaseEntity *pActor, bool bNot ); - - virtual void OnRestore( void ); - - //--------------------------------- - // General conditions info - - bool m_fDisabled; - bool m_bLeaveAsleep; - EHANDLE m_hTarget; - - float m_flRequiredTime; // How long should the conditions me true - -#ifndef HL2_EPISODIC - EHANDLE m_hActor; - CSimTimer m_Timer; // @TODO (toml 07-16-02): save/load of timer once Jay has save/load of contained objects - CSimTimer m_Timeout; -#endif - - //--------------------------------- - // Specific conditions data - NPC_STATE m_fMinState; - NPC_STATE m_fMaxState; - ThreeState_t m_fScriptStatus; - ThreeState_t m_fActorSeePlayer; - string_t m_Actor; - - float m_flPlayerActorProximity; - CAI_ProxTester m_PlayerActorProxTester; - - float m_flPlayerActorFOV; - bool m_bPlayerActorFOVTrueCone; - ThreeState_t m_fPlayerActorLOS; - ThreeState_t m_fActorSeeTarget; - - float m_flActorTargetProximity; - CAI_ProxTester m_ActorTargetProxTester; - - float m_flPlayerTargetProximity; - CAI_ProxTester m_PlayerTargetProxTester; - - float m_flPlayerTargetFOV; - bool m_bPlayerTargetFOVTrueCone; - ThreeState_t m_fPlayerTargetLOS; - ThreeState_t m_fPlayerBlockingActor; - ThreeState_t m_fActorInPVS; - - float m_flMinTimeout; - float m_flMaxTimeout; - - CUtlVector< CAI_ScriptConditionsElement > m_ElementList; - - //--------------------------------- - - DECLARE_DATADESC(); -}; - -//============================================================================= - -#endif // AI_SCRIPTCONDITIONS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef AI_SCRIPTCONDITIONS_H +#define AI_SCRIPTCONDITIONS_H + +#include "baseentity.h" +#include "entityoutput.h" +#include "simtimer.h" +#include "ai_npcstate.h" + +#if defined( _WIN32 ) +#pragma once +#endif + +//----------------------------------------------------------------------------- + +class CAI_ProxTester +{ +public: + CAI_ProxTester() + : m_distSq( 0 ), + m_fInside( false ) + { + } + + void Init( float dist ) + { + m_fInside = ( dist > 0 ); + m_distSq = dist * dist; + } + + bool Check( CBaseEntity *pEntity1, CBaseEntity *pEntity2 ) + { + if ( m_distSq != 0 ) + { + float distSq = ( pEntity1->GetAbsOrigin() - pEntity2->GetAbsOrigin() ).LengthSqr(); + bool fInside = ( distSq < m_distSq ); + + return ( m_fInside == fInside ); + } + return true; + } + + DECLARE_SIMPLE_DATADESC(); + +private: + + float m_distSq; + bool m_fInside; +}; + +//----------------------------------------------------------------------------- +class CAI_ScriptConditionsElement +{ +public: + + DECLARE_SIMPLE_DATADESC(); + + void SetActor( CBaseEntity *pEntity ) { m_hActor = pEntity; } + CBaseEntity *GetActor( void ){ return m_hActor.Get(); } + + void SetTimer( CSimTimer timer ) { m_Timer = timer; } + CSimTimer *GetTimer( void ) { return &m_Timer; } + + void SetTimeOut( CSimTimer timeout) { m_Timeout = timeout; } + CSimTimer *GetTimeOut( void ) { return &m_Timeout; } + +private: + EHANDLE m_hActor; + CSimTimer m_Timer; + CSimTimer m_Timeout; +}; + +//----------------------------------------------------------------------------- +// class CAI_ScriptConditions +// +// Purpose: Watches a set of conditions relative to a given NPC, and when they +// are all satisfied, fires the relevant output +//----------------------------------------------------------------------------- + +class CAI_ScriptConditions : public CBaseEntity, public IEntityListener +{ + DECLARE_CLASS( CAI_ScriptConditions, CBaseEntity ); + +public: + CAI_ScriptConditions() + : m_fDisabled( true ), + m_flRequiredTime( 0 ), + m_fMinState( NPC_STATE_IDLE ), + m_fMaxState( NPC_STATE_IDLE ), + m_fScriptStatus( TRS_NONE ), + m_fActorSeePlayer( TRS_NONE ), + m_flPlayerActorProximity( 0 ), + m_flPlayerActorFOV( -1 ), + m_fPlayerActorLOS( TRS_NONE ), + m_fActorSeeTarget( TRS_NONE ), + m_flActorTargetProximity( 0 ), + m_flPlayerTargetProximity( 0 ), + m_flPlayerTargetFOV( 0 ), + m_fPlayerTargetLOS( TRS_NONE ), + m_fPlayerBlockingActor( TRS_NONE ), + m_fActorInPVS( TRS_NONE ), + m_flMinTimeout( 0 ), + m_flMaxTimeout( 0 ) + { +#ifndef HL2_EPISODIC + m_hActor = NULL; +#endif + } + +private: + void Spawn(); + void Activate(); + + void EvaluationThink(); + + void Enable(); + void Disable(); + + void SetThinkTime() { SetNextThink( gpGlobals->curtime + 0.250 ); } + + // Evaluators + struct EvalArgs_t + { + CBaseEntity *pActor; + CBaseEntity *pPlayer; + CBaseEntity *pTarget; + }; + + bool EvalState( const EvalArgs_t &args ); + bool EvalActorSeePlayer( const EvalArgs_t &args ); + bool EvalPlayerActorLook( const EvalArgs_t &args ); + bool EvalPlayerTargetLook( const EvalArgs_t &args ); + bool EvalPlayerActorProximity( const EvalArgs_t &args ); + bool EvalPlayerTargetProximity( const EvalArgs_t &args ); + bool EvalActorTargetProximity( const EvalArgs_t &args ); + bool EvalActorSeeTarget( const EvalArgs_t &args ); + bool EvalPlayerActorLOS( const EvalArgs_t &args ); + bool EvalPlayerTargetLOS( const EvalArgs_t &args ); + bool EvalPlayerBlockingActor( const EvalArgs_t &args ); + bool EvalActorInPVS( const EvalArgs_t &args ); + + void OnEntitySpawned( CBaseEntity *pEntity ); + + int AddNewElement( CBaseEntity *pActor ); + + bool ActorInList( CBaseEntity *pActor ); + void UpdateOnRemove( void ); + + // Input handlers + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + // Output handlers + COutputEvent m_OnConditionsSatisfied; + COutputEvent m_OnConditionsTimeout; + COutputEvent m_NoValidActors; + + //--------------------------------- + +#ifndef HL2_EPISODIC + CBaseEntity *GetActor() { return m_hActor.Get(); } +#endif + CBaseEntity *GetPlayer() { return UTIL_GetLocalPlayer(); } + + //--------------------------------- + + // @Note (toml 07-17-02): At some point, it may be desireable to switch to using function objects instead of functions. Probably + // if support for NPCs addiing custom conditions becomes necessary + typedef bool (CAI_ScriptConditions::*EvaluationFunc_t)( const EvalArgs_t &args ); + + struct EvaluatorInfo_t + { + EvaluationFunc_t pfnEvaluator; + const char *pszName; + }; + + static EvaluatorInfo_t gm_Evaluators[]; + + //--------------------------------- + // Evaluation helpers + + static bool IsInFOV( CBaseEntity *pViewer, CBaseEntity *pViewed, float fov, bool bTrueCone ); + static bool PlayerHasLineOfSight( CBaseEntity *pViewer, CBaseEntity *pViewed, bool fNot ); + static bool ActorInPlayersPVS( CBaseEntity *pActor, bool bNot ); + + virtual void OnRestore( void ); + + //--------------------------------- + // General conditions info + + bool m_fDisabled; + bool m_bLeaveAsleep; + EHANDLE m_hTarget; + + float m_flRequiredTime; // How long should the conditions me true + +#ifndef HL2_EPISODIC + EHANDLE m_hActor; + CSimTimer m_Timer; // @TODO (toml 07-16-02): save/load of timer once Jay has save/load of contained objects + CSimTimer m_Timeout; +#endif + + //--------------------------------- + // Specific conditions data + NPC_STATE m_fMinState; + NPC_STATE m_fMaxState; + ThreeState_t m_fScriptStatus; + ThreeState_t m_fActorSeePlayer; + string_t m_Actor; + + float m_flPlayerActorProximity; + CAI_ProxTester m_PlayerActorProxTester; + + float m_flPlayerActorFOV; + bool m_bPlayerActorFOVTrueCone; + ThreeState_t m_fPlayerActorLOS; + ThreeState_t m_fActorSeeTarget; + + float m_flActorTargetProximity; + CAI_ProxTester m_ActorTargetProxTester; + + float m_flPlayerTargetProximity; + CAI_ProxTester m_PlayerTargetProxTester; + + float m_flPlayerTargetFOV; + bool m_bPlayerTargetFOVTrueCone; + ThreeState_t m_fPlayerTargetLOS; + ThreeState_t m_fPlayerBlockingActor; + ThreeState_t m_fActorInPVS; + + float m_flMinTimeout; + float m_flMaxTimeout; + + CUtlVector< CAI_ScriptConditionsElement > m_ElementList; + + //--------------------------------- + + DECLARE_DATADESC(); +}; + +//============================================================================= + +#endif // AI_SCRIPTCONDITIONS_H diff --git a/dlls/ai_senses.cpp b/dlls/ai_senses.cpp index 21e2b8d1..250f7999 100644 --- a/dlls/ai_senses.cpp +++ b/dlls/ai_senses.cpp @@ -37,8 +37,7 @@ CAI_SensedObjectsManager g_AI_SensedObjectsManager; //----------------------------------------------------------------------------- -#pragma pack(push) -#pragma pack(1) +#pragma pack(push, 1) struct AISightIterVal_t { @@ -243,7 +242,7 @@ CBaseEntity *CAI_Senses::GetFirstSeenEntity( AISightIter_t *pIter, seentype_t iS pIterVal->SeenArray = (char)iSeenType; int iFirstArray = ( iSeenType == SEEN_ALL ) ? 0 : iSeenType; - for ( int i = iFirstArray; i < ARRAYSIZE( m_SeenArrays ); i++ ) + for ( int i = iFirstArray; i < static_cast(ARRAYSIZE( m_SeenArrays )); i++ ) { if ( m_SeenArrays[i]->Count() != 0 ) { @@ -265,7 +264,7 @@ CBaseEntity *CAI_Senses::GetNextSeenEntity( AISightIter_t *pIter ) const { AISightIterVal_t *pIterVal = (AISightIterVal_t *)pIter; - for ( int i = pIterVal->array; i < ARRAYSIZE( m_SeenArrays ); i++ ) + for ( int i = pIterVal->array; i < static_cast(ARRAYSIZE( m_SeenArrays )); i++ ) { for ( int j = pIterVal->iNext; j < m_SeenArrays[i]->Count(); j++ ) { @@ -618,7 +617,7 @@ void CAI_Senses::PerformSensing( void ) // ----------------- // Look // ----------------- - Look( m_LookDist ); + Look( static_cast(m_LookDist) ); // ------------------ // Listen diff --git a/dlls/ai_speech.cpp b/dlls/ai_speech.cpp index de2c4b34..870a08e8 100644 --- a/dlls/ai_speech.cpp +++ b/dlls/ai_speech.cpp @@ -213,8 +213,8 @@ CAI_Expresser::CAI_Expresser( CAI_BaseNPC *pOuter ) : CAI_Component( pOuter ), m_pSink( NULL ), m_flStopTalkTime( 0 ), - m_flBlockedTalkTime( 0 ), m_flStopTalkTimeWithoutDelay( 0 ), + m_flBlockedTalkTime( 0 ), m_voicePitch( 100 ) { #ifdef DEBUG @@ -828,7 +828,7 @@ void CAI_ExpresserHost_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_Crite } static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" }; - if ( (int)pSpeaker->m_NPCState < ARRAYSIZE(pStateNames) ) + if ( (int)pSpeaker->m_NPCState < static_cast(ARRAYSIZE(pStateNames)) ) { set.AppendCriteria( "npcstate", UTIL_VarArgs( "[NPCState::%s]", pStateNames[pSpeaker->m_NPCState] ) ); } diff --git a/dlls/ai_speech.h b/dlls/ai_speech.h index cf8fd2e1..842cea49 100644 --- a/dlls/ai_speech.h +++ b/dlls/ai_speech.h @@ -260,15 +260,15 @@ public: virtual void PostSpeakDispatchResponse( AIConcept_t concept, AI_Response *response ) { return; } float GetResponseDuration( AI_Response *response ); - float GetTimeSpeechComplete() const { return GetExpresser()->GetTimeSpeechComplete(); } + float GetTimeSpeechComplete() const { return this->GetExpresser()->GetTimeSpeechComplete(); } - bool IsSpeaking() { return GetExpresser()->IsSpeaking(); } - bool CanSpeak() { return GetExpresser()->CanSpeak(); } - bool CanSpeakAfterMyself() { return GetExpresser()->CanSpeakAfterMyself(); } + bool IsSpeaking() { return this->GetExpresser()->IsSpeaking(); } + bool CanSpeak() { return this->GetExpresser()->CanSpeak(); } + bool CanSpeakAfterMyself() { return this->GetExpresser()->CanSpeakAfterMyself(); } - void SetSpokeConcept( AIConcept_t concept, AI_Response *response, bool bCallback = true ) { GetExpresser()->SetSpokeConcept( concept, response, bCallback ); } - float GetTimeSpokeConcept( AIConcept_t concept ) { return GetExpresser()->GetTimeSpokeConcept( concept ); } - bool SpokeConcept( AIConcept_t concept ) { return GetExpresser()->SpokeConcept( concept ); } + void SetSpokeConcept( AIConcept_t concept, AI_Response *response, bool bCallback = true ) { this->GetExpresser()->SetSpokeConcept( concept, response, bCallback ); } + float GetTimeSpokeConcept( AIConcept_t concept ) { return this->GetExpresser()->GetTimeSpokeConcept( concept ); } + bool SpokeConcept( AIConcept_t concept ) { return this->GetExpresser()->SpokeConcept( concept ); } protected: virtual bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0 ); @@ -285,7 +285,7 @@ protected: template inline void CAI_ExpresserHost::NoteSpeaking( float duration, float delay ) { - GetExpresser()->NoteSpeaking( duration, delay ); + this->GetExpresser()->NoteSpeaking( duration, delay ); } //----------------------------------------------------------------------------- @@ -293,8 +293,8 @@ inline void CAI_ExpresserHost::NoteSpeaking( float duration, float del template inline bool CAI_ExpresserHost::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */ ) { - AssertOnce( GetExpresser()->GetOuter() == this ); - return GetExpresser()->Speak( concept, modifiers, pszOutResponseChosen, bufsize ); + AssertOnce( this->GetExpresser()->GetOuter() == this ); + return this->GetExpresser()->Speak( concept, modifiers, pszOutResponseChosen, bufsize ); } //----------------------------------------------------------------------------- @@ -302,7 +302,7 @@ inline bool CAI_ExpresserHost::Speak( AIConcept_t concept, const char template inline int CAI_ExpresserHost::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener ) { - return GetExpresser()->SpeakRawSentence( pszSentence, delay, volume, soundlevel, pListener ); + return this->GetExpresser()->SpeakRawSentence( pszSentence, delay, volume, soundlevel, pListener ); } //----------------------------------------------------------------------------- @@ -332,7 +332,7 @@ inline IResponseSystem *CAI_ExpresserHost::GetResponseSystem() template inline AI_Response *CAI_ExpresserHost::SpeakFindResponse( AIConcept_t concept, const char *modifiers /*= NULL*/ ) { - return GetExpresser()->SpeakFindResponse( concept, modifiers ); + return this->GetExpresser()->SpeakFindResponse( concept, modifiers ); } //----------------------------------------------------------------------------- @@ -340,7 +340,7 @@ inline AI_Response *CAI_ExpresserHost::SpeakFindResponse( AIConcept_t template inline bool CAI_ExpresserHost::SpeakDispatchResponse( AIConcept_t concept, AI_Response *response ) { - if ( GetExpresser()->SpeakDispatchResponse( concept, response ) ) + if ( this->GetExpresser()->SpeakDispatchResponse( concept, response ) ) { PostSpeakDispatchResponse( concept, response ); return true; @@ -354,7 +354,7 @@ inline bool CAI_ExpresserHost::SpeakDispatchResponse( AIConcept_t conc template inline float CAI_ExpresserHost::GetResponseDuration( AI_Response *response ) { - return GetExpresser()->GetResponseDuration( response ); + return this->GetExpresser()->GetResponseDuration( response ); } //----------------------------------------------------------------------------- diff --git a/dlls/ai_squad.cpp b/dlls/ai_squad.cpp index 22503827..b1fd020c 100644 --- a/dlls/ai_squad.cpp +++ b/dlls/ai_squad.cpp @@ -262,7 +262,7 @@ void CAI_Squad::AddToSquad(CAI_BaseNPC *pNPC) if (m_SquadMembers.Count() == MAX_SQUAD_MEMBERS) { - DevMsg("Error!! Squad %s is too big!!! Replacing last member\n",this->m_Name); + DevMsg("Error!! Squad %s is too big!!! Replacing last member\n", STRING(this->m_Name)); m_SquadMembers.Remove(m_SquadMembers.Count()-1); } m_SquadMembers.AddToTail(pNPC); diff --git a/dlls/ai_tacticalservices.cpp b/dlls/ai_tacticalservices.cpp index c1244c3c..cf51de8a 100644 --- a/dlls/ai_tacticalservices.cpp +++ b/dlls/ai_tacticalservices.cpp @@ -660,7 +660,7 @@ bool CAI_TacticalServices::FindLateralLos( const Vector &vecThreat, Vector *pRes int iDelta = COVER_DELTA; // If we're limited in how far we're allowed to move laterally, don't bother checking past it - int iMaxLateralDelta = GetOuter()->GetMaxTacticalLateralMovement(); + int iMaxLateralDelta = static_cast(GetOuter()->GetMaxTacticalLateralMovement()); if ( iMaxLateralDelta != MAXTACLAT_IGNORE && iMaxLateralDelta < iDelta ) { iChecks = 1; diff --git a/dlls/ai_trackpather.cpp b/dlls/ai_trackpather.cpp index 667a174c..75828186 100644 --- a/dlls/ai_trackpather.cpp +++ b/dlls/ai_trackpather.cpp @@ -1,1684 +1,1684 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" - -#include "trains.h" -#include "ai_trackpather.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define TRACKPATHER_DEBUG_LEADING 1 -#define TRACKPATHER_DEBUG_PATH 2 -#define TRACKPATHER_DEBUG_TRACKS 3 -ConVar g_debug_trackpather( "g_debug_trackpather", "0", FCVAR_CHEAT ); - -//------------------------------------------------------------------------------ - -BEGIN_DATADESC( CAI_TrackPather ) - DEFINE_FIELD( m_vecDesiredPosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vecGoalOrientation, FIELD_VECTOR ), - - DEFINE_FIELD( m_pCurrentPathTarget, FIELD_CLASSPTR ), - DEFINE_FIELD( m_pDestPathTarget, FIELD_CLASSPTR ), - DEFINE_FIELD( m_pLastPathTarget, FIELD_CLASSPTR ), - DEFINE_FIELD( m_pTargetNearestPath, FIELD_CLASSPTR ), - - DEFINE_FIELD( m_strCurrentPathName, FIELD_STRING ), - DEFINE_FIELD( m_strDestPathName, FIELD_STRING ), - DEFINE_FIELD( m_strLastPathName, FIELD_STRING ), - DEFINE_FIELD( m_strTargetNearestPathName, FIELD_STRING ), - - DEFINE_FIELD( m_vecLastGoalCheckPosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_flEnemyPathUpdateTime, FIELD_TIME ), - DEFINE_FIELD( m_bForcedMove, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bPatrolling, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bPatrolBreakable, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bLeading, FIELD_BOOLEAN ), - - // Derived class pathing data - DEFINE_FIELD( m_flTargetDistanceThreshold, FIELD_FLOAT ), - DEFINE_FIELD( m_flAvoidDistance, FIELD_FLOAT ), - - DEFINE_FIELD( m_flTargetTolerance, FIELD_FLOAT ), - DEFINE_FIELD( m_vecSegmentStartPoint, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vecSegmentStartSplinePoint, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_bMovingForward, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bChooseFarthestPoint, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flFarthestPathDist, FIELD_FLOAT ), - DEFINE_FIELD( m_flPathMaxSpeed, FIELD_FLOAT ), - DEFINE_FIELD( m_flTargetDistFromPath, FIELD_FLOAT ), - DEFINE_FIELD( m_flLeadDistance, FIELD_FLOAT ), - DEFINE_FIELD( m_vecTargetPathDir, FIELD_VECTOR ), - DEFINE_FIELD( m_vecTargetPathPoint, FIELD_POSITION_VECTOR ), - - DEFINE_FIELD( m_nPauseState, FIELD_INTEGER ), - - // Inputs - DEFINE_INPUTFUNC( FIELD_STRING, "SetTrack", InputSetTrack ), - DEFINE_INPUTFUNC( FIELD_STRING, "FlyToSpecificTrackViaPath", InputFlyToPathTrack ), - DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrol", InputStartPatrol ), - DEFINE_INPUTFUNC( FIELD_VOID, "StopPatrol", InputStopPatrol ), - DEFINE_INPUTFUNC( FIELD_VOID, "StartBreakableMovement", InputStartBreakableMovement ), - DEFINE_INPUTFUNC( FIELD_VOID, "StopBreakableMovement", InputStopBreakableMovement ), - DEFINE_INPUTFUNC( FIELD_VOID, "ChooseFarthestPathPoint", InputChooseFarthestPathPoint ), - DEFINE_INPUTFUNC( FIELD_VOID, "ChooseNearestPathPoint", InputChooseNearestPathPoint ), - DEFINE_INPUTFUNC( FIELD_INTEGER,"InputStartLeading", InputStartLeading ), - DEFINE_INPUTFUNC( FIELD_VOID, "InputStopLeading", InputStopLeading ), - - // Obsolete, for backwards compatibility - DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrolBreakable", InputStartPatrolBreakable ), - DEFINE_INPUTFUNC( FIELD_STRING, "FlyToPathTrack", InputFlyToPathTrack ), -END_DATADESC() - - -//----------------------------------------------------------------------------- -// Purpose: Initialize pathing data -//----------------------------------------------------------------------------- -void CAI_TrackPather::InitPathingData( float flTrackArrivalTolerance, float flTargetDistance, float flAvoidDistance ) -{ - m_flTargetTolerance = flTrackArrivalTolerance; - m_flTargetDistanceThreshold = flTargetDistance; - m_flAvoidDistance = flAvoidDistance; - - m_pCurrentPathTarget = NULL; - m_pDestPathTarget = NULL; - m_pLastPathTarget = NULL; - m_pTargetNearestPath = NULL; - m_bLeading = false; - - m_flEnemyPathUpdateTime = gpGlobals->curtime; - m_bForcedMove = false; - m_bPatrolling = false; - m_bPatrolBreakable = false; - m_flLeadDistance = 0.0f; - m_bMovingForward = true; - m_vecSegmentStartPoint = m_vecSegmentStartSplinePoint = m_vecDesiredPosition = GetAbsOrigin(); - m_bChooseFarthestPoint = true; - m_flFarthestPathDist = 1e10; - m_flPathMaxSpeed = 0; - m_nPauseState = PAUSE_NO_PAUSE; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_TrackPather::OnRestore( void ) -{ - BaseClass::OnRestore(); - - // Restore current path - if ( m_strCurrentPathName != NULL_STRING ) - { - m_pCurrentPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strCurrentPathName ); - } - else - { - m_pCurrentPathTarget = NULL; - } - - // Restore destination path - if ( m_strDestPathName != NULL_STRING ) - { - m_pDestPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strDestPathName ); - } - else - { - m_pDestPathTarget = NULL; - } - - // Restore last path - if ( m_strLastPathName != NULL_STRING ) - { - m_pLastPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strLastPathName ); - } - else - { - m_pLastPathTarget = NULL; - } - - // Restore target nearest path - if ( m_strTargetNearestPathName != NULL_STRING ) - { - m_pTargetNearestPath = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strTargetNearestPathName ); - } - else - { - m_pTargetNearestPath = NULL; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_TrackPather::OnSave( IEntitySaveUtils *pUtils ) -{ - BaseClass::OnSave( pUtils ); - - // Stash all the paths into strings for restoration later - m_strCurrentPathName = ( m_pCurrentPathTarget != NULL ) ? m_pCurrentPathTarget->GetEntityName() : NULL_STRING; - m_strDestPathName = ( m_pDestPathTarget != NULL ) ? m_pDestPathTarget->GetEntityName() : NULL_STRING; - m_strLastPathName = ( m_pLastPathTarget != NULL ) ? m_pLastPathTarget->GetEntityName() : NULL_STRING; - m_strTargetNearestPathName = ( m_pTargetNearestPath != NULL ) ? m_pTargetNearestPath->GetEntityName() : NULL_STRING; -} - - -//----------------------------------------------------------------------------- -// Leading distance -//----------------------------------------------------------------------------- -void CAI_TrackPather::EnableLeading( bool bEnable ) -{ - m_bLeading = bEnable; - if ( m_bLeading ) - { - m_bPatrolling = false; - } -} - -void CAI_TrackPather::SetLeadingDistance( float flLeadDistance ) -{ - m_flLeadDistance = flLeadDistance; -} - -float CAI_TrackPather::GetLeadingDistance( ) const -{ - return m_flLeadDistance; -} - - -//----------------------------------------------------------------------------- -// Returns the next path along our current path -//----------------------------------------------------------------------------- -inline CPathTrack *CAI_TrackPather::NextAlongCurrentPath( CPathTrack *pPath ) const -{ - return CPathTrack::ValidPath( m_bMovingForward ? pPath->GetNext() : pPath->GetPrevious() ); -} - -inline CPathTrack *CAI_TrackPather::PreviousAlongCurrentPath( CPathTrack *pPath ) const -{ - return CPathTrack::ValidPath( m_bMovingForward ? pPath->GetPrevious() : pPath->GetNext() ); -} - -inline CPathTrack *CAI_TrackPather::AdjustForMovementDirection( CPathTrack *pPath ) const -{ - if ( !m_bMovingForward && CPathTrack::ValidPath( pPath->GetPrevious( ) ) ) - { - pPath = CPathTrack::ValidPath( pPath->GetPrevious() ); - } - return pPath; -} - - -//----------------------------------------------------------------------------- -// Enemy visibility check -//----------------------------------------------------------------------------- -CBaseEntity *CAI_TrackPather::FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos ) -{ - trace_t tr; - AI_TraceHull( vecViewPoint, vecTargetPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); - return (tr.fraction != 1.0f) ? tr.m_pEnt : NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &targetPos - -// Output : CBaseEntity -//----------------------------------------------------------------------------- -CPathTrack *CAI_TrackPather::BestPointOnPath( CPathTrack *pPath, const Vector &targetPos, float flAvoidRadius, bool visible, bool bFarthestPoint ) -{ - // Find the node nearest to the destination path target if a path is not specified - if ( pPath == NULL ) - { - pPath = m_pDestPathTarget; - } - - // If the path node we're trying to use is not valid, then we're done. - if ( CPathTrack::ValidPath( pPath ) == NULL ) - { - //FIXME: Implement - Assert(0); - return NULL; - } - - // Our target may be in a vehicle - CBaseEntity *pVehicle = NULL; - CBaseEntity *pTargetEnt = GetTrackPatherTargetEnt(); - if ( pTargetEnt != NULL ) - { - CBaseCombatCharacter *pCCTarget = pTargetEnt->MyCombatCharacterPointer(); - if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) - { - pVehicle = pCCTarget->GetVehicleEntity(); - } - } - - // Faster math... - flAvoidRadius *= flAvoidRadius; - - // Find the nearest node to the target (going forward) - CPathTrack *pNearestPath = NULL; - float flNearestDist = bFarthestPoint ? 0 : 999999999; - float flPathDist; - - float flFarthestDistSqr = ( m_flFarthestPathDist - 2.0f * m_flTargetDistanceThreshold ); - flFarthestDistSqr *= flFarthestDistSqr; - - // NOTE: Gotta do it this crazy way because paths can be one-way. - for ( int i = 0; i < 2; ++i ) - { - int loopCheck = 0; - CPathTrack *pTravPath = pPath; - CPathTrack *pNextPath; - - BEGIN_PATH_TRACK_ITERATION(); - for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath, loopCheck++ ) - { - // Circular loop checking - if ( pTravPath->HasBeenVisited() ) - break; - - pTravPath->Visit(); - - pNextPath = (i == 0) ? pTravPath->GetPrevious() : pTravPath->GetNext(); - - // Find the distance between this test point and our goal point - flPathDist = ( pTravPath->GetAbsOrigin() - targetPos ).LengthSqr(); - - // See if it's closer and it's also not within our avoidance radius - if ( bFarthestPoint ) - { - if ( ( flPathDist <= flNearestDist ) && ( flNearestDist <= flFarthestDistSqr ) ) - continue; - } - else - { - if ( flPathDist >= flNearestDist ) - continue; - } - - // Don't choose points that are within the avoid radius - if ( flAvoidRadius && ( pTravPath->GetAbsOrigin() - targetPos ).Length2DSqr() <= flAvoidRadius ) - continue; - - if ( visible ) - { - // If it has to be visible, run those checks - CBaseEntity *pBlocker = FindTrackBlocker( pTravPath->GetAbsOrigin(), targetPos ); - - // Check to see if we've hit the target, or the player's vehicle if it's a player in a vehicle - bool bHitTarget = ( pTargetEnt && ( pTargetEnt == pBlocker ) ) || - ( pVehicle && ( pVehicle == pBlocker ) ); - - // If we hit something, and it wasn't the target or his vehicle, then no dice - // If we hit the target and forced move was set, *still* no dice - if ( (pBlocker != NULL) && ( !bHitTarget || m_bForcedMove ) ) - continue; - } - - pNearestPath = pTravPath; - flNearestDist = flPathDist; - } - } - - return pNearestPath; -} - - -//----------------------------------------------------------------------------- -// Compute a point n units along a path -//----------------------------------------------------------------------------- -CPathTrack *CAI_TrackPather::ComputeLeadingPointAlongPath( const Vector &vecStartPoint, - CPathTrack *pFirstTrack, float flDistance, Vector *pTarget ) -{ - bool bMovingForward = (flDistance > 0.0f); - flDistance = fabs(flDistance); - - CPathTrack *pTravPath = pFirstTrack; - if ( (!bMovingForward) && pFirstTrack->GetPrevious() ) - { - pTravPath = pFirstTrack->GetPrevious(); - } - - *pTarget = vecStartPoint; - CPathTrack *pNextPath; - - // No circular loop checking needed; eventually, it'll run out of distance - for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath ) - { - pNextPath = bMovingForward ? pTravPath->GetNext() : pTravPath->GetPrevious(); - - // Find the distance between this test point and our goal point - float flPathDist = pTarget->DistTo( pTravPath->GetAbsOrigin() ); - - // Find the distance between this test point and our goal point - if ( flPathDist <= flDistance ) - { - flDistance -= flPathDist; - *pTarget = pTravPath->GetAbsOrigin(); - if ( !CPathTrack::ValidPath(pNextPath) ) - return bMovingForward ? pTravPath : pTravPath->GetNext(); - - continue; - } - - ComputeClosestPoint( *pTarget, flDistance, pTravPath->GetAbsOrigin(), pTarget ); - return bMovingForward ? pTravPath : pTravPath->GetNext(); - } - - return NULL; -} - - -//----------------------------------------------------------------------------- -// Compute the distance to a particular point on the path -//----------------------------------------------------------------------------- -float CAI_TrackPather::ComputeDistanceAlongPathToPoint( CPathTrack *pStartTrack, - CPathTrack *pDestTrack, const Vector &vecDestPosition, bool bMovingForward ) -{ - float flTotalDist = 0.0f; - - Vector vecPoint; - ClosestPointToCurrentPath( &vecPoint ); - - CPathTrack *pTravPath = pStartTrack; - CPathTrack *pNextPath, *pTestPath; - BEGIN_PATH_TRACK_ITERATION(); - for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath ) - { - // Circular loop checking - if ( pTravPath->HasBeenVisited() ) - break; - - // Mark it as being visited. - pTravPath->Visit(); - - pNextPath = bMovingForward ? pTravPath->GetNext() : pTravPath->GetPrevious(); - pTestPath = pTravPath; - Assert( pTestPath ); - - if ( pTravPath == pDestTrack ) - { - Vector vecDelta; - Vector vecPathDelta; - VectorSubtract( vecDestPosition, vecPoint, vecDelta ); - ComputePathDirection( pTravPath, &vecPathDelta ); - float flDot = DotProduct( vecDelta, vecPathDelta ); - flTotalDist += (flDot > 0.0f ? 1.0f : -1.0f) * vecDelta.Length2D(); - break; - } - - // NOTE: This would be made more accurate if we did the path direction check here too. - // The starting vecPoint is sometimes *not* within the bounds of the line segment. - - // Find the distance between this test point and our goal point - flTotalDist += (bMovingForward ? 1.0f : -1.0f) * vecPoint.AsVector2D().DistTo( pTestPath->GetAbsOrigin().AsVector2D() ); - vecPoint = pTestPath->GetAbsOrigin(); - } - - return flTotalDist; -} - - -//------------------------------------------------------------------------------ -// Track debugging info -//------------------------------------------------------------------------------ -void CAI_TrackPather::VisualizeDebugInfo( const Vector &vecNearestPoint, const Vector &vecTarget ) -{ - if ( g_debug_trackpather.GetInt() == TRACKPATHER_DEBUG_PATH ) - { - NDebugOverlay::Line( m_vecSegmentStartPoint, vecTarget, 0, 0, 255, true, 0.1f ); - NDebugOverlay::Cross3D( vecNearestPoint, -Vector(16,16,16), Vector(16,16,16), 255, 0, 0, true, 0.1f ); - NDebugOverlay::Cross3D( m_pCurrentPathTarget->GetAbsOrigin(), -Vector(16,16,16), Vector(16,16,16), 0, 255, 0, true, 0.1f ); - NDebugOverlay::Cross3D( m_vecDesiredPosition, -Vector(16,16,16), Vector(16,16,16), 0, 0, 255, true, 0.1f ); - NDebugOverlay::Cross3D( m_pDestPathTarget->GetAbsOrigin(), -Vector(16,16,16), Vector(16,16,16), 255, 255, 255, true, 0.1f ); - - if ( m_pTargetNearestPath ) - { - NDebugOverlay::Cross3D( m_pTargetNearestPath->GetAbsOrigin(), -Vector(24,24,24), Vector(24,24,24), 255, 0, 255, true, 0.1f ); - } - } - - if ( g_debug_trackpather.GetInt() == TRACKPATHER_DEBUG_TRACKS ) - { - if ( m_pCurrentPathTarget ) - { - CPathTrack *pPathTrack = m_pCurrentPathTarget; - for ( ; CPathTrack::ValidPath( pPathTrack ); pPathTrack = pPathTrack->GetNext() ) - { - NDebugOverlay::Box( pPathTrack->GetAbsOrigin(), -Vector(2,2,2), Vector(2,2,2), 0,255, 0, 8, 0.1 ); - if ( CPathTrack::ValidPath( pPathTrack->GetNext() ) ) - { - NDebugOverlay::Line( pPathTrack->GetAbsOrigin(), pPathTrack->GetNext()->GetAbsOrigin(), 0,255,0, true, 0.1 ); - } - - if ( pPathTrack->GetNext() == m_pCurrentPathTarget ) - break; - } - } - } -} - - -//------------------------------------------------------------------------------ -// Does this path track have LOS to the target? -//------------------------------------------------------------------------------ -bool CAI_TrackPather::HasLOSToTarget( CPathTrack *pTrack ) -{ - CBaseEntity *pTargetEnt = GetTrackPatherTargetEnt(); - if ( !pTargetEnt ) - return true; - - Vector targetPos; - if ( !GetTrackPatherTarget( &targetPos ) ) - return true; - - // Translate driver into vehicle for testing - CBaseEntity *pVehicle = NULL; - CBaseCombatCharacter *pCCTarget = pTargetEnt->MyCombatCharacterPointer(); - if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) - { - pVehicle = pCCTarget->GetVehicleEntity(); - } - - // If it has to be visible, run those checks - CBaseEntity *pBlocker = FindTrackBlocker( pTrack->GetAbsOrigin(), targetPos ); - - // Check to see if we've hit the target, or the player's vehicle if it's a player in a vehicle - bool bHitTarget = ( pTargetEnt && ( pTargetEnt == pBlocker ) ) || - ( pVehicle && ( pVehicle == pBlocker ) ); - - return (pBlocker == NULL) || bHitTarget; -} - - -//------------------------------------------------------------------------------ -// Moves to the track -//------------------------------------------------------------------------------ -void CAI_TrackPather::UpdateCurrentTarget() -{ - // Find the point along the line that we're closest to. - const Vector &vecTarget = m_pCurrentPathTarget->GetAbsOrigin(); - Vector vecPoint; - float t = ClosestPointToCurrentPath( &vecPoint ); - if ( (t < 1.0f) && ( vecPoint.DistToSqr( vecTarget ) > m_flTargetTolerance * m_flTargetTolerance ) ) - goto visualizeDebugInfo; - - // Forced move is gone as soon as we've reached the first point on our path - if ( m_bLeading ) - { - m_bForcedMove = false; - } - - // Trip our "path_track reached" output - if ( m_pCurrentPathTarget != m_pLastPathTarget ) - { - // Get the path's specified max speed - m_flPathMaxSpeed = m_pCurrentPathTarget->m_flSpeed; - - variant_t emptyVariant; - m_pCurrentPathTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 ); - m_pLastPathTarget = m_pCurrentPathTarget; - } - - if ( m_nPauseState == PAUSED_AT_POSITION ) - return; - - if ( m_nPauseState == PAUSE_AT_NEXT_LOS_POSITION ) - { - if ( HasLOSToTarget(m_pCurrentPathTarget) ) - { - m_nPauseState = PAUSED_AT_POSITION; - return; - } - } - - // Update our dest path target, if appropriate... - if ( m_pCurrentPathTarget == m_pDestPathTarget ) - { - m_bForcedMove = false; - SelectNewDestTarget(); - } - - // Did SelectNewDestTarget give us a new point to move to? - if ( m_pCurrentPathTarget != m_pDestPathTarget ) - { - // Update to the next path, if there is one... - m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); - if ( !m_pCurrentPathTarget ) - { - m_pCurrentPathTarget = m_pLastPathTarget; - } - } - else - { - // We're at rest (no patrolling behavior), which means we're moving forward now. - m_bMovingForward = true; - } - - SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() ); - m_vecSegmentStartSplinePoint = m_vecSegmentStartPoint; - m_vecSegmentStartPoint = m_pLastPathTarget->GetAbsOrigin(); - -visualizeDebugInfo: - VisualizeDebugInfo( vecPoint, vecTarget ); -} - - -//----------------------------------------------------------------------------- -// -// NOTE: All code below is used exclusively for leading/trailing behavior -// -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// Compute the distance to the leading position -//----------------------------------------------------------------------------- -float CAI_TrackPather::ComputeDistanceToLeadingPosition() -{ - return ComputeDistanceAlongPathToPoint( m_pCurrentPathTarget, m_pDestPathTarget, GetDesiredPosition(), m_bMovingForward ); -} - - -//----------------------------------------------------------------------------- -// Compute the distance to the *target* position -//----------------------------------------------------------------------------- -float CAI_TrackPather::ComputeDistanceToTargetPosition() -{ - Assert( m_pTargetNearestPath ); - - CPathTrack *pDest = m_bMovingForward ? m_pTargetNearestPath.Get() : m_pTargetNearestPath->GetPrevious(); - if ( !pDest ) - { - pDest = m_pTargetNearestPath; - } - bool bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pDest ); - - CPathTrack *pStart = m_pCurrentPathTarget; - if ( bMovingForward != m_bMovingForward ) - { - if (bMovingForward) - { - if ( pStart->GetNext() ) - { - pStart = pStart->GetNext(); - } - if ( pDest->GetNext() ) - { - pDest = pDest->GetNext(); - } - } - else - { - if ( pStart->GetPrevious() ) - { - pStart = pStart->GetPrevious(); - } - if ( pDest->GetPrevious() ) - { - pDest = pDest->GetPrevious(); - } - } - } - - return ComputeDistanceAlongPathToPoint( pStart, pDest, m_vecTargetPathPoint, bMovingForward ); -} - - -//----------------------------------------------------------------------------- -// Compute a path direction -//----------------------------------------------------------------------------- -void CAI_TrackPather::ComputePathDirection( CPathTrack *pPath, Vector *pVecPathDir ) -{ - if ( pPath->GetPrevious() ) - { - VectorSubtract( pPath->GetAbsOrigin(), pPath->GetPrevious()->GetAbsOrigin(), *pVecPathDir ); - } - else - { - if ( pPath->GetNext() ) - { - VectorSubtract( pPath->GetNext()->GetAbsOrigin(), pPath->GetAbsOrigin(), *pVecPathDir ); - } - else - { - pVecPathDir->Init( 1, 0, 0 ); - } - } - VectorNormalize( *pVecPathDir ); -} - - -//----------------------------------------------------------------------------- -// What's the current path direction? -//----------------------------------------------------------------------------- -void CAI_TrackPather::CurrentPathDirection( Vector *pVecPathDir ) -{ - if ( m_pCurrentPathTarget ) - { - ComputePathDirection( m_pCurrentPathTarget, pVecPathDir ); - } - else - { - pVecPathDir->Init( 0, 0, 1 ); - } -} - - - -//----------------------------------------------------------------------------- -// Compute a point n units along the current path from our current position -// (but don't pass the desired target point) -//----------------------------------------------------------------------------- -void CAI_TrackPather::ComputePointAlongCurrentPath( float flDistance, float flPerpDist, Vector *pTarget ) -{ - Vector vecPathDir; - Vector vecStartPoint; - ClosestPointToCurrentPath( &vecStartPoint ); - *pTarget = vecStartPoint; - - if ( flDistance != 0.0f ) - { - Vector vecPrevPoint = vecStartPoint; - CPathTrack *pTravPath = m_pCurrentPathTarget; - CPathTrack *pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget ); - for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = NextAlongCurrentPath( pTravPath ) ) - { - if ( pTravPath == pAdjustedDest ) - { - ComputePathDirection( pTravPath, &vecPathDir ); - - float flPathDist = pTarget->DistTo( GetDesiredPosition() ); - if ( flDistance > flPathDist ) - { - *pTarget = GetDesiredPosition(); - } - else - { - ComputeClosestPoint( *pTarget, flDistance, GetDesiredPosition(), pTarget ); - } - break; - } - - // Find the distance between this test point and our goal point - float flPathDist = pTarget->DistTo( pTravPath->GetAbsOrigin() ); - - // Find the distance between this test point and our goal point - if ( flPathDist <= flDistance ) - { - flDistance -= flPathDist; - *pTarget = pTravPath->GetAbsOrigin(); - - // FIXME: Reduce the distance further based on the angle between this segment + the next - continue; - } - - ComputePathDirection( pTravPath, &vecPathDir ); - ComputeClosestPoint( *pTarget, flDistance, pTravPath->GetAbsOrigin(), pTarget ); - break; - } - } - else - { - VectorSubtract( m_pCurrentPathTarget->GetAbsOrigin(), m_vecSegmentStartPoint, vecPathDir ); - VectorNormalize( vecPathDir ); - } - - // Add in the horizontal component - ComputePointFromPerpDistance( *pTarget, vecPathDir, flPerpDist, pTarget ); -} - - -//----------------------------------------------------------------------------- -// Methods to find a signed perp distance from the track -// and to compute a point off the path based on the signed perp distance -//----------------------------------------------------------------------------- -float CAI_TrackPather::ComputePerpDistanceFromPath( const Vector &vecPointOnPath, const Vector &vecPathDir, const Vector &vecPointOffPath ) -{ - // Make it be a signed distance of the target from the path - // Positive means on the right side, negative means on the left side - Vector vecAcross, vecDelta; - CrossProduct( vecPathDir, Vector( 0, 0, 1 ), vecAcross ); - VectorSubtract( vecPointOffPath, vecPointOnPath, vecDelta ); - VectorMA( vecDelta, -DotProduct( vecPathDir, vecDelta ), vecPathDir, vecDelta ); - - float flDistanceFromPath = vecDelta.Length2D(); - if ( DotProduct2D( vecAcross.AsVector2D(), vecDelta.AsVector2D() ) < 0.0f ) - { - flDistanceFromPath *= -1.0f; - } - - return flDistanceFromPath; -} - -void CAI_TrackPather::ComputePointFromPerpDistance( const Vector &vecPointOnPath, const Vector &vecPathDir, float flPerpDist, Vector *pResult ) -{ - Vector vecAcross; - CrossProduct( vecPathDir, Vector( 0, 0, 1 ), vecAcross ); - VectorMA( vecPointOnPath, flPerpDist, vecAcross, *pResult ); -} - - -//----------------------------------------------------------------------------- -// Finds the closest point on the path, returns a signed perpendicular distance -// where negative means on the left side of the path (when travelled from prev to next) -// and positive means on the right side -//----------------------------------------------------------------------------- -CPathTrack *CAI_TrackPather::FindClosestPointOnPath( CPathTrack *pPath, - const Vector &targetPos, Vector *pVecClosestPoint, Vector *pVecPathDir, float *pDistanceFromPath ) -{ - // Find the node nearest to the destination path target if a path is not specified - if ( pPath == NULL ) - { - pPath = m_pDestPathTarget; - } - - // If the path node we're trying to use is not valid, then we're done. - if ( CPathTrack::ValidPath( pPath ) == NULL ) - { - //FIXME: Implement - Assert(0); - return NULL; - } - - // Find the nearest node to the target (going forward) - CPathTrack *pNearestPath = NULL; - float flNearestDist2D = 999999999; - float flNearestDist = 999999999; - float flPathDist, flPathDist2D; - - // NOTE: Gotta do it this crazy way because paths can be one-way. - Vector vecNearestPoint; - Vector vecNearestPathSegment; - for ( int i = 0; i < 2; ++i ) - { - int loopCheck = 0; - CPathTrack *pTravPath = pPath; - CPathTrack *pNextPath; - - BEGIN_PATH_TRACK_ITERATION(); - for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath, loopCheck++ ) - { - // Circular loop checking - if ( pTravPath->HasBeenVisited() ) - break; - - // Mark it as being visited. - pTravPath->Visit(); - - pNextPath = (i == 0) ? pTravPath->GetPrevious() : pTravPath->GetNext(); - - // No alt paths allowed in leading mode. - if ( pTravPath->m_paltpath ) - { - Warning( "%s: Alternative paths in path_track not allowed when using the leading behavior!\n", GetEntityName() ); - } - - // Need line segments - if ( !CPathTrack::ValidPath(pNextPath) ) - break; - - // Find the closest point on the line segment on the path - Vector vecClosest; - CalcClosestPointOnLineSegment( targetPos, pTravPath->GetAbsOrigin(), pNextPath->GetAbsOrigin(), vecClosest ); - - // Find the distance between this test point and our goal point - flPathDist2D = vecClosest.AsVector2D().DistToSqr( targetPos.AsVector2D() ); - if ( flPathDist2D > flNearestDist2D ) - continue; - - flPathDist = vecClosest.z - targetPos.z; - flPathDist *= flPathDist; - flPathDist += flPathDist2D; - if (( flPathDist2D == flNearestDist2D ) && ( flPathDist >= flNearestDist )) - continue; - - pNearestPath = (i == 0) ? pTravPath : pNextPath; - flNearestDist2D = flPathDist2D; - flNearestDist = flPathDist; - vecNearestPoint = vecClosest; - VectorSubtract( pNextPath->GetAbsOrigin(), pTravPath->GetAbsOrigin(), vecNearestPathSegment ); - if ( i == 0 ) - { - vecNearestPathSegment *= -1.0f; - } - } - } - - VectorNormalize( vecNearestPathSegment ); - *pDistanceFromPath = ComputePerpDistanceFromPath( vecNearestPoint, vecNearestPathSegment, targetPos ); - *pVecClosestPoint = vecNearestPoint; - *pVecPathDir = vecNearestPathSegment; - return pNearestPath; -} - - -//----------------------------------------------------------------------------- -// Breakable paths? -//----------------------------------------------------------------------------- -void CAI_TrackPather::InputStartBreakableMovement( inputdata_t &inputdata ) -{ - m_bPatrolBreakable = true; -} - -void CAI_TrackPather::InputStopBreakableMovement( inputdata_t &inputdata ) -{ - m_bPatrolBreakable = false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CAI_TrackPather::InputStartPatrol( inputdata_t &inputdata ) -{ - m_bPatrolling = true; -} - - -//----------------------------------------------------------------------------- -// -//----------------------------------------------------------------------------- -void CAI_TrackPather::InputStopPatrol( inputdata_t &inputdata ) -{ - m_bPatrolling = false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CAI_TrackPather::InputStartPatrolBreakable( inputdata_t &inputdata ) -{ - m_bPatrolBreakable = true; - m_bPatrolling = true; -} - - -//------------------------------------------------------------------------------ -// Leading behaviors -//------------------------------------------------------------------------------ -void CAI_TrackPather::InputStartLeading( inputdata_t &inputdata ) -{ - EnableLeading( true ); - SetLeadingDistance( inputdata.value.Int() ); -} - -void CAI_TrackPather::InputStopLeading( inputdata_t &inputdata ) -{ - EnableLeading( false ); -} - - -//------------------------------------------------------------------------------ -// Selects a new destination target -//------------------------------------------------------------------------------ -void CAI_TrackPather::SelectNewDestTarget() -{ - if ( !m_bPatrolling ) - return; - - // NOTE: This version is bugged, but I didn't want to make the fix - // here for fear of breaking a lot of maps late in the day. - // So, only the chopper does the "right" thing. -#ifdef HL2_EPISODIC - // Episodic uses the fixed logic for all trackpathers - if ( 1 ) -#else - if ( ShouldUseFixedPatrolLogic() ) -#endif - { - CPathTrack *pOldDest = m_pDestPathTarget; - - // Only switch polarity of movement if we're at the *end* of the path - // This is really useful for initial conditions of patrolling - // NOTE: We've got to do some extra work for circular paths - bool bIsCircular = false; - { - BEGIN_PATH_TRACK_ITERATION(); - CPathTrack *pTravPath = m_pDestPathTarget; - while( CPathTrack::ValidPath( pTravPath ) ) - { - // Circular loop checking - if ( pTravPath->HasBeenVisited() ) - { - bIsCircular = true; - break; - } - - pTravPath->Visit(); - pTravPath = NextAlongCurrentPath( pTravPath ); - } - } - - if ( bIsCircular || (NextAlongCurrentPath( m_pDestPathTarget ) == NULL) ) - { - m_bMovingForward = !m_bMovingForward; - } - - BEGIN_PATH_TRACK_ITERATION(); - - while ( true ) - { - CPathTrack *pNextTrack = NextAlongCurrentPath( m_pDestPathTarget ); - if ( !pNextTrack || (pNextTrack == pOldDest) || pNextTrack->HasBeenVisited() ) - break; - - pNextTrack->Visit(); - m_pDestPathTarget = pNextTrack; - } - } - else - { - CPathTrack *pOldDest = m_pDestPathTarget; - - // For patrolling, switch the polarity of movement - m_bMovingForward = !m_bMovingForward; - - int loopCount = 0; - while ( true ) - { - CPathTrack *pNextTrack = NextAlongCurrentPath( m_pDestPathTarget ); - if ( !pNextTrack ) - break; - if ( ++loopCount > 1024 ) - { - DevMsg(1,"WARNING: Looping path for %s\n", GetDebugName() ); - break; - } - - m_pDestPathTarget = pNextTrack; - } - - if ( m_pDestPathTarget == pOldDest ) - { - // This can occur if we move to the first point on the path - SelectNewDestTarget(); - } - } -} - - -//------------------------------------------------------------------------------ -// Moves to the track -//------------------------------------------------------------------------------ -void CAI_TrackPather::UpdateCurrentTargetLeading() -{ - bool bRestingAtDest = false; - CPathTrack *pAdjustedDest; - - // Find the point along the line that we're closest to. - const Vector &vecTarget = m_pCurrentPathTarget->GetAbsOrigin(); - Vector vecPoint; - float t = ClosestPointToCurrentPath( &vecPoint ); - if ( (t < 1.0f) && ( vecPoint.DistToSqr( vecTarget ) > m_flTargetTolerance * m_flTargetTolerance ) ) - goto visualizeDebugInfo; - - // Trip our "path_track reached" output - if ( m_pCurrentPathTarget != m_pLastPathTarget ) - { - // Get the path's specified max speed - m_flPathMaxSpeed = m_pCurrentPathTarget->m_flSpeed; - - variant_t emptyVariant; - m_pCurrentPathTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 ); - m_pLastPathTarget = m_pCurrentPathTarget; - } - - // NOTE: CurrentPathTarget doesn't mean the same thing as dest path target! - // It's the "next"most when moving forward + "prev"most when moving backward - // Must do the tests in the same space - pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget ); - - // Update our dest path target, if appropriate... - if ( m_pCurrentPathTarget == pAdjustedDest ) - { - m_bForcedMove = false; - SelectNewDestTarget(); - - // NOTE: Must do this again since SelectNewDestTarget may change m_pDestPathTarget - pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget ); - } - - if ( m_pCurrentPathTarget != pAdjustedDest ) - { - // Update to the next path, if there is one... - m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); - if ( !m_pCurrentPathTarget ) - { - m_pCurrentPathTarget = m_pLastPathTarget; - } - } - else - { - // NOTE: Have to do this here because the NextAlongCurrentPath call above - // could make m_pCurrentPathTarget == m_pDestPathTarget. - // In this case, we're at rest (no patrolling behavior) - bRestingAtDest = true; - } - - if ( bRestingAtDest ) - { - // NOTE: Must use current path target, instead of dest - // to get the PreviousAlongCurrentPath working correctly - CPathTrack *pSegmentStart = PreviousAlongCurrentPath( m_pCurrentPathTarget ); - if ( !pSegmentStart ) - { - pSegmentStart = m_pCurrentPathTarget; - } - m_vecSegmentStartPoint = pSegmentStart->GetAbsOrigin(); - } - else - { - m_vecSegmentStartPoint = m_pLastPathTarget->GetAbsOrigin(); - } - -visualizeDebugInfo: - VisualizeDebugInfo( vecPoint, vecTarget ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_TrackPather::UpdateTargetPositionLeading( void ) -{ - Vector targetPos; - if ( !GetTrackPatherTarget( &targetPos ) ) - return; - - // NOTE: FindClosestPointOnPath *always* returns the point on the "far", - // end of the line segment containing the closest point (namely the 'next' - // track, as opposed to the 'prev' track) - Vector vecClosestPoint, vecPathDir; - float flTargetDistanceFromPath; - CPathTrack *pNextPath = FindClosestPointOnPath( m_pCurrentPathTarget, - targetPos, &vecClosestPoint, &vecPathDir, &flTargetDistanceFromPath ); - - // This means that a valid path could not be found to our target! - if ( CPathTrack::ValidPath( pNextPath ) == NULL ) - return; - -// NDebugOverlay::Cross3D( vecClosestPoint, -Vector(24,24,24), Vector(24,24,24), 0, 255, 255, true, 0.1f ); -// NDebugOverlay::Cross3D( pNextPath->GetAbsOrigin(), -Vector(24,24,24), Vector(24,24,24), 255, 255, 0, true, 0.1f ); - - // Here's how far we are from the path - m_flTargetDistFromPath = flTargetDistanceFromPath; - m_vecTargetPathDir = vecPathDir; - - // Here's info about where the target is along the path - m_vecTargetPathPoint = vecClosestPoint; - m_pTargetNearestPath = pNextPath; - - // Find the best position to be on our path - // NOTE: This will *also* return a path track on the "far" end of the line segment - // containing the leading position, namely the "next" end of the segment as opposed - // to the "prev" end of the segment. - CPathTrack *pDest = ComputeLeadingPointAlongPath( vecClosestPoint, pNextPath, m_flLeadDistance, &targetPos ); - SetDesiredPosition( targetPos ); - - // We only want to switch movement directions when absolutely necessary - // so convert dest into a more appropriate value based on the current movement direction - if ( pDest != m_pDestPathTarget ) - { - // NOTE: This is really tricky + subtle - // For leading, we don't want to ever change direction when the current target == the - // adjusted destination target. Namely, if we're going forward, both dest + curr - // mean the "next"most node so we can compare them directly against eath other. - // If we're moving backward, dest means "next"most, but curr means "prev"most. - // We first have to adjust the dest to mean "prev"most, and then do the comparison. - // If the adjusted dest == curr, then maintain direction. Otherwise, use the forward along path test. - bool bMovingForward = m_bMovingForward; - CPathTrack *pAdjustedDest = AdjustForMovementDirection( pDest ); - if ( m_pCurrentPathTarget != pAdjustedDest ) - { - bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pAdjustedDest ); - } - - if ( bMovingForward != m_bMovingForward ) - { - // As a result of the tricky note above, this should never occur - Assert( pAdjustedDest != m_pCurrentPathTarget ); - - // Oops! Need to reverse direction - m_bMovingForward = bMovingForward; - m_vecSegmentStartPoint = m_pCurrentPathTarget->GetAbsOrigin(); - m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); - } - m_pDestPathTarget = pDest; - } - -// NDebugOverlay::Cross3D( m_pCurrentPathTarget->GetAbsOrigin(), -Vector(36,36,36), Vector(36,36,36), 255, 0, 0, true, 0.1f ); -// NDebugOverlay::Cross3D( m_pDestPathTarget->GetAbsOrigin(), -Vector(48,48,48), Vector(48,48,48), 0, 255, 0, true, 0.1f ); -// NDebugOverlay::Cross3D( targetPos, -Vector(36,36,36), Vector(36,36,36), 0, 0, 255, true, 0.1f ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CAI_TrackPather::UpdateTargetPosition( void ) -{ - // Don't update our target if we're being told to go somewhere - if ( m_bForcedMove && !m_bPatrolBreakable ) - return; - - // Don't update our target if we're patrolling - if ( m_bPatrolling ) - { - // If we have an enemy, and our patrol is breakable, stop patrolling - if ( !m_bPatrolBreakable || !GetEnemy() ) - return; - - m_bPatrolling = false; - } - - Vector targetPos; - if ( !GetTrackPatherTarget( &targetPos ) ) - return; - - // Not time to update again - if ( m_flEnemyPathUpdateTime > gpGlobals->curtime ) - return; - - // See if the target has moved enough to make us recheck - float flDistSqr = ( targetPos - m_vecLastGoalCheckPosition ).LengthSqr(); - if ( flDistSqr < m_flTargetDistanceThreshold * m_flTargetDistanceThreshold ) - return; - - // Find the best position to be on our path - CPathTrack *pDest = BestPointOnPath( m_pCurrentPathTarget, targetPos, m_flAvoidDistance, true, m_bChooseFarthestPoint ); - - if ( CPathTrack::ValidPath( pDest ) == NULL ) - { - // This means that a valid path could not be found to our target! -// Assert(0); - return; - } - - if ( pDest != m_pDestPathTarget ) - { - // This is our new destination - bool bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pDest ); - if ( bMovingForward != m_bMovingForward ) - { - // Oops! Need to reverse direction - m_bMovingForward = bMovingForward; - if ( pDest != m_pCurrentPathTarget ) - { - SetupNewCurrentTarget( NextAlongCurrentPath( m_pCurrentPathTarget ) ); - } - } - m_pDestPathTarget = pDest; - } - - // Keep this goal point for comparisons later - m_vecLastGoalCheckPosition = targetPos; - - // Only do this on set intervals - m_flEnemyPathUpdateTime = gpGlobals->curtime + 1.0f; -} - - -//------------------------------------------------------------------------------ -// Returns the direction of the path at the closest point to the target -//------------------------------------------------------------------------------ -const Vector &CAI_TrackPather::TargetPathDirection() const -{ - return m_vecTargetPathDir; -} - -const Vector &CAI_TrackPather::TargetPathAcrossDirection() const -{ - static Vector s_Result; - CrossProduct( m_vecTargetPathDir, Vector( 0, 0, 1 ), s_Result ); - return s_Result; -} - - -//------------------------------------------------------------------------------ -// Returns the speed of the target relative to the path -//------------------------------------------------------------------------------ -float CAI_TrackPather::TargetSpeedAlongPath() const -{ - if ( !GetEnemy() || !IsLeading() ) - return 0.0f; - - Vector vecSmoothedVelocity = GetEnemy()->GetSmoothedVelocity(); - return DotProduct( vecSmoothedVelocity, TargetPathDirection() ); -} - - -//------------------------------------------------------------------------------ -// Returns the speed of the target *across* the path -//------------------------------------------------------------------------------ -float CAI_TrackPather::TargetSpeedAcrossPath() const -{ - if ( !GetEnemy() || !IsLeading() ) - return 0.0f; - - Vector vecSmoothedVelocity = GetEnemy()->GetSmoothedVelocity(); - return DotProduct( vecSmoothedVelocity, TargetPathAcrossDirection() ); -} - - -//------------------------------------------------------------------------------ -// Returns the max distance we can be from the path -//------------------------------------------------------------------------------ -float CAI_TrackPather::MaxDistanceFromCurrentPath() const -{ - if ( !IsLeading() || !m_pCurrentPathTarget ) - return 0.0f; - - CPathTrack *pPrevPath = PreviousAlongCurrentPath( m_pCurrentPathTarget ); - if ( !pPrevPath ) - { - pPrevPath = m_pCurrentPathTarget; - } - - // NOTE: Can't use m_vecSegmentStartPoint because we don't have a radius defined for it - float t; - Vector vecTemp; - CalcClosestPointOnLine( GetAbsOrigin(), pPrevPath->GetAbsOrigin(), - m_pCurrentPathTarget->GetAbsOrigin(), vecTemp, &t ); - t = clamp( t, 0.0f, 1.0f ); - float flRadius = (1.0f - t) * pPrevPath->GetRadius() + t * m_pCurrentPathTarget->GetRadius(); - return flRadius; -} - - -//------------------------------------------------------------------------------ -// Purpose : A different version of the track pather which is more explicit about -// the meaning of dest, current, and prev path points -//------------------------------------------------------------------------------ -void CAI_TrackPather::UpdateTrackNavigation( void ) -{ - // No target? Use the string specified. We have no spawn method (sucky!!) so this is how that works - if ( ( CPathTrack::ValidPath( m_pDestPathTarget ) == NULL ) && ( m_target != NULL_STRING ) ) - { - FlyToPathTrack( m_target ); - m_target = NULL_STRING; - } - - if ( !IsLeading() ) - { - if ( !m_pCurrentPathTarget ) - return; - - // Updates our destination node if we're tracking something - UpdateTargetPosition(); - - // Move along our path towards our current destination - UpdateCurrentTarget(); - } - else - { - // Updates our destination position if we're leading something - UpdateTargetPositionLeading(); - - // Move along our path towards our current destination - UpdateCurrentTargetLeading(); - } -} - - -//------------------------------------------------------------------------------ -// Sets the farthest path distance -//------------------------------------------------------------------------------ -void CAI_TrackPather::SetFarthestPathDist( float flMaxPathDist ) -{ - m_flFarthestPathDist = flMaxPathDist; -} - - -//------------------------------------------------------------------------------ -// Sets up a new current path target -//------------------------------------------------------------------------------ -void CAI_TrackPather::SetupNewCurrentTarget( CPathTrack *pTrack ) -{ - Assert( pTrack ); - m_vecSegmentStartPoint = GetAbsOrigin(); - VectorMA( m_vecSegmentStartPoint, -2.0f, GetAbsVelocity(), m_vecSegmentStartSplinePoint ); - m_pCurrentPathTarget = pTrack; - SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() ); -} - - -//------------------------------------------------------------------------------ -// Moves to an explicit track point -//------------------------------------------------------------------------------ -void CAI_TrackPather::MoveToTrackPoint( CPathTrack *pTrack ) -{ - if ( IsOnSameTrack( pTrack, m_pDestPathTarget ) ) - { - // The track must be valid - if ( CPathTrack::ValidPath( pTrack ) == NULL ) - return; - - m_pDestPathTarget = pTrack; - m_bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pTrack ); - m_bForcedMove = true; - } - else - { - CPathTrack *pClosestTrack = BestPointOnPath( pTrack, WorldSpaceCenter(), 0.0f, false, false ); - - // The track must be valid - if ( CPathTrack::ValidPath( pClosestTrack ) == NULL ) - return; - - SetupNewCurrentTarget( pClosestTrack ); - m_pDestPathTarget = pTrack; - m_bMovingForward = IsForwardAlongPath( pClosestTrack, pTrack ); - m_bForcedMove = true; - } -} - - -//------------------------------------------------------------------------------ -// Moves to the closest track point -//------------------------------------------------------------------------------ -void CAI_TrackPather::MoveToClosestTrackPoint( CPathTrack *pTrack ) -{ - if ( IsOnSameTrack( pTrack, m_pDestPathTarget ) ) - return; - - CPathTrack *pClosestTrack = BestPointOnPath( pTrack, WorldSpaceCenter(), 0.0f, false, false ); - - // The track must be valid - if ( CPathTrack::ValidPath( pClosestTrack ) == NULL ) - return; - - SetupNewCurrentTarget( pClosestTrack ); - m_pDestPathTarget = pClosestTrack; - m_bMovingForward = true; - - // Force us to switch tracks if we're leading - if ( IsLeading() ) - { - m_bForcedMove = true; - } -} - - -//----------------------------------------------------------------------------- -// Are the two path tracks connected? -//----------------------------------------------------------------------------- -bool CAI_TrackPather::IsOnSameTrack( CPathTrack *pPath1, CPathTrack *pPath2 ) const -{ - if ( pPath1 == pPath2 ) - return true; - - { - BEGIN_PATH_TRACK_ITERATION(); - CPathTrack *pTravPath = pPath1->GetPrevious(); - while( CPathTrack::ValidPath( pTravPath ) && (pTravPath != pPath1) ) - { - // Circular loop checking - if ( pTravPath->HasBeenVisited() ) - break; - - pTravPath->Visit(); - - if ( pTravPath == pPath2 ) - return true; - - pTravPath = pTravPath->GetPrevious(); - } - } - - { - BEGIN_PATH_TRACK_ITERATION(); - CPathTrack *pTravPath = pPath1->GetNext(); - while( CPathTrack::ValidPath( pTravPath ) && (pTravPath != pPath1) ) - { - // Circular loop checking - if ( pTravPath->HasBeenVisited() ) - break; - - pTravPath->Visit(); - - if ( pTravPath == pPath2 ) - return true; - - pTravPath = pTravPath->GetNext(); - } - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Deal with teleportation -//----------------------------------------------------------------------------- -void CAI_TrackPather::Teleported() -{ - // This updates the paths so they are reasonable - CPathTrack *pClosestTrack = BestPointOnPath( GetDestPathTarget(), WorldSpaceCenter(), 0.0f, false, false ); - m_pDestPathTarget = NULL; - MoveToClosestTrackPoint( pClosestTrack ); -} - - -//----------------------------------------------------------------------------- -// Returns distance along path to target, returns FLT_MAX if there's no path -//----------------------------------------------------------------------------- -float CAI_TrackPather::ComputePathDistance( CPathTrack *pPath, CPathTrack *pDest, bool bForward ) const -{ - float flDist = 0.0f; - CPathTrack *pLast = pPath; - - BEGIN_PATH_TRACK_ITERATION(); - while ( CPathTrack::ValidPath( pPath ) ) - { - // Ciruclar loop checking - if ( pPath->HasBeenVisited() ) - return FLT_MAX; - - pPath->Visit(); - - flDist += pLast->GetAbsOrigin().DistTo( pPath->GetAbsOrigin() ); - - if ( pDest == pPath ) - return flDist; - - pLast = pPath; - pPath = bForward ? pPath->GetNext() : pPath->GetPrevious(); - } - - return FLT_MAX; -} - - -//----------------------------------------------------------------------------- -// Is pPathTest in "front" of pPath on the same path? (Namely, does GetNext() get us there?) -//----------------------------------------------------------------------------- -bool CAI_TrackPather::IsForwardAlongPath( CPathTrack *pPath, CPathTrack *pPathTest ) const -{ - // Also, in the case of looping paths, we want to return the shortest path - float flForwardDist = ComputePathDistance( pPath, pPathTest, true ); - float flReverseDist = ComputePathDistance( pPath, pPathTest, false ); - - Assert( ( flForwardDist != FLT_MAX ) || ( flReverseDist != FLT_MAX ) ); - return ( flForwardDist <= flReverseDist ); -} - - -//----------------------------------------------------------------------------- -// Computes distance + nearest point from the current path.. -//----------------------------------------------------------------------------- -float CAI_TrackPather::ClosestPointToCurrentPath( Vector *pVecPoint ) const -{ - if (!m_pCurrentPathTarget) - { - *pVecPoint = GetAbsOrigin(); - return 0; - } - - float t; - CalcClosestPointOnLine( GetAbsOrigin(), m_vecSegmentStartPoint, - m_pCurrentPathTarget->GetAbsOrigin(), *pVecPoint, &t ); - return t; -} - - -//----------------------------------------------------------------------------- -// Computes a "path" velocity at a particular point along the current path -//----------------------------------------------------------------------------- -void CAI_TrackPather::ComputePathTangent( float t, Vector *pVecTangent ) const -{ - CPathTrack *pNextTrack = NextAlongCurrentPath(m_pCurrentPathTarget); - if ( !pNextTrack ) - { - pNextTrack = m_pCurrentPathTarget; - } - - t = clamp( t, 0.0f, 1.0f ); - pVecTangent->Init(0,0,0); - Catmull_Rom_Spline_Tangent( m_vecSegmentStartSplinePoint, m_vecSegmentStartPoint, - m_pCurrentPathTarget->GetAbsOrigin(), pNextTrack->GetAbsOrigin(), t, *pVecTangent ); - VectorNormalize( *pVecTangent ); -} - - -//----------------------------------------------------------------------------- -// Computes the *normalized* velocity at which the helicopter should approach the final point -//----------------------------------------------------------------------------- -void CAI_TrackPather::ComputeNormalizedDestVelocity( Vector *pVecVelocity ) const -{ - if ( m_nPauseState != PAUSE_NO_PAUSE ) - { - pVecVelocity->Init(0,0,0); - return; - } - - CPathTrack *pNextTrack = NextAlongCurrentPath(m_pCurrentPathTarget); - if ( !pNextTrack ) - { - pNextTrack = m_pCurrentPathTarget; - } - - if ( ( pNextTrack == m_pCurrentPathTarget ) || ( m_pCurrentPathTarget == m_pDestPathTarget ) ) - { - pVecVelocity->Init(0,0,0); - return; - } - - VectorSubtract( pNextTrack->GetAbsOrigin(), m_pCurrentPathTarget->GetAbsOrigin(), *pVecVelocity ); - VectorNormalize( *pVecVelocity ); - - // Slow it down if we're approaching a sharp corner - Vector vecDelta; - VectorSubtract( m_pCurrentPathTarget->GetAbsOrigin(), m_vecSegmentStartPoint, vecDelta ); - VectorNormalize( vecDelta ); - float flDot = DotProduct( *pVecVelocity, vecDelta ); - *pVecVelocity *= clamp( flDot, 0.0f, 1.0f ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CAI_TrackPather::SetTrack( CBaseEntity *pGoalEnt ) -{ - // Ignore this input if we're *already* on that path. - CPathTrack *pTrack = dynamic_cast(pGoalEnt); - if ( !pTrack ) - { - DevWarning( "%s: Specified entity '%s' must be a path_track!\n", pGoalEnt->GetClassname(), pGoalEnt->GetEntityName() ); - return; - } - - MoveToClosestTrackPoint( pTrack ); -} - -void CAI_TrackPather::SetTrack( string_t strTrackName ) -{ - // Find our specified target - CBaseEntity *pGoalEnt = gEntList.FindEntityByName( NULL, strTrackName ); - if ( pGoalEnt == NULL ) - { - DevWarning( "%s: Could not find path_track '%s'!\n", GetClassname(), STRING( strTrackName ) ); - return; - } - - SetTrack( pGoalEnt ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CAI_TrackPather::InputSetTrack( inputdata_t &inputdata ) -{ - string_t strTrackName = MAKE_STRING( inputdata.value.String() ); - SetTrack( MAKE_STRING( inputdata.value.String() ) ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : strTrackName - -//----------------------------------------------------------------------------- -void CAI_TrackPather::FlyToPathTrack( string_t strTrackName ) -{ - CBaseEntity *pGoalEnt = gEntList.FindEntityByName( NULL, strTrackName ); - if ( pGoalEnt == NULL ) - { - DevWarning( "%s: Could not find path_track '%s'!\n", GetClassname(), STRING( strTrackName ) ); - return; - } - - // Ignore this input if we're *already* on that path. - CPathTrack *pTrack = dynamic_cast(pGoalEnt); - if ( !pTrack ) - { - DevWarning( "%s: Specified entity '%s' must be a path_track!\n", GetClassname(), STRING( strTrackName ) ); - return; - } - - // Find our specified target - MoveToTrackPoint( pTrack ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CAI_TrackPather::InputFlyToPathTrack( inputdata_t &inputdata ) -{ - // Find our specified target - string_t strTrackName = MAKE_STRING( inputdata.value.String() ); - FlyToPathTrack( strTrackName ); -} - - -//----------------------------------------------------------------------------- -// Changes the mode used to determine which path point to move to -//----------------------------------------------------------------------------- -void CAI_TrackPather::InputChooseFarthestPathPoint( inputdata_t &inputdata ) -{ - UseFarthestPathPoint( true ); -} - -void CAI_TrackPather::InputChooseNearestPathPoint( inputdata_t &inputdata ) -{ - UseFarthestPathPoint( false ); -} - -void CAI_TrackPather::UseFarthestPathPoint( bool useFarthest ) -{ - m_bChooseFarthestPoint = useFarthest; -} +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" + +#include "trains.h" +#include "ai_trackpather.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define TRACKPATHER_DEBUG_LEADING 1 +#define TRACKPATHER_DEBUG_PATH 2 +#define TRACKPATHER_DEBUG_TRACKS 3 +ConVar g_debug_trackpather( "g_debug_trackpather", "0", FCVAR_CHEAT ); + +//------------------------------------------------------------------------------ + +BEGIN_DATADESC( CAI_TrackPather ) + DEFINE_FIELD( m_vecDesiredPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecGoalOrientation, FIELD_VECTOR ), + + DEFINE_FIELD( m_pCurrentPathTarget, FIELD_CLASSPTR ), + DEFINE_FIELD( m_pDestPathTarget, FIELD_CLASSPTR ), + DEFINE_FIELD( m_pLastPathTarget, FIELD_CLASSPTR ), + DEFINE_FIELD( m_pTargetNearestPath, FIELD_CLASSPTR ), + + DEFINE_FIELD( m_strCurrentPathName, FIELD_STRING ), + DEFINE_FIELD( m_strDestPathName, FIELD_STRING ), + DEFINE_FIELD( m_strLastPathName, FIELD_STRING ), + DEFINE_FIELD( m_strTargetNearestPathName, FIELD_STRING ), + + DEFINE_FIELD( m_vecLastGoalCheckPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_flEnemyPathUpdateTime, FIELD_TIME ), + DEFINE_FIELD( m_bForcedMove, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bPatrolling, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bPatrolBreakable, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bLeading, FIELD_BOOLEAN ), + + // Derived class pathing data + DEFINE_FIELD( m_flTargetDistanceThreshold, FIELD_FLOAT ), + DEFINE_FIELD( m_flAvoidDistance, FIELD_FLOAT ), + + DEFINE_FIELD( m_flTargetTolerance, FIELD_FLOAT ), + DEFINE_FIELD( m_vecSegmentStartPoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecSegmentStartSplinePoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_bMovingForward, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bChooseFarthestPoint, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flFarthestPathDist, FIELD_FLOAT ), + DEFINE_FIELD( m_flPathMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( m_flTargetDistFromPath, FIELD_FLOAT ), + DEFINE_FIELD( m_flLeadDistance, FIELD_FLOAT ), + DEFINE_FIELD( m_vecTargetPathDir, FIELD_VECTOR ), + DEFINE_FIELD( m_vecTargetPathPoint, FIELD_POSITION_VECTOR ), + + DEFINE_FIELD( m_nPauseState, FIELD_INTEGER ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_STRING, "SetTrack", InputSetTrack ), + DEFINE_INPUTFUNC( FIELD_STRING, "FlyToSpecificTrackViaPath", InputFlyToPathTrack ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrol", InputStartPatrol ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopPatrol", InputStopPatrol ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartBreakableMovement", InputStartBreakableMovement ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopBreakableMovement", InputStopBreakableMovement ), + DEFINE_INPUTFUNC( FIELD_VOID, "ChooseFarthestPathPoint", InputChooseFarthestPathPoint ), + DEFINE_INPUTFUNC( FIELD_VOID, "ChooseNearestPathPoint", InputChooseNearestPathPoint ), + DEFINE_INPUTFUNC( FIELD_INTEGER,"InputStartLeading", InputStartLeading ), + DEFINE_INPUTFUNC( FIELD_VOID, "InputStopLeading", InputStopLeading ), + + // Obsolete, for backwards compatibility + DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrolBreakable", InputStartPatrolBreakable ), + DEFINE_INPUTFUNC( FIELD_STRING, "FlyToPathTrack", InputFlyToPathTrack ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: Initialize pathing data +//----------------------------------------------------------------------------- +void CAI_TrackPather::InitPathingData( float flTrackArrivalTolerance, float flTargetDistance, float flAvoidDistance ) +{ + m_flTargetTolerance = flTrackArrivalTolerance; + m_flTargetDistanceThreshold = flTargetDistance; + m_flAvoidDistance = flAvoidDistance; + + m_pCurrentPathTarget = NULL; + m_pDestPathTarget = NULL; + m_pLastPathTarget = NULL; + m_pTargetNearestPath = NULL; + m_bLeading = false; + + m_flEnemyPathUpdateTime = gpGlobals->curtime; + m_bForcedMove = false; + m_bPatrolling = false; + m_bPatrolBreakable = false; + m_flLeadDistance = 0.0f; + m_bMovingForward = true; + m_vecSegmentStartPoint = m_vecSegmentStartSplinePoint = m_vecDesiredPosition = GetAbsOrigin(); + m_bChooseFarthestPoint = true; + m_flFarthestPathDist = 1e10; + m_flPathMaxSpeed = 0; + m_nPauseState = PAUSE_NO_PAUSE; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_TrackPather::OnRestore( void ) +{ + BaseClass::OnRestore(); + + // Restore current path + if ( m_strCurrentPathName != NULL_STRING ) + { + m_pCurrentPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strCurrentPathName ); + } + else + { + m_pCurrentPathTarget = NULL; + } + + // Restore destination path + if ( m_strDestPathName != NULL_STRING ) + { + m_pDestPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strDestPathName ); + } + else + { + m_pDestPathTarget = NULL; + } + + // Restore last path + if ( m_strLastPathName != NULL_STRING ) + { + m_pLastPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strLastPathName ); + } + else + { + m_pLastPathTarget = NULL; + } + + // Restore target nearest path + if ( m_strTargetNearestPathName != NULL_STRING ) + { + m_pTargetNearestPath = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strTargetNearestPathName ); + } + else + { + m_pTargetNearestPath = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_TrackPather::OnSave( IEntitySaveUtils *pUtils ) +{ + BaseClass::OnSave( pUtils ); + + // Stash all the paths into strings for restoration later + m_strCurrentPathName = ( m_pCurrentPathTarget != NULL ) ? m_pCurrentPathTarget->GetEntityName() : NULL_STRING; + m_strDestPathName = ( m_pDestPathTarget != NULL ) ? m_pDestPathTarget->GetEntityName() : NULL_STRING; + m_strLastPathName = ( m_pLastPathTarget != NULL ) ? m_pLastPathTarget->GetEntityName() : NULL_STRING; + m_strTargetNearestPathName = ( m_pTargetNearestPath != NULL ) ? m_pTargetNearestPath->GetEntityName() : NULL_STRING; +} + + +//----------------------------------------------------------------------------- +// Leading distance +//----------------------------------------------------------------------------- +void CAI_TrackPather::EnableLeading( bool bEnable ) +{ + m_bLeading = bEnable; + if ( m_bLeading ) + { + m_bPatrolling = false; + } +} + +void CAI_TrackPather::SetLeadingDistance( float flLeadDistance ) +{ + m_flLeadDistance = flLeadDistance; +} + +float CAI_TrackPather::GetLeadingDistance( ) const +{ + return m_flLeadDistance; +} + + +//----------------------------------------------------------------------------- +// Returns the next path along our current path +//----------------------------------------------------------------------------- +inline CPathTrack *CAI_TrackPather::NextAlongCurrentPath( CPathTrack *pPath ) const +{ + return CPathTrack::ValidPath( m_bMovingForward ? pPath->GetNext() : pPath->GetPrevious() ); +} + +inline CPathTrack *CAI_TrackPather::PreviousAlongCurrentPath( CPathTrack *pPath ) const +{ + return CPathTrack::ValidPath( m_bMovingForward ? pPath->GetPrevious() : pPath->GetNext() ); +} + +inline CPathTrack *CAI_TrackPather::AdjustForMovementDirection( CPathTrack *pPath ) const +{ + if ( !m_bMovingForward && CPathTrack::ValidPath( pPath->GetPrevious( ) ) ) + { + pPath = CPathTrack::ValidPath( pPath->GetPrevious() ); + } + return pPath; +} + + +//----------------------------------------------------------------------------- +// Enemy visibility check +//----------------------------------------------------------------------------- +CBaseEntity *CAI_TrackPather::FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos ) +{ + trace_t tr; + AI_TraceHull( vecViewPoint, vecTargetPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + return (tr.fraction != 1.0f) ? tr.m_pEnt : NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &targetPos - +// Output : CBaseEntity +//----------------------------------------------------------------------------- +CPathTrack *CAI_TrackPather::BestPointOnPath( CPathTrack *pPath, const Vector &targetPos, float flAvoidRadius, bool visible, bool bFarthestPoint ) +{ + // Find the node nearest to the destination path target if a path is not specified + if ( pPath == NULL ) + { + pPath = m_pDestPathTarget; + } + + // If the path node we're trying to use is not valid, then we're done. + if ( CPathTrack::ValidPath( pPath ) == NULL ) + { + //FIXME: Implement + Assert(0); + return NULL; + } + + // Our target may be in a vehicle + CBaseEntity *pVehicle = NULL; + CBaseEntity *pTargetEnt = GetTrackPatherTargetEnt(); + if ( pTargetEnt != NULL ) + { + CBaseCombatCharacter *pCCTarget = pTargetEnt->MyCombatCharacterPointer(); + if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) + { + pVehicle = pCCTarget->GetVehicleEntity(); + } + } + + // Faster math... + flAvoidRadius *= flAvoidRadius; + + // Find the nearest node to the target (going forward) + CPathTrack *pNearestPath = NULL; + float flNearestDist = bFarthestPoint ? 0 : 999999999; + float flPathDist; + + float flFarthestDistSqr = ( m_flFarthestPathDist - 2.0f * m_flTargetDistanceThreshold ); + flFarthestDistSqr *= flFarthestDistSqr; + + // NOTE: Gotta do it this crazy way because paths can be one-way. + for ( int i = 0; i < 2; ++i ) + { + int loopCheck = 0; + CPathTrack *pTravPath = pPath; + CPathTrack *pNextPath; + + BEGIN_PATH_TRACK_ITERATION(); + for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath, loopCheck++ ) + { + // Circular loop checking + if ( pTravPath->HasBeenVisited() ) + break; + + pTravPath->Visit(); + + pNextPath = (i == 0) ? pTravPath->GetPrevious() : pTravPath->GetNext(); + + // Find the distance between this test point and our goal point + flPathDist = ( pTravPath->GetAbsOrigin() - targetPos ).LengthSqr(); + + // See if it's closer and it's also not within our avoidance radius + if ( bFarthestPoint ) + { + if ( ( flPathDist <= flNearestDist ) && ( flNearestDist <= flFarthestDistSqr ) ) + continue; + } + else + { + if ( flPathDist >= flNearestDist ) + continue; + } + + // Don't choose points that are within the avoid radius + if ( flAvoidRadius && ( pTravPath->GetAbsOrigin() - targetPos ).Length2DSqr() <= flAvoidRadius ) + continue; + + if ( visible ) + { + // If it has to be visible, run those checks + CBaseEntity *pBlocker = FindTrackBlocker( pTravPath->GetAbsOrigin(), targetPos ); + + // Check to see if we've hit the target, or the player's vehicle if it's a player in a vehicle + bool bHitTarget = ( pTargetEnt && ( pTargetEnt == pBlocker ) ) || + ( pVehicle && ( pVehicle == pBlocker ) ); + + // If we hit something, and it wasn't the target or his vehicle, then no dice + // If we hit the target and forced move was set, *still* no dice + if ( (pBlocker != NULL) && ( !bHitTarget || m_bForcedMove ) ) + continue; + } + + pNearestPath = pTravPath; + flNearestDist = flPathDist; + } + } + + return pNearestPath; +} + + +//----------------------------------------------------------------------------- +// Compute a point n units along a path +//----------------------------------------------------------------------------- +CPathTrack *CAI_TrackPather::ComputeLeadingPointAlongPath( const Vector &vecStartPoint, + CPathTrack *pFirstTrack, float flDistance, Vector *pTarget ) +{ + bool bMovingForward = (flDistance > 0.0f); + flDistance = fabs(flDistance); + + CPathTrack *pTravPath = pFirstTrack; + if ( (!bMovingForward) && pFirstTrack->GetPrevious() ) + { + pTravPath = pFirstTrack->GetPrevious(); + } + + *pTarget = vecStartPoint; + CPathTrack *pNextPath; + + // No circular loop checking needed; eventually, it'll run out of distance + for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath ) + { + pNextPath = bMovingForward ? pTravPath->GetNext() : pTravPath->GetPrevious(); + + // Find the distance between this test point and our goal point + float flPathDist = pTarget->DistTo( pTravPath->GetAbsOrigin() ); + + // Find the distance between this test point and our goal point + if ( flPathDist <= flDistance ) + { + flDistance -= flPathDist; + *pTarget = pTravPath->GetAbsOrigin(); + if ( !CPathTrack::ValidPath(pNextPath) ) + return bMovingForward ? pTravPath : pTravPath->GetNext(); + + continue; + } + + ComputeClosestPoint( *pTarget, flDistance, pTravPath->GetAbsOrigin(), pTarget ); + return bMovingForward ? pTravPath : pTravPath->GetNext(); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Compute the distance to a particular point on the path +//----------------------------------------------------------------------------- +float CAI_TrackPather::ComputeDistanceAlongPathToPoint( CPathTrack *pStartTrack, + CPathTrack *pDestTrack, const Vector &vecDestPosition, bool bMovingForward ) +{ + float flTotalDist = 0.0f; + + Vector vecPoint; + ClosestPointToCurrentPath( &vecPoint ); + + CPathTrack *pTravPath = pStartTrack; + CPathTrack *pNextPath, *pTestPath; + BEGIN_PATH_TRACK_ITERATION(); + for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath ) + { + // Circular loop checking + if ( pTravPath->HasBeenVisited() ) + break; + + // Mark it as being visited. + pTravPath->Visit(); + + pNextPath = bMovingForward ? pTravPath->GetNext() : pTravPath->GetPrevious(); + pTestPath = pTravPath; + Assert( pTestPath ); + + if ( pTravPath == pDestTrack ) + { + Vector vecDelta; + Vector vecPathDelta; + VectorSubtract( vecDestPosition, vecPoint, vecDelta ); + ComputePathDirection( pTravPath, &vecPathDelta ); + float flDot = DotProduct( vecDelta, vecPathDelta ); + flTotalDist += (flDot > 0.0f ? 1.0f : -1.0f) * vecDelta.Length2D(); + break; + } + + // NOTE: This would be made more accurate if we did the path direction check here too. + // The starting vecPoint is sometimes *not* within the bounds of the line segment. + + // Find the distance between this test point and our goal point + flTotalDist += (bMovingForward ? 1.0f : -1.0f) * vecPoint.AsVector2D().DistTo( pTestPath->GetAbsOrigin().AsVector2D() ); + vecPoint = pTestPath->GetAbsOrigin(); + } + + return flTotalDist; +} + + +//------------------------------------------------------------------------------ +// Track debugging info +//------------------------------------------------------------------------------ +void CAI_TrackPather::VisualizeDebugInfo( const Vector &vecNearestPoint, const Vector &vecTarget ) +{ + if ( g_debug_trackpather.GetInt() == TRACKPATHER_DEBUG_PATH ) + { + NDebugOverlay::Line( m_vecSegmentStartPoint, vecTarget, 0, 0, 255, true, 0.1f ); + NDebugOverlay::Cross3D( vecNearestPoint, -Vector(16,16,16), Vector(16,16,16), 255, 0, 0, true, 0.1f ); + NDebugOverlay::Cross3D( m_pCurrentPathTarget->GetAbsOrigin(), -Vector(16,16,16), Vector(16,16,16), 0, 255, 0, true, 0.1f ); + NDebugOverlay::Cross3D( m_vecDesiredPosition, -Vector(16,16,16), Vector(16,16,16), 0, 0, 255, true, 0.1f ); + NDebugOverlay::Cross3D( m_pDestPathTarget->GetAbsOrigin(), -Vector(16,16,16), Vector(16,16,16), 255, 255, 255, true, 0.1f ); + + if ( m_pTargetNearestPath ) + { + NDebugOverlay::Cross3D( m_pTargetNearestPath->GetAbsOrigin(), -Vector(24,24,24), Vector(24,24,24), 255, 0, 255, true, 0.1f ); + } + } + + if ( g_debug_trackpather.GetInt() == TRACKPATHER_DEBUG_TRACKS ) + { + if ( m_pCurrentPathTarget ) + { + CPathTrack *pPathTrack = m_pCurrentPathTarget; + for ( ; CPathTrack::ValidPath( pPathTrack ); pPathTrack = pPathTrack->GetNext() ) + { + NDebugOverlay::Box( pPathTrack->GetAbsOrigin(), -Vector(2,2,2), Vector(2,2,2), 0,255, 0, 8, 0.1 ); + if ( CPathTrack::ValidPath( pPathTrack->GetNext() ) ) + { + NDebugOverlay::Line( pPathTrack->GetAbsOrigin(), pPathTrack->GetNext()->GetAbsOrigin(), 0,255,0, true, 0.1 ); + } + + if ( pPathTrack->GetNext() == m_pCurrentPathTarget ) + break; + } + } + } +} + + +//------------------------------------------------------------------------------ +// Does this path track have LOS to the target? +//------------------------------------------------------------------------------ +bool CAI_TrackPather::HasLOSToTarget( CPathTrack *pTrack ) +{ + CBaseEntity *pTargetEnt = GetTrackPatherTargetEnt(); + if ( !pTargetEnt ) + return true; + + Vector targetPos; + if ( !GetTrackPatherTarget( &targetPos ) ) + return true; + + // Translate driver into vehicle for testing + CBaseEntity *pVehicle = NULL; + CBaseCombatCharacter *pCCTarget = pTargetEnt->MyCombatCharacterPointer(); + if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) + { + pVehicle = pCCTarget->GetVehicleEntity(); + } + + // If it has to be visible, run those checks + CBaseEntity *pBlocker = FindTrackBlocker( pTrack->GetAbsOrigin(), targetPos ); + + // Check to see if we've hit the target, or the player's vehicle if it's a player in a vehicle + bool bHitTarget = ( pTargetEnt && ( pTargetEnt == pBlocker ) ) || + ( pVehicle && ( pVehicle == pBlocker ) ); + + return (pBlocker == NULL) || bHitTarget; +} + + +//------------------------------------------------------------------------------ +// Moves to the track +//------------------------------------------------------------------------------ +void CAI_TrackPather::UpdateCurrentTarget() +{ + // Find the point along the line that we're closest to. + const Vector &vecTarget = m_pCurrentPathTarget->GetAbsOrigin(); + Vector vecPoint; + float t = ClosestPointToCurrentPath( &vecPoint ); + if ( (t < 1.0f) && ( vecPoint.DistToSqr( vecTarget ) > m_flTargetTolerance * m_flTargetTolerance ) ) + goto visualizeDebugInfo; + + // Forced move is gone as soon as we've reached the first point on our path + if ( m_bLeading ) + { + m_bForcedMove = false; + } + + // Trip our "path_track reached" output + if ( m_pCurrentPathTarget != m_pLastPathTarget ) + { + // Get the path's specified max speed + m_flPathMaxSpeed = m_pCurrentPathTarget->m_flSpeed; + + variant_t emptyVariant; + m_pCurrentPathTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 ); + m_pLastPathTarget = m_pCurrentPathTarget; + } + + if ( m_nPauseState == PAUSED_AT_POSITION ) + return; + + if ( m_nPauseState == PAUSE_AT_NEXT_LOS_POSITION ) + { + if ( HasLOSToTarget(m_pCurrentPathTarget) ) + { + m_nPauseState = PAUSED_AT_POSITION; + return; + } + } + + // Update our dest path target, if appropriate... + if ( m_pCurrentPathTarget == m_pDestPathTarget ) + { + m_bForcedMove = false; + SelectNewDestTarget(); + } + + // Did SelectNewDestTarget give us a new point to move to? + if ( m_pCurrentPathTarget != m_pDestPathTarget ) + { + // Update to the next path, if there is one... + m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); + if ( !m_pCurrentPathTarget ) + { + m_pCurrentPathTarget = m_pLastPathTarget; + } + } + else + { + // We're at rest (no patrolling behavior), which means we're moving forward now. + m_bMovingForward = true; + } + + SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() ); + m_vecSegmentStartSplinePoint = m_vecSegmentStartPoint; + m_vecSegmentStartPoint = m_pLastPathTarget->GetAbsOrigin(); + +visualizeDebugInfo: + VisualizeDebugInfo( vecPoint, vecTarget ); +} + + +//----------------------------------------------------------------------------- +// +// NOTE: All code below is used exclusively for leading/trailing behavior +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Compute the distance to the leading position +//----------------------------------------------------------------------------- +float CAI_TrackPather::ComputeDistanceToLeadingPosition() +{ + return ComputeDistanceAlongPathToPoint( m_pCurrentPathTarget, m_pDestPathTarget, GetDesiredPosition(), m_bMovingForward ); +} + + +//----------------------------------------------------------------------------- +// Compute the distance to the *target* position +//----------------------------------------------------------------------------- +float CAI_TrackPather::ComputeDistanceToTargetPosition() +{ + Assert( m_pTargetNearestPath ); + + CPathTrack *pDest = m_bMovingForward ? m_pTargetNearestPath.Get() : m_pTargetNearestPath->GetPrevious(); + if ( !pDest ) + { + pDest = m_pTargetNearestPath; + } + bool bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pDest ); + + CPathTrack *pStart = m_pCurrentPathTarget; + if ( bMovingForward != m_bMovingForward ) + { + if (bMovingForward) + { + if ( pStart->GetNext() ) + { + pStart = pStart->GetNext(); + } + if ( pDest->GetNext() ) + { + pDest = pDest->GetNext(); + } + } + else + { + if ( pStart->GetPrevious() ) + { + pStart = pStart->GetPrevious(); + } + if ( pDest->GetPrevious() ) + { + pDest = pDest->GetPrevious(); + } + } + } + + return ComputeDistanceAlongPathToPoint( pStart, pDest, m_vecTargetPathPoint, bMovingForward ); +} + + +//----------------------------------------------------------------------------- +// Compute a path direction +//----------------------------------------------------------------------------- +void CAI_TrackPather::ComputePathDirection( CPathTrack *pPath, Vector *pVecPathDir ) +{ + if ( pPath->GetPrevious() ) + { + VectorSubtract( pPath->GetAbsOrigin(), pPath->GetPrevious()->GetAbsOrigin(), *pVecPathDir ); + } + else + { + if ( pPath->GetNext() ) + { + VectorSubtract( pPath->GetNext()->GetAbsOrigin(), pPath->GetAbsOrigin(), *pVecPathDir ); + } + else + { + pVecPathDir->Init( 1, 0, 0 ); + } + } + VectorNormalize( *pVecPathDir ); +} + + +//----------------------------------------------------------------------------- +// What's the current path direction? +//----------------------------------------------------------------------------- +void CAI_TrackPather::CurrentPathDirection( Vector *pVecPathDir ) +{ + if ( m_pCurrentPathTarget ) + { + ComputePathDirection( m_pCurrentPathTarget, pVecPathDir ); + } + else + { + pVecPathDir->Init( 0, 0, 1 ); + } +} + + + +//----------------------------------------------------------------------------- +// Compute a point n units along the current path from our current position +// (but don't pass the desired target point) +//----------------------------------------------------------------------------- +void CAI_TrackPather::ComputePointAlongCurrentPath( float flDistance, float flPerpDist, Vector *pTarget ) +{ + Vector vecPathDir; + Vector vecStartPoint; + ClosestPointToCurrentPath( &vecStartPoint ); + *pTarget = vecStartPoint; + + if ( flDistance != 0.0f ) + { + Vector vecPrevPoint = vecStartPoint; + CPathTrack *pTravPath = m_pCurrentPathTarget; + CPathTrack *pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget ); + for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = NextAlongCurrentPath( pTravPath ) ) + { + if ( pTravPath == pAdjustedDest ) + { + ComputePathDirection( pTravPath, &vecPathDir ); + + float flPathDist = pTarget->DistTo( GetDesiredPosition() ); + if ( flDistance > flPathDist ) + { + *pTarget = GetDesiredPosition(); + } + else + { + ComputeClosestPoint( *pTarget, flDistance, GetDesiredPosition(), pTarget ); + } + break; + } + + // Find the distance between this test point and our goal point + float flPathDist = pTarget->DistTo( pTravPath->GetAbsOrigin() ); + + // Find the distance between this test point and our goal point + if ( flPathDist <= flDistance ) + { + flDistance -= flPathDist; + *pTarget = pTravPath->GetAbsOrigin(); + + // FIXME: Reduce the distance further based on the angle between this segment + the next + continue; + } + + ComputePathDirection( pTravPath, &vecPathDir ); + ComputeClosestPoint( *pTarget, flDistance, pTravPath->GetAbsOrigin(), pTarget ); + break; + } + } + else + { + VectorSubtract( m_pCurrentPathTarget->GetAbsOrigin(), m_vecSegmentStartPoint, vecPathDir ); + VectorNormalize( vecPathDir ); + } + + // Add in the horizontal component + ComputePointFromPerpDistance( *pTarget, vecPathDir, flPerpDist, pTarget ); +} + + +//----------------------------------------------------------------------------- +// Methods to find a signed perp distance from the track +// and to compute a point off the path based on the signed perp distance +//----------------------------------------------------------------------------- +float CAI_TrackPather::ComputePerpDistanceFromPath( const Vector &vecPointOnPath, const Vector &vecPathDir, const Vector &vecPointOffPath ) +{ + // Make it be a signed distance of the target from the path + // Positive means on the right side, negative means on the left side + Vector vecAcross, vecDelta; + CrossProduct( vecPathDir, Vector( 0, 0, 1 ), vecAcross ); + VectorSubtract( vecPointOffPath, vecPointOnPath, vecDelta ); + VectorMA( vecDelta, -DotProduct( vecPathDir, vecDelta ), vecPathDir, vecDelta ); + + float flDistanceFromPath = vecDelta.Length2D(); + if ( DotProduct2D( vecAcross.AsVector2D(), vecDelta.AsVector2D() ) < 0.0f ) + { + flDistanceFromPath *= -1.0f; + } + + return flDistanceFromPath; +} + +void CAI_TrackPather::ComputePointFromPerpDistance( const Vector &vecPointOnPath, const Vector &vecPathDir, float flPerpDist, Vector *pResult ) +{ + Vector vecAcross; + CrossProduct( vecPathDir, Vector( 0, 0, 1 ), vecAcross ); + VectorMA( vecPointOnPath, flPerpDist, vecAcross, *pResult ); +} + + +//----------------------------------------------------------------------------- +// Finds the closest point on the path, returns a signed perpendicular distance +// where negative means on the left side of the path (when travelled from prev to next) +// and positive means on the right side +//----------------------------------------------------------------------------- +CPathTrack *CAI_TrackPather::FindClosestPointOnPath( CPathTrack *pPath, + const Vector &targetPos, Vector *pVecClosestPoint, Vector *pVecPathDir, float *pDistanceFromPath ) +{ + // Find the node nearest to the destination path target if a path is not specified + if ( pPath == NULL ) + { + pPath = m_pDestPathTarget; + } + + // If the path node we're trying to use is not valid, then we're done. + if ( CPathTrack::ValidPath( pPath ) == NULL ) + { + //FIXME: Implement + Assert(0); + return NULL; + } + + // Find the nearest node to the target (going forward) + CPathTrack *pNearestPath = NULL; + float flNearestDist2D = 999999999; + float flNearestDist = 999999999; + float flPathDist, flPathDist2D; + + // NOTE: Gotta do it this crazy way because paths can be one-way. + Vector vecNearestPoint; + Vector vecNearestPathSegment; + for ( int i = 0; i < 2; ++i ) + { + int loopCheck = 0; + CPathTrack *pTravPath = pPath; + CPathTrack *pNextPath; + + BEGIN_PATH_TRACK_ITERATION(); + for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath, loopCheck++ ) + { + // Circular loop checking + if ( pTravPath->HasBeenVisited() ) + break; + + // Mark it as being visited. + pTravPath->Visit(); + + pNextPath = (i == 0) ? pTravPath->GetPrevious() : pTravPath->GetNext(); + + // No alt paths allowed in leading mode. + if ( pTravPath->m_paltpath ) + { + Warning( "%s: Alternative paths in path_track not allowed when using the leading behavior!\n", STRING(GetEntityName()) ); + } + + // Need line segments + if ( !CPathTrack::ValidPath(pNextPath) ) + break; + + // Find the closest point on the line segment on the path + Vector vecClosest; + CalcClosestPointOnLineSegment( targetPos, pTravPath->GetAbsOrigin(), pNextPath->GetAbsOrigin(), vecClosest ); + + // Find the distance between this test point and our goal point + flPathDist2D = vecClosest.AsVector2D().DistToSqr( targetPos.AsVector2D() ); + if ( flPathDist2D > flNearestDist2D ) + continue; + + flPathDist = vecClosest.z - targetPos.z; + flPathDist *= flPathDist; + flPathDist += flPathDist2D; + if (( flPathDist2D == flNearestDist2D ) && ( flPathDist >= flNearestDist )) + continue; + + pNearestPath = (i == 0) ? pTravPath : pNextPath; + flNearestDist2D = flPathDist2D; + flNearestDist = flPathDist; + vecNearestPoint = vecClosest; + VectorSubtract( pNextPath->GetAbsOrigin(), pTravPath->GetAbsOrigin(), vecNearestPathSegment ); + if ( i == 0 ) + { + vecNearestPathSegment *= -1.0f; + } + } + } + + VectorNormalize( vecNearestPathSegment ); + *pDistanceFromPath = ComputePerpDistanceFromPath( vecNearestPoint, vecNearestPathSegment, targetPos ); + *pVecClosestPoint = vecNearestPoint; + *pVecPathDir = vecNearestPathSegment; + return pNearestPath; +} + + +//----------------------------------------------------------------------------- +// Breakable paths? +//----------------------------------------------------------------------------- +void CAI_TrackPather::InputStartBreakableMovement( inputdata_t &inputdata ) +{ + m_bPatrolBreakable = true; +} + +void CAI_TrackPather::InputStopBreakableMovement( inputdata_t &inputdata ) +{ + m_bPatrolBreakable = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_TrackPather::InputStartPatrol( inputdata_t &inputdata ) +{ + m_bPatrolling = true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CAI_TrackPather::InputStopPatrol( inputdata_t &inputdata ) +{ + m_bPatrolling = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_TrackPather::InputStartPatrolBreakable( inputdata_t &inputdata ) +{ + m_bPatrolBreakable = true; + m_bPatrolling = true; +} + + +//------------------------------------------------------------------------------ +// Leading behaviors +//------------------------------------------------------------------------------ +void CAI_TrackPather::InputStartLeading( inputdata_t &inputdata ) +{ + EnableLeading( true ); + SetLeadingDistance( inputdata.value.Int() ); +} + +void CAI_TrackPather::InputStopLeading( inputdata_t &inputdata ) +{ + EnableLeading( false ); +} + + +//------------------------------------------------------------------------------ +// Selects a new destination target +//------------------------------------------------------------------------------ +void CAI_TrackPather::SelectNewDestTarget() +{ + if ( !m_bPatrolling ) + return; + + // NOTE: This version is bugged, but I didn't want to make the fix + // here for fear of breaking a lot of maps late in the day. + // So, only the chopper does the "right" thing. +#ifdef HL2_EPISODIC + // Episodic uses the fixed logic for all trackpathers + if ( 1 ) +#else + if ( ShouldUseFixedPatrolLogic() ) +#endif + { + CPathTrack *pOldDest = m_pDestPathTarget; + + // Only switch polarity of movement if we're at the *end* of the path + // This is really useful for initial conditions of patrolling + // NOTE: We've got to do some extra work for circular paths + bool bIsCircular = false; + { + BEGIN_PATH_TRACK_ITERATION(); + CPathTrack *pTravPath = m_pDestPathTarget; + while( CPathTrack::ValidPath( pTravPath ) ) + { + // Circular loop checking + if ( pTravPath->HasBeenVisited() ) + { + bIsCircular = true; + break; + } + + pTravPath->Visit(); + pTravPath = NextAlongCurrentPath( pTravPath ); + } + } + + if ( bIsCircular || (NextAlongCurrentPath( m_pDestPathTarget ) == NULL) ) + { + m_bMovingForward = !m_bMovingForward; + } + + BEGIN_PATH_TRACK_ITERATION(); + + while ( true ) + { + CPathTrack *pNextTrack = NextAlongCurrentPath( m_pDestPathTarget ); + if ( !pNextTrack || (pNextTrack == pOldDest) || pNextTrack->HasBeenVisited() ) + break; + + pNextTrack->Visit(); + m_pDestPathTarget = pNextTrack; + } + } + else + { + CPathTrack *pOldDest = m_pDestPathTarget; + + // For patrolling, switch the polarity of movement + m_bMovingForward = !m_bMovingForward; + + int loopCount = 0; + while ( true ) + { + CPathTrack *pNextTrack = NextAlongCurrentPath( m_pDestPathTarget ); + if ( !pNextTrack ) + break; + if ( ++loopCount > 1024 ) + { + DevMsg(1,"WARNING: Looping path for %s\n", GetDebugName() ); + break; + } + + m_pDestPathTarget = pNextTrack; + } + + if ( m_pDestPathTarget == pOldDest ) + { + // This can occur if we move to the first point on the path + SelectNewDestTarget(); + } + } +} + + +//------------------------------------------------------------------------------ +// Moves to the track +//------------------------------------------------------------------------------ +void CAI_TrackPather::UpdateCurrentTargetLeading() +{ + bool bRestingAtDest = false; + CPathTrack *pAdjustedDest; + + // Find the point along the line that we're closest to. + const Vector &vecTarget = m_pCurrentPathTarget->GetAbsOrigin(); + Vector vecPoint; + float t = ClosestPointToCurrentPath( &vecPoint ); + if ( (t < 1.0f) && ( vecPoint.DistToSqr( vecTarget ) > m_flTargetTolerance * m_flTargetTolerance ) ) + goto visualizeDebugInfo; + + // Trip our "path_track reached" output + if ( m_pCurrentPathTarget != m_pLastPathTarget ) + { + // Get the path's specified max speed + m_flPathMaxSpeed = m_pCurrentPathTarget->m_flSpeed; + + variant_t emptyVariant; + m_pCurrentPathTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 ); + m_pLastPathTarget = m_pCurrentPathTarget; + } + + // NOTE: CurrentPathTarget doesn't mean the same thing as dest path target! + // It's the "next"most when moving forward + "prev"most when moving backward + // Must do the tests in the same space + pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget ); + + // Update our dest path target, if appropriate... + if ( m_pCurrentPathTarget == pAdjustedDest ) + { + m_bForcedMove = false; + SelectNewDestTarget(); + + // NOTE: Must do this again since SelectNewDestTarget may change m_pDestPathTarget + pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget ); + } + + if ( m_pCurrentPathTarget != pAdjustedDest ) + { + // Update to the next path, if there is one... + m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); + if ( !m_pCurrentPathTarget ) + { + m_pCurrentPathTarget = m_pLastPathTarget; + } + } + else + { + // NOTE: Have to do this here because the NextAlongCurrentPath call above + // could make m_pCurrentPathTarget == m_pDestPathTarget. + // In this case, we're at rest (no patrolling behavior) + bRestingAtDest = true; + } + + if ( bRestingAtDest ) + { + // NOTE: Must use current path target, instead of dest + // to get the PreviousAlongCurrentPath working correctly + CPathTrack *pSegmentStart = PreviousAlongCurrentPath( m_pCurrentPathTarget ); + if ( !pSegmentStart ) + { + pSegmentStart = m_pCurrentPathTarget; + } + m_vecSegmentStartPoint = pSegmentStart->GetAbsOrigin(); + } + else + { + m_vecSegmentStartPoint = m_pLastPathTarget->GetAbsOrigin(); + } + +visualizeDebugInfo: + VisualizeDebugInfo( vecPoint, vecTarget ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_TrackPather::UpdateTargetPositionLeading( void ) +{ + Vector targetPos; + if ( !GetTrackPatherTarget( &targetPos ) ) + return; + + // NOTE: FindClosestPointOnPath *always* returns the point on the "far", + // end of the line segment containing the closest point (namely the 'next' + // track, as opposed to the 'prev' track) + Vector vecClosestPoint, vecPathDir; + float flTargetDistanceFromPath; + CPathTrack *pNextPath = FindClosestPointOnPath( m_pCurrentPathTarget, + targetPos, &vecClosestPoint, &vecPathDir, &flTargetDistanceFromPath ); + + // This means that a valid path could not be found to our target! + if ( CPathTrack::ValidPath( pNextPath ) == NULL ) + return; + +// NDebugOverlay::Cross3D( vecClosestPoint, -Vector(24,24,24), Vector(24,24,24), 0, 255, 255, true, 0.1f ); +// NDebugOverlay::Cross3D( pNextPath->GetAbsOrigin(), -Vector(24,24,24), Vector(24,24,24), 255, 255, 0, true, 0.1f ); + + // Here's how far we are from the path + m_flTargetDistFromPath = flTargetDistanceFromPath; + m_vecTargetPathDir = vecPathDir; + + // Here's info about where the target is along the path + m_vecTargetPathPoint = vecClosestPoint; + m_pTargetNearestPath = pNextPath; + + // Find the best position to be on our path + // NOTE: This will *also* return a path track on the "far" end of the line segment + // containing the leading position, namely the "next" end of the segment as opposed + // to the "prev" end of the segment. + CPathTrack *pDest = ComputeLeadingPointAlongPath( vecClosestPoint, pNextPath, m_flLeadDistance, &targetPos ); + SetDesiredPosition( targetPos ); + + // We only want to switch movement directions when absolutely necessary + // so convert dest into a more appropriate value based on the current movement direction + if ( pDest != m_pDestPathTarget ) + { + // NOTE: This is really tricky + subtle + // For leading, we don't want to ever change direction when the current target == the + // adjusted destination target. Namely, if we're going forward, both dest + curr + // mean the "next"most node so we can compare them directly against eath other. + // If we're moving backward, dest means "next"most, but curr means "prev"most. + // We first have to adjust the dest to mean "prev"most, and then do the comparison. + // If the adjusted dest == curr, then maintain direction. Otherwise, use the forward along path test. + bool bMovingForward = m_bMovingForward; + CPathTrack *pAdjustedDest = AdjustForMovementDirection( pDest ); + if ( m_pCurrentPathTarget != pAdjustedDest ) + { + bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pAdjustedDest ); + } + + if ( bMovingForward != m_bMovingForward ) + { + // As a result of the tricky note above, this should never occur + Assert( pAdjustedDest != m_pCurrentPathTarget ); + + // Oops! Need to reverse direction + m_bMovingForward = bMovingForward; + m_vecSegmentStartPoint = m_pCurrentPathTarget->GetAbsOrigin(); + m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); + } + m_pDestPathTarget = pDest; + } + +// NDebugOverlay::Cross3D( m_pCurrentPathTarget->GetAbsOrigin(), -Vector(36,36,36), Vector(36,36,36), 255, 0, 0, true, 0.1f ); +// NDebugOverlay::Cross3D( m_pDestPathTarget->GetAbsOrigin(), -Vector(48,48,48), Vector(48,48,48), 0, 255, 0, true, 0.1f ); +// NDebugOverlay::Cross3D( targetPos, -Vector(36,36,36), Vector(36,36,36), 0, 0, 255, true, 0.1f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_TrackPather::UpdateTargetPosition( void ) +{ + // Don't update our target if we're being told to go somewhere + if ( m_bForcedMove && !m_bPatrolBreakable ) + return; + + // Don't update our target if we're patrolling + if ( m_bPatrolling ) + { + // If we have an enemy, and our patrol is breakable, stop patrolling + if ( !m_bPatrolBreakable || !GetEnemy() ) + return; + + m_bPatrolling = false; + } + + Vector targetPos; + if ( !GetTrackPatherTarget( &targetPos ) ) + return; + + // Not time to update again + if ( m_flEnemyPathUpdateTime > gpGlobals->curtime ) + return; + + // See if the target has moved enough to make us recheck + float flDistSqr = ( targetPos - m_vecLastGoalCheckPosition ).LengthSqr(); + if ( flDistSqr < m_flTargetDistanceThreshold * m_flTargetDistanceThreshold ) + return; + + // Find the best position to be on our path + CPathTrack *pDest = BestPointOnPath( m_pCurrentPathTarget, targetPos, m_flAvoidDistance, true, m_bChooseFarthestPoint ); + + if ( CPathTrack::ValidPath( pDest ) == NULL ) + { + // This means that a valid path could not be found to our target! +// Assert(0); + return; + } + + if ( pDest != m_pDestPathTarget ) + { + // This is our new destination + bool bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pDest ); + if ( bMovingForward != m_bMovingForward ) + { + // Oops! Need to reverse direction + m_bMovingForward = bMovingForward; + if ( pDest != m_pCurrentPathTarget ) + { + SetupNewCurrentTarget( NextAlongCurrentPath( m_pCurrentPathTarget ) ); + } + } + m_pDestPathTarget = pDest; + } + + // Keep this goal point for comparisons later + m_vecLastGoalCheckPosition = targetPos; + + // Only do this on set intervals + m_flEnemyPathUpdateTime = gpGlobals->curtime + 1.0f; +} + + +//------------------------------------------------------------------------------ +// Returns the direction of the path at the closest point to the target +//------------------------------------------------------------------------------ +const Vector &CAI_TrackPather::TargetPathDirection() const +{ + return m_vecTargetPathDir; +} + +const Vector &CAI_TrackPather::TargetPathAcrossDirection() const +{ + static Vector s_Result; + CrossProduct( m_vecTargetPathDir, Vector( 0, 0, 1 ), s_Result ); + return s_Result; +} + + +//------------------------------------------------------------------------------ +// Returns the speed of the target relative to the path +//------------------------------------------------------------------------------ +float CAI_TrackPather::TargetSpeedAlongPath() const +{ + if ( !GetEnemy() || !IsLeading() ) + return 0.0f; + + Vector vecSmoothedVelocity = GetEnemy()->GetSmoothedVelocity(); + return DotProduct( vecSmoothedVelocity, TargetPathDirection() ); +} + + +//------------------------------------------------------------------------------ +// Returns the speed of the target *across* the path +//------------------------------------------------------------------------------ +float CAI_TrackPather::TargetSpeedAcrossPath() const +{ + if ( !GetEnemy() || !IsLeading() ) + return 0.0f; + + Vector vecSmoothedVelocity = GetEnemy()->GetSmoothedVelocity(); + return DotProduct( vecSmoothedVelocity, TargetPathAcrossDirection() ); +} + + +//------------------------------------------------------------------------------ +// Returns the max distance we can be from the path +//------------------------------------------------------------------------------ +float CAI_TrackPather::MaxDistanceFromCurrentPath() const +{ + if ( !IsLeading() || !m_pCurrentPathTarget ) + return 0.0f; + + CPathTrack *pPrevPath = PreviousAlongCurrentPath( m_pCurrentPathTarget ); + if ( !pPrevPath ) + { + pPrevPath = m_pCurrentPathTarget; + } + + // NOTE: Can't use m_vecSegmentStartPoint because we don't have a radius defined for it + float t; + Vector vecTemp; + CalcClosestPointOnLine( GetAbsOrigin(), pPrevPath->GetAbsOrigin(), + m_pCurrentPathTarget->GetAbsOrigin(), vecTemp, &t ); + t = clamp( t, 0.0f, 1.0f ); + float flRadius = (1.0f - t) * pPrevPath->GetRadius() + t * m_pCurrentPathTarget->GetRadius(); + return flRadius; +} + + +//------------------------------------------------------------------------------ +// Purpose : A different version of the track pather which is more explicit about +// the meaning of dest, current, and prev path points +//------------------------------------------------------------------------------ +void CAI_TrackPather::UpdateTrackNavigation( void ) +{ + // No target? Use the string specified. We have no spawn method (sucky!!) so this is how that works + if ( ( CPathTrack::ValidPath( m_pDestPathTarget ) == NULL ) && ( m_target != NULL_STRING ) ) + { + FlyToPathTrack( m_target ); + m_target = NULL_STRING; + } + + if ( !IsLeading() ) + { + if ( !m_pCurrentPathTarget ) + return; + + // Updates our destination node if we're tracking something + UpdateTargetPosition(); + + // Move along our path towards our current destination + UpdateCurrentTarget(); + } + else + { + // Updates our destination position if we're leading something + UpdateTargetPositionLeading(); + + // Move along our path towards our current destination + UpdateCurrentTargetLeading(); + } +} + + +//------------------------------------------------------------------------------ +// Sets the farthest path distance +//------------------------------------------------------------------------------ +void CAI_TrackPather::SetFarthestPathDist( float flMaxPathDist ) +{ + m_flFarthestPathDist = flMaxPathDist; +} + + +//------------------------------------------------------------------------------ +// Sets up a new current path target +//------------------------------------------------------------------------------ +void CAI_TrackPather::SetupNewCurrentTarget( CPathTrack *pTrack ) +{ + Assert( pTrack ); + m_vecSegmentStartPoint = GetAbsOrigin(); + VectorMA( m_vecSegmentStartPoint, -2.0f, GetAbsVelocity(), m_vecSegmentStartSplinePoint ); + m_pCurrentPathTarget = pTrack; + SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() ); +} + + +//------------------------------------------------------------------------------ +// Moves to an explicit track point +//------------------------------------------------------------------------------ +void CAI_TrackPather::MoveToTrackPoint( CPathTrack *pTrack ) +{ + if ( IsOnSameTrack( pTrack, m_pDestPathTarget ) ) + { + // The track must be valid + if ( CPathTrack::ValidPath( pTrack ) == NULL ) + return; + + m_pDestPathTarget = pTrack; + m_bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pTrack ); + m_bForcedMove = true; + } + else + { + CPathTrack *pClosestTrack = BestPointOnPath( pTrack, WorldSpaceCenter(), 0.0f, false, false ); + + // The track must be valid + if ( CPathTrack::ValidPath( pClosestTrack ) == NULL ) + return; + + SetupNewCurrentTarget( pClosestTrack ); + m_pDestPathTarget = pTrack; + m_bMovingForward = IsForwardAlongPath( pClosestTrack, pTrack ); + m_bForcedMove = true; + } +} + + +//------------------------------------------------------------------------------ +// Moves to the closest track point +//------------------------------------------------------------------------------ +void CAI_TrackPather::MoveToClosestTrackPoint( CPathTrack *pTrack ) +{ + if ( IsOnSameTrack( pTrack, m_pDestPathTarget ) ) + return; + + CPathTrack *pClosestTrack = BestPointOnPath( pTrack, WorldSpaceCenter(), 0.0f, false, false ); + + // The track must be valid + if ( CPathTrack::ValidPath( pClosestTrack ) == NULL ) + return; + + SetupNewCurrentTarget( pClosestTrack ); + m_pDestPathTarget = pClosestTrack; + m_bMovingForward = true; + + // Force us to switch tracks if we're leading + if ( IsLeading() ) + { + m_bForcedMove = true; + } +} + + +//----------------------------------------------------------------------------- +// Are the two path tracks connected? +//----------------------------------------------------------------------------- +bool CAI_TrackPather::IsOnSameTrack( CPathTrack *pPath1, CPathTrack *pPath2 ) const +{ + if ( pPath1 == pPath2 ) + return true; + + { + BEGIN_PATH_TRACK_ITERATION(); + CPathTrack *pTravPath = pPath1->GetPrevious(); + while( CPathTrack::ValidPath( pTravPath ) && (pTravPath != pPath1) ) + { + // Circular loop checking + if ( pTravPath->HasBeenVisited() ) + break; + + pTravPath->Visit(); + + if ( pTravPath == pPath2 ) + return true; + + pTravPath = pTravPath->GetPrevious(); + } + } + + { + BEGIN_PATH_TRACK_ITERATION(); + CPathTrack *pTravPath = pPath1->GetNext(); + while( CPathTrack::ValidPath( pTravPath ) && (pTravPath != pPath1) ) + { + // Circular loop checking + if ( pTravPath->HasBeenVisited() ) + break; + + pTravPath->Visit(); + + if ( pTravPath == pPath2 ) + return true; + + pTravPath = pTravPath->GetNext(); + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Deal with teleportation +//----------------------------------------------------------------------------- +void CAI_TrackPather::Teleported() +{ + // This updates the paths so they are reasonable + CPathTrack *pClosestTrack = BestPointOnPath( GetDestPathTarget(), WorldSpaceCenter(), 0.0f, false, false ); + m_pDestPathTarget = NULL; + MoveToClosestTrackPoint( pClosestTrack ); +} + + +//----------------------------------------------------------------------------- +// Returns distance along path to target, returns FLT_MAX if there's no path +//----------------------------------------------------------------------------- +float CAI_TrackPather::ComputePathDistance( CPathTrack *pPath, CPathTrack *pDest, bool bForward ) const +{ + float flDist = 0.0f; + CPathTrack *pLast = pPath; + + BEGIN_PATH_TRACK_ITERATION(); + while ( CPathTrack::ValidPath( pPath ) ) + { + // Ciruclar loop checking + if ( pPath->HasBeenVisited() ) + return FLT_MAX; + + pPath->Visit(); + + flDist += pLast->GetAbsOrigin().DistTo( pPath->GetAbsOrigin() ); + + if ( pDest == pPath ) + return flDist; + + pLast = pPath; + pPath = bForward ? pPath->GetNext() : pPath->GetPrevious(); + } + + return FLT_MAX; +} + + +//----------------------------------------------------------------------------- +// Is pPathTest in "front" of pPath on the same path? (Namely, does GetNext() get us there?) +//----------------------------------------------------------------------------- +bool CAI_TrackPather::IsForwardAlongPath( CPathTrack *pPath, CPathTrack *pPathTest ) const +{ + // Also, in the case of looping paths, we want to return the shortest path + float flForwardDist = ComputePathDistance( pPath, pPathTest, true ); + float flReverseDist = ComputePathDistance( pPath, pPathTest, false ); + + Assert( ( flForwardDist != FLT_MAX ) || ( flReverseDist != FLT_MAX ) ); + return ( flForwardDist <= flReverseDist ); +} + + +//----------------------------------------------------------------------------- +// Computes distance + nearest point from the current path.. +//----------------------------------------------------------------------------- +float CAI_TrackPather::ClosestPointToCurrentPath( Vector *pVecPoint ) const +{ + if (!m_pCurrentPathTarget) + { + *pVecPoint = GetAbsOrigin(); + return 0; + } + + float t; + CalcClosestPointOnLine( GetAbsOrigin(), m_vecSegmentStartPoint, + m_pCurrentPathTarget->GetAbsOrigin(), *pVecPoint, &t ); + return t; +} + + +//----------------------------------------------------------------------------- +// Computes a "path" velocity at a particular point along the current path +//----------------------------------------------------------------------------- +void CAI_TrackPather::ComputePathTangent( float t, Vector *pVecTangent ) const +{ + CPathTrack *pNextTrack = NextAlongCurrentPath(m_pCurrentPathTarget); + if ( !pNextTrack ) + { + pNextTrack = m_pCurrentPathTarget; + } + + t = clamp( t, 0.0f, 1.0f ); + pVecTangent->Init(0,0,0); + Catmull_Rom_Spline_Tangent( m_vecSegmentStartSplinePoint, m_vecSegmentStartPoint, + m_pCurrentPathTarget->GetAbsOrigin(), pNextTrack->GetAbsOrigin(), t, *pVecTangent ); + VectorNormalize( *pVecTangent ); +} + + +//----------------------------------------------------------------------------- +// Computes the *normalized* velocity at which the helicopter should approach the final point +//----------------------------------------------------------------------------- +void CAI_TrackPather::ComputeNormalizedDestVelocity( Vector *pVecVelocity ) const +{ + if ( m_nPauseState != PAUSE_NO_PAUSE ) + { + pVecVelocity->Init(0,0,0); + return; + } + + CPathTrack *pNextTrack = NextAlongCurrentPath(m_pCurrentPathTarget); + if ( !pNextTrack ) + { + pNextTrack = m_pCurrentPathTarget; + } + + if ( ( pNextTrack == m_pCurrentPathTarget ) || ( m_pCurrentPathTarget == m_pDestPathTarget ) ) + { + pVecVelocity->Init(0,0,0); + return; + } + + VectorSubtract( pNextTrack->GetAbsOrigin(), m_pCurrentPathTarget->GetAbsOrigin(), *pVecVelocity ); + VectorNormalize( *pVecVelocity ); + + // Slow it down if we're approaching a sharp corner + Vector vecDelta; + VectorSubtract( m_pCurrentPathTarget->GetAbsOrigin(), m_vecSegmentStartPoint, vecDelta ); + VectorNormalize( vecDelta ); + float flDot = DotProduct( *pVecVelocity, vecDelta ); + *pVecVelocity *= clamp( flDot, 0.0f, 1.0f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_TrackPather::SetTrack( CBaseEntity *pGoalEnt ) +{ + // Ignore this input if we're *already* on that path. + CPathTrack *pTrack = dynamic_cast(pGoalEnt); + if ( !pTrack ) + { + DevWarning( "%s: Specified entity '%s' must be a path_track!\n", pGoalEnt->GetClassname(), STRING(pGoalEnt->GetEntityName()) ); + return; + } + + MoveToClosestTrackPoint( pTrack ); +} + +void CAI_TrackPather::SetTrack( string_t strTrackName ) +{ + // Find our specified target + CBaseEntity *pGoalEnt = gEntList.FindEntityByName( NULL, strTrackName ); + if ( pGoalEnt == NULL ) + { + DevWarning( "%s: Could not find path_track '%s'!\n", GetClassname(), STRING( strTrackName ) ); + return; + } + + SetTrack( pGoalEnt ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_TrackPather::InputSetTrack( inputdata_t &inputdata ) +{ + string_t strTrackName = MAKE_STRING( inputdata.value.String() ); + SetTrack( MAKE_STRING( inputdata.value.String() ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : strTrackName - +//----------------------------------------------------------------------------- +void CAI_TrackPather::FlyToPathTrack( string_t strTrackName ) +{ + CBaseEntity *pGoalEnt = gEntList.FindEntityByName( NULL, strTrackName ); + if ( pGoalEnt == NULL ) + { + DevWarning( "%s: Could not find path_track '%s'!\n", GetClassname(), STRING( strTrackName ) ); + return; + } + + // Ignore this input if we're *already* on that path. + CPathTrack *pTrack = dynamic_cast(pGoalEnt); + if ( !pTrack ) + { + DevWarning( "%s: Specified entity '%s' must be a path_track!\n", GetClassname(), STRING( strTrackName ) ); + return; + } + + // Find our specified target + MoveToTrackPoint( pTrack ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_TrackPather::InputFlyToPathTrack( inputdata_t &inputdata ) +{ + // Find our specified target + string_t strTrackName = MAKE_STRING( inputdata.value.String() ); + FlyToPathTrack( strTrackName ); +} + + +//----------------------------------------------------------------------------- +// Changes the mode used to determine which path point to move to +//----------------------------------------------------------------------------- +void CAI_TrackPather::InputChooseFarthestPathPoint( inputdata_t &inputdata ) +{ + UseFarthestPathPoint( true ); +} + +void CAI_TrackPather::InputChooseNearestPathPoint( inputdata_t &inputdata ) +{ + UseFarthestPathPoint( false ); +} + +void CAI_TrackPather::UseFarthestPathPoint( bool useFarthest ) +{ + m_bChooseFarthestPoint = useFarthest; +} diff --git a/dlls/baseanimating.cpp b/dlls/baseanimating.cpp index c14776e6..23b5591a 100644 --- a/dlls/baseanimating.cpp +++ b/dlls/baseanimating.cpp @@ -500,12 +500,12 @@ void CBaseAnimating::SetLightingOriginRelative( string_t strLightingOriginRelati { if( !pLightingOrigin ) { - DevWarning( "%s: Cannot find Lighting Origin named: %s\n", GetEntityName(), strLightingOriginRelative ); + DevWarning( "%s: Cannot find Lighting Origin named: %s\n", STRING(GetEntityName()), STRING(strLightingOriginRelative) ); } else { DevWarning( "%s: Specified entity '%s' must be a info_lighting_relative!\n", - pLightingOrigin->GetClassname(), pLightingOrigin->GetEntityName() ); + pLightingOrigin->GetClassname(), STRING(pLightingOrigin->GetEntityName()) ); } return; } diff --git a/dlls/basebludgeonweapon.h b/dlls/basebludgeonweapon.h index 0f5eab5c..a8acc376 100644 --- a/dlls/basebludgeonweapon.h +++ b/dlls/basebludgeonweapon.h @@ -1,57 +1,57 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: The class from which all bludgeon melee -// weapons are derived. -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#include "basehlcombatweapon.h" - -#ifndef BASEBLUDGEONWEAPON_H -#define BASEBLUDGEONWEAPON_H - -//========================================================= -// CBaseHLBludgeonWeapon -//========================================================= -class CBaseHLBludgeonWeapon : public CBaseHLCombatWeapon -{ - DECLARE_CLASS( CBaseHLBludgeonWeapon, CBaseHLCombatWeapon ); -public: - CBaseHLBludgeonWeapon(); - - DECLARE_SERVERCLASS(); - - virtual void Spawn( void ); - virtual void Precache( void ); - - //Attack functions - virtual void PrimaryAttack( void ); - virtual void SecondaryAttack( void ); - - virtual void ItemPostFrame( void ); - - //Functions to select animation sequences - virtual Activity GetPrimaryAttackActivity( void ) { return ACT_VM_HITCENTER; } - virtual Activity GetSecondaryAttackActivity( void ) { return ACT_VM_HITCENTER2; } - - virtual float GetFireRate( void ) { return 0.2f; } - virtual float GetRange( void ) { return 32.0f; } - virtual float GetDamageForActivity( Activity hitActivity ) { return 1.0f; } - - virtual int CapabilitiesGet( void ); - virtual int WeaponMeleeAttack1Condition( float flDot, float flDist ); - -protected: - virtual void ImpactEffect( trace_t &trace ); - -private: - bool ImpactWater( const Vector &start, const Vector &end ); - void Swing( int bIsSecondary ); - void Hit( trace_t &traceHit, Activity nHitActivity ); - Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); -}; - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: The class from which all bludgeon melee +// weapons are derived. +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "basehlcombatweapon.h" + +#ifndef BASEBLUDGEONWEAPON_H +#define BASEBLUDGEONWEAPON_H + +//========================================================= +// CBaseHLBludgeonWeapon +//========================================================= +class CBaseHLBludgeonWeapon : public CBaseHLCombatWeapon +{ + DECLARE_CLASS( CBaseHLBludgeonWeapon, CBaseHLCombatWeapon ); +public: + CBaseHLBludgeonWeapon(); + + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + virtual void Precache( void ); + + //Attack functions + virtual void PrimaryAttack( void ); + virtual void SecondaryAttack( void ); + + virtual void ItemPostFrame( void ); + + //Functions to select animation sequences + virtual Activity GetPrimaryAttackActivity( void ) { return ACT_VM_HITCENTER; } + virtual Activity GetSecondaryAttackActivity( void ) { return ACT_VM_HITCENTER2; } + + virtual float GetFireRate( void ) { return 0.2f; } + virtual float GetRange( void ) { return 32.0f; } + virtual float GetDamageForActivity( Activity hitActivity ) { return 1.0f; } + + virtual int CapabilitiesGet( void ); + virtual int WeaponMeleeAttack1Condition( float flDot, float flDist ); + +protected: + virtual void ImpactEffect( trace_t &trace ); + +private: + bool ImpactWater( const Vector &start, const Vector &end ); + void Swing( int bIsSecondary ); + void Hit( trace_t &traceHit, Activity nHitActivity ); + Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); +}; + +#endif diff --git a/dlls/basecombatcharacter.cpp b/dlls/basecombatcharacter.cpp index 9a6ff9d8..da96f002 100644 --- a/dlls/basecombatcharacter.cpp +++ b/dlls/basecombatcharacter.cpp @@ -1300,8 +1300,6 @@ Killed */ void CBaseCombatCharacter::Event_Killed( const CTakeDamageInfo &info ) { - extern ConVar npc_vphysics; - // Advance life state to dying m_lifeState = LIFE_DYING; @@ -2858,7 +2856,7 @@ void RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrc, float flRa // be less than 128 units. float soundRadius = max( 128.0f, flRadius * 1.5 ); - CSoundEnt::InsertSound( SOUND_COMBAT | SOUND_CONTEXT_EXPLOSION, vecSrc, soundRadius, 0.25, info.GetInflictor() ); + CSoundEnt::InsertSound( SOUND_COMBAT | SOUND_CONTEXT_EXPLOSION, vecSrc, static_cast(soundRadius), 0.25, info.GetInflictor() ); } } diff --git a/dlls/basecombatweapon.cpp b/dlls/basecombatweapon.cpp index 1c8eac6a..15c72f01 100644 --- a/dlls/basecombatweapon.cpp +++ b/dlls/basecombatweapon.cpp @@ -208,7 +208,7 @@ class CWeaponLOSFilter : public CTraceFilterSkipTwoEntities { DECLARE_CLASS( CWeaponLOSFilter, CTraceFilterSkipTwoEntities ); public: - CWeaponLOSFilter::CWeaponLOSFilter( IHandleEntity *pHandleEntity, IHandleEntity *pHandleEntity2, int collisionGroup ) : + CWeaponLOSFilter( IHandleEntity *pHandleEntity, IHandleEntity *pHandleEntity2, int collisionGroup ) : CTraceFilterSkipTwoEntities( pHandleEntity, pHandleEntity2, collisionGroup ) { } diff --git a/dlls/baseentity.cpp b/dlls/baseentity.cpp index 010b82db..569bf263 100644 --- a/dlls/baseentity.cpp +++ b/dlls/baseentity.cpp @@ -547,8 +547,8 @@ void CBaseEntity::AddTimedOverlay( const char *msg, int endTime ) int len = strlen(msg); pNewTO->msg = new char[len + 1]; Q_strncpy(pNewTO->msg,msg, len+1); - pNewTO->msgEndTime = gpGlobals->curtime + endTime; - pNewTO->msgStartTime = gpGlobals->curtime; + pNewTO->msgEndTime = static_cast(gpGlobals->curtime + endTime); + pNewTO->msgStartTime = static_cast(gpGlobals->curtime); pNewTO->pNextTimedOverlay = m_pTimedOverlay; m_pTimedOverlay = pNewTO; } @@ -689,7 +689,7 @@ void CBaseEntity::DrawTimedOverlays(void) // If messages aren't paused fade out if (!CBaseEntity::Debug_IsPaused()) { - nAlpha = 255*((gpGlobals->curtime - pTO->msgStartTime)/(pTO->msgEndTime - pTO->msgStartTime)); + nAlpha = static_cast(255*((gpGlobals->curtime - pTO->msgStartTime)/(pTO->msgEndTime - pTO->msgStartTime))); } int r = 185; int g = 145; @@ -3300,7 +3300,7 @@ void CBaseEntity::DrawInputOverlay(const char *szInputName, CBaseEntity *pCaller { Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) <-- (%s)\n", gpGlobals->curtime, szInputName, pCaller ? pCaller->GetDebugName() : NULL); } - AddTimedOverlay(bigstring, 10.0); + AddTimedOverlay(bigstring, 10); if ( Value.FieldType() == FIELD_INTEGER ) { @@ -3331,7 +3331,7 @@ void CBaseEntity::DrawOutputOverlay(CEventAction *ev) { Q_snprintf( bigstring,sizeof(bigstring), "%3.1f (%s) --> (%s)\n", gpGlobals->curtime, STRING(ev->m_iTargetInput), STRING(ev->m_iTarget)); } - AddTimedOverlay(bigstring, 10.0); + AddTimedOverlay(bigstring, 10); // Now print to the console if ( ev->m_flDelay ) @@ -4545,7 +4545,7 @@ void CC_Find_Ent( void ) if ( bMatches ) { iCount++; - Msg(" '%s' : '%s' (entindex %d) \n", ent->GetClassname(), ent->GetEntityName(), ent->entindex() ); + Msg(" '%s' : '%s' (entindex %d) \n", ent->GetClassname(), STRING(ent->GetEntityName()), ent->entindex() ); } } @@ -4607,6 +4607,8 @@ void CC_Ent_Dump( void ) } } break; + default: + break; } // don't print out the duplicate keys @@ -6381,8 +6383,8 @@ void CBaseEntity::SUB_PerformFadeOut( void ) dt = 0.1f; } m_nRenderMode = kRenderTransTexture; - int speed = max(1,256*dt); // fade out over 1 second - SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) ); + int speed = static_cast(max(1,256*dt)); // fade out over 1 second + SetRenderColorA( static_cast(UTIL_Approach( 0, m_clrRender->a, speed )) ); } bool CBaseEntity::SUB_AllowedToFade( void ) diff --git a/dlls/baseentity.h b/dlls/baseentity.h index a3bb8a14..c3a2ae84 100644 --- a/dlls/baseentity.h +++ b/dlls/baseentity.h @@ -1007,21 +1007,21 @@ public: ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) { - COMPILE_TIME_ASSERT( sizeof(func) == 4 ); + COMPILE_TIME_ASSERT( sizeof(func) == MFP_SIZE ); m_pfnTouch = func; FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnTouch)))), name ); return func; } USEPTR UseSet( USEPTR func, char *name ) { - COMPILE_TIME_ASSERT( sizeof(func) == 4 ); + COMPILE_TIME_ASSERT( sizeof(func) == MFP_SIZE ); m_pfnUse = func; FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnUse)))), name ); return func; } ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) { - COMPILE_TIME_ASSERT( sizeof(func) == 4 ); + COMPILE_TIME_ASSERT( sizeof(func) == MFP_SIZE ); m_pfnBlocked = func; FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnBlocked)))), name ); return func; @@ -1789,7 +1789,7 @@ inline void CBaseEntity::SetName( string_t newName ) inline bool CBaseEntity::NameMatches( const char *pszNameOrWildcard ) { - if ( IDENT_STRINGS(m_iName, pszNameOrWildcard) ) + if ( IDENT_STRINGS(m_iName, MAKE_STRING(pszNameOrWildcard)) ) return true; return NameMatchesComplex( pszNameOrWildcard ); } @@ -1803,7 +1803,7 @@ inline bool CBaseEntity::NameMatches( string_t nameStr ) inline bool CBaseEntity::ClassMatches( const char *pszClassOrWildcard ) { - if ( IDENT_STRINGS(m_iClassname, pszClassOrWildcard ) ) + if ( IDENT_STRINGS(m_iClassname, MAKE_STRING(pszClassOrWildcard) ) ) return true; return ClassMatchesComplex( pszClassOrWildcard ); } diff --git a/dlls/baseflex.cpp b/dlls/baseflex.cpp index d2a30b78..4956e86e 100644 --- a/dlls/baseflex.cpp +++ b/dlls/baseflex.cpp @@ -361,6 +361,8 @@ bool CBaseFlex::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canc } } return true; + default: + break; } return false; } @@ -642,7 +644,7 @@ bool CBaseFlex::HandleStartGestureSceneEvent( CSceneEventInfo *info, CChoreoScen if ( looping ) { DevMsg( 1, "vcd error, gesture %s of model %s is marked as STUDIO_LOOPING!\n", - event->GetParameters(), GetModelName() ); + event->GetParameters(), STRING(GetModelName()) ); } SetLayerLooping( info->m_iLayer, false ); // force to not loop @@ -731,6 +733,9 @@ bool CBaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CCh case CChoreoEvent::EXPRESSION: // These are handled client-side return true; + + default: + break; } return false; diff --git a/dlls/basegrenade_concussion.cpp b/dlls/basegrenade_concussion.cpp index 530a45df..a00ca823 100644 --- a/dlls/basegrenade_concussion.cpp +++ b/dlls/basegrenade_concussion.cpp @@ -47,7 +47,7 @@ void CBaseGrenadeConcussion::FallThink(void) Remove( ); return; } - CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, GetAbsVelocity().Length( ), 0.2 ); + CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, static_cast(GetAbsVelocity().Length()), 0.2 ); SetNextThink( gpGlobals->curtime + random->RandomFloat(0.05, 0.1) ); diff --git a/dlls/basetempentity.h b/dlls/basetempentity.h index b6817307..2eeef8ce 100644 --- a/dlls/basetempentity.h +++ b/dlls/basetempentity.h @@ -1,65 +1,65 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#if !defined( BASETEMPENTITY_H ) -#define BASETEMPENTITY_H -#ifdef _WIN32 -#pragma once -#endif - -#include "edict.h" - -// This is the base class for TEMP ENTITIES that use the -// event system to propagate -class CBaseTempEntity -{ -public: - DECLARE_CLASS_NOBASE( CBaseTempEntity ); - DECLARE_SERVERCLASS(); - - CBaseTempEntity( const char *name ); - virtual ~CBaseTempEntity( void ); - - const char *GetName( void ); - - // Force all derived classes to implement a test - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - virtual void Create( IRecipientFilter& filter, float delay = 0.0 ); - - virtual void Precache( void ); - - CBaseTempEntity *GetNext( void ); - - // Get list of tempentities - static CBaseTempEntity *GetList( void ); - - // Called at startup to allow temp entities to precache any models/sounds that they need - static void PrecacheTempEnts( void ); - - void NetworkStateChanged() {} // TE's are sent out right away so we don't track whether state changes or not, - // but we want to allow CNetworkVars. - void NetworkStateChanged( void *pVar ) {} - -private: - // Descriptive name, for when running tests - const char *m_pszName; - - // Next in chain - CBaseTempEntity *m_pNext; - - // ConVars add themselves to this list for the executable. Then ConVarMgr::Init() runs through - // all the console variables and registers them. - static CBaseTempEntity *s_pTempEntities; -}; - -#endif // BASETEMPENTITY_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( BASETEMPENTITY_H ) +#define BASETEMPENTITY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "edict.h" + +// This is the base class for TEMP ENTITIES that use the +// event system to propagate +class CBaseTempEntity +{ +public: + DECLARE_CLASS_NOBASE( CBaseTempEntity ); + DECLARE_SERVERCLASS(); + + CBaseTempEntity( const char *name ); + virtual ~CBaseTempEntity( void ); + + const char *GetName( void ); + + // Force all derived classes to implement a test + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + virtual void Create( IRecipientFilter& filter, float delay = 0.0 ); + + virtual void Precache( void ); + + CBaseTempEntity *GetNext( void ); + + // Get list of tempentities + static CBaseTempEntity *GetList( void ); + + // Called at startup to allow temp entities to precache any models/sounds that they need + static void PrecacheTempEnts( void ); + + void NetworkStateChanged() {} // TE's are sent out right away so we don't track whether state changes or not, + // but we want to allow CNetworkVars. + void NetworkStateChanged( void *pVar ) {} + +private: + // Descriptive name, for when running tests + const char *m_pszName; + + // Next in chain + CBaseTempEntity *m_pNext; + + // ConVars add themselves to this list for the executable. Then ConVarMgr::Init() runs through + // all the console variables and registers them. + static CBaseTempEntity *s_pTempEntities; +}; + +#endif // BASETEMPENTITY_H diff --git a/dlls/baseviewmodel.h b/dlls/baseviewmodel.h index 3fddb478..b4dce934 100644 --- a/dlls/baseviewmodel.h +++ b/dlls/baseviewmodel.h @@ -1,17 +1,17 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Server side view model object -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#if !defined( BASEVIEWMODEL_H ) -#define BASEVIEWMODEL_H -#ifdef _WIN32 -#pragma once -#endif - -#include "baseviewmodel_shared.h" - -#endif // BASEVIEWMODEL_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Server side view model object +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( BASEVIEWMODEL_H ) +#define BASEVIEWMODEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "baseviewmodel_shared.h" + +#endif // BASEVIEWMODEL_H diff --git a/dlls/bitstring.h b/dlls/bitstring.h index 59746d31..2e627d57 100644 --- a/dlls/bitstring.h +++ b/dlls/bitstring.h @@ -325,8 +325,8 @@ inline CBitStringT::CBitStringT(int numBits) template inline bool CBitStringT::GetBit( int bitNum ) const { - Assert( bitNum >= 0 && bitNum < Size() ); - const int *pInt = GetInts() + BitString_Int( bitNum ); + Assert( bitNum >= 0 && bitNum < this->Size() ); + const int *pInt = this->GetInts() + BitString_Int( bitNum ); return ( ( *pInt & BitString_Bit( bitNum ) ) != 0 ); } @@ -335,8 +335,8 @@ inline bool CBitStringT::GetBit( int bitNum ) const template inline void CBitStringT::SetBit( int bitNum ) { - Assert( bitNum >= 0 && bitNum < Size() ); - int *pInt = GetInts() + BitString_Int( bitNum ); + Assert( bitNum >= 0 && bitNum < this->Size() ); + int *pInt = this->GetInts() + BitString_Int( bitNum ); *pInt |= BitString_Bit( bitNum ); } @@ -345,8 +345,8 @@ inline void CBitStringT::SetBit( int bitNum ) template inline void CBitStringT::ClearBit(int bitNum) { - Assert( bitNum >= 0 && bitNum < Size() ); - int *pInt = GetInts() + BitString_Int( bitNum ); + Assert( bitNum >= 0 && bitNum < this->Size() ); + int *pInt = this->GetInts() + BitString_Int( bitNum ); *pInt &= ~BitString_Bit( bitNum ); } @@ -362,10 +362,10 @@ inline void CBitStringT::And(const CBitStringT &addStr, CBitStringT *o ValidateOperand( *out ); int * pDest = out->GetInts(); - const int *pOperand1 = GetInts(); + const int *pOperand1 = this->GetInts(); const int *pOperand2 = addStr.GetInts(); - for (int i = GetNumInts() - 1; i >= 0 ; --i) + for (int i = this->GetNumInts() - 1; i >= 0 ; --i) { pDest[i] = pOperand1[i] & pOperand2[i]; } @@ -383,10 +383,10 @@ inline void CBitStringT::Or(const CBitStringT &orStr, CBitStringT *out ValidateOperand( *out ); int * pDest = out->GetInts(); - const int *pOperand1 = GetInts(); + const int *pOperand1 = this->GetInts(); const int *pOperand2 = orStr.GetInts(); - for (int i = GetNumInts() - 1; i >= 0; --i) + for (int i = this->GetNumInts() - 1; i >= 0; --i) { pDest[i] = pOperand1[i] | pOperand2[i]; } @@ -401,10 +401,10 @@ template inline void CBitStringT::Xor(const CBitStringT &xorStr, CBitStringT *out) const { int * pDest = out->GetInts(); - const int *pOperand1 = GetInts(); + const int *pOperand1 = this->GetInts(); const int *pOperand2 = xorStr.GetInts(); - for (int i = GetNumInts() - 1; i >= 0; --i) + for (int i = this->GetNumInts() - 1; i >= 0; --i) { pDest[i] = pOperand1[i] ^ pOperand2[i]; } @@ -421,9 +421,9 @@ inline void CBitStringT::Not(CBitStringT *out) const ValidateOperand( *out ); int * pDest = out->GetInts(); - const int *pOperand = GetInts(); + const int *pOperand = this->GetInts(); - for (int i = GetNumInts() - 1; i >= 0; --i) + for (int i = this->GetNumInts() - 1; i >= 0; --i) { pDest[i] = ~(pOperand[i]); } @@ -440,7 +440,7 @@ inline void CBitStringT::Copy(CBitStringT *out) const ValidateOperand( *out ); Assert( out != this ); - memcpy( out->GetInts(), GetInts(), GetNumInts() * sizeof( int ) ); + memcpy( out->GetInts(), this->GetInts(), this->GetNumInts() * sizeof( int ) ); } //----------------------------------------------------------------------------- @@ -454,11 +454,11 @@ inline bool CBitStringT::IsAllClear(void) const // Number of available bits may be more than the number // actually used, so make sure to mask out unused bits // before testing for zero - (const_cast(this))->GetInts()[GetNumInts()-1] &= ~CBitStringT::GetEndMask(); // external semantics of const retained + (const_cast(this))->GetInts()[this->GetNumInts()-1] &= ~CBitStringT::GetEndMask(); // external semantics of const retained - for (int i = GetNumInts() - 1; i >= 0; --i) + for (int i = this->GetNumInts() - 1; i >= 0; --i) { - if ( GetInts()[i] !=0 ) + if ( this->GetInts()[i] !=0 ) { return false; } @@ -477,11 +477,11 @@ inline bool CBitStringT::IsAllSet(void) const // Number of available bits may be more than the number // actually used, so make sure to mask out unused bits // before testing for set bits - (const_cast(this))->GetInts()[GetNumInts()-1] |= CBitStringT::GetEndMask(); // external semantics of const retained + (const_cast(this))->GetInts()[this->GetNumInts()-1] |= CBitStringT::GetEndMask(); // external semantics of const retained - for (int i = GetNumInts() - 1; i >= 0; --i) + for (int i = this->GetNumInts() - 1; i >= 0; --i) { - if ( GetInts()[i] != ~0 ) + if ( this->GetInts()[i] != ~0 ) { return false; } @@ -497,8 +497,8 @@ inline bool CBitStringT::IsAllSet(void) const template inline void CBitStringT::SetAllBits(void) { - if ( GetInts() ) - memset( GetInts(), 0xff, GetNumInts() * sizeof(int) ); + if ( this->GetInts() ) + memset( this->GetInts(), 0xff, this->GetNumInts() * sizeof(int) ); } //----------------------------------------------------------------------------- @@ -509,8 +509,8 @@ inline void CBitStringT::SetAllBits(void) template inline void CBitStringT::ClearAllBits(void) { - if ( GetInts() ) - memset( GetInts(), 0, GetNumInts() * sizeof(int) ); + if ( this->GetInts() ) + memset( this->GetInts(), 0, this->GetNumInts() * sizeof(int) ); } //----------------------------------------------------------------------------- @@ -518,8 +518,8 @@ inline void CBitStringT::ClearAllBits(void) template inline void CBitStringT::DebugPrintBits(void) const { - (const_cast(this))->GetInts()[GetNumInts()-1] &= ~CBitStringT::GetEndMask(); // external semantics of const retained - DebugPrintBitStringBits( GetInts(), GetNumInts() ); + (const_cast(this))->GetInts()[this->GetNumInts()-1] &= ~CBitStringT::GetEndMask(); // external semantics of const retained + DebugPrintBitStringBits( this->GetInts(), this->GetNumInts() ); } //----------------------------------------------------------------------------- @@ -527,8 +527,8 @@ inline void CBitStringT::DebugPrintBits(void) const template inline void CBitStringT::SaveBitString(CUtlBuffer& buf) const { - (const_cast(this))->GetInts()[GetNumInts()-1] &= ~CBitStringT::GetEndMask(); // external semantics of const retained - ::SaveBitString( GetInts(), GetNumInts(), buf ); + (const_cast(this))->GetInts()[this->GetNumInts()-1] &= ~CBitStringT::GetEndMask(); // external semantics of const retained + ::SaveBitString( this->GetInts(), this->GetNumInts(), buf ); } //----------------------------------------------------------------------------- @@ -536,8 +536,8 @@ inline void CBitStringT::SaveBitString(CUtlBuffer& buf) const template inline void CBitStringT::LoadBitString(CUtlBuffer& buf) { - (const_cast(this))->GetInts()[GetNumInts()-1] &= ~CBitStringT::GetEndMask(); - ::LoadBitString( GetInts(), GetNumInts(), buf ); + (const_cast(this))->GetInts()[this->GetNumInts()-1] &= ~CBitStringT::GetEndMask(); + ::LoadBitString( this->GetInts(), this->GetNumInts(), buf ); } //----------------------------------------------------------------------------- diff --git a/dlls/bmodels.cpp b/dlls/bmodels.cpp index 5f1a689a..aaa72cd7 100644 --- a/dlls/bmodels.cpp +++ b/dlls/bmodels.cpp @@ -701,7 +701,7 @@ void CFuncRotating::RampPitchVol( void ) float fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct; - int pitch = clamp(fpitch, 0, 255); + int pitch = static_cast(clamp(fpitch, 0, 255)); if (pitch == PITCH_NORM) { pitch = PITCH_NORM - 1; diff --git a/dlls/buttons.cpp b/dlls/buttons.cpp index 7d2735b5..ee872a62 100644 --- a/dlls/buttons.cpp +++ b/dlls/buttons.cpp @@ -1,1485 +1,1485 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Implements buttons. -// -//=============================================================================// - -#include "cbase.h" -#include "doors.h" -#include "ndebugoverlay.h" -#include "spark.h" -#include "vstdlib/random.h" -#include "engine/IEngineSound.h" -#include "vstdlib/strtools.h" -#include "buttons.h" -#include "eventqueue.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -void PlayLockSounds( CBaseEntity *pEdict, locksound_t *pls, int flocked, int fbutton ); -string_t MakeButtonSound( int sound ); // get string of button sound number - - -#define SF_BUTTON_DONTMOVE 1 -#define SF_ROTBUTTON_NOTSOLID 1 -#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated -#define SF_BUTTON_TOUCH_ACTIVATES 256 // Button fires when touched. -#define SF_BUTTON_DAMAGE_ACTIVATES 512 // Button fires when damaged. -#define SF_BUTTON_USE_ACTIVATES 1024 // Button fires when used. -#define SF_BUTTON_LOCKED 2048 // Whether the button is initially locked. -#define SF_BUTTON_SPARK_IF_OFF 4096 // button sparks in OFF state -#define SF_BUTTON_JIGGLE_ON_USE_LOCKED 8192 // whether to jiggle if someone uses us when we're locked - -BEGIN_DATADESC( CBaseButton ) - - DEFINE_KEYFIELD( m_vecMoveDir, FIELD_VECTOR, "movedir" ), - DEFINE_FIELD( m_fStayPushed, FIELD_BOOLEAN ), - DEFINE_FIELD( m_fRotating, FIELD_BOOLEAN ), - - DEFINE_FIELD( m_bLockedSound, FIELD_CHARACTER ), - DEFINE_FIELD( m_bLockedSentence, FIELD_CHARACTER ), - DEFINE_FIELD( m_bUnlockedSound, FIELD_CHARACTER ), - DEFINE_FIELD( m_bUnlockedSentence, FIELD_CHARACTER ), - DEFINE_FIELD( m_bLocked, FIELD_BOOLEAN ), - DEFINE_FIELD( m_sNoise, FIELD_SOUNDNAME ), - DEFINE_FIELD( m_flUseLockedTime, FIELD_TIME ), - DEFINE_FIELD( m_bSolidBsp, FIELD_BOOLEAN ), - - DEFINE_KEYFIELD( m_sounds, FIELD_INTEGER, "sounds" ), - -// DEFINE_FIELD( m_ls, FIELD_SOUNDNAME ), // This is restored in Precache() -// DEFINE_FIELD( m_nState, FIELD_INTEGER ), - - // Function Pointers - DEFINE_FUNCTION( ButtonTouch ), - DEFINE_FUNCTION( ButtonSpark ), - DEFINE_FUNCTION( TriggerAndWait ), - DEFINE_FUNCTION( ButtonReturn ), - DEFINE_FUNCTION( ButtonBackHome ), - DEFINE_FUNCTION( ButtonUse ), - - // Inputs - DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ), - DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), - DEFINE_INPUTFUNC( FIELD_VOID, "Press", InputPress ), - - // Outputs - DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ), - DEFINE_OUTPUT( m_OnPressed, "OnPressed" ), - DEFINE_OUTPUT( m_OnUseLocked, "OnUseLocked" ), - DEFINE_OUTPUT( m_OnIn, "OnIn" ), - DEFINE_OUTPUT( m_OnOut, "OnOut" ), - -END_DATADESC() - - -LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); - - - -void CBaseButton::Precache( void ) -{ - // get door button sounds, for doors which require buttons to open - if (m_bLockedSound) - { - m_ls.sLockedSound = MakeButtonSound( (int)m_bLockedSound ); - PrecacheScriptSound(m_ls.sLockedSound.ToCStr()); - } - - if (m_bUnlockedSound) - { - m_ls.sUnlockedSound = MakeButtonSound( (int)m_bUnlockedSound ); - PrecacheScriptSound(m_ls.sUnlockedSound.ToCStr()); - } - - // get sentence group names, for doors which are directly 'touched' to open - - switch (m_bLockedSentence) - { - case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied - case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout - case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door - case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door - case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door - case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door - case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment - case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door - case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door - - default: m_ls.sLockedSentence = NULL_STRING; break; - } - - switch (m_bUnlockedSentence) - { - case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted - case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door - case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door - case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door - case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door - case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door - case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment - case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door - - default: m_ls.sUnlockedSentence = NULL_STRING; break; - } - - if ( m_sNoise != NULL_STRING ) - { - PrecacheScriptSound( STRING( m_sNoise ) ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Cache user-entity-field values until spawn is called. -// Input : szKeyName - -// szValue - -// Output : Returns true if handled, false if not. -//----------------------------------------------------------------------------- -bool CBaseButton::KeyValue( const char *szKeyName, const char *szValue ) -{ - if (FStrEq(szKeyName, "locked_sound")) - { - m_bLockedSound = atof(szValue); - } - else if (FStrEq(szKeyName, "locked_sentence")) - { - m_bLockedSentence = atof(szValue); - } - else if (FStrEq(szKeyName, "unlocked_sound")) - { - m_bUnlockedSound = atof(szValue); - } - else if (FStrEq(szKeyName, "unlocked_sentence")) - { - m_bUnlockedSentence = atof(szValue); - } - else - { - return BaseClass::KeyValue( szKeyName, szValue ); - } - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Locks the button. If locked, the button will play the locked sound -// when the player tries to use it. -//----------------------------------------------------------------------------- -void CBaseButton::Lock() -{ - m_bLocked = true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Unlocks the button, making it able to be pressed again. -//----------------------------------------------------------------------------- -void CBaseButton::Unlock() -{ - m_bLocked = false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Locks the button. If locked, the button will play the locked sound -// when the player tries to use it. -//----------------------------------------------------------------------------- -void CBaseButton::InputLock( inputdata_t &inputdata ) -{ - Lock(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Unlocks the button, making it able to be pressed again. -//----------------------------------------------------------------------------- -void CBaseButton::InputUnlock( inputdata_t &inputdata ) -{ - Unlock(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Locks the button. If locked, the button will play the locked sound -// when the player tries to use it. -//----------------------------------------------------------------------------- -void CBaseButton::InputPress( inputdata_t &inputdata ) -{ - if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) - { - return; - } - - // FIXME: consolidate all the button press code into one place! - if (m_bLocked) - { - // play button locked sound - PlayLockSounds(this, &m_ls, TRUE, TRUE); - return; - } - - // Temporarily disable the touch function, until movement is finished. - SetTouch( NULL ); - - if ( m_toggle_state == TS_AT_TOP) - { - if ( m_sNoise != NULL_STRING ) - { - CPASAttenuationFilter filter( this ); - - EmitSound_t ep; - ep.m_nChannel = CHAN_VOICE; - ep.m_pSoundName = (char*)STRING(m_sNoise); - ep.m_flVolume = 1; - ep.m_SoundLevel = SNDLVL_NORM; - - EmitSound( filter, entindex(), ep ); - } - - m_OnPressed.FireOutput(m_hActivator, this); - ButtonReturn(); - } - else - { - m_OnPressed.FireOutput(m_hActivator, this); - ButtonActivate( ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: We have been damaged. Possibly activate, depending on our flags. -// Input : pInflictor - -// pAttacker - -// flDamage - -// bitsDamageType - -// Output : -//----------------------------------------------------------------------------- -int CBaseButton::OnTakeDamage( const CTakeDamageInfo &info ) -{ - m_OnDamaged.FireOutput(m_hActivator, this); - - // dvsents2: remove obselete health keyvalue from func_button - if (!HasSpawnFlags(SF_BUTTON_DAMAGE_ACTIVATES) && (m_iHealth == 0)) - { - return(0); - } - - BUTTON_CODE code = ButtonResponseToTouch(); - - if ( code == BUTTON_NOTHING ) - return 0; - - m_hActivator = info.GetAttacker(); - - // dvsents2: why would activator be NULL here? - if ( m_hActivator == NULL ) - return 0; - - if (m_bLocked) - { - return(0); - } - - // Temporarily disable the touch function, until movement is finished. - SetTouch( NULL ); - - if ( code == BUTTON_RETURN ) - { - if ( m_sNoise != NULL_STRING ) - { - CPASAttenuationFilter filter( this ); - - EmitSound_t ep; - ep.m_nChannel = CHAN_VOICE; - ep.m_pSoundName = (char*)STRING(m_sNoise); - ep.m_flVolume = 1; - ep.m_SoundLevel = SNDLVL_NORM; - - EmitSound( filter, entindex(), ep ); - } - - m_OnPressed.FireOutput(m_hActivator, this); - ButtonReturn(); - } - else - { - // code == BUTTON_ACTIVATE - m_OnPressed.FireOutput(m_hActivator, this); - ButtonActivate( ); - } - - return 0; -} - - -void CBaseButton::Spawn( ) -{ - //---------------------------------------------------- - //determine sounds for buttons - //a sound of 0 should not make a sound - //---------------------------------------------------- - if ( m_sounds ) - { - m_sNoise = MakeButtonSound( m_sounds ); - PrecacheScriptSound(m_sNoise.ToCStr()); - } - else - { - m_sNoise = NULL_STRING; - } - - Precache(); - - if ( HasSpawnFlags( SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state - { - SetThink ( &CBaseButton::ButtonSpark ); - SetNextThink( gpGlobals->curtime + 0.5f );// no hurry, make sure everything else spawns - } - - // Convert movedir from angles to a vector - QAngle angMoveDir = QAngle( m_vecMoveDir.x, m_vecMoveDir.y, m_vecMoveDir.z ); - AngleVectors( angMoveDir, &m_vecMoveDir ); - - SetMoveType( MOVETYPE_PUSH ); - SetSolid( SOLID_BSP ); - SetModel( STRING( GetModelName() ) ); - - if (m_flSpeed == 0) - { - m_flSpeed = 40; - } - - m_takedamage = DAMAGE_YES; - - if (m_flWait == 0) - { - m_flWait = 1; - } - - if (m_flLip == 0) - { - m_flLip = 4; - } - - m_toggle_state = TS_AT_BOTTOM; - m_vecPosition1 = GetLocalOrigin(); - - // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big - Vector vecButtonOBB = CollisionProp()->OBBSize(); - vecButtonOBB -= Vector( 2, 2, 2 ); - m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * (DotProductAbs( m_vecMoveDir, vecButtonOBB ) - m_flLip)); - - // Is this a non-moving button? - if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || HasSpawnFlags(SF_BUTTON_DONTMOVE) ) - { - m_vecPosition2 = m_vecPosition1; - } - - m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); - m_fRotating = FALSE; - - if (HasSpawnFlags(SF_BUTTON_LOCKED)) - { - m_bLocked = true; - } - - // - // If using activates the button, set its use function. - // - if (HasSpawnFlags(SF_BUTTON_USE_ACTIVATES)) - { - SetUse(&CBaseButton::ButtonUse); - } - else - { - SetUse(NULL); - } - - // - // If touching activates the button, set its touch function. - // - if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) - { - SetTouch( &CBaseButton::ButtonTouch ); - } - else - { - SetTouch ( NULL ); - } - - CreateVPhysics(); -} - -//----------------------------------------------------------------------------- - -bool CBaseButton::CreateVPhysics() -{ - VPhysicsInitShadow( false, false ); - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Button sound table. -// Also used by CBaseDoor to get 'touched' door lock/unlock sounds -// Input : sound - index of sound to look up. -// Output : Returns a pointer to the corresponding sound file. -//----------------------------------------------------------------------------- -string_t MakeButtonSound( int sound ) -{ - char tmp[1024]; - Q_snprintf( tmp, sizeof(tmp), "Buttons.snd%d", sound ); - return AllocPooledString(tmp); -} - - -//----------------------------------------------------------------------------- -// Purpose: Think function that emits sparks at random intervals. -//----------------------------------------------------------------------------- -void CBaseButton::ButtonSpark ( void ) -{ - SetThink ( &CBaseButton::ButtonSpark ); - SetNextThink( gpGlobals->curtime + 0.1 + random->RandomFloat ( 0, 1.5 ) );// spark again at random interval - - DoSpark( this, WorldSpaceCenter(), 1, 1, true, vec3_origin ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Called when someone uses us whilst we are locked. -//----------------------------------------------------------------------------- -bool CBaseButton::OnUseLocked( CBaseEntity *pActivator ) -{ - PlayLockSounds(this, &m_ls, TRUE, TRUE); - - if ( gpGlobals->curtime > m_flUseLockedTime ) - { - m_OnUseLocked.FireOutput( pActivator, this ); - m_flUseLockedTime = gpGlobals->curtime + 0.5; - return true; - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Use function that starts the button moving. -// Input : pActivator - -// pCaller - -// useType - -// value - -//----------------------------------------------------------------------------- -void CBaseButton::ButtonUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. - // UNDONE: Should this use ButtonResponseToTouch() too? - if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) - return; - - if (m_bLocked) - { - OnUseLocked( pActivator ); - return; - } - - m_hActivator = pActivator; - - if ( m_toggle_state == TS_AT_TOP) - { - // - // If it's a toggle button it can return now. Otherwise, it will either - // return on its own or will stay pressed indefinitely. - // - if ( HasSpawnFlags(SF_BUTTON_TOGGLE)) - { - if ( m_sNoise != NULL_STRING ) - { - CPASAttenuationFilter filter( this ); - - EmitSound_t ep; - ep.m_nChannel = CHAN_VOICE; - ep.m_pSoundName = (char*)STRING(m_sNoise); - ep.m_flVolume = 1; - ep.m_SoundLevel = SNDLVL_NORM; - - EmitSound( filter, entindex(), ep ); - } - - m_OnPressed.FireOutput(m_hActivator, this); - ButtonReturn(); - } - } - else - { - m_OnPressed.FireOutput(m_hActivator, this); - ButtonActivate( ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Returns a code indicating how the button should respond to being touched. -// Output : Returns one of the following: -// BUTTON_NOTHING - do nothing -// BUTTON_RETURN - -// BUTTON_ACTIVATE - act as if pressed -//----------------------------------------------------------------------------- -CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) -{ - // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. - if (m_toggle_state == TS_GOING_UP || - m_toggle_state == TS_GOING_DOWN || - (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !HasSpawnFlags(SF_BUTTON_TOGGLE) ) ) - return BUTTON_NOTHING; - - if (m_toggle_state == TS_AT_TOP) - { - if ( HasSpawnFlags(SF_BUTTON_TOGGLE) && !m_fStayPushed) - { - return BUTTON_RETURN; - } - } - else - return BUTTON_ACTIVATE; - - return BUTTON_NOTHING; -} - - -//----------------------------------------------------------------------------- -// Purpose: Touch function that activates the button if it responds to touch. -// Input : pOther - The entity that touched us. -//----------------------------------------------------------------------------- -void CBaseButton::ButtonTouch( CBaseEntity *pOther ) -{ - // Ignore touches by anything but players - if ( !pOther->IsPlayer() ) - return; - - m_hActivator = pOther; - - BUTTON_CODE code = ButtonResponseToTouch(); - - if ( code == BUTTON_NOTHING ) - return; - - if (!UTIL_IsMasterTriggered(m_sMaster, pOther) || m_bLocked) - { - // play button locked sound - PlayLockSounds(this, &m_ls, TRUE, TRUE); - return; - } - - // Temporarily disable the touch function, until movement is finished. - SetTouch( NULL ); - - if ( code == BUTTON_RETURN ) - { - if ( m_sNoise != NULL_STRING ) - { - CPASAttenuationFilter filter( this ); - - EmitSound_t ep; - ep.m_nChannel = CHAN_VOICE; - ep.m_pSoundName = (char*)STRING(m_sNoise); - ep.m_flVolume = 1; - ep.m_SoundLevel = SNDLVL_NORM; - - EmitSound( filter, entindex(), ep ); - } - - m_OnPressed.FireOutput(m_hActivator, this); - ButtonReturn(); - } - else - { - // code == BUTTON_ACTIVATE - m_OnPressed.FireOutput(m_hActivator, this); - ButtonActivate( ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Starts the button moving "in/up". -// Input : *pOther - -//----------------------------------------------------------------------------- -void CBaseButton::ButtonActivate( void ) -{ - if ( m_sNoise != NULL_STRING ) - { - CPASAttenuationFilter filter( this ); - - EmitSound_t ep; - ep.m_nChannel = CHAN_VOICE; - ep.m_pSoundName = (char*)STRING(m_sNoise); - ep.m_flVolume = 1; - ep.m_SoundLevel = SNDLVL_NORM; - - EmitSound( filter, entindex(), ep ); - } - - if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator) || m_bLocked) - { - // button is locked, play locked sound - PlayLockSounds(this, &m_ls, TRUE, TRUE); - return; - } - else - { - // button is unlocked, play unlocked sound - PlayLockSounds(this, &m_ls, FALSE, TRUE); - } - - ASSERT(m_toggle_state == TS_AT_BOTTOM); - m_toggle_state = TS_GOING_UP; - - SetMoveDone( &CBaseButton::TriggerAndWait ); - if (!m_fRotating) - LinearMove( m_vecPosition2, m_flSpeed); - else - AngularMove( m_vecAngle2, m_flSpeed); -} - - -//----------------------------------------------------------------------------- -// Purpose: Enables or disables the use capability based on our spawnflags. -//----------------------------------------------------------------------------- -int CBaseButton::ObjectCaps(void) -{ - return((BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | - (HasSpawnFlags(SF_BUTTON_USE_ACTIVATES) ? (FCAP_IMPULSE_USE | FCAP_USE_IN_RADIUS) : 0)); -} - - -//----------------------------------------------------------------------------- -// Purpose: Button has reached the "in/up" position. Activate its "targets", -// and pause before "popping out". -//----------------------------------------------------------------------------- -void CBaseButton::TriggerAndWait( void ) -{ - ASSERT(m_toggle_state == TS_GOING_UP); - - if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator) || m_bLocked) - { - return; - } - - m_toggle_state = TS_AT_TOP; - - // - // Re-instate touches if the button is of the toggle variety. - // - if (m_fStayPushed || HasSpawnFlags(SF_BUTTON_TOGGLE ) ) - { - if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) - { - SetTouch(&CBaseButton::ButtonTouch); - } - else - { - // BUGBUG: ALL buttons no longer respond to touch - SetTouch (NULL); - } - } - - // - // If button automatically comes back out, start it moving out. - // - else - { - SetNextThink( gpGlobals->curtime + m_flWait ); - SetThink( &CBaseButton::ButtonReturn ); - } - - m_nState = 1; // use alternate textures - - m_OnIn.FireOutput(m_hActivator, this); -} - - -//----------------------------------------------------------------------------- -// Purpose: Starts the button moving "out/down". -//----------------------------------------------------------------------------- -void CBaseButton::ButtonReturn( void ) -{ - ASSERT(m_toggle_state == TS_AT_TOP); - m_toggle_state = TS_GOING_DOWN; - - SetMoveDone( &CBaseButton::ButtonBackHome ); - if (!m_fRotating) - LinearMove( m_vecPosition1, m_flSpeed); - else - AngularMove( m_vecAngle1, m_flSpeed); - - m_nState = 0; // use normal textures -} - - -//----------------------------------------------------------------------------- -// Purpose: Button has returned to start state. Quiesce it. -//----------------------------------------------------------------------------- -void CBaseButton::ButtonBackHome( void ) -{ - ASSERT(m_toggle_state == TS_GOING_DOWN); - m_toggle_state = TS_AT_BOTTOM; - - m_OnOut.FireOutput(m_hActivator, this); - - // - // Re-instate touch method, movement cycle is complete. - // - if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) - { - SetTouch( &CBaseButton::ButtonTouch ); - } - else - { - // BUGBUG: ALL buttons no longer respond to touch - SetTouch ( NULL ); - } - - // reset think for a sparking button - if (HasSpawnFlags( SF_BUTTON_SPARK_IF_OFF ) ) - { - SetThink ( &CBaseButton::ButtonSpark ); - SetNextThink( gpGlobals->curtime + 0.5f );// no hurry - } -} - - -// -// Rotating button (aka "lever") -// -LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); - - -void CRotButton::Spawn( void ) -{ - //---------------------------------------------------- - //determine sounds for buttons - //a sound of 0 should not make a sound - //---------------------------------------------------- - if ( m_sounds ) - { - m_sNoise = MakeButtonSound( m_sounds ); - PrecacheScriptSound(m_sNoise.ToCStr()); - } - else - { - m_sNoise = NULL_STRING; - } - - // set the axis of rotation - CBaseToggle::AxisDir(); - - // check for clockwise rotation - if ( HasSpawnFlags( SF_DOOR_ROTATE_BACKWARDS) ) - { - m_vecMoveAng = m_vecMoveAng * -1; - } - - SetMoveType( MOVETYPE_PUSH ); - -#ifdef HL1_DLL - SetSolid( SOLID_BSP ); -#else - SetSolid( SOLID_VPHYSICS ); -#endif - if ( HasSpawnFlags( SF_ROTBUTTON_NOTSOLID ) ) - { - AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); - AddSolidFlags( FSOLID_NOT_SOLID ); - } - - SetModel( STRING( GetModelName() ) ); - - if (m_flSpeed == 0) - m_flSpeed = 40; - - if (m_flWait == 0) - m_flWait = 1; - - if (m_iHealth > 0) - { - m_takedamage = DAMAGE_YES; - } - - m_toggle_state = TS_AT_BOTTOM; - m_vecAngle1 = GetLocalAngles(); - m_vecAngle2 = GetLocalAngles() + m_vecMoveAng * m_flMoveDistance; - ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal\n"); - - m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); - m_fRotating = TRUE; - - SetUse(&CRotButton::ButtonUse); - - // - // If touching activates the button, set its touch function. - // - if (!HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) - { - SetTouch ( NULL ); - } - else - { - SetTouch( &CRotButton::ButtonTouch ); - } - - CreateVPhysics(); -} - - -bool CRotButton::CreateVPhysics( void ) -{ - VPhysicsInitShadow( false, false ); - return true; -} - - -//----------------------------------------------------------------------------- -// CMomentaryRotButton spawnflags -//----------------------------------------------------------------------------- -#define SF_MOMENTARY_DOOR 1 -#define SF_MOMENTARY_NOT_USABLE 2 -#define SF_MOMENTARY_AUTO_RETURN 16 - - -BEGIN_DATADESC( CMomentaryRotButton ) - - DEFINE_FIELD( m_lastUsed, FIELD_INTEGER ), - DEFINE_FIELD( m_start, FIELD_VECTOR ), - DEFINE_FIELD( m_end, FIELD_VECTOR ), - DEFINE_FIELD( m_IdealYaw, FIELD_FLOAT ), - DEFINE_FIELD( m_flTimeDelta, FIELD_FLOAT ), - DEFINE_FIELD( m_sNoise, FIELD_SOUNDNAME ), - DEFINE_FIELD( m_bUpdateTarget, FIELD_BOOLEAN ), - - DEFINE_KEYFIELD( m_direction, FIELD_INTEGER, "StartDirection" ), - DEFINE_KEYFIELD( m_returnSpeed, FIELD_FLOAT, "returnspeed" ), - DEFINE_KEYFIELD( m_flStartPosition, FIELD_FLOAT, "StartPosition"), - DEFINE_KEYFIELD( m_bSolidBsp, FIELD_BOOLEAN, "solidbsp" ), - - // Function Pointers - DEFINE_FUNCTION( UseMoveDone ), - DEFINE_FUNCTION( ReturnMoveDone ), - DEFINE_FUNCTION( SetPositionMoveDone ), - DEFINE_FUNCTION( UpdateThink ), - - // Inputs - DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPosition", InputSetPosition ), - DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPositionImmediately", InputSetPositionImmediately ), - DEFINE_INPUTFUNC( FIELD_VOID, "_DisableUpdateTarget", InputDisableUpdateTarget ), - DEFINE_INPUTFUNC( FIELD_VOID, "_EnableUpdateTarget", InputEnableUpdateTarget ), - - // Outputs - DEFINE_OUTPUT( m_Position, "Position" ), - DEFINE_OUTPUT( m_OnUnpressed, "OnUnpressed" ), - DEFINE_OUTPUT( m_OnFullyClosed, "OnFullyClosed" ), - DEFINE_OUTPUT( m_OnFullyOpen, "OnFullyOpen" ), - DEFINE_OUTPUT( m_OnReachedPosition, "OnReachedPosition" ), - - DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), - DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), - DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ) - -END_DATADESC() - - -LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); - - -//----------------------------------------------------------------------------- -// Purpose: Called when spawning, after keyvalues have been handled. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::Spawn( void ) -{ - CBaseToggle::AxisDir(); - - m_bUpdateTarget = true; - - if ( m_flSpeed == 0 ) - { - m_flSpeed = 100; - } - - // Clamp start position and issue bounds warning - if (m_flStartPosition < 0.0 || m_flStartPosition > 1.0) - { - Warning("WARNING: Momentary door (%s) start position not between 0 and 1. Clamping.\n",GetDebugName()); - m_flStartPosition = clamp(m_IdealYaw, 0, 1); - } - - // Check direction fields (for backward compatibility) - if (m_direction != 1 && m_direction != -1) - { - m_direction = 1; - } - - if (m_flMoveDistance < 0) - { - m_vecMoveAng = m_vecMoveAng * -1; - m_flMoveDistance = -m_flMoveDistance; - } - - m_start = GetLocalAngles() - m_vecMoveAng * m_flMoveDistance * m_flStartPosition; - m_end = GetLocalAngles() + m_vecMoveAng * m_flMoveDistance * (1-m_flStartPosition); - - m_IdealYaw = m_flStartPosition; - - // Force start direction at end points - if (m_flStartPosition == 0.0) - { - m_direction = -1; - } - else if (m_flStartPosition == 1.0) - { - m_direction = 1; - } - - if (HasSpawnFlags(SF_BUTTON_LOCKED)) - { - m_bLocked = true; - } - - if ( HasSpawnFlags( SF_BUTTON_USE_ACTIVATES ) ) - { - if ( m_sounds ) - { - m_sNoise = MakeButtonSound( m_sounds ); - PrecacheScriptSound(m_sNoise.ToCStr()); - } - else - { - m_sNoise = NULL_STRING; - } - - m_lastUsed = 0; - UpdateTarget(0,this); - } - -#ifdef HL1_DLL - SetSolid( SOLID_BSP ); -#else - SetSolid( SOLID_VPHYSICS ); -#endif - if (HasSpawnFlags(SF_ROTBUTTON_NOTSOLID)) - { - AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); - AddSolidFlags( FSOLID_NOT_SOLID ); - } - - SetMoveType( MOVETYPE_PUSH ); - SetModel( STRING( GetModelName() ) ); - - CreateVPhysics(); - - // Slam the object back to solid - if we really want it to be solid. - if ( m_bSolidBsp ) - { - SetSolid( SOLID_BSP ); - } - - m_bDisabled = false; -} - -int CMomentaryRotButton::ObjectCaps( void ) -{ - int flags = BaseClass::ObjectCaps(); - if (!HasSpawnFlags(SF_BUTTON_USE_ACTIVATES)) - { - return flags; - } - else - { - return (flags | FCAP_CONTINUOUS_USE | FCAP_USE_IN_RADIUS); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CMomentaryRotButton::CreateVPhysics( void ) -{ - VPhysicsInitShadow( false, false ); - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CMomentaryRotButton::PlaySound( void ) -{ - if ( m_sNoise == NULL_STRING ) - return; - - CPASAttenuationFilter filter( this ); - - EmitSound_t ep; - ep.m_nChannel = CHAN_VOICE; - ep.m_pSoundName = (char*)STRING(m_sNoise); - ep.m_flVolume = 1; - ep.m_SoundLevel = SNDLVL_NORM; - - EmitSound( filter, entindex(), ep ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Returns a given angular position as a value along our motion from 0 to 1. -// Input : vecAngles - -//----------------------------------------------------------------------------- -float CMomentaryRotButton::GetPos( const QAngle &vecAngles ) -{ - float flScale = 1; - if (( m_vecMoveAng[0] < 0 ) || ( m_vecMoveAng[1] < 0 ) || ( m_vecMoveAng[2] < 0 )) - { - flScale = -1; - } - - float flPos = flScale * CBaseToggle::AxisDelta( m_spawnflags, vecAngles, m_start ) / m_flMoveDistance; - return( clamp( flPos, 0, 1 )); -} - - -//------------------------------------------------------------------------------ -// Purpose : -// Input : flPosition -//------------------------------------------------------------------------------ -void CMomentaryRotButton::InputSetPosition( inputdata_t &inputdata ) -{ - m_IdealYaw = clamp( inputdata.value.Float(), 0, 1 ); - - float flCurPos = GetPos( GetLocalAngles() ); - if ( flCurPos < m_IdealYaw ) - { - // Moving forward (from start to end). - SetLocalAngularVelocity( m_flSpeed * m_vecMoveAng ); - m_direction = 1; - } - else if ( flCurPos > m_IdealYaw ) - { - // Moving backward (from end to start). - SetLocalAngularVelocity( -m_flSpeed * m_vecMoveAng ); - m_direction = -1; - } - else - { - // We're there already; nothing to do. - SetLocalAngularVelocity( vec3_angle ); - return; - } - - SetMoveDone( &CMomentaryRotButton::SetPositionMoveDone ); - - SetThink( &CMomentaryRotButton::UpdateThink ); - SetNextThink( gpGlobals->curtime ); - - // - // Think again in 0.1 seconds or the time that it will take us to reach our movement goal, - // whichever is the shorter interval. This prevents us from overshooting and stuttering when we - // are told to change position in very small increments. - // - QAngle vecNewAngles = m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ); - float flAngleDelta = fabs( AxisDelta( m_spawnflags, vecNewAngles, GetLocalAngles() )); - m_flTimeDelta = min( flAngleDelta / m_flSpeed, 0.1f ); - - SetMoveDoneTime( m_flTimeDelta ); -} - - -//------------------------------------------------------------------------------ -// Purpose : -// Input : flPosition -//------------------------------------------------------------------------------ -void CMomentaryRotButton::InputSetPositionImmediately( inputdata_t &inputdata ) -{ - m_IdealYaw = clamp( inputdata.value.Float(), 0, 1 ); - SetLocalAngles( m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ) ); -} - - -//------------------------------------------------------------------------------ -// Purpose: Turns off target updates so that we can change the wheel's position -// without changing the target's position. Used for jiggling when locked. -//------------------------------------------------------------------------------ -void CMomentaryRotButton::InputDisableUpdateTarget( inputdata_t &inputdata ) -{ - m_bUpdateTarget = false; -} - - -//------------------------------------------------------------------------------ -// Purpose: Turns target updates back on (after jiggling). -//------------------------------------------------------------------------------ -void CMomentaryRotButton::InputEnableUpdateTarget( inputdata_t &inputdata ) -{ - m_bUpdateTarget = true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Locks the button. If locked, the button will play the locked sound -// when the player tries to use it. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::Lock() -{ - BaseClass::Lock(); - - SetLocalAngularVelocity( vec3_angle ); - SetMoveDoneTime( -1 ); - SetMoveDone( NULL ); - - SetNextThink( TICK_NEVER_THINK ); - SetThink( NULL ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Unlocks the button, making it able to be pressed again. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::Unlock() -{ - BaseClass::Unlock(); - - SetMoveDone( &CMomentaryRotButton::ReturnMoveDone ); - - // Delay before autoreturn. - SetMoveDoneTime( 0.1f ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Fires the appropriate outputs at the extremes of motion. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::OutputMovementComplete( void ) -{ - if (m_IdealYaw == 1.0) - { - m_OnFullyClosed.FireOutput(this, this); - } - else if (m_IdealYaw == 0.0) - { - m_OnFullyOpen.FireOutput(this, this); - } - - m_OnReachedPosition.FireOutput( this, this ); -} - - -//------------------------------------------------------------------------------ -// Purpose: MoveDone function for the SetPosition input handler. Tracks our -// progress toward a movement goal and updates our outputs. -//------------------------------------------------------------------------------ -void CMomentaryRotButton::SetPositionMoveDone(void) -{ - float flCurPos = GetPos( GetLocalAngles() ); - - if ((( flCurPos >= m_IdealYaw ) && ( m_direction == 1 )) || - (( flCurPos <= m_IdealYaw ) && ( m_direction == -1 ))) - { - // - // We reached or surpassed our movement goal. - // - SetLocalAngularVelocity( vec3_angle ); - SetLocalAngles( m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ) ); - SetNextThink( TICK_NEVER_THINK ); - SetMoveDoneTime( -1 ); - UpdateTarget( m_IdealYaw, this ); - OutputMovementComplete(); - return; - } - - // TODO: change this to use a Think function like ReturnThink. - SetMoveDoneTime( m_flTimeDelta ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pActivator - -// pCaller - -// useType - -// value - -//----------------------------------------------------------------------------- -void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - if ( m_bDisabled == true ) - return; - - if (m_bLocked) - { - if ( OnUseLocked( pActivator ) && HasSpawnFlags( SF_BUTTON_JIGGLE_ON_USE_LOCKED ) ) - { - // Jiggle two degrees. - float flDist = 2.0 / m_flMoveDistance; - - // Must be first! - g_EventQueue.AddEvent( this, "_DisableUpdateTarget", 0, this, this ); - - variant_t value; - value.SetFloat( flDist ); - g_EventQueue.AddEvent( this, "SetPosition", value, 0.01, this, this ); - - value.SetFloat( 0.0 ); - g_EventQueue.AddEvent( this, "SetPosition", value, 0.1, this, this ); - - value.SetFloat( 0.5 * flDist ); - g_EventQueue.AddEvent( this, "SetPosition", value, 0.2, this, this ); - - value.SetFloat( 0.0 ); - g_EventQueue.AddEvent( this, "SetPosition", value, 0.3, this, this ); - - // Must be last! And must be late enough to cover the settling time. - g_EventQueue.AddEvent( this, "_EnableUpdateTarget", 0.5, this, this ); - } - - return; - } - - // - // Reverse our direction and play movement sound every time the player - // pauses between uses. - // - bool bPlaySound = false; - - if ( !m_lastUsed ) - { - bPlaySound = true; - m_direction = -m_direction; - - //Alert that we've been pressed - m_OnPressed.FireOutput( m_hActivator, this ); - } - - m_lastUsed = 1; - - float flPos = GetPos( GetLocalAngles() ); - UpdateSelf( flPos, bPlaySound ); - - // - // Think every frame while we are moving. - // - SetThink( &CMomentaryRotButton::UpdateThink ); - SetNextThink( gpGlobals->curtime ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Handles changing direction at the extremes of our range of motion -// and updating our avelocity while being used by the player. -// Input : value - Number from 0 to 1 indicating our desired position within -// our range of motion, 0 = start, 1 = end. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::UpdateSelf( float value, bool bPlaySound ) -{ - // - // Set our move clock to 0.1 seconds in the future so we stop spinning unless we are - // used again before then. - // - SetMoveDoneTime( 0.1 ); - - // - // If we hit the end, zero our avelocity and snap to the end angles. - // - if ( m_direction > 0 && value >= 1.0 ) - { - SetLocalAngularVelocity( vec3_angle ); - SetLocalAngles( m_end ); - - m_OnFullyClosed.FireOutput(this, this); - return; - } - // - // If we returned to the start, zero our avelocity and snap to the start angles. - // - else if ( m_direction < 0 && value <= 0 ) - { - SetLocalAngularVelocity( vec3_angle ); - SetLocalAngles( m_start ); - - m_OnFullyOpen.FireOutput(this, this); - return; - } - - if ( bPlaySound ) - { - PlaySound(); - } - - SetLocalAngularVelocity( ( m_direction * m_flSpeed ) * m_vecMoveAng ); - SetMoveDone( &CMomentaryRotButton::UseMoveDone ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Updates the value of our position, firing any targets. -// Input : value - New position, from 0 - 1. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::UpdateTarget( float value, CBaseEntity *pActivator ) -{ - if ( !m_bUpdateTarget ) - return; - - if (m_Position.Get() != value) - { - m_Position.Set(value, pActivator, this); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Handles the end of motion caused by player use. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::UseMoveDone( void ) -{ - SetLocalAngularVelocity( vec3_angle ); - - // Make sure our targets stop where we stopped. - float flPos = GetPos( GetLocalAngles() ); - UpdateTarget( flPos, this ); - - // Alert that we've been unpressed - m_OnUnpressed.FireOutput( m_hActivator, this ); - - m_lastUsed = 0; - - if ( !HasSpawnFlags( SF_BUTTON_TOGGLE ) && m_returnSpeed > 0 ) - { - SetMoveDone( &CMomentaryRotButton::ReturnMoveDone ); - m_direction = -1; - - // Delay before autoreturn. - SetMoveDoneTime( 0.1f ); - } - else - { - SetThink( NULL ); - SetMoveDone( NULL ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: MoveDone function for rotating back to the start position. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::ReturnMoveDone( void ) -{ - float value = GetPos( GetLocalAngles() ); - if ( value <= 0 ) - { - // - // Got back to the start, stop spinning. - // - SetLocalAngularVelocity( vec3_angle ); - SetLocalAngles( m_start ); - - UpdateTarget( 0, NULL ); - - SetMoveDoneTime( -1 ); - SetMoveDone( NULL ); - - SetNextThink( TICK_NEVER_THINK ); - SetThink( NULL ); - } - else - { - SetLocalAngularVelocity( -m_returnSpeed * m_vecMoveAng ); - SetMoveDoneTime( 0.1f ); - - SetThink( &CMomentaryRotButton::UpdateThink ); - SetNextThink( gpGlobals->curtime + 0.01f ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Think function for updating target as we move. -//----------------------------------------------------------------------------- -void CMomentaryRotButton::UpdateThink( void ) -{ - float value = GetPos( GetLocalAngles() ); - UpdateTarget( value, NULL ); - SetNextThink( gpGlobals->curtime ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Draw any debug text overlays -// Output : Current text offset from the top -//----------------------------------------------------------------------------- -int CMomentaryRotButton::DrawDebugTextOverlays(void) -{ - int text_offset = BaseClass::DrawDebugTextOverlays(); - - if (m_debugOverlays & OVERLAY_TEXT_BIT) - { - char tempstr[255]; - - Q_snprintf(tempstr,sizeof(tempstr),"QAngle: %.2f %.2f %.2f", GetLocalAngles()[0], GetLocalAngles()[1], GetLocalAngles()[2]); - EntityText(text_offset,tempstr,0); - text_offset++; - - Q_snprintf(tempstr,sizeof(tempstr),"AVelocity: %.2f %.2f %.2f", GetLocalAngularVelocity()[0], GetLocalAngularVelocity()[1], GetLocalAngularVelocity()[2]); - EntityText(text_offset,tempstr,0); - text_offset++; - - Q_snprintf(tempstr,sizeof(tempstr),"Target Pos: %3.3f",m_IdealYaw); - EntityText(text_offset,tempstr,0); - text_offset++; - - float flCurPos = GetPos(GetLocalAngles()); - Q_snprintf(tempstr,sizeof(tempstr),"Current Pos: %3.3f",flCurPos); - EntityText(text_offset,tempstr,0); - text_offset++; - - Q_snprintf(tempstr,sizeof(tempstr),"Direction: %s",(m_direction == 1) ? "Forward" : "Backward"); - EntityText(text_offset,tempstr,0); - text_offset++; - - } - return text_offset; -} - -//----------------------------------------------------------------------------- -// Purpose: Input hander that starts the spawner -//----------------------------------------------------------------------------- -void CMomentaryRotButton::InputEnable( inputdata_t &inputdata ) -{ - Enable(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Input hander that stops the spawner -//----------------------------------------------------------------------------- -void CMomentaryRotButton::InputDisable( inputdata_t &inputdata ) -{ - Disable(); -} - -//----------------------------------------------------------------------------- -// Purpose: Start the spawner -//----------------------------------------------------------------------------- -void CMomentaryRotButton::Enable( void ) -{ - m_bDisabled = false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Stop the spawner -//----------------------------------------------------------------------------- -void CMomentaryRotButton::Disable( void ) -{ - m_bDisabled = true; -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Implements buttons. +// +//=============================================================================// + +#include "cbase.h" +#include "doors.h" +#include "ndebugoverlay.h" +#include "spark.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "vstdlib/strtools.h" +#include "buttons.h" +#include "eventqueue.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +void PlayLockSounds( CBaseEntity *pEdict, locksound_t *pls, int flocked, int fbutton ); +string_t MakeButtonSound( int sound ); // get string of button sound number + + +#define SF_BUTTON_DONTMOVE 1 +#define SF_ROTBUTTON_NOTSOLID 1 +#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated +#define SF_BUTTON_TOUCH_ACTIVATES 256 // Button fires when touched. +#define SF_BUTTON_DAMAGE_ACTIVATES 512 // Button fires when damaged. +#define SF_BUTTON_USE_ACTIVATES 1024 // Button fires when used. +#define SF_BUTTON_LOCKED 2048 // Whether the button is initially locked. +#define SF_BUTTON_SPARK_IF_OFF 4096 // button sparks in OFF state +#define SF_BUTTON_JIGGLE_ON_USE_LOCKED 8192 // whether to jiggle if someone uses us when we're locked + +BEGIN_DATADESC( CBaseButton ) + + DEFINE_KEYFIELD( m_vecMoveDir, FIELD_VECTOR, "movedir" ), + DEFINE_FIELD( m_fStayPushed, FIELD_BOOLEAN ), + DEFINE_FIELD( m_fRotating, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( m_bUnlockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( m_bLocked, FIELD_BOOLEAN ), + DEFINE_FIELD( m_sNoise, FIELD_SOUNDNAME ), + DEFINE_FIELD( m_flUseLockedTime, FIELD_TIME ), + DEFINE_FIELD( m_bSolidBsp, FIELD_BOOLEAN ), + + DEFINE_KEYFIELD( m_sounds, FIELD_INTEGER, "sounds" ), + +// DEFINE_FIELD( m_ls, FIELD_SOUNDNAME ), // This is restored in Precache() +// DEFINE_FIELD( m_nState, FIELD_INTEGER ), + + // Function Pointers + DEFINE_FUNCTION( ButtonTouch ), + DEFINE_FUNCTION( ButtonSpark ), + DEFINE_FUNCTION( TriggerAndWait ), + DEFINE_FUNCTION( ButtonReturn ), + DEFINE_FUNCTION( ButtonBackHome ), + DEFINE_FUNCTION( ButtonUse ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ), + DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), + DEFINE_INPUTFUNC( FIELD_VOID, "Press", InputPress ), + + // Outputs + DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ), + DEFINE_OUTPUT( m_OnPressed, "OnPressed" ), + DEFINE_OUTPUT( m_OnUseLocked, "OnUseLocked" ), + DEFINE_OUTPUT( m_OnIn, "OnIn" ), + DEFINE_OUTPUT( m_OnOut, "OnOut" ), + +END_DATADESC() + + +LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); + + + +void CBaseButton::Precache( void ) +{ + // get door button sounds, for doors which require buttons to open + if (m_bLockedSound) + { + m_ls.sLockedSound = MakeButtonSound( (int)m_bLockedSound ); + PrecacheScriptSound(m_ls.sLockedSound.ToCStr()); + } + + if (m_bUnlockedSound) + { + m_ls.sUnlockedSound = MakeButtonSound( (int)m_bUnlockedSound ); + PrecacheScriptSound(m_ls.sUnlockedSound.ToCStr()); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = NULL_STRING; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = NULL_STRING; break; + } + + if ( m_sNoise != NULL_STRING ) + { + PrecacheScriptSound( STRING( m_sNoise ) ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Cache user-entity-field values until spawn is called. +// Input : szKeyName - +// szValue - +// Output : Returns true if handled, false if not. +//----------------------------------------------------------------------------- +bool CBaseButton::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "locked_sound")) + { + m_bLockedSound = static_cast(atof(szValue)); + } + else if (FStrEq(szKeyName, "locked_sentence")) + { + m_bLockedSentence = static_cast(atof(szValue)); + } + else if (FStrEq(szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = static_cast(atof(szValue)); + } + else if (FStrEq(szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = static_cast(atof(szValue)); + } + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Locks the button. If locked, the button will play the locked sound +// when the player tries to use it. +//----------------------------------------------------------------------------- +void CBaseButton::Lock() +{ + m_bLocked = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Unlocks the button, making it able to be pressed again. +//----------------------------------------------------------------------------- +void CBaseButton::Unlock() +{ + m_bLocked = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Locks the button. If locked, the button will play the locked sound +// when the player tries to use it. +//----------------------------------------------------------------------------- +void CBaseButton::InputLock( inputdata_t &inputdata ) +{ + Lock(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Unlocks the button, making it able to be pressed again. +//----------------------------------------------------------------------------- +void CBaseButton::InputUnlock( inputdata_t &inputdata ) +{ + Unlock(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Locks the button. If locked, the button will play the locked sound +// when the player tries to use it. +//----------------------------------------------------------------------------- +void CBaseButton::InputPress( inputdata_t &inputdata ) +{ + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + { + return; + } + + // FIXME: consolidate all the button press code into one place! + if (m_bLocked) + { + // play button locked sound + PlayLockSounds(this, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( m_toggle_state == TS_AT_TOP) + { + if ( m_sNoise != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = (char*)STRING(m_sNoise); + ep.m_flVolume = 1; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } + + m_OnPressed.FireOutput(m_hActivator, this); + ButtonReturn(); + } + else + { + m_OnPressed.FireOutput(m_hActivator, this); + ButtonActivate( ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: We have been damaged. Possibly activate, depending on our flags. +// Input : pInflictor - +// pAttacker - +// flDamage - +// bitsDamageType - +// Output : +//----------------------------------------------------------------------------- +int CBaseButton::OnTakeDamage( const CTakeDamageInfo &info ) +{ + m_OnDamaged.FireOutput(m_hActivator, this); + + // dvsents2: remove obselete health keyvalue from func_button + if (!HasSpawnFlags(SF_BUTTON_DAMAGE_ACTIVATES) && (m_iHealth == 0)) + { + return(0); + } + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return 0; + + m_hActivator = info.GetAttacker(); + + // dvsents2: why would activator be NULL here? + if ( m_hActivator == NULL ) + return 0; + + if (m_bLocked) + { + return(0); + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + if ( m_sNoise != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = (char*)STRING(m_sNoise); + ep.m_flVolume = 1; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } + + m_OnPressed.FireOutput(m_hActivator, this); + ButtonReturn(); + } + else + { + // code == BUTTON_ACTIVATE + m_OnPressed.FireOutput(m_hActivator, this); + ButtonActivate( ); + } + + return 0; +} + + +void CBaseButton::Spawn( ) +{ + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + if ( m_sounds ) + { + m_sNoise = MakeButtonSound( m_sounds ); + PrecacheScriptSound(m_sNoise.ToCStr()); + } + else + { + m_sNoise = NULL_STRING; + } + + Precache(); + + if ( HasSpawnFlags( SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + SetThink ( &CBaseButton::ButtonSpark ); + SetNextThink( gpGlobals->curtime + 0.5f );// no hurry, make sure everything else spawns + } + + // Convert movedir from angles to a vector + QAngle angMoveDir = QAngle( m_vecMoveDir.x, m_vecMoveDir.y, m_vecMoveDir.z ); + AngleVectors( angMoveDir, &m_vecMoveDir ); + + SetMoveType( MOVETYPE_PUSH ); + SetSolid( SOLID_BSP ); + SetModel( STRING( GetModelName() ) ); + + if (m_flSpeed == 0) + { + m_flSpeed = 40; + } + + m_takedamage = DAMAGE_YES; + + if (m_flWait == 0) + { + m_flWait = 1; + } + + if (m_flLip == 0) + { + m_flLip = 4; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecPosition1 = GetLocalOrigin(); + + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + Vector vecButtonOBB = CollisionProp()->OBBSize(); + vecButtonOBB -= Vector( 2, 2, 2 ); + m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * (DotProductAbs( m_vecMoveDir, vecButtonOBB ) - m_flLip)); + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || HasSpawnFlags(SF_BUTTON_DONTMOVE) ) + { + m_vecPosition2 = m_vecPosition1; + } + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = FALSE; + + if (HasSpawnFlags(SF_BUTTON_LOCKED)) + { + m_bLocked = true; + } + + // + // If using activates the button, set its use function. + // + if (HasSpawnFlags(SF_BUTTON_USE_ACTIVATES)) + { + SetUse(&CBaseButton::ButtonUse); + } + else + { + SetUse(NULL); + } + + // + // If touching activates the button, set its touch function. + // + if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) + { + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + SetTouch ( NULL ); + } + + CreateVPhysics(); +} + +//----------------------------------------------------------------------------- + +bool CBaseButton::CreateVPhysics() +{ + VPhysicsInitShadow( false, false ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Button sound table. +// Also used by CBaseDoor to get 'touched' door lock/unlock sounds +// Input : sound - index of sound to look up. +// Output : Returns a pointer to the corresponding sound file. +//----------------------------------------------------------------------------- +string_t MakeButtonSound( int sound ) +{ + char tmp[1024]; + Q_snprintf( tmp, sizeof(tmp), "Buttons.snd%d", sound ); + return AllocPooledString(tmp); +} + + +//----------------------------------------------------------------------------- +// Purpose: Think function that emits sparks at random intervals. +//----------------------------------------------------------------------------- +void CBaseButton::ButtonSpark ( void ) +{ + SetThink ( &CBaseButton::ButtonSpark ); + SetNextThink( gpGlobals->curtime + 0.1 + random->RandomFloat ( 0, 1.5 ) );// spark again at random interval + + DoSpark( this, WorldSpaceCenter(), 1, 1, true, vec3_origin ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when someone uses us whilst we are locked. +//----------------------------------------------------------------------------- +bool CBaseButton::OnUseLocked( CBaseEntity *pActivator ) +{ + PlayLockSounds(this, &m_ls, TRUE, TRUE); + + if ( gpGlobals->curtime > m_flUseLockedTime ) + { + m_OnUseLocked.FireOutput( pActivator, this ); + m_flUseLockedTime = gpGlobals->curtime + 0.5; + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Use function that starts the button moving. +// Input : pActivator - +// pCaller - +// useType - +// value - +//----------------------------------------------------------------------------- +void CBaseButton::ButtonUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + // UNDONE: Should this use ButtonResponseToTouch() too? + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + return; + + if (m_bLocked) + { + OnUseLocked( pActivator ); + return; + } + + m_hActivator = pActivator; + + if ( m_toggle_state == TS_AT_TOP) + { + // + // If it's a toggle button it can return now. Otherwise, it will either + // return on its own or will stay pressed indefinitely. + // + if ( HasSpawnFlags(SF_BUTTON_TOGGLE)) + { + if ( m_sNoise != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = (char*)STRING(m_sNoise); + ep.m_flVolume = 1; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } + + m_OnPressed.FireOutput(m_hActivator, this); + ButtonReturn(); + } + } + else + { + m_OnPressed.FireOutput(m_hActivator, this); + ButtonActivate( ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a code indicating how the button should respond to being touched. +// Output : Returns one of the following: +// BUTTON_NOTHING - do nothing +// BUTTON_RETURN - +// BUTTON_ACTIVATE - act as if pressed +//----------------------------------------------------------------------------- +CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + if (m_toggle_state == TS_GOING_UP || + m_toggle_state == TS_GOING_DOWN || + (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !HasSpawnFlags(SF_BUTTON_TOGGLE) ) ) + return BUTTON_NOTHING; + + if (m_toggle_state == TS_AT_TOP) + { + if ( HasSpawnFlags(SF_BUTTON_TOGGLE) && !m_fStayPushed) + { + return BUTTON_RETURN; + } + } + else + return BUTTON_ACTIVATE; + + return BUTTON_NOTHING; +} + + +//----------------------------------------------------------------------------- +// Purpose: Touch function that activates the button if it responds to touch. +// Input : pOther - The entity that touched us. +//----------------------------------------------------------------------------- +void CBaseButton::ButtonTouch( CBaseEntity *pOther ) +{ + // Ignore touches by anything but players + if ( !pOther->IsPlayer() ) + return; + + m_hActivator = pOther; + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther) || m_bLocked) + { + // play button locked sound + PlayLockSounds(this, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + if ( m_sNoise != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = (char*)STRING(m_sNoise); + ep.m_flVolume = 1; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } + + m_OnPressed.FireOutput(m_hActivator, this); + ButtonReturn(); + } + else + { + // code == BUTTON_ACTIVATE + m_OnPressed.FireOutput(m_hActivator, this); + ButtonActivate( ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Starts the button moving "in/up". +// Input : *pOther - +//----------------------------------------------------------------------------- +void CBaseButton::ButtonActivate( void ) +{ + if ( m_sNoise != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = (char*)STRING(m_sNoise); + ep.m_flVolume = 1; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator) || m_bLocked) + { + // button is locked, play locked sound + PlayLockSounds(this, &m_ls, TRUE, TRUE); + return; + } + else + { + // button is unlocked, play unlocked sound + PlayLockSounds(this, &m_ls, FALSE, TRUE); + } + + ASSERT(m_toggle_state == TS_AT_BOTTOM); + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseButton::TriggerAndWait ); + if (!m_fRotating) + LinearMove( m_vecPosition2, m_flSpeed); + else + AngularMove( m_vecAngle2, m_flSpeed); +} + + +//----------------------------------------------------------------------------- +// Purpose: Enables or disables the use capability based on our spawnflags. +//----------------------------------------------------------------------------- +int CBaseButton::ObjectCaps(void) +{ + return((BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | + (HasSpawnFlags(SF_BUTTON_USE_ACTIVATES) ? (FCAP_IMPULSE_USE | FCAP_USE_IN_RADIUS) : 0)); +} + + +//----------------------------------------------------------------------------- +// Purpose: Button has reached the "in/up" position. Activate its "targets", +// and pause before "popping out". +//----------------------------------------------------------------------------- +void CBaseButton::TriggerAndWait( void ) +{ + ASSERT(m_toggle_state == TS_GOING_UP); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator) || m_bLocked) + { + return; + } + + m_toggle_state = TS_AT_TOP; + + // + // Re-instate touches if the button is of the toggle variety. + // + if (m_fStayPushed || HasSpawnFlags(SF_BUTTON_TOGGLE ) ) + { + if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) + { + SetTouch(&CBaseButton::ButtonTouch); + } + else + { + // BUGBUG: ALL buttons no longer respond to touch + SetTouch (NULL); + } + } + + // + // If button automatically comes back out, start it moving out. + // + else + { + SetNextThink( gpGlobals->curtime + m_flWait ); + SetThink( &CBaseButton::ButtonReturn ); + } + + m_nState = 1; // use alternate textures + + m_OnIn.FireOutput(m_hActivator, this); +} + + +//----------------------------------------------------------------------------- +// Purpose: Starts the button moving "out/down". +//----------------------------------------------------------------------------- +void CBaseButton::ButtonReturn( void ) +{ + ASSERT(m_toggle_state == TS_AT_TOP); + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseButton::ButtonBackHome ); + if (!m_fRotating) + LinearMove( m_vecPosition1, m_flSpeed); + else + AngularMove( m_vecAngle1, m_flSpeed); + + m_nState = 0; // use normal textures +} + + +//----------------------------------------------------------------------------- +// Purpose: Button has returned to start state. Quiesce it. +//----------------------------------------------------------------------------- +void CBaseButton::ButtonBackHome( void ) +{ + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + m_OnOut.FireOutput(m_hActivator, this); + + // + // Re-instate touch method, movement cycle is complete. + // + if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) + { + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + // BUGBUG: ALL buttons no longer respond to touch + SetTouch ( NULL ); + } + + // reset think for a sparking button + if (HasSpawnFlags( SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink ( &CBaseButton::ButtonSpark ); + SetNextThink( gpGlobals->curtime + 0.5f );// no hurry + } +} + + +// +// Rotating button (aka "lever") +// +LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); + + +void CRotButton::Spawn( void ) +{ + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + if ( m_sounds ) + { + m_sNoise = MakeButtonSound( m_sounds ); + PrecacheScriptSound(m_sNoise.ToCStr()); + } + else + { + m_sNoise = NULL_STRING; + } + + // set the axis of rotation + CBaseToggle::AxisDir(); + + // check for clockwise rotation + if ( HasSpawnFlags( SF_DOOR_ROTATE_BACKWARDS) ) + { + m_vecMoveAng = m_vecMoveAng * -1; + } + + SetMoveType( MOVETYPE_PUSH ); + +#ifdef HL1_DLL + SetSolid( SOLID_BSP ); +#else + SetSolid( SOLID_VPHYSICS ); +#endif + if ( HasSpawnFlags( SF_ROTBUTTON_NOTSOLID ) ) + { + AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); + AddSolidFlags( FSOLID_NOT_SOLID ); + } + + SetModel( STRING( GetModelName() ) ); + + if (m_flSpeed == 0) + m_flSpeed = 40; + + if (m_flWait == 0) + m_flWait = 1; + + if (m_iHealth > 0) + { + m_takedamage = DAMAGE_YES; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecAngle1 = GetLocalAngles(); + m_vecAngle2 = GetLocalAngles() + m_vecMoveAng * m_flMoveDistance; + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal\n"); + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = TRUE; + + SetUse(&CRotButton::ButtonUse); + + // + // If touching activates the button, set its touch function. + // + if (!HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) + { + SetTouch ( NULL ); + } + else + { + SetTouch( &CRotButton::ButtonTouch ); + } + + CreateVPhysics(); +} + + +bool CRotButton::CreateVPhysics( void ) +{ + VPhysicsInitShadow( false, false ); + return true; +} + + +//----------------------------------------------------------------------------- +// CMomentaryRotButton spawnflags +//----------------------------------------------------------------------------- +#define SF_MOMENTARY_DOOR 1 +#define SF_MOMENTARY_NOT_USABLE 2 +#define SF_MOMENTARY_AUTO_RETURN 16 + + +BEGIN_DATADESC( CMomentaryRotButton ) + + DEFINE_FIELD( m_lastUsed, FIELD_INTEGER ), + DEFINE_FIELD( m_start, FIELD_VECTOR ), + DEFINE_FIELD( m_end, FIELD_VECTOR ), + DEFINE_FIELD( m_IdealYaw, FIELD_FLOAT ), + DEFINE_FIELD( m_flTimeDelta, FIELD_FLOAT ), + DEFINE_FIELD( m_sNoise, FIELD_SOUNDNAME ), + DEFINE_FIELD( m_bUpdateTarget, FIELD_BOOLEAN ), + + DEFINE_KEYFIELD( m_direction, FIELD_INTEGER, "StartDirection" ), + DEFINE_KEYFIELD( m_returnSpeed, FIELD_FLOAT, "returnspeed" ), + DEFINE_KEYFIELD( m_flStartPosition, FIELD_FLOAT, "StartPosition"), + DEFINE_KEYFIELD( m_bSolidBsp, FIELD_BOOLEAN, "solidbsp" ), + + // Function Pointers + DEFINE_FUNCTION( UseMoveDone ), + DEFINE_FUNCTION( ReturnMoveDone ), + DEFINE_FUNCTION( SetPositionMoveDone ), + DEFINE_FUNCTION( UpdateThink ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPosition", InputSetPosition ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPositionImmediately", InputSetPositionImmediately ), + DEFINE_INPUTFUNC( FIELD_VOID, "_DisableUpdateTarget", InputDisableUpdateTarget ), + DEFINE_INPUTFUNC( FIELD_VOID, "_EnableUpdateTarget", InputEnableUpdateTarget ), + + // Outputs + DEFINE_OUTPUT( m_Position, "Position" ), + DEFINE_OUTPUT( m_OnUnpressed, "OnUnpressed" ), + DEFINE_OUTPUT( m_OnFullyClosed, "OnFullyClosed" ), + DEFINE_OUTPUT( m_OnFullyOpen, "OnFullyOpen" ), + DEFINE_OUTPUT( m_OnReachedPosition, "OnReachedPosition" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ) + +END_DATADESC() + + +LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); + + +//----------------------------------------------------------------------------- +// Purpose: Called when spawning, after keyvalues have been handled. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::Spawn( void ) +{ + CBaseToggle::AxisDir(); + + m_bUpdateTarget = true; + + if ( m_flSpeed == 0 ) + { + m_flSpeed = 100; + } + + // Clamp start position and issue bounds warning + if (m_flStartPosition < 0.0 || m_flStartPosition > 1.0) + { + Warning("WARNING: Momentary door (%s) start position not between 0 and 1. Clamping.\n",GetDebugName()); + m_flStartPosition = clamp(m_IdealYaw, 0, 1); + } + + // Check direction fields (for backward compatibility) + if (m_direction != 1 && m_direction != -1) + { + m_direction = 1; + } + + if (m_flMoveDistance < 0) + { + m_vecMoveAng = m_vecMoveAng * -1; + m_flMoveDistance = -m_flMoveDistance; + } + + m_start = GetLocalAngles() - m_vecMoveAng * m_flMoveDistance * m_flStartPosition; + m_end = GetLocalAngles() + m_vecMoveAng * m_flMoveDistance * (1-m_flStartPosition); + + m_IdealYaw = m_flStartPosition; + + // Force start direction at end points + if (m_flStartPosition == 0.0) + { + m_direction = -1; + } + else if (m_flStartPosition == 1.0) + { + m_direction = 1; + } + + if (HasSpawnFlags(SF_BUTTON_LOCKED)) + { + m_bLocked = true; + } + + if ( HasSpawnFlags( SF_BUTTON_USE_ACTIVATES ) ) + { + if ( m_sounds ) + { + m_sNoise = MakeButtonSound( m_sounds ); + PrecacheScriptSound(m_sNoise.ToCStr()); + } + else + { + m_sNoise = NULL_STRING; + } + + m_lastUsed = 0; + UpdateTarget(0,this); + } + +#ifdef HL1_DLL + SetSolid( SOLID_BSP ); +#else + SetSolid( SOLID_VPHYSICS ); +#endif + if (HasSpawnFlags(SF_ROTBUTTON_NOTSOLID)) + { + AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); + AddSolidFlags( FSOLID_NOT_SOLID ); + } + + SetMoveType( MOVETYPE_PUSH ); + SetModel( STRING( GetModelName() ) ); + + CreateVPhysics(); + + // Slam the object back to solid - if we really want it to be solid. + if ( m_bSolidBsp ) + { + SetSolid( SOLID_BSP ); + } + + m_bDisabled = false; +} + +int CMomentaryRotButton::ObjectCaps( void ) +{ + int flags = BaseClass::ObjectCaps(); + if (!HasSpawnFlags(SF_BUTTON_USE_ACTIVATES)) + { + return flags; + } + else + { + return (flags | FCAP_CONTINUOUS_USE | FCAP_USE_IN_RADIUS); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CMomentaryRotButton::CreateVPhysics( void ) +{ + VPhysicsInitShadow( false, false ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMomentaryRotButton::PlaySound( void ) +{ + if ( m_sNoise == NULL_STRING ) + return; + + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_VOICE; + ep.m_pSoundName = (char*)STRING(m_sNoise); + ep.m_flVolume = 1; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a given angular position as a value along our motion from 0 to 1. +// Input : vecAngles - +//----------------------------------------------------------------------------- +float CMomentaryRotButton::GetPos( const QAngle &vecAngles ) +{ + float flScale = 1; + if (( m_vecMoveAng[0] < 0 ) || ( m_vecMoveAng[1] < 0 ) || ( m_vecMoveAng[2] < 0 )) + { + flScale = -1; + } + + float flPos = flScale * CBaseToggle::AxisDelta( m_spawnflags, vecAngles, m_start ) / m_flMoveDistance; + return( clamp( flPos, 0, 1 )); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : flPosition +//------------------------------------------------------------------------------ +void CMomentaryRotButton::InputSetPosition( inputdata_t &inputdata ) +{ + m_IdealYaw = clamp( inputdata.value.Float(), 0, 1 ); + + float flCurPos = GetPos( GetLocalAngles() ); + if ( flCurPos < m_IdealYaw ) + { + // Moving forward (from start to end). + SetLocalAngularVelocity( m_flSpeed * m_vecMoveAng ); + m_direction = 1; + } + else if ( flCurPos > m_IdealYaw ) + { + // Moving backward (from end to start). + SetLocalAngularVelocity( -m_flSpeed * m_vecMoveAng ); + m_direction = -1; + } + else + { + // We're there already; nothing to do. + SetLocalAngularVelocity( vec3_angle ); + return; + } + + SetMoveDone( &CMomentaryRotButton::SetPositionMoveDone ); + + SetThink( &CMomentaryRotButton::UpdateThink ); + SetNextThink( gpGlobals->curtime ); + + // + // Think again in 0.1 seconds or the time that it will take us to reach our movement goal, + // whichever is the shorter interval. This prevents us from overshooting and stuttering when we + // are told to change position in very small increments. + // + QAngle vecNewAngles = m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ); + float flAngleDelta = fabs( AxisDelta( m_spawnflags, vecNewAngles, GetLocalAngles() )); + m_flTimeDelta = min( flAngleDelta / m_flSpeed, 0.1f ); + + SetMoveDoneTime( m_flTimeDelta ); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : flPosition +//------------------------------------------------------------------------------ +void CMomentaryRotButton::InputSetPositionImmediately( inputdata_t &inputdata ) +{ + m_IdealYaw = clamp( inputdata.value.Float(), 0, 1 ); + SetLocalAngles( m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ) ); +} + + +//------------------------------------------------------------------------------ +// Purpose: Turns off target updates so that we can change the wheel's position +// without changing the target's position. Used for jiggling when locked. +//------------------------------------------------------------------------------ +void CMomentaryRotButton::InputDisableUpdateTarget( inputdata_t &inputdata ) +{ + m_bUpdateTarget = false; +} + + +//------------------------------------------------------------------------------ +// Purpose: Turns target updates back on (after jiggling). +//------------------------------------------------------------------------------ +void CMomentaryRotButton::InputEnableUpdateTarget( inputdata_t &inputdata ) +{ + m_bUpdateTarget = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Locks the button. If locked, the button will play the locked sound +// when the player tries to use it. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::Lock() +{ + BaseClass::Lock(); + + SetLocalAngularVelocity( vec3_angle ); + SetMoveDoneTime( -1 ); + SetMoveDone( NULL ); + + SetNextThink( TICK_NEVER_THINK ); + SetThink( NULL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Unlocks the button, making it able to be pressed again. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::Unlock() +{ + BaseClass::Unlock(); + + SetMoveDone( &CMomentaryRotButton::ReturnMoveDone ); + + // Delay before autoreturn. + SetMoveDoneTime( 0.1f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Fires the appropriate outputs at the extremes of motion. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::OutputMovementComplete( void ) +{ + if (m_IdealYaw == 1.0) + { + m_OnFullyClosed.FireOutput(this, this); + } + else if (m_IdealYaw == 0.0) + { + m_OnFullyOpen.FireOutput(this, this); + } + + m_OnReachedPosition.FireOutput( this, this ); +} + + +//------------------------------------------------------------------------------ +// Purpose: MoveDone function for the SetPosition input handler. Tracks our +// progress toward a movement goal and updates our outputs. +//------------------------------------------------------------------------------ +void CMomentaryRotButton::SetPositionMoveDone(void) +{ + float flCurPos = GetPos( GetLocalAngles() ); + + if ((( flCurPos >= m_IdealYaw ) && ( m_direction == 1 )) || + (( flCurPos <= m_IdealYaw ) && ( m_direction == -1 ))) + { + // + // We reached or surpassed our movement goal. + // + SetLocalAngularVelocity( vec3_angle ); + SetLocalAngles( m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ) ); + SetNextThink( TICK_NEVER_THINK ); + SetMoveDoneTime( -1 ); + UpdateTarget( m_IdealYaw, this ); + OutputMovementComplete(); + return; + } + + // TODO: change this to use a Think function like ReturnThink. + SetMoveDoneTime( m_flTimeDelta ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pActivator - +// pCaller - +// useType - +// value - +//----------------------------------------------------------------------------- +void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_bDisabled == true ) + return; + + if (m_bLocked) + { + if ( OnUseLocked( pActivator ) && HasSpawnFlags( SF_BUTTON_JIGGLE_ON_USE_LOCKED ) ) + { + // Jiggle two degrees. + float flDist = 2.0 / m_flMoveDistance; + + // Must be first! + g_EventQueue.AddEvent( this, "_DisableUpdateTarget", 0, this, this ); + + variant_t value; + value.SetFloat( flDist ); + g_EventQueue.AddEvent( this, "SetPosition", value, 0.01, this, this ); + + value.SetFloat( 0.0 ); + g_EventQueue.AddEvent( this, "SetPosition", value, 0.1, this, this ); + + value.SetFloat( 0.5 * flDist ); + g_EventQueue.AddEvent( this, "SetPosition", value, 0.2, this, this ); + + value.SetFloat( 0.0 ); + g_EventQueue.AddEvent( this, "SetPosition", value, 0.3, this, this ); + + // Must be last! And must be late enough to cover the settling time. + g_EventQueue.AddEvent( this, "_EnableUpdateTarget", 0.5, this, this ); + } + + return; + } + + // + // Reverse our direction and play movement sound every time the player + // pauses between uses. + // + bool bPlaySound = false; + + if ( !m_lastUsed ) + { + bPlaySound = true; + m_direction = -m_direction; + + //Alert that we've been pressed + m_OnPressed.FireOutput( m_hActivator, this ); + } + + m_lastUsed = 1; + + float flPos = GetPos( GetLocalAngles() ); + UpdateSelf( flPos, bPlaySound ); + + // + // Think every frame while we are moving. + // + SetThink( &CMomentaryRotButton::UpdateThink ); + SetNextThink( gpGlobals->curtime ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles changing direction at the extremes of our range of motion +// and updating our avelocity while being used by the player. +// Input : value - Number from 0 to 1 indicating our desired position within +// our range of motion, 0 = start, 1 = end. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::UpdateSelf( float value, bool bPlaySound ) +{ + // + // Set our move clock to 0.1 seconds in the future so we stop spinning unless we are + // used again before then. + // + SetMoveDoneTime( 0.1 ); + + // + // If we hit the end, zero our avelocity and snap to the end angles. + // + if ( m_direction > 0 && value >= 1.0 ) + { + SetLocalAngularVelocity( vec3_angle ); + SetLocalAngles( m_end ); + + m_OnFullyClosed.FireOutput(this, this); + return; + } + // + // If we returned to the start, zero our avelocity and snap to the start angles. + // + else if ( m_direction < 0 && value <= 0 ) + { + SetLocalAngularVelocity( vec3_angle ); + SetLocalAngles( m_start ); + + m_OnFullyOpen.FireOutput(this, this); + return; + } + + if ( bPlaySound ) + { + PlaySound(); + } + + SetLocalAngularVelocity( ( m_direction * m_flSpeed ) * m_vecMoveAng ); + SetMoveDone( &CMomentaryRotButton::UseMoveDone ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Updates the value of our position, firing any targets. +// Input : value - New position, from 0 - 1. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::UpdateTarget( float value, CBaseEntity *pActivator ) +{ + if ( !m_bUpdateTarget ) + return; + + if (m_Position.Get() != value) + { + m_Position.Set(value, pActivator, this); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles the end of motion caused by player use. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::UseMoveDone( void ) +{ + SetLocalAngularVelocity( vec3_angle ); + + // Make sure our targets stop where we stopped. + float flPos = GetPos( GetLocalAngles() ); + UpdateTarget( flPos, this ); + + // Alert that we've been unpressed + m_OnUnpressed.FireOutput( m_hActivator, this ); + + m_lastUsed = 0; + + if ( !HasSpawnFlags( SF_BUTTON_TOGGLE ) && m_returnSpeed > 0 ) + { + SetMoveDone( &CMomentaryRotButton::ReturnMoveDone ); + m_direction = -1; + + // Delay before autoreturn. + SetMoveDoneTime( 0.1f ); + } + else + { + SetThink( NULL ); + SetMoveDone( NULL ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: MoveDone function for rotating back to the start position. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::ReturnMoveDone( void ) +{ + float value = GetPos( GetLocalAngles() ); + if ( value <= 0 ) + { + // + // Got back to the start, stop spinning. + // + SetLocalAngularVelocity( vec3_angle ); + SetLocalAngles( m_start ); + + UpdateTarget( 0, NULL ); + + SetMoveDoneTime( -1 ); + SetMoveDone( NULL ); + + SetNextThink( TICK_NEVER_THINK ); + SetThink( NULL ); + } + else + { + SetLocalAngularVelocity( -m_returnSpeed * m_vecMoveAng ); + SetMoveDoneTime( 0.1f ); + + SetThink( &CMomentaryRotButton::UpdateThink ); + SetNextThink( gpGlobals->curtime + 0.01f ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Think function for updating target as we move. +//----------------------------------------------------------------------------- +void CMomentaryRotButton::UpdateThink( void ) +{ + float value = GetPos( GetLocalAngles() ); + UpdateTarget( value, NULL ); + SetNextThink( gpGlobals->curtime ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CMomentaryRotButton::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[255]; + + Q_snprintf(tempstr,sizeof(tempstr),"QAngle: %.2f %.2f %.2f", GetLocalAngles()[0], GetLocalAngles()[1], GetLocalAngles()[2]); + EntityText(text_offset,tempstr,0); + text_offset++; + + Q_snprintf(tempstr,sizeof(tempstr),"AVelocity: %.2f %.2f %.2f", GetLocalAngularVelocity()[0], GetLocalAngularVelocity()[1], GetLocalAngularVelocity()[2]); + EntityText(text_offset,tempstr,0); + text_offset++; + + Q_snprintf(tempstr,sizeof(tempstr),"Target Pos: %3.3f",m_IdealYaw); + EntityText(text_offset,tempstr,0); + text_offset++; + + float flCurPos = GetPos(GetLocalAngles()); + Q_snprintf(tempstr,sizeof(tempstr),"Current Pos: %3.3f",flCurPos); + EntityText(text_offset,tempstr,0); + text_offset++; + + Q_snprintf(tempstr,sizeof(tempstr),"Direction: %s",(m_direction == 1) ? "Forward" : "Backward"); + EntityText(text_offset,tempstr,0); + text_offset++; + + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// Purpose: Input hander that starts the spawner +//----------------------------------------------------------------------------- +void CMomentaryRotButton::InputEnable( inputdata_t &inputdata ) +{ + Enable(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input hander that stops the spawner +//----------------------------------------------------------------------------- +void CMomentaryRotButton::InputDisable( inputdata_t &inputdata ) +{ + Disable(); +} + +//----------------------------------------------------------------------------- +// Purpose: Start the spawner +//----------------------------------------------------------------------------- +void CMomentaryRotButton::Enable( void ) +{ + m_bDisabled = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Stop the spawner +//----------------------------------------------------------------------------- +void CMomentaryRotButton::Disable( void ) +{ + m_bDisabled = true; +} diff --git a/dlls/cbase.cpp b/dlls/cbase.cpp index 2c2f703c..3c5cf209 100644 --- a/dlls/cbase.cpp +++ b/dlls/cbase.cpp @@ -1200,6 +1200,7 @@ void variant_t::SetOther( void *data ) case FIELD_EHANDLE: *((EHANDLE *)data) = eVal; break; case FIELD_CLASSPTR: *((CBaseEntity **)data) = eVal; break; + default: break; } } @@ -1252,6 +1253,8 @@ bool variant_t::Convert( fieldtype_t newType ) SetBool( iVal != 0 ); return true; } + default: + break; } break; } @@ -1271,6 +1274,8 @@ bool variant_t::Convert( fieldtype_t newType ) SetBool( flVal != 0 ); return true; } + default: + break; } break; } @@ -1358,6 +1363,9 @@ bool variant_t::Convert( fieldtype_t newType ) SetEntity( ent ); return true; } + + default: + break; } break; @@ -1377,9 +1385,13 @@ bool variant_t::Convert( fieldtype_t newType ) } return true; } + default: + break; } break; } + default: + break; } // invalid conversion @@ -1457,6 +1469,9 @@ const char *variant_t::ToString( void ) const Q_strncpy( szBuf, pszName, 512 ); return (szBuf); } + + default: + break; } return("No conversion to string"); diff --git a/dlls/client.cpp b/dlls/client.cpp index 695e196d..37574d99 100644 --- a/dlls/client.cpp +++ b/dlls/client.cpp @@ -830,7 +830,7 @@ void CC_Player_TestDispatchEffect( void ) AngleVectors( vecAngles, &data.m_vNormal ); } data.m_nEntIndex = pPlayer->entindex(); - data.m_fFlags = flags; + data.m_fFlags = static_cast(flags); data.m_flMagnitude = magnitude; data.m_flScale = scale; DispatchEffect( (char *)engine->Cmd_Argv(1), data ); @@ -1148,6 +1148,7 @@ void CC_HurtMe_f(void) static ConCommand hurtme("hurtme", CC_HurtMe_f, "Hurts the player.\n\tArguments: ", FCVAR_CHEAT); +#ifdef _DEBUG static bool IsInGroundList( CBaseEntity *ent, CBaseEntity *ground ) { if ( !ground || !ent ) @@ -1169,6 +1170,7 @@ static bool IsInGroundList( CBaseEntity *ent, CBaseEntity *ground ) return false; } +#endif static int DescribeGroundList( CBaseEntity *ent ) { diff --git a/dlls/controlentities.cpp b/dlls/controlentities.cpp index 17713e24..49a5c38c 100644 --- a/dlls/controlentities.cpp +++ b/dlls/controlentities.cpp @@ -128,7 +128,7 @@ void CTargetChangeGravity::InputChangeGrav( inputdata_t &inputdata ) // Save the gravity to restore it in InputResetGrav if ( m_iOldGrav ) - m_iOldGrav = pl->GetGravity(); + m_iOldGrav = static_cast(pl->GetGravity()); pl->SetGravity(m_iGravity); } diff --git a/dlls/cplane.cpp b/dlls/cplane.cpp index c6051056..fbfc8e57 100644 --- a/dlls/cplane.cpp +++ b/dlls/cplane.cpp @@ -1,71 +1,71 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "game.h" -#include "cplane.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//========================================================= -// Plane -//========================================================= -CPlane::CPlane ( void ) -{ - m_fInitialized = FALSE; -} - -//========================================================= -// InitializePlane - Takes a normal for the plane and a -// point on the plane and -//========================================================= -void CPlane::InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) -{ - m_vecNormal = vecNormal; - m_flDist = DotProduct ( m_vecNormal, vecPoint ); - m_fInitialized = TRUE; -} - - -//========================================================= -// PointInFront - determines whether the given vector is -// in front of the plane. -//========================================================= -bool CPlane::PointInFront ( const Vector &vecPoint ) -{ - float flFace; - - if ( !m_fInitialized ) - { - return FALSE; - } - - flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; - - if ( flFace >= 0 ) - { - return TRUE; - } - - return FALSE; -} - -//========================================================= -//========================================================= -float CPlane::PointDist ( const Vector &vecPoint ) -{ - float flDist; - - if ( !m_fInitialized ) - { - return FALSE; - } - - flDist = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; - - return flDist; -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "game.h" +#include "cplane.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//========================================================= +// Plane +//========================================================= +CPlane::CPlane ( void ) +{ + m_fInitialized = FALSE; +} + +//========================================================= +// InitializePlane - Takes a normal for the plane and a +// point on the plane and +//========================================================= +void CPlane::InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) +{ + m_vecNormal = vecNormal; + m_flDist = DotProduct ( m_vecNormal, vecPoint ); + m_fInitialized = TRUE; +} + + +//========================================================= +// PointInFront - determines whether the given vector is +// in front of the plane. +//========================================================= +bool CPlane::PointInFront ( const Vector &vecPoint ) +{ + float flFace; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + if ( flFace >= 0 ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +//========================================================= +float CPlane::PointDist ( const Vector &vecPoint ) +{ + float flDist; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flDist = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + return flDist; +} diff --git a/dlls/cplane.h b/dlls/cplane.h index e1ecd9ec..61bfedf3 100644 --- a/dlls/cplane.h +++ b/dlls/cplane.h @@ -1,44 +1,44 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#pragma once - -#ifndef CPLANE_H -#define CPLANE_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 ); - - //========================================================= - // How far off the plane is this point? - //========================================================= - float PointDist( const Vector &vecPoint ); - -private: - Vector m_vecNormal; - float m_flDist; - bool m_fInitialized; -}; - -#endif //CPLANE_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#pragma once + +#ifndef CPLANE_H +#define CPLANE_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 ); + + //========================================================= + // How far off the plane is this point? + //========================================================= + float PointDist( const Vector &vecPoint ); + +private: + Vector m_vecNormal; + float m_flDist; + bool m_fInitialized; +}; + +#endif //CPLANE_H diff --git a/dlls/cterrainmorph.cpp b/dlls/cterrainmorph.cpp index 0e53c786..18227504 100644 --- a/dlls/cterrainmorph.cpp +++ b/dlls/cterrainmorph.cpp @@ -175,7 +175,7 @@ void CTerrainMorph::MorphThink( void ) //Msg( "Strength: %f - Radius: %f\n", params.m_flStrength, params.m_flRadius ); TerrainMod_Add( type, params ); - m_iIterations += params.m_flStrength; + m_iIterations += static_cast(params.m_flStrength); } #if 0 NDebugOverlay::Line( m_Params.m_vecLocation, m_Params.m_vecLocation + Vector( 200, 200, 0 ), 0,255,0, true, 3 ); diff --git a/dlls/doors.cpp b/dlls/doors.cpp index 8e13a1d9..a758f6fa 100644 --- a/dlls/doors.cpp +++ b/dlls/doors.cpp @@ -214,11 +214,11 @@ bool CBaseDoor::KeyValue( const char *szKeyName, const char *szValue ) { if (FStrEq(szKeyName, "locked_sentence")) { - m_bLockedSentence = atof(szValue); + m_bLockedSentence = static_cast(atof(szValue)); } else if (FStrEq(szKeyName, "unlocked_sentence")) { - m_bUnlockedSentence = atof(szValue); + m_bUnlockedSentence = static_cast(atof(szValue)); } else return BaseClass::KeyValue( szKeyName, szValue ); @@ -454,6 +454,8 @@ void CBaseDoor::Activate( void ) case TS_AT_BOTTOM: UpdateAreaPortals( false ); break; + default: + break; } #ifdef HL1_DLL diff --git a/dlls/effects.cpp b/dlls/effects.cpp index b3441cf8..945df75f 100644 --- a/dlls/effects.cpp +++ b/dlls/effects.cpp @@ -968,7 +968,7 @@ void CTestEffect::Think( void ) for (i = 0; i < m_iBeam; i++) { t = (gpGlobals->curtime - m_flBeamTime[i]) / ( 3 + m_flStartTime - m_flBeamTime[i]); - m_pBeam[i]->SetBrightness( 255 * t ); + m_pBeam[i]->SetBrightness( static_cast(255 * t) ); // m_pBeam[i]->SetScrollRate( 20 * t ); } SetNextThink( gpGlobals->curtime + 0.1f ); @@ -1149,11 +1149,11 @@ void CBlood::InputEmitBlood( inputdata_t &inputdata ) { if ( HasSpawnFlags( SF_BLOOD_STREAM ) ) { - UTIL_BloodStream( BloodPosition(inputdata.pActivator), Direction(), Color(), BloodAmount() ); + UTIL_BloodStream( BloodPosition(inputdata.pActivator), Direction(), Color(), static_cast(BloodAmount()) ); } else { - UTIL_BloodDrips( BloodPosition(inputdata.pActivator), Direction(), Color(), BloodAmount() ); + UTIL_BloodDrips( BloodPosition(inputdata.pActivator), Direction(), Color(), static_cast(BloodAmount()) ); } if ( HasSpawnFlags( SF_BLOOD_DECAL ) ) @@ -1190,7 +1190,7 @@ void CBlood::InputEmitBlood( inputdata_t &inputdata ) nFlags |= FX_BLOODSPRAY_GORE; } - UTIL_BloodSpray(GetAbsOrigin(), Direction(), Color(), BloodAmount(), nFlags); + UTIL_BloodSpray(GetAbsOrigin(), Direction(), Color(), static_cast(BloodAmount()), nFlags); } } @@ -1591,7 +1591,7 @@ void CEnvWind::Spawn( void ) SetSolid( SOLID_NONE ); AddEffects( EF_NODRAW ); - m_EnvWindShared.Init( entindex(), 0, gpGlobals->frametime, GetLocalAngles().y, 0 ); + m_EnvWindShared.Init( entindex(), 0, gpGlobals->frametime, static_cast(GetLocalAngles().y), 0 ); SetThink( &CEnvWind::WindThink ); SetNextThink( gpGlobals->curtime ); diff --git a/dlls/entityblocker.cpp b/dlls/entityblocker.cpp index 21878dad..9674cf7e 100644 --- a/dlls/entityblocker.cpp +++ b/dlls/entityblocker.cpp @@ -1,76 +1,76 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" -#include "entityblocker.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -LINK_ENTITY_TO_CLASS( entity_blocker, CEntityBlocker ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &origin - -// &mins - -// &maxs - -// NULL - -// Output : CEntityBlocker -//----------------------------------------------------------------------------- -CEntityBlocker *CEntityBlocker::Create( const Vector &origin, const Vector &mins, const Vector &maxs, CBaseEntity *pOwner, bool bBlockPhysics ) -{ - CEntityBlocker *pBlocker = (CEntityBlocker *) CBaseEntity::Create( "entity_blocker", origin, vec3_angle, pOwner ); - - if ( pBlocker != NULL ) - { - pBlocker->SetSize( mins, maxs ); - if ( bBlockPhysics ) - { - pBlocker->VPhysicsInitStatic(); - } - } - - return pBlocker; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CEntityBlocker::Spawn( void ) -{ - SetSolid( SOLID_BBOX ); - AddSolidFlags( FSOLID_CUSTOMRAYTEST ); -} - -//----------------------------------------------------------------------------- -// Purpose: Entity blockers don't block tracelines so they don't screw up weapon fire, etc -//----------------------------------------------------------------------------- -bool CEntityBlocker::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) -{ - return false; -} - -//------------------------------------------------------------------------------ -// Purpose : -// Input : -// Output : -//------------------------------------------------------------------------------ -void CC_Test_Entity_Blocker( void ) -{ - CBasePlayer *pPlayer = UTIL_GetCommandClient(); - Vector vecForward; - pPlayer->GetVectors( &vecForward, NULL, NULL ); - - trace_t tr; - Vector vecOrigin = pPlayer->GetAbsOrigin() + (vecForward * 256); - UTIL_TraceHull( vecOrigin + Vector(0,0,256), vecOrigin - Vector(0,0,256), VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); - if ( !tr.allsolid && !tr.startsolid ) - { - CEntityBlocker::Create( tr.endpos, VEC_HULL_MIN, VEC_HULL_MAX, NULL, true ); - NDebugOverlay::Box( tr.endpos, VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 64, 1000.0 ); - } -} -static ConCommand test_entity_blocker("test_entity_blocker", CC_Test_Entity_Blocker, "Test command that drops an entity blocker out in front of the player.", FCVAR_CHEAT ); \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "entityblocker.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LINK_ENTITY_TO_CLASS( entity_blocker, CEntityBlocker ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &mins - +// &maxs - +// NULL - +// Output : CEntityBlocker +//----------------------------------------------------------------------------- +CEntityBlocker *CEntityBlocker::Create( const Vector &origin, const Vector &mins, const Vector &maxs, CBaseEntity *pOwner, bool bBlockPhysics ) +{ + CEntityBlocker *pBlocker = (CEntityBlocker *) CBaseEntity::Create( "entity_blocker", origin, vec3_angle, pOwner ); + + if ( pBlocker != NULL ) + { + pBlocker->SetSize( mins, maxs ); + if ( bBlockPhysics ) + { + pBlocker->VPhysicsInitStatic(); + } + } + + return pBlocker; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEntityBlocker::Spawn( void ) +{ + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_CUSTOMRAYTEST ); +} + +//----------------------------------------------------------------------------- +// Purpose: Entity blockers don't block tracelines so they don't screw up weapon fire, etc +//----------------------------------------------------------------------------- +bool CEntityBlocker::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) +{ + return false; +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CC_Test_Entity_Blocker( void ) +{ + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + Vector vecForward; + pPlayer->GetVectors( &vecForward, NULL, NULL ); + + trace_t tr; + Vector vecOrigin = pPlayer->GetAbsOrigin() + (vecForward * 256); + UTIL_TraceHull( vecOrigin + Vector(0,0,256), vecOrigin - Vector(0,0,256), VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + if ( !tr.allsolid && !tr.startsolid ) + { + CEntityBlocker::Create( tr.endpos, VEC_HULL_MIN, VEC_HULL_MAX, NULL, true ); + NDebugOverlay::Box( tr.endpos, VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 64, 1000.0 ); + } +} +static ConCommand test_entity_blocker("test_entity_blocker", CC_Test_Entity_Blocker, "Test command that drops an entity blocker out in front of the player.", FCVAR_CHEAT ); diff --git a/dlls/entitylist.cpp b/dlls/entitylist.cpp index 355f60b0..610e03bd 100644 --- a/dlls/entitylist.cpp +++ b/dlls/entitylist.cpp @@ -146,7 +146,7 @@ public: void Clear() { m_simThinkList.Purge(); - for ( int i = 0; i < ARRAYSIZE(m_entinfoIndex); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(m_entinfoIndex); i++ ) { m_entinfoIndex[i] = 0xFFFF; } diff --git a/dlls/env_entity_maker.cpp b/dlls/env_entity_maker.cpp index c505511c..449f6cfb 100644 --- a/dlls/env_entity_maker.cpp +++ b/dlls/env_entity_maker.cpp @@ -109,7 +109,7 @@ void CEnvEntityMaker::Activate( void ) // check for valid template if ( m_iszTemplate == NULL_STRING ) { - Warning( "env_entity_maker %s has no template entity!\n", GetEntityName() ); + Warning( "env_entity_maker %s has no template entity!\n", STRING(GetEntityName()) ); UTIL_Remove( this ); return; } @@ -131,7 +131,7 @@ CPointTemplate *CEnvEntityMaker::FindTemplate() CPointTemplate *pTemplate = dynamic_cast(gEntList.FindEntityByName( NULL, STRING(m_iszTemplate) )); if ( !pTemplate ) { - Warning( "env_entity_maker %s failed to find template %s.\n", GetEntityName(), STRING(m_iszTemplate) ); + Warning( "env_entity_maker %s failed to find template %s.\n", STRING(GetEntityName()), STRING(m_iszTemplate) ); } return pTemplate; diff --git a/dlls/env_zoom.h b/dlls/env_zoom.h index 960aabec..64e5eefd 100644 --- a/dlls/env_zoom.h +++ b/dlls/env_zoom.h @@ -1,13 +1,13 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef ENV_ZOOM_H -#define ENV_ZOOM_H - -bool CanOverrideEnvZoomOwner( CBaseEntity *pZoomOwner ); -float GetZoomOwnerDesiredFOV( CBaseEntity *pZoomOwner ); - -#endif //ENV_ZOOM_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef ENV_ZOOM_H +#define ENV_ZOOM_H + +bool CanOverrideEnvZoomOwner( CBaseEntity *pZoomOwner ); +float GetZoomOwnerDesiredFOV( CBaseEntity *pZoomOwner ); + +#endif //ENV_ZOOM_H diff --git a/dlls/envmicrophone.cpp b/dlls/envmicrophone.cpp index dda4314e..3e099b61 100644 --- a/dlls/envmicrophone.cpp +++ b/dlls/envmicrophone.cpp @@ -92,7 +92,7 @@ void CEnvMicrophone::Spawn(void) { SF_MICROPHONE_SOUND_EXPLOSION, SOUND_CONTEXT_EXPLOSION }, }; - for (int i = 0; i < sizeof(nFlags) / sizeof(nFlags[0]); i++) + for (size_t i = 0; i < sizeof(nFlags) / sizeof(nFlags[0]); i++) { if (m_spawnflags & nFlags[i][0]) { diff --git a/dlls/episodic/ai_behavior_passenger_companion.cpp b/dlls/episodic/ai_behavior_passenger_companion.cpp index 3b23a62d..bc05b79c 100644 --- a/dlls/episodic/ai_behavior_passenger_companion.cpp +++ b/dlls/episodic/ai_behavior_passenger_companion.cpp @@ -64,9 +64,9 @@ BEGIN_SIMPLE_DATADESC( FailPosition_t ) END_DATADESC(); CAI_PassengerBehaviorCompanion::CAI_PassengerBehaviorCompanion( void ) : -m_flUnseenDuration( 0.0f ), m_flNextOverturnWarning( 0.0f ), m_flOverturnedDuration( 0.0f ), +m_flUnseenDuration( 0.0f ), m_nExitAttempts( 0 ) { memset( &m_vehicleState, 0, sizeof( m_vehicleState ) ); diff --git a/dlls/episodic/ai_behavior_passenger_zombie.cpp b/dlls/episodic/ai_behavior_passenger_zombie.cpp index a0a23e7c..b17750bf 100644 --- a/dlls/episodic/ai_behavior_passenger_zombie.cpp +++ b/dlls/episodic/ai_behavior_passenger_zombie.cpp @@ -70,8 +70,8 @@ impactdamagetable_t gZombiePassengerImpactDamageTable = // Constructor //----------------------------------------------------------------------------- CAI_PassengerBehaviorZombie::CAI_PassengerBehaviorZombie( void ) : -m_flLastVerticalLean( 0.0f ), m_flLastLateralLean( 0.0f ), +m_flLastVerticalLean( 0.0f ), m_flNextLeapTime( 0.0f ) { } diff --git a/dlls/episodic/grenade_hopwire.cpp b/dlls/episodic/grenade_hopwire.cpp index 714c397c..bd3bd9e8 100644 --- a/dlls/episodic/grenade_hopwire.cpp +++ b/dlls/episodic/grenade_hopwire.cpp @@ -38,7 +38,7 @@ class CGravityVortexController : public CBaseEntity public: - CGravityVortexController( void ) : m_flEndTime( 0.0f ), m_flRadius( 256 ), m_flStrength( 256 ), m_flMass( 0.0f ) {} + CGravityVortexController( void ) : m_flMass( 0.0f ), m_flEndTime( 0.0f ), m_flRadius( 256 ), m_flStrength( 256 ) {} float GetConsumedMass( void ) const; static CGravityVortexController *Create( const Vector &origin, float radius, float strength, float duration ); diff --git a/dlls/episodic/grenade_hopwire.h b/dlls/episodic/grenade_hopwire.h index fc063618..3b5cee4b 100644 --- a/dlls/episodic/grenade_hopwire.h +++ b/dlls/episodic/grenade_hopwire.h @@ -11,7 +11,7 @@ #endif #include "basegrenade_shared.h" -#include "sprite.h" +#include "Sprite.h" extern ConVar hopwire_trap; diff --git a/dlls/episodic/prop_coreball.cpp b/dlls/episodic/prop_coreball.cpp index 7c4ed909..d45cc80a 100644 --- a/dlls/episodic/prop_coreball.cpp +++ b/dlls/episodic/prop_coreball.cpp @@ -1,133 +1,133 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Big pulsating ball inside the core of the citadel -// -//=============================================================================// - -#include "cbase.h" -#include "baseentity.h" - -#define COREBALL_MODEL "models/props_combine/coreball.mdl" - -class CPropCoreBall : public CBaseAnimating -{ -public: - DECLARE_CLASS( CPropCoreBall, CBaseAnimating ); - DECLARE_DATADESC(); - DECLARE_SERVERCLASS(); - - CPropCoreBall(); - - virtual void Spawn( void ); - virtual void Precache( void ); - - CNetworkVar( float, m_flScaleX ); - CNetworkVar( float, m_flScaleY ); - CNetworkVar( float, m_flScaleZ ); - - CNetworkVar( float, m_flLerpTimeX ); - CNetworkVar( float, m_flLerpTimeY ); - CNetworkVar( float, m_flLerpTimeZ ); - - CNetworkVar( float, m_flGoalTimeX ); - CNetworkVar( float, m_flGoalTimeY ); - CNetworkVar( float, m_flGoalTimeZ ); - - void InputSetScaleX( inputdata_t &inputdata ); - void InputSetScaleY( inputdata_t &inputdata ); - void InputSetScaleZ( inputdata_t &inputdata ); -}; - -LINK_ENTITY_TO_CLASS( prop_coreball, CPropCoreBall ); - -BEGIN_DATADESC( CPropCoreBall ) - DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleX", InputSetScaleX ), - DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleY", InputSetScaleY ), - DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleZ", InputSetScaleZ ), - - DEFINE_FIELD( m_flScaleX, FIELD_FLOAT ), - DEFINE_FIELD( m_flScaleY, FIELD_FLOAT ), - DEFINE_FIELD( m_flScaleZ, FIELD_FLOAT ), - - DEFINE_FIELD( m_flLerpTimeX, FIELD_FLOAT ), - DEFINE_FIELD( m_flLerpTimeY, FIELD_FLOAT ), - DEFINE_FIELD( m_flLerpTimeZ, FIELD_FLOAT ), - - DEFINE_FIELD( m_flGoalTimeX, FIELD_FLOAT ), - DEFINE_FIELD( m_flGoalTimeY, FIELD_FLOAT ), - DEFINE_FIELD( m_flGoalTimeZ, FIELD_FLOAT ), -END_DATADESC() - -IMPLEMENT_SERVERCLASS_ST( CPropCoreBall, DT_PropCoreBall ) - SendPropFloat( SENDINFO(m_flScaleX), 0, SPROP_NOSCALE ), - SendPropFloat( SENDINFO(m_flScaleY), 0, SPROP_NOSCALE ), - SendPropFloat( SENDINFO(m_flScaleZ), 0, SPROP_NOSCALE ), - - SendPropFloat( SENDINFO(m_flLerpTimeX), 0, SPROP_NOSCALE ), - SendPropFloat( SENDINFO(m_flLerpTimeY), 0, SPROP_NOSCALE ), - SendPropFloat( SENDINFO(m_flLerpTimeZ), 0, SPROP_NOSCALE ), - - SendPropFloat( SENDINFO(m_flGoalTimeX), 0, SPROP_NOSCALE ), - SendPropFloat( SENDINFO(m_flGoalTimeY), 0, SPROP_NOSCALE ), - SendPropFloat( SENDINFO(m_flGoalTimeZ), 0, SPROP_NOSCALE ), -END_SEND_TABLE() - -CPropCoreBall::CPropCoreBall( void ) -{ - m_flScaleX = 1.0f; - m_flScaleY = 1.0f; - m_flScaleZ = 1.0f; - - UseClientSideAnimation(); -} - -void CPropCoreBall::Spawn( void ) -{ - Precache(); - SetModel( COREBALL_MODEL ); - - SetMoveType( MOVETYPE_NONE ); - - BaseClass::Spawn(); - - AddEffects( EF_NOSHADOW ); - - SetSequence( 0 ); - SetPlaybackRate( 1.0f ); -} - -void CPropCoreBall::Precache( void ) -{ - BaseClass::Precache(); - PrecacheModel( COREBALL_MODEL ); -} - -void CPropCoreBall::InputSetScaleX( inputdata_t &inputdata ) -{ - Vector vecScale; - inputdata.value.Vector3D( vecScale ); - - m_flScaleX = vecScale.x; - m_flLerpTimeX = vecScale.y; - m_flGoalTimeX = gpGlobals->curtime; -} - -void CPropCoreBall::InputSetScaleY( inputdata_t &inputdata ) -{ - Vector vecScale; - inputdata.value.Vector3D( vecScale ); - - m_flScaleY = vecScale.x; - m_flLerpTimeY = vecScale.y; - m_flGoalTimeY = gpGlobals->curtime; -} - -void CPropCoreBall::InputSetScaleZ( inputdata_t &inputdata ) -{ - Vector vecScale; - inputdata.value.Vector3D( vecScale ); - - m_flScaleZ = vecScale.x; - m_flLerpTimeZ = vecScale.y; - m_flGoalTimeZ = gpGlobals->curtime; -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Big pulsating ball inside the core of the citadel +// +//=============================================================================// + +#include "cbase.h" +#include "baseentity.h" + +#define COREBALL_MODEL "models/props_combine/coreball.mdl" + +class CPropCoreBall : public CBaseAnimating +{ +public: + DECLARE_CLASS( CPropCoreBall, CBaseAnimating ); + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CPropCoreBall(); + + virtual void Spawn( void ); + virtual void Precache( void ); + + CNetworkVar( float, m_flScaleX ); + CNetworkVar( float, m_flScaleY ); + CNetworkVar( float, m_flScaleZ ); + + CNetworkVar( float, m_flLerpTimeX ); + CNetworkVar( float, m_flLerpTimeY ); + CNetworkVar( float, m_flLerpTimeZ ); + + CNetworkVar( float, m_flGoalTimeX ); + CNetworkVar( float, m_flGoalTimeY ); + CNetworkVar( float, m_flGoalTimeZ ); + + void InputSetScaleX( inputdata_t &inputdata ); + void InputSetScaleY( inputdata_t &inputdata ); + void InputSetScaleZ( inputdata_t &inputdata ); +}; + +LINK_ENTITY_TO_CLASS( prop_coreball, CPropCoreBall ); + +BEGIN_DATADESC( CPropCoreBall ) + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleX", InputSetScaleX ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleY", InputSetScaleY ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleZ", InputSetScaleZ ), + + DEFINE_FIELD( m_flScaleX, FIELD_FLOAT ), + DEFINE_FIELD( m_flScaleY, FIELD_FLOAT ), + DEFINE_FIELD( m_flScaleZ, FIELD_FLOAT ), + + DEFINE_FIELD( m_flLerpTimeX, FIELD_FLOAT ), + DEFINE_FIELD( m_flLerpTimeY, FIELD_FLOAT ), + DEFINE_FIELD( m_flLerpTimeZ, FIELD_FLOAT ), + + DEFINE_FIELD( m_flGoalTimeX, FIELD_FLOAT ), + DEFINE_FIELD( m_flGoalTimeY, FIELD_FLOAT ), + DEFINE_FIELD( m_flGoalTimeZ, FIELD_FLOAT ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CPropCoreBall, DT_PropCoreBall ) + SendPropFloat( SENDINFO(m_flScaleX), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flScaleY), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flScaleZ), 0, SPROP_NOSCALE ), + + SendPropFloat( SENDINFO(m_flLerpTimeX), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flLerpTimeY), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flLerpTimeZ), 0, SPROP_NOSCALE ), + + SendPropFloat( SENDINFO(m_flGoalTimeX), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flGoalTimeY), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flGoalTimeZ), 0, SPROP_NOSCALE ), +END_SEND_TABLE() + +CPropCoreBall::CPropCoreBall( void ) +{ + m_flScaleX = 1.0f; + m_flScaleY = 1.0f; + m_flScaleZ = 1.0f; + + UseClientSideAnimation(); +} + +void CPropCoreBall::Spawn( void ) +{ + Precache(); + SetModel( COREBALL_MODEL ); + + SetMoveType( MOVETYPE_NONE ); + + BaseClass::Spawn(); + + AddEffects( EF_NOSHADOW ); + + SetSequence( 0 ); + SetPlaybackRate( 1.0f ); +} + +void CPropCoreBall::Precache( void ) +{ + BaseClass::Precache(); + PrecacheModel( COREBALL_MODEL ); +} + +void CPropCoreBall::InputSetScaleX( inputdata_t &inputdata ) +{ + Vector vecScale; + inputdata.value.Vector3D( vecScale ); + + m_flScaleX = vecScale.x; + m_flLerpTimeX = vecScale.y; + m_flGoalTimeX = gpGlobals->curtime; +} + +void CPropCoreBall::InputSetScaleY( inputdata_t &inputdata ) +{ + Vector vecScale; + inputdata.value.Vector3D( vecScale ); + + m_flScaleY = vecScale.x; + m_flLerpTimeY = vecScale.y; + m_flGoalTimeY = gpGlobals->curtime; +} + +void CPropCoreBall::InputSetScaleZ( inputdata_t &inputdata ) +{ + Vector vecScale; + inputdata.value.Vector3D( vecScale ); + + m_flScaleZ = vecScale.x; + m_flLerpTimeZ = vecScale.y; + m_flGoalTimeZ = gpGlobals->curtime; +} diff --git a/dlls/event_tempentity_tester.cpp b/dlls/event_tempentity_tester.cpp index ab8a9e09..ba0350d3 100644 --- a/dlls/event_tempentity_tester.cpp +++ b/dlls/event_tempentity_tester.cpp @@ -1,125 +1,125 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "event_tempentity_tester.h" -#include "vstdlib/strtools.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define TEMPENT_TEST_GAP 1.0f - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &vecOrigin - -// &vecAngles - -// *single_te - -// Output : CBaseEntity -//----------------------------------------------------------------------------- -CBaseEntity *CTempEntTester::Create( const Vector &vecOrigin, const QAngle &vecAngles, const char *lifetime, const char *single_te ) -{ - float life; - char classname[ 128 ]; - if ( lifetime && lifetime[0] ) - { - life = atoi( lifetime ); - life = max( 1.0, life ); - life = min( 1000.0, life ); - - life += gpGlobals->curtime; - } - else - { - Msg( "Usage: te \n" ); - return NULL; - } - - if ( single_te && single_te[0] ) - { - Q_strncpy( classname, single_te ,sizeof(classname)); - strlwr( classname ); - } - else - { - Msg( "Usage: te \n" ); - return NULL; - } - - CTempEntTester *p = ( CTempEntTester * )CBaseEntity::CreateNoSpawn( "te_tester", vecOrigin, vecAngles ); - if ( !p ) - { - return NULL; - } - - Q_strncpy( p->m_szClass, classname ,sizeof(p->m_szClass)); - p->m_fLifeTime = life; - - p->Spawn(); - - return p; -} - -LINK_ENTITY_TO_CLASS( te_tester, CTempEntTester ); - -//----------------------------------------------------------------------------- -// Purpose: Called when object is being created -//----------------------------------------------------------------------------- -void CTempEntTester::Spawn( void ) -{ - // Not a physical thing... - AddEffects( EF_NODRAW ); - - m_pCurrent = CBaseTempEntity::GetList(); - while ( m_pCurrent ) - { - char name[ 128 ]; - Q_strncpy( name, m_pCurrent->GetName() ,sizeof(name)); - strlwr( name ); - if ( strstr( name, m_szClass ) ) - { - break; - } - - m_pCurrent = m_pCurrent->GetNext(); - } - - if ( !m_pCurrent ) - { - DevMsg("Couldn't find temp entity '%s'\n", m_szClass ); - UTIL_Remove( this ); - return; - } - - // Think right away - SetNextThink( gpGlobals->curtime ); -} - -//----------------------------------------------------------------------------- -// Purpose: Called when object should fire itself and move on -//----------------------------------------------------------------------------- -void CTempEntTester::Think( void ) -{ - // Should never happen - if ( !m_pCurrent ) - { - UTIL_Remove( this ); - return; - } - - m_pCurrent->Test( GetLocalOrigin(), GetLocalAngles() ); - SetNextThink( gpGlobals->curtime + TEMPENT_TEST_GAP ); - - // Time to destroy? - if ( gpGlobals->curtime >= m_fLifeTime ) - { - UTIL_Remove( this ); - return; - } -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "event_tempentity_tester.h" +#include "vstdlib/strtools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define TEMPENT_TEST_GAP 1.0f + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecOrigin - +// &vecAngles - +// *single_te - +// Output : CBaseEntity +//----------------------------------------------------------------------------- +CBaseEntity *CTempEntTester::Create( const Vector &vecOrigin, const QAngle &vecAngles, const char *lifetime, const char *single_te ) +{ + float life; + char classname[ 128 ]; + if ( lifetime && lifetime[0] ) + { + life = atoi( lifetime ); + life = max( 1.0, life ); + life = min( 1000.0, life ); + + life += gpGlobals->curtime; + } + else + { + Msg( "Usage: te \n" ); + return NULL; + } + + if ( single_te && single_te[0] ) + { + Q_strncpy( classname, single_te ,sizeof(classname)); + Q_strlower( classname ); + } + else + { + Msg( "Usage: te \n" ); + return NULL; + } + + CTempEntTester *p = ( CTempEntTester * )CBaseEntity::CreateNoSpawn( "te_tester", vecOrigin, vecAngles ); + if ( !p ) + { + return NULL; + } + + Q_strncpy( p->m_szClass, classname ,sizeof(p->m_szClass)); + p->m_fLifeTime = life; + + p->Spawn(); + + return p; +} + +LINK_ENTITY_TO_CLASS( te_tester, CTempEntTester ); + +//----------------------------------------------------------------------------- +// Purpose: Called when object is being created +//----------------------------------------------------------------------------- +void CTempEntTester::Spawn( void ) +{ + // Not a physical thing... + AddEffects( EF_NODRAW ); + + m_pCurrent = CBaseTempEntity::GetList(); + while ( m_pCurrent ) + { + char name[ 128 ]; + Q_strncpy( name, m_pCurrent->GetName() ,sizeof(name)); + Q_strlower( name ); + if ( strstr( name, m_szClass ) ) + { + break; + } + + m_pCurrent = m_pCurrent->GetNext(); + } + + if ( !m_pCurrent ) + { + DevMsg("Couldn't find temp entity '%s'\n", m_szClass ); + UTIL_Remove( this ); + return; + } + + // Think right away + SetNextThink( gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when object should fire itself and move on +//----------------------------------------------------------------------------- +void CTempEntTester::Think( void ) +{ + // Should never happen + if ( !m_pCurrent ) + { + UTIL_Remove( this ); + return; + } + + m_pCurrent->Test( GetLocalOrigin(), GetLocalAngles() ); + SetNextThink( gpGlobals->curtime + TEMPENT_TEST_GAP ); + + // Time to destroy? + if ( gpGlobals->curtime >= m_fLifeTime ) + { + UTIL_Remove( this ); + return; + } +} diff --git a/dlls/event_tempentity_tester.h b/dlls/event_tempentity_tester.h index 6a810d65..67af11cc 100644 --- a/dlls/event_tempentity_tester.h +++ b/dlls/event_tempentity_tester.h @@ -1,40 +1,40 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#if !defined( EVENT_TEMPENTITY_TESTER_H ) -#define EVENT_TEMPENTITY_TESTER_H -#ifdef _WIN32 -#pragma once -#endif - -class CBaseTempEntity; - - -//----------------------------------------------------------------------------- -// Purpose: A server object that is used to test all registered temp entities -//----------------------------------------------------------------------------- -class CTempEntTester : public CPointEntity -{ -public: - DECLARE_CLASS( CTempEntTester, CPointEntity ); - - void Spawn( void ); - void Think( void ); - - static CBaseEntity *Create( const Vector &vecOrigin, const QAngle &vecAngles, const char *lifetime, const char *single_te ); -private: - // Current temp entity to test - CBaseTempEntity *m_pCurrent; - - // Lifetime - float m_fLifeTime; - - char m_szClass[ 64 ]; -}; - -#endif // EVENT_TEMPENTITY_TESTER_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( EVENT_TEMPENTITY_TESTER_H ) +#define EVENT_TEMPENTITY_TESTER_H +#ifdef _WIN32 +#pragma once +#endif + +class CBaseTempEntity; + + +//----------------------------------------------------------------------------- +// Purpose: A server object that is used to test all registered temp entities +//----------------------------------------------------------------------------- +class CTempEntTester : public CPointEntity +{ +public: + DECLARE_CLASS( CTempEntTester, CPointEntity ); + + void Spawn( void ); + void Think( void ); + + static CBaseEntity *Create( const Vector &vecOrigin, const QAngle &vecAngles, const char *lifetime, const char *single_te ); +private: + // Current temp entity to test + CBaseTempEntity *m_pCurrent; + + // Lifetime + float m_fLifeTime; + + char m_szClass[ 64 ]; +}; + +#endif // EVENT_TEMPENTITY_TESTER_H diff --git a/dlls/explode.cpp b/dlls/explode.cpp index 3511592c..5fa7c5bd 100644 --- a/dlls/explode.cpp +++ b/dlls/explode.cpp @@ -293,7 +293,7 @@ void CEnvExplosion::InputExplode( inputdata_t &inputdata ) } //Get the damage override if specified - int iRadius = ( m_iRadiusOverride > 0 ) ? m_iRadiusOverride : ( m_iMagnitude * 2.5f ); + int iRadius = ( m_iRadiusOverride > 0 ) ? m_iRadiusOverride : ( static_cast(m_iMagnitude * 2.5f) ); CPASFilter filter( vecExplodeOrigin ); te->Explosion( filter, 0.0, diff --git a/dlls/fire.cpp b/dlls/fire.cpp index 29f54f9a..fa711485 100644 --- a/dlls/fire.cpp +++ b/dlls/fire.cpp @@ -940,7 +940,7 @@ void CFire::Update( float simTime ) if ( m_flDamageTime <= gpGlobals->curtime ) { m_flDamageTime = gpGlobals->curtime + fire_dmginterval.GetFloat(); - outputDamage = (fire_dmgbase.GetFloat() + outputHeat * fire_dmgscale.GetFloat() * m_flDamageScale) * fire_dmginterval.GetFloat(); + outputDamage = static_cast((fire_dmgbase.GetFloat() + outputHeat * fire_dmgscale.GetFloat() * m_flDamageScale) * fire_dmginterval.GetFloat()); if ( outputDamage ) { damage = true; @@ -957,7 +957,7 @@ void CFire::Update( float simTime ) } else if ( FClassnameIs( pOther, "env_fire" ) ) { - if ( fireCount < ARRAYSIZE(pFires) ) + if ( fireCount < static_cast(ARRAYSIZE(pFires)) ) { pFires[fireCount] = (CFire *)pOther; fireCount++; diff --git a/dlls/fire_smoke.cpp b/dlls/fire_smoke.cpp index 6da8dc1f..204fc71b 100644 --- a/dlls/fire_smoke.cpp +++ b/dlls/fire_smoke.cpp @@ -1,195 +1,195 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "cbase.h" -#include "fire_smoke.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//--------------------------------------------------------- -// Save/Restore -//--------------------------------------------------------- -BEGIN_DATADESC( CBaseFire ) - - DEFINE_FIELD( m_flStartScale, FIELD_FLOAT ), - DEFINE_FIELD( m_flScale, FIELD_FLOAT ), - DEFINE_FIELD( m_flScaleTime, FIELD_TIME ), - DEFINE_FIELD( m_nFlags, FIELD_INTEGER ), - -END_DATADESC() - - -//================================================== -// CBaseFire -//================================================== - -CBaseFire::CBaseFire( void ) -{ - m_flStartScale = 0.0f; - m_flScale = 0.0f; - m_flScaleTime = 0.0f; - m_nFlags = bitsFIRE_NONE; -} - -CBaseFire::~CBaseFire( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: Take the current scale of the flame and move it towards a destination -// Input : size - destination size -// time - time to scale across -//----------------------------------------------------------------------------- -void CBaseFire::Scale( float size, float time ) -{ - //Send to the client - m_flScale = size; - m_flScaleTime = time; -} - -//----------------------------------------------------------------------------- -// Purpose: Overloaded Scale() function to set size -// Input : start - beginning sizek -// size - destination size -// time - time to scale across -//----------------------------------------------------------------------------- -void CBaseFire::Scale( float start, float size, float time ) -{ - //Send to the client - m_flStartScale = start; - m_flScale = size; - m_flScaleTime = time; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : state - -//----------------------------------------------------------------------------- -void CBaseFire::Enable( int state ) -{ - if ( state ) - { - m_nFlags |= bitsFIRE_ACTIVE; - } - else - { - m_nFlags &= ~bitsFIRE_ACTIVE; - } -} - -//================================================== -// CFireSmoke -//================================================== - -//Link the entity -LINK_ENTITY_TO_CLASS( _firesmoke, CFireSmoke ); - -//Send datatable -IMPLEMENT_SERVERCLASS_ST( CFireSmoke, DT_FireSmoke ) - SendPropFloat( SENDINFO( m_flStartScale ), 0, SPROP_NOSCALE), - SendPropFloat( SENDINFO( m_flScale ), 0, SPROP_NOSCALE), - SendPropFloat( SENDINFO( m_flScaleTime ), 0, SPROP_NOSCALE), - SendPropInt( SENDINFO( m_nFlags ), 8, SPROP_UNSIGNED ), - SendPropModelIndex( SENDINFO( m_nFlameModelIndex ) ), - SendPropModelIndex( SENDINFO( m_nFlameFromAboveModelIndex ) ), -END_SEND_TABLE() - -//Data description -BEGIN_DATADESC( CFireSmoke ) - - DEFINE_FIELD( m_flStartScale, FIELD_FLOAT ), - DEFINE_FIELD( m_flScale, FIELD_FLOAT ), - DEFINE_FIELD( m_flScaleTime, FIELD_FLOAT ), - DEFINE_FIELD( m_nFlags, FIELD_INTEGER ), - DEFINE_FIELD( m_nFlameFromAboveModelIndex, FIELD_INTEGER ), - -END_DATADESC() - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CFireSmoke::CFireSmoke( void ) -{ - //Client-side - m_flScale = 0.0f; - m_flScaleTime = 0.0f; - m_nFlags = bitsFIRE_NONE; - - //Server-side - AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CFireSmoke::~CFireSmoke( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CFireSmoke::Precache() -{ - BaseClass::Precache(); - m_nFlameModelIndex = PrecacheModel( "sprites/fire1.vmt" ); - - // This asset doesn't appear to exist anymore. What's going on? - // Commenting this out so that level designers don't get a red error about missing material. (sjb) - //m_nFlameFromAboveModelIndex = PrecacheModel( "sprites/flamefromabove.vmt" ); -} - -void CFireSmoke::Spawn() -{ - Precache(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : state - -//----------------------------------------------------------------------------- -void CFireSmoke::EnableSmoke( int state ) -{ - if ( state ) - { - m_nFlags |= bitsFIRESMOKE_SMOKE; - } - else - { - m_nFlags &= ~bitsFIRESMOKE_SMOKE; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : state - -//----------------------------------------------------------------------------- -void CFireSmoke::EnableGlow( int state ) -{ - if ( state ) - { - m_nFlags |= bitsFIRESMOKE_GLOW; - } - else - { - m_nFlags &= ~bitsFIRESMOKE_GLOW; - } -} - -void CFireSmoke::EnableVisibleFromAbove( int state ) -{ - if ( state ) - { - m_nFlags |= bitsFIRESMOKE_VISIBLE_FROM_ABOVE; - } - else - { - m_nFlags &= ~bitsFIRESMOKE_VISIBLE_FROM_ABOVE; - } -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "fire_smoke.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CBaseFire ) + + DEFINE_FIELD( m_flStartScale, FIELD_FLOAT ), + DEFINE_FIELD( m_flScale, FIELD_FLOAT ), + DEFINE_FIELD( m_flScaleTime, FIELD_TIME ), + DEFINE_FIELD( m_nFlags, FIELD_INTEGER ), + +END_DATADESC() + + +//================================================== +// CBaseFire +//================================================== + +CBaseFire::CBaseFire( void ) +{ + m_flStartScale = 0.0f; + m_flScale = 0.0f; + m_flScaleTime = 0.0f; + m_nFlags = bitsFIRE_NONE; +} + +CBaseFire::~CBaseFire( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Take the current scale of the flame and move it towards a destination +// Input : size - destination size +// time - time to scale across +//----------------------------------------------------------------------------- +void CBaseFire::Scale( float size, float time ) +{ + //Send to the client + m_flScale = size; + m_flScaleTime = time; +} + +//----------------------------------------------------------------------------- +// Purpose: Overloaded Scale() function to set size +// Input : start - beginning sizek +// size - destination size +// time - time to scale across +//----------------------------------------------------------------------------- +void CBaseFire::Scale( float start, float size, float time ) +{ + //Send to the client + m_flStartScale = start; + m_flScale = size; + m_flScaleTime = time; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void CBaseFire::Enable( int state ) +{ + if ( state ) + { + m_nFlags |= bitsFIRE_ACTIVE; + } + else + { + m_nFlags &= ~bitsFIRE_ACTIVE; + } +} + +//================================================== +// CFireSmoke +//================================================== + +//Link the entity +LINK_ENTITY_TO_CLASS( _firesmoke, CFireSmoke ); + +//Send datatable +IMPLEMENT_SERVERCLASS_ST( CFireSmoke, DT_FireSmoke ) + SendPropFloat( SENDINFO( m_flStartScale ), 0, SPROP_NOSCALE), + SendPropFloat( SENDINFO( m_flScale ), 0, SPROP_NOSCALE), + SendPropFloat( SENDINFO( m_flScaleTime ), 0, SPROP_NOSCALE), + SendPropInt( SENDINFO( m_nFlags ), 8, SPROP_UNSIGNED ), + SendPropModelIndex( SENDINFO( m_nFlameModelIndex ) ), + SendPropModelIndex( SENDINFO( m_nFlameFromAboveModelIndex ) ), +END_SEND_TABLE() + +//Data description +BEGIN_DATADESC( CFireSmoke ) + + DEFINE_FIELD( m_flStartScale, FIELD_FLOAT ), + DEFINE_FIELD( m_flScale, FIELD_FLOAT ), + DEFINE_FIELD( m_flScaleTime, FIELD_FLOAT ), + DEFINE_FIELD( m_nFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_nFlameFromAboveModelIndex, FIELD_INTEGER ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CFireSmoke::CFireSmoke( void ) +{ + //Client-side + m_flScale = 0.0f; + m_flScaleTime = 0.0f; + m_nFlags = bitsFIRE_NONE; + + //Server-side + AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFireSmoke::~CFireSmoke( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFireSmoke::Precache() +{ + BaseClass::Precache(); + m_nFlameModelIndex = PrecacheModel( "sprites/fire1.vmt" ); + + // This asset doesn't appear to exist anymore. What's going on? + // Commenting this out so that level designers don't get a red error about missing material. (sjb) + //m_nFlameFromAboveModelIndex = PrecacheModel( "sprites/flamefromabove.vmt" ); +} + +void CFireSmoke::Spawn() +{ + Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void CFireSmoke::EnableSmoke( int state ) +{ + if ( state ) + { + m_nFlags |= bitsFIRESMOKE_SMOKE; + } + else + { + m_nFlags &= ~bitsFIRESMOKE_SMOKE; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void CFireSmoke::EnableGlow( int state ) +{ + if ( state ) + { + m_nFlags |= bitsFIRESMOKE_GLOW; + } + else + { + m_nFlags &= ~bitsFIRESMOKE_GLOW; + } +} + +void CFireSmoke::EnableVisibleFromAbove( int state ) +{ + if ( state ) + { + m_nFlags |= bitsFIRESMOKE_VISIBLE_FROM_ABOVE; + } + else + { + m_nFlags &= ~bitsFIRESMOKE_VISIBLE_FROM_ABOVE; + } +} diff --git a/dlls/fogcontroller.cpp b/dlls/fogcontroller.cpp index 1cf2c26b..ebc2b0da 100644 --- a/dlls/fogcontroller.cpp +++ b/dlls/fogcontroller.cpp @@ -48,7 +48,7 @@ public: void InputStartFogTransition(inputdata_t &data); - int CFogController::DrawDebugTextOverlays(void); + int DrawDebugTextOverlays(void); void SetLerpValues( void ); void Spawn( void ); diff --git a/dlls/fourwheelvehiclephysics.cpp b/dlls/fourwheelvehiclephysics.cpp index 5f107bdf..4ae8cfbe 100644 --- a/dlls/fourwheelvehiclephysics.cpp +++ b/dlls/fourwheelvehiclephysics.cpp @@ -741,7 +741,7 @@ bool CFourWheelVehiclePhysics::Think() m_nLastSpeed = m_nSpeed; m_nSpeed = ( int )carSpeed; m_nRPM = ( int )carState.engineRPM; - m_nHasBoost = vehicleData.engine.boostDelay; // if we have any boost delay, vehicle has boost ability + m_nHasBoost = static_cast(vehicleData.engine.boostDelay); // if we have any boost delay, vehicle has boost ability m_pVehicle->Update( gpGlobals->frametime, m_controls); diff --git a/dlls/fourwheelvehiclephysics.h b/dlls/fourwheelvehiclephysics.h index 250f59f9..0a9db41b 100644 --- a/dlls/fourwheelvehiclephysics.h +++ b/dlls/fourwheelvehiclephysics.h @@ -183,7 +183,7 @@ inline int CFourWheelVehiclePhysics::GetSpeed() const inline int CFourWheelVehiclePhysics::GetMaxSpeed() const { - return INS2MPH(m_pVehicle->GetVehicleParams().engine.maxSpeed); + return static_cast(INS2MPH(m_pVehicle->GetVehicleParams().engine.maxSpeed)); } inline int CFourWheelVehiclePhysics::GetRPM() const diff --git a/dlls/func_break.cpp b/dlls/func_break.cpp index 7e58f841..057965be 100644 --- a/dlls/func_break.cpp +++ b/dlls/func_break.cpp @@ -55,11 +55,11 @@ extern Vector g_vecAttackDir; "item_rpg_round", // 12 "unused (item_smg1_grenade) 13",// 13 "item_box_sniper_rounds", // 14 - "unused (???) 15", // 15 + "unused (?) 15", // 15 "weapon_stunstick", // 16 "unused (weapon_ar1) 17", // 17 "weapon_ar2", // 18 - "unused (???) 19", // 19 + "unused (?) 19", // 19 "weapon_rpg", // 20 "weapon_smg1", // 21 "unused (weapon_smg2) 22", // 22 @@ -209,13 +209,13 @@ bool CBreakable::KeyValue( const char *szKeyName, const char *szValue ) else if (FStrEq(szKeyName, "spawnobject") ) { int object = atoi( szValue ); - if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) ) + if ( object > 0 && object < static_cast(ARRAYSIZE(pSpawnObjects)) ) m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); } else if (FStrEq(szKeyName, "propdata") ) { int pdata = atoi( szValue ); - if ( pdata > 0 && pdata < ARRAYSIZE(pFGDPropData) ) + if ( pdata > 0 && pdata < static_cast(ARRAYSIZE(pFGDPropData)) ) { m_iszPropData = MAKE_STRING( pFGDPropData[pdata] ); } @@ -452,7 +452,7 @@ void CBreakable::Precache( void ) else { // Actually, precache all possible objects... - for ( int i = 0; i < ARRAYSIZE(pSpawnObjects) ; ++i ) + for ( size_t i = 0; i < ARRAYSIZE(pSpawnObjects) ; ++i ) { if ( !pSpawnObjects[ i ] ) continue; @@ -716,7 +716,10 @@ void CBreakable::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, case matUnbreakableGlass: g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) ); - break; + break; + + default: + break; } } @@ -978,7 +981,7 @@ void CBreakable::Die( void ) CCollisionProperty *pCollisionProp = CollisionProp(); Vector vSize = pCollisionProp->OBBSize(); - int iCount = ( vSize[0] * vSize[1] + vSize[1] * vSize[2] + vSize[2] * vSize[0] ) / ( 3 * 12 * 12 ); + int iCount = static_cast(( vSize[0] * vSize[1] + vSize[1] * vSize[2] + vSize[2] * vSize[0] ) / ( 3 * 12 * 12 )); if ( iCount > func_break_max_pieces.GetInt() ) { @@ -1034,7 +1037,7 @@ void CBreakable::Die( void ) if ( Explodable() ) { - ExplosionCreate( vecSpot, pCollisionProp->GetCollisionAngles(), this, GetExplosiveDamage(), GetExplosiveRadius(), true ); + ExplosionCreate( vecSpot, pCollisionProp->GetCollisionAngles(), this, static_cast(GetExplosiveDamage()), static_cast(GetExplosiveRadius()), true ); } } diff --git a/dlls/func_break.h b/dlls/func_break.h index 85cb5eda..87868be2 100644 --- a/dlls/func_break.h +++ b/dlls/func_break.h @@ -91,7 +91,7 @@ public: float GetDmgModClub( void ) { return m_flDmgModClub; } float GetDmgModExplosive( void ) { return m_flDmgModExplosive; } void SetExplosiveRadius( float flRadius ) { m_explodeRadius = flRadius; } - void SetExplosiveDamage( float flDamage ) { m_ExplosionMagnitude = flDamage; } + void SetExplosiveDamage( float flDamage ) { m_ExplosionMagnitude = static_cast(flDamage); } float GetExplosiveRadius( void ) { return m_explodeRadius; } float GetExplosiveDamage( void ) { return m_ExplosionMagnitude; } void SetPhysicsDamageTable( string_t iszTableName ) { m_iszPhysicsDamageTableName = iszTableName; } diff --git a/dlls/func_breakablesurf.cpp b/dlls/func_breakablesurf.cpp index e5234846..2b0fc5d4 100644 --- a/dlls/func_breakablesurf.cpp +++ b/dlls/func_breakablesurf.cpp @@ -367,8 +367,8 @@ void CBreakableSurface::TraceAttack( const CTakeDamageInfo &info, const Vector & // Figure out which panel has taken the damage and break it float flWidth,flHeight; PanePos(ptr->endpos,&flWidth,&flHeight); - int nWidth = flWidth; - int nHeight = flHeight; + int nWidth = static_cast(flWidth); + int nHeight = static_cast(flHeight); if ( ShatterPane(nWidth, nHeight,vecDir*500,ptr->endpos) ) { @@ -438,8 +438,8 @@ void CBreakableSurface::TraceAttack( const CTakeDamageInfo &info, const Vector & { PanePos(ptr->endpos,&flWidth,&flHeight); } - int nWidth = flWidth; - int nHeight = flHeight; + int nWidth = static_cast(flWidth); + int nHeight = static_cast(flHeight); // Blow out a roughly circular patch of tile with some randomness for (int width =nWidth-4;width( GetNextTarget() ); - if ( !other ) - { - DevMsg( 1, "func_ladderendpoint(%s) without matching target\n", GetEntityName() ); - return false; - } - - Vector endPos = other->GetAbsOrigin(); - - CFuncLadder *ladder = ( CFuncLadder * )CreateEntityByName( "func_useableladder" ); - if ( ladder ) - { - ladder->SetEndPoints( startPos, endPos ); - ladder->SetAbsOrigin( GetAbsOrigin() ); - ladder->SetParent( GetParent() ); - ladder->SetName( GetEntityName() ); - ladder->Spawn(); - } - - // Delete both endpoints - UTIL_Remove( other ); - UTIL_Remove( this ); - - return true; -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "func_ladder.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: A transient entity used to construct a true CFuncLadder +// FIXME: THIS ENTITY IS OBSOLETE NOW, SHOULD BE REMOVED FROM HERE AND .FGD AT SOME POINT!!! +//----------------------------------------------------------------------------- +class CFuncLadderEndPoint : public CBaseEntity +{ +public: + DECLARE_CLASS( CFuncLadderEndPoint, CBaseEntity ); + + virtual void Activate(); + +private: + bool Validate(); +}; + +LINK_ENTITY_TO_CLASS( func_ladderendpoint, CFuncLadderEndPoint ); + +void CFuncLadderEndPoint::Activate() +{ + BaseClass::Activate(); + + if ( IsMarkedForDeletion() ) + return; + + Validate(); +} + +bool CFuncLadderEndPoint::Validate() +{ + // Find the the other end + Vector startPos = GetAbsOrigin(); + + CFuncLadderEndPoint *other = dynamic_cast< CFuncLadderEndPoint * >( GetNextTarget() ); + if ( !other ) + { + DevMsg( 1, "func_ladderendpoint(%s) without matching target\n", STRING(GetEntityName()) ); + return false; + } + + Vector endPos = other->GetAbsOrigin(); + + CFuncLadder *ladder = ( CFuncLadder * )CreateEntityByName( "func_useableladder" ); + if ( ladder ) + { + ladder->SetEndPoints( startPos, endPos ); + ladder->SetAbsOrigin( GetAbsOrigin() ); + ladder->SetParent( GetParent() ); + ladder->SetName( GetEntityName() ); + ladder->Spawn(); + } + + // Delete both endpoints + UTIL_Remove( other ); + UTIL_Remove( this ); + + return true; +} diff --git a/dlls/game_ui.cpp b/dlls/game_ui.cpp index dbdd6134..21d82a04 100644 --- a/dlls/game_ui.cpp +++ b/dlls/game_ui.cpp @@ -182,7 +182,7 @@ void CGameUI::InputActivate( inputdata_t &inputdata ) CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller ); if ( pEntity == NULL || pEntity->IsPlayer() == false ) { - Warning( "%s InputActivate: entity %s not found or is not a player!\n", GetEntityName(), inputdata.value.String() ); + Warning( "%s InputActivate: entity %s not found or is not a player!\n", STRING(GetEntityName()), inputdata.value.String() ); return; } @@ -193,7 +193,7 @@ void CGameUI::InputActivate( inputdata_t &inputdata ) // Otherwise try to use the activator if ( inputdata.pActivator == NULL || inputdata.pActivator->IsPlayer() == false ) { - Warning( "%s InputActivate: invalid or missing !activator!\n", GetEntityName(), inputdata.value.String() ); + Warning( "%s InputActivate: invalid or missing !activator!\n", STRING(GetEntityName()), inputdata.value.String() ); return; } diff --git a/dlls/gameinterface.cpp b/dlls/gameinterface.cpp index 175c07d9..50b634c5 100644 --- a/dlls/gameinterface.cpp +++ b/dlls/gameinterface.cpp @@ -1,2591 +1,2590 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: encapsulates and implements all the accessing of the game dll from external -// sources (only the engine at the time of writing) -// This files ONLY contains functions and data necessary to build an interface -// to external modules -//=============================================================================// - -#include "cbase.h" -#include "gamestringpool.h" -#include "mapentities_shared.h" -#include "game.h" -#include "entityapi.h" -#include "client.h" -#include "saverestore.h" -#include "entitylist.h" -#include "gamerules.h" -#include "soundent.h" -#include "player.h" -#include "server_class.h" -#include "ai_node.h" -#include "ai_link.h" -#include "ai_saverestore.h" -#include "ai_networkmanager.h" -#include "ndebugoverlay.h" -#include "ivoiceserver.h" -#include -#include "movehelper_server.h" -#include "networkstringtable_gamedll.h" -#include "filesystem.h" -#include "terrainmodmgr.h" -#include "func_areaportalwindow.h" -#include "igamesystem.h" -#include "init_factory.h" -#include "vstdlib/random.h" -#include "env_wind_shared.h" -#include "engine/IEngineSound.h" -#include "ispatialpartition.h" -#include "textstatsmgr.h" -#include "bitbuf.h" -#include "saverestoretypes.h" -#include "physics_saverestore.h" -#include "tier0/vprof.h" -#include "effect_dispatch_data.h" -#include "engine/IStaticPropMgr.h" -#include "TemplateEntities.h" -#include "ai_speech.h" -#include "soundenvelope.h" -#include "usermessages.h" -#include "physics.h" -#include "mapentities.h" -#include "igameevents.h" -#include "EventLog.h" -#include "datacache/idatacache.h" -#include "engine/ivdebugoverlay.h" -#include "shareddefs.h" -#include "props.h" -#include "timedeventmgr.h" -#include "gameinterface.h" -#include "eventqueue.h" -#include "hltvdirector.h" -#include "SoundEmitterSystem/isoundemittersystembase.h" -#include "nav_mesh.h" -#include "AI_ResponseSystem.h" -#include "saverestore_stringtable.h" -#include "util.h" -#include "vstdlib/ICommandLine.h" -#include "datacache/imdlcache.h" -#include "engine/iserverplugin.h" -#ifdef _WIN32 -#include "ienginevgui.h" -#include "vgui_gamedll_int.h" -#include "vgui_controls/AnimationController.h" -#endif -#include "ragdoll_shared.h" -#include "toolframework/iserverenginetools.h" -#include "sceneentity.h" -#include "appframework/IAppSystemGroup.h" -#include "scenefilecache/ISceneFileCache.h" - -#if !defined( _RETAIL ) -#ifdef _XBOX -#include "xbox/xbox_platform.h" -#include "xbox/xbox_win32stubs.h" -#include "xbox/xbox_core.h" -#endif -#endif - -#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out -#include "bot/bot.h" -#endif - -IUploadGameStats *gamestatsuploader = NULL; - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -CTimedEventMgr g_NetworkPropertyEventMgr; - -ISaveRestoreBlockHandler *GetEventQueueSaveRestoreBlockHandler(); -ISaveRestoreBlockHandler *GetCommentarySaveRestoreBlockHandler(); - -// Engine interfaces. -IVEngineServer *engine = NULL; -IVoiceServer *g_pVoiceServer = NULL; -ICvar *cvar = NULL; -#if !defined(_STATIC_LINKED) -IFileSystem *filesystem = NULL; -#else -extern IFileSystem *filesystem; -#endif -INetworkStringTableContainer *networkstringtable = NULL; -IStaticPropMgrServer *staticpropmgr = NULL; -IUniformRandomStream *random = NULL; -IEngineSound *enginesound = NULL; -ISpatialPartition *partition = NULL; -IVModelInfo *modelinfo = NULL; -IEngineTrace *enginetrace = NULL; -IGameEventManager2 *gameeventmanager = NULL; -IDataCache *datacache = NULL; -IVDebugOverlay * debugoverlay = NULL; -ISoundEmitterSystemBase *soundemitterbase = NULL; -IMDLCache *mdlcache = NULL; -IServerPluginHelpers *serverpluginhelpers = NULL; -#ifdef _WIN32 -IEngineVGui *enginevgui = NULL; -#endif -IServerEngineTools *serverenginetools = NULL; -ISceneFileCache *scenefilecache = NULL; - -IGameSystem *SoundEmitterSystem(); - -bool SceneCacheInit(); -void SceneCacheShutdown(); -bool ModelSoundsCacheInit(); -void ModelSoundsCacheShutdown(); - -void SceneManager_ClientActive( CBasePlayer *player ); - -class IMaterialSystem; -class IStudioRender; - -#ifdef _DEBUG -static ConVar s_UseNetworkVars( "UseNetworkVars", "1", FCVAR_CHEAT, "For profiling, toggle network vars." ); -#endif - -extern ConVar sv_noclipduringpause; -ConVar sv_massreport( "sv_massreport", "0" ); - -ConVar sv_autosave( "sv_autosave", "1", 0, "Set to 1 to save game on level transition. Does not affect autosave triggers." ); -ConVar *sv_maxreplay = NULL; - -// String tables -INetworkStringTable *g_pStringTableEffectDispatch = NULL; -INetworkStringTable *g_pStringTableVguiScreen = NULL; -INetworkStringTable *g_pStringTableMaterials = NULL; -INetworkStringTable *g_pStringTableInfoPanel = NULL; -INetworkStringTable *g_pStringTableClientSideChoreoScenes = NULL; - -CStringTableSaveRestoreOps g_VguiScreenStringOps; - -// Holds global variables shared between engine and game. -CGlobalVars *gpGlobals; -edict_t *g_pDebugEdictBase = 0; -static int g_nCommandClientIndex = 0; - -static ConVar sv_showhitboxes( "sv_showhitboxes", "-1", FCVAR_CHEAT, "Send server-side hitboxes for specified entity to client (NOTE: this uses lots of bandwidth, use on listen server only)." ); - -void PrecachePointTemplates(); - -static ClientPutInServerOverrideFn g_pClientPutInServerOverride = NULL; -static void UpdateChapterRestrictions( const char *mapname ); - - -#if !defined( _XBOX ) // Don't doubly define this symbol. -CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL; - -#endif - -IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() -{ - return engine->GetChangeAccessor( (const edict_t *)this ); -} - -const IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() const -{ - return engine->GetChangeAccessor( (const edict_t *)this ); -} - -const char *GetHintTypeDescription( CAI_Hint *pHint ); - -void ClientPutInServerOverride( ClientPutInServerOverrideFn fn ) -{ - g_pClientPutInServerOverride = fn; -} - - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : int -//----------------------------------------------------------------------------- -int UTIL_GetCommandClientIndex( void ) -{ - // -1 == unknown,dedicated server console - // 0 == player 1 - - // Convert to 1 based offset - return (g_nCommandClientIndex+1); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : CBasePlayer -//----------------------------------------------------------------------------- -CBasePlayer *UTIL_GetCommandClient( void ) -{ - int idx = UTIL_GetCommandClientIndex(); - if ( idx > 0 ) - { - return UTIL_PlayerByIndex( idx ); - } - - // HLDS console issued command - return NULL; -} - -extern void InitializeCvars( void ); - -CBaseEntity* FindPickerEntity( CBasePlayer* pPlayer ); -CAI_Node* FindPickerAINode( CBasePlayer* pPlayer, NodeType_e nNodeType ); -CAI_Link* FindPickerAILink( CBasePlayer* pPlayer ); -float GetFloorZ(const Vector &origin); -void UpdateAllClientData( void ); -void DrawMessageEntities(); - -#include "ai_network.h" - -// For now just using one big AI network -extern ConVar think_limit; - - -#if 0 -//----------------------------------------------------------------------------- -// Purpose: Draw output overlays for any measure sections -// Input : -//----------------------------------------------------------------------------- -void DrawMeasuredSections(void) -{ - int row = 1; - float rowheight = 0.025; - - CMeasureSection *p = CMeasureSection::GetList(); - while ( p ) - { - char str[256]; - Q_snprintf(str,sizeof(str),"%s",p->GetName()); - NDebugOverlay::ScreenText( 0.01,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); - - Q_snprintf(str,sizeof(str),"%5.2f\n",p->GetTime().GetMillisecondsF()); - //Q_snprintf(str,sizeof(str),"%3.3f\n",p->GetTime().GetSeconds() * 100.0 / engine->Time()); - NDebugOverlay::ScreenText( 0.28,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); - - Q_snprintf(str,sizeof(str),"%5.2f\n",p->GetMaxTime().GetMillisecondsF()); - //Q_snprintf(str,sizeof(str),"%3.3f\n",p->GetTime().GetSeconds() * 100.0 / engine->Time()); - NDebugOverlay::ScreenText( 0.34,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); - - - row++; - - p = p->GetNext(); - } - - bool sort_reset = false; - - // Time to redo sort? - if ( measure_resort.GetFloat() > 0.0 && - engine->Time() >= CMeasureSection::m_dNextResort ) - { - // Redo it - CMeasureSection::SortSections(); - // Set next time - CMeasureSection::m_dNextResort = engine->Time() + measure_resort.GetFloat(); - // Flag to reset sort accumulator, too - sort_reset = true; - } - - // Iterate through the sections now - p = CMeasureSection::GetList(); - while ( p ) - { - // Update max - p->UpdateMax(); - - // Reset regular accum. - p->Reset(); - // Reset sort accum less often - if ( sort_reset ) - { - p->SortReset(); - } - p = p->GetNext(); - } - -} -#endif - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void DrawAllDebugOverlays( void ) -{ - // If in debug select mode print the selection entities name or classname - if (CBaseEntity::m_bInDebugSelect) - { - CBasePlayer* pPlayer = UTIL_PlayerByIndex( CBaseEntity::m_nDebugPlayer ); - - if (pPlayer) - { - // First try to trace a hull to an entity - CBaseEntity *pEntity = FindPickerEntity( pPlayer ); - - if ( pEntity ) - { - pEntity->DrawDebugTextOverlays(); - pEntity->DrawBBoxOverlay(); - pEntity->SendDebugPivotOverlay(); - } - } - } - - // -------------------------------------------------------- - // Draw debug overlay lines - // -------------------------------------------------------- - UTIL_DrawOverlayLines(); - - // ------------------------------------------------------------------------ - // If in wc_edit mode draw a box to highlight which node I'm looking at - // ------------------------------------------------------------------------ - if (engine->IsInEditMode()) - { - CBasePlayer* pPlayer = UTIL_PlayerByIndex( CBaseEntity::m_nDebugPlayer ); - if (pPlayer) - { - if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode) - { - CAI_Link* pAILink = FindPickerAILink(pPlayer); - if (pAILink) - { - // For now just using one big AI network - Vector startPos = g_pBigAINet->GetNode(pAILink->m_iSrcID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); - Vector endPos = g_pBigAINet->GetNode(pAILink->m_iDestID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); - Vector linkDir = startPos-endPos; - float linkLen = VectorNormalize( linkDir ); - - // Draw in green if link that's been turned off - if (pAILink->m_LinkInfo & bits_LINK_OFF) - { - NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 0,255,0,40,0); - } - else - { - NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 255,0,0,40,0); - } - } - } - else - { - CAI_Node* pAINode; - if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) - { - pAINode = FindPickerAINode(pPlayer,NODE_AIR); - } - else - { - pAINode = FindPickerAINode(pPlayer,NODE_GROUND); - } - - if (pAINode) - { - Vector vecPos = pAINode->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); - NDebugOverlay::Box( vecPos, Vector(-8,-8,-8), Vector(8,8,8), 255,0,0,40,0); - - if ( pAINode->GetHint() ) - { - CBaseEntity *pEnt = (CBaseEntity *)pAINode->GetHint(); - if ( pEnt->GetEntityName() != NULL_STRING ) - { - NDebugOverlay::Text( vecPos + Vector(0,0,6), STRING(pEnt->GetEntityName()), false, 0 ); - } - NDebugOverlay::Text( vecPos, GetHintTypeDescription( pAINode->GetHint() ), false, 0 ); - } - } - } - // ------------------------------------ - // If in air edit mode draw guide line - // ------------------------------------ - if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) - { - UTIL_DrawPositioningOverlay(g_pAINetworkManager->GetEditOps()->m_flAirEditDistance); - } - else - { - NDebugOverlay::DrawGroundCrossHairOverlay(); - } - } - } - - // For not just using one big AI Network - if ( g_pAINetworkManager ) - { - g_pAINetworkManager->GetEditOps()->DrawAINetworkOverlay(); - } - - // PERFORMANCE: only do this in developer mode - if ( g_pDeveloper->GetInt() ) - { - // iterate through all objects for debug overlays - const CEntInfo *pInfo = gEntList.FirstEntInfo(); - - for ( ;pInfo; pInfo = pInfo->m_pNext ) - { - CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; - // HACKHACK: to flag off these calls - if ( ent->m_debugOverlays || ent->m_pTimedOverlay ) - { - MDLCACHE_CRITICAL_SECTION(); - ent->DrawDebugGeometryOverlays(); - } - } - } - - if ( sv_massreport.GetInt() ) - { - // iterate through all objects for debug overlays - const CEntInfo *pInfo = gEntList.FirstEntInfo(); - - for ( ;pInfo; pInfo = pInfo->m_pNext ) - { - CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; - if (!ent->VPhysicsGetObject()) - continue; - - char tempstr[512]; - Q_snprintf(tempstr, sizeof(tempstr),"%s: Mass: %.2f kg / %.2f lb (%s)", - ent->GetModelName(), ent->VPhysicsGetObject()->GetMass(), - kg2lbs(ent->VPhysicsGetObject()->GetMass()), - GetMassEquivalent(ent->VPhysicsGetObject()->GetMass())); - ent->EntityText(0, tempstr, 0); - } - } - - // A hack to draw point_message entities w/o developer required - DrawMessageEntities(); -} - -CServerGameDLL g_ServerGameDLL; -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameDLL, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL, g_ServerGameDLL); - -bool CServerGameDLL::DLLInit(CreateInterfaceFn engineFactory, - CreateInterfaceFn physicsFactory, CreateInterfaceFn fileSystemFactory, - CGlobalVars *pGlobals) -{ - // init each (seperated for ease of debugging) - if ( (engine = (IVEngineServer*)engineFactory(INTERFACEVERSION_VENGINESERVER, NULL)) == NULL ) - return false; - if ( (g_pVoiceServer = (IVoiceServer*)engineFactory(INTERFACEVERSION_VOICESERVER, NULL)) == NULL ) - return false; - if ( (cvar = (ICvar*)engineFactory(VENGINE_CVAR_INTERFACE_VERSION, NULL)) == NULL ) - return false; - if ( (networkstringtable = (INetworkStringTableContainer *)engineFactory(INTERFACENAME_NETWORKSTRINGTABLESERVER,NULL)) == NULL ) - return false; - if ( (staticpropmgr = (IStaticPropMgrServer *)engineFactory(INTERFACEVERSION_STATICPROPMGR_SERVER,NULL)) == NULL ) - return false; - if ( (random = (IUniformRandomStream *)engineFactory(VENGINE_SERVER_RANDOM_INTERFACE_VERSION, NULL)) == NULL ) - return false; - if ( (enginesound = (IEngineSound *)engineFactory(IENGINESOUND_SERVER_INTERFACE_VERSION, NULL)) == NULL ) - return false; - if ( (partition = (ISpatialPartition *)engineFactory(INTERFACEVERSION_SPATIALPARTITION, NULL)) == NULL ) - return false; - if ( (modelinfo = (IVModelInfo *)engineFactory(VMODELINFO_SERVER_INTERFACE_VERSION, NULL)) == NULL ) - return false; - if ( (enginetrace = (IEngineTrace *)engineFactory(INTERFACEVERSION_ENGINETRACE_SERVER,NULL)) == NULL ) - return false; - if ( (filesystem = (IFileSystem *)fileSystemFactory(FILESYSTEM_INTERFACE_VERSION,NULL)) == NULL ) - return false; - if ( (gameeventmanager = (IGameEventManager2 *)engineFactory(INTERFACEVERSION_GAMEEVENTSMANAGER2,NULL)) == NULL ) - return false; - if ( (datacache = (IDataCache*)engineFactory(DATACACHE_INTERFACE_VERSION, NULL )) == NULL ) - return false; - if ( (soundemitterbase = (ISoundEmitterSystemBase *)engineFactory(SOUNDEMITTERSYSTEM_INTERFACE_VERSION, NULL)) == NULL ) - return false; -#ifndef _XBOX - if ( (gamestatsuploader = (IUploadGameStats *)engineFactory( INTERFACEVERSION_UPLOADGAMESTATS, NULL )) == NULL ) - return false; -#endif - if ( (mdlcache = (IMDLCache*)engineFactory( MDLCACHE_INTERFACE_VERSION, NULL )) == NULL ) - return false; - if ( (serverpluginhelpers = (IServerPluginHelpers *)engineFactory(INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL)) == NULL ) - return false; - if ( (scenefilecache = (ISceneFileCache *)engineFactory( SCENE_FILE_CACHE_INTERFACE_VERSION, NULL )) == NULL ) - return false; - - // If not running dedicated, grab the engine vgui interface - if ( !engine->IsDedicatedServer() ) - { -#ifdef _WIN32 - if ( ( enginevgui = ( IEngineVGui * )engineFactory(VENGINE_VGUI_VERSION, NULL)) == NULL ) - return false; - - // This interface is optional, and is only valid when running with -tools - serverenginetools = ( IServerEngineTools * )engineFactory( VSERVERENGINETOOLS_INTERFACE_VERSION, NULL ); -#endif - } - - // Yes, both the client and game .dlls will try to Connect, the soundemittersystem.dll will handle this gracefully - if ( !soundemitterbase->Connect( engineFactory ) ) - return false; - - // cache the globals - gpGlobals = pGlobals; - - g_pSharedChangeInfo = engine->GetSharedEdictChangeInfo(); - - MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); - - // save these in case other system inits need them - factorylist_t factories; - factories.engineFactory = engineFactory; - factories.fileSystemFactory = fileSystemFactory; - factories.physicsFactory = physicsFactory; - FactoryList_Store( factories ); - - // load used game events - gameeventmanager->LoadEventsFromFile("resource/gameevents.res"); - - // init the cvar list first in case inits want to reference them - InitializeCvars(); - - sv_cheats = (ConVar*) ConCommandBase::FindCommand( "sv_cheats" ); - if ( !sv_cheats ) - return false; - - sv_maxreplay = (ConVar*) ConCommandBase::FindCommand( "sv_maxreplay" ); - - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEntitySaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetPhysSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetAISaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetTemplateSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetDefaultResponseSystemSaveRestoreBlockHandler() ); -#if !defined( _CONSOLE ) - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetCommentarySaveRestoreBlockHandler() ); -#endif - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); - - // The string system must init first + shutdown last - IGameSystem::Add( GameStringSystem() ); - - // Physics must occur before the sound envelope manager - IGameSystem::Add( PhysicsGameSystem() ); - - // Add game log system - IGameSystem::Add( GameLogSystem() ); -#ifndef _XBOX - // Add HLTV director - IGameSystem::Add( HLTVDirectorSystem() ); -#endif - // Add sound emitter - IGameSystem::Add( SoundEmitterSystem() ); - -#ifdef _WIN32 - // Startup vgui - if ( enginevgui ) - { - if(!VGui_Startup( engineFactory )) - return false; - } -#endif - - // load Mod specific game events ( MUST be before InitAllSystems() so it can pickup the mod specific events) - gameeventmanager->LoadEventsFromFile("resource/ModEvents.res"); - -#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out - InstallBotControl(); -#endif - - if ( !IGameSystem::InitAllSystems() ) - return false; - - // Due to dependencies, these are not autogamesystems - if ( !SceneCacheInit() ) - { - return false; - } - - // Due to dependencies, these are not autogamesystems - if ( !ModelSoundsCacheInit() ) - { - return false; - } - - // try to get debug overlay, may be NULL if on HLDS - debugoverlay = (IVDebugOverlay *)engineFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL ); - -#ifndef _XBOX - // create the Navigation Mesh interface - TheNavMesh = new CNavMesh; - - // init the gamestatsupload connection - gamestatsuploader->InitConnection(); -#endif - -#if !defined( _RETAIL ) -#if defined( _XBOX ) - XBX_rTimeStampLog( Plat_FloatTime(), "DLLInit finished" ); -#endif -#endif - return true; -} - -void CServerGameDLL::PostInit() -{ -#ifdef _WIN32 - if ( enginevgui ) - { - if ( VGui_PostInit() ) - { - // all good - } - } -#endif -} - -void CServerGameDLL::DLLShutdown( void ) -{ - - // Due to dependencies, these are not autogamesystems - ModelSoundsCacheShutdown(); - SceneCacheShutdown(); - -#if !defined( _CONSOLE ) - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetCommentarySaveRestoreBlockHandler() ); -#endif - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetDefaultResponseSystemSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetTemplateSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetAISaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetPhysSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEntitySaveRestoreBlockHandler() ); - - char *pFilename = g_TextStatsMgr.GetStatsFilename(); - if ( !pFilename || !pFilename[0] ) - { - g_TextStatsMgr.SetStatsFilename( "stats.txt" ); - } - g_TextStatsMgr.WriteFile( filesystem ); - - IGameSystem::ShutdownAllSystems(); - -#ifdef _WIN32 - if ( enginevgui ) - { - VGui_Shutdown(); - } -#endif - -#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out - RemoveBotControl(); -#endif - -#ifndef _XBOX - // destroy the Navigation Mesh interface - if (TheNavMesh) - { - delete TheNavMesh; - TheNavMesh = NULL; - } -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: See shareddefs.h for redefining this. Don't even think about it, though, for HL2. Or you will pay. ywb 9/22/03 -// Output : float -//----------------------------------------------------------------------------- - -float CServerGameDLL::GetTickInterval( void ) const -{ - float tickinterval = DEFAULT_TICK_INTERVAL; - -#if defined( CSTRIKE_DLL ) - // in CS reduce tickrate/sec by defualt - tickinterval *= 2; -#endif - - // override if tick rate specified in command line - if ( CommandLine()->CheckParm( "-tickrate" ) ) - { - float tickrate = CommandLine()->ParmValue( "-tickrate", 0 ); - if ( tickrate > 10 ) - tickinterval = 1.0f / tickrate; - } - - return tickinterval; -} - -// This is called when a new game is started. (restart, map) -bool CServerGameDLL::GameInit( void ) -{ - ResetGlobalState(); - engine->ServerCommand( "exec game.cfg\n" ); - engine->ServerExecute( ); - // clear out any old game's temporary save data -// engine->ClearSaveDir(); - return true; -} - -// This is called when a game ends (server disconnect, death, restart, load) -// NOT on level transitions within a game -void CServerGameDLL::GameShutdown( void ) -{ - ResetGlobalState(); -} - -static bool g_OneWayTransition = false; -void Game_SetOneWayTransition( void ) -{ - g_OneWayTransition = true; -} - -static CUtlVector g_RestoredEntities; -// just for debugging, assert that this is the only time this function is called -static bool g_InRestore = false; - -void AddRestoredEntity( CBaseEntity *pEntity ) -{ - Assert(g_InRestore); - if ( !pEntity ) - return; - - g_RestoredEntities.AddToTail( EHANDLE(pEntity) ); -} - -void EndRestoreEntities() -{ - if ( !g_InRestore ) - return; - - // Call all entities' OnRestore handlers - for ( int i = g_RestoredEntities.Count()-1; i >=0; --i ) - { - CBaseEntity *pEntity = g_RestoredEntities[i].Get(); - if ( pEntity && !pEntity->IsDormant() ) - { - MDLCACHE_CRITICAL_SECTION(); - pEntity->OnRestore(); - } - } - - g_RestoredEntities.Purge(); - - IGameSystem::OnRestoreAllSystems(); - - g_InRestore = false; - gEntList.CleanupDeleteList(); - - // HACKHACK: UNDONE: We need to redesign the main loop with respect to save/load/server activate - g_ServerGameDLL.ServerActivate( NULL, 0, 0 ); - CBaseEntity::SetAllowPrecache( false ); -} - -void BeginRestoreEntities() -{ - if ( g_InRestore ) - { - DevMsg( "BeginRestoreEntities without previous EndRestoreEntities.\n" ); - gEntList.CleanupDeleteList(); - } - g_RestoredEntities.Purge(); - g_InRestore = true; - CBaseEntity::SetAllowPrecache( true ); -} - -//----------------------------------------------------------------------------- -// Purpose: This prevents sv.tickcount/gpGlobals->tickcount from advancing during restore which -// would cause a lot of the NPCs to fast forward their think times to the same -// tick due to some ticks being elapsed during restore where there was no simulation going on -//----------------------------------------------------------------------------- -bool CServerGameDLL::IsRestoring() -{ - return g_InRestore; -} - -// Called any time a new level is started (after GameInit() also on level transitions within a game) -bool CServerGameDLL::LevelInit( const char *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background ) -{ - VPROF("CServerGameDLL::LevelInit"); - ResetWindspeed(); - UpdateChapterRestrictions( pMapName ); - - // IGameSystem::LevelInitPreEntityAllSystems() is called when the world is precached - // That happens either in LoadGameState() or in MapEntity_ParseAllEntities() - if ( loadGame ) - { - BeginRestoreEntities(); - if ( !engine->LoadGameState( pMapName, 1 ) ) - { - MapEntity_ParseAllEntities( pMapEntities ); - } - - if ( pOldLevel ) - { - gpGlobals->eLoadType = MapLoad_Transition; - engine->LoadAdjacentEnts( pOldLevel, pLandmarkName ); - } - else - { - gpGlobals->eLoadType = MapLoad_LoadGame; - } - - if ( g_OneWayTransition ) - { - engine->ClearSaveDirAfterClientLoad(); - } - - if ( pOldLevel && sv_autosave.GetBool() == true ) - { - // This is a single-player style level transition. - // Queue up an autosave one second into the level - CBaseEntity *pAutosave = CBaseEntity::Create( "logic_autosave", vec3_origin, vec3_angle, NULL ); - if ( pAutosave ) - { - g_EventQueue.AddEvent( pAutosave, "Save", 1.0, NULL, NULL ); - g_EventQueue.AddEvent( pAutosave, "Kill", 1.1, NULL, NULL ); - } - } - } - else - { - if ( background ) - { - gpGlobals->eLoadType = MapLoad_Background; - } - else - { - gpGlobals->eLoadType = MapLoad_NewGame; - } - - LevelInit_ParseAllEntities( pMapEntities ); - } - - // Check low violence settings for this map - g_RagdollLVManager.SetLowViolence( pMapName ); - - // Now that all of the active entities have been loaded in, precache any entities who need point_template parameters - // to be parsed (the above code has loaded all point_template entities) - PrecachePointTemplates(); - - // load MOTD from file into stringtable - LoadMessageOfTheDay(); - - // Sometimes an ent will Remove() itself during its precache, so RemoveImmediate won't happen. - // This makes sure those ents get cleaned up. - gEntList.CleanupDeleteList(); - - g_AIFriendliesTalkSemaphore.Release(); - g_AIFoesTalkSemaphore.Release(); - g_OneWayTransition = false; - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: called after every level change and load game, iterates through all the -// active entities and gives them a chance to fix up their state -//----------------------------------------------------------------------------- -#ifdef DEBUG -bool g_bReceivedChainedActivate; -bool g_bCheckForChainedActivate; -#define BeginCheckChainedActivate() if (0) ; else { g_bCheckForChainedActivate = true; g_bReceivedChainedActivate = false; } -#define EndCheckChainedActivate( bCheck ) \ - if (0) ; else \ - { \ - if ( bCheck ) \ - { \ - char msg[ 1024 ]; \ - Q_snprintf( msg, sizeof( msg ), "Entity (%i/%s/%s) failed to call base class Activate()\n", pClass->entindex(), pClass->GetClassname(), STRING( pClass->GetEntityName() ) ); \ - AssertMsg( g_bReceivedChainedActivate == true, msg ); \ - } \ - g_bCheckForChainedActivate = false; \ - } -#else -#define BeginCheckChainedActivate() ((void)0) -#define EndCheckChainedActivate( bCheck ) ((void)0) -#endif - -void CServerGameDLL::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) -{ - // HACKHACK: UNDONE: We need to redesign the main loop with respect to save/load/server activate - if ( g_InRestore ) - return; - - if ( gEntList.ResetDeleteList() != 0 ) - { - Msg( "ERROR: Entity delete queue not empty on level start!\n" ); - } - - for ( CBaseEntity *pClass = gEntList.FirstEnt(); pClass != NULL; pClass = gEntList.NextEnt(pClass) ) - { - if ( pClass && !pClass->IsDormant() ) - { - MDLCACHE_CRITICAL_SECTION(); - - BeginCheckChainedActivate(); - pClass->Activate(); - - // We don't care if it finished activating if it decided to remove itself. - EndCheckChainedActivate( !( pClass->GetEFlags() & EFL_KILLME ) ); - } - } - - IGameSystem::LevelInitPostEntityAllSystems(); - // No more precaching after PostEntityAllSystems!!! - CBaseEntity::SetAllowPrecache( false ); - - // only display the think limit when the game is run with "developer" mode set - if ( !g_pDeveloper->GetInt() ) - { - think_limit.SetValue( 0 ); - } - -#ifndef _XBOX - // load the Navigation Mesh for this map - TheNavMesh->Load(); -#endif - -#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out - TheBots->ServerActivate(); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: Called at the start of every game frame -//----------------------------------------------------------------------------- -ConVar trace_report( "trace_report", "0" ); - -void CServerGameDLL::GameFrame( bool simulating ) -{ - VPROF( "CServerGameDLL::GameFrame" ); - - // Don't run frames until fully restored - if ( g_InRestore ) - return; - - static bool skipframe = false; - - // If server is skipping frames, don't run simulation this time through - if ( skipframe ) - { - UpdateAllClientData(); - skipframe = false; - return; - } - - float oldframetime = gpGlobals->frametime; - if ( CBaseEntity::IsSimulatingOnAlternateTicks() ) - { - skipframe = true; - // If we're skipping frames, then the frametime is 2x the normal tick - gpGlobals->frametime *= 2.0f; - } - -#ifdef _DEBUG - // For profiling.. let them enable/disable the networkvar manual mode stuff. - g_bUseNetworkVars = s_UseNetworkVars.GetBool(); -#endif - - extern void GameStartFrame( void ); - extern void ServiceEventQueue( void ); - extern void Physics_RunThinkFunctions( bool simulating ); - - // Delete anything that was marked for deletion - // outside of server frameloop (e.g., in response to concommand) - gEntList.CleanupDeleteList(); - - IGameSystem::FrameUpdatePreEntityThinkAllSystems(); - GameStartFrame(); - -#ifndef _XBOX - TheNavMesh->Update(); - - gamestatsuploader->UpdateConnection(); -#endif - - Physics_RunThinkFunctions( simulating ); - - IGameSystem::FrameUpdatePostEntityThinkAllSystems(); - - // UNDONE: Make these systems IGameSystems and move these calls into FrameUpdatePostEntityThink() - // service event queue, firing off any actions whos time has come - ServiceEventQueue(); - - // free all ents marked in think functions - gEntList.CleanupDeleteList(); - - // FIXME: Should this only occur on the final tick? - UpdateAllClientData(); - - if ( g_pGameRules ) - { - g_pGameRules->EndGameFrame(); - } - - if ( trace_report.GetBool() ) - { - int total = 0, totals[3]; - for ( int i = 0; i < 3; i++ ) - { - totals[i] = enginetrace->GetStatByIndex( i, true ); - if ( totals[i] > 0 ) - { - total += totals[i]; - } - } - - if ( total ) - { - Msg("Trace: %d, contents %d, enumerate %d\n", totals[0], totals[1], totals[2] ); - } - } - - // Any entities that detect network state changes on a timer do it here. - g_NetworkPropertyEventMgr.FireEvents(); - - gpGlobals->frametime = oldframetime; -} - -//----------------------------------------------------------------------------- -// Purpose: Called every frame even if not ticking -// Input : simulating - -//----------------------------------------------------------------------------- -void CServerGameDLL::PreClientUpdate( bool simulating ) -{ - if ( !simulating ) - return; - - /* - if (game_speeds.GetInt()) - { - DrawMeasuredSections(); - } - */ - -//#ifdef _DEBUG - allow this in release for now - DrawAllDebugOverlays(); -//#endif - - IGameSystem::PreClientUpdateAllSystems(); - - if ( sv_showhitboxes.GetInt() == -1 ) - return; - - if ( sv_showhitboxes.GetInt() == 0 ) - { - // assume it's text - CBaseEntity *pEntity = NULL; - - while (1) - { - pEntity = gEntList.FindEntityByName( pEntity, sv_showhitboxes.GetString() ); - if ( !pEntity ) - break; - - CBaseAnimating *anim = dynamic_cast< CBaseAnimating * >( pEntity ); - - if (anim) - { - anim->DrawServerHitboxes(); - } - } - return; - } - - CBaseAnimating *anim = dynamic_cast< CBaseAnimating * >( CBaseEntity::Instance( engine->PEntityOfEntIndex( sv_showhitboxes.GetInt() ) ) ); - if ( !anim ) - return; - - anim->DrawServerHitboxes(); -} - -void CServerGameDLL::Think( bool finalTick ) -{ - if ( m_fAutoSaveDangerousTime != 0.0f && m_fAutoSaveDangerousTime < gpGlobals->curtime ) - { - // The safety timer for a dangerous auto save has expired - CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); - - if ( pPlayer->GetDeathTime() == 0.0f || pPlayer->GetDeathTime() > gpGlobals->curtime ) - { - // The player isn't dead, so make the dangerous auto save safe - engine->ServerCommand( "autosavedangerousissafe\n" ); - } - - m_fAutoSaveDangerousTime = 0.0f; - } -} - - -// Called when a level is shutdown (including changing levels) -void CServerGameDLL::LevelShutdown( void ) -{ - IGameSystem::LevelShutdownPreEntityAllSystems(); - - // YWB: - // This entity pointer is going away now and is corrupting memory on level transitions/restarts - CSoundEnt::ShutdownSoundEnt(); - - gEntList.Clear(); - - IGameSystem::LevelShutdownPostEntityAllSystems(); - - // In case we quit out during initial load - CBaseEntity::SetAllowPrecache( false ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : ServerClass* -//----------------------------------------------------------------------------- -ServerClass* CServerGameDLL::GetAllServerClasses() -{ - return g_pServerClassHead; -} - - -const char *CServerGameDLL::GetGameDescription( void ) -{ - return ::GetGameDescription(); -} - -void CServerGameDLL::CreateNetworkStringTables( void ) -{ - // Create any shared string tables here (and only here!) - // E.g.: xxx = networkstringtable->CreateStringTable( "SceneStrings", 512 ); - g_pStringTableEffectDispatch = networkstringtable->CreateStringTable( "EffectDispatch", MAX_EFFECT_DISPATCH_STRINGS ); - g_pStringTableVguiScreen = networkstringtable->CreateStringTable( "VguiScreen", MAX_VGUI_SCREEN_STRINGS ); - g_pStringTableMaterials = networkstringtable->CreateStringTable( "Materials", MAX_MATERIAL_STRINGS ); - g_pStringTableInfoPanel = networkstringtable->CreateStringTable( "InfoPanel", MAX_INFOPANEL_STRINGS ); - g_pStringTableClientSideChoreoScenes = networkstringtable->CreateStringTable( "Scenes", MAX_CHOREO_SCENES_STRINGS ); - - Assert( g_pStringTableEffectDispatch && - g_pStringTableVguiScreen && - g_pStringTableMaterials && - g_pStringTableInfoPanel && - g_pStringTableClientSideChoreoScenes ); - - // Need this so we have the error material always handy - PrecacheMaterial( "debug/debugempty" ); - Assert( GetMaterialIndex( "debug/debugempty" ) == 0 ); - - CreateNetworkStringTables_GameRules(); - - // Set up save/load utilities for string tables - g_VguiScreenStringOps.Init( g_pStringTableVguiScreen ); -} - -CSaveRestoreData *CServerGameDLL::SaveInit( int size ) -{ - return ::SaveInit(size); -} - -//----------------------------------------------------------------------------- -// Purpose: Saves data from a struct into a saverestore object, to be saved to disk -// Input : *pSaveData - the saverestore object -// char *pname - the name of the data to write -// *pBaseData - the struct into which the data is to be read -// *pFields - pointer to an array of data field descriptions -// fieldCount - the size of the array (number of field descriptions) -//----------------------------------------------------------------------------- -void CServerGameDLL::SaveWriteFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) -{ - CSave saveHelper( pSaveData ); - saveHelper.WriteFields( pname, pBaseData, pMap, pFields, fieldCount ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Reads data from a save/restore block into a structure -// Input : *pSaveData - the saverestore object -// char *pname - the name of the data to extract from -// *pBaseData - the struct into which the data is to be restored -// *pFields - pointer to an array of data field descriptions -// fieldCount - the size of the array (number of field descriptions) -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- - -void CServerGameDLL::SaveReadFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) -{ - CRestore restoreHelper( pSaveData ); - restoreHelper.ReadFields( pname, pBaseData, pMap, pFields, fieldCount ); -} - -//----------------------------------------------------------------------------- - -void CServerGameDLL::SaveGlobalState( CSaveRestoreData *s ) -{ - ::SaveGlobalState(s); -} - -void CServerGameDLL::RestoreGlobalState(CSaveRestoreData *s) -{ - ::RestoreGlobalState(s); -} - -void CServerGameDLL::Save( CSaveRestoreData *s ) -{ - CSave saveHelper( s ); - g_pGameSaveRestoreBlockSet->Save( &saveHelper ); -} - -void CServerGameDLL::Restore( CSaveRestoreData *s, bool b) -{ - CRestore restore(s); - g_pGameSaveRestoreBlockSet->Restore( &restore, b ); - g_pGameSaveRestoreBlockSet->PostRestore(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_type - -// *name - -// size - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- - -bool CServerGameDLL::GetUserMessageInfo( int msg_type, char *name, int maxnamelength, int& size ) -{ - if ( !usermessages->IsValidIndex( msg_type ) ) - return false; - - Q_strncpy( name, usermessages->GetUserMessageName( msg_type ), maxnamelength ); - size = usermessages->GetUserMessageSize( msg_type ); - return true; -} - -CStandardSendProxies* CServerGameDLL::GetStandardSendProxies() -{ - return &g_StandardSendProxies; -} - -int CServerGameDLL::CreateEntityTransitionList( CSaveRestoreData *s, int a) -{ - CRestore restoreHelper( s ); - // save off file base - int base = restoreHelper.GetReadPos(); - - int movedCount = ::CreateEntityTransitionList(s, a); - if ( movedCount ) - { - g_pGameSaveRestoreBlockSet->CallBlockHandlerRestore( GetPhysSaveRestoreBlockHandler(), base, &restoreHelper, false ); - g_pGameSaveRestoreBlockSet->CallBlockHandlerRestore( GetAISaveRestoreBlockHandler(), base, &restoreHelper, false ); - } - - GetPhysSaveRestoreBlockHandler()->PostRestore(); - GetAISaveRestoreBlockHandler()->PostRestore(); - - return movedCount; -} - -void CServerGameDLL::PreSave( CSaveRestoreData *s ) -{ - g_pGameSaveRestoreBlockSet->PreSave( s ); -} - -#include "client_textmessage.h" - -// This little hack lets me marry BSP names to messages in titles.txt -typedef struct -{ - char *pBSPName; - char *pTitleName; -} TITLECOMMENT; - -// this list gets searched for the first partial match, so some are out of order -static TITLECOMMENT gTitleComments[] = -{ -#ifdef HL1_DLL - { "t0a0", "#T0A0TITLE" }, - { "c0a0", "#HL1_Chapter1_Title" }, - { "c1a0", "#HL1_Chapter2_Title" }, - { "c1a1", "#HL1_Chapter3_Title" }, - { "c1a2", "#HL1_Chapter4_Title" }, - { "c1a3", "#HL1_Chapter5_Title" }, - { "c1a4", "#HL1_Chapter6_Title" }, - { "c2a1", "#HL1_Chapter7_Title" }, - { "c2a2", "#HL1_Chapter8_Title" }, - { "c2a3", "#HL1_Chapter9_Title" }, - { "c2a4d", "#HL1_Chapter11_Title" }, // These must appear before "C2A4" so all other map names starting with C2A4 get that title - { "c2a4e", "#HL1_Chapter11_Title" }, - { "c2a4f", "#HL1_Chapter11_Title" }, - { "c2a4g", "#HL1_Chapter11_Title" }, - { "c2a4", "#HL1_Chapter10_Title" }, - { "c2a5", "#HL1_Chapter12_Title" }, - { "c3a1", "#HL1_Chapter13_Title" }, - { "c3a2", "#HL1_Chapter14_Title" }, - { "c4a1a", "#HL1_Chapter17_Title" }, // Order is important, see above - { "c4a1b", "#HL1_Chapter17_Title" }, - { "c4a1c", "#HL1_Chapter17_Title" }, - { "c4a1d", "#HL1_Chapter17_Title" }, - { "c4a1e", "#HL1_Chapter17_Title" }, - { "c4a1", "#HL1_Chapter15_Title" }, - { "c4a2", "#HL1_Chapter16_Title" }, - { "c4a3", "#HL1_Chapter18_Title" }, - { "c5a1", "#HL1_Chapter19_Title" }, -#else - { "intro", "#HL2_Chapter1_Title" }, - - { "d1_trainstation_05", "#HL2_Chapter2_Title" }, - { "d1_trainstation_06", "#HL2_Chapter2_Title" }, - - { "d1_trainstation_", "#HL2_Chapter1_Title" }, - - { "d1_canals_06a", "#HL2_Chapter4_Title" }, - { "d1_canals_06b", "#HL2_Chapter4_Title" }, - { "d1_canals_07a", "#HL2_Chapter4_Title" }, - { "d1_canals_07b", "#HL2_Chapter4_Title" }, - { "d1_canals_08", "#HL2_Chapter4_Title" }, - { "d1_canals_09", "#HL2_Chapter4_Title" }, - { "d1_canals_1", "#HL2_Chapter4_Title" }, - - { "d1_canals_0", "#HL2_Chapter3_Title" }, - - { "d1_eli_", "#HL2_Chapter5_Title" }, - - { "d1_town_", "#HL2_Chapter6_Title" }, - - { "d2_coast_09a", "#HL2_Chapter8_Title" }, - { "d2_coast_09b", "#HL2_Chapter8_Title" }, - { "d2_coast_1", "#HL2_Chapter8_Title" }, - { "d2_prison_01", "#HL2_Chapter8_Title" }, - - { "d2_coast_", "#HL2_Chapter7_Title" }, - - { "d2_prison_06a", "#HL2_Chapter10_Title" }, - { "d2_prison_06b", "#HL2_Chapter10_Title" }, - { "d2_prison_07a", "#HL2_Chapter10_Title" }, - { "d2_prison_07b", "#HL2_Chapter10_Title" }, - { "d2_prison_08", "#HL2_Chapter10_Title" }, - { "d3_c17_01", "#HL2_Chapter10_Title" }, - - { "d2_prison_", "#HL2_Chapter9_Title" }, - - { "d3_c17_09", "#HL2_Chapter12_Title" }, - { "d3_c17_1", "#HL2_Chapter12_Title" }, - - { "d3_c17_", "#HL2_Chapter11_Title" }, - - { "d3_citadel_", "#HL2_Chapter13_Title" }, - - { "d3_breen_", "#HL2_Chapter14_Title" }, - { "credits", "#HL2_Chapter15_Title" }, - - { "ep1_citadel_00", "#episodic_Chapter1_Title" }, - { "ep1_citadel_01", "#episodic_Chapter1_Title" }, - { "ep1_citadel_02", "#episodic_Chapter1_Title" }, - { "ep1_citadel_02b", "#episodic_Chapter1_Title" }, - { "ep1_citadel_03", "#episodic_Chapter2_Title" }, - { "ep1_citadel_04", "#episodic_Chapter2_Title" }, - { "ep1_c17_00", "#episodic_Chapter3_Title" }, - { "ep1_c17_00a", "#episodic_Chapter3_Title" }, - { "ep1_c17_01", "#episodic_Chapter4_Title" }, - { "ep1_c17_02", "#episodic_Chapter4_Title" }, - { "ep1_c17_02b", "#episodic_Chapter4_Title" }, - { "ep1_c17_05", "#episodic_Chapter5_Title" }, - { "ep1_c17_06", "#episodic_Chapter5_Title" }, -#endif -}; - -#ifdef _XBOX -void CServerGameDLL::GetTitleName( const char *pMapName, char* pTitleBuff, int titleBuffSize ) -{ - // Try to find a matching title comment for this mapname - for ( int i = 0; i < ARRAYSIZE(gTitleComments); i++ ) - { - if ( !Q_strnicmp( pMapName, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) - { - Q_strncpy( pTitleBuff, gTitleComments[i].pTitleName, titleBuffSize ); - return; - } - } - Q_strncpy( pTitleBuff, pMapName, titleBuffSize ); -} -#endif - -void CServerGameDLL::GetSaveCommentEx( char *text, int maxlength, float flMinutes, float flSeconds ) -{ - char comment[64]; - const char *pName; - int i; - - char const *mapname = STRING( gpGlobals->mapname ); - - pName = NULL; - - // Try to find a matching title comment for this mapname - for ( i = 0; i < ARRAYSIZE(gTitleComments) && !pName; i++ ) - { - if ( !Q_strnicmp( mapname, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) - { - // found one - int j; - - // Got a message, post-process it to be save name friendly - Q_strncpy( comment, gTitleComments[i].pTitleName, sizeof( comment ) ); - pName = comment; - j = 0; - // Strip out CRs - while ( j < 64 && comment[j] ) - { - if ( comment[j] == '\n' || comment[j] == '\r' ) - comment[j] = 0; - else - j++; - } - break; - } - } - - // If we didn't get one, use the designer's map name, or the BSP name itself - if ( !pName ) - { - pName = mapname; - } - - if ( flMinutes == 0 && flSeconds == 0 ) - { - Q_snprintf( text, maxlength, "%-64.64s", pName ); - } - else - { - int totalSeconds = gpGlobals->curtime + flSeconds; - int minutes = (int)( totalSeconds / 60.0f ) + flMinutes; - int seconds = (int)fmod( totalSeconds, 60.0f ); - - // Wow, this guy/gal must suck...! - if ( minutes >= 1000 ) - { - minutes = 999; - seconds = 59; - } - - // add the elapsed time at the end of the comment, for the ui to parse out - Q_snprintf( text, maxlength, "%-64.64s %03d:%02d", pName, minutes, seconds ); - } -} - -void CServerGameDLL::WriteSaveHeaders( CSaveRestoreData *s ) -{ - CSave saveHelper( s ); - g_pGameSaveRestoreBlockSet->WriteSaveHeaders( &saveHelper ); - g_pGameSaveRestoreBlockSet->PostSave(); -} - -void CServerGameDLL::ReadRestoreHeaders( CSaveRestoreData *s ) -{ - CRestore restoreHelper( s ); - g_pGameSaveRestoreBlockSet->PreRestore(); - g_pGameSaveRestoreBlockSet->ReadRestoreHeaders( &restoreHelper ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Called during a transition, to build a map adjacency list -//----------------------------------------------------------------------------- -void CServerGameDLL::BuildAdjacentMapList( void ) -{ - // retrieve the pointer to the save data - CSaveRestoreData *pSaveData = gpGlobals->pSaveData; - - if ( pSaveData ) - pSaveData->levelInfo.connectionCount = BuildChangeList( pSaveData->levelInfo.levelList, MAX_LEVEL_CONNECTIONS ); -} - -//----------------------------------------------------------------------------- -// Purpose: Sanity-check to verify that a path is a relative path inside the game dir -// Taken From: engine/cmd.cpp -//----------------------------------------------------------------------------- -static bool IsValidPath( const char *pszFilename ) -{ - if ( !pszFilename ) - { - return false; - } - - if ( Q_strlen( pszFilename ) <= 0 || - Q_IsAbsolutePath( pszFilename ) || // to protect absolute paths - Q_strstr( pszFilename, ".." ) ) // to protect relative paths - { - return false; - } - - return true; -} - -static void ValidateMOTDFilename( ConVar *var, const char *oldValue ) -{ - if ( !IsValidPath( var->GetString() ) ) - { - var->Revert(); - } -} - -static ConVar motdfile( "motdfile", "motd.txt", 0, "The MOTD file to load.", ValidateMOTDFilename ); -void CServerGameDLL::LoadMessageOfTheDay() -{ -#ifndef _XBOX - char data[2048]; - - int length = filesystem->Size( motdfile.GetString(), "GAME" ); - - if ( length <= 0 || length >= (sizeof(data)-1) ) - { - DevMsg("Invalid file size for %s\n", motdfile.GetString() ); - return; - } - - FileHandle_t hFile = filesystem->Open( motdfile.GetString(), "rb", "GAME" ); - - if ( hFile == FILESYSTEM_INVALID_HANDLE ) - return; - - filesystem->Read( data, length, hFile ); - filesystem->Close( hFile ); - - data[length] = 0; - - g_pStringTableInfoPanel->AddString( "motd", length+1, data ); -#endif -} - -// keeps track of which chapters the user has unlocked -ConVar sv_unlockedchapters( "sv_unlockedchapters", "1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX ); - -//----------------------------------------------------------------------------- -// Purpose: Updates which chapters are unlocked -//----------------------------------------------------------------------------- -void UpdateChapterRestrictions( const char *mapname ) -{ - // look at the chapter for this map - char chapterTitle[64]; - chapterTitle[0] = 0; - for ( int i = 0; i < ARRAYSIZE(gTitleComments); i++ ) - { - if ( !Q_strnicmp( mapname, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) - { - // found - Q_strncpy( chapterTitle, gTitleComments[i].pTitleName, sizeof( chapterTitle ) ); - int j = 0; - while ( j < 64 && chapterTitle[j] ) - { - if ( chapterTitle[j] == '\n' || chapterTitle[j] == '\r' ) - chapterTitle[j] = 0; - else - j++; - } - - break; - } - } - - if ( !chapterTitle[0] ) - return; - - // make sure the specified chapter title is unlocked - strlwr( chapterTitle ); - - const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); - - char chapterNumberPrefix[64]; - Q_snprintf(chapterNumberPrefix, sizeof(chapterNumberPrefix), "#%s_chapter", pGameDir); - - const char *newChapterNumber = strstr( chapterTitle, chapterNumberPrefix ); - if ( newChapterNumber ) - { - // cut off the front - newChapterNumber += strlen( chapterNumberPrefix ); - char newChapter[32]; - Q_strncpy( newChapter, newChapterNumber, sizeof(newChapter) ); - - // cut off the end - char *end = strstr( newChapter, "_title" ); - if ( end ) - { - *end = 0; - } - - // ok we have the string, see if it's newer - const char *unlockedChapter = sv_unlockedchapters.GetString(); - if ( atoi(unlockedChapter) < atoi(newChapter) - || (atoi(unlockedChapter) == atoi(newChapter) && stricmp(unlockedChapter, newChapter) < 0) ) - { - // ok we're at a higher chapter, unlock - sv_unlockedchapters.SetValue( newChapter ); - } - } -} - -//----------------------------------------------------------------------------- -// Precaches a vgui screen overlay material -//----------------------------------------------------------------------------- -void PrecacheMaterial( const char *pMaterialName ) -{ - Assert( pMaterialName && pMaterialName[0] ); - g_pStringTableMaterials->AddString( pMaterialName ); -} - - -//----------------------------------------------------------------------------- -// Converts a previously precached material into an index -//----------------------------------------------------------------------------- -int GetMaterialIndex( const char *pMaterialName ) -{ - if (pMaterialName) - { - int nIndex = g_pStringTableMaterials->FindStringIndex( pMaterialName ); - - if (nIndex != INVALID_STRING_INDEX ) - { - return nIndex; - } - else - { - DevMsg("Warning! GetMaterialIndex: couldn't find material %s\n ", pMaterialName ); - return 0; - } - } - - // This is the invalid string index - return 0; -} - -//----------------------------------------------------------------------------- -// Converts a previously precached material index into a string -//----------------------------------------------------------------------------- -const char *GetMaterialNameFromIndex( int nMaterialIndex ) -{ - return g_pStringTableMaterials->GetString( nMaterialIndex ); -} - - -class CServerGameEnts : public IServerGameEnts -{ -public: - virtual void SetDebugEdictBase(edict_t *base); - virtual void MarkEntitiesAsTouching( edict_t *e1, edict_t *e2 ); - virtual void FreeContainingEntity( edict_t * ); - virtual edict_t* BaseEntityToEdict( CBaseEntity *pEnt ); - virtual CBaseEntity* EdictToBaseEntity( edict_t *pEdict ); - virtual void CheckTransmit( CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts ); -}; -EXPOSE_SINGLE_INTERFACE(CServerGameEnts, IServerGameEnts, INTERFACEVERSION_SERVERGAMEENTS); - -void CServerGameEnts::SetDebugEdictBase(edict_t *base) -{ - g_pDebugEdictBase = base; -} - -//----------------------------------------------------------------------------- -// Purpose: Marks entities as touching -// Input : *e1 - -// *e2 - -//----------------------------------------------------------------------------- -void CServerGameEnts::MarkEntitiesAsTouching( edict_t *e1, edict_t *e2 ) -{ - CBaseEntity *entity = GetContainingEntity( e1 ); - CBaseEntity *entityTouched = GetContainingEntity( e2 ); - if ( entity && entityTouched ) - { - // HACKHACK: UNDONE: Pass in the trace here??!?!? - trace_t tr; - UTIL_ClearTrace( tr ); - tr.endpos = (entity->GetAbsOrigin() + entityTouched->GetAbsOrigin()) * 0.5; - entity->PhysicsMarkEntitiesAsTouching( entityTouched, tr ); - } -} - -void CServerGameEnts::FreeContainingEntity( edict_t *e ) -{ - ::FreeContainingEntity(e); -} - -edict_t* CServerGameEnts::BaseEntityToEdict( CBaseEntity *pEnt ) -{ - if ( pEnt ) - return pEnt->edict(); - else - return NULL; -} - -CBaseEntity* CServerGameEnts::EdictToBaseEntity( edict_t *pEdict ) -{ - if ( pEdict ) - return CBaseEntity::Instance( pEdict ); - else - return NULL; -} - - -/* Yuck.. ideally this would be in CServerNetworkProperty's header, but it requires CBaseEntity and -// inlining it gives a nice speedup. -inline void CServerNetworkProperty::CheckTransmit( CCheckTransmitInfo *pInfo ) -{ - // If we have a transmit proxy, let it hook our ShouldTransmit return value. - if ( m_pTransmitProxy ) - { - nShouldTransmit = m_pTransmitProxy->ShouldTransmit( pInfo, nShouldTransmit ); - } - - if ( m_pOuter->ShouldTransmit( pInfo ) ) - { - m_pOuter->SetTransmit( pInfo ); - } -} */ - -void CServerGameEnts::CheckTransmit( CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts ) -{ - // NOTE: for speed's sake, this assumes that all networkables are CBaseEntities and that the edict list - // is consecutive in memory. If either of these things change, then this routine needs to change, but - // ideally we won't be calling any virtual from this routine. This speedy routine was added as an - // optimization which would be nice to keep. - edict_t *pBaseEdict = engine->PEntityOfEntIndex( 0 ); - - // get recipient player's skybox: - CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); - - Assert( pRecipientEntity && pRecipientEntity->IsPlayer() ); - - if ( !pRecipientEntity ) - return; - - MDLCACHE_CRITICAL_SECTION(); - CBasePlayer *pRecipientPlayer = static_cast( pRecipientEntity ); - - const int skyBoxArea = pRecipientPlayer->m_Local.m_skybox3d.area; - const bool bIsHLTV = pRecipientPlayer->IsHLTV(); - - // m_pTransmitAlways must be set if HLTV client - Assert( bIsHLTV == ( pInfo->m_pTransmitAlways != NULL) ); - - // int dontSend = 0; int always = 0; int fullCheck = 0; int PVS = 0; - - - for ( int i=0; i < nEdicts; i++ ) - { - int iEdict = pEdictIndices[i]; - - edict_t *pEdict = &pBaseEdict[iEdict]; - Assert( pEdict == engine->PEntityOfEntIndex( iEdict ) ); - int nFlags = pEdict->m_fStateFlags & (FL_EDICT_DONTSEND|FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_FULLCHECK); - - if ( nFlags & FL_EDICT_DONTSEND ) - { - // entity needs no transmit - continue; - } - - if ( pInfo->m_pTransmitEdict->Get( iEdict ) ) - { - // entity is already marked for sending - continue; - } - else if ( pEdict->m_fStateFlags & FL_EDICT_DONTSEND ) - { - continue; - } - - if ( nFlags & FL_EDICT_ALWAYS ) - { - while ( true ) - { - // mark entity for sending - pInfo->m_pTransmitEdict->Set( iEdict ); - - if ( bIsHLTV ) - { - pInfo->m_pTransmitAlways->Set( iEdict ); - } - - CBaseEntity *pEnt = (CBaseEntity*)pEdict->GetUnknown(); - if ( !pEnt ) - break; - - CBaseEntity *pParent = pEnt->GetMoveParent(); - if ( !pParent ) - break; - - pEdict = pParent->edict(); - iEdict = pParent->entindex(); - } - - - continue; - } - - // get the Baseentity - - CBaseEntity *pEnt = ( CBaseEntity * )pEdict->GetUnknown(); - Assert( dynamic_cast< CBaseEntity* >( pEdict->GetUnknown() ) == pEnt ); - - if ( nFlags == FL_EDICT_FULLCHECK ) - { - // do a full ShouldTransmit() check, may return FL_EDICT_CHECKPVS - nFlags = pEnt->ShouldTransmit( pInfo ); - - Assert( !(nFlags & FL_EDICT_FULLCHECK) ); - - if ( nFlags & FL_EDICT_ALWAYS ) - { - pEnt->SetTransmit( pInfo, true ); - continue; - } - } - - if ( !( nFlags & FL_EDICT_PVSCHECK ) ) - { - // dont send this entity - continue; - } - - CServerNetworkProperty *netProp = pEnt->NetworkProp(); - if ( bIsHLTV ) - { - // for the HLTV we don't cull against PVS - if ( netProp->AreaNum() == skyBoxArea ) - { - pEnt->SetTransmit( pInfo, true ); - } - else - { - pEnt->SetTransmit( pInfo, false ); - } - continue; - } - - - bool bSameAreaAsSky = netProp->AreaNum() == skyBoxArea; - // Always send entities in the player's 3d skybox. - // Sidenote: call of AreaNum() ensures that PVS data is up to date for this entity - if ( bSameAreaAsSky ) - { - pEnt->SetTransmit( pInfo, true ); - } - else - { - bool bInPVS = netProp->IsInPVS( pInfo ); - if ( bInPVS ) - { - // only send if entity is in PVS - pEnt->SetTransmit( pInfo, false ); - } - else - { - // If the entity is marked "check PVS" but it's in hierarchy, walk up the hierarchy looking for the - // for any parent which is also in the PVS. If none are found, then we don't need to worry about sending ourself - CBaseEntity *orig = pEnt; - CBaseEntity *check = pEnt->GetMoveParent(); - - // BUG BUG: I think it might be better to build up a list of edict indices which "depend" on other answers and then - // resolve them in a second pass. Not sure what happens if an entity has two parents who both request PVS check? - - while ( check ) - { - int checkIndex = check->entindex(); - - // Parent already being sent - if ( pInfo->m_pTransmitEdict->Get( checkIndex ) ) - { - orig->SetTransmit( pInfo, true ); - break; - } - - edict_t *checkEdict = check->edict(); - int checkFlags = checkEdict->m_fStateFlags & (FL_EDICT_DONTSEND|FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_FULLCHECK); - if ( checkFlags & FL_EDICT_DONTSEND ) - { - break; - } - if ( checkFlags & FL_EDICT_ALWAYS ) - { - orig->SetTransmit( pInfo, true ); - break; - } - if ( checkFlags == FL_EDICT_FULLCHECK ) - { - // do a full ShouldTransmit() check, may return FL_EDICT_CHECKPVS - nFlags = check->ShouldTransmit( pInfo ); - Assert( !(nFlags & FL_EDICT_FULLCHECK) ); - if ( nFlags & FL_EDICT_ALWAYS ) - { - check->SetTransmit( pInfo, true ); - orig->SetTransmit( pInfo, true ); - } - break; - } - if ( checkFlags & FL_EDICT_PVSCHECK ) - { - // Check pvs - CServerNetworkProperty *netProp = check->NetworkProp(); - netProp->RecomputePVSInformation(); - bool bMoveParentInPVS = netProp->IsInPVS( pInfo ); - if ( bMoveParentInPVS ) - { - orig->SetTransmit( pInfo, true ); - break; - } - } - - // Continue up chain just in case the parent itself has a parent that's in the PVS... - check = check->GetMoveParent(); - } - } - } - } - -// Msg("A:%i, N:%i, F: %i, P: %i\n", always, dontSend, fullCheck, PVS ); -} - - -CServerGameClients g_ServerGameClients; -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameClients, IServerGameClients, INTERFACEVERSION_SERVERGAMECLIENTS, g_ServerGameClients ); - - -//----------------------------------------------------------------------------- -// Purpose: called when a player tries to connect to the server -// Input : *pEdict - the new player -// char *pszName - the players name -// char *pszAddress - the IP address of the player -// reject - output - fill in with the reason why -// maxrejectlen -- sizeof output buffer -// the player was not allowed to connect. -// Output : Returns TRUE if player is allowed to join, FALSE if connection is denied. -//----------------------------------------------------------------------------- -bool CServerGameClients::ClientConnect( edict_t *pEdict, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) -{ - return g_pGameRules->ClientConnected( pEdict, pszName, pszAddress, reject, maxrejectlen ); -} - -//----------------------------------------------------------------------------- -// Purpose: Called when a player is fully active (i.e. ready to receive messages) -// Input : *pEntity - the player -//----------------------------------------------------------------------------- -void CServerGameClients::ClientActive( edict_t *pEdict, bool bLoadGame ) -{ - MDLCACHE_CRITICAL_SECTION(); - - ::ClientActive( pEdict, bLoadGame ); - - // If we just loaded from a save file, call OnRestore on valid entities - EndRestoreEntities(); - - if ( gpGlobals->eLoadType != MapLoad_LoadGame ) - { - // notify all entities that the player is now in the game - for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) ) - { - pEntity->PostClientActive(); - } - } - - SimThink_SortThinkList(); - // Tell the sound controller to check looping sounds - CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - CSoundEnvelopeController::GetController().CheckLoopingSoundsForPlayer( pPlayer ); - SceneManager_ClientActive( pPlayer ); -} - -//----------------------------------------------------------------------------- -// Purpose: called when a player disconnects from a server -// Input : *pEdict - the player -//----------------------------------------------------------------------------- -void CServerGameClients::ClientDisconnect( edict_t *pEdict ) -{ - extern bool g_fGameOver; - - CBasePlayer *player = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - if ( player ) - { - if ( !g_fGameOver ) - { - player->SetMaxSpeed( 0.0f ); - - CSound *pSound; - pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEdict ) ); - { - // since this client isn't around to think anymore, reset their sound. - if ( pSound ) - { - pSound->Reset(); - } - } - - // since the edict doesn't get deleted, fix it so it doesn't interfere. - player->RemoveFlag( FL_AIMTARGET ); // don't attract autoaim - player->AddFlag( FL_DONTTOUCH ); // stop it touching anything - player->AddFlag( FL_NOTARGET ); // stop NPCs noticing it - player->AddSolidFlags( FSOLID_NOT_SOLID ); // nonsolid - - if ( g_pGameRules ) - { - g_pGameRules->ClientDisconnected( pEdict ); - } - } - - // Make sure all Untouch()'s are called for this client leaving - CBaseEntity::PhysicsRemoveTouchedList( player ); - CBaseEntity::PhysicsRemoveGroundList( player ); - -#if !defined( NO_ENTITY_PREDICTION ) - // Make sure anything we "own" is simulated by the server from now on - player->ClearPlayerSimulationList(); -#endif - } -} - -void CServerGameClients::ClientPutInServer( edict_t *pEntity, const char *playername ) -{ - if ( g_pClientPutInServerOverride ) - g_pClientPutInServerOverride( pEntity, playername ); - else - ::ClientPutInServer( pEntity, playername ); -} - -void CServerGameClients::ClientCommand( edict_t *pEntity ) -{ - CBasePlayer *player = ToBasePlayer( GetContainingEntity( pEntity ) ); - ::ClientCommand(player); -} - -//----------------------------------------------------------------------------- -// Purpose: called after the player changes userinfo - gives dll a chance to modify -// it before it gets sent into the rest of the engine-> -// Input : *pEdict - the player -// *infobuffer - their infobuffer -//----------------------------------------------------------------------------- -void CServerGameClients::ClientSettingsChanged( edict_t *pEdict ) -{ - // Is the client spawned yet? - if ( !pEdict->GetUnknown() ) - return; - - CBasePlayer *player = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - - if ( !player ) - return; - -#define QUICKGETCVARVALUE(v) (engine->GetClientConVarValue( player->entindex(), v )) - - // get network setting for prediction & lag compensation - player->m_nUpdateRate = Q_atoi( QUICKGETCVARVALUE("cl_updaterate") ); - - bool useInterpolation = Q_atoi( QUICKGETCVARVALUE("cl_interpolate") ) != 0; - - if ( useInterpolation ) - { - player->m_fLerpTime = Q_atof( QUICKGETCVARVALUE("cl_interp") ); - } - else - { - player->m_fLerpTime = 0.0f; - } - -#if !defined( NO_ENTITY_PREDICTION ) - bool usePrediction = Q_atoi( QUICKGETCVARVALUE("cl_predict")) != 0; - - if ( usePrediction ) - { - player->m_bPredictWeapons = Q_atoi( QUICKGETCVARVALUE("cl_predictweapons")) != 0; - player->m_bLagCompensation = Q_atoi( QUICKGETCVARVALUE("cl_lagcompensation")) != 0; - } - else -#endif - { - player->m_bPredictWeapons = false; - player->m_bLagCompensation = false; - } - - -#undef QUICKGETCVARVALUE - - g_pGameRules->ClientSettingsChanged( player ); -} - - -//----------------------------------------------------------------------------- -// Purpose: A client can have a separate "view entity" indicating that his/her view should depend on the origin of that -// view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current -// entity's origin is used. Either is offset by the m_vecViewOffset to get the eye position. -// From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could -// override the actual PAS or PVS values, or use a different origin. -// NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame -// Input : *pViewEntity - -// *pClient - -// **pvs - -// **pas - -//----------------------------------------------------------------------------- -void CServerGameClients::ClientSetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char *pvs, int pvssize ) -{ - Vector org; - - // Reset the PVS!!! - engine->ResetPVS( pvs, pvssize ); - - // Find the client's PVS - CBaseEntity *pVE = NULL; - if ( pViewEntity ) - { - pVE = GetContainingEntity( pViewEntity ); - // If we have a viewentity, it overrides the player's origin - if ( pVE ) - { - org = pVE->EyePosition(); - engine->AddOriginToPVS( org ); - } - } - - float fovDistanceAdjustFactor = 1; - - CBasePlayer *pPlayer = ( CBasePlayer * )GetContainingEntity( pClient ); - if ( pPlayer ) - { - org = pPlayer->EyePosition(); - pPlayer->SetupVisibility( pVE, pvs, pvssize ); - UTIL_SetClientVisibilityPVS( pClient, pvs, pvssize ); - fovDistanceAdjustFactor = pPlayer->GetFOVDistanceAdjustFactor(); - } - - unsigned char portalBits[MAX_AREA_PORTAL_STATE_BYTES]; - memset( portalBits, 0, sizeof( portalBits ) ); - - int portalNums[512]; - int isOpen[512]; - int iOutPortal = 0; - - for( unsigned short i = g_AreaPortals.Head(); i != g_AreaPortals.InvalidIndex(); i = g_AreaPortals.Next(i) ) - { - CFuncAreaPortalBase *pCur = g_AreaPortals[i]; - - bool bIsOpenOnClient = true; - - // Update our array of which portals are open and flush it if necessary. - portalNums[iOutPortal] = pCur->m_portalNumber; - isOpen[iOutPortal] = pCur->UpdateVisibility( org, fovDistanceAdjustFactor, bIsOpenOnClient ); - ++iOutPortal; - if ( iOutPortal >= ARRAYSIZE( portalNums ) ) - { - engine->SetAreaPortalStates( portalNums, isOpen, iOutPortal ); - iOutPortal = 0; - } - - // Version 0 portals (ie: shipping Half-Life 2 era) are always treated as open - // for purposes of the m_chAreaPortalBits array on the client. - if ( pCur->m_iPortalVersion == 0 ) - bIsOpenOnClient = true; - - if ( bIsOpenOnClient ) - { - if ( pCur->m_portalNumber < 0 ) - continue; - else if ( pCur->m_portalNumber >= sizeof( portalBits ) * 8 ) - Error( "ClientSetupVisibility: portal number (%d) too large", pCur->m_portalNumber ); - else - portalBits[pCur->m_portalNumber >> 3] |= (1 << (pCur->m_portalNumber & 7)); - } - } - - // Flush the remaining areaportal states. - engine->SetAreaPortalStates( portalNums, isOpen, iOutPortal ); - - // Update the area bits that get sent to the client. - pPlayer->m_Local.UpdateAreaBits( pPlayer, portalBits ); -} - - - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *player - -// *buf - -// numcmds - -// totalcmds - -// dropped_packets - -// ignore - -// paused - -// Output : float -//----------------------------------------------------------------------------- -float CServerGameClients::ProcessUsercmds( edict_t *player, bf_read *buf, int numcmds, int totalcmds, - int dropped_packets, bool ignore, bool paused ) -{ - int i; - CUserCmd *from, *to; - - // We track last three command in case we drop some - // packets but get them back. - CUserCmd cmds[ CMD_MAXBACKUP ]; - - CUserCmd cmdNull; // For delta compression - - Assert( numcmds >= 0 ); - Assert( ( totalcmds - numcmds ) >= 0 ); - - CBasePlayer *pPlayer = NULL; - CBaseEntity *pEnt = CBaseEntity::Instance(player); - if ( pEnt && pEnt->IsPlayer() ) - { - pPlayer = static_cast< CBasePlayer * >( pEnt ); - } - // Too many commands? - if ( totalcmds < 0 || totalcmds >= ( CMD_MAXBACKUP - 1 ) ) - { - const char *name = "unknown"; - if ( pPlayer ) - { - name = pPlayer->GetPlayerName(); - } - - Msg("CBasePlayer::ProcessUsercmds: too many cmds %i sent for player %s\n", totalcmds, name ); - // FIXME: Need a way to drop the client from here - //SV_DropClient ( host_client, false, "CMD_MAXBACKUP hit" ); - buf->SetOverflowFlag(); - return 0.0f; - } - - // Initialize for reading delta compressed usercmds - cmdNull.Reset(); - from = &cmdNull; - for ( i = totalcmds - 1; i >= 0; i-- ) - { - to = &cmds[ i ]; - ReadUsercmd( buf, to, from ); - from = to; - } - - // Client not fully connected or server has gone inactive or is paused, just ignore - if ( ignore || !pPlayer ) - { - return 0.0f; - } - - MDLCACHE_CRITICAL_SECTION(); - pPlayer->ProcessUsercmds( cmds, numcmds, totalcmds, dropped_packets, paused ); - - return TICK_INTERVAL; -} - - -void CServerGameClients::PostClientMessagesSent( void ) -{ - VPROF("CServerGameClients::PostClient"); - gEntList.PostClientMessagesSent(); -} - -// Sets the client index for the client who typed the command into his/her console -void CServerGameClients::SetCommandClient( int index ) -{ - g_nCommandClientIndex = index; -} - -int CServerGameClients::GetReplayDelay( edict_t *pEdict, int &entity ) -{ - CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - - if ( !pPlayer ) - return 0; - - entity = pPlayer->GetReplayEntity(); - - return pPlayer->GetDelayTicks(); -} - - -//----------------------------------------------------------------------------- -// The client's userinfo data lump has changed -//----------------------------------------------------------------------------- -void CServerGameClients::ClientEarPosition( edict_t *pEdict, Vector *pEarOrigin ) -{ - CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - if (pPlayer) - { - *pEarOrigin = pPlayer->EarPosition(); - } - else - { - // Shouldn't happen - Assert(0); - *pEarOrigin = vec3_origin; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *player - -// Output : CPlayerState -//----------------------------------------------------------------------------- -CPlayerState *CServerGameClients::GetPlayerState( edict_t *player ) -{ - // Is the client spawned yet? - if ( !player || !player->GetUnknown() ) - return NULL; - - CBasePlayer *pBasePlayer = ( CBasePlayer * )CBaseEntity::Instance( player ); - if ( !pBasePlayer ) - return NULL; - - return &pBasePlayer->pl; -} - -//----------------------------------------------------------------------------- -// Purpose: Anything this game .dll wants to add to the bug reporter text (e.g., the entity/model under the picker crosshair) -// can be added here -// Input : *buf - -// buflen - -//----------------------------------------------------------------------------- -void CServerGameClients::GetBugReportInfo( char *buf, int buflen ) -{ - recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ]; - int num; - int i; - - buf[ 0 ] = 0; - - if ( gpGlobals->maxClients == 1 ) - { - CBaseEntity *ent = FindPickerEntity( UTIL_PlayerByIndex(1) ); - if ( ent ) - { - Q_snprintf( buf, buflen, "Picker %i/%s - ent %s model %s\n", - ent->entindex(), - ent->GetClassname(), - STRING( ent->GetEntityName() ), - ent->GetModelName() ); - } - - // get any sounds that were spoken by NPCs recently - num = GetRecentNPCSpeech( speech ); - if ( num > 0 ) - { - Q_snprintf( buf, buflen, "%sRecent NPC speech:\n", buf ); - for( i = 0; i < num; i++ ) - { - Q_snprintf( buf, buflen, "%s time: %6.3f sound name: %s scene: %s\n", buf, speech[ i ].time, speech[ i ].name, speech[ i ].sceneName ); - } - Q_snprintf( buf, buflen, "%sCurrent time: %6.3f\n", buf, gpGlobals->curtime ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -static bf_write *g_pMsgBuffer = NULL; - -void EntityMessageBegin( CBaseEntity * entity, bool reliable /*= false*/ ) -{ - Assert( !g_pMsgBuffer ); - - Assert ( entity ); - - g_pMsgBuffer = engine->EntityMessageBegin( entity->entindex(), entity->GetServerClass(), reliable ); -} - -void UserMessageBegin( IRecipientFilter& filter, const char *messagename ) -{ - Assert( !g_pMsgBuffer ); - - Assert( messagename ); - - int msg_type = usermessages->LookupUserMessage( messagename ); - - if ( msg_type == -1 ) - { - Error( "UserMessageBegin: Unregistered message '%s'\n", messagename ); - } - - g_pMsgBuffer = engine->UserMessageBegin( &filter, msg_type ); -} - -void MessageEnd( void ) -{ - Assert( g_pMsgBuffer ); - - engine->MessageEnd(); - - g_pMsgBuffer = NULL; -} - -void MessageWriteByte( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WRITE_BYTE called with no active message\n" ); - - g_pMsgBuffer->WriteByte( iValue ); -} - -void MessageWriteChar( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WRITE_CHAR called with no active message\n" ); - - g_pMsgBuffer->WriteChar( iValue ); -} - -void MessageWriteShort( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WRITE_SHORT called with no active message\n" ); - - g_pMsgBuffer->WriteShort( iValue ); -} - -void MessageWriteWord( int iValue ) -{ - if (!g_pMsgBuffer) - Error( "WRITE_WORD called with no active message\n" ); - - g_pMsgBuffer->WriteWord( iValue ); -} - -void MessageWriteLong( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WriteLong called with no active message\n" ); - - g_pMsgBuffer->WriteLong( iValue ); -} - -void MessageWriteFloat( float flValue) -{ - if (!g_pMsgBuffer) - Error( "WriteFloat called with no active message\n" ); - - g_pMsgBuffer->WriteFloat( flValue ); -} - -void MessageWriteAngle( float flValue) -{ - if (!g_pMsgBuffer) - Error( "WriteAngle called with no active message\n" ); - - g_pMsgBuffer->WriteBitAngle( flValue, 8 ); -} - -void MessageWriteCoord( float flValue) -{ - if (!g_pMsgBuffer) - Error( "WriteCoord called with no active message\n" ); - - g_pMsgBuffer->WriteBitCoord( flValue ); -} - -void MessageWriteVec3Coord( const Vector& rgflValue) -{ - if (!g_pMsgBuffer) - Error( "WriteVec3Coord called with no active message\n" ); - - g_pMsgBuffer->WriteBitVec3Coord( rgflValue ); -} - -void MessageWriteVec3Normal( const Vector& rgflValue) -{ - if (!g_pMsgBuffer) - Error( "WriteVec3Normal called with no active message\n" ); - - g_pMsgBuffer->WriteBitVec3Normal( rgflValue ); -} - -void MessageWriteAngles( const QAngle& rgflValue) -{ - if (!g_pMsgBuffer) - Error( "WriteVec3Normal called with no active message\n" ); - - g_pMsgBuffer->WriteBitAngles( rgflValue ); -} - -void MessageWriteString( const char *sz ) -{ - if (!g_pMsgBuffer) - Error( "WriteString called with no active message\n" ); - - g_pMsgBuffer->WriteString( sz ); -} - -void MessageWriteEntity( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WriteEntity called with no active message\n" ); - - g_pMsgBuffer->WriteShort( iValue ); -} - -void MessageWriteEHandle( CBaseEntity *pEntity ) -{ - if (!g_pMsgBuffer) - Error( "WriteEHandle called with no active message\n" ); - - long iEncodedEHandle; - - if( pEntity ) - { - EHANDLE hEnt = pEntity; - - int iSerialNum = hEnt.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1; - iEncodedEHandle = hEnt.GetEntryIndex() | (iSerialNum << MAX_EDICT_BITS); - } - else - { - iEncodedEHandle = INVALID_NETWORKED_EHANDLE_VALUE; - } - - g_pMsgBuffer->WriteLong( iEncodedEHandle ); -} - -// bitwise -void MessageWriteBool( bool bValue ) -{ - if (!g_pMsgBuffer) - Error( "WriteBool called with no active message\n" ); - - g_pMsgBuffer->WriteOneBit( bValue ? 1 : 0 ); -} - -void MessageWriteUBitLong( unsigned int data, int numbits ) -{ - if (!g_pMsgBuffer) - Error( "WriteUBitLong called with no active message\n" ); - - g_pMsgBuffer->WriteUBitLong( data, numbits ); -} - -void MessageWriteSBitLong( int data, int numbits ) -{ - if (!g_pMsgBuffer) - Error( "WriteSBitLong called with no active message\n" ); - - g_pMsgBuffer->WriteSBitLong( data, numbits ); -} - -void MessageWriteBits( const void *pIn, int nBits ) -{ - if (!g_pMsgBuffer) - Error( "WriteBits called with no active message\n" ); - - g_pMsgBuffer->WriteBits( pIn, nBits ); -} - -class CServerDLLSharedAppSystems : public IServerDLLSharedAppSystems -{ -public: - CServerDLLSharedAppSystems() - { - AddAppSystem( "soundemittersystem", SOUNDEMITTERSYSTEM_INTERFACE_VERSION ); - AddAppSystem( "scenefilecache", SCENE_FILE_CACHE_INTERFACE_VERSION ); - } - - virtual int Count() - { - return m_Systems.Count(); - } - virtual char const *GetDllName( int idx ) - { - return m_Systems[ idx ].m_pModuleName; - } - virtual char const *GetInterfaceName( int idx ) - { - return m_Systems[ idx ].m_pInterfaceName; - } -private: - void AddAppSystem( char const *moduleName, char const *interfaceName ) - { - AppSystemInfo_t sys; - sys.m_pModuleName = moduleName; - sys.m_pInterfaceName = interfaceName; - m_Systems.AddToTail( sys ); - } - - CUtlVector< AppSystemInfo_t > m_Systems; -}; - -EXPOSE_SINGLE_INTERFACE( CServerDLLSharedAppSystems, IServerDLLSharedAppSystems, SERVER_DLL_SHARED_APPSYSTEMS ); - - +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: encapsulates and implements all the accessing of the game dll from external +// sources (only the engine at the time of writing) +// This files ONLY contains functions and data necessary to build an interface +// to external modules +//=============================================================================// + +#include "cbase.h" +#include "gamestringpool.h" +#include "mapentities_shared.h" +#include "game.h" +#include "entityapi.h" +#include "client.h" +#include "saverestore.h" +#include "entitylist.h" +#include "gamerules.h" +#include "soundent.h" +#include "player.h" +#include "server_class.h" +#include "ai_node.h" +#include "ai_link.h" +#include "ai_saverestore.h" +#include "ai_networkmanager.h" +#include "ndebugoverlay.h" +#include "ivoiceserver.h" +#include +#include "movehelper_server.h" +#include "networkstringtable_gamedll.h" +#include "filesystem.h" +#include "terrainmodmgr.h" +#include "func_areaportalwindow.h" +#include "igamesystem.h" +#include "init_factory.h" +#include "vstdlib/random.h" +#include "env_wind_shared.h" +#include "engine/IEngineSound.h" +#include "ispatialpartition.h" +#include "textstatsmgr.h" +#include "bitbuf.h" +#include "saverestoretypes.h" +#include "physics_saverestore.h" +#include "tier0/vprof.h" +#include "effect_dispatch_data.h" +#include "engine/IStaticPropMgr.h" +#include "TemplateEntities.h" +#include "ai_speech.h" +#include "soundenvelope.h" +#include "usermessages.h" +#include "physics.h" +#include "mapentities.h" +#include "igameevents.h" +#include "EventLog.h" +#include "datacache/idatacache.h" +#include "engine/ivdebugoverlay.h" +#include "shareddefs.h" +#include "props.h" +#include "timedeventmgr.h" +#include "gameinterface.h" +#include "eventqueue.h" +#include "hltvdirector.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "nav_mesh.h" +#include "AI_ResponseSystem.h" +#include "saverestore_stringtable.h" +#include "util.h" +#include "vstdlib/ICommandLine.h" +#include "datacache/imdlcache.h" +#include "engine/iserverplugin.h" +#ifdef _WIN32 +#include "ienginevgui.h" +#include "vgui_gamedll_int.h" +#include "vgui_controls/AnimationController.h" +#endif +#include "ragdoll_shared.h" +#include "toolframework/iserverenginetools.h" +#include "sceneentity.h" +#include "appframework/IAppSystemGroup.h" +#include "scenefilecache/ISceneFileCache.h" + +#if !defined( _RETAIL ) +#ifdef _XBOX +#include "xbox/xbox_platform.h" +#include "xbox/xbox_win32stubs.h" +#include "xbox/xbox_core.h" +#endif +#endif + +#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out +#include "bot/bot.h" +#endif + +IUploadGameStats *gamestatsuploader = NULL; + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CTimedEventMgr g_NetworkPropertyEventMgr; + +ISaveRestoreBlockHandler *GetEventQueueSaveRestoreBlockHandler(); +ISaveRestoreBlockHandler *GetCommentarySaveRestoreBlockHandler(); + +// Engine interfaces. +IVEngineServer *engine = NULL; +IVoiceServer *g_pVoiceServer = NULL; +ICvar *cvar = NULL; +#if !defined(_STATIC_LINKED) +IFileSystem *filesystem = NULL; +#else +extern IFileSystem *filesystem; +#endif +INetworkStringTableContainer *networkstringtable = NULL; +IStaticPropMgrServer *staticpropmgr = NULL; +IUniformRandomStream *random = NULL; +IEngineSound *enginesound = NULL; +ISpatialPartition *partition = NULL; +IVModelInfo *modelinfo = NULL; +IEngineTrace *enginetrace = NULL; +IGameEventManager2 *gameeventmanager = NULL; +IDataCache *datacache = NULL; +IVDebugOverlay * debugoverlay = NULL; +ISoundEmitterSystemBase *soundemitterbase = NULL; +IMDLCache *mdlcache = NULL; +IServerPluginHelpers *serverpluginhelpers = NULL; +#ifdef _WIN32 +IEngineVGui *enginevgui = NULL; +#endif +IServerEngineTools *serverenginetools = NULL; +ISceneFileCache *scenefilecache = NULL; + +IGameSystem *SoundEmitterSystem(); + +bool SceneCacheInit(); +void SceneCacheShutdown(); +bool ModelSoundsCacheInit(); +void ModelSoundsCacheShutdown(); + +void SceneManager_ClientActive( CBasePlayer *player ); + +class IMaterialSystem; +class IStudioRender; + +#ifdef _DEBUG +static ConVar s_UseNetworkVars( "UseNetworkVars", "1", FCVAR_CHEAT, "For profiling, toggle network vars." ); +#endif + +extern ConVar sv_noclipduringpause; +ConVar sv_massreport( "sv_massreport", "0" ); + +ConVar sv_autosave( "sv_autosave", "1", 0, "Set to 1 to save game on level transition. Does not affect autosave triggers." ); +ConVar *sv_maxreplay = NULL; + +// String tables +INetworkStringTable *g_pStringTableEffectDispatch = NULL; +INetworkStringTable *g_pStringTableVguiScreen = NULL; +INetworkStringTable *g_pStringTableMaterials = NULL; +INetworkStringTable *g_pStringTableInfoPanel = NULL; +INetworkStringTable *g_pStringTableClientSideChoreoScenes = NULL; + +CStringTableSaveRestoreOps g_VguiScreenStringOps; + +// Holds global variables shared between engine and game. +CGlobalVars *gpGlobals; +edict_t *g_pDebugEdictBase = 0; +static int g_nCommandClientIndex = 0; + +static ConVar sv_showhitboxes( "sv_showhitboxes", "-1", FCVAR_CHEAT, "Send server-side hitboxes for specified entity to client (NOTE: this uses lots of bandwidth, use on listen server only)." ); + +void PrecachePointTemplates(); + +static ClientPutInServerOverrideFn g_pClientPutInServerOverride = NULL; +static void UpdateChapterRestrictions( const char *mapname ); + + +#if !defined( _XBOX ) // Don't doubly define this symbol. +CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL; + +#endif + +IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() +{ + return engine->GetChangeAccessor( (const edict_t *)this ); +} + +const IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() const +{ + return engine->GetChangeAccessor( (const edict_t *)this ); +} + +const char *GetHintTypeDescription( CAI_Hint *pHint ); + +void ClientPutInServerOverride( ClientPutInServerOverrideFn fn ) +{ + g_pClientPutInServerOverride = fn; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int UTIL_GetCommandClientIndex( void ) +{ + // -1 == unknown,dedicated server console + // 0 == player 1 + + // Convert to 1 based offset + return (g_nCommandClientIndex+1); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CBasePlayer +//----------------------------------------------------------------------------- +CBasePlayer *UTIL_GetCommandClient( void ) +{ + int idx = UTIL_GetCommandClientIndex(); + if ( idx > 0 ) + { + return UTIL_PlayerByIndex( idx ); + } + + // HLDS console issued command + return NULL; +} + +extern void InitializeCvars( void ); + +CBaseEntity* FindPickerEntity( CBasePlayer* pPlayer ); +CAI_Node* FindPickerAINode( CBasePlayer* pPlayer, NodeType_e nNodeType ); +CAI_Link* FindPickerAILink( CBasePlayer* pPlayer ); +float GetFloorZ(const Vector &origin); +void UpdateAllClientData( void ); +void DrawMessageEntities(); + +#include "ai_network.h" + +// For now just using one big AI network +extern ConVar think_limit; + + +#if 0 +//----------------------------------------------------------------------------- +// Purpose: Draw output overlays for any measure sections +// Input : +//----------------------------------------------------------------------------- +void DrawMeasuredSections(void) +{ + int row = 1; + float rowheight = 0.025; + + CMeasureSection *p = CMeasureSection::GetList(); + while ( p ) + { + char str[256]; + Q_snprintf(str,sizeof(str),"%s",p->GetName()); + NDebugOverlay::ScreenText( 0.01,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); + + Q_snprintf(str,sizeof(str),"%5.2f\n",p->GetTime().GetMillisecondsF()); + //Q_snprintf(str,sizeof(str),"%3.3f\n",p->GetTime().GetSeconds() * 100.0 / engine->Time()); + NDebugOverlay::ScreenText( 0.28,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); + + Q_snprintf(str,sizeof(str),"%5.2f\n",p->GetMaxTime().GetMillisecondsF()); + //Q_snprintf(str,sizeof(str),"%3.3f\n",p->GetTime().GetSeconds() * 100.0 / engine->Time()); + NDebugOverlay::ScreenText( 0.34,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); + + + row++; + + p = p->GetNext(); + } + + bool sort_reset = false; + + // Time to redo sort? + if ( measure_resort.GetFloat() > 0.0 && + engine->Time() >= CMeasureSection::m_dNextResort ) + { + // Redo it + CMeasureSection::SortSections(); + // Set next time + CMeasureSection::m_dNextResort = engine->Time() + measure_resort.GetFloat(); + // Flag to reset sort accumulator, too + sort_reset = true; + } + + // Iterate through the sections now + p = CMeasureSection::GetList(); + while ( p ) + { + // Update max + p->UpdateMax(); + + // Reset regular accum. + p->Reset(); + // Reset sort accum less often + if ( sort_reset ) + { + p->SortReset(); + } + p = p->GetNext(); + } + +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DrawAllDebugOverlays( void ) +{ + // If in debug select mode print the selection entities name or classname + if (CBaseEntity::m_bInDebugSelect) + { + CBasePlayer* pPlayer = UTIL_PlayerByIndex( CBaseEntity::m_nDebugPlayer ); + + if (pPlayer) + { + // First try to trace a hull to an entity + CBaseEntity *pEntity = FindPickerEntity( pPlayer ); + + if ( pEntity ) + { + pEntity->DrawDebugTextOverlays(); + pEntity->DrawBBoxOverlay(); + pEntity->SendDebugPivotOverlay(); + } + } + } + + // -------------------------------------------------------- + // Draw debug overlay lines + // -------------------------------------------------------- + UTIL_DrawOverlayLines(); + + // ------------------------------------------------------------------------ + // If in wc_edit mode draw a box to highlight which node I'm looking at + // ------------------------------------------------------------------------ + if (engine->IsInEditMode()) + { + CBasePlayer* pPlayer = UTIL_PlayerByIndex( CBaseEntity::m_nDebugPlayer ); + if (pPlayer) + { + if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode) + { + CAI_Link* pAILink = FindPickerAILink(pPlayer); + if (pAILink) + { + // For now just using one big AI network + Vector startPos = g_pBigAINet->GetNode(pAILink->m_iSrcID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); + Vector endPos = g_pBigAINet->GetNode(pAILink->m_iDestID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); + Vector linkDir = startPos-endPos; + float linkLen = VectorNormalize( linkDir ); + + // Draw in green if link that's been turned off + if (pAILink->m_LinkInfo & bits_LINK_OFF) + { + NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 0,255,0,40,0); + } + else + { + NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 255,0,0,40,0); + } + } + } + else + { + CAI_Node* pAINode; + if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) + { + pAINode = FindPickerAINode(pPlayer,NODE_AIR); + } + else + { + pAINode = FindPickerAINode(pPlayer,NODE_GROUND); + } + + if (pAINode) + { + Vector vecPos = pAINode->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); + NDebugOverlay::Box( vecPos, Vector(-8,-8,-8), Vector(8,8,8), 255,0,0,40,0); + + if ( pAINode->GetHint() ) + { + CBaseEntity *pEnt = (CBaseEntity *)pAINode->GetHint(); + if ( pEnt->GetEntityName() != NULL_STRING ) + { + NDebugOverlay::Text( vecPos + Vector(0,0,6), STRING(pEnt->GetEntityName()), false, 0 ); + } + NDebugOverlay::Text( vecPos, GetHintTypeDescription( pAINode->GetHint() ), false, 0 ); + } + } + } + // ------------------------------------ + // If in air edit mode draw guide line + // ------------------------------------ + if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) + { + UTIL_DrawPositioningOverlay(g_pAINetworkManager->GetEditOps()->m_flAirEditDistance); + } + else + { + NDebugOverlay::DrawGroundCrossHairOverlay(); + } + } + } + + // For not just using one big AI Network + if ( g_pAINetworkManager ) + { + g_pAINetworkManager->GetEditOps()->DrawAINetworkOverlay(); + } + + // PERFORMANCE: only do this in developer mode + if ( g_pDeveloper->GetInt() ) + { + // iterate through all objects for debug overlays + const CEntInfo *pInfo = gEntList.FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + // HACKHACK: to flag off these calls + if ( ent->m_debugOverlays || ent->m_pTimedOverlay ) + { + MDLCACHE_CRITICAL_SECTION(); + ent->DrawDebugGeometryOverlays(); + } + } + } + + if ( sv_massreport.GetInt() ) + { + // iterate through all objects for debug overlays + const CEntInfo *pInfo = gEntList.FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + if (!ent->VPhysicsGetObject()) + continue; + + char tempstr[512]; + Q_snprintf(tempstr, sizeof(tempstr),"%s: Mass: %.2f kg / %.2f lb (%s)", + STRING(ent->GetModelName()), ent->VPhysicsGetObject()->GetMass(), + kg2lbs(ent->VPhysicsGetObject()->GetMass()), + GetMassEquivalent(ent->VPhysicsGetObject()->GetMass())); + ent->EntityText(0, tempstr, 0); + } + } + + // A hack to draw point_message entities w/o developer required + DrawMessageEntities(); +} + +CServerGameDLL g_ServerGameDLL; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameDLL, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL, g_ServerGameDLL); + +bool CServerGameDLL::DLLInit(CreateInterfaceFn engineFactory, + CreateInterfaceFn physicsFactory, CreateInterfaceFn fileSystemFactory, + CGlobalVars *pGlobals) +{ + // init each (seperated for ease of debugging) + if ( (engine = (IVEngineServer*)engineFactory(INTERFACEVERSION_VENGINESERVER, NULL)) == NULL ) + return false; + if ( (g_pVoiceServer = (IVoiceServer*)engineFactory(INTERFACEVERSION_VOICESERVER, NULL)) == NULL ) + return false; + if ( (cvar = (ICvar*)engineFactory(VENGINE_CVAR_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (networkstringtable = (INetworkStringTableContainer *)engineFactory(INTERFACENAME_NETWORKSTRINGTABLESERVER,NULL)) == NULL ) + return false; + if ( (staticpropmgr = (IStaticPropMgrServer *)engineFactory(INTERFACEVERSION_STATICPROPMGR_SERVER,NULL)) == NULL ) + return false; + if ( (random = (IUniformRandomStream *)engineFactory(VENGINE_SERVER_RANDOM_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (enginesound = (IEngineSound *)engineFactory(IENGINESOUND_SERVER_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (partition = (ISpatialPartition *)engineFactory(INTERFACEVERSION_SPATIALPARTITION, NULL)) == NULL ) + return false; + if ( (modelinfo = (IVModelInfo *)engineFactory(VMODELINFO_SERVER_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (enginetrace = (IEngineTrace *)engineFactory(INTERFACEVERSION_ENGINETRACE_SERVER,NULL)) == NULL ) + return false; + if ( (filesystem = (IFileSystem *)fileSystemFactory(FILESYSTEM_INTERFACE_VERSION,NULL)) == NULL ) + return false; + if ( (gameeventmanager = (IGameEventManager2 *)engineFactory(INTERFACEVERSION_GAMEEVENTSMANAGER2,NULL)) == NULL ) + return false; + if ( (datacache = (IDataCache*)engineFactory(DATACACHE_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (soundemitterbase = (ISoundEmitterSystemBase *)engineFactory(SOUNDEMITTERSYSTEM_INTERFACE_VERSION, NULL)) == NULL ) + return false; +#ifndef _XBOX + if ( (gamestatsuploader = (IUploadGameStats *)engineFactory( INTERFACEVERSION_UPLOADGAMESTATS, NULL )) == NULL ) + return false; +#endif + if ( (mdlcache = (IMDLCache*)engineFactory( MDLCACHE_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (serverpluginhelpers = (IServerPluginHelpers *)engineFactory(INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL)) == NULL ) + return false; + if ( (scenefilecache = (ISceneFileCache *)engineFactory( SCENE_FILE_CACHE_INTERFACE_VERSION, NULL )) == NULL ) + return false; + + // If not running dedicated, grab the engine vgui interface + if ( !engine->IsDedicatedServer() ) + { +#ifdef _WIN32 + if ( ( enginevgui = ( IEngineVGui * )engineFactory(VENGINE_VGUI_VERSION, NULL)) == NULL ) + return false; + + // This interface is optional, and is only valid when running with -tools + serverenginetools = ( IServerEngineTools * )engineFactory( VSERVERENGINETOOLS_INTERFACE_VERSION, NULL ); +#endif + } + + // Yes, both the client and game .dlls will try to Connect, the soundemittersystem.dll will handle this gracefully + if ( !soundemitterbase->Connect( engineFactory ) ) + return false; + + // cache the globals + gpGlobals = pGlobals; + + g_pSharedChangeInfo = engine->GetSharedEdictChangeInfo(); + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2 ); + + // save these in case other system inits need them + factorylist_t factories; + factories.engineFactory = engineFactory; + factories.fileSystemFactory = fileSystemFactory; + factories.physicsFactory = physicsFactory; + FactoryList_Store( factories ); + + // load used game events + gameeventmanager->LoadEventsFromFile("resource/gameevents.res"); + + // init the cvar list first in case inits want to reference them + InitializeCvars(); + + sv_cheats = (ConVar*) ConCommandBase::FindCommand( "sv_cheats" ); + if ( !sv_cheats ) + return false; + + sv_maxreplay = (ConVar*) ConCommandBase::FindCommand( "sv_maxreplay" ); + + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEntitySaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetPhysSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetAISaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetTemplateSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetDefaultResponseSystemSaveRestoreBlockHandler() ); +#if !defined( _CONSOLE ) + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetCommentarySaveRestoreBlockHandler() ); +#endif + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); + + // The string system must init first + shutdown last + IGameSystem::Add( GameStringSystem() ); + + // Physics must occur before the sound envelope manager + IGameSystem::Add( PhysicsGameSystem() ); + + // Add game log system + IGameSystem::Add( GameLogSystem() ); +#ifndef _XBOX + // Add HLTV director + IGameSystem::Add( HLTVDirectorSystem() ); +#endif + // Add sound emitter + IGameSystem::Add( SoundEmitterSystem() ); + +#ifdef _WIN32 + // Startup vgui + if ( enginevgui ) + { + if(!VGui_Startup( engineFactory )) + return false; + } +#endif + + // load Mod specific game events ( MUST be before InitAllSystems() so it can pickup the mod specific events) + gameeventmanager->LoadEventsFromFile("resource/ModEvents.res"); + +#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out + InstallBotControl(); +#endif + + if ( !IGameSystem::InitAllSystems() ) + return false; + + // Due to dependencies, these are not autogamesystems + if ( !SceneCacheInit() ) + { + return false; + } + + // Due to dependencies, these are not autogamesystems + if ( !ModelSoundsCacheInit() ) + { + return false; + } + + // try to get debug overlay, may be NULL if on HLDS + debugoverlay = (IVDebugOverlay *)engineFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL ); + +#ifndef _XBOX + // create the Navigation Mesh interface + TheNavMesh = new CNavMesh; + + // init the gamestatsupload connection + gamestatsuploader->InitConnection(); +#endif + +#if !defined( _RETAIL ) +#if defined( _XBOX ) + XBX_rTimeStampLog( Plat_FloatTime(), "DLLInit finished" ); +#endif +#endif + return true; +} + +void CServerGameDLL::PostInit() +{ +#ifdef _WIN32 + if ( enginevgui ) + { + if ( VGui_PostInit() ) + { + // all good + } + } +#endif +} + +void CServerGameDLL::DLLShutdown( void ) +{ + + // Due to dependencies, these are not autogamesystems + ModelSoundsCacheShutdown(); + SceneCacheShutdown(); + +#if !defined( _CONSOLE ) + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetCommentarySaveRestoreBlockHandler() ); +#endif + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetDefaultResponseSystemSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetTemplateSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetAISaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetPhysSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEntitySaveRestoreBlockHandler() ); + + char *pFilename = g_TextStatsMgr.GetStatsFilename(); + if ( !pFilename || !pFilename[0] ) + { + g_TextStatsMgr.SetStatsFilename( "stats.txt" ); + } + g_TextStatsMgr.WriteFile( filesystem ); + + IGameSystem::ShutdownAllSystems(); + +#ifdef _WIN32 + if ( enginevgui ) + { + VGui_Shutdown(); + } +#endif + +#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out + RemoveBotControl(); +#endif + +#ifndef _XBOX + // destroy the Navigation Mesh interface + if (TheNavMesh) + { + delete TheNavMesh; + TheNavMesh = NULL; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: See shareddefs.h for redefining this. Don't even think about it, though, for HL2. Or you will pay. ywb 9/22/03 +// Output : float +//----------------------------------------------------------------------------- + +float CServerGameDLL::GetTickInterval( void ) const +{ + float tickinterval = DEFAULT_TICK_INTERVAL; + +#if defined( CSTRIKE_DLL ) + // in CS reduce tickrate/sec by defualt + tickinterval *= 2; +#endif + + // override if tick rate specified in command line + if ( CommandLine()->CheckParm( "-tickrate" ) ) + { + float tickrate = CommandLine()->ParmValue( "-tickrate", 0 ); + if ( tickrate > 10 ) + tickinterval = 1.0f / tickrate; + } + + return tickinterval; +} + +// This is called when a new game is started. (restart, map) +bool CServerGameDLL::GameInit( void ) +{ + ResetGlobalState(); + engine->ServerCommand( "exec game.cfg\n" ); + engine->ServerExecute( ); + // clear out any old game's temporary save data +// engine->ClearSaveDir(); + return true; +} + +// This is called when a game ends (server disconnect, death, restart, load) +// NOT on level transitions within a game +void CServerGameDLL::GameShutdown( void ) +{ + ResetGlobalState(); +} + +static bool g_OneWayTransition = false; +void Game_SetOneWayTransition( void ) +{ + g_OneWayTransition = true; +} + +static CUtlVector g_RestoredEntities; +// just for debugging, assert that this is the only time this function is called +static bool g_InRestore = false; + +void AddRestoredEntity( CBaseEntity *pEntity ) +{ + Assert(g_InRestore); + if ( !pEntity ) + return; + + g_RestoredEntities.AddToTail( EHANDLE(pEntity) ); +} + +void EndRestoreEntities() +{ + if ( !g_InRestore ) + return; + + // Call all entities' OnRestore handlers + for ( int i = g_RestoredEntities.Count()-1; i >=0; --i ) + { + CBaseEntity *pEntity = g_RestoredEntities[i].Get(); + if ( pEntity && !pEntity->IsDormant() ) + { + MDLCACHE_CRITICAL_SECTION(); + pEntity->OnRestore(); + } + } + + g_RestoredEntities.Purge(); + + IGameSystem::OnRestoreAllSystems(); + + g_InRestore = false; + gEntList.CleanupDeleteList(); + + // HACKHACK: UNDONE: We need to redesign the main loop with respect to save/load/server activate + g_ServerGameDLL.ServerActivate( NULL, 0, 0 ); + CBaseEntity::SetAllowPrecache( false ); +} + +void BeginRestoreEntities() +{ + if ( g_InRestore ) + { + DevMsg( "BeginRestoreEntities without previous EndRestoreEntities.\n" ); + gEntList.CleanupDeleteList(); + } + g_RestoredEntities.Purge(); + g_InRestore = true; + CBaseEntity::SetAllowPrecache( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: This prevents sv.tickcount/gpGlobals->tickcount from advancing during restore which +// would cause a lot of the NPCs to fast forward their think times to the same +// tick due to some ticks being elapsed during restore where there was no simulation going on +//----------------------------------------------------------------------------- +bool CServerGameDLL::IsRestoring() +{ + return g_InRestore; +} + +// Called any time a new level is started (after GameInit() also on level transitions within a game) +bool CServerGameDLL::LevelInit( const char *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background ) +{ + VPROF("CServerGameDLL::LevelInit"); + ResetWindspeed(); + UpdateChapterRestrictions( pMapName ); + + // IGameSystem::LevelInitPreEntityAllSystems() is called when the world is precached + // That happens either in LoadGameState() or in MapEntity_ParseAllEntities() + if ( loadGame ) + { + BeginRestoreEntities(); + if ( !engine->LoadGameState( pMapName, 1 ) ) + { + MapEntity_ParseAllEntities( pMapEntities ); + } + + if ( pOldLevel ) + { + gpGlobals->eLoadType = MapLoad_Transition; + engine->LoadAdjacentEnts( pOldLevel, pLandmarkName ); + } + else + { + gpGlobals->eLoadType = MapLoad_LoadGame; + } + + if ( g_OneWayTransition ) + { + engine->ClearSaveDirAfterClientLoad(); + } + + if ( pOldLevel && sv_autosave.GetBool() == true ) + { + // This is a single-player style level transition. + // Queue up an autosave one second into the level + CBaseEntity *pAutosave = CBaseEntity::Create( "logic_autosave", vec3_origin, vec3_angle, NULL ); + if ( pAutosave ) + { + g_EventQueue.AddEvent( pAutosave, "Save", 1.0, NULL, NULL ); + g_EventQueue.AddEvent( pAutosave, "Kill", 1.1, NULL, NULL ); + } + } + } + else + { + if ( background ) + { + gpGlobals->eLoadType = MapLoad_Background; + } + else + { + gpGlobals->eLoadType = MapLoad_NewGame; + } + + LevelInit_ParseAllEntities( pMapEntities ); + } + + // Check low violence settings for this map + g_RagdollLVManager.SetLowViolence( pMapName ); + + // Now that all of the active entities have been loaded in, precache any entities who need point_template parameters + // to be parsed (the above code has loaded all point_template entities) + PrecachePointTemplates(); + + // load MOTD from file into stringtable + LoadMessageOfTheDay(); + + // Sometimes an ent will Remove() itself during its precache, so RemoveImmediate won't happen. + // This makes sure those ents get cleaned up. + gEntList.CleanupDeleteList(); + + g_AIFriendliesTalkSemaphore.Release(); + g_AIFoesTalkSemaphore.Release(); + g_OneWayTransition = false; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: called after every level change and load game, iterates through all the +// active entities and gives them a chance to fix up their state +//----------------------------------------------------------------------------- +#ifdef DEBUG +bool g_bReceivedChainedActivate; +bool g_bCheckForChainedActivate; +#define BeginCheckChainedActivate() if (0) ; else { g_bCheckForChainedActivate = true; g_bReceivedChainedActivate = false; } +#define EndCheckChainedActivate( bCheck ) \ + if (0) ; else \ + { \ + if ( bCheck ) \ + { \ + char msg[ 1024 ]; \ + Q_snprintf( msg, sizeof( msg ), "Entity (%i/%s/%s) failed to call base class Activate()\n", pClass->entindex(), pClass->GetClassname(), STRING( pClass->GetEntityName() ) ); \ + AssertMsg( g_bReceivedChainedActivate == true, msg ); \ + } \ + g_bCheckForChainedActivate = false; \ + } +#else +#define BeginCheckChainedActivate() ((void)0) +#define EndCheckChainedActivate( bCheck ) ((void)0) +#endif + +void CServerGameDLL::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + // HACKHACK: UNDONE: We need to redesign the main loop with respect to save/load/server activate + if ( g_InRestore ) + return; + + if ( gEntList.ResetDeleteList() != 0 ) + { + Msg( "ERROR: Entity delete queue not empty on level start!\n" ); + } + + for ( CBaseEntity *pClass = gEntList.FirstEnt(); pClass != NULL; pClass = gEntList.NextEnt(pClass) ) + { + if ( pClass && !pClass->IsDormant() ) + { + MDLCACHE_CRITICAL_SECTION(); + + BeginCheckChainedActivate(); + pClass->Activate(); + + // We don't care if it finished activating if it decided to remove itself. + EndCheckChainedActivate( !( pClass->GetEFlags() & EFL_KILLME ) ); + } + } + + IGameSystem::LevelInitPostEntityAllSystems(); + // No more precaching after PostEntityAllSystems!!! + CBaseEntity::SetAllowPrecache( false ); + + // only display the think limit when the game is run with "developer" mode set + if ( !g_pDeveloper->GetInt() ) + { + think_limit.SetValue( 0 ); + } + +#ifndef _XBOX + // load the Navigation Mesh for this map + TheNavMesh->Load(); +#endif + +#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out + TheBots->ServerActivate(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Called at the start of every game frame +//----------------------------------------------------------------------------- +ConVar trace_report( "trace_report", "0" ); + +void CServerGameDLL::GameFrame( bool simulating ) +{ + VPROF( "CServerGameDLL::GameFrame" ); + + // Don't run frames until fully restored + if ( g_InRestore ) + return; + + static bool skipframe = false; + + // If server is skipping frames, don't run simulation this time through + if ( skipframe ) + { + UpdateAllClientData(); + skipframe = false; + return; + } + + float oldframetime = gpGlobals->frametime; + if ( CBaseEntity::IsSimulatingOnAlternateTicks() ) + { + skipframe = true; + // If we're skipping frames, then the frametime is 2x the normal tick + gpGlobals->frametime *= 2.0f; + } + +#ifdef _DEBUG + // For profiling.. let them enable/disable the networkvar manual mode stuff. + g_bUseNetworkVars = s_UseNetworkVars.GetBool(); +#endif + + extern void GameStartFrame( void ); + extern void ServiceEventQueue( void ); + extern void Physics_RunThinkFunctions( bool simulating ); + + // Delete anything that was marked for deletion + // outside of server frameloop (e.g., in response to concommand) + gEntList.CleanupDeleteList(); + + IGameSystem::FrameUpdatePreEntityThinkAllSystems(); + GameStartFrame(); + +#ifndef _XBOX + TheNavMesh->Update(); + + gamestatsuploader->UpdateConnection(); +#endif + + Physics_RunThinkFunctions( simulating ); + + IGameSystem::FrameUpdatePostEntityThinkAllSystems(); + + // UNDONE: Make these systems IGameSystems and move these calls into FrameUpdatePostEntityThink() + // service event queue, firing off any actions whos time has come + ServiceEventQueue(); + + // free all ents marked in think functions + gEntList.CleanupDeleteList(); + + // FIXME: Should this only occur on the final tick? + UpdateAllClientData(); + + if ( g_pGameRules ) + { + g_pGameRules->EndGameFrame(); + } + + if ( trace_report.GetBool() ) + { + int total = 0, totals[3]; + for ( int i = 0; i < 3; i++ ) + { + totals[i] = enginetrace->GetStatByIndex( i, true ); + if ( totals[i] > 0 ) + { + total += totals[i]; + } + } + + if ( total ) + { + Msg("Trace: %d, contents %d, enumerate %d\n", totals[0], totals[1], totals[2] ); + } + } + + // Any entities that detect network state changes on a timer do it here. + g_NetworkPropertyEventMgr.FireEvents(); + + gpGlobals->frametime = oldframetime; +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame even if not ticking +// Input : simulating - +//----------------------------------------------------------------------------- +void CServerGameDLL::PreClientUpdate( bool simulating ) +{ + if ( !simulating ) + return; + + /* + if (game_speeds.GetInt()) + { + DrawMeasuredSections(); + } + */ + +//#ifdef _DEBUG - allow this in release for now + DrawAllDebugOverlays(); +//#endif + + IGameSystem::PreClientUpdateAllSystems(); + + if ( sv_showhitboxes.GetInt() == -1 ) + return; + + if ( sv_showhitboxes.GetInt() == 0 ) + { + // assume it's text + CBaseEntity *pEntity = NULL; + + while (1) + { + pEntity = gEntList.FindEntityByName( pEntity, sv_showhitboxes.GetString() ); + if ( !pEntity ) + break; + + CBaseAnimating *anim = dynamic_cast< CBaseAnimating * >( pEntity ); + + if (anim) + { + anim->DrawServerHitboxes(); + } + } + return; + } + + CBaseAnimating *anim = dynamic_cast< CBaseAnimating * >( CBaseEntity::Instance( engine->PEntityOfEntIndex( sv_showhitboxes.GetInt() ) ) ); + if ( !anim ) + return; + + anim->DrawServerHitboxes(); +} + +void CServerGameDLL::Think( bool finalTick ) +{ + if ( m_fAutoSaveDangerousTime != 0.0f && m_fAutoSaveDangerousTime < gpGlobals->curtime ) + { + // The safety timer for a dangerous auto save has expired + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + + if ( pPlayer->GetDeathTime() == 0.0f || pPlayer->GetDeathTime() > gpGlobals->curtime ) + { + // The player isn't dead, so make the dangerous auto save safe + engine->ServerCommand( "autosavedangerousissafe\n" ); + } + + m_fAutoSaveDangerousTime = 0.0f; + } +} + + +// Called when a level is shutdown (including changing levels) +void CServerGameDLL::LevelShutdown( void ) +{ + IGameSystem::LevelShutdownPreEntityAllSystems(); + + // YWB: + // This entity pointer is going away now and is corrupting memory on level transitions/restarts + CSoundEnt::ShutdownSoundEnt(); + + gEntList.Clear(); + + IGameSystem::LevelShutdownPostEntityAllSystems(); + + // In case we quit out during initial load + CBaseEntity::SetAllowPrecache( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : ServerClass* +//----------------------------------------------------------------------------- +ServerClass* CServerGameDLL::GetAllServerClasses() +{ + return g_pServerClassHead; +} + + +const char *CServerGameDLL::GetGameDescription( void ) +{ + return ::GetGameDescription(); +} + +void CServerGameDLL::CreateNetworkStringTables( void ) +{ + // Create any shared string tables here (and only here!) + // E.g.: xxx = networkstringtable->CreateStringTable( "SceneStrings", 512 ); + g_pStringTableEffectDispatch = networkstringtable->CreateStringTable( "EffectDispatch", MAX_EFFECT_DISPATCH_STRINGS ); + g_pStringTableVguiScreen = networkstringtable->CreateStringTable( "VguiScreen", MAX_VGUI_SCREEN_STRINGS ); + g_pStringTableMaterials = networkstringtable->CreateStringTable( "Materials", MAX_MATERIAL_STRINGS ); + g_pStringTableInfoPanel = networkstringtable->CreateStringTable( "InfoPanel", MAX_INFOPANEL_STRINGS ); + g_pStringTableClientSideChoreoScenes = networkstringtable->CreateStringTable( "Scenes", MAX_CHOREO_SCENES_STRINGS ); + + Assert( g_pStringTableEffectDispatch && + g_pStringTableVguiScreen && + g_pStringTableMaterials && + g_pStringTableInfoPanel && + g_pStringTableClientSideChoreoScenes ); + + // Need this so we have the error material always handy + PrecacheMaterial( "debug/debugempty" ); + Assert( GetMaterialIndex( "debug/debugempty" ) == 0 ); + + CreateNetworkStringTables_GameRules(); + + // Set up save/load utilities for string tables + g_VguiScreenStringOps.Init( g_pStringTableVguiScreen ); +} + +CSaveRestoreData *CServerGameDLL::SaveInit( int size ) +{ + return ::SaveInit(size); +} + +//----------------------------------------------------------------------------- +// Purpose: Saves data from a struct into a saverestore object, to be saved to disk +// Input : *pSaveData - the saverestore object +// char *pname - the name of the data to write +// *pBaseData - the struct into which the data is to be read +// *pFields - pointer to an array of data field descriptions +// fieldCount - the size of the array (number of field descriptions) +//----------------------------------------------------------------------------- +void CServerGameDLL::SaveWriteFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pMap, pFields, fieldCount ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Reads data from a save/restore block into a structure +// Input : *pSaveData - the saverestore object +// char *pname - the name of the data to extract from +// *pBaseData - the struct into which the data is to be restored +// *pFields - pointer to an array of data field descriptions +// fieldCount - the size of the array (number of field descriptions) +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +void CServerGameDLL::SaveReadFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pMap, pFields, fieldCount ); +} + +//----------------------------------------------------------------------------- + +void CServerGameDLL::SaveGlobalState( CSaveRestoreData *s ) +{ + ::SaveGlobalState(s); +} + +void CServerGameDLL::RestoreGlobalState(CSaveRestoreData *s) +{ + ::RestoreGlobalState(s); +} + +void CServerGameDLL::Save( CSaveRestoreData *s ) +{ + CSave saveHelper( s ); + g_pGameSaveRestoreBlockSet->Save( &saveHelper ); +} + +void CServerGameDLL::Restore( CSaveRestoreData *s, bool b) +{ + CRestore restore(s); + g_pGameSaveRestoreBlockSet->Restore( &restore, b ); + g_pGameSaveRestoreBlockSet->PostRestore(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_type - +// *name - +// size - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- + +bool CServerGameDLL::GetUserMessageInfo( int msg_type, char *name, int maxnamelength, int& size ) +{ + if ( !usermessages->IsValidIndex( msg_type ) ) + return false; + + Q_strncpy( name, usermessages->GetUserMessageName( msg_type ), maxnamelength ); + size = usermessages->GetUserMessageSize( msg_type ); + return true; +} + +CStandardSendProxies* CServerGameDLL::GetStandardSendProxies() +{ + return &g_StandardSendProxies; +} + +int CServerGameDLL::CreateEntityTransitionList( CSaveRestoreData *s, int a) +{ + CRestore restoreHelper( s ); + // save off file base + int base = restoreHelper.GetReadPos(); + + int movedCount = ::CreateEntityTransitionList(s, a); + if ( movedCount ) + { + g_pGameSaveRestoreBlockSet->CallBlockHandlerRestore( GetPhysSaveRestoreBlockHandler(), base, &restoreHelper, false ); + g_pGameSaveRestoreBlockSet->CallBlockHandlerRestore( GetAISaveRestoreBlockHandler(), base, &restoreHelper, false ); + } + + GetPhysSaveRestoreBlockHandler()->PostRestore(); + GetAISaveRestoreBlockHandler()->PostRestore(); + + return movedCount; +} + +void CServerGameDLL::PreSave( CSaveRestoreData *s ) +{ + g_pGameSaveRestoreBlockSet->PreSave( s ); +} + +#include "client_textmessage.h" + +// This little hack lets me marry BSP names to messages in titles.txt +typedef struct +{ + char *pBSPName; + char *pTitleName; +} TITLECOMMENT; + +// this list gets searched for the first partial match, so some are out of order +static TITLECOMMENT gTitleComments[] = +{ +#ifdef HL1_DLL + { "t0a0", "#T0A0TITLE" }, + { "c0a0", "#HL1_Chapter1_Title" }, + { "c1a0", "#HL1_Chapter2_Title" }, + { "c1a1", "#HL1_Chapter3_Title" }, + { "c1a2", "#HL1_Chapter4_Title" }, + { "c1a3", "#HL1_Chapter5_Title" }, + { "c1a4", "#HL1_Chapter6_Title" }, + { "c2a1", "#HL1_Chapter7_Title" }, + { "c2a2", "#HL1_Chapter8_Title" }, + { "c2a3", "#HL1_Chapter9_Title" }, + { "c2a4d", "#HL1_Chapter11_Title" }, // These must appear before "C2A4" so all other map names starting with C2A4 get that title + { "c2a4e", "#HL1_Chapter11_Title" }, + { "c2a4f", "#HL1_Chapter11_Title" }, + { "c2a4g", "#HL1_Chapter11_Title" }, + { "c2a4", "#HL1_Chapter10_Title" }, + { "c2a5", "#HL1_Chapter12_Title" }, + { "c3a1", "#HL1_Chapter13_Title" }, + { "c3a2", "#HL1_Chapter14_Title" }, + { "c4a1a", "#HL1_Chapter17_Title" }, // Order is important, see above + { "c4a1b", "#HL1_Chapter17_Title" }, + { "c4a1c", "#HL1_Chapter17_Title" }, + { "c4a1d", "#HL1_Chapter17_Title" }, + { "c4a1e", "#HL1_Chapter17_Title" }, + { "c4a1", "#HL1_Chapter15_Title" }, + { "c4a2", "#HL1_Chapter16_Title" }, + { "c4a3", "#HL1_Chapter18_Title" }, + { "c5a1", "#HL1_Chapter19_Title" }, +#else + { "intro", "#HL2_Chapter1_Title" }, + + { "d1_trainstation_05", "#HL2_Chapter2_Title" }, + { "d1_trainstation_06", "#HL2_Chapter2_Title" }, + + { "d1_trainstation_", "#HL2_Chapter1_Title" }, + + { "d1_canals_06a", "#HL2_Chapter4_Title" }, + { "d1_canals_06b", "#HL2_Chapter4_Title" }, + { "d1_canals_07a", "#HL2_Chapter4_Title" }, + { "d1_canals_07b", "#HL2_Chapter4_Title" }, + { "d1_canals_08", "#HL2_Chapter4_Title" }, + { "d1_canals_09", "#HL2_Chapter4_Title" }, + { "d1_canals_1", "#HL2_Chapter4_Title" }, + + { "d1_canals_0", "#HL2_Chapter3_Title" }, + + { "d1_eli_", "#HL2_Chapter5_Title" }, + + { "d1_town_", "#HL2_Chapter6_Title" }, + + { "d2_coast_09a", "#HL2_Chapter8_Title" }, + { "d2_coast_09b", "#HL2_Chapter8_Title" }, + { "d2_coast_1", "#HL2_Chapter8_Title" }, + { "d2_prison_01", "#HL2_Chapter8_Title" }, + + { "d2_coast_", "#HL2_Chapter7_Title" }, + + { "d2_prison_06a", "#HL2_Chapter10_Title" }, + { "d2_prison_06b", "#HL2_Chapter10_Title" }, + { "d2_prison_07a", "#HL2_Chapter10_Title" }, + { "d2_prison_07b", "#HL2_Chapter10_Title" }, + { "d2_prison_08", "#HL2_Chapter10_Title" }, + { "d3_c17_01", "#HL2_Chapter10_Title" }, + + { "d2_prison_", "#HL2_Chapter9_Title" }, + + { "d3_c17_09", "#HL2_Chapter12_Title" }, + { "d3_c17_1", "#HL2_Chapter12_Title" }, + + { "d3_c17_", "#HL2_Chapter11_Title" }, + + { "d3_citadel_", "#HL2_Chapter13_Title" }, + + { "d3_breen_", "#HL2_Chapter14_Title" }, + { "credits", "#HL2_Chapter15_Title" }, + + { "ep1_citadel_00", "#episodic_Chapter1_Title" }, + { "ep1_citadel_01", "#episodic_Chapter1_Title" }, + { "ep1_citadel_02", "#episodic_Chapter1_Title" }, + { "ep1_citadel_02b", "#episodic_Chapter1_Title" }, + { "ep1_citadel_03", "#episodic_Chapter2_Title" }, + { "ep1_citadel_04", "#episodic_Chapter2_Title" }, + { "ep1_c17_00", "#episodic_Chapter3_Title" }, + { "ep1_c17_00a", "#episodic_Chapter3_Title" }, + { "ep1_c17_01", "#episodic_Chapter4_Title" }, + { "ep1_c17_02", "#episodic_Chapter4_Title" }, + { "ep1_c17_02b", "#episodic_Chapter4_Title" }, + { "ep1_c17_05", "#episodic_Chapter5_Title" }, + { "ep1_c17_06", "#episodic_Chapter5_Title" }, +#endif +}; + +#ifdef _XBOX +void CServerGameDLL::GetTitleName( const char *pMapName, char* pTitleBuff, int titleBuffSize ) +{ + // Try to find a matching title comment for this mapname + for ( int i = 0; i < ARRAYSIZE(gTitleComments); i++ ) + { + if ( !Q_strnicmp( pMapName, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) + { + Q_strncpy( pTitleBuff, gTitleComments[i].pTitleName, titleBuffSize ); + return; + } + } + Q_strncpy( pTitleBuff, pMapName, titleBuffSize ); +} +#endif + +void CServerGameDLL::GetSaveCommentEx( char *text, int maxlength, float flMinutes, float flSeconds ) +{ + char comment[64]; + const char *pName; + + char const *mapname = STRING( gpGlobals->mapname ); + + pName = NULL; + + // Try to find a matching title comment for this mapname + for ( size_t i = 0; i < ARRAYSIZE(gTitleComments) && !pName; i++ ) + { + if ( !Q_strnicmp( mapname, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) + { + // found one + int j; + + // Got a message, post-process it to be save name friendly + Q_strncpy( comment, gTitleComments[i].pTitleName, sizeof( comment ) ); + pName = comment; + j = 0; + // Strip out CRs + while ( j < 64 && comment[j] ) + { + if ( comment[j] == '\n' || comment[j] == '\r' ) + comment[j] = 0; + else + j++; + } + break; + } + } + + // If we didn't get one, use the designer's map name, or the BSP name itself + if ( !pName ) + { + pName = mapname; + } + + if ( flMinutes == 0 && flSeconds == 0 ) + { + Q_snprintf( text, maxlength, "%-64.64s", pName ); + } + else + { + int totalSeconds = static_cast(gpGlobals->curtime + flSeconds); + int minutes = static_cast(( totalSeconds / 60.0f ) + flMinutes); + int seconds = static_cast(fmod( totalSeconds, 60.0f )); + + // Wow, this guy/gal must suck...! + if ( minutes >= 1000 ) + { + minutes = 999; + seconds = 59; + } + + // add the elapsed time at the end of the comment, for the ui to parse out + Q_snprintf( text, maxlength, "%-64.64s %03d:%02d", pName, minutes, seconds ); + } +} + +void CServerGameDLL::WriteSaveHeaders( CSaveRestoreData *s ) +{ + CSave saveHelper( s ); + g_pGameSaveRestoreBlockSet->WriteSaveHeaders( &saveHelper ); + g_pGameSaveRestoreBlockSet->PostSave(); +} + +void CServerGameDLL::ReadRestoreHeaders( CSaveRestoreData *s ) +{ + CRestore restoreHelper( s ); + g_pGameSaveRestoreBlockSet->PreRestore(); + g_pGameSaveRestoreBlockSet->ReadRestoreHeaders( &restoreHelper ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called during a transition, to build a map adjacency list +//----------------------------------------------------------------------------- +void CServerGameDLL::BuildAdjacentMapList( void ) +{ + // retrieve the pointer to the save data + CSaveRestoreData *pSaveData = gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->levelInfo.connectionCount = BuildChangeList( pSaveData->levelInfo.levelList, MAX_LEVEL_CONNECTIONS ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sanity-check to verify that a path is a relative path inside the game dir +// Taken From: engine/cmd.cpp +//----------------------------------------------------------------------------- +static bool IsValidPath( const char *pszFilename ) +{ + if ( !pszFilename ) + { + return false; + } + + if ( Q_strlen( pszFilename ) <= 0 || + Q_IsAbsolutePath( pszFilename ) || // to protect absolute paths + Q_strstr( pszFilename, ".." ) ) // to protect relative paths + { + return false; + } + + return true; +} + +static void ValidateMOTDFilename( ConVar *var, const char *oldValue ) +{ + if ( !IsValidPath( var->GetString() ) ) + { + var->Revert(); + } +} + +static ConVar motdfile( "motdfile", "motd.txt", 0, "The MOTD file to load.", ValidateMOTDFilename ); +void CServerGameDLL::LoadMessageOfTheDay() +{ +#ifndef _XBOX + char data[2048]; + + int length = filesystem->Size( motdfile.GetString(), "GAME" ); + + if ( length <= 0 || length >= static_cast((sizeof(data)-1)) ) + { + DevMsg("Invalid file size for %s\n", motdfile.GetString() ); + return; + } + + FileHandle_t hFile = filesystem->Open( motdfile.GetString(), "rb", "GAME" ); + + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + return; + + filesystem->Read( data, length, hFile ); + filesystem->Close( hFile ); + + data[length] = 0; + + g_pStringTableInfoPanel->AddString( "motd", length+1, data ); +#endif +} + +// keeps track of which chapters the user has unlocked +ConVar sv_unlockedchapters( "sv_unlockedchapters", "1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX ); + +//----------------------------------------------------------------------------- +// Purpose: Updates which chapters are unlocked +//----------------------------------------------------------------------------- +void UpdateChapterRestrictions( const char *mapname ) +{ + // look at the chapter for this map + char chapterTitle[64]; + chapterTitle[0] = 0; + for ( size_t i = 0; i < ARRAYSIZE(gTitleComments); i++ ) + { + if ( !Q_strnicmp( mapname, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) + { + // found + Q_strncpy( chapterTitle, gTitleComments[i].pTitleName, sizeof( chapterTitle ) ); + int j = 0; + while ( j < 64 && chapterTitle[j] ) + { + if ( chapterTitle[j] == '\n' || chapterTitle[j] == '\r' ) + chapterTitle[j] = 0; + else + j++; + } + + break; + } + } + + if ( !chapterTitle[0] ) + return; + + // make sure the specified chapter title is unlocked + Q_strlower( chapterTitle ); + + const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); + + char chapterNumberPrefix[64]; + Q_snprintf(chapterNumberPrefix, sizeof(chapterNumberPrefix), "#%s_chapter", pGameDir); + + const char *newChapterNumber = strstr( chapterTitle, chapterNumberPrefix ); + if ( newChapterNumber ) + { + // cut off the front + newChapterNumber += strlen( chapterNumberPrefix ); + char newChapter[32]; + Q_strncpy( newChapter, newChapterNumber, sizeof(newChapter) ); + + // cut off the end + char *end = strstr( newChapter, "_title" ); + if ( end ) + { + *end = 0; + } + + // ok we have the string, see if it's newer + const char *unlockedChapter = sv_unlockedchapters.GetString(); + if ( atoi(unlockedChapter) < atoi(newChapter) + || (atoi(unlockedChapter) == atoi(newChapter) && stricmp(unlockedChapter, newChapter) < 0) ) + { + // ok we're at a higher chapter, unlock + sv_unlockedchapters.SetValue( newChapter ); + } + } +} + +//----------------------------------------------------------------------------- +// Precaches a vgui screen overlay material +//----------------------------------------------------------------------------- +void PrecacheMaterial( const char *pMaterialName ) +{ + Assert( pMaterialName && pMaterialName[0] ); + g_pStringTableMaterials->AddString( pMaterialName ); +} + + +//----------------------------------------------------------------------------- +// Converts a previously precached material into an index +//----------------------------------------------------------------------------- +int GetMaterialIndex( const char *pMaterialName ) +{ + if (pMaterialName) + { + int nIndex = g_pStringTableMaterials->FindStringIndex( pMaterialName ); + + if (nIndex != INVALID_STRING_INDEX ) + { + return nIndex; + } + else + { + DevMsg("Warning! GetMaterialIndex: couldn't find material %s\n ", pMaterialName ); + return 0; + } + } + + // This is the invalid string index + return 0; +} + +//----------------------------------------------------------------------------- +// Converts a previously precached material index into a string +//----------------------------------------------------------------------------- +const char *GetMaterialNameFromIndex( int nMaterialIndex ) +{ + return g_pStringTableMaterials->GetString( nMaterialIndex ); +} + + +class CServerGameEnts : public IServerGameEnts +{ +public: + virtual void SetDebugEdictBase(edict_t *base); + virtual void MarkEntitiesAsTouching( edict_t *e1, edict_t *e2 ); + virtual void FreeContainingEntity( edict_t * ); + virtual edict_t* BaseEntityToEdict( CBaseEntity *pEnt ); + virtual CBaseEntity* EdictToBaseEntity( edict_t *pEdict ); + virtual void CheckTransmit( CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts ); +}; +EXPOSE_SINGLE_INTERFACE(CServerGameEnts, IServerGameEnts, INTERFACEVERSION_SERVERGAMEENTS); + +void CServerGameEnts::SetDebugEdictBase(edict_t *base) +{ + g_pDebugEdictBase = base; +} + +//----------------------------------------------------------------------------- +// Purpose: Marks entities as touching +// Input : *e1 - +// *e2 - +//----------------------------------------------------------------------------- +void CServerGameEnts::MarkEntitiesAsTouching( edict_t *e1, edict_t *e2 ) +{ + CBaseEntity *entity = GetContainingEntity( e1 ); + CBaseEntity *entityTouched = GetContainingEntity( e2 ); + if ( entity && entityTouched ) + { + // HACKHACK: UNDONE: Pass in the trace here??!?!? + trace_t tr; + UTIL_ClearTrace( tr ); + tr.endpos = (entity->GetAbsOrigin() + entityTouched->GetAbsOrigin()) * 0.5; + entity->PhysicsMarkEntitiesAsTouching( entityTouched, tr ); + } +} + +void CServerGameEnts::FreeContainingEntity( edict_t *e ) +{ + ::FreeContainingEntity(e); +} + +edict_t* CServerGameEnts::BaseEntityToEdict( CBaseEntity *pEnt ) +{ + if ( pEnt ) + return pEnt->edict(); + else + return NULL; +} + +CBaseEntity* CServerGameEnts::EdictToBaseEntity( edict_t *pEdict ) +{ + if ( pEdict ) + return CBaseEntity::Instance( pEdict ); + else + return NULL; +} + + +/* Yuck.. ideally this would be in CServerNetworkProperty's header, but it requires CBaseEntity and +// inlining it gives a nice speedup. +inline void CServerNetworkProperty::CheckTransmit( CCheckTransmitInfo *pInfo ) +{ + // If we have a transmit proxy, let it hook our ShouldTransmit return value. + if ( m_pTransmitProxy ) + { + nShouldTransmit = m_pTransmitProxy->ShouldTransmit( pInfo, nShouldTransmit ); + } + + if ( m_pOuter->ShouldTransmit( pInfo ) ) + { + m_pOuter->SetTransmit( pInfo ); + } +} */ + +void CServerGameEnts::CheckTransmit( CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts ) +{ + // NOTE: for speed's sake, this assumes that all networkables are CBaseEntities and that the edict list + // is consecutive in memory. If either of these things change, then this routine needs to change, but + // ideally we won't be calling any virtual from this routine. This speedy routine was added as an + // optimization which would be nice to keep. + edict_t *pBaseEdict = engine->PEntityOfEntIndex( 0 ); + + // get recipient player's skybox: + CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + + Assert( pRecipientEntity && pRecipientEntity->IsPlayer() ); + + if ( !pRecipientEntity ) + return; + + MDLCACHE_CRITICAL_SECTION(); + CBasePlayer *pRecipientPlayer = static_cast( pRecipientEntity ); + + const int skyBoxArea = pRecipientPlayer->m_Local.m_skybox3d.area; + const bool bIsHLTV = pRecipientPlayer->IsHLTV(); + + // m_pTransmitAlways must be set if HLTV client + Assert( bIsHLTV == ( pInfo->m_pTransmitAlways != NULL) ); + + // int dontSend = 0; int always = 0; int fullCheck = 0; int PVS = 0; + + + for ( int i=0; i < nEdicts; i++ ) + { + int iEdict = pEdictIndices[i]; + + edict_t *pEdict = &pBaseEdict[iEdict]; + Assert( pEdict == engine->PEntityOfEntIndex( iEdict ) ); + int nFlags = pEdict->m_fStateFlags & (FL_EDICT_DONTSEND|FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_FULLCHECK); + + if ( nFlags & FL_EDICT_DONTSEND ) + { + // entity needs no transmit + continue; + } + + if ( pInfo->m_pTransmitEdict->Get( iEdict ) ) + { + // entity is already marked for sending + continue; + } + else if ( pEdict->m_fStateFlags & FL_EDICT_DONTSEND ) + { + continue; + } + + if ( nFlags & FL_EDICT_ALWAYS ) + { + while ( true ) + { + // mark entity for sending + pInfo->m_pTransmitEdict->Set( iEdict ); + + if ( bIsHLTV ) + { + pInfo->m_pTransmitAlways->Set( iEdict ); + } + + CBaseEntity *pEnt = (CBaseEntity*)pEdict->GetUnknown(); + if ( !pEnt ) + break; + + CBaseEntity *pParent = pEnt->GetMoveParent(); + if ( !pParent ) + break; + + pEdict = pParent->edict(); + iEdict = pParent->entindex(); + } + + + continue; + } + + // get the Baseentity + + CBaseEntity *pEnt = ( CBaseEntity * )pEdict->GetUnknown(); + Assert( dynamic_cast< CBaseEntity* >( pEdict->GetUnknown() ) == pEnt ); + + if ( nFlags == FL_EDICT_FULLCHECK ) + { + // do a full ShouldTransmit() check, may return FL_EDICT_CHECKPVS + nFlags = pEnt->ShouldTransmit( pInfo ); + + Assert( !(nFlags & FL_EDICT_FULLCHECK) ); + + if ( nFlags & FL_EDICT_ALWAYS ) + { + pEnt->SetTransmit( pInfo, true ); + continue; + } + } + + if ( !( nFlags & FL_EDICT_PVSCHECK ) ) + { + // dont send this entity + continue; + } + + CServerNetworkProperty *netProp = pEnt->NetworkProp(); + if ( bIsHLTV ) + { + // for the HLTV we don't cull against PVS + if ( netProp->AreaNum() == skyBoxArea ) + { + pEnt->SetTransmit( pInfo, true ); + } + else + { + pEnt->SetTransmit( pInfo, false ); + } + continue; + } + + + bool bSameAreaAsSky = netProp->AreaNum() == skyBoxArea; + // Always send entities in the player's 3d skybox. + // Sidenote: call of AreaNum() ensures that PVS data is up to date for this entity + if ( bSameAreaAsSky ) + { + pEnt->SetTransmit( pInfo, true ); + } + else + { + bool bInPVS = netProp->IsInPVS( pInfo ); + if ( bInPVS ) + { + // only send if entity is in PVS + pEnt->SetTransmit( pInfo, false ); + } + else + { + // If the entity is marked "check PVS" but it's in hierarchy, walk up the hierarchy looking for the + // for any parent which is also in the PVS. If none are found, then we don't need to worry about sending ourself + CBaseEntity *orig = pEnt; + CBaseEntity *check = pEnt->GetMoveParent(); + + // BUG BUG: I think it might be better to build up a list of edict indices which "depend" on other answers and then + // resolve them in a second pass. Not sure what happens if an entity has two parents who both request PVS check? + + while ( check ) + { + int checkIndex = check->entindex(); + + // Parent already being sent + if ( pInfo->m_pTransmitEdict->Get( checkIndex ) ) + { + orig->SetTransmit( pInfo, true ); + break; + } + + edict_t *checkEdict = check->edict(); + int checkFlags = checkEdict->m_fStateFlags & (FL_EDICT_DONTSEND|FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_FULLCHECK); + if ( checkFlags & FL_EDICT_DONTSEND ) + { + break; + } + if ( checkFlags & FL_EDICT_ALWAYS ) + { + orig->SetTransmit( pInfo, true ); + break; + } + if ( checkFlags == FL_EDICT_FULLCHECK ) + { + // do a full ShouldTransmit() check, may return FL_EDICT_CHECKPVS + nFlags = check->ShouldTransmit( pInfo ); + Assert( !(nFlags & FL_EDICT_FULLCHECK) ); + if ( nFlags & FL_EDICT_ALWAYS ) + { + check->SetTransmit( pInfo, true ); + orig->SetTransmit( pInfo, true ); + } + break; + } + if ( checkFlags & FL_EDICT_PVSCHECK ) + { + // Check pvs + CServerNetworkProperty *netProp = check->NetworkProp(); + netProp->RecomputePVSInformation(); + bool bMoveParentInPVS = netProp->IsInPVS( pInfo ); + if ( bMoveParentInPVS ) + { + orig->SetTransmit( pInfo, true ); + break; + } + } + + // Continue up chain just in case the parent itself has a parent that's in the PVS... + check = check->GetMoveParent(); + } + } + } + } + +// Msg("A:%i, N:%i, F: %i, P: %i\n", always, dontSend, fullCheck, PVS ); +} + + +CServerGameClients g_ServerGameClients; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameClients, IServerGameClients, INTERFACEVERSION_SERVERGAMECLIENTS, g_ServerGameClients ); + + +//----------------------------------------------------------------------------- +// Purpose: called when a player tries to connect to the server +// Input : *pEdict - the new player +// char *pszName - the players name +// char *pszAddress - the IP address of the player +// reject - output - fill in with the reason why +// maxrejectlen -- sizeof output buffer +// the player was not allowed to connect. +// Output : Returns TRUE if player is allowed to join, FALSE if connection is denied. +//----------------------------------------------------------------------------- +bool CServerGameClients::ClientConnect( edict_t *pEdict, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) +{ + return g_pGameRules->ClientConnected( pEdict, pszName, pszAddress, reject, maxrejectlen ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when a player is fully active (i.e. ready to receive messages) +// Input : *pEntity - the player +//----------------------------------------------------------------------------- +void CServerGameClients::ClientActive( edict_t *pEdict, bool bLoadGame ) +{ + MDLCACHE_CRITICAL_SECTION(); + + ::ClientActive( pEdict, bLoadGame ); + + // If we just loaded from a save file, call OnRestore on valid entities + EndRestoreEntities(); + + if ( gpGlobals->eLoadType != MapLoad_LoadGame ) + { + // notify all entities that the player is now in the game + for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) ) + { + pEntity->PostClientActive(); + } + } + + SimThink_SortThinkList(); + // Tell the sound controller to check looping sounds + CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + CSoundEnvelopeController::GetController().CheckLoopingSoundsForPlayer( pPlayer ); + SceneManager_ClientActive( pPlayer ); +} + +//----------------------------------------------------------------------------- +// Purpose: called when a player disconnects from a server +// Input : *pEdict - the player +//----------------------------------------------------------------------------- +void CServerGameClients::ClientDisconnect( edict_t *pEdict ) +{ + extern bool g_fGameOver; + + CBasePlayer *player = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + if ( player ) + { + if ( !g_fGameOver ) + { + player->SetMaxSpeed( 0.0f ); + + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEdict ) ); + { + // since this client isn't around to think anymore, reset their sound. + if ( pSound ) + { + pSound->Reset(); + } + } + + // since the edict doesn't get deleted, fix it so it doesn't interfere. + player->RemoveFlag( FL_AIMTARGET ); // don't attract autoaim + player->AddFlag( FL_DONTTOUCH ); // stop it touching anything + player->AddFlag( FL_NOTARGET ); // stop NPCs noticing it + player->AddSolidFlags( FSOLID_NOT_SOLID ); // nonsolid + + if ( g_pGameRules ) + { + g_pGameRules->ClientDisconnected( pEdict ); + } + } + + // Make sure all Untouch()'s are called for this client leaving + CBaseEntity::PhysicsRemoveTouchedList( player ); + CBaseEntity::PhysicsRemoveGroundList( player ); + +#if !defined( NO_ENTITY_PREDICTION ) + // Make sure anything we "own" is simulated by the server from now on + player->ClearPlayerSimulationList(); +#endif + } +} + +void CServerGameClients::ClientPutInServer( edict_t *pEntity, const char *playername ) +{ + if ( g_pClientPutInServerOverride ) + g_pClientPutInServerOverride( pEntity, playername ); + else + ::ClientPutInServer( pEntity, playername ); +} + +void CServerGameClients::ClientCommand( edict_t *pEntity ) +{ + CBasePlayer *player = ToBasePlayer( GetContainingEntity( pEntity ) ); + ::ClientCommand(player); +} + +//----------------------------------------------------------------------------- +// Purpose: called after the player changes userinfo - gives dll a chance to modify +// it before it gets sent into the rest of the engine-> +// Input : *pEdict - the player +// *infobuffer - their infobuffer +//----------------------------------------------------------------------------- +void CServerGameClients::ClientSettingsChanged( edict_t *pEdict ) +{ + // Is the client spawned yet? + if ( !pEdict->GetUnknown() ) + return; + + CBasePlayer *player = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + + if ( !player ) + return; + +#define QUICKGETCVARVALUE(v) (engine->GetClientConVarValue( player->entindex(), v )) + + // get network setting for prediction & lag compensation + player->m_nUpdateRate = Q_atoi( QUICKGETCVARVALUE("cl_updaterate") ); + + bool useInterpolation = Q_atoi( QUICKGETCVARVALUE("cl_interpolate") ) != 0; + + if ( useInterpolation ) + { + player->m_fLerpTime = Q_atof( QUICKGETCVARVALUE("cl_interp") ); + } + else + { + player->m_fLerpTime = 0.0f; + } + +#if !defined( NO_ENTITY_PREDICTION ) + bool usePrediction = Q_atoi( QUICKGETCVARVALUE("cl_predict")) != 0; + + if ( usePrediction ) + { + player->m_bPredictWeapons = Q_atoi( QUICKGETCVARVALUE("cl_predictweapons")) != 0; + player->m_bLagCompensation = Q_atoi( QUICKGETCVARVALUE("cl_lagcompensation")) != 0; + } + else +#endif + { + player->m_bPredictWeapons = false; + player->m_bLagCompensation = false; + } + + +#undef QUICKGETCVARVALUE + + g_pGameRules->ClientSettingsChanged( player ); +} + + +//----------------------------------------------------------------------------- +// Purpose: A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +// view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +// entity's origin is used. Either is offset by the m_vecViewOffset to get the eye position. +// From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could +// override the actual PAS or PVS values, or use a different origin. +// NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +// Input : *pViewEntity - +// *pClient - +// **pvs - +// **pas - +//----------------------------------------------------------------------------- +void CServerGameClients::ClientSetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char *pvs, int pvssize ) +{ + Vector org; + + // Reset the PVS!!! + engine->ResetPVS( pvs, pvssize ); + + // Find the client's PVS + CBaseEntity *pVE = NULL; + if ( pViewEntity ) + { + pVE = GetContainingEntity( pViewEntity ); + // If we have a viewentity, it overrides the player's origin + if ( pVE ) + { + org = pVE->EyePosition(); + engine->AddOriginToPVS( org ); + } + } + + float fovDistanceAdjustFactor = 1; + + CBasePlayer *pPlayer = ( CBasePlayer * )GetContainingEntity( pClient ); + if ( pPlayer ) + { + org = pPlayer->EyePosition(); + pPlayer->SetupVisibility( pVE, pvs, pvssize ); + UTIL_SetClientVisibilityPVS( pClient, pvs, pvssize ); + fovDistanceAdjustFactor = pPlayer->GetFOVDistanceAdjustFactor(); + } + + unsigned char portalBits[MAX_AREA_PORTAL_STATE_BYTES]; + memset( portalBits, 0, sizeof( portalBits ) ); + + int portalNums[512]; + int isOpen[512]; + int iOutPortal = 0; + + for( unsigned short i = g_AreaPortals.Head(); i != g_AreaPortals.InvalidIndex(); i = g_AreaPortals.Next(i) ) + { + CFuncAreaPortalBase *pCur = g_AreaPortals[i]; + + bool bIsOpenOnClient = true; + + // Update our array of which portals are open and flush it if necessary. + portalNums[iOutPortal] = pCur->m_portalNumber; + isOpen[iOutPortal] = pCur->UpdateVisibility( org, fovDistanceAdjustFactor, bIsOpenOnClient ); + ++iOutPortal; + if ( iOutPortal >= static_cast(ARRAYSIZE( portalNums )) ) + { + engine->SetAreaPortalStates( portalNums, isOpen, iOutPortal ); + iOutPortal = 0; + } + + // Version 0 portals (ie: shipping Half-Life 2 era) are always treated as open + // for purposes of the m_chAreaPortalBits array on the client. + if ( pCur->m_iPortalVersion == 0 ) + bIsOpenOnClient = true; + + if ( bIsOpenOnClient ) + { + if ( pCur->m_portalNumber < 0 ) + continue; + else if ( pCur->m_portalNumber >= static_cast((sizeof( portalBits ) * 8)) ) + Error( "ClientSetupVisibility: portal number (%d) too large", pCur->m_portalNumber ); + else + portalBits[pCur->m_portalNumber >> 3] |= (1 << (pCur->m_portalNumber & 7)); + } + } + + // Flush the remaining areaportal states. + engine->SetAreaPortalStates( portalNums, isOpen, iOutPortal ); + + // Update the area bits that get sent to the client. + pPlayer->m_Local.UpdateAreaBits( pPlayer, portalBits ); +} + + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +// *buf - +// numcmds - +// totalcmds - +// dropped_packets - +// ignore - +// paused - +// Output : float +//----------------------------------------------------------------------------- +float CServerGameClients::ProcessUsercmds( edict_t *player, bf_read *buf, int numcmds, int totalcmds, + int dropped_packets, bool ignore, bool paused ) +{ + int i; + CUserCmd *from, *to; + + // We track last three command in case we drop some + // packets but get them back. + CUserCmd cmds[ CMD_MAXBACKUP ]; + + CUserCmd cmdNull; // For delta compression + + Assert( numcmds >= 0 ); + Assert( ( totalcmds - numcmds ) >= 0 ); + + CBasePlayer *pPlayer = NULL; + CBaseEntity *pEnt = CBaseEntity::Instance(player); + if ( pEnt && pEnt->IsPlayer() ) + { + pPlayer = static_cast< CBasePlayer * >( pEnt ); + } + // Too many commands? + if ( totalcmds < 0 || totalcmds >= ( CMD_MAXBACKUP - 1 ) ) + { + const char *name = "unknown"; + if ( pPlayer ) + { + name = pPlayer->GetPlayerName(); + } + + Msg("CBasePlayer::ProcessUsercmds: too many cmds %i sent for player %s\n", totalcmds, name ); + // FIXME: Need a way to drop the client from here + //SV_DropClient ( host_client, false, "CMD_MAXBACKUP hit" ); + buf->SetOverflowFlag(); + return 0.0f; + } + + // Initialize for reading delta compressed usercmds + cmdNull.Reset(); + from = &cmdNull; + for ( i = totalcmds - 1; i >= 0; i-- ) + { + to = &cmds[ i ]; + ReadUsercmd( buf, to, from ); + from = to; + } + + // Client not fully connected or server has gone inactive or is paused, just ignore + if ( ignore || !pPlayer ) + { + return 0.0f; + } + + MDLCACHE_CRITICAL_SECTION(); + pPlayer->ProcessUsercmds( cmds, numcmds, totalcmds, dropped_packets, paused ); + + return TICK_INTERVAL; +} + + +void CServerGameClients::PostClientMessagesSent( void ) +{ + VPROF("CServerGameClients::PostClient"); + gEntList.PostClientMessagesSent(); +} + +// Sets the client index for the client who typed the command into his/her console +void CServerGameClients::SetCommandClient( int index ) +{ + g_nCommandClientIndex = index; +} + +int CServerGameClients::GetReplayDelay( edict_t *pEdict, int &entity ) +{ + CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + + if ( !pPlayer ) + return 0; + + entity = pPlayer->GetReplayEntity(); + + return pPlayer->GetDelayTicks(); +} + + +//----------------------------------------------------------------------------- +// The client's userinfo data lump has changed +//----------------------------------------------------------------------------- +void CServerGameClients::ClientEarPosition( edict_t *pEdict, Vector *pEarOrigin ) +{ + CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + if (pPlayer) + { + *pEarOrigin = pPlayer->EarPosition(); + } + else + { + // Shouldn't happen + Assert(0); + *pEarOrigin = vec3_origin; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +// Output : CPlayerState +//----------------------------------------------------------------------------- +CPlayerState *CServerGameClients::GetPlayerState( edict_t *player ) +{ + // Is the client spawned yet? + if ( !player || !player->GetUnknown() ) + return NULL; + + CBasePlayer *pBasePlayer = ( CBasePlayer * )CBaseEntity::Instance( player ); + if ( !pBasePlayer ) + return NULL; + + return &pBasePlayer->pl; +} + +//----------------------------------------------------------------------------- +// Purpose: Anything this game .dll wants to add to the bug reporter text (e.g., the entity/model under the picker crosshair) +// can be added here +// Input : *buf - +// buflen - +//----------------------------------------------------------------------------- +void CServerGameClients::GetBugReportInfo( char *buf, int buflen ) +{ + recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ]; + int num; + int i; + + buf[ 0 ] = 0; + + if ( gpGlobals->maxClients == 1 ) + { + CBaseEntity *ent = FindPickerEntity( UTIL_PlayerByIndex(1) ); + if ( ent ) + { + Q_snprintf( buf, buflen, "Picker %i/%s - ent %s model %s\n", + ent->entindex(), + ent->GetClassname(), + STRING( ent->GetEntityName() ), + STRING( ent->GetModelName() ) ); + } + + // get any sounds that were spoken by NPCs recently + num = GetRecentNPCSpeech( speech ); + if ( num > 0 ) + { + Q_snprintf( buf, buflen, "%sRecent NPC speech:\n", buf ); + for( i = 0; i < num; i++ ) + { + Q_snprintf( buf, buflen, "%s time: %6.3f sound name: %s scene: %s\n", buf, speech[ i ].time, speech[ i ].name, speech[ i ].sceneName ); + } + Q_snprintf( buf, buflen, "%sCurrent time: %6.3f\n", buf, gpGlobals->curtime ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static bf_write *g_pMsgBuffer = NULL; + +void EntityMessageBegin( CBaseEntity * entity, bool reliable /*= false*/ ) +{ + Assert( !g_pMsgBuffer ); + + Assert ( entity ); + + g_pMsgBuffer = engine->EntityMessageBegin( entity->entindex(), entity->GetServerClass(), reliable ); +} + +void UserMessageBegin( IRecipientFilter& filter, const char *messagename ) +{ + Assert( !g_pMsgBuffer ); + + Assert( messagename ); + + int msg_type = usermessages->LookupUserMessage( messagename ); + + if ( msg_type == -1 ) + { + Error( "UserMessageBegin: Unregistered message '%s'\n", messagename ); + } + + g_pMsgBuffer = engine->UserMessageBegin( &filter, msg_type ); +} + +void MessageEnd( void ) +{ + Assert( g_pMsgBuffer ); + + engine->MessageEnd(); + + g_pMsgBuffer = NULL; +} + +void MessageWriteByte( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WRITE_BYTE called with no active message\n" ); + + g_pMsgBuffer->WriteByte( iValue ); +} + +void MessageWriteChar( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WRITE_CHAR called with no active message\n" ); + + g_pMsgBuffer->WriteChar( iValue ); +} + +void MessageWriteShort( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WRITE_SHORT called with no active message\n" ); + + g_pMsgBuffer->WriteShort( iValue ); +} + +void MessageWriteWord( int iValue ) +{ + if (!g_pMsgBuffer) + Error( "WRITE_WORD called with no active message\n" ); + + g_pMsgBuffer->WriteWord( iValue ); +} + +void MessageWriteLong( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WriteLong called with no active message\n" ); + + g_pMsgBuffer->WriteLong( iValue ); +} + +void MessageWriteFloat( float flValue) +{ + if (!g_pMsgBuffer) + Error( "WriteFloat called with no active message\n" ); + + g_pMsgBuffer->WriteFloat( flValue ); +} + +void MessageWriteAngle( float flValue) +{ + if (!g_pMsgBuffer) + Error( "WriteAngle called with no active message\n" ); + + g_pMsgBuffer->WriteBitAngle( flValue, 8 ); +} + +void MessageWriteCoord( float flValue) +{ + if (!g_pMsgBuffer) + Error( "WriteCoord called with no active message\n" ); + + g_pMsgBuffer->WriteBitCoord( flValue ); +} + +void MessageWriteVec3Coord( const Vector& rgflValue) +{ + if (!g_pMsgBuffer) + Error( "WriteVec3Coord called with no active message\n" ); + + g_pMsgBuffer->WriteBitVec3Coord( rgflValue ); +} + +void MessageWriteVec3Normal( const Vector& rgflValue) +{ + if (!g_pMsgBuffer) + Error( "WriteVec3Normal called with no active message\n" ); + + g_pMsgBuffer->WriteBitVec3Normal( rgflValue ); +} + +void MessageWriteAngles( const QAngle& rgflValue) +{ + if (!g_pMsgBuffer) + Error( "WriteVec3Normal called with no active message\n" ); + + g_pMsgBuffer->WriteBitAngles( rgflValue ); +} + +void MessageWriteString( const char *sz ) +{ + if (!g_pMsgBuffer) + Error( "WriteString called with no active message\n" ); + + g_pMsgBuffer->WriteString( sz ); +} + +void MessageWriteEntity( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WriteEntity called with no active message\n" ); + + g_pMsgBuffer->WriteShort( iValue ); +} + +void MessageWriteEHandle( CBaseEntity *pEntity ) +{ + if (!g_pMsgBuffer) + Error( "WriteEHandle called with no active message\n" ); + + long iEncodedEHandle; + + if( pEntity ) + { + EHANDLE hEnt = pEntity; + + int iSerialNum = hEnt.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1; + iEncodedEHandle = hEnt.GetEntryIndex() | (iSerialNum << MAX_EDICT_BITS); + } + else + { + iEncodedEHandle = INVALID_NETWORKED_EHANDLE_VALUE; + } + + g_pMsgBuffer->WriteLong( iEncodedEHandle ); +} + +// bitwise +void MessageWriteBool( bool bValue ) +{ + if (!g_pMsgBuffer) + Error( "WriteBool called with no active message\n" ); + + g_pMsgBuffer->WriteOneBit( bValue ? 1 : 0 ); +} + +void MessageWriteUBitLong( unsigned int data, int numbits ) +{ + if (!g_pMsgBuffer) + Error( "WriteUBitLong called with no active message\n" ); + + g_pMsgBuffer->WriteUBitLong( data, numbits ); +} + +void MessageWriteSBitLong( int data, int numbits ) +{ + if (!g_pMsgBuffer) + Error( "WriteSBitLong called with no active message\n" ); + + g_pMsgBuffer->WriteSBitLong( data, numbits ); +} + +void MessageWriteBits( const void *pIn, int nBits ) +{ + if (!g_pMsgBuffer) + Error( "WriteBits called with no active message\n" ); + + g_pMsgBuffer->WriteBits( pIn, nBits ); +} + +class CServerDLLSharedAppSystems : public IServerDLLSharedAppSystems +{ +public: + CServerDLLSharedAppSystems() + { + AddAppSystem( "soundemittersystem", SOUNDEMITTERSYSTEM_INTERFACE_VERSION ); + AddAppSystem( "scenefilecache", SCENE_FILE_CACHE_INTERFACE_VERSION ); + } + + virtual int Count() + { + return m_Systems.Count(); + } + virtual char const *GetDllName( int idx ) + { + return m_Systems[ idx ].m_pModuleName; + } + virtual char const *GetInterfaceName( int idx ) + { + return m_Systems[ idx ].m_pInterfaceName; + } +private: + void AddAppSystem( char const *moduleName, char const *interfaceName ) + { + AppSystemInfo_t sys; + sys.m_pModuleName = moduleName; + sys.m_pInterfaceName = interfaceName; + m_Systems.AddToTail( sys ); + } + + CUtlVector< AppSystemInfo_t > m_Systems; +}; + +EXPOSE_SINGLE_INTERFACE( CServerDLLSharedAppSystems, IServerDLLSharedAppSystems, SERVER_DLL_SHARED_APPSYSTEMS ); + + diff --git a/dlls/genericactor.cpp b/dlls/genericactor.cpp index 43760448..5b7bbdff 100644 --- a/dlls/genericactor.cpp +++ b/dlls/genericactor.cpp @@ -92,7 +92,7 @@ void CGenericActor::HandleAnimEvent( animevent_t *pEvent ) //========================================================= int CGenericActor::GetSoundInterests ( void ) { - return NULL; + return 0; } //========================================================= diff --git a/dlls/genericmonster.cpp b/dlls/genericmonster.cpp index 7830cbac..a34f4624 100644 --- a/dlls/genericmonster.cpp +++ b/dlls/genericmonster.cpp @@ -126,7 +126,7 @@ void CGenericNPC::HandleAnimEvent( animevent_t *pEvent ) //========================================================= int CGenericNPC::GetSoundInterests ( void ) { - return NULL; + return 0; } //========================================================= diff --git a/dlls/hl2_dll/ai_behavior_functank.cpp b/dlls/hl2_dll/ai_behavior_functank.cpp index 68d52043..36c211a8 100644 --- a/dlls/hl2_dll/ai_behavior_functank.cpp +++ b/dlls/hl2_dll/ai_behavior_functank.cpp @@ -645,7 +645,7 @@ CBaseEntity *CAI_FuncTankBehavior::BestEnemy( void ) { bBestSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) ); // @TODO (toml 04-02-03): Need to optimize CanSeeEntity() so multiple calls in frame do not recalculate, rather cache iBestPriority = pNPC->IRelationPriority( pEnemy ); - iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); + iBestDistSq = static_cast((pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); pBestEnemy = pEnemy; bBestUnreachable = bUnreachable; } @@ -656,7 +656,7 @@ CBaseEntity *CAI_FuncTankBehavior::BestEnemy( void ) // currently think is the best visible enemy. No need to do // a distance check, just get mad at this one for now. iBestPriority = pNPC->IRelationPriority ( pEnemy ); - iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); + iBestDistSq = static_cast(( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); pBestEnemy = pEnemy; bBestUnreachable = bUnreachable; } @@ -665,7 +665,7 @@ CBaseEntity *CAI_FuncTankBehavior::BestEnemy( void ) // 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. - iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); + iDistSq = static_cast(( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr()); bool bCloser = ( iDistSq < iBestDistSq ) ; diff --git a/dlls/hl2_dll/ai_behavior_functank.h b/dlls/hl2_dll/ai_behavior_functank.h index d9f1f33f..002ae52d 100644 --- a/dlls/hl2_dll/ai_behavior_functank.h +++ b/dlls/hl2_dll/ai_behavior_functank.h @@ -1,120 +1,120 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef AI_BEHAVIOR_FUNCTANK_H -#define AI_BEHAVIOR_FUNCTANK_H -#ifdef _WIN32 -#pragma once -#endif - -#include "simtimer.h" -#include "ai_behavior.h" -#include "func_tank.h" - -#define AI_FUNCTANK_BEHAVIOR_BUSYTIME 10.0f - -enum -{ - FUNCTANK_SENTENCE_MOVE_TO_MOUNT = SENTENCE_BASE_BEHAVIOR_INDEX, - FUNCTANK_SENTENCE_JUST_MOUNTED, - FUNCTANK_SENTENCE_SCAN_FOR_ENEMIES, - FUNCTANK_SENTENCE_DISMOUNTING, -}; - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -class CAI_FuncTankBehavior : public CAI_SimpleBehavior -{ - DECLARE_CLASS( CAI_FuncTankBehavior, CAI_SimpleBehavior ); - DEFINE_CUSTOM_SCHEDULE_PROVIDER; - DECLARE_DATADESC(); - -public: - // Contructor/Deconstructor - CAI_FuncTankBehavior(); - ~CAI_FuncTankBehavior(); - - void UpdateOnRemove(); - - // Identifier - const char *GetName() { return "FuncTank"; } - - // Schedule - bool CanSelectSchedule(); - void BeginScheduleSelection(); - void EndScheduleSelection(); - void PrescheduleThink(); - - Activity NPC_TranslateActivity( Activity activity ); - - // Conditions: - virtual void GatherConditions(); - - enum - { - SCHED_MOVE_TO_FUNCTANK = BaseClass::NEXT_SCHEDULE, - SCHED_FIRE_FUNCTANK, - SCHED_SCAN_WITH_FUNCTANK, - SCHED_FAIL_MOVE_TO_FUNCTANK, - }; - - // Tasks - void StartTask( const Task_t *pTask ); - void RunTask( const Task_t *pTask ); - - enum - { - TASK_GET_PATH_TO_FUNCTANK = BaseClass::NEXT_TASK, - TASK_FACE_FUNCTANK, - TASK_HOLSTER_WEAPON, - TASK_FIRE_FUNCTANK, - TASK_SCAN_LEFT_FUNCTANK, - TASK_SCAN_RIGHT_FUNCTANK, - TASK_FORGET_ABOUT_FUNCTANK, - TASK_FUNCTANK_ANNOUNCE_SCAN, - }; - - enum - { - COND_FUNCTANK_DISMOUNT = BaseClass::NEXT_CONDITION, - NEXT_CONDITION, - }; - - // Combat. - CBaseEntity *BestEnemy( void ); - void Event_Killed( const CTakeDamageInfo &info ); - - bool HasFuncTank( void ) { return ( m_hFuncTank != NULL ); } - void SetFuncTank( CHandle hFuncTank ); - CFuncTank *GetFuncTank() { return m_hFuncTank; } - void AimGun( void ); - - void Dismount( void ); - - int OnTakeDamage_Alive( const CTakeDamageInfo &info ); - - // Time. - void SetBusy( float flTime ) { m_flBusyTime = flTime; } - bool IsBusy( void ) { return ( gpGlobals->curtime < m_flBusyTime ); } - - bool IsMounted( void ) { return m_bMounted; } - -private: - - // Schedule - int SelectSchedule(); - -private: - - CHandle m_hFuncTank; - bool m_bMounted; - float m_flBusyTime; - bool m_bSpottedPlayerOutOfCover; -}; - -#endif // AI_BEHAVIOR_FUNCTANK_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef AI_BEHAVIOR_FUNCTANK_H +#define AI_BEHAVIOR_FUNCTANK_H +#ifdef _WIN32 +#pragma once +#endif + +#include "simtimer.h" +#include "ai_behavior.h" +#include "func_tank.h" + +#define AI_FUNCTANK_BEHAVIOR_BUSYTIME 10.0f + +enum +{ + FUNCTANK_SENTENCE_MOVE_TO_MOUNT = SENTENCE_BASE_BEHAVIOR_INDEX, + FUNCTANK_SENTENCE_JUST_MOUNTED, + FUNCTANK_SENTENCE_SCAN_FOR_ENEMIES, + FUNCTANK_SENTENCE_DISMOUNTING, +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CAI_FuncTankBehavior : public CAI_SimpleBehavior +{ + DECLARE_CLASS( CAI_FuncTankBehavior, CAI_SimpleBehavior ); + DEFINE_CUSTOM_SCHEDULE_PROVIDER; + DECLARE_DATADESC(); + +public: + // Contructor/Deconstructor + CAI_FuncTankBehavior(); + ~CAI_FuncTankBehavior(); + + void UpdateOnRemove(); + + // Identifier + const char *GetName() { return "FuncTank"; } + + // Schedule + bool CanSelectSchedule(); + void BeginScheduleSelection(); + void EndScheduleSelection(); + void PrescheduleThink(); + + Activity NPC_TranslateActivity( Activity activity ); + + // Conditions: + virtual void GatherConditions(); + + enum + { + SCHED_MOVE_TO_FUNCTANK = BaseClass::NEXT_SCHEDULE, + SCHED_FIRE_FUNCTANK, + SCHED_SCAN_WITH_FUNCTANK, + SCHED_FAIL_MOVE_TO_FUNCTANK, + }; + + // Tasks + void StartTask( const Task_t *pTask ); + void RunTask( const Task_t *pTask ); + + enum + { + TASK_GET_PATH_TO_FUNCTANK = BaseClass::NEXT_TASK, + TASK_FACE_FUNCTANK, + TASK_HOLSTER_WEAPON, + TASK_FIRE_FUNCTANK, + TASK_SCAN_LEFT_FUNCTANK, + TASK_SCAN_RIGHT_FUNCTANK, + TASK_FORGET_ABOUT_FUNCTANK, + TASK_FUNCTANK_ANNOUNCE_SCAN, + }; + + enum + { + COND_FUNCTANK_DISMOUNT = BaseClass::NEXT_CONDITION, + NEXT_CONDITION, + }; + + // Combat. + CBaseEntity *BestEnemy( void ); + void Event_Killed( const CTakeDamageInfo &info ); + + bool HasFuncTank( void ) { return ( m_hFuncTank != NULL ); } + void SetFuncTank( CHandle hFuncTank ); + CFuncTank *GetFuncTank() { return m_hFuncTank; } + void AimGun( void ); + + void Dismount( void ); + + int OnTakeDamage_Alive( const CTakeDamageInfo &info ); + + // Time. + void SetBusy( float flTime ) { m_flBusyTime = flTime; } + bool IsBusy( void ) { return ( gpGlobals->curtime < m_flBusyTime ); } + + bool IsMounted( void ) { return m_bMounted; } + +private: + + // Schedule + int SelectSchedule(); + +private: + + CHandle m_hFuncTank; + bool m_bMounted; + float m_flBusyTime; + bool m_bSpottedPlayerOutOfCover; +}; + +#endif // AI_BEHAVIOR_FUNCTANK_H diff --git a/dlls/hl2_dll/ai_behavior_police.cpp b/dlls/hl2_dll/ai_behavior_police.cpp index 6fc85b0a..eb80e7dd 100644 --- a/dlls/hl2_dll/ai_behavior_police.cpp +++ b/dlls/hl2_dll/ai_behavior_police.cpp @@ -333,7 +333,7 @@ void CAI_PolicingBehavior::StartTask( const Task_t *pTask ) } } - if ( GetNavigator()->SetGoal( harassPos, pTask->flTaskData ) ) + if ( GetNavigator()->SetGoal( harassPos, static_cast(pTask->flTaskData) ) ) { GetNavigator()->SetMovementActivity( (Activity) ACT_WALK_ANGRY ); GetNavigator()->SetArrivalDirection( m_hPoliceGoal->GetTarget() ); @@ -348,7 +348,7 @@ void CAI_PolicingBehavior::StartTask( const Task_t *pTask ) case TASK_POLICE_GET_PATH_TO_POLICE_GOAL: { - if ( GetNavigator()->SetGoal( m_hPoliceGoal->GetAbsOrigin(), pTask->flTaskData ) ) + if ( GetNavigator()->SetGoal( m_hPoliceGoal->GetAbsOrigin(), static_cast(pTask->flTaskData) ) ) { GetNavigator()->SetArrivalDirection( m_hPoliceGoal->GetAbsAngles() ); TaskComplete(); @@ -368,7 +368,7 @@ void CAI_PolicingBehavior::StartTask( const Task_t *pTask ) m_flNextHarassTime = gpGlobals->curtime + random->RandomInt( 4, 6 ); // Scatter rubber-neckers - CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin(), 256.0f, 2.0f, GetOuter() ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin(), 256, 2.0f, GetOuter() ); } TaskComplete(); break; diff --git a/dlls/hl2_dll/ai_goal_police.cpp b/dlls/hl2_dll/ai_goal_police.cpp index c6ecb2ad..0c2bd803 100644 --- a/dlls/hl2_dll/ai_goal_police.cpp +++ b/dlls/hl2_dll/ai_goal_police.cpp @@ -65,7 +65,7 @@ CBaseEntity *CAI_PoliceGoal::GetTarget( void ) if ( pTarget == NULL ) { - DevMsg( "Unable to find ai_goal_police target: %s\n", m_iszTarget ); + DevMsg( "Unable to find ai_goal_police target: %s\n", STRING(m_iszTarget) ); return NULL; } diff --git a/dlls/hl2_dll/ai_interactions.h b/dlls/hl2_dll/ai_interactions.h index ba4ac6b9..0c4cbe78 100644 --- a/dlls/hl2_dll/ai_interactions.h +++ b/dlls/hl2_dll/ai_interactions.h @@ -1,80 +1,80 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -//================================================== -// Definition for all AI interactions -//================================================== - -#ifndef AI_INTERACTIONS_H -#define AI_INTERACTIONS_H - -#ifdef _WIN32 -#pragma once -#endif - -//Antlion -extern int g_interactionAntlionKilled; - -//Barnacle -extern int g_interactionBarnacleVictimDangle; -extern int g_interactionBarnacleVictimReleased; -extern int g_interactionBarnacleVictimGrab; - -//Bullsquid -//extern int g_interactionBullsquidPlay; -//extern int g_interactionBullsquidThrow; - -//Combine -extern int g_interactionCombineBash; -extern int g_interactionCombineRequestCover; - -//Houndeye -//extern int g_interactionHoundeyeGroupAttack; -//extern int g_interactionHoundeyeGroupRetreat; -//extern int g_interactionHoundeyeGroupRalley; - -//Scanner -extern int g_interactionScannerInspect; -extern int g_interactionScannerInspectBegin; -extern int g_interactionScannerInspectDone; -extern int g_interactionScannerInspectHandsUp; -extern int g_interactionScannerInspectShowArmband; -extern int g_interactionScannerSupportEntity; -extern int g_interactionScannerSupportPosition; - -//Metrocop -extern int g_interactionMetrocopPointed; -extern int g_interactionMetrocopStartedStitch; - -//ScriptedTarget -extern int g_interactionScriptedTarget; - -//Stalker -extern int g_interactionStalkerBurn; - -//Vortigaunt -extern int g_interactionVortigauntStomp; -extern int g_interactionVortigauntStompFail; -extern int g_interactionVortigauntStompHit; -extern int g_interactionVortigauntKick; -extern int g_interactionVortigauntClaw; - -//Floor turret -extern int g_interactionTurretStillStanding; - -// AI Interaction for being hit by a physics object -extern int g_interactionHitByPlayerThrownPhysObj; - -// Alerts vital allies when the player punts a large object (car) -extern int g_interactionPlayerPuntedHeavyObject; - -// Zombie -// Melee attack will land in one second or so. -extern int g_interactionZombieMeleeWarning; - -#endif //AI_INTERACTIONS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +//================================================== +// Definition for all AI interactions +//================================================== + +#ifndef AI_INTERACTIONS_H +#define AI_INTERACTIONS_H + +#ifdef _WIN32 +#pragma once +#endif + +//Antlion +extern int g_interactionAntlionKilled; + +//Barnacle +extern int g_interactionBarnacleVictimDangle; +extern int g_interactionBarnacleVictimReleased; +extern int g_interactionBarnacleVictimGrab; + +//Bullsquid +//extern int g_interactionBullsquidPlay; +//extern int g_interactionBullsquidThrow; + +//Combine +extern int g_interactionCombineBash; +extern int g_interactionCombineRequestCover; + +//Houndeye +//extern int g_interactionHoundeyeGroupAttack; +//extern int g_interactionHoundeyeGroupRetreat; +//extern int g_interactionHoundeyeGroupRalley; + +//Scanner +extern int g_interactionScannerInspect; +extern int g_interactionScannerInspectBegin; +extern int g_interactionScannerInspectDone; +extern int g_interactionScannerInspectHandsUp; +extern int g_interactionScannerInspectShowArmband; +extern int g_interactionScannerSupportEntity; +extern int g_interactionScannerSupportPosition; + +//Metrocop +extern int g_interactionMetrocopPointed; +extern int g_interactionMetrocopStartedStitch; + +//ScriptedTarget +extern int g_interactionScriptedTarget; + +//Stalker +extern int g_interactionStalkerBurn; + +//Vortigaunt +extern int g_interactionVortigauntStomp; +extern int g_interactionVortigauntStompFail; +extern int g_interactionVortigauntStompHit; +extern int g_interactionVortigauntKick; +extern int g_interactionVortigauntClaw; + +//Floor turret +extern int g_interactionTurretStillStanding; + +// AI Interaction for being hit by a physics object +extern int g_interactionHitByPlayerThrownPhysObj; + +// Alerts vital allies when the player punts a large object (car) +extern int g_interactionPlayerPuntedHeavyObject; + +// Zombie +// Melee attack will land in one second or so. +extern int g_interactionZombieMeleeWarning; + +#endif //AI_INTERACTIONS_H diff --git a/dlls/hl2_dll/ai_spotlight.cpp b/dlls/hl2_dll/ai_spotlight.cpp index bac29efa..5495848f 100644 --- a/dlls/hl2_dll/ai_spotlight.cpp +++ b/dlls/hl2_dll/ai_spotlight.cpp @@ -365,12 +365,12 @@ void CAI_Spotlight::UpdateSpotlightEndpoint( void ) } else if (m_flSpotlightCurLength > m_flSpotlightMaxLength) { - m_hSpotlightTarget->SetRenderColorA( (1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength)) ); + m_hSpotlightTarget->SetRenderColorA( static_cast((1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength))) ); m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength); } else { - m_hSpotlightTarget->SetRenderColorA( 1.0 ); + m_hSpotlightTarget->SetRenderColorA( 1 ); m_hSpotlight->SetFadeLength(m_flSpotlightCurLength); } diff --git a/dlls/hl2_dll/antlion_dust.h b/dlls/hl2_dll/antlion_dust.h index 110716b9..a79ba0fc 100644 --- a/dlls/hl2_dll/antlion_dust.h +++ b/dlls/hl2_dll/antlion_dust.h @@ -1,32 +1,32 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#ifndef ANTLION_DUST_H -#define ANTLION_DUST_H - -#include "te_particlesystem.h" - -class CTEAntlionDust : public CTEParticleSystem -{ -public: - - DECLARE_CLASS( CTEAntlionDust, CTEParticleSystem ); - DECLARE_SERVERCLASS(); - - CTEAntlionDust( const char *name ); - virtual ~CTEAntlionDust( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ) { }; - - CNetworkVector( m_vecOrigin ); - CNetworkVar( QAngle, m_vecAngles ); - CNetworkVar( bool, m_bBlockedSpawner ); -}; - -void UTIL_CreateAntlionDust( const Vector &origin, const QAngle &angles, bool bBlockedSpawner = false ); - -#endif //ANTLION_DUST_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef ANTLION_DUST_H +#define ANTLION_DUST_H + +#include "te_particlesystem.h" + +class CTEAntlionDust : public CTEParticleSystem +{ +public: + + DECLARE_CLASS( CTEAntlionDust, CTEParticleSystem ); + DECLARE_SERVERCLASS(); + + CTEAntlionDust( const char *name ); + virtual ~CTEAntlionDust( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ) { }; + + CNetworkVector( m_vecOrigin ); + CNetworkVar( QAngle, m_vecAngles ); + CNetworkVar( bool, m_bBlockedSpawner ); +}; + +void UTIL_CreateAntlionDust( const Vector &origin, const QAngle &angles, bool bBlockedSpawner = false ); + +#endif //ANTLION_DUST_H diff --git a/dlls/hl2_dll/antlion_maker.cpp b/dlls/hl2_dll/antlion_maker.cpp index 26fb32cb..f16399dc 100644 --- a/dlls/hl2_dll/antlion_maker.cpp +++ b/dlls/hl2_dll/antlion_maker.cpp @@ -1634,20 +1634,20 @@ void CAntlionTemplateMaker::DrawDebugGeometryOverlays( void ) if ( m_debugOverlays & OVERLAY_TEXT_BIT ) { - float r, g, b; + int r, g, b; // Color by active state if ( m_bDisabled ) { - r = 255.0f; - g = 0.0f; - b = 0.0f; + r = 255; + g = 0; + b = 0; } else { - r = 0.0f; - g = 255.0f; - b = 0.0f; + r = 0; + g = 255; + b = 0; } // Draw ourself diff --git a/dlls/hl2_dll/basehlcombatweapon.cpp b/dlls/hl2_dll/basehlcombatweapon.cpp index 01a685c1..fa97dfc4 100644 --- a/dlls/hl2_dll/basehlcombatweapon.cpp +++ b/dlls/hl2_dll/basehlcombatweapon.cpp @@ -100,7 +100,7 @@ void CHLMachineGun::PrimaryAttack( void ) //Factor in the view kick AddViewKick(); - CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pPlayer ); + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), static_cast(SOUNDENT_VOLUME_MACHINEGUN), 0.2, pPlayer ); if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) { diff --git a/dlls/hl2_dll/cbasehelicopter.cpp b/dlls/hl2_dll/cbasehelicopter.cpp index 362102c5..ef09b1fd 100644 --- a/dlls/hl2_dll/cbasehelicopter.cpp +++ b/dlls/hl2_dll/cbasehelicopter.cpp @@ -674,7 +674,7 @@ void CBaseHelicopter::UpdateEnemy() // be sure and change my prevseen/lastseen timers. if( m_lifeState == LIFE_ALIVE ) { - GetSenses()->Look( EnemySearchDistance() ); + GetSenses()->Look( static_cast(EnemySearchDistance()) ); GetEnemies()->RefreshMemories(); ChooseEnemy(); @@ -834,7 +834,7 @@ void CBaseHelicopter::UpdatePlayerDopplerShift( ) float velReceiver = DotProduct( pPlayer->GetAbsVelocity(), dir ); float velTransmitter = -DotProduct( GetAbsVelocity(), dir ); // speed of sound == 13049in/s - int iPitch = 100 * ((1 - velReceiver / 13049) / (1 + velTransmitter / 13049)); + int iPitch = static_cast(100 * ((1 - velReceiver / 13049) / (1 + velTransmitter / 13049))); #else // This is a bogus doppler shift, but I like it better float relV = DotProduct( GetAbsVelocity() - pPlayer->GetAbsVelocity(), dir ); diff --git a/dlls/hl2_dll/cbasespriteprojectile.h b/dlls/hl2_dll/cbasespriteprojectile.h index 1c163c3d..0d9d78f8 100644 --- a/dlls/hl2_dll/cbasespriteprojectile.h +++ b/dlls/hl2_dll/cbasespriteprojectile.h @@ -27,7 +27,7 @@ class CBaseSpriteProjectile : public CSprite public: void Touch( CBaseEntity *pOther ); - void CBaseSpriteProjectile::Spawn( char *pszModel, + void Spawn( char *pszModel, const Vector &vecOrigin, const Vector &vecVelocity, edict_t *pOwner, diff --git a/dlls/hl2_dll/combine_mine.cpp b/dlls/hl2_dll/combine_mine.cpp index fe6386ed..694b361b 100644 --- a/dlls/hl2_dll/combine_mine.cpp +++ b/dlls/hl2_dll/combine_mine.cpp @@ -955,7 +955,7 @@ void CBounceBomb::ExplodeThink() CBaseEntity *pThrower = HasPhysicsAttacker( 0.5 ); - ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, BOUNCEBOMB_EXPLODE_DAMAGE, BOUNCEBOMB_EXPLODE_RADIUS, true ); + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, static_cast(BOUNCEBOMB_EXPLODE_DAMAGE), static_cast(BOUNCEBOMB_EXPLODE_RADIUS), true ); UTIL_Remove( this ); } @@ -1098,7 +1098,7 @@ void CBounceBomb::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t re // Try to scatter NPCs without panicking them. Make a move away sound up around their // ear level. - CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin() + Vector( 0, 0, 60), 32.0f, 0.2f ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin() + Vector( 0, 0, 60), 32, 0.2f ); return; } else diff --git a/dlls/hl2_dll/energy_wave.h b/dlls/hl2_dll/energy_wave.h index b1af1196..fa7d92f7 100644 --- a/dlls/hl2_dll/energy_wave.h +++ b/dlls/hl2_dll/energy_wave.h @@ -1,37 +1,37 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef ENERGYWAVE_H -#define ENERGYWAVE_H -#ifdef _WIN32 -#pragma once -#endif - -#include "basecombatweapon.h" -#include "energy_wave.h" - - -//----------------------------------------------------------------------------- -// Purpose: Shield -//----------------------------------------------------------------------------- -class CEnergyWave : public CBaseEntity -{ - DECLARE_DATADESC(); -public: - DECLARE_CLASS( CEnergyWave, CBaseEntity ); - DECLARE_SERVERCLASS(); - -public: - void Spawn( void ); - void Precache( void ); - -public: - static CEnergyWave* Create( CBaseEntity *pentOwner ); -}; - - -#endif //ENERGYWAVE_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ENERGYWAVE_H +#define ENERGYWAVE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basecombatweapon.h" +#include "energy_wave.h" + + +//----------------------------------------------------------------------------- +// Purpose: Shield +//----------------------------------------------------------------------------- +class CEnergyWave : public CBaseEntity +{ + DECLARE_DATADESC(); +public: + DECLARE_CLASS( CEnergyWave, CBaseEntity ); + DECLARE_SERVERCLASS(); + +public: + void Spawn( void ); + void Precache( void ); + +public: + static CEnergyWave* Create( CBaseEntity *pentOwner ); +}; + + +#endif //ENERGYWAVE_H diff --git a/dlls/hl2_dll/env_alyxemp.cpp b/dlls/hl2_dll/env_alyxemp.cpp index c41695f4..dcf66761 100644 --- a/dlls/hl2_dll/env_alyxemp.cpp +++ b/dlls/hl2_dll/env_alyxemp.cpp @@ -84,7 +84,7 @@ void CAlyxEmpEffect::SetTargetEntity( const char *szEntityName ) if ( pTargetEnt == NULL ) { Assert(0); - DevMsg( "Unable to find env_alyxemp (%s) target %s!\n", GetEntityName(), szEntityName ); + DevMsg( "Unable to find env_alyxemp (%s) target %s!\n", STRING(GetEntityName()), szEntityName ); } } diff --git a/dlls/hl2_dll/env_headcrabcanister.cpp b/dlls/hl2_dll/env_headcrabcanister.cpp index aca15151..730e311b 100644 --- a/dlls/hl2_dll/env_headcrabcanister.cpp +++ b/dlls/hl2_dll/env_headcrabcanister.cpp @@ -381,7 +381,7 @@ CSkyCamera *CEnvHeadcrabCanister::PlaceCanisterInWorld() CBaseEntity *pLaunchPos = gEntList.FindEntityByName( NULL, m_iszLaunchPositionName ); if ( !pLaunchPos ) { - Warning("%s (%s) could not find an entity matching LaunchPositionName of '%s'\n", GetEntityName(), GetDebugName(), STRING(m_iszLaunchPositionName) ); + Warning("%s (%s) could not find an entity matching LaunchPositionName of '%s'\n", STRING(GetEntityName()), GetDebugName(), STRING(m_iszLaunchPositionName) ); SUB_Remove(); } else @@ -937,7 +937,7 @@ void CEnvHeadcrabCanister::Detonate( ) if ( !HasSpawnFlags( SF_NO_IMPACT_EFFECTS ) ) { // Normal explosion - ExplosionCreate( m_vecImpactPosition, GetAbsAngles(), this, 50.0f, 500.0f, + ExplosionCreate( m_vecImpactPosition, GetAbsAngles(), this, 50, 500, SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSOUND, 1300.0f ); // Dust explosion diff --git a/dlls/hl2_dll/env_starfield.cpp b/dlls/hl2_dll/env_starfield.cpp index 8c287efd..249bd60d 100644 --- a/dlls/hl2_dll/env_starfield.cpp +++ b/dlls/hl2_dll/env_starfield.cpp @@ -1,107 +1,107 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "baseparticleentity.h" -#include "sendproxy.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -class CEnvStarfield : public CBaseEntity -{ - DECLARE_CLASS( CEnvStarfield, CBaseEntity ); -public: - DECLARE_SERVERCLASS(); - DECLARE_DATADESC(); - - virtual void Precache(); - virtual void Spawn( void ); - virtual int UpdateTransmitState(void); - - // Inputs - void InputTurnOn( inputdata_t &inputdata ); - void InputTurnOff( inputdata_t &inputdata ); - void InputSetDensity( inputdata_t &inputdata ); - -private: - CNetworkVar( bool, m_bOn ); - CNetworkVar( float, m_flDensity ); -}; - -BEGIN_DATADESC( CEnvStarfield ) - DEFINE_FIELD( m_bOn, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flDensity, FIELD_FLOAT ), - - DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), - DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), - DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDensity", InputSetDensity ), -END_DATADESC() - -IMPLEMENT_SERVERCLASS_ST( CEnvStarfield, DT_EnvStarfield ) - SendPropInt( SENDINFO(m_bOn), 1, SPROP_UNSIGNED ), - SendPropFloat( SENDINFO(m_flDensity), 0, SPROP_NOSCALE), -END_SEND_TABLE() - -LINK_ENTITY_TO_CLASS( env_starfield, CEnvStarfield ); - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CEnvStarfield::Spawn() -{ - BaseClass::Spawn(); - - m_flDensity = 1.0; - m_bOn = false; - - Precache(); -} - -void CEnvStarfield::Precache() -{ - BaseClass::Precache(); - - PrecacheMaterial( "effects/spark_noz" ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CEnvStarfield::UpdateTransmitState() -{ - return SetTransmitState( FL_EDICT_ALWAYS ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CEnvStarfield::InputTurnOn( inputdata_t &inputdata ) -{ - m_bOn = true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CEnvStarfield::InputTurnOff( inputdata_t &inputdata ) -{ - m_bOn = false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CEnvStarfield::InputSetDensity( inputdata_t &inputdata ) -{ - m_flDensity = inputdata.value.Float(); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "baseparticleentity.h" +#include "sendproxy.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEnvStarfield : public CBaseEntity +{ + DECLARE_CLASS( CEnvStarfield, CBaseEntity ); +public: + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + virtual void Precache(); + virtual void Spawn( void ); + virtual int UpdateTransmitState(void); + + // Inputs + void InputTurnOn( inputdata_t &inputdata ); + void InputTurnOff( inputdata_t &inputdata ); + void InputSetDensity( inputdata_t &inputdata ); + +private: + CNetworkVar( bool, m_bOn ); + CNetworkVar( float, m_flDensity ); +}; + +BEGIN_DATADESC( CEnvStarfield ) + DEFINE_FIELD( m_bOn, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flDensity, FIELD_FLOAT ), + + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDensity", InputSetDensity ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CEnvStarfield, DT_EnvStarfield ) + SendPropInt( SENDINFO(m_bOn), 1, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO(m_flDensity), 0, SPROP_NOSCALE), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( env_starfield, CEnvStarfield ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnvStarfield::Spawn() +{ + BaseClass::Spawn(); + + m_flDensity = 1.0; + m_bOn = false; + + Precache(); +} + +void CEnvStarfield::Precache() +{ + BaseClass::Precache(); + + PrecacheMaterial( "effects/spark_noz" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEnvStarfield::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnvStarfield::InputTurnOn( inputdata_t &inputdata ) +{ + m_bOn = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvStarfield::InputTurnOff( inputdata_t &inputdata ) +{ + m_bOn = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvStarfield::InputSetDensity( inputdata_t &inputdata ) +{ + m_flDensity = inputdata.value.Float(); +} diff --git a/dlls/hl2_dll/func_recharge.cpp b/dlls/hl2_dll/func_recharge.cpp index c849e95f..b73cad2b 100644 --- a/dlls/hl2_dll/func_recharge.cpp +++ b/dlls/hl2_dll/func_recharge.cpp @@ -123,7 +123,7 @@ void CRecharge::Spawn() SetModel( STRING( GetModelName() ) ); - UpdateJuice( MaxJuice() ); + UpdateJuice( static_cast(MaxJuice()) ); m_nState = 0; @@ -303,7 +303,7 @@ void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE use void CRecharge::Recharge(void) { - UpdateJuice( MaxJuice() ); + UpdateJuice( static_cast(MaxJuice()) ); m_nState = 0; SetThink( &CRecharge::SUB_DoNothing ); } @@ -318,7 +318,7 @@ void CRecharge::Off(void) m_iOn = 0; - if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime() ) > 0) ) + if ((!m_iJuice) && ( ( m_iReactivate = static_cast(g_pGameRules->FlHEVChargerRechargeTime()) ) > 0) ) { SetNextThink( gpGlobals->curtime + m_iReactivate ); SetThink(&CRecharge::Recharge); @@ -449,17 +449,17 @@ void CNewRecharge::SetInitialCharge( void ) if ( HasSpawnFlags( SF_KLEINER_RECHARGER ) ) { // The charger in Kleiner's lab. - m_iMaxJuice = 25.0f; + m_iMaxJuice = 25; return; } if ( HasSpawnFlags( SF_CITADEL_RECHARGER ) ) { - m_iMaxJuice = sk_suitcharger_citadel.GetFloat(); + m_iMaxJuice = sk_suitcharger_citadel.GetInt(); return; } - m_iMaxJuice = sk_suitcharger.GetFloat(); + m_iMaxJuice = sk_suitcharger.GetInt(); } void CNewRecharge::Spawn() @@ -477,7 +477,7 @@ void CNewRecharge::Spawn() SetInitialCharge(); - UpdateJuice( MaxJuice() ); + UpdateJuice( static_cast(MaxJuice()) ); m_nState = 0; m_iCaps = FCAP_CONTINUOUS_USE; @@ -728,7 +728,7 @@ void CNewRecharge::Recharge(void) EmitSound( "SuitRecharge.Start" ); ResetSequence( LookupSequence( "idle" ) ); - UpdateJuice( MaxJuice() ); + UpdateJuice( static_cast(MaxJuice()) ); m_nState = 0; m_flJuice = m_iJuice; @@ -760,11 +760,11 @@ void CNewRecharge::Off(void) { if ( HasSpawnFlags( SF_CITADEL_RECHARGER ) ) { - m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime() * 2; + m_iReactivate = static_cast(g_pGameRules->FlHEVChargerRechargeTime() * 2); } else { - m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime(); + m_iReactivate = static_cast(g_pGameRules->FlHEVChargerRechargeTime()); } SetNextThink( gpGlobals->curtime + m_iReactivate ); SetThink(&CNewRecharge::Recharge); diff --git a/dlls/hl2_dll/func_tank.cpp b/dlls/hl2_dll/func_tank.cpp index b34487c0..7bc3d538 100644 --- a/dlls/hl2_dll/func_tank.cpp +++ b/dlls/hl2_dll/func_tank.cpp @@ -824,7 +824,7 @@ void CFuncTank::Spawn( void ) m_sightOrigin = WorldBarrelPosition(); // Point at the end of the barrel - if ( m_spread > MAX_FIRING_SPREADS ) + if ( m_spread > static_cast(MAX_FIRING_SPREADS) ) { m_spread = 0; } @@ -1143,7 +1143,7 @@ void CFuncTank::ControllerPostFrame( void ) AngleVectors( GetAbsAngles(), &forward ); m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets - int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate; + int bulletCount = static_cast((gpGlobals->curtime - m_fireLast) * m_fireRate); if( HasSpawnFlags( SF_TANK_AIM_ASSISTANCE ) ) { @@ -1417,7 +1417,7 @@ float CFuncTank::GetRandomFireTime( void ) //----------------------------------------------------------------------------- int CFuncTank::GetRandomBurst( void ) { - return random->RandomInt( m_fireRate-2, m_fireRate+2 ); + return random->RandomInt( static_cast(m_fireRate-2), static_cast(m_fireRate+2) ); } //----------------------------------------------------------------------------- @@ -2110,7 +2110,7 @@ void CFuncTank::FiringSequence( const Vector &barrelEnd, const Vector &forward, { if ( m_fireLast != 0 ) { - int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate; + int bulletCount = static_cast((gpGlobals->curtime - m_fireLast) * m_fireRate); if ( bulletCount > 0 ) { @@ -2227,7 +2227,7 @@ void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &fo #ifdef _XBOX UTIL_PlayerByIndex(1)->RumbleEffect( RUMBLE_AR2, 0, RUMBLE_FLAG_RESTART | RUMBLE_FLAG_RANDOM_AMPLITUDE ); #else - CSoundEnt::InsertSound( SOUND_MOVE_AWAY, barrelEnd + forward * 32.0f, 32.0f, 0.2f, pAttacker, SOUNDENT_CHANNEL_WEAPON ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, barrelEnd + forward * 32.0f, 32, 0.2f, pAttacker, SOUNDENT_CHANNEL_WEAPON ); #endif } @@ -3517,7 +3517,7 @@ void CMortarShell::FlyThink() if ( gpGlobals->curtime > m_flNPCWarnTime ) { // Warn the AI. Make this radius a little larger than the explosion will be, and make the sound last a little longer. - CSoundEnt::InsertSound ( SOUND_DANGER | SOUND_CONTEXT_MORTAR, GetAbsOrigin(), MORTAR_BLAST_RADIUS * 1.25, (m_flImpactTime - m_flNPCWarnTime) + 0.15 ); + CSoundEnt::InsertSound ( SOUND_DANGER | SOUND_CONTEXT_MORTAR, GetAbsOrigin(), static_cast(MORTAR_BLAST_RADIUS * 1.25), (m_flImpactTime - m_flNPCWarnTime) + 0.15 ); m_flNPCWarnTime = FLT_MAX; } @@ -3531,11 +3531,11 @@ void CMortarShell::FlyThink() // Beam updates START - m_pBeamEffect[0]->SetBrightness( 255 * curve1 ); + m_pBeamEffect[0]->SetBrightness( static_cast(255 * curve1) ); m_pBeamEffect[0]->SetWidth( 64.0f * curve1 ); m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 ); - m_pBeamEffect[1]->SetBrightness( 255 * curve1 ); + m_pBeamEffect[1]->SetBrightness( static_cast(255 * curve1) ); m_pBeamEffect[1]->SetWidth( 8.0f * curve1 ); m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 ); @@ -3543,14 +3543,14 @@ void CMortarShell::FlyThink() if ( m_pBeamEffect[2] ) { - m_pBeamEffect[2]->SetBrightness( 255 * curve2 ); + m_pBeamEffect[2]->SetBrightness( static_cast(255 * curve2) ); m_pBeamEffect[2]->SetWidth( 32.0f * curve2 ); m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 ); } if ( m_pBeamEffect[3] ) { - m_pBeamEffect[3]->SetBrightness( 255 * curve2 ); + m_pBeamEffect[3]->SetBrightness( static_cast(255 * curve2) ); m_pBeamEffect[3]->SetWidth( 8.0f * curve2 ); m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 ); } @@ -3687,11 +3687,11 @@ void CMortarShell::FadeThink( void ) // Beam updates START - m_pBeamEffect[0]->SetBrightness( 255 * curve1 ); + m_pBeamEffect[0]->SetBrightness( static_cast(255 * curve1) ); m_pBeamEffect[0]->SetWidth( 64.0f * curve1 ); m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 ); - m_pBeamEffect[1]->SetBrightness( 255 * curve1 ); + m_pBeamEffect[1]->SetBrightness( static_cast(255 * curve1) ); m_pBeamEffect[1]->SetWidth( 8.0f * curve1 ); m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 ); @@ -3699,14 +3699,14 @@ void CMortarShell::FadeThink( void ) if ( m_pBeamEffect[2] ) { - m_pBeamEffect[2]->SetBrightness( 255 * curve2 ); + m_pBeamEffect[2]->SetBrightness( static_cast(255 * curve2) ); m_pBeamEffect[2]->SetWidth( 32.0f * curve2 ); m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 ); } if ( m_pBeamEffect[3] ) { - m_pBeamEffect[3]->SetBrightness( 255 * curve2 ); + m_pBeamEffect[3]->SetBrightness( static_cast(255 * curve2) ); m_pBeamEffect[3]->SetWidth( 8.0f * curve2 ); m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 ); } diff --git a/dlls/hl2_dll/func_tank.h b/dlls/hl2_dll/func_tank.h index 07b3efa2..7891b579 100644 --- a/dlls/hl2_dll/func_tank.h +++ b/dlls/hl2_dll/func_tank.h @@ -1,341 +1,341 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef FUNC_TANK_H -#define FUNC_TANK_H -#ifdef _WIN32 -#pragma once -#endif - -#include "triggers.h" - -#define SF_TANK_ACTIVE 0x0001 -#define SF_TANK_PLAYER 0x0002 -#define SF_TANK_HUMANS 0x0004 -#define SF_TANK_ALIENS 0x0008 -#define SF_TANK_LINEOFSIGHT 0x0010 -#define SF_TANK_CANCONTROL 0x0020 -#define SF_TANK_DAMAGE_KICK 0x0040 // Kick when take damage -#define SF_TANK_AIM_AT_POS 0x0080 // Aim at a particular position -#define SF_TANK_AIM_ASSISTANCE 0x0100 -#define SF_TANK_NPC 0x0200 -#define SF_TANK_NPC_CONTROLLABLE 0x0400 // 1024 -#define SF_TANK_NPC_SET_CONTROLLER 0x0800 // 2048 -#define SF_TANK_ALLOW_PLAYER_HITS 0x1000 // 4096 Allow friendly NPCs to fire upon enemies near the player -#define SF_TANK_IGNORE_RANGE_IN_VIEWCONE 0x2000 // 8192 Don't use range as a factor in determining if something is in view cone -#define SF_TANK_NOTSOLID 0x8000 // 32768 -#define SF_TANK_SOUNDON 0x10000 // FIXME: This is not really a spawnflag! It holds transient state!!! -#define SF_TANK_HACKPLAYERHIT 0x20000 // 131072 Make this func_tank cheat and hit the player regularly - -#define FUNCTANK_DISTANCE_MAX 1200 // 100 ft. -#define FUNCTANK_DISTANCE_MIN_TO_ENEMY 180 -#define FUNCTANK_FIREVOLUME 1000 -#define FUNCTANK_NPC_ROUTE_TIME 5.0f - -// Effect handling -// If the func_tank has a chosen method of handling effects, use that -// instead of the individual effect settings. (muzzleflash, sound, tracer, etc) -enum FUNCTANK_EFFECT_HANDLING -{ - EH_NONE, // Use the effect settings - EH_AR2, // Use AR2 effects - EH_COMBINE_CANNON // Large Combine cannon -}; - -enum TANKBULLET -{ - TANK_BULLET_NONE = 0, - TANK_BULLET_SMALL = 1, - TANK_BULLET_MEDIUM = 2, - TANK_BULLET_LARGE = 3, -}; - -#define MORTAR_BLAST_RADIUS 350 - - -// Custom damage -// env_laser (duration is 0.5 rate of fire) -// rockets -// explosion? - -class CFuncTank : public CBaseEntity -{ - - DECLARE_CLASS( CFuncTank, CBaseEntity ); - -public: - - CFuncTank(); - ~CFuncTank(); - void Spawn( void ); - void Activate( void ); - void Precache( void ); - bool CreateVPhysics( void ); - bool KeyValue( const char *szKeyName, const char *szValue ); - void UpdateOnRemove(); - - int ObjectCaps( void ) - { - return ( BaseClass::ObjectCaps() | FCAP_IMPULSE_USE | FCAP_USE_IN_RADIUS ); - } - - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void Think( void ); - - int GetAmmoCount( void ) { return m_iAmmoCount; } - - // NPC - bool NPC_FindManPoint( Vector &vecPos ); - bool NPC_HasEnemy( void ); - void NPC_Fire( void ); - void NPC_InterruptRoute( void ); - void NPC_JustSawPlayer( CBaseEntity *pTarget ); - void NPC_SetInRoute( bool bInRoute ) { m_bNPCInRoute = bInRoute; } - void NPC_SetIdleAngle( Vector vecIdle ) { m_vecNPCIdleTarget = vecIdle; } - - // LOS - bool IsEntityInViewCone( CBaseEntity *pEntity ); - bool HasLOSTo( CBaseEntity *pEntity ); - - // Controller - CBaseCombatCharacter *GetController( void ); - bool StartControl( CBaseCombatCharacter *pController ); - void StopControl( void ); - - const float YawCenter() const { return m_yawCenter; } - const float YawCenterWorld() const { return m_yawCenterWorld; } - const float YawRange() const { return m_yawRange; } - const float PitchCenter() const { return m_pitchCenter; } - const float PitchCenterWorld() const { return m_pitchCenterWorld; } - const float PitchRange() const { return m_pitchRange; } - - virtual void PhysicsSimulate( void ); - - virtual void OnStartControlled() {} - virtual void OnStopControlled() {} - - // SF Tests. - inline bool IsControllable( void ) { return ( m_spawnflags & SF_TANK_CANCONTROL ) ? true : false; } - inline bool IsActive( void ) { return ( m_spawnflags & SF_TANK_ACTIVE ) ? true : false; } - inline bool IsNPCControllable( void ) { return ( m_spawnflags & SF_TANK_NPC_CONTROLLABLE ) ? true : false; } - inline bool IsNPCSetController( void ) { return ( m_spawnflags & SF_TANK_NPC_SET_CONTROLLER ) ? true : false; } - - virtual void DoMuzzleFlash( void ); - virtual const char *GetTracerType( void ); - -protected: - virtual float GetShotSpeed() { return 0; } - - virtual Vector WorldBarrelPosition( void ); - void UpdateMatrix( void ); - - float GetNextAttack() const { return m_flNextAttack; } - virtual void SetNextAttack( float flWait ) { m_flNextAttack = flWait; } - - virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); - void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, trace_t &tr ); - int GetRandomBurst( void ); - float GetRandomFireTime( void ); - - void CalcPlayerCrosshairTarget( Vector *pVecTarget ); - void CalcNPCEnemyTarget( Vector *pVecTarget ); - - inline bool IsPlayerManned( void ) { return m_hController && m_hController->IsPlayer() && ( m_spawnflags & SF_TANK_PLAYER ); } - inline bool IsNPCManned( void ) { return m_hController && m_hController->MyNPCPointer() && ( m_spawnflags & SF_TANK_NPC ); } - -private: - void TrackTarget( void ); - int DrawDebugTextOverlays(void); - void DrawDebugGeometryOverlays(void); - - virtual void FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); - - void StartRotSound( void ); - void StopRotSound( void ); - - // Input handlers. - void InputActivate( inputdata_t &inputdata ); - void InputDeactivate( inputdata_t &inputdata ); - void InputSetFireRate( inputdata_t &inputdata ); - void InputSetDamage( inputdata_t &inputdata ); - void InputSetTargetDir( inputdata_t &inputdata ); - void InputSetTargetPosition( inputdata_t &inputdata ); - void InputSetTargetEntityName( inputdata_t &inputdata ); - void InputSetTargetEntity( inputdata_t &inputdata ); - void InputClearTargetEntity( inputdata_t &inputdata ); - void InputFindNPCToManTank( inputdata_t &inputdata ); - void InputStopFindingNPCs( inputdata_t &inputdata ); - void InputStartFindingNPCs( inputdata_t &inputdata ); - void InputForceNPCOff( inputdata_t &inputdata ); - void InputSetMaxRange( inputdata_t &inputdata ); - - void TankActivate(void); - void TankDeactivate(void); - - inline bool CanFire( void ); - bool InRange( float range ); - bool InRange2( float flRange2 ); - - void TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType); - - QAngle AimBarrelAt( const Vector &parentTarget ); - - DECLARE_DATADESC(); - - bool OnControls( CBaseEntity *pTest ); - bool HasController( void ); - - CBaseEntity *FindTarget( string_t targetName, CBaseEntity *pActivator ); - - // NPC - void NPC_FindController( void ); - bool NPC_InRoute( void ) { return m_bNPCInRoute; } - bool NPC_InterruptController( void ); - - // Aim the tank at the player crosshair - void AimBarrelAtPlayerCrosshair( QAngle *pAngles ); - - // Aim the tank at the NPC's enemy - void AimBarrelAtNPCEnemy( QAngle *pAngles ); - - // Aim the tank at the func_tank's enemy - void AimFuncTankAtTarget( void ); - - // Returns true if the desired angles are out of range - bool RotateTankToAngles( const QAngle &angles, float *pDistX = NULL, float *pDistY = NULL ); - - // We lost our target! - void LostTarget( void ); - - // Purpose: - void ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition ); - -protected: - virtual void ControllerPostFrame( void ); - - float m_fireLast; // Last time I fired - float m_fireRate; // How many rounds/second - - EHANDLE m_hTarget; - - TANKBULLET m_bulletType; // Bullet type - int m_iBulletDamage; // 0 means use Bullet type's default damage - int m_iBulletDamageVsPlayer; // Damage vs player. 0 means use m_iBulletDamage - -#ifdef HL2_EPISODIC - string_t m_iszAmmoType; // The name of the ammodef that we use when we fire. Bullet damage still comes from keyvalues. - int m_iAmmoType; // The cached index of the ammodef that we use when we fire. -#else - int m_iSmallAmmoType; - int m_iMediumAmmoType; - int m_iLargeAmmoType; -#endif // HL2_EPISODIC - - int m_spread; // firing spread - - EntityMatrix m_parentMatrix; - - Vector m_sightOrigin; // Last sight of target - EHANDLE m_hFuncTankTarget; - - int m_nBulletCount; - -private: - - // This is either the player manning the func_tank, or an NPC. The NPC is either manning the tank, or running - // to the man point. If he's en-route, m_bNPCInRoute will be true. - CHandle m_hController; - - float m_flNextAttack; - Vector m_vecControllerUsePos; - - float m_yawCenter; // "Center" yaw - float m_yawCenterWorld; // "Center" yaw in world space - float m_yawRate; // Max turn rate to track targets - float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) - // Zero is full rotation - float m_yawTolerance; // Tolerance angle - - float m_pitchCenter; // "Center" pitch - float m_pitchCenterWorld; // "Center" pitch in world space - float m_pitchRate; // Max turn rate on pitch - float m_pitchRange; // Range of pitch motion as above - float m_pitchTolerance; // Tolerance angle - - float m_fireTime; // How much time has been used to fire the weapon so far. - float m_lastSightTime;// Last time I saw target - float m_persist; // Persistence of firing (how long do I shoot when I can't see) - float m_persist2; // Secondary persistence of firing (randomly shooting when I can't see) - float m_persist2burst;// How long secondary persistence burst lasts - float m_minRange; // Minimum range to aim/track - float m_maxRange; // Max range to aim/track - float m_flMinRange2; - float m_flMaxRange2; - int m_iAmmoCount; // ammo - - Vector m_barrelPos; // Length of the freakin barrel - float m_spriteScale; // Scale of any sprites we shoot - string_t m_iszSpriteSmoke; - string_t m_iszSpriteFlash; - - string_t m_iszMaster; // Master entity (game_team_master or multisource) - - string_t m_soundStartRotate; - string_t m_soundStopRotate; - string_t m_soundLoopRotate; - - float m_flPlayerGracePeriod; - float m_flIgnoreGraceUpto; - float m_flPlayerLockTimeBeforeFire; - float m_flLastSawNonPlayer; - - string_t m_targetEntityName; - Vector m_vTargetPosition; - Vector m_vecNPCIdleTarget; - - // Used for when the gun is attached to another entity - string_t m_iszBarrelAttachment; - int m_nBarrelAttachment; - string_t m_iszBaseAttachment; - - // Used when the gun is actually a part of the parent entity, and pose params aim it - string_t m_iszYawPoseParam; - string_t m_iszPitchPoseParam; - float m_flYawPoseCenter; - float m_flPitchPoseCenter; - bool m_bUsePoseParameters; - - // Lead the target? - bool m_bPerformLeading; - float m_flStartLeadFactor; - float m_flStartLeadFactorTime; - float m_flNextLeadFactor; - float m_flNextLeadFactorTime; - - COutputEvent m_OnFire; - COutputEvent m_OnLoseTarget; - COutputEvent m_OnAquireTarget; - COutputEvent m_OnAmmoDepleted; - COutputEvent m_OnGotController; - COutputEvent m_OnLostController; - COutputEvent m_OnGotPlayerController; - COutputEvent m_OnLostPlayerController; - COutputEvent m_OnReadyToFire; - - CHandle m_hControlVolume; - string_t m_iszControlVolume; - - float m_flNextControllerSearch; - bool m_bShouldFindNPCs; - bool m_bNPCInRoute; - string_t m_iszNPCManPoint; - - bool m_bReadyToFire; - - int m_iEffectHandling; -}; - -#endif // FUNC_TANK_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FUNC_TANK_H +#define FUNC_TANK_H +#ifdef _WIN32 +#pragma once +#endif + +#include "triggers.h" + +#define SF_TANK_ACTIVE 0x0001 +#define SF_TANK_PLAYER 0x0002 +#define SF_TANK_HUMANS 0x0004 +#define SF_TANK_ALIENS 0x0008 +#define SF_TANK_LINEOFSIGHT 0x0010 +#define SF_TANK_CANCONTROL 0x0020 +#define SF_TANK_DAMAGE_KICK 0x0040 // Kick when take damage +#define SF_TANK_AIM_AT_POS 0x0080 // Aim at a particular position +#define SF_TANK_AIM_ASSISTANCE 0x0100 +#define SF_TANK_NPC 0x0200 +#define SF_TANK_NPC_CONTROLLABLE 0x0400 // 1024 +#define SF_TANK_NPC_SET_CONTROLLER 0x0800 // 2048 +#define SF_TANK_ALLOW_PLAYER_HITS 0x1000 // 4096 Allow friendly NPCs to fire upon enemies near the player +#define SF_TANK_IGNORE_RANGE_IN_VIEWCONE 0x2000 // 8192 Don't use range as a factor in determining if something is in view cone +#define SF_TANK_NOTSOLID 0x8000 // 32768 +#define SF_TANK_SOUNDON 0x10000 // FIXME: This is not really a spawnflag! It holds transient state!!! +#define SF_TANK_HACKPLAYERHIT 0x20000 // 131072 Make this func_tank cheat and hit the player regularly + +#define FUNCTANK_DISTANCE_MAX 1200 // 100 ft. +#define FUNCTANK_DISTANCE_MIN_TO_ENEMY 180 +#define FUNCTANK_FIREVOLUME 1000 +#define FUNCTANK_NPC_ROUTE_TIME 5.0f + +// Effect handling +// If the func_tank has a chosen method of handling effects, use that +// instead of the individual effect settings. (muzzleflash, sound, tracer, etc) +enum FUNCTANK_EFFECT_HANDLING +{ + EH_NONE, // Use the effect settings + EH_AR2, // Use AR2 effects + EH_COMBINE_CANNON // Large Combine cannon +}; + +enum TANKBULLET +{ + TANK_BULLET_NONE = 0, + TANK_BULLET_SMALL = 1, + TANK_BULLET_MEDIUM = 2, + TANK_BULLET_LARGE = 3, +}; + +#define MORTAR_BLAST_RADIUS 350 + + +// Custom damage +// env_laser (duration is 0.5 rate of fire) +// rockets +// explosion? + +class CFuncTank : public CBaseEntity +{ + + DECLARE_CLASS( CFuncTank, CBaseEntity ); + +public: + + CFuncTank(); + ~CFuncTank(); + void Spawn( void ); + void Activate( void ); + void Precache( void ); + bool CreateVPhysics( void ); + bool KeyValue( const char *szKeyName, const char *szValue ); + void UpdateOnRemove(); + + int ObjectCaps( void ) + { + return ( BaseClass::ObjectCaps() | FCAP_IMPULSE_USE | FCAP_USE_IN_RADIUS ); + } + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + + int GetAmmoCount( void ) { return m_iAmmoCount; } + + // NPC + bool NPC_FindManPoint( Vector &vecPos ); + bool NPC_HasEnemy( void ); + void NPC_Fire( void ); + void NPC_InterruptRoute( void ); + void NPC_JustSawPlayer( CBaseEntity *pTarget ); + void NPC_SetInRoute( bool bInRoute ) { m_bNPCInRoute = bInRoute; } + void NPC_SetIdleAngle( Vector vecIdle ) { m_vecNPCIdleTarget = vecIdle; } + + // LOS + bool IsEntityInViewCone( CBaseEntity *pEntity ); + bool HasLOSTo( CBaseEntity *pEntity ); + + // Controller + CBaseCombatCharacter *GetController( void ); + bool StartControl( CBaseCombatCharacter *pController ); + void StopControl( void ); + + const float YawCenter() const { return m_yawCenter; } + const float YawCenterWorld() const { return m_yawCenterWorld; } + const float YawRange() const { return m_yawRange; } + const float PitchCenter() const { return m_pitchCenter; } + const float PitchCenterWorld() const { return m_pitchCenterWorld; } + const float PitchRange() const { return m_pitchRange; } + + virtual void PhysicsSimulate( void ); + + virtual void OnStartControlled() {} + virtual void OnStopControlled() {} + + // SF Tests. + inline bool IsControllable( void ) { return ( m_spawnflags & SF_TANK_CANCONTROL ) ? true : false; } + inline bool IsActive( void ) { return ( m_spawnflags & SF_TANK_ACTIVE ) ? true : false; } + inline bool IsNPCControllable( void ) { return ( m_spawnflags & SF_TANK_NPC_CONTROLLABLE ) ? true : false; } + inline bool IsNPCSetController( void ) { return ( m_spawnflags & SF_TANK_NPC_SET_CONTROLLER ) ? true : false; } + + virtual void DoMuzzleFlash( void ); + virtual const char *GetTracerType( void ); + +protected: + virtual float GetShotSpeed() { return 0; } + + virtual Vector WorldBarrelPosition( void ); + void UpdateMatrix( void ); + + float GetNextAttack() const { return m_flNextAttack; } + virtual void SetNextAttack( float flWait ) { m_flNextAttack = flWait; } + + virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, trace_t &tr ); + int GetRandomBurst( void ); + float GetRandomFireTime( void ); + + void CalcPlayerCrosshairTarget( Vector *pVecTarget ); + void CalcNPCEnemyTarget( Vector *pVecTarget ); + + inline bool IsPlayerManned( void ) { return m_hController && m_hController->IsPlayer() && ( m_spawnflags & SF_TANK_PLAYER ); } + inline bool IsNPCManned( void ) { return m_hController && m_hController->MyNPCPointer() && ( m_spawnflags & SF_TANK_NPC ); } + +private: + void TrackTarget( void ); + int DrawDebugTextOverlays(void); + void DrawDebugGeometryOverlays(void); + + virtual void FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); + + void StartRotSound( void ); + void StopRotSound( void ); + + // Input handlers. + void InputActivate( inputdata_t &inputdata ); + void InputDeactivate( inputdata_t &inputdata ); + void InputSetFireRate( inputdata_t &inputdata ); + void InputSetDamage( inputdata_t &inputdata ); + void InputSetTargetDir( inputdata_t &inputdata ); + void InputSetTargetPosition( inputdata_t &inputdata ); + void InputSetTargetEntityName( inputdata_t &inputdata ); + void InputSetTargetEntity( inputdata_t &inputdata ); + void InputClearTargetEntity( inputdata_t &inputdata ); + void InputFindNPCToManTank( inputdata_t &inputdata ); + void InputStopFindingNPCs( inputdata_t &inputdata ); + void InputStartFindingNPCs( inputdata_t &inputdata ); + void InputForceNPCOff( inputdata_t &inputdata ); + void InputSetMaxRange( inputdata_t &inputdata ); + + void TankActivate(void); + void TankDeactivate(void); + + inline bool CanFire( void ); + bool InRange( float range ); + bool InRange2( float flRange2 ); + + void TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType); + + QAngle AimBarrelAt( const Vector &parentTarget ); + + DECLARE_DATADESC(); + + bool OnControls( CBaseEntity *pTest ); + bool HasController( void ); + + CBaseEntity *FindTarget( string_t targetName, CBaseEntity *pActivator ); + + // NPC + void NPC_FindController( void ); + bool NPC_InRoute( void ) { return m_bNPCInRoute; } + bool NPC_InterruptController( void ); + + // Aim the tank at the player crosshair + void AimBarrelAtPlayerCrosshair( QAngle *pAngles ); + + // Aim the tank at the NPC's enemy + void AimBarrelAtNPCEnemy( QAngle *pAngles ); + + // Aim the tank at the func_tank's enemy + void AimFuncTankAtTarget( void ); + + // Returns true if the desired angles are out of range + bool RotateTankToAngles( const QAngle &angles, float *pDistX = NULL, float *pDistY = NULL ); + + // We lost our target! + void LostTarget( void ); + + // Purpose: + void ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition ); + +protected: + virtual void ControllerPostFrame( void ); + + float m_fireLast; // Last time I fired + float m_fireRate; // How many rounds/second + + EHANDLE m_hTarget; + + TANKBULLET m_bulletType; // Bullet type + int m_iBulletDamage; // 0 means use Bullet type's default damage + int m_iBulletDamageVsPlayer; // Damage vs player. 0 means use m_iBulletDamage + +#ifdef HL2_EPISODIC + string_t m_iszAmmoType; // The name of the ammodef that we use when we fire. Bullet damage still comes from keyvalues. + int m_iAmmoType; // The cached index of the ammodef that we use when we fire. +#else + int m_iSmallAmmoType; + int m_iMediumAmmoType; + int m_iLargeAmmoType; +#endif // HL2_EPISODIC + + int m_spread; // firing spread + + EntityMatrix m_parentMatrix; + + Vector m_sightOrigin; // Last sight of target + EHANDLE m_hFuncTankTarget; + + int m_nBulletCount; + +private: + + // This is either the player manning the func_tank, or an NPC. The NPC is either manning the tank, or running + // to the man point. If he's en-route, m_bNPCInRoute will be true. + CHandle m_hController; + + float m_flNextAttack; + Vector m_vecControllerUsePos; + + float m_yawCenter; // "Center" yaw + float m_yawCenterWorld; // "Center" yaw in world space + float m_yawRate; // Max turn rate to track targets + float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) + // Zero is full rotation + float m_yawTolerance; // Tolerance angle + + float m_pitchCenter; // "Center" pitch + float m_pitchCenterWorld; // "Center" pitch in world space + float m_pitchRate; // Max turn rate on pitch + float m_pitchRange; // Range of pitch motion as above + float m_pitchTolerance; // Tolerance angle + + float m_fireTime; // How much time has been used to fire the weapon so far. + float m_lastSightTime;// Last time I saw target + float m_persist; // Persistence of firing (how long do I shoot when I can't see) + float m_persist2; // Secondary persistence of firing (randomly shooting when I can't see) + float m_persist2burst;// How long secondary persistence burst lasts + float m_minRange; // Minimum range to aim/track + float m_maxRange; // Max range to aim/track + float m_flMinRange2; + float m_flMaxRange2; + int m_iAmmoCount; // ammo + + Vector m_barrelPos; // Length of the freakin barrel + float m_spriteScale; // Scale of any sprites we shoot + string_t m_iszSpriteSmoke; + string_t m_iszSpriteFlash; + + string_t m_iszMaster; // Master entity (game_team_master or multisource) + + string_t m_soundStartRotate; + string_t m_soundStopRotate; + string_t m_soundLoopRotate; + + float m_flPlayerGracePeriod; + float m_flIgnoreGraceUpto; + float m_flPlayerLockTimeBeforeFire; + float m_flLastSawNonPlayer; + + string_t m_targetEntityName; + Vector m_vTargetPosition; + Vector m_vecNPCIdleTarget; + + // Used for when the gun is attached to another entity + string_t m_iszBarrelAttachment; + int m_nBarrelAttachment; + string_t m_iszBaseAttachment; + + // Used when the gun is actually a part of the parent entity, and pose params aim it + string_t m_iszYawPoseParam; + string_t m_iszPitchPoseParam; + float m_flYawPoseCenter; + float m_flPitchPoseCenter; + bool m_bUsePoseParameters; + + // Lead the target? + bool m_bPerformLeading; + float m_flStartLeadFactor; + float m_flStartLeadFactorTime; + float m_flNextLeadFactor; + float m_flNextLeadFactorTime; + + COutputEvent m_OnFire; + COutputEvent m_OnLoseTarget; + COutputEvent m_OnAquireTarget; + COutputEvent m_OnAmmoDepleted; + COutputEvent m_OnGotController; + COutputEvent m_OnLostController; + COutputEvent m_OnGotPlayerController; + COutputEvent m_OnLostPlayerController; + COutputEvent m_OnReadyToFire; + + CHandle m_hControlVolume; + string_t m_iszControlVolume; + + float m_flNextControllerSearch; + bool m_bShouldFindNPCs; + bool m_bNPCInRoute; + string_t m_iszNPCManPoint; + + bool m_bReadyToFire; + + int m_iEffectHandling; +}; + +#endif // FUNC_TANK_H diff --git a/dlls/hl2_dll/grenade_ar2.cpp b/dlls/hl2_dll/grenade_ar2.cpp index 97cbe70b..41406cad 100644 --- a/dlls/hl2_dll/grenade_ar2.cpp +++ b/dlls/hl2_dll/grenade_ar2.cpp @@ -152,7 +152,7 @@ void CGrenadeAR2::GrenadeAR2Think( void ) m_fDangerRadius += ( AR2_GRENADE_MAX_DANGER_RADIUS * 0.05 ); } - CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, m_fDangerRadius, 0.2, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, static_cast(m_fDangerRadius), 0.2, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); } void CGrenadeAR2::Event_Killed( const CTakeDamageInfo &info ) diff --git a/dlls/hl2_dll/grenade_bugbait.cpp b/dlls/hl2_dll/grenade_bugbait.cpp index b06c15e5..58f01dc2 100644 --- a/dlls/hl2_dll/grenade_bugbait.cpp +++ b/dlls/hl2_dll/grenade_bugbait.cpp @@ -161,7 +161,7 @@ void CGrenadeBugBait::BugBaitTouch( CBaseEntity *pOther ) pSporeExplosion->SetLocalOrigin( GetAbsOrigin() ); pSporeExplosion->m_flSpawnRate = 8.0f; pSporeExplosion->m_flParticleLifetime = 2.0f; - pSporeExplosion->SetRenderColor( 0.0f, 0.5f, 0.25f, 0.15f ); + pSporeExplosion->SetRenderColor( 0, 1, 0, 0 ); pSporeExplosion->m_flStartSize = 32.0f; pSporeExplosion->m_flEndSize = 64.0f; diff --git a/dlls/hl2_dll/grenade_frag.cpp b/dlls/hl2_dll/grenade_frag.cpp index 15ad3b75..0fbfe1a6 100644 --- a/dlls/hl2_dll/grenade_frag.cpp +++ b/dlls/hl2_dll/grenade_frag.cpp @@ -1,392 +1,392 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "basegrenade_shared.h" -#include "grenade_frag.h" -#include "Sprite.h" -#include "SpriteTrail.h" -#include "soundent.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define FRAG_GRENADE_BLIP_FREQUENCY 1.0f -#define FRAG_GRENADE_BLIP_FAST_FREQUENCY 0.3f - -#define FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP 1.5f - -const float GRENADE_COEFFICIENT_OF_RESTITUTION = 0.2f; - -ConVar sk_plr_dmg_fraggrenade ( "sk_plr_dmg_fraggrenade","0"); -ConVar sk_npc_dmg_fraggrenade ( "sk_npc_dmg_fraggrenade","0"); -ConVar sk_fraggrenade_radius ( "sk_fraggrenade_radius", "0"); - -#define GRENADE_MODEL "models/Weapons/w_grenade.mdl" - -class CGrenadeFrag : public CBaseGrenade -{ - DECLARE_CLASS( CGrenadeFrag, CBaseGrenade ); - -#if !defined( CLIENT_DLL ) - DECLARE_DATADESC(); -#endif - - ~CGrenadeFrag( void ); - -public: - void Spawn( void ); - void OnRestore( void ); - void Precache( void ); - bool CreateVPhysics( void ); - void CreateEffects( void ); - void SetTimer( float detonateDelay, float warnDelay ); - void SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity ); - int OnTakeDamage( const CTakeDamageInfo &inputInfo ); - void BlipSound() { EmitSound( "Grenade.Blip" ); } - void DelayThink(); - void VPhysicsUpdate( IPhysicsObject *pPhysics ); - void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ); - void SetCombineSpawned( bool combineSpawned ) { m_combineSpawned = combineSpawned; } - bool IsCombineSpawned( void ) const { return m_combineSpawned; } - void SetPunted( bool punt ) { m_punted = punt; } - bool WasPunted( void ) const { return m_punted; } - -protected: - CHandle m_pMainGlow; - CHandle m_pGlowTrail; - - float m_flNextBlipTime; - bool m_inSolid; - bool m_combineSpawned; - bool m_punted; -}; - -LINK_ENTITY_TO_CLASS( npc_grenade_frag, CGrenadeFrag ); - -BEGIN_DATADESC( CGrenadeFrag ) - - // Fields - DEFINE_FIELD( m_pMainGlow, FIELD_EHANDLE ), - DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ), - DEFINE_FIELD( m_flNextBlipTime, FIELD_TIME ), - DEFINE_FIELD( m_inSolid, FIELD_BOOLEAN ), - DEFINE_FIELD( m_combineSpawned, FIELD_BOOLEAN ), - DEFINE_FIELD( m_punted, FIELD_BOOLEAN ), - - // Function Pointers - DEFINE_THINKFUNC( DelayThink ), - -END_DATADESC() - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CGrenadeFrag::~CGrenadeFrag( void ) -{ -} - -void CGrenadeFrag::Spawn( void ) -{ - Precache( ); - - SetModel( GRENADE_MODEL ); - - if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() ) - { - m_flDamage = sk_plr_dmg_fraggrenade.GetFloat(); - m_DmgRadius = sk_fraggrenade_radius.GetFloat(); - } - else - { - m_flDamage = sk_npc_dmg_fraggrenade.GetFloat(); - m_DmgRadius = sk_fraggrenade_radius.GetFloat(); - } - - m_takedamage = DAMAGE_YES; - m_iHealth = 1; - - SetSize( -Vector(4,4,4), Vector(4,4,4) ); - SetCollisionGroup( COLLISION_GROUP_WEAPON ); - CreateVPhysics(); - - CreateEffects(); - - BlipSound(); - m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY; - - AddSolidFlags( FSOLID_NOT_STANDABLE ); - - m_combineSpawned = false; - m_punted = false; - - BaseClass::Spawn(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CGrenadeFrag::OnRestore( void ) -{ - CreateEffects(); - - BaseClass::OnRestore(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CGrenadeFrag::CreateEffects( void ) -{ - // Start up the eye glow - m_pMainGlow = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false ); - - int nAttachment = LookupAttachment( "fuse" ); - - if ( m_pMainGlow != NULL ) - { - m_pMainGlow->FollowEntity( this ); - m_pMainGlow->SetAttachment( this, nAttachment ); - m_pMainGlow->SetTransparency( kRenderGlow, 255, 255, 255, 200, kRenderFxNoDissipation ); - m_pMainGlow->SetScale( 0.2f ); - m_pMainGlow->SetGlowProxySize( 4.0f ); - } - - // Start up the eye trail - m_pGlowTrail = CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false ); - - if ( m_pGlowTrail != NULL ) - { - m_pGlowTrail->FollowEntity( this ); - m_pGlowTrail->SetAttachment( this, nAttachment ); - m_pGlowTrail->SetTransparency( kRenderTransAdd, 255, 0, 0, 255, kRenderFxNone ); - m_pGlowTrail->SetStartWidth( 8.0f ); - m_pGlowTrail->SetEndWidth( 1.0f ); - m_pGlowTrail->SetLifeTime( 0.5f ); - } -} - -bool CGrenadeFrag::CreateVPhysics() -{ - // Create the object in the physics system - VPhysicsInitNormal( SOLID_BBOX, 0, false ); - return true; -} - -// this will hit only things that are in newCollisionGroup, but NOT in collisionGroupAlreadyChecked -class CTraceFilterCollisionGroupDelta : public CTraceFilterEntitiesOnly -{ -public: - // It does have a base, but we'll never network anything below here.. - DECLARE_CLASS_NOBASE( CTraceFilterCollisionGroupDelta ); - - CTraceFilterCollisionGroupDelta( const IHandleEntity *passentity, int collisionGroupAlreadyChecked, int newCollisionGroup ) - : m_pPassEnt(passentity), m_collisionGroupAlreadyChecked( collisionGroupAlreadyChecked ), m_newCollisionGroup( newCollisionGroup ) - { - } - - virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) - { - if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) - return false; - CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); - - if ( pEntity ) - { - if ( g_pGameRules->ShouldCollide( m_collisionGroupAlreadyChecked, pEntity->GetCollisionGroup() ) ) - return false; - if ( g_pGameRules->ShouldCollide( m_newCollisionGroup, pEntity->GetCollisionGroup() ) ) - return true; - } - - return false; - } - -protected: - const IHandleEntity *m_pPassEnt; - int m_collisionGroupAlreadyChecked; - int m_newCollisionGroup; -}; - -void CGrenadeFrag::VPhysicsUpdate( IPhysicsObject *pPhysics ) -{ - BaseClass::VPhysicsUpdate( pPhysics ); - Vector vel; - AngularImpulse angVel; - pPhysics->GetVelocity( &vel, &angVel ); - - Vector start = GetAbsOrigin(); - // find all entities that my collision group wouldn't hit, but COLLISION_GROUP_NONE would and bounce off of them as a ray cast - CTraceFilterCollisionGroupDelta filter( this, GetCollisionGroup(), COLLISION_GROUP_NONE ); - trace_t tr; - - // UNDONE: Hull won't work with hitboxes - hits outer hull. But the whole point of this test is to hit hitboxes. -#if 0 - UTIL_TraceHull( start, start + vel * gpGlobals->frametime, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr ); -#else - UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr ); -#endif - if ( tr.startsolid ) - { - if ( !m_inSolid ) - { - // UNDONE: Do a better contact solution that uses relative velocity? - vel *= -GRENADE_COEFFICIENT_OF_RESTITUTION; // bounce backwards - pPhysics->SetVelocity( &vel, NULL ); - } - m_inSolid = true; - return; - } - m_inSolid = false; - if ( tr.DidHit() ) - { - Vector dir = vel; - VectorNormalize(dir); - // send a tiny amount of damage so the character will react to getting bonked - CTakeDamageInfo info( this, GetThrower(), pPhysics->GetMass() * vel, GetAbsOrigin(), 0.1f, DMG_CRUSH ); - tr.m_pEnt->TakeDamage( info ); - - // reflect velocity around normal - vel = -2.0f * tr.plane.normal * DotProduct(vel,tr.plane.normal) + vel; - - // absorb 80% in impact - vel *= GRENADE_COEFFICIENT_OF_RESTITUTION; - angVel *= -0.5f; - pPhysics->SetVelocity( &vel, &angVel ); - } -} - - -void CGrenadeFrag::Precache( void ) -{ - PrecacheModel( GRENADE_MODEL ); - - PrecacheScriptSound( "Grenade.Blip" ); - - PrecacheModel( "sprites/redglow1.vmt" ); - PrecacheModel( "sprites/bluelaser1.vmt" ); - - BaseClass::Precache(); -} - -void CGrenadeFrag::SetTimer( float detonateDelay, float warnDelay ) -{ - m_flDetonateTime = gpGlobals->curtime + detonateDelay; - m_flWarnAITime = gpGlobals->curtime + warnDelay; - SetThink( &CGrenadeFrag::DelayThink ); - SetNextThink( gpGlobals->curtime ); -} - -void CGrenadeFrag::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) -{ -#ifdef HL2MP - SetTimer( FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP, FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP / 2); - SetThrower( pPhysGunUser ); - - BlipSound(); - m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY; - m_bHasWarnedAI = true; -#endif - -#ifdef HL2_EPISODIC - SetThrower( pPhysGunUser ); - SetPunted( true ); -#endif - - BaseClass::OnPhysGunPickup( pPhysGunUser, reason ); -} - -void CGrenadeFrag::DelayThink() -{ - if( gpGlobals->curtime > m_flDetonateTime ) - { - Detonate(); - return; - } - - if( !m_bHasWarnedAI && gpGlobals->curtime >= m_flWarnAITime ) - { -#if !defined( CLIENT_DLL ) - CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 1.5, this ); -#endif - m_bHasWarnedAI = true; - } - - if( gpGlobals->curtime > m_flNextBlipTime ) - { - BlipSound(); - - if( m_bHasWarnedAI ) - { - m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY; - } - else - { - m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY; - } - } - - SetNextThink( gpGlobals->curtime + 0.1 ); -} - -void CGrenadeFrag::SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity ) -{ - IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); - if ( pPhysicsObject ) - { - pPhysicsObject->AddVelocity( &velocity, &angVelocity ); - } -} - -int CGrenadeFrag::OnTakeDamage( const CTakeDamageInfo &inputInfo ) -{ - // Manually apply vphysics because BaseCombatCharacter takedamage doesn't call back to CBaseEntity OnTakeDamage - VPhysicsTakeDamage( inputInfo ); - - // Grenades only suffer blast damage and burn damage. - if( !(inputInfo.GetDamageType() & (DMG_BLAST|DMG_BURN) ) ) - return 0; - - return BaseClass::OnTakeDamage( inputInfo ); -} - -CBaseGrenade *Fraggrenade_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer, bool combineSpawned ) -{ - // Don't set the owner here, or the player can't interact with grenades he's thrown - CGrenadeFrag *pGrenade = (CGrenadeFrag *)CBaseEntity::Create( "npc_grenade_frag", position, angles, pOwner ); - - pGrenade->SetTimer( timer, timer - 1.5f ); - pGrenade->SetVelocity( velocity, angVelocity ); - pGrenade->SetThrower( ToBaseCombatCharacter( pOwner ) ); - pGrenade->m_takedamage = DAMAGE_EVENTS_ONLY; - pGrenade->SetCombineSpawned( combineSpawned ); - - return pGrenade; -} - -bool Fraggrenade_WasPunted( const CBaseEntity *pEntity ) -{ - const CGrenadeFrag *pFrag = dynamic_cast( pEntity ); - if ( pFrag ) - { - return pFrag->WasPunted(); - } - - return false; -} - -bool Fraggrenade_WasCreatedByCombine( const CBaseEntity *pEntity ) -{ - const CGrenadeFrag *pFrag = dynamic_cast( pEntity ); - if ( pFrag ) - { - return pFrag->IsCombineSpawned(); - } - - return false; -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "basegrenade_shared.h" +#include "grenade_frag.h" +#include "Sprite.h" +#include "SpriteTrail.h" +#include "soundent.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define FRAG_GRENADE_BLIP_FREQUENCY 1.0f +#define FRAG_GRENADE_BLIP_FAST_FREQUENCY 0.3f + +#define FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP 1.5f + +const float GRENADE_COEFFICIENT_OF_RESTITUTION = 0.2f; + +ConVar sk_plr_dmg_fraggrenade ( "sk_plr_dmg_fraggrenade","0"); +ConVar sk_npc_dmg_fraggrenade ( "sk_npc_dmg_fraggrenade","0"); +ConVar sk_fraggrenade_radius ( "sk_fraggrenade_radius", "0"); + +#define GRENADE_MODEL "models/Weapons/w_grenade.mdl" + +class CGrenadeFrag : public CBaseGrenade +{ + DECLARE_CLASS( CGrenadeFrag, CBaseGrenade ); + +#if !defined( CLIENT_DLL ) + DECLARE_DATADESC(); +#endif + + ~CGrenadeFrag( void ); + +public: + void Spawn( void ); + void OnRestore( void ); + void Precache( void ); + bool CreateVPhysics( void ); + void CreateEffects( void ); + void SetTimer( float detonateDelay, float warnDelay ); + void SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity ); + int OnTakeDamage( const CTakeDamageInfo &inputInfo ); + void BlipSound() { EmitSound( "Grenade.Blip" ); } + void DelayThink(); + void VPhysicsUpdate( IPhysicsObject *pPhysics ); + void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ); + void SetCombineSpawned( bool combineSpawned ) { m_combineSpawned = combineSpawned; } + bool IsCombineSpawned( void ) const { return m_combineSpawned; } + void SetPunted( bool punt ) { m_punted = punt; } + bool WasPunted( void ) const { return m_punted; } + +protected: + CHandle m_pMainGlow; + CHandle m_pGlowTrail; + + float m_flNextBlipTime; + bool m_inSolid; + bool m_combineSpawned; + bool m_punted; +}; + +LINK_ENTITY_TO_CLASS( npc_grenade_frag, CGrenadeFrag ); + +BEGIN_DATADESC( CGrenadeFrag ) + + // Fields + DEFINE_FIELD( m_pMainGlow, FIELD_EHANDLE ), + DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ), + DEFINE_FIELD( m_flNextBlipTime, FIELD_TIME ), + DEFINE_FIELD( m_inSolid, FIELD_BOOLEAN ), + DEFINE_FIELD( m_combineSpawned, FIELD_BOOLEAN ), + DEFINE_FIELD( m_punted, FIELD_BOOLEAN ), + + // Function Pointers + DEFINE_THINKFUNC( DelayThink ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CGrenadeFrag::~CGrenadeFrag( void ) +{ +} + +void CGrenadeFrag::Spawn( void ) +{ + Precache( ); + + SetModel( GRENADE_MODEL ); + + if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() ) + { + m_flDamage = sk_plr_dmg_fraggrenade.GetFloat(); + m_DmgRadius = sk_fraggrenade_radius.GetFloat(); + } + else + { + m_flDamage = sk_npc_dmg_fraggrenade.GetFloat(); + m_DmgRadius = sk_fraggrenade_radius.GetFloat(); + } + + m_takedamage = DAMAGE_YES; + m_iHealth = 1; + + SetSize( -Vector(4,4,4), Vector(4,4,4) ); + SetCollisionGroup( COLLISION_GROUP_WEAPON ); + CreateVPhysics(); + + CreateEffects(); + + BlipSound(); + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY; + + AddSolidFlags( FSOLID_NOT_STANDABLE ); + + m_combineSpawned = false; + m_punted = false; + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeFrag::OnRestore( void ) +{ + CreateEffects(); + + BaseClass::OnRestore(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeFrag::CreateEffects( void ) +{ + // Start up the eye glow + m_pMainGlow = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false ); + + int nAttachment = LookupAttachment( "fuse" ); + + if ( m_pMainGlow != NULL ) + { + m_pMainGlow->FollowEntity( this ); + m_pMainGlow->SetAttachment( this, nAttachment ); + m_pMainGlow->SetTransparency( kRenderGlow, 255, 255, 255, 200, kRenderFxNoDissipation ); + m_pMainGlow->SetScale( 0.2f ); + m_pMainGlow->SetGlowProxySize( 4.0f ); + } + + // Start up the eye trail + m_pGlowTrail = CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false ); + + if ( m_pGlowTrail != NULL ) + { + m_pGlowTrail->FollowEntity( this ); + m_pGlowTrail->SetAttachment( this, nAttachment ); + m_pGlowTrail->SetTransparency( kRenderTransAdd, 255, 0, 0, 255, kRenderFxNone ); + m_pGlowTrail->SetStartWidth( 8.0f ); + m_pGlowTrail->SetEndWidth( 1.0f ); + m_pGlowTrail->SetLifeTime( 0.5f ); + } +} + +bool CGrenadeFrag::CreateVPhysics() +{ + // Create the object in the physics system + VPhysicsInitNormal( SOLID_BBOX, 0, false ); + return true; +} + +// this will hit only things that are in newCollisionGroup, but NOT in collisionGroupAlreadyChecked +class CTraceFilterCollisionGroupDelta : public CTraceFilterEntitiesOnly +{ +public: + // It does have a base, but we'll never network anything below here.. + DECLARE_CLASS_NOBASE( CTraceFilterCollisionGroupDelta ); + + CTraceFilterCollisionGroupDelta( const IHandleEntity *passentity, int collisionGroupAlreadyChecked, int newCollisionGroup ) + : m_pPassEnt(passentity), m_collisionGroupAlreadyChecked( collisionGroupAlreadyChecked ), m_newCollisionGroup( newCollisionGroup ) + { + } + + virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) + return false; + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + + if ( pEntity ) + { + if ( g_pGameRules->ShouldCollide( m_collisionGroupAlreadyChecked, pEntity->GetCollisionGroup() ) ) + return false; + if ( g_pGameRules->ShouldCollide( m_newCollisionGroup, pEntity->GetCollisionGroup() ) ) + return true; + } + + return false; + } + +protected: + const IHandleEntity *m_pPassEnt; + int m_collisionGroupAlreadyChecked; + int m_newCollisionGroup; +}; + +void CGrenadeFrag::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + BaseClass::VPhysicsUpdate( pPhysics ); + Vector vel; + AngularImpulse angVel; + pPhysics->GetVelocity( &vel, &angVel ); + + Vector start = GetAbsOrigin(); + // find all entities that my collision group wouldn't hit, but COLLISION_GROUP_NONE would and bounce off of them as a ray cast + CTraceFilterCollisionGroupDelta filter( this, GetCollisionGroup(), COLLISION_GROUP_NONE ); + trace_t tr; + + // UNDONE: Hull won't work with hitboxes - hits outer hull. But the whole point of this test is to hit hitboxes. +#if 0 + UTIL_TraceHull( start, start + vel * gpGlobals->frametime, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr ); +#else + UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr ); +#endif + if ( tr.startsolid ) + { + if ( !m_inSolid ) + { + // UNDONE: Do a better contact solution that uses relative velocity? + vel *= -GRENADE_COEFFICIENT_OF_RESTITUTION; // bounce backwards + pPhysics->SetVelocity( &vel, NULL ); + } + m_inSolid = true; + return; + } + m_inSolid = false; + if ( tr.DidHit() ) + { + Vector dir = vel; + VectorNormalize(dir); + // send a tiny amount of damage so the character will react to getting bonked + CTakeDamageInfo info( this, GetThrower(), pPhysics->GetMass() * vel, GetAbsOrigin(), 0.1f, DMG_CRUSH ); + tr.m_pEnt->TakeDamage( info ); + + // reflect velocity around normal + vel = -2.0f * tr.plane.normal * DotProduct(vel,tr.plane.normal) + vel; + + // absorb 80% in impact + vel *= GRENADE_COEFFICIENT_OF_RESTITUTION; + angVel *= -0.5f; + pPhysics->SetVelocity( &vel, &angVel ); + } +} + + +void CGrenadeFrag::Precache( void ) +{ + PrecacheModel( GRENADE_MODEL ); + + PrecacheScriptSound( "Grenade.Blip" ); + + PrecacheModel( "sprites/redglow1.vmt" ); + PrecacheModel( "sprites/bluelaser1.vmt" ); + + BaseClass::Precache(); +} + +void CGrenadeFrag::SetTimer( float detonateDelay, float warnDelay ) +{ + m_flDetonateTime = gpGlobals->curtime + detonateDelay; + m_flWarnAITime = gpGlobals->curtime + warnDelay; + SetThink( &CGrenadeFrag::DelayThink ); + SetNextThink( gpGlobals->curtime ); +} + +void CGrenadeFrag::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) +{ +#ifdef HL2MP + SetTimer( FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP, FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP / 2); + SetThrower( pPhysGunUser ); + + BlipSound(); + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY; + m_bHasWarnedAI = true; +#endif + +#ifdef HL2_EPISODIC + SetThrower( pPhysGunUser ); + SetPunted( true ); +#endif + + BaseClass::OnPhysGunPickup( pPhysGunUser, reason ); +} + +void CGrenadeFrag::DelayThink() +{ + if( gpGlobals->curtime > m_flDetonateTime ) + { + Detonate(); + return; + } + + if( !m_bHasWarnedAI && gpGlobals->curtime >= m_flWarnAITime ) + { +#if !defined( CLIENT_DLL ) + CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 1.5, this ); +#endif + m_bHasWarnedAI = true; + } + + if( gpGlobals->curtime > m_flNextBlipTime ) + { + BlipSound(); + + if( m_bHasWarnedAI ) + { + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY; + } + else + { + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY; + } + } + + SetNextThink( gpGlobals->curtime + 0.1 ); +} + +void CGrenadeFrag::SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity ) +{ + IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); + if ( pPhysicsObject ) + { + pPhysicsObject->AddVelocity( &velocity, &angVelocity ); + } +} + +int CGrenadeFrag::OnTakeDamage( const CTakeDamageInfo &inputInfo ) +{ + // Manually apply vphysics because BaseCombatCharacter takedamage doesn't call back to CBaseEntity OnTakeDamage + VPhysicsTakeDamage( inputInfo ); + + // Grenades only suffer blast damage and burn damage. + if( !(inputInfo.GetDamageType() & (DMG_BLAST|DMG_BURN) ) ) + return 0; + + return BaseClass::OnTakeDamage( inputInfo ); +} + +CBaseGrenade *Fraggrenade_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer, bool combineSpawned ) +{ + // Don't set the owner here, or the player can't interact with grenades he's thrown + CGrenadeFrag *pGrenade = (CGrenadeFrag *)CBaseEntity::Create( "npc_grenade_frag", position, angles, pOwner ); + + pGrenade->SetTimer( timer, timer - 1.5f ); + pGrenade->SetVelocity( velocity, angVelocity ); + pGrenade->SetThrower( ToBaseCombatCharacter( pOwner ) ); + pGrenade->m_takedamage = DAMAGE_EVENTS_ONLY; + pGrenade->SetCombineSpawned( combineSpawned ); + + return pGrenade; +} + +bool Fraggrenade_WasPunted( const CBaseEntity *pEntity ) +{ + const CGrenadeFrag *pFrag = dynamic_cast( pEntity ); + if ( pFrag ) + { + return pFrag->WasPunted(); + } + + return false; +} + +bool Fraggrenade_WasCreatedByCombine( const CBaseEntity *pEntity ) +{ + const CGrenadeFrag *pFrag = dynamic_cast( pEntity ); + if ( pFrag ) + { + return pFrag->IsCombineSpawned(); + } + + return false; +} diff --git a/dlls/hl2_dll/grenade_homer.cpp b/dlls/hl2_dll/grenade_homer.cpp index cb866ca8..08f60910 100644 --- a/dlls/hl2_dll/grenade_homer.cpp +++ b/dlls/hl2_dll/grenade_homer.cpp @@ -145,7 +145,7 @@ void CGrenadeHomer::SetSpin(float flSpinMagnitude, float flSpinSpeed) { m_flSpinMagnitude = flSpinMagnitude; m_flSpinSpeed = flSpinSpeed; - m_flSpinOffset = random->RandomInt(-m_flSpinSpeed,m_flSpinSpeed); + m_flSpinOffset = random->RandomFloat(-m_flSpinSpeed,m_flSpinSpeed); } //------------------------------------------------------------------------------ diff --git a/dlls/hl2_dll/hl2_client.cpp b/dlls/hl2_dll/hl2_client.cpp index 9169891e..e4abea42 100644 --- a/dlls/hl2_dll/hl2_client.cpp +++ b/dlls/hl2_dll/hl2_client.cpp @@ -18,7 +18,7 @@ #include "hl2_gamerules.h" #include "gamerules.h" #include "teamplay_gamerules.h" -#include "EntityList.h" +#include "entitylist.h" #include "physics.h" #include "game.h" #include "player_resource.h" diff --git a/dlls/hl2_dll/hl2_player.cpp b/dlls/hl2_dll/hl2_player.cpp index 873d6a6d..65a29574 100644 --- a/dlls/hl2_dll/hl2_player.cpp +++ b/dlls/hl2_dll/hl2_player.cpp @@ -1,3465 +1,3465 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Player for HL2. -// -//=============================================================================// - -#include "cbase.h" -#include "hl2_player.h" -#include "globalstate.h" -#include "game.h" -#include "gamerules.h" -#include "trains.h" -#include "basehlcombatweapon_shared.h" -#include "vcollide_parse.h" -#include "in_buttons.h" -#include "ai_interactions.h" -#include "ai_squad.h" -#include "igamemovement.h" -#include "ai_hull.h" -#include "hl2_shareddefs.h" -#include "info_camera_link.h" -#include "point_camera.h" -#include "engine/IEngineSound.h" -#include "ndebugoverlay.h" -#include "iservervehicle.h" -#include "IVehicle.h" -#include "globals.h" -#include "collisionutils.h" -#include "coordsize.h" -#include "effect_color_tables.h" -#include "vphysics/player_controller.h" -#include "player_pickup.h" -#include "weapon_physcannon.h" -#include "script_intro.h" -#include "effect_dispatch_data.h" -#include "te_effect_dispatch.h" -#include "ai_basenpc.h" -#include "AI_Criteria.h" -#include "npc_barnacle.h" -#include "entitylist.h" -#include "env_zoom.h" -#include "hl2_gamerules.h" -#include "prop_combine_ball.h" -#include "datacache/imdlcache.h" -#include "eventqueue.h" - -#ifdef HL2_EPISODIC -#include "npc_alyx_episodic.h" -#endif - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern ConVar weapon_showproficiency; -extern ConVar autoaim_max_dist; - -// Do not touch with without seeing me, please! (sjb) -// For consistency's sake, enemy gunfire is traced against a scaled down -// version of the player's hull, not the hitboxes for the player's model -// because the player isn't aware of his model, and can't do anything about -// preventing headshots and other such things. Also, game difficulty will -// not change if the model changes. This is the value by which to scale -// the X/Y of the player's hull to get the volume to trace bullets against. -#define PLAYER_HULL_REDUCTION 0.70 - -// This switches between the single primary weapon, and multiple weapons with buckets approach (jdw) -#define HL2_SINGLE_PRIMARY_WEAPON_MODE 0 - -#define TIME_IGNORE_FALL_DAMAGE 10.0 - -extern int gEvilImpulse101; - -ConVar sv_autojump( "sv_autojump", "0" ); - -ConVar hl2_walkspeed( "hl2_walkspeed", "150" ); -ConVar hl2_normspeed( "hl2_normspeed", "190" ); -ConVar hl2_sprintspeed( "hl2_sprintspeed", "320" ); - -ConVar hl2_xbox_aiming( "hl2_xbox_aiming", "1" ); - -ConVar hl2_darkness_flashlight_factor ( "hl2_darkness_flashlight_factor", "1" ); - -#ifdef HL2MP - #define HL2_WALK_SPEED 150 - #define HL2_NORM_SPEED 190 - #define HL2_SPRINT_SPEED 320 -#else - #define HL2_WALK_SPEED hl2_walkspeed.GetFloat() - #define HL2_NORM_SPEED hl2_normspeed.GetFloat() - #define HL2_SPRINT_SPEED hl2_sprintspeed.GetFloat() -#endif - -#ifdef _XBOX - #define AUTOSPRINT_MIN_DURATION 1.0f -#endif//_XBOX - -ConVar player_showpredictedposition( "player_showpredictedposition", "0" ); -ConVar player_showpredictedposition_timestep( "player_showpredictedposition_timestep", "1.0" ); - -ConVar player_squad_transient_commands( "player_squad_transient_commands", "1", FCVAR_REPLICATED ); -ConVar player_squad_double_tap_time( "player_squad_double_tap_time", "0.25" ); - -ConVar sv_infinite_aux_power( "sv_infinite_aux_power", "0", FCVAR_CHEAT ); - -ConVar autoaim_unlock_target( "autoaim_unlock_target", "0.8666" ); - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ -void CC_ToggleZoom( void ) -{ - CBasePlayer* pPlayer = UTIL_GetCommandClient(); - - if( pPlayer ) - { - CHL2_Player *pHL2Player = dynamic_cast(pPlayer); - - if( pHL2Player && pHL2Player->IsSuitEquipped() ) - { - pHL2Player->ToggleZoom(); - } - } -} - -static ConCommand toggle_zoom("toggle_zoom", CC_ToggleZoom, "Toggles zoom display" ); - -// ConVar cl_forwardspeed( "cl_forwardspeed", "400", FCVAR_CHEAT ); // Links us to the client's version -ConVar xc_crouch_range( "xc_crouch_range", "0.85", FCVAR_ARCHIVE, "Percentarge [1..0] of joystick range to allow ducking within" ); // Only 1/2 of the range is used -ConVar xc_use_crouch_limiter( "xc_use_crouch_limiter", "0", FCVAR_ARCHIVE, "Use the crouch limiting logic on the controller" ); - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ -void CC_ToggleDuck( void ) -{ - CBasePlayer* pPlayer = UTIL_GetCommandClient(); - if ( pPlayer == NULL ) - return; - - // Cannot be frozen - if ( pPlayer->GetFlags() & FL_FROZEN ) - return; - - static bool bChecked = false; - static ConVar *pCVcl_forwardspeed = NULL; - if ( !bChecked ) - { - bChecked = true; - pCVcl_forwardspeed = ( ConVar * )cvar->FindVar( "cl_forwardspeed" ); - } - - - // If we're not ducked, do extra checking - if ( xc_use_crouch_limiter.GetBool() ) - { - if ( pPlayer->GetToggledDuckState() == false ) - { - float flForwardSpeed = 400.0f; - if ( pCVcl_forwardspeed ) - { - flForwardSpeed = pCVcl_forwardspeed->GetFloat(); - } - - flForwardSpeed = max( 1.0f, flForwardSpeed ); - - // Make sure we're not in the blindspot on the crouch detection - float flStickDistPerc = ( pPlayer->GetStickDist() / flForwardSpeed ); // Speed is the magnitude - if ( flStickDistPerc > xc_crouch_range.GetFloat() ) - return; - } - } - - // Toggle the duck - pPlayer->ToggleDuck(); -} - -static ConCommand toggle_duck("toggle_duck", CC_ToggleDuck, "Toggles duck" ); - -#ifndef HL2MP -LINK_ENTITY_TO_CLASS( player, CHL2_Player ); -#endif - -PRECACHE_REGISTER(player); - -CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull ); - -BEGIN_SIMPLE_DATADESC( LadderMove_t ) - DEFINE_FIELD( m_bForceLadderMove, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bForceMount, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flStartTime, FIELD_TIME ), - DEFINE_FIELD( m_flArrivalTime, FIELD_TIME ), - DEFINE_FIELD( m_vecGoalPosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vecStartPosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_hForceLadder, FIELD_EHANDLE ), - DEFINE_FIELD( m_hReservedSpot, FIELD_EHANDLE ), -END_DATADESC() - -// Global Savedata for HL2 player -BEGIN_DATADESC( CHL2_Player ) - - DEFINE_FIELD( m_nControlClass, FIELD_INTEGER ), - DEFINE_EMBEDDED( m_HL2Local ), - - DEFINE_FIELD( m_bSprintEnabled, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flTimeAllSuitDevicesOff, FIELD_TIME ), - DEFINE_FIELD( m_fIsSprinting, FIELD_BOOLEAN ), - DEFINE_FIELD( m_fIsWalking, FIELD_BOOLEAN ), - - /* - // These are initialized every time the player calls Activate() - DEFINE_FIELD( m_bIsAutoSprinting, FIELD_BOOLEAN ), - DEFINE_FIELD( m_fAutoSprintMinTime, FIELD_TIME ), - */ - - // Field is used within a single tick, no need to save restore - // DEFINE_FIELD( m_bPlayUseDenySound, FIELD_BOOLEAN ), - // m_pPlayerAISquad reacquired on load - - DEFINE_AUTO_ARRAY( m_vecMissPositions, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_nNumMissPositions, FIELD_INTEGER ), - - // m_pPlayerAISquad - DEFINE_EMBEDDED( m_CommanderUpdateTimer ), - // m_RealTimeLastSquadCommand - DEFINE_FIELD( m_QueuedCommand, FIELD_INTEGER ), - - DEFINE_FIELD( m_flTimeIgnoreFallDamage, FIELD_TIME ), - - // Suit power fields - DEFINE_FIELD( m_flSuitPowerLoad, FIELD_FLOAT ), - - DEFINE_FIELD( m_flIdleTime, FIELD_TIME ), - DEFINE_FIELD( m_flMoveTime, FIELD_TIME ), - DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ), - DEFINE_FIELD( m_flTargetFindTime, FIELD_TIME ), - - DEFINE_FIELD( m_flAdmireGlovesAnimTime, FIELD_TIME ), - DEFINE_FIELD( m_flNextFlashlightCheckTime, FIELD_TIME ), - DEFINE_FIELD( m_flFlashlightPowerDrainScale, FIELD_FLOAT ), - DEFINE_FIELD( m_bFlashlightDisabled, FIELD_BOOLEAN ), - - DEFINE_FIELD( m_hLockedAutoAimEntity, FIELD_EHANDLE ), - - DEFINE_EMBEDDED( m_LowerWeaponTimer ), - DEFINE_EMBEDDED( m_AutoaimTimer ), - - DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreFallDamage", InputIgnoreFallDamage ), - DEFINE_INPUTFUNC( FIELD_VOID, "OnSquadMemberKilled", OnSquadMemberKilled ), - DEFINE_INPUTFUNC( FIELD_VOID, "DisableFlashlight", InputDisableFlashlight ), - DEFINE_INPUTFUNC( FIELD_VOID, "EnableFlashlight", InputEnableFlashlight ), - DEFINE_INPUTFUNC( FIELD_VOID, "ForceDropPhysObjects", InputForceDropPhysObjects ), - - DEFINE_SOUNDPATCH( m_sndLeeches ), - DEFINE_SOUNDPATCH( m_sndWaterSplashes ), - - DEFINE_FIELD( m_flArmorReductionTime, FIELD_TIME ), - DEFINE_FIELD( m_iArmorReductionFrom, FIELD_INTEGER ), - - DEFINE_FIELD( m_flTimeUseSuspended, FIELD_TIME ), - - //DEFINE_FIELD( m_hPlayerProxy, FIELD_EHANDLE ), //Shut up class check! - -END_DATADESC() - -CHL2_Player::CHL2_Player() -{ - m_nNumMissPositions = 0; - m_pPlayerAISquad = 0; - m_bSprintEnabled = true; - - m_flArmorReductionTime = 0.0f; - m_iArmorReductionFrom = 0; -} - -// -// SUIT POWER DEVICES -// -#define SUITPOWER_CHARGE_RATE 12.5 // 100 units in 8 seconds - -#ifdef HL2MP - CSuitPowerDevice SuitDeviceSprint( bits_SUIT_DEVICE_SPRINT, 25.0f ); // 100 units in 4 seconds -#else - CSuitPowerDevice SuitDeviceSprint( bits_SUIT_DEVICE_SPRINT, 12.5f ); // 100 units in 8 seconds -#endif - -#ifdef HL2_EPISODIC - CSuitPowerDevice SuitDeviceFlashlight( bits_SUIT_DEVICE_FLASHLIGHT, 1.111 ); // 100 units in 90 second -#else - CSuitPowerDevice SuitDeviceFlashlight( bits_SUIT_DEVICE_FLASHLIGHT, 2.222 ); // 100 units in 45 second -#endif -CSuitPowerDevice SuitDeviceBreather( bits_SUIT_DEVICE_BREATHER, 6.7f ); // 100 units in 15 seconds (plus three padded seconds) - - -IMPLEMENT_SERVERCLASS_ST(CHL2_Player, DT_HL2_Player) - SendPropDataTable(SENDINFO_DT(m_HL2Local), &REFERENCE_SEND_TABLE(DT_HL2Local), SendProxy_SendLocalDataTable), - SendPropBool( SENDINFO(m_fIsSprinting) ), -END_SEND_TABLE() - - -void CHL2_Player::Precache( void ) -{ - BaseClass::Precache(); - - PrecacheScriptSound( "HL2Player.SprintNoPower" ); - PrecacheScriptSound( "HL2Player.SprintStart" ); - PrecacheScriptSound( "HL2Player.UseDeny" ); - PrecacheScriptSound( "HL2Player.FlashLightOn" ); - PrecacheScriptSound( "HL2Player.FlashLightOff" ); - PrecacheScriptSound( "HL2Player.PickupWeapon" ); - PrecacheScriptSound( "HL2Player.TrainUse" ); - PrecacheScriptSound( "HL2Player.Use" ); - PrecacheScriptSound( "HL2Player.BurnPain" ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2_Player::CheckSuitZoom( void ) -{ -//#ifndef _XBOX - //Adrian - No zooming without a suit! - if ( IsSuitEquipped() ) - { - if ( m_afButtonReleased & IN_ZOOM ) - { - StopZooming(); - } - else if ( m_afButtonPressed & IN_ZOOM ) - { - StartZooming(); - } - } -//#endif//_XBOX -} - -void CHL2_Player::EquipSuit( bool bPlayEffects ) -{ - MDLCACHE_CRITICAL_SECTION(); - BaseClass::EquipSuit(); - - m_HL2Local.m_bDisplayReticle = true; - - if ( bPlayEffects == true ) - { - StartAdmireGlovesAnimation(); - } -} - -void CHL2_Player::RemoveSuit( void ) -{ - BaseClass::RemoveSuit(); - - m_HL2Local.m_bDisplayReticle = false; -} - -void CHL2_Player::HandleSpeedChanges( void ) -{ - int buttonsChanged = m_afButtonPressed | m_afButtonReleased; - - if( buttonsChanged & IN_SPEED ) - { - // The state of the sprint/run button has changed. - if ( IsSuitEquipped() ) - { - if ( !(m_afButtonPressed & IN_SPEED) && IsSprinting() ) - { -#ifndef _XBOX - StopSprinting(); -#endif - } - else if ( (m_afButtonPressed & IN_SPEED) && !IsSprinting() ) - { - if ( CanSprint() ) - { -#ifdef _XBOX - StartAutoSprint(); -#else - StartSprinting(); -#endif - } - else - { - // Reset key, so it will be activated post whatever is suppressing it. - m_nButtons &= ~IN_SPEED; - } - } -#ifdef _XBOX - else if( (m_afButtonPressed & IN_SPEED) && IsSprinting() ) - { - StopSprinting(); - } -#endif//_XBOX - } - else - { - // ROBIN: We're trialling Sprint not affecting movement speed without the HEV suit - /* - // The state of the WALK button has changed. - if( !IsWalking() && !(m_afButtonPressed & IN_SPEED) ) - { - StartWalking(); - } - else if( IsWalking() && !IsSprinting() && (m_afButtonPressed & IN_SPEED) && !(m_nButtons & IN_DUCK) ) - { - StopWalking(); - } - */ - } - } - else if( buttonsChanged & IN_WALK ) - { - if ( IsSuitEquipped() ) - { - // The state of the WALK button has changed. - if( IsWalking() && !(m_afButtonPressed & IN_WALK) ) - { - StopWalking(); - } - else if( !IsWalking() && !IsSprinting() && (m_afButtonPressed & IN_WALK) && !(m_nButtons & IN_DUCK) ) - { - StartWalking(); - } - } - } - - if ( IsSuitEquipped() && m_fIsWalking && !(m_nButtons & IN_WALK) ) - StopWalking(); -} - -//----------------------------------------------------------------------------- -// This happens when we powerdown from the mega physcannon to the regular one -//----------------------------------------------------------------------------- -void CHL2_Player::HandleArmorReduction( void ) -{ - if ( m_flArmorReductionTime < gpGlobals->curtime ) - return; - - if ( ArmorValue() <= 0 ) - return; - - float flPercent = 1.0f - (( m_flArmorReductionTime - gpGlobals->curtime ) / ARMOR_DECAY_TIME ); - - int iArmor = Lerp( flPercent, m_iArmorReductionFrom, 0 ); - - SetArmorValue( iArmor ); -} - -//----------------------------------------------------------------------------- -// Purpose: Allow pre-frame adjustments on the player -//----------------------------------------------------------------------------- -void CHL2_Player::PreThink(void) -{ - if ( player_showpredictedposition.GetBool() ) - { - Vector predPos; - - UTIL_PredictedPosition( this, player_showpredictedposition_timestep.GetFloat(), &predPos ); - - NDebugOverlay::Box( predPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), 0, 255, 0, 0, 0.01f ); - NDebugOverlay::Line( GetAbsOrigin(), predPos, 0, 255, 0, 0, 0.01f ); - } - - // Riding a vehicle? - if ( IsInAVehicle() ) - { - VPROF( "CHL2_Player::PreThink-Vehicle" ); - // make sure we update the client, check for timed damage and update suit even if we are in a vehicle - UpdateClientData(); - CheckTimeBasedDamage(); - - // Allow the suit to recharge when in the vehicle. - SuitPower_Update(); - CheckSuitUpdate(); - CheckSuitZoom(); - - WaterMove(); - return; - } - - // This is an experiment of mine- autojumping! - // only affects you if sv_autojump is nonzero. - if( (GetFlags() & FL_ONGROUND) && sv_autojump.GetFloat() != 0 ) - { - VPROF( "CHL2_Player::PreThink-Autojump" ); - // check autojump - Vector vecCheckDir; - - vecCheckDir = GetAbsVelocity(); - - float flVelocity = VectorNormalize( vecCheckDir ); - - if( flVelocity > 200 ) - { - // Going fast enough to autojump - vecCheckDir = WorldSpaceCenter() + vecCheckDir * 34 - Vector( 0, 0, 16 ); - - trace_t tr; - - UTIL_TraceHull( WorldSpaceCenter() - Vector( 0, 0, 16 ), vecCheckDir, NAI_Hull::Mins(HULL_TINY_CENTERED),NAI_Hull::Maxs(HULL_TINY_CENTERED), MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER, &tr ); - - //NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 10 ); - - if( tr.fraction == 1.0 && !tr.startsolid ) - { - // Now trace down! - UTIL_TraceLine( vecCheckDir, vecCheckDir - Vector( 0, 0, 64 ), MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &tr ); - - //NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 10 ); - - if( tr.fraction == 1.0 && !tr.startsolid ) - { - // !!!HACKHACK - // I KNOW, I KNOW, this is definitely not the right way to do this, - // but I'm prototyping! (sjb) - Vector vecNewVelocity = GetAbsVelocity(); - vecNewVelocity.z += 250; - SetAbsVelocity( vecNewVelocity ); - } - } - } - } - - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-Speed" ); - HandleSpeedChanges(); -#ifdef HL2_EPISODIC - HandleArmorReduction(); -#endif - -#ifdef _XBOX - if( m_bIsAutoSprinting ) - { - // Stop sprinting if the player lets off the stick for a moment. - if( GetStickDist() == 0.0f ) - { - if( gpGlobals->curtime > m_fAutoSprintMinTime ) - { - StopSprinting(); - } - } - else - { - // Stop sprinting one half second after the player stops inputting with the move stick. - m_fAutoSprintMinTime = gpGlobals->curtime + 0.5f; - } - } -#endif//_XBOX - VPROF_SCOPE_END(); - - if ( g_fGameOver || IsPlayerLockedInPlace() ) - return; // finale - - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-ItemPreFrame" ); - ItemPreFrame( ); - VPROF_SCOPE_END(); - - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-WaterMove" ); - WaterMove(); - VPROF_SCOPE_END(); - - if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) - m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; - else - m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT; - - - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CommanderUpdate" ); - CommanderUpdate(); - VPROF_SCOPE_END(); - - // Operate suit accessories and manage power consumption/charge - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-SuitPower_Update" ); - SuitPower_Update(); - VPROF_SCOPE_END(); - - // checks if new client data (for HUD and view control) needs to be sent to the client - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-UpdateClientData" ); - UpdateClientData(); - VPROF_SCOPE_END(); - - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckTimeBasedDamage" ); - CheckTimeBasedDamage(); - VPROF_SCOPE_END(); - - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckSuitUpdate" ); - CheckSuitUpdate(); - VPROF_SCOPE_END(); - - VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckSuitZoom" ); - CheckSuitZoom(); - VPROF_SCOPE_END(); - - if (m_lifeState >= LIFE_DYING) - { - PlayerDeathThink(); - return; - } - -#ifdef HL2_EPISODIC - CheckFlashlight(); -#endif - - // So the correct flags get sent to client asap. - // - if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) - AddFlag( FL_ONTRAIN ); - else - RemoveFlag( FL_ONTRAIN ); - - // Train speed control - if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) - { - CBaseEntity *pTrain = GetGroundEntity(); - float vel; - - if ( pTrain ) - { - if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) - pTrain = NULL; - } - - if ( !pTrain ) - { - if ( GetActiveWeapon() && (GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) - { - m_iTrain = TRAIN_ACTIVE | TRAIN_NEW; - - if ( m_nButtons & IN_FORWARD ) - { - m_iTrain |= TRAIN_FAST; - } - else if ( m_nButtons & IN_BACK ) - { - m_iTrain |= TRAIN_BACK; - } - else - { - m_iTrain |= TRAIN_NEUTRAL; - } - return; - } - else - { - trace_t trainTrace; - // Maybe this is on the other side of a level transition - UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38), - MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace ); - - if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt ) - pTrain = trainTrace.m_pEnt; - - - if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(this) ) - { -// Warning( "In train mode with no train!\n" ); - m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; - m_iTrain = TRAIN_NEW|TRAIN_OFF; - return; - } - } - } - else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) ) - { - // Turn off the train if you jump, strafe, or the train controls go dead - m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; - m_iTrain = TRAIN_NEW|TRAIN_OFF; - return; - } - - SetAbsVelocity( vec3_origin ); - vel = 0; - if ( m_afButtonPressed & IN_FORWARD ) - { - vel = 1; - pTrain->Use( this, this, USE_SET, (float)vel ); - } - else if ( m_afButtonPressed & IN_BACK ) - { - vel = -1; - pTrain->Use( this, this, USE_SET, (float)vel ); - } - - if (vel) - { - m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed()); - m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; - } - } - else if (m_iTrain & TRAIN_ACTIVE) - { - m_iTrain = TRAIN_NEW; // turn off train - } - - - // - // If we're not on the ground, we're falling. Update our falling velocity. - // - if ( !( GetFlags() & FL_ONGROUND ) ) - { - m_Local.m_flFallVelocity = -GetAbsVelocity().z; - } - - if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) - { - bool bOnBarnacle = false; - CNPC_Barnacle *pBarnacle = NULL; - do - { - // FIXME: Not a good or fast solution, but maybe it will catch the bug! - pBarnacle = (CNPC_Barnacle*)gEntList.FindEntityByClassname( pBarnacle, "npc_barnacle" ); - if ( pBarnacle ) - { - if ( pBarnacle->GetEnemy() == this ) - { - bOnBarnacle = true; - } - } - } while ( pBarnacle ); - - if ( !bOnBarnacle ) - { - Warning( "Attached to barnacle?\n" ); - Assert( 0 ); - m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; - } - else - { - SetAbsVelocity( vec3_origin ); - } - } - // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? - - // Update weapon's ready status - UpdateWeaponPosture(); - - // Disallow shooting while zooming -#ifdef _XBOX - if ( IsZooming() ) - { - if( !GetActiveWeapon() || !GetActiveWeapon()->IsWeaponZoomed() ) - { - // If not zoomed because of the weapon itself, do not attack. - m_nButtons &= ~(IN_ATTACK|IN_ATTACK2); - } - } -#else - if ( m_nButtons & IN_ZOOM ) - { - //FIXME: Held weapons like the grenade get sad when this happens -#ifdef HL2_EPISODIC - // Episodic allows players to zoom while using a func_tank - if ( !m_hUseEntity || GetActiveWeapon()->IsWeaponVisible() ) -#endif - m_nButtons &= ~(IN_ATTACK|IN_ATTACK2); - } -#endif//_XBOX -} - -void CHL2_Player::PostThink( void ) -{ - BaseClass::PostThink(); - - if ( !g_fGameOver && !IsPlayerLockedInPlace() && IsAlive() ) - { - HandleAdmireGlovesAnimation(); - } -} - -void CHL2_Player::StartAdmireGlovesAnimation( void ) -{ - CBaseViewModel *vm = GetViewModel( 0 ); - - if ( vm && !GetActiveWeapon() ) - { - vm->SetWeaponModel( "models/weapons/v_hands.mdl", NULL ); - ShowViewModel( true ); - - int idealSequence = vm->SelectWeightedSequence( ACT_VM_IDLE ); - - if ( idealSequence >= 0 ) - { - vm->SendViewModelMatchingSequence( idealSequence ); - m_flAdmireGlovesAnimTime = gpGlobals->curtime + vm->SequenceDuration( idealSequence ); - } - } -} - -void CHL2_Player::HandleAdmireGlovesAnimation( void ) -{ - CBaseViewModel *pVM = GetViewModel(); - - if ( pVM && pVM->GetOwningWeapon() == NULL ) - { - if ( m_flAdmireGlovesAnimTime != 0.0 ) - { - if ( m_flAdmireGlovesAnimTime > gpGlobals->curtime ) - { - pVM->m_flPlaybackRate = 1.0f; - pVM->StudioFrameAdvance( ); - } - else if ( m_flAdmireGlovesAnimTime < gpGlobals->curtime ) - { - m_flAdmireGlovesAnimTime = 0.0f; - pVM->SetWeaponModel( NULL, NULL ); - } - } - } - else - m_flAdmireGlovesAnimTime = 0.0f; -} - -#define HL2PLAYER_RELOADGAME_ATTACK_DELAY 1.0f - -void CHL2_Player::Activate( void ) -{ - BaseClass::Activate(); - InitSprinting(); - -#ifdef HL2_EPISODIC - - // Delay attacks by 1 second after loading a game. - if ( GetActiveWeapon() ) - { - float flRemaining = GetActiveWeapon()->m_flNextPrimaryAttack - gpGlobals->curtime; - - if ( flRemaining < HL2PLAYER_RELOADGAME_ATTACK_DELAY ) - { - GetActiveWeapon()->m_flNextPrimaryAttack = gpGlobals->curtime + HL2PLAYER_RELOADGAME_ATTACK_DELAY; - } - - flRemaining = GetActiveWeapon()->m_flNextSecondaryAttack - gpGlobals->curtime; - - if ( flRemaining < HL2PLAYER_RELOADGAME_ATTACK_DELAY ) - { - GetActiveWeapon()->m_flNextSecondaryAttack = gpGlobals->curtime + HL2PLAYER_RELOADGAME_ATTACK_DELAY; - } - } - -#endif -} - -//------------------------------------------------------------------------------ -// Purpose : -// Input : -// Output : -//------------------------------------------------------------------------------ -Class_T CHL2_Player::Classify ( void ) -{ - // If player controlling another entity? If so, return this class - if (m_nControlClass != CLASS_NONE) - { - return m_nControlClass; - } - else - { - if(IsInAVehicle()) - { - IServerVehicle *pVehicle = GetVehicle(); - return pVehicle->ClassifyPassenger( this, CLASS_PLAYER ); - } - else - { - return CLASS_PLAYER; - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: This is a generic function (to be implemented by sub-classes) to -// handle specific interactions between different types of characters -// (For example the barnacle grabbing an NPC) -// Input : Constant for the type of interaction -// Output : true - if sub-class has a response for the interaction -// false - if sub-class has no response -//----------------------------------------------------------------------------- -bool CHL2_Player::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) -{ - if ( interactionType == g_interactionBarnacleVictimDangle ) - return false; - - if (interactionType == g_interactionBarnacleVictimReleased) - { - m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; - SetMoveType( MOVETYPE_WALK ); - return true; - } - else if (interactionType == g_interactionBarnacleVictimGrab) - { -#ifdef HL2_EPISODIC - CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); - if ( pAlyx ) - { - // Make Alyx totally hate this barnacle so that she saves the player. - int priority; - - priority = pAlyx->IRelationPriority(sourceEnt); - pAlyx->AddEntityRelationship( sourceEnt, D_HT, priority + 5 ); - } -#endif//HL2_EPISODIC - - m_afPhysicsFlags |= PFLAG_ONBARNACLE; - ClearUseEntity(); - return true; - } - return false; -} - - -void CHL2_Player::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) -{ - // Handle FL_FROZEN. - if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) - { - ucmd->forwardmove = 0; - ucmd->sidemove = 0; - ucmd->upmove = 0; - ucmd->buttons &= ~IN_USE; - } - - // Can't use stuff while dead - if ( IsDead() ) - { - ucmd->buttons &= ~IN_USE; - } - - //Update our movement information - if ( ( ucmd->forwardmove != 0 ) || ( ucmd->sidemove != 0 ) || ( ucmd->upmove != 0 ) ) - { - m_flIdleTime -= TICK_INTERVAL * 2.0f; - - if ( m_flIdleTime < 0.0f ) - { - m_flIdleTime = 0.0f; - } - - m_flMoveTime += TICK_INTERVAL; - - if ( m_flMoveTime > 4.0f ) - { - m_flMoveTime = 4.0f; - } - } - else - { - m_flIdleTime += TICK_INTERVAL; - - if ( m_flIdleTime > 4.0f ) - { - m_flIdleTime = 4.0f; - } - - m_flMoveTime -= TICK_INTERVAL * 2.0f; - - if ( m_flMoveTime < 0.0f ) - { - m_flMoveTime = 0.0f; - } - } - - //Msg("Player time: [ACTIVE: %f]\t[IDLE: %f]\n", m_flMoveTime, m_flIdleTime ); - - BaseClass::PlayerRunCommand( ucmd, moveHelper ); -} - -//----------------------------------------------------------------------------- -// Purpose: Sets HL2 specific defaults. -//----------------------------------------------------------------------------- -void CHL2_Player::Spawn(void) -{ - -#ifndef HL2MP - SetModel( "models/player.mdl" ); -#endif - - BaseClass::Spawn(); - - // - // Our player movement speed is set once here. This will override the cl_xxxx - // cvars unless they are set to be lower than this. - // - //m_flMaxspeed = 320; - - if ( !IsSuitEquipped() ) - StartWalking(); - - SuitPower_SetCharge( 100 ); - - m_Local.m_iHideHUD |= HIDEHUD_CHAT; - - m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME)); - - InitSprinting(); - - GetPlayerProxy(); - - SetFlashlightPowerDrainScale( 1.0f ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::InitSprinting( void ) -{ - StopSprinting(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Returns whether or not we are allowed to sprint now. -//----------------------------------------------------------------------------- -bool CHL2_Player::CanSprint() -{ - return ( m_bSprintEnabled && // Only if sprint is enabled - !IsWalking() && // Not if we're walking - !( m_Local.m_bDucked && !m_Local.m_bDucking ) && // Nor if we're ducking - (GetWaterLevel() != 3) && // Certainly not underwater - (GlobalEntity_GetState("suit_no_sprint") != GLOBAL_ON) ); // Out of the question without the sprint module -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -#ifdef _XBOX -void CHL2_Player::StartAutoSprint() -{ - if( IsSprinting() ) - { - StopSprinting(); - } - else - { - StartSprinting(); - m_bIsAutoSprinting = true; - m_fAutoSprintMinTime = gpGlobals->curtime + 1.5f; - } -} -#endif// _XBOX - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::StartSprinting( void ) -{ - if( m_HL2Local.m_flSuitPower < 10 ) - { - // Don't sprint unless there's a reasonable - // amount of suit power. - CPASAttenuationFilter filter( this ); - filter.UsePredictionRules(); - EmitSound( filter, entindex(), "HL2Player.SprintNoPower" ); - return; - } - - if( !SuitPower_AddDevice( SuitDeviceSprint ) ) - return; - - CPASAttenuationFilter filter( this ); - filter.UsePredictionRules(); - EmitSound( filter, entindex(), "HL2Player.SprintStart" ); - - SetMaxSpeed( HL2_SPRINT_SPEED ); - m_fIsSprinting = true; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::StopSprinting( void ) -{ - if ( m_HL2Local.m_bitsActiveDevices & SuitDeviceSprint.GetDeviceID() ) - { - SuitPower_RemoveDevice( SuitDeviceSprint ); - } - SetMaxSpeed( HL2_NORM_SPEED ); - m_fIsSprinting = false; - -#ifdef _XBOX - m_bIsAutoSprinting = false; - m_fAutoSprintMinTime = 0.0f; -#endif//_XBOX -} - - -//----------------------------------------------------------------------------- -// Purpose: Called to disable and enable sprint due to temporary circumstances: -// - Carrying a heavy object with the physcannon -//----------------------------------------------------------------------------- -void CHL2_Player::EnableSprint( bool bEnable ) -{ - if ( !bEnable && IsSprinting() ) - { - StopSprinting(); - } - - m_bSprintEnabled = bEnable; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::StartWalking( void ) -{ - SetMaxSpeed( HL2_WALK_SPEED ); - m_fIsWalking = true; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::StopWalking( void ) -{ - SetMaxSpeed( HL2_NORM_SPEED ); - m_fIsWalking = false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CHL2_Player::CanZoom( CBaseEntity *pRequester ) -{ - if ( IsZooming() ) - return false; - - //Check our weapon - - return true; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::ToggleZoom(void) -{ - if( IsZooming() ) - { - StopZooming(); - } - else - { - StartZooming(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: +zoom suit zoom -//----------------------------------------------------------------------------- -void CHL2_Player::StartZooming( void ) -{ - int iFOV = 25; - if ( SetFOV( this, iFOV, 0.4f ) ) - { - m_HL2Local.m_bZooming = true; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2_Player::StopZooming( void ) -{ - int iFOV = GetZoomOwnerDesiredFOV( m_hZoomOwner ); - - if ( SetFOV( this, iFOV, 0.2f ) ) - { - m_HL2Local.m_bZooming = false; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CHL2_Player::IsZooming( void ) -{ - if ( m_hZoomOwner != NULL ) - return true; - - return false; -} - -class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent -{ -public: - int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position ) - { - CHL2_Player *pPlayer = (CHL2_Player *)pObject->GetGameData(); - if ( pPlayer ) - { - if ( pPlayer->TouchedPhysics() ) - { - return 0; - } - } - return 1; - } -}; - -static CPhysicsPlayerCallback playerCallback; - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2_Player::InitVCollision( void ) -{ - BaseClass::InitVCollision(); - - // Setup the HL2 specific callback. - IPhysicsPlayerController *pPlayerController = GetPhysicsController(); - if ( pPlayerController ) - { - pPlayerController->SetEventHandler( &playerCallback ); - } -} - - -CHL2_Player::~CHL2_Player( void ) -{ -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -bool CHL2_Player::CommanderFindGoal( commandgoal_t *pGoal ) -{ - CAI_BaseNPC *pAllyNpc; - trace_t tr; - Vector vecTarget; - Vector forward; - - EyeVectors( &forward ); - - //--------------------------------- - // MASK_SHOT on purpose! So that you don't hit the invisible hulls of the NPCs. - CTraceFilterSkipTwoEntities filter( this, PhysCannonGetHeldEntity( GetActiveWeapon() ), COLLISION_GROUP_INTERACTIVE_DEBRIS ); - - UTIL_TraceLine( EyePosition(), EyePosition() + forward * MAX_COORD_RANGE, MASK_SHOT, &filter, &tr ); - - if( !tr.DidHitWorld() ) - { - CUtlVector Allies; - AISquadIter_t iter; - for ( pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) - { - if ( pAllyNpc->IsCommandable() ) - Allies.AddToTail( pAllyNpc ); - } - - for( int i = 0 ; i < Allies.Count() ; i++ ) - { - if( Allies[ i ]->IsValidCommandTarget( tr.m_pEnt ) ) - { - pGoal->m_pGoalEntity = tr.m_pEnt; - return true; - } - } - } - - if( tr.fraction == 1.0 || (tr.surface.flags & SURF_SKY) ) - { - // Move commands invalid against skybox. - pGoal->m_vecGoalLocation = tr.endpos; - return false; - } - - if ( tr.m_pEnt->IsNPC() && ((CAI_BaseNPC *)(tr.m_pEnt))->IsCommandable() ) - { - pGoal->m_vecGoalLocation = tr.m_pEnt->GetAbsOrigin(); - } - else - { - vecTarget = tr.endpos; - - Vector mins( -16, -16, 0 ); - Vector maxs( 16, 16, 0 ); - - // Back up from whatever we hit so that there's enough space at the - // target location for a bounding box. - // Now trace down. - //UTIL_TraceLine( vecTarget, vecTarget - Vector( 0, 0, 8192 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); - UTIL_TraceHull( vecTarget + tr.plane.normal * 24, - vecTarget - Vector( 0, 0, 8192 ), - mins, - maxs, - MASK_SOLID_BRUSHONLY, - this, - COLLISION_GROUP_NONE, - &tr ); - - - if ( !tr.startsolid ) - pGoal->m_vecGoalLocation = tr.endpos; - else - pGoal->m_vecGoalLocation = vecTarget; - } - - pAllyNpc = GetSquadCommandRepresentative(); - if ( !pAllyNpc ) - return false; - - vecTarget = pGoal->m_vecGoalLocation; - if ( !pAllyNpc->FindNearestValidGoalPos( vecTarget, &pGoal->m_vecGoalLocation ) ) - return false; - - return ( ( vecTarget - pGoal->m_vecGoalLocation ).LengthSqr() < Square( 15*12 ) ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CAI_BaseNPC *CHL2_Player::GetSquadCommandRepresentative() -{ - if ( m_pPlayerAISquad != NULL ) - { - CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(); - - if ( pAllyNpc ) - { - return pAllyNpc->GetSquadCommandRepresentative(); - } - } - - return NULL; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CHL2_Player::GetNumSquadCommandables() -{ - AISquadIter_t iter; - int c = 0; - for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) - { - if ( pAllyNpc->IsCommandable() ) - c++; - } - return c; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CHL2_Player::GetNumSquadCommandableMedics() -{ - AISquadIter_t iter; - int c = 0; - for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) - { - if ( pAllyNpc->IsCommandable() && pAllyNpc->IsMedic() ) - c++; - } - return c; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::CommanderUpdate() -{ - CAI_BaseNPC *pCommandRepresentative = GetSquadCommandRepresentative(); - bool bFollowMode = false; - if ( pCommandRepresentative ) - { - bFollowMode = ( pCommandRepresentative->GetCommandGoal() == vec3_invalid ); - - // set the variables for network transmission (to show on the hud) - m_HL2Local.m_iSquadMemberCount = GetNumSquadCommandables(); - m_HL2Local.m_iSquadMedicCount = GetNumSquadCommandableMedics(); - m_HL2Local.m_fSquadInFollowMode = bFollowMode; - - // debugging code for displaying extra squad indicators - /* - char *pszMoving = ""; - AISquadIter_t iter; - for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) - { - if ( pAllyNpc->IsCommandMoving() ) - { - pszMoving = "<-"; - break; - } - } - - NDebugOverlay::ScreenText( - 0.932, 0.919, - CFmtStr( "%d|%c%s", GetNumSquadCommandables(), ( bFollowMode ) ? 'F' : 'S', pszMoving ), - 255, 128, 0, 128, - 0 ); - */ - - } - else - { - m_HL2Local.m_iSquadMemberCount = 0; - m_HL2Local.m_iSquadMedicCount = 0; - m_HL2Local.m_fSquadInFollowMode = true; - } - - if ( m_QueuedCommand != CC_NONE && ( m_QueuedCommand == CC_FOLLOW || gpGlobals->realtime - m_RealTimeLastSquadCommand >= player_squad_double_tap_time.GetFloat() ) ) - { - CommanderExecute( m_QueuedCommand ); - m_QueuedCommand = CC_NONE; - } - else if ( !bFollowMode && pCommandRepresentative && m_CommanderUpdateTimer.Expired() && player_squad_transient_commands.GetBool() ) - { - m_CommanderUpdateTimer.Set(2.5); - - if ( pCommandRepresentative->ShouldAutoSummon() ) - CommanderExecute( CC_FOLLOW ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// -// bHandled - indicates whether to continue delivering this order to -// all allies. Allows us to stop delivering certain types of orders once we find -// a suitable candidate. (like picking up a single weapon. We don't wish for -// all allies to respond and try to pick up one weapon). -//----------------------------------------------------------------------------- -bool CHL2_Player::CommanderExecuteOne( CAI_BaseNPC *pNpc, const commandgoal_t &goal, CAI_BaseNPC **Allies, int numAllies ) -{ - if ( goal.m_pGoalEntity ) - { - return pNpc->TargetOrder( goal.m_pGoalEntity, Allies, numAllies ); - } - else if ( pNpc->IsInPlayerSquad() ) - { - pNpc->MoveOrder( goal.m_vecGoalLocation, Allies, numAllies ); - } - - return true; -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -void CHL2_Player::CommanderExecute( CommanderCommand_t command ) -{ - CAI_BaseNPC *pPlayerSquadLeader = GetSquadCommandRepresentative(); - - if ( !pPlayerSquadLeader ) - { - EmitSound( "HL2Player.UseDeny" ); - return; - } - - int i; - CUtlVector Allies; - commandgoal_t goal; - - if ( command == CC_TOGGLE ) - { - if ( pPlayerSquadLeader->GetCommandGoal() != vec3_invalid ) - command = CC_FOLLOW; - else - command = CC_SEND; - } - else - { - if ( command == CC_FOLLOW && pPlayerSquadLeader->GetCommandGoal() == vec3_invalid ) - return; - } - - if ( command == CC_FOLLOW ) - { - goal.m_pGoalEntity = this; - goal.m_vecGoalLocation = vec3_invalid; - } - else - { - goal.m_pGoalEntity = NULL; - goal.m_vecGoalLocation = vec3_invalid; - - // Find a goal for ourselves. - if( !CommanderFindGoal( &goal ) ) - { - EmitSound( "HL2Player.UseDeny" ); - return; // just keep following - } - } - -#ifdef _DEBUG - if( goal.m_pGoalEntity == NULL && goal.m_vecGoalLocation == vec3_invalid ) - { - DevMsg( 1, "**ERROR: Someone sent an invalid goal to CommanderExecute!\n" ); - } -#endif // _DEBUG - - AISquadIter_t iter; - for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) - { - if ( pAllyNpc->IsCommandable() ) - Allies.AddToTail( pAllyNpc ); - } - - //--------------------------------- - // If the trace hits an NPC, send all ally NPCs a "target" order. Always - // goes to targeted one first -#ifdef DEBUG - int nAIs = g_AI_Manager.NumAIs(); -#endif - CAI_BaseNPC * pTargetNpc = (goal.m_pGoalEntity) ? goal.m_pGoalEntity->MyNPCPointer() : NULL; - - bool bHandled = false; - if( pTargetNpc ) - { - bHandled = !CommanderExecuteOne( pTargetNpc, goal, Allies.Base(), Allies.Count() ); - } - - for ( i = 0; !bHandled && i < Allies.Count(); i++ ) - { - if ( Allies[i] != pTargetNpc && Allies[i]->IsPlayerAlly() ) - { - bHandled = !CommanderExecuteOne( Allies[i], goal, Allies.Base(), Allies.Count() ); - } - Assert( nAIs == g_AI_Manager.NumAIs() ); // not coded to support mutating set of NPCs - } -} - -//----------------------------------------------------------------------------- -// Enter/exit commander mode, manage ally selection. -//----------------------------------------------------------------------------- -void CHL2_Player::CommanderMode() -{ - float commandInterval = gpGlobals->realtime - m_RealTimeLastSquadCommand; - m_RealTimeLastSquadCommand = gpGlobals->realtime; - if ( commandInterval < player_squad_double_tap_time.GetFloat() ) - { - m_QueuedCommand = CC_FOLLOW; - } - else - { - m_QueuedCommand = (player_squad_transient_commands.GetBool()) ? CC_SEND : CC_TOGGLE; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iImpulse - -//----------------------------------------------------------------------------- -void CHL2_Player::CheatImpulseCommands( int iImpulse ) -{ - switch( iImpulse ) - { - case 50: - { - CommanderMode(); - break; - } - - case 51: - { - // Cheat to create a dynamic resupply item - Vector vecForward; - AngleVectors( EyeAngles(), &vecForward ); - CBaseEntity *pItem = (CBaseEntity *)CreateEntityByName( "item_dynamic_resupply" ); - if ( pItem ) - { - Vector vecOrigin = GetAbsOrigin() + vecForward * 256 + Vector(0,0,64); - QAngle vecAngles( 0, GetAbsAngles().y - 90, 0 ); - pItem->SetAbsOrigin( vecOrigin ); - pItem->SetAbsAngles( vecAngles ); - pItem->KeyValue( "targetname", "resupply" ); - pItem->Spawn(); - pItem->Activate(); - } - break; - } - - case 52: - { - // Rangefinder - trace_t tr; - UTIL_TraceLine( EyePosition(), EyePosition() + EyeDirection3D() * MAX_COORD_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); - - if( tr.fraction != 1.0 ) - { - float flDist = (tr.startpos - tr.endpos).Length(); - float flDist2D = (tr.startpos - tr.endpos).Length2D(); - DevMsg( 1,"\nStartPos: %.4f %.4f %.4f --- EndPos: %.4f %.4f %.4f\n", tr.startpos.x,tr.startpos.y,tr.startpos.z,tr.endpos.x,tr.endpos.y,tr.endpos.z ); - DevMsg( 1,"3D Distance: %.4f units (%.2f feet) --- 2D Distance: %.4f units (%.2f feet)\n", flDist, flDist / 12.0, flDist2D, flDist2D / 12.0 ); - } - - break; - } - - default: - BaseClass::CheatImpulseCommands( iImpulse ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2_Player::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) -{ - BaseClass::SetupVisibility( pViewEntity, pvs, pvssize ); - - int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum(); - PointCameraSetupVisibility( this, area, pvs, pvssize ); - - // If the intro script is playing, we want to get it's visibility points - if ( g_hIntroScript ) - { - Vector vecOrigin; - CBaseEntity *pCamera; - if ( g_hIntroScript->GetIncludedPVSOrigin( &vecOrigin, &pCamera ) ) - { - // If it's a point camera, turn it on - CPointCamera *pPointCamera = dynamic_cast< CPointCamera* >(pCamera); - if ( pPointCamera ) - { - pPointCamera->SetActive( true ); - } - engine->AddOriginToPVS( vecOrigin ); - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::SuitPower_Update( void ) -{ - if( SuitPower_ShouldRecharge() ) - { - SuitPower_Charge( SUITPOWER_CHARGE_RATE * gpGlobals->frametime ); - } - else if( m_HL2Local.m_bitsActiveDevices ) - { - float flPowerLoad = m_flSuitPowerLoad; - -#ifndef _XBOX - //Since XBox quickly shuts off sprint if it isn't being used, this isn't an issue. - if( SuitPower_IsDeviceActive(SuitDeviceSprint) ) - { - if( !fabs(GetAbsVelocity().x) && !fabs(GetAbsVelocity().y) ) - { - // If player's not moving, don't drain sprint juice. - flPowerLoad -= SuitDeviceSprint.GetDeviceDrainRate(); - } - } -#endif//_XBOX - - if( SuitPower_IsDeviceActive(SuitDeviceFlashlight) ) - { - float factor; - - factor = 1.0f / m_flFlashlightPowerDrainScale; - - flPowerLoad -= ( SuitDeviceFlashlight.GetDeviceDrainRate() * (1.0f - factor) ); - } - - if( !SuitPower_Drain( flPowerLoad * gpGlobals->frametime ) ) - { - // TURN OFF ALL DEVICES!! - if( IsSprinting() ) - { - StopSprinting(); - } - - if( FlashlightIsOn() ) - { -#ifndef HL2MP - FlashlightTurnOff(); -#endif - } - } - - // turn off flashlight a little bit after it hits below one aux power notch (5%) - if( m_HL2Local.m_flSuitPower < 4.8f && FlashlightIsOn() ) - { -#ifndef HL2MP - FlashlightTurnOff(); -#endif - } - } -} - - -//----------------------------------------------------------------------------- -// Charge battery fully, turn off all devices. -//----------------------------------------------------------------------------- -void CHL2_Player::SuitPower_Initialize( void ) -{ - m_HL2Local.m_bitsActiveDevices = 0x00000000; - m_HL2Local.m_flSuitPower = 100.0; - m_flSuitPowerLoad = 0.0; -} - - -//----------------------------------------------------------------------------- -// Purpose: Interface to drain power from the suit's power supply. -// Input: Amount of charge to remove (expressed as percentage of full charge) -// Output: Returns TRUE if successful, FALSE if not enough power available. -//----------------------------------------------------------------------------- -bool CHL2_Player::SuitPower_Drain( float flPower ) -{ - // Suitpower cheat on? - if ( sv_infinite_aux_power.GetBool() ) - return true; - - m_HL2Local.m_flSuitPower -= flPower; - - if( m_HL2Local.m_flSuitPower < 0.0 ) - { - // Power is depleted! - // Clamp and fail - m_HL2Local.m_flSuitPower = 0.0; - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Interface to add power to the suit's power supply -// Input: Amount of charge to add -//----------------------------------------------------------------------------- -void CHL2_Player::SuitPower_Charge( float flPower ) -{ - m_HL2Local.m_flSuitPower += flPower; - - if( m_HL2Local.m_flSuitPower > 100.0 ) - { - // Full charge, clamp. - m_HL2Local.m_flSuitPower = 100.0; - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CHL2_Player::SuitPower_IsDeviceActive( const CSuitPowerDevice &device ) -{ - return (m_HL2Local.m_bitsActiveDevices & device.GetDeviceID()) != 0; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CHL2_Player::SuitPower_AddDevice( const CSuitPowerDevice &device ) -{ - // Make sure this device is NOT active!! - if( m_HL2Local.m_bitsActiveDevices & device.GetDeviceID() ) - return false; - - if( !IsSuitEquipped() ) - return false; - - m_HL2Local.m_bitsActiveDevices |= device.GetDeviceID(); - m_flSuitPowerLoad += device.GetDeviceDrainRate(); - return true; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CHL2_Player::SuitPower_RemoveDevice( const CSuitPowerDevice &device ) -{ - // Make sure this device is active!! - if( ! (m_HL2Local.m_bitsActiveDevices & device.GetDeviceID()) ) - return false; - - if( !IsSuitEquipped() ) - return false; - - // Take a little bit of suit power when you disable a device. If the device is shutting off - // because the battery is drained, no harm done, the battery charge cannot go below 0. - // This code in combination with the delay before the suit can start recharging are a defense - // against exploits where the player could rapidly tap sprint and never run out of power. - SuitPower_Drain( device.GetDeviceDrainRate() * 0.1f ); - - m_HL2Local.m_bitsActiveDevices &= ~device.GetDeviceID(); - m_flSuitPowerLoad -= device.GetDeviceDrainRate(); - - if( m_HL2Local.m_bitsActiveDevices == 0x00000000 ) - { - // With this device turned off, we can set this timer which tells us when the - // suit power system entered a no-load state. - m_flTimeAllSuitDevicesOff = gpGlobals->curtime; - } - - return true; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -#define SUITPOWER_BEGIN_RECHARGE_DELAY 0.5f -bool CHL2_Player::SuitPower_ShouldRecharge( void ) -{ - // Make sure all devices are off. - if( m_HL2Local.m_bitsActiveDevices != 0x00000000 ) - return false; - - // Is the system fully charged? - if( m_HL2Local.m_flSuitPower >= 100.0f ) - return false; - - // Has the system been in a no-load state for long enough - // to begin recharging? - if( gpGlobals->curtime < m_flTimeAllSuitDevicesOff + SUITPOWER_BEGIN_RECHARGE_DELAY ) - return false; - - return true; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -ConVar sk_battery( "sk_battery","0" ); - -bool CHL2_Player::ApplyBattery( float powerMultiplier ) -{ - const float MAX_NORMAL_BATTERY = 100; - if ((ArmorValue() < MAX_NORMAL_BATTERY) && IsSuitEquipped()) - { - int pct; - char szcharge[64]; - - IncrementArmorValue( sk_battery.GetFloat() * powerMultiplier, MAX_NORMAL_BATTERY ); - - CPASAttenuationFilter filter( this, "ItemBattery.Touch" ); - EmitSound( filter, entindex(), "ItemBattery.Touch" ); - - CSingleUserRecipientFilter user( this ); - user.MakeReliable(); - - UserMessageBegin( user, "ItemPickup" ); - WRITE_STRING( "item_battery" ); - MessageEnd(); - - - // Suit reports new power level - // For some reason this wasn't working in release build -- round it. - pct = (int)( (float)(ArmorValue() * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); - pct = (pct / 5); - if (pct > 0) - pct--; - - Q_snprintf( szcharge,sizeof(szcharge),"!HEV_%1dP", pct ); - - //UTIL_EmitSoundSuit(edict(), szcharge); - //SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC); - return true; - } - return false; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CHL2_Player::FlashlightIsOn( void ) -{ - return IsEffectActive( EF_DIMLIGHT ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::FlashlightTurnOn( void ) -{ - if( m_bFlashlightDisabled ) - return; - - if( !SuitPower_AddDevice( SuitDeviceFlashlight ) ) - return; - - AddEffects( EF_DIMLIGHT ); - EmitSound( "HL2Player.FlashLightOn" ); - - variant_t flashlighton; - flashlighton.SetFloat( m_HL2Local.m_flSuitPower / 100.0f ); - - FirePlayerProxyOutput( "OnFlashlightOn", flashlighton, this, this ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::FlashlightTurnOff( void ) -{ - if( !SuitPower_RemoveDevice( SuitDeviceFlashlight ) ) - return; - - RemoveEffects( EF_DIMLIGHT ); - EmitSound( "HL2Player.FlashLightOff" ); - - variant_t flashlightoff; - flashlightoff.SetFloat( m_HL2Local.m_flSuitPower / 100.0f ); - FirePlayerProxyOutput( "OnFlashlightOff", flashlightoff, this, this ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -#define FLASHLIGHT_RANGE Square(600) -bool CHL2_Player::IsIlluminatedByFlashlight( CBaseEntity *pEntity, float *flReturnDot ) -{ - if( !FlashlightIsOn() ) - return false; - - if( pEntity->Classify() == CLASS_BARNACLE && pEntity->GetEnemy() == this ) - { - // As long as my flashlight is on, the barnacle that's pulling me in is considered illuminated. - // This is because players often shine their flashlights at Alyx when they are in a barnacle's - // grasp, and wonder why Alyx isn't helping. Alyx isn't helping because the light isn't pointed - // at the barnacle. This will allow Alyx to see the barnacle no matter which way the light is pointed. - return true; - } - - // Within 50 feet? - float flDistSqr = GetAbsOrigin().DistToSqr(pEntity->GetAbsOrigin()); - if( flDistSqr > FLASHLIGHT_RANGE ) - return false; - - // Within 45 degrees? - Vector vecSpot = pEntity->WorldSpaceCenter(); - Vector los; - - // If the eyeposition is too close, move it back. Solves problems - // caused by the player being too close the target. - if ( flDistSqr < (128 * 128) ) - { - Vector vecForward; - EyeVectors( &vecForward ); - Vector vecMovedEyePos = EyePosition() - (vecForward * 128); - los = ( vecSpot - vecMovedEyePos ); - } - else - { - los = ( vecSpot - EyePosition() ); - } - - VectorNormalize( los ); - Vector facingDir = EyeDirection3D( ); - float flDot = DotProduct( los, facingDir ); - - if ( flReturnDot ) - { - *flReturnDot = flDot; - } - - if ( flDot < 0.92387f ) - return false; - - if( !FVisible(pEntity) ) - return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Let NPCs know when the flashlight is trained on them -//----------------------------------------------------------------------------- -void CHL2_Player::CheckFlashlight( void ) -{ - if ( !FlashlightIsOn() ) - return; - - if ( m_flNextFlashlightCheckTime > gpGlobals->curtime ) - return; - m_flNextFlashlightCheckTime = gpGlobals->curtime + FLASHLIGHT_NPC_CHECK_INTERVAL; - - // Loop through NPCs looking for illuminated ones - for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) - { - CAI_BaseNPC *pNPC = g_AI_Manager.AccessAIs()[i]; - - float flDot; - - if ( IsIlluminatedByFlashlight( pNPC, &flDot ) ) - { - pNPC->PlayerHasIlluminatedNPC( this, flDot ); - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::SetPlayerUnderwater( bool state ) -{ - if ( state ) - { - SuitPower_AddDevice( SuitDeviceBreather ); - } - else - { - SuitPower_RemoveDevice( SuitDeviceBreather ); - } - - BaseClass::SetPlayerUnderwater( state ); -} - -//----------------------------------------------------------------------------- -bool CHL2_Player::PassesDamageFilter( const CTakeDamageInfo &info ) -{ - if( info.GetAttacker()->MyNPCPointer() && info.GetAttacker()->MyNPCPointer()->IsPlayerAlly() ) - { - return false; - } - - return BaseClass::PassesDamageFilter( info ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::InputDisableFlashlight( inputdata_t &inputdata ) -{ - if( FlashlightIsOn() ) - FlashlightTurnOff(); - - m_bFlashlightDisabled = true; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::InputEnableFlashlight( inputdata_t &inputdata ) -{ - m_bFlashlightDisabled = false; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::InputIgnoreFallDamage( inputdata_t &inputdata ) -{ - float timeToIgnore = inputdata.value.Float(); - - if ( timeToIgnore <= 0.0 ) - timeToIgnore = TIME_IGNORE_FALL_DAMAGE; - - m_flTimeIgnoreFallDamage = gpGlobals->curtime + timeToIgnore; -} - -//----------------------------------------------------------------------------- -// Purpose: Notification of a player's npc ally in the players squad being killed -//----------------------------------------------------------------------------- -void CHL2_Player::OnSquadMemberKilled( inputdata_t &data ) -{ - // send a message to the client, to notify the hud of the loss - CSingleUserRecipientFilter user( this ); - user.MakeReliable(); - UserMessageBegin( user, "SquadMemberDied" ); - MessageEnd(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2_Player::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity ) -{ - CAI_BaseNPC *pAttacker = pAttackerEntity->MyNPCPointer(); - if ( pAttacker ) - { - const Vector &origin = GetAbsOrigin(); - for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) - { - const float NEAR_Z = 12*12; - const float NEAR_XY_SQ = Square( 50*12 ); - CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i]; - if ( pNpc->IsPlayerAlly() ) - { - const Vector &originNpc = pNpc->GetAbsOrigin(); - if ( fabsf( originNpc.z - origin.z ) < NEAR_Z ) - { - if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ ) - { - pNpc->OnFriendDamaged( this, pAttacker ); - } - } - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CHL2_Player::OnTakeDamage( const CTakeDamageInfo &info ) -{ - if ( GlobalEntity_GetState( "gordon_invulnerable" ) == GLOBAL_ON ) - return 0; - - if ( ( info.GetDamageType() & DMG_FALL ) && m_flTimeIgnoreFallDamage > gpGlobals->curtime ) - { - m_flTimeIgnoreFallDamage = 0; - return 0; - } - - if( info.GetDamageType() & DMG_BLAST_SURFACE ) - { - if( GetWaterLevel() > 2 ) - { - // Don't take blast damage from anything above the surface. - if( info.GetInflictor()->GetWaterLevel() == 0 ) - { - return 0; - } - } - } - - if ( info.GetDamage() > 0.0f ) - { - m_flLastDamageTime = gpGlobals->curtime; - - if ( info.GetAttacker() ) - NotifyFriendsOfDamage( info.GetAttacker() ); - } - - // Modify the amount of damage the player takes, based on skill. - CTakeDamageInfo playerDamage = info; - - // Should we run this damage through the skill level adjustment? - bool bAdjustForSkillLevel = true; - - if( info.GetDamageType() == DMG_GENERIC && info.GetAttacker() == this && info.GetInflictor() == this ) - { - // Only do a skill level adjustment if the player isn't his own attacker AND inflictor. - // This prevents damage from SetHealth() inputs from being adjusted for skill level. - bAdjustForSkillLevel = false; - } - - if( bAdjustForSkillLevel ) - { - playerDamage.AdjustPlayerDamageTakenForSkillLevel(); - } - - return BaseClass::OnTakeDamage( playerDamage ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &info - -//----------------------------------------------------------------------------- -int CHL2_Player::OnTakeDamage_Alive( const CTakeDamageInfo &info ) -{ - // Drown - if( info.GetDamageType() & DMG_DROWN ) - { - if( m_idrowndmg == m_idrownrestored ) - { - EmitSound( "Player.DrownStart" ); - } - else - { - EmitSound( "Player.DrownContinue" ); - } - } - - // Burnt - if ( info.GetDamageType() & DMG_BURN ) - { - EmitSound( "HL2Player.BurnPain" ); - } - - - if( (info.GetDamageType() & DMG_SLASH) && hl2_episodic.GetBool() ) - { - if( m_afPhysicsFlags & PFLAG_USING ) - { - // Stop the player using a rotating button for a short time if hit by a creature's melee attack. - // This is for the antlion burrow-corking training in EP1 (sjb). - SuspendUse( 0.5f ); - } - } - - - // Call the base class implementation - return BaseClass::OnTakeDamage_Alive( info ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::OnDamagedByExplosion( const CTakeDamageInfo &info ) -{ - if ( info.GetInflictor() && info.GetInflictor()->ClassMatches( "mortarshell" ) ) - { - // No ear ringing for mortar - UTIL_ScreenShake( info.GetInflictor()->GetAbsOrigin(), 4.0, 1.0, 0.5, 1000, SHAKE_START, false ); - return; - } - BaseClass::OnDamagedByExplosion( info ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CHL2_Player::ShouldShootMissTarget( CBaseCombatCharacter *pAttacker ) -{ - if( gpGlobals->curtime > m_flTargetFindTime ) - { - // Put this off into the future again. - m_flTargetFindTime = gpGlobals->curtime + random->RandomFloat( 3, 5 ); - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Notifies Alyx that player has put a combine ball into a socket so she can comment on it. -// Input : pCombineBall - ball the was socketed -//----------------------------------------------------------------------------- -void CHL2_Player::CombineBallSocketed( CPropCombineBall *pCombineBall ) -{ -#ifdef HL2_EPISODIC - CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); - if ( pAlyx ) - { - pAlyx->CombineBallSocketed( pCombineBall->NumBounces() ); - } -#endif -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) -{ - BaseClass::Event_KilledOther( pVictim, info ); - -#ifdef HL2_EPISODIC - CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); - if ( pAlyx ) - { - pAlyx->PlayerKilledOther( pVictim, info ); - } -#endif -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::Event_Killed( const CTakeDamageInfo &info ) -{ - BaseClass::Event_Killed( info ); - - NotifyScriptsOfDeath(); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::NotifyScriptsOfDeath( void ) -{ - CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "scripted_sequence" ); - - while( pEnt ) - { - variant_t emptyVariant; - pEnt->AcceptInput( "ScriptPlayerDeath", NULL, NULL, emptyVariant, 0 ); - - pEnt = gEntList.FindEntityByClassname( pEnt, "scripted_sequence" ); - } - - pEnt = gEntList.FindEntityByClassname( NULL, "logic_choreographed_scene" ); - - while( pEnt ) - { - variant_t emptyVariant; - pEnt->AcceptInput( "ScriptPlayerDeath", NULL, NULL, emptyVariant, 0 ); - - pEnt = gEntList.FindEntityByClassname( pEnt, "logic_choreographed_scene" ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2_Player::GetAutoaimVector( autoaim_params_t ¶ms ) -{ - BaseClass::GetAutoaimVector( params ); - - if ( IsXbox() ) - { - if( IsInAVehicle() ) - { - if( m_hLockedAutoAimEntity && m_hLockedAutoAimEntity->IsAlive() && ShouldKeepLockedAutoaimTarget(m_hLockedAutoAimEntity) ) - { - if( params.m_hAutoAimEntity && params.m_hAutoAimEntity != m_hLockedAutoAimEntity ) - { - // Autoaim has picked a new target. Switch. - m_hLockedAutoAimEntity = params.m_hAutoAimEntity; - } - - // Ignore autoaim and just keep aiming at this target. - params.m_hAutoAimEntity = m_hLockedAutoAimEntity; - Vector vecTarget = m_hLockedAutoAimEntity->BodyTarget( EyePosition(), false ); - Vector vecDir = vecTarget - EyePosition(); - VectorNormalize( vecDir ); - - params.m_vecAutoAimDir = vecDir; - params.m_vecAutoAimPoint = vecTarget; - return; - } - else - { - m_hLockedAutoAimEntity = NULL; - } - } - - // If the player manually gets his crosshair onto a target, make that target sticky - if( params.m_fScale != AUTOAIM_SCALE_DIRECT_ONLY ) - { - // Only affect this for 'real' queries - //if( params.m_hAutoAimEntity && params.m_bOnTargetNatural ) - if( params.m_hAutoAimEntity ) - { - // Turn on sticky. - m_HL2Local.m_bStickyAutoAim = true; - - if( IsInAVehicle() ) - { - m_hLockedAutoAimEntity = params.m_hAutoAimEntity; - } - } - else if( !params.m_hAutoAimEntity ) - { - // Turn off sticky only if there's no target at all. - m_HL2Local.m_bStickyAutoAim = false; - - m_hLockedAutoAimEntity = NULL; - } - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CHL2_Player::ShouldKeepLockedAutoaimTarget( EHANDLE hLockedTarget ) -{ - Vector vecLooking; - Vector vecToTarget; - - vecToTarget = hLockedTarget->WorldSpaceCenter() - EyePosition(); - float flDist = vecToTarget.Length2D(); - VectorNormalize( vecToTarget ); - - if( flDist > autoaim_max_dist.GetFloat() ) - return false; - - float flDot; - - vecLooking = EyeDirection3D(); - flDot = DotProduct( vecLooking, vecToTarget ); - - if( flDot < autoaim_unlock_target.GetFloat() ) - return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iCount - -// iAmmoIndex - -// bSuppressSound - -// Output : int -//----------------------------------------------------------------------------- -int CHL2_Player::GiveAmmo( int nCount, int nAmmoIndex, bool bSuppressSound) -{ - // Don't try to give the player invalid ammo indices. - if (nAmmoIndex < 0) - return 0; - - bool bCheckAutoSwitch = false; - if (!HasAnyAmmoOfType(nAmmoIndex)) - { - bCheckAutoSwitch = true; - } - - int nAdd = BaseClass::GiveAmmo(nCount, nAmmoIndex, bSuppressSound); - - if ( nCount > 0 && nAdd == 0 ) - { - // we've been denied the pickup, display a hud icon to show that - CSingleUserRecipientFilter user( this ); - user.MakeReliable(); - UserMessageBegin( user, "AmmoDenied" ); - WRITE_SHORT( nAmmoIndex ); - MessageEnd(); - } - - // - // If I was dry on ammo for my best weapon and justed picked up ammo for it, - // autoswitch to my best weapon now. - // - if (bCheckAutoSwitch) - { - CBaseCombatWeapon *pWeapon = g_pGameRules->GetNextBestWeapon(this, GetActiveWeapon()); - - if ( pWeapon && pWeapon->GetPrimaryAmmoType() == nAmmoIndex ) - { - SwitchToNextBestWeapon(GetActiveWeapon()); - } - } - - return nAdd; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CHL2_Player::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) -{ -#ifndef HL2MP - if ( pWeapon->ClassMatches( "weapon_stunstick" ) ) - { - if ( ApplyBattery( 0.5 ) ) - UTIL_Remove( pWeapon ); - return false; - } -#endif - - return BaseClass::Weapon_CanUse( pWeapon ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pWeapon - -//----------------------------------------------------------------------------- -void CHL2_Player::Weapon_Equip( CBaseCombatWeapon *pWeapon ) -{ -#if HL2_SINGLE_PRIMARY_WEAPON_MODE - - if ( pWeapon->GetSlot() == WEAPON_PRIMARY_SLOT ) - { - Weapon_DropSlot( WEAPON_PRIMARY_SLOT ); - } - -#endif - - BaseClass::Weapon_Equip( pWeapon ); -} - -//----------------------------------------------------------------------------- -// Purpose: Player reacts to bumping a weapon. -// Input : pWeapon - the weapon that the player bumped into. -// Output : Returns true if player picked up the weapon -//----------------------------------------------------------------------------- -bool CHL2_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) -{ - -#if HL2_SINGLE_PRIMARY_WEAPON_MODE - - CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); - - // Can I have this weapon type? - if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) - { - if ( gEvilImpulse101 ) - { - UTIL_Remove( pWeapon ); - } - return false; - } - - // ---------------------------------------- - // If I already have it just take the ammo - // ---------------------------------------- - if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType())) - { - //Only remove the weapon if we attained ammo from it - if ( Weapon_EquipAmmoOnly( pWeapon ) == false ) - return false; - - // Only remove me if I have no ammo left - // Can't just check HasAnyAmmo because if I don't use clips, I want to be removed, - if ( pWeapon->UsesClipsForAmmo1() && pWeapon->HasPrimaryAmmo() ) - return false; - - UTIL_Remove( pWeapon ); - return false; - } - // ------------------------- - // Otherwise take the weapon - // ------------------------- - else - { - //Make sure we're not trying to take a new weapon type we already have - if ( Weapon_SlotOccupied( pWeapon ) ) - { - CBaseCombatWeapon *pActiveWeapon = Weapon_GetSlot( WEAPON_PRIMARY_SLOT ); - - if ( pActiveWeapon != NULL && pActiveWeapon->HasAnyAmmo() == false && Weapon_CanSwitchTo( pWeapon ) ) - { - Weapon_Equip( pWeapon ); - return true; - } - - //Attempt to take ammo if this is the gun we're holding already - if ( Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType() ) ) - { - Weapon_EquipAmmoOnly( pWeapon ); - } - - return false; - } - - pWeapon->CheckRespawn(); - - pWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); - pWeapon->AddEffects( EF_NODRAW ); - - Weapon_Equip( pWeapon ); - - EmitSound( "HL2Player.PickupWeapon" ); - - return true; - } -#else - - return BaseClass::BumpWeapon( pWeapon ); - -#endif - -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *cmd - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CHL2_Player::ClientCommand(const char *cmd) -{ -#if HL2_SINGLE_PRIMARY_WEAPON_MODE - - //Drop primary weapon - if( stricmp( cmd, "DropPrimary" ) == 0 ) - { - Weapon_DropSlot( WEAPON_PRIMARY_SLOT ); - return true; - } - -#endif - - if ( !stricmp( cmd, "emit" ) ) - { - CSingleUserRecipientFilter filter( this ); - if ( engine->Cmd_Argc() > 1 ) - { - EmitSound( filter, entindex(), engine->Cmd_Argv( 1 ) ); - } - else - { - EmitSound( filter, entindex(), "Test.Sound" ); - } - return true; - } - - return BaseClass::ClientCommand( cmd ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : void CBasePlayer::PlayerUse -//----------------------------------------------------------------------------- -void CHL2_Player::PlayerUse ( void ) -{ - // Was use pressed or released? - if ( ! ((m_nButtons | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) - return; - - if ( m_afButtonPressed & IN_USE ) - { - // Currently using a latched entity? - if ( ClearUseEntity() ) - { - return; - } - else - { - if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) - { - m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; - m_iTrain = TRAIN_NEW|TRAIN_OFF; - return; - } - else - { // Start controlling the train! - CBaseEntity *pTrain = GetGroundEntity(); - if ( pTrain && !(m_nButtons & IN_JUMP) && (GetFlags() & FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(this) ) - { - m_afPhysicsFlags |= PFLAG_DIROVERRIDE; - m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed()); - m_iTrain |= TRAIN_NEW; - EmitSound( "HL2Player.TrainUse" ); - return; - } - } - } - - // Tracker 3926: We can't +USE something if we're climbing a ladder - if ( GetMoveType() == MOVETYPE_LADDER ) - { - return; - } - } - - if( m_flTimeUseSuspended > gpGlobals->curtime ) - { - // Something has temporarily stopped us being able to USE things. - // Obviously, this should be used very carefully.(sjb) - return; - } - - CBaseEntity *pUseEntity = FindUseEntity(); - - bool usedSomething = false; - - // Found an object - if ( pUseEntity ) - { - //!!!UNDONE: traceline here to prevent +USEing buttons through walls - int caps = pUseEntity->ObjectCaps(); - variant_t emptyVariant; - - if ( m_afButtonPressed & IN_USE ) - { - // Robin: Don't play sounds for NPCs, because NPCs will allow respond with speech. - if ( !pUseEntity->MyNPCPointer() ) - { - EmitSound( "HL2Player.Use" ); - } - } - - if ( ( (m_nButtons & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || - ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) - { - if ( caps & FCAP_CONTINUOUS_USE ) - m_afPhysicsFlags |= PFLAG_USING; - - pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE ); - - usedSomething = true; - } - // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away - else if ( (m_afButtonReleased & IN_USE) && (pUseEntity->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use - { - pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE ); - - usedSomething = true; - } - -#if HL2_SINGLE_PRIMARY_WEAPON_MODE - - //Check for weapon pick-up - if ( m_afButtonPressed & IN_USE ) - { - CBaseCombatWeapon *pWeapon = dynamic_cast(pUseEntity); - - if ( ( pWeapon != NULL ) && ( Weapon_CanSwitchTo( pWeapon ) ) ) - { - //Try to take ammo or swap the weapon - if ( Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType() ) ) - { - Weapon_EquipAmmoOnly( pWeapon ); - } - else - { - Weapon_DropSlot( pWeapon->GetSlot() ); - Weapon_Equip( pWeapon ); - } - - usedSomething = true; - } - } -#endif - } - else if ( m_afButtonPressed & IN_USE ) - { - // Signal that we want to play the deny sound, unless the user is +USEing on a ladder! - // The sound is emitted in ItemPostFrame, since that occurs after GameMovement::ProcessMove which - // lets the ladder code unset this flag. - m_bPlayUseDenySound = true; - } - - // Debounce the use key - if ( usedSomething && pUseEntity ) - { - m_Local.m_nOldButtons |= IN_USE; - m_afButtonPressed &= ~IN_USE; - } -} - -ConVar sv_show_crosshair_target( "sv_show_crosshair_target", "0" ); - -//----------------------------------------------------------------------------- -// Purpose: Updates the posture of the weapon from lowered to ready -//----------------------------------------------------------------------------- -void CHL2_Player::UpdateWeaponPosture( void ) -{ - CBaseHLCombatWeapon *pWeapon = dynamic_cast(GetActiveWeapon()); - - if ( pWeapon && m_LowerWeaponTimer.Expired() && pWeapon->CanLower() ) - { - m_LowerWeaponTimer.Set( .3 ); - VPROF( "CHL2_Player::UpdateWeaponPosture-CheckLower" ); - Vector vecAim = BaseClass::GetAutoaimVector( AUTOAIM_SCALE_DIRECT_ONLY ); - - const float CHECK_FRIENDLY_RANGE = 50 * 12; - trace_t tr; - UTIL_TraceLine( EyePosition(), EyePosition() + vecAim * CHECK_FRIENDLY_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); - - CBaseEntity *aimTarget = tr.m_pEnt; - - //If we're over something - if ( aimTarget && !tr.DidHitWorld() ) - { - if ( !aimTarget->IsNPC() || aimTarget->MyNPCPointer()->GetState() != NPC_STATE_COMBAT ) - { - Disposition_t dis = IRelationType( aimTarget ); - - //Debug info for seeing what an object "cons" as - if ( sv_show_crosshair_target.GetBool() ) - { - int text_offset = BaseClass::DrawDebugTextOverlays(); - - char tempstr[255]; - - switch ( dis ) - { - case D_LI: - Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Like" ); - break; - - case D_HT: - Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Hate" ); - break; - - case D_FR: - Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Fear" ); - break; - - case D_NU: - Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Neutral" ); - break; - - default: - case D_ER: - Q_snprintf( tempstr, sizeof(tempstr), "Disposition: !!!ERROR!!!" ); - break; - } - - //Draw the text - NDebugOverlay::EntityText( aimTarget->entindex(), text_offset, tempstr, 0 ); - } - - //See if we hates it - if ( dis == D_LI ) - { - //We're over a friendly, drop our weapon - if ( Weapon_Lower() == false ) - { - //FIXME: We couldn't lower our weapon! - } - - return; - } - } - } - - if ( Weapon_Ready() == false ) - { - //FIXME: We couldn't raise our weapon! - } - } - - if( hl2_xbox_aiming.GetBool() ) - { - if( !pWeapon || !m_AutoaimTimer.Expired() ) - { - return; - } - - m_AutoaimTimer.Set( .1 ); - - VPROF( "hl2_xbox_aiming" ); - - // Call the autoaim code to update the local player data, which allows the client to update. - autoaim_params_t params; - params.m_fScale = AUTOAIM_SCALE_DEFAULT; - params.m_fMaxDist = autoaim_max_dist.GetFloat(); - GetAutoaimVector( params ); - m_HL2Local.m_hAutoAimTarget.Set( params.m_hAutoAimEntity ); - m_HL2Local.m_vecAutoAimPoint.Set( params.m_vecAutoAimPoint ); - return; - } - else - { - // Make sure there's no residual autoaim target if the user changes the xbox_aiming convar on the fly. - m_HL2Local.m_hAutoAimTarget.Set(NULL); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Lowers the weapon posture (for hovering over friendlies) -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CHL2_Player::Weapon_Lower( void ) -{ - VPROF( "CHL2_Player::Weapon_Lower" ); - // Already lowered? - if ( m_HL2Local.m_bWeaponLowered ) - return true; - - m_HL2Local.m_bWeaponLowered = true; - - CBaseHLCombatWeapon *pWeapon = dynamic_cast(GetActiveWeapon()); - - if ( pWeapon == NULL ) - return false; - - return pWeapon->Lower(); -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the weapon posture to normal -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CHL2_Player::Weapon_Ready( void ) -{ - VPROF( "CHL2_Player::Weapon_Ready" ); - - // Already ready? - if ( m_HL2Local.m_bWeaponLowered == false ) - return true; - - m_HL2Local.m_bWeaponLowered = false; - - CBaseHLCombatWeapon *pWeapon = dynamic_cast(GetActiveWeapon()); - - if ( pWeapon == NULL ) - return false; - - return pWeapon->Ready(); -} - -//----------------------------------------------------------------------------- -// Purpose: Returns whether or not we can switch to the given weapon. -// Input : pWeapon - -//----------------------------------------------------------------------------- -bool CHL2_Player::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) -{ - CBasePlayer *pPlayer = (CBasePlayer *)this; -#if !defined( CLIENT_DLL ) - IServerVehicle *pVehicle = pPlayer->GetVehicle(); -#else - IClientVehicle *pVehicle = pPlayer->GetVehicle(); -#endif - if (pVehicle && !pPlayer->UsingStandardWeaponsInVehicle()) - return false; - - if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) ) - return false; - - if ( !pWeapon->CanDeploy() ) - return false; - - if ( GetActiveWeapon() ) - { - if ( PhysCannonGetHeldEntity( GetActiveWeapon() ) == pWeapon && - Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()) ) - { - return true; - } - - if ( !GetActiveWeapon()->CanHolster() ) - return false; - } - - return true; -} - -void CHL2_Player::PickupObject( CBaseEntity *pObject, bool bLimitMassAndSize ) -{ - // can't pick up what you're standing on - if ( GetGroundEntity() == pObject ) - return; - - if ( bLimitMassAndSize == true ) - { - if ( CBasePlayer::CanPickupObject( pObject, 35, 128 ) == false ) - return; - } - - // Can't be picked up if NPCs are on me - if ( pObject->HasNPCsOnIt() ) - return; - - PlayerPickupObject( this, pObject ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : CBaseEntity -//----------------------------------------------------------------------------- -bool CHL2_Player::IsHoldingEntity( CBaseEntity *pEnt ) -{ - return PlayerPickupControllerIsHoldingEntity( m_hUseEntity, pEnt ); -} - -float CHL2_Player::GetHeldObjectMass( IPhysicsObject *pHeldObject ) -{ - float mass = PlayerPickupGetHeldObjectMass( m_hUseEntity, pHeldObject ); - if ( mass == 0.0f ) - { - mass = PhysCannonGetHeldObjectMass( GetActiveWeapon(), pHeldObject ); - } - return mass; -} - -//----------------------------------------------------------------------------- -// Purpose: Force the player to drop any physics objects he's carrying -//----------------------------------------------------------------------------- -void CHL2_Player::ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldingThis ) -{ - if ( PhysIsInCallback() ) - { - variant_t value; - g_EventQueue.AddEvent( this, "ForceDropPhysObjects", value, 0.01f, pOnlyIfHoldingThis, this ); - return; - } - // Drop any objects being handheld. - ClearUseEntity(); - - // Then force the physcannon to drop anything it's holding, if it's our active weapon - PhysCannonForceDrop( GetActiveWeapon(), pOnlyIfHoldingThis ); -} - -void CHL2_Player::InputForceDropPhysObjects( inputdata_t &data ) -{ - ForceDropOfCarriedPhysObjects( data.pActivator ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2_Player::UpdateClientData( void ) -{ - if (m_DmgTake || m_DmgSave || m_bitsHUDDamage != m_bitsDamageType) - { - // Comes from inside me if not set - Vector damageOrigin = GetLocalOrigin(); - // send "damage" message - // causes screen to flash, and pain compass to show direction of damage - damageOrigin = m_DmgOrigin; - - // only send down damage type that have hud art - int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; - - m_DmgTake = clamp( m_DmgTake, 0, 255 ); - m_DmgSave = clamp( m_DmgSave, 0, 255 ); - - // If we're poisoned, but it wasn't this frame, don't send the indicator - // Without this check, any damage that occured to the player while they were - // recovering from a poison bite would register as poisonous as well and flash - // the whole screen! -- jdw - if ( visibleDamageBits & DMG_POISON ) - { - float flLastPoisonedDelta = gpGlobals->curtime - m_tbdPrev; - if ( flLastPoisonedDelta > 0.1f ) - { - visibleDamageBits &= ~DMG_POISON; - } - } - - CSingleUserRecipientFilter user( this ); - user.MakeReliable(); - UserMessageBegin( user, "Damage" ); - WRITE_BYTE( m_DmgSave ); - WRITE_BYTE( m_DmgTake ); - WRITE_LONG( visibleDamageBits ); - WRITE_FLOAT( damageOrigin.x ); //BUG: Should be fixed point (to hud) not floats - WRITE_FLOAT( damageOrigin.y ); //BUG: However, the HUD does _not_ implement bitfield messages (yet) - WRITE_FLOAT( damageOrigin.z ); //BUG: We use WRITE_VEC3COORD for everything else - MessageEnd(); - - m_DmgTake = 0; - m_DmgSave = 0; - m_bitsHUDDamage = m_bitsDamageType; - - // Clear off non-time-based damage indicators - m_bitsDamageType &= DMG_TIMEBASED; - } - - BaseClass::UpdateClientData(); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -void CHL2_Player::OnRestore() -{ - BaseClass::OnRestore(); - m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME)); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -Vector CHL2_Player::EyeDirection2D( void ) -{ - Vector vecReturn = EyeDirection3D(); - vecReturn.z = 0; - vecReturn.AsVector2D().NormalizeInPlace(); - - return vecReturn; -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -Vector CHL2_Player::EyeDirection3D( void ) -{ - Vector vecForward; - IServerVehicle *pVehicle = GetVehicle(); - if ( pVehicle ) - { - Assert( pVehicle ); - int nRole = pVehicle->GetPassengerRole( this ); - - Vector vecEyeOrigin; - QAngle angEyeAngles; - pVehicle->GetVehicleViewPosition( nRole, &vecEyeOrigin, &angEyeAngles ); - AngleVectors( angEyeAngles, &vecForward ); - } - else - { - AngleVectors( EyeAngles(), &vecForward ); - } - return vecForward; -} - - -//--------------------------------------------------------- -//--------------------------------------------------------- -bool CHL2_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex ) -{ - // Recalculate proficiency! - SetCurrentWeaponProficiency( CalcWeaponProficiency( pWeapon ) ); - - // Come out of suit zoom mode - if ( IsZooming() ) - { - StopZooming(); - } - - return BaseClass::Weapon_Switch( pWeapon, viewmodelindex ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -WeaponProficiency_t CHL2_Player::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon ) -{ - WeaponProficiency_t proficiency; - - proficiency = WEAPON_PROFICIENCY_PERFECT; - - if( weapon_showproficiency.GetBool() != 0 ) - { - Msg("Player switched to %s, proficiency is %s\n", pWeapon->GetClassname(), GetWeaponProficiencyName( proficiency ) ); - } - - return proficiency; -} - -//----------------------------------------------------------------------------- -// Purpose: override how single player rays hit the player -//----------------------------------------------------------------------------- - -bool LineCircleIntersection( - const Vector2D ¢er, - const float radius, - const Vector2D &vLinePt, - const Vector2D &vLineDir, - float *fIntersection1, - float *fIntersection2) -{ - // Line = P + Vt - // Sphere = r (assume we've translated to origin) - // (P + Vt)^2 = r^2 - // VVt^2 + 2PVt + (PP - r^2) - // Solve as quadratic: (-b +/- sqrt(b^2 - 4ac)) / 2a - // If (b^2 - 4ac) is < 0 there is no solution. - // If (b^2 - 4ac) is = 0 there is one solution (a case this function doesn't support). - // If (b^2 - 4ac) is > 0 there are two solutions. - Vector2D P; - float a, b, c, sqr, insideSqr; - - - // Translate circle to origin. - P[0] = vLinePt[0] - center[0]; - P[1] = vLinePt[1] - center[1]; - - a = vLineDir.Dot(vLineDir); - b = 2.0f * P.Dot(vLineDir); - c = P.Dot(P) - (radius * radius); - - insideSqr = b*b - 4*a*c; - if(insideSqr <= 0.000001f) - return false; - - // Ok, two solutions. - sqr = (float)FastSqrt(insideSqr); - - float denom = 1.0 / (2.0f * a); - - *fIntersection1 = (-b - sqr) * denom; - *fIntersection2 = (-b + sqr) * denom; - - return true; -} - -static void Collision_ClearTrace( const Vector &vecRayStart, const Vector &vecRayDelta, CBaseTrace *pTrace ) -{ - pTrace->startpos = vecRayStart; - pTrace->endpos = vecRayStart; - pTrace->endpos += vecRayDelta; - pTrace->startsolid = false; - pTrace->allsolid = false; - pTrace->fraction = 1.0f; - pTrace->contents = 0; -} - - -bool IntersectRayWithAACylinder( const Ray_t &ray, - const Vector ¢er, float radius, float height, CBaseTrace *pTrace ) -{ - Assert( ray.m_IsRay ); - Collision_ClearTrace( ray.m_Start, ray.m_Delta, pTrace ); - - // First intersect the ray with the top + bottom planes - float halfHeight = height * 0.5; - - // Handle parallel case - Vector vStart = ray.m_Start - center; - Vector vEnd = vStart + ray.m_Delta; - - float flEnterFrac, flLeaveFrac; - if (FloatMakePositive(ray.m_Delta.z) < 1e-8) - { - if ( (vStart.z < -halfHeight) || (vStart.z > halfHeight) ) - { - return false; // no hit - } - flEnterFrac = 0.0f; flLeaveFrac = 1.0f; - } - else - { - // Clip the ray to the top and bottom of box - flEnterFrac = IntersectRayWithAAPlane( vStart, vEnd, 2, 1, halfHeight); - flLeaveFrac = IntersectRayWithAAPlane( vStart, vEnd, 2, 1, -halfHeight); - - if ( flLeaveFrac < flEnterFrac ) - { - float temp = flLeaveFrac; - flLeaveFrac = flEnterFrac; - flEnterFrac = temp; - } - - if ( flLeaveFrac < 0 || flEnterFrac > 1) - { - return false; - } - } - - // Intersect with circle - float flCircleEnterFrac, flCircleLeaveFrac; - if ( !LineCircleIntersection( vec3_origin.AsVector2D(), radius, - vStart.AsVector2D(), ray.m_Delta.AsVector2D(), &flCircleEnterFrac, &flCircleLeaveFrac ) ) - { - return false; // no hit - } - - Assert( flCircleEnterFrac <= flCircleLeaveFrac ); - if ( flCircleLeaveFrac < 0 || flCircleEnterFrac > 1) - { - return false; - } - - if ( flEnterFrac < flCircleEnterFrac ) - flEnterFrac = flCircleEnterFrac; - if ( flLeaveFrac > flCircleLeaveFrac ) - flLeaveFrac = flCircleLeaveFrac; - - if ( flLeaveFrac < flEnterFrac ) - return false; - - VectorMA( ray.m_Start, flEnterFrac , ray.m_Delta, pTrace->endpos ); - pTrace->fraction = flEnterFrac; - pTrace->contents = CONTENTS_SOLID; - - // Calculate the point on our center line where we're nearest the intersection point - Vector collisionCenter; - CalcClosestPointOnLineSegment( pTrace->endpos, center + Vector( 0, 0, halfHeight ), center - Vector( 0, 0, halfHeight ), collisionCenter ); - - // Our normal is the direction from that center point to the intersection point - pTrace->plane.normal = pTrace->endpos - collisionCenter; - VectorNormalize( pTrace->plane.normal ); - - return true; -} - - -bool CHL2_Player::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) -{ - if( g_pGameRules->IsMultiplayer() ) - { - return BaseClass::TestHitboxes( ray, fContentsMask, tr ); - } - else - { - Assert( ray.m_IsRay ); - - Vector mins, maxs; - - mins = WorldAlignMins(); - maxs = WorldAlignMaxs(); - - if ( IntersectRayWithAACylinder( ray, WorldSpaceCenter(), maxs.x * PLAYER_HULL_REDUCTION, maxs.z - mins.z, &tr ) ) - { - CStudioHdr *pStudioHdr = GetModelPtr( ); - if (!pStudioHdr) - return false; - - mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet ); - if ( !set || !set->numhitboxes ) - return false; - - mstudiobbox_t *pbox = set->pHitbox( tr.hitbox ); - mstudiobone_t *pBone = pStudioHdr->pBone(pbox->bone); - tr.surface.name = "**studio**"; - tr.surface.flags = SURF_HITBOX; - tr.surface.surfaceProps = physprops->GetSurfaceIndex( pBone->pszSurfaceProp() ); - } - - return true; - } -} - -//--------------------------------------------------------- -// Show the player's scaled down bbox that we use for -// bullet impacts. -//--------------------------------------------------------- -void CHL2_Player::DrawDebugGeometryOverlays(void) -{ - BaseClass::DrawDebugGeometryOverlays(); - - if (m_debugOverlays & OVERLAY_BBOX_BIT) - { - Vector mins, maxs; - - mins = WorldAlignMins(); - maxs = WorldAlignMaxs(); - - mins.x *= PLAYER_HULL_REDUCTION; - mins.y *= PLAYER_HULL_REDUCTION; - - maxs.x *= PLAYER_HULL_REDUCTION; - maxs.y *= PLAYER_HULL_REDUCTION; - - NDebugOverlay::Box( GetAbsOrigin(), mins, maxs, 255, 0, 0, 100, 0 ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Helper to remove from ladder -//----------------------------------------------------------------------------- -void CHL2_Player::ExitLadder() -{ - if ( MOVETYPE_LADDER != GetMoveType() ) - return; - - SetMoveType( MOVETYPE_WALK ); - SetMoveCollide( MOVECOLLIDE_DEFAULT ); - // Remove from ladder - m_HL2Local.m_hLadder.Set( NULL ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Queues up a use deny sound, played in ItemPostFrame. -//----------------------------------------------------------------------------- -void CHL2_Player::PlayUseDenySound() -{ - m_bPlayUseDenySound = true; -} - - -void CHL2_Player::ItemPostFrame() -{ - BaseClass::ItemPostFrame(); - - if ( m_bPlayUseDenySound ) - { - m_bPlayUseDenySound = false; - EmitSound( "HL2Player.UseDeny" ); - } -} - - -void CHL2_Player::StartWaterDeathSounds( void ) -{ - CPASAttenuationFilter filter( this ); - - if ( m_sndLeeches == NULL ) - { - m_sndLeeches = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "coast.leech_bites_loop" , ATTN_NORM ); - } - - if ( m_sndLeeches ) - { - (CSoundEnvelopeController::GetController()).Play( m_sndLeeches, 1.0f, 100 ); - } - - if ( m_sndWaterSplashes == NULL ) - { - m_sndWaterSplashes = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "coast.leech_water_churn_loop" , ATTN_NORM ); - } - - if ( m_sndWaterSplashes ) - { - (CSoundEnvelopeController::GetController()).Play( m_sndWaterSplashes, 1.0f, 100 ); - } -} - -void CHL2_Player::StopWaterDeathSounds( void ) -{ - if ( m_sndLeeches ) - { - (CSoundEnvelopeController::GetController()).SoundFadeOut( m_sndLeeches, 0.5f, true ); - m_sndLeeches = NULL; - } - - if ( m_sndWaterSplashes ) - { - (CSoundEnvelopeController::GetController()).SoundFadeOut( m_sndWaterSplashes, 0.5f, true ); - m_sndWaterSplashes = NULL; - } -} - -//----------------------------------------------------------------------------- -// Shuts down sounds -//----------------------------------------------------------------------------- -void CHL2_Player::StopLoopingSounds( void ) -{ - if ( m_sndLeeches != NULL ) - { - (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndLeeches ); - m_sndLeeches = NULL; - } - - if ( m_sndWaterSplashes != NULL ) - { - (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndWaterSplashes ); - m_sndWaterSplashes = NULL; - } - - BaseClass::StopLoopingSounds(); -} - -//----------------------------------------------------------------------------- -void CHL2_Player::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set ) -{ - BaseClass::ModifyOrAppendPlayerCriteria( set ); - - if ( GlobalEntity_GetIndex( "gordon_precriminal" ) == -1 ) - { - set.AppendCriteria( "gordon_precriminal", "0" ); - } -} - -CLogicPlayerProxy *CHL2_Player::GetPlayerProxy( void ) -{ - CLogicPlayerProxy *pProxy = dynamic_cast< CLogicPlayerProxy* > ( m_hPlayerProxy.Get() ); - - if ( pProxy == NULL ) - { - pProxy = (CLogicPlayerProxy*)gEntList.FindEntityByClassname(NULL, "logic_playerproxy" ); - - if ( pProxy == NULL ) - return NULL; - - pProxy->m_hPlayer = this; - m_hPlayerProxy = pProxy; - } - - return pProxy; -} - -void CHL2_Player::FirePlayerProxyOutput( const char *pszOutputName, variant_t variant, CBaseEntity *pActivator, CBaseEntity *pCaller ) -{ - if ( GetPlayerProxy() == NULL ) - return; - - GetPlayerProxy()->FireNamedOutput( pszOutputName, variant, pActivator, pCaller ); -} - -LINK_ENTITY_TO_CLASS( logic_playerproxy, CLogicPlayerProxy); - -BEGIN_DATADESC( CLogicPlayerProxy ) - DEFINE_OUTPUT( m_OnFlashlightOn, "OnFlashlightOn" ), - DEFINE_OUTPUT( m_OnFlashlightOff, "OnFlashlightOff" ), - DEFINE_OUTPUT( m_RequestedPlayerHealth, "PlayerHealth" ), - DEFINE_OUTPUT( m_PlayerHasAmmo, "PlayerHasAmmo" ), - DEFINE_OUTPUT( m_PlayerHasNoAmmo, "PlayerHasNoAmmo" ), - DEFINE_INPUTFUNC( FIELD_VOID, "RequestPlayerHealth", InputRequestPlayerHealth ), - DEFINE_INPUTFUNC( FIELD_VOID, "SetFlashlightSlowDrain", InputSetFlashlightSlowDrain ), - DEFINE_INPUTFUNC( FIELD_VOID, "SetFlashlightNormalDrain", InputSetFlashlightNormalDrain ), - DEFINE_INPUTFUNC( FIELD_INTEGER, "SetPlayerHealth", InputSetPlayerHealth ), - DEFINE_INPUTFUNC( FIELD_VOID, "RequestAmmoState", InputRequestAmmoState ), - DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ), -END_DATADESC() - -void CLogicPlayerProxy::Activate( void ) -{ - BaseClass::Activate(); - - if ( m_hPlayer == NULL ) - { - m_hPlayer = AI_GetSinglePlayer(); - } -} - -void CLogicPlayerProxy::InputSetPlayerHealth( inputdata_t &inputdata ) -{ - if ( m_hPlayer == NULL ) - return; - - m_hPlayer->SetHealth( inputdata.value.Int() ); - -} - -void CLogicPlayerProxy::InputRequestPlayerHealth( inputdata_t &inputdata ) -{ - if ( m_hPlayer == NULL ) - return; - - m_RequestedPlayerHealth.Set( m_hPlayer->GetHealth(), inputdata.pActivator, inputdata.pCaller ); -} - -void CLogicPlayerProxy::InputSetFlashlightSlowDrain( inputdata_t &inputdata ) -{ - if( m_hPlayer == NULL ) - return; - - CHL2_Player *pPlayer = dynamic_cast(m_hPlayer.Get()); - - if( pPlayer ) - pPlayer->SetFlashlightPowerDrainScale( hl2_darkness_flashlight_factor.GetFloat() ); -} - -void CLogicPlayerProxy::InputSetFlashlightNormalDrain( inputdata_t &inputdata ) -{ - if( m_hPlayer == NULL ) - return; - - CHL2_Player *pPlayer = dynamic_cast(m_hPlayer.Get()); - - if( pPlayer ) - pPlayer->SetFlashlightPowerDrainScale( 1.0f ); -} - -void CLogicPlayerProxy::InputRequestAmmoState( inputdata_t &inputdata ) -{ - if( m_hPlayer == NULL ) - return; - - CHL2_Player *pPlayer = dynamic_cast(m_hPlayer.Get()); - - for ( int i = 0 ; i < pPlayer->WeaponCount(); ++i ) - { - CBaseCombatWeapon* pCheck = pPlayer->GetWeapon( i ); - - if ( pCheck ) - { - if ( pCheck->HasAnyAmmo() && (pCheck->UsesPrimaryAmmo() || pCheck->UsesSecondaryAmmo())) - { - m_PlayerHasAmmo.FireOutput( this, this, 0 ); - return; - } - } - } - - m_PlayerHasNoAmmo.FireOutput( this, this, 0 ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Player for HL2. +// +//=============================================================================// + +#include "cbase.h" +#include "hl2_player.h" +#include "globalstate.h" +#include "game.h" +#include "gamerules.h" +#include "trains.h" +#include "basehlcombatweapon_shared.h" +#include "vcollide_parse.h" +#include "in_buttons.h" +#include "ai_interactions.h" +#include "ai_squad.h" +#include "igamemovement.h" +#include "ai_hull.h" +#include "hl2_shareddefs.h" +#include "info_camera_link.h" +#include "point_camera.h" +#include "engine/IEngineSound.h" +#include "ndebugoverlay.h" +#include "iservervehicle.h" +#include "IVehicle.h" +#include "globals.h" +#include "collisionutils.h" +#include "coordsize.h" +#include "effect_color_tables.h" +#include "vphysics/player_controller.h" +#include "player_pickup.h" +#include "weapon_physcannon.h" +#include "script_intro.h" +#include "effect_dispatch_data.h" +#include "te_effect_dispatch.h" +#include "ai_basenpc.h" +#include "AI_Criteria.h" +#include "npc_barnacle.h" +#include "entitylist.h" +#include "env_zoom.h" +#include "hl2_gamerules.h" +#include "prop_combine_ball.h" +#include "datacache/imdlcache.h" +#include "eventqueue.h" + +#ifdef HL2_EPISODIC +#include "npc_alyx_episodic.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar weapon_showproficiency; +extern ConVar autoaim_max_dist; + +// Do not touch with without seeing me, please! (sjb) +// For consistency's sake, enemy gunfire is traced against a scaled down +// version of the player's hull, not the hitboxes for the player's model +// because the player isn't aware of his model, and can't do anything about +// preventing headshots and other such things. Also, game difficulty will +// not change if the model changes. This is the value by which to scale +// the X/Y of the player's hull to get the volume to trace bullets against. +#define PLAYER_HULL_REDUCTION 0.70 + +// This switches between the single primary weapon, and multiple weapons with buckets approach (jdw) +#define HL2_SINGLE_PRIMARY_WEAPON_MODE 0 + +#define TIME_IGNORE_FALL_DAMAGE 10.0 + +extern int gEvilImpulse101; + +ConVar sv_autojump( "sv_autojump", "0" ); + +ConVar hl2_walkspeed( "hl2_walkspeed", "150" ); +ConVar hl2_normspeed( "hl2_normspeed", "190" ); +ConVar hl2_sprintspeed( "hl2_sprintspeed", "320" ); + +ConVar hl2_xbox_aiming( "hl2_xbox_aiming", "1" ); + +ConVar hl2_darkness_flashlight_factor ( "hl2_darkness_flashlight_factor", "1" ); + +#ifdef HL2MP + #define HL2_WALK_SPEED 150 + #define HL2_NORM_SPEED 190 + #define HL2_SPRINT_SPEED 320 +#else + #define HL2_WALK_SPEED hl2_walkspeed.GetFloat() + #define HL2_NORM_SPEED hl2_normspeed.GetFloat() + #define HL2_SPRINT_SPEED hl2_sprintspeed.GetFloat() +#endif + +#ifdef _XBOX + #define AUTOSPRINT_MIN_DURATION 1.0f +#endif//_XBOX + +ConVar player_showpredictedposition( "player_showpredictedposition", "0" ); +ConVar player_showpredictedposition_timestep( "player_showpredictedposition_timestep", "1.0" ); + +ConVar player_squad_transient_commands( "player_squad_transient_commands", "1", FCVAR_REPLICATED ); +ConVar player_squad_double_tap_time( "player_squad_double_tap_time", "0.25" ); + +ConVar sv_infinite_aux_power( "sv_infinite_aux_power", "0", FCVAR_CHEAT ); + +ConVar autoaim_unlock_target( "autoaim_unlock_target", "0.8666" ); + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CC_ToggleZoom( void ) +{ + CBasePlayer* pPlayer = UTIL_GetCommandClient(); + + if( pPlayer ) + { + CHL2_Player *pHL2Player = dynamic_cast(pPlayer); + + if( pHL2Player && pHL2Player->IsSuitEquipped() ) + { + pHL2Player->ToggleZoom(); + } + } +} + +static ConCommand toggle_zoom("toggle_zoom", CC_ToggleZoom, "Toggles zoom display" ); + +// ConVar cl_forwardspeed( "cl_forwardspeed", "400", FCVAR_CHEAT ); // Links us to the client's version +ConVar xc_crouch_range( "xc_crouch_range", "0.85", FCVAR_ARCHIVE, "Percentarge [1..0] of joystick range to allow ducking within" ); // Only 1/2 of the range is used +ConVar xc_use_crouch_limiter( "xc_use_crouch_limiter", "0", FCVAR_ARCHIVE, "Use the crouch limiting logic on the controller" ); + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CC_ToggleDuck( void ) +{ + CBasePlayer* pPlayer = UTIL_GetCommandClient(); + if ( pPlayer == NULL ) + return; + + // Cannot be frozen + if ( pPlayer->GetFlags() & FL_FROZEN ) + return; + + static bool bChecked = false; + static ConVar *pCVcl_forwardspeed = NULL; + if ( !bChecked ) + { + bChecked = true; + pCVcl_forwardspeed = ( ConVar * )cvar->FindVar( "cl_forwardspeed" ); + } + + + // If we're not ducked, do extra checking + if ( xc_use_crouch_limiter.GetBool() ) + { + if ( pPlayer->GetToggledDuckState() == false ) + { + float flForwardSpeed = 400.0f; + if ( pCVcl_forwardspeed ) + { + flForwardSpeed = pCVcl_forwardspeed->GetFloat(); + } + + flForwardSpeed = max( 1.0f, flForwardSpeed ); + + // Make sure we're not in the blindspot on the crouch detection + float flStickDistPerc = ( pPlayer->GetStickDist() / flForwardSpeed ); // Speed is the magnitude + if ( flStickDistPerc > xc_crouch_range.GetFloat() ) + return; + } + } + + // Toggle the duck + pPlayer->ToggleDuck(); +} + +static ConCommand toggle_duck("toggle_duck", CC_ToggleDuck, "Toggles duck" ); + +#ifndef HL2MP +LINK_ENTITY_TO_CLASS( player, CHL2_Player ); +#endif + +PRECACHE_REGISTER(player); + +CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull ); + +BEGIN_SIMPLE_DATADESC( LadderMove_t ) + DEFINE_FIELD( m_bForceLadderMove, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bForceMount, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flStartTime, FIELD_TIME ), + DEFINE_FIELD( m_flArrivalTime, FIELD_TIME ), + DEFINE_FIELD( m_vecGoalPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecStartPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_hForceLadder, FIELD_EHANDLE ), + DEFINE_FIELD( m_hReservedSpot, FIELD_EHANDLE ), +END_DATADESC() + +// Global Savedata for HL2 player +BEGIN_DATADESC( CHL2_Player ) + + DEFINE_FIELD( m_nControlClass, FIELD_INTEGER ), + DEFINE_EMBEDDED( m_HL2Local ), + + DEFINE_FIELD( m_bSprintEnabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flTimeAllSuitDevicesOff, FIELD_TIME ), + DEFINE_FIELD( m_fIsSprinting, FIELD_BOOLEAN ), + DEFINE_FIELD( m_fIsWalking, FIELD_BOOLEAN ), + + /* + // These are initialized every time the player calls Activate() + DEFINE_FIELD( m_bIsAutoSprinting, FIELD_BOOLEAN ), + DEFINE_FIELD( m_fAutoSprintMinTime, FIELD_TIME ), + */ + + // Field is used within a single tick, no need to save restore + // DEFINE_FIELD( m_bPlayUseDenySound, FIELD_BOOLEAN ), + // m_pPlayerAISquad reacquired on load + + DEFINE_AUTO_ARRAY( m_vecMissPositions, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_nNumMissPositions, FIELD_INTEGER ), + + // m_pPlayerAISquad + DEFINE_EMBEDDED( m_CommanderUpdateTimer ), + // m_RealTimeLastSquadCommand + DEFINE_FIELD( m_QueuedCommand, FIELD_INTEGER ), + + DEFINE_FIELD( m_flTimeIgnoreFallDamage, FIELD_TIME ), + + // Suit power fields + DEFINE_FIELD( m_flSuitPowerLoad, FIELD_FLOAT ), + + DEFINE_FIELD( m_flIdleTime, FIELD_TIME ), + DEFINE_FIELD( m_flMoveTime, FIELD_TIME ), + DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ), + DEFINE_FIELD( m_flTargetFindTime, FIELD_TIME ), + + DEFINE_FIELD( m_flAdmireGlovesAnimTime, FIELD_TIME ), + DEFINE_FIELD( m_flNextFlashlightCheckTime, FIELD_TIME ), + DEFINE_FIELD( m_flFlashlightPowerDrainScale, FIELD_FLOAT ), + DEFINE_FIELD( m_bFlashlightDisabled, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_hLockedAutoAimEntity, FIELD_EHANDLE ), + + DEFINE_EMBEDDED( m_LowerWeaponTimer ), + DEFINE_EMBEDDED( m_AutoaimTimer ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreFallDamage", InputIgnoreFallDamage ), + DEFINE_INPUTFUNC( FIELD_VOID, "OnSquadMemberKilled", OnSquadMemberKilled ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableFlashlight", InputDisableFlashlight ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableFlashlight", InputEnableFlashlight ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForceDropPhysObjects", InputForceDropPhysObjects ), + + DEFINE_SOUNDPATCH( m_sndLeeches ), + DEFINE_SOUNDPATCH( m_sndWaterSplashes ), + + DEFINE_FIELD( m_flArmorReductionTime, FIELD_TIME ), + DEFINE_FIELD( m_iArmorReductionFrom, FIELD_INTEGER ), + + DEFINE_FIELD( m_flTimeUseSuspended, FIELD_TIME ), + + //DEFINE_FIELD( m_hPlayerProxy, FIELD_EHANDLE ), //Shut up class check! + +END_DATADESC() + +CHL2_Player::CHL2_Player() +{ + m_nNumMissPositions = 0; + m_pPlayerAISquad = 0; + m_bSprintEnabled = true; + + m_flArmorReductionTime = 0.0f; + m_iArmorReductionFrom = 0; +} + +// +// SUIT POWER DEVICES +// +#define SUITPOWER_CHARGE_RATE 12.5 // 100 units in 8 seconds + +#ifdef HL2MP + CSuitPowerDevice SuitDeviceSprint( bits_SUIT_DEVICE_SPRINT, 25.0f ); // 100 units in 4 seconds +#else + CSuitPowerDevice SuitDeviceSprint( bits_SUIT_DEVICE_SPRINT, 12.5f ); // 100 units in 8 seconds +#endif + +#ifdef HL2_EPISODIC + CSuitPowerDevice SuitDeviceFlashlight( bits_SUIT_DEVICE_FLASHLIGHT, 1.111 ); // 100 units in 90 second +#else + CSuitPowerDevice SuitDeviceFlashlight( bits_SUIT_DEVICE_FLASHLIGHT, 2.222 ); // 100 units in 45 second +#endif +CSuitPowerDevice SuitDeviceBreather( bits_SUIT_DEVICE_BREATHER, 6.7f ); // 100 units in 15 seconds (plus three padded seconds) + + +IMPLEMENT_SERVERCLASS_ST(CHL2_Player, DT_HL2_Player) + SendPropDataTable(SENDINFO_DT(m_HL2Local), &REFERENCE_SEND_TABLE(DT_HL2Local), SendProxy_SendLocalDataTable), + SendPropBool( SENDINFO(m_fIsSprinting) ), +END_SEND_TABLE() + + +void CHL2_Player::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheScriptSound( "HL2Player.SprintNoPower" ); + PrecacheScriptSound( "HL2Player.SprintStart" ); + PrecacheScriptSound( "HL2Player.UseDeny" ); + PrecacheScriptSound( "HL2Player.FlashLightOn" ); + PrecacheScriptSound( "HL2Player.FlashLightOff" ); + PrecacheScriptSound( "HL2Player.PickupWeapon" ); + PrecacheScriptSound( "HL2Player.TrainUse" ); + PrecacheScriptSound( "HL2Player.Use" ); + PrecacheScriptSound( "HL2Player.BurnPain" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::CheckSuitZoom( void ) +{ +//#ifndef _XBOX + //Adrian - No zooming without a suit! + if ( IsSuitEquipped() ) + { + if ( m_afButtonReleased & IN_ZOOM ) + { + StopZooming(); + } + else if ( m_afButtonPressed & IN_ZOOM ) + { + StartZooming(); + } + } +//#endif//_XBOX +} + +void CHL2_Player::EquipSuit( bool bPlayEffects ) +{ + MDLCACHE_CRITICAL_SECTION(); + BaseClass::EquipSuit(); + + m_HL2Local.m_bDisplayReticle = true; + + if ( bPlayEffects == true ) + { + StartAdmireGlovesAnimation(); + } +} + +void CHL2_Player::RemoveSuit( void ) +{ + BaseClass::RemoveSuit(); + + m_HL2Local.m_bDisplayReticle = false; +} + +void CHL2_Player::HandleSpeedChanges( void ) +{ + int buttonsChanged = m_afButtonPressed | m_afButtonReleased; + + if( buttonsChanged & IN_SPEED ) + { + // The state of the sprint/run button has changed. + if ( IsSuitEquipped() ) + { + if ( !(m_afButtonPressed & IN_SPEED) && IsSprinting() ) + { +#ifndef _XBOX + StopSprinting(); +#endif + } + else if ( (m_afButtonPressed & IN_SPEED) && !IsSprinting() ) + { + if ( CanSprint() ) + { +#ifdef _XBOX + StartAutoSprint(); +#else + StartSprinting(); +#endif + } + else + { + // Reset key, so it will be activated post whatever is suppressing it. + m_nButtons &= ~IN_SPEED; + } + } +#ifdef _XBOX + else if( (m_afButtonPressed & IN_SPEED) && IsSprinting() ) + { + StopSprinting(); + } +#endif//_XBOX + } + else + { + // ROBIN: We're trialling Sprint not affecting movement speed without the HEV suit + /* + // The state of the WALK button has changed. + if( !IsWalking() && !(m_afButtonPressed & IN_SPEED) ) + { + StartWalking(); + } + else if( IsWalking() && !IsSprinting() && (m_afButtonPressed & IN_SPEED) && !(m_nButtons & IN_DUCK) ) + { + StopWalking(); + } + */ + } + } + else if( buttonsChanged & IN_WALK ) + { + if ( IsSuitEquipped() ) + { + // The state of the WALK button has changed. + if( IsWalking() && !(m_afButtonPressed & IN_WALK) ) + { + StopWalking(); + } + else if( !IsWalking() && !IsSprinting() && (m_afButtonPressed & IN_WALK) && !(m_nButtons & IN_DUCK) ) + { + StartWalking(); + } + } + } + + if ( IsSuitEquipped() && m_fIsWalking && !(m_nButtons & IN_WALK) ) + StopWalking(); +} + +//----------------------------------------------------------------------------- +// This happens when we powerdown from the mega physcannon to the regular one +//----------------------------------------------------------------------------- +void CHL2_Player::HandleArmorReduction( void ) +{ + if ( m_flArmorReductionTime < gpGlobals->curtime ) + return; + + if ( ArmorValue() <= 0 ) + return; + + float flPercent = 1.0f - (( m_flArmorReductionTime - gpGlobals->curtime ) / ARMOR_DECAY_TIME ); + + int iArmor = Lerp( flPercent, m_iArmorReductionFrom, 0 ); + + SetArmorValue( iArmor ); +} + +//----------------------------------------------------------------------------- +// Purpose: Allow pre-frame adjustments on the player +//----------------------------------------------------------------------------- +void CHL2_Player::PreThink(void) +{ + if ( player_showpredictedposition.GetBool() ) + { + Vector predPos; + + UTIL_PredictedPosition( this, player_showpredictedposition_timestep.GetFloat(), &predPos ); + + NDebugOverlay::Box( predPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), 0, 255, 0, 0, 0.01f ); + NDebugOverlay::Line( GetAbsOrigin(), predPos, 0, 255, 0, 0, 0.01f ); + } + + // Riding a vehicle? + if ( IsInAVehicle() ) + { + VPROF( "CHL2_Player::PreThink-Vehicle" ); + // make sure we update the client, check for timed damage and update suit even if we are in a vehicle + UpdateClientData(); + CheckTimeBasedDamage(); + + // Allow the suit to recharge when in the vehicle. + SuitPower_Update(); + CheckSuitUpdate(); + CheckSuitZoom(); + + WaterMove(); + return; + } + + // This is an experiment of mine- autojumping! + // only affects you if sv_autojump is nonzero. + if( (GetFlags() & FL_ONGROUND) && sv_autojump.GetFloat() != 0 ) + { + VPROF( "CHL2_Player::PreThink-Autojump" ); + // check autojump + Vector vecCheckDir; + + vecCheckDir = GetAbsVelocity(); + + float flVelocity = VectorNormalize( vecCheckDir ); + + if( flVelocity > 200 ) + { + // Going fast enough to autojump + vecCheckDir = WorldSpaceCenter() + vecCheckDir * 34 - Vector( 0, 0, 16 ); + + trace_t tr; + + UTIL_TraceHull( WorldSpaceCenter() - Vector( 0, 0, 16 ), vecCheckDir, NAI_Hull::Mins(HULL_TINY_CENTERED),NAI_Hull::Maxs(HULL_TINY_CENTERED), MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER, &tr ); + + //NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 10 ); + + if( tr.fraction == 1.0 && !tr.startsolid ) + { + // Now trace down! + UTIL_TraceLine( vecCheckDir, vecCheckDir - Vector( 0, 0, 64 ), MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &tr ); + + //NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 10 ); + + if( tr.fraction == 1.0 && !tr.startsolid ) + { + // !!!HACKHACK + // I KNOW, I KNOW, this is definitely not the right way to do this, + // but I'm prototyping! (sjb) + Vector vecNewVelocity = GetAbsVelocity(); + vecNewVelocity.z += 250; + SetAbsVelocity( vecNewVelocity ); + } + } + } + } + + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-Speed" ); + HandleSpeedChanges(); +#ifdef HL2_EPISODIC + HandleArmorReduction(); +#endif + +#ifdef _XBOX + if( m_bIsAutoSprinting ) + { + // Stop sprinting if the player lets off the stick for a moment. + if( GetStickDist() == 0.0f ) + { + if( gpGlobals->curtime > m_fAutoSprintMinTime ) + { + StopSprinting(); + } + } + else + { + // Stop sprinting one half second after the player stops inputting with the move stick. + m_fAutoSprintMinTime = gpGlobals->curtime + 0.5f; + } + } +#endif//_XBOX + VPROF_SCOPE_END(); + + if ( g_fGameOver || IsPlayerLockedInPlace() ) + return; // finale + + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-ItemPreFrame" ); + ItemPreFrame( ); + VPROF_SCOPE_END(); + + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-WaterMove" ); + WaterMove(); + VPROF_SCOPE_END(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CommanderUpdate" ); + CommanderUpdate(); + VPROF_SCOPE_END(); + + // Operate suit accessories and manage power consumption/charge + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-SuitPower_Update" ); + SuitPower_Update(); + VPROF_SCOPE_END(); + + // checks if new client data (for HUD and view control) needs to be sent to the client + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-UpdateClientData" ); + UpdateClientData(); + VPROF_SCOPE_END(); + + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckTimeBasedDamage" ); + CheckTimeBasedDamage(); + VPROF_SCOPE_END(); + + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckSuitUpdate" ); + CheckSuitUpdate(); + VPROF_SCOPE_END(); + + VPROF_SCOPE_BEGIN( "CHL2_Player::PreThink-CheckSuitZoom" ); + CheckSuitZoom(); + VPROF_SCOPE_END(); + + if (m_lifeState >= LIFE_DYING) + { + PlayerDeathThink(); + return; + } + +#ifdef HL2_EPISODIC + CheckFlashlight(); +#endif + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) + AddFlag( FL_ONTRAIN ); + else + RemoveFlag( FL_ONTRAIN ); + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) + { + CBaseEntity *pTrain = GetGroundEntity(); + float vel; + + if ( pTrain ) + { + if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) + pTrain = NULL; + } + + if ( !pTrain ) + { + if ( GetActiveWeapon() && (GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) + { + m_iTrain = TRAIN_ACTIVE | TRAIN_NEW; + + if ( m_nButtons & IN_FORWARD ) + { + m_iTrain |= TRAIN_FAST; + } + else if ( m_nButtons & IN_BACK ) + { + m_iTrain |= TRAIN_BACK; + } + else + { + m_iTrain |= TRAIN_NEUTRAL; + } + return; + } + else + { + trace_t trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38), + MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace ); + + if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt ) + pTrain = trainTrace.m_pEnt; + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(this) ) + { +// Warning( "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + } + else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + SetAbsVelocity( vec3_origin ); + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(static_cast(pTrain->m_flSpeed), static_cast(((CFuncTrackTrain*)pTrain)->GetMaxSpeed())); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + } + else if (m_iTrain & TRAIN_ACTIVE) + { + m_iTrain = TRAIN_NEW; // turn off train + } + + + // + // If we're not on the ground, we're falling. Update our falling velocity. + // + if ( !( GetFlags() & FL_ONGROUND ) ) + { + m_Local.m_flFallVelocity = -GetAbsVelocity().z; + } + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + bool bOnBarnacle = false; + CNPC_Barnacle *pBarnacle = NULL; + do + { + // FIXME: Not a good or fast solution, but maybe it will catch the bug! + pBarnacle = (CNPC_Barnacle*)gEntList.FindEntityByClassname( pBarnacle, "npc_barnacle" ); + if ( pBarnacle ) + { + if ( pBarnacle->GetEnemy() == this ) + { + bOnBarnacle = true; + } + } + } while ( pBarnacle ); + + if ( !bOnBarnacle ) + { + Warning( "Attached to barnacle?\n" ); + Assert( 0 ); + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; + } + else + { + SetAbsVelocity( vec3_origin ); + } + } + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Update weapon's ready status + UpdateWeaponPosture(); + + // Disallow shooting while zooming +#ifdef _XBOX + if ( IsZooming() ) + { + if( !GetActiveWeapon() || !GetActiveWeapon()->IsWeaponZoomed() ) + { + // If not zoomed because of the weapon itself, do not attack. + m_nButtons &= ~(IN_ATTACK|IN_ATTACK2); + } + } +#else + if ( m_nButtons & IN_ZOOM ) + { + //FIXME: Held weapons like the grenade get sad when this happens +#ifdef HL2_EPISODIC + // Episodic allows players to zoom while using a func_tank + if ( !m_hUseEntity || GetActiveWeapon()->IsWeaponVisible() ) +#endif + m_nButtons &= ~(IN_ATTACK|IN_ATTACK2); + } +#endif//_XBOX +} + +void CHL2_Player::PostThink( void ) +{ + BaseClass::PostThink(); + + if ( !g_fGameOver && !IsPlayerLockedInPlace() && IsAlive() ) + { + HandleAdmireGlovesAnimation(); + } +} + +void CHL2_Player::StartAdmireGlovesAnimation( void ) +{ + CBaseViewModel *vm = GetViewModel( 0 ); + + if ( vm && !GetActiveWeapon() ) + { + vm->SetWeaponModel( "models/weapons/v_hands.mdl", NULL ); + ShowViewModel( true ); + + int idealSequence = vm->SelectWeightedSequence( ACT_VM_IDLE ); + + if ( idealSequence >= 0 ) + { + vm->SendViewModelMatchingSequence( idealSequence ); + m_flAdmireGlovesAnimTime = gpGlobals->curtime + vm->SequenceDuration( idealSequence ); + } + } +} + +void CHL2_Player::HandleAdmireGlovesAnimation( void ) +{ + CBaseViewModel *pVM = GetViewModel(); + + if ( pVM && pVM->GetOwningWeapon() == NULL ) + { + if ( m_flAdmireGlovesAnimTime != 0.0 ) + { + if ( m_flAdmireGlovesAnimTime > gpGlobals->curtime ) + { + pVM->m_flPlaybackRate = 1.0f; + pVM->StudioFrameAdvance( ); + } + else if ( m_flAdmireGlovesAnimTime < gpGlobals->curtime ) + { + m_flAdmireGlovesAnimTime = 0.0f; + pVM->SetWeaponModel( NULL, NULL ); + } + } + } + else + m_flAdmireGlovesAnimTime = 0.0f; +} + +#define HL2PLAYER_RELOADGAME_ATTACK_DELAY 1.0f + +void CHL2_Player::Activate( void ) +{ + BaseClass::Activate(); + InitSprinting(); + +#ifdef HL2_EPISODIC + + // Delay attacks by 1 second after loading a game. + if ( GetActiveWeapon() ) + { + float flRemaining = GetActiveWeapon()->m_flNextPrimaryAttack - gpGlobals->curtime; + + if ( flRemaining < HL2PLAYER_RELOADGAME_ATTACK_DELAY ) + { + GetActiveWeapon()->m_flNextPrimaryAttack = gpGlobals->curtime + HL2PLAYER_RELOADGAME_ATTACK_DELAY; + } + + flRemaining = GetActiveWeapon()->m_flNextSecondaryAttack - gpGlobals->curtime; + + if ( flRemaining < HL2PLAYER_RELOADGAME_ATTACK_DELAY ) + { + GetActiveWeapon()->m_flNextSecondaryAttack = gpGlobals->curtime + HL2PLAYER_RELOADGAME_ATTACK_DELAY; + } + } + +#endif +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +Class_T CHL2_Player::Classify ( void ) +{ + // If player controlling another entity? If so, return this class + if (m_nControlClass != CLASS_NONE) + { + return m_nControlClass; + } + else + { + if(IsInAVehicle()) + { + IServerVehicle *pVehicle = GetVehicle(); + return pVehicle->ClassifyPassenger( this, CLASS_PLAYER ); + } + else + { + return CLASS_PLAYER; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: This is a generic function (to be implemented by sub-classes) to +// handle specific interactions between different types of characters +// (For example the barnacle grabbing an NPC) +// Input : Constant for the type of interaction +// Output : true - if sub-class has a response for the interaction +// false - if sub-class has no response +//----------------------------------------------------------------------------- +bool CHL2_Player::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) +{ + if ( interactionType == g_interactionBarnacleVictimDangle ) + return false; + + if (interactionType == g_interactionBarnacleVictimReleased) + { + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; + SetMoveType( MOVETYPE_WALK ); + return true; + } + else if (interactionType == g_interactionBarnacleVictimGrab) + { +#ifdef HL2_EPISODIC + CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); + if ( pAlyx ) + { + // Make Alyx totally hate this barnacle so that she saves the player. + int priority; + + priority = pAlyx->IRelationPriority(sourceEnt); + pAlyx->AddEntityRelationship( sourceEnt, D_HT, priority + 5 ); + } +#endif//HL2_EPISODIC + + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + ClearUseEntity(); + return true; + } + return false; +} + + +void CHL2_Player::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) +{ + // Handle FL_FROZEN. + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + ucmd->forwardmove = 0; + ucmd->sidemove = 0; + ucmd->upmove = 0; + ucmd->buttons &= ~IN_USE; + } + + // Can't use stuff while dead + if ( IsDead() ) + { + ucmd->buttons &= ~IN_USE; + } + + //Update our movement information + if ( ( ucmd->forwardmove != 0 ) || ( ucmd->sidemove != 0 ) || ( ucmd->upmove != 0 ) ) + { + m_flIdleTime -= TICK_INTERVAL * 2.0f; + + if ( m_flIdleTime < 0.0f ) + { + m_flIdleTime = 0.0f; + } + + m_flMoveTime += TICK_INTERVAL; + + if ( m_flMoveTime > 4.0f ) + { + m_flMoveTime = 4.0f; + } + } + else + { + m_flIdleTime += TICK_INTERVAL; + + if ( m_flIdleTime > 4.0f ) + { + m_flIdleTime = 4.0f; + } + + m_flMoveTime -= TICK_INTERVAL * 2.0f; + + if ( m_flMoveTime < 0.0f ) + { + m_flMoveTime = 0.0f; + } + } + + //Msg("Player time: [ACTIVE: %f]\t[IDLE: %f]\n", m_flMoveTime, m_flIdleTime ); + + BaseClass::PlayerRunCommand( ucmd, moveHelper ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets HL2 specific defaults. +//----------------------------------------------------------------------------- +void CHL2_Player::Spawn(void) +{ + +#ifndef HL2MP + SetModel( "models/player.mdl" ); +#endif + + BaseClass::Spawn(); + + // + // Our player movement speed is set once here. This will override the cl_xxxx + // cvars unless they are set to be lower than this. + // + //m_flMaxspeed = 320; + + if ( !IsSuitEquipped() ) + StartWalking(); + + SuitPower_SetCharge( 100 ); + + m_Local.m_iHideHUD |= HIDEHUD_CHAT; + + m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME)); + + InitSprinting(); + + GetPlayerProxy(); + + SetFlashlightPowerDrainScale( 1.0f ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InitSprinting( void ) +{ + StopSprinting(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns whether or not we are allowed to sprint now. +//----------------------------------------------------------------------------- +bool CHL2_Player::CanSprint() +{ + return ( m_bSprintEnabled && // Only if sprint is enabled + !IsWalking() && // Not if we're walking + !( m_Local.m_bDucked && !m_Local.m_bDucking ) && // Nor if we're ducking + (GetWaterLevel() != 3) && // Certainly not underwater + (GlobalEntity_GetState("suit_no_sprint") != GLOBAL_ON) ); // Out of the question without the sprint module +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#ifdef _XBOX +void CHL2_Player::StartAutoSprint() +{ + if( IsSprinting() ) + { + StopSprinting(); + } + else + { + StartSprinting(); + m_bIsAutoSprinting = true; + m_fAutoSprintMinTime = gpGlobals->curtime + 1.5f; + } +} +#endif// _XBOX + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::StartSprinting( void ) +{ + if( m_HL2Local.m_flSuitPower < 10 ) + { + // Don't sprint unless there's a reasonable + // amount of suit power. + CPASAttenuationFilter filter( this ); + filter.UsePredictionRules(); + EmitSound( filter, entindex(), "HL2Player.SprintNoPower" ); + return; + } + + if( !SuitPower_AddDevice( SuitDeviceSprint ) ) + return; + + CPASAttenuationFilter filter( this ); + filter.UsePredictionRules(); + EmitSound( filter, entindex(), "HL2Player.SprintStart" ); + + SetMaxSpeed( HL2_SPRINT_SPEED ); + m_fIsSprinting = true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::StopSprinting( void ) +{ + if ( m_HL2Local.m_bitsActiveDevices & SuitDeviceSprint.GetDeviceID() ) + { + SuitPower_RemoveDevice( SuitDeviceSprint ); + } + SetMaxSpeed( HL2_NORM_SPEED ); + m_fIsSprinting = false; + +#ifdef _XBOX + m_bIsAutoSprinting = false; + m_fAutoSprintMinTime = 0.0f; +#endif//_XBOX +} + + +//----------------------------------------------------------------------------- +// Purpose: Called to disable and enable sprint due to temporary circumstances: +// - Carrying a heavy object with the physcannon +//----------------------------------------------------------------------------- +void CHL2_Player::EnableSprint( bool bEnable ) +{ + if ( !bEnable && IsSprinting() ) + { + StopSprinting(); + } + + m_bSprintEnabled = bEnable; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::StartWalking( void ) +{ + SetMaxSpeed( HL2_WALK_SPEED ); + m_fIsWalking = true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::StopWalking( void ) +{ + SetMaxSpeed( HL2_NORM_SPEED ); + m_fIsWalking = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHL2_Player::CanZoom( CBaseEntity *pRequester ) +{ + if ( IsZooming() ) + return false; + + //Check our weapon + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::ToggleZoom(void) +{ + if( IsZooming() ) + { + StopZooming(); + } + else + { + StartZooming(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +zoom suit zoom +//----------------------------------------------------------------------------- +void CHL2_Player::StartZooming( void ) +{ + int iFOV = 25; + if ( SetFOV( this, iFOV, 0.4f ) ) + { + m_HL2Local.m_bZooming = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::StopZooming( void ) +{ + int iFOV = static_cast(GetZoomOwnerDesiredFOV( m_hZoomOwner )); + + if ( SetFOV( this, iFOV, 0.2f ) ) + { + m_HL2Local.m_bZooming = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHL2_Player::IsZooming( void ) +{ + if ( m_hZoomOwner != NULL ) + return true; + + return false; +} + +class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent +{ +public: + int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position ) + { + CHL2_Player *pPlayer = (CHL2_Player *)pObject->GetGameData(); + if ( pPlayer ) + { + if ( pPlayer->TouchedPhysics() ) + { + return 0; + } + } + return 1; + } +}; + +static CPhysicsPlayerCallback playerCallback; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::InitVCollision( void ) +{ + BaseClass::InitVCollision(); + + // Setup the HL2 specific callback. + IPhysicsPlayerController *pPlayerController = GetPhysicsController(); + if ( pPlayerController ) + { + pPlayerController->SetEventHandler( &playerCallback ); + } +} + + +CHL2_Player::~CHL2_Player( void ) +{ +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +bool CHL2_Player::CommanderFindGoal( commandgoal_t *pGoal ) +{ + CAI_BaseNPC *pAllyNpc; + trace_t tr; + Vector vecTarget; + Vector forward; + + EyeVectors( &forward ); + + //--------------------------------- + // MASK_SHOT on purpose! So that you don't hit the invisible hulls of the NPCs. + CTraceFilterSkipTwoEntities filter( this, PhysCannonGetHeldEntity( GetActiveWeapon() ), COLLISION_GROUP_INTERACTIVE_DEBRIS ); + + UTIL_TraceLine( EyePosition(), EyePosition() + forward * MAX_COORD_RANGE, MASK_SHOT, &filter, &tr ); + + if( !tr.DidHitWorld() ) + { + CUtlVector Allies; + AISquadIter_t iter; + for ( pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) + { + if ( pAllyNpc->IsCommandable() ) + Allies.AddToTail( pAllyNpc ); + } + + for( int i = 0 ; i < Allies.Count() ; i++ ) + { + if( Allies[ i ]->IsValidCommandTarget( tr.m_pEnt ) ) + { + pGoal->m_pGoalEntity = tr.m_pEnt; + return true; + } + } + } + + if( tr.fraction == 1.0 || (tr.surface.flags & SURF_SKY) ) + { + // Move commands invalid against skybox. + pGoal->m_vecGoalLocation = tr.endpos; + return false; + } + + if ( tr.m_pEnt->IsNPC() && ((CAI_BaseNPC *)(tr.m_pEnt))->IsCommandable() ) + { + pGoal->m_vecGoalLocation = tr.m_pEnt->GetAbsOrigin(); + } + else + { + vecTarget = tr.endpos; + + Vector mins( -16, -16, 0 ); + Vector maxs( 16, 16, 0 ); + + // Back up from whatever we hit so that there's enough space at the + // target location for a bounding box. + // Now trace down. + //UTIL_TraceLine( vecTarget, vecTarget - Vector( 0, 0, 8192 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + UTIL_TraceHull( vecTarget + tr.plane.normal * 24, + vecTarget - Vector( 0, 0, 8192 ), + mins, + maxs, + MASK_SOLID_BRUSHONLY, + this, + COLLISION_GROUP_NONE, + &tr ); + + + if ( !tr.startsolid ) + pGoal->m_vecGoalLocation = tr.endpos; + else + pGoal->m_vecGoalLocation = vecTarget; + } + + pAllyNpc = GetSquadCommandRepresentative(); + if ( !pAllyNpc ) + return false; + + vecTarget = pGoal->m_vecGoalLocation; + if ( !pAllyNpc->FindNearestValidGoalPos( vecTarget, &pGoal->m_vecGoalLocation ) ) + return false; + + return ( ( vecTarget - pGoal->m_vecGoalLocation ).LengthSqr() < Square( 15*12 ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CAI_BaseNPC *CHL2_Player::GetSquadCommandRepresentative() +{ + if ( m_pPlayerAISquad != NULL ) + { + CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(); + + if ( pAllyNpc ) + { + return pAllyNpc->GetSquadCommandRepresentative(); + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CHL2_Player::GetNumSquadCommandables() +{ + AISquadIter_t iter; + int c = 0; + for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) + { + if ( pAllyNpc->IsCommandable() ) + c++; + } + return c; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CHL2_Player::GetNumSquadCommandableMedics() +{ + AISquadIter_t iter; + int c = 0; + for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) + { + if ( pAllyNpc->IsCommandable() && pAllyNpc->IsMedic() ) + c++; + } + return c; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::CommanderUpdate() +{ + CAI_BaseNPC *pCommandRepresentative = GetSquadCommandRepresentative(); + bool bFollowMode = false; + if ( pCommandRepresentative ) + { + bFollowMode = ( pCommandRepresentative->GetCommandGoal() == vec3_invalid ); + + // set the variables for network transmission (to show on the hud) + m_HL2Local.m_iSquadMemberCount = GetNumSquadCommandables(); + m_HL2Local.m_iSquadMedicCount = GetNumSquadCommandableMedics(); + m_HL2Local.m_fSquadInFollowMode = bFollowMode; + + // debugging code for displaying extra squad indicators + /* + char *pszMoving = ""; + AISquadIter_t iter; + for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) + { + if ( pAllyNpc->IsCommandMoving() ) + { + pszMoving = "<-"; + break; + } + } + + NDebugOverlay::ScreenText( + 0.932, 0.919, + CFmtStr( "%d|%c%s", GetNumSquadCommandables(), ( bFollowMode ) ? 'F' : 'S', pszMoving ), + 255, 128, 0, 128, + 0 ); + */ + + } + else + { + m_HL2Local.m_iSquadMemberCount = 0; + m_HL2Local.m_iSquadMedicCount = 0; + m_HL2Local.m_fSquadInFollowMode = true; + } + + if ( m_QueuedCommand != CC_NONE && ( m_QueuedCommand == CC_FOLLOW || gpGlobals->realtime - m_RealTimeLastSquadCommand >= player_squad_double_tap_time.GetFloat() ) ) + { + CommanderExecute( m_QueuedCommand ); + m_QueuedCommand = CC_NONE; + } + else if ( !bFollowMode && pCommandRepresentative && m_CommanderUpdateTimer.Expired() && player_squad_transient_commands.GetBool() ) + { + m_CommanderUpdateTimer.Set(2.5); + + if ( pCommandRepresentative->ShouldAutoSummon() ) + CommanderExecute( CC_FOLLOW ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// bHandled - indicates whether to continue delivering this order to +// all allies. Allows us to stop delivering certain types of orders once we find +// a suitable candidate. (like picking up a single weapon. We don't wish for +// all allies to respond and try to pick up one weapon). +//----------------------------------------------------------------------------- +bool CHL2_Player::CommanderExecuteOne( CAI_BaseNPC *pNpc, const commandgoal_t &goal, CAI_BaseNPC **Allies, int numAllies ) +{ + if ( goal.m_pGoalEntity ) + { + return pNpc->TargetOrder( goal.m_pGoalEntity, Allies, numAllies ); + } + else if ( pNpc->IsInPlayerSquad() ) + { + pNpc->MoveOrder( goal.m_vecGoalLocation, Allies, numAllies ); + } + + return true; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CHL2_Player::CommanderExecute( CommanderCommand_t command ) +{ + CAI_BaseNPC *pPlayerSquadLeader = GetSquadCommandRepresentative(); + + if ( !pPlayerSquadLeader ) + { + EmitSound( "HL2Player.UseDeny" ); + return; + } + + int i; + CUtlVector Allies; + commandgoal_t goal; + + if ( command == CC_TOGGLE ) + { + if ( pPlayerSquadLeader->GetCommandGoal() != vec3_invalid ) + command = CC_FOLLOW; + else + command = CC_SEND; + } + else + { + if ( command == CC_FOLLOW && pPlayerSquadLeader->GetCommandGoal() == vec3_invalid ) + return; + } + + if ( command == CC_FOLLOW ) + { + goal.m_pGoalEntity = this; + goal.m_vecGoalLocation = vec3_invalid; + } + else + { + goal.m_pGoalEntity = NULL; + goal.m_vecGoalLocation = vec3_invalid; + + // Find a goal for ourselves. + if( !CommanderFindGoal( &goal ) ) + { + EmitSound( "HL2Player.UseDeny" ); + return; // just keep following + } + } + +#ifdef _DEBUG + if( goal.m_pGoalEntity == NULL && goal.m_vecGoalLocation == vec3_invalid ) + { + DevMsg( 1, "**ERROR: Someone sent an invalid goal to CommanderExecute!\n" ); + } +#endif // _DEBUG + + AISquadIter_t iter; + for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) + { + if ( pAllyNpc->IsCommandable() ) + Allies.AddToTail( pAllyNpc ); + } + + //--------------------------------- + // If the trace hits an NPC, send all ally NPCs a "target" order. Always + // goes to targeted one first +#ifdef DEBUG + int nAIs = g_AI_Manager.NumAIs(); +#endif + CAI_BaseNPC * pTargetNpc = (goal.m_pGoalEntity) ? goal.m_pGoalEntity->MyNPCPointer() : NULL; + + bool bHandled = false; + if( pTargetNpc ) + { + bHandled = !CommanderExecuteOne( pTargetNpc, goal, Allies.Base(), Allies.Count() ); + } + + for ( i = 0; !bHandled && i < Allies.Count(); i++ ) + { + if ( Allies[i] != pTargetNpc && Allies[i]->IsPlayerAlly() ) + { + bHandled = !CommanderExecuteOne( Allies[i], goal, Allies.Base(), Allies.Count() ); + } + Assert( nAIs == g_AI_Manager.NumAIs() ); // not coded to support mutating set of NPCs + } +} + +//----------------------------------------------------------------------------- +// Enter/exit commander mode, manage ally selection. +//----------------------------------------------------------------------------- +void CHL2_Player::CommanderMode() +{ + float commandInterval = gpGlobals->realtime - m_RealTimeLastSquadCommand; + m_RealTimeLastSquadCommand = gpGlobals->realtime; + if ( commandInterval < player_squad_double_tap_time.GetFloat() ) + { + m_QueuedCommand = CC_FOLLOW; + } + else + { + m_QueuedCommand = (player_squad_transient_commands.GetBool()) ? CC_SEND : CC_TOGGLE; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iImpulse - +//----------------------------------------------------------------------------- +void CHL2_Player::CheatImpulseCommands( int iImpulse ) +{ + switch( iImpulse ) + { + case 50: + { + CommanderMode(); + break; + } + + case 51: + { + // Cheat to create a dynamic resupply item + Vector vecForward; + AngleVectors( EyeAngles(), &vecForward ); + CBaseEntity *pItem = (CBaseEntity *)CreateEntityByName( "item_dynamic_resupply" ); + if ( pItem ) + { + Vector vecOrigin = GetAbsOrigin() + vecForward * 256 + Vector(0,0,64); + QAngle vecAngles( 0, GetAbsAngles().y - 90, 0 ); + pItem->SetAbsOrigin( vecOrigin ); + pItem->SetAbsAngles( vecAngles ); + pItem->KeyValue( "targetname", "resupply" ); + pItem->Spawn(); + pItem->Activate(); + } + break; + } + + case 52: + { + // Rangefinder + trace_t tr; + UTIL_TraceLine( EyePosition(), EyePosition() + EyeDirection3D() * MAX_COORD_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + if( tr.fraction != 1.0 ) + { + float flDist = (tr.startpos - tr.endpos).Length(); + float flDist2D = (tr.startpos - tr.endpos).Length2D(); + DevMsg( 1,"\nStartPos: %.4f %.4f %.4f --- EndPos: %.4f %.4f %.4f\n", tr.startpos.x,tr.startpos.y,tr.startpos.z,tr.endpos.x,tr.endpos.y,tr.endpos.z ); + DevMsg( 1,"3D Distance: %.4f units (%.2f feet) --- 2D Distance: %.4f units (%.2f feet)\n", flDist, flDist / 12.0, flDist2D, flDist2D / 12.0 ); + } + + break; + } + + default: + BaseClass::CheatImpulseCommands( iImpulse ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) +{ + BaseClass::SetupVisibility( pViewEntity, pvs, pvssize ); + + int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum(); + PointCameraSetupVisibility( this, area, pvs, pvssize ); + + // If the intro script is playing, we want to get it's visibility points + if ( g_hIntroScript ) + { + Vector vecOrigin; + CBaseEntity *pCamera; + if ( g_hIntroScript->GetIncludedPVSOrigin( &vecOrigin, &pCamera ) ) + { + // If it's a point camera, turn it on + CPointCamera *pPointCamera = dynamic_cast< CPointCamera* >(pCamera); + if ( pPointCamera ) + { + pPointCamera->SetActive( true ); + } + engine->AddOriginToPVS( vecOrigin ); + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::SuitPower_Update( void ) +{ + if( SuitPower_ShouldRecharge() ) + { + SuitPower_Charge( SUITPOWER_CHARGE_RATE * gpGlobals->frametime ); + } + else if( m_HL2Local.m_bitsActiveDevices ) + { + float flPowerLoad = m_flSuitPowerLoad; + +#ifndef _XBOX + //Since XBox quickly shuts off sprint if it isn't being used, this isn't an issue. + if( SuitPower_IsDeviceActive(SuitDeviceSprint) ) + { + if( !fabs(GetAbsVelocity().x) && !fabs(GetAbsVelocity().y) ) + { + // If player's not moving, don't drain sprint juice. + flPowerLoad -= SuitDeviceSprint.GetDeviceDrainRate(); + } + } +#endif//_XBOX + + if( SuitPower_IsDeviceActive(SuitDeviceFlashlight) ) + { + float factor; + + factor = 1.0f / m_flFlashlightPowerDrainScale; + + flPowerLoad -= ( SuitDeviceFlashlight.GetDeviceDrainRate() * (1.0f - factor) ); + } + + if( !SuitPower_Drain( flPowerLoad * gpGlobals->frametime ) ) + { + // TURN OFF ALL DEVICES!! + if( IsSprinting() ) + { + StopSprinting(); + } + + if( FlashlightIsOn() ) + { +#ifndef HL2MP + FlashlightTurnOff(); +#endif + } + } + + // turn off flashlight a little bit after it hits below one aux power notch (5%) + if( m_HL2Local.m_flSuitPower < 4.8f && FlashlightIsOn() ) + { +#ifndef HL2MP + FlashlightTurnOff(); +#endif + } + } +} + + +//----------------------------------------------------------------------------- +// Charge battery fully, turn off all devices. +//----------------------------------------------------------------------------- +void CHL2_Player::SuitPower_Initialize( void ) +{ + m_HL2Local.m_bitsActiveDevices = 0x00000000; + m_HL2Local.m_flSuitPower = 100.0; + m_flSuitPowerLoad = 0.0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Interface to drain power from the suit's power supply. +// Input: Amount of charge to remove (expressed as percentage of full charge) +// Output: Returns TRUE if successful, FALSE if not enough power available. +//----------------------------------------------------------------------------- +bool CHL2_Player::SuitPower_Drain( float flPower ) +{ + // Suitpower cheat on? + if ( sv_infinite_aux_power.GetBool() ) + return true; + + m_HL2Local.m_flSuitPower -= flPower; + + if( m_HL2Local.m_flSuitPower < 0.0 ) + { + // Power is depleted! + // Clamp and fail + m_HL2Local.m_flSuitPower = 0.0; + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Interface to add power to the suit's power supply +// Input: Amount of charge to add +//----------------------------------------------------------------------------- +void CHL2_Player::SuitPower_Charge( float flPower ) +{ + m_HL2Local.m_flSuitPower += flPower; + + if( m_HL2Local.m_flSuitPower > 100.0 ) + { + // Full charge, clamp. + m_HL2Local.m_flSuitPower = 100.0; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CHL2_Player::SuitPower_IsDeviceActive( const CSuitPowerDevice &device ) +{ + return (m_HL2Local.m_bitsActiveDevices & device.GetDeviceID()) != 0; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CHL2_Player::SuitPower_AddDevice( const CSuitPowerDevice &device ) +{ + // Make sure this device is NOT active!! + if( m_HL2Local.m_bitsActiveDevices & device.GetDeviceID() ) + return false; + + if( !IsSuitEquipped() ) + return false; + + m_HL2Local.m_bitsActiveDevices |= device.GetDeviceID(); + m_flSuitPowerLoad += device.GetDeviceDrainRate(); + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CHL2_Player::SuitPower_RemoveDevice( const CSuitPowerDevice &device ) +{ + // Make sure this device is active!! + if( ! (m_HL2Local.m_bitsActiveDevices & device.GetDeviceID()) ) + return false; + + if( !IsSuitEquipped() ) + return false; + + // Take a little bit of suit power when you disable a device. If the device is shutting off + // because the battery is drained, no harm done, the battery charge cannot go below 0. + // This code in combination with the delay before the suit can start recharging are a defense + // against exploits where the player could rapidly tap sprint and never run out of power. + SuitPower_Drain( device.GetDeviceDrainRate() * 0.1f ); + + m_HL2Local.m_bitsActiveDevices &= ~device.GetDeviceID(); + m_flSuitPowerLoad -= device.GetDeviceDrainRate(); + + if( m_HL2Local.m_bitsActiveDevices == 0x00000000 ) + { + // With this device turned off, we can set this timer which tells us when the + // suit power system entered a no-load state. + m_flTimeAllSuitDevicesOff = gpGlobals->curtime; + } + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#define SUITPOWER_BEGIN_RECHARGE_DELAY 0.5f +bool CHL2_Player::SuitPower_ShouldRecharge( void ) +{ + // Make sure all devices are off. + if( m_HL2Local.m_bitsActiveDevices != 0x00000000 ) + return false; + + // Is the system fully charged? + if( m_HL2Local.m_flSuitPower >= 100.0f ) + return false; + + // Has the system been in a no-load state for long enough + // to begin recharging? + if( gpGlobals->curtime < m_flTimeAllSuitDevicesOff + SUITPOWER_BEGIN_RECHARGE_DELAY ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ConVar sk_battery( "sk_battery","0" ); + +bool CHL2_Player::ApplyBattery( float powerMultiplier ) +{ + const float MAX_NORMAL_BATTERY = 100; + if ((ArmorValue() < MAX_NORMAL_BATTERY) && IsSuitEquipped()) + { + int pct; + char szcharge[64]; + + IncrementArmorValue( static_cast(sk_battery.GetFloat() * powerMultiplier), static_cast(MAX_NORMAL_BATTERY) ); + + CPASAttenuationFilter filter( this, "ItemBattery.Touch" ); + EmitSound( filter, entindex(), "ItemBattery.Touch" ); + + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + + UserMessageBegin( user, "ItemPickup" ); + WRITE_STRING( "item_battery" ); + MessageEnd(); + + + // Suit reports new power level + // For some reason this wasn't working in release build -- round it. + pct = (int)( (float)(ArmorValue() * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); + pct = (pct / 5); + if (pct > 0) + pct--; + + Q_snprintf( szcharge,sizeof(szcharge),"!HEV_%1dP", pct ); + + //UTIL_EmitSoundSuit(edict(), szcharge); + //SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC); + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CHL2_Player::FlashlightIsOn( void ) +{ + return IsEffectActive( EF_DIMLIGHT ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::FlashlightTurnOn( void ) +{ + if( m_bFlashlightDisabled ) + return; + + if( !SuitPower_AddDevice( SuitDeviceFlashlight ) ) + return; + + AddEffects( EF_DIMLIGHT ); + EmitSound( "HL2Player.FlashLightOn" ); + + variant_t flashlighton; + flashlighton.SetFloat( m_HL2Local.m_flSuitPower / 100.0f ); + + FirePlayerProxyOutput( "OnFlashlightOn", flashlighton, this, this ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::FlashlightTurnOff( void ) +{ + if( !SuitPower_RemoveDevice( SuitDeviceFlashlight ) ) + return; + + RemoveEffects( EF_DIMLIGHT ); + EmitSound( "HL2Player.FlashLightOff" ); + + variant_t flashlightoff; + flashlightoff.SetFloat( m_HL2Local.m_flSuitPower / 100.0f ); + FirePlayerProxyOutput( "OnFlashlightOff", flashlightoff, this, this ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#define FLASHLIGHT_RANGE Square(600) +bool CHL2_Player::IsIlluminatedByFlashlight( CBaseEntity *pEntity, float *flReturnDot ) +{ + if( !FlashlightIsOn() ) + return false; + + if( pEntity->Classify() == CLASS_BARNACLE && pEntity->GetEnemy() == this ) + { + // As long as my flashlight is on, the barnacle that's pulling me in is considered illuminated. + // This is because players often shine their flashlights at Alyx when they are in a barnacle's + // grasp, and wonder why Alyx isn't helping. Alyx isn't helping because the light isn't pointed + // at the barnacle. This will allow Alyx to see the barnacle no matter which way the light is pointed. + return true; + } + + // Within 50 feet? + float flDistSqr = GetAbsOrigin().DistToSqr(pEntity->GetAbsOrigin()); + if( flDistSqr > FLASHLIGHT_RANGE ) + return false; + + // Within 45 degrees? + Vector vecSpot = pEntity->WorldSpaceCenter(); + Vector los; + + // If the eyeposition is too close, move it back. Solves problems + // caused by the player being too close the target. + if ( flDistSqr < (128 * 128) ) + { + Vector vecForward; + EyeVectors( &vecForward ); + Vector vecMovedEyePos = EyePosition() - (vecForward * 128); + los = ( vecSpot - vecMovedEyePos ); + } + else + { + los = ( vecSpot - EyePosition() ); + } + + VectorNormalize( los ); + Vector facingDir = EyeDirection3D( ); + float flDot = DotProduct( los, facingDir ); + + if ( flReturnDot ) + { + *flReturnDot = flDot; + } + + if ( flDot < 0.92387f ) + return false; + + if( !FVisible(pEntity) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Let NPCs know when the flashlight is trained on them +//----------------------------------------------------------------------------- +void CHL2_Player::CheckFlashlight( void ) +{ + if ( !FlashlightIsOn() ) + return; + + if ( m_flNextFlashlightCheckTime > gpGlobals->curtime ) + return; + m_flNextFlashlightCheckTime = gpGlobals->curtime + FLASHLIGHT_NPC_CHECK_INTERVAL; + + // Loop through NPCs looking for illuminated ones + for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + CAI_BaseNPC *pNPC = g_AI_Manager.AccessAIs()[i]; + + float flDot; + + if ( IsIlluminatedByFlashlight( pNPC, &flDot ) ) + { + pNPC->PlayerHasIlluminatedNPC( this, flDot ); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::SetPlayerUnderwater( bool state ) +{ + if ( state ) + { + SuitPower_AddDevice( SuitDeviceBreather ); + } + else + { + SuitPower_RemoveDevice( SuitDeviceBreather ); + } + + BaseClass::SetPlayerUnderwater( state ); +} + +//----------------------------------------------------------------------------- +bool CHL2_Player::PassesDamageFilter( const CTakeDamageInfo &info ) +{ + if( info.GetAttacker()->MyNPCPointer() && info.GetAttacker()->MyNPCPointer()->IsPlayerAlly() ) + { + return false; + } + + return BaseClass::PassesDamageFilter( info ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputDisableFlashlight( inputdata_t &inputdata ) +{ + if( FlashlightIsOn() ) + FlashlightTurnOff(); + + m_bFlashlightDisabled = true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputEnableFlashlight( inputdata_t &inputdata ) +{ + m_bFlashlightDisabled = false; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputIgnoreFallDamage( inputdata_t &inputdata ) +{ + float timeToIgnore = inputdata.value.Float(); + + if ( timeToIgnore <= 0.0 ) + timeToIgnore = TIME_IGNORE_FALL_DAMAGE; + + m_flTimeIgnoreFallDamage = gpGlobals->curtime + timeToIgnore; +} + +//----------------------------------------------------------------------------- +// Purpose: Notification of a player's npc ally in the players squad being killed +//----------------------------------------------------------------------------- +void CHL2_Player::OnSquadMemberKilled( inputdata_t &data ) +{ + // send a message to the client, to notify the hud of the loss + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + UserMessageBegin( user, "SquadMemberDied" ); + MessageEnd(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity ) +{ + CAI_BaseNPC *pAttacker = pAttackerEntity->MyNPCPointer(); + if ( pAttacker ) + { + const Vector &origin = GetAbsOrigin(); + for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + const float NEAR_Z = 12*12; + const float NEAR_XY_SQ = Square( 50*12 ); + CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i]; + if ( pNpc->IsPlayerAlly() ) + { + const Vector &originNpc = pNpc->GetAbsOrigin(); + if ( fabsf( originNpc.z - origin.z ) < NEAR_Z ) + { + if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ ) + { + pNpc->OnFriendDamaged( this, pAttacker ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHL2_Player::OnTakeDamage( const CTakeDamageInfo &info ) +{ + if ( GlobalEntity_GetState( "gordon_invulnerable" ) == GLOBAL_ON ) + return 0; + + if ( ( info.GetDamageType() & DMG_FALL ) && m_flTimeIgnoreFallDamage > gpGlobals->curtime ) + { + m_flTimeIgnoreFallDamage = 0; + return 0; + } + + if( info.GetDamageType() & DMG_BLAST_SURFACE ) + { + if( GetWaterLevel() > 2 ) + { + // Don't take blast damage from anything above the surface. + if( info.GetInflictor()->GetWaterLevel() == 0 ) + { + return 0; + } + } + } + + if ( info.GetDamage() > 0.0f ) + { + m_flLastDamageTime = gpGlobals->curtime; + + if ( info.GetAttacker() ) + NotifyFriendsOfDamage( info.GetAttacker() ); + } + + // Modify the amount of damage the player takes, based on skill. + CTakeDamageInfo playerDamage = info; + + // Should we run this damage through the skill level adjustment? + bool bAdjustForSkillLevel = true; + + if( info.GetDamageType() == DMG_GENERIC && info.GetAttacker() == this && info.GetInflictor() == this ) + { + // Only do a skill level adjustment if the player isn't his own attacker AND inflictor. + // This prevents damage from SetHealth() inputs from being adjusted for skill level. + bAdjustForSkillLevel = false; + } + + if( bAdjustForSkillLevel ) + { + playerDamage.AdjustPlayerDamageTakenForSkillLevel(); + } + + return BaseClass::OnTakeDamage( playerDamage ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &info - +//----------------------------------------------------------------------------- +int CHL2_Player::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + // Drown + if( info.GetDamageType() & DMG_DROWN ) + { + if( m_idrowndmg == m_idrownrestored ) + { + EmitSound( "Player.DrownStart" ); + } + else + { + EmitSound( "Player.DrownContinue" ); + } + } + + // Burnt + if ( info.GetDamageType() & DMG_BURN ) + { + EmitSound( "HL2Player.BurnPain" ); + } + + + if( (info.GetDamageType() & DMG_SLASH) && hl2_episodic.GetBool() ) + { + if( m_afPhysicsFlags & PFLAG_USING ) + { + // Stop the player using a rotating button for a short time if hit by a creature's melee attack. + // This is for the antlion burrow-corking training in EP1 (sjb). + SuspendUse( 0.5f ); + } + } + + + // Call the base class implementation + return BaseClass::OnTakeDamage_Alive( info ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::OnDamagedByExplosion( const CTakeDamageInfo &info ) +{ + if ( info.GetInflictor() && info.GetInflictor()->ClassMatches( "mortarshell" ) ) + { + // No ear ringing for mortar + UTIL_ScreenShake( info.GetInflictor()->GetAbsOrigin(), 4.0, 1.0, 0.5, 1000, SHAKE_START, false ); + return; + } + BaseClass::OnDamagedByExplosion( info ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CHL2_Player::ShouldShootMissTarget( CBaseCombatCharacter *pAttacker ) +{ + if( gpGlobals->curtime > m_flTargetFindTime ) + { + // Put this off into the future again. + m_flTargetFindTime = gpGlobals->curtime + random->RandomFloat( 3, 5 ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Notifies Alyx that player has put a combine ball into a socket so she can comment on it. +// Input : pCombineBall - ball the was socketed +//----------------------------------------------------------------------------- +void CHL2_Player::CombineBallSocketed( CPropCombineBall *pCombineBall ) +{ +#ifdef HL2_EPISODIC + CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); + if ( pAlyx ) + { + pAlyx->CombineBallSocketed( pCombineBall->NumBounces() ); + } +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) +{ + BaseClass::Event_KilledOther( pVictim, info ); + +#ifdef HL2_EPISODIC + CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); + if ( pAlyx ) + { + pAlyx->PlayerKilledOther( pVictim, info ); + } +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::Event_Killed( const CTakeDamageInfo &info ) +{ + BaseClass::Event_Killed( info ); + + NotifyScriptsOfDeath(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::NotifyScriptsOfDeath( void ) +{ + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "scripted_sequence" ); + + while( pEnt ) + { + variant_t emptyVariant; + pEnt->AcceptInput( "ScriptPlayerDeath", NULL, NULL, emptyVariant, 0 ); + + pEnt = gEntList.FindEntityByClassname( pEnt, "scripted_sequence" ); + } + + pEnt = gEntList.FindEntityByClassname( NULL, "logic_choreographed_scene" ); + + while( pEnt ) + { + variant_t emptyVariant; + pEnt->AcceptInput( "ScriptPlayerDeath", NULL, NULL, emptyVariant, 0 ); + + pEnt = gEntList.FindEntityByClassname( pEnt, "logic_choreographed_scene" ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::GetAutoaimVector( autoaim_params_t ¶ms ) +{ + BaseClass::GetAutoaimVector( params ); + + if ( IsXbox() ) + { + if( IsInAVehicle() ) + { + if( m_hLockedAutoAimEntity && m_hLockedAutoAimEntity->IsAlive() && ShouldKeepLockedAutoaimTarget(m_hLockedAutoAimEntity) ) + { + if( params.m_hAutoAimEntity && params.m_hAutoAimEntity != m_hLockedAutoAimEntity ) + { + // Autoaim has picked a new target. Switch. + m_hLockedAutoAimEntity = params.m_hAutoAimEntity; + } + + // Ignore autoaim and just keep aiming at this target. + params.m_hAutoAimEntity = m_hLockedAutoAimEntity; + Vector vecTarget = m_hLockedAutoAimEntity->BodyTarget( EyePosition(), false ); + Vector vecDir = vecTarget - EyePosition(); + VectorNormalize( vecDir ); + + params.m_vecAutoAimDir = vecDir; + params.m_vecAutoAimPoint = vecTarget; + return; + } + else + { + m_hLockedAutoAimEntity = NULL; + } + } + + // If the player manually gets his crosshair onto a target, make that target sticky + if( params.m_fScale != AUTOAIM_SCALE_DIRECT_ONLY ) + { + // Only affect this for 'real' queries + //if( params.m_hAutoAimEntity && params.m_bOnTargetNatural ) + if( params.m_hAutoAimEntity ) + { + // Turn on sticky. + m_HL2Local.m_bStickyAutoAim = true; + + if( IsInAVehicle() ) + { + m_hLockedAutoAimEntity = params.m_hAutoAimEntity; + } + } + else if( !params.m_hAutoAimEntity ) + { + // Turn off sticky only if there's no target at all. + m_HL2Local.m_bStickyAutoAim = false; + + m_hLockedAutoAimEntity = NULL; + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CHL2_Player::ShouldKeepLockedAutoaimTarget( EHANDLE hLockedTarget ) +{ + Vector vecLooking; + Vector vecToTarget; + + vecToTarget = hLockedTarget->WorldSpaceCenter() - EyePosition(); + float flDist = vecToTarget.Length2D(); + VectorNormalize( vecToTarget ); + + if( flDist > autoaim_max_dist.GetFloat() ) + return false; + + float flDot; + + vecLooking = EyeDirection3D(); + flDot = DotProduct( vecLooking, vecToTarget ); + + if( flDot < autoaim_unlock_target.GetFloat() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iCount - +// iAmmoIndex - +// bSuppressSound - +// Output : int +//----------------------------------------------------------------------------- +int CHL2_Player::GiveAmmo( int nCount, int nAmmoIndex, bool bSuppressSound) +{ + // Don't try to give the player invalid ammo indices. + if (nAmmoIndex < 0) + return 0; + + bool bCheckAutoSwitch = false; + if (!HasAnyAmmoOfType(nAmmoIndex)) + { + bCheckAutoSwitch = true; + } + + int nAdd = BaseClass::GiveAmmo(nCount, nAmmoIndex, bSuppressSound); + + if ( nCount > 0 && nAdd == 0 ) + { + // we've been denied the pickup, display a hud icon to show that + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + UserMessageBegin( user, "AmmoDenied" ); + WRITE_SHORT( nAmmoIndex ); + MessageEnd(); + } + + // + // If I was dry on ammo for my best weapon and justed picked up ammo for it, + // autoswitch to my best weapon now. + // + if (bCheckAutoSwitch) + { + CBaseCombatWeapon *pWeapon = g_pGameRules->GetNextBestWeapon(this, GetActiveWeapon()); + + if ( pWeapon && pWeapon->GetPrimaryAmmoType() == nAmmoIndex ) + { + SwitchToNextBestWeapon(GetActiveWeapon()); + } + } + + return nAdd; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CHL2_Player::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) +{ +#ifndef HL2MP + if ( pWeapon->ClassMatches( "weapon_stunstick" ) ) + { + if ( ApplyBattery( 0.5 ) ) + UTIL_Remove( pWeapon ); + return false; + } +#endif + + return BaseClass::Weapon_CanUse( pWeapon ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pWeapon - +//----------------------------------------------------------------------------- +void CHL2_Player::Weapon_Equip( CBaseCombatWeapon *pWeapon ) +{ +#if HL2_SINGLE_PRIMARY_WEAPON_MODE + + if ( pWeapon->GetSlot() == WEAPON_PRIMARY_SLOT ) + { + Weapon_DropSlot( WEAPON_PRIMARY_SLOT ); + } + +#endif + + BaseClass::Weapon_Equip( pWeapon ); +} + +//----------------------------------------------------------------------------- +// Purpose: Player reacts to bumping a weapon. +// Input : pWeapon - the weapon that the player bumped into. +// Output : Returns true if player picked up the weapon +//----------------------------------------------------------------------------- +bool CHL2_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) +{ + +#if HL2_SINGLE_PRIMARY_WEAPON_MODE + + CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); + + // Can I have this weapon type? + if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) + { + if ( gEvilImpulse101 ) + { + UTIL_Remove( pWeapon ); + } + return false; + } + + // ---------------------------------------- + // If I already have it just take the ammo + // ---------------------------------------- + if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType())) + { + //Only remove the weapon if we attained ammo from it + if ( Weapon_EquipAmmoOnly( pWeapon ) == false ) + return false; + + // Only remove me if I have no ammo left + // Can't just check HasAnyAmmo because if I don't use clips, I want to be removed, + if ( pWeapon->UsesClipsForAmmo1() && pWeapon->HasPrimaryAmmo() ) + return false; + + UTIL_Remove( pWeapon ); + return false; + } + // ------------------------- + // Otherwise take the weapon + // ------------------------- + else + { + //Make sure we're not trying to take a new weapon type we already have + if ( Weapon_SlotOccupied( pWeapon ) ) + { + CBaseCombatWeapon *pActiveWeapon = Weapon_GetSlot( WEAPON_PRIMARY_SLOT ); + + if ( pActiveWeapon != NULL && pActiveWeapon->HasAnyAmmo() == false && Weapon_CanSwitchTo( pWeapon ) ) + { + Weapon_Equip( pWeapon ); + return true; + } + + //Attempt to take ammo if this is the gun we're holding already + if ( Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType() ) ) + { + Weapon_EquipAmmoOnly( pWeapon ); + } + + return false; + } + + pWeapon->CheckRespawn(); + + pWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); + pWeapon->AddEffects( EF_NODRAW ); + + Weapon_Equip( pWeapon ); + + EmitSound( "HL2Player.PickupWeapon" ); + + return true; + } +#else + + return BaseClass::BumpWeapon( pWeapon ); + +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *cmd - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHL2_Player::ClientCommand(const char *cmd) +{ +#if HL2_SINGLE_PRIMARY_WEAPON_MODE + + //Drop primary weapon + if( stricmp( cmd, "DropPrimary" ) == 0 ) + { + Weapon_DropSlot( WEAPON_PRIMARY_SLOT ); + return true; + } + +#endif + + if ( !stricmp( cmd, "emit" ) ) + { + CSingleUserRecipientFilter filter( this ); + if ( engine->Cmd_Argc() > 1 ) + { + EmitSound( filter, entindex(), engine->Cmd_Argv( 1 ) ); + } + else + { + EmitSound( filter, entindex(), "Test.Sound" ); + } + return true; + } + + return BaseClass::ClientCommand( cmd ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : void CBasePlayer::PlayerUse +//----------------------------------------------------------------------------- +void CHL2_Player::PlayerUse ( void ) +{ + // Was use pressed or released? + if ( ! ((m_nButtons | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + if ( m_afButtonPressed & IN_USE ) + { + // Currently using a latched entity? + if ( ClearUseEntity() ) + { + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) + { + m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = GetGroundEntity(); + if ( pTrain && !(m_nButtons & IN_JUMP) && (GetFlags() & FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(this) ) + { + m_afPhysicsFlags |= PFLAG_DIROVERRIDE; + m_iTrain = TrainSpeed(static_cast(pTrain->m_flSpeed), static_cast(((CFuncTrackTrain*)pTrain)->GetMaxSpeed())); + m_iTrain |= TRAIN_NEW; + EmitSound( "HL2Player.TrainUse" ); + return; + } + } + } + + // Tracker 3926: We can't +USE something if we're climbing a ladder + if ( GetMoveType() == MOVETYPE_LADDER ) + { + return; + } + } + + if( m_flTimeUseSuspended > gpGlobals->curtime ) + { + // Something has temporarily stopped us being able to USE things. + // Obviously, this should be used very carefully.(sjb) + return; + } + + CBaseEntity *pUseEntity = FindUseEntity(); + + bool usedSomething = false; + + // Found an object + if ( pUseEntity ) + { + //!!!UNDONE: traceline here to prevent +USEing buttons through walls + int caps = pUseEntity->ObjectCaps(); + variant_t emptyVariant; + + if ( m_afButtonPressed & IN_USE ) + { + // Robin: Don't play sounds for NPCs, because NPCs will allow respond with speech. + if ( !pUseEntity->MyNPCPointer() ) + { + EmitSound( "HL2Player.Use" ); + } + } + + if ( ( (m_nButtons & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE ); + + usedSomething = true; + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + else if ( (m_afButtonReleased & IN_USE) && (pUseEntity->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE ); + + usedSomething = true; + } + +#if HL2_SINGLE_PRIMARY_WEAPON_MODE + + //Check for weapon pick-up + if ( m_afButtonPressed & IN_USE ) + { + CBaseCombatWeapon *pWeapon = dynamic_cast(pUseEntity); + + if ( ( pWeapon != NULL ) && ( Weapon_CanSwitchTo( pWeapon ) ) ) + { + //Try to take ammo or swap the weapon + if ( Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType() ) ) + { + Weapon_EquipAmmoOnly( pWeapon ); + } + else + { + Weapon_DropSlot( pWeapon->GetSlot() ); + Weapon_Equip( pWeapon ); + } + + usedSomething = true; + } + } +#endif + } + else if ( m_afButtonPressed & IN_USE ) + { + // Signal that we want to play the deny sound, unless the user is +USEing on a ladder! + // The sound is emitted in ItemPostFrame, since that occurs after GameMovement::ProcessMove which + // lets the ladder code unset this flag. + m_bPlayUseDenySound = true; + } + + // Debounce the use key + if ( usedSomething && pUseEntity ) + { + m_Local.m_nOldButtons |= IN_USE; + m_afButtonPressed &= ~IN_USE; + } +} + +ConVar sv_show_crosshair_target( "sv_show_crosshair_target", "0" ); + +//----------------------------------------------------------------------------- +// Purpose: Updates the posture of the weapon from lowered to ready +//----------------------------------------------------------------------------- +void CHL2_Player::UpdateWeaponPosture( void ) +{ + CBaseHLCombatWeapon *pWeapon = dynamic_cast(GetActiveWeapon()); + + if ( pWeapon && m_LowerWeaponTimer.Expired() && pWeapon->CanLower() ) + { + m_LowerWeaponTimer.Set( .3 ); + VPROF( "CHL2_Player::UpdateWeaponPosture-CheckLower" ); + Vector vecAim = BaseClass::GetAutoaimVector( AUTOAIM_SCALE_DIRECT_ONLY ); + + const float CHECK_FRIENDLY_RANGE = 50 * 12; + trace_t tr; + UTIL_TraceLine( EyePosition(), EyePosition() + vecAim * CHECK_FRIENDLY_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + CBaseEntity *aimTarget = tr.m_pEnt; + + //If we're over something + if ( aimTarget && !tr.DidHitWorld() ) + { + if ( !aimTarget->IsNPC() || aimTarget->MyNPCPointer()->GetState() != NPC_STATE_COMBAT ) + { + Disposition_t dis = IRelationType( aimTarget ); + + //Debug info for seeing what an object "cons" as + if ( sv_show_crosshair_target.GetBool() ) + { + int text_offset = BaseClass::DrawDebugTextOverlays(); + + char tempstr[255]; + + switch ( dis ) + { + case D_LI: + Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Like" ); + break; + + case D_HT: + Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Hate" ); + break; + + case D_FR: + Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Fear" ); + break; + + case D_NU: + Q_snprintf( tempstr, sizeof(tempstr), "Disposition: Neutral" ); + break; + + default: + case D_ER: + Q_snprintf( tempstr, sizeof(tempstr), "Disposition: !!!ERROR!!!" ); + break; + } + + //Draw the text + NDebugOverlay::EntityText( aimTarget->entindex(), text_offset, tempstr, 0 ); + } + + //See if we hates it + if ( dis == D_LI ) + { + //We're over a friendly, drop our weapon + if ( Weapon_Lower() == false ) + { + //FIXME: We couldn't lower our weapon! + } + + return; + } + } + } + + if ( Weapon_Ready() == false ) + { + //FIXME: We couldn't raise our weapon! + } + } + + if( hl2_xbox_aiming.GetBool() ) + { + if( !pWeapon || !m_AutoaimTimer.Expired() ) + { + return; + } + + m_AutoaimTimer.Set( .1 ); + + VPROF( "hl2_xbox_aiming" ); + + // Call the autoaim code to update the local player data, which allows the client to update. + autoaim_params_t params; + params.m_fScale = AUTOAIM_SCALE_DEFAULT; + params.m_fMaxDist = autoaim_max_dist.GetFloat(); + GetAutoaimVector( params ); + m_HL2Local.m_hAutoAimTarget.Set( params.m_hAutoAimEntity ); + m_HL2Local.m_vecAutoAimPoint.Set( params.m_vecAutoAimPoint ); + return; + } + else + { + // Make sure there's no residual autoaim target if the user changes the xbox_aiming convar on the fly. + m_HL2Local.m_hAutoAimTarget.Set(NULL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Lowers the weapon posture (for hovering over friendlies) +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHL2_Player::Weapon_Lower( void ) +{ + VPROF( "CHL2_Player::Weapon_Lower" ); + // Already lowered? + if ( m_HL2Local.m_bWeaponLowered ) + return true; + + m_HL2Local.m_bWeaponLowered = true; + + CBaseHLCombatWeapon *pWeapon = dynamic_cast(GetActiveWeapon()); + + if ( pWeapon == NULL ) + return false; + + return pWeapon->Lower(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the weapon posture to normal +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHL2_Player::Weapon_Ready( void ) +{ + VPROF( "CHL2_Player::Weapon_Ready" ); + + // Already ready? + if ( m_HL2Local.m_bWeaponLowered == false ) + return true; + + m_HL2Local.m_bWeaponLowered = false; + + CBaseHLCombatWeapon *pWeapon = dynamic_cast(GetActiveWeapon()); + + if ( pWeapon == NULL ) + return false; + + return pWeapon->Ready(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether or not we can switch to the given weapon. +// Input : pWeapon - +//----------------------------------------------------------------------------- +bool CHL2_Player::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) +{ + CBasePlayer *pPlayer = (CBasePlayer *)this; +#if !defined( CLIENT_DLL ) + IServerVehicle *pVehicle = pPlayer->GetVehicle(); +#else + IClientVehicle *pVehicle = pPlayer->GetVehicle(); +#endif + if (pVehicle && !pPlayer->UsingStandardWeaponsInVehicle()) + return false; + + if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) ) + return false; + + if ( !pWeapon->CanDeploy() ) + return false; + + if ( GetActiveWeapon() ) + { + if ( PhysCannonGetHeldEntity( GetActiveWeapon() ) == pWeapon && + Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()) ) + { + return true; + } + + if ( !GetActiveWeapon()->CanHolster() ) + return false; + } + + return true; +} + +void CHL2_Player::PickupObject( CBaseEntity *pObject, bool bLimitMassAndSize ) +{ + // can't pick up what you're standing on + if ( GetGroundEntity() == pObject ) + return; + + if ( bLimitMassAndSize == true ) + { + if ( CBasePlayer::CanPickupObject( pObject, 35, 128 ) == false ) + return; + } + + // Can't be picked up if NPCs are on me + if ( pObject->HasNPCsOnIt() ) + return; + + PlayerPickupObject( this, pObject ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CBaseEntity +//----------------------------------------------------------------------------- +bool CHL2_Player::IsHoldingEntity( CBaseEntity *pEnt ) +{ + return PlayerPickupControllerIsHoldingEntity( m_hUseEntity, pEnt ); +} + +float CHL2_Player::GetHeldObjectMass( IPhysicsObject *pHeldObject ) +{ + float mass = PlayerPickupGetHeldObjectMass( m_hUseEntity, pHeldObject ); + if ( mass == 0.0f ) + { + mass = PhysCannonGetHeldObjectMass( GetActiveWeapon(), pHeldObject ); + } + return mass; +} + +//----------------------------------------------------------------------------- +// Purpose: Force the player to drop any physics objects he's carrying +//----------------------------------------------------------------------------- +void CHL2_Player::ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldingThis ) +{ + if ( PhysIsInCallback() ) + { + variant_t value; + g_EventQueue.AddEvent( this, "ForceDropPhysObjects", value, 0.01f, pOnlyIfHoldingThis, this ); + return; + } + // Drop any objects being handheld. + ClearUseEntity(); + + // Then force the physcannon to drop anything it's holding, if it's our active weapon + PhysCannonForceDrop( GetActiveWeapon(), pOnlyIfHoldingThis ); +} + +void CHL2_Player::InputForceDropPhysObjects( inputdata_t &data ) +{ + ForceDropOfCarriedPhysObjects( data.pActivator ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::UpdateClientData( void ) +{ + if (m_DmgTake || m_DmgSave || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = GetLocalOrigin(); + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + damageOrigin = m_DmgOrigin; + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + m_DmgTake = clamp( m_DmgTake, 0, 255 ); + m_DmgSave = clamp( m_DmgSave, 0, 255 ); + + // If we're poisoned, but it wasn't this frame, don't send the indicator + // Without this check, any damage that occured to the player while they were + // recovering from a poison bite would register as poisonous as well and flash + // the whole screen! -- jdw + if ( visibleDamageBits & DMG_POISON ) + { + float flLastPoisonedDelta = gpGlobals->curtime - m_tbdPrev; + if ( flLastPoisonedDelta > 0.1f ) + { + visibleDamageBits &= ~DMG_POISON; + } + } + + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + UserMessageBegin( user, "Damage" ); + WRITE_BYTE( static_cast(m_DmgSave) ); + WRITE_BYTE( static_cast(m_DmgTake) ); + WRITE_LONG( visibleDamageBits ); + WRITE_FLOAT( damageOrigin.x ); //BUG: Should be fixed point (to hud) not floats + WRITE_FLOAT( damageOrigin.y ); //BUG: However, the HUD does _not_ implement bitfield messages (yet) + WRITE_FLOAT( damageOrigin.z ); //BUG: We use WRITE_VEC3COORD for everything else + MessageEnd(); + + m_DmgTake = 0; + m_DmgSave = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + BaseClass::UpdateClientData(); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CHL2_Player::OnRestore() +{ + BaseClass::OnRestore(); + m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME)); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +Vector CHL2_Player::EyeDirection2D( void ) +{ + Vector vecReturn = EyeDirection3D(); + vecReturn.z = 0; + vecReturn.AsVector2D().NormalizeInPlace(); + + return vecReturn; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +Vector CHL2_Player::EyeDirection3D( void ) +{ + Vector vecForward; + IServerVehicle *pVehicle = GetVehicle(); + if ( pVehicle ) + { + Assert( pVehicle ); + int nRole = pVehicle->GetPassengerRole( this ); + + Vector vecEyeOrigin; + QAngle angEyeAngles; + pVehicle->GetVehicleViewPosition( nRole, &vecEyeOrigin, &angEyeAngles ); + AngleVectors( angEyeAngles, &vecForward ); + } + else + { + AngleVectors( EyeAngles(), &vecForward ); + } + return vecForward; +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CHL2_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex ) +{ + // Recalculate proficiency! + SetCurrentWeaponProficiency( CalcWeaponProficiency( pWeapon ) ); + + // Come out of suit zoom mode + if ( IsZooming() ) + { + StopZooming(); + } + + return BaseClass::Weapon_Switch( pWeapon, viewmodelindex ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +WeaponProficiency_t CHL2_Player::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon ) +{ + WeaponProficiency_t proficiency; + + proficiency = WEAPON_PROFICIENCY_PERFECT; + + if( weapon_showproficiency.GetBool() != 0 ) + { + Msg("Player switched to %s, proficiency is %s\n", pWeapon->GetClassname(), GetWeaponProficiencyName( proficiency ) ); + } + + return proficiency; +} + +//----------------------------------------------------------------------------- +// Purpose: override how single player rays hit the player +//----------------------------------------------------------------------------- + +bool LineCircleIntersection( + const Vector2D ¢er, + const float radius, + const Vector2D &vLinePt, + const Vector2D &vLineDir, + float *fIntersection1, + float *fIntersection2) +{ + // Line = P + Vt + // Sphere = r (assume we've translated to origin) + // (P + Vt)^2 = r^2 + // VVt^2 + 2PVt + (PP - r^2) + // Solve as quadratic: (-b +/- sqrt(b^2 - 4ac)) / 2a + // If (b^2 - 4ac) is < 0 there is no solution. + // If (b^2 - 4ac) is = 0 there is one solution (a case this function doesn't support). + // If (b^2 - 4ac) is > 0 there are two solutions. + Vector2D P; + float a, b, c, sqr, insideSqr; + + + // Translate circle to origin. + P[0] = vLinePt[0] - center[0]; + P[1] = vLinePt[1] - center[1]; + + a = vLineDir.Dot(vLineDir); + b = 2.0f * P.Dot(vLineDir); + c = P.Dot(P) - (radius * radius); + + insideSqr = b*b - 4*a*c; + if(insideSqr <= 0.000001f) + return false; + + // Ok, two solutions. + sqr = (float)FastSqrt(insideSqr); + + float denom = 1.0 / (2.0f * a); + + *fIntersection1 = (-b - sqr) * denom; + *fIntersection2 = (-b + sqr) * denom; + + return true; +} + +static void Collision_ClearTrace( const Vector &vecRayStart, const Vector &vecRayDelta, CBaseTrace *pTrace ) +{ + pTrace->startpos = vecRayStart; + pTrace->endpos = vecRayStart; + pTrace->endpos += vecRayDelta; + pTrace->startsolid = false; + pTrace->allsolid = false; + pTrace->fraction = 1.0f; + pTrace->contents = 0; +} + + +bool IntersectRayWithAACylinder( const Ray_t &ray, + const Vector ¢er, float radius, float height, CBaseTrace *pTrace ) +{ + Assert( ray.m_IsRay ); + Collision_ClearTrace( ray.m_Start, ray.m_Delta, pTrace ); + + // First intersect the ray with the top + bottom planes + float halfHeight = height * 0.5; + + // Handle parallel case + Vector vStart = ray.m_Start - center; + Vector vEnd = vStart + ray.m_Delta; + + float flEnterFrac, flLeaveFrac; + if (FloatMakePositive(ray.m_Delta.z) < 1e-8) + { + if ( (vStart.z < -halfHeight) || (vStart.z > halfHeight) ) + { + return false; // no hit + } + flEnterFrac = 0.0f; flLeaveFrac = 1.0f; + } + else + { + // Clip the ray to the top and bottom of box + flEnterFrac = IntersectRayWithAAPlane( vStart, vEnd, 2, 1, halfHeight); + flLeaveFrac = IntersectRayWithAAPlane( vStart, vEnd, 2, 1, -halfHeight); + + if ( flLeaveFrac < flEnterFrac ) + { + float temp = flLeaveFrac; + flLeaveFrac = flEnterFrac; + flEnterFrac = temp; + } + + if ( flLeaveFrac < 0 || flEnterFrac > 1) + { + return false; + } + } + + // Intersect with circle + float flCircleEnterFrac, flCircleLeaveFrac; + if ( !LineCircleIntersection( vec3_origin.AsVector2D(), radius, + vStart.AsVector2D(), ray.m_Delta.AsVector2D(), &flCircleEnterFrac, &flCircleLeaveFrac ) ) + { + return false; // no hit + } + + Assert( flCircleEnterFrac <= flCircleLeaveFrac ); + if ( flCircleLeaveFrac < 0 || flCircleEnterFrac > 1) + { + return false; + } + + if ( flEnterFrac < flCircleEnterFrac ) + flEnterFrac = flCircleEnterFrac; + if ( flLeaveFrac > flCircleLeaveFrac ) + flLeaveFrac = flCircleLeaveFrac; + + if ( flLeaveFrac < flEnterFrac ) + return false; + + VectorMA( ray.m_Start, flEnterFrac , ray.m_Delta, pTrace->endpos ); + pTrace->fraction = flEnterFrac; + pTrace->contents = CONTENTS_SOLID; + + // Calculate the point on our center line where we're nearest the intersection point + Vector collisionCenter; + CalcClosestPointOnLineSegment( pTrace->endpos, center + Vector( 0, 0, halfHeight ), center - Vector( 0, 0, halfHeight ), collisionCenter ); + + // Our normal is the direction from that center point to the intersection point + pTrace->plane.normal = pTrace->endpos - collisionCenter; + VectorNormalize( pTrace->plane.normal ); + + return true; +} + + +bool CHL2_Player::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) +{ + if( g_pGameRules->IsMultiplayer() ) + { + return BaseClass::TestHitboxes( ray, fContentsMask, tr ); + } + else + { + Assert( ray.m_IsRay ); + + Vector mins, maxs; + + mins = WorldAlignMins(); + maxs = WorldAlignMaxs(); + + if ( IntersectRayWithAACylinder( ray, WorldSpaceCenter(), maxs.x * PLAYER_HULL_REDUCTION, maxs.z - mins.z, &tr ) ) + { + CStudioHdr *pStudioHdr = GetModelPtr( ); + if (!pStudioHdr) + return false; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet ); + if ( !set || !set->numhitboxes ) + return false; + + mstudiobbox_t *pbox = set->pHitbox( tr.hitbox ); + mstudiobone_t *pBone = pStudioHdr->pBone(pbox->bone); + tr.surface.name = "**studio**"; + tr.surface.flags = SURF_HITBOX; + tr.surface.surfaceProps = physprops->GetSurfaceIndex( pBone->pszSurfaceProp() ); + } + + return true; + } +} + +//--------------------------------------------------------- +// Show the player's scaled down bbox that we use for +// bullet impacts. +//--------------------------------------------------------- +void CHL2_Player::DrawDebugGeometryOverlays(void) +{ + BaseClass::DrawDebugGeometryOverlays(); + + if (m_debugOverlays & OVERLAY_BBOX_BIT) + { + Vector mins, maxs; + + mins = WorldAlignMins(); + maxs = WorldAlignMaxs(); + + mins.x *= PLAYER_HULL_REDUCTION; + mins.y *= PLAYER_HULL_REDUCTION; + + maxs.x *= PLAYER_HULL_REDUCTION; + maxs.y *= PLAYER_HULL_REDUCTION; + + NDebugOverlay::Box( GetAbsOrigin(), mins, maxs, 255, 0, 0, 100, 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Helper to remove from ladder +//----------------------------------------------------------------------------- +void CHL2_Player::ExitLadder() +{ + if ( MOVETYPE_LADDER != GetMoveType() ) + return; + + SetMoveType( MOVETYPE_WALK ); + SetMoveCollide( MOVECOLLIDE_DEFAULT ); + // Remove from ladder + m_HL2Local.m_hLadder.Set( NULL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Queues up a use deny sound, played in ItemPostFrame. +//----------------------------------------------------------------------------- +void CHL2_Player::PlayUseDenySound() +{ + m_bPlayUseDenySound = true; +} + + +void CHL2_Player::ItemPostFrame() +{ + BaseClass::ItemPostFrame(); + + if ( m_bPlayUseDenySound ) + { + m_bPlayUseDenySound = false; + EmitSound( "HL2Player.UseDeny" ); + } +} + + +void CHL2_Player::StartWaterDeathSounds( void ) +{ + CPASAttenuationFilter filter( this ); + + if ( m_sndLeeches == NULL ) + { + m_sndLeeches = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "coast.leech_bites_loop" , ATTN_NORM ); + } + + if ( m_sndLeeches ) + { + (CSoundEnvelopeController::GetController()).Play( m_sndLeeches, 1.0f, 100 ); + } + + if ( m_sndWaterSplashes == NULL ) + { + m_sndWaterSplashes = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "coast.leech_water_churn_loop" , ATTN_NORM ); + } + + if ( m_sndWaterSplashes ) + { + (CSoundEnvelopeController::GetController()).Play( m_sndWaterSplashes, 1.0f, 100 ); + } +} + +void CHL2_Player::StopWaterDeathSounds( void ) +{ + if ( m_sndLeeches ) + { + (CSoundEnvelopeController::GetController()).SoundFadeOut( m_sndLeeches, 0.5f, true ); + m_sndLeeches = NULL; + } + + if ( m_sndWaterSplashes ) + { + (CSoundEnvelopeController::GetController()).SoundFadeOut( m_sndWaterSplashes, 0.5f, true ); + m_sndWaterSplashes = NULL; + } +} + +//----------------------------------------------------------------------------- +// Shuts down sounds +//----------------------------------------------------------------------------- +void CHL2_Player::StopLoopingSounds( void ) +{ + if ( m_sndLeeches != NULL ) + { + (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndLeeches ); + m_sndLeeches = NULL; + } + + if ( m_sndWaterSplashes != NULL ) + { + (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndWaterSplashes ); + m_sndWaterSplashes = NULL; + } + + BaseClass::StopLoopingSounds(); +} + +//----------------------------------------------------------------------------- +void CHL2_Player::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendPlayerCriteria( set ); + + if ( GlobalEntity_GetIndex( "gordon_precriminal" ) == -1 ) + { + set.AppendCriteria( "gordon_precriminal", "0" ); + } +} + +CLogicPlayerProxy *CHL2_Player::GetPlayerProxy( void ) +{ + CLogicPlayerProxy *pProxy = dynamic_cast< CLogicPlayerProxy* > ( m_hPlayerProxy.Get() ); + + if ( pProxy == NULL ) + { + pProxy = (CLogicPlayerProxy*)gEntList.FindEntityByClassname(NULL, "logic_playerproxy" ); + + if ( pProxy == NULL ) + return NULL; + + pProxy->m_hPlayer = this; + m_hPlayerProxy = pProxy; + } + + return pProxy; +} + +void CHL2_Player::FirePlayerProxyOutput( const char *pszOutputName, variant_t variant, CBaseEntity *pActivator, CBaseEntity *pCaller ) +{ + if ( GetPlayerProxy() == NULL ) + return; + + GetPlayerProxy()->FireNamedOutput( pszOutputName, variant, pActivator, pCaller ); +} + +LINK_ENTITY_TO_CLASS( logic_playerproxy, CLogicPlayerProxy); + +BEGIN_DATADESC( CLogicPlayerProxy ) + DEFINE_OUTPUT( m_OnFlashlightOn, "OnFlashlightOn" ), + DEFINE_OUTPUT( m_OnFlashlightOff, "OnFlashlightOff" ), + DEFINE_OUTPUT( m_RequestedPlayerHealth, "PlayerHealth" ), + DEFINE_OUTPUT( m_PlayerHasAmmo, "PlayerHasAmmo" ), + DEFINE_OUTPUT( m_PlayerHasNoAmmo, "PlayerHasNoAmmo" ), + DEFINE_INPUTFUNC( FIELD_VOID, "RequestPlayerHealth", InputRequestPlayerHealth ), + DEFINE_INPUTFUNC( FIELD_VOID, "SetFlashlightSlowDrain", InputSetFlashlightSlowDrain ), + DEFINE_INPUTFUNC( FIELD_VOID, "SetFlashlightNormalDrain", InputSetFlashlightNormalDrain ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetPlayerHealth", InputSetPlayerHealth ), + DEFINE_INPUTFUNC( FIELD_VOID, "RequestAmmoState", InputRequestAmmoState ), + DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ), +END_DATADESC() + +void CLogicPlayerProxy::Activate( void ) +{ + BaseClass::Activate(); + + if ( m_hPlayer == NULL ) + { + m_hPlayer = AI_GetSinglePlayer(); + } +} + +void CLogicPlayerProxy::InputSetPlayerHealth( inputdata_t &inputdata ) +{ + if ( m_hPlayer == NULL ) + return; + + m_hPlayer->SetHealth( inputdata.value.Int() ); + +} + +void CLogicPlayerProxy::InputRequestPlayerHealth( inputdata_t &inputdata ) +{ + if ( m_hPlayer == NULL ) + return; + + m_RequestedPlayerHealth.Set( m_hPlayer->GetHealth(), inputdata.pActivator, inputdata.pCaller ); +} + +void CLogicPlayerProxy::InputSetFlashlightSlowDrain( inputdata_t &inputdata ) +{ + if( m_hPlayer == NULL ) + return; + + CHL2_Player *pPlayer = dynamic_cast(m_hPlayer.Get()); + + if( pPlayer ) + pPlayer->SetFlashlightPowerDrainScale( hl2_darkness_flashlight_factor.GetFloat() ); +} + +void CLogicPlayerProxy::InputSetFlashlightNormalDrain( inputdata_t &inputdata ) +{ + if( m_hPlayer == NULL ) + return; + + CHL2_Player *pPlayer = dynamic_cast(m_hPlayer.Get()); + + if( pPlayer ) + pPlayer->SetFlashlightPowerDrainScale( 1.0f ); +} + +void CLogicPlayerProxy::InputRequestAmmoState( inputdata_t &inputdata ) +{ + if( m_hPlayer == NULL ) + return; + + CHL2_Player *pPlayer = dynamic_cast(m_hPlayer.Get()); + + for ( int i = 0 ; i < pPlayer->WeaponCount(); ++i ) + { + CBaseCombatWeapon* pCheck = pPlayer->GetWeapon( i ); + + if ( pCheck ) + { + if ( pCheck->HasAnyAmmo() && (pCheck->UsesPrimaryAmmo() || pCheck->UsesSecondaryAmmo())) + { + m_PlayerHasAmmo.FireOutput( this, this, 0 ); + return; + } + } + } + + m_PlayerHasNoAmmo.FireOutput( this, this, 0 ); +} diff --git a/dlls/hl2_dll/hl2_triggers.cpp b/dlls/hl2_dll/hl2_triggers.cpp index 68ec4bf0..22a9e905 100644 --- a/dlls/hl2_dll/hl2_triggers.cpp +++ b/dlls/hl2_dll/hl2_triggers.cpp @@ -542,12 +542,12 @@ void CWateryDeathLeech::LeechThink( void ) dt = 0.1f; } m_nRenderMode = kRenderTransTexture; - int speed = max(1,256*dt); // fade out over 1 second + int speed = static_cast(max(1,256*dt)); // fade out over 1 second if ( m_iFadeState == -1 ) - SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) ); + SetRenderColorA( static_cast(UTIL_Approach( 0, m_clrRender->a, speed )) ); else - SetRenderColorA( UTIL_Approach( 255, m_clrRender->a, speed ) ); + SetRenderColorA( static_cast(UTIL_Approach( 255, m_clrRender->a, speed )) ); if ( m_clrRender->a == 0 ) { diff --git a/dlls/hl2_dll/item_ammo.cpp b/dlls/hl2_dll/item_ammo.cpp index 71fc934d..6c8f36de 100644 --- a/dlls/hl2_dll/item_ammo.cpp +++ b/dlls/hl2_dll/item_ammo.cpp @@ -32,7 +32,7 @@ int ITEM_GiveAmmo( CBasePlayer *pPlayer, float flCount, const char *pszAmmoName, // Don't give out less than 1 of anything. flCount = max( 1.0f, flCount ); - return pPlayer->GiveAmmo( flCount, iAmmoType, bSuppressSound ); + return pPlayer->GiveAmmo( static_cast(flCount), iAmmoType, bSuppressSound ); } // ======================================================================== diff --git a/dlls/hl2_dll/item_dynamic_resupply.cpp b/dlls/hl2_dll/item_dynamic_resupply.cpp index 5ace966b..969a2552 100644 --- a/dlls/hl2_dll/item_dynamic_resupply.cpp +++ b/dlls/hl2_dll/item_dynamic_resupply.cpp @@ -218,12 +218,12 @@ void CItem_DynamicResupply::Precache( void ) { // Precache all the items potentially spawned int i; - for ( i = 0; i < NUM_HEALTH_ITEMS; i++ ) + for ( i = 0; i < static_cast(NUM_HEALTH_ITEMS); i++ ) { UTIL_PrecacheOther( g_DynamicResupplyHealthItems[i].sEntityName ); } - for ( i = 0; i < NUM_AMMO_ITEMS; i++ ) + for ( i = 0; i < static_cast(NUM_AMMO_ITEMS); i++ ) { UTIL_PrecacheOther( g_DynamicResupplyAmmoItems[i].sEntityName ); } @@ -293,7 +293,7 @@ void CItem_DynamicResupply::SpawnFullItem( CItem_DynamicResupply *pMaster, CBase float flRatio[NUM_AMMO_ITEMS]; int i; float flTotalProb = 0.0f; - for ( i = 0; i < NUM_AMMO_ITEMS; ++i ) + for ( i = 0; i < static_cast(NUM_AMMO_ITEMS); ++i ) { int iAmmoType = GetAmmoDef()->Index( g_DynamicResupplyAmmoItems[i].sAmmoDef ); bool bCanSpawn = pPlayer->Weapon_GetWpnForAmmo( iAmmoType ) != NULL; @@ -329,7 +329,7 @@ void CItem_DynamicResupply::SpawnFullItem( CItem_DynamicResupply *pMaster, CBase } float flChoice = random->RandomFloat( 0.0f, flTotalProb ); - for ( i = 0; i < NUM_AMMO_ITEMS; ++i ) + for ( i = 0; i < static_cast(NUM_AMMO_ITEMS); ++i ) { if ( flChoice <= flRatio[i] ) { @@ -399,7 +399,7 @@ void CItem_DynamicResupply::FindPotentialItems( int nCount, DynamicResupplyItems //----------------------------------------------------------------------------- void CItem_DynamicResupply::ComputeHealthRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo ) { - for ( int i = 0; i < NUM_HEALTH_ITEMS; i++ ) + for ( int i = 0; i < static_cast(NUM_HEALTH_ITEMS); i++ ) { // Figure out the current level of this resupply type float flMax; @@ -435,7 +435,7 @@ void CItem_DynamicResupply::ComputeHealthRatios( CItem_DynamicResupply* pMaster, if ( iDebug ) { Msg("Calculating desired health ratios & deltas:\n"); - for ( int i = 0; i < NUM_HEALTH_ITEMS; i++ ) + for ( int i = 0; i < static_cast(NUM_HEALTH_ITEMS); i++ ) { Msg(" %s Desired Ratio: %.2f, Current Ratio: %.2f = Delta of %.2f\n", g_DynamicResupplyHealthItems[i].sEntityName, pSpawnInfo[i].m_flDesiredRatio, pSpawnInfo[i].m_flCurrentRatio, pSpawnInfo[i].m_flDelta ); @@ -449,7 +449,7 @@ void CItem_DynamicResupply::ComputeHealthRatios( CItem_DynamicResupply* pMaster, //----------------------------------------------------------------------------- void CItem_DynamicResupply::ComputeAmmoRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo ) { - for ( int i = 0; i < NUM_AMMO_ITEMS; i++ ) + for ( int i = 0; i < static_cast(NUM_AMMO_ITEMS); i++ ) { // Get the ammodef's int iAmmoType = GetAmmoDef()->Index( g_DynamicResupplyAmmoItems[i].sAmmoDef ); @@ -477,7 +477,7 @@ void CItem_DynamicResupply::ComputeAmmoRatios( CItem_DynamicResupply* pMaster, C if ( iDebug ) { Msg("Calculating desired ammo ratios & deltas:\n"); - for ( int i = 0; i < NUM_AMMO_ITEMS; i++ ) + for ( int i = 0; i < static_cast(NUM_AMMO_ITEMS); i++ ) { Msg(" %s Desired Ratio: %.2f, Current Ratio: %.2f = Delta of %.2f\n", g_DynamicResupplyAmmoItems[i].sEntityName, pSpawnInfo[i].m_flDesiredRatio, pSpawnInfo[i].m_flCurrentRatio, pSpawnInfo[i].m_flDelta ); diff --git a/dlls/hl2_dll/item_healthkit.cpp b/dlls/hl2_dll/item_healthkit.cpp index 041e81db..c8233b6c 100644 --- a/dlls/hl2_dll/item_healthkit.cpp +++ b/dlls/hl2_dll/item_healthkit.cpp @@ -243,7 +243,7 @@ void CWallHealth::Spawn(void) SetModel( STRING( GetModelName() ) ); - m_iJuice = sk_healthcharger.GetFloat(); + m_iJuice = sk_healthcharger.GetInt(); m_nState = 0; @@ -388,7 +388,7 @@ void CWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE u void CWallHealth::Recharge(void) { EmitSound( "WallHealth.Recharge" ); - m_iJuice = sk_healthcharger.GetFloat(); + m_iJuice = sk_healthcharger.GetInt(); m_nState = 0; SetThink( NULL ); } @@ -405,7 +405,7 @@ void CWallHealth::Off(void) m_iOn = 0; - if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHealthChargerRechargeTime() ) > 0) ) + if ((!m_iJuice) && ( ( m_iReactivate = static_cast(g_pGameRules->FlHealthChargerRechargeTime()) ) > 0) ) { SetNextThink( gpGlobals->curtime + m_iReactivate ); SetThink(&CWallHealth::Recharge); @@ -520,7 +520,7 @@ void CNewWallHealth::Spawn(void) ResetSequence( LookupSequence( "idle" ) ); - m_iJuice = sk_healthcharger.GetFloat(); + m_iJuice = sk_healthcharger.GetInt(); m_nState = 0; @@ -700,7 +700,7 @@ void CNewWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYP void CNewWallHealth::Recharge(void) { EmitSound( "WallHealth.Recharge" ); - m_flJuice = m_iJuice = sk_healthcharger.GetFloat(); + m_flJuice = m_iJuice = sk_healthcharger.GetInt(); m_nState = 0; ResetSequence( LookupSequence( "idle" ) ); @@ -733,7 +733,7 @@ void CNewWallHealth::Off(void) { if ((!m_iJuice) && g_pGameRules->FlHealthChargerRechargeTime() > 0 ) { - m_iReactivate = g_pGameRules->FlHealthChargerRechargeTime(); + m_iReactivate = static_cast(g_pGameRules->FlHealthChargerRechargeTime()); SetNextThink( gpGlobals->curtime + m_iReactivate ); SetThink(&CNewWallHealth::Recharge); } diff --git a/dlls/hl2_dll/npc_BaseZombie.cpp b/dlls/hl2_dll/npc_BaseZombie.cpp index 218e2314..b783cb44 100644 --- a/dlls/hl2_dll/npc_BaseZombie.cpp +++ b/dlls/hl2_dll/npc_BaseZombie.cpp @@ -890,6 +890,9 @@ int CNPC_BaseZombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) case RELEASE_SCHEDULED: SetCondition( COND_ZOMBIE_RELEASECRAB ); break; + + default: + break; } if( ShouldBecomeTorso( info, flDamageThreshold ) ) @@ -945,7 +948,7 @@ void CNPC_BaseZombie::MakeAISpookySound( float volume, float duration ) { if ( HL2GameRules()->IsAlyxInDarknessMode() ) { - CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), volume, duration, this, SOUNDENT_CHANNEL_SPOOKY_NOISE ); + CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), static_cast(volume), duration, this, SOUNDENT_CHANNEL_SPOOKY_NOISE ); } } @@ -1010,7 +1013,7 @@ void CNPC_BaseZombie::MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize ) float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pMoanSound, SOUNDCTRL_CHANGE_VOLUME, pEnvelope, iEnvelopeSize ); - float flPitch = random->RandomInt( m_flMoanPitch + zombie_changemin.GetInt(), m_flMoanPitch + zombie_changemax.GetInt() ); + float flPitch = random->RandomFloat( m_flMoanPitch + zombie_changemin.GetFloat(), m_flMoanPitch + zombie_changemax.GetFloat() ); ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, flPitch, 0.3 ); m_flNextMoanSound = gpGlobals->curtime + duration + 9999; @@ -1221,7 +1224,7 @@ void CNPC_BaseZombie::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize #endif // HL2_EPISODIC // Set the zombie up to burn to death in about ten seconds. - SetHealth( min( m_iHealth, FLAME_DIRECT_DAMAGE_PER_SEC * (ZOMBIE_BURN_TIME + random->RandomFloat( -ZOMBIE_BURN_TIME_NOISE, ZOMBIE_BURN_TIME_NOISE)) ) ); + SetHealth( static_cast(min( m_iHealth, FLAME_DIRECT_DAMAGE_PER_SEC * (ZOMBIE_BURN_TIME + random->RandomFloat( -ZOMBIE_BURN_TIME_NOISE, ZOMBIE_BURN_TIME_NOISE))) ) ); // FIXME: use overlays when they come online //AddOverlay( ACT_ZOM_WALK_ON_FIRE, false ); @@ -1537,7 +1540,7 @@ void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent ) right = right * 100; forward = forward * 200; - ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), QAngle( -15, -20, -10 ), right + forward, ZOMBIE_BLOOD_RIGHT_HAND ); + ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetInt(), QAngle( -15, -20, -10 ), right + forward, ZOMBIE_BLOOD_RIGHT_HAND ); return; } @@ -1549,7 +1552,7 @@ void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent ) right = right * -100; forward = forward * 200; - ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), QAngle( -15, 20, -10 ), right + forward, ZOMBIE_BLOOD_LEFT_HAND ); + ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetInt(), QAngle( -15, 20, -10 ), right + forward, ZOMBIE_BLOOD_LEFT_HAND ); return; } @@ -1559,7 +1562,7 @@ void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent ) QAngle qaPunch( 45, random->RandomInt(-5,5), random->RandomInt(-5,5) ); AngleVectors( GetLocalAngles(), &forward ); forward = forward * 200; - ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS ); + ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetInt(), qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS ); return; } @@ -1860,13 +1863,15 @@ int CNPC_BaseZombie::SelectSchedule ( void ) #ifdef DEBUG_ZOMBIES DevMsg("Wandering\n"); -#endif+ +#endif // Just lost track of our enemy. // Wander around a bit so we don't look like a dingus. return SCHED_ZOMBIE_WANDER_MEDIUM; } break; + default: + break; } return BaseClass::SelectSchedule(); @@ -2567,6 +2572,9 @@ Activity CNPC_BaseZombie::NPC_TranslateActivity( Activity baseAct ) // I'm on fire. Put ME out. return ( Activity )ACT_IDLE_ON_FIRE; } + + default: + break; } } diff --git a/dlls/hl2_dll/npc_BaseZombie.h b/dlls/hl2_dll/npc_BaseZombie.h index dcd75a19..be961047 100644 --- a/dlls/hl2_dll/npc_BaseZombie.h +++ b/dlls/hl2_dll/npc_BaseZombie.h @@ -185,7 +185,7 @@ public: virtual void SetZombieModel( void ) { }; virtual void BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce ); virtual bool CanBecomeLiveTorso() { return false; } - virtual bool CNPC_BaseZombie::HeadcrabFits( CBaseAnimating *pCrab ); + virtual bool HeadcrabFits( CBaseAnimating *pCrab ); void ReleaseHeadcrab( const Vector &vecOrigin, const Vector &vecVelocity, bool fRemoveHead, bool fRagdollBody, bool fRagdollCrab = false ); void SetHeadcrabSpawnLocation( int iCrabAttachment, CBaseAnimating *pCrab ); diff --git a/dlls/hl2_dll/npc_PoisonZombie.cpp b/dlls/hl2_dll/npc_PoisonZombie.cpp index 20d69f07..ba1bf00a 100644 --- a/dlls/hl2_dll/npc_PoisonZombie.cpp +++ b/dlls/hl2_dll/npc_PoisonZombie.cpp @@ -712,7 +712,7 @@ void CNPC_PoisonZombie::HandleAnimEvent( animevent_t *pEvent ) QAngle qaPunch( 45, random->RandomInt(-5, 5), random->RandomInt(-5, 5) ); AngleVectors( GetLocalAngles(), &forward ); forward = forward * 200; - ClawAttack( GetClawAttackRange(), sk_zombie_poison_dmg_spit.GetFloat(), qaPunch, forward, ZOMBIE_BLOOD_BITE ); + ClawAttack( GetClawAttackRange(), sk_zombie_poison_dmg_spit.GetInt(), qaPunch, forward, ZOMBIE_BLOOD_BITE ); return; } diff --git a/dlls/hl2_dll/npc_alyx_episodic.cpp b/dlls/hl2_dll/npc_alyx_episodic.cpp index 1a9e7837..2463490b 100644 --- a/dlls/hl2_dll/npc_alyx_episodic.cpp +++ b/dlls/hl2_dll/npc_alyx_episodic.cpp @@ -158,6 +158,8 @@ bool CNPC_Alyx::FValidateHintType( CAI_Hint *pHint ) case HINT_WORLD_VISUALLY_INTERESTING_STEALTH: return true; break; + default: + break; } return BaseClass::FValidateHintType( pHint ); @@ -485,7 +487,7 @@ void CNPC_Alyx::ReadinessLevelChanged( int iPriorLevel ) { AIRL_STIMULATED, AIRL_RELAXED, ACT_IDLE, ACT_READINESS_STIMULATED_TO_RELAXED, } }; - for ( int i = 0; i < ARRAYSIZE( readinessTransitions ); i++ ) + for ( int i = 0; i < static_cast(ARRAYSIZE( readinessTransitions )); i++ ) { if ( GetIdealActivity() != readinessTransitions[i].requiredActivity ) continue; @@ -1678,6 +1680,9 @@ void CNPC_Alyx::BuildScheduleTestBits() SetCustomInterruptCondition( COND_ALYX_HAS_INTERACT_TARGET ); SetCustomInterruptCondition( COND_ALYX_CAN_INTERACT_WITH_TARGET ); break; + + default: + break; } } @@ -3175,6 +3180,9 @@ bool CNPC_Alyx::IsCrouchedActivity( Activity activity ) case ACT_RANGE_AIM_PISTOL_LOW: case ACT_RANGE_AIM_AR2_LOW: return true; + + default: + break; } return false; } diff --git a/dlls/hl2_dll/npc_antlion.cpp b/dlls/hl2_dll/npc_antlion.cpp index 8481efcf..b4a21213 100644 --- a/dlls/hl2_dll/npc_antlion.cpp +++ b/dlls/hl2_dll/npc_antlion.cpp @@ -498,7 +498,7 @@ void CNPC_Antlion::MeleeAttack( float distance, float damage, QAngle &viewPunch, return; } - CBaseEntity *pHurt = CheckTraceHullAttack( distance, -Vector(16,16,32), Vector(16,16,32), damage, DMG_SLASH, 5.0f ); + CBaseEntity *pHurt = CheckTraceHullAttack( distance, -Vector(16,16,32), Vector(16,16,32), static_cast(damage), DMG_SLASH, 5.0f ); if ( pHurt ) { @@ -1949,7 +1949,7 @@ void CNPC_Antlion::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDi if ( newInfo.GetDamageType() & (DMG_CRUSH|DMG_PHYSGUN) ) { PainSound( newInfo ); - ApplyAbsVelocityImpulse( ( vecShoveDir * random->RandomInt( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f) ); + ApplyAbsVelocityImpulse( ( vecShoveDir * random->RandomFloat( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f) ); SetGroundEntity( NULL ); } @@ -1974,7 +1974,7 @@ void CNPC_Antlion::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDi SetCondition( COND_ANTLION_FLIPPED ); } - ApplyAbsVelocityImpulse( ( vecShoveDir * random->RandomInt( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f) ); + ApplyAbsVelocityImpulse( ( vecShoveDir * random->RandomFloat( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f) ); SetGroundEntity( NULL ); } else @@ -1987,7 +1987,7 @@ void CNPC_Antlion::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDi SetCondition( COND_ANTLION_FLIPPED ); //Get tossed! - ApplyAbsVelocityImpulse( ( vecShoveDir * random->RandomInt( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f) ); + ApplyAbsVelocityImpulse( ( vecShoveDir * random->RandomFloat( 500.0f, 1000.0f ) ) + Vector(0,0,64.0f) ); SetGroundEntity( NULL ); } } diff --git a/dlls/hl2_dll/npc_antlionguard.cpp b/dlls/hl2_dll/npc_antlionguard.cpp index f6269c87..43166406 100644 --- a/dlls/hl2_dll/npc_antlionguard.cpp +++ b/dlls/hl2_dll/npc_antlionguard.cpp @@ -2561,7 +2561,7 @@ void CNPC_AntlionGuard::SummonAntlions( void ) // Only spawn up to our max count int iSpawnPoint = 0; - for ( int i = 0; (m_iNumLiveAntlions < ANTLIONGUARD_SUMMON_COUNT) && (iSpawnPoint < ARRAYSIZE(sAntlionSpawnPositions)); i++ ) + for ( int i = 0; (m_iNumLiveAntlions < ANTLIONGUARD_SUMMON_COUNT) && (iSpawnPoint < static_cast(ARRAYSIZE(sAntlionSpawnPositions))); i++ ) { // Determine spawn position for the antlion Vector vecSpawn = GetAbsOrigin() + ( sAntlionSpawnPositions[iSpawnPoint].flForward * vecForward ) + ( sAntlionSpawnPositions[iSpawnPoint].flRight * vecRight ); diff --git a/dlls/hl2_dll/npc_attackchopper.cpp b/dlls/hl2_dll/npc_attackchopper.cpp index 1abf2986..d6fcb8ac 100644 --- a/dlls/hl2_dll/npc_attackchopper.cpp +++ b/dlls/hl2_dll/npc_attackchopper.cpp @@ -1429,7 +1429,7 @@ void CNPC_AttackHelicopter::InputSetHealthFraction( inputdata_t &inputdata ) // Sets the health fraction, no damage effects if ( inputdata.value.Float() > 0 ) { - SetHealth( GetMaxHealth() * inputdata.value.Float() * 0.01f ); + SetHealth( static_cast(GetMaxHealth() * inputdata.value.Float() * 0.01f) ); } } @@ -2131,7 +2131,7 @@ bool CNPC_AttackHelicopter::DoGunCharging( ) case SHOOT_MODE_DEFAULT: { int nBurstCount = sk_helicopter_burstcount.GetInt(); - m_nRemainingBursts = random->RandomInt( nBurstCount, 2.0 * nBurstCount ); + m_nRemainingBursts = random->RandomInt( nBurstCount, 2 * nBurstCount ); m_flIdleTimeDelay = 0.1f * ( m_nRemainingBursts - nBurstCount ); } break; @@ -3262,7 +3262,7 @@ int CNPC_AttackHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { if ( nPrevHealth != GetMaxHealth() ) { - DropCorpse( info.GetDamage() ); + DropCorpse( static_cast(info.GetDamage()) ); } } @@ -4703,8 +4703,8 @@ int CGrenadeHelicopter::OnTakeDamage( const CTakeDamageInfo &info ) //------------------------------------------------------------------------------ void CGrenadeHelicopter::DoExplosion( const Vector &vecOrigin, const Vector &vecVelocity ) { - ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), this, sk_helicopter_grenadedamage.GetFloat(), - sk_helicopter_grenaderadius.GetFloat(), (SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODECAL|SF_ENVEXPLOSION_NOFIREBALL|SF_ENVEXPLOSION_NOPARTICLES), + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), this, sk_helicopter_grenadedamage.GetInt(), + sk_helicopter_grenaderadius.GetInt(), (SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODECAL|SF_ENVEXPLOSION_NOFIREBALL|SF_ENVEXPLOSION_NOPARTICLES), sk_helicopter_grenadeforce.GetFloat() ); if ( GetShakeAmplitude() ) diff --git a/dlls/hl2_dll/npc_barnacle.cpp b/dlls/hl2_dll/npc_barnacle.cpp index d81d5705..86bbb5a5 100644 --- a/dlls/hl2_dll/npc_barnacle.cpp +++ b/dlls/hl2_dll/npc_barnacle.cpp @@ -360,7 +360,7 @@ void CNPC_Barnacle::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot ) // Create a sound to scare friendly allies away from the base on the barnacle if( IsAlive() ) { - CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_ALLIES_ONLY, m_vecTip, 60.0f, FLASHLIGHT_NPC_CHECK_INTERVAL ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_ALLIES_ONLY, m_vecTip, 60, FLASHLIGHT_NPC_CHECK_INTERVAL ); } } @@ -1733,7 +1733,7 @@ void CNPC_Barnacle::SpawnDeathGibs( void ) bool bDroppedAny = false; // Drop a random number of gibs - for ( int i=0; i < ARRAYSIZE(m_szGibNames); i++ ) + for ( int i=0; i < static_cast(ARRAYSIZE(m_szGibNames)); i++ ) { if ( random->RandomInt( 0, 1 ) ) { @@ -1941,7 +1941,7 @@ void CNPC_Barnacle::Precache() PrecacheModel("models/barnacle.mdl"); // Precache all gibs - for ( int i=0; i < ARRAYSIZE(m_szGibNames); i++ ) + for ( int i=0; i < static_cast(ARRAYSIZE(m_szGibNames)); i++ ) { PrecacheModel( m_szGibNames[i] ); } diff --git a/dlls/hl2_dll/npc_breen.cpp b/dlls/hl2_dll/npc_breen.cpp index 738d2c82..df4daa55 100644 --- a/dlls/hl2_dll/npc_breen.cpp +++ b/dlls/hl2_dll/npc_breen.cpp @@ -69,7 +69,7 @@ void CNPC_Breen::HandleAnimEvent( animevent_t *pEvent ) //----------------------------------------------------------------------------- int CNPC_Breen::GetSoundInterests ( void ) { - return NULL; + return 0; } //----------------------------------------------------------------------------- diff --git a/dlls/hl2_dll/npc_citizen17.cpp b/dlls/hl2_dll/npc_citizen17.cpp index 524530f9..ff69775d 100644 --- a/dlls/hl2_dll/npc_citizen17.cpp +++ b/dlls/hl2_dll/npc_citizen17.cpp @@ -116,23 +116,23 @@ struct citizen_expression_list_t // Scared citizen_expression_list_t ScaredExpressions[STATES_WITH_EXPRESSIONS] = { - { "scenes/Expressions/citizen_scared_idle_01.vcd" }, - { "scenes/Expressions/citizen_scared_alert_01.vcd" }, - { "scenes/Expressions/citizen_scared_combat_01.vcd" }, + {{ "scenes/Expressions/citizen_scared_idle_01.vcd" }}, + {{ "scenes/Expressions/citizen_scared_alert_01.vcd" }}, + {{ "scenes/Expressions/citizen_scared_combat_01.vcd" }}, }; // Normal citizen_expression_list_t NormalExpressions[STATES_WITH_EXPRESSIONS] = { - { "scenes/Expressions/citizen_normal_idle_01.vcd" }, - { "scenes/Expressions/citizen_normal_alert_01.vcd" }, - { "scenes/Expressions/citizen_normal_combat_01.vcd" }, + {{ "scenes/Expressions/citizen_normal_idle_01.vcd" }}, + {{ "scenes/Expressions/citizen_normal_alert_01.vcd" }}, + {{ "scenes/Expressions/citizen_normal_combat_01.vcd" }}, }; // Angry citizen_expression_list_t AngryExpressions[STATES_WITH_EXPRESSIONS] = { - { "scenes/Expressions/citizen_angry_idle_01.vcd" }, - { "scenes/Expressions/citizen_angry_alert_01.vcd" }, - { "scenes/Expressions/citizen_angry_combat_01.vcd" }, + {{ "scenes/Expressions/citizen_angry_idle_01.vcd" }}, + {{ "scenes/Expressions/citizen_angry_alert_01.vcd" }}, + {{ "scenes/Expressions/citizen_angry_combat_01.vcd" }}, }; //----------------------------------------------------------------------------- @@ -374,15 +374,15 @@ void CNPC_Citizen::Precache() for ( int i = 0; i < STATES_WITH_EXPRESSIONS; i++ ) { - for ( int j = 0; j < ARRAYSIZE(ScaredExpressions[i].szExpressions); j++ ) + for ( int j = 0; j < static_cast(ARRAYSIZE(ScaredExpressions[i].szExpressions)); j++ ) { PrecacheInstancedScene( ScaredExpressions[i].szExpressions[j] ); } - for ( int j = 0; j < ARRAYSIZE(NormalExpressions[i].szExpressions); j++ ) + for ( int j = 0; j < static_cast(ARRAYSIZE(NormalExpressions[i].szExpressions)); j++ ) { PrecacheInstancedScene( NormalExpressions[i].szExpressions[j] ); } - for ( int j = 0; j < ARRAYSIZE(AngryExpressions[i].szExpressions); j++ ) + for ( int j = 0; j < static_cast(ARRAYSIZE(AngryExpressions[i].szExpressions)); j++ ) { PrecacheInstancedScene( AngryExpressions[i].szExpressions[j] ); } @@ -579,7 +579,7 @@ void CNPC_Citizen::SelectModel() Q_strncpy(szMapName, STRING(gpGlobals->mapname), sizeof(szMapName) ); Q_strlower(szMapName); - for ( int i = 0; i < ARRAYSIZE(CitizenTypeMappings); i++ ) + for ( int i = 0; i < static_cast(ARRAYSIZE(CitizenTypeMappings)); i++ ) { if ( Q_stristr( szMapName, CitizenTypeMappings[i].pszMapTag ) ) { @@ -613,7 +613,7 @@ void CNPC_Citizen::SelectModel() for ( i = 0; i < g_AI_Manager.NumAIs(); i++ ) { CNPC_Citizen *pCitizen = dynamic_cast(g_AI_Manager.AccessAIs()[i]); - if ( pCitizen && pCitizen != this && pCitizen->m_iHead >= 0 && pCitizen->m_iHead < ARRAYSIZE(g_ppszRandomHeads) ) + if ( pCitizen && pCitizen != this && pCitizen->m_iHead >= 0 && pCitizen->m_iHead < static_cast(ARRAYSIZE(g_ppszRandomHeads)) ) { headCounts[pCitizen->m_iHead]++; } @@ -622,7 +622,7 @@ void CNPC_Citizen::SelectModel() // Find all candidates CUtlVectorFixed candidates; - for ( i = 0; i < ARRAYSIZE(g_ppszRandomHeads); i++ ) + for ( i = 0; i < static_cast(ARRAYSIZE(g_ppszRandomHeads)); i++ ) { if ( !gender || g_ppszRandomHeads[i][0] == gender ) { @@ -666,7 +666,7 @@ void CNPC_Citizen::SelectModel() pszModelName++; if ( m_iHead == -1 ) { - for ( int i = 0; i < ARRAYSIZE(g_ppszRandomHeads); i++ ) + for ( int i = 0; i < static_cast(ARRAYSIZE(g_ppszRandomHeads)); i++ ) { if ( Q_stricmp( g_ppszRandomHeads[i], pszModelName ) == 0 ) { @@ -991,8 +991,8 @@ void CNPC_Citizen::PrescheduleThink() float b = bMin + ( bMax - bMin ) * fade; // THIS IS A PLACEHOLDER UNTIL WE HAVE A REAL DESIGN & ART -- DO NOT REMOVE - NDebugOverlay::Line( Vector( mins.x, GetAbsOrigin().y, GetAbsOrigin().z+1 ), Vector( maxs.x, GetAbsOrigin().y, GetAbsOrigin().z+1 ), r, g, b, false, .11 ); - NDebugOverlay::Line( Vector( GetAbsOrigin().x, mins.y, GetAbsOrigin().z+1 ), Vector( GetAbsOrigin().x, maxs.y, GetAbsOrigin().z+1 ), r, g, b, false, .11 ); + NDebugOverlay::Line( Vector( mins.x, GetAbsOrigin().y, GetAbsOrigin().z+1 ), Vector( maxs.x, GetAbsOrigin().y, GetAbsOrigin().z+1 ), static_cast(r), static_cast(g), static_cast(b), false, .11 ); + NDebugOverlay::Line( Vector( GetAbsOrigin().x, mins.y, GetAbsOrigin().z+1 ), Vector( GetAbsOrigin().x, maxs.y, GetAbsOrigin().z+1 ), static_cast(r), static_cast(g), static_cast(b), false, .11 ); } if( GetEnemy() && g_ai_citizen_show_enemy.GetBool() ) { @@ -1708,7 +1708,7 @@ void CNPC_Citizen::HandleAnimEvent( animevent_t *pEvent ) // If I have a name, make my weapon match it with "_weapon" appended if ( GetEntityName() != NULL_STRING ) { - pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", GetEntityName())) ); + pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", STRING(GetEntityName()))) ); } Weapon_Equip( pWeapon ); } @@ -1943,7 +1943,7 @@ Vector CNPC_Citizen::GetActualShootPosition( const Vector &shootOrigin ) 128, -128 }; - for ( int i = 0; i < ARRAYSIZE(flShotOffsets); i++ ) + for ( int i = 0; i < static_cast(ARRAYSIZE(flShotOffsets)); i++ ) { Vector vecTest = vecTarget + (vecRight * flShotOffsets[i]); // Add some random height to it @@ -3044,7 +3044,7 @@ int __cdecl SquadSortFunc( const SquadMemberInfo_t *pLeft, const SquadMemberInfo return 1; } - return ( pLeft->distSq - pRight->distSq ); + return static_cast(pLeft->distSq - pRight->distSq); } CAI_BaseNPC *CNPC_Citizen::GetSquadCommandRepresentative() @@ -3281,9 +3281,9 @@ bool CNPC_Citizen::ShouldHealTarget( CBaseEntity *pTarget, bool bActiveUse ) int requiredHealth; if ( bTargetIsPlayer ) - requiredHealth = pTarget->GetMaxHealth() - sk_citizen_heal_player.GetFloat(); + requiredHealth = pTarget->GetMaxHealth() - sk_citizen_heal_player.GetInt(); else - requiredHealth = pTarget->GetMaxHealth() * sk_citizen_heal_player_min_pct.GetFloat(); + requiredHealth = pTarget->GetMaxHealth() * sk_citizen_heal_player_min_pct.GetInt(); if ( ( pTarget->m_iHealth <= requiredHealth ) && IRelationType( pTarget ) == D_LI ) return true; diff --git a/dlls/hl2_dll/npc_combine_episodic.cpp b/dlls/hl2_dll/npc_combine_episodic.cpp index beebc664..9c3248e0 100644 --- a/dlls/hl2_dll/npc_combine_episodic.cpp +++ b/dlls/hl2_dll/npc_combine_episodic.cpp @@ -991,7 +991,7 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) info.SetAttacker( this ); info.SetInflictor( this ); info.SetDamage( m_iHealth ); - info.SetDamageType( pTask->flTaskData ); + info.SetDamageType( static_cast(pTask->flTaskData) ); info.SetDamageForce( Vector( 0.1, 0.1, 0.1 ) ); TakeDamage( info ); @@ -1319,6 +1319,9 @@ Activity CNPC_Combine::NPC_TranslateActivity( Activity eNewActivity ) case ACT_RUN: eNewActivity = ACT_RUN_AIM; break; + + default: + break; } } @@ -1419,6 +1422,9 @@ void CNPC_Combine::AnnounceEnemyType( CBaseEntity *pEnemy ) case CLASS_BARNACLE: pSentenceName = "COMBINE_MONST_PARASITES"; break; + + default: + break; } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); @@ -1455,6 +1461,9 @@ void CNPC_Combine::AnnounceEnemyKill( CBaseEntity *pEnemy ) case CLASS_HEADCRAB: case CLASS_BARNACLE: break; + + default: + break; } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); @@ -1820,6 +1829,9 @@ int CNPC_Combine::SelectSchedule( void ) return nSched; } break; + + default: + break; } // no special cases here, call the base class diff --git a/dlls/hl2_dll/npc_combinedropship.cpp b/dlls/hl2_dll/npc_combinedropship.cpp index 775da5c3..eade4081 100644 --- a/dlls/hl2_dll/npc_combinedropship.cpp +++ b/dlls/hl2_dll/npc_combinedropship.cpp @@ -612,7 +612,7 @@ int CCombineDropshipContainer::OnTakeDamage( const CTakeDamageInfo &info ) if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) ) { - ExplosionCreate( dmgInfo.GetDamagePosition(), vec3_angle, this, 1000, 500.0f, + ExplosionCreate( dmgInfo.GetDamagePosition(), vec3_angle, this, 1000, 500, SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0 ); UTIL_ScreenShake( dmgInfo.GetDamagePosition(), 25.0, 150.0, 1.0, 750.0f, SHAKE_START ); @@ -630,7 +630,7 @@ int CCombineDropshipContainer::OnTakeDamage( const CTakeDamageInfo &info ) void CCombineDropshipContainer::AddSmokeTrail( const Vector &vecPos ) { // Start this trail out with a bang! - ExplosionCreate( vecPos, vec3_angle, this, 1000, 500.0f, SF_ENVEXPLOSION_NODAMAGE | + ExplosionCreate( vecPos, vec3_angle, this, 1000, 500, SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0 ); UTIL_ScreenShake( vecPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START ); @@ -2220,6 +2220,9 @@ void CNPC_CombineDropship::PrescheduleThink( void ) DoRotorWash(); } break; + + default: + break; } if ( !(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) ) @@ -2289,7 +2292,7 @@ void CNPC_CombineDropship::SpawnTroop( void ) QAngle vecDeployEndAngles; m_hContainer->GetAttachment( m_iAttachmentTroopDeploy, vecDeployEndPoint, vecDeployEndAngles ); vecDeployEndPoint = GetDropoffFinishPosition( vecDeployEndPoint, NULL, vecNPCMins, vecNPCMaxs ); - CSoundEnt::InsertSound( SOUND_DANGER, vecDeployEndPoint, 120.0f, 2.0f, this ); + CSoundEnt::InsertSound( SOUND_DANGER, vecDeployEndPoint, 120, 2.0f, this ); // Make sure there are no NPCs on the spot trace_t tr; @@ -2794,7 +2797,7 @@ bool CNPC_CombineDropship::FireCannonRound( void ) //------------------------------------------------------------------------------ void CNPC_CombineDropship::DoImpactEffect( trace_t &tr, int nDamageType ) { - CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, tr.endpos, 120.0f, 0.3f, this ); + CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, tr.endpos, 120, 0.3f, this ); BaseClass::DoImpactEffect( tr, nDamageType ); } diff --git a/dlls/hl2_dll/npc_combinegunship.cpp b/dlls/hl2_dll/npc_combinegunship.cpp index 3f21032b..8f332060 100644 --- a/dlls/hl2_dll/npc_combinegunship.cpp +++ b/dlls/hl2_dll/npc_combinegunship.cpp @@ -957,7 +957,7 @@ void CNPC_CombineGunship::ManageWarningBeam( void ) g_pEffects->EnergySplash( tr2.endpos + vDir * 8, tr2.plane.normal, true ); } - g_pEffects->Sparks( tr2.endpos, 3.0f - (m_flGroundAttackTime-gpGlobals->curtime), 3.5f - (m_flGroundAttackTime-gpGlobals->curtime), &tr2.plane.normal ); + g_pEffects->Sparks( tr2.endpos, static_cast(3.0f - (m_flGroundAttackTime-gpGlobals->curtime)), static_cast(3.5f - (m_flGroundAttackTime-gpGlobals->curtime)), &tr2.plane.normal ); } } @@ -1958,7 +1958,7 @@ void CNPC_CombineGunship::BeginDestruct( void ) return; } - m_hRagdoll->SetName( AllocPooledString( UTIL_VarArgs("%s_ragdoll", GetEntityName()) ) ); + m_hRagdoll->SetName( AllocPooledString( UTIL_VarArgs("%s_ragdoll", STRING(GetEntityName())) ) ); // Tell the smoke trail to follow the ragdoll CreateSmokeTrail(); @@ -2899,16 +2899,16 @@ int CNPC_CombineGunship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) int iHealthIncrements = sk_gunship_health_increments.GetInt(); if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) { - iHealthIncrements = ceil( iHealthIncrements * 0.5 ); + iHealthIncrements = static_cast(ceil( iHealthIncrements * 0.5 )); } else if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) ) { - iHealthIncrements = floor( iHealthIncrements * 1.5 ); + iHealthIncrements = static_cast(floor( iHealthIncrements * 1.5 )); } info.SetDamage( ( GetMaxHealth() / (float)iHealthIncrements ) + 1 ); // Find out which "stage" we're at in our health - int healthIncrement = iHealthIncrements - ( GetHealth() / (float)(( GetMaxHealth() / (float)iHealthIncrements )) ); + int healthIncrement = iHealthIncrements - ( GetHealth() / ( GetMaxHealth() / iHealthIncrements ) ); switch ( healthIncrement ) { case 1: diff --git a/dlls/hl2_dll/npc_combines.cpp b/dlls/hl2_dll/npc_combines.cpp index d50e3e0e..1cbde8e0 100644 --- a/dlls/hl2_dll/npc_combines.cpp +++ b/dlls/hl2_dll/npc_combines.cpp @@ -58,15 +58,15 @@ void CNPC_CombineS::Spawn( void ) if( IsElite() ) { // Stronger, tougher. - SetHealth( sk_combine_guard_health.GetFloat() ); - SetMaxHealth( sk_combine_guard_health.GetFloat() ); - SetKickDamage( sk_combine_guard_kick.GetFloat() ); + SetHealth( sk_combine_guard_health.GetInt() ); + SetMaxHealth( sk_combine_guard_health.GetInt() ); + SetKickDamage( sk_combine_guard_kick.GetInt() ); } else { - SetHealth( sk_combine_s_health.GetFloat() ); - SetMaxHealth( sk_combine_s_health.GetFloat() ); - SetKickDamage( sk_combine_s_kick.GetFloat() ); + SetHealth( sk_combine_s_health.GetInt() ); + SetMaxHealth( sk_combine_s_health.GetInt() ); + SetKickDamage( sk_combine_s_kick.GetInt() ); } CapabilitiesAdd( bits_CAP_ANIMATEDFACE ); @@ -356,7 +356,7 @@ bool CNPC_CombineS::IsHeavyDamage( const CTakeDamageInfo &info ) // Shotgun blasts where at least half the pellets hit me are heavy damage if ( info.GetDamageType() & DMG_BUCKSHOT ) { - int iHalfMax = sk_plr_dmg_buckshot.GetFloat() * sk_plr_num_shotgun_pellets.GetInt() * 0.5; + int iHalfMax = static_cast(sk_plr_dmg_buckshot.GetInt() * sk_plr_num_shotgun_pellets.GetInt() * 0.5); if ( info.GetDamage() >= iHalfMax ) return true; } diff --git a/dlls/hl2_dll/npc_cranedriver.cpp b/dlls/hl2_dll/npc_cranedriver.cpp index 24059c89..a76826e0 100644 --- a/dlls/hl2_dll/npc_cranedriver.cpp +++ b/dlls/hl2_dll/npc_cranedriver.cpp @@ -259,6 +259,9 @@ int CNPC_CraneDriver::SelectSchedule( void ) // We can't attack him, so if we don't have anything on the crane, grab something if ( m_hCrane->GetTotalMassOnCrane() == 0 ) return SCHED_CRANE_FIND_LARGE_OBJECT; + + default: + break; } return BaseClass::SelectSchedule(); diff --git a/dlls/hl2_dll/npc_crow.cpp b/dlls/hl2_dll/npc_crow.cpp index 7893a3be..337ec115 100644 --- a/dlls/hl2_dll/npc_crow.cpp +++ b/dlls/hl2_dll/npc_crow.cpp @@ -1223,6 +1223,9 @@ int CNPC_Crow::SelectSchedule( void ) // TODO: need idle flying behaviors! } + + default: + break; } return BaseClass::SelectSchedule(); diff --git a/dlls/hl2_dll/npc_eli.cpp b/dlls/hl2_dll/npc_eli.cpp index edef31f2..c1cf81c2 100644 --- a/dlls/hl2_dll/npc_eli.cpp +++ b/dlls/hl2_dll/npc_eli.cpp @@ -72,7 +72,7 @@ void CNPC_Eli::HandleAnimEvent( animevent_t *pEvent ) //----------------------------------------------------------------------------- int CNPC_Eli::GetSoundInterests ( void ) { - return NULL; + return 0; } //----------------------------------------------------------------------------- diff --git a/dlls/hl2_dll/npc_fastzombie.cpp b/dlls/hl2_dll/npc_fastzombie.cpp index d4ca9125..f468ec06 100644 --- a/dlls/hl2_dll/npc_fastzombie.cpp +++ b/dlls/hl2_dll/npc_fastzombie.cpp @@ -500,6 +500,9 @@ int CFastZombie::SelectSchedule ( void ) return SCHED_ZOMBIE_WANDER_MEDIUM; } break; + + default: + break; } return BaseClass::SelectSchedule(); @@ -553,7 +556,7 @@ void CFastZombie::PrescheduleThink( void ) int iPitch; m_flDistFactor = min( 1.0, 1 - flDistNoBBox / FASTZOMBIE_EXCITE_DIST ); - iPitch = FASTZOMBIE_MIN_PITCH + ( ( FASTZOMBIE_MAX_PITCH - FASTZOMBIE_MIN_PITCH ) * m_flDistFactor); + iPitch = static_cast(FASTZOMBIE_MIN_PITCH + ( ( FASTZOMBIE_MAX_PITCH - FASTZOMBIE_MIN_PITCH ) * m_flDistFactor)); ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, iPitch, FASTZOMBIE_SOUND_UPDATE_FREQ ); } diff --git a/dlls/hl2_dll/npc_gman.cpp b/dlls/hl2_dll/npc_gman.cpp index 75b64354..36f21dad 100644 --- a/dlls/hl2_dll/npc_gman.cpp +++ b/dlls/hl2_dll/npc_gman.cpp @@ -84,7 +84,7 @@ void CNPC_GMan::HandleAnimEvent( animevent_t *pEvent ) //----------------------------------------------------------------------------- int CNPC_GMan::GetSoundInterests ( void ) { - return NULL; + return 0; } diff --git a/dlls/hl2_dll/npc_headcrab.cpp b/dlls/hl2_dll/npc_headcrab.cpp index 31c65ea8..d2d15c6c 100644 --- a/dlls/hl2_dll/npc_headcrab.cpp +++ b/dlls/hl2_dll/npc_headcrab.cpp @@ -971,7 +971,7 @@ int CBaseHeadcrab::CalcDamageInfo( CTakeDamageInfo *pInfo ) { pInfo->Set( this, this, sk_headcrab_melee_dmg.GetFloat(), DMG_SLASH ); CalculateMeleeDamageForce( pInfo, GetAbsVelocity(), GetAbsOrigin() ); - return pInfo->GetDamage(); + return static_cast(pInfo->GetDamage()); } //----------------------------------------------------------------------------- @@ -1842,7 +1842,7 @@ int CBaseHeadcrab::SelectSchedule( void ) return SCHED_HEADCRAB_UNHIDE; } - return m_bBurrowed ? SCHED_HEADCRAB_BURROW_WAIT : SCHED_IDLE_STAND; + return m_bBurrowed ? static_cast(SCHED_HEADCRAB_BURROW_WAIT) : static_cast(SCHED_IDLE_STAND); } if ( GetSpawnFlags() & SF_HEADCRAB_START_HANGING && IsHangingFromCeiling() == false ) @@ -1916,6 +1916,9 @@ int CBaseHeadcrab::SelectSchedule( void ) } break; } + + default: + break; } if ( HasCondition( COND_FLOATING_OFF_GROUND ) ) diff --git a/dlls/hl2_dll/npc_kleiner.cpp b/dlls/hl2_dll/npc_kleiner.cpp index db790d54..3b3cbfcc 100644 --- a/dlls/hl2_dll/npc_kleiner.cpp +++ b/dlls/hl2_dll/npc_kleiner.cpp @@ -70,7 +70,7 @@ void CNPC_Kleiner::HandleAnimEvent( animevent_t *pEvent ) //----------------------------------------------------------------------------- int CNPC_Kleiner::GetSoundInterests ( void ) { - return NULL; + return 0; } //----------------------------------------------------------------------------- diff --git a/dlls/hl2_dll/npc_launcher.cpp b/dlls/hl2_dll/npc_launcher.cpp index c247c5e0..c6eef1e6 100644 --- a/dlls/hl2_dll/npc_launcher.cpp +++ b/dlls/hl2_dll/npc_launcher.cpp @@ -376,7 +376,7 @@ void CNPC_Launcher::LauncherThink( void ) // Otherwise look for enemy to fire at else { - GetSenses()->Look(m_flMaxAttackDist); + GetSenses()->Look(static_cast(m_flMaxAttackDist)); CBaseEntity* pBestEnemy = BestEnemy(); if (pBestEnemy) diff --git a/dlls/hl2_dll/npc_manhack.cpp b/dlls/hl2_dll/npc_manhack.cpp index d1ddf8f4..c4dc5726 100644 --- a/dlls/hl2_dll/npc_manhack.cpp +++ b/dlls/hl2_dll/npc_manhack.cpp @@ -1589,7 +1589,7 @@ void CNPC_Manhack::Bump( CBaseEntity *pHitEntity, float flInterval, trace_t &tr if (moveVec.z < 0) { float floorZ = GetFloorZ(GetAbsOrigin()); - if (abs(GetAbsOrigin().z - floorZ) < 36) + if (abs(static_cast(GetAbsOrigin().z - floorZ)) < 36) { moveVec.z = 0; } @@ -1811,11 +1811,11 @@ void CNPC_Manhack::PlayFlySound(void) float flDistFactor; flDistFactor = min( 1.0, 1 - flEnemyDist / MANHACK_PITCH_DIST1 ); - iPitch1 = MANHACK_MIN_PITCH1 + ( ( MANHACK_MAX_PITCH1 - MANHACK_MIN_PITCH1 ) * flDistFactor); + iPitch1 = static_cast(MANHACK_MIN_PITCH1 + ( ( MANHACK_MAX_PITCH1 - MANHACK_MIN_PITCH1 ) * flDistFactor)); // NOTE: MANHACK_PITCH_DIST2 must be < MANHACK_PITCH_DIST1 flDistFactor = min( 1.0, 1 - flEnemyDist / MANHACK_PITCH_DIST2 ); - iPitch2 = MANHACK_MIN_PITCH2 + ( ( MANHACK_MAX_PITCH2 - MANHACK_MIN_PITCH2 ) * flDistFactor); + iPitch2 = static_cast(MANHACK_MIN_PITCH2 + ( ( MANHACK_MAX_PITCH2 - MANHACK_MIN_PITCH2 ) * flDistFactor)); m_nEnginePitch1 = iPitch1; m_flEnginePitch1Time = gpGlobals->curtime + 0.1f; diff --git a/dlls/hl2_dll/npc_metropolice.cpp b/dlls/hl2_dll/npc_metropolice.cpp index 0b61527e..55c64008 100644 --- a/dlls/hl2_dll/npc_metropolice.cpp +++ b/dlls/hl2_dll/npc_metropolice.cpp @@ -995,6 +995,9 @@ void CNPC_MetroPolice::AnnounceEnemyType( CBaseEntity *pEnemy ) case CLASS_BARNACLE: pSentenceName = "METROPOLICE_MONST_PARASITES"; break; + + default: + break; } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); @@ -1050,6 +1053,9 @@ void CNPC_MetroPolice::AnnounceEnemyKill( CBaseEntity *pEnemy ) case CLASS_BARNACLE: pSentenceName = "METROPOLICE_KILL_PARASITES"; break; + + default: + break; } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); @@ -1210,8 +1216,8 @@ void CNPC_MetroPolice::OnUpdateShotRegulator( ) float factor = (dist - MIN_PISTOL_MODIFY_DIST) / (MAX_PISTOL_MODIFY_DIST - MIN_PISTOL_MODIFY_DIST); - int nMinBurst = MIN_MIN_PISTOL_BURST + ( MAX_MIN_PISTOL_BURST - MIN_MIN_PISTOL_BURST ) * (1.0 - factor); - int nMaxBurst = MIN_MAX_PISTOL_BURST + ( MAX_MAX_PISTOL_BURST - MIN_MAX_PISTOL_BURST ) * (1.0 - factor); + int nMinBurst = static_cast(MIN_MIN_PISTOL_BURST + ( MAX_MIN_PISTOL_BURST - MIN_MIN_PISTOL_BURST ) * (1.0 - factor)); + int nMaxBurst = static_cast(MIN_MAX_PISTOL_BURST + ( MAX_MAX_PISTOL_BURST - MIN_MAX_PISTOL_BURST ) * (1.0 - factor)); float flMinRestInterval = MIN_MIN_PISTOL_REST_INTERVAL + ( MAX_MIN_PISTOL_REST_INTERVAL - MIN_MIN_PISTOL_REST_INTERVAL ) * factor; float flMaxRestInterval = MIN_MAX_PISTOL_REST_INTERVAL + ( MAX_MAX_PISTOL_REST_INTERVAL - MIN_MAX_PISTOL_REST_INTERVAL ) * factor; @@ -1675,7 +1681,7 @@ float CNPC_MetroPolice::AimBurstAtReactionTime( float flReactionTime, float flDi #define AIM_AT_SHOT_SPEED_COUNT 6 #define AIM_AT_SHOT_DIST_COUNT 6 -static int s_pShotCountFraction[AIM_AT_TIME_DIST_COUNT][AIM_AT_TIME_SPEED_COUNT] = +static float s_pShotCountFraction[AIM_AT_TIME_DIST_COUNT][AIM_AT_TIME_SPEED_COUNT] = { { 3.0f, 3.0f, 2.5f, 1.5f, 1.0f, 0.0f }, { 3.0f, 3.0f, 2.5f, 1.25f, 0.5f, 0.0f }, @@ -1706,7 +1712,7 @@ int CNPC_MetroPolice::AimBurstAtSetupHitCount( float flDistToTarget, float flCur flShotFactor += s_pShotCountFraction[nv][nu+1] * fu * (1.0f - fv); flShotFactor += s_pShotCountFraction[nv+1][nu+1] * fu * fv; - int nExtraShots = nHitCount * flShotFactor; + int nExtraShots = static_cast(nHitCount * flShotFactor); m_nMaxBurstHits += random->RandomInt( nExtraShots, nExtraShots + 1 ); return nExtraShots; } @@ -4169,6 +4175,9 @@ int CNPC_MetroPolice::SelectSchedule( void ) return nResult; } break; + + default: + break; } } @@ -4355,7 +4364,7 @@ void CNPC_MetroPolice::StartTask( const Task_t *pTask ) { case TASK_METROPOLICE_WAIT_FOR_SENTENCE: { - if ( FOkToMakeSound( pTask->flTaskData ) ) + if ( FOkToMakeSound( static_cast(pTask->flTaskData) ) ) { TaskComplete(); } @@ -4429,7 +4438,7 @@ void CNPC_MetroPolice::StartTask( const Task_t *pTask ) info.SetAttacker( this ); info.SetInflictor( this ); info.SetDamage( m_iHealth ); - info.SetDamageType( pTask->flTaskData ); + info.SetDamageType( static_cast(pTask->flTaskData) ); info.SetDamageForce( Vector( 0.1, 0.1, 0.1 ) ); TakeDamage( info ); @@ -4666,7 +4675,7 @@ void CNPC_MetroPolice::RunTask( const Task_t *pTask ) case TASK_METROPOLICE_WAIT_FOR_SENTENCE: { - if ( FOkToMakeSound( pTask->flTaskData ) ) + if ( FOkToMakeSound( static_cast(pTask->flTaskData) ) ) { TaskComplete(); } @@ -4853,7 +4862,7 @@ int CNPC_MetroPolice::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) { // Keep track of recent damage by my attacker. If it seems like we're // being killed, consider running off and hiding. - m_nRecentDamage += info.GetDamage(); + m_nRecentDamage += static_cast(info.GetDamage()); m_flRecentDamageTime = gpGlobals->curtime; } diff --git a/dlls/hl2_dll/npc_mossman.cpp b/dlls/hl2_dll/npc_mossman.cpp index 3bfcb8bd..ef186004 100644 --- a/dlls/hl2_dll/npc_mossman.cpp +++ b/dlls/hl2_dll/npc_mossman.cpp @@ -82,7 +82,7 @@ void CNPC_Mossman::HandleAnimEvent( animevent_t *pEvent ) //----------------------------------------------------------------------------- int CNPC_Mossman::GetSoundInterests ( void ) { - return NULL; + return 0; } //----------------------------------------------------------------------------- diff --git a/dlls/hl2_dll/npc_playercompanion.cpp b/dlls/hl2_dll/npc_playercompanion.cpp index d935c60f..e76aa57e 100644 --- a/dlls/hl2_dll/npc_playercompanion.cpp +++ b/dlls/hl2_dll/npc_playercompanion.cpp @@ -2702,7 +2702,7 @@ bool CNPC_PlayerCompanion::OverrideMove( float flInterval ) } } - for ( int i = 0; i < ARRAYSIZE(classNames); i++ ) + for ( int i = 0; i < static_cast(ARRAYSIZE(classNames)); i++ ) { if ( classNames[i] != NULL_STRING ) { diff --git a/dlls/hl2_dll/npc_rollermine.cpp b/dlls/hl2_dll/npc_rollermine.cpp index cfd4989a..f380d275 100644 --- a/dlls/hl2_dll/npc_rollermine.cpp +++ b/dlls/hl2_dll/npc_rollermine.cpp @@ -753,6 +753,8 @@ NPC_STATE CNPC_RollerMine::SelectIdealState( void ) return NPC_STATE_ALERT; } } + default: + break; } return BaseClass::SelectIdealState(); @@ -840,12 +842,12 @@ void CNPC_RollerMine::RunAI() if( m_bHackedByAlyx ) { // Scare combine - CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_COMBINE_ONLY | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120.0f, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_COMBINE_ONLY | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); } else { // Scare player allies - CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_EXCLUDE_COMBINE | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120.0f, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_EXCLUDE_COMBINE | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); } } @@ -2530,7 +2532,7 @@ void CNPC_RollerMine::Explode( void ) } else { - ExplosionCreate( WorldSpaceCenter(), GetLocalAngles(), this, expDamage, 128, true ); + ExplosionCreate( WorldSpaceCenter(), GetLocalAngles(), this, static_cast(expDamage), 128, true ); } CTakeDamageInfo info( this, this, 1, DMG_GENERIC ); @@ -2637,6 +2639,9 @@ void CNPC_RollerMine::UpdateRollingSound() case ROLL_SOUND_OFF: // no sound break; + + default: + break; } // start the new sound playing if necessary diff --git a/dlls/hl2_dll/npc_scanner.cpp b/dlls/hl2_dll/npc_scanner.cpp index 197da2a3..a5fb2797 100644 --- a/dlls/hl2_dll/npc_scanner.cpp +++ b/dlls/hl2_dll/npc_scanner.cpp @@ -1168,7 +1168,7 @@ void CNPC_CScanner::AttackDivebombCollide(float flInterval) if (vBounceVel.z < 0) { float floorZ = GetFloorZ(GetAbsOrigin()); - if (abs(GetAbsOrigin().z - floorZ) < 36) + if (abs(static_cast(GetAbsOrigin().z - floorZ)) < 36) { vBounceVel.z = 0; } @@ -1359,7 +1359,7 @@ void CNPC_CScanner::PlayFlySound(void) float speed = GetCurrentVelocity().Length(); float flVolume = 0.25f + (0.75f*(speed/GetMaxSpeed())); - int iPitch = min( 255, 80 + (20*(speed/GetMaxSpeed())) ); + int iPitch = static_cast(min( 255, 80 + (20*(speed/GetMaxSpeed())) )); //Update our pitch and volume based on our speed controller.SoundChangePitch( m_pEngineSound, iPitch, 0.1f ); @@ -2761,12 +2761,12 @@ void CNPC_CScanner::SpotlightUpdate(void) } else if (m_flSpotlightCurLength > m_flSpotlightMaxLength) { - m_hSpotlightTarget->SetRenderColorA( (1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength)) ); + m_hSpotlightTarget->SetRenderColorA( static_cast((1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength))) ); m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength); } else { - m_hSpotlightTarget->SetRenderColorA( 1.0 ); + m_hSpotlightTarget->SetRenderColorA( 1 ); m_hSpotlight->SetFadeLength(m_flSpotlightCurLength); } @@ -2920,7 +2920,7 @@ void CNPC_CScanner::BlindFlashTarget( CBaseEntity *pTarget ) if ( tr.startsolid == false && tr.fraction == 1.0) { - color32 white = { 255, 255, 255, SCANNER_FLASH_MAX_VALUE * dotPr }; + color32 white = { 255, 255, 255, static_cast(SCANNER_FLASH_MAX_VALUE * dotPr) }; UTIL_ScreenFade( pTarget, white, 3.0, 0.5, FFADE_IN ); } } @@ -3891,6 +3891,9 @@ float CNPC_CScanner::GetGoalDistance( void ) case SCANNER_FLY_FOLLOW: return ( SCANNER_FOLLOW_DIST ); break; + + default: + break; } return 128.0f; diff --git a/dlls/hl2_dll/npc_stalker.cpp b/dlls/hl2_dll/npc_stalker.cpp index cd368a8f..6e65c31f 100644 --- a/dlls/hl2_dll/npc_stalker.cpp +++ b/dlls/hl2_dll/npc_stalker.cpp @@ -757,6 +757,9 @@ int CNPC_Stalker::SelectSchedule( void ) break; } + + default: + break; } // no special cases here, call the base class @@ -1261,7 +1264,7 @@ void CNPC_Stalker::HandleAnimEvent( animevent_t *pEvent ) { CBaseEntity *pHurt; - pHurt = CheckTraceHullAttack( 32, Vector(-16,-16,-16), Vector(16,16,16), sk_stalker_melee_dmg.GetFloat(), DMG_SLASH ); + pHurt = CheckTraceHullAttack( 32, Vector(-16,-16,-16), Vector(16,16,16), sk_stalker_melee_dmg.GetInt(), DMG_SLASH ); if ( pHurt ) { diff --git a/dlls/hl2_dll/npc_strider.cpp b/dlls/hl2_dll/npc_strider.cpp index 9f5f900d..dc2a8243 100644 --- a/dlls/hl2_dll/npc_strider.cpp +++ b/dlls/hl2_dll/npc_strider.cpp @@ -2704,15 +2704,15 @@ int CNPC_Strider::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { if( g_pGameRules->IsSkillLevel(SKILL_EASY) ) { - damage = GetMaxHealth() / sk_strider_num_missiles1.GetFloat(); + damage = GetMaxHealth() / sk_strider_num_missiles1.GetInt(); } else if( g_pGameRules->IsSkillLevel(SKILL_HARD) ) { - damage = GetMaxHealth() / sk_strider_num_missiles3.GetFloat(); + damage = GetMaxHealth() / sk_strider_num_missiles3.GetInt(); } else // Medium, or unspecified { - damage = GetMaxHealth() / sk_strider_num_missiles2.GetFloat(); + damage = GetMaxHealth() / sk_strider_num_missiles2.GetInt(); } } @@ -2815,7 +2815,7 @@ int CNPC_Strider::TakeDamageFromCombineBall( const CTakeDamageInfo &info ) m_iHealth -= damage; - return damage; + return static_cast(damage); } //--------------------------------------------------------- diff --git a/dlls/hl2_dll/npc_turret_ceiling.cpp b/dlls/hl2_dll/npc_turret_ceiling.cpp index 5c14ba62..1c097aed 100644 --- a/dlls/hl2_dll/npc_turret_ceiling.cpp +++ b/dlls/hl2_dll/npc_turret_ceiling.cpp @@ -1,1127 +1,1127 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "ai_basenpc.h" -#include "ai_senses.h" -#include "ai_memory.h" -#include "engine/IEngineSound.h" -#include "ammodef.h" -#include "Sprite.h" -#include "hl2_dll/hl2_player.h" -#include "soundenvelope.h" -#include "explode.h" -#include "IEffects.h" -#include "animation.h" -#include "basehlcombatweapon_shared.h" -#include "iservervehicle.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//Debug visualization -ConVar g_debug_turret_ceiling( "g_debug_turret_ceiling", "0" ); - -#define CEILING_TURRET_MODEL "models/combine_turrets/ceiling_turret.mdl" -#define CEILING_TURRET_GLOW_SPRITE "sprites/glow1.vmt" -#define CEILING_TURRET_BC_YAW "aim_yaw" -#define CEILING_TURRET_BC_PITCH "aim_pitch" -#define CEILING_TURRET_RANGE 1500 -#define CEILING_TURRET_SPREAD VECTOR_CONE_2DEGREES -#define CEILING_TURRET_MAX_WAIT 5 -#define CEILING_TURRET_PING_TIME 1.0f //LPB!! - -#define CEILING_TURRET_VOICE_PITCH_LOW 45 -#define CEILING_TURRET_VOICE_PITCH_HIGH 100 - -//Aiming variables -#define CEILING_TURRET_MAX_NOHARM_PERIOD 0.0f -#define CEILING_TURRET_MAX_GRACE_PERIOD 3.0f - -//Spawnflags -#define SF_CEILING_TURRET_AUTOACTIVATE 0x00000020 -#define SF_CEILING_TURRET_STARTINACTIVE 0x00000040 -#define SF_CEILING_TURRET_NEVERRETIRE 0x00000080 -#define SF_CEILING_TURRET_OUT_OF_AMMO 0x00000100 - -//Heights -#define CEILING_TURRET_RETRACT_HEIGHT 24 -#define CEILING_TURRET_DEPLOY_HEIGHT 64 - -//Activities -int ACT_CEILING_TURRET_OPEN; -int ACT_CEILING_TURRET_CLOSE; -int ACT_CEILING_TURRET_OPEN_IDLE; -int ACT_CEILING_TURRET_CLOSED_IDLE; -int ACT_CEILING_TURRET_FIRE; -int ACT_CEILING_TURRET_DRYFIRE; - -//Turret states -enum turretState_e -{ - TURRET_SEARCHING, - TURRET_AUTO_SEARCHING, - TURRET_ACTIVE, - TURRET_DEPLOYING, - TURRET_RETIRING, - TURRET_DEAD, -}; - -//Eye states -enum eyeState_t -{ - TURRET_EYE_SEE_TARGET, //Sees the target, bright and big - TURRET_EYE_SEEKING_TARGET, //Looking for a target, blinking (bright) - TURRET_EYE_DORMANT, //Not active - TURRET_EYE_DEAD, //Completely invisible - TURRET_EYE_DISABLED, //Turned off, must be reactivated before it'll deploy again (completely invisible) -}; - -// -// Ceiling Turret -// - -class CNPC_CeilingTurret : public CAI_BaseNPC -{ - DECLARE_CLASS( CNPC_CeilingTurret, CAI_BaseNPC ); -public: - - CNPC_CeilingTurret( void ); - ~CNPC_CeilingTurret( void ); - - void Precache( void ); - void Spawn( void ); - - // Think functions - void Retire( void ); - void Deploy( void ); - void ActiveThink( void ); - void SearchThink( void ); - void AutoSearchThink( void ); - void DeathThink( void ); - - // Inputs - void InputToggle( inputdata_t &inputdata ); - void InputEnable( inputdata_t &inputdata ); - void InputDisable( inputdata_t &inputdata ); - - void SetLastSightTime(); - - float MaxYawSpeed( void ); - - int OnTakeDamage( const CTakeDamageInfo &inputInfo ); - - virtual bool CanBeAnEnemyOf( CBaseEntity *pEnemy ); - - Class_T Classify( void ) - { - if( m_bEnabled ) - return CLASS_COMBINE; - - return CLASS_NONE; - } - - bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_OPAQUE, CBaseEntity **ppBlocker = NULL ); - - Vector EyeOffset( Activity nActivity ) - { - Vector vecEyeOffset(0,0,-64); - GetEyePosition( GetModelPtr(), vecEyeOffset ); - return vecEyeOffset; - } - - Vector EyePosition( void ) - { - return GetAbsOrigin() + EyeOffset(GetActivity()); - } - - Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) - { - return VECTOR_CONE_5DEGREES * ((CBaseHLCombatWeapon::GetDefaultProficiencyValues())[ WEAPON_PROFICIENCY_PERFECT ].spreadscale); - } - -protected: - - bool PreThink( turretState_e state ); - void Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy ); - void SetEyeState( eyeState_t state ); - void Ping( void ); - void Toggle( void ); - void Enable( void ); - void Disable( void ); - void SpinUp( void ); - void SpinDown( void ); - void SetHeight( float height ); - - bool UpdateFacing( void ); - - int m_iAmmoType; - int m_iMinHealthDmg; - - bool m_bAutoStart; - bool m_bActive; //Denotes the turret is deployed and looking for targets - bool m_bBlinkState; - bool m_bEnabled; //Denotes whether the turret is able to deploy or not - - float m_flShotTime; - float m_flLastSight; - float m_flPingTime; - - QAngle m_vecGoalAngles; - - CSprite *m_pEyeGlow; - - COutputEvent m_OnDeploy; - COutputEvent m_OnRetire; - COutputEvent m_OnTipped; - - DECLARE_DATADESC(); -}; - -//Datatable -BEGIN_DATADESC( CNPC_CeilingTurret ) - - DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), - DEFINE_KEYFIELD( m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg" ), - DEFINE_FIELD( m_bAutoStart, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flShotTime, FIELD_TIME ), - DEFINE_FIELD( m_flLastSight, FIELD_TIME ), - DEFINE_FIELD( m_flPingTime, FIELD_TIME ), - DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ), - DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ), - - DEFINE_THINKFUNC( Retire ), - DEFINE_THINKFUNC( Deploy ), - DEFINE_THINKFUNC( ActiveThink ), - DEFINE_THINKFUNC( SearchThink ), - DEFINE_THINKFUNC( AutoSearchThink ), - DEFINE_THINKFUNC( DeathThink ), - - // Inputs - DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), - DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), - DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), - - DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ), - DEFINE_OUTPUT( m_OnRetire, "OnRetire" ), - DEFINE_OUTPUT( m_OnTipped, "OnTipped" ), - -END_DATADESC() - -LINK_ENTITY_TO_CLASS( npc_turret_ceiling, CNPC_CeilingTurret ); - -//----------------------------------------------------------------------------- -// Constructor -//----------------------------------------------------------------------------- -CNPC_CeilingTurret::CNPC_CeilingTurret( void ) -{ - m_bActive = false; - m_pEyeGlow = NULL; - m_iAmmoType = -1; - m_iMinHealthDmg = 0; - m_bAutoStart = false; - m_flPingTime = 0; - m_flShotTime = 0; - m_flLastSight = 0; - m_bBlinkState = false; - m_bEnabled = false; - - m_vecGoalAngles.Init(); -} - -CNPC_CeilingTurret::~CNPC_CeilingTurret( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: Precache -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Precache( void ) -{ - PrecacheModel( CEILING_TURRET_MODEL ); - PrecacheModel( CEILING_TURRET_GLOW_SPRITE ); - - // Activities - ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_OPEN ); - ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_CLOSE ); - ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_CLOSED_IDLE ); - ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_OPEN_IDLE ); - ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_FIRE ); - ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_DRYFIRE ); - - PrecacheScriptSound( "NPC_CeilingTurret.Retire" ); - PrecacheScriptSound( "NPC_CeilingTurret.Deploy" ); - PrecacheScriptSound( "NPC_CeilingTurret.Move" ); - PrecacheScriptSound( "NPC_CeilingTurret.Active" ); - PrecacheScriptSound( "NPC_CeilingTurret.Alert" ); - PrecacheScriptSound( "NPC_CeilingTurret.ShotSounds" ); - PrecacheScriptSound( "NPC_CeilingTurret.Ping" ); - PrecacheScriptSound( "NPC_CeilingTurret.Die" ); - - PrecacheScriptSound( "NPC_FloorTurret.DryFire" ); - - BaseClass::Precache(); -} - -//----------------------------------------------------------------------------- -// Purpose: Spawn the entity -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Spawn( void ) -{ - Precache(); - - SetModel( CEILING_TURRET_MODEL ); - - BaseClass::Spawn(); - - m_HackedGunPos = Vector( 0, 0, 12.75 ); - SetViewOffset( EyeOffset( ACT_IDLE ) ); - m_flFieldOfView = 0.0f; - m_takedamage = DAMAGE_YES; - m_iHealth = 1000; - m_bloodColor = BLOOD_COLOR_MECH; - - SetSolid( SOLID_BBOX ); - AddSolidFlags( FSOLID_NOT_STANDABLE ); - - SetHeight( CEILING_TURRET_RETRACT_HEIGHT ); - - AddFlag( FL_AIMTARGET ); - AddEFlags( EFL_NO_DISSOLVE ); - - SetPoseParameter( CEILING_TURRET_BC_YAW, 0 ); - SetPoseParameter( CEILING_TURRET_BC_PITCH, 0 ); - - m_iAmmoType = GetAmmoDef()->Index( "AR2" ); - - //Create our eye sprite - m_pEyeGlow = CSprite::SpriteCreate( CEILING_TURRET_GLOW_SPRITE, GetLocalOrigin(), false ); - m_pEyeGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation ); - m_pEyeGlow->SetAttachment( this, 2 ); - - //Set our autostart state - m_bAutoStart = !!( m_spawnflags & SF_CEILING_TURRET_AUTOACTIVATE ); - m_bEnabled = ( ( m_spawnflags & SF_CEILING_TURRET_STARTINACTIVE ) == false ); - - //Do we start active? - if ( m_bAutoStart && m_bEnabled ) - { - SetThink( &CNPC_CeilingTurret::AutoSearchThink ); - SetEyeState( TURRET_EYE_DORMANT ); - } - else - { - SetEyeState( TURRET_EYE_DISABLED ); - } - - //Stagger our starting times - SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ) ); - - // Don't allow us to skip animation setup because our attachments are critical to us! - SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CNPC_CeilingTurret::OnTakeDamage( const CTakeDamageInfo &inputInfo ) -{ - if ( !m_takedamage ) - return 0; - - CTakeDamageInfo info = inputInfo; - - if ( m_bActive == false ) - info.ScaleDamage( 0.1f ); - - // If attacker can't do at least the min required damage to us, don't take any damage from them - if ( info.GetDamage() < m_iMinHealthDmg ) - return 0; - - m_iHealth -= info.GetDamage(); - - if ( m_iHealth <= 0 ) - { - m_iHealth = 0; - m_takedamage = DAMAGE_NO; - - RemoveFlag( FL_NPC ); // why are they set in the first place??? - - //FIXME: This needs to throw a ragdoll gib or something other than animating the retraction -- jdw - - ExplosionCreate( GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false ); - SetThink( &CNPC_CeilingTurret::DeathThink ); - - StopSound( "NPC_CeilingTurret.Alert" ); - - m_OnDamaged.FireOutput( info.GetInflictor(), this ); - - SetNextThink( gpGlobals->curtime + 0.1f ); - - return 0; - } - - return 1; -} - -//----------------------------------------------------------------------------- -// Purpose: Retract and stop attacking -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Retire( void ) -{ - if ( PreThink( TURRET_RETIRING ) ) - return; - - //Level out the turret - m_vecGoalAngles = GetAbsAngles(); - SetNextThink( gpGlobals->curtime ); - - //Set ourselves to close - if ( GetActivity() != ACT_CEILING_TURRET_CLOSE ) - { - //Set our visible state to dormant - SetEyeState( TURRET_EYE_DORMANT ); - - SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); - - //If we're done moving to our desired facing, close up - if ( UpdateFacing() == false ) - { - SetActivity( (Activity) ACT_CEILING_TURRET_CLOSE ); - EmitSound( "NPC_CeilingTurret.Retire" ); - - //Notify of the retraction - m_OnRetire.FireOutput( NULL, this ); - } - } - else if ( IsActivityFinished() ) - { - SetHeight( CEILING_TURRET_RETRACT_HEIGHT ); - - m_bActive = false; - m_flLastSight = 0; - - SetActivity( (Activity) ACT_CEILING_TURRET_CLOSED_IDLE ); - - //Go back to auto searching - if ( m_bAutoStart ) - { - SetThink( &CNPC_CeilingTurret::AutoSearchThink ); - SetNextThink( gpGlobals->curtime + 0.05f ); - } - else - { - //Set our visible state to dormant - SetEyeState( TURRET_EYE_DISABLED ); - SetThink( &CNPC_CeilingTurret::SUB_DoNothing ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Deploy and start attacking -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Deploy( void ) -{ - if ( PreThink( TURRET_DEPLOYING ) ) - return; - - m_vecGoalAngles = GetAbsAngles(); - - SetNextThink( gpGlobals->curtime ); - - //Show we've seen a target - SetEyeState( TURRET_EYE_SEE_TARGET ); - - //Open if we're not already - if ( GetActivity() != ACT_CEILING_TURRET_OPEN ) - { - m_bActive = true; - SetActivity( (Activity) ACT_CEILING_TURRET_OPEN ); - EmitSound( "NPC_CeilingTurret.Deploy" ); - - //Notify we're deploying - m_OnDeploy.FireOutput( NULL, this ); - } - - //If we're done, then start searching - if ( IsActivityFinished() ) - { - SetHeight( CEILING_TURRET_DEPLOY_HEIGHT ); - - SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); - - m_flShotTime = gpGlobals->curtime + 1.0f; - - m_flPlaybackRate = 0; - SetThink( &CNPC_CeilingTurret::SearchThink ); - - EmitSound( "NPC_CeilingTurret.Move" ); - } - - SetLastSightTime(); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::SetLastSightTime() -{ - if( HasSpawnFlags( SF_CEILING_TURRET_NEVERRETIRE ) ) - { - m_flLastSight = FLT_MAX; - } - else - { - m_flLastSight = gpGlobals->curtime + CEILING_TURRET_MAX_WAIT; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the speed at which the turret can face a target -//----------------------------------------------------------------------------- -float CNPC_CeilingTurret::MaxYawSpeed( void ) -{ - //TODO: Scale by difficulty? - return 360.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: Causes the turret to face its desired angles -//----------------------------------------------------------------------------- -bool CNPC_CeilingTurret::UpdateFacing( void ) -{ - bool bMoved = false; - matrix3x4_t localToWorld; - - GetAttachment( LookupAttachment( "eyes" ), localToWorld ); - - Vector vecGoalDir; - AngleVectors( m_vecGoalAngles, &vecGoalDir ); - - Vector vecGoalLocalDir; - VectorIRotate( vecGoalDir, localToWorld, vecGoalLocalDir ); - - if ( g_debug_turret_ceiling.GetBool() ) - { - Vector vecMuzzle, vecMuzzleDir; - QAngle vecMuzzleAng; - - GetAttachment( "eyes", vecMuzzle, vecMuzzleAng ); - AngleVectors( vecMuzzleAng, &vecMuzzleDir ); - - NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 ); - NDebugOverlay::Cross3D( vecMuzzle+(vecMuzzleDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 ); - NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecMuzzleDir*256), 255, 255, 0, false, 0.05 ); - - NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 ); - NDebugOverlay::Cross3D( vecMuzzle+(vecGoalDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 ); - NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecGoalDir*256), 255, 0, 0, false, 0.05 ); - } - - QAngle vecGoalLocalAngles; - VectorAngles( vecGoalLocalDir, vecGoalLocalAngles ); - - // Update pitch - float flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.1f * MaxYawSpeed() ) ); - - int iPose = LookupPoseParameter( CEILING_TURRET_BC_PITCH ); - SetPoseParameter( iPose, GetPoseParameter( iPose ) + ( flDiff / 1.5f ) ); - - if ( fabs( flDiff ) > 0.1f ) - { - bMoved = true; - } - - // Update yaw - flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1f * MaxYawSpeed() ) ); - - iPose = LookupPoseParameter( CEILING_TURRET_BC_YAW ); - SetPoseParameter( iPose, GetPoseParameter( iPose ) + ( flDiff / 1.5f ) ); - - if ( fabs( flDiff ) > 0.1f ) - { - bMoved = true; - } - - InvalidateBoneCache(); - - return bMoved; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEntity - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CNPC_CeilingTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) -{ - CBaseEntity *pHitEntity = NULL; - if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) ) - return true; - - // If we hit something that's okay to hit anyway, still fire - if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() ) - { - if (IRelationType(pHitEntity) == D_HT) - return true; - } - - if (ppBlocker) - { - *ppBlocker = pHitEntity; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Allows the turret to fire on targets if they're visible -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::ActiveThink( void ) -{ - //Allow descended classes a chance to do something before the think function - if ( PreThink( TURRET_ACTIVE ) ) - return; - - //Update our think time - SetNextThink( gpGlobals->curtime + 0.1f ); - - //If we've become inactive, go back to searching - if ( ( m_bActive == false ) || ( GetEnemy() == NULL ) ) - { - SetEnemy( NULL ); - SetLastSightTime(); - SetThink( &CNPC_CeilingTurret::SearchThink ); - m_vecGoalAngles = GetAbsAngles(); - return; - } - - //Get our shot positions - Vector vecMid = EyePosition(); - Vector vecMidEnemy = GetEnemy()->GetAbsOrigin(); - - //Store off our last seen location - UpdateEnemyMemory( GetEnemy(), vecMidEnemy ); - - //Look for our current enemy - bool bEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() ) && GetEnemy()->IsAlive(); - - //Calculate dir and dist to enemy - Vector vecDirToEnemy = vecMidEnemy - vecMid; - float flDistToEnemy = VectorNormalize( vecDirToEnemy ); - - //We want to look at the enemy's eyes so we don't jitter - Vector vecDirToEnemyEyes = GetEnemy()->WorldSpaceCenter() - vecMid; - VectorNormalize( vecDirToEnemyEyes ); - - QAngle vecAnglesToEnemy; - VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy ); - - //Draw debug info - if ( g_debug_turret_ceiling.GetBool() ) - { - NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); - NDebugOverlay::Cross3D( GetEnemy()->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); - NDebugOverlay::Line( vecMid, GetEnemy()->WorldSpaceCenter(), 0, 255, 0, false, 0.05 ); - - NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); - NDebugOverlay::Cross3D( vecMidEnemy, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); - NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f ); - } - - //Current enemy is not visible - if ( ( bEnemyVisible == false ) || ( flDistToEnemy > CEILING_TURRET_RANGE )) - { - if ( m_flLastSight ) - { - m_flLastSight = gpGlobals->curtime + 0.5f; - } - else if ( gpGlobals->curtime > m_flLastSight ) - { - // Should we look for a new target? - ClearEnemyMemory(); - SetEnemy( NULL ); - SetLastSightTime(); - SetThink( &CNPC_CeilingTurret::SearchThink ); - m_vecGoalAngles = GetAbsAngles(); - - SpinDown(); - - return; - } - - bEnemyVisible = false; - } - - Vector vecMuzzle, vecMuzzleDir; - QAngle vecMuzzleAng; - - GetAttachment( "eyes", vecMuzzle, vecMuzzleAng ); - AngleVectors( vecMuzzleAng, &vecMuzzleDir ); - - if ( m_flShotTime < gpGlobals->curtime ) - { - //Fire the gun - if ( DotProduct( vecDirToEnemy, vecMuzzleDir ) >= 0.9848 ) // 10 degree slop - { - if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) - { - SetActivity( (Activity) ACT_CEILING_TURRET_DRYFIRE ); - } - else - { - SetActivity( (Activity) ACT_CEILING_TURRET_FIRE ); - } - - //Fire the weapon - Shoot( vecMuzzle, vecMuzzleDir ); - } - } - else - { - SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); - } - - //If we can see our enemy, face it - if ( bEnemyVisible ) - { - m_vecGoalAngles.y = vecAnglesToEnemy.y; - m_vecGoalAngles.x = vecAnglesToEnemy.x; - } - - //Turn to face - UpdateFacing(); -} - -//----------------------------------------------------------------------------- -// Purpose: Target doesn't exist or has eluded us, so search for one -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::SearchThink( void ) -{ - //Allow descended classes a chance to do something before the think function - if ( PreThink( TURRET_SEARCHING ) ) - return; - - SetNextThink( gpGlobals->curtime + 0.05f ); - - SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); - - //If our enemy has died, pick a new enemy - if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) ) - { - SetEnemy( NULL ); - } - - //Acquire the target - if ( GetEnemy() == NULL ) - { - GetSenses()->Look( CEILING_TURRET_RANGE ); - CBaseEntity *pEnemy = BestEnemy(); - if ( pEnemy ) - { - SetEnemy( pEnemy ); - } - } - - //If we've found a target, spin up the barrel and start to attack - if ( GetEnemy() != NULL ) - { - //Give players a grace period - if ( GetEnemy()->IsPlayer() ) - { - m_flShotTime = gpGlobals->curtime + 0.5f; - } - else - { - m_flShotTime = gpGlobals->curtime + 0.1f; - } - - m_flLastSight = 0; - SetThink( &CNPC_CeilingTurret::ActiveThink ); - SetEyeState( TURRET_EYE_SEE_TARGET ); - - SpinUp(); - EmitSound( "NPC_CeilingTurret.Active" ); - return; - } - - //Are we out of time and need to retract? - if ( gpGlobals->curtime > m_flLastSight ) - { - //Before we retrace, make sure that we are spun down. - m_flLastSight = 0; - SetThink( &CNPC_CeilingTurret::Retire ); - return; - } - - //Display that we're scanning - m_vecGoalAngles.x = 15.0f; - m_vecGoalAngles.y = GetAbsAngles().y + ( sin( gpGlobals->curtime * 2.0f ) * 45.0f ); - - //Turn and ping - UpdateFacing(); - Ping(); -} - -//----------------------------------------------------------------------------- -// Purpose: Watch for a target to wander into our view -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::AutoSearchThink( void ) -{ - //Allow descended classes a chance to do something before the think function - if ( PreThink( TURRET_AUTO_SEARCHING ) ) - return; - - //Spread out our thinking - SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.2f, 0.4f ) ); - - //If the enemy is dead, find a new one - if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) ) - { - SetEnemy( NULL ); - } - - //Acquire Target - if ( GetEnemy() == NULL ) - { - GetSenses()->Look( CEILING_TURRET_RANGE ); - SetEnemy( BestEnemy() ); - } - - //Deploy if we've got an active target - if ( GetEnemy() != NULL ) - { - SetThink( &CNPC_CeilingTurret::Deploy ); - EmitSound( "NPC_CeilingTurret.Alert" ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Fire! -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy ) -{ - if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) - { - EmitSound( "NPC_FloorTurret.DryFire"); - EmitSound( "NPC_CeilingTurret.Activate" ); - - if ( RandomFloat( 0, 1 ) > 0.7 ) - { - m_flShotTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.5 ); - } - else - { - m_flShotTime = gpGlobals->curtime; - } - return; - } - - FireBulletsInfo_t info; - - if ( GetEnemy() != NULL ) - { - Vector vecDir = GetActualShootTrajectory( vecSrc ); - - info.m_vecSrc = vecSrc; - info.m_vecDirShooting = vecDir; - info.m_iTracerFreq = 1; - info.m_iShots = 1; - info.m_pAttacker = this; - info.m_vecSpread = VECTOR_CONE_PRECALCULATED; - info.m_flDistance = MAX_COORD_RANGE; - info.m_iAmmoType = m_iAmmoType; - } - else - { - // Just shoot where you're facing! - Vector vecMuzzle, vecMuzzleDir; - QAngle vecMuzzleAng; - - info.m_vecSrc = vecSrc; - info.m_vecDirShooting = vecDirToEnemy; - info.m_iTracerFreq = 1; - info.m_iShots = 1; - info.m_pAttacker = this; - info.m_vecSpread = GetAttackSpread( NULL, NULL ); - info.m_flDistance = MAX_COORD_RANGE; - info.m_iAmmoType = m_iAmmoType; - } - - FireBullets( info ); - EmitSound( "NPC_CeilingTurret.ShotSounds" ); - DoMuzzleFlash(); -} - -//----------------------------------------------------------------------------- -// Purpose: Allows a generic think function before the others are called -// Input : state - which state the turret is currently in -//----------------------------------------------------------------------------- -bool CNPC_CeilingTurret::PreThink( turretState_e state ) -{ - CheckPVSCondition(); - - //Animate - StudioFrameAdvance(); - - //Do not interrupt current think function - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Sets the state of the glowing eye attached to the turret -// Input : state - state the eye should be in -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::SetEyeState( eyeState_t state ) -{ - //Must have a valid eye to affect - if ( m_pEyeGlow == NULL ) - return; - - //Set the state - switch( state ) - { - default: - case TURRET_EYE_SEE_TARGET: //Fade in and scale up - m_pEyeGlow->SetColor( 255, 0, 0 ); - m_pEyeGlow->SetBrightness( 164, 0.1f ); - m_pEyeGlow->SetScale( 0.4f, 0.1f ); - break; - - case TURRET_EYE_SEEKING_TARGET: //Ping-pongs - - //Toggle our state - m_bBlinkState = !m_bBlinkState; - m_pEyeGlow->SetColor( 255, 128, 0 ); - - if ( m_bBlinkState ) - { - //Fade up and scale up - m_pEyeGlow->SetScale( 0.25f, 0.1f ); - m_pEyeGlow->SetBrightness( 164, 0.1f ); - } - else - { - //Fade down and scale down - m_pEyeGlow->SetScale( 0.2f, 0.1f ); - m_pEyeGlow->SetBrightness( 64, 0.1f ); - } - - break; - - case TURRET_EYE_DORMANT: //Fade out and scale down - m_pEyeGlow->SetColor( 0, 255, 0 ); - m_pEyeGlow->SetScale( 0.1f, 0.5f ); - m_pEyeGlow->SetBrightness( 64, 0.5f ); - break; - - case TURRET_EYE_DEAD: //Fade out slowly - m_pEyeGlow->SetColor( 255, 0, 0 ); - m_pEyeGlow->SetScale( 0.1f, 3.0f ); - m_pEyeGlow->SetBrightness( 0, 3.0f ); - break; - - case TURRET_EYE_DISABLED: - m_pEyeGlow->SetColor( 0, 255, 0 ); - m_pEyeGlow->SetScale( 0.1f, 1.0f ); - m_pEyeGlow->SetBrightness( 0, 1.0f ); - break; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Make a pinging noise so the player knows where we are -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Ping( void ) -{ - //See if it's time to ping again - if ( m_flPingTime > gpGlobals->curtime ) - return; - - //Ping! - EmitSound( "NPC_CeilingTurret.Ping" ); - - SetEyeState( TURRET_EYE_SEEKING_TARGET ); - - m_flPingTime = gpGlobals->curtime + CEILING_TURRET_PING_TIME; -} - -//----------------------------------------------------------------------------- -// Purpose: Toggle the turret's state -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Toggle( void ) -{ - //Toggle the state - if ( m_bEnabled ) - { - Disable(); - } - else - { - Enable(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Enable the turret and deploy -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Enable( void ) -{ - m_bEnabled = true; - - // if the turret is flagged as an autoactivate turret, re-enable its ability open self. - if ( m_spawnflags & SF_CEILING_TURRET_AUTOACTIVATE ) - { - m_bAutoStart = true; - } - - SetThink( &CNPC_CeilingTurret::Deploy ); - SetNextThink( gpGlobals->curtime + 0.05f ); -} - -//----------------------------------------------------------------------------- -// Purpose: Retire the turret until enabled again -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::Disable( void ) -{ - m_bEnabled = false; - m_bAutoStart = false; - - SetEnemy( NULL ); - SetThink( &CNPC_CeilingTurret::Retire ); - SetNextThink( gpGlobals->curtime + 0.1f ); -} - -//----------------------------------------------------------------------------- -// Purpose: Toggle the turret's state via input function -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::InputToggle( inputdata_t &inputdata ) -{ - Toggle(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::InputEnable( inputdata_t &inputdata ) -{ - Enable(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::InputDisable( inputdata_t &inputdata ) -{ - Disable(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::SpinUp( void ) -{ -} - -#define CEILING_TURRET_MIN_SPIN_DOWN 1.0f - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::SpinDown( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::DeathThink( void ) -{ - if ( PreThink( TURRET_DEAD ) ) - return; - - //Level out our angles - m_vecGoalAngles = GetAbsAngles(); - SetNextThink( gpGlobals->curtime ); - - if ( m_lifeState != LIFE_DEAD ) - { - m_lifeState = LIFE_DEAD; - - EmitSound( "NPC_CeilingTurret.Die" ); - - SetActivity( (Activity) ACT_CEILING_TURRET_CLOSE ); - } - - // lots of smoke - Vector pos; - CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos ); - - CBroadcastRecipientFilter filter; - - te->Smoke( filter, 0.0, &pos, g_sModelIndexSmoke, 2.5, 10 ); - - g_pEffects->Sparks( pos ); - - if ( IsActivityFinished() && ( UpdateFacing() == false ) ) - { - SetHeight( CEILING_TURRET_RETRACT_HEIGHT ); - - m_flPlaybackRate = 0; - SetThink( NULL ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : height - -//----------------------------------------------------------------------------- -void CNPC_CeilingTurret::SetHeight( float height ) -{ - Vector forward, right, up; - AngleVectors( GetLocalAngles(), &forward, &right, &up ); - - Vector mins = ( forward * -16.0f ) + ( right * -16.0f ); - Vector maxs = ( forward * 16.0f ) + ( right * 16.0f ) + ( up * -height ); - - if ( mins.x > maxs.x ) - { - swap( mins.x, maxs.x ); - } - - if ( mins.y > maxs.y ) - { - swap( mins.y, maxs.y ); - } - - if ( mins.z > maxs.z ) - { - swap( mins.z, maxs.z ); - } - - SetCollisionBounds( mins, maxs ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEnemy - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CNPC_CeilingTurret::CanBeAnEnemyOf( CBaseEntity *pEnemy ) -{ - // If we're out of ammo, make friendly companions ignore us - if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) - { - if ( pEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL ) - return false; - } - - return BaseClass::CanBeAnEnemyOf( pEnemy ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "ai_basenpc.h" +#include "ai_senses.h" +#include "ai_memory.h" +#include "engine/IEngineSound.h" +#include "ammodef.h" +#include "Sprite.h" +#include "hl2_dll/hl2_player.h" +#include "soundenvelope.h" +#include "explode.h" +#include "IEffects.h" +#include "animation.h" +#include "basehlcombatweapon_shared.h" +#include "iservervehicle.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//Debug visualization +ConVar g_debug_turret_ceiling( "g_debug_turret_ceiling", "0" ); + +#define CEILING_TURRET_MODEL "models/combine_turrets/ceiling_turret.mdl" +#define CEILING_TURRET_GLOW_SPRITE "sprites/glow1.vmt" +#define CEILING_TURRET_BC_YAW "aim_yaw" +#define CEILING_TURRET_BC_PITCH "aim_pitch" +#define CEILING_TURRET_RANGE 1500 +#define CEILING_TURRET_SPREAD VECTOR_CONE_2DEGREES +#define CEILING_TURRET_MAX_WAIT 5 +#define CEILING_TURRET_PING_TIME 1.0f //LPB!! + +#define CEILING_TURRET_VOICE_PITCH_LOW 45 +#define CEILING_TURRET_VOICE_PITCH_HIGH 100 + +//Aiming variables +#define CEILING_TURRET_MAX_NOHARM_PERIOD 0.0f +#define CEILING_TURRET_MAX_GRACE_PERIOD 3.0f + +//Spawnflags +#define SF_CEILING_TURRET_AUTOACTIVATE 0x00000020 +#define SF_CEILING_TURRET_STARTINACTIVE 0x00000040 +#define SF_CEILING_TURRET_NEVERRETIRE 0x00000080 +#define SF_CEILING_TURRET_OUT_OF_AMMO 0x00000100 + +//Heights +#define CEILING_TURRET_RETRACT_HEIGHT 24 +#define CEILING_TURRET_DEPLOY_HEIGHT 64 + +//Activities +int ACT_CEILING_TURRET_OPEN; +int ACT_CEILING_TURRET_CLOSE; +int ACT_CEILING_TURRET_OPEN_IDLE; +int ACT_CEILING_TURRET_CLOSED_IDLE; +int ACT_CEILING_TURRET_FIRE; +int ACT_CEILING_TURRET_DRYFIRE; + +//Turret states +enum turretState_e +{ + TURRET_SEARCHING, + TURRET_AUTO_SEARCHING, + TURRET_ACTIVE, + TURRET_DEPLOYING, + TURRET_RETIRING, + TURRET_DEAD, +}; + +//Eye states +enum eyeState_t +{ + TURRET_EYE_SEE_TARGET, //Sees the target, bright and big + TURRET_EYE_SEEKING_TARGET, //Looking for a target, blinking (bright) + TURRET_EYE_DORMANT, //Not active + TURRET_EYE_DEAD, //Completely invisible + TURRET_EYE_DISABLED, //Turned off, must be reactivated before it'll deploy again (completely invisible) +}; + +// +// Ceiling Turret +// + +class CNPC_CeilingTurret : public CAI_BaseNPC +{ + DECLARE_CLASS( CNPC_CeilingTurret, CAI_BaseNPC ); +public: + + CNPC_CeilingTurret( void ); + ~CNPC_CeilingTurret( void ); + + void Precache( void ); + void Spawn( void ); + + // Think functions + void Retire( void ); + void Deploy( void ); + void ActiveThink( void ); + void SearchThink( void ); + void AutoSearchThink( void ); + void DeathThink( void ); + + // Inputs + void InputToggle( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + void SetLastSightTime(); + + float MaxYawSpeed( void ); + + int OnTakeDamage( const CTakeDamageInfo &inputInfo ); + + virtual bool CanBeAnEnemyOf( CBaseEntity *pEnemy ); + + Class_T Classify( void ) + { + if( m_bEnabled ) + return CLASS_COMBINE; + + return CLASS_NONE; + } + + bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_OPAQUE, CBaseEntity **ppBlocker = NULL ); + + Vector EyeOffset( Activity nActivity ) + { + Vector vecEyeOffset(0,0,-64); + GetEyePosition( GetModelPtr(), vecEyeOffset ); + return vecEyeOffset; + } + + Vector EyePosition( void ) + { + return GetAbsOrigin() + EyeOffset(GetActivity()); + } + + Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) + { + return VECTOR_CONE_5DEGREES * ((CBaseHLCombatWeapon::GetDefaultProficiencyValues())[ WEAPON_PROFICIENCY_PERFECT ].spreadscale); + } + +protected: + + bool PreThink( turretState_e state ); + void Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy ); + void SetEyeState( eyeState_t state ); + void Ping( void ); + void Toggle( void ); + void Enable( void ); + void Disable( void ); + void SpinUp( void ); + void SpinDown( void ); + void SetHeight( float height ); + + bool UpdateFacing( void ); + + int m_iAmmoType; + int m_iMinHealthDmg; + + bool m_bAutoStart; + bool m_bActive; //Denotes the turret is deployed and looking for targets + bool m_bBlinkState; + bool m_bEnabled; //Denotes whether the turret is able to deploy or not + + float m_flShotTime; + float m_flLastSight; + float m_flPingTime; + + QAngle m_vecGoalAngles; + + CSprite *m_pEyeGlow; + + COutputEvent m_OnDeploy; + COutputEvent m_OnRetire; + COutputEvent m_OnTipped; + + DECLARE_DATADESC(); +}; + +//Datatable +BEGIN_DATADESC( CNPC_CeilingTurret ) + + DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), + DEFINE_KEYFIELD( m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg" ), + DEFINE_FIELD( m_bAutoStart, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flShotTime, FIELD_TIME ), + DEFINE_FIELD( m_flLastSight, FIELD_TIME ), + DEFINE_FIELD( m_flPingTime, FIELD_TIME ), + DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ), + DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ), + + DEFINE_THINKFUNC( Retire ), + DEFINE_THINKFUNC( Deploy ), + DEFINE_THINKFUNC( ActiveThink ), + DEFINE_THINKFUNC( SearchThink ), + DEFINE_THINKFUNC( AutoSearchThink ), + DEFINE_THINKFUNC( DeathThink ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ), + DEFINE_OUTPUT( m_OnRetire, "OnRetire" ), + DEFINE_OUTPUT( m_OnTipped, "OnTipped" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( npc_turret_ceiling, CNPC_CeilingTurret ); + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CNPC_CeilingTurret::CNPC_CeilingTurret( void ) +{ + m_bActive = false; + m_pEyeGlow = NULL; + m_iAmmoType = -1; + m_iMinHealthDmg = 0; + m_bAutoStart = false; + m_flPingTime = 0; + m_flShotTime = 0; + m_flLastSight = 0; + m_bBlinkState = false; + m_bEnabled = false; + + m_vecGoalAngles.Init(); +} + +CNPC_CeilingTurret::~CNPC_CeilingTurret( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Precache +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Precache( void ) +{ + PrecacheModel( CEILING_TURRET_MODEL ); + PrecacheModel( CEILING_TURRET_GLOW_SPRITE ); + + // Activities + ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_OPEN ); + ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_CLOSE ); + ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_CLOSED_IDLE ); + ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_OPEN_IDLE ); + ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_FIRE ); + ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_DRYFIRE ); + + PrecacheScriptSound( "NPC_CeilingTurret.Retire" ); + PrecacheScriptSound( "NPC_CeilingTurret.Deploy" ); + PrecacheScriptSound( "NPC_CeilingTurret.Move" ); + PrecacheScriptSound( "NPC_CeilingTurret.Active" ); + PrecacheScriptSound( "NPC_CeilingTurret.Alert" ); + PrecacheScriptSound( "NPC_CeilingTurret.ShotSounds" ); + PrecacheScriptSound( "NPC_CeilingTurret.Ping" ); + PrecacheScriptSound( "NPC_CeilingTurret.Die" ); + + PrecacheScriptSound( "NPC_FloorTurret.DryFire" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn the entity +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Spawn( void ) +{ + Precache(); + + SetModel( CEILING_TURRET_MODEL ); + + BaseClass::Spawn(); + + m_HackedGunPos = Vector( 0, 0, 12.75 ); + SetViewOffset( EyeOffset( ACT_IDLE ) ); + m_flFieldOfView = 0.0f; + m_takedamage = DAMAGE_YES; + m_iHealth = 1000; + m_bloodColor = BLOOD_COLOR_MECH; + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + + SetHeight( CEILING_TURRET_RETRACT_HEIGHT ); + + AddFlag( FL_AIMTARGET ); + AddEFlags( EFL_NO_DISSOLVE ); + + SetPoseParameter( CEILING_TURRET_BC_YAW, 0 ); + SetPoseParameter( CEILING_TURRET_BC_PITCH, 0 ); + + m_iAmmoType = GetAmmoDef()->Index( "AR2" ); + + //Create our eye sprite + m_pEyeGlow = CSprite::SpriteCreate( CEILING_TURRET_GLOW_SPRITE, GetLocalOrigin(), false ); + m_pEyeGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( this, 2 ); + + //Set our autostart state + m_bAutoStart = !!( m_spawnflags & SF_CEILING_TURRET_AUTOACTIVATE ); + m_bEnabled = ( ( m_spawnflags & SF_CEILING_TURRET_STARTINACTIVE ) == false ); + + //Do we start active? + if ( m_bAutoStart && m_bEnabled ) + { + SetThink( &CNPC_CeilingTurret::AutoSearchThink ); + SetEyeState( TURRET_EYE_DORMANT ); + } + else + { + SetEyeState( TURRET_EYE_DISABLED ); + } + + //Stagger our starting times + SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ) ); + + // Don't allow us to skip animation setup because our attachments are critical to us! + SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CNPC_CeilingTurret::OnTakeDamage( const CTakeDamageInfo &inputInfo ) +{ + if ( !m_takedamage ) + return 0; + + CTakeDamageInfo info = inputInfo; + + if ( m_bActive == false ) + info.ScaleDamage( 0.1f ); + + // If attacker can't do at least the min required damage to us, don't take any damage from them + if ( info.GetDamage() < m_iMinHealthDmg ) + return 0; + + m_iHealth -= info.GetDamage(); + + if ( m_iHealth <= 0 ) + { + m_iHealth = 0; + m_takedamage = DAMAGE_NO; + + RemoveFlag( FL_NPC ); // why are they set in the first place??? + + //FIXME: This needs to throw a ragdoll gib or something other than animating the retraction -- jdw + + ExplosionCreate( GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false ); + SetThink( &CNPC_CeilingTurret::DeathThink ); + + StopSound( "NPC_CeilingTurret.Alert" ); + + m_OnDamaged.FireOutput( info.GetInflictor(), this ); + + SetNextThink( gpGlobals->curtime + 0.1f ); + + return 0; + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Retract and stop attacking +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Retire( void ) +{ + if ( PreThink( TURRET_RETIRING ) ) + return; + + //Level out the turret + m_vecGoalAngles = GetAbsAngles(); + SetNextThink( gpGlobals->curtime ); + + //Set ourselves to close + if ( GetActivity() != ACT_CEILING_TURRET_CLOSE ) + { + //Set our visible state to dormant + SetEyeState( TURRET_EYE_DORMANT ); + + SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); + + //If we're done moving to our desired facing, close up + if ( UpdateFacing() == false ) + { + SetActivity( (Activity) ACT_CEILING_TURRET_CLOSE ); + EmitSound( "NPC_CeilingTurret.Retire" ); + + //Notify of the retraction + m_OnRetire.FireOutput( NULL, this ); + } + } + else if ( IsActivityFinished() ) + { + SetHeight( CEILING_TURRET_RETRACT_HEIGHT ); + + m_bActive = false; + m_flLastSight = 0; + + SetActivity( (Activity) ACT_CEILING_TURRET_CLOSED_IDLE ); + + //Go back to auto searching + if ( m_bAutoStart ) + { + SetThink( &CNPC_CeilingTurret::AutoSearchThink ); + SetNextThink( gpGlobals->curtime + 0.05f ); + } + else + { + //Set our visible state to dormant + SetEyeState( TURRET_EYE_DISABLED ); + SetThink( &CNPC_CeilingTurret::SUB_DoNothing ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Deploy and start attacking +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Deploy( void ) +{ + if ( PreThink( TURRET_DEPLOYING ) ) + return; + + m_vecGoalAngles = GetAbsAngles(); + + SetNextThink( gpGlobals->curtime ); + + //Show we've seen a target + SetEyeState( TURRET_EYE_SEE_TARGET ); + + //Open if we're not already + if ( GetActivity() != ACT_CEILING_TURRET_OPEN ) + { + m_bActive = true; + SetActivity( (Activity) ACT_CEILING_TURRET_OPEN ); + EmitSound( "NPC_CeilingTurret.Deploy" ); + + //Notify we're deploying + m_OnDeploy.FireOutput( NULL, this ); + } + + //If we're done, then start searching + if ( IsActivityFinished() ) + { + SetHeight( CEILING_TURRET_DEPLOY_HEIGHT ); + + SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); + + m_flShotTime = gpGlobals->curtime + 1.0f; + + m_flPlaybackRate = 0; + SetThink( &CNPC_CeilingTurret::SearchThink ); + + EmitSound( "NPC_CeilingTurret.Move" ); + } + + SetLastSightTime(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::SetLastSightTime() +{ + if( HasSpawnFlags( SF_CEILING_TURRET_NEVERRETIRE ) ) + { + m_flLastSight = FLT_MAX; + } + else + { + m_flLastSight = gpGlobals->curtime + CEILING_TURRET_MAX_WAIT; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the speed at which the turret can face a target +//----------------------------------------------------------------------------- +float CNPC_CeilingTurret::MaxYawSpeed( void ) +{ + //TODO: Scale by difficulty? + return 360.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Causes the turret to face its desired angles +//----------------------------------------------------------------------------- +bool CNPC_CeilingTurret::UpdateFacing( void ) +{ + bool bMoved = false; + matrix3x4_t localToWorld; + + GetAttachment( LookupAttachment( "eyes" ), localToWorld ); + + Vector vecGoalDir; + AngleVectors( m_vecGoalAngles, &vecGoalDir ); + + Vector vecGoalLocalDir; + VectorIRotate( vecGoalDir, localToWorld, vecGoalLocalDir ); + + if ( g_debug_turret_ceiling.GetBool() ) + { + Vector vecMuzzle, vecMuzzleDir; + QAngle vecMuzzleAng; + + GetAttachment( "eyes", vecMuzzle, vecMuzzleAng ); + AngleVectors( vecMuzzleAng, &vecMuzzleDir ); + + NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 ); + NDebugOverlay::Cross3D( vecMuzzle+(vecMuzzleDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 ); + NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecMuzzleDir*256), 255, 255, 0, false, 0.05 ); + + NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 ); + NDebugOverlay::Cross3D( vecMuzzle+(vecGoalDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 ); + NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecGoalDir*256), 255, 0, 0, false, 0.05 ); + } + + QAngle vecGoalLocalAngles; + VectorAngles( vecGoalLocalDir, vecGoalLocalAngles ); + + // Update pitch + float flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.1f * MaxYawSpeed() ) ); + + int iPose = LookupPoseParameter( CEILING_TURRET_BC_PITCH ); + SetPoseParameter( iPose, GetPoseParameter( iPose ) + ( flDiff / 1.5f ) ); + + if ( fabs( flDiff ) > 0.1f ) + { + bMoved = true; + } + + // Update yaw + flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1f * MaxYawSpeed() ) ); + + iPose = LookupPoseParameter( CEILING_TURRET_BC_YAW ); + SetPoseParameter( iPose, GetPoseParameter( iPose ) + ( flDiff / 1.5f ) ); + + if ( fabs( flDiff ) > 0.1f ) + { + bMoved = true; + } + + InvalidateBoneCache(); + + return bMoved; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEntity - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_CeilingTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) +{ + CBaseEntity *pHitEntity = NULL; + if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) ) + return true; + + // If we hit something that's okay to hit anyway, still fire + if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() ) + { + if (IRelationType(pHitEntity) == D_HT) + return true; + } + + if (ppBlocker) + { + *ppBlocker = pHitEntity; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Allows the turret to fire on targets if they're visible +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::ActiveThink( void ) +{ + //Allow descended classes a chance to do something before the think function + if ( PreThink( TURRET_ACTIVE ) ) + return; + + //Update our think time + SetNextThink( gpGlobals->curtime + 0.1f ); + + //If we've become inactive, go back to searching + if ( ( m_bActive == false ) || ( GetEnemy() == NULL ) ) + { + SetEnemy( NULL ); + SetLastSightTime(); + SetThink( &CNPC_CeilingTurret::SearchThink ); + m_vecGoalAngles = GetAbsAngles(); + return; + } + + //Get our shot positions + Vector vecMid = EyePosition(); + Vector vecMidEnemy = GetEnemy()->GetAbsOrigin(); + + //Store off our last seen location + UpdateEnemyMemory( GetEnemy(), vecMidEnemy ); + + //Look for our current enemy + bool bEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() ) && GetEnemy()->IsAlive(); + + //Calculate dir and dist to enemy + Vector vecDirToEnemy = vecMidEnemy - vecMid; + float flDistToEnemy = VectorNormalize( vecDirToEnemy ); + + //We want to look at the enemy's eyes so we don't jitter + Vector vecDirToEnemyEyes = GetEnemy()->WorldSpaceCenter() - vecMid; + VectorNormalize( vecDirToEnemyEyes ); + + QAngle vecAnglesToEnemy; + VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy ); + + //Draw debug info + if ( g_debug_turret_ceiling.GetBool() ) + { + NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); + NDebugOverlay::Cross3D( GetEnemy()->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); + NDebugOverlay::Line( vecMid, GetEnemy()->WorldSpaceCenter(), 0, 255, 0, false, 0.05 ); + + NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); + NDebugOverlay::Cross3D( vecMidEnemy, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); + NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f ); + } + + //Current enemy is not visible + if ( ( bEnemyVisible == false ) || ( flDistToEnemy > CEILING_TURRET_RANGE )) + { + if ( m_flLastSight ) + { + m_flLastSight = gpGlobals->curtime + 0.5f; + } + else if ( gpGlobals->curtime > m_flLastSight ) + { + // Should we look for a new target? + ClearEnemyMemory(); + SetEnemy( NULL ); + SetLastSightTime(); + SetThink( &CNPC_CeilingTurret::SearchThink ); + m_vecGoalAngles = GetAbsAngles(); + + SpinDown(); + + return; + } + + bEnemyVisible = false; + } + + Vector vecMuzzle, vecMuzzleDir; + QAngle vecMuzzleAng; + + GetAttachment( "eyes", vecMuzzle, vecMuzzleAng ); + AngleVectors( vecMuzzleAng, &vecMuzzleDir ); + + if ( m_flShotTime < gpGlobals->curtime ) + { + //Fire the gun + if ( DotProduct( vecDirToEnemy, vecMuzzleDir ) >= 0.9848 ) // 10 degree slop + { + if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) + { + SetActivity( (Activity) ACT_CEILING_TURRET_DRYFIRE ); + } + else + { + SetActivity( (Activity) ACT_CEILING_TURRET_FIRE ); + } + + //Fire the weapon + Shoot( vecMuzzle, vecMuzzleDir ); + } + } + else + { + SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); + } + + //If we can see our enemy, face it + if ( bEnemyVisible ) + { + m_vecGoalAngles.y = vecAnglesToEnemy.y; + m_vecGoalAngles.x = vecAnglesToEnemy.x; + } + + //Turn to face + UpdateFacing(); +} + +//----------------------------------------------------------------------------- +// Purpose: Target doesn't exist or has eluded us, so search for one +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::SearchThink( void ) +{ + //Allow descended classes a chance to do something before the think function + if ( PreThink( TURRET_SEARCHING ) ) + return; + + SetNextThink( gpGlobals->curtime + 0.05f ); + + SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); + + //If our enemy has died, pick a new enemy + if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) ) + { + SetEnemy( NULL ); + } + + //Acquire the target + if ( GetEnemy() == NULL ) + { + GetSenses()->Look( CEILING_TURRET_RANGE ); + CBaseEntity *pEnemy = BestEnemy(); + if ( pEnemy ) + { + SetEnemy( pEnemy ); + } + } + + //If we've found a target, spin up the barrel and start to attack + if ( GetEnemy() != NULL ) + { + //Give players a grace period + if ( GetEnemy()->IsPlayer() ) + { + m_flShotTime = gpGlobals->curtime + 0.5f; + } + else + { + m_flShotTime = gpGlobals->curtime + 0.1f; + } + + m_flLastSight = 0; + SetThink( &CNPC_CeilingTurret::ActiveThink ); + SetEyeState( TURRET_EYE_SEE_TARGET ); + + SpinUp(); + EmitSound( "NPC_CeilingTurret.Active" ); + return; + } + + //Are we out of time and need to retract? + if ( gpGlobals->curtime > m_flLastSight ) + { + //Before we retrace, make sure that we are spun down. + m_flLastSight = 0; + SetThink( &CNPC_CeilingTurret::Retire ); + return; + } + + //Display that we're scanning + m_vecGoalAngles.x = 15.0f; + m_vecGoalAngles.y = GetAbsAngles().y + ( sin( gpGlobals->curtime * 2.0f ) * 45.0f ); + + //Turn and ping + UpdateFacing(); + Ping(); +} + +//----------------------------------------------------------------------------- +// Purpose: Watch for a target to wander into our view +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::AutoSearchThink( void ) +{ + //Allow descended classes a chance to do something before the think function + if ( PreThink( TURRET_AUTO_SEARCHING ) ) + return; + + //Spread out our thinking + SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.2f, 0.4f ) ); + + //If the enemy is dead, find a new one + if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) ) + { + SetEnemy( NULL ); + } + + //Acquire Target + if ( GetEnemy() == NULL ) + { + GetSenses()->Look( CEILING_TURRET_RANGE ); + SetEnemy( BestEnemy() ); + } + + //Deploy if we've got an active target + if ( GetEnemy() != NULL ) + { + SetThink( &CNPC_CeilingTurret::Deploy ); + EmitSound( "NPC_CeilingTurret.Alert" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fire! +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy ) +{ + if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) + { + EmitSound( "NPC_FloorTurret.DryFire"); + EmitSound( "NPC_CeilingTurret.Activate" ); + + if ( RandomFloat( 0, 1 ) > 0.7 ) + { + m_flShotTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.5 ); + } + else + { + m_flShotTime = gpGlobals->curtime; + } + return; + } + + FireBulletsInfo_t info; + + if ( GetEnemy() != NULL ) + { + Vector vecDir = GetActualShootTrajectory( vecSrc ); + + info.m_vecSrc = vecSrc; + info.m_vecDirShooting = vecDir; + info.m_iTracerFreq = 1; + info.m_iShots = 1; + info.m_pAttacker = this; + info.m_vecSpread = VECTOR_CONE_PRECALCULATED; + info.m_flDistance = MAX_COORD_RANGE; + info.m_iAmmoType = m_iAmmoType; + } + else + { + // Just shoot where you're facing! + Vector vecMuzzle, vecMuzzleDir; + QAngle vecMuzzleAng; + + info.m_vecSrc = vecSrc; + info.m_vecDirShooting = vecDirToEnemy; + info.m_iTracerFreq = 1; + info.m_iShots = 1; + info.m_pAttacker = this; + info.m_vecSpread = GetAttackSpread( NULL, NULL ); + info.m_flDistance = MAX_COORD_RANGE; + info.m_iAmmoType = m_iAmmoType; + } + + FireBullets( info ); + EmitSound( "NPC_CeilingTurret.ShotSounds" ); + DoMuzzleFlash(); +} + +//----------------------------------------------------------------------------- +// Purpose: Allows a generic think function before the others are called +// Input : state - which state the turret is currently in +//----------------------------------------------------------------------------- +bool CNPC_CeilingTurret::PreThink( turretState_e state ) +{ + CheckPVSCondition(); + + //Animate + StudioFrameAdvance(); + + //Do not interrupt current think function + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the state of the glowing eye attached to the turret +// Input : state - state the eye should be in +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::SetEyeState( eyeState_t state ) +{ + //Must have a valid eye to affect + if ( m_pEyeGlow == NULL ) + return; + + //Set the state + switch( state ) + { + default: + case TURRET_EYE_SEE_TARGET: //Fade in and scale up + m_pEyeGlow->SetColor( 255, 0, 0 ); + m_pEyeGlow->SetBrightness( 164, 0.1f ); + m_pEyeGlow->SetScale( 0.4f, 0.1f ); + break; + + case TURRET_EYE_SEEKING_TARGET: //Ping-pongs + + //Toggle our state + m_bBlinkState = !m_bBlinkState; + m_pEyeGlow->SetColor( 255, 128, 0 ); + + if ( m_bBlinkState ) + { + //Fade up and scale up + m_pEyeGlow->SetScale( 0.25f, 0.1f ); + m_pEyeGlow->SetBrightness( 164, 0.1f ); + } + else + { + //Fade down and scale down + m_pEyeGlow->SetScale( 0.2f, 0.1f ); + m_pEyeGlow->SetBrightness( 64, 0.1f ); + } + + break; + + case TURRET_EYE_DORMANT: //Fade out and scale down + m_pEyeGlow->SetColor( 0, 255, 0 ); + m_pEyeGlow->SetScale( 0.1f, 0.5f ); + m_pEyeGlow->SetBrightness( 64, 0.5f ); + break; + + case TURRET_EYE_DEAD: //Fade out slowly + m_pEyeGlow->SetColor( 255, 0, 0 ); + m_pEyeGlow->SetScale( 0.1f, 3.0f ); + m_pEyeGlow->SetBrightness( 0, 3.0f ); + break; + + case TURRET_EYE_DISABLED: + m_pEyeGlow->SetColor( 0, 255, 0 ); + m_pEyeGlow->SetScale( 0.1f, 1.0f ); + m_pEyeGlow->SetBrightness( 0, 1.0f ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Make a pinging noise so the player knows where we are +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Ping( void ) +{ + //See if it's time to ping again + if ( m_flPingTime > gpGlobals->curtime ) + return; + + //Ping! + EmitSound( "NPC_CeilingTurret.Ping" ); + + SetEyeState( TURRET_EYE_SEEKING_TARGET ); + + m_flPingTime = gpGlobals->curtime + CEILING_TURRET_PING_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle the turret's state +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Toggle( void ) +{ + //Toggle the state + if ( m_bEnabled ) + { + Disable(); + } + else + { + Enable(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Enable the turret and deploy +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Enable( void ) +{ + m_bEnabled = true; + + // if the turret is flagged as an autoactivate turret, re-enable its ability open self. + if ( m_spawnflags & SF_CEILING_TURRET_AUTOACTIVATE ) + { + m_bAutoStart = true; + } + + SetThink( &CNPC_CeilingTurret::Deploy ); + SetNextThink( gpGlobals->curtime + 0.05f ); +} + +//----------------------------------------------------------------------------- +// Purpose: Retire the turret until enabled again +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Disable( void ) +{ + m_bEnabled = false; + m_bAutoStart = false; + + SetEnemy( NULL ); + SetThink( &CNPC_CeilingTurret::Retire ); + SetNextThink( gpGlobals->curtime + 0.1f ); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle the turret's state via input function +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::InputToggle( inputdata_t &inputdata ) +{ + Toggle(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::InputEnable( inputdata_t &inputdata ) +{ + Enable(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::InputDisable( inputdata_t &inputdata ) +{ + Disable(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::SpinUp( void ) +{ +} + +#define CEILING_TURRET_MIN_SPIN_DOWN 1.0f + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::SpinDown( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::DeathThink( void ) +{ + if ( PreThink( TURRET_DEAD ) ) + return; + + //Level out our angles + m_vecGoalAngles = GetAbsAngles(); + SetNextThink( gpGlobals->curtime ); + + if ( m_lifeState != LIFE_DEAD ) + { + m_lifeState = LIFE_DEAD; + + EmitSound( "NPC_CeilingTurret.Die" ); + + SetActivity( (Activity) ACT_CEILING_TURRET_CLOSE ); + } + + // lots of smoke + Vector pos; + CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos ); + + CBroadcastRecipientFilter filter; + + te->Smoke( filter, 0.0, &pos, g_sModelIndexSmoke, 2.5, 10 ); + + g_pEffects->Sparks( pos ); + + if ( IsActivityFinished() && ( UpdateFacing() == false ) ) + { + SetHeight( CEILING_TURRET_RETRACT_HEIGHT ); + + m_flPlaybackRate = 0; + SetThink( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : height - +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::SetHeight( float height ) +{ + Vector forward, right, up; + AngleVectors( GetLocalAngles(), &forward, &right, &up ); + + Vector mins = ( forward * -16.0f ) + ( right * -16.0f ); + Vector maxs = ( forward * 16.0f ) + ( right * 16.0f ) + ( up * -height ); + + if ( mins.x > maxs.x ) + { + swap( mins.x, maxs.x ); + } + + if ( mins.y > maxs.y ) + { + swap( mins.y, maxs.y ); + } + + if ( mins.z > maxs.z ) + { + swap( mins.z, maxs.z ); + } + + SetCollisionBounds( mins, maxs ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEnemy - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_CeilingTurret::CanBeAnEnemyOf( CBaseEntity *pEnemy ) +{ + // If we're out of ammo, make friendly companions ignore us + if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) + { + if ( pEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL ) + return false; + } + + return BaseClass::CanBeAnEnemyOf( pEnemy ); +} diff --git a/dlls/hl2_dll/npc_turret_floor.cpp b/dlls/hl2_dll/npc_turret_floor.cpp index 258f608f..fed42de0 100644 --- a/dlls/hl2_dll/npc_turret_floor.cpp +++ b/dlls/hl2_dll/npc_turret_floor.cpp @@ -1955,3 +1955,4 @@ AI_BEGIN_CUSTOM_NPC( npc_turret_floor, CNPC_FloorTurret ) DECLARE_INTERACTION( g_interactionTurretStillStanding ); AI_END_CUSTOM_NPC() + diff --git a/dlls/hl2_dll/npc_vortigaunt.cpp b/dlls/hl2_dll/npc_vortigaunt.cpp index 0a79acba..e2c878bb 100644 --- a/dlls/hl2_dll/npc_vortigaunt.cpp +++ b/dlls/hl2_dll/npc_vortigaunt.cpp @@ -830,7 +830,7 @@ CBaseEntity* CNPC_Vortigaunt::Kick( void ) //------------------------------------------------------------------------------ void CNPC_Vortigaunt::Claw( int iAttachment) { - CBaseEntity *pHurt = CheckTraceHullAttack( 40, Vector(-10,-10,-10), Vector(10,10,10),sk_vortigaunt_dmg_claw.GetFloat(), DMG_SLASH ); + CBaseEntity *pHurt = CheckTraceHullAttack( 40, Vector(-10,-10,-10), Vector(10,10,10),sk_vortigaunt_dmg_claw.GetInt(), DMG_SLASH ); if ( pHurt ) { pHurt->ViewPunch( QAngle(5,0,-18) ); @@ -1744,6 +1744,9 @@ int CNPC_Vortigaunt::SelectSchedule( void ) } break; + + default: + break; } return BaseClass::SelectSchedule(); diff --git a/dlls/hl2_dll/prop_combine_ball.cpp b/dlls/hl2_dll/prop_combine_ball.cpp index 3855947b..1d3b02d3 100644 --- a/dlls/hl2_dll/prop_combine_ball.cpp +++ b/dlls/hl2_dll/prop_combine_ball.cpp @@ -1068,7 +1068,7 @@ void CPropCombineBall::DoExplosion( ) if( hl2_episodic.GetBool() ) { - CSoundEnt::InsertSound( SOUND_COMBAT | SOUND_CONTEXT_EXPLOSION, WorldSpaceCenter(), 180.0f, 0.25, this ); + CSoundEnt::InsertSound( SOUND_COMBAT | SOUND_CONTEXT_EXPLOSION, WorldSpaceCenter(), 180, 0.25, this ); } // Turn us off and wait because we need our trails to finish up properly diff --git a/dlls/hl2_dll/prop_thumper.cpp b/dlls/hl2_dll/prop_thumper.cpp index 13f9f4f9..1ae16b68 100644 --- a/dlls/hl2_dll/prop_thumper.cpp +++ b/dlls/hl2_dll/prop_thumper.cpp @@ -185,7 +185,7 @@ void CPropThumper::Thump ( void ) } EmitSound( "coast.thumper_dust" ); - CSoundEnt::InsertSound ( SOUND_THUMPER, GetAbsOrigin(), THUMPER_RADIUS * m_flPlaybackRate, THUMPER_SOUND_DURATION, this ); + CSoundEnt::InsertSound ( SOUND_THUMPER, GetAbsOrigin(), static_cast(THUMPER_RADIUS * m_flPlaybackRate), THUMPER_SOUND_DURATION, this ); if ( m_flPlaybackRate < 0.7f ) return; diff --git a/dlls/hl2_dll/proto_sniper.cpp b/dlls/hl2_dll/proto_sniper.cpp index 1dc0abe0..696de887 100644 --- a/dlls/hl2_dll/proto_sniper.cpp +++ b/dlls/hl2_dll/proto_sniper.cpp @@ -641,7 +641,7 @@ void CProtoSniper::LaserOn( const Vector &vecTarget, const Vector &vecDeviance ) m_pBeam->SetScrollRate( 0 ); m_pBeam->SetFadeLength( 0 ); m_pBeam->SetHaloTexture( sHaloSprite ); - m_pBeam->SetHaloScale( 4.0f ); + m_pBeam->SetHaloScale( 4 ); m_vecPaintStart = vecInitialAim; @@ -1100,7 +1100,7 @@ int CProtoSniper::IRelationPriority( CBaseEntity *pTarget ) if ( flDistance <= SNIPER_PROTECTION_MINDIST ) { float flBonus = (1.0 - (flDistance / SNIPER_PROTECTION_MINDIST)) * SNIPER_PROTECTION_PRIORITYCAP; - priority += flBonus; + priority += static_cast(flBonus); if ( m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) { diff --git a/dlls/hl2_dll/rotorwash.cpp b/dlls/hl2_dll/rotorwash.cpp index 561a331f..c8312b58 100644 --- a/dlls/hl2_dll/rotorwash.cpp +++ b/dlls/hl2_dll/rotorwash.cpp @@ -1,96 +1,96 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" -#include "rotorwash.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// ============================================== -// Rotorwash entity -// ============================================== - -class CRotorWashEmitter : public CBaseEntity -{ -public: - DECLARE_CLASS( CRotorWashEmitter, CBaseEntity ); - DECLARE_SERVERCLASS(); - DECLARE_DATADESC(); - - void SetAltitude( float flAltitude ) { m_flAltitude = flAltitude; } - void SetEmit( bool state ) { m_bEmit = state; } - void Spawn ( void ); - int ShouldTransmit( const CCheckTransmitInfo *pInfo ); - int UpdateTransmitState( void ); - -protected: - - CNetworkVar( bool, m_bEmit ); - CNetworkVar( float, m_flAltitude ); -}; - -IMPLEMENT_SERVERCLASS_ST( CRotorWashEmitter, DT_RotorWashEmitter ) - SendPropFloat(SENDINFO(m_flAltitude), -1, SPROP_NOSCALE ), -END_SEND_TABLE() - -LINK_ENTITY_TO_CLASS( env_rotorwash_emitter, CRotorWashEmitter ); - -BEGIN_DATADESC( CRotorWashEmitter ) - DEFINE_FIELD( m_bEmit, FIELD_BOOLEAN ), - DEFINE_KEYFIELD( m_flAltitude, FIELD_FLOAT, "altitude" ), -END_DATADESC() - -void CRotorWashEmitter::Spawn( void ) -{ - BaseClass::Spawn(); - SetEmit( false ); -} - -int CRotorWashEmitter::ShouldTransmit( const CCheckTransmitInfo *pInfo ) -{ - if ( GetParent() ) - { - return GetParent()->ShouldTransmit( pInfo ); - } - - return FL_EDICT_PVSCHECK; -} - -int CRotorWashEmitter::UpdateTransmitState( void ) -{ - if ( GetParent() ) - { - return SetTransmitState( FL_EDICT_FULLCHECK ); - } - - return SetTransmitState( FL_EDICT_PVSCHECK ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &localOrigin - -// &localAngles - -// *pOwner - -// flAltitude - -// Output : CBaseEntity -//----------------------------------------------------------------------------- -CBaseEntity *CreateRotorWashEmitter( const Vector &localOrigin, const QAngle &localAngles, CBaseEntity *pOwner, float flAltitude ) -{ - CRotorWashEmitter *pEmitter = (CRotorWashEmitter *) CreateEntityByName( "env_rotorwash_emitter" ); - - if ( pEmitter == NULL ) - return NULL; - - pEmitter->SetAbsOrigin( localOrigin ); - pEmitter->SetAbsAngles( localAngles ); - pEmitter->FollowEntity( pOwner ); - - pEmitter->SetAltitude( flAltitude ); - pEmitter->SetEmit( false ); - - return pEmitter; -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "rotorwash.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ============================================== +// Rotorwash entity +// ============================================== + +class CRotorWashEmitter : public CBaseEntity +{ +public: + DECLARE_CLASS( CRotorWashEmitter, CBaseEntity ); + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + void SetAltitude( float flAltitude ) { m_flAltitude = flAltitude; } + void SetEmit( bool state ) { m_bEmit = state; } + void Spawn ( void ); + int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + int UpdateTransmitState( void ); + +protected: + + CNetworkVar( bool, m_bEmit ); + CNetworkVar( float, m_flAltitude ); +}; + +IMPLEMENT_SERVERCLASS_ST( CRotorWashEmitter, DT_RotorWashEmitter ) + SendPropFloat(SENDINFO(m_flAltitude), -1, SPROP_NOSCALE ), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( env_rotorwash_emitter, CRotorWashEmitter ); + +BEGIN_DATADESC( CRotorWashEmitter ) + DEFINE_FIELD( m_bEmit, FIELD_BOOLEAN ), + DEFINE_KEYFIELD( m_flAltitude, FIELD_FLOAT, "altitude" ), +END_DATADESC() + +void CRotorWashEmitter::Spawn( void ) +{ + BaseClass::Spawn(); + SetEmit( false ); +} + +int CRotorWashEmitter::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + if ( GetParent() ) + { + return GetParent()->ShouldTransmit( pInfo ); + } + + return FL_EDICT_PVSCHECK; +} + +int CRotorWashEmitter::UpdateTransmitState( void ) +{ + if ( GetParent() ) + { + return SetTransmitState( FL_EDICT_FULLCHECK ); + } + + return SetTransmitState( FL_EDICT_PVSCHECK ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &localOrigin - +// &localAngles - +// *pOwner - +// flAltitude - +// Output : CBaseEntity +//----------------------------------------------------------------------------- +CBaseEntity *CreateRotorWashEmitter( const Vector &localOrigin, const QAngle &localAngles, CBaseEntity *pOwner, float flAltitude ) +{ + CRotorWashEmitter *pEmitter = (CRotorWashEmitter *) CreateEntityByName( "env_rotorwash_emitter" ); + + if ( pEmitter == NULL ) + return NULL; + + pEmitter->SetAbsOrigin( localOrigin ); + pEmitter->SetAbsAngles( localAngles ); + pEmitter->FollowEntity( pOwner ); + + pEmitter->SetAltitude( flAltitude ); + pEmitter->SetEmit( false ); + + return pEmitter; +} diff --git a/dlls/hl2_dll/vehicle_airboat.cpp b/dlls/hl2_dll/vehicle_airboat.cpp index a6bae7a7..78a42a68 100644 --- a/dlls/hl2_dll/vehicle_airboat.cpp +++ b/dlls/hl2_dll/vehicle_airboat.cpp @@ -716,7 +716,7 @@ void CPropAirboat::ExitVehicle( int nRole ) ep.m_pSoundName = "Airboat_engine_stop"; ep.m_flVolume = controller.SoundGetVolume( m_pEngineSound ); ep.m_SoundLevel = SNDLVL_NORM; - ep.m_nPitch = controller.SoundGetPitch( m_pEngineSound ); + ep.m_nPitch = static_cast(controller.SoundGetPitch( m_pEngineSound )); EmitSound( filter, entindex(), ep ); @@ -910,7 +910,9 @@ void CPropAirboat::VPhysicsFriction( IPhysicsObject *pObject, float energy, int //----------------------------------------------------------------------------- // This fixes an optimizer bug that was causing targetYaw and targetPitch to // always be reported as clamped, thus disabling the gun. Ack! +#ifdef _MSC_VER #pragma optimize("", off) +#endif void CPropAirboat::AimGunAt( const Vector &aimPos, float flInterval ) { matrix3x4_t gunMatrix; @@ -960,8 +962,9 @@ void CPropAirboat::AimGunAt( const Vector &aimPos, float flInterval ) m_aimPitch = GetPoseParameter( AIRBOAT_GUN_PITCH ); m_aimYaw = GetPoseParameter( AIRBOAT_GUN_YAW ); } +#ifdef _MSC_VER #pragma optimize("", on) - +#endif //----------------------------------------------------------------------------- // Removes the ammo... @@ -1838,8 +1841,8 @@ void CPropAirboat::CreateDangerSounds( void ) // 0.7 seconds ahead vecSpot = vecStart + vecDir * (speed * 0.7f); - CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, radius, soundDuration, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); - CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, radius, soundDuration, this, SOUNDENT_CHANNEL_REPEATED_PHYSICS_DANGER ); + CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, static_cast(radius), soundDuration, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, static_cast(radius), soundDuration, this, SOUNDENT_CHANNEL_REPEATED_PHYSICS_DANGER ); //NDebugOverlay::Box(vecSpot, Vector(-radius,-radius,-radius),Vector(radius,radius,radius), 255, 0, 255, 0, soundDuration); #if 0 @@ -2057,7 +2060,7 @@ float CPropAirboat::CalculatePhysicsStressDamage( vphysics_objectstress_t *pStre //----------------------------------------------------------------------------- void CPropAirboat::ApplyStressDamage( IPhysicsObject *pPhysics ) { - vphysics_objectstress_t stressOut; + vphysics_objectstress_t stressOut = {0.0f, 0.0f, false, false}; float damage = CalculatePhysicsStressDamage( &stressOut, pPhysics ); if ( ( damage > 0 ) && ( m_hPlayer != NULL ) ) { diff --git a/dlls/hl2_dll/vehicle_apc.cpp b/dlls/hl2_dll/vehicle_apc.cpp index d553c8e8..4aadee66 100644 --- a/dlls/hl2_dll/vehicle_apc.cpp +++ b/dlls/hl2_dll/vehicle_apc.cpp @@ -293,7 +293,7 @@ Vector CPropAPC::BodyTarget( const Vector &posSrc, bool bNoisy ) void CPropAPC::AddSmokeTrail( const Vector &vecPos ) { // Start this trail out with a bang! - ExplosionCreate( vecPos, vec3_angle, this, 1000, 500.0f, SF_ENVEXPLOSION_NODAMAGE | + ExplosionCreate( vecPos, vec3_angle, this, 1000, 500, SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_NOFIREBALLSMOKE, 0 ); UTIL_ScreenShake( vecPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START ); @@ -351,7 +351,7 @@ void CPropAPC::AddSmokeTrail( const Vector &vecPos ) //------------------------------------------------------------------------------ void CPropAPC::ExplodeAndThrowChunk( const Vector &vecExplosionPos ) { - ExplosionCreate( vecExplosionPos, vec3_angle, this, 1000, 500.0f, + ExplosionCreate( vecExplosionPos, vec3_angle, this, 1000, 500, SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_NOFIREBALLSMOKE, 0 ); UTIL_ScreenShake( vecExplosionPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START ); diff --git a/dlls/hl2_dll/vehicle_jeep.cpp b/dlls/hl2_dll/vehicle_jeep.cpp index 26e8d7fb..ef40031f 100644 --- a/dlls/hl2_dll/vehicle_jeep.cpp +++ b/dlls/hl2_dll/vehicle_jeep.cpp @@ -1387,8 +1387,8 @@ void CPropJeep::CreateDangerSounds( void ) const float radius = speed * 0.4; // 0.3 seconds ahead of the jeep vecSpot = vecStart + vecDir * (speed * 0.3f); - CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, radius, soundDuration, this, 0 ); - CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, radius, soundDuration, this, 1 ); + CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, static_cast(radius), soundDuration, this, 0 ); + CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, static_cast(radius), soundDuration, this, 1 ); //NDebugOverlay::Box(vecSpot, Vector(-radius,-radius,-radius),Vector(radius,radius,radius), 255, 0, 255, 0, soundDuration); #if 0 diff --git a/dlls/hl2_dll/vehicle_prisoner_pod.cpp b/dlls/hl2_dll/vehicle_prisoner_pod.cpp index 44faaf40..d54b36f2 100644 --- a/dlls/hl2_dll/vehicle_prisoner_pod.cpp +++ b/dlls/hl2_dll/vehicle_prisoner_pod.cpp @@ -41,10 +41,10 @@ class CPropVehiclePrisonerPod; // Pod bones that have physics followers -static const char *pPodFollowerBoneNames[] = +/*static const char *pPodFollowerBoneNames[] = { "base", -}; +};*/ //----------------------------------------------------------------------------- diff --git a/dlls/hl2_dll/weapon_357.cpp b/dlls/hl2_dll/weapon_357.cpp index c74665ea..d2152995 100644 --- a/dlls/hl2_dll/weapon_357.cpp +++ b/dlls/hl2_dll/weapon_357.cpp @@ -6,10 +6,10 @@ //=============================================================================// #include "cbase.h" -#include "NPCEvent.h" +#include "npcevent.h" #include "basehlcombatweapon.h" #include "basecombatcharacter.h" -#include "AI_BaseNPC.h" +#include "ai_basenpc.h" #include "player.h" #include "gamerules.h" #include "in_buttons.h" diff --git a/dlls/hl2_dll/weapon_ar2.cpp b/dlls/hl2_dll/weapon_ar2.cpp index 1281649c..c749d9ba 100644 --- a/dlls/hl2_dll/weapon_ar2.cpp +++ b/dlls/hl2_dll/weapon_ar2.cpp @@ -7,16 +7,16 @@ #include "cbase.h" #include "basecombatweapon.h" -#include "NPCevent.h" +#include "npcevent.h" #include "basecombatcharacter.h" -#include "AI_BaseNPC.h" +#include "ai_basenpc.h" #include "player.h" #include "weapon_ar2.h" #include "grenade_ar2.h" #include "gamerules.h" #include "game.h" #include "in_buttons.h" -#include "AI_Memory.h" +#include "ai_memory.h" #include "soundent.h" #include "hl2_player.h" #include "EntityFlame.h" @@ -342,7 +342,7 @@ void CWeaponAR2::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool bUs WeaponSoundRealtime( SINGLE_NPC ); - CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), static_cast(SOUNDENT_VOLUME_MACHINEGUN), 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2 ); diff --git a/dlls/hl2_dll/weapon_brickbat.cpp b/dlls/hl2_dll/weapon_brickbat.cpp index 88c01238..bd59a371 100644 --- a/dlls/hl2_dll/weapon_brickbat.cpp +++ b/dlls/hl2_dll/weapon_brickbat.cpp @@ -8,11 +8,11 @@ //=============================================================================// #include "cbase.h" -#include "NPCEvent.h" +#include "npcvent.h" #include "basehlcombatweapon.h" #include "basecombatcharacter.h" -#include "AI_BaseNPC.h" -#include "AI_Memory.h" +#include "ai_basenpc.h" +#include "ai_memory.h" #include "player.h" #include "gamerules.h" // For g_pGameRules #include "weapon_brickbat.h" diff --git a/dlls/hl2_dll/weapon_bugbait.cpp b/dlls/hl2_dll/weapon_bugbait.cpp index 4449b5ee..2b8d3ade 100644 --- a/dlls/hl2_dll/weapon_bugbait.cpp +++ b/dlls/hl2_dll/weapon_bugbait.cpp @@ -165,7 +165,7 @@ void CWeaponBugBait::Drop( const Vector &vecVelocity ) pSporeExplosion->m_flSpawnRate = 16.0f; pSporeExplosion->m_flParticleLifetime = 0.5f; - pSporeExplosion->SetRenderColor( 0.0f, 0.5f, 0.25f, 0.15f ); + pSporeExplosion->SetRenderColor( 0, 1, 0, 0 ); pSporeExplosion->m_flStartSize = 32; pSporeExplosion->m_flEndSize = 48; diff --git a/dlls/hl2_dll/weapon_cguard.cpp b/dlls/hl2_dll/weapon_cguard.cpp index bb7c4782..3e0d6fc0 100644 --- a/dlls/hl2_dll/weapon_cguard.cpp +++ b/dlls/hl2_dll/weapon_cguard.cpp @@ -123,8 +123,8 @@ public: TE_ConcussiveExplosion( filter, 0.0, &GetAbsOrigin(),//position 1.0f, //scale - 256*magnitude, //radius - 175*magnitude, //magnitude + static_cast(256 * magnitude), //radius + static_cast(175 * magnitude), //magnitude &vecForward ); //normal int colorRamp = random->RandomInt( 128, 255 ); diff --git a/dlls/hl2_dll/weapon_citizenpackage.cpp b/dlls/hl2_dll/weapon_citizenpackage.cpp index 1e05f5e6..3ee31df6 100644 --- a/dlls/hl2_dll/weapon_citizenpackage.cpp +++ b/dlls/hl2_dll/weapon_citizenpackage.cpp @@ -72,3 +72,4 @@ acttable_t CWeaponCitizenSuitcase::m_acttable[] = { ACT_WALK, ACT_WALK_SUITCASE, false }, }; IMPLEMENT_ACTTABLE(CWeaponCitizenSuitcase); + diff --git a/dlls/hl2_dll/weapon_citizenpackage.h b/dlls/hl2_dll/weapon_citizenpackage.h index 51a24418..44231a1f 100644 --- a/dlls/hl2_dll/weapon_citizenpackage.h +++ b/dlls/hl2_dll/weapon_citizenpackage.h @@ -1,31 +1,31 @@ -//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= -// -// Purpose: -// -//============================================================================= -#ifndef WEAPON_CITIZENPACKAGE_H -#define WEAPON_CITIZENPACKAGE_H -#ifdef _WIN32 -#pragma once -#endif - -#include "basehlcombatweapon.h" - -//============================================================================= -// -// Weapon - Citizen Package Class -// -class CWeaponCitizenPackage : public CBaseHLCombatWeapon -{ - DECLARE_CLASS( CWeaponCitizenPackage, CBaseHLCombatWeapon ); -public: - - DECLARE_SERVERCLASS(); - DECLARE_DATADESC(); - DECLARE_ACTTABLE(); - - void ItemPostFrame( void ); - void Drop( const Vector &vecVelocity ); -}; - -#endif // WEAPON_CITIZENPACKAGE_H \ No newline at end of file +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= +#ifndef WEAPON_CITIZENPACKAGE_H +#define WEAPON_CITIZENPACKAGE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basehlcombatweapon.h" + +//============================================================================= +// +// Weapon - Citizen Package Class +// +class CWeaponCitizenPackage : public CBaseHLCombatWeapon +{ + DECLARE_CLASS( CWeaponCitizenPackage, CBaseHLCombatWeapon ); +public: + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + DECLARE_ACTTABLE(); + + void ItemPostFrame( void ); + void Drop( const Vector &vecVelocity ); +}; + +#endif // WEAPON_CITIZENPACKAGE_H diff --git a/dlls/hl2_dll/weapon_crossbow.cpp b/dlls/hl2_dll/weapon_crossbow.cpp index 8d02608f..10ffb204 100644 --- a/dlls/hl2_dll/weapon_crossbow.cpp +++ b/dlls/hl2_dll/weapon_crossbow.cpp @@ -5,10 +5,10 @@ //=============================================================================// #include "cbase.h" -#include "NPCEvent.h" +#include "npcevent.h" #include "basehlcombatweapon_shared.h" #include "basecombatcharacter.h" -#include "AI_BaseNPC.h" +#include "ai_basenpc.h" #include "player.h" #include "gamerules.h" #include "in_buttons.h" @@ -18,8 +18,8 @@ #include "engine/IEngineSound.h" #include "IEffects.h" #include "te_effect_dispatch.h" -#include "sprite.h" -#include "spritetrail.h" +#include "Sprite.h" +#include "SpriteTrail.h" #include "beam_shared.h" #include "rumble_shared.h" @@ -370,7 +370,7 @@ void CCrossbowBolt::BubbleThink( void ) SetNextThink( gpGlobals->curtime + 0.1f ); // Make danger sounds out in front of me, to scare snipers back into their hole - CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, GetAbsOrigin() + GetAbsVelocity() * 0.2, 120.0f, 0.5f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, GetAbsOrigin() + GetAbsVelocity() * 0.2, 120, 0.5f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); if ( GetWaterLevel() == 0 ) return; diff --git a/dlls/hl2_dll/weapon_crowbar.cpp b/dlls/hl2_dll/weapon_crowbar.cpp index a999f2ce..7bd688b7 100644 --- a/dlls/hl2_dll/weapon_crowbar.cpp +++ b/dlls/hl2_dll/weapon_crowbar.cpp @@ -165,7 +165,7 @@ void CWeaponCrowbar::HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCh Vector vecEnd; VectorMA( pOperator->Weapon_ShootPosition(), 50, vecDirection, vecEnd ); CBaseEntity *pHurt = pOperator->CheckTraceHullAttack( pOperator->Weapon_ShootPosition(), vecEnd, - Vector(-16,-16,-16), Vector(36,36,36), sk_npc_dmg_crowbar.GetFloat(), DMG_CLUB, 0.75 ); + Vector(-16,-16,-16), Vector(36,36,36), sk_npc_dmg_crowbar.GetInt(), DMG_CLUB, 0.75 ); // did I hit someone? if ( pHurt ) diff --git a/dlls/hl2_dll/weapon_physcannon.cpp b/dlls/hl2_dll/weapon_physcannon.cpp index ba9ad0e2..2f2f22e2 100644 --- a/dlls/hl2_dll/weapon_physcannon.cpp +++ b/dlls/hl2_dll/weapon_physcannon.cpp @@ -1,4468 +1,4468 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Physics cannon -// -//=============================================================================// - -#include "cbase.h" -#include "player.h" -#include "gamerules.h" -#include "soundenvelope.h" -#include "engine/IEngineSound.h" -#include "physics.h" -#include "in_buttons.h" -#include "soundent.h" -#include "IEffects.h" -#include "ndebugoverlay.h" -#include "shake.h" -#include "hl2_player.h" -#include "beam_shared.h" -#include "sprite.h" -#include "util.h" -#include "weapon_physcannon.h" -#include "physics_saverestore.h" -#include "ai_basenpc.h" -#include "player_pickup.h" -#include "physics_prop_ragdoll.h" -#include "globalstate.h" -#include "props.h" -#include "movevars_shared.h" -#include "basehlcombatweapon.h" -#include "te_effect_dispatch.h" -#include "vphysics/friction.h" -#include "saverestore_utlvector.h" -#include "prop_combine_ball.h" -#include "physobj.h" -#include "hl2_gamerules.h" -#include "citadel_effects_shared.h" -#include "eventqueue.h" -#include "model_types.h" -#include "ai_interactions.h" -#include "rumble_shared.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -static const char *s_pWaitForUpgradeContext = "WaitForUpgrade"; - -ConVar g_debug_physcannon( "g_debug_physcannon", "0" ); - -ConVar physcannon_minforce( "physcannon_minforce", "700" ); -ConVar physcannon_maxforce( "physcannon_maxforce", "1500" ); -ConVar physcannon_maxmass( "physcannon_maxmass", "250" ); -ConVar physcannon_tracelength( "physcannon_tracelength", "250" ); -ConVar physcannon_mega_tracelength( "physcannon_mega_tracelength", "850" ); -ConVar physcannon_chargetime("physcannon_chargetime", "2" ); -ConVar physcannon_pullforce( "physcannon_pullforce", "4000" ); -ConVar physcannon_mega_pullforce( "physcannon_mega_pullforce", "8000" ); -ConVar physcannon_cone( "physcannon_cone", "0.97" ); -ConVar physcannon_ball_cone( "physcannon_ball_cone", "0.997" ); -ConVar physcannon_punt_cone( "physcannon_punt_cone", "0.997" ); -ConVar player_throwforce( "player_throwforce", "1000" ); - -extern ConVar hl2_normspeed; -extern ConVar hl2_walkspeed; - -#define PHYSCANNON_BEAM_SPRITE "sprites/orangelight1.vmt" -#define PHYSCANNON_GLOW_SPRITE "sprites/glow04_noz.vmt" -#define PHYSCANNON_ENDCAP_SPRITE "sprites/orangeflare1.vmt" -#define PHYSCANNON_CENTER_GLOW "sprites/orangecore1.vmt" -#define PHYSCANNON_BLAST_SPRITE "sprites/orangecore2.vmt" - -#define MEGACANNON_BEAM_SPRITE "sprites/lgtning_noz.vmt" -#define MEGACANNON_GLOW_SPRITE "sprites/blueflare1_noz.vmt" -#define MEGACANNON_ENDCAP_SPRITE "sprites/blueflare1_noz.vmt" -#define MEGACANNON_CENTER_GLOW "effects/fluttercore.vmt" -#define MEGACANNON_BLAST_SPRITE "effects/fluttercore.vmt" - -#define MEGACANNON_RAGDOLL_BOOGIE_SPRITE "sprites/lgtning_noz.vmt" - -#define MEGACANNON_MODEL "models/weapons/v_superphyscannon.mdl" -#define MEGACANNON_SKIN 1 - -// ------------------------------------------------------------------------- -// Physcannon trace filter to handle special cases -// ------------------------------------------------------------------------- - -class CTraceFilterPhyscannon : public CTraceFilterSimple -{ -public: - DECLARE_CLASS( CTraceFilterPhyscannon, CTraceFilterSimple ); - - CTraceFilterPhyscannon( const IHandleEntity *passentity, int collisionGroup ) - : CTraceFilterSimple( NULL, collisionGroup ), m_pTraceOwner( passentity ) { } - - // For this test, we only test against entities (then world brushes afterwards) - virtual TraceType_t GetTraceType() const { return TRACE_ENTITIES_ONLY; } - - bool HasContentsGrate( CBaseEntity *pEntity ) - { - // FIXME: Move this into the GetModelContents() function in base entity - - // Find the contents based on the model type - int nModelType = modelinfo->GetModelType( pEntity->GetModel() ); - if ( nModelType == mod_studio ) - { - CBaseAnimating *pAnim = dynamic_cast(pEntity); - if ( pAnim != NULL ) - { - CStudioHdr *pStudioHdr = pAnim->GetModelPtr(); - if ( pStudioHdr != NULL && (pStudioHdr->contents() & CONTENTS_GRATE) ) - return true; - } - } - else if ( nModelType == mod_brush ) - { - // Brushes poll their contents differently - int contents = modelinfo->GetModelContents( pEntity->GetModelIndex() ); - if ( contents & CONTENTS_GRATE ) - return true; - } - - return false; - } - - virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) - { - // Only skip ourselves (not things we own) - if ( pHandleEntity == m_pTraceOwner ) - return false; - - // Get the entity referenced by this handle - CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); - if ( pEntity == NULL ) - return false; - - // Handle grate entities differently - if ( HasContentsGrate( pEntity ) ) - { - // See if it's a grabbable physics prop - CPhysicsProp *pPhysProp = dynamic_cast(pEntity); - if ( pPhysProp != NULL ) - return pPhysProp->CanBePickedUpByPhyscannon(); - - // See if it's a grabbable physics prop - if ( FClassnameIs( pEntity, "prop_physics" ) ) - { - CPhysicsProp *pPhysProp = dynamic_cast(pEntity); - if ( pPhysProp != NULL ) - return pPhysProp->CanBePickedUpByPhyscannon(); - - // Somehow had a classname that didn't match the class! - Assert(0); - } - else if ( FClassnameIs( pEntity, "func_physbox" ) ) - { - // Must be a moveable physbox - IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject(); - if ( pPhysObj != NULL && pPhysObj->IsMoveable() ) - return true; - } - - // Don't bother with any other sort of grated entity - return false; - } - - // Use the default rules - return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); - } - -protected: - const IHandleEntity *m_pTraceOwner; -}; - -// We want to test against brushes alone -class CTraceFilterOnlyBrushes : public CTraceFilterSimple -{ -public: - DECLARE_CLASS( CTraceFilterOnlyBrushes, CTraceFilterSimple ); - CTraceFilterOnlyBrushes( int collisionGroup ) : CTraceFilterSimple( NULL, collisionGroup ) {} - virtual TraceType_t GetTraceType() const { return TRACE_WORLD_ONLY; } -}; - -//----------------------------------------------------------------------------- -// this will hit skip the pass entity, but not anything it owns -// (lets player grab own grenades) -class CTraceFilterNoOwnerTest : public CTraceFilterSimple -{ -public: - DECLARE_CLASS( CTraceFilterNoOwnerTest, CTraceFilterSimple ); - - CTraceFilterNoOwnerTest( const IHandleEntity *passentity, int collisionGroup ) - : CTraceFilterSimple( NULL, collisionGroup ), m_pPassNotOwner(passentity) - { - } - - virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) - { - if ( pHandleEntity != m_pPassNotOwner ) - return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); - - return false; - } - -protected: - const IHandleEntity *m_pPassNotOwner; -}; - -//----------------------------------------------------------------------------- -// Purpose: Trace a line the special physcannon way! -//----------------------------------------------------------------------------- -void UTIL_PhyscannonTraceLine( const Vector &vecAbsStart, const Vector &vecAbsEnd, CBaseEntity *pTraceOwner, trace_t *pTrace ) -{ - // Default to HL2 vanilla - if ( hl2_episodic.GetBool() == false ) - { - CTraceFilterNoOwnerTest filter( pTraceOwner, COLLISION_GROUP_NONE ); - UTIL_TraceLine( vecAbsStart, vecAbsEnd, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); - return; - } - - // First, trace against entities - CTraceFilterPhyscannon filter( pTraceOwner, COLLISION_GROUP_NONE ); - UTIL_TraceLine( vecAbsStart, vecAbsEnd, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); - - // If we've hit something, test again to make sure no brushes block us - if ( pTrace->m_pEnt != NULL ) - { - trace_t testTrace; - CTraceFilterOnlyBrushes brushFilter( COLLISION_GROUP_NONE ); - UTIL_TraceLine( pTrace->startpos, pTrace->endpos, MASK_SHOT, &brushFilter, &testTrace ); - - // If we hit a brush, replace the trace with that result - if ( testTrace.fraction < 1.0f || testTrace.startsolid || testTrace.allsolid ) - { - *pTrace = testTrace; - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Trace a hull for the physcannon -//----------------------------------------------------------------------------- -void UTIL_PhyscannonTraceHull( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &vecAbsMins, const Vector &vecAbsMaxs, CBaseEntity *pTraceOwner, trace_t *pTrace ) -{ - // Default to HL2 vanilla - if ( hl2_episodic.GetBool() == false ) - { - CTraceFilterNoOwnerTest filter( pTraceOwner, COLLISION_GROUP_NONE ); - UTIL_TraceHull( vecAbsStart, vecAbsEnd, vecAbsMins, vecAbsMaxs, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); - return; - } - - // First, trace against entities - CTraceFilterPhyscannon filter( pTraceOwner, COLLISION_GROUP_NONE ); - UTIL_TraceHull( vecAbsStart, vecAbsEnd, vecAbsMins, vecAbsMaxs, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); - - // If we've hit something, test again to make sure no brushes block us - if ( pTrace->m_pEnt != NULL ) - { - trace_t testTrace; - CTraceFilterOnlyBrushes brushFilter( COLLISION_GROUP_NONE ); - UTIL_TraceHull( pTrace->startpos, pTrace->endpos, vecAbsMins, vecAbsMaxs, MASK_SHOT, &brushFilter, &testTrace ); - - // If we hit a brush, replace the trace with that result - if ( testTrace.fraction < 1.0f || testTrace.startsolid || testTrace.allsolid ) - { - *pTrace = testTrace; - } - } -} - -static void MatrixOrthogonalize( matrix3x4_t &matrix, int column ) -{ - Vector columns[3]; - int i; - - for ( i = 0; i < 3; i++ ) - { - MatrixGetColumn( matrix, i, columns[i] ); - } - - int index0 = column; - int index1 = (column+1)%3; - int index2 = (column+2)%3; - - columns[index2] = CrossProduct( columns[index0], columns[index1] ); - columns[index1] = CrossProduct( columns[index2], columns[index0] ); - VectorNormalize( columns[index2] ); - VectorNormalize( columns[index1] ); - MatrixSetColumn( columns[index1], index1, matrix ); - MatrixSetColumn( columns[index2], index2, matrix ); -} - -#define SIGN(x) ( (x) < 0 ? -1 : 1 ) - -static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle ) -{ - matrix3x4_t alignMatrix; - AngleMatrix( angles, alignMatrix ); - - // NOTE: Must align z first - for ( int j = 3; --j >= 0; ) - { - Vector vec; - MatrixGetColumn( alignMatrix, j, vec ); - for ( int i = 0; i < 3; i++ ) - { - if ( fabs(vec[i]) > cosineAlignAngle ) - { - vec[i] = SIGN(vec[i]); - vec[(i+1)%3] = 0; - vec[(i+2)%3] = 0; - MatrixSetColumn( vec, j, alignMatrix ); - MatrixOrthogonalize( alignMatrix, j ); - break; - } - } - } - - QAngle out; - MatrixAngles( alignMatrix, out ); - return out; -} - - -static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr ) -{ - physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr ); - - if ( ptr->DidHit() ) - { - ptr->endpos = start * (1-ptr->fraction) + end * ptr->fraction; - ptr->startpos = start; - ptr->plane.dist = -ptr->plane.dist; - ptr->plane.normal *= -1; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Finds the nearest ragdoll sub-piece to a location and returns it -// Input : *pTarget - entity that is the potential ragdoll -// &position - position we're testing against -// Output : IPhysicsObject - sub-object (if any) -//----------------------------------------------------------------------------- -IPhysicsObject *GetRagdollChildAtPosition( CBaseEntity *pTarget, const Vector &position ) -{ - // Check for a ragdoll - if ( dynamic_cast( pTarget ) == NULL ) - return NULL; - - // Get the root - IPhysicsObject *pList[32]; - int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE( pList ) ); - - IPhysicsObject *pBestChild = NULL; - float flBestDist = 99999999.0f; - float flDist; - Vector vPos; - - // Find the nearest child to where we're looking - for ( int i = 0; i < count; i++ ) - { - pList[i]->GetPosition( &vPos, NULL ); - - flDist = ( position - vPos ).LengthSqr(); - - if ( flDist < flBestDist ) - { - pBestChild = pList[i]; - flBestDist = flDist; - } - } - - // Make this our base now - pTarget->VPhysicsSwapObject( pBestChild ); - - return pTarget->VPhysicsGetObject(); -} - -//----------------------------------------------------------------------------- -// Purpose: Computes a local matrix for the player clamped to valid carry ranges -//----------------------------------------------------------------------------- -// when looking level, hold bottom of object 8 inches below eye level -#define PLAYER_HOLD_LEVEL_EYES -8 - -// when looking down, hold bottom of object 0 inches from feet -#define PLAYER_HOLD_DOWN_FEET 2 - -// when looking up, hold bottom of object 24 inches above eye level -#define PLAYER_HOLD_UP_EYES 24 - -// use a +/-30 degree range for the entire range of motion of pitch -#define PLAYER_LOOK_PITCH_RANGE 30 - -// player can reach down 2ft below his feet (otherwise he'll hold the object above the bottom) -#define PLAYER_REACH_DOWN_DISTANCE 24 - -static void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out ) -{ - if ( !pPlayer ) - return; - - QAngle angles = pPlayer->EyeAngles(); - Vector origin = pPlayer->EyePosition(); - - // 0-360 / -180-180 - //angles.x = init ? 0 : AngleDistance( angles.x, 0 ); - //angles.x = clamp( angles.x, -PLAYER_LOOK_PITCH_RANGE, PLAYER_LOOK_PITCH_RANGE ); - angles.x = 0; - - float feet = pPlayer->GetAbsOrigin().z + pPlayer->WorldAlignMins().z; - float eyes = origin.z; - float zoffset = 0; - // moving up (negative pitch is up) - if ( angles.x < 0 ) - { - zoffset = RemapVal( angles.x, 0, -PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_UP_EYES ); - } - else - { - zoffset = RemapVal( angles.x, 0, PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_DOWN_FEET + (feet - eyes) ); - } - origin.z += zoffset; - angles.x = 0; - AngleMatrix( angles, origin, out ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -// derive from this so we can add save/load data to it -struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t -{ - DECLARE_SIMPLE_DATADESC(); -}; - -BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t ) - - DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( targetRotation, FIELD_VECTOR ), - DEFINE_FIELD( maxAngular, FIELD_FLOAT ), - DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ), - DEFINE_FIELD( maxSpeed, FIELD_FLOAT ), - DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ), - DEFINE_FIELD( dampFactor, FIELD_FLOAT ), - DEFINE_FIELD( teleportDistance, FIELD_FLOAT ), - -END_DATADESC() - -//----------------------------------------------------------------------------- -class CGrabController : public IMotionEvent -{ - DECLARE_SIMPLE_DATADESC(); - -public: - - CGrabController( void ); - ~CGrabController( void ); - void AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition ); - void DetachEntity( bool bClearVelocity ); - void OnRestore(); - - bool UpdateObject( CBasePlayer *pPlayer, float flError ); - - void SetTargetPosition( const Vector &target, const QAngle &targetOrientation ); - float ComputeError(); - float GetLoadWeight( void ) const { return m_flLoadWeight; } - void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; } - void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; } - QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); - QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); - - CBaseEntity *GetAttached() { return (CBaseEntity *)m_attachedEntity; } - - IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ); - float GetSavedMass( IPhysicsObject *pObject ); - -private: - // Compute the max speed for an attached object - void ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ); - - game_shadowcontrol_params_t m_shadow; - float m_timeToArrive; - float m_errorTime; - float m_error; - float m_contactAmount; - float m_angleAlignment; - bool m_bCarriedEntityBlocksLOS; - bool m_bIgnoreRelativePitch; - - float m_flLoadWeight; - float m_savedRotDamping[VPHYSICS_MAX_OBJECT_LIST_COUNT]; - float m_savedMass[VPHYSICS_MAX_OBJECT_LIST_COUNT]; - EHANDLE m_attachedEntity; - QAngle m_vecPreferredCarryAngles; - bool m_bHasPreferredCarryAngles; - float m_flDistanceOffset; - - QAngle m_attachedAnglesPlayerSpace; - Vector m_attachedPositionObjectSpace; - - IPhysicsMotionController *m_controller; - - friend class CWeaponPhysCannon; -}; - -BEGIN_SIMPLE_DATADESC( CGrabController ) - - DEFINE_EMBEDDED( m_shadow ), - - DEFINE_FIELD( m_timeToArrive, FIELD_FLOAT ), - DEFINE_FIELD( m_errorTime, FIELD_FLOAT ), - DEFINE_FIELD( m_error, FIELD_FLOAT ), - DEFINE_FIELD( m_contactAmount, FIELD_FLOAT ), - DEFINE_AUTO_ARRAY( m_savedRotDamping, FIELD_FLOAT ), - DEFINE_AUTO_ARRAY( m_savedMass, FIELD_FLOAT ), - DEFINE_FIELD( m_flLoadWeight, FIELD_FLOAT ), - DEFINE_FIELD( m_bCarriedEntityBlocksLOS, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bIgnoreRelativePitch, FIELD_BOOLEAN ), - DEFINE_FIELD( m_attachedEntity, FIELD_EHANDLE ), - DEFINE_FIELD( m_angleAlignment, FIELD_FLOAT ), - DEFINE_FIELD( m_vecPreferredCarryAngles, FIELD_VECTOR ), - DEFINE_FIELD( m_bHasPreferredCarryAngles, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flDistanceOffset, FIELD_FLOAT ), - DEFINE_FIELD( m_attachedAnglesPlayerSpace, FIELD_VECTOR ), - DEFINE_FIELD( m_attachedPositionObjectSpace, FIELD_VECTOR ), - - // Physptrs can't be inside embedded classes - // DEFINE_PHYSPTR( m_controller ), - -END_DATADESC() - -const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f; -const float REDUCED_CARRY_MASS = 1.0f; - -CGrabController::CGrabController( void ) -{ - m_shadow.dampFactor = 1.0; - m_shadow.teleportDistance = 0; - m_errorTime = 0; - m_error = 0; - // make this controller really stiff! - m_shadow.maxSpeed = 1000; - m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; - m_shadow.maxDampSpeed = m_shadow.maxSpeed*2; - m_shadow.maxDampAngular = m_shadow.maxAngular; - m_attachedEntity = NULL; - m_vecPreferredCarryAngles = vec3_angle; - m_bHasPreferredCarryAngles = false; - m_flDistanceOffset = 0; -} - -CGrabController::~CGrabController( void ) -{ - DetachEntity( false ); -} - -void CGrabController::OnRestore() -{ - if ( m_controller ) - { - m_controller->SetEventHandler( this ); - } -} - -void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation ) -{ - m_shadow.targetPosition = target; - m_shadow.targetRotation = targetOrientation; - - m_timeToArrive = gpGlobals->frametime; - - CBaseEntity *pAttached = GetAttached(); - if ( pAttached ) - { - IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); - - if ( pObj != NULL ) - { - pObj->Wake(); - } - else - { - DetachEntity( false ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : float -//----------------------------------------------------------------------------- -float CGrabController::ComputeError() -{ - if ( m_errorTime <= 0 ) - return 0; - - CBaseEntity *pAttached = GetAttached(); - if ( pAttached ) - { - Vector pos; - IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); - - if ( pObj ) - { - pObj->GetShadowPosition( &pos, NULL ); - - float error = (m_shadow.targetPosition - pos).Length(); - if ( m_errorTime > 0 ) - { - if ( m_errorTime > 1 ) - { - m_errorTime = 1; - } - float speed = error / m_errorTime; - if ( speed > m_shadow.maxSpeed ) - { - error *= 0.5; - } - m_error = (1-m_errorTime) * m_error + error * m_errorTime; - } - } - else - { - DevMsg( "Object attached to Physcannon has no physics object\n" ); - DetachEntity( false ); - return 9999; // force detach - } - } - - if ( pAttached->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) ) - { - m_error *= 3.0f; - } - - m_errorTime = 0; - - return m_error; -} - - -#define MASS_SPEED_SCALE 60 -#define MAX_MASS 40 - -void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ) -{ - m_shadow.maxSpeed = 1000; - m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; - - // Compute total mass... - float flMass = PhysGetEntityMass( pEntity ); - float flMaxMass = physcannon_maxmass.GetFloat(); - if ( flMass <= flMaxMass ) - return; - - float flLerpFactor = clamp( flMass, flMaxMass, 500.0f ); - flLerpFactor = SimpleSplineRemapVal( flLerpFactor, flMaxMass, 500.0f, 0.0f, 1.0f ); - - float invMass = pPhysics->GetInvMass(); - float invInertia = pPhysics->GetInvInertia().Length(); - - float invMaxMass = 1.0f / MAX_MASS; - float ratio = invMaxMass / invMass; - invMass = invMaxMass; - invInertia *= ratio; - - float maxSpeed = invMass * MASS_SPEED_SCALE * 200; - float maxAngular = invInertia * MASS_SPEED_SCALE * 360; - - m_shadow.maxSpeed = Lerp( flLerpFactor, m_shadow.maxSpeed, maxSpeed ); - m_shadow.maxAngular = Lerp( flLerpFactor, m_shadow.maxAngular, maxAngular ); -} - - -QAngle CGrabController::TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) -{ - if ( m_bIgnoreRelativePitch ) - { - matrix3x4_t test; - QAngle angleTest = pPlayer->EyeAngles(); - angleTest.x = 0; - AngleMatrix( angleTest, test ); - return TransformAnglesToLocalSpace( anglesIn, test ); - } - return TransformAnglesToLocalSpace( anglesIn, pPlayer->EntityToWorldTransform() ); -} - -QAngle CGrabController::TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) -{ - if ( m_bIgnoreRelativePitch ) - { - matrix3x4_t test; - QAngle angleTest = pPlayer->EyeAngles(); - angleTest.x = 0; - AngleMatrix( angleTest, test ); - return TransformAnglesToWorldSpace( anglesIn, test ); - } - return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() ); -} - - -void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition ) -{ - // play the impact sound of the object hitting the player - // used as feedback to let the player know he picked up the object - int hitMaterial = pPhys->GetMaterialIndex(); - int playerMaterial = pPlayer->VPhysicsGetObject() ? pPlayer->VPhysicsGetObject()->GetMaterialIndex() : hitMaterial; - PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, hitMaterial, playerMaterial, 1.0, 64 ); - Vector position; - QAngle angles; - pPhys->GetPosition( &position, &angles ); - // If it has a preferred orientation, use that instead. - Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); - -// ComputeMaxSpeed( pEntity, pPhys ); - - // If we haven't been killed by a grab, we allow the gun to grab the nearest part of a ragdoll - if ( bUseGrabPosition ) - { - IPhysicsObject *pChild = GetRagdollChildAtPosition( pEntity, vGrabPosition ); - - if ( pChild ) - { - pPhys = pChild; - } - } - - // Carried entities can never block LOS - m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS(); - pEntity->SetBlocksLOS( false ); - m_controller = physenv->CreateMotionController( this ); - m_controller->AttachObject( pPhys, true ); - // Don't do this, it's causing trouble with constraint solvers. - //m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY ); - - pPhys->Wake(); - PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); - SetTargetPosition( position, angles ); - m_attachedEntity = pEntity; - IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; - int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); - m_flLoadWeight = 0; - float damping = 10; - float flFactor = count / 7.5f; - if ( flFactor < 1.0f ) - { - flFactor = 1.0f; - } - for ( int i = 0; i < count; i++ ) - { - float mass = pList[i]->GetMass(); - pList[i]->GetDamping( NULL, &m_savedRotDamping[i] ); - m_flLoadWeight += mass; - m_savedMass[i] = mass; - - // reduce the mass to prevent the player from adding crazy amounts of energy to the system - pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor ); - pList[i]->SetDamping( NULL, &damping ); - } - - // Give extra mass to the phys object we're actually picking up - pPhys->SetMass( REDUCED_CARRY_MASS ); - pPhys->EnableDrag( false ); - - m_errorTime = bIsMegaPhysCannon ? -1.5f : -1.0f; // 1 seconds until error starts accumulating - m_error = 0; - m_contactAmount = 0; - - m_attachedAnglesPlayerSpace = TransformAnglesToPlayerSpace( angles, pPlayer ); - if ( m_angleAlignment != 0 ) - { - m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment ); - } - - // Ragdolls don't offset this way - if ( dynamic_cast(pEntity) ) - { - m_attachedPositionObjectSpace.Init(); - } - else - { - VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace ); - } - - // If it's a prop, see if it has desired carry angles - CPhysicsProp *pProp = dynamic_cast(pEntity); - if ( pProp ) - { - m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); - m_flDistanceOffset = pProp->GetCarryDistanceOffset(); - } - else - { - m_bHasPreferredCarryAngles = false; - m_flDistanceOffset = 0; - } -} - -static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit ) -{ - Vector vel; - AngularImpulse angVel; - pPhys->GetVelocity( &vel, &angVel ); - float speed = VectorNormalize(vel) - linearLimit; - float angSpeed = VectorNormalize(angVel) - angularLimit; - speed = speed < 0 ? 0 : -speed; - angSpeed = angSpeed < 0 ? 0 : -angSpeed; - vel *= speed; - angVel *= angSpeed; - pPhys->AddVelocity( &vel, &angVel ); -} - -void CGrabController::DetachEntity( bool bClearVelocity ) -{ - Assert(!PhysIsInCallback()); - CBaseEntity *pEntity = GetAttached(); - if ( pEntity ) - { - // Restore the LS blocking state - pEntity->SetBlocksLOS( m_bCarriedEntityBlocksLOS ); - IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; - int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); - for ( int i = 0; i < count; i++ ) - { - IPhysicsObject *pPhys = pList[i]; - if ( !pPhys ) - continue; - - // on the odd chance that it's gone to sleep while under anti-gravity - pPhys->EnableDrag( true ); - pPhys->Wake(); - pPhys->SetMass( m_savedMass[i] ); - pPhys->SetDamping( NULL, &m_savedRotDamping[i] ); - PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); - if ( bClearVelocity ) - { - PhysForceClearVelocity( pPhys ); - } - else - { - ClampPhysicsVelocity( pPhys, hl2_normspeed.GetFloat() * 1.5f, 2.0f * 360.0f ); - } - - } - } - - m_attachedEntity = NULL; - physenv->DestroyMotionController( m_controller ); - m_controller = NULL; -} - -static bool InContactWithHeavyObject( IPhysicsObject *pObject, float heavyMass ) -{ - bool contact = false; - IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); - while ( pSnapshot->IsValid() ) - { - IPhysicsObject *pOther = pSnapshot->GetObject( 1 ); - if ( !pOther->IsMoveable() || pOther->GetMass() > heavyMass ) - { - contact = true; - break; - } - pSnapshot->NextFrictionData(); - } - pObject->DestroyFrictionSnapshot( pSnapshot ); - return contact; -} - -IMotionEvent::simresult_e CGrabController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) -{ - game_shadowcontrol_params_t shadowParams = m_shadow; - if ( InContactWithHeavyObject( pObject, GetLoadWeight() ) ) - { - m_contactAmount = Approach( 0.1f, m_contactAmount, deltaTime*2.0f ); - } - else - { - m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime*2.0f ); - } - shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount; - m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime ); - - // Slide along the current contact points to fix bouncing problems - Vector velocity; - AngularImpulse angVel; - pObject->GetVelocity( &velocity, &angVel ); - PhysComputeSlideDirection( pObject, velocity, angVel, &velocity, &angVel, GetLoadWeight() ); - pObject->SetVelocityInstantaneous( &velocity, NULL ); - - linear.Init(); - angular.Init(); - m_errorTime += deltaTime; - - return SIM_LOCAL_ACCELERATION; -} - -float CGrabController::GetSavedMass( IPhysicsObject *pObject ) -{ - CBaseEntity *pHeld = m_attachedEntity; - if ( pHeld ) - { - if ( pObject->GetGameData() == (void*)pHeld ) - { - IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; - int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); - for ( int i = 0; i < count; i++ ) - { - if ( pList[i] == pObject ) - return m_savedMass[i]; - } - } - } - return 0.0f; -} - -//----------------------------------------------------------------------------- -// Player pickup controller -//----------------------------------------------------------------------------- -class CPlayerPickupController : public CBaseEntity -{ - DECLARE_DATADESC(); - DECLARE_CLASS( CPlayerPickupController, CBaseEntity ); -public: - void Init( CBasePlayer *pPlayer, CBaseEntity *pObject ); - void Shutdown( bool bThrown = false ); - bool OnControls( CBaseEntity *pControls ) { return true; } - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void OnRestore() - { - m_grabController.OnRestore(); - } - void VPhysicsUpdate( IPhysicsObject *pPhysics ){} - void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {} - - bool IsHoldingEntity( CBaseEntity *pEnt ); - CGrabController &GetGrabController() { return m_grabController; } - -private: - CGrabController m_grabController; - CBasePlayer *m_pPlayer; -}; - -LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController ); - -//--------------------------------------------------------- -// Save/Restore -//--------------------------------------------------------- -BEGIN_DATADESC( CPlayerPickupController ) - - DEFINE_EMBEDDED( m_grabController ), - - // Physptrs can't be inside embedded classes - DEFINE_PHYSPTR( m_grabController.m_controller ), - - DEFINE_FIELD( m_pPlayer, FIELD_CLASSPTR ), - -END_DATADESC() - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pPlayer - -// *pObject - -//----------------------------------------------------------------------------- -void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) -{ - // Holster player's weapon - if ( pPlayer->GetActiveWeapon() ) - { - if ( !pPlayer->GetActiveWeapon()->Holster() ) - { - Shutdown(); - return; - } - } - - CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( pPlayer ); - if ( pOwner ) - { - pOwner->EnableSprint( false ); - } - - // If the target is debris, convert it to non-debris - if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) - { - // Interactive debris converts back to debris when it comes to rest - pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); - } - - // done so I'll go across level transitions with the player - SetParent( pPlayer ); - m_grabController.SetIgnorePitch( true ); - m_grabController.SetAngleAlignment( DOT_30DEGREE ); - m_pPlayer = pPlayer; - IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); - - Pickup_OnPhysGunPickup( pObject, m_pPlayer, PICKED_UP_BY_PLAYER ); - - m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false ); - - m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; - m_pPlayer->SetUseEntity( this ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : bool - -//----------------------------------------------------------------------------- -void CPlayerPickupController::Shutdown( bool bThrown ) -{ - CBaseEntity *pObject = m_grabController.GetAttached(); - - bool bClearVelocity = false; - if ( !bThrown && pObject && pObject->VPhysicsGetObject() && pObject->VPhysicsGetObject()->GetContactPoint(NULL,NULL) ) - { - bClearVelocity = true; - } - - m_grabController.DetachEntity( bClearVelocity ); - - if ( pObject != NULL ) - { - Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER ); - } - - if ( m_pPlayer ) - { - CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( m_pPlayer ); - if ( pOwner ) - { - pOwner->EnableSprint( true ); - } - - m_pPlayer->SetUseEntity( NULL ); - if ( m_pPlayer->GetActiveWeapon() ) - { - if ( !m_pPlayer->GetActiveWeapon()->Deploy() ) - { - // We tried to restore the player's weapon, but we couldn't. - // This usually happens when they're holding an empty weapon that doesn't - // autoswitch away when out of ammo. Switch to next best weapon. - m_pPlayer->SwitchToNextBestWeapon( NULL ); - } - } - - m_pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; - } - Remove(); -} - - -void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - if ( ToBasePlayer(pActivator) == m_pPlayer ) - { - CBaseEntity *pAttached = m_grabController.GetAttached(); - - // UNDONE: Use vphysics stress to decide to drop objects - // UNDONE: Must fix case of forcing objects into the ground you're standing on (causes stress) before that will work - if ( !pAttached || useType == USE_OFF || (m_pPlayer->m_nButtons & IN_ATTACK2) || m_grabController.ComputeError() > 12 ) - { - Shutdown(); - return; - } - - //Adrian: Oops, our object became motion disabled, let go! - IPhysicsObject *pPhys = pAttached->VPhysicsGetObject(); - if ( pPhys && pPhys->IsMoveable() == false ) - { - Shutdown(); - return; - } - -#if STRESS_TEST - vphysics_objectstress_t stress; - CalculateObjectStress( pPhys, pAttached, &stress ); - if ( stress.exertedStress > 250 ) - { - Shutdown(); - return; - } -#endif - // +ATTACK will throw phys objects - if ( m_pPlayer->m_nButtons & IN_ATTACK ) - { - Shutdown( true ); - Vector vecLaunch; - m_pPlayer->EyeVectors( &vecLaunch ); - // JAY: Scale this with mass because some small objects really go flying - float massFactor = clamp( pPhys->GetMass(), 0.5, 15 ); - massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 ); - vecLaunch *= player_throwforce.GetFloat() * massFactor; - - pPhys->ApplyForceCenter( vecLaunch ); - AngularImpulse aVel = RandomAngularImpulse( -10, 10 ) * massFactor; - pPhys->ApplyTorqueCenter( aVel ); - return; - } - - if ( useType == USE_SET ) - { - // update position - m_grabController.UpdateObject( m_pPlayer, 12 ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEnt - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt ) -{ - return ( m_grabController.GetAttached() == pEnt ); -} - -void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject ) -{ - //Don't pick up if we don't have a phys object. - if ( pObject->VPhysicsGetObject() == NULL ) - return; - - CPlayerPickupController *pController = (CPlayerPickupController *)CBaseEntity::Create( "player_pickup", pObject->GetAbsOrigin(), vec3_angle, pPlayer ); - - if ( !pController ) - return; - - pController->Init( pPlayer, pObject ); -} - -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// Physcannon -//----------------------------------------------------------------------------- - -#define NUM_BEAMS 4 -#define NUM_SPRITES 6 - -struct thrown_objects_t -{ - float fTimeThrown; - EHANDLE hEntity; - - DECLARE_SIMPLE_DATADESC(); -}; - -BEGIN_SIMPLE_DATADESC( thrown_objects_t ) - DEFINE_FIELD( fTimeThrown, FIELD_TIME ), - DEFINE_FIELD( hEntity, FIELD_EHANDLE ), -END_DATADESC() - -class CWeaponPhysCannon : public CBaseHLCombatWeapon -{ -public: - DECLARE_CLASS( CWeaponPhysCannon, CBaseHLCombatWeapon ); - - DECLARE_SERVERCLASS(); - DECLARE_DATADESC(); - - CWeaponPhysCannon( void ); - - void Drop( const Vector &vecVelocity ); - void Precache(); - virtual void Spawn(); - virtual void OnRestore(); - virtual void StopLoopingSounds(); - virtual void UpdateOnRemove(void); - void PrimaryAttack(); - void SecondaryAttack(); - void WeaponIdle(); - void ItemPreFrame(); - void ItemPostFrame(); - void ItemBusyFrame(); - - virtual float GetMaxAutoAimDeflection() { return 0.90f; } - - void ForceDrop( void ); - bool DropIfEntityHeld( CBaseEntity *pTarget ); // Drops its held entity if it matches the entity passed in - CGrabController &GetGrabController() { return m_grabController; } - - bool CanHolster( void ); - bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); - bool Deploy( void ); - - bool HasAnyAmmo( void ) { return true; } - - void InputBecomeMegaCannon( inputdata_t &inputdata ); - - void BeginUpgrade(); - - virtual void SetViewModel( void ); - virtual const char *GetShootSound( int iIndex ) const; - - void RecordThrownObject( CBaseEntity *pObject ); - void PurgeThrownObjects(); - bool IsAccountableForObject( CBaseEntity *pObject ); - - bool ShouldDisplayHUDHint() { return true; } - - - -protected: - enum FindObjectResult_t - { - OBJECT_FOUND = 0, - OBJECT_NOT_FOUND, - OBJECT_BEING_DETACHED, - }; - - void DoMegaEffect( int effectType, Vector *pos = NULL ); - void DoEffect( int effectType, Vector *pos = NULL ); - - void OpenElements( void ); - void CloseElements( void ); - - // Pickup and throw objects. - bool CanPickupObject( CBaseEntity *pTarget ); - bool IsFlesh( CBaseEntity *pTarget ); - void CheckForTarget( void ); - FindObjectResult_t FindObject( void ); - CBaseEntity *MegaPhysCannonFindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone, float flCombineBallCone, bool bOnlyCombineBalls ); - CBaseEntity *FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ); - bool AttachObject( CBaseEntity *pObject, const Vector &vPosition ); - void UpdateObject( void ); - void DetachObject( bool playSound = true, bool wasLaunched = false ); - void LaunchObject( const Vector &vecDir, float flForce ); - void StartEffects( void ); // Initialize all sprites and beams - void StopEffects( bool stopSound = true ); // Hide all effects temporarily - void DestroyEffects( void ); // Destroy all sprites and beams - - // Punt objects - this is pointing at an object in the world and applying a force to it. - void PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); - void PuntVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); - void PuntRagdoll( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); - - // Velocity-based throw common to punt and launch code. - void ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ); - - // Physgun effects - void DoEffectClosed( void ); - void DoMegaEffectClosed( void ); - - void DoEffectReady( void ); - void DoMegaEffectReady( void ); - - void DoMegaEffectHolding( void ); - void DoEffectHolding( void ); - - void DoMegaEffectLaunch( Vector *pos ); - void DoEffectLaunch( Vector *pos ); - - void DoEffectNone( void ); - void DoEffectIdle( void ); - - // Trace length - float TraceLength(); - - // Do we have the super-phys gun? - inline bool IsMegaPhysCannon() - { - return PlayerHasMegaPhysCannon(); - } - - // Sprite scale factor - float SpriteScaleFactor(); - - float GetLoadPercentage(); - CSoundPatch *GetMotorSound( void ); - - void DryFire( void ); - void PrimaryFireEffect( void ); - - // What happens when the physgun picks up something - void Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ); - - // Wait until we're done upgrading - void WaitForUpgradeThink(); - - bool EntityAllowsPunts( CBaseEntity *pEntity ); - - bool m_bOpen; - bool m_bActive; - int m_nChangeState; //For delayed state change of elements - float m_flCheckSuppressTime; //Amount of time to suppress the checking for targets - bool m_flLastDenySoundPlayed; //Debounce for deny sound - int m_nAttack2Debounce; - - CNetworkVar( bool, m_bIsCurrentlyUpgrading ); - - float m_flElementDebounce; - float m_flElementPosition; - float m_flElementDestination; - - CHandle m_hBeams[NUM_BEAMS]; - CHandle m_hGlowSprites[NUM_SPRITES]; - CHandle m_hEndSprites[2]; - float m_flEndSpritesOverride[2]; - CHandle m_hCenterSprite; - CHandle m_hBlastSprite; - - CSoundPatch *m_sndMotor; // Whirring sound for the gun - - CGrabController m_grabController; - - int m_EffectState; // Current state of the effects on the gun - - bool m_bPhyscannonState; - - // A list of the objects thrown or punted recently, and the time done so. - CUtlVector< thrown_objects_t > m_ThrownEntities; - - float m_flTimeNextObjectPurge; -}; - -IMPLEMENT_SERVERCLASS_ST(CWeaponPhysCannon, DT_WeaponPhysCannon) - SendPropBool( SENDINFO( m_bIsCurrentlyUpgrading ) ), -END_SEND_TABLE() - -LINK_ENTITY_TO_CLASS( weapon_physcannon, CWeaponPhysCannon ); -PRECACHE_WEAPON_REGISTER( weapon_physcannon ); - -BEGIN_DATADESC( CWeaponPhysCannon ) - - DEFINE_FIELD( m_bOpen, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), - - DEFINE_FIELD( m_nChangeState, FIELD_INTEGER ), - DEFINE_FIELD( m_flCheckSuppressTime, FIELD_TIME ), - DEFINE_FIELD( m_flElementDebounce, FIELD_TIME ), - DEFINE_FIELD( m_flElementPosition, FIELD_FLOAT ), - DEFINE_FIELD( m_flElementDestination, FIELD_FLOAT ), - DEFINE_FIELD( m_nAttack2Debounce, FIELD_INTEGER ), - DEFINE_FIELD( m_bIsCurrentlyUpgrading, FIELD_BOOLEAN ), - DEFINE_FIELD( m_EffectState, FIELD_INTEGER ), - - DEFINE_AUTO_ARRAY( m_hBeams, FIELD_EHANDLE ), - DEFINE_AUTO_ARRAY( m_hGlowSprites, FIELD_EHANDLE ), - DEFINE_AUTO_ARRAY( m_hEndSprites, FIELD_EHANDLE ), - DEFINE_AUTO_ARRAY( m_flEndSpritesOverride, FIELD_TIME ), - DEFINE_FIELD( m_hCenterSprite, FIELD_EHANDLE ), - DEFINE_FIELD( m_hBlastSprite, FIELD_EHANDLE ), - DEFINE_FIELD( m_flLastDenySoundPlayed, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bPhyscannonState, FIELD_BOOLEAN ), - DEFINE_SOUNDPATCH( m_sndMotor ), - - DEFINE_EMBEDDED( m_grabController ), - - // Physptrs can't be inside embedded classes - DEFINE_PHYSPTR( m_grabController.m_controller ), - - DEFINE_THINKFUNC( WaitForUpgradeThink ), - - DEFINE_UTLVECTOR( m_ThrownEntities, FIELD_EMBEDDED ), - - DEFINE_FIELD( m_flTimeNextObjectPurge, FIELD_TIME ), - -END_DATADESC() - - -enum -{ - ELEMENT_STATE_NONE = -1, - ELEMENT_STATE_OPEN, - ELEMENT_STATE_CLOSED, -}; - -enum -{ - EFFECT_NONE, - EFFECT_CLOSED, - EFFECT_READY, - EFFECT_HOLDING, - EFFECT_LAUNCH, -}; - - -//----------------------------------------------------------------------------- -// Do we have the super-phys gun? -//----------------------------------------------------------------------------- -bool PlayerHasMegaPhysCannon() -{ - return ( HL2GameRules()->MegaPhyscannonActive() == true ); -} - - -//----------------------------------------------------------------------------- -// Constructor -//----------------------------------------------------------------------------- -CWeaponPhysCannon::CWeaponPhysCannon( void ) -{ - m_flElementPosition = 0.0f; - m_flElementDestination = 0.0f; - m_bOpen = false; - m_nChangeState = ELEMENT_STATE_NONE; - m_flCheckSuppressTime = 0.0f; - m_EffectState = EFFECT_NONE; - m_flLastDenySoundPlayed = false; - - m_flEndSpritesOverride[0] = 0.0f; - m_flEndSpritesOverride[1] = 0.0f; - - m_bPhyscannonState = false; -} - -//----------------------------------------------------------------------------- -// Purpose: Precache -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::Precache( void ) -{ - PrecacheModel( PHYSCANNON_BEAM_SPRITE ); - PrecacheModel( PHYSCANNON_GLOW_SPRITE ); - PrecacheModel( PHYSCANNON_ENDCAP_SPRITE ); - PrecacheModel( PHYSCANNON_CENTER_GLOW ); - PrecacheModel( PHYSCANNON_BLAST_SPRITE ); - - PrecacheModel( MEGACANNON_BEAM_SPRITE ); - PrecacheModel( MEGACANNON_GLOW_SPRITE ); - PrecacheModel( MEGACANNON_ENDCAP_SPRITE ); - PrecacheModel( MEGACANNON_CENTER_GLOW ); - PrecacheModel( MEGACANNON_BLAST_SPRITE ); - - PrecacheModel( MEGACANNON_RAGDOLL_BOOGIE_SPRITE ); - - // Precache our alternate model - PrecacheModel( MEGACANNON_MODEL ); - - PrecacheScriptSound( "Weapon_PhysCannon.HoldSound" ); - PrecacheScriptSound( "Weapon_Physgun.Off" ); - - PrecacheScriptSound( "Weapon_MegaPhysCannon.DryFire" ); - PrecacheScriptSound( "Weapon_MegaPhysCannon.Launch" ); - PrecacheScriptSound( "Weapon_MegaPhysCannon.Pickup"); - PrecacheScriptSound( "Weapon_MegaPhysCannon.Drop"); - PrecacheScriptSound( "Weapon_MegaPhysCannon.HoldSound"); - PrecacheScriptSound( "Weapon_MegaPhysCannon.ChargeZap"); - - BaseClass::Precache(); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::Spawn( void ) -{ - BaseClass::Spawn(); - - // Need to get close to pick it up - CollisionProp()->UseTriggerBounds( false ); - - m_bPhyscannonState = IsMegaPhysCannon(); - - // The megacannon uses a different skin - if ( IsMegaPhysCannon() ) - { - m_nSkin = MEGACANNON_SKIN; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Restore -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::OnRestore() -{ - BaseClass::OnRestore(); - m_grabController.OnRestore(); - - m_bPhyscannonState = IsMegaPhysCannon(); - - // Tracker 8106: Physcannon effects disappear through level transition, so - // just recreate any effects here - if ( m_EffectState != EFFECT_NONE ) - { - DoEffect( m_EffectState, NULL ); - } -} - - -//----------------------------------------------------------------------------- -// On Remove -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::UpdateOnRemove(void) -{ - DestroyEffects( ); - BaseClass::UpdateOnRemove(); -} - - -//----------------------------------------------------------------------------- -// Sprite scale factor -//----------------------------------------------------------------------------- -inline float CWeaponPhysCannon::SpriteScaleFactor() -{ - return IsMegaPhysCannon() ? 1.5f : 1.0f; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CWeaponPhysCannon::Deploy( void ) -{ - CloseElements(); - DoEffect( EFFECT_READY ); - - // Unbloat our bounds - if ( IsMegaPhysCannon() ) - { - CollisionProp()->UseTriggerBounds( false ); - } - - m_flTimeNextObjectPurge = gpGlobals->curtime; - - return BaseClass::Deploy(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::SetViewModel( void ) -{ - if ( !IsMegaPhysCannon() ) - { - BaseClass::SetViewModel(); - return; - } - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner == NULL ) - return; - - CBaseViewModel *vm = pOwner->GetViewModel( m_nViewModelIndex ); - if ( vm == NULL ) - return; - - vm->SetWeaponModel( MEGACANNON_MODEL, this ); -} - -//----------------------------------------------------------------------------- -// Purpose: Force the cannon to drop anything it's carrying -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::ForceDrop( void ) -{ - CloseElements(); - DetachObject(); - StopEffects(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Drops its held entity if it matches the entity passed in -// Input : *pTarget - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CWeaponPhysCannon::DropIfEntityHeld( CBaseEntity *pTarget ) -{ - if ( pTarget == NULL ) - return false; - - CBaseEntity *pHeld = m_grabController.GetAttached(); - - if ( pHeld == NULL ) - return false; - - if ( pHeld == pTarget ) - { - ForceDrop(); - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::Drop( const Vector &vecVelocity ) -{ - ForceDrop(); - BaseClass::Drop( vecVelocity ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CWeaponPhysCannon::CanHolster( void ) -{ - //Don't holster this weapon if we're holding onto something - if ( m_bActive ) - return false; - - return BaseClass::CanHolster(); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CWeaponPhysCannon::Holster( CBaseCombatWeapon *pSwitchingTo ) -{ - //Don't holster this weapon if we're holding onto something - if ( m_bActive ) - { - if ( m_grabController.GetAttached() == pSwitchingTo && - GetOwner()->Weapon_OwnsThisType( pSwitchingTo->GetClassname(), pSwitchingTo->GetSubType()) ) - { - - } - else - { - return false; - } - } - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner ) - { - pOwner->RumbleEffect( RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_STOP ); - } - - ForceDrop(); - - return BaseClass::Holster( pSwitchingTo ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DryFire( void ) -{ - SendWeaponAnim( ACT_VM_PRIMARYATTACK ); - WeaponSound( EMPTY ); - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner ) - { - pOwner->RumbleEffect( RUMBLE_PISTOL, 0, RUMBLE_FLAG_RESTART ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::PrimaryFireEffect( void ) -{ - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - - if ( pOwner == NULL ) - return; - - pOwner->ViewPunch( QAngle(-6, random->RandomInt(-2,2) ,0) ); - - color32 white = { 245, 245, 255, 32 }; - UTIL_ScreenFade( pOwner, white, 0.1f, 0.0f, FFADE_IN ); - - WeaponSound( SINGLE ); -} - -#define MAX_KNOCKBACK_FORCE 128 - -//----------------------------------------------------------------------------- -// Punt non-physics -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ) -{ - CTakeDamageInfo info; - - info.SetAttacker( GetOwner() ); - info.SetInflictor( this ); - info.SetDamage( 1.0f ); - info.SetDamageType( DMG_CRUSH | DMG_PHYSGUN ); - info.SetDamageForce( forward ); // Scale? - info.SetDamagePosition( tr.endpos ); - - pEntity->DispatchTraceAttack( info, forward, &tr ); - - ApplyMultiDamage(); - - //Explosion effect - DoEffect( EFFECT_LAUNCH, &tr.endpos ); - - PrimaryFireEffect(); - SendWeaponAnim( ACT_VM_SECONDARYATTACK ); - - m_nChangeState = ELEMENT_STATE_CLOSED; - m_flElementDebounce = gpGlobals->curtime + 0.5f; - m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; -} - - -//----------------------------------------------------------------------------- -// What happens when the physgun picks up something -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ) -{ - // If the target is debris, convert it to non-debris - if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) - { - // Interactive debris converts back to debris when it comes to rest - pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); - } - - float mass = 0.0f; - if( pEntity->VPhysicsGetObject() ) - { - mass = pEntity->VPhysicsGetObject()->GetMass(); - } - - if( reason == PUNTED_BY_CANNON ) - { - pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAGS_NONE ); - RecordThrownObject( pEntity ); - } - - // Warn Alyx if the player is punting a car around. - if( hl2_episodic.GetBool() && mass > 250.0f ) - { - CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); - int nAIs = g_AI_Manager.NumAIs(); - - for ( int i = 0; i < nAIs; i++ ) - { - if( ppAIs[ i ]->Classify() == CLASS_PLAYER_ALLY_VITAL ) - { - ppAIs[ i ]->DispatchInteraction( g_interactionPlayerPuntedHeavyObject, pEntity, pOwner ); - } - } - } - - Pickup_OnPhysGunPickup( pEntity, pOwner, reason ); -} - - -//----------------------------------------------------------------------------- -// Punt vphysics -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr ) -{ - CTakeDamageInfo info; - - Vector forward = vecForward; - - info.SetAttacker( GetOwner() ); - info.SetInflictor( this ); - info.SetDamage( 0.0f ); - info.SetDamageType( DMG_PHYSGUN ); - pEntity->DispatchTraceAttack( info, forward, &tr ); - ApplyMultiDamage(); - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) ) - { - IPhysicsObject *pList[64]; - int listCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); - if ( !listCount ) - { - //FIXME: Do we want to do this if there's no physics object? - Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); - DryFire(); - return; - } - - if( forward.z < 0 ) - { - //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets - forward.z *= -0.65f; - } - - // NOTE: Do this first to enable motion (if disabled) - so forces will work - // Tell the object it's been punted - Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); - - // don't push vehicles that are attached to the world via fixed constraints - // they will just wiggle... - if ( (pList[0]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) && pEntity->GetServerVehicle() ) - { - forward.Init(); - } - - if ( !IsMegaPhysCannon() && !Pickup_ShouldPuntUseLaunchForces( pEntity ) ) - { - int i; - - // limit mass to avoid punting REALLY huge things - float totalMass = 0; - for ( i = 0; i < listCount; i++ ) - { - totalMass += pList[i]->GetMass(); - } - float maxMass = 250; - IServerVehicle *pVehicle = pEntity->GetServerVehicle(); - if ( pVehicle ) - { - maxMass *= 2.5; // 625 for vehicles - } - float mass = min(totalMass, maxMass); // max 250kg of additional force - - // Put some spin on the object - for ( i = 0; i < listCount; i++ ) - { - const float hitObjectFactor = 0.5f; - const float otherObjectFactor = 1.0f - hitObjectFactor; - // Must be light enough - float ratio = pList[i]->GetMass() / totalMass; - if ( pList[i] == pEntity->VPhysicsGetObject() ) - { - ratio += hitObjectFactor; - ratio = min(ratio,1.0f); - } - else - { - ratio *= otherObjectFactor; - } - pList[i]->ApplyForceCenter( forward * 15000.0f * ratio ); - pList[i]->ApplyForceOffset( forward * mass * 600.0f * ratio, tr.endpos ); - } - } - else - { - ApplyVelocityBasedForce( pEntity, vecForward ); - } - } - - // Add recoil - QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 ); - pOwner->ViewPunch( recoil ); - - //Explosion effect - DoEffect( EFFECT_LAUNCH, &tr.endpos ); - - PrimaryFireEffect(); - SendWeaponAnim( ACT_VM_SECONDARYATTACK ); - - m_nChangeState = ELEMENT_STATE_CLOSED; - m_flElementDebounce = gpGlobals->curtime + 0.5f; - m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; - - // Don't allow the gun to regrab a thrown object!! - m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; -} - -//----------------------------------------------------------------------------- -// Purpose: Applies velocity-based forces to throw the entity. This code is -// called from both punt and launch carried code. -// ASSUMES: that pEntity is a vphysics entity. -// Input : - -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ) -{ - // Get the launch velocity - Vector vVel = Pickup_PhysGunLaunchVelocity( pEntity, forward ); - - // Get the launch angular impulse - AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity ); - - // Get the physics object (MUST have one) - IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); - if ( pPhysicsObject == NULL ) - { - Assert( 0 ); - return; - } - - // Affect the object - CRagdollProp *pRagdoll = dynamic_cast( pEntity ); - if ( pRagdoll == NULL ) - { - pPhysicsObject->AddVelocity( &vVel, &aVel ); - } - else - { - Vector vTempVel; - AngularImpulse vTempAVel; - - ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( ); - for ( int j = 0; j < pRagdollPhys->listCount; ++j ) - { - pRagdollPhys->list[j].pObject->AddVelocity( &vVel, &aVel ); - } - } -} - - -//----------------------------------------------------------------------------- -// Punt non-physics -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::PuntRagdoll( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr ) -{ - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - Pickup_OnPhysGunDrop( pEntity, pOwner, LAUNCHED_BY_CANNON ); - - CTakeDamageInfo info; - - Vector forward = vecForward; - info.SetAttacker( GetOwner() ); - info.SetInflictor( this ); - info.SetDamage( 0.0f ); - info.SetDamageType( DMG_PHYSGUN ); - pEntity->DispatchTraceAttack( info, forward, &tr ); - ApplyMultiDamage(); - - if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) ) - { - Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); - - if( forward.z < 0 ) - { - //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets - forward.z *= -0.65f; - } - - Vector vVel = forward * 1500; - AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity ); - - CRagdollProp *pRagdoll = dynamic_cast( pEntity ); - ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( ); - - int j; - for ( j = 0; j < pRagdollPhys->listCount; ++j ) - { - pRagdollPhys->list[j].pObject->AddVelocity( &vVel, NULL ); - } - } - - // Add recoil - QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 ); - pOwner->ViewPunch( recoil ); - - //Explosion effect - DoEffect( EFFECT_LAUNCH, &tr.endpos ); - - PrimaryFireEffect(); - SendWeaponAnim( ACT_VM_SECONDARYATTACK ); - - m_nChangeState = ELEMENT_STATE_CLOSED; - m_flElementDebounce = gpGlobals->curtime + 0.5f; - m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; - - // Don't allow the gun to regrab a thrown object!! - m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; -} - - -//----------------------------------------------------------------------------- -// Trace length -//----------------------------------------------------------------------------- -float CWeaponPhysCannon::TraceLength() -{ - if ( !IsMegaPhysCannon() ) - { - return physcannon_tracelength.GetFloat(); - } - - return physcannon_mega_tracelength.GetFloat(); -} - -//----------------------------------------------------------------------------- -// If there's any special rejection code you need to do per entity then do it here -// This is kinda nasty but I'd hate to move more physcannon related stuff into CBaseEntity -//----------------------------------------------------------------------------- -bool CWeaponPhysCannon::EntityAllowsPunts( CBaseEntity *pEntity ) -{ - if ( pEntity->HasSpawnFlags( SF_PHYSBOX_NEVER_PUNT ) ) - { - CPhysBox *pPhysBox = dynamic_cast(pEntity); - - if ( pPhysBox != NULL ) - { - if ( pPhysBox->HasSpawnFlags( SF_PHYSBOX_NEVER_PUNT ) ) - { - return false; - } - } - } - - if ( pEntity->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) ) - { - CBaseCombatWeapon *pWeapon = dynamic_cast(pEntity); - - if ( pWeapon != NULL ) - { - if ( pWeapon->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) ) - { - return false; - } - } - } - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// -// This mode is a toggle. Primary fire one time to pick up a physics object. -// With an object held, click primary fire again to drop object. -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::PrimaryAttack( void ) -{ - if( m_flNextPrimaryAttack > gpGlobals->curtime ) - return; - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - - if ( pOwner == NULL ) - return; - - if( m_bActive ) - { - // Punch the object being held!! - Vector forward; - pOwner->EyeVectors( &forward ); - - // Validate the item is within punt range - CBaseEntity *pHeld = m_grabController.GetAttached(); - Assert( pHeld != NULL ); - - if ( pHeld != NULL ) - { - float heldDist = ( pHeld->WorldSpaceCenter() - pOwner->WorldSpaceCenter() ).Length(); - - if ( heldDist > physcannon_tracelength.GetFloat() ) - { - // We can't punt this yet - DryFire(); - return; - } - } - - LaunchObject( forward, physcannon_maxforce.GetFloat() ); - - PrimaryFireEffect(); - SendWeaponAnim( ACT_VM_SECONDARYATTACK ); - return; - } - - // If not active, just issue a physics punch in the world. - m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; - - Vector forward; - pOwner->EyeVectors( &forward ); - - // NOTE: Notice we're *not* using the mega tracelength here - // when you have the mega cannon. Punting has shorter range. - Vector start, end; - start = pOwner->Weapon_ShootPosition(); - float flPuntDistance = physcannon_tracelength.GetFloat(); - VectorMA( start, flPuntDistance, forward, end ); - - trace_t tr; - UTIL_PhyscannonTraceHull( start, end, -Vector(8,8,8), Vector(8,8,8), pOwner, &tr ); - bool bValid = true; - CBaseEntity *pEntity = tr.m_pEnt; - if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) - { - bValid = false; - } - else if ( (pEntity->GetMoveType() != MOVETYPE_VPHYSICS) && ( pEntity->m_takedamage == DAMAGE_NO ) ) - { - bValid = false; - } - - // If the entity we've hit is invalid, try a traceline instead - if ( !bValid ) - { - UTIL_PhyscannonTraceLine( start, end, pOwner, &tr ); - if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) - { - if( hl2_episodic.GetBool() ) - { - // Try to find something in a very small cone. - CBaseEntity *pObject = FindObjectInCone( start, forward, physcannon_punt_cone.GetFloat() ); - - if( pObject ) - { - // Trace to the object. - UTIL_PhyscannonTraceLine( start, pObject->WorldSpaceCenter(), pOwner, &tr ); - - if( tr.m_pEnt && tr.m_pEnt == pObject && !(pObject->IsEFlagSet(EFL_NO_PHYSCANNON_INTERACTION)) ) - { - bValid = true; - pEntity = pObject; - } - } - } - } - else - { - bValid = true; - pEntity = tr.m_pEnt; - } - } - - if( !bValid ) - { - DryFire(); - return; - } - - // See if we hit something - if ( pEntity->GetMoveType() != MOVETYPE_VPHYSICS ) - { - if ( pEntity->m_takedamage == DAMAGE_NO ) - { - DryFire(); - return; - } - - if( GetOwner()->IsPlayer() && !IsMegaPhysCannon() ) - { - // Don't let the player zap any NPC's except regular antlions and headcrabs. - if( pEntity->IsNPC() && pEntity->Classify() != CLASS_HEADCRAB && !FClassnameIs(pEntity, "npc_antlion") ) - { - DryFire(); - return; - } - } - - if ( IsMegaPhysCannon() ) - { - if ( pEntity->IsNPC() && !pEntity->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) && pEntity->MyNPCPointer()->CanBecomeRagdoll() ) - { - CTakeDamageInfo info( pOwner, pOwner, 1.0f, DMG_GENERIC ); - CBaseEntity *pRagdoll = CreateServerRagdoll( pEntity->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); - PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS ); - pRagdoll->SetCollisionBounds( pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs() ); - - // Necessary to cause it to do the appropriate death cleanup - CTakeDamageInfo ragdollInfo( pOwner, pOwner, 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL ); - pEntity->TakeDamage( ragdollInfo ); - - PuntRagdoll( pRagdoll, forward, tr ); - return; - } - } - - PuntNonVPhysics( pEntity, forward, tr ); - } - else - { - if ( EntityAllowsPunts( pEntity) == false ) - { - DryFire(); - return; - } - - if ( !IsMegaPhysCannon() ) - { - if ( IsFlesh( pEntity ) ) - { - DryFire(); - return; - } - PuntVPhysics( pEntity, forward, tr ); - } - else - { - if ( dynamic_cast(pEntity) ) - { - PuntRagdoll( pEntity, forward, tr ); - } - else - { - PuntVPhysics( pEntity, forward, tr ); - } - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Click secondary attack whilst holding an object to hurl it. -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::SecondaryAttack( void ) -{ - if ( m_flNextSecondaryAttack > gpGlobals->curtime ) - return; - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - - if ( pOwner == NULL ) - return; - - // See if we should drop a held item - if ( ( m_bActive ) && ( pOwner->m_afButtonPressed & IN_ATTACK2 ) ) - { - // Drop the held object - m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; - m_flNextSecondaryAttack = gpGlobals->curtime + 0.5; - - DetachObject(); - - DoEffect( EFFECT_READY ); - - SendWeaponAnim( ACT_VM_PRIMARYATTACK ); - } - else - { - // Otherwise pick it up - FindObjectResult_t result = FindObject(); - switch ( result ) - { - case OBJECT_FOUND: - WeaponSound( SPECIAL1 ); - SendWeaponAnim( ACT_VM_PRIMARYATTACK ); - m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f; - - // We found an object. Debounce the button - m_nAttack2Debounce |= pOwner->m_nButtons; - break; - - case OBJECT_NOT_FOUND: - m_flNextSecondaryAttack = gpGlobals->curtime + 0.1f; - CloseElements(); - break; - - case OBJECT_BEING_DETACHED: - m_flNextSecondaryAttack = gpGlobals->curtime + 0.01f; - break; - } - - DoEffect( EFFECT_HOLDING ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::WeaponIdle( void ) -{ - if ( HasWeaponIdleTimeElapsed() ) - { - if ( m_bActive ) - { - //Shake when holding an item - SendWeaponAnim( ACT_VM_RELOAD ); - } - else - { - //Otherwise idle simply - SendWeaponAnim( ACT_VM_IDLE ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pObject - -//----------------------------------------------------------------------------- -bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosition ) -{ - if ( m_bActive ) - return false; - - if ( CanPickupObject( pObject ) == false ) - return false; - - m_grabController.SetIgnorePitch( false ); - m_grabController.SetAngleAlignment( 0 ); - - bool bKilledByGrab = false; - - bool bIsMegaPhysCannon = IsMegaPhysCannon(); - if ( bIsMegaPhysCannon ) - { - if ( pObject->IsNPC() && !pObject->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) ) - { - Assert( pObject->MyNPCPointer()->CanBecomeRagdoll() ); - CTakeDamageInfo info( GetOwner(), GetOwner(), 1.0f, DMG_GENERIC ); - CBaseEntity *pRagdoll = CreateServerRagdoll( pObject->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); - PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS ); - - pRagdoll->SetCollisionBounds( pObject->CollisionProp()->OBBMins(), pObject->CollisionProp()->OBBMaxs() ); - - // Necessary to cause it to do the appropriate death cleanup - CTakeDamageInfo ragdollInfo( GetOwner(), GetOwner(), 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL ); - pObject->TakeDamage( ragdollInfo ); - - // Now we act on the ragdoll for the remainder of the time - pObject = pRagdoll; - bKilledByGrab = true; - } - } - - IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); - - // Must be valid - if ( !pPhysics ) - return false; - - CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( GetOwner() ); - - m_bActive = true; - if( pOwner ) - { -#ifdef HL2_EPISODIC - CBreakableProp *pProp = dynamic_cast< CBreakableProp * >( pObject ); - - if ( pProp && pProp->HasInteraction( PROPINTER_PHYSGUN_CREATE_FLARE ) ) - { - pOwner->FlashlightTurnOff(); - } -#endif - - // NOTE: This can change the mass; so it must be done before max speed setting - Physgun_OnPhysGunPickup( pObject, pOwner, PICKED_UP_BY_CANNON ); - } - - // NOTE :This must happen after OnPhysGunPickup because that can change the mass - m_grabController.AttachEntity( pOwner, pObject, pPhysics, bIsMegaPhysCannon, vPosition, (!bKilledByGrab) ); - - if( pOwner ) - { - pOwner->EnableSprint( false ); - - float loadWeight = ( 1.0f - GetLoadPercentage() ); - float maxSpeed = hl2_walkspeed.GetFloat() + ( ( hl2_normspeed.GetFloat() - hl2_walkspeed.GetFloat() ) * loadWeight ); - - //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); - pOwner->SetMaxSpeed( maxSpeed ); - } - - // Don't drop again for a slight delay, in case they were pulling objects near them - m_flNextSecondaryAttack = gpGlobals->curtime + 0.4f; - - DoEffect( EFFECT_HOLDING ); - OpenElements(); - - if ( GetMotorSound() ) - { - (CSoundEnvelopeController::GetController()).Play( GetMotorSound(), 0.0f, 50 ); - (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 100, 0.5f ); - (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.8f, 0.5f ); - } - - return true; -} - -CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) -{ - CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); - - Assert( pPlayer ); - if ( pPlayer == NULL ) - return OBJECT_NOT_FOUND; - - Vector forward; - pPlayer->EyeVectors( &forward ); - - // Setup our positions - Vector start = pPlayer->Weapon_ShootPosition(); - float testLength = TraceLength() * 4.0f; - Vector end = start + forward * testLength; - - if( IsMegaPhysCannon() && hl2_episodic.GetBool() ) - { - Vector vecAutoAimDir = pPlayer->GetAutoaimVector( 1.0f, testLength ); - end = start + vecAutoAimDir * testLength; - } - - // Try to find an object by looking straight ahead - trace_t tr; - UTIL_PhyscannonTraceLine( start, end, pPlayer, &tr ); - - // Try again with a hull trace - if ( ( tr.fraction == 1.0 ) || ( tr.m_pEnt == NULL ) || ( tr.m_pEnt->IsWorld() ) ) - { - UTIL_PhyscannonTraceHull( start, end, -Vector(4,4,4), Vector(4,4,4), pPlayer, &tr ); - } - - CBaseEntity *pEntity = tr.m_pEnt ? tr.m_pEnt->GetRootMoveParent() : NULL; - bool bAttach = false; - bool bPull = false; - - // If we hit something, pick it up or pull it - if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt ) && ( tr.m_pEnt->IsWorld() == false ) ) - { - // Attempt to attach if within range - if ( tr.fraction <= 0.25f ) - { - bAttach = true; - } - else if ( tr.fraction > 0.25f ) - { - bPull = true; - } - } - - // Find anything within a general cone in front - CBaseEntity *pConeEntity = NULL; - if ( !IsMegaPhysCannon() ) - { - if (!bAttach && !bPull) - { - pConeEntity = FindObjectInCone( start, forward, physcannon_cone.GetFloat() ); - } - } - else - { - pConeEntity = MegaPhysCannonFindObjectInCone( start, forward, - physcannon_cone.GetFloat(), physcannon_ball_cone.GetFloat(), bAttach || bPull ); - } - - if ( pConeEntity ) - { - pEntity = pConeEntity; - - // If the object is near, grab it. Else, pull it a bit. - if ( pEntity->WorldSpaceCenter().DistToSqr( start ) <= (testLength * testLength) ) - { - bAttach = true; - } - else - { - bPull = true; - } - } - - if ( CanPickupObject( pEntity ) == false ) - { - CBaseEntity *pNewObject = Pickup_OnFailedPhysGunPickup( pEntity, start ); - - if ( pNewObject && CanPickupObject( pNewObject ) ) - { - pEntity = pNewObject; - } - else - { - // Make a noise to signify we can't pick this up - if ( !m_flLastDenySoundPlayed ) - { - m_flLastDenySoundPlayed = true; - WeaponSound( SPECIAL3 ); - } - - return OBJECT_NOT_FOUND; - } - } - - // Check to see if the object is constrained + needs to be ripped off... - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( !Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PICKED_UP_BY_CANNON ) ) - return OBJECT_BEING_DETACHED; - - if ( bAttach ) - { - return AttachObject( pEntity, tr.endpos ) ? OBJECT_FOUND : OBJECT_NOT_FOUND; - } - - if ( !bPull ) - return OBJECT_NOT_FOUND; - - // FIXME: This needs to be run through the CanPickupObject logic - IPhysicsObject *pObj = pEntity->VPhysicsGetObject(); - if ( !pObj ) - return OBJECT_NOT_FOUND; - - // If we're too far, simply start to pull the object towards us - Vector pullDir = start - pEntity->WorldSpaceCenter(); - VectorNormalize( pullDir ); - pullDir *= IsMegaPhysCannon() ? physcannon_mega_pullforce.GetFloat() : physcannon_pullforce.GetFloat(); - - float mass = PhysGetEntityMass( pEntity ); - if ( mass < 50.0f ) - { - pullDir *= (mass + 0.5) * (1/50.0f); - } - - // Nudge it towards us - pObj->ApplyForceCenter( pullDir ); - return OBJECT_NOT_FOUND; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CBaseEntity *CWeaponPhysCannon::MegaPhysCannonFindObjectInCone( const Vector &vecOrigin, - const Vector &vecDir, float flCone, float flCombineBallCone, bool bOnlyCombineBalls ) -{ - // Find the nearest physics-based item in a cone in front of me. - CBaseEntity *list[1024]; - float flMaxDist = TraceLength() + 1.0; - float flNearestDist = flMaxDist; - bool bNearestIsCombineBall = bOnlyCombineBalls ? true : false; - Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist ); - Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist ); - - CBaseEntity *pNearest = NULL; - - int count = UTIL_EntitiesInBox( list, 1024, mins, maxs, 0 ); - for( int i = 0 ; i < count ; i++ ) - { - if ( !list[ i ]->VPhysicsGetObject() ) - continue; - - bool bIsCombineBall = FClassnameIs( list[ i ], "prop_combine_ball" ); - if ( !bIsCombineBall && bNearestIsCombineBall ) - continue; - - // Closer than other objects - Vector los; - VectorSubtract( list[ i ]->WorldSpaceCenter(), vecOrigin, los ); - float flDist = VectorNormalize( los ); - - if ( !bIsCombineBall || bNearestIsCombineBall ) - { - // Closer than other objects - if( flDist >= flNearestDist ) - continue; - - // Cull to the cone - if ( DotProduct( los, vecDir ) <= flCone ) - continue; - } - else - { - // Close enough? - if ( flDist >= flMaxDist ) - continue; - - // Cull to the cone - if ( DotProduct( los, vecDir ) <= flCone ) - continue; - - // NOW: If it's either closer than nearest dist or within the ball cone, use it! - if ( (flDist > flNearestDist) && (DotProduct( los, vecDir ) <= flCombineBallCone) ) - continue; - } - - // Make sure it isn't occluded! - trace_t tr; - UTIL_PhyscannonTraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), GetOwner(), &tr ); - if( tr.m_pEnt == list[ i ] ) - { - flNearestDist = flDist; - pNearest = list[ i ]; - bNearestIsCombineBall = bIsCombineBall; - } - } - - return pNearest; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CBaseEntity *CWeaponPhysCannon::FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ) -{ - // Find the nearest physics-based item in a cone in front of me. - CBaseEntity *list[256]; - float flNearestDist = physcannon_tracelength.GetFloat() + 1.0; //Use regular distance. - Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist ); - Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist ); - - CBaseEntity *pNearest = NULL; - - int count = UTIL_EntitiesInBox( list, 256, mins, maxs, 0 ); - for( int i = 0 ; i < count ; i++ ) - { - if ( !list[ i ]->VPhysicsGetObject() ) - continue; - - // Closer than other objects - Vector los = ( list[ i ]->WorldSpaceCenter() - vecOrigin ); - float flDist = VectorNormalize( los ); - if( flDist >= flNearestDist ) - continue; - - // Cull to the cone - if ( DotProduct( los, vecDir ) <= flCone ) - continue; - - // Make sure it isn't occluded! - trace_t tr; - UTIL_PhyscannonTraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), GetOwner(), &tr ); - if( tr.m_pEnt == list[ i ] ) - { - flNearestDist = flDist; - pNearest = list[ i ]; - } - } - - return pNearest; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError ) -{ - CBaseEntity *pEntity = GetAttached(); - if ( !pEntity || ComputeError() > flError || pPlayer->GetGroundEntity() == pEntity || !pEntity->VPhysicsGetObject() ) - { - return false; - } - - //Adrian: Oops, our object became motion disabled, let go! - IPhysicsObject *pPhys = pEntity->VPhysicsGetObject(); - if ( pPhys && pPhys->IsMoveable() == false ) - { - return false; - } - - Vector forward, right, up; - QAngle playerAngles = pPlayer->EyeAngles(); - AngleVectors( playerAngles, &forward, &right, &up ); - - if ( HL2GameRules()->MegaPhyscannonActive() ) - { - Vector los = ( pEntity->WorldSpaceCenter() - pPlayer->Weapon_ShootPosition() ); - VectorNormalize( los ); - - float flDot = DotProduct( los, forward ); - - //Let go of the item if we turn around too fast. - if ( flDot <= 0.35f ) - return false; - } - - // If this is set to true, the player will be allowed to lift the held object to - // a position directly over their own head. Otherwise, this behavior is prevented (default). - bool bAllowObjectOverhead = UTIL_IsCombineBallDefinite(pEntity); - - - - float pitch = AngleDistance(playerAngles.x,0); - - if( !bAllowObjectOverhead ) - { - playerAngles.x = clamp( pitch, -75, 75 ); - } - else - { - playerAngles.x = clamp( pitch, -90, 75 ); - } - - - - // Now clamp a sphere of object radius at end to the player's bbox - Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -forward ); - Vector player2d = pPlayer->CollisionProp()->OBBMaxs(); - float playerRadius = player2d.Length2D(); - float radius = playerRadius + fabs(DotProduct( forward, radial )); - - float distance = 24 + ( radius * 2.0f ); - - // Add the prop's distance offset - distance += m_flDistanceOffset; - - Vector start = pPlayer->Weapon_ShootPosition(); - Vector end = start + ( forward * distance ); - - trace_t tr; - CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE ); - Ray_t ray; - ray.Init( start, end ); - enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr ); - - if ( tr.fraction < 0.5 ) - { - end = start + forward * (radius*0.5f); - } - else if ( tr.fraction <= 1.0f ) - { - end = start + forward * ( distance - radius ); - } - Vector playerMins, playerMaxs, nearest; - pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs ); - Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter(); - CalcClosestPointOnLine( end, playerLine+Vector(0,0,playerMins.z), playerLine+Vector(0,0,playerMaxs.z), nearest, NULL ); - - if( !bAllowObjectOverhead ) - { - Vector delta = end - nearest; - float len = VectorNormalize(delta); - if ( len < radius ) - { - end = nearest + radius * delta; - } - } - - //Show overlays of radius - if ( g_debug_physcannon.GetBool() ) - { - NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 ); - - NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(), - -Vector( radius, radius, radius), - Vector( radius, radius, radius ), - 255, 0, 0, - true, - 0.0f ); - } - - QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer ); - - // If it has a preferred orientation, update to ensure we're still oriented correctly. - Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); - - // We may be holding a prop that has preferred carry angles - if ( m_bHasPreferredCarryAngles ) - { - matrix3x4_t tmp; - ComputePlayerMatrix( pPlayer, tmp ); - angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp ); - } - - matrix3x4_t attachedToWorld; - Vector offset; - AngleMatrix( angles, attachedToWorld ); - VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset ); - - SetTargetPosition( end - offset, angles ); - - return true; -} - -void CWeaponPhysCannon::UpdateObject( void ) -{ - CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); - Assert( pPlayer ); - - float flError = IsMegaPhysCannon() ? 18 : 12; - if ( !m_grabController.UpdateObject( pPlayer, flError ) ) - { - DetachObject(); - return; - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched ) -{ - if ( m_bActive == false ) - return; - - CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( GetOwner() ); - if( pOwner != NULL ) - { - pOwner->EnableSprint( true ); - pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); - - if( wasLaunched ) - { - pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAG_RESTART ); - } - } - - CBaseEntity *pObject = m_grabController.GetAttached(); - - m_grabController.DetachEntity( wasLaunched ); - - if ( pObject != NULL ) - { - Pickup_OnPhysGunDrop( pObject, pOwner, wasLaunched ? LAUNCHED_BY_CANNON : DROPPED_BY_CANNON ); - } - - // Stop our looping sound - if ( GetMotorSound() ) - { - (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); - (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); - } - - m_bActive = false; - - if ( playSound ) - { - //Play the detach sound - WeaponSound( MELEE_MISS ); - } - - RecordThrownObject( pObject ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::ItemPreFrame() -{ - BaseClass::ItemPreFrame(); - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - - if ( pOwner == NULL ) - return; - - m_flElementPosition = UTIL_Approach( m_flElementDestination, m_flElementPosition, 0.1f ); - - CBaseViewModel *vm = pOwner->GetViewModel(); - - if ( vm != NULL ) - { - vm->SetPoseParameter( "active", m_flElementPosition ); - } - - // Update the object if the weapon is switched on. - if( m_bActive ) - { - UpdateObject(); - } - - if( gpGlobals->curtime >= m_flTimeNextObjectPurge ) - { - PurgeThrownObjects(); - m_flTimeNextObjectPurge = gpGlobals->curtime + 0.5f; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::CheckForTarget( void ) -{ - //See if we're suppressing this - if ( m_flCheckSuppressTime > gpGlobals->curtime ) - return; - - // holstered - if ( IsEffectActive( EF_NODRAW ) ) - return; - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - - if ( pOwner == NULL ) - return; - - if ( m_bActive ) - return; - - Vector aimDir; - pOwner->EyeVectors( &aimDir ); - - Vector startPos = pOwner->Weapon_ShootPosition(); - Vector endPos; - VectorMA( startPos, TraceLength(), aimDir, endPos ); - - trace_t tr; - UTIL_PhyscannonTraceHull( startPos, endPos, -Vector(4,4,4), Vector(4,4,4), GetOwner(), &tr ); - - if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt != NULL ) ) - { - // FIXME: Try just having the elements always open when pointed at a physics object - if ( CanPickupObject( tr.m_pEnt ) || Pickup_ForcePhysGunOpen( tr.m_pEnt, pOwner ) ) - // if ( ( tr.m_pEnt->VPhysicsGetObject() != NULL ) && ( tr.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS ) ) - { - m_nChangeState = ELEMENT_STATE_NONE; - OpenElements(); - return; - } - } - - // Close the elements after a delay to prevent overact state switching - if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState == ELEMENT_STATE_NONE ) ) - { - m_nChangeState = ELEMENT_STATE_CLOSED; - m_flElementDebounce = gpGlobals->curtime + 0.5f; - } -} - - -//----------------------------------------------------------------------------- -// Begin upgrading! -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::BeginUpgrade() -{ - if ( IsMegaPhysCannon() ) - return; - - if ( m_bIsCurrentlyUpgrading ) - return; - - SetSequence( SelectWeightedSequence( ACT_PHYSCANNON_UPGRADE ) ); - ResetSequenceInfo(); - - m_bIsCurrentlyUpgrading = true; - - SetContextThink( &CWeaponPhysCannon::WaitForUpgradeThink, gpGlobals->curtime + 6.0f, s_pWaitForUpgradeContext ); - - EmitSound( "WeaponDissolve.Charge" ); - - // Bloat our bounds - CollisionProp()->UseTriggerBounds( true, 32.0f ); - - // Turn on the new skin - m_nSkin = MEGACANNON_SKIN; -} - - -//----------------------------------------------------------------------------- -// Wait until we're done upgrading -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::WaitForUpgradeThink() -{ - Assert( m_bIsCurrentlyUpgrading ); - - StudioFrameAdvance(); - if ( !IsActivityFinished() ) - { - SetContextThink( &CWeaponPhysCannon::WaitForUpgradeThink, gpGlobals->curtime + 0.1f, s_pWaitForUpgradeContext ); - return; - } - - if ( !GlobalEntity_IsInTable( "super_phys_gun" ) ) - { - GlobalEntity_Add( MAKE_STRING("super_phys_gun"), gpGlobals->mapname, GLOBAL_ON ); - } - else - { - GlobalEntity_SetState( MAKE_STRING("super_phys_gun"), GLOBAL_ON ); - } - m_bIsCurrentlyUpgrading = false; - - // This is necessary to get the effects to look different - DestroyEffects(); - - // HACK: Hacky notification back to the level that we've finish upgrading - CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, "script_physcannon_upgrade" ); - if ( pEnt ) - { - variant_t emptyVariant; - pEnt->AcceptInput( "Trigger", this, this, emptyVariant, 0 ); - } - - StopSound( "WeaponDissolve.Charge" ); - - // Re-enable weapon pickup - AddSolidFlags( FSOLID_TRIGGER ); - - SetContextThink( NULL, gpGlobals->curtime, s_pWaitForUpgradeContext ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoEffectIdle( void ) -{ - if ( IsEffectActive( EF_NODRAW ) ) - { - StopEffects(); - return; - } - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - - if ( pOwner == NULL ) - return; - - if ( m_bPhyscannonState != IsMegaPhysCannon() ) - { - DestroyEffects(); - StartEffects(); - - m_bPhyscannonState = IsMegaPhysCannon(); - - //This means we just switched to regular physcannon this frame. - if ( m_bPhyscannonState == false ) - { - EmitSound( "Weapon_Physgun.Off" ); - -#ifdef HL2_EPISODIC - ForceDrop(); - - CHL2_Player *pPlayer = dynamic_cast( pOwner ); - - if ( pPlayer ) - { - pPlayer->StartArmorReduction(); - } -#endif - - CCitadelEnergyCore *pCore = static_cast( CreateEntityByName( "env_citadel_energy_core" ) ); - - if ( pCore == NULL ) - return; - - CBaseAnimating *pBeamEnt = pOwner->GetViewModel(); - - if ( pBeamEnt ) - { - int iAttachment = pBeamEnt->LookupAttachment( "muzzle" ); - - Vector vOrigin; - QAngle vAngle; - - pBeamEnt->GetAttachment( iAttachment, vOrigin, vAngle ); - - pCore->SetAbsOrigin( vOrigin ); - pCore->SetAbsAngles( vAngle ); - - DispatchSpawn( pCore ); - pCore->Activate(); - - pCore->SetParent( pBeamEnt, iAttachment ); - pCore->SetScale( 2.5f ); - - variant_t variant; - variant.SetFloat( 1.0f ); - - g_EventQueue.AddEvent( pCore, "StartDischarge", 0, pOwner, pOwner ); - g_EventQueue.AddEvent( pCore, "Stop", variant, 1, pOwner, pOwner ); - - pCore->SetThink ( &CCitadelEnergyCore::SUB_Remove ); - pCore->SetNextThink( gpGlobals->curtime + 10.0f ); - - m_nSkin = 0; - } - } - } - - float flScaleFactor = SpriteScaleFactor(); - - // Flicker the end sprites - if ( ( m_hEndSprites[0] != NULL ) && ( m_hEndSprites[1] != NULL ) ) - { - //Make the end points flicker as fast as possible - //FIXME: Make this a property of the CSprite class! - for ( int i = 0; i < 2; i++ ) - { - m_hEndSprites[i]->SetBrightness( random->RandomInt( 200, 255 ) ); - m_hEndSprites[i]->SetScale( random->RandomFloat( 0.1, 0.15 ) * flScaleFactor ); - } - } - - // Flicker the glow sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] == NULL ) - continue; - - if ( IsMegaPhysCannon() ) - { - m_hGlowSprites[i]->SetBrightness( random->RandomInt( 32, 48 ) ); - m_hGlowSprites[i]->SetScale( random->RandomFloat( 0.15, 0.2 ) * flScaleFactor ); - } - else - { - m_hGlowSprites[i]->SetBrightness( random->RandomInt( 16, 24 ) ); - m_hGlowSprites[i]->SetScale( random->RandomFloat( 0.3, 0.35 ) * flScaleFactor ); - } - } - - // Only do these effects on the mega-cannon - if ( IsMegaPhysCannon() ) - { - // Randomly arc between the elements and core - if ( random->RandomInt( 0, 100 ) == 0 && !engine->IsPaused() ) - { - CBeam *pBeam = CBeam::BeamCreate( MEGACANNON_BEAM_SPRITE, 1 ); - - CBaseEntity *pBeamEnt = pOwner->GetViewModel(); - pBeam->EntsInit( pBeamEnt, pBeamEnt ); - - int startAttachment; - int sprite; - - if ( random->RandomInt( 0, 1 ) ) - { - startAttachment = LookupAttachment( "fork1t" ); - sprite = 0; - } - else - { - startAttachment = LookupAttachment( "fork2t" ); - sprite = 1; - } - - int endAttachment = 1; - - pBeam->SetStartAttachment( startAttachment ); - pBeam->SetEndAttachment( endAttachment ); - pBeam->SetNoise( random->RandomFloat( 8.0f, 16.0f ) ); - pBeam->SetColor( 255, 255, 255 ); - pBeam->SetScrollRate( 25 ); - pBeam->SetBrightness( 128 ); - pBeam->SetWidth( 1 ); - pBeam->SetEndWidth( random->RandomFloat( 2, 8 ) ); - - float lifetime = random->RandomFloat( 0.2f, 0.4f ); - - pBeam->LiveForTime( lifetime ); - - if ( m_hEndSprites[sprite] != NULL ) - { - // Turn on the sprite for awhile - m_hEndSprites[sprite]->TurnOn(); - m_flEndSpritesOverride[sprite] = gpGlobals->curtime + lifetime; - EmitSound( "Weapon_MegaPhysCannon.ChargeZap" ); - } - } - - if ( m_hCenterSprite != NULL ) - { - if ( m_EffectState == EFFECT_HOLDING ) - { - m_hCenterSprite->SetBrightness( random->RandomInt( 32, 64 ) ); - m_hCenterSprite->SetScale( random->RandomFloat( 0.2, 0.25 ) * flScaleFactor ); - } - else - { - m_hCenterSprite->SetBrightness( random->RandomInt( 32, 64 ) ); - m_hCenterSprite->SetScale( random->RandomFloat( 0.125, 0.15 ) * flScaleFactor ); - } - } - - if ( m_hBlastSprite != NULL ) - { - if ( m_EffectState == EFFECT_HOLDING ) - { - m_hBlastSprite->SetBrightness( random->RandomInt( 125, 150 ) ); - m_hBlastSprite->SetScale( random->RandomFloat( 0.125, 0.15 ) * flScaleFactor ); - } - else - { - m_hBlastSprite->SetBrightness( random->RandomInt( 32, 64 ) ); - m_hBlastSprite->SetScale( random->RandomFloat( 0.075, 0.15 ) * flScaleFactor ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Update our idle effects even when deploying -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::ItemBusyFrame( void ) -{ - DoEffectIdle(); - - BaseClass::ItemBusyFrame(); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::ItemPostFrame() -{ - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner == NULL ) - { - // We found an object. Debounce the button - m_nAttack2Debounce = 0; - return; - } - - //Check for object in pickup range - if ( m_bActive == false ) - { - CheckForTarget(); - - if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState != ELEMENT_STATE_NONE ) ) - { - if ( m_nChangeState == ELEMENT_STATE_OPEN ) - { - OpenElements(); - } - else if ( m_nChangeState == ELEMENT_STATE_CLOSED ) - { - CloseElements(); - } - - m_nChangeState = ELEMENT_STATE_NONE; - } - } - - // NOTE: Attack2 will be considered to be pressed until the first item is picked up. - int nAttack2Mask = pOwner->m_nButtons & (~m_nAttack2Debounce); - if ( nAttack2Mask & IN_ATTACK2 ) - { - SecondaryAttack(); - } - else - { - // Reset our debouncer - m_flLastDenySoundPlayed = false; - - if ( m_bActive == false ) - { - DoEffect( EFFECT_READY ); - } - } - - if (( pOwner->m_nButtons & IN_ATTACK2 ) == 0 ) - { - m_nAttack2Debounce = 0; - } - - if ( pOwner->m_nButtons & IN_ATTACK ) - { - PrimaryAttack(); - } - else - { - WeaponIdle(); - } - - if ( hl2_episodic.GetBool() == true ) - { - if ( IsMegaPhysCannon() ) - { - if ( !( pOwner->m_nButtons & IN_ATTACK ) ) - { - m_flNextPrimaryAttack = gpGlobals->curtime; - } - } - } - - // Update our idle effects (flickers, etc) - DoEffectIdle(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -#define PHYSCANNON_DANGER_SOUND_RADIUS 128 - -void CWeaponPhysCannon::LaunchObject( const Vector &vecDir, float flForce ) -{ - // FIRE!!! - if( m_grabController.GetAttached() ) - { - CBaseEntity *pObject = m_grabController.GetAttached(); - - DetachObject( false, true ); - - // Trace ahead a bit and make a chain of danger sounds ahead of the phys object - // to scare potential targets - trace_t tr; - Vector vecStart = pObject->GetAbsOrigin(); - Vector vecSpot; - int iLength; - int i; - - UTIL_TraceLine( vecStart, vecStart + vecDir * flForce, MASK_SHOT, pObject, COLLISION_GROUP_NONE, &tr ); - iLength = ( tr.startpos - tr.endpos ).Length(); - vecSpot = vecStart + vecDir * PHYSCANNON_DANGER_SOUND_RADIUS; - - for( i = PHYSCANNON_DANGER_SOUND_RADIUS ; i < iLength ; i += PHYSCANNON_DANGER_SOUND_RADIUS ) - { - CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, PHYSCANNON_DANGER_SOUND_RADIUS, 0.5, pObject ); - vecSpot = vecSpot + ( vecDir * PHYSCANNON_DANGER_SOUND_RADIUS ); - } - - // Launch - ApplyVelocityBasedForce( pObject, vecDir ); - - // Don't allow the gun to regrab a thrown object!! - m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; - - Vector center = pObject->WorldSpaceCenter(); - - //Do repulse effect - DoEffect( EFFECT_LAUNCH, ¢er ); - } - - // Stop our looping sound - if ( GetMotorSound() ) - { - (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); - (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); - } - - //Close the elements and suppress checking for a bit - m_nChangeState = ELEMENT_STATE_CLOSED; - m_flElementDebounce = gpGlobals->curtime + 0.1f; - m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CWeaponPhysCannon::IsFlesh( CBaseEntity *pTarget ) -{ - Assert( pTarget ); - IPhysicsObject *pList[64]; - int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); - for ( int i = 0; i < count; i++ ) - { - int material = pList[i]->GetMaterialIndex(); - surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material ); - // Is flesh ?, don't allow pickup - if ( pSurfaceData->game.material == 'A' || pSurfaceData->game.material == 'F' || pSurfaceData->game.material == 'B' || pSurfaceData->game.material == 'H' ) - return true; - } - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pTarget - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget ) -{ - if ( pTarget == NULL ) - return false; - - if ( pTarget->GetBaseAnimating() && pTarget->GetBaseAnimating()->IsDissolving() ) - return false; - - if ( pTarget->HasSpawnFlags( SF_PHYSBOX_ALWAYS_PICK_UP ) || pTarget->HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) ) - { - // It may seem strange to check this spawnflag before we know the class of this object, since the - // spawnflag only applies to func_physbox, but it can act as a filter of sorts to reduce the number - // of irrelevant entities that fall through to this next casting check, which is slower. - CPhysBox *pPhysBox = dynamic_cast(pTarget); - - if ( pPhysBox != NULL ) - { - if ( pTarget->HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) ) - return false; - else - return true; - } - } - - if ( pTarget->HasSpawnFlags(SF_PHYSPROP_ALWAYS_PICK_UP) ) - { - // It may seem strange to check this spawnflag before we know the class of this object, since the - // spawnflag only applies to func_physbox, but it can act as a filter of sorts to reduce the number - // of irrelevant entities that fall through to this next casting check, which is slower. - CPhysicsProp *pPhysProp = dynamic_cast(pTarget); - if ( pPhysProp != NULL ) - return true; - } - - if ( pTarget->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) - return false; - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner && pOwner->GetGroundEntity() == pTarget ) - return false; - - if ( !IsMegaPhysCannon() ) - { - if ( IsFlesh( pTarget ) ) - return false; - return CBasePlayer::CanPickupObject( pTarget, physcannon_maxmass.GetFloat(), 0 ); - } - - if ( pTarget->IsNPC() && pTarget->MyNPCPointer()->CanBecomeRagdoll() ) - return true; - - if ( dynamic_cast(pTarget) ) - return true; - - return CBasePlayer::CanPickupObject( pTarget, 0, 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::OpenElements( void ) -{ - if ( m_bOpen ) - return; - - WeaponSound( SPECIAL2 ); - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - - if ( pOwner == NULL ) - return; - - if( !IsMegaPhysCannon() ) - { - pOwner->RumbleEffect(RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_RESTART|RUMBLE_FLAG_LOOP); - } - - if ( m_flElementPosition < 0.0f ) - m_flElementPosition = 0.0f; - - m_flElementDestination = 1.0f; - - SendWeaponAnim( ACT_VM_IDLE ); - - m_bOpen = true; - - DoEffect( EFFECT_READY ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::CloseElements( void ) -{ - // The mega cannon cannot be closed! - if ( IsMegaPhysCannon() ) - { - OpenElements(); - return; - } - - if ( m_bOpen == false ) - return; - - WeaponSound( MELEE_HIT ); - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - - if ( pOwner == NULL ) - return; - - pOwner->RumbleEffect(RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_STOP); - - if ( m_flElementPosition > 1.0f ) - m_flElementPosition = 1.0f; - - m_flElementDestination = 0.0f; - - SendWeaponAnim( ACT_VM_IDLE ); - - m_bOpen = false; - - if ( GetMotorSound() ) - { - (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); - (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); - } - - DoEffect( EFFECT_CLOSED ); -} - -#define PHYSCANNON_MAX_MASS 500 - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : float -//----------------------------------------------------------------------------- -float CWeaponPhysCannon::GetLoadPercentage( void ) -{ - float loadWeight = m_grabController.GetLoadWeight(); - loadWeight /= physcannon_maxmass.GetFloat(); - loadWeight = clamp( loadWeight, 0.0f, 1.0f ); - return loadWeight; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : CSoundPatch -//----------------------------------------------------------------------------- -CSoundPatch *CWeaponPhysCannon::GetMotorSound( void ) -{ - if ( m_sndMotor == NULL ) - { - CPASAttenuationFilter filter( this ); - - if ( IsMegaPhysCannon() ) - { - m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_MegaPhysCannon.HoldSound", ATTN_NORM ); - } - else - { - m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_PhysCannon.HoldSound", ATTN_NORM ); - } - } - - return m_sndMotor; -} - - -//----------------------------------------------------------------------------- -// Shuts down sounds -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::StopLoopingSounds() -{ - if ( m_sndMotor != NULL ) - { - (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndMotor ); - m_sndMotor = NULL; - } - - BaseClass::StopLoopingSounds(); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DestroyEffects( void ) -{ - //Turn off main glow - if ( m_hCenterSprite != NULL ) - { - UTIL_Remove( m_hCenterSprite ); - m_hCenterSprite = NULL; - } - - if ( m_hBlastSprite != NULL ) - { - UTIL_Remove( m_hBlastSprite ); - m_hBlastSprite = NULL; - } - - // Turn off beams - for ( int i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] != NULL ) - { - UTIL_Remove( m_hBeams[i] ); - m_hBeams[i] = NULL; - } - } - - // Turn off sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] != NULL ) - { - UTIL_Remove( m_hGlowSprites[i] ); - m_hGlowSprites[i] = NULL; - } - } - - for ( int i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] != NULL ) - { - UTIL_Remove( m_hEndSprites[i] ); - m_hEndSprites[i] = NULL; - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::StopEffects( bool stopSound ) -{ - // Turn off our effect state - DoEffect( EFFECT_NONE ); - - //Turn off main glow - if ( m_hCenterSprite != NULL ) - { - m_hCenterSprite->TurnOff(); - } - - if ( m_hBlastSprite != NULL ) - { - m_hBlastSprite->TurnOff(); - } - - //Turn off beams - for ( int i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] != NULL ) - { - m_hBeams[i]->SetBrightness( 0 ); - } - } - - //Turn off sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] != NULL ) - { - m_hGlowSprites[i]->TurnOff(); - } - } - - for ( int i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] != NULL ) - { - m_hEndSprites[i]->TurnOff(); - } - } - - //Shut off sounds - if ( stopSound && GetMotorSound() != NULL ) - { - (CSoundEnvelopeController::GetController()).SoundFadeOut( GetMotorSound(), 0.1f ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::StartEffects( void ) -{ - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner == NULL ) - return; - - bool bIsMegaCannon = IsMegaPhysCannon(); - - int i; - float flScaleFactor = SpriteScaleFactor(); - CBaseEntity *pBeamEnt = pOwner->GetViewModel(); - - // Create the beams - for ( i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] ) - continue; - - const char *beamAttachNames[] = - { - "fork1t", - "fork2t", - "fork1t", - "fork2t", - "fork1t", - "fork2t", - }; - - m_hBeams[i] = CBeam::BeamCreate( - bIsMegaCannon ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 1.0f ); - m_hBeams[i]->EntsInit( pBeamEnt, pBeamEnt ); - - int startAttachment = LookupAttachment( beamAttachNames[i] ); - int endAttachment = 1; - - m_hBeams[i]->FollowEntity( pBeamEnt ); - - m_hBeams[i]->AddSpawnFlags( SF_BEAM_TEMPORARY ); - m_hBeams[i]->SetStartAttachment( startAttachment ); - m_hBeams[i]->SetEndAttachment( endAttachment ); - m_hBeams[i]->SetNoise( random->RandomFloat( 8.0f, 16.0f ) ); - m_hBeams[i]->SetColor( 255, 255, 255 ); - m_hBeams[i]->SetScrollRate( 25 ); - m_hBeams[i]->SetBrightness( 128 ); - m_hBeams[i]->SetWidth( 0 ); - m_hBeams[i]->SetEndWidth( random->RandomFloat( 2, 4 ) ); - } - - //Create the glow sprites - for ( i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] ) - continue; - - const char *attachNames[] = - { - "fork1b", - "fork1m", - "fork1t", - "fork2b", - "fork2m", - "fork2t" - }; - - m_hGlowSprites[i] = CSprite::SpriteCreate( - bIsMegaCannon ? MEGACANNON_GLOW_SPRITE : PHYSCANNON_GLOW_SPRITE, - GetAbsOrigin(), false ); - - m_hGlowSprites[i]->SetAsTemporary(); - - m_hGlowSprites[i]->SetAttachment( pOwner->GetViewModel(), LookupAttachment( attachNames[i] ) ); - - if ( bIsMegaCannon ) - { - m_hGlowSprites[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 128, kRenderFxNone ); - } - else - { - m_hGlowSprites[i]->SetTransparency( kRenderTransAdd, 255, 128, 0, 64, kRenderFxNoDissipation ); - } - - m_hGlowSprites[i]->SetBrightness( 255, 0.2f ); - m_hGlowSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f ); - } - - //Create the endcap sprites - for ( i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] == NULL ) - { - const char *attachNames[] = - { - "fork1t", - "fork2t" - }; - - m_hEndSprites[i] = CSprite::SpriteCreate( - bIsMegaCannon ? MEGACANNON_ENDCAP_SPRITE : PHYSCANNON_ENDCAP_SPRITE, - GetAbsOrigin(), false ); - - m_hEndSprites[i]->SetAsTemporary(); - m_hEndSprites[i]->SetAttachment( pOwner->GetViewModel(), LookupAttachment( attachNames[i] ) ); - m_hEndSprites[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); - m_hEndSprites[i]->SetBrightness( 255, 0.2f ); - m_hEndSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f ); - m_hEndSprites[i]->TurnOff(); - } - } - - //Create the center glow - if ( m_hCenterSprite == NULL ) - { - m_hCenterSprite = CSprite::SpriteCreate( - bIsMegaCannon ? MEGACANNON_CENTER_GLOW : PHYSCANNON_CENTER_GLOW, - GetAbsOrigin(), false ); - - m_hCenterSprite->SetAsTemporary(); - m_hCenterSprite->SetAttachment( pOwner->GetViewModel(), 1 ); - m_hCenterSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone ); - m_hCenterSprite->SetBrightness( 255, 0.2f ); - m_hCenterSprite->SetScale( 0.1f, 0.2f ); - } - - //Create the blast sprite - if ( m_hBlastSprite == NULL ) - { - m_hBlastSprite = CSprite::SpriteCreate( - bIsMegaCannon ? MEGACANNON_BLAST_SPRITE : PHYSCANNON_BLAST_SPRITE, - GetAbsOrigin(), false ); - - m_hBlastSprite->SetAsTemporary(); - m_hBlastSprite->SetAttachment( pOwner->GetViewModel(), 1 ); - m_hBlastSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone ); - m_hBlastSprite->SetBrightness( 255, 0.2f ); - m_hBlastSprite->SetScale( 0.1f, 0.2f ); - m_hBlastSprite->TurnOff(); - } -} - -//----------------------------------------------------------------------------- -// Closing effects -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoEffectClosed( void ) -{ - float flScaleFactor = SpriteScaleFactor(); - - // Turn off the center sprite - if ( m_hCenterSprite != NULL ) - { - m_hCenterSprite->SetBrightness( 0.0, 0.1f ); - m_hCenterSprite->SetScale( 0.0f, 0.1f ); - m_hCenterSprite->TurnOff(); - } - - // Turn off the end-caps - for ( int i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] != NULL ) - { - m_hEndSprites[i]->TurnOff(); - } - } - - // Turn off the lightning - for ( int i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] != NULL ) - { - m_hBeams[i]->SetBrightness( 0 ); - } - } - - // Turn on the glow sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] != NULL ) - { - m_hGlowSprites[i]->TurnOn(); - m_hGlowSprites[i]->SetBrightness( 16.0f, 0.2f ); - m_hGlowSprites[i]->SetScale( 0.3f * flScaleFactor, 0.2f ); - } - } - - // Prepare for scale down - if ( m_hBlastSprite != NULL ) - { - m_hBlastSprite->TurnOn(); - m_hBlastSprite->SetScale( 1.0f, 0.0f ); - m_hBlastSprite->SetBrightness( 0, 0.0f ); - } -} - -//----------------------------------------------------------------------------- -// Closing effects -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoMegaEffectClosed( void ) -{ - float flScaleFactor = SpriteScaleFactor(); - - // Turn off the center sprite - if ( m_hCenterSprite != NULL ) - { - m_hCenterSprite->SetBrightness( 0.0, 0.1f ); - m_hCenterSprite->SetScale( 0.0f, 0.1f ); - m_hCenterSprite->TurnOff(); - } - - // Turn off the end-caps - for ( int i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] != NULL ) - { - m_hEndSprites[i]->TurnOff(); - } - } - - // Turn off the lightning - for ( int i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] != NULL ) - { - m_hBeams[i]->SetBrightness( 0 ); - } - } - - // Turn on the glow sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] != NULL ) - { - m_hGlowSprites[i]->TurnOn(); - m_hGlowSprites[i]->SetBrightness( 16.0f, 0.2f ); - m_hGlowSprites[i]->SetScale( 0.3f * flScaleFactor, 0.2f ); - } - } - - // Prepare for scale down - if ( m_hBlastSprite != NULL ) - { - m_hBlastSprite->TurnOn(); - m_hBlastSprite->SetScale( 1.0f, 0.0f ); - m_hBlastSprite->SetBrightness( 0, 0.0f ); - } -} - -//----------------------------------------------------------------------------- -// Ready effects -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoEffectReady( ) -{ - float flScaleFactor = SpriteScaleFactor(); - - //Turn on the center sprite - if ( m_hCenterSprite != NULL ) - { - m_hCenterSprite->SetBrightness( 128, 0.2f ); - m_hCenterSprite->SetScale( 0.15f, 0.2f ); - m_hCenterSprite->TurnOn(); - } - - //Turn off the end-caps - for ( int i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] != NULL ) - { - m_hEndSprites[i]->TurnOff(); - } - } - - //Turn off the lightning - for ( int i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] != NULL ) - { - m_hBeams[i]->SetBrightness( 0 ); - } - } - - //Turn on the glow sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] != NULL ) - { - m_hGlowSprites[i]->TurnOn(); - m_hGlowSprites[i]->SetBrightness( 32.0f, 0.2f ); - m_hGlowSprites[i]->SetScale( 0.4f * flScaleFactor, 0.2f ); - } - } - - //Scale down - if ( m_hBlastSprite != NULL ) - { - m_hBlastSprite->TurnOn(); - m_hBlastSprite->SetScale( 0.1f, 0.2f ); - m_hBlastSprite->SetBrightness( 255, 0.1f ); - } -} - - -//----------------------------------------------------------------------------- -// Holding effects -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoEffectHolding( ) -{ - float flScaleFactor = SpriteScaleFactor(); - - // Turn off the center sprite - if ( m_hCenterSprite != NULL ) - { - m_hCenterSprite->SetBrightness( 255, 0.1f ); - m_hCenterSprite->SetScale( 0.2f, 0.2f ); - m_hCenterSprite->TurnOn(); - } - - // Turn off the end-caps - for ( int i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] != NULL ) - { - m_hEndSprites[i]->TurnOn(); - } - } - - // Turn off the lightning - for ( int i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] != NULL ) - { - m_hBeams[i]->SetBrightness( 128 ); - } - } - - // Turn on the glow sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] != NULL ) - { - m_hGlowSprites[i]->TurnOn(); - m_hGlowSprites[i]->SetBrightness( 64.0f, 0.2f ); - m_hGlowSprites[i]->SetScale( 0.5f * flScaleFactor, 0.2f ); - } - } - - // Prepare for scale up - if ( m_hBlastSprite != NULL ) - { - m_hBlastSprite->TurnOff(); - m_hBlastSprite->SetScale( 0.1f, 0.0f ); - m_hBlastSprite->SetBrightness( 0, 0.0f ); - } -} - - -//----------------------------------------------------------------------------- -// Launch effects -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoEffectLaunch( Vector *pos ) -{ - Assert( pos ); - if ( pos == NULL ) - return; - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner == NULL ) - return; - - Vector endpos = *pos; - - // Check to store off our view model index - CBeam *pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 8 ); - - if ( pBeam != NULL ) - { - pBeam->PointEntInit( endpos, this ); - pBeam->SetEndAttachment( 1 ); - pBeam->SetWidth( 6.4 ); - pBeam->SetEndWidth( 12.8 ); - pBeam->SetBrightness( 255 ); - pBeam->SetColor( 255, 255, 255 ); - pBeam->LiveForTime( 0.1f ); - pBeam->RelinkBeam(); - pBeam->SetNoise( 2 ); - } - - Vector shotDir = ( endpos - pOwner->Weapon_ShootPosition() ); - VectorNormalize( shotDir ); - - //End hit - //FIXME: Probably too big - CPVSFilter filter( endpos ); - te->GaussExplosion( filter, 0.0f, endpos - ( shotDir * 4.0f ), RandomVector(-1.0f, 1.0f), 0 ); - - if ( m_hBlastSprite != NULL ) - { - m_hBlastSprite->TurnOn(); - m_hBlastSprite->SetScale( 2.0f, 0.1f ); - m_hBlastSprite->SetBrightness( 0.0f, 0.1f ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pos - -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoMegaEffectLaunch( Vector *pos ) -{ - Assert( pos ); - if ( pos == NULL ) - return; - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner == NULL ) - return; - - Vector endpos = *pos; - - // Check to store off our view model index - CBaseViewModel *vm = pOwner->GetViewModel(); - - int numBeams = random->RandomInt( 1, 2 ); - - CBeam *pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 0.8 ); - - if ( pBeam != NULL ) - { - pBeam->PointEntInit( endpos, vm ); - pBeam->SetEndAttachment( 1 ); - pBeam->SetWidth( 2 ); - pBeam->SetEndWidth( 12 ); - pBeam->SetBrightness( 255 ); - pBeam->SetColor( 255, 255, 255 ); - pBeam->LiveForTime( 0.1f ); - pBeam->RelinkBeam(); - pBeam->SetNoise( 0 ); - } - - for ( int i = 0; i < numBeams; i++ ) - { - pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 0.8 ); - - if ( pBeam != NULL ) - { - pBeam->PointEntInit( endpos, vm ); - pBeam->SetEndAttachment( 1 ); - pBeam->SetWidth( 2 ); - pBeam->SetEndWidth( random->RandomInt( 1, 2 ) ); - pBeam->SetBrightness( 255 ); - pBeam->SetColor( 255, 255, 255 ); - pBeam->LiveForTime( 0.1f ); - pBeam->RelinkBeam(); - pBeam->SetNoise( random->RandomInt( 8, 12 ) ); - } - } - - Vector shotDir = ( endpos - pOwner->Weapon_ShootPosition() ); - VectorNormalize( shotDir ); - - //End hit - //FIXME: Probably too big - CPVSFilter filter( endpos ); - te->GaussExplosion( filter, 0.0f, endpos - ( shotDir * 4.0f ), RandomVector(-1.0f, 1.0f), 0 ); -} - -//----------------------------------------------------------------------------- -// Holding effects -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoMegaEffectHolding( void ) -{ - float flScaleFactor = SpriteScaleFactor(); - - // Turn off the center sprite - if ( m_hCenterSprite != NULL ) - { - m_hCenterSprite->SetBrightness( 255, 0.1f ); - m_hCenterSprite->SetScale( 0.2f, 0.2f ); - m_hCenterSprite->TurnOn(); - } - - // Turn off the end-caps - for ( int i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] != NULL ) - { - m_hEndSprites[i]->TurnOn(); - } - } - - // Turn off the lightning - for ( int i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] != NULL ) - { - m_hBeams[i]->SetBrightness( 128 ); - } - } - - // Turn on the glow sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] != NULL ) - { - m_hGlowSprites[i]->TurnOn(); - m_hGlowSprites[i]->SetBrightness( 32.0f, 0.2f ); - m_hGlowSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f ); - } - } -} - -//----------------------------------------------------------------------------- -// Ready effects -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoMegaEffectReady( void ) -{ - float flScaleFactor = SpriteScaleFactor(); - - //Turn on the center sprite - if ( m_hCenterSprite != NULL ) - { - m_hCenterSprite->SetBrightness( 128, 0.2f ); - m_hCenterSprite->SetScale( 0.15f, 0.2f ); - m_hCenterSprite->TurnOn(); - } - - //Turn off the end-caps - for ( int i = 0; i < 2; i++ ) - { - if ( m_hEndSprites[i] != NULL ) - { - if ( m_flEndSpritesOverride[i] < gpGlobals->curtime ) - { - m_hEndSprites[i]->TurnOff(); - } - } - } - - //Turn off the lightning - for ( int i = 0; i < NUM_BEAMS; i++ ) - { - if ( m_hBeams[i] != NULL ) - { - m_hBeams[i]->SetBrightness( 0 ); - } - } - - //Turn on the glow sprites - for ( int i = 0; i < NUM_SPRITES; i++ ) - { - if ( m_hGlowSprites[i] != NULL ) - { - m_hGlowSprites[i]->TurnOn(); - m_hGlowSprites[i]->SetBrightness( 24.0f, 0.2f ); - m_hGlowSprites[i]->SetScale( 0.2f * flScaleFactor, 0.2f ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Shutdown for the weapon when it's holstered -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoEffectNone( void ) -{ - if ( m_hBlastSprite != NULL ) - { - // Become small - m_hBlastSprite->SetScale( 0.001f ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : effectType - -// *pos - -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoMegaEffect( int effectType, Vector *pos ) -{ - switch( effectType ) - { - case EFFECT_CLOSED: - DoMegaEffectClosed(); - break; - - case EFFECT_READY: - DoMegaEffectReady(); - break; - - case EFFECT_HOLDING: - DoMegaEffectHolding(); - break; - - case EFFECT_LAUNCH: - DoMegaEffectLaunch( pos ); - break; - - default: - case EFFECT_NONE: - break; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : effectType - -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos ) -{ - // Make sure we're active - StartEffects(); - - m_EffectState = effectType; - - // Do different effects when upgraded - if ( IsMegaPhysCannon() ) - { - DoMegaEffect( effectType, pos ); - return; - } - - switch( effectType ) - { - case EFFECT_CLOSED: - DoEffectClosed( ); - break; - - case EFFECT_READY: - DoEffectReady( ); - break; - - case EFFECT_HOLDING: - DoEffectHolding(); - break; - - case EFFECT_LAUNCH: - DoEffectLaunch( pos ); - break; - - default: - case EFFECT_NONE: - DoEffectNone(); - break; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iIndex - -// Output : const char -//----------------------------------------------------------------------------- -const char *CWeaponPhysCannon::GetShootSound( int iIndex ) const -{ - // Just do this normally if we're a normal physcannon - if ( PlayerHasMegaPhysCannon() == false ) - return BaseClass::GetShootSound( iIndex ); - - // We override this if we're the charged up version - switch( iIndex ) - { - case EMPTY: - return "Weapon_MegaPhysCannon.DryFire"; - break; - - case SINGLE: - return "Weapon_MegaPhysCannon.Launch"; - break; - - case SPECIAL1: - return "Weapon_MegaPhysCannon.Pickup"; - break; - - case MELEE_MISS: - return "Weapon_MegaPhysCannon.Drop"; - break; - - default: - break; - } - - return BaseClass::GetShootSound( iIndex ); -} - -//----------------------------------------------------------------------------- -// Purpose: Adds the specified object to the list of objects that have been -// propelled by this physgun, along with a timestamp of when the -// object was added to the list. This list is checked when a physics -// object strikes another entity, to resolve whether the player is -// accountable for the impact. -// -// Input : pObject - pointer to the object being thrown by the physcannon. -//----------------------------------------------------------------------------- -void CWeaponPhysCannon::RecordThrownObject( CBaseEntity *pObject ) -{ - thrown_objects_t thrown; - thrown.hEntity = pObject; - thrown.fTimeThrown = gpGlobals->curtime; - - // Get rid of stale and dead objects in the list. - PurgeThrownObjects(); - - // See if this object is already in the list. - int count = m_ThrownEntities.Count(); - - for( int i = 0 ; i < count ; i++ ) - { - if( m_ThrownEntities[i].hEntity == pObject ) - { - // Just update the time. - //Msg("++UPDATING: %s (%d)\n", m_ThrownEntities[i].hEntity->GetClassname(), m_ThrownEntities[i].hEntity->entindex() ); - m_ThrownEntities[i] = thrown; - return; - } - } - - //Msg("++ADDING: %s (%d)\n", pObject->GetClassname(), pObject->entindex() ); - - m_ThrownEntities.AddToTail(thrown); -} - -//----------------------------------------------------------------------------- -// Purpose: Go through the objects in the thrown objects list and discard any -// objects that have gone 'stale'. (Were thrown several seconds ago), or -// have been destroyed or removed. -// -//----------------------------------------------------------------------------- -#define PHYSCANNON_THROWN_LIST_TIMEOUT 10.0f -void CWeaponPhysCannon::PurgeThrownObjects() -{ - bool bListChanged; - - // This is bubble-sorty, but the list is also very short. - do - { - bListChanged = false; - - int count = m_ThrownEntities.Count(); - for( int i = 0 ; i < count ; i++ ) - { - bool bRemove = false; - - if( !m_ThrownEntities[i].hEntity.Get() ) - { - bRemove = true; - } - else if( gpGlobals->curtime > (m_ThrownEntities[i].fTimeThrown + PHYSCANNON_THROWN_LIST_TIMEOUT) ) - { - bRemove = true; - } - else - { - IPhysicsObject *pObject = m_ThrownEntities[i].hEntity->VPhysicsGetObject(); - - if( pObject && pObject->IsAsleep() ) - { - bRemove = true; - } - } - - if( bRemove ) - { - //Msg("--REMOVING: %s (%d)\n", m_ThrownEntities[i].hEntity->GetClassname(), m_ThrownEntities[i].hEntity->entindex() ); - m_ThrownEntities.Remove(i); - bListChanged = true; - break; - } - } - } while( bListChanged ); -} - -bool CWeaponPhysCannon::IsAccountableForObject( CBaseEntity *pObject ) -{ - // Clean out the stale and dead items. - PurgeThrownObjects(); - - // Now if this object is in the list, the player is accountable for it striking something. - int count = m_ThrownEntities.Count(); - - for( int i = 0 ; i < count ; i++ ) - { - if( m_ThrownEntities[i].hEntity == pObject ) - { - return true; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -// EXTERNAL API -//----------------------------------------------------------------------------- -void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIfHoldingThis ) -{ - CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); - if ( pCannon ) - { - if ( pOnlyIfHoldingThis ) - { - pCannon->DropIfEntityHeld( pOnlyIfHoldingThis ); - } - else - { - pCannon->ForceDrop(); - } - } -} - -void PhysCannonBeginUpgrade( CBaseAnimating *pAnim ) -{ - CWeaponPhysCannon *pWeaponPhyscannon = assert_cast< CWeaponPhysCannon* >( pAnim ); - pWeaponPhyscannon->BeginUpgrade(); -} - - -bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupControllerEntity, CBaseEntity *pHeldEntity ) -{ - CPlayerPickupController *pController = dynamic_cast(pPickupControllerEntity); - - return pController ? pController->IsHoldingEntity( pHeldEntity ) : false; -} - - -float PhysCannonGetHeldObjectMass( CBaseCombatWeapon *pActiveWeapon, IPhysicsObject *pHeldObject ) -{ - float mass = 0.0f; - CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); - if ( pCannon ) - { - CGrabController &grab = pCannon->GetGrabController(); - mass = grab.GetSavedMass( pHeldObject ); - } - - return mass; -} - -CBaseEntity *PhysCannonGetHeldEntity( CBaseCombatWeapon *pActiveWeapon ) -{ - CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); - if ( pCannon ) - { - CGrabController &grab = pCannon->GetGrabController(); - return grab.GetAttached(); - } - - return NULL; -} - -CBaseEntity *GetPlayerHeldEntity( CBasePlayer *pPlayer ) -{ - CBaseEntity *pObject = NULL; - CPlayerPickupController *pPlayerPickupController = (CPlayerPickupController *)(pPlayer->GetUseEntity()); - - if ( pPlayerPickupController ) - { - pObject = pPlayerPickupController->GetGrabController().GetAttached(); - } - - return pObject; -} - -bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject ) -{ - CWeaponPhysCannon *pCannon = dynamic_cast(pPhysCannon); - if ( pCannon ) - { - return pCannon->IsAccountableForObject(pObject); - } - - return false; -} - -float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject ) -{ - float mass = 0.0f; - CPlayerPickupController *pController = dynamic_cast(pPickupControllerEntity); - if ( pController ) - { - CGrabController &grab = pController->GetGrabController(); - mass = grab.GetSavedMass( pHeldObject ); - } - return mass; -} +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Physics cannon +// +//=============================================================================// + +#include "cbase.h" +#include "player.h" +#include "gamerules.h" +#include "soundenvelope.h" +#include "engine/IEngineSound.h" +#include "physics.h" +#include "in_buttons.h" +#include "soundent.h" +#include "IEffects.h" +#include "ndebugoverlay.h" +#include "shake.h" +#include "hl2_player.h" +#include "beam_shared.h" +#include "Sprite.h" +#include "util.h" +#include "weapon_physcannon.h" +#include "physics_saverestore.h" +#include "ai_basenpc.h" +#include "player_pickup.h" +#include "physics_prop_ragdoll.h" +#include "globalstate.h" +#include "props.h" +#include "movevars_shared.h" +#include "basehlcombatweapon.h" +#include "te_effect_dispatch.h" +#include "vphysics/friction.h" +#include "saverestore_utlvector.h" +#include "prop_combine_ball.h" +#include "physobj.h" +#include "hl2_gamerules.h" +#include "citadel_effects_shared.h" +#include "eventqueue.h" +#include "model_types.h" +#include "ai_interactions.h" +#include "rumble_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static const char *s_pWaitForUpgradeContext = "WaitForUpgrade"; + +ConVar g_debug_physcannon( "g_debug_physcannon", "0" ); + +ConVar physcannon_minforce( "physcannon_minforce", "700" ); +ConVar physcannon_maxforce( "physcannon_maxforce", "1500" ); +ConVar physcannon_maxmass( "physcannon_maxmass", "250" ); +ConVar physcannon_tracelength( "physcannon_tracelength", "250" ); +ConVar physcannon_mega_tracelength( "physcannon_mega_tracelength", "850" ); +ConVar physcannon_chargetime("physcannon_chargetime", "2" ); +ConVar physcannon_pullforce( "physcannon_pullforce", "4000" ); +ConVar physcannon_mega_pullforce( "physcannon_mega_pullforce", "8000" ); +ConVar physcannon_cone( "physcannon_cone", "0.97" ); +ConVar physcannon_ball_cone( "physcannon_ball_cone", "0.997" ); +ConVar physcannon_punt_cone( "physcannon_punt_cone", "0.997" ); +ConVar player_throwforce( "player_throwforce", "1000" ); + +extern ConVar hl2_normspeed; +extern ConVar hl2_walkspeed; + +#define PHYSCANNON_BEAM_SPRITE "sprites/orangelight1.vmt" +#define PHYSCANNON_GLOW_SPRITE "sprites/glow04_noz.vmt" +#define PHYSCANNON_ENDCAP_SPRITE "sprites/orangeflare1.vmt" +#define PHYSCANNON_CENTER_GLOW "sprites/orangecore1.vmt" +#define PHYSCANNON_BLAST_SPRITE "sprites/orangecore2.vmt" + +#define MEGACANNON_BEAM_SPRITE "sprites/lgtning_noz.vmt" +#define MEGACANNON_GLOW_SPRITE "sprites/blueflare1_noz.vmt" +#define MEGACANNON_ENDCAP_SPRITE "sprites/blueflare1_noz.vmt" +#define MEGACANNON_CENTER_GLOW "effects/fluttercore.vmt" +#define MEGACANNON_BLAST_SPRITE "effects/fluttercore.vmt" + +#define MEGACANNON_RAGDOLL_BOOGIE_SPRITE "sprites/lgtning_noz.vmt" + +#define MEGACANNON_MODEL "models/weapons/v_superphyscannon.mdl" +#define MEGACANNON_SKIN 1 + +// ------------------------------------------------------------------------- +// Physcannon trace filter to handle special cases +// ------------------------------------------------------------------------- + +class CTraceFilterPhyscannon : public CTraceFilterSimple +{ +public: + DECLARE_CLASS( CTraceFilterPhyscannon, CTraceFilterSimple ); + + CTraceFilterPhyscannon( const IHandleEntity *passentity, int collisionGroup ) + : CTraceFilterSimple( NULL, collisionGroup ), m_pTraceOwner( passentity ) { } + + // For this test, we only test against entities (then world brushes afterwards) + virtual TraceType_t GetTraceType() const { return TRACE_ENTITIES_ONLY; } + + bool HasContentsGrate( CBaseEntity *pEntity ) + { + // FIXME: Move this into the GetModelContents() function in base entity + + // Find the contents based on the model type + int nModelType = modelinfo->GetModelType( pEntity->GetModel() ); + if ( nModelType == mod_studio ) + { + CBaseAnimating *pAnim = dynamic_cast(pEntity); + if ( pAnim != NULL ) + { + CStudioHdr *pStudioHdr = pAnim->GetModelPtr(); + if ( pStudioHdr != NULL && (pStudioHdr->contents() & CONTENTS_GRATE) ) + return true; + } + } + else if ( nModelType == mod_brush ) + { + // Brushes poll their contents differently + int contents = modelinfo->GetModelContents( pEntity->GetModelIndex() ); + if ( contents & CONTENTS_GRATE ) + return true; + } + + return false; + } + + virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + // Only skip ourselves (not things we own) + if ( pHandleEntity == m_pTraceOwner ) + return false; + + // Get the entity referenced by this handle + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + if ( pEntity == NULL ) + return false; + + // Handle grate entities differently + if ( HasContentsGrate( pEntity ) ) + { + // See if it's a grabbable physics prop + CPhysicsProp *pPhysProp = dynamic_cast(pEntity); + if ( pPhysProp != NULL ) + return pPhysProp->CanBePickedUpByPhyscannon(); + + // See if it's a grabbable physics prop + if ( FClassnameIs( pEntity, "prop_physics" ) ) + { + CPhysicsProp *pPhysProp = dynamic_cast(pEntity); + if ( pPhysProp != NULL ) + return pPhysProp->CanBePickedUpByPhyscannon(); + + // Somehow had a classname that didn't match the class! + Assert(0); + } + else if ( FClassnameIs( pEntity, "func_physbox" ) ) + { + // Must be a moveable physbox + IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject(); + if ( pPhysObj != NULL && pPhysObj->IsMoveable() ) + return true; + } + + // Don't bother with any other sort of grated entity + return false; + } + + // Use the default rules + return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); + } + +protected: + const IHandleEntity *m_pTraceOwner; +}; + +// We want to test against brushes alone +class CTraceFilterOnlyBrushes : public CTraceFilterSimple +{ +public: + DECLARE_CLASS( CTraceFilterOnlyBrushes, CTraceFilterSimple ); + CTraceFilterOnlyBrushes( int collisionGroup ) : CTraceFilterSimple( NULL, collisionGroup ) {} + virtual TraceType_t GetTraceType() const { return TRACE_WORLD_ONLY; } +}; + +//----------------------------------------------------------------------------- +// this will hit skip the pass entity, but not anything it owns +// (lets player grab own grenades) +class CTraceFilterNoOwnerTest : public CTraceFilterSimple +{ +public: + DECLARE_CLASS( CTraceFilterNoOwnerTest, CTraceFilterSimple ); + + CTraceFilterNoOwnerTest( const IHandleEntity *passentity, int collisionGroup ) + : CTraceFilterSimple( NULL, collisionGroup ), m_pPassNotOwner(passentity) + { + } + + virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + if ( pHandleEntity != m_pPassNotOwner ) + return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); + + return false; + } + +protected: + const IHandleEntity *m_pPassNotOwner; +}; + +//----------------------------------------------------------------------------- +// Purpose: Trace a line the special physcannon way! +//----------------------------------------------------------------------------- +void UTIL_PhyscannonTraceLine( const Vector &vecAbsStart, const Vector &vecAbsEnd, CBaseEntity *pTraceOwner, trace_t *pTrace ) +{ + // Default to HL2 vanilla + if ( hl2_episodic.GetBool() == false ) + { + CTraceFilterNoOwnerTest filter( pTraceOwner, COLLISION_GROUP_NONE ); + UTIL_TraceLine( vecAbsStart, vecAbsEnd, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); + return; + } + + // First, trace against entities + CTraceFilterPhyscannon filter( pTraceOwner, COLLISION_GROUP_NONE ); + UTIL_TraceLine( vecAbsStart, vecAbsEnd, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); + + // If we've hit something, test again to make sure no brushes block us + if ( pTrace->m_pEnt != NULL ) + { + trace_t testTrace; + CTraceFilterOnlyBrushes brushFilter( COLLISION_GROUP_NONE ); + UTIL_TraceLine( pTrace->startpos, pTrace->endpos, MASK_SHOT, &brushFilter, &testTrace ); + + // If we hit a brush, replace the trace with that result + if ( testTrace.fraction < 1.0f || testTrace.startsolid || testTrace.allsolid ) + { + *pTrace = testTrace; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Trace a hull for the physcannon +//----------------------------------------------------------------------------- +void UTIL_PhyscannonTraceHull( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &vecAbsMins, const Vector &vecAbsMaxs, CBaseEntity *pTraceOwner, trace_t *pTrace ) +{ + // Default to HL2 vanilla + if ( hl2_episodic.GetBool() == false ) + { + CTraceFilterNoOwnerTest filter( pTraceOwner, COLLISION_GROUP_NONE ); + UTIL_TraceHull( vecAbsStart, vecAbsEnd, vecAbsMins, vecAbsMaxs, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); + return; + } + + // First, trace against entities + CTraceFilterPhyscannon filter( pTraceOwner, COLLISION_GROUP_NONE ); + UTIL_TraceHull( vecAbsStart, vecAbsEnd, vecAbsMins, vecAbsMaxs, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); + + // If we've hit something, test again to make sure no brushes block us + if ( pTrace->m_pEnt != NULL ) + { + trace_t testTrace; + CTraceFilterOnlyBrushes brushFilter( COLLISION_GROUP_NONE ); + UTIL_TraceHull( pTrace->startpos, pTrace->endpos, vecAbsMins, vecAbsMaxs, MASK_SHOT, &brushFilter, &testTrace ); + + // If we hit a brush, replace the trace with that result + if ( testTrace.fraction < 1.0f || testTrace.startsolid || testTrace.allsolid ) + { + *pTrace = testTrace; + } + } +} + +static void MatrixOrthogonalize( matrix3x4_t &matrix, int column ) +{ + Vector columns[3]; + int i; + + for ( i = 0; i < 3; i++ ) + { + MatrixGetColumn( matrix, i, columns[i] ); + } + + int index0 = column; + int index1 = (column+1)%3; + int index2 = (column+2)%3; + + columns[index2] = CrossProduct( columns[index0], columns[index1] ); + columns[index1] = CrossProduct( columns[index2], columns[index0] ); + VectorNormalize( columns[index2] ); + VectorNormalize( columns[index1] ); + MatrixSetColumn( columns[index1], index1, matrix ); + MatrixSetColumn( columns[index2], index2, matrix ); +} + +#define SIGN(x) ( (x) < 0 ? -1 : 1 ) + +static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle ) +{ + matrix3x4_t alignMatrix; + AngleMatrix( angles, alignMatrix ); + + // NOTE: Must align z first + for ( int j = 3; --j >= 0; ) + { + Vector vec; + MatrixGetColumn( alignMatrix, j, vec ); + for ( int i = 0; i < 3; i++ ) + { + if ( fabs(vec[i]) > cosineAlignAngle ) + { + vec[i] = SIGN(vec[i]); + vec[(i+1)%3] = 0; + vec[(i+2)%3] = 0; + MatrixSetColumn( vec, j, alignMatrix ); + MatrixOrthogonalize( alignMatrix, j ); + break; + } + } + } + + QAngle out; + MatrixAngles( alignMatrix, out ); + return out; +} + + +/*static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr ) +{ + physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr ); + + if ( ptr->DidHit() ) + { + ptr->endpos = start * (1-ptr->fraction) + end * ptr->fraction; + ptr->startpos = start; + ptr->plane.dist = -ptr->plane.dist; + ptr->plane.normal *= -1; + } +}*/ + +//----------------------------------------------------------------------------- +// Purpose: Finds the nearest ragdoll sub-piece to a location and returns it +// Input : *pTarget - entity that is the potential ragdoll +// &position - position we're testing against +// Output : IPhysicsObject - sub-object (if any) +//----------------------------------------------------------------------------- +IPhysicsObject *GetRagdollChildAtPosition( CBaseEntity *pTarget, const Vector &position ) +{ + // Check for a ragdoll + if ( dynamic_cast( pTarget ) == NULL ) + return NULL; + + // Get the root + IPhysicsObject *pList[32]; + int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE( pList ) ); + + IPhysicsObject *pBestChild = NULL; + float flBestDist = 99999999.0f; + float flDist; + Vector vPos; + + // Find the nearest child to where we're looking + for ( int i = 0; i < count; i++ ) + { + pList[i]->GetPosition( &vPos, NULL ); + + flDist = ( position - vPos ).LengthSqr(); + + if ( flDist < flBestDist ) + { + pBestChild = pList[i]; + flBestDist = flDist; + } + } + + // Make this our base now + pTarget->VPhysicsSwapObject( pBestChild ); + + return pTarget->VPhysicsGetObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: Computes a local matrix for the player clamped to valid carry ranges +//----------------------------------------------------------------------------- +// when looking level, hold bottom of object 8 inches below eye level +#define PLAYER_HOLD_LEVEL_EYES -8 + +// when looking down, hold bottom of object 0 inches from feet +#define PLAYER_HOLD_DOWN_FEET 2 + +// when looking up, hold bottom of object 24 inches above eye level +#define PLAYER_HOLD_UP_EYES 24 + +// use a +/-30 degree range for the entire range of motion of pitch +#define PLAYER_LOOK_PITCH_RANGE 30 + +// player can reach down 2ft below his feet (otherwise he'll hold the object above the bottom) +#define PLAYER_REACH_DOWN_DISTANCE 24 + +static void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out ) +{ + if ( !pPlayer ) + return; + + QAngle angles = pPlayer->EyeAngles(); + Vector origin = pPlayer->EyePosition(); + + // 0-360 / -180-180 + //angles.x = init ? 0 : AngleDistance( angles.x, 0 ); + //angles.x = clamp( angles.x, -PLAYER_LOOK_PITCH_RANGE, PLAYER_LOOK_PITCH_RANGE ); + angles.x = 0; + + float feet = pPlayer->GetAbsOrigin().z + pPlayer->WorldAlignMins().z; + float eyes = origin.z; + float zoffset = 0; + // moving up (negative pitch is up) + if ( angles.x < 0 ) + { + zoffset = RemapVal( angles.x, 0, -PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_UP_EYES ); + } + else + { + zoffset = RemapVal( angles.x, 0, PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_DOWN_FEET + (feet - eyes) ); + } + origin.z += zoffset; + angles.x = 0; + AngleMatrix( angles, origin, out ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +// derive from this so we can add save/load data to it +struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t +{ + DECLARE_SIMPLE_DATADESC(); +}; + +BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t ) + + DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( targetRotation, FIELD_VECTOR ), + DEFINE_FIELD( maxAngular, FIELD_FLOAT ), + DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ), + DEFINE_FIELD( maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( dampFactor, FIELD_FLOAT ), + DEFINE_FIELD( teleportDistance, FIELD_FLOAT ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +class CGrabController : public IMotionEvent +{ + DECLARE_SIMPLE_DATADESC(); + +public: + + CGrabController( void ); + ~CGrabController( void ); + void AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition ); + void DetachEntity( bool bClearVelocity ); + void OnRestore(); + + bool UpdateObject( CBasePlayer *pPlayer, float flError ); + + void SetTargetPosition( const Vector &target, const QAngle &targetOrientation ); + float ComputeError(); + float GetLoadWeight( void ) const { return m_flLoadWeight; } + void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; } + void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; } + QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); + QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); + + CBaseEntity *GetAttached() { return (CBaseEntity *)m_attachedEntity; } + + IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ); + float GetSavedMass( IPhysicsObject *pObject ); + +private: + // Compute the max speed for an attached object + void ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ); + + game_shadowcontrol_params_t m_shadow; + float m_timeToArrive; + float m_errorTime; + float m_error; + float m_contactAmount; + float m_angleAlignment; + bool m_bCarriedEntityBlocksLOS; + bool m_bIgnoreRelativePitch; + + float m_flLoadWeight; + float m_savedRotDamping[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + float m_savedMass[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + EHANDLE m_attachedEntity; + QAngle m_vecPreferredCarryAngles; + bool m_bHasPreferredCarryAngles; + float m_flDistanceOffset; + + QAngle m_attachedAnglesPlayerSpace; + Vector m_attachedPositionObjectSpace; + + IPhysicsMotionController *m_controller; + + friend class CWeaponPhysCannon; +}; + +BEGIN_SIMPLE_DATADESC( CGrabController ) + + DEFINE_EMBEDDED( m_shadow ), + + DEFINE_FIELD( m_timeToArrive, FIELD_FLOAT ), + DEFINE_FIELD( m_errorTime, FIELD_FLOAT ), + DEFINE_FIELD( m_error, FIELD_FLOAT ), + DEFINE_FIELD( m_contactAmount, FIELD_FLOAT ), + DEFINE_AUTO_ARRAY( m_savedRotDamping, FIELD_FLOAT ), + DEFINE_AUTO_ARRAY( m_savedMass, FIELD_FLOAT ), + DEFINE_FIELD( m_flLoadWeight, FIELD_FLOAT ), + DEFINE_FIELD( m_bCarriedEntityBlocksLOS, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bIgnoreRelativePitch, FIELD_BOOLEAN ), + DEFINE_FIELD( m_attachedEntity, FIELD_EHANDLE ), + DEFINE_FIELD( m_angleAlignment, FIELD_FLOAT ), + DEFINE_FIELD( m_vecPreferredCarryAngles, FIELD_VECTOR ), + DEFINE_FIELD( m_bHasPreferredCarryAngles, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flDistanceOffset, FIELD_FLOAT ), + DEFINE_FIELD( m_attachedAnglesPlayerSpace, FIELD_VECTOR ), + DEFINE_FIELD( m_attachedPositionObjectSpace, FIELD_VECTOR ), + + // Physptrs can't be inside embedded classes + // DEFINE_PHYSPTR( m_controller ), + +END_DATADESC() + +const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f; +const float REDUCED_CARRY_MASS = 1.0f; + +CGrabController::CGrabController( void ) +{ + m_shadow.dampFactor = 1.0; + m_shadow.teleportDistance = 0; + m_errorTime = 0; + m_error = 0; + // make this controller really stiff! + m_shadow.maxSpeed = 1000; + m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; + m_shadow.maxDampSpeed = m_shadow.maxSpeed*2; + m_shadow.maxDampAngular = m_shadow.maxAngular; + m_attachedEntity = NULL; + m_vecPreferredCarryAngles = vec3_angle; + m_bHasPreferredCarryAngles = false; + m_flDistanceOffset = 0; +} + +CGrabController::~CGrabController( void ) +{ + DetachEntity( false ); +} + +void CGrabController::OnRestore() +{ + if ( m_controller ) + { + m_controller->SetEventHandler( this ); + } +} + +void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation ) +{ + m_shadow.targetPosition = target; + m_shadow.targetRotation = targetOrientation; + + m_timeToArrive = gpGlobals->frametime; + + CBaseEntity *pAttached = GetAttached(); + if ( pAttached ) + { + IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); + + if ( pObj != NULL ) + { + pObj->Wake(); + } + else + { + DetachEntity( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CGrabController::ComputeError() +{ + if ( m_errorTime <= 0 ) + return 0; + + CBaseEntity *pAttached = GetAttached(); + if ( pAttached ) + { + Vector pos; + IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); + + if ( pObj ) + { + pObj->GetShadowPosition( &pos, NULL ); + + float error = (m_shadow.targetPosition - pos).Length(); + if ( m_errorTime > 0 ) + { + if ( m_errorTime > 1 ) + { + m_errorTime = 1; + } + float speed = error / m_errorTime; + if ( speed > m_shadow.maxSpeed ) + { + error *= 0.5; + } + m_error = (1-m_errorTime) * m_error + error * m_errorTime; + } + } + else + { + DevMsg( "Object attached to Physcannon has no physics object\n" ); + DetachEntity( false ); + return 9999; // force detach + } + } + + if ( pAttached->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) ) + { + m_error *= 3.0f; + } + + m_errorTime = 0; + + return m_error; +} + + +#define MASS_SPEED_SCALE 60 +#define MAX_MASS 40 + +void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ) +{ + m_shadow.maxSpeed = 1000; + m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; + + // Compute total mass... + float flMass = PhysGetEntityMass( pEntity ); + float flMaxMass = physcannon_maxmass.GetFloat(); + if ( flMass <= flMaxMass ) + return; + + float flLerpFactor = clamp( flMass, flMaxMass, 500.0f ); + flLerpFactor = SimpleSplineRemapVal( flLerpFactor, flMaxMass, 500.0f, 0.0f, 1.0f ); + + float invMass = pPhysics->GetInvMass(); + float invInertia = pPhysics->GetInvInertia().Length(); + + float invMaxMass = 1.0f / MAX_MASS; + float ratio = invMaxMass / invMass; + invMass = invMaxMass; + invInertia *= ratio; + + float maxSpeed = invMass * MASS_SPEED_SCALE * 200; + float maxAngular = invInertia * MASS_SPEED_SCALE * 360; + + m_shadow.maxSpeed = Lerp( flLerpFactor, m_shadow.maxSpeed, maxSpeed ); + m_shadow.maxAngular = Lerp( flLerpFactor, m_shadow.maxAngular, maxAngular ); +} + + +QAngle CGrabController::TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) +{ + if ( m_bIgnoreRelativePitch ) + { + matrix3x4_t test; + QAngle angleTest = pPlayer->EyeAngles(); + angleTest.x = 0; + AngleMatrix( angleTest, test ); + return TransformAnglesToLocalSpace( anglesIn, test ); + } + return TransformAnglesToLocalSpace( anglesIn, pPlayer->EntityToWorldTransform() ); +} + +QAngle CGrabController::TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) +{ + if ( m_bIgnoreRelativePitch ) + { + matrix3x4_t test; + QAngle angleTest = pPlayer->EyeAngles(); + angleTest.x = 0; + AngleMatrix( angleTest, test ); + return TransformAnglesToWorldSpace( anglesIn, test ); + } + return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() ); +} + + +void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition ) +{ + // play the impact sound of the object hitting the player + // used as feedback to let the player know he picked up the object + int hitMaterial = pPhys->GetMaterialIndex(); + int playerMaterial = pPlayer->VPhysicsGetObject() ? pPlayer->VPhysicsGetObject()->GetMaterialIndex() : hitMaterial; + PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, hitMaterial, playerMaterial, 1.0, 64 ); + Vector position; + QAngle angles; + pPhys->GetPosition( &position, &angles ); + // If it has a preferred orientation, use that instead. + Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); + +// ComputeMaxSpeed( pEntity, pPhys ); + + // If we haven't been killed by a grab, we allow the gun to grab the nearest part of a ragdoll + if ( bUseGrabPosition ) + { + IPhysicsObject *pChild = GetRagdollChildAtPosition( pEntity, vGrabPosition ); + + if ( pChild ) + { + pPhys = pChild; + } + } + + // Carried entities can never block LOS + m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS(); + pEntity->SetBlocksLOS( false ); + m_controller = physenv->CreateMotionController( this ); + m_controller->AttachObject( pPhys, true ); + // Don't do this, it's causing trouble with constraint solvers. + //m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY ); + + pPhys->Wake(); + PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); + SetTargetPosition( position, angles ); + m_attachedEntity = pEntity; + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + m_flLoadWeight = 0; + float damping = 10; + float flFactor = count / 7.5f; + if ( flFactor < 1.0f ) + { + flFactor = 1.0f; + } + for ( int i = 0; i < count; i++ ) + { + float mass = pList[i]->GetMass(); + pList[i]->GetDamping( NULL, &m_savedRotDamping[i] ); + m_flLoadWeight += mass; + m_savedMass[i] = mass; + + // reduce the mass to prevent the player from adding crazy amounts of energy to the system + pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor ); + pList[i]->SetDamping( NULL, &damping ); + } + + // Give extra mass to the phys object we're actually picking up + pPhys->SetMass( REDUCED_CARRY_MASS ); + pPhys->EnableDrag( false ); + + m_errorTime = bIsMegaPhysCannon ? -1.5f : -1.0f; // 1 seconds until error starts accumulating + m_error = 0; + m_contactAmount = 0; + + m_attachedAnglesPlayerSpace = TransformAnglesToPlayerSpace( angles, pPlayer ); + if ( m_angleAlignment != 0 ) + { + m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment ); + } + + // Ragdolls don't offset this way + if ( dynamic_cast(pEntity) ) + { + m_attachedPositionObjectSpace.Init(); + } + else + { + VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace ); + } + + // If it's a prop, see if it has desired carry angles + CPhysicsProp *pProp = dynamic_cast(pEntity); + if ( pProp ) + { + m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); + m_flDistanceOffset = pProp->GetCarryDistanceOffset(); + } + else + { + m_bHasPreferredCarryAngles = false; + m_flDistanceOffset = 0; + } +} + +static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit ) +{ + Vector vel; + AngularImpulse angVel; + pPhys->GetVelocity( &vel, &angVel ); + float speed = VectorNormalize(vel) - linearLimit; + float angSpeed = VectorNormalize(angVel) - angularLimit; + speed = speed < 0 ? 0 : -speed; + angSpeed = angSpeed < 0 ? 0 : -angSpeed; + vel *= speed; + angVel *= angSpeed; + pPhys->AddVelocity( &vel, &angVel ); +} + +void CGrabController::DetachEntity( bool bClearVelocity ) +{ + Assert(!PhysIsInCallback()); + CBaseEntity *pEntity = GetAttached(); + if ( pEntity ) + { + // Restore the LS blocking state + pEntity->SetBlocksLOS( m_bCarriedEntityBlocksLOS ); + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + IPhysicsObject *pPhys = pList[i]; + if ( !pPhys ) + continue; + + // on the odd chance that it's gone to sleep while under anti-gravity + pPhys->EnableDrag( true ); + pPhys->Wake(); + pPhys->SetMass( m_savedMass[i] ); + pPhys->SetDamping( NULL, &m_savedRotDamping[i] ); + PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); + if ( bClearVelocity ) + { + PhysForceClearVelocity( pPhys ); + } + else + { + ClampPhysicsVelocity( pPhys, hl2_normspeed.GetFloat() * 1.5f, 2.0f * 360.0f ); + } + + } + } + + m_attachedEntity = NULL; + physenv->DestroyMotionController( m_controller ); + m_controller = NULL; +} + +static bool InContactWithHeavyObject( IPhysicsObject *pObject, float heavyMass ) +{ + bool contact = false; + IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); + while ( pSnapshot->IsValid() ) + { + IPhysicsObject *pOther = pSnapshot->GetObject( 1 ); + if ( !pOther->IsMoveable() || pOther->GetMass() > heavyMass ) + { + contact = true; + break; + } + pSnapshot->NextFrictionData(); + } + pObject->DestroyFrictionSnapshot( pSnapshot ); + return contact; +} + +IMotionEvent::simresult_e CGrabController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) +{ + game_shadowcontrol_params_t shadowParams = m_shadow; + if ( InContactWithHeavyObject( pObject, GetLoadWeight() ) ) + { + m_contactAmount = Approach( 0.1f, m_contactAmount, deltaTime*2.0f ); + } + else + { + m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime*2.0f ); + } + shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount; + m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime ); + + // Slide along the current contact points to fix bouncing problems + Vector velocity; + AngularImpulse angVel; + pObject->GetVelocity( &velocity, &angVel ); + PhysComputeSlideDirection( pObject, velocity, angVel, &velocity, &angVel, GetLoadWeight() ); + pObject->SetVelocityInstantaneous( &velocity, NULL ); + + linear.Init(); + angular.Init(); + m_errorTime += deltaTime; + + return SIM_LOCAL_ACCELERATION; +} + +float CGrabController::GetSavedMass( IPhysicsObject *pObject ) +{ + CBaseEntity *pHeld = m_attachedEntity; + if ( pHeld ) + { + if ( pObject->GetGameData() == (void*)pHeld ) + { + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] == pObject ) + return m_savedMass[i]; + } + } + } + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Player pickup controller +//----------------------------------------------------------------------------- +class CPlayerPickupController : public CBaseEntity +{ + DECLARE_DATADESC(); + DECLARE_CLASS( CPlayerPickupController, CBaseEntity ); +public: + void Init( CBasePlayer *pPlayer, CBaseEntity *pObject ); + void Shutdown( bool bThrown = false ); + bool OnControls( CBaseEntity *pControls ) { return true; } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void OnRestore() + { + m_grabController.OnRestore(); + } + void VPhysicsUpdate( IPhysicsObject *pPhysics ){} + void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {} + + bool IsHoldingEntity( CBaseEntity *pEnt ); + CGrabController &GetGrabController() { return m_grabController; } + +private: + CGrabController m_grabController; + CBasePlayer *m_pPlayer; +}; + +LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController ); + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CPlayerPickupController ) + + DEFINE_EMBEDDED( m_grabController ), + + // Physptrs can't be inside embedded classes + DEFINE_PHYSPTR( m_grabController.m_controller ), + + DEFINE_FIELD( m_pPlayer, FIELD_CLASSPTR ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +// *pObject - +//----------------------------------------------------------------------------- +void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) +{ + // Holster player's weapon + if ( pPlayer->GetActiveWeapon() ) + { + if ( !pPlayer->GetActiveWeapon()->Holster() ) + { + Shutdown(); + return; + } + } + + CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( pPlayer ); + if ( pOwner ) + { + pOwner->EnableSprint( false ); + } + + // If the target is debris, convert it to non-debris + if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) + { + // Interactive debris converts back to debris when it comes to rest + pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); + } + + // done so I'll go across level transitions with the player + SetParent( pPlayer ); + m_grabController.SetIgnorePitch( true ); + m_grabController.SetAngleAlignment( DOT_30DEGREE ); + m_pPlayer = pPlayer; + IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); + + Pickup_OnPhysGunPickup( pObject, m_pPlayer, PICKED_UP_BY_PLAYER ); + + m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false ); + + m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; + m_pPlayer->SetUseEntity( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void CPlayerPickupController::Shutdown( bool bThrown ) +{ + CBaseEntity *pObject = m_grabController.GetAttached(); + + bool bClearVelocity = false; + if ( !bThrown && pObject && pObject->VPhysicsGetObject() && pObject->VPhysicsGetObject()->GetContactPoint(NULL,NULL) ) + { + bClearVelocity = true; + } + + m_grabController.DetachEntity( bClearVelocity ); + + if ( pObject != NULL ) + { + Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER ); + } + + if ( m_pPlayer ) + { + CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( m_pPlayer ); + if ( pOwner ) + { + pOwner->EnableSprint( true ); + } + + m_pPlayer->SetUseEntity( NULL ); + if ( m_pPlayer->GetActiveWeapon() ) + { + if ( !m_pPlayer->GetActiveWeapon()->Deploy() ) + { + // We tried to restore the player's weapon, but we couldn't. + // This usually happens when they're holding an empty weapon that doesn't + // autoswitch away when out of ammo. Switch to next best weapon. + m_pPlayer->SwitchToNextBestWeapon( NULL ); + } + } + + m_pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; + } + Remove(); +} + + +void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ToBasePlayer(pActivator) == m_pPlayer ) + { + CBaseEntity *pAttached = m_grabController.GetAttached(); + + // UNDONE: Use vphysics stress to decide to drop objects + // UNDONE: Must fix case of forcing objects into the ground you're standing on (causes stress) before that will work + if ( !pAttached || useType == USE_OFF || (m_pPlayer->m_nButtons & IN_ATTACK2) || m_grabController.ComputeError() > 12 ) + { + Shutdown(); + return; + } + + //Adrian: Oops, our object became motion disabled, let go! + IPhysicsObject *pPhys = pAttached->VPhysicsGetObject(); + if ( pPhys && pPhys->IsMoveable() == false ) + { + Shutdown(); + return; + } + +#if STRESS_TEST + vphysics_objectstress_t stress; + CalculateObjectStress( pPhys, pAttached, &stress ); + if ( stress.exertedStress > 250 ) + { + Shutdown(); + return; + } +#endif + // +ATTACK will throw phys objects + if ( m_pPlayer->m_nButtons & IN_ATTACK ) + { + Shutdown( true ); + Vector vecLaunch; + m_pPlayer->EyeVectors( &vecLaunch ); + // JAY: Scale this with mass because some small objects really go flying + float massFactor = clamp( pPhys->GetMass(), 0.5, 15 ); + massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 ); + vecLaunch *= player_throwforce.GetFloat() * massFactor; + + pPhys->ApplyForceCenter( vecLaunch ); + AngularImpulse aVel = RandomAngularImpulse( -10, 10 ) * massFactor; + pPhys->ApplyTorqueCenter( aVel ); + return; + } + + if ( useType == USE_SET ) + { + // update position + m_grabController.UpdateObject( m_pPlayer, 12 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEnt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt ) +{ + return ( m_grabController.GetAttached() == pEnt ); +} + +void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject ) +{ + //Don't pick up if we don't have a phys object. + if ( pObject->VPhysicsGetObject() == NULL ) + return; + + CPlayerPickupController *pController = (CPlayerPickupController *)CBaseEntity::Create( "player_pickup", pObject->GetAbsOrigin(), vec3_angle, pPlayer ); + + if ( !pController ) + return; + + pController->Init( pPlayer, pObject ); +} + +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Physcannon +//----------------------------------------------------------------------------- + +#define NUM_BEAMS 4 +#define NUM_SPRITES 6 + +struct thrown_objects_t +{ + float fTimeThrown; + EHANDLE hEntity; + + DECLARE_SIMPLE_DATADESC(); +}; + +BEGIN_SIMPLE_DATADESC( thrown_objects_t ) + DEFINE_FIELD( fTimeThrown, FIELD_TIME ), + DEFINE_FIELD( hEntity, FIELD_EHANDLE ), +END_DATADESC() + +class CWeaponPhysCannon : public CBaseHLCombatWeapon +{ +public: + DECLARE_CLASS( CWeaponPhysCannon, CBaseHLCombatWeapon ); + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CWeaponPhysCannon( void ); + + void Drop( const Vector &vecVelocity ); + void Precache(); + virtual void Spawn(); + virtual void OnRestore(); + virtual void StopLoopingSounds(); + virtual void UpdateOnRemove(void); + void PrimaryAttack(); + void SecondaryAttack(); + void WeaponIdle(); + void ItemPreFrame(); + void ItemPostFrame(); + void ItemBusyFrame(); + + virtual float GetMaxAutoAimDeflection() { return 0.90f; } + + void ForceDrop( void ); + bool DropIfEntityHeld( CBaseEntity *pTarget ); // Drops its held entity if it matches the entity passed in + CGrabController &GetGrabController() { return m_grabController; } + + bool CanHolster( void ); + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + bool Deploy( void ); + + bool HasAnyAmmo( void ) { return true; } + + void InputBecomeMegaCannon( inputdata_t &inputdata ); + + void BeginUpgrade(); + + virtual void SetViewModel( void ); + virtual const char *GetShootSound( int iIndex ) const; + + void RecordThrownObject( CBaseEntity *pObject ); + void PurgeThrownObjects(); + bool IsAccountableForObject( CBaseEntity *pObject ); + + bool ShouldDisplayHUDHint() { return true; } + + + +protected: + enum FindObjectResult_t + { + OBJECT_FOUND = 0, + OBJECT_NOT_FOUND, + OBJECT_BEING_DETACHED, + }; + + void DoMegaEffect( int effectType, Vector *pos = NULL ); + void DoEffect( int effectType, Vector *pos = NULL ); + + void OpenElements( void ); + void CloseElements( void ); + + // Pickup and throw objects. + bool CanPickupObject( CBaseEntity *pTarget ); + bool IsFlesh( CBaseEntity *pTarget ); + void CheckForTarget( void ); + FindObjectResult_t FindObject( void ); + CBaseEntity *MegaPhysCannonFindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone, float flCombineBallCone, bool bOnlyCombineBalls ); + CBaseEntity *FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ); + bool AttachObject( CBaseEntity *pObject, const Vector &vPosition ); + void UpdateObject( void ); + void DetachObject( bool playSound = true, bool wasLaunched = false ); + void LaunchObject( const Vector &vecDir, float flForce ); + void StartEffects( void ); // Initialize all sprites and beams + void StopEffects( bool stopSound = true ); // Hide all effects temporarily + void DestroyEffects( void ); // Destroy all sprites and beams + + // Punt objects - this is pointing at an object in the world and applying a force to it. + void PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); + void PuntVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); + void PuntRagdoll( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); + + // Velocity-based throw common to punt and launch code. + void ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ); + + // Physgun effects + void DoEffectClosed( void ); + void DoMegaEffectClosed( void ); + + void DoEffectReady( void ); + void DoMegaEffectReady( void ); + + void DoMegaEffectHolding( void ); + void DoEffectHolding( void ); + + void DoMegaEffectLaunch( Vector *pos ); + void DoEffectLaunch( Vector *pos ); + + void DoEffectNone( void ); + void DoEffectIdle( void ); + + // Trace length + float TraceLength(); + + // Do we have the super-phys gun? + inline bool IsMegaPhysCannon() + { + return PlayerHasMegaPhysCannon(); + } + + // Sprite scale factor + float SpriteScaleFactor(); + + float GetLoadPercentage(); + CSoundPatch *GetMotorSound( void ); + + void DryFire( void ); + void PrimaryFireEffect( void ); + + // What happens when the physgun picks up something + void Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ); + + // Wait until we're done upgrading + void WaitForUpgradeThink(); + + bool EntityAllowsPunts( CBaseEntity *pEntity ); + + bool m_bOpen; + bool m_bActive; + int m_nChangeState; //For delayed state change of elements + float m_flCheckSuppressTime; //Amount of time to suppress the checking for targets + bool m_flLastDenySoundPlayed; //Debounce for deny sound + int m_nAttack2Debounce; + + CNetworkVar( bool, m_bIsCurrentlyUpgrading ); + + float m_flElementDebounce; + float m_flElementPosition; + float m_flElementDestination; + + CHandle m_hBeams[NUM_BEAMS]; + CHandle m_hGlowSprites[NUM_SPRITES]; + CHandle m_hEndSprites[2]; + float m_flEndSpritesOverride[2]; + CHandle m_hCenterSprite; + CHandle m_hBlastSprite; + + CSoundPatch *m_sndMotor; // Whirring sound for the gun + + CGrabController m_grabController; + + int m_EffectState; // Current state of the effects on the gun + + bool m_bPhyscannonState; + + // A list of the objects thrown or punted recently, and the time done so. + CUtlVector< thrown_objects_t > m_ThrownEntities; + + float m_flTimeNextObjectPurge; +}; + +IMPLEMENT_SERVERCLASS_ST(CWeaponPhysCannon, DT_WeaponPhysCannon) + SendPropBool( SENDINFO( m_bIsCurrentlyUpgrading ) ), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( weapon_physcannon, CWeaponPhysCannon ); +PRECACHE_WEAPON_REGISTER( weapon_physcannon ); + +BEGIN_DATADESC( CWeaponPhysCannon ) + + DEFINE_FIELD( m_bOpen, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_nChangeState, FIELD_INTEGER ), + DEFINE_FIELD( m_flCheckSuppressTime, FIELD_TIME ), + DEFINE_FIELD( m_flElementDebounce, FIELD_TIME ), + DEFINE_FIELD( m_flElementPosition, FIELD_FLOAT ), + DEFINE_FIELD( m_flElementDestination, FIELD_FLOAT ), + DEFINE_FIELD( m_nAttack2Debounce, FIELD_INTEGER ), + DEFINE_FIELD( m_bIsCurrentlyUpgrading, FIELD_BOOLEAN ), + DEFINE_FIELD( m_EffectState, FIELD_INTEGER ), + + DEFINE_AUTO_ARRAY( m_hBeams, FIELD_EHANDLE ), + DEFINE_AUTO_ARRAY( m_hGlowSprites, FIELD_EHANDLE ), + DEFINE_AUTO_ARRAY( m_hEndSprites, FIELD_EHANDLE ), + DEFINE_AUTO_ARRAY( m_flEndSpritesOverride, FIELD_TIME ), + DEFINE_FIELD( m_hCenterSprite, FIELD_EHANDLE ), + DEFINE_FIELD( m_hBlastSprite, FIELD_EHANDLE ), + DEFINE_FIELD( m_flLastDenySoundPlayed, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bPhyscannonState, FIELD_BOOLEAN ), + DEFINE_SOUNDPATCH( m_sndMotor ), + + DEFINE_EMBEDDED( m_grabController ), + + // Physptrs can't be inside embedded classes + DEFINE_PHYSPTR( m_grabController.m_controller ), + + DEFINE_THINKFUNC( WaitForUpgradeThink ), + + DEFINE_UTLVECTOR( m_ThrownEntities, FIELD_EMBEDDED ), + + DEFINE_FIELD( m_flTimeNextObjectPurge, FIELD_TIME ), + +END_DATADESC() + + +enum +{ + ELEMENT_STATE_NONE = -1, + ELEMENT_STATE_OPEN, + ELEMENT_STATE_CLOSED, +}; + +enum +{ + EFFECT_NONE, + EFFECT_CLOSED, + EFFECT_READY, + EFFECT_HOLDING, + EFFECT_LAUNCH, +}; + + +//----------------------------------------------------------------------------- +// Do we have the super-phys gun? +//----------------------------------------------------------------------------- +bool PlayerHasMegaPhysCannon() +{ + return ( HL2GameRules()->MegaPhyscannonActive() == true ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CWeaponPhysCannon::CWeaponPhysCannon( void ) +{ + m_flElementPosition = 0.0f; + m_flElementDestination = 0.0f; + m_bOpen = false; + m_nChangeState = ELEMENT_STATE_NONE; + m_flCheckSuppressTime = 0.0f; + m_EffectState = EFFECT_NONE; + m_flLastDenySoundPlayed = false; + + m_flEndSpritesOverride[0] = 0.0f; + m_flEndSpritesOverride[1] = 0.0f; + + m_bPhyscannonState = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Precache +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::Precache( void ) +{ + PrecacheModel( PHYSCANNON_BEAM_SPRITE ); + PrecacheModel( PHYSCANNON_GLOW_SPRITE ); + PrecacheModel( PHYSCANNON_ENDCAP_SPRITE ); + PrecacheModel( PHYSCANNON_CENTER_GLOW ); + PrecacheModel( PHYSCANNON_BLAST_SPRITE ); + + PrecacheModel( MEGACANNON_BEAM_SPRITE ); + PrecacheModel( MEGACANNON_GLOW_SPRITE ); + PrecacheModel( MEGACANNON_ENDCAP_SPRITE ); + PrecacheModel( MEGACANNON_CENTER_GLOW ); + PrecacheModel( MEGACANNON_BLAST_SPRITE ); + + PrecacheModel( MEGACANNON_RAGDOLL_BOOGIE_SPRITE ); + + // Precache our alternate model + PrecacheModel( MEGACANNON_MODEL ); + + PrecacheScriptSound( "Weapon_PhysCannon.HoldSound" ); + PrecacheScriptSound( "Weapon_Physgun.Off" ); + + PrecacheScriptSound( "Weapon_MegaPhysCannon.DryFire" ); + PrecacheScriptSound( "Weapon_MegaPhysCannon.Launch" ); + PrecacheScriptSound( "Weapon_MegaPhysCannon.Pickup"); + PrecacheScriptSound( "Weapon_MegaPhysCannon.Drop"); + PrecacheScriptSound( "Weapon_MegaPhysCannon.HoldSound"); + PrecacheScriptSound( "Weapon_MegaPhysCannon.ChargeZap"); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::Spawn( void ) +{ + BaseClass::Spawn(); + + // Need to get close to pick it up + CollisionProp()->UseTriggerBounds( false ); + + m_bPhyscannonState = IsMegaPhysCannon(); + + // The megacannon uses a different skin + if ( IsMegaPhysCannon() ) + { + m_nSkin = MEGACANNON_SKIN; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Restore +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::OnRestore() +{ + BaseClass::OnRestore(); + m_grabController.OnRestore(); + + m_bPhyscannonState = IsMegaPhysCannon(); + + // Tracker 8106: Physcannon effects disappear through level transition, so + // just recreate any effects here + if ( m_EffectState != EFFECT_NONE ) + { + DoEffect( m_EffectState, NULL ); + } +} + + +//----------------------------------------------------------------------------- +// On Remove +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::UpdateOnRemove(void) +{ + DestroyEffects( ); + BaseClass::UpdateOnRemove(); +} + + +//----------------------------------------------------------------------------- +// Sprite scale factor +//----------------------------------------------------------------------------- +inline float CWeaponPhysCannon::SpriteScaleFactor() +{ + return IsMegaPhysCannon() ? 1.5f : 1.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::Deploy( void ) +{ + CloseElements(); + DoEffect( EFFECT_READY ); + + // Unbloat our bounds + if ( IsMegaPhysCannon() ) + { + CollisionProp()->UseTriggerBounds( false ); + } + + m_flTimeNextObjectPurge = gpGlobals->curtime; + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::SetViewModel( void ) +{ + if ( !IsMegaPhysCannon() ) + { + BaseClass::SetViewModel(); + return; + } + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + return; + + CBaseViewModel *vm = pOwner->GetViewModel( m_nViewModelIndex ); + if ( vm == NULL ) + return; + + vm->SetWeaponModel( MEGACANNON_MODEL, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the cannon to drop anything it's carrying +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ForceDrop( void ) +{ + CloseElements(); + DetachObject(); + StopEffects(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Drops its held entity if it matches the entity passed in +// Input : *pTarget - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::DropIfEntityHeld( CBaseEntity *pTarget ) +{ + if ( pTarget == NULL ) + return false; + + CBaseEntity *pHeld = m_grabController.GetAttached(); + + if ( pHeld == NULL ) + return false; + + if ( pHeld == pTarget ) + { + ForceDrop(); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::Drop( const Vector &vecVelocity ) +{ + ForceDrop(); + BaseClass::Drop( vecVelocity ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::CanHolster( void ) +{ + //Don't holster this weapon if we're holding onto something + if ( m_bActive ) + return false; + + return BaseClass::CanHolster(); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + //Don't holster this weapon if we're holding onto something + if ( m_bActive ) + { + if ( m_grabController.GetAttached() == pSwitchingTo && + GetOwner()->Weapon_OwnsThisType( pSwitchingTo->GetClassname(), pSwitchingTo->GetSubType()) ) + { + + } + else + { + return false; + } + } + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner ) + { + pOwner->RumbleEffect( RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_STOP ); + } + + ForceDrop(); + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DryFire( void ) +{ + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + WeaponSound( EMPTY ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner ) + { + pOwner->RumbleEffect( RUMBLE_PISTOL, 0, RUMBLE_FLAG_RESTART ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PrimaryFireEffect( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + pOwner->ViewPunch( QAngle(-6, random->RandomInt(-2,2) ,0) ); + + color32 white = { 245, 245, 255, 32 }; + UTIL_ScreenFade( pOwner, white, 0.1f, 0.0f, FFADE_IN ); + + WeaponSound( SINGLE ); +} + +#define MAX_KNOCKBACK_FORCE 128 + +//----------------------------------------------------------------------------- +// Punt non-physics +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ) +{ + CTakeDamageInfo info; + + info.SetAttacker( GetOwner() ); + info.SetInflictor( this ); + info.SetDamage( 1.0f ); + info.SetDamageType( DMG_CRUSH | DMG_PHYSGUN ); + info.SetDamageForce( forward ); // Scale? + info.SetDamagePosition( tr.endpos ); + + pEntity->DispatchTraceAttack( info, forward, &tr ); + + ApplyMultiDamage(); + + //Explosion effect + DoEffect( EFFECT_LAUNCH, &tr.endpos ); + + PrimaryFireEffect(); + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.5f; + m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; +} + + +//----------------------------------------------------------------------------- +// What happens when the physgun picks up something +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ) +{ + // If the target is debris, convert it to non-debris + if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) + { + // Interactive debris converts back to debris when it comes to rest + pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); + } + + float mass = 0.0f; + if( pEntity->VPhysicsGetObject() ) + { + mass = pEntity->VPhysicsGetObject()->GetMass(); + } + + if( reason == PUNTED_BY_CANNON ) + { + pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAGS_NONE ); + RecordThrownObject( pEntity ); + } + + // Warn Alyx if the player is punting a car around. + if( hl2_episodic.GetBool() && mass > 250.0f ) + { + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + int nAIs = g_AI_Manager.NumAIs(); + + for ( int i = 0; i < nAIs; i++ ) + { + if( ppAIs[ i ]->Classify() == CLASS_PLAYER_ALLY_VITAL ) + { + ppAIs[ i ]->DispatchInteraction( g_interactionPlayerPuntedHeavyObject, pEntity, pOwner ); + } + } + } + + Pickup_OnPhysGunPickup( pEntity, pOwner, reason ); +} + + +//----------------------------------------------------------------------------- +// Punt vphysics +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr ) +{ + CTakeDamageInfo info; + + Vector forward = vecForward; + + info.SetAttacker( GetOwner() ); + info.SetInflictor( this ); + info.SetDamage( 0.0f ); + info.SetDamageType( DMG_PHYSGUN ); + pEntity->DispatchTraceAttack( info, forward, &tr ); + ApplyMultiDamage(); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) ) + { + IPhysicsObject *pList[64]; + int listCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + if ( !listCount ) + { + //FIXME: Do we want to do this if there's no physics object? + Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); + DryFire(); + return; + } + + if( forward.z < 0 ) + { + //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets + forward.z *= -0.65f; + } + + // NOTE: Do this first to enable motion (if disabled) - so forces will work + // Tell the object it's been punted + Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); + + // don't push vehicles that are attached to the world via fixed constraints + // they will just wiggle... + if ( (pList[0]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) && pEntity->GetServerVehicle() ) + { + forward.Init(); + } + + if ( !IsMegaPhysCannon() && !Pickup_ShouldPuntUseLaunchForces( pEntity ) ) + { + int i; + + // limit mass to avoid punting REALLY huge things + float totalMass = 0; + for ( i = 0; i < listCount; i++ ) + { + totalMass += pList[i]->GetMass(); + } + float maxMass = 250; + IServerVehicle *pVehicle = pEntity->GetServerVehicle(); + if ( pVehicle ) + { + maxMass *= 2.5; // 625 for vehicles + } + float mass = min(totalMass, maxMass); // max 250kg of additional force + + // Put some spin on the object + for ( i = 0; i < listCount; i++ ) + { + const float hitObjectFactor = 0.5f; + const float otherObjectFactor = 1.0f - hitObjectFactor; + // Must be light enough + float ratio = pList[i]->GetMass() / totalMass; + if ( pList[i] == pEntity->VPhysicsGetObject() ) + { + ratio += hitObjectFactor; + ratio = min(ratio,1.0f); + } + else + { + ratio *= otherObjectFactor; + } + pList[i]->ApplyForceCenter( forward * 15000.0f * ratio ); + pList[i]->ApplyForceOffset( forward * mass * 600.0f * ratio, tr.endpos ); + } + } + else + { + ApplyVelocityBasedForce( pEntity, vecForward ); + } + } + + // Add recoil + QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 ); + pOwner->ViewPunch( recoil ); + + //Explosion effect + DoEffect( EFFECT_LAUNCH, &tr.endpos ); + + PrimaryFireEffect(); + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.5f; + m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; + + // Don't allow the gun to regrab a thrown object!! + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; +} + +//----------------------------------------------------------------------------- +// Purpose: Applies velocity-based forces to throw the entity. This code is +// called from both punt and launch carried code. +// ASSUMES: that pEntity is a vphysics entity. +// Input : - +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ) +{ + // Get the launch velocity + Vector vVel = Pickup_PhysGunLaunchVelocity( pEntity, forward ); + + // Get the launch angular impulse + AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity ); + + // Get the physics object (MUST have one) + IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); + if ( pPhysicsObject == NULL ) + { + Assert( 0 ); + return; + } + + // Affect the object + CRagdollProp *pRagdoll = dynamic_cast( pEntity ); + if ( pRagdoll == NULL ) + { + pPhysicsObject->AddVelocity( &vVel, &aVel ); + } + else + { + Vector vTempVel; + AngularImpulse vTempAVel; + + ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( ); + for ( int j = 0; j < pRagdollPhys->listCount; ++j ) + { + pRagdollPhys->list[j].pObject->AddVelocity( &vVel, &aVel ); + } + } +} + + +//----------------------------------------------------------------------------- +// Punt non-physics +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PuntRagdoll( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + Pickup_OnPhysGunDrop( pEntity, pOwner, LAUNCHED_BY_CANNON ); + + CTakeDamageInfo info; + + Vector forward = vecForward; + info.SetAttacker( GetOwner() ); + info.SetInflictor( this ); + info.SetDamage( 0.0f ); + info.SetDamageType( DMG_PHYSGUN ); + pEntity->DispatchTraceAttack( info, forward, &tr ); + ApplyMultiDamage(); + + if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) ) + { + Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); + + if( forward.z < 0 ) + { + //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets + forward.z *= -0.65f; + } + + Vector vVel = forward * 1500; + AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity ); + + CRagdollProp *pRagdoll = dynamic_cast( pEntity ); + ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( ); + + int j; + for ( j = 0; j < pRagdollPhys->listCount; ++j ) + { + pRagdollPhys->list[j].pObject->AddVelocity( &vVel, NULL ); + } + } + + // Add recoil + QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 ); + pOwner->ViewPunch( recoil ); + + //Explosion effect + DoEffect( EFFECT_LAUNCH, &tr.endpos ); + + PrimaryFireEffect(); + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.5f; + m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; + + // Don't allow the gun to regrab a thrown object!! + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; +} + + +//----------------------------------------------------------------------------- +// Trace length +//----------------------------------------------------------------------------- +float CWeaponPhysCannon::TraceLength() +{ + if ( !IsMegaPhysCannon() ) + { + return physcannon_tracelength.GetFloat(); + } + + return physcannon_mega_tracelength.GetFloat(); +} + +//----------------------------------------------------------------------------- +// If there's any special rejection code you need to do per entity then do it here +// This is kinda nasty but I'd hate to move more physcannon related stuff into CBaseEntity +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::EntityAllowsPunts( CBaseEntity *pEntity ) +{ + if ( pEntity->HasSpawnFlags( SF_PHYSBOX_NEVER_PUNT ) ) + { + CPhysBox *pPhysBox = dynamic_cast(pEntity); + + if ( pPhysBox != NULL ) + { + if ( pPhysBox->HasSpawnFlags( SF_PHYSBOX_NEVER_PUNT ) ) + { + return false; + } + } + } + + if ( pEntity->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) ) + { + CBaseCombatWeapon *pWeapon = dynamic_cast(pEntity); + + if ( pWeapon != NULL ) + { + if ( pWeapon->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) ) + { + return false; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// This mode is a toggle. Primary fire one time to pick up a physics object. +// With an object held, click primary fire again to drop object. +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PrimaryAttack( void ) +{ + if( m_flNextPrimaryAttack > gpGlobals->curtime ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if( m_bActive ) + { + // Punch the object being held!! + Vector forward; + pOwner->EyeVectors( &forward ); + + // Validate the item is within punt range + CBaseEntity *pHeld = m_grabController.GetAttached(); + Assert( pHeld != NULL ); + + if ( pHeld != NULL ) + { + float heldDist = ( pHeld->WorldSpaceCenter() - pOwner->WorldSpaceCenter() ).Length(); + + if ( heldDist > physcannon_tracelength.GetFloat() ) + { + // We can't punt this yet + DryFire(); + return; + } + } + + LaunchObject( forward, physcannon_maxforce.GetFloat() ); + + PrimaryFireEffect(); + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + return; + } + + // If not active, just issue a physics punch in the world. + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; + + Vector forward; + pOwner->EyeVectors( &forward ); + + // NOTE: Notice we're *not* using the mega tracelength here + // when you have the mega cannon. Punting has shorter range. + Vector start, end; + start = pOwner->Weapon_ShootPosition(); + float flPuntDistance = physcannon_tracelength.GetFloat(); + VectorMA( start, flPuntDistance, forward, end ); + + trace_t tr; + UTIL_PhyscannonTraceHull( start, end, -Vector(8,8,8), Vector(8,8,8), pOwner, &tr ); + bool bValid = true; + CBaseEntity *pEntity = tr.m_pEnt; + if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) + { + bValid = false; + } + else if ( (pEntity->GetMoveType() != MOVETYPE_VPHYSICS) && ( pEntity->m_takedamage == DAMAGE_NO ) ) + { + bValid = false; + } + + // If the entity we've hit is invalid, try a traceline instead + if ( !bValid ) + { + UTIL_PhyscannonTraceLine( start, end, pOwner, &tr ); + if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) + { + if( hl2_episodic.GetBool() ) + { + // Try to find something in a very small cone. + CBaseEntity *pObject = FindObjectInCone( start, forward, physcannon_punt_cone.GetFloat() ); + + if( pObject ) + { + // Trace to the object. + UTIL_PhyscannonTraceLine( start, pObject->WorldSpaceCenter(), pOwner, &tr ); + + if( tr.m_pEnt && tr.m_pEnt == pObject && !(pObject->IsEFlagSet(EFL_NO_PHYSCANNON_INTERACTION)) ) + { + bValid = true; + pEntity = pObject; + } + } + } + } + else + { + bValid = true; + pEntity = tr.m_pEnt; + } + } + + if( !bValid ) + { + DryFire(); + return; + } + + // See if we hit something + if ( pEntity->GetMoveType() != MOVETYPE_VPHYSICS ) + { + if ( pEntity->m_takedamage == DAMAGE_NO ) + { + DryFire(); + return; + } + + if( GetOwner()->IsPlayer() && !IsMegaPhysCannon() ) + { + // Don't let the player zap any NPC's except regular antlions and headcrabs. + if( pEntity->IsNPC() && pEntity->Classify() != CLASS_HEADCRAB && !FClassnameIs(pEntity, "npc_antlion") ) + { + DryFire(); + return; + } + } + + if ( IsMegaPhysCannon() ) + { + if ( pEntity->IsNPC() && !pEntity->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) && pEntity->MyNPCPointer()->CanBecomeRagdoll() ) + { + CTakeDamageInfo info( pOwner, pOwner, 1.0f, DMG_GENERIC ); + CBaseEntity *pRagdoll = CreateServerRagdoll( pEntity->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS ); + pRagdoll->SetCollisionBounds( pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs() ); + + // Necessary to cause it to do the appropriate death cleanup + CTakeDamageInfo ragdollInfo( pOwner, pOwner, 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL ); + pEntity->TakeDamage( ragdollInfo ); + + PuntRagdoll( pRagdoll, forward, tr ); + return; + } + } + + PuntNonVPhysics( pEntity, forward, tr ); + } + else + { + if ( EntityAllowsPunts( pEntity) == false ) + { + DryFire(); + return; + } + + if ( !IsMegaPhysCannon() ) + { + if ( IsFlesh( pEntity ) ) + { + DryFire(); + return; + } + PuntVPhysics( pEntity, forward, tr ); + } + else + { + if ( dynamic_cast(pEntity) ) + { + PuntRagdoll( pEntity, forward, tr ); + } + else + { + PuntVPhysics( pEntity, forward, tr ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Click secondary attack whilst holding an object to hurl it. +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::SecondaryAttack( void ) +{ + if ( m_flNextSecondaryAttack > gpGlobals->curtime ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + // See if we should drop a held item + if ( ( m_bActive ) && ( pOwner->m_afButtonPressed & IN_ATTACK2 ) ) + { + // Drop the held object + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; + m_flNextSecondaryAttack = gpGlobals->curtime + 0.5; + + DetachObject(); + + DoEffect( EFFECT_READY ); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + } + else + { + // Otherwise pick it up + FindObjectResult_t result = FindObject(); + switch ( result ) + { + case OBJECT_FOUND: + WeaponSound( SPECIAL1 ); + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f; + + // We found an object. Debounce the button + m_nAttack2Debounce |= pOwner->m_nButtons; + break; + + case OBJECT_NOT_FOUND: + m_flNextSecondaryAttack = gpGlobals->curtime + 0.1f; + CloseElements(); + break; + + case OBJECT_BEING_DETACHED: + m_flNextSecondaryAttack = gpGlobals->curtime + 0.01f; + break; + } + + DoEffect( EFFECT_HOLDING ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::WeaponIdle( void ) +{ + if ( HasWeaponIdleTimeElapsed() ) + { + if ( m_bActive ) + { + //Shake when holding an item + SendWeaponAnim( ACT_VM_RELOAD ); + } + else + { + //Otherwise idle simply + SendWeaponAnim( ACT_VM_IDLE ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject - +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosition ) +{ + if ( m_bActive ) + return false; + + if ( CanPickupObject( pObject ) == false ) + return false; + + m_grabController.SetIgnorePitch( false ); + m_grabController.SetAngleAlignment( 0 ); + + bool bKilledByGrab = false; + + bool bIsMegaPhysCannon = IsMegaPhysCannon(); + if ( bIsMegaPhysCannon ) + { + if ( pObject->IsNPC() && !pObject->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) ) + { + Assert( pObject->MyNPCPointer()->CanBecomeRagdoll() ); + CTakeDamageInfo info( GetOwner(), GetOwner(), 1.0f, DMG_GENERIC ); + CBaseEntity *pRagdoll = CreateServerRagdoll( pObject->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS ); + + pRagdoll->SetCollisionBounds( pObject->CollisionProp()->OBBMins(), pObject->CollisionProp()->OBBMaxs() ); + + // Necessary to cause it to do the appropriate death cleanup + CTakeDamageInfo ragdollInfo( GetOwner(), GetOwner(), 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL ); + pObject->TakeDamage( ragdollInfo ); + + // Now we act on the ragdoll for the remainder of the time + pObject = pRagdoll; + bKilledByGrab = true; + } + } + + IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); + + // Must be valid + if ( !pPhysics ) + return false; + + CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( GetOwner() ); + + m_bActive = true; + if( pOwner ) + { +#ifdef HL2_EPISODIC + CBreakableProp *pProp = dynamic_cast< CBreakableProp * >( pObject ); + + if ( pProp && pProp->HasInteraction( PROPINTER_PHYSGUN_CREATE_FLARE ) ) + { + pOwner->FlashlightTurnOff(); + } +#endif + + // NOTE: This can change the mass; so it must be done before max speed setting + Physgun_OnPhysGunPickup( pObject, pOwner, PICKED_UP_BY_CANNON ); + } + + // NOTE :This must happen after OnPhysGunPickup because that can change the mass + m_grabController.AttachEntity( pOwner, pObject, pPhysics, bIsMegaPhysCannon, vPosition, (!bKilledByGrab) ); + + if( pOwner ) + { + pOwner->EnableSprint( false ); + + float loadWeight = ( 1.0f - GetLoadPercentage() ); + float maxSpeed = hl2_walkspeed.GetFloat() + ( ( hl2_normspeed.GetFloat() - hl2_walkspeed.GetFloat() ) * loadWeight ); + + //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); + pOwner->SetMaxSpeed( maxSpeed ); + } + + // Don't drop again for a slight delay, in case they were pulling objects near them + m_flNextSecondaryAttack = gpGlobals->curtime + 0.4f; + + DoEffect( EFFECT_HOLDING ); + OpenElements(); + + if ( GetMotorSound() ) + { + (CSoundEnvelopeController::GetController()).Play( GetMotorSound(), 0.0f, 50 ); + (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 100, 0.5f ); + (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.8f, 0.5f ); + } + + return true; +} + +CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + Assert( pPlayer ); + if ( pPlayer == NULL ) + return OBJECT_NOT_FOUND; + + Vector forward; + pPlayer->EyeVectors( &forward ); + + // Setup our positions + Vector start = pPlayer->Weapon_ShootPosition(); + float testLength = TraceLength() * 4.0f; + Vector end = start + forward * testLength; + + if( IsMegaPhysCannon() && hl2_episodic.GetBool() ) + { + Vector vecAutoAimDir = pPlayer->GetAutoaimVector( 1.0f, testLength ); + end = start + vecAutoAimDir * testLength; + } + + // Try to find an object by looking straight ahead + trace_t tr; + UTIL_PhyscannonTraceLine( start, end, pPlayer, &tr ); + + // Try again with a hull trace + if ( ( tr.fraction == 1.0 ) || ( tr.m_pEnt == NULL ) || ( tr.m_pEnt->IsWorld() ) ) + { + UTIL_PhyscannonTraceHull( start, end, -Vector(4,4,4), Vector(4,4,4), pPlayer, &tr ); + } + + CBaseEntity *pEntity = tr.m_pEnt ? tr.m_pEnt->GetRootMoveParent() : NULL; + bool bAttach = false; + bool bPull = false; + + // If we hit something, pick it up or pull it + if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt ) && ( tr.m_pEnt->IsWorld() == false ) ) + { + // Attempt to attach if within range + if ( tr.fraction <= 0.25f ) + { + bAttach = true; + } + else if ( tr.fraction > 0.25f ) + { + bPull = true; + } + } + + // Find anything within a general cone in front + CBaseEntity *pConeEntity = NULL; + if ( !IsMegaPhysCannon() ) + { + if (!bAttach && !bPull) + { + pConeEntity = FindObjectInCone( start, forward, physcannon_cone.GetFloat() ); + } + } + else + { + pConeEntity = MegaPhysCannonFindObjectInCone( start, forward, + physcannon_cone.GetFloat(), physcannon_ball_cone.GetFloat(), bAttach || bPull ); + } + + if ( pConeEntity ) + { + pEntity = pConeEntity; + + // If the object is near, grab it. Else, pull it a bit. + if ( pEntity->WorldSpaceCenter().DistToSqr( start ) <= (testLength * testLength) ) + { + bAttach = true; + } + else + { + bPull = true; + } + } + + if ( CanPickupObject( pEntity ) == false ) + { + CBaseEntity *pNewObject = Pickup_OnFailedPhysGunPickup( pEntity, start ); + + if ( pNewObject && CanPickupObject( pNewObject ) ) + { + pEntity = pNewObject; + } + else + { + // Make a noise to signify we can't pick this up + if ( !m_flLastDenySoundPlayed ) + { + m_flLastDenySoundPlayed = true; + WeaponSound( SPECIAL3 ); + } + + return OBJECT_NOT_FOUND; + } + } + + // Check to see if the object is constrained + needs to be ripped off... + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( !Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PICKED_UP_BY_CANNON ) ) + return OBJECT_BEING_DETACHED; + + if ( bAttach ) + { + return AttachObject( pEntity, tr.endpos ) ? OBJECT_FOUND : OBJECT_NOT_FOUND; + } + + if ( !bPull ) + return OBJECT_NOT_FOUND; + + // FIXME: This needs to be run through the CanPickupObject logic + IPhysicsObject *pObj = pEntity->VPhysicsGetObject(); + if ( !pObj ) + return OBJECT_NOT_FOUND; + + // If we're too far, simply start to pull the object towards us + Vector pullDir = start - pEntity->WorldSpaceCenter(); + VectorNormalize( pullDir ); + pullDir *= IsMegaPhysCannon() ? physcannon_mega_pullforce.GetFloat() : physcannon_pullforce.GetFloat(); + + float mass = PhysGetEntityMass( pEntity ); + if ( mass < 50.0f ) + { + pullDir *= (mass + 0.5) * (1/50.0f); + } + + // Nudge it towards us + pObj->ApplyForceCenter( pullDir ); + return OBJECT_NOT_FOUND; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CBaseEntity *CWeaponPhysCannon::MegaPhysCannonFindObjectInCone( const Vector &vecOrigin, + const Vector &vecDir, float flCone, float flCombineBallCone, bool bOnlyCombineBalls ) +{ + // Find the nearest physics-based item in a cone in front of me. + CBaseEntity *list[1024]; + float flMaxDist = TraceLength() + 1.0; + float flNearestDist = flMaxDist; + bool bNearestIsCombineBall = bOnlyCombineBalls ? true : false; + Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist ); + Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist ); + + CBaseEntity *pNearest = NULL; + + int count = UTIL_EntitiesInBox( list, 1024, mins, maxs, 0 ); + for( int i = 0 ; i < count ; i++ ) + { + if ( !list[ i ]->VPhysicsGetObject() ) + continue; + + bool bIsCombineBall = FClassnameIs( list[ i ], "prop_combine_ball" ); + if ( !bIsCombineBall && bNearestIsCombineBall ) + continue; + + // Closer than other objects + Vector los; + VectorSubtract( list[ i ]->WorldSpaceCenter(), vecOrigin, los ); + float flDist = VectorNormalize( los ); + + if ( !bIsCombineBall || bNearestIsCombineBall ) + { + // Closer than other objects + if( flDist >= flNearestDist ) + continue; + + // Cull to the cone + if ( DotProduct( los, vecDir ) <= flCone ) + continue; + } + else + { + // Close enough? + if ( flDist >= flMaxDist ) + continue; + + // Cull to the cone + if ( DotProduct( los, vecDir ) <= flCone ) + continue; + + // NOW: If it's either closer than nearest dist or within the ball cone, use it! + if ( (flDist > flNearestDist) && (DotProduct( los, vecDir ) <= flCombineBallCone) ) + continue; + } + + // Make sure it isn't occluded! + trace_t tr; + UTIL_PhyscannonTraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), GetOwner(), &tr ); + if( tr.m_pEnt == list[ i ] ) + { + flNearestDist = flDist; + pNearest = list[ i ]; + bNearestIsCombineBall = bIsCombineBall; + } + } + + return pNearest; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CBaseEntity *CWeaponPhysCannon::FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ) +{ + // Find the nearest physics-based item in a cone in front of me. + CBaseEntity *list[256]; + float flNearestDist = physcannon_tracelength.GetFloat() + 1.0; //Use regular distance. + Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist ); + Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist ); + + CBaseEntity *pNearest = NULL; + + int count = UTIL_EntitiesInBox( list, 256, mins, maxs, 0 ); + for( int i = 0 ; i < count ; i++ ) + { + if ( !list[ i ]->VPhysicsGetObject() ) + continue; + + // Closer than other objects + Vector los = ( list[ i ]->WorldSpaceCenter() - vecOrigin ); + float flDist = VectorNormalize( los ); + if( flDist >= flNearestDist ) + continue; + + // Cull to the cone + if ( DotProduct( los, vecDir ) <= flCone ) + continue; + + // Make sure it isn't occluded! + trace_t tr; + UTIL_PhyscannonTraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), GetOwner(), &tr ); + if( tr.m_pEnt == list[ i ] ) + { + flNearestDist = flDist; + pNearest = list[ i ]; + } + } + + return pNearest; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError ) +{ + CBaseEntity *pEntity = GetAttached(); + if ( !pEntity || ComputeError() > flError || pPlayer->GetGroundEntity() == pEntity || !pEntity->VPhysicsGetObject() ) + { + return false; + } + + //Adrian: Oops, our object became motion disabled, let go! + IPhysicsObject *pPhys = pEntity->VPhysicsGetObject(); + if ( pPhys && pPhys->IsMoveable() == false ) + { + return false; + } + + Vector forward, right, up; + QAngle playerAngles = pPlayer->EyeAngles(); + AngleVectors( playerAngles, &forward, &right, &up ); + + if ( HL2GameRules()->MegaPhyscannonActive() ) + { + Vector los = ( pEntity->WorldSpaceCenter() - pPlayer->Weapon_ShootPosition() ); + VectorNormalize( los ); + + float flDot = DotProduct( los, forward ); + + //Let go of the item if we turn around too fast. + if ( flDot <= 0.35f ) + return false; + } + + // If this is set to true, the player will be allowed to lift the held object to + // a position directly over their own head. Otherwise, this behavior is prevented (default). + bool bAllowObjectOverhead = UTIL_IsCombineBallDefinite(pEntity); + + + + float pitch = AngleDistance(playerAngles.x,0); + + if( !bAllowObjectOverhead ) + { + playerAngles.x = clamp( pitch, -75, 75 ); + } + else + { + playerAngles.x = clamp( pitch, -90, 75 ); + } + + + + // Now clamp a sphere of object radius at end to the player's bbox + Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -forward ); + Vector player2d = pPlayer->CollisionProp()->OBBMaxs(); + float playerRadius = player2d.Length2D(); + float radius = playerRadius + fabs(DotProduct( forward, radial )); + + float distance = 24 + ( radius * 2.0f ); + + // Add the prop's distance offset + distance += m_flDistanceOffset; + + Vector start = pPlayer->Weapon_ShootPosition(); + Vector end = start + ( forward * distance ); + + trace_t tr; + CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE ); + Ray_t ray; + ray.Init( start, end ); + enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr ); + + if ( tr.fraction < 0.5 ) + { + end = start + forward * (radius*0.5f); + } + else if ( tr.fraction <= 1.0f ) + { + end = start + forward * ( distance - radius ); + } + Vector playerMins, playerMaxs, nearest; + pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs ); + Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter(); + CalcClosestPointOnLine( end, playerLine+Vector(0,0,playerMins.z), playerLine+Vector(0,0,playerMaxs.z), nearest, NULL ); + + if( !bAllowObjectOverhead ) + { + Vector delta = end - nearest; + float len = VectorNormalize(delta); + if ( len < radius ) + { + end = nearest + radius * delta; + } + } + + //Show overlays of radius + if ( g_debug_physcannon.GetBool() ) + { + NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 ); + + NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(), + -Vector( radius, radius, radius), + Vector( radius, radius, radius ), + 255, 0, 0, + true, + 0.0f ); + } + + QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer ); + + // If it has a preferred orientation, update to ensure we're still oriented correctly. + Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); + + // We may be holding a prop that has preferred carry angles + if ( m_bHasPreferredCarryAngles ) + { + matrix3x4_t tmp; + ComputePlayerMatrix( pPlayer, tmp ); + angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp ); + } + + matrix3x4_t attachedToWorld; + Vector offset; + AngleMatrix( angles, attachedToWorld ); + VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset ); + + SetTargetPosition( end - offset, angles ); + + return true; +} + +void CWeaponPhysCannon::UpdateObject( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + Assert( pPlayer ); + + float flError = IsMegaPhysCannon() ? 18 : 12; + if ( !m_grabController.UpdateObject( pPlayer, flError ) ) + { + DetachObject(); + return; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched ) +{ + if ( m_bActive == false ) + return; + + CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( GetOwner() ); + if( pOwner != NULL ) + { + pOwner->EnableSprint( true ); + pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); + + if( wasLaunched ) + { + pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAG_RESTART ); + } + } + + CBaseEntity *pObject = m_grabController.GetAttached(); + + m_grabController.DetachEntity( wasLaunched ); + + if ( pObject != NULL ) + { + Pickup_OnPhysGunDrop( pObject, pOwner, wasLaunched ? LAUNCHED_BY_CANNON : DROPPED_BY_CANNON ); + } + + // Stop our looping sound + if ( GetMotorSound() ) + { + (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); + (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); + } + + m_bActive = false; + + if ( playSound ) + { + //Play the detach sound + WeaponSound( MELEE_MISS ); + } + + RecordThrownObject( pObject ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ItemPreFrame() +{ + BaseClass::ItemPreFrame(); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + m_flElementPosition = UTIL_Approach( m_flElementDestination, m_flElementPosition, 0.1f ); + + CBaseViewModel *vm = pOwner->GetViewModel(); + + if ( vm != NULL ) + { + vm->SetPoseParameter( "active", m_flElementPosition ); + } + + // Update the object if the weapon is switched on. + if( m_bActive ) + { + UpdateObject(); + } + + if( gpGlobals->curtime >= m_flTimeNextObjectPurge ) + { + PurgeThrownObjects(); + m_flTimeNextObjectPurge = gpGlobals->curtime + 0.5f; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::CheckForTarget( void ) +{ + //See if we're suppressing this + if ( m_flCheckSuppressTime > gpGlobals->curtime ) + return; + + // holstered + if ( IsEffectActive( EF_NODRAW ) ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if ( m_bActive ) + return; + + Vector aimDir; + pOwner->EyeVectors( &aimDir ); + + Vector startPos = pOwner->Weapon_ShootPosition(); + Vector endPos; + VectorMA( startPos, TraceLength(), aimDir, endPos ); + + trace_t tr; + UTIL_PhyscannonTraceHull( startPos, endPos, -Vector(4,4,4), Vector(4,4,4), GetOwner(), &tr ); + + if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt != NULL ) ) + { + // FIXME: Try just having the elements always open when pointed at a physics object + if ( CanPickupObject( tr.m_pEnt ) || Pickup_ForcePhysGunOpen( tr.m_pEnt, pOwner ) ) + // if ( ( tr.m_pEnt->VPhysicsGetObject() != NULL ) && ( tr.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS ) ) + { + m_nChangeState = ELEMENT_STATE_NONE; + OpenElements(); + return; + } + } + + // Close the elements after a delay to prevent overact state switching + if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState == ELEMENT_STATE_NONE ) ) + { + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.5f; + } +} + + +//----------------------------------------------------------------------------- +// Begin upgrading! +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::BeginUpgrade() +{ + if ( IsMegaPhysCannon() ) + return; + + if ( m_bIsCurrentlyUpgrading ) + return; + + SetSequence( SelectWeightedSequence( ACT_PHYSCANNON_UPGRADE ) ); + ResetSequenceInfo(); + + m_bIsCurrentlyUpgrading = true; + + SetContextThink( &CWeaponPhysCannon::WaitForUpgradeThink, gpGlobals->curtime + 6.0f, s_pWaitForUpgradeContext ); + + EmitSound( "WeaponDissolve.Charge" ); + + // Bloat our bounds + CollisionProp()->UseTriggerBounds( true, 32.0f ); + + // Turn on the new skin + m_nSkin = MEGACANNON_SKIN; +} + + +//----------------------------------------------------------------------------- +// Wait until we're done upgrading +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::WaitForUpgradeThink() +{ + Assert( m_bIsCurrentlyUpgrading ); + + StudioFrameAdvance(); + if ( !IsActivityFinished() ) + { + SetContextThink( &CWeaponPhysCannon::WaitForUpgradeThink, gpGlobals->curtime + 0.1f, s_pWaitForUpgradeContext ); + return; + } + + if ( !GlobalEntity_IsInTable( "super_phys_gun" ) ) + { + GlobalEntity_Add( MAKE_STRING("super_phys_gun"), gpGlobals->mapname, GLOBAL_ON ); + } + else + { + GlobalEntity_SetState( MAKE_STRING("super_phys_gun"), GLOBAL_ON ); + } + m_bIsCurrentlyUpgrading = false; + + // This is necessary to get the effects to look different + DestroyEffects(); + + // HACK: Hacky notification back to the level that we've finish upgrading + CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, "script_physcannon_upgrade" ); + if ( pEnt ) + { + variant_t emptyVariant; + pEnt->AcceptInput( "Trigger", this, this, emptyVariant, 0 ); + } + + StopSound( "WeaponDissolve.Charge" ); + + // Re-enable weapon pickup + AddSolidFlags( FSOLID_TRIGGER ); + + SetContextThink( NULL, gpGlobals->curtime, s_pWaitForUpgradeContext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectIdle( void ) +{ + if ( IsEffectActive( EF_NODRAW ) ) + { + StopEffects(); + return; + } + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if ( m_bPhyscannonState != IsMegaPhysCannon() ) + { + DestroyEffects(); + StartEffects(); + + m_bPhyscannonState = IsMegaPhysCannon(); + + //This means we just switched to regular physcannon this frame. + if ( m_bPhyscannonState == false ) + { + EmitSound( "Weapon_Physgun.Off" ); + +#ifdef HL2_EPISODIC + ForceDrop(); + + CHL2_Player *pPlayer = dynamic_cast( pOwner ); + + if ( pPlayer ) + { + pPlayer->StartArmorReduction(); + } +#endif + + CCitadelEnergyCore *pCore = static_cast( CreateEntityByName( "env_citadel_energy_core" ) ); + + if ( pCore == NULL ) + return; + + CBaseAnimating *pBeamEnt = pOwner->GetViewModel(); + + if ( pBeamEnt ) + { + int iAttachment = pBeamEnt->LookupAttachment( "muzzle" ); + + Vector vOrigin; + QAngle vAngle; + + pBeamEnt->GetAttachment( iAttachment, vOrigin, vAngle ); + + pCore->SetAbsOrigin( vOrigin ); + pCore->SetAbsAngles( vAngle ); + + DispatchSpawn( pCore ); + pCore->Activate(); + + pCore->SetParent( pBeamEnt, iAttachment ); + pCore->SetScale( 2.5f ); + + variant_t variant; + variant.SetFloat( 1.0f ); + + g_EventQueue.AddEvent( pCore, "StartDischarge", 0, pOwner, pOwner ); + g_EventQueue.AddEvent( pCore, "Stop", variant, 1, pOwner, pOwner ); + + pCore->SetThink ( &CCitadelEnergyCore::SUB_Remove ); + pCore->SetNextThink( gpGlobals->curtime + 10.0f ); + + m_nSkin = 0; + } + } + } + + float flScaleFactor = SpriteScaleFactor(); + + // Flicker the end sprites + if ( ( m_hEndSprites[0] != NULL ) && ( m_hEndSprites[1] != NULL ) ) + { + //Make the end points flicker as fast as possible + //FIXME: Make this a property of the CSprite class! + for ( int i = 0; i < 2; i++ ) + { + m_hEndSprites[i]->SetBrightness( random->RandomInt( 200, 255 ) ); + m_hEndSprites[i]->SetScale( random->RandomFloat( 0.1, 0.15 ) * flScaleFactor ); + } + } + + // Flicker the glow sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] == NULL ) + continue; + + if ( IsMegaPhysCannon() ) + { + m_hGlowSprites[i]->SetBrightness( random->RandomInt( 32, 48 ) ); + m_hGlowSprites[i]->SetScale( random->RandomFloat( 0.15, 0.2 ) * flScaleFactor ); + } + else + { + m_hGlowSprites[i]->SetBrightness( random->RandomInt( 16, 24 ) ); + m_hGlowSprites[i]->SetScale( random->RandomFloat( 0.3, 0.35 ) * flScaleFactor ); + } + } + + // Only do these effects on the mega-cannon + if ( IsMegaPhysCannon() ) + { + // Randomly arc between the elements and core + if ( random->RandomInt( 0, 100 ) == 0 && !engine->IsPaused() ) + { + CBeam *pBeam = CBeam::BeamCreate( MEGACANNON_BEAM_SPRITE, 1 ); + + CBaseEntity *pBeamEnt = pOwner->GetViewModel(); + pBeam->EntsInit( pBeamEnt, pBeamEnt ); + + int startAttachment; + int sprite; + + if ( random->RandomInt( 0, 1 ) ) + { + startAttachment = LookupAttachment( "fork1t" ); + sprite = 0; + } + else + { + startAttachment = LookupAttachment( "fork2t" ); + sprite = 1; + } + + int endAttachment = 1; + + pBeam->SetStartAttachment( startAttachment ); + pBeam->SetEndAttachment( endAttachment ); + pBeam->SetNoise( random->RandomFloat( 8.0f, 16.0f ) ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetScrollRate( 25 ); + pBeam->SetBrightness( 128 ); + pBeam->SetWidth( 1 ); + pBeam->SetEndWidth( random->RandomFloat( 2, 8 ) ); + + float lifetime = random->RandomFloat( 0.2f, 0.4f ); + + pBeam->LiveForTime( lifetime ); + + if ( m_hEndSprites[sprite] != NULL ) + { + // Turn on the sprite for awhile + m_hEndSprites[sprite]->TurnOn(); + m_flEndSpritesOverride[sprite] = gpGlobals->curtime + lifetime; + EmitSound( "Weapon_MegaPhysCannon.ChargeZap" ); + } + } + + if ( m_hCenterSprite != NULL ) + { + if ( m_EffectState == EFFECT_HOLDING ) + { + m_hCenterSprite->SetBrightness( random->RandomInt( 32, 64 ) ); + m_hCenterSprite->SetScale( random->RandomFloat( 0.2, 0.25 ) * flScaleFactor ); + } + else + { + m_hCenterSprite->SetBrightness( random->RandomInt( 32, 64 ) ); + m_hCenterSprite->SetScale( random->RandomFloat( 0.125, 0.15 ) * flScaleFactor ); + } + } + + if ( m_hBlastSprite != NULL ) + { + if ( m_EffectState == EFFECT_HOLDING ) + { + m_hBlastSprite->SetBrightness( random->RandomInt( 125, 150 ) ); + m_hBlastSprite->SetScale( random->RandomFloat( 0.125, 0.15 ) * flScaleFactor ); + } + else + { + m_hBlastSprite->SetBrightness( random->RandomInt( 32, 64 ) ); + m_hBlastSprite->SetScale( random->RandomFloat( 0.075, 0.15 ) * flScaleFactor ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update our idle effects even when deploying +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ItemBusyFrame( void ) +{ + DoEffectIdle(); + + BaseClass::ItemBusyFrame(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ItemPostFrame() +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + { + // We found an object. Debounce the button + m_nAttack2Debounce = 0; + return; + } + + //Check for object in pickup range + if ( m_bActive == false ) + { + CheckForTarget(); + + if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState != ELEMENT_STATE_NONE ) ) + { + if ( m_nChangeState == ELEMENT_STATE_OPEN ) + { + OpenElements(); + } + else if ( m_nChangeState == ELEMENT_STATE_CLOSED ) + { + CloseElements(); + } + + m_nChangeState = ELEMENT_STATE_NONE; + } + } + + // NOTE: Attack2 will be considered to be pressed until the first item is picked up. + int nAttack2Mask = pOwner->m_nButtons & (~m_nAttack2Debounce); + if ( nAttack2Mask & IN_ATTACK2 ) + { + SecondaryAttack(); + } + else + { + // Reset our debouncer + m_flLastDenySoundPlayed = false; + + if ( m_bActive == false ) + { + DoEffect( EFFECT_READY ); + } + } + + if (( pOwner->m_nButtons & IN_ATTACK2 ) == 0 ) + { + m_nAttack2Debounce = 0; + } + + if ( pOwner->m_nButtons & IN_ATTACK ) + { + PrimaryAttack(); + } + else + { + WeaponIdle(); + } + + if ( hl2_episodic.GetBool() == true ) + { + if ( IsMegaPhysCannon() ) + { + if ( !( pOwner->m_nButtons & IN_ATTACK ) ) + { + m_flNextPrimaryAttack = gpGlobals->curtime; + } + } + } + + // Update our idle effects (flickers, etc) + DoEffectIdle(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#define PHYSCANNON_DANGER_SOUND_RADIUS 128 + +void CWeaponPhysCannon::LaunchObject( const Vector &vecDir, float flForce ) +{ + // FIRE!!! + if( m_grabController.GetAttached() ) + { + CBaseEntity *pObject = m_grabController.GetAttached(); + + DetachObject( false, true ); + + // Trace ahead a bit and make a chain of danger sounds ahead of the phys object + // to scare potential targets + trace_t tr; + Vector vecStart = pObject->GetAbsOrigin(); + Vector vecSpot; + int iLength; + int i; + + UTIL_TraceLine( vecStart, vecStart + vecDir * flForce, MASK_SHOT, pObject, COLLISION_GROUP_NONE, &tr ); + iLength = static_cast(( tr.startpos - tr.endpos ).Length()); + vecSpot = vecStart + vecDir * PHYSCANNON_DANGER_SOUND_RADIUS; + + for( i = PHYSCANNON_DANGER_SOUND_RADIUS ; i < iLength ; i += PHYSCANNON_DANGER_SOUND_RADIUS ) + { + CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, PHYSCANNON_DANGER_SOUND_RADIUS, 0.5, pObject ); + vecSpot = vecSpot + ( vecDir * PHYSCANNON_DANGER_SOUND_RADIUS ); + } + + // Launch + ApplyVelocityBasedForce( pObject, vecDir ); + + // Don't allow the gun to regrab a thrown object!! + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; + + Vector center = pObject->WorldSpaceCenter(); + + //Do repulse effect + DoEffect( EFFECT_LAUNCH, ¢er ); + } + + // Stop our looping sound + if ( GetMotorSound() ) + { + (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); + (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); + } + + //Close the elements and suppress checking for a bit + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.1f; + m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::IsFlesh( CBaseEntity *pTarget ) +{ + Assert( pTarget ); + IPhysicsObject *pList[64]; + int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + int material = pList[i]->GetMaterialIndex(); + surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material ); + // Is flesh ?, don't allow pickup + if ( pSurfaceData->game.material == 'A' || pSurfaceData->game.material == 'F' || pSurfaceData->game.material == 'B' || pSurfaceData->game.material == 'H' ) + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTarget - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget ) +{ + if ( pTarget == NULL ) + return false; + + if ( pTarget->GetBaseAnimating() && pTarget->GetBaseAnimating()->IsDissolving() ) + return false; + + if ( pTarget->HasSpawnFlags( SF_PHYSBOX_ALWAYS_PICK_UP ) || pTarget->HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) ) + { + // It may seem strange to check this spawnflag before we know the class of this object, since the + // spawnflag only applies to func_physbox, but it can act as a filter of sorts to reduce the number + // of irrelevant entities that fall through to this next casting check, which is slower. + CPhysBox *pPhysBox = dynamic_cast(pTarget); + + if ( pPhysBox != NULL ) + { + if ( pTarget->HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) ) + return false; + else + return true; + } + } + + if ( pTarget->HasSpawnFlags(SF_PHYSPROP_ALWAYS_PICK_UP) ) + { + // It may seem strange to check this spawnflag before we know the class of this object, since the + // spawnflag only applies to func_physbox, but it can act as a filter of sorts to reduce the number + // of irrelevant entities that fall through to this next casting check, which is slower. + CPhysicsProp *pPhysProp = dynamic_cast(pTarget); + if ( pPhysProp != NULL ) + return true; + } + + if ( pTarget->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) + return false; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner && pOwner->GetGroundEntity() == pTarget ) + return false; + + if ( !IsMegaPhysCannon() ) + { + if ( IsFlesh( pTarget ) ) + return false; + return CBasePlayer::CanPickupObject( pTarget, physcannon_maxmass.GetFloat(), 0 ); + } + + if ( pTarget->IsNPC() && pTarget->MyNPCPointer()->CanBecomeRagdoll() ) + return true; + + if ( dynamic_cast(pTarget) ) + return true; + + return CBasePlayer::CanPickupObject( pTarget, 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::OpenElements( void ) +{ + if ( m_bOpen ) + return; + + WeaponSound( SPECIAL2 ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if( !IsMegaPhysCannon() ) + { + pOwner->RumbleEffect(RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_RESTART|RUMBLE_FLAG_LOOP); + } + + if ( m_flElementPosition < 0.0f ) + m_flElementPosition = 0.0f; + + m_flElementDestination = 1.0f; + + SendWeaponAnim( ACT_VM_IDLE ); + + m_bOpen = true; + + DoEffect( EFFECT_READY ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::CloseElements( void ) +{ + // The mega cannon cannot be closed! + if ( IsMegaPhysCannon() ) + { + OpenElements(); + return; + } + + if ( m_bOpen == false ) + return; + + WeaponSound( MELEE_HIT ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + pOwner->RumbleEffect(RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_STOP); + + if ( m_flElementPosition > 1.0f ) + m_flElementPosition = 1.0f; + + m_flElementDestination = 0.0f; + + SendWeaponAnim( ACT_VM_IDLE ); + + m_bOpen = false; + + if ( GetMotorSound() ) + { + (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); + (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); + } + + DoEffect( EFFECT_CLOSED ); +} + +#define PHYSCANNON_MAX_MASS 500 + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CWeaponPhysCannon::GetLoadPercentage( void ) +{ + float loadWeight = m_grabController.GetLoadWeight(); + loadWeight /= physcannon_maxmass.GetFloat(); + loadWeight = clamp( loadWeight, 0.0f, 1.0f ); + return loadWeight; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CSoundPatch +//----------------------------------------------------------------------------- +CSoundPatch *CWeaponPhysCannon::GetMotorSound( void ) +{ + if ( m_sndMotor == NULL ) + { + CPASAttenuationFilter filter( this ); + + if ( IsMegaPhysCannon() ) + { + m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_MegaPhysCannon.HoldSound", ATTN_NORM ); + } + else + { + m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_PhysCannon.HoldSound", ATTN_NORM ); + } + } + + return m_sndMotor; +} + + +//----------------------------------------------------------------------------- +// Shuts down sounds +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::StopLoopingSounds() +{ + if ( m_sndMotor != NULL ) + { + (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndMotor ); + m_sndMotor = NULL; + } + + BaseClass::StopLoopingSounds(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DestroyEffects( void ) +{ + //Turn off main glow + if ( m_hCenterSprite != NULL ) + { + UTIL_Remove( m_hCenterSprite ); + m_hCenterSprite = NULL; + } + + if ( m_hBlastSprite != NULL ) + { + UTIL_Remove( m_hBlastSprite ); + m_hBlastSprite = NULL; + } + + // Turn off beams + for ( int i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] != NULL ) + { + UTIL_Remove( m_hBeams[i] ); + m_hBeams[i] = NULL; + } + } + + // Turn off sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] != NULL ) + { + UTIL_Remove( m_hGlowSprites[i] ); + m_hGlowSprites[i] = NULL; + } + } + + for ( int i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] != NULL ) + { + UTIL_Remove( m_hEndSprites[i] ); + m_hEndSprites[i] = NULL; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::StopEffects( bool stopSound ) +{ + // Turn off our effect state + DoEffect( EFFECT_NONE ); + + //Turn off main glow + if ( m_hCenterSprite != NULL ) + { + m_hCenterSprite->TurnOff(); + } + + if ( m_hBlastSprite != NULL ) + { + m_hBlastSprite->TurnOff(); + } + + //Turn off beams + for ( int i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] != NULL ) + { + m_hBeams[i]->SetBrightness( 0 ); + } + } + + //Turn off sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] != NULL ) + { + m_hGlowSprites[i]->TurnOff(); + } + } + + for ( int i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] != NULL ) + { + m_hEndSprites[i]->TurnOff(); + } + } + + //Shut off sounds + if ( stopSound && GetMotorSound() != NULL ) + { + (CSoundEnvelopeController::GetController()).SoundFadeOut( GetMotorSound(), 0.1f ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::StartEffects( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + return; + + bool bIsMegaCannon = IsMegaPhysCannon(); + + int i; + float flScaleFactor = SpriteScaleFactor(); + CBaseEntity *pBeamEnt = pOwner->GetViewModel(); + + // Create the beams + for ( i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] ) + continue; + + const char *beamAttachNames[] = + { + "fork1t", + "fork2t", + "fork1t", + "fork2t", + "fork1t", + "fork2t", + }; + + m_hBeams[i] = CBeam::BeamCreate( + bIsMegaCannon ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 1.0f ); + m_hBeams[i]->EntsInit( pBeamEnt, pBeamEnt ); + + int startAttachment = LookupAttachment( beamAttachNames[i] ); + int endAttachment = 1; + + m_hBeams[i]->FollowEntity( pBeamEnt ); + + m_hBeams[i]->AddSpawnFlags( SF_BEAM_TEMPORARY ); + m_hBeams[i]->SetStartAttachment( startAttachment ); + m_hBeams[i]->SetEndAttachment( endAttachment ); + m_hBeams[i]->SetNoise( random->RandomFloat( 8.0f, 16.0f ) ); + m_hBeams[i]->SetColor( 255, 255, 255 ); + m_hBeams[i]->SetScrollRate( 25 ); + m_hBeams[i]->SetBrightness( 128 ); + m_hBeams[i]->SetWidth( 0 ); + m_hBeams[i]->SetEndWidth( random->RandomFloat( 2, 4 ) ); + } + + //Create the glow sprites + for ( i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] ) + continue; + + const char *attachNames[] = + { + "fork1b", + "fork1m", + "fork1t", + "fork2b", + "fork2m", + "fork2t" + }; + + m_hGlowSprites[i] = CSprite::SpriteCreate( + bIsMegaCannon ? MEGACANNON_GLOW_SPRITE : PHYSCANNON_GLOW_SPRITE, + GetAbsOrigin(), false ); + + m_hGlowSprites[i]->SetAsTemporary(); + + m_hGlowSprites[i]->SetAttachment( pOwner->GetViewModel(), LookupAttachment( attachNames[i] ) ); + + if ( bIsMegaCannon ) + { + m_hGlowSprites[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 128, kRenderFxNone ); + } + else + { + m_hGlowSprites[i]->SetTransparency( kRenderTransAdd, 255, 128, 0, 64, kRenderFxNoDissipation ); + } + + m_hGlowSprites[i]->SetBrightness( 255, 0.2f ); + m_hGlowSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f ); + } + + //Create the endcap sprites + for ( i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] == NULL ) + { + const char *attachNames[] = + { + "fork1t", + "fork2t" + }; + + m_hEndSprites[i] = CSprite::SpriteCreate( + bIsMegaCannon ? MEGACANNON_ENDCAP_SPRITE : PHYSCANNON_ENDCAP_SPRITE, + GetAbsOrigin(), false ); + + m_hEndSprites[i]->SetAsTemporary(); + m_hEndSprites[i]->SetAttachment( pOwner->GetViewModel(), LookupAttachment( attachNames[i] ) ); + m_hEndSprites[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_hEndSprites[i]->SetBrightness( 255, 0.2f ); + m_hEndSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f ); + m_hEndSprites[i]->TurnOff(); + } + } + + //Create the center glow + if ( m_hCenterSprite == NULL ) + { + m_hCenterSprite = CSprite::SpriteCreate( + bIsMegaCannon ? MEGACANNON_CENTER_GLOW : PHYSCANNON_CENTER_GLOW, + GetAbsOrigin(), false ); + + m_hCenterSprite->SetAsTemporary(); + m_hCenterSprite->SetAttachment( pOwner->GetViewModel(), 1 ); + m_hCenterSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone ); + m_hCenterSprite->SetBrightness( 255, 0.2f ); + m_hCenterSprite->SetScale( 0.1f, 0.2f ); + } + + //Create the blast sprite + if ( m_hBlastSprite == NULL ) + { + m_hBlastSprite = CSprite::SpriteCreate( + bIsMegaCannon ? MEGACANNON_BLAST_SPRITE : PHYSCANNON_BLAST_SPRITE, + GetAbsOrigin(), false ); + + m_hBlastSprite->SetAsTemporary(); + m_hBlastSprite->SetAttachment( pOwner->GetViewModel(), 1 ); + m_hBlastSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone ); + m_hBlastSprite->SetBrightness( 255, 0.2f ); + m_hBlastSprite->SetScale( 0.1f, 0.2f ); + m_hBlastSprite->TurnOff(); + } +} + +//----------------------------------------------------------------------------- +// Closing effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectClosed( void ) +{ + float flScaleFactor = SpriteScaleFactor(); + + // Turn off the center sprite + if ( m_hCenterSprite != NULL ) + { + m_hCenterSprite->SetBrightness( 0, 0.1f ); + m_hCenterSprite->SetScale( 0.0f, 0.1f ); + m_hCenterSprite->TurnOff(); + } + + // Turn off the end-caps + for ( int i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] != NULL ) + { + m_hEndSprites[i]->TurnOff(); + } + } + + // Turn off the lightning + for ( int i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] != NULL ) + { + m_hBeams[i]->SetBrightness( 0 ); + } + } + + // Turn on the glow sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] != NULL ) + { + m_hGlowSprites[i]->TurnOn(); + m_hGlowSprites[i]->SetBrightness( 16, 0.2f ); + m_hGlowSprites[i]->SetScale( 0.3f * flScaleFactor, 0.2f ); + } + } + + // Prepare for scale down + if ( m_hBlastSprite != NULL ) + { + m_hBlastSprite->TurnOn(); + m_hBlastSprite->SetScale( 1.0f, 0.0f ); + m_hBlastSprite->SetBrightness( 0, 0.0f ); + } +} + +//----------------------------------------------------------------------------- +// Closing effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffectClosed( void ) +{ + float flScaleFactor = SpriteScaleFactor(); + + // Turn off the center sprite + if ( m_hCenterSprite != NULL ) + { + m_hCenterSprite->SetBrightness( 0, 0.1f ); + m_hCenterSprite->SetScale( 0.0f, 0.1f ); + m_hCenterSprite->TurnOff(); + } + + // Turn off the end-caps + for ( int i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] != NULL ) + { + m_hEndSprites[i]->TurnOff(); + } + } + + // Turn off the lightning + for ( int i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] != NULL ) + { + m_hBeams[i]->SetBrightness( 0 ); + } + } + + // Turn on the glow sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] != NULL ) + { + m_hGlowSprites[i]->TurnOn(); + m_hGlowSprites[i]->SetBrightness( 16, 0.2f ); + m_hGlowSprites[i]->SetScale( 0.3f * flScaleFactor, 0.2f ); + } + } + + // Prepare for scale down + if ( m_hBlastSprite != NULL ) + { + m_hBlastSprite->TurnOn(); + m_hBlastSprite->SetScale( 1.0f, 0.0f ); + m_hBlastSprite->SetBrightness( 0, 0.0f ); + } +} + +//----------------------------------------------------------------------------- +// Ready effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectReady( ) +{ + float flScaleFactor = SpriteScaleFactor(); + + //Turn on the center sprite + if ( m_hCenterSprite != NULL ) + { + m_hCenterSprite->SetBrightness( 128, 0.2f ); + m_hCenterSprite->SetScale( 0.15f, 0.2f ); + m_hCenterSprite->TurnOn(); + } + + //Turn off the end-caps + for ( int i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] != NULL ) + { + m_hEndSprites[i]->TurnOff(); + } + } + + //Turn off the lightning + for ( int i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] != NULL ) + { + m_hBeams[i]->SetBrightness( 0 ); + } + } + + //Turn on the glow sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] != NULL ) + { + m_hGlowSprites[i]->TurnOn(); + m_hGlowSprites[i]->SetBrightness( 32, 0.2f ); + m_hGlowSprites[i]->SetScale( 0.4f * flScaleFactor, 0.2f ); + } + } + + //Scale down + if ( m_hBlastSprite != NULL ) + { + m_hBlastSprite->TurnOn(); + m_hBlastSprite->SetScale( 0.1f, 0.2f ); + m_hBlastSprite->SetBrightness( 255, 0.1f ); + } +} + + +//----------------------------------------------------------------------------- +// Holding effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectHolding( ) +{ + float flScaleFactor = SpriteScaleFactor(); + + // Turn off the center sprite + if ( m_hCenterSprite != NULL ) + { + m_hCenterSprite->SetBrightness( 255, 0.1f ); + m_hCenterSprite->SetScale( 0.2f, 0.2f ); + m_hCenterSprite->TurnOn(); + } + + // Turn off the end-caps + for ( int i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] != NULL ) + { + m_hEndSprites[i]->TurnOn(); + } + } + + // Turn off the lightning + for ( int i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] != NULL ) + { + m_hBeams[i]->SetBrightness( 128 ); + } + } + + // Turn on the glow sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] != NULL ) + { + m_hGlowSprites[i]->TurnOn(); + m_hGlowSprites[i]->SetBrightness( 64, 0.2f ); + m_hGlowSprites[i]->SetScale( 0.5f * flScaleFactor, 0.2f ); + } + } + + // Prepare for scale up + if ( m_hBlastSprite != NULL ) + { + m_hBlastSprite->TurnOff(); + m_hBlastSprite->SetScale( 0.1f, 0.0f ); + m_hBlastSprite->SetBrightness( 0, 0.0f ); + } +} + + +//----------------------------------------------------------------------------- +// Launch effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectLaunch( Vector *pos ) +{ + Assert( pos ); + if ( pos == NULL ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + return; + + Vector endpos = *pos; + + // Check to store off our view model index + CBeam *pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 8 ); + + if ( pBeam != NULL ) + { + pBeam->PointEntInit( endpos, this ); + pBeam->SetEndAttachment( 1 ); + pBeam->SetWidth( 6.4 ); + pBeam->SetEndWidth( 12.8 ); + pBeam->SetBrightness( 255 ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->LiveForTime( 0.1f ); + pBeam->RelinkBeam(); + pBeam->SetNoise( 2 ); + } + + Vector shotDir = ( endpos - pOwner->Weapon_ShootPosition() ); + VectorNormalize( shotDir ); + + //End hit + //FIXME: Probably too big + CPVSFilter filter( endpos ); + te->GaussExplosion( filter, 0.0f, endpos - ( shotDir * 4.0f ), RandomVector(-1.0f, 1.0f), 0 ); + + if ( m_hBlastSprite != NULL ) + { + m_hBlastSprite->TurnOn(); + m_hBlastSprite->SetScale( 2.0f, 0.1f ); + m_hBlastSprite->SetBrightness( 0, 0.1f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pos - +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffectLaunch( Vector *pos ) +{ + Assert( pos ); + if ( pos == NULL ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + return; + + Vector endpos = *pos; + + // Check to store off our view model index + CBaseViewModel *vm = pOwner->GetViewModel(); + + int numBeams = random->RandomInt( 1, 2 ); + + CBeam *pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 0.8 ); + + if ( pBeam != NULL ) + { + pBeam->PointEntInit( endpos, vm ); + pBeam->SetEndAttachment( 1 ); + pBeam->SetWidth( 2 ); + pBeam->SetEndWidth( 12 ); + pBeam->SetBrightness( 255 ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->LiveForTime( 0.1f ); + pBeam->RelinkBeam(); + pBeam->SetNoise( 0 ); + } + + for ( int i = 0; i < numBeams; i++ ) + { + pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 0.8 ); + + if ( pBeam != NULL ) + { + pBeam->PointEntInit( endpos, vm ); + pBeam->SetEndAttachment( 1 ); + pBeam->SetWidth( 2 ); + pBeam->SetEndWidth( random->RandomInt( 1, 2 ) ); + pBeam->SetBrightness( 255 ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->LiveForTime( 0.1f ); + pBeam->RelinkBeam(); + pBeam->SetNoise( random->RandomInt( 8, 12 ) ); + } + } + + Vector shotDir = ( endpos - pOwner->Weapon_ShootPosition() ); + VectorNormalize( shotDir ); + + //End hit + //FIXME: Probably too big + CPVSFilter filter( endpos ); + te->GaussExplosion( filter, 0.0f, endpos - ( shotDir * 4.0f ), RandomVector(-1.0f, 1.0f), 0 ); +} + +//----------------------------------------------------------------------------- +// Holding effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffectHolding( void ) +{ + float flScaleFactor = SpriteScaleFactor(); + + // Turn off the center sprite + if ( m_hCenterSprite != NULL ) + { + m_hCenterSprite->SetBrightness( 255, 0.1f ); + m_hCenterSprite->SetScale( 0.2f, 0.2f ); + m_hCenterSprite->TurnOn(); + } + + // Turn off the end-caps + for ( int i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] != NULL ) + { + m_hEndSprites[i]->TurnOn(); + } + } + + // Turn off the lightning + for ( int i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] != NULL ) + { + m_hBeams[i]->SetBrightness( 128 ); + } + } + + // Turn on the glow sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] != NULL ) + { + m_hGlowSprites[i]->TurnOn(); + m_hGlowSprites[i]->SetBrightness( 32, 0.2f ); + m_hGlowSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f ); + } + } +} + +//----------------------------------------------------------------------------- +// Ready effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffectReady( void ) +{ + float flScaleFactor = SpriteScaleFactor(); + + //Turn on the center sprite + if ( m_hCenterSprite != NULL ) + { + m_hCenterSprite->SetBrightness( 128, 0.2f ); + m_hCenterSprite->SetScale( 0.15f, 0.2f ); + m_hCenterSprite->TurnOn(); + } + + //Turn off the end-caps + for ( int i = 0; i < 2; i++ ) + { + if ( m_hEndSprites[i] != NULL ) + { + if ( m_flEndSpritesOverride[i] < gpGlobals->curtime ) + { + m_hEndSprites[i]->TurnOff(); + } + } + } + + //Turn off the lightning + for ( int i = 0; i < NUM_BEAMS; i++ ) + { + if ( m_hBeams[i] != NULL ) + { + m_hBeams[i]->SetBrightness( 0 ); + } + } + + //Turn on the glow sprites + for ( int i = 0; i < NUM_SPRITES; i++ ) + { + if ( m_hGlowSprites[i] != NULL ) + { + m_hGlowSprites[i]->TurnOn(); + m_hGlowSprites[i]->SetBrightness( 24, 0.2f ); + m_hGlowSprites[i]->SetScale( 0.2f * flScaleFactor, 0.2f ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shutdown for the weapon when it's holstered +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectNone( void ) +{ + if ( m_hBlastSprite != NULL ) + { + // Become small + m_hBlastSprite->SetScale( 0.001f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : effectType - +// *pos - +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffect( int effectType, Vector *pos ) +{ + switch( effectType ) + { + case EFFECT_CLOSED: + DoMegaEffectClosed(); + break; + + case EFFECT_READY: + DoMegaEffectReady(); + break; + + case EFFECT_HOLDING: + DoMegaEffectHolding(); + break; + + case EFFECT_LAUNCH: + DoMegaEffectLaunch( pos ); + break; + + default: + case EFFECT_NONE: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : effectType - +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos ) +{ + // Make sure we're active + StartEffects(); + + m_EffectState = effectType; + + // Do different effects when upgraded + if ( IsMegaPhysCannon() ) + { + DoMegaEffect( effectType, pos ); + return; + } + + switch( effectType ) + { + case EFFECT_CLOSED: + DoEffectClosed( ); + break; + + case EFFECT_READY: + DoEffectReady( ); + break; + + case EFFECT_HOLDING: + DoEffectHolding(); + break; + + case EFFECT_LAUNCH: + DoEffectLaunch( pos ); + break; + + default: + case EFFECT_NONE: + DoEffectNone(); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iIndex - +// Output : const char +//----------------------------------------------------------------------------- +const char *CWeaponPhysCannon::GetShootSound( int iIndex ) const +{ + // Just do this normally if we're a normal physcannon + if ( PlayerHasMegaPhysCannon() == false ) + return BaseClass::GetShootSound( iIndex ); + + // We override this if we're the charged up version + switch( iIndex ) + { + case EMPTY: + return "Weapon_MegaPhysCannon.DryFire"; + break; + + case SINGLE: + return "Weapon_MegaPhysCannon.Launch"; + break; + + case SPECIAL1: + return "Weapon_MegaPhysCannon.Pickup"; + break; + + case MELEE_MISS: + return "Weapon_MegaPhysCannon.Drop"; + break; + + default: + break; + } + + return BaseClass::GetShootSound( iIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds the specified object to the list of objects that have been +// propelled by this physgun, along with a timestamp of when the +// object was added to the list. This list is checked when a physics +// object strikes another entity, to resolve whether the player is +// accountable for the impact. +// +// Input : pObject - pointer to the object being thrown by the physcannon. +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::RecordThrownObject( CBaseEntity *pObject ) +{ + thrown_objects_t thrown; + thrown.hEntity = pObject; + thrown.fTimeThrown = gpGlobals->curtime; + + // Get rid of stale and dead objects in the list. + PurgeThrownObjects(); + + // See if this object is already in the list. + int count = m_ThrownEntities.Count(); + + for( int i = 0 ; i < count ; i++ ) + { + if( m_ThrownEntities[i].hEntity == pObject ) + { + // Just update the time. + //Msg("++UPDATING: %s (%d)\n", m_ThrownEntities[i].hEntity->GetClassname(), m_ThrownEntities[i].hEntity->entindex() ); + m_ThrownEntities[i] = thrown; + return; + } + } + + //Msg("++ADDING: %s (%d)\n", pObject->GetClassname(), pObject->entindex() ); + + m_ThrownEntities.AddToTail(thrown); +} + +//----------------------------------------------------------------------------- +// Purpose: Go through the objects in the thrown objects list and discard any +// objects that have gone 'stale'. (Were thrown several seconds ago), or +// have been destroyed or removed. +// +//----------------------------------------------------------------------------- +#define PHYSCANNON_THROWN_LIST_TIMEOUT 10.0f +void CWeaponPhysCannon::PurgeThrownObjects() +{ + bool bListChanged; + + // This is bubble-sorty, but the list is also very short. + do + { + bListChanged = false; + + int count = m_ThrownEntities.Count(); + for( int i = 0 ; i < count ; i++ ) + { + bool bRemove = false; + + if( !m_ThrownEntities[i].hEntity.Get() ) + { + bRemove = true; + } + else if( gpGlobals->curtime > (m_ThrownEntities[i].fTimeThrown + PHYSCANNON_THROWN_LIST_TIMEOUT) ) + { + bRemove = true; + } + else + { + IPhysicsObject *pObject = m_ThrownEntities[i].hEntity->VPhysicsGetObject(); + + if( pObject && pObject->IsAsleep() ) + { + bRemove = true; + } + } + + if( bRemove ) + { + //Msg("--REMOVING: %s (%d)\n", m_ThrownEntities[i].hEntity->GetClassname(), m_ThrownEntities[i].hEntity->entindex() ); + m_ThrownEntities.Remove(i); + bListChanged = true; + break; + } + } + } while( bListChanged ); +} + +bool CWeaponPhysCannon::IsAccountableForObject( CBaseEntity *pObject ) +{ + // Clean out the stale and dead items. + PurgeThrownObjects(); + + // Now if this object is in the list, the player is accountable for it striking something. + int count = m_ThrownEntities.Count(); + + for( int i = 0 ; i < count ; i++ ) + { + if( m_ThrownEntities[i].hEntity == pObject ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// EXTERNAL API +//----------------------------------------------------------------------------- +void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIfHoldingThis ) +{ + CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); + if ( pCannon ) + { + if ( pOnlyIfHoldingThis ) + { + pCannon->DropIfEntityHeld( pOnlyIfHoldingThis ); + } + else + { + pCannon->ForceDrop(); + } + } +} + +void PhysCannonBeginUpgrade( CBaseAnimating *pAnim ) +{ + CWeaponPhysCannon *pWeaponPhyscannon = assert_cast< CWeaponPhysCannon* >( pAnim ); + pWeaponPhyscannon->BeginUpgrade(); +} + + +bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupControllerEntity, CBaseEntity *pHeldEntity ) +{ + CPlayerPickupController *pController = dynamic_cast(pPickupControllerEntity); + + return pController ? pController->IsHoldingEntity( pHeldEntity ) : false; +} + + +float PhysCannonGetHeldObjectMass( CBaseCombatWeapon *pActiveWeapon, IPhysicsObject *pHeldObject ) +{ + float mass = 0.0f; + CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); + if ( pCannon ) + { + CGrabController &grab = pCannon->GetGrabController(); + mass = grab.GetSavedMass( pHeldObject ); + } + + return mass; +} + +CBaseEntity *PhysCannonGetHeldEntity( CBaseCombatWeapon *pActiveWeapon ) +{ + CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); + if ( pCannon ) + { + CGrabController &grab = pCannon->GetGrabController(); + return grab.GetAttached(); + } + + return NULL; +} + +CBaseEntity *GetPlayerHeldEntity( CBasePlayer *pPlayer ) +{ + CBaseEntity *pObject = NULL; + CPlayerPickupController *pPlayerPickupController = (CPlayerPickupController *)(pPlayer->GetUseEntity()); + + if ( pPlayerPickupController ) + { + pObject = pPlayerPickupController->GetGrabController().GetAttached(); + } + + return pObject; +} + +bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject ) +{ + CWeaponPhysCannon *pCannon = dynamic_cast(pPhysCannon); + if ( pCannon ) + { + return pCannon->IsAccountableForObject(pObject); + } + + return false; +} + +float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject ) +{ + float mass = 0.0f; + CPlayerPickupController *pController = dynamic_cast(pPickupControllerEntity); + if ( pController ) + { + CGrabController &grab = pController->GetGrabController(); + mass = grab.GetSavedMass( pHeldObject ); + } + return mass; +} diff --git a/dlls/hl2_dll/weapon_pistol.cpp b/dlls/hl2_dll/weapon_pistol.cpp index 951dd065..cad414ca 100644 --- a/dlls/hl2_dll/weapon_pistol.cpp +++ b/dlls/hl2_dll/weapon_pistol.cpp @@ -6,10 +6,10 @@ //=============================================================================// #include "cbase.h" -#include "NPCEvent.h" +#include "npcevent.h" #include "basehlcombatweapon.h" #include "basecombatcharacter.h" -#include "AI_BaseNPC.h" +#include "ai_basenpc.h" #include "player.h" #include "gamerules.h" #include "in_buttons.h" @@ -190,7 +190,7 @@ void CWeaponPistol::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCh vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); - CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), static_cast(SOUNDENT_VOLUME_PISTOL), 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); WeaponSound( SINGLE_NPC ); pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2 ); @@ -232,7 +232,7 @@ void CWeaponPistol::PrimaryAttack( void ) m_flLastAttackTime = gpGlobals->curtime; m_flSoonestPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME; - CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, GetOwner() ); + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), static_cast(SOUNDENT_VOLUME_PISTOL), 0.2, GetOwner() ); CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); diff --git a/dlls/hl2_dll/weapon_rpg.cpp b/dlls/hl2_dll/weapon_rpg.cpp index d8480d85..3b9b14e4 100644 --- a/dlls/hl2_dll/weapon_rpg.cpp +++ b/dlls/hl2_dll/weapon_rpg.cpp @@ -18,8 +18,8 @@ #include "in_buttons.h" #include "weapon_rpg.h" #include "shake.h" -#include "AI_BaseNPC.h" -#include "AI_Squad.h" +#include "ai_basenpc.h" +#include "ai_squad.h" #include "te_effect_dispatch.h" #include "triggers.h" #include "smoke_trail.h" @@ -85,7 +85,7 @@ public: // a list of laser dots to search quickly CEntityClassList g_LaserDotList; -CLaserDot *CEntityClassList::m_pClassList = NULL; +template<> CLaserDot *CEntityClassList::m_pClassList = NULL; CLaserDot *GetLaserDotList() { return g_LaserDotList.m_pClassList; @@ -335,7 +335,7 @@ void CMissile::ShotDown( void ) void CMissile::DoExplosion( void ) { // Explode - ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), GetDamage(), CMissile::EXPLOSION_RADIUS, + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), static_cast(GetDamage()), CMissile::EXPLOSION_RADIUS, SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this); } @@ -819,7 +819,7 @@ CBaseEntity *CInfoAPCMissileHint::FindAimTarget( CBaseEntity *pMissile, const ch // a list of missiles to search quickly //----------------------------------------------------------------------------- CEntityClassList g_APCMissileList; -CAPCMissile *CEntityClassList::m_pClassList = NULL; +template <> CAPCMissile *CEntityClassList::m_pClassList = NULL; CAPCMissile *GetAPCMissileList() { return g_APCMissileList.m_pClassList; @@ -1083,9 +1083,9 @@ void CAPCMissile::DoExplosion( void ) else { #ifdef HL2_EPISODIC - ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), this, APC_MISSILE_DAMAGE, 100, true, 20000 ); + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), this, static_cast(APC_MISSILE_DAMAGE), 100, true, 20000 ); #else - ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), APC_MISSILE_DAMAGE, 100, true, 20000 ); + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), static_cast(APC_MISSILE_DAMAGE), 100, true, 20000 ); #endif } } @@ -1177,7 +1177,7 @@ void CAPCMissile::ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActua m_hSpecificTarget = CInfoAPCMissileHint::FindAimTarget( this, STRING( m_strHint ), vecOrigin, vecVelocity ); } - CBaseEntity *pLaserTarget = m_hSpecificTarget ? m_hSpecificTarget : pLaserDot->GetTargetEntity(); + CBaseEntity *pLaserTarget = m_hSpecificTarget ? m_hSpecificTarget.Get() : pLaserDot->GetTargetEntity(); if ( !pLaserTarget ) { BaseClass::ComputeActualDotPosition( pLaserDot, pActualDotPosition, pHomingSpeed ); diff --git a/dlls/hl2_dll/weapon_shotgun.cpp b/dlls/hl2_dll/weapon_shotgun.cpp index 7674c8ce..2cd55088 100644 --- a/dlls/hl2_dll/weapon_shotgun.cpp +++ b/dlls/hl2_dll/weapon_shotgun.cpp @@ -8,14 +8,14 @@ //=============================================================================// #include "cbase.h" -#include "NPCEvent.h" -#include "basehlcombatweapon_shared.h" -#include "basecombatcharacter.h" -#include "AI_BaseNPC.h" -#include "player.h" -#include "gamerules.h" // For g_pGameRules -#include "in_buttons.h" -#include "soundent.h" +#include "npcevent.h" +#include "basehlcombatweapon_shared.h" +#include "basecombatcharacter.h" +#include "ai_basenpc.h" +#include "player.h" +#include "gamerules.h" // For g_pGameRules +#include "in_buttons.h" +#include "soundent.h" #include "vstdlib/random.h" // memdbgon must be the last include file in a .cpp file!!! @@ -420,7 +420,7 @@ void CWeaponShotgun::PrimaryAttack( void ) pPlayer->ViewPunch( QAngle( random->RandomFloat( -2, -1 ), random->RandomFloat( -2, 2 ), 0 ) ); - CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_SHOTGUN, 0.2, GetOwner() ); + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), static_cast(SOUNDENT_VOLUME_SHOTGUN), 0.2, GetOwner() ); if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) { @@ -474,7 +474,7 @@ void CWeaponShotgun::SecondaryAttack( void ) pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 1.0 ); - CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_SHOTGUN, 0.2 ); + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), static_cast(SOUNDENT_VOLUME_SHOTGUN), 0.2 ); if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) { diff --git a/dlls/hl2_dll/weapon_smg1.cpp b/dlls/hl2_dll/weapon_smg1.cpp index 43ac032a..78fe4246 100644 --- a/dlls/hl2_dll/weapon_smg1.cpp +++ b/dlls/hl2_dll/weapon_smg1.cpp @@ -6,14 +6,14 @@ #include "cbase.h" #include "basehlcombatweapon.h" -#include "NPCevent.h" +#include "npcevent.h" #include "basecombatcharacter.h" -#include "AI_BaseNPC.h" +#include "ai_basenpc.h" #include "player.h" #include "game.h" #include "in_buttons.h" #include "grenade_ar2.h" -#include "AI_Memory.h" +#include "ai_memory.h" #include "soundent.h" #include "rumble_shared.h" @@ -178,7 +178,7 @@ void CWeaponSMG1::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector // FIXME: use the returned number of bullets to account for >10hz firerate WeaponSoundRealtime( SINGLE_NPC ); - CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), static_cast(SOUNDENT_VOLUME_MACHINEGUN), 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), 0 ); diff --git a/dlls/hl2_dll/weapon_stunstick.cpp b/dlls/hl2_dll/weapon_stunstick.cpp index bd5b2ec4..1f1e54c2 100644 --- a/dlls/hl2_dll/weapon_stunstick.cpp +++ b/dlls/hl2_dll/weapon_stunstick.cpp @@ -211,7 +211,7 @@ void CWeaponStunStick::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseComba VectorMA( pOperator->Weapon_ShootPosition(), 32, vecDirection, vecEnd ); // Stretch the swing box down to catch low level physics objects CBaseEntity *pHurt = pOperator->CheckTraceHullAttack( pOperator->Weapon_ShootPosition(), vecEnd, - Vector(-16,-16,-40), Vector(16,16,16), GetDamageForActivity( GetActivity() ), DMG_CLUB, 0.5f, false ); + Vector(-16,-16,-40), Vector(16,16,16), static_cast(GetDamageForActivity( GetActivity() )), DMG_CLUB, 0.5f, false ); // did I hit someone? if ( pHurt ) diff --git a/dlls/hl2_dll/weapon_thumper.cpp b/dlls/hl2_dll/weapon_thumper.cpp index 8825b372..bb9da9ba 100644 --- a/dlls/hl2_dll/weapon_thumper.cpp +++ b/dlls/hl2_dll/weapon_thumper.cpp @@ -8,9 +8,9 @@ #include "cbase.h" #include "basehlcombatweapon.h" -#include "NPCevent.h" +#include "npcevent.h" #include "basecombatcharacter.h" -#include "AI_BaseNPC.h" +#include "ai_basenpc.h" #include "player.h" #include "entitylist.h" #include "ndebugoverlay.h" diff --git a/dlls/hl2mp_dll/grenade_satchel.cpp b/dlls/hl2mp_dll/grenade_satchel.cpp index 31d016af..4405f26d 100644 --- a/dlls/hl2mp_dll/grenade_satchel.cpp +++ b/dlls/hl2mp_dll/grenade_satchel.cpp @@ -118,7 +118,7 @@ void CSatchelCharge::CreateEffects( void ) //----------------------------------------------------------------------------- void CSatchelCharge::InputExplode( inputdata_t &inputdata ) { - ExplosionCreate( GetAbsOrigin() + Vector( 0, 0, 16 ), GetAbsAngles(), GetThrower(), GetDamage(), 200, + ExplosionCreate( GetAbsOrigin() + Vector( 0, 0, 16 ), GetAbsAngles(), GetThrower(), static_cast(GetDamage()), 200, SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this); UTIL_Remove( this ); diff --git a/dlls/hl2mp_dll/grenade_tripmine.cpp b/dlls/hl2mp_dll/grenade_tripmine.cpp index 6b4dd86d..03f5274c 100644 --- a/dlls/hl2mp_dll/grenade_tripmine.cpp +++ b/dlls/hl2mp_dll/grenade_tripmine.cpp @@ -176,7 +176,7 @@ void CTripmineGrenade::MakeBeam( void ) m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 0.35 ); m_pBeam->PointEntInit( vecTmpEnd, this ); m_pBeam->SetColor( 255, 55, 52 ); - m_pBeam->SetScrollRate( 25.6 ); + m_pBeam->SetScrollRate( static_cast(25.6) ); m_pBeam->SetBrightness( 64 ); int beamAttach = LookupAttachment("beam_attach"); @@ -266,7 +266,7 @@ void CTripmineGrenade::DelayDeathThink( void ) UTIL_TraceLine ( GetAbsOrigin() + m_vecDir * 8, GetAbsOrigin() - m_vecDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, & tr); UTIL_ScreenShake( GetAbsOrigin(), 25.0, 150.0, 1.0, 750, SHAKE_START ); - ExplosionCreate( GetAbsOrigin() + m_vecDir * 8, GetAbsAngles(), m_hOwner, GetDamage(), 200, + ExplosionCreate( GetAbsOrigin() + m_vecDir * 8, GetAbsAngles(), m_hOwner, static_cast(GetDamage()), 200, SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this); UTIL_Remove( this ); diff --git a/dlls/hl2mp_dll/hl2mp_bot_temp.cpp b/dlls/hl2mp_dll/hl2mp_bot_temp.cpp index 88419b80..3ba4fb6c 100644 --- a/dlls/hl2mp_dll/hl2mp_bot_temp.cpp +++ b/dlls/hl2mp_dll/hl2mp_bot_temp.cpp @@ -40,7 +40,6 @@ ConVar bot_crouch( "bot_crouch", "0", 0, "Bot crouches" ); static int BotNumber = 1; static int g_iNextBotTeam = -1; -static int g_iNextBotClass = -1; typedef struct { @@ -250,7 +249,7 @@ void Bot_Think( CHL2MP_Player *pBot ) QAngle angle; float angledelta = 15.0; - int maxtries = (int)360.0/angledelta; + int maxtries = static_cast(360.0 / angledelta); if ( botdata->lastturntoright ) { diff --git a/dlls/hl2mp_dll/hl2mp_bot_temp.h b/dlls/hl2mp_dll/hl2mp_bot_temp.h index 03b0bb75..562dade8 100644 --- a/dlls/hl2mp_dll/hl2mp_bot_temp.h +++ b/dlls/hl2mp_dll/hl2mp_bot_temp.h @@ -1,19 +1,18 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// -#ifndef BOT_BASE_H -#define BOT_BASE_H -#ifdef _WIN32 -#pragma once -#endif - - -// If iTeam or iClass is -1, then a team or class is randomly chosen. -CBasePlayer *BotPutInServer( bool bFrozen, int iTeam ); - - -#endif // BOT_BASE_H - +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef BOT_BASE_H +#define BOT_BASE_H +#ifdef _WIN32 +#pragma once +#endif + + +// If iTeam or iClass is -1, then a team or class is randomly chosen. +CBasePlayer *BotPutInServer( bool bFrozen, int iTeam ); + + +#endif // BOT_BASE_H diff --git a/dlls/hl2mp_dll/hl2mp_cvars.cpp b/dlls/hl2mp_dll/hl2mp_cvars.cpp index 21f0ac00..9b59389c 100644 --- a/dlls/hl2mp_dll/hl2mp_cvars.cpp +++ b/dlls/hl2mp_dll/hl2mp_cvars.cpp @@ -1,32 +1,32 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "hl2mp_cvars.h" - - - -// Restart the round -ConVar mp_restartround( - "mp_restartround", - "0", - FCVAR_GAMEDLL, - "If non-zero, game will restart in the specified number of seconds" ); - -// Ready restart -ConVar mp_readyrestart( - "mp_readyrestart", - "0", - FCVAR_GAMEDLL, - "If non-zero, game will restart once each player gives the ready signal" ); - -// Ready signal -ConVar mp_ready_signal( - "mp_ready_signal", - "ready", - FCVAR_GAMEDLL, - "Text that each player must speak for the match to begin" ); \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "hl2mp_cvars.h" + + + +// Restart the round +ConVar mp_restartround( + "mp_restartround", + "0", + FCVAR_GAMEDLL, + "If non-zero, game will restart in the specified number of seconds" ); + +// Ready restart +ConVar mp_readyrestart( + "mp_readyrestart", + "0", + FCVAR_GAMEDLL, + "If non-zero, game will restart once each player gives the ready signal" ); + +// Ready signal +ConVar mp_ready_signal( + "mp_ready_signal", + "ready", + FCVAR_GAMEDLL, + "Text that each player must speak for the match to begin" ); diff --git a/dlls/hl2mp_dll/hl2mp_cvars.h b/dlls/hl2mp_dll/hl2mp_cvars.h index 3602ac60..64d725d8 100644 --- a/dlls/hl2mp_dll/hl2mp_cvars.h +++ b/dlls/hl2mp_dll/hl2mp_cvars.h @@ -1,20 +1,20 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef HL2MP_CVARS_H -#define HL2MP_CVARS_H -#ifdef _WIN32 -#pragma once -#endif - -#define MAX_INTERMISSION_TIME 120 - -extern ConVar mp_restartround; -extern ConVar mp_readyrestart; -extern ConVar mp_ready_signal; - -#endif //HL2MP_CVARS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef HL2MP_CVARS_H +#define HL2MP_CVARS_H +#ifdef _WIN32 +#pragma once +#endif + +#define MAX_INTERMISSION_TIME 120 + +extern ConVar mp_restartround; +extern ConVar mp_readyrestart; +extern ConVar mp_ready_signal; + +#endif //HL2MP_CVARS_H diff --git a/dlls/hl2mp_dll/hl2mp_gameinterface.cpp b/dlls/hl2mp_dll/hl2mp_gameinterface.cpp index 42d12104..d511c5fb 100644 --- a/dlls/hl2mp_dll/hl2mp_gameinterface.cpp +++ b/dlls/hl2mp_dll/hl2mp_gameinterface.cpp @@ -1,64 +1,64 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" -#include "gameinterface.h" -#include "mapentities.h" -#include "hl2mp_gameinterface.h" - -#include "utllinkedlist.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -CUtlLinkedList g_MapEntityRefs; - -void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const -{ - minplayers = defaultMaxPlayers = 2; - maxplayers = 16; -} - - -// -------------------------------------------------------------------------------------------- // -// Mod-specific CServerGameDLL implementation. -// -------------------------------------------------------------------------------------------- // - -class CHL2MPMapLoadEntityFilter : public IMapEntityFilter -{ -public: - virtual bool ShouldCreateEntity( const char *pClassname ) - { - // During map load, create all the entities. - return true; - } - - virtual CBaseEntity* CreateNextEntity( const char *pClassname ) - { - CBaseEntity *pRet = CreateEntityByName( pClassname ); - - CMapEntityRef ref; - ref.m_iEdict = -1; - ref.m_iSerialNumber = -1; - - if ( pRet ) - { - ref.m_iEdict = pRet->entindex(); - if ( pRet->edict() ) - ref.m_iSerialNumber = pRet->edict()->m_NetworkSerialNumber; - } - - g_MapEntityRefs.AddToTail( ref ); - return pRet; - } -}; - -void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities ) -{ - g_MapEntityRefs.Purge(); - CHL2MPMapLoadEntityFilter filter; - MapEntity_ParseAllEntities( pMapEntities, &filter ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "gameinterface.h" +#include "mapentities.h" +#include "hl2mp_gameinterface.h" + +#include "utllinkedlist.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CUtlLinkedList g_MapEntityRefs; + +void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const +{ + minplayers = defaultMaxPlayers = 2; + maxplayers = 16; +} + + +// -------------------------------------------------------------------------------------------- // +// Mod-specific CServerGameDLL implementation. +// -------------------------------------------------------------------------------------------- // + +class CHL2MPMapLoadEntityFilter : public IMapEntityFilter +{ +public: + virtual bool ShouldCreateEntity( const char *pClassname ) + { + // During map load, create all the entities. + return true; + } + + virtual CBaseEntity* CreateNextEntity( const char *pClassname ) + { + CBaseEntity *pRet = CreateEntityByName( pClassname ); + + CMapEntityRef ref; + ref.m_iEdict = -1; + ref.m_iSerialNumber = -1; + + if ( pRet ) + { + ref.m_iEdict = pRet->entindex(); + if ( pRet->edict() ) + ref.m_iSerialNumber = pRet->edict()->m_NetworkSerialNumber; + } + + g_MapEntityRefs.AddToTail( ref ); + return pRet; + } +}; + +void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities ) +{ + g_MapEntityRefs.Purge(); + CHL2MPMapLoadEntityFilter filter; + MapEntity_ParseAllEntities( pMapEntities, &filter ); +} diff --git a/dlls/hl2mp_dll/hl2mp_gameinterface.h b/dlls/hl2mp_dll/hl2mp_gameinterface.h index 2648b132..2e1c0b18 100644 --- a/dlls/hl2mp_dll/hl2mp_gameinterface.h +++ b/dlls/hl2mp_dll/hl2mp_gameinterface.h @@ -1,20 +1,20 @@ -#ifndef HL2MP_GAMEINTERFACE_H -#define HL2MP_GAMEINTERFACE_H -#ifdef _WIN32 -#pragma once -#endif - -#include "utllinkedlist.h" - - -// These are created for map entities in order as the map entities are spawned. -class CMapEntityRef -{ -public: - int m_iEdict; // Which edict slot this entity got. -1 if CreateEntityByName failed. - int m_iSerialNumber; // The edict serial number. TODO used anywhere ? -}; - -extern CUtlLinkedList g_MapEntityRefs; - -#endif \ No newline at end of file +#ifndef HL2MP_GAMEINTERFACE_H +#define HL2MP_GAMEINTERFACE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utllinkedlist.h" + + +// These are created for map entities in order as the map entities are spawned. +class CMapEntityRef +{ +public: + int m_iEdict; // Which edict slot this entity got. -1 if CreateEntityByName failed. + int m_iSerialNumber; // The edict serial number. TODO used anywhere ? +}; + +extern CUtlLinkedList g_MapEntityRefs; + +#endif diff --git a/dlls/hl2mp_dll/hl2mp_player.cpp b/dlls/hl2mp_dll/hl2mp_player.cpp index 8b4fb66f..5b1ad534 100644 --- a/dlls/hl2mp_dll/hl2mp_player.cpp +++ b/dlls/hl2mp_dll/hl2mp_player.cpp @@ -1,1614 +1,1616 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Player for HL2. -// -//=============================================================================// - -#include "weapon_hl2mpbasehlmpcombatweapon.h" - -#include "cbase.h" -#include "hl2mp_player.h" -#include "globalstate.h" -#include "game.h" -#include "gamerules.h" -#include "hl2mp_player_shared.h" -#include "predicted_viewmodel.h" -#include "in_buttons.h" -#include "hl2mp_gamerules.h" -#include "KeyValues.h" -#include "team.h" -#include "weapon_hl2mpbase.h" -#include "grenade_satchel.h" -#include "eventqueue.h" - -#include "engine/IEngineSound.h" -#include "SoundEmitterSystem/isoundemittersystembase.h" - -#include "ilagcompensationmanager.h" - -int g_iLastCitizenModel = 0; -int g_iLastCombineModel = 0; - -CBaseEntity *g_pLastCombineSpawn = NULL; -CBaseEntity *g_pLastRebelSpawn = NULL; -extern CBaseEntity *g_pLastSpawn; - -#define HL2MP_COMMAND_MAX_RATE 0.3 - -void ClientKill( edict_t *pEdict ); -void DropPrimedFragGrenade( CHL2MP_Player *pPlayer, CBaseCombatWeapon *pGrenade ); - -LINK_ENTITY_TO_CLASS( player, CHL2MP_Player ); - -LINK_ENTITY_TO_CLASS( info_player_combine, CPointEntity ); -LINK_ENTITY_TO_CLASS( info_player_rebel, CPointEntity ); - -IMPLEMENT_SERVERCLASS_ST(CHL2MP_Player, DT_HL2MP_Player) - SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11, SPROP_CHANGES_OFTEN ), - SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11, SPROP_CHANGES_OFTEN ), - SendPropEHandle( SENDINFO( m_hRagdoll ) ), - SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ), - SendPropInt( SENDINFO( m_iPlayerSoundType), 3 ), - - SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), - SendPropExclude( "DT_BaseFlex", "m_viewtarget" ), - -// SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), -// SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ), - -END_SEND_TABLE() - -BEGIN_DATADESC( CHL2MP_Player ) -END_DATADESC() - -const char *g_ppszRandomCitizenModels[] = -{ - "models/humans/group03/male_01.mdl", - "models/humans/group03/male_02.mdl", - "models/humans/group03/female_01.mdl", - "models/humans/group03/male_03.mdl", - "models/humans/group03/female_02.mdl", - "models/humans/group03/male_04.mdl", - "models/humans/group03/female_03.mdl", - "models/humans/group03/male_05.mdl", - "models/humans/group03/female_04.mdl", - "models/humans/group03/male_06.mdl", - "models/humans/group03/female_06.mdl", - "models/humans/group03/male_07.mdl", - "models/humans/group03/female_07.mdl", - "models/humans/group03/male_08.mdl", - "models/humans/group03/male_09.mdl", -}; - -const char *g_ppszRandomCombineModels[] = -{ - "models/combine_soldier.mdl", - "models/combine_soldier_prisonguard.mdl", - "models/combine_super_soldier.mdl", - "models/police.mdl", -}; - - -#define MAX_COMBINE_MODELS 4 -#define MODEL_CHANGE_INTERVAL 5.0f -#define TEAM_CHANGE_INTERVAL 5.0f - -#define HL2MPPLAYER_PHYSDAMAGE_SCALE 4.0f - -#pragma warning( disable : 4355 ) - -CHL2MP_Player::CHL2MP_Player() : m_PlayerAnimState( this ) -{ - m_angEyeAngles.Init(); - - m_iLastWeaponFireUsercmd = 0; - - m_flNextModelChangeTime = 0.0f; - m_flNextTeamChangeTime = 0.0f; - - m_iSpawnInterpCounter = 0; - - m_bEnterObserver = false; - m_bReady = false; - - BaseClass::ChangeTeam( 0 ); - -// UseClientSideAnimation(); -} - -CHL2MP_Player::~CHL2MP_Player( void ) -{ - -} - -void CHL2MP_Player::UpdateOnRemove( void ) -{ - if ( m_hRagdoll ) - { - UTIL_RemoveImmediate( m_hRagdoll ); - m_hRagdoll = NULL; - } - - BaseClass::UpdateOnRemove(); -} - -void CHL2MP_Player::Precache( void ) -{ - BaseClass::Precache(); - - PrecacheModel ( "sprites/glow01.vmt" ); - - //Precache Citizen models - int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); - int i; - - for ( i = 0; i < nHeads; ++i ) - PrecacheModel( g_ppszRandomCitizenModels[i] ); - - //Precache Combine Models - nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); - - for ( i = 0; i < nHeads; ++i ) - PrecacheModel( g_ppszRandomCombineModels[i] ); - - PrecacheFootStepSounds(); - - PrecacheScriptSound( "NPC_MetroPolice.Die" ); - PrecacheScriptSound( "NPC_CombineS.Die" ); - PrecacheScriptSound( "NPC_Citizen.die" ); -} - -void CHL2MP_Player::GiveAllItems( void ) -{ - EquipSuit(); - - CBasePlayer::GiveAmmo( 255, "Pistol"); - CBasePlayer::GiveAmmo( 255, "AR2" ); - CBasePlayer::GiveAmmo( 5, "AR2AltFire" ); - CBasePlayer::GiveAmmo( 255, "SMG1"); - CBasePlayer::GiveAmmo( 1, "smg1_grenade"); - CBasePlayer::GiveAmmo( 255, "Buckshot"); - CBasePlayer::GiveAmmo( 32, "357" ); - CBasePlayer::GiveAmmo( 3, "rpg_round"); - - CBasePlayer::GiveAmmo( 1, "grenade" ); - CBasePlayer::GiveAmmo( 2, "slam" ); - - GiveNamedItem( "weapon_crowbar" ); - GiveNamedItem( "weapon_stunstick" ); - GiveNamedItem( "weapon_pistol" ); - GiveNamedItem( "weapon_357" ); - - GiveNamedItem( "weapon_smg1" ); - GiveNamedItem( "weapon_ar2" ); - - GiveNamedItem( "weapon_shotgun" ); - GiveNamedItem( "weapon_frag" ); - - GiveNamedItem( "weapon_crossbow" ); - - GiveNamedItem( "weapon_rpg" ); - - GiveNamedItem( "weapon_slam" ); - - GiveNamedItem( "weapon_physcannon" ); - -} - -void CHL2MP_Player::GiveDefaultItems( void ) -{ - EquipSuit(); - - CBasePlayer::GiveAmmo( 255, "Pistol"); - CBasePlayer::GiveAmmo( 45, "SMG1"); - CBasePlayer::GiveAmmo( 1, "grenade" ); - CBasePlayer::GiveAmmo( 6, "Buckshot"); - CBasePlayer::GiveAmmo( 6, "357" ); - - if ( GetPlayerModelType() == PLAYER_SOUNDS_METROPOLICE || GetPlayerModelType() == PLAYER_SOUNDS_COMBINESOLDIER ) - { - GiveNamedItem( "weapon_stunstick" ); - } - else if ( GetPlayerModelType() == PLAYER_SOUNDS_CITIZEN ) - { - GiveNamedItem( "weapon_crowbar" ); - } - - GiveNamedItem( "weapon_pistol" ); - GiveNamedItem( "weapon_smg1" ); - GiveNamedItem( "weapon_frag" ); - GiveNamedItem( "weapon_physcannon" ); - - const char *szDefaultWeaponName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_defaultweapon" ); - - CBaseCombatWeapon *pDefaultWeapon = Weapon_OwnsThisType( szDefaultWeaponName ); - - if ( pDefaultWeapon ) - { - Weapon_Switch( pDefaultWeapon ); - } - else - { - Weapon_Switch( Weapon_OwnsThisType( "weapon_physcannon" ) ); - } -} - -void CHL2MP_Player::PickDefaultSpawnTeam( void ) -{ - if ( GetTeamNumber() == 0 ) - { - if ( HL2MPRules()->IsTeamplay() == false ) - { - if ( GetModelPtr() == NULL ) - { - const char *szModelName = NULL; - szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); - - if ( ValidatePlayerModel( szModelName ) == false ) - { - char szReturnString[512]; - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel models/combine_soldier.mdl\n" ); - engine->ClientCommand ( edict(), szReturnString ); - } - - ChangeTeam( TEAM_UNASSIGNED ); - } - } - else - { - CTeam *pCombine = g_Teams[TEAM_COMBINE]; - CTeam *pRebels = g_Teams[TEAM_REBELS]; - - if ( pCombine == NULL || pRebels == NULL ) - { - ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) ); - } - else - { - if ( pCombine->GetNumPlayers() > pRebels->GetNumPlayers() ) - { - ChangeTeam( TEAM_REBELS ); - } - else if ( pCombine->GetNumPlayers() < pRebels->GetNumPlayers() ) - { - ChangeTeam( TEAM_COMBINE ); - } - else - { - ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) ); - } - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Sets HL2 specific defaults. -//----------------------------------------------------------------------------- -void CHL2MP_Player::Spawn(void) -{ - m_flNextModelChangeTime = 0.0f; - m_flNextTeamChangeTime = 0.0f; - - PickDefaultSpawnTeam(); - - BaseClass::Spawn(); - - if ( !IsObserver() ) - { - pl.deadflag = false; - RemoveSolidFlags( FSOLID_NOT_SOLID ); - - RemoveEffects( EF_NODRAW ); - - GiveDefaultItems(); - } - - RemoveEffects( EF_NOINTERP ); - - SetNumAnimOverlays( 3 ); - ResetAnimation(); - - m_nRenderFX = kRenderNormal; - - m_Local.m_iHideHUD = 0; - - AddFlag(FL_ONGROUND); // set the player on the ground at the start of the round. - - m_impactEnergyScale = HL2MPPLAYER_PHYSDAMAGE_SCALE; - - if ( HL2MPRules()->IsIntermission() ) - { - AddFlag( FL_FROZEN ); - } - else - { - RemoveFlag( FL_FROZEN ); - } - - m_iSpawnInterpCounter = (m_iSpawnInterpCounter + 1) % 8; - - m_Local.m_bDucked = false; - - SetPlayerUnderwater(false); - - m_bReady = false; -} - -void CHL2MP_Player::PickupObject( CBaseEntity *pObject, bool bLimitMassAndSize ) -{ - -} - -bool CHL2MP_Player::ValidatePlayerModel( const char *pModel ) -{ - int iModels = ARRAYSIZE( g_ppszRandomCitizenModels ); - int i; - - for ( i = 0; i < iModels; ++i ) - { - if ( !Q_stricmp( g_ppszRandomCitizenModels[i], pModel ) ) - { - return true; - } - } - - iModels = ARRAYSIZE( g_ppszRandomCombineModels ); - - for ( i = 0; i < iModels; ++i ) - { - if ( !Q_stricmp( g_ppszRandomCombineModels[i], pModel ) ) - { - return true; - } - } - - return false; -} - -void CHL2MP_Player::SetPlayerTeamModel( void ) -{ - const char *szModelName = NULL; - szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); - - int modelIndex = modelinfo->GetModelIndex( szModelName ); - - if ( modelIndex == -1 || ValidatePlayerModel( szModelName ) == false ) - { - szModelName = "models/Combine_Soldier.mdl"; - m_iModelType = TEAM_COMBINE; - - char szReturnString[512]; - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName ); - engine->ClientCommand ( edict(), szReturnString ); - } - - if ( GetTeamNumber() == TEAM_COMBINE ) - { - if ( Q_stristr( szModelName, "models/human") ) - { - int nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); - - g_iLastCombineModel = ( g_iLastCombineModel + 1 ) % nHeads; - szModelName = g_ppszRandomCombineModels[g_iLastCombineModel]; - } - - m_iModelType = TEAM_COMBINE; - } - else if ( GetTeamNumber() == TEAM_REBELS ) - { - if ( !Q_stristr( szModelName, "models/human") ) - { - int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); - - g_iLastCitizenModel = ( g_iLastCitizenModel + 1 ) % nHeads; - szModelName = g_ppszRandomCitizenModels[g_iLastCitizenModel]; - } - - m_iModelType = TEAM_REBELS; - } - - SetModel( szModelName ); - SetupPlayerSoundsByModel( szModelName ); - - m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; -} - -void CHL2MP_Player::SetPlayerModel( void ) -{ - const char *szModelName = NULL; - const char *pszCurrentModelName = modelinfo->GetModelName( GetModel()); - - szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); - - if ( ValidatePlayerModel( szModelName ) == false ) - { - char szReturnString[512]; - - if ( ValidatePlayerModel( pszCurrentModelName ) == false ) - { - pszCurrentModelName = "models/Combine_Soldier.mdl"; - } - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pszCurrentModelName ); - engine->ClientCommand ( edict(), szReturnString ); - - szModelName = pszCurrentModelName; - } - - if ( GetTeamNumber() == TEAM_COMBINE ) - { - int nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); - - g_iLastCombineModel = ( g_iLastCombineModel + 1 ) % nHeads; - szModelName = g_ppszRandomCombineModels[g_iLastCombineModel]; - - m_iModelType = TEAM_COMBINE; - } - else if ( GetTeamNumber() == TEAM_REBELS ) - { - int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); - - g_iLastCitizenModel = ( g_iLastCitizenModel + 1 ) % nHeads; - szModelName = g_ppszRandomCitizenModels[g_iLastCitizenModel]; - - m_iModelType = TEAM_REBELS; - } - else - { - if ( Q_strlen( szModelName ) == 0 ) - { - szModelName = g_ppszRandomCitizenModels[0]; - } - - if ( Q_stristr( szModelName, "models/human") ) - { - m_iModelType = TEAM_REBELS; - } - else - { - m_iModelType = TEAM_COMBINE; - } - } - - int modelIndex = modelinfo->GetModelIndex( szModelName ); - - if ( modelIndex == -1 ) - { - szModelName = "models/Combine_Soldier.mdl"; - m_iModelType = TEAM_COMBINE; - - char szReturnString[512]; - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName ); - engine->ClientCommand ( edict(), szReturnString ); - } - - SetModel( szModelName ); - SetupPlayerSoundsByModel( szModelName ); - - m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; -} - -void CHL2MP_Player::SetupPlayerSoundsByModel( const char *pModelName ) -{ - if ( Q_stristr( pModelName, "models/human") ) - { - m_iPlayerSoundType = (int)PLAYER_SOUNDS_CITIZEN; - } - else if ( Q_stristr(pModelName, "police" ) ) - { - m_iPlayerSoundType = (int)PLAYER_SOUNDS_METROPOLICE; - } - else if ( Q_stristr(pModelName, "combine" ) ) - { - m_iPlayerSoundType = (int)PLAYER_SOUNDS_COMBINESOLDIER; - } -} - -void CHL2MP_Player::ResetAnimation( void ) -{ - if ( IsAlive() ) - { - SetSequence ( -1 ); - SetActivity( ACT_INVALID ); - - if (!GetAbsVelocity().x && !GetAbsVelocity().y) - SetAnimation( PLAYER_IDLE ); - else if ((GetAbsVelocity().x || GetAbsVelocity().y) && ( GetFlags() & FL_ONGROUND )) - SetAnimation( PLAYER_WALK ); - else if (GetWaterLevel() > 1) - SetAnimation( PLAYER_WALK ); - } -} - - -bool CHL2MP_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex ) -{ - bool bRet = BaseClass::Weapon_Switch( pWeapon, viewmodelindex ); - - if ( bRet == true ) - { - ResetAnimation(); - } - - return bRet; -} - -void CHL2MP_Player::PreThink( void ) -{ - QAngle vOldAngles = GetLocalAngles(); - QAngle vTempAngles = GetLocalAngles(); - - vTempAngles = EyeAngles(); - - if ( vTempAngles[PITCH] > 180.0f ) - { - vTempAngles[PITCH] -= 360.0f; - } - - SetLocalAngles( vTempAngles ); - - BaseClass::PreThink(); - State_PreThink(); - - //Reset bullet force accumulator, only lasts one frame - m_vecTotalBulletForce = vec3_origin; - SetLocalAngles( vOldAngles ); -} - -void CHL2MP_Player::PostThink( void ) -{ - BaseClass::PostThink(); - - if ( GetFlags() & FL_DUCKING ) - { - SetCollisionBounds( VEC_CROUCH_TRACE_MIN, VEC_CROUCH_TRACE_MAX ); - } - - m_PlayerAnimState.Update(); - - // Store the eye angles pitch so the client can compute its animation state correctly. - m_angEyeAngles = EyeAngles(); - - QAngle angles = GetLocalAngles(); - angles[PITCH] = 0; - SetLocalAngles( angles ); -} - -void CHL2MP_Player::PlayerDeathThink() -{ - if( !IsObserver() ) - { - BaseClass::PlayerDeathThink(); - } -} - -void CHL2MP_Player::FireBullets ( const FireBulletsInfo_t &info ) -{ - // Move other players back to history positions based on local player's lag - lagcompensation->StartLagCompensation( this, this->GetCurrentCommand() ); - - FireBulletsInfo_t modinfo = info; - - CWeaponHL2MPBase *pWeapon = dynamic_cast( GetActiveWeapon() ); - - if ( pWeapon ) - { - modinfo.m_iPlayerDamage = modinfo.m_iDamage = pWeapon->GetHL2MPWpnData().m_iPlayerDamage; - } - - NoteWeaponFired(); - - BaseClass::FireBullets( modinfo ); - - // Move other players back to history positions based on local player's lag - lagcompensation->FinishLagCompensation( this ); -} - -void CHL2MP_Player::NoteWeaponFired( void ) -{ - Assert( m_pCurrentCommand ); - if( m_pCurrentCommand ) - { - m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number; - } -} - -extern ConVar sv_maxunlag; - -bool CHL2MP_Player::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec *pEntityTransmitBits ) const -{ - // No need to lag compensate at all if we're not attacking in this command and - // we haven't attacked recently. - if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) ) - return false; - - // If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it. - if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) ) - return false; - - const Vector &vMyOrigin = GetAbsOrigin(); - const Vector &vHisOrigin = pPlayer->GetAbsOrigin(); - - // get max distance player could have moved within max lag compensation time, - // multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value) - float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat(); - - // If the player is within this distance, lag compensate them in case they're running past us. - if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance ) - return true; - - // If their origin is not within a 45 degree cone in front of us, no need to lag compensate. - Vector vForward; - AngleVectors( pCmd->viewangles, &vForward ); - - Vector vDiff = vHisOrigin - vMyOrigin; - VectorNormalize( vDiff ); - - float flCosAngle = 0.707107f; // 45 degree angle - if ( vForward.Dot( vDiff ) < flCosAngle ) - return false; - - return true; -} - -Activity CHL2MP_Player::TranslateTeamActivity( Activity ActToTranslate ) -{ - if ( m_iModelType == TEAM_COMBINE ) - return ActToTranslate; - - if ( ActToTranslate == ACT_RUN ) - return ACT_RUN_AIM_AGITATED; - - if ( ActToTranslate == ACT_IDLE ) - return ACT_IDLE_AIM_AGITATED; - - if ( ActToTranslate == ACT_WALK ) - return ACT_WALK_AIM_AGITATED; - - return ActToTranslate; -} - -extern ConVar hl2_normspeed; - -// Set the activity based on an event or current state -void CHL2MP_Player::SetAnimation( PLAYER_ANIM playerAnim ) -{ - int animDesired; - - float speed; - - speed = GetAbsVelocity().Length2D(); - - - // bool bRunning = true; - - //Revisit! -/* if ( ( m_nButtons & ( IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) ) ) - { - if ( speed > 1.0f && speed < hl2_normspeed.GetFloat() - 20.0f ) - { - bRunning = false; - } - }*/ - - if ( GetFlags() & ( FL_FROZEN | FL_ATCONTROLS ) ) - { - speed = 0; - playerAnim = PLAYER_IDLE; - } - - Activity idealActivity = ACT_HL2MP_RUN; - - // This could stand to be redone. Why is playerAnim abstracted from activity? (sjb) - if ( playerAnim == PLAYER_JUMP ) - { - idealActivity = ACT_HL2MP_JUMP; - } - else if ( playerAnim == PLAYER_DIE ) - { - if ( m_lifeState == LIFE_ALIVE ) - { - return; - } - } - else if ( playerAnim == PLAYER_ATTACK1 ) - { - if ( GetActivity( ) == ACT_HOVER || - GetActivity( ) == ACT_SWIM || - GetActivity( ) == ACT_HOP || - GetActivity( ) == ACT_LEAP || - GetActivity( ) == ACT_DIESIMPLE ) - { - idealActivity = GetActivity( ); - } - else - { - idealActivity = ACT_HL2MP_GESTURE_RANGE_ATTACK; - } - } - else if ( playerAnim == PLAYER_RELOAD ) - { - idealActivity = ACT_HL2MP_GESTURE_RELOAD; - } - else if ( playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK ) - { - if ( !( GetFlags() & FL_ONGROUND ) && GetActivity( ) == ACT_HL2MP_JUMP ) // Still jumping - { - idealActivity = GetActivity( ); - } - /* - else if ( GetWaterLevel() > 1 ) - { - if ( speed == 0 ) - idealActivity = ACT_HOVER; - else - idealActivity = ACT_SWIM; - } - */ - else - { - if ( GetFlags() & FL_DUCKING ) - { - if ( speed > 0 ) - { - idealActivity = ACT_HL2MP_WALK_CROUCH; - } - else - { - idealActivity = ACT_HL2MP_IDLE_CROUCH; - } - } - else - { - if ( speed > 0 ) - { - /* - if ( bRunning == false ) - { - idealActivity = ACT_WALK; - } - else - */ - { - idealActivity = ACT_HL2MP_RUN; - } - } - else - { - idealActivity = ACT_HL2MP_IDLE; - } - } - } - - idealActivity = TranslateTeamActivity( idealActivity ); - } - - if ( idealActivity == ACT_HL2MP_GESTURE_RANGE_ATTACK ) - { - RestartGesture( Weapon_TranslateActivity( idealActivity ) ); - - // FIXME: this seems a bit wacked - Weapon_SetActivity( Weapon_TranslateActivity( ACT_RANGE_ATTACK1 ), 0 ); - - return; - } - else if ( idealActivity == ACT_HL2MP_GESTURE_RELOAD ) - { - RestartGesture( Weapon_TranslateActivity( idealActivity ) ); - return; - } - else - { - SetActivity( idealActivity ); - - animDesired = SelectWeightedSequence( Weapon_TranslateActivity ( idealActivity ) ); - - if (animDesired == -1) - { - animDesired = SelectWeightedSequence( idealActivity ); - - if ( animDesired == -1 ) - { - animDesired = 0; - } - } - - // Already using the desired animation? - if ( GetSequence() == animDesired ) - return; - - m_flPlaybackRate = 1.0; - ResetSequence( animDesired ); - SetCycle( 0 ); - return; - } - - // Already using the desired animation? - if ( GetSequence() == animDesired ) - return; - - //Msg( "Set animation to %d\n", animDesired ); - // Reset to first frame of desired animation - ResetSequence( animDesired ); - SetCycle( 0 ); -} - - -extern int gEvilImpulse101; -//----------------------------------------------------------------------------- -// Purpose: Player reacts to bumping a weapon. -// Input : pWeapon - the weapon that the player bumped into. -// Output : Returns true if player picked up the weapon -//----------------------------------------------------------------------------- -bool CHL2MP_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) -{ - CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); - - // Can I have this weapon type? - if ( IsEFlagSet( EFL_NO_WEAPON_PICKUP ) ) - return false; - - if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) - { - if ( gEvilImpulse101 ) - { - UTIL_Remove( pWeapon ); - } - return false; - } - - // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows) - if( !pWeapon->FVisible( this, MASK_SOLID ) && !(GetFlags() & FL_NOTARGET) ) - { - return false; - } - - bool bOwnsWeaponAlready = !!Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()); - - if ( bOwnsWeaponAlready == true ) - { - //If we have room for the ammo, then "take" the weapon too. - if ( Weapon_EquipAmmoOnly( pWeapon ) ) - { - pWeapon->CheckRespawn(); - - UTIL_Remove( pWeapon ); - return true; - } - else - { - return false; - } - } - - pWeapon->CheckRespawn(); - Weapon_Equip( pWeapon ); - - return true; -} - -void CHL2MP_Player::ChangeTeam( int iTeam ) -{ -/* if ( GetNextTeamChangeTime() >= gpGlobals->curtime ) - { - char szReturnString[128]; - Q_snprintf( szReturnString, sizeof( szReturnString ), "Please wait %d more seconds before trying to switch teams again.\n", (int)(GetNextTeamChangeTime() - gpGlobals->curtime) ); - - ClientPrint( this, HUD_PRINTTALK, szReturnString ); - return; - }*/ - - bool bKill = false; - - if ( HL2MPRules()->IsTeamplay() != true && iTeam != TEAM_SPECTATOR ) - { - //don't let them try to join combine or rebels during deathmatch. - iTeam = TEAM_UNASSIGNED; - } - - if ( HL2MPRules()->IsTeamplay() == true ) - { - if ( iTeam != GetTeamNumber() && GetTeamNumber() != TEAM_UNASSIGNED ) - { - bKill = true; - } - } - - BaseClass::ChangeTeam( iTeam ); - - m_flNextTeamChangeTime = gpGlobals->curtime + TEAM_CHANGE_INTERVAL; - - if ( HL2MPRules()->IsTeamplay() == true ) - { - SetPlayerTeamModel(); - } - else - { - SetPlayerModel(); - } - - if ( iTeam == TEAM_SPECTATOR ) - { - RemoveAllItems( true ); - - State_Transition( STATE_OBSERVER_MODE ); - } - - if ( bKill == true ) - { - ClientKill( edict() ); - } -} - -bool CHL2MP_Player::HandleCommand_JoinTeam( int team ) -{ - if ( !GetGlobalTeam( team ) ) - { - Warning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team ); - return false; - } - - if ( team == TEAM_SPECTATOR ) - { - // Prevent this is the cvar is set - if ( !mp_allowspectators.GetInt() ) - { - ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" ); - return false; - } - - if ( GetTeamNumber() != TEAM_UNASSIGNED && !IsDead() ) - { - m_fNextSuicideTime = gpGlobals->curtime; // allow the suicide to work - - ClientKill( edict() ); - - // add 1 to frags to balance out the 1 subtracted for killing yourself - IncrementFragCount( 1 ); - } - - ChangeTeam( TEAM_SPECTATOR ); - - return true; - } - else - { - StopObserverMode(); - State_Transition(STATE_ACTIVE); - } - - // Switch their actual team... - ChangeTeam( team ); - - return true; -} - -bool CHL2MP_Player::ClientCommand( const char *pcmd ) -{ - if ( FStrEq( pcmd, "spectate" ) ) - { - if ( ShouldRunRateLimitedCommand( pcmd ) ) - { - // instantly join spectators - HandleCommand_JoinTeam( TEAM_SPECTATOR ); - } - return true; - } - else if ( FStrEq( pcmd, "jointeam" ) ) - { - if ( engine->Cmd_Argc() < 2 ) - { - Warning( "Player sent bad jointeam syntax\n" ); - } - - if ( ShouldRunRateLimitedCommand( pcmd ) ) - { - int iTeam = atoi( engine->Cmd_Argv(1) ); - HandleCommand_JoinTeam( iTeam ); - } - return true; - } - else if ( FStrEq( pcmd, "joingame" ) ) - { - - return true; - } - - return BaseClass::ClientCommand( pcmd ); -} - -void CHL2MP_Player::CheatImpulseCommands( int iImpulse ) -{ - switch ( iImpulse ) - { - case 101: - { - if( sv_cheats->GetBool() ) - { - GiveAllItems(); - } - } - break; - - default: - BaseClass::CheatImpulseCommands( iImpulse ); - } -} - -bool CHL2MP_Player::ShouldRunRateLimitedCommand( const char *pcmd ) -{ - int i = m_RateLimitLastCommandTimes.Find( pcmd ); - if ( i == m_RateLimitLastCommandTimes.InvalidIndex() ) - { - m_RateLimitLastCommandTimes.Insert( pcmd, gpGlobals->curtime ); - return true; - } - else if ( (gpGlobals->curtime - m_RateLimitLastCommandTimes[i]) < HL2MP_COMMAND_MAX_RATE ) - { - // Too fast. - return false; - } - else - { - m_RateLimitLastCommandTimes[i] = gpGlobals->curtime; - return true; - } -} - -void CHL2MP_Player::CreateViewModel( int index /*=0*/ ) -{ - Assert( index >= 0 && index < MAX_VIEWMODELS ); - - if ( GetViewModel( index ) ) - return; - - CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" ); - if ( vm ) - { - vm->SetAbsOrigin( GetAbsOrigin() ); - vm->SetOwner( this ); - vm->SetIndex( index ); - DispatchSpawn( vm ); - vm->FollowEntity( this, false ); - m_hViewModel.Set( index, vm ); - } -} - -bool CHL2MP_Player::BecomeRagdollOnClient( const Vector &force ) -{ - return true; -} - -// -------------------------------------------------------------------------------- // -// Ragdoll entities. -// -------------------------------------------------------------------------------- // - -class CHL2MPRagdoll : public CBaseAnimatingOverlay -{ -public: - DECLARE_CLASS( CHL2MPRagdoll, CBaseAnimatingOverlay ); - DECLARE_SERVERCLASS(); - - // Transmit ragdolls to everyone. - virtual int UpdateTransmitState() - { - return SetTransmitState( FL_EDICT_ALWAYS ); - } - -public: - // In case the client has the player entity, we transmit the player index. - // In case the client doesn't have it, we transmit the player's model index, origin, and angles - // so they can create a ragdoll in the right place. - CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle - CNetworkVector( m_vecRagdollVelocity ); - CNetworkVector( m_vecRagdollOrigin ); -}; - -LINK_ENTITY_TO_CLASS( hl2mp_ragdoll, CHL2MPRagdoll ); - -IMPLEMENT_SERVERCLASS_ST_NOBASE( CHL2MPRagdoll, DT_HL2MPRagdoll ) - SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ), - SendPropEHandle( SENDINFO( m_hPlayer ) ), - SendPropModelIndex( SENDINFO( m_nModelIndex ) ), - SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ), - SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ), - SendPropVector( SENDINFO( m_vecRagdollVelocity ) ) -END_SEND_TABLE() - - -void CHL2MP_Player::CreateRagdollEntity( void ) -{ - if ( m_hRagdoll ) - { - UTIL_RemoveImmediate( m_hRagdoll ); - m_hRagdoll = NULL; - } - - // If we already have a ragdoll, don't make another one. - CHL2MPRagdoll *pRagdoll = dynamic_cast< CHL2MPRagdoll* >( m_hRagdoll.Get() ); - - if ( !pRagdoll ) - { - // create a new one - pRagdoll = dynamic_cast< CHL2MPRagdoll* >( CreateEntityByName( "hl2mp_ragdoll" ) ); - } - - if ( pRagdoll ) - { - pRagdoll->m_hPlayer = this; - pRagdoll->m_vecRagdollOrigin = GetAbsOrigin(); - pRagdoll->m_vecRagdollVelocity = GetAbsVelocity(); - pRagdoll->m_nModelIndex = m_nModelIndex; - pRagdoll->m_nForceBone = m_nForceBone; - pRagdoll->m_vecForce = m_vecTotalBulletForce; - pRagdoll->SetAbsOrigin( GetAbsOrigin() ); - } - - // ragdolls will be removed on round restart automatically - m_hRagdoll = pRagdoll; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CHL2MP_Player::FlashlightIsOn( void ) -{ - return IsEffectActive( EF_DIMLIGHT ); -} - -extern ConVar flashlight; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2MP_Player::FlashlightTurnOn( void ) -{ - if( flashlight.GetInt() > 0 && IsAlive() ) - { - AddEffects( EF_DIMLIGHT ); - EmitSound( "HL2Player.FlashlightOn" ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CHL2MP_Player::FlashlightTurnOff( void ) -{ - RemoveEffects( EF_DIMLIGHT ); - - if( IsAlive() ) - { - EmitSound( "HL2Player.FlashlightOff" ); - } -} - -void CHL2MP_Player::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget, const Vector *pVelocity ) -{ - //Drop a grenade if it's primed. - if ( GetActiveWeapon() ) - { - CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType("weapon_frag"); - - if ( GetActiveWeapon() == pGrenade ) - { - if ( ( m_nButtons & IN_ATTACK ) || (m_nButtons & IN_ATTACK2) ) - { - DropPrimedFragGrenade( this, pGrenade ); - return; - } - } - } - - BaseClass::Weapon_Drop( pWeapon, pvecTarget, pVelocity ); -} - - -void CHL2MP_Player::DetonateTripmines( void ) -{ - CBaseEntity *pEntity = NULL; - - while ((pEntity = gEntList.FindEntityByClassname( pEntity, "npc_satchel" )) != NULL) - { - CSatchelCharge *pSatchel = dynamic_cast(pEntity); - if (pSatchel->m_bIsLive && pSatchel->GetThrower() == this ) - { - g_EventQueue.AddEvent( pSatchel, "Explode", 0.20, this, this ); - } - } - - // Play sound for pressing the detonator - EmitSound( "Weapon_SLAM.SatchelDetonate" ); -} - -void CHL2MP_Player::Event_Killed( const CTakeDamageInfo &info ) -{ - //update damage info with our accumulated physics force - CTakeDamageInfo subinfo = info; - subinfo.SetDamageForce( m_vecTotalBulletForce ); - - SetNumAnimOverlays( 0 ); - - // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW - // because we still want to transmit to the clients in our PVS. - CreateRagdollEntity(); - - DetonateTripmines(); - - BaseClass::Event_Killed( subinfo ); - - if ( info.GetDamageType() & DMG_DISSOLVE ) - { - if ( m_hRagdoll ) - { - m_hRagdoll->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); - } - } - - CBaseEntity *pAttacker = info.GetAttacker(); - - if ( pAttacker ) - { - int iScoreToAdd = 1; - - if ( pAttacker == this ) - { - iScoreToAdd = -1; - } - - GetGlobalTeam( pAttacker->GetTeamNumber() )->AddScore( iScoreToAdd ); - } - - FlashlightTurnOff(); - - m_lifeState = LIFE_DEAD; - - RemoveEffects( EF_NODRAW ); // still draw player body - StopZooming(); -} - -int CHL2MP_Player::OnTakeDamage( const CTakeDamageInfo &inputInfo ) -{ - //return here if the player is in the respawn grace period vs. slams. - if ( gpGlobals->curtime < m_flSlamProtectTime && (inputInfo.GetDamageType() == DMG_BLAST ) ) - return 0; - - m_vecTotalBulletForce += inputInfo.GetDamageForce(); - - return BaseClass::OnTakeDamage( inputInfo ); -} - -void CHL2MP_Player::DeathSound( const CTakeDamageInfo &info ) -{ - if ( m_hRagdoll && m_hRagdoll->GetBaseAnimating()->IsDissolving() ) - return; - - char szStepSound[128]; - - Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.Die", GetPlayerModelSoundPrefix() ); - - const char *pModelName = STRING( GetModelName() ); - - CSoundParameters params; - if ( GetParametersForSound( szStepSound, params, pModelName ) == false ) - return; - - Vector vecOrigin = GetAbsOrigin(); - - CRecipientFilter filter; - filter.AddRecipientsByPAS( vecOrigin ); - - EmitSound_t ep; - ep.m_nChannel = params.channel; - ep.m_pSoundName = params.soundname; - ep.m_flVolume = params.volume; - ep.m_SoundLevel = params.soundlevel; - ep.m_nFlags = 0; - ep.m_nPitch = params.pitch; - ep.m_pOrigin = &vecOrigin; - - EmitSound( filter, entindex(), ep ); -} - -CBaseEntity* CHL2MP_Player::EntSelectSpawnPoint( void ) -{ - CBaseEntity *pSpot = NULL; - CBaseEntity *pLastSpawnPoint = g_pLastSpawn; - edict_t *player = edict(); - const char *pSpawnpointName = "info_player_deathmatch"; - - if ( HL2MPRules()->IsTeamplay() == true ) - { - if ( GetTeamNumber() == TEAM_COMBINE ) - { - pSpawnpointName = "info_player_combine"; - pLastSpawnPoint = g_pLastCombineSpawn; - } - else if ( GetTeamNumber() == TEAM_REBELS ) - { - pSpawnpointName = "info_player_rebel"; - pLastSpawnPoint = g_pLastRebelSpawn; - } - - if ( gEntList.FindEntityByClassname( NULL, pSpawnpointName ) == NULL ) - { - pSpawnpointName = "info_player_deathmatch"; - pLastSpawnPoint = g_pLastSpawn; - } - } - - pSpot = pLastSpawnPoint; - // Randomize the start spot - for ( int i = random->RandomInt(1,5); i > 0; i-- ) - pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); - if ( !pSpot ) // skip over the null point - pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); - - CBaseEntity *pFirstSpot = pSpot; - - do - { - if ( pSpot ) - { - // check if pSpot is valid - if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) ) - { - if ( pSpot->GetLocalOrigin() == vec3_origin ) - { - pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); - continue; - } - - // if so, go to pSpot - goto ReturnSpot; - } - } - // increment pSpot - pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); - } while ( pSpot != pFirstSpot ); // loop if we're not back to the start - - // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there - if ( pSpot ) - { - CBaseEntity *ent = NULL; - for ( CEntitySphereQuery sphere( pSpot->GetAbsOrigin(), 128 ); (ent = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) - { - // if ent is a client, kill em (unless they are ourselves) - if ( ent->IsPlayer() && !(ent->edict() == player) ) - ent->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 300, DMG_GENERIC ) ); - } - goto ReturnSpot; - } - - if ( !pSpot ) - { - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_start" ); - - if ( pSpot ) - goto ReturnSpot; - } - -ReturnSpot: - - if ( HL2MPRules()->IsTeamplay() == true ) - { - if ( GetTeamNumber() == TEAM_COMBINE ) - { - g_pLastCombineSpawn = pSpot; - } - else if ( GetTeamNumber() == TEAM_REBELS ) - { - g_pLastRebelSpawn = pSpot; - } - } - - g_pLastSpawn = pSpot; - - m_flSlamProtectTime = gpGlobals->curtime + 0.5; - - return pSpot; -} - - -CON_COMMAND( timeleft, "prints the time remaining in the match" ) -{ - CHL2MP_Player *pPlayer = ToHL2MPPlayer( UTIL_GetCommandClient() ); - - int iTimeRemaining = (int)HL2MPRules()->GetMapRemainingTime(); - - if ( iTimeRemaining == 0 ) - { - if ( pPlayer ) - { - ClientPrint( pPlayer, HUD_PRINTTALK, "This game has no timelimit." ); - } - else - { - Msg( "* No Time Limit *\n" ); - } - } - else - { - int iMinutes, iSeconds; - iMinutes = iTimeRemaining / 60; - iSeconds = iTimeRemaining % 60; - - char minutes[8]; - char seconds[8]; - - Q_snprintf( minutes, sizeof(minutes), "%d", iMinutes ); - Q_snprintf( seconds, sizeof(seconds), "%2.2d", iSeconds ); - - if ( pPlayer ) - { - ClientPrint( pPlayer, HUD_PRINTTALK, "Time left in map: %s1:%s2", minutes, seconds ); - } - else - { - Msg( "Time Remaining: %s:%s\n", minutes, seconds ); - } - } -} - - -void CHL2MP_Player::Reset() -{ - ResetDeathCount(); - ResetFragCount(); -} - -bool CHL2MP_Player::IsReady() -{ - return m_bReady; -} - -void CHL2MP_Player::SetReady( bool bReady ) -{ - m_bReady = bReady; -} - -void CHL2MP_Player::CheckChatText( char *p, int bufsize ) -{ - //Look for escape sequences and replace - - char *buf = new char[bufsize]; - int pos = 0; - - // Parse say text for escape sequences - for ( char *pSrc = p; pSrc != NULL && *pSrc != 0 && pos < bufsize-1; pSrc++ ) - { - // copy each char across - buf[pos] = *pSrc; - pos++; - } - - buf[pos] = '\0'; - - // copy buf back into p - Q_strncpy( p, buf, bufsize ); - - delete[] buf; - - const char *pReadyCheck = p; - - HL2MPRules()->CheckChatForReadySignal( this, pReadyCheck ); -} - -void CHL2MP_Player::State_Transition( HL2MPPlayerState newState ) -{ - State_Leave(); - State_Enter( newState ); -} - - -void CHL2MP_Player::State_Enter( HL2MPPlayerState newState ) -{ - m_iPlayerState = newState; - m_pCurStateInfo = State_LookupInfo( newState ); - - // Initialize the new state. - if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState ) - (this->*m_pCurStateInfo->pfnEnterState)(); -} - - -void CHL2MP_Player::State_Leave() -{ - if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState ) - { - (this->*m_pCurStateInfo->pfnLeaveState)(); - } -} - - -void CHL2MP_Player::State_PreThink() -{ - if ( m_pCurStateInfo && m_pCurStateInfo->pfnPreThink ) - { - (this->*m_pCurStateInfo->pfnPreThink)(); - } -} - - -CHL2MPPlayerStateInfo *CHL2MP_Player::State_LookupInfo( HL2MPPlayerState state ) -{ - // This table MUST match the - static CHL2MPPlayerStateInfo playerStateInfos[] = - { - { STATE_ACTIVE, "STATE_ACTIVE", &CHL2MP_Player::State_Enter_ACTIVE, NULL, &CHL2MP_Player::State_PreThink_ACTIVE }, - { STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CHL2MP_Player::State_Enter_OBSERVER_MODE, NULL, &CHL2MP_Player::State_PreThink_OBSERVER_MODE } - }; - - for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ ) - { - if ( playerStateInfos[i].m_iPlayerState == state ) - return &playerStateInfos[i]; - } - - return NULL; -} - -bool CHL2MP_Player::StartObserverMode(int mode) -{ - //we only want to go into observer mode if the player asked to, not on a death timeout - if ( m_bEnterObserver == true ) - { - return BaseClass::StartObserverMode( mode ); - } - return false; -} - -void CHL2MP_Player::StopObserverMode() -{ - m_bEnterObserver = false; - BaseClass::StopObserverMode(); -} - -void CHL2MP_Player::State_Enter_OBSERVER_MODE() -{ - int observerMode = m_iObserverLastMode; - if ( IsNetClient() ) - { - const char *pIdealMode = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_spec_mode" ); - if ( pIdealMode ) - { - observerMode = atoi( pIdealMode ); - if ( observerMode <= OBS_MODE_FIXED || observerMode > OBS_MODE_ROAMING ) - { - observerMode = m_iObserverLastMode; - } - } - } - m_bEnterObserver = true; - StartObserverMode( observerMode ); -} - -void CHL2MP_Player::State_PreThink_OBSERVER_MODE() -{ - // Make sure nobody has changed any of our state. - // Assert( GetMoveType() == MOVETYPE_FLY ); - Assert( m_takedamage == DAMAGE_NO ); - Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) ); - // Assert( IsEffectActive( EF_NODRAW ) ); - - // Must be dead. - Assert( m_lifeState == LIFE_DEAD ); - Assert( pl.deadflag ); -} - - -void CHL2MP_Player::State_Enter_ACTIVE() -{ - SetMoveType( MOVETYPE_WALK ); - RemoveSolidFlags( FSOLID_NOT_SOLID ); - m_Local.m_iHideHUD = 0; -} - - -void CHL2MP_Player::State_PreThink_ACTIVE() -{ - //we don't really need to do anything here. - //This state_prethink structure came over from CS:S and was doing an assert check that fails the way hl2dm handles death -} +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Player for HL2. +// +//=============================================================================// + +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#include "cbase.h" +#include "hl2mp_player.h" +#include "globalstate.h" +#include "game.h" +#include "gamerules.h" +#include "hl2mp_player_shared.h" +#include "predicted_viewmodel.h" +#include "in_buttons.h" +#include "hl2mp_gamerules.h" +#include "KeyValues.h" +#include "team.h" +#include "weapon_hl2mpbase.h" +#include "grenade_satchel.h" +#include "eventqueue.h" + +#include "engine/IEngineSound.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" + +#include "ilagcompensationmanager.h" + +int g_iLastCitizenModel = 0; +int g_iLastCombineModel = 0; + +CBaseEntity *g_pLastCombineSpawn = NULL; +CBaseEntity *g_pLastRebelSpawn = NULL; +extern CBaseEntity *g_pLastSpawn; + +#define HL2MP_COMMAND_MAX_RATE 0.3 + +void ClientKill( edict_t *pEdict ); +void DropPrimedFragGrenade( CHL2MP_Player *pPlayer, CBaseCombatWeapon *pGrenade ); + +LINK_ENTITY_TO_CLASS( player, CHL2MP_Player ); + +LINK_ENTITY_TO_CLASS( info_player_combine, CPointEntity ); +LINK_ENTITY_TO_CLASS( info_player_rebel, CPointEntity ); + +IMPLEMENT_SERVERCLASS_ST(CHL2MP_Player, DT_HL2MP_Player) + SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11, SPROP_CHANGES_OFTEN ), + SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11, SPROP_CHANGES_OFTEN ), + SendPropEHandle( SENDINFO( m_hRagdoll ) ), + SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ), + SendPropInt( SENDINFO( m_iPlayerSoundType), 3 ), + + SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), + SendPropExclude( "DT_BaseFlex", "m_viewtarget" ), + +// SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), +// SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ), + +END_SEND_TABLE() + +BEGIN_DATADESC( CHL2MP_Player ) +END_DATADESC() + +const char *g_ppszRandomCitizenModels[] = +{ + "models/humans/group03/male_01.mdl", + "models/humans/group03/male_02.mdl", + "models/humans/group03/female_01.mdl", + "models/humans/group03/male_03.mdl", + "models/humans/group03/female_02.mdl", + "models/humans/group03/male_04.mdl", + "models/humans/group03/female_03.mdl", + "models/humans/group03/male_05.mdl", + "models/humans/group03/female_04.mdl", + "models/humans/group03/male_06.mdl", + "models/humans/group03/female_06.mdl", + "models/humans/group03/male_07.mdl", + "models/humans/group03/female_07.mdl", + "models/humans/group03/male_08.mdl", + "models/humans/group03/male_09.mdl", +}; + +const char *g_ppszRandomCombineModels[] = +{ + "models/combine_soldier.mdl", + "models/combine_soldier_prisonguard.mdl", + "models/combine_super_soldier.mdl", + "models/police.mdl", +}; + + +#define MAX_COMBINE_MODELS 4 +#define MODEL_CHANGE_INTERVAL 5.0f +#define TEAM_CHANGE_INTERVAL 5.0f + +#define HL2MPPLAYER_PHYSDAMAGE_SCALE 4.0f + +#ifdef _MSC_VER +#pragma warning( disable : 4355 ) +#endif + +CHL2MP_Player::CHL2MP_Player() : m_PlayerAnimState( this ) +{ + m_angEyeAngles.Init(); + + m_iLastWeaponFireUsercmd = 0; + + m_flNextModelChangeTime = 0.0f; + m_flNextTeamChangeTime = 0.0f; + + m_iSpawnInterpCounter = 0; + + m_bEnterObserver = false; + m_bReady = false; + + BaseClass::ChangeTeam( 0 ); + +// UseClientSideAnimation(); +} + +CHL2MP_Player::~CHL2MP_Player( void ) +{ + +} + +void CHL2MP_Player::UpdateOnRemove( void ) +{ + if ( m_hRagdoll ) + { + UTIL_RemoveImmediate( m_hRagdoll ); + m_hRagdoll = NULL; + } + + BaseClass::UpdateOnRemove(); +} + +void CHL2MP_Player::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheModel ( "sprites/glow01.vmt" ); + + //Precache Citizen models + int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); + int i; + + for ( i = 0; i < nHeads; ++i ) + PrecacheModel( g_ppszRandomCitizenModels[i] ); + + //Precache Combine Models + nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); + + for ( i = 0; i < nHeads; ++i ) + PrecacheModel( g_ppszRandomCombineModels[i] ); + + PrecacheFootStepSounds(); + + PrecacheScriptSound( "NPC_MetroPolice.Die" ); + PrecacheScriptSound( "NPC_CombineS.Die" ); + PrecacheScriptSound( "NPC_Citizen.die" ); +} + +void CHL2MP_Player::GiveAllItems( void ) +{ + EquipSuit(); + + CBasePlayer::GiveAmmo( 255, "Pistol"); + CBasePlayer::GiveAmmo( 255, "AR2" ); + CBasePlayer::GiveAmmo( 5, "AR2AltFire" ); + CBasePlayer::GiveAmmo( 255, "SMG1"); + CBasePlayer::GiveAmmo( 1, "smg1_grenade"); + CBasePlayer::GiveAmmo( 255, "Buckshot"); + CBasePlayer::GiveAmmo( 32, "357" ); + CBasePlayer::GiveAmmo( 3, "rpg_round"); + + CBasePlayer::GiveAmmo( 1, "grenade" ); + CBasePlayer::GiveAmmo( 2, "slam" ); + + GiveNamedItem( "weapon_crowbar" ); + GiveNamedItem( "weapon_stunstick" ); + GiveNamedItem( "weapon_pistol" ); + GiveNamedItem( "weapon_357" ); + + GiveNamedItem( "weapon_smg1" ); + GiveNamedItem( "weapon_ar2" ); + + GiveNamedItem( "weapon_shotgun" ); + GiveNamedItem( "weapon_frag" ); + + GiveNamedItem( "weapon_crossbow" ); + + GiveNamedItem( "weapon_rpg" ); + + GiveNamedItem( "weapon_slam" ); + + GiveNamedItem( "weapon_physcannon" ); + +} + +void CHL2MP_Player::GiveDefaultItems( void ) +{ + EquipSuit(); + + CBasePlayer::GiveAmmo( 255, "Pistol"); + CBasePlayer::GiveAmmo( 45, "SMG1"); + CBasePlayer::GiveAmmo( 1, "grenade" ); + CBasePlayer::GiveAmmo( 6, "Buckshot"); + CBasePlayer::GiveAmmo( 6, "357" ); + + if ( GetPlayerModelType() == PLAYER_SOUNDS_METROPOLICE || GetPlayerModelType() == PLAYER_SOUNDS_COMBINESOLDIER ) + { + GiveNamedItem( "weapon_stunstick" ); + } + else if ( GetPlayerModelType() == PLAYER_SOUNDS_CITIZEN ) + { + GiveNamedItem( "weapon_crowbar" ); + } + + GiveNamedItem( "weapon_pistol" ); + GiveNamedItem( "weapon_smg1" ); + GiveNamedItem( "weapon_frag" ); + GiveNamedItem( "weapon_physcannon" ); + + const char *szDefaultWeaponName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_defaultweapon" ); + + CBaseCombatWeapon *pDefaultWeapon = Weapon_OwnsThisType( szDefaultWeaponName ); + + if ( pDefaultWeapon ) + { + Weapon_Switch( pDefaultWeapon ); + } + else + { + Weapon_Switch( Weapon_OwnsThisType( "weapon_physcannon" ) ); + } +} + +void CHL2MP_Player::PickDefaultSpawnTeam( void ) +{ + if ( GetTeamNumber() == 0 ) + { + if ( HL2MPRules()->IsTeamplay() == false ) + { + if ( GetModelPtr() == NULL ) + { + const char *szModelName = NULL; + szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); + + if ( ValidatePlayerModel( szModelName ) == false ) + { + char szReturnString[512]; + + Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel models/combine_soldier.mdl\n" ); + engine->ClientCommand ( edict(), szReturnString ); + } + + ChangeTeam( TEAM_UNASSIGNED ); + } + } + else + { + CTeam *pCombine = g_Teams[TEAM_COMBINE]; + CTeam *pRebels = g_Teams[TEAM_REBELS]; + + if ( pCombine == NULL || pRebels == NULL ) + { + ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) ); + } + else + { + if ( pCombine->GetNumPlayers() > pRebels->GetNumPlayers() ) + { + ChangeTeam( TEAM_REBELS ); + } + else if ( pCombine->GetNumPlayers() < pRebels->GetNumPlayers() ) + { + ChangeTeam( TEAM_COMBINE ); + } + else + { + ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets HL2 specific defaults. +//----------------------------------------------------------------------------- +void CHL2MP_Player::Spawn(void) +{ + m_flNextModelChangeTime = 0.0f; + m_flNextTeamChangeTime = 0.0f; + + PickDefaultSpawnTeam(); + + BaseClass::Spawn(); + + if ( !IsObserver() ) + { + pl.deadflag = false; + RemoveSolidFlags( FSOLID_NOT_SOLID ); + + RemoveEffects( EF_NODRAW ); + + GiveDefaultItems(); + } + + RemoveEffects( EF_NOINTERP ); + + SetNumAnimOverlays( 3 ); + ResetAnimation(); + + m_nRenderFX = kRenderNormal; + + m_Local.m_iHideHUD = 0; + + AddFlag(FL_ONGROUND); // set the player on the ground at the start of the round. + + m_impactEnergyScale = HL2MPPLAYER_PHYSDAMAGE_SCALE; + + if ( HL2MPRules()->IsIntermission() ) + { + AddFlag( FL_FROZEN ); + } + else + { + RemoveFlag( FL_FROZEN ); + } + + m_iSpawnInterpCounter = (m_iSpawnInterpCounter + 1) % 8; + + m_Local.m_bDucked = false; + + SetPlayerUnderwater(false); + + m_bReady = false; +} + +void CHL2MP_Player::PickupObject( CBaseEntity *pObject, bool bLimitMassAndSize ) +{ + +} + +bool CHL2MP_Player::ValidatePlayerModel( const char *pModel ) +{ + int iModels = ARRAYSIZE( g_ppszRandomCitizenModels ); + int i; + + for ( i = 0; i < iModels; ++i ) + { + if ( !Q_stricmp( g_ppszRandomCitizenModels[i], pModel ) ) + { + return true; + } + } + + iModels = ARRAYSIZE( g_ppszRandomCombineModels ); + + for ( i = 0; i < iModels; ++i ) + { + if ( !Q_stricmp( g_ppszRandomCombineModels[i], pModel ) ) + { + return true; + } + } + + return false; +} + +void CHL2MP_Player::SetPlayerTeamModel( void ) +{ + const char *szModelName = NULL; + szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); + + int modelIndex = modelinfo->GetModelIndex( szModelName ); + + if ( modelIndex == -1 || ValidatePlayerModel( szModelName ) == false ) + { + szModelName = "models/Combine_Soldier.mdl"; + m_iModelType = TEAM_COMBINE; + + char szReturnString[512]; + + Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName ); + engine->ClientCommand ( edict(), szReturnString ); + } + + if ( GetTeamNumber() == TEAM_COMBINE ) + { + if ( Q_stristr( szModelName, "models/human") ) + { + int nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); + + g_iLastCombineModel = ( g_iLastCombineModel + 1 ) % nHeads; + szModelName = g_ppszRandomCombineModels[g_iLastCombineModel]; + } + + m_iModelType = TEAM_COMBINE; + } + else if ( GetTeamNumber() == TEAM_REBELS ) + { + if ( !Q_stristr( szModelName, "models/human") ) + { + int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); + + g_iLastCitizenModel = ( g_iLastCitizenModel + 1 ) % nHeads; + szModelName = g_ppszRandomCitizenModels[g_iLastCitizenModel]; + } + + m_iModelType = TEAM_REBELS; + } + + SetModel( szModelName ); + SetupPlayerSoundsByModel( szModelName ); + + m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; +} + +void CHL2MP_Player::SetPlayerModel( void ) +{ + const char *szModelName = NULL; + const char *pszCurrentModelName = modelinfo->GetModelName( GetModel()); + + szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); + + if ( ValidatePlayerModel( szModelName ) == false ) + { + char szReturnString[512]; + + if ( ValidatePlayerModel( pszCurrentModelName ) == false ) + { + pszCurrentModelName = "models/Combine_Soldier.mdl"; + } + + Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pszCurrentModelName ); + engine->ClientCommand ( edict(), szReturnString ); + + szModelName = pszCurrentModelName; + } + + if ( GetTeamNumber() == TEAM_COMBINE ) + { + int nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); + + g_iLastCombineModel = ( g_iLastCombineModel + 1 ) % nHeads; + szModelName = g_ppszRandomCombineModels[g_iLastCombineModel]; + + m_iModelType = TEAM_COMBINE; + } + else if ( GetTeamNumber() == TEAM_REBELS ) + { + int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); + + g_iLastCitizenModel = ( g_iLastCitizenModel + 1 ) % nHeads; + szModelName = g_ppszRandomCitizenModels[g_iLastCitizenModel]; + + m_iModelType = TEAM_REBELS; + } + else + { + if ( Q_strlen( szModelName ) == 0 ) + { + szModelName = g_ppszRandomCitizenModels[0]; + } + + if ( Q_stristr( szModelName, "models/human") ) + { + m_iModelType = TEAM_REBELS; + } + else + { + m_iModelType = TEAM_COMBINE; + } + } + + int modelIndex = modelinfo->GetModelIndex( szModelName ); + + if ( modelIndex == -1 ) + { + szModelName = "models/Combine_Soldier.mdl"; + m_iModelType = TEAM_COMBINE; + + char szReturnString[512]; + + Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName ); + engine->ClientCommand ( edict(), szReturnString ); + } + + SetModel( szModelName ); + SetupPlayerSoundsByModel( szModelName ); + + m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; +} + +void CHL2MP_Player::SetupPlayerSoundsByModel( const char *pModelName ) +{ + if ( Q_stristr( pModelName, "models/human") ) + { + m_iPlayerSoundType = (int)PLAYER_SOUNDS_CITIZEN; + } + else if ( Q_stristr(pModelName, "police" ) ) + { + m_iPlayerSoundType = (int)PLAYER_SOUNDS_METROPOLICE; + } + else if ( Q_stristr(pModelName, "combine" ) ) + { + m_iPlayerSoundType = (int)PLAYER_SOUNDS_COMBINESOLDIER; + } +} + +void CHL2MP_Player::ResetAnimation( void ) +{ + if ( IsAlive() ) + { + SetSequence ( -1 ); + SetActivity( ACT_INVALID ); + + if (!GetAbsVelocity().x && !GetAbsVelocity().y) + SetAnimation( PLAYER_IDLE ); + else if ((GetAbsVelocity().x || GetAbsVelocity().y) && ( GetFlags() & FL_ONGROUND )) + SetAnimation( PLAYER_WALK ); + else if (GetWaterLevel() > 1) + SetAnimation( PLAYER_WALK ); + } +} + + +bool CHL2MP_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex ) +{ + bool bRet = BaseClass::Weapon_Switch( pWeapon, viewmodelindex ); + + if ( bRet == true ) + { + ResetAnimation(); + } + + return bRet; +} + +void CHL2MP_Player::PreThink( void ) +{ + QAngle vOldAngles = GetLocalAngles(); + QAngle vTempAngles = GetLocalAngles(); + + vTempAngles = EyeAngles(); + + if ( vTempAngles[PITCH] > 180.0f ) + { + vTempAngles[PITCH] -= 360.0f; + } + + SetLocalAngles( vTempAngles ); + + BaseClass::PreThink(); + State_PreThink(); + + //Reset bullet force accumulator, only lasts one frame + m_vecTotalBulletForce = vec3_origin; + SetLocalAngles( vOldAngles ); +} + +void CHL2MP_Player::PostThink( void ) +{ + BaseClass::PostThink(); + + if ( GetFlags() & FL_DUCKING ) + { + SetCollisionBounds( VEC_CROUCH_TRACE_MIN, VEC_CROUCH_TRACE_MAX ); + } + + m_PlayerAnimState.Update(); + + // Store the eye angles pitch so the client can compute its animation state correctly. + m_angEyeAngles = EyeAngles(); + + QAngle angles = GetLocalAngles(); + angles[PITCH] = 0; + SetLocalAngles( angles ); +} + +void CHL2MP_Player::PlayerDeathThink() +{ + if( !IsObserver() ) + { + BaseClass::PlayerDeathThink(); + } +} + +void CHL2MP_Player::FireBullets ( const FireBulletsInfo_t &info ) +{ + // Move other players back to history positions based on local player's lag + lagcompensation->StartLagCompensation( this, this->GetCurrentCommand() ); + + FireBulletsInfo_t modinfo = info; + + CWeaponHL2MPBase *pWeapon = dynamic_cast( GetActiveWeapon() ); + + if ( pWeapon ) + { + modinfo.m_iPlayerDamage = modinfo.m_iDamage = pWeapon->GetHL2MPWpnData().m_iPlayerDamage; + } + + NoteWeaponFired(); + + BaseClass::FireBullets( modinfo ); + + // Move other players back to history positions based on local player's lag + lagcompensation->FinishLagCompensation( this ); +} + +void CHL2MP_Player::NoteWeaponFired( void ) +{ + Assert( m_pCurrentCommand ); + if( m_pCurrentCommand ) + { + m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number; + } +} + +extern ConVar sv_maxunlag; + +bool CHL2MP_Player::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec *pEntityTransmitBits ) const +{ + // No need to lag compensate at all if we're not attacking in this command and + // we haven't attacked recently. + if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) ) + return false; + + // If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it. + if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) ) + return false; + + const Vector &vMyOrigin = GetAbsOrigin(); + const Vector &vHisOrigin = pPlayer->GetAbsOrigin(); + + // get max distance player could have moved within max lag compensation time, + // multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value) + float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat(); + + // If the player is within this distance, lag compensate them in case they're running past us. + if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance ) + return true; + + // If their origin is not within a 45 degree cone in front of us, no need to lag compensate. + Vector vForward; + AngleVectors( pCmd->viewangles, &vForward ); + + Vector vDiff = vHisOrigin - vMyOrigin; + VectorNormalize( vDiff ); + + float flCosAngle = 0.707107f; // 45 degree angle + if ( vForward.Dot( vDiff ) < flCosAngle ) + return false; + + return true; +} + +Activity CHL2MP_Player::TranslateTeamActivity( Activity ActToTranslate ) +{ + if ( m_iModelType == TEAM_COMBINE ) + return ActToTranslate; + + if ( ActToTranslate == ACT_RUN ) + return ACT_RUN_AIM_AGITATED; + + if ( ActToTranslate == ACT_IDLE ) + return ACT_IDLE_AIM_AGITATED; + + if ( ActToTranslate == ACT_WALK ) + return ACT_WALK_AIM_AGITATED; + + return ActToTranslate; +} + +extern ConVar hl2_normspeed; + +// Set the activity based on an event or current state +void CHL2MP_Player::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + + float speed; + + speed = GetAbsVelocity().Length2D(); + + + // bool bRunning = true; + + //Revisit! +/* if ( ( m_nButtons & ( IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) ) ) + { + if ( speed > 1.0f && speed < hl2_normspeed.GetFloat() - 20.0f ) + { + bRunning = false; + } + }*/ + + if ( GetFlags() & ( FL_FROZEN | FL_ATCONTROLS ) ) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + Activity idealActivity = ACT_HL2MP_RUN; + + // This could stand to be redone. Why is playerAnim abstracted from activity? (sjb) + if ( playerAnim == PLAYER_JUMP ) + { + idealActivity = ACT_HL2MP_JUMP; + } + else if ( playerAnim == PLAYER_DIE ) + { + if ( m_lifeState == LIFE_ALIVE ) + { + return; + } + } + else if ( playerAnim == PLAYER_ATTACK1 ) + { + if ( GetActivity( ) == ACT_HOVER || + GetActivity( ) == ACT_SWIM || + GetActivity( ) == ACT_HOP || + GetActivity( ) == ACT_LEAP || + GetActivity( ) == ACT_DIESIMPLE ) + { + idealActivity = GetActivity( ); + } + else + { + idealActivity = ACT_HL2MP_GESTURE_RANGE_ATTACK; + } + } + else if ( playerAnim == PLAYER_RELOAD ) + { + idealActivity = ACT_HL2MP_GESTURE_RELOAD; + } + else if ( playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK ) + { + if ( !( GetFlags() & FL_ONGROUND ) && GetActivity( ) == ACT_HL2MP_JUMP ) // Still jumping + { + idealActivity = GetActivity( ); + } + /* + else if ( GetWaterLevel() > 1 ) + { + if ( speed == 0 ) + idealActivity = ACT_HOVER; + else + idealActivity = ACT_SWIM; + } + */ + else + { + if ( GetFlags() & FL_DUCKING ) + { + if ( speed > 0 ) + { + idealActivity = ACT_HL2MP_WALK_CROUCH; + } + else + { + idealActivity = ACT_HL2MP_IDLE_CROUCH; + } + } + else + { + if ( speed > 0 ) + { + /* + if ( bRunning == false ) + { + idealActivity = ACT_WALK; + } + else + */ + { + idealActivity = ACT_HL2MP_RUN; + } + } + else + { + idealActivity = ACT_HL2MP_IDLE; + } + } + } + + idealActivity = TranslateTeamActivity( idealActivity ); + } + + if ( idealActivity == ACT_HL2MP_GESTURE_RANGE_ATTACK ) + { + RestartGesture( Weapon_TranslateActivity( idealActivity ) ); + + // FIXME: this seems a bit wacked + Weapon_SetActivity( Weapon_TranslateActivity( ACT_RANGE_ATTACK1 ), 0 ); + + return; + } + else if ( idealActivity == ACT_HL2MP_GESTURE_RELOAD ) + { + RestartGesture( Weapon_TranslateActivity( idealActivity ) ); + return; + } + else + { + SetActivity( idealActivity ); + + animDesired = SelectWeightedSequence( Weapon_TranslateActivity ( idealActivity ) ); + + if (animDesired == -1) + { + animDesired = SelectWeightedSequence( idealActivity ); + + if ( animDesired == -1 ) + { + animDesired = 0; + } + } + + // Already using the desired animation? + if ( GetSequence() == animDesired ) + return; + + m_flPlaybackRate = 1.0; + ResetSequence( animDesired ); + SetCycle( 0 ); + return; + } + + // Already using the desired animation? + if ( GetSequence() == animDesired ) + return; + + //Msg( "Set animation to %d\n", animDesired ); + // Reset to first frame of desired animation + ResetSequence( animDesired ); + SetCycle( 0 ); +} + + +extern int gEvilImpulse101; +//----------------------------------------------------------------------------- +// Purpose: Player reacts to bumping a weapon. +// Input : pWeapon - the weapon that the player bumped into. +// Output : Returns true if player picked up the weapon +//----------------------------------------------------------------------------- +bool CHL2MP_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) +{ + CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); + + // Can I have this weapon type? + if ( IsEFlagSet( EFL_NO_WEAPON_PICKUP ) ) + return false; + + if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) + { + if ( gEvilImpulse101 ) + { + UTIL_Remove( pWeapon ); + } + return false; + } + + // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows) + if( !pWeapon->FVisible( this, MASK_SOLID ) && !(GetFlags() & FL_NOTARGET) ) + { + return false; + } + + bool bOwnsWeaponAlready = !!Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()); + + if ( bOwnsWeaponAlready == true ) + { + //If we have room for the ammo, then "take" the weapon too. + if ( Weapon_EquipAmmoOnly( pWeapon ) ) + { + pWeapon->CheckRespawn(); + + UTIL_Remove( pWeapon ); + return true; + } + else + { + return false; + } + } + + pWeapon->CheckRespawn(); + Weapon_Equip( pWeapon ); + + return true; +} + +void CHL2MP_Player::ChangeTeam( int iTeam ) +{ +/* if ( GetNextTeamChangeTime() >= gpGlobals->curtime ) + { + char szReturnString[128]; + Q_snprintf( szReturnString, sizeof( szReturnString ), "Please wait %d more seconds before trying to switch teams again.\n", (int)(GetNextTeamChangeTime() - gpGlobals->curtime) ); + + ClientPrint( this, HUD_PRINTTALK, szReturnString ); + return; + }*/ + + bool bKill = false; + + if ( HL2MPRules()->IsTeamplay() != true && iTeam != TEAM_SPECTATOR ) + { + //don't let them try to join combine or rebels during deathmatch. + iTeam = TEAM_UNASSIGNED; + } + + if ( HL2MPRules()->IsTeamplay() == true ) + { + if ( iTeam != GetTeamNumber() && GetTeamNumber() != TEAM_UNASSIGNED ) + { + bKill = true; + } + } + + BaseClass::ChangeTeam( iTeam ); + + m_flNextTeamChangeTime = gpGlobals->curtime + TEAM_CHANGE_INTERVAL; + + if ( HL2MPRules()->IsTeamplay() == true ) + { + SetPlayerTeamModel(); + } + else + { + SetPlayerModel(); + } + + if ( iTeam == TEAM_SPECTATOR ) + { + RemoveAllItems( true ); + + State_Transition( STATE_OBSERVER_MODE ); + } + + if ( bKill == true ) + { + ClientKill( edict() ); + } +} + +bool CHL2MP_Player::HandleCommand_JoinTeam( int team ) +{ + if ( !GetGlobalTeam( team ) ) + { + Warning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team ); + return false; + } + + if ( team == TEAM_SPECTATOR ) + { + // Prevent this is the cvar is set + if ( !mp_allowspectators.GetInt() ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" ); + return false; + } + + if ( GetTeamNumber() != TEAM_UNASSIGNED && !IsDead() ) + { + m_fNextSuicideTime = gpGlobals->curtime; // allow the suicide to work + + ClientKill( edict() ); + + // add 1 to frags to balance out the 1 subtracted for killing yourself + IncrementFragCount( 1 ); + } + + ChangeTeam( TEAM_SPECTATOR ); + + return true; + } + else + { + StopObserverMode(); + State_Transition(STATE_ACTIVE); + } + + // Switch their actual team... + ChangeTeam( team ); + + return true; +} + +bool CHL2MP_Player::ClientCommand( const char *pcmd ) +{ + if ( FStrEq( pcmd, "spectate" ) ) + { + if ( ShouldRunRateLimitedCommand( pcmd ) ) + { + // instantly join spectators + HandleCommand_JoinTeam( TEAM_SPECTATOR ); + } + return true; + } + else if ( FStrEq( pcmd, "jointeam" ) ) + { + if ( engine->Cmd_Argc() < 2 ) + { + Warning( "Player sent bad jointeam syntax\n" ); + } + + if ( ShouldRunRateLimitedCommand( pcmd ) ) + { + int iTeam = atoi( engine->Cmd_Argv(1) ); + HandleCommand_JoinTeam( iTeam ); + } + return true; + } + else if ( FStrEq( pcmd, "joingame" ) ) + { + + return true; + } + + return BaseClass::ClientCommand( pcmd ); +} + +void CHL2MP_Player::CheatImpulseCommands( int iImpulse ) +{ + switch ( iImpulse ) + { + case 101: + { + if( sv_cheats->GetBool() ) + { + GiveAllItems(); + } + } + break; + + default: + BaseClass::CheatImpulseCommands( iImpulse ); + } +} + +bool CHL2MP_Player::ShouldRunRateLimitedCommand( const char *pcmd ) +{ + int i = m_RateLimitLastCommandTimes.Find( pcmd ); + if ( i == m_RateLimitLastCommandTimes.InvalidIndex() ) + { + m_RateLimitLastCommandTimes.Insert( pcmd, gpGlobals->curtime ); + return true; + } + else if ( (gpGlobals->curtime - m_RateLimitLastCommandTimes[i]) < HL2MP_COMMAND_MAX_RATE ) + { + // Too fast. + return false; + } + else + { + m_RateLimitLastCommandTimes[i] = gpGlobals->curtime; + return true; + } +} + +void CHL2MP_Player::CreateViewModel( int index /*=0*/ ) +{ + Assert( index >= 0 && index < MAX_VIEWMODELS ); + + if ( GetViewModel( index ) ) + return; + + CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" ); + if ( vm ) + { + vm->SetAbsOrigin( GetAbsOrigin() ); + vm->SetOwner( this ); + vm->SetIndex( index ); + DispatchSpawn( vm ); + vm->FollowEntity( this, false ); + m_hViewModel.Set( index, vm ); + } +} + +bool CHL2MP_Player::BecomeRagdollOnClient( const Vector &force ) +{ + return true; +} + +// -------------------------------------------------------------------------------- // +// Ragdoll entities. +// -------------------------------------------------------------------------------- // + +class CHL2MPRagdoll : public CBaseAnimatingOverlay +{ +public: + DECLARE_CLASS( CHL2MPRagdoll, CBaseAnimatingOverlay ); + DECLARE_SERVERCLASS(); + + // Transmit ragdolls to everyone. + virtual int UpdateTransmitState() + { + return SetTransmitState( FL_EDICT_ALWAYS ); + } + +public: + // In case the client has the player entity, we transmit the player index. + // In case the client doesn't have it, we transmit the player's model index, origin, and angles + // so they can create a ragdoll in the right place. + CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle + CNetworkVector( m_vecRagdollVelocity ); + CNetworkVector( m_vecRagdollOrigin ); +}; + +LINK_ENTITY_TO_CLASS( hl2mp_ragdoll, CHL2MPRagdoll ); + +IMPLEMENT_SERVERCLASS_ST_NOBASE( CHL2MPRagdoll, DT_HL2MPRagdoll ) + SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ), + SendPropEHandle( SENDINFO( m_hPlayer ) ), + SendPropModelIndex( SENDINFO( m_nModelIndex ) ), + SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ), + SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ), + SendPropVector( SENDINFO( m_vecRagdollVelocity ) ) +END_SEND_TABLE() + + +void CHL2MP_Player::CreateRagdollEntity( void ) +{ + if ( m_hRagdoll ) + { + UTIL_RemoveImmediate( m_hRagdoll ); + m_hRagdoll = NULL; + } + + // If we already have a ragdoll, don't make another one. + CHL2MPRagdoll *pRagdoll = dynamic_cast< CHL2MPRagdoll* >( m_hRagdoll.Get() ); + + if ( !pRagdoll ) + { + // create a new one + pRagdoll = dynamic_cast< CHL2MPRagdoll* >( CreateEntityByName( "hl2mp_ragdoll" ) ); + } + + if ( pRagdoll ) + { + pRagdoll->m_hPlayer = this; + pRagdoll->m_vecRagdollOrigin = GetAbsOrigin(); + pRagdoll->m_vecRagdollVelocity = GetAbsVelocity(); + pRagdoll->m_nModelIndex = m_nModelIndex; + pRagdoll->m_nForceBone = m_nForceBone; + pRagdoll->m_vecForce = m_vecTotalBulletForce; + pRagdoll->SetAbsOrigin( GetAbsOrigin() ); + } + + // ragdolls will be removed on round restart automatically + m_hRagdoll = pRagdoll; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CHL2MP_Player::FlashlightIsOn( void ) +{ + return IsEffectActive( EF_DIMLIGHT ); +} + +extern ConVar flashlight; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2MP_Player::FlashlightTurnOn( void ) +{ + if( flashlight.GetInt() > 0 && IsAlive() ) + { + AddEffects( EF_DIMLIGHT ); + EmitSound( "HL2Player.FlashlightOn" ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2MP_Player::FlashlightTurnOff( void ) +{ + RemoveEffects( EF_DIMLIGHT ); + + if( IsAlive() ) + { + EmitSound( "HL2Player.FlashlightOff" ); + } +} + +void CHL2MP_Player::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget, const Vector *pVelocity ) +{ + //Drop a grenade if it's primed. + if ( GetActiveWeapon() ) + { + CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType("weapon_frag"); + + if ( GetActiveWeapon() == pGrenade ) + { + if ( ( m_nButtons & IN_ATTACK ) || (m_nButtons & IN_ATTACK2) ) + { + DropPrimedFragGrenade( this, pGrenade ); + return; + } + } + } + + BaseClass::Weapon_Drop( pWeapon, pvecTarget, pVelocity ); +} + + +void CHL2MP_Player::DetonateTripmines( void ) +{ + CBaseEntity *pEntity = NULL; + + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "npc_satchel" )) != NULL) + { + CSatchelCharge *pSatchel = dynamic_cast(pEntity); + if (pSatchel->m_bIsLive && pSatchel->GetThrower() == this ) + { + g_EventQueue.AddEvent( pSatchel, "Explode", 0.20, this, this ); + } + } + + // Play sound for pressing the detonator + EmitSound( "Weapon_SLAM.SatchelDetonate" ); +} + +void CHL2MP_Player::Event_Killed( const CTakeDamageInfo &info ) +{ + //update damage info with our accumulated physics force + CTakeDamageInfo subinfo = info; + subinfo.SetDamageForce( m_vecTotalBulletForce ); + + SetNumAnimOverlays( 0 ); + + // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW + // because we still want to transmit to the clients in our PVS. + CreateRagdollEntity(); + + DetonateTripmines(); + + BaseClass::Event_Killed( subinfo ); + + if ( info.GetDamageType() & DMG_DISSOLVE ) + { + if ( m_hRagdoll ) + { + m_hRagdoll->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); + } + } + + CBaseEntity *pAttacker = info.GetAttacker(); + + if ( pAttacker ) + { + int iScoreToAdd = 1; + + if ( pAttacker == this ) + { + iScoreToAdd = -1; + } + + GetGlobalTeam( pAttacker->GetTeamNumber() )->AddScore( iScoreToAdd ); + } + + FlashlightTurnOff(); + + m_lifeState = LIFE_DEAD; + + RemoveEffects( EF_NODRAW ); // still draw player body + StopZooming(); +} + +int CHL2MP_Player::OnTakeDamage( const CTakeDamageInfo &inputInfo ) +{ + //return here if the player is in the respawn grace period vs. slams. + if ( gpGlobals->curtime < m_flSlamProtectTime && (inputInfo.GetDamageType() == DMG_BLAST ) ) + return 0; + + m_vecTotalBulletForce += inputInfo.GetDamageForce(); + + return BaseClass::OnTakeDamage( inputInfo ); +} + +void CHL2MP_Player::DeathSound( const CTakeDamageInfo &info ) +{ + if ( m_hRagdoll && m_hRagdoll->GetBaseAnimating()->IsDissolving() ) + return; + + char szStepSound[128]; + + Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.Die", GetPlayerModelSoundPrefix() ); + + const char *pModelName = STRING( GetModelName() ); + + CSoundParameters params; + if ( GetParametersForSound( szStepSound, params, pModelName ) == false ) + return; + + Vector vecOrigin = GetAbsOrigin(); + + CRecipientFilter filter; + filter.AddRecipientsByPAS( vecOrigin ); + + EmitSound_t ep; + ep.m_nChannel = params.channel; + ep.m_pSoundName = params.soundname; + ep.m_flVolume = params.volume; + ep.m_SoundLevel = params.soundlevel; + ep.m_nFlags = 0; + ep.m_nPitch = params.pitch; + ep.m_pOrigin = &vecOrigin; + + EmitSound( filter, entindex(), ep ); +} + +CBaseEntity* CHL2MP_Player::EntSelectSpawnPoint( void ) +{ + CBaseEntity *pSpot = NULL; + CBaseEntity *pLastSpawnPoint = g_pLastSpawn; + edict_t *player = edict(); + const char *pSpawnpointName = "info_player_deathmatch"; + + if ( HL2MPRules()->IsTeamplay() == true ) + { + if ( GetTeamNumber() == TEAM_COMBINE ) + { + pSpawnpointName = "info_player_combine"; + pLastSpawnPoint = g_pLastCombineSpawn; + } + else if ( GetTeamNumber() == TEAM_REBELS ) + { + pSpawnpointName = "info_player_rebel"; + pLastSpawnPoint = g_pLastRebelSpawn; + } + + if ( gEntList.FindEntityByClassname( NULL, pSpawnpointName ) == NULL ) + { + pSpawnpointName = "info_player_deathmatch"; + pLastSpawnPoint = g_pLastSpawn; + } + } + + pSpot = pLastSpawnPoint; + // Randomize the start spot + for ( int i = random->RandomInt(1,5); i > 0; i-- ) + pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); + if ( !pSpot ) // skip over the null point + pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) ) + { + if ( pSpot->GetLocalOrigin() == vec3_origin ) + { + pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( pSpot ) + { + CBaseEntity *ent = NULL; + for ( CEntitySphereQuery sphere( pSpot->GetAbsOrigin(), 128 ); (ent = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) ) + ent->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 300, DMG_GENERIC ) ); + } + goto ReturnSpot; + } + + if ( !pSpot ) + { + pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_start" ); + + if ( pSpot ) + goto ReturnSpot; + } + +ReturnSpot: + + if ( HL2MPRules()->IsTeamplay() == true ) + { + if ( GetTeamNumber() == TEAM_COMBINE ) + { + g_pLastCombineSpawn = pSpot; + } + else if ( GetTeamNumber() == TEAM_REBELS ) + { + g_pLastRebelSpawn = pSpot; + } + } + + g_pLastSpawn = pSpot; + + m_flSlamProtectTime = gpGlobals->curtime + 0.5; + + return pSpot; +} + + +CON_COMMAND( timeleft, "prints the time remaining in the match" ) +{ + CHL2MP_Player *pPlayer = ToHL2MPPlayer( UTIL_GetCommandClient() ); + + int iTimeRemaining = (int)HL2MPRules()->GetMapRemainingTime(); + + if ( iTimeRemaining == 0 ) + { + if ( pPlayer ) + { + ClientPrint( pPlayer, HUD_PRINTTALK, "This game has no timelimit." ); + } + else + { + Msg( "* No Time Limit *\n" ); + } + } + else + { + int iMinutes, iSeconds; + iMinutes = iTimeRemaining / 60; + iSeconds = iTimeRemaining % 60; + + char minutes[8]; + char seconds[8]; + + Q_snprintf( minutes, sizeof(minutes), "%d", iMinutes ); + Q_snprintf( seconds, sizeof(seconds), "%2.2d", iSeconds ); + + if ( pPlayer ) + { + ClientPrint( pPlayer, HUD_PRINTTALK, "Time left in map: %s1:%s2", minutes, seconds ); + } + else + { + Msg( "Time Remaining: %s:%s\n", minutes, seconds ); + } + } +} + + +void CHL2MP_Player::Reset() +{ + ResetDeathCount(); + ResetFragCount(); +} + +bool CHL2MP_Player::IsReady() +{ + return m_bReady; +} + +void CHL2MP_Player::SetReady( bool bReady ) +{ + m_bReady = bReady; +} + +void CHL2MP_Player::CheckChatText( char *p, int bufsize ) +{ + //Look for escape sequences and replace + + char *buf = new char[bufsize]; + int pos = 0; + + // Parse say text for escape sequences + for ( char *pSrc = p; pSrc != NULL && *pSrc != 0 && pos < bufsize-1; pSrc++ ) + { + // copy each char across + buf[pos] = *pSrc; + pos++; + } + + buf[pos] = '\0'; + + // copy buf back into p + Q_strncpy( p, buf, bufsize ); + + delete[] buf; + + const char *pReadyCheck = p; + + HL2MPRules()->CheckChatForReadySignal( this, pReadyCheck ); +} + +void CHL2MP_Player::State_Transition( HL2MPPlayerState newState ) +{ + State_Leave(); + State_Enter( newState ); +} + + +void CHL2MP_Player::State_Enter( HL2MPPlayerState newState ) +{ + m_iPlayerState = newState; + m_pCurStateInfo = State_LookupInfo( newState ); + + // Initialize the new state. + if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState ) + (this->*m_pCurStateInfo->pfnEnterState)(); +} + + +void CHL2MP_Player::State_Leave() +{ + if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState ) + { + (this->*m_pCurStateInfo->pfnLeaveState)(); + } +} + + +void CHL2MP_Player::State_PreThink() +{ + if ( m_pCurStateInfo && m_pCurStateInfo->pfnPreThink ) + { + (this->*m_pCurStateInfo->pfnPreThink)(); + } +} + + +CHL2MPPlayerStateInfo *CHL2MP_Player::State_LookupInfo( HL2MPPlayerState state ) +{ + // This table MUST match the + static CHL2MPPlayerStateInfo playerStateInfos[] = + { + { STATE_ACTIVE, "STATE_ACTIVE", &CHL2MP_Player::State_Enter_ACTIVE, NULL, &CHL2MP_Player::State_PreThink_ACTIVE }, + { STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CHL2MP_Player::State_Enter_OBSERVER_MODE, NULL, &CHL2MP_Player::State_PreThink_OBSERVER_MODE } + }; + + for ( size_t i=0; i < ARRAYSIZE( playerStateInfos ); i++ ) + { + if ( playerStateInfos[i].m_iPlayerState == state ) + return &playerStateInfos[i]; + } + + return NULL; +} + +bool CHL2MP_Player::StartObserverMode(int mode) +{ + //we only want to go into observer mode if the player asked to, not on a death timeout + if ( m_bEnterObserver == true ) + { + return BaseClass::StartObserverMode( mode ); + } + return false; +} + +void CHL2MP_Player::StopObserverMode() +{ + m_bEnterObserver = false; + BaseClass::StopObserverMode(); +} + +void CHL2MP_Player::State_Enter_OBSERVER_MODE() +{ + int observerMode = m_iObserverLastMode; + if ( IsNetClient() ) + { + const char *pIdealMode = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_spec_mode" ); + if ( pIdealMode ) + { + observerMode = atoi( pIdealMode ); + if ( observerMode <= OBS_MODE_FIXED || observerMode > OBS_MODE_ROAMING ) + { + observerMode = m_iObserverLastMode; + } + } + } + m_bEnterObserver = true; + StartObserverMode( observerMode ); +} + +void CHL2MP_Player::State_PreThink_OBSERVER_MODE() +{ + // Make sure nobody has changed any of our state. + // Assert( GetMoveType() == MOVETYPE_FLY ); + Assert( m_takedamage == DAMAGE_NO ); + Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) ); + // Assert( IsEffectActive( EF_NODRAW ) ); + + // Must be dead. + Assert( m_lifeState == LIFE_DEAD ); + Assert( pl.deadflag ); +} + + +void CHL2MP_Player::State_Enter_ACTIVE() +{ + SetMoveType( MOVETYPE_WALK ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_Local.m_iHideHUD = 0; +} + + +void CHL2MP_Player::State_PreThink_ACTIVE() +{ + //we don't really need to do anything here. + //This state_prethink structure came over from CS:S and was doing an assert check that fails the way hl2dm handles death +} diff --git a/dlls/hltvdirector.cpp b/dlls/hltvdirector.cpp index 5e7847a9..14740bca 100644 --- a/dlls/hltvdirector.cpp +++ b/dlls/hltvdirector.cpp @@ -390,7 +390,7 @@ void CHLTVDirector::StartBestPlayerCameraShot() { shot->SetInt("target1", iBestCamera ); shot->SetInt("target2", iBestTarget ); - shot->SetInt("distance", 112.0f ); + shot->SetInt("distance", 112 ); shot->SetInt("phi", 20 ); // view over shoulder, randomly left or right @@ -431,11 +431,11 @@ void CHLTVDirector::StartFixedCameraShot(int iCamera, int iTarget) if ( shot ) { - shot->SetInt("posx", vCamPos.x ); - shot->SetInt("posy", vCamPos.y ); - shot->SetInt("posz", vCamPos.z ); - shot->SetInt("theta", aViewAngle.x ); - shot->SetInt("phi", aViewAngle.y ); + shot->SetInt("posx", static_cast(vCamPos.x) ); + shot->SetInt("posy", static_cast(vCamPos.y) ); + shot->SetInt("posz", static_cast(vCamPos.z) ); + shot->SetInt("theta", static_cast(aViewAngle.x) ); + shot->SetInt("phi", static_cast(aViewAngle.y) ); shot->SetInt("target", iTarget ); shot->SetFloat("fov", RandomFloat(50,110) ); diff --git a/dlls/item_world.cpp b/dlls/item_world.cpp index 8c603634..2b214846 100644 --- a/dlls/item_world.cpp +++ b/dlls/item_world.cpp @@ -218,6 +218,8 @@ void CItem::OnEntityEvent( EntityEvent_t event, void *pEventData ) SetNextThink( gpGlobals->curtime + 0.1f ); } break; + default: + break; } } diff --git a/dlls/lightglow.cpp b/dlls/lightglow.cpp index 20b30c9f..d5ffdb76 100644 --- a/dlls/lightglow.cpp +++ b/dlls/lightglow.cpp @@ -44,6 +44,7 @@ public: CNetworkVar( float, m_flHDRColorScale ); }; +void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ); IMPLEMENT_SERVERCLASS_ST_NOBASE( CLightGlow, DT_LightGlow ) SendPropInt( SENDINFO(m_clrRender), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt ), diff --git a/dlls/lights.cpp b/dlls/lights.cpp index a19da9c8..ec2be340 100644 --- a/dlls/lights.cpp +++ b/dlls/lights.cpp @@ -218,7 +218,7 @@ void CLight::FadeThink(void) { char sCurString[2]; sCurString[0] = m_iCurrentFade; - sCurString[1] = NULL; + sCurString[1] = 0; engine->LightStyle(m_iStyle, sCurString); // UNDONE: Consider making this settable war to control fade speed diff --git a/dlls/logicentities.cpp b/dlls/logicentities.cpp index d35edd2b..639d180d 100644 --- a/dlls/logicentities.cpp +++ b/dlls/logicentities.cpp @@ -711,10 +711,10 @@ void CMathColorBlend::InputValue( inputdata_t &inputdata ) // Remap the input value to the desired output color and update the output. // color32 Color; - Color.r = m_OutColor1.r + (((flClampValue - m_flInMin) * (m_OutColor2.r - m_OutColor1.r)) / (m_flInMax - m_flInMin)); - Color.g = m_OutColor1.g + (((flClampValue - m_flInMin) * (m_OutColor2.g - m_OutColor1.g)) / (m_flInMax - m_flInMin)); - Color.b = m_OutColor1.b + (((flClampValue - m_flInMin) * (m_OutColor2.b - m_OutColor1.b)) / (m_flInMax - m_flInMin)); - Color.a = m_OutColor1.a + (((flClampValue - m_flInMin) * (m_OutColor2.a - m_OutColor1.a)) / (m_flInMax - m_flInMin)); + Color.r = static_cast(m_OutColor1.r + (((flClampValue - m_flInMin) * (m_OutColor2.r - m_OutColor1.r)) / (m_flInMax - m_flInMin))); + Color.g = static_cast(m_OutColor1.g + (((flClampValue - m_flInMin) * (m_OutColor2.g - m_OutColor1.g)) / (m_flInMax - m_flInMin))); + Color.b = static_cast(m_OutColor1.b + (((flClampValue - m_flInMin) * (m_OutColor2.b - m_OutColor1.b)) / (m_flInMax - m_flInMin))); + Color.a = static_cast(m_OutColor1.a + (((flClampValue - m_flInMin) * (m_OutColor2.a - m_OutColor1.a)) / (m_flInMax - m_flInMin))); m_OutValue.Set(Color, inputdata.pActivator, this); } diff --git a/dlls/movement.cpp b/dlls/movement.cpp index 6dd4d94a..b7c8a7d8 100644 --- a/dlls/movement.cpp +++ b/dlls/movement.cpp @@ -146,9 +146,9 @@ void CPathKeyFrame::CalculateFrameDuration( void ) float x = 0; for ( int i = 0; i < 3; i++ ) { - if ( abs(ang[i]) > x ) + if ( abs(static_cast(ang[i])) > x ) { - x = abs(ang[i]); + x = abs(static_cast(ang[i])); } } diff --git a/dlls/nav.h b/dlls/nav.h index d4a42616..1bd82a31 100644 --- a/dlls/nav.h +++ b/dlls/nav.h @@ -174,6 +174,7 @@ inline NavDirType OppositeDirection( NavDirType dir ) case SOUTH: return NORTH; case EAST: return WEST; case WEST: return EAST; + default: break; } return NORTH; @@ -188,6 +189,7 @@ inline NavDirType DirectionLeft( NavDirType dir ) case SOUTH: return EAST; case EAST: return NORTH; case WEST: return SOUTH; + default: break; } return NORTH; @@ -202,6 +204,7 @@ inline NavDirType DirectionRight( NavDirType dir ) case SOUTH: return WEST; case EAST: return SOUTH; case WEST: return NORTH; + default: break; } return NORTH; @@ -216,6 +219,7 @@ inline void AddDirectionVector( Vector *v, NavDirType dir, float amount ) case SOUTH: v->y += amount; return; case EAST: v->x += amount; return; case WEST: v->x -= amount; return; + default: break; } } @@ -228,6 +232,7 @@ inline float DirectionToAngle( NavDirType dir ) case SOUTH: return 90.0f; case EAST: return 0.0f; case WEST: return 180.0f; + default: break; } return 0.0f; @@ -263,6 +268,7 @@ inline void DirectionToVector2D( NavDirType dir, Vector2D *v ) case SOUTH: v->x = 0.0f; v->y = 1.0f; break; case EAST: v->x = 1.0f; v->y = 0.0f; break; case WEST: v->x = -1.0f; v->y = 0.0f; break; + default: break; } } @@ -276,6 +282,7 @@ inline void CornerToVector2D( NavCornerType dir, Vector2D *v ) case NORTH_EAST: v->x = 1.0f; v->y = -1.0f; break; case SOUTH_EAST: v->x = 1.0f; v->y = 1.0f; break; case SOUTH_WEST: v->x = -1.0f; v->y = 1.0f; break; + default: break; } v->NormalizeInPlace(); diff --git a/dlls/nav_area.cpp b/dlls/nav_area.cpp index 5d3d17fa..df3f01d7 100644 --- a/dlls/nav_area.cpp +++ b/dlls/nav_area.cpp @@ -877,6 +877,8 @@ void CNavArea::FinishSplitEdit( CNavArea *newArea, NavDirType ignoreEdge ) corner[0] = NORTH_WEST; corner[1] = SOUTH_WEST; break; + default: + break; } while ( !newArea->IsOverlapping( *newArea->m_node[ corner[0] ]->GetPosition(), GenerationStepSize/2 ) ) @@ -2221,6 +2223,8 @@ void CNavArea::DrawConnectedAreas( void ) const from = hookPos + Vector( size, 0.0f, 0.0f ); to = hookPos + Vector( -size, 0.0f, 0.0f ); break; + default: + break; } from.z = GetZ( from ); @@ -2504,6 +2508,8 @@ static Vector FindPositionInArea( CNavArea *area, NavCornerType corner ) multX = -1; multY = -1; break; + default: + break; } const float offset = 12.5f; @@ -3027,6 +3033,8 @@ void CNavArea::RaiseCorner( NavCornerType corner, int amount, bool raiseAdjacent case SOUTH_EAST: m_extent.hi.z += amount; break; + default: + break; } // Recompute the center @@ -3083,7 +3091,7 @@ void CNavArea::RaiseCorner( NavCornerType corner, int amount, bool raiseAdjacent if ( areaPos.DistTo( cornerPos ) < tolerance ) { float heightDiff = (cornerPos.z + amount ) - areaPos.z; - area->RaiseCorner( NavCornerType(i), heightDiff, false ); + area->RaiseCorner( NavCornerType(i), static_cast(heightDiff), false ); } } } @@ -3200,25 +3208,25 @@ void CNavArea::PlaceOnGround( NavCornerType corner, float inset ) if ( corner == NORTH_WEST || corner == NUM_CORNERS ) { float newZ = FindGroundZ( nw, ne, sw ); - RaiseCorner( NORTH_WEST, newZ - nw.z ); + RaiseCorner( NORTH_WEST, static_cast(newZ - nw.z) ); } if ( corner == NORTH_EAST || corner == NUM_CORNERS ) { float newZ = FindGroundZ( ne, nw, se ); - RaiseCorner( NORTH_EAST, newZ - ne.z ); + RaiseCorner( NORTH_EAST, static_cast(newZ - ne.z) ); } if ( corner == SOUTH_WEST || corner == NUM_CORNERS ) { float newZ = FindGroundZ( sw, nw, se ); - RaiseCorner( SOUTH_WEST, newZ - sw.z ); + RaiseCorner( SOUTH_WEST, static_cast(newZ - sw.z) ); } if ( corner == SOUTH_EAST || corner == NUM_CORNERS ) { float newZ = FindGroundZ( se, ne, sw ); - RaiseCorner( SOUTH_EAST, newZ - se.z ); + RaiseCorner( SOUTH_EAST, static_cast(newZ - se.z) ); } } @@ -3351,7 +3359,7 @@ public: //----------------------------------------------------------------------------------------------------- -static int GetPushawayEntsInVolume( const Vector& origin, const Vector& mins, const Vector& maxs, CBaseEntity **ents, int nMaxEnts, int PartitionMask, CNavBlockerEnumerator *enumerator ) +/*static int GetPushawayEntsInVolume( const Vector& origin, const Vector& mins, const Vector& maxs, CBaseEntity **ents, int nMaxEnts, int PartitionMask, CNavBlockerEnumerator *enumerator ) { Ray_t ray; ray.Init( origin, origin, mins, maxs ); @@ -3371,7 +3379,7 @@ static int GetPushawayEntsInVolume( const Vector& origin, const Vector& mins, co delete physPropEnum; return numHit; -} +}*/ //-------------------------------------------------------------------------------------------------------------- diff --git a/dlls/nav_colors.h b/dlls/nav_colors.h index 8b0bd643..da1c8d49 100644 --- a/dlls/nav_colors.h +++ b/dlls/nav_colors.h @@ -1,69 +1,69 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -// Colors used for nav editing - -#ifndef NAV_COLORS_H -#define NAV_COLORS_H - -//-------------------------------------------------------------------------------------------------------------- -enum NavEditColor -{ - // Degenerate area colors - NavDegenerateFirstColor = 0, - NavDegenerateSecondColor, - - // Place painting color - NavSamePlaceColor, - NavDifferentPlaceColor, - NavNoPlaceColor, - - // Normal colors - NavSelectedColor, - NavMarkedColor, - NavNormalColor, - NavCornerColor, - NavBlockedColor, - - // Hiding spot colors - NavIdealSniperColor, - NavGoodSniperColor, - NavGoodCoverColor, - NavExposedColor, - NavApproachPointColor, - - // Connector colors - NavConnectedTwoWaysColor, - NavConnectedOneWayColor, - - // Editing colors - NavCursorColor, - NavSplitLineColor, - NavCreationColor, - NavGridColor, - - // Nav attribute colors - NavAttributeCrouchColor, - NavAttributeJumpColor, - NavAttributePreciseColor, - NavAttributeNoJumpColor, - NavAttributeStopColor, - NavAttributeRunColor, - NavAttributeWalkColor, - NavAttributeAvoidColor, -}; - -//-------------------------------------------------------------------------------------------------------------- - -void NavDrawLine( const Vector& from, const Vector& to, NavEditColor navColor ); -void NavDrawTriangle( const Vector& point1, const Vector& point2, const Vector& point3, NavEditColor navColor ); -void NavDrawHorizontalArrow( const Vector& from, const Vector& to, float width, NavEditColor navColor ); -void NavDrawDashedLine( const Vector& from, const Vector& to, NavEditColor navColor ); - -//-------------------------------------------------------------------------------------------------------------- - -#endif // NAV_COLORS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Colors used for nav editing + +#ifndef NAV_COLORS_H +#define NAV_COLORS_H + +//-------------------------------------------------------------------------------------------------------------- +enum NavEditColor +{ + // Degenerate area colors + NavDegenerateFirstColor = 0, + NavDegenerateSecondColor, + + // Place painting color + NavSamePlaceColor, + NavDifferentPlaceColor, + NavNoPlaceColor, + + // Normal colors + NavSelectedColor, + NavMarkedColor, + NavNormalColor, + NavCornerColor, + NavBlockedColor, + + // Hiding spot colors + NavIdealSniperColor, + NavGoodSniperColor, + NavGoodCoverColor, + NavExposedColor, + NavApproachPointColor, + + // Connector colors + NavConnectedTwoWaysColor, + NavConnectedOneWayColor, + + // Editing colors + NavCursorColor, + NavSplitLineColor, + NavCreationColor, + NavGridColor, + + // Nav attribute colors + NavAttributeCrouchColor, + NavAttributeJumpColor, + NavAttributePreciseColor, + NavAttributeNoJumpColor, + NavAttributeStopColor, + NavAttributeRunColor, + NavAttributeWalkColor, + NavAttributeAvoidColor, +}; + +//-------------------------------------------------------------------------------------------------------------- + +void NavDrawLine( const Vector& from, const Vector& to, NavEditColor navColor ); +void NavDrawTriangle( const Vector& point1, const Vector& point2, const Vector& point3, NavEditColor navColor ); +void NavDrawHorizontalArrow( const Vector& from, const Vector& to, float width, NavEditColor navColor ); +void NavDrawDashedLine( const Vector& from, const Vector& to, NavEditColor navColor ); + +//-------------------------------------------------------------------------------------------------------------- + +#endif // NAV_COLORS_H diff --git a/dlls/nav_file.cpp b/dlls/nav_file.cpp index ce342175..75fa8519 100644 --- a/dlls/nav_file.cpp +++ b/dlls/nav_file.cpp @@ -1117,7 +1117,7 @@ static NavErrorType CheckNavFile( const char *bspFilename ) // read file version number unsigned int version; result = filesystem->Read( &version, sizeof(unsigned int), file ); - if (!result || version > NavCurrentVersion || version < 4) + if (!result || version > static_cast(NavCurrentVersion) || version < 4) { filesystem->Close( file ); return NAV_BAD_FILE_VERSION; @@ -1166,6 +1166,8 @@ void CommandNavCheckFileConsistency( void ) case NAV_OK: Msg( "The nav file for %s is up-to-date\n", bspFilename ); break; + default: + break; } bspFilename = filesystem->FindNext( findHandle ); @@ -1218,7 +1220,7 @@ NavErrorType CNavMesh::Load( void ) // read file version number unsigned int version; result = filesystem->Read( &version, sizeof(unsigned int), file ); - if (!result || version > NavCurrentVersion) + if (!result || version > static_cast(NavCurrentVersion)) { Msg( "Unknown navigation file version.\n" ); filesystem->Close( file ); diff --git a/dlls/nav_generate.cpp b/dlls/nav_generate.cpp index d86b03da..3ec7dff4 100644 --- a/dlls/nav_generate.cpp +++ b/dlls/nav_generate.cpp @@ -1895,6 +1895,9 @@ bool CNavMesh::UpdateGeneration( float maxTime ) return false; } + + default: + break; } return false; @@ -2073,16 +2076,16 @@ bool CNavMesh::SampleStep( void ) Vector pos = *m_currentNode->GetPosition(); // snap to grid - int cx = SnapToGrid( pos.x ); - int cy = SnapToGrid( pos.y ); + int cx = static_cast(SnapToGrid( pos.x )); + int cy = static_cast(SnapToGrid( pos.y )); // attempt to move to adjacent node switch( dir ) { - case NORTH: cy -= GenerationStepSize; break; - case SOUTH: cy += GenerationStepSize; break; - case EAST: cx += GenerationStepSize; break; - case WEST: cx -= GenerationStepSize; break; + case NORTH: cy -= static_cast(GenerationStepSize); break; + case SOUTH: cy += static_cast(GenerationStepSize); break; + case EAST: cx += static_cast(GenerationStepSize); break; + case WEST: cx -= static_cast(GenerationStepSize); break; } pos.x = cx; diff --git a/dlls/nav_ladder.cpp b/dlls/nav_ladder.cpp index 38f8d15a..25b90aac 100644 --- a/dlls/nav_ladder.cpp +++ b/dlls/nav_ladder.cpp @@ -93,6 +93,8 @@ CNavArea ** CNavLadder::GetConnection( LadderConnectionType dir ) return &m_topBehindArea; case LADDER_BOTTOM: return &m_bottomArea; + default: + break; } return NULL; diff --git a/dlls/nav_mesh.cpp b/dlls/nav_mesh.cpp index ca5d73e1..4d9ada43 100644 --- a/dlls/nav_mesh.cpp +++ b/dlls/nav_mesh.cpp @@ -502,7 +502,7 @@ CNavArea *CNavMesh::GetNearestNavArea( const Vector &pos, bool anyZ, float maxDi int originX = WorldToGridX( pos.x ); int originY = WorldToGridY( pos.y ); - int shiftLimit = maxDist / m_gridCellSize; + int shiftLimit = static_cast(maxDist / m_gridCellSize); // // Search in increasing rings out from origin, starting with cell diff --git a/dlls/npc_talker.cpp b/dlls/npc_talker.cpp index 5220fdcc..5992b2ad 100644 --- a/dlls/npc_talker.cpp +++ b/dlls/npc_talker.cpp @@ -765,7 +765,7 @@ int CNPCSimpleTalker::PlayScriptedSentence( const char *pszSentence, float delay m_useTime = gpGlobals->curtime + delay; // Stop all idle speech until after the sentence has completed - DeferAllIdleSpeech( delay + random->RandomInt( 3.0f, 5.0f ) ); + DeferAllIdleSpeech( delay + random->RandomFloat( 3.0f, 5.0f ) ); return sentenceIndex; } @@ -1126,7 +1126,7 @@ bool CNPCSimpleTalker::ShouldSpeakRandom( int iChance, float flModifier ) if ( !flModifier ) return false; - iChance = floor( (float)iChance / flModifier ); + iChance = static_cast(floor( (float)iChance / flModifier )); } return (random->RandomInt(0,iChance) == 0); diff --git a/dlls/npc_talker.h b/dlls/npc_talker.h index fc0f1db0..f1fb8524 100644 --- a/dlls/npc_talker.h +++ b/dlls/npc_talker.h @@ -15,7 +15,7 @@ #include #endif -#ifndef _XBOX +#if !defined _XBOX && defined _MSC_VER #pragma warning(push) #include #pragma warning(pop) diff --git a/dlls/npc_vehicledriver.cpp b/dlls/npc_vehicledriver.cpp index d5c02d6b..a6774cf0 100644 --- a/dlls/npc_vehicledriver.cpp +++ b/dlls/npc_vehicledriver.cpp @@ -297,6 +297,8 @@ int CNPC_VehicleDriver::SelectSchedule( void ) return SCHED_VEHICLEDRIVER_COMBAT_WAIT; } break; + default: + break; } return BaseClass::SelectSchedule(); diff --git a/dlls/physics.cpp b/dlls/physics.cpp index 7dc70510..ab293383 100644 --- a/dlls/physics.cpp +++ b/dlls/physics.cpp @@ -506,7 +506,7 @@ static void ReportPenetration( CBaseEntity *pEntity, float duration ) pEntity->m_debugOverlays |= OVERLAY_ABSBOX_BIT; } - pEntity->AddTimedOverlay( UTIL_VarArgs("VPhysics Penetration Error (%s)!", pEntity->GetDebugName()), duration ); + pEntity->AddTimedOverlay( UTIL_VarArgs("VPhysics Penetration Error (%s)!", pEntity->GetDebugName()), static_cast(duration) ); } } @@ -1461,7 +1461,7 @@ friction_t *CCollisionEvent::FindFriction( CBaseEntity *pObject ) { friction_t *pFree = NULL; - for ( int i = 0; i < ARRAYSIZE(m_current); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(m_current); i++ ) { if ( !m_current[i].pObject && !pFree ) pFree = &m_current[i]; @@ -1541,7 +1541,7 @@ float CCollisionEvent::DeltaTimeSinceLastFluid( CBaseEntity *pEntity ) void CCollisionEvent::UpdateFrictionSounds( void ) { - for ( int i = 0; i < ARRAYSIZE(m_current); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(m_current); i++ ) { if ( m_current[i].patch ) { @@ -1875,7 +1875,7 @@ int CCollisionEvent::AddDamageInflictor( IPhysicsObject *pInflictorPhysics, floa void CCollisionEvent::LevelShutdown( void ) { - for ( int i = 0; i < ARRAYSIZE(m_current); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(m_current); i++ ) { if ( m_current[i].patch ) { diff --git a/dlls/physics_cannister.cpp b/dlls/physics_cannister.cpp index a766bae3..d83e6fb8 100644 --- a/dlls/physics_cannister.cpp +++ b/dlls/physics_cannister.cpp @@ -364,7 +364,7 @@ void CPhysicsCannister::Explode( CBaseEntity *pAttacker ) pPhysics->GetVelocity( &velocity, &angVelocity ); PropBreakableCreateAll( GetModelIndex(), pPhysics, GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity, 1.0, 20, COLLISION_GROUP_DEBRIS ); - ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), pAttacker, m_damage, 0, true ); + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), pAttacker, static_cast(m_damage), 0, true ); UTIL_Remove( this ); } diff --git a/dlls/physics_impact_damage.cpp b/dlls/physics_impact_damage.cpp index 12258d16..90f4a166 100644 --- a/dlls/physics_impact_damage.cpp +++ b/dlls/physics_impact_damage.cpp @@ -467,7 +467,7 @@ float CalculateDefaultPhysicsDamage( int index, gamevcollisionevent_t *pEvent, f // If we have a specified damage table, find it and use it instead if ( iszDamageTableName != NULL_STRING ) { - for ( int i = 0; i < ARRAYSIZE(gDamageTableRegistry); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(gDamageTableRegistry); i++ ) { if ( !Q_strcmp( gDamageTableRegistry[i].pszTableName, STRING(iszDamageTableName) ) ) return CalculatePhysicsImpactDamage( index, pEvent, *(gDamageTableRegistry[i].pTable), energyScale, allowStaticDamage, damageType, bDamageFromHeldObjects ); diff --git a/dlls/physics_main.cpp b/dlls/physics_main.cpp index 5c1fdc1b..0f60d2ea 100644 --- a/dlls/physics_main.cpp +++ b/dlls/physics_main.cpp @@ -578,8 +578,8 @@ void CPhysicsPushedEntities::StoreMovedEntities( physicspushlist_t &list ) list.localOrigin = m_rootPusherStartLocalOrigin; list.localAngles = m_rootPusherStartLocalAngles; list.pushedCount = CountMovedEntities(); - Assert(list.pushedCount < ARRAYSIZE(list.pushedEnts)); - if ( list.pushedCount > ARRAYSIZE(list.pushedEnts) ) + Assert(static_cast(list.pushedCount) < ARRAYSIZE(list.pushedEnts)); + if ( list.pushedCount > static_cast(ARRAYSIZE(list.pushedEnts)) ) { list.pushedCount = ARRAYSIZE(list.pushedEnts); } @@ -1474,7 +1474,7 @@ void CBaseEntity::PerformPush( float movetime ) m_pBlocker = pBlocker; if (m_pBlocker.ToInt() != hPrevBlocker) { - if (hPrevBlocker != INVALID_EHANDLE_INDEX) + if (hPrevBlocker != static_cast(INVALID_EHANDLE_INDEX)) { EndBlocked(); } diff --git a/dlls/physics_prop_ragdoll.cpp b/dlls/physics_prop_ragdoll.cpp index 9b65bc9a..a775cfa5 100644 --- a/dlls/physics_prop_ragdoll.cpp +++ b/dlls/physics_prop_ragdoll.cpp @@ -343,7 +343,7 @@ void CRagdollProp::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t r } else { - CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 2.0f, 0.0f ); + CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 2.0f, 0 ); } } diff --git a/dlls/physobj.cpp b/dlls/physobj.cpp index c5f181ae..af685de7 100644 --- a/dlls/physobj.cpp +++ b/dlls/physobj.cpp @@ -1243,7 +1243,7 @@ void CPhysConvert::InputConvertTarget( inputdata_t &inputdata ) while ( (pEntity = gEntList.FindEntityByName( pEntity, m_target, NULL, inputdata.pActivator, inputdata.pCaller )) != NULL ) { entlist[count++] = pEntity; - if ( count >= ARRAYSIZE(entlist) ) + if ( count >= static_cast(ARRAYSIZE(entlist)) ) break; } diff --git a/dlls/player.cpp b/dlls/player.cpp index 04aaebd0..eaa9547c 100644 --- a/dlls/player.cpp +++ b/dlls/player.cpp @@ -1,8134 +1,8131 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Functions dealing with the player. -// -//=============================================================================// - -#include "cbase.h" -#include "const.h" -#include "baseplayer_shared.h" -#include "trains.h" -#include "soundent.h" -#include "gib.h" -#include "shake.h" -#include "decals.h" -#include "gamerules.h" -#include "game.h" -#include "entityapi.h" -#include "entitylist.h" -#include "eventqueue.h" -#include "worldsize.h" -#include "isaverestore.h" -#include "globalstate.h" -#include "basecombatweapon.h" -#include "ai_basenpc.h" -#include "ai_network.h" -#include "ai_node.h" -#include "ai_networkmanager.h" -#include "ammodef.h" -#include "mathlib.h" -#include "ndebugoverlay.h" -#include "baseviewmodel.h" -#include "in_buttons.h" -#include "client.h" -#include "team.h" -#include "particle_smokegrenade.h" -#include "IEffects.h" -#include "vstdlib/random.h" -#include "engine/IEngineSound.h" -#include "movehelper_server.h" -#include "igamemovement.h" -#include "saverestoretypes.h" -#include "iservervehicle.h" -#include "movevars_shared.h" -#include "vcollide_parse.h" -#include "player_command.h" -#include "vehicle_base.h" -#include "AI_Criteria.h" -#include "globals.h" -#include "usermessages.h" -#include "gamevars_shared.h" -#include "world.h" -#include "physobj.h" -#include "KeyValues.h" -#include "coordsize.h" -#include "vphysics/player_controller.h" -#include "saverestore_utlvector.h" -#include "hltvdirector.h" -#include "nav_mesh.h" -#include "env_zoom.h" -#include "rumble_shared.h" - -#ifdef HL2_DLL -#include "combine_mine.h" -#include "weapon_physcannon.h" -#endif - -#ifdef HL2_DLL -extern ConVar hl2_xbox_aiming; -#define UseXboxAiming() hl2_xbox_aiming.GetBool() -#else -#define UseXboxAiming() 0 -#endif - -ConVar autoaim_max_dist( "autoaim_max_dist", "2160" ); // 2160 = 180 feet -ConVar autoaim_max_deflect( "autoaim_max_deflect", "0.99" ); - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -static ConVar old_armor( "player_old_armor", "0" ); - -static ConVar physicsshadowupdate_render( "physicsshadowupdate_render", "0" ); -bool IsInCommentaryMode( void ); -bool IsListeningToCommentary( void ); - -// This is declared in the engine, too -ConVar sv_noclipduringpause( "sv_noclipduringpause", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If cheats are enabled, then you can noclip with the game paused (for doing screenshots, etc.)." ); - -extern ConVar sv_maxunlag; -extern ConVar sv_turbophysics; -extern ConVar *sv_maxreplay; - -// TIME BASED DAMAGE AMOUNT -// 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 - -//---------------------------------------------------- -// Player Physics Shadow -//---------------------------------------------------- -#define VPHYS_MAX_DISTANCE 2.0 -#define VPHYS_MAX_VEL 10 -#define VPHYS_MAX_DISTSQR (VPHYS_MAX_DISTANCE*VPHYS_MAX_DISTANCE) -#define VPHYS_MAX_VELSQR (VPHYS_MAX_VEL*VPHYS_MAX_VEL) - - -extern bool g_fDrawLines; -int gEvilImpulse101; - -bool gInitHUD = true; - -extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); -int MapTextureTypeStepType(char chTextureType); -extern void SpawnBlood(Vector vecSpot, const Vector &vecDir, int bloodColor, float flDamage); -extern void AddMultiDamage( const CTakeDamageInfo &info, CBaseEntity *pEntity ); - - -#define CMD_MOSTRECENT 0 - -//#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes -//#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) - - -//#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage -//#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet -//#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 ) -//#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 ) - -// player damage adjusters -ConVar sk_player_head( "sk_player_head","2" ); -ConVar sk_player_chest( "sk_player_chest","1" ); -ConVar sk_player_stomach( "sk_player_stomach","1" ); -ConVar sk_player_arm( "sk_player_arm","1" ); -ConVar sk_player_leg( "sk_player_leg","1" ); - -void CC_GiveCurrentAmmo( void ) -{ -#ifdef _XBOX - CBasePlayer *pPlayer = UTIL_PlayerByIndex(1); - - if( pPlayer ) - { - CBaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon(); - - if( pWeapon ) - { - if( pWeapon->UsesPrimaryAmmo() ) - { - int ammoIndex = pWeapon->GetPrimaryAmmoType(); - - if( ammoIndex != -1 ) - { - int giveAmount; - giveAmount = GetAmmoDef()->MaxCarry(ammoIndex); - pPlayer->GiveAmmo( giveAmount, GetAmmoDef()->GetAmmoOfIndex(ammoIndex)->pName ); - } - } - if( pWeapon->UsesSecondaryAmmo() && pWeapon->HasSecondaryAmmo() ) - { - // Give secondary ammo out, as long as the player already has some - // from a presumeably natural source. This prevents players on XBox - // having Combine Balls and so forth in areas of the game that - // were not tested with these items. - int ammoIndex = pWeapon->GetSecondaryAmmoType(); - - if( ammoIndex != -1 ) - { - int giveAmount; - giveAmount = GetAmmoDef()->MaxCarry(ammoIndex); - pPlayer->GiveAmmo( giveAmount, GetAmmoDef()->GetAmmoOfIndex(ammoIndex)->pName ); - } - } - } - } -#endif//_XBOX -} -static ConCommand givecurrentammo("givecurrentammo", CC_GiveCurrentAmmo, "Give a supply of ammo for current weapon..\n", FCVAR_CHEAT ); - - -// pl -BEGIN_SIMPLE_DATADESC( CPlayerState ) - // DEFINE_FIELD( netname, FIELD_STRING ), // Don't stomp player name with what's in save/restore - DEFINE_FIELD( v_angle, FIELD_VECTOR ), - DEFINE_FIELD( deadflag, FIELD_BOOLEAN ), - - // this is always set to true on restore, don't bother saving it. - // DEFINE_FIELD( fixangle, FIELD_INTEGER ), - // DEFINE_FIELD( anglechange, FIELD_FLOAT ), - // DEFINE_FIELD( hltv, FIELD_BOOLEAN ), - // DEFINE_FIELD( frags, FIELD_INTEGER ), - // DEFINE_FIELD( deaths, FIELD_INTEGER ), -END_DATADESC() - -// Global Savedata for player -BEGIN_DATADESC( CBasePlayer ) - - DEFINE_EMBEDDED( m_Local ), - DEFINE_UTLVECTOR( m_hTriggerSoundscapeList, FIELD_EHANDLE ), - DEFINE_EMBEDDED( pl ), - - DEFINE_FIELD( m_StuckLast, FIELD_INTEGER ), - - DEFINE_FIELD( m_nButtons, FIELD_INTEGER ), - DEFINE_FIELD( m_afButtonLast, FIELD_INTEGER ), - DEFINE_FIELD( m_afButtonPressed, FIELD_INTEGER ), - DEFINE_FIELD( m_afButtonReleased, FIELD_INTEGER ), - - DEFINE_FIELD( m_iFOV, FIELD_INTEGER ), - DEFINE_FIELD( m_iDefaultFOV, FIELD_INTEGER ), - - //DEFINE_FIELD( m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore - DEFINE_FIELD( m_iObserverMode, FIELD_INTEGER ), - DEFINE_FIELD( m_iObserverLastMode, FIELD_INTEGER ), - DEFINE_FIELD( m_hObserverTarget, FIELD_EHANDLE ), - DEFINE_FIELD( m_bForcedObserverMode, FIELD_BOOLEAN ), - DEFINE_AUTO_ARRAY( m_szAnimExtension, FIELD_CHARACTER ), -// DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ), - - DEFINE_FIELD( m_nUpdateRate, FIELD_INTEGER ), - DEFINE_FIELD( m_fLerpTime, FIELD_FLOAT ), - DEFINE_FIELD( m_bLagCompensation, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bPredictWeapons, FIELD_BOOLEAN ), - - DEFINE_FIELD( m_vecAdditionalPVSOrigin, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vecCameraPVSOrigin, FIELD_POSITION_VECTOR ), - - DEFINE_FIELD( m_hUseEntity, FIELD_EHANDLE ), - DEFINE_FIELD( m_iTrain, FIELD_INTEGER ), - DEFINE_FIELD( m_iRespawnFrames, FIELD_FLOAT ), - DEFINE_FIELD( m_afPhysicsFlags, FIELD_INTEGER ), - DEFINE_FIELD( m_hVehicle, FIELD_EHANDLE ), - - // recreate, don't restore - // DEFINE_FIELD( m_CommandContext, CUtlVector < CCommandContext > ), - //DEFINE_FIELD( m_pPhysicsController, FIELD_POINTER ), - //DEFINE_FIELD( m_pShadowStand, FIELD_POINTER ), - //DEFINE_FIELD( m_pShadowCrouch, FIELD_POINTER ), - //DEFINE_FIELD( m_vphysicsCollisionState, FIELD_INTEGER ), - // DEFINE_FIELD( m_lastNavArea, CNavArea ), - DEFINE_ARRAY( m_szNetworkIDString, FIELD_CHARACTER, MAX_NETWORKID_LENGTH ), - DEFINE_FIELD( m_oldOrigin, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( m_vecSmoothedVelocity, FIELD_VECTOR ), - //DEFINE_FIELD( m_touchedPhysObject, FIELD_BOOLEAN ), - //DEFINE_FIELD( m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() - DEFINE_FIELD( m_iTargetVolume, FIELD_INTEGER ), - DEFINE_AUTO_ARRAY( m_rgItems, FIELD_INTEGER ), - //DEFINE_FIELD( m_fNextSuicideTime, FIELD_TIME ), - - DEFINE_FIELD( m_flTimeStepSound, FIELD_TIME ), - DEFINE_FIELD( m_flSwimTime, FIELD_TIME ), - DEFINE_FIELD( m_flDuckTime, FIELD_TIME ), - DEFINE_FIELD( m_flDuckJumpTime, FIELD_TIME ), - - DEFINE_FIELD( m_flSuitUpdate, FIELD_TIME ), - DEFINE_AUTO_ARRAY( m_rgSuitPlayList, FIELD_INTEGER ), - DEFINE_FIELD( m_iSuitPlayNext, FIELD_INTEGER ), - DEFINE_AUTO_ARRAY( m_rgiSuitNoRepeat, FIELD_INTEGER ), - DEFINE_AUTO_ARRAY( m_rgflSuitNoRepeatTime, FIELD_TIME ), - DEFINE_FIELD( m_lastDamageAmount, FIELD_INTEGER ), - DEFINE_FIELD( m_tbdPrev, FIELD_TIME ), - - //DEFINE_FIELD( m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() - //DEFINE_FIELD( m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() - //DEFINE_FIELD( m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() - //DEFINE_FIELD( m_iStepLeft, FIELD_INTEGER ), // Don't need to restore - //DEFINE_FIELD( m_chTextureType, FIELD_CHARACTER ), // Don't need to restore - - DEFINE_FIELD( m_idrowndmg, FIELD_INTEGER ), - DEFINE_FIELD( m_idrownrestored, FIELD_INTEGER ), - - DEFINE_FIELD( m_nPoisonDmg, FIELD_INTEGER ), - DEFINE_FIELD( m_nPoisonRestored, FIELD_INTEGER ), - - DEFINE_FIELD( m_bitsHUDDamage, FIELD_INTEGER ), - DEFINE_FIELD( m_fInitHUD, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flDeathTime, FIELD_TIME ), - - //DEFINE_FIELD( m_fGameHUDInitialized, FIELD_BOOLEAN ), // only used in multiplayer games - //DEFINE_FIELD( m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset - //DEFINE_FIELD( m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore - //DEFINE_FIELD( m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset - //DEFINE_FIELD( m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset - //DEFINE_FIELD( m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed - //DEFINE_FIELD( m_lastx, FIELD_INTEGER ), - //DEFINE_FIELD( m_lasty, FIELD_INTEGER ), - - DEFINE_FIELD( m_iFrags, FIELD_INTEGER ), - DEFINE_FIELD( m_iDeaths, FIELD_INTEGER ), - DEFINE_FIELD( m_flNextDecalTime, FIELD_TIME ), - //DEFINE_AUTO_ARRAY( m_szTeamName, FIELD_STRING ), // mp - - //DEFINE_FIELD( m_iConnected, FIELD_INTEGER ), - // from edict_t - DEFINE_FIELD( m_ArmorValue, FIELD_INTEGER ), - DEFINE_FIELD( m_DmgOrigin, FIELD_VECTOR ), - DEFINE_FIELD( m_DmgTake, FIELD_FLOAT ), - DEFINE_FIELD( m_DmgSave, FIELD_FLOAT ), - DEFINE_FIELD( m_AirFinished, FIELD_TIME ), - DEFINE_FIELD( m_PainFinished, FIELD_TIME ), - - DEFINE_FIELD( m_iPlayerLocked, FIELD_INTEGER ), - - DEFINE_AUTO_ARRAY( m_hViewModel, FIELD_EHANDLE ), - - DEFINE_FIELD( m_flMaxspeed, FIELD_FLOAT ), - DEFINE_FIELD( m_flWaterJumpTime, FIELD_TIME ), - DEFINE_FIELD( m_vecWaterJumpVel, FIELD_VECTOR ), - DEFINE_FIELD( m_nImpulse, FIELD_INTEGER ), - DEFINE_FIELD( m_flStepSoundTime, FIELD_TIME ), - DEFINE_FIELD( m_flSwimSoundTime, FIELD_TIME ), - DEFINE_FIELD( m_vecLadderNormal, FIELD_VECTOR ), - - DEFINE_FIELD( m_flFlashTime, FIELD_TIME ), - DEFINE_FIELD( m_nDrownDmgRate, FIELD_INTEGER ), - - // NOT SAVED - //DEFINE_FIELD( m_vForcedOrigin, FIELD_VECTOR ), - //DEFINE_FIELD( m_bForceOrigin, FIELD_BOOLEAN ), - //DEFINE_FIELD( m_nTickBase, FIELD_INTEGER ), - //DEFINE_FIELD( m_LastCmd, FIELD_ ), - // DEFINE_FIELD( m_pCurrentCommand, CUserCmd ), - //DEFINE_FIELD( m_bGamePaused, FIELD_BOOLEAN ), - - DEFINE_FIELD( m_bitsDamageType, FIELD_INTEGER ), - DEFINE_AUTO_ARRAY( m_rgbTimeBasedDamage, FIELD_CHARACTER ), - DEFINE_FIELD( m_fLastPlayerTalkTime, FIELD_FLOAT ), - DEFINE_FIELD( m_hLastWeapon, FIELD_EHANDLE ), - -#if !defined( NO_ENTITY_PREDICTION ) - // DEFINE_FIELD( m_SimulatedByThisPlayer, CUtlVector < CHandle < CBaseEntity > > ), -#endif - - DEFINE_FIELD( m_flOldPlayerZ, FIELD_FLOAT ), - DEFINE_FIELD( m_flOldPlayerViewOffsetZ, FIELD_FLOAT ), - DEFINE_FIELD( m_bPlayerUnderwater, FIELD_BOOLEAN ), - DEFINE_FIELD( m_hViewEntity, FIELD_EHANDLE ), - - DEFINE_FIELD( m_hConstraintEntity, FIELD_EHANDLE ), - DEFINE_FIELD( m_vecConstraintCenter, FIELD_VECTOR ), - DEFINE_FIELD( m_flConstraintRadius, FIELD_FLOAT ), - DEFINE_FIELD( m_flConstraintWidth, FIELD_FLOAT ), - DEFINE_FIELD( m_flConstraintSpeedFactor, FIELD_FLOAT ), - DEFINE_FIELD( m_hZoomOwner, FIELD_EHANDLE ), - - DEFINE_FIELD( m_flLaggedMovementValue, FIELD_FLOAT ), - - DEFINE_FIELD( m_vNewVPhysicsPosition, FIELD_VECTOR ), - DEFINE_FIELD( m_vNewVPhysicsVelocity, FIELD_VECTOR ), - - DEFINE_FIELD( m_bSinglePlayerGameEnding, FIELD_BOOLEAN ), - - // Function Pointers - DEFINE_FUNCTION( PlayerDeathThink ), - - // Inputs - DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), - DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetHUDVisibility", InputSetHUDVisibility ), - - DEFINE_FIELD( m_nNumCrouches, FIELD_INTEGER ), - DEFINE_FIELD( m_bDuckToggled, FIELD_BOOLEAN ), - - DEFINE_FIELD( m_nNumCrateHudHints, FIELD_INTEGER ), - -END_DATADESC() - -int giPrecacheGrunt = 0; - -edict_t *CBasePlayer::s_PlayerEdict = NULL; - - -inline bool ShouldRunCommandsInContext( const CCommandContext *ctx ) -{ - // TODO: This should be enabled at some point. If usercmds can run while paused, then - // they can create entities which will never die and it will fill up the entity list. -#ifdef NO_USERCMDS_DURING_PAUSE - return !ctx->paused || sv_noclipduringpause.GetInt(); -#else - return true; -#endif -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : CBaseViewModel -//----------------------------------------------------------------------------- -CBaseViewModel *CBasePlayer::GetViewModel( int index /*= 0*/ ) -{ - Assert( index >= 0 && index < MAX_VIEWMODELS ); - return m_hViewModel[ index ].Get(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::CreateViewModel( int index /*=0*/ ) -{ - Assert( index >= 0 && index < MAX_VIEWMODELS ); - - if ( GetViewModel( index ) ) - return; - - CBaseViewModel *vm = ( CBaseViewModel * )CreateEntityByName( "viewmodel" ); - if ( vm ) - { - vm->SetAbsOrigin( GetAbsOrigin() ); - vm->SetOwner( this ); - vm->SetIndex( index ); - DispatchSpawn( vm ); - vm->FollowEntity( this ); - m_hViewModel.Set( index, vm ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::DestroyViewModels( void ) -{ - int i; - for ( i = MAX_VIEWMODELS - 1; i >= 0; i-- ) - { - CBaseViewModel *vm = GetViewModel( i ); - if ( !vm ) - continue; - - UTIL_Remove( vm ); - m_hViewModel.Set( i, NULL ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Static member function to create a player of the specified class -// Input : *className - -// *ed - -// Output : CBasePlayer -//----------------------------------------------------------------------------- -CBasePlayer *CBasePlayer::CreatePlayer( const char *className, edict_t *ed ) -{ - CBasePlayer *player; - CBasePlayer::s_PlayerEdict = ed; - player = ( CBasePlayer * )CreateEntityByName( className ); - return player; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -CBasePlayer::CBasePlayer( ) -{ - AddEFlags( EFL_NO_AUTO_EDICT_ATTACH ); - -#ifdef _DEBUG - m_vecAutoAim.Init(); - m_vecAdditionalPVSOrigin.Init(); - m_vecCameraPVSOrigin.Init(); - m_DmgOrigin.Init(); - m_vecLadderNormal.Init(); - - m_oldOrigin.Init(); - m_vecSmoothedVelocity.Init(); -#endif - - if ( s_PlayerEdict ) - { - // take the assigned edict_t and attach it - Assert( s_PlayerEdict != NULL ); - NetworkProp()->AttachEdict( s_PlayerEdict ); - s_PlayerEdict = NULL; - } - - m_flFlashTime = -1; - pl.fixangle = FIXANGLE_ABSOLUTE; - pl.hltv = false; - pl.frags = 0; - pl.deaths = 0; - - m_szNetname[0] = '\0'; - - m_iHealth = 0; - Weapon_SetLast( NULL ); - m_bitsDamageType = 0; - - m_bForceOrigin = false; - m_hVehicle = NULL; - m_pCurrentCommand = NULL; - - // Setup our default FOV - m_iDefaultFOV = g_pGameRules->DefaultFOV(); - - m_hZoomOwner = NULL; - - m_nUpdateRate = 20; // cl_updaterate defualt - m_fLerpTime = 0.1f; // cl_interp default - m_bPredictWeapons = true; - m_bLagCompensation = false; - m_flLaggedMovementValue = 1.0f; - m_StuckLast = 0; - m_impactEnergyScale = 1.0f; - m_fLastPlayerTalkTime = 0.0f; - m_PlayerInfo.SetParent( this ); - - ResetObserverMode(); - - m_surfaceProps = 0; - m_pSurfaceData = NULL; - m_surfaceFriction = 1.0f; - m_chTextureType = 0; - m_chPreviousTextureType = 0; - - m_fDelay = 0.0f; - m_fReplayEnd = -1; - m_iReplayEntity = 0; - - m_autoKickDisabled = false; - - m_nNumCrouches = 0; - m_bDuckToggled = false; -} - -CBasePlayer::~CBasePlayer( ) -{ - VPhysicsDestroyObject(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -void CBasePlayer::UpdateOnRemove( void ) -{ - VPhysicsDestroyObject(); - - // Remove him from his current team - if ( GetTeam() ) - { - GetTeam()->RemovePlayer( this ); - } - - // Chain at end to mimic destructor unwind order - BaseClass::UpdateOnRemove(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : **pvs - -// **pas - -//----------------------------------------------------------------------------- -void CBasePlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) -{ - // If we have a viewentity, we don't add the player's origin. - if ( pViewEntity ) - return; - - Vector org; - org = EyePosition(); - - engine->AddOriginToPVS( org ); -} - -int CBasePlayer::UpdateTransmitState() -{ - // always call ShouldTransmit() for players - return SetTransmitState( FL_EDICT_FULLCHECK ); -} - -int CBasePlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo ) -{ - // Allow me to introduce myself to, err, myself. - // I.e., always update the recipient player data even if it's nodraw (first person mode) - if ( pInfo->m_pClientEnt == edict() ) - { - return FL_EDICT_ALWAYS; - } - - // when HLTV is connected and spectators press +USE, they - // signal that they are recording a interesting scene - // so transmit these 'cameramans' to the HLTV client - if ( HLTVDirector()->GetCameraMan() == entindex() ) - { - CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); - - Assert( pRecipientEntity->IsPlayer() ); - - CBasePlayer *pRecipientPlayer = static_cast( pRecipientEntity ); - if ( pRecipientPlayer->IsHLTV() ) - { - // HACK force calling RecomputePVSInformation to update PVS data - NetworkProp()->AreaNum(); - return FL_EDICT_ALWAYS; - } - } - - // Transmit for a short time after death so ragdolls can access reliable player data - if ( IsEffectActive( EF_NODRAW ) || ( IsObserver() && ( gpGlobals->curtime - m_flDeathTime > 0.5 ) ) ) - { - return FL_EDICT_DONTSEND; - } - - return BaseClass::ShouldTransmit( pInfo ); -} - - -bool CBasePlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec *pEntityTransmitBits ) const -{ - // Team members shouldn't be adjusted unless friendly fire is on. - if ( !friendlyfire.GetInt() && pPlayer->GetTeamNumber() == GetTeamNumber() ) - return false; - - // If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it. - if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) ) - return false; - - const Vector &vMyOrigin = GetAbsOrigin(); - const Vector &vHisOrigin = pPlayer->GetAbsOrigin(); - - // get max distance player could have moved within max lag compensation time, - // multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value) - float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat(); - - // If the player is within this distance, lag compensate them in case they're running past us. - if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance ) - return true; - - // If their origin is not within a 45 degree cone in front of us, no need to lag compensate. - Vector vForward; - AngleVectors( pCmd->viewangles, &vForward ); - - Vector vDiff = vHisOrigin - vMyOrigin; - VectorNormalize( vDiff ); - - float flCosAngle = 0.707107f; // 45 degree angle - if ( vForward.Dot( vDiff ) < flCosAngle ) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -// Sets the view angles -//----------------------------------------------------------------------------- -void CBasePlayer::SnapEyeAngles( const QAngle &viewAngles ) -{ - pl.v_angle = viewAngles; - pl.fixangle = FIXANGLE_ABSOLUTE; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iSpeed - -// iMax - -// Output : int -//----------------------------------------------------------------------------- -int TrainSpeed(int iSpeed, int iMax) -{ - float fSpeed, fMax; - int iRet = 0; - - fMax = (float)iMax; - fSpeed = iSpeed; - - fSpeed = fSpeed/fMax; - - if (iSpeed < 0) - iRet = TRAIN_BACK; - else if (iSpeed == 0) - iRet = TRAIN_NEUTRAL; - else if (fSpeed < 0.33) - iRet = TRAIN_SLOW; - else if (fSpeed < 0.66) - iRet = TRAIN_MEDIUM; - else - iRet = TRAIN_FAST; - - return iRet; -} - -void CBasePlayer::DeathSound( const CTakeDamageInfo &info ) -{ - // temporarily using pain sounds for death sounds - - // Did we die from falling? - if ( m_bitsDamageType & DMG_FALL ) - { - // They died in the fall. Play a splat sound. - EmitSound( "Player.FallGib" ); - } - else - { - EmitSound( "Player.Death" ); - } - - // play one of the suit death alarms - if ( IsSuitEquipped() ) - { - UTIL_EmitGroupnameSuit(edict(), "HEV_DEAD"); - } -} - -// override takehealth -// bitsDamageType indicates type of damage healed. - -int CBasePlayer::TakeHealth( float flHealth, int bitsDamageType ) -{ - // clear out any damage types we healed. - // UNDONE: generic health should not heal any - // UNDONE: time-based damage - if (m_takedamage) - { - m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); - } - - return BaseClass::TakeHealth (flHealth, bitsDamageType); -} - - -//----------------------------------------------------------------------------- -// Purpose: Draw all overlays (should be implemented in cascade by subclass to add -// any additional non-text overlays) -// Input : -// Output : Current text offset from the top -//----------------------------------------------------------------------------- -void CBasePlayer::DrawDebugGeometryOverlays(void) -{ - // -------------------------------------------------------- - // If in buddha mode and dead draw lines to indicate death - // -------------------------------------------------------- - if ((m_debugOverlays & OVERLAY_BUDDHA_MODE) && m_iHealth == 1) - { - Vector vBodyDir = BodyDirection2D( ); - Vector eyePos = EyePosition() + vBodyDir*10.0; - Vector vUp = Vector(0,0,8); - Vector vSide; - CrossProduct( vBodyDir, vUp, vSide); - NDebugOverlay::Line(eyePos+vSide+vUp, eyePos-vSide-vUp, 255,0,0, false, 0); - NDebugOverlay::Line(eyePos+vSide-vUp, eyePos-vSide+vUp, 255,0,0, false, 0); - } - BaseClass::DrawDebugGeometryOverlays(); -} - -//========================================================= -// TraceAttack -//========================================================= -void CBasePlayer::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr ) -{ - if ( m_takedamage ) - { - CTakeDamageInfo info = inputInfo; - - // -------------------------------------------------- - // If an NPC check if friendly fire is disallowed - // -------------------------------------------------- - CAI_BaseNPC *pNPC = info.GetAttacker()->MyNPCPointer(); - if ( pNPC && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) && pNPC->IRelationType( this ) != D_HT ) - { - return; - } - - // Prevent team damage here so blood doesn't appear - if ( info.GetAttacker()->IsPlayer() ) - { - if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker() ) ) - return; - } - - SetLastHitGroup( ptr->hitgroup ); - - - switch ( ptr->hitgroup ) - { - case HITGROUP_GENERIC: - break; - case HITGROUP_HEAD: - info.ScaleDamage( sk_player_head.GetFloat() ); - break; - case HITGROUP_CHEST: - info.ScaleDamage( sk_player_chest.GetFloat() ); - break; - case HITGROUP_STOMACH: - info.ScaleDamage( sk_player_stomach.GetFloat() ); - break; - case HITGROUP_LEFTARM: - case HITGROUP_RIGHTARM: - info.ScaleDamage( sk_player_arm.GetFloat() ); - break; - case HITGROUP_LEFTLEG: - case HITGROUP_RIGHTLEG: - info.ScaleDamage( sk_player_leg.GetFloat() ); - break; - default: - break; - } - - SpawnBlood(ptr->endpos, vecDir, BloodColor(), info.GetDamage());// a little surface blood. - TraceBleed( info.GetDamage(), vecDir, ptr, info.GetDamageType() ); - AddMultiDamage( info, this ); - } -} - -//------------------------------------------------------------------------------ -// Purpose : Do some kind of damage effect for the type of damage -// Input : -// Output : -//------------------------------------------------------------------------------ -void CBasePlayer::DamageEffect(float flDamage, int fDamageType) -{ - if (fDamageType & DMG_CRUSH) - { - //Red damage indicator - color32 red = {128,0,0,128}; - UTIL_ScreenFade( this, red, 1.0f, 0.1f, FFADE_IN ); - } - else if (fDamageType & DMG_DROWN) - { - //Red damage indicator - color32 blue = {0,0,128,128}; - UTIL_ScreenFade( this, blue, 1.0f, 0.1f, FFADE_IN ); - } - else if (fDamageType & DMG_SLASH) - { - // If slash damage shoot some blood - SpawnBlood(EyePosition(), g_vecAttackDir, BloodColor(), flDamage); - } - else if (fDamageType & DMG_PLASMA) - { - // Blue screen fade - color32 blue = {0,0,255,100}; - UTIL_ScreenFade( this, blue, 0.2, 0.4, FFADE_MODULATE ); - - // Very small screen shake - ViewPunch(QAngle(random->RandomInt(-0.1,0.1), random->RandomInt(-0.1,0.1), random->RandomInt(-0.1,0.1))); - - // Burn sound - EmitSound( "Player.PlasmaDamage" ); - } - else if (fDamageType & DMG_SONIC) - { - // Sonic damage sound - EmitSound( "Player.SonicDamage" ); - } - else if ( fDamageType & DMG_BULLET ) - { - EmitSound( "Flesh.BulletImpact" ); - } -} - -/* - Take some damage. - NOTE: each call to OnTakeDamage with bitsDamageType set to a time-based damage - type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation - etc are implemented with subsequent calls to OnTakeDamage using DMG_GENERIC. -*/ - -// Old values -#define OLD_ARMOR_RATIO 0.2 // Armor Takes 80% of the damage -#define OLD_ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health - -// New values -#define ARMOR_RATIO 0.2 -#define ARMOR_BONUS 1.0 - -//--------------------------------------------------------- -//--------------------------------------------------------- -bool CBasePlayer::ShouldTakeDamageInCommentaryMode( const CTakeDamageInfo &inputInfo ) -{ - // Only ignore damage when we're listening to a commentary node - if ( !IsListeningToCommentary() ) - return true; - - // Allow SetHealth inputs to kill player. - if ( inputInfo.GetInflictor() == this && inputInfo.GetAttacker() == this ) - return true; - - // In commentary, ignore all damage except for falling and leeches - if ( !(inputInfo.GetDamageType() & (DMG_BURN | DMG_PLASMA | DMG_FALL | DMG_CRUSH)) && inputInfo.GetDamageType() != DMG_GENERIC ) - return false; - - // We let DMG_CRUSH pass the check above so that we can check here for stress damage. Deny the CRUSH damage if there is no attacker, - // or if the attacker isn't a BSP model. Therefore, we're allowing any CRUSH damage done by a BSP model. - if ( (inputInfo.GetDamageType() & DMG_CRUSH) && ( inputInfo.GetAttacker() == NULL || !inputInfo.GetAttacker()->IsBSPModel() ) ) - return false; - - return true; -} - -int CBasePlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo ) -{ - // have suit diagnose the problem - ie: report damage type - int bitsDamage = inputInfo.GetDamageType(); - int ffound = true; - int fmajor; - int fcritical; - int fTookDamage; - int ftrivial; - float flRatio; - float flBonus; - float flHealthPrev = m_iHealth; - - CTakeDamageInfo info = inputInfo; - - IServerVehicle *pVehicle = GetVehicle(); - if ( pVehicle ) - { - // Players don't take blast or radiation damage while in vehicles. - // The vehicle has to deal it to him - if ( info.GetDamageType() & (DMG_BLAST|DMG_RADIATION) ) - return 0; - - info.ScaleDamage(pVehicle->DamageModifier(info)); - } - - if ( IsInCommentaryMode() ) - { - if( !ShouldTakeDamageInCommentaryMode( info ) ) - return 0; - } - - if ( GetFlags() & FL_GODMODE ) - return 0; - - if ( m_debugOverlays & OVERLAY_BUDDHA_MODE ) - { - if ((m_iHealth - info.GetDamage()) <= 0) - { - m_iHealth = 1; - return 0; - } - } - - // Early out if there's no damage - if ( !info.GetDamage() ) - return 0; - - if( old_armor.GetBool() ) - { - flBonus = OLD_ARMOR_BONUS; - flRatio = OLD_ARMOR_RATIO; - } - else - { - flBonus = ARMOR_BONUS; - flRatio = ARMOR_RATIO; - } - - if ( ( info.GetDamageType() & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) - { - // blasts damage armor more. - flBonus *= 2; - } - - // Already dead - if ( !IsAlive() ) - return 0; - // go take the damage first - - - if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker() ) ) - { - // Refuse the damage - return 0; - } - - // keep track of amount of damage last sustained - m_lastDamageAmount = info.GetDamage(); - - // Armor. - if (m_ArmorValue && !(info.GetDamageType() & (DMG_FALL | DMG_DROWN | DMG_POISON | DMG_RADIATION)) )// armor doesn't protect against fall or drown damage! - { - float flNew = info.GetDamage() * flRatio; - - float flArmor; - - flArmor = (info.GetDamage() - flNew) * flBonus; - - if( !old_armor.GetBool() ) - { - if( flArmor < 1.0 ) - { - flArmor = 1.0; - } - } - - // Does this use more armor than we have? - if (flArmor > m_ArmorValue) - { - flArmor = m_ArmorValue; - flArmor *= (1/flBonus); - flNew = info.GetDamage() - flArmor; - m_DmgSave = m_ArmorValue; - m_ArmorValue = 0; - } - else - { - m_DmgSave = flArmor; - m_ArmorValue -= flArmor; - } - - info.SetDamage( flNew ); - } - - // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that - // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) - - // NOTENOTE: jdw - We are now capable of retaining the matissa of this damage value and deferring its application - - // info.SetDamage( (int)info.GetDamage() ); - - // Call up to the base class - fTookDamage = BaseClass::OnTakeDamage( info ); - - // Early out if the base class took no damage - if ( !fTookDamage ) - return 0; - - // 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 ( info.GetInflictor() && info.GetInflictor()->edict() ) - m_DmgOrigin = info.GetInflictor()->GetAbsOrigin(); - - m_DmgTake += (int)info.GetDamage(); - - // reset damage time countdown for each type of time based damage player just sustained - - for (int i = 0; i < CDMG_TIMEBASED; i++) - { - if (info.GetDamageType() & (DMG_PARALYZE << i)) - { - m_rgbTimeBasedDamage[i] = 0; - } - } - - // Display any effect associate with this damage type - DamageEffect(info.GetDamage(),bitsDamage); - - // how bad is it, doc? - ftrivial = (m_iHealth > 75 || m_lastDamageAmount < 5); - fmajor = (m_lastDamageAmount > 25); - fcritical = (m_iHealth < 30); - - // handle all bits set in this damage message, - // let the suit give player the diagnosis - - // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash ) - - // UNDONE: still need to record damage and heal messages for the following types - - // DMG_BURN - // DMG_FREEZE - // DMG_BLAST - // DMG_SHOCK - - m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client - m_bitsHUDDamage = -1; // make sure the damage bits get resent - - while (fTookDamage && (!ftrivial || (bitsDamage & DMG_TIMEBASED)) && ffound && bitsDamage) - { - ffound = false; - - if (bitsDamage & DMG_CLUB) - { - if (fmajor) - SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture - bitsDamage &= ~DMG_CLUB; - ffound = true; - } - if (bitsDamage & (DMG_FALL | DMG_CRUSH)) - { - if (fmajor) - SetSuitUpdate("!HEV_DMG5", false, SUIT_NEXT_IN_30SEC); // major fracture - else - SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture - - bitsDamage &= ~(DMG_FALL | DMG_CRUSH); - ffound = true; - } - - if (bitsDamage & DMG_BULLET) - { - if (m_lastDamageAmount > 5) - SetSuitUpdate("!HEV_DMG6", false, SUIT_NEXT_IN_30SEC); // blood loss detected - //else - // SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration - - bitsDamage &= ~DMG_BULLET; - ffound = true; - } - - if (bitsDamage & DMG_SLASH) - { - if (fmajor) - SetSuitUpdate("!HEV_DMG1", false, SUIT_NEXT_IN_30SEC); // major laceration - else - SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration - - bitsDamage &= ~DMG_SLASH; - ffound = true; - } - - if (bitsDamage & DMG_SONIC) - { - if (fmajor) - SetSuitUpdate("!HEV_DMG2", false, SUIT_NEXT_IN_1MIN); // internal bleeding - bitsDamage &= ~DMG_SONIC; - ffound = true; - } - - if (bitsDamage & (DMG_POISON | DMG_PARALYZE)) - { - if (bitsDamage & DMG_POISON) - { - m_nPoisonDmg += info.GetDamage(); - m_tbdPrev = gpGlobals->curtime; - m_rgbTimeBasedDamage[itbd_PoisonRecover] = 0; - } - - SetSuitUpdate("!HEV_DMG3", false, SUIT_NEXT_IN_1MIN); // blood toxins detected - bitsDamage &= ~( DMG_POISON | DMG_PARALYZE ); - ffound = true; - } - - if (bitsDamage & DMG_ACID) - { - SetSuitUpdate("!HEV_DET1", false, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected - bitsDamage &= ~DMG_ACID; - ffound = true; - } - - if (bitsDamage & DMG_NERVEGAS) - { - SetSuitUpdate("!HEV_DET0", false, SUIT_NEXT_IN_1MIN); // biohazard detected - bitsDamage &= ~DMG_NERVEGAS; - ffound = true; - } - - if (bitsDamage & DMG_RADIATION) - { - SetSuitUpdate("!HEV_DET2", false, SUIT_NEXT_IN_1MIN); // radiation detected - bitsDamage &= ~DMG_RADIATION; - ffound = true; - } - if (bitsDamage & DMG_SHOCK) - { - bitsDamage &= ~DMG_SHOCK; - ffound = true; - } - } - - m_Local.m_vecPunchAngle.SetX( -2 ); - - if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75) - { - // first time we take major damage... - // turn automedic on if not on - SetSuitUpdate("!HEV_MED1", false, SUIT_NEXT_IN_30MIN); // automedic on - - // give morphine shot if not given recently - SetSuitUpdate("!HEV_HEAL7", false, SUIT_NEXT_IN_30MIN); // morphine shot - } - - if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75) - { - - // already took major damage, now it's critical... - if (m_iHealth < 6) - SetSuitUpdate("!HEV_HLTH3", false, SUIT_NEXT_IN_10MIN); // near death - else if (m_iHealth < 20) - SetSuitUpdate("!HEV_HLTH2", false, SUIT_NEXT_IN_10MIN); // health critical - - // give critical health warnings - if (!random->RandomInt(0,3) && flHealthPrev < 50) - SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention - } - - // if we're taking time based damage, warn about its continuing effects - if (fTookDamage && (info.GetDamageType() & DMG_TIMEBASED) && flHealthPrev < 75) - { - if (flHealthPrev < 50) - { - if (!random->RandomInt(0,3)) - SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention - } - else - SetSuitUpdate("!HEV_HLTH1", false, SUIT_NEXT_IN_10MIN); // health dropping - } - - // Do special explosion damage effect - if ( bitsDamage & DMG_BLAST ) - { - OnDamagedByExplosion( info ); - } - - return fTookDamage; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &info - -// damageAmount - -//----------------------------------------------------------------------------- -#define MIN_SHOCK_AND_CONFUSION_DAMAGE 30.0f -#define MIN_EAR_RINGING_DISTANCE 240.0f // 20 feet - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &info - -//----------------------------------------------------------------------------- -void CBasePlayer::OnDamagedByExplosion( const CTakeDamageInfo &info ) -{ - float lastDamage = info.GetDamage(); - - float distanceFromPlayer = 9999.0f; - - CBaseEntity *inflictor = info.GetInflictor(); - if ( inflictor ) - { - Vector delta = GetAbsOrigin() - inflictor->GetAbsOrigin(); - distanceFromPlayer = delta.Length(); - } - - bool ear_ringing = distanceFromPlayer < MIN_EAR_RINGING_DISTANCE ? true : false; - bool shock = lastDamage >= MIN_SHOCK_AND_CONFUSION_DAMAGE; - - if ( !shock && !ear_ringing ) - return; - - int effect = shock ? - random->RandomInt( 35, 37 ) : - random->RandomInt( 32, 34 ); - - CSingleUserRecipientFilter user( this ); - enginesound->SetPlayerDSP( user, effect, false ); -} - -//========================================================= -// PackDeadPlayerItems - call this when a player dies to -// pack up the appropriate weapons and ammo items, and to -// destroy anything that shouldn't be packed. -// -// This is pretty brute force :( -//========================================================= -void CBasePlayer::PackDeadPlayerItems( void ) -{ - int iWeaponRules; - int iAmmoRules; - int i; - CBaseCombatWeapon *rgpPackWeapons[ 20 ];// 20 hardcoded for now. How to determine exactly how many weapons we have? - int iPackAmmo[ MAX_AMMO_SLOTS + 1]; - int iPW = 0;// index into packweapons array - int iPA = 0;// index into packammo array - - memset(rgpPackWeapons, NULL, sizeof(rgpPackWeapons) ); - memset(iPackAmmo, -1, sizeof(iPackAmmo) ); - - // get the game rules - iWeaponRules = g_pGameRules->DeadPlayerWeapons( this ); - iAmmoRules = g_pGameRules->DeadPlayerAmmo( this ); - - if ( iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO ) - { - // nothing to pack. Remove the weapons and return. Don't call create on the box! - RemoveAllItems( true ); - return; - } - -// go through all of the weapons and make a list of the ones to pack - for ( i = 0 ; i < WeaponCount() ; i++ ) - { - // there's a weapon here. Should I pack it? - CBaseCombatWeapon *pPlayerItem = GetWeapon( i ); - if ( pPlayerItem ) - { - switch( iWeaponRules ) - { - case GR_PLR_DROP_GUN_ACTIVE: - if ( GetActiveWeapon() && pPlayerItem == GetActiveWeapon() ) - { - // this is the active item. Pack it. - rgpPackWeapons[ iPW++ ] = pPlayerItem; - } - break; - - case GR_PLR_DROP_GUN_ALL: - rgpPackWeapons[ iPW++ ] = pPlayerItem; - break; - - default: - break; - } - } - } - -// now go through ammo and make a list of which types to pack. - if ( iAmmoRules != GR_PLR_DROP_AMMO_NO ) - { - for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) - { - if ( GetAmmoCount( i ) > 0 ) - { - // player has some ammo of this type. - switch ( iAmmoRules ) - { - case GR_PLR_DROP_AMMO_ALL: - iPackAmmo[ iPA++ ] = i; - break; - - case GR_PLR_DROP_AMMO_ACTIVE: - // WEAPONTODO: Make this work - /* - if ( GetActiveWeapon() && i == GetActiveWeapon()->m_iPrimaryAmmoType ) - { - // this is the primary ammo type for the active weapon - iPackAmmo[ iPA++ ] = i; - } - else if ( GetActiveWeapon() && i == GetActiveWeapon()->m_iSecondaryAmmoType ) - { - // this is the secondary ammo type for the active weapon - iPackAmmo[ iPA++ ] = i; - } - */ - break; - - default: - break; - } - } - } - } - - RemoveAllItems( true );// now strip off everything that wasn't handled by the code above. -} - -void CBasePlayer::RemoveAllItems( bool removeSuit ) -{ - if (GetActiveWeapon()) - { - ResetAutoaim( ); - GetActiveWeapon()->Holster( ); - } - - Weapon_SetLast( NULL ); - RemoveAllWeapons(); - RemoveAllAmmo(); - - if ( removeSuit ) - { - RemoveSuit(); - } - - UpdateClientData(); -} - -bool CBasePlayer::IsDead() const -{ - return m_lifeState == LIFE_DEAD; -} - -static float DamageForce( const Vector &size, float damage ) -{ - float force = damage * ((32 * 32 * 72.0) / (size.x * size.y * size.z)) * 5; - - if ( force > 1000.0) - { - force = 1000.0; - } - - return force; -} - - -int CBasePlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info ) -{ - // set damage type sustained - m_bitsDamageType |= info.GetDamageType(); - - if ( !BaseClass::OnTakeDamage_Alive( info ) ) - return 0; - - CBaseEntity * attacker = info.GetAttacker(); - - if ( !attacker ) - return 0; - - Vector vecDir = vec3_origin; - if ( info.GetInflictor() ) - { - vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter(); - VectorNormalize( vecDir ); - } - - if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK) && - ( !attacker->IsSolidFlagSet(FSOLID_TRIGGER)) ) - { - Vector force = vecDir * -DamageForce( WorldAlignSize(), info.GetBaseDamage() ); - if ( force.z > 250.0f ) - { - force.z = 250.0f; - } - ApplyAbsVelocityImpulse( force ); - } - - // fire global game event - - IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" ); - if ( event ) - { - event->SetInt("userid", GetUserID() ); - event->SetInt("health", max(0, m_iHealth) ); - event->SetInt("priority", 5 ); // HLTV event priority, not transmitted - - if ( attacker->IsPlayer() ) - { - CBasePlayer *player = ToBasePlayer( attacker ); - event->SetInt("attacker", player->GetUserID() ); // hurt by other player - } - else - { - event->SetInt("attacker", 0 ); // hurt by "world" - } - - gameeventmanager->FireEvent( event ); - } - - // Insert a combat sound so that nearby NPCs hear battle - if ( attacker->IsNPC() ) - { - CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 512, 0.5, this );//<>//magic number - } - - return 1; -} - - -void CBasePlayer::Event_Killed( const CTakeDamageInfo &info ) -{ - CSound *pSound; - - g_pGameRules->PlayerKilled( this, info ); - - - RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE ); - - ClearUseEntity(); - - // this client isn't going to be thinking for a while, so reset the sound until they respawn - pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); - { - if ( pSound ) - { - pSound->Reset(); - } - } - - // don't let the status bar glitch for players with <0 health. - if (m_iHealth < -99) - { - m_iHealth = 0; - } - - // holster the current weapon - if ( GetActiveWeapon() ) - { - GetActiveWeapon()->Holster(); - } - - SetAnimation( PLAYER_DIE ); - - SetViewOffset( VEC_DEAD_VIEWHEIGHT ); - m_lifeState = LIFE_DYING; - - pl.deadflag = true; - AddSolidFlags( FSOLID_NOT_SOLID ); - // force contact points to get flushed if no longer valid - // UNDONE: Always do this on RecheckCollisionFilter() ? - IPhysicsObject *pObject = VPhysicsGetObject(); - if ( pObject ) - { - pObject->RecheckContactPoints(); - } - - SetMoveType( MOVETYPE_FLYGRAVITY ); - SetGroundEntity( NULL ); - - // clear out the suit message cache so we don't keep chattering - SetSuitUpdate(NULL, false, 0); - - // reset FOV - SetFOV( this, 0 ); - - if ( FlashlightIsOn() ) - { - FlashlightTurnOff(); - } - - m_flDeathTime = gpGlobals->curtime; - - // only count alive players - if (m_lastNavArea) - { - m_lastNavArea->DecrementPlayerCount( GetTeamNumber() ); - m_lastNavArea = NULL; - } - - BaseClass::Event_Killed( info ); -} - -void CBasePlayer::Event_Dying() -{ - // NOT GIBBED, RUN THIS CODE - - CTakeDamageInfo info; - DeathSound( info ); - - // The dead body rolls out of the vehicle. - if ( IsInAVehicle() ) - { - LeaveVehicle(); - } - - QAngle angles = GetLocalAngles(); - - angles.x = 0; - angles.z = 0; - - SetLocalAngles( angles ); - - SetThink(&CBasePlayer::PlayerDeathThink); - SetNextThink( gpGlobals->curtime + 0.1f ); - BaseClass::Event_Dying(); -} - - -// Set the activity based on an event or current state -void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) -{ - int animDesired; - char szAnim[64]; - - float speed; - - speed = GetAbsVelocity().Length2D(); - - if (GetFlags() & (FL_FROZEN|FL_ATCONTROLS)) - { - speed = 0; - playerAnim = PLAYER_IDLE; - } - - Activity idealActivity = ACT_WALK;// TEMP!!!!! - - // This could stand to be redone. Why is playerAnim abstracted from activity? (sjb) - if (playerAnim == PLAYER_JUMP) - { - idealActivity = ACT_HOP; - } - else if (playerAnim == PLAYER_SUPERJUMP) - { - idealActivity = ACT_LEAP; - } - else if (playerAnim == PLAYER_DIE) - { - if ( m_lifeState == LIFE_ALIVE ) - { - idealActivity = GetDeathActivity(); - } - } - else if (playerAnim == PLAYER_ATTACK1) - { - if ( m_Activity == ACT_HOVER || - m_Activity == ACT_SWIM || - m_Activity == ACT_HOP || - m_Activity == ACT_LEAP || - m_Activity == ACT_DIESIMPLE ) - { - idealActivity = m_Activity; - } - else - { - idealActivity = ACT_RANGE_ATTACK1; - } - } - else if (playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK) - { - if ( !( GetFlags() & FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping - { - idealActivity = m_Activity; - } - else if ( GetWaterLevel() > 1 ) - { - if ( speed == 0 ) - idealActivity = ACT_HOVER; - else - idealActivity = ACT_SWIM; - } - else - { - idealActivity = ACT_WALK; - } - } - - - if (idealActivity == ACT_RANGE_ATTACK1) - { - if ( GetFlags() & FL_DUCKING ) // crouching - { - Q_strncpy( szAnim, "crouch_shoot_" ,sizeof(szAnim)); - } - else - { - Q_strncpy( szAnim, "ref_shoot_" ,sizeof(szAnim)); - } - Q_strncat( szAnim, m_szAnimExtension ,sizeof(szAnim), COPY_ALL_CHARACTERS ); - animDesired = LookupSequence( szAnim ); - if (animDesired == -1) - animDesired = 0; - - if ( GetSequence() != animDesired || !SequenceLoops() ) - { - SetCycle( 0 ); - } - - // Tracker 24588: In single player when firing own weapon this causes eye and punchangle to jitter - //if (!SequenceLoops()) - //{ - // AddEffects( EF_NOINTERP ); - //} - - SetActivity( idealActivity ); - ResetSequence( animDesired ); - } - else if (idealActivity == ACT_WALK) - { - if (GetActivity() != ACT_RANGE_ATTACK1 || IsActivityFinished()) - { - if ( GetFlags() & FL_DUCKING ) // crouching - { - Q_strncpy( szAnim, "crouch_aim_" ,sizeof(szAnim)); - } - else - { - Q_strncpy( szAnim, "ref_aim_" ,sizeof(szAnim)); - } - Q_strncat( szAnim, m_szAnimExtension,sizeof(szAnim), COPY_ALL_CHARACTERS ); - animDesired = LookupSequence( szAnim ); - if (animDesired == -1) - animDesired = 0; - SetActivity( ACT_WALK ); - } - else - { - animDesired = GetSequence(); - } - } - else - { - if ( GetActivity() == idealActivity) - return; - - SetActivity( idealActivity ); - - animDesired = SelectWeightedSequence( m_Activity ); - - // Already using the desired animation? - if (GetSequence() == animDesired) - return; - - ResetSequence( animDesired ); - SetCycle( 0 ); - return; - } - - // Already using the desired animation? - if (GetSequence() == animDesired) - return; - - //Msg( "Set animation to %d\n", animDesired ); - // Reset to first frame of desired animation - ResetSequence( animDesired ); - SetCycle( 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: data accessor -// ensure that for every emitsound there is a matching stopsound -//----------------------------------------------------------------------------- -void CBasePlayer::SetPlayerUnderwater( bool state ) -{ - if ( m_bPlayerUnderwater != state ) - { - m_bPlayerUnderwater = state; - - if ( state ) - EmitSound( "Player.AmbientUnderWater" ); - else - StopSound( "Player.AmbientUnderWater" ); - } -} - -/* -=========== -WaterMove -============ -*/ -#ifdef HL2_DLL - -// test for HL2 drowning damage increase (aux power used instead) -#define AIRTIME 7 // lung full of air lasts this many seconds -#define DROWNING_DAMAGE_INITIAL 10 -#define DROWNING_DAMAGE_MAX 10 - -#else - -#define AIRTIME 12 // lung full of air lasts this many seconds -#define DROWNING_DAMAGE_INITIAL 2 -#define DROWNING_DAMAGE_MAX 5 - -#endif - -void CBasePlayer::WaterMove() -{ - int air; - - if ( ( GetMoveType() == MOVETYPE_NOCLIP ) && !GetMoveParent() ) - { - m_AirFinished = gpGlobals->curtime + AIRTIME; - return; - } - - if ( m_iHealth < 0 || !IsAlive() ) - { - if ( GetWaterLevel() < WL_Eyes ) - { - if ( IsPlayerUnderwater() ) - { - SetPlayerUnderwater( false ); - } - } - else if ( GetWaterLevel() < WL_Waist ) - { - if ( GetWaterLevel() == 0 ) - { - if ( GetFlags() & FL_INWATER ) - { - RemoveFlag( FL_INWATER ); - } - return; - } - } - else if ( GetWaterLevel() > WL_Waist ) - { - if ( IsPlayerUnderwater() == false ) - { - SetPlayerUnderwater( true ); - } - return; - } - return; - } - - // waterlevel 0 - not in water (WL_NotInWater) - // waterlevel 1 - feet in water (WL_Feet) - // waterlevel 2 - waist in water (WL_Waist) - // waterlevel 3 - head in water (WL_Eyes) - - if (GetWaterLevel() != WL_Eyes || CanBreatheUnderwater()) - { - // not underwater - - // play 'up for air' sound - - if (m_AirFinished < gpGlobals->curtime) - { - EmitSound( "Player.DrownStart" ); - } - - m_AirFinished = gpGlobals->curtime + AIRTIME; - m_nDrownDmgRate = DROWNING_DAMAGE_INITIAL; - - // if we took drowning damage, give it back slowly - if (m_idrowndmg > m_idrownrestored) - { - // set drowning damage bit. hack - dmg_drownrecover actually - // makes the time based damage code 'give back' health over time. - // make sure counter is cleared so we start count correctly. - - // NOTE: this actually causes the count to continue restarting - // until all drowning damage is healed. - - m_bitsDamageType |= DMG_DROWNRECOVER; - m_bitsDamageType &= ~DMG_DROWN; - m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; - } - - } - else - { // fully under water - // stop restoring damage while underwater - m_bitsDamageType &= ~DMG_DROWNRECOVER; - m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; - - if (m_AirFinished < gpGlobals->curtime && !(GetFlags() & FL_GODMODE) ) // drown! - { - if (m_PainFinished < gpGlobals->curtime) - { - // take drowning damage - m_nDrownDmgRate += 1; - if (m_nDrownDmgRate > DROWNING_DAMAGE_MAX) - { - m_nDrownDmgRate = DROWNING_DAMAGE_MAX; - } - - OnTakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), m_nDrownDmgRate, DMG_DROWN ) ); - m_PainFinished = gpGlobals->curtime + 1; - - // track drowning damage, give it back when - // player finally takes a breath - m_idrowndmg += m_nDrownDmgRate; - } - } - else - { - m_bitsDamageType &= ~DMG_DROWN; - } - } - - if ( GetWaterLevel() < WL_Eyes ) - { - if ( IsPlayerUnderwater() ) - { - SetPlayerUnderwater( false ); - } - } - else if ( GetWaterLevel() < WL_Waist ) - { - if ( GetWaterLevel() == 0 ) - { - if ( GetFlags() & FL_INWATER ) - { - EmitSound( "Player.Wade" ); - RemoveFlag( FL_INWATER ); - } - return; - } - } - else if ( GetWaterLevel() > WL_Waist ) - { - if ( IsPlayerUnderwater() == false ) - { - SetPlayerUnderwater( true ); - } - return; - } - - // make bubbles - - air = (int)( m_AirFinished - gpGlobals->curtime ); - -#if 0 - if (GetWaterType() == CONTENT_LAVA) // do damage - { - if (m_flDamageTime < gpGlobals->curtime) - { - OnTakeDamage( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 10 * GetWaterLevel(), DMG_BURN); - } - } - else if (GetWaterType() == CONTENT_SLIME) // do damage - { - m_flDamageTime = gpGlobals->curtime + 1; - OnTakeDamage(GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 4 * GetWaterLevel(), DMG_ACID); - } -#endif - - if (!(GetFlags() & FL_INWATER)) - { - // player enter water sound - if (GetWaterType() == CONTENTS_WATER) - { - EmitSound( "Player.Wade" ); - } - - AddFlag( FL_INWATER ); - } -} - - -// true if the player is attached to a ladder -bool CBasePlayer::IsOnLadder( void ) -{ - return (GetMoveType() == MOVETYPE_LADDER); -} - - -float CBasePlayer::GetWaterJumpTime() const -{ - return m_flWaterJumpTime; -} - -void CBasePlayer::SetWaterJumpTime( float flWaterJumpTime ) -{ - m_flWaterJumpTime = flWaterJumpTime; -} - -float CBasePlayer::GetSwimSoundTime( void ) const -{ - return m_flSwimSoundTime; -} - -void CBasePlayer::SetSwimSoundTime( float flSwimSoundTime ) -{ - m_flSwimSoundTime = flSwimSoundTime; -} - -void CBasePlayer::ShowViewPortPanel( const char * name, bool bShow, KeyValues *data ) -{ - CSingleUserRecipientFilter filter( this ); - filter.MakeReliable(); - - int count = 0; - KeyValues *subkey = NULL; - - if ( data ) - { - subkey = data->GetFirstSubKey(); - while ( subkey ) - { - count++; subkey = subkey->GetNextKey(); - } - - subkey = data->GetFirstSubKey(); // reset - } - - UserMessageBegin( filter, "VGUIMenu" ); - WRITE_STRING( name ); // menu name - WRITE_BYTE( bShow?1:0 ); - WRITE_BYTE( count ); - - // write additional data (be carefull not more than 192 bytes!) - while ( subkey ) - { - WRITE_STRING( subkey->GetName() ); - WRITE_STRING( subkey->GetString() ); - subkey = subkey->GetNextKey(); - } - MessageEnd(); -} - - -void CBasePlayer::PlayerDeathThink(void) -{ - float flForward; - - SetNextThink( gpGlobals->curtime + 0.1f ); - - if (GetFlags() & FL_ONGROUND) - { - flForward = GetAbsVelocity().Length() - 20; - if (flForward <= 0) - { - SetAbsVelocity( vec3_origin ); - } - else - { - Vector vecNewVelocity = GetAbsVelocity(); - VectorNormalize( vecNewVelocity ); - vecNewVelocity *= flForward; - SetAbsVelocity( vecNewVelocity ); - } - } - - if ( HasWeapons() ) - { - // we drop the guns here because weapons that have an area effect and can kill their user - // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the - // player class sometimes is freed. It's safer to manipulate the weapons once we know - // we aren't calling into any of their code anymore through the player pointer. - PackDeadPlayerItems(); - } - - if (GetModelIndex() && (!IsSequenceFinished()) && (m_lifeState == LIFE_DYING)) - { - StudioFrameAdvance( ); - - m_iRespawnFrames++; - if ( m_iRespawnFrames < 60 ) // animations should be no longer than this - return; - } - - if (m_lifeState == LIFE_DYING) - m_lifeState = LIFE_DEAD; - - StopAnimation(); - - AddEffects( EF_NOINTERP ); - m_flPlaybackRate = 0.0; - - int fAnyButtonDown = (m_nButtons & ~IN_SCORE); - - // Strip out the duck key from this check if it's toggled - if ( (fAnyButtonDown & IN_DUCK) && GetToggledDuckState()) - { - fAnyButtonDown &= ~IN_DUCK; - } - - // wait for all buttons released - if (m_lifeState == LIFE_DEAD) - { - if (fAnyButtonDown) - return; - - if ( g_pGameRules->FPlayerCanRespawn( this ) ) - { - m_lifeState = LIFE_RESPAWNABLE; - } - - return; - } - -// if the player has been dead for one second longer than allowed by forcerespawn, -// forcerespawn isn't on. Send the player off to an intermission camera until they -// choose to respawn. - if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->curtime > (m_flDeathTime + DEATH_ANIMATION_TIME) ) && !IsObserver() ) - { - // go to dead camera. - StartObserverMode( m_iObserverLastMode ); - } - -// wait for any button down, or mp_forcerespawn is set and the respawn time is up - if (!fAnyButtonDown - && !( g_pGameRules->IsMultiplayer() && forcerespawn.GetInt() > 0 && (gpGlobals->curtime > (m_flDeathTime + 5))) ) - return; - - m_nButtons = 0; - m_iRespawnFrames = 0; - - //Msg( "Respawn\n"); - - respawn( this, !IsObserver() );// don't copy a corpse if we're in deathcam. - SetNextThink( TICK_NEVER_THINK ); -} - -/* - -//========================================================= -// StartDeathCam - find an intermission spot and send the -// player off into observer mode -//========================================================= -void CBasePlayer::StartDeathCam( void ) -{ - CBaseEntity *pSpot, *pNewSpot; - int iRand; - - if ( GetViewOffset() == vec3_origin ) - { - // don't accept subsequent attempts to StartDeathCam() - return; - } - - pSpot = gEntList.FindEntityByClassname( NULL, "info_intermission"); - - if ( pSpot ) - { - // at least one intermission spot in the world. - iRand = random->RandomInt( 0, 3 ); - - while ( iRand > 0 ) - { - pNewSpot = gEntList.FindEntityByClassname( pSpot, "info_intermission"); - - if ( pNewSpot ) - { - pSpot = pNewSpot; - } - - iRand--; - } - - CreateCorpse(); - StartObserverMode( pSpot->GetAbsOrigin(), pSpot->GetAbsAngles() ); - } - else - { - // no intermission spot. Push them up in the air, looking down at their corpse - trace_t tr; - - CreateCorpse(); - - UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 128 ), - MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); - QAngle angles; - VectorAngles( GetAbsOrigin() - tr.endpos, angles ); - StartObserverMode( tr.endpos, angles ); - return; - } -} */ - -void CBasePlayer::StopObserverMode() -{ - m_bForcedObserverMode = false; - m_afPhysicsFlags &= ~PFLAG_OBSERVER; - - if ( m_iObserverMode == OBS_MODE_NONE ) - return; - - if ( m_iObserverMode > OBS_MODE_DEATHCAM ) - { - m_iObserverLastMode = m_iObserverMode; - } - - m_iObserverMode.Set( OBS_MODE_NONE ); - - ShowViewPortPanel( "specmenu", false ); - ShowViewPortPanel( "specgui", false ); - ShowViewPortPanel( "overview", false ); -} - -bool CBasePlayer::StartObserverMode(int mode) -{ - if ( !IsObserver() ) - { - // set position to last view offset - SetAbsOrigin( GetAbsOrigin() + GetViewOffset() ); - SetViewOffset( vec3_origin ); - } - - Assert( mode > OBS_MODE_NONE ); - - m_afPhysicsFlags |= PFLAG_OBSERVER; - - // Holster weapon immediately, to allow it to cleanup - if ( GetActiveWeapon() ) - GetActiveWeapon()->Holster(); - - // clear out the suit message cache so we don't keep chattering - SetSuitUpdate(NULL, FALSE, 0); - - SetGroundEntity( (CBaseEntity *)NULL ); - - RemoveFlag( FL_DUCKING ); - - AddSolidFlags( FSOLID_NOT_SOLID ); - - SetObserverMode( mode ); - - ShowViewPortPanel( "specgui" ); - - // Setup flags - m_Local.m_iHideHUD = HIDEHUD_HEALTH; - m_takedamage = DAMAGE_NO; - - //Don't set the player to EF_NODRAW - the client can determine - //whether to draw the player or not with ShouldDraw - //AddEffects( EF_NODRAW ); - - m_iHealth = 1; - m_lifeState = LIFE_DEAD; // Can't be dead, otherwise movement doesn't work right. - pl.deadflag = true; - - return true; -} - -bool CBasePlayer::SetObserverMode(int mode ) -{ - if ( mode < OBS_MODE_NONE || mode > OBS_MODE_ROAMING ) - return false; - - - // check mp_forcecamera settings for dead players - if ( mode > OBS_MODE_FIXED && GetTeamNumber() > TEAM_SPECTATOR ) - { - switch ( mp_forcecamera.GetInt() ) - { - case OBS_ALLOW_ALL : break; // no restrictions - case OBS_ALLOW_TEAM : mode = OBS_MODE_IN_EYE; break; - case OBS_ALLOW_NONE : mode = OBS_MODE_FIXED; break; // donw't allow anything - } - } - - if ( m_iObserverMode > OBS_MODE_DEATHCAM ) - { - // remember mode if we were really spectating before - m_iObserverLastMode = m_iObserverMode; - } - - m_iObserverMode = mode; - - switch ( mode ) - { - case OBS_MODE_NONE: - case OBS_MODE_FIXED : - case OBS_MODE_DEATHCAM : - SetFOV( this, 0 ); // Reset FOV - SetViewOffset( vec3_origin ); - SetMoveType( MOVETYPE_NONE ); - break; - - case OBS_MODE_CHASE : - case OBS_MODE_IN_EYE : - // udpate FOV and viewmodels - SetObserverTarget( m_hObserverTarget ); - SetMoveType( MOVETYPE_OBSERVER ); - break; - - case OBS_MODE_ROAMING : - SetFOV( this, 0 ); // Reset FOV - SetObserverTarget( m_hObserverTarget ); - SetViewOffset( vec3_origin ); - SetMoveType( MOVETYPE_OBSERVER ); - break; - - } - - CheckObserverSettings(); - - return true; -} - -int CBasePlayer::GetObserverMode() -{ - return m_iObserverMode; -} - -void CBasePlayer::ForceObserverMode(int mode) -{ - int tempMode = OBS_MODE_ROAMING; - - if ( m_iObserverMode == mode ) - return; - - // don't change last mode if already in forced mode - - if ( m_bForcedObserverMode ) - { - tempMode = m_iObserverLastMode; - } - - SetObserverMode( mode ); - - if ( m_bForcedObserverMode ) - { - m_iObserverLastMode = tempMode; - } - - m_bForcedObserverMode = true; -} - -void CBasePlayer::CheckObserverSettings() -{ - // check if we are in forced mode and may go back to old mode - if ( m_bForcedObserverMode ) - { - CBaseEntity * target = m_hObserverTarget; - - if ( !IsValidObserverTarget(target) ) - { - // if old target is still invalid, try to find valid one - target = FindNextObserverTarget( false ); - } - - if ( target ) - { - // we found a valid target - m_bForcedObserverMode = false; // disable force mode - SetObserverMode( m_iObserverLastMode ); // switch to last mode - SetObserverTarget( target ); // goto target - - // TODO check for HUD icons - return; - } - else - { - // else stay in forced mode, no changes - return; - } - } - - // make sure our last mode is valid - if ( m_iObserverLastMode < OBS_MODE_FIXED ) - { - m_iObserverLastMode = OBS_MODE_ROAMING; - } - - // check if our spectating target is still a valid one - - if ( m_iObserverMode == OBS_MODE_IN_EYE || m_iObserverMode == OBS_MODE_CHASE ) - { - if ( !IsValidObserverTarget( m_hObserverTarget.Get() ) ) - { - // our target is not valid, try to find new target - CBaseEntity * target = FindNextObserverTarget( false ); - if ( target ) - { - // switch to new valid target - SetObserverTarget( target ); - } - else - { - // couldn't find new target, switch to temporary mode - if ( mp_forcecamera.GetInt() == OBS_ALLOW_ALL ) - { - // let player roam around - ForceObserverMode( OBS_MODE_ROAMING ); - } - else - { - // fix player view right where it is - ForceObserverMode( OBS_MODE_FIXED ); - m_hObserverTarget.Set( NULL ); // no traget to follow - } - } - } - - CBasePlayer *target = ToBasePlayer( m_hObserverTarget.Get() ); - - // for ineye mode we have to copy several data to see exactly the same - - if ( target && m_iObserverMode == OBS_MODE_IN_EYE ) - { - int flagMask = FL_ONGROUND | FL_DUCKING ; - - int flags = target->GetFlags() & flagMask; - - if ( (GetFlags() & flagMask) != flags ) - { - flags |= GetFlags() & (~flagMask); // keep other flags - ClearFlags(); - AddFlag( flags ); - } - - if ( target->GetViewOffset() != GetViewOffset() ) - { - SetViewOffset( target->GetViewOffset() ); - } - } - } -} - -bool CBasePlayer::StartReplayMode( float fDelay, float fDuration, int iEntity ) -{ - if ( ( sv_maxreplay == NULL ) || ( sv_maxreplay->GetFloat() <= 0 ) ) - return false; - - m_fDelay = fDelay; - m_fReplayEnd = gpGlobals->curtime + fDuration; - m_iReplayEntity = iEntity; - - return true; -} - -void CBasePlayer::StopReplayMode() -{ - m_fDelay = 0.0f; - m_fReplayEnd = -1; - m_iReplayEntity = 0; -} - -int CBasePlayer::GetDelayTicks() -{ - if ( m_fReplayEnd > gpGlobals->curtime ) - { - return TIME_TO_TICKS( m_fDelay ); - } - else - { - if ( m_fDelay > 0.0f ) - StopReplayMode(); - - return 0; - } -} - -int CBasePlayer::GetReplayEntity() -{ - return m_iReplayEntity; -} - -CBaseEntity * CBasePlayer::GetObserverTarget() -{ - return m_hObserverTarget.Get(); -} - -void CBasePlayer::ObserverUse( bool bIsPressed ) -{ -#ifndef _XBOX - if ( !HLTVDirector()->IsActive() ) - return; - - if ( GetTeamNumber() != TEAM_SPECTATOR ) - return; // only pure spectators can play cameraman - - if ( !bIsPressed ) - return; - - int iCameraManIndex = HLTVDirector()->GetCameraMan(); - - if ( iCameraManIndex == 0 ) - { - // turn camera on - HLTVDirector()->SetCameraMan( entindex() ); - } - else if ( iCameraManIndex == entindex() ) - { - // turn camera off - HLTVDirector()->SetCameraMan( 0 ); - } - else - { - ClientPrint( this, HUD_PRINTTALK, "Camera in use by other player." ); - } - - /* UTIL_SayText( "Spectator can not USE anything", this ); - - Vector dir,end; - Vector start = GetAbsOrigin(); - - AngleVectors( GetAbsAngles(), &dir ); - VectorNormalize( dir ); - - VectorMA( start, 32.0f, dir, end ); - - trace_t tr; - UTIL_TraceLine( start, end, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); - - if ( tr.fraction == 1.0f ) - return; // no obstacles in spectators way - - VectorMA( start, 128.0f, dir, end ); - - Ray_t ray; - ray.Init( end, start, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); - - UTIL_TraceRay( ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); - - if ( tr.startsolid || tr.allsolid ) - return; - - SetAbsOrigin( tr.endpos ); */ -#endif -} - -void CBasePlayer::JumptoPosition(const Vector &origin, const QAngle &angles) -{ - SetAbsOrigin( origin ); - SetAbsVelocity( vec3_origin ); // stop movement - SetLocalAngles( angles ); - SnapEyeAngles( angles ); -} - -bool CBasePlayer::SetObserverTarget(CBaseEntity *target) -{ - if ( !IsValidObserverTarget( target ) ) - return false; - - // set new target - m_hObserverTarget.Set( target ); - - // reset fov to default - SetFOV( this, 0 ); - - if ( m_iObserverMode == OBS_MODE_ROAMING ) - { - Vector dir, end; - Vector start = target->EyePosition(); - - AngleVectors( target->EyeAngles(), &dir ); - VectorNormalize( dir ); - VectorMA( start, -64.0f, dir, end ); - - Ray_t ray; - ray.Init( start, end, VEC_DUCK_HULL_MIN , VEC_DUCK_HULL_MAX ); - - trace_t tr; - UTIL_TraceRay( ray, MASK_PLAYERSOLID, target, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); - - JumptoPosition( tr.endpos, target->EyeAngles() ); - } - - return true; -} - -bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target) -{ - if ( target == NULL ) - return false; - - // MOD AUTHORS: Add checks on target here or in derived methode - - if ( !target->IsPlayer() ) // only track players - return false; - - CBasePlayer * player = ToBasePlayer( target ); - - /* Don't spec observers or players who haven't picked a class yet - if ( player->IsObserver() ) - return false; */ - - if( player == this ) - return false; // We can't observe ourselves. - - if ( player->IsEffectActive( EF_NODRAW ) ) // don't watch invisible players - return false; - - if ( player->m_lifeState == LIFE_RESPAWNABLE ) // target is dead, waiting for respawn - return false; - - if ( player->m_lifeState == LIFE_DEAD || player->m_lifeState == LIFE_DYING ) - { - if ( (player->m_flDeathTime + DEATH_ANIMATION_TIME ) < gpGlobals->curtime ) - { - return false; // allow watching until 3 seconds after death to see death animation - } - } - - // check forcecamera settings for active players - if ( GetTeamNumber() != TEAM_SPECTATOR ) - { - switch ( mp_forcecamera.GetInt() ) - { - case OBS_ALLOW_ALL : break; - case OBS_ALLOW_TEAM : if ( GetTeamNumber() != target->GetTeamNumber() ) - return false; - break; - case OBS_ALLOW_NONE : return false; - } - } - - return true; // passed all test -} - -int CBasePlayer::GetNextObserverSearchStartPoint( bool bReverse ) -{ - int iDir = bReverse ? -1 : 1; - - int startIndex; - - if ( m_hObserverTarget ) - { - // start using last followed player - startIndex = m_hObserverTarget->entindex(); - } - else - { - // start using own player index - startIndex = this->entindex(); - } - - startIndex += iDir; - if (startIndex > gpGlobals->maxClients) - startIndex = 1; - else if (startIndex < 1) - startIndex = gpGlobals->maxClients; - - return startIndex; -} - -CBaseEntity * CBasePlayer::FindNextObserverTarget(bool bReverse) -{ - // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching - // only a subset of the players. e.g. Make it check the target's team. - -/* if ( m_flNextFollowTime && m_flNextFollowTime > gpGlobals->time ) - { - return; - } - - m_flNextFollowTime = gpGlobals->time + 0.25; - */ // TODO move outside this function - - int startIndex = GetNextObserverSearchStartPoint( bReverse ); - - int currentIndex = startIndex; - int iDir = bReverse ? -1 : 1; - - do - { - CBaseEntity * nextTarget = UTIL_PlayerByIndex( currentIndex ); - - if ( IsValidObserverTarget( nextTarget ) ) - { - return nextTarget; // found next valid player - } - - currentIndex += iDir; - - // Loop through the clients - if (currentIndex > gpGlobals->maxClients) - currentIndex = 1; - else if (currentIndex < 1) - currentIndex = gpGlobals->maxClients; - - } while ( currentIndex != startIndex ); - - return NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Return true if this object can be +used by the player -//----------------------------------------------------------------------------- -bool CBasePlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) -{ - if ( pEntity ) - { - int caps = pEntity->ObjectCaps(); - if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) ) - { - if ( (caps & requiredCaps) == requiredCaps ) - { - return true; - } - } - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CBasePlayer::CanPickupObject( CBaseEntity *pObject, float massLimit, float sizeLimit ) -{ - // UNDONE: Make this virtual and move to HL2 player -#ifdef HL2_DLL - //Must be valid - if ( pObject == NULL ) - return false; - - //Must move with physics - if ( pObject->GetMoveType() != MOVETYPE_VPHYSICS ) - return false; - - IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; - int count = pObject->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); - - //Must have a physics object - if (!count) - return false; - - float objectMass = 0; - bool checkEnable = false; - for ( int i = 0; i < count; i++ ) - { - objectMass += pList[i]->GetMass(); - if ( !pList[i]->IsMoveable() ) - { - checkEnable = true; - } - if ( pList[i]->GetGameFlags() & FVPHYSICS_NO_PLAYER_PICKUP ) - return false; - if ( pList[i]->IsHinged() ) - return false; - } - - - //Msg( "Target mass: %f\n", pPhys->GetMass() ); - - //Must be under our threshold weight - if ( massLimit > 0 && objectMass > massLimit ) - return false; - - if ( checkEnable ) - { - // Allowing picking up of bouncebombs. - CBounceBomb *pBomb = dynamic_cast(pObject); - if( pBomb ) - return true; - - // Allow pickup of phys props that are motion enabled on player pickup - CPhysicsProp *pProp = dynamic_cast(pObject); - CPhysBox *pBox = dynamic_cast(pObject); - if ( !pProp && !pBox ) - return false; - - if ( pProp && !(pProp->HasSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON )) ) - return false; - - if ( pBox && !(pBox->HasSpawnFlags( SF_PHYSBOX_ENABLE_ON_PHYSCANNON )) ) - return false; - } - - if ( sizeLimit > 0 ) - { - const Vector &size = pObject->CollisionProp()->OBBSize(); - if ( size.x > sizeLimit || size.y > sizeLimit || size.z > sizeLimit ) - return false; - } - - return true; -#else - return false; -#endif -} - -float CBasePlayer::GetHeldObjectMass( IPhysicsObject *pHeldObject ) -{ - return 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: Server side of jumping rules. Most jumping logic is already -// handled in shared gamemovement code. Put stuff here that should -// only be done server side. -//----------------------------------------------------------------------------- -void CBasePlayer::Jump() -{ -} - -void CBasePlayer::Duck( ) -{ - if (m_nButtons & IN_DUCK) - { - if ( m_Activity != ACT_LEAP ) - { - SetAnimation( PLAYER_WALK ); - } - } -} - -// -// ID's player as such. -// -Class_T CBasePlayer::Classify ( void ) -{ - return CLASS_PLAYER; -} - - -void CBasePlayer::ResetFragCount() -{ - m_iFrags = 0; - pl.frags = m_iFrags; -} - -void CBasePlayer::IncrementFragCount( int nCount ) -{ - m_iFrags += nCount; - pl.frags = m_iFrags; -} - -void CBasePlayer::ResetDeathCount() -{ - m_iDeaths = 0; - pl.deaths = m_iDeaths; -} - -void CBasePlayer::IncrementDeathCount( int nCount ) -{ - m_iDeaths += nCount; - pl.deaths = m_iDeaths; -} - -void CBasePlayer::AddPoints( int score, bool bAllowNegativeScore ) -{ - // Positive score always adds - if ( score < 0 ) - { - if ( !bAllowNegativeScore ) - { - if ( m_iFrags < 0 ) // Can't go more negative - return; - - if ( -score > m_iFrags ) // Will this go negative? - { - score = -m_iFrags; // Sum will be 0 - } - } - } - - m_iFrags += score; - pl.frags = m_iFrags; -} - -void CBasePlayer::AddPointsToTeam( int score, bool bAllowNegativeScore ) -{ - if ( GetTeam() ) - { - GetTeam()->AddScore( score ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : int -//----------------------------------------------------------------------------- -int CBasePlayer::GetCommandContextCount( void ) const -{ - return m_CommandContext.Count(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : index - -// Output : CCommandContext -//----------------------------------------------------------------------------- -CCommandContext *CBasePlayer::GetCommandContext( int index ) -{ - if ( index < 0 || index >= m_CommandContext.Count() ) - return NULL; - - return &m_CommandContext[ index ]; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CCommandContext *CBasePlayer::AllocCommandContext( void ) -{ - int idx = m_CommandContext.AddToTail(); - return &m_CommandContext[ idx ]; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : index - -//----------------------------------------------------------------------------- -void CBasePlayer::RemoveCommandContext( int index ) -{ - m_CommandContext.Remove( index ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::RemoveAllCommandContexts() -{ - m_CommandContext.RemoveAll(); -} - -//----------------------------------------------------------------------------- -// Purpose: Determine how much time we will be running this frame -// Output : float -//----------------------------------------------------------------------------- -int CBasePlayer::DetermineSimulationTicks( void ) -{ - int command_context_count = GetCommandContextCount(); - - int context_number; - - int simulation_ticks = 0; - - // Determine how much time we will be running this frame and fixup player clock as needed - for ( context_number = 0; context_number < command_context_count; context_number++ ) - { - CCommandContext const *ctx = GetCommandContext( context_number ); - Assert( ctx ); - Assert( ctx->numcmds > 0 ); - Assert( ctx->dropped_packets >= 0 ); - - // Determine how long it will take to run those packets - simulation_ticks += ctx->numcmds + ctx->dropped_packets; - } - - return simulation_ticks; -} - -// 2 ticks ahead or behind current clock means we need to fix clock on client -#define TARGET_CLOCK_CORRECTION_TICKS (TIME_TO_TICKS(0.06f)) - - -extern ConVar skip; - -//----------------------------------------------------------------------------- -// Purpose: Based upon amount of time in simulation time, adjust m_nTickBase so that -// we just end at the end of the current frame (so the player is basically on clock -// with the server) -// Input : simulation_ticks - -//----------------------------------------------------------------------------- -void CBasePlayer::AdjustPlayerTimeBase( int simulation_ticks ) -{ - Assert( simulation_ticks >= 0 ); - if ( simulation_ticks < 0 ) - return; - - // Start in the past so that we get to the sv.time that we'll hit at the end of the - // frame, just as we process the final command - - if ( gpGlobals->maxClients == 1 ) - { - // set TickBase so that player simulation tick matches gpGlobals->tickcount after - // all commands have been executed - m_nTickBase = gpGlobals->tickcount - simulation_ticks + 1; - } - else // multiplayer - { - // set the target tick 2 ticks ahead in the future. this way the client can - // alternate around this targettick without getting smaller than gpGlobals->tickcount - // after running the commands simulation time should never be smaller than the - // current gpGlobals->tickcount, otherwise the simulation time drops out of the - // clientside view interpolation buffer. - - int end_of_frame_ticks = gpGlobals->tickcount + TARGET_CLOCK_CORRECTION_TICKS; - - int estimated_end_tick = m_nTickBase + simulation_ticks; - - // If client gets ahead of this, we'll need to correct - int too_fast_limit = end_of_frame_ticks + TARGET_CLOCK_CORRECTION_TICKS; - // If client falls behind this, we'll also need to correct - int too_slow_limit = end_of_frame_ticks - TARGET_CLOCK_CORRECTION_TICKS; - - // See if we are too fast - if ( estimated_end_tick > too_fast_limit ) - { - // DevMsg( "client too fast by %i ticks\n", estimated_end_tick - end_of_frame_ticks ); - m_nTickBase = end_of_frame_ticks - simulation_ticks + 1; - } - // Or to slow - else if ( estimated_end_tick < too_slow_limit ) - { - // DevMsg( "client too slow by %i ticks\n", end_of_frame_ticks - estimated_end_tick ); - m_nTickBase = end_of_frame_ticks - simulation_ticks + 1; - } - } -} - -void CBasePlayer::RunNullCommand( void ) -{ - CUserCmd cmd; // NULL command - - // Store off the globals.. they're gonna get whacked - float flOldFrametime = gpGlobals->frametime; - float flOldCurtime = gpGlobals->curtime; - - pl.fixangle = FIXANGLE_NONE; - cmd.viewangles = EyeAngles(); - - float flTimeBase = gpGlobals->curtime; - SetTimeBase( flTimeBase ); - - MoveHelperServer()->SetHost( this ); - PlayerRunCommand( &cmd, MoveHelperServer() ); - - // save off the last good usercmd - SetLastUserCommand( cmd ); - - // Restore the globals.. - gpGlobals->frametime = flOldFrametime; - gpGlobals->curtime = flOldCurtime; - - MoveHelperServer()->SetHost( NULL ); -} - -//----------------------------------------------------------------------------- -// Purpose: Note, don't chain to BaseClass::PhysicsSimulate -//----------------------------------------------------------------------------- -void CBasePlayer::PhysicsSimulate( void ) -{ - VPROF_BUDGET( "CBasePlayer::PhysicsSimulate", VPROF_BUDGETGROUP_PLAYER ); - // If we've got a moveparent, we must simulate that first. - CBaseEntity *pMoveParent = GetMoveParent(); - if (pMoveParent) - { - pMoveParent->PhysicsSimulate(); - } - - // Make sure not to simulate this guy twice per frame - if (m_nSimulationTick == gpGlobals->tickcount ) - { - return; - } - - m_nSimulationTick = gpGlobals->tickcount; - - // See how much time has queued up for running - int simulation_ticks = DetermineSimulationTicks(); - - // If some time will elapse, make sure our clock (m_nTickBase) starts at the correct time - if ( simulation_ticks > 0 ) - { - AdjustPlayerTimeBase( simulation_ticks ); - } - - if ( IsHLTV() ) - { - // just run a single, empty command to makke sure - // all preThink/Postthink functions are called as usual - Assert ( GetCommandContextCount() == 0 ); - RunNullCommand(); - RemoveAllCommandContexts(); - return; - } - - // Store off true server timestamps - float savetime = gpGlobals->curtime; - float saveframetime = gpGlobals->frametime; - - int command_context_count = GetCommandContextCount(); - for ( int context_number = 0; context_number < command_context_count; context_number++ ) - { - // Get oldest ( newer are added to tail ) - CCommandContext *ctx = GetCommandContext( context_number ); - Assert( ctx ); - - int i; - int numbackup = ctx->totalcmds - ctx->numcmds; - - // If the server is paused, zero out motion,buttons,view changes - if ( ctx->paused ) - { - bool clear_angles = true; - - // If no clipping and cheats enabled and noclipduring game enabled, then leave - // forwardmove and angles stuff in usercmd - if ( GetMoveType() == MOVETYPE_NOCLIP && - sv_cheats->GetBool() && - sv_noclipduringpause.GetBool() ) - { - clear_angles = false; - } - - for ( i = 0; i < ctx->numcmds; i++ ) - { - ctx->cmds[ i ].buttons = 0; - if ( clear_angles ) - { - ctx->cmds[ i ].forwardmove = 0; - ctx->cmds[ i ].sidemove = 0; - ctx->cmds[ i ].upmove = 0; - VectorCopy ( pl.v_angle, ctx->cmds[ i ].viewangles ); - } - } - - ctx->dropped_packets = 0; - } - - MoveHelperServer()->SetHost( this ); - - // Suppress predicted events, etc. - if ( IsPredictingWeapons() ) - { - IPredictionSystem::SuppressHostEvents( this ); - } - - // If we haven't dropped too many packets, then run some commands - if ( ctx->dropped_packets < 24 ) - { - int droppedcmds = ctx->dropped_packets; - - if ( droppedcmds > numbackup ) - { - // Msg( "lost %i cmds\n", droppedcmds ); - } - - // run the last known cmd for each dropped cmd we don't have a backup for - while ( droppedcmds > numbackup ) - { - m_LastCmd.tick_count++; - - if ( ShouldRunCommandsInContext( ctx ) ) - { - PlayerRunCommand( &m_LastCmd, MoveHelperServer() ); - } - droppedcmds--; - } - - // Now run the "history" commands if we still have dropped packets - while ( droppedcmds > 0 ) - { - int cmdnum = ctx->numcmds + droppedcmds - 1; - if ( ShouldRunCommandsInContext( ctx ) ) - { - PlayerRunCommand( &ctx->cmds[cmdnum], MoveHelperServer() ); - } - droppedcmds--; - } - } - - // Now run any new command(s). Go backward because the most recent command is at index 0. - for ( i = ctx->numcmds - 1; i >= 0; i-- ) - { - if ( ShouldRunCommandsInContext( ctx ) ) - { - PlayerRunCommand( &ctx->cmds[ i ], MoveHelperServer() ); - } - } - - // Save off the last good command in case we drop > numbackup packets and need to rerun them - // we'll use this to "guess" at what was in the missing packets - m_LastCmd = ctx->cmds[ CMD_MOSTRECENT ]; - - // Update our vphysics object. - if ( m_pPhysicsController ) - { - VPROF( "CBasePlayer::PhysicsSimulate-UpdateVPhysicsPosition" ); - // If simulating at 2 * TICK_INTERVAL, add an extra TICK_INTERVAL to position arrival computation - int additionalTick = CBaseEntity::IsSimulatingOnAlternateTicks() ? 1 : 0; - - float flSecondsToArrival = ( ctx->numcmds + ctx->dropped_packets + additionalTick ) * TICK_INTERVAL; - UpdateVPhysicsPosition( m_vNewVPhysicsPosition, m_vNewVPhysicsVelocity, flSecondsToArrival ); - } - - // Always reset after running commands - IPredictionSystem::SuppressHostEvents( NULL ); - - MoveHelperServer()->SetHost( NULL ); - } - - // Clear all contexts - RemoveAllCommandContexts(); - - // Restore the true server clock - // FIXME: Should this occur after simulation of children so - // that they are in the timespace of the player? - gpGlobals->curtime = savetime; - gpGlobals->frametime = saveframetime; -} - -unsigned int CBasePlayer::PhysicsSolidMaskForEntity() const -{ - return MASK_PLAYERSOLID; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *buf - -// totalcmds - -// dropped_packets - -// ignore - -// paused - -// Output : float -- Time in seconds of last movement command -//----------------------------------------------------------------------------- -void CBasePlayer::ProcessUsercmds( CUserCmd *cmds, int numcmds, int totalcmds, - int dropped_packets, bool paused ) -{ - CCommandContext *ctx = AllocCommandContext(); - Assert( ctx ); - - int i; - for ( i = totalcmds - 1; i >= 0; i-- ) - { - ctx->cmds[ i ] = cmds[ i ]; - } - ctx->numcmds = numcmds; - ctx->totalcmds = totalcmds, - ctx->dropped_packets = dropped_packets; - ctx->paused = paused; - - // Set global pause state for this player - m_bGamePaused = paused; - - if ( paused ) - { - m_nSimulationTick = -1; - // Just run the commands right away if paused - PhysicsSimulate(); - } -} - -// Duck debouncing code to stop menu changes from disallowing crouch/uncrouch -ConVar xc_crouch_debounce( "xc_crouch_debounce", "0", FCVAR_NONE ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *ucmd - -// *moveHelper - -//----------------------------------------------------------------------------- -void CBasePlayer::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) -{ - m_touchedPhysObject = false; - - if ( pl.fixangle == FIXANGLE_NONE) - { - VectorCopy ( ucmd->viewangles, pl.v_angle ); - } - - // Handle FL_FROZEN. - // Prevent player moving for some seconds after New Game, so that they pick up everything - if( GetFlags() & FL_FROZEN || - (developer.GetInt() == 0 && gpGlobals->eLoadType == MapLoad_NewGame && gpGlobals->curtime < 3.0 ) ) - { - ucmd->forwardmove = 0; - ucmd->sidemove = 0; - ucmd->upmove = 0; - ucmd->buttons = 0; - ucmd->impulse = 0; - VectorCopy ( pl.v_angle, ucmd->viewangles ); - } -#ifdef _XBOX - else - { - // Force a duck if we're toggled - if ( GetToggledDuckState() ) - { - // If this is set, we've altered our menu options and need to debounce the duck - if ( xc_crouch_debounce.GetBool() ) - { - ToggleDuck(); - - // Mark it as handled - xc_crouch_debounce.SetValue( 0 ); - } - else - { - ucmd->buttons |= IN_DUCK; - } - } - } -#endif // _XBOX - - PlayerMove()->RunCommand(this, ucmd, moveHelper); -} - - -void CBasePlayer::HandleFuncTrain(void) -{ - if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) - AddFlag( FL_ONTRAIN ); - else - RemoveFlag( FL_ONTRAIN ); - - // Train speed control - if (( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) == 0) - { - if (m_iTrain & TRAIN_ACTIVE) - { - m_iTrain = TRAIN_NEW; // turn off train - } - return; - } - - CBaseEntity *pTrain = GetGroundEntity(); - float vel; - - if ( pTrain ) - { - if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) - pTrain = NULL; - } - - if ( !pTrain ) - { - if ( GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE ) - { - m_iTrain = TRAIN_ACTIVE | TRAIN_NEW; - - if ( m_nButtons & IN_FORWARD ) - { - m_iTrain |= TRAIN_FAST; - } - else if ( m_nButtons & IN_BACK ) - { - m_iTrain |= TRAIN_BACK; - } - else - { - m_iTrain |= TRAIN_NEUTRAL; - } - return; - } - else - { - trace_t trainTrace; - // Maybe this is on the other side of a level transition - UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38), - MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace ); - - if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt ) - pTrain = trainTrace.m_pEnt; - - - if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(this) ) - { - m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; - m_iTrain = TRAIN_NEW|TRAIN_OFF; - return; - } - } - } - else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) ) - { - // Turn off the train if you jump, strafe, or the train controls go dead - m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; - m_iTrain = TRAIN_NEW|TRAIN_OFF; - return; - } - - SetAbsVelocity( vec3_origin ); - vel = 0; - if ( m_afButtonPressed & IN_FORWARD ) - { - vel = 1; - pTrain->Use( this, this, USE_SET, (float)vel ); - } - else if ( m_afButtonPressed & IN_BACK ) - { - vel = -1; - pTrain->Use( this, this, USE_SET, (float)vel ); - } - - if (vel) - { - m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed()); - m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; - } -} - - -void CBasePlayer::PreThink(void) -{ - if ( g_fGameOver || m_iPlayerLocked ) - return; // intermission or finale - - ItemPreFrame( ); - WaterMove(); - - if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) - m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; - else - m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT; - - // checks if new client data (for HUD and view control) needs to be sent to the client - UpdateClientData(); - - CheckTimeBasedDamage(); - - CheckSuitUpdate(); - - if ( GetObserverMode() > OBS_MODE_FIXED ) - { - CheckObserverSettings(); // do this each frame - } - - if (m_lifeState >= LIFE_DYING) - return; - - HandleFuncTrain(); - - if (m_nButtons & IN_JUMP) - { - // If on a ladder, jump off the ladder - // else Jump - Jump(); - } - - // If trying to duck, already ducked, or in the process of ducking - if ((m_nButtons & IN_DUCK) || (GetFlags() & FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) - Duck(); - - // - // If we're not on the ground, we're falling. Update our falling velocity. - // - if ( !( GetFlags() & FL_ONGROUND ) ) - { - m_Local.m_flFallVelocity = -GetAbsVelocity().z; - } - -#ifndef _XBOX - CNavArea *area = TheNavMesh->GetNavArea( GetAbsOrigin() ); - if (area && area != m_lastNavArea) - { - // player entered a new nav area - if (m_lastNavArea) - { - m_lastNavArea->DecrementPlayerCount( GetTeamNumber() ); - } - - area->IncrementPlayerCount( GetTeamNumber() ); - - m_lastNavArea = area; - if ( area->GetPlace() != UNDEFINED_PLACE ) - { - const char *placeName = TheNavMesh->PlaceToName( area->GetPlace() ); - if ( placeName && *placeName ) - { - Q_strncpy( m_szLastPlaceName.GetForModify(), placeName, MAX_PLACE_NAME_LENGTH ); - } - } - - // generate event - //KeyValues *event = new KeyValues( "player_entered_area" ); - //event->SetInt( "userid", GetUserID() ); - //event->SetInt( "areaid", area->GetID() ); - //gameeventmanager->FireEvent( event ); - } -#endif - - // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? -} - - -/* Time based Damage works as follows: - 1) There are several types of timebased damage: - - #define DMG_PARALYZE (1 << 14) // slows affected creature down - #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad - #define DMG_POISON (1 << 16) // blood poisioning - #define DMG_RADIATION (1 << 17) // radiation exposure - #define DMG_DROWNRECOVER (1 << 18) // drown recovery - #define DMG_ACID (1 << 19) // toxic chemicals or acid burns - #define DMG_SLOWBURN (1 << 20) // in an oven - - 2) A new hit inflicting tbd restarts the tbd counter - each NPC has an 8bit counter, - per damage type. The counter is decremented every second, so the maximum time - an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius - of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. - - 3) Every second that a tbd counter is running, the player takes damage. The damage - is determined by the type of tdb. - Paralyze - 1/2 movement rate, 30 second duration. - Nervegas - 5 points per second, 16 second duration = 80 points max dose. - Poison - 2 points per second, 25 second duration = 50 points max dose. - Radiation - 1 point per second, 50 second duration = 50 points max dose. - Drown - 5 points per second, 2 second duration. - Acid/Chemical - 5 points per second, 10 second duration = 50 points max. - Burn - 10 points per second, 2 second duration. - Freeze - 3 points per second, 10 second duration = 30 points max. - - 4) Certain actions or countermeasures counteract the damaging effects of tbds: - - Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body - - recharged by suit recharger - Air In Lungs - drowning damage is done to air in lungs first, then to body - - recharged by poking head out of water - - 10 seconds if swiming fast - Air In SCUBA - drowning damage is done to air in tanks first, then to body - - 2 minutes in tanks. Need new tank once empty. - Radiation Syringe - Each syringe full provides protection vs one radiation dosage - Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). - Health kit - Immediate stop to acid/chemical, fire or freeze damage. - Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. - - -*/ - -// If player is taking time based damage, continue doing damage to player - -// this simulates the effect of being poisoned, gassed, dosed with radiation etc - -// anything that continues to do damage even after the initial contact stops. -// Update all time based damage counters, and shut off any that are done. - -// The m_bitsDamageType bit MUST be set if any damage is to be taken. -// This routine will detect the initial on value of the m_bitsDamageType -// and init the appropriate counter. Only processes damage every second. - -//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage -//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval - -//#define NERVEGAS_DURATION 16 -//#define NERVEGAS_DAMAGE 5.0 - -//#define POISON_DURATION 25 -//#define POISON_DAMAGE 2.0 - -//#define RADIATION_DURATION 50 -//#define RADIATION_DAMAGE 1.0 - -//#define ACID_DURATION 10 -//#define ACID_DAMAGE 5.0 - -//#define SLOWBURN_DURATION 2 -//#define SLOWBURN_DAMAGE 1.0 - -//#define SLOWFREEZE_DURATION 1.0 -//#define SLOWFREEZE_DAMAGE 3.0 - -/* */ - - -void CBasePlayer::CheckTimeBasedDamage() -{ - int i; - byte bDuration = 0; - - static float gtbdPrev = 0.0; - - if (!(m_bitsDamageType & DMG_TIMEBASED)) - return; - - // only check for time based damage approx. every 2 seconds - if (abs(gpGlobals->curtime - m_tbdPrev) < 2.0) - return; - - m_tbdPrev = gpGlobals->curtime; - - for (i = 0; i < CDMG_TIMEBASED; i++) - { - // make sure bit is set for damage type - if (m_bitsDamageType & (DMG_PARALYZE << i)) - { - switch (i) - { - case itbd_Paralyze: - // UNDONE - flag movement as half-speed - bDuration = PARALYZE_DURATION; - break; - case itbd_NerveGas: -// OnTakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); - bDuration = NERVEGAS_DURATION; - break; -// case itbd_Poison: -// OnTakeDamage( CTakeDamageInfo( this, this, POISON_DAMAGE, DMG_GENERIC ) ); -// bDuration = POISON_DURATION; -// break; - case itbd_Radiation: -// OnTakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); - bDuration = RADIATION_DURATION; - break; - case itbd_DrownRecover: - // NOTE: this hack is actually used to RESTORE health - // after the player has been drowning and finally takes a breath - if (m_idrowndmg > m_idrownrestored) - { - int idif = min(m_idrowndmg - m_idrownrestored, 10); - - TakeHealth(idif, DMG_GENERIC); - m_idrownrestored += idif; - } - bDuration = 4; // get up to 5*10 = 50 points back - break; - - case itbd_PoisonRecover: - { - // NOTE: this hack is actually used to RESTORE health - // after the player has been poisoned. - if (m_nPoisonDmg > m_nPoisonRestored) - { - int nDif = min(m_nPoisonDmg - m_nPoisonRestored, 10); - TakeHealth(nDif, DMG_GENERIC); - m_nPoisonRestored += nDif; - } - bDuration = 9; // get up to 10*10 = 100 points back - break; - } - - case itbd_Acid: -// OnTakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); - bDuration = ACID_DURATION; - break; - case itbd_SlowBurn: -// OnTakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); - bDuration = SLOWBURN_DURATION; - break; - case itbd_SlowFreeze: -// OnTakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); - bDuration = SLOWFREEZE_DURATION; - break; - default: - bDuration = 0; - } - - if (m_rgbTimeBasedDamage[i]) - { - // decrement damage duration, detect when done. - if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) - { - m_rgbTimeBasedDamage[i] = 0; - // if we're done, clear damage bits - m_bitsDamageType &= ~(DMG_PARALYZE << i); - } - } - else - // first time taking this damage type - init damage duration - m_rgbTimeBasedDamage[i] = bDuration; - } - } -} - -/* -THE POWER SUIT - -The Suit provides 3 main functions: Protection, Notification and Augmentation. -Some functions are automatic, some require power. -The player gets the suit shortly after getting off the train in C1A0 and it stays -with him for the entire game. - -Protection - - Heat/Cold - When the player enters a hot/cold area, the heating/cooling indicator on the suit - will come on and the battery will drain while the player stays in the area. - After the battery is dead, the player starts to take damage. - This feature is built into the suit and is automatically engaged. - Radiation Syringe - This will cause the player to be immune from the effects of radiation for N seconds. Single use item. - Anti-Toxin Syringe - This will cure the player from being poisoned. Single use item. - Health - Small (1st aid kits, food, etc.) - Large (boxes on walls) - Armor - The armor works using energy to create a protective field that deflects a - percentage of damage projectile and explosive attacks. After the armor has been deployed, - it will attempt to recharge itself to full capacity with the energy reserves from the battery. - It takes the armor N seconds to fully charge. - -Notification (via the HUD) - -x Health -x Ammo -x Automatic Health Care - Notifies the player when automatic healing has been engaged. -x Geiger counter - Classic Geiger counter sound and status bar at top of HUD - alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. -x Poison - Armor - Displays the current level of armor. - -Augmentation - - Reanimation (w/adrenaline) - Causes the player to come back to life after he has been dead for 3 seconds. - Will not work if player was gibbed. Single use. - Long Jump - Used by hitting the ??? key(s). Caused the player to further than normal. - SCUBA - Used automatically after picked up and after player enters the water. - Works for N seconds. Single use. - -Things powered by the battery - - Armor - Uses N watts for every M units of damage. - Heat/Cool - Uses N watts for every second in hot/cold area. - Long Jump - Uses N watts for every jump. - Alien Cloak - Uses N watts for each use. Each use lasts M seconds. - Alien Shield - Augments armor. Reduces Armor drain by one half - -*/ - -// if in range of radiation source, ping geiger counter - -#define GEIGERDELAY 0.25 - -void CBasePlayer::UpdateGeigerCounter( void ) -{ - byte range; - - // delay per update ie: don't flood net with these msgs - if (gpGlobals->curtime < m_flgeigerDelay) - return; - - m_flgeigerDelay = gpGlobals->curtime + GEIGERDELAY; - - // send range to radition source to client - range = (byte) clamp(m_flgeigerRange / 4, 0, 255); - - // This is to make sure you aren't driven crazy by geiger while in the airboat - if ( IsInAVehicle() ) - { - range = clamp( (int)range * 4, 0, 255 ); - } - - if (range != m_igeigerRangePrev) - { - m_igeigerRangePrev = range; - - CSingleUserRecipientFilter user( this ); - user.MakeReliable(); - UserMessageBegin( user, "Geiger" ); - WRITE_BYTE( range ); - MessageEnd(); - } - - // reset counter and semaphore - if (!random->RandomInt(0,3)) - { - m_flgeigerRange = 1000; - } -} - -/* -================ -CheckSuitUpdate - -Play suit update if it's time -================ -*/ - -#define SUITUPDATETIME 3.5 -#define SUITFIRSTUPDATETIME 0.1 - -void CBasePlayer::CheckSuitUpdate() -{ - int i; - int isentence = 0; - int isearch = m_iSuitPlayNext; - - // Ignore suit updates if no suit - if ( !IsSuitEquipped() ) - return; - - // if in range of radiation source, ping geiger counter - UpdateGeigerCounter(); - - if ( g_pGameRules->IsMultiplayer() ) - { - // don't bother updating HEV voice in multiplayer. - return; - } - - if ( gpGlobals->curtime >= m_flSuitUpdate && m_flSuitUpdate > 0) - { - // play a sentence off of the end of the queue - for (i = 0; i < CSUITPLAYLIST; i++) - { - if ((isentence = m_rgSuitPlayList[isearch]) != 0) - break; - - if (++isearch == CSUITPLAYLIST) - isearch = 0; - } - - if (isentence) - { - m_rgSuitPlayList[isearch] = 0; - if (isentence > 0) - { - // play sentence number - - char sentence[512]; - Q_snprintf( sentence, sizeof( sentence ), "!%s", engine->SentenceNameFromIndex( isentence ) ); - UTIL_EmitSoundSuit( edict(), sentence ); - } - else - { - // play sentence group - UTIL_EmitGroupIDSuit(edict(), -isentence); - } - m_flSuitUpdate = gpGlobals->curtime + SUITUPDATETIME; - } - else - // queue is empty, don't check - m_flSuitUpdate = 0; - } -} - -// add sentence to suit playlist queue. if fgroup is true, then -// name is a sentence group (HEV_AA), otherwise name is a specific -// sentence name ie: !HEV_AA0. If iNoRepeat is specified in -// seconds, then we won't repeat playback of this word or sentence -// for at least that number of seconds. - -void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) -{ - int i; - int isentence; - int iempty = -1; - - - // Ignore suit updates if no suit - if ( !IsSuitEquipped() ) - return; - - if ( g_pGameRules->IsMultiplayer() ) - { - // due to static channel design, etc. We don't play HEV sounds in multiplayer right now. - return; - } - - // if name == NULL, then clear out the queue - - if (!name) - { - for (i = 0; i < CSUITPLAYLIST; i++) - m_rgSuitPlayList[i] = 0; - return; - } - // get sentence or group number - if (!fgroup) - { - isentence = SENTENCEG_Lookup(name); // Lookup sentence index (not group) by name - if (isentence < 0) - return; - } - else - // mark group number as negative - isentence = -SENTENCEG_GetIndex(name); // Lookup group index by name - - // check norepeat list - this list lets us cancel - // the playback of words or sentences that have already - // been played within a certain time. - - for (i = 0; i < CSUITNOREPEAT; i++) - { - if (isentence == m_rgiSuitNoRepeat[i]) - { - // this sentence or group is already in - // the norepeat list - - if (m_rgflSuitNoRepeatTime[i] < gpGlobals->curtime) - { - // norepeat time has expired, clear it out - m_rgiSuitNoRepeat[i] = 0; - m_rgflSuitNoRepeatTime[i] = 0.0; - iempty = i; - break; - } - else - { - // don't play, still marked as norepeat - return; - } - } - // keep track of empty slot - if (!m_rgiSuitNoRepeat[i]) - iempty = i; - } - - // sentence is not in norepeat list, save if norepeat time was given - - if (iNoRepeatTime) - { - if (iempty < 0) - iempty = random->RandomInt(0, CSUITNOREPEAT-1); // pick random slot to take over - m_rgiSuitNoRepeat[iempty] = isentence; - m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->curtime; - } - - // find empty spot in queue, or overwrite last spot - - m_rgSuitPlayList[m_iSuitPlayNext++] = isentence; - if (m_iSuitPlayNext == CSUITPLAYLIST) - m_iSuitPlayNext = 0; - - if (m_flSuitUpdate <= gpGlobals->curtime) - { - if (m_flSuitUpdate == 0) - // play queue is empty, don't delay too long before playback - m_flSuitUpdate = gpGlobals->curtime + SUITFIRSTUPDATETIME; - else - m_flSuitUpdate = gpGlobals->curtime + SUITUPDATETIME; - } - -} - -//========================================================= -// UpdatePlayerSound - updates the position of the player's -// reserved sound slot in the sound list. -//========================================================= -void CBasePlayer::UpdatePlayerSound ( void ) -{ - int iBodyVolume; - int iVolume; - CSound *pSound; - - pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); - - if ( !pSound ) - { - Msg( "Client lost reserved sound!\n" ); - return; - } - - if (GetFlags() & FL_NOTARGET) - { - pSound->m_iVolume = 0; - return; - } - - // now figure out how loud the player's movement is. - if ( GetFlags() & FL_ONGROUND ) - { - iBodyVolume = GetAbsVelocity().Length(); - - // clamp the noise that can be made by the body, in case a push trigger, - // weapon recoil, or anything shoves the player abnormally fast. - // NOTE: 512 units is a pretty large radius for a sound made by the player's body. - // then again, I think some materials are pretty loud. - if ( iBodyVolume > 512 ) - { - iBodyVolume = 512; - } - } - else - { - iBodyVolume = 0; - } - - if ( m_nButtons & IN_JUMP ) - { - // Jumping is a little louder. - iBodyVolume += 100; - } - - m_iTargetVolume = iBodyVolume; - - // if target volume is greater than the player sound's current volume, we paste the new volume in - // immediately. If target is less than the current volume, current volume is not set immediately to the - // lower volume, rather works itself towards target volume over time. This gives NPCs a much better chance - // to hear a sound, especially if they don't listen every frame. - iVolume = pSound->Volume(); - - if ( m_iTargetVolume > iVolume ) - { - iVolume = m_iTargetVolume; - } - else if ( iVolume > m_iTargetVolume ) - { - iVolume -= 250 * gpGlobals->frametime; - - if ( iVolume < m_iTargetVolume ) - { - iVolume = 0; - } - } - - if ( pSound ) - { - pSound->SetSoundOrigin( GetAbsOrigin() ); - pSound->m_iType = SOUND_PLAYER; - pSound->m_iVolume = iVolume; - } - - // Below are a couple of useful little bits that make it easier to visualize just how much noise the - // player is making. - //Vector forward = UTIL_YawToVector( pl.v_angle.y ); - //UTIL_Sparks( GetAbsOrigin() + forward * iVolume ); - //Msg( "%d/%d\n", iVolume, m_iTargetVolume ); -} - -// This is a glorious hack to find free space when you've crouched into some solid space -// Our crouching collisions do not work correctly for some reason and this is easier -// than fixing the problem :( -void FixPlayerCrouchStuck( CBasePlayer *pPlayer ) -{ - trace_t trace; - - // Move up as many as 18 pixels if the player is stuck. - for ( int i = 0; i < 18; i++ ) - { - UTIL_TraceHull( pPlayer->GetAbsOrigin(), pPlayer->GetAbsOrigin(), - VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); - if ( trace.startsolid ) - { - Vector origin = pPlayer->GetAbsOrigin(); - origin.z += 1.0f; - pPlayer->SetLocalOrigin( origin ); - } - else - break; - } -} - -#define SMOOTHING_FACTOR 0.9 -extern CMoveData *g_pMoveData; - -// UNDONE: Look and see if the ground entity is in hierarchy with a MOVETYPE_VPHYSICS? -// Behavior in that case is not as good currently when the parent is rideable -bool CBasePlayer::IsRideablePhysics( IPhysicsObject *pPhysics ) -{ - if ( pPhysics ) - { - if ( pPhysics->GetMass() > (VPhysicsGetObject()->GetMass()*2) ) - return true; - } - - return false; -} - -IPhysicsObject *CBasePlayer::GetGroundVPhysics() -{ - CBaseEntity *pGroundEntity = GetGroundEntity(); - if ( pGroundEntity && pGroundEntity->GetMoveType() == MOVETYPE_VPHYSICS ) - { - IPhysicsObject *pPhysGround = pGroundEntity->VPhysicsGetObject(); - if ( pPhysGround && pPhysGround->IsMoveable() ) - return pPhysGround; - } - return NULL; -} - - -//----------------------------------------------------------------------------- -// For debugging... -//----------------------------------------------------------------------------- -void CBasePlayer::ForceOrigin( const Vector &vecOrigin ) -{ - m_bForceOrigin = true; - m_vForcedOrigin = vecOrigin; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::PostThink() -{ - m_vecSmoothedVelocity = m_vecSmoothedVelocity * SMOOTHING_FACTOR + GetAbsVelocity() * ( 1 - SMOOTHING_FACTOR ); - - if ( !g_fGameOver && !m_iPlayerLocked && IsAlive() ) - { - // set correct collision bounds (may have changed in player movement code) - VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-Bounds" ); - if ( GetFlags() & FL_DUCKING ) - { - SetCollisionBounds( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); - } - else - { - SetCollisionBounds( VEC_HULL_MIN, VEC_HULL_MAX ); - } - VPROF_SCOPE_END(); - - VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-Use" ); - // Handle controlling an entity - if ( m_hUseEntity != NULL ) - { - // if they've moved too far from the gun, or deployed another weapon, unuse the gun - if ( m_hUseEntity->OnControls( this ) && - ( !GetActiveWeapon() || GetActiveWeapon()->IsEffectActive( EF_NODRAW ) || - ( GetActiveWeapon()->GetActivity() == ACT_VM_HOLSTER ) - ) ) - { - m_hUseEntity->Use( this, this, USE_SET, 2 ); // try fire the gun - } - else - { - // they've moved off the controls - ClearUseEntity(); - } - } - VPROF_SCOPE_END(); - - // do weapon stuff - VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-ItemPostFrame" ); - ItemPostFrame(); - VPROF_SCOPE_END(); - - if ( GetFlags() & FL_ONGROUND ) - { - if (m_Local.m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) - { - CSoundEnt::InsertSound ( SOUND_PLAYER, GetAbsOrigin(), m_Local.m_flFallVelocity, 0.2, this ); - // Msg( "fall %f\n", m_Local.m_flFallVelocity ); - } - m_Local.m_flFallVelocity = 0; - } - - // select the proper animation for the player character - if ( IsAlive() ) - { - VPROF( "CBasePlayer::PostThink-Animation" ); - // If he's in a vehicle, sit down - if ( IsInAVehicle() ) - SetAnimation( PLAYER_IN_VEHICLE ); - else if (!GetAbsVelocity().x && !GetAbsVelocity().y) - SetAnimation( PLAYER_IDLE ); - else if ((GetAbsVelocity().x || GetAbsVelocity().y) && ( GetFlags() & FL_ONGROUND )) - SetAnimation( PLAYER_WALK ); - else if (GetWaterLevel() > 1) - SetAnimation( PLAYER_WALK ); - } - - // Don't allow bogus sequence on player - if ( GetSequence() == -1 ) - { - SetSequence( 0 ); - } - - VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-StudioFrameAdvance" ); - StudioFrameAdvance(); - VPROF_SCOPE_END(); - - VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-DispatchAnimEvents" ); - DispatchAnimEvents( this ); - VPROF_SCOPE_END(); - - SetSimulationTime( gpGlobals->curtime ); - - //Let the weapon update as well - VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-Weapon_FrameUpdate" ); - Weapon_FrameUpdate(); - VPROF_SCOPE_END(); - - VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-UpdatePlayerSound" ); - UpdatePlayerSound(); - VPROF_SCOPE_END(); - - if ( m_bForceOrigin ) - { - SetLocalOrigin( m_vForcedOrigin ); - SetLocalAngles( m_Local.m_vecPunchAngle ); - m_Local.m_vecPunchAngle = RandomAngle( -25, 25 ); - m_Local.m_vecPunchAngleVel.Init(); - } - - VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-PostThinkVPhysics" ); - PostThinkVPhysics(); - VPROF_SCOPE_END(); - } - -#if !defined( NO_ENTITY_PREDICTION ) - // Even if dead simulate entities - SimulatePlayerSimulatedEntities(); -#endif - -} - -// handles touching physics objects -void CBasePlayer::Touch( CBaseEntity *pOther ) -{ - if ( pOther == GetGroundEntity() ) - return; - - if ( pOther->GetMoveType() != MOVETYPE_VPHYSICS || pOther->GetSolid() != SOLID_VPHYSICS || (pOther->GetSolidFlags() & FSOLID_TRIGGER) ) - return; - - IPhysicsObject *pPhys = pOther->VPhysicsGetObject(); - if ( !pPhys || !pPhys->IsMoveable() ) - return; - - SetTouchedPhysics( true ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::PostThinkVPhysics( void ) -{ - // Check to see if things are initialized! - if ( !m_pPhysicsController ) - return; - - Vector newPosition = GetAbsOrigin(); - float frametime = gpGlobals->frametime; - if ( frametime <= 0 || frametime > 0.1f ) - frametime = 0.1f; - - IPhysicsObject *pPhysGround = GetGroundVPhysics(); - - if ( !pPhysGround && m_touchedPhysObject && g_pMoveData->m_outStepHeight <= 0.f && (GetFlags() & FL_ONGROUND) ) - { - newPosition = m_oldOrigin + frametime * g_pMoveData->m_outWishVel; - newPosition = (GetAbsOrigin() * 0.5f) + (newPosition * 0.5f); - } - - int collisionState = VPHYS_WALK; - if ( GetMoveType() == MOVETYPE_NOCLIP || GetMoveType() == MOVETYPE_OBSERVER ) - { - collisionState = VPHYS_NOCLIP; - } - else if ( GetFlags() & FL_DUCKING ) - { - collisionState = VPHYS_CROUCH; - } - - if ( collisionState != m_vphysicsCollisionState ) - { - SetVCollisionState( collisionState ); - } - - if ( !(TouchedPhysics() || pPhysGround) ) - { - float maxSpeed = m_flMaxspeed > 0.0f ? m_flMaxspeed : sv_maxspeed.GetFloat(); - g_pMoveData->m_outWishVel.Init( maxSpeed, maxSpeed, maxSpeed ); - } - - // teleport the physics object up by stepheight (game code does this - reflect in the physics) - if ( g_pMoveData->m_outStepHeight > 0.1f ) - { - if ( g_pMoveData->m_outStepHeight > 4.0f ) - { - VPhysicsGetObject()->SetPosition( GetAbsOrigin(), vec3_angle, true ); - } - else - { - // don't ever teleport into solid - Vector position, end; - VPhysicsGetObject()->GetPosition( &position, NULL ); - end = position; - end.z += g_pMoveData->m_outStepHeight; - trace_t trace; - UTIL_TraceEntity( this, position, end, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); - if ( trace.DidHit() ) - { - g_pMoveData->m_outStepHeight = trace.endpos.z - position.z; - } - m_pPhysicsController->StepUp( g_pMoveData->m_outStepHeight ); - } - m_pPhysicsController->Jump(); - } - g_pMoveData->m_outStepHeight = 0.0f; - - // Store these off because after running the usercmds, it'll pass them - // to UpdateVPhysicsPosition. - m_vNewVPhysicsPosition = newPosition; - m_vNewVPhysicsVelocity = g_pMoveData->m_outWishVel; - - m_oldOrigin = GetAbsOrigin(); -} - -void CBasePlayer::UpdateVPhysicsPosition( const Vector &position, const Vector &velocity, float secondsToArrival ) -{ - bool onground = (GetFlags() & FL_ONGROUND) ? true : false; - IPhysicsObject *pPhysGround = GetGroundVPhysics(); - - // if the object is much heavier than the player, treat it as a local coordinate system - // the player controller will solve movement differently in this case. - if ( !IsRideablePhysics(pPhysGround) ) - { - pPhysGround = NULL; - } - - m_pPhysicsController->Update( position, velocity, secondsToArrival, onground, pPhysGround ); -} - -void CBasePlayer::UpdatePhysicsShadowToCurrentPosition() -{ - UpdateVPhysicsPosition( GetAbsOrigin(), vec3_origin, gpGlobals->frametime ); -} - -Vector CBasePlayer::GetSmoothedVelocity( void ) -{ - if ( IsInAVehicle() ) - { - return GetVehicle()->GetVehicleEnt()->GetSmoothedVelocity(); - } - return m_vecSmoothedVelocity; -} - - -CBaseEntity *g_pLastSpawn = NULL; - - -//----------------------------------------------------------------------------- -// Purpose: Finds a player start entity of the given classname. If any entity of -// of the given classname has the SF_PLAYER_START_MASTER flag set, that -// is the entity that will be returned. Otherwise, the first entity of -// the given classname is returned. -// Input : pszClassName - should be "info_player_start", "info_player_coop", or -// "info_player_deathmatch" -//----------------------------------------------------------------------------- -CBaseEntity *FindPlayerStart(const char *pszClassName) -{ - #define SF_PLAYER_START_MASTER 1 - - CBaseEntity *pStart = gEntList.FindEntityByClassname(NULL, pszClassName); - CBaseEntity *pStartFirst = pStart; - while (pStart != NULL) - { - if (pStart->HasSpawnFlags(SF_PLAYER_START_MASTER)) - { - return pStart; - } - - pStart = gEntList.FindEntityByClassname(pStart, pszClassName); - } - - return pStartFirst; -} - -/* -============ -EntSelectSpawnPoint - -Returns the entity to spawn at - -USES AND SETS GLOBAL g_pLastSpawn -============ -*/ -CBaseEntity *CBasePlayer::EntSelectSpawnPoint() -{ - CBaseEntity *pSpot; - edict_t *player; - - player = edict(); - -// choose a info_player_deathmatch point - if (g_pGameRules->IsCoOp()) - { - pSpot = gEntList.FindEntityByClassname( g_pLastSpawn, "info_player_coop"); - if ( pSpot ) - goto ReturnSpot; - pSpot = gEntList.FindEntityByClassname( g_pLastSpawn, "info_player_start"); - if ( pSpot ) - goto ReturnSpot; - } - else if ( g_pGameRules->IsDeathmatch() ) - { - pSpot = g_pLastSpawn; - // Randomize the start spot - for ( int i = random->RandomInt(1,5); i > 0; i-- ) - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); - if ( !pSpot ) // skip over the null point - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); - - CBaseEntity *pFirstSpot = pSpot; - - do - { - if ( pSpot ) - { - // check if pSpot is valid - if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) ) - { - if ( pSpot->GetLocalOrigin() == vec3_origin ) - { - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); - continue; - } - - // if so, go to pSpot - goto ReturnSpot; - } - } - // increment pSpot - pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); - } while ( pSpot != pFirstSpot ); // loop if we're not back to the start - - // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there - if ( pSpot ) - { - CBaseEntity *ent = NULL; - for ( CEntitySphereQuery sphere( pSpot->GetAbsOrigin(), 128 ); (ent = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) - { - // if ent is a client, kill em (unless they are ourselves) - if ( ent->IsPlayer() && !(ent->edict() == player) ) - ent->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 300, DMG_GENERIC ) ); - } - goto ReturnSpot; - } - } - - // If startspot is set, (re)spawn there. - if ( !gpGlobals->startspot || !strlen(STRING(gpGlobals->startspot))) - { - pSpot = FindPlayerStart( "info_player_start" ); - if ( pSpot ) - goto ReturnSpot; - } - else - { - pSpot = gEntList.FindEntityByName( NULL, gpGlobals->startspot ); - if ( pSpot ) - goto ReturnSpot; - } - -ReturnSpot: - if ( !pSpot ) - { - Warning( "PutClientInServer: no info_player_start on level\n"); - return CBaseEntity::Instance( INDEXENT( 0 ) ); - } - - g_pLastSpawn = pSpot; - return pSpot; -} - -//----------------------------------------------------------------------------- -// Purpose: Called the first time the player's created -//----------------------------------------------------------------------------- -void CBasePlayer::InitialSpawn( void ) -{ - m_iConnected = PlayerConnected; -} - -//----------------------------------------------------------------------------- -// Purpose: Called everytime the player respawns -//----------------------------------------------------------------------------- -void CBasePlayer::Spawn( void ) -{ - SetClassname( "player" ); - - // Shared spawning code.. - SharedSpawn(); - - SetSimulatedEveryTick( true ); - SetAnimatedEveryTick( true ); - - m_ArmorValue = SpawnArmorValue(); - SetBlocksLOS( false ); - m_iMaxHealth = m_iHealth; - - // Clear all flags except for FL_FULLEDICT - if ( GetFlags() & FL_FAKECLIENT ) - { - ClearFlags(); - AddFlag( FL_CLIENT | FL_FAKECLIENT ); - } - else - { - ClearFlags(); - AddFlag( FL_CLIENT ); - } - - AddFlag( FL_AIMTARGET ); - - m_AirFinished = gpGlobals->curtime + AIRTIME; - m_nDrownDmgRate = DROWNING_DAMAGE_INITIAL; - - // only preserve the shadow flag - int effects = GetEffects() & EF_NOSHADOW; - SetEffects( effects ); - - m_DmgTake = 0; - m_DmgSave = 0; - m_bitsHUDDamage = -1; - m_bitsDamageType = 0; - m_afPhysicsFlags = 0; - - SetFOV( this, 0 ); - - m_flNextDecalTime = 0;// let this player decal as soon as he spawns. - - m_flgeigerDelay = gpGlobals->curtime + 2.0; // wait a few seconds until user-defined message registrations - // are recieved by all clients - - m_flTimeStepSound = 0; - m_flFieldOfView = 0.766;// some NPCs use this to determine whether or not the player is looking at them. - - m_vecAdditionalPVSOrigin = vec3_origin; - m_vecCameraPVSOrigin = vec3_origin; - - if ( !m_fGameHUDInitialized ) - g_pGameRules->SetDefaultPlayerTeam( this ); - - g_pGameRules->GetPlayerSpawnSpot( this ); - - m_Local.m_bDucked = false;// This will persist over round restart if you hold duck otherwise. - m_Local.m_bDucking = false; - SetViewOffset( VEC_VIEW ); - Precache(); - - m_bitsDamageType = 0; - m_bitsHUDDamage = -1; - SetPlayerUnderwater( false ); - - m_iTrain = TRAIN_NEW; - - m_HackedGunPos = Vector( 0, 32, 0 ); - - if ( m_iPlayerSound == SOUNDLIST_EMPTY ) - { - Msg( "Couldn't alloc player sound slot!\n" ); - } - - SetThink(NULL); - m_fInitHUD = true; - m_fWeapon = false; - m_iClientBattery = -1; - - m_lastx = m_lasty = 0; - - m_lastNavArea = NULL; - -#ifndef _XBOX - /// @todo Do this once per round instead of once per player - if (TheNavMesh) - { - TheNavMesh->ClearPlayerCounts(); - } -#endif - - Q_strncpy( m_szLastPlaceName.GetForModify(), "", MAX_PLACE_NAME_LENGTH ); - - CSingleUserRecipientFilter user( this ); - enginesound->SetPlayerDSP( user, 0, false ); - - CreateViewModel(); - - SetCollisionGroup( COLLISION_GROUP_PLAYER ); - - // if the player is locked, make sure he stays locked - if ( m_iPlayerLocked ) - { - m_iPlayerLocked = false; - LockPlayerInPlace(); - } - - if ( GetTeamNumber() != TEAM_SPECTATOR ) - { - StopObserverMode(); - } - else - { - StartObserverMode( m_iObserverLastMode ); - } - - StopReplayMode(); - - // Clear any screenfade - color32 nothing = {0,0,0,255}; - UTIL_ScreenFade( this, nothing, 0, 0, FFADE_IN | FFADE_PURGE ); - - g_pGameRules->PlayerSpawn( this ); - - m_flLaggedMovementValue = 1.0f; - m_vecSmoothedVelocity = vec3_origin; - InitVCollision(); - - IGameEvent *event = gameeventmanager->CreateEvent( "player_spawn" ); - - if ( event ) - { - event->SetInt("userid", GetUserID() ); - gameeventmanager->FireEvent( event ); - } - - RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE ); -} - -void CBasePlayer::Activate( void ) -{ - BaseClass::Activate(); - - AimTarget_ForceRepopulateList(); - - RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE ); - - // Reset the analog bias. If the player is in a vehicle when the game - // reloads, it will autosense and apply the correct bias. - m_iVehicleAnalogBias = VEHICLE_ANALOG_BIAS_NONE; -} - -void CBasePlayer::Precache( void ) -{ - BaseClass::Precache(); - - - PrecacheScriptSound( "Player.FallGib" ); - PrecacheScriptSound( "Player.Death" ); - PrecacheScriptSound( "Player.PlasmaDamage" ); - PrecacheScriptSound( "Player.SonicDamage" ); - PrecacheScriptSound( "Player.DrownStart" ); - PrecacheScriptSound( "Player.DrownContinue" ); - PrecacheScriptSound( "Player.Wade" ); - PrecacheScriptSound( "Player.AmbientUnderWater" ); - PrecacheScriptSound( "Player.Wade" ); - enginesound->PrecacheSentenceGroup( "HEV" ); - - // in the event that the player JUST spawned, and the level node graph - // was loaded, fix all of the node graph pointers before the game starts. - - // !!!BUGBUG - now that we have multiplayer, this needs to be moved! - /* todo - put in better spot and use new ainetowrk stuff - if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) - { - if ( !WorldGraph.FSetGraphPointers() ) - { - Msg( "**Graph pointers were not set!\n"); - } - else - { - Msg( "**Graph Pointers Set!\n" ); - } - } - */ - - // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) - // because they need to precache before any clients have connected - - // init geiger counter vars during spawn and each time - // we cross a level transition - m_flgeigerRange = 1000; - m_igeigerRangePrev = 1000; - -#if 0 - // @Note (toml 04-19-04): These are saved, used to be slammed here - m_bitsDamageType = 0; - m_bitsHUDDamage = -1; - SetPlayerUnderwter( false ); - - m_iTrain = TRAIN_NEW; -#endif - - m_iClientBattery = -1; - - m_iUpdateTime = 5; // won't update for 1/2 a second - - if ( gInitHUD ) - m_fInitHUD = true; - -} - - - -int CBasePlayer::Save( ISave &save ) -{ - if ( !BaseClass::Save(save) ) - return 0; - - return 1; -} - - -int CBasePlayer::Restore( IRestore &restore ) -{ - int status = BaseClass::Restore(restore); - if ( !status ) - return 0; - - CSaveRestoreData *pSaveData = gpGlobals->pSaveData; - // landmark isn't present. - if ( !pSaveData->levelInfo.fUseLandmark ) - { - Msg( "No Landmark:%s\n", pSaveData->levelInfo.szLandmarkName ); - - // default to normal spawn - CBaseEntity *pSpawnSpot = EntSelectSpawnPoint(); - SetLocalOrigin( pSpawnSpot->GetLocalOrigin() + Vector(0,0,1) ); - SetLocalAngles( pSpawnSpot->GetLocalAngles() ); - } - - QAngle newViewAngles = pl.v_angle; - newViewAngles.z = 0; // Clear out roll - SetLocalAngles( newViewAngles ); - SnapEyeAngles( newViewAngles ); - - // Copied from spawn() for now - SetBloodColor( BLOOD_COLOR_RED ); - - // clear this - it will get reset by touching the trigger again - m_afPhysicsFlags &= ~PFLAG_VPHYSICS_MOTIONCONTROLLER; - - if ( GetFlags() & FL_DUCKING ) - { - // Use the crouch HACK - FixPlayerCrouchStuck( this ); - UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); - m_Local.m_bDucked = true; - } - else - { - m_Local.m_bDucked = false; - UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); - } - - InitVCollision(); - - // success - return 1; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::OnRestore( void ) -{ - BaseClass::OnRestore(); - - SetViewEntity( m_hViewEntity ); -} - -/* void CBasePlayer::SetTeamName( const char *pTeamName ) -{ - Q_strncpy( m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); -} */ - -void CBasePlayer::SetArmorValue( int value ) -{ - m_ArmorValue = value; -} - -void CBasePlayer::IncrementArmorValue( int nCount, int nMaxValue ) -{ - m_ArmorValue += nCount; - if (nMaxValue > 0) - { - if (m_ArmorValue > nMaxValue) - m_ArmorValue = nMaxValue; - } -} - -// Only used by the physics gun... is there a better interface? -void CBasePlayer::SetPhysicsFlag( int nFlag, bool bSet ) -{ - if (bSet) - m_afPhysicsFlags |= nFlag; - else - m_afPhysicsFlags &= ~nFlag; -} - - -void CBasePlayer::NotifyNearbyRadiationSource( float flRange ) -{ - // if player's current geiger counter range is larger - // than range to this trigger hurt, reset player's - // geiger counter range - - if (m_flgeigerRange >= flRange) - m_flgeigerRange = flRange; -} - -void CBasePlayer::AllowImmediateDecalPainting() -{ - m_flNextDecalTime = gpGlobals->curtime; -} - -// Suicide... -void CBasePlayer::CommitSuicide() -{ - if( !IsAlive() ) - return; - - // prevent suiciding too often - if ( m_fNextSuicideTime > gpGlobals->curtime ) - return; - - // don't let them suicide for 5 seconds after suiciding - m_fNextSuicideTime = gpGlobals->curtime + 5; - - // have the player kill themself - m_iHealth = 0; - Event_Killed( CTakeDamageInfo( this, this, 0, DMG_NEVERGIB ) ); - Event_Dying(); -} - -//============================================== -// HasWeapons - do I have any weapons at all? -//============================================== -bool CBasePlayer::HasWeapons( void ) -{ - int i; - - for ( i = 0 ; i < WeaponCount() ; i++ ) - { - if ( GetWeapon(i) ) - { - return true; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &vecForce - -//----------------------------------------------------------------------------- -void CBasePlayer::VelocityPunch( const Vector &vecForce ) -{ - // Clear onground and add velocity. - SetGroundEntity( NULL ); - ApplyAbsVelocityImpulse(vecForce ); -} - - -//-------------------------------------------------------------------------------------------------------------- -// VEHICLES -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// Purpose: Put this player in a vehicle -//----------------------------------------------------------------------------- -bool CBasePlayer::GetInVehicle( IServerVehicle *pVehicle, int nRole ) -{ - Assert( NULL == m_hVehicle.Get() ); - Assert( nRole >= 0 ); - - if ( pVehicle->GetPassenger( nRole ) ) - return false; - - CBaseEntity *pEnt = pVehicle->GetVehicleEnt(); - Assert( pEnt ); - - if (!pVehicle->IsPassengerUsingStandardWeapons( nRole )) - { - CBaseCombatWeapon *pWeapon = GetActiveWeapon(); - - //Must be able to stow our weapon - if ( ( pWeapon != NULL ) && ( pWeapon->Holster( NULL ) == false ) ) - return false; - -#ifndef HL2_DLL - m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; -#endif - m_Local.m_iHideHUD |= HIDEHUD_INVEHICLE; - } - - if ( !pVehicle->IsPassengerVisible( nRole ) ) - { - AddEffects( EF_NODRAW ); - } - - ViewPunchReset(); - - // Setting the velocity to 0 will cause the IDLE animation to play - SetAbsVelocity( vec3_origin ); - SetMoveType( MOVETYPE_NOCLIP ); - - // Choose the entry point of the vehicle, - // By default, just stay at the point you started at... - // NOTE: we have to set this first so that when the parent is set - // the local position just works - Vector vNewPos = GetAbsOrigin(); - QAngle qAngles = GetAbsAngles(); - pVehicle->GetPassengerStartPoint( nRole, &vNewPos, &qAngles ); - SetAbsOrigin( vNewPos ); - SetAbsAngles( qAngles ); - SetParent( pEnt ); - - SetCollisionGroup( COLLISION_GROUP_IN_VEHICLE ); - - // We cannot be ducking -- do all this before SetPassenger because it - // saves our view offset for restoration when we exit the vehicle. - RemoveFlag( FL_DUCKING ); - SetViewOffset( VEC_VIEW ); - m_Local.m_bDucked = false; - m_Local.m_bDucking = false; - m_Local.m_flDucktime = 0.0f; - m_Local.m_flDuckJumpTime = 0.0f; - m_Local.m_flJumpTime = 0.0f; - - // Turn our toggled duck off - if ( GetToggledDuckState() ) - { - ToggleDuck(); - } - - pVehicle->SetPassenger( nRole, this ); - - m_hVehicle = pEnt; - - // Throw an event indicating that the player entered the vehicle. - g_pNotify->ReportNamedEvent( this, "PlayerEnteredVehicle" ); - - m_iVehicleAnalogBias = VEHICLE_ANALOG_BIAS_NONE; - - OnVehicleStart(); - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Remove this player from a vehicle -//----------------------------------------------------------------------------- -void CBasePlayer::LeaveVehicle( const Vector &vecExitPoint, const QAngle &vecExitAngles ) -{ - if ( NULL == m_hVehicle.Get() ) - return; - - IServerVehicle *pVehicle = GetVehicle(); - Assert( pVehicle ); - - int nRole = pVehicle->GetPassengerRole( this ); - Assert( nRole >= 0 ); - - SetParent( NULL ); - - // Find the first non-blocked exit point: - Vector vNewPos = GetAbsOrigin(); - QAngle qAngles = GetAbsAngles(); - if ( vecExitPoint == vec3_origin ) - { - // FIXME: this might fail to find a safe exit point!! - pVehicle->GetPassengerExitPoint( nRole, &vNewPos, &qAngles ); - } - else - { - vNewPos = vecExitPoint; - qAngles = vecExitAngles; - } - OnVehicleEnd( vNewPos ); - SetAbsOrigin( vNewPos ); - SetAbsAngles( qAngles ); - // Clear out any leftover velocity - SetAbsVelocity( vec3_origin ); - - qAngles[ROLL] = 0; - SnapEyeAngles( qAngles ); - -#ifndef HL2_DLL - m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; -#endif - - m_Local.m_iHideHUD &= ~HIDEHUD_INVEHICLE; - - RemoveEffects( EF_NODRAW ); - - SetMoveType( MOVETYPE_WALK ); - SetCollisionGroup( COLLISION_GROUP_PLAYER ); - - if ( VPhysicsGetObject() ) - { - VPhysicsGetObject()->SetPosition( vNewPos, vec3_angle, true ); - } - - m_hVehicle = NULL; - pVehicle->SetPassenger(nRole, NULL); - - // Re-deploy our weapon - if ( IsAlive() ) - { - if ( GetActiveWeapon() && GetActiveWeapon()->IsWeaponVisible() == false ) - { - GetActiveWeapon()->Deploy(); - ShowCrosshair( true ); - } - } - -#ifdef _XBOX - // Just cut all of the rumble effects. - RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE ); -#endif//_XBOX -} - - -//============================================== -// !!!UNDONE:ultra temporary SprayCan entity to apply -// decal frame at a time. For PreAlpha CD -//============================================== -class CSprayCan : public CPointEntity -{ -public: - DECLARE_CLASS( CSprayCan, CPointEntity ); - - void Spawn ( CBasePlayer *pOwner ); - void Think( void ); - - virtual void Precache(); - - virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } -}; - -LINK_ENTITY_TO_CLASS( spraycan, CSprayCan ); -PRECACHE_REGISTER( spraycan ); - -void CSprayCan::Spawn ( CBasePlayer *pOwner ) -{ - SetLocalOrigin( pOwner->WorldSpaceCenter() + Vector ( 0 , 0 , 32 ) ); - SetLocalAngles( pOwner->EyeAngles() ); - SetOwnerEntity( pOwner ); - SetNextThink( gpGlobals->curtime ); - EmitSound( "SprayCan.Paint" ); -} - -void CSprayCan::Precache() -{ - BaseClass::Precache(); - - PrecacheScriptSound( "SprayCan.Paint" ); -} - -void CSprayCan::Think( void ) -{ - trace_t tr; - int playernum; - int nFrames; - CBasePlayer *pPlayer; - - pPlayer = ToBasePlayer( GetOwnerEntity() ); - - nFrames = 1; // FIXME, look up from material - - playernum = GetOwnerEntity()->entindex(); - - // Msg( "Spray by player %i, %i of %i\n", playernum, (int)(m_flFrame + 1), nFrames); - - Vector forward; - AngleVectors( GetAbsAngles(), &forward ); - UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + forward * 128, - MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, & tr); - - UTIL_PlayerDecalTrace( &tr, playernum ); - - // Just painted last custom frame. - UTIL_Remove( this ); -} - -class CBloodSplat : public CPointEntity -{ -public: - DECLARE_CLASS( CBloodSplat, CPointEntity ); - - void Spawn ( CBaseEntity *pOwner ); - void Think ( void ); -}; - -void CBloodSplat::Spawn ( CBaseEntity *pOwner ) -{ - SetLocalOrigin( pOwner->WorldSpaceCenter() + Vector ( 0 , 0 , 32 ) ); - SetLocalAngles( pOwner->GetLocalAngles() ); - SetOwnerEntity( pOwner ); - - SetNextThink( gpGlobals->curtime + 0.1f ); -} - -void CBloodSplat::Think( void ) -{ - trace_t tr; - - if ( g_Language.GetInt() != LANGUAGE_GERMAN ) - { - CBasePlayer *pPlayer; - pPlayer = ToBasePlayer( GetOwnerEntity() ); - - Vector forward; - AngleVectors( GetAbsAngles(), &forward ); - UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + forward * 128, - MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, & tr); - - UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); - } - UTIL_Remove( this ); -} - -//============================================== - -//----------------------------------------------------------------------------- -// Purpose: Create and give the named item to the player. Then return it. -//----------------------------------------------------------------------------- -CBaseEntity *CBasePlayer::GiveNamedItem( const char *pszName, int iSubType ) -{ - // If I already own this type don't create one - if ( Weapon_OwnsThisType(pszName, iSubType) ) - return NULL; - - // Msg( "giving %s\n", pszName ); - - EHANDLE pent; - - pent = CreateEntityByName(pszName); - if ( pent == NULL ) - { - Msg( "NULL Ent in GiveNamedItem!\n" ); - return NULL; - } - - pent->SetLocalOrigin( GetLocalOrigin() ); - pent->AddSpawnFlags( SF_NORESPAWN ); - - if ( iSubType ) - { - CBaseCombatWeapon *pWeapon = dynamic_cast( (CBaseEntity*)pent ); - if ( pWeapon ) - { - pWeapon->SetSubType( iSubType ); - } - } - - DispatchSpawn( pent ); - - if ( pent != NULL && !(pent->IsMarkedForDeletion()) ) - { - pent->Touch( this ); - } - - return pent; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the nearest COLLIBALE entity in front of the player -// that has a clear line of sight with the given classname -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseEntity *FindEntityClassForward( CBasePlayer *pMe, char *classname ) -{ - trace_t tr; - - Vector forward; - pMe->EyeVectors( &forward ); - UTIL_TraceLine(pMe->EyePosition(), - pMe->EyePosition() + forward * MAX_COORD_RANGE, - MASK_SOLID, pMe, COLLISION_GROUP_NONE, &tr ); - if ( tr.fraction != 1.0 && tr.DidHitNonWorldEntity() ) - { - CBaseEntity *pHit = tr.m_pEnt; - if (FClassnameIs( pHit,classname ) ) - { - return pHit; - } - } - return NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the nearest COLLIBALE entity in front of the player -// that has a clear line of sight. If HULL is true, the trace will -// hit the collision hull of entities. Otherwise, the trace will hit -// hitboxes. -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull ) -{ - if ( pMe ) - { - trace_t tr; - Vector forward; - int mask; - - if( fHull ) - { - mask = MASK_SOLID; - } - else - { - mask = MASK_SHOT; - } - - pMe->EyeVectors( &forward ); - UTIL_TraceLine(pMe->EyePosition(), - pMe->EyePosition() + forward * MAX_COORD_RANGE, - mask, pMe, COLLISION_GROUP_NONE, &tr ); - if ( tr.fraction != 1.0 && tr.DidHitNonWorldEntity() ) - { - return tr.m_pEnt; - } - } - return NULL; - -} - -//----------------------------------------------------------------------------- -// Purpose: Finds the nearest entity in front of the player of the given -// classname, preferring collidable entities, but allows selection of -// enities that are on the other side of walls or objects -// -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseEntity *FindPickerEntityClass( CBasePlayer *pPlayer, char *classname ) -{ - // First try to trace a hull to an entity - CBaseEntity *pEntity = FindEntityClassForward( pPlayer, classname ); - - // If that fails just look for the nearest facing entity - if (!pEntity) - { - Vector forward; - Vector origin; - pPlayer->EyeVectors( &forward ); - origin = pPlayer->WorldSpaceCenter(); - pEntity = gEntList.FindEntityClassNearestFacing( origin, forward,0.95,classname); - } - return pEntity; -} - -//----------------------------------------------------------------------------- -// Purpose: Finds the nearest entity in front of the player, preferring -// collidable entities, but allows selection of enities that are -// on the other side of walls or objects -// Input : -// Output : -//----------------------------------------------------------------------------- -CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ) -{ - // First try to trace a hull to an entity - CBaseEntity *pEntity = FindEntityForward( pPlayer, true ); - - // If that fails just look for the nearest facing entity - if (!pEntity) - { - Vector forward; - Vector origin; - pPlayer->EyeVectors( &forward ); - origin = pPlayer->WorldSpaceCenter(); - pEntity = gEntList.FindEntityNearestFacing( origin, forward,0.95); - } - return pEntity; -} - -//----------------------------------------------------------------------------- -// Purpose: Finds the nearest node in front of the player -// Input : -// Output : -//----------------------------------------------------------------------------- -CAI_Node *FindPickerAINode( CBasePlayer *pPlayer, NodeType_e nNodeType ) -{ - Vector forward; - Vector origin; - - pPlayer->EyeVectors( &forward ); - origin = pPlayer->EyePosition(); - return g_pAINetworkManager->GetEditOps()->FindAINodeNearestFacing( origin, forward,0.90, nNodeType); -} - -//----------------------------------------------------------------------------- -// Purpose: Finds the nearest link in front of the player -// Input : -// Output : -//----------------------------------------------------------------------------- -CAI_Link *FindPickerAILink( CBasePlayer* pPlayer ) -{ - Vector forward; - Vector origin; - - pPlayer->EyeVectors( &forward ); - origin = pPlayer->EyePosition(); - return g_pAINetworkManager->GetEditOps()->FindAILinkNearestFacing( origin, forward,0.90); -} - -/* -=============== -ForceClientDllUpdate - -When recording a demo, we need to have the server tell us the entire client state -so that the client side .dll can behave correctly. -Reset stuff so that the state is transmitted. -=============== -*/ -void CBasePlayer::ForceClientDllUpdate( void ) -{ - m_iClientBattery = -1; - m_iTrain |= TRAIN_NEW; // Force new train message. - m_fWeapon = false; // Force weapon send - - // Force all HUD data to be resent to client - m_fInitHUD = true; - - // Now force all the necessary messages - // to be sent. - UpdateClientData(); - - UTIL_RestartAmbientSounds(); // MOTODO that updates the sounds for everybody -} - -/* -============ -ImpulseCommands -============ -*/ - -void CBasePlayer::ImpulseCommands( ) -{ - trace_t tr; - - int iImpulse = (int)m_nImpulse; - switch (iImpulse) - { - case 100: - // temporary flashlight for level designers - if ( FlashlightIsOn() ) - { - FlashlightTurnOff(); - } - else - { - FlashlightTurnOn(); - } - break; - - case 200: - if ( sv_cheats->GetBool() ) - { - CBaseCombatWeapon *pWeapon; - - pWeapon = GetActiveWeapon(); - - if( pWeapon->IsEffectActive( EF_NODRAW ) ) - { - pWeapon->Deploy(); - } - else - { - pWeapon->Holster(); - } - } - break; - - case 201:// paint decal - - if ( gpGlobals->curtime < m_flNextDecalTime ) - { - // too early! - break; - } - - { - Vector forward; - EyeVectors( &forward ); - UTIL_TraceLine ( EyePosition(), - EyePosition() + forward * 128, - MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, & tr); - } - - if ( tr.fraction != 1.0 ) - {// line hit something, so paint a decal - m_flNextDecalTime = gpGlobals->curtime + decalfrequency.GetFloat(); - CSprayCan *pCan = CREATE_UNSAVED_ENTITY( CSprayCan, "spraycan" ); - pCan->Spawn( this ); - } - - break; - - case 202:// player jungle sound - if ( gpGlobals->curtime < m_flNextDecalTime ) - { - // too early! - break; - - } - - EntityMessageBegin( this ); - WRITE_BYTE( PLAY_PLAYER_JINGLE ); - MessageEnd(); - - m_flNextDecalTime = gpGlobals->curtime + decalfrequency.GetFloat(); - break; - - default: - // check all of the cheat impulse commands now - CheatImpulseCommands( iImpulse ); - break; - } - - m_nImpulse = 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -static void CreateJeep( CBasePlayer *pPlayer ) -{ - // Cheat to create a jeep in front of the player - Vector vecForward; - AngleVectors( pPlayer->EyeAngles(), &vecForward ); - CBaseEntity *pJeep = (CBaseEntity *)CreateEntityByName( "prop_vehicle_jeep" ); - if ( pJeep ) - { - Vector vecOrigin = pPlayer->GetAbsOrigin() + vecForward * 256 + Vector(0,0,64); - QAngle vecAngles( 0, pPlayer->GetAbsAngles().y - 90, 0 ); - pJeep->SetAbsOrigin( vecOrigin ); - pJeep->SetAbsAngles( vecAngles ); - pJeep->KeyValue( "model", "models/buggy.mdl" ); - pJeep->KeyValue( "solid", "6" ); - pJeep->KeyValue( "targetname", "jeep" ); - pJeep->KeyValue( "vehiclescript", "scripts/vehicles/jeep_test.txt" ); - pJeep->Spawn(); - pJeep->Activate(); - pJeep->Teleport( &vecOrigin, &vecAngles, NULL ); - } -} - - -void CC_CH_CreateJeep( void ) -{ - CBasePlayer *pPlayer = UTIL_GetCommandClient(); - if ( !pPlayer ) - return; - CreateJeep( pPlayer ); -} - -static ConCommand ch_createjeep("ch_createjeep", CC_CH_CreateJeep, "Spawn jeep in front of the player.", FCVAR_CHEAT); - - -//----------------------------------------------------------------------------- -// Create an airboat in front of the specified player -//----------------------------------------------------------------------------- -static void CreateAirboat( CBasePlayer *pPlayer ) -{ - // Cheat to create a jeep in front of the player - Vector vecForward; - AngleVectors( pPlayer->EyeAngles(), &vecForward ); - CBaseEntity *pJeep = ( CBaseEntity* )CreateEntityByName( "prop_vehicle_airboat" ); - if ( pJeep ) - { - Vector vecOrigin = pPlayer->GetAbsOrigin() + vecForward * 256 + Vector( 0,0,64 ); - QAngle vecAngles( 0, pPlayer->GetAbsAngles().y - 90, 0 ); - pJeep->SetAbsOrigin( vecOrigin ); - pJeep->SetAbsAngles( vecAngles ); - pJeep->KeyValue( "model", "models/airboat.mdl" ); - pJeep->KeyValue( "solid", "6" ); - pJeep->KeyValue( "targetname", "airboat" ); - pJeep->KeyValue( "vehiclescript", "scripts/vehicles/airboat.txt" ); - pJeep->Spawn(); - pJeep->Activate(); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CC_CH_CreateAirboat( void ) -{ - CBasePlayer *pPlayer = UTIL_GetCommandClient(); - if ( !pPlayer ) - return; - - CreateAirboat( pPlayer ); - -} - -static ConCommand ch_createairboat( "ch_createairboat", CC_CH_CreateAirboat, "Spawn airboat in front of the player.", FCVAR_CHEAT ); - - -//========================================================= -//========================================================= -void CBasePlayer::CheatImpulseCommands( int iImpulse ) -{ -#if !defined( HLDEMO_BUILD ) - if ( !sv_cheats->GetBool() ) - { - return; - } - - CBaseEntity *pEntity; - trace_t tr; - - switch ( iImpulse ) - { - case 76: - { - if (!giPrecacheGrunt) - { - giPrecacheGrunt = 1; - Msg( "You must now restart to use Grunt-o-matic.\n"); - } - else - { - Vector forward = UTIL_YawToVector( EyeAngles().y ); - Create("NPC_human_grunt", GetLocalOrigin() + forward * 128, GetLocalAngles()); - } - break; - } - - case 81: - - GiveNamedItem( "weapon_cubemap" ); - break; - - case 82: - // Cheat to create a jeep in front of the player - CreateJeep( this ); - break; - - case 83: - // Cheat to create a airboat in front of the player - CreateAirboat( this ); - break; - - case 101: - gEvilImpulse101 = true; - - EquipSuit(); - - // Give the player everything! - GiveAmmo( 255, "Pistol"); - GiveAmmo( 255, "AR2"); - GiveAmmo( 5, "AR2AltFire"); - GiveAmmo( 255, "SMG1"); - GiveAmmo( 255, "Buckshot"); - GiveAmmo( 3, "smg1_grenade"); - GiveAmmo( 3, "rpg_round"); - GiveAmmo( 5, "grenade"); - GiveAmmo( 32, "357" ); - GiveAmmo( 16, "XBowBolt" ); -#ifdef HL2_EPISODIC - GiveAmmo( 5, "Hopwire" ); -#endif - GiveNamedItem( "weapon_smg1" ); - GiveNamedItem( "weapon_frag" ); - GiveNamedItem( "weapon_crowbar" ); - GiveNamedItem( "weapon_pistol" ); - GiveNamedItem( "weapon_ar2" ); - GiveNamedItem( "weapon_shotgun" ); - GiveNamedItem( "weapon_physcannon" ); - GiveNamedItem( "weapon_bugbait" ); - GiveNamedItem( "weapon_rpg" ); - GiveNamedItem( "weapon_357" ); - GiveNamedItem( "weapon_crossbow" ); -#ifdef HL2_EPISODIC - //GiveNamedItem( "weapon_hopwire" ); -#endif - if ( GetHealth() < 100 ) - { - TakeHealth( 25, DMG_GENERIC ); - } - - gEvilImpulse101 = false; - - break; - - case 102: - // Gibbage!!! - CGib::SpawnRandomGibs( this, 1, GIB_HUMAN ); - break; - - case 103: - // What the hell are you doing? - pEntity = FindEntityForward( this, true ); - if ( pEntity ) - { - CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); - if ( pNPC ) - pNPC->ReportAIState(); - } - break; - - case 106: - // Give me the classname and targetname of this entity. - pEntity = FindEntityForward( this, true ); - if ( pEntity ) - { - Msg( "Classname: %s", pEntity->GetClassname() ); - - if ( pEntity->GetEntityName() != NULL_STRING ) - { - Msg( " - Name: %s\n", STRING( pEntity->GetEntityName() ) ); - } - else - { - Msg( " - Name: No Targetname\n" ); - } - - if ( pEntity->m_iParent != NULL_STRING ) - Msg( "Parent: %s\n", STRING(pEntity->m_iParent) ); - - Msg( "Model: %s\n", STRING( pEntity->GetModelName() ) ); - if ( pEntity->m_iGlobalname != NULL_STRING ) - Msg( "Globalname: %s\n", STRING(pEntity->m_iGlobalname) ); - } - break; - - case 107: - { - trace_t tr; - - edict_t *pWorld = engine->PEntityOfEntIndex( 0 ); - - Vector start = EyePosition(); - Vector forward; - EyeVectors( &forward ); - Vector end = start + forward * 1024; - UTIL_TraceLine( start, end, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); - if ( tr.m_pEnt ) - pWorld = tr.m_pEnt->edict(); - - const char *pTextureName = tr.surface.name; - - if ( pTextureName ) - Msg( "Texture: %s\n", pTextureName ); - } - break; - - // - // Sets the debug NPC to be the NPC under the crosshair. - // - case 108: - { - pEntity = FindEntityForward( this, true ); - if ( pEntity ) - { - CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); - if ( pNPC != NULL ) - { - Msg( "Debugging %s (0x%x)\n", pNPC->GetClassname(), pNPC ); - CAI_BaseNPC::SetDebugNPC( pNPC ); - } - } - break; - } - - case 195:// show shortest paths for entire level to nearest node - { - Create("node_viewer_fly", GetLocalOrigin(), GetLocalAngles()); - } - break; - case 196:// show shortest paths for entire level to nearest node - { - Create("node_viewer_large", GetLocalOrigin(), GetLocalAngles()); - } - break; - case 197:// show shortest paths for entire level to nearest node - { - Create("node_viewer_human", GetLocalOrigin(), GetLocalAngles()); - } - break; - case 202:// Random blood splatter - { - Vector forward; - EyeVectors( &forward ); - UTIL_TraceLine ( EyePosition(), - EyePosition() + forward * 128, - MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, & tr); - - if ( tr.fraction != 1.0 ) - {// line hit something, so paint a decal - CBloodSplat *pBlood = CREATE_UNSAVED_ENTITY( CBloodSplat, "bloodsplat" ); - pBlood->Spawn( this ); - } - } - break; - case 203:// remove creature. - pEntity = FindEntityForward( this, true ); - if ( pEntity ) - { - UTIL_Remove( pEntity ); -// if ( pEntity->m_takedamage ) -// pEntity->SetThink(SUB_Remove); - } - break; - } -#endif // HLDEMO_BUILD -} - - -bool CBasePlayer::ClientCommand(const char *cmd) -{ -#ifdef _DEBUG - if( stricmp( cmd, "test_SmokeGrenade" ) == 0 ) - { - if ( sv_cheats && sv_cheats->GetBool() ) - { - ParticleSmokeGrenade *pSmoke = dynamic_cast( CreateEntityByName(PARTICLESMOKEGRENADE_ENTITYNAME) ); - if ( pSmoke ) - { - Vector vForward; - AngleVectors( GetLocalAngles(), &vForward ); - vForward.z = 0; - VectorNormalize( vForward ); - - pSmoke->SetLocalOrigin( GetLocalOrigin() + vForward * 100 ); - pSmoke->SetFadeTime(25, 30); // Fade out between 25 seconds and 30 seconds. - pSmoke->Activate(); - pSmoke->SetLifetime(30); - pSmoke->FillVolume(); - - return true; - } - } - } - else -#endif // _DEBUG - if( stricmp( cmd, "vehicleRole" ) == 0 ) - { - // Get the vehicle role value. - if ( engine->Cmd_Argc() == 2 ) - { - // Check to see if a player is in a vehicle. - if ( IsInAVehicle() ) - { - int nRole = atoi( engine->Cmd_Argv( 1 ) ); - IServerVehicle *pVehicle = GetVehicle(); - if ( pVehicle ) - { - // Only switch roles if role is empty! - if ( !pVehicle->GetPassenger( nRole ) ) - { - LeaveVehicle(); - GetInVehicle( pVehicle, nRole ); - } - } - } - - return true; - } - } - else if ( stricmp( cmd, "spectate" ) == 0 ) // join spectator team & start observer mode - { - if ( GetTeamNumber() == TEAM_SPECTATOR ) - return true; - - if ( !IsDead() ) - { - ClientKill( edict() ); // kill player - - // add 1 to frags to balance out the 1 subtracted for killing yourself - IncrementFragCount( 1 ); - } - - RemoveAllItems( true ); - - ChangeTeam( TEAM_SPECTATOR ); - - StartObserverMode( OBS_MODE_ROAMING ); - return true; - } - else if ( stricmp( cmd, "spec_mode" ) == 0 ) // new observer mode - { - int mode; - - // check for parameters. - if ( engine->Cmd_Argc() >= 2 ) - { - mode = atoi( engine->Cmd_Argv(1) ); - - if ( mode < OBS_MODE_IN_EYE || mode > OBS_MODE_ROAMING ) - mode = OBS_MODE_IN_EYE; - } - else - { - // sitch to next spec mode if no parameter give - mode = GetObserverMode() + 1; - - if ( mode > OBS_MODE_ROAMING ) - { - mode = OBS_MODE_IN_EYE; - } - else if ( mode < OBS_MODE_IN_EYE ) - { - mode = OBS_MODE_ROAMING; - } - - } - - // don't allow input while player or death cam animation - if ( GetObserverMode() > OBS_MODE_DEATHCAM ) - { - // set new spectator mode, don't allow OBS_MODE_NONE - if ( !SetObserverMode( mode ) ) - ClientPrint( this, HUD_PRINTCONSOLE, "#Spectator_Mode_Unkown"); - else - engine->ClientCommand( edict(), "cl_spec_mode %d", mode ); - } - else - { - // remember spectator mode for later use - m_iObserverLastMode = mode; - engine->ClientCommand( edict(), "cl_spec_mode %d", mode ); - } - - return true; - } - else if ( stricmp( cmd, "spec_next" ) == 0 ) // chase next player - { - if ( GetObserverMode() > OBS_MODE_FIXED ) - { - // set new spectator mode - CBaseEntity * target = FindNextObserverTarget( false ); - if ( target ) - SetObserverTarget( target ); - } - - return true; - } - else if ( stricmp( cmd, "spec_prev" ) == 0 ) // chase prevoius player - { - if ( GetObserverMode() > OBS_MODE_FIXED ) - { - // set new spectator mode - CBaseEntity * target = FindNextObserverTarget( true ); - if ( target ) - SetObserverTarget( target ); - } - - return true; - } - - else if ( stricmp( cmd, "spec_player" ) == 0 ) // chase next player - { - if ( GetObserverMode() > OBS_MODE_FIXED && - engine->Cmd_Argc() == 2 ) - { - int index = atoi( engine->Cmd_Argv(1) ); - - CBasePlayer * target; - - if ( index == 0 ) - { - target = UTIL_PlayerByName( engine->Cmd_Argv(1) ); - } - else - { - target = UTIL_PlayerByIndex( index ); - } - - if ( IsValidObserverTarget( target ) ) - { - SetObserverTarget( target ); - } - } - - return true; - } - - else if ( stricmp( cmd, "spec_goto" ) == 0 ) // chase next player - { - if ( ( GetObserverMode() == OBS_MODE_FIXED || - GetObserverMode() == OBS_MODE_ROAMING ) && - engine->Cmd_Argc() == 6 ) - { - Vector origin; - origin.x = atof( engine->Cmd_Argv(1) ); - origin.y = atof( engine->Cmd_Argv(2) ); - origin.z = atof( engine->Cmd_Argv(3) ); - - QAngle angle; - angle.x = atof( engine->Cmd_Argv(4) ); - angle.y = atof( engine->Cmd_Argv(5) ); - angle.z = 0.0f; - - JumptoPosition( origin, angle ); - } - - return true; - } - - - - return false; -} - -extern bool UTIL_ItemCanBeTouchedByPlayer( CBaseEntity *pItem, CBasePlayer *pPlayer ); - -//----------------------------------------------------------------------------- -// Purpose: Player reacts to bumping a weapon. -// Input : pWeapon - the weapon that the player bumped into. -// Output : Returns true if player picked up the weapon -//----------------------------------------------------------------------------- -bool CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) -{ - CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); - - // Can I have this weapon type? - if ( IsEFlagSet( EFL_NO_WEAPON_PICKUP ) ) - return false; - - if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) - { - if ( gEvilImpulse101 ) - { - UTIL_Remove( pWeapon ); - } - return false; - } - - // Act differently in the episodes - if ( hl2_episodic.GetBool() ) - { - // Don't let the player touch the item unless unobstructed - if ( !UTIL_ItemCanBeTouchedByPlayer( pWeapon, this ) && !gEvilImpulse101 ) - return false; - } - else - { - // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows) - if( pWeapon->FVisible( this, MASK_SOLID ) == false && !(GetFlags() & FL_NOTARGET) ) - return false; - } - - // ---------------------------------------- - // If I already have it just take the ammo - // ---------------------------------------- - if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType())) - { - if( Weapon_EquipAmmoOnly( pWeapon ) ) - { - // Only remove me if I have no ammo left - if ( pWeapon->HasPrimaryAmmo() ) - return false; - - UTIL_Remove( pWeapon ); - return true; - } - else - { - return false; - } - } - // ------------------------- - // Otherwise take the weapon - // ------------------------- - else - { - pWeapon->CheckRespawn(); - - pWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); - pWeapon->AddEffects( EF_NODRAW ); - - Weapon_Equip( pWeapon ); - if ( IsInAVehicle() ) - { - pWeapon->Holster(); - } - else - { -#ifdef HL2_DLL - -#ifdef _XBOX - CFmtStr hint; - hint.sprintf( "#valve_hint_select_%s", pWeapon->GetClassname() ); - UTIL_HudHintText( this, hint.Access() ); -#endif // _XBOX - - // Always switch to a newly-picked up weapon - if ( !PlayerHasMegaPhysCannon() ) - { - // If it uses clips, load it full. (this is the first time you've picked up this type of weapon) - if ( pWeapon->UsesClipsForAmmo1() ) - { - pWeapon->m_iClip1 = pWeapon->GetMaxClip1(); - } - - Weapon_Switch( pWeapon ); - } -#endif - } - return true; - } -} - - -bool CBasePlayer::RemovePlayerItem( CBaseCombatWeapon *pItem ) -{ - if (GetActiveWeapon() == pItem) - { - ResetAutoaim( ); - pItem->Holster( ); - pItem->SetNextThink( TICK_NEVER_THINK );; // crowbar may be trying to swing again, etc - pItem->SetThink( NULL ); - } - - if ( m_hLastWeapon.Get() == pItem ) - { - Weapon_SetLast( NULL ); - } - - return Weapon_Detach( pItem ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Hides or shows the player's view model. The "r_drawviewmodel" cvar -// can still hide the viewmodel even if this is set to true. -// Input : bShow - true to show, false to hide the view model. -//----------------------------------------------------------------------------- -void CBasePlayer::ShowViewModel(bool bShow) -{ - m_Local.m_bDrawViewmodel = bShow; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : bDraw - -//----------------------------------------------------------------------------- -void CBasePlayer::ShowCrosshair( bool bShow ) -{ - if ( bShow ) - { - m_Local.m_iHideHUD &= ~HIDEHUD_CROSSHAIR; - } - else - { - m_Local.m_iHideHUD |= HIDEHUD_CROSSHAIR; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -QAngle CBasePlayer::BodyAngles() -{ - return EyeAngles(); -} - -//------------------------------------------------------------------------------ -// Purpose : Add noise to BodyTarget() to give enemy a better chance of -// getting a clear shot when the player is peeking above a hole -// or behind a ladder (eventually the randomly-picked point -// along the spine will be one that is exposed above the hole or -// between rungs of a ladder.) -// Input : -// Output : -//------------------------------------------------------------------------------ -Vector CBasePlayer::BodyTarget( const Vector &posSrc, bool bNoisy ) -{ - if ( IsInAVehicle() ) - { - return GetVehicle()->GetVehicleEnt()->BodyTarget( posSrc, bNoisy ); - } - if (bNoisy) - { - return GetAbsOrigin() + (GetViewOffset() * random->RandomFloat( 0.7, 1.0 )); - } - else - { - return EyePosition(); - } -}; - -/* -========================================================= - UpdateClientData - -resends any changed player HUD info to the client. -Called every frame by PlayerPreThink -Also called at start of demo recording and playback by -ForceClientDllUpdate to ensure the demo gets messages -reflecting all of the HUD state info. -========================================================= -*/ -void CBasePlayer::UpdateClientData( void ) -{ - CSingleUserRecipientFilter user( this ); - user.MakeReliable(); - - if (m_fInitHUD) - { - m_fInitHUD = false; - gInitHUD = false; - - UserMessageBegin( user, "ResetHUD" ); - WRITE_BYTE( 0 ); - MessageEnd(); - - if ( !m_fGameHUDInitialized ) - { - g_pGameRules->InitHUD( this ); - InitHUD(); - m_fGameHUDInitialized = true; - if ( g_pGameRules->IsMultiplayer() ) - { - variant_t value; - g_EventQueue.AddEvent( "game_player_manager", "OnPlayerJoin", value, 0, this, this ); - } - } - - variant_t value; - g_EventQueue.AddEvent( "game_player_manager", "OnPlayerSpawn", value, 0, this, this ); - } - - // HACKHACK -- send the message to display the game title - CWorld *world = GetWorldEntity(); - if ( world && world->GetDisplayTitle() ) - { - UserMessageBegin( user, "GameTitle" ); - MessageEnd(); - world->SetDisplayTitle( false ); - } - - if (m_ArmorValue != m_iClientBattery) - { - m_iClientBattery = m_ArmorValue; - - // send "battery" update message - if ( usermessages->LookupUserMessage( "Battery" ) != -1 ) - { - UserMessageBegin( user, "Battery" ); - WRITE_SHORT( (int)m_ArmorValue); - MessageEnd(); - } - } - -#if 0 // BYE BYE!! - // Update Flashlight - if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->curtime)) - { - if (FlashlightIsOn()) - { - if (m_iFlashBattery) - { - m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->curtime; - m_iFlashBattery--; - - if (!m_iFlashBattery) - FlashlightTurnOff(); - } - } - else - { - if (m_iFlashBattery < 100) - { - m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->curtime; - m_iFlashBattery++; - } - else - m_flFlashLightTime = 0; - } - } -#endif - - CheckTrainUpdate(); - - // Update all the items - for ( int i = 0; i < WeaponCount(); i++ ) - { - if ( GetWeapon(i) ) // each item updates it's successors - GetWeapon(i)->UpdateClientData( this ); - } - - // update the client with our poison state - m_Local.m_bPoisoned = ( m_bitsDamageType & DMG_POISON ) - && ( m_nPoisonDmg > m_nPoisonRestored ) - && ( m_iHealth < 100 ); - - // Let any global rules update the HUD, too - g_pGameRules->UpdateClientData( this ); -} - -#ifdef _XBOX -void CBasePlayer::RumbleEffect( unsigned char index, unsigned char rumbleData, unsigned char rumbleFlags ) -{ - if( !IsAlive() ) - return; - - CSingleUserRecipientFilter filter( this ); - filter.MakeReliable(); - - UserMessageBegin( filter, "XBoxRumble" ); - WRITE_BYTE( index ); - WRITE_BYTE( rumbleData ); - WRITE_BYTE( rumbleFlags ); - MessageEnd(); -} -#else -void CBasePlayer::RumbleEffect( unsigned char index, unsigned char rumbleData, unsigned char rumbleFlags ) -{ - return; -} -#endif//_XBOX - - -void CBasePlayer::EnableControl(bool fControl) -{ - if (!fControl) - AddFlag( FL_FROZEN ); - else - RemoveFlag( FL_FROZEN ); - -} - -void CBasePlayer::CheckTrainUpdate( void ) -{ - if ( IsPC() && ( m_iTrain & TRAIN_NEW ) ) - { - CSingleUserRecipientFilter user( this ); - user.MakeReliable(); - - // send "Train" update message - UserMessageBegin( user, "Train" ); - WRITE_BYTE(m_iTrain & 0xF); - MessageEnd(); - - m_iTrain &= ~TRAIN_NEW; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Returns whether the player should autoaim or not -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBasePlayer::ShouldAutoaim( void ) -{ - // cannot be in multiplayer - if ( gpGlobals->maxClients > 1 ) - return false; - - // autoaiming is only for easy and medium skill - return ( IsXbox() || !g_pGameRules->IsSkillLevel(SKILL_HARD) ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -Vector CBasePlayer::GetAutoaimVector( float flScale ) -{ - autoaim_params_t params; - - params.m_fScale = flScale; - params.m_fMaxDist = autoaim_max_dist.GetFloat(); - - GetAutoaimVector( params ); - return params.m_vecAutoAimDir; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -Vector CBasePlayer::GetAutoaimVector( float flScale, float flMaxDist ) -{ - autoaim_params_t params; - - params.m_fScale = flScale; - params.m_fMaxDist = flMaxDist; - - GetAutoaimVector( params ); - return params.m_vecAutoAimDir; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CBasePlayer::GetAutoaimVector( autoaim_params_t ¶ms ) -{ - // Assume autoaim will not be assisting. - params.m_bAutoAimAssisting = false; - - if ( ( ShouldAutoaim() == false ) || ( params.m_fScale == AUTOAIM_SCALE_DIRECT_ONLY ) ) - { - Vector forward; - AngleVectors( EyeAngles() + m_Local.m_vecPunchAngle, &forward ); - - params.m_vecAutoAimDir = forward; - params.m_hAutoAimEntity.Set(NULL); - params.m_vecAutoAimPoint = vec3_invalid; - params.m_bAutoAimAssisting = false; - return; - } - - Vector vecSrc = Weapon_ShootPosition( ); - - m_vecAutoAim.Init( 0.0f, 0.0f, 0.0f ); - - QAngle angles = AutoaimDeflection( vecSrc, params ); - - // update ontarget if changed - if ( !g_pGameRules->AllowAutoTargetCrosshair() ) - m_fOnTarget = false; - - if (angles.x > 180) - angles.x -= 360; - if (angles.x < -180) - angles.x += 360; - if (angles.y > 180) - angles.y -= 360; - if (angles.y < -180) - angles.y += 360; - - if (angles.x > 25) - angles.x = 25; - if (angles.x < -25) - angles.x = -25; - if (angles.y > 12) - angles.y = 12; - if (angles.y < -12) - angles.y = -12; - - Vector forward; - - if( IsXbox() && IsInAVehicle() ) - { - m_vecAutoAim = angles; - AngleVectors( EyeAngles() + m_vecAutoAim, &forward ); - } - else - { - // always use non-sticky autoaim - m_vecAutoAim = angles * 0.9f; - AngleVectors( EyeAngles() + m_Local.m_vecPunchAngle + m_vecAutoAim, &forward ); - } - - params.m_vecAutoAimDir = forward; -} - -//----------------------------------------------------------------------------- -// Targets represent themselves to autoaim as a viewplane-parallel disc with -// a radius specified by the target. The player then modifies this radius -// to achieve more or less aggressive aiming assistance -//----------------------------------------------------------------------------- -float CBasePlayer::GetAutoaimScore( const Vector &eyePosition, const Vector &viewDir, const Vector &vecTarget, CBaseEntity *pTarget, float fScale ) -{ - float radiusSqr; - float targetRadiusSqr = Square( (pTarget->GetAutoAimRadius() * fScale) ); - - Vector vecNearestPoint = PointOnLineNearestPoint( eyePosition, eyePosition + viewDir * 8192, vecTarget ); - Vector vecDiff = vecTarget - vecNearestPoint; - - radiusSqr = vecDiff.LengthSqr(); - - if( radiusSqr <= targetRadiusSqr ) - { - float score; - - score = 1.0f - (radiusSqr / targetRadiusSqr); - - Assert( score >= 0.0f && score <= 1.0f ); - return score; - } - - // 0 means no score- doesn't qualify for autoaim. - return 0.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &vecSrc - -// flDist - -// flDelta - -// Output : Vector -//----------------------------------------------------------------------------- -QAngle CBasePlayer::AutoaimDeflection( Vector &vecSrc, autoaim_params_t ¶ms ) -{ - float bestscore; - float score; - QAngle eyeAngles; - Vector bestdir; - CBaseEntity *bestent; - trace_t tr; - Vector v_forward, v_right, v_up; - - if ( ShouldAutoaim() == false ) - { - m_fOnTarget = false; - return vec3_angle; - } - - eyeAngles = EyeAngles(); - AngleVectors( eyeAngles + m_Local.m_vecPunchAngle + m_vecAutoAim, &v_forward, &v_right, &v_up ); - - // try all possible entities - bestdir = v_forward; - bestscore = 0.0f; - bestent = NULL; - - //Reset this data - m_fOnTarget = false; - params.m_bOnTargetNatural = false; - - CBaseEntity *pIgnore = NULL; - - if( IsInAVehicle() ) - { - pIgnore = GetVehicleEntity(); - } - - CTraceFilterSkipTwoEntities traceFilter( this, pIgnore, COLLISION_GROUP_NONE ); - - UTIL_TraceLine( vecSrc, vecSrc + bestdir * MAX_COORD_FLOAT, MASK_SHOT, &traceFilter, &tr ); - - CBaseEntity *pEntHit = tr.m_pEnt; - - if ( pEntHit && pEntHit->m_takedamage != DAMAGE_NO && pEntHit->GetHealth() > 0 ) - { - // don't look through water - if (!((GetWaterLevel() != 3 && pEntHit->GetWaterLevel() == 3) || (GetWaterLevel() == 3 && pEntHit->GetWaterLevel() == 0))) - { - if( pEntHit->GetFlags() & FL_AIMTARGET ) - { - bool bAimAtThis = true; - - if( UseXboxAiming() && pEntHit->IsNPC() ) - { - int iRelationType = GetDefaultRelationshipDisposition( pEntHit->Classify() ); - - if( iRelationType != D_HT ) - { - bAimAtThis = false; - } - } - - if( bAimAtThis ) - { - if ( pEntHit->GetFlags() & FL_AIMTARGET ) - { - m_fOnTarget = true; - } - - // Player is already on target naturally, don't autoaim. - // Fill out the autoaim_params_t struct, though. - params.m_hAutoAimEntity.Set(pEntHit); - params.m_vecAutoAimDir = bestdir; - params.m_vecAutoAimPoint = tr.endpos; - params.m_bAutoAimAssisting = false; - params.m_bOnTargetNatural = true; - } - } - - return vec3_angle; - } - } - - int count = AimTarget_ListCount(); - if ( count ) - { - CBaseEntity **pList = (CBaseEntity **)stackalloc( sizeof(CBaseEntity *) * count ); - AimTarget_ListCopy( pList, count ); - - for ( int i = 0; i < count; i++ ) - { - Vector center; - Vector dir; - CBaseEntity *pEntity = pList[i]; - - // Don't shoot yourself - if ( pEntity == this ) - continue; - - if (!pEntity->IsAlive() || !pEntity->edict() ) - continue; - - if ( !g_pGameRules->ShouldAutoAim( this, pEntity->edict() ) ) - continue; - - // don't look through water - if ((GetWaterLevel() != 3 && pEntity->GetWaterLevel() == 3) || (GetWaterLevel() == 3 && pEntity->GetWaterLevel() == 0)) - continue; - - if( pEntity->MyNPCPointer() ) - { - // If this entity is an NPC, only aim if it is an enemy. - if ( IRelationType( pEntity ) != D_HT && !pEntity->ShouldAttractAutoAim(this) ) - { - if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) - // Msg( "friend\n"); - continue; - } - } - - // Don't autoaim at the noisy bodytarget, this makes the autoaim crosshair wobble. - //center = pEntity->BodyTarget( vecSrc, false ); - center = pEntity->WorldSpaceCenter(); - - dir = (center - vecSrc); - - float dist = dir.Length2D(); - VectorNormalize( dir ); - - // Skip if out of range. - if( dist > params.m_fMaxDist ) - continue; - - float dot = DotProduct (dir, v_forward ); - - // make sure it's in front of the player - if( dot < 0 ) - continue; - - if( !(pEntity->GetFlags() & FL_FLY) ) - { - // Refuse to take wild shots at targets far from reticle. - if( GetActiveWeapon() != NULL && dot < GetActiveWeapon()->GetMaxAutoAimDeflection() ) - { - continue; - } - } - - score = GetAutoaimScore(vecSrc, v_forward, pEntity->GetAutoAimCenter(), pEntity, params.m_fScale); - - if( score <= bestscore ) - { - continue; - } - - UTIL_TraceLine( vecSrc, center, MASK_SHOT, &traceFilter, &tr ); - - if (tr.fraction != 1.0 && tr.m_pEnt != pEntity ) - { - // Msg( "hit %s, can't see %s\n", STRING( tr.u.ent->classname ), STRING( pEdict->classname ) ); - continue; - } - - // This is the best candidate so far. - bestscore = score; - bestent = pEntity; - bestdir = dir; - } - if ( bestent ) - { - QAngle bestang; - - VectorAngles( bestdir, bestang ); - - if( IsInAVehicle() ) - { - bestang -= EyeAngles(); - } - else - { - bestang -= EyeAngles() - m_Local.m_vecPunchAngle; - } - - m_fOnTarget = true; - - // Autoaim detected a target for us. Aim automatically at its bodytarget. - params.m_hAutoAimEntity.Set(bestent); - params.m_vecAutoAimDir = bestdir; - params.m_vecAutoAimPoint = bestent->BodyTarget( vecSrc, false ); - params.m_bAutoAimAssisting = true; - - return bestang; - } - } - - return QAngle( 0, 0, 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::ResetAutoaim( void ) -{ - if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) - { - m_vecAutoAim = QAngle( 0, 0, 0 ); - engine->CrosshairAngle( edict(), 0, 0 ); - } - m_fOnTarget = false; -} - -// ========================================================================== -// > Weapon stuff -// ========================================================================== - -//----------------------------------------------------------------------------- -// Purpose: Override base class, player can always use weapon -// Input : A weapon -// Output : true or false -//----------------------------------------------------------------------------- -bool CBasePlayer::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) -{ - return true; -} - - - -//----------------------------------------------------------------------------- -// Purpose: Override to clear dropped weapon from the hud -//----------------------------------------------------------------------------- -void CBasePlayer::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget /* = NULL */, const Vector *pVelocity /* = NULL */ ) -{ - bool bWasActiveWeapon = false; - if ( pWeapon == GetActiveWeapon() ) - { - bWasActiveWeapon = true; - } - - if ( pWeapon ) - { - if ( bWasActiveWeapon ) - { - pWeapon->SendWeaponAnim( ACT_VM_IDLE ); - } - } - - BaseClass::Weapon_Drop( pWeapon, pvecTarget, pVelocity ); - - if ( bWasActiveWeapon ) - { - if (!SwitchToNextBestWeapon( NULL )) - { - CBaseViewModel *vm = GetViewModel(); - if ( vm ) - { - vm->AddEffects( EF_NODRAW ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : weaponSlot - -//----------------------------------------------------------------------------- -void CBasePlayer::Weapon_DropSlot( int weaponSlot ) -{ - CBaseCombatWeapon *pWeapon; - - // Check for that slot being occupied already - for ( int i=0; i < MAX_WEAPONS; i++ ) - { - pWeapon = GetWeapon( i ); - - if ( pWeapon != NULL ) - { - // If the slots match, it's already occupied - if ( pWeapon->GetSlot() == weaponSlot ) - { - Weapon_Drop( pWeapon, NULL, NULL ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Override to add weapon to the hud -//----------------------------------------------------------------------------- -void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) -{ - BaseClass::Weapon_Equip( pWeapon ); - - bool bShouldSwitch = g_pGameRules->FShouldSwitchWeapon( this, pWeapon ); - -#ifdef HL2_DLL - if ( bShouldSwitch == false && PhysCannonGetHeldEntity( GetActiveWeapon() ) == pWeapon && - Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()) ) - { - bShouldSwitch = true; - } -#endif//HL2_DLL - - // should we switch to this item? - if ( bShouldSwitch ) - { - Weapon_Switch( pWeapon ); - } -} - - -//========================================================= -// HasNamedPlayerItem Does the player already have this item? -//========================================================= -CBaseEntity *CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) -{ - for ( int i = 0 ; i < WeaponCount() ; i++ ) - { - if ( !GetWeapon(i) ) - continue; - - if ( FStrEq( pszItemName, GetWeapon(i)->GetClassname() ) ) - { - return GetWeapon(i); - } - } - - return NULL; -} - - - -//================================================================================ -// TEAM HANDLING -//================================================================================ -//----------------------------------------------------------------------------- -// Purpose: Put the player in the specified team -//----------------------------------------------------------------------------- - -void CBasePlayer::ChangeTeam( int iTeamNum ) -{ - if ( !GetGlobalTeam( iTeamNum ) ) - { - Warning( "CBasePlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum ); - return; - } - - // if this is our current team, just abort - if ( iTeamNum == GetTeamNumber() ) - { - return; - } - - // Immediately tell all clients that he's changing team. This has to be done - // first, so that all user messages that follow as a result of the team change - // come after this one, allowing the client to be prepared for them. - IGameEvent * event = gameeventmanager->CreateEvent( "player_team" ); - if ( event ) - { - event->SetInt("userid", GetUserID() ); - event->SetInt("team", iTeamNum ); - event->SetInt("oldteam", GetTeamNumber() ); - event->SetInt("disconnect", IsDisconnecting()); - - gameeventmanager->FireEvent( event ); - } - - // Remove him from his current team - if ( GetTeam() ) - { - GetTeam()->RemovePlayer( this ); - } - - // Are we being added to a team? - if ( iTeamNum ) - { - GetGlobalTeam( iTeamNum )->AddPlayer( this ); - } - - BaseClass::ChangeTeam( iTeamNum ); -} - - - -//----------------------------------------------------------------------------- -// Purpose: Locks a player to the spot; they can't move, shoot, or be hurt -//----------------------------------------------------------------------------- -void CBasePlayer::LockPlayerInPlace( void ) -{ - if ( m_iPlayerLocked ) - return; - - AddFlag( FL_GODMODE | FL_FROZEN ); - SetMoveType( MOVETYPE_NONE ); - m_iPlayerLocked = true; - - // force a client data update, so that anything that has been done to - // this player previously this frame won't get delayed in being sent - UpdateClientData(); -} - -//----------------------------------------------------------------------------- -// Purpose: Unlocks a previously locked player -//----------------------------------------------------------------------------- -void CBasePlayer::UnlockPlayer( void ) -{ - if ( !m_iPlayerLocked ) - return; - - RemoveFlag( FL_GODMODE | FL_FROZEN ); - SetMoveType( MOVETYPE_WALK ); - m_iPlayerLocked = false; -} - -bool CBasePlayer::ClearUseEntity() -{ - if ( m_hUseEntity != NULL ) - { - // Stop controlling the train/object - // TODO: Send HUD Update - m_hUseEntity->Use( this, this, USE_OFF, 0 ); - m_hUseEntity = NULL; - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::HideViewModels( void ) -{ - for ( int i = 0 ; i < MAX_VIEWMODELS; i++ ) - { - CBaseViewModel *vm = GetViewModel( i ); - if ( !vm ) - continue; - - vm->SetWeaponModel( NULL, NULL ); - } -} - -class CStripWeapons : public CPointEntity -{ - DECLARE_CLASS( CStripWeapons, CPointEntity ); -public: - void InputStripWeapons(inputdata_t &data); - void InputStripWeaponsAndSuit(inputdata_t &data); - - void StripWeapons(inputdata_t &data, bool stripSuit); - DECLARE_DATADESC(); -}; - -LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); - -BEGIN_DATADESC( CStripWeapons ) - DEFINE_INPUTFUNC( FIELD_VOID, "Strip", InputStripWeapons ), - DEFINE_INPUTFUNC( FIELD_VOID, "StripWeaponsAndSuit", InputStripWeaponsAndSuit ), -END_DATADESC() - - -void CStripWeapons::InputStripWeapons(inputdata_t &data) -{ - StripWeapons(data, false); -} - -void CStripWeapons::InputStripWeaponsAndSuit(inputdata_t &data) -{ - StripWeapons(data, true); -} - -void CStripWeapons::StripWeapons(inputdata_t &data, bool stripSuit) -{ - CBasePlayer *pPlayer = NULL; - - if ( data.pActivator && data.pActivator->IsPlayer() ) - { - pPlayer = (CBasePlayer *)data.pActivator; - } - else if ( !g_pGameRules->IsDeathmatch() ) - { - pPlayer = UTIL_GetLocalPlayer(); - } - - if ( pPlayer ) - { - pPlayer->RemoveAllItems( stripSuit ); - } -} - - -class CRevertSaved : public CPointEntity -{ - DECLARE_CLASS( CRevertSaved, CPointEntity ); -public: - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void LoadThink( void ); - - DECLARE_DATADESC(); - - inline float Duration( void ) { return m_Duration; } - inline float HoldTime( void ) { return m_HoldTime; } - inline float LoadTime( void ) { return m_loadTime; } - - inline void SetDuration( float duration ) { m_Duration = duration; } - inline void SetHoldTime( float hold ) { m_HoldTime = hold; } - inline void SetLoadTime( float time ) { m_loadTime = time; } - - //Inputs - void InputReload(inputdata_t &data); - -#ifdef HL1_DLL - void MessageThink( void ); - inline float MessageTime( void ) { return m_messageTime; } - inline void SetMessageTime( float time ) { m_messageTime = time; } -#endif - -private: - - float m_loadTime; - float m_Duration; - float m_HoldTime; - -#ifdef HL1_DLL - string_t m_iszMessage; - float m_messageTime; -#endif -}; - -LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); - -BEGIN_DATADESC( CRevertSaved ) - -#ifdef HL1_DLL - DEFINE_KEYFIELD( m_iszMessage, FIELD_STRING, "message" ), - DEFINE_KEYFIELD( m_messageTime, FIELD_FLOAT, "messagetime" ), // These are not actual times, but durations, so save as floats - - DEFINE_FUNCTION( MessageThink ), -#endif - - DEFINE_KEYFIELD( m_loadTime, FIELD_FLOAT, "loadtime" ), - DEFINE_KEYFIELD( m_Duration, FIELD_FLOAT, "duration" ), - DEFINE_KEYFIELD( m_HoldTime, FIELD_FLOAT, "holdtime" ), - - DEFINE_INPUTFUNC( FIELD_VOID, "Reload", InputReload ), - - - // Function Pointers - DEFINE_FUNCTION( LoadThink ), - -END_DATADESC() - -CBaseEntity *CreatePlayerLoadSave( Vector vOrigin, float flDuration, float flHoldTime, float flLoadTime ) -{ - CRevertSaved *pRevertSaved = (CRevertSaved *) CreateEntityByName( "player_loadsaved" ); - - if ( pRevertSaved == NULL ) - return NULL; - - UTIL_SetOrigin( pRevertSaved, vOrigin ); - - pRevertSaved->Spawn(); - pRevertSaved->SetDuration( flDuration ); - pRevertSaved->SetHoldTime( flHoldTime ); - pRevertSaved->SetLoadTime( flLoadTime ); - - return pRevertSaved; -} - - - -void CRevertSaved::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), FFADE_OUT ); - SetNextThink( gpGlobals->curtime + LoadTime() ); - SetThink( &CRevertSaved::LoadThink ); - - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - - if ( pPlayer ) - { - //Adrian: Setting this flag so we can't move or save a game. - pPlayer->pl.deadflag = true; - pPlayer->AddFlag( (FL_NOTARGET|FL_FROZEN) ); - } -} - -void CRevertSaved::InputReload( inputdata_t &inputdata ) -{ - UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), FFADE_OUT ); - -#ifdef HL1_DLL - SetNextThink( gpGlobals->curtime + MessageTime() ); - SetThink( &CRevertSaved::MessageThink ); -#else - SetNextThink( gpGlobals->curtime + LoadTime() ); - SetThink( &CRevertSaved::LoadThink ); -#endif - - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - - if ( pPlayer ) - { - //Adrian: Setting this flag so we can't move or save a game. - pPlayer->pl.deadflag = true; - pPlayer->AddFlag( (FL_NOTARGET|FL_FROZEN) ); - } -} - -#ifdef HL1_DLL -void CRevertSaved::MessageThink( void ) -{ - UTIL_ShowMessageAll( STRING( m_iszMessage ) ); - float nextThink = LoadTime() - MessageTime(); - if ( nextThink > 0 ) - { - SetNextThink( gpGlobals->curtime + nextThink ); - SetThink( &CRevertSaved::LoadThink ); - } - else - LoadThink(); -} -#endif - - -void CRevertSaved::LoadThink( void ) -{ - if ( !gpGlobals->deathmatch ) - { - engine->ServerCommand("reload\n"); - } -} - -class CMovementSpeedMod : public CPointEntity -{ - DECLARE_CLASS( CMovementSpeedMod, CPointEntity ); -public: - void InputSpeedMod(inputdata_t &data); - - DECLARE_DATADESC(); -}; - -LINK_ENTITY_TO_CLASS( player_speedmod, CMovementSpeedMod ); - -BEGIN_DATADESC( CMovementSpeedMod ) - DEFINE_INPUTFUNC( FIELD_FLOAT, "ModifySpeed", InputSpeedMod ), -END_DATADESC() - - -void CMovementSpeedMod::InputSpeedMod(inputdata_t &data) -{ - CBasePlayer *pPlayer = NULL; - - if ( data.pActivator && data.pActivator->IsPlayer() ) - { - pPlayer = (CBasePlayer *)data.pActivator; - } - else if ( !g_pGameRules->IsDeathmatch() ) - { - pPlayer = UTIL_GetLocalPlayer(); - } - - if ( pPlayer ) - { - pPlayer->SetLaggedMovementValue( data.value.Float() ); - } -} - - -void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID) -{ - int mask = (1<m_Int = ( data & mask ); -} -// -------------------------------------------------------------------------------- // -// SendTable for CPlayerState. -// -------------------------------------------------------------------------------- // - - BEGIN_SEND_TABLE_NOBASE(CPlayerState, DT_PlayerState) - SendPropInt (SENDINFO(deadflag), 1, SPROP_UNSIGNED ), - END_SEND_TABLE() - -// -------------------------------------------------------------------------------- // -// This data only gets sent to clients that ARE this player entity. -// -------------------------------------------------------------------------------- // - - BEGIN_SEND_TABLE_NOBASE( CBasePlayer, DT_LocalPlayerExclusive ) - - SendPropDataTable ( SENDINFO_DT(m_Local), &REFERENCE_SEND_TABLE(DT_Local) ), - -// If HL2_DLL is defined, then baseflex.cpp already sends these. -#ifndef HL2_DLL - SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 0), 8, SPROP_ROUNDDOWN, -32.0, 32.0f), - SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 1), 8, SPROP_ROUNDDOWN, -32.0, 32.0f), - SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 2), 10, SPROP_CHANGES_OFTEN, 0.0f, 128.0f), -#endif - - SendPropFloat ( SENDINFO(m_flFriction), 8, SPROP_ROUNDDOWN, 0.0f, 4.0f), - - SendPropArray3 ( SENDINFO_ARRAY3(m_iAmmo), SendPropInt( SENDINFO_ARRAY(m_iAmmo), 10, SPROP_UNSIGNED ) ), - - SendPropInt ( SENDINFO( m_fOnTarget ), 2, SPROP_UNSIGNED ), - - SendPropInt ( SENDINFO( m_nTickBase ), -1, SPROP_CHANGES_OFTEN ), - SendPropInt ( SENDINFO( m_nNextThinkTick ) ), - - SendPropEHandle ( SENDINFO( m_hLastWeapon ) ), - SendPropEHandle ( SENDINFO( m_hGroundEntity ), SPROP_CHANGES_OFTEN ), - - SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 0), 20, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), - SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 1), 20, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), - SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 2), 16, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), - - SendPropVector ( SENDINFO( m_vecBaseVelocity ), 20, 0, -1000, 1000 ), - - SendPropEHandle ( SENDINFO( m_hConstraintEntity)), - SendPropVector ( SENDINFO( m_vecConstraintCenter), 0, SPROP_NOSCALE ), - SendPropFloat ( SENDINFO( m_flConstraintRadius ), 0, SPROP_NOSCALE ), - SendPropFloat ( SENDINFO( m_flConstraintWidth ), 0, SPROP_NOSCALE ), - SendPropFloat ( SENDINFO( m_flConstraintSpeedFactor ), 0, SPROP_NOSCALE ), - - SendPropFloat ( SENDINFO( m_flDeathTime ), 0, SPROP_NOSCALE ), - - SendPropInt ( SENDINFO( m_nWaterLevel ), 2, SPROP_UNSIGNED ), - SendPropFloat ( SENDINFO( m_flLaggedMovementValue ), 0, SPROP_NOSCALE ), - - END_SEND_TABLE() - - -// -------------------------------------------------------------------------------- // -// DT_BasePlayer sendtable. -// -------------------------------------------------------------------------------- // - - IMPLEMENT_SERVERCLASS_ST( CBasePlayer, DT_BasePlayer ) - - SendPropDataTable(SENDINFO_DT(pl), &REFERENCE_SEND_TABLE(DT_PlayerState), SendProxy_DataTableToDataTable), - SendPropEHandle(SENDINFO(m_hVehicle)), - SendPropEHandle(SENDINFO(m_hUseEntity)), - SendPropInt (SENDINFO(m_iHealth), 10 ), - SendPropInt (SENDINFO(m_lifeState), 3, SPROP_UNSIGNED ), - SendPropFloat (SENDINFO(m_flMaxspeed), 12, SPROP_ROUNDDOWN, 0.0f, 2048.0f ), // CL - SendPropInt (SENDINFO(m_fFlags), PLAYER_FLAG_BITS, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN, SendProxy_CropFlagsToPlayerFlagBitsLength ), - SendPropInt (SENDINFO(m_iObserverMode), 3, SPROP_UNSIGNED ), - SendPropEHandle (SENDINFO(m_hObserverTarget) ), - SendPropInt (SENDINFO(m_iFOV), 8, SPROP_UNSIGNED ), - SendPropInt (SENDINFO(m_iDefaultFOV), 8, SPROP_UNSIGNED ), - SendPropArray ( SendPropEHandle( SENDINFO_ARRAY( m_hViewModel ) ), m_hViewModel ), - SendPropString (SENDINFO(m_szLastPlaceName) ), - - // Data that only gets sent to the local player. - SendPropDataTable( "localdata", 0, &REFERENCE_SEND_TABLE(DT_LocalPlayerExclusive), SendProxy_SendLocalDataTable ), - - END_SEND_TABLE() - -//============================================================================= -// -// Player Physics Shadow Code -// - -void CBasePlayer::SetupVPhysicsShadow( CPhysCollide *pStandModel, const char *pStandHullName, CPhysCollide *pCrouchModel, const char *pCrouchHullName ) -{ - solid_t solid; - Q_strncpy( solid.surfaceprop, "player", sizeof(solid.surfaceprop) ); - solid.params = g_PhysDefaultObjectParams; - solid.params.mass = 85.0f; - solid.params.inertia = 1e24f; - solid.params.enableCollisions = false; - //disable drag - solid.params.dragCoefficient = 0; - // create standing hull - m_pShadowStand = PhysModelCreateCustom( this, pStandModel, GetLocalOrigin(), GetLocalAngles(), pStandHullName, false, &solid ); - m_pShadowStand->SetCallbackFlags( CALLBACK_GLOBAL_COLLISION | CALLBACK_SHADOW_COLLISION ); - - // create crouchig hull - m_pShadowCrouch = PhysModelCreateCustom( this, pCrouchModel, GetLocalOrigin(), GetLocalAngles(), pCrouchHullName, false, &solid ); - m_pShadowCrouch->SetCallbackFlags( CALLBACK_GLOBAL_COLLISION | CALLBACK_SHADOW_COLLISION ); - - // default to stand - VPhysicsSetObject( m_pShadowStand ); - - // tell physics lists I'm a shadow controller object - PhysAddShadow( this ); - m_pPhysicsController = physenv->CreatePlayerController( m_pShadowStand ); - m_pPhysicsController->SetPushMassLimit( 350.0f ); - m_pPhysicsController->SetPushSpeedLimit( 50.0f ); - - // Give the controller a valid position so it doesn't do anything rash. - UpdatePhysicsShadowToCurrentPosition(); - - // init state - if ( GetFlags() & FL_DUCKING ) - { - SetVCollisionState( VPHYS_CROUCH ); - } - else - { - SetVCollisionState( VPHYS_WALK ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Empty, just want to keep the baseentity version from being called -// current so we don't kick up dust, etc. -//----------------------------------------------------------------------------- -void CBasePlayer::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::VPhysicsUpdate( IPhysicsObject *pPhysics ) -{ - float savedImpact = m_impactEnergyScale; - - // HACKHACK: Reduce player's stress by 1/8th - m_impactEnergyScale *= 0.125f; - ApplyStressDamage( pPhysics, true ); - m_impactEnergyScale = savedImpact; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) -{ - if ( sv_turbophysics.GetBool() ) - return; - - Vector newPosition; - - bool physicsUpdated = m_pPhysicsController->GetShadowPosition( &newPosition, NULL ) > 0 ? true : false; - - // UNDONE: If the player is penetrating, but the player's game collisions are not stuck, teleport the physics shadow to the game position - if ( pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING ) - { - CUtlVector list; - PhysGetListOfPenetratingEntities( this, list ); - for ( int i = list.Count()-1; i >= 0; --i ) - { - // filter out anything that isn't simulated by vphysics - // UNDONE: Filter out motion disabled objects? - if ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS ) - { - // I'm currently stuck inside a moving object, so allow vphysics to - // apply velocity to the player in order to separate these objects - m_touchedPhysObject = true; - } - - // if it's an NPC, tell them that the player is intersecting them - CAI_BaseNPC *pNPC = list[i]->MyNPCPointer(); - if ( pNPC ) - { - pNPC->PlayerPenetratingVPhysics(); - } - } - } - - if ( m_pPhysicsController->IsInContact() || (m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER) ) - { - m_touchedPhysObject = true; - } - - if ( IsFollowingPhysics() ) - { - m_touchedPhysObject = true; - } - - if ( GetMoveType() == MOVETYPE_NOCLIP ) - { - m_oldOrigin = GetAbsOrigin(); - return; - } - - if ( phys_timescale.GetFloat() == 0.0f ) - { - physicsUpdated = false; - } - - if ( !physicsUpdated ) - return; - - IPhysicsObject *pPhysGround = GetGroundVPhysics(); - - Vector newVelocity; - pPhysics->GetPosition( &newPosition, 0 ); - m_pPhysicsController->GetShadowVelocity( &newVelocity ); - - if ( physicsshadowupdate_render.GetBool() ) - { - NDebugOverlay::Box( GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 24, 15.0f ); - NDebugOverlay::Box( newPosition, WorldAlignMins(), WorldAlignMaxs(), 0,0,255, 24, 15.0f); - // NDebugOverlay::Box( newPosition, WorldAlignMins(), WorldAlignMaxs(), 0,0,255, 24, .01f); - } - - Vector tmp = GetAbsOrigin() - newPosition; - if ( !m_touchedPhysObject && !(GetFlags() & FL_ONGROUND) ) - { - tmp.z *= 0.5f; // don't care about z delta as much - } - - float dist = tmp.LengthSqr(); - float deltaV = (newVelocity - GetAbsVelocity()).LengthSqr(); - - float maxDistErrorSqr = VPHYS_MAX_DISTSQR; - float maxVelErrorSqr = VPHYS_MAX_VELSQR; - if ( IsRideablePhysics(pPhysGround) ) - { - maxDistErrorSqr *= 0.25; - maxVelErrorSqr *= 0.25; - } - - if ( dist >= maxDistErrorSqr || deltaV >= maxVelErrorSqr || (pPhysGround && !m_touchedPhysObject) ) - { - if ( m_touchedPhysObject || pPhysGround ) - { - // BUGBUG: Rewrite this code using fixed timestep - if ( deltaV >= maxVelErrorSqr ) - { - Vector dir = GetAbsVelocity(); - float len = VectorNormalize(dir); - float dot = DotProduct( newVelocity, dir ); - if ( dot > len ) - { - dot = len; - } - else if ( dot < -len ) - { - dot = -len; - } - - VectorMA( newVelocity, -dot, dir, newVelocity ); - - if ( m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER ) - { - float val = Lerp( 0.1f, len, dot ); - VectorMA( newVelocity, val - len, dir, newVelocity ); - } - - if ( !IsRideablePhysics(pPhysGround) ) - { - if ( !(m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER ) && IsSimulatingOnAlternateTicks() ) - { - newVelocity *= 0.5f; - } - ApplyAbsVelocityImpulse( newVelocity ); - } - } - - trace_t trace; - UTIL_TraceEntity( this, newPosition, newPosition, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); - if ( !trace.allsolid && !trace.startsolid ) - { - SetAbsOrigin( newPosition ); - } - } - else - { - trace_t trace; - UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); - - // current position is not ok, fixup - if ( trace.allsolid || trace.startsolid ) - { - // STUCK!?!?! - //Warning( "Stuck2 on %s!!\n", trace.m_pEnt->GetClassname() ); - SetAbsOrigin( newPosition ); - } - } - } - else - { - if ( m_touchedPhysObject ) - { - // check my position (physics object could have simulated into my position - // physics is not very far away, check my position - trace_t trace; - UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), - MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); - - // is current position ok? - if ( trace.allsolid || trace.startsolid ) - { - // stuck????!?!? - //Msg("Stuck on %s\n", trace.m_pEnt->GetClassName()); - SetAbsOrigin( newPosition ); - UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), - MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); - if ( trace.allsolid || trace.startsolid ) - { - //Msg("Double Stuck\n"); - SetAbsOrigin( m_oldOrigin ); - } - } - } - } - m_oldOrigin = GetAbsOrigin(); - // UNDONE: Force physics object to be at player position when not touching phys??? -} - -// recreate physics on save/load, don't try to save the state! -bool CBasePlayer::ShouldSavePhysics() -{ - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::InitVCollision( void ) -{ - // Cleanup any old vphysics stuff. - VPhysicsDestroyObject(); - - // in turbo physics players dont have a physics shadow - if ( sv_turbophysics.GetBool() ) - return; - - CPhysCollide *pModel = PhysCreateBbox( VEC_HULL_MIN, VEC_HULL_MAX ); - CPhysCollide *pCrouchModel = PhysCreateBbox( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); - - SetupVPhysicsShadow( pModel, "player_stand", pCrouchModel, "player_crouch" ); -} - - -void CBasePlayer::VPhysicsDestroyObject() -{ - // Since CBasePlayer aliases its pointer to the physics object, tell CBaseEntity to - // clear out its physics object pointer so we don't wind up deleting one of - // the aliased objects twice. - VPhysicsSetObject( NULL ); - - PhysRemoveShadow( this ); - - if ( m_pPhysicsController ) - { - physenv->DestroyPlayerController( m_pPhysicsController ); - m_pPhysicsController = NULL; - } - - if ( m_pShadowStand ) - { - PhysDestroyObject( m_pShadowStand ); - m_pShadowStand = NULL; - } - if ( m_pShadowCrouch ) - { - PhysDestroyObject( m_pShadowCrouch ); - m_pShadowCrouch = NULL; - } - - BaseClass::VPhysicsDestroyObject(); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::SetVCollisionState( int collisionState ) -{ - Vector vel = vec3_origin; - Vector pos = vec3_origin; - vel = GetAbsVelocity(); - pos = GetAbsOrigin(); - - m_vphysicsCollisionState = collisionState; - switch( collisionState ) - { - case VPHYS_WALK: - m_pShadowStand->SetPosition( pos, vec3_angle, true ); - m_pShadowStand->SetVelocity( &vel, NULL ); - m_pShadowCrouch->EnableCollisions( false ); - m_pPhysicsController->SetObject( m_pShadowStand ); - VPhysicsSwapObject( m_pShadowStand ); - m_pShadowStand->EnableCollisions( true ); - break; - - case VPHYS_CROUCH: - m_pShadowCrouch->SetPosition( pos, vec3_angle, true ); - m_pShadowCrouch->SetVelocity( &vel, NULL ); - m_pShadowStand->EnableCollisions( false ); - m_pPhysicsController->SetObject( m_pShadowCrouch ); - VPhysicsSwapObject( m_pShadowCrouch ); - m_pShadowCrouch->EnableCollisions( true ); - break; - - case VPHYS_NOCLIP: - m_pShadowCrouch->EnableCollisions( false ); - m_pShadowStand->EnableCollisions( false ); - break; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : int -//----------------------------------------------------------------------------- -int CBasePlayer::GetFOV( void ) const -{ - if ( m_iFOV == 0 ) - return GetDefaultFOV(); - - return m_iFOV; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : FOV - -// zoomRate - -//----------------------------------------------------------------------------- -bool CBasePlayer::SetFOV( CBaseEntity *pRequester, int FOV, float zoomRate ) -{ - //NOTENOTE: You MUST specify who is requesting the zoom change - assert( pRequester != NULL ); - if ( pRequester == NULL ) - return false; - - // If we already have an owner, we only allow requests from that owner - if ( ( m_hZoomOwner != NULL ) && ( m_hZoomOwner != pRequester ) ) - { - if ( CanOverrideEnvZoomOwner( m_hZoomOwner ) == false ) - return false; - } - else - { - //FIXME: Maybe do this is as an accessor instead - if ( FOV == 0 ) - { - m_hZoomOwner = NULL; - } - else - { - m_hZoomOwner = pRequester; - } - } - - m_iFOV = FOV; - - m_Local.m_flFOVRate = zoomRate; - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Sets the default FOV for the player if nothing else is going on -// Input : FOV - the new base FOV for this player -//----------------------------------------------------------------------------- -void CBasePlayer::SetDefaultFOV( int FOV ) -{ - m_iDefaultFOV = ( FOV == 0 ) ? g_pGameRules->DefaultFOV() : FOV; -} - -//----------------------------------------------------------------------------- -// Purpose: // static func -// Input : set - -//----------------------------------------------------------------------------- -void CBasePlayer::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set ) -{ - // Append our health - set.AppendCriteria( "playerhealth", UTIL_VarArgs( "%i", GetHealth() ) ); - float healthfrac = 0.0f; - if ( GetMaxHealth() > 0 ) - { - healthfrac = (float)GetHealth() / (float)GetMaxHealth(); - } - - set.AppendCriteria( "playerhealthfrac", UTIL_VarArgs( "%.3f", healthfrac ) ); - - CBaseCombatWeapon *weapon = GetActiveWeapon(); - if ( weapon ) - { - set.AppendCriteria( "playerweapon", weapon->GetClassname() ); - } - else - { - set.AppendCriteria( "playerweapon", "none" ); - } - - // Append current activity name - set.AppendCriteria( "playeractivity", CAI_BaseNPC::GetActivityName( GetActivity() ) ); - - set.AppendCriteria( "playerspeed", UTIL_VarArgs( "%.3f", GetAbsVelocity().Length() ) ); - - AppendContextToCriteria( set, "player" ); -} - - -const QAngle& CBasePlayer::GetPunchAngle() -{ - return m_Local.m_vecPunchAngle.Get(); -} - - -void CBasePlayer::SetPunchAngle( const QAngle &punchAngle ) -{ - m_Local.m_vecPunchAngle = punchAngle; - - if ( IsAlive() ) - { - int index = entindex(); - - for ( int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if ( pPlayer && i != index && pPlayer->GetObserverTarget() == this && pPlayer->GetObserverMode() == OBS_MODE_IN_EYE ) - { - pPlayer->SetPunchAngle( punchAngle ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Apply a movement constraint to the player -//----------------------------------------------------------------------------- -void CBasePlayer::ActivateMovementConstraint( CBaseEntity *pEntity, const Vector &vecCenter, float flRadius, float flConstraintWidth, float flSpeedFactor ) -{ - m_hConstraintEntity = pEntity; - m_vecConstraintCenter = vecCenter; - m_flConstraintRadius = flRadius; - m_flConstraintWidth = flConstraintWidth; - m_flConstraintSpeedFactor = flSpeedFactor; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::DeactivateMovementConstraint( ) -{ - m_hConstraintEntity = NULL; - m_flConstraintRadius = 0.0f; - m_vecConstraintCenter = vec3_origin; -} - -//----------------------------------------------------------------------------- -// Perhaps a poorly-named function. This function traces against the supplied -// NPC's hitboxes (instead of hull). If the trace hits a different NPC, the -// new NPC is selected. Otherwise, the supplied NPC is determined to be the -// one the citizen wants. This function allows the selection of a citizen over -// another citizen's shoulder, which is impossible without tracing against -// hitboxes instead of the hull (sjb) -//----------------------------------------------------------------------------- -CBaseEntity *CBasePlayer::DoubleCheckUseNPC( CBaseEntity *pNPC, const Vector &vecSrc, const Vector &vecDir ) -{ - trace_t tr; - - UTIL_TraceLine( vecSrc, vecSrc + vecDir * 1024, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); - - if( tr.m_pEnt != NULL && tr.m_pEnt->MyNPCPointer() && tr.m_pEnt != pNPC ) - { - // Player is selecting a different NPC through some negative space - // in the first NPC's hitboxes (between legs, over shoulder, etc). - return tr.m_pEnt; - } - - return pNPC; -} - - -bool CBasePlayer::IsBot() const -{ - return (GetFlags() & FL_FAKECLIENT) != 0; -} - -bool CBasePlayer::IsFakeClient() const -{ - return (GetFlags() & FL_FAKECLIENT) != 0; -} - -void CBasePlayer::EquipSuit( bool bPlayEffects ) -{ - m_Local.m_bWearingSuit = true; -} - -void CBasePlayer::RemoveSuit( void ) -{ - m_Local.m_bWearingSuit = false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const char -//----------------------------------------------------------------------------- -const char *CBasePlayer::GetTracerType( void ) -{ - if ( GetActiveWeapon() ) - { - return GetActiveWeapon()->GetTracerType(); - } - - return BaseClass::GetTracerType(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &tr - -// nDamageType - -//----------------------------------------------------------------------------- -void CBasePlayer::DoImpactEffect( trace_t &tr, int nDamageType ) -{ - if ( GetActiveWeapon() ) - { - GetActiveWeapon()->DoImpactEffect( tr, nDamageType ); - return; - } - - BaseClass::DoImpactEffect( tr, nDamageType ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBasePlayer::InputSetHealth( inputdata_t &inputdata ) -{ - int iNewHealth = inputdata.value.Int(); - int iDelta = abs(GetHealth() - iNewHealth); - if ( iNewHealth > GetHealth() ) - { - TakeHealth( iDelta, DMG_GENERIC ); - } - else if ( iNewHealth < GetHealth() ) - { - // Strip off and restore armor so that it doesn't absorb any of this damage. - int armor = m_ArmorValue; - m_ArmorValue = 0; - TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) ); - m_ArmorValue = armor; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Hides or displays the HUD -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CBasePlayer::InputSetHUDVisibility( inputdata_t &inputdata ) -{ - bool bEnable = inputdata.value.Bool(); - - if ( bEnable ) - { - m_Local.m_iHideHUD &= ~HIDEHUD_ALL; - } - else - { - m_Local.m_iHideHUD |= HIDEHUD_ALL; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pEntity - -//----------------------------------------------------------------------------- -void CBasePlayer::SetViewEntity( CBaseEntity *pEntity ) -{ - m_hViewEntity = pEntity; - - if ( m_hViewEntity ) - { - engine->SetView( edict(), m_hViewEntity->edict() ); - } - else - { - engine->SetView( edict(), edict() ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Looks at the player's reserve ammo and also all his weapons for any ammo -// of the specified type -// Input : nAmmoIndex - ammo to look for -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBasePlayer::HasAnyAmmoOfType( int nAmmoIndex ) -{ - // Must be a valid index - if ( nAmmoIndex < 0 ) - return false; - - // If we have some in reserve, we're already done - if ( GetAmmoCount( nAmmoIndex ) ) - return true; - - CBaseCombatWeapon *pWeapon; - - // Check all held weapons - for ( int i=0; i < MAX_WEAPONS; i++ ) - { - pWeapon = GetWeapon( i ); - - if ( !pWeapon ) - continue; - - // We must use clips and use this sort of ammo - if ( pWeapon->UsesClipsForAmmo1() && pWeapon->GetPrimaryAmmoType() == nAmmoIndex ) - { - // If we have any ammo, we're done - if ( pWeapon->HasPrimaryAmmo() ) - return true; - } - - // We'll check both clips for the same ammo type, just in case - if ( pWeapon->UsesClipsForAmmo2() && pWeapon->GetSecondaryAmmoType() == nAmmoIndex ) - { - if ( pWeapon->HasSecondaryAmmo() ) - return true; - } - } - - // We're completely without this type of ammo - return false; -} - -//----------------------------------------------------------------------------- -// return a string version of the players network (i.e steam) ID. -// -//----------------------------------------------------------------------------- -const char *CBasePlayer::GetNetworkIDString() -{ - Q_strncpy( m_szNetworkIDString, engine->GetPlayerNetworkIDString( edict() ), sizeof(m_szNetworkIDString) ); - return m_szNetworkIDString; -} - -//----------------------------------------------------------------------------- -// Assign the player a name -//----------------------------------------------------------------------------- -void CBasePlayer::SetPlayerName( const char *name ) -{ - Assert( name ); - - if ( name ) - { - Assert( strlen(name) > 0 ); - - Q_strncpy( m_szNetname, name, sizeof(m_szNetname) ); - } -} - -//----------------------------------------------------------------------------- -// sets the "don't autokick me" flag on a player -//----------------------------------------------------------------------------- -class DisableAutokick -{ -public: - DisableAutokick( int userID ) - { - m_userID = userID; - } - - bool operator()( CBasePlayer *player ) - { - if ( player->GetUserID() == m_userID ) - { - Msg( "autokick is disabled for %s\n", player->GetPlayerName() ); - player->DisableAutoKick( true ); - return false; // don't need to check other players - } - - return true; // keep looking at other players - } - -private: - int m_userID; -}; - -//----------------------------------------------------------------------------- -// sets the "don't autokick me" flag on a player -//----------------------------------------------------------------------------- -CON_COMMAND( mp_disable_autokick, "Prevents a userid from being auto-kicked" ) -{ - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - { - Msg( "You must be a server admin to use mp_disable_autokick\n" ); - return; - } - - if ( engine->Cmd_Argc() != 2 ) - { - Msg( "Usage: mp_disable_autokick \n" ); - return; - } - - int userID = atoi( engine->Cmd_Argv( 1 ) ); - DisableAutokick disable( userID ); - ForEachPlayer( disable ); -} - -//----------------------------------------------------------------------------- -// Purpose: Toggle between the duck being on and off -//----------------------------------------------------------------------------- -void CBasePlayer::ToggleDuck( void ) -{ - // Toggle the state - m_bDuckToggled = !m_bDuckToggled; -} - -//----------------------------------------------------------------------------- -// Just tells us how far the stick is from the center. No directional info -//----------------------------------------------------------------------------- -float CBasePlayer::GetStickDist() -{ - Vector2D controlStick; - - controlStick.x = m_flForwardMove; - controlStick.y = m_flSideMove; - - return controlStick.Length(); -} - -//----------------------------------------------------------------------------- -// CPlayerInfo functions (simple passthroughts to get around the CBasePlayer multiple inheritence limitation) -//----------------------------------------------------------------------------- -const char *CPlayerInfo::GetName() -{ - Assert( m_pParent ); - return m_pParent->GetPlayerName(); -} - -int CPlayerInfo::GetUserID() -{ - Assert( m_pParent ); - return engine->GetPlayerUserId( m_pParent->edict() ); -} - -const char *CPlayerInfo::GetNetworkIDString() -{ - Assert( m_pParent ); - return m_pParent->GetNetworkIDString(); -} - -int CPlayerInfo::GetTeamIndex() -{ - Assert( m_pParent ); - return m_pParent->GetTeamNumber(); -} - -void CPlayerInfo::ChangeTeam( int iTeamNum ) -{ - Assert( m_pParent ); - m_pParent->ChangeTeam(iTeamNum); -} - -int CPlayerInfo::GetFragCount() -{ - Assert( m_pParent ); - return m_pParent->FragCount(); -} - -int CPlayerInfo::GetDeathCount() -{ - Assert( m_pParent ); - return m_pParent->DeathCount(); -} - -bool CPlayerInfo::IsConnected() -{ - Assert( m_pParent ); - return m_pParent->IsConnected(); -} - -int CPlayerInfo::GetArmorValue() -{ - Assert( m_pParent ); - return m_pParent->ArmorValue(); -} - -bool CPlayerInfo::IsHLTV() -{ - Assert( m_pParent ); - return m_pParent->IsHLTV(); -} - -bool CPlayerInfo::IsPlayer() -{ - Assert( m_pParent ); - return m_pParent->IsPlayer(); -} - -bool CPlayerInfo::IsFakeClient() -{ - Assert( m_pParent ); - return m_pParent->IsFakeClient(); -} - -bool CPlayerInfo::IsDead() -{ - Assert( m_pParent ); - return m_pParent->IsDead(); -} - -bool CPlayerInfo::IsInAVehicle() -{ - Assert( m_pParent ); - return m_pParent->IsInAVehicle(); -} - -bool CPlayerInfo::IsObserver() -{ - Assert( m_pParent ); - return m_pParent->IsObserver(); -} - -const Vector CPlayerInfo::GetAbsOrigin() -{ - Assert( m_pParent ); - return m_pParent->GetAbsOrigin(); -} - -const QAngle CPlayerInfo::GetAbsAngles() -{ - Assert( m_pParent ); - return m_pParent->GetAbsAngles(); -} - -const Vector CPlayerInfo::GetPlayerMins() -{ - Assert( m_pParent ); - return m_pParent->GetPlayerMins(); -} - -const Vector CPlayerInfo::GetPlayerMaxs() -{ - Assert( m_pParent ); - return m_pParent->GetPlayerMaxs(); -} - -const char *CPlayerInfo::GetWeaponName() -{ - Assert( m_pParent ); - CBaseCombatWeapon *weap = m_pParent->GetActiveWeapon(); - if ( !weap ) - { - return NULL; - } - return weap->GetName(); -} - -const char *CPlayerInfo::GetModelName() -{ - Assert( m_pParent ); - return m_pParent->GetModelName().ToCStr(); -} - -const int CPlayerInfo::GetHealth() -{ - Assert( m_pParent ); - return m_pParent->GetHealth(); -} - -const int CPlayerInfo::GetMaxHealth() -{ - Assert( m_pParent ); - return m_pParent->GetMaxHealth(); -} - - - - - -void CPlayerInfo::SetAbsOrigin( Vector & vec ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - m_pParent->SetAbsOrigin(vec); - } -} - -void CPlayerInfo::SetAbsAngles( QAngle & ang ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - m_pParent->SetAbsAngles(ang); - } -} - -void CPlayerInfo::RemoveAllItems( bool removeSuit ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - m_pParent->RemoveAllItems(removeSuit); - } -} - -void CPlayerInfo::SetActiveWeapon( const char *WeaponName ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - CBaseCombatWeapon *weap = m_pParent->Weapon_Create( WeaponName ); - if ( weap ) - { - m_pParent->Weapon_Equip(weap); - m_pParent->Weapon_Switch(weap); - } - } -} - -void CPlayerInfo::SetLocalOrigin( const Vector& origin ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - m_pParent->SetLocalOrigin(origin); - } -} - -const Vector CPlayerInfo::GetLocalOrigin( void ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - Vector origin = m_pParent->GetLocalOrigin(); - return origin; - } - else - { - return Vector( 0, 0, 0 ); - } -} - -void CPlayerInfo::SetLocalAngles( const QAngle& angles ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - m_pParent->SetLocalAngles( angles ); - } -} - -const QAngle CPlayerInfo::GetLocalAngles( void ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - return m_pParent->GetLocalAngles(); - } - else - { - return QAngle(); - } -} - -void CPlayerInfo::PostClientMessagesSent( void ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - m_pParent->PostClientMessagesSent(); - } -} - -bool CPlayerInfo::IsEFlagSet( int nEFlagMask ) -{ - Assert( m_pParent ); - if ( m_pParent->IsBot() ) - { - return m_pParent->IsEFlagSet(nEFlagMask); - } - return false; -} - -void CPlayerInfo::RunPlayerMove( CBotCmd *ucmd ) -{ - if ( m_pParent->IsBot() ) - { - Assert( m_pParent ); - CUserCmd cmd; - cmd.buttons = ucmd->buttons; - cmd.command_number = ucmd->command_number; - cmd.forwardmove = ucmd->forwardmove; - cmd.hasbeenpredicted = ucmd->hasbeenpredicted; - cmd.impulse = ucmd->impulse; - cmd.mousedx = ucmd->mousedx; - cmd.mousedy = ucmd->mousedy; - cmd.random_seed = ucmd->random_seed; - cmd.sidemove = ucmd->sidemove; - cmd.tick_count = ucmd->tick_count; - cmd.upmove = ucmd->upmove; - cmd.viewangles = ucmd->viewangles; - cmd.weaponselect = ucmd->weaponselect; - cmd.weaponsubtype = ucmd->weaponsubtype; - - // Store off the globals.. they're gonna get whacked - float flOldFrametime = gpGlobals->frametime; - float flOldCurtime = gpGlobals->curtime; - - m_pParent->SetTimeBase( gpGlobals->curtime ); - - MoveHelperServer()->SetHost( m_pParent ); - m_pParent->PlayerRunCommand( &cmd, MoveHelperServer() ); - - // save off the last good usercmd - m_pParent->SetLastUserCommand( cmd ); - - // Clear out any fixangle that has been set - m_pParent->pl.fixangle = FIXANGLE_NONE; - - // Restore the globals.. - gpGlobals->frametime = flOldFrametime; - gpGlobals->curtime = flOldCurtime; - MoveHelperServer()->SetHost( NULL ); - } -} - -void CPlayerInfo::SetLastUserCommand( const CBotCmd &ucmd ) -{ - if ( m_pParent->IsBot() ) - { - Assert( m_pParent ); - CUserCmd cmd; - cmd.buttons = ucmd.buttons; - cmd.command_number = ucmd.command_number; - cmd.forwardmove = ucmd.forwardmove; - cmd.hasbeenpredicted = ucmd.hasbeenpredicted; - cmd.impulse = ucmd.impulse; - cmd.mousedx = ucmd.mousedx; - cmd.mousedy = ucmd.mousedy; - cmd.random_seed = ucmd.random_seed; - cmd.sidemove = ucmd.sidemove; - cmd.tick_count = ucmd.tick_count; - cmd.upmove = ucmd.upmove; - cmd.viewangles = ucmd.viewangles; - cmd.weaponselect = ucmd.weaponselect; - cmd.weaponsubtype = ucmd.weaponsubtype; - - m_pParent->SetLastUserCommand(cmd); - } -} - - -CBotCmd CPlayerInfo::GetLastUserCommand() -{ - CBotCmd cmd; - const CUserCmd *ucmd = m_pParent->GetLastUserCommand(); - if ( ucmd ) - { - cmd.buttons = ucmd->buttons; - cmd.command_number = ucmd->command_number; - cmd.forwardmove = ucmd->forwardmove; - cmd.hasbeenpredicted = ucmd->hasbeenpredicted; - cmd.impulse = ucmd->impulse; - cmd.mousedx = ucmd->mousedx; - cmd.mousedy = ucmd->mousedy; - cmd.random_seed = ucmd->random_seed; - cmd.sidemove = ucmd->sidemove; - cmd.tick_count = ucmd->tick_count; - cmd.upmove = ucmd->upmove; - cmd.viewangles = ucmd->viewangles; - cmd.weaponselect = ucmd->weaponselect; - cmd.weaponsubtype = ucmd->weaponsubtype; - } - return cmd; -} - +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Functions dealing with the player. +// +//=============================================================================// + +#include "cbase.h" +#include "const.h" +#include "baseplayer_shared.h" +#include "trains.h" +#include "soundent.h" +#include "gib.h" +#include "shake.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" +#include "entityapi.h" +#include "entitylist.h" +#include "eventqueue.h" +#include "worldsize.h" +#include "isaverestore.h" +#include "globalstate.h" +#include "basecombatweapon.h" +#include "ai_basenpc.h" +#include "ai_network.h" +#include "ai_node.h" +#include "ai_networkmanager.h" +#include "ammodef.h" +#include "mathlib.h" +#include "ndebugoverlay.h" +#include "baseviewmodel.h" +#include "in_buttons.h" +#include "client.h" +#include "team.h" +#include "particle_smokegrenade.h" +#include "IEffects.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "movehelper_server.h" +#include "igamemovement.h" +#include "saverestoretypes.h" +#include "iservervehicle.h" +#include "movevars_shared.h" +#include "vcollide_parse.h" +#include "player_command.h" +#include "vehicle_base.h" +#include "AI_Criteria.h" +#include "globals.h" +#include "usermessages.h" +#include "gamevars_shared.h" +#include "world.h" +#include "physobj.h" +#include "KeyValues.h" +#include "coordsize.h" +#include "vphysics/player_controller.h" +#include "saverestore_utlvector.h" +#include "hltvdirector.h" +#include "nav_mesh.h" +#include "env_zoom.h" +#include "rumble_shared.h" + +#ifdef HL2_DLL +#include "combine_mine.h" +#include "weapon_physcannon.h" +#endif + +#ifdef HL2_DLL +extern ConVar hl2_xbox_aiming; +#define UseXboxAiming() hl2_xbox_aiming.GetBool() +#else +#define UseXboxAiming() 0 +#endif + +ConVar autoaim_max_dist( "autoaim_max_dist", "2160" ); // 2160 = 180 feet +ConVar autoaim_max_deflect( "autoaim_max_deflect", "0.99" ); + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar old_armor( "player_old_armor", "0" ); + +static ConVar physicsshadowupdate_render( "physicsshadowupdate_render", "0" ); +bool IsInCommentaryMode( void ); +bool IsListeningToCommentary( void ); + +// This is declared in the engine, too +ConVar sv_noclipduringpause( "sv_noclipduringpause", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If cheats are enabled, then you can noclip with the game paused (for doing screenshots, etc.)." ); + +extern ConVar sv_maxunlag; +extern ConVar sv_turbophysics; +extern ConVar *sv_maxreplay; + +// TIME BASED DAMAGE AMOUNT +// 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 + +//---------------------------------------------------- +// Player Physics Shadow +//---------------------------------------------------- +#define VPHYS_MAX_DISTANCE 2.0 +#define VPHYS_MAX_VEL 10 +#define VPHYS_MAX_DISTSQR (VPHYS_MAX_DISTANCE*VPHYS_MAX_DISTANCE) +#define VPHYS_MAX_VELSQR (VPHYS_MAX_VEL*VPHYS_MAX_VEL) + + +extern bool g_fDrawLines; +int gEvilImpulse101; + +bool gInitHUD = true; + +extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); +int MapTextureTypeStepType(char chTextureType); +extern void SpawnBlood(Vector vecSpot, const Vector &vecDir, int bloodColor, float flDamage); +extern void AddMultiDamage( const CTakeDamageInfo &info, CBaseEntity *pEntity ); + + +#define CMD_MOSTRECENT 0 + +//#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +//#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + + +//#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage +//#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet +//#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 ) +//#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 ) + +// player damage adjusters +ConVar sk_player_head( "sk_player_head","2" ); +ConVar sk_player_chest( "sk_player_chest","1" ); +ConVar sk_player_stomach( "sk_player_stomach","1" ); +ConVar sk_player_arm( "sk_player_arm","1" ); +ConVar sk_player_leg( "sk_player_leg","1" ); + +void CC_GiveCurrentAmmo( void ) +{ +#ifdef _XBOX + CBasePlayer *pPlayer = UTIL_PlayerByIndex(1); + + if( pPlayer ) + { + CBaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon(); + + if( pWeapon ) + { + if( pWeapon->UsesPrimaryAmmo() ) + { + int ammoIndex = pWeapon->GetPrimaryAmmoType(); + + if( ammoIndex != -1 ) + { + int giveAmount; + giveAmount = GetAmmoDef()->MaxCarry(ammoIndex); + pPlayer->GiveAmmo( giveAmount, GetAmmoDef()->GetAmmoOfIndex(ammoIndex)->pName ); + } + } + if( pWeapon->UsesSecondaryAmmo() && pWeapon->HasSecondaryAmmo() ) + { + // Give secondary ammo out, as long as the player already has some + // from a presumeably natural source. This prevents players on XBox + // having Combine Balls and so forth in areas of the game that + // were not tested with these items. + int ammoIndex = pWeapon->GetSecondaryAmmoType(); + + if( ammoIndex != -1 ) + { + int giveAmount; + giveAmount = GetAmmoDef()->MaxCarry(ammoIndex); + pPlayer->GiveAmmo( giveAmount, GetAmmoDef()->GetAmmoOfIndex(ammoIndex)->pName ); + } + } + } + } +#endif//_XBOX +} +static ConCommand givecurrentammo("givecurrentammo", CC_GiveCurrentAmmo, "Give a supply of ammo for current weapon..\n", FCVAR_CHEAT ); + + +// pl +BEGIN_SIMPLE_DATADESC( CPlayerState ) + // DEFINE_FIELD( netname, FIELD_STRING ), // Don't stomp player name with what's in save/restore + DEFINE_FIELD( v_angle, FIELD_VECTOR ), + DEFINE_FIELD( deadflag, FIELD_BOOLEAN ), + + // this is always set to true on restore, don't bother saving it. + // DEFINE_FIELD( fixangle, FIELD_INTEGER ), + // DEFINE_FIELD( anglechange, FIELD_FLOAT ), + // DEFINE_FIELD( hltv, FIELD_BOOLEAN ), + // DEFINE_FIELD( frags, FIELD_INTEGER ), + // DEFINE_FIELD( deaths, FIELD_INTEGER ), +END_DATADESC() + +// Global Savedata for player +BEGIN_DATADESC( CBasePlayer ) + + DEFINE_EMBEDDED( m_Local ), + DEFINE_UTLVECTOR( m_hTriggerSoundscapeList, FIELD_EHANDLE ), + DEFINE_EMBEDDED( pl ), + + DEFINE_FIELD( m_StuckLast, FIELD_INTEGER ), + + DEFINE_FIELD( m_nButtons, FIELD_INTEGER ), + DEFINE_FIELD( m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_FIELD( m_iFOV, FIELD_INTEGER ), + DEFINE_FIELD( m_iDefaultFOV, FIELD_INTEGER ), + + //DEFINE_FIELD( m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + DEFINE_FIELD( m_iObserverMode, FIELD_INTEGER ), + DEFINE_FIELD( m_iObserverLastMode, FIELD_INTEGER ), + DEFINE_FIELD( m_hObserverTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_bForcedObserverMode, FIELD_BOOLEAN ), + DEFINE_AUTO_ARRAY( m_szAnimExtension, FIELD_CHARACTER ), +// DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ), + + DEFINE_FIELD( m_nUpdateRate, FIELD_INTEGER ), + DEFINE_FIELD( m_fLerpTime, FIELD_FLOAT ), + DEFINE_FIELD( m_bLagCompensation, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bPredictWeapons, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_vecAdditionalPVSOrigin, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecCameraPVSOrigin, FIELD_POSITION_VECTOR ), + + DEFINE_FIELD( m_hUseEntity, FIELD_EHANDLE ), + DEFINE_FIELD( m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( m_iRespawnFrames, FIELD_FLOAT ), + DEFINE_FIELD( m_afPhysicsFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_hVehicle, FIELD_EHANDLE ), + + // recreate, don't restore + // DEFINE_FIELD( m_CommandContext, CUtlVector < CCommandContext > ), + //DEFINE_FIELD( m_pPhysicsController, FIELD_POINTER ), + //DEFINE_FIELD( m_pShadowStand, FIELD_POINTER ), + //DEFINE_FIELD( m_pShadowCrouch, FIELD_POINTER ), + //DEFINE_FIELD( m_vphysicsCollisionState, FIELD_INTEGER ), + // DEFINE_FIELD( m_lastNavArea, CNavArea ), + DEFINE_ARRAY( m_szNetworkIDString, FIELD_CHARACTER, MAX_NETWORKID_LENGTH ), + DEFINE_FIELD( m_oldOrigin, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecSmoothedVelocity, FIELD_VECTOR ), + //DEFINE_FIELD( m_touchedPhysObject, FIELD_BOOLEAN ), + //DEFINE_FIELD( m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + DEFINE_FIELD( m_iTargetVolume, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( m_rgItems, FIELD_INTEGER ), + //DEFINE_FIELD( m_fNextSuicideTime, FIELD_TIME ), + + DEFINE_FIELD( m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( m_flDuckJumpTime, FIELD_TIME ), + + DEFINE_FIELD( m_flSuitUpdate, FIELD_TIME ), + DEFINE_AUTO_ARRAY( m_rgSuitPlayList, FIELD_INTEGER ), + DEFINE_FIELD( m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( m_rgiSuitNoRepeat, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( m_rgflSuitNoRepeatTime, FIELD_TIME ), + DEFINE_FIELD( m_lastDamageAmount, FIELD_INTEGER ), + DEFINE_FIELD( m_tbdPrev, FIELD_TIME ), + + //DEFINE_FIELD( m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( m_chTextureType, FIELD_CHARACTER ), // Don't need to restore + + DEFINE_FIELD( m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( m_idrownrestored, FIELD_INTEGER ), + + DEFINE_FIELD( m_nPoisonDmg, FIELD_INTEGER ), + DEFINE_FIELD( m_nPoisonRestored, FIELD_INTEGER ), + + DEFINE_FIELD( m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flDeathTime, FIELD_TIME ), + + //DEFINE_FIELD( m_fGameHUDInitialized, FIELD_BOOLEAN ), // only used in multiplayer games + //DEFINE_FIELD( m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_FIELD( m_lastx, FIELD_INTEGER ), + //DEFINE_FIELD( m_lasty, FIELD_INTEGER ), + + DEFINE_FIELD( m_iFrags, FIELD_INTEGER ), + DEFINE_FIELD( m_iDeaths, FIELD_INTEGER ), + DEFINE_FIELD( m_flNextDecalTime, FIELD_TIME ), + //DEFINE_AUTO_ARRAY( m_szTeamName, FIELD_STRING ), // mp + + //DEFINE_FIELD( m_iConnected, FIELD_INTEGER ), + // from edict_t + DEFINE_FIELD( m_ArmorValue, FIELD_INTEGER ), + DEFINE_FIELD( m_DmgOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_DmgTake, FIELD_FLOAT ), + DEFINE_FIELD( m_DmgSave, FIELD_FLOAT ), + DEFINE_FIELD( m_AirFinished, FIELD_TIME ), + DEFINE_FIELD( m_PainFinished, FIELD_TIME ), + + DEFINE_FIELD( m_iPlayerLocked, FIELD_INTEGER ), + + DEFINE_AUTO_ARRAY( m_hViewModel, FIELD_EHANDLE ), + + DEFINE_FIELD( m_flMaxspeed, FIELD_FLOAT ), + DEFINE_FIELD( m_flWaterJumpTime, FIELD_TIME ), + DEFINE_FIELD( m_vecWaterJumpVel, FIELD_VECTOR ), + DEFINE_FIELD( m_nImpulse, FIELD_INTEGER ), + DEFINE_FIELD( m_flStepSoundTime, FIELD_TIME ), + DEFINE_FIELD( m_flSwimSoundTime, FIELD_TIME ), + DEFINE_FIELD( m_vecLadderNormal, FIELD_VECTOR ), + + DEFINE_FIELD( m_flFlashTime, FIELD_TIME ), + DEFINE_FIELD( m_nDrownDmgRate, FIELD_INTEGER ), + + // NOT SAVED + //DEFINE_FIELD( m_vForcedOrigin, FIELD_VECTOR ), + //DEFINE_FIELD( m_bForceOrigin, FIELD_BOOLEAN ), + //DEFINE_FIELD( m_nTickBase, FIELD_INTEGER ), + //DEFINE_FIELD( m_LastCmd, FIELD_ ), + // DEFINE_FIELD( m_pCurrentCommand, CUserCmd ), + //DEFINE_FIELD( m_bGamePaused, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_bitsDamageType, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( m_rgbTimeBasedDamage, FIELD_CHARACTER ), + DEFINE_FIELD( m_fLastPlayerTalkTime, FIELD_FLOAT ), + DEFINE_FIELD( m_hLastWeapon, FIELD_EHANDLE ), + +#if !defined( NO_ENTITY_PREDICTION ) + // DEFINE_FIELD( m_SimulatedByThisPlayer, CUtlVector < CHandle < CBaseEntity > > ), +#endif + + DEFINE_FIELD( m_flOldPlayerZ, FIELD_FLOAT ), + DEFINE_FIELD( m_flOldPlayerViewOffsetZ, FIELD_FLOAT ), + DEFINE_FIELD( m_bPlayerUnderwater, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hViewEntity, FIELD_EHANDLE ), + + DEFINE_FIELD( m_hConstraintEntity, FIELD_EHANDLE ), + DEFINE_FIELD( m_vecConstraintCenter, FIELD_VECTOR ), + DEFINE_FIELD( m_flConstraintRadius, FIELD_FLOAT ), + DEFINE_FIELD( m_flConstraintWidth, FIELD_FLOAT ), + DEFINE_FIELD( m_flConstraintSpeedFactor, FIELD_FLOAT ), + DEFINE_FIELD( m_hZoomOwner, FIELD_EHANDLE ), + + DEFINE_FIELD( m_flLaggedMovementValue, FIELD_FLOAT ), + + DEFINE_FIELD( m_vNewVPhysicsPosition, FIELD_VECTOR ), + DEFINE_FIELD( m_vNewVPhysicsVelocity, FIELD_VECTOR ), + + DEFINE_FIELD( m_bSinglePlayerGameEnding, FIELD_BOOLEAN ), + + // Function Pointers + DEFINE_FUNCTION( PlayerDeathThink ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetHUDVisibility", InputSetHUDVisibility ), + + DEFINE_FIELD( m_nNumCrouches, FIELD_INTEGER ), + DEFINE_FIELD( m_bDuckToggled, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_nNumCrateHudHints, FIELD_INTEGER ), + +END_DATADESC() + +int giPrecacheGrunt = 0; + +edict_t *CBasePlayer::s_PlayerEdict = NULL; + + +inline bool ShouldRunCommandsInContext( const CCommandContext *ctx ) +{ + // TODO: This should be enabled at some point. If usercmds can run while paused, then + // they can create entities which will never die and it will fill up the entity list. +#ifdef NO_USERCMDS_DURING_PAUSE + return !ctx->paused || sv_noclipduringpause.GetInt(); +#else + return true; +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CBaseViewModel +//----------------------------------------------------------------------------- +CBaseViewModel *CBasePlayer::GetViewModel( int index /*= 0*/ ) +{ + Assert( index >= 0 && index < MAX_VIEWMODELS ); + return m_hViewModel[ index ].Get(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::CreateViewModel( int index /*=0*/ ) +{ + Assert( index >= 0 && index < MAX_VIEWMODELS ); + + if ( GetViewModel( index ) ) + return; + + CBaseViewModel *vm = ( CBaseViewModel * )CreateEntityByName( "viewmodel" ); + if ( vm ) + { + vm->SetAbsOrigin( GetAbsOrigin() ); + vm->SetOwner( this ); + vm->SetIndex( index ); + DispatchSpawn( vm ); + vm->FollowEntity( this ); + m_hViewModel.Set( index, vm ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::DestroyViewModels( void ) +{ + int i; + for ( i = MAX_VIEWMODELS - 1; i >= 0; i-- ) + { + CBaseViewModel *vm = GetViewModel( i ); + if ( !vm ) + continue; + + UTIL_Remove( vm ); + m_hViewModel.Set( i, NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Static member function to create a player of the specified class +// Input : *className - +// *ed - +// Output : CBasePlayer +//----------------------------------------------------------------------------- +CBasePlayer *CBasePlayer::CreatePlayer( const char *className, edict_t *ed ) +{ + CBasePlayer *player; + CBasePlayer::s_PlayerEdict = ed; + player = ( CBasePlayer * )CreateEntityByName( className ); + return player; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +CBasePlayer::CBasePlayer( ) +{ + AddEFlags( EFL_NO_AUTO_EDICT_ATTACH ); + +#ifdef _DEBUG + m_vecAutoAim.Init(); + m_vecAdditionalPVSOrigin.Init(); + m_vecCameraPVSOrigin.Init(); + m_DmgOrigin.Init(); + m_vecLadderNormal.Init(); + + m_oldOrigin.Init(); + m_vecSmoothedVelocity.Init(); +#endif + + if ( s_PlayerEdict ) + { + // take the assigned edict_t and attach it + Assert( s_PlayerEdict != NULL ); + NetworkProp()->AttachEdict( s_PlayerEdict ); + s_PlayerEdict = NULL; + } + + m_flFlashTime = -1; + pl.fixangle = FIXANGLE_ABSOLUTE; + pl.hltv = false; + pl.frags = 0; + pl.deaths = 0; + + m_szNetname[0] = '\0'; + + m_iHealth = 0; + Weapon_SetLast( NULL ); + m_bitsDamageType = 0; + + m_bForceOrigin = false; + m_hVehicle = NULL; + m_pCurrentCommand = NULL; + + // Setup our default FOV + m_iDefaultFOV = g_pGameRules->DefaultFOV(); + + m_hZoomOwner = NULL; + + m_nUpdateRate = 20; // cl_updaterate defualt + m_fLerpTime = 0.1f; // cl_interp default + m_bPredictWeapons = true; + m_bLagCompensation = false; + m_flLaggedMovementValue = 1.0f; + m_StuckLast = 0; + m_impactEnergyScale = 1.0f; + m_fLastPlayerTalkTime = 0.0f; + m_PlayerInfo.SetParent( this ); + + ResetObserverMode(); + + m_surfaceProps = 0; + m_pSurfaceData = NULL; + m_surfaceFriction = 1.0f; + m_chTextureType = 0; + m_chPreviousTextureType = 0; + + m_fDelay = 0.0f; + m_fReplayEnd = -1; + m_iReplayEntity = 0; + + m_autoKickDisabled = false; + + m_nNumCrouches = 0; + m_bDuckToggled = false; +} + +CBasePlayer::~CBasePlayer( ) +{ + VPhysicsDestroyObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CBasePlayer::UpdateOnRemove( void ) +{ + VPhysicsDestroyObject(); + + // Remove him from his current team + if ( GetTeam() ) + { + GetTeam()->RemovePlayer( this ); + } + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : **pvs - +// **pas - +//----------------------------------------------------------------------------- +void CBasePlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) +{ + // If we have a viewentity, we don't add the player's origin. + if ( pViewEntity ) + return; + + Vector org; + org = EyePosition(); + + engine->AddOriginToPVS( org ); +} + +int CBasePlayer::UpdateTransmitState() +{ + // always call ShouldTransmit() for players + return SetTransmitState( FL_EDICT_FULLCHECK ); +} + +int CBasePlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo ) +{ + // Allow me to introduce myself to, err, myself. + // I.e., always update the recipient player data even if it's nodraw (first person mode) + if ( pInfo->m_pClientEnt == edict() ) + { + return FL_EDICT_ALWAYS; + } + + // when HLTV is connected and spectators press +USE, they + // signal that they are recording a interesting scene + // so transmit these 'cameramans' to the HLTV client + if ( HLTVDirector()->GetCameraMan() == entindex() ) + { + CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + + Assert( pRecipientEntity->IsPlayer() ); + + CBasePlayer *pRecipientPlayer = static_cast( pRecipientEntity ); + if ( pRecipientPlayer->IsHLTV() ) + { + // HACK force calling RecomputePVSInformation to update PVS data + NetworkProp()->AreaNum(); + return FL_EDICT_ALWAYS; + } + } + + // Transmit for a short time after death so ragdolls can access reliable player data + if ( IsEffectActive( EF_NODRAW ) || ( IsObserver() && ( gpGlobals->curtime - m_flDeathTime > 0.5 ) ) ) + { + return FL_EDICT_DONTSEND; + } + + return BaseClass::ShouldTransmit( pInfo ); +} + + +bool CBasePlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec *pEntityTransmitBits ) const +{ + // Team members shouldn't be adjusted unless friendly fire is on. + if ( !friendlyfire.GetInt() && pPlayer->GetTeamNumber() == GetTeamNumber() ) + return false; + + // If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it. + if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) ) + return false; + + const Vector &vMyOrigin = GetAbsOrigin(); + const Vector &vHisOrigin = pPlayer->GetAbsOrigin(); + + // get max distance player could have moved within max lag compensation time, + // multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value) + float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat(); + + // If the player is within this distance, lag compensate them in case they're running past us. + if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance ) + return true; + + // If their origin is not within a 45 degree cone in front of us, no need to lag compensate. + Vector vForward; + AngleVectors( pCmd->viewangles, &vForward ); + + Vector vDiff = vHisOrigin - vMyOrigin; + VectorNormalize( vDiff ); + + float flCosAngle = 0.707107f; // 45 degree angle + if ( vForward.Dot( vDiff ) < flCosAngle ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Sets the view angles +//----------------------------------------------------------------------------- +void CBasePlayer::SnapEyeAngles( const QAngle &viewAngles ) +{ + pl.v_angle = viewAngles; + pl.fixangle = FIXANGLE_ABSOLUTE; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iSpeed - +// iMax - +// Output : int +//----------------------------------------------------------------------------- +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + +void CBasePlayer::DeathSound( const CTakeDamageInfo &info ) +{ + // temporarily using pain sounds for death sounds + + // Did we die from falling? + if ( m_bitsDamageType & DMG_FALL ) + { + // They died in the fall. Play a splat sound. + EmitSound( "Player.FallGib" ); + } + else + { + EmitSound( "Player.Death" ); + } + + // play one of the suit death alarms + if ( IsSuitEquipped() ) + { + UTIL_EmitGroupnameSuit(edict(), "HEV_DEAD"); + } +} + +// override takehealth +// bitsDamageType indicates type of damage healed. + +int CBasePlayer::TakeHealth( float flHealth, int bitsDamageType ) +{ + // clear out any damage types we healed. + // UNDONE: generic health should not heal any + // UNDONE: time-based damage + if (m_takedamage) + { + m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); + } + + return BaseClass::TakeHealth (flHealth, bitsDamageType); +} + + +//----------------------------------------------------------------------------- +// Purpose: Draw all overlays (should be implemented in cascade by subclass to add +// any additional non-text overlays) +// Input : +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +void CBasePlayer::DrawDebugGeometryOverlays(void) +{ + // -------------------------------------------------------- + // If in buddha mode and dead draw lines to indicate death + // -------------------------------------------------------- + if ((m_debugOverlays & OVERLAY_BUDDHA_MODE) && m_iHealth == 1) + { + Vector vBodyDir = BodyDirection2D( ); + Vector eyePos = EyePosition() + vBodyDir*10.0; + Vector vUp = Vector(0,0,8); + Vector vSide; + CrossProduct( vBodyDir, vUp, vSide); + NDebugOverlay::Line(eyePos+vSide+vUp, eyePos-vSide-vUp, 255,0,0, false, 0); + NDebugOverlay::Line(eyePos+vSide-vUp, eyePos-vSide+vUp, 255,0,0, false, 0); + } + BaseClass::DrawDebugGeometryOverlays(); +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr ) +{ + if ( m_takedamage ) + { + CTakeDamageInfo info = inputInfo; + + // -------------------------------------------------- + // If an NPC check if friendly fire is disallowed + // -------------------------------------------------- + CAI_BaseNPC *pNPC = info.GetAttacker()->MyNPCPointer(); + if ( pNPC && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) && pNPC->IRelationType( this ) != D_HT ) + { + return; + } + + // Prevent team damage here so blood doesn't appear + if ( info.GetAttacker()->IsPlayer() ) + { + if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker() ) ) + return; + } + + SetLastHitGroup( ptr->hitgroup ); + + + switch ( ptr->hitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + info.ScaleDamage( sk_player_head.GetFloat() ); + break; + case HITGROUP_CHEST: + info.ScaleDamage( sk_player_chest.GetFloat() ); + break; + case HITGROUP_STOMACH: + info.ScaleDamage( sk_player_stomach.GetFloat() ); + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + info.ScaleDamage( sk_player_arm.GetFloat() ); + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + info.ScaleDamage( sk_player_leg.GetFloat() ); + break; + default: + break; + } + + SpawnBlood(ptr->endpos, vecDir, BloodColor(), info.GetDamage());// a little surface blood. + TraceBleed( info.GetDamage(), vecDir, ptr, info.GetDamageType() ); + AddMultiDamage( info, this ); + } +} + +//------------------------------------------------------------------------------ +// Purpose : Do some kind of damage effect for the type of damage +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBasePlayer::DamageEffect(float flDamage, int fDamageType) +{ + if (fDamageType & DMG_CRUSH) + { + //Red damage indicator + color32 red = {128,0,0,128}; + UTIL_ScreenFade( this, red, 1.0f, 0.1f, FFADE_IN ); + } + else if (fDamageType & DMG_DROWN) + { + //Red damage indicator + color32 blue = {0,0,128,128}; + UTIL_ScreenFade( this, blue, 1.0f, 0.1f, FFADE_IN ); + } + else if (fDamageType & DMG_SLASH) + { + // If slash damage shoot some blood + SpawnBlood(EyePosition(), g_vecAttackDir, BloodColor(), flDamage); + } + else if (fDamageType & DMG_PLASMA) + { + // Blue screen fade + color32 blue = {0,0,255,100}; + UTIL_ScreenFade( this, blue, 0.2, 0.4, FFADE_MODULATE ); + + // Very small screen shake + ViewPunch(QAngle(random->RandomFloat(-0.1,0.1), random->RandomFloat(-0.1,0.1), random->RandomFloat(-0.1,0.1))); + + // Burn sound + EmitSound( "Player.PlasmaDamage" ); + } + else if (fDamageType & DMG_SONIC) + { + // Sonic damage sound + EmitSound( "Player.SonicDamage" ); + } + else if ( fDamageType & DMG_BULLET ) + { + EmitSound( "Flesh.BulletImpact" ); + } +} + +/* + Take some damage. + NOTE: each call to OnTakeDamage with bitsDamageType set to a time-based damage + type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation + etc are implemented with subsequent calls to OnTakeDamage using DMG_GENERIC. +*/ + +// Old values +#define OLD_ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +#define OLD_ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health + +// New values +#define ARMOR_RATIO 0.2 +#define ARMOR_BONUS 1.0 + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CBasePlayer::ShouldTakeDamageInCommentaryMode( const CTakeDamageInfo &inputInfo ) +{ + // Only ignore damage when we're listening to a commentary node + if ( !IsListeningToCommentary() ) + return true; + + // Allow SetHealth inputs to kill player. + if ( inputInfo.GetInflictor() == this && inputInfo.GetAttacker() == this ) + return true; + + // In commentary, ignore all damage except for falling and leeches + if ( !(inputInfo.GetDamageType() & (DMG_BURN | DMG_PLASMA | DMG_FALL | DMG_CRUSH)) && inputInfo.GetDamageType() != DMG_GENERIC ) + return false; + + // We let DMG_CRUSH pass the check above so that we can check here for stress damage. Deny the CRUSH damage if there is no attacker, + // or if the attacker isn't a BSP model. Therefore, we're allowing any CRUSH damage done by a BSP model. + if ( (inputInfo.GetDamageType() & DMG_CRUSH) && ( inputInfo.GetAttacker() == NULL || !inputInfo.GetAttacker()->IsBSPModel() ) ) + return false; + + return true; +} + +int CBasePlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo ) +{ + // have suit diagnose the problem - ie: report damage type + int bitsDamage = inputInfo.GetDamageType(); + int ffound = true; + int fmajor; + int fcritical; + int fTookDamage; + int ftrivial; + float flRatio; + float flBonus; + float flHealthPrev = m_iHealth; + + CTakeDamageInfo info = inputInfo; + + IServerVehicle *pVehicle = GetVehicle(); + if ( pVehicle ) + { + // Players don't take blast or radiation damage while in vehicles. + // The vehicle has to deal it to him + if ( info.GetDamageType() & (DMG_BLAST|DMG_RADIATION) ) + return 0; + + info.ScaleDamage(pVehicle->DamageModifier(info)); + } + + if ( IsInCommentaryMode() ) + { + if( !ShouldTakeDamageInCommentaryMode( info ) ) + return 0; + } + + if ( GetFlags() & FL_GODMODE ) + return 0; + + if ( m_debugOverlays & OVERLAY_BUDDHA_MODE ) + { + if ((m_iHealth - info.GetDamage()) <= 0) + { + m_iHealth = 1; + return 0; + } + } + + // Early out if there's no damage + if ( !info.GetDamage() ) + return 0; + + if( old_armor.GetBool() ) + { + flBonus = OLD_ARMOR_BONUS; + flRatio = OLD_ARMOR_RATIO; + } + else + { + flBonus = ARMOR_BONUS; + flRatio = ARMOR_RATIO; + } + + if ( ( info.GetDamageType() & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) + { + // blasts damage armor more. + flBonus *= 2; + } + + // Already dead + if ( !IsAlive() ) + return 0; + // go take the damage first + + + if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker() ) ) + { + // Refuse the damage + return 0; + } + + // keep track of amount of damage last sustained + m_lastDamageAmount = static_cast(info.GetDamage()); + + // Armor. + if (m_ArmorValue && !(info.GetDamageType() & (DMG_FALL | DMG_DROWN | DMG_POISON | DMG_RADIATION)) )// armor doesn't protect against fall or drown damage! + { + float flNew = info.GetDamage() * flRatio; + + float flArmor; + + flArmor = (info.GetDamage() - flNew) * flBonus; + + if( !old_armor.GetBool() ) + { + if( flArmor < 1.0 ) + { + flArmor = 1.0; + } + } + + // Does this use more armor than we have? + if (flArmor > m_ArmorValue) + { + flArmor = m_ArmorValue; + flArmor *= (1/flBonus); + flNew = info.GetDamage() - flArmor; + m_DmgSave = m_ArmorValue; + m_ArmorValue = 0; + } + else + { + m_DmgSave = flArmor; + m_ArmorValue -= flArmor; + } + + info.SetDamage( flNew ); + } + + // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that + // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) + + // NOTENOTE: jdw - We are now capable of retaining the matissa of this damage value and deferring its application + + // info.SetDamage( (int)info.GetDamage() ); + + // Call up to the base class + fTookDamage = BaseClass::OnTakeDamage( info ); + + // Early out if the base class took no damage + if ( !fTookDamage ) + return 0; + + // 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 ( info.GetInflictor() && info.GetInflictor()->edict() ) + m_DmgOrigin = info.GetInflictor()->GetAbsOrigin(); + + m_DmgTake += (int)info.GetDamage(); + + // reset damage time countdown for each type of time based damage player just sustained + + for (int i = 0; i < CDMG_TIMEBASED; i++) + { + if (info.GetDamageType() & (DMG_PARALYZE << i)) + { + m_rgbTimeBasedDamage[i] = 0; + } + } + + // Display any effect associate with this damage type + DamageEffect(info.GetDamage(),bitsDamage); + + // how bad is it, doc? + ftrivial = (m_iHealth > 75 || m_lastDamageAmount < 5); + fmajor = (m_lastDamageAmount > 25); + fcritical = (m_iHealth < 30); + + // handle all bits set in this damage message, + // let the suit give player the diagnosis + + // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash ) + + // UNDONE: still need to record damage and heal messages for the following types + + // DMG_BURN + // DMG_FREEZE + // DMG_BLAST + // DMG_SHOCK + + m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + while (fTookDamage && (!ftrivial || (bitsDamage & DMG_TIMEBASED)) && ffound && bitsDamage) + { + ffound = false; + + if (bitsDamage & DMG_CLUB) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture + bitsDamage &= ~DMG_CLUB; + ffound = true; + } + if (bitsDamage & (DMG_FALL | DMG_CRUSH)) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG5", false, SUIT_NEXT_IN_30SEC); // major fracture + else + SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture + + bitsDamage &= ~(DMG_FALL | DMG_CRUSH); + ffound = true; + } + + if (bitsDamage & DMG_BULLET) + { + if (m_lastDamageAmount > 5) + SetSuitUpdate("!HEV_DMG6", false, SUIT_NEXT_IN_30SEC); // blood loss detected + //else + // SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_BULLET; + ffound = true; + } + + if (bitsDamage & DMG_SLASH) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG1", false, SUIT_NEXT_IN_30SEC); // major laceration + else + SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_SLASH; + ffound = true; + } + + if (bitsDamage & DMG_SONIC) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG2", false, SUIT_NEXT_IN_1MIN); // internal bleeding + bitsDamage &= ~DMG_SONIC; + ffound = true; + } + + if (bitsDamage & (DMG_POISON | DMG_PARALYZE)) + { + if (bitsDamage & DMG_POISON) + { + m_nPoisonDmg += static_cast(info.GetDamage()); + m_tbdPrev = gpGlobals->curtime; + m_rgbTimeBasedDamage[itbd_PoisonRecover] = 0; + } + + SetSuitUpdate("!HEV_DMG3", false, SUIT_NEXT_IN_1MIN); // blood toxins detected + bitsDamage &= ~( DMG_POISON | DMG_PARALYZE ); + ffound = true; + } + + if (bitsDamage & DMG_ACID) + { + SetSuitUpdate("!HEV_DET1", false, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected + bitsDamage &= ~DMG_ACID; + ffound = true; + } + + if (bitsDamage & DMG_NERVEGAS) + { + SetSuitUpdate("!HEV_DET0", false, SUIT_NEXT_IN_1MIN); // biohazard detected + bitsDamage &= ~DMG_NERVEGAS; + ffound = true; + } + + if (bitsDamage & DMG_RADIATION) + { + SetSuitUpdate("!HEV_DET2", false, SUIT_NEXT_IN_1MIN); // radiation detected + bitsDamage &= ~DMG_RADIATION; + ffound = true; + } + if (bitsDamage & DMG_SHOCK) + { + bitsDamage &= ~DMG_SHOCK; + ffound = true; + } + } + + m_Local.m_vecPunchAngle.SetX( -2 ); + + if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75) + { + // first time we take major damage... + // turn automedic on if not on + SetSuitUpdate("!HEV_MED1", false, SUIT_NEXT_IN_30MIN); // automedic on + + // give morphine shot if not given recently + SetSuitUpdate("!HEV_HEAL7", false, SUIT_NEXT_IN_30MIN); // morphine shot + } + + if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75) + { + + // already took major damage, now it's critical... + if (m_iHealth < 6) + SetSuitUpdate("!HEV_HLTH3", false, SUIT_NEXT_IN_10MIN); // near death + else if (m_iHealth < 20) + SetSuitUpdate("!HEV_HLTH2", false, SUIT_NEXT_IN_10MIN); // health critical + + // give critical health warnings + if (!random->RandomInt(0,3) && flHealthPrev < 50) + SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention + } + + // if we're taking time based damage, warn about its continuing effects + if (fTookDamage && (info.GetDamageType() & DMG_TIMEBASED) && flHealthPrev < 75) + { + if (flHealthPrev < 50) + { + if (!random->RandomInt(0,3)) + SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention + } + else + SetSuitUpdate("!HEV_HLTH1", false, SUIT_NEXT_IN_10MIN); // health dropping + } + + // Do special explosion damage effect + if ( bitsDamage & DMG_BLAST ) + { + OnDamagedByExplosion( info ); + } + + return fTookDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &info - +// damageAmount - +//----------------------------------------------------------------------------- +#define MIN_SHOCK_AND_CONFUSION_DAMAGE 30.0f +#define MIN_EAR_RINGING_DISTANCE 240.0f // 20 feet + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &info - +//----------------------------------------------------------------------------- +void CBasePlayer::OnDamagedByExplosion( const CTakeDamageInfo &info ) +{ + float lastDamage = info.GetDamage(); + + float distanceFromPlayer = 9999.0f; + + CBaseEntity *inflictor = info.GetInflictor(); + if ( inflictor ) + { + Vector delta = GetAbsOrigin() - inflictor->GetAbsOrigin(); + distanceFromPlayer = delta.Length(); + } + + bool ear_ringing = distanceFromPlayer < MIN_EAR_RINGING_DISTANCE ? true : false; + bool shock = lastDamage >= MIN_SHOCK_AND_CONFUSION_DAMAGE; + + if ( !shock && !ear_ringing ) + return; + + int effect = shock ? + random->RandomInt( 35, 37 ) : + random->RandomInt( 32, 34 ); + + CSingleUserRecipientFilter user( this ); + enginesound->SetPlayerDSP( user, effect, false ); +} + +//========================================================= +// PackDeadPlayerItems - call this when a player dies to +// pack up the appropriate weapons and ammo items, and to +// destroy anything that shouldn't be packed. +// +// This is pretty brute force :( +//========================================================= +void CBasePlayer::PackDeadPlayerItems( void ) +{ + int iWeaponRules; + int iAmmoRules; + int i; + CBaseCombatWeapon *rgpPackWeapons[ 20 ];// 20 hardcoded for now. How to determine exactly how many weapons we have? + int iPackAmmo[ MAX_AMMO_SLOTS + 1]; + int iPW = 0;// index into packweapons array + int iPA = 0;// index into packammo array + + memset(rgpPackWeapons, 0, sizeof(rgpPackWeapons) ); + memset(iPackAmmo, -1, sizeof(iPackAmmo) ); + + // get the game rules + iWeaponRules = g_pGameRules->DeadPlayerWeapons( this ); + iAmmoRules = g_pGameRules->DeadPlayerAmmo( this ); + + if ( iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO ) + { + // nothing to pack. Remove the weapons and return. Don't call create on the box! + RemoveAllItems( true ); + return; + } + +// go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < WeaponCount() ; i++ ) + { + // there's a weapon here. Should I pack it? + CBaseCombatWeapon *pPlayerItem = GetWeapon( i ); + if ( pPlayerItem ) + { + switch( iWeaponRules ) + { + case GR_PLR_DROP_GUN_ACTIVE: + if ( GetActiveWeapon() && pPlayerItem == GetActiveWeapon() ) + { + // this is the active item. Pack it. + rgpPackWeapons[ iPW++ ] = pPlayerItem; + } + break; + + case GR_PLR_DROP_GUN_ALL: + rgpPackWeapons[ iPW++ ] = pPlayerItem; + break; + + default: + break; + } + } + } + +// now go through ammo and make a list of which types to pack. + if ( iAmmoRules != GR_PLR_DROP_AMMO_NO ) + { + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( GetAmmoCount( i ) > 0 ) + { + // player has some ammo of this type. + switch ( iAmmoRules ) + { + case GR_PLR_DROP_AMMO_ALL: + iPackAmmo[ iPA++ ] = i; + break; + + case GR_PLR_DROP_AMMO_ACTIVE: + // WEAPONTODO: Make this work + /* + if ( GetActiveWeapon() && i == GetActiveWeapon()->m_iPrimaryAmmoType ) + { + // this is the primary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + else if ( GetActiveWeapon() && i == GetActiveWeapon()->m_iSecondaryAmmoType ) + { + // this is the secondary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + */ + break; + + default: + break; + } + } + } + } + + RemoveAllItems( true );// now strip off everything that wasn't handled by the code above. +} + +void CBasePlayer::RemoveAllItems( bool removeSuit ) +{ + if (GetActiveWeapon()) + { + ResetAutoaim( ); + GetActiveWeapon()->Holster( ); + } + + Weapon_SetLast( NULL ); + RemoveAllWeapons(); + RemoveAllAmmo(); + + if ( removeSuit ) + { + RemoveSuit(); + } + + UpdateClientData(); +} + +bool CBasePlayer::IsDead() const +{ + return m_lifeState == LIFE_DEAD; +} + +static float DamageForce( const Vector &size, float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (size.x * size.y * size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + + +int CBasePlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + // set damage type sustained + m_bitsDamageType |= info.GetDamageType(); + + if ( !BaseClass::OnTakeDamage_Alive( info ) ) + return 0; + + CBaseEntity * attacker = info.GetAttacker(); + + if ( !attacker ) + return 0; + + Vector vecDir = vec3_origin; + if ( info.GetInflictor() ) + { + vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter(); + VectorNormalize( vecDir ); + } + + if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK) && + ( !attacker->IsSolidFlagSet(FSOLID_TRIGGER)) ) + { + Vector force = vecDir * -DamageForce( WorldAlignSize(), info.GetBaseDamage() ); + if ( force.z > 250.0f ) + { + force.z = 250.0f; + } + ApplyAbsVelocityImpulse( force ); + } + + // fire global game event + + IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" ); + if ( event ) + { + event->SetInt("userid", GetUserID() ); + event->SetInt("health", max(0, m_iHealth) ); + event->SetInt("priority", 5 ); // HLTV event priority, not transmitted + + if ( attacker->IsPlayer() ) + { + CBasePlayer *player = ToBasePlayer( attacker ); + event->SetInt("attacker", player->GetUserID() ); // hurt by other player + } + else + { + event->SetInt("attacker", 0 ); // hurt by "world" + } + + gameeventmanager->FireEvent( event ); + } + + // Insert a combat sound so that nearby NPCs hear battle + if ( attacker->IsNPC() ) + { + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 512, 0.5, this );//<>//magic number + } + + return 1; +} + + +void CBasePlayer::Event_Killed( const CTakeDamageInfo &info ) +{ + CSound *pSound; + + g_pGameRules->PlayerKilled( this, info ); + + RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE ); + + ClearUseEntity(); + + // this client isn't going to be thinking for a while, so reset the sound until they respawn + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + { + if ( pSound ) + { + pSound->Reset(); + } + } + + // don't let the status bar glitch for players with <0 health. + if (m_iHealth < -99) + { + m_iHealth = 0; + } + + // holster the current weapon + if ( GetActiveWeapon() ) + { + GetActiveWeapon()->Holster(); + } + + SetAnimation( PLAYER_DIE ); + + SetViewOffset( VEC_DEAD_VIEWHEIGHT ); + m_lifeState = LIFE_DYING; + + pl.deadflag = true; + AddSolidFlags( FSOLID_NOT_SOLID ); + // force contact points to get flushed if no longer valid + // UNDONE: Always do this on RecheckCollisionFilter() ? + IPhysicsObject *pObject = VPhysicsGetObject(); + if ( pObject ) + { + pObject->RecheckContactPoints(); + } + + SetMoveType( MOVETYPE_FLYGRAVITY ); + SetGroundEntity( NULL ); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, false, 0); + + // reset FOV + SetFOV( this, 0 ); + + if ( FlashlightIsOn() ) + { + FlashlightTurnOff(); + } + + m_flDeathTime = gpGlobals->curtime; + + // only count alive players + if (m_lastNavArea) + { + m_lastNavArea->DecrementPlayerCount( GetTeamNumber() ); + m_lastNavArea = NULL; + } + + BaseClass::Event_Killed( info ); +} + +void CBasePlayer::Event_Dying() +{ + // NOT GIBBED, RUN THIS CODE + + CTakeDamageInfo info; + DeathSound( info ); + + // The dead body rolls out of the vehicle. + if ( IsInAVehicle() ) + { + LeaveVehicle(); + } + + QAngle angles = GetLocalAngles(); + + angles.x = 0; + angles.z = 0; + + SetLocalAngles( angles ); + + SetThink(&CBasePlayer::PlayerDeathThink); + SetNextThink( gpGlobals->curtime + 0.1f ); + BaseClass::Event_Dying(); +} + + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + char szAnim[64]; + + float speed; + + speed = GetAbsVelocity().Length2D(); + + if (GetFlags() & (FL_FROZEN|FL_ATCONTROLS)) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + Activity idealActivity = ACT_WALK;// TEMP!!!!! + + // This could stand to be redone. Why is playerAnim abstracted from activity? (sjb) + if (playerAnim == PLAYER_JUMP) + { + idealActivity = ACT_HOP; + } + else if (playerAnim == PLAYER_SUPERJUMP) + { + idealActivity = ACT_LEAP; + } + else if (playerAnim == PLAYER_DIE) + { + if ( m_lifeState == LIFE_ALIVE ) + { + idealActivity = GetDeathActivity(); + } + } + else if (playerAnim == PLAYER_ATTACK1) + { + if ( m_Activity == ACT_HOVER || + m_Activity == ACT_SWIM || + m_Activity == ACT_HOP || + m_Activity == ACT_LEAP || + m_Activity == ACT_DIESIMPLE ) + { + idealActivity = m_Activity; + } + else + { + idealActivity = ACT_RANGE_ATTACK1; + } + } + else if (playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK) + { + if ( !( GetFlags() & FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping + { + idealActivity = m_Activity; + } + else if ( GetWaterLevel() > 1 ) + { + if ( speed == 0 ) + idealActivity = ACT_HOVER; + else + idealActivity = ACT_SWIM; + } + else + { + idealActivity = ACT_WALK; + } + } + + + if (idealActivity == ACT_RANGE_ATTACK1) + { + if ( GetFlags() & FL_DUCKING ) // crouching + { + Q_strncpy( szAnim, "crouch_shoot_" ,sizeof(szAnim)); + } + else + { + Q_strncpy( szAnim, "ref_shoot_" ,sizeof(szAnim)); + } + Q_strncat( szAnim, m_szAnimExtension ,sizeof(szAnim), COPY_ALL_CHARACTERS ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + + if ( GetSequence() != animDesired || !SequenceLoops() ) + { + SetCycle( 0 ); + } + + // Tracker 24588: In single player when firing own weapon this causes eye and punchangle to jitter + //if (!SequenceLoops()) + //{ + // AddEffects( EF_NOINTERP ); + //} + + SetActivity( idealActivity ); + ResetSequence( animDesired ); + } + else if (idealActivity == ACT_WALK) + { + if (GetActivity() != ACT_RANGE_ATTACK1 || IsActivityFinished()) + { + if ( GetFlags() & FL_DUCKING ) // crouching + { + Q_strncpy( szAnim, "crouch_aim_" ,sizeof(szAnim)); + } + else + { + Q_strncpy( szAnim, "ref_aim_" ,sizeof(szAnim)); + } + Q_strncat( szAnim, m_szAnimExtension,sizeof(szAnim), COPY_ALL_CHARACTERS ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + SetActivity( ACT_WALK ); + } + else + { + animDesired = GetSequence(); + } + } + else + { + if ( GetActivity() == idealActivity) + return; + + SetActivity( idealActivity ); + + animDesired = SelectWeightedSequence( m_Activity ); + + // Already using the desired animation? + if (GetSequence() == animDesired) + return; + + ResetSequence( animDesired ); + SetCycle( 0 ); + return; + } + + // Already using the desired animation? + if (GetSequence() == animDesired) + return; + + //Msg( "Set animation to %d\n", animDesired ); + // Reset to first frame of desired animation + ResetSequence( animDesired ); + SetCycle( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +// ensure that for every emitsound there is a matching stopsound +//----------------------------------------------------------------------------- +void CBasePlayer::SetPlayerUnderwater( bool state ) +{ + if ( m_bPlayerUnderwater != state ) + { + m_bPlayerUnderwater = state; + + if ( state ) + EmitSound( "Player.AmbientUnderWater" ); + else + StopSound( "Player.AmbientUnderWater" ); + } +} + +/* +=========== +WaterMove +============ +*/ +#ifdef HL2_DLL + +// test for HL2 drowning damage increase (aux power used instead) +#define AIRTIME 7 // lung full of air lasts this many seconds +#define DROWNING_DAMAGE_INITIAL 10 +#define DROWNING_DAMAGE_MAX 10 + +#else + +#define AIRTIME 12 // lung full of air lasts this many seconds +#define DROWNING_DAMAGE_INITIAL 2 +#define DROWNING_DAMAGE_MAX 5 + +#endif + +void CBasePlayer::WaterMove() +{ + int air; + + if ( ( GetMoveType() == MOVETYPE_NOCLIP ) && !GetMoveParent() ) + { + m_AirFinished = gpGlobals->curtime + AIRTIME; + return; + } + + if ( m_iHealth < 0 || !IsAlive() ) + { + if ( GetWaterLevel() < WL_Eyes ) + { + if ( IsPlayerUnderwater() ) + { + SetPlayerUnderwater( false ); + } + } + else if ( GetWaterLevel() < WL_Waist ) + { + if ( GetWaterLevel() == 0 ) + { + if ( GetFlags() & FL_INWATER ) + { + RemoveFlag( FL_INWATER ); + } + return; + } + } + else if ( GetWaterLevel() > WL_Waist ) + { + if ( IsPlayerUnderwater() == false ) + { + SetPlayerUnderwater( true ); + } + return; + } + return; + } + + // waterlevel 0 - not in water (WL_NotInWater) + // waterlevel 1 - feet in water (WL_Feet) + // waterlevel 2 - waist in water (WL_Waist) + // waterlevel 3 - head in water (WL_Eyes) + + if (GetWaterLevel() != WL_Eyes || CanBreatheUnderwater()) + { + // not underwater + + // play 'up for air' sound + + if (m_AirFinished < gpGlobals->curtime) + { + EmitSound( "Player.DrownStart" ); + } + + m_AirFinished = gpGlobals->curtime + AIRTIME; + m_nDrownDmgRate = DROWNING_DAMAGE_INITIAL; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType |= DMG_DROWNRECOVER; + m_bitsDamageType &= ~DMG_DROWN; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + } + + } + else + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (m_AirFinished < gpGlobals->curtime && !(GetFlags() & FL_GODMODE) ) // drown! + { + if (m_PainFinished < gpGlobals->curtime) + { + // take drowning damage + m_nDrownDmgRate += 1; + if (m_nDrownDmgRate > DROWNING_DAMAGE_MAX) + { + m_nDrownDmgRate = DROWNING_DAMAGE_MAX; + } + + OnTakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), m_nDrownDmgRate, DMG_DROWN ) ); + m_PainFinished = gpGlobals->curtime + 1; + + // track drowning damage, give it back when + // player finally takes a breath + m_idrowndmg += m_nDrownDmgRate; + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if ( GetWaterLevel() < WL_Eyes ) + { + if ( IsPlayerUnderwater() ) + { + SetPlayerUnderwater( false ); + } + } + else if ( GetWaterLevel() < WL_Waist ) + { + if ( GetWaterLevel() == 0 ) + { + if ( GetFlags() & FL_INWATER ) + { + EmitSound( "Player.Wade" ); + RemoveFlag( FL_INWATER ); + } + return; + } + } + else if ( GetWaterLevel() > WL_Waist ) + { + if ( IsPlayerUnderwater() == false ) + { + SetPlayerUnderwater( true ); + } + return; + } + + // make bubbles + + air = (int)( m_AirFinished - gpGlobals->curtime ); + +#if 0 + if (GetWaterType() == CONTENT_LAVA) // do damage + { + if (m_flDamageTime < gpGlobals->curtime) + { + OnTakeDamage( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 10 * GetWaterLevel(), DMG_BURN); + } + } + else if (GetWaterType() == CONTENT_SLIME) // do damage + { + m_flDamageTime = gpGlobals->curtime + 1; + OnTakeDamage(GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 4 * GetWaterLevel(), DMG_ACID); + } +#endif + + if (!(GetFlags() & FL_INWATER)) + { + // player enter water sound + if (GetWaterType() == CONTENTS_WATER) + { + EmitSound( "Player.Wade" ); + } + + AddFlag( FL_INWATER ); + } +} + + +// true if the player is attached to a ladder +bool CBasePlayer::IsOnLadder( void ) +{ + return (GetMoveType() == MOVETYPE_LADDER); +} + + +float CBasePlayer::GetWaterJumpTime() const +{ + return m_flWaterJumpTime; +} + +void CBasePlayer::SetWaterJumpTime( float flWaterJumpTime ) +{ + m_flWaterJumpTime = flWaterJumpTime; +} + +float CBasePlayer::GetSwimSoundTime( void ) const +{ + return m_flSwimSoundTime; +} + +void CBasePlayer::SetSwimSoundTime( float flSwimSoundTime ) +{ + m_flSwimSoundTime = flSwimSoundTime; +} + +void CBasePlayer::ShowViewPortPanel( const char * name, bool bShow, KeyValues *data ) +{ + CSingleUserRecipientFilter filter( this ); + filter.MakeReliable(); + + int count = 0; + KeyValues *subkey = NULL; + + if ( data ) + { + subkey = data->GetFirstSubKey(); + while ( subkey ) + { + count++; subkey = subkey->GetNextKey(); + } + + subkey = data->GetFirstSubKey(); // reset + } + + UserMessageBegin( filter, "VGUIMenu" ); + WRITE_STRING( name ); // menu name + WRITE_BYTE( bShow?1:0 ); + WRITE_BYTE( count ); + + // write additional data (be carefull not more than 192 bytes!) + while ( subkey ) + { + WRITE_STRING( subkey->GetName() ); + WRITE_STRING( subkey->GetString() ); + subkey = subkey->GetNextKey(); + } + MessageEnd(); +} + + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + SetNextThink( gpGlobals->curtime + 0.1f ); + + if (GetFlags() & FL_ONGROUND) + { + flForward = GetAbsVelocity().Length() - 20; + if (flForward <= 0) + { + SetAbsVelocity( vec3_origin ); + } + else + { + Vector vecNewVelocity = GetAbsVelocity(); + VectorNormalize( vecNewVelocity ); + vecNewVelocity *= flForward; + SetAbsVelocity( vecNewVelocity ); + } + } + + if ( HasWeapons() ) + { + // we drop the guns here because weapons that have an area effect and can kill their user + // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the + // player class sometimes is freed. It's safer to manipulate the weapons once we know + // we aren't calling into any of their code anymore through the player pointer. + PackDeadPlayerItems(); + } + + if (GetModelIndex() && (!IsSequenceFinished()) && (m_lifeState == LIFE_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; + if ( m_iRespawnFrames < 60 ) // animations should be no longer than this + return; + } + + if (m_lifeState == LIFE_DYING) + m_lifeState = LIFE_DEAD; + + StopAnimation(); + + AddEffects( EF_NOINTERP ); + m_flPlaybackRate = 0.0; + + int fAnyButtonDown = (m_nButtons & ~IN_SCORE); + + // Strip out the duck key from this check if it's toggled + if ( (fAnyButtonDown & IN_DUCK) && GetToggledDuckState()) + { + fAnyButtonDown &= ~IN_DUCK; + } + + // wait for all buttons released + if (m_lifeState == LIFE_DEAD) + { + if (fAnyButtonDown) + return; + + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_lifeState = LIFE_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. + if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->curtime > (m_flDeathTime + DEATH_ANIMATION_TIME) ) && !IsObserver() ) + { + // go to dead camera. + StartObserverMode( m_iObserverLastMode ); + } + +// wait for any button down, or mp_forcerespawn is set and the respawn time is up + if (!fAnyButtonDown + && !( g_pGameRules->IsMultiplayer() && forcerespawn.GetInt() > 0 && (gpGlobals->curtime > (m_flDeathTime + 5))) ) + return; + + m_nButtons = 0; + m_iRespawnFrames = 0; + + //Msg( "Respawn\n"); + + respawn( this, !IsObserver() );// don't copy a corpse if we're in deathcam. + SetNextThink( TICK_NEVER_THINK ); +} + +/* + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + CBaseEntity *pSpot, *pNewSpot; + int iRand; + + if ( GetViewOffset() == vec3_origin ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = gEntList.FindEntityByClassname( NULL, "info_intermission"); + + if ( pSpot ) + { + // at least one intermission spot in the world. + iRand = random->RandomInt( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = gEntList.FindEntityByClassname( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CreateCorpse(); + StartObserverMode( pSpot->GetAbsOrigin(), pSpot->GetAbsAngles() ); + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + trace_t tr; + + CreateCorpse(); + + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 128 ), + MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + QAngle angles; + VectorAngles( GetAbsOrigin() - tr.endpos, angles ); + StartObserverMode( tr.endpos, angles ); + return; + } +} */ + +void CBasePlayer::StopObserverMode() +{ + m_bForcedObserverMode = false; + m_afPhysicsFlags &= ~PFLAG_OBSERVER; + + if ( m_iObserverMode == OBS_MODE_NONE ) + return; + + if ( m_iObserverMode > OBS_MODE_DEATHCAM ) + { + m_iObserverLastMode = m_iObserverMode; + } + + m_iObserverMode.Set( OBS_MODE_NONE ); + + ShowViewPortPanel( "specmenu", false ); + ShowViewPortPanel( "specgui", false ); + ShowViewPortPanel( "overview", false ); +} + +bool CBasePlayer::StartObserverMode(int mode) +{ + if ( !IsObserver() ) + { + // set position to last view offset + SetAbsOrigin( GetAbsOrigin() + GetViewOffset() ); + SetViewOffset( vec3_origin ); + } + + Assert( mode > OBS_MODE_NONE ); + + m_afPhysicsFlags |= PFLAG_OBSERVER; + + // Holster weapon immediately, to allow it to cleanup + if ( GetActiveWeapon() ) + GetActiveWeapon()->Holster(); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + SetGroundEntity( (CBaseEntity *)NULL ); + + RemoveFlag( FL_DUCKING ); + + AddSolidFlags( FSOLID_NOT_SOLID ); + + SetObserverMode( mode ); + + ShowViewPortPanel( "specgui" ); + + // Setup flags + m_Local.m_iHideHUD = HIDEHUD_HEALTH; + m_takedamage = DAMAGE_NO; + + //Don't set the player to EF_NODRAW - the client can determine + //whether to draw the player or not with ShouldDraw + //AddEffects( EF_NODRAW ); + + m_iHealth = 1; + m_lifeState = LIFE_DEAD; // Can't be dead, otherwise movement doesn't work right. + pl.deadflag = true; + + return true; +} + +bool CBasePlayer::SetObserverMode(int mode ) +{ + if ( mode < OBS_MODE_NONE || mode > OBS_MODE_ROAMING ) + return false; + + + // check mp_forcecamera settings for dead players + if ( mode > OBS_MODE_FIXED && GetTeamNumber() > TEAM_SPECTATOR ) + { + switch ( mp_forcecamera.GetInt() ) + { + case OBS_ALLOW_ALL : break; // no restrictions + case OBS_ALLOW_TEAM : mode = OBS_MODE_IN_EYE; break; + case OBS_ALLOW_NONE : mode = OBS_MODE_FIXED; break; // donw't allow anything + } + } + + if ( m_iObserverMode > OBS_MODE_DEATHCAM ) + { + // remember mode if we were really spectating before + m_iObserverLastMode = m_iObserverMode; + } + + m_iObserverMode = mode; + + switch ( mode ) + { + case OBS_MODE_NONE: + case OBS_MODE_FIXED : + case OBS_MODE_DEATHCAM : + SetFOV( this, 0 ); // Reset FOV + SetViewOffset( vec3_origin ); + SetMoveType( MOVETYPE_NONE ); + break; + + case OBS_MODE_CHASE : + case OBS_MODE_IN_EYE : + // udpate FOV and viewmodels + SetObserverTarget( m_hObserverTarget ); + SetMoveType( MOVETYPE_OBSERVER ); + break; + + case OBS_MODE_ROAMING : + SetFOV( this, 0 ); // Reset FOV + SetObserverTarget( m_hObserverTarget ); + SetViewOffset( vec3_origin ); + SetMoveType( MOVETYPE_OBSERVER ); + break; + + } + + CheckObserverSettings(); + + return true; +} + +int CBasePlayer::GetObserverMode() +{ + return m_iObserverMode; +} + +void CBasePlayer::ForceObserverMode(int mode) +{ + int tempMode = OBS_MODE_ROAMING; + + if ( m_iObserverMode == mode ) + return; + + // don't change last mode if already in forced mode + + if ( m_bForcedObserverMode ) + { + tempMode = m_iObserverLastMode; + } + + SetObserverMode( mode ); + + if ( m_bForcedObserverMode ) + { + m_iObserverLastMode = tempMode; + } + + m_bForcedObserverMode = true; +} + +void CBasePlayer::CheckObserverSettings() +{ + // check if we are in forced mode and may go back to old mode + if ( m_bForcedObserverMode ) + { + CBaseEntity * target = m_hObserverTarget; + + if ( !IsValidObserverTarget(target) ) + { + // if old target is still invalid, try to find valid one + target = FindNextObserverTarget( false ); + } + + if ( target ) + { + // we found a valid target + m_bForcedObserverMode = false; // disable force mode + SetObserverMode( m_iObserverLastMode ); // switch to last mode + SetObserverTarget( target ); // goto target + + // TODO check for HUD icons + return; + } + else + { + // else stay in forced mode, no changes + return; + } + } + + // make sure our last mode is valid + if ( m_iObserverLastMode < OBS_MODE_FIXED ) + { + m_iObserverLastMode = OBS_MODE_ROAMING; + } + + // check if our spectating target is still a valid one + + if ( m_iObserverMode == OBS_MODE_IN_EYE || m_iObserverMode == OBS_MODE_CHASE ) + { + if ( !IsValidObserverTarget( m_hObserverTarget.Get() ) ) + { + // our target is not valid, try to find new target + CBaseEntity * target = FindNextObserverTarget( false ); + if ( target ) + { + // switch to new valid target + SetObserverTarget( target ); + } + else + { + // couldn't find new target, switch to temporary mode + if ( mp_forcecamera.GetInt() == OBS_ALLOW_ALL ) + { + // let player roam around + ForceObserverMode( OBS_MODE_ROAMING ); + } + else + { + // fix player view right where it is + ForceObserverMode( OBS_MODE_FIXED ); + m_hObserverTarget.Set( NULL ); // no traget to follow + } + } + } + + CBasePlayer *target = ToBasePlayer( m_hObserverTarget.Get() ); + + // for ineye mode we have to copy several data to see exactly the same + + if ( target && m_iObserverMode == OBS_MODE_IN_EYE ) + { + int flagMask = FL_ONGROUND | FL_DUCKING ; + + int flags = target->GetFlags() & flagMask; + + if ( (GetFlags() & flagMask) != flags ) + { + flags |= GetFlags() & (~flagMask); // keep other flags + ClearFlags(); + AddFlag( flags ); + } + + if ( target->GetViewOffset() != GetViewOffset() ) + { + SetViewOffset( target->GetViewOffset() ); + } + } + } +} + +bool CBasePlayer::StartReplayMode( float fDelay, float fDuration, int iEntity ) +{ + if ( ( sv_maxreplay == NULL ) || ( sv_maxreplay->GetFloat() <= 0 ) ) + return false; + + m_fDelay = fDelay; + m_fReplayEnd = gpGlobals->curtime + fDuration; + m_iReplayEntity = iEntity; + + return true; +} + +void CBasePlayer::StopReplayMode() +{ + m_fDelay = 0.0f; + m_fReplayEnd = -1; + m_iReplayEntity = 0; +} + +int CBasePlayer::GetDelayTicks() +{ + if ( m_fReplayEnd > gpGlobals->curtime ) + { + return TIME_TO_TICKS( m_fDelay ); + } + else + { + if ( m_fDelay > 0.0f ) + StopReplayMode(); + + return 0; + } +} + +int CBasePlayer::GetReplayEntity() +{ + return m_iReplayEntity; +} + +CBaseEntity * CBasePlayer::GetObserverTarget() +{ + return m_hObserverTarget.Get(); +} + +void CBasePlayer::ObserverUse( bool bIsPressed ) +{ +#ifndef _XBOX + if ( !HLTVDirector()->IsActive() ) + return; + + if ( GetTeamNumber() != TEAM_SPECTATOR ) + return; // only pure spectators can play cameraman + + if ( !bIsPressed ) + return; + + int iCameraManIndex = HLTVDirector()->GetCameraMan(); + + if ( iCameraManIndex == 0 ) + { + // turn camera on + HLTVDirector()->SetCameraMan( entindex() ); + } + else if ( iCameraManIndex == entindex() ) + { + // turn camera off + HLTVDirector()->SetCameraMan( 0 ); + } + else + { + ClientPrint( this, HUD_PRINTTALK, "Camera in use by other player." ); + } + + /* UTIL_SayText( "Spectator can not USE anything", this ); + + Vector dir,end; + Vector start = GetAbsOrigin(); + + AngleVectors( GetAbsAngles(), &dir ); + VectorNormalize( dir ); + + VectorMA( start, 32.0f, dir, end ); + + trace_t tr; + UTIL_TraceLine( start, end, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + + if ( tr.fraction == 1.0f ) + return; // no obstacles in spectators way + + VectorMA( start, 128.0f, dir, end ); + + Ray_t ray; + ray.Init( end, start, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); + + UTIL_TraceRay( ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + + if ( tr.startsolid || tr.allsolid ) + return; + + SetAbsOrigin( tr.endpos ); */ +#endif +} + +void CBasePlayer::JumptoPosition(const Vector &origin, const QAngle &angles) +{ + SetAbsOrigin( origin ); + SetAbsVelocity( vec3_origin ); // stop movement + SetLocalAngles( angles ); + SnapEyeAngles( angles ); +} + +bool CBasePlayer::SetObserverTarget(CBaseEntity *target) +{ + if ( !IsValidObserverTarget( target ) ) + return false; + + // set new target + m_hObserverTarget.Set( target ); + + // reset fov to default + SetFOV( this, 0 ); + + if ( m_iObserverMode == OBS_MODE_ROAMING ) + { + Vector dir, end; + Vector start = target->EyePosition(); + + AngleVectors( target->EyeAngles(), &dir ); + VectorNormalize( dir ); + VectorMA( start, -64.0f, dir, end ); + + Ray_t ray; + ray.Init( start, end, VEC_DUCK_HULL_MIN , VEC_DUCK_HULL_MAX ); + + trace_t tr; + UTIL_TraceRay( ray, MASK_PLAYERSOLID, target, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); + + JumptoPosition( tr.endpos, target->EyeAngles() ); + } + + return true; +} + +bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target) +{ + if ( target == NULL ) + return false; + + // MOD AUTHORS: Add checks on target here or in derived methode + + if ( !target->IsPlayer() ) // only track players + return false; + + CBasePlayer * player = ToBasePlayer( target ); + + /* Don't spec observers or players who haven't picked a class yet + if ( player->IsObserver() ) + return false; */ + + if( player == this ) + return false; // We can't observe ourselves. + + if ( player->IsEffectActive( EF_NODRAW ) ) // don't watch invisible players + return false; + + if ( player->m_lifeState == LIFE_RESPAWNABLE ) // target is dead, waiting for respawn + return false; + + if ( player->m_lifeState == LIFE_DEAD || player->m_lifeState == LIFE_DYING ) + { + if ( (player->m_flDeathTime + DEATH_ANIMATION_TIME ) < gpGlobals->curtime ) + { + return false; // allow watching until 3 seconds after death to see death animation + } + } + + // check forcecamera settings for active players + if ( GetTeamNumber() != TEAM_SPECTATOR ) + { + switch ( mp_forcecamera.GetInt() ) + { + case OBS_ALLOW_ALL : break; + case OBS_ALLOW_TEAM : if ( GetTeamNumber() != target->GetTeamNumber() ) + return false; + break; + case OBS_ALLOW_NONE : return false; + } + } + + return true; // passed all test +} + +int CBasePlayer::GetNextObserverSearchStartPoint( bool bReverse ) +{ + int iDir = bReverse ? -1 : 1; + + int startIndex; + + if ( m_hObserverTarget ) + { + // start using last followed player + startIndex = m_hObserverTarget->entindex(); + } + else + { + // start using own player index + startIndex = this->entindex(); + } + + startIndex += iDir; + if (startIndex > gpGlobals->maxClients) + startIndex = 1; + else if (startIndex < 1) + startIndex = gpGlobals->maxClients; + + return startIndex; +} + +CBaseEntity * CBasePlayer::FindNextObserverTarget(bool bReverse) +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + +/* if ( m_flNextFollowTime && m_flNextFollowTime > gpGlobals->time ) + { + return; + } + + m_flNextFollowTime = gpGlobals->time + 0.25; + */ // TODO move outside this function + + int startIndex = GetNextObserverSearchStartPoint( bReverse ); + + int currentIndex = startIndex; + int iDir = bReverse ? -1 : 1; + + do + { + CBaseEntity * nextTarget = UTIL_PlayerByIndex( currentIndex ); + + if ( IsValidObserverTarget( nextTarget ) ) + { + return nextTarget; // found next valid player + } + + currentIndex += iDir; + + // Loop through the clients + if (currentIndex > gpGlobals->maxClients) + currentIndex = 1; + else if (currentIndex < 1) + currentIndex = gpGlobals->maxClients; + + } while ( currentIndex != startIndex ); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this object can be +used by the player +//----------------------------------------------------------------------------- +bool CBasePlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) +{ + if ( pEntity ) + { + int caps = pEntity->ObjectCaps(); + if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) ) + { + if ( (caps & requiredCaps) == requiredCaps ) + { + return true; + } + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBasePlayer::CanPickupObject( CBaseEntity *pObject, float massLimit, float sizeLimit ) +{ + // UNDONE: Make this virtual and move to HL2 player +#ifdef HL2_DLL + //Must be valid + if ( pObject == NULL ) + return false; + + //Must move with physics + if ( pObject->GetMoveType() != MOVETYPE_VPHYSICS ) + return false; + + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pObject->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + + //Must have a physics object + if (!count) + return false; + + float objectMass = 0; + bool checkEnable = false; + for ( int i = 0; i < count; i++ ) + { + objectMass += pList[i]->GetMass(); + if ( !pList[i]->IsMoveable() ) + { + checkEnable = true; + } + if ( pList[i]->GetGameFlags() & FVPHYSICS_NO_PLAYER_PICKUP ) + return false; + if ( pList[i]->IsHinged() ) + return false; + } + + + //Msg( "Target mass: %f\n", pPhys->GetMass() ); + + //Must be under our threshold weight + if ( massLimit > 0 && objectMass > massLimit ) + return false; + + if ( checkEnable ) + { + // Allowing picking up of bouncebombs. + CBounceBomb *pBomb = dynamic_cast(pObject); + if( pBomb ) + return true; + + // Allow pickup of phys props that are motion enabled on player pickup + CPhysicsProp *pProp = dynamic_cast(pObject); + CPhysBox *pBox = dynamic_cast(pObject); + if ( !pProp && !pBox ) + return false; + + if ( pProp && !(pProp->HasSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON )) ) + return false; + + if ( pBox && !(pBox->HasSpawnFlags( SF_PHYSBOX_ENABLE_ON_PHYSCANNON )) ) + return false; + } + + if ( sizeLimit > 0 ) + { + const Vector &size = pObject->CollisionProp()->OBBSize(); + if ( size.x > sizeLimit || size.y > sizeLimit || size.z > sizeLimit ) + return false; + } + + return true; +#else + return false; +#endif +} + +float CBasePlayer::GetHeldObjectMass( IPhysicsObject *pHeldObject ) +{ + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Server side of jumping rules. Most jumping logic is already +// handled in shared gamemovement code. Put stuff here that should +// only be done server side. +//----------------------------------------------------------------------------- +void CBasePlayer::Jump() +{ +} + +void CBasePlayer::Duck( ) +{ + if (m_nButtons & IN_DUCK) + { + if ( m_Activity != ACT_LEAP ) + { + SetAnimation( PLAYER_WALK ); + } + } +} + +// +// ID's player as such. +// +Class_T CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::ResetFragCount() +{ + m_iFrags = 0; + pl.frags = m_iFrags; +} + +void CBasePlayer::IncrementFragCount( int nCount ) +{ + m_iFrags += nCount; + pl.frags = m_iFrags; +} + +void CBasePlayer::ResetDeathCount() +{ + m_iDeaths = 0; + pl.deaths = m_iDeaths; +} + +void CBasePlayer::IncrementDeathCount( int nCount ) +{ + m_iDeaths += nCount; + pl.deaths = m_iDeaths; +} + +void CBasePlayer::AddPoints( int score, bool bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( m_iFrags < 0 ) // Can't go more negative + return; + + if ( -score > m_iFrags ) // Will this go negative? + { + score = -m_iFrags; // Sum will be 0 + } + } + } + + m_iFrags += score; + pl.frags = m_iFrags; +} + +void CBasePlayer::AddPointsToTeam( int score, bool bAllowNegativeScore ) +{ + if ( GetTeam() ) + { + GetTeam()->AddScore( score ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CBasePlayer::GetCommandContextCount( void ) const +{ + return m_CommandContext.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : CCommandContext +//----------------------------------------------------------------------------- +CCommandContext *CBasePlayer::GetCommandContext( int index ) +{ + if ( index < 0 || index >= m_CommandContext.Count() ) + return NULL; + + return &m_CommandContext[ index ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommandContext *CBasePlayer::AllocCommandContext( void ) +{ + int idx = m_CommandContext.AddToTail(); + return &m_CommandContext[ idx ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +//----------------------------------------------------------------------------- +void CBasePlayer::RemoveCommandContext( int index ) +{ + m_CommandContext.Remove( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::RemoveAllCommandContexts() +{ + m_CommandContext.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Determine how much time we will be running this frame +// Output : float +//----------------------------------------------------------------------------- +int CBasePlayer::DetermineSimulationTicks( void ) +{ + int command_context_count = GetCommandContextCount(); + + int context_number; + + int simulation_ticks = 0; + + // Determine how much time we will be running this frame and fixup player clock as needed + for ( context_number = 0; context_number < command_context_count; context_number++ ) + { + CCommandContext const *ctx = GetCommandContext( context_number ); + Assert( ctx ); + Assert( ctx->numcmds > 0 ); + Assert( ctx->dropped_packets >= 0 ); + + // Determine how long it will take to run those packets + simulation_ticks += ctx->numcmds + ctx->dropped_packets; + } + + return simulation_ticks; +} + +// 2 ticks ahead or behind current clock means we need to fix clock on client +#define TARGET_CLOCK_CORRECTION_TICKS (TIME_TO_TICKS(0.06f)) + + +extern ConVar skip; + +//----------------------------------------------------------------------------- +// Purpose: Based upon amount of time in simulation time, adjust m_nTickBase so that +// we just end at the end of the current frame (so the player is basically on clock +// with the server) +// Input : simulation_ticks - +//----------------------------------------------------------------------------- +void CBasePlayer::AdjustPlayerTimeBase( int simulation_ticks ) +{ + Assert( simulation_ticks >= 0 ); + if ( simulation_ticks < 0 ) + return; + + // Start in the past so that we get to the sv.time that we'll hit at the end of the + // frame, just as we process the final command + + if ( gpGlobals->maxClients == 1 ) + { + // set TickBase so that player simulation tick matches gpGlobals->tickcount after + // all commands have been executed + m_nTickBase = gpGlobals->tickcount - simulation_ticks + 1; + } + else // multiplayer + { + // set the target tick 2 ticks ahead in the future. this way the client can + // alternate around this targettick without getting smaller than gpGlobals->tickcount + // after running the commands simulation time should never be smaller than the + // current gpGlobals->tickcount, otherwise the simulation time drops out of the + // clientside view interpolation buffer. + + int end_of_frame_ticks = gpGlobals->tickcount + TARGET_CLOCK_CORRECTION_TICKS; + + int estimated_end_tick = m_nTickBase + simulation_ticks; + + // If client gets ahead of this, we'll need to correct + int too_fast_limit = end_of_frame_ticks + TARGET_CLOCK_CORRECTION_TICKS; + // If client falls behind this, we'll also need to correct + int too_slow_limit = end_of_frame_ticks - TARGET_CLOCK_CORRECTION_TICKS; + + // See if we are too fast + if ( estimated_end_tick > too_fast_limit ) + { + // DevMsg( "client too fast by %i ticks\n", estimated_end_tick - end_of_frame_ticks ); + m_nTickBase = end_of_frame_ticks - simulation_ticks + 1; + } + // Or to slow + else if ( estimated_end_tick < too_slow_limit ) + { + // DevMsg( "client too slow by %i ticks\n", end_of_frame_ticks - estimated_end_tick ); + m_nTickBase = end_of_frame_ticks - simulation_ticks + 1; + } + } +} + +void CBasePlayer::RunNullCommand( void ) +{ + CUserCmd cmd; // NULL command + + // Store off the globals.. they're gonna get whacked + float flOldFrametime = gpGlobals->frametime; + float flOldCurtime = gpGlobals->curtime; + + pl.fixangle = FIXANGLE_NONE; + cmd.viewangles = EyeAngles(); + + float flTimeBase = gpGlobals->curtime; + SetTimeBase( flTimeBase ); + + MoveHelperServer()->SetHost( this ); + PlayerRunCommand( &cmd, MoveHelperServer() ); + + // save off the last good usercmd + SetLastUserCommand( cmd ); + + // Restore the globals.. + gpGlobals->frametime = flOldFrametime; + gpGlobals->curtime = flOldCurtime; + + MoveHelperServer()->SetHost( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Note, don't chain to BaseClass::PhysicsSimulate +//----------------------------------------------------------------------------- +void CBasePlayer::PhysicsSimulate( void ) +{ + VPROF_BUDGET( "CBasePlayer::PhysicsSimulate", VPROF_BUDGETGROUP_PLAYER ); + // If we've got a moveparent, we must simulate that first. + CBaseEntity *pMoveParent = GetMoveParent(); + if (pMoveParent) + { + pMoveParent->PhysicsSimulate(); + } + + // Make sure not to simulate this guy twice per frame + if (m_nSimulationTick == gpGlobals->tickcount ) + { + return; + } + + m_nSimulationTick = gpGlobals->tickcount; + + // See how much time has queued up for running + int simulation_ticks = DetermineSimulationTicks(); + + // If some time will elapse, make sure our clock (m_nTickBase) starts at the correct time + if ( simulation_ticks > 0 ) + { + AdjustPlayerTimeBase( simulation_ticks ); + } + + if ( IsHLTV() ) + { + // just run a single, empty command to makke sure + // all preThink/Postthink functions are called as usual + Assert ( GetCommandContextCount() == 0 ); + RunNullCommand(); + RemoveAllCommandContexts(); + return; + } + + // Store off true server timestamps + float savetime = gpGlobals->curtime; + float saveframetime = gpGlobals->frametime; + + int command_context_count = GetCommandContextCount(); + for ( int context_number = 0; context_number < command_context_count; context_number++ ) + { + // Get oldest ( newer are added to tail ) + CCommandContext *ctx = GetCommandContext( context_number ); + Assert( ctx ); + + int i; + int numbackup = ctx->totalcmds - ctx->numcmds; + + // If the server is paused, zero out motion,buttons,view changes + if ( ctx->paused ) + { + bool clear_angles = true; + + // If no clipping and cheats enabled and noclipduring game enabled, then leave + // forwardmove and angles stuff in usercmd + if ( GetMoveType() == MOVETYPE_NOCLIP && + sv_cheats->GetBool() && + sv_noclipduringpause.GetBool() ) + { + clear_angles = false; + } + + for ( i = 0; i < ctx->numcmds; i++ ) + { + ctx->cmds[ i ].buttons = 0; + if ( clear_angles ) + { + ctx->cmds[ i ].forwardmove = 0; + ctx->cmds[ i ].sidemove = 0; + ctx->cmds[ i ].upmove = 0; + VectorCopy ( pl.v_angle, ctx->cmds[ i ].viewangles ); + } + } + + ctx->dropped_packets = 0; + } + + MoveHelperServer()->SetHost( this ); + + // Suppress predicted events, etc. + if ( IsPredictingWeapons() ) + { + IPredictionSystem::SuppressHostEvents( this ); + } + + // If we haven't dropped too many packets, then run some commands + if ( ctx->dropped_packets < 24 ) + { + int droppedcmds = ctx->dropped_packets; + + if ( droppedcmds > numbackup ) + { + // Msg( "lost %i cmds\n", droppedcmds ); + } + + // run the last known cmd for each dropped cmd we don't have a backup for + while ( droppedcmds > numbackup ) + { + m_LastCmd.tick_count++; + + if ( ShouldRunCommandsInContext( ctx ) ) + { + PlayerRunCommand( &m_LastCmd, MoveHelperServer() ); + } + droppedcmds--; + } + + // Now run the "history" commands if we still have dropped packets + while ( droppedcmds > 0 ) + { + int cmdnum = ctx->numcmds + droppedcmds - 1; + if ( ShouldRunCommandsInContext( ctx ) ) + { + PlayerRunCommand( &ctx->cmds[cmdnum], MoveHelperServer() ); + } + droppedcmds--; + } + } + + // Now run any new command(s). Go backward because the most recent command is at index 0. + for ( i = ctx->numcmds - 1; i >= 0; i-- ) + { + if ( ShouldRunCommandsInContext( ctx ) ) + { + PlayerRunCommand( &ctx->cmds[ i ], MoveHelperServer() ); + } + } + + // Save off the last good command in case we drop > numbackup packets and need to rerun them + // we'll use this to "guess" at what was in the missing packets + m_LastCmd = ctx->cmds[ CMD_MOSTRECENT ]; + + // Update our vphysics object. + if ( m_pPhysicsController ) + { + VPROF( "CBasePlayer::PhysicsSimulate-UpdateVPhysicsPosition" ); + // If simulating at 2 * TICK_INTERVAL, add an extra TICK_INTERVAL to position arrival computation + int additionalTick = CBaseEntity::IsSimulatingOnAlternateTicks() ? 1 : 0; + + float flSecondsToArrival = ( ctx->numcmds + ctx->dropped_packets + additionalTick ) * TICK_INTERVAL; + UpdateVPhysicsPosition( m_vNewVPhysicsPosition, m_vNewVPhysicsVelocity, flSecondsToArrival ); + } + + // Always reset after running commands + IPredictionSystem::SuppressHostEvents( NULL ); + + MoveHelperServer()->SetHost( NULL ); + } + + // Clear all contexts + RemoveAllCommandContexts(); + + // Restore the true server clock + // FIXME: Should this occur after simulation of children so + // that they are in the timespace of the player? + gpGlobals->curtime = savetime; + gpGlobals->frametime = saveframetime; +} + +unsigned int CBasePlayer::PhysicsSolidMaskForEntity() const +{ + return MASK_PLAYERSOLID; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *buf - +// totalcmds - +// dropped_packets - +// ignore - +// paused - +// Output : float -- Time in seconds of last movement command +//----------------------------------------------------------------------------- +void CBasePlayer::ProcessUsercmds( CUserCmd *cmds, int numcmds, int totalcmds, + int dropped_packets, bool paused ) +{ + CCommandContext *ctx = AllocCommandContext(); + Assert( ctx ); + + int i; + for ( i = totalcmds - 1; i >= 0; i-- ) + { + ctx->cmds[ i ] = cmds[ i ]; + } + ctx->numcmds = numcmds; + ctx->totalcmds = totalcmds, + ctx->dropped_packets = dropped_packets; + ctx->paused = paused; + + // Set global pause state for this player + m_bGamePaused = paused; + + if ( paused ) + { + m_nSimulationTick = -1; + // Just run the commands right away if paused + PhysicsSimulate(); + } +} + +// Duck debouncing code to stop menu changes from disallowing crouch/uncrouch +ConVar xc_crouch_debounce( "xc_crouch_debounce", "0", FCVAR_NONE ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *ucmd - +// *moveHelper - +//----------------------------------------------------------------------------- +void CBasePlayer::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) +{ + m_touchedPhysObject = false; + + if ( pl.fixangle == FIXANGLE_NONE) + { + VectorCopy ( ucmd->viewangles, pl.v_angle ); + } + + // Handle FL_FROZEN. + // Prevent player moving for some seconds after New Game, so that they pick up everything + if( GetFlags() & FL_FROZEN || + (developer.GetInt() == 0 && gpGlobals->eLoadType == MapLoad_NewGame && gpGlobals->curtime < 3.0 ) ) + { + ucmd->forwardmove = 0; + ucmd->sidemove = 0; + ucmd->upmove = 0; + ucmd->buttons = 0; + ucmd->impulse = 0; + VectorCopy ( pl.v_angle, ucmd->viewangles ); + } +#ifdef _XBOX + else + { + // Force a duck if we're toggled + if ( GetToggledDuckState() ) + { + // If this is set, we've altered our menu options and need to debounce the duck + if ( xc_crouch_debounce.GetBool() ) + { + ToggleDuck(); + + // Mark it as handled + xc_crouch_debounce.SetValue( 0 ); + } + else + { + ucmd->buttons |= IN_DUCK; + } + } + } +#endif // _XBOX + + PlayerMove()->RunCommand(this, ucmd, moveHelper); +} + + +void CBasePlayer::HandleFuncTrain(void) +{ + if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) + AddFlag( FL_ONTRAIN ); + else + RemoveFlag( FL_ONTRAIN ); + + // Train speed control + if (( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) == 0) + { + if (m_iTrain & TRAIN_ACTIVE) + { + m_iTrain = TRAIN_NEW; // turn off train + } + return; + } + + CBaseEntity *pTrain = GetGroundEntity(); + float vel; + + if ( pTrain ) + { + if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) + pTrain = NULL; + } + + if ( !pTrain ) + { + if ( GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE ) + { + m_iTrain = TRAIN_ACTIVE | TRAIN_NEW; + + if ( m_nButtons & IN_FORWARD ) + { + m_iTrain |= TRAIN_FAST; + } + else if ( m_nButtons & IN_BACK ) + { + m_iTrain |= TRAIN_BACK; + } + else + { + m_iTrain |= TRAIN_NEUTRAL; + } + return; + } + else + { + trace_t trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38), + MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace ); + + if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt ) + pTrain = trainTrace.m_pEnt; + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(this) ) + { + m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + } + else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + SetAbsVelocity( vec3_origin ); + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(static_cast(pTrain->m_flSpeed), static_cast(((CFuncTrackTrain*)pTrain)->GetMaxSpeed())); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } +} + + +void CBasePlayer::PreThink(void) +{ + if ( g_fGameOver || m_iPlayerLocked ) + return; // intermission or finale + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + // checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + CheckSuitUpdate(); + + if ( GetObserverMode() > OBS_MODE_FIXED ) + { + CheckObserverSettings(); // do this each frame + } + + if (m_lifeState >= LIFE_DYING) + return; + + HandleFuncTrain(); + + if (m_nButtons & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + + // If trying to duck, already ducked, or in the process of ducking + if ((m_nButtons & IN_DUCK) || (GetFlags() & FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + Duck(); + + // + // If we're not on the ground, we're falling. Update our falling velocity. + // + if ( !( GetFlags() & FL_ONGROUND ) ) + { + m_Local.m_flFallVelocity = -GetAbsVelocity().z; + } + +#ifndef _XBOX + CNavArea *area = TheNavMesh->GetNavArea( GetAbsOrigin() ); + if (area && area != m_lastNavArea) + { + // player entered a new nav area + if (m_lastNavArea) + { + m_lastNavArea->DecrementPlayerCount( GetTeamNumber() ); + } + + area->IncrementPlayerCount( GetTeamNumber() ); + + m_lastNavArea = area; + if ( area->GetPlace() != UNDEFINED_PLACE ) + { + const char *placeName = TheNavMesh->PlaceToName( area->GetPlace() ); + if ( placeName && *placeName ) + { + Q_strncpy( m_szLastPlaceName.GetForModify(), placeName, MAX_PLACE_NAME_LENGTH ); + } + } + + // generate event + //KeyValues *event = new KeyValues( "player_entered_area" ); + //event->SetInt( "userid", GetUserID() ); + //event->SetInt( "areaid", area->GetID() ); + //gameeventmanager->FireEvent( event ); + } +#endif + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? +} + + +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + + 2) A new hit inflicting tbd restarts the tbd counter - each NPC has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + byte bDuration = 0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(static_cast(gpGlobals->curtime - m_tbdPrev) < 2.0)) + return; + + m_tbdPrev = gpGlobals->curtime; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// OnTakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; +// case itbd_Poison: +// OnTakeDamage( CTakeDamageInfo( this, this, POISON_DAMAGE, DMG_GENERIC ) ); +// bDuration = POISON_DURATION; +// break; + case itbd_Radiation: +// OnTakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + + case itbd_PoisonRecover: + { + // NOTE: this hack is actually used to RESTORE health + // after the player has been poisoned. + if (m_nPoisonDmg > m_nPoisonRestored) + { + int nDif = min(m_nPoisonDmg - m_nPoisonRestored, 10); + TakeHealth(nDif, DMG_GENERIC); + m_nPoisonRestored += nDif; + } + bDuration = 9; // get up to 10*10 = 100 points back + break; + } + + case itbd_Acid: +// OnTakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// OnTakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// OnTakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter + +#define GEIGERDELAY 0.25 + +void CBasePlayer::UpdateGeigerCounter( void ) +{ + byte range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->curtime < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->curtime + GEIGERDELAY; + + // send range to radition source to client + range = (byte) clamp(m_flgeigerRange / 4, 0, 255); + + // This is to make sure you aren't driven crazy by geiger while in the airboat + if ( IsInAVehicle() ) + { + range = clamp( (int)range * 4, 0, 255 ); + } + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + UserMessageBegin( user, "Geiger" ); + WRITE_BYTE( range ); + MessageEnd(); + } + + // reset counter and semaphore + if (!random->RandomInt(0,3)) + { + m_flgeigerRange = 1000; + } +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer::CheckSuitUpdate() +{ + int i; + int isentence = 0; + int isearch = m_iSuitPlayNext; + + // Ignore suit updates if no suit + if ( !IsSuitEquipped() ) + return; + + // if in range of radiation source, ping geiger counter + UpdateGeigerCounter(); + + if ( g_pGameRules->IsMultiplayer() ) + { + // don't bother updating HEV voice in multiplayer. + return; + } + + if ( gpGlobals->curtime >= m_flSuitUpdate && m_flSuitUpdate > 0) + { + // play a sentence off of the end of the queue + for (i = 0; i < CSUITPLAYLIST; i++) + { + if ((isentence = m_rgSuitPlayList[isearch]) != 0) + break; + + if (++isearch == CSUITPLAYLIST) + isearch = 0; + } + + if (isentence) + { + m_rgSuitPlayList[isearch] = 0; + if (isentence > 0) + { + // play sentence number + + char sentence[512]; + Q_snprintf( sentence, sizeof( sentence ), "!%s", engine->SentenceNameFromIndex( isentence ) ); + UTIL_EmitSoundSuit( edict(), sentence ); + } + else + { + // play sentence group + UTIL_EmitGroupIDSuit(edict(), -isentence); + } + m_flSuitUpdate = gpGlobals->curtime + SUITUPDATETIME; + } + else + // queue is empty, don't check + m_flSuitUpdate = 0; + } +} + +// add sentence to suit playlist queue. if fgroup is true, then +// name is a sentence group (HEV_AA), otherwise name is a specific +// sentence name ie: !HEV_AA0. If iNoRepeat is specified in +// seconds, then we won't repeat playback of this word or sentence +// for at least that number of seconds. + +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) +{ + int i; + int isentence; + int iempty = -1; + + + // Ignore suit updates if no suit + if ( !IsSuitEquipped() ) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + // due to static channel design, etc. We don't play HEV sounds in multiplayer right now. + return; + } + + // if name == NULL, then clear out the queue + + if (!name) + { + for (i = 0; i < CSUITPLAYLIST; i++) + m_rgSuitPlayList[i] = 0; + return; + } + // get sentence or group number + if (!fgroup) + { + isentence = SENTENCEG_Lookup(name); // Lookup sentence index (not group) by name + if (isentence < 0) + return; + } + else + // mark group number as negative + isentence = -SENTENCEG_GetIndex(name); // Lookup group index by name + + // check norepeat list - this list lets us cancel + // the playback of words or sentences that have already + // been played within a certain time. + + for (i = 0; i < CSUITNOREPEAT; i++) + { + if (isentence == m_rgiSuitNoRepeat[i]) + { + // this sentence or group is already in + // the norepeat list + + if (m_rgflSuitNoRepeatTime[i] < gpGlobals->curtime) + { + // norepeat time has expired, clear it out + m_rgiSuitNoRepeat[i] = 0; + m_rgflSuitNoRepeatTime[i] = 0.0; + iempty = i; + break; + } + else + { + // don't play, still marked as norepeat + return; + } + } + // keep track of empty slot + if (!m_rgiSuitNoRepeat[i]) + iempty = i; + } + + // sentence is not in norepeat list, save if norepeat time was given + + if (iNoRepeatTime) + { + if (iempty < 0) + iempty = random->RandomInt(0, CSUITNOREPEAT-1); // pick random slot to take over + m_rgiSuitNoRepeat[iempty] = isentence; + m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->curtime; + } + + // find empty spot in queue, or overwrite last spot + + m_rgSuitPlayList[m_iSuitPlayNext++] = isentence; + if (m_iSuitPlayNext == CSUITPLAYLIST) + m_iSuitPlayNext = 0; + + if (m_flSuitUpdate <= gpGlobals->curtime) + { + if (m_flSuitUpdate == 0) + // play queue is empty, don't delay too long before playback + m_flSuitUpdate = gpGlobals->curtime + SUITFIRSTUPDATETIME; + else + m_flSuitUpdate = gpGlobals->curtime + SUITUPDATETIME; + } + +} + +//========================================================= +// UpdatePlayerSound - updates the position of the player's +// reserved sound slot in the sound list. +//========================================================= +void CBasePlayer::UpdatePlayerSound ( void ) +{ + int iBodyVolume; + int iVolume; + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + + if ( !pSound ) + { + Msg( "Client lost reserved sound!\n" ); + return; + } + + if (GetFlags() & FL_NOTARGET) + { + pSound->m_iVolume = 0; + return; + } + + // now figure out how loud the player's movement is. + if ( GetFlags() & FL_ONGROUND ) + { + iBodyVolume = static_cast(GetAbsVelocity().Length()); + + // clamp the noise that can be made by the body, in case a push trigger, + // weapon recoil, or anything shoves the player abnormally fast. + // NOTE: 512 units is a pretty large radius for a sound made by the player's body. + // then again, I think some materials are pretty loud. + if ( iBodyVolume > 512 ) + { + iBodyVolume = 512; + } + } + else + { + iBodyVolume = 0; + } + + if ( m_nButtons & IN_JUMP ) + { + // Jumping is a little louder. + iBodyVolume += 100; + } + + m_iTargetVolume = iBodyVolume; + + // if target volume is greater than the player sound's current volume, we paste the new volume in + // immediately. If target is less than the current volume, current volume is not set immediately to the + // lower volume, rather works itself towards target volume over time. This gives NPCs a much better chance + // to hear a sound, especially if they don't listen every frame. + iVolume = pSound->Volume(); + + if ( m_iTargetVolume > iVolume ) + { + iVolume = m_iTargetVolume; + } + else if ( iVolume > m_iTargetVolume ) + { + iVolume -= static_cast(250 * gpGlobals->frametime); + + if ( iVolume < m_iTargetVolume ) + { + iVolume = 0; + } + } + + if ( pSound ) + { + pSound->SetSoundOrigin( GetAbsOrigin() ); + pSound->m_iType = SOUND_PLAYER; + pSound->m_iVolume = iVolume; + } + + // Below are a couple of useful little bits that make it easier to visualize just how much noise the + // player is making. + //Vector forward = UTIL_YawToVector( pl.v_angle.y ); + //UTIL_Sparks( GetAbsOrigin() + forward * iVolume ); + //Msg( "%d/%d\n", iVolume, m_iTargetVolume ); +} + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( CBasePlayer *pPlayer ) +{ + trace_t trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->GetAbsOrigin(), pPlayer->GetAbsOrigin(), + VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); + if ( trace.startsolid ) + { + Vector origin = pPlayer->GetAbsOrigin(); + origin.z += 1.0f; + pPlayer->SetLocalOrigin( origin ); + } + else + break; + } +} + +#define SMOOTHING_FACTOR 0.9 +extern CMoveData *g_pMoveData; + +// UNDONE: Look and see if the ground entity is in hierarchy with a MOVETYPE_VPHYSICS? +// Behavior in that case is not as good currently when the parent is rideable +bool CBasePlayer::IsRideablePhysics( IPhysicsObject *pPhysics ) +{ + if ( pPhysics ) + { + if ( pPhysics->GetMass() > (VPhysicsGetObject()->GetMass()*2) ) + return true; + } + + return false; +} + +IPhysicsObject *CBasePlayer::GetGroundVPhysics() +{ + CBaseEntity *pGroundEntity = GetGroundEntity(); + if ( pGroundEntity && pGroundEntity->GetMoveType() == MOVETYPE_VPHYSICS ) + { + IPhysicsObject *pPhysGround = pGroundEntity->VPhysicsGetObject(); + if ( pPhysGround && pPhysGround->IsMoveable() ) + return pPhysGround; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// For debugging... +//----------------------------------------------------------------------------- +void CBasePlayer::ForceOrigin( const Vector &vecOrigin ) +{ + m_bForceOrigin = true; + m_vForcedOrigin = vecOrigin; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::PostThink() +{ + m_vecSmoothedVelocity = m_vecSmoothedVelocity * SMOOTHING_FACTOR + GetAbsVelocity() * ( 1 - SMOOTHING_FACTOR ); + + if ( !g_fGameOver && !m_iPlayerLocked && IsAlive() ) + { + // set correct collision bounds (may have changed in player movement code) + VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-Bounds" ); + if ( GetFlags() & FL_DUCKING ) + { + SetCollisionBounds( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); + } + else + { + SetCollisionBounds( VEC_HULL_MIN, VEC_HULL_MAX ); + } + VPROF_SCOPE_END(); + + VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-Use" ); + // Handle controlling an entity + if ( m_hUseEntity != NULL ) + { + // if they've moved too far from the gun, or deployed another weapon, unuse the gun + if ( m_hUseEntity->OnControls( this ) && + ( !GetActiveWeapon() || GetActiveWeapon()->IsEffectActive( EF_NODRAW ) || + ( GetActiveWeapon()->GetActivity() == ACT_VM_HOLSTER ) + ) ) + { + m_hUseEntity->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { + // they've moved off the controls + ClearUseEntity(); + } + } + VPROF_SCOPE_END(); + + // do weapon stuff + VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-ItemPostFrame" ); + ItemPostFrame(); + VPROF_SCOPE_END(); + + if ( GetFlags() & FL_ONGROUND ) + { + if (m_Local.m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + { + CSoundEnt::InsertSound ( SOUND_PLAYER, GetAbsOrigin(), m_Local.m_flFallVelocity, 0.2, this ); + // Msg( "fall %f\n", m_Local.m_flFallVelocity ); + } + m_Local.m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() ) + { + VPROF( "CBasePlayer::PostThink-Animation" ); + // If he's in a vehicle, sit down + if ( IsInAVehicle() ) + SetAnimation( PLAYER_IN_VEHICLE ); + else if (!GetAbsVelocity().x && !GetAbsVelocity().y) + SetAnimation( PLAYER_IDLE ); + else if ((GetAbsVelocity().x || GetAbsVelocity().y) && ( GetFlags() & FL_ONGROUND )) + SetAnimation( PLAYER_WALK ); + else if (GetWaterLevel() > 1) + SetAnimation( PLAYER_WALK ); + } + + // Don't allow bogus sequence on player + if ( GetSequence() == -1 ) + { + SetSequence( 0 ); + } + + VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-StudioFrameAdvance" ); + StudioFrameAdvance(); + VPROF_SCOPE_END(); + + VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-DispatchAnimEvents" ); + DispatchAnimEvents( this ); + VPROF_SCOPE_END(); + + SetSimulationTime( gpGlobals->curtime ); + + //Let the weapon update as well + VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-Weapon_FrameUpdate" ); + Weapon_FrameUpdate(); + VPROF_SCOPE_END(); + + VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-UpdatePlayerSound" ); + UpdatePlayerSound(); + VPROF_SCOPE_END(); + + if ( m_bForceOrigin ) + { + SetLocalOrigin( m_vForcedOrigin ); + SetLocalAngles( m_Local.m_vecPunchAngle ); + m_Local.m_vecPunchAngle = RandomAngle( -25, 25 ); + m_Local.m_vecPunchAngleVel.Init(); + } + + VPROF_SCOPE_BEGIN( "CBasePlayer::PostThink-PostThinkVPhysics" ); + PostThinkVPhysics(); + VPROF_SCOPE_END(); + } + +#if !defined( NO_ENTITY_PREDICTION ) + // Even if dead simulate entities + SimulatePlayerSimulatedEntities(); +#endif + +} + +// handles touching physics objects +void CBasePlayer::Touch( CBaseEntity *pOther ) +{ + if ( pOther == GetGroundEntity() ) + return; + + if ( pOther->GetMoveType() != MOVETYPE_VPHYSICS || pOther->GetSolid() != SOLID_VPHYSICS || (pOther->GetSolidFlags() & FSOLID_TRIGGER) ) + return; + + IPhysicsObject *pPhys = pOther->VPhysicsGetObject(); + if ( !pPhys || !pPhys->IsMoveable() ) + return; + + SetTouchedPhysics( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::PostThinkVPhysics( void ) +{ + // Check to see if things are initialized! + if ( !m_pPhysicsController ) + return; + + Vector newPosition = GetAbsOrigin(); + float frametime = gpGlobals->frametime; + if ( frametime <= 0 || frametime > 0.1f ) + frametime = 0.1f; + + IPhysicsObject *pPhysGround = GetGroundVPhysics(); + + if ( !pPhysGround && m_touchedPhysObject && g_pMoveData->m_outStepHeight <= 0.f && (GetFlags() & FL_ONGROUND) ) + { + newPosition = m_oldOrigin + frametime * g_pMoveData->m_outWishVel; + newPosition = (GetAbsOrigin() * 0.5f) + (newPosition * 0.5f); + } + + int collisionState = VPHYS_WALK; + if ( GetMoveType() == MOVETYPE_NOCLIP || GetMoveType() == MOVETYPE_OBSERVER ) + { + collisionState = VPHYS_NOCLIP; + } + else if ( GetFlags() & FL_DUCKING ) + { + collisionState = VPHYS_CROUCH; + } + + if ( collisionState != m_vphysicsCollisionState ) + { + SetVCollisionState( collisionState ); + } + + if ( !(TouchedPhysics() || pPhysGround) ) + { + float maxSpeed = m_flMaxspeed > 0.0f ? m_flMaxspeed : sv_maxspeed.GetFloat(); + g_pMoveData->m_outWishVel.Init( maxSpeed, maxSpeed, maxSpeed ); + } + + // teleport the physics object up by stepheight (game code does this - reflect in the physics) + if ( g_pMoveData->m_outStepHeight > 0.1f ) + { + if ( g_pMoveData->m_outStepHeight > 4.0f ) + { + VPhysicsGetObject()->SetPosition( GetAbsOrigin(), vec3_angle, true ); + } + else + { + // don't ever teleport into solid + Vector position, end; + VPhysicsGetObject()->GetPosition( &position, NULL ); + end = position; + end.z += g_pMoveData->m_outStepHeight; + trace_t trace; + UTIL_TraceEntity( this, position, end, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + if ( trace.DidHit() ) + { + g_pMoveData->m_outStepHeight = trace.endpos.z - position.z; + } + m_pPhysicsController->StepUp( g_pMoveData->m_outStepHeight ); + } + m_pPhysicsController->Jump(); + } + g_pMoveData->m_outStepHeight = 0.0f; + + // Store these off because after running the usercmds, it'll pass them + // to UpdateVPhysicsPosition. + m_vNewVPhysicsPosition = newPosition; + m_vNewVPhysicsVelocity = g_pMoveData->m_outWishVel; + + m_oldOrigin = GetAbsOrigin(); +} + +void CBasePlayer::UpdateVPhysicsPosition( const Vector &position, const Vector &velocity, float secondsToArrival ) +{ + bool onground = (GetFlags() & FL_ONGROUND) ? true : false; + IPhysicsObject *pPhysGround = GetGroundVPhysics(); + + // if the object is much heavier than the player, treat it as a local coordinate system + // the player controller will solve movement differently in this case. + if ( !IsRideablePhysics(pPhysGround) ) + { + pPhysGround = NULL; + } + + m_pPhysicsController->Update( position, velocity, secondsToArrival, onground, pPhysGround ); +} + +void CBasePlayer::UpdatePhysicsShadowToCurrentPosition() +{ + UpdateVPhysicsPosition( GetAbsOrigin(), vec3_origin, gpGlobals->frametime ); +} + +Vector CBasePlayer::GetSmoothedVelocity( void ) +{ + if ( IsInAVehicle() ) + { + return GetVehicle()->GetVehicleEnt()->GetSmoothedVelocity(); + } + return m_vecSmoothedVelocity; +} + + +CBaseEntity *g_pLastSpawn = NULL; + + +//----------------------------------------------------------------------------- +// Purpose: Finds a player start entity of the given classname. If any entity of +// of the given classname has the SF_PLAYER_START_MASTER flag set, that +// is the entity that will be returned. Otherwise, the first entity of +// the given classname is returned. +// Input : pszClassName - should be "info_player_start", "info_player_coop", or +// "info_player_deathmatch" +//----------------------------------------------------------------------------- +CBaseEntity *FindPlayerStart(const char *pszClassName) +{ + #define SF_PLAYER_START_MASTER 1 + + CBaseEntity *pStart = gEntList.FindEntityByClassname(NULL, pszClassName); + CBaseEntity *pStartFirst = pStart; + while (pStart != NULL) + { + if (pStart->HasSpawnFlags(SF_PLAYER_START_MASTER)) + { + return pStart; + } + + pStart = gEntList.FindEntityByClassname(pStart, pszClassName); + } + + return pStartFirst; +} + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +CBaseEntity *CBasePlayer::EntSelectSpawnPoint() +{ + CBaseEntity *pSpot; + edict_t *player; + + player = edict(); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = gEntList.FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( pSpot ) + goto ReturnSpot; + pSpot = gEntList.FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( pSpot ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = random->RandomInt(1,5); i > 0; i-- ) + pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( !pSpot ) // skip over the null point + pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) ) + { + if ( pSpot->GetLocalOrigin() == vec3_origin ) + { + pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( pSpot ) + { + CBaseEntity *ent = NULL; + for ( CEntitySphereQuery sphere( pSpot->GetAbsOrigin(), 128 ); (ent = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) ) + ent->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 300, DMG_GENERIC ) ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( !gpGlobals->startspot || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = FindPlayerStart( "info_player_start" ); + if ( pSpot ) + goto ReturnSpot; + } + else + { + pSpot = gEntList.FindEntityByName( NULL, gpGlobals->startspot ); + if ( pSpot ) + goto ReturnSpot; + } + +ReturnSpot: + if ( !pSpot ) + { + Warning( "PutClientInServer: no info_player_start on level\n"); + return CBaseEntity::Instance( INDEXENT( 0 ) ); + } + + g_pLastSpawn = pSpot; + return pSpot; +} + +//----------------------------------------------------------------------------- +// Purpose: Called the first time the player's created +//----------------------------------------------------------------------------- +void CBasePlayer::InitialSpawn( void ) +{ + m_iConnected = PlayerConnected; +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime the player respawns +//----------------------------------------------------------------------------- +void CBasePlayer::Spawn( void ) +{ + SetClassname( "player" ); + + // Shared spawning code.. + SharedSpawn(); + + SetSimulatedEveryTick( true ); + SetAnimatedEveryTick( true ); + + m_ArmorValue = SpawnArmorValue(); + SetBlocksLOS( false ); + m_iMaxHealth = m_iHealth; + + // Clear all flags except for FL_FULLEDICT + if ( GetFlags() & FL_FAKECLIENT ) + { + ClearFlags(); + AddFlag( FL_CLIENT | FL_FAKECLIENT ); + } + else + { + ClearFlags(); + AddFlag( FL_CLIENT ); + } + + AddFlag( FL_AIMTARGET ); + + m_AirFinished = gpGlobals->curtime + AIRTIME; + m_nDrownDmgRate = DROWNING_DAMAGE_INITIAL; + + // only preserve the shadow flag + int effects = GetEffects() & EF_NOSHADOW; + SetEffects( effects ); + + m_DmgTake = 0; + m_DmgSave = 0; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + + SetFOV( this, 0 ); + + m_flNextDecalTime = 0;// let this player decal as soon as he spawns. + + m_flgeigerDelay = gpGlobals->curtime + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_flFieldOfView = 0.766;// some NPCs use this to determine whether or not the player is looking at them. + + m_vecAdditionalPVSOrigin = vec3_origin; + m_vecCameraPVSOrigin = vec3_origin; + + if ( !m_fGameHUDInitialized ) + g_pGameRules->SetDefaultPlayerTeam( this ); + + g_pGameRules->GetPlayerSpawnSpot( this ); + + m_Local.m_bDucked = false;// This will persist over round restart if you hold duck otherwise. + m_Local.m_bDucking = false; + SetViewOffset( VEC_VIEW ); + Precache(); + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + SetPlayerUnderwater( false ); + + m_iTrain = TRAIN_NEW; + + m_HackedGunPos = Vector( 0, 32, 0 ); + + if ( m_iPlayerSound == SOUNDLIST_EMPTY ) + { + Msg( "Couldn't alloc player sound slot!\n" ); + } + + SetThink(NULL); + m_fInitHUD = true; + m_fWeapon = false; + m_iClientBattery = -1; + + m_lastx = m_lasty = 0; + + m_lastNavArea = NULL; + +#ifndef _XBOX + /// @todo Do this once per round instead of once per player + if (TheNavMesh) + { + TheNavMesh->ClearPlayerCounts(); + } +#endif + + Q_strncpy( m_szLastPlaceName.GetForModify(), "", MAX_PLACE_NAME_LENGTH ); + + CSingleUserRecipientFilter user( this ); + enginesound->SetPlayerDSP( user, 0, false ); + + CreateViewModel(); + + SetCollisionGroup( COLLISION_GROUP_PLAYER ); + + // if the player is locked, make sure he stays locked + if ( m_iPlayerLocked ) + { + m_iPlayerLocked = false; + LockPlayerInPlace(); + } + + if ( GetTeamNumber() != TEAM_SPECTATOR ) + { + StopObserverMode(); + } + else + { + StartObserverMode( m_iObserverLastMode ); + } + + StopReplayMode(); + + // Clear any screenfade + color32 nothing = {0,0,0,255}; + UTIL_ScreenFade( this, nothing, 0, 0, FFADE_IN | FFADE_PURGE ); + + g_pGameRules->PlayerSpawn( this ); + + m_flLaggedMovementValue = 1.0f; + m_vecSmoothedVelocity = vec3_origin; + InitVCollision(); + + IGameEvent *event = gameeventmanager->CreateEvent( "player_spawn" ); + + if ( event ) + { + event->SetInt("userid", GetUserID() ); + gameeventmanager->FireEvent( event ); + } + + RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE ); +} + +void CBasePlayer::Activate( void ) +{ + BaseClass::Activate(); + + AimTarget_ForceRepopulateList(); + + RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE ); + + // Reset the analog bias. If the player is in a vehicle when the game + // reloads, it will autosense and apply the correct bias. + m_iVehicleAnalogBias = VEHICLE_ANALOG_BIAS_NONE; +} + +void CBasePlayer::Precache( void ) +{ + BaseClass::Precache(); + + + PrecacheScriptSound( "Player.FallGib" ); + PrecacheScriptSound( "Player.Death" ); + PrecacheScriptSound( "Player.PlasmaDamage" ); + PrecacheScriptSound( "Player.SonicDamage" ); + PrecacheScriptSound( "Player.DrownStart" ); + PrecacheScriptSound( "Player.DrownContinue" ); + PrecacheScriptSound( "Player.Wade" ); + PrecacheScriptSound( "Player.AmbientUnderWater" ); + PrecacheScriptSound( "Player.Wade" ); + enginesound->PrecacheSentenceGroup( "HEV" ); + + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // !!!BUGBUG - now that we have multiplayer, this needs to be moved! + /* todo - put in better spot and use new ainetowrk stuff + if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) + { + if ( !WorldGraph.FSetGraphPointers() ) + { + Msg( "**Graph pointers were not set!\n"); + } + else + { + Msg( "**Graph Pointers Set!\n" ); + } + } + */ + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + + // init geiger counter vars during spawn and each time + // we cross a level transition + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + +#if 0 + // @Note (toml 04-19-04): These are saved, used to be slammed here + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + SetPlayerUnderwter( false ); + + m_iTrain = TRAIN_NEW; +#endif + + m_iClientBattery = -1; + + m_iUpdateTime = 5; // won't update for 1/2 a second + + if ( gInitHUD ) + m_fInitHUD = true; + +} + + + +int CBasePlayer::Save( ISave &save ) +{ + if ( !BaseClass::Save(save) ) + return 0; + + return 1; +} + + +int CBasePlayer::Restore( IRestore &restore ) +{ + int status = BaseClass::Restore(restore); + if ( !status ) + return 0; + + CSaveRestoreData *pSaveData = gpGlobals->pSaveData; + // landmark isn't present. + if ( !pSaveData->levelInfo.fUseLandmark ) + { + Msg( "No Landmark:%s\n", pSaveData->levelInfo.szLandmarkName ); + + // default to normal spawn + CBaseEntity *pSpawnSpot = EntSelectSpawnPoint(); + SetLocalOrigin( pSpawnSpot->GetLocalOrigin() + Vector(0,0,1) ); + SetLocalAngles( pSpawnSpot->GetLocalAngles() ); + } + + QAngle newViewAngles = pl.v_angle; + newViewAngles.z = 0; // Clear out roll + SetLocalAngles( newViewAngles ); + SnapEyeAngles( newViewAngles ); + + // Copied from spawn() for now + SetBloodColor( BLOOD_COLOR_RED ); + + // clear this - it will get reset by touching the trigger again + m_afPhysicsFlags &= ~PFLAG_VPHYSICS_MOTIONCONTROLLER; + + if ( GetFlags() & FL_DUCKING ) + { + // Use the crouch HACK + FixPlayerCrouchStuck( this ); + UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + m_Local.m_bDucked = true; + } + else + { + m_Local.m_bDucked = false; + UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); + } + + InitVCollision(); + + // success + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::OnRestore( void ) +{ + BaseClass::OnRestore(); + + SetViewEntity( m_hViewEntity ); +} + +/* void CBasePlayer::SetTeamName( const char *pTeamName ) +{ + Q_strncpy( m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); +} */ + +void CBasePlayer::SetArmorValue( int value ) +{ + m_ArmorValue = value; +} + +void CBasePlayer::IncrementArmorValue( int nCount, int nMaxValue ) +{ + m_ArmorValue += nCount; + if (nMaxValue > 0) + { + if (m_ArmorValue > nMaxValue) + m_ArmorValue = nMaxValue; + } +} + +// Only used by the physics gun... is there a better interface? +void CBasePlayer::SetPhysicsFlag( int nFlag, bool bSet ) +{ + if (bSet) + m_afPhysicsFlags |= nFlag; + else + m_afPhysicsFlags &= ~nFlag; +} + + +void CBasePlayer::NotifyNearbyRadiationSource( float flRange ) +{ + // if player's current geiger counter range is larger + // than range to this trigger hurt, reset player's + // geiger counter range + + if (m_flgeigerRange >= flRange) + m_flgeigerRange = flRange; +} + +void CBasePlayer::AllowImmediateDecalPainting() +{ + m_flNextDecalTime = gpGlobals->curtime; +} + +// Suicide... +void CBasePlayer::CommitSuicide() +{ + if( !IsAlive() ) + return; + + // prevent suiciding too often + if ( m_fNextSuicideTime > gpGlobals->curtime ) + return; + + // don't let them suicide for 5 seconds after suiciding + m_fNextSuicideTime = gpGlobals->curtime + 5; + + // have the player kill themself + m_iHealth = 0; + Event_Killed( CTakeDamageInfo( this, this, 0, DMG_NEVERGIB ) ); + Event_Dying(); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +bool CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < WeaponCount() ; i++ ) + { + if ( GetWeapon(i) ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecForce - +//----------------------------------------------------------------------------- +void CBasePlayer::VelocityPunch( const Vector &vecForce ) +{ + // Clear onground and add velocity. + SetGroundEntity( NULL ); + ApplyAbsVelocityImpulse(vecForce ); +} + + +//-------------------------------------------------------------------------------------------------------------- +// VEHICLES +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: Put this player in a vehicle +//----------------------------------------------------------------------------- +bool CBasePlayer::GetInVehicle( IServerVehicle *pVehicle, int nRole ) +{ + Assert( NULL == m_hVehicle.Get() ); + Assert( nRole >= 0 ); + + if ( pVehicle->GetPassenger( nRole ) ) + return false; + + CBaseEntity *pEnt = pVehicle->GetVehicleEnt(); + Assert( pEnt ); + + if (!pVehicle->IsPassengerUsingStandardWeapons( nRole )) + { + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + + //Must be able to stow our weapon + if ( ( pWeapon != NULL ) && ( pWeapon->Holster( NULL ) == false ) ) + return false; + +#ifndef HL2_DLL + m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; +#endif + m_Local.m_iHideHUD |= HIDEHUD_INVEHICLE; + } + + if ( !pVehicle->IsPassengerVisible( nRole ) ) + { + AddEffects( EF_NODRAW ); + } + + ViewPunchReset(); + + // Setting the velocity to 0 will cause the IDLE animation to play + SetAbsVelocity( vec3_origin ); + SetMoveType( MOVETYPE_NOCLIP ); + + // Choose the entry point of the vehicle, + // By default, just stay at the point you started at... + // NOTE: we have to set this first so that when the parent is set + // the local position just works + Vector vNewPos = GetAbsOrigin(); + QAngle qAngles = GetAbsAngles(); + pVehicle->GetPassengerStartPoint( nRole, &vNewPos, &qAngles ); + SetAbsOrigin( vNewPos ); + SetAbsAngles( qAngles ); + SetParent( pEnt ); + + SetCollisionGroup( COLLISION_GROUP_IN_VEHICLE ); + + // We cannot be ducking -- do all this before SetPassenger because it + // saves our view offset for restoration when we exit the vehicle. + RemoveFlag( FL_DUCKING ); + SetViewOffset( VEC_VIEW ); + m_Local.m_bDucked = false; + m_Local.m_bDucking = false; + m_Local.m_flDucktime = 0.0f; + m_Local.m_flDuckJumpTime = 0.0f; + m_Local.m_flJumpTime = 0.0f; + + // Turn our toggled duck off + if ( GetToggledDuckState() ) + { + ToggleDuck(); + } + + pVehicle->SetPassenger( nRole, this ); + + m_hVehicle = pEnt; + + // Throw an event indicating that the player entered the vehicle. + g_pNotify->ReportNamedEvent( this, "PlayerEnteredVehicle" ); + + m_iVehicleAnalogBias = VEHICLE_ANALOG_BIAS_NONE; + + OnVehicleStart(); + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove this player from a vehicle +//----------------------------------------------------------------------------- +void CBasePlayer::LeaveVehicle( const Vector &vecExitPoint, const QAngle &vecExitAngles ) +{ + if ( NULL == m_hVehicle.Get() ) + return; + + IServerVehicle *pVehicle = GetVehicle(); + Assert( pVehicle ); + + int nRole = pVehicle->GetPassengerRole( this ); + Assert( nRole >= 0 ); + + SetParent( NULL ); + + // Find the first non-blocked exit point: + Vector vNewPos = GetAbsOrigin(); + QAngle qAngles = GetAbsAngles(); + if ( vecExitPoint == vec3_origin ) + { + // FIXME: this might fail to find a safe exit point!! + pVehicle->GetPassengerExitPoint( nRole, &vNewPos, &qAngles ); + } + else + { + vNewPos = vecExitPoint; + qAngles = vecExitAngles; + } + OnVehicleEnd( vNewPos ); + SetAbsOrigin( vNewPos ); + SetAbsAngles( qAngles ); + // Clear out any leftover velocity + SetAbsVelocity( vec3_origin ); + + qAngles[ROLL] = 0; + SnapEyeAngles( qAngles ); + +#ifndef HL2_DLL + m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; +#endif + + m_Local.m_iHideHUD &= ~HIDEHUD_INVEHICLE; + + RemoveEffects( EF_NODRAW ); + + SetMoveType( MOVETYPE_WALK ); + SetCollisionGroup( COLLISION_GROUP_PLAYER ); + + if ( VPhysicsGetObject() ) + { + VPhysicsGetObject()->SetPosition( vNewPos, vec3_angle, true ); + } + + m_hVehicle = NULL; + pVehicle->SetPassenger(nRole, NULL); + + // Re-deploy our weapon + if ( IsAlive() ) + { + if ( GetActiveWeapon() && GetActiveWeapon()->IsWeaponVisible() == false ) + { + GetActiveWeapon()->Deploy(); + ShowCrosshair( true ); + } + } + +#ifdef _XBOX + // Just cut all of the rumble effects. + RumbleEffect( RUMBLE_STOP_ALL, 0, RUMBLE_FLAGS_NONE ); +#endif//_XBOX +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CPointEntity +{ +public: + DECLARE_CLASS( CSprayCan, CPointEntity ); + + void Spawn ( CBasePlayer *pOwner ); + void Think( void ); + + virtual void Precache(); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( spraycan, CSprayCan ); +PRECACHE_REGISTER( spraycan ); + +void CSprayCan::Spawn ( CBasePlayer *pOwner ) +{ + SetLocalOrigin( pOwner->WorldSpaceCenter() + Vector ( 0 , 0 , 32 ) ); + SetLocalAngles( pOwner->EyeAngles() ); + SetOwnerEntity( pOwner ); + SetNextThink( gpGlobals->curtime ); + EmitSound( "SprayCan.Paint" ); +} + +void CSprayCan::Precache() +{ + BaseClass::Precache(); + + PrecacheScriptSound( "SprayCan.Paint" ); +} + +void CSprayCan::Think( void ) +{ + trace_t tr; + int playernum; + int nFrames; + CBasePlayer *pPlayer; + + pPlayer = ToBasePlayer( GetOwnerEntity() ); + + nFrames = 1; // FIXME, look up from material + + playernum = GetOwnerEntity()->entindex(); + + // Msg( "Spray by player %i, %i of %i\n", playernum, (int)(m_flFrame + 1), nFrames); + + Vector forward; + AngleVectors( GetAbsAngles(), &forward ); + UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + forward * 128, + MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, & tr); + + UTIL_PlayerDecalTrace( &tr, playernum ); + + // Just painted last custom frame. + UTIL_Remove( this ); +} + +class CBloodSplat : public CPointEntity +{ +public: + DECLARE_CLASS( CBloodSplat, CPointEntity ); + + void Spawn ( CBaseEntity *pOwner ); + void Think ( void ); +}; + +void CBloodSplat::Spawn ( CBaseEntity *pOwner ) +{ + SetLocalOrigin( pOwner->WorldSpaceCenter() + Vector ( 0 , 0 , 32 ) ); + SetLocalAngles( pOwner->GetLocalAngles() ); + SetOwnerEntity( pOwner ); + + SetNextThink( gpGlobals->curtime + 0.1f ); +} + +void CBloodSplat::Think( void ) +{ + trace_t tr; + + if ( g_Language.GetInt() != LANGUAGE_GERMAN ) + { + CBasePlayer *pPlayer; + pPlayer = ToBasePlayer( GetOwnerEntity() ); + + Vector forward; + AngleVectors( GetAbsAngles(), &forward ); + UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + forward * 128, + MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, & tr); + + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + UTIL_Remove( this ); +} + +//============================================== + +//----------------------------------------------------------------------------- +// Purpose: Create and give the named item to the player. Then return it. +//----------------------------------------------------------------------------- +CBaseEntity *CBasePlayer::GiveNamedItem( const char *pszName, int iSubType ) +{ + // If I already own this type don't create one + if ( Weapon_OwnsThisType(pszName, iSubType) ) + return NULL; + + // Msg( "giving %s\n", pszName ); + + EHANDLE pent; + + pent = CreateEntityByName(pszName); + if ( pent == NULL ) + { + Msg( "NULL Ent in GiveNamedItem!\n" ); + return NULL; + } + + pent->SetLocalOrigin( GetLocalOrigin() ); + pent->AddSpawnFlags( SF_NORESPAWN ); + + if ( iSubType ) + { + CBaseCombatWeapon *pWeapon = dynamic_cast( (CBaseEntity*)pent ); + if ( pWeapon ) + { + pWeapon->SetSubType( iSubType ); + } + } + + DispatchSpawn( pent ); + + if ( pent != NULL && !(pent->IsMarkedForDeletion()) ) + { + pent->Touch( this ); + } + + return pent; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the nearest COLLIBALE entity in front of the player +// that has a clear line of sight with the given classname +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *FindEntityClassForward( CBasePlayer *pMe, char *classname ) +{ + trace_t tr; + + Vector forward; + pMe->EyeVectors( &forward ); + UTIL_TraceLine(pMe->EyePosition(), + pMe->EyePosition() + forward * MAX_COORD_RANGE, + MASK_SOLID, pMe, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0 && tr.DidHitNonWorldEntity() ) + { + CBaseEntity *pHit = tr.m_pEnt; + if (FClassnameIs( pHit,classname ) ) + { + return pHit; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the nearest COLLIBALE entity in front of the player +// that has a clear line of sight. If HULL is true, the trace will +// hit the collision hull of entities. Otherwise, the trace will hit +// hitboxes. +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull ) +{ + if ( pMe ) + { + trace_t tr; + Vector forward; + int mask; + + if( fHull ) + { + mask = MASK_SOLID; + } + else + { + mask = MASK_SHOT; + } + + pMe->EyeVectors( &forward ); + UTIL_TraceLine(pMe->EyePosition(), + pMe->EyePosition() + forward * MAX_COORD_RANGE, + mask, pMe, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0 && tr.DidHitNonWorldEntity() ) + { + return tr.m_pEnt; + } + } + return NULL; + +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the nearest entity in front of the player of the given +// classname, preferring collidable entities, but allows selection of +// enities that are on the other side of walls or objects +// +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *FindPickerEntityClass( CBasePlayer *pPlayer, char *classname ) +{ + // First try to trace a hull to an entity + CBaseEntity *pEntity = FindEntityClassForward( pPlayer, classname ); + + // If that fails just look for the nearest facing entity + if (!pEntity) + { + Vector forward; + Vector origin; + pPlayer->EyeVectors( &forward ); + origin = pPlayer->WorldSpaceCenter(); + pEntity = gEntList.FindEntityClassNearestFacing( origin, forward,0.95,classname); + } + return pEntity; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the nearest entity in front of the player, preferring +// collidable entities, but allows selection of enities that are +// on the other side of walls or objects +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ) +{ + // First try to trace a hull to an entity + CBaseEntity *pEntity = FindEntityForward( pPlayer, true ); + + // If that fails just look for the nearest facing entity + if (!pEntity) + { + Vector forward; + Vector origin; + pPlayer->EyeVectors( &forward ); + origin = pPlayer->WorldSpaceCenter(); + pEntity = gEntList.FindEntityNearestFacing( origin, forward,0.95); + } + return pEntity; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the nearest node in front of the player +// Input : +// Output : +//----------------------------------------------------------------------------- +CAI_Node *FindPickerAINode( CBasePlayer *pPlayer, NodeType_e nNodeType ) +{ + Vector forward; + Vector origin; + + pPlayer->EyeVectors( &forward ); + origin = pPlayer->EyePosition(); + return g_pAINetworkManager->GetEditOps()->FindAINodeNearestFacing( origin, forward,0.90, nNodeType); +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the nearest link in front of the player +// Input : +// Output : +//----------------------------------------------------------------------------- +CAI_Link *FindPickerAILink( CBasePlayer* pPlayer ) +{ + Vector forward; + Vector origin; + + pPlayer->EyeVectors( &forward ); + origin = pPlayer->EyePosition(); + return g_pAINetworkManager->GetEditOps()->FindAILinkNearestFacing( origin, forward,0.90); +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer::ForceClientDllUpdate( void ) +{ + m_iClientBattery = -1; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = false; // Force weapon send + + // Force all HUD data to be resent to client + m_fInitHUD = true; + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); + + UTIL_RestartAmbientSounds(); // MOTODO that updates the sounds for everybody +} + +/* +============ +ImpulseCommands +============ +*/ + +void CBasePlayer::ImpulseCommands( ) +{ + trace_t tr; + + int iImpulse = (int)m_nImpulse; + switch (iImpulse) + { + case 100: + // temporary flashlight for level designers + if ( FlashlightIsOn() ) + { + FlashlightTurnOff(); + } + else + { + FlashlightTurnOn(); + } + break; + + case 200: + if ( sv_cheats->GetBool() ) + { + CBaseCombatWeapon *pWeapon; + + pWeapon = GetActiveWeapon(); + + if( pWeapon->IsEffectActive( EF_NODRAW ) ) + { + pWeapon->Deploy(); + } + else + { + pWeapon->Holster(); + } + } + break; + + case 201:// paint decal + + if ( gpGlobals->curtime < m_flNextDecalTime ) + { + // too early! + break; + } + + { + Vector forward; + EyeVectors( &forward ); + UTIL_TraceLine ( EyePosition(), + EyePosition() + forward * 128, + MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, & tr); + } + + if ( tr.fraction != 1.0 ) + {// line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->curtime + decalfrequency.GetFloat(); + CSprayCan *pCan = CREATE_UNSAVED_ENTITY( CSprayCan, "spraycan" ); + pCan->Spawn( this ); + } + + break; + + case 202:// player jungle sound + if ( gpGlobals->curtime < m_flNextDecalTime ) + { + // too early! + break; + + } + + EntityMessageBegin( this ); + WRITE_BYTE( PLAY_PLAYER_JINGLE ); + MessageEnd(); + + m_flNextDecalTime = gpGlobals->curtime + decalfrequency.GetFloat(); + break; + + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + + m_nImpulse = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void CreateJeep( CBasePlayer *pPlayer ) +{ + // Cheat to create a jeep in front of the player + Vector vecForward; + AngleVectors( pPlayer->EyeAngles(), &vecForward ); + CBaseEntity *pJeep = (CBaseEntity *)CreateEntityByName( "prop_vehicle_jeep" ); + if ( pJeep ) + { + Vector vecOrigin = pPlayer->GetAbsOrigin() + vecForward * 256 + Vector(0,0,64); + QAngle vecAngles( 0, pPlayer->GetAbsAngles().y - 90, 0 ); + pJeep->SetAbsOrigin( vecOrigin ); + pJeep->SetAbsAngles( vecAngles ); + pJeep->KeyValue( "model", "models/buggy.mdl" ); + pJeep->KeyValue( "solid", "6" ); + pJeep->KeyValue( "targetname", "jeep" ); + pJeep->KeyValue( "vehiclescript", "scripts/vehicles/jeep_test.txt" ); + pJeep->Spawn(); + pJeep->Activate(); + pJeep->Teleport( &vecOrigin, &vecAngles, NULL ); + } +} + + +void CC_CH_CreateJeep( void ) +{ + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) + return; + CreateJeep( pPlayer ); +} + +static ConCommand ch_createjeep("ch_createjeep", CC_CH_CreateJeep, "Spawn jeep in front of the player.", FCVAR_CHEAT); + + +//----------------------------------------------------------------------------- +// Create an airboat in front of the specified player +//----------------------------------------------------------------------------- +static void CreateAirboat( CBasePlayer *pPlayer ) +{ + // Cheat to create a jeep in front of the player + Vector vecForward; + AngleVectors( pPlayer->EyeAngles(), &vecForward ); + CBaseEntity *pJeep = ( CBaseEntity* )CreateEntityByName( "prop_vehicle_airboat" ); + if ( pJeep ) + { + Vector vecOrigin = pPlayer->GetAbsOrigin() + vecForward * 256 + Vector( 0,0,64 ); + QAngle vecAngles( 0, pPlayer->GetAbsAngles().y - 90, 0 ); + pJeep->SetAbsOrigin( vecOrigin ); + pJeep->SetAbsAngles( vecAngles ); + pJeep->KeyValue( "model", "models/airboat.mdl" ); + pJeep->KeyValue( "solid", "6" ); + pJeep->KeyValue( "targetname", "airboat" ); + pJeep->KeyValue( "vehiclescript", "scripts/vehicles/airboat.txt" ); + pJeep->Spawn(); + pJeep->Activate(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CC_CH_CreateAirboat( void ) +{ + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) + return; + + CreateAirboat( pPlayer ); + +} + +static ConCommand ch_createairboat( "ch_createairboat", CC_CH_CreateAirboat, "Spawn airboat in front of the player.", FCVAR_CHEAT ); + + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ +#if !defined( HLDEMO_BUILD ) + if ( !sv_cheats->GetBool() ) + { + return; + } + + CBaseEntity *pEntity; + trace_t tr; + + switch ( iImpulse ) + { + case 76: + { + if (!giPrecacheGrunt) + { + giPrecacheGrunt = 1; + Msg( "You must now restart to use Grunt-o-matic.\n"); + } + else + { + Vector forward = UTIL_YawToVector( EyeAngles().y ); + Create("NPC_human_grunt", GetLocalOrigin() + forward * 128, GetLocalAngles()); + } + break; + } + + case 81: + + GiveNamedItem( "weapon_cubemap" ); + break; + + case 82: + // Cheat to create a jeep in front of the player + CreateJeep( this ); + break; + + case 83: + // Cheat to create a airboat in front of the player + CreateAirboat( this ); + break; + + case 101: + gEvilImpulse101 = true; + + EquipSuit(); + + // Give the player everything! + GiveAmmo( 255, "Pistol"); + GiveAmmo( 255, "AR2"); + GiveAmmo( 5, "AR2AltFire"); + GiveAmmo( 255, "SMG1"); + GiveAmmo( 255, "Buckshot"); + GiveAmmo( 3, "smg1_grenade"); + GiveAmmo( 3, "rpg_round"); + GiveAmmo( 5, "grenade"); + GiveAmmo( 32, "357" ); + GiveAmmo( 16, "XBowBolt" ); +#ifdef HL2_EPISODIC + GiveAmmo( 5, "Hopwire" ); +#endif + GiveNamedItem( "weapon_smg1" ); + GiveNamedItem( "weapon_frag" ); + GiveNamedItem( "weapon_crowbar" ); + GiveNamedItem( "weapon_pistol" ); + GiveNamedItem( "weapon_ar2" ); + GiveNamedItem( "weapon_shotgun" ); + GiveNamedItem( "weapon_physcannon" ); + GiveNamedItem( "weapon_bugbait" ); + GiveNamedItem( "weapon_rpg" ); + GiveNamedItem( "weapon_357" ); + GiveNamedItem( "weapon_crossbow" ); +#ifdef HL2_EPISODIC + //GiveNamedItem( "weapon_hopwire" ); +#endif + if ( GetHealth() < 100 ) + { + TakeHealth( 25, DMG_GENERIC ); + } + + gEvilImpulse101 = false; + + break; + + case 102: + // Gibbage!!! + CGib::SpawnRandomGibs( this, 1, GIB_HUMAN ); + break; + + case 103: + // What the hell are you doing? + pEntity = FindEntityForward( this, true ); + if ( pEntity ) + { + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if ( pNPC ) + pNPC->ReportAIState(); + } + break; + + case 106: + // Give me the classname and targetname of this entity. + pEntity = FindEntityForward( this, true ); + if ( pEntity ) + { + Msg( "Classname: %s", pEntity->GetClassname() ); + + if ( pEntity->GetEntityName() != NULL_STRING ) + { + Msg( " - Name: %s\n", STRING( pEntity->GetEntityName() ) ); + } + else + { + Msg( " - Name: No Targetname\n" ); + } + + if ( pEntity->m_iParent != NULL_STRING ) + Msg( "Parent: %s\n", STRING(pEntity->m_iParent) ); + + Msg( "Model: %s\n", STRING( pEntity->GetModelName() ) ); + if ( pEntity->m_iGlobalname != NULL_STRING ) + Msg( "Globalname: %s\n", STRING(pEntity->m_iGlobalname) ); + } + break; + + case 107: + { + trace_t tr; + + edict_t *pWorld = engine->PEntityOfEntIndex( 0 ); + + Vector start = EyePosition(); + Vector forward; + EyeVectors( &forward ); + Vector end = start + forward * 1024; + UTIL_TraceLine( start, end, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + if ( tr.m_pEnt ) + pWorld = tr.m_pEnt->edict(); + + const char *pTextureName = tr.surface.name; + + if ( pTextureName ) + Msg( "Texture: %s\n", pTextureName ); + } + break; + + // + // Sets the debug NPC to be the NPC under the crosshair. + // + case 108: + { + pEntity = FindEntityForward( this, true ); + if ( pEntity ) + { + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if ( pNPC != NULL ) + { + Msg( "Debugging %s (0x%x)\n", pNPC->GetClassname(), pNPC ); + CAI_BaseNPC::SetDebugNPC( pNPC ); + } + } + break; + } + + case 195:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", GetLocalOrigin(), GetLocalAngles()); + } + break; + case 196:// show shortest paths for entire level to nearest node + { + Create("node_viewer_large", GetLocalOrigin(), GetLocalAngles()); + } + break; + case 197:// show shortest paths for entire level to nearest node + { + Create("node_viewer_human", GetLocalOrigin(), GetLocalAngles()); + } + break; + case 202:// Random blood splatter + { + Vector forward; + EyeVectors( &forward ); + UTIL_TraceLine ( EyePosition(), + EyePosition() + forward * 128, + MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, & tr); + + if ( tr.fraction != 1.0 ) + {// line hit something, so paint a decal + CBloodSplat *pBlood = CREATE_UNSAVED_ENTITY( CBloodSplat, "bloodsplat" ); + pBlood->Spawn( this ); + } + } + break; + case 203:// remove creature. + pEntity = FindEntityForward( this, true ); + if ( pEntity ) + { + UTIL_Remove( pEntity ); +// if ( pEntity->m_takedamage ) +// pEntity->SetThink(SUB_Remove); + } + break; + } +#endif // HLDEMO_BUILD +} + + +bool CBasePlayer::ClientCommand(const char *cmd) +{ +#ifdef _DEBUG + if( stricmp( cmd, "test_SmokeGrenade" ) == 0 ) + { + if ( sv_cheats && sv_cheats->GetBool() ) + { + ParticleSmokeGrenade *pSmoke = dynamic_cast( CreateEntityByName(PARTICLESMOKEGRENADE_ENTITYNAME) ); + if ( pSmoke ) + { + Vector vForward; + AngleVectors( GetLocalAngles(), &vForward ); + vForward.z = 0; + VectorNormalize( vForward ); + + pSmoke->SetLocalOrigin( GetLocalOrigin() + vForward * 100 ); + pSmoke->SetFadeTime(25, 30); // Fade out between 25 seconds and 30 seconds. + pSmoke->Activate(); + pSmoke->SetLifetime(30); + pSmoke->FillVolume(); + + return true; + } + } + } + else +#endif // _DEBUG + if( stricmp( cmd, "vehicleRole" ) == 0 ) + { + // Get the vehicle role value. + if ( engine->Cmd_Argc() == 2 ) + { + // Check to see if a player is in a vehicle. + if ( IsInAVehicle() ) + { + int nRole = atoi( engine->Cmd_Argv( 1 ) ); + IServerVehicle *pVehicle = GetVehicle(); + if ( pVehicle ) + { + // Only switch roles if role is empty! + if ( !pVehicle->GetPassenger( nRole ) ) + { + LeaveVehicle(); + GetInVehicle( pVehicle, nRole ); + } + } + } + + return true; + } + } + else if ( stricmp( cmd, "spectate" ) == 0 ) // join spectator team & start observer mode + { + if ( GetTeamNumber() == TEAM_SPECTATOR ) + return true; + + if ( !IsDead() ) + { + ClientKill( edict() ); // kill player + + // add 1 to frags to balance out the 1 subtracted for killing yourself + IncrementFragCount( 1 ); + } + + RemoveAllItems( true ); + + ChangeTeam( TEAM_SPECTATOR ); + + StartObserverMode( OBS_MODE_ROAMING ); + return true; + } + else if ( stricmp( cmd, "spec_mode" ) == 0 ) // new observer mode + { + int mode; + + // check for parameters. + if ( engine->Cmd_Argc() >= 2 ) + { + mode = atoi( engine->Cmd_Argv(1) ); + + if ( mode < OBS_MODE_IN_EYE || mode > OBS_MODE_ROAMING ) + mode = OBS_MODE_IN_EYE; + } + else + { + // sitch to next spec mode if no parameter give + mode = GetObserverMode() + 1; + + if ( mode > OBS_MODE_ROAMING ) + { + mode = OBS_MODE_IN_EYE; + } + else if ( mode < OBS_MODE_IN_EYE ) + { + mode = OBS_MODE_ROAMING; + } + + } + + // don't allow input while player or death cam animation + if ( GetObserverMode() > OBS_MODE_DEATHCAM ) + { + // set new spectator mode, don't allow OBS_MODE_NONE + if ( !SetObserverMode( mode ) ) + ClientPrint( this, HUD_PRINTCONSOLE, "#Spectator_Mode_Unkown"); + else + engine->ClientCommand( edict(), "cl_spec_mode %d", mode ); + } + else + { + // remember spectator mode for later use + m_iObserverLastMode = mode; + engine->ClientCommand( edict(), "cl_spec_mode %d", mode ); + } + + return true; + } + else if ( stricmp( cmd, "spec_next" ) == 0 ) // chase next player + { + if ( GetObserverMode() > OBS_MODE_FIXED ) + { + // set new spectator mode + CBaseEntity * target = FindNextObserverTarget( false ); + if ( target ) + SetObserverTarget( target ); + } + + return true; + } + else if ( stricmp( cmd, "spec_prev" ) == 0 ) // chase prevoius player + { + if ( GetObserverMode() > OBS_MODE_FIXED ) + { + // set new spectator mode + CBaseEntity * target = FindNextObserverTarget( true ); + if ( target ) + SetObserverTarget( target ); + } + + return true; + } + + else if ( stricmp( cmd, "spec_player" ) == 0 ) // chase next player + { + if ( GetObserverMode() > OBS_MODE_FIXED && + engine->Cmd_Argc() == 2 ) + { + int index = atoi( engine->Cmd_Argv(1) ); + + CBasePlayer * target; + + if ( index == 0 ) + { + target = UTIL_PlayerByName( engine->Cmd_Argv(1) ); + } + else + { + target = UTIL_PlayerByIndex( index ); + } + + if ( IsValidObserverTarget( target ) ) + { + SetObserverTarget( target ); + } + } + + return true; + } + + else if ( stricmp( cmd, "spec_goto" ) == 0 ) // chase next player + { + if ( ( GetObserverMode() == OBS_MODE_FIXED || + GetObserverMode() == OBS_MODE_ROAMING ) && + engine->Cmd_Argc() == 6 ) + { + Vector origin; + origin.x = atof( engine->Cmd_Argv(1) ); + origin.y = atof( engine->Cmd_Argv(2) ); + origin.z = atof( engine->Cmd_Argv(3) ); + + QAngle angle; + angle.x = atof( engine->Cmd_Argv(4) ); + angle.y = atof( engine->Cmd_Argv(5) ); + angle.z = 0.0f; + + JumptoPosition( origin, angle ); + } + + return true; + } + + + + return false; +} + +extern bool UTIL_ItemCanBeTouchedByPlayer( CBaseEntity *pItem, CBasePlayer *pPlayer ); + +//----------------------------------------------------------------------------- +// Purpose: Player reacts to bumping a weapon. +// Input : pWeapon - the weapon that the player bumped into. +// Output : Returns true if player picked up the weapon +//----------------------------------------------------------------------------- +bool CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) +{ + CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); + + // Can I have this weapon type? + if ( IsEFlagSet( EFL_NO_WEAPON_PICKUP ) ) + return false; + + if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) + { + if ( gEvilImpulse101 ) + { + UTIL_Remove( pWeapon ); + } + return false; + } + + // Act differently in the episodes + if ( hl2_episodic.GetBool() ) + { + // Don't let the player touch the item unless unobstructed + if ( !UTIL_ItemCanBeTouchedByPlayer( pWeapon, this ) && !gEvilImpulse101 ) + return false; + } + else + { + // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows) + if( pWeapon->FVisible( this, MASK_SOLID ) == false && !(GetFlags() & FL_NOTARGET) ) + return false; + } + + // ---------------------------------------- + // If I already have it just take the ammo + // ---------------------------------------- + if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType())) + { + if( Weapon_EquipAmmoOnly( pWeapon ) ) + { + // Only remove me if I have no ammo left + if ( pWeapon->HasPrimaryAmmo() ) + return false; + + UTIL_Remove( pWeapon ); + return true; + } + else + { + return false; + } + } + // ------------------------- + // Otherwise take the weapon + // ------------------------- + else + { + pWeapon->CheckRespawn(); + + pWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); + pWeapon->AddEffects( EF_NODRAW ); + + Weapon_Equip( pWeapon ); + if ( IsInAVehicle() ) + { + pWeapon->Holster(); + } + else + { +#ifdef HL2_DLL + +#ifdef _XBOX + CFmtStr hint; + hint.sprintf( "#valve_hint_select_%s", pWeapon->GetClassname() ); + UTIL_HudHintText( this, hint.Access() ); +#endif // _XBOX + + // Always switch to a newly-picked up weapon + if ( !PlayerHasMegaPhysCannon() ) + { + // If it uses clips, load it full. (this is the first time you've picked up this type of weapon) + if ( pWeapon->UsesClipsForAmmo1() ) + { + pWeapon->m_iClip1 = pWeapon->GetMaxClip1(); + } + + Weapon_Switch( pWeapon ); + } +#endif + } + return true; + } +} + + +bool CBasePlayer::RemovePlayerItem( CBaseCombatWeapon *pItem ) +{ + if (GetActiveWeapon() == pItem) + { + ResetAutoaim( ); + pItem->Holster( ); + pItem->SetNextThink( TICK_NEVER_THINK );; // crowbar may be trying to swing again, etc + pItem->SetThink( NULL ); + } + + if ( m_hLastWeapon.Get() == pItem ) + { + Weapon_SetLast( NULL ); + } + + return Weapon_Detach( pItem ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Hides or shows the player's view model. The "r_drawviewmodel" cvar +// can still hide the viewmodel even if this is set to true. +// Input : bShow - true to show, false to hide the view model. +//----------------------------------------------------------------------------- +void CBasePlayer::ShowViewModel(bool bShow) +{ + m_Local.m_bDrawViewmodel = bShow; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bDraw - +//----------------------------------------------------------------------------- +void CBasePlayer::ShowCrosshair( bool bShow ) +{ + if ( bShow ) + { + m_Local.m_iHideHUD &= ~HIDEHUD_CROSSHAIR; + } + else + { + m_Local.m_iHideHUD |= HIDEHUD_CROSSHAIR; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +QAngle CBasePlayer::BodyAngles() +{ + return EyeAngles(); +} + +//------------------------------------------------------------------------------ +// Purpose : Add noise to BodyTarget() to give enemy a better chance of +// getting a clear shot when the player is peeking above a hole +// or behind a ladder (eventually the randomly-picked point +// along the spine will be one that is exposed above the hole or +// between rungs of a ladder.) +// Input : +// Output : +//------------------------------------------------------------------------------ +Vector CBasePlayer::BodyTarget( const Vector &posSrc, bool bNoisy ) +{ + if ( IsInAVehicle() ) + { + return GetVehicle()->GetVehicleEnt()->BodyTarget( posSrc, bNoisy ); + } + if (bNoisy) + { + return GetAbsOrigin() + (GetViewOffset() * random->RandomFloat( 0.7, 1.0 )); + } + else + { + return EyePosition(); + } +}; + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by PlayerPreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ +void CBasePlayer::UpdateClientData( void ) +{ + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + + if (m_fInitHUD) + { + m_fInitHUD = false; + gInitHUD = false; + + UserMessageBegin( user, "ResetHUD" ); + WRITE_BYTE( 0 ); + MessageEnd(); + + if ( !m_fGameHUDInitialized ) + { + g_pGameRules->InitHUD( this ); + InitHUD(); + m_fGameHUDInitialized = true; + if ( g_pGameRules->IsMultiplayer() ) + { + variant_t value; + g_EventQueue.AddEvent( "game_player_manager", "OnPlayerJoin", value, 0, this, this ); + } + } + + variant_t value; + g_EventQueue.AddEvent( "game_player_manager", "OnPlayerSpawn", value, 0, this, this ); + } + + // HACKHACK -- send the message to display the game title + CWorld *world = GetWorldEntity(); + if ( world && world->GetDisplayTitle() ) + { + UserMessageBegin( user, "GameTitle" ); + MessageEnd(); + world->SetDisplayTitle( false ); + } + + if (m_ArmorValue != m_iClientBattery) + { + m_iClientBattery = m_ArmorValue; + + // send "battery" update message + if ( usermessages->LookupUserMessage( "Battery" ) != -1 ) + { + UserMessageBegin( user, "Battery" ); + WRITE_SHORT( (int)m_ArmorValue); + MessageEnd(); + } + } + +#if 0 // BYE BYE!! + // Update Flashlight + if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->curtime)) + { + if (FlashlightIsOn()) + { + if (m_iFlashBattery) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->curtime; + m_iFlashBattery--; + + if (!m_iFlashBattery) + FlashlightTurnOff(); + } + } + else + { + if (m_iFlashBattery < 100) + { + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->curtime; + m_iFlashBattery++; + } + else + m_flFlashLightTime = 0; + } + } +#endif + + CheckTrainUpdate(); + + // Update all the items + for ( int i = 0; i < WeaponCount(); i++ ) + { + if ( GetWeapon(i) ) // each item updates it's successors + GetWeapon(i)->UpdateClientData( this ); + } + + // update the client with our poison state + m_Local.m_bPoisoned = ( m_bitsDamageType & DMG_POISON ) + && ( m_nPoisonDmg > m_nPoisonRestored ) + && ( m_iHealth < 100 ); + + // Let any global rules update the HUD, too + g_pGameRules->UpdateClientData( this ); +} + +#ifdef _XBOX +void CBasePlayer::RumbleEffect( unsigned char index, unsigned char rumbleData, unsigned char rumbleFlags ) +{ + if( !IsAlive() ) + return; + + CSingleUserRecipientFilter filter( this ); + filter.MakeReliable(); + + UserMessageBegin( filter, "XBoxRumble" ); + WRITE_BYTE( index ); + WRITE_BYTE( rumbleData ); + WRITE_BYTE( rumbleFlags ); + MessageEnd(); +} +#else +void CBasePlayer::RumbleEffect( unsigned char index, unsigned char rumbleData, unsigned char rumbleFlags ) +{ + return; +} +#endif//_XBOX + + +void CBasePlayer::EnableControl(bool fControl) +{ + if (!fControl) + AddFlag( FL_FROZEN ); + else + RemoveFlag( FL_FROZEN ); + +} + +void CBasePlayer::CheckTrainUpdate( void ) +{ + if ( IsPC() && ( m_iTrain & TRAIN_NEW ) ) + { + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + + // send "Train" update message + UserMessageBegin( user, "Train" ); + WRITE_BYTE(m_iTrain & 0xF); + MessageEnd(); + + m_iTrain &= ~TRAIN_NEW; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether the player should autoaim or not +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBasePlayer::ShouldAutoaim( void ) +{ + // cannot be in multiplayer + if ( gpGlobals->maxClients > 1 ) + return false; + + // autoaiming is only for easy and medium skill + return ( IsXbox() || !g_pGameRules->IsSkillLevel(SKILL_HARD) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +Vector CBasePlayer::GetAutoaimVector( float flScale ) +{ + autoaim_params_t params; + + params.m_fScale = flScale; + params.m_fMaxDist = autoaim_max_dist.GetFloat(); + + GetAutoaimVector( params ); + return params.m_vecAutoAimDir; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +Vector CBasePlayer::GetAutoaimVector( float flScale, float flMaxDist ) +{ + autoaim_params_t params; + + params.m_fScale = flScale; + params.m_fMaxDist = flMaxDist; + + GetAutoaimVector( params ); + return params.m_vecAutoAimDir; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBasePlayer::GetAutoaimVector( autoaim_params_t ¶ms ) +{ + // Assume autoaim will not be assisting. + params.m_bAutoAimAssisting = false; + + if ( ( ShouldAutoaim() == false ) || ( params.m_fScale == AUTOAIM_SCALE_DIRECT_ONLY ) ) + { + Vector forward; + AngleVectors( EyeAngles() + m_Local.m_vecPunchAngle, &forward ); + + params.m_vecAutoAimDir = forward; + params.m_hAutoAimEntity.Set(NULL); + params.m_vecAutoAimPoint = vec3_invalid; + params.m_bAutoAimAssisting = false; + return; + } + + Vector vecSrc = Weapon_ShootPosition( ); + + m_vecAutoAim.Init( 0.0f, 0.0f, 0.0f ); + + QAngle angles = AutoaimDeflection( vecSrc, params ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = false; + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + Vector forward; + + if( IsXbox() && IsInAVehicle() ) + { + m_vecAutoAim = angles; + AngleVectors( EyeAngles() + m_vecAutoAim, &forward ); + } + else + { + // always use non-sticky autoaim + m_vecAutoAim = angles * 0.9f; + AngleVectors( EyeAngles() + m_Local.m_vecPunchAngle + m_vecAutoAim, &forward ); + } + + params.m_vecAutoAimDir = forward; +} + +//----------------------------------------------------------------------------- +// Targets represent themselves to autoaim as a viewplane-parallel disc with +// a radius specified by the target. The player then modifies this radius +// to achieve more or less aggressive aiming assistance +//----------------------------------------------------------------------------- +float CBasePlayer::GetAutoaimScore( const Vector &eyePosition, const Vector &viewDir, const Vector &vecTarget, CBaseEntity *pTarget, float fScale ) +{ + float radiusSqr; + float targetRadiusSqr = Square( (pTarget->GetAutoAimRadius() * fScale) ); + + Vector vecNearestPoint = PointOnLineNearestPoint( eyePosition, eyePosition + viewDir * 8192, vecTarget ); + Vector vecDiff = vecTarget - vecNearestPoint; + + radiusSqr = vecDiff.LengthSqr(); + + if( radiusSqr <= targetRadiusSqr ) + { + float score; + + score = 1.0f - (radiusSqr / targetRadiusSqr); + + Assert( score >= 0.0f && score <= 1.0f ); + return score; + } + + // 0 means no score- doesn't qualify for autoaim. + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecSrc - +// flDist - +// flDelta - +// Output : Vector +//----------------------------------------------------------------------------- +QAngle CBasePlayer::AutoaimDeflection( Vector &vecSrc, autoaim_params_t ¶ms ) +{ + float bestscore; + float score; + QAngle eyeAngles; + Vector bestdir; + CBaseEntity *bestent; + trace_t tr; + Vector v_forward, v_right, v_up; + + if ( ShouldAutoaim() == false ) + { + m_fOnTarget = false; + return vec3_angle; + } + + eyeAngles = EyeAngles(); + AngleVectors( eyeAngles + m_Local.m_vecPunchAngle + m_vecAutoAim, &v_forward, &v_right, &v_up ); + + // try all possible entities + bestdir = v_forward; + bestscore = 0.0f; + bestent = NULL; + + //Reset this data + m_fOnTarget = false; + params.m_bOnTargetNatural = false; + + CBaseEntity *pIgnore = NULL; + + if( IsInAVehicle() ) + { + pIgnore = GetVehicleEntity(); + } + + CTraceFilterSkipTwoEntities traceFilter( this, pIgnore, COLLISION_GROUP_NONE ); + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * MAX_COORD_FLOAT, MASK_SHOT, &traceFilter, &tr ); + + CBaseEntity *pEntHit = tr.m_pEnt; + + if ( pEntHit && pEntHit->m_takedamage != DAMAGE_NO && pEntHit->GetHealth() > 0 ) + { + // don't look through water + if (!((GetWaterLevel() != 3 && pEntHit->GetWaterLevel() == 3) || (GetWaterLevel() == 3 && pEntHit->GetWaterLevel() == 0))) + { + if( pEntHit->GetFlags() & FL_AIMTARGET ) + { + bool bAimAtThis = true; + + if( UseXboxAiming() && pEntHit->IsNPC() ) + { + int iRelationType = GetDefaultRelationshipDisposition( pEntHit->Classify() ); + + if( iRelationType != D_HT ) + { + bAimAtThis = false; + } + } + + if( bAimAtThis ) + { + if ( pEntHit->GetFlags() & FL_AIMTARGET ) + { + m_fOnTarget = true; + } + + // Player is already on target naturally, don't autoaim. + // Fill out the autoaim_params_t struct, though. + params.m_hAutoAimEntity.Set(pEntHit); + params.m_vecAutoAimDir = bestdir; + params.m_vecAutoAimPoint = tr.endpos; + params.m_bAutoAimAssisting = false; + params.m_bOnTargetNatural = true; + } + } + + return vec3_angle; + } + } + + int count = AimTarget_ListCount(); + if ( count ) + { + CBaseEntity **pList = (CBaseEntity **)stackalloc( sizeof(CBaseEntity *) * count ); + AimTarget_ListCopy( pList, count ); + + for ( int i = 0; i < count; i++ ) + { + Vector center; + Vector dir; + CBaseEntity *pEntity = pList[i]; + + // Don't shoot yourself + if ( pEntity == this ) + continue; + + if (!pEntity->IsAlive() || !pEntity->edict() ) + continue; + + if ( !g_pGameRules->ShouldAutoAim( this, pEntity->edict() ) ) + continue; + + // don't look through water + if ((GetWaterLevel() != 3 && pEntity->GetWaterLevel() == 3) || (GetWaterLevel() == 3 && pEntity->GetWaterLevel() == 0)) + continue; + + if( pEntity->MyNPCPointer() ) + { + // If this entity is an NPC, only aim if it is an enemy. + if ( IRelationType( pEntity ) != D_HT && !pEntity->ShouldAttractAutoAim(this) ) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // Msg( "friend\n"); + continue; + } + } + + // Don't autoaim at the noisy bodytarget, this makes the autoaim crosshair wobble. + //center = pEntity->BodyTarget( vecSrc, false ); + center = pEntity->WorldSpaceCenter(); + + dir = (center - vecSrc); + + float dist = dir.Length2D(); + VectorNormalize( dir ); + + // Skip if out of range. + if( dist > params.m_fMaxDist ) + continue; + + float dot = DotProduct (dir, v_forward ); + + // make sure it's in front of the player + if( dot < 0 ) + continue; + + if( !(pEntity->GetFlags() & FL_FLY) ) + { + // Refuse to take wild shots at targets far from reticle. + if( GetActiveWeapon() != NULL && dot < GetActiveWeapon()->GetMaxAutoAimDeflection() ) + { + continue; + } + } + + score = GetAutoaimScore(vecSrc, v_forward, pEntity->GetAutoAimCenter(), pEntity, params.m_fScale); + + if( score <= bestscore ) + { + continue; + } + + UTIL_TraceLine( vecSrc, center, MASK_SHOT, &traceFilter, &tr ); + + if (tr.fraction != 1.0 && tr.m_pEnt != pEntity ) + { + // Msg( "hit %s, can't see %s\n", STRING( tr.u.ent->classname ), STRING( pEdict->classname ) ); + continue; + } + + // This is the best candidate so far. + bestscore = score; + bestent = pEntity; + bestdir = dir; + } + if ( bestent ) + { + QAngle bestang; + + VectorAngles( bestdir, bestang ); + + if( IsInAVehicle() ) + { + bestang -= EyeAngles(); + } + else + { + bestang -= EyeAngles() - m_Local.m_vecPunchAngle; + } + + m_fOnTarget = true; + + // Autoaim detected a target for us. Aim automatically at its bodytarget. + params.m_hAutoAimEntity.Set(bestent); + params.m_vecAutoAimDir = bestdir; + params.m_vecAutoAimPoint = bestent->BodyTarget( vecSrc, false ); + params.m_bAutoAimAssisting = true; + + return bestang; + } + } + + return QAngle( 0, 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::ResetAutoaim( void ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = QAngle( 0, 0, 0 ); + engine->CrosshairAngle( edict(), 0, 0 ); + } + m_fOnTarget = false; +} + +// ========================================================================== +// > Weapon stuff +// ========================================================================== + +//----------------------------------------------------------------------------- +// Purpose: Override base class, player can always use weapon +// Input : A weapon +// Output : true or false +//----------------------------------------------------------------------------- +bool CBasePlayer::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) +{ + return true; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Override to clear dropped weapon from the hud +//----------------------------------------------------------------------------- +void CBasePlayer::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget /* = NULL */, const Vector *pVelocity /* = NULL */ ) +{ + bool bWasActiveWeapon = false; + if ( pWeapon == GetActiveWeapon() ) + { + bWasActiveWeapon = true; + } + + if ( pWeapon ) + { + if ( bWasActiveWeapon ) + { + pWeapon->SendWeaponAnim( ACT_VM_IDLE ); + } + } + + BaseClass::Weapon_Drop( pWeapon, pvecTarget, pVelocity ); + + if ( bWasActiveWeapon ) + { + if (!SwitchToNextBestWeapon( NULL )) + { + CBaseViewModel *vm = GetViewModel(); + if ( vm ) + { + vm->AddEffects( EF_NODRAW ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : weaponSlot - +//----------------------------------------------------------------------------- +void CBasePlayer::Weapon_DropSlot( int weaponSlot ) +{ + CBaseCombatWeapon *pWeapon; + + // Check for that slot being occupied already + for ( int i=0; i < MAX_WEAPONS; i++ ) + { + pWeapon = GetWeapon( i ); + + if ( pWeapon != NULL ) + { + // If the slots match, it's already occupied + if ( pWeapon->GetSlot() == weaponSlot ) + { + Weapon_Drop( pWeapon, NULL, NULL ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override to add weapon to the hud +//----------------------------------------------------------------------------- +void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) +{ + BaseClass::Weapon_Equip( pWeapon ); + + bool bShouldSwitch = g_pGameRules->FShouldSwitchWeapon( this, pWeapon ); + +#ifdef HL2_DLL + if ( bShouldSwitch == false && PhysCannonGetHeldEntity( GetActiveWeapon() ) == pWeapon && + Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()) ) + { + bShouldSwitch = true; + } +#endif//HL2_DLL + + // should we switch to this item? + if ( bShouldSwitch ) + { + Weapon_Switch( pWeapon ); + } +} + + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +CBaseEntity *CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + for ( int i = 0 ; i < WeaponCount() ; i++ ) + { + if ( !GetWeapon(i) ) + continue; + + if ( FStrEq( pszItemName, GetWeapon(i)->GetClassname() ) ) + { + return GetWeapon(i); + } + } + + return NULL; +} + + + +//================================================================================ +// TEAM HANDLING +//================================================================================ +//----------------------------------------------------------------------------- +// Purpose: Put the player in the specified team +//----------------------------------------------------------------------------- + +void CBasePlayer::ChangeTeam( int iTeamNum ) +{ + if ( !GetGlobalTeam( iTeamNum ) ) + { + Warning( "CBasePlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum ); + return; + } + + // if this is our current team, just abort + if ( iTeamNum == GetTeamNumber() ) + { + return; + } + + // Immediately tell all clients that he's changing team. This has to be done + // first, so that all user messages that follow as a result of the team change + // come after this one, allowing the client to be prepared for them. + IGameEvent * event = gameeventmanager->CreateEvent( "player_team" ); + if ( event ) + { + event->SetInt("userid", GetUserID() ); + event->SetInt("team", iTeamNum ); + event->SetInt("oldteam", GetTeamNumber() ); + event->SetInt("disconnect", IsDisconnecting()); + + gameeventmanager->FireEvent( event ); + } + + // Remove him from his current team + if ( GetTeam() ) + { + GetTeam()->RemovePlayer( this ); + } + + // Are we being added to a team? + if ( iTeamNum ) + { + GetGlobalTeam( iTeamNum )->AddPlayer( this ); + } + + BaseClass::ChangeTeam( iTeamNum ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Locks a player to the spot; they can't move, shoot, or be hurt +//----------------------------------------------------------------------------- +void CBasePlayer::LockPlayerInPlace( void ) +{ + if ( m_iPlayerLocked ) + return; + + AddFlag( FL_GODMODE | FL_FROZEN ); + SetMoveType( MOVETYPE_NONE ); + m_iPlayerLocked = true; + + // force a client data update, so that anything that has been done to + // this player previously this frame won't get delayed in being sent + UpdateClientData(); +} + +//----------------------------------------------------------------------------- +// Purpose: Unlocks a previously locked player +//----------------------------------------------------------------------------- +void CBasePlayer::UnlockPlayer( void ) +{ + if ( !m_iPlayerLocked ) + return; + + RemoveFlag( FL_GODMODE | FL_FROZEN ); + SetMoveType( MOVETYPE_WALK ); + m_iPlayerLocked = false; +} + +bool CBasePlayer::ClearUseEntity() +{ + if ( m_hUseEntity != NULL ) + { + // Stop controlling the train/object + // TODO: Send HUD Update + m_hUseEntity->Use( this, this, USE_OFF, 0 ); + m_hUseEntity = NULL; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::HideViewModels( void ) +{ + for ( int i = 0 ; i < MAX_VIEWMODELS; i++ ) + { + CBaseViewModel *vm = GetViewModel( i ); + if ( !vm ) + continue; + + vm->SetWeaponModel( NULL, NULL ); + } +} + +class CStripWeapons : public CPointEntity +{ + DECLARE_CLASS( CStripWeapons, CPointEntity ); +public: + void InputStripWeapons(inputdata_t &data); + void InputStripWeaponsAndSuit(inputdata_t &data); + + void StripWeapons(inputdata_t &data, bool stripSuit); + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); + +BEGIN_DATADESC( CStripWeapons ) + DEFINE_INPUTFUNC( FIELD_VOID, "Strip", InputStripWeapons ), + DEFINE_INPUTFUNC( FIELD_VOID, "StripWeaponsAndSuit", InputStripWeaponsAndSuit ), +END_DATADESC() + + +void CStripWeapons::InputStripWeapons(inputdata_t &data) +{ + StripWeapons(data, false); +} + +void CStripWeapons::InputStripWeaponsAndSuit(inputdata_t &data) +{ + StripWeapons(data, true); +} + +void CStripWeapons::StripWeapons(inputdata_t &data, bool stripSuit) +{ + CBasePlayer *pPlayer = NULL; + + if ( data.pActivator && data.pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)data.pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + + if ( pPlayer ) + { + pPlayer->RemoveAllItems( stripSuit ); + } +} + + +class CRevertSaved : public CPointEntity +{ + DECLARE_CLASS( CRevertSaved, CPointEntity ); +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void LoadThink( void ); + + DECLARE_DATADESC(); + + inline float Duration( void ) { return m_Duration; } + inline float HoldTime( void ) { return m_HoldTime; } + inline float LoadTime( void ) { return m_loadTime; } + + inline void SetDuration( float duration ) { m_Duration = duration; } + inline void SetHoldTime( float hold ) { m_HoldTime = hold; } + inline void SetLoadTime( float time ) { m_loadTime = time; } + + //Inputs + void InputReload(inputdata_t &data); + +#ifdef HL1_DLL + void MessageThink( void ); + inline float MessageTime( void ) { return m_messageTime; } + inline void SetMessageTime( float time ) { m_messageTime = time; } +#endif + +private: + + float m_loadTime; + float m_Duration; + float m_HoldTime; + +#ifdef HL1_DLL + string_t m_iszMessage; + float m_messageTime; +#endif +}; + +LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); + +BEGIN_DATADESC( CRevertSaved ) + +#ifdef HL1_DLL + DEFINE_KEYFIELD( m_iszMessage, FIELD_STRING, "message" ), + DEFINE_KEYFIELD( m_messageTime, FIELD_FLOAT, "messagetime" ), // These are not actual times, but durations, so save as floats + + DEFINE_FUNCTION( MessageThink ), +#endif + + DEFINE_KEYFIELD( m_loadTime, FIELD_FLOAT, "loadtime" ), + DEFINE_KEYFIELD( m_Duration, FIELD_FLOAT, "duration" ), + DEFINE_KEYFIELD( m_HoldTime, FIELD_FLOAT, "holdtime" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Reload", InputReload ), + + + // Function Pointers + DEFINE_FUNCTION( LoadThink ), + +END_DATADESC() + +CBaseEntity *CreatePlayerLoadSave( Vector vOrigin, float flDuration, float flHoldTime, float flLoadTime ) +{ + CRevertSaved *pRevertSaved = (CRevertSaved *) CreateEntityByName( "player_loadsaved" ); + + if ( pRevertSaved == NULL ) + return NULL; + + UTIL_SetOrigin( pRevertSaved, vOrigin ); + + pRevertSaved->Spawn(); + pRevertSaved->SetDuration( flDuration ); + pRevertSaved->SetHoldTime( flHoldTime ); + pRevertSaved->SetLoadTime( flLoadTime ); + + return pRevertSaved; +} + + + +void CRevertSaved::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), FFADE_OUT ); + SetNextThink( gpGlobals->curtime + LoadTime() ); + SetThink( &CRevertSaved::LoadThink ); + + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + + if ( pPlayer ) + { + //Adrian: Setting this flag so we can't move or save a game. + pPlayer->pl.deadflag = true; + pPlayer->AddFlag( (FL_NOTARGET|FL_FROZEN) ); + } +} + +void CRevertSaved::InputReload( inputdata_t &inputdata ) +{ + UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), FFADE_OUT ); + +#ifdef HL1_DLL + SetNextThink( gpGlobals->curtime + MessageTime() ); + SetThink( &CRevertSaved::MessageThink ); +#else + SetNextThink( gpGlobals->curtime + LoadTime() ); + SetThink( &CRevertSaved::LoadThink ); +#endif + + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + + if ( pPlayer ) + { + //Adrian: Setting this flag so we can't move or save a game. + pPlayer->pl.deadflag = true; + pPlayer->AddFlag( (FL_NOTARGET|FL_FROZEN) ); + } +} + +#ifdef HL1_DLL +void CRevertSaved::MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING( m_iszMessage ) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + SetNextThink( gpGlobals->curtime + nextThink ); + SetThink( &CRevertSaved::LoadThink ); + } + else + LoadThink(); +} +#endif + + +void CRevertSaved::LoadThink( void ) +{ + if ( !gpGlobals->deathmatch ) + { + engine->ServerCommand("reload\n"); + } +} + +class CMovementSpeedMod : public CPointEntity +{ + DECLARE_CLASS( CMovementSpeedMod, CPointEntity ); +public: + void InputSpeedMod(inputdata_t &data); + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( player_speedmod, CMovementSpeedMod ); + +BEGIN_DATADESC( CMovementSpeedMod ) + DEFINE_INPUTFUNC( FIELD_FLOAT, "ModifySpeed", InputSpeedMod ), +END_DATADESC() + + +void CMovementSpeedMod::InputSpeedMod(inputdata_t &data) +{ + CBasePlayer *pPlayer = NULL; + + if ( data.pActivator && data.pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)data.pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + + if ( pPlayer ) + { + pPlayer->SetLaggedMovementValue( data.value.Float() ); + } +} + + +void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID) +{ + int mask = (1<m_Int = ( data & mask ); +} +// -------------------------------------------------------------------------------- // +// SendTable for CPlayerState. +// -------------------------------------------------------------------------------- // + + BEGIN_SEND_TABLE_NOBASE(CPlayerState, DT_PlayerState) + SendPropInt (SENDINFO(deadflag), 1, SPROP_UNSIGNED ), + END_SEND_TABLE() + +// -------------------------------------------------------------------------------- // +// This data only gets sent to clients that ARE this player entity. +// -------------------------------------------------------------------------------- // + + BEGIN_SEND_TABLE_NOBASE( CBasePlayer, DT_LocalPlayerExclusive ) + + SendPropDataTable ( SENDINFO_DT(m_Local), &REFERENCE_SEND_TABLE(DT_Local) ), + +// If HL2_DLL is defined, then baseflex.cpp already sends these. +#ifndef HL2_DLL + SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 0), 8, SPROP_ROUNDDOWN, -32.0, 32.0f), + SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 1), 8, SPROP_ROUNDDOWN, -32.0, 32.0f), + SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 2), 10, SPROP_CHANGES_OFTEN, 0.0f, 128.0f), +#endif + + SendPropFloat ( SENDINFO(m_flFriction), 8, SPROP_ROUNDDOWN, 0.0f, 4.0f), + + SendPropArray3 ( SENDINFO_ARRAY3(m_iAmmo), SendPropInt( SENDINFO_ARRAY(m_iAmmo), 10, SPROP_UNSIGNED ) ), + + SendPropInt ( SENDINFO( m_fOnTarget ), 2, SPROP_UNSIGNED ), + + SendPropInt ( SENDINFO( m_nTickBase ), -1, SPROP_CHANGES_OFTEN ), + SendPropInt ( SENDINFO( m_nNextThinkTick ) ), + + SendPropEHandle ( SENDINFO( m_hLastWeapon ) ), + SendPropEHandle ( SENDINFO( m_hGroundEntity ), SPROP_CHANGES_OFTEN ), + + SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 0), 20, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), + SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 1), 20, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), + SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 2), 16, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), + + SendPropVector ( SENDINFO( m_vecBaseVelocity ), 20, 0, -1000, 1000 ), + + SendPropEHandle ( SENDINFO( m_hConstraintEntity)), + SendPropVector ( SENDINFO( m_vecConstraintCenter), 0, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO( m_flConstraintRadius ), 0, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO( m_flConstraintWidth ), 0, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO( m_flConstraintSpeedFactor ), 0, SPROP_NOSCALE ), + + SendPropFloat ( SENDINFO( m_flDeathTime ), 0, SPROP_NOSCALE ), + + SendPropInt ( SENDINFO( m_nWaterLevel ), 2, SPROP_UNSIGNED ), + SendPropFloat ( SENDINFO( m_flLaggedMovementValue ), 0, SPROP_NOSCALE ), + + END_SEND_TABLE() + + +// -------------------------------------------------------------------------------- // +// DT_BasePlayer sendtable. +// -------------------------------------------------------------------------------- // + + IMPLEMENT_SERVERCLASS_ST( CBasePlayer, DT_BasePlayer ) + + SendPropDataTable(SENDINFO_DT(pl), &REFERENCE_SEND_TABLE(DT_PlayerState), SendProxy_DataTableToDataTable), + SendPropEHandle(SENDINFO(m_hVehicle)), + SendPropEHandle(SENDINFO(m_hUseEntity)), + SendPropInt (SENDINFO(m_iHealth), 10 ), + SendPropInt (SENDINFO(m_lifeState), 3, SPROP_UNSIGNED ), + SendPropFloat (SENDINFO(m_flMaxspeed), 12, SPROP_ROUNDDOWN, 0.0f, 2048.0f ), // CL + SendPropInt (SENDINFO(m_fFlags), PLAYER_FLAG_BITS, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN, SendProxy_CropFlagsToPlayerFlagBitsLength ), + SendPropInt (SENDINFO(m_iObserverMode), 3, SPROP_UNSIGNED ), + SendPropEHandle (SENDINFO(m_hObserverTarget) ), + SendPropInt (SENDINFO(m_iFOV), 8, SPROP_UNSIGNED ), + SendPropInt (SENDINFO(m_iDefaultFOV), 8, SPROP_UNSIGNED ), + SendPropArray ( SendPropEHandle( SENDINFO_ARRAY( m_hViewModel ) ), m_hViewModel ), + SendPropString (SENDINFO(m_szLastPlaceName) ), + + // Data that only gets sent to the local player. + SendPropDataTable( "localdata", 0, &REFERENCE_SEND_TABLE(DT_LocalPlayerExclusive), SendProxy_SendLocalDataTable ), + + END_SEND_TABLE() + +//============================================================================= +// +// Player Physics Shadow Code +// + +void CBasePlayer::SetupVPhysicsShadow( CPhysCollide *pStandModel, const char *pStandHullName, CPhysCollide *pCrouchModel, const char *pCrouchHullName ) +{ + solid_t solid; + Q_strncpy( solid.surfaceprop, "player", sizeof(solid.surfaceprop) ); + solid.params = g_PhysDefaultObjectParams; + solid.params.mass = 85.0f; + solid.params.inertia = 1e24f; + solid.params.enableCollisions = false; + //disable drag + solid.params.dragCoefficient = 0; + // create standing hull + m_pShadowStand = PhysModelCreateCustom( this, pStandModel, GetLocalOrigin(), GetLocalAngles(), pStandHullName, false, &solid ); + m_pShadowStand->SetCallbackFlags( CALLBACK_GLOBAL_COLLISION | CALLBACK_SHADOW_COLLISION ); + + // create crouchig hull + m_pShadowCrouch = PhysModelCreateCustom( this, pCrouchModel, GetLocalOrigin(), GetLocalAngles(), pCrouchHullName, false, &solid ); + m_pShadowCrouch->SetCallbackFlags( CALLBACK_GLOBAL_COLLISION | CALLBACK_SHADOW_COLLISION ); + + // default to stand + VPhysicsSetObject( m_pShadowStand ); + + // tell physics lists I'm a shadow controller object + PhysAddShadow( this ); + m_pPhysicsController = physenv->CreatePlayerController( m_pShadowStand ); + m_pPhysicsController->SetPushMassLimit( 350.0f ); + m_pPhysicsController->SetPushSpeedLimit( 50.0f ); + + // Give the controller a valid position so it doesn't do anything rash. + UpdatePhysicsShadowToCurrentPosition(); + + // init state + if ( GetFlags() & FL_DUCKING ) + { + SetVCollisionState( VPHYS_CROUCH ); + } + else + { + SetVCollisionState( VPHYS_WALK ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Empty, just want to keep the baseentity version from being called +// current so we don't kick up dust, etc. +//----------------------------------------------------------------------------- +void CBasePlayer::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + float savedImpact = m_impactEnergyScale; + + // HACKHACK: Reduce player's stress by 1/8th + m_impactEnergyScale *= 0.125f; + ApplyStressDamage( pPhysics, true ); + m_impactEnergyScale = savedImpact; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) +{ + if ( sv_turbophysics.GetBool() ) + return; + + Vector newPosition; + + bool physicsUpdated = m_pPhysicsController->GetShadowPosition( &newPosition, NULL ) > 0 ? true : false; + + // UNDONE: If the player is penetrating, but the player's game collisions are not stuck, teleport the physics shadow to the game position + if ( pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING ) + { + CUtlVector list; + PhysGetListOfPenetratingEntities( this, list ); + for ( int i = list.Count()-1; i >= 0; --i ) + { + // filter out anything that isn't simulated by vphysics + // UNDONE: Filter out motion disabled objects? + if ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS ) + { + // I'm currently stuck inside a moving object, so allow vphysics to + // apply velocity to the player in order to separate these objects + m_touchedPhysObject = true; + } + + // if it's an NPC, tell them that the player is intersecting them + CAI_BaseNPC *pNPC = list[i]->MyNPCPointer(); + if ( pNPC ) + { + pNPC->PlayerPenetratingVPhysics(); + } + } + } + + if ( m_pPhysicsController->IsInContact() || (m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER) ) + { + m_touchedPhysObject = true; + } + + if ( IsFollowingPhysics() ) + { + m_touchedPhysObject = true; + } + + if ( GetMoveType() == MOVETYPE_NOCLIP ) + { + m_oldOrigin = GetAbsOrigin(); + return; + } + + if ( phys_timescale.GetFloat() == 0.0f ) + { + physicsUpdated = false; + } + + if ( !physicsUpdated ) + return; + + IPhysicsObject *pPhysGround = GetGroundVPhysics(); + + Vector newVelocity; + pPhysics->GetPosition( &newPosition, 0 ); + m_pPhysicsController->GetShadowVelocity( &newVelocity ); + + if ( physicsshadowupdate_render.GetBool() ) + { + NDebugOverlay::Box( GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 24, 15.0f ); + NDebugOverlay::Box( newPosition, WorldAlignMins(), WorldAlignMaxs(), 0,0,255, 24, 15.0f); + // NDebugOverlay::Box( newPosition, WorldAlignMins(), WorldAlignMaxs(), 0,0,255, 24, .01f); + } + + Vector tmp = GetAbsOrigin() - newPosition; + if ( !m_touchedPhysObject && !(GetFlags() & FL_ONGROUND) ) + { + tmp.z *= 0.5f; // don't care about z delta as much + } + + float dist = tmp.LengthSqr(); + float deltaV = (newVelocity - GetAbsVelocity()).LengthSqr(); + + float maxDistErrorSqr = VPHYS_MAX_DISTSQR; + float maxVelErrorSqr = VPHYS_MAX_VELSQR; + if ( IsRideablePhysics(pPhysGround) ) + { + maxDistErrorSqr *= 0.25; + maxVelErrorSqr *= 0.25; + } + + if ( dist >= maxDistErrorSqr || deltaV >= maxVelErrorSqr || (pPhysGround && !m_touchedPhysObject) ) + { + if ( m_touchedPhysObject || pPhysGround ) + { + // BUGBUG: Rewrite this code using fixed timestep + if ( deltaV >= maxVelErrorSqr ) + { + Vector dir = GetAbsVelocity(); + float len = VectorNormalize(dir); + float dot = DotProduct( newVelocity, dir ); + if ( dot > len ) + { + dot = len; + } + else if ( dot < -len ) + { + dot = -len; + } + + VectorMA( newVelocity, -dot, dir, newVelocity ); + + if ( m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER ) + { + float val = Lerp( 0.1f, len, dot ); + VectorMA( newVelocity, val - len, dir, newVelocity ); + } + + if ( !IsRideablePhysics(pPhysGround) ) + { + if ( !(m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER ) && IsSimulatingOnAlternateTicks() ) + { + newVelocity *= 0.5f; + } + ApplyAbsVelocityImpulse( newVelocity ); + } + } + + trace_t trace; + UTIL_TraceEntity( this, newPosition, newPosition, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + if ( !trace.allsolid && !trace.startsolid ) + { + SetAbsOrigin( newPosition ); + } + } + else + { + trace_t trace; + UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + + // current position is not ok, fixup + if ( trace.allsolid || trace.startsolid ) + { + // STUCK!?!?! + //Warning( "Stuck2 on %s!!\n", trace.m_pEnt->GetClassname() ); + SetAbsOrigin( newPosition ); + } + } + } + else + { + if ( m_touchedPhysObject ) + { + // check my position (physics object could have simulated into my position + // physics is not very far away, check my position + trace_t trace; + UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), + MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + + // is current position ok? + if ( trace.allsolid || trace.startsolid ) + { + // stuck????!?!? + //Msg("Stuck on %s\n", trace.m_pEnt->GetClassName()); + SetAbsOrigin( newPosition ); + UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), + MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + if ( trace.allsolid || trace.startsolid ) + { + //Msg("Double Stuck\n"); + SetAbsOrigin( m_oldOrigin ); + } + } + } + } + m_oldOrigin = GetAbsOrigin(); + // UNDONE: Force physics object to be at player position when not touching phys??? +} + +// recreate physics on save/load, don't try to save the state! +bool CBasePlayer::ShouldSavePhysics() +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::InitVCollision( void ) +{ + // Cleanup any old vphysics stuff. + VPhysicsDestroyObject(); + + // in turbo physics players dont have a physics shadow + if ( sv_turbophysics.GetBool() ) + return; + + CPhysCollide *pModel = PhysCreateBbox( VEC_HULL_MIN, VEC_HULL_MAX ); + CPhysCollide *pCrouchModel = PhysCreateBbox( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); + + SetupVPhysicsShadow( pModel, "player_stand", pCrouchModel, "player_crouch" ); +} + + +void CBasePlayer::VPhysicsDestroyObject() +{ + // Since CBasePlayer aliases its pointer to the physics object, tell CBaseEntity to + // clear out its physics object pointer so we don't wind up deleting one of + // the aliased objects twice. + VPhysicsSetObject( NULL ); + + PhysRemoveShadow( this ); + + if ( m_pPhysicsController ) + { + physenv->DestroyPlayerController( m_pPhysicsController ); + m_pPhysicsController = NULL; + } + + if ( m_pShadowStand ) + { + PhysDestroyObject( m_pShadowStand ); + m_pShadowStand = NULL; + } + if ( m_pShadowCrouch ) + { + PhysDestroyObject( m_pShadowCrouch ); + m_pShadowCrouch = NULL; + } + + BaseClass::VPhysicsDestroyObject(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::SetVCollisionState( int collisionState ) +{ + Vector vel = vec3_origin; + Vector pos = vec3_origin; + vel = GetAbsVelocity(); + pos = GetAbsOrigin(); + + m_vphysicsCollisionState = collisionState; + switch( collisionState ) + { + case VPHYS_WALK: + m_pShadowStand->SetPosition( pos, vec3_angle, true ); + m_pShadowStand->SetVelocity( &vel, NULL ); + m_pShadowCrouch->EnableCollisions( false ); + m_pPhysicsController->SetObject( m_pShadowStand ); + VPhysicsSwapObject( m_pShadowStand ); + m_pShadowStand->EnableCollisions( true ); + break; + + case VPHYS_CROUCH: + m_pShadowCrouch->SetPosition( pos, vec3_angle, true ); + m_pShadowCrouch->SetVelocity( &vel, NULL ); + m_pShadowStand->EnableCollisions( false ); + m_pPhysicsController->SetObject( m_pShadowCrouch ); + VPhysicsSwapObject( m_pShadowCrouch ); + m_pShadowCrouch->EnableCollisions( true ); + break; + + case VPHYS_NOCLIP: + m_pShadowCrouch->EnableCollisions( false ); + m_pShadowStand->EnableCollisions( false ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CBasePlayer::GetFOV( void ) const +{ + if ( m_iFOV == 0 ) + return GetDefaultFOV(); + + return m_iFOV; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : FOV - +// zoomRate - +//----------------------------------------------------------------------------- +bool CBasePlayer::SetFOV( CBaseEntity *pRequester, int FOV, float zoomRate ) +{ + //NOTENOTE: You MUST specify who is requesting the zoom change + assert( pRequester != NULL ); + if ( pRequester == NULL ) + return false; + + // If we already have an owner, we only allow requests from that owner + if ( ( m_hZoomOwner != NULL ) && ( m_hZoomOwner != pRequester ) ) + { + if ( CanOverrideEnvZoomOwner( m_hZoomOwner ) == false ) + return false; + } + else + { + //FIXME: Maybe do this is as an accessor instead + if ( FOV == 0 ) + { + m_hZoomOwner = NULL; + } + else + { + m_hZoomOwner = pRequester; + } + } + + m_iFOV = FOV; + + m_Local.m_flFOVRate = zoomRate; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the default FOV for the player if nothing else is going on +// Input : FOV - the new base FOV for this player +//----------------------------------------------------------------------------- +void CBasePlayer::SetDefaultFOV( int FOV ) +{ + m_iDefaultFOV = ( FOV == 0 ) ? g_pGameRules->DefaultFOV() : FOV; +} + +//----------------------------------------------------------------------------- +// Purpose: // static func +// Input : set - +//----------------------------------------------------------------------------- +void CBasePlayer::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set ) +{ + // Append our health + set.AppendCriteria( "playerhealth", UTIL_VarArgs( "%i", GetHealth() ) ); + float healthfrac = 0.0f; + if ( GetMaxHealth() > 0 ) + { + healthfrac = (float)GetHealth() / (float)GetMaxHealth(); + } + + set.AppendCriteria( "playerhealthfrac", UTIL_VarArgs( "%.3f", healthfrac ) ); + + CBaseCombatWeapon *weapon = GetActiveWeapon(); + if ( weapon ) + { + set.AppendCriteria( "playerweapon", weapon->GetClassname() ); + } + else + { + set.AppendCriteria( "playerweapon", "none" ); + } + + // Append current activity name + set.AppendCriteria( "playeractivity", CAI_BaseNPC::GetActivityName( GetActivity() ) ); + + set.AppendCriteria( "playerspeed", UTIL_VarArgs( "%.3f", GetAbsVelocity().Length() ) ); + + AppendContextToCriteria( set, "player" ); +} + + +const QAngle& CBasePlayer::GetPunchAngle() +{ + return m_Local.m_vecPunchAngle.Get(); +} + + +void CBasePlayer::SetPunchAngle( const QAngle &punchAngle ) +{ + m_Local.m_vecPunchAngle = punchAngle; + + if ( IsAlive() ) + { + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index && pPlayer->GetObserverTarget() == this && pPlayer->GetObserverMode() == OBS_MODE_IN_EYE ) + { + pPlayer->SetPunchAngle( punchAngle ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Apply a movement constraint to the player +//----------------------------------------------------------------------------- +void CBasePlayer::ActivateMovementConstraint( CBaseEntity *pEntity, const Vector &vecCenter, float flRadius, float flConstraintWidth, float flSpeedFactor ) +{ + m_hConstraintEntity = pEntity; + m_vecConstraintCenter = vecCenter; + m_flConstraintRadius = flRadius; + m_flConstraintWidth = flConstraintWidth; + m_flConstraintSpeedFactor = flSpeedFactor; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::DeactivateMovementConstraint( ) +{ + m_hConstraintEntity = NULL; + m_flConstraintRadius = 0.0f; + m_vecConstraintCenter = vec3_origin; +} + +//----------------------------------------------------------------------------- +// Perhaps a poorly-named function. This function traces against the supplied +// NPC's hitboxes (instead of hull). If the trace hits a different NPC, the +// new NPC is selected. Otherwise, the supplied NPC is determined to be the +// one the citizen wants. This function allows the selection of a citizen over +// another citizen's shoulder, which is impossible without tracing against +// hitboxes instead of the hull (sjb) +//----------------------------------------------------------------------------- +CBaseEntity *CBasePlayer::DoubleCheckUseNPC( CBaseEntity *pNPC, const Vector &vecSrc, const Vector &vecDir ) +{ + trace_t tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 1024, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + if( tr.m_pEnt != NULL && tr.m_pEnt->MyNPCPointer() && tr.m_pEnt != pNPC ) + { + // Player is selecting a different NPC through some negative space + // in the first NPC's hitboxes (between legs, over shoulder, etc). + return tr.m_pEnt; + } + + return pNPC; +} + + +bool CBasePlayer::IsBot() const +{ + return (GetFlags() & FL_FAKECLIENT) != 0; +} + +bool CBasePlayer::IsFakeClient() const +{ + return (GetFlags() & FL_FAKECLIENT) != 0; +} + +void CBasePlayer::EquipSuit( bool bPlayEffects ) +{ + m_Local.m_bWearingSuit = true; +} + +void CBasePlayer::RemoveSuit( void ) +{ + m_Local.m_bWearingSuit = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CBasePlayer::GetTracerType( void ) +{ + if ( GetActiveWeapon() ) + { + return GetActiveWeapon()->GetTracerType(); + } + + return BaseClass::GetTracerType(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &tr - +// nDamageType - +//----------------------------------------------------------------------------- +void CBasePlayer::DoImpactEffect( trace_t &tr, int nDamageType ) +{ + if ( GetActiveWeapon() ) + { + GetActiveWeapon()->DoImpactEffect( tr, nDamageType ); + return; + } + + BaseClass::DoImpactEffect( tr, nDamageType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::InputSetHealth( inputdata_t &inputdata ) +{ + int iNewHealth = inputdata.value.Int(); + int iDelta = abs(GetHealth() - iNewHealth); + if ( iNewHealth > GetHealth() ) + { + TakeHealth( iDelta, DMG_GENERIC ); + } + else if ( iNewHealth < GetHealth() ) + { + // Strip off and restore armor so that it doesn't absorb any of this damage. + int armor = m_ArmorValue; + m_ArmorValue = 0; + TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) ); + m_ArmorValue = armor; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Hides or displays the HUD +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CBasePlayer::InputSetHUDVisibility( inputdata_t &inputdata ) +{ + bool bEnable = inputdata.value.Bool(); + + if ( bEnable ) + { + m_Local.m_iHideHUD &= ~HIDEHUD_ALL; + } + else + { + m_Local.m_iHideHUD |= HIDEHUD_ALL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEntity - +//----------------------------------------------------------------------------- +void CBasePlayer::SetViewEntity( CBaseEntity *pEntity ) +{ + m_hViewEntity = pEntity; + + if ( m_hViewEntity ) + { + engine->SetView( edict(), m_hViewEntity->edict() ); + } + else + { + engine->SetView( edict(), edict() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Looks at the player's reserve ammo and also all his weapons for any ammo +// of the specified type +// Input : nAmmoIndex - ammo to look for +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBasePlayer::HasAnyAmmoOfType( int nAmmoIndex ) +{ + // Must be a valid index + if ( nAmmoIndex < 0 ) + return false; + + // If we have some in reserve, we're already done + if ( GetAmmoCount( nAmmoIndex ) ) + return true; + + CBaseCombatWeapon *pWeapon; + + // Check all held weapons + for ( int i=0; i < MAX_WEAPONS; i++ ) + { + pWeapon = GetWeapon( i ); + + if ( !pWeapon ) + continue; + + // We must use clips and use this sort of ammo + if ( pWeapon->UsesClipsForAmmo1() && pWeapon->GetPrimaryAmmoType() == nAmmoIndex ) + { + // If we have any ammo, we're done + if ( pWeapon->HasPrimaryAmmo() ) + return true; + } + + // We'll check both clips for the same ammo type, just in case + if ( pWeapon->UsesClipsForAmmo2() && pWeapon->GetSecondaryAmmoType() == nAmmoIndex ) + { + if ( pWeapon->HasSecondaryAmmo() ) + return true; + } + } + + // We're completely without this type of ammo + return false; +} + +//----------------------------------------------------------------------------- +// return a string version of the players network (i.e steam) ID. +// +//----------------------------------------------------------------------------- +const char *CBasePlayer::GetNetworkIDString() +{ + Q_strncpy( m_szNetworkIDString, engine->GetPlayerNetworkIDString( edict() ), sizeof(m_szNetworkIDString) ); + return m_szNetworkIDString; +} + +//----------------------------------------------------------------------------- +// Assign the player a name +//----------------------------------------------------------------------------- +void CBasePlayer::SetPlayerName( const char *name ) +{ + Assert( name ); + + if ( name ) + { + Assert( strlen(name) > 0 ); + + Q_strncpy( m_szNetname, name, sizeof(m_szNetname) ); + } +} + +//----------------------------------------------------------------------------- +// sets the "don't autokick me" flag on a player +//----------------------------------------------------------------------------- +class DisableAutokick +{ +public: + DisableAutokick( int userID ) + { + m_userID = userID; + } + + bool operator()( CBasePlayer *player ) + { + if ( player->GetUserID() == m_userID ) + { + Msg( "autokick is disabled for %s\n", player->GetPlayerName() ); + player->DisableAutoKick( true ); + return false; // don't need to check other players + } + + return true; // keep looking at other players + } + +private: + int m_userID; +}; + +//----------------------------------------------------------------------------- +// sets the "don't autokick me" flag on a player +//----------------------------------------------------------------------------- +CON_COMMAND( mp_disable_autokick, "Prevents a userid from being auto-kicked" ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + { + Msg( "You must be a server admin to use mp_disable_autokick\n" ); + return; + } + + if ( engine->Cmd_Argc() != 2 ) + { + Msg( "Usage: mp_disable_autokick \n" ); + return; + } + + int userID = atoi( engine->Cmd_Argv( 1 ) ); + DisableAutokick disable( userID ); + ForEachPlayer( disable ); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle between the duck being on and off +//----------------------------------------------------------------------------- +void CBasePlayer::ToggleDuck( void ) +{ + // Toggle the state + m_bDuckToggled = !m_bDuckToggled; +} + +//----------------------------------------------------------------------------- +// Just tells us how far the stick is from the center. No directional info +//----------------------------------------------------------------------------- +float CBasePlayer::GetStickDist() +{ + Vector2D controlStick; + + controlStick.x = m_flForwardMove; + controlStick.y = m_flSideMove; + + return controlStick.Length(); +} + +//----------------------------------------------------------------------------- +// CPlayerInfo functions (simple passthroughts to get around the CBasePlayer multiple inheritence limitation) +//----------------------------------------------------------------------------- +const char *CPlayerInfo::GetName() +{ + Assert( m_pParent ); + return m_pParent->GetPlayerName(); +} + +int CPlayerInfo::GetUserID() +{ + Assert( m_pParent ); + return engine->GetPlayerUserId( m_pParent->edict() ); +} + +const char *CPlayerInfo::GetNetworkIDString() +{ + Assert( m_pParent ); + return m_pParent->GetNetworkIDString(); +} + +int CPlayerInfo::GetTeamIndex() +{ + Assert( m_pParent ); + return m_pParent->GetTeamNumber(); +} + +void CPlayerInfo::ChangeTeam( int iTeamNum ) +{ + Assert( m_pParent ); + m_pParent->ChangeTeam(iTeamNum); +} + +int CPlayerInfo::GetFragCount() +{ + Assert( m_pParent ); + return m_pParent->FragCount(); +} + +int CPlayerInfo::GetDeathCount() +{ + Assert( m_pParent ); + return m_pParent->DeathCount(); +} + +bool CPlayerInfo::IsConnected() +{ + Assert( m_pParent ); + return m_pParent->IsConnected(); +} + +int CPlayerInfo::GetArmorValue() +{ + Assert( m_pParent ); + return m_pParent->ArmorValue(); +} + +bool CPlayerInfo::IsHLTV() +{ + Assert( m_pParent ); + return m_pParent->IsHLTV(); +} + +bool CPlayerInfo::IsPlayer() +{ + Assert( m_pParent ); + return m_pParent->IsPlayer(); +} + +bool CPlayerInfo::IsFakeClient() +{ + Assert( m_pParent ); + return m_pParent->IsFakeClient(); +} + +bool CPlayerInfo::IsDead() +{ + Assert( m_pParent ); + return m_pParent->IsDead(); +} + +bool CPlayerInfo::IsInAVehicle() +{ + Assert( m_pParent ); + return m_pParent->IsInAVehicle(); +} + +bool CPlayerInfo::IsObserver() +{ + Assert( m_pParent ); + return m_pParent->IsObserver(); +} + +const Vector CPlayerInfo::GetAbsOrigin() +{ + Assert( m_pParent ); + return m_pParent->GetAbsOrigin(); +} + +const QAngle CPlayerInfo::GetAbsAngles() +{ + Assert( m_pParent ); + return m_pParent->GetAbsAngles(); +} + +const Vector CPlayerInfo::GetPlayerMins() +{ + Assert( m_pParent ); + return m_pParent->GetPlayerMins(); +} + +const Vector CPlayerInfo::GetPlayerMaxs() +{ + Assert( m_pParent ); + return m_pParent->GetPlayerMaxs(); +} + +const char *CPlayerInfo::GetWeaponName() +{ + Assert( m_pParent ); + CBaseCombatWeapon *weap = m_pParent->GetActiveWeapon(); + if ( !weap ) + { + return NULL; + } + return weap->GetName(); +} + +const char *CPlayerInfo::GetModelName() +{ + Assert( m_pParent ); + return m_pParent->GetModelName().ToCStr(); +} + +const int CPlayerInfo::GetHealth() +{ + Assert( m_pParent ); + return m_pParent->GetHealth(); +} + +const int CPlayerInfo::GetMaxHealth() +{ + Assert( m_pParent ); + return m_pParent->GetMaxHealth(); +} + + + + + +void CPlayerInfo::SetAbsOrigin( Vector & vec ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + m_pParent->SetAbsOrigin(vec); + } +} + +void CPlayerInfo::SetAbsAngles( QAngle & ang ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + m_pParent->SetAbsAngles(ang); + } +} + +void CPlayerInfo::RemoveAllItems( bool removeSuit ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + m_pParent->RemoveAllItems(removeSuit); + } +} + +void CPlayerInfo::SetActiveWeapon( const char *WeaponName ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + CBaseCombatWeapon *weap = m_pParent->Weapon_Create( WeaponName ); + if ( weap ) + { + m_pParent->Weapon_Equip(weap); + m_pParent->Weapon_Switch(weap); + } + } +} + +void CPlayerInfo::SetLocalOrigin( const Vector& origin ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + m_pParent->SetLocalOrigin(origin); + } +} + +const Vector CPlayerInfo::GetLocalOrigin( void ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + Vector origin = m_pParent->GetLocalOrigin(); + return origin; + } + else + { + return Vector( 0, 0, 0 ); + } +} + +void CPlayerInfo::SetLocalAngles( const QAngle& angles ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + m_pParent->SetLocalAngles( angles ); + } +} + +const QAngle CPlayerInfo::GetLocalAngles( void ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + return m_pParent->GetLocalAngles(); + } + else + { + return QAngle(); + } +} + +void CPlayerInfo::PostClientMessagesSent( void ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + m_pParent->PostClientMessagesSent(); + } +} + +bool CPlayerInfo::IsEFlagSet( int nEFlagMask ) +{ + Assert( m_pParent ); + if ( m_pParent->IsBot() ) + { + return m_pParent->IsEFlagSet(nEFlagMask); + } + return false; +} + +void CPlayerInfo::RunPlayerMove( CBotCmd *ucmd ) +{ + if ( m_pParent->IsBot() ) + { + Assert( m_pParent ); + CUserCmd cmd; + cmd.buttons = ucmd->buttons; + cmd.command_number = ucmd->command_number; + cmd.forwardmove = ucmd->forwardmove; + cmd.hasbeenpredicted = ucmd->hasbeenpredicted; + cmd.impulse = ucmd->impulse; + cmd.mousedx = ucmd->mousedx; + cmd.mousedy = ucmd->mousedy; + cmd.random_seed = ucmd->random_seed; + cmd.sidemove = ucmd->sidemove; + cmd.tick_count = ucmd->tick_count; + cmd.upmove = ucmd->upmove; + cmd.viewangles = ucmd->viewangles; + cmd.weaponselect = ucmd->weaponselect; + cmd.weaponsubtype = ucmd->weaponsubtype; + + // Store off the globals.. they're gonna get whacked + float flOldFrametime = gpGlobals->frametime; + float flOldCurtime = gpGlobals->curtime; + + m_pParent->SetTimeBase( gpGlobals->curtime ); + + MoveHelperServer()->SetHost( m_pParent ); + m_pParent->PlayerRunCommand( &cmd, MoveHelperServer() ); + + // save off the last good usercmd + m_pParent->SetLastUserCommand( cmd ); + + // Clear out any fixangle that has been set + m_pParent->pl.fixangle = FIXANGLE_NONE; + + // Restore the globals.. + gpGlobals->frametime = flOldFrametime; + gpGlobals->curtime = flOldCurtime; + MoveHelperServer()->SetHost( NULL ); + } +} + +void CPlayerInfo::SetLastUserCommand( const CBotCmd &ucmd ) +{ + if ( m_pParent->IsBot() ) + { + Assert( m_pParent ); + CUserCmd cmd; + cmd.buttons = ucmd.buttons; + cmd.command_number = ucmd.command_number; + cmd.forwardmove = ucmd.forwardmove; + cmd.hasbeenpredicted = ucmd.hasbeenpredicted; + cmd.impulse = ucmd.impulse; + cmd.mousedx = ucmd.mousedx; + cmd.mousedy = ucmd.mousedy; + cmd.random_seed = ucmd.random_seed; + cmd.sidemove = ucmd.sidemove; + cmd.tick_count = ucmd.tick_count; + cmd.upmove = ucmd.upmove; + cmd.viewangles = ucmd.viewangles; + cmd.weaponselect = ucmd.weaponselect; + cmd.weaponsubtype = ucmd.weaponsubtype; + + m_pParent->SetLastUserCommand(cmd); + } +} + + +CBotCmd CPlayerInfo::GetLastUserCommand() +{ + CBotCmd cmd; + const CUserCmd *ucmd = m_pParent->GetLastUserCommand(); + if ( ucmd ) + { + cmd.buttons = ucmd->buttons; + cmd.command_number = ucmd->command_number; + cmd.forwardmove = ucmd->forwardmove; + cmd.hasbeenpredicted = ucmd->hasbeenpredicted; + cmd.impulse = ucmd->impulse; + cmd.mousedx = ucmd->mousedx; + cmd.mousedy = ucmd->mousedy; + cmd.random_seed = ucmd->random_seed; + cmd.sidemove = ucmd->sidemove; + cmd.tick_count = ucmd->tick_count; + cmd.upmove = ucmd->upmove; + cmd.viewangles = ucmd->viewangles; + cmd.weaponselect = ucmd->weaponselect; + cmd.weaponsubtype = ucmd->weaponsubtype; + } + return cmd; +} + diff --git a/dlls/player_lagcompensation.cpp b/dlls/player_lagcompensation.cpp index 189e12d9..dc946d97 100644 --- a/dlls/player_lagcompensation.cpp +++ b/dlls/player_lagcompensation.cpp @@ -234,7 +234,7 @@ void CLagCompensationManager::FrameUpdatePostEntityThink() VPROF_BUDGET( "FrameUpdatePostEntityThink", "CLagCompensationManager" ); // remove all records before that time: - int flDeadtime = gpGlobals->curtime - sv_maxunlag.GetFloat(); + int flDeadtime = static_cast(gpGlobals->curtime - sv_maxunlag.GetFloat()); // Iterate all active players for ( int i = 1; i <= gpGlobals->maxClients; i++ ) diff --git a/dlls/player_resource.cpp b/dlls/player_resource.cpp index a62a35ea..8aa25b42 100644 --- a/dlls/player_resource.cpp +++ b/dlls/player_resource.cpp @@ -115,7 +115,7 @@ void CPlayerResource::UpdatePlayerData( void ) UTIL_GetPlayerConnectionInfo( i, ping, packetloss ); // calc avg for scoreboard so it's not so jittery - ping = 0.8f * m_iPing.Get(i) + 0.2f * ping; + ping = static_cast(0.8f * m_iPing.Get(i) + 0.2f * ping); m_iPing.Set( i, ping ); diff --git a/dlls/playerinfomanager.cpp b/dlls/playerinfomanager.cpp index 3e1285e4..c9f7359b 100644 --- a/dlls/playerinfomanager.cpp +++ b/dlls/playerinfomanager.cpp @@ -1,134 +1,134 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: implementation of player info manager -// -//=============================================================================// - -#include "cbase.h" -#include "player.h" -#include "playerinfomanager.h" -#include "edict.h" - -extern CGlobalVars *gpGlobals; -static CPlayerInfoManager s_PlayerInfoManager; -static CPluginBotManager s_BotManager; - -namespace -{ - - // - // Old version support - // - abstract_class IPlayerInfo_V1 - { - public: - // returns the players name (UTF-8 encoded) - virtual const char *GetName() = 0; - // returns the userid (slot number) - virtual int GetUserID() = 0; - // returns the string of their network (i.e Steam) ID - virtual const char *GetNetworkIDString() = 0; - // returns the team the player is on - virtual int GetTeamIndex() = 0; - // changes the player to a new team (if the game dll logic allows it) - virtual void ChangeTeam( int iTeamNum ) = 0; - // returns the number of kills this player has (exact meaning is mod dependent) - virtual int GetFragCount() = 0; - // returns the number of deaths this player has (exact meaning is mod dependent) - virtual int GetDeathCount() = 0; - // returns if this player slot is actually valid - virtual bool IsConnected() = 0; - // returns the armor/health of the player (exact meaning is mod dependent) - virtual int GetArmorValue() = 0; - }; - - abstract_class IPlayerInfoManager_V1 - { - public: - virtual IPlayerInfo_V1 *GetPlayerInfo( edict_t *pEdict ) = 0; - }; - - - class CPlayerInfoManager_V1: public IPlayerInfoManager_V1 - { - public: - virtual IPlayerInfo_V1 *GetPlayerInfo( edict_t *pEdict ); - }; - - static CPlayerInfoManager_V1 s_PlayerInfoManager_V1; - - - IPlayerInfo_V1 *CPlayerInfoManager_V1::GetPlayerInfo( edict_t *pEdict ) - { - CBasePlayer *pPlayer = ( ( CBasePlayer * )CBaseEntity::Instance( pEdict )); - if ( pPlayer ) - { - return (IPlayerInfo_V1 *)pPlayer->GetPlayerInfo(); - } - else - { - return NULL; - } - } - - EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CPlayerInfoManager, IPlayerInfoManager_V1, "PlayerInfoManager001", s_PlayerInfoManager); -}; - -IPlayerInfo *CPlayerInfoManager::GetPlayerInfo( edict_t *pEdict ) -{ - CBasePlayer *pPlayer = ( ( CBasePlayer * )CBaseEntity::Instance( pEdict )); - if ( pPlayer ) - { - return pPlayer->GetPlayerInfo(); - } - else - { - return NULL; - } -} - -CGlobalVars *CPlayerInfoManager::GetGlobalVars() -{ - return gpGlobals; -} - - - -IBotController *CPluginBotManager::GetBotController( edict_t *pEdict ) -{ - CBasePlayer *pPlayer = ( ( CBasePlayer * )CBaseEntity::Instance( pEdict )); - if ( pPlayer && pPlayer->IsBot() ) - { - return pPlayer->GetBotController(); - } - else - { - return NULL; - } -} - -edict_t *CPluginBotManager::CreateBot( const char *botname ) -{ - edict_t *pEdict = engine->CreateFakeClient( botname ); - if (!pEdict) - { - Msg( "Failed to create Bot.\n"); - return NULL; - } - - // Allocate a player entity for the bot, and call spawn - CBasePlayer *pPlayer = ((CBasePlayer*)CBaseEntity::Instance( pEdict )); - - pPlayer->ClearFlags(); - pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT ); - - pPlayer->ChangeTeam( TEAM_UNASSIGNED ); - pPlayer->RemoveAllItems( true ); - pPlayer->Spawn(); - - return pEdict; -} - -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CPlayerInfoManager, IPlayerInfoManager, INTERFACEVERSION_PLAYERINFOMANAGER, s_PlayerInfoManager); -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CPluginBotManager, IBotManager, INTERFACEVERSION_PLAYERBOTMANAGER, s_BotManager); - +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: implementation of player info manager +// +//=============================================================================// + +#include "cbase.h" +#include "player.h" +#include "playerinfomanager.h" +#include "edict.h" + +extern CGlobalVars *gpGlobals; +static CPlayerInfoManager s_PlayerInfoManager; +static CPluginBotManager s_BotManager; + +namespace +{ + + // + // Old version support + // + abstract_class IPlayerInfo_V1 + { + public: + // returns the players name (UTF-8 encoded) + virtual const char *GetName() = 0; + // returns the userid (slot number) + virtual int GetUserID() = 0; + // returns the string of their network (i.e Steam) ID + virtual const char *GetNetworkIDString() = 0; + // returns the team the player is on + virtual int GetTeamIndex() = 0; + // changes the player to a new team (if the game dll logic allows it) + virtual void ChangeTeam( int iTeamNum ) = 0; + // returns the number of kills this player has (exact meaning is mod dependent) + virtual int GetFragCount() = 0; + // returns the number of deaths this player has (exact meaning is mod dependent) + virtual int GetDeathCount() = 0; + // returns if this player slot is actually valid + virtual bool IsConnected() = 0; + // returns the armor/health of the player (exact meaning is mod dependent) + virtual int GetArmorValue() = 0; + }; + + abstract_class IPlayerInfoManager_V1 + { + public: + virtual IPlayerInfo_V1 *GetPlayerInfo( edict_t *pEdict ) = 0; + }; + + + class CPlayerInfoManager_V1: public IPlayerInfoManager_V1 + { + public: + virtual IPlayerInfo_V1 *GetPlayerInfo( edict_t *pEdict ); + }; + + static CPlayerInfoManager_V1 s_PlayerInfoManager_V1; + + + IPlayerInfo_V1 *CPlayerInfoManager_V1::GetPlayerInfo( edict_t *pEdict ) + { + CBasePlayer *pPlayer = ( ( CBasePlayer * )CBaseEntity::Instance( pEdict )); + if ( pPlayer ) + { + return (IPlayerInfo_V1 *)pPlayer->GetPlayerInfo(); + } + else + { + return NULL; + } + } + + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CPlayerInfoManager, IPlayerInfoManager_V1, "PlayerInfoManager001", s_PlayerInfoManager); +}; + +IPlayerInfo *CPlayerInfoManager::GetPlayerInfo( edict_t *pEdict ) +{ + CBasePlayer *pPlayer = ( ( CBasePlayer * )CBaseEntity::Instance( pEdict )); + if ( pPlayer ) + { + return pPlayer->GetPlayerInfo(); + } + else + { + return NULL; + } +} + +CGlobalVars *CPlayerInfoManager::GetGlobalVars() +{ + return gpGlobals; +} + + + +IBotController *CPluginBotManager::GetBotController( edict_t *pEdict ) +{ + CBasePlayer *pPlayer = ( ( CBasePlayer * )CBaseEntity::Instance( pEdict )); + if ( pPlayer && pPlayer->IsBot() ) + { + return pPlayer->GetBotController(); + } + else + { + return NULL; + } +} + +edict_t *CPluginBotManager::CreateBot( const char *botname ) +{ + edict_t *pEdict = engine->CreateFakeClient( botname ); + if (!pEdict) + { + Msg( "Failed to create Bot.\n"); + return NULL; + } + + // Allocate a player entity for the bot, and call spawn + CBasePlayer *pPlayer = ((CBasePlayer*)CBaseEntity::Instance( pEdict )); + + pPlayer->ClearFlags(); + pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT ); + + pPlayer->ChangeTeam( TEAM_UNASSIGNED ); + pPlayer->RemoveAllItems( true ); + pPlayer->Spawn(); + + return pEdict; +} + +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CPlayerInfoManager, IPlayerInfoManager, INTERFACEVERSION_PLAYERINFOMANAGER, s_PlayerInfoManager); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CPluginBotManager, IBotManager, INTERFACEVERSION_PLAYERBOTMANAGER, s_BotManager); + diff --git a/dlls/playerinfomanager.h b/dlls/playerinfomanager.h index b1a2c3cc..aabfc9ec 100644 --- a/dlls/playerinfomanager.h +++ b/dlls/playerinfomanager.h @@ -1,32 +1,32 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: implementation of player info manager -// -//=============================================================================// -#ifndef PLAYERINFOMANAGER_H -#define PLAYERINFOMANAGER_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "dlls/iplayerinfo.h" - -//----------------------------------------------------------------------------- -// Purpose: interface for plugins to get player info -//----------------------------------------------------------------------------- -class CPlayerInfoManager: public IPlayerInfoManager -{ -public: - virtual IPlayerInfo *GetPlayerInfo( edict_t *pEdict ); - virtual CGlobalVars *GetGlobalVars(); -}; - -class CPluginBotManager: public IBotManager -{ -public: - virtual IBotController *GetBotController( edict_t *pEdict ); - virtual edict_t *CreateBot( const char *botname ); -}; - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: implementation of player info manager +// +//=============================================================================// +#ifndef PLAYERINFOMANAGER_H +#define PLAYERINFOMANAGER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "dlls/iplayerinfo.h" + +//----------------------------------------------------------------------------- +// Purpose: interface for plugins to get player info +//----------------------------------------------------------------------------- +class CPlayerInfoManager: public IPlayerInfoManager +{ +public: + virtual IPlayerInfo *GetPlayerInfo( edict_t *pEdict ); + virtual CGlobalVars *GetGlobalVars(); +}; + +class CPluginBotManager: public IBotManager +{ +public: + virtual IBotController *GetBotController( edict_t *pEdict ); + virtual edict_t *CreateBot( const char *botname ); +}; + +#endif diff --git a/dlls/point_spotlight.cpp b/dlls/point_spotlight.cpp index 6a171729..d3b5d8fc 100644 --- a/dlls/point_spotlight.cpp +++ b/dlls/point_spotlight.cpp @@ -186,12 +186,12 @@ void CPointSpotlight::ComputeRenderInfo() } else if ( m_flSpotlightCurLength > m_flSpotlightMaxLength ) { - m_hSpotlightTarget->SetRenderColorA( (1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength)) ); + m_hSpotlightTarget->SetRenderColorA( static_cast((1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength))) ); m_hSpotlight->SetFadeLength( m_flSpotlightMaxLength ); } else { - m_hSpotlightTarget->SetRenderColorA( 1.0 ); + m_hSpotlightTarget->SetRenderColorA( 1 ); m_hSpotlight->SetFadeLength( m_flSpotlightCurLength ); } diff --git a/dlls/props.cpp b/dlls/props.cpp index 346240ce..c557e5d0 100644 --- a/dlls/props.cpp +++ b/dlls/props.cpp @@ -260,7 +260,7 @@ void CBaseProp::Activate( void ) // Make sure mapmakers haven't used the wrong prop type. if ( m_takedamage == DAMAGE_NO && m_iHealth != 0 ) { - Warning("%s has a health specified in model '%s'. Use prop_physics or prop_dynamic instead.\n", GetClassname(), GetModelName() ); + Warning("%s has a health specified in model '%s'. Use prop_physics or prop_dynamic instead.\n", GetClassname(), STRING(GetModelName()) ); } } @@ -365,7 +365,7 @@ void CBaseProp::DrawDebugGeometryOverlays( void ) // Remap health to green brightness float flG = RemapVal( m_iHealth, 0, 100, 64, 255 ); flG = clamp( flG, 0, 255 ); - NDebugOverlay::EntityBounds(this, 0, flG, 0, 0, 0 ); + NDebugOverlay::EntityBounds(this, 0, static_cast(flG), 0, 0, 0 ); } } } @@ -443,7 +443,7 @@ void CBreakableProp::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, } // Frighten AIs, just in case this is an exploding thing. - CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 128.0f, 1.0f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 128, 1.0f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); } //----------------------------------------------------------------------------- @@ -1469,7 +1469,7 @@ void CBreakableProp::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Rea SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime ); - if( Reason == PUNTED_BY_CANNON ) + if( Reason == static_cast(PUNTED_BY_CANNON) ) { PlayPuntSound(); } @@ -1618,14 +1618,14 @@ void CBreakableProp::Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info ) { if( HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE ) ) { - ExplosionCreate( WorldSpaceCenter(), angles, pAttacker, m_explodeDamage, m_explodeRadius, + ExplosionCreate( WorldSpaceCenter(), angles, pAttacker, static_cast(m_explodeDamage), static_cast(m_explodeRadius), SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_SURFACEONLY | SF_ENVEXPLOSION_NOSOUND, 0.0f, this ); EmitSound("PropaneTank.Burst"); } else { - ExplosionCreate( WorldSpaceCenter(), angles, pAttacker, m_explodeDamage, m_explodeRadius, + ExplosionCreate( WorldSpaceCenter(), angles, pAttacker, static_cast(m_explodeDamage), static_cast(m_explodeRadius), SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_SURFACEONLY, 0.0f, this ); } @@ -1688,7 +1688,7 @@ void CBreakableProp::Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info ) { if ( bExploded == false ) { - ExplosionCreate( origin, angles, pAttacker, 1, m_explodeRadius, + ExplosionCreate( origin, angles, pAttacker, 1, static_cast(m_explodeRadius), SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this ); } @@ -5004,7 +5004,7 @@ void CPropDoorRotating::BeginOpening(CBaseEntity *pOpenAwayFrom) // Make respectful entities move away from our path if( !HasSpawnFlags(SF_DOOR_SILENT_TO_NPCS) ) { - CSoundEnt::InsertSound( SOUND_MOVE_AWAY, volumeCenter, volumeRadius, 0.5f, pOpenAwayFrom ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, volumeCenter, static_cast(volumeRadius), 0.5f, pOpenAwayFrom ); } // Do final setup @@ -5263,7 +5263,7 @@ class CPhysicsPropMultiplayer : public CPhysicsProp, public IMultiplayerPhysics DECLARE_SERVERCLASS(); DECLARE_DATADESC(); - CPhysicsPropMultiplayer::CPhysicsPropMultiplayer() + CPhysicsPropMultiplayer() { m_iPhysicsMode = PHYSICS_MULTIPLAYER_AUTODETECT; m_usingCustomCollisionBounds = false; diff --git a/dlls/recipientfilter.cpp b/dlls/recipientfilter.cpp index fbf78c20..2f4da1ac 100644 --- a/dlls/recipientfilter.cpp +++ b/dlls/recipientfilter.cpp @@ -1,373 +1,373 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "cbase.h" -#include "recipientfilter.h" -#include "team.h" -#include "ipredictionsystem.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -static IPredictionSystem g_RecipientFilterPredictionSystem; - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CRecipientFilter::CRecipientFilter() -{ - Reset(); -} - -CRecipientFilter::~CRecipientFilter() -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : src - -//----------------------------------------------------------------------------- -void CRecipientFilter::CopyFrom( const CRecipientFilter& src ) -{ - m_bReliable = src.IsReliable(); - m_bInitMessage = src.IsInitMessage(); - - m_bUsingPredictionRules = src.IsUsingPredictionRules(); - m_bIgnorePredictionCull = src.IgnorePredictionCull(); - - int c = src.GetRecipientCount(); - for ( int i = 0; i < c; ++i ) - { - m_Recipients.AddToTail( src.GetRecipientIndex( i ) ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CRecipientFilter::Reset( void ) -{ - m_bReliable = false; - m_bInitMessage = false; - m_Recipients.RemoveAll(); - m_bUsingPredictionRules = false; - m_bIgnorePredictionCull = false; -} - -void CRecipientFilter::MakeReliable( void ) -{ - m_bReliable = true; -} - -bool CRecipientFilter::IsReliable( void ) const -{ - return m_bReliable; -} - -int CRecipientFilter::GetRecipientCount( void ) const -{ - return m_Recipients.Size(); -} - -int CRecipientFilter::GetRecipientIndex( int slot ) const -{ - if ( slot < 0 || slot >= GetRecipientCount() ) - return -1; - - return m_Recipients[ slot ]; -} - -void CRecipientFilter::AddAllPlayers( void ) -{ - m_Recipients.RemoveAll(); - - int i; - for ( i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - if ( !pPlayer ) - { - continue; - } - - AddRecipient( pPlayer ); - } -} - -void CRecipientFilter::AddRecipient( CBasePlayer *player ) -{ - Assert( player ); - - int index = player->entindex(); - - // If we're predicting and this is not the first time we've predicted this sound - // then don't send it to the local player again. - if ( m_bUsingPredictionRules ) - { - // Only add local player if this is the first time doing prediction - if ( g_RecipientFilterPredictionSystem.GetSuppressHost() == player ) - { - return; - } - } - - // Already in list - if ( m_Recipients.Find( index ) != m_Recipients.InvalidIndex() ) - return; - - m_Recipients.AddToTail( index ); -} - -void CRecipientFilter::RemoveAllRecipients( void ) -{ - m_Recipients.RemoveAll(); -} - -void CRecipientFilter::RemoveRecipient( CBasePlayer *player ) -{ - Assert( player ); - - int index = player->entindex(); - - // Remove it if it's in the list - m_Recipients.FindAndRemove( index ); -} - -void CRecipientFilter::AddRecipientsByTeam( CTeam *team ) -{ - Assert( team ); - - int i; - int c = team->GetNumPlayers(); - for ( i = 0 ; i < c ; i++ ) - { - CBasePlayer *player = team->GetPlayer( i ); - if ( !player ) - continue; - - AddRecipient( player ); - } -} - -void CRecipientFilter::RemoveRecipientsByTeam( CTeam *team ) -{ - Assert( team ); - - int i; - int c = team->GetNumPlayers(); - for ( i = 0 ; i < c ; i++ ) - { - CBasePlayer *player = team->GetPlayer( i ); - if ( !player ) - continue; - - RemoveRecipient( player ); - } -} - -void CRecipientFilter::AddPlayersFromBitMask( CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits ) -{ - int index = playerbits.FindNextSetBit( 0 ); - - while ( index > -1 ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( index + 1 ); - if ( pPlayer ) - { - AddRecipient( pPlayer ); - } - - index = playerbits.FindNextSetBit( index + 1 ); - } -} - -void CRecipientFilter::RemovePlayersFromBitMask( CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits ) -{ - int index = playerbits.FindNextSetBit( 0 ); - - while ( index > -1 ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( index + 1 ); - if ( pPlayer ) - { - RemoveRecipient( pPlayer ); - } - - index = playerbits.FindNextSetBit( index + 1 ); - } -} - -void CRecipientFilter::AddRecipientsByPVS( const Vector& origin ) -{ - if ( gpGlobals->maxClients == 1 ) - { - AddAllPlayers(); - } - else - { - CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; - engine->Message_DetermineMulticastRecipients( false, origin, playerbits ); - AddPlayersFromBitMask( playerbits ); - } -} - -void CRecipientFilter::RemoveRecipientsByPVS( const Vector& origin ) -{ - if ( gpGlobals->maxClients == 1 ) - { - m_Recipients.RemoveAll(); - } - else - { - CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; - engine->Message_DetermineMulticastRecipients( false, origin, playerbits ); - RemovePlayersFromBitMask( playerbits ); - } -} - - - -void CRecipientFilter::AddRecipientsByPAS( const Vector& origin ) -{ - if ( gpGlobals->maxClients == 1 ) - { - AddAllPlayers(); - } - else - { - CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; - engine->Message_DetermineMulticastRecipients( true, origin, playerbits ); - AddPlayersFromBitMask( playerbits ); - } -} - -bool CRecipientFilter::IsInitMessage( void ) const -{ - return m_bInitMessage; -} - -void CRecipientFilter::MakeInitMessage( void ) -{ - m_bInitMessage = true; -} - -void CRecipientFilter::UsePredictionRules( void ) -{ - if ( m_bUsingPredictionRules ) - return; - - m_bUsingPredictionRules = true; - - // Cull list now, if needed - if ( GetRecipientCount() == 0 ) - return; - - CBasePlayer *pPlayer = ToBasePlayer( (CBaseEntity*)g_RecipientFilterPredictionSystem.GetSuppressHost() ); - - if ( pPlayer) - { - RemoveRecipient( pPlayer ); - } -} - -bool CRecipientFilter::IsUsingPredictionRules( void ) const -{ - return m_bUsingPredictionRules; -} - -bool CRecipientFilter:: IgnorePredictionCull( void ) const -{ - return m_bIgnorePredictionCull; -} - -void CRecipientFilter::SetIgnorePredictionCull( bool ignore ) -{ - m_bIgnorePredictionCull = ignore; -} - -//----------------------------------------------------------------------------- -// Purpose: Simple class to create a filter for all players on a given team -//----------------------------------------------------------------------------- -CTeamRecipientFilter::CTeamRecipientFilter( int team, bool isReliable ) -{ - if (isReliable) - MakeReliable(); - - RemoveAllRecipients(); - - for ( int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if ( !pPlayer ) - { - continue; - } - - if ( pPlayer->GetTeamNumber() != team ) - { - continue; - } - - AddRecipient( pPlayer ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : origin - -// ATTN_NORM - -//----------------------------------------------------------------------------- -void CPASAttenuationFilter::Filter( const Vector& origin, float attenuation /*= ATTN_NORM*/ ) -{ - // Don't crop for attenuation in single player - if ( gpGlobals->maxClients == 1 ) - return; - - // CPASFilter adds them by pure PVS in constructor - if ( attenuation <= 0 ) - return; - - // Now remove recipients that are outside sound radius - float distance, maxAudible; - Vector vecRelative; - - int c = GetRecipientCount(); - - for ( int i = c - 1; i >= 0; i-- ) - { - int index = GetRecipientIndex( i ); - - CBaseEntity *ent = CBaseEntity::Instance( index ); - if ( !ent || !ent->IsPlayer() ) - { - Assert( 0 ); - continue; - } - - CBasePlayer *player = ToBasePlayer( ent ); - if ( !player ) - { - Assert( 0 ); - continue; - } - -#ifndef _XBOX - // never remove the HLTV bot - if ( player->IsHLTV() ) - continue; -#endif - - VectorSubtract( player->EarPosition(), origin, vecRelative ); - distance = VectorLength( vecRelative ); - maxAudible = ( 2 * SOUND_NORMAL_CLIP_DIST ) / attenuation; - if ( distance <= maxAudible ) - continue; - - RemoveRecipient( player ); - } -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "recipientfilter.h" +#include "team.h" +#include "ipredictionsystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static IPredictionSystem g_RecipientFilterPredictionSystem; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRecipientFilter::CRecipientFilter() +{ + Reset(); +} + +CRecipientFilter::~CRecipientFilter() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : src - +//----------------------------------------------------------------------------- +void CRecipientFilter::CopyFrom( const CRecipientFilter& src ) +{ + m_bReliable = src.IsReliable(); + m_bInitMessage = src.IsInitMessage(); + + m_bUsingPredictionRules = src.IsUsingPredictionRules(); + m_bIgnorePredictionCull = src.IgnorePredictionCull(); + + int c = src.GetRecipientCount(); + for ( int i = 0; i < c; ++i ) + { + m_Recipients.AddToTail( src.GetRecipientIndex( i ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRecipientFilter::Reset( void ) +{ + m_bReliable = false; + m_bInitMessage = false; + m_Recipients.RemoveAll(); + m_bUsingPredictionRules = false; + m_bIgnorePredictionCull = false; +} + +void CRecipientFilter::MakeReliable( void ) +{ + m_bReliable = true; +} + +bool CRecipientFilter::IsReliable( void ) const +{ + return m_bReliable; +} + +int CRecipientFilter::GetRecipientCount( void ) const +{ + return m_Recipients.Size(); +} + +int CRecipientFilter::GetRecipientIndex( int slot ) const +{ + if ( slot < 0 || slot >= GetRecipientCount() ) + return -1; + + return m_Recipients[ slot ]; +} + +void CRecipientFilter::AddAllPlayers( void ) +{ + m_Recipients.RemoveAll(); + + int i; + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( !pPlayer ) + { + continue; + } + + AddRecipient( pPlayer ); + } +} + +void CRecipientFilter::AddRecipient( CBasePlayer *player ) +{ + Assert( player ); + + int index = player->entindex(); + + // If we're predicting and this is not the first time we've predicted this sound + // then don't send it to the local player again. + if ( m_bUsingPredictionRules ) + { + // Only add local player if this is the first time doing prediction + if ( g_RecipientFilterPredictionSystem.GetSuppressHost() == player ) + { + return; + } + } + + // Already in list + if ( m_Recipients.Find( index ) != m_Recipients.InvalidIndex() ) + return; + + m_Recipients.AddToTail( index ); +} + +void CRecipientFilter::RemoveAllRecipients( void ) +{ + m_Recipients.RemoveAll(); +} + +void CRecipientFilter::RemoveRecipient( CBasePlayer *player ) +{ + Assert( player ); + + int index = player->entindex(); + + // Remove it if it's in the list + m_Recipients.FindAndRemove( index ); +} + +void CRecipientFilter::AddRecipientsByTeam( CTeam *team ) +{ + Assert( team ); + + int i; + int c = team->GetNumPlayers(); + for ( i = 0 ; i < c ; i++ ) + { + CBasePlayer *player = team->GetPlayer( i ); + if ( !player ) + continue; + + AddRecipient( player ); + } +} + +void CRecipientFilter::RemoveRecipientsByTeam( CTeam *team ) +{ + Assert( team ); + + int i; + int c = team->GetNumPlayers(); + for ( i = 0 ; i < c ; i++ ) + { + CBasePlayer *player = team->GetPlayer( i ); + if ( !player ) + continue; + + RemoveRecipient( player ); + } +} + +void CRecipientFilter::AddPlayersFromBitMask( CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits ) +{ + int index = playerbits.FindNextSetBit( 0 ); + + while ( index > -1 ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( index + 1 ); + if ( pPlayer ) + { + AddRecipient( pPlayer ); + } + + index = playerbits.FindNextSetBit( index + 1 ); + } +} + +void CRecipientFilter::RemovePlayersFromBitMask( CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits ) +{ + int index = playerbits.FindNextSetBit( 0 ); + + while ( index > -1 ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( index + 1 ); + if ( pPlayer ) + { + RemoveRecipient( pPlayer ); + } + + index = playerbits.FindNextSetBit( index + 1 ); + } +} + +void CRecipientFilter::AddRecipientsByPVS( const Vector& origin ) +{ + if ( gpGlobals->maxClients == 1 ) + { + AddAllPlayers(); + } + else + { + CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; + engine->Message_DetermineMulticastRecipients( false, origin, playerbits ); + AddPlayersFromBitMask( playerbits ); + } +} + +void CRecipientFilter::RemoveRecipientsByPVS( const Vector& origin ) +{ + if ( gpGlobals->maxClients == 1 ) + { + m_Recipients.RemoveAll(); + } + else + { + CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; + engine->Message_DetermineMulticastRecipients( false, origin, playerbits ); + RemovePlayersFromBitMask( playerbits ); + } +} + + + +void CRecipientFilter::AddRecipientsByPAS( const Vector& origin ) +{ + if ( gpGlobals->maxClients == 1 ) + { + AddAllPlayers(); + } + else + { + CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; + engine->Message_DetermineMulticastRecipients( true, origin, playerbits ); + AddPlayersFromBitMask( playerbits ); + } +} + +bool CRecipientFilter::IsInitMessage( void ) const +{ + return m_bInitMessage; +} + +void CRecipientFilter::MakeInitMessage( void ) +{ + m_bInitMessage = true; +} + +void CRecipientFilter::UsePredictionRules( void ) +{ + if ( m_bUsingPredictionRules ) + return; + + m_bUsingPredictionRules = true; + + // Cull list now, if needed + if ( GetRecipientCount() == 0 ) + return; + + CBasePlayer *pPlayer = ToBasePlayer( (CBaseEntity*)g_RecipientFilterPredictionSystem.GetSuppressHost() ); + + if ( pPlayer) + { + RemoveRecipient( pPlayer ); + } +} + +bool CRecipientFilter::IsUsingPredictionRules( void ) const +{ + return m_bUsingPredictionRules; +} + +bool CRecipientFilter:: IgnorePredictionCull( void ) const +{ + return m_bIgnorePredictionCull; +} + +void CRecipientFilter::SetIgnorePredictionCull( bool ignore ) +{ + m_bIgnorePredictionCull = ignore; +} + +//----------------------------------------------------------------------------- +// Purpose: Simple class to create a filter for all players on a given team +//----------------------------------------------------------------------------- +CTeamRecipientFilter::CTeamRecipientFilter( int team, bool isReliable ) +{ + if (isReliable) + MakeReliable(); + + RemoveAllRecipients(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + { + continue; + } + + if ( pPlayer->GetTeamNumber() != team ) + { + continue; + } + + AddRecipient( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : origin - +// ATTN_NORM - +//----------------------------------------------------------------------------- +void CPASAttenuationFilter::Filter( const Vector& origin, float attenuation /*= ATTN_NORM*/ ) +{ + // Don't crop for attenuation in single player + if ( gpGlobals->maxClients == 1 ) + return; + + // CPASFilter adds them by pure PVS in constructor + if ( attenuation <= 0 ) + return; + + // Now remove recipients that are outside sound radius + float distance, maxAudible; + Vector vecRelative; + + int c = GetRecipientCount(); + + for ( int i = c - 1; i >= 0; i-- ) + { + int index = GetRecipientIndex( i ); + + CBaseEntity *ent = CBaseEntity::Instance( index ); + if ( !ent || !ent->IsPlayer() ) + { + Assert( 0 ); + continue; + } + + CBasePlayer *player = ToBasePlayer( ent ); + if ( !player ) + { + Assert( 0 ); + continue; + } + +#ifndef _XBOX + // never remove the HLTV bot + if ( player->IsHLTV() ) + continue; +#endif + + VectorSubtract( player->EarPosition(), origin, vecRelative ); + distance = VectorLength( vecRelative ); + maxAudible = ( 2 * SOUND_NORMAL_CLIP_DIST ) / attenuation; + if ( distance <= maxAudible ) + continue; + + RemoveRecipient( player ); + } +} diff --git a/dlls/sceneentity.cpp b/dlls/sceneentity.cpp index 2b28d625..64fba186 100644 --- a/dlls/sceneentity.cpp +++ b/dlls/sceneentity.cpp @@ -136,7 +136,7 @@ private: CRestoreSceneSound() { actor = NULL; - soundname[ 0 ] = NULL; + soundname[ 0 ] = 0; soundlevel = SNDLVL_NORM; time_in_past = 0.0f; } @@ -3143,9 +3143,9 @@ CChoreoScene *CSceneEntity::BlockingLoadScene( const char *filename ) Q_FixSlashes( loadfile ); // Load the file - char *buffer = NULL; + void *buffer = NULL; - int filesize = filesystem->ReadFileEx( loadfile, ( IsXbox() ) ? "XGAME" : "GAME", (void **)&buffer, true ); + int filesize = filesystem->ReadFileEx( loadfile, ( IsXbox() ) ? "XGAME" : "GAME", &buffer, true ); if ( filesize <= 0 ) { @@ -3153,10 +3153,10 @@ CChoreoScene *CSceneEntity::BlockingLoadScene( const char *filename ) return NULL; } - g_TokenProcessor.SetBuffer( buffer ); + g_TokenProcessor.SetBuffer( reinterpret_cast(buffer) ); CChoreoScene *scene = ChoreoLoadScene( loadfile, NULL, &g_TokenProcessor, LocalScene_Printf ); - delete[] buffer; + delete[] reinterpret_cast(buffer); return scene; } @@ -4242,7 +4242,7 @@ bool CopySceneFileIntoMemory( char const *filename, void **buffer ) //----------------------------------------------------------------------------- void FreeSceneFileMemory( void *buffer ) { - delete[] buffer; + delete[] reinterpret_cast(buffer); } CON_COMMAND( scene_mem, "Spew memory usage for .vcds in the instanced scene resource manager." ) @@ -4645,7 +4645,7 @@ void CSceneManager::PauseActorsScenes( CBaseFlex *pActor, bool bInstancedOnly ) if ( pScene->InvolvesActor( pActor ) && pScene->IsPlayingBack() ) { - LocalScene_Printf( "Pausing actor %s scripted scene: %s\n", pActor->GetDebugName(), pScene->m_iszSceneFile ); + LocalScene_Printf( "Pausing actor %s scripted scene: %s\n", pActor->GetDebugName(), STRING(pScene->m_iszSceneFile) ); variant_t emptyVariant; pScene->AcceptInput( "Pause", pScene, pScene, emptyVariant, 0 ); @@ -4702,7 +4702,7 @@ void CSceneManager::ResumeActorsScenes( CBaseFlex *pActor, bool bInstancedOnly if ( pScene->InvolvesActor( pActor ) && pScene->IsPlayingBack() ) { - LocalScene_Printf( "Resuming actor %s scripted scene: %s\n", pActor->GetDebugName(), pScene->m_iszSceneFile ); + LocalScene_Printf( "Resuming actor %s scripted scene: %s\n", pActor->GetDebugName(), STRING(pScene->m_iszSceneFile) ); variant_t emptyVariant; pScene->AcceptInput( "Resume", pScene, pScene, emptyVariant, 0 ); diff --git a/dlls/sceneentity.h b/dlls/sceneentity.h index 57f2fb5c..9faa6ce2 100644 --- a/dlls/sceneentity.h +++ b/dlls/sceneentity.h @@ -1,46 +1,46 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// -#ifndef SCENEENTITY_H -#define SCENEENTITY_H -#ifdef _WIN32 -#pragma once -#endif - -// List of the last 5 lines of speech from NPCs for bug reports -#define SPEECH_LIST_MAX_SOUNDS 5 - -class AI_Response; - -struct recentNPCSpeech_t -{ - float time; - char name[ 512 ]; - char sceneName[ 128 ]; -}; - -int GetRecentNPCSpeech( recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ] ); -float InstancedScriptedScene( CBaseFlex *pActor, const char *pszScene, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL ); -float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt = NULL ); -void StopScriptedScene( CBaseFlex *pActor, EHANDLE hSceneEnt ); -void RemoveActorFromScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly, bool nonidlescenesonly = false, const char *pszThisSceneOnly = NULL ); -void PauseActorsScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly ); -void ResumeActorsScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly ); -void QueueActorsScriptedScenesToResume( CBaseFlex *pActor, bool instancedscenesonly ); -bool IsRunningScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes = true ); -bool IsRunningScriptedSceneWithSpeech( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); -float GetSceneDuration( char const *pszScene ); -int GetSceneSpeechCount( char const *pszScene ); -bool IsInInterruptableScenes( CBaseFlex *pActor ); - -void PrecacheInstancedScene( char const *pszScene ); -void ResetPrecacheInstancedSceneDictionary(); - -char const *GetSceneFilename( CBaseEntity *ent ); -void ReloadSceneFromDisk( CBaseEntity *ent ); - - -#endif // SCENEENTITY_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef SCENEENTITY_H +#define SCENEENTITY_H +#ifdef _WIN32 +#pragma once +#endif + +// List of the last 5 lines of speech from NPCs for bug reports +#define SPEECH_LIST_MAX_SOUNDS 5 + +class AI_Response; + +struct recentNPCSpeech_t +{ + float time; + char name[ 512 ]; + char sceneName[ 128 ]; +}; + +int GetRecentNPCSpeech( recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ] ); +float InstancedScriptedScene( CBaseFlex *pActor, const char *pszScene, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL ); +float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt = NULL ); +void StopScriptedScene( CBaseFlex *pActor, EHANDLE hSceneEnt ); +void RemoveActorFromScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly, bool nonidlescenesonly = false, const char *pszThisSceneOnly = NULL ); +void PauseActorsScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly ); +void ResumeActorsScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly ); +void QueueActorsScriptedScenesToResume( CBaseFlex *pActor, bool instancedscenesonly ); +bool IsRunningScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes = true ); +bool IsRunningScriptedSceneWithSpeech( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); +float GetSceneDuration( char const *pszScene ); +int GetSceneSpeechCount( char const *pszScene ); +bool IsInInterruptableScenes( CBaseFlex *pActor ); + +void PrecacheInstancedScene( char const *pszScene ); +void ResetPrecacheInstancedSceneDictionary(); + +char const *GetSceneFilename( CBaseEntity *ent ); +void ReloadSceneFromDisk( CBaseEntity *ent ); + + +#endif // SCENEENTITY_H diff --git a/dlls/scripted.cpp b/dlls/scripted.cpp index 1d2a2a15..16377622 100644 --- a/dlls/scripted.cpp +++ b/dlls/scripted.cpp @@ -745,7 +745,7 @@ void CAI_ScriptedSequence::ScriptThink( void ) else if (FindEntity()) { StartScript( ); - DevMsg( 2, "scripted_sequence \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING( m_iszEntity ), GetTarget()->GetEntityName() ); + DevMsg( 2, "scripted_sequence \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING( m_iszEntity ), STRING(GetTarget()->GetEntityName()) ); } else { @@ -1616,7 +1616,7 @@ void CAI_ScriptedSchedule::ScriptThink( void ) pTarget = FindScriptEntity( (m_spawnflags & SF_SCRIPT_SEARCH_CYCLICALLY) != 0 ); if ( pTarget ) { - DevMsg( 2, "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING( m_iszEntity ), pTarget->GetEntityName() ); + DevMsg( 2, "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING( m_iszEntity ), STRING(pTarget->GetEntityName()) ); StartSchedule( pTarget ); success = true; } @@ -1626,7 +1626,7 @@ void CAI_ScriptedSchedule::ScriptThink( void ) m_hLastFoundEntity = NULL; while ( ( pTarget = FindScriptEntity( true ) ) != NULL ) { - DevMsg( 2, "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), pTarget->GetEntityName(), STRING( m_iszEntity ) ); + DevMsg( 2, "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING(pTarget->GetEntityName()), STRING( m_iszEntity ) ); StartSchedule( pTarget ); success = true; } @@ -1714,7 +1714,7 @@ void CAI_ScriptedSchedule::StartSchedule( CAI_BaseNPC *pTarget ) pTarget->ForceDecisionThink(); - Assert( m_nForceState >= 0 && m_nForceState < ARRAYSIZE(forcedStatesMap) ); + Assert( m_nForceState >= 0 && m_nForceState < static_cast(ARRAYSIZE(forcedStatesMap)) ); NPC_STATE forcedState = forcedStatesMap[m_nForceState]; @@ -1790,6 +1790,8 @@ void CAI_ScriptedSchedule::StartSchedule( CAI_BaseNPC *pTarget ) bDidSetSchedule = true; break; } + default: + break; } if ( bDidSetSchedule ) diff --git a/dlls/sdk/sdk_bot_temp.cpp b/dlls/sdk/sdk_bot_temp.cpp index 563f9564..de10ff8c 100644 --- a/dlls/sdk/sdk_bot_temp.cpp +++ b/dlls/sdk/sdk_bot_temp.cpp @@ -253,7 +253,7 @@ void Bot_UpdateDirection( CSDKBot *pBot ) float angledelta = 15.0; QAngle angle; - int maxtries = (int)360.0/angledelta; + int maxtries = static_cast(360.0 / angledelta); if ( pBot->m_bLastTurnToRight ) { diff --git a/dlls/sdk/sdk_env_sparkler.cpp b/dlls/sdk/sdk_env_sparkler.cpp index 9dbbb977..5d8a303a 100644 --- a/dlls/sdk/sdk_env_sparkler.cpp +++ b/dlls/sdk/sdk_env_sparkler.cpp @@ -1,104 +1,104 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: A simple test entity for creating special effects -// -//=============================================================================// - -#include "cbase.h" -#include "te_effect_dispatch.h" - -// Declare the sparkler entity for the server-side -class CSparkler : public CBaseEntity -{ -public: - DECLARE_SERVERCLASS(); - DECLARE_CLASS( CSparkler, CBaseEntity ); - - void Spawn( void ); - - void InputToggle( inputdata_t &input ); // Input function for toggling our effect's state - void InputScale( inputdata_t &input ); - -private: - CNetworkVar( bool, m_bEmit ); // Marks whether the effect should be active or not - CNetworkVar( float, m_flScale ); // The size and speed of the effect - - DECLARE_DATADESC(); -}; - -// Link our class to the "env_sparkler" entity classname -LINK_ENTITY_TO_CLASS( env_sparkler, CSparkler ); - -// Declare our data description for this entity -BEGIN_DATADESC( CSparkler ) - DEFINE_FIELD( m_bEmit, FIELD_BOOLEAN ), - - DEFINE_KEYFIELD( m_flScale, FIELD_FLOAT, "scale" ), - - DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), // Declare our toggle input function - DEFINE_INPUTFUNC( FIELD_FLOAT, "Scale", InputScale ), -END_DATADESC() - -// Declare the data-table for server/client communication -IMPLEMENT_SERVERCLASS_ST( CSparkler, DT_Sparkler ) - SendPropInt( SENDINFO( m_bEmit ), 1, SPROP_UNSIGNED ), // Declare our boolean state variable - SendPropFloat( SENDINFO( m_flScale ), 0, SPROP_NOSCALE ), -END_SEND_TABLE() - -//----------------------------------------------------------------------------- -// Purpose: Spawn function for this entity -//----------------------------------------------------------------------------- -void CSparkler::Spawn( void ) -{ - SetMoveType( MOVETYPE_NONE ); // Will not move on its own - SetSolid( SOLID_NONE ); // Will not collide with anything - - // Set a size for culling - UTIL_SetSize( this, -Vector(2,2,2), Vector(2,2,2) ); - - // We must add this flag to receive network transmitions - AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); -} - -//----------------------------------------------------------------------------- -// Purpose: Toggles the emission state of the effect -//----------------------------------------------------------------------------- -void CSparkler::InputToggle( inputdata_t &input ) -{ - // Toggle our state - m_bEmit = !m_bEmit; -} - -//----------------------------------------------------------------------------- -// Purpose: Change our scale via a float value -//----------------------------------------------------------------------------- -void CSparkler::InputScale( inputdata_t &input ) -{ - // Change our scale - m_flScale = input.value.Float(); -} - -// ============================================================================ -// -// Dispatch Effect version -// -// ============================================================================ - -//----------------------------------------------------------------------------- -// Purpose: Create a sparkle effect at the given location of the given size -// Input : &position - Where to emit from -// flSize - Size of the effect -//----------------------------------------------------------------------------- -void MakeSparkle( const Vector &origin, float flScale ) -{ - CEffectData data; - - // Send our origin - data.m_vOrigin = origin; - - // Send our scale - data.m_flScale = flScale; - - // Send the effect off to the client - DispatchEffect( "Sparkle", data ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: A simple test entity for creating special effects +// +//=============================================================================// + +#include "cbase.h" +#include "te_effect_dispatch.h" + +// Declare the sparkler entity for the server-side +class CSparkler : public CBaseEntity +{ +public: + DECLARE_SERVERCLASS(); + DECLARE_CLASS( CSparkler, CBaseEntity ); + + void Spawn( void ); + + void InputToggle( inputdata_t &input ); // Input function for toggling our effect's state + void InputScale( inputdata_t &input ); + +private: + CNetworkVar( bool, m_bEmit ); // Marks whether the effect should be active or not + CNetworkVar( float, m_flScale ); // The size and speed of the effect + + DECLARE_DATADESC(); +}; + +// Link our class to the "env_sparkler" entity classname +LINK_ENTITY_TO_CLASS( env_sparkler, CSparkler ); + +// Declare our data description for this entity +BEGIN_DATADESC( CSparkler ) + DEFINE_FIELD( m_bEmit, FIELD_BOOLEAN ), + + DEFINE_KEYFIELD( m_flScale, FIELD_FLOAT, "scale" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), // Declare our toggle input function + DEFINE_INPUTFUNC( FIELD_FLOAT, "Scale", InputScale ), +END_DATADESC() + +// Declare the data-table for server/client communication +IMPLEMENT_SERVERCLASS_ST( CSparkler, DT_Sparkler ) + SendPropInt( SENDINFO( m_bEmit ), 1, SPROP_UNSIGNED ), // Declare our boolean state variable + SendPropFloat( SENDINFO( m_flScale ), 0, SPROP_NOSCALE ), +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: Spawn function for this entity +//----------------------------------------------------------------------------- +void CSparkler::Spawn( void ) +{ + SetMoveType( MOVETYPE_NONE ); // Will not move on its own + SetSolid( SOLID_NONE ); // Will not collide with anything + + // Set a size for culling + UTIL_SetSize( this, -Vector(2,2,2), Vector(2,2,2) ); + + // We must add this flag to receive network transmitions + AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggles the emission state of the effect +//----------------------------------------------------------------------------- +void CSparkler::InputToggle( inputdata_t &input ) +{ + // Toggle our state + m_bEmit = !m_bEmit; +} + +//----------------------------------------------------------------------------- +// Purpose: Change our scale via a float value +//----------------------------------------------------------------------------- +void CSparkler::InputScale( inputdata_t &input ) +{ + // Change our scale + m_flScale = input.value.Float(); +} + +// ============================================================================ +// +// Dispatch Effect version +// +// ============================================================================ + +//----------------------------------------------------------------------------- +// Purpose: Create a sparkle effect at the given location of the given size +// Input : &position - Where to emit from +// flSize - Size of the effect +//----------------------------------------------------------------------------- +void MakeSparkle( const Vector &origin, float flScale ) +{ + CEffectData data; + + // Send our origin + data.m_vOrigin = origin; + + // Send our scale + data.m_flScale = flScale; + + // Send the effect off to the client + DispatchEffect( "Sparkle", data ); +} diff --git a/dlls/sdk/sdk_logicalentity.cpp b/dlls/sdk/sdk_logicalentity.cpp index aee13ef6..6d181fec 100644 --- a/dlls/sdk/sdk_logicalentity.cpp +++ b/dlls/sdk/sdk_logicalentity.cpp @@ -1,66 +1,66 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Simple logical entity that counts up to a threshold value, then -// fires an output when reached. -// -//=============================================================================// - -#include "cbase.h" - -class CMyLogicalEntity : public CLogicalEntity -{ -public: - DECLARE_CLASS( CMyLogicalEntity , CLogicalEntity ); - DECLARE_DATADESC(); - - // Constructor - CMyLogicalEntity ( void ) : m_nCounter( 0 ) {} - - // Input function - void InputTick( inputdata_t &inputData ); - -private: - - int m_nThreshold; // Count at which to fire our output - int m_nCounter; // Internal counter - - COutputEvent m_OnThreshold; // Output even when the counter reaches the threshold -}; - -LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity ); - -// Start of our data description for the class -BEGIN_DATADESC( CMyLogicalEntity ) - - // For save/load - DEFINE_FIELD( m_nCounter, FIELD_INTEGER ), - - // Links our member variable to our keyvalue from Hammer - DEFINE_KEYFIELD( m_nThreshold, FIELD_INTEGER, "threshold" ), - - // Links our input name from Hammer to our input member function - DEFINE_INPUTFUNC( FIELD_VOID, "Tick", InputTick ), - - // Links our output member to the output name used by Hammer - DEFINE_OUTPUT( m_OnThreshold, "OnThreshold" ), - -END_DATADESC() - -//----------------------------------------------------------------------------- -// Purpose: Handle a tick input from another entity -//----------------------------------------------------------------------------- -void CMyLogicalEntity ::InputTick( inputdata_t &inputData ) -{ - // Increment our counter - m_nCounter++; - - // See if we've met or crossed our threshold value - if ( m_nCounter >= m_nThreshold ) - { - // Fire an output event - m_OnThreshold.FireOutput( inputData.pActivator, this ); - - // Reset our counter - m_nCounter = 0; - } -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Simple logical entity that counts up to a threshold value, then +// fires an output when reached. +// +//=============================================================================// + +#include "cbase.h" + +class CMyLogicalEntity : public CLogicalEntity +{ +public: + DECLARE_CLASS( CMyLogicalEntity , CLogicalEntity ); + DECLARE_DATADESC(); + + // Constructor + CMyLogicalEntity ( void ) : m_nCounter( 0 ) {} + + // Input function + void InputTick( inputdata_t &inputData ); + +private: + + int m_nThreshold; // Count at which to fire our output + int m_nCounter; // Internal counter + + COutputEvent m_OnThreshold; // Output even when the counter reaches the threshold +}; + +LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity ); + +// Start of our data description for the class +BEGIN_DATADESC( CMyLogicalEntity ) + + // For save/load + DEFINE_FIELD( m_nCounter, FIELD_INTEGER ), + + // Links our member variable to our keyvalue from Hammer + DEFINE_KEYFIELD( m_nThreshold, FIELD_INTEGER, "threshold" ), + + // Links our input name from Hammer to our input member function + DEFINE_INPUTFUNC( FIELD_VOID, "Tick", InputTick ), + + // Links our output member to the output name used by Hammer + DEFINE_OUTPUT( m_OnThreshold, "OnThreshold" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Handle a tick input from another entity +//----------------------------------------------------------------------------- +void CMyLogicalEntity ::InputTick( inputdata_t &inputData ) +{ + // Increment our counter + m_nCounter++; + + // See if we've met or crossed our threshold value + if ( m_nCounter >= m_nThreshold ) + { + // Fire an output event + m_OnThreshold.FireOutput( inputData.pActivator, this ); + + // Reset our counter + m_nCounter = 0; + } +} diff --git a/dlls/sdk/sdk_vehicle_jeep.cpp b/dlls/sdk/sdk_vehicle_jeep.cpp index dc0917ee..5dada6b4 100644 --- a/dlls/sdk/sdk_vehicle_jeep.cpp +++ b/dlls/sdk/sdk_vehicle_jeep.cpp @@ -1438,8 +1438,8 @@ void CPropJeep::CreateDangerSounds( void ) const float radius = speed * 0.4; // 0.3 seconds ahead of the jeep vecSpot = vecStart + vecDir * (speed * 0.3f); - CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, radius, soundDuration, this, 0 ); - CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, radius, soundDuration, this, 1 ); + CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, static_cast(radius), soundDuration, this, 0 ); + CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, static_cast(radius), soundDuration, this, 1 ); //NDebugOverlay::Box(vecSpot, Vector(-radius,-radius,-radius),Vector(radius,radius,radius), 255, 0, 255, 0, soundDuration); #if 0 diff --git a/dlls/sendproxy.cpp b/dlls/sendproxy.cpp index 8b16a7f3..0f47d33e 100644 --- a/dlls/sendproxy.cpp +++ b/dlls/sendproxy.cpp @@ -18,7 +18,8 @@ void SendProxy_Color32ToInt( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ) { color32 *pIn = (color32*)pData; - *((unsigned int*)&pOut->m_Int) = ((unsigned int)pIn->r << 24) | ((unsigned int)pIn->g << 16) | ((unsigned int)pIn->b << 8) | ((unsigned int)pIn->a); + void *out = &pOut->m_Int; + *reinterpret_cast(out) = ((unsigned int)pIn->r << 24) | ((unsigned int)pIn->g << 16) | ((unsigned int)pIn->b << 8) | ((unsigned int)pIn->a); } void SendProxy_EHandleToInt( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID) @@ -106,7 +107,7 @@ REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_OnlyToTeam ); #define TIME_BITS 24 // This table encodes edict data. -static void SendProxy_Time( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID ) +/*static void SendProxy_Time( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID ) { float clock_base = floor( gpGlobals->curtime ); float t = *( float * )pVarData; @@ -118,7 +119,7 @@ static void SendProxy_Time( const SendProp *pProp, const void *pStruct, const vo addt = clamp( addt, -maxoffset, maxoffset ); pOut->m_Int = addt; -} +}*/ //----------------------------------------------------------------------------- // Purpose: diff --git a/dlls/server_hl2-2003.vcproj b/dlls/server_hl2-2003.vcproj index 24e46ad0..2cff11a6 100644 --- a/dlls/server_hl2-2003.vcproj +++ b/dlls/server_hl2-2003.vcproj @@ -22,10 +22,10 @@ Name="VCCLCompilerTool" Optimization="0" OptimizeForProcessor="2" - AdditionalIncludeDirectories="../game_shared/hl2,./,../Public,../Public/tier1,../game_shared,../utils/common,../dlls,../../dlls,../dlls/hl2_dll,./episodic" - PreprocessorDefinitions="HL2_EPISODIC;HL2_DLL;USES_SAVERESTORE;_DEBUG;fopen=dont_use_fopen;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" + AdditionalIncludeDirectories="../game_shared/hl2,./,../Public,../Public/tier1,../game_shared,../utils/common,../dlls,../../dlls,../dlls/hl2_dll,../dlls/episodic" + PreprocessorDefinitions="HL2_EPISODIC;HL2_DLL;USES_SAVERESTORE;_DEBUG;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" ExceptionHandling="FALSE" - RuntimeLibrary="5" + RuntimeLibrary="1" ForceConformanceInForLoopScope="TRUE" RuntimeTypeInfo="TRUE" UsePrecompiledHeader="3" @@ -54,7 +54,7 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMT,LIBCMTD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/server.pdb" SubSystem="2" @@ -107,11 +107,11 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. EnableIntrinsicFunctions="TRUE" FavorSizeOrSpeed="1" OptimizeForProcessor="3" - AdditionalIncludeDirectories="../game_shared/hl2,./,../Public,../Public/tier1,../game_shared,../utils/common,../dlls,../../dlls,../dlls/hl2_dll,./episodic" + AdditionalIncludeDirectories="../game_shared/hl2,./,../Public,../Public/tier1,../game_shared,../utils/common,../dlls,../../dlls,../dlls/hl2_dll,../dlls/episodic" PreprocessorDefinitions="HL2_EPISODIC;HL2_DLL;USES_SAVERESTORE;NDEBUG;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" StringPooling="TRUE" ExceptionHandling="FALSE" - RuntimeLibrary="4" + RuntimeLibrary="0" BufferSecurityCheck="FALSE" EnableFunctionLevelLinking="TRUE" ForceConformanceInForLoopScope="TRUE" @@ -145,7 +145,7 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMT,LIBCMTD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/server.pdb" GenerateMapFile="TRUE" @@ -3185,18 +3185,6 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. - - - - - - @@ -4110,24 +4098,28 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/dlls/server_hl2-2005.vcproj b/dlls/server_hl2-2005.vcproj index 26163358..a2271001 100644 --- a/dlls/server_hl2-2005.vcproj +++ b/dlls/server_hl2-2005.vcproj @@ -50,8 +50,8 @@ - - - - - - - - - - - - - - - + - - - - - - - - - + - - - + + + + + + + - - - + + + + + + + + + + + + diff --git a/dlls/server_hl2mp-2003.vcproj b/dlls/server_hl2mp-2003.vcproj index fc36efad..940a7532 100644 --- a/dlls/server_hl2mp-2003.vcproj +++ b/dlls/server_hl2mp-2003.vcproj @@ -22,10 +22,10 @@ Name="VCCLCompilerTool" Optimization="0" OptimizeForProcessor="2" - AdditionalIncludeDirectories="../game_shared/hl2,./,../Public,../Public/tier1,../game_shared,../utils/common,../dlls,../../dlls,../dlls/hl2_dll,../dlls/hl2mp_dll,../game_shared/hl2mp,./episodic" - PreprocessorDefinitions="HL2_EPISODIC;HL2MP;HL2_DLL;USES_SAVERESTORE;_DEBUG;fopen=dont_use_fopen;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" + AdditionalIncludeDirectories="../game_shared/hl2,./,../Public,../Public/tier1,../game_shared,../utils/common,../dlls,../../dlls,../dlls/hl2_dll,../dlls/hl2mp_dll,../game_shared/hl2mp,../dlls/episodic" + PreprocessorDefinitions="HL2_EPISODIC;HL2MP;HL2_DLL;USES_SAVERESTORE;_DEBUG;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" ExceptionHandling="FALSE" - RuntimeLibrary="5" + RuntimeLibrary="1" ForceConformanceInForLoopScope="TRUE" RuntimeTypeInfo="TRUE" UsePrecompiledHeader="3" @@ -54,7 +54,7 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMT,LIBCMTD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/server.pdb" SubSystem="2" @@ -107,11 +107,11 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. EnableIntrinsicFunctions="TRUE" FavorSizeOrSpeed="1" OptimizeForProcessor="3" - AdditionalIncludeDirectories="../game_shared/hl2,./,../Public,../Public/tier1,../game_shared,../utils/common,../dlls,../../dlls,../dlls/hl2_dll,../dlls/hl2mp_dll,../game_shared/hl2mp,./episodic" + AdditionalIncludeDirectories="../game_shared/hl2,./,../Public,../Public/tier1,../game_shared,../utils/common,../dlls,../../dlls,../dlls/hl2_dll,../dlls/hl2mp_dll,../game_shared/hl2mp,../dlls/episodic" PreprocessorDefinitions="HL2_EPISODIC;HL2MP;HL2_DLL;USES_SAVERESTORE;NDEBUG;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" StringPooling="TRUE" ExceptionHandling="FALSE" - RuntimeLibrary="4" + RuntimeLibrary="0" BufferSecurityCheck="FALSE" EnableFunctionLevelLinking="TRUE" ForceConformanceInForLoopScope="TRUE" @@ -145,7 +145,7 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMT,LIBCMTD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/server.pdb" GenerateMapFile="TRUE" @@ -3204,18 +3204,6 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. - - - - - - @@ -3439,36 +3427,12 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. Filter=""> - - - - - - - - - - - - @@ -3477,82 +3441,22 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. RelativePath=".\hl2mp_dll\te_hl2mp_shotgun_shot.h"> - - - - - - + RelativePath="..\game_shared\hl2mp\weapon_357.cpp"> - - - - - - - - - - - - - - - - - - - - - - - - @@ -3580,117 +3484,33 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -4365,24 +4185,28 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. RelativePath="..\public\zip_uncompressed.h"> - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/dlls/server_hl2mp-2005.vcproj b/dlls/server_hl2mp-2005.vcproj index f4471409..d81c1d7f 100644 --- a/dlls/server_hl2mp-2005.vcproj +++ b/dlls/server_hl2mp-2005.vcproj @@ -50,8 +50,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -5798,66 +5558,70 @@ > - - - - - - - - - + - - - - - - - - - + - - - + + + + + + + - - - + + + + + + + + + + + + diff --git a/dlls/server_scratch-2003.vcproj b/dlls/server_scratch-2003.vcproj index 244a6a03..c678c643 100644 --- a/dlls/server_scratch-2003.vcproj +++ b/dlls/server_scratch-2003.vcproj @@ -30,7 +30,7 @@ PreprocessorDefinitions="NDEBUG;BOTS;SDK_DLL;USES_SAVERESTORE;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" StringPooling="TRUE" ExceptionHandling="FALSE" - RuntimeLibrary="4" + RuntimeLibrary="0" BufferSecurityCheck="FALSE" EnableFunctionLevelLinking="TRUE" ForceConformanceInForLoopScope="TRUE" @@ -64,7 +64,7 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMT,LIBCMTD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/server.pdb" GenerateMapFile="TRUE" @@ -115,9 +115,9 @@ if exist "$(TargetDir)"server.pdb copy "$(TargetDir)"server. Optimization="0" OptimizeForProcessor="2" AdditionalIncludeDirectories="../Public/tier1;../game_shared/sdk;./;../Public;../game_shared;../utils/common;../dlls;../dlls/sdk" - PreprocessorDefinitions="_DEBUG;fopen=dont_use_fopen;BOTS;SDK_DLL;USES_SAVERESTORE;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" + PreprocessorDefinitions="_DEBUG;BOTS;SDK_DLL;USES_SAVERESTORE;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" ExceptionHandling="FALSE" - RuntimeLibrary="5" + RuntimeLibrary="1" ForceConformanceInForLoopScope="TRUE" RuntimeTypeInfo="TRUE" UsePrecompiledHeader="3" @@ -147,7 +147,7 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. LinkIncremental="2" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="..\lib-vc7\public" - IgnoreDefaultLibraryNames="LIBCMT,LIBCMTD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" GenerateDebugInformation="TRUE" ProgramDatabaseFile="$(IntDir)/server.pdb" SubSystem="2" @@ -2694,16 +2694,6 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. - - - - - - @@ -2728,16 +2718,6 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. - - - - - - @@ -2750,16 +2730,6 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. - - - - - - @@ -2772,16 +2742,6 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. - - - - - - @@ -2791,48 +2751,18 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. - - - - - - - - - - - - - - - - - - @@ -2845,60 +2775,18 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. - - - - - - - - - - - - - - - - - - - - - - - - @@ -2911,31 +2799,9 @@ if exist "$(TargetDir)"server.dll copy "$(TargetDir)"server. - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/dlls/server_scratch-2005.vcproj b/dlls/server_scratch-2005.vcproj index 25b197e4..79aa5ce6 100644 --- a/dlls/server_scratch-2005.vcproj +++ b/dlls/server_scratch-2005.vcproj @@ -94,7 +94,7 @@ LinkIncremental="1" SuppressStartupBanner="true" AdditionalLibraryDirectories="..\lib\public" - IgnoreDefaultLibraryNames="LIBC,LIBCD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMTD" GenerateDebugInformation="true" ProgramDatabaseFile="$(IntDir)/server.pdb" GenerateMapFile="true" @@ -166,7 +166,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="../Public/tier1;../game_shared/sdk;./;../Public;../game_shared;../utils/common;../dlls;../dlls/sdk" - PreprocessorDefinitions="_DEBUG;fopen=dont_use_fopen;BOTS;SDK_DLL;USES_SAVERESTORE;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" + PreprocessorDefinitions="_DEBUG;BOTS;SDK_DLL;USES_SAVERESTORE;GAME_DLL;sprintf=use_Q_snprintf_instead_of_sprintf;_WINDOWS;VECTOR;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead;_WIN32;PROTECTED_THINGS_ENABLE" ExceptionHandling="0" RuntimeLibrary="1" ForceConformanceInForLoopScope="true" @@ -204,7 +204,7 @@ LinkIncremental="2" SuppressStartupBanner="true" AdditionalLibraryDirectories="..\lib\public" - IgnoreDefaultLibraryNames="LIBC,LIBCD" + IgnoreDefaultLibraryNames="LIBC,LIBCD,LIBCMT" GenerateDebugInformation="true" ProgramDatabaseFile="$(IntDir)/server.pdb" SubSystem="2" @@ -3573,20 +3573,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - + - - - + + + + + + + - - - + + + + + + + + + + + + diff --git a/dlls/sound.cpp b/dlls/sound.cpp index 87dc8a28..9fedb54f 100644 --- a/dlls/sound.cpp +++ b/dlls/sound.cpp @@ -356,7 +356,7 @@ void CAmbientGeneric::ComputeMaxAudibleDistance( ) //----------------------------------------------------------------------------- void CAmbientGeneric::InputPitch( inputdata_t &inputdata ) { - m_dpv.pitch = clamp( inputdata.value.Float(), 0, 255 ); + m_dpv.pitch = clamp( inputdata.value.Int(), 0, 255 ); SendSound( SND_CHANGE_PITCH ); } @@ -371,7 +371,7 @@ void CAmbientGeneric::InputVolume( inputdata_t &inputdata ) // // Multiply the input value by ten since volumes are expected to be from 0 - 100. // - m_dpv.vol = clamp( inputdata.value.Float(), 0, 10 ) * 10; + m_dpv.vol = static_cast(clamp( inputdata.value.Float(), 0, 10 ) * 10); m_dpv.volfrac = m_dpv.vol << 8; SendSound( SND_CHANGE_VOL ); @@ -387,7 +387,7 @@ void CAmbientGeneric::InputFadeIn( inputdata_t &inputdata ) // cancel any fade out that might be happening m_dpv.fadeout = 0; - m_dpv.fadein = inputdata.value.Float(); + m_dpv.fadein = inputdata.value.Int(); if (m_dpv.fadein > 100) m_dpv.fadein = 100; if (m_dpv.fadein < 0) m_dpv.fadein = 0; @@ -407,7 +407,7 @@ void CAmbientGeneric::InputFadeOut( inputdata_t &inputdata ) // cancel any fade in that might be happening m_dpv.fadein = 0; - m_dpv.fadeout = inputdata.value.Float(); + m_dpv.fadeout = inputdata.value.Int(); if (m_dpv.fadeout > 100) m_dpv.fadeout = 100; if (m_dpv.fadeout < 0) m_dpv.fadeout = 0; diff --git a/dlls/soundent.cpp b/dlls/soundent.cpp index 36c8b1b8..4b64473a 100644 --- a/dlls/soundent.cpp +++ b/dlls/soundent.cpp @@ -514,7 +514,7 @@ void CSoundEnt::Initialize ( void ) int i; int iSound; - m_cLastActiveSounds; + m_cLastActiveSounds = 0; m_iFreeSound = 0; m_iActiveSound = SOUNDLIST_EMPTY; diff --git a/dlls/soundscape.cpp b/dlls/soundscape.cpp index 3932dd4e..f27767da 100644 --- a/dlls/soundscape.cpp +++ b/dlls/soundscape.cpp @@ -50,7 +50,7 @@ void CEnvSoundscapeProxy::Activate() { // Copy the relevant parameters from our main soundscape. m_soundscapeIndex = m_hProxySoundscape->m_soundscapeIndex; - for ( int i=0; i < ARRAYSIZE( m_positionNames ); i++ ) + for ( size_t i=0; i < ARRAYSIZE( m_positionNames ); i++ ) m_positionNames[i] = m_hProxySoundscape->m_positionNames[i]; } else @@ -223,7 +223,7 @@ void CEnvSoundscape::WriteAudioParamsTo( audioparams_t &audio ) audio.ent.Set( this ); audio.soundscapeIndex = m_soundscapeIndex; audio.localBits = 0; - for ( int i = 0; i < ARRAYSIZE(m_positionNames); i++ ) + for ( int i = 0; i < static_cast(ARRAYSIZE(m_positionNames)); i++ ) { if ( m_positionNames[i] != NULL_STRING ) { diff --git a/dlls/soundscape_system.cpp b/dlls/soundscape_system.cpp index 49814ef0..7985bc55 100644 --- a/dlls/soundscape_system.cpp +++ b/dlls/soundscape_system.cpp @@ -1,300 +1,300 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - - -#include "cbase.h" -#include "soundscape_system.h" -#include "soundscape.h" -#include "KeyValues.h" -#include "filesystem.h" -#include "game.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define SOUNDSCAPE_MANIFEST_FILE "scripts/soundscapes_manifest.txt" - -CON_COMMAND(soundscape_flush, "Flushes the server & client side soundscapes") -{ - g_SoundscapeSystem.FlushSoundscapes(); // don't bother forgetting about the entities - g_SoundscapeSystem.Init(); - engine->ClientCommand( UTIL_GetCommandClient()->edict(), "cl_soundscape_flush\n" ); -} - -CSoundscapeSystem g_SoundscapeSystem( "CSoundscapeSystem" ); - - -void CSoundscapeSystem::AddSoundscapeFile( const char *filename ) -{ - MEM_ALLOC_CREDIT(); - // Open the soundscape data file, and abort if we can't - KeyValues *pKeyValuesData = new KeyValues( filename ); - if ( filesystem->LoadKeyValues( *pKeyValuesData, IFileSystem::TYPE_SOUNDSCAPE, filename, "GAME" ) ) - { - // parse out all of the top level sections and save their names - KeyValues *pKeys = pKeyValuesData; - while ( pKeys ) - { - if ( pKeys->GetFirstSubKey() ) - { - if ( g_pDeveloper->GetBool() ) - { - if ( strstr( pKeys->GetName(), "{" ) ) - { - Msg("Error parsing soundscape file %s after %s\n", filename, m_soundscapeCount>0 ?m_soundscapes.GetStringText( m_soundscapeCount-1 ) : "FIRST" ); - } - } - m_soundscapes.AddString( pKeys->GetName(), m_soundscapeCount ); -#ifdef _XBOX - const char *pStr = pKeys->GetName(); - AddSoundscapeSounds( pKeys, m_soundscapeCount ); -#endif - m_soundscapeCount++; - } - pKeys = pKeys->GetNextKey(); - } - } - pKeyValuesData->deleteThis(); -} - -CON_COMMAND(sv_soundscape_printdebuginfo, "print soundscapes") -{ - g_SoundscapeSystem.PrintDebugInfo(); -} - - -void CSoundscapeSystem::PrintDebugInfo() -{ - Msg( "\n------- SERVER SOUNDSCAPES -------\n" ); - for ( int key=m_soundscapes.First(); key != m_soundscapes.InvalidIndex(); key = m_soundscapes.Next( key ) ) - { - int id = m_soundscapes.GetIDForKey( key ); - const char *pName = m_soundscapes.GetStringForKey( key ); - - Msg( "- %d: %s\n", id, pName ); - } - Msg( "-------- SOUNDSCAPE ENTITIES -----\n" ); - for( int entityIndex = 0; entityIndex < m_soundscapeEntities.Size(); ++entityIndex ) - { - CEnvSoundscape *currentSoundscape = m_soundscapeEntities[entityIndex]; - Msg("- %d: %s x:%.4f y:%.4f z:%.4f\n", - entityIndex, - currentSoundscape->GetSoundscapeName(), - currentSoundscape->GetAbsOrigin().x, - currentSoundscape->GetAbsOrigin().y, - currentSoundscape->GetAbsOrigin().z - ); - } - Msg( "----------------------------------\n\n" ); -} - -bool CSoundscapeSystem::Init() -{ - m_soundscapeCount = 0; - - const char *mapname = STRING( gpGlobals->mapname ); - const char *mapSoundscapeFilename = NULL; - if ( mapname && *mapname ) - { - mapSoundscapeFilename = UTIL_VarArgs( "scripts/soundscapes_%s.txt", mapname ); - } - - KeyValues *manifest = new KeyValues( SOUNDSCAPE_MANIFEST_FILE ); - if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDSCAPE, SOUNDSCAPE_MANIFEST_FILE, "GAME" ) ) - { - for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) - { - if ( !Q_stricmp( sub->GetName(), "file" ) ) - { - // Add - AddSoundscapeFile( sub->GetString() ); - if ( mapSoundscapeFilename && FStrEq( sub->GetString(), mapSoundscapeFilename ) ) - { - mapSoundscapeFilename = NULL; // we've already loaded the map's soundscape - } - continue; - } - - Warning( "CSoundscapeSystem::Init: Manifest '%s' with bogus file type '%s', expecting 'file'\n", - SOUNDSCAPE_MANIFEST_FILE, sub->GetName() ); - } - - if ( mapSoundscapeFilename && filesystem->FileExists( mapSoundscapeFilename ) ) - { - AddSoundscapeFile( mapSoundscapeFilename ); - } - } - else - { - Error( "Unable to load manifest file '%s'\n", SOUNDSCAPE_MANIFEST_FILE ); - } - manifest->deleteThis(); - m_activeIndex = -1; - - return true; -} - -void CSoundscapeSystem::FlushSoundscapes( void ) -{ - m_soundscapeCount = 0; - m_soundscapes.ClearStrings(); -} - -void CSoundscapeSystem::Shutdown() -{ - FlushSoundscapes(); - m_soundscapeEntities.RemoveAll(); - m_activeIndex = -1; -#ifdef _XBOX - m_soundscapeSounds.Purge(); -#endif - -} - -void CSoundscapeSystem::LevelInitPreEntity() -{ - g_SoundscapeSystem.Shutdown(); - g_SoundscapeSystem.Init(); -} - -void CSoundscapeSystem::LevelInitPostEntity() -{ -#ifdef _XBOX - m_soundscapeSounds.Purge(); -#endif -} - -int CSoundscapeSystem::GetSoundscapeIndex( const char *pName ) -{ - return m_soundscapes.GetStringID( pName ); -} - -bool CSoundscapeSystem::IsValidIndex( int index ) -{ - if ( index >= 0 && index < m_soundscapeCount ) - return true; - return false; -} - -void CSoundscapeSystem::AddSoundscapeEntity( CEnvSoundscape *pSoundscape ) -{ - if ( m_soundscapeEntities.Find( pSoundscape ) == -1 ) - { - m_soundscapeEntities.AddToTail( pSoundscape ); - } -} - -void CSoundscapeSystem::RemoveSoundscapeEntity( CEnvSoundscape *pSoundscape ) -{ - m_soundscapeEntities.FindAndRemove( pSoundscape ); -} - -void CSoundscapeSystem::FrameUpdatePostEntityThink() -{ - int total = m_soundscapeEntities.Count(); - if ( total > 0 ) - { - if ( !m_soundscapeEntities.IsValidIndex(m_activeIndex) ) - { - m_activeIndex = 0; - } - - // update 2 soundscape entities each tick - int count = min(2, total); - for ( int i = 0; i < count; i++ ) - { - m_activeIndex++; - m_activeIndex = m_activeIndex % total; - m_soundscapeEntities[m_activeIndex]->Update(); - } - } -} - -#ifdef _XBOX -void CSoundscapeSystem::AddSoundscapeSounds( KeyValues *pSoundscape, int soundscapeIndex ) -{ - int i = m_soundscapeSounds.AddToTail(); - Assert( i == soundscapeIndex ); - - KeyValues *pKey = pSoundscape->GetFirstSubKey(); - while ( pKey ) - { - if ( !Q_strcasecmp( pKey->GetName(), "playlooping" ) ) - { - KeyValues *pAmbientKey = pKey->GetFirstSubKey(); - while ( pAmbientKey ) - { - if ( !Q_strcasecmp( pAmbientKey->GetName(), "wave" ) ) - { - char const *pSoundName = pAmbientKey->GetString(); - m_soundscapeSounds[i].AddToTail( pSoundName ); - } - pAmbientKey = pAmbientKey->GetNextKey(); - } - } - else if ( !Q_strcasecmp( pKey->GetName(), "playrandom" ) ) - { - KeyValues *pRandomKey = pKey->GetFirstSubKey(); - while ( pRandomKey ) - { - if ( !Q_strcasecmp( pRandomKey->GetName(), "rndwave" ) ) - { - KeyValues *pRndWaveKey = pRandomKey->GetFirstSubKey(); - while ( pRndWaveKey ) - { - if ( !Q_strcasecmp( pRndWaveKey->GetName(), "wave" ) ) - { - char const *pSoundName = pRndWaveKey->GetString(); - m_soundscapeSounds[i].AddToTail( pSoundName ); - } - pRndWaveKey = pRndWaveKey->GetNextKey(); - } - } - pRandomKey = pRandomKey->GetNextKey(); - } - } - else if ( !Q_strcasecmp( pKey->GetName(), "playsoundscape" ) ) - { - KeyValues *pPlayKey = pKey->GetFirstSubKey(); - while ( pPlayKey ) - { - if ( !Q_strcasecmp( pPlayKey->GetName(), "name" ) ) - { - char const *pSoundName = pPlayKey->GetString(); - m_soundscapeSounds[i].AddToTail( pSoundName ); - } - pPlayKey = pPlayKey->GetNextKey(); - } - } - pKey = pKey->GetNextKey(); - } -} -#endif - -#ifdef _XBOX -void CSoundscapeSystem::PrecacheSounds( int soundscapeIndex ) -{ - if ( !IsValidIndex( soundscapeIndex ) ) - { - return; - } - - int count = m_soundscapeSounds[soundscapeIndex].Count(); - for ( int i=0; iClientCommand( UTIL_GetCommandClient()->edict(), "cl_soundscape_flush\n" ); +} + +CSoundscapeSystem g_SoundscapeSystem( "CSoundscapeSystem" ); + + +void CSoundscapeSystem::AddSoundscapeFile( const char *filename ) +{ + MEM_ALLOC_CREDIT(); + // Open the soundscape data file, and abort if we can't + KeyValues *pKeyValuesData = new KeyValues( filename ); + if ( filesystem->LoadKeyValues( *pKeyValuesData, IFileSystem::TYPE_SOUNDSCAPE, filename, "GAME" ) ) + { + // parse out all of the top level sections and save their names + KeyValues *pKeys = pKeyValuesData; + while ( pKeys ) + { + if ( pKeys->GetFirstSubKey() ) + { + if ( g_pDeveloper->GetBool() ) + { + if ( strstr( pKeys->GetName(), "{" ) ) + { + Msg("Error parsing soundscape file %s after %s\n", filename, m_soundscapeCount>0 ?m_soundscapes.GetStringText( m_soundscapeCount-1 ) : "FIRST" ); + } + } + m_soundscapes.AddString( pKeys->GetName(), m_soundscapeCount ); +#ifdef _XBOX + const char *pStr = pKeys->GetName(); + AddSoundscapeSounds( pKeys, m_soundscapeCount ); +#endif + m_soundscapeCount++; + } + pKeys = pKeys->GetNextKey(); + } + } + pKeyValuesData->deleteThis(); +} + +CON_COMMAND(sv_soundscape_printdebuginfo, "print soundscapes") +{ + g_SoundscapeSystem.PrintDebugInfo(); +} + + +void CSoundscapeSystem::PrintDebugInfo() +{ + Msg( "\n------- SERVER SOUNDSCAPES -------\n" ); + for ( int key=m_soundscapes.First(); key != m_soundscapes.InvalidIndex(); key = m_soundscapes.Next( key ) ) + { + int id = m_soundscapes.GetIDForKey( key ); + const char *pName = m_soundscapes.GetStringForKey( key ); + + Msg( "- %d: %s\n", id, pName ); + } + Msg( "-------- SOUNDSCAPE ENTITIES -----\n" ); + for( int entityIndex = 0; entityIndex < m_soundscapeEntities.Size(); ++entityIndex ) + { + CEnvSoundscape *currentSoundscape = m_soundscapeEntities[entityIndex]; + Msg("- %d: %s x:%.4f y:%.4f z:%.4f\n", + entityIndex, + STRING(currentSoundscape->GetSoundscapeName()), + currentSoundscape->GetAbsOrigin().x, + currentSoundscape->GetAbsOrigin().y, + currentSoundscape->GetAbsOrigin().z + ); + } + Msg( "----------------------------------\n\n" ); +} + +bool CSoundscapeSystem::Init() +{ + m_soundscapeCount = 0; + + const char *mapname = STRING( gpGlobals->mapname ); + const char *mapSoundscapeFilename = NULL; + if ( mapname && *mapname ) + { + mapSoundscapeFilename = UTIL_VarArgs( "scripts/soundscapes_%s.txt", mapname ); + } + + KeyValues *manifest = new KeyValues( SOUNDSCAPE_MANIFEST_FILE ); + if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDSCAPE, SOUNDSCAPE_MANIFEST_FILE, "GAME" ) ) + { + for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "file" ) ) + { + // Add + AddSoundscapeFile( sub->GetString() ); + if ( mapSoundscapeFilename && FStrEq( sub->GetString(), mapSoundscapeFilename ) ) + { + mapSoundscapeFilename = NULL; // we've already loaded the map's soundscape + } + continue; + } + + Warning( "CSoundscapeSystem::Init: Manifest '%s' with bogus file type '%s', expecting 'file'\n", + SOUNDSCAPE_MANIFEST_FILE, sub->GetName() ); + } + + if ( mapSoundscapeFilename && filesystem->FileExists( mapSoundscapeFilename ) ) + { + AddSoundscapeFile( mapSoundscapeFilename ); + } + } + else + { + Error( "Unable to load manifest file '%s'\n", SOUNDSCAPE_MANIFEST_FILE ); + } + manifest->deleteThis(); + m_activeIndex = -1; + + return true; +} + +void CSoundscapeSystem::FlushSoundscapes( void ) +{ + m_soundscapeCount = 0; + m_soundscapes.ClearStrings(); +} + +void CSoundscapeSystem::Shutdown() +{ + FlushSoundscapes(); + m_soundscapeEntities.RemoveAll(); + m_activeIndex = -1; +#ifdef _XBOX + m_soundscapeSounds.Purge(); +#endif + +} + +void CSoundscapeSystem::LevelInitPreEntity() +{ + g_SoundscapeSystem.Shutdown(); + g_SoundscapeSystem.Init(); +} + +void CSoundscapeSystem::LevelInitPostEntity() +{ +#ifdef _XBOX + m_soundscapeSounds.Purge(); +#endif +} + +int CSoundscapeSystem::GetSoundscapeIndex( const char *pName ) +{ + return m_soundscapes.GetStringID( pName ); +} + +bool CSoundscapeSystem::IsValidIndex( int index ) +{ + if ( index >= 0 && index < m_soundscapeCount ) + return true; + return false; +} + +void CSoundscapeSystem::AddSoundscapeEntity( CEnvSoundscape *pSoundscape ) +{ + if ( m_soundscapeEntities.Find( pSoundscape ) == -1 ) + { + m_soundscapeEntities.AddToTail( pSoundscape ); + } +} + +void CSoundscapeSystem::RemoveSoundscapeEntity( CEnvSoundscape *pSoundscape ) +{ + m_soundscapeEntities.FindAndRemove( pSoundscape ); +} + +void CSoundscapeSystem::FrameUpdatePostEntityThink() +{ + int total = m_soundscapeEntities.Count(); + if ( total > 0 ) + { + if ( !m_soundscapeEntities.IsValidIndex(m_activeIndex) ) + { + m_activeIndex = 0; + } + + // update 2 soundscape entities each tick + int count = min(2, total); + for ( int i = 0; i < count; i++ ) + { + m_activeIndex++; + m_activeIndex = m_activeIndex % total; + m_soundscapeEntities[m_activeIndex]->Update(); + } + } +} + +#ifdef _XBOX +void CSoundscapeSystem::AddSoundscapeSounds( KeyValues *pSoundscape, int soundscapeIndex ) +{ + int i = m_soundscapeSounds.AddToTail(); + Assert( i == soundscapeIndex ); + + KeyValues *pKey = pSoundscape->GetFirstSubKey(); + while ( pKey ) + { + if ( !Q_strcasecmp( pKey->GetName(), "playlooping" ) ) + { + KeyValues *pAmbientKey = pKey->GetFirstSubKey(); + while ( pAmbientKey ) + { + if ( !Q_strcasecmp( pAmbientKey->GetName(), "wave" ) ) + { + char const *pSoundName = pAmbientKey->GetString(); + m_soundscapeSounds[i].AddToTail( pSoundName ); + } + pAmbientKey = pAmbientKey->GetNextKey(); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "playrandom" ) ) + { + KeyValues *pRandomKey = pKey->GetFirstSubKey(); + while ( pRandomKey ) + { + if ( !Q_strcasecmp( pRandomKey->GetName(), "rndwave" ) ) + { + KeyValues *pRndWaveKey = pRandomKey->GetFirstSubKey(); + while ( pRndWaveKey ) + { + if ( !Q_strcasecmp( pRndWaveKey->GetName(), "wave" ) ) + { + char const *pSoundName = pRndWaveKey->GetString(); + m_soundscapeSounds[i].AddToTail( pSoundName ); + } + pRndWaveKey = pRndWaveKey->GetNextKey(); + } + } + pRandomKey = pRandomKey->GetNextKey(); + } + } + else if ( !Q_strcasecmp( pKey->GetName(), "playsoundscape" ) ) + { + KeyValues *pPlayKey = pKey->GetFirstSubKey(); + while ( pPlayKey ) + { + if ( !Q_strcasecmp( pPlayKey->GetName(), "name" ) ) + { + char const *pSoundName = pPlayKey->GetString(); + m_soundscapeSounds[i].AddToTail( pSoundName ); + } + pPlayKey = pPlayKey->GetNextKey(); + } + } + pKey = pKey->GetNextKey(); + } +} +#endif + +#ifdef _XBOX +void CSoundscapeSystem::PrecacheSounds( int soundscapeIndex ) +{ + if ( !IsValidIndex( soundscapeIndex ) ) + { + return; + } + + int count = m_soundscapeSounds[soundscapeIndex].Count(); + for ( int i=0; iGetMaxSpeed() ) { - return 90 + (fabs(pTrain->GetCurrentSpeed()) * (20) / pTrain->GetMaxSpeed()); + return static_cast(90 + (fabs(pTrain->GetCurrentSpeed()) * (20) / pTrain->GetMaxSpeed())); } return 100; } diff --git a/dlls/te.h b/dlls/te.h index 1070a665..acd8e00c 100644 --- a/dlls/te.h +++ b/dlls/te.h @@ -1,17 +1,17 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $NoKeywords: $ -//=============================================================================// - -#if !defined( TE_H ) -#define TE_H -#ifdef _WIN32 -#pragma once -#endif - -#include "itempents.h" - -#endif // TE_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// + +#if !defined( TE_H ) +#define TE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "itempents.h" + +#endif // TE_H diff --git a/dlls/te_basebeam.h b/dlls/te_basebeam.h index c08e48fa..0a18b12b 100644 --- a/dlls/te_basebeam.h +++ b/dlls/te_basebeam.h @@ -1,59 +1,59 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -//----------------------------------------------------------------------------- -// Purpose: Dispatches a beam ring between two entities -//----------------------------------------------------------------------------- -#if !defined( TE_BASEBEAM_H ) -#define TE_BASEBEAM_H -#ifdef _WIN32 -#pragma once -#endif - -#include "basetempentity.h" - -abstract_class CTEBaseBeam : public CBaseTempEntity -{ -public: - - DECLARE_CLASS( CTEBaseBeam, CBaseTempEntity ); - DECLARE_SERVERCLASS(); - - -public: - CTEBaseBeam( const char *name ); - virtual ~CTEBaseBeam( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ) = 0; - -public: - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( int, m_nHaloIndex ); - CNetworkVar( int, m_nStartFrame ); - CNetworkVar( int, m_nFrameRate ); - CNetworkVar( float, m_fLife ); - CNetworkVar( float, m_fWidth ); - CNetworkVar( float, m_fEndWidth ); - CNetworkVar( int, m_nFadeLength ); - CNetworkVar( float, m_fAmplitude ); - CNetworkVar( int, r ); - CNetworkVar( int, g ); - CNetworkVar( int, b ); - CNetworkVar( int, a ); - CNetworkVar( int, m_nSpeed ); - CNetworkVar( int, m_nFlags ); -}; - -EXTERN_SEND_TABLE(DT_BaseBeam); - -#endif // TE_BASEBEAM_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +//----------------------------------------------------------------------------- +// Purpose: Dispatches a beam ring between two entities +//----------------------------------------------------------------------------- +#if !defined( TE_BASEBEAM_H ) +#define TE_BASEBEAM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basetempentity.h" + +abstract_class CTEBaseBeam : public CBaseTempEntity +{ +public: + + DECLARE_CLASS( CTEBaseBeam, CBaseTempEntity ); + DECLARE_SERVERCLASS(); + + +public: + CTEBaseBeam( const char *name ); + virtual ~CTEBaseBeam( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ) = 0; + +public: + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( int, m_nHaloIndex ); + CNetworkVar( int, m_nStartFrame ); + CNetworkVar( int, m_nFrameRate ); + CNetworkVar( float, m_fLife ); + CNetworkVar( float, m_fWidth ); + CNetworkVar( float, m_fEndWidth ); + CNetworkVar( int, m_nFadeLength ); + CNetworkVar( float, m_fAmplitude ); + CNetworkVar( int, r ); + CNetworkVar( int, g ); + CNetworkVar( int, b ); + CNetworkVar( int, a ); + CNetworkVar( int, m_nSpeed ); + CNetworkVar( int, m_nFlags ); +}; + +EXTERN_SEND_TABLE(DT_BaseBeam); + +#endif // TE_BASEBEAM_H diff --git a/dlls/te_beamentpoint.cpp b/dlls/te_beamentpoint.cpp index 280e9a5d..d379dd41 100644 --- a/dlls/te_beamentpoint.cpp +++ b/dlls/te_beamentpoint.cpp @@ -1,154 +1,154 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "te_basebeam.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches a beam ring between two entities -//----------------------------------------------------------------------------- -class CTEBeamEntPoint : public CTEBaseBeam -{ -public: - DECLARE_CLASS( CTEBeamEntPoint, CTEBaseBeam ); - DECLARE_SERVERCLASS(); - - CTEBeamEntPoint( const char *name ); - virtual ~CTEBeamEntPoint( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - -public: - CNetworkVar( int, m_nStartEntity ); - CNetworkVector( m_vecStartPoint ); - CNetworkVar( int, m_nEndEntity ); - CNetworkVector( m_vecEndPoint ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBeamEntPoint::CTEBeamEntPoint( const char *name ) : - CTEBaseBeam( name ) -{ - m_nStartEntity = 0; - m_nEndEntity = 0; - m_vecStartPoint.Init(); - m_vecEndPoint.Init(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBeamEntPoint::~CTEBeamEntPoint( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBeamEntPoint::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - m_nStartEntity = 1; - - m_nModelIndex = g_sModelIndexSmoke; - m_nStartFrame = 0; - m_nFrameRate = 10; - m_fLife = 2.0; - m_fWidth = 1.0; - m_fAmplitude = 1.0; - r = 0; - g = 63; - b = 127; - a = 150; - m_nSpeed = 1; - - m_vecEndPoint = current_origin; - - Vector forward, right; - - m_vecEndPoint += Vector( 0, 0, 24 ); - - AngleVectors( current_angles, &forward, &right, 0 ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecEndPoint, 50.0, forward, m_vecEndPoint.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEBeamEntPoint, DT_TEBeamEntPoint) - SendPropInt( SENDINFO(m_nStartEntity), 24, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nEndEntity), 24, SPROP_UNSIGNED ), - SendPropVector( SENDINFO(m_vecStartPoint), -1, SPROP_COORD ), - SendPropVector( SENDINFO(m_vecEndPoint), -1, SPROP_COORD ), -END_SEND_TABLE() - - -// Singleton to fire TEBeamEntPoint objects -static CTEBeamEntPoint g_TEBeamEntPoint( "BeamEntPoint" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// int start - -// *end - -// modelindex - -// startframe - -// framerate - -// msg_dest - -// delay - -// origin - -// recipient - -//----------------------------------------------------------------------------- -void TE_BeamEntPoint( IRecipientFilter& filter, float delay, - int nStartEntity, const Vector *start, int nEndEntity, const Vector* end, - int modelindex, int haloindex, int startframe, int framerate, - float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) -{ - g_TEBeamEntPoint.m_nStartEntity = (nStartEntity > 0) ? (nStartEntity & 0x0FFF) | ((1 & 0xF)<<12) : 0; - g_TEBeamEntPoint.m_nEndEntity = (nEndEntity > 0) ? (nEndEntity & 0x0FFF) | ((1 & 0xF)<<12) : 0; - g_TEBeamEntPoint.m_vecStartPoint = start ? *start : vec3_origin; - g_TEBeamEntPoint.m_vecEndPoint = end ? *end : vec3_origin; - g_TEBeamEntPoint.m_nModelIndex = modelindex; - g_TEBeamEntPoint.m_nHaloIndex = haloindex; - g_TEBeamEntPoint.m_nStartFrame = startframe; - g_TEBeamEntPoint.m_nFrameRate = framerate; - g_TEBeamEntPoint.m_fLife = life; - g_TEBeamEntPoint.m_fWidth = width; - g_TEBeamEntPoint.m_fEndWidth = endWidth; - g_TEBeamEntPoint.m_nFadeLength = fadeLength; - g_TEBeamEntPoint.m_fAmplitude = amplitude; - g_TEBeamEntPoint.m_nSpeed = speed; - g_TEBeamEntPoint.r = r; - g_TEBeamEntPoint.g = g; - g_TEBeamEntPoint.b = b; - g_TEBeamEntPoint.a = a; - - // Send it over the wire - g_TEBeamEntPoint.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "te_basebeam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches a beam ring between two entities +//----------------------------------------------------------------------------- +class CTEBeamEntPoint : public CTEBaseBeam +{ +public: + DECLARE_CLASS( CTEBeamEntPoint, CTEBaseBeam ); + DECLARE_SERVERCLASS(); + + CTEBeamEntPoint( const char *name ); + virtual ~CTEBeamEntPoint( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + +public: + CNetworkVar( int, m_nStartEntity ); + CNetworkVector( m_vecStartPoint ); + CNetworkVar( int, m_nEndEntity ); + CNetworkVector( m_vecEndPoint ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBeamEntPoint::CTEBeamEntPoint( const char *name ) : + CTEBaseBeam( name ) +{ + m_nStartEntity = 0; + m_nEndEntity = 0; + m_vecStartPoint.Init(); + m_vecEndPoint.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBeamEntPoint::~CTEBeamEntPoint( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBeamEntPoint::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + m_nStartEntity = 1; + + m_nModelIndex = g_sModelIndexSmoke; + m_nStartFrame = 0; + m_nFrameRate = 10; + m_fLife = 2.0; + m_fWidth = 1.0; + m_fAmplitude = 1.0; + r = 0; + g = 63; + b = 127; + a = 150; + m_nSpeed = 1; + + m_vecEndPoint = current_origin; + + Vector forward, right; + + m_vecEndPoint += Vector( 0, 0, 24 ); + + AngleVectors( current_angles, &forward, &right, 0 ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecEndPoint, 50.0, forward, m_vecEndPoint.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEBeamEntPoint, DT_TEBeamEntPoint) + SendPropInt( SENDINFO(m_nStartEntity), 24, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nEndEntity), 24, SPROP_UNSIGNED ), + SendPropVector( SENDINFO(m_vecStartPoint), -1, SPROP_COORD ), + SendPropVector( SENDINFO(m_vecEndPoint), -1, SPROP_COORD ), +END_SEND_TABLE() + + +// Singleton to fire TEBeamEntPoint objects +static CTEBeamEntPoint g_TEBeamEntPoint( "BeamEntPoint" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// int start - +// *end - +// modelindex - +// startframe - +// framerate - +// msg_dest - +// delay - +// origin - +// recipient - +//----------------------------------------------------------------------------- +void TE_BeamEntPoint( IRecipientFilter& filter, float delay, + int nStartEntity, const Vector *start, int nEndEntity, const Vector* end, + int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) +{ + g_TEBeamEntPoint.m_nStartEntity = (nStartEntity > 0) ? (nStartEntity & 0x0FFF) | ((1 & 0xF)<<12) : 0; + g_TEBeamEntPoint.m_nEndEntity = (nEndEntity > 0) ? (nEndEntity & 0x0FFF) | ((1 & 0xF)<<12) : 0; + g_TEBeamEntPoint.m_vecStartPoint = start ? *start : vec3_origin; + g_TEBeamEntPoint.m_vecEndPoint = end ? *end : vec3_origin; + g_TEBeamEntPoint.m_nModelIndex = modelindex; + g_TEBeamEntPoint.m_nHaloIndex = haloindex; + g_TEBeamEntPoint.m_nStartFrame = startframe; + g_TEBeamEntPoint.m_nFrameRate = framerate; + g_TEBeamEntPoint.m_fLife = life; + g_TEBeamEntPoint.m_fWidth = width; + g_TEBeamEntPoint.m_fEndWidth = endWidth; + g_TEBeamEntPoint.m_nFadeLength = fadeLength; + g_TEBeamEntPoint.m_fAmplitude = amplitude; + g_TEBeamEntPoint.m_nSpeed = speed; + g_TEBeamEntPoint.r = r; + g_TEBeamEntPoint.g = g; + g_TEBeamEntPoint.b = b; + g_TEBeamEntPoint.a = a; + + // Send it over the wire + g_TEBeamEntPoint.Create( filter, delay ); +} diff --git a/dlls/te_beaments.cpp b/dlls/te_beaments.cpp index b17e2464..c1240e1b 100644 --- a/dlls/te_beaments.cpp +++ b/dlls/te_beaments.cpp @@ -1,134 +1,134 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "te_basebeam.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches a beam between two entities -//----------------------------------------------------------------------------- -class CTEBeamEnts : public CTEBaseBeam -{ -public: - DECLARE_CLASS( CTEBeamEnts, CTEBaseBeam ); - DECLARE_SERVERCLASS(); - - CTEBeamEnts( const char *name ); - virtual ~CTEBeamEnts( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - -public: - CNetworkVar( int, m_nStartEntity ); - CNetworkVar( int, m_nEndEntity ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBeamEnts::CTEBeamEnts( const char *name ) : - CTEBaseBeam( name ) -{ - m_nStartEntity = 0; - m_nEndEntity = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBeamEnts::~CTEBeamEnts( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBeamEnts::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - m_nStartEntity = 1; - m_nEndEntity = 0; - - m_nModelIndex = g_sModelIndexSmoke; - m_nStartFrame = 0; - m_nFrameRate = 10; - m_fLife = 2.0; - m_fWidth = 1.0; - m_fAmplitude = 1; - r = 127; - g = 63; - b = 0; - a = 150; - m_nSpeed = 1; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEBeamEnts, DT_TEBeamEnts) - SendPropInt( SENDINFO(m_nStartEntity), 24, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nEndEntity), 24, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEBeamEnts objects -static CTEBeamEnts g_TEBeamEnts( "BeamEnts" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// int start - -// end - -// modelindex - -// startframe - -// framerate - -// msg_dest - -// delay - -// origin - -// recipient - -//----------------------------------------------------------------------------- -void TE_BeamEnts( IRecipientFilter& filter, float delay, - int start, int end, int modelindex, int haloindex, int startframe, int framerate, - float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) -{ - g_TEBeamEnts.m_nStartEntity = (start & 0x0FFF) | ((1 & 0xF)<<12); - g_TEBeamEnts.m_nEndEntity = (end & 0x0FFF) | ((1 & 0xF)<<12); - g_TEBeamEnts.m_nModelIndex = modelindex; - g_TEBeamEnts.m_nHaloIndex = haloindex; - g_TEBeamEnts.m_nStartFrame = startframe; - g_TEBeamEnts.m_nFrameRate = framerate; - g_TEBeamEnts.m_fLife = life; - g_TEBeamEnts.m_fWidth = width; - g_TEBeamEnts.m_fEndWidth = endWidth; - g_TEBeamEnts.m_nFadeLength = fadeLength; - g_TEBeamEnts.m_fAmplitude = amplitude; - g_TEBeamEnts.m_nSpeed = speed; - g_TEBeamEnts.r = r; - g_TEBeamEnts.g = g; - g_TEBeamEnts.b = b; - g_TEBeamEnts.a = a; - - // Send it over the wire - g_TEBeamEnts.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "te_basebeam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches a beam between two entities +//----------------------------------------------------------------------------- +class CTEBeamEnts : public CTEBaseBeam +{ +public: + DECLARE_CLASS( CTEBeamEnts, CTEBaseBeam ); + DECLARE_SERVERCLASS(); + + CTEBeamEnts( const char *name ); + virtual ~CTEBeamEnts( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + +public: + CNetworkVar( int, m_nStartEntity ); + CNetworkVar( int, m_nEndEntity ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBeamEnts::CTEBeamEnts( const char *name ) : + CTEBaseBeam( name ) +{ + m_nStartEntity = 0; + m_nEndEntity = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBeamEnts::~CTEBeamEnts( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBeamEnts::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + m_nStartEntity = 1; + m_nEndEntity = 0; + + m_nModelIndex = g_sModelIndexSmoke; + m_nStartFrame = 0; + m_nFrameRate = 10; + m_fLife = 2.0; + m_fWidth = 1.0; + m_fAmplitude = 1; + r = 127; + g = 63; + b = 0; + a = 150; + m_nSpeed = 1; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEBeamEnts, DT_TEBeamEnts) + SendPropInt( SENDINFO(m_nStartEntity), 24, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nEndEntity), 24, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEBeamEnts objects +static CTEBeamEnts g_TEBeamEnts( "BeamEnts" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// int start - +// end - +// modelindex - +// startframe - +// framerate - +// msg_dest - +// delay - +// origin - +// recipient - +//----------------------------------------------------------------------------- +void TE_BeamEnts( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) +{ + g_TEBeamEnts.m_nStartEntity = (start & 0x0FFF) | ((1 & 0xF)<<12); + g_TEBeamEnts.m_nEndEntity = (end & 0x0FFF) | ((1 & 0xF)<<12); + g_TEBeamEnts.m_nModelIndex = modelindex; + g_TEBeamEnts.m_nHaloIndex = haloindex; + g_TEBeamEnts.m_nStartFrame = startframe; + g_TEBeamEnts.m_nFrameRate = framerate; + g_TEBeamEnts.m_fLife = life; + g_TEBeamEnts.m_fWidth = width; + g_TEBeamEnts.m_fEndWidth = endWidth; + g_TEBeamEnts.m_nFadeLength = fadeLength; + g_TEBeamEnts.m_fAmplitude = amplitude; + g_TEBeamEnts.m_nSpeed = speed; + g_TEBeamEnts.r = r; + g_TEBeamEnts.g = g; + g_TEBeamEnts.b = b; + g_TEBeamEnts.a = a; + + // Send it over the wire + g_TEBeamEnts.Create( filter, delay ); +} diff --git a/dlls/te_beamfollow.cpp b/dlls/te_beamfollow.cpp index 28b06b3b..07b3c54c 100644 --- a/dlls/te_beamfollow.cpp +++ b/dlls/te_beamfollow.cpp @@ -1,112 +1,112 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "te_basebeam.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches a beam ring between two entities -//----------------------------------------------------------------------------- -class CTEBeamFollow : public CTEBaseBeam -{ - DECLARE_CLASS( CTEBeamFollow, CTEBaseBeam ); -public: - - DECLARE_SERVERCLASS(); - - CTEBeamFollow( const char *name ); - virtual ~CTEBeamFollow( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - -public: - - CNetworkVar( int, m_iEntIndex ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBeamFollow::CTEBeamFollow( const char *name ) : - CTEBaseBeam( name ) -{ - m_iEntIndex = -1; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBeamFollow::~CTEBeamFollow( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBeamFollow::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - m_iEntIndex = 1; -} - -IMPLEMENT_SERVERCLASS_ST(CTEBeamFollow, DT_TEBeamFollow) - SendPropInt( SENDINFO(m_iEntIndex), 24, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEBeamEntPoint objects -static CTEBeamFollow g_TEBeamFollow( "BeamFollow" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : filter - -// delay - -// iEntIndex - -// modelIndex - -// modelindex - -// haloIndex - -// life - -// width - -// endWidth - -// fadeLength - -// r - -// g - -// b - -// a - -//----------------------------------------------------------------------------- -void TE_BeamFollow( IRecipientFilter& filter, float delay, - int iEntIndex, int modelIndex, int haloIndex, float life, float width, float endWidth, - float fadeLength,float r, float g, float b, float a ) -{ - g_TEBeamFollow.m_iEntIndex = (iEntIndex & 0x0FFF) | ((1 & 0xF)<<12); - g_TEBeamFollow.m_nModelIndex = modelIndex; - g_TEBeamFollow.m_nHaloIndex = haloIndex; - g_TEBeamFollow.m_nStartFrame = 0; - g_TEBeamFollow.m_nFrameRate = 0; - g_TEBeamFollow.m_fLife = life; - g_TEBeamFollow.m_fWidth = width; - g_TEBeamFollow.m_fEndWidth = endWidth; - g_TEBeamFollow.m_nFadeLength = fadeLength; - g_TEBeamFollow.r = r; - g_TEBeamFollow.g = g; - g_TEBeamFollow.b = b; - g_TEBeamFollow.a = a; - - // Send it over the wire - g_TEBeamFollow.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "te_basebeam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches a beam ring between two entities +//----------------------------------------------------------------------------- +class CTEBeamFollow : public CTEBaseBeam +{ + DECLARE_CLASS( CTEBeamFollow, CTEBaseBeam ); +public: + + DECLARE_SERVERCLASS(); + + CTEBeamFollow( const char *name ); + virtual ~CTEBeamFollow( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + +public: + + CNetworkVar( int, m_iEntIndex ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBeamFollow::CTEBeamFollow( const char *name ) : + CTEBaseBeam( name ) +{ + m_iEntIndex = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBeamFollow::~CTEBeamFollow( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBeamFollow::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + m_iEntIndex = 1; +} + +IMPLEMENT_SERVERCLASS_ST(CTEBeamFollow, DT_TEBeamFollow) + SendPropInt( SENDINFO(m_iEntIndex), 24, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEBeamEntPoint objects +static CTEBeamFollow g_TEBeamFollow( "BeamFollow" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : filter - +// delay - +// iEntIndex - +// modelIndex - +// modelindex - +// haloIndex - +// life - +// width - +// endWidth - +// fadeLength - +// r - +// g - +// b - +// a - +//----------------------------------------------------------------------------- +void TE_BeamFollow( IRecipientFilter& filter, float delay, + int iEntIndex, int modelIndex, int haloIndex, float life, float width, float endWidth, + float fadeLength,float r, float g, float b, float a ) +{ + g_TEBeamFollow.m_iEntIndex = (iEntIndex & 0x0FFF) | ((1 & 0xF)<<12); + g_TEBeamFollow.m_nModelIndex = modelIndex; + g_TEBeamFollow.m_nHaloIndex = haloIndex; + g_TEBeamFollow.m_nStartFrame = 0; + g_TEBeamFollow.m_nFrameRate = 0; + g_TEBeamFollow.m_fLife = life; + g_TEBeamFollow.m_fWidth = width; + g_TEBeamFollow.m_fEndWidth = endWidth; + g_TEBeamFollow.m_nFadeLength = fadeLength; + g_TEBeamFollow.r = r; + g_TEBeamFollow.g = g; + g_TEBeamFollow.b = b; + g_TEBeamFollow.a = a; + + // Send it over the wire + g_TEBeamFollow.Create( filter, delay ); +} diff --git a/dlls/te_beamlaser.cpp b/dlls/te_beamlaser.cpp index 5cde9912..148e9874 100644 --- a/dlls/te_beamlaser.cpp +++ b/dlls/te_beamlaser.cpp @@ -1,127 +1,127 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Beam used for Laser sights. Fades out when it's perpendicular to the viewpoint. -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "te_basebeam.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Beam used for Laser sights. Fades out when it's perpendicular to the viewpoint. -//----------------------------------------------------------------------------- -class CTEBeamLaser : public CTEBaseBeam -{ - DECLARE_CLASS( CTEBeamLaser, CTEBaseBeam ); -public: - DECLARE_SERVERCLASS(); - - CTEBeamLaser( const char *name ); - virtual ~CTEBeamLaser( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - -public: - CNetworkVar( int, m_nStartEntity ); - CNetworkVar( int, m_nEndEntity ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBeamLaser::CTEBeamLaser( const char *name ) : - CTEBaseBeam( name ) -{ - m_nStartEntity = 0; - m_nEndEntity = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBeamLaser::~CTEBeamLaser( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBeamLaser::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - m_nStartEntity = 1; - m_nEndEntity = 0; - - m_nModelIndex = g_sModelIndexSmoke; - m_nStartFrame = 0; - m_nFrameRate = 10; - m_fLife = 2.0; - m_fWidth = 1.0; - m_fAmplitude = 1.0; - r = 127; - g = 63; - b = 0; - a = 150; - m_nSpeed = 1; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST( CTEBeamLaser, DT_TEBeamLaser) - SendPropInt( SENDINFO(m_nStartEntity), 24, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nEndEntity), 24, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEBeamLaser objects -static CTEBeamLaser g_TEBeamLaser( "BeamLaser" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *start - -// *end - -// modelindex - -// startframe - -// framerate - -// msg_dest - -// delay - -// origin - -// recipient - -//----------------------------------------------------------------------------- -void TE_BeamLaser( IRecipientFilter& filter, float delay, - int start, int end, int modelindex, int haloindex, int startframe, int framerate, - float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) -{ - g_TEBeamLaser.m_nStartEntity = (start & 0x0FFF) | ((1 & 0xF)<<12); - g_TEBeamLaser.m_nEndEntity = (end & 0x0FFF) | ((1 & 0xF)<<12); - g_TEBeamLaser.m_nModelIndex = modelindex; - g_TEBeamLaser.m_nHaloIndex = haloindex; - g_TEBeamLaser.m_nStartFrame = startframe; - g_TEBeamLaser.m_nFrameRate = framerate; - g_TEBeamLaser.m_fLife = life; - g_TEBeamLaser.m_fWidth = width; - g_TEBeamLaser.m_fEndWidth = endWidth; - g_TEBeamLaser.m_nFadeLength = fadeLength; - g_TEBeamLaser.m_fAmplitude = amplitude; - g_TEBeamLaser.m_nSpeed = speed; - g_TEBeamLaser.r = r; - g_TEBeamLaser.g = g; - g_TEBeamLaser.b = b; - g_TEBeamLaser.a = a; - - // Send it over the wire - g_TEBeamLaser.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Beam used for Laser sights. Fades out when it's perpendicular to the viewpoint. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "te_basebeam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Beam used for Laser sights. Fades out when it's perpendicular to the viewpoint. +//----------------------------------------------------------------------------- +class CTEBeamLaser : public CTEBaseBeam +{ + DECLARE_CLASS( CTEBeamLaser, CTEBaseBeam ); +public: + DECLARE_SERVERCLASS(); + + CTEBeamLaser( const char *name ); + virtual ~CTEBeamLaser( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + +public: + CNetworkVar( int, m_nStartEntity ); + CNetworkVar( int, m_nEndEntity ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBeamLaser::CTEBeamLaser( const char *name ) : + CTEBaseBeam( name ) +{ + m_nStartEntity = 0; + m_nEndEntity = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBeamLaser::~CTEBeamLaser( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBeamLaser::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + m_nStartEntity = 1; + m_nEndEntity = 0; + + m_nModelIndex = g_sModelIndexSmoke; + m_nStartFrame = 0; + m_nFrameRate = 10; + m_fLife = 2.0; + m_fWidth = 1.0; + m_fAmplitude = 1.0; + r = 127; + g = 63; + b = 0; + a = 150; + m_nSpeed = 1; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST( CTEBeamLaser, DT_TEBeamLaser) + SendPropInt( SENDINFO(m_nStartEntity), 24, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nEndEntity), 24, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEBeamLaser objects +static CTEBeamLaser g_TEBeamLaser( "BeamLaser" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *start - +// *end - +// modelindex - +// startframe - +// framerate - +// msg_dest - +// delay - +// origin - +// recipient - +//----------------------------------------------------------------------------- +void TE_BeamLaser( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) +{ + g_TEBeamLaser.m_nStartEntity = (start & 0x0FFF) | ((1 & 0xF)<<12); + g_TEBeamLaser.m_nEndEntity = (end & 0x0FFF) | ((1 & 0xF)<<12); + g_TEBeamLaser.m_nModelIndex = modelindex; + g_TEBeamLaser.m_nHaloIndex = haloindex; + g_TEBeamLaser.m_nStartFrame = startframe; + g_TEBeamLaser.m_nFrameRate = framerate; + g_TEBeamLaser.m_fLife = life; + g_TEBeamLaser.m_fWidth = width; + g_TEBeamLaser.m_fEndWidth = endWidth; + g_TEBeamLaser.m_nFadeLength = fadeLength; + g_TEBeamLaser.m_fAmplitude = amplitude; + g_TEBeamLaser.m_nSpeed = speed; + g_TEBeamLaser.r = r; + g_TEBeamLaser.g = g; + g_TEBeamLaser.b = b; + g_TEBeamLaser.a = a; + + // Send it over the wire + g_TEBeamLaser.Create( filter, delay ); +} diff --git a/dlls/te_beampoints.cpp b/dlls/te_beampoints.cpp index bcf0cbe9..ecdb7216 100644 --- a/dlls/te_beampoints.cpp +++ b/dlls/te_beampoints.cpp @@ -1,144 +1,144 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "te_basebeam.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches a beam ring between two entities -//----------------------------------------------------------------------------- -class CTEBeamPoints : public CTEBaseBeam -{ -public: - DECLARE_CLASS( CTEBeamPoints, CTEBaseBeam ); - DECLARE_SERVERCLASS(); - - CTEBeamPoints( const char *name ); - virtual ~CTEBeamPoints( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - -public: - CNetworkVector( m_vecStartPoint ); - CNetworkVector( m_vecEndPoint ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBeamPoints::CTEBeamPoints( const char *name ) : - CTEBaseBeam( name ) -{ - m_vecStartPoint.Init(); - m_vecEndPoint.Init(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBeamPoints::~CTEBeamPoints( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBeamPoints::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - m_nModelIndex = g_sModelIndexSmoke; - m_nStartFrame = 0; - m_nFrameRate = 10; - m_fLife = 2.0; - m_fWidth = 1.0; - m_fAmplitude = 1; - r = 0; - g = 63; - b = 127; - a = 150; - m_nSpeed = 1; - - m_vecStartPoint = current_origin; - - Vector forward, right; - - m_vecStartPoint += Vector( 0, 0, 30 ); - - AngleVectors( current_angles, &forward, &right, 0 ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecStartPoint, 75.0, forward, m_vecStartPoint.GetForModify() ); - VectorMA( m_vecStartPoint, 25.0, right, m_vecEndPoint.GetForModify() ); - VectorMA( m_vecStartPoint, -25.0, right, m_vecStartPoint.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST( CTEBeamPoints, DT_TEBeamPoints) - SendPropVector( SENDINFO(m_vecStartPoint), -1, SPROP_COORD ), - SendPropVector( SENDINFO(m_vecEndPoint), -1, SPROP_COORD ), -END_SEND_TABLE() - - -// Singleton to fire TEBeamPoints objects -static CTEBeamPoints g_TEBeamPoints( "BeamPoints" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *start - -// *end - -// modelindex - -// startframe - -// framerate - -// msg_dest - -// delay - -// origin - -// recipient - -//----------------------------------------------------------------------------- -void TE_BeamPoints( IRecipientFilter& filter, float delay, - const Vector* start, const Vector* end, int modelindex, int haloindex, int startframe, int framerate, - float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) -{ - g_TEBeamPoints.m_vecStartPoint = *start; - g_TEBeamPoints.m_vecEndPoint = *end; - g_TEBeamPoints.m_nModelIndex = modelindex; - g_TEBeamPoints.m_nHaloIndex = haloindex; - g_TEBeamPoints.m_nStartFrame = startframe; - g_TEBeamPoints.m_nFrameRate = framerate; - g_TEBeamPoints.m_fLife = life; - g_TEBeamPoints.m_fWidth = width; - g_TEBeamPoints.m_fEndWidth = endWidth; - g_TEBeamPoints.m_nFadeLength = fadeLength; - g_TEBeamPoints.m_fAmplitude = amplitude; - g_TEBeamPoints.m_nSpeed = speed; - g_TEBeamPoints.r = r; - g_TEBeamPoints.g = g; - g_TEBeamPoints.b = b; - g_TEBeamPoints.a = a; - - // Send it over the wire - g_TEBeamPoints.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "te_basebeam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches a beam ring between two entities +//----------------------------------------------------------------------------- +class CTEBeamPoints : public CTEBaseBeam +{ +public: + DECLARE_CLASS( CTEBeamPoints, CTEBaseBeam ); + DECLARE_SERVERCLASS(); + + CTEBeamPoints( const char *name ); + virtual ~CTEBeamPoints( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + +public: + CNetworkVector( m_vecStartPoint ); + CNetworkVector( m_vecEndPoint ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBeamPoints::CTEBeamPoints( const char *name ) : + CTEBaseBeam( name ) +{ + m_vecStartPoint.Init(); + m_vecEndPoint.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBeamPoints::~CTEBeamPoints( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBeamPoints::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + m_nModelIndex = g_sModelIndexSmoke; + m_nStartFrame = 0; + m_nFrameRate = 10; + m_fLife = 2.0; + m_fWidth = 1.0; + m_fAmplitude = 1; + r = 0; + g = 63; + b = 127; + a = 150; + m_nSpeed = 1; + + m_vecStartPoint = current_origin; + + Vector forward, right; + + m_vecStartPoint += Vector( 0, 0, 30 ); + + AngleVectors( current_angles, &forward, &right, 0 ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecStartPoint, 75.0, forward, m_vecStartPoint.GetForModify() ); + VectorMA( m_vecStartPoint, 25.0, right, m_vecEndPoint.GetForModify() ); + VectorMA( m_vecStartPoint, -25.0, right, m_vecStartPoint.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST( CTEBeamPoints, DT_TEBeamPoints) + SendPropVector( SENDINFO(m_vecStartPoint), -1, SPROP_COORD ), + SendPropVector( SENDINFO(m_vecEndPoint), -1, SPROP_COORD ), +END_SEND_TABLE() + + +// Singleton to fire TEBeamPoints objects +static CTEBeamPoints g_TEBeamPoints( "BeamPoints" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *start - +// *end - +// modelindex - +// startframe - +// framerate - +// msg_dest - +// delay - +// origin - +// recipient - +//----------------------------------------------------------------------------- +void TE_BeamPoints( IRecipientFilter& filter, float delay, + const Vector* start, const Vector* end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, float endWidth, int fadeLength, float amplitude, int r, int g, int b, int a, int speed ) +{ + g_TEBeamPoints.m_vecStartPoint = *start; + g_TEBeamPoints.m_vecEndPoint = *end; + g_TEBeamPoints.m_nModelIndex = modelindex; + g_TEBeamPoints.m_nHaloIndex = haloindex; + g_TEBeamPoints.m_nStartFrame = startframe; + g_TEBeamPoints.m_nFrameRate = framerate; + g_TEBeamPoints.m_fLife = life; + g_TEBeamPoints.m_fWidth = width; + g_TEBeamPoints.m_fEndWidth = endWidth; + g_TEBeamPoints.m_nFadeLength = fadeLength; + g_TEBeamPoints.m_fAmplitude = amplitude; + g_TEBeamPoints.m_nSpeed = speed; + g_TEBeamPoints.r = r; + g_TEBeamPoints.g = g; + g_TEBeamPoints.b = b; + g_TEBeamPoints.a = a; + + // Send it over the wire + g_TEBeamPoints.Create( filter, delay ); +} diff --git a/dlls/te_beamring.cpp b/dlls/te_beamring.cpp index 2308f572..21b86add 100644 --- a/dlls/te_beamring.cpp +++ b/dlls/te_beamring.cpp @@ -1,135 +1,135 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "te_basebeam.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches a beam ring between two entities -//----------------------------------------------------------------------------- -class CTEBeamRing : public CTEBaseBeam -{ -public: - DECLARE_CLASS( CTEBeamRing, CTEBaseBeam ); - DECLARE_SERVERCLASS(); - - CTEBeamRing( const char *name ); - virtual ~CTEBeamRing( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - -public: - CNetworkVar( int, m_nStartEntity ); - CNetworkVar( int, m_nEndEntity ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBeamRing::CTEBeamRing( const char *name ) : - CTEBaseBeam( name ) -{ - m_nStartEntity = 0; - m_nEndEntity = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBeamRing::~CTEBeamRing( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBeamRing::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - m_nStartEntity = 1; - m_nEndEntity = 0; - - m_nModelIndex = g_sModelIndexSmoke; - m_nStartFrame = 0; - m_nFrameRate = 2; - m_fLife = 10.0; - m_fWidth = 2.0; - m_fAmplitude = 1; - r = 255; - g = 255; - b = 0; - a = 127; - m_nSpeed = 5; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - - -IMPLEMENT_SERVERCLASS_ST( CTEBeamRing, DT_TEBeamRing) - SendPropInt( SENDINFO(m_nStartEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nEndEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEBeamRing objects -static CTEBeamRing g_TEBeamRing( "BeamRing" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// int start - -// end - -// modelindex - -// startframe - -// framerate - -// msg_dest - -// delay - -// origin - -// recipient - -//----------------------------------------------------------------------------- -void TE_BeamRing( IRecipientFilter& filter, float delay, - int start, int end, int modelindex, int haloindex, int startframe, int framerate, - float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags ) -{ - g_TEBeamRing.m_nStartEntity = (start & 0x0FFF) | ((1 & 0xF)<<12); - g_TEBeamRing.m_nEndEntity = (end & 0x0FFF) | ((1 & 0xF)<<12); - g_TEBeamRing.m_nModelIndex = modelindex; - g_TEBeamRing.m_nHaloIndex = haloindex; - g_TEBeamRing.m_nStartFrame = startframe; - g_TEBeamRing.m_nFrameRate = framerate; - g_TEBeamRing.m_fLife = life; - g_TEBeamRing.m_fWidth = width; - g_TEBeamRing.m_fEndWidth = width; - g_TEBeamRing.m_nFadeLength = 0; - g_TEBeamRing.m_fAmplitude = amplitude; - g_TEBeamRing.m_nSpeed = speed; - g_TEBeamRing.r = r; - g_TEBeamRing.g = g; - g_TEBeamRing.b = b; - g_TEBeamRing.a = a; - g_TEBeamRing.m_nFlags = flags; - - // Send it over the wire - g_TEBeamRing.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "te_basebeam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches a beam ring between two entities +//----------------------------------------------------------------------------- +class CTEBeamRing : public CTEBaseBeam +{ +public: + DECLARE_CLASS( CTEBeamRing, CTEBaseBeam ); + DECLARE_SERVERCLASS(); + + CTEBeamRing( const char *name ); + virtual ~CTEBeamRing( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + +public: + CNetworkVar( int, m_nStartEntity ); + CNetworkVar( int, m_nEndEntity ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBeamRing::CTEBeamRing( const char *name ) : + CTEBaseBeam( name ) +{ + m_nStartEntity = 0; + m_nEndEntity = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBeamRing::~CTEBeamRing( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBeamRing::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + m_nStartEntity = 1; + m_nEndEntity = 0; + + m_nModelIndex = g_sModelIndexSmoke; + m_nStartFrame = 0; + m_nFrameRate = 2; + m_fLife = 10.0; + m_fWidth = 2.0; + m_fAmplitude = 1; + r = 255; + g = 255; + b = 0; + a = 127; + m_nSpeed = 5; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + + +IMPLEMENT_SERVERCLASS_ST( CTEBeamRing, DT_TEBeamRing) + SendPropInt( SENDINFO(m_nStartEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nEndEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEBeamRing objects +static CTEBeamRing g_TEBeamRing( "BeamRing" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// int start - +// end - +// modelindex - +// startframe - +// framerate - +// msg_dest - +// delay - +// origin - +// recipient - +//----------------------------------------------------------------------------- +void TE_BeamRing( IRecipientFilter& filter, float delay, + int start, int end, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags ) +{ + g_TEBeamRing.m_nStartEntity = (start & 0x0FFF) | ((1 & 0xF)<<12); + g_TEBeamRing.m_nEndEntity = (end & 0x0FFF) | ((1 & 0xF)<<12); + g_TEBeamRing.m_nModelIndex = modelindex; + g_TEBeamRing.m_nHaloIndex = haloindex; + g_TEBeamRing.m_nStartFrame = startframe; + g_TEBeamRing.m_nFrameRate = framerate; + g_TEBeamRing.m_fLife = life; + g_TEBeamRing.m_fWidth = width; + g_TEBeamRing.m_fEndWidth = width; + g_TEBeamRing.m_nFadeLength = 0; + g_TEBeamRing.m_fAmplitude = amplitude; + g_TEBeamRing.m_nSpeed = speed; + g_TEBeamRing.r = r; + g_TEBeamRing.g = g; + g_TEBeamRing.b = b; + g_TEBeamRing.a = a; + g_TEBeamRing.m_nFlags = flags; + + // Send it over the wire + g_TEBeamRing.Create( filter, delay ); +} diff --git a/dlls/te_beamringpoint.cpp b/dlls/te_beamringpoint.cpp index 8d4d2f1d..f97eed43 100644 --- a/dlls/te_beamringpoint.cpp +++ b/dlls/te_beamringpoint.cpp @@ -1,124 +1,124 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "te_basebeam.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches a beam ring between two entities -//----------------------------------------------------------------------------- -class CTEBeamRingPoint : public CTEBaseBeam -{ -public: - DECLARE_CLASS( CTEBeamRingPoint, CTEBaseBeam ); - DECLARE_SERVERCLASS(); - - CTEBeamRingPoint( const char *name ); - virtual ~CTEBeamRingPoint( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - -public: - CNetworkVector( m_vecCenter ); - CNetworkVar( float, m_flStartRadius ); - CNetworkVar( float, m_flEndRadius ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBeamRingPoint::CTEBeamRingPoint( const char *name ) : - CTEBaseBeam( name ) -{ - m_vecCenter.Init(); - m_flStartRadius = 0.0f; - m_flEndRadius = 0.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBeamRingPoint::~CTEBeamRingPoint( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBeamRingPoint::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - m_vecCenter = current_origin; - m_flEndRadius = 256.0f; - m_flStartRadius = 16.0f; - - m_nModelIndex = g_sModelIndexSmoke; - m_nStartFrame = 0; - m_nFrameRate = 2; - m_fLife = 10.0; - m_fWidth = 2.0; - m_fAmplitude = 1; - r = 255; - g = 255; - b = 0; - a = 127; - m_nSpeed = 5; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - - -IMPLEMENT_SERVERCLASS_ST( CTEBeamRingPoint, DT_TEBeamRingPoint) - SendPropVector( SENDINFO(m_vecCenter), -1, SPROP_COORD ), - SendPropFloat( SENDINFO(m_flStartRadius), 16, SPROP_ROUNDUP, 0.0f, 4096.0f ), - SendPropFloat( SENDINFO(m_flEndRadius), 16, SPROP_ROUNDUP, 0.0f, 4096.0f ), -END_SEND_TABLE() - - -// Singleton to fire TEBeamRingPoint objects -static CTEBeamRingPoint g_TEBeamRingPoint( "BeamRingPoint" ); - -void TE_BeamRingPoint( IRecipientFilter& filter, float delay, - const Vector& center, float start_radius, float end_radius, int modelindex, int haloindex, int startframe, int framerate, - float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags ) -{ - g_TEBeamRingPoint.m_vecCenter = center; - g_TEBeamRingPoint.m_flStartRadius = start_radius; - g_TEBeamRingPoint.m_flEndRadius = end_radius; - g_TEBeamRingPoint.m_nModelIndex = modelindex; - g_TEBeamRingPoint.m_nHaloIndex = haloindex; - g_TEBeamRingPoint.m_nStartFrame = startframe; - g_TEBeamRingPoint.m_nFrameRate = framerate; - g_TEBeamRingPoint.m_fLife = life; - g_TEBeamRingPoint.m_fWidth = width; - g_TEBeamRingPoint.m_fEndWidth = width; - g_TEBeamRingPoint.m_nFadeLength = 0; - g_TEBeamRingPoint.m_fAmplitude = amplitude; - g_TEBeamRingPoint.m_nSpeed = speed; - g_TEBeamRingPoint.r = r; - g_TEBeamRingPoint.g = g; - g_TEBeamRingPoint.b = b; - g_TEBeamRingPoint.a = a; - g_TEBeamRingPoint.m_nFlags = flags; - - // Send it over the wire - g_TEBeamRingPoint.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "te_basebeam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches a beam ring between two entities +//----------------------------------------------------------------------------- +class CTEBeamRingPoint : public CTEBaseBeam +{ +public: + DECLARE_CLASS( CTEBeamRingPoint, CTEBaseBeam ); + DECLARE_SERVERCLASS(); + + CTEBeamRingPoint( const char *name ); + virtual ~CTEBeamRingPoint( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + +public: + CNetworkVector( m_vecCenter ); + CNetworkVar( float, m_flStartRadius ); + CNetworkVar( float, m_flEndRadius ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBeamRingPoint::CTEBeamRingPoint( const char *name ) : + CTEBaseBeam( name ) +{ + m_vecCenter.Init(); + m_flStartRadius = 0.0f; + m_flEndRadius = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBeamRingPoint::~CTEBeamRingPoint( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBeamRingPoint::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + m_vecCenter = current_origin; + m_flEndRadius = 256.0f; + m_flStartRadius = 16.0f; + + m_nModelIndex = g_sModelIndexSmoke; + m_nStartFrame = 0; + m_nFrameRate = 2; + m_fLife = 10.0; + m_fWidth = 2.0; + m_fAmplitude = 1; + r = 255; + g = 255; + b = 0; + a = 127; + m_nSpeed = 5; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + + +IMPLEMENT_SERVERCLASS_ST( CTEBeamRingPoint, DT_TEBeamRingPoint) + SendPropVector( SENDINFO(m_vecCenter), -1, SPROP_COORD ), + SendPropFloat( SENDINFO(m_flStartRadius), 16, SPROP_ROUNDUP, 0.0f, 4096.0f ), + SendPropFloat( SENDINFO(m_flEndRadius), 16, SPROP_ROUNDUP, 0.0f, 4096.0f ), +END_SEND_TABLE() + + +// Singleton to fire TEBeamRingPoint objects +static CTEBeamRingPoint g_TEBeamRingPoint( "BeamRingPoint" ); + +void TE_BeamRingPoint( IRecipientFilter& filter, float delay, + const Vector& center, float start_radius, float end_radius, int modelindex, int haloindex, int startframe, int framerate, + float life, float width, int spread, float amplitude, int r, int g, int b, int a, int speed, int flags ) +{ + g_TEBeamRingPoint.m_vecCenter = center; + g_TEBeamRingPoint.m_flStartRadius = start_radius; + g_TEBeamRingPoint.m_flEndRadius = end_radius; + g_TEBeamRingPoint.m_nModelIndex = modelindex; + g_TEBeamRingPoint.m_nHaloIndex = haloindex; + g_TEBeamRingPoint.m_nStartFrame = startframe; + g_TEBeamRingPoint.m_nFrameRate = framerate; + g_TEBeamRingPoint.m_fLife = life; + g_TEBeamRingPoint.m_fWidth = width; + g_TEBeamRingPoint.m_fEndWidth = width; + g_TEBeamRingPoint.m_nFadeLength = 0; + g_TEBeamRingPoint.m_fAmplitude = amplitude; + g_TEBeamRingPoint.m_nSpeed = speed; + g_TEBeamRingPoint.r = r; + g_TEBeamRingPoint.g = g; + g_TEBeamRingPoint.b = b; + g_TEBeamRingPoint.a = a; + g_TEBeamRingPoint.m_nFlags = flags; + + // Send it over the wire + g_TEBeamRingPoint.Create( filter, delay ); +} diff --git a/dlls/te_beamspline.cpp b/dlls/te_beamspline.cpp index 637fce13..77fefce5 100644 --- a/dlls/te_beamspline.cpp +++ b/dlls/te_beamspline.cpp @@ -1,134 +1,134 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define MAX_SPLINE_POINTS 16 -//----------------------------------------------------------------------------- -// Purpose: Dispatches beam spline tempentity -//----------------------------------------------------------------------------- -class CTEBeamSpline : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEBeamSpline, CBaseTempEntity ); - - CTEBeamSpline( const char *name ); - virtual ~CTEBeamSpline( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkArray( Vector, m_vecPoints, MAX_SPLINE_POINTS ); - CNetworkVar( int, m_nPoints ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBeamSpline::CTEBeamSpline( const char *name ) : - CBaseTempEntity( name ) -{ - int i; - for ( i = 0; i < MAX_SPLINE_POINTS; i++ ) - { - m_vecPoints.GetForModify( i ).Init(); - } - m_nPoints = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBeamSpline::~CTEBeamSpline( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBeamSpline::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nPoints = 6; - Vector m_vecStart = current_origin; - - Vector forward, right; - - m_vecStart[2] += 24; - - AngleVectors( current_angles, &forward, &right, 0 ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecStart, 100.0, forward, m_vecStart ); - - VectorMA( m_vecStart, -128.0, right, m_vecStart ); - - for ( int i = 0; i < m_nPoints; i++ ) - { - m_vecPoints.Set( i, m_vecStart ); - VectorMA( m_vecStart, 128/m_nPoints, right, m_vecStart ); - VectorMA( m_vecStart, 30.0/m_nPoints, forward, m_vecStart ); - } - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEBeamSpline, DT_TEBeamSpline) - SendPropInt( SENDINFO( m_nPoints ), 5, SPROP_UNSIGNED ), - - SendPropArray( - SendPropVector( SENDINFO_ARRAY(m_vecPoints), -1, SPROP_COORD), - m_vecPoints) -END_SEND_TABLE() - - -// Singleton to fire TEBeamSpline objects -static CTEBeamSpline g_TEBeamSpline( "BeamSpline" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// points - -// *points - -//----------------------------------------------------------------------------- -void TE_BeamSpline( IRecipientFilter& filter, float delay, - int points, Vector* rgPoints ) -{ - int i; - g_TEBeamSpline.m_nPoints = points; - for ( i = 0; i < points; i++ ) - { - g_TEBeamSpline.m_vecPoints.Set( i, rgPoints[ i ] ); - } - - for ( ; i < MAX_SPLINE_POINTS; i++ ) - { - g_TEBeamSpline.m_vecPoints.GetForModify( i ).Init(); - } - - // Send it over the wire - g_TEBeamSpline.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define MAX_SPLINE_POINTS 16 +//----------------------------------------------------------------------------- +// Purpose: Dispatches beam spline tempentity +//----------------------------------------------------------------------------- +class CTEBeamSpline : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEBeamSpline, CBaseTempEntity ); + + CTEBeamSpline( const char *name ); + virtual ~CTEBeamSpline( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkArray( Vector, m_vecPoints, MAX_SPLINE_POINTS ); + CNetworkVar( int, m_nPoints ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBeamSpline::CTEBeamSpline( const char *name ) : + CBaseTempEntity( name ) +{ + int i; + for ( i = 0; i < MAX_SPLINE_POINTS; i++ ) + { + m_vecPoints.GetForModify( i ).Init(); + } + m_nPoints = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBeamSpline::~CTEBeamSpline( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBeamSpline::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nPoints = 6; + Vector m_vecStart = current_origin; + + Vector forward, right; + + m_vecStart[2] += 24; + + AngleVectors( current_angles, &forward, &right, 0 ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecStart, 100.0, forward, m_vecStart ); + + VectorMA( m_vecStart, -128.0, right, m_vecStart ); + + for ( int i = 0; i < m_nPoints; i++ ) + { + m_vecPoints.Set( i, m_vecStart ); + VectorMA( m_vecStart, 128/m_nPoints, right, m_vecStart ); + VectorMA( m_vecStart, 30.0/m_nPoints, forward, m_vecStart ); + } + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEBeamSpline, DT_TEBeamSpline) + SendPropInt( SENDINFO( m_nPoints ), 5, SPROP_UNSIGNED ), + + SendPropArray( + SendPropVector( SENDINFO_ARRAY(m_vecPoints), -1, SPROP_COORD), + m_vecPoints) +END_SEND_TABLE() + + +// Singleton to fire TEBeamSpline objects +static CTEBeamSpline g_TEBeamSpline( "BeamSpline" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// points - +// *points - +//----------------------------------------------------------------------------- +void TE_BeamSpline( IRecipientFilter& filter, float delay, + int points, Vector* rgPoints ) +{ + int i; + g_TEBeamSpline.m_nPoints = points; + for ( i = 0; i < points; i++ ) + { + g_TEBeamSpline.m_vecPoints.Set( i, rgPoints[ i ] ); + } + + for ( ; i < MAX_SPLINE_POINTS; i++ ) + { + g_TEBeamSpline.m_vecPoints.GetForModify( i ).Init(); + } + + // Send it over the wire + g_TEBeamSpline.Create( filter, delay ); +} diff --git a/dlls/te_bloodsprite.cpp b/dlls/te_bloodsprite.cpp index 5b54c119..dbcc961b 100644 --- a/dlls/te_bloodsprite.cpp +++ b/dlls/te_bloodsprite.cpp @@ -1,151 +1,151 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexBloodDrop; // (in combatweapon.cpp) holds the sprite index for the initial blood -extern short g_sModelIndexBloodSpray; // (in combatweapon.cpp) holds the sprite index for splattered blood - -//----------------------------------------------------------------------------- -// Purpose: Display's a blood sprite -//----------------------------------------------------------------------------- -class CTEBloodSprite : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEBloodSprite, CBaseTempEntity ); - - CTEBloodSprite( const char *name ); - virtual ~CTEBloodSprite( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVector( m_vecDirection ); - CNetworkVar( int, m_nSprayModel ); - CNetworkVar( int, m_nDropModel ); - CNetworkVar( int, r ); - CNetworkVar( int, g ); - CNetworkVar( int, b ); - CNetworkVar( int, a ); - CNetworkVar( int, m_nSize ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBloodSprite::CTEBloodSprite( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_nSprayModel = 0; - m_nDropModel = 0; - r = 0; - g = 0; - b = 0; - a = 0; - m_nSize = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBloodSprite::~CTEBloodSprite( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBloodSprite::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - r = 255; - g = 255; - b = 63; - a = 255; - m_nSize = 16; - m_vecOrigin = current_origin; - - m_nSprayModel = g_sModelIndexBloodSpray; - m_nDropModel = g_sModelIndexBloodDrop; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEBloodSprite, DT_TEBloodSprite) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropVector( SENDINFO(m_vecDirection), -1, SPROP_COORD), - SendPropInt( SENDINFO(r), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(g), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(b), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(a), 8, SPROP_UNSIGNED ), - SendPropModelIndex( SENDINFO(m_nSprayModel) ), - SendPropModelIndex( SENDINFO(m_nDropModel) ), - SendPropInt( SENDINFO(m_nSize), 8, SPROP_UNSIGNED ), -END_SEND_TABLE() - -// Singleton -static CTEBloodSprite g_TEBloodSprite( "Blood Sprite" ); - -//----------------------------------------------------------------------------- -// Purpose: Public interface -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *org - -// r - -// g - -// b - -// a - -// size - -//----------------------------------------------------------------------------- -void TE_BloodSprite( IRecipientFilter& filter, float delay, - const Vector *org, const Vector *dir, int r, int g, int b, int a, int size ) -{ - // Set up parameters - g_TEBloodSprite.m_vecOrigin = *org; - g_TEBloodSprite.m_vecDirection = *dir; - g_TEBloodSprite.r = r; - g_TEBloodSprite.g = g; - g_TEBloodSprite.b = b; - g_TEBloodSprite.a = a; - g_TEBloodSprite.m_nSize = size; - - // Implicit - g_TEBloodSprite.m_nSprayModel = g_sModelIndexBloodSpray; - g_TEBloodSprite.m_nDropModel = g_sModelIndexBloodDrop; - - // Create it - g_TEBloodSprite.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexBloodDrop; // (in combatweapon.cpp) holds the sprite index for the initial blood +extern short g_sModelIndexBloodSpray; // (in combatweapon.cpp) holds the sprite index for splattered blood + +//----------------------------------------------------------------------------- +// Purpose: Display's a blood sprite +//----------------------------------------------------------------------------- +class CTEBloodSprite : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEBloodSprite, CBaseTempEntity ); + + CTEBloodSprite( const char *name ); + virtual ~CTEBloodSprite( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVector( m_vecDirection ); + CNetworkVar( int, m_nSprayModel ); + CNetworkVar( int, m_nDropModel ); + CNetworkVar( int, r ); + CNetworkVar( int, g ); + CNetworkVar( int, b ); + CNetworkVar( int, a ); + CNetworkVar( int, m_nSize ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBloodSprite::CTEBloodSprite( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_nSprayModel = 0; + m_nDropModel = 0; + r = 0; + g = 0; + b = 0; + a = 0; + m_nSize = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBloodSprite::~CTEBloodSprite( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBloodSprite::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + r = 255; + g = 255; + b = 63; + a = 255; + m_nSize = 16; + m_vecOrigin = current_origin; + + m_nSprayModel = g_sModelIndexBloodSpray; + m_nDropModel = g_sModelIndexBloodDrop; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEBloodSprite, DT_TEBloodSprite) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropVector( SENDINFO(m_vecDirection), -1, SPROP_COORD), + SendPropInt( SENDINFO(r), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(g), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(b), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(a), 8, SPROP_UNSIGNED ), + SendPropModelIndex( SENDINFO(m_nSprayModel) ), + SendPropModelIndex( SENDINFO(m_nDropModel) ), + SendPropInt( SENDINFO(m_nSize), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + +// Singleton +static CTEBloodSprite g_TEBloodSprite( "Blood Sprite" ); + +//----------------------------------------------------------------------------- +// Purpose: Public interface +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *org - +// r - +// g - +// b - +// a - +// size - +//----------------------------------------------------------------------------- +void TE_BloodSprite( IRecipientFilter& filter, float delay, + const Vector *org, const Vector *dir, int r, int g, int b, int a, int size ) +{ + // Set up parameters + g_TEBloodSprite.m_vecOrigin = *org; + g_TEBloodSprite.m_vecDirection = *dir; + g_TEBloodSprite.r = r; + g_TEBloodSprite.g = g; + g_TEBloodSprite.b = b; + g_TEBloodSprite.a = a; + g_TEBloodSprite.m_nSize = size; + + // Implicit + g_TEBloodSprite.m_nSprayModel = g_sModelIndexBloodSpray; + g_TEBloodSprite.m_nDropModel = g_sModelIndexBloodDrop; + + // Create it + g_TEBloodSprite.Create( filter, delay ); +} diff --git a/dlls/te_bloodstream.cpp b/dlls/te_bloodstream.cpp index d723c735..a5f9787c 100644 --- a/dlls/te_bloodstream.cpp +++ b/dlls/te_bloodstream.cpp @@ -1,135 +1,135 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "te_particlesystem.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches blood stream tempentity -//----------------------------------------------------------------------------- -class CTEBloodStream : public CTEParticleSystem -{ -public: - DECLARE_CLASS( CTEBloodStream, CTEParticleSystem ); - - CTEBloodStream( const char *name ); - virtual ~CTEBloodStream( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecDirection ); - CNetworkVar( int, r ); - CNetworkVar( int, g ); - CNetworkVar( int, b ); - CNetworkVar( int, a ); - CNetworkVar( int, m_nAmount ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBloodStream::CTEBloodStream( const char *name ) : - BaseClass( name ) -{ - m_vecDirection.Init(); - r = 0; - g = 0; - b = 0; - a = 0; - m_nAmount = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBloodStream::~CTEBloodStream( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBloodStream::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - r = 247; - g = 0; - b = 0; - a = 255; - m_nAmount = random->RandomInt(50, 150); - m_vecOrigin = current_origin; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - m_vecOrigin += forward * 50; - - m_vecDirection = UTIL_RandomBloodVector(); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEBloodStream, DT_TEBloodStream) - SendPropVector( SENDINFO(m_vecDirection), 11, 0, -10.0, 10.0 ), - SendPropInt( SENDINFO(r), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(g), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(b), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(a), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nAmount), 8, SPROP_UNSIGNED ), -END_SEND_TABLE() - -// Singleton to fire TEBloodStream objects -static CTEBloodStream g_TEBloodStream( "Blood Stream" ); - -//----------------------------------------------------------------------------- -// Purpose: Creates a blood stream -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *org - -// *dir - -// r - -// g - -// b - -// a - -// amount - -//----------------------------------------------------------------------------- -void TE_BloodStream( IRecipientFilter& filter, float delay, - const Vector* org, const Vector* dir, int r, int g, int b, int a, int amount ) -{ - g_TEBloodStream.m_vecOrigin = *org; - g_TEBloodStream.m_vecDirection = *dir; - g_TEBloodStream.r = r; - g_TEBloodStream.g = g; - g_TEBloodStream.b = b; - g_TEBloodStream.a = a; - g_TEBloodStream.m_nAmount = amount; - - // Send it over the wire - g_TEBloodStream.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "te_particlesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches blood stream tempentity +//----------------------------------------------------------------------------- +class CTEBloodStream : public CTEParticleSystem +{ +public: + DECLARE_CLASS( CTEBloodStream, CTEParticleSystem ); + + CTEBloodStream( const char *name ); + virtual ~CTEBloodStream( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecDirection ); + CNetworkVar( int, r ); + CNetworkVar( int, g ); + CNetworkVar( int, b ); + CNetworkVar( int, a ); + CNetworkVar( int, m_nAmount ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBloodStream::CTEBloodStream( const char *name ) : + BaseClass( name ) +{ + m_vecDirection.Init(); + r = 0; + g = 0; + b = 0; + a = 0; + m_nAmount = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBloodStream::~CTEBloodStream( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBloodStream::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + r = 247; + g = 0; + b = 0; + a = 255; + m_nAmount = random->RandomInt(50, 150); + m_vecOrigin = current_origin; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + m_vecOrigin += forward * 50; + + m_vecDirection = UTIL_RandomBloodVector(); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEBloodStream, DT_TEBloodStream) + SendPropVector( SENDINFO(m_vecDirection), 11, 0, -10.0, 10.0 ), + SendPropInt( SENDINFO(r), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(g), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(b), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(a), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nAmount), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + +// Singleton to fire TEBloodStream objects +static CTEBloodStream g_TEBloodStream( "Blood Stream" ); + +//----------------------------------------------------------------------------- +// Purpose: Creates a blood stream +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *org - +// *dir - +// r - +// g - +// b - +// a - +// amount - +//----------------------------------------------------------------------------- +void TE_BloodStream( IRecipientFilter& filter, float delay, + const Vector* org, const Vector* dir, int r, int g, int b, int a, int amount ) +{ + g_TEBloodStream.m_vecOrigin = *org; + g_TEBloodStream.m_vecDirection = *dir; + g_TEBloodStream.r = r; + g_TEBloodStream.g = g; + g_TEBloodStream.b = b; + g_TEBloodStream.a = a; + g_TEBloodStream.m_nAmount = amount; + + // Send it over the wire + g_TEBloodStream.Create( filter, delay ); +} diff --git a/dlls/te_breakmodel.cpp b/dlls/te_breakmodel.cpp index 43cfced7..bfc8e689 100644 --- a/dlls/te_breakmodel.cpp +++ b/dlls/te_breakmodel.cpp @@ -1,146 +1,146 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" -#include "vstdlib/random.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches model smash pieces -//----------------------------------------------------------------------------- -class CTEBreakModel : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEBreakModel, CBaseTempEntity ); - - CTEBreakModel( const char *name ); - virtual ~CTEBreakModel( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - virtual void Precache( void ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVector( m_vecSize ); - CNetworkVector( m_vecVelocity ); - CNetworkQAngle( m_angRotation ); - CNetworkVar( int, m_nRandomization ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( int, m_nCount ); - CNetworkVar( float, m_fTime ); - CNetworkVar( int, m_nFlags ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBreakModel::CTEBreakModel( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_vecSize.Init(); - m_vecVelocity.Init(); - m_angRotation.Init(); - m_nModelIndex = 0; - m_nRandomization = 0; - m_nCount = 0; - m_fTime = 0.0; - m_nFlags = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBreakModel::~CTEBreakModel( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CTEBreakModel::Precache( void ) -{ - CBaseEntity::PrecacheModel( "models/gibs/hgibs.mdl" ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBreakModel::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = CBaseEntity::PrecacheModel( "models/gibs/hgibs.mdl" ); - m_vecOrigin = current_origin; - m_angRotation = current_angles; - m_vecSize.Init( 16, 16, 16 ); - - m_vecVelocity.Init( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( 0, 20 ) ); - - m_nRandomization = 100; - m_nCount = 10; - m_fTime = 5.0; - m_nFlags = 0; - - Vector forward, right; - - m_vecOrigin += Vector( 0, 0, 24 ); - - AngleVectors( current_angles, &forward, &right, 0 ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, 25.0, right, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEBreakModel, DT_TEBreakModel) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 13 ), - SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 13 ), - SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 13 ), - SendPropVector( SENDINFO(m_vecSize), -1, SPROP_COORD), - SendPropVector( SENDINFO(m_vecVelocity), -1, SPROP_COORD), - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropInt( SENDINFO(m_nRandomization), 9, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nCount), 8, SPROP_UNSIGNED ), - SendPropFloat( SENDINFO(m_fTime), 10, 0, 0, 102.4 ), - SendPropInt( SENDINFO(m_nFlags), 8, SPROP_UNSIGNED ), -END_SEND_TABLE() - -// Singleton to fire TEBreakModel objects -static CTEBreakModel g_TEBreakModel( "breakmodel" ); - -void TE_BreakModel( IRecipientFilter& filter, float delay, - const Vector& pos, const QAngle& angles, const Vector& size, const Vector& vel, int modelindex, int randomization, - int count, float time, int flags ) -{ - g_TEBreakModel.m_vecOrigin = pos; - g_TEBreakModel.m_angRotation = angles; - g_TEBreakModel.m_vecSize = size; - g_TEBreakModel.m_vecVelocity = vel; - g_TEBreakModel.m_nModelIndex = modelindex; - g_TEBreakModel.m_nRandomization = randomization; - g_TEBreakModel.m_nCount = count; - g_TEBreakModel.m_fTime = time; - g_TEBreakModel.m_nFlags = flags; - - // Send it over the wire - g_TEBreakModel.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" +#include "vstdlib/random.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches model smash pieces +//----------------------------------------------------------------------------- +class CTEBreakModel : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEBreakModel, CBaseTempEntity ); + + CTEBreakModel( const char *name ); + virtual ~CTEBreakModel( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + virtual void Precache( void ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVector( m_vecSize ); + CNetworkVector( m_vecVelocity ); + CNetworkQAngle( m_angRotation ); + CNetworkVar( int, m_nRandomization ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( int, m_nCount ); + CNetworkVar( float, m_fTime ); + CNetworkVar( int, m_nFlags ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBreakModel::CTEBreakModel( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_vecSize.Init(); + m_vecVelocity.Init(); + m_angRotation.Init(); + m_nModelIndex = 0; + m_nRandomization = 0; + m_nCount = 0; + m_fTime = 0.0; + m_nFlags = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBreakModel::~CTEBreakModel( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTEBreakModel::Precache( void ) +{ + CBaseEntity::PrecacheModel( "models/gibs/hgibs.mdl" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBreakModel::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = CBaseEntity::PrecacheModel( "models/gibs/hgibs.mdl" ); + m_vecOrigin = current_origin; + m_angRotation = current_angles; + m_vecSize.Init( 16, 16, 16 ); + + m_vecVelocity.Init( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( 0, 20 ) ); + + m_nRandomization = 100; + m_nCount = 10; + m_fTime = 5.0; + m_nFlags = 0; + + Vector forward, right; + + m_vecOrigin += Vector( 0, 0, 24 ); + + AngleVectors( current_angles, &forward, &right, 0 ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, 25.0, right, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEBreakModel, DT_TEBreakModel) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 13 ), + SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 13 ), + SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 13 ), + SendPropVector( SENDINFO(m_vecSize), -1, SPROP_COORD), + SendPropVector( SENDINFO(m_vecVelocity), -1, SPROP_COORD), + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropInt( SENDINFO(m_nRandomization), 9, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nCount), 8, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO(m_fTime), 10, 0, 0, 102.4 ), + SendPropInt( SENDINFO(m_nFlags), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + +// Singleton to fire TEBreakModel objects +static CTEBreakModel g_TEBreakModel( "breakmodel" ); + +void TE_BreakModel( IRecipientFilter& filter, float delay, + const Vector& pos, const QAngle& angles, const Vector& size, const Vector& vel, int modelindex, int randomization, + int count, float time, int flags ) +{ + g_TEBreakModel.m_vecOrigin = pos; + g_TEBreakModel.m_angRotation = angles; + g_TEBreakModel.m_vecSize = size; + g_TEBreakModel.m_vecVelocity = vel; + g_TEBreakModel.m_nModelIndex = modelindex; + g_TEBreakModel.m_nRandomization = randomization; + g_TEBreakModel.m_nCount = count; + g_TEBreakModel.m_fTime = time; + g_TEBreakModel.m_nFlags = flags; + + // Send it over the wire + g_TEBreakModel.Create( filter, delay ); +} diff --git a/dlls/te_bspdecal.cpp b/dlls/te_bspdecal.cpp index fd35901e..5572b856 100644 --- a/dlls/te_bspdecal.cpp +++ b/dlls/te_bspdecal.cpp @@ -1,125 +1,125 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches BSP decal tempentity -//----------------------------------------------------------------------------- -class CTEBSPDecal : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEBSPDecal, CBaseTempEntity ); - - CTEBSPDecal( const char *name ); - virtual ~CTEBSPDecal( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVar( int, m_nEntity ); - CNetworkVar( int, m_nIndex ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBSPDecal::CTEBSPDecal( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_nEntity = 0; - m_nIndex = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBSPDecal::~CTEBSPDecal( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBSPDecal::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nEntity = 0; - m_nIndex = 0; - m_vecOrigin = current_origin; - - Vector vecEnd; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, 1024.0, forward, vecEnd ); - - trace_t tr; - - UTIL_TraceLine( m_vecOrigin, vecEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); - - m_vecOrigin = tr.endpos; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - - -IMPLEMENT_SERVERCLASS_ST(CTEBSPDecal, DT_TEBSPDecal) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropInt( SENDINFO(m_nEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nIndex), 9, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEBSPDecal objects -static CTEBSPDecal g_TEBSPDecal( "BSP Decal" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *pos - -// entity - -// index - -// modelindex - -//----------------------------------------------------------------------------- -void TE_BSPDecal( IRecipientFilter& filter, float delay, - const Vector* pos, int entity, int index ) -{ - g_TEBSPDecal.m_vecOrigin = *pos; - g_TEBSPDecal.m_nEntity = entity; - g_TEBSPDecal.m_nIndex = index; - - // Send it over the wire - g_TEBSPDecal.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches BSP decal tempentity +//----------------------------------------------------------------------------- +class CTEBSPDecal : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEBSPDecal, CBaseTempEntity ); + + CTEBSPDecal( const char *name ); + virtual ~CTEBSPDecal( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVar( int, m_nEntity ); + CNetworkVar( int, m_nIndex ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBSPDecal::CTEBSPDecal( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_nEntity = 0; + m_nIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBSPDecal::~CTEBSPDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBSPDecal::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nEntity = 0; + m_nIndex = 0; + m_vecOrigin = current_origin; + + Vector vecEnd; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, 1024.0, forward, vecEnd ); + + trace_t tr; + + UTIL_TraceLine( m_vecOrigin, vecEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + m_vecOrigin = tr.endpos; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + + +IMPLEMENT_SERVERCLASS_ST(CTEBSPDecal, DT_TEBSPDecal) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropInt( SENDINFO(m_nEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nIndex), 9, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEBSPDecal objects +static CTEBSPDecal g_TEBSPDecal( "BSP Decal" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *pos - +// entity - +// index - +// modelindex - +//----------------------------------------------------------------------------- +void TE_BSPDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int entity, int index ) +{ + g_TEBSPDecal.m_vecOrigin = *pos; + g_TEBSPDecal.m_nEntity = entity; + g_TEBSPDecal.m_nIndex = index; + + // Send it over the wire + g_TEBSPDecal.Create( filter, delay ); +} diff --git a/dlls/te_bubbles.cpp b/dlls/te_bubbles.cpp index 04495726..ab4f03d3 100644 --- a/dlls/te_bubbles.cpp +++ b/dlls/te_bubbles.cpp @@ -1,137 +1,137 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexBubbles;// holds the index for the bubbles model - -//----------------------------------------------------------------------------- -// Purpose: Dispatches bubbles -//----------------------------------------------------------------------------- -class CTEBubbles : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEBubbles, CBaseTempEntity ); - - CTEBubbles( const char *name ); - virtual ~CTEBubbles( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecMins ); - CNetworkVector( m_vecMaxs ); - CNetworkVar( float, m_fHeight ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( int, m_nCount ); - CNetworkVar( float, m_fSpeed ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBubbles::CTEBubbles( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecMins.Init(); - m_vecMaxs.Init(); - m_fHeight = 0.0; - m_nModelIndex = 0; - m_nCount = 0; - m_fSpeed = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBubbles::~CTEBubbles( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBubbles::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_vecMins = current_origin; - - Vector forward; - - m_vecMins.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecMins, 100.0, forward, m_vecMins.GetForModify() ); - - m_vecMaxs = m_vecMins + Vector( 256, 256, 256 ); - - m_fSpeed = 2; - m_nCount = 50; - m_fHeight = 256; - - m_nModelIndex = g_sModelIndexBubbles; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEBubbles, DT_TEBubbles) - SendPropVector( SENDINFO(m_vecMins), -1, SPROP_COORD), - SendPropVector( SENDINFO(m_vecMaxs), -1, SPROP_COORD), - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropFloat( SENDINFO(m_fHeight ), 17, 0, MIN_COORD_INTEGER, MAX_COORD_INTEGER ), - SendPropInt( SENDINFO(m_nCount), 8, SPROP_UNSIGNED ), - SendPropFloat( SENDINFO(m_fSpeed ), 17, 0, MIN_COORD_INTEGER, MAX_COORD_INTEGER ), -END_SEND_TABLE() - - -// Singleton to fire TEBubbles objects -static CTEBubbles g_TEBubbles( "Bubbles" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *mins - -// *maxs - -// height - -// modelindex - -// count - -// speed - -//----------------------------------------------------------------------------- -void TE_Bubbles( IRecipientFilter& filter, float delay, - const Vector* mins, const Vector* maxs, float height, int modelindex, int count, float speed ) -{ - g_TEBubbles.m_vecMins = *mins; - g_TEBubbles.m_vecMaxs = *maxs; - g_TEBubbles.m_fHeight = height; - g_TEBubbles.m_nModelIndex = modelindex; - g_TEBubbles.m_nCount = count; - g_TEBubbles.m_fSpeed = speed; - - // Send it over the wire - g_TEBubbles.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexBubbles;// holds the index for the bubbles model + +//----------------------------------------------------------------------------- +// Purpose: Dispatches bubbles +//----------------------------------------------------------------------------- +class CTEBubbles : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEBubbles, CBaseTempEntity ); + + CTEBubbles( const char *name ); + virtual ~CTEBubbles( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecMins ); + CNetworkVector( m_vecMaxs ); + CNetworkVar( float, m_fHeight ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( int, m_nCount ); + CNetworkVar( float, m_fSpeed ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBubbles::CTEBubbles( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecMins.Init(); + m_vecMaxs.Init(); + m_fHeight = 0.0; + m_nModelIndex = 0; + m_nCount = 0; + m_fSpeed = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBubbles::~CTEBubbles( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBubbles::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_vecMins = current_origin; + + Vector forward; + + m_vecMins.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecMins, 100.0, forward, m_vecMins.GetForModify() ); + + m_vecMaxs = m_vecMins + Vector( 256, 256, 256 ); + + m_fSpeed = 2; + m_nCount = 50; + m_fHeight = 256; + + m_nModelIndex = g_sModelIndexBubbles; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEBubbles, DT_TEBubbles) + SendPropVector( SENDINFO(m_vecMins), -1, SPROP_COORD), + SendPropVector( SENDINFO(m_vecMaxs), -1, SPROP_COORD), + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropFloat( SENDINFO(m_fHeight ), 17, 0, MIN_COORD_INTEGER, MAX_COORD_INTEGER ), + SendPropInt( SENDINFO(m_nCount), 8, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO(m_fSpeed ), 17, 0, MIN_COORD_INTEGER, MAX_COORD_INTEGER ), +END_SEND_TABLE() + + +// Singleton to fire TEBubbles objects +static CTEBubbles g_TEBubbles( "Bubbles" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *mins - +// *maxs - +// height - +// modelindex - +// count - +// speed - +//----------------------------------------------------------------------------- +void TE_Bubbles( IRecipientFilter& filter, float delay, + const Vector* mins, const Vector* maxs, float height, int modelindex, int count, float speed ) +{ + g_TEBubbles.m_vecMins = *mins; + g_TEBubbles.m_vecMaxs = *maxs; + g_TEBubbles.m_fHeight = height; + g_TEBubbles.m_nModelIndex = modelindex; + g_TEBubbles.m_nCount = count; + g_TEBubbles.m_fSpeed = speed; + + // Send it over the wire + g_TEBubbles.Create( filter, delay ); +} diff --git a/dlls/te_bubbletrail.cpp b/dlls/te_bubbletrail.cpp index da0fbd20..3a535a1a 100644 --- a/dlls/te_bubbletrail.cpp +++ b/dlls/te_bubbletrail.cpp @@ -1,140 +1,140 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexBubbles;// holds the index for the bubbles model - -enum -{ - BUBBLE_TRAIL_COUNT_BITS = 8, - BUBBLE_TRAIL_MAX_COUNT = ( (1 << BUBBLE_TRAIL_COUNT_BITS) - 1 ), -}; - - -//----------------------------------------------------------------------------- -// Purpose: Dispatches bubble trail -//----------------------------------------------------------------------------- -class CTEBubbleTrail : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEBubbleTrail, CBaseTempEntity ); - - CTEBubbleTrail( const char *name ); - virtual ~CTEBubbleTrail( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecMins ); - CNetworkVector( m_vecMaxs ); - CNetworkVar( float, m_flWaterZ ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( int, m_nCount ); - CNetworkVar( float, m_fSpeed ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEBubbleTrail::CTEBubbleTrail( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecMins.Init(); - m_vecMaxs.Init(); - m_flWaterZ = 0.0; - m_nModelIndex = 0; - m_nCount = 0; - m_fSpeed = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEBubbleTrail::~CTEBubbleTrail( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEBubbleTrail::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_vecMins = current_origin; - - Vector forward; - - m_vecMins.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecMins, 100.0, forward, m_vecMins.GetForModify() ); - - m_vecMaxs = m_vecMins + Vector( 256, 256, 256 ); - - m_fSpeed = 8; - m_nCount = 20; - m_flWaterZ = 0; - - m_nModelIndex = g_sModelIndexBubbles; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEBubbleTrail, DT_TEBubbleTrail) - SendPropVector( SENDINFO(m_vecMins), -1, SPROP_COORD), - SendPropVector( SENDINFO(m_vecMaxs), -1, SPROP_COORD), - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropFloat( SENDINFO(m_flWaterZ ), 17, 0, MIN_COORD_INTEGER, MAX_COORD_INTEGER ), - SendPropInt( SENDINFO(m_nCount), BUBBLE_TRAIL_COUNT_BITS, SPROP_UNSIGNED ), - SendPropFloat( SENDINFO(m_fSpeed ), 17, 0, MIN_COORD_INTEGER, MAX_COORD_INTEGER ), -END_SEND_TABLE() - - -// Singleton to fire TEBubbleTrail objects -static CTEBubbleTrail g_TEBubbleTrail( "Bubble Trail" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *mins - -// *maxs - -// height - -// modelindex - -// count - -// speed - -//----------------------------------------------------------------------------- -void TE_BubbleTrail( IRecipientFilter& filter, float delay, - const Vector* mins, const Vector* maxs, float flWaterZ, int modelindex, int count, float speed ) -{ - g_TEBubbleTrail.m_vecMins = *mins; - g_TEBubbleTrail.m_vecMaxs = *maxs; - g_TEBubbleTrail.m_flWaterZ = flWaterZ; - g_TEBubbleTrail.m_nModelIndex = modelindex; - g_TEBubbleTrail.m_nCount = min( count, BUBBLE_TRAIL_MAX_COUNT ); - g_TEBubbleTrail.m_fSpeed = speed; - - // Send it over the wire - g_TEBubbleTrail.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexBubbles;// holds the index for the bubbles model + +enum +{ + BUBBLE_TRAIL_COUNT_BITS = 8, + BUBBLE_TRAIL_MAX_COUNT = ( (1 << BUBBLE_TRAIL_COUNT_BITS) - 1 ), +}; + + +//----------------------------------------------------------------------------- +// Purpose: Dispatches bubble trail +//----------------------------------------------------------------------------- +class CTEBubbleTrail : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEBubbleTrail, CBaseTempEntity ); + + CTEBubbleTrail( const char *name ); + virtual ~CTEBubbleTrail( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecMins ); + CNetworkVector( m_vecMaxs ); + CNetworkVar( float, m_flWaterZ ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( int, m_nCount ); + CNetworkVar( float, m_fSpeed ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEBubbleTrail::CTEBubbleTrail( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecMins.Init(); + m_vecMaxs.Init(); + m_flWaterZ = 0.0; + m_nModelIndex = 0; + m_nCount = 0; + m_fSpeed = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEBubbleTrail::~CTEBubbleTrail( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEBubbleTrail::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_vecMins = current_origin; + + Vector forward; + + m_vecMins.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecMins, 100.0, forward, m_vecMins.GetForModify() ); + + m_vecMaxs = m_vecMins + Vector( 256, 256, 256 ); + + m_fSpeed = 8; + m_nCount = 20; + m_flWaterZ = 0; + + m_nModelIndex = g_sModelIndexBubbles; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEBubbleTrail, DT_TEBubbleTrail) + SendPropVector( SENDINFO(m_vecMins), -1, SPROP_COORD), + SendPropVector( SENDINFO(m_vecMaxs), -1, SPROP_COORD), + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropFloat( SENDINFO(m_flWaterZ ), 17, 0, MIN_COORD_INTEGER, MAX_COORD_INTEGER ), + SendPropInt( SENDINFO(m_nCount), BUBBLE_TRAIL_COUNT_BITS, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO(m_fSpeed ), 17, 0, MIN_COORD_INTEGER, MAX_COORD_INTEGER ), +END_SEND_TABLE() + + +// Singleton to fire TEBubbleTrail objects +static CTEBubbleTrail g_TEBubbleTrail( "Bubble Trail" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *mins - +// *maxs - +// height - +// modelindex - +// count - +// speed - +//----------------------------------------------------------------------------- +void TE_BubbleTrail( IRecipientFilter& filter, float delay, + const Vector* mins, const Vector* maxs, float flWaterZ, int modelindex, int count, float speed ) +{ + g_TEBubbleTrail.m_vecMins = *mins; + g_TEBubbleTrail.m_vecMaxs = *maxs; + g_TEBubbleTrail.m_flWaterZ = flWaterZ; + g_TEBubbleTrail.m_nModelIndex = modelindex; + g_TEBubbleTrail.m_nCount = min( count, BUBBLE_TRAIL_MAX_COUNT ); + g_TEBubbleTrail.m_fSpeed = speed; + + // Send it over the wire + g_TEBubbleTrail.Create( filter, delay ); +} diff --git a/dlls/te_decal.cpp b/dlls/te_decal.cpp index eacfe2cf..470e1926 100644 --- a/dlls/te_decal.cpp +++ b/dlls/te_decal.cpp @@ -1,131 +1,131 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches decal tempentity -//----------------------------------------------------------------------------- -class CTEDecal : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEDecal, CBaseTempEntity ); - - CTEDecal( const char *name ); - virtual ~CTEDecal( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVector( m_vecStart ); - CNetworkVar( int, m_nEntity ); - CNetworkVar( int, m_nHitbox ); - CNetworkVar( int, m_nIndex ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEDecal::CTEDecal( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_nEntity = 0; - m_nIndex = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEDecal::~CTEDecal( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEDecal::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nEntity = 0; - m_nIndex = 0; - m_vecOrigin = current_origin; - - Vector vecEnd; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, 1024.0, forward, vecEnd ); - - trace_t tr; - - UTIL_TraceLine( m_vecOrigin, vecEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); - - m_vecOrigin = tr.endpos; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - - -IMPLEMENT_SERVERCLASS_ST(CTEDecal, DT_TEDecal) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropVector( SENDINFO(m_vecStart), -1, SPROP_COORD), - SendPropInt( SENDINFO(m_nEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nHitbox), 11, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nIndex), 9, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEDecal objects -static CTEDecal g_TEDecal( "Entity Decal" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *pos - -// entity - -// index - -//----------------------------------------------------------------------------- -void TE_Decal( IRecipientFilter& filter, float delay, - const Vector* pos, const Vector* start, int entity, int hitbox, int index ) -{ - Assert( pos && start ); - g_TEDecal.m_vecOrigin = *pos; - g_TEDecal.m_vecStart = *start; - g_TEDecal.m_nEntity = entity; - g_TEDecal.m_nHitbox = hitbox; - g_TEDecal.m_nIndex = index; - - // Send it over the wire - g_TEDecal.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches decal tempentity +//----------------------------------------------------------------------------- +class CTEDecal : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEDecal, CBaseTempEntity ); + + CTEDecal( const char *name ); + virtual ~CTEDecal( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVector( m_vecStart ); + CNetworkVar( int, m_nEntity ); + CNetworkVar( int, m_nHitbox ); + CNetworkVar( int, m_nIndex ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEDecal::CTEDecal( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_nEntity = 0; + m_nIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEDecal::~CTEDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEDecal::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nEntity = 0; + m_nIndex = 0; + m_vecOrigin = current_origin; + + Vector vecEnd; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, 1024.0, forward, vecEnd ); + + trace_t tr; + + UTIL_TraceLine( m_vecOrigin, vecEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + m_vecOrigin = tr.endpos; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + + +IMPLEMENT_SERVERCLASS_ST(CTEDecal, DT_TEDecal) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropVector( SENDINFO(m_vecStart), -1, SPROP_COORD), + SendPropInt( SENDINFO(m_nEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nHitbox), 11, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nIndex), 9, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEDecal objects +static CTEDecal g_TEDecal( "Entity Decal" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *pos - +// entity - +// index - +//----------------------------------------------------------------------------- +void TE_Decal( IRecipientFilter& filter, float delay, + const Vector* pos, const Vector* start, int entity, int hitbox, int index ) +{ + Assert( pos && start ); + g_TEDecal.m_vecOrigin = *pos; + g_TEDecal.m_vecStart = *start; + g_TEDecal.m_nEntity = entity; + g_TEDecal.m_nHitbox = hitbox; + g_TEDecal.m_nIndex = index; + + // Send it over the wire + g_TEDecal.Create( filter, delay ); +} diff --git a/dlls/te_dynamiclight.cpp b/dlls/te_dynamiclight.cpp index f9639b43..8a36b701 100644 --- a/dlls/te_dynamiclight.cpp +++ b/dlls/te_dynamiclight.cpp @@ -1,144 +1,144 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Displays a dynamic light -//----------------------------------------------------------------------------- -class CTEDynamicLight : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEDynamicLight, CBaseTempEntity ); - - CTEDynamicLight( const char *name ); - virtual ~CTEDynamicLight( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVar( float, m_fRadius ); - CNetworkVar( int, r ); - CNetworkVar( int, g ); - CNetworkVar( int, b ); - CNetworkVar( int, exponent ); - CNetworkVar( float, m_fTime ); - CNetworkVar( float, m_fDecay ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEDynamicLight::CTEDynamicLight( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - r = 0; - g = 0; - b = 0; - exponent = 0; - m_fRadius = 0.0; - m_fTime = 0.0; - m_fDecay = 0.0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEDynamicLight::~CTEDynamicLight( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEDynamicLight::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - r = 255; - g = 255; - b = 63; - m_vecOrigin = current_origin; - - m_fRadius = 200; - m_fTime = 2.0; - m_fDecay = 0.0; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEDynamicLight, DT_TEDynamicLight) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropInt( SENDINFO(r), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(g), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(b), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(exponent), 8, 0 ), - SendPropFloat( SENDINFO(m_fRadius), 8, SPROP_ROUNDUP, 0, 2560.0 ), - SendPropFloat( SENDINFO(m_fTime), 8, SPROP_ROUNDDOWN, 0, 25.6 ), - SendPropFloat( SENDINFO(m_fDecay), 8, SPROP_ROUNDDOWN, 0, 2560.0 ), -END_SEND_TABLE() - - -// Singleton -static CTEDynamicLight g_TEDynamicLight( "Dynamic Light" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *org - -// r - -// g - -// b - -// radius - -// time - -// decay - -//----------------------------------------------------------------------------- -void TE_DynamicLight( IRecipientFilter& filter, float delay, - const Vector* org, int r, int g, int b, int exponent, float radius, float time, float decay ) -{ - // Set up parameters - g_TEDynamicLight.m_vecOrigin = *org; - g_TEDynamicLight.r = r; - g_TEDynamicLight.g = g; - g_TEDynamicLight.b = b; - g_TEDynamicLight.exponent = exponent; - g_TEDynamicLight.m_fRadius = radius; - g_TEDynamicLight.m_fTime = time; - g_TEDynamicLight.m_fDecay = decay; - - // Create it - g_TEDynamicLight.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Displays a dynamic light +//----------------------------------------------------------------------------- +class CTEDynamicLight : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEDynamicLight, CBaseTempEntity ); + + CTEDynamicLight( const char *name ); + virtual ~CTEDynamicLight( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVar( float, m_fRadius ); + CNetworkVar( int, r ); + CNetworkVar( int, g ); + CNetworkVar( int, b ); + CNetworkVar( int, exponent ); + CNetworkVar( float, m_fTime ); + CNetworkVar( float, m_fDecay ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEDynamicLight::CTEDynamicLight( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + r = 0; + g = 0; + b = 0; + exponent = 0; + m_fRadius = 0.0; + m_fTime = 0.0; + m_fDecay = 0.0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEDynamicLight::~CTEDynamicLight( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEDynamicLight::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + r = 255; + g = 255; + b = 63; + m_vecOrigin = current_origin; + + m_fRadius = 200; + m_fTime = 2.0; + m_fDecay = 0.0; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEDynamicLight, DT_TEDynamicLight) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropInt( SENDINFO(r), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(g), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(b), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(exponent), 8, 0 ), + SendPropFloat( SENDINFO(m_fRadius), 8, SPROP_ROUNDUP, 0, 2560.0 ), + SendPropFloat( SENDINFO(m_fTime), 8, SPROP_ROUNDDOWN, 0, 25.6 ), + SendPropFloat( SENDINFO(m_fDecay), 8, SPROP_ROUNDDOWN, 0, 2560.0 ), +END_SEND_TABLE() + + +// Singleton +static CTEDynamicLight g_TEDynamicLight( "Dynamic Light" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *org - +// r - +// g - +// b - +// radius - +// time - +// decay - +//----------------------------------------------------------------------------- +void TE_DynamicLight( IRecipientFilter& filter, float delay, + const Vector* org, int r, int g, int b, int exponent, float radius, float time, float decay ) +{ + // Set up parameters + g_TEDynamicLight.m_vecOrigin = *org; + g_TEDynamicLight.r = r; + g_TEDynamicLight.g = g; + g_TEDynamicLight.b = b; + g_TEDynamicLight.exponent = exponent; + g_TEDynamicLight.m_fRadius = radius; + g_TEDynamicLight.m_fTime = time; + g_TEDynamicLight.m_fDecay = decay; + + // Create it + g_TEDynamicLight.Create( filter, delay ); +} diff --git a/dlls/te_explosion.cpp b/dlls/te_explosion.cpp index 49c504fa..dd09c640 100644 --- a/dlls/te_explosion.cpp +++ b/dlls/te_explosion.cpp @@ -1,132 +1,132 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "te_particlesystem.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexFireball; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches explosion tempentity -//----------------------------------------------------------------------------- -class CTEExplosion : public CTEParticleSystem -{ -public: - DECLARE_CLASS( CTEExplosion, CTEParticleSystem ); - DECLARE_SERVERCLASS(); - - CTEExplosion( const char *name ); - virtual ~CTEExplosion( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - -public: - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( float, m_fScale ); - CNetworkVar( int, m_nFrameRate ); - CNetworkVar( int, m_nFlags ); - CNetworkVector( m_vecNormal ); - CNetworkVar( unsigned char, m_chMaterialType ); - CNetworkVar( int, m_nRadius ); - CNetworkVar( int, m_nMagnitude ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEExplosion::CTEExplosion( const char *name ) : - BaseClass( name ) -{ - m_nModelIndex = 0; - m_fScale = 0; - m_nFrameRate = 0; - m_nFlags = 0; - m_vecNormal.Init(); - m_chMaterialType = 'C'; - m_nRadius = 0; - m_nMagnitude = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEExplosion::~CTEExplosion( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEExplosion::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = g_sModelIndexFireball; - m_fScale = 0.5; - m_nFrameRate = 15; - m_nFlags = TE_EXPLFLAG_NONE; - m_vecOrigin = current_origin; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - m_vecOrigin += forward * 50; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEExplosion, DT_TEExplosion) - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropFloat( SENDINFO(m_fScale ), 9, 0, 0.0, 51.2 ), - SendPropInt( SENDINFO(m_nFrameRate), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nFlags), 8, SPROP_UNSIGNED ), - SendPropVector( SENDINFO(m_vecNormal), -1, SPROP_COORD), - SendPropInt( SENDINFO(m_chMaterialType), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nRadius), 32, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nMagnitude), 32, SPROP_UNSIGNED ), -END_SEND_TABLE() - -// Singleton to fire TEExplosion objects -static CTEExplosion g_TEExplosion( "Explosion" ); - -void TE_Explosion( IRecipientFilter& filter, float delay, - const Vector* pos, int modelindex, float scale, int framerate, int flags, int radius, int magnitude, const Vector* normal, unsigned char materialType ) -{ - g_TEExplosion.m_vecOrigin = *pos; - g_TEExplosion.m_nModelIndex = modelindex; - g_TEExplosion.m_fScale = scale; - g_TEExplosion.m_nFrameRate = framerate; - g_TEExplosion.m_nFlags = flags; - g_TEExplosion.m_nRadius = radius; - g_TEExplosion.m_nMagnitude = magnitude; - - if ( normal ) - g_TEExplosion.m_vecNormal = *normal; - else - g_TEExplosion.m_vecNormal = Vector(0,0,1); - g_TEExplosion.m_chMaterialType = materialType; - - // Send it over the wire - g_TEExplosion.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "te_particlesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexFireball; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches explosion tempentity +//----------------------------------------------------------------------------- +class CTEExplosion : public CTEParticleSystem +{ +public: + DECLARE_CLASS( CTEExplosion, CTEParticleSystem ); + DECLARE_SERVERCLASS(); + + CTEExplosion( const char *name ); + virtual ~CTEExplosion( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + +public: + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( float, m_fScale ); + CNetworkVar( int, m_nFrameRate ); + CNetworkVar( int, m_nFlags ); + CNetworkVector( m_vecNormal ); + CNetworkVar( unsigned char, m_chMaterialType ); + CNetworkVar( int, m_nRadius ); + CNetworkVar( int, m_nMagnitude ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEExplosion::CTEExplosion( const char *name ) : + BaseClass( name ) +{ + m_nModelIndex = 0; + m_fScale = 0; + m_nFrameRate = 0; + m_nFlags = 0; + m_vecNormal.Init(); + m_chMaterialType = 'C'; + m_nRadius = 0; + m_nMagnitude = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEExplosion::~CTEExplosion( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEExplosion::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = g_sModelIndexFireball; + m_fScale = 0.5; + m_nFrameRate = 15; + m_nFlags = TE_EXPLFLAG_NONE; + m_vecOrigin = current_origin; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + m_vecOrigin += forward * 50; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEExplosion, DT_TEExplosion) + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropFloat( SENDINFO(m_fScale ), 9, 0, 0.0, 51.2 ), + SendPropInt( SENDINFO(m_nFrameRate), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nFlags), 8, SPROP_UNSIGNED ), + SendPropVector( SENDINFO(m_vecNormal), -1, SPROP_COORD), + SendPropInt( SENDINFO(m_chMaterialType), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nRadius), 32, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nMagnitude), 32, SPROP_UNSIGNED ), +END_SEND_TABLE() + +// Singleton to fire TEExplosion objects +static CTEExplosion g_TEExplosion( "Explosion" ); + +void TE_Explosion( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float scale, int framerate, int flags, int radius, int magnitude, const Vector* normal, unsigned char materialType ) +{ + g_TEExplosion.m_vecOrigin = *pos; + g_TEExplosion.m_nModelIndex = modelindex; + g_TEExplosion.m_fScale = scale; + g_TEExplosion.m_nFrameRate = framerate; + g_TEExplosion.m_nFlags = flags; + g_TEExplosion.m_nRadius = radius; + g_TEExplosion.m_nMagnitude = magnitude; + + if ( normal ) + g_TEExplosion.m_vecNormal = *normal; + else + g_TEExplosion.m_vecNormal = Vector(0,0,1); + g_TEExplosion.m_chMaterialType = materialType; + + // Send it over the wire + g_TEExplosion.Create( filter, delay ); +} diff --git a/dlls/te_fizz.cpp b/dlls/te_fizz.cpp index 75e614fb..8c806bbc 100644 --- a/dlls/te_fizz.cpp +++ b/dlls/te_fizz.cpp @@ -1,112 +1,112 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches Fizz tempentity -//----------------------------------------------------------------------------- -class CTEFizz : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEFizz, CBaseTempEntity ); - - CTEFizz( const char *name ); - virtual ~CTEFizz( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - virtual void Precache( void ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVar( int, m_nEntity ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( int, m_nDensity ); - CNetworkVar( int, m_nCurrent ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEFizz::CTEFizz( const char *name ) : - CBaseTempEntity( name ) -{ - m_nEntity = 0; - m_nModelIndex = 0; - m_nDensity = 0; - m_nCurrent = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEFizz::~CTEFizz( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEFizz::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = CBaseEntity::PrecacheModel( "sprites/bubble.vmt" );; - m_nDensity = 200; - m_nEntity = 1; - m_nCurrent = 100; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CTEFizz::Precache( void ) -{ - CBaseEntity::PrecacheModel( "sprites/bubble.vmt" ); -} - - -IMPLEMENT_SERVERCLASS_ST(CTEFizz, DT_TEFizz) - SendPropInt( SENDINFO(m_nEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropInt( SENDINFO(m_nDensity), 8, SPROP_UNSIGNED ), - SendPropInt(SENDINFO(m_nCurrent), 16 ), -END_SEND_TABLE() - - -// Singleton to fire TEFizz objects -static CTEFizz g_TEFizz( "Fizz" ); - -void TE_Fizz( IRecipientFilter& filter, float delay, - const CBaseEntity *entity, int modelindex, int density, int current ) -{ - Assert( entity ); - - g_TEFizz.m_nEntity = ENTINDEX( (edict_t *)entity->edict() ); - g_TEFizz.m_nModelIndex = modelindex; - g_TEFizz.m_nDensity = density; - g_TEFizz.m_nCurrent = current; - - // Send it over the wire - g_TEFizz.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches Fizz tempentity +//----------------------------------------------------------------------------- +class CTEFizz : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEFizz, CBaseTempEntity ); + + CTEFizz( const char *name ); + virtual ~CTEFizz( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + virtual void Precache( void ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVar( int, m_nEntity ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( int, m_nDensity ); + CNetworkVar( int, m_nCurrent ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEFizz::CTEFizz( const char *name ) : + CBaseTempEntity( name ) +{ + m_nEntity = 0; + m_nModelIndex = 0; + m_nDensity = 0; + m_nCurrent = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEFizz::~CTEFizz( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEFizz::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = CBaseEntity::PrecacheModel( "sprites/bubble.vmt" );; + m_nDensity = 200; + m_nEntity = 1; + m_nCurrent = 100; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTEFizz::Precache( void ) +{ + CBaseEntity::PrecacheModel( "sprites/bubble.vmt" ); +} + + +IMPLEMENT_SERVERCLASS_ST(CTEFizz, DT_TEFizz) + SendPropInt( SENDINFO(m_nEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropInt( SENDINFO(m_nDensity), 8, SPROP_UNSIGNED ), + SendPropInt(SENDINFO(m_nCurrent), 16 ), +END_SEND_TABLE() + + +// Singleton to fire TEFizz objects +static CTEFizz g_TEFizz( "Fizz" ); + +void TE_Fizz( IRecipientFilter& filter, float delay, + const CBaseEntity *entity, int modelindex, int density, int current ) +{ + Assert( entity ); + + g_TEFizz.m_nEntity = ENTINDEX( (edict_t *)entity->edict() ); + g_TEFizz.m_nModelIndex = modelindex; + g_TEFizz.m_nDensity = density; + g_TEFizz.m_nCurrent = current; + + // Send it over the wire + g_TEFizz.Create( filter, delay ); +} diff --git a/dlls/te_footprintdecal.cpp b/dlls/te_footprintdecal.cpp index 6f463ae4..c625ab25 100644 --- a/dlls/te_footprintdecal.cpp +++ b/dlls/te_footprintdecal.cpp @@ -1,91 +1,91 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches footprint decal tempentity -//----------------------------------------------------------------------------- - -#define FOOTPRINT_DECAY_TIME 3.0f - -class CTEFootprintDecal : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEFootprintDecal, CBaseTempEntity ); - - CTEFootprintDecal( const char *name ); - virtual ~CTEFootprintDecal( void ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVector( m_vecDirection ); - CNetworkVar( int, m_nEntity ); - CNetworkVar( int, m_nIndex ); - CNetworkVar( unsigned char, m_chMaterialType ); -}; - -IMPLEMENT_SERVERCLASS_ST(CTEFootprintDecal, DT_TEFootprintDecal) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropVector( SENDINFO(m_vecDirection), -1, SPROP_COORD), - SendPropInt( SENDINFO(m_nEntity), 11, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nIndex), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_chMaterialType), 8, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEFootprintDecal objects -static CTEFootprintDecal g_TEFootprintDecal( "Footprint Decal" ); - -//----------------------------------------------------------------------------- -// constructor, destructor -//----------------------------------------------------------------------------- - -CTEFootprintDecal::CTEFootprintDecal( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_nEntity = 0; - m_nIndex = 0; - m_chMaterialType = 'C'; -} - -CTEFootprintDecal::~CTEFootprintDecal( void ) -{ -} - -//----------------------------------------------------------------------------- -// places a footprint decal -//----------------------------------------------------------------------------- - -void TE_FootprintDecal( IRecipientFilter& filter, float delay, - const Vector *origin, const Vector *right, int entity, int index, - unsigned char materialType ) -{ - Assert( origin ); - g_TEFootprintDecal.m_vecOrigin = *origin; - g_TEFootprintDecal.m_vecDirection = *right; - g_TEFootprintDecal.m_nEntity = entity; - g_TEFootprintDecal.m_nIndex = index; - g_TEFootprintDecal.m_chMaterialType = materialType; - - VectorNormalize(g_TEFootprintDecal.m_vecDirection.GetForModify()); - - // Send it over the wire - g_TEFootprintDecal.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches footprint decal tempentity +//----------------------------------------------------------------------------- + +#define FOOTPRINT_DECAY_TIME 3.0f + +class CTEFootprintDecal : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEFootprintDecal, CBaseTempEntity ); + + CTEFootprintDecal( const char *name ); + virtual ~CTEFootprintDecal( void ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVector( m_vecDirection ); + CNetworkVar( int, m_nEntity ); + CNetworkVar( int, m_nIndex ); + CNetworkVar( unsigned char, m_chMaterialType ); +}; + +IMPLEMENT_SERVERCLASS_ST(CTEFootprintDecal, DT_TEFootprintDecal) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropVector( SENDINFO(m_vecDirection), -1, SPROP_COORD), + SendPropInt( SENDINFO(m_nEntity), 11, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nIndex), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_chMaterialType), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEFootprintDecal objects +static CTEFootprintDecal g_TEFootprintDecal( "Footprint Decal" ); + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +CTEFootprintDecal::CTEFootprintDecal( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_nEntity = 0; + m_nIndex = 0; + m_chMaterialType = 'C'; +} + +CTEFootprintDecal::~CTEFootprintDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// places a footprint decal +//----------------------------------------------------------------------------- + +void TE_FootprintDecal( IRecipientFilter& filter, float delay, + const Vector *origin, const Vector *right, int entity, int index, + unsigned char materialType ) +{ + Assert( origin ); + g_TEFootprintDecal.m_vecOrigin = *origin; + g_TEFootprintDecal.m_vecDirection = *right; + g_TEFootprintDecal.m_nEntity = entity; + g_TEFootprintDecal.m_nIndex = index; + g_TEFootprintDecal.m_chMaterialType = materialType; + + VectorNormalize(g_TEFootprintDecal.m_vecDirection.GetForModify()); + + // Send it over the wire + g_TEFootprintDecal.Create( filter, delay ); +} diff --git a/dlls/te_glowsprite.cpp b/dlls/te_glowsprite.cpp index e604b270..541b437d 100644 --- a/dlls/te_glowsprite.cpp +++ b/dlls/te_glowsprite.cpp @@ -1,130 +1,130 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches Sprite tempentity -//----------------------------------------------------------------------------- -class CTEGlowSprite : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEGlowSprite, CBaseTempEntity ); - - CTEGlowSprite( const char *name ); - virtual ~CTEGlowSprite( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( float, m_fScale ); - CNetworkVar( float, m_fLife ); - CNetworkVar( int, m_nBrightness ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEGlowSprite::CTEGlowSprite( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_nModelIndex = 0; - m_fScale = 0; - m_fLife = 0; - m_nBrightness = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEGlowSprite::~CTEGlowSprite( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEGlowSprite::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = g_sModelIndexSmoke; - m_fScale = 0.8; - m_nBrightness = 200; - m_fLife = 2.0; - m_vecOrigin = current_origin; - - Vector forward, right; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward, &right, NULL ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, -25.0, right, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - - -IMPLEMENT_SERVERCLASS_ST(CTEGlowSprite, DT_TEGlowSprite) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropFloat( SENDINFO(m_fScale ), 8, SPROP_ROUNDDOWN, 0.0, 25.6 ), - SendPropFloat( SENDINFO(m_fLife ), 8, SPROP_ROUNDDOWN, 0.0, 25.6 ), - SendPropInt( SENDINFO(m_nBrightness), 8, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEGlowSprite objects -static CTEGlowSprite g_TEGlowSprite( "GlowSprite" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *pos - -// modelindex - -// life - -// size - -// brightness - -//----------------------------------------------------------------------------- -void TE_GlowSprite( IRecipientFilter& filter, float delay, - const Vector* pos, int modelindex, float life, float size, int brightness ) -{ - g_TEGlowSprite.m_vecOrigin = *pos; - g_TEGlowSprite.m_nModelIndex = modelindex; - g_TEGlowSprite.m_fLife = life; - g_TEGlowSprite.m_fScale = size; - g_TEGlowSprite.m_nBrightness = brightness; - - // Send it over the wire - g_TEGlowSprite.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches Sprite tempentity +//----------------------------------------------------------------------------- +class CTEGlowSprite : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEGlowSprite, CBaseTempEntity ); + + CTEGlowSprite( const char *name ); + virtual ~CTEGlowSprite( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( float, m_fScale ); + CNetworkVar( float, m_fLife ); + CNetworkVar( int, m_nBrightness ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEGlowSprite::CTEGlowSprite( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_nModelIndex = 0; + m_fScale = 0; + m_fLife = 0; + m_nBrightness = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEGlowSprite::~CTEGlowSprite( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEGlowSprite::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = g_sModelIndexSmoke; + m_fScale = 0.8; + m_nBrightness = 200; + m_fLife = 2.0; + m_vecOrigin = current_origin; + + Vector forward, right; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward, &right, NULL ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, -25.0, right, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + + +IMPLEMENT_SERVERCLASS_ST(CTEGlowSprite, DT_TEGlowSprite) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropFloat( SENDINFO(m_fScale ), 8, SPROP_ROUNDDOWN, 0.0, 25.6 ), + SendPropFloat( SENDINFO(m_fLife ), 8, SPROP_ROUNDDOWN, 0.0, 25.6 ), + SendPropInt( SENDINFO(m_nBrightness), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEGlowSprite objects +static CTEGlowSprite g_TEGlowSprite( "GlowSprite" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *pos - +// modelindex - +// life - +// size - +// brightness - +//----------------------------------------------------------------------------- +void TE_GlowSprite( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float life, float size, int brightness ) +{ + g_TEGlowSprite.m_vecOrigin = *pos; + g_TEGlowSprite.m_nModelIndex = modelindex; + g_TEGlowSprite.m_fLife = life; + g_TEGlowSprite.m_fScale = size; + g_TEGlowSprite.m_nBrightness = brightness; + + // Send it over the wire + g_TEGlowSprite.Create( filter, delay ); +} diff --git a/dlls/te_impact.cpp b/dlls/te_impact.cpp index eb34416e..4d1e15a0 100644 --- a/dlls/te_impact.cpp +++ b/dlls/te_impact.cpp @@ -1,97 +1,97 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Send generic impact messages to the client for visualization -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches Gunshot decal tempentity -//----------------------------------------------------------------------------- -class CTEImpact : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEImpact, CBaseTempEntity ); - - DECLARE_SERVERCLASS(); - - CTEImpact( const char *name ); - virtual ~CTEImpact(); - - void Precache( void ); - void Test( const Vector& current_origin, const Vector& current_normal ); - -public: - - CNetworkVector( m_vecOrigin ); - CNetworkVector( m_vecNormal ); //NOTENOTE: In a multi-play setup we'll probably want non-oriented effects for bandwidth - CNetworkVar( int, m_iType ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -// Output : -//----------------------------------------------------------------------------- -CTEImpact::CTEImpact( const char *name ) : CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_vecNormal.Init(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEImpact::~CTEImpact( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CTEImpact::Precache( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEImpact::Test( const Vector& current_origin, const Vector& current_normal ) -{ -} - - -//Server class implementation -IMPLEMENT_SERVERCLASS_ST( CTEImpact, DT_TEImpact) - SendPropVector( SENDINFO( m_vecOrigin ), -1, SPROP_COORD ), - SendPropVector( SENDINFO( m_vecNormal ), -1, SPROP_COORD ), - SendPropInt( SENDINFO( m_iType ), 32, SPROP_UNSIGNED ), -END_SEND_TABLE() - -// Singleton to fire TEImpact objects -static CTEImpact g_TEImpact( "Impact" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -//----------------------------------------------------------------------------- -void TE_Impact( IRecipientFilter& filter, float delay ) -{ - g_TEImpact.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Send generic impact messages to the client for visualization +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches Gunshot decal tempentity +//----------------------------------------------------------------------------- +class CTEImpact : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEImpact, CBaseTempEntity ); + + DECLARE_SERVERCLASS(); + + CTEImpact( const char *name ); + virtual ~CTEImpact(); + + void Precache( void ); + void Test( const Vector& current_origin, const Vector& current_normal ); + +public: + + CNetworkVector( m_vecOrigin ); + CNetworkVector( m_vecNormal ); //NOTENOTE: In a multi-play setup we'll probably want non-oriented effects for bandwidth + CNetworkVar( int, m_iType ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : +//----------------------------------------------------------------------------- +CTEImpact::CTEImpact( const char *name ) : CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_vecNormal.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEImpact::~CTEImpact( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTEImpact::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEImpact::Test( const Vector& current_origin, const Vector& current_normal ) +{ +} + + +//Server class implementation +IMPLEMENT_SERVERCLASS_ST( CTEImpact, DT_TEImpact) + SendPropVector( SENDINFO( m_vecOrigin ), -1, SPROP_COORD ), + SendPropVector( SENDINFO( m_vecNormal ), -1, SPROP_COORD ), + SendPropInt( SENDINFO( m_iType ), 32, SPROP_UNSIGNED ), +END_SEND_TABLE() + +// Singleton to fire TEImpact objects +static CTEImpact g_TEImpact( "Impact" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +//----------------------------------------------------------------------------- +void TE_Impact( IRecipientFilter& filter, float delay ) +{ + g_TEImpact.Create( filter, delay ); +} diff --git a/dlls/te_killplayerattachments.cpp b/dlls/te_killplayerattachments.cpp index c180b4f1..5cd7a8e9 100644 --- a/dlls/te_killplayerattachments.cpp +++ b/dlls/te_killplayerattachments.cpp @@ -1,92 +1,92 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches blood stream tempentity -//----------------------------------------------------------------------------- -class CTEKillPlayerAttachments : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEKillPlayerAttachments, CBaseTempEntity ); - - CTEKillPlayerAttachments( const char *name ); - virtual ~CTEKillPlayerAttachments( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVar( int, m_nPlayer ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEKillPlayerAttachments::CTEKillPlayerAttachments( const char *name ) : - CBaseTempEntity( name ) -{ - m_nPlayer = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEKillPlayerAttachments::~CTEKillPlayerAttachments( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEKillPlayerAttachments::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - m_nPlayer = 1; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - - -IMPLEMENT_SERVERCLASS_ST(CTEKillPlayerAttachments, DT_TEKillPlayerAttachments) - SendPropInt( SENDINFO(m_nPlayer), 5, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEKillPlayerAttachments objects -static CTEKillPlayerAttachments g_TEKillPlayerAttachments( "KillPlayerAttachments" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// player - -//----------------------------------------------------------------------------- -void TE_KillPlayerAttachments( IRecipientFilter& filter, float delay, - int player ) -{ - g_TEKillPlayerAttachments.m_nPlayer = player; - - // Send it over the wire - g_TEKillPlayerAttachments.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches blood stream tempentity +//----------------------------------------------------------------------------- +class CTEKillPlayerAttachments : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEKillPlayerAttachments, CBaseTempEntity ); + + CTEKillPlayerAttachments( const char *name ); + virtual ~CTEKillPlayerAttachments( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVar( int, m_nPlayer ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEKillPlayerAttachments::CTEKillPlayerAttachments( const char *name ) : + CBaseTempEntity( name ) +{ + m_nPlayer = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEKillPlayerAttachments::~CTEKillPlayerAttachments( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEKillPlayerAttachments::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + m_nPlayer = 1; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + + +IMPLEMENT_SERVERCLASS_ST(CTEKillPlayerAttachments, DT_TEKillPlayerAttachments) + SendPropInt( SENDINFO(m_nPlayer), 5, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEKillPlayerAttachments objects +static CTEKillPlayerAttachments g_TEKillPlayerAttachments( "KillPlayerAttachments" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// player - +//----------------------------------------------------------------------------- +void TE_KillPlayerAttachments( IRecipientFilter& filter, float delay, + int player ) +{ + g_TEKillPlayerAttachments.m_nPlayer = player; + + // Send it over the wire + g_TEKillPlayerAttachments.Create( filter, delay ); +} diff --git a/dlls/te_largefunnel.cpp b/dlls/te_largefunnel.cpp index c0d5427a..dbb0abab 100644 --- a/dlls/te_largefunnel.cpp +++ b/dlls/te_largefunnel.cpp @@ -1,103 +1,103 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "te_particlesystem.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches smoke tempentity -//----------------------------------------------------------------------------- -class CTELargeFunnel : public CTEParticleSystem -{ -public: - DECLARE_CLASS( CTELargeFunnel, CTEParticleSystem ); - DECLARE_SERVERCLASS(); - - CTELargeFunnel( const char *name ); - virtual ~CTELargeFunnel( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - -public: - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( int, m_nReversed ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTELargeFunnel::CTELargeFunnel( const char *name ) : - BaseClass( name ) -{ - m_nModelIndex = 0; - m_nReversed = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTELargeFunnel::~CTELargeFunnel( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTELargeFunnel::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = g_sModelIndexSmoke; - m_nReversed = 0; - m_vecOrigin = current_origin; - - Vector forward, right; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward, &right, NULL ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin.Get(), 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin.Get(), 25.0, right, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTELargeFunnel, DT_TELargeFunnel) - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropInt( SENDINFO(m_nReversed), 2, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TELargeFunnel objects -static CTELargeFunnel g_TELargeFunnel( "Large Funnel" ); - -void TE_LargeFunnel( IRecipientFilter& filter, float delay, - const Vector* pos, int modelindex, int reversed ) -{ - g_TELargeFunnel.m_vecOrigin = *pos; - g_TELargeFunnel.m_nModelIndex = modelindex; - g_TELargeFunnel.m_nReversed = reversed; - - // Send it over the wire - g_TELargeFunnel.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "te_particlesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches smoke tempentity +//----------------------------------------------------------------------------- +class CTELargeFunnel : public CTEParticleSystem +{ +public: + DECLARE_CLASS( CTELargeFunnel, CTEParticleSystem ); + DECLARE_SERVERCLASS(); + + CTELargeFunnel( const char *name ); + virtual ~CTELargeFunnel( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + +public: + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( int, m_nReversed ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTELargeFunnel::CTELargeFunnel( const char *name ) : + BaseClass( name ) +{ + m_nModelIndex = 0; + m_nReversed = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTELargeFunnel::~CTELargeFunnel( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTELargeFunnel::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = g_sModelIndexSmoke; + m_nReversed = 0; + m_vecOrigin = current_origin; + + Vector forward, right; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward, &right, NULL ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin.Get(), 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin.Get(), 25.0, right, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTELargeFunnel, DT_TELargeFunnel) + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropInt( SENDINFO(m_nReversed), 2, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TELargeFunnel objects +static CTELargeFunnel g_TELargeFunnel( "Large Funnel" ); + +void TE_LargeFunnel( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, int reversed ) +{ + g_TELargeFunnel.m_vecOrigin = *pos; + g_TELargeFunnel.m_nModelIndex = modelindex; + g_TELargeFunnel.m_nReversed = reversed; + + // Send it over the wire + g_TELargeFunnel.Create( filter, delay ); +} diff --git a/dlls/te_muzzleflash.cpp b/dlls/te_muzzleflash.cpp index 9cdda73f..d80fdf7c 100644 --- a/dlls/te_muzzleflash.cpp +++ b/dlls/te_muzzleflash.cpp @@ -1,99 +1,99 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Create a muzzle flash temp ent -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches user tracer stream tempentity -//----------------------------------------------------------------------------- -class CTEMuzzleFlash : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEMuzzleFlash, CBaseTempEntity ); - - DECLARE_SERVERCLASS(); - - CTEMuzzleFlash( const char *name ); - virtual ~CTEMuzzleFlash( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - -public: - - CNetworkVector( m_vecOrigin ); - CNetworkQAngle( m_vecAngles ); - CNetworkVar( float, m_flScale ); - CNetworkVar( int, m_nType ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEMuzzleFlash::CTEMuzzleFlash( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_vecAngles.Init(); - - m_flScale = 1.0f; - m_nType = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEMuzzleFlash::~CTEMuzzleFlash( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEMuzzleFlash::Test( const Vector& current_origin, const QAngle& current_angles ) -{ -} - - -IMPLEMENT_SERVERCLASS_ST( CTEMuzzleFlash, DT_TEMuzzleFlash ) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD ), - SendPropVector( SENDINFO(m_vecAngles), -1, SPROP_COORD ), - SendPropFloat( SENDINFO(m_flScale), -1, SPROP_NOSCALE ), - SendPropInt( SENDINFO(m_nType), 32, SPROP_UNSIGNED ), -END_SEND_TABLE() - -// Singleton to fire TEMuzzleFlash objects -static CTEMuzzleFlash g_TEMuzzleFlash( "MuzzleFlash" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// origin - -// *recipient - -// *origin - -// *dir - -// scale - -// type - -//----------------------------------------------------------------------------- -void TE_MuzzleFlash( IRecipientFilter& filter, float delay, - const Vector &start, const QAngle &angles, float scale, int type ) -{ - g_TEMuzzleFlash.m_vecOrigin = start; - g_TEMuzzleFlash.m_vecAngles = angles; - g_TEMuzzleFlash.m_flScale = scale; - g_TEMuzzleFlash.m_nType = type; - - // Send it over the wire - g_TEMuzzleFlash.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Create a muzzle flash temp ent +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches user tracer stream tempentity +//----------------------------------------------------------------------------- +class CTEMuzzleFlash : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEMuzzleFlash, CBaseTempEntity ); + + DECLARE_SERVERCLASS(); + + CTEMuzzleFlash( const char *name ); + virtual ~CTEMuzzleFlash( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + +public: + + CNetworkVector( m_vecOrigin ); + CNetworkQAngle( m_vecAngles ); + CNetworkVar( float, m_flScale ); + CNetworkVar( int, m_nType ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEMuzzleFlash::CTEMuzzleFlash( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_vecAngles.Init(); + + m_flScale = 1.0f; + m_nType = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEMuzzleFlash::~CTEMuzzleFlash( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEMuzzleFlash::Test( const Vector& current_origin, const QAngle& current_angles ) +{ +} + + +IMPLEMENT_SERVERCLASS_ST( CTEMuzzleFlash, DT_TEMuzzleFlash ) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD ), + SendPropVector( SENDINFO(m_vecAngles), -1, SPROP_COORD ), + SendPropFloat( SENDINFO(m_flScale), -1, SPROP_NOSCALE ), + SendPropInt( SENDINFO(m_nType), 32, SPROP_UNSIGNED ), +END_SEND_TABLE() + +// Singleton to fire TEMuzzleFlash objects +static CTEMuzzleFlash g_TEMuzzleFlash( "MuzzleFlash" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// origin - +// *recipient - +// *origin - +// *dir - +// scale - +// type - +//----------------------------------------------------------------------------- +void TE_MuzzleFlash( IRecipientFilter& filter, float delay, + const Vector &start, const QAngle &angles, float scale, int type ) +{ + g_TEMuzzleFlash.m_vecOrigin = start; + g_TEMuzzleFlash.m_vecAngles = angles; + g_TEMuzzleFlash.m_flScale = scale; + g_TEMuzzleFlash.m_nType = type; + + // Send it over the wire + g_TEMuzzleFlash.Create( filter, delay ); +} diff --git a/dlls/te_particlesystem.cpp b/dlls/te_particlesystem.cpp index bd978d24..f3def596 100644 --- a/dlls/te_particlesystem.cpp +++ b/dlls/te_particlesystem.cpp @@ -18,7 +18,3 @@ IMPLEMENT_SERVERCLASS_ST(CTEParticleSystem, DT_TEParticleSystem) SendPropFloat( SENDINFO_VECTORELEM(m_vecOrigin, 1), -1, SPROP_COORD), SendPropFloat( SENDINFO_VECTORELEM(m_vecOrigin, 2), -1, SPROP_COORD), END_SEND_TABLE() - - - - diff --git a/dlls/te_physicsprop.cpp b/dlls/te_physicsprop.cpp index 3db27ac9..68a5d573 100644 --- a/dlls/te_physicsprop.cpp +++ b/dlls/te_physicsprop.cpp @@ -1,133 +1,133 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: create clientside physics prop, as breaks model if needed -//----------------------------------------------------------------------------- -class CTEPhysicsProp : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEPhysicsProp, CBaseTempEntity ); - - CTEPhysicsProp( const char *name ); - virtual ~CTEPhysicsProp( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - virtual void Precache( void ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkQAngle( m_angRotation ); - CNetworkVector( m_vecVelocity ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( int, m_nSkin ); - CNetworkVar( int, m_nFlags ); - CNetworkVar( int, m_nEffects ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEPhysicsProp::CTEPhysicsProp( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_angRotation.Init(); - m_vecVelocity.Init(); - m_nModelIndex = 0; - m_nSkin = 0; - m_nFlags = 0; - m_nEffects = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEPhysicsProp::~CTEPhysicsProp( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CTEPhysicsProp::Precache( void ) -{ - CBaseEntity::PrecacheModel( "models/gibs/hgibs.mdl" ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEPhysicsProp::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = CBaseEntity::PrecacheModel( "models/gibs/hgibs.mdl" ); - m_nSkin = 0; - m_vecOrigin = current_origin; - m_angRotation = current_angles; - - m_vecVelocity.Init( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( 0, 20 ) ); - m_nFlags = 0; - m_nEffects = 0; - - Vector forward, right; - - m_vecOrigin += Vector( 0, 0, 24 ); - - AngleVectors( current_angles, &forward, &right, 0 ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, 25.0, right, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEPhysicsProp, DT_TEPhysicsProp) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 13 ), - SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 13 ), - SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 13 ), - SendPropVector( SENDINFO(m_vecVelocity), -1, SPROP_COORD), - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropInt( SENDINFO(m_nSkin), ANIMATION_SKIN_BITS), - SendPropInt( SENDINFO(m_nFlags), 2, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nEffects), EF_MAX_BITS, SPROP_UNSIGNED), -END_SEND_TABLE() - -// Singleton to fire TEBreakModel objects -static CTEPhysicsProp s_TEPhysicsProp( "physicsprop" ); - -void TE_PhysicsProp( IRecipientFilter& filter, float delay, - int modelindex, int skin, const Vector& pos, const QAngle &angles, const Vector& vel, int flags, int effects ) -{ - s_TEPhysicsProp.m_vecOrigin = pos; - s_TEPhysicsProp.m_angRotation = angles; - s_TEPhysicsProp.m_vecVelocity = vel; - s_TEPhysicsProp.m_nModelIndex = modelindex; - s_TEPhysicsProp.m_nSkin = skin; - s_TEPhysicsProp.m_nFlags = flags; - s_TEPhysicsProp.m_nEffects = effects; - - // Send it over the wire - s_TEPhysicsProp.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: create clientside physics prop, as breaks model if needed +//----------------------------------------------------------------------------- +class CTEPhysicsProp : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEPhysicsProp, CBaseTempEntity ); + + CTEPhysicsProp( const char *name ); + virtual ~CTEPhysicsProp( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + virtual void Precache( void ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkQAngle( m_angRotation ); + CNetworkVector( m_vecVelocity ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( int, m_nSkin ); + CNetworkVar( int, m_nFlags ); + CNetworkVar( int, m_nEffects ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEPhysicsProp::CTEPhysicsProp( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_angRotation.Init(); + m_vecVelocity.Init(); + m_nModelIndex = 0; + m_nSkin = 0; + m_nFlags = 0; + m_nEffects = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEPhysicsProp::~CTEPhysicsProp( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTEPhysicsProp::Precache( void ) +{ + CBaseEntity::PrecacheModel( "models/gibs/hgibs.mdl" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEPhysicsProp::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = CBaseEntity::PrecacheModel( "models/gibs/hgibs.mdl" ); + m_nSkin = 0; + m_vecOrigin = current_origin; + m_angRotation = current_angles; + + m_vecVelocity.Init( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( 0, 20 ) ); + m_nFlags = 0; + m_nEffects = 0; + + Vector forward, right; + + m_vecOrigin += Vector( 0, 0, 24 ); + + AngleVectors( current_angles, &forward, &right, 0 ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, 25.0, right, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEPhysicsProp, DT_TEPhysicsProp) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 13 ), + SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 13 ), + SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 13 ), + SendPropVector( SENDINFO(m_vecVelocity), -1, SPROP_COORD), + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropInt( SENDINFO(m_nSkin), ANIMATION_SKIN_BITS), + SendPropInt( SENDINFO(m_nFlags), 2, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nEffects), EF_MAX_BITS, SPROP_UNSIGNED), +END_SEND_TABLE() + +// Singleton to fire TEBreakModel objects +static CTEPhysicsProp s_TEPhysicsProp( "physicsprop" ); + +void TE_PhysicsProp( IRecipientFilter& filter, float delay, + int modelindex, int skin, const Vector& pos, const QAngle &angles, const Vector& vel, int flags, int effects ) +{ + s_TEPhysicsProp.m_vecOrigin = pos; + s_TEPhysicsProp.m_angRotation = angles; + s_TEPhysicsProp.m_vecVelocity = vel; + s_TEPhysicsProp.m_nModelIndex = modelindex; + s_TEPhysicsProp.m_nSkin = skin; + s_TEPhysicsProp.m_nFlags = flags; + s_TEPhysicsProp.m_nEffects = effects; + + // Send it over the wire + s_TEPhysicsProp.Create( filter, delay ); +} diff --git a/dlls/te_playerdecal.cpp b/dlls/te_playerdecal.cpp index bfa4b5dc..68480c4d 100644 --- a/dlls/te_playerdecal.cpp +++ b/dlls/te_playerdecal.cpp @@ -1,124 +1,124 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches decal tempentity -//----------------------------------------------------------------------------- -class CTEPlayerDecal : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEPlayerDecal, CBaseTempEntity ); - - CTEPlayerDecal( const char *name ); - virtual ~CTEPlayerDecal( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVar( int, m_nPlayer ); - CNetworkVector( m_vecOrigin ); - CNetworkVar( int, m_nEntity ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEPlayerDecal::CTEPlayerDecal( const char *name ) : - CBaseTempEntity( name ) -{ - m_nPlayer = 0; - m_vecOrigin.Init(); - m_nEntity = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEPlayerDecal::~CTEPlayerDecal( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEPlayerDecal::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nPlayer = 1; - m_nEntity = 0; - m_vecOrigin = current_origin; - - Vector vecEnd; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, 1024.0, forward, vecEnd ); - - trace_t tr; - - UTIL_TraceLine( m_vecOrigin, vecEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); - - m_vecOrigin = tr.endpos; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEPlayerDecal, DT_TEPlayerDecal) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropInt( SENDINFO(m_nEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nPlayer), Q_log2( MAX_PLAYERS ), SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEPlayerDecal objects -static CTEPlayerDecal g_TEPlayerDecal( "Player Decal" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *pos - -// player - -// entity - -// index - -//----------------------------------------------------------------------------- -void TE_PlayerDecal( IRecipientFilter& filter, float delay, - const Vector* pos, int player, int entity ) -{ - g_TEPlayerDecal.m_vecOrigin = *pos; - g_TEPlayerDecal.m_nPlayer = player; - g_TEPlayerDecal.m_nEntity = entity; - - // Send it over the wire - g_TEPlayerDecal.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches decal tempentity +//----------------------------------------------------------------------------- +class CTEPlayerDecal : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEPlayerDecal, CBaseTempEntity ); + + CTEPlayerDecal( const char *name ); + virtual ~CTEPlayerDecal( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVar( int, m_nPlayer ); + CNetworkVector( m_vecOrigin ); + CNetworkVar( int, m_nEntity ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEPlayerDecal::CTEPlayerDecal( const char *name ) : + CBaseTempEntity( name ) +{ + m_nPlayer = 0; + m_vecOrigin.Init(); + m_nEntity = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEPlayerDecal::~CTEPlayerDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEPlayerDecal::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nPlayer = 1; + m_nEntity = 0; + m_vecOrigin = current_origin; + + Vector vecEnd; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, 1024.0, forward, vecEnd ); + + trace_t tr; + + UTIL_TraceLine( m_vecOrigin, vecEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + m_vecOrigin = tr.endpos; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEPlayerDecal, DT_TEPlayerDecal) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropInt( SENDINFO(m_nEntity), MAX_EDICT_BITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nPlayer), Q_log2( MAX_PLAYERS ), SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEPlayerDecal objects +static CTEPlayerDecal g_TEPlayerDecal( "Player Decal" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *pos - +// player - +// entity - +// index - +//----------------------------------------------------------------------------- +void TE_PlayerDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int player, int entity ) +{ + g_TEPlayerDecal.m_vecOrigin = *pos; + g_TEPlayerDecal.m_nPlayer = player; + g_TEPlayerDecal.m_nEntity = entity; + + // Send it over the wire + g_TEPlayerDecal.Create( filter, delay ); +} diff --git a/dlls/te_projecteddecal.cpp b/dlls/te_projecteddecal.cpp index ac25c810..a189524c 100644 --- a/dlls/te_projecteddecal.cpp +++ b/dlls/te_projecteddecal.cpp @@ -1,122 +1,122 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches BSP decal tempentity -//----------------------------------------------------------------------------- -class CTEProjectedDecal : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEProjectedDecal, CBaseTempEntity ); - - CTEProjectedDecal( const char *name ); - virtual ~CTEProjectedDecal( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVar( int, m_nIndex ); - CNetworkVar( float, m_flDistance ); - CNetworkQAngle( m_angRotation ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEProjectedDecal::CTEProjectedDecal( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_angRotation.Init(); - m_flDistance = 64.0f; - m_nIndex = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEProjectedDecal::~CTEProjectedDecal( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEProjectedDecal::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_flDistance = 1024.0f; - m_nIndex = 0; - m_vecOrigin = current_origin; - m_angRotation = current_angles; - - Vector vecEnd; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 24.0, forward, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEProjectedDecal, DT_TEProjectedDecal) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropQAngles( SENDINFO(m_angRotation), 10 ), - SendPropFloat( SENDINFO(m_flDistance), 10, SPROP_ROUNDUP, 0, 1024 ), - SendPropInt( SENDINFO(m_nIndex), 9, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEBSPDecal objects -static CTEProjectedDecal g_TEProjectedDecal( "Projected Decal" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *pos - -// entity - -// index - -// modelindex - -//----------------------------------------------------------------------------- -void TE_ProjectDecal( IRecipientFilter& filter, float delay, - const Vector* pos, const QAngle *angles, float distance, int index ) -{ - g_TEProjectedDecal.m_vecOrigin = *pos; - g_TEProjectedDecal.m_angRotation = *angles; - g_TEProjectedDecal.m_flDistance = distance; - g_TEProjectedDecal.m_nIndex = index; - - // Send it over the wire - g_TEProjectedDecal.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches BSP decal tempentity +//----------------------------------------------------------------------------- +class CTEProjectedDecal : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEProjectedDecal, CBaseTempEntity ); + + CTEProjectedDecal( const char *name ); + virtual ~CTEProjectedDecal( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVar( int, m_nIndex ); + CNetworkVar( float, m_flDistance ); + CNetworkQAngle( m_angRotation ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEProjectedDecal::CTEProjectedDecal( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_angRotation.Init(); + m_flDistance = 64.0f; + m_nIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEProjectedDecal::~CTEProjectedDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEProjectedDecal::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_flDistance = 1024.0f; + m_nIndex = 0; + m_vecOrigin = current_origin; + m_angRotation = current_angles; + + Vector vecEnd; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 24.0, forward, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEProjectedDecal, DT_TEProjectedDecal) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropQAngles( SENDINFO(m_angRotation), 10 ), + SendPropFloat( SENDINFO(m_flDistance), 10, SPROP_ROUNDUP, 0, 1024 ), + SendPropInt( SENDINFO(m_nIndex), 9, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEBSPDecal objects +static CTEProjectedDecal g_TEProjectedDecal( "Projected Decal" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *pos - +// entity - +// index - +// modelindex - +//----------------------------------------------------------------------------- +void TE_ProjectDecal( IRecipientFilter& filter, float delay, + const Vector* pos, const QAngle *angles, float distance, int index ) +{ + g_TEProjectedDecal.m_vecOrigin = *pos; + g_TEProjectedDecal.m_angRotation = *angles; + g_TEProjectedDecal.m_flDistance = distance; + g_TEProjectedDecal.m_nIndex = index; + + // Send it over the wire + g_TEProjectedDecal.Create( filter, delay ); +} diff --git a/dlls/te_showline.cpp b/dlls/te_showline.cpp index 58df9624..e1818e86 100644 --- a/dlls/te_showline.cpp +++ b/dlls/te_showline.cpp @@ -1,107 +1,107 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "te_particlesystem.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches line -//----------------------------------------------------------------------------- -class CTEShowLine : public CTEParticleSystem -{ -public: - DECLARE_CLASS( CTEShowLine, CTEParticleSystem ); - DECLARE_SERVERCLASS(); - - CTEShowLine( const char *name ); - virtual ~CTEShowLine( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - -public: - CNetworkVector( m_vecEnd ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEShowLine::CTEShowLine( const char *name ) : - BaseClass( name ) -{ - m_vecEnd.Init(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEShowLine::~CTEShowLine( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEShowLine::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_vecOrigin = current_origin; - - Vector forward, right; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward, &right, NULL ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 100.0, forward, m_vecEnd.GetForModify() ); - - m_vecOrigin = m_vecEnd + right * -128; - m_vecEnd += right * 128; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST( CTEShowLine, DT_TEShowLine) - SendPropVector( SENDINFO(m_vecEnd), -1, SPROP_COORD), -END_SEND_TABLE() - - -// Singleton to fire TEShowLine objects -static CTEShowLine g_TEShowLine( "Show Line" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *start - -// *end - -//----------------------------------------------------------------------------- -void TE_ShowLine( IRecipientFilter& filter, float delay, - const Vector* start, const Vector* end ) -{ - g_TEShowLine.m_vecOrigin = *start; - g_TEShowLine.m_vecEnd = *end; - - // Send it over the wire - g_TEShowLine.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "te_particlesystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches line +//----------------------------------------------------------------------------- +class CTEShowLine : public CTEParticleSystem +{ +public: + DECLARE_CLASS( CTEShowLine, CTEParticleSystem ); + DECLARE_SERVERCLASS(); + + CTEShowLine( const char *name ); + virtual ~CTEShowLine( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + +public: + CNetworkVector( m_vecEnd ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEShowLine::CTEShowLine( const char *name ) : + BaseClass( name ) +{ + m_vecEnd.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEShowLine::~CTEShowLine( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEShowLine::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_vecOrigin = current_origin; + + Vector forward, right; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward, &right, NULL ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 100.0, forward, m_vecEnd.GetForModify() ); + + m_vecOrigin = m_vecEnd + right * -128; + m_vecEnd += right * 128; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST( CTEShowLine, DT_TEShowLine) + SendPropVector( SENDINFO(m_vecEnd), -1, SPROP_COORD), +END_SEND_TABLE() + + +// Singleton to fire TEShowLine objects +static CTEShowLine g_TEShowLine( "Show Line" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *start - +// *end - +//----------------------------------------------------------------------------- +void TE_ShowLine( IRecipientFilter& filter, float delay, + const Vector* start, const Vector* end ) +{ + g_TEShowLine.m_vecOrigin = *start; + g_TEShowLine.m_vecEnd = *end; + + // Send it over the wire + g_TEShowLine.Create( filter, delay ); +} diff --git a/dlls/te_smoke.cpp b/dlls/te_smoke.cpp index 83b60cd7..5675661d 100644 --- a/dlls/te_smoke.cpp +++ b/dlls/te_smoke.cpp @@ -1,112 +1,112 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches smoke tempentity -//----------------------------------------------------------------------------- -class CTESmoke : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTESmoke, CBaseTempEntity ); - - CTESmoke( const char *name ); - virtual ~CTESmoke( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( float, m_fScale ); - CNetworkVar( int, m_nFrameRate ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTESmoke::CTESmoke( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_nModelIndex = 0; - m_fScale = 0; - m_nFrameRate = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTESmoke::~CTESmoke( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTESmoke::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = g_sModelIndexSmoke; - m_fScale = 5.0; - m_nFrameRate = 12; - m_vecOrigin = current_origin; - - Vector forward, right; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward, &right, NULL ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, 25.0, right, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTESmoke, DT_TESmoke) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropFloat( SENDINFO(m_fScale ), 8, SPROP_ROUNDDOWN, 0.0, 25.6 ), - SendPropInt( SENDINFO(m_nFrameRate), 8, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TESmoke objects -static CTESmoke g_TESmoke( "Smoke" ); - -void TE_Smoke( IRecipientFilter& filter, float delay, - const Vector* pos, int modelindex, float scale, int framerate ) -{ - g_TESmoke.m_vecOrigin = *pos; - g_TESmoke.m_nModelIndex = modelindex; - g_TESmoke.m_fScale = scale; - g_TESmoke.m_nFrameRate = framerate; - - // Send it over the wire - g_TESmoke.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches smoke tempentity +//----------------------------------------------------------------------------- +class CTESmoke : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTESmoke, CBaseTempEntity ); + + CTESmoke( const char *name ); + virtual ~CTESmoke( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( float, m_fScale ); + CNetworkVar( int, m_nFrameRate ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTESmoke::CTESmoke( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_nModelIndex = 0; + m_fScale = 0; + m_nFrameRate = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTESmoke::~CTESmoke( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTESmoke::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = g_sModelIndexSmoke; + m_fScale = 5.0; + m_nFrameRate = 12; + m_vecOrigin = current_origin; + + Vector forward, right; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward, &right, NULL ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, 25.0, right, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTESmoke, DT_TESmoke) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropFloat( SENDINFO(m_fScale ), 8, SPROP_ROUNDDOWN, 0.0, 25.6 ), + SendPropInt( SENDINFO(m_nFrameRate), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TESmoke objects +static CTESmoke g_TESmoke( "Smoke" ); + +void TE_Smoke( IRecipientFilter& filter, float delay, + const Vector* pos, int modelindex, float scale, int framerate ) +{ + g_TESmoke.m_vecOrigin = *pos; + g_TESmoke.m_nModelIndex = modelindex; + g_TESmoke.m_fScale = scale; + g_TESmoke.m_nFrameRate = framerate; + + // Send it over the wire + g_TESmoke.Create( filter, delay ); +} diff --git a/dlls/te_sprite.cpp b/dlls/te_sprite.cpp index ce9af039..b9633f69 100644 --- a/dlls/te_sprite.cpp +++ b/dlls/te_sprite.cpp @@ -1,121 +1,121 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches Sprite tempentity -//----------------------------------------------------------------------------- -class CTESprite : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTESprite, CBaseTempEntity ); - - CTESprite( const char *name ); - virtual ~CTESprite( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - virtual void Precache( void ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( float, m_fScale ); - CNetworkVar( int, m_nBrightness ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTESprite::CTESprite( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_nModelIndex = 0; - m_fScale = 0; - m_nBrightness = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTESprite::~CTESprite( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CTESprite::Precache( void ) -{ - CBaseEntity::PrecacheModel("sprites/gunsmoke.vmt"); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTESprite::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = CBaseEntity::PrecacheModel("sprites/gunsmoke.vmt"); - m_fScale = 0.8; - m_nBrightness = 200; - m_vecOrigin = current_origin; - - Vector forward, right; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward, &right, NULL ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, -25.0, right, m_vecOrigin.GetForModify() ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - - -IMPLEMENT_SERVERCLASS_ST(CTESprite, DT_TESprite) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropModelIndex( SENDINFO(m_nModelIndex) ), - SendPropFloat( SENDINFO(m_fScale ), 8, SPROP_ROUNDDOWN, 0.0, 25.6 ), - SendPropInt( SENDINFO(m_nBrightness), 8, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TESprite objects -static CTESprite g_TESprite( "Sprite" ); - -void TE_Sprite( IRecipientFilter& filter, float delay, - const Vector *pos, int modelindex, float size, int brightness ) -{ - g_TESprite.m_vecOrigin = *pos; - g_TESprite.m_nModelIndex = modelindex; - g_TESprite.m_fScale = size; - g_TESprite.m_nBrightness = brightness; - - // Send it over the wire - g_TESprite.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches Sprite tempentity +//----------------------------------------------------------------------------- +class CTESprite : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTESprite, CBaseTempEntity ); + + CTESprite( const char *name ); + virtual ~CTESprite( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + virtual void Precache( void ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( float, m_fScale ); + CNetworkVar( int, m_nBrightness ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTESprite::CTESprite( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_nModelIndex = 0; + m_fScale = 0; + m_nBrightness = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTESprite::~CTESprite( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTESprite::Precache( void ) +{ + CBaseEntity::PrecacheModel("sprites/gunsmoke.vmt"); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTESprite::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = CBaseEntity::PrecacheModel("sprites/gunsmoke.vmt"); + m_fScale = 0.8; + m_nBrightness = 200; + m_vecOrigin = current_origin; + + Vector forward, right; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward, &right, NULL ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, -25.0, right, m_vecOrigin.GetForModify() ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + + +IMPLEMENT_SERVERCLASS_ST(CTESprite, DT_TESprite) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropModelIndex( SENDINFO(m_nModelIndex) ), + SendPropFloat( SENDINFO(m_fScale ), 8, SPROP_ROUNDDOWN, 0.0, 25.6 ), + SendPropInt( SENDINFO(m_nBrightness), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TESprite objects +static CTESprite g_TESprite( "Sprite" ); + +void TE_Sprite( IRecipientFilter& filter, float delay, + const Vector *pos, int modelindex, float size, int brightness ) +{ + g_TESprite.m_vecOrigin = *pos; + g_TESprite.m_nModelIndex = modelindex; + g_TESprite.m_fScale = size; + g_TESprite.m_nBrightness = brightness; + + // Send it over the wire + g_TESprite.Create( filter, delay ); +} diff --git a/dlls/te_spritespray.cpp b/dlls/te_spritespray.cpp index d1800569..39af3e23 100644 --- a/dlls/te_spritespray.cpp +++ b/dlls/te_spritespray.cpp @@ -1,136 +1,136 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud - -//----------------------------------------------------------------------------- -// Purpose: Dispatches Sprite Spray tempentity -//----------------------------------------------------------------------------- -class CTESpriteSpray : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTESpriteSpray, CBaseTempEntity ); - - CTESpriteSpray( const char *name ); - virtual ~CTESpriteSpray( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVector( m_vecDirection ); - CNetworkVar( int, m_nModelIndex ); - CNetworkVar( int, m_nSpeed ); - CNetworkVar( float, m_fNoise ); - CNetworkVar( int, m_nCount ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTESpriteSpray::CTESpriteSpray( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_vecDirection.Init(); - m_nModelIndex = 0; - m_fNoise = 0; - m_nSpeed = 0; - m_nCount = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTESpriteSpray::~CTESpriteSpray( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTESpriteSpray::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nModelIndex = g_sModelIndexSmoke; - m_fNoise = 0.8; - m_nCount = 5; - m_nSpeed = 30; - m_vecOrigin = current_origin; - - Vector forward, right; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward, &right, NULL ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, -25.0, right, m_vecOrigin.GetForModify() ); - - m_vecDirection.Init( random->RandomInt( -100, 100 ), random->RandomInt( -100, 100 ), random->RandomInt( 0, 100 ) ); - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTESpriteSpray, DT_TESpriteSpray) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropVector( SENDINFO(m_vecDirection), -1, SPROP_COORD), - SendPropModelIndex(SENDINFO(m_nModelIndex)), - SendPropFloat( SENDINFO(m_fNoise ), 8, SPROP_ROUNDDOWN, 0.0, 2.56 ), - SendPropInt( SENDINFO(m_nSpeed ), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_nCount), 8, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TESpriteSpray objects -static CTESpriteSpray g_TESpriteSpray( "Sprite Spray" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *pos - -// *dir - -// modelindex - -// speed - -// noise - -// count - -//----------------------------------------------------------------------------- -void TE_SpriteSpray( IRecipientFilter& filter, float delay, - const Vector *pos, const Vector *dir, int modelindex, int speed, float noise, int count ) -{ - g_TESpriteSpray.m_vecOrigin = *pos; - g_TESpriteSpray.m_vecDirection = *dir; - g_TESpriteSpray.m_nModelIndex = modelindex; - g_TESpriteSpray.m_nSpeed = speed; - g_TESpriteSpray.m_fNoise = noise; - g_TESpriteSpray.m_nCount = count; - - // Send it over the wire - g_TESpriteSpray.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud + +//----------------------------------------------------------------------------- +// Purpose: Dispatches Sprite Spray tempentity +//----------------------------------------------------------------------------- +class CTESpriteSpray : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTESpriteSpray, CBaseTempEntity ); + + CTESpriteSpray( const char *name ); + virtual ~CTESpriteSpray( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVector( m_vecDirection ); + CNetworkVar( int, m_nModelIndex ); + CNetworkVar( int, m_nSpeed ); + CNetworkVar( float, m_fNoise ); + CNetworkVar( int, m_nCount ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTESpriteSpray::CTESpriteSpray( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_vecDirection.Init(); + m_nModelIndex = 0; + m_fNoise = 0; + m_nSpeed = 0; + m_nCount = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTESpriteSpray::~CTESpriteSpray( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTESpriteSpray::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nModelIndex = g_sModelIndexSmoke; + m_fNoise = 0.8; + m_nCount = 5; + m_nSpeed = 30; + m_vecOrigin = current_origin; + + Vector forward, right; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward, &right, NULL ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, -25.0, right, m_vecOrigin.GetForModify() ); + + m_vecDirection.Init( random->RandomInt( -100, 100 ), random->RandomInt( -100, 100 ), random->RandomInt( 0, 100 ) ); + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTESpriteSpray, DT_TESpriteSpray) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropVector( SENDINFO(m_vecDirection), -1, SPROP_COORD), + SendPropModelIndex(SENDINFO(m_nModelIndex)), + SendPropFloat( SENDINFO(m_fNoise ), 8, SPROP_ROUNDDOWN, 0.0, 2.56 ), + SendPropInt( SENDINFO(m_nSpeed ), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_nCount), 8, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TESpriteSpray objects +static CTESpriteSpray g_TESpriteSpray( "Sprite Spray" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *pos - +// *dir - +// modelindex - +// speed - +// noise - +// count - +//----------------------------------------------------------------------------- +void TE_SpriteSpray( IRecipientFilter& filter, float delay, + const Vector *pos, const Vector *dir, int modelindex, int speed, float noise, int count ) +{ + g_TESpriteSpray.m_vecOrigin = *pos; + g_TESpriteSpray.m_vecDirection = *dir; + g_TESpriteSpray.m_nModelIndex = modelindex; + g_TESpriteSpray.m_nSpeed = speed; + g_TESpriteSpray.m_fNoise = noise; + g_TESpriteSpray.m_nCount = count; + + // Send it over the wire + g_TESpriteSpray.Create( filter, delay ); +} diff --git a/dlls/te_worlddecal.cpp b/dlls/te_worlddecal.cpp index 8f015a9d..77bb9932 100644 --- a/dlls/te_worlddecal.cpp +++ b/dlls/te_worlddecal.cpp @@ -1,117 +1,117 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "basetempentity.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Dispatches world decal tempentity -//----------------------------------------------------------------------------- -class CTEWorldDecal : public CBaseTempEntity -{ -public: - DECLARE_CLASS( CTEWorldDecal, CBaseTempEntity ); - - CTEWorldDecal( const char *name ); - virtual ~CTEWorldDecal( void ); - - virtual void Test( const Vector& current_origin, const QAngle& current_angles ); - - DECLARE_SERVERCLASS(); - -public: - CNetworkVector( m_vecOrigin ); - CNetworkVar( int, m_nIndex ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -//----------------------------------------------------------------------------- -CTEWorldDecal::CTEWorldDecal( const char *name ) : - CBaseTempEntity( name ) -{ - m_vecOrigin.Init(); - m_nIndex = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CTEWorldDecal::~CTEWorldDecal( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *current_origin - -// *current_angles - -//----------------------------------------------------------------------------- -void CTEWorldDecal::Test( const Vector& current_origin, const QAngle& current_angles ) -{ - // Fill in data - m_nIndex = 0; - m_vecOrigin = current_origin; - - Vector vecEnd; - - Vector forward; - - m_vecOrigin.GetForModify()[2] += 24; - - AngleVectors( current_angles, &forward ); - forward[2] = 0.0; - VectorNormalize( forward ); - - VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); - VectorMA( m_vecOrigin, 1024.0, forward, vecEnd ); - - trace_t tr; - - UTIL_TraceLine( m_vecOrigin, vecEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); - - m_vecOrigin = tr.endpos; - - CBroadcastRecipientFilter filter; - Create( filter, 0.0 ); -} - -IMPLEMENT_SERVERCLASS_ST(CTEWorldDecal, DT_TEWorldDecal) - SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), - SendPropInt( SENDINFO(m_nIndex), 9, SPROP_UNSIGNED ), -END_SEND_TABLE() - - -// Singleton to fire TEWorldDecal objects -static CTEWorldDecal g_TEWorldDecal( "World Decal" ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_dest - -// delay - -// *origin - -// *recipient - -// *pos - -// index - -//----------------------------------------------------------------------------- -void TE_WorldDecal( IRecipientFilter& filter, float delay, - const Vector* pos, int index ) -{ - g_TEWorldDecal.m_vecOrigin = *pos; - g_TEWorldDecal.m_nIndex = index; - - // Send it over the wire - g_TEWorldDecal.Create( filter, delay ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "basetempentity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Dispatches world decal tempentity +//----------------------------------------------------------------------------- +class CTEWorldDecal : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEWorldDecal, CBaseTempEntity ); + + CTEWorldDecal( const char *name ); + virtual ~CTEWorldDecal( void ); + + virtual void Test( const Vector& current_origin, const QAngle& current_angles ); + + DECLARE_SERVERCLASS(); + +public: + CNetworkVector( m_vecOrigin ); + CNetworkVar( int, m_nIndex ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CTEWorldDecal::CTEWorldDecal( const char *name ) : + CBaseTempEntity( name ) +{ + m_vecOrigin.Init(); + m_nIndex = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTEWorldDecal::~CTEWorldDecal( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *current_origin - +// *current_angles - +//----------------------------------------------------------------------------- +void CTEWorldDecal::Test( const Vector& current_origin, const QAngle& current_angles ) +{ + // Fill in data + m_nIndex = 0; + m_vecOrigin = current_origin; + + Vector vecEnd; + + Vector forward; + + m_vecOrigin.GetForModify()[2] += 24; + + AngleVectors( current_angles, &forward ); + forward[2] = 0.0; + VectorNormalize( forward ); + + VectorMA( m_vecOrigin, 50.0, forward, m_vecOrigin.GetForModify() ); + VectorMA( m_vecOrigin, 1024.0, forward, vecEnd ); + + trace_t tr; + + UTIL_TraceLine( m_vecOrigin, vecEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + m_vecOrigin = tr.endpos; + + CBroadcastRecipientFilter filter; + Create( filter, 0.0 ); +} + +IMPLEMENT_SERVERCLASS_ST(CTEWorldDecal, DT_TEWorldDecal) + SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD), + SendPropInt( SENDINFO(m_nIndex), 9, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// Singleton to fire TEWorldDecal objects +static CTEWorldDecal g_TEWorldDecal( "World Decal" ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_dest - +// delay - +// *origin - +// *recipient - +// *pos - +// index - +//----------------------------------------------------------------------------- +void TE_WorldDecal( IRecipientFilter& filter, float delay, + const Vector* pos, int index ) +{ + g_TEWorldDecal.m_vecOrigin = *pos; + g_TEWorldDecal.m_nIndex = index; + + // Send it over the wire + g_TEWorldDecal.Create( filter, delay ); +} diff --git a/dlls/triggers.cpp b/dlls/triggers.cpp index 27cf9990..69d46dad 100644 --- a/dlls/triggers.cpp +++ b/dlls/triggers.cpp @@ -1522,7 +1522,6 @@ void CChangeLevel::WarnAboutActiveLead( void ) void CChangeLevel::ChangeLevelNow( CBaseEntity *pActivator ) { CBaseEntity *pLandmark; - levellist_t levels[16]; Assert(!FStrEq(m_szMapName, "")); @@ -3616,7 +3615,7 @@ END_DATADESC() void CTriggerWind::Spawn( void ) { m_bSwitch = true; - m_nDirBase = GetLocalAngles().y; + m_nDirBase = static_cast(GetLocalAngles().y); BaseClass::Spawn(); @@ -3739,7 +3738,7 @@ void CTriggerWind::WindThink( void ) // Set new target direction and speed m_nSpeedTarget = m_nSpeedBase + random->RandomInt( -m_nSpeedNoise, m_nSpeedNoise ); - m_nDirTarget = UTIL_AngleMod( m_nDirBase + random->RandomInt(-m_nDirNoise, m_nDirNoise) ); + m_nDirTarget = static_cast(UTIL_AngleMod( m_nDirBase + random->RandomInt(-m_nDirNoise, m_nDirNoise) )); } else { @@ -3747,14 +3746,14 @@ void CTriggerWind::WindThink( void ) // either ramp up, or sleep till change if (abs(m_nSpeedTarget - m_nSpeedCurrent) > MAX_WIND_CHANGE) { - m_nSpeedCurrent += (m_nSpeedTarget > m_nSpeedCurrent) ? MAX_WIND_CHANGE : -MAX_WIND_CHANGE; + m_nSpeedCurrent += (m_nSpeedTarget > m_nSpeedCurrent) ? static_cast(MAX_WIND_CHANGE) : static_cast(-MAX_WIND_CHANGE); bDone = false; } if (abs(m_nDirTarget - m_nDirCurrent) > MAX_WIND_CHANGE) { - m_nDirCurrent = UTIL_ApproachAngle( m_nDirTarget, m_nDirCurrent, MAX_WIND_CHANGE ); + m_nDirCurrent = static_cast(UTIL_ApproachAngle( m_nDirTarget, m_nDirCurrent, MAX_WIND_CHANGE )); bDone = false; } diff --git a/dlls/util.cpp b/dlls/util.cpp index 092b2158..3883953b 100644 --- a/dlls/util.cpp +++ b/dlls/util.cpp @@ -687,10 +687,10 @@ void UTIL_GetPlayerConnectionInfo( int playerIndex, int& ping, int &packetloss ) // Source pings by half a tick to match the old GoldSrc pings. latency -= TICKS_TO_TIME( 0.5f ); - ping = latency * 1000.0f; // as msecs + ping = static_cast(latency * 1000.0f); // as msecs ping = clamp( ping, 5, 1000 ); // set bounds, dont show pings under 5 msecs - packetloss = 100.0f * nci->GetAvgLoss( FLOW_INCOMING ); // loss in percentage + packetloss = static_cast(100.0f * nci->GetAvgLoss( FLOW_INCOMING )); // loss in percentage packetloss = clamp( packetloss, 0, 100 ); } else @@ -704,7 +704,7 @@ static unsigned short FixedUnsigned16( float value, float scale ) { int output; - output = value * scale; + output = static_cast(value * scale); if ( output < 0 ) output = 0; if ( output > 0xFFFF ) @@ -2151,7 +2151,7 @@ void UTIL_SetClientVisibilityPVS( edict_t *pClient, const unsigned char *pvs, in { if ( pClient == UTIL_GetCurrentCheckClient() ) { - Assert( pvssize <= sizeof(g_CheckClient.m_checkVisibilityPVS) ); + Assert( pvssize <= static_cast(sizeof(g_CheckClient.m_checkVisibilityPVS)) ); g_CheckClient.m_bClientPVSIsExpanded = false; @@ -2289,7 +2289,6 @@ CBaseEntity *UTIL_EntitiesInPVS( CBaseEntity *pPVSEntity, CBaseEntity *pStarting Vector org; static byte pvs[ MAX_MAP_CLUSTERS/8 ]; static Vector lastOrg( 0, 0, 0 ); - static int lastCluster = -1; if ( !pPVSEntity ) return NULL; diff --git a/dlls/util.h b/dlls/util.h index a1f823b0..87e95589 100644 --- a/dlls/util.h +++ b/dlls/util.h @@ -1,619 +1,620 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Misc utility code. -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef UTIL_H -#define UTIL_H -#ifdef _WIN32 -#pragma once -#endif - -#include "ai_activity.h" -#include "enginecallback.h" -#include "basetypes.h" -#include "tempentity.h" -#include "string_t.h" -#include "gamestringpool.h" -#include "engine/IEngineTrace.h" -#include "worldsize.h" -#include "dt_send.h" -#include "server_class.h" -#include "shake.h" - -#include "vstdlib/random.h" -#include - -#include "utlvector.h" -#include "util_shared.h" -#include "shareddefs.h" -#include "networkvar.h" - -struct levellist_t; -class IServerNetworkable; -class IEntityFactory; - -#ifdef _WIN32 - #define SETUP_EXTERNC(mapClassName)\ - extern "C" _declspec( dllexport ) IServerNetworkable* mapClassName( void ); -#else - #define SETUP_EXTERNC(mapClassName) -#endif - - -#include "tier0/memdbgon.h" - - -// entity creation -// creates an entity that has not been linked to a classname -template< class T > -T *_CreateEntityTemplate( T *newEnt, const char *className ) -{ - newEnt = new T; // this is the only place 'new' should be used! - newEnt->PostConstructor( className ); - return newEnt; -} - -#include "tier0/memdbgoff.h" - -// creates an entity by name, and ensure it's correctness -// does not spawn the entity -// use the CREATE_ENTITY() macro which wraps this, instead of using it directly -template< class T > -T *_CreateEntity( T *newClass, const char *className ) -{ - T *newEnt = dynamic_cast( CreateEntityByName(className) ); - if ( !newEnt ) - { - Warning( "classname %s used to create wrong class type\n" ); - Assert(0); - } - - return newEnt; -} - -#define CREATE_ENTITY( newClass, className ) _CreateEntity( (newClass*)NULL, className ) -#define CREATE_UNSAVED_ENTITY( newClass, className ) _CreateEntityTemplate( (newClass*)NULL, className ) - - -// This is the glue that hooks .MAP entity class names to our CPP classes -abstract_class IEntityFactoryDictionary -{ -public: - virtual void InstallFactory( IEntityFactory *pFactory, const char *pClassName ) = 0; - virtual IServerNetworkable *Create( const char *pClassName ) = 0; - virtual void Destroy( const char *pClassName, IServerNetworkable *pNetworkable ) = 0; - virtual IEntityFactory *FindFactory( const char *pClassName ) = 0; - virtual const char *GetCannonicalName( const char *pClassName ) = 0; -}; - -IEntityFactoryDictionary *EntityFactoryDictionary(); - -inline bool CanCreateEntityClass( const char *pszClassname ) -{ - return ( EntityFactoryDictionary() != NULL && EntityFactoryDictionary()->FindFactory( pszClassname ) != NULL ); -} - -abstract_class IEntityFactory -{ -public: - virtual IServerNetworkable *Create( const char *pClassName ) = 0; - virtual void Destroy( IServerNetworkable *pNetworkable ) = 0; - virtual size_t GetEntitySize() = 0; -}; - -template -class CEntityFactory : public IEntityFactory -{ -public: - CEntityFactory( const char *pClassName ) - { - EntityFactoryDictionary()->InstallFactory( this, pClassName ); - } - - IServerNetworkable *Create( const char *pClassName ) - { - T* pEnt = _CreateEntityTemplate((T*)NULL, pClassName); - return pEnt->NetworkProp(); - } - - void Destroy( IServerNetworkable *pNetworkable ) - { - if ( pNetworkable ) - { - pNetworkable->Release(); - } - } - - virtual size_t GetEntitySize() - { - return sizeof(T); - } -}; - -#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ - static CEntityFactory mapClassName( #mapClassName ); - - -// -// Conversion among the three types of "entity", including identity-conversions. -// -inline int ENTINDEX( edict_t *pEdict) -{ - return engine->IndexOfEdict(pEdict); -} - -int ENTINDEX( CBaseEntity *pEnt ); - -inline edict_t* INDEXENT( int iEdictNum ) -{ - return engine->PEntityOfEntIndex(iEdictNum); -} - -// Testing the three types of "entity" for nullity -inline bool FNullEnt(const edict_t* pent) -{ - return pent == NULL || ENTINDEX((edict_t*)pent) == 0; -} - -// 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 - -class CBaseEntity; -class CBasePlayer; - -extern CGlobalVars *gpGlobals; - -// Misc useful -inline bool FStrEq(const char *sz1, const char *sz2) -{ - return(stricmp(sz1, sz2) == 0); -} - -#if 0 -// UNDONE: Remove/alter MAKE_STRING so we can do this? -inline bool FStrEq( string_t str1, string_t str2 ) -{ - // now that these are pooled, we can compare them with - // integer equality - return str1 == str2; -} -#endif - -const char *nexttoken(char *token, const char *str, char sep); - -// Misc. Prototypes -void UTIL_SetSize (CBaseEntity *pEnt, const Vector &vecMin, const Vector &vecMax); -void UTIL_ClearTrace ( trace_t &trace ); -void UTIL_SetTrace (trace_t& tr, const Ray_t &ray, edict_t* edict, float fraction, int hitgroup, unsigned int contents, const Vector& normal, float intercept ); - -int UTIL_PrecacheDecal ( const char *name, bool preload = false ); - -//----------------------------------------------------------------------------- - -float UTIL_GetSimulationInterval(); - -//----------------------------------------------------------------------------- -// Purpose: Gets a player pointer by 1-based index -// If player is not yet spawned or connected, returns NULL -// Input : playerIndex - index of the player - first player is index 1 -//----------------------------------------------------------------------------- - -// NOTENOTE: Use UTIL_GetLocalPlayer instead of UTIL_PlayerByIndex IF you're in single player -// and you want the player. -CBasePlayer *UTIL_PlayerByIndex( int playerIndex ); - -// NOTENOTE: Use this instead of UTIL_PlayerByIndex IF you're in single player -// and you want the player. -// not useable in multiplayer - see UTIL_GetListenServerHost() -CBasePlayer* UTIL_GetLocalPlayer( void ); - -// get the local player on a listen server -CBasePlayer *UTIL_GetListenServerHost( void ); - -CBasePlayer* UTIL_PlayerByUserId( int userID ); -CBasePlayer* UTIL_PlayerByName( const char *name ); // not case sensitive - -// Returns true if the command was issued by the listenserver host, or by the dedicated server, via rcon or the server console. -// This is valid during ConCommand execution. -bool UTIL_IsCommandIssuedByServerAdmin( void ); - -CBaseEntity* UTIL_EntityByIndex( int entityIndex ); - -void UTIL_GetPlayerConnectionInfo( int playerIndex, int& ping, int &packetloss ); - -void UTIL_SetClientVisibilityPVS( edict_t *pClient, const unsigned char *pvs, int pvssize ); -bool UTIL_ClientPVSIsExpanded(); - -edict_t *UTIL_FindClientInPVS( edict_t *pEdict ); -edict_t *UTIL_FindClientInVisibilityPVS( edict_t *pEdict ); - -// This is a version which finds any clients whose PVS intersects the box -CBaseEntity *UTIL_FindClientInPVS( const Vector &vecBoxMins, const Vector &vecBoxMaxs ); - -CBaseEntity *UTIL_EntitiesInPVS( CBaseEntity *pPVSEntity, CBaseEntity *pStartingEntity ); - -//----------------------------------------------------------------------------- -// class CFlaggedEntitiesEnum -//----------------------------------------------------------------------------- -// enumerate entities that match a set of edict flags into a static array -class CFlaggedEntitiesEnum : public IPartitionEnumerator -{ -public: - CFlaggedEntitiesEnum( CBaseEntity **pList, int listMax, int flagMask ); - - // This gets called by the enumeration methods with each element - // that passes the test. - virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); - - int GetCount() { return m_count; } - bool AddToList( CBaseEntity *pEntity ); - -private: - CBaseEntity **m_pList; - int m_listMax; - int m_flagMask; - int m_count; -}; - -// Pass in an array of pointers and an array size, it fills the array and returns the number inserted -int UTIL_EntitiesInBox( const Vector &mins, const Vector &maxs, CFlaggedEntitiesEnum *pEnum ); -int UTIL_EntitiesAlongRay( const Ray_t &ray, CFlaggedEntitiesEnum *pEnum ); -int UTIL_EntitiesInSphere( const Vector ¢er, float radius, CFlaggedEntitiesEnum *pEnum ); - -inline int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) -{ - CFlaggedEntitiesEnum boxEnum( pList, listMax, flagMask ); - return UTIL_EntitiesInBox( mins, maxs, &boxEnum ); -} - -inline int UTIL_EntitiesAlongRay( CBaseEntity **pList, int listMax, const Ray_t &ray, int flagMask ) -{ - CFlaggedEntitiesEnum rayEnum( pList, listMax, flagMask ); - return UTIL_EntitiesAlongRay( ray, &rayEnum ); -} - -inline int UTIL_EntitiesInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius, int flagMask ) -{ - CFlaggedEntitiesEnum sphereEnum( pList, listMax, flagMask ); - return UTIL_EntitiesInSphere( center, radius, &sphereEnum ); -} - -// marks the entity for deletion so it will get removed next frame -void UTIL_Remove( IServerNetworkable *oldObj ); -void UTIL_Remove( CBaseEntity *oldObj ); - -// deletes an entity, without any delay. Only use this when sure no pointers rely on this entity. -void UTIL_DisableRemoveImmediate(); -void UTIL_EnableRemoveImmediate(); -void UTIL_RemoveImmediate( CBaseEntity *oldObj ); - -// make this a fixed size so it just sits on the stack -#define MAX_SPHERE_QUERY 512 -class CEntitySphereQuery -{ -public: - // currently this builds the list in the constructor - // UNDONE: make an iterative query of ISpatialPartition so we could - // make queries like this optimal - CEntitySphereQuery( const Vector ¢er, float radius, int flagMask=0 ); - CBaseEntity *GetCurrentEntity(); - inline void NextEntity() { m_listIndex++; } - -private: - int m_listIndex; - int m_listCount; - CBaseEntity *m_pList[MAX_SPHERE_QUERY]; -}; - -enum soundlevel_t; - -// Drops an entity onto the floor -int UTIL_DropToFloor( CBaseEntity *pEntity, unsigned int mask ); - -// Returns false if any part of the bottom of the entity is off an edge that is not a staircase. -bool UTIL_CheckBottom( CBaseEntity *pEntity, ITraceFilter *pTraceFilter, float flStepSize ); - -void UTIL_SetOrigin ( CBaseEntity *entity, const Vector &vecOrigin, bool bFireTriggers = false ); -void UTIL_EmitAmbientSound ( int entindex, const Vector &vecOrigin, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float soundtime = 0.0f, float *duration = NULL ); -void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); -void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake=false ); -void UTIL_ScreenShakeObject ( CBaseEntity *pEnt, const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake=false ); -void UTIL_ViewPunch ( const Vector ¢er, QAngle angPunch, float radius, bool bInAir ); -void UTIL_ShowMessage ( const char *pString, CBasePlayer *pPlayer ); -void UTIL_ShowMessageAll ( const char *pString ); -void UTIL_ScreenFadeAll ( const color32 &color, float fadeTime, float holdTime, int flags ); -void UTIL_ScreenFade ( CBaseEntity *pEntity, const color32 &color, float fadeTime, float fadeHold, int flags ); -void UTIL_MuzzleFlash ( const Vector &origin, const QAngle &angles, int scale, int type ); -Vector UTIL_PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint, bool clampEnds = false ); - -int UTIL_EntityInSolid( CBaseEntity *ent ); - -bool UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); -void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); -void UTIL_BloodSpray( const Vector &pos, const Vector &dir, int color, int amount, int flags ); -Vector UTIL_RandomBloodVector( void ); -void UTIL_ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName = NULL ); -void UTIL_PlayerDecalTrace( trace_t *pTrace, int playernum ); -void UTIL_Smoke( const Vector &origin, const float scale, const float framerate ); -void UTIL_AxisStringToPointDir( Vector &start, Vector &dir, const char *pString ); -void UTIL_AxisStringToPointPoint( Vector &start, Vector &end, const char *pString ); -void UTIL_AxisStringToUnitDir( Vector &dir, const char *pString ); -void UTIL_ClipPunchAngleOffset( QAngle &in, const QAngle &punch, const QAngle &clip ); -void UTIL_PredictedPosition( CBaseEntity *pTarget, float flTimeDelta, Vector *vecPredictedPosition ); -void UTIL_Beam( Vector &Start, Vector &End, int nModelIndex, int nHaloIndex, unsigned char FrameStart, unsigned char FrameRate, - float Life, unsigned char Width, unsigned char EndWidth, unsigned char FadeLength, unsigned char Noise, unsigned char Red, unsigned char Green, - unsigned char Blue, unsigned char Brightness, unsigned char Speed); - -char *UTIL_VarArgs( char *format, ... ); -bool UTIL_IsValidEntity( CBaseEntity *pEnt ); -bool UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); - -// snaps a vector to the nearest axis vector (if within epsilon) -void UTIL_SnapDirectionToAxis( Vector &direction, float epsilon = 0.01f ); - -//Set the entity to point at the target specified -bool UTIL_PointAtEntity( CBaseEntity *pEnt, CBaseEntity *pTarget ); -void UTIL_PointAtNamedEntity( CBaseEntity *pEnt, string_t strTarget ); - -// Search for water transition along a vertical line -float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); - -// Like UTIL_WaterLevel, but *way* less expensive. -// I didn't replace UTIL_WaterLevel everywhere to avoid breaking anything. -float UTIL_FindWaterSurface( const Vector &position, float minz, float maxz ); - -void UTIL_Bubbles( const Vector& mins, const Vector& maxs, int count ); -void UTIL_BubbleTrail( const Vector& from, const Vector& to, int count ); - -// allows precacheing of other entities -void UTIL_PrecacheOther( const char *szClassname, const char *modelName = NULL ); - -// prints a message to each client -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 ); -} - -void UTIL_ValidateSoundName( string_t &name, const char *defaultStr ); - -void UTIL_ClientPrintFilter( IRecipientFilter& filter, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); - -// prints messages through the HUD -void ClientPrint( CBasePlayer *player, 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) -void UTIL_SayText( const char *pText, CBasePlayer *pEntity ); -void UTIL_SayTextAll( const char *pText, CBasePlayer *pEntity = NULL, bool bChat = false ); -void UTIL_SayTextFilter( IRecipientFilter& filter, const char *pText, CBasePlayer *pEntity, bool bChat ); -void UTIL_SayText2Filter( IRecipientFilter& filter, CBasePlayer *pEntity, bool bChat, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); - -byte *UTIL_LoadFileForMe( const char *filename, int *pLength ); -void UTIL_FreeFile( byte *buffer ); - -class CGameTrace; -typedef CGameTrace trace_t; - -//----------------------------------------------------------------------------- -// These are inlined for backwards compatibility -//----------------------------------------------------------------------------- -inline float UTIL_Approach( float target, float value, float speed ) -{ - return Approach( target, value, speed ); -} - -inline float UTIL_ApproachAngle( float target, float value, float speed ) -{ - return ApproachAngle( target, value, speed ); -} - -inline float UTIL_AngleDistance( float next, float cur ) -{ - return AngleDistance( next, cur ); -} - -inline float UTIL_AngleMod(float a) -{ - return anglemod(a); -} - -inline float UTIL_AngleDiff( float destAngle, float srcAngle ) -{ - return AngleDiff( destAngle, srcAngle ); -} - -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; - - -//----------------------------------------------------------------------------- -// Sets the model to be associated with an entity -//----------------------------------------------------------------------------- -void UTIL_SetModel( CBaseEntity *pEntity, const char *pModelName ); - - -// prints as transparent 'title' to the HUD -void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); -void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage ); - -// brings up hud keyboard hints display -void UTIL_HudHintText( CBaseEntity *pEntity, const char *pMessage ); - -// Writes message to console with timestamp and FragLog header. -void UTIL_LogPrintf( char *fmt, ... ); - -// Sorta like FInViewCone, but for nonNPCs. -float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); - -void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames - -// Misc functions -int BuildChangeList( levellist_t *pLevelList, int maxList ); - -// computes gravity scale for an absolute gravity. Pass the result into CBaseEntity::SetGravity() -float UTIL_ScaleForGravity( float desiredGravity ); -// -// 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((bool)((f)!=0), #f, __FILE__, __LINE__, NULL) -#define ASSERTSZ(f, sz) DBG_AssertFunction((bool)((f)!=0), #f, __FILE__, __LINE__, sz) -#else // !DEBUG -#define ASSERT(f) -#define ASSERTSZ(f, sz) -#endif // !DEBUG - - -// -// Constants that were used only by QC (maybe not used at all now) -// -// Un-comment only as needed -// - -#include "globals.h" - -#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_START_ON 1 -#define SF_BRUSH_ROTATE_BACKWARDS 2 -#define SF_BRUSH_ROTATE_Z_AXIS 4 -#define SF_BRUSH_ROTATE_X_AXIS 8 - - -#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 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 - -// Sound Utilities - -enum soundlevel_t; - -void SENTENCEG_Init(); -void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); -int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, soundlevel_t soundlevel, int flags, int pitch); -int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, soundlevel_t soundlevel, int flags, int pitch); -int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, soundlevel_t soundlevel, int flags, int pitch, int ipick, int freset); -void SENTENCEG_PlaySentenceIndex( edict_t *entity, int iSentenceIndex, float volume, soundlevel_t soundlevel, int flags, int pitch ); -int SENTENCEG_PickRndSz(const char *szrootname); -int SENTENCEG_GetIndex(const char *szrootname); -int SENTENCEG_Lookup(const char *sample); - -char TEXTURETYPE_Find( trace_t *ptr ); - -void UTIL_EmitSoundSuit(edict_t *entity, const char *sample); -int UTIL_EmitGroupIDSuit(edict_t *entity, int isentenceg); -int UTIL_EmitGroupnameSuit(edict_t *entity, const char *groupname); -void UTIL_RestartAmbientSounds( void ); - -class EntityMatrix : public VMatrix -{ -public: - void InitFromEntity( CBaseEntity *pEntity, int iAttachment=0 ); - void InitFromEntityLocal( CBaseEntity *entity ); - - inline Vector LocalToWorld( const Vector &vVec ) const - { - return VMul4x3( vVec ); - } - - inline Vector WorldToLocal( const Vector &vVec ) const - { - return VMul4x3Transpose( vVec ); - } - - inline Vector LocalToWorldRotation( const Vector &vVec ) const - { - return VMul3x3( vVec ); - } - - inline Vector WorldToLocalRotation( const Vector &vVec ) const - { - return VMul3x3Transpose( vVec ); - } -}; - -inline float UTIL_DistApprox( const Vector &vec1, const Vector &vec2 ); -inline float UTIL_DistApprox2D( const Vector &vec1, const Vector &vec2 ); - -//--------------------------------------------------------- -//--------------------------------------------------------- -inline float UTIL_DistApprox( const Vector &vec1, const Vector &vec2 ) -{ - float dx; - float dy; - float dz; - - dx = vec1.x - vec2.x; - dy = vec1.y - vec2.y; - dz = vec1.z - vec2.z; - - return fabs(dx) + fabs(dy) + fabs(dz); -} - -//--------------------------------------------------------- -//--------------------------------------------------------- -inline float UTIL_DistApprox2D( const Vector &vec1, const Vector &vec2 ) -{ - float dx; - float dy; - - dx = vec1.x - vec2.x; - dy = vec1.y - vec2.y; - - return fabs(dx) + fabs(dy); -} - -// Find out if an entity is facing another entity or position within a given tolerance range -bool UTIL_IsFacingWithinTolerance( CBaseEntity *pViewer, const Vector &vecPosition, float flDotTolerance, float *pflDot = NULL ); -bool UTIL_IsFacingWithinTolerance( CBaseEntity *pViewer, CBaseEntity *pTarget, float flDotTolerance, float *pflDot = NULL ); - -void UTIL_GetDebugColorForRelationship( int nRelationship, int &r, int &g, int &b ); - -struct datamap_t; -extern const char *UTIL_FunctionToName( datamap_t *pMap, void *function ); -extern void *UTIL_FunctionFromName( datamap_t *pMap, const char *pName ); - -int UTIL_GetCommandClientIndex( void ); -CBasePlayer *UTIL_GetCommandClient( void ); - -AngularImpulse WorldToLocalRotation( const VMatrix &localToWorld, const Vector &worldAxis, float rotation ); - -bool UTIL_LoadAndSpawnEntitiesFromScript( CUtlVector &entities, const char *pScriptFile, const char *pBlock, bool bActivate = true ); - -#endif // UTIL_H +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Misc utility code. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef UTIL_H +#define UTIL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ai_activity.h" +#include "enginecallback.h" +#include "basetypes.h" +#include "tempentity.h" +#include "string_t.h" +#include "gamestringpool.h" +#include "engine/IEngineTrace.h" +#include "worldsize.h" +#include "dt_send.h" +#include "server_class.h" +#include "shake.h" + +#include "vstdlib/random.h" +#include + +#include "utlvector.h" +#include "util_shared.h" +#include "shareddefs.h" +#include "networkvar.h" + +struct levellist_t; +class IServerNetworkable; +class IEntityFactory; + +#ifdef _WIN32 + #define SETUP_EXTERNC(mapClassName)\ + extern "C" _declspec( dllexport ) IServerNetworkable* mapClassName( void ); +#else + #define SETUP_EXTERNC(mapClassName) +#endif + + +#include "tier0/memdbgon.h" + +CBaseEntity *CreateEntityByName(const char *className, int iForceEdictIndex); + +// entity creation +// creates an entity that has not been linked to a classname +template< class T > +T *_CreateEntityTemplate( T *newEnt, const char *className ) +{ + newEnt = new T; // this is the only place 'new' should be used! + newEnt->PostConstructor( className ); + return newEnt; +} + +#include "tier0/memdbgoff.h" + +// creates an entity by name, and ensure it's correctness +// does not spawn the entity +// use the CREATE_ENTITY() macro which wraps this, instead of using it directly +template< class T > +T *_CreateEntity( T *newClass, const char *className ) +{ + T *newEnt = dynamic_cast( CreateEntityByName(className, -1) ); + if ( !newEnt ) + { + Warning( "classname %s used to create wrong class type\n" ); + Assert(0); + } + + return newEnt; +} + +#define CREATE_ENTITY( newClass, className ) _CreateEntity( (newClass*)NULL, className ) +#define CREATE_UNSAVED_ENTITY( newClass, className ) _CreateEntityTemplate( (newClass*)NULL, className ) + + +// This is the glue that hooks .MAP entity class names to our CPP classes +abstract_class IEntityFactoryDictionary +{ +public: + virtual void InstallFactory( IEntityFactory *pFactory, const char *pClassName ) = 0; + virtual IServerNetworkable *Create( const char *pClassName ) = 0; + virtual void Destroy( const char *pClassName, IServerNetworkable *pNetworkable ) = 0; + virtual IEntityFactory *FindFactory( const char *pClassName ) = 0; + virtual const char *GetCannonicalName( const char *pClassName ) = 0; +}; + +IEntityFactoryDictionary *EntityFactoryDictionary(); + +inline bool CanCreateEntityClass( const char *pszClassname ) +{ + return ( EntityFactoryDictionary() != NULL && EntityFactoryDictionary()->FindFactory( pszClassname ) != NULL ); +} + +abstract_class IEntityFactory +{ +public: + virtual IServerNetworkable *Create( const char *pClassName ) = 0; + virtual void Destroy( IServerNetworkable *pNetworkable ) = 0; + virtual size_t GetEntitySize() = 0; +}; + +template +class CEntityFactory : public IEntityFactory +{ +public: + CEntityFactory( const char *pClassName ) + { + EntityFactoryDictionary()->InstallFactory( this, pClassName ); + } + + IServerNetworkable *Create( const char *pClassName ) + { + T* pEnt = _CreateEntityTemplate((T*)NULL, pClassName); + return pEnt->NetworkProp(); + } + + void Destroy( IServerNetworkable *pNetworkable ) + { + if ( pNetworkable ) + { + pNetworkable->Release(); + } + } + + virtual size_t GetEntitySize() + { + return sizeof(T); + } +}; + +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + static CEntityFactory mapClassName( #mapClassName ); + + +// +// Conversion among the three types of "entity", including identity-conversions. +// +inline int ENTINDEX( edict_t *pEdict) +{ + return engine->IndexOfEdict(pEdict); +} + +int ENTINDEX( CBaseEntity *pEnt ); + +inline edict_t* INDEXENT( int iEdictNum ) +{ + return engine->PEntityOfEntIndex(iEdictNum); +} + +// Testing the three types of "entity" for nullity +inline bool FNullEnt(const edict_t* pent) +{ + return pent == NULL || ENTINDEX((edict_t*)pent) == 0; +} + +// 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 + +class CBaseEntity; +class CBasePlayer; + +extern CGlobalVars *gpGlobals; + +// Misc useful +inline bool FStrEq(const char *sz1, const char *sz2) +{ + return(stricmp(sz1, sz2) == 0); +} + +#if 0 +// UNDONE: Remove/alter MAKE_STRING so we can do this? +inline bool FStrEq( string_t str1, string_t str2 ) +{ + // now that these are pooled, we can compare them with + // integer equality + return str1 == str2; +} +#endif + +const char *nexttoken(char *token, const char *str, char sep); + +// Misc. Prototypes +void UTIL_SetSize (CBaseEntity *pEnt, const Vector &vecMin, const Vector &vecMax); +void UTIL_ClearTrace ( trace_t &trace ); +void UTIL_SetTrace (trace_t& tr, const Ray_t &ray, edict_t* edict, float fraction, int hitgroup, unsigned int contents, const Vector& normal, float intercept ); + +int UTIL_PrecacheDecal ( const char *name, bool preload = false ); + +//----------------------------------------------------------------------------- + +float UTIL_GetSimulationInterval(); + +//----------------------------------------------------------------------------- +// Purpose: Gets a player pointer by 1-based index +// If player is not yet spawned or connected, returns NULL +// Input : playerIndex - index of the player - first player is index 1 +//----------------------------------------------------------------------------- + +// NOTENOTE: Use UTIL_GetLocalPlayer instead of UTIL_PlayerByIndex IF you're in single player +// and you want the player. +CBasePlayer *UTIL_PlayerByIndex( int playerIndex ); + +// NOTENOTE: Use this instead of UTIL_PlayerByIndex IF you're in single player +// and you want the player. +// not useable in multiplayer - see UTIL_GetListenServerHost() +CBasePlayer* UTIL_GetLocalPlayer( void ); + +// get the local player on a listen server +CBasePlayer *UTIL_GetListenServerHost( void ); + +CBasePlayer* UTIL_PlayerByUserId( int userID ); +CBasePlayer* UTIL_PlayerByName( const char *name ); // not case sensitive + +// Returns true if the command was issued by the listenserver host, or by the dedicated server, via rcon or the server console. +// This is valid during ConCommand execution. +bool UTIL_IsCommandIssuedByServerAdmin( void ); + +CBaseEntity* UTIL_EntityByIndex( int entityIndex ); + +void UTIL_GetPlayerConnectionInfo( int playerIndex, int& ping, int &packetloss ); + +void UTIL_SetClientVisibilityPVS( edict_t *pClient, const unsigned char *pvs, int pvssize ); +bool UTIL_ClientPVSIsExpanded(); + +edict_t *UTIL_FindClientInPVS( edict_t *pEdict ); +edict_t *UTIL_FindClientInVisibilityPVS( edict_t *pEdict ); + +// This is a version which finds any clients whose PVS intersects the box +CBaseEntity *UTIL_FindClientInPVS( const Vector &vecBoxMins, const Vector &vecBoxMaxs ); + +CBaseEntity *UTIL_EntitiesInPVS( CBaseEntity *pPVSEntity, CBaseEntity *pStartingEntity ); + +//----------------------------------------------------------------------------- +// class CFlaggedEntitiesEnum +//----------------------------------------------------------------------------- +// enumerate entities that match a set of edict flags into a static array +class CFlaggedEntitiesEnum : public IPartitionEnumerator +{ +public: + CFlaggedEntitiesEnum( CBaseEntity **pList, int listMax, int flagMask ); + + // This gets called by the enumeration methods with each element + // that passes the test. + virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); + + int GetCount() { return m_count; } + bool AddToList( CBaseEntity *pEntity ); + +private: + CBaseEntity **m_pList; + int m_listMax; + int m_flagMask; + int m_count; +}; + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +int UTIL_EntitiesInBox( const Vector &mins, const Vector &maxs, CFlaggedEntitiesEnum *pEnum ); +int UTIL_EntitiesAlongRay( const Ray_t &ray, CFlaggedEntitiesEnum *pEnum ); +int UTIL_EntitiesInSphere( const Vector ¢er, float radius, CFlaggedEntitiesEnum *pEnum ); + +inline int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + CFlaggedEntitiesEnum boxEnum( pList, listMax, flagMask ); + return UTIL_EntitiesInBox( mins, maxs, &boxEnum ); +} + +inline int UTIL_EntitiesAlongRay( CBaseEntity **pList, int listMax, const Ray_t &ray, int flagMask ) +{ + CFlaggedEntitiesEnum rayEnum( pList, listMax, flagMask ); + return UTIL_EntitiesAlongRay( ray, &rayEnum ); +} + +inline int UTIL_EntitiesInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius, int flagMask ) +{ + CFlaggedEntitiesEnum sphereEnum( pList, listMax, flagMask ); + return UTIL_EntitiesInSphere( center, radius, &sphereEnum ); +} + +// marks the entity for deletion so it will get removed next frame +void UTIL_Remove( IServerNetworkable *oldObj ); +void UTIL_Remove( CBaseEntity *oldObj ); + +// deletes an entity, without any delay. Only use this when sure no pointers rely on this entity. +void UTIL_DisableRemoveImmediate(); +void UTIL_EnableRemoveImmediate(); +void UTIL_RemoveImmediate( CBaseEntity *oldObj ); + +// make this a fixed size so it just sits on the stack +#define MAX_SPHERE_QUERY 512 +class CEntitySphereQuery +{ +public: + // currently this builds the list in the constructor + // UNDONE: make an iterative query of ISpatialPartition so we could + // make queries like this optimal + CEntitySphereQuery( const Vector ¢er, float radius, int flagMask=0 ); + CBaseEntity *GetCurrentEntity(); + inline void NextEntity() { m_listIndex++; } + +private: + int m_listIndex; + int m_listCount; + CBaseEntity *m_pList[MAX_SPHERE_QUERY]; +}; + +enum soundlevel_t; + +// Drops an entity onto the floor +int UTIL_DropToFloor( CBaseEntity *pEntity, unsigned int mask ); + +// Returns false if any part of the bottom of the entity is off an edge that is not a staircase. +bool UTIL_CheckBottom( CBaseEntity *pEntity, ITraceFilter *pTraceFilter, float flStepSize ); + +void UTIL_SetOrigin ( CBaseEntity *entity, const Vector &vecOrigin, bool bFireTriggers = false ); +void UTIL_EmitAmbientSound ( int entindex, const Vector &vecOrigin, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float soundtime = 0.0f, float *duration = NULL ); +void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake=false ); +void UTIL_ScreenShakeObject ( CBaseEntity *pEnt, const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake=false ); +void UTIL_ViewPunch ( const Vector ¢er, QAngle angPunch, float radius, bool bInAir ); +void UTIL_ShowMessage ( const char *pString, CBasePlayer *pPlayer ); +void UTIL_ShowMessageAll ( const char *pString ); +void UTIL_ScreenFadeAll ( const color32 &color, float fadeTime, float holdTime, int flags ); +void UTIL_ScreenFade ( CBaseEntity *pEntity, const color32 &color, float fadeTime, float fadeHold, int flags ); +void UTIL_MuzzleFlash ( const Vector &origin, const QAngle &angles, int scale, int type ); +Vector UTIL_PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint, bool clampEnds = false ); + +int UTIL_EntityInSolid( CBaseEntity *ent ); + +bool UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +void UTIL_BloodSpray( const Vector &pos, const Vector &dir, int color, int amount, int flags ); +Vector UTIL_RandomBloodVector( void ); +void UTIL_ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName = NULL ); +void UTIL_PlayerDecalTrace( trace_t *pTrace, int playernum ); +void UTIL_Smoke( const Vector &origin, const float scale, const float framerate ); +void UTIL_AxisStringToPointDir( Vector &start, Vector &dir, const char *pString ); +void UTIL_AxisStringToPointPoint( Vector &start, Vector &end, const char *pString ); +void UTIL_AxisStringToUnitDir( Vector &dir, const char *pString ); +void UTIL_ClipPunchAngleOffset( QAngle &in, const QAngle &punch, const QAngle &clip ); +void UTIL_PredictedPosition( CBaseEntity *pTarget, float flTimeDelta, Vector *vecPredictedPosition ); +void UTIL_Beam( Vector &Start, Vector &End, int nModelIndex, int nHaloIndex, unsigned char FrameStart, unsigned char FrameRate, + float Life, unsigned char Width, unsigned char EndWidth, unsigned char FadeLength, unsigned char Noise, unsigned char Red, unsigned char Green, + unsigned char Blue, unsigned char Brightness, unsigned char Speed); + +char *UTIL_VarArgs( char *format, ... ); +bool UTIL_IsValidEntity( CBaseEntity *pEnt ); +bool UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); + +// snaps a vector to the nearest axis vector (if within epsilon) +void UTIL_SnapDirectionToAxis( Vector &direction, float epsilon = 0.01f ); + +//Set the entity to point at the target specified +bool UTIL_PointAtEntity( CBaseEntity *pEnt, CBaseEntity *pTarget ); +void UTIL_PointAtNamedEntity( CBaseEntity *pEnt, string_t strTarget ); + +// Search for water transition along a vertical line +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); + +// Like UTIL_WaterLevel, but *way* less expensive. +// I didn't replace UTIL_WaterLevel everywhere to avoid breaking anything. +float UTIL_FindWaterSurface( const Vector &position, float minz, float maxz ); + +void UTIL_Bubbles( const Vector& mins, const Vector& maxs, int count ); +void UTIL_BubbleTrail( const Vector& from, const Vector& to, int count ); + +// allows precacheing of other entities +void UTIL_PrecacheOther( const char *szClassname, const char *modelName = NULL ); + +// prints a message to each client +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 ); +} + +void UTIL_ValidateSoundName( string_t &name, const char *defaultStr ); + +void UTIL_ClientPrintFilter( IRecipientFilter& filter, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints messages through the HUD +void ClientPrint( CBasePlayer *player, 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) +void UTIL_SayText( const char *pText, CBasePlayer *pEntity ); +void UTIL_SayTextAll( const char *pText, CBasePlayer *pEntity = NULL, bool bChat = false ); +void UTIL_SayTextFilter( IRecipientFilter& filter, const char *pText, CBasePlayer *pEntity, bool bChat ); +void UTIL_SayText2Filter( IRecipientFilter& filter, CBasePlayer *pEntity, bool bChat, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +byte *UTIL_LoadFileForMe( const char *filename, int *pLength ); +void UTIL_FreeFile( byte *buffer ); + +class CGameTrace; +typedef CGameTrace trace_t; + +//----------------------------------------------------------------------------- +// These are inlined for backwards compatibility +//----------------------------------------------------------------------------- +inline float UTIL_Approach( float target, float value, float speed ) +{ + return Approach( target, value, speed ); +} + +inline float UTIL_ApproachAngle( float target, float value, float speed ) +{ + return ApproachAngle( target, value, speed ); +} + +inline float UTIL_AngleDistance( float next, float cur ) +{ + return AngleDistance( next, cur ); +} + +inline float UTIL_AngleMod(float a) +{ + return anglemod(a); +} + +inline float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + return AngleDiff( destAngle, srcAngle ); +} + +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; + + +//----------------------------------------------------------------------------- +// Sets the model to be associated with an entity +//----------------------------------------------------------------------------- +void UTIL_SetModel( CBaseEntity *pEntity, const char *pModelName ); + + +// prints as transparent 'title' to the HUD +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage ); + +// brings up hud keyboard hints display +void UTIL_HudHintText( CBaseEntity *pEntity, const char *pMessage ); + +// Writes message to console with timestamp and FragLog header. +void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonNPCs. +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +// Misc functions +int BuildChangeList( levellist_t *pLevelList, int maxList ); + +// computes gravity scale for an absolute gravity. Pass the result into CBaseEntity::SetGravity() +float UTIL_ScaleForGravity( float desiredGravity ); +// +// 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((bool)((f)!=0), #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction((bool)((f)!=0), #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// + +#include "globals.h" + +#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_START_ON 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 + + +#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 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 + +// Sound Utilities + +enum soundlevel_t; + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, soundlevel_t soundlevel, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, soundlevel_t soundlevel, int flags, int pitch); +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, soundlevel_t soundlevel, int flags, int pitch, int ipick, int freset); +void SENTENCEG_PlaySentenceIndex( edict_t *entity, int iSentenceIndex, float volume, soundlevel_t soundlevel, int flags, int pitch ); +int SENTENCEG_PickRndSz(const char *szrootname); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample); + +char TEXTURETYPE_Find( trace_t *ptr ); + +void UTIL_EmitSoundSuit(edict_t *entity, const char *sample); +int UTIL_EmitGroupIDSuit(edict_t *entity, int isentenceg); +int UTIL_EmitGroupnameSuit(edict_t *entity, const char *groupname); +void UTIL_RestartAmbientSounds( void ); + +class EntityMatrix : public VMatrix +{ +public: + void InitFromEntity( CBaseEntity *pEntity, int iAttachment=0 ); + void InitFromEntityLocal( CBaseEntity *entity ); + + inline Vector LocalToWorld( const Vector &vVec ) const + { + return VMul4x3( vVec ); + } + + inline Vector WorldToLocal( const Vector &vVec ) const + { + return VMul4x3Transpose( vVec ); + } + + inline Vector LocalToWorldRotation( const Vector &vVec ) const + { + return VMul3x3( vVec ); + } + + inline Vector WorldToLocalRotation( const Vector &vVec ) const + { + return VMul3x3Transpose( vVec ); + } +}; + +inline float UTIL_DistApprox( const Vector &vec1, const Vector &vec2 ); +inline float UTIL_DistApprox2D( const Vector &vec1, const Vector &vec2 ); + +//--------------------------------------------------------- +//--------------------------------------------------------- +inline float UTIL_DistApprox( const Vector &vec1, const Vector &vec2 ) +{ + float dx; + float dy; + float dz; + + dx = vec1.x - vec2.x; + dy = vec1.y - vec2.y; + dz = vec1.z - vec2.z; + + return fabs(dx) + fabs(dy) + fabs(dz); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +inline float UTIL_DistApprox2D( const Vector &vec1, const Vector &vec2 ) +{ + float dx; + float dy; + + dx = vec1.x - vec2.x; + dy = vec1.y - vec2.y; + + return fabs(dx) + fabs(dy); +} + +// Find out if an entity is facing another entity or position within a given tolerance range +bool UTIL_IsFacingWithinTolerance( CBaseEntity *pViewer, const Vector &vecPosition, float flDotTolerance, float *pflDot = NULL ); +bool UTIL_IsFacingWithinTolerance( CBaseEntity *pViewer, CBaseEntity *pTarget, float flDotTolerance, float *pflDot = NULL ); + +void UTIL_GetDebugColorForRelationship( int nRelationship, int &r, int &g, int &b ); + +struct datamap_t; +extern const char *UTIL_FunctionToName( datamap_t *pMap, void *function ); +extern void *UTIL_FunctionFromName( datamap_t *pMap, const char *pName ); + +int UTIL_GetCommandClientIndex( void ); +CBasePlayer *UTIL_GetCommandClient( void ); + +AngularImpulse WorldToLocalRotation( const VMatrix &localToWorld, const Vector &worldAxis, float rotation ); + +bool UTIL_LoadAndSpawnEntitiesFromScript( CUtlVector &entities, const char *pScriptFile, const char *pBlock, bool bActivate = true ); + +#endif // UTIL_H diff --git a/dlls/variant_t.h b/dlls/variant_t.h index 0add8063..024410a8 100644 --- a/dlls/variant_t.h +++ b/dlls/variant_t.h @@ -22,7 +22,7 @@ class CBaseEntity; // A variant class for passing data in entity input/output connections. // class variant_t -{ +{ union { bool bVal; @@ -33,13 +33,13 @@ class variant_t color32 rgbaVal; }; CHandle eVal; // this can't be in the union because it has a constructor. - + fieldtype_t fieldType; - + public: // constructor - variant_t() : fieldType(FIELD_VOID), iVal(0) {} + variant_t() : iVal(0), fieldType(FIELD_VOID) {} inline bool Bool( void ) const { return( fieldType == FIELD_BOOLEAN ) ? bVal : false; } inline const char *String( void ) const { return( fieldType == FIELD_STRING ) ? STRING(iszVal) : ToString(); } diff --git a/dlls/vehicle_base.cpp b/dlls/vehicle_base.cpp index bec6c0fb..9779e0d2 100644 --- a/dlls/vehicle_base.cpp +++ b/dlls/vehicle_base.cpp @@ -63,13 +63,16 @@ LINK_ENTITY_TO_CLASS( prop_vehicle, CPropVehicle ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- +#ifdef _MSC_VER #pragma warning (disable:4355) +#endif CPropVehicle::CPropVehicle() : m_VehiclePhysics( this ) { SetVehicleType( VEHICLE_TYPE_CAR_WHEELS ); } +#ifdef _MSC_VER #pragma warning (default:4355) - +#endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/dlls/vehicle_baseserver.cpp b/dlls/vehicle_baseserver.cpp index 3ef319d0..e6ff440b 100644 --- a/dlls/vehicle_baseserver.cpp +++ b/dlls/vehicle_baseserver.cpp @@ -834,7 +834,7 @@ void CBaseServerVehicle::ParseNPCRoles( KeyValues *pkvPassengerList ) Msg("Passenger Roles Parsed:\t%d\n\n", m_PassengerRoles.Count() ); for ( int i = 0; i < m_PassengerRoles.Count(); i++ ) { - Msg("\tPassenger Role:\t%s (%d seats)\n", m_PassengerRoles[i].m_strName, m_PassengerRoles[i].m_PassengerSeats.Count() ); + Msg("\tPassenger Role:\t%s (%d seats)\n", STRING(m_PassengerRoles[i].m_strName), m_PassengerRoles[i].m_PassengerSeats.Count() ); // Iterate through all information sets under this name for ( int j = 0; j < m_PassengerRoles[i].m_PassengerSeats.Count(); j++ ) @@ -847,7 +847,7 @@ void CBaseServerVehicle::ParseNPCRoles( KeyValues *pkvPassengerList ) for ( int nEntry = 0; nEntry < m_PassengerRoles[i].m_PassengerSeats[j].m_EntryTransitions.Count(); nEntry++ ) { - Msg("\t\t\tAnimation:\t%s\t(Priority %d)\n", m_PassengerRoles[i].m_PassengerSeats[j].m_EntryTransitions[nEntry].m_strAnimationName, + Msg("\t\t\tAnimation:\t%s\t(Priority %d)\n", STRING(m_PassengerRoles[i].m_PassengerSeats[j].m_EntryTransitions[nEntry].m_strAnimationName), m_PassengerRoles[i].m_PassengerSeats[j].m_EntryTransitions[nEntry].m_nPriority ); } @@ -859,7 +859,7 @@ void CBaseServerVehicle::ParseNPCRoles( KeyValues *pkvPassengerList ) for ( int nExits = 0; nExits < m_PassengerRoles[i].m_PassengerSeats[j].m_ExitTransitions.Count(); nExits++ ) { - Msg("\t\t\tAnimation:\t%s\t(Priority %d)\n", m_PassengerRoles[i].m_PassengerSeats[j].m_ExitTransitions[nExits].m_strAnimationName, + Msg("\t\t\tAnimation:\t%s\t(Priority %d)\n", STRING(m_PassengerRoles[i].m_PassengerSeats[j].m_ExitTransitions[nExits].m_strAnimationName), m_PassengerRoles[i].m_PassengerSeats[j].m_ExitTransitions[nExits].m_nPriority ); } } @@ -1609,7 +1609,7 @@ static int SoundStateIndexFromName( const char *pName ) { for ( int i = 0; i < SS_NUM_STATES; i++ ) { - Assert( i < ARRAYSIZE(pSoundStateNames) ); + Assert( i < static_cast(ARRAYSIZE(pSoundStateNames)) ); if ( !strcmpi( pSoundStateNames[i], pName ) ) return i; } @@ -1797,6 +1797,8 @@ sound_states CBaseServerVehicle::SoundState_ChooseState( vbs_sound_update_t &par case SS_SHUTDOWN: case SS_SHUTDOWN_WATER: return m_soundState; + default: + break; } return SS_SHUTDOWN; } @@ -1851,6 +1853,8 @@ sound_states CBaseServerVehicle::SoundState_ChooseState( vbs_sound_update_t &par if ( CheckCrash(params) ) return SS_IDLE; break; + default: + break; } switch( m_soundState ) @@ -2080,6 +2084,8 @@ void CBaseServerVehicle::SoundStartDisabled() case SS_START_WATER: PlaySound( StateSoundName(newState) ); break; + default: + break; } } diff --git a/dlls/vehicle_choreo_generic.cpp b/dlls/vehicle_choreo_generic.cpp index 47bda041..d39d3566 100644 --- a/dlls/vehicle_choreo_generic.cpp +++ b/dlls/vehicle_choreo_generic.cpp @@ -1,918 +1,917 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" -#include "npcevent.h" -#include "vehicle_base.h" -#include "engine/IEngineSound.h" -#include "in_buttons.h" -#include "soundenvelope.h" -#include "soundent.h" -#include "physics_saverestore.h" -#include "vphysics/constraints.h" -#include "vcollide_parse.h" -#include "ndebugoverlay.h" -#include "hl2_player.h" -#include "props.h" -#include "vehicle_choreo_generic_shared.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define VEHICLE_HITBOX_DRIVER 1 - -#define CHOREO_VEHICLE_VIEW_FOV 90 -#define CHOREO_VEHICLE_VIEW_YAW_MIN -60 -#define CHOREO_VEHICLE_VIEW_YAW_MAX 60 -#define CHOREO_VEHICLE_VIEW_PITCH_MIN -90 -#define CHOREO_VEHICLE_VIEW_PITCH_MAX 38 - -BEGIN_DATADESC_NO_BASE( vehicleview_t ) - DEFINE_FIELD( bClampEyeAngles, FIELD_BOOLEAN ), - DEFINE_FIELD( flPitchCurveZero, FIELD_FLOAT ), - DEFINE_FIELD( flPitchCurveLinear, FIELD_FLOAT ), - DEFINE_FIELD( flRollCurveZero, FIELD_FLOAT ), - DEFINE_FIELD( flRollCurveLinear, FIELD_FLOAT ), - DEFINE_FIELD( flFOV, FIELD_FLOAT ), - DEFINE_FIELD( flYawMin, FIELD_FLOAT ), - DEFINE_FIELD( flYawMax, FIELD_FLOAT ), - DEFINE_FIELD( flPitchMin, FIELD_FLOAT ), - DEFINE_FIELD( flPitchMax, FIELD_FLOAT ), -END_DATADESC() - -// -// Anim events. -// -enum -{ - AE_CHOREO_VEHICLE_OPEN = 1, - AE_CHOREO_VEHICLE_CLOSE = 2, -}; - - -extern ConVar g_debug_vehicledriver; - - -class CPropVehicleChoreoGeneric; - -static const char *pChoreoGenericFollowerBoneNames[] = -{ - "base", -}; - - -//----------------------------------------------------------------------------- -// Purpose: A KeyValues parse for vehicle sound blocks -//----------------------------------------------------------------------------- -class CVehicleChoreoViewParser : public IVPhysicsKeyHandler -{ -public: - CVehicleChoreoViewParser( void ); - - void ParseVehicleSounds( const char *pScriptName, vehiclesounds_t *pSounds ); - -private: - virtual void ParseKeyValue( void *pData, const char *pKey, const char *pValue ); - virtual void SetDefaults( void *pData ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -class CChoreoGenericServerVehicle : public CBaseServerVehicle -{ - typedef CBaseServerVehicle BaseClass; - -// IServerVehicle -public: - void GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles ); - virtual void ItemPostFrame( CBasePlayer *pPlayer ); - -protected: - - CPropVehicleChoreoGeneric *GetVehicle( void ); -}; - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -class CPropVehicleChoreoGeneric : public CPhysicsProp, public IDrivableVehicle -{ - DECLARE_CLASS( CPropVehicleChoreoGeneric, CPhysicsProp ); - -public: - DECLARE_DATADESC(); - DECLARE_SERVERCLASS(); - - CPropVehicleChoreoGeneric( void ) - { - m_ServerVehicle.SetVehicle( this ); - m_bIgnoreMoveParent = false; - } - - ~CPropVehicleChoreoGeneric( void ) - { - } - - // CBaseEntity - virtual void Precache( void ); - void Spawn( void ); - void Think(void); - virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_IMPULSE_USE; }; - virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - virtual void DrawDebugGeometryOverlays( void ); - - virtual Vector BodyTarget( const Vector &posSrc, bool bNoisy = true ); - virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ); - virtual int OnTakeDamage( const CTakeDamageInfo &info ); - - void PlayerControlInit( CBasePlayer *pPlayer ); - void PlayerControlShutdown( void ); - void ResetUseKey( CBasePlayer *pPlayer ); - - virtual bool OverridePropdata() { return true; } - - bool ParseViewParams( const char *pScriptName ); - - void GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const; - - bool CreateVPhysics() - { - SetSolid(SOLID_VPHYSICS); - SetMoveType(MOVETYPE_NONE); - return true; - } - bool ShouldForceExit() { return m_bForcedExit; } - void ClearForcedExit() { m_bForcedExit = false; } - - // CBaseAnimating - void HandleAnimEvent( animevent_t *pEvent ); - - // Inputs - void InputEnterVehicleImmediate( inputdata_t &inputdata ); - void InputEnterVehicle( inputdata_t &inputdata ); - void InputExitVehicle( inputdata_t &inputdata ); - void InputLock( inputdata_t &inputdata ); - void InputUnlock( inputdata_t &inputdata ); - void InputOpen( inputdata_t &inputdata ); - void InputClose( inputdata_t &inputdata ); - - bool ShouldIgnoreParent( void ) { return m_bIgnoreMoveParent; } - - CNetworkHandle( CBasePlayer, m_hPlayer ); - - CNetworkVarEmbedded( vehicleview_t, m_vehicleView ); - -// IDrivableVehicle -public: - - virtual CBaseEntity *GetDriver( void ); - virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData ) { return; } - virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ) { return; } - virtual bool CanEnterVehicle( CBaseEntity *pEntity ); - virtual bool CanExitVehicle( CBaseEntity *pEntity ); - virtual void SetVehicleEntryAnim( bool bOn ); - virtual void SetVehicleExitAnim( bool bOn, Vector vecEyeExitEndpoint ) { m_bExitAnimOn = bOn; if ( bOn ) m_vecEyeExitEndpoint = vecEyeExitEndpoint; } - virtual void EnterVehicle( CBaseCombatCharacter *pPassenger ); - - virtual bool AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole ) { return true; } - virtual bool AllowMidairExit( CBaseCombatCharacter *pPassenger, int nRole ) { return true; } - virtual void PreExitVehicle( CBaseCombatCharacter *pPassenger, int nRole ) {} - virtual void ExitVehicle( int nRole ); - - virtual void ItemPostFrame( CBasePlayer *pPlayer ) {} - virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) {} - - // If this is a vehicle, returns the vehicle interface - virtual IServerVehicle *GetServerVehicle() { return &m_ServerVehicle; } - - bool ShouldCollide( int collisionGroup, int contentsMask ) const; - -protected: - - // Contained IServerVehicle - CChoreoGenericServerVehicle m_ServerVehicle; - -private: - - // Entering / Exiting - bool m_bLocked; - CNetworkVar( bool, m_bEnterAnimOn ); - CNetworkVar( bool, m_bExitAnimOn ); - CNetworkVector( m_vecEyeExitEndpoint ); - bool m_bForcedExit; - bool m_bIgnoreMoveParent; - bool m_bIgnorePlayerCollisions; - - // Vehicle script filename - string_t m_vehicleScript; - - COutputEvent m_playerOn; - COutputEvent m_playerOff; - COutputEvent m_OnOpen; - COutputEvent m_OnClose; -}; - -LINK_ENTITY_TO_CLASS( prop_vehicle_choreo_generic, CPropVehicleChoreoGeneric ); - -BEGIN_DATADESC( CPropVehicleChoreoGeneric ) - - // Inputs - DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ), - DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), - DEFINE_INPUTFUNC( FIELD_VOID, "EnterVehicle", InputEnterVehicle ), - DEFINE_INPUTFUNC( FIELD_VOID, "EnterVehicleImmediate", InputEnterVehicleImmediate ), - DEFINE_INPUTFUNC( FIELD_VOID, "ExitVehicle", InputExitVehicle ), - DEFINE_INPUTFUNC( FIELD_VOID, "Open", InputOpen ), - DEFINE_INPUTFUNC( FIELD_VOID, "Close", InputClose ), - - // Keys - DEFINE_EMBEDDED( m_ServerVehicle ), - - DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ), - DEFINE_FIELD( m_bEnterAnimOn, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bExitAnimOn, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bForcedExit, FIELD_BOOLEAN ), - DEFINE_FIELD( m_vecEyeExitEndpoint, FIELD_POSITION_VECTOR ), - - DEFINE_KEYFIELD( m_vehicleScript, FIELD_STRING, "vehiclescript" ), - DEFINE_KEYFIELD( m_bLocked, FIELD_BOOLEAN, "vehiclelocked" ), - - DEFINE_KEYFIELD( m_bIgnoreMoveParent, FIELD_BOOLEAN, "ignoremoveparent" ), - DEFINE_KEYFIELD( m_bIgnorePlayerCollisions, FIELD_BOOLEAN, "ignoreplayer" ), - - DEFINE_OUTPUT( m_playerOn, "PlayerOn" ), - DEFINE_OUTPUT( m_playerOff, "PlayerOff" ), - DEFINE_OUTPUT( m_OnOpen, "OnOpen" ), - DEFINE_OUTPUT( m_OnClose, "OnClose" ), - - DEFINE_EMBEDDED( m_vehicleView ), - -END_DATADESC() - -IMPLEMENT_SERVERCLASS_ST(CPropVehicleChoreoGeneric, DT_PropVehicleChoreoGeneric) - SendPropEHandle(SENDINFO(m_hPlayer)), - SendPropBool(SENDINFO(m_bEnterAnimOn)), - SendPropBool(SENDINFO(m_bExitAnimOn)), - SendPropVector(SENDINFO(m_vecEyeExitEndpoint), -1, SPROP_COORD), - SendPropBool( SENDINFO_STRUCTELEM( m_vehicleView.bClampEyeAngles ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchCurveZero ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchCurveLinear ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flRollCurveZero ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flRollCurveLinear ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flFOV ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flYawMin ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flYawMax ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchMin ) ), - SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchMax ) ), -END_SEND_TABLE(); - - -bool ShouldVehicleIgnoreEntity( CBaseEntity *pVehicle, CBaseEntity *pCollide ) -{ - CPropVehicleChoreoGeneric *pChoreoVehicle = dynamic_cast ( pVehicle ); - - if ( pChoreoVehicle == NULL ) - return false; - - if ( pCollide == NULL ) - return false; - - if ( pChoreoVehicle->ShouldIgnoreParent() == false ) - return false; - - if ( pChoreoVehicle->GetMoveParent() == pCollide ) - return true; - - return false; -} - - -//------------------------------------------------ -// Precache -//------------------------------------------------ -void CPropVehicleChoreoGeneric::Precache( void ) -{ - BaseClass::Precache(); - - m_ServerVehicle.Initialize( STRING(m_vehicleScript) ); -} - - -//------------------------------------------------ -// Spawn -//------------------------------------------------ -void CPropVehicleChoreoGeneric::Spawn( void ) -{ - Precache(); - SetModel( STRING( GetModelName() ) ); - SetCollisionGroup( COLLISION_GROUP_VEHICLE ); - - if ( GetSolid() != SOLID_NONE ) - { - BaseClass::Spawn(); - } - - m_takedamage = DAMAGE_EVENTS_ONLY; - - SetNextThink( gpGlobals->curtime ); - - ParseViewParams( STRING(m_vehicleScript) ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) -{ - if ( ptr->hitbox == VEHICLE_HITBOX_DRIVER ) - { - if ( m_hPlayer != NULL ) - { - m_hPlayer->TakeDamage( info ); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CPropVehicleChoreoGeneric::OnTakeDamage( const CTakeDamageInfo &inputInfo ) -{ - CTakeDamageInfo info = inputInfo; - info.ScaleDamage( 25 ); - - // reset the damage - info.SetDamage( inputInfo.GetDamage() ); - - // Check to do damage to prisoner - if ( m_hPlayer != NULL ) - { - // Take no damage from physics damages - if ( info.GetDamageType() & DMG_CRUSH ) - return 0; - - // Take the damage - m_hPlayer->TakeDamage( info ); - } - - return 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -Vector CPropVehicleChoreoGeneric::BodyTarget( const Vector &posSrc, bool bNoisy ) -{ - Vector shotPos; - - int eyeAttachmentIndex = LookupAttachment("vehicle_driver_eyes"); - GetAttachment( eyeAttachmentIndex, shotPos ); - - if ( bNoisy ) - { - shotPos[0] += random->RandomFloat( -8.0f, 8.0f ); - shotPos[1] += random->RandomFloat( -8.0f, 8.0f ); - shotPos[2] += random->RandomFloat( -8.0f, 8.0f ); - } - - return shotPos; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::Think(void) -{ - SetNextThink( gpGlobals->curtime + 0.1 ); - - if ( GetDriver() ) - { - BaseClass::Think(); - - // If the enter or exit animation has finished, tell the server vehicle - if ( IsSequenceFinished() && (m_bExitAnimOn || m_bEnterAnimOn) ) - { - GetServerVehicle()->HandleEntryExitFinish( m_bExitAnimOn, true ); - } - } - - StudioFrameAdvance(); - DispatchAnimEvents( this ); -} - - -//------------------------------------------------------------------------------ -// Purpose: -//------------------------------------------------------------------------------ -void CPropVehicleChoreoGeneric::InputOpen( inputdata_t &inputdata ) -{ - int nSequence = LookupSequence( "open" ); - - // Set to the desired anim, or default anim if the desired is not present - if ( nSequence > ACTIVITY_NOT_AVAILABLE ) - { - SetCycle( 0 ); - m_flAnimTime = gpGlobals->curtime; - ResetSequence( nSequence ); - ResetClientsideFrame(); - } - else - { - // Not available try to get default anim - Msg( "Choreo Generic Vehicle %s: missing open sequence\n", GetDebugName() ); - SetSequence( 0 ); - } -} - - -//------------------------------------------------------------------------------ -// Purpose: -//------------------------------------------------------------------------------ -void CPropVehicleChoreoGeneric::InputClose( inputdata_t &inputdata ) -{ - if ( m_bLocked || m_bEnterAnimOn ) - return; - - int nSequence = LookupSequence( "close" ); - - // Set to the desired anim, or default anim if the desired is not present - if ( nSequence > ACTIVITY_NOT_AVAILABLE ) - { - SetCycle( 0 ); - m_flAnimTime = gpGlobals->curtime; - ResetSequence( nSequence ); - ResetClientsideFrame(); - } - else - { - // Not available try to get default anim - Msg( "Choreo Generic Vehicle %s: missing close sequence\n", GetDebugName() ); - SetSequence( 0 ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::HandleAnimEvent( animevent_t *pEvent ) -{ - if ( pEvent->event == AE_CHOREO_VEHICLE_OPEN ) - { - m_OnOpen.FireOutput( this, this ); - m_bLocked = false; - } - else if ( pEvent->event == AE_CHOREO_VEHICLE_CLOSE ) - { - m_OnClose.FireOutput( this, this ); - m_bLocked = true; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - CBasePlayer *pPlayer = ToBasePlayer( pActivator ); - if ( !pPlayer ) - return; - - ResetUseKey( pPlayer ); - - GetServerVehicle()->HandlePassengerEntry( pPlayer, (value > 0) ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Return true of the player's allowed to enter / exit the vehicle -//----------------------------------------------------------------------------- -bool CPropVehicleChoreoGeneric::CanEnterVehicle( CBaseEntity *pEntity ) -{ - // Prevent entering if the vehicle's being driven by an NPC - if ( GetDriver() && GetDriver() != pEntity ) - return false; - - // Prevent entering if the vehicle's locked - return !m_bLocked; -} - - -//----------------------------------------------------------------------------- -// Purpose: Return true of the player is allowed to exit the vehicle. -//----------------------------------------------------------------------------- -bool CPropVehicleChoreoGeneric::CanExitVehicle( CBaseEntity *pEntity ) -{ - // Prevent exiting if the vehicle's locked, rotating, or playing an entry/exit anim. - return ( !m_bLocked && (GetLocalAngularVelocity() == vec3_angle) && !m_bEnterAnimOn && !m_bExitAnimOn ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Override base class to add display -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::DrawDebugGeometryOverlays(void) -{ - // Draw if BBOX is on - if ( m_debugOverlays & OVERLAY_BBOX_BIT ) - { - } - - BaseClass::DrawDebugGeometryOverlays(); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::EnterVehicle( CBaseCombatCharacter *pPassenger ) -{ - if ( pPassenger == NULL ) - return; - - CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); - if ( pPlayer != NULL ) - { - // Remove any player who may be in the vehicle at the moment - if ( m_hPlayer ) - { - ExitVehicle( VEHICLE_ROLE_DRIVER ); - } - - m_hPlayer = pPlayer; - m_playerOn.FireOutput( pPlayer, this, 0 ); - - m_ServerVehicle.SoundStart(); - } - else - { - // NPCs not supported yet - jdw - Assert( 0 ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::SetVehicleEntryAnim( bool bOn ) -{ - m_bEnterAnimOn = bOn; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::ExitVehicle( int nRole ) -{ - CBasePlayer *pPlayer = m_hPlayer; - if ( !pPlayer ) - return; - - m_hPlayer = NULL; - ResetUseKey( pPlayer ); - - m_playerOff.FireOutput( pPlayer, this, 0 ); - m_bEnterAnimOn = false; - - m_ServerVehicle.SoundShutdown( 1.0 ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::ResetUseKey( CBasePlayer *pPlayer ) -{ - pPlayer->m_afButtonPressed &= ~IN_USE; -} - - -//----------------------------------------------------------------------------- -// Purpose: Vehicles are permanently oriented off angle for vphysics. -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const -{ - // This call is necessary to cause m_rgflCoordinateFrame to be recomputed - const matrix3x4_t &entityToWorld = EntityToWorldTransform(); - - if (pForward != NULL) - { - MatrixGetColumn( entityToWorld, 1, *pForward ); - } - - if (pRight != NULL) - { - MatrixGetColumn( entityToWorld, 0, *pRight ); - } - - if (pUp != NULL) - { - MatrixGetColumn( entityToWorld, 2, *pUp ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CBaseEntity *CPropVehicleChoreoGeneric::GetDriver( void ) -{ - return m_hPlayer; -} - -//----------------------------------------------------------------------------- -// Purpose: Prevent the player from entering / exiting the vehicle -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::InputLock( inputdata_t &inputdata ) -{ - m_bLocked = true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Allow the player to enter / exit the vehicle -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::InputUnlock( inputdata_t &inputdata ) -{ - m_bLocked = false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Force the player to enter the vehicle. -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::InputEnterVehicle( inputdata_t &inputdata ) -{ - if ( m_bEnterAnimOn ) - return; - - // Try the activator first & use them if they are a player. - CBaseCombatCharacter *pPassenger = ToBaseCombatCharacter( inputdata.pActivator ); - if ( pPassenger == NULL ) - { - // Activator was not a player, just grab the singleplayer player. - pPassenger = UTIL_PlayerByIndex( 1 ); - if ( pPassenger == NULL ) - return; - } - - // FIXME: I hate code like this. I should really add a parameter to HandlePassengerEntry - // to allow entry into locked vehicles - bool bWasLocked = m_bLocked; - m_bLocked = false; - GetServerVehicle()->HandlePassengerEntry( pPassenger, true ); - m_bLocked = bWasLocked; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &inputdata - -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::InputEnterVehicleImmediate( inputdata_t &inputdata ) -{ - if ( m_bEnterAnimOn ) - return; - - // Try the activator first & use them if they are a player. - CBaseCombatCharacter *pPassenger = ToBaseCombatCharacter( inputdata.pActivator ); - if ( pPassenger == NULL ) - { - // Activator was not a player, just grab the singleplayer player. - pPassenger = UTIL_PlayerByIndex( 1 ); - if ( pPassenger == NULL ) - return; - } - - CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); - if ( pPlayer != NULL ) - { - if ( pPlayer->IsInAVehicle() ) - { - // Force the player out of whatever vehicle they are in. - pPlayer->LeaveVehicle(); - } - - pPlayer->GetInVehicle( GetServerVehicle(), VEHICLE_ROLE_DRIVER ); - } - else - { - // NPCs not supported yet - jdw - Assert( 0 ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Force the player to exit the vehicle. -//----------------------------------------------------------------------------- -void CPropVehicleChoreoGeneric::InputExitVehicle( inputdata_t &inputdata ) -{ - m_bForcedExit = true; -} - -//----------------------------------------------------------------------------- -// Purpose: Parses the vehicle's script for the vehicle view parameters -//----------------------------------------------------------------------------- -bool CPropVehicleChoreoGeneric::ParseViewParams( const char *pScriptName ) -{ - byte *pFile = UTIL_LoadFileForMe( pScriptName, NULL ); - if ( !pFile ) - return false; - - IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( (char *)pFile ); - CVehicleChoreoViewParser viewParser; - while ( !pParse->Finished() ) - { - const char *pBlock = pParse->GetCurrentBlockName(); - if ( !strcmpi( pBlock, "vehicle_view" ) ) - { - pParse->ParseCustom( &m_vehicleView, &viewParser ); - } - else - { - pParse->SkipBlock(); - } - } - physcollision->VPhysicsKeyParserDestroy( pParse ); - UTIL_FreeFile( pFile ); - - Precache(); - - return true; -} - -//======================================================================================================================================== -// CRANE VEHICLE SERVER VEHICLE -//======================================================================================================================================== -CPropVehicleChoreoGeneric *CChoreoGenericServerVehicle::GetVehicle( void ) -{ - return (CPropVehicleChoreoGeneric *)GetDrivableVehicle(); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pPlayer - -//----------------------------------------------------------------------------- -void CChoreoGenericServerVehicle::ItemPostFrame( CBasePlayer *player ) -{ - Assert( player == GetDriver() ); - - GetDrivableVehicle()->ItemPostFrame( player ); - - if (( player->m_afButtonPressed & IN_USE ) || GetVehicle()->ShouldForceExit() ) - { - GetVehicle()->ClearForcedExit(); - if ( GetDrivableVehicle()->CanExitVehicle(player) ) - { - // Let the vehicle try to play the exit animation - if ( !HandlePassengerExit( player ) && ( player != NULL ) ) - { - player->PlayUseDenySound(); - } - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CChoreoGenericServerVehicle::GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles ) -{ - Assert( nRole == VEHICLE_ROLE_DRIVER ); - CBasePlayer *pPlayer = ToBasePlayer( GetDrivableVehicle()->GetDriver() ); - Assert( pPlayer ); - - *pAbsAngles = pPlayer->EyeAngles(); // yuck. this is an in/out parameter. - - float flPitchFactor = 1.0; - matrix3x4_t vehicleEyePosToWorld; - Vector vehicleEyeOrigin; - QAngle vehicleEyeAngles; - GetVehicle()->GetAttachment( "vehicle_driver_eyes", vehicleEyeOrigin, vehicleEyeAngles ); - AngleMatrix( vehicleEyeAngles, vehicleEyePosToWorld ); - - // Compute the relative rotation between the unperterbed eye attachment + the eye angles - matrix3x4_t cameraToWorld; - AngleMatrix( *pAbsAngles, cameraToWorld ); - - matrix3x4_t worldToEyePos; - MatrixInvert( vehicleEyePosToWorld, worldToEyePos ); - - matrix3x4_t vehicleCameraToEyePos; - ConcatTransforms( worldToEyePos, cameraToWorld, vehicleCameraToEyePos ); - - // Now perterb the attachment point - vehicleEyeAngles.x = RemapAngleRange( PITCH_CURVE_ZERO * flPitchFactor, PITCH_CURVE_LINEAR, vehicleEyeAngles.x ); - vehicleEyeAngles.z = RemapAngleRange( ROLL_CURVE_ZERO * flPitchFactor, ROLL_CURVE_LINEAR, vehicleEyeAngles.z ); - AngleMatrix( vehicleEyeAngles, vehicleEyeOrigin, vehicleEyePosToWorld ); - - // Now treat the relative eye angles as being relative to this new, perterbed view position... - matrix3x4_t newCameraToWorld; - ConcatTransforms( vehicleEyePosToWorld, vehicleCameraToEyePos, newCameraToWorld ); - - // output new view abs angles - MatrixAngles( newCameraToWorld, *pAbsAngles ); - - // UNDONE: *pOrigin would already be correct in single player if the HandleView() on the server ran after vphysics - MatrixGetColumn( newCameraToWorld, 3, *pAbsOrigin ); -} - -bool CPropVehicleChoreoGeneric::ShouldCollide( int collisionGroup, int contentsMask ) const -{ - if ( m_bIgnorePlayerCollisions == true ) - { - if ( collisionGroup == COLLISION_GROUP_PLAYER || collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) - return false; - } - - return BaseClass::ShouldCollide( collisionGroup, contentsMask ); -} - - -CVehicleChoreoViewParser::CVehicleChoreoViewParser( void ) -{ - -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CVehicleChoreoViewParser::ParseKeyValue( void *pData, const char *pKey, const char *pValue ) -{ - vehicleview_t *pView = (vehicleview_t *)pData; - // New gear? - if ( !strcmpi( pKey, "clamp" ) ) - { - pView->bClampEyeAngles = !!atoi( pValue ); - } - else if ( !strcmpi( pKey, "pitchcurvezero" ) ) - { - pView->flPitchCurveZero = atof( pValue ); - } - else if ( !strcmpi( pKey, "pitchcurvelinear" ) ) - { - pView->flPitchCurveLinear = atof( pValue ); - } - else if ( !strcmpi( pKey, "rollcurvezero" ) ) - { - pView->flRollCurveZero = atof( pValue ); - } - else if ( !strcmpi( pKey, "rollcurvelinear" ) ) - { - pView->flRollCurveLinear = atof( pValue ); - } - else if ( !strcmpi( pKey, "yawmin" ) ) - { - pView->flYawMin = atof( pValue ); - } - else if ( !strcmpi( pKey, "yawmax" ) ) - { - pView->flYawMax = atof( pValue ); - } - else if ( !strcmpi( pKey, "pitchmin" ) ) - { - pView->flPitchMin = atof( pValue ); - } - else if ( !strcmpi( pKey, "pitchmax" ) ) - { - pView->flPitchMax = atof( pValue ); - } - else if ( !strcmpi( pKey, "fov" ) ) - { - pView->flFOV = atof( pValue ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CVehicleChoreoViewParser::SetDefaults( void *pData ) -{ - vehicleview_t *pView = (vehicleview_t *)pData; - - pView->bClampEyeAngles = true; - - pView->flPitchCurveZero = PITCH_CURVE_ZERO; - pView->flPitchCurveLinear = PITCH_CURVE_LINEAR; - pView->flRollCurveZero = ROLL_CURVE_ZERO; - pView->flRollCurveLinear = ROLL_CURVE_LINEAR; - pView->flFOV = CHOREO_VEHICLE_VIEW_FOV; - pView->flYawMin = CHOREO_VEHICLE_VIEW_YAW_MIN; - pView->flYawMax = CHOREO_VEHICLE_VIEW_YAW_MAX; - pView->flPitchMin = CHOREO_VEHICLE_VIEW_PITCH_MIN; - pView->flPitchMax = CHOREO_VEHICLE_VIEW_PITCH_MAX; - -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "vehicle_base.h" +#include "engine/IEngineSound.h" +#include "in_buttons.h" +#include "soundenvelope.h" +#include "soundent.h" +#include "physics_saverestore.h" +#include "vphysics/constraints.h" +#include "vcollide_parse.h" +#include "ndebugoverlay.h" +#include "hl2_player.h" +#include "props.h" +#include "vehicle_choreo_generic_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define VEHICLE_HITBOX_DRIVER 1 + +#define CHOREO_VEHICLE_VIEW_FOV 90 +#define CHOREO_VEHICLE_VIEW_YAW_MIN -60 +#define CHOREO_VEHICLE_VIEW_YAW_MAX 60 +#define CHOREO_VEHICLE_VIEW_PITCH_MIN -90 +#define CHOREO_VEHICLE_VIEW_PITCH_MAX 38 + +BEGIN_DATADESC_NO_BASE( vehicleview_t ) + DEFINE_FIELD( bClampEyeAngles, FIELD_BOOLEAN ), + DEFINE_FIELD( flPitchCurveZero, FIELD_FLOAT ), + DEFINE_FIELD( flPitchCurveLinear, FIELD_FLOAT ), + DEFINE_FIELD( flRollCurveZero, FIELD_FLOAT ), + DEFINE_FIELD( flRollCurveLinear, FIELD_FLOAT ), + DEFINE_FIELD( flFOV, FIELD_FLOAT ), + DEFINE_FIELD( flYawMin, FIELD_FLOAT ), + DEFINE_FIELD( flYawMax, FIELD_FLOAT ), + DEFINE_FIELD( flPitchMin, FIELD_FLOAT ), + DEFINE_FIELD( flPitchMax, FIELD_FLOAT ), +END_DATADESC() + +// +// Anim events. +// +enum +{ + AE_CHOREO_VEHICLE_OPEN = 1, + AE_CHOREO_VEHICLE_CLOSE = 2, +}; + + +extern ConVar g_debug_vehicledriver; + + +class CPropVehicleChoreoGeneric; + +/*static const char *pChoreoGenericFollowerBoneNames[] = +{ + "base", +};*/ + + +//----------------------------------------------------------------------------- +// Purpose: A KeyValues parse for vehicle sound blocks +//----------------------------------------------------------------------------- +class CVehicleChoreoViewParser : public IVPhysicsKeyHandler +{ +public: + CVehicleChoreoViewParser( void ); + + void ParseVehicleSounds( const char *pScriptName, vehiclesounds_t *pSounds ); + +private: + virtual void ParseKeyValue( void *pData, const char *pKey, const char *pValue ); + virtual void SetDefaults( void *pData ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CChoreoGenericServerVehicle : public CBaseServerVehicle +{ + typedef CBaseServerVehicle BaseClass; + +// IServerVehicle +public: + void GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles ); + virtual void ItemPostFrame( CBasePlayer *pPlayer ); + +protected: + + CPropVehicleChoreoGeneric *GetVehicle( void ); +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CPropVehicleChoreoGeneric : public CPhysicsProp, public IDrivableVehicle +{ + DECLARE_CLASS( CPropVehicleChoreoGeneric, CPhysicsProp ); + +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CPropVehicleChoreoGeneric( void ) + { + m_ServerVehicle.SetVehicle( this ); + m_bIgnoreMoveParent = false; + } + + ~CPropVehicleChoreoGeneric( void ) + { + } + + // CBaseEntity + virtual void Precache( void ); + void Spawn( void ); + void Think(void); + virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_IMPULSE_USE; }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void DrawDebugGeometryOverlays( void ); + + virtual Vector BodyTarget( const Vector &posSrc, bool bNoisy = true ); + virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + + void PlayerControlInit( CBasePlayer *pPlayer ); + void PlayerControlShutdown( void ); + void ResetUseKey( CBasePlayer *pPlayer ); + + virtual bool OverridePropdata() { return true; } + + bool ParseViewParams( const char *pScriptName ); + + void GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const; + + bool CreateVPhysics() + { + SetSolid(SOLID_VPHYSICS); + SetMoveType(MOVETYPE_NONE); + return true; + } + bool ShouldForceExit() { return m_bForcedExit; } + void ClearForcedExit() { m_bForcedExit = false; } + + // CBaseAnimating + void HandleAnimEvent( animevent_t *pEvent ); + + // Inputs + void InputEnterVehicleImmediate( inputdata_t &inputdata ); + void InputEnterVehicle( inputdata_t &inputdata ); + void InputExitVehicle( inputdata_t &inputdata ); + void InputLock( inputdata_t &inputdata ); + void InputUnlock( inputdata_t &inputdata ); + void InputOpen( inputdata_t &inputdata ); + void InputClose( inputdata_t &inputdata ); + + bool ShouldIgnoreParent( void ) { return m_bIgnoreMoveParent; } + + CNetworkHandle( CBasePlayer, m_hPlayer ); + + CNetworkVarEmbedded( vehicleview_t, m_vehicleView ); + +// IDrivableVehicle +public: + + virtual CBaseEntity *GetDriver( void ); + virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData ) { return; } + virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ) { return; } + virtual bool CanEnterVehicle( CBaseEntity *pEntity ); + virtual bool CanExitVehicle( CBaseEntity *pEntity ); + virtual void SetVehicleEntryAnim( bool bOn ); + virtual void SetVehicleExitAnim( bool bOn, Vector vecEyeExitEndpoint ) { m_bExitAnimOn = bOn; if ( bOn ) m_vecEyeExitEndpoint = vecEyeExitEndpoint; } + virtual void EnterVehicle( CBaseCombatCharacter *pPassenger ); + + virtual bool AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole ) { return true; } + virtual bool AllowMidairExit( CBaseCombatCharacter *pPassenger, int nRole ) { return true; } + virtual void PreExitVehicle( CBaseCombatCharacter *pPassenger, int nRole ) {} + virtual void ExitVehicle( int nRole ); + + virtual void ItemPostFrame( CBasePlayer *pPlayer ) {} + virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) {} + + // If this is a vehicle, returns the vehicle interface + virtual IServerVehicle *GetServerVehicle() { return &m_ServerVehicle; } + + bool ShouldCollide( int collisionGroup, int contentsMask ) const; + +protected: + + // Contained IServerVehicle + CChoreoGenericServerVehicle m_ServerVehicle; + +private: + + // Entering / Exiting + bool m_bLocked; + CNetworkVar( bool, m_bEnterAnimOn ); + CNetworkVar( bool, m_bExitAnimOn ); + CNetworkVector( m_vecEyeExitEndpoint ); + bool m_bForcedExit; + bool m_bIgnoreMoveParent; + bool m_bIgnorePlayerCollisions; + + // Vehicle script filename + string_t m_vehicleScript; + + COutputEvent m_playerOn; + COutputEvent m_playerOff; + COutputEvent m_OnOpen; + COutputEvent m_OnClose; +}; + +LINK_ENTITY_TO_CLASS( prop_vehicle_choreo_generic, CPropVehicleChoreoGeneric ); + +BEGIN_DATADESC( CPropVehicleChoreoGeneric ) + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ), + DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnterVehicle", InputEnterVehicle ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnterVehicleImmediate", InputEnterVehicleImmediate ), + DEFINE_INPUTFUNC( FIELD_VOID, "ExitVehicle", InputExitVehicle ), + DEFINE_INPUTFUNC( FIELD_VOID, "Open", InputOpen ), + DEFINE_INPUTFUNC( FIELD_VOID, "Close", InputClose ), + + // Keys + DEFINE_EMBEDDED( m_ServerVehicle ), + + DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ), + DEFINE_FIELD( m_bEnterAnimOn, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bExitAnimOn, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bForcedExit, FIELD_BOOLEAN ), + DEFINE_FIELD( m_vecEyeExitEndpoint, FIELD_POSITION_VECTOR ), + + DEFINE_KEYFIELD( m_vehicleScript, FIELD_STRING, "vehiclescript" ), + DEFINE_KEYFIELD( m_bLocked, FIELD_BOOLEAN, "vehiclelocked" ), + + DEFINE_KEYFIELD( m_bIgnoreMoveParent, FIELD_BOOLEAN, "ignoremoveparent" ), + DEFINE_KEYFIELD( m_bIgnorePlayerCollisions, FIELD_BOOLEAN, "ignoreplayer" ), + + DEFINE_OUTPUT( m_playerOn, "PlayerOn" ), + DEFINE_OUTPUT( m_playerOff, "PlayerOff" ), + DEFINE_OUTPUT( m_OnOpen, "OnOpen" ), + DEFINE_OUTPUT( m_OnClose, "OnClose" ), + + DEFINE_EMBEDDED( m_vehicleView ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CPropVehicleChoreoGeneric, DT_PropVehicleChoreoGeneric) + SendPropEHandle(SENDINFO(m_hPlayer)), + SendPropBool(SENDINFO(m_bEnterAnimOn)), + SendPropBool(SENDINFO(m_bExitAnimOn)), + SendPropVector(SENDINFO(m_vecEyeExitEndpoint), -1, SPROP_COORD), + SendPropBool( SENDINFO_STRUCTELEM( m_vehicleView.bClampEyeAngles ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchCurveZero ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchCurveLinear ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flRollCurveZero ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flRollCurveLinear ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flFOV ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flYawMin ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flYawMax ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchMin ) ), + SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchMax ) ), +END_SEND_TABLE(); + + +bool ShouldVehicleIgnoreEntity( CBaseEntity *pVehicle, CBaseEntity *pCollide ) +{ + CPropVehicleChoreoGeneric *pChoreoVehicle = dynamic_cast ( pVehicle ); + + if ( pChoreoVehicle == NULL ) + return false; + + if ( pCollide == NULL ) + return false; + + if ( pChoreoVehicle->ShouldIgnoreParent() == false ) + return false; + + if ( pChoreoVehicle->GetMoveParent() == pCollide ) + return true; + + return false; +} + + +//------------------------------------------------ +// Precache +//------------------------------------------------ +void CPropVehicleChoreoGeneric::Precache( void ) +{ + BaseClass::Precache(); + + m_ServerVehicle.Initialize( STRING(m_vehicleScript) ); +} + + +//------------------------------------------------ +// Spawn +//------------------------------------------------ +void CPropVehicleChoreoGeneric::Spawn( void ) +{ + Precache(); + SetModel( STRING( GetModelName() ) ); + SetCollisionGroup( COLLISION_GROUP_VEHICLE ); + + if ( GetSolid() != SOLID_NONE ) + { + BaseClass::Spawn(); + } + + m_takedamage = DAMAGE_EVENTS_ONLY; + + SetNextThink( gpGlobals->curtime ); + + ParseViewParams( STRING(m_vehicleScript) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) +{ + if ( ptr->hitbox == VEHICLE_HITBOX_DRIVER ) + { + if ( m_hPlayer != NULL ) + { + m_hPlayer->TakeDamage( info ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CPropVehicleChoreoGeneric::OnTakeDamage( const CTakeDamageInfo &inputInfo ) +{ + CTakeDamageInfo info = inputInfo; + info.ScaleDamage( 25 ); + + // reset the damage + info.SetDamage( inputInfo.GetDamage() ); + + // Check to do damage to prisoner + if ( m_hPlayer != NULL ) + { + // Take no damage from physics damages + if ( info.GetDamageType() & DMG_CRUSH ) + return 0; + + // Take the damage + m_hPlayer->TakeDamage( info ); + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CPropVehicleChoreoGeneric::BodyTarget( const Vector &posSrc, bool bNoisy ) +{ + Vector shotPos; + + int eyeAttachmentIndex = LookupAttachment("vehicle_driver_eyes"); + GetAttachment( eyeAttachmentIndex, shotPos ); + + if ( bNoisy ) + { + shotPos[0] += random->RandomFloat( -8.0f, 8.0f ); + shotPos[1] += random->RandomFloat( -8.0f, 8.0f ); + shotPos[2] += random->RandomFloat( -8.0f, 8.0f ); + } + + return shotPos; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::Think(void) +{ + SetNextThink( gpGlobals->curtime + 0.1 ); + + if ( GetDriver() ) + { + BaseClass::Think(); + + // If the enter or exit animation has finished, tell the server vehicle + if ( IsSequenceFinished() && (m_bExitAnimOn || m_bEnterAnimOn) ) + { + GetServerVehicle()->HandleEntryExitFinish( m_bExitAnimOn, true ); + } + } + + StudioFrameAdvance(); + DispatchAnimEvents( this ); +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPropVehicleChoreoGeneric::InputOpen( inputdata_t &inputdata ) +{ + int nSequence = LookupSequence( "open" ); + + // Set to the desired anim, or default anim if the desired is not present + if ( nSequence > ACTIVITY_NOT_AVAILABLE ) + { + SetCycle( 0 ); + m_flAnimTime = gpGlobals->curtime; + ResetSequence( nSequence ); + ResetClientsideFrame(); + } + else + { + // Not available try to get default anim + Msg( "Choreo Generic Vehicle %s: missing open sequence\n", GetDebugName() ); + SetSequence( 0 ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPropVehicleChoreoGeneric::InputClose( inputdata_t &inputdata ) +{ + if ( m_bLocked || m_bEnterAnimOn ) + return; + + int nSequence = LookupSequence( "close" ); + + // Set to the desired anim, or default anim if the desired is not present + if ( nSequence > ACTIVITY_NOT_AVAILABLE ) + { + SetCycle( 0 ); + m_flAnimTime = gpGlobals->curtime; + ResetSequence( nSequence ); + ResetClientsideFrame(); + } + else + { + // Not available try to get default anim + Msg( "Choreo Generic Vehicle %s: missing close sequence\n", GetDebugName() ); + SetSequence( 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::HandleAnimEvent( animevent_t *pEvent ) +{ + if ( pEvent->event == AE_CHOREO_VEHICLE_OPEN ) + { + m_OnOpen.FireOutput( this, this ); + m_bLocked = false; + } + else if ( pEvent->event == AE_CHOREO_VEHICLE_CLOSE ) + { + m_OnClose.FireOutput( this, this ); + m_bLocked = true; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = ToBasePlayer( pActivator ); + if ( !pPlayer ) + return; + + ResetUseKey( pPlayer ); + + GetServerVehicle()->HandlePassengerEntry( pPlayer, (value > 0) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true of the player's allowed to enter / exit the vehicle +//----------------------------------------------------------------------------- +bool CPropVehicleChoreoGeneric::CanEnterVehicle( CBaseEntity *pEntity ) +{ + // Prevent entering if the vehicle's being driven by an NPC + if ( GetDriver() && GetDriver() != pEntity ) + return false; + + // Prevent entering if the vehicle's locked + return !m_bLocked; +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true of the player is allowed to exit the vehicle. +//----------------------------------------------------------------------------- +bool CPropVehicleChoreoGeneric::CanExitVehicle( CBaseEntity *pEntity ) +{ + // Prevent exiting if the vehicle's locked, rotating, or playing an entry/exit anim. + return ( !m_bLocked && (GetLocalAngularVelocity() == vec3_angle) && !m_bEnterAnimOn && !m_bExitAnimOn ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Override base class to add display +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::DrawDebugGeometryOverlays(void) +{ + // Draw if BBOX is on + if ( m_debugOverlays & OVERLAY_BBOX_BIT ) + { + } + + BaseClass::DrawDebugGeometryOverlays(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::EnterVehicle( CBaseCombatCharacter *pPassenger ) +{ + if ( pPassenger == NULL ) + return; + + CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); + if ( pPlayer != NULL ) + { + // Remove any player who may be in the vehicle at the moment + if ( m_hPlayer ) + { + ExitVehicle( VEHICLE_ROLE_DRIVER ); + } + + m_hPlayer = pPlayer; + m_playerOn.FireOutput( pPlayer, this, 0 ); + + m_ServerVehicle.SoundStart(); + } + else + { + // NPCs not supported yet - jdw + Assert( 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::SetVehicleEntryAnim( bool bOn ) +{ + m_bEnterAnimOn = bOn; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::ExitVehicle( int nRole ) +{ + CBasePlayer *pPlayer = m_hPlayer; + if ( !pPlayer ) + return; + + m_hPlayer = NULL; + ResetUseKey( pPlayer ); + + m_playerOff.FireOutput( pPlayer, this, 0 ); + m_bEnterAnimOn = false; + + m_ServerVehicle.SoundShutdown( 1.0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::ResetUseKey( CBasePlayer *pPlayer ) +{ + pPlayer->m_afButtonPressed &= ~IN_USE; +} + + +//----------------------------------------------------------------------------- +// Purpose: Vehicles are permanently oriented off angle for vphysics. +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const +{ + // This call is necessary to cause m_rgflCoordinateFrame to be recomputed + const matrix3x4_t &entityToWorld = EntityToWorldTransform(); + + if (pForward != NULL) + { + MatrixGetColumn( entityToWorld, 1, *pForward ); + } + + if (pRight != NULL) + { + MatrixGetColumn( entityToWorld, 0, *pRight ); + } + + if (pUp != NULL) + { + MatrixGetColumn( entityToWorld, 2, *pUp ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *CPropVehicleChoreoGeneric::GetDriver( void ) +{ + return m_hPlayer; +} + +//----------------------------------------------------------------------------- +// Purpose: Prevent the player from entering / exiting the vehicle +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::InputLock( inputdata_t &inputdata ) +{ + m_bLocked = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Allow the player to enter / exit the vehicle +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::InputUnlock( inputdata_t &inputdata ) +{ + m_bLocked = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Force the player to enter the vehicle. +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::InputEnterVehicle( inputdata_t &inputdata ) +{ + if ( m_bEnterAnimOn ) + return; + + // Try the activator first & use them if they are a player. + CBaseCombatCharacter *pPassenger = ToBaseCombatCharacter( inputdata.pActivator ); + if ( pPassenger == NULL ) + { + // Activator was not a player, just grab the singleplayer player. + pPassenger = UTIL_PlayerByIndex( 1 ); + if ( pPassenger == NULL ) + return; + } + + // FIXME: I hate code like this. I should really add a parameter to HandlePassengerEntry + // to allow entry into locked vehicles + bool bWasLocked = m_bLocked; + m_bLocked = false; + GetServerVehicle()->HandlePassengerEntry( pPassenger, true ); + m_bLocked = bWasLocked; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::InputEnterVehicleImmediate( inputdata_t &inputdata ) +{ + if ( m_bEnterAnimOn ) + return; + + // Try the activator first & use them if they are a player. + CBaseCombatCharacter *pPassenger = ToBaseCombatCharacter( inputdata.pActivator ); + if ( pPassenger == NULL ) + { + // Activator was not a player, just grab the singleplayer player. + pPassenger = UTIL_PlayerByIndex( 1 ); + if ( pPassenger == NULL ) + return; + } + + CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); + if ( pPlayer != NULL ) + { + if ( pPlayer->IsInAVehicle() ) + { + // Force the player out of whatever vehicle they are in. + pPlayer->LeaveVehicle(); + } + + pPlayer->GetInVehicle( GetServerVehicle(), VEHICLE_ROLE_DRIVER ); + } + else + { + // NPCs not supported yet - jdw + Assert( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Force the player to exit the vehicle. +//----------------------------------------------------------------------------- +void CPropVehicleChoreoGeneric::InputExitVehicle( inputdata_t &inputdata ) +{ + m_bForcedExit = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Parses the vehicle's script for the vehicle view parameters +//----------------------------------------------------------------------------- +bool CPropVehicleChoreoGeneric::ParseViewParams( const char *pScriptName ) +{ + byte *pFile = UTIL_LoadFileForMe( pScriptName, NULL ); + if ( !pFile ) + return false; + + IVPhysicsKeyParser *pParse = physcollision->VPhysicsKeyParserCreate( (char *)pFile ); + CVehicleChoreoViewParser viewParser; + while ( !pParse->Finished() ) + { + const char *pBlock = pParse->GetCurrentBlockName(); + if ( !strcmpi( pBlock, "vehicle_view" ) ) + { + pParse->ParseCustom( &m_vehicleView, &viewParser ); + } + else + { + pParse->SkipBlock(); + } + } + physcollision->VPhysicsKeyParserDestroy( pParse ); + UTIL_FreeFile( pFile ); + + Precache(); + + return true; +} + +//======================================================================================================================================== +// CRANE VEHICLE SERVER VEHICLE +//======================================================================================================================================== +CPropVehicleChoreoGeneric *CChoreoGenericServerVehicle::GetVehicle( void ) +{ + return (CPropVehicleChoreoGeneric *)GetDrivableVehicle(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pPlayer - +//----------------------------------------------------------------------------- +void CChoreoGenericServerVehicle::ItemPostFrame( CBasePlayer *player ) +{ + Assert( player == GetDriver() ); + + GetDrivableVehicle()->ItemPostFrame( player ); + + if (( player->m_afButtonPressed & IN_USE ) || GetVehicle()->ShouldForceExit() ) + { + GetVehicle()->ClearForcedExit(); + if ( GetDrivableVehicle()->CanExitVehicle(player) ) + { + // Let the vehicle try to play the exit animation + if ( !HandlePassengerExit( player ) && ( player != NULL ) ) + { + player->PlayUseDenySound(); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoGenericServerVehicle::GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles ) +{ + Assert( nRole == VEHICLE_ROLE_DRIVER ); + CBasePlayer *pPlayer = ToBasePlayer( GetDrivableVehicle()->GetDriver() ); + Assert( pPlayer ); + + *pAbsAngles = pPlayer->EyeAngles(); // yuck. this is an in/out parameter. + + float flPitchFactor = 1.0; + matrix3x4_t vehicleEyePosToWorld; + Vector vehicleEyeOrigin; + QAngle vehicleEyeAngles; + GetVehicle()->GetAttachment( "vehicle_driver_eyes", vehicleEyeOrigin, vehicleEyeAngles ); + AngleMatrix( vehicleEyeAngles, vehicleEyePosToWorld ); + + // Compute the relative rotation between the unperterbed eye attachment + the eye angles + matrix3x4_t cameraToWorld; + AngleMatrix( *pAbsAngles, cameraToWorld ); + + matrix3x4_t worldToEyePos; + MatrixInvert( vehicleEyePosToWorld, worldToEyePos ); + + matrix3x4_t vehicleCameraToEyePos; + ConcatTransforms( worldToEyePos, cameraToWorld, vehicleCameraToEyePos ); + + // Now perterb the attachment point + vehicleEyeAngles.x = RemapAngleRange( PITCH_CURVE_ZERO * flPitchFactor, PITCH_CURVE_LINEAR, vehicleEyeAngles.x ); + vehicleEyeAngles.z = RemapAngleRange( ROLL_CURVE_ZERO * flPitchFactor, ROLL_CURVE_LINEAR, vehicleEyeAngles.z ); + AngleMatrix( vehicleEyeAngles, vehicleEyeOrigin, vehicleEyePosToWorld ); + + // Now treat the relative eye angles as being relative to this new, perterbed view position... + matrix3x4_t newCameraToWorld; + ConcatTransforms( vehicleEyePosToWorld, vehicleCameraToEyePos, newCameraToWorld ); + + // output new view abs angles + MatrixAngles( newCameraToWorld, *pAbsAngles ); + + // UNDONE: *pOrigin would already be correct in single player if the HandleView() on the server ran after vphysics + MatrixGetColumn( newCameraToWorld, 3, *pAbsOrigin ); +} + +bool CPropVehicleChoreoGeneric::ShouldCollide( int collisionGroup, int contentsMask ) const +{ + if ( m_bIgnorePlayerCollisions == true ) + { + if ( collisionGroup == COLLISION_GROUP_PLAYER || collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) + return false; + } + + return BaseClass::ShouldCollide( collisionGroup, contentsMask ); +} + + +CVehicleChoreoViewParser::CVehicleChoreoViewParser( void ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleChoreoViewParser::ParseKeyValue( void *pData, const char *pKey, const char *pValue ) +{ + vehicleview_t *pView = (vehicleview_t *)pData; + // New gear? + if ( !strcmpi( pKey, "clamp" ) ) + { + pView->bClampEyeAngles = !!atoi( pValue ); + } + else if ( !strcmpi( pKey, "pitchcurvezero" ) ) + { + pView->flPitchCurveZero = atof( pValue ); + } + else if ( !strcmpi( pKey, "pitchcurvelinear" ) ) + { + pView->flPitchCurveLinear = atof( pValue ); + } + else if ( !strcmpi( pKey, "rollcurvezero" ) ) + { + pView->flRollCurveZero = atof( pValue ); + } + else if ( !strcmpi( pKey, "rollcurvelinear" ) ) + { + pView->flRollCurveLinear = atof( pValue ); + } + else if ( !strcmpi( pKey, "yawmin" ) ) + { + pView->flYawMin = atof( pValue ); + } + else if ( !strcmpi( pKey, "yawmax" ) ) + { + pView->flYawMax = atof( pValue ); + } + else if ( !strcmpi( pKey, "pitchmin" ) ) + { + pView->flPitchMin = atof( pValue ); + } + else if ( !strcmpi( pKey, "pitchmax" ) ) + { + pView->flPitchMax = atof( pValue ); + } + else if ( !strcmpi( pKey, "fov" ) ) + { + pView->flFOV = atof( pValue ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVehicleChoreoViewParser::SetDefaults( void *pData ) +{ + vehicleview_t *pView = (vehicleview_t *)pData; + + pView->bClampEyeAngles = true; + + pView->flPitchCurveZero = PITCH_CURVE_ZERO; + pView->flPitchCurveLinear = PITCH_CURVE_LINEAR; + pView->flRollCurveZero = ROLL_CURVE_ZERO; + pView->flRollCurveLinear = ROLL_CURVE_LINEAR; + pView->flFOV = CHOREO_VEHICLE_VIEW_FOV; + pView->flYawMin = CHOREO_VEHICLE_VIEW_YAW_MIN; + pView->flYawMax = CHOREO_VEHICLE_VIEW_YAW_MAX; + pView->flPitchMin = CHOREO_VEHICLE_VIEW_PITCH_MIN; + pView->flPitchMax = CHOREO_VEHICLE_VIEW_PITCH_MAX; +} diff --git a/dlls/wcedit.h b/dlls/wcedit.h index 102109b7..f7961456 100644 --- a/dlls/wcedit.h +++ b/dlls/wcedit.h @@ -1,36 +1,36 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Namespace for functions having to do with WC Edit mode -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef WCEDIT_H -#define WCEDIT_H -#pragma once - -class CBaseEntity; - -//============================================================================= -// >> NWCEdit -//============================================================================= -namespace NWCEdit -{ - Vector AirNodePlacementPosition( void ); - bool IsWCVersionValid(void); - void CreateAINode( CBasePlayer *pPlayer ); - void DestroyAINode( CBasePlayer *pPlayer ); - void CreateAILink( CBasePlayer *pPlayer ); - void DestroyAILink( CBasePlayer *pPlayer ); - void UndoDestroyAINode(void); - void RememberEntityPosition( CBaseEntity *pEntity ); - void UpdateEntityPosition( CBaseEntity *pEntity ); -}; - -#endif // WCEDIT_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Namespace for functions having to do with WC Edit mode +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef WCEDIT_H +#define WCEDIT_H +#pragma once + +class CBaseEntity; + +//============================================================================= +// >> NWCEdit +//============================================================================= +namespace NWCEdit +{ + Vector AirNodePlacementPosition( void ); + bool IsWCVersionValid(void); + void CreateAINode( CBasePlayer *pPlayer ); + void DestroyAINode( CBasePlayer *pPlayer ); + void CreateAILink( CBasePlayer *pPlayer ); + void DestroyAILink( CBasePlayer *pPlayer ); + void UndoDestroyAINode(void); + void RememberEntityPosition( CBaseEntity *pEntity ); + void UpdateEntityPosition( CBaseEntity *pEntity ); +}; + +#endif // WCEDIT_H diff --git a/dlls/world.cpp b/dlls/world.cpp index 016b3256..0c2f13c0 100644 --- a/dlls/world.cpp +++ b/dlls/world.cpp @@ -166,7 +166,7 @@ void CDecal::StaticDecal( void ) if ( pEntity->IsEffectActive( EF_NODRAW ) ) return false; - for ( int i = 0; i < ARRAYSIZE(ppszIgnoredClasses); i++ ) + for ( size_t i = 0; i < ARRAYSIZE(ppszIgnoredClasses); i++ ) { if ( pEntity->ClassMatches( ppszIgnoredClasses[i] ) ) return false; @@ -186,7 +186,7 @@ void CDecal::StaticDecal( void ) bool canDraw = true; - entityIndex = (short)trace.m_pEnt ? trace.m_pEnt->entindex() : 0; + entityIndex = trace.m_pEnt ? trace.m_pEnt->entindex() : 0; if ( entityIndex ) { CBaseEntity *ent = trace.m_pEnt; @@ -561,7 +561,7 @@ static const char *g_DefaultLightstyles[] = const char *GetDefaultLightstyleString( int styleIndex ) { - if ( styleIndex < ARRAYSIZE(g_DefaultLightstyles) ) + if ( styleIndex < static_cast(ARRAYSIZE(g_DefaultLightstyles)) ) { return g_DefaultLightstyles[styleIndex]; } @@ -647,7 +647,7 @@ void CWorld::Precache( void ) // // Setup light animation tables. 'a' is total darkness, 'z' is maxbright. // - for ( int i = 0; i < ARRAYSIZE(g_DefaultLightstyles); i++ ) + for ( int i = 0; i < static_cast(ARRAYSIZE(g_DefaultLightstyles)); i++ ) { engine->LightStyle( i, GetDefaultLightstyleString(i) ); } diff --git a/game_shared/ModelSoundsCache.cpp b/game_shared/ModelSoundsCache.cpp index 6588b66a..12661619 100644 --- a/game_shared/ModelSoundsCache.cpp +++ b/game_shared/ModelSoundsCache.cpp @@ -1,217 +1,217 @@ -//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= -// -// Purpose: -// -//============================================================================= - -#include "cbase.h" -#include "ModelSoundsCache.h" -#include "studio.h" -#include "eventlist.h" -#include "scriptevent.h" - -extern ISoundEmitterSystemBase *soundemitterbase; - -CStudioHdr *ModelSoundsCache_LoadModel( char const *filename ); -void ModelSoundsCache_PrecacheScriptSound( const char *soundname ); -void ModelSoundsCache_FinishModel( CStudioHdr *hdr ); -//----------------------------------------------------------------------------- -// Purpose: -// Input : *hdr - -// Output : static void -//----------------------------------------------------------------------------- - -void VerifySequenceIndex( CStudioHdr *pstudiohdr ); - -// HACK: This must match the #define in cl_animevent.h in the client .dll code!!! -#define CL_EVENT_SOUND 5004 -#define CL_EVENT_FOOTSTEP_LEFT 6004 -#define CL_EVENT_FOOTSTEP_RIGHT 6005 -#define CL_EVENT_MFOOTSTEP_LEFT 6006 -#define CL_EVENT_MFOOTSTEP_RIGHT 6007 - - -extern ISoundEmitterSystemBase *soundemitterbase; - -CModelSoundsCache::CModelSoundsCache() -{ -} - -CModelSoundsCache::CModelSoundsCache( const CModelSoundsCache& src ) -{ - sounds = src.sounds; -} - -char const *CModelSoundsCache::GetSoundName( int index ) -{ - return soundemitterbase->GetSoundName( sounds[ index ] ); -} - -void CModelSoundsCache::Save( CUtlBuffer& buf ) -{ - buf.PutShort( sounds.Count() ); - - for ( int i = 0; i < sounds.Count(); ++i ) - { - buf.PutString( GetSoundName( i ) ); - } -} - -void CModelSoundsCache::Restore( CUtlBuffer& buf ) -{ - MEM_ALLOC_CREDIT(); - unsigned short c; - - c = (unsigned short)buf.GetShort(); - - for ( int i = 0; i < c; ++i ) - { - char soundname[ 512 ]; - - buf.GetString( soundname, sizeof( soundname ) ); - - int idx = soundemitterbase->GetSoundIndex( soundname ); - if ( idx != -1 ) - { - Assert( idx <= 65535 ); - if ( sounds.Find( idx ) == sounds.InvalidIndex() ) - { - sounds.AddToTail( (unsigned short)idx ); - } - } - } -} - -void CModelSoundsCache::Rebuild( char const *filename ) -{ - sounds.RemoveAll(); - - CStudioHdr *hdr = ModelSoundsCache_LoadModel( filename ); - - if ( hdr ) - { - // Precache all sounds referenced in animation events - BuildAnimationEventSoundList( hdr, sounds ); - ModelSoundsCache_FinishModel( hdr ); - } -} - -void CModelSoundsCache::PrecacheSoundList() -{ - for ( int i = 0; i < sounds.Count(); ++i ) - { - ModelSoundsCache_PrecacheScriptSound( GetSoundName( i ) ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Static method -// Input : sounds - -// *soundname - -//----------------------------------------------------------------------------- -void CModelSoundsCache::FindOrAddScriptSound( CUtlVector< unsigned short >& sounds, char const *soundname ) -{ - int soundindex = soundemitterbase->GetSoundIndex( soundname ); - if ( soundindex != -1 ) - { - // Only add it once per model... - if ( sounds.Find( soundindex ) == sounds.InvalidIndex() ) - { - MEM_ALLOC_CREDIT(); - sounds.AddToTail( soundindex ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Static method -// Input : *hdr - -// sounds - -//----------------------------------------------------------------------------- -void CModelSoundsCache::BuildAnimationEventSoundList( CStudioHdr *hdr, CUtlVector< unsigned short >& sounds ) -{ - Assert( hdr ); - - // force animation event resolution!!! - VerifySequenceIndex( hdr ); - - // Find all animation events which fire off sound script entries... - for ( int iSeq=0; iSeq < hdr->GetNumSeq(); iSeq++ ) - { - mstudioseqdesc_t *pSeq = &hdr->pSeqdesc( iSeq ); - - // Now read out all the sound events with their timing - for ( int iEvent=0; iEvent < (int)pSeq->numevents; iEvent++ ) - { - mstudioevent_t *pEvent = pSeq->pEvent( iEvent ); - - switch ( pEvent->event ) - { - default: - { - if ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM ) - { - if ( pEvent->event == AE_SV_PLAYSOUND ) - { - FindOrAddScriptSound( sounds, pEvent->pszOptions() ); - } - } - } - break; - // Old-style client .dll animation event - case CL_EVENT_SOUND: - { - FindOrAddScriptSound( sounds, pEvent->pszOptions() ); - } - break; - case CL_EVENT_FOOTSTEP_LEFT: - case CL_EVENT_FOOTSTEP_RIGHT: - { - char soundname[256]; - char const *options = pEvent->pszOptions(); - if ( !options || !options[0] ) - { - options = "NPC_CombineS"; - } - - Q_snprintf( soundname, 256, "%s.RunFootstepLeft", options ); - FindOrAddScriptSound( sounds, soundname ); - Q_snprintf( soundname, 256, "%s.RunFootstepRight", options ); - FindOrAddScriptSound( sounds, soundname ); - Q_snprintf( soundname, 256, "%s.FootstepLeft", options ); - FindOrAddScriptSound( sounds, soundname ); - Q_snprintf( soundname, 256, "%s.FootstepRight", options ); - FindOrAddScriptSound( sounds, soundname ); - } - break; - case AE_CL_PLAYSOUND: - { - if ( !( pEvent->type & AE_TYPE_CLIENT ) ) - break; - - if ( pEvent->pszOptions()[0] ) - { - FindOrAddScriptSound( sounds, pEvent->pszOptions() ); - } - else - { - Warning( "-- Error --: empty soundname, .qc error on AE_CL_PLAYSOUND in model %s, sequence %s, animevent # %i\n", - hdr->pszName(), pSeq->pszLabel(), iEvent+1 ); - } - } - break; - case SCRIPT_EVENT_SOUND: - { - FindOrAddScriptSound( sounds, pEvent->pszOptions() ); - } - break; - - case SCRIPT_EVENT_SOUND_VOICE: - { - FindOrAddScriptSound( sounds, pEvent->pszOptions() ); - } - break; - } - } - } -} \ No newline at end of file +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "ModelSoundsCache.h" +#include "studio.h" +#include "eventlist.h" +#include "scriptevent.h" + +extern ISoundEmitterSystemBase *soundemitterbase; + +CStudioHdr *ModelSoundsCache_LoadModel( char const *filename ); +void ModelSoundsCache_PrecacheScriptSound( const char *soundname ); +void ModelSoundsCache_FinishModel( CStudioHdr *hdr ); +//----------------------------------------------------------------------------- +// Purpose: +// Input : *hdr - +// Output : static void +//----------------------------------------------------------------------------- + +void VerifySequenceIndex( CStudioHdr *pstudiohdr ); + +// HACK: This must match the #define in cl_animevent.h in the client .dll code!!! +#define CL_EVENT_SOUND 5004 +#define CL_EVENT_FOOTSTEP_LEFT 6004 +#define CL_EVENT_FOOTSTEP_RIGHT 6005 +#define CL_EVENT_MFOOTSTEP_LEFT 6006 +#define CL_EVENT_MFOOTSTEP_RIGHT 6007 + + +extern ISoundEmitterSystemBase *soundemitterbase; + +CModelSoundsCache::CModelSoundsCache() +{ +} + +CModelSoundsCache::CModelSoundsCache( const CModelSoundsCache& src ) +{ + sounds = src.sounds; +} + +char const *CModelSoundsCache::GetSoundName( int index ) +{ + return soundemitterbase->GetSoundName( sounds[ index ] ); +} + +void CModelSoundsCache::Save( CUtlBuffer& buf ) +{ + buf.PutShort( sounds.Count() ); + + for ( int i = 0; i < sounds.Count(); ++i ) + { + buf.PutString( GetSoundName( i ) ); + } +} + +void CModelSoundsCache::Restore( CUtlBuffer& buf ) +{ + MEM_ALLOC_CREDIT(); + unsigned short c; + + c = (unsigned short)buf.GetShort(); + + for ( int i = 0; i < c; ++i ) + { + char soundname[ 512 ]; + + buf.GetString( soundname, sizeof( soundname ) ); + + int idx = soundemitterbase->GetSoundIndex( soundname ); + if ( idx != -1 ) + { + Assert( idx <= 65535 ); + if ( sounds.Find( idx ) == sounds.InvalidIndex() ) + { + sounds.AddToTail( (unsigned short)idx ); + } + } + } +} + +void CModelSoundsCache::Rebuild( char const *filename ) +{ + sounds.RemoveAll(); + + CStudioHdr *hdr = ModelSoundsCache_LoadModel( filename ); + + if ( hdr ) + { + // Precache all sounds referenced in animation events + BuildAnimationEventSoundList( hdr, sounds ); + ModelSoundsCache_FinishModel( hdr ); + } +} + +void CModelSoundsCache::PrecacheSoundList() +{ + for ( int i = 0; i < sounds.Count(); ++i ) + { + ModelSoundsCache_PrecacheScriptSound( GetSoundName( i ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Static method +// Input : sounds - +// *soundname - +//----------------------------------------------------------------------------- +void CModelSoundsCache::FindOrAddScriptSound( CUtlVector< unsigned short >& sounds, char const *soundname ) +{ + int soundindex = soundemitterbase->GetSoundIndex( soundname ); + if ( soundindex != -1 ) + { + // Only add it once per model... + if ( sounds.Find( soundindex ) == sounds.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + sounds.AddToTail( soundindex ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Static method +// Input : *hdr - +// sounds - +//----------------------------------------------------------------------------- +void CModelSoundsCache::BuildAnimationEventSoundList( CStudioHdr *hdr, CUtlVector< unsigned short >& sounds ) +{ + Assert( hdr ); + + // force animation event resolution!!! + VerifySequenceIndex( hdr ); + + // Find all animation events which fire off sound script entries... + for ( int iSeq=0; iSeq < hdr->GetNumSeq(); iSeq++ ) + { + mstudioseqdesc_t *pSeq = &hdr->pSeqdesc( iSeq ); + + // Now read out all the sound events with their timing + for ( int iEvent=0; iEvent < (int)pSeq->numevents; iEvent++ ) + { + mstudioevent_t *pEvent = pSeq->pEvent( iEvent ); + + switch ( pEvent->event ) + { + default: + { + if ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM ) + { + if ( pEvent->event == AE_SV_PLAYSOUND ) + { + FindOrAddScriptSound( sounds, pEvent->pszOptions() ); + } + } + } + break; + // Old-style client .dll animation event + case CL_EVENT_SOUND: + { + FindOrAddScriptSound( sounds, pEvent->pszOptions() ); + } + break; + case CL_EVENT_FOOTSTEP_LEFT: + case CL_EVENT_FOOTSTEP_RIGHT: + { + char soundname[256]; + char const *options = pEvent->pszOptions(); + if ( !options || !options[0] ) + { + options = "NPC_CombineS"; + } + + Q_snprintf( soundname, 256, "%s.RunFootstepLeft", options ); + FindOrAddScriptSound( sounds, soundname ); + Q_snprintf( soundname, 256, "%s.RunFootstepRight", options ); + FindOrAddScriptSound( sounds, soundname ); + Q_snprintf( soundname, 256, "%s.FootstepLeft", options ); + FindOrAddScriptSound( sounds, soundname ); + Q_snprintf( soundname, 256, "%s.FootstepRight", options ); + FindOrAddScriptSound( sounds, soundname ); + } + break; + case AE_CL_PLAYSOUND: + { + if ( !( pEvent->type & AE_TYPE_CLIENT ) ) + break; + + if ( pEvent->pszOptions()[0] ) + { + FindOrAddScriptSound( sounds, pEvent->pszOptions() ); + } + else + { + Warning( "-- Error --: empty soundname, .qc error on AE_CL_PLAYSOUND in model %s, sequence %s, animevent # %i\n", + hdr->pszName(), pSeq->pszLabel(), iEvent+1 ); + } + } + break; + case SCRIPT_EVENT_SOUND: + { + FindOrAddScriptSound( sounds, pEvent->pszOptions() ); + } + break; + + case SCRIPT_EVENT_SOUND_VOICE: + { + FindOrAddScriptSound( sounds, pEvent->pszOptions() ); + } + break; + } + } + } +} diff --git a/game_shared/SoundEmitterSystem.cpp b/game_shared/SoundEmitterSystem.cpp index 6a534c93..9b301bfa 100644 --- a/game_shared/SoundEmitterSystem.cpp +++ b/game_shared/SoundEmitterSystem.cpp @@ -894,13 +894,13 @@ public: void StopSound( int entindex, const char *soundname ) { - int soundindex = soundemitterbase->GetSoundIndex( soundname ); + short soundindex = soundemitterbase->GetSoundIndex( soundname ); if ( soundindex == -1 ) { return; } - StopSoundByHandle( entindex, soundname, (HSOUNDSCRIPTHANDLE &)soundindex ); + StopSoundByHandle( entindex, soundname, soundindex); } diff --git a/game_shared/Sprite.cpp b/game_shared/Sprite.cpp index 74a94ec3..38cb6d4f 100644 --- a/game_shared/Sprite.cpp +++ b/game_shared/Sprite.cpp @@ -580,19 +580,19 @@ void CSprite::InputShowSprite( inputdata_t &inputdata ) void CSprite::InputColorRedValue( inputdata_t &inputdata ) { - int nNewColor = clamp( inputdata.value.Float(), 0, 255 ); + int nNewColor = clamp( inputdata.value.Int(), 0, 255 ); SetColor( nNewColor, m_clrRender->g, m_clrRender->b ); } void CSprite::InputColorGreenValue( inputdata_t &inputdata ) { - int nNewColor = clamp( inputdata.value.Float(), 0, 255 ); + int nNewColor = clamp( inputdata.value.Int(), 0, 255 ); SetColor( m_clrRender->r, nNewColor, m_clrRender->b ); } void CSprite::InputColorBlueValue( inputdata_t &inputdata ) { - int nNewColor = clamp( inputdata.value.Float(), 0, 255 ); + int nNewColor = clamp( inputdata.value.Int(), 0, 255 ); SetColor( m_clrRender->r, m_clrRender->g, nNewColor ); } diff --git a/game_shared/activitylist.h b/game_shared/activitylist.h index 8d5e2b3f..9a1af908 100644 --- a/game_shared/activitylist.h +++ b/game_shared/activitylist.h @@ -19,7 +19,7 @@ class CActivityRemap { public: - CActivityRemap() + CActivityRemap() : mappedActivity(ACT_IDLE) { pExtraBlock = NULL; } diff --git a/game_shared/ammodef.cpp b/game_shared/ammodef.cpp index ba0119de..55c0e494 100644 --- a/game_shared/ammodef.cpp +++ b/game_shared/ammodef.cpp @@ -59,7 +59,7 @@ int CAmmoDef::PlrDamage(int nAmmoIndex) { if ( m_AmmoType[nAmmoIndex].pPlrDmgCVar ) { - return m_AmmoType[nAmmoIndex].pPlrDmgCVar->GetFloat(); + return m_AmmoType[nAmmoIndex].pPlrDmgCVar->GetInt(); } return 0; @@ -84,7 +84,7 @@ int CAmmoDef::NPCDamage(int nAmmoIndex) { if ( m_AmmoType[nAmmoIndex].pNPCDmgCVar ) { - return m_AmmoType[nAmmoIndex].pNPCDmgCVar->GetFloat(); + return m_AmmoType[nAmmoIndex].pNPCDmgCVar->GetInt(); } return 0; @@ -108,7 +108,7 @@ int CAmmoDef::MaxCarry(int nAmmoIndex) if ( m_AmmoType[nAmmoIndex].pMaxCarry == USE_CVAR ) { if ( m_AmmoType[nAmmoIndex].pMaxCarryCVar ) - return m_AmmoType[nAmmoIndex].pMaxCarryCVar->GetFloat(); + return m_AmmoType[nAmmoIndex].pMaxCarryCVar->GetInt(); return 0; } diff --git a/game_shared/ammodef.h b/game_shared/ammodef.h index 84968ec9..ec1cf56a 100644 --- a/game_shared/ammodef.h +++ b/game_shared/ammodef.h @@ -1,103 +1,103 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Holds defintion for game ammo types -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#ifndef AI_AMMODEF_H -#define AI_AMMODEF_H - -#ifdef _WIN32 -#pragma once -#endif - -class ConVar; - -struct Ammo_t -{ - char *pName; - int nDamageType; - int eTracerType; - float physicsForceImpulse; - int nMinSplashSize; - int nMaxSplashSize; - - int nFlags; - - // Values for player/NPC damage and carrying capability - // If the integers are set, they override the CVars - int pPlrDmg; // CVar for player damage amount - int pNPCDmg; // CVar for NPC damage amount - int pMaxCarry; // CVar for maximum number can carry - const ConVar* pPlrDmgCVar; // CVar for player damage amount - const ConVar* pNPCDmgCVar; // CVar for NPC damage amount - const ConVar* pMaxCarryCVar; // CVar for maximum number can carry -}; - -// Used to tell AmmoDef to use the cvars, not the integers -#define USE_CVAR -1 -// Ammo is infinite -#define INFINITE_AMMO -2 - -enum AmmoTracer_t -{ - TRACER_NONE, - TRACER_LINE, - TRACER_RAIL, - TRACER_BEAM, - TRACER_LINE_AND_WHIZ, -}; - -enum AmmoFlags_t -{ - AMMO_FORCE_DROP_IF_CARRIED = 0x1, - AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER = 0x2, -}; - - -#include "shareddefs.h" - -//============================================================================= -// >> CAmmoDef -//============================================================================= -class CAmmoDef -{ - -public: - int m_nAmmoIndex; - - Ammo_t m_AmmoType[MAX_AMMO_TYPES]; - - Ammo_t *GetAmmoOfIndex(int nAmmoIndex); - int Index(const char *psz); - int PlrDamage(int nAmmoIndex); - int NPCDamage(int nAmmoIndex); - int MaxCarry(int nAmmoIndex); - int DamageType(int nAmmoIndex); - int TracerType(int nAmmoIndex); - float DamageForce(int nAmmoIndex); - int MinSplashSize(int nAmmoIndex); - int MaxSplashSize(int nAmmoIndex); - int Flags(int nAmmoIndex); - - void AddAmmoType(char const* name, int damageType, int tracerType, int plr_dmg, int npc_dmg, int carry, float physicsForceImpulse, int nFlags, int minSplashSize = 4, int maxSplashSize = 8 ); - void AddAmmoType(char const* name, int damageType, int tracerType, char const* plr_cvar, char const* npc_var, char const* carry_cvar, float physicsForceImpulse, int nFlags, int minSplashSize = 4, int maxSplashSize = 8 ); - - CAmmoDef(void); - virtual ~CAmmoDef( void ); - -private: - bool AddAmmoType(char const* name, int damageType, int tracerType, int nFlags, int minSplashSize, int maxSplashSize ); -}; - - -// Get the global ammodef object. This is usually implemented in each mod's game rules file somewhere, -// so the mod can setup custom ammo types. -CAmmoDef* GetAmmoDef(); - - -#endif // AI_AMMODEF_H - \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds defintion for game ammo types +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef AI_AMMODEF_H +#define AI_AMMODEF_H + +#ifdef _WIN32 +#pragma once +#endif + +class ConVar; + +struct Ammo_t +{ + char *pName; + int nDamageType; + int eTracerType; + float physicsForceImpulse; + int nMinSplashSize; + int nMaxSplashSize; + + int nFlags; + + // Values for player/NPC damage and carrying capability + // If the integers are set, they override the CVars + int pPlrDmg; // CVar for player damage amount + int pNPCDmg; // CVar for NPC damage amount + int pMaxCarry; // CVar for maximum number can carry + const ConVar* pPlrDmgCVar; // CVar for player damage amount + const ConVar* pNPCDmgCVar; // CVar for NPC damage amount + const ConVar* pMaxCarryCVar; // CVar for maximum number can carry +}; + +// Used to tell AmmoDef to use the cvars, not the integers +#define USE_CVAR -1 +// Ammo is infinite +#define INFINITE_AMMO -2 + +enum AmmoTracer_t +{ + TRACER_NONE, + TRACER_LINE, + TRACER_RAIL, + TRACER_BEAM, + TRACER_LINE_AND_WHIZ, +}; + +enum AmmoFlags_t +{ + AMMO_FORCE_DROP_IF_CARRIED = 0x1, + AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER = 0x2, +}; + + +#include "shareddefs.h" + +//============================================================================= +// >> CAmmoDef +//============================================================================= +class CAmmoDef +{ + +public: + int m_nAmmoIndex; + + Ammo_t m_AmmoType[MAX_AMMO_TYPES]; + + Ammo_t *GetAmmoOfIndex(int nAmmoIndex); + int Index(const char *psz); + int PlrDamage(int nAmmoIndex); + int NPCDamage(int nAmmoIndex); + int MaxCarry(int nAmmoIndex); + int DamageType(int nAmmoIndex); + int TracerType(int nAmmoIndex); + float DamageForce(int nAmmoIndex); + int MinSplashSize(int nAmmoIndex); + int MaxSplashSize(int nAmmoIndex); + int Flags(int nAmmoIndex); + + void AddAmmoType(char const* name, int damageType, int tracerType, int plr_dmg, int npc_dmg, int carry, float physicsForceImpulse, int nFlags, int minSplashSize = 4, int maxSplashSize = 8 ); + void AddAmmoType(char const* name, int damageType, int tracerType, char const* plr_cvar, char const* npc_var, char const* carry_cvar, float physicsForceImpulse, int nFlags, int minSplashSize = 4, int maxSplashSize = 8 ); + + CAmmoDef(void); + virtual ~CAmmoDef( void ); + +private: + bool AddAmmoType(char const* name, int damageType, int tracerType, int nFlags, int minSplashSize, int maxSplashSize ); +}; + + +// Get the global ammodef object. This is usually implemented in each mod's game rules file somewhere, +// so the mod can setup custom ammo types. +CAmmoDef* GetAmmoDef(); + + +#endif // AI_AMMODEF_H + diff --git a/game_shared/animation.cpp b/game_shared/animation.cpp index b7f1d14a..e9325678 100644 --- a/game_shared/animation.cpp +++ b/game_shared/animation.cpp @@ -25,7 +25,9 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef _MSC_VER #pragma warning( disable : 4244 ) +#endif #define iabs(i) (( (i) >= 0 ) ? (i) : -(i) ) int ExtractBbox( CStudioHdr *pstudiohdr, int sequence, Vector& mins, Vector& maxs ) diff --git a/game_shared/basecombatweapon_shared.cpp b/game_shared/basecombatweapon_shared.cpp index c10f44a9..10e24e9e 100644 --- a/game_shared/basecombatweapon_shared.cpp +++ b/game_shared/basecombatweapon_shared.cpp @@ -1606,7 +1606,7 @@ void CBaseCombatWeapon::WeaponSound( WeaponSound_t sound_type, float soundtime / #if !defined( CLIENT_DLL ) if( sound_type == EMPTY ) { - CSoundEnt::InsertSound( SOUND_COMBAT, GetOwner()->GetAbsOrigin(), SOUNDENT_VOLUME_EMPTY, 0.2, GetOwner() ); + CSoundEnt::InsertSound( SOUND_COMBAT, GetOwner()->GetAbsOrigin(), static_cast(SOUNDENT_VOLUME_EMPTY), 0.2, GetOwner() ); } #endif } diff --git a/game_shared/baseentity_shared.cpp b/game_shared/baseentity_shared.cpp index 2770088e..4ddb039b 100644 --- a/game_shared/baseentity_shared.cpp +++ b/game_shared/baseentity_shared.cpp @@ -619,7 +619,7 @@ BASEPTR CBaseEntity::ThinkSet( BASEPTR func, float thinkTime, const char *szCont { #if !defined( CLIENT_DLL ) #ifdef _DEBUG - COMPILE_TIME_ASSERT( sizeof(func) == 4 ); + COMPILE_TIME_ASSERT( sizeof(func) == MFP_SIZE ); #endif #endif @@ -1001,6 +1001,9 @@ void CBaseEntity::VPhysicsUpdate( IPhysicsObject *pPhysics ) VPhysicsUpdatePusher( pPhysics ); #endif break; + + default: + break; } } diff --git a/game_shared/basegrenade_shared.cpp b/game_shared/basegrenade_shared.cpp index db5a58d5..279c511e 100644 --- a/game_shared/basegrenade_shared.cpp +++ b/game_shared/basegrenade_shared.cpp @@ -307,7 +307,7 @@ void CBaseGrenade::DangerSoundThink( void ) } #if !defined( CLIENT_DLL ) - CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, GetAbsVelocity().Length( ), 0.2, this ); + CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, static_cast(GetAbsVelocity().Length()), 0.2, this ); #endif SetNextThink( gpGlobals->curtime + 0.2 ); @@ -364,7 +364,7 @@ void CBaseGrenade::BounceTouch( CBaseEntity *pOther ) // register a radius louder than the explosion, so we make sure everyone gets out of the way #if !defined( CLIENT_DLL ) - CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), m_flDamage / 0.4, 0.3, this ); + CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), static_cast(m_flDamage / 0.4), 0.3, this ); #endif m_bHasWarnedAI = true; } diff --git a/game_shared/baseplayer_shared.cpp b/game_shared/baseplayer_shared.cpp index 9e4f5e6e..62512485 100644 --- a/game_shared/baseplayer_shared.cpp +++ b/game_shared/baseplayer_shared.cpp @@ -1117,7 +1117,7 @@ void CBasePlayer::PlayerUse ( void ) if ( pTrain && !(m_nButtons & IN_JUMP) && (GetFlags() & FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(this) ) { m_afPhysicsFlags |= PFLAG_DIROVERRIDE; - m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed()); + m_iTrain = TrainSpeed(static_cast(pTrain->m_flSpeed), static_cast(((CFuncTrackTrain*)pTrain)->GetMaxSpeed())); m_iTrain |= TRAIN_NEW; EmitSound( "Player.UseTrain" ); return; @@ -1267,7 +1267,7 @@ void CBasePlayer::SmoothViewOnStairs( Vector& eyeOrigin ) } } -static bool IsWaterContents( int contents ) +/*static bool IsWaterContents( int contents ) { if ( contents & MASK_WATER ) return true; @@ -1276,7 +1276,7 @@ static bool IsWaterContents( int contents ) // return true; return false; -} +}*/ void CBasePlayer::ResetObserverMode() { diff --git a/game_shared/beam_flags.h b/game_shared/beam_flags.h index a7f2e65f..7268d288 100644 --- a/game_shared/beam_flags.h +++ b/game_shared/beam_flags.h @@ -1,37 +1,37 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#if !defined( BEAM_FLAGS_H ) -#define BEAM_FLAGS_H -#ifdef _WIN32 -#pragma once -#endif - -enum -{ - FBEAM_STARTENTITY = 0x00000001, - FBEAM_ENDENTITY = 0x00000002, - FBEAM_FADEIN = 0x00000004, - FBEAM_FADEOUT = 0x00000008, - FBEAM_SINENOISE = 0x00000010, - FBEAM_SOLID = 0x00000020, - FBEAM_SHADEIN = 0x00000040, - FBEAM_SHADEOUT = 0x00000080, - FBEAM_ONLYNOISEONCE = 0x00000100, // Only calculate our noise once - FBEAM_NOTILE = 0x00000200, - FBEAM_USE_HITBOXES = 0x00000400, // Attachment indices represent hitbox indices instead when this is set. - FBEAM_STARTVISIBLE = 0x00000800, // Has this client actually seen this beam's start entity yet? - FBEAM_ENDVISIBLE = 0x00001000, // Has this client actually seen this beam's end entity yet? - FBEAM_ISACTIVE = 0x00002000, - FBEAM_FOREVER = 0x00004000, - FBEAM_HALOBEAM = 0x00008000, // When drawing a beam with a halo, don't ignore the segments and endwidth - NUM_BEAM_FLAGS = 16 // KEEP THIS UPDATED! -}; - -#endif // BEAM_FLAGS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#if !defined( BEAM_FLAGS_H ) +#define BEAM_FLAGS_H +#ifdef _WIN32 +#pragma once +#endif + +enum +{ + FBEAM_STARTENTITY = 0x00000001, + FBEAM_ENDENTITY = 0x00000002, + FBEAM_FADEIN = 0x00000004, + FBEAM_FADEOUT = 0x00000008, + FBEAM_SINENOISE = 0x00000010, + FBEAM_SOLID = 0x00000020, + FBEAM_SHADEIN = 0x00000040, + FBEAM_SHADEOUT = 0x00000080, + FBEAM_ONLYNOISEONCE = 0x00000100, // Only calculate our noise once + FBEAM_NOTILE = 0x00000200, + FBEAM_USE_HITBOXES = 0x00000400, // Attachment indices represent hitbox indices instead when this is set. + FBEAM_STARTVISIBLE = 0x00000800, // Has this client actually seen this beam's start entity yet? + FBEAM_ENDVISIBLE = 0x00001000, // Has this client actually seen this beam's end entity yet? + FBEAM_ISACTIVE = 0x00002000, + FBEAM_FOREVER = 0x00004000, + FBEAM_HALOBEAM = 0x00008000, // When drawing a beam with a halo, don't ignore the segments and endwidth + NUM_BEAM_FLAGS = 16 // KEEP THIS UPDATED! +}; + +#endif // BEAM_FLAGS_H diff --git a/game_shared/beam_shared.cpp b/game_shared/beam_shared.cpp index 1233570d..4c5a1c75 100644 --- a/game_shared/beam_shared.cpp +++ b/game_shared/beam_shared.cpp @@ -758,19 +758,19 @@ void CBeam::InputWidth( inputdata_t &inputdata ) void CBeam::InputColorRedValue( inputdata_t &inputdata ) { - int nNewColor = clamp( inputdata.value.Float(), 0, 255 ); + int nNewColor = clamp( inputdata.value.Int(), 0, 255 ); SetColor( nNewColor, m_clrRender->g, m_clrRender->b ); } void CBeam::InputColorGreenValue( inputdata_t &inputdata ) { - int nNewColor = clamp( inputdata.value.Float(), 0, 255 ); + int nNewColor = clamp( inputdata.value.Int(), 0, 255 ); SetColor( m_clrRender->r, nNewColor, m_clrRender->b ); } void CBeam::InputColorBlueValue( inputdata_t &inputdata ) { - int nNewColor = clamp( inputdata.value.Float(), 0, 255 ); + int nNewColor = clamp( inputdata.value.Int(), 0, 255 ); SetColor( m_clrRender->r, m_clrRender->g, nNewColor ); } diff --git a/game_shared/debugoverlay_shared.cpp b/game_shared/debugoverlay_shared.cpp index 1209d132..2d416759 100644 --- a/game_shared/debugoverlay_shared.cpp +++ b/game_shared/debugoverlay_shared.cpp @@ -335,7 +335,7 @@ void NDebugOverlay::DrawTickMarkedLine(const Vector &startPos, const Vector &end Vector lineDir = (endPos - startPos); float lineDist = VectorNormalize( lineDir ); - int numTicks = lineDist/tickDist; + int numTicks = static_cast(lineDist / tickDist); Vector vBodyDir; #if defined( CLIENT_DLL ) diff --git a/game_shared/effect_color_tables.h b/game_shared/effect_color_tables.h index d52420ee..080c3642 100644 --- a/game_shared/effect_color_tables.h +++ b/game_shared/effect_color_tables.h @@ -31,12 +31,13 @@ enum }; // Commander mode table +#ifdef CLIENT_DLL static colorentry_t commandercolors[] = { - { COMMAND_POINT_RED, 1.0, 0.0, 0.0 }, - { COMMAND_POINT_BLUE, 0.0, 0.0, 1.0 }, - { COMMAND_POINT_GREEN, 0.0, 1.0, 0.0 }, - { COMMAND_POINT_YELLOW, 1.0, 1.0, 0.0 }, + { COMMAND_POINT_RED, 1, 0, 0 }, + { COMMAND_POINT_BLUE, 0, 0, 1 }, + { COMMAND_POINT_GREEN, 0, 1, 0 }, + { COMMAND_POINT_YELLOW, 1, 1, 0 }, }; static colorentry_t bloodcolors[] = @@ -45,5 +46,6 @@ static colorentry_t bloodcolors[] = { BLOOD_COLOR_YELLOW, 195, 195, 0 }, { BLOOD_COLOR_MECH, 20, 20, 20 }, }; +#endif #endif // EFFECT_COLOR_TABLES_H diff --git a/game_shared/env_wind_shared.cpp b/game_shared/env_wind_shared.cpp index e6b17ff5..bc0a1868 100644 --- a/game_shared/env_wind_shared.cpp +++ b/game_shared/env_wind_shared.cpp @@ -257,7 +257,7 @@ float CEnvWindShared::WindThink( float flTime ) m_flAveWindSpeed = m_Stream.RandomInt( m_iMinGust, m_iMaxGust ); // change wind direction, maybe a lot - m_iWindDir = anglemod( m_iWindDir + m_Stream.RandomInt(-m_iGustDirChange, m_iGustDirChange) ); + m_iWindDir = static_cast(anglemod( m_iWindDir + m_Stream.RandomInt(-m_iGustDirChange, m_iGustDirChange) )); // set up to stop the gust in a short while m_bGusting = true; diff --git a/game_shared/eventlist.cpp b/game_shared/eventlist.cpp index 494e03cf..5b75a0b0 100644 --- a/game_shared/eventlist.cpp +++ b/game_shared/eventlist.cpp @@ -1,233 +1,232 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "cbase.h" -#include "eventlist.h" -#include "stringregistry.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// NOTE: If CStringRegistry allowed storing arbitrary data, we could just use that. -// in this case we have the "isPrivate" member and the replacement rules -// (eventIndex can be reused by private activities), so a custom table is necessary -struct eventlist_t -{ - int eventIndex; - int iType; - unsigned short stringKey; - short isPrivate; -}; - -CUtlVector g_EventList; - -// This stores the actual event names. Also, the string ID in the registry is simply an index -// into the g_EventList array. -CStringRegistry g_EventStrings; - -// this is just here to accelerate adds -static int g_HighestEvent = 0; - -int g_nEventListVersion = 1; - - -void EventList_Init( void ) -{ - g_HighestEvent = 0; -} - -void EventList_Free( void ) -{ - g_EventStrings.ClearStrings(); - g_EventList.Purge(); - - // So studiohdrs can reindex event indices - ++g_nEventListVersion; -} - -// add a new event to the database -eventlist_t *EventList_AddEventEntry( const char *pName, int iEventIndex, bool isPrivate, int iType ) -{ - MEM_ALLOC_CREDIT(); - int index = g_EventList.AddToTail(); - eventlist_t *pList = &g_EventList[index]; - pList->eventIndex = iEventIndex; - pList->stringKey = g_EventStrings.AddString( pName, index ); - pList->isPrivate = isPrivate; - pList->iType = iType; - - // UNDONE: This implies that ALL shared activities are added before ANY custom activities - // UNDONE: Segment these instead? It's a 32-bit int, how many activities do we need? - if ( iEventIndex > g_HighestEvent ) - { - g_HighestEvent = iEventIndex; - } - - return pList; -} - -// get the database entry from a string -static eventlist_t *ListFromString( const char *pString ) -{ - // just use the string registry to do this search/map - int stringID = g_EventStrings.GetStringID( pString ); - if ( stringID < 0 ) - return NULL; - - return &g_EventList[stringID]; -} - -// Get the database entry for an index -static eventlist_t *ListFromEvent( int eventIndex ) -{ - // ugly linear search - for ( int i = 0; i < g_EventList.Size(); i++ ) - { - if ( g_EventList[i].eventIndex == eventIndex ) - { - return &g_EventList[i]; - } - } - - return NULL; -} - -int EventList_GetEventType( int eventIndex ) -{ - eventlist_t *pEvent = ListFromEvent( eventIndex ); - - if ( pEvent ) - { - return pEvent->iType; - } - - return -1; -} - - -bool EventList_RegisterSharedEvent( const char *pszEventName, int iEventIndex, int iType ) -{ - // UNDONE: Do we want to do these checks when not in developer mode? or maybe DEBUG only? - // They really only matter when you change the list of code controlled activities. IDs - // for content controlled activities never collide because they are generated. - - // first, check to make sure the slot we're asking for is free. It must be for - // a shared event. - eventlist_t *pList = ListFromString( pszEventName ); - if ( !pList ) - { - pList = ListFromEvent( iEventIndex ); - } - - //Already in list. - if ( pList ) - { - return false; - } - // ---------------------------------------------------------------- - - EventList_AddEventEntry( pszEventName, iEventIndex, false, iType ); - return true; -} - -Animevent EventList_RegisterPrivateEvent( const char *pszEventName ) -{ - eventlist_t *pList = ListFromString( pszEventName ); - if ( pList ) - { - // this activity is already in the list. If the activity we collided with is also private, - // then the collision is OK. Otherwise, it's a bug. - if ( pList->isPrivate ) - { - return (Animevent)pList->eventIndex; - } - else - { - // this private activity collides with a shared activity. That is not allowed. - Warning( "***\nShared<->Private Event collision!\n***\n" ); - Assert(0); - return AE_INVALID; - } - } - - pList = EventList_AddEventEntry( pszEventName, g_HighestEvent+1, true, AE_TYPE_SERVER ); - return (Animevent)pList->eventIndex; -} - -// Get the index for a given Event name -// Done at load time for all models -int EventList_IndexForName( const char *pszEventName ) -{ - // this is a fast O(lgn) search (actually does 2 O(lgn) searches) - eventlist_t *pList = ListFromString( pszEventName ); - - if ( pList ) - { - return pList->eventIndex; - } - - return -1; -} - -// Get the name for a given index -// This should only be used in debug code, it does a linear search -// But at least it only compares integers -const char *EventList_NameForIndex( int eventIndex ) -{ - eventlist_t *pList = ListFromEvent( eventIndex ); - if ( pList ) - { - return g_EventStrings.GetStringForKey( pList->stringKey ); - } - return NULL; -} - -void EventList_RegisterSharedEvents( void ) -{ - REGISTER_SHARED_ANIMEVENT( AE_EMPTY, AE_TYPE_SERVER ); - - REGISTER_SHARED_ANIMEVENT( AE_NPC_LEFTFOOT, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_RIGHTFOOT, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_BODYDROP_LIGHT, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_BODYDROP_HEAVY, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_SWISHSOUND, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_180TURN, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_ITEM_PICKUP, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_DROP, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_SET_SEQUENCE_NAME, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_SET_SEQUENCE_NUMBER, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_SET_ACTIVITY, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_HOLSTER, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_DRAW, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_FIRE, AE_TYPE_SERVER | AE_TYPE_WEAPON ); - - REGISTER_SHARED_ANIMEVENT( AE_CL_PLAYSOUND, AE_TYPE_CLIENT ); - REGISTER_SHARED_ANIMEVENT( AE_SV_PLAYSOUND, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_CL_STOPSOUND, AE_TYPE_CLIENT ); - - REGISTER_SHARED_ANIMEVENT( AE_START_SCRIPTED_EFFECT, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_STOP_SCRIPTED_EFFECT, AE_TYPE_SERVER ); - - REGISTER_SHARED_ANIMEVENT( AE_CLIENT_EFFECT_ATTACH, AE_TYPE_CLIENT ); - - REGISTER_SHARED_ANIMEVENT( AE_MUZZLEFLASH, AE_TYPE_CLIENT ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_MUZZLEFLASH, AE_TYPE_CLIENT ); - - REGISTER_SHARED_ANIMEVENT( AE_THUMPER_THUMP, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_AMMOCRATE_PICKUP_AMMO, AE_TYPE_SERVER ); - - REGISTER_SHARED_ANIMEVENT( AE_NPC_RAGDOLL, AE_TYPE_SERVER ); - - REGISTER_SHARED_ANIMEVENT( AE_NPC_ADDGESTURE, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_RESTARTGESTURE, AE_TYPE_SERVER ); - - REGISTER_SHARED_ANIMEVENT( AE_NPC_ATTACK_BROADCAST, AE_TYPE_SERVER ); - - REGISTER_SHARED_ANIMEVENT( AE_NPC_HURT_INTERACTION_PARTNER, AE_TYPE_SERVER ); - REGISTER_SHARED_ANIMEVENT( AE_NPC_SET_INTERACTION_CANTDIE, AE_TYPE_SERVER ); - -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "eventlist.h" +#include "stringregistry.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// NOTE: If CStringRegistry allowed storing arbitrary data, we could just use that. +// in this case we have the "isPrivate" member and the replacement rules +// (eventIndex can be reused by private activities), so a custom table is necessary +struct eventlist_t +{ + int eventIndex; + int iType; + unsigned short stringKey; + short isPrivate; +}; + +CUtlVector g_EventList; + +// This stores the actual event names. Also, the string ID in the registry is simply an index +// into the g_EventList array. +CStringRegistry g_EventStrings; + +// this is just here to accelerate adds +static int g_HighestEvent = 0; + +int g_nEventListVersion = 1; + + +void EventList_Init( void ) +{ + g_HighestEvent = 0; +} + +void EventList_Free( void ) +{ + g_EventStrings.ClearStrings(); + g_EventList.Purge(); + + // So studiohdrs can reindex event indices + ++g_nEventListVersion; +} + +// add a new event to the database +eventlist_t *EventList_AddEventEntry( const char *pName, int iEventIndex, bool isPrivate, int iType ) +{ + MEM_ALLOC_CREDIT(); + int index = g_EventList.AddToTail(); + eventlist_t *pList = &g_EventList[index]; + pList->eventIndex = iEventIndex; + pList->stringKey = g_EventStrings.AddString( pName, index ); + pList->isPrivate = isPrivate; + pList->iType = iType; + + // UNDONE: This implies that ALL shared activities are added before ANY custom activities + // UNDONE: Segment these instead? It's a 32-bit int, how many activities do we need? + if ( iEventIndex > g_HighestEvent ) + { + g_HighestEvent = iEventIndex; + } + + return pList; +} + +// get the database entry from a string +static eventlist_t *ListFromString( const char *pString ) +{ + // just use the string registry to do this search/map + int stringID = g_EventStrings.GetStringID( pString ); + if ( stringID < 0 ) + return NULL; + + return &g_EventList[stringID]; +} + +// Get the database entry for an index +static eventlist_t *ListFromEvent( int eventIndex ) +{ + // ugly linear search + for ( int i = 0; i < g_EventList.Size(); i++ ) + { + if ( g_EventList[i].eventIndex == eventIndex ) + { + return &g_EventList[i]; + } + } + + return NULL; +} + +int EventList_GetEventType( int eventIndex ) +{ + eventlist_t *pEvent = ListFromEvent( eventIndex ); + + if ( pEvent ) + { + return pEvent->iType; + } + + return -1; +} + + +bool EventList_RegisterSharedEvent( const char *pszEventName, int iEventIndex, int iType ) +{ + // UNDONE: Do we want to do these checks when not in developer mode? or maybe DEBUG only? + // They really only matter when you change the list of code controlled activities. IDs + // for content controlled activities never collide because they are generated. + + // first, check to make sure the slot we're asking for is free. It must be for + // a shared event. + eventlist_t *pList = ListFromString( pszEventName ); + if ( !pList ) + { + pList = ListFromEvent( iEventIndex ); + } + + //Already in list. + if ( pList ) + { + return false; + } + // ---------------------------------------------------------------- + + EventList_AddEventEntry( pszEventName, iEventIndex, false, iType ); + return true; +} + +Animevent EventList_RegisterPrivateEvent( const char *pszEventName ) +{ + eventlist_t *pList = ListFromString( pszEventName ); + if ( pList ) + { + // this activity is already in the list. If the activity we collided with is also private, + // then the collision is OK. Otherwise, it's a bug. + if ( pList->isPrivate ) + { + return (Animevent)pList->eventIndex; + } + else + { + // this private activity collides with a shared activity. That is not allowed. + Warning( "***\nShared<->Private Event collision!\n***\n" ); + Assert(0); + return AE_INVALID; + } + } + + pList = EventList_AddEventEntry( pszEventName, g_HighestEvent+1, true, AE_TYPE_SERVER ); + return (Animevent)pList->eventIndex; +} + +// Get the index for a given Event name +// Done at load time for all models +int EventList_IndexForName( const char *pszEventName ) +{ + // this is a fast O(lgn) search (actually does 2 O(lgn) searches) + eventlist_t *pList = ListFromString( pszEventName ); + + if ( pList ) + { + return pList->eventIndex; + } + + return -1; +} + +// Get the name for a given index +// This should only be used in debug code, it does a linear search +// But at least it only compares integers +const char *EventList_NameForIndex( int eventIndex ) +{ + eventlist_t *pList = ListFromEvent( eventIndex ); + if ( pList ) + { + return g_EventStrings.GetStringForKey( pList->stringKey ); + } + return NULL; +} + +void EventList_RegisterSharedEvents( void ) +{ + REGISTER_SHARED_ANIMEVENT( AE_EMPTY, AE_TYPE_SERVER ); + + REGISTER_SHARED_ANIMEVENT( AE_NPC_LEFTFOOT, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_RIGHTFOOT, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_BODYDROP_LIGHT, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_BODYDROP_HEAVY, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_SWISHSOUND, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_180TURN, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_ITEM_PICKUP, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_DROP, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_SET_SEQUENCE_NAME, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_SET_SEQUENCE_NUMBER, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_SET_ACTIVITY, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_HOLSTER, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_DRAW, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_WEAPON_FIRE, AE_TYPE_SERVER | AE_TYPE_WEAPON ); + + REGISTER_SHARED_ANIMEVENT( AE_CL_PLAYSOUND, AE_TYPE_CLIENT ); + REGISTER_SHARED_ANIMEVENT( AE_SV_PLAYSOUND, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_CL_STOPSOUND, AE_TYPE_CLIENT ); + + REGISTER_SHARED_ANIMEVENT( AE_START_SCRIPTED_EFFECT, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_STOP_SCRIPTED_EFFECT, AE_TYPE_SERVER ); + + REGISTER_SHARED_ANIMEVENT( AE_CLIENT_EFFECT_ATTACH, AE_TYPE_CLIENT ); + + REGISTER_SHARED_ANIMEVENT( AE_MUZZLEFLASH, AE_TYPE_CLIENT ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_MUZZLEFLASH, AE_TYPE_CLIENT ); + + REGISTER_SHARED_ANIMEVENT( AE_THUMPER_THUMP, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_AMMOCRATE_PICKUP_AMMO, AE_TYPE_SERVER ); + + REGISTER_SHARED_ANIMEVENT( AE_NPC_RAGDOLL, AE_TYPE_SERVER ); + + REGISTER_SHARED_ANIMEVENT( AE_NPC_ADDGESTURE, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_RESTARTGESTURE, AE_TYPE_SERVER ); + + REGISTER_SHARED_ANIMEVENT( AE_NPC_ATTACK_BROADCAST, AE_TYPE_SERVER ); + + REGISTER_SHARED_ANIMEVENT( AE_NPC_HURT_INTERACTION_PARTNER, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_SET_INTERACTION_CANTDIE, AE_TYPE_SERVER ); +} diff --git a/game_shared/func_ladder.cpp b/game_shared/func_ladder.cpp index 7f5d3b11..ef059cae 100644 --- a/game_shared/func_ladder.cpp +++ b/game_shared/func_ladder.cpp @@ -87,7 +87,7 @@ void CFuncLadder::Spawn() m_vecPlayerMountPositionBottom.GetZ(), bottomtrace.m_pEnt ? - UTIL_VarArgs( "%s/%s", bottomtrace.m_pEnt->GetClassname(), bottomtrace.m_pEnt->GetEntityName() ) + UTIL_VarArgs( "%s/%s", bottomtrace.m_pEnt->GetClassname(), STRING(bottomtrace.m_pEnt->GetEntityName()) ) : "NULL" ); } @@ -99,7 +99,7 @@ void CFuncLadder::Spawn() m_vecPlayerMountPositionTop.GetZ(), toptrace.m_pEnt ? - UTIL_VarArgs( "%s/%s", toptrace.m_pEnt->GetClassname(), toptrace.m_pEnt->GetEntityName() ) + UTIL_VarArgs( "%s/%s", toptrace.m_pEnt->GetClassname(), STRING(toptrace.m_pEnt->GetEntityName()) ) : "NULL" ); } diff --git a/game_shared/gameeventdefs.h b/game_shared/gameeventdefs.h index e738ecd8..620e0d96 100644 --- a/game_shared/gameeventdefs.h +++ b/game_shared/gameeventdefs.h @@ -1,21 +1,21 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef GAMEEVENTDEFS_H -#define GAMEEVENTDEFS_H - -#ifdef _WIN32 -#pragma once -#endif - -// Make sure your gameevents.res and this file is in sync -// Event names may be 32 characters long and are case sensitive -// 256 is the maximum number of game events - -#define GAME_EVENT_PLAYER_DEATH "player_death" -#define GAME_EVENT_SAY_TEXT "say_text" - -#endif // GAMEEVENTDEFS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef GAMEEVENTDEFS_H +#define GAMEEVENTDEFS_H + +#ifdef _WIN32 +#pragma once +#endif + +// Make sure your gameevents.res and this file is in sync +// Event names may be 32 characters long and are case sensitive +// 256 is the maximum number of game events + +#define GAME_EVENT_PLAYER_DEATH "player_death" +#define GAME_EVENT_SAY_TEXT "say_text" + +#endif // GAMEEVENTDEFS_H diff --git a/game_shared/gamemovement.cpp b/game_shared/gamemovement.cpp index b9dcf468..3b9dc87c 100644 --- a/game_shared/gamemovement.cpp +++ b/game_shared/gamemovement.cpp @@ -2813,7 +2813,7 @@ void CreateStuckTable( void ) } } } - Assert( idx < sizeof(rgv3tStuckTable)/sizeof(rgv3tStuckTable[0])); + Assert( idx < static_cast(sizeof(rgv3tStuckTable)/sizeof(rgv3tStuckTable[0]))); } #else extern void CreateStuckTable( void ); @@ -4202,7 +4202,9 @@ void CGameMovement::FullTossMove( void ) // Purpose: //----------------------------------------------------------------------------- +#ifdef _MSC_VER #pragma warning (disable : 4701) +#endif void CGameMovement::IsometricMove( void ) { @@ -4235,4 +4237,6 @@ void CGameMovement::IsometricMove( void ) mv->m_vecVelocity.Init(); } +#ifdef _MSC_VER #pragma warning (default : 4701) +#endif diff --git a/game_shared/gamestats/ep1_gamestats.cpp b/game_shared/gamestats/ep1_gamestats.cpp index 8dc43fae..d3a94c77 100644 --- a/game_shared/gamestats/ep1_gamestats.cpp +++ b/game_shared/gamestats/ep1_gamestats.cpp @@ -1,197 +1,197 @@ -//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= -// -// Purpose: -// -//============================================================================= - -#include "gamestats/ep1_gamestats.h" -#include "tier1/UtlBuffer.h" - -static int g_nParseVersion = -1; - -void GameStatsRecord_t::Clear() -{ - m_nCount = 0; - m_nSeconds = 0; - m_nCommentary = 0; - m_nHDR = 0; - m_nCaptions = 0; - m_bSteam = true; - m_bCyberCafe = false; - Q_memset( m_nSkill, 0, sizeof( m_nSkill ) ); - m_nDeaths = 0; -} - -void GameStatsRecord_t::SaveToBuffer( CUtlBuffer &buf ) -{ - buf.PutInt( m_nCount ); - buf.PutInt( m_nSeconds ); - buf.PutInt( m_nCommentary ); - buf.PutInt( m_nHDR ); - buf.PutInt( m_nCaptions ); - for ( int i = 0; i < 3; ++i ) - { - buf.PutInt( m_nSkill[ i ] ); - } - - buf.PutChar( m_bSteam ? 1 : 0 ); - buf.PutChar( m_bCyberCafe ? 1 : 0 ); - buf.PutInt( m_nDeaths ); -} - -bool GameStatsRecord_t::ParseFromBuffer( CUtlBuffer &buf ) -{ - bool bret = true; - m_nCount = buf.GetInt(); - - if ( m_nCount > 100000 || m_nCount < 0 ) - { - bret = false; - } - - m_nSeconds = buf.GetInt(); - // Note, don't put the buf.GetInt() in the macro since it'll get evaluated twice!!! - m_nSeconds = max( m_nSeconds, 0 ); - - m_nCommentary = buf.GetInt(); - if ( m_nCommentary < 0 || m_nCommentary > 100000 ) - { - bret = false; - } - - m_nHDR = buf.GetInt(); - if ( m_nHDR < 0 || m_nHDR > 100000 ) - { - bret = false; - } - - m_nCaptions = buf.GetInt(); - if ( m_nCaptions < 0 || m_nCaptions > 100000 ) - { - bret = false; - } - - for ( int i = 0; i < 3; ++i ) - { - m_nSkill[ i ] = buf.GetInt(); - if ( m_nSkill[ i ] < 0 || m_nSkill[ i ] > 100000 ) - { - bret = false; - } - } - - if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD ) - { - m_bSteam = buf.GetChar() ? true : false; - } - if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD2 ) - { - m_bCyberCafe = buf.GetChar() ? true : false; - } - if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD5 ) - { - m_nDeaths = buf.GetInt(); - } - - return bret; -} - -void GameStats_t::Clear() -{ - m_nVersion = GAMESTATS_FILE_VERSION; - m_szUserID[ 0 ] = 0; - m_nSecondsToCompleteGame = 0; - m_Summary.Clear(); - m_MapTotals.Purge(); -} - -void GameStats_t::SaveToBuffer( CUtlBuffer& buf ) -{ - buf.PutShort( GAMESTATS_FILE_VERSION ); - buf.Put( m_szUserID, 16 ); - buf.PutInt( m_nSecondsToCompleteGame ); - - m_Summary.SaveToBuffer( buf ); - - int c = m_MapTotals.Count(); - buf.PutInt( c ); - for ( int i = m_MapTotals.First(); i != m_MapTotals.InvalidIndex(); i = m_MapTotals.Next( i ) ) - { - char const *name = m_MapTotals.GetElementName( i ); - GameStatsRecord_t &rec = m_MapTotals[ i ]; - - buf.PutString( name ); - rec.SaveToBuffer( buf ); - } - - buf.PutChar( (char)m_nHL2ChaptureUnlocked ); - buf.PutChar( m_bSteam ? 1 : 0 ); - buf.PutChar( m_bCyberCafe ? 1 : 0 ); - buf.PutShort( (short)m_nDXLevel ); -} - -GameStatsRecord_t *GameStats_t::FindOrAddRecordForMap( char const *mapname ) -{ - int idx = m_MapTotals.Find( mapname ); - if ( idx == m_MapTotals.InvalidIndex() ) - { - idx = m_MapTotals.Insert( mapname ); - } - - return &m_MapTotals[ idx ]; -} - -bool GameStats_t::ParseFromBuffer( CUtlBuffer& buf ) -{ - bool bret = true; - int version = buf.GetShort(); - if ( version > GAMESTATS_FILE_VERSION ) - return false; - - // Set global parse version - g_nParseVersion = version; - m_nVersion = version; - - buf.Get( m_szUserID, 16 ); - m_szUserID[ sizeof( m_szUserID ) - 1 ] = 0; - m_nSecondsToCompleteGame = buf.GetInt(); - if ( m_nSecondsToCompleteGame < 0 || m_nSecondsToCompleteGame > 10000000 ) - { - bret = false; - } - - m_Summary.ParseFromBuffer( buf ); - int c = buf.GetInt(); - if ( c > 1024 || c < 0 ) - { - bret = false; - } - - for ( int i = 0; i < c; ++i ) - { - char mapname[ 256 ]; - buf.GetString( mapname, sizeof( mapname ) ); - - GameStatsRecord_t *rec = FindOrAddRecordForMap( mapname ); - bool valid= rec->ParseFromBuffer( buf ); - if ( !valid ) - { - bret = false; - } - } - - if ( g_nParseVersion >= GAMESTATS_FILE_VERSION_OLD2 ) - { - m_nHL2ChaptureUnlocked = (int)buf.GetChar(); - m_bSteam = buf.GetChar() ? true : false; - } - if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD2 ) - { - m_bCyberCafe = buf.GetChar() ? true : false; - } - if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD3 ) - { - m_nDXLevel = (int)buf.GetShort(); - } - return bret; -} \ No newline at end of file +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "gamestats/ep1_gamestats.h" +#include "tier1/utlbuffer.h" + +static int g_nParseVersion = -1; + +void GameStatsRecord_t::Clear() +{ + m_nCount = 0; + m_nSeconds = 0; + m_nCommentary = 0; + m_nHDR = 0; + m_nCaptions = 0; + m_bSteam = true; + m_bCyberCafe = false; + Q_memset( m_nSkill, 0, sizeof( m_nSkill ) ); + m_nDeaths = 0; +} + +void GameStatsRecord_t::SaveToBuffer( CUtlBuffer &buf ) +{ + buf.PutInt( m_nCount ); + buf.PutInt( m_nSeconds ); + buf.PutInt( m_nCommentary ); + buf.PutInt( m_nHDR ); + buf.PutInt( m_nCaptions ); + for ( int i = 0; i < 3; ++i ) + { + buf.PutInt( m_nSkill[ i ] ); + } + + buf.PutChar( m_bSteam ? 1 : 0 ); + buf.PutChar( m_bCyberCafe ? 1 : 0 ); + buf.PutInt( m_nDeaths ); +} + +bool GameStatsRecord_t::ParseFromBuffer( CUtlBuffer &buf ) +{ + bool bret = true; + m_nCount = buf.GetInt(); + + if ( m_nCount > 100000 || m_nCount < 0 ) + { + bret = false; + } + + m_nSeconds = buf.GetInt(); + // Note, don't put the buf.GetInt() in the macro since it'll get evaluated twice!!! + m_nSeconds = max( m_nSeconds, 0 ); + + m_nCommentary = buf.GetInt(); + if ( m_nCommentary < 0 || m_nCommentary > 100000 ) + { + bret = false; + } + + m_nHDR = buf.GetInt(); + if ( m_nHDR < 0 || m_nHDR > 100000 ) + { + bret = false; + } + + m_nCaptions = buf.GetInt(); + if ( m_nCaptions < 0 || m_nCaptions > 100000 ) + { + bret = false; + } + + for ( int i = 0; i < 3; ++i ) + { + m_nSkill[ i ] = buf.GetInt(); + if ( m_nSkill[ i ] < 0 || m_nSkill[ i ] > 100000 ) + { + bret = false; + } + } + + if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD ) + { + m_bSteam = buf.GetChar() ? true : false; + } + if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD2 ) + { + m_bCyberCafe = buf.GetChar() ? true : false; + } + if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD5 ) + { + m_nDeaths = buf.GetInt(); + } + + return bret; +} + +void GameStats_t::Clear() +{ + m_nVersion = GAMESTATS_FILE_VERSION; + m_szUserID[ 0 ] = 0; + m_nSecondsToCompleteGame = 0; + m_Summary.Clear(); + m_MapTotals.Purge(); +} + +void GameStats_t::SaveToBuffer( CUtlBuffer& buf ) +{ + buf.PutShort( GAMESTATS_FILE_VERSION ); + buf.Put( m_szUserID, 16 ); + buf.PutInt( m_nSecondsToCompleteGame ); + + m_Summary.SaveToBuffer( buf ); + + int c = m_MapTotals.Count(); + buf.PutInt( c ); + for ( int i = m_MapTotals.First(); i != m_MapTotals.InvalidIndex(); i = m_MapTotals.Next( i ) ) + { + char const *name = m_MapTotals.GetElementName( i ); + GameStatsRecord_t &rec = m_MapTotals[ i ]; + + buf.PutString( name ); + rec.SaveToBuffer( buf ); + } + + buf.PutChar( (char)m_nHL2ChaptureUnlocked ); + buf.PutChar( m_bSteam ? 1 : 0 ); + buf.PutChar( m_bCyberCafe ? 1 : 0 ); + buf.PutShort( (short)m_nDXLevel ); +} + +GameStatsRecord_t *GameStats_t::FindOrAddRecordForMap( char const *mapname ) +{ + int idx = m_MapTotals.Find( mapname ); + if ( idx == m_MapTotals.InvalidIndex() ) + { + idx = m_MapTotals.Insert( mapname ); + } + + return &m_MapTotals[ idx ]; +} + +bool GameStats_t::ParseFromBuffer( CUtlBuffer& buf ) +{ + bool bret = true; + int version = buf.GetShort(); + if ( version > GAMESTATS_FILE_VERSION ) + return false; + + // Set global parse version + g_nParseVersion = version; + m_nVersion = version; + + buf.Get( m_szUserID, 16 ); + m_szUserID[ sizeof( m_szUserID ) - 1 ] = 0; + m_nSecondsToCompleteGame = buf.GetInt(); + if ( m_nSecondsToCompleteGame < 0 || m_nSecondsToCompleteGame > 10000000 ) + { + bret = false; + } + + m_Summary.ParseFromBuffer( buf ); + int c = buf.GetInt(); + if ( c > 1024 || c < 0 ) + { + bret = false; + } + + for ( int i = 0; i < c; ++i ) + { + char mapname[ 256 ]; + buf.GetString( mapname, sizeof( mapname ) ); + + GameStatsRecord_t *rec = FindOrAddRecordForMap( mapname ); + bool valid= rec->ParseFromBuffer( buf ); + if ( !valid ) + { + bret = false; + } + } + + if ( g_nParseVersion >= GAMESTATS_FILE_VERSION_OLD2 ) + { + m_nHL2ChaptureUnlocked = (int)buf.GetChar(); + m_bSteam = buf.GetChar() ? true : false; + } + if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD2 ) + { + m_bCyberCafe = buf.GetChar() ? true : false; + } + if ( g_nParseVersion > GAMESTATS_FILE_VERSION_OLD3 ) + { + m_nDXLevel = (int)buf.GetShort(); + } + return bret; +} diff --git a/game_shared/gamestats/ep1_gamestats.h b/game_shared/gamestats/ep1_gamestats.h index b5439eb4..b70529dd 100644 --- a/game_shared/gamestats/ep1_gamestats.h +++ b/game_shared/gamestats/ep1_gamestats.h @@ -11,7 +11,7 @@ #endif class CUtlBuffer; -#include "tier1/UtlDict.h" +#include "tier1/utldict.h" #define GAMESTATS_FILE_VERSION_OLD 001 #define GAMESTATS_FILE_VERSION_OLD2 002 @@ -61,9 +61,9 @@ public: GameStats_t() : m_nVersion( GAMESTATS_FILE_VERSION ), m_nSecondsToCompleteGame( 0 ), - m_nHL2ChaptureUnlocked( 0 ), m_bSteam( true ), m_bCyberCafe( false ), + m_nHL2ChaptureUnlocked( 0 ), m_nDXLevel( 0 ) { m_szUserID[ 0 ] = 0; diff --git a/game_shared/gamestringpool.cpp b/game_shared/gamestringpool.cpp index 5b504ddb..859a37cd 100644 --- a/game_shared/gamestringpool.cpp +++ b/game_shared/gamestringpool.cpp @@ -1,77 +1,77 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" - -#include "stringpool.h" -#include "igamesystem.h" -#include "gamestringpool.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: The actual storage for pooled per-level strings -//----------------------------------------------------------------------------- -class CGameStringPool : public CStringPool, public CBaseGameSystem -{ - virtual char const *Name() { return "CGameStringPool"; } - - virtual void LevelShutdownPostEntity() - { - FreeAll(); - } - -public: - void CGameStringPool::Dump( void ) - { - for ( int i = m_Strings.FirstInorder(); i != m_Strings.InvalidIndex(); i = m_Strings.NextInorder(i) ) - { - DevMsg( " %d (0x%x) : %s\n", i, m_Strings[i], m_Strings[i] ); - } - DevMsg( "\n" ); - DevMsg( "Size: %d items\n", m_Strings.Count() ); - } -}; - -static CGameStringPool g_GameStringPool; - - -//----------------------------------------------------------------------------- -// String system accessor -//----------------------------------------------------------------------------- -IGameSystem *GameStringSystem() -{ - return &g_GameStringPool; -} - - -//----------------------------------------------------------------------------- -// Purpose: The public accessor for the level-global pooled strings -//----------------------------------------------------------------------------- -string_t AllocPooledString( const char * pszValue ) -{ - if (pszValue && *pszValue) - return MAKE_STRING( g_GameStringPool.Allocate( pszValue ) ); - return NULL_STRING; -} - -string_t FindPooledString( const char *pszValue ) -{ - return MAKE_STRING( g_GameStringPool.Find( pszValue ) ); -} - -#ifndef CLIENT_DLL -//------------------------------------------------------------------------------ -// Purpose: -//------------------------------------------------------------------------------ -void CC_DumpGameStringTable( void ) -{ - g_GameStringPool.Dump(); -} -static ConCommand dumpgamestringtable("dumpgamestringtable", CC_DumpGameStringTable, "Dump the contents of the game string table to the console.", FCVAR_CHEAT); -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "stringpool.h" +#include "igamesystem.h" +#include "gamestringpool.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: The actual storage for pooled per-level strings +//----------------------------------------------------------------------------- +class CGameStringPool : public CStringPool, public CBaseGameSystem +{ + virtual char const *Name() { return "CGameStringPool"; } + + virtual void LevelShutdownPostEntity() + { + FreeAll(); + } + +public: + void Dump( void ) + { + for ( int i = m_Strings.FirstInorder(); i != m_Strings.InvalidIndex(); i = m_Strings.NextInorder(i) ) + { + DevMsg( " %d (0x%x) : %s\n", i, m_Strings[i], m_Strings[i] ); + } + DevMsg( "\n" ); + DevMsg( "Size: %d items\n", m_Strings.Count() ); + } +}; + +static CGameStringPool g_GameStringPool; + + +//----------------------------------------------------------------------------- +// String system accessor +//----------------------------------------------------------------------------- +IGameSystem *GameStringSystem() +{ + return &g_GameStringPool; +} + + +//----------------------------------------------------------------------------- +// Purpose: The public accessor for the level-global pooled strings +//----------------------------------------------------------------------------- +string_t AllocPooledString( const char * pszValue ) +{ + if (pszValue && *pszValue) + return MAKE_STRING( g_GameStringPool.Allocate( pszValue ) ); + return NULL_STRING; +} + +string_t FindPooledString( const char *pszValue ) +{ + return MAKE_STRING( g_GameStringPool.Find( pszValue ) ); +} + +#ifndef CLIENT_DLL +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CC_DumpGameStringTable( void ) +{ + g_GameStringPool.Dump(); +} +static ConCommand dumpgamestringtable("dumpgamestringtable", CC_DumpGameStringTable, "Dump the contents of the game string table to the console.", FCVAR_CHEAT); +#endif diff --git a/game_shared/hl2/basehlcombatweapon_shared.cpp b/game_shared/hl2/basehlcombatweapon_shared.cpp index dc0cb4d3..a63c05ec 100644 --- a/game_shared/hl2/basehlcombatweapon_shared.cpp +++ b/game_shared/hl2/basehlcombatweapon_shared.cpp @@ -1,420 +1,420 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" -#include "basehlcombatweapon_shared.h" - -#include "hl2_player_shared.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -LINK_ENTITY_TO_CLASS( basehlcombatweapon, CBaseHLCombatWeapon ); - -IMPLEMENT_NETWORKCLASS_ALIASED( BaseHLCombatWeapon , DT_BaseHLCombatWeapon ) - -BEGIN_NETWORK_TABLE( CBaseHLCombatWeapon , DT_BaseHLCombatWeapon ) -#if !defined( CLIENT_DLL ) -// SendPropInt( SENDINFO( m_bReflectViewModelAnimations ), 1, SPROP_UNSIGNED ), -#else -// RecvPropInt( RECVINFO( m_bReflectViewModelAnimations ) ), -#endif -END_NETWORK_TABLE() - - -#if !defined( CLIENT_DLL ) - -#include "globalstate.h" - -//--------------------------------------------------------- -// Save/Restore -//--------------------------------------------------------- -BEGIN_DATADESC( CBaseHLCombatWeapon ) - - DEFINE_FIELD( m_bLowered, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flRaiseTime, FIELD_TIME ), - DEFINE_FIELD( m_flHolsterTime, FIELD_TIME ), - -END_DATADESC() - -#endif - -BEGIN_PREDICTION_DATA( CBaseHLCombatWeapon ) -END_PREDICTION_DATA() - -ConVar sk_auto_reload_time( "sk_auto_reload_time", "3", FCVAR_REPLICATED ); - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBaseHLCombatWeapon::ItemHolsterFrame( void ) -{ - BaseClass::ItemHolsterFrame(); - - // Must be player held - if ( GetOwner() && GetOwner()->IsPlayer() == false ) - return; - - // We can't be active - if ( GetOwner()->GetActiveWeapon() == this ) - return; - - // If it's been longer than three seconds, reload - if ( ( gpGlobals->curtime - m_flHolsterTime ) > sk_auto_reload_time.GetFloat() ) - { - // Just load the clip with no animations - FinishReload(); - m_flHolsterTime = gpGlobals->curtime; - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CBaseHLCombatWeapon::CanLower() -{ - if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) - return false; - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Drops the weapon into a lowered pose -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHLCombatWeapon::Lower( void ) -{ - //Don't bother if we don't have the animation - if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) - return false; - - m_bLowered = true; - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Brings the weapon up to the ready position -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHLCombatWeapon::Ready( void ) -{ - //Don't bother if we don't have the animation - if ( SelectWeightedSequence( ACT_VM_LOWERED_TO_IDLE ) == ACTIVITY_NOT_AVAILABLE ) - return false; - - m_bLowered = false; - m_flRaiseTime = gpGlobals->curtime + 0.5f; - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHLCombatWeapon::Deploy( void ) -{ - // If we should be lowered, deploy in the lowered position - // We have to ask the player if the last time it checked, the weapon was lowered - if ( GetOwner() && GetOwner()->IsPlayer() ) - { - CHL2_Player *pPlayer = assert_cast( GetOwner() ); - if ( pPlayer->IsWeaponLowered() ) - { - if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) != ACTIVITY_NOT_AVAILABLE ) - { - if ( DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_VM_IDLE_LOWERED, (char*)GetAnimPrefix() ) ) - { - m_bLowered = true; - - // Stomp the next attack time to fix the fact that the lower idles are long - pPlayer->SetNextAttack( gpGlobals->curtime + 1.0 ); - m_flNextPrimaryAttack = gpGlobals->curtime + 1.0; - m_flNextSecondaryAttack = gpGlobals->curtime + 1.0; - return true; - } - } - } - } - - m_bLowered = false; - return BaseClass::Deploy(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHLCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) -{ - if ( BaseClass::Holster( pSwitchingTo ) ) - { - m_flHolsterTime = gpGlobals->curtime; - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHLCombatWeapon::WeaponShouldBeLowered( void ) -{ - // Can't be in the middle of another animation - if ( GetIdealActivity() != ACT_VM_IDLE_LOWERED && GetIdealActivity() != ACT_VM_IDLE && - GetIdealActivity() != ACT_VM_IDLE_TO_LOWERED && GetIdealActivity() != ACT_VM_LOWERED_TO_IDLE ) - return false; - - if ( m_bLowered ) - return true; - -#if !defined( CLIENT_DLL ) - - if ( GlobalEntity_GetState( "friendly_encounter" ) == GLOBAL_ON ) - return true; - -#endif - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Allows the weapon to choose proper weapon idle animation -//----------------------------------------------------------------------------- -void CBaseHLCombatWeapon::WeaponIdle( void ) -{ - //See if we should idle high or low - if ( WeaponShouldBeLowered() ) - { - // Move to lowered position if we're not there yet - if ( GetActivity() != ACT_VM_IDLE_LOWERED && GetActivity() != ACT_VM_IDLE_TO_LOWERED - && GetActivity() != ACT_TRANSITION ) - { - SendWeaponAnim( ACT_VM_IDLE_LOWERED ); - } - else if ( HasWeaponIdleTimeElapsed() ) - { - // Keep idling low - SendWeaponAnim( ACT_VM_IDLE_LOWERED ); - } - } - else - { - // See if we need to raise immediately - if ( m_flRaiseTime < gpGlobals->curtime && GetActivity() == ACT_VM_IDLE_LOWERED ) - { - SendWeaponAnim( ACT_VM_IDLE ); - } - else if ( HasWeaponIdleTimeElapsed() ) - { - SendWeaponAnim( ACT_VM_IDLE ); - } - } -} - -float g_lateralBob; -float g_verticalBob; - -#if defined( CLIENT_DLL ) && !defined( HL2MP ) - -#define HL2_BOB_CYCLE_MIN 1.0f -#define HL2_BOB_CYCLE_MAX 0.45f -#define HL2_BOB 0.002f -#define HL2_BOB_UP 0.5f - - -static ConVar cl_bobcycle( "cl_bobcycle","0.8" ); -static ConVar cl_bob( "cl_bob","0.002" ); -static ConVar cl_bobup( "cl_bobup","0.5" ); - -// Register these cvars if needed for easy tweaking -static ConVar v_iyaw_cycle( "v_iyaw_cycle", "2"/*, FCVAR_UNREGISTERED*/ ); -static ConVar v_iroll_cycle( "v_iroll_cycle", "0.5"/*, FCVAR_UNREGISTERED*/ ); -static ConVar v_ipitch_cycle( "v_ipitch_cycle", "1"/*, FCVAR_UNREGISTERED*/ ); -static ConVar v_iyaw_level( "v_iyaw_level", "0.3"/*, FCVAR_UNREGISTERED*/ ); -static ConVar v_iroll_level( "v_iroll_level", "0.1"/*, FCVAR_UNREGISTERED*/ ); -static ConVar v_ipitch_level( "v_ipitch_level", "0.3"/*, FCVAR_UNREGISTERED*/ ); - -//----------------------------------------------------------------------------- -// Purpose: -// Output : float -//----------------------------------------------------------------------------- -float CBaseHLCombatWeapon::CalcViewmodelBob( void ) -{ - static float bobtime; - static float lastbobtime; - float cycle; - - CBasePlayer *player = ToBasePlayer( GetOwner() ); - //Assert( player ); - - //NOTENOTE: For now, let this cycle continue when in the air, because it snaps badly without it - - if ( ( !gpGlobals->frametime ) || ( player == NULL ) ) - { - //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) - return 0.0f;// just use old value - } - - //Find the speed of the player - float speed = player->GetLocalVelocity().Length2D(); - - //FIXME: This maximum speed value must come from the server. - // MaxSpeed() is not sufficient for dealing with sprinting - jdw - - speed = clamp( speed, -320, 320 ); - - float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f ); - - bobtime += ( gpGlobals->curtime - lastbobtime ) * bob_offset; - lastbobtime = gpGlobals->curtime; - - //Calculate the vertical bob - cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX)*HL2_BOB_CYCLE_MAX; - cycle /= HL2_BOB_CYCLE_MAX; - - if ( cycle < HL2_BOB_UP ) - { - cycle = M_PI * cycle / HL2_BOB_UP; - } - else - { - cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); - } - - g_verticalBob = speed*0.005f; - g_verticalBob = g_verticalBob*0.3 + g_verticalBob*0.7*sin(cycle); - - g_verticalBob = clamp( g_verticalBob, -7.0f, 4.0f ); - - //Calculate the lateral bob - cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX*2)*HL2_BOB_CYCLE_MAX*2; - cycle /= HL2_BOB_CYCLE_MAX*2; - - if ( cycle < HL2_BOB_UP ) - { - cycle = M_PI * cycle / HL2_BOB_UP; - } - else - { - cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); - } - - g_lateralBob = speed*0.005f; - g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); - g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); - - //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) - return 0.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &origin - -// &angles - -// viewmodelindex - -//----------------------------------------------------------------------------- -void CBaseHLCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) -{ - Vector forward, right; - AngleVectors( angles, &forward, &right, NULL ); - - CalcViewmodelBob(); - - // Apply bob, but scaled down to 40% - VectorMA( origin, g_verticalBob * 0.1f, forward, origin ); - - // Z bob a bit more - origin[2] += g_verticalBob * 0.1f; - - // bob the angles - angles[ ROLL ] += g_verticalBob * 0.5f; - angles[ PITCH ] -= g_verticalBob * 0.4f; - - angles[ YAW ] -= g_lateralBob * 0.3f; - - VectorMA( origin, g_lateralBob * 0.8f, right, origin ); -} - -//----------------------------------------------------------------------------- -Vector CBaseHLCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) -{ - return BaseClass::GetBulletSpread( proficiency ); -} - -//----------------------------------------------------------------------------- -float CBaseHLCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) -{ - return BaseClass::GetSpreadBias( proficiency ); -} -//----------------------------------------------------------------------------- - -const WeaponProficiencyInfo_t *CBaseHLCombatWeapon::GetProficiencyValues() -{ - return NULL; -} - -#else - -// Server stubs -float CBaseHLCombatWeapon::CalcViewmodelBob( void ) -{ - return 0.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &origin - -// &angles - -// viewmodelindex - -//----------------------------------------------------------------------------- -void CBaseHLCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) -{ -} - - -//----------------------------------------------------------------------------- -Vector CBaseHLCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) -{ - Vector baseSpread = BaseClass::GetBulletSpread( proficiency ); - - const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); - float flModifier = (pProficiencyValues)[ proficiency ].spreadscale; - return ( baseSpread * flModifier ); -} - -//----------------------------------------------------------------------------- -float CBaseHLCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) -{ - const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); - return (pProficiencyValues)[ proficiency ].bias; -} - -//----------------------------------------------------------------------------- -const WeaponProficiencyInfo_t *CBaseHLCombatWeapon::GetProficiencyValues() -{ - return GetDefaultProficiencyValues(); -} - -//----------------------------------------------------------------------------- -const WeaponProficiencyInfo_t *CBaseHLCombatWeapon::GetDefaultProficiencyValues() -{ - // Weapon proficiency table. Keep this in sync with WeaponProficiency_t enum in the header!! - static WeaponProficiencyInfo_t g_BaseWeaponProficiencyTable[] = - { - { 2.50, 1.0 }, - { 2.00, 1.0 }, - { 1.50, 1.0 }, - { 1.25, 1.0 }, - { 1.00, 1.0 }, - }; - - COMPILE_TIME_ASSERT( ARRAYSIZE(g_BaseWeaponProficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); - - return g_BaseWeaponProficiencyTable; -} - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "basehlcombatweapon_shared.h" + +#include "hl2_player_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LINK_ENTITY_TO_CLASS( basehlcombatweapon, CBaseHLCombatWeapon ); + +IMPLEMENT_NETWORKCLASS_ALIASED( BaseHLCombatWeapon , DT_BaseHLCombatWeapon ) + +BEGIN_NETWORK_TABLE( CBaseHLCombatWeapon , DT_BaseHLCombatWeapon ) +#if !defined( CLIENT_DLL ) +// SendPropInt( SENDINFO( m_bReflectViewModelAnimations ), 1, SPROP_UNSIGNED ), +#else +// RecvPropInt( RECVINFO( m_bReflectViewModelAnimations ) ), +#endif +END_NETWORK_TABLE() + + +#if !defined( CLIENT_DLL ) + +#include "globalstate.h" + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CBaseHLCombatWeapon ) + + DEFINE_FIELD( m_bLowered, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flRaiseTime, FIELD_TIME ), + DEFINE_FIELD( m_flHolsterTime, FIELD_TIME ), + +END_DATADESC() + +#endif + +BEGIN_PREDICTION_DATA( CBaseHLCombatWeapon ) +END_PREDICTION_DATA() + +ConVar sk_auto_reload_time( "sk_auto_reload_time", "3", FCVAR_REPLICATED ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseHLCombatWeapon::ItemHolsterFrame( void ) +{ + BaseClass::ItemHolsterFrame(); + + // Must be player held + if ( GetOwner() && GetOwner()->IsPlayer() == false ) + return; + + // We can't be active + if ( GetOwner()->GetActiveWeapon() == this ) + return; + + // If it's been longer than three seconds, reload + if ( ( gpGlobals->curtime - m_flHolsterTime ) > sk_auto_reload_time.GetFloat() ) + { + // Just load the clip with no animations + FinishReload(); + m_flHolsterTime = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CBaseHLCombatWeapon::CanLower() +{ + if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) + return false; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Drops the weapon into a lowered pose +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHLCombatWeapon::Lower( void ) +{ + //Don't bother if we don't have the animation + if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) + return false; + + m_bLowered = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Brings the weapon up to the ready position +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHLCombatWeapon::Ready( void ) +{ + //Don't bother if we don't have the animation + if ( SelectWeightedSequence( ACT_VM_LOWERED_TO_IDLE ) == ACTIVITY_NOT_AVAILABLE ) + return false; + + m_bLowered = false; + m_flRaiseTime = gpGlobals->curtime + 0.5f; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHLCombatWeapon::Deploy( void ) +{ + // If we should be lowered, deploy in the lowered position + // We have to ask the player if the last time it checked, the weapon was lowered + if ( GetOwner() && GetOwner()->IsPlayer() ) + { + CHL2_Player *pPlayer = assert_cast( GetOwner() ); + if ( pPlayer->IsWeaponLowered() ) + { + if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) != ACTIVITY_NOT_AVAILABLE ) + { + if ( DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_VM_IDLE_LOWERED, (char*)GetAnimPrefix() ) ) + { + m_bLowered = true; + + // Stomp the next attack time to fix the fact that the lower idles are long + pPlayer->SetNextAttack( gpGlobals->curtime + 1.0 ); + m_flNextPrimaryAttack = gpGlobals->curtime + 1.0; + m_flNextSecondaryAttack = gpGlobals->curtime + 1.0; + return true; + } + } + } + } + + m_bLowered = false; + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHLCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( BaseClass::Holster( pSwitchingTo ) ) + { + m_flHolsterTime = gpGlobals->curtime; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHLCombatWeapon::WeaponShouldBeLowered( void ) +{ + // Can't be in the middle of another animation + if ( GetIdealActivity() != ACT_VM_IDLE_LOWERED && GetIdealActivity() != ACT_VM_IDLE && + GetIdealActivity() != ACT_VM_IDLE_TO_LOWERED && GetIdealActivity() != ACT_VM_LOWERED_TO_IDLE ) + return false; + + if ( m_bLowered ) + return true; + +#if !defined( CLIENT_DLL ) + + if ( GlobalEntity_GetState( "friendly_encounter" ) == GLOBAL_ON ) + return true; + +#endif + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Allows the weapon to choose proper weapon idle animation +//----------------------------------------------------------------------------- +void CBaseHLCombatWeapon::WeaponIdle( void ) +{ + //See if we should idle high or low + if ( WeaponShouldBeLowered() ) + { + // Move to lowered position if we're not there yet + if ( GetActivity() != ACT_VM_IDLE_LOWERED && GetActivity() != ACT_VM_IDLE_TO_LOWERED + && GetActivity() != ACT_TRANSITION ) + { + SendWeaponAnim( ACT_VM_IDLE_LOWERED ); + } + else if ( HasWeaponIdleTimeElapsed() ) + { + // Keep idling low + SendWeaponAnim( ACT_VM_IDLE_LOWERED ); + } + } + else + { + // See if we need to raise immediately + if ( m_flRaiseTime < gpGlobals->curtime && GetActivity() == ACT_VM_IDLE_LOWERED ) + { + SendWeaponAnim( ACT_VM_IDLE ); + } + else if ( HasWeaponIdleTimeElapsed() ) + { + SendWeaponAnim( ACT_VM_IDLE ); + } + } +} + +float g_lateralBob; +float g_verticalBob; + +#if defined( CLIENT_DLL ) && !defined( HL2MP ) + +#define HL2_BOB_CYCLE_MIN 1.0f +#define HL2_BOB_CYCLE_MAX 0.45f +#define HL2_BOB 0.002f +#define HL2_BOB_UP 0.5f + + +static ConVar cl_bobcycle( "cl_bobcycle","0.8" ); +static ConVar cl_bob( "cl_bob","0.002" ); +static ConVar cl_bobup( "cl_bobup","0.5" ); + +// Register these cvars if needed for easy tweaking +static ConVar v_iyaw_cycle( "v_iyaw_cycle", "2"/*, FCVAR_UNREGISTERED*/ ); +static ConVar v_iroll_cycle( "v_iroll_cycle", "0.5"/*, FCVAR_UNREGISTERED*/ ); +static ConVar v_ipitch_cycle( "v_ipitch_cycle", "1"/*, FCVAR_UNREGISTERED*/ ); +static ConVar v_iyaw_level( "v_iyaw_level", "0.3"/*, FCVAR_UNREGISTERED*/ ); +static ConVar v_iroll_level( "v_iroll_level", "0.1"/*, FCVAR_UNREGISTERED*/ ); +static ConVar v_ipitch_level( "v_ipitch_level", "0.3"/*, FCVAR_UNREGISTERED*/ ); + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CBaseHLCombatWeapon::CalcViewmodelBob( void ) +{ + static float bobtime; + static float lastbobtime; + float cycle; + + CBasePlayer *player = ToBasePlayer( GetOwner() ); + //Assert( player ); + + //NOTENOTE: For now, let this cycle continue when in the air, because it snaps badly without it + + if ( ( !gpGlobals->frametime ) || ( player == NULL ) ) + { + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f;// just use old value + } + + //Find the speed of the player + float speed = player->GetLocalVelocity().Length2D(); + + //FIXME: This maximum speed value must come from the server. + // MaxSpeed() is not sufficient for dealing with sprinting - jdw + + speed = clamp( speed, -320, 320 ); + + float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f ); + + bobtime += ( gpGlobals->curtime - lastbobtime ) * bob_offset; + lastbobtime = gpGlobals->curtime; + + //Calculate the vertical bob + cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX)*HL2_BOB_CYCLE_MAX; + cycle /= HL2_BOB_CYCLE_MAX; + + if ( cycle < HL2_BOB_UP ) + { + cycle = M_PI * cycle / HL2_BOB_UP; + } + else + { + cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); + } + + g_verticalBob = speed*0.005f; + g_verticalBob = g_verticalBob*0.3 + g_verticalBob*0.7*sin(cycle); + + g_verticalBob = clamp( g_verticalBob, -7.0f, 4.0f ); + + //Calculate the lateral bob + cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX*2)*HL2_BOB_CYCLE_MAX*2; + cycle /= HL2_BOB_CYCLE_MAX*2; + + if ( cycle < HL2_BOB_UP ) + { + cycle = M_PI * cycle / HL2_BOB_UP; + } + else + { + cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); + } + + g_lateralBob = speed*0.005f; + g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); + g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); + + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &angles - +// viewmodelindex - +//----------------------------------------------------------------------------- +void CBaseHLCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) +{ + Vector forward, right; + AngleVectors( angles, &forward, &right, NULL ); + + CalcViewmodelBob(); + + // Apply bob, but scaled down to 40% + VectorMA( origin, g_verticalBob * 0.1f, forward, origin ); + + // Z bob a bit more + origin[2] += g_verticalBob * 0.1f; + + // bob the angles + angles[ ROLL ] += g_verticalBob * 0.5f; + angles[ PITCH ] -= g_verticalBob * 0.4f; + + angles[ YAW ] -= g_lateralBob * 0.3f; + + VectorMA( origin, g_lateralBob * 0.8f, right, origin ); +} + +//----------------------------------------------------------------------------- +Vector CBaseHLCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) +{ + return BaseClass::GetBulletSpread( proficiency ); +} + +//----------------------------------------------------------------------------- +float CBaseHLCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) +{ + return BaseClass::GetSpreadBias( proficiency ); +} +//----------------------------------------------------------------------------- + +const WeaponProficiencyInfo_t *CBaseHLCombatWeapon::GetProficiencyValues() +{ + return NULL; +} + +#else + +// Server stubs +float CBaseHLCombatWeapon::CalcViewmodelBob( void ) +{ + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &angles - +// viewmodelindex - +//----------------------------------------------------------------------------- +void CBaseHLCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) +{ +} + + +//----------------------------------------------------------------------------- +Vector CBaseHLCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) +{ + Vector baseSpread = BaseClass::GetBulletSpread( proficiency ); + + const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); + float flModifier = (pProficiencyValues)[ proficiency ].spreadscale; + return ( baseSpread * flModifier ); +} + +//----------------------------------------------------------------------------- +float CBaseHLCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) +{ + const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); + return (pProficiencyValues)[ proficiency ].bias; +} + +//----------------------------------------------------------------------------- +const WeaponProficiencyInfo_t *CBaseHLCombatWeapon::GetProficiencyValues() +{ + return GetDefaultProficiencyValues(); +} + +//----------------------------------------------------------------------------- +const WeaponProficiencyInfo_t *CBaseHLCombatWeapon::GetDefaultProficiencyValues() +{ + // Weapon proficiency table. Keep this in sync with WeaponProficiency_t enum in the header!! + static WeaponProficiencyInfo_t g_BaseWeaponProficiencyTable[] = + { + { 2.50, 1.0 }, + { 2.00, 1.0 }, + { 1.50, 1.0 }, + { 1.25, 1.0 }, + { 1.00, 1.0 }, + }; + + COMPILE_TIME_ASSERT( ARRAYSIZE(g_BaseWeaponProficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); + + return g_BaseWeaponProficiencyTable; +} + +#endif diff --git a/game_shared/hl2/hl2_gamerules.cpp b/game_shared/hl2/hl2_gamerules.cpp index b8ed75f6..40a5cca2 100644 --- a/game_shared/hl2/hl2_gamerules.cpp +++ b/game_shared/hl2/hl2_gamerules.cpp @@ -1696,7 +1696,7 @@ CAmmoDef *GetAmmoDef() def.AddAmmoType("Thumper", DMG_SONIC, TRACER_NONE, 10, 10, 2, 0, 0 ); def.AddAmmoType("Gravity", DMG_CLUB, TRACER_NONE, 0, 0, 8, 0, 0 ); // def.AddAmmoType("Extinguisher", DMG_BURN, TRACER_NONE, 0, 0, 100, 0, 0 ); - def.AddAmmoType("Battery", DMG_CLUB, TRACER_NONE, NULL, NULL, NULL, 0, 0 ); + def.AddAmmoType("Battery", DMG_CLUB, TRACER_NONE, 0, 0, 0, 0, 0 ); def.AddAmmoType("GaussEnergy", DMG_SHOCK, TRACER_NONE, "sk_jeep_gauss_damage", "sk_jeep_gauss_damage", "sk_max_gauss_round", BULLET_IMPULSE(650, 8000), 0 ); // hit like a 10kg weight at 400 in/s def.AddAmmoType("CombineCannon", DMG_BULLET, TRACER_LINE, "sk_npc_dmg_gunship_to_plr", "sk_npc_dmg_gunship", NULL, 1.5 * 750 * 12, 0 ); // hit like a 1.5kg weight at 750 ft/s def.AddAmmoType("AirboatGun", DMG_AIRBOAT, TRACER_LINE, "sk_plr_dmg_airboat", "sk_npc_dmg_airboat", NULL, BULLET_IMPULSE(10, 600), 0 ); @@ -1707,7 +1707,7 @@ CAmmoDef *GetAmmoDef() def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, "sk_plr_dmg_grenade", "sk_npc_dmg_grenade", "sk_max_grenade", 0, 0); #ifdef HL2_EPISODIC def.AddAmmoType("Hopwire", DMG_BLAST, TRACER_NONE, "sk_plr_dmg_grenade", "sk_npc_dmg_grenade", "sk_max_hopwire", 0, 0); - def.AddAmmoType("CombineHeavyCannon", DMG_BULLET, TRACER_LINE, 40, 40, NULL, 1.5 * 750 * 12, AMMO_FORCE_DROP_IF_CARRIED ); // hit like a 100 kg weight at 750 ft/s + def.AddAmmoType("CombineHeavyCannon", DMG_BULLET, TRACER_LINE, 40, 40, 0, 1.5 * 750 * 12, AMMO_FORCE_DROP_IF_CARRIED ); // hit like a 100 kg weight at 750 ft/s #endif // HL2_EPISODIC } diff --git a/game_shared/hl2/hl2_usermessages.cpp b/game_shared/hl2/hl2_usermessages.cpp index e9f2aa19..325566d2 100644 --- a/game_shared/hl2/hl2_usermessages.cpp +++ b/game_shared/hl2/hl2_usermessages.cpp @@ -1,42 +1,42 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" -#include "usermessages.h" -#include "shake.h" -#include "voice_gamemgr.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -void RegisterUserMessages( void ) -{ - usermessages->Register( "Geiger", 1 ); - if ( !IsXbox() ) - usermessages->Register( "Train", 1 ); - usermessages->Register( "HudText", -1 ); - usermessages->Register( "SayText", -1 ); - usermessages->Register( "TextMsg", -1 ); - usermessages->Register( "HudMsg", -1 ); - usermessages->Register( "ResetHUD", 1); // called every respawn - usermessages->Register( "GameTitle", 0 ); - usermessages->Register( "ItemPickup", -1 ); - usermessages->Register( "ShowMenu", -1 ); - usermessages->Register( "Shake", 13 ); - usermessages->Register( "Fade", 10 ); - usermessages->Register( "VGUIMenu", -1 ); // Show VGUI menu - usermessages->Register( "XBoxRumble", 3 ); // Send a rumble to XBox controller - usermessages->Register( "Battery", 2 ); - usermessages->Register( "Damage", 18 ); // BUG: floats are sent for coords, no variable bitfields in hud & fixed size Msg - usermessages->Register( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 + 1 ); - usermessages->Register( "RequestState", 0 ); - usermessages->Register( "CloseCaption", ( !IsXbox() ) ? 7 : -1 ); // Show a caption (by string id number)(duration in 10th of a second) - usermessages->Register( "HintText", -1 ); // Displays hint text display - usermessages->Register( "SquadMemberDied", 0 ); - usermessages->Register( "AmmoDenied", 2 ); - usermessages->Register( "CreditsMsg", 1 ); - usermessages->Register( "LogoTimeMsg", 4 ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "usermessages.h" +#include "shake.h" +#include "voice_gamemgr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +void RegisterUserMessages( void ) +{ + usermessages->Register( "Geiger", 1 ); + if ( !IsXbox() ) + usermessages->Register( "Train", 1 ); + usermessages->Register( "HudText", -1 ); + usermessages->Register( "SayText", -1 ); + usermessages->Register( "TextMsg", -1 ); + usermessages->Register( "HudMsg", -1 ); + usermessages->Register( "ResetHUD", 1); // called every respawn + usermessages->Register( "GameTitle", 0 ); + usermessages->Register( "ItemPickup", -1 ); + usermessages->Register( "ShowMenu", -1 ); + usermessages->Register( "Shake", 13 ); + usermessages->Register( "Fade", 10 ); + usermessages->Register( "VGUIMenu", -1 ); // Show VGUI menu + usermessages->Register( "XBoxRumble", 3 ); // Send a rumble to XBox controller + usermessages->Register( "Battery", 2 ); + usermessages->Register( "Damage", 18 ); // BUG: floats are sent for coords, no variable bitfields in hud & fixed size Msg + usermessages->Register( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 + 1 ); + usermessages->Register( "RequestState", 0 ); + usermessages->Register( "CloseCaption", ( !IsXbox() ) ? 7 : -1 ); // Show a caption (by string id number)(duration in 10th of a second) + usermessages->Register( "HintText", -1 ); // Displays hint text display + usermessages->Register( "SquadMemberDied", 0 ); + usermessages->Register( "AmmoDenied", 2 ); + usermessages->Register( "CreditsMsg", 1 ); + usermessages->Register( "LogoTimeMsg", 4 ); +} diff --git a/game_shared/hl2/survival_gamerules.cpp b/game_shared/hl2/survival_gamerules.cpp index 88fd3dcd..bca8709c 100644 --- a/game_shared/hl2/survival_gamerules.cpp +++ b/game_shared/hl2/survival_gamerules.cpp @@ -1,272 +1,272 @@ -#include "cbase.h" - -#ifdef HL2_EPISODIC - -#include "hl2_gamerules.h" -#include "ammodef.h" -#include "hl2_shareddefs.h" -#include "filesystem.h" -#include - -#ifdef CLIENT_DLL - -#else -#include "player.h" -#include "game.h" -#include "gamerules.h" -#include "teamplay_gamerules.h" -#include "hl2_player.h" -#include "voice_gamemgr.h" -#include "globalstate.h" -#include "ai_basenpc.h" -#include "weapon_physcannon.h" -#include "ammodef.h" -#endif - -#ifdef CLIENT_DLL -#define CHalfLife2Survival C_HalfLife2Survival -#define CHalfLife2SurvivalProxy C_HalfLife2SurvivalProxy -#endif - -ConVar gamerules_survival( "gamerules_survival", "0", FCVAR_REPLICATED ); - -class CHalfLife2SurvivalProxy : public CGameRulesProxy -{ -public: - DECLARE_CLASS( CHalfLife2SurvivalProxy, CGameRulesProxy ); - DECLARE_NETWORKCLASS(); -}; - -class CSurvivalAmmo -{ -public: - - char m_szAmmoName[256]; - int m_iAmount; -}; - -class CSurvivalSettings -{ -public: - - CSurvivalSettings(); - - CUtlVector > m_Loadout; - int m_iSpawnHealth; - string_t m_szPickups; - CUtlVector m_Ammo; -}; - -CSurvivalSettings::CSurvivalSettings() -{ - m_iSpawnHealth = 100; -} - -class CHalfLife2Survival : public CHalfLife2 -{ -public: - DECLARE_CLASS( CHalfLife2Survival, CHalfLife2 ); - -#ifdef CLIENT_DLL - - DECLARE_CLIENTCLASS_NOBASE(); // This makes datatables able to access our private vars. - -#else - - DECLARE_SERVERCLASS_NOBASE(); // This makes datatables able to access our private vars. - - CHalfLife2Survival(); - virtual ~CHalfLife2Survival() {} - - virtual void Think( void ); - virtual void PlayerSpawn( CBasePlayer *pPlayer ); - virtual bool IsAllowedToSpawn( CBaseEntity *pEntity ); - virtual void CreateStandardEntities(); - - void ReadSurvivalScriptFile( void ); - void ParseSurvivalSettings( KeyValues *pSubKey ); - void ParseSurvivalAmmo( KeyValues *pSubKey ); - -private: - bool m_bActive; - CSurvivalSettings m_SurvivalSettings; -#endif - -}; - -//----------------------------------------------------------------------------- -// Gets us at the Half-Life 2 game rules -//----------------------------------------------------------------------------- -inline CHalfLife2Survival* HL2SurvivalGameRules() -{ - return static_cast(g_pGameRules); -} - -REGISTER_GAMERULES_CLASS( CHalfLife2Survival ); - -BEGIN_NETWORK_TABLE_NOBASE( CHalfLife2Survival, DT_HL2SurvivalGameRules ) -END_NETWORK_TABLE() - - -LINK_ENTITY_TO_CLASS( hl2_survival_gamerules, CHalfLife2SurvivalProxy ); -IMPLEMENT_NETWORKCLASS_ALIASED( HalfLife2SurvivalProxy, DT_HalfLife2SurvivalProxy ) - -#ifdef CLIENT_DLL - void RecvProxy_HL2SurvivalGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) - { - CHalfLife2Survival *pRules = HL2SurvivalGameRules(); - Assert( pRules ); - *pOut = pRules; - } - - BEGIN_RECV_TABLE( CHalfLife2SurvivalProxy, DT_HalfLife2SurvivalProxy ) - RecvPropDataTable( "hl2_survival_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_HL2SurvivalGameRules ), RecvProxy_HL2SurvivalGameRules ) - END_RECV_TABLE() - #else - void* SendProxy_HL2SurvivalGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) - { - CHalfLife2Survival *pRules = HL2SurvivalGameRules(); - Assert( pRules ); - pRecipients->SetAllRecipients(); - return pRules; - } - - BEGIN_SEND_TABLE( CHalfLife2SurvivalProxy, DT_HalfLife2SurvivalProxy ) - SendPropDataTable( "hl2_survival_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_HL2SurvivalGameRules ), SendProxy_HL2SurvivalGameRules ) - END_SEND_TABLE() -#endif - -#ifndef CLIENT_DLL - -CHalfLife2Survival::CHalfLife2Survival() -{ - m_bActive = false; -} - -void CHalfLife2Survival::Think( void ) -{ - -} - -bool CHalfLife2Survival::IsAllowedToSpawn( CBaseEntity *pEntity ) -{ - if ( !m_bActive ) - return BaseClass::IsAllowedToSpawn( pEntity ); - - const char *pPickups = STRING( m_SurvivalSettings.m_szPickups ); - if ( !pPickups ) - return false; - - if ( Q_stristr( pPickups, "everything" ) ) - return true; - - if ( Q_stristr( pPickups, pEntity->GetClassname() ) || Q_stristr( pPickups, STRING( pEntity->GetEntityName() ) ) ) - return true; - - return false; -} - -void CHalfLife2Survival::PlayerSpawn( CBasePlayer *pPlayer ) -{ - BaseClass::PlayerSpawn( pPlayer ); - - if ( !m_bActive ) - return; - - pPlayer->EquipSuit(); - pPlayer->SetHealth( m_SurvivalSettings.m_iSpawnHealth ); - - for ( int i = 0; i < m_SurvivalSettings.m_Loadout.Count(); ++i ) - { - pPlayer->GiveNamedItem( m_SurvivalSettings.m_Loadout[i] ); - } - - for ( int i = 0; i < m_SurvivalSettings.m_Ammo.Count(); ++i ) - { - pPlayer->CBasePlayer::GiveAmmo( m_SurvivalSettings.m_Ammo[i].m_iAmount , m_SurvivalSettings.m_Ammo[i].m_szAmmoName ); - } -} - -void CHalfLife2Survival::ParseSurvivalSettings( KeyValues *pSubKey ) -{ - if ( pSubKey == NULL ) - return; - - m_SurvivalSettings.m_szPickups = NULL_STRING; - m_SurvivalSettings.m_iSpawnHealth = 100; - - KeyValues *pTestKey = pSubKey->GetFirstSubKey(); - - while ( pTestKey ) - { - if ( !stricmp( pTestKey->GetName(), "weapons" ) ) - { - const char *pLoadout = pTestKey->GetString(); - Q_SplitString( pLoadout, ";", m_SurvivalSettings.m_Loadout ); - } - else if ( !stricmp( pTestKey->GetName(), "spawnhealth" ) ) - { - m_SurvivalSettings.m_iSpawnHealth = pTestKey->GetInt( NULL, 100 ); - } - else if ( !stricmp( pTestKey->GetName(), "allowedpickups" ) ) - { - m_SurvivalSettings.m_szPickups = MAKE_STRING( pTestKey->GetString() ); - } - - pTestKey = pTestKey->GetNextKey(); - } -} - -void CHalfLife2Survival::ParseSurvivalAmmo( KeyValues *pSubKey ) -{ - if ( pSubKey ) - { - KeyValues *pAmmoKey = pSubKey->GetFirstSubKey(); - - while ( pAmmoKey ) - { - CSurvivalAmmo ammo; - - Q_strcpy( ammo.m_szAmmoName, pAmmoKey->GetName() ); - ammo.m_iAmount = pAmmoKey->GetInt(); - - m_SurvivalSettings.m_Ammo.AddToTail( ammo ); - - pAmmoKey = pAmmoKey->GetNextKey(); - } - } -} - -void CHalfLife2Survival::ReadSurvivalScriptFile( void ) -{ - char szFullName[512]; - Q_snprintf( szFullName, sizeof( szFullName ), "maps/%s_survival.txt", gpGlobals->mapname ); - - KeyValues *pkvFile = new KeyValues( "Survival" ); - if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) ) - { - ParseSurvivalSettings( pkvFile->FindKey( "settings" ) ); - ParseSurvivalAmmo( pkvFile->FindKey( "ammo" ) ); - - CUtlVector entities; - UTIL_LoadAndSpawnEntitiesFromScript( entities, szFullName, "Survival", true ); - - // It's important to turn on survival mode after we create all the entities - // in the script, so that we don't remove them if they violate survival rules. - // i.e. we want the player to start with a shotgun, but prevent all future shotguns from spawning. - m_bActive = true; - } - else - { - m_bActive = false; - } -} - -void CHalfLife2Survival::CreateStandardEntities( void ) -{ - ReadSurvivalScriptFile(); -} - -#endif - -#endif \ No newline at end of file +#include "cbase.h" + +#ifdef HL2_EPISODIC + +#include "hl2_gamerules.h" +#include "ammodef.h" +#include "hl2_shareddefs.h" +#include "filesystem.h" +#include + +#ifdef CLIENT_DLL + +#else +#include "player.h" +#include "game.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "hl2_player.h" +#include "voice_gamemgr.h" +#include "globalstate.h" +#include "ai_basenpc.h" +#include "weapon_physcannon.h" +#include "ammodef.h" +#endif + +#ifdef CLIENT_DLL +#define CHalfLife2Survival C_HalfLife2Survival +#define CHalfLife2SurvivalProxy C_HalfLife2SurvivalProxy +#endif + +ConVar gamerules_survival( "gamerules_survival", "0", FCVAR_REPLICATED ); + +class CHalfLife2SurvivalProxy : public CGameRulesProxy +{ +public: + DECLARE_CLASS( CHalfLife2SurvivalProxy, CGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class CSurvivalAmmo +{ +public: + + char m_szAmmoName[256]; + int m_iAmount; +}; + +class CSurvivalSettings +{ +public: + + CSurvivalSettings(); + + CUtlVector > m_Loadout; + int m_iSpawnHealth; + string_t m_szPickups; + CUtlVector m_Ammo; +}; + +CSurvivalSettings::CSurvivalSettings() +{ + m_iSpawnHealth = 100; +} + +class CHalfLife2Survival : public CHalfLife2 +{ +public: + DECLARE_CLASS( CHalfLife2Survival, CHalfLife2 ); + +#ifdef CLIENT_DLL + + DECLARE_CLIENTCLASS_NOBASE(); // This makes datatables able to access our private vars. + +#else + + DECLARE_SERVERCLASS_NOBASE(); // This makes datatables able to access our private vars. + + CHalfLife2Survival(); + virtual ~CHalfLife2Survival() {} + + virtual void Think( void ); + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual bool IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual void CreateStandardEntities(); + + void ReadSurvivalScriptFile( void ); + void ParseSurvivalSettings( KeyValues *pSubKey ); + void ParseSurvivalAmmo( KeyValues *pSubKey ); + +private: + bool m_bActive; + CSurvivalSettings m_SurvivalSettings; +#endif + +}; + +//----------------------------------------------------------------------------- +// Gets us at the Half-Life 2 game rules +//----------------------------------------------------------------------------- +inline CHalfLife2Survival* HL2SurvivalGameRules() +{ + return static_cast(g_pGameRules); +} + +REGISTER_GAMERULES_CLASS( CHalfLife2Survival ); + +BEGIN_NETWORK_TABLE_NOBASE( CHalfLife2Survival, DT_HL2SurvivalGameRules ) +END_NETWORK_TABLE() + + +LINK_ENTITY_TO_CLASS( hl2_survival_gamerules, CHalfLife2SurvivalProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( HalfLife2SurvivalProxy, DT_HalfLife2SurvivalProxy ) + +#ifdef CLIENT_DLL + void RecvProxy_HL2SurvivalGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) + { + CHalfLife2Survival *pRules = HL2SurvivalGameRules(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CHalfLife2SurvivalProxy, DT_HalfLife2SurvivalProxy ) + RecvPropDataTable( "hl2_survival_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_HL2SurvivalGameRules ), RecvProxy_HL2SurvivalGameRules ) + END_RECV_TABLE() + #else + void* SendProxy_HL2SurvivalGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) + { + CHalfLife2Survival *pRules = HL2SurvivalGameRules(); + Assert( pRules ); + pRecipients->SetAllRecipients(); + return pRules; + } + + BEGIN_SEND_TABLE( CHalfLife2SurvivalProxy, DT_HalfLife2SurvivalProxy ) + SendPropDataTable( "hl2_survival_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_HL2SurvivalGameRules ), SendProxy_HL2SurvivalGameRules ) + END_SEND_TABLE() +#endif + +#ifndef CLIENT_DLL + +CHalfLife2Survival::CHalfLife2Survival() +{ + m_bActive = false; +} + +void CHalfLife2Survival::Think( void ) +{ + +} + +bool CHalfLife2Survival::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ + if ( !m_bActive ) + return BaseClass::IsAllowedToSpawn( pEntity ); + + const char *pPickups = STRING( m_SurvivalSettings.m_szPickups ); + if ( !pPickups ) + return false; + + if ( Q_stristr( pPickups, "everything" ) ) + return true; + + if ( Q_stristr( pPickups, pEntity->GetClassname() ) || Q_stristr( pPickups, STRING( pEntity->GetEntityName() ) ) ) + return true; + + return false; +} + +void CHalfLife2Survival::PlayerSpawn( CBasePlayer *pPlayer ) +{ + BaseClass::PlayerSpawn( pPlayer ); + + if ( !m_bActive ) + return; + + pPlayer->EquipSuit(); + pPlayer->SetHealth( m_SurvivalSettings.m_iSpawnHealth ); + + for ( int i = 0; i < m_SurvivalSettings.m_Loadout.Count(); ++i ) + { + pPlayer->GiveNamedItem( m_SurvivalSettings.m_Loadout[i] ); + } + + for ( int i = 0; i < m_SurvivalSettings.m_Ammo.Count(); ++i ) + { + pPlayer->CBasePlayer::GiveAmmo( m_SurvivalSettings.m_Ammo[i].m_iAmount , m_SurvivalSettings.m_Ammo[i].m_szAmmoName ); + } +} + +void CHalfLife2Survival::ParseSurvivalSettings( KeyValues *pSubKey ) +{ + if ( pSubKey == NULL ) + return; + + m_SurvivalSettings.m_szPickups = NULL_STRING; + m_SurvivalSettings.m_iSpawnHealth = 100; + + KeyValues *pTestKey = pSubKey->GetFirstSubKey(); + + while ( pTestKey ) + { + if ( !stricmp( pTestKey->GetName(), "weapons" ) ) + { + const char *pLoadout = pTestKey->GetString(); + Q_SplitString( pLoadout, ";", m_SurvivalSettings.m_Loadout ); + } + else if ( !stricmp( pTestKey->GetName(), "spawnhealth" ) ) + { + m_SurvivalSettings.m_iSpawnHealth = pTestKey->GetInt( 0, 100 ); + } + else if ( !stricmp( pTestKey->GetName(), "allowedpickups" ) ) + { + m_SurvivalSettings.m_szPickups = MAKE_STRING( pTestKey->GetString() ); + } + + pTestKey = pTestKey->GetNextKey(); + } +} + +void CHalfLife2Survival::ParseSurvivalAmmo( KeyValues *pSubKey ) +{ + if ( pSubKey ) + { + KeyValues *pAmmoKey = pSubKey->GetFirstSubKey(); + + while ( pAmmoKey ) + { + CSurvivalAmmo ammo; + + Q_strcpy( ammo.m_szAmmoName, pAmmoKey->GetName() ); + ammo.m_iAmount = pAmmoKey->GetInt(); + + m_SurvivalSettings.m_Ammo.AddToTail( ammo ); + + pAmmoKey = pAmmoKey->GetNextKey(); + } + } +} + +void CHalfLife2Survival::ReadSurvivalScriptFile( void ) +{ + char szFullName[512]; + Q_snprintf( szFullName, sizeof( szFullName ), "maps/%s_survival.txt", STRING(gpGlobals->mapname) ); + + KeyValues *pkvFile = new KeyValues( "Survival" ); + if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) ) + { + ParseSurvivalSettings( pkvFile->FindKey( "settings" ) ); + ParseSurvivalAmmo( pkvFile->FindKey( "ammo" ) ); + + CUtlVector entities; + UTIL_LoadAndSpawnEntitiesFromScript( entities, szFullName, "Survival", true ); + + // It's important to turn on survival mode after we create all the entities + // in the script, so that we don't remove them if they violate survival rules. + // i.e. we want the player to start with a shotgun, but prevent all future shotguns from spawning. + m_bActive = true; + } + else + { + m_bActive = false; + } +} + +void CHalfLife2Survival::CreateStandardEntities( void ) +{ + ReadSurvivalScriptFile(); +} + +#endif + +#endif diff --git a/game_shared/hl2mp/hl2mp_gamerules.cpp b/game_shared/hl2mp/hl2mp_gamerules.cpp index cb3797e3..4f403718 100644 --- a/game_shared/hl2mp/hl2mp_gamerules.cpp +++ b/game_shared/hl2mp/hl2mp_gamerules.cpp @@ -1,1237 +1,1237 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// -#include "cbase.h" -#include "hl2mp_gamerules.h" -#include "viewport_panel_names.h" -#include "gameeventdefs.h" -#include -#include "ammodef.h" - -#ifdef CLIENT_DLL - #include "c_hl2mp_player.h" -#else - - #include "eventqueue.h" - #include "player.h" - #include "gamerules.h" - #include "game.h" - #include "items.h" - #include "entitylist.h" - #include "mapentities.h" - #include "in_buttons.h" - #include - #include "voice_gamemgr.h" - #include "iscorer.h" - #include "hl2mp_player.h" - #include "weapon_hl2mpbasehlmpcombatweapon.h" - #include "team.h" - #include "voice_gamemgr.h" - #include "hl2mp_gameinterface.h" - #include "hl2mp_cvars.h" - -#ifdef DEBUG - #include "hl2mp_bot_temp.h" -#endif - -extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); - - -ConVar sv_hl2mp_weapon_respawn_time( "sv_hl2mp_weapon_respawn_time", "20", FCVAR_GAMEDLL | FCVAR_NOTIFY ); -ConVar sv_hl2mp_item_respawn_time( "sv_hl2mp_item_respawn_time", "30", FCVAR_GAMEDLL | FCVAR_NOTIFY ); -ConVar mp_restartgame( "mp_restartgame", "0", 0, "If non-zero, game will restart in the specified number of seconds" ); -ConVar sv_report_client_settings("sv_report_client_settings", "0", FCVAR_GAMEDLL | FCVAR_NOTIFY ); - -extern ConVar mp_chattime; - -extern CBaseEntity *g_pLastCombineSpawn; -extern CBaseEntity *g_pLastRebelSpawn; - -#define WEAPON_MAX_DISTANCE_FROM_SPAWN 64 - -#endif - - -REGISTER_GAMERULES_CLASS( CHL2MPRules ); - -BEGIN_NETWORK_TABLE_NOBASE( CHL2MPRules, DT_HL2MPRules ) - - #ifdef CLIENT_DLL - RecvPropBool( RECVINFO( m_bTeamPlayEnabled ) ), - #else - SendPropBool( SENDINFO( m_bTeamPlayEnabled ) ), - #endif - -END_NETWORK_TABLE() - - -LINK_ENTITY_TO_CLASS( hl2mp_gamerules, CHL2MPGameRulesProxy ); -IMPLEMENT_NETWORKCLASS_ALIASED( HL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) - -static HL2MPViewVectors g_HL2MPViewVectors( - Vector( 0, 0, 64 ), //m_vView - - Vector(-16, -16, 0 ), //m_vHullMin - Vector( 16, 16, 72 ), //m_vHullMax - - Vector(-16, -16, 0 ), //m_vDuckHullMin - Vector( 16, 16, 36 ), //m_vDuckHullMax - Vector( 0, 0, 28 ), //m_vDuckView - - Vector(-10, -10, -10 ), //m_vObsHullMin - Vector( 10, 10, 10 ), //m_vObsHullMax - - Vector( 0, 0, 14 ), //m_vDeadViewHeight - - Vector(-16, -16, 0 ), //m_vCrouchTraceMin - Vector( 16, 16, 60 ) //m_vCrouchTraceMax -); - -static const char *s_PreserveEnts[] = -{ - "ai_network", - "ai_hint", - "hl2mp_gamerules", - "team_manager", - "player_manager", - "env_soundscape", - "env_soundscape_proxy", - "env_soundscape_triggerable", - "env_sun", - "env_wind", - "env_fog_controller", - "func_brush", - "func_wall", - "func_buyzone", - "func_illusionary", - "infodecal", - "info_projecteddecal", - "info_node", - "info_target", - "info_node_hint", - "info_player_deathmatch", - "info_player_combine", - "info_player_rebel", - "info_map_parameters", - "keyframe_rope", - "move_rope", - "info_ladder", - "player", - "point_viewcontrol", - "scene_manager", - "shadow_control", - "sky_camera", - "soundent", - "trigger_soundscape", - "viewmodel", - "predicted_viewmodel", - "worldspawn", - "point_devshot_camera", - "", // END Marker -}; - - - -#ifdef CLIENT_DLL - void RecvProxy_HL2MPRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) - { - CHL2MPRules *pRules = HL2MPRules(); - Assert( pRules ); - *pOut = pRules; - } - - BEGIN_RECV_TABLE( CHL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) - RecvPropDataTable( "hl2mp_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_HL2MPRules ), RecvProxy_HL2MPRules ) - END_RECV_TABLE() -#else - void* SendProxy_HL2MPRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) - { - CHL2MPRules *pRules = HL2MPRules(); - Assert( pRules ); - return pRules; - } - - BEGIN_SEND_TABLE( CHL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) - SendPropDataTable( "hl2mp_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_HL2MPRules ), SendProxy_HL2MPRules ) - END_SEND_TABLE() -#endif - -#ifndef CLIENT_DLL - - class CVoiceGameMgrHelper : public IVoiceGameMgrHelper - { - public: - virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker ) - { - return ( pListener->GetTeamNumber() == pTalker->GetTeamNumber() ); - } - }; - CVoiceGameMgrHelper g_VoiceGameMgrHelper; - IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper; - -#endif - -// NOTE: the indices here must match TEAM_TERRORIST, TEAM_CT, TEAM_SPECTATOR, etc. -char *sTeamNames[] = -{ - "Unassigned", - "Spectator", - "Combine", - "Rebels", -}; - -CHL2MPRules::CHL2MPRules() -{ -#ifndef CLIENT_DLL - // Create the team managers - for ( int i = 0; i < ARRAYSIZE( sTeamNames ); i++ ) - { - CTeam *pTeam = static_cast(CreateEntityByName( "team_manager" )); - pTeam->Init( sTeamNames[i], i ); - - g_Teams.AddToTail( pTeam ); - } - - m_bTeamPlayEnabled = teamplay.GetBool(); - m_flIntermissionEndTime = 0.0f; - m_flGameStartTime = 0; - - m_hRespawnableItemsAndWeapons.RemoveAll(); - m_tmNextPeriodicThink = 0; - m_flRestartGameTime = 0; - m_bCompleteReset = false; - m_bHeardAllPlayersReady = false; - m_bAwaitingReadyRestart = false; - -#endif -} - -const CViewVectors* CHL2MPRules::GetViewVectors()const -{ - return &g_HL2MPViewVectors; -} - -const HL2MPViewVectors* CHL2MPRules::GetHL2MPViewVectors()const -{ - return &g_HL2MPViewVectors; -} - -CHL2MPRules::~CHL2MPRules( void ) -{ -#ifndef CLIENT_DLL - // Note, don't delete each team since they are in the gEntList and will - // automatically be deleted from there, instead. - g_Teams.Purge(); -#endif -} - -void CHL2MPRules::CreateStandardEntities( void ) -{ - -#ifndef CLIENT_DLL - // Create the entity that will send our data to the client. - - BaseClass::CreateStandardEntities(); - - g_pLastCombineSpawn = NULL; - g_pLastRebelSpawn = NULL; - -#ifdef _DEBUG - CBaseEntity *pEnt = -#endif - CBaseEntity::Create( "hl2mp_gamerules", vec3_origin, vec3_angle ); - Assert( pEnt ); -#endif -} - -//========================================================= -// FlWeaponRespawnTime - what is the time in the future -// at which this weapon may spawn? -//========================================================= -float CHL2MPRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ) -{ -#ifndef CLIENT_DLL - if ( weaponstay.GetInt() > 0 ) - { - // make sure it's only certain weapons - if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) - { - return 0; // weapon respawns almost instantly - } - } - - return sv_hl2mp_weapon_respawn_time.GetFloat(); -#endif - - return 0; // weapon respawns almost instantly -} - - -bool CHL2MPRules::IsIntermission( void ) -{ -#ifndef CLIENT_DLL - return m_flIntermissionEndTime > gpGlobals->curtime; -#endif - - return false; -} - -void CHL2MPRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) -{ -#ifndef CLIENT_DLL - if ( IsIntermission() ) - return; - BaseClass::PlayerKilled( pVictim, info ); -#endif -} - - -void CHL2MPRules::Think( void ) -{ - -#ifndef CLIENT_DLL - - CGameRules::Think(); - - if ( g_fGameOver ) // someone else quit the game already - { - // check to see if we should change levels now - if ( m_flIntermissionEndTime < gpGlobals->curtime ) - { - ChangeLevel(); // intermission is over - } - - return; - } - -// float flTimeLimit = mp_timelimit.GetFloat() * 60; - float flFragLimit = fraglimit.GetFloat(); - - if ( GetMapRemainingTime() < 0 ) - { - GoToIntermission(); - return; - } - - if ( flFragLimit ) - { - if( IsTeamplay() == true ) - { - CTeam *pCombine = g_Teams[TEAM_COMBINE]; - CTeam *pRebels = g_Teams[TEAM_REBELS]; - - if ( pCombine->GetScore() >= flFragLimit || pRebels->GetScore() >= flFragLimit ) - { - GoToIntermission(); - return; - } - } - else - { - // check if any player is over the frag limit - for ( int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if ( pPlayer && pPlayer->FragCount() >= flFragLimit ) - { - GoToIntermission(); - return; - } - } - } - } - - if ( gpGlobals->curtime > m_tmNextPeriodicThink ) - { - CheckAllPlayersReady(); - CheckRestartGame(); - m_tmNextPeriodicThink = gpGlobals->curtime + 1.0; - } - - if ( m_flRestartGameTime > 0.0f && m_flRestartGameTime <= gpGlobals->curtime ) - { - RestartGame(); - } - - if( m_bAwaitingReadyRestart && m_bHeardAllPlayersReady ) - { - UTIL_ClientPrintAll( HUD_PRINTCENTER, "All players ready. Game will restart in 5 seconds" ); - UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "All players ready. Game will restart in 5 seconds" ); - - m_flRestartGameTime = gpGlobals->curtime + 5; - m_bAwaitingReadyRestart = false; - } - - ManageObjectRelocation(); - -#endif -} - -void CHL2MPRules::GoToIntermission( void ) -{ -#ifndef CLIENT_DLL - if ( g_fGameOver ) - return; - - g_fGameOver = true; - - m_flIntermissionEndTime = gpGlobals->curtime + mp_chattime.GetInt(); - - for ( int i = 0; i < MAX_PLAYERS; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if ( !pPlayer ) - continue; - - pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD ); - pPlayer->AddFlag( FL_FROZEN ); - } -#endif - -} - -bool CHL2MPRules::CheckGameOver() -{ -#ifndef CLIENT_DLL - if ( g_fGameOver ) // someone else quit the game already - { - // check to see if we should change levels now - if ( m_flIntermissionEndTime < gpGlobals->curtime ) - { - ChangeLevel(); // intermission is over - } - - return true; - } -#endif - - return false; -} - -// when we are within this close to running out of entities, items -// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn -#define ENTITY_INTOLERANCE 100 - -//========================================================= -// FlWeaponRespawnTime - Returns 0 if the weapon can respawn -// now, otherwise it returns the time at which it can try -// to spawn again. -//========================================================= -float CHL2MPRules::FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ) -{ -#ifndef CLIENT_DLL - if ( pWeapon && (pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) - { - if ( gEntList.NumberOfEntities() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) - return 0; - - // we're past the entity tolerance level, so delay the respawn - return FlWeaponRespawnTime( pWeapon ); - } -#endif - return 0; -} - -//========================================================= -// VecWeaponRespawnSpot - where should this weapon spawn? -// Some game variations may choose to randomize spawn locations -//========================================================= -Vector CHL2MPRules::VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ) -{ -#ifndef CLIENT_DLL - CWeaponHL2MPBase *pHL2Weapon = dynamic_cast< CWeaponHL2MPBase*>( pWeapon ); - - if ( pHL2Weapon ) - { - return pHL2Weapon->GetOriginalSpawnOrigin(); - } -#endif - - return pWeapon->GetAbsOrigin(); -} - -#ifndef CLIENT_DLL - -CItem* IsManagedObjectAnItem( CBaseEntity *pObject ) -{ - return dynamic_cast< CItem*>( pObject ); -} - -CWeaponHL2MPBase* IsManagedObjectAWeapon( CBaseEntity *pObject ) -{ - return dynamic_cast< CWeaponHL2MPBase*>( pObject ); -} - -bool GetObjectsOriginalParameters( CBaseEntity *pObject, Vector &vOriginalOrigin, QAngle &vOriginalAngles ) -{ - if ( CItem *pItem = IsManagedObjectAnItem( pObject ) ) - { - if ( pItem->m_flNextResetCheckTime > gpGlobals->curtime ) - return false; - - vOriginalOrigin = pItem->GetOriginalSpawnOrigin(); - vOriginalAngles = pItem->GetOriginalSpawnAngles(); - - pItem->m_flNextResetCheckTime = gpGlobals->curtime + sv_hl2mp_item_respawn_time.GetFloat(); - return true; - } - else if ( CWeaponHL2MPBase *pWeapon = IsManagedObjectAWeapon( pObject )) - { - if ( pWeapon->m_flNextResetCheckTime > gpGlobals->curtime ) - return false; - - vOriginalOrigin = pWeapon->GetOriginalSpawnOrigin(); - vOriginalAngles = pWeapon->GetOriginalSpawnAngles(); - - pWeapon->m_flNextResetCheckTime = gpGlobals->curtime + sv_hl2mp_weapon_respawn_time.GetFloat(); - return true; - } - - return false; -} - -void CHL2MPRules::ManageObjectRelocation( void ) -{ - int iTotal = m_hRespawnableItemsAndWeapons.Count(); - - if ( iTotal > 0 ) - { - for ( int i = 0; i < iTotal; i++ ) - { - CBaseEntity *pObject = m_hRespawnableItemsAndWeapons[i].Get(); - - if ( pObject ) - { - Vector vSpawOrigin; - QAngle vSpawnAngles; - - if ( GetObjectsOriginalParameters( pObject, vSpawOrigin, vSpawnAngles ) == true ) - { - float flDistanceFromSpawn = (pObject->GetAbsOrigin() - vSpawOrigin ).Length(); - - if ( flDistanceFromSpawn > WEAPON_MAX_DISTANCE_FROM_SPAWN ) - { - bool shouldReset = false; - IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); - - if ( pPhysics ) - { - shouldReset = pPhysics->IsAsleep(); - } - else - { - shouldReset = (pObject->GetFlags() & FL_ONGROUND) ? true : false; - } - - if ( shouldReset ) - { - pObject->Teleport( &vSpawOrigin, &vSpawnAngles, NULL ); - pObject->EmitSound( "AlyxEmp.Charge" ); - - IPhysicsObject *pPhys = pObject->VPhysicsGetObject(); - - if ( pPhys ) - { - pPhys->Wake(); - } - } - } - } - } - } - } -} - -//========================================================= -//AddLevelDesignerPlacedWeapon -//========================================================= -void CHL2MPRules::AddLevelDesignerPlacedObject( CBaseEntity *pEntity ) -{ - if ( m_hRespawnableItemsAndWeapons.Find( pEntity ) == -1 ) - { - m_hRespawnableItemsAndWeapons.AddToTail( pEntity ); - } -} - -//========================================================= -//RemoveLevelDesignerPlacedWeapon -//========================================================= -void CHL2MPRules::RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ) -{ - if ( m_hRespawnableItemsAndWeapons.Find( pEntity ) != -1 ) - { - m_hRespawnableItemsAndWeapons.FindAndRemove( pEntity ); - } -} - -//========================================================= -// Where should this item respawn? -// Some game variations may choose to randomize spawn locations -//========================================================= -Vector CHL2MPRules::VecItemRespawnSpot( CItem *pItem ) -{ - return pItem->GetOriginalSpawnOrigin(); -} - -//========================================================= -// What angles should this item use to respawn? -//========================================================= -QAngle CHL2MPRules::VecItemRespawnAngles( CItem *pItem ) -{ - return pItem->GetOriginalSpawnAngles(); -} - -//========================================================= -// At what time in the future may this Item respawn? -//========================================================= -float CHL2MPRules::FlItemRespawnTime( CItem *pItem ) -{ - return sv_hl2mp_item_respawn_time.GetFloat(); -} - - -//========================================================= -// CanHaveWeapon - returns false if the player is not allowed -// to pick up this weapon -//========================================================= -bool CHL2MPRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ) -{ - if ( weaponstay.GetInt() > 0 ) - { - if ( pPlayer->Weapon_OwnsThisType( pItem->GetClassname(), pItem->GetSubType() ) ) - return false; - } - - return BaseClass::CanHavePlayerItem( pPlayer, pItem ); -} - -#endif - -//========================================================= -// WeaponShouldRespawn - any conditions inhibiting the -// respawning of this weapon? -//========================================================= -int CHL2MPRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) -{ -#ifndef CLIENT_DLL - if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) ) - { - return GR_WEAPON_RESPAWN_NO; - } -#endif - - return GR_WEAPON_RESPAWN_YES; -} - -//----------------------------------------------------------------------------- -// Purpose: Player has just left the game -//----------------------------------------------------------------------------- -void CHL2MPRules::ClientDisconnected( edict_t *pClient ) -{ -#ifndef CLIENT_DLL - // Msg( "CLIENT DISCONNECTED, REMOVING FROM TEAM.\n" ); - - CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); - if ( pPlayer ) - { - // Remove the player from his team - if ( pPlayer->GetTeam() ) - { - pPlayer->GetTeam()->RemovePlayer( pPlayer ); - } - } - - BaseClass::ClientDisconnected( pClient ); - -#endif -} - - -//========================================================= -// Deathnotice. -//========================================================= -void CHL2MPRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) -{ -#ifndef CLIENT_DLL - // Work out what killed the player, and send a message to all clients about it - const char *killer_weapon_name = "world"; // by default, the player is killed by the world - int killer_ID = 0; - - // Find the killer & the scorer - CBaseEntity *pInflictor = info.GetInflictor(); - CBaseEntity *pKiller = info.GetAttacker(); - CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor ); - - // Custom kill type? - if ( info.GetCustomKill() ) - { - killer_weapon_name = GetCustomKillString( info ); - if ( pScorer ) - { - killer_ID = pScorer->GetUserID(); - } - } - else - { - // Is the killer a client? - if ( pScorer ) - { - killer_ID = pScorer->GetUserID(); - - if ( pInflictor ) - { - if ( pInflictor == pScorer ) - { - // If the inflictor is the killer, then it must be their current weapon doing the damage - if ( pScorer->GetActiveWeapon() ) - { - killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname(); - } - } - else - { - killer_weapon_name = pInflictor->GetClassname(); // it's just that easy - } - } - } - else - { - killer_weapon_name = pInflictor->GetClassname(); - } - - // strip the NPC_* or weapon_* from the inflictor's classname - if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) - { - killer_weapon_name += 7; - } - else if ( strncmp( killer_weapon_name, "npc_", 4 ) == 0 ) - { - killer_weapon_name += 4; - } - else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) - { - killer_weapon_name += 5; - } - else if ( strstr( killer_weapon_name, "physics" ) ) - { - killer_weapon_name = "physics"; - } - - if ( strcmp( killer_weapon_name, "prop_combine_ball" ) == 0 ) - { - killer_weapon_name = "combine_ball"; - } - else if ( strcmp( killer_weapon_name, "grenade_ar2" ) == 0 ) - { - killer_weapon_name = "smg1_grenade"; - } - else if ( strcmp( killer_weapon_name, "satchel" ) == 0 || strcmp( killer_weapon_name, "tripmine" ) == 0) - { - killer_weapon_name = "slam"; - } - - - } - - IGameEvent *event = gameeventmanager->CreateEvent( "player_death" ); - if( event ) - { - event->SetInt("userid", pVictim->GetUserID() ); - event->SetInt("attacker", killer_ID ); - event->SetString("weapon", killer_weapon_name ); - event->SetInt( "priority", 7 ); - gameeventmanager->FireEvent( event ); - } -#endif - -} - -void CHL2MPRules::ClientSettingsChanged( CBasePlayer *pPlayer ) -{ -#ifndef CLIENT_DLL - - CHL2MP_Player *pHL2Player = ToHL2MPPlayer( pPlayer ); - - if ( pHL2Player == NULL ) - return; - - const char *pCurrentModel = modelinfo->GetModelName( pPlayer->GetModel() ); - const char *szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_playermodel" ); - - //If we're different. - if ( stricmp( szModelName, pCurrentModel ) ) - { - //Too soon, set the cvar back to what it was. - //Note: this will make this function be called again - //but since our models will match it'll just skip this whole dealio. - if ( pHL2Player->GetNextModelChangeTime() >= gpGlobals->curtime ) - { - char szReturnString[512]; - - Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pCurrentModel ); - engine->ClientCommand ( pHL2Player->edict(), szReturnString ); - - Q_snprintf( szReturnString, sizeof( szReturnString ), "Please wait %d more seconds before trying to switch.\n", (int)(pHL2Player->GetNextModelChangeTime() - gpGlobals->curtime) ); - ClientPrint( pHL2Player, HUD_PRINTTALK, szReturnString ); - return; - } - - if ( HL2MPRules()->IsTeamplay() == false ) - { - pHL2Player->SetPlayerModel(); - - const char *pszCurrentModelName = modelinfo->GetModelName( pHL2Player->GetModel() ); - - char szReturnString[128]; - Q_snprintf( szReturnString, sizeof( szReturnString ), "Your player model is: %s\n", pszCurrentModelName ); - - ClientPrint( pHL2Player, HUD_PRINTTALK, szReturnString ); - } - else - { - if ( Q_stristr( szModelName, "models/human") ) - { - pHL2Player->ChangeTeam( TEAM_REBELS ); - } - else - { - pHL2Player->ChangeTeam( TEAM_COMBINE ); - } - } - } - if ( sv_report_client_settings.GetInt() == 1 ) - { - UTIL_LogPrintf( "\"%s\" cl_cmdrate = \"%s\"\n", pHL2Player->GetPlayerName(), engine->GetClientConVarValue( pHL2Player->entindex(), "cl_cmdrate" )); - } - - BaseClass::ClientSettingsChanged( pPlayer ); -#endif - -} - -int CHL2MPRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) -{ -#ifndef CLIENT_DLL - // half life multiplay has a simple concept of Player Relationships. - // you are either on another player's team, or you are not. - if ( !pPlayer || !pTarget || !pTarget->IsPlayer() || IsTeamplay() == false ) - return GR_NOTTEAMMATE; - - if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) - { - return GR_TEAMMATE; - } -#endif - - return GR_NOTTEAMMATE; -} - -const char *CHL2MPRules::GetGameDescription( void ) -{ - if ( IsTeamplay() ) - return "Team Deathmatch"; - - return "Deathmatch"; -} - - -float CHL2MPRules::GetMapRemainingTime() -{ - // if timelimit is disabled, return 0 - if ( mp_timelimit.GetInt() <= 0 ) - return 0; - - // timelimit is in minutes - - float timeleft = (m_flGameStartTime + mp_timelimit.GetInt() * 60.0f ) - gpGlobals->curtime; - - return timeleft; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHL2MPRules::Precache( void ) -{ - CBaseEntity::PrecacheScriptSound( "AlyxEmp.Charge" ); -} - -bool CHL2MPRules::ShouldCollide( int collisionGroup0, int collisionGroup1 ) -{ - if ( collisionGroup0 > collisionGroup1 ) - { - // swap so that lowest is always first - swap(collisionGroup0,collisionGroup1); - } - - if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && - collisionGroup1 == COLLISION_GROUP_WEAPON ) - { - return false; - } - - return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 ); - -} - -bool CHL2MPRules::ClientCommand(const char *pcmd, CBaseEntity *pEdict ) -{ - -#ifndef CLIENT_DLL - if( BaseClass::ClientCommand(pcmd, pEdict) ) - return true; - - - CHL2MP_Player *pPlayer = (CHL2MP_Player *) pEdict; - - if ( pPlayer->ClientCommand( pcmd ) ) - return true; -#endif - - return false; -} - -// shared ammo definition -// JAY: Trying to make a more physical bullet response -#define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f) -#define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains)) - -// exaggerate all of the forces, but use real numbers to keep them consistent -#define BULLET_IMPULSE_EXAGGERATION 3.5 -// convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s -#define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION) - - -CAmmoDef *GetAmmoDef() -{ - static CAmmoDef def; - static bool bInitted = false; - - if ( !bInitted ) - { - bInitted = true; - - def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 60, BULLET_IMPULSE(200, 1225), 0 ); - def.AddAmmoType("AR2AltFire", DMG_DISSOLVE, TRACER_NONE, 0, 0, 3, 0, 0 ); - def.AddAmmoType("Pistol", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 150, BULLET_IMPULSE(200, 1225), 0 ); - def.AddAmmoType("SMG1", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 225, BULLET_IMPULSE(200, 1225), 0 ); - def.AddAmmoType("357", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 12, BULLET_IMPULSE(800, 5000), 0 ); - def.AddAmmoType("XBowBolt", DMG_BULLET, TRACER_LINE, 0, 0, 10, BULLET_IMPULSE(800, 8000), 0 ); - def.AddAmmoType("Buckshot", DMG_BULLET | DMG_BUCKSHOT, TRACER_LINE, 0, 0, 30, BULLET_IMPULSE(400, 1200), 0 ); - def.AddAmmoType("RPG_Round", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0 ); - def.AddAmmoType("SMG1_Grenade", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0 ); - def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); - def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); - } - - return &def; -} - -#ifdef CLIENT_DLL - - ConVar cl_autowepswitch( - "cl_autowepswitch", - "1", - FCVAR_ARCHIVE | FCVAR_USERINFO, - "Automatically switch to picked up weapons (if more powerful)" ); - -#else - -#ifdef DEBUG - - // Handler for the "bot" command. - void Bot_f() - { - // Look at -count. - int count = 1; - count = clamp( count, 1, 16 ); - - int iTeam = TEAM_COMBINE; - - // Look at -frozen. - bool bFrozen = false; - - // Ok, spawn all the bots. - while ( --count >= 0 ) - { - BotPutInServer( bFrozen, iTeam ); - } - } - - - ConCommand cc_Bot( "bot", Bot_f, "Add a bot.", FCVAR_CHEAT ); - -#endif - - bool CHL2MPRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ) - { - if ( pPlayer->GetActiveWeapon() && pPlayer->IsNetClient() ) - { - // Player has an active item, so let's check cl_autowepswitch. - const char *cl_autowepswitch = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_autowepswitch" ); - if ( cl_autowepswitch && atoi( cl_autowepswitch ) <= 0 ) - { - return false; - } - } - - return BaseClass::FShouldSwitchWeapon( pPlayer, pWeapon ); - } - -#endif - -#ifndef CLIENT_DLL - -bool FindInList( const char **pStrings, const char *pToFind ) -{ - int i = 0; - while ( pStrings[i][0] != 0 ) - { - if ( Q_stricmp( pStrings[i], pToFind ) == 0 ) - return true; - i++; - } - - return false; -} - -void CHL2MPRules::RestartGame() -{ - // bounds check - if ( mp_timelimit.GetInt() < 0 ) - { - mp_timelimit.SetValue( 0 ); - } - m_flGameStartTime = gpGlobals->curtime; - if ( !IsFinite( m_flGameStartTime.Get() ) ) - { - Warning( "Trying to set a NaN game start time\n" ); - m_flGameStartTime.GetForModify() = 0.0f; - } - - CleanUpMap(); - - // now respawn all players - for (int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CHL2MP_Player *pPlayer = (CHL2MP_Player*) UTIL_PlayerByIndex( i ); - - if ( !pPlayer ) - continue; - - if ( pPlayer->GetActiveWeapon() ) - { - pPlayer->GetActiveWeapon()->Holster(); - } - pPlayer->RemoveAllItems( true ); - respawn( pPlayer, false ); - pPlayer->Reset(); - } - - // Respawn entities (glass, doors, etc..) - - CTeam *pRebels = GetGlobalTeam( TEAM_REBELS ); - CTeam *pCombine = GetGlobalTeam( TEAM_COMBINE ); - - if ( pRebels ) - { - pRebels->SetScore( 0 ); - } - - if ( pCombine ) - { - pCombine->SetScore( 0 ); - } - - m_flIntermissionEndTime = 0; - m_flRestartGameTime = 0.0; - m_bCompleteReset = false; - - IGameEvent * event = gameeventmanager->CreateEvent( "round_start" ); - if ( event ) - { - event->SetInt("fraglimit", 0 ); - event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted - - event->SetString("objective","DEATHMATCH"); - - gameeventmanager->FireEvent( event ); - } -} - -void CHL2MPRules::CleanUpMap() -{ - // Recreate all the map entities from the map data (preserving their indices), - // then remove everything else except the players. - - // Get rid of all entities except players. - CBaseEntity *pCur = gEntList.FirstEnt(); - while ( pCur ) - { - CBaseHL2MPCombatWeapon *pWeapon = dynamic_cast< CBaseHL2MPCombatWeapon* >( pCur ); - // Weapons with owners don't want to be removed.. - if ( pWeapon ) - { - if ( !pWeapon->GetPlayerOwner() ) - { - UTIL_Remove( pCur ); - } - } - // remove entities that has to be restored on roundrestart (breakables etc) - else if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) ) - { - UTIL_Remove( pCur ); - } - - pCur = gEntList.NextEnt( pCur ); - } - - // Really remove the entities so we can have access to their slots below. - gEntList.CleanupDeleteList(); - - // Cancel all queued events, in case a func_bomb_target fired some delayed outputs that - // could kill respawning CTs - g_EventQueue.Clear(); - - // Now reload the map entities. - class CHL2MPMapEntityFilter : public IMapEntityFilter - { - public: - virtual bool ShouldCreateEntity( const char *pClassname ) - { - // Don't recreate the preserved entities. - if ( !FindInList( s_PreserveEnts, pClassname ) ) - { - return true; - } - else - { - // Increment our iterator since it's not going to call CreateNextEntity for this ent. - if ( m_iIterator != g_MapEntityRefs.InvalidIndex() ) - m_iIterator = g_MapEntityRefs.Next( m_iIterator ); - - return false; - } - } - - - virtual CBaseEntity* CreateNextEntity( const char *pClassname ) - { - if ( m_iIterator == g_MapEntityRefs.InvalidIndex() ) - { - // This shouldn't be possible. When we loaded the map, it should have used - // CCSMapLoadEntityFilter, which should have built the g_MapEntityRefs list - // with the same list of entities we're referring to here. - Assert( false ); - return NULL; - } - else - { - CMapEntityRef &ref = g_MapEntityRefs[m_iIterator]; - m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity. - - if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) ) - { - // Doh! The entity was delete and its slot was reused. - // Just use any old edict slot. This case sucks because we lose the baseline. - return CreateEntityByName( pClassname ); - } - else - { - // Cool, the slot where this entity was is free again (most likely, the entity was - // freed above). Now create an entity with this specific index. - return CreateEntityByName( pClassname, ref.m_iEdict ); - } - } - } - - public: - int m_iIterator; // Iterator into g_MapEntityRefs. - }; - CHL2MPMapEntityFilter filter; - filter.m_iIterator = g_MapEntityRefs.Head(); - - // DO NOT CALL SPAWN ON info_node ENTITIES! - - MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); -} - -void CHL2MPRules::CheckChatForReadySignal( CHL2MP_Player *pPlayer, const char *chatmsg ) -{ - if( m_bAwaitingReadyRestart && FStrEq( chatmsg, mp_ready_signal.GetString() ) ) - { - if( !pPlayer->IsReady() ) - { - pPlayer->SetReady( true ); - } - } -} - -void CHL2MPRules::CheckRestartGame( void ) -{ - // Restart the game if specified by the server - int iRestartDelay = mp_restartgame.GetInt(); - - if ( iRestartDelay > 0 ) - { - if ( iRestartDelay > 60 ) - iRestartDelay = 60; - - - // let the players know - char strRestartDelay[64]; - Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay ); - UTIL_ClientPrintAll( HUD_PRINTCENTER, "Game will restart in %s1 %s2", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); - UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "Game will restart in %s1 %s2", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); - - m_flRestartGameTime = gpGlobals->curtime + iRestartDelay; - m_bCompleteReset = true; - mp_restartgame.SetValue( 0 ); - } - - if( mp_readyrestart.GetBool() ) - { - m_bAwaitingReadyRestart = true; - m_bHeardAllPlayersReady = false; - - - const char *pszReadyString = mp_ready_signal.GetString(); - - - // Don't let them put anything malicious in there - if( pszReadyString == NULL || Q_strlen(pszReadyString) > 16 ) - { - pszReadyString = "ready"; - } - - IGameEvent *event = gameeventmanager->CreateEvent( "hl2mp_ready_restart" ); - if ( event ) - gameeventmanager->FireEvent( event ); - - mp_readyrestart.SetValue( 0 ); - - // cancel any restart round in progress - m_flRestartGameTime = -1; - } -} - -void CHL2MPRules::CheckAllPlayersReady( void ) -{ - for (int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CHL2MP_Player *pPlayer = (CHL2MP_Player*) UTIL_PlayerByIndex( i ); - - if ( !pPlayer ) - continue; - if ( !pPlayer->IsReady() ) - return; - } - m_bHeardAllPlayersReady = true; -} - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "hl2mp_gamerules.h" +#include "viewport_panel_names.h" +#include "gameeventdefs.h" +#include +#include "ammodef.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" +#else + + #include "eventqueue.h" + #include "player.h" + #include "gamerules.h" + #include "game.h" + #include "items.h" + #include "entitylist.h" + #include "mapentities.h" + #include "in_buttons.h" + #include + #include "voice_gamemgr.h" + #include "iscorer.h" + #include "hl2mp_player.h" + #include "weapon_hl2mpbasehlmpcombatweapon.h" + #include "team.h" + #include "voice_gamemgr.h" + #include "hl2mp_gameinterface.h" + #include "hl2mp_cvars.h" + +#ifdef DEBUG + #include "hl2mp_bot_temp.h" +#endif + +extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); + + +ConVar sv_hl2mp_weapon_respawn_time( "sv_hl2mp_weapon_respawn_time", "20", FCVAR_GAMEDLL | FCVAR_NOTIFY ); +ConVar sv_hl2mp_item_respawn_time( "sv_hl2mp_item_respawn_time", "30", FCVAR_GAMEDLL | FCVAR_NOTIFY ); +ConVar mp_restartgame( "mp_restartgame", "0", 0, "If non-zero, game will restart in the specified number of seconds" ); +ConVar sv_report_client_settings("sv_report_client_settings", "0", FCVAR_GAMEDLL | FCVAR_NOTIFY ); + +extern ConVar mp_chattime; + +extern CBaseEntity *g_pLastCombineSpawn; +extern CBaseEntity *g_pLastRebelSpawn; + +#define WEAPON_MAX_DISTANCE_FROM_SPAWN 64 + +#endif + + +REGISTER_GAMERULES_CLASS( CHL2MPRules ); + +BEGIN_NETWORK_TABLE_NOBASE( CHL2MPRules, DT_HL2MPRules ) + + #ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bTeamPlayEnabled ) ), + #else + SendPropBool( SENDINFO( m_bTeamPlayEnabled ) ), + #endif + +END_NETWORK_TABLE() + + +LINK_ENTITY_TO_CLASS( hl2mp_gamerules, CHL2MPGameRulesProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( HL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) + +static HL2MPViewVectors g_HL2MPViewVectors( + Vector( 0, 0, 64 ), //m_vView + + Vector(-16, -16, 0 ), //m_vHullMin + Vector( 16, 16, 72 ), //m_vHullMax + + Vector(-16, -16, 0 ), //m_vDuckHullMin + Vector( 16, 16, 36 ), //m_vDuckHullMax + Vector( 0, 0, 28 ), //m_vDuckView + + Vector(-10, -10, -10 ), //m_vObsHullMin + Vector( 10, 10, 10 ), //m_vObsHullMax + + Vector( 0, 0, 14 ), //m_vDeadViewHeight + + Vector(-16, -16, 0 ), //m_vCrouchTraceMin + Vector( 16, 16, 60 ) //m_vCrouchTraceMax +); + +static const char *s_PreserveEnts[] = +{ + "ai_network", + "ai_hint", + "hl2mp_gamerules", + "team_manager", + "player_manager", + "env_soundscape", + "env_soundscape_proxy", + "env_soundscape_triggerable", + "env_sun", + "env_wind", + "env_fog_controller", + "func_brush", + "func_wall", + "func_buyzone", + "func_illusionary", + "infodecal", + "info_projecteddecal", + "info_node", + "info_target", + "info_node_hint", + "info_player_deathmatch", + "info_player_combine", + "info_player_rebel", + "info_map_parameters", + "keyframe_rope", + "move_rope", + "info_ladder", + "player", + "point_viewcontrol", + "scene_manager", + "shadow_control", + "sky_camera", + "soundent", + "trigger_soundscape", + "viewmodel", + "predicted_viewmodel", + "worldspawn", + "point_devshot_camera", + "", // END Marker +}; + + + +#ifdef CLIENT_DLL + void RecvProxy_HL2MPRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) + { + CHL2MPRules *pRules = HL2MPRules(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CHL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) + RecvPropDataTable( "hl2mp_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_HL2MPRules ), RecvProxy_HL2MPRules ) + END_RECV_TABLE() +#else + void* SendProxy_HL2MPRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) + { + CHL2MPRules *pRules = HL2MPRules(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE( CHL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) + SendPropDataTable( "hl2mp_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_HL2MPRules ), SendProxy_HL2MPRules ) + END_SEND_TABLE() +#endif + +#ifndef CLIENT_DLL + + class CVoiceGameMgrHelper : public IVoiceGameMgrHelper + { + public: + virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker ) + { + return ( pListener->GetTeamNumber() == pTalker->GetTeamNumber() ); + } + }; + CVoiceGameMgrHelper g_VoiceGameMgrHelper; + IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper; + +#endif + +// NOTE: the indices here must match TEAM_TERRORIST, TEAM_CT, TEAM_SPECTATOR, etc. +char *sTeamNames[] = +{ + "Unassigned", + "Spectator", + "Combine", + "Rebels", +}; + +CHL2MPRules::CHL2MPRules() +{ +#ifndef CLIENT_DLL + // Create the team managers + for ( int i = 0; i < static_cast(ARRAYSIZE( sTeamNames )); i++ ) + { + CTeam *pTeam = static_cast(CreateEntityByName( "team_manager" )); + pTeam->Init( sTeamNames[i], i ); + + g_Teams.AddToTail( pTeam ); + } + + m_bTeamPlayEnabled = teamplay.GetBool(); + m_flIntermissionEndTime = 0.0f; + m_flGameStartTime = 0; + + m_hRespawnableItemsAndWeapons.RemoveAll(); + m_tmNextPeriodicThink = 0; + m_flRestartGameTime = 0; + m_bCompleteReset = false; + m_bHeardAllPlayersReady = false; + m_bAwaitingReadyRestart = false; + +#endif +} + +const CViewVectors* CHL2MPRules::GetViewVectors()const +{ + return &g_HL2MPViewVectors; +} + +const HL2MPViewVectors* CHL2MPRules::GetHL2MPViewVectors()const +{ + return &g_HL2MPViewVectors; +} + +CHL2MPRules::~CHL2MPRules( void ) +{ +#ifndef CLIENT_DLL + // Note, don't delete each team since they are in the gEntList and will + // automatically be deleted from there, instead. + g_Teams.Purge(); +#endif +} + +void CHL2MPRules::CreateStandardEntities( void ) +{ + +#ifndef CLIENT_DLL + // Create the entity that will send our data to the client. + + BaseClass::CreateStandardEntities(); + + g_pLastCombineSpawn = NULL; + g_pLastRebelSpawn = NULL; + +#ifdef _DEBUG + CBaseEntity *pEnt = +#endif + CBaseEntity::Create( "hl2mp_gamerules", vec3_origin, vec3_angle ); + Assert( pEnt ); +#endif +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHL2MPRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ) +{ +#ifndef CLIENT_DLL + if ( weaponstay.GetInt() > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return 0; // weapon respawns almost instantly + } + } + + return sv_hl2mp_weapon_respawn_time.GetFloat(); +#endif + + return 0; // weapon respawns almost instantly +} + + +bool CHL2MPRules::IsIntermission( void ) +{ +#ifndef CLIENT_DLL + return m_flIntermissionEndTime > gpGlobals->curtime; +#endif + + return false; +} + +void CHL2MPRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) +{ +#ifndef CLIENT_DLL + if ( IsIntermission() ) + return; + BaseClass::PlayerKilled( pVictim, info ); +#endif +} + + +void CHL2MPRules::Think( void ) +{ + +#ifndef CLIENT_DLL + + CGameRules::Think(); + + if ( g_fGameOver ) // someone else quit the game already + { + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->curtime ) + { + ChangeLevel(); // intermission is over + } + + return; + } + +// float flTimeLimit = mp_timelimit.GetFloat() * 60; + float flFragLimit = fraglimit.GetFloat(); + + if ( GetMapRemainingTime() < 0 ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit ) + { + if( IsTeamplay() == true ) + { + CTeam *pCombine = g_Teams[TEAM_COMBINE]; + CTeam *pRebels = g_Teams[TEAM_REBELS]; + + if ( pCombine->GetScore() >= flFragLimit || pRebels->GetScore() >= flFragLimit ) + { + GoToIntermission(); + return; + } + } + else + { + // check if any player is over the frag limit + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->FragCount() >= flFragLimit ) + { + GoToIntermission(); + return; + } + } + } + } + + if ( gpGlobals->curtime > m_tmNextPeriodicThink ) + { + CheckAllPlayersReady(); + CheckRestartGame(); + m_tmNextPeriodicThink = gpGlobals->curtime + 1.0; + } + + if ( m_flRestartGameTime > 0.0f && m_flRestartGameTime <= gpGlobals->curtime ) + { + RestartGame(); + } + + if( m_bAwaitingReadyRestart && m_bHeardAllPlayersReady ) + { + UTIL_ClientPrintAll( HUD_PRINTCENTER, "All players ready. Game will restart in 5 seconds" ); + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "All players ready. Game will restart in 5 seconds" ); + + m_flRestartGameTime = gpGlobals->curtime + 5; + m_bAwaitingReadyRestart = false; + } + + ManageObjectRelocation(); + +#endif +} + +void CHL2MPRules::GoToIntermission( void ) +{ +#ifndef CLIENT_DLL + if ( g_fGameOver ) + return; + + g_fGameOver = true; + + m_flIntermissionEndTime = gpGlobals->curtime + mp_chattime.GetInt(); + + for ( int i = 0; i < MAX_PLAYERS; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD ); + pPlayer->AddFlag( FL_FROZEN ); + } +#endif + +} + +bool CHL2MPRules::CheckGameOver() +{ +#ifndef CLIENT_DLL + if ( g_fGameOver ) // someone else quit the game already + { + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->curtime ) + { + ChangeLevel(); // intermission is over + } + + return true; + } +#endif + + return false; +} + +// when we are within this close to running out of entities, items +// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn +#define ENTITY_INTOLERANCE 100 + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHL2MPRules::FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ) +{ +#ifndef CLIENT_DLL + if ( pWeapon && (pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + if ( gEntList.NumberOfEntities() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) + return 0; + + // we're past the entity tolerance level, so delay the respawn + return FlWeaponRespawnTime( pWeapon ); + } +#endif + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHL2MPRules::VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ) +{ +#ifndef CLIENT_DLL + CWeaponHL2MPBase *pHL2Weapon = dynamic_cast< CWeaponHL2MPBase*>( pWeapon ); + + if ( pHL2Weapon ) + { + return pHL2Weapon->GetOriginalSpawnOrigin(); + } +#endif + + return pWeapon->GetAbsOrigin(); +} + +#ifndef CLIENT_DLL + +CItem* IsManagedObjectAnItem( CBaseEntity *pObject ) +{ + return dynamic_cast< CItem*>( pObject ); +} + +CWeaponHL2MPBase* IsManagedObjectAWeapon( CBaseEntity *pObject ) +{ + return dynamic_cast< CWeaponHL2MPBase*>( pObject ); +} + +bool GetObjectsOriginalParameters( CBaseEntity *pObject, Vector &vOriginalOrigin, QAngle &vOriginalAngles ) +{ + if ( CItem *pItem = IsManagedObjectAnItem( pObject ) ) + { + if ( pItem->m_flNextResetCheckTime > gpGlobals->curtime ) + return false; + + vOriginalOrigin = pItem->GetOriginalSpawnOrigin(); + vOriginalAngles = pItem->GetOriginalSpawnAngles(); + + pItem->m_flNextResetCheckTime = gpGlobals->curtime + sv_hl2mp_item_respawn_time.GetFloat(); + return true; + } + else if ( CWeaponHL2MPBase *pWeapon = IsManagedObjectAWeapon( pObject )) + { + if ( pWeapon->m_flNextResetCheckTime > gpGlobals->curtime ) + return false; + + vOriginalOrigin = pWeapon->GetOriginalSpawnOrigin(); + vOriginalAngles = pWeapon->GetOriginalSpawnAngles(); + + pWeapon->m_flNextResetCheckTime = gpGlobals->curtime + sv_hl2mp_weapon_respawn_time.GetFloat(); + return true; + } + + return false; +} + +void CHL2MPRules::ManageObjectRelocation( void ) +{ + int iTotal = m_hRespawnableItemsAndWeapons.Count(); + + if ( iTotal > 0 ) + { + for ( int i = 0; i < iTotal; i++ ) + { + CBaseEntity *pObject = m_hRespawnableItemsAndWeapons[i].Get(); + + if ( pObject ) + { + Vector vSpawOrigin; + QAngle vSpawnAngles; + + if ( GetObjectsOriginalParameters( pObject, vSpawOrigin, vSpawnAngles ) == true ) + { + float flDistanceFromSpawn = (pObject->GetAbsOrigin() - vSpawOrigin ).Length(); + + if ( flDistanceFromSpawn > WEAPON_MAX_DISTANCE_FROM_SPAWN ) + { + bool shouldReset = false; + IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); + + if ( pPhysics ) + { + shouldReset = pPhysics->IsAsleep(); + } + else + { + shouldReset = (pObject->GetFlags() & FL_ONGROUND) ? true : false; + } + + if ( shouldReset ) + { + pObject->Teleport( &vSpawOrigin, &vSpawnAngles, NULL ); + pObject->EmitSound( "AlyxEmp.Charge" ); + + IPhysicsObject *pPhys = pObject->VPhysicsGetObject(); + + if ( pPhys ) + { + pPhys->Wake(); + } + } + } + } + } + } + } +} + +//========================================================= +//AddLevelDesignerPlacedWeapon +//========================================================= +void CHL2MPRules::AddLevelDesignerPlacedObject( CBaseEntity *pEntity ) +{ + if ( m_hRespawnableItemsAndWeapons.Find( pEntity ) == -1 ) + { + m_hRespawnableItemsAndWeapons.AddToTail( pEntity ); + } +} + +//========================================================= +//RemoveLevelDesignerPlacedWeapon +//========================================================= +void CHL2MPRules::RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ) +{ + if ( m_hRespawnableItemsAndWeapons.Find( pEntity ) != -1 ) + { + m_hRespawnableItemsAndWeapons.FindAndRemove( pEntity ); + } +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHL2MPRules::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->GetOriginalSpawnOrigin(); +} + +//========================================================= +// What angles should this item use to respawn? +//========================================================= +QAngle CHL2MPRules::VecItemRespawnAngles( CItem *pItem ) +{ + return pItem->GetOriginalSpawnAngles(); +} + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHL2MPRules::FlItemRespawnTime( CItem *pItem ) +{ + return sv_hl2mp_item_respawn_time.GetFloat(); +} + + +//========================================================= +// CanHaveWeapon - returns false if the player is not allowed +// to pick up this weapon +//========================================================= +bool CHL2MPRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ) +{ + if ( weaponstay.GetInt() > 0 ) + { + if ( pPlayer->Weapon_OwnsThisType( pItem->GetClassname(), pItem->GetSubType() ) ) + return false; + } + + return BaseClass::CanHavePlayerItem( pPlayer, pItem ); +} + +#endif + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHL2MPRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) +{ +#ifndef CLIENT_DLL + if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) ) + { + return GR_WEAPON_RESPAWN_NO; + } +#endif + + return GR_WEAPON_RESPAWN_YES; +} + +//----------------------------------------------------------------------------- +// Purpose: Player has just left the game +//----------------------------------------------------------------------------- +void CHL2MPRules::ClientDisconnected( edict_t *pClient ) +{ +#ifndef CLIENT_DLL + // Msg( "CLIENT DISCONNECTED, REMOVING FROM TEAM.\n" ); + + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + if ( pPlayer ) + { + // Remove the player from his team + if ( pPlayer->GetTeam() ) + { + pPlayer->GetTeam()->RemovePlayer( pPlayer ); + } + } + + BaseClass::ClientDisconnected( pClient ); + +#endif +} + + +//========================================================= +// Deathnotice. +//========================================================= +void CHL2MPRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) +{ +#ifndef CLIENT_DLL + // Work out what killed the player, and send a message to all clients about it + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_ID = 0; + + // Find the killer & the scorer + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor ); + + // Custom kill type? + if ( info.GetCustomKill() ) + { + killer_weapon_name = GetCustomKillString( info ); + if ( pScorer ) + { + killer_ID = pScorer->GetUserID(); + } + } + else + { + // Is the killer a client? + if ( pScorer ) + { + killer_ID = pScorer->GetUserID(); + + if ( pInflictor ) + { + if ( pInflictor == pScorer ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + if ( pScorer->GetActiveWeapon() ) + { + killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname(); + } + } + else + { + killer_weapon_name = pInflictor->GetClassname(); // it's just that easy + } + } + } + else + { + killer_weapon_name = pInflictor->GetClassname(); + } + + // strip the NPC_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + { + killer_weapon_name += 7; + } + else if ( strncmp( killer_weapon_name, "npc_", 4 ) == 0 ) + { + killer_weapon_name += 4; + } + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + { + killer_weapon_name += 5; + } + else if ( strstr( killer_weapon_name, "physics" ) ) + { + killer_weapon_name = "physics"; + } + + if ( strcmp( killer_weapon_name, "prop_combine_ball" ) == 0 ) + { + killer_weapon_name = "combine_ball"; + } + else if ( strcmp( killer_weapon_name, "grenade_ar2" ) == 0 ) + { + killer_weapon_name = "smg1_grenade"; + } + else if ( strcmp( killer_weapon_name, "satchel" ) == 0 || strcmp( killer_weapon_name, "tripmine" ) == 0) + { + killer_weapon_name = "slam"; + } + + + } + + IGameEvent *event = gameeventmanager->CreateEvent( "player_death" ); + if( event ) + { + event->SetInt("userid", pVictim->GetUserID() ); + event->SetInt("attacker", killer_ID ); + event->SetString("weapon", killer_weapon_name ); + event->SetInt( "priority", 7 ); + gameeventmanager->FireEvent( event ); + } +#endif + +} + +void CHL2MPRules::ClientSettingsChanged( CBasePlayer *pPlayer ) +{ +#ifndef CLIENT_DLL + + CHL2MP_Player *pHL2Player = ToHL2MPPlayer( pPlayer ); + + if ( pHL2Player == NULL ) + return; + + const char *pCurrentModel = modelinfo->GetModelName( pPlayer->GetModel() ); + const char *szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_playermodel" ); + + //If we're different. + if ( stricmp( szModelName, pCurrentModel ) ) + { + //Too soon, set the cvar back to what it was. + //Note: this will make this function be called again + //but since our models will match it'll just skip this whole dealio. + if ( pHL2Player->GetNextModelChangeTime() >= gpGlobals->curtime ) + { + char szReturnString[512]; + + Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pCurrentModel ); + engine->ClientCommand ( pHL2Player->edict(), szReturnString ); + + Q_snprintf( szReturnString, sizeof( szReturnString ), "Please wait %d more seconds before trying to switch.\n", (int)(pHL2Player->GetNextModelChangeTime() - gpGlobals->curtime) ); + ClientPrint( pHL2Player, HUD_PRINTTALK, szReturnString ); + return; + } + + if ( HL2MPRules()->IsTeamplay() == false ) + { + pHL2Player->SetPlayerModel(); + + const char *pszCurrentModelName = modelinfo->GetModelName( pHL2Player->GetModel() ); + + char szReturnString[128]; + Q_snprintf( szReturnString, sizeof( szReturnString ), "Your player model is: %s\n", pszCurrentModelName ); + + ClientPrint( pHL2Player, HUD_PRINTTALK, szReturnString ); + } + else + { + if ( Q_stristr( szModelName, "models/human") ) + { + pHL2Player->ChangeTeam( TEAM_REBELS ); + } + else + { + pHL2Player->ChangeTeam( TEAM_COMBINE ); + } + } + } + if ( sv_report_client_settings.GetInt() == 1 ) + { + UTIL_LogPrintf( "\"%s\" cl_cmdrate = \"%s\"\n", pHL2Player->GetPlayerName(), engine->GetClientConVarValue( pHL2Player->entindex(), "cl_cmdrate" )); + } + + BaseClass::ClientSettingsChanged( pPlayer ); +#endif + +} + +int CHL2MPRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ +#ifndef CLIENT_DLL + // half life multiplay has a simple concept of Player Relationships. + // you are either on another player's team, or you are not. + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() || IsTeamplay() == false ) + return GR_NOTTEAMMATE; + + if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) + { + return GR_TEAMMATE; + } +#endif + + return GR_NOTTEAMMATE; +} + +const char *CHL2MPRules::GetGameDescription( void ) +{ + if ( IsTeamplay() ) + return "Team Deathmatch"; + + return "Deathmatch"; +} + + +float CHL2MPRules::GetMapRemainingTime() +{ + // if timelimit is disabled, return 0 + if ( mp_timelimit.GetInt() <= 0 ) + return 0; + + // timelimit is in minutes + + float timeleft = (m_flGameStartTime + mp_timelimit.GetInt() * 60.0f ) - gpGlobals->curtime; + + return timeleft; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPRules::Precache( void ) +{ + CBaseEntity::PrecacheScriptSound( "AlyxEmp.Charge" ); +} + +bool CHL2MPRules::ShouldCollide( int collisionGroup0, int collisionGroup1 ) +{ + if ( collisionGroup0 > collisionGroup1 ) + { + // swap so that lowest is always first + swap(collisionGroup0,collisionGroup1); + } + + if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && + collisionGroup1 == COLLISION_GROUP_WEAPON ) + { + return false; + } + + return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 ); + +} + +bool CHL2MPRules::ClientCommand(const char *pcmd, CBaseEntity *pEdict ) +{ + +#ifndef CLIENT_DLL + if( BaseClass::ClientCommand(pcmd, pEdict) ) + return true; + + + CHL2MP_Player *pPlayer = (CHL2MP_Player *) pEdict; + + if ( pPlayer->ClientCommand( pcmd ) ) + return true; +#endif + + return false; +} + +// shared ammo definition +// JAY: Trying to make a more physical bullet response +#define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f) +#define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains)) + +// exaggerate all of the forces, but use real numbers to keep them consistent +#define BULLET_IMPULSE_EXAGGERATION 3.5 +// convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s +#define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION) + + +CAmmoDef *GetAmmoDef() +{ + static CAmmoDef def; + static bool bInitted = false; + + if ( !bInitted ) + { + bInitted = true; + + def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 60, BULLET_IMPULSE(200, 1225), 0 ); + def.AddAmmoType("AR2AltFire", DMG_DISSOLVE, TRACER_NONE, 0, 0, 3, 0, 0 ); + def.AddAmmoType("Pistol", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 150, BULLET_IMPULSE(200, 1225), 0 ); + def.AddAmmoType("SMG1", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 225, BULLET_IMPULSE(200, 1225), 0 ); + def.AddAmmoType("357", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 12, BULLET_IMPULSE(800, 5000), 0 ); + def.AddAmmoType("XBowBolt", DMG_BULLET, TRACER_LINE, 0, 0, 10, BULLET_IMPULSE(800, 8000), 0 ); + def.AddAmmoType("Buckshot", DMG_BULLET | DMG_BUCKSHOT, TRACER_LINE, 0, 0, 30, BULLET_IMPULSE(400, 1200), 0 ); + def.AddAmmoType("RPG_Round", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0 ); + def.AddAmmoType("SMG1_Grenade", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0 ); + def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); + def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); + } + + return &def; +} + +#ifdef CLIENT_DLL + + ConVar cl_autowepswitch( + "cl_autowepswitch", + "1", + FCVAR_ARCHIVE | FCVAR_USERINFO, + "Automatically switch to picked up weapons (if more powerful)" ); + +#else + +#ifdef DEBUG + + // Handler for the "bot" command. + void Bot_f() + { + // Look at -count. + int count = 1; + count = clamp( count, 1, 16 ); + + int iTeam = TEAM_COMBINE; + + // Look at -frozen. + bool bFrozen = false; + + // Ok, spawn all the bots. + while ( --count >= 0 ) + { + BotPutInServer( bFrozen, iTeam ); + } + } + + + ConCommand cc_Bot( "bot", Bot_f, "Add a bot.", FCVAR_CHEAT ); + +#endif + + bool CHL2MPRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ) + { + if ( pPlayer->GetActiveWeapon() && pPlayer->IsNetClient() ) + { + // Player has an active item, so let's check cl_autowepswitch. + const char *cl_autowepswitch = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_autowepswitch" ); + if ( cl_autowepswitch && atoi( cl_autowepswitch ) <= 0 ) + { + return false; + } + } + + return BaseClass::FShouldSwitchWeapon( pPlayer, pWeapon ); + } + +#endif + +#ifndef CLIENT_DLL + +bool FindInList( const char **pStrings, const char *pToFind ) +{ + int i = 0; + while ( pStrings[i][0] != 0 ) + { + if ( Q_stricmp( pStrings[i], pToFind ) == 0 ) + return true; + i++; + } + + return false; +} + +void CHL2MPRules::RestartGame() +{ + // bounds check + if ( mp_timelimit.GetInt() < 0 ) + { + mp_timelimit.SetValue( 0 ); + } + m_flGameStartTime = gpGlobals->curtime; + if ( !IsFinite( m_flGameStartTime.Get() ) ) + { + Warning( "Trying to set a NaN game start time\n" ); + m_flGameStartTime.GetForModify() = 0.0f; + } + + CleanUpMap(); + + // now respawn all players + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CHL2MP_Player *pPlayer = (CHL2MP_Player*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Holster(); + } + pPlayer->RemoveAllItems( true ); + respawn( pPlayer, false ); + pPlayer->Reset(); + } + + // Respawn entities (glass, doors, etc..) + + CTeam *pRebels = GetGlobalTeam( TEAM_REBELS ); + CTeam *pCombine = GetGlobalTeam( TEAM_COMBINE ); + + if ( pRebels ) + { + pRebels->SetScore( 0 ); + } + + if ( pCombine ) + { + pCombine->SetScore( 0 ); + } + + m_flIntermissionEndTime = 0; + m_flRestartGameTime = 0.0; + m_bCompleteReset = false; + + IGameEvent * event = gameeventmanager->CreateEvent( "round_start" ); + if ( event ) + { + event->SetInt("fraglimit", 0 ); + event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted + + event->SetString("objective","DEATHMATCH"); + + gameeventmanager->FireEvent( event ); + } +} + +void CHL2MPRules::CleanUpMap() +{ + // Recreate all the map entities from the map data (preserving their indices), + // then remove everything else except the players. + + // Get rid of all entities except players. + CBaseEntity *pCur = gEntList.FirstEnt(); + while ( pCur ) + { + CBaseHL2MPCombatWeapon *pWeapon = dynamic_cast< CBaseHL2MPCombatWeapon* >( pCur ); + // Weapons with owners don't want to be removed.. + if ( pWeapon ) + { + if ( !pWeapon->GetPlayerOwner() ) + { + UTIL_Remove( pCur ); + } + } + // remove entities that has to be restored on roundrestart (breakables etc) + else if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) ) + { + UTIL_Remove( pCur ); + } + + pCur = gEntList.NextEnt( pCur ); + } + + // Really remove the entities so we can have access to their slots below. + gEntList.CleanupDeleteList(); + + // Cancel all queued events, in case a func_bomb_target fired some delayed outputs that + // could kill respawning CTs + g_EventQueue.Clear(); + + // Now reload the map entities. + class CHL2MPMapEntityFilter : public IMapEntityFilter + { + public: + virtual bool ShouldCreateEntity( const char *pClassname ) + { + // Don't recreate the preserved entities. + if ( !FindInList( s_PreserveEnts, pClassname ) ) + { + return true; + } + else + { + // Increment our iterator since it's not going to call CreateNextEntity for this ent. + if ( m_iIterator != g_MapEntityRefs.InvalidIndex() ) + m_iIterator = g_MapEntityRefs.Next( m_iIterator ); + + return false; + } + } + + + virtual CBaseEntity* CreateNextEntity( const char *pClassname ) + { + if ( m_iIterator == g_MapEntityRefs.InvalidIndex() ) + { + // This shouldn't be possible. When we loaded the map, it should have used + // CCSMapLoadEntityFilter, which should have built the g_MapEntityRefs list + // with the same list of entities we're referring to here. + Assert( false ); + return NULL; + } + else + { + CMapEntityRef &ref = g_MapEntityRefs[m_iIterator]; + m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity. + + if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) ) + { + // Doh! The entity was delete and its slot was reused. + // Just use any old edict slot. This case sucks because we lose the baseline. + return CreateEntityByName( pClassname ); + } + else + { + // Cool, the slot where this entity was is free again (most likely, the entity was + // freed above). Now create an entity with this specific index. + return CreateEntityByName( pClassname, ref.m_iEdict ); + } + } + } + + public: + int m_iIterator; // Iterator into g_MapEntityRefs. + }; + CHL2MPMapEntityFilter filter; + filter.m_iIterator = g_MapEntityRefs.Head(); + + // DO NOT CALL SPAWN ON info_node ENTITIES! + + MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); +} + +void CHL2MPRules::CheckChatForReadySignal( CHL2MP_Player *pPlayer, const char *chatmsg ) +{ + if( m_bAwaitingReadyRestart && FStrEq( chatmsg, mp_ready_signal.GetString() ) ) + { + if( !pPlayer->IsReady() ) + { + pPlayer->SetReady( true ); + } + } +} + +void CHL2MPRules::CheckRestartGame( void ) +{ + // Restart the game if specified by the server + int iRestartDelay = mp_restartgame.GetInt(); + + if ( iRestartDelay > 0 ) + { + if ( iRestartDelay > 60 ) + iRestartDelay = 60; + + + // let the players know + char strRestartDelay[64]; + Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay ); + UTIL_ClientPrintAll( HUD_PRINTCENTER, "Game will restart in %s1 %s2", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "Game will restart in %s1 %s2", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); + + m_flRestartGameTime = gpGlobals->curtime + iRestartDelay; + m_bCompleteReset = true; + mp_restartgame.SetValue( 0 ); + } + + if( mp_readyrestart.GetBool() ) + { + m_bAwaitingReadyRestart = true; + m_bHeardAllPlayersReady = false; + + + const char *pszReadyString = mp_ready_signal.GetString(); + + + // Don't let them put anything malicious in there + if( pszReadyString == NULL || Q_strlen(pszReadyString) > 16 ) + { + pszReadyString = "ready"; + } + + IGameEvent *event = gameeventmanager->CreateEvent( "hl2mp_ready_restart" ); + if ( event ) + gameeventmanager->FireEvent( event ); + + mp_readyrestart.SetValue( 0 ); + + // cancel any restart round in progress + m_flRestartGameTime = -1; + } +} + +void CHL2MPRules::CheckAllPlayersReady( void ) +{ + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CHL2MP_Player *pPlayer = (CHL2MP_Player*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + if ( !pPlayer->IsReady() ) + return; + } + m_bHeardAllPlayersReady = true; +} + +#endif diff --git a/game_shared/hl2mp/hl2mp_gamerules.h b/game_shared/hl2mp/hl2mp_gamerules.h index 1f9f28ae..bd28764b 100644 --- a/game_shared/hl2mp/hl2mp_gamerules.h +++ b/game_shared/hl2mp/hl2mp_gamerules.h @@ -1,167 +1,167 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef HL2MP_GAMERULES_H -#define HL2MP_GAMERULES_H -#pragma once - -#include "gamerules.h" -#include "teamplay_gamerules.h" -#include "gamevars_shared.h" - -#ifndef CLIENT_DLL -#include "hl2mp_player.h" -#endif - -#define VEC_CROUCH_TRACE_MIN HL2MPRules()->GetHL2MPViewVectors()->m_vCrouchTraceMin -#define VEC_CROUCH_TRACE_MAX HL2MPRules()->GetHL2MPViewVectors()->m_vCrouchTraceMax - -enum -{ - TEAM_COMBINE = 2, - TEAM_REBELS, -}; - - -#ifdef CLIENT_DLL - #define CHL2MPRules C_HL2MPRules - #define CHL2MPGameRulesProxy C_HL2MPGameRulesProxy -#endif - -class CHL2MPGameRulesProxy : public CGameRulesProxy -{ -public: - DECLARE_CLASS( CHL2MPGameRulesProxy, CGameRulesProxy ); - DECLARE_NETWORKCLASS(); -}; - -class HL2MPViewVectors : public CViewVectors -{ -public: - HL2MPViewVectors( - Vector vView, - Vector vHullMin, - Vector vHullMax, - Vector vDuckHullMin, - Vector vDuckHullMax, - Vector vDuckView, - Vector vObsHullMin, - Vector vObsHullMax, - Vector vDeadViewHeight, - Vector vCrouchTraceMin, - Vector vCrouchTraceMax ) : - CViewVectors( - vView, - vHullMin, - vHullMax, - vDuckHullMin, - vDuckHullMax, - vDuckView, - vObsHullMin, - vObsHullMax, - vDeadViewHeight ) - { - m_vCrouchTraceMin = vCrouchTraceMin; - m_vCrouchTraceMax = vCrouchTraceMax; - } - - Vector m_vCrouchTraceMin; - Vector m_vCrouchTraceMax; -}; - -class CHL2MPRules : public CTeamplayRules -{ -public: - DECLARE_CLASS( CHL2MPRules, CTeamplayRules ); - -#ifdef CLIENT_DLL - - DECLARE_CLIENTCLASS_NOBASE(); // This makes datatables able to access our private vars. - -#else - - DECLARE_SERVERCLASS_NOBASE(); // This makes datatables able to access our private vars. -#endif - - CHL2MPRules(); - virtual ~CHL2MPRules(); - - virtual void Precache( void ); - virtual bool ShouldCollide( int collisionGroup0, int collisionGroup1 ); - virtual bool ClientCommand( const char *pcmd, CBaseEntity *pEdict ); - - virtual float FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ); - virtual float FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ); - virtual Vector VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ); - virtual int WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ); - virtual void Think( void ); - virtual void CreateStandardEntities( void ); - virtual void ClientSettingsChanged( CBasePlayer *pPlayer ); - virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); - virtual void GoToIntermission( void ); - virtual void DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ); - virtual const char *GetGameDescription( void ); - // derive this function if you mod uses encrypted weapon info files - virtual const unsigned char *GetEncryptionKey( void ) { return (unsigned char *)"x9Ke0BY7"; } - virtual const CViewVectors* GetViewVectors() const; - const HL2MPViewVectors* GetHL2MPViewVectors() const; - - float GetMapRemainingTime(); - void CleanUpMap(); - void CheckRestartGame(); - void RestartGame(); - -#ifndef CLIENT_DLL - virtual Vector VecItemRespawnSpot( CItem *pItem ); - virtual QAngle VecItemRespawnAngles( CItem *pItem ); - virtual float FlItemRespawnTime( CItem *pItem ); - virtual bool CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ); - virtual bool FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ); - - void AddLevelDesignerPlacedObject( CBaseEntity *pEntity ); - void RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ); - void ManageObjectRelocation( void ); - void CheckChatForReadySignal( CHL2MP_Player *pPlayer, const char *chatmsg ); - -#endif - virtual void ClientDisconnected( edict_t *pClient ); - - bool CheckGameOver( void ); - bool IsIntermission( void ); - - void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ); - - - bool IsTeamplay( void ) { return m_bTeamPlayEnabled; } - void CheckAllPlayersReady( void ); - -private: - - CNetworkVar( bool, m_bTeamPlayEnabled ); - CNetworkVar( float, m_flGameStartTime ); - CUtlVector m_hRespawnableItemsAndWeapons; - float m_tmNextPeriodicThink; - float m_flRestartGameTime; - bool m_bCompleteReset; - bool m_bAwaitingReadyRestart; - bool m_bHeardAllPlayersReady; - - -}; - -inline CHL2MPRules* HL2MPRules() -{ - return static_cast(g_pGameRules); -} - -#endif //HL2MP_GAMERULES_H +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef HL2MP_GAMERULES_H +#define HL2MP_GAMERULES_H +#pragma once + +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "gamevars_shared.h" + +#ifndef CLIENT_DLL +#include "hl2mp_player.h" +#endif + +#define VEC_CROUCH_TRACE_MIN HL2MPRules()->GetHL2MPViewVectors()->m_vCrouchTraceMin +#define VEC_CROUCH_TRACE_MAX HL2MPRules()->GetHL2MPViewVectors()->m_vCrouchTraceMax + +enum +{ + TEAM_COMBINE = 2, + TEAM_REBELS, +}; + + +#ifdef CLIENT_DLL + #define CHL2MPRules C_HL2MPRules + #define CHL2MPGameRulesProxy C_HL2MPGameRulesProxy +#endif + +class CHL2MPGameRulesProxy : public CGameRulesProxy +{ +public: + DECLARE_CLASS( CHL2MPGameRulesProxy, CGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class HL2MPViewVectors : public CViewVectors +{ +public: + HL2MPViewVectors( + Vector vView, + Vector vHullMin, + Vector vHullMax, + Vector vDuckHullMin, + Vector vDuckHullMax, + Vector vDuckView, + Vector vObsHullMin, + Vector vObsHullMax, + Vector vDeadViewHeight, + Vector vCrouchTraceMin, + Vector vCrouchTraceMax ) : + CViewVectors( + vView, + vHullMin, + vHullMax, + vDuckHullMin, + vDuckHullMax, + vDuckView, + vObsHullMin, + vObsHullMax, + vDeadViewHeight ) + { + m_vCrouchTraceMin = vCrouchTraceMin; + m_vCrouchTraceMax = vCrouchTraceMax; + } + + Vector m_vCrouchTraceMin; + Vector m_vCrouchTraceMax; +}; + +class CHL2MPRules : public CTeamplayRules +{ +public: + DECLARE_CLASS( CHL2MPRules, CTeamplayRules ); + +#ifdef CLIENT_DLL + + DECLARE_CLIENTCLASS_NOBASE(); // This makes datatables able to access our private vars. + +#else + + DECLARE_SERVERCLASS_NOBASE(); // This makes datatables able to access our private vars. +#endif + + CHL2MPRules(); + virtual ~CHL2MPRules(); + + virtual void Precache( void ); + virtual bool ShouldCollide( int collisionGroup0, int collisionGroup1 ); + virtual bool ClientCommand( const char *pcmd, CBaseEntity *pEdict ); + + virtual float FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ); + virtual float FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ); + virtual int WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ); + virtual void Think( void ); + virtual void CreateStandardEntities( void ); + virtual void ClientSettingsChanged( CBasePlayer *pPlayer ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual void GoToIntermission( void ); + virtual void DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ); + virtual const char *GetGameDescription( void ); + // derive this function if you mod uses encrypted weapon info files + virtual const unsigned char *GetEncryptionKey( void ) { return (unsigned char *)"x9Ke0BY7"; } + virtual const CViewVectors* GetViewVectors() const; + const HL2MPViewVectors* GetHL2MPViewVectors() const; + + float GetMapRemainingTime(); + void CleanUpMap(); + void CheckRestartGame(); + void RestartGame(); + +#ifndef CLIENT_DLL + virtual Vector VecItemRespawnSpot( CItem *pItem ); + virtual QAngle VecItemRespawnAngles( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual bool CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ); + virtual bool FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ); + + void AddLevelDesignerPlacedObject( CBaseEntity *pEntity ); + void RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ); + void ManageObjectRelocation( void ); + void CheckChatForReadySignal( CHL2MP_Player *pPlayer, const char *chatmsg ); + +#endif + virtual void ClientDisconnected( edict_t *pClient ); + + bool CheckGameOver( void ); + bool IsIntermission( void ); + + void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ); + + + bool IsTeamplay( void ) { return m_bTeamPlayEnabled; } + void CheckAllPlayersReady( void ); + +private: + + CNetworkVar( bool, m_bTeamPlayEnabled ); + CNetworkVar( float, m_flGameStartTime ); + CUtlVector m_hRespawnableItemsAndWeapons; + float m_tmNextPeriodicThink; + float m_flRestartGameTime; + bool m_bCompleteReset; + bool m_bAwaitingReadyRestart; + bool m_bHeardAllPlayersReady; + + +}; + +inline CHL2MPRules* HL2MPRules() +{ + return static_cast(g_pGameRules); +} + +#endif //HL2MP_GAMERULES_H diff --git a/game_shared/hl2mp/hl2mp_player_shared.cpp b/game_shared/hl2mp/hl2mp_player_shared.cpp index 0464bb6d..a6ec6cb6 100644 --- a/game_shared/hl2mp/hl2mp_player_shared.cpp +++ b/game_shared/hl2mp/hl2mp_player_shared.cpp @@ -1,573 +1,573 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "cbase.h" - -#ifdef CLIENT_DLL -#include "c_hl2mp_player.h" -#include "prediction.h" -#define CRecipientFilter C_RecipientFilter -#else -#include "hl2mp_player.h" -#endif - -#include "engine/IEngineSound.h" -#include "SoundEmitterSystem/isoundemittersystembase.h" - -extern ConVar sv_footsteps; - -const char *g_ppszPlayerSoundPrefixNames[PLAYER_SOUNDS_MAX] = -{ - "NPC_Citizen", - "NPC_CombineS", - "NPC_MetroPolice", -}; - -const char *CHL2MP_Player::GetPlayerModelSoundPrefix( void ) -{ - return g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType]; -} - -void CHL2MP_Player::PrecacheFootStepSounds( void ) -{ - int iFootstepSounds = ARRAYSIZE( g_ppszPlayerSoundPrefixNames ); - int i; - - for ( i = 0; i < iFootstepSounds; ++i ) - { - char szFootStepName[128]; - - Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[i] ); - PrecacheScriptSound( szFootStepName ); - - Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[i] ); - PrecacheScriptSound( szFootStepName ); - } -} - -//----------------------------------------------------------------------------- -// Consider the weapon's built-in accuracy, this character's proficiency with -// the weapon, and the status of the target. Use this information to determine -// how accurately to shoot at the target. -//----------------------------------------------------------------------------- -Vector CHL2MP_Player::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) -{ - if ( pWeapon ) - return pWeapon->GetBulletSpread( WEAPON_PROFICIENCY_PERFECT ); - - return VECTOR_CONE_15DEGREES; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : step - -// fvol - -// force - force sound to play -//----------------------------------------------------------------------------- -void CHL2MP_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ) -{ - if ( gpGlobals->maxClients > 1 && !sv_footsteps.GetFloat() ) - return; - -#if defined( CLIENT_DLL ) - // during prediction play footstep sounds only once - if ( !prediction->IsFirstTimePredicted() ) - return; -#endif - - if ( GetFlags() & FL_DUCKING ) - return; - - m_Local.m_nStepside = !m_Local.m_nStepside; - - char szStepSound[128]; - - if ( m_Local.m_nStepside ) - { - Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); - } - else - { - Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); - } - - CSoundParameters params; - if ( GetParametersForSound( szStepSound, params, NULL ) == false ) - return; - - CRecipientFilter filter; - filter.AddRecipientsByPAS( vecOrigin ); - -#ifndef CLIENT_DLL - // im MP, server removed all players in origins PVS, these players - // generate the footsteps clientside - if ( gpGlobals->maxClients > 1 ) - filter.RemoveRecipientsByPVS( vecOrigin ); -#endif - - EmitSound_t ep; - ep.m_nChannel = CHAN_BODY; - ep.m_pSoundName = params.soundname; - ep.m_flVolume = fvol; - ep.m_SoundLevel = params.soundlevel; - ep.m_nFlags = 0; - ep.m_nPitch = params.pitch; - ep.m_pOrigin = &vecOrigin; - - EmitSound( filter, entindex(), ep ); -} - - -//========================== -// ANIMATION CODE -//========================== - - -// Below this many degrees, slow down turning rate linearly -#define FADE_TURN_DEGREES 45.0f -// After this, need to start turning feet -#define MAX_TORSO_ANGLE 90.0f -// Below this amount, don't play a turning animation/perform IK -#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f - -static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." ); -extern ConVar sv_backspeed; -extern ConVar mp_feetyawrate; -extern ConVar mp_facefronttime; -extern ConVar mp_ik; - -CPlayerAnimState::CPlayerAnimState( CHL2MP_Player *outer ) - : m_pOuter( outer ) -{ - m_flGaitYaw = 0.0f; - m_flGoalFeetYaw = 0.0f; - m_flCurrentFeetYaw = 0.0f; - m_flCurrentTorsoYaw = 0.0f; - m_flLastYaw = 0.0f; - m_flLastTurnTime = 0.0f; - m_flTurnCorrectionTime = 0.0f; -}; - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPlayerAnimState::Update() -{ - m_angRender = GetOuter()->GetLocalAngles(); - - ComputePoseParam_BodyYaw(); - ComputePoseParam_BodyPitch(GetOuter()->GetModelPtr()); - ComputePoseParam_BodyLookYaw(); - - ComputePlaybackRate(); - -#ifdef CLIENT_DLL - GetOuter()->UpdateLookAt(); -#endif - -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPlayerAnimState::ComputePlaybackRate() -{ - // Determine ideal playback rate - Vector vel; - GetOuterAbsVelocity( vel ); - - float speed = vel.Length2D(); - - bool isMoving = ( speed > 0.5f ) ? true : false; - - float maxspeed = GetOuter()->GetSequenceGroundSpeed( GetOuter()->GetSequence() ); - - if ( isMoving && ( maxspeed > 0.0f ) ) - { - float flFactor = 1.0f; - - // Note this gets set back to 1.0 if sequence changes due to ResetSequenceInfo below - GetOuter()->SetPlaybackRate( ( speed * flFactor ) / maxspeed ); - - // BUG BUG: - // This stuff really should be m_flPlaybackRate = speed / m_flGroundSpeed - } - else - { - GetOuter()->SetPlaybackRate( 1.0f ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : CBasePlayer -//----------------------------------------------------------------------------- -CHL2MP_Player *CPlayerAnimState::GetOuter() -{ - return m_pOuter; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : dt - -//----------------------------------------------------------------------------- -void CPlayerAnimState::EstimateYaw( void ) -{ - float dt = gpGlobals->frametime; - - if ( !dt ) - { - return; - } - - Vector est_velocity; - QAngle angles; - - GetOuterAbsVelocity( est_velocity ); - - angles = GetOuter()->GetLocalAngles(); - - if ( est_velocity[1] == 0 && est_velocity[0] == 0 ) - { - float flYawDiff = angles[YAW] - m_flGaitYaw; - flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; - if (flYawDiff > 180) - flYawDiff -= 360; - if (flYawDiff < -180) - flYawDiff += 360; - - if (dt < 0.25) - flYawDiff *= dt * 4; - else - flYawDiff *= dt; - - m_flGaitYaw += flYawDiff; - m_flGaitYaw = m_flGaitYaw - (int)(m_flGaitYaw / 360) * 360; - } - else - { - m_flGaitYaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); - - if (m_flGaitYaw > 180) - m_flGaitYaw = 180; - else if (m_flGaitYaw < -180) - m_flGaitYaw = -180; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Override for backpeddling -// Input : dt - -//----------------------------------------------------------------------------- -void CPlayerAnimState::ComputePoseParam_BodyYaw( void ) -{ - int iYaw = GetOuter()->LookupPoseParameter( "move_yaw" ); - if ( iYaw < 0 ) - return; - - // view direction relative to movement - float flYaw; - - EstimateYaw(); - - QAngle angles = GetOuter()->GetLocalAngles(); - float ang = angles[ YAW ]; - if ( ang > 180.0f ) - { - ang -= 360.0f; - } - else if ( ang < -180.0f ) - { - ang += 360.0f; - } - - // calc side to side turning - flYaw = ang - m_flGaitYaw; - // Invert for mapping into 8way blend - flYaw = -flYaw; - flYaw = flYaw - (int)(flYaw / 360) * 360; - - if (flYaw < -180) - { - flYaw = flYaw + 360; - } - else if (flYaw > 180) - { - flYaw = flYaw - 360; - } - - GetOuter()->SetPoseParameter( iYaw, flYaw ); - -#ifndef CLIENT_DLL - //Adrian: Make the model's angle match the legs so the hitboxes match on both sides. - GetOuter()->SetLocalAngles( QAngle( GetOuter()->GetAnimEyeAngles().x, m_flCurrentFeetYaw, 0 ) ); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) -{ - // Get pitch from v_angle - float flPitch = GetOuter()->GetLocalAngles()[ PITCH ]; - - if ( flPitch > 180.0f ) - { - flPitch -= 360.0f; - } - flPitch = clamp( flPitch, -90, 90 ); - - QAngle absangles = GetOuter()->GetAbsAngles(); - absangles.x = 0.0f; - m_angRender = absangles; - - // See if we have a blender for pitch - GetOuter()->SetPoseParameter( pStudioHdr, "aim_pitch", flPitch ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : goal - -// maxrate - -// dt - -// current - -// Output : int -//----------------------------------------------------------------------------- -int CPlayerAnimState::ConvergeAngles( float goal,float maxrate, float dt, float& current ) -{ - int direction = TURN_NONE; - - float anglediff = goal - current; - float anglediffabs = fabs( anglediff ); - - anglediff = AngleNormalize( anglediff ); - - float scale = 1.0f; - if ( anglediffabs <= FADE_TURN_DEGREES ) - { - scale = anglediffabs / FADE_TURN_DEGREES; - // Always do at least a bit of the turn ( 1% ) - scale = clamp( scale, 0.01f, 1.0f ); - } - - float maxmove = maxrate * dt * scale; - - if ( fabs( anglediff ) < maxmove ) - { - current = goal; - } - else - { - if ( anglediff > 0 ) - { - current += maxmove; - direction = TURN_LEFT; - } - else - { - current -= maxmove; - direction = TURN_RIGHT; - } - } - - current = AngleNormalize( current ); - - return direction; -} - -void CPlayerAnimState::ComputePoseParam_BodyLookYaw( void ) -{ - QAngle absangles = GetOuter()->GetAbsAngles(); - absangles.y = AngleNormalize( absangles.y ); - m_angRender = absangles; - - // See if we even have a blender for pitch - int upper_body_yaw = GetOuter()->LookupPoseParameter( "aim_yaw" ); - if ( upper_body_yaw < 0 ) - { - return; - } - - // Assume upper and lower bodies are aligned and that we're not turning - float flGoalTorsoYaw = 0.0f; - int turning = TURN_NONE; - float turnrate = 360.0f; - - Vector vel; - - GetOuterAbsVelocity( vel ); - - bool isMoving = ( vel.Length() > 1.0f ) ? true : false; - - if ( !isMoving ) - { - // Just stopped moving, try and clamp feet - if ( m_flLastTurnTime <= 0.0f ) - { - m_flLastTurnTime = gpGlobals->curtime; - m_flLastYaw = GetOuter()->GetAnimEyeAngles().y; - // Snap feet to be perfectly aligned with torso/eyes - m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; - m_flCurrentFeetYaw = m_flGoalFeetYaw; - m_nTurningInPlace = TURN_NONE; - } - - // If rotating in place, update stasis timer - if ( m_flLastYaw != GetOuter()->GetAnimEyeAngles().y ) - { - m_flLastTurnTime = gpGlobals->curtime; - m_flLastYaw = GetOuter()->GetAnimEyeAngles().y; - } - - if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) - { - m_flLastTurnTime = gpGlobals->curtime; - } - - turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); - - QAngle eyeAngles = GetOuter()->GetAnimEyeAngles(); - QAngle vAngle = GetOuter()->GetLocalAngles(); - - // See how far off current feetyaw is from true yaw - float yawdelta = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw; - yawdelta = AngleNormalize( yawdelta ); - - bool rotated_too_far = false; - - float yawmagnitude = fabs( yawdelta ); - - // If too far, then need to turn in place - if ( yawmagnitude > 45 ) - { - rotated_too_far = true; - } - - // Standing still for a while, rotate feet around to face forward - // Or rotated too far - // FIXME: Play an in place turning animation - if ( rotated_too_far || - ( gpGlobals->curtime > m_flLastTurnTime + mp_facefronttime.GetFloat() ) ) - { - m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; - m_flLastTurnTime = gpGlobals->curtime; - - /* float yd = m_flCurrentFeetYaw - m_flGoalFeetYaw; - if ( yd > 0 ) - { - m_nTurningInPlace = TURN_RIGHT; - } - else if ( yd < 0 ) - { - m_nTurningInPlace = TURN_LEFT; - } - else - { - m_nTurningInPlace = TURN_NONE; - } - - turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); - yawdelta = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw;*/ - - } - - // Snap upper body into position since the delta is already smoothed for the feet - flGoalTorsoYaw = yawdelta; - m_flCurrentTorsoYaw = flGoalTorsoYaw; - } - else - { - m_flLastTurnTime = 0.0f; - m_nTurningInPlace = TURN_NONE; - m_flCurrentFeetYaw = m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; - flGoalTorsoYaw = 0.0f; - m_flCurrentTorsoYaw = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw; - } - - - if ( turning == TURN_NONE ) - { - m_nTurningInPlace = turning; - } - - if ( m_nTurningInPlace != TURN_NONE ) - { - // If we're close to finishing the turn, then turn off the turning animation - if ( fabs( m_flCurrentFeetYaw - m_flGoalFeetYaw ) < MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION ) - { - m_nTurningInPlace = TURN_NONE; - } - } - - // Rotate entire body into position - absangles = GetOuter()->GetAbsAngles(); - absangles.y = m_flCurrentFeetYaw; - m_angRender = absangles; - - GetOuter()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -60.0f, 60.0f ) ); - - /* - // FIXME: Adrian, what is this? - int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" ); - - if ( body_yaw >= 0 ) - { - GetOuter()->SetPoseParameter( body_yaw, 30 ); - } - */ - -} - - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : activity - -// Output : Activity -//----------------------------------------------------------------------------- -Activity CPlayerAnimState::BodyYawTranslateActivity( Activity activity ) -{ - // Not even standing still, sigh - if ( activity != ACT_IDLE ) - return activity; - - // Not turning - switch ( m_nTurningInPlace ) - { - default: - case TURN_NONE: - return activity; - /* - case TURN_RIGHT: - return ACT_TURNRIGHT45; - case TURN_LEFT: - return ACT_TURNLEFT45; - */ - case TURN_RIGHT: - case TURN_LEFT: - return mp_ik.GetBool() ? ACT_TURN : activity; - } - - Assert( 0 ); - return activity; -} - -const QAngle& CPlayerAnimState::GetRenderAngles() -{ - return m_angRender; -} - - -void CPlayerAnimState::GetOuterAbsVelocity( Vector& vel ) -{ -#if defined( CLIENT_DLL ) - GetOuter()->EstimateAbsVelocity( vel ); -#else - vel = GetOuter()->GetAbsVelocity(); -#endif -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" + +#ifdef CLIENT_DLL +#include "c_hl2mp_player.h" +#include "prediction.h" +#define CRecipientFilter C_RecipientFilter +#else +#include "hl2mp_player.h" +#endif + +#include "engine/IEngineSound.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" + +extern ConVar sv_footsteps; + +const char *g_ppszPlayerSoundPrefixNames[PLAYER_SOUNDS_MAX] = +{ + "NPC_Citizen", + "NPC_CombineS", + "NPC_MetroPolice", +}; + +const char *CHL2MP_Player::GetPlayerModelSoundPrefix( void ) +{ + return g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType]; +} + +void CHL2MP_Player::PrecacheFootStepSounds( void ) +{ + int iFootstepSounds = ARRAYSIZE( g_ppszPlayerSoundPrefixNames ); + int i; + + for ( i = 0; i < iFootstepSounds; ++i ) + { + char szFootStepName[128]; + + Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[i] ); + PrecacheScriptSound( szFootStepName ); + + Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[i] ); + PrecacheScriptSound( szFootStepName ); + } +} + +//----------------------------------------------------------------------------- +// Consider the weapon's built-in accuracy, this character's proficiency with +// the weapon, and the status of the target. Use this information to determine +// how accurately to shoot at the target. +//----------------------------------------------------------------------------- +Vector CHL2MP_Player::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) +{ + if ( pWeapon ) + return pWeapon->GetBulletSpread( WEAPON_PROFICIENCY_PERFECT ); + + return VECTOR_CONE_15DEGREES; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : step - +// fvol - +// force - force sound to play +//----------------------------------------------------------------------------- +void CHL2MP_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ) +{ + if ( gpGlobals->maxClients > 1 && !sv_footsteps.GetFloat() ) + return; + +#if defined( CLIENT_DLL ) + // during prediction play footstep sounds only once + if ( !prediction->IsFirstTimePredicted() ) + return; +#endif + + if ( GetFlags() & FL_DUCKING ) + return; + + m_Local.m_nStepside = !m_Local.m_nStepside; + + char szStepSound[128]; + + if ( m_Local.m_nStepside ) + { + Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); + } + else + { + Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); + } + + CSoundParameters params; + if ( GetParametersForSound( szStepSound, params, NULL ) == false ) + return; + + CRecipientFilter filter; + filter.AddRecipientsByPAS( vecOrigin ); + +#ifndef CLIENT_DLL + // im MP, server removed all players in origins PVS, these players + // generate the footsteps clientside + if ( gpGlobals->maxClients > 1 ) + filter.RemoveRecipientsByPVS( vecOrigin ); +#endif + + EmitSound_t ep; + ep.m_nChannel = CHAN_BODY; + ep.m_pSoundName = params.soundname; + ep.m_flVolume = fvol; + ep.m_SoundLevel = params.soundlevel; + ep.m_nFlags = 0; + ep.m_nPitch = params.pitch; + ep.m_pOrigin = &vecOrigin; + + EmitSound( filter, entindex(), ep ); +} + + +//========================== +// ANIMATION CODE +//========================== + + +// Below this many degrees, slow down turning rate linearly +#define FADE_TURN_DEGREES 45.0f +// After this, need to start turning feet +#define MAX_TORSO_ANGLE 90.0f +// Below this amount, don't play a turning animation/perform IK +#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f + +static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." ); +extern ConVar sv_backspeed; +extern ConVar mp_feetyawrate; +extern ConVar mp_facefronttime; +extern ConVar mp_ik; + +CPlayerAnimState::CPlayerAnimState( CHL2MP_Player *outer ) + : m_pOuter( outer ) +{ + m_flGaitYaw = 0.0f; + m_flGoalFeetYaw = 0.0f; + m_flCurrentFeetYaw = 0.0f; + m_flCurrentTorsoYaw = 0.0f; + m_flLastYaw = 0.0f; + m_flLastTurnTime = 0.0f; + m_flTurnCorrectionTime = 0.0f; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerAnimState::Update() +{ + m_angRender = GetOuter()->GetLocalAngles(); + + ComputePoseParam_BodyYaw(); + ComputePoseParam_BodyPitch(GetOuter()->GetModelPtr()); + ComputePoseParam_BodyLookYaw(); + + ComputePlaybackRate(); + +#ifdef CLIENT_DLL + GetOuter()->UpdateLookAt(); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerAnimState::ComputePlaybackRate() +{ + // Determine ideal playback rate + Vector vel; + GetOuterAbsVelocity( vel ); + + float speed = vel.Length2D(); + + bool isMoving = ( speed > 0.5f ) ? true : false; + + float maxspeed = GetOuter()->GetSequenceGroundSpeed( GetOuter()->GetSequence() ); + + if ( isMoving && ( maxspeed > 0.0f ) ) + { + float flFactor = 1.0f; + + // Note this gets set back to 1.0 if sequence changes due to ResetSequenceInfo below + GetOuter()->SetPlaybackRate( ( speed * flFactor ) / maxspeed ); + + // BUG BUG: + // This stuff really should be m_flPlaybackRate = speed / m_flGroundSpeed + } + else + { + GetOuter()->SetPlaybackRate( 1.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CBasePlayer +//----------------------------------------------------------------------------- +CHL2MP_Player *CPlayerAnimState::GetOuter() +{ + return m_pOuter; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void CPlayerAnimState::EstimateYaw( void ) +{ + float dt = gpGlobals->frametime; + + if ( !dt ) + { + return; + } + + Vector est_velocity; + QAngle angles; + + GetOuterAbsVelocity( est_velocity ); + + angles = GetOuter()->GetLocalAngles(); + + if ( est_velocity[1] == 0 && est_velocity[0] == 0 ) + { + float flYawDiff = angles[YAW] - m_flGaitYaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_flGaitYaw += flYawDiff; + m_flGaitYaw = m_flGaitYaw - (int)(m_flGaitYaw / 360) * 360; + } + else + { + m_flGaitYaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + + if (m_flGaitYaw > 180) + m_flGaitYaw = 180; + else if (m_flGaitYaw < -180) + m_flGaitYaw = -180; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override for backpeddling +// Input : dt - +//----------------------------------------------------------------------------- +void CPlayerAnimState::ComputePoseParam_BodyYaw( void ) +{ + int iYaw = GetOuter()->LookupPoseParameter( "move_yaw" ); + if ( iYaw < 0 ) + return; + + // view direction relative to movement + float flYaw; + + EstimateYaw(); + + QAngle angles = GetOuter()->GetLocalAngles(); + float ang = angles[ YAW ]; + if ( ang > 180.0f ) + { + ang -= 360.0f; + } + else if ( ang < -180.0f ) + { + ang += 360.0f; + } + + // calc side to side turning + flYaw = ang - m_flGaitYaw; + // Invert for mapping into 8way blend + flYaw = -flYaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + + if (flYaw < -180) + { + flYaw = flYaw + 360; + } + else if (flYaw > 180) + { + flYaw = flYaw - 360; + } + + GetOuter()->SetPoseParameter( iYaw, flYaw ); + +#ifndef CLIENT_DLL + //Adrian: Make the model's angle match the legs so the hitboxes match on both sides. + GetOuter()->SetLocalAngles( QAngle( GetOuter()->GetAnimEyeAngles().x, m_flCurrentFeetYaw, 0 ) ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) +{ + // Get pitch from v_angle + float flPitch = GetOuter()->GetLocalAngles()[ PITCH ]; + + if ( flPitch > 180.0f ) + { + flPitch -= 360.0f; + } + flPitch = clamp( flPitch, -90, 90 ); + + QAngle absangles = GetOuter()->GetAbsAngles(); + absangles.x = 0.0f; + m_angRender = absangles; + + // See if we have a blender for pitch + GetOuter()->SetPoseParameter( pStudioHdr, "aim_pitch", flPitch ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : goal - +// maxrate - +// dt - +// current - +// Output : int +//----------------------------------------------------------------------------- +int CPlayerAnimState::ConvergeAngles( float goal,float maxrate, float dt, float& current ) +{ + int direction = TURN_NONE; + + float anglediff = goal - current; + float anglediffabs = fabs( anglediff ); + + anglediff = AngleNormalize( anglediff ); + + float scale = 1.0f; + if ( anglediffabs <= FADE_TURN_DEGREES ) + { + scale = anglediffabs / FADE_TURN_DEGREES; + // Always do at least a bit of the turn ( 1% ) + scale = clamp( scale, 0.01f, 1.0f ); + } + + float maxmove = maxrate * dt * scale; + + if ( fabs( anglediff ) < maxmove ) + { + current = goal; + } + else + { + if ( anglediff > 0 ) + { + current += maxmove; + direction = TURN_LEFT; + } + else + { + current -= maxmove; + direction = TURN_RIGHT; + } + } + + current = AngleNormalize( current ); + + return direction; +} + +void CPlayerAnimState::ComputePoseParam_BodyLookYaw( void ) +{ + QAngle absangles = GetOuter()->GetAbsAngles(); + absangles.y = AngleNormalize( absangles.y ); + m_angRender = absangles; + + // See if we even have a blender for pitch + int upper_body_yaw = GetOuter()->LookupPoseParameter( "aim_yaw" ); + if ( upper_body_yaw < 0 ) + { + return; + } + + // Assume upper and lower bodies are aligned and that we're not turning + float flGoalTorsoYaw = 0.0f; + int turning = TURN_NONE; + float turnrate = 360.0f; + + Vector vel; + + GetOuterAbsVelocity( vel ); + + bool isMoving = ( vel.Length() > 1.0f ) ? true : false; + + if ( !isMoving ) + { + // Just stopped moving, try and clamp feet + if ( m_flLastTurnTime <= 0.0f ) + { + m_flLastTurnTime = gpGlobals->curtime; + m_flLastYaw = GetOuter()->GetAnimEyeAngles().y; + // Snap feet to be perfectly aligned with torso/eyes + m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; + m_flCurrentFeetYaw = m_flGoalFeetYaw; + m_nTurningInPlace = TURN_NONE; + } + + // If rotating in place, update stasis timer + if ( m_flLastYaw != GetOuter()->GetAnimEyeAngles().y ) + { + m_flLastTurnTime = gpGlobals->curtime; + m_flLastYaw = GetOuter()->GetAnimEyeAngles().y; + } + + if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) + { + m_flLastTurnTime = gpGlobals->curtime; + } + + turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); + + QAngle eyeAngles = GetOuter()->GetAnimEyeAngles(); + QAngle vAngle = GetOuter()->GetLocalAngles(); + + // See how far off current feetyaw is from true yaw + float yawdelta = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw; + yawdelta = AngleNormalize( yawdelta ); + + bool rotated_too_far = false; + + float yawmagnitude = fabs( yawdelta ); + + // If too far, then need to turn in place + if ( yawmagnitude > 45 ) + { + rotated_too_far = true; + } + + // Standing still for a while, rotate feet around to face forward + // Or rotated too far + // FIXME: Play an in place turning animation + if ( rotated_too_far || + ( gpGlobals->curtime > m_flLastTurnTime + mp_facefronttime.GetFloat() ) ) + { + m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; + m_flLastTurnTime = gpGlobals->curtime; + + /* float yd = m_flCurrentFeetYaw - m_flGoalFeetYaw; + if ( yd > 0 ) + { + m_nTurningInPlace = TURN_RIGHT; + } + else if ( yd < 0 ) + { + m_nTurningInPlace = TURN_LEFT; + } + else + { + m_nTurningInPlace = TURN_NONE; + } + + turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); + yawdelta = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw;*/ + + } + + // Snap upper body into position since the delta is already smoothed for the feet + flGoalTorsoYaw = yawdelta; + m_flCurrentTorsoYaw = flGoalTorsoYaw; + } + else + { + m_flLastTurnTime = 0.0f; + m_nTurningInPlace = TURN_NONE; + m_flCurrentFeetYaw = m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; + flGoalTorsoYaw = 0.0f; + m_flCurrentTorsoYaw = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw; + } + + + if ( turning == TURN_NONE ) + { + m_nTurningInPlace = turning; + } + + if ( m_nTurningInPlace != TURN_NONE ) + { + // If we're close to finishing the turn, then turn off the turning animation + if ( fabs( m_flCurrentFeetYaw - m_flGoalFeetYaw ) < MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION ) + { + m_nTurningInPlace = TURN_NONE; + } + } + + // Rotate entire body into position + absangles = GetOuter()->GetAbsAngles(); + absangles.y = m_flCurrentFeetYaw; + m_angRender = absangles; + + GetOuter()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -60.0f, 60.0f ) ); + + /* + // FIXME: Adrian, what is this? + int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" ); + + if ( body_yaw >= 0 ) + { + GetOuter()->SetPoseParameter( body_yaw, 30 ); + } + */ + +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : activity - +// Output : Activity +//----------------------------------------------------------------------------- +Activity CPlayerAnimState::BodyYawTranslateActivity( Activity activity ) +{ + // Not even standing still, sigh + if ( activity != ACT_IDLE ) + return activity; + + // Not turning + switch ( m_nTurningInPlace ) + { + default: + case TURN_NONE: + return activity; + /* + case TURN_RIGHT: + return ACT_TURNRIGHT45; + case TURN_LEFT: + return ACT_TURNLEFT45; + */ + case TURN_RIGHT: + case TURN_LEFT: + return mp_ik.GetBool() ? ACT_TURN : activity; + } + + Assert( 0 ); + return activity; +} + +const QAngle& CPlayerAnimState::GetRenderAngles() +{ + return m_angRender; +} + + +void CPlayerAnimState::GetOuterAbsVelocity( Vector& vel ) +{ +#if defined( CLIENT_DLL ) + GetOuter()->EstimateAbsVelocity( vel ); +#else + vel = GetOuter()->GetAbsVelocity(); +#endif +} diff --git a/game_shared/hl2mp/hl2mp_player_shared.h b/game_shared/hl2mp/hl2mp_player_shared.h index ef68d217..558ac3b1 100644 --- a/game_shared/hl2mp/hl2mp_player_shared.h +++ b/game_shared/hl2mp/hl2mp_player_shared.h @@ -95,3 +95,4 @@ private: }; #endif //HL2MP_PLAYER_SHARED_h + diff --git a/game_shared/hl2mp/weapon_crowbar.cpp b/game_shared/hl2mp/weapon_crowbar.cpp index e91b3a39..1b871c1c 100644 --- a/game_shared/hl2mp/weapon_crowbar.cpp +++ b/game_shared/hl2mp/weapon_crowbar.cpp @@ -1,261 +1,261 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Crowbar - an old favorite -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "weapon_hl2mpbasehlmpcombatweapon.h" -#include "gamerules.h" -#include "ammodef.h" -#include "mathlib.h" -#include "in_buttons.h" -#include "weapon_hl2mpbasebasebludgeon.h" -#include "vstdlib/random.h" -#include "npcevent.h" - -#if defined( CLIENT_DLL ) - #include "c_hl2mp_player.h" -#else - #include "hl2mp_player.h" - #include "ai_basenpc.h" -#endif - - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define CROWBAR_RANGE 75.0f -#define CROWBAR_REFIRE 0.6f - - -#ifdef CLIENT_DLL -#define CWeaponCrowbar C_WeaponCrowbar -#endif - -//----------------------------------------------------------------------------- -// CWeaponCrowbar -//----------------------------------------------------------------------------- - -class CWeaponCrowbar : public CBaseHL2MPBludgeonWeapon -{ -public: - DECLARE_CLASS( CWeaponCrowbar, CBaseHL2MPBludgeonWeapon ); - - DECLARE_NETWORKCLASS(); - DECLARE_PREDICTABLE(); - -#ifndef CLIENT_DLL - DECLARE_ACTTABLE(); -#endif - - CWeaponCrowbar(); - - float GetRange( void ) { return CROWBAR_RANGE; } - float GetFireRate( void ) { return CROWBAR_REFIRE; } - - void AddViewKick( void ); - float GetDamageForActivity( Activity hitActivity ); - void SecondaryAttack( void ) { return; } - - void Drop( const Vector &vecVelocity ); - - - // Animation event -#ifndef CLIENT_DLL - virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); - void HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); - int WeaponMeleeAttack1Condition( float flDot, float flDist ); -#endif - - CWeaponCrowbar( const CWeaponCrowbar & ); - -private: - -}; - -//----------------------------------------------------------------------------- -// CWeaponCrowbar -//----------------------------------------------------------------------------- - -IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCrowbar, DT_WeaponCrowbar ) - -BEGIN_NETWORK_TABLE( CWeaponCrowbar, DT_WeaponCrowbar ) -END_NETWORK_TABLE() - -BEGIN_PREDICTION_DATA( CWeaponCrowbar ) -END_PREDICTION_DATA() - -LINK_ENTITY_TO_CLASS( weapon_crowbar, CWeaponCrowbar ); -PRECACHE_WEAPON_REGISTER( weapon_crowbar ); - -#ifndef CLIENT_DLL - -acttable_t CWeaponCrowbar::m_acttable[] = -{ - { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, - { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, - { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, - { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, - { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, - { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, - { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, - { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, -}; - -IMPLEMENT_ACTTABLE(CWeaponCrowbar); - -#endif - -//----------------------------------------------------------------------------- -// Constructor -//----------------------------------------------------------------------------- -CWeaponCrowbar::CWeaponCrowbar( void ) -{ -} - -//----------------------------------------------------------------------------- -// Purpose: Get the damage amount for the animation we're doing -// Input : hitActivity - currently played activity -// Output : Damage amount -//----------------------------------------------------------------------------- -float CWeaponCrowbar::GetDamageForActivity( Activity hitActivity ) -{ - return 25.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: Add in a view kick for this weapon -//----------------------------------------------------------------------------- -void CWeaponCrowbar::AddViewKick( void ) -{ - CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); - - if ( pPlayer == NULL ) - return; - - QAngle punchAng; - - punchAng.x = random->RandomFloat( 1.0f, 2.0f ); - punchAng.y = random->RandomFloat( -2.0f, -1.0f ); - punchAng.z = 0.0f; - - pPlayer->ViewPunch( punchAng ); -} - - -#ifndef CLIENT_DLL -//----------------------------------------------------------------------------- -// Animation event handlers -//----------------------------------------------------------------------------- -void CWeaponCrowbar::HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) -{ - // Trace up or down based on where the enemy is... - // But only if we're basically facing that direction - Vector vecDirection; - AngleVectors( GetAbsAngles(), &vecDirection ); - - Vector vecEnd; - VectorMA( pOperator->Weapon_ShootPosition(), 50, vecDirection, vecEnd ); - CBaseEntity *pHurt = pOperator->CheckTraceHullAttack( pOperator->Weapon_ShootPosition(), vecEnd, - Vector(-16,-16,-16), Vector(36,36,36), GetDamageForActivity( GetActivity() ), DMG_CLUB, 0.75 ); - - // did I hit someone? - if ( pHurt ) - { - // play sound - WeaponSound( MELEE_HIT ); - - // Fake a trace impact, so the effects work out like a player's crowbaw - trace_t traceHit; - UTIL_TraceLine( pOperator->Weapon_ShootPosition(), pHurt->GetAbsOrigin(), MASK_SHOT_HULL, pOperator, COLLISION_GROUP_NONE, &traceHit ); - ImpactEffect( traceHit ); - } - else - { - WeaponSound( MELEE_MISS ); - } -} - - -//----------------------------------------------------------------------------- -// Animation event -//----------------------------------------------------------------------------- -void CWeaponCrowbar::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) -{ - switch( pEvent->event ) - { - case EVENT_WEAPON_MELEE_HIT: - HandleAnimEventMeleeHit( pEvent, pOperator ); - break; - - default: - BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); - break; - } -} - -//----------------------------------------------------------------------------- -// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) -//----------------------------------------------------------------------------- -ConVar sk_crowbar_lead_time( "sk_crowbar_lead_time", "0.9" ); - -int CWeaponCrowbar::WeaponMeleeAttack1Condition( float flDot, float flDist ) -{ - // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) - CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); - CBaseEntity *pEnemy = pNPC->GetEnemy(); - if (!pEnemy) - return COND_NONE; - - Vector vecVelocity; - vecVelocity = pEnemy->GetSmoothedVelocity( ); - - // Project where the enemy will be in a little while - float dt = sk_crowbar_lead_time.GetFloat(); - dt += random->RandomFloat( -0.3f, 0.2f ); - if ( dt < 0.0f ) - dt = 0.0f; - - Vector vecExtrapolatedPos; - VectorMA( pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos ); - - Vector vecDelta; - VectorSubtract( vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta ); - - if ( fabs( vecDelta.z ) > 70 ) - { - return COND_TOO_FAR_TO_ATTACK; - } - - Vector vecForward = pNPC->BodyDirection2D( ); - vecDelta.z = 0.0f; - float flExtrapolatedDist = Vector2DNormalize( vecDelta.AsVector2D() ); - if ((flDist > 64) && (flExtrapolatedDist > 64)) - { - return COND_TOO_FAR_TO_ATTACK; - } - - float flExtrapolatedDot = DotProduct2D( vecDelta.AsVector2D(), vecForward.AsVector2D() ); - if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) - { - return COND_NOT_FACING_ATTACK; - } - - return COND_CAN_MELEE_ATTACK1; -} - -#endif - - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponCrowbar::Drop( const Vector &vecVelocity ) -{ -#ifndef CLIENT_DLL - UTIL_Remove( this ); -#endif -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Crowbar - an old favorite +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "weapon_hl2mpbasehlmpcombatweapon.h" +#include "gamerules.h" +#include "ammodef.h" +#include "mathlib.h" +#include "in_buttons.h" +#include "weapon_hl2mpbasebasebludgeon.h" +#include "vstdlib/random.h" +#include "npcevent.h" + +#if defined( CLIENT_DLL ) + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" + #include "ai_basenpc.h" +#endif + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define CROWBAR_RANGE 75.0f +#define CROWBAR_REFIRE 0.6f + + +#ifdef CLIENT_DLL +#define CWeaponCrowbar C_WeaponCrowbar +#endif + +//----------------------------------------------------------------------------- +// CWeaponCrowbar +//----------------------------------------------------------------------------- + +class CWeaponCrowbar : public CBaseHL2MPBludgeonWeapon +{ +public: + DECLARE_CLASS( CWeaponCrowbar, CBaseHL2MPBludgeonWeapon ); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + + CWeaponCrowbar(); + + float GetRange( void ) { return CROWBAR_RANGE; } + float GetFireRate( void ) { return CROWBAR_REFIRE; } + + void AddViewKick( void ); + float GetDamageForActivity( Activity hitActivity ); + void SecondaryAttack( void ) { return; } + + void Drop( const Vector &vecVelocity ); + + + // Animation event +#ifndef CLIENT_DLL + virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + void HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#endif + + CWeaponCrowbar( const CWeaponCrowbar & ); + +private: + +}; + +//----------------------------------------------------------------------------- +// CWeaponCrowbar +//----------------------------------------------------------------------------- + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCrowbar, DT_WeaponCrowbar ) + +BEGIN_NETWORK_TABLE( CWeaponCrowbar, DT_WeaponCrowbar ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponCrowbar ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_crowbar, CWeaponCrowbar ); +PRECACHE_WEAPON_REGISTER( weapon_crowbar ); + +#ifndef CLIENT_DLL + +acttable_t CWeaponCrowbar::m_acttable[] = +{ + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponCrowbar); + +#endif + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CWeaponCrowbar::CWeaponCrowbar( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Get the damage amount for the animation we're doing +// Input : hitActivity - currently played activity +// Output : Damage amount +//----------------------------------------------------------------------------- +float CWeaponCrowbar::GetDamageForActivity( Activity hitActivity ) +{ + return 25.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Add in a view kick for this weapon +//----------------------------------------------------------------------------- +void CWeaponCrowbar::AddViewKick( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer == NULL ) + return; + + QAngle punchAng; + + punchAng.x = random->RandomFloat( 1.0f, 2.0f ); + punchAng.y = random->RandomFloat( -2.0f, -1.0f ); + punchAng.z = 0.0f; + + pPlayer->ViewPunch( punchAng ); +} + + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Animation event handlers +//----------------------------------------------------------------------------- +void CWeaponCrowbar::HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + // Trace up or down based on where the enemy is... + // But only if we're basically facing that direction + Vector vecDirection; + AngleVectors( GetAbsAngles(), &vecDirection ); + + Vector vecEnd; + VectorMA( pOperator->Weapon_ShootPosition(), 50, vecDirection, vecEnd ); + CBaseEntity *pHurt = pOperator->CheckTraceHullAttack( pOperator->Weapon_ShootPosition(), vecEnd, + Vector(-16,-16,-16), Vector(36,36,36), static_cast(GetDamageForActivity( GetActivity() )), DMG_CLUB, 0.75 ); + + // did I hit someone? + if ( pHurt ) + { + // play sound + WeaponSound( MELEE_HIT ); + + // Fake a trace impact, so the effects work out like a player's crowbaw + trace_t traceHit; + UTIL_TraceLine( pOperator->Weapon_ShootPosition(), pHurt->GetAbsOrigin(), MASK_SHOT_HULL, pOperator, COLLISION_GROUP_NONE, &traceHit ); + ImpactEffect( traceHit ); + } + else + { + WeaponSound( MELEE_MISS ); + } +} + + +//----------------------------------------------------------------------------- +// Animation event +//----------------------------------------------------------------------------- +void CWeaponCrowbar::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_MELEE_HIT: + HandleAnimEventMeleeHit( pEvent, pOperator ); + break; + + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} + +//----------------------------------------------------------------------------- +// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) +//----------------------------------------------------------------------------- +ConVar sk_crowbar_lead_time( "sk_crowbar_lead_time", "0.9" ); + +int CWeaponCrowbar::WeaponMeleeAttack1Condition( float flDot, float flDist ) +{ + // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) + CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); + CBaseEntity *pEnemy = pNPC->GetEnemy(); + if (!pEnemy) + return COND_NONE; + + Vector vecVelocity; + vecVelocity = pEnemy->GetSmoothedVelocity( ); + + // Project where the enemy will be in a little while + float dt = sk_crowbar_lead_time.GetFloat(); + dt += random->RandomFloat( -0.3f, 0.2f ); + if ( dt < 0.0f ) + dt = 0.0f; + + Vector vecExtrapolatedPos; + VectorMA( pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos ); + + Vector vecDelta; + VectorSubtract( vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta ); + + if ( fabs( vecDelta.z ) > 70 ) + { + return COND_TOO_FAR_TO_ATTACK; + } + + Vector vecForward = pNPC->BodyDirection2D( ); + vecDelta.z = 0.0f; + float flExtrapolatedDist = Vector2DNormalize( vecDelta.AsVector2D() ); + if ((flDist > 64) && (flExtrapolatedDist > 64)) + { + return COND_TOO_FAR_TO_ATTACK; + } + + float flExtrapolatedDot = DotProduct2D( vecDelta.AsVector2D(), vecForward.AsVector2D() ); + if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_MELEE_ATTACK1; +} + +#endif + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrowbar::Drop( const Vector &vecVelocity ) +{ +#ifndef CLIENT_DLL + UTIL_Remove( this ); +#endif +} diff --git a/game_shared/hl2mp/weapon_hl2mpbase.cpp b/game_shared/hl2mp/weapon_hl2mpbase.cpp index 1cec0965..45d2f8e2 100644 --- a/game_shared/hl2mp/weapon_hl2mpbase.cpp +++ b/game_shared/hl2mp/weapon_hl2mpbase.cpp @@ -45,7 +45,7 @@ bool IsAmmoType( int iAmmoType, const char *pAmmoName ) return GetAmmoDef()->Index( pAmmoName ) == iAmmoType; } -static const char * s_WeaponAliasInfo[] = +/*static const char * s_WeaponAliasInfo[] = { "none", // WEAPON_NONE = 0, @@ -53,7 +53,7 @@ static const char * s_WeaponAliasInfo[] = "shotgun", //WEAPON_AMERKNIFE, NULL, // end of list marker -}; +};*/ // ----------------------------------------------------------------------------- // diff --git a/game_shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp b/game_shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp index e13e3c8c..812bc4a5 100644 --- a/game_shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp +++ b/game_shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp @@ -361,3 +361,4 @@ void CBaseHL2MPBludgeonWeapon::Swing( int bIsSecondary ) m_flNextPrimaryAttack = gpGlobals->curtime + GetFireRate(); m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); } + diff --git a/game_shared/hl2mp/weapon_hl2mpbasebasebludgeon.h b/game_shared/hl2mp/weapon_hl2mpbasebasebludgeon.h index e19bffd6..b4b3301e 100644 --- a/game_shared/hl2mp/weapon_hl2mpbasebasebludgeon.h +++ b/game_shared/hl2mp/weapon_hl2mpbasebasebludgeon.h @@ -1,66 +1,66 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: The class from which all bludgeon melee -// weapons are derived. -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#include "weapon_hl2mpbasehlmpcombatweapon.h" - -#ifndef BASEBLUDGEONWEAPON_H -#define BASEBLUDGEONWEAPON_H - -#ifdef _WIN32 -#pragma once -#endif - - -#if defined( CLIENT_DLL ) -#define CBaseHL2MPBludgeonWeapon C_BaseHL2MPBludgeonWeapon -#endif - -//========================================================= -// CBaseHLBludgeonWeapon -//========================================================= -class CBaseHL2MPBludgeonWeapon : public CBaseHL2MPCombatWeapon -{ - DECLARE_CLASS( CBaseHL2MPBludgeonWeapon, CBaseHL2MPCombatWeapon ); -public: - CBaseHL2MPBludgeonWeapon(); - - DECLARE_NETWORKCLASS(); - DECLARE_PREDICTABLE(); - - virtual void Spawn( void ); - virtual void Precache( void ); - - //Attack functions - virtual void PrimaryAttack( void ); - virtual void SecondaryAttack( void ); - - virtual void ItemPostFrame( void ); - - //Functions to select animation sequences - virtual Activity GetPrimaryAttackActivity( void ) { return ACT_VM_HITCENTER; } - virtual Activity GetSecondaryAttackActivity( void ) { return ACT_VM_HITCENTER2; } - - virtual float GetFireRate( void ) { return 0.2f; } - virtual float GetRange( void ) { return 32.0f; } - virtual float GetDamageForActivity( Activity hitActivity ) { return 1.0f; } - - CBaseHL2MPBludgeonWeapon( const CBaseHL2MPBludgeonWeapon & ); - -protected: - virtual void ImpactEffect( trace_t &trace ); - -private: - bool ImpactWater( const Vector &start, const Vector &end ); - void Swing( int bIsSecondary ); - void Hit( trace_t &traceHit, Activity nHitActivity ); - Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); -}; - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: The class from which all bludgeon melee +// weapons are derived. +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#ifndef BASEBLUDGEONWEAPON_H +#define BASEBLUDGEONWEAPON_H + +#ifdef _WIN32 +#pragma once +#endif + + +#if defined( CLIENT_DLL ) +#define CBaseHL2MPBludgeonWeapon C_BaseHL2MPBludgeonWeapon +#endif + +//========================================================= +// CBaseHLBludgeonWeapon +//========================================================= +class CBaseHL2MPBludgeonWeapon : public CBaseHL2MPCombatWeapon +{ + DECLARE_CLASS( CBaseHL2MPBludgeonWeapon, CBaseHL2MPCombatWeapon ); +public: + CBaseHL2MPBludgeonWeapon(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + virtual void Spawn( void ); + virtual void Precache( void ); + + //Attack functions + virtual void PrimaryAttack( void ); + virtual void SecondaryAttack( void ); + + virtual void ItemPostFrame( void ); + + //Functions to select animation sequences + virtual Activity GetPrimaryAttackActivity( void ) { return ACT_VM_HITCENTER; } + virtual Activity GetSecondaryAttackActivity( void ) { return ACT_VM_HITCENTER2; } + + virtual float GetFireRate( void ) { return 0.2f; } + virtual float GetRange( void ) { return 32.0f; } + virtual float GetDamageForActivity( Activity hitActivity ) { return 1.0f; } + + CBaseHL2MPBludgeonWeapon( const CBaseHL2MPBludgeonWeapon & ); + +protected: + virtual void ImpactEffect( trace_t &trace ); + +private: + bool ImpactWater( const Vector &start, const Vector &end ); + void Swing( int bIsSecondary ); + void Hit( trace_t &traceHit, Activity nHitActivity ); + Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); +}; + +#endif diff --git a/game_shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp b/game_shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp index 5a23d029..b69e314a 100644 --- a/game_shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp +++ b/game_shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp @@ -1,416 +1,416 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "cbase.h" -#include "weapon_hl2mpbasehlmpcombatweapon.h" - -#include "hl2mp_player_shared.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -LINK_ENTITY_TO_CLASS( basehl2mpcombatweapon, CBaseHL2MPCombatWeapon ); - -IMPLEMENT_NETWORKCLASS_ALIASED( BaseHL2MPCombatWeapon , DT_BaseHL2MPCombatWeapon ) - -BEGIN_NETWORK_TABLE( CBaseHL2MPCombatWeapon , DT_BaseHL2MPCombatWeapon ) -#if !defined( CLIENT_DLL ) -// SendPropInt( SENDINFO( m_bReflectViewModelAnimations ), 1, SPROP_UNSIGNED ), -#else -// RecvPropInt( RECVINFO( m_bReflectViewModelAnimations ) ), -#endif -END_NETWORK_TABLE() - - -#if !defined( CLIENT_DLL ) - -#include "globalstate.h" - -//--------------------------------------------------------- -// Save/Restore -//--------------------------------------------------------- -BEGIN_DATADESC( CBaseHL2MPCombatWeapon ) - - DEFINE_FIELD( m_bLowered, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flRaiseTime, FIELD_TIME ), - DEFINE_FIELD( m_flHolsterTime, FIELD_TIME ), - -END_DATADESC() - -#endif - -BEGIN_PREDICTION_DATA( CBaseHL2MPCombatWeapon ) -END_PREDICTION_DATA() - -extern ConVar sk_auto_reload_time; - -CBaseHL2MPCombatWeapon::CBaseHL2MPCombatWeapon( void ) -{ - -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CBaseHL2MPCombatWeapon::ItemHolsterFrame( void ) -{ - BaseClass::ItemHolsterFrame(); - - // Must be player held - if ( GetOwner() && GetOwner()->IsPlayer() == false ) - return; - - // We can't be active - if ( GetOwner()->GetActiveWeapon() == this ) - return; - - // If it's been longer than three seconds, reload - if ( ( gpGlobals->curtime - m_flHolsterTime ) > sk_auto_reload_time.GetFloat() ) - { - // Just load the clip with no animations - FinishReload(); - m_flHolsterTime = gpGlobals->curtime; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Drops the weapon into a lowered pose -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHL2MPCombatWeapon::Lower( void ) -{ - //Don't bother if we don't have the animation - if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) - return false; - - m_bLowered = true; - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Brings the weapon up to the ready position -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHL2MPCombatWeapon::Ready( void ) -{ - //Don't bother if we don't have the animation - if ( SelectWeightedSequence( ACT_VM_LOWERED_TO_IDLE ) == ACTIVITY_NOT_AVAILABLE ) - return false; - - m_bLowered = false; - m_flRaiseTime = gpGlobals->curtime + 0.5f; - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHL2MPCombatWeapon::Deploy( void ) -{ - // If we should be lowered, deploy in the lowered position - // We have to ask the player if the last time it checked, the weapon was lowered - if ( GetOwner() && GetOwner()->IsPlayer() ) - { - CHL2MP_Player *pPlayer = assert_cast( GetOwner() ); - if ( pPlayer->IsWeaponLowered() ) - { - if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) != ACTIVITY_NOT_AVAILABLE ) - { - if ( DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_VM_IDLE_LOWERED, (char*)GetAnimPrefix() ) ) - { - m_bLowered = true; - - // Stomp the next attack time to fix the fact that the lower idles are long - pPlayer->SetNextAttack( gpGlobals->curtime + 1.0 ); - m_flNextPrimaryAttack = gpGlobals->curtime + 1.0; - m_flNextSecondaryAttack = gpGlobals->curtime + 1.0; - return true; - } - } - } - } - - m_bLowered = false; - return BaseClass::Deploy(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHL2MPCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) -{ - if ( BaseClass::Holster( pSwitchingTo ) ) - { - SetWeaponVisible( false ); - m_flHolsterTime = gpGlobals->curtime; - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CBaseHL2MPCombatWeapon::WeaponShouldBeLowered( void ) -{ - // Can't be in the middle of another animation - if ( GetIdealActivity() != ACT_VM_IDLE_LOWERED && GetIdealActivity() != ACT_VM_IDLE && - GetIdealActivity() != ACT_VM_IDLE_TO_LOWERED && GetIdealActivity() != ACT_VM_LOWERED_TO_IDLE ) - return false; - - if ( m_bLowered ) - return true; - -#if !defined( CLIENT_DLL ) - - if ( GlobalEntity_GetState( "friendly_encounter" ) == GLOBAL_ON ) - return true; - -#endif - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Allows the weapon to choose proper weapon idle animation -//----------------------------------------------------------------------------- -void CBaseHL2MPCombatWeapon::WeaponIdle( void ) -{ - //See if we should idle high or low - if ( WeaponShouldBeLowered() ) - { - // Move to lowered position if we're not there yet - if ( GetActivity() != ACT_VM_IDLE_LOWERED && GetActivity() != ACT_VM_IDLE_TO_LOWERED - && GetActivity() != ACT_TRANSITION ) - { - SendWeaponAnim( ACT_VM_IDLE_LOWERED ); - } - else if ( HasWeaponIdleTimeElapsed() ) - { - // Keep idling low - SendWeaponAnim( ACT_VM_IDLE_LOWERED ); - } - } - else - { - // See if we need to raise immediately - if ( m_flRaiseTime < gpGlobals->curtime && GetActivity() == ACT_VM_IDLE_LOWERED ) - { - SendWeaponAnim( ACT_VM_IDLE ); - } - else if ( HasWeaponIdleTimeElapsed() ) - { - SendWeaponAnim( ACT_VM_IDLE ); - } - } -} - -#if defined( CLIENT_DLL ) - -#define HL2_BOB_CYCLE_MIN 1.0f -#define HL2_BOB_CYCLE_MAX 0.45f -#define HL2_BOB 0.002f -#define HL2_BOB_UP 0.5f - -extern float g_lateralBob; -extern float g_verticalBob; - -static ConVar cl_bobcycle( "cl_bobcycle","0.8" ); -static ConVar cl_bob( "cl_bob","0.002" ); -static ConVar cl_bobup( "cl_bobup","0.5" ); - -// Register these cvars if needed for easy tweaking -static ConVar v_iyaw_cycle( "v_iyaw_cycle", "2", FCVAR_REPLICATED | FCVAR_CHEAT ); -static ConVar v_iroll_cycle( "v_iroll_cycle", "0.5", FCVAR_REPLICATED | FCVAR_CHEAT ); -static ConVar v_ipitch_cycle( "v_ipitch_cycle", "1", FCVAR_REPLICATED | FCVAR_CHEAT ); -static ConVar v_iyaw_level( "v_iyaw_level", "0.3", FCVAR_REPLICATED | FCVAR_CHEAT ); -static ConVar v_iroll_level( "v_iroll_level", "0.1", FCVAR_REPLICATED | FCVAR_CHEAT ); -static ConVar v_ipitch_level( "v_ipitch_level", "0.3", FCVAR_REPLICATED | FCVAR_CHEAT ); - -//----------------------------------------------------------------------------- -// Purpose: -// Output : float -//----------------------------------------------------------------------------- -float CBaseHL2MPCombatWeapon::CalcViewmodelBob( void ) -{ - static float bobtime; - static float lastbobtime; - float cycle; - - CBasePlayer *player = ToBasePlayer( GetOwner() ); - //Assert( player ); - - //NOTENOTE: For now, let this cycle continue when in the air, because it snaps badly without it - - if ( ( !gpGlobals->frametime ) || ( player == NULL ) ) - { - //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) - return 0.0f;// just use old value - } - - //Find the speed of the player - float speed = player->GetLocalVelocity().Length2D(); - - //FIXME: This maximum speed value must come from the server. - // MaxSpeed() is not sufficient for dealing with sprinting - jdw - - speed = clamp( speed, -320, 320 ); - - float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f ); - - bobtime += ( gpGlobals->curtime - lastbobtime ) * bob_offset; - lastbobtime = gpGlobals->curtime; - - //Calculate the vertical bob - cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX)*HL2_BOB_CYCLE_MAX; - cycle /= HL2_BOB_CYCLE_MAX; - - if ( cycle < HL2_BOB_UP ) - { - cycle = M_PI * cycle / HL2_BOB_UP; - } - else - { - cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); - } - - g_verticalBob = speed*0.005f; - g_verticalBob = g_verticalBob*0.3 + g_verticalBob*0.7*sin(cycle); - - g_verticalBob = clamp( g_verticalBob, -7.0f, 4.0f ); - - //Calculate the lateral bob - cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX*2)*HL2_BOB_CYCLE_MAX*2; - cycle /= HL2_BOB_CYCLE_MAX*2; - - if ( cycle < HL2_BOB_UP ) - { - cycle = M_PI * cycle / HL2_BOB_UP; - } - else - { - cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); - } - - g_lateralBob = speed*0.005f; - g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); - g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); - - //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) - return 0.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &origin - -// &angles - -// viewmodelindex - -//----------------------------------------------------------------------------- -void CBaseHL2MPCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) -{ - Vector forward, right; - AngleVectors( angles, &forward, &right, NULL ); - - CalcViewmodelBob(); - - // Apply bob, but scaled down to 40% - VectorMA( origin, g_verticalBob * 0.1f, forward, origin ); - - // Z bob a bit more - origin[2] += g_verticalBob * 0.1f; - - // bob the angles - angles[ ROLL ] += g_verticalBob * 0.5f; - angles[ PITCH ] -= g_verticalBob * 0.4f; - - angles[ YAW ] -= g_lateralBob * 0.3f; - - VectorMA( origin, g_lateralBob * 0.8f, right, origin ); -} - -//----------------------------------------------------------------------------- -Vector CBaseHL2MPCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) -{ - return BaseClass::GetBulletSpread( proficiency ); -} - -//----------------------------------------------------------------------------- -float CBaseHL2MPCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) -{ - return BaseClass::GetSpreadBias( proficiency ); -} -//----------------------------------------------------------------------------- - -const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetProficiencyValues() -{ - return NULL; -} - -#else - -// Server stubs -float CBaseHL2MPCombatWeapon::CalcViewmodelBob( void ) -{ - return 0.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &origin - -// &angles - -// viewmodelindex - -//----------------------------------------------------------------------------- -void CBaseHL2MPCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) -{ -} - - -//----------------------------------------------------------------------------- -Vector CBaseHL2MPCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) -{ - Vector baseSpread = BaseClass::GetBulletSpread( proficiency ); - - const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); - float flModifier = (pProficiencyValues)[ proficiency ].spreadscale; - return ( baseSpread * flModifier ); -} - -//----------------------------------------------------------------------------- -float CBaseHL2MPCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) -{ - const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); - return (pProficiencyValues)[ proficiency ].bias; -} - -//----------------------------------------------------------------------------- -const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetProficiencyValues() -{ - return GetDefaultProficiencyValues(); -} - -//----------------------------------------------------------------------------- -const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetDefaultProficiencyValues() -{ - // Weapon proficiency table. Keep this in sync with WeaponProficiency_t enum in the header!! - static WeaponProficiencyInfo_t g_BaseWeaponProficiencyTable[] = - { - { 2.50, 1.0 }, - { 2.00, 1.0 }, - { 1.50, 1.0 }, - { 1.25, 1.0 }, - { 1.00, 1.0 }, - }; - - COMPILE_TIME_ASSERT( ARRAYSIZE(g_BaseWeaponProficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); - - return g_BaseWeaponProficiencyTable; -} - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#include "hl2mp_player_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LINK_ENTITY_TO_CLASS( basehl2mpcombatweapon, CBaseHL2MPCombatWeapon ); + +IMPLEMENT_NETWORKCLASS_ALIASED( BaseHL2MPCombatWeapon , DT_BaseHL2MPCombatWeapon ) + +BEGIN_NETWORK_TABLE( CBaseHL2MPCombatWeapon , DT_BaseHL2MPCombatWeapon ) +#if !defined( CLIENT_DLL ) +// SendPropInt( SENDINFO( m_bReflectViewModelAnimations ), 1, SPROP_UNSIGNED ), +#else +// RecvPropInt( RECVINFO( m_bReflectViewModelAnimations ) ), +#endif +END_NETWORK_TABLE() + + +#if !defined( CLIENT_DLL ) + +#include "globalstate.h" + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CBaseHL2MPCombatWeapon ) + + DEFINE_FIELD( m_bLowered, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flRaiseTime, FIELD_TIME ), + DEFINE_FIELD( m_flHolsterTime, FIELD_TIME ), + +END_DATADESC() + +#endif + +BEGIN_PREDICTION_DATA( CBaseHL2MPCombatWeapon ) +END_PREDICTION_DATA() + +extern ConVar sk_auto_reload_time; + +CBaseHL2MPCombatWeapon::CBaseHL2MPCombatWeapon( void ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseHL2MPCombatWeapon::ItemHolsterFrame( void ) +{ + BaseClass::ItemHolsterFrame(); + + // Must be player held + if ( GetOwner() && GetOwner()->IsPlayer() == false ) + return; + + // We can't be active + if ( GetOwner()->GetActiveWeapon() == this ) + return; + + // If it's been longer than three seconds, reload + if ( ( gpGlobals->curtime - m_flHolsterTime ) > sk_auto_reload_time.GetFloat() ) + { + // Just load the clip with no animations + FinishReload(); + m_flHolsterTime = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Drops the weapon into a lowered pose +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::Lower( void ) +{ + //Don't bother if we don't have the animation + if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) + return false; + + m_bLowered = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Brings the weapon up to the ready position +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::Ready( void ) +{ + //Don't bother if we don't have the animation + if ( SelectWeightedSequence( ACT_VM_LOWERED_TO_IDLE ) == ACTIVITY_NOT_AVAILABLE ) + return false; + + m_bLowered = false; + m_flRaiseTime = gpGlobals->curtime + 0.5f; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::Deploy( void ) +{ + // If we should be lowered, deploy in the lowered position + // We have to ask the player if the last time it checked, the weapon was lowered + if ( GetOwner() && GetOwner()->IsPlayer() ) + { + CHL2MP_Player *pPlayer = assert_cast( GetOwner() ); + if ( pPlayer->IsWeaponLowered() ) + { + if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) != ACTIVITY_NOT_AVAILABLE ) + { + if ( DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_VM_IDLE_LOWERED, (char*)GetAnimPrefix() ) ) + { + m_bLowered = true; + + // Stomp the next attack time to fix the fact that the lower idles are long + pPlayer->SetNextAttack( gpGlobals->curtime + 1.0 ); + m_flNextPrimaryAttack = gpGlobals->curtime + 1.0; + m_flNextSecondaryAttack = gpGlobals->curtime + 1.0; + return true; + } + } + } + } + + m_bLowered = false; + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( BaseClass::Holster( pSwitchingTo ) ) + { + SetWeaponVisible( false ); + m_flHolsterTime = gpGlobals->curtime; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::WeaponShouldBeLowered( void ) +{ + // Can't be in the middle of another animation + if ( GetIdealActivity() != ACT_VM_IDLE_LOWERED && GetIdealActivity() != ACT_VM_IDLE && + GetIdealActivity() != ACT_VM_IDLE_TO_LOWERED && GetIdealActivity() != ACT_VM_LOWERED_TO_IDLE ) + return false; + + if ( m_bLowered ) + return true; + +#if !defined( CLIENT_DLL ) + + if ( GlobalEntity_GetState( "friendly_encounter" ) == GLOBAL_ON ) + return true; + +#endif + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Allows the weapon to choose proper weapon idle animation +//----------------------------------------------------------------------------- +void CBaseHL2MPCombatWeapon::WeaponIdle( void ) +{ + //See if we should idle high or low + if ( WeaponShouldBeLowered() ) + { + // Move to lowered position if we're not there yet + if ( GetActivity() != ACT_VM_IDLE_LOWERED && GetActivity() != ACT_VM_IDLE_TO_LOWERED + && GetActivity() != ACT_TRANSITION ) + { + SendWeaponAnim( ACT_VM_IDLE_LOWERED ); + } + else if ( HasWeaponIdleTimeElapsed() ) + { + // Keep idling low + SendWeaponAnim( ACT_VM_IDLE_LOWERED ); + } + } + else + { + // See if we need to raise immediately + if ( m_flRaiseTime < gpGlobals->curtime && GetActivity() == ACT_VM_IDLE_LOWERED ) + { + SendWeaponAnim( ACT_VM_IDLE ); + } + else if ( HasWeaponIdleTimeElapsed() ) + { + SendWeaponAnim( ACT_VM_IDLE ); + } + } +} + +#if defined( CLIENT_DLL ) + +#define HL2_BOB_CYCLE_MIN 1.0f +#define HL2_BOB_CYCLE_MAX 0.45f +#define HL2_BOB 0.002f +#define HL2_BOB_UP 0.5f + +extern float g_lateralBob; +extern float g_verticalBob; + +static ConVar cl_bobcycle( "cl_bobcycle","0.8" ); +static ConVar cl_bob( "cl_bob","0.002" ); +static ConVar cl_bobup( "cl_bobup","0.5" ); + +// Register these cvars if needed for easy tweaking +static ConVar v_iyaw_cycle( "v_iyaw_cycle", "2", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_iroll_cycle( "v_iroll_cycle", "0.5", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_ipitch_cycle( "v_ipitch_cycle", "1", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_iyaw_level( "v_iyaw_level", "0.3", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_iroll_level( "v_iroll_level", "0.1", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_ipitch_level( "v_ipitch_level", "0.3", FCVAR_REPLICATED | FCVAR_CHEAT ); + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CBaseHL2MPCombatWeapon::CalcViewmodelBob( void ) +{ + static float bobtime; + static float lastbobtime; + float cycle; + + CBasePlayer *player = ToBasePlayer( GetOwner() ); + //Assert( player ); + + //NOTENOTE: For now, let this cycle continue when in the air, because it snaps badly without it + + if ( ( !gpGlobals->frametime ) || ( player == NULL ) ) + { + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f;// just use old value + } + + //Find the speed of the player + float speed = player->GetLocalVelocity().Length2D(); + + //FIXME: This maximum speed value must come from the server. + // MaxSpeed() is not sufficient for dealing with sprinting - jdw + + speed = clamp( speed, -320, 320 ); + + float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f ); + + bobtime += ( gpGlobals->curtime - lastbobtime ) * bob_offset; + lastbobtime = gpGlobals->curtime; + + //Calculate the vertical bob + cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX)*HL2_BOB_CYCLE_MAX; + cycle /= HL2_BOB_CYCLE_MAX; + + if ( cycle < HL2_BOB_UP ) + { + cycle = M_PI * cycle / HL2_BOB_UP; + } + else + { + cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); + } + + g_verticalBob = speed*0.005f; + g_verticalBob = g_verticalBob*0.3 + g_verticalBob*0.7*sin(cycle); + + g_verticalBob = clamp( g_verticalBob, -7.0f, 4.0f ); + + //Calculate the lateral bob + cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX*2)*HL2_BOB_CYCLE_MAX*2; + cycle /= HL2_BOB_CYCLE_MAX*2; + + if ( cycle < HL2_BOB_UP ) + { + cycle = M_PI * cycle / HL2_BOB_UP; + } + else + { + cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); + } + + g_lateralBob = speed*0.005f; + g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); + g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); + + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &angles - +// viewmodelindex - +//----------------------------------------------------------------------------- +void CBaseHL2MPCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) +{ + Vector forward, right; + AngleVectors( angles, &forward, &right, NULL ); + + CalcViewmodelBob(); + + // Apply bob, but scaled down to 40% + VectorMA( origin, g_verticalBob * 0.1f, forward, origin ); + + // Z bob a bit more + origin[2] += g_verticalBob * 0.1f; + + // bob the angles + angles[ ROLL ] += g_verticalBob * 0.5f; + angles[ PITCH ] -= g_verticalBob * 0.4f; + + angles[ YAW ] -= g_lateralBob * 0.3f; + + VectorMA( origin, g_lateralBob * 0.8f, right, origin ); +} + +//----------------------------------------------------------------------------- +Vector CBaseHL2MPCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) +{ + return BaseClass::GetBulletSpread( proficiency ); +} + +//----------------------------------------------------------------------------- +float CBaseHL2MPCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) +{ + return BaseClass::GetSpreadBias( proficiency ); +} +//----------------------------------------------------------------------------- + +const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetProficiencyValues() +{ + return NULL; +} + +#else + +// Server stubs +float CBaseHL2MPCombatWeapon::CalcViewmodelBob( void ) +{ + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &angles - +// viewmodelindex - +//----------------------------------------------------------------------------- +void CBaseHL2MPCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) +{ +} + + +//----------------------------------------------------------------------------- +Vector CBaseHL2MPCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) +{ + Vector baseSpread = BaseClass::GetBulletSpread( proficiency ); + + const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); + float flModifier = (pProficiencyValues)[ proficiency ].spreadscale; + return ( baseSpread * flModifier ); +} + +//----------------------------------------------------------------------------- +float CBaseHL2MPCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) +{ + const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); + return (pProficiencyValues)[ proficiency ].bias; +} + +//----------------------------------------------------------------------------- +const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetProficiencyValues() +{ + return GetDefaultProficiencyValues(); +} + +//----------------------------------------------------------------------------- +const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetDefaultProficiencyValues() +{ + // Weapon proficiency table. Keep this in sync with WeaponProficiency_t enum in the header!! + static WeaponProficiencyInfo_t g_BaseWeaponProficiencyTable[] = + { + { 2.50, 1.0 }, + { 2.00, 1.0 }, + { 1.50, 1.0 }, + { 1.25, 1.0 }, + { 1.00, 1.0 }, + }; + + COMPILE_TIME_ASSERT( ARRAYSIZE(g_BaseWeaponProficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); + + return g_BaseWeaponProficiencyTable; +} + +#endif diff --git a/game_shared/hl2mp/weapon_physcannon.cpp b/game_shared/hl2mp/weapon_physcannon.cpp index 64e55d31..fcf1ed02 100644 --- a/game_shared/hl2mp/weapon_physcannon.cpp +++ b/game_shared/hl2mp/weapon_physcannon.cpp @@ -50,7 +50,7 @@ #define SPRITE_SCALE 128.0f -static const char *s_pWaitForUpgradeContext = "WaitForUpgrade"; +//static const char *s_pWaitForUpgradeContext = "WaitForUpgrade"; ConVar g_debug_physcannon( "g_debug_physcannon", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); @@ -191,7 +191,7 @@ static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle ) } -static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr ) +/*static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr ) { physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr ); @@ -202,7 +202,7 @@ static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector ptr->plane.dist = -ptr->plane.dist; ptr->plane.normal *= -1; } -} +}*/ //----------------------------------------------------------------------------- // Purpose: Computes a local matrix for the player clamped to valid carry ranges diff --git a/game_shared/hl2mp/weapon_rpg.cpp b/game_shared/hl2mp/weapon_rpg.cpp index 39c2a7a3..189a235e 100644 --- a/game_shared/hl2mp/weapon_rpg.cpp +++ b/game_shared/hl2mp/weapon_rpg.cpp @@ -360,7 +360,7 @@ void CMissile::ShotDown( void ) void CMissile::DoExplosion( void ) { // Explode - ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), GetDamage(), GetDamage() * 2, + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), static_cast(GetDamage()), static_cast(GetDamage() * 2), SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this); } @@ -1037,7 +1037,7 @@ void CAPCMissile::DoExplosion( void ) else { ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), - APC_MISSILE_DAMAGE, 100, true, 20000 ); + static_cast(APC_MISSILE_DAMAGE), 100, true, 20000 ); } } diff --git a/game_shared/hl2mp/weapon_stunstick.cpp b/game_shared/hl2mp/weapon_stunstick.cpp index 16068e2b..47642d4a 100644 --- a/game_shared/hl2mp/weapon_stunstick.cpp +++ b/game_shared/hl2mp/weapon_stunstick.cpp @@ -1,896 +1,896 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Stun Stick- beating stick with a zappy end -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "npcevent.h" -#include "weapon_hl2mpbasebasebludgeon.h" -#include "IEffects.h" -#include "debugoverlay_shared.h" - -#ifndef CLIENT_DLL - #include "npc_metropolice.h" - #include "te_effect_dispatch.h" -#endif - -#ifdef CLIENT_DLL - - #include "iviewrender_beams.h" - #include "beam_shared.h" - #include "materialsystem/IMaterial.h" - #include "model_types.h" - #include "c_te_effect_dispatch.h" - #include "fx_quad.h" - #include "fx.h" - - extern void DrawHalo( IMaterial* pMaterial, const Vector &source, float scale, float const *color, float flHDRColorScale ); - extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); - -#endif - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -extern ConVar metropolice_move_and_melee; - -#define STUNSTICK_RANGE 75.0f -#define STUNSTICK_REFIRE 0.8f -#define STUNSTICK_BEAM_MATERIAL "sprites/lgtning.vmt" -#define STUNSTICK_GLOW_MATERIAL "sprites/light_glow02_add" -#define STUNSTICK_GLOW_MATERIAL2 "effects/blueflare1" -#define STUNSTICK_GLOW_MATERIAL_NOZ "sprites/light_glow02_add_noz" - -#ifdef CLIENT_DLL -#define CWeaponStunStick C_WeaponStunStick -#endif - -class CWeaponStunStick : public CBaseHL2MPBludgeonWeapon -{ - DECLARE_CLASS( CWeaponStunStick, CBaseHL2MPBludgeonWeapon ); - -public: - - CWeaponStunStick(); - - DECLARE_NETWORKCLASS(); - DECLARE_PREDICTABLE(); - -#ifndef CLIENT_DLL - DECLARE_ACTTABLE(); -#endif - -#ifdef CLIENT_DLL - virtual int DrawModel( int flags ); - virtual void ClientThink( void ); - virtual void OnDataChanged( DataUpdateType_t updateType ); - virtual RenderGroup_t GetRenderGroup( void ); - virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel ); - -#endif - - virtual void Precache(); - - void Spawn(); - - float GetRange( void ) { return STUNSTICK_RANGE; } - float GetFireRate( void ) { return STUNSTICK_REFIRE; } - - - bool Deploy( void ); - bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); - - void Drop( const Vector &vecVelocity ); - void ImpactEffect( trace_t &traceHit ); - void SecondaryAttack( void ) {} - void SetStunState( bool state ); - bool GetStunState( void ); - -#ifndef CLIENT_DLL - void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); - int WeaponMeleeAttack1Condition( float flDot, float flDist ); -#endif - - float GetDamageForActivity( Activity hitActivity ); - - CWeaponStunStick( const CWeaponStunStick & ); - -private: - -#ifdef CLIENT_DLL - - #define NUM_BEAM_ATTACHMENTS 9 - - struct stunstickBeamInfo_t - { - int IDs[2]; // 0 - top, 1 - bottom - }; - - stunstickBeamInfo_t m_BeamAttachments[NUM_BEAM_ATTACHMENTS]; // Lookup for arc attachment points on the head of the stick - int m_BeamCenterAttachment; // "Core" of the effect (center of the head) - - void SetupAttachmentPoints( void ); - void DrawFirstPersonEffects( void ); - void DrawThirdPersonEffects( void ); - void DrawEffects( void ); - bool InSwing( void ); - - bool m_bSwungLastFrame; - - #define FADE_DURATION 0.25f - - float m_flFadeTime; - -#endif - - CNetworkVar( bool, m_bActive ); -}; - -//----------------------------------------------------------------------------- -// CWeaponStunStick -//----------------------------------------------------------------------------- -IMPLEMENT_NETWORKCLASS_ALIASED( WeaponStunStick, DT_WeaponStunStick ) - -BEGIN_NETWORK_TABLE( CWeaponStunStick, DT_WeaponStunStick ) -#ifdef CLIENT_DLL - RecvPropInt( RECVINFO( m_bActive ) ), -#else - SendPropInt( SENDINFO( m_bActive ), 1, SPROP_UNSIGNED ), -#endif - -END_NETWORK_TABLE() - -BEGIN_PREDICTION_DATA( CWeaponStunStick ) -END_PREDICTION_DATA() - -LINK_ENTITY_TO_CLASS( weapon_stunstick, CWeaponStunStick ); -PRECACHE_WEAPON_REGISTER( weapon_stunstick ); - - -#ifndef CLIENT_DLL - -acttable_t CWeaponStunStick::m_acttable[] = -{ - { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, - { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, - { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, - { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, - { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, - { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, - { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, - { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, -}; - -IMPLEMENT_ACTTABLE(CWeaponStunStick); - -#endif - - -//----------------------------------------------------------------------------- -// Constructor -//----------------------------------------------------------------------------- -CWeaponStunStick::CWeaponStunStick( void ) -{ - // HACK: Don't call SetStunState because this tried to Emit a sound before - // any players are connected which is a bug - m_bActive = false; - -#ifdef CLIENT_DLL - m_bSwungLastFrame = false; - m_flFadeTime = FADE_DURATION; // Start off past the fade point -#endif -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CWeaponStunStick::Spawn() -{ - Precache(); - - BaseClass::Spawn(); - AddSolidFlags( FSOLID_NOT_STANDABLE ); -} - -void CWeaponStunStick::Precache() -{ - BaseClass::Precache(); - - PrecacheScriptSound( "Weapon_StunStick.Activate" ); - PrecacheScriptSound( "Weapon_StunStick.Deactivate" ); - - PrecacheModel( STUNSTICK_BEAM_MATERIAL ); -} - -//----------------------------------------------------------------------------- -// Purpose: Get the damage amount for the animation we're doing -// Input : hitActivity - currently played activity -// Output : Damage amount -//----------------------------------------------------------------------------- -float CWeaponStunStick::GetDamageForActivity( Activity hitActivity ) -{ - return 40.0f; -} - -//----------------------------------------------------------------------------- -// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) -//----------------------------------------------------------------------------- -extern ConVar sk_crowbar_lead_time; - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CWeaponStunStick::ImpactEffect( trace_t &traceHit ) -{ - -//#ifndef CLIENT_DLL - - CEffectData data; - - data.m_vNormal = traceHit.plane.normal; - data.m_vOrigin = traceHit.endpos + ( data.m_vNormal * 4.0f ); - - DispatchEffect( "StunstickImpact", data ); - -//#endif - - //FIXME: need new decals - UTIL_ImpactTrace( &traceHit, DMG_CLUB ); -} - -#ifndef CLIENT_DLL - - -int CWeaponStunStick::WeaponMeleeAttack1Condition( float flDot, float flDist ) -{ - // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) - CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); - CBaseEntity *pEnemy = pNPC->GetEnemy(); - if (!pEnemy) - return COND_NONE; - - Vector vecVelocity; - AngularImpulse angVelocity; - pEnemy->GetVelocity( &vecVelocity, &angVelocity ); - - // Project where the enemy will be in a little while, add some randomness so he doesn't always hit - float dt = sk_crowbar_lead_time.GetFloat(); - dt += random->RandomFloat( -0.3f, 0.2f ); - if ( dt < 0.0f ) - dt = 0.0f; - - Vector vecExtrapolatedPos; - VectorMA( pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos ); - - Vector vecDelta; - VectorSubtract( vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta ); - - if ( fabs( vecDelta.z ) > 70 ) - { - return COND_TOO_FAR_TO_ATTACK; - } - - Vector vecForward = pNPC->BodyDirection2D( ); - vecDelta.z = 0.0f; - float flExtrapolatedDot = DotProduct2D( vecDelta.AsVector2D(), vecForward.AsVector2D() ); - if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) - { - return COND_NOT_FACING_ATTACK; - } - - float flExtrapolatedDist = Vector2DNormalize( vecDelta.AsVector2D() ); - - if( pEnemy->IsPlayer() ) - { - //Vector vecDir = pEnemy->GetSmoothedVelocity(); - //float flSpeed = VectorNormalize( vecDir ); - - // If player will be in front of me in one-half second, clock his arse. - Vector vecProjectEnemy = pEnemy->GetAbsOrigin() + (pEnemy->GetAbsVelocity() * 0.35); - Vector vecProjectMe = GetAbsOrigin(); - - if( (vecProjectMe - vecProjectEnemy).Length2D() <= 48.0f ) - { - return COND_CAN_MELEE_ATTACK1; - } - } -/* - if( metropolice_move_and_melee.GetBool() ) - { - if( pNPC->IsMoving() ) - { - flTargetDist *= 1.5f; - } - } -*/ - float flTargetDist = 48.0f; - if ((flDist > flTargetDist) && (flExtrapolatedDist > flTargetDist)) - { - return COND_TOO_FAR_TO_ATTACK; - } - - return COND_CAN_MELEE_ATTACK1; -} - - -void CWeaponStunStick::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) -{ - switch( pEvent->event ) - { - case EVENT_WEAPON_MELEE_HIT: - { - // Trace up or down based on where the enemy is... - // But only if we're basically facing that direction - Vector vecDirection; - AngleVectors( GetAbsAngles(), &vecDirection ); - - CBaseEntity *pEnemy = pOperator->MyNPCPointer() ? pOperator->MyNPCPointer()->GetEnemy() : NULL; - if ( pEnemy ) - { - Vector vecDelta; - VectorSubtract( pEnemy->WorldSpaceCenter(), pOperator->Weapon_ShootPosition(), vecDelta ); - VectorNormalize( vecDelta ); - - Vector2D vecDelta2D = vecDelta.AsVector2D(); - Vector2DNormalize( vecDelta2D ); - if ( DotProduct2D( vecDelta2D, vecDirection.AsVector2D() ) > 0.8f ) - { - vecDirection = vecDelta; - } - } - - Vector vecEnd; - VectorMA( pOperator->Weapon_ShootPosition(), 32, vecDirection, vecEnd ); - // Stretch the swing box down to catch low level physics objects - CBaseEntity *pHurt = pOperator->CheckTraceHullAttack( pOperator->Weapon_ShootPosition(), vecEnd, - Vector(-16,-16,-40), Vector(16,16,16), GetDamageForActivity( GetActivity() ), DMG_CLUB, 0.5f, false ); - - // did I hit someone? - if ( pHurt ) - { - // play sound - WeaponSound( MELEE_HIT ); - - CBasePlayer *pPlayer = ToBasePlayer( pHurt ); - - bool bFlashed = false; - - // Punch angles - if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE) ) - { - float yawKick = random->RandomFloat( -48, -24 ); - - //Kick the player angles - pPlayer->ViewPunch( QAngle( -16, yawKick, 2 ) ); - - Vector dir = pHurt->GetAbsOrigin() - GetAbsOrigin(); - - // If the player's on my head, don't knock him up - if ( pPlayer->GetGroundEntity() == pOperator ) - { - dir = vecDirection; - dir.z = 0; - } - - VectorNormalize(dir); - - dir *= 500.0f; - - //If not on ground, then don't make them fly! - if ( !(pPlayer->GetFlags() & FL_ONGROUND ) ) - dir.z = 0.0f; - - //Push the target back - pHurt->ApplyAbsVelocityImpulse( dir ); - - if ( !bFlashed ) - { - color32 red = {128,0,0,128}; - UTIL_ScreenFade( pPlayer, red, 0.5f, 0.1f, FFADE_IN ); - } - - // Force the player to drop anyting they were holding - pPlayer->ForceDropOfCarriedPhysObjects(); - } - - // do effect? - } - else - { - WeaponSound( MELEE_MISS ); - } - } - break; - default: - BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); - break; - } -} - -#endif - -//----------------------------------------------------------------------------- -// Purpose: Sets the state of the stun stick -//----------------------------------------------------------------------------- -void CWeaponStunStick::SetStunState( bool state ) -{ - m_bActive = state; - - if ( m_bActive ) - { - //FIXME: START - Move to client-side - - Vector vecAttachment; - QAngle vecAttachmentAngles; - - GetAttachment( 1, vecAttachment, vecAttachmentAngles ); - g_pEffects->Sparks( vecAttachment ); - - //FIXME: END - Move to client-side - - EmitSound( "Weapon_StunStick.Activate" ); - } - else - { - EmitSound( "Weapon_StunStick.Deactivate" ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CWeaponStunStick::Deploy( void ) -{ - SetStunState( true ); - - return BaseClass::Deploy(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CWeaponStunStick::Holster( CBaseCombatWeapon *pSwitchingTo ) -{ - if ( BaseClass::Holster( pSwitchingTo ) == false ) - return false; - - SetStunState( false ); - SetWeaponVisible( false ); - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : &vecVelocity - -//----------------------------------------------------------------------------- -void CWeaponStunStick::Drop( const Vector &vecVelocity ) -{ - SetStunState( false ); - -#ifndef CLIENT_DLL - UTIL_Remove( this ); -#endif - -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CWeaponStunStick::GetStunState( void ) -{ - return m_bActive; -} - -#ifdef CLIENT_DLL - -//----------------------------------------------------------------------------- -// Purpose: Get the attachment point on a viewmodel that a base weapon is using -//----------------------------------------------------------------------------- -bool UTIL_GetWeaponAttachment( C_BaseCombatWeapon *pWeapon, int attachmentID, Vector &absOrigin, QAngle &absAngles ) -{ - // This is already correct in third-person - if ( pWeapon && pWeapon->IsCarriedByLocalPlayer() == false ) - { - return pWeapon->GetAttachment( attachmentID, absOrigin, absAngles ); - } - - // Otherwise we need to translate the attachment to the viewmodel's version and reformat it - CBasePlayer *pOwner = ToBasePlayer( pWeapon->GetOwner() ); - - if ( pOwner != NULL ) - { - int ret = pOwner->GetViewModel()->GetAttachment( attachmentID, absOrigin, absAngles ); - FormatViewModelAttachment( absOrigin, true ); - - return ret; - } - - // Wasn't found - return false; -} - -#define BEAM_ATTACH_CORE_NAME "sparkrear" - -//----------------------------------------------------------------------------- -// Purpose: Sets up the attachment point lookup for the model -//----------------------------------------------------------------------------- -void C_WeaponStunStick::SetupAttachmentPoints( void ) -{ - // Setup points for both types of views - if ( IsCarriedByLocalPlayer() ) - { - const char *szBeamAttachNamesTop[NUM_BEAM_ATTACHMENTS] = - { - "spark1a","spark2a","spark3a","spark4a", - "spark5a","spark6a","spark7a","spark8a", - "spark9a", - }; - - const char *szBeamAttachNamesBottom[NUM_BEAM_ATTACHMENTS] = - { - "spark1b","spark2b","spark3b","spark4b", - "spark5b","spark6b","spark7b","spark8b", - "spark9b", - }; - - // Lookup and store all connections - for ( int i = 0; i < NUM_BEAM_ATTACHMENTS; i++ ) - { - m_BeamAttachments[i].IDs[0] = LookupAttachment( szBeamAttachNamesTop[i] ); - m_BeamAttachments[i].IDs[1] = LookupAttachment( szBeamAttachNamesBottom[i] ); - } - - // Setup the center beam point - m_BeamCenterAttachment = LookupAttachment( BEAM_ATTACH_CORE_NAME ); - } - else - { - // Setup the center beam point - m_BeamCenterAttachment = 1; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Draws the stunstick model (with extra effects) -//----------------------------------------------------------------------------- -int C_WeaponStunStick::DrawModel( int flags ) -{ - if ( ShouldDraw() == false ) - return 0; - - // Only render these on the transparent pass - if ( flags & STUDIO_TRANSPARENCY ) - { - DrawEffects(); - return 1; - } - - return BaseClass::DrawModel( flags ); -} - -//----------------------------------------------------------------------------- -// Purpose: Randomly adds extra effects -//----------------------------------------------------------------------------- -void C_WeaponStunStick::ClientThink( void ) -{ - if ( InSwing() == false ) - { - if ( m_bSwungLastFrame ) - { - // Start fading - m_flFadeTime = gpGlobals->curtime; - m_bSwungLastFrame = false; - } - - return; - } - - // Remember if we were swinging last frame - m_bSwungLastFrame = InSwing(); - - if ( IsEffectActive( EF_NODRAW ) ) - return; - - if ( IsCarriedByLocalPlayer() ) - { - // Update our effects - if ( gpGlobals->frametime != 0.0f && ( random->RandomInt( 0, 3 ) == 0 ) ) - { - Vector vecOrigin; - QAngle vecAngles; - - // Inner beams - BeamInfo_t beamInfo; - - int attachment = random->RandomInt( 0, 15 ); - - UTIL_GetWeaponAttachment( this, attachment, vecOrigin, vecAngles ); - ::FormatViewModelAttachment( vecOrigin, false ); - - CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - CBaseEntity *pBeamEnt = pOwner->GetViewModel(); - - beamInfo.m_vecStart = vec3_origin; - beamInfo.m_pStartEnt= pBeamEnt; - beamInfo.m_nStartAttachment = attachment; - - beamInfo.m_pEndEnt = NULL; - beamInfo.m_nEndAttachment = -1; - beamInfo.m_vecEnd = vecOrigin + RandomVector( -8, 8 ); - - beamInfo.m_pszModelName = STUNSTICK_BEAM_MATERIAL; - beamInfo.m_flHaloScale = 0.0f; - beamInfo.m_flLife = 0.05f; - beamInfo.m_flWidth = random->RandomFloat( 1.0f, 2.0f ); - beamInfo.m_flEndWidth = 0; - beamInfo.m_flFadeLength = 0.0f; - beamInfo.m_flAmplitude = random->RandomFloat( 16, 32 ); - beamInfo.m_flBrightness = 255.0; - beamInfo.m_flSpeed = 0.0; - beamInfo.m_nStartFrame = 0.0; - beamInfo.m_flFrameRate = 1.0f; - beamInfo.m_flRed = 255.0f;; - beamInfo.m_flGreen = 255.0f; - beamInfo.m_flBlue = 255.0f; - beamInfo.m_nSegments = 16; - beamInfo.m_bRenderable = true; - beamInfo.m_nFlags = 0; - - beams->CreateBeamEntPoint( beamInfo ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Starts the client-side version thinking -//----------------------------------------------------------------------------- -void C_WeaponStunStick::OnDataChanged( DataUpdateType_t updateType ) -{ - BaseClass::OnDataChanged( updateType ); - if ( updateType == DATA_UPDATE_CREATED ) - { - SetNextClientThink( CLIENT_THINK_ALWAYS ); - SetupAttachmentPoints(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Tells us we're always a translucent entity -//----------------------------------------------------------------------------- -RenderGroup_t C_WeaponStunStick::GetRenderGroup( void ) -{ - return RENDER_GROUP_TWOPASS; -} - -//----------------------------------------------------------------------------- -// Purpose: Tells us we're always a translucent entity -//----------------------------------------------------------------------------- -bool C_WeaponStunStick::InSwing( void ) -{ - int activity = GetActivity(); - - // FIXME: This is needed until the actual animation works - if ( IsCarriedByLocalPlayer() == false ) - return true; - - // These are the swing activities this weapon can play - if ( activity == GetPrimaryAttackActivity() || - activity == GetSecondaryAttackActivity() || - activity == ACT_VM_MISSCENTER || - activity == ACT_VM_MISSCENTER2 ) - return true; - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Draw our special effects -//----------------------------------------------------------------------------- -void C_WeaponStunStick::DrawThirdPersonEffects( void ) -{ - Vector vecOrigin; - QAngle vecAngles; - float color[3]; - float scale; - - IMaterial *pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL, NULL, false ); - materials->Bind( pMaterial ); - - // Get bright when swung - if ( InSwing() ) - { - color[0] = color[1] = color[2] = 0.4f; - scale = 22.0f; - } - else - { - color[0] = color[1] = color[2] = 0.1f; - scale = 20.0f; - } - - // Draw an all encompassing glow around the entire head - UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); - DrawHalo( pMaterial, vecOrigin, scale, color ); - - if ( InSwing() ) - { - pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL2, NULL, false ); - materials->Bind( pMaterial ); - - color[0] = color[1] = color[2] = random->RandomFloat( 0.6f, 0.8f ); - scale = random->RandomFloat( 4.0f, 6.0f ); - - // Draw an all encompassing glow around the entire head - UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); - DrawHalo( pMaterial, vecOrigin, scale, color ); - - // Update our effects - if ( gpGlobals->frametime != 0.0f && ( random->RandomInt( 0, 5 ) == 0 ) ) - { - Vector vecOrigin; - QAngle vecAngles; - - GetAttachment( 1, vecOrigin, vecAngles ); - - Vector vForward; - AngleVectors( vecAngles, &vForward ); - - Vector vEnd = vecOrigin - vForward * 1.0f; - - // Inner beams - BeamInfo_t beamInfo; - - beamInfo.m_vecStart = vEnd; - Vector offset = RandomVector( -12, 8 ); - - offset += Vector(4,4,4); - beamInfo.m_vecEnd = vecOrigin + offset; - - beamInfo.m_pStartEnt= cl_entitylist->GetEnt( BEAMENT_ENTITY( entindex() ) ); - beamInfo.m_pEndEnt = cl_entitylist->GetEnt( BEAMENT_ENTITY( entindex() ) ); - beamInfo.m_nStartAttachment = 1; - beamInfo.m_nEndAttachment = -1; - - beamInfo.m_nType = TE_BEAMTESLA; - beamInfo.m_pszModelName = STUNSTICK_BEAM_MATERIAL; - beamInfo.m_flHaloScale = 0.0f; - beamInfo.m_flLife = 0.01f; - beamInfo.m_flWidth = random->RandomFloat( 1.0f, 3.0f ); - beamInfo.m_flEndWidth = 0; - beamInfo.m_flFadeLength = 0.0f; - beamInfo.m_flAmplitude = random->RandomFloat( 1, 2 ); - beamInfo.m_flBrightness = 255.0; - beamInfo.m_flSpeed = 0.0; - beamInfo.m_nStartFrame = 0.0; - beamInfo.m_flFrameRate = 1.0f; - beamInfo.m_flRed = 255.0f;; - beamInfo.m_flGreen = 255.0f; - beamInfo.m_flBlue = 255.0f; - beamInfo.m_nSegments = 16; - beamInfo.m_bRenderable = true; - beamInfo.m_nFlags = FBEAM_SHADEOUT; - - beams->CreateBeamPoints( beamInfo ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Draw our special effects -//----------------------------------------------------------------------------- -void C_WeaponStunStick::DrawFirstPersonEffects( void ) -{ - Vector vecOrigin; - QAngle vecAngles; - float color[3]; - float scale; - - IMaterial *pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL_NOZ, NULL, false ); - materials->Bind( pMaterial ); - - // Find where we are in the fade - float fadeAmount = RemapValClamped( gpGlobals->curtime, m_flFadeTime, m_flFadeTime + FADE_DURATION, 1.0f, 0.1f ); - - // Get bright when swung - if ( InSwing() ) - { - color[0] = color[1] = color[2] = 0.4f; - scale = 22.0f; - } - else - { - color[0] = color[1] = color[2] = 0.4f * fadeAmount; - scale = 20.0f; - } - - if ( color[0] > 0.0f ) - { - // Draw an all encompassing glow around the entire head - UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); - DrawHalo( pMaterial, vecOrigin, scale, color ); - } - - // Draw bright points at each attachment location - for ( int i = 0; i < (NUM_BEAM_ATTACHMENTS*2)+1; i++ ) - { - if ( InSwing() ) - { - color[0] = color[1] = color[2] = random->RandomFloat( 0.05f, 0.5f ); - scale = random->RandomFloat( 4.0f, 5.0f ); - } - else - { - color[0] = color[1] = color[2] = random->RandomFloat( 0.05f, 0.5f ) * fadeAmount; - scale = random->RandomFloat( 4.0f, 5.0f ) * fadeAmount; - } - - if ( color[0] > 0.0f ) - { - UTIL_GetWeaponAttachment( this, i, vecOrigin, vecAngles ); - DrawHalo( pMaterial, vecOrigin, scale, color ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Draw our special effects -//----------------------------------------------------------------------------- -void C_WeaponStunStick::DrawEffects( void ) -{ - if ( IsCarriedByLocalPlayer() ) - { - DrawFirstPersonEffects(); - } - else - { - DrawThirdPersonEffects(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Viewmodel was drawn -//----------------------------------------------------------------------------- -void C_WeaponStunStick::ViewModelDrawn( C_BaseViewModel *pBaseViewModel ) -{ - // Don't bother when we're not deployed - if ( IsWeaponVisible() ) - { - // Do all our special effects - DrawEffects(); - } - - BaseClass::ViewModelDrawn( pBaseViewModel ); -} - -//----------------------------------------------------------------------------- -// Purpose: Draw a cheap glow quad at our impact point (with sparks) -//----------------------------------------------------------------------------- -void StunstickImpactCallback( const CEffectData &data ) -{ - float scale = random->RandomFloat( 16, 32 ); - - FX_AddQuad( data.m_vOrigin, - data.m_vNormal, - scale, - scale*2.0f, - 1.0f, - 1.0f, - 0.0f, - 0.0f, - random->RandomInt( 0, 360 ), - 0, - Vector( 1.0f, 1.0f, 1.0f ), - 0.1f, - "sprites/light_glow02_add", - 0 ); - - FX_Sparks( data.m_vOrigin, 1, 2, data.m_vNormal, 6, 64, 256 ); -} - -DECLARE_CLIENT_EFFECT( "StunstickImpact", StunstickImpactCallback ); - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Stun Stick- beating stick with a zappy end +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "weapon_hl2mpbasebasebludgeon.h" +#include "IEffects.h" +#include "debugoverlay_shared.h" + +#ifndef CLIENT_DLL + #include "npc_metropolice.h" + #include "te_effect_dispatch.h" +#endif + +#ifdef CLIENT_DLL + + #include "iviewrender_beams.h" + #include "beam_shared.h" + #include "materialsystem/IMaterial.h" + #include "model_types.h" + #include "c_te_effect_dispatch.h" + #include "fx_quad.h" + #include "fx.h" + + extern void DrawHalo( IMaterial* pMaterial, const Vector &source, float scale, float const *color, float flHDRColorScale ); + extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); + +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar metropolice_move_and_melee; + +#define STUNSTICK_RANGE 75.0f +#define STUNSTICK_REFIRE 0.8f +#define STUNSTICK_BEAM_MATERIAL "sprites/lgtning.vmt" +#define STUNSTICK_GLOW_MATERIAL "sprites/light_glow02_add" +#define STUNSTICK_GLOW_MATERIAL2 "effects/blueflare1" +#define STUNSTICK_GLOW_MATERIAL_NOZ "sprites/light_glow02_add_noz" + +#ifdef CLIENT_DLL +#define CWeaponStunStick C_WeaponStunStick +#endif + +class CWeaponStunStick : public CBaseHL2MPBludgeonWeapon +{ + DECLARE_CLASS( CWeaponStunStick, CBaseHL2MPBludgeonWeapon ); + +public: + + CWeaponStunStick(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + +#ifdef CLIENT_DLL + virtual int DrawModel( int flags ); + virtual void ClientThink( void ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual RenderGroup_t GetRenderGroup( void ); + virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel ); + +#endif + + virtual void Precache(); + + void Spawn(); + + float GetRange( void ) { return STUNSTICK_RANGE; } + float GetFireRate( void ) { return STUNSTICK_REFIRE; } + + + bool Deploy( void ); + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + + void Drop( const Vector &vecVelocity ); + void ImpactEffect( trace_t &traceHit ); + void SecondaryAttack( void ) {} + void SetStunState( bool state ); + bool GetStunState( void ); + +#ifndef CLIENT_DLL + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#endif + + float GetDamageForActivity( Activity hitActivity ); + + CWeaponStunStick( const CWeaponStunStick & ); + +private: + +#ifdef CLIENT_DLL + + #define NUM_BEAM_ATTACHMENTS 9 + + struct stunstickBeamInfo_t + { + int IDs[2]; // 0 - top, 1 - bottom + }; + + stunstickBeamInfo_t m_BeamAttachments[NUM_BEAM_ATTACHMENTS]; // Lookup for arc attachment points on the head of the stick + int m_BeamCenterAttachment; // "Core" of the effect (center of the head) + + void SetupAttachmentPoints( void ); + void DrawFirstPersonEffects( void ); + void DrawThirdPersonEffects( void ); + void DrawEffects( void ); + bool InSwing( void ); + + bool m_bSwungLastFrame; + + #define FADE_DURATION 0.25f + + float m_flFadeTime; + +#endif + + CNetworkVar( bool, m_bActive ); +}; + +//----------------------------------------------------------------------------- +// CWeaponStunStick +//----------------------------------------------------------------------------- +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponStunStick, DT_WeaponStunStick ) + +BEGIN_NETWORK_TABLE( CWeaponStunStick, DT_WeaponStunStick ) +#ifdef CLIENT_DLL + RecvPropInt( RECVINFO( m_bActive ) ), +#else + SendPropInt( SENDINFO( m_bActive ), 1, SPROP_UNSIGNED ), +#endif + +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponStunStick ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_stunstick, CWeaponStunStick ); +PRECACHE_WEAPON_REGISTER( weapon_stunstick ); + + +#ifndef CLIENT_DLL + +acttable_t CWeaponStunStick::m_acttable[] = +{ + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponStunStick); + +#endif + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CWeaponStunStick::CWeaponStunStick( void ) +{ + // HACK: Don't call SetStunState because this tried to Emit a sound before + // any players are connected which is a bug + m_bActive = false; + +#ifdef CLIENT_DLL + m_bSwungLastFrame = false; + m_flFadeTime = FADE_DURATION; // Start off past the fade point +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CWeaponStunStick::Spawn() +{ + Precache(); + + BaseClass::Spawn(); + AddSolidFlags( FSOLID_NOT_STANDABLE ); +} + +void CWeaponStunStick::Precache() +{ + BaseClass::Precache(); + + PrecacheScriptSound( "Weapon_StunStick.Activate" ); + PrecacheScriptSound( "Weapon_StunStick.Deactivate" ); + + PrecacheModel( STUNSTICK_BEAM_MATERIAL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the damage amount for the animation we're doing +// Input : hitActivity - currently played activity +// Output : Damage amount +//----------------------------------------------------------------------------- +float CWeaponStunStick::GetDamageForActivity( Activity hitActivity ) +{ + return 40.0f; +} + +//----------------------------------------------------------------------------- +// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) +//----------------------------------------------------------------------------- +extern ConVar sk_crowbar_lead_time; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponStunStick::ImpactEffect( trace_t &traceHit ) +{ + +//#ifndef CLIENT_DLL + + CEffectData data; + + data.m_vNormal = traceHit.plane.normal; + data.m_vOrigin = traceHit.endpos + ( data.m_vNormal * 4.0f ); + + DispatchEffect( "StunstickImpact", data ); + +//#endif + + //FIXME: need new decals + UTIL_ImpactTrace( &traceHit, DMG_CLUB ); +} + +#ifndef CLIENT_DLL + + +int CWeaponStunStick::WeaponMeleeAttack1Condition( float flDot, float flDist ) +{ + // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) + CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); + CBaseEntity *pEnemy = pNPC->GetEnemy(); + if (!pEnemy) + return COND_NONE; + + Vector vecVelocity; + AngularImpulse angVelocity; + pEnemy->GetVelocity( &vecVelocity, &angVelocity ); + + // Project where the enemy will be in a little while, add some randomness so he doesn't always hit + float dt = sk_crowbar_lead_time.GetFloat(); + dt += random->RandomFloat( -0.3f, 0.2f ); + if ( dt < 0.0f ) + dt = 0.0f; + + Vector vecExtrapolatedPos; + VectorMA( pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos ); + + Vector vecDelta; + VectorSubtract( vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta ); + + if ( fabs( vecDelta.z ) > 70 ) + { + return COND_TOO_FAR_TO_ATTACK; + } + + Vector vecForward = pNPC->BodyDirection2D( ); + vecDelta.z = 0.0f; + float flExtrapolatedDot = DotProduct2D( vecDelta.AsVector2D(), vecForward.AsVector2D() ); + if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) + { + return COND_NOT_FACING_ATTACK; + } + + float flExtrapolatedDist = Vector2DNormalize( vecDelta.AsVector2D() ); + + if( pEnemy->IsPlayer() ) + { + //Vector vecDir = pEnemy->GetSmoothedVelocity(); + //float flSpeed = VectorNormalize( vecDir ); + + // If player will be in front of me in one-half second, clock his arse. + Vector vecProjectEnemy = pEnemy->GetAbsOrigin() + (pEnemy->GetAbsVelocity() * 0.35); + Vector vecProjectMe = GetAbsOrigin(); + + if( (vecProjectMe - vecProjectEnemy).Length2D() <= 48.0f ) + { + return COND_CAN_MELEE_ATTACK1; + } + } +/* + if( metropolice_move_and_melee.GetBool() ) + { + if( pNPC->IsMoving() ) + { + flTargetDist *= 1.5f; + } + } +*/ + float flTargetDist = 48.0f; + if ((flDist > flTargetDist) && (flExtrapolatedDist > flTargetDist)) + { + return COND_TOO_FAR_TO_ATTACK; + } + + return COND_CAN_MELEE_ATTACK1; +} + + +void CWeaponStunStick::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_MELEE_HIT: + { + // Trace up or down based on where the enemy is... + // But only if we're basically facing that direction + Vector vecDirection; + AngleVectors( GetAbsAngles(), &vecDirection ); + + CBaseEntity *pEnemy = pOperator->MyNPCPointer() ? pOperator->MyNPCPointer()->GetEnemy() : NULL; + if ( pEnemy ) + { + Vector vecDelta; + VectorSubtract( pEnemy->WorldSpaceCenter(), pOperator->Weapon_ShootPosition(), vecDelta ); + VectorNormalize( vecDelta ); + + Vector2D vecDelta2D = vecDelta.AsVector2D(); + Vector2DNormalize( vecDelta2D ); + if ( DotProduct2D( vecDelta2D, vecDirection.AsVector2D() ) > 0.8f ) + { + vecDirection = vecDelta; + } + } + + Vector vecEnd; + VectorMA( pOperator->Weapon_ShootPosition(), 32, vecDirection, vecEnd ); + // Stretch the swing box down to catch low level physics objects + CBaseEntity *pHurt = pOperator->CheckTraceHullAttack( pOperator->Weapon_ShootPosition(), vecEnd, + Vector(-16,-16,-40), Vector(16,16,16), static_cast(GetDamageForActivity( GetActivity() )), DMG_CLUB, 0.5f, false ); + + // did I hit someone? + if ( pHurt ) + { + // play sound + WeaponSound( MELEE_HIT ); + + CBasePlayer *pPlayer = ToBasePlayer( pHurt ); + + bool bFlashed = false; + + // Punch angles + if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE) ) + { + float yawKick = random->RandomFloat( -48, -24 ); + + //Kick the player angles + pPlayer->ViewPunch( QAngle( -16, yawKick, 2 ) ); + + Vector dir = pHurt->GetAbsOrigin() - GetAbsOrigin(); + + // If the player's on my head, don't knock him up + if ( pPlayer->GetGroundEntity() == pOperator ) + { + dir = vecDirection; + dir.z = 0; + } + + VectorNormalize(dir); + + dir *= 500.0f; + + //If not on ground, then don't make them fly! + if ( !(pPlayer->GetFlags() & FL_ONGROUND ) ) + dir.z = 0.0f; + + //Push the target back + pHurt->ApplyAbsVelocityImpulse( dir ); + + if ( !bFlashed ) + { + color32 red = {128,0,0,128}; + UTIL_ScreenFade( pPlayer, red, 0.5f, 0.1f, FFADE_IN ); + } + + // Force the player to drop anyting they were holding + pPlayer->ForceDropOfCarriedPhysObjects(); + } + + // do effect? + } + else + { + WeaponSound( MELEE_MISS ); + } + } + break; + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Sets the state of the stun stick +//----------------------------------------------------------------------------- +void CWeaponStunStick::SetStunState( bool state ) +{ + m_bActive = state; + + if ( m_bActive ) + { + //FIXME: START - Move to client-side + + Vector vecAttachment; + QAngle vecAttachmentAngles; + + GetAttachment( 1, vecAttachment, vecAttachmentAngles ); + g_pEffects->Sparks( vecAttachment ); + + //FIXME: END - Move to client-side + + EmitSound( "Weapon_StunStick.Activate" ); + } + else + { + EmitSound( "Weapon_StunStick.Deactivate" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponStunStick::Deploy( void ) +{ + SetStunState( true ); + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponStunStick::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( BaseClass::Holster( pSwitchingTo ) == false ) + return false; + + SetStunState( false ); + SetWeaponVisible( false ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecVelocity - +//----------------------------------------------------------------------------- +void CWeaponStunStick::Drop( const Vector &vecVelocity ) +{ + SetStunState( false ); + +#ifndef CLIENT_DLL + UTIL_Remove( this ); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponStunStick::GetStunState( void ) +{ + return m_bActive; +} + +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Get the attachment point on a viewmodel that a base weapon is using +//----------------------------------------------------------------------------- +bool UTIL_GetWeaponAttachment( C_BaseCombatWeapon *pWeapon, int attachmentID, Vector &absOrigin, QAngle &absAngles ) +{ + // This is already correct in third-person + if ( pWeapon && pWeapon->IsCarriedByLocalPlayer() == false ) + { + return pWeapon->GetAttachment( attachmentID, absOrigin, absAngles ); + } + + // Otherwise we need to translate the attachment to the viewmodel's version and reformat it + CBasePlayer *pOwner = ToBasePlayer( pWeapon->GetOwner() ); + + if ( pOwner != NULL ) + { + int ret = pOwner->GetViewModel()->GetAttachment( attachmentID, absOrigin, absAngles ); + FormatViewModelAttachment( absOrigin, true ); + + return ret; + } + + // Wasn't found + return false; +} + +#define BEAM_ATTACH_CORE_NAME "sparkrear" + +//----------------------------------------------------------------------------- +// Purpose: Sets up the attachment point lookup for the model +//----------------------------------------------------------------------------- +void C_WeaponStunStick::SetupAttachmentPoints( void ) +{ + // Setup points for both types of views + if ( IsCarriedByLocalPlayer() ) + { + const char *szBeamAttachNamesTop[NUM_BEAM_ATTACHMENTS] = + { + "spark1a","spark2a","spark3a","spark4a", + "spark5a","spark6a","spark7a","spark8a", + "spark9a", + }; + + const char *szBeamAttachNamesBottom[NUM_BEAM_ATTACHMENTS] = + { + "spark1b","spark2b","spark3b","spark4b", + "spark5b","spark6b","spark7b","spark8b", + "spark9b", + }; + + // Lookup and store all connections + for ( int i = 0; i < NUM_BEAM_ATTACHMENTS; i++ ) + { + m_BeamAttachments[i].IDs[0] = LookupAttachment( szBeamAttachNamesTop[i] ); + m_BeamAttachments[i].IDs[1] = LookupAttachment( szBeamAttachNamesBottom[i] ); + } + + // Setup the center beam point + m_BeamCenterAttachment = LookupAttachment( BEAM_ATTACH_CORE_NAME ); + } + else + { + // Setup the center beam point + m_BeamCenterAttachment = 1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draws the stunstick model (with extra effects) +//----------------------------------------------------------------------------- +int C_WeaponStunStick::DrawModel( int flags ) +{ + if ( ShouldDraw() == false ) + return 0; + + // Only render these on the transparent pass + if ( flags & STUDIO_TRANSPARENCY ) + { + DrawEffects(); + return 1; + } + + return BaseClass::DrawModel( flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: Randomly adds extra effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::ClientThink( void ) +{ + if ( InSwing() == false ) + { + if ( m_bSwungLastFrame ) + { + // Start fading + m_flFadeTime = gpGlobals->curtime; + m_bSwungLastFrame = false; + } + + return; + } + + // Remember if we were swinging last frame + m_bSwungLastFrame = InSwing(); + + if ( IsEffectActive( EF_NODRAW ) ) + return; + + if ( IsCarriedByLocalPlayer() ) + { + // Update our effects + if ( gpGlobals->frametime != 0.0f && ( random->RandomInt( 0, 3 ) == 0 ) ) + { + Vector vecOrigin; + QAngle vecAngles; + + // Inner beams + BeamInfo_t beamInfo; + + int attachment = random->RandomInt( 0, 15 ); + + UTIL_GetWeaponAttachment( this, attachment, vecOrigin, vecAngles ); + ::FormatViewModelAttachment( vecOrigin, false ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + CBaseEntity *pBeamEnt = pOwner->GetViewModel(); + + beamInfo.m_vecStart = vec3_origin; + beamInfo.m_pStartEnt= pBeamEnt; + beamInfo.m_nStartAttachment = attachment; + + beamInfo.m_pEndEnt = NULL; + beamInfo.m_nEndAttachment = -1; + beamInfo.m_vecEnd = vecOrigin + RandomVector( -8, 8 ); + + beamInfo.m_pszModelName = STUNSTICK_BEAM_MATERIAL; + beamInfo.m_flHaloScale = 0.0f; + beamInfo.m_flLife = 0.05f; + beamInfo.m_flWidth = random->RandomFloat( 1.0f, 2.0f ); + beamInfo.m_flEndWidth = 0; + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = random->RandomFloat( 16, 32 ); + beamInfo.m_flBrightness = 255.0; + beamInfo.m_flSpeed = 0.0; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 1.0f; + beamInfo.m_flRed = 255.0f;; + beamInfo.m_flGreen = 255.0f; + beamInfo.m_flBlue = 255.0f; + beamInfo.m_nSegments = 16; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = 0; + + beams->CreateBeamEntPoint( beamInfo ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Starts the client-side version thinking +//----------------------------------------------------------------------------- +void C_WeaponStunStick::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + if ( updateType == DATA_UPDATE_CREATED ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + SetupAttachmentPoints(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tells us we're always a translucent entity +//----------------------------------------------------------------------------- +RenderGroup_t C_WeaponStunStick::GetRenderGroup( void ) +{ + return RENDER_GROUP_TWOPASS; +} + +//----------------------------------------------------------------------------- +// Purpose: Tells us we're always a translucent entity +//----------------------------------------------------------------------------- +bool C_WeaponStunStick::InSwing( void ) +{ + int activity = GetActivity(); + + // FIXME: This is needed until the actual animation works + if ( IsCarriedByLocalPlayer() == false ) + return true; + + // These are the swing activities this weapon can play + if ( activity == GetPrimaryAttackActivity() || + activity == GetSecondaryAttackActivity() || + activity == ACT_VM_MISSCENTER || + activity == ACT_VM_MISSCENTER2 ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Draw our special effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::DrawThirdPersonEffects( void ) +{ + Vector vecOrigin; + QAngle vecAngles; + float color[3]; + float scale; + + IMaterial *pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL, NULL, false ); + materials->Bind( pMaterial ); + + // Get bright when swung + if ( InSwing() ) + { + color[0] = color[1] = color[2] = 0.4f; + scale = 22.0f; + } + else + { + color[0] = color[1] = color[2] = 0.1f; + scale = 20.0f; + } + + // Draw an all encompassing glow around the entire head + UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); + DrawHalo( pMaterial, vecOrigin, scale, color ); + + if ( InSwing() ) + { + pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL2, NULL, false ); + materials->Bind( pMaterial ); + + color[0] = color[1] = color[2] = random->RandomFloat( 0.6f, 0.8f ); + scale = random->RandomFloat( 4.0f, 6.0f ); + + // Draw an all encompassing glow around the entire head + UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); + DrawHalo( pMaterial, vecOrigin, scale, color ); + + // Update our effects + if ( gpGlobals->frametime != 0.0f && ( random->RandomInt( 0, 5 ) == 0 ) ) + { + Vector vecOrigin; + QAngle vecAngles; + + GetAttachment( 1, vecOrigin, vecAngles ); + + Vector vForward; + AngleVectors( vecAngles, &vForward ); + + Vector vEnd = vecOrigin - vForward * 1.0f; + + // Inner beams + BeamInfo_t beamInfo; + + beamInfo.m_vecStart = vEnd; + Vector offset = RandomVector( -12, 8 ); + + offset += Vector(4,4,4); + beamInfo.m_vecEnd = vecOrigin + offset; + + beamInfo.m_pStartEnt= cl_entitylist->GetEnt( BEAMENT_ENTITY( entindex() ) ); + beamInfo.m_pEndEnt = cl_entitylist->GetEnt( BEAMENT_ENTITY( entindex() ) ); + beamInfo.m_nStartAttachment = 1; + beamInfo.m_nEndAttachment = -1; + + beamInfo.m_nType = TE_BEAMTESLA; + beamInfo.m_pszModelName = STUNSTICK_BEAM_MATERIAL; + beamInfo.m_flHaloScale = 0.0f; + beamInfo.m_flLife = 0.01f; + beamInfo.m_flWidth = random->RandomFloat( 1.0f, 3.0f ); + beamInfo.m_flEndWidth = 0; + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = random->RandomFloat( 1, 2 ); + beamInfo.m_flBrightness = 255.0; + beamInfo.m_flSpeed = 0.0; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 1.0f; + beamInfo.m_flRed = 255.0f;; + beamInfo.m_flGreen = 255.0f; + beamInfo.m_flBlue = 255.0f; + beamInfo.m_nSegments = 16; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = FBEAM_SHADEOUT; + + beams->CreateBeamPoints( beamInfo ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw our special effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::DrawFirstPersonEffects( void ) +{ + Vector vecOrigin; + QAngle vecAngles; + float color[3]; + float scale; + + IMaterial *pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL_NOZ, NULL, false ); + materials->Bind( pMaterial ); + + // Find where we are in the fade + float fadeAmount = RemapValClamped( gpGlobals->curtime, m_flFadeTime, m_flFadeTime + FADE_DURATION, 1.0f, 0.1f ); + + // Get bright when swung + if ( InSwing() ) + { + color[0] = color[1] = color[2] = 0.4f; + scale = 22.0f; + } + else + { + color[0] = color[1] = color[2] = 0.4f * fadeAmount; + scale = 20.0f; + } + + if ( color[0] > 0.0f ) + { + // Draw an all encompassing glow around the entire head + UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); + DrawHalo( pMaterial, vecOrigin, scale, color ); + } + + // Draw bright points at each attachment location + for ( int i = 0; i < (NUM_BEAM_ATTACHMENTS*2)+1; i++ ) + { + if ( InSwing() ) + { + color[0] = color[1] = color[2] = random->RandomFloat( 0.05f, 0.5f ); + scale = random->RandomFloat( 4.0f, 5.0f ); + } + else + { + color[0] = color[1] = color[2] = random->RandomFloat( 0.05f, 0.5f ) * fadeAmount; + scale = random->RandomFloat( 4.0f, 5.0f ) * fadeAmount; + } + + if ( color[0] > 0.0f ) + { + UTIL_GetWeaponAttachment( this, i, vecOrigin, vecAngles ); + DrawHalo( pMaterial, vecOrigin, scale, color ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw our special effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::DrawEffects( void ) +{ + if ( IsCarriedByLocalPlayer() ) + { + DrawFirstPersonEffects(); + } + else + { + DrawThirdPersonEffects(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Viewmodel was drawn +//----------------------------------------------------------------------------- +void C_WeaponStunStick::ViewModelDrawn( C_BaseViewModel *pBaseViewModel ) +{ + // Don't bother when we're not deployed + if ( IsWeaponVisible() ) + { + // Do all our special effects + DrawEffects(); + } + + BaseClass::ViewModelDrawn( pBaseViewModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a cheap glow quad at our impact point (with sparks) +//----------------------------------------------------------------------------- +void StunstickImpactCallback( const CEffectData &data ) +{ + float scale = random->RandomFloat( 16, 32 ); + + FX_AddQuad( data.m_vOrigin, + data.m_vNormal, + scale, + scale*2.0f, + 1.0f, + 1.0f, + 0.0f, + 0.0f, + random->RandomInt( 0, 360 ), + 0, + Vector( 1.0f, 1.0f, 1.0f ), + 0.1f, + "sprites/light_glow02_add", + 0 ); + + FX_Sparks( data.m_vOrigin, 1, 2, data.m_vNormal, 6, 64, 256 ); +} + +DECLARE_CLIENT_EFFECT( "StunstickImpact", StunstickImpactCallback ); + +#endif diff --git a/game_shared/in_buttons.h b/game_shared/in_buttons.h index 62c44e8d..3d462296 100644 --- a/game_shared/in_buttons.h +++ b/game_shared/in_buttons.h @@ -1,40 +1,40 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#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_ALT2 (1 << 15) -#define IN_SCORE (1 << 16) // Used by client.dll for when scoreboard is held down -#define IN_SPEED (1 << 17) // Player is holding the speed key -#define IN_WALK (1 << 18) // Player holding walk key -#define IN_ZOOM (1 << 19) // Zoom key for HUD zoom -#define IN_WEAPON1 (1 << 20) // weapon defines these bits -#define IN_WEAPON2 (1 << 21) // weapon defines these bits -#define IN_BULLRUSH (1 << 22) -#define IN_GRENADE1 (1 << 23) // grenade 1 -#define IN_GRENADE2 (1 << 24) // grenade 2 - -#endif // IN_BUTTONS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#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_ALT2 (1 << 15) +#define IN_SCORE (1 << 16) // Used by client.dll for when scoreboard is held down +#define IN_SPEED (1 << 17) // Player is holding the speed key +#define IN_WALK (1 << 18) // Player holding walk key +#define IN_ZOOM (1 << 19) // Zoom key for HUD zoom +#define IN_WEAPON1 (1 << 20) // weapon defines these bits +#define IN_WEAPON2 (1 << 21) // weapon defines these bits +#define IN_BULLRUSH (1 << 22) +#define IN_GRENADE1 (1 << 23) // grenade 1 +#define IN_GRENADE2 (1 << 24) // grenade 2 + +#endif // IN_BUTTONS_H diff --git a/game_shared/mapdata_shared.h b/game_shared/mapdata_shared.h index 0f58447e..c7287ce4 100644 --- a/game_shared/mapdata_shared.h +++ b/game_shared/mapdata_shared.h @@ -1,33 +1,33 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef MAPDATA_SHARED_H -#define MAPDATA_SHARED_H -#ifdef _WIN32 -#pragma once -#endif - -#include "interface.h" -#include "vector.h" - -#define INTERFACEVERSION_MAPDATA "MapData001" - -abstract_class IMapData -{ -public: - - // World data queries. - virtual void GetMapBounds( Vector &vecMins, Vector &vecMaxs ) = 0; - virtual void GetMapOrigin( Vector &vecOrigin ) = 0; - virtual void GetMapSize( Vector &vecSize ) = 0; - - // 3D Skybox data queries. - virtual void Get3DSkyboxOrigin( Vector &vecOrigin ) = 0; - virtual float Get3DSkyboxScale( void ) = 0; -}; - -#endif // MAPDATA_SHARED_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MAPDATA_SHARED_H +#define MAPDATA_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + +#include "interface.h" +#include "vector.h" + +#define INTERFACEVERSION_MAPDATA "MapData001" + +abstract_class IMapData +{ +public: + + // World data queries. + virtual void GetMapBounds( Vector &vecMins, Vector &vecMaxs ) = 0; + virtual void GetMapOrigin( Vector &vecOrigin ) = 0; + virtual void GetMapSize( Vector &vecSize ) = 0; + + // 3D Skybox data queries. + virtual void Get3DSkyboxOrigin( Vector &vecOrigin ) = 0; + virtual float Get3DSkyboxScale( void ) = 0; +}; + +#endif // MAPDATA_SHARED_H diff --git a/game_shared/mapentities_shared.cpp b/game_shared/mapentities_shared.cpp index b4d6eee0..d69d74c8 100644 --- a/game_shared/mapentities_shared.cpp +++ b/game_shared/mapentities_shared.cpp @@ -125,7 +125,7 @@ const char *MapEntity_ParseToken( const char *data, char *newToken ) for ( const char *c = s_BraceChars; *c; c++ ) { - s_BraceCharacters[*c] = true; + s_BraceCharacters[static_cast(*c)] = true; } } diff --git a/game_shared/physics_main_shared.cpp b/game_shared/physics_main_shared.cpp index 619ad8ef..79379157 100644 --- a/game_shared/physics_main_shared.cpp +++ b/game_shared/physics_main_shared.cpp @@ -52,7 +52,7 @@ public: CDataObjectAccessSystem() { - COMPILE_TIME_ASSERT( NUM_DATAOBJECT_TYPES <= MAX_ACCESSORS ); + COMPILE_TIME_ASSERT( static_cast(NUM_DATAOBJECT_TYPES) <= static_cast(MAX_ACCESSORS) ); Q_memset( m_Accessors, 0, sizeof( m_Accessors ) ); } diff --git a/game_shared/predictableid.cpp b/game_shared/predictableid.cpp index 1a6e3fe6..5706f464 100644 --- a/game_shared/predictableid.cpp +++ b/game_shared/predictableid.cpp @@ -109,7 +109,9 @@ void CPredictableId::ResetInstanceCounters( void ) //----------------------------------------------------------------------------- bool CPredictableId::IsActive( void ) const { - if ( *(const int *)&m_PredictableID == 0 ) + const void *predict = &m_PredictableID; + + if (*reinterpret_cast(predict) == 0) return false; return true; @@ -267,7 +269,8 @@ bool CPredictableId::GetAcknowledged( void ) const //----------------------------------------------------------------------------- int CPredictableId::GetRaw( void ) const { - return *(int *)&m_PredictableID; + const void *predict = &m_PredictableID; + return *reinterpret_cast(predict); } //----------------------------------------------------------------------------- @@ -276,7 +279,8 @@ int CPredictableId::GetRaw( void ) const //----------------------------------------------------------------------------- void CPredictableId::SetRaw( int raw ) { - *(int *)&m_PredictableID = raw; + void *predict = &m_PredictableID; + *reinterpret_cast(predict) = raw; } //----------------------------------------------------------------------------- diff --git a/game_shared/predicted_viewmodel.cpp b/game_shared/predicted_viewmodel.cpp index 38114d0e..e4324258 100644 --- a/game_shared/predicted_viewmodel.cpp +++ b/game_shared/predicted_viewmodel.cpp @@ -1,71 +1,71 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// -#include "cbase.h" -#include "predicted_viewmodel.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -LINK_ENTITY_TO_CLASS( predicted_viewmodel, CPredictedViewModel ); - -IMPLEMENT_NETWORKCLASS_ALIASED( PredictedViewModel, DT_PredictedViewModel ) - -BEGIN_NETWORK_TABLE( CPredictedViewModel, DT_PredictedViewModel ) -END_NETWORK_TABLE() - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -#ifdef CLIENT_DLL -CPredictedViewModel::CPredictedViewModel() : m_LagAnglesHistory("CPredictedViewModel::m_LagAnglesHistory") -{ - m_vLagAngles.Init(); - m_LagAnglesHistory.Setup( &m_vLagAngles, 0 ); -} -#else -CPredictedViewModel::CPredictedViewModel() -{ -} -#endif - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CPredictedViewModel::~CPredictedViewModel() -{ -} - -#ifdef CLIENT_DLL -ConVar cl_wpn_sway_interp( "cl_wpn_sway_interp", "0.1", FCVAR_CLIENTDLL ); -ConVar cl_wpn_sway_scale( "cl_wpn_sway_scale", "1.0", FCVAR_CLIENTDLL ); -#endif - -void CPredictedViewModel::CalcViewModelLag( Vector& origin, QAngle& angles, QAngle& original_angles ) -{ - #ifdef CLIENT_DLL - // Calculate our drift - Vector forward, right, up; - AngleVectors( angles, &forward, &right, &up ); - - // Add an entry to the history. - m_vLagAngles = angles; - m_LagAnglesHistory.NoteChanged( gpGlobals->curtime, cl_wpn_sway_interp.GetFloat() ); - - // Interpolate back 100ms. - m_LagAnglesHistory.Interpolate( gpGlobals->curtime, cl_wpn_sway_interp.GetFloat() ); - - // Now take the 100ms angle difference and figure out how far the forward vector moved in local space. - Vector vLaggedForward; - QAngle angleDiff = m_vLagAngles - angles; - AngleVectors( -angleDiff, &vLaggedForward, 0, 0 ); - Vector vForwardDiff = Vector(1,0,0) - vLaggedForward; - - // Now offset the origin using that. - vForwardDiff *= cl_wpn_sway_scale.GetFloat(); - origin += forward*vForwardDiff.x + right*-vForwardDiff.y + up*vForwardDiff.z; - #endif -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "predicted_viewmodel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LINK_ENTITY_TO_CLASS( predicted_viewmodel, CPredictedViewModel ); + +IMPLEMENT_NETWORKCLASS_ALIASED( PredictedViewModel, DT_PredictedViewModel ) + +BEGIN_NETWORK_TABLE( CPredictedViewModel, DT_PredictedViewModel ) +END_NETWORK_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef CLIENT_DLL +CPredictedViewModel::CPredictedViewModel() : m_LagAnglesHistory("CPredictedViewModel::m_LagAnglesHistory") +{ + m_vLagAngles.Init(); + m_LagAnglesHistory.Setup( &m_vLagAngles, 0 ); +} +#else +CPredictedViewModel::CPredictedViewModel() +{ +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPredictedViewModel::~CPredictedViewModel() +{ +} + +#ifdef CLIENT_DLL +ConVar cl_wpn_sway_interp( "cl_wpn_sway_interp", "0.1", FCVAR_CLIENTDLL ); +ConVar cl_wpn_sway_scale( "cl_wpn_sway_scale", "1.0", FCVAR_CLIENTDLL ); +#endif + +void CPredictedViewModel::CalcViewModelLag( Vector& origin, QAngle& angles, QAngle& original_angles ) +{ + #ifdef CLIENT_DLL + // Calculate our drift + Vector forward, right, up; + AngleVectors( angles, &forward, &right, &up ); + + // Add an entry to the history. + m_vLagAngles = angles; + m_LagAnglesHistory.NoteChanged( gpGlobals->curtime, cl_wpn_sway_interp.GetFloat() ); + + // Interpolate back 100ms. + m_LagAnglesHistory.Interpolate( gpGlobals->curtime, cl_wpn_sway_interp.GetFloat() ); + + // Now take the 100ms angle difference and figure out how far the forward vector moved in local space. + Vector vLaggedForward; + QAngle angleDiff = m_vLagAngles - angles; + AngleVectors( -angleDiff, &vLaggedForward, 0, 0 ); + Vector vForwardDiff = Vector(1,0,0) - vLaggedForward; + + // Now offset the origin using that. + vForwardDiff *= cl_wpn_sway_scale.GetFloat(); + origin += forward*vForwardDiff.x + right*-vForwardDiff.y + up*vForwardDiff.z; + #endif +} diff --git a/game_shared/props_shared.cpp b/game_shared/props_shared.cpp index baefefb2..68bf1fbe 100644 --- a/game_shared/props_shared.cpp +++ b/game_shared/props_shared.cpp @@ -276,14 +276,14 @@ int CPropData::ParsePropFromKV( CBaseEntity *pProp, KeyValues *pSection, KeyValu } // Allow overriding of Block LOS - int iBlockLOS = pSection->GetFloat( "blockLOS", -1 ); + int iBlockLOS = pSection->GetInt( "blockLOS", -1 ); if ( iBlockLOS != -1 ) { pBreakableInterface->SetPropDataBlocksLOS( iBlockLOS != 0 ); } // Set whether AI can walk on this prop - int iIsWalkable = pSection->GetFloat( "AIWalkable", -1 ); + int iIsWalkable = pSection->GetInt( "AIWalkable", -1 ); if ( iIsWalkable != -1 ) { pBreakableInterface->SetPropDataIsAIWalkable( iIsWalkable != 0 ); @@ -380,7 +380,7 @@ int CPropData::ParsePropFromKV( CBaseEntity *pProp, KeyValues *pSection, KeyValu int iSmallest = SmallestAxis(vecSize); vecSize[iSmallest] = 1; float flVolume = vecSize.x * vecSize.y * vecSize.z; - int iMaxSize = floor( flVolume / (32.0*32.0) ); + int iMaxSize = static_cast(floor( flVolume / (32.0*32.0) )); pBreakableInterface->SetMaxBreakableSize( iMaxSize ); // Now parse our interactions @@ -520,7 +520,7 @@ class CBreakParser : public IVPhysicsKeyHandler { public: CBreakParser( float defaultBurstScale, int defaultCollisionGroup ) - : m_defaultBurstScale(defaultBurstScale), m_defaultCollisionGroup(defaultCollisionGroup) {} + : m_defaultCollisionGroup(defaultCollisionGroup), m_defaultBurstScale(defaultBurstScale) {} void ParseModelName( breakmodel_t *pModel, const char *pValue ) { @@ -744,7 +744,7 @@ const char *GetMassEquivalent(float flMass) { 7e24, "really freaking heavy" }, }; - for (int i = 0; i < sizeof(masstext) / sizeof(masstext[0]) - 1; i++) + for (size_t i = 0; i < sizeof(masstext) / sizeof(masstext[0]) - 1; i++) { if (flMass < masstext[i].flMass) { diff --git a/game_shared/rumble_shared.h b/game_shared/rumble_shared.h index 3e09e342..d331d20f 100644 --- a/game_shared/rumble_shared.h +++ b/game_shared/rumble_shared.h @@ -1,65 +1,65 @@ -//======= Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// -// -// Purpose: Shared code for XBox Rumble Effects -// -// $NoKeywords: $ -// -//=============================================================================// -#pragma once -#ifndef RUMBLE_SHARED_H -#define RUMBLE_SHARED_H - -#define RUMBLE_FLAGS_NONE 0x0000 -#define RUMBLE_FLAG_STOP 0x0001 // Stop any instance of this type of effect that's already playing. -#define RUMBLE_FLAG_LOOP 0x0002 // Make this effect loop. -#define RUMBLE_FLAG_RESTART 0x0004 // If this effect is already playing, restart it. -#define RUMBLE_FLAG_UPDATE_SCALE 0x0008 // Apply DATA to this effect if already playing, but don't restart. -#define RUMBLE_FLAG_ONLYONE 0x0010 // Don't play this effect if it is already playing. -#define RUMBLE_FLAG_RANDOM_AMPLITUDE 0x0020 // Amplitude scale will be randomly chosen. Between 10% and 100% -#define RUMBLE_FLAG_INITIAL_SCALE 0x0040 // Data is the initial scale to start this effect ( * 100 ) - -enum -{ -// DO NOT CHANGE THE ORDER OF ANY OF THESE ENUMS -// DO NOT INSERT ANY ITEMS - RUMBLE_INVALID = -1, - - RUMBLE_STOP_ALL = 0, // Cease all current rumbling effects. - - // Weapons - RUMBLE_PISTOL, - RUMBLE_357, - RUMBLE_SMG1, - RUMBLE_AR2, - RUMBLE_SHOTGUN_SINGLE, - RUMBLE_SHOTGUN_DOUBLE, - RUMBLE_AR2_ALT_FIRE, - -// YOU MAY INSERT/REARRANGE ITEMS FROM HERE DOWN, AS YOU SEE FIT - RUMBLE_RPG_MISSILE, - - RUMBLE_CROWBAR_SWING, - - // Vehicles - RUMBLE_AIRBOAT_GUN, - RUMBLE_JEEP_ENGINE_LOOP, - - RUMBLE_FLAT_LEFT, - RUMBLE_FLAT_RIGHT, - RUMBLE_FLAT_BOTH, - - // Damage - RUMBLE_DMG_LOW, - RUMBLE_DMG_MED, - RUMBLE_DMG_HIGH, - - RUMBLE_PHYSCANNON_OPEN, - RUMBLE_PHYSCANNON_PUNT, - RUMBLE_PHYSCANNON_LOW, - RUMBLE_PHYSCANNON_MEDIUM, - RUMBLE_PHYSCANNON_HIGH, - - NUM_RUMBLE_EFFECTS, // THIS MUST BE LAST!!! -}; - -#endif//RUMBLE_SHARED_H \ No newline at end of file +//======= Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Shared code for XBox Rumble Effects +// +// $NoKeywords: $ +// +//=============================================================================// +#pragma once +#ifndef RUMBLE_SHARED_H +#define RUMBLE_SHARED_H + +#define RUMBLE_FLAGS_NONE 0x0000 +#define RUMBLE_FLAG_STOP 0x0001 // Stop any instance of this type of effect that's already playing. +#define RUMBLE_FLAG_LOOP 0x0002 // Make this effect loop. +#define RUMBLE_FLAG_RESTART 0x0004 // If this effect is already playing, restart it. +#define RUMBLE_FLAG_UPDATE_SCALE 0x0008 // Apply DATA to this effect if already playing, but don't restart. +#define RUMBLE_FLAG_ONLYONE 0x0010 // Don't play this effect if it is already playing. +#define RUMBLE_FLAG_RANDOM_AMPLITUDE 0x0020 // Amplitude scale will be randomly chosen. Between 10% and 100% +#define RUMBLE_FLAG_INITIAL_SCALE 0x0040 // Data is the initial scale to start this effect ( * 100 ) + +enum +{ +// DO NOT CHANGE THE ORDER OF ANY OF THESE ENUMS +// DO NOT INSERT ANY ITEMS + RUMBLE_INVALID = -1, + + RUMBLE_STOP_ALL = 0, // Cease all current rumbling effects. + + // Weapons + RUMBLE_PISTOL, + RUMBLE_357, + RUMBLE_SMG1, + RUMBLE_AR2, + RUMBLE_SHOTGUN_SINGLE, + RUMBLE_SHOTGUN_DOUBLE, + RUMBLE_AR2_ALT_FIRE, + +// YOU MAY INSERT/REARRANGE ITEMS FROM HERE DOWN, AS YOU SEE FIT + RUMBLE_RPG_MISSILE, + + RUMBLE_CROWBAR_SWING, + + // Vehicles + RUMBLE_AIRBOAT_GUN, + RUMBLE_JEEP_ENGINE_LOOP, + + RUMBLE_FLAT_LEFT, + RUMBLE_FLAT_RIGHT, + RUMBLE_FLAT_BOTH, + + // Damage + RUMBLE_DMG_LOW, + RUMBLE_DMG_MED, + RUMBLE_DMG_HIGH, + + RUMBLE_PHYSCANNON_OPEN, + RUMBLE_PHYSCANNON_PUNT, + RUMBLE_PHYSCANNON_LOW, + RUMBLE_PHYSCANNON_MEDIUM, + RUMBLE_PHYSCANNON_HIGH, + + NUM_RUMBLE_EFFECTS, // THIS MUST BE LAST!!! +}; + +#endif//RUMBLE_SHARED_H diff --git a/game_shared/saverestore.cpp b/game_shared/saverestore.cpp index 0315ad9b..342f8015 100644 --- a/game_shared/saverestore.cpp +++ b/game_shared/saverestore.cpp @@ -1,3379 +1,3379 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Helper classes and functions for the save/restore system. -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include -#include "isaverestore.h" -#include "saverestore.h" -#include -#include "shake.h" -#include "decals.h" -#include "gamerules.h" -#include "bspfile.h" -#include "mathlib.h" -#include "engine/IEngineSound.h" -#include "saverestoretypes.h" -#include "saverestore_utlvector.h" -#include "model_types.h" -#include "igamesystem.h" -#include "interval.h" -#include "vphysics/object_hash.h" -#include "datacache/imdlcache.h" - -#if !defined( CLIENT_DLL ) - -#include "globalstate.h" -#include "entitylist.h" - -#else - -#include "gamestringpool.h" - -#endif - -// HACKHACK: Builds a global list of entities that were restored from all levels -#if !defined( CLIENT_DLL ) -void AddRestoredEntity( CBaseEntity *pEntity ); -#else -void AddRestoredEntity( C_BaseEntity *pEntity ); -#endif - - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define MAX_ENTITYARRAY 1024 -#define ZERO_TIME ((FLT_MAX*-0.5)) -// A bit arbitrary, but unlikely to collide with any saved games... -#define TICK_NEVER_THINK_ENCODE ( INT_MAX - 3 ) - -ASSERT_INVARIANT( sizeof(EHandlePlaceholder_t) == sizeof(EHANDLE) ); - -//----------------------------------------------------------------------------- - -static int gSizes[FIELD_TYPECOUNT] = -{ - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), - CDatamapFieldSizeDeducer::FieldSize(), -}; - - -// helpers to offset worldspace matrices -static void VMatrixOffset( VMatrix &dest, const VMatrix &matrixIn, const Vector &offset ) -{ - dest = matrixIn; - dest.PostTranslate( offset ); -} - -static void Matrix3x4Offset( matrix3x4_t& dest, const matrix3x4_t& matrixIn, const Vector &offset ) -{ - MatrixCopy( matrixIn, dest ); - Vector out; - MatrixGetColumn( matrixIn, 3, out ); - out += offset; - MatrixSetColumn( out, 3, dest ); -} - -// This does the necessary casting / extract to grab a pointer to a member function as a void * -// UNDONE: Cast to BASEPTR or something else here? -#define EXTRACT_VOID_FUNCTIONPTR(x) (*(void **)(&(x))) -//----------------------------------------------------------------------------- -// Purpose: Search this datamap for the name of this member function -// This is used to save/restore function pointers (convert pointer to text) -// Input : *function - pointer to member function -// Output : const char * - function name -//----------------------------------------------------------------------------- -const char *UTIL_FunctionToName( datamap_t *pMap, void *function ) -{ - while ( pMap ) - { - for ( int i = 0; i < pMap->dataNumFields; i++ ) - { - if ( pMap->dataDesc[i].flags & FTYPEDESC_FUNCTIONTABLE ) - { - Assert( sizeof(pMap->dataDesc[i].inputFunc) == sizeof(void *) ); - void *pTest = EXTRACT_VOID_FUNCTIONPTR(pMap->dataDesc[i].inputFunc); - if ( pTest == function ) - return pMap->dataDesc[i].fieldName; - } - } - pMap = pMap->baseMap; - } - - return NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Search the datamap for a function named pName -// This is used to save/restore function pointers (convert text back to pointer) -// Input : *pName - name of the member function -//----------------------------------------------------------------------------- -void *UTIL_FunctionFromName( datamap_t *pMap, const char *pName ) -{ - while ( pMap ) - { - for ( int i = 0; i < pMap->dataNumFields; i++ ) - { - Assert( sizeof(pMap->dataDesc[i].inputFunc) == sizeof(void *) ); - - if ( pMap->dataDesc[i].flags & FTYPEDESC_FUNCTIONTABLE ) - { - if ( FStrEq( pName, pMap->dataDesc[i].fieldName ) ) - { - return EXTRACT_VOID_FUNCTIONPTR(pMap->dataDesc[i].inputFunc); - } - } - } - pMap = pMap->baseMap; - } - - Msg( "Failed to find function %s\n", pName ); - - return NULL; -} - -//----------------------------------------------------------------------------- -// -// CSave -// -//----------------------------------------------------------------------------- - -CSave::CSave( CSaveRestoreData *pdata ) - : m_pData(pdata), - m_pGameInfo( pdata ) -{ - m_BlockStartStack.EnsureCapacity( 32 ); - - // Logging. - m_hLogFile = NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Start logging save data. -//----------------------------------------------------------------------------- -void CSave::StartLogging( const char *pszLogName ) -{ - m_hLogFile = filesystem->Open( pszLogName, "w" ); -} - -//----------------------------------------------------------------------------- -// Purpose: Stop logging save data. -//----------------------------------------------------------------------------- -void CSave::EndLogging( void ) -{ - if ( m_hLogFile ) - { - filesystem->Close( m_hLogFile ); - } - m_hLogFile = NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Check to see if we are logging data. -//----------------------------------------------------------------------------- -bool CSave::IsLogging( void ) -{ - return ( m_hLogFile != NULL ); -} - -//----------------------------------------------------------------------------- -// Purpose: Log data. -//----------------------------------------------------------------------------- -void CSave::Log( const char *pName, fieldtype_t fieldType, void *value, int count ) -{ - // Check to see if we are logging. - if ( !IsLogging() ) - return; - - static char szBuf[1024]; - static char szTempBuf[256]; - - // Save the name. - Q_snprintf( szBuf, sizeof( szBuf ), "%s ", pName ); - - for ( int iCount = 0; iCount < count; ++iCount ) - { - switch ( fieldType ) - { - case FIELD_SHORT: - { - short *pValue = ( short* )( value ); - short nValue = pValue[iCount]; - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%d", nValue ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - break; - } - case FIELD_FLOAT: - { - float *pValue = ( float* )( value ); - float flValue = pValue[iCount]; - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%f", flValue ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - break; - } - case FIELD_BOOLEAN: - { - bool *pValue = ( bool* )( value ); - bool bValue = pValue[iCount]; - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%d", ( int )( bValue ) ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - break; - } - case FIELD_INTEGER: - { - int *pValue = ( int* )( value ); - int nValue = pValue[iCount]; - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%d", nValue ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - break; - } - case FIELD_STRING: - { - string_t *pValue = ( string_t* )( value ); - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%s", ( char* )STRING( *pValue ) ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - break; - } - case FIELD_VECTOR: - { - Vector *pValue = ( Vector* )( value ); - Vector vecValue = pValue[iCount]; - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "(%f %f %f)", vecValue.x, vecValue.y, vecValue.z ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - break; - } - case FIELD_QUATERNION: - { - Quaternion *pValue = ( Quaternion* )( value ); - Quaternion q = pValue[iCount]; - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "(%f %f %f %f)", q[0], q[1], q[2], q[3] ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - break; - } - case FIELD_CHARACTER: - { - char *pValue = ( char* )( value ); - char chValue = pValue[iCount]; - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%c", chValue ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - } - case FIELD_COLOR32: - { - byte *pValue = ( byte* )( value ); - byte *pColor = &pValue[iCount*4]; - Q_snprintf( szTempBuf, sizeof( szTempBuf ), "(%d %d %d %d)", ( int )pColor[0], ( int )pColor[1], ( int )pColor[2], ( int )pColor[3] ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - } - case FIELD_EMBEDDED: - case FIELD_CUSTOM: - default: - { - break; - } - } - - // Add space data. - if ( ( iCount + 1 ) != count ) - { - Q_strncpy( szTempBuf, " ", sizeof( szTempBuf ) ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - } - else - { - Q_strncpy( szTempBuf, "\n", sizeof( szTempBuf ) ); - Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); - } - } - - int nLength = strlen( szBuf ) + 1; - filesystem->Write( szBuf, nLength, m_hLogFile ); -} - -//------------------------------------- - -int CSave::GetWritePos() const -{ - return m_pData->GetCurPos(); -} - -//------------------------------------- - -void CSave::SetWritePos(int pos) -{ - m_pData->Seek(pos); -} - -//------------------------------------- - -void CSave::WriteShort( const short *value, int count ) -{ - BufferData( (const char *)value, sizeof(short) * count ); -} - -//------------------------------------- - -void CSave::WriteInt( const int *value, int count ) -{ - BufferData( (const char *)value, sizeof(int) * count ); -} - -//------------------------------------- - -void CSave::WriteBool( const bool *value, int count ) -{ - COMPILE_TIME_ASSERT( sizeof(bool) == sizeof(char) ); - BufferData( (const char *)value, sizeof(bool) * count ); -} - -//------------------------------------- - -void CSave::WriteFloat( const float *value, int count ) -{ - BufferData( (const char *)value, sizeof(float) * count ); -} - -//------------------------------------- - -void CSave::WriteData( const char *pdata , int size ) -{ - BufferData( pdata, size ); -} - -//------------------------------------- - -void CSave::WriteString( const char *pstring ) -{ - BufferData( pstring, strlen(pstring) + 1 ); -} - -//------------------------------------- - -void CSave::WriteString( const string_t *stringId, int count ) -{ - for ( int i = 0; i < count; i++ ) - { - const char *pString = STRING(stringId[i]); - BufferData( pString, strlen(pString)+1 ); - } -} - -//------------------------------------- - -void CSave::WriteVector( const Vector &value ) -{ - BufferData( (const char *)&value, sizeof(Vector) ); -} - -//------------------------------------- - -void CSave::WriteVector( const Vector *value, int count ) -{ - BufferData( (const char *)value, sizeof(Vector) * count ); -} - -void CSave::WriteQuaternion( const Quaternion &value ) -{ - BufferData( (const char *)&value, sizeof(Quaternion) ); -} - -//------------------------------------- - -void CSave::WriteQuaternion( const Quaternion *value, int count ) -{ - BufferData( (const char *)value, sizeof(Quaternion) * count ); -} - - -//------------------------------------- - -void CSave::WriteData( const char *pname, int size, const char *pdata ) -{ - BufferField( pname, size, pdata ); -} - -//------------------------------------- - -void CSave::WriteShort( const char *pname, const short *data, int count ) -{ - BufferField( pname, sizeof(short) * count, (const char *)data ); -} - -//------------------------------------- - -void CSave::WriteInt( const char *pname, const int *data, int count ) -{ - BufferField( pname, sizeof(int) * count, (const char *)data ); -} - -//------------------------------------- - -void CSave::WriteBool( const char *pname, const bool *data, int count ) -{ - COMPILE_TIME_ASSERT( sizeof(bool) == sizeof(char) ); - BufferField( pname, sizeof(bool) * count, (const char *)data ); -} - -//------------------------------------- - -void CSave::WriteFloat( const char *pname, const float *data, int count ) -{ - BufferField( pname, sizeof(float) * count, (const char *)data ); -} - -//------------------------------------- - -void CSave::WriteString( const char *pname, const char *pdata ) -{ - BufferField( pname, strlen(pdata) + 1, pdata ); -} - -//------------------------------------- - -void CSave::WriteString( const char *pname, const string_t *stringId, int count ) -{ - int i, size; - - size = 0; - for ( i = 0; i < count; i++ ) - size += strlen( STRING( stringId[i] ) ) + 1; - - WriteHeader( pname, size ); - WriteString( stringId, count ); -} - -//------------------------------------- - -void CSave::WriteVector( const char *pname, const Vector &value ) -{ - WriteVector( pname, &value, 1 ); -} - -//------------------------------------- - -void CSave::WriteVector( const char *pname, const Vector *value, int count ) -{ - WriteHeader( pname, sizeof(Vector) * count ); - BufferData( (const char *)value, sizeof(Vector) * count ); -} - -void CSave::WriteQuaternion( const char *pname, const Quaternion &value ) -{ - WriteQuaternion( pname, &value, 1 ); -} - -//------------------------------------- - -void CSave::WriteQuaternion( const char *pname, const Quaternion *value, int count ) -{ - WriteHeader( pname, sizeof(Quaternion) * count ); - BufferData( (const char *)value, sizeof(Quaternion) * count ); -} - - -//------------------------------------- - -void CSave::WriteVMatrix( const VMatrix *value, int count ) -{ - BufferData( (const char *)value, sizeof(VMatrix) * count ); -} - -//------------------------------------- - -void CSave::WriteVMatrix( const char *pname, const VMatrix *value, int count ) -{ - WriteHeader( pname, sizeof(VMatrix) * count ); - BufferData( (const char *)value, sizeof(VMatrix) * count ); -} - -//------------------------------------- - -void CSave::WriteVMatrixWorldspace( const VMatrix *value, int count ) -{ - for ( int i = 0; i < count; i++ ) - { - VMatrix tmp; - VMatrixOffset( tmp, value[i], -m_pGameInfo->GetLandmark() ); - BufferData( (const char *)&tmp, sizeof(VMatrix) ); - } -} - -//------------------------------------- - -void CSave::WriteVMatrixWorldspace( const char *pname, const VMatrix *value, int count ) -{ - WriteHeader( pname, sizeof(VMatrix) * count ); - WriteVMatrixWorldspace( value, count ); -} - -void CSave::WriteMatrix3x4Worldspace( const matrix3x4_t *value, int count ) -{ - Vector offset = -m_pGameInfo->GetLandmark(); - for ( int i = 0; i < count; i++ ) - { - matrix3x4_t tmp; - Matrix3x4Offset( tmp, value[i], offset ); - BufferData( (const char *)value, sizeof(matrix3x4_t) ); - } -} - -//------------------------------------- - -void CSave::WriteMatrix3x4Worldspace( const char *pname, const matrix3x4_t *value, int count ) -{ - WriteHeader( pname, sizeof(matrix3x4_t) * count ); - WriteMatrix3x4Worldspace( value, count ); -} - -void CSave::WriteInterval( const char *pname, const interval_t *value, int count ) -{ - WriteHeader( pname, sizeof( interval_t ) * count ); - WriteInterval( value, count ); -} - -void CSave::WriteInterval( const interval_t *value, int count ) -{ - BufferData( (const char *)value, count * sizeof( interval_t ) ); -} - -//------------------------------------- - -bool CSave::ShouldSaveField( const void *pData, typedescription_t *pField ) -{ - if ( !(pField->flags & FTYPEDESC_SAVE) || pField->fieldType == FIELD_VOID ) - return false; - - switch ( pField->fieldType ) - { - case FIELD_EMBEDDED: - { - if ( pField->flags & FTYPEDESC_PTR ) - { - AssertMsg( pField->fieldSize == 1, "Arrays of embedded pointer types presently unsupported by save/restore" ); - if ( pField->fieldSize != 1 ) - return false; - } - - AssertMsg( pField->td != NULL, "Embedded type appears to have not had type description implemented" ); - if ( pField->td == NULL ) - return false; - - if ( (pField->flags & FTYPEDESC_PTR) && !*((void **)pData) ) - return false; - - int nFieldCount = pField->fieldSize; - char *pTestData = (char *)( ( !(pField->flags & FTYPEDESC_PTR) ) ? pData : *((void **)pData) ); - while ( --nFieldCount >= 0 ) - { - typedescription_t *pTestField = pField->td->dataDesc; - typedescription_t *pLimit = pField->td->dataDesc + pField->td->dataNumFields; - - for ( ; pTestField < pLimit; ++pTestField ) - { - if ( ShouldSaveField( pTestData + pTestField->fieldOffset[ TD_OFFSET_NORMAL ], pTestField ) ) - return true; - } - - pTestData += pField->fieldSizeInBytes; - } - return false; - } - - case FIELD_CUSTOM: - { - // ask the data if it's empty - SaveRestoreFieldInfo_t fieldInfo = - { - const_cast(pData), - ((char *)pData) - pField->fieldOffset[ TD_OFFSET_NORMAL ], - pField - }; - if ( pField->pSaveRestoreOps->IsEmpty( fieldInfo ) ) - return false; - } - return true; - - case FIELD_EHANDLE: - { - if ( (pField->fieldSizeInBytes != pField->fieldSize * gSizes[pField->fieldType]) ) - { - Warning("WARNING! Field %s is using the wrong FIELD_ type!\nFix this or you'll see a crash.\n", pField->fieldName ); - Assert( 0 ); - } - - int *pEHandle = (int *)pData; - for ( int i = 0; i < pField->fieldSize; ++i, ++pEHandle ) - { - if ( (*pEHandle) != 0xFFFFFFFF ) - return true; - } - } - return false; - - default: - { - if ( (pField->fieldSizeInBytes != pField->fieldSize * gSizes[pField->fieldType]) ) - { - Warning("WARNING! Field %s is using the wrong FIELD_ type!\nFix this or you'll see a crash.\n", pField->fieldName ); - Assert( 0 ); - } - - // old byte-by-byte null check - if ( DataEmpty( (const char *)pData, pField->fieldSize * gSizes[pField->fieldType] ) ) - return false; - } - return true; - } -} - -//------------------------------------- -// Purpose: Writes all the fields that are client neutral. In the event of -// a librarization of save/restore, these would reside in the library -// - -bool CSave::WriteBasicField( const char *pname, void *pData, datamap_t *pRootMap, typedescription_t *pField ) -{ - switch( pField->fieldType ) - { - case FIELD_FLOAT: - WriteFloat( pField->fieldName, (float *)pData, pField->fieldSize ); - break; - - case FIELD_STRING: - WriteString( pField->fieldName, (string_t *)pData, pField->fieldSize ); - break; - - case FIELD_VECTOR: - WriteVector( pField->fieldName, (Vector *)pData, pField->fieldSize ); - break; - - case FIELD_QUATERNION: - WriteQuaternion( pField->fieldName, (Quaternion *)pData, pField->fieldSize ); - break; - - case FIELD_INTEGER: - WriteInt( pField->fieldName, (int *)pData, pField->fieldSize ); - break; - - case FIELD_BOOLEAN: - WriteBool( pField->fieldName, (bool *)pData, pField->fieldSize ); - break; - - case FIELD_SHORT: - WriteData( pField->fieldName, 2 * pField->fieldSize, ((char *)pData) ); - break; - - case FIELD_CHARACTER: - WriteData( pField->fieldName, pField->fieldSize, ((char *)pData) ); - break; - - case FIELD_COLOR32: - WriteData( pField->fieldName, 4*pField->fieldSize, (char *)pData ); - break; - - case FIELD_EMBEDDED: - { - AssertMsg( ( (pField->flags & FTYPEDESC_PTR) == 0 ) || (pField->fieldSize == 1), "Arrays of embedded pointer types presently unsupported by save/restore" ); - Assert( !(pField->flags & FTYPEDESC_PTR) || *((void **)pData) ); - int nFieldCount = pField->fieldSize; - char *pFieldData = (char *)( ( !(pField->flags & FTYPEDESC_PTR) ) ? pData : *((void **)pData) ); - - StartBlock( pField->fieldName ); - - while ( --nFieldCount >= 0 ) - { - WriteAll( pFieldData, pField->td ); - pFieldData += pField->fieldSizeInBytes; - } - - EndBlock(); - break; - } - - case FIELD_CUSTOM: - { - // Note it is up to the custom type implementor to handle arrays - StartBlock( pField->fieldName ); - - SaveRestoreFieldInfo_t fieldInfo = - { - pData, - ((char *)pData) - pField->fieldOffset[ TD_OFFSET_NORMAL ], - pField - }; - pField->pSaveRestoreOps->Save( fieldInfo, this ); - - EndBlock(); - break; - } - - default: - Warning( "Bad field type\n" ); - Assert(0); - return false; - } - - return true; -} - -//------------------------------------- - -bool CSave::WriteField( const char *pname, void *pData, datamap_t *pRootMap, typedescription_t *pField ) -{ -#ifdef _DEBUG - Log( pname, (fieldtype_t)pField->fieldType, pData, pField->fieldSize ); -#endif - - if ( pField->fieldType <= FIELD_CUSTOM ) - { - return WriteBasicField( pname, pData, pRootMap, pField ); - } - return WriteGameField( pname, pData, pRootMap, pField ); -} - -//------------------------------------- - -int CSave::CountFieldsToSave( const void *pBaseData, typedescription_t *pFields, int fieldCount ) -{ - int result = 0; - for ( int i = 0; i < fieldCount; i++ ) - { - if ( ShouldSaveField( (char *)pBaseData + pFields[i].fieldOffset[ TD_OFFSET_NORMAL ], &pFields[i] ) ) - result++; - } - return result; -} - -//------------------------------------- - -int CSave::WriteFields( const char *pname, const void *pBaseData, datamap_t *pRootMap, typedescription_t *pFields, int fieldCount ) -{ - int i, actualCount; - typedescription_t *pTest; - - // Empty fields will not be written, write out the actual number of fields to be written - actualCount = CountFieldsToSave( pBaseData, pFields, fieldCount ); - WriteInt( pname, &actualCount, 1 ); - - for ( i = 0; i < fieldCount; i++ ) - { - pTest = &pFields[ i ]; - - void *pOutputData = ( (char *)pBaseData + pTest->fieldOffset[ TD_OFFSET_NORMAL ] ); - if ( !ShouldSaveField( pOutputData, pTest ) ) - continue; - - if ( !WriteField( pname, pOutputData, pRootMap, pTest ) ) - break; - } - - return 1; -} - -//------------------------------------- -// Purpose: Recursively saves all the classes in an object, in reverse order (top down) -// Output : int 0 on failure, 1 on success - -int CSave::DoWriteAll( const void *pLeafObject, datamap_t *pLeafMap, datamap_t *pCurMap ) -{ - // save base classes first - if ( pCurMap->baseMap ) - { - int status = DoWriteAll( pLeafObject, pLeafMap, pCurMap->baseMap ); - if ( !status ) - return status; - } - - return WriteFields( pCurMap->dataClassName, pLeafObject, pLeafMap, pCurMap->dataDesc, pCurMap->dataNumFields ); -} - -//------------------------------------- - -void CSave::StartBlock( const char *pszBlockName ) -{ - WriteHeader( pszBlockName, 0 ); // placeholder - m_BlockStartStack.AddToTail( GetWritePos() ); -} - -//------------------------------------- - -void CSave::StartBlock() -{ - StartBlock( "" ); -} - -//------------------------------------- - -void CSave::EndBlock() -{ - int endPos = GetWritePos(); - int startPos = m_BlockStartStack[ m_BlockStartStack.Count() - 1 ]; - short sizeBlock = endPos - startPos; - - m_BlockStartStack.Remove( m_BlockStartStack.Count() - 1 ); - - // Move to the the location where the size of the block was written & rewrite the size - SetWritePos( startPos - sizeof(SaveRestoreRecordHeader_t) ); - BufferData( (const char *)&sizeBlock, sizeof(short) ); - - SetWritePos( endPos ); -} - -//------------------------------------- - -void CSave::BufferString( char *pdata, int len ) -{ - char c = 0; - - BufferData( pdata, len ); // Write the string - BufferData( &c, 1 ); // Write a null terminator -} - -//------------------------------------- - -int CSave::DataEmpty( const char *pdata, int size ) -{ - for ( int i = 0; i < size; i++ ) - { - if ( pdata[i] ) - return 0; - } - return 1; -} - -//------------------------------------- - -void CSave::BufferField( const char *pname, int size, const char *pdata ) -{ - WriteHeader( pname, size ); - BufferData( pdata, size ); -} - -//------------------------------------- - -void CSave::WriteHeader( const char *pname, int size ) -{ - short shortSize = size; - short hashvalue = m_pData->FindCreateSymbol( pname ); - if ( size > SHRT_MAX || size < 0 ) - { - Warning( "CSave::WriteHeader() size parameter exceeds 'short'!\n" ); - Assert(0); - } - - BufferData( (const char *)&shortSize, sizeof(short) ); - BufferData( (const char *)&hashvalue, sizeof(short) ); -} - -//------------------------------------- - -void CSave::BufferData( const char *pdata, int size ) -{ - if ( !m_pData ) - return; - - if ( !m_pData->Write( pdata, size ) ) - { - Warning( "Save/Restore overflow!\n" ); - Assert(0); - } -} - -//--------------------------------------------------------- -// -// Game centric save methods. -// -int CSave::EntityIndex( const edict_t *pentLookup ) -{ -#if !defined( CLIENT_DLL ) - if ( pentLookup == NULL ) - return -1; - return EntityIndex( CBaseEntity::Instance(pentLookup) ); -#else - Assert( !"CSave::EntityIndex( edict_t * ) not valid on client!" ); - return -1; -#endif -} - - -//------------------------------------- - -int CSave::EntityIndex( const CBaseEntity *pEntity ) -{ - if ( !m_pGameInfo || pEntity == NULL ) - return -1; - - int i; - entitytable_t *pTable; - - for ( i = 0; i < m_pGameInfo->NumEntities(); i++ ) - { - pTable = m_pGameInfo->GetEntityInfo( i ); - if ( pTable->hEnt == pEntity ) - return pTable->id; - } - return -1; -} - -//------------------------------------- - -int CSave::EntityFlagsSet( int entityIndex, int flags ) -{ - if ( !m_pGameInfo || entityIndex < 0 ) - return 0; - if ( entityIndex > m_pGameInfo->NumEntities() ) - return 0; - - m_pGameInfo->GetEntityInfo( entityIndex )->flags |= flags; - - return m_pGameInfo->GetEntityInfo( entityIndex )->flags; -} - -//------------------------------------- - -void CSave::WriteTime( const char *pname, const float *data, int count ) -{ - int i; - float tmp; - - WriteHeader( pname, sizeof(float) * count ); - for ( i = 0; i < count; i++ ) - { - // Always encode time as a delta from the current time so it can be re-based if loaded in a new level - // Times of 0 are never written to the file, so they will be restored as 0, not a relative time - Assert( data[i] != ZERO_TIME ); - - if ( data[i] == 0.0 ) - { - tmp = ZERO_TIME; - } - else if ( data[i] == INVALID_TIME || data[i] == FLT_MAX ) - { - tmp = data[i]; - } - else - { - tmp = data[i] - m_pGameInfo->GetBaseTime(); - if ( fabsf( tmp ) < 0.001 ) // never allow a time to become zero due to rebasing - tmp = 0.001; - } - - WriteData( (const char *)&tmp, sizeof(float) ); - } -} - -//------------------------------------- - -void CSave::WriteTime( const float *data, int count ) -{ - int i; - float tmp; - - for ( i = 0; i < count; i++ ) - { - // Always encode time as a delta from the current time so it can be re-based if loaded in a new level - // Times of 0 are never written to the file, so they will be restored as 0, not a relative time - if ( data[i] == 0.0 ) - { - tmp = ZERO_TIME; - } - else if ( data[i] == INVALID_TIME || data[i] == FLT_MAX ) - { - tmp = data[i]; - } - else - { - tmp = data[i] - m_pGameInfo->GetBaseTime(); - if ( fabsf( tmp ) < 0.001 ) // never allow a time to become zero due to rebasing - tmp = 0.001; - } - - WriteData( (const char *)&tmp, sizeof(float) ); - } -} - -void CSave::WriteTick( const char *pname, const int *data, int count ) -{ - WriteHeader( pname, sizeof(int) * count ); - WriteTick( data, count ); -} - -//------------------------------------- - -void CSave::WriteTick( const int *data, int count ) -{ - int i; - int tmp; - - int baseTick = TIME_TO_TICKS( m_pGameInfo->GetBaseTime() ); - - for ( i = 0; i < count; i++ ) - { - // Always encode time as a delta from the current time so it can be re-based if loaded in a new level - // Times of 0 are never written to the file, so they will be restored as 0, not a relative time - tmp = data[ i ]; - if ( data[ i ] == TICK_NEVER_THINK ) - { - tmp = TICK_NEVER_THINK_ENCODE; - } - else - { - // Rebase it... - tmp -= baseTick; - } - WriteData( (const char *)&tmp, sizeof(int) ); - } -} -//------------------------------------- - -void CSave::WritePositionVector( const char *pname, const Vector &value ) -{ - Vector tmp = value; - - if ( tmp != vec3_invalid ) - tmp -= m_pGameInfo->GetLandmark(); - - WriteVector( pname, tmp ); -} - -//------------------------------------- - -void CSave::WritePositionVector( const Vector &value ) -{ - Vector tmp = value; - - if ( tmp != vec3_invalid ) - tmp -= m_pGameInfo->GetLandmark(); - - WriteVector( tmp ); -} - -//------------------------------------- - -void CSave::WritePositionVector( const char *pname, const Vector *value, int count ) -{ - WriteHeader( pname, sizeof(Vector) * count ); - WritePositionVector( value, count ); -} - -//------------------------------------- - -void CSave::WritePositionVector( const Vector *value, int count ) -{ - int i; - Vector tmp; - for ( i = 0; i < count; i++ ) - { - Vector tmp = value[i]; - - if ( tmp != vec3_invalid ) - tmp -= m_pGameInfo->GetLandmark(); - - WriteData( (const char *)&tmp.x, sizeof(Vector) ); - } -} - -//------------------------------------- - -void CSave::WriteFunction( datamap_t *pRootMap, const char *pname, const int *data, int count ) -{ - AssertMsg( count == 1, "Arrays of functions not presently supported" ); - const char *functionName = UTIL_FunctionToName( pRootMap, (void *)(*data) ); - - if ( functionName ) - { - BufferField( pname, strlen(functionName) + 1, functionName ); - } - else - { - Warning( "Invalid function pointer in entity!\n" ); - Assert(0); - } -} - -//------------------------------------- - -void CSave::WriteEntityPtr( const char *pname, CBaseEntity **ppEntity, int count ) -{ - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) - { - entityArray[i] = EntityIndex( ppEntity[i] ); - } - WriteInt( pname, entityArray, count ); -} - -//------------------------------------- - -void CSave::WriteEntityPtr( CBaseEntity **ppEntity, int count ) -{ - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) - { - entityArray[i] = EntityIndex( ppEntity[i] ); - } - WriteInt( entityArray, count ); -} - -//------------------------------------- - -void CSave::WriteEdictPtr( const char *pname, edict_t **ppEdict, int count ) -{ - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) - { - entityArray[i] = EntityIndex( ppEdict[i] ); - } - WriteInt( pname, entityArray, count ); -} - -//------------------------------------- - -void CSave::WriteEdictPtr( edict_t **ppEdict, int count ) -{ - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) - { - entityArray[i] = EntityIndex( ppEdict[i] ); - } - WriteInt( entityArray, count ); -} - -//------------------------------------- - -void CSave::WriteEHandle( const char *pname, const EHANDLE *pEHandle, int count ) -{ - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) - { - entityArray[i] = EntityIndex( (CBaseEntity *)(const_cast(pEHandle)[i]) ); - } - WriteInt( pname, entityArray, count ); -} - -//------------------------------------- - -void CSave::WriteEHandle( const EHANDLE *pEHandle, int count ) -{ - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) - { - entityArray[i] = EntityIndex( (CBaseEntity *)(const_cast(pEHandle)[i]) ); - } - WriteInt( entityArray, count ); -} - -//------------------------------------- -// Purpose: Writes all the fields that are not client neutral. In the event of -// a librarization of save/restore, these would not reside in the library - -bool CSave::WriteGameField( const char *pname, void *pData, datamap_t *pRootMap, typedescription_t *pField ) -{ - switch( pField->fieldType ) - { - case FIELD_CLASSPTR: - WriteEntityPtr( pField->fieldName, (CBaseEntity **)pData, pField->fieldSize ); - break; - - case FIELD_EDICT: - WriteEdictPtr( pField->fieldName, (edict_t **)pData, pField->fieldSize ); - break; - - case FIELD_EHANDLE: - WriteEHandle( pField->fieldName, (EHANDLE *)pData, pField->fieldSize ); - break; - - case FIELD_POSITION_VECTOR: - WritePositionVector( pField->fieldName, (Vector *)pData, pField->fieldSize ); - break; - - case FIELD_TIME: - WriteTime( pField->fieldName, (float *)pData, pField->fieldSize ); - break; - - case FIELD_TICK: - WriteTick( pField->fieldName, (int *)pData, pField->fieldSize ); - break; - - case FIELD_MODELINDEX: - { - int nModelIndex = *(int*)pData; - string_t strModelName = NULL_STRING; - const model_t *pModel = modelinfo->GetModel( nModelIndex ); - if ( pModel ) - { - strModelName = AllocPooledString( modelinfo->GetModelName( pModel ) ); - } - WriteString( pField->fieldName, (string_t *)&strModelName, pField->fieldSize ); - } - break; - - case FIELD_MATERIALINDEX: - { - int nMateralIndex = *(int*)pData; - string_t strMaterialName = NULL_STRING; - const char *pMaterialName = GetMaterialNameFromIndex( nMateralIndex ); - if ( pMaterialName ) - { - strMaterialName = MAKE_STRING( pMaterialName ); - } - WriteString( pField->fieldName, (string_t *)&strMaterialName, pField->fieldSize ); - } - break; - - case FIELD_MODELNAME: - case FIELD_SOUNDNAME: - WriteString( pField->fieldName, (string_t *)pData, pField->fieldSize ); - break; - - // For now, just write the address out, we're not going to change memory while doing this yet! - case FIELD_FUNCTION: - WriteFunction( pRootMap, pField->fieldName, (int *)(char *)pData, pField->fieldSize ); - break; - - case FIELD_VMATRIX: - WriteVMatrix( pField->fieldName, (VMatrix *)pData, pField->fieldSize ); - break; - case FIELD_VMATRIX_WORLDSPACE: - WriteVMatrixWorldspace( pField->fieldName, (VMatrix *)pData, pField->fieldSize ); - break; - - case FIELD_MATRIX3X4_WORLDSPACE: - WriteMatrix3x4Worldspace( pField->fieldName, (const matrix3x4_t *)pData, pField->fieldSize ); - break; - - case FIELD_INTERVAL: - WriteInterval( pField->fieldName, (interval_t *)pData, pField->fieldSize ); - break; - - default: - Warning( "Bad field type\n" ); - Assert(0); - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// -// CRestore -// -//----------------------------------------------------------------------------- - -CRestore::CRestore( CSaveRestoreData *pdata ) - : m_pData( pdata ), - m_pGameInfo( pdata ), - m_global( 0 ), - m_precache( true ) -{ - m_BlockEndStack.EnsureCapacity( 32 ); -} - -//------------------------------------- - -int CRestore::GetReadPos() const -{ - return m_pData->GetCurPos(); -} - -//------------------------------------- - -void CRestore::SetReadPos( int pos ) -{ - m_pData->Seek(pos); -} - -//------------------------------------- - -const char *CRestore::StringFromHeaderSymbol( int symbol ) -{ - const char *pszResult = m_pData->StringFromSymbol( symbol ); - return ( pszResult ) ? pszResult : ""; -} - -//------------------------------------- -// Purpose: Reads all the fields that are client neutral. In the event of -// a librarization of save/restore, these would reside in the library - -void CRestore::ReadBasicField( const SaveRestoreRecordHeader_t &header, void *pDest, datamap_t *pRootMap, typedescription_t *pField ) -{ - switch( pField->fieldType ) - { - case FIELD_FLOAT: - { - ReadFloat( (float *)pDest, pField->fieldSize, header.size ); - break; - } - case FIELD_STRING: - { - ReadString( (string_t *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_VECTOR: - { - ReadVector( (Vector *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_QUATERNION: - { - ReadQuaternion( (Quaternion *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_INTEGER: - { - ReadInt( (int *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_BOOLEAN: - { - ReadBool( (bool *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_SHORT: - { - ReadShort( (short *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_CHARACTER: - { - ReadData( (char *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_COLOR32: - { - COMPILE_TIME_ASSERT( sizeof(color32) == sizeof(int) ); - ReadInt( (int *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_EMBEDDED: - { - AssertMsg( (( pField->flags & FTYPEDESC_PTR ) == 0) || (pField->fieldSize == 1), "Arrays of embedded pointer types presently unsupported by save/restore" ); -#ifdef _DEBUG - int startPos = GetReadPos(); -#endif - if ( !(pField->flags & FTYPEDESC_PTR) || *((void **)pDest) ) - { - int nFieldCount = pField->fieldSize; - char *pFieldData = (char *)( ( !(pField->flags & FTYPEDESC_PTR) ) ? pDest : *((void **)pDest) ); - while ( --nFieldCount >= 0 ) - { - // No corresponding "block" (see write) as it was used as the header of the field - ReadAll( pFieldData, pField->td ); - pFieldData += pField->fieldSizeInBytes; - } - Assert( GetReadPos() - startPos == header.size ); - } - else - { - SetReadPos( GetReadPos() + header.size ); - Warning( "Attempted to restore FIELD_EMBEDDEDBYREF %s but there is no destination memory\n", pField->fieldName ); - } - break; - - } - case FIELD_CUSTOM: - { - // No corresponding "block" (see write) as it was used as the header of the field - int posNextField = GetReadPos() + header.size; - - SaveRestoreFieldInfo_t fieldInfo = - { - pDest, - ((char *)pDest) - pField->fieldOffset[ TD_OFFSET_NORMAL ], - pField - }; - - pField->pSaveRestoreOps->Restore( fieldInfo, this ); - - Assert( posNextField >= GetReadPos() ); - SetReadPos( posNextField ); - break; - } - - default: - Warning( "Bad field type\n" ); - Assert(0); - } -} - -//------------------------------------- - -void CRestore::ReadField( const SaveRestoreRecordHeader_t &header, void *pDest, datamap_t *pRootMap, typedescription_t *pField ) -{ - if ( pField->fieldType <= FIELD_CUSTOM ) - ReadBasicField( header, pDest, pRootMap, pField ); - else - ReadGameField( header, pDest, pRootMap, pField ); -} - -//------------------------------------- - -bool CRestore::ShouldReadField( typedescription_t *pField ) -{ - if ( (pField->flags & FTYPEDESC_SAVE) == 0 ) - return false; - - if ( m_global && (pField->flags & FTYPEDESC_GLOBAL) ) - return false; - - return true; -} - -//------------------------------------- - -typedescription_t *CRestore::FindField( const char *pszFieldName, typedescription_t *pFields, int fieldCount, int *pCookie ) -{ - int &fieldNumber = *pCookie; - if ( pszFieldName ) - { - typedescription_t *pTest; - - for ( int i = 0; i < fieldCount; i++ ) - { - pTest = &pFields[fieldNumber]; - - ++fieldNumber; - if ( fieldNumber == fieldCount ) - fieldNumber = 0; - - if ( stricmp( pTest->fieldName, pszFieldName ) == 0 ) - return pTest; - } - } - - fieldNumber = 0; - return NULL; -} - -//------------------------------------- - -bool CRestore::ShouldEmptyField( typedescription_t *pField ) -{ - // don't clear out fields that don't get saved, or that are handled specially - if ( !( pField->flags & FTYPEDESC_SAVE ) ) - return false; - - // Don't clear global fields - if ( m_global && (pField->flags & FTYPEDESC_GLOBAL) ) - return false; - - return true; -} - -//------------------------------------- - -void CRestore::EmptyFields( void *pBaseData, typedescription_t *pFields, int fieldCount ) -{ - int i; - for ( i = 0; i < fieldCount; i++ ) - { - typedescription_t *pField = &pFields[i]; - if ( !ShouldEmptyField( pField ) ) - continue; - - void *pFieldData = (char *)pBaseData + pField->fieldOffset[ TD_OFFSET_NORMAL ]; - switch( pField->fieldType ) - { - case FIELD_CUSTOM: - { - SaveRestoreFieldInfo_t fieldInfo = - { - pFieldData, - pBaseData, - pField - }; - pField->pSaveRestoreOps->MakeEmpty( fieldInfo ); - } - break; - - case FIELD_EMBEDDED: - { - if ( (pField->flags & FTYPEDESC_PTR) && !*((void **)pFieldData) ) - break; - - int nFieldCount = pField->fieldSize; - char *pFieldMemory = (char *)( ( !(pField->flags & FTYPEDESC_PTR) ) ? pFieldData : *((void **)pFieldData) ); - while ( --nFieldCount >= 0 ) - { - EmptyFields( pFieldMemory, pField->td->dataDesc, pField->td->dataNumFields ); - pFieldMemory += pField->fieldSizeInBytes; - } - } - break; - - default: - // NOTE: If you hit this assertion, you've got a bug where you're using - // the wrong field type for your field - if ( pField->fieldSizeInBytes != pField->fieldSize * gSizes[pField->fieldType] ) - { - Warning("WARNING! Field %s is using the wrong FIELD_ type!\nFix this or you'll see a crash.\n", pField->fieldName ); - Assert( 0 ); - } - memset( pFieldData, (pField->fieldType != FIELD_EHANDLE) ? 0 : 0xFF, pField->fieldSize * gSizes[pField->fieldType] ); - break; - } - } -} - -//------------------------------------- - -void CRestore::StartBlock( SaveRestoreRecordHeader_t *pHeader ) -{ - ReadHeader( pHeader ); - m_BlockEndStack.AddToTail( GetReadPos() + pHeader->size ); -} - -//------------------------------------- - -void CRestore::StartBlock( char szBlockName[] ) -{ - SaveRestoreRecordHeader_t header; - StartBlock( &header ); - Q_strncpy( szBlockName, StringFromHeaderSymbol( header.symbol ), SIZE_BLOCK_NAME_BUF ); -} - -//------------------------------------- - -void CRestore::StartBlock() -{ - char szBlockName[SIZE_BLOCK_NAME_BUF]; - StartBlock( szBlockName ); -} - -//------------------------------------- - -void CRestore::EndBlock() -{ - int endPos = m_BlockEndStack[ m_BlockEndStack.Count() - 1 ]; - m_BlockEndStack.Remove( m_BlockEndStack.Count() - 1 ); - SetReadPos( endPos ); -} - -//------------------------------------- - -int CRestore::ReadFields( const char *pname, void *pBaseData, datamap_t *pRootMap, typedescription_t *pFields, int fieldCount ) -{ - static int lastName = -1; - Verify( ReadShort() == sizeof(int) ); // First entry should be an int - int symName = m_pData->FindCreateSymbol(pname); - - // Check the struct name - int curSym = ReadShort(); - if ( curSym != symName ) // Field Set marker - { - const char *pLastName = m_pData->StringFromSymbol( lastName ); - const char *pCurName = m_pData->StringFromSymbol( curSym ); - Msg( "Expected %s found %s ( raw '%s' )! (prev: %s)\n", pname, pCurName, BufferPointer(), pLastName ); - Msg( "Field type name may have changed or inheritance graph changed, save file is suspect\n" ); - m_pData->Rewind( 2*sizeof(short) ); - return 0; - } - lastName = symName; - - // Clear out base data - EmptyFields( pBaseData, pFields, fieldCount ); - - // Skip over the struct name - int i; - int nFieldsSaved = ReadInt(); // Read field count - int searchCookie = 0; // Make searches faster, most data is read/written in the same order - SaveRestoreRecordHeader_t header; - - for ( i = 0; i < nFieldsSaved; i++ ) - { - ReadHeader( &header ); - - typedescription_t *pField = FindField( m_pData->StringFromSymbol( header.symbol ), pFields, fieldCount, &searchCookie); - if ( pField && ShouldReadField( pField ) ) - { - ReadField( header, ((char *)pBaseData + pField->fieldOffset[ TD_OFFSET_NORMAL ]), pRootMap, pField ); - } - else - { - BufferSkipBytes( header.size ); // Advance to next field - } - } - - return 1; -} - -//------------------------------------- - -void CRestore::ReadHeader( SaveRestoreRecordHeader_t *pheader ) -{ - if ( pheader != NULL ) - { - Assert( pheader!=NULL ); - pheader->size = ReadShort(); // Read field size - pheader->symbol = ReadShort(); // Read field name token - } - else - { - BufferSkipBytes( sizeof(short) * 2 ); - } -} - -//------------------------------------- - -short CRestore::ReadShort( void ) -{ - short tmp = 0; - - BufferReadBytes( (char *)&tmp, sizeof(short) ); - - return tmp; -} - -//------------------------------------- - -int CRestore::ReadInt( void ) -{ - int tmp = 0; - - BufferReadBytes( (char *)&tmp, sizeof(int) ); - - return tmp; -} - -//------------------------------------- -// Purpose: Recursively restores all the classes in an object, in reverse order (top down) -// Output : int 0 on failure, 1 on success - -int CRestore::DoReadAll( void *pLeafObject, datamap_t *pLeafMap, datamap_t *pCurMap ) -{ - // restore base classes first - if ( pCurMap->baseMap ) - { - int status = DoReadAll( pLeafObject, pLeafMap, pCurMap->baseMap ); - if ( !status ) - return status; - } - - return ReadFields( pCurMap->dataClassName, pLeafObject, pLeafMap, pCurMap->dataDesc, pCurMap->dataNumFields ); -} - -//------------------------------------- - -char *CRestore::BufferPointer( void ) -{ - if ( !m_pData ) - return NULL; - - return m_pData->AccessCurPos(); -} - -//------------------------------------- - -void CRestore::BufferReadBytes( char *pOutput, int size ) -{ - Assert( m_pData !=NULL ); - - if ( !m_pData || m_pData->BytesAvailable() == 0 ) - return; - - if ( !m_pData->Read( pOutput, size ) ) - { - Warning( "Restore underflow!\n" ); - Assert(0); - } -} - -//------------------------------------- - -void CRestore::BufferSkipBytes( int bytes ) -{ - BufferReadBytes( NULL, bytes ); -} - -//------------------------------------- - -int CRestore::ReadShort( short *pValue, int nElems, int nBytesAvailable ) -{ - return ReadSimple( pValue, nElems, nBytesAvailable ); -} - -//------------------------------------- - -int CRestore::ReadInt( int *pValue, int nElems, int nBytesAvailable ) -{ - return ReadSimple( pValue, nElems, nBytesAvailable ); -} - -//------------------------------------- - -int CRestore::ReadBool( bool *pValue, int nElems, int nBytesAvailable ) -{ - COMPILE_TIME_ASSERT( sizeof(bool) == sizeof(char) ); - return ReadSimple( pValue, nElems, nBytesAvailable ); -} - -//------------------------------------- - -int CRestore::ReadFloat( float *pValue, int nElems, int nBytesAvailable ) -{ - return ReadSimple( pValue, nElems, nBytesAvailable ); -} - -//------------------------------------- - -int CRestore::ReadData( char *pData, int size, int nBytesAvailable ) -{ - return ReadSimple( pData, size, nBytesAvailable ); -} - -//------------------------------------- - -void CRestore::ReadString( char *pDest, int nSizeDest, int nBytesAvailable ) -{ - const char *pString = BufferPointer(); - if ( !nBytesAvailable ) - nBytesAvailable = strlen( pString ) + 1; - BufferSkipBytes( nBytesAvailable ); - - Q_strncpy(pDest, pString, nSizeDest ); -} - -//------------------------------------- - -int CRestore::ReadString( string_t *pValue, int nElems, int nBytesAvailable ) -{ - AssertMsg( nBytesAvailable > 0, "CRestore::ReadString() implementation does not currently support unspecified bytes available"); - - int i; - char *pString = BufferPointer(); - char *pLimit = pString + nBytesAvailable; - for ( i = 0; i < nElems && pString < pLimit; i++ ) - { - if ( *((char *)pString) == 0 ) - pValue[i] = NULL_STRING; - else - pValue[i] = AllocPooledString( (char *)pString ); - - while (*pString) - pString++; - pString++; - } - - BufferSkipBytes( nBytesAvailable ); - - return i; -} - -//------------------------------------- - -int CRestore::ReadVector( Vector *pValue) -{ - BufferReadBytes( (char *)pValue, sizeof(Vector) ); - return 1; -} - -//------------------------------------- - -int CRestore::ReadVector( Vector *pValue, int nElems, int nBytesAvailable ) -{ - return ReadSimple( pValue, nElems, nBytesAvailable ); -} - -int CRestore::ReadQuaternion( Quaternion *pValue) -{ - BufferReadBytes( (char *)pValue, sizeof(Quaternion) ); - return 1; -} - -//------------------------------------- - -int CRestore::ReadQuaternion( Quaternion *pValue, int nElems, int nBytesAvailable ) -{ - return ReadSimple( pValue, nElems, nBytesAvailable ); -} - -//------------------------------------- -int CRestore::ReadVMatrix( VMatrix *pValue, int nElems, int nBytesAvailable ) -{ - return ReadSimple( pValue, nElems, nBytesAvailable ); -} - - -int CRestore::ReadVMatrixWorldspace( VMatrix *pValue, int nElems, int nBytesAvailable ) -{ - Vector basePosition = m_pGameInfo->GetLandmark(); - VMatrix tmp; - - for ( int i = 0; i < nElems; i++ ) - { - BufferReadBytes( (char *)&tmp, sizeof(float)*16 ); - - VMatrixOffset( pValue[i], tmp, basePosition ); - } - return nElems; -} - - -int CRestore::ReadMatrix3x4Worldspace( matrix3x4_t *pValue, int nElems, int nBytesAvailable ) -{ - Vector basePosition = m_pGameInfo->GetLandmark(); - matrix3x4_t tmp; - - for ( int i = 0; i < nElems; i++ ) - { - BufferReadBytes( (char *)&tmp, sizeof(matrix3x4_t) ); - - Matrix3x4Offset( pValue[i], tmp, basePosition ); - } - return nElems; -} - -int CRestore::ReadInterval( interval_t *interval, int count, int nBytesAvailable ) -{ - return ReadSimple( interval, count, nBytesAvailable ); -} - -//--------------------------------------------------------- -// -// Game centric restore methods -// - -CBaseEntity *CRestore::EntityFromIndex( int entityIndex ) -{ - if ( !m_pGameInfo || entityIndex < 0 ) - return NULL; - - int i; - entitytable_t *pTable; - - for ( i = 0; i < m_pGameInfo->NumEntities(); i++ ) - { - pTable = m_pGameInfo->GetEntityInfo( i ); - if ( pTable->id == entityIndex ) - return pTable->hEnt; - } - return NULL; -} - -//------------------------------------- - -int CRestore::ReadEntityPtr( CBaseEntity **ppEntity, int count, int nBytesAvailable ) -{ - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - - int nRead = ReadInt( entityArray, count, nBytesAvailable ); - - for ( int i = 0; i < nRead; i++ ) // nRead is never greater than count - { - ppEntity[i] = EntityFromIndex( entityArray[i] ); - } - - if ( nRead < count) - { - memset( &ppEntity[nRead], 0, ( count - nRead ) * sizeof(ppEntity[0]) ); - } - - return nRead; -} - -//------------------------------------- -int CRestore::ReadEdictPtr( edict_t **ppEdict, int count, int nBytesAvailable ) -{ -#if !defined( CLIENT_DLL ) - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - CBaseEntity *pEntity; - - int nRead = ReadInt( entityArray, count, nBytesAvailable ); - - for ( int i = 0; i < nRead; i++ ) // nRead is never greater than count - { - pEntity = EntityFromIndex( entityArray[i] ); - ppEdict[i] = (pEntity) ? pEntity->edict() : NULL; - } - - if ( nRead < count) - { - memset( &ppEdict[nRead], 0, ( count - nRead ) * sizeof(ppEdict[0]) ); - } - - return nRead; -#else - return 0; -#endif -} - - -//------------------------------------- - -int CRestore::ReadEHandle( EHANDLE *pEHandle, int count, int nBytesAvailable ) -{ - AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); - int entityArray[MAX_ENTITYARRAY]; - - int nRead = ReadInt( entityArray, count, nBytesAvailable ); - - for ( int i = 0; i < nRead; i++ ) // nRead is never greater than count - { - pEHandle[i] = EntityFromIndex( entityArray[i] ); - } - - if ( nRead < count) - { - memset( &pEHandle[nRead], 0xFF, ( count - nRead ) * sizeof(pEHandle[0]) ); - } - - return nRead; -} - -//------------------------------------- -// Purpose: Reads all the fields that are not client neutral. In the event of -// a librarization of save/restore, these would NOT reside in the library - -void CRestore::ReadGameField( const SaveRestoreRecordHeader_t &header, void *pDest, datamap_t *pRootMap, typedescription_t *pField ) -{ - switch( pField->fieldType ) - { - case FIELD_POSITION_VECTOR: - { - ReadPositionVector( (Vector *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_TIME: - { - ReadTime( (float *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_TICK: - { - ReadTick( (int *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_FUNCTION: - { - ReadFunction( pRootMap, (void **)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_MODELINDEX: - { - int *pModelIndex = (int*)pDest; - string_t *pModelName = (string_t *)stackalloc( pField->fieldSize * sizeof(string_t) ); - int nRead = ReadString( pModelName, pField->fieldSize, header.size ); - - for ( int i = 0; i < nRead; i++ ) - { - if ( pModelName[i] == NULL_STRING ) - { - pModelIndex[i] = -1; - continue; - } - - pModelIndex[i] = modelinfo->GetModelIndex( STRING( pModelName[i] ) ); - -#if !defined( CLIENT_DLL ) - if ( m_precache ) - { - CBaseEntity::PrecacheModel( STRING( pModelName[i] ) ); - } -#endif - } - break; - } - - case FIELD_MATERIALINDEX: - { - int *pMaterialIndex = (int*)pDest; - string_t *pMaterialName = (string_t *)stackalloc( pField->fieldSize * sizeof(string_t) ); - int nRead = ReadString( pMaterialName, pField->fieldSize, header.size ); - - for ( int i = 0; i < nRead; i++ ) - { - if ( pMaterialName[i] == NULL_STRING ) - { - pMaterialIndex[i] = 0; - continue; - } - - pMaterialIndex[i] = GetMaterialIndex( STRING( pMaterialName[i] ) ); - -#if !defined( CLIENT_DLL ) - if ( m_precache ) - { - PrecacheMaterial( STRING( pMaterialName[i] ) ); - } -#endif - } - break; - } - - case FIELD_MODELNAME: - case FIELD_SOUNDNAME: - { - string_t *pStringDest = (string_t *)pDest; - int nRead = ReadString( pStringDest, pField->fieldSize, header.size ); - if ( m_precache ) - { - for ( int i = 0; i < nRead; i++ ) - { - if ( pStringDest[i] != NULL_STRING ) - { -#if !defined( CLIENT_DLL ) - if ( pField->fieldType == FIELD_MODELNAME ) - { - CBaseEntity::PrecacheModel( STRING( pStringDest[i] ) ); - } - else if ( pField->fieldType == FIELD_SOUNDNAME ) - { - CBaseEntity::PrecacheScriptSound( STRING( pStringDest[i] ) ); - } -#endif - } - } - } - break; - } - - case FIELD_CLASSPTR: - ReadEntityPtr( (CBaseEntity **)pDest, pField->fieldSize, header.size ); - break; - - case FIELD_EDICT: -#if !defined( CLIENT_DLL ) - ReadEdictPtr( (edict_t **)pDest, pField->fieldSize, header.size ); -#else - Assert( !"FIELD_EDICT not valid for client .dll" ); -#endif - break; - case FIELD_EHANDLE: - ReadEHandle( (EHANDLE *)pDest, pField->fieldSize, header.size ); - break; - - case FIELD_VMATRIX: - { - ReadVMatrix( (VMatrix *)pDest, pField->fieldSize, header.size ); - break; - } - - case FIELD_VMATRIX_WORLDSPACE: - ReadVMatrixWorldspace( (VMatrix *)pDest, pField->fieldSize, header.size ); - break; - - case FIELD_MATRIX3X4_WORLDSPACE: - ReadMatrix3x4Worldspace( (matrix3x4_t *)pDest, pField->fieldSize, header.size ); - break; - - case FIELD_INTERVAL: - ReadInterval( (interval_t *)pDest, pField->fieldSize, header.size ); - break; - - default: - Warning( "Bad field type\n" ); - Assert(0); - } -} - -//------------------------------------- - -int CRestore::ReadTime( float *pValue, int count, int nBytesAvailable ) -{ - float baseTime = m_pGameInfo->GetBaseTime(); - int nRead = ReadFloat( pValue, count, nBytesAvailable ); - - for ( int i = nRead - 1; i >= 0; i-- ) - { - if ( pValue[i] == ZERO_TIME ) - pValue[i] = 0.0; - else if ( pValue[i] != INVALID_TIME && pValue[i] != FLT_MAX ) - pValue[i] += baseTime; - } - - return nRead; -} - -int CRestore::ReadTick( int *pValue, int count, int nBytesAvailable ) -{ - // HACK HACK: Adding 0.1f here makes sure that all tick times read - // from .sav file which are near the basetime will end up just ahead of - // the base time, because we are restoring we'll have a slow frame of the - // max frametime of 0.1 seconds and that could otherwise cause all of our - // think times to get synchronized to each other... sigh. ywb... - int baseTick = TIME_TO_TICKS( m_pGameInfo->GetBaseTime() + 0.1f ); - int nRead = ReadInt( pValue, count, nBytesAvailable ); - - for ( int i = nRead - 1; i >= 0; i-- ) - { - if ( pValue[ i ] != TICK_NEVER_THINK_ENCODE ) - { - // Rebase it - pValue[i] += baseTick; - } - else - { - // Slam to -1 value - pValue[ i ] = TICK_NEVER_THINK; - } - } - - return nRead; -} - -//------------------------------------- - -int CRestore::ReadPositionVector( Vector *pValue ) -{ - return ReadPositionVector( pValue, 1, sizeof(Vector) ); -} - -//------------------------------------- - -int CRestore::ReadPositionVector( Vector *pValue, int count, int nBytesAvailable ) -{ - Vector basePosition = m_pGameInfo->GetLandmark(); - int nRead = ReadVector( pValue, count, nBytesAvailable ); - - for ( int i = nRead - 1; i >= 0; i-- ) - { - if ( pValue[i] != vec3_invalid ) - pValue[i] += basePosition; - } - - return nRead; -} - -//------------------------------------- - -int CRestore::ReadFunction( datamap_t *pMap, void **pValue, int count, int nBytesAvailable ) -{ - AssertMsg( nBytesAvailable > 0, "CRestore::ReadFunction() implementation does not currently support unspecified bytes available"); - - char *pszFunctionName = BufferPointer(); - BufferSkipBytes( nBytesAvailable ); - - AssertMsg( count == 1, "Arrays of functions not presently supported" ); - - if ( *pszFunctionName == 0 ) - *pValue = NULL; - else - *pValue = UTIL_FunctionFromName( pMap, pszFunctionName ); - - return 0; -} - -//----------------------------------------------------------------------------- -// -// Entity data saving routines -// -//----------------------------------------------------------------------------- - -BEGIN_SIMPLE_DATADESC(entitytable_t) - DEFINE_FIELD( id, FIELD_INTEGER ), - DEFINE_FIELD( edictindex, FIELD_INTEGER ), - DEFINE_FIELD( saveentityindex, FIELD_INTEGER ), -// DEFINE_FIELD( restoreentityindex, FIELD_INTEGER ), - // hEnt (not saved, this is the fixup) - DEFINE_FIELD( location, FIELD_INTEGER ), - DEFINE_FIELD( size, FIELD_INTEGER ), - DEFINE_FIELD( flags, FIELD_INTEGER ), - DEFINE_FIELD( classname, FIELD_STRING ), - DEFINE_FIELD( globalname, FIELD_STRING ), - DEFINE_FIELD( landmarkModelSpace, FIELD_VECTOR ), - DEFINE_FIELD( modelname, FIELD_STRING ), -END_DATADESC() - - -//----------------------------------------------------------------------------- -// Utilities entities can use when saving -//----------------------------------------------------------------------------- -class CEntitySaveUtils : public IEntitySaveUtils -{ -public: - // Call these in pre-save + post save - void PreSave(); - void PostSave(); - - // Methods of IEntitySaveUtils - virtual void AddLevelTransitionSaveDependency( CBaseEntity *pEntity1, CBaseEntity *pEntity2 ); - virtual int GetEntityDependencyCount( CBaseEntity *pEntity ); - virtual int GetEntityDependencies( CBaseEntity *pEntity, int nCount, CBaseEntity **ppEntList ); - -private: - IPhysicsObjectPairHash *m_pLevelAdjacencyDependencyHash; -}; - - -//----------------------------------------------------------------------------- -// Call these in pre-save + post save -//----------------------------------------------------------------------------- -void CEntitySaveUtils::PreSave() -{ - Assert( !m_pLevelAdjacencyDependencyHash ); - MEM_ALLOC_CREDIT(); - m_pLevelAdjacencyDependencyHash = physics->CreateObjectPairHash(); -} - -void CEntitySaveUtils::PostSave() -{ - physics->DestroyObjectPairHash( m_pLevelAdjacencyDependencyHash ); - m_pLevelAdjacencyDependencyHash = NULL; -} - - -//----------------------------------------------------------------------------- -// Gets the # of dependencies for a particular entity -//----------------------------------------------------------------------------- -int CEntitySaveUtils::GetEntityDependencyCount( CBaseEntity *pEntity ) -{ - return m_pLevelAdjacencyDependencyHash->GetPairCountForObject( pEntity ); -} - - -//----------------------------------------------------------------------------- -// Gets all dependencies for a particular entity -//----------------------------------------------------------------------------- -int CEntitySaveUtils::GetEntityDependencies( CBaseEntity *pEntity, int nCount, CBaseEntity **ppEntList ) -{ - return m_pLevelAdjacencyDependencyHash->GetPairListForObject( pEntity, nCount, (void**)ppEntList ); -} - - -//----------------------------------------------------------------------------- -// Methods of IEntitySaveUtils -//----------------------------------------------------------------------------- -void CEntitySaveUtils::AddLevelTransitionSaveDependency( CBaseEntity *pEntity1, CBaseEntity *pEntity2 ) -{ - if ( pEntity1 != pEntity2 ) - { - m_pLevelAdjacencyDependencyHash->AddObjectPair( pEntity1, pEntity2 ); - } -} - - -//----------------------------------------------------------------------------- -// Block handler for save/restore of entities -//----------------------------------------------------------------------------- -class CEntitySaveRestoreBlockHandler : public ISaveRestoreBlockHandler -{ -public: - const char *GetBlockName(); - void PreSave( CSaveRestoreData *pSaveData ); - void Save( ISave *pSave ); - void WriteSaveHeaders( ISave *pSave ); - virtual void PostSave(); - virtual void PreRestore(); - void ReadRestoreHeaders( IRestore *pRestore ); - - void Restore( IRestore *pRestore, bool createPlayers ); - virtual void PostRestore(); - - inline IEntitySaveUtils * GetEntitySaveUtils() { return &m_EntitySaveUtils; } - -private: - friend int CreateEntityTransitionList( CSaveRestoreData *pSaveData, int levelMask ); - bool SaveInitEntities( CSaveRestoreData *pSaveData ); - bool DoRestoreEntity( CBaseEntity *pEntity, IRestore *pRestore ); - Vector ModelSpaceLandmark( int modelIndex ); - int RestoreEntity( CBaseEntity *pEntity, IRestore *pRestore, entitytable_t *pEntInfo ); - -#if !defined( CLIENT_DLL ) - // Find the matching global entity. Spit out an error if the designer made entities of - // different classes with the same global name - CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ); - - int RestoreGlobalEntity( CBaseEntity *pEntity, CSaveRestoreData *pSaveData, entitytable_t *pEntInfo ); -#endif - -private: - CEntitySaveUtils m_EntitySaveUtils; -}; - - -//----------------------------------------------------------------------------- - -CEntitySaveRestoreBlockHandler g_EntitySaveRestoreBlockHandler; - -//------------------------------------- - -ISaveRestoreBlockHandler *GetEntitySaveRestoreBlockHandler() -{ - return &g_EntitySaveRestoreBlockHandler; -} - -IEntitySaveUtils *GetEntitySaveUtils() -{ - return g_EntitySaveRestoreBlockHandler.GetEntitySaveUtils(); -} - - -//----------------------------------------------------------------------------- -// Implementation of the block handler for save/restore of entities -//----------------------------------------------------------------------------- -const char *CEntitySaveRestoreBlockHandler::GetBlockName() -{ - return "Entities"; -} - -//--------------------------------- - -void CEntitySaveRestoreBlockHandler::PreSave( CSaveRestoreData *pSaveData ) -{ - IGameSystem::OnSaveAllSystems(); - - m_EntitySaveUtils.PreSave(); - - // Allow the entities to do some work - CBaseEntity *pEnt = NULL; -#if !defined( CLIENT_DLL ) - while ( (pEnt = gEntList.NextEnt( pEnt )) != NULL ) - { - pEnt->OnSave( &m_EntitySaveUtils ); - } -#else - // Do this because it'll force entities to figure out their origins, and that requires - // SetupBones in the case of aiments. - C_BaseAnimating::PushAllowBoneAccess( true, true ); - - int last = ClientEntityList().GetHighestEntityIndex(); - ClientEntityHandle_t iter = ClientEntityList().FirstHandle(); - - for ( int e = 0; e <= last; e++ ) - { - pEnt = ClientEntityList().GetBaseEntity( e ); - - if( !pEnt ) - continue; - - pEnt->OnSave(); - } - - while ( iter != ClientEntityList().InvalidHandle() ) - { - pEnt = ClientEntityList().GetBaseEntityFromHandle( iter ); - - if ( pEnt && pEnt->ObjectCaps() & FCAP_SAVE_NON_NETWORKABLE ) - { - pEnt->OnSave(); - } - - iter = ClientEntityList().NextHandle( iter ); - } - - - C_BaseAnimating::PopBoneAccess(); -#endif - SaveInitEntities( pSaveData ); -} - -//--------------------------------- - -void CEntitySaveRestoreBlockHandler::Save( ISave *pSave ) -{ - CGameSaveRestoreInfo *pSaveData = pSave->GetGameSaveRestoreInfo(); - - // write entity list that was previously built by SaveInitEntities() - for ( int i = 0; i < pSaveData->NumEntities(); i++ ) - { - entitytable_t *pEntInfo = pSaveData->GetEntityInfo( i ); - pEntInfo->location = pSave->GetWritePos(); - pEntInfo->size = 0; - - CBaseEntity *pEnt = pEntInfo->hEnt; - if ( pEnt && !( pEnt->ObjectCaps() & FCAP_DONT_SAVE ) ) - { - MDLCACHE_CRITICAL_SECTION(); -#if !defined( CLIENT_DLL ) - AssertMsg( !pEnt->edict() || ( pEnt->m_iClassname != NULL_STRING && - (STRING(pEnt->m_iClassname)[0] != 0) && - FStrEq( STRING(pEnt->m_iClassname), pEnt->GetClassname()) ), - "Saving entity with invalid classname" ); -#endif - - pSaveData->SetCurrentEntityContext( pEnt ); - pEnt->Save( *pSave ); - pSaveData->SetCurrentEntityContext( NULL ); - - pEntInfo->size = pSave->GetWritePos() - pEntInfo->location; // Size of entity block is data size written to block - - pEntInfo->classname = pEnt->m_iClassname; // Remember entity class for respawn - -#if !defined( CLIENT_DLL ) - pEntInfo->globalname = pEnt->m_iGlobalname; // remember global name - pEntInfo->landmarkModelSpace = ModelSpaceLandmark( pEnt->GetModelIndex() ); - int nEntIndex = pEnt->edict() ? ENTINDEX(pEnt->edict()) : -1; - bool bIsPlayer = ( ( nEntIndex >= 1 ) && ( nEntIndex <= gpGlobals->maxClients ) ) ? true : false; - if ( bIsPlayer ) - { - pEntInfo->flags |= FENTTABLE_PLAYER; - } -#endif - } - } -} - -//--------------------------------- - -void CEntitySaveRestoreBlockHandler::WriteSaveHeaders( ISave *pSave ) -{ - CGameSaveRestoreInfo *pSaveData = pSave->GetGameSaveRestoreInfo(); - - int nEntities = pSaveData->NumEntities(); - pSave->WriteInt( &nEntities ); - - for ( int i = 0; i < pSaveData->NumEntities(); i++ ) - pSave->WriteFields( "ETABLE", pSaveData->GetEntityInfo( i ), NULL, entitytable_t::m_DataMap.dataDesc, entitytable_t::m_DataMap.dataNumFields ); -} - -//--------------------------------- - -void CEntitySaveRestoreBlockHandler::PostSave() -{ - m_EntitySaveUtils.PostSave(); -} - -//--------------------------------- - -void CEntitySaveRestoreBlockHandler::PreRestore() -{ -} - -//--------------------------------- - -void CEntitySaveRestoreBlockHandler::ReadRestoreHeaders( IRestore *pRestore ) -{ - CGameSaveRestoreInfo *pSaveData = pRestore->GetGameSaveRestoreInfo(); - - int nEntities; - pRestore->ReadInt( &nEntities ); - - entitytable_t *pEntityTable = ( entitytable_t *)engine->SaveAllocMemory( (sizeof(entitytable_t) * nEntities), sizeof(char) ); - - pSaveData->InitEntityTable( pEntityTable, nEntities ); - - for ( int i = 0; i < pSaveData->NumEntities(); i++ ) - pRestore->ReadFields( "ETABLE", pSaveData->GetEntityInfo( i ), NULL, entitytable_t::m_DataMap.dataDesc, entitytable_t::m_DataMap.dataNumFields ); - -} - -//--------------------------------- - -#if !defined( CLIENT_DLL ) - -void CEntitySaveRestoreBlockHandler::Restore( IRestore *pRestore, bool createPlayers ) -{ - entitytable_t *pEntInfo; - CBaseEntity *pent; - - CGameSaveRestoreInfo *pSaveData = pRestore->GetGameSaveRestoreInfo(); - - bool restoredWorld = false; - - // Create entity list - int i; - for ( i = 0; i < pSaveData->NumEntities(); i++ ) - { - pEntInfo = pSaveData->GetEntityInfo( i ); - - if ( pEntInfo->classname != NULL_STRING && pEntInfo->size && !(pEntInfo->flags & FENTTABLE_REMOVED) ) - { - if ( pEntInfo->edictindex == 0 ) // worldspawn - { - Assert( i == 0 ); - pent = CreateEntityByName( STRING(pEntInfo->classname) ); - pRestore->SetReadPos( pEntInfo->location ); - if ( RestoreEntity( pent, pRestore, pEntInfo ) < 0 ) - { - pEntInfo->hEnt = NULL; - pEntInfo->restoreentityindex = -1; - UTIL_RemoveImmediate( pent ); - } - else - { - // force the entity to be relinked - AddRestoredEntity( pent ); - } - } - else if ( (pEntInfo->edictindex > 0) && (pEntInfo->edictindex <= gpGlobals->maxClients) ) - { - if ( !(pEntInfo->flags & FENTTABLE_PLAYER) ) - { - Warning( "ENTITY IS NOT A PLAYER: %d\n" , i ); - Assert(0); - } - - edict_t *ed = INDEXENT( pEntInfo->edictindex ); - - if ( ed && createPlayers ) - { - // create the player - pent = CBasePlayer::CreatePlayer( STRING(pEntInfo->classname), ed ); - } - else - pent = NULL; - } - else - { - pent = CreateEntityByName( STRING(pEntInfo->classname) ); - } - pEntInfo->hEnt = pent; - pEntInfo->restoreentityindex = pent ? pent->entindex() : - 1; - if ( pent && pEntInfo->restoreentityindex == 0 ) - { - if ( !FClassnameIs( pent, "worldspawn" ) ) - { - pEntInfo->restoreentityindex = -1; - } - } - - if ( pEntInfo->restoreentityindex == 0 ) - { - Assert( !restoredWorld ); - restoredWorld = true; - } - } - else - { - pEntInfo->hEnt = NULL; - pEntInfo->restoreentityindex = -1; - } - } - - // Now spawn entities - for ( i = 0; i < pSaveData->NumEntities(); i++ ) - { - pEntInfo = pSaveData->GetEntityInfo( i ); - if ( pEntInfo->edictindex != 0 ) - { - pent = pEntInfo->hEnt; - pRestore->SetReadPos( pEntInfo->location ); - if ( pent ) - { - if ( RestoreEntity( pent, pRestore, pEntInfo ) < 0 ) - { - pEntInfo->hEnt = NULL; - pEntInfo->restoreentityindex = -1; - UTIL_RemoveImmediate( pent ); - } - else - { - AddRestoredEntity( pent ); - } - } - } - } -} - -#else // CLIENT DLL VERSION - -void CEntitySaveRestoreBlockHandler::Restore( IRestore *pRestore, bool createPlayers ) -{ - entitytable_t *pEntInfo; - CBaseEntity *pent; - - CGameSaveRestoreInfo *pSaveData = pRestore->GetGameSaveRestoreInfo(); - - // Create entity list - int i; - bool restoredWorld = false; - - for ( i = 0; i < pSaveData->NumEntities(); i++ ) - { - pEntInfo = pSaveData->GetEntityInfo( i ); - pent = ClientEntityList().GetBaseEntity( pEntInfo->restoreentityindex ); - pEntInfo->hEnt = pent; - } - - // Blast saved data into entities - for ( i = 0; i < pSaveData->NumEntities(); i++ ) - { - pEntInfo = pSaveData->GetEntityInfo( i ); - - bool bRestoredCorrectly = false; - // FIXME, need to translate save spot to real index here using lookup table transmitted from server - //Assert( !"Need translation still" ); - if ( pEntInfo->restoreentityindex >= 0 ) - { - if ( pEntInfo->restoreentityindex == 0 ) - { - Assert( !restoredWorld ); - restoredWorld = true; - } - - pent = ClientEntityList().GetBaseEntity( pEntInfo->restoreentityindex ); - pRestore->SetReadPos( pEntInfo->location ); - if ( pent ) - { - if ( RestoreEntity( pent, pRestore, pEntInfo ) >= 0 ) - { - // Call the OnRestore method - AddRestoredEntity( pent ); - bRestoredCorrectly = true; - } - } - } - // BUGBUG: JAY: Disable ragdolls across transitions until PVS/solid check & client entity patch file are implemented - else if ( !pSaveData->levelInfo.fUseLandmark ) - { - if ( pEntInfo->classname != NULL_STRING ) - { - pent = CreateEntityByName( STRING(pEntInfo->classname) ); - pent->InitializeAsClientEntity( NULL, RENDER_GROUP_OPAQUE_ENTITY ); - - pRestore->SetReadPos( pEntInfo->location ); - - if ( pent ) - { - if ( RestoreEntity( pent, pRestore, pEntInfo ) >= 0 ) - { - pEntInfo->hEnt = pent; - AddRestoredEntity( pent ); - bRestoredCorrectly = true; - } - } - } - } - - if ( !bRestoredCorrectly ) - { - pEntInfo->hEnt = NULL; - pEntInfo->restoreentityindex = -1; - } - } - - // Note, server does this after local player connects fully - IGameSystem::OnRestoreAllSystems(); - - // Tell hud elements to modify behavior based on game restoration, if applicable - gHUD.OnRestore(); -} -#endif - -void CEntitySaveRestoreBlockHandler::PostRestore() -{ -} - -void SaveEntityOnTable( CBaseEntity *pEntity, CSaveRestoreData *pSaveData, int &iSlot ) -{ - entitytable_t *pEntInfo = pSaveData->GetEntityInfo( iSlot ); - pEntInfo->id = iSlot; -#if !defined( CLIENT_DLL ) - pEntInfo->edictindex = pEntity->RequiredEdictIndex(); -#else - pEntInfo->edictindex = -1; - pEntInfo->modelname = pEntity->GetModelName(); -#endif - pEntInfo->restoreentityindex = -1; - pEntInfo->saveentityindex = pEntity ? pEntity->entindex() : -1; - pEntInfo->hEnt = pEntity; - pEntInfo->flags = 0; - pEntInfo->location = 0; - pEntInfo->size = 0; - pEntInfo->classname = NULL_STRING; - - iSlot++; -} - - -//--------------------------------- - -bool CEntitySaveRestoreBlockHandler::SaveInitEntities( CSaveRestoreData *pSaveData ) -{ - int number_of_entities; - -#if !defined( CLIENT_DLL ) - number_of_entities = gEntList.NumberOfEntities(); -#else - number_of_entities = ClientEntityList().NumberOfEntities( true ); -#endif - entitytable_t *pEntityTable = ( entitytable_t *)engine->SaveAllocMemory( (sizeof(entitytable_t) * number_of_entities), sizeof(char) ); - pSaveData->InitEntityTable( pEntityTable, number_of_entities ); - - // build the table of entities - // this is used to turn pointers into savable indices - // build up ID numbers for each entity, for use in pointer conversions - // if an entity requires a certain edict number upon restore, save that as well - CBaseEntity *pEnt = NULL; - int i = 0; - -#if !defined( CLIENT_DLL ) - while ( (pEnt = gEntList.NextEnt( pEnt )) != NULL ) - { -#else - int last = ClientEntityList().GetHighestEntityIndex(); - - for ( int e = 0; e <= last; e++ ) - { - pEnt = ClientEntityList().GetBaseEntity( e ); - if( !pEnt ) - continue; -#endif - SaveEntityOnTable( pEnt, pSaveData, i ); - } - -#if defined( CLIENT_DLL ) - ClientEntityHandle_t iter = ClientEntityList().FirstHandle(); - - while ( iter != ClientEntityList().InvalidHandle() ) - { - pEnt = ClientEntityList().GetBaseEntityFromHandle( iter ); - - if ( pEnt && pEnt->ObjectCaps() & FCAP_SAVE_NON_NETWORKABLE ) - { - SaveEntityOnTable( pEnt, pSaveData, i ); - } - - iter = ClientEntityList().NextHandle( iter ); - } -#endif - - Assert( i == pSaveData->NumEntities() ); - return ( i == pSaveData->NumEntities() ); -} - -//--------------------------------- - -#if !defined( CLIENT_DLL ) - -// Find the matching global entity. Spit out an error if the designer made entities of -// different classes with the same global name -CBaseEntity *CEntitySaveRestoreBlockHandler::FindGlobalEntity( string_t classname, string_t globalname ) -{ - CBaseEntity *pReturn = NULL; - - while ( (pReturn = gEntList.NextEnt( pReturn )) != NULL ) - { - if ( FStrEq( STRING(pReturn->m_iGlobalname), STRING(globalname)) ) - break; - } - - if ( pReturn ) - { - if ( !FClassnameIs( pReturn, STRING(classname) ) ) - { - Warning( "Global entity found %s, wrong class %s [expects class %s]\n", STRING(globalname), STRING(pReturn->m_iClassname), classname ); - pReturn = NULL; - } - } - - return pReturn; -} - -#endif // !defined( CLIENT_DLL ) - -//--------------------------------- - -bool CEntitySaveRestoreBlockHandler::DoRestoreEntity( CBaseEntity *pEntity, IRestore *pRestore ) -{ - MDLCACHE_CRITICAL_SECTION(); - - EHANDLE hEntity; - - hEntity = pEntity; - - pRestore->GetGameSaveRestoreInfo()->SetCurrentEntityContext( pEntity ); - pEntity->Restore( *pRestore ); - pRestore->GetGameSaveRestoreInfo()->SetCurrentEntityContext( NULL ); - -#if !defined( CLIENT_DLL ) - if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) - { - pEntity->Spawn(); - } - else - { - pEntity->Precache( ); - } -#endif - - // Above calls may have resulted in self destruction - return ( hEntity != NULL ); -} - -//--------------------------------- -// Get a reference position in model space to compute -// changes in model space for global brush entities (designer models them in different coords!) -Vector CEntitySaveRestoreBlockHandler::ModelSpaceLandmark( int modelIndex ) -{ - const model_t *pModel = modelinfo->GetModel( modelIndex ); - if ( modelinfo->GetModelType( pModel ) != mod_brush ) - return vec3_origin; - - Vector mins, maxs; - modelinfo->GetModelBounds( pModel, mins, maxs ); - return mins; -} - - -int CEntitySaveRestoreBlockHandler::RestoreEntity( CBaseEntity *pEntity, IRestore *pRestore, entitytable_t *pEntInfo ) -{ - if ( !DoRestoreEntity( pEntity, pRestore ) ) - return 0; - -#if !defined( CLIENT_DLL ) - if ( pEntity->m_iGlobalname != NULL_STRING ) - { - int globalIndex = GlobalEntity_GetIndex( pEntity->m_iGlobalname ); - if ( globalIndex >= 0 ) - { - // Already dead? delete - if ( GlobalEntity_GetState( globalIndex ) == GLOBAL_DEAD ) - return -1; - else if ( !FStrEq( STRING(gpGlobals->mapname), GlobalEntity_GetMap(globalIndex) ) ) - { - pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive - } - // In this level & not dead, continue on as normal - } - else - { - Warning( "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->m_iGlobalname), STRING(pEntity->m_iClassname) ); - // Spawned entities default to 'On' - GlobalEntity_Add( pEntity->m_iGlobalname, gpGlobals->mapname, GLOBAL_ON ); - } - } -#endif - - return 0; -} - -//--------------------------------- - -#if !defined( CLIENT_DLL ) - -int CEntitySaveRestoreBlockHandler::RestoreGlobalEntity( CBaseEntity *pEntity, CSaveRestoreData *pSaveData, entitytable_t *pEntInfo ) -{ - Vector oldOffset; - EHANDLE hEntitySafeHandle; - hEntitySafeHandle = pEntity; - - oldOffset.Init(); - CRestore restoreHelper( pSaveData ); - - string_t globalName = pEntInfo->globalname, className = pEntInfo->classname; - - // ------------------- - - int globalIndex = GlobalEntity_GetIndex( globalName ); - - // Don't overlay any instance of the global that isn't the latest - // pSaveData->szCurrentMapName is the level this entity is coming from - // pGlobal->levelName is the last level the global entity was active in. - // If they aren't the same, then this global update is out of date. - if ( !FStrEq( pSaveData->levelInfo.szCurrentMapName, GlobalEntity_GetMap(globalIndex) ) ) - { - return 0; - } - - // Compute the new global offset - CBaseEntity *pNewEntity = FindGlobalEntity( className, globalName ); - if ( pNewEntity ) - { -// Msg( "Overlay %s with %s\n", pNewEntity->GetClassname(), STRING(tmpEnt->classname) ); - // Tell the restore code we're overlaying a global entity from another level - restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields - - pSaveData->modelSpaceOffset = pEntInfo->landmarkModelSpace - ModelSpaceLandmark( pNewEntity->GetModelIndex() ); - - UTIL_Remove( pEntity ); - pEntity = pNewEntity;// we're going to restore this data OVER the old entity - pEntInfo->hEnt = pEntity; - // HACKHACK: Do we need system-wide support for removing non-global spawn allocated resources? - pEntity->VPhysicsDestroyObject(); - Assert( pEntInfo->edictindex == -1 ); - // Update the global table to say that the global definition of this entity should come from this level - GlobalEntity_SetMap( globalIndex, gpGlobals->mapname ); - } - else - { - // This entity will be freed automatically by the engine-> If we don't do a restore on a matching entity (below) - // or call EntityUpdate() to move it to this level, we haven't changed global state at all. - DevMsg( "Warning: No match for global entity %s found in destination level\n", STRING(globalName) ); - return 0; - } - - if ( !DoRestoreEntity( pEntity, &restoreHelper ) ) - { - pEntity = NULL; - } - - // Is this an overriding global entity (coming over the transition) - pSaveData->modelSpaceOffset.Init(); - if ( pEntity ) - return 1; - return 0; -} - -#endif // !defined( CLIENT_DLL ) - - - -//----------------------------------------------------------------------------- - -CSaveRestoreData *SaveInit( int size ) -{ - CSaveRestoreData *pSaveData; - -#ifdef DISABLE_DEBUG_HISTORY - if ( size <= 0 ) - size = 2*1024*1024; // Reserve 2048K for now, UNDONE: Shrink this after compressing strings -#else - if ( size <= 0 ) - size = 3*1024*1024; // Reserve 3096K for now, UNDONE: Shrink this after compressing strings -#endif - - int numentities; - -#if !defined( CLIENT_DLL ) - numentities = gEntList.NumberOfEntities(); -#else - numentities = ClientEntityList().NumberOfEntities(); -#endif - - pSaveData = MakeSaveRestoreData(engine->SaveAllocMemory( sizeof(CSaveRestoreData) + (sizeof(entitytable_t) * numentities) + size, sizeof(char) )); - pSaveData->Init( (char *)(pSaveData + 1), size ); // skip the save structure - - const int nTokens = 0xfff; // Assume a maximum of 4K-1 symbol table entries(each of some length) - pSaveData->InitSymbolTable( (char **)engine->SaveAllocMemory( nTokens, sizeof( char * ) ), nTokens ); - - //--------------------------------- - - pSaveData->levelInfo.time = gpGlobals->curtime; // Use DLL time - pSaveData->levelInfo.vecLandmarkOffset = vec3_origin; - pSaveData->levelInfo.fUseLandmark = false; - pSaveData->levelInfo.connectionCount = 0; - - //--------------------------------- - - gpGlobals->pSaveData = pSaveData; - - return pSaveData; -} - - - -//----------------------------------------------------------------------------- -// -// ISaveRestoreBlockSet -// -// Purpose: Serves as holder for a group of sibling save sections. Takes -// care of iterating over them, making sure read points are -// queued up to the right spot (in case one section due to datadesc -// changes reads less than expected, or doesn't leave the -// read pointer at the right point), and ensuring the read pointer -// is at the end of the entire set when the set read is done. -//----------------------------------------------------------------------------- - -struct SaveRestoreBlockHeader_t -{ - char szName[MAX_BLOCK_NAME_LEN + 1]; - int locHeader; - int locBody; - - DECLARE_SIMPLE_DATADESC(); -}; - - -//------------------------------------- - -class CSaveRestoreBlockSet : public ISaveRestoreBlockSet -{ -public: - CSaveRestoreBlockSet( const char *pszName ) - { - Q_strncpy( m_Name, pszName, sizeof(m_Name) ); - } - - const char *GetBlockName() - { - return m_Name; - } - - //--------------------------------- - - void PreSave( CSaveRestoreData *pData ) - { - m_BlockHeaders.SetCount( m_Handlers.Count() ); - for ( int i = 0; i < m_Handlers.Count(); i++ ) - { - Q_strncpy( m_BlockHeaders[i].szName, m_Handlers[i]->GetBlockName(), MAX_BLOCK_NAME_LEN + 1 ); - m_Handlers[i]->PreSave( pData ); - } - } - - void Save( ISave *pSave ) - { - int base = pSave->GetWritePos(); - for ( int i = 0; i < m_Handlers.Count(); i++ ) - { - m_BlockHeaders[i].locBody = pSave->GetWritePos() - base; - m_Handlers[i]->Save( pSave ); - } - m_SizeBodies = pSave->GetWritePos() - base; - } - - void WriteSaveHeaders( ISave *pSave ) - { - int base = pSave->GetWritePos(); - - // - // Reserve space for a fully populated header - // - int dummyInt = -1; - CUtlVector dummyArr; - - dummyArr.SetCount( m_BlockHeaders.Count() ); - memset( &dummyArr[0], 0xff, dummyArr.Count() * sizeof(SaveRestoreBlockHeader_t) ); - - pSave->WriteInt( &dummyInt ); // size all headers - pSave->WriteInt( &dummyInt ); // size all bodies - SaveUtlVector( pSave, &dummyArr, FIELD_EMBEDDED ); - - // - // Write the data - // - for ( int i = 0; i < m_Handlers.Count(); i++ ) - { - m_BlockHeaders[i].locHeader = pSave->GetWritePos() - base; - m_Handlers[i]->WriteSaveHeaders( pSave ); - } - - m_SizeHeaders = pSave->GetWritePos() - base; - - // - // Write the actual header - // - int savedPos = pSave->GetWritePos(); - pSave->SetWritePos(base); - - pSave->WriteInt( &m_SizeHeaders ); - pSave->WriteInt( &m_SizeBodies ); - SaveUtlVector( pSave, &m_BlockHeaders, FIELD_EMBEDDED ); - - pSave->SetWritePos(savedPos); - } - - void PostSave() - { - for ( int i = 0; i < m_Handlers.Count(); i++ ) - { - m_Handlers[i]->PostSave(); - } - m_BlockHeaders.Purge(); - } - - //--------------------------------- - - void PreRestore() - { - for ( int i = 0; i < m_Handlers.Count(); i++ ) - { - m_Handlers[i]->PreRestore(); - } - } - - void ReadRestoreHeaders( IRestore *pRestore ) - { - int base = pRestore->GetReadPos(); - - pRestore->ReadInt( &m_SizeHeaders ); - pRestore->ReadInt( &m_SizeBodies ); - RestoreUtlVector( pRestore, &m_BlockHeaders, FIELD_EMBEDDED ); - - for ( int i = 0; i < m_Handlers.Count(); i++ ) - { - int location = GetBlockHeaderLoc( m_Handlers[i]->GetBlockName() ); - if ( location != -1 ) - { - pRestore->SetReadPos( base + location ); - m_Handlers[i]->ReadRestoreHeaders( pRestore ); - } - } - - pRestore->SetReadPos( base + m_SizeHeaders ); - } - - void CallBlockHandlerRestore( ISaveRestoreBlockHandler *pHandler, int baseFilePos, IRestore *pRestore, bool fCreatePlayers ) - { - int location = GetBlockBodyLoc( pHandler->GetBlockName() ); - if ( location != -1 ) - { - pRestore->SetReadPos( baseFilePos + location ); - pHandler->Restore( pRestore, fCreatePlayers ); - } - } - - void Restore( IRestore *pRestore, bool fCreatePlayers ) - { - int base = pRestore->GetReadPos(); - - for ( int i = 0; i < m_Handlers.Count(); i++ ) - { - CallBlockHandlerRestore( m_Handlers[i], base, pRestore, fCreatePlayers ); - } - pRestore->SetReadPos( base + m_SizeBodies ); - } - - void PostRestore() - { - for ( int i = 0; i < m_Handlers.Count(); i++ ) - { - m_Handlers[i]->PostRestore(); - } - m_BlockHeaders.Purge(); - } - - //--------------------------------- - - void AddBlockHandler( ISaveRestoreBlockHandler *pHandler ) - { - // Grody, but... while this class is still isolated in saverestore.cpp, this seems like a fine time to assert: - AssertMsg( pHandler == &g_EntitySaveRestoreBlockHandler || (m_Handlers.Count() >= 1 && m_Handlers[0] == &g_EntitySaveRestoreBlockHandler), "Expected entity save load to always be first" ); - - Assert( pHandler != this ); - m_Handlers.AddToTail( pHandler ); - } - - void RemoveBlockHandler( ISaveRestoreBlockHandler *pHandler ) - { - m_Handlers.FindAndRemove( pHandler ); - } - - //--------------------------------- - -private: - int GetBlockBodyLoc( const char *pszName ) - { - for ( int i = 0; i < m_BlockHeaders.Count(); i++ ) - { - if ( strcmp( m_BlockHeaders[i].szName, pszName ) == 0 ) - return m_BlockHeaders[i].locBody; - } - return -1; - } - - int GetBlockHeaderLoc( const char *pszName ) - { - for ( int i = 0; i < m_BlockHeaders.Count(); i++ ) - { - if ( strcmp( m_BlockHeaders[i].szName, pszName ) == 0 ) - return m_BlockHeaders[i].locHeader; - } - return -1; - } - - char m_Name[MAX_BLOCK_NAME_LEN + 1]; - CUtlVector m_Handlers; - - int m_SizeHeaders; - int m_SizeBodies; - CUtlVector m_BlockHeaders; -}; - -//------------------------------------- - -BEGIN_SIMPLE_DATADESC( SaveRestoreBlockHeader_t ) - DEFINE_ARRAY(szName, FIELD_CHARACTER, MAX_BLOCK_NAME_LEN + 1), - DEFINE_FIELD(locHeader, FIELD_INTEGER), - DEFINE_FIELD(locBody, FIELD_INTEGER), -END_DATADESC() - -//------------------------------------- - -CSaveRestoreBlockSet g_SaveRestoreBlockSet("Game"); -ISaveRestoreBlockSet *g_pGameSaveRestoreBlockSet = &g_SaveRestoreBlockSet; - -//============================================================================= -#if !defined( CLIENT_DLL ) - -//------------------------------------------------------------------------------ -// Creates all entities that lie in the transition list -//------------------------------------------------------------------------------ -void CreateEntitiesInTransitionList( CSaveRestoreData *pSaveData, int levelMask ) -{ - CBaseEntity *pent; - int i; - for ( i = 0; i < pSaveData->NumEntities(); i++ ) - { - entitytable_t *pEntInfo = pSaveData->GetEntityInfo( i ); - pEntInfo->hEnt = NULL; - - if ( pEntInfo->size == 0 || pEntInfo->edictindex == 0 ) - continue; - - if ( pEntInfo->classname == NULL_STRING ) - { - Warning( "Entity with data saved, but with no classname\n" ); - Assert(0); - continue; - } - - bool active = (pEntInfo->flags & levelMask) ? 1 : 0; - - // spawn players - pent = NULL; - if ( (pEntInfo->edictindex > 0) && (pEntInfo->edictindex <= gpGlobals->maxClients) ) - { - edict_t *ed = INDEXENT( pEntInfo->edictindex ); - - if ( active && ed && !ed->IsFree() ) - { - if ( !(pEntInfo->flags & FENTTABLE_PLAYER) ) - { - Warning( "ENTITY IS NOT A PLAYER: %d\n" , i ); - Assert(0); - } - - pent = CBasePlayer::CreatePlayer( STRING(pEntInfo->classname), ed ); - } - } - else if ( active ) - { - pent = CreateEntityByName( STRING(pEntInfo->classname) ); - } - - pEntInfo->hEnt = pent; - } -} - - -//----------------------------------------------------------------------------- -int CreateEntityTransitionList( CSaveRestoreData *pSaveData, int levelMask ) -{ - CBaseEntity *pent; - entitytable_t *pEntInfo; - - // Create entity list - CreateEntitiesInTransitionList( pSaveData, levelMask ); - - // Now spawn entities - CUtlVector checkList; - - int i; - int movedCount = 0; - for ( i = 0; i < pSaveData->NumEntities(); i++ ) - { - pEntInfo = pSaveData->GetEntityInfo( i ); - pent = pEntInfo->hEnt; -// pSaveData->currentIndex = i; - pSaveData->Seek( pEntInfo->location ); - - // clear this out - it must be set on a per-entity basis - pSaveData->modelSpaceOffset.Init(); - - if ( pent && (pEntInfo->flags & levelMask) ) // Screen out the player if he's not to be spawned - { - if ( pEntInfo->flags & FENTTABLE_GLOBAL ) - { - DevMsg( 2, "Merging changes for global: %s\n", STRING(pEntInfo->classname) ); - - // ------------------------------------------------------------------------- - // Pass the "global" flag to the DLL to indicate this entity should only override - // a matching entity, not be spawned - if ( g_EntitySaveRestoreBlockHandler.RestoreGlobalEntity( pent, pSaveData, pEntInfo ) > 0 ) - { - movedCount++; - pEntInfo->restoreentityindex = pEntInfo->hEnt.Get()->entindex(); - AddRestoredEntity( pEntInfo->hEnt.Get() ); - } - else - { - UTIL_RemoveImmediate( pEntInfo->hEnt.Get() ); - } - // ------------------------------------------------------------------------- - } - else - { - DevMsg( 2, "Transferring %s (%d)\n", STRING(pEntInfo->classname), pent->edict() ? ENTINDEX(pent->edict()) : -1 ); - CRestore restoreHelper( pSaveData ); - if ( g_EntitySaveRestoreBlockHandler.RestoreEntity( pent, &restoreHelper, pEntInfo ) < 0 ) - { - UTIL_RemoveImmediate( pent ); - } - else - { - // needs to be checked. Do this in a separate pass so that pointers & hierarchy can be traversed - checkList.AddToTail(i); - } - } - - // Remove any entities that were removed using UTIL_Remove() as a result of the above calls to UTIL_RemoveImmediate() - gEntList.CleanupDeleteList(); - } - } - - for ( i = checkList.Count()-1; i >= 0; --i ) - { - pEntInfo = pSaveData->GetEntityInfo( checkList[i] ); - pent = pEntInfo->hEnt; - - // NOTE: pent can be NULL because UTIL_RemoveImmediate (called below) removes all in hierarchy - if ( !pent ) - continue; - - MDLCACHE_CRITICAL_SECTION(); - - if ( !(pEntInfo->flags & FENTTABLE_PLAYER) && UTIL_EntityInSolid( pent ) ) - { - // this can happen during normal processing - PVS is just a guess, some map areas won't exist in the new map - DevMsg( 2, "Suppressing %s\n", STRING(pEntInfo->classname) ); - UTIL_RemoveImmediate( pent ); - // Remove any entities that were removed using UTIL_Remove() as a result of the above calls to UTIL_RemoveImmediate() - gEntList.CleanupDeleteList(); - } - else - { - movedCount++; - pEntInfo->flags = FENTTABLE_REMOVED; - pEntInfo->restoreentityindex = pent->entindex(); - AddRestoredEntity( pent ); - } - } - - return movedCount; -} -#endif +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Helper classes and functions for the save/restore system. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include +#include "isaverestore.h" +#include "saverestore.h" +#include +#include "shake.h" +#include "decals.h" +#include "gamerules.h" +#include "bspfile.h" +#include "mathlib.h" +#include "engine/IEngineSound.h" +#include "saverestoretypes.h" +#include "saverestore_utlvector.h" +#include "model_types.h" +#include "igamesystem.h" +#include "interval.h" +#include "vphysics/object_hash.h" +#include "datacache/imdlcache.h" + +#if !defined( CLIENT_DLL ) + +#include "globalstate.h" +#include "entitylist.h" + +#else + +#include "gamestringpool.h" + +#endif + +// HACKHACK: Builds a global list of entities that were restored from all levels +#if !defined( CLIENT_DLL ) +void AddRestoredEntity( CBaseEntity *pEntity ); +#else +void AddRestoredEntity( C_BaseEntity *pEntity ); +#endif + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define MAX_ENTITYARRAY 1024 +#define ZERO_TIME ((FLT_MAX*-0.5)) +// A bit arbitrary, but unlikely to collide with any saved games... +#define TICK_NEVER_THINK_ENCODE ( INT_MAX - 3 ) + +ASSERT_INVARIANT( sizeof(EHandlePlaceholder_t) == sizeof(EHANDLE) ); + +//----------------------------------------------------------------------------- + +static int gSizes[FIELD_TYPECOUNT] = +{ + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), + CDatamapFieldSizeDeducer::FieldSize(), +}; + + +// helpers to offset worldspace matrices +static void VMatrixOffset( VMatrix &dest, const VMatrix &matrixIn, const Vector &offset ) +{ + dest = matrixIn; + dest.PostTranslate( offset ); +} + +static void Matrix3x4Offset( matrix3x4_t& dest, const matrix3x4_t& matrixIn, const Vector &offset ) +{ + MatrixCopy( matrixIn, dest ); + Vector out; + MatrixGetColumn( matrixIn, 3, out ); + out += offset; + MatrixSetColumn( out, 3, dest ); +} + +// This does the necessary casting / extract to grab a pointer to a member function as a void * +// UNDONE: Cast to BASEPTR or something else here? +#define EXTRACT_VOID_FUNCTIONPTR(x) (*(void **)(&(x))) +//----------------------------------------------------------------------------- +// Purpose: Search this datamap for the name of this member function +// This is used to save/restore function pointers (convert pointer to text) +// Input : *function - pointer to member function +// Output : const char * - function name +//----------------------------------------------------------------------------- +const char *UTIL_FunctionToName( datamap_t *pMap, void *function ) +{ + while ( pMap ) + { + for ( int i = 0; i < pMap->dataNumFields; i++ ) + { + if ( pMap->dataDesc[i].flags & FTYPEDESC_FUNCTIONTABLE ) + { + Assert( sizeof(pMap->dataDesc[i].inputFunc) == sizeof(void *) ); + void *pTest = EXTRACT_VOID_FUNCTIONPTR(pMap->dataDesc[i].inputFunc); + if ( pTest == function ) + return pMap->dataDesc[i].fieldName; + } + } + pMap = pMap->baseMap; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Search the datamap for a function named pName +// This is used to save/restore function pointers (convert text back to pointer) +// Input : *pName - name of the member function +//----------------------------------------------------------------------------- +void *UTIL_FunctionFromName( datamap_t *pMap, const char *pName ) +{ + while ( pMap ) + { + for ( int i = 0; i < pMap->dataNumFields; i++ ) + { + Assert( sizeof(pMap->dataDesc[i].inputFunc) == sizeof(void *) ); + + if ( pMap->dataDesc[i].flags & FTYPEDESC_FUNCTIONTABLE ) + { + if ( FStrEq( pName, pMap->dataDesc[i].fieldName ) ) + { + return EXTRACT_VOID_FUNCTIONPTR(pMap->dataDesc[i].inputFunc); + } + } + } + pMap = pMap->baseMap; + } + + Msg( "Failed to find function %s\n", pName ); + + return NULL; +} + +//----------------------------------------------------------------------------- +// +// CSave +// +//----------------------------------------------------------------------------- + +CSave::CSave( CSaveRestoreData *pdata ) + : m_pData(pdata), + m_pGameInfo( pdata ) +{ + m_BlockStartStack.EnsureCapacity( 32 ); + + // Logging. + m_hLogFile = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Start logging save data. +//----------------------------------------------------------------------------- +void CSave::StartLogging( const char *pszLogName ) +{ + m_hLogFile = filesystem->Open( pszLogName, "w" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Stop logging save data. +//----------------------------------------------------------------------------- +void CSave::EndLogging( void ) +{ + if ( m_hLogFile ) + { + filesystem->Close( m_hLogFile ); + } + m_hLogFile = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if we are logging data. +//----------------------------------------------------------------------------- +bool CSave::IsLogging( void ) +{ + return ( m_hLogFile != NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Log data. +//----------------------------------------------------------------------------- +void CSave::Log( const char *pName, fieldtype_t fieldType, void *value, int count ) +{ + // Check to see if we are logging. + if ( !IsLogging() ) + return; + + static char szBuf[1024]; + static char szTempBuf[256]; + + // Save the name. + Q_snprintf( szBuf, sizeof( szBuf ), "%s ", pName ); + + for ( int iCount = 0; iCount < count; ++iCount ) + { + switch ( fieldType ) + { + case FIELD_SHORT: + { + short *pValue = ( short* )( value ); + short nValue = pValue[iCount]; + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%d", nValue ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + break; + } + case FIELD_FLOAT: + { + float *pValue = ( float* )( value ); + float flValue = pValue[iCount]; + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%f", flValue ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + break; + } + case FIELD_BOOLEAN: + { + bool *pValue = ( bool* )( value ); + bool bValue = pValue[iCount]; + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%d", ( int )( bValue ) ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + break; + } + case FIELD_INTEGER: + { + int *pValue = ( int* )( value ); + int nValue = pValue[iCount]; + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%d", nValue ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + break; + } + case FIELD_STRING: + { + string_t *pValue = ( string_t* )( value ); + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%s", ( char* )STRING( *pValue ) ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + break; + } + case FIELD_VECTOR: + { + Vector *pValue = ( Vector* )( value ); + Vector vecValue = pValue[iCount]; + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "(%f %f %f)", vecValue.x, vecValue.y, vecValue.z ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + break; + } + case FIELD_QUATERNION: + { + Quaternion *pValue = ( Quaternion* )( value ); + Quaternion q = pValue[iCount]; + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "(%f %f %f %f)", q[0], q[1], q[2], q[3] ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + break; + } + case FIELD_CHARACTER: + { + char *pValue = ( char* )( value ); + char chValue = pValue[iCount]; + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "%c", chValue ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + } + case FIELD_COLOR32: + { + byte *pValue = ( byte* )( value ); + byte *pColor = &pValue[iCount*4]; + Q_snprintf( szTempBuf, sizeof( szTempBuf ), "(%d %d %d %d)", ( int )pColor[0], ( int )pColor[1], ( int )pColor[2], ( int )pColor[3] ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + } + case FIELD_EMBEDDED: + case FIELD_CUSTOM: + default: + { + break; + } + } + + // Add space data. + if ( ( iCount + 1 ) != count ) + { + Q_strncpy( szTempBuf, " ", sizeof( szTempBuf ) ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + } + else + { + Q_strncpy( szTempBuf, "\n", sizeof( szTempBuf ) ); + Q_strncat( szBuf, szTempBuf, sizeof( szTempBuf ), COPY_ALL_CHARACTERS ); + } + } + + int nLength = strlen( szBuf ) + 1; + filesystem->Write( szBuf, nLength, m_hLogFile ); +} + +//------------------------------------- + +int CSave::GetWritePos() const +{ + return m_pData->GetCurPos(); +} + +//------------------------------------- + +void CSave::SetWritePos(int pos) +{ + m_pData->Seek(pos); +} + +//------------------------------------- + +void CSave::WriteShort( const short *value, int count ) +{ + BufferData( (const char *)value, sizeof(short) * count ); +} + +//------------------------------------- + +void CSave::WriteInt( const int *value, int count ) +{ + BufferData( (const char *)value, sizeof(int) * count ); +} + +//------------------------------------- + +void CSave::WriteBool( const bool *value, int count ) +{ + COMPILE_TIME_ASSERT( sizeof(bool) == sizeof(char) ); + BufferData( (const char *)value, sizeof(bool) * count ); +} + +//------------------------------------- + +void CSave::WriteFloat( const float *value, int count ) +{ + BufferData( (const char *)value, sizeof(float) * count ); +} + +//------------------------------------- + +void CSave::WriteData( const char *pdata , int size ) +{ + BufferData( pdata, size ); +} + +//------------------------------------- + +void CSave::WriteString( const char *pstring ) +{ + BufferData( pstring, strlen(pstring) + 1 ); +} + +//------------------------------------- + +void CSave::WriteString( const string_t *stringId, int count ) +{ + for ( int i = 0; i < count; i++ ) + { + const char *pString = STRING(stringId[i]); + BufferData( pString, strlen(pString)+1 ); + } +} + +//------------------------------------- + +void CSave::WriteVector( const Vector &value ) +{ + BufferData( (const char *)&value, sizeof(Vector) ); +} + +//------------------------------------- + +void CSave::WriteVector( const Vector *value, int count ) +{ + BufferData( (const char *)value, sizeof(Vector) * count ); +} + +void CSave::WriteQuaternion( const Quaternion &value ) +{ + BufferData( (const char *)&value, sizeof(Quaternion) ); +} + +//------------------------------------- + +void CSave::WriteQuaternion( const Quaternion *value, int count ) +{ + BufferData( (const char *)value, sizeof(Quaternion) * count ); +} + + +//------------------------------------- + +void CSave::WriteData( const char *pname, int size, const char *pdata ) +{ + BufferField( pname, size, pdata ); +} + +//------------------------------------- + +void CSave::WriteShort( const char *pname, const short *data, int count ) +{ + BufferField( pname, sizeof(short) * count, (const char *)data ); +} + +//------------------------------------- + +void CSave::WriteInt( const char *pname, const int *data, int count ) +{ + BufferField( pname, sizeof(int) * count, (const char *)data ); +} + +//------------------------------------- + +void CSave::WriteBool( const char *pname, const bool *data, int count ) +{ + COMPILE_TIME_ASSERT( sizeof(bool) == sizeof(char) ); + BufferField( pname, sizeof(bool) * count, (const char *)data ); +} + +//------------------------------------- + +void CSave::WriteFloat( const char *pname, const float *data, int count ) +{ + BufferField( pname, sizeof(float) * count, (const char *)data ); +} + +//------------------------------------- + +void CSave::WriteString( const char *pname, const char *pdata ) +{ + BufferField( pname, strlen(pdata) + 1, pdata ); +} + +//------------------------------------- + +void CSave::WriteString( const char *pname, const string_t *stringId, int count ) +{ + int i, size; + + size = 0; + for ( i = 0; i < count; i++ ) + size += strlen( STRING( stringId[i] ) ) + 1; + + WriteHeader( pname, size ); + WriteString( stringId, count ); +} + +//------------------------------------- + +void CSave::WriteVector( const char *pname, const Vector &value ) +{ + WriteVector( pname, &value, 1 ); +} + +//------------------------------------- + +void CSave::WriteVector( const char *pname, const Vector *value, int count ) +{ + WriteHeader( pname, sizeof(Vector) * count ); + BufferData( (const char *)value, sizeof(Vector) * count ); +} + +void CSave::WriteQuaternion( const char *pname, const Quaternion &value ) +{ + WriteQuaternion( pname, &value, 1 ); +} + +//------------------------------------- + +void CSave::WriteQuaternion( const char *pname, const Quaternion *value, int count ) +{ + WriteHeader( pname, sizeof(Quaternion) * count ); + BufferData( (const char *)value, sizeof(Quaternion) * count ); +} + + +//------------------------------------- + +void CSave::WriteVMatrix( const VMatrix *value, int count ) +{ + BufferData( (const char *)value, sizeof(VMatrix) * count ); +} + +//------------------------------------- + +void CSave::WriteVMatrix( const char *pname, const VMatrix *value, int count ) +{ + WriteHeader( pname, sizeof(VMatrix) * count ); + BufferData( (const char *)value, sizeof(VMatrix) * count ); +} + +//------------------------------------- + +void CSave::WriteVMatrixWorldspace( const VMatrix *value, int count ) +{ + for ( int i = 0; i < count; i++ ) + { + VMatrix tmp; + VMatrixOffset( tmp, value[i], -m_pGameInfo->GetLandmark() ); + BufferData( (const char *)&tmp, sizeof(VMatrix) ); + } +} + +//------------------------------------- + +void CSave::WriteVMatrixWorldspace( const char *pname, const VMatrix *value, int count ) +{ + WriteHeader( pname, sizeof(VMatrix) * count ); + WriteVMatrixWorldspace( value, count ); +} + +void CSave::WriteMatrix3x4Worldspace( const matrix3x4_t *value, int count ) +{ + Vector offset = -m_pGameInfo->GetLandmark(); + for ( int i = 0; i < count; i++ ) + { + matrix3x4_t tmp; + Matrix3x4Offset( tmp, value[i], offset ); + BufferData( (const char *)value, sizeof(matrix3x4_t) ); + } +} + +//------------------------------------- + +void CSave::WriteMatrix3x4Worldspace( const char *pname, const matrix3x4_t *value, int count ) +{ + WriteHeader( pname, sizeof(matrix3x4_t) * count ); + WriteMatrix3x4Worldspace( value, count ); +} + +void CSave::WriteInterval( const char *pname, const interval_t *value, int count ) +{ + WriteHeader( pname, sizeof( interval_t ) * count ); + WriteInterval( value, count ); +} + +void CSave::WriteInterval( const interval_t *value, int count ) +{ + BufferData( (const char *)value, count * sizeof( interval_t ) ); +} + +//------------------------------------- + +bool CSave::ShouldSaveField( const void *pData, typedescription_t *pField ) +{ + if ( !(pField->flags & FTYPEDESC_SAVE) || pField->fieldType == FIELD_VOID ) + return false; + + switch ( pField->fieldType ) + { + case FIELD_EMBEDDED: + { + if ( pField->flags & FTYPEDESC_PTR ) + { + AssertMsg( pField->fieldSize == 1, "Arrays of embedded pointer types presently unsupported by save/restore" ); + if ( pField->fieldSize != 1 ) + return false; + } + + AssertMsg( pField->td != NULL, "Embedded type appears to have not had type description implemented" ); + if ( pField->td == NULL ) + return false; + + if ( (pField->flags & FTYPEDESC_PTR) && !*((void **)pData) ) + return false; + + int nFieldCount = pField->fieldSize; + char *pTestData = (char *)( ( !(pField->flags & FTYPEDESC_PTR) ) ? pData : *((void **)pData) ); + while ( --nFieldCount >= 0 ) + { + typedescription_t *pTestField = pField->td->dataDesc; + typedescription_t *pLimit = pField->td->dataDesc + pField->td->dataNumFields; + + for ( ; pTestField < pLimit; ++pTestField ) + { + if ( ShouldSaveField( pTestData + pTestField->fieldOffset[ TD_OFFSET_NORMAL ], pTestField ) ) + return true; + } + + pTestData += pField->fieldSizeInBytes; + } + return false; + } + + case FIELD_CUSTOM: + { + // ask the data if it's empty + SaveRestoreFieldInfo_t fieldInfo = + { + const_cast(pData), + ((char *)pData) - pField->fieldOffset[ TD_OFFSET_NORMAL ], + pField + }; + if ( pField->pSaveRestoreOps->IsEmpty( fieldInfo ) ) + return false; + } + return true; + + case FIELD_EHANDLE: + { + if ( (pField->fieldSizeInBytes != pField->fieldSize * gSizes[pField->fieldType]) ) + { + Warning("WARNING! Field %s is using the wrong FIELD_ type!\nFix this or you'll see a crash.\n", pField->fieldName ); + Assert( 0 ); + } + + int *pEHandle = (int *)pData; + for ( int i = 0; i < static_cast(pField->fieldSize); ++i, ++pEHandle ) + { + if ( (*pEHandle) != static_cast(0xFFFFFFFF) ) + return true; + } + } + return false; + + default: + { + if ( (pField->fieldSizeInBytes != pField->fieldSize * gSizes[pField->fieldType]) ) + { + Warning("WARNING! Field %s is using the wrong FIELD_ type!\nFix this or you'll see a crash.\n", pField->fieldName ); + Assert( 0 ); + } + + // old byte-by-byte null check + if ( DataEmpty( (const char *)pData, pField->fieldSize * gSizes[pField->fieldType] ) ) + return false; + } + return true; + } +} + +//------------------------------------- +// Purpose: Writes all the fields that are client neutral. In the event of +// a librarization of save/restore, these would reside in the library +// + +bool CSave::WriteBasicField( const char *pname, void *pData, datamap_t *pRootMap, typedescription_t *pField ) +{ + switch( pField->fieldType ) + { + case FIELD_FLOAT: + WriteFloat( pField->fieldName, (float *)pData, pField->fieldSize ); + break; + + case FIELD_STRING: + WriteString( pField->fieldName, (string_t *)pData, pField->fieldSize ); + break; + + case FIELD_VECTOR: + WriteVector( pField->fieldName, (Vector *)pData, pField->fieldSize ); + break; + + case FIELD_QUATERNION: + WriteQuaternion( pField->fieldName, (Quaternion *)pData, pField->fieldSize ); + break; + + case FIELD_INTEGER: + WriteInt( pField->fieldName, (int *)pData, pField->fieldSize ); + break; + + case FIELD_BOOLEAN: + WriteBool( pField->fieldName, (bool *)pData, pField->fieldSize ); + break; + + case FIELD_SHORT: + WriteData( pField->fieldName, 2 * pField->fieldSize, ((char *)pData) ); + break; + + case FIELD_CHARACTER: + WriteData( pField->fieldName, pField->fieldSize, ((char *)pData) ); + break; + + case FIELD_COLOR32: + WriteData( pField->fieldName, 4*pField->fieldSize, (char *)pData ); + break; + + case FIELD_EMBEDDED: + { + AssertMsg( ( (pField->flags & FTYPEDESC_PTR) == 0 ) || (pField->fieldSize == 1), "Arrays of embedded pointer types presently unsupported by save/restore" ); + Assert( !(pField->flags & FTYPEDESC_PTR) || *((void **)pData) ); + int nFieldCount = pField->fieldSize; + char *pFieldData = (char *)( ( !(pField->flags & FTYPEDESC_PTR) ) ? pData : *((void **)pData) ); + + StartBlock( pField->fieldName ); + + while ( --nFieldCount >= 0 ) + { + WriteAll( pFieldData, pField->td ); + pFieldData += pField->fieldSizeInBytes; + } + + EndBlock(); + break; + } + + case FIELD_CUSTOM: + { + // Note it is up to the custom type implementor to handle arrays + StartBlock( pField->fieldName ); + + SaveRestoreFieldInfo_t fieldInfo = + { + pData, + ((char *)pData) - pField->fieldOffset[ TD_OFFSET_NORMAL ], + pField + }; + pField->pSaveRestoreOps->Save( fieldInfo, this ); + + EndBlock(); + break; + } + + default: + Warning( "Bad field type\n" ); + Assert(0); + return false; + } + + return true; +} + +//------------------------------------- + +bool CSave::WriteField( const char *pname, void *pData, datamap_t *pRootMap, typedescription_t *pField ) +{ +#ifdef _DEBUG + Log( pname, (fieldtype_t)pField->fieldType, pData, pField->fieldSize ); +#endif + + if ( pField->fieldType <= FIELD_CUSTOM ) + { + return WriteBasicField( pname, pData, pRootMap, pField ); + } + return WriteGameField( pname, pData, pRootMap, pField ); +} + +//------------------------------------- + +int CSave::CountFieldsToSave( const void *pBaseData, typedescription_t *pFields, int fieldCount ) +{ + int result = 0; + for ( int i = 0; i < fieldCount; i++ ) + { + if ( ShouldSaveField( (char *)pBaseData + pFields[i].fieldOffset[ TD_OFFSET_NORMAL ], &pFields[i] ) ) + result++; + } + return result; +} + +//------------------------------------- + +int CSave::WriteFields( const char *pname, const void *pBaseData, datamap_t *pRootMap, typedescription_t *pFields, int fieldCount ) +{ + int i, actualCount; + typedescription_t *pTest; + + // Empty fields will not be written, write out the actual number of fields to be written + actualCount = CountFieldsToSave( pBaseData, pFields, fieldCount ); + WriteInt( pname, &actualCount, 1 ); + + for ( i = 0; i < fieldCount; i++ ) + { + pTest = &pFields[ i ]; + + void *pOutputData = ( (char *)pBaseData + pTest->fieldOffset[ TD_OFFSET_NORMAL ] ); + if ( !ShouldSaveField( pOutputData, pTest ) ) + continue; + + if ( !WriteField( pname, pOutputData, pRootMap, pTest ) ) + break; + } + + return 1; +} + +//------------------------------------- +// Purpose: Recursively saves all the classes in an object, in reverse order (top down) +// Output : int 0 on failure, 1 on success + +int CSave::DoWriteAll( const void *pLeafObject, datamap_t *pLeafMap, datamap_t *pCurMap ) +{ + // save base classes first + if ( pCurMap->baseMap ) + { + int status = DoWriteAll( pLeafObject, pLeafMap, pCurMap->baseMap ); + if ( !status ) + return status; + } + + return WriteFields( pCurMap->dataClassName, pLeafObject, pLeafMap, pCurMap->dataDesc, pCurMap->dataNumFields ); +} + +//------------------------------------- + +void CSave::StartBlock( const char *pszBlockName ) +{ + WriteHeader( pszBlockName, 0 ); // placeholder + m_BlockStartStack.AddToTail( GetWritePos() ); +} + +//------------------------------------- + +void CSave::StartBlock() +{ + StartBlock( "" ); +} + +//------------------------------------- + +void CSave::EndBlock() +{ + int endPos = GetWritePos(); + int startPos = m_BlockStartStack[ m_BlockStartStack.Count() - 1 ]; + short sizeBlock = endPos - startPos; + + m_BlockStartStack.Remove( m_BlockStartStack.Count() - 1 ); + + // Move to the the location where the size of the block was written & rewrite the size + SetWritePos( startPos - sizeof(SaveRestoreRecordHeader_t) ); + BufferData( (const char *)&sizeBlock, sizeof(short) ); + + SetWritePos( endPos ); +} + +//------------------------------------- + +void CSave::BufferString( char *pdata, int len ) +{ + char c = 0; + + BufferData( pdata, len ); // Write the string + BufferData( &c, 1 ); // Write a null terminator +} + +//------------------------------------- + +int CSave::DataEmpty( const char *pdata, int size ) +{ + for ( int i = 0; i < size; i++ ) + { + if ( pdata[i] ) + return 0; + } + return 1; +} + +//------------------------------------- + +void CSave::BufferField( const char *pname, int size, const char *pdata ) +{ + WriteHeader( pname, size ); + BufferData( pdata, size ); +} + +//------------------------------------- + +void CSave::WriteHeader( const char *pname, int size ) +{ + short shortSize = size; + short hashvalue = m_pData->FindCreateSymbol( pname ); + if ( size > SHRT_MAX || size < 0 ) + { + Warning( "CSave::WriteHeader() size parameter exceeds 'short'!\n" ); + Assert(0); + } + + BufferData( (const char *)&shortSize, sizeof(short) ); + BufferData( (const char *)&hashvalue, sizeof(short) ); +} + +//------------------------------------- + +void CSave::BufferData( const char *pdata, int size ) +{ + if ( !m_pData ) + return; + + if ( !m_pData->Write( pdata, size ) ) + { + Warning( "Save/Restore overflow!\n" ); + Assert(0); + } +} + +//--------------------------------------------------------- +// +// Game centric save methods. +// +int CSave::EntityIndex( const edict_t *pentLookup ) +{ +#if !defined( CLIENT_DLL ) + if ( pentLookup == NULL ) + return -1; + return EntityIndex( CBaseEntity::Instance(pentLookup) ); +#else + Assert( !"CSave::EntityIndex( edict_t * ) not valid on client!" ); + return -1; +#endif +} + + +//------------------------------------- + +int CSave::EntityIndex( const CBaseEntity *pEntity ) +{ + if ( !m_pGameInfo || pEntity == NULL ) + return -1; + + int i; + entitytable_t *pTable; + + for ( i = 0; i < m_pGameInfo->NumEntities(); i++ ) + { + pTable = m_pGameInfo->GetEntityInfo( i ); + if ( pTable->hEnt == pEntity ) + return pTable->id; + } + return -1; +} + +//------------------------------------- + +int CSave::EntityFlagsSet( int entityIndex, int flags ) +{ + if ( !m_pGameInfo || entityIndex < 0 ) + return 0; + if ( entityIndex > m_pGameInfo->NumEntities() ) + return 0; + + m_pGameInfo->GetEntityInfo( entityIndex )->flags |= flags; + + return m_pGameInfo->GetEntityInfo( entityIndex )->flags; +} + +//------------------------------------- + +void CSave::WriteTime( const char *pname, const float *data, int count ) +{ + int i; + float tmp; + + WriteHeader( pname, sizeof(float) * count ); + for ( i = 0; i < count; i++ ) + { + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + Assert( data[i] != ZERO_TIME ); + + if ( data[i] == 0.0 ) + { + tmp = ZERO_TIME; + } + else if ( data[i] == INVALID_TIME || data[i] == FLT_MAX ) + { + tmp = data[i]; + } + else + { + tmp = data[i] - m_pGameInfo->GetBaseTime(); + if ( fabsf( tmp ) < 0.001 ) // never allow a time to become zero due to rebasing + tmp = 0.001; + } + + WriteData( (const char *)&tmp, sizeof(float) ); + } +} + +//------------------------------------- + +void CSave::WriteTime( const float *data, int count ) +{ + int i; + float tmp; + + for ( i = 0; i < count; i++ ) + { + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + if ( data[i] == 0.0 ) + { + tmp = ZERO_TIME; + } + else if ( data[i] == INVALID_TIME || data[i] == FLT_MAX ) + { + tmp = data[i]; + } + else + { + tmp = data[i] - m_pGameInfo->GetBaseTime(); + if ( fabsf( tmp ) < 0.001 ) // never allow a time to become zero due to rebasing + tmp = 0.001; + } + + WriteData( (const char *)&tmp, sizeof(float) ); + } +} + +void CSave::WriteTick( const char *pname, const int *data, int count ) +{ + WriteHeader( pname, sizeof(int) * count ); + WriteTick( data, count ); +} + +//------------------------------------- + +void CSave::WriteTick( const int *data, int count ) +{ + int i; + int tmp; + + int baseTick = TIME_TO_TICKS( m_pGameInfo->GetBaseTime() ); + + for ( i = 0; i < count; i++ ) + { + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + tmp = data[ i ]; + if ( data[ i ] == TICK_NEVER_THINK ) + { + tmp = TICK_NEVER_THINK_ENCODE; + } + else + { + // Rebase it... + tmp -= baseTick; + } + WriteData( (const char *)&tmp, sizeof(int) ); + } +} +//------------------------------------- + +void CSave::WritePositionVector( const char *pname, const Vector &value ) +{ + Vector tmp = value; + + if ( tmp != vec3_invalid ) + tmp -= m_pGameInfo->GetLandmark(); + + WriteVector( pname, tmp ); +} + +//------------------------------------- + +void CSave::WritePositionVector( const Vector &value ) +{ + Vector tmp = value; + + if ( tmp != vec3_invalid ) + tmp -= m_pGameInfo->GetLandmark(); + + WriteVector( tmp ); +} + +//------------------------------------- + +void CSave::WritePositionVector( const char *pname, const Vector *value, int count ) +{ + WriteHeader( pname, sizeof(Vector) * count ); + WritePositionVector( value, count ); +} + +//------------------------------------- + +void CSave::WritePositionVector( const Vector *value, int count ) +{ + int i; + Vector tmp; + for ( i = 0; i < count; i++ ) + { + Vector tmp = value[i]; + + if ( tmp != vec3_invalid ) + tmp -= m_pGameInfo->GetLandmark(); + + WriteData( (const char *)&tmp.x, sizeof(Vector) ); + } +} + +//------------------------------------- + +void CSave::WriteFunction( datamap_t *pRootMap, const char *pname, const int *data, int count ) +{ + AssertMsg( count == 1, "Arrays of functions not presently supported" ); + const char *functionName = UTIL_FunctionToName( pRootMap, (void *)(*data) ); + + if ( functionName ) + { + BufferField( pname, strlen(functionName) + 1, functionName ); + } + else + { + Warning( "Invalid function pointer in entity!\n" ); + Assert(0); + } +} + +//------------------------------------- + +void CSave::WriteEntityPtr( const char *pname, CBaseEntity **ppEntity, int count ) +{ + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) + { + entityArray[i] = EntityIndex( ppEntity[i] ); + } + WriteInt( pname, entityArray, count ); +} + +//------------------------------------- + +void CSave::WriteEntityPtr( CBaseEntity **ppEntity, int count ) +{ + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) + { + entityArray[i] = EntityIndex( ppEntity[i] ); + } + WriteInt( entityArray, count ); +} + +//------------------------------------- + +void CSave::WriteEdictPtr( const char *pname, edict_t **ppEdict, int count ) +{ + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) + { + entityArray[i] = EntityIndex( ppEdict[i] ); + } + WriteInt( pname, entityArray, count ); +} + +//------------------------------------- + +void CSave::WriteEdictPtr( edict_t **ppEdict, int count ) +{ + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) + { + entityArray[i] = EntityIndex( ppEdict[i] ); + } + WriteInt( entityArray, count ); +} + +//------------------------------------- + +void CSave::WriteEHandle( const char *pname, const EHANDLE *pEHandle, int count ) +{ + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) + { + entityArray[i] = EntityIndex( (CBaseEntity *)(const_cast(pEHandle)[i]) ); + } + WriteInt( pname, entityArray, count ); +} + +//------------------------------------- + +void CSave::WriteEHandle( const EHANDLE *pEHandle, int count ) +{ + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + for ( int i = 0; i < count && i < MAX_ENTITYARRAY; i++ ) + { + entityArray[i] = EntityIndex( (CBaseEntity *)(const_cast(pEHandle)[i]) ); + } + WriteInt( entityArray, count ); +} + +//------------------------------------- +// Purpose: Writes all the fields that are not client neutral. In the event of +// a librarization of save/restore, these would not reside in the library + +bool CSave::WriteGameField( const char *pname, void *pData, datamap_t *pRootMap, typedescription_t *pField ) +{ + switch( pField->fieldType ) + { + case FIELD_CLASSPTR: + WriteEntityPtr( pField->fieldName, (CBaseEntity **)pData, pField->fieldSize ); + break; + + case FIELD_EDICT: + WriteEdictPtr( pField->fieldName, (edict_t **)pData, pField->fieldSize ); + break; + + case FIELD_EHANDLE: + WriteEHandle( pField->fieldName, (EHANDLE *)pData, pField->fieldSize ); + break; + + case FIELD_POSITION_VECTOR: + WritePositionVector( pField->fieldName, (Vector *)pData, pField->fieldSize ); + break; + + case FIELD_TIME: + WriteTime( pField->fieldName, (float *)pData, pField->fieldSize ); + break; + + case FIELD_TICK: + WriteTick( pField->fieldName, (int *)pData, pField->fieldSize ); + break; + + case FIELD_MODELINDEX: + { + int nModelIndex = *(int*)pData; + string_t strModelName = NULL_STRING; + const model_t *pModel = modelinfo->GetModel( nModelIndex ); + if ( pModel ) + { + strModelName = AllocPooledString( modelinfo->GetModelName( pModel ) ); + } + WriteString( pField->fieldName, (string_t *)&strModelName, pField->fieldSize ); + } + break; + + case FIELD_MATERIALINDEX: + { + int nMateralIndex = *(int*)pData; + string_t strMaterialName = NULL_STRING; + const char *pMaterialName = GetMaterialNameFromIndex( nMateralIndex ); + if ( pMaterialName ) + { + strMaterialName = MAKE_STRING( pMaterialName ); + } + WriteString( pField->fieldName, (string_t *)&strMaterialName, pField->fieldSize ); + } + break; + + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + WriteString( pField->fieldName, (string_t *)pData, pField->fieldSize ); + break; + + // For now, just write the address out, we're not going to change memory while doing this yet! + case FIELD_FUNCTION: + WriteFunction( pRootMap, pField->fieldName, (int *)(char *)pData, pField->fieldSize ); + break; + + case FIELD_VMATRIX: + WriteVMatrix( pField->fieldName, (VMatrix *)pData, pField->fieldSize ); + break; + case FIELD_VMATRIX_WORLDSPACE: + WriteVMatrixWorldspace( pField->fieldName, (VMatrix *)pData, pField->fieldSize ); + break; + + case FIELD_MATRIX3X4_WORLDSPACE: + WriteMatrix3x4Worldspace( pField->fieldName, (const matrix3x4_t *)pData, pField->fieldSize ); + break; + + case FIELD_INTERVAL: + WriteInterval( pField->fieldName, (interval_t *)pData, pField->fieldSize ); + break; + + default: + Warning( "Bad field type\n" ); + Assert(0); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// +// CRestore +// +//----------------------------------------------------------------------------- + +CRestore::CRestore( CSaveRestoreData *pdata ) + : m_pData( pdata ), + m_pGameInfo( pdata ), + m_global( 0 ), + m_precache( true ) +{ + m_BlockEndStack.EnsureCapacity( 32 ); +} + +//------------------------------------- + +int CRestore::GetReadPos() const +{ + return m_pData->GetCurPos(); +} + +//------------------------------------- + +void CRestore::SetReadPos( int pos ) +{ + m_pData->Seek(pos); +} + +//------------------------------------- + +const char *CRestore::StringFromHeaderSymbol( int symbol ) +{ + const char *pszResult = m_pData->StringFromSymbol( symbol ); + return ( pszResult ) ? pszResult : ""; +} + +//------------------------------------- +// Purpose: Reads all the fields that are client neutral. In the event of +// a librarization of save/restore, these would reside in the library + +void CRestore::ReadBasicField( const SaveRestoreRecordHeader_t &header, void *pDest, datamap_t *pRootMap, typedescription_t *pField ) +{ + switch( pField->fieldType ) + { + case FIELD_FLOAT: + { + ReadFloat( (float *)pDest, pField->fieldSize, header.size ); + break; + } + case FIELD_STRING: + { + ReadString( (string_t *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_VECTOR: + { + ReadVector( (Vector *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_QUATERNION: + { + ReadQuaternion( (Quaternion *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_INTEGER: + { + ReadInt( (int *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_BOOLEAN: + { + ReadBool( (bool *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_SHORT: + { + ReadShort( (short *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_CHARACTER: + { + ReadData( (char *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_COLOR32: + { + COMPILE_TIME_ASSERT( sizeof(color32) == sizeof(int) ); + ReadInt( (int *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_EMBEDDED: + { + AssertMsg( (( pField->flags & FTYPEDESC_PTR ) == 0) || (pField->fieldSize == 1), "Arrays of embedded pointer types presently unsupported by save/restore" ); +#ifdef _DEBUG + int startPos = GetReadPos(); +#endif + if ( !(pField->flags & FTYPEDESC_PTR) || *((void **)pDest) ) + { + int nFieldCount = pField->fieldSize; + char *pFieldData = (char *)( ( !(pField->flags & FTYPEDESC_PTR) ) ? pDest : *((void **)pDest) ); + while ( --nFieldCount >= 0 ) + { + // No corresponding "block" (see write) as it was used as the header of the field + ReadAll( pFieldData, pField->td ); + pFieldData += pField->fieldSizeInBytes; + } + Assert( GetReadPos() - startPos == header.size ); + } + else + { + SetReadPos( GetReadPos() + header.size ); + Warning( "Attempted to restore FIELD_EMBEDDEDBYREF %s but there is no destination memory\n", pField->fieldName ); + } + break; + + } + case FIELD_CUSTOM: + { + // No corresponding "block" (see write) as it was used as the header of the field + int posNextField = GetReadPos() + header.size; + + SaveRestoreFieldInfo_t fieldInfo = + { + pDest, + ((char *)pDest) - pField->fieldOffset[ TD_OFFSET_NORMAL ], + pField + }; + + pField->pSaveRestoreOps->Restore( fieldInfo, this ); + + Assert( posNextField >= GetReadPos() ); + SetReadPos( posNextField ); + break; + } + + default: + Warning( "Bad field type\n" ); + Assert(0); + } +} + +//------------------------------------- + +void CRestore::ReadField( const SaveRestoreRecordHeader_t &header, void *pDest, datamap_t *pRootMap, typedescription_t *pField ) +{ + if ( pField->fieldType <= FIELD_CUSTOM ) + ReadBasicField( header, pDest, pRootMap, pField ); + else + ReadGameField( header, pDest, pRootMap, pField ); +} + +//------------------------------------- + +bool CRestore::ShouldReadField( typedescription_t *pField ) +{ + if ( (pField->flags & FTYPEDESC_SAVE) == 0 ) + return false; + + if ( m_global && (pField->flags & FTYPEDESC_GLOBAL) ) + return false; + + return true; +} + +//------------------------------------- + +typedescription_t *CRestore::FindField( const char *pszFieldName, typedescription_t *pFields, int fieldCount, int *pCookie ) +{ + int &fieldNumber = *pCookie; + if ( pszFieldName ) + { + typedescription_t *pTest; + + for ( int i = 0; i < fieldCount; i++ ) + { + pTest = &pFields[fieldNumber]; + + ++fieldNumber; + if ( fieldNumber == fieldCount ) + fieldNumber = 0; + + if ( stricmp( pTest->fieldName, pszFieldName ) == 0 ) + return pTest; + } + } + + fieldNumber = 0; + return NULL; +} + +//------------------------------------- + +bool CRestore::ShouldEmptyField( typedescription_t *pField ) +{ + // don't clear out fields that don't get saved, or that are handled specially + if ( !( pField->flags & FTYPEDESC_SAVE ) ) + return false; + + // Don't clear global fields + if ( m_global && (pField->flags & FTYPEDESC_GLOBAL) ) + return false; + + return true; +} + +//------------------------------------- + +void CRestore::EmptyFields( void *pBaseData, typedescription_t *pFields, int fieldCount ) +{ + int i; + for ( i = 0; i < fieldCount; i++ ) + { + typedescription_t *pField = &pFields[i]; + if ( !ShouldEmptyField( pField ) ) + continue; + + void *pFieldData = (char *)pBaseData + pField->fieldOffset[ TD_OFFSET_NORMAL ]; + switch( pField->fieldType ) + { + case FIELD_CUSTOM: + { + SaveRestoreFieldInfo_t fieldInfo = + { + pFieldData, + pBaseData, + pField + }; + pField->pSaveRestoreOps->MakeEmpty( fieldInfo ); + } + break; + + case FIELD_EMBEDDED: + { + if ( (pField->flags & FTYPEDESC_PTR) && !*((void **)pFieldData) ) + break; + + int nFieldCount = pField->fieldSize; + char *pFieldMemory = (char *)( ( !(pField->flags & FTYPEDESC_PTR) ) ? pFieldData : *((void **)pFieldData) ); + while ( --nFieldCount >= 0 ) + { + EmptyFields( pFieldMemory, pField->td->dataDesc, pField->td->dataNumFields ); + pFieldMemory += pField->fieldSizeInBytes; + } + } + break; + + default: + // NOTE: If you hit this assertion, you've got a bug where you're using + // the wrong field type for your field + if ( pField->fieldSizeInBytes != pField->fieldSize * gSizes[pField->fieldType] ) + { + Warning("WARNING! Field %s is using the wrong FIELD_ type!\nFix this or you'll see a crash.\n", pField->fieldName ); + Assert( 0 ); + } + memset( pFieldData, (pField->fieldType != FIELD_EHANDLE) ? 0 : 0xFF, pField->fieldSize * gSizes[pField->fieldType] ); + break; + } + } +} + +//------------------------------------- + +void CRestore::StartBlock( SaveRestoreRecordHeader_t *pHeader ) +{ + ReadHeader( pHeader ); + m_BlockEndStack.AddToTail( GetReadPos() + pHeader->size ); +} + +//------------------------------------- + +void CRestore::StartBlock( char szBlockName[] ) +{ + SaveRestoreRecordHeader_t header; + StartBlock( &header ); + Q_strncpy( szBlockName, StringFromHeaderSymbol( header.symbol ), SIZE_BLOCK_NAME_BUF ); +} + +//------------------------------------- + +void CRestore::StartBlock() +{ + char szBlockName[SIZE_BLOCK_NAME_BUF]; + StartBlock( szBlockName ); +} + +//------------------------------------- + +void CRestore::EndBlock() +{ + int endPos = m_BlockEndStack[ m_BlockEndStack.Count() - 1 ]; + m_BlockEndStack.Remove( m_BlockEndStack.Count() - 1 ); + SetReadPos( endPos ); +} + +//------------------------------------- + +int CRestore::ReadFields( const char *pname, void *pBaseData, datamap_t *pRootMap, typedescription_t *pFields, int fieldCount ) +{ + static int lastName = -1; + Verify( ReadShort() == sizeof(int) ); // First entry should be an int + int symName = m_pData->FindCreateSymbol(pname); + + // Check the struct name + int curSym = ReadShort(); + if ( curSym != symName ) // Field Set marker + { + const char *pLastName = m_pData->StringFromSymbol( lastName ); + const char *pCurName = m_pData->StringFromSymbol( curSym ); + Msg( "Expected %s found %s ( raw '%s' )! (prev: %s)\n", pname, pCurName, BufferPointer(), pLastName ); + Msg( "Field type name may have changed or inheritance graph changed, save file is suspect\n" ); + m_pData->Rewind( 2*sizeof(short) ); + return 0; + } + lastName = symName; + + // Clear out base data + EmptyFields( pBaseData, pFields, fieldCount ); + + // Skip over the struct name + int i; + int nFieldsSaved = ReadInt(); // Read field count + int searchCookie = 0; // Make searches faster, most data is read/written in the same order + SaveRestoreRecordHeader_t header; + + for ( i = 0; i < nFieldsSaved; i++ ) + { + ReadHeader( &header ); + + typedescription_t *pField = FindField( m_pData->StringFromSymbol( header.symbol ), pFields, fieldCount, &searchCookie); + if ( pField && ShouldReadField( pField ) ) + { + ReadField( header, ((char *)pBaseData + pField->fieldOffset[ TD_OFFSET_NORMAL ]), pRootMap, pField ); + } + else + { + BufferSkipBytes( header.size ); // Advance to next field + } + } + + return 1; +} + +//------------------------------------- + +void CRestore::ReadHeader( SaveRestoreRecordHeader_t *pheader ) +{ + if ( pheader != NULL ) + { + Assert( pheader!=NULL ); + pheader->size = ReadShort(); // Read field size + pheader->symbol = ReadShort(); // Read field name token + } + else + { + BufferSkipBytes( sizeof(short) * 2 ); + } +} + +//------------------------------------- + +short CRestore::ReadShort( void ) +{ + short tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(short) ); + + return tmp; +} + +//------------------------------------- + +int CRestore::ReadInt( void ) +{ + int tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(int) ); + + return tmp; +} + +//------------------------------------- +// Purpose: Recursively restores all the classes in an object, in reverse order (top down) +// Output : int 0 on failure, 1 on success + +int CRestore::DoReadAll( void *pLeafObject, datamap_t *pLeafMap, datamap_t *pCurMap ) +{ + // restore base classes first + if ( pCurMap->baseMap ) + { + int status = DoReadAll( pLeafObject, pLeafMap, pCurMap->baseMap ); + if ( !status ) + return status; + } + + return ReadFields( pCurMap->dataClassName, pLeafObject, pLeafMap, pCurMap->dataDesc, pCurMap->dataNumFields ); +} + +//------------------------------------- + +char *CRestore::BufferPointer( void ) +{ + if ( !m_pData ) + return NULL; + + return m_pData->AccessCurPos(); +} + +//------------------------------------- + +void CRestore::BufferReadBytes( char *pOutput, int size ) +{ + Assert( m_pData !=NULL ); + + if ( !m_pData || m_pData->BytesAvailable() == 0 ) + return; + + if ( !m_pData->Read( pOutput, size ) ) + { + Warning( "Restore underflow!\n" ); + Assert(0); + } +} + +//------------------------------------- + +void CRestore::BufferSkipBytes( int bytes ) +{ + BufferReadBytes( NULL, bytes ); +} + +//------------------------------------- + +int CRestore::ReadShort( short *pValue, int nElems, int nBytesAvailable ) +{ + return ReadSimple( pValue, nElems, nBytesAvailable ); +} + +//------------------------------------- + +int CRestore::ReadInt( int *pValue, int nElems, int nBytesAvailable ) +{ + return ReadSimple( pValue, nElems, nBytesAvailable ); +} + +//------------------------------------- + +int CRestore::ReadBool( bool *pValue, int nElems, int nBytesAvailable ) +{ + COMPILE_TIME_ASSERT( sizeof(bool) == sizeof(char) ); + return ReadSimple( pValue, nElems, nBytesAvailable ); +} + +//------------------------------------- + +int CRestore::ReadFloat( float *pValue, int nElems, int nBytesAvailable ) +{ + return ReadSimple( pValue, nElems, nBytesAvailable ); +} + +//------------------------------------- + +int CRestore::ReadData( char *pData, int size, int nBytesAvailable ) +{ + return ReadSimple( pData, size, nBytesAvailable ); +} + +//------------------------------------- + +void CRestore::ReadString( char *pDest, int nSizeDest, int nBytesAvailable ) +{ + const char *pString = BufferPointer(); + if ( !nBytesAvailable ) + nBytesAvailable = strlen( pString ) + 1; + BufferSkipBytes( nBytesAvailable ); + + Q_strncpy(pDest, pString, nSizeDest ); +} + +//------------------------------------- + +int CRestore::ReadString( string_t *pValue, int nElems, int nBytesAvailable ) +{ + AssertMsg( nBytesAvailable > 0, "CRestore::ReadString() implementation does not currently support unspecified bytes available"); + + int i; + char *pString = BufferPointer(); + char *pLimit = pString + nBytesAvailable; + for ( i = 0; i < nElems && pString < pLimit; i++ ) + { + if ( *((char *)pString) == 0 ) + pValue[i] = NULL_STRING; + else + pValue[i] = AllocPooledString( (char *)pString ); + + while (*pString) + pString++; + pString++; + } + + BufferSkipBytes( nBytesAvailable ); + + return i; +} + +//------------------------------------- + +int CRestore::ReadVector( Vector *pValue) +{ + BufferReadBytes( (char *)pValue, sizeof(Vector) ); + return 1; +} + +//------------------------------------- + +int CRestore::ReadVector( Vector *pValue, int nElems, int nBytesAvailable ) +{ + return ReadSimple( pValue, nElems, nBytesAvailable ); +} + +int CRestore::ReadQuaternion( Quaternion *pValue) +{ + BufferReadBytes( (char *)pValue, sizeof(Quaternion) ); + return 1; +} + +//------------------------------------- + +int CRestore::ReadQuaternion( Quaternion *pValue, int nElems, int nBytesAvailable ) +{ + return ReadSimple( pValue, nElems, nBytesAvailable ); +} + +//------------------------------------- +int CRestore::ReadVMatrix( VMatrix *pValue, int nElems, int nBytesAvailable ) +{ + return ReadSimple( pValue, nElems, nBytesAvailable ); +} + + +int CRestore::ReadVMatrixWorldspace( VMatrix *pValue, int nElems, int nBytesAvailable ) +{ + Vector basePosition = m_pGameInfo->GetLandmark(); + VMatrix tmp; + + for ( int i = 0; i < nElems; i++ ) + { + BufferReadBytes( (char *)&tmp, sizeof(float)*16 ); + + VMatrixOffset( pValue[i], tmp, basePosition ); + } + return nElems; +} + + +int CRestore::ReadMatrix3x4Worldspace( matrix3x4_t *pValue, int nElems, int nBytesAvailable ) +{ + Vector basePosition = m_pGameInfo->GetLandmark(); + matrix3x4_t tmp; + + for ( int i = 0; i < nElems; i++ ) + { + BufferReadBytes( (char *)&tmp, sizeof(matrix3x4_t) ); + + Matrix3x4Offset( pValue[i], tmp, basePosition ); + } + return nElems; +} + +int CRestore::ReadInterval( interval_t *interval, int count, int nBytesAvailable ) +{ + return ReadSimple( interval, count, nBytesAvailable ); +} + +//--------------------------------------------------------- +// +// Game centric restore methods +// + +CBaseEntity *CRestore::EntityFromIndex( int entityIndex ) +{ + if ( !m_pGameInfo || entityIndex < 0 ) + return NULL; + + int i; + entitytable_t *pTable; + + for ( i = 0; i < m_pGameInfo->NumEntities(); i++ ) + { + pTable = m_pGameInfo->GetEntityInfo( i ); + if ( pTable->id == entityIndex ) + return pTable->hEnt; + } + return NULL; +} + +//------------------------------------- + +int CRestore::ReadEntityPtr( CBaseEntity **ppEntity, int count, int nBytesAvailable ) +{ + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + + int nRead = ReadInt( entityArray, count, nBytesAvailable ); + + for ( int i = 0; i < nRead; i++ ) // nRead is never greater than count + { + ppEntity[i] = EntityFromIndex( entityArray[i] ); + } + + if ( nRead < count) + { + memset( &ppEntity[nRead], 0, ( count - nRead ) * sizeof(ppEntity[0]) ); + } + + return nRead; +} + +//------------------------------------- +int CRestore::ReadEdictPtr( edict_t **ppEdict, int count, int nBytesAvailable ) +{ +#if !defined( CLIENT_DLL ) + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + CBaseEntity *pEntity; + + int nRead = ReadInt( entityArray, count, nBytesAvailable ); + + for ( int i = 0; i < nRead; i++ ) // nRead is never greater than count + { + pEntity = EntityFromIndex( entityArray[i] ); + ppEdict[i] = (pEntity) ? pEntity->edict() : NULL; + } + + if ( nRead < count) + { + memset( &ppEdict[nRead], 0, ( count - nRead ) * sizeof(ppEdict[0]) ); + } + + return nRead; +#else + return 0; +#endif +} + + +//------------------------------------- + +int CRestore::ReadEHandle( EHANDLE *pEHandle, int count, int nBytesAvailable ) +{ + AssertMsg( count <= MAX_ENTITYARRAY, "Array of entities or ehandles exceeds limit supported by save/restore" ); + int entityArray[MAX_ENTITYARRAY]; + + int nRead = ReadInt( entityArray, count, nBytesAvailable ); + + for ( int i = 0; i < nRead; i++ ) // nRead is never greater than count + { + pEHandle[i] = EntityFromIndex( entityArray[i] ); + } + + if ( nRead < count) + { + memset( &pEHandle[nRead], 0xFF, ( count - nRead ) * sizeof(pEHandle[0]) ); + } + + return nRead; +} + +//------------------------------------- +// Purpose: Reads all the fields that are not client neutral. In the event of +// a librarization of save/restore, these would NOT reside in the library + +void CRestore::ReadGameField( const SaveRestoreRecordHeader_t &header, void *pDest, datamap_t *pRootMap, typedescription_t *pField ) +{ + switch( pField->fieldType ) + { + case FIELD_POSITION_VECTOR: + { + ReadPositionVector( (Vector *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_TIME: + { + ReadTime( (float *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_TICK: + { + ReadTick( (int *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_FUNCTION: + { + ReadFunction( pRootMap, (void **)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_MODELINDEX: + { + int *pModelIndex = (int*)pDest; + string_t *pModelName = (string_t *)stackalloc( pField->fieldSize * sizeof(string_t) ); + int nRead = ReadString( pModelName, pField->fieldSize, header.size ); + + for ( int i = 0; i < nRead; i++ ) + { + if ( pModelName[i] == NULL_STRING ) + { + pModelIndex[i] = -1; + continue; + } + + pModelIndex[i] = modelinfo->GetModelIndex( STRING( pModelName[i] ) ); + +#if !defined( CLIENT_DLL ) + if ( m_precache ) + { + CBaseEntity::PrecacheModel( STRING( pModelName[i] ) ); + } +#endif + } + break; + } + + case FIELD_MATERIALINDEX: + { + int *pMaterialIndex = (int*)pDest; + string_t *pMaterialName = (string_t *)stackalloc( pField->fieldSize * sizeof(string_t) ); + int nRead = ReadString( pMaterialName, pField->fieldSize, header.size ); + + for ( int i = 0; i < nRead; i++ ) + { + if ( pMaterialName[i] == NULL_STRING ) + { + pMaterialIndex[i] = 0; + continue; + } + + pMaterialIndex[i] = GetMaterialIndex( STRING( pMaterialName[i] ) ); + +#if !defined( CLIENT_DLL ) + if ( m_precache ) + { + PrecacheMaterial( STRING( pMaterialName[i] ) ); + } +#endif + } + break; + } + + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + { + string_t *pStringDest = (string_t *)pDest; + int nRead = ReadString( pStringDest, pField->fieldSize, header.size ); + if ( m_precache ) + { + for ( int i = 0; i < nRead; i++ ) + { + if ( pStringDest[i] != NULL_STRING ) + { +#if !defined( CLIENT_DLL ) + if ( pField->fieldType == FIELD_MODELNAME ) + { + CBaseEntity::PrecacheModel( STRING( pStringDest[i] ) ); + } + else if ( pField->fieldType == FIELD_SOUNDNAME ) + { + CBaseEntity::PrecacheScriptSound( STRING( pStringDest[i] ) ); + } +#endif + } + } + } + break; + } + + case FIELD_CLASSPTR: + ReadEntityPtr( (CBaseEntity **)pDest, pField->fieldSize, header.size ); + break; + + case FIELD_EDICT: +#if !defined( CLIENT_DLL ) + ReadEdictPtr( (edict_t **)pDest, pField->fieldSize, header.size ); +#else + Assert( !"FIELD_EDICT not valid for client .dll" ); +#endif + break; + case FIELD_EHANDLE: + ReadEHandle( (EHANDLE *)pDest, pField->fieldSize, header.size ); + break; + + case FIELD_VMATRIX: + { + ReadVMatrix( (VMatrix *)pDest, pField->fieldSize, header.size ); + break; + } + + case FIELD_VMATRIX_WORLDSPACE: + ReadVMatrixWorldspace( (VMatrix *)pDest, pField->fieldSize, header.size ); + break; + + case FIELD_MATRIX3X4_WORLDSPACE: + ReadMatrix3x4Worldspace( (matrix3x4_t *)pDest, pField->fieldSize, header.size ); + break; + + case FIELD_INTERVAL: + ReadInterval( (interval_t *)pDest, pField->fieldSize, header.size ); + break; + + default: + Warning( "Bad field type\n" ); + Assert(0); + } +} + +//------------------------------------- + +int CRestore::ReadTime( float *pValue, int count, int nBytesAvailable ) +{ + float baseTime = m_pGameInfo->GetBaseTime(); + int nRead = ReadFloat( pValue, count, nBytesAvailable ); + + for ( int i = nRead - 1; i >= 0; i-- ) + { + if ( pValue[i] == ZERO_TIME ) + pValue[i] = 0.0; + else if ( pValue[i] != INVALID_TIME && pValue[i] != FLT_MAX ) + pValue[i] += baseTime; + } + + return nRead; +} + +int CRestore::ReadTick( int *pValue, int count, int nBytesAvailable ) +{ + // HACK HACK: Adding 0.1f here makes sure that all tick times read + // from .sav file which are near the basetime will end up just ahead of + // the base time, because we are restoring we'll have a slow frame of the + // max frametime of 0.1 seconds and that could otherwise cause all of our + // think times to get synchronized to each other... sigh. ywb... + int baseTick = TIME_TO_TICKS( m_pGameInfo->GetBaseTime() + 0.1f ); + int nRead = ReadInt( pValue, count, nBytesAvailable ); + + for ( int i = nRead - 1; i >= 0; i-- ) + { + if ( pValue[ i ] != TICK_NEVER_THINK_ENCODE ) + { + // Rebase it + pValue[i] += baseTick; + } + else + { + // Slam to -1 value + pValue[ i ] = TICK_NEVER_THINK; + } + } + + return nRead; +} + +//------------------------------------- + +int CRestore::ReadPositionVector( Vector *pValue ) +{ + return ReadPositionVector( pValue, 1, sizeof(Vector) ); +} + +//------------------------------------- + +int CRestore::ReadPositionVector( Vector *pValue, int count, int nBytesAvailable ) +{ + Vector basePosition = m_pGameInfo->GetLandmark(); + int nRead = ReadVector( pValue, count, nBytesAvailable ); + + for ( int i = nRead - 1; i >= 0; i-- ) + { + if ( pValue[i] != vec3_invalid ) + pValue[i] += basePosition; + } + + return nRead; +} + +//------------------------------------- + +int CRestore::ReadFunction( datamap_t *pMap, void **pValue, int count, int nBytesAvailable ) +{ + AssertMsg( nBytesAvailable > 0, "CRestore::ReadFunction() implementation does not currently support unspecified bytes available"); + + char *pszFunctionName = BufferPointer(); + BufferSkipBytes( nBytesAvailable ); + + AssertMsg( count == 1, "Arrays of functions not presently supported" ); + + if ( *pszFunctionName == 0 ) + *pValue = NULL; + else + *pValue = UTIL_FunctionFromName( pMap, pszFunctionName ); + + return 0; +} + +//----------------------------------------------------------------------------- +// +// Entity data saving routines +// +//----------------------------------------------------------------------------- + +BEGIN_SIMPLE_DATADESC(entitytable_t) + DEFINE_FIELD( id, FIELD_INTEGER ), + DEFINE_FIELD( edictindex, FIELD_INTEGER ), + DEFINE_FIELD( saveentityindex, FIELD_INTEGER ), +// DEFINE_FIELD( restoreentityindex, FIELD_INTEGER ), + // hEnt (not saved, this is the fixup) + DEFINE_FIELD( location, FIELD_INTEGER ), + DEFINE_FIELD( size, FIELD_INTEGER ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( classname, FIELD_STRING ), + DEFINE_FIELD( globalname, FIELD_STRING ), + DEFINE_FIELD( landmarkModelSpace, FIELD_VECTOR ), + DEFINE_FIELD( modelname, FIELD_STRING ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Utilities entities can use when saving +//----------------------------------------------------------------------------- +class CEntitySaveUtils : public IEntitySaveUtils +{ +public: + // Call these in pre-save + post save + void PreSave(); + void PostSave(); + + // Methods of IEntitySaveUtils + virtual void AddLevelTransitionSaveDependency( CBaseEntity *pEntity1, CBaseEntity *pEntity2 ); + virtual int GetEntityDependencyCount( CBaseEntity *pEntity ); + virtual int GetEntityDependencies( CBaseEntity *pEntity, int nCount, CBaseEntity **ppEntList ); + +private: + IPhysicsObjectPairHash *m_pLevelAdjacencyDependencyHash; +}; + + +//----------------------------------------------------------------------------- +// Call these in pre-save + post save +//----------------------------------------------------------------------------- +void CEntitySaveUtils::PreSave() +{ + Assert( !m_pLevelAdjacencyDependencyHash ); + MEM_ALLOC_CREDIT(); + m_pLevelAdjacencyDependencyHash = physics->CreateObjectPairHash(); +} + +void CEntitySaveUtils::PostSave() +{ + physics->DestroyObjectPairHash( m_pLevelAdjacencyDependencyHash ); + m_pLevelAdjacencyDependencyHash = NULL; +} + + +//----------------------------------------------------------------------------- +// Gets the # of dependencies for a particular entity +//----------------------------------------------------------------------------- +int CEntitySaveUtils::GetEntityDependencyCount( CBaseEntity *pEntity ) +{ + return m_pLevelAdjacencyDependencyHash->GetPairCountForObject( pEntity ); +} + + +//----------------------------------------------------------------------------- +// Gets all dependencies for a particular entity +//----------------------------------------------------------------------------- +int CEntitySaveUtils::GetEntityDependencies( CBaseEntity *pEntity, int nCount, CBaseEntity **ppEntList ) +{ + return m_pLevelAdjacencyDependencyHash->GetPairListForObject( pEntity, nCount, (void**)ppEntList ); +} + + +//----------------------------------------------------------------------------- +// Methods of IEntitySaveUtils +//----------------------------------------------------------------------------- +void CEntitySaveUtils::AddLevelTransitionSaveDependency( CBaseEntity *pEntity1, CBaseEntity *pEntity2 ) +{ + if ( pEntity1 != pEntity2 ) + { + m_pLevelAdjacencyDependencyHash->AddObjectPair( pEntity1, pEntity2 ); + } +} + + +//----------------------------------------------------------------------------- +// Block handler for save/restore of entities +//----------------------------------------------------------------------------- +class CEntitySaveRestoreBlockHandler : public ISaveRestoreBlockHandler +{ +public: + const char *GetBlockName(); + void PreSave( CSaveRestoreData *pSaveData ); + void Save( ISave *pSave ); + void WriteSaveHeaders( ISave *pSave ); + virtual void PostSave(); + virtual void PreRestore(); + void ReadRestoreHeaders( IRestore *pRestore ); + + void Restore( IRestore *pRestore, bool createPlayers ); + virtual void PostRestore(); + + inline IEntitySaveUtils * GetEntitySaveUtils() { return &m_EntitySaveUtils; } + +private: + friend int CreateEntityTransitionList( CSaveRestoreData *pSaveData, int levelMask ); + bool SaveInitEntities( CSaveRestoreData *pSaveData ); + bool DoRestoreEntity( CBaseEntity *pEntity, IRestore *pRestore ); + Vector ModelSpaceLandmark( int modelIndex ); + int RestoreEntity( CBaseEntity *pEntity, IRestore *pRestore, entitytable_t *pEntInfo ); + +#if !defined( CLIENT_DLL ) + // Find the matching global entity. Spit out an error if the designer made entities of + // different classes with the same global name + CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ); + + int RestoreGlobalEntity( CBaseEntity *pEntity, CSaveRestoreData *pSaveData, entitytable_t *pEntInfo ); +#endif + +private: + CEntitySaveUtils m_EntitySaveUtils; +}; + + +//----------------------------------------------------------------------------- + +CEntitySaveRestoreBlockHandler g_EntitySaveRestoreBlockHandler; + +//------------------------------------- + +ISaveRestoreBlockHandler *GetEntitySaveRestoreBlockHandler() +{ + return &g_EntitySaveRestoreBlockHandler; +} + +IEntitySaveUtils *GetEntitySaveUtils() +{ + return g_EntitySaveRestoreBlockHandler.GetEntitySaveUtils(); +} + + +//----------------------------------------------------------------------------- +// Implementation of the block handler for save/restore of entities +//----------------------------------------------------------------------------- +const char *CEntitySaveRestoreBlockHandler::GetBlockName() +{ + return "Entities"; +} + +//--------------------------------- + +void CEntitySaveRestoreBlockHandler::PreSave( CSaveRestoreData *pSaveData ) +{ + IGameSystem::OnSaveAllSystems(); + + m_EntitySaveUtils.PreSave(); + + // Allow the entities to do some work + CBaseEntity *pEnt = NULL; +#if !defined( CLIENT_DLL ) + while ( (pEnt = gEntList.NextEnt( pEnt )) != NULL ) + { + pEnt->OnSave( &m_EntitySaveUtils ); + } +#else + // Do this because it'll force entities to figure out their origins, and that requires + // SetupBones in the case of aiments. + C_BaseAnimating::PushAllowBoneAccess( true, true ); + + int last = ClientEntityList().GetHighestEntityIndex(); + ClientEntityHandle_t iter = ClientEntityList().FirstHandle(); + + for ( int e = 0; e <= last; e++ ) + { + pEnt = ClientEntityList().GetBaseEntity( e ); + + if( !pEnt ) + continue; + + pEnt->OnSave(); + } + + while ( iter != ClientEntityList().InvalidHandle() ) + { + pEnt = ClientEntityList().GetBaseEntityFromHandle( iter ); + + if ( pEnt && pEnt->ObjectCaps() & FCAP_SAVE_NON_NETWORKABLE ) + { + pEnt->OnSave(); + } + + iter = ClientEntityList().NextHandle( iter ); + } + + + C_BaseAnimating::PopBoneAccess(); +#endif + SaveInitEntities( pSaveData ); +} + +//--------------------------------- + +void CEntitySaveRestoreBlockHandler::Save( ISave *pSave ) +{ + CGameSaveRestoreInfo *pSaveData = pSave->GetGameSaveRestoreInfo(); + + // write entity list that was previously built by SaveInitEntities() + for ( int i = 0; i < pSaveData->NumEntities(); i++ ) + { + entitytable_t *pEntInfo = pSaveData->GetEntityInfo( i ); + pEntInfo->location = pSave->GetWritePos(); + pEntInfo->size = 0; + + CBaseEntity *pEnt = pEntInfo->hEnt; + if ( pEnt && !( pEnt->ObjectCaps() & FCAP_DONT_SAVE ) ) + { + MDLCACHE_CRITICAL_SECTION(); +#if !defined( CLIENT_DLL ) + AssertMsg( !pEnt->edict() || ( pEnt->m_iClassname != NULL_STRING && + (STRING(pEnt->m_iClassname)[0] != 0) && + FStrEq( STRING(pEnt->m_iClassname), pEnt->GetClassname()) ), + "Saving entity with invalid classname" ); +#endif + + pSaveData->SetCurrentEntityContext( pEnt ); + pEnt->Save( *pSave ); + pSaveData->SetCurrentEntityContext( NULL ); + + pEntInfo->size = pSave->GetWritePos() - pEntInfo->location; // Size of entity block is data size written to block + + pEntInfo->classname = pEnt->m_iClassname; // Remember entity class for respawn + +#if !defined( CLIENT_DLL ) + pEntInfo->globalname = pEnt->m_iGlobalname; // remember global name + pEntInfo->landmarkModelSpace = ModelSpaceLandmark( pEnt->GetModelIndex() ); + int nEntIndex = pEnt->edict() ? ENTINDEX(pEnt->edict()) : -1; + bool bIsPlayer = ( ( nEntIndex >= 1 ) && ( nEntIndex <= gpGlobals->maxClients ) ) ? true : false; + if ( bIsPlayer ) + { + pEntInfo->flags |= FENTTABLE_PLAYER; + } +#endif + } + } +} + +//--------------------------------- + +void CEntitySaveRestoreBlockHandler::WriteSaveHeaders( ISave *pSave ) +{ + CGameSaveRestoreInfo *pSaveData = pSave->GetGameSaveRestoreInfo(); + + int nEntities = pSaveData->NumEntities(); + pSave->WriteInt( &nEntities ); + + for ( int i = 0; i < pSaveData->NumEntities(); i++ ) + pSave->WriteFields( "ETABLE", pSaveData->GetEntityInfo( i ), NULL, entitytable_t::m_DataMap.dataDesc, entitytable_t::m_DataMap.dataNumFields ); +} + +//--------------------------------- + +void CEntitySaveRestoreBlockHandler::PostSave() +{ + m_EntitySaveUtils.PostSave(); +} + +//--------------------------------- + +void CEntitySaveRestoreBlockHandler::PreRestore() +{ +} + +//--------------------------------- + +void CEntitySaveRestoreBlockHandler::ReadRestoreHeaders( IRestore *pRestore ) +{ + CGameSaveRestoreInfo *pSaveData = pRestore->GetGameSaveRestoreInfo(); + + int nEntities; + pRestore->ReadInt( &nEntities ); + + entitytable_t *pEntityTable = ( entitytable_t *)engine->SaveAllocMemory( (sizeof(entitytable_t) * nEntities), sizeof(char) ); + + pSaveData->InitEntityTable( pEntityTable, nEntities ); + + for ( int i = 0; i < pSaveData->NumEntities(); i++ ) + pRestore->ReadFields( "ETABLE", pSaveData->GetEntityInfo( i ), NULL, entitytable_t::m_DataMap.dataDesc, entitytable_t::m_DataMap.dataNumFields ); + +} + +//--------------------------------- + +#if !defined( CLIENT_DLL ) + +void CEntitySaveRestoreBlockHandler::Restore( IRestore *pRestore, bool createPlayers ) +{ + entitytable_t *pEntInfo; + CBaseEntity *pent; + + CGameSaveRestoreInfo *pSaveData = pRestore->GetGameSaveRestoreInfo(); + + bool restoredWorld = false; + + // Create entity list + int i; + for ( i = 0; i < pSaveData->NumEntities(); i++ ) + { + pEntInfo = pSaveData->GetEntityInfo( i ); + + if ( pEntInfo->classname != NULL_STRING && pEntInfo->size && !(pEntInfo->flags & FENTTABLE_REMOVED) ) + { + if ( pEntInfo->edictindex == 0 ) // worldspawn + { + Assert( i == 0 ); + pent = CreateEntityByName( STRING(pEntInfo->classname) ); + pRestore->SetReadPos( pEntInfo->location ); + if ( RestoreEntity( pent, pRestore, pEntInfo ) < 0 ) + { + pEntInfo->hEnt = NULL; + pEntInfo->restoreentityindex = -1; + UTIL_RemoveImmediate( pent ); + } + else + { + // force the entity to be relinked + AddRestoredEntity( pent ); + } + } + else if ( (pEntInfo->edictindex > 0) && (pEntInfo->edictindex <= gpGlobals->maxClients) ) + { + if ( !(pEntInfo->flags & FENTTABLE_PLAYER) ) + { + Warning( "ENTITY IS NOT A PLAYER: %d\n" , i ); + Assert(0); + } + + edict_t *ed = INDEXENT( pEntInfo->edictindex ); + + if ( ed && createPlayers ) + { + // create the player + pent = CBasePlayer::CreatePlayer( STRING(pEntInfo->classname), ed ); + } + else + pent = NULL; + } + else + { + pent = CreateEntityByName( STRING(pEntInfo->classname) ); + } + pEntInfo->hEnt = pent; + pEntInfo->restoreentityindex = pent ? pent->entindex() : - 1; + if ( pent && pEntInfo->restoreentityindex == 0 ) + { + if ( !FClassnameIs( pent, "worldspawn" ) ) + { + pEntInfo->restoreentityindex = -1; + } + } + + if ( pEntInfo->restoreentityindex == 0 ) + { + Assert( !restoredWorld ); + restoredWorld = true; + } + } + else + { + pEntInfo->hEnt = NULL; + pEntInfo->restoreentityindex = -1; + } + } + + // Now spawn entities + for ( i = 0; i < pSaveData->NumEntities(); i++ ) + { + pEntInfo = pSaveData->GetEntityInfo( i ); + if ( pEntInfo->edictindex != 0 ) + { + pent = pEntInfo->hEnt; + pRestore->SetReadPos( pEntInfo->location ); + if ( pent ) + { + if ( RestoreEntity( pent, pRestore, pEntInfo ) < 0 ) + { + pEntInfo->hEnt = NULL; + pEntInfo->restoreentityindex = -1; + UTIL_RemoveImmediate( pent ); + } + else + { + AddRestoredEntity( pent ); + } + } + } + } +} + +#else // CLIENT DLL VERSION + +void CEntitySaveRestoreBlockHandler::Restore( IRestore *pRestore, bool createPlayers ) +{ + entitytable_t *pEntInfo; + CBaseEntity *pent; + + CGameSaveRestoreInfo *pSaveData = pRestore->GetGameSaveRestoreInfo(); + + // Create entity list + int i; + bool restoredWorld = false; + + for ( i = 0; i < pSaveData->NumEntities(); i++ ) + { + pEntInfo = pSaveData->GetEntityInfo( i ); + pent = ClientEntityList().GetBaseEntity( pEntInfo->restoreentityindex ); + pEntInfo->hEnt = pent; + } + + // Blast saved data into entities + for ( i = 0; i < pSaveData->NumEntities(); i++ ) + { + pEntInfo = pSaveData->GetEntityInfo( i ); + + bool bRestoredCorrectly = false; + // FIXME, need to translate save spot to real index here using lookup table transmitted from server + //Assert( !"Need translation still" ); + if ( pEntInfo->restoreentityindex >= 0 ) + { + if ( pEntInfo->restoreentityindex == 0 ) + { + Assert( !restoredWorld ); + restoredWorld = true; + } + + pent = ClientEntityList().GetBaseEntity( pEntInfo->restoreentityindex ); + pRestore->SetReadPos( pEntInfo->location ); + if ( pent ) + { + if ( RestoreEntity( pent, pRestore, pEntInfo ) >= 0 ) + { + // Call the OnRestore method + AddRestoredEntity( pent ); + bRestoredCorrectly = true; + } + } + } + // BUGBUG: JAY: Disable ragdolls across transitions until PVS/solid check & client entity patch file are implemented + else if ( !pSaveData->levelInfo.fUseLandmark ) + { + if ( pEntInfo->classname != NULL_STRING ) + { + pent = CreateEntityByName( STRING(pEntInfo->classname) ); + pent->InitializeAsClientEntity( NULL, RENDER_GROUP_OPAQUE_ENTITY ); + + pRestore->SetReadPos( pEntInfo->location ); + + if ( pent ) + { + if ( RestoreEntity( pent, pRestore, pEntInfo ) >= 0 ) + { + pEntInfo->hEnt = pent; + AddRestoredEntity( pent ); + bRestoredCorrectly = true; + } + } + } + } + + if ( !bRestoredCorrectly ) + { + pEntInfo->hEnt = NULL; + pEntInfo->restoreentityindex = -1; + } + } + + // Note, server does this after local player connects fully + IGameSystem::OnRestoreAllSystems(); + + // Tell hud elements to modify behavior based on game restoration, if applicable + gHUD.OnRestore(); +} +#endif + +void CEntitySaveRestoreBlockHandler::PostRestore() +{ +} + +void SaveEntityOnTable( CBaseEntity *pEntity, CSaveRestoreData *pSaveData, int &iSlot ) +{ + entitytable_t *pEntInfo = pSaveData->GetEntityInfo( iSlot ); + pEntInfo->id = iSlot; +#if !defined( CLIENT_DLL ) + pEntInfo->edictindex = pEntity->RequiredEdictIndex(); +#else + pEntInfo->edictindex = -1; + pEntInfo->modelname = pEntity->GetModelName(); +#endif + pEntInfo->restoreentityindex = -1; + pEntInfo->saveentityindex = pEntity ? pEntity->entindex() : -1; + pEntInfo->hEnt = pEntity; + pEntInfo->flags = 0; + pEntInfo->location = 0; + pEntInfo->size = 0; + pEntInfo->classname = NULL_STRING; + + iSlot++; +} + + +//--------------------------------- + +bool CEntitySaveRestoreBlockHandler::SaveInitEntities( CSaveRestoreData *pSaveData ) +{ + int number_of_entities; + +#if !defined( CLIENT_DLL ) + number_of_entities = gEntList.NumberOfEntities(); +#else + number_of_entities = ClientEntityList().NumberOfEntities( true ); +#endif + entitytable_t *pEntityTable = ( entitytable_t *)engine->SaveAllocMemory( (sizeof(entitytable_t) * number_of_entities), sizeof(char) ); + pSaveData->InitEntityTable( pEntityTable, number_of_entities ); + + // build the table of entities + // this is used to turn pointers into savable indices + // build up ID numbers for each entity, for use in pointer conversions + // if an entity requires a certain edict number upon restore, save that as well + CBaseEntity *pEnt = NULL; + int i = 0; + +#if !defined( CLIENT_DLL ) + while ( (pEnt = gEntList.NextEnt( pEnt )) != NULL ) + { +#else + int last = ClientEntityList().GetHighestEntityIndex(); + + for ( int e = 0; e <= last; e++ ) + { + pEnt = ClientEntityList().GetBaseEntity( e ); + if( !pEnt ) + continue; +#endif + SaveEntityOnTable( pEnt, pSaveData, i ); + } + +#if defined( CLIENT_DLL ) + ClientEntityHandle_t iter = ClientEntityList().FirstHandle(); + + while ( iter != ClientEntityList().InvalidHandle() ) + { + pEnt = ClientEntityList().GetBaseEntityFromHandle( iter ); + + if ( pEnt && pEnt->ObjectCaps() & FCAP_SAVE_NON_NETWORKABLE ) + { + SaveEntityOnTable( pEnt, pSaveData, i ); + } + + iter = ClientEntityList().NextHandle( iter ); + } +#endif + + Assert( i == pSaveData->NumEntities() ); + return ( i == pSaveData->NumEntities() ); +} + +//--------------------------------- + +#if !defined( CLIENT_DLL ) + +// Find the matching global entity. Spit out an error if the designer made entities of +// different classes with the same global name +CBaseEntity *CEntitySaveRestoreBlockHandler::FindGlobalEntity( string_t classname, string_t globalname ) +{ + CBaseEntity *pReturn = NULL; + + while ( (pReturn = gEntList.NextEnt( pReturn )) != NULL ) + { + if ( FStrEq( STRING(pReturn->m_iGlobalname), STRING(globalname)) ) + break; + } + + if ( pReturn ) + { + if ( !FClassnameIs( pReturn, STRING(classname) ) ) + { + Warning( "Global entity found %s, wrong class %s [expects class %s]\n", STRING(globalname), STRING(pReturn->m_iClassname), STRING(classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + +#endif // !defined( CLIENT_DLL ) + +//--------------------------------- + +bool CEntitySaveRestoreBlockHandler::DoRestoreEntity( CBaseEntity *pEntity, IRestore *pRestore ) +{ + MDLCACHE_CRITICAL_SECTION(); + + EHANDLE hEntity; + + hEntity = pEntity; + + pRestore->GetGameSaveRestoreInfo()->SetCurrentEntityContext( pEntity ); + pEntity->Restore( *pRestore ); + pRestore->GetGameSaveRestoreInfo()->SetCurrentEntityContext( NULL ); + +#if !defined( CLIENT_DLL ) + if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) + { + pEntity->Spawn(); + } + else + { + pEntity->Precache( ); + } +#endif + + // Above calls may have resulted in self destruction + return ( hEntity != NULL ); +} + +//--------------------------------- +// Get a reference position in model space to compute +// changes in model space for global brush entities (designer models them in different coords!) +Vector CEntitySaveRestoreBlockHandler::ModelSpaceLandmark( int modelIndex ) +{ + const model_t *pModel = modelinfo->GetModel( modelIndex ); + if ( modelinfo->GetModelType( pModel ) != mod_brush ) + return vec3_origin; + + Vector mins, maxs; + modelinfo->GetModelBounds( pModel, mins, maxs ); + return mins; +} + + +int CEntitySaveRestoreBlockHandler::RestoreEntity( CBaseEntity *pEntity, IRestore *pRestore, entitytable_t *pEntInfo ) +{ + if ( !DoRestoreEntity( pEntity, pRestore ) ) + return 0; + +#if !defined( CLIENT_DLL ) + if ( pEntity->m_iGlobalname != NULL_STRING ) + { + int globalIndex = GlobalEntity_GetIndex( pEntity->m_iGlobalname ); + if ( globalIndex >= 0 ) + { + // Already dead? delete + if ( GlobalEntity_GetState( globalIndex ) == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), GlobalEntity_GetMap(globalIndex) ) ) + { + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + } + // In this level & not dead, continue on as normal + } + else + { + Warning( "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->m_iGlobalname), STRING(pEntity->m_iClassname) ); + // Spawned entities default to 'On' + GlobalEntity_Add( pEntity->m_iGlobalname, gpGlobals->mapname, GLOBAL_ON ); + } + } +#endif + + return 0; +} + +//--------------------------------- + +#if !defined( CLIENT_DLL ) + +int CEntitySaveRestoreBlockHandler::RestoreGlobalEntity( CBaseEntity *pEntity, CSaveRestoreData *pSaveData, entitytable_t *pEntInfo ) +{ + Vector oldOffset; + EHANDLE hEntitySafeHandle; + hEntitySafeHandle = pEntity; + + oldOffset.Init(); + CRestore restoreHelper( pSaveData ); + + string_t globalName = pEntInfo->globalname, className = pEntInfo->classname; + + // ------------------- + + int globalIndex = GlobalEntity_GetIndex( globalName ); + + // Don't overlay any instance of the global that isn't the latest + // pSaveData->szCurrentMapName is the level this entity is coming from + // pGlobal->levelName is the last level the global entity was active in. + // If they aren't the same, then this global update is out of date. + if ( !FStrEq( pSaveData->levelInfo.szCurrentMapName, GlobalEntity_GetMap(globalIndex) ) ) + { + return 0; + } + + // Compute the new global offset + CBaseEntity *pNewEntity = FindGlobalEntity( className, globalName ); + if ( pNewEntity ) + { +// Msg( "Overlay %s with %s\n", pNewEntity->GetClassname(), STRING(tmpEnt->classname) ); + // Tell the restore code we're overlaying a global entity from another level + restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields + + pSaveData->modelSpaceOffset = pEntInfo->landmarkModelSpace - ModelSpaceLandmark( pNewEntity->GetModelIndex() ); + + UTIL_Remove( pEntity ); + pEntity = pNewEntity;// we're going to restore this data OVER the old entity + pEntInfo->hEnt = pEntity; + // HACKHACK: Do we need system-wide support for removing non-global spawn allocated resources? + pEntity->VPhysicsDestroyObject(); + Assert( pEntInfo->edictindex == -1 ); + // Update the global table to say that the global definition of this entity should come from this level + GlobalEntity_SetMap( globalIndex, gpGlobals->mapname ); + } + else + { + // This entity will be freed automatically by the engine-> If we don't do a restore on a matching entity (below) + // or call EntityUpdate() to move it to this level, we haven't changed global state at all. + DevMsg( "Warning: No match for global entity %s found in destination level\n", STRING(globalName) ); + return 0; + } + + if ( !DoRestoreEntity( pEntity, &restoreHelper ) ) + { + pEntity = NULL; + } + + // Is this an overriding global entity (coming over the transition) + pSaveData->modelSpaceOffset.Init(); + if ( pEntity ) + return 1; + return 0; +} + +#endif // !defined( CLIENT_DLL ) + + + +//----------------------------------------------------------------------------- + +CSaveRestoreData *SaveInit( int size ) +{ + CSaveRestoreData *pSaveData; + +#ifdef DISABLE_DEBUG_HISTORY + if ( size <= 0 ) + size = 2*1024*1024; // Reserve 2048K for now, UNDONE: Shrink this after compressing strings +#else + if ( size <= 0 ) + size = 3*1024*1024; // Reserve 3096K for now, UNDONE: Shrink this after compressing strings +#endif + + int numentities; + +#if !defined( CLIENT_DLL ) + numentities = gEntList.NumberOfEntities(); +#else + numentities = ClientEntityList().NumberOfEntities(); +#endif + + pSaveData = MakeSaveRestoreData(engine->SaveAllocMemory( sizeof(CSaveRestoreData) + (sizeof(entitytable_t) * numentities) + size, sizeof(char) )); + pSaveData->Init( (char *)(pSaveData + 1), size ); // skip the save structure + + const int nTokens = 0xfff; // Assume a maximum of 4K-1 symbol table entries(each of some length) + pSaveData->InitSymbolTable( (char **)engine->SaveAllocMemory( nTokens, sizeof( char * ) ), nTokens ); + + //--------------------------------- + + pSaveData->levelInfo.time = gpGlobals->curtime; // Use DLL time + pSaveData->levelInfo.vecLandmarkOffset = vec3_origin; + pSaveData->levelInfo.fUseLandmark = false; + pSaveData->levelInfo.connectionCount = 0; + + //--------------------------------- + + gpGlobals->pSaveData = pSaveData; + + return pSaveData; +} + + + +//----------------------------------------------------------------------------- +// +// ISaveRestoreBlockSet +// +// Purpose: Serves as holder for a group of sibling save sections. Takes +// care of iterating over them, making sure read points are +// queued up to the right spot (in case one section due to datadesc +// changes reads less than expected, or doesn't leave the +// read pointer at the right point), and ensuring the read pointer +// is at the end of the entire set when the set read is done. +//----------------------------------------------------------------------------- + +struct SaveRestoreBlockHeader_t +{ + char szName[MAX_BLOCK_NAME_LEN + 1]; + int locHeader; + int locBody; + + DECLARE_SIMPLE_DATADESC(); +}; + + +//------------------------------------- + +class CSaveRestoreBlockSet : public ISaveRestoreBlockSet +{ +public: + CSaveRestoreBlockSet( const char *pszName ) + { + Q_strncpy( m_Name, pszName, sizeof(m_Name) ); + } + + const char *GetBlockName() + { + return m_Name; + } + + //--------------------------------- + + void PreSave( CSaveRestoreData *pData ) + { + m_BlockHeaders.SetCount( m_Handlers.Count() ); + for ( int i = 0; i < m_Handlers.Count(); i++ ) + { + Q_strncpy( m_BlockHeaders[i].szName, m_Handlers[i]->GetBlockName(), MAX_BLOCK_NAME_LEN + 1 ); + m_Handlers[i]->PreSave( pData ); + } + } + + void Save( ISave *pSave ) + { + int base = pSave->GetWritePos(); + for ( int i = 0; i < m_Handlers.Count(); i++ ) + { + m_BlockHeaders[i].locBody = pSave->GetWritePos() - base; + m_Handlers[i]->Save( pSave ); + } + m_SizeBodies = pSave->GetWritePos() - base; + } + + void WriteSaveHeaders( ISave *pSave ) + { + int base = pSave->GetWritePos(); + + // + // Reserve space for a fully populated header + // + int dummyInt = -1; + CUtlVector dummyArr; + + dummyArr.SetCount( m_BlockHeaders.Count() ); + memset( &dummyArr[0], 0xff, dummyArr.Count() * sizeof(SaveRestoreBlockHeader_t) ); + + pSave->WriteInt( &dummyInt ); // size all headers + pSave->WriteInt( &dummyInt ); // size all bodies + SaveUtlVector( pSave, &dummyArr, FIELD_EMBEDDED ); + + // + // Write the data + // + for ( int i = 0; i < m_Handlers.Count(); i++ ) + { + m_BlockHeaders[i].locHeader = pSave->GetWritePos() - base; + m_Handlers[i]->WriteSaveHeaders( pSave ); + } + + m_SizeHeaders = pSave->GetWritePos() - base; + + // + // Write the actual header + // + int savedPos = pSave->GetWritePos(); + pSave->SetWritePos(base); + + pSave->WriteInt( &m_SizeHeaders ); + pSave->WriteInt( &m_SizeBodies ); + SaveUtlVector( pSave, &m_BlockHeaders, FIELD_EMBEDDED ); + + pSave->SetWritePos(savedPos); + } + + void PostSave() + { + for ( int i = 0; i < m_Handlers.Count(); i++ ) + { + m_Handlers[i]->PostSave(); + } + m_BlockHeaders.Purge(); + } + + //--------------------------------- + + void PreRestore() + { + for ( int i = 0; i < m_Handlers.Count(); i++ ) + { + m_Handlers[i]->PreRestore(); + } + } + + void ReadRestoreHeaders( IRestore *pRestore ) + { + int base = pRestore->GetReadPos(); + + pRestore->ReadInt( &m_SizeHeaders ); + pRestore->ReadInt( &m_SizeBodies ); + RestoreUtlVector( pRestore, &m_BlockHeaders, FIELD_EMBEDDED ); + + for ( int i = 0; i < m_Handlers.Count(); i++ ) + { + int location = GetBlockHeaderLoc( m_Handlers[i]->GetBlockName() ); + if ( location != -1 ) + { + pRestore->SetReadPos( base + location ); + m_Handlers[i]->ReadRestoreHeaders( pRestore ); + } + } + + pRestore->SetReadPos( base + m_SizeHeaders ); + } + + void CallBlockHandlerRestore( ISaveRestoreBlockHandler *pHandler, int baseFilePos, IRestore *pRestore, bool fCreatePlayers ) + { + int location = GetBlockBodyLoc( pHandler->GetBlockName() ); + if ( location != -1 ) + { + pRestore->SetReadPos( baseFilePos + location ); + pHandler->Restore( pRestore, fCreatePlayers ); + } + } + + void Restore( IRestore *pRestore, bool fCreatePlayers ) + { + int base = pRestore->GetReadPos(); + + for ( int i = 0; i < m_Handlers.Count(); i++ ) + { + CallBlockHandlerRestore( m_Handlers[i], base, pRestore, fCreatePlayers ); + } + pRestore->SetReadPos( base + m_SizeBodies ); + } + + void PostRestore() + { + for ( int i = 0; i < m_Handlers.Count(); i++ ) + { + m_Handlers[i]->PostRestore(); + } + m_BlockHeaders.Purge(); + } + + //--------------------------------- + + void AddBlockHandler( ISaveRestoreBlockHandler *pHandler ) + { + // Grody, but... while this class is still isolated in saverestore.cpp, this seems like a fine time to assert: + AssertMsg( pHandler == &g_EntitySaveRestoreBlockHandler || (m_Handlers.Count() >= 1 && m_Handlers[0] == &g_EntitySaveRestoreBlockHandler), "Expected entity save load to always be first" ); + + Assert( pHandler != this ); + m_Handlers.AddToTail( pHandler ); + } + + void RemoveBlockHandler( ISaveRestoreBlockHandler *pHandler ) + { + m_Handlers.FindAndRemove( pHandler ); + } + + //--------------------------------- + +private: + int GetBlockBodyLoc( const char *pszName ) + { + for ( int i = 0; i < m_BlockHeaders.Count(); i++ ) + { + if ( strcmp( m_BlockHeaders[i].szName, pszName ) == 0 ) + return m_BlockHeaders[i].locBody; + } + return -1; + } + + int GetBlockHeaderLoc( const char *pszName ) + { + for ( int i = 0; i < m_BlockHeaders.Count(); i++ ) + { + if ( strcmp( m_BlockHeaders[i].szName, pszName ) == 0 ) + return m_BlockHeaders[i].locHeader; + } + return -1; + } + + char m_Name[MAX_BLOCK_NAME_LEN + 1]; + CUtlVector m_Handlers; + + int m_SizeHeaders; + int m_SizeBodies; + CUtlVector m_BlockHeaders; +}; + +//------------------------------------- + +BEGIN_SIMPLE_DATADESC( SaveRestoreBlockHeader_t ) + DEFINE_ARRAY(szName, FIELD_CHARACTER, MAX_BLOCK_NAME_LEN + 1), + DEFINE_FIELD(locHeader, FIELD_INTEGER), + DEFINE_FIELD(locBody, FIELD_INTEGER), +END_DATADESC() + +//------------------------------------- + +CSaveRestoreBlockSet g_SaveRestoreBlockSet("Game"); +ISaveRestoreBlockSet *g_pGameSaveRestoreBlockSet = &g_SaveRestoreBlockSet; + +//============================================================================= +#if !defined( CLIENT_DLL ) + +//------------------------------------------------------------------------------ +// Creates all entities that lie in the transition list +//------------------------------------------------------------------------------ +void CreateEntitiesInTransitionList( CSaveRestoreData *pSaveData, int levelMask ) +{ + CBaseEntity *pent; + int i; + for ( i = 0; i < pSaveData->NumEntities(); i++ ) + { + entitytable_t *pEntInfo = pSaveData->GetEntityInfo( i ); + pEntInfo->hEnt = NULL; + + if ( pEntInfo->size == 0 || pEntInfo->edictindex == 0 ) + continue; + + if ( pEntInfo->classname == NULL_STRING ) + { + Warning( "Entity with data saved, but with no classname\n" ); + Assert(0); + continue; + } + + bool active = (pEntInfo->flags & levelMask) ? 1 : 0; + + // spawn players + pent = NULL; + if ( (pEntInfo->edictindex > 0) && (pEntInfo->edictindex <= gpGlobals->maxClients) ) + { + edict_t *ed = INDEXENT( pEntInfo->edictindex ); + + if ( active && ed && !ed->IsFree() ) + { + if ( !(pEntInfo->flags & FENTTABLE_PLAYER) ) + { + Warning( "ENTITY IS NOT A PLAYER: %d\n" , i ); + Assert(0); + } + + pent = CBasePlayer::CreatePlayer( STRING(pEntInfo->classname), ed ); + } + } + else if ( active ) + { + pent = CreateEntityByName( STRING(pEntInfo->classname) ); + } + + pEntInfo->hEnt = pent; + } +} + + +//----------------------------------------------------------------------------- +int CreateEntityTransitionList( CSaveRestoreData *pSaveData, int levelMask ) +{ + CBaseEntity *pent; + entitytable_t *pEntInfo; + + // Create entity list + CreateEntitiesInTransitionList( pSaveData, levelMask ); + + // Now spawn entities + CUtlVector checkList; + + int i; + int movedCount = 0; + for ( i = 0; i < pSaveData->NumEntities(); i++ ) + { + pEntInfo = pSaveData->GetEntityInfo( i ); + pent = pEntInfo->hEnt; +// pSaveData->currentIndex = i; + pSaveData->Seek( pEntInfo->location ); + + // clear this out - it must be set on a per-entity basis + pSaveData->modelSpaceOffset.Init(); + + if ( pent && (pEntInfo->flags & levelMask) ) // Screen out the player if he's not to be spawned + { + if ( pEntInfo->flags & FENTTABLE_GLOBAL ) + { + DevMsg( 2, "Merging changes for global: %s\n", STRING(pEntInfo->classname) ); + + // ------------------------------------------------------------------------- + // Pass the "global" flag to the DLL to indicate this entity should only override + // a matching entity, not be spawned + if ( g_EntitySaveRestoreBlockHandler.RestoreGlobalEntity( pent, pSaveData, pEntInfo ) > 0 ) + { + movedCount++; + pEntInfo->restoreentityindex = pEntInfo->hEnt.Get()->entindex(); + AddRestoredEntity( pEntInfo->hEnt.Get() ); + } + else + { + UTIL_RemoveImmediate( pEntInfo->hEnt.Get() ); + } + // ------------------------------------------------------------------------- + } + else + { + DevMsg( 2, "Transferring %s (%d)\n", STRING(pEntInfo->classname), pent->edict() ? ENTINDEX(pent->edict()) : -1 ); + CRestore restoreHelper( pSaveData ); + if ( g_EntitySaveRestoreBlockHandler.RestoreEntity( pent, &restoreHelper, pEntInfo ) < 0 ) + { + UTIL_RemoveImmediate( pent ); + } + else + { + // needs to be checked. Do this in a separate pass so that pointers & hierarchy can be traversed + checkList.AddToTail(i); + } + } + + // Remove any entities that were removed using UTIL_Remove() as a result of the above calls to UTIL_RemoveImmediate() + gEntList.CleanupDeleteList(); + } + } + + for ( i = checkList.Count()-1; i >= 0; --i ) + { + pEntInfo = pSaveData->GetEntityInfo( checkList[i] ); + pent = pEntInfo->hEnt; + + // NOTE: pent can be NULL because UTIL_RemoveImmediate (called below) removes all in hierarchy + if ( !pent ) + continue; + + MDLCACHE_CRITICAL_SECTION(); + + if ( !(pEntInfo->flags & FENTTABLE_PLAYER) && UTIL_EntityInSolid( pent ) ) + { + // this can happen during normal processing - PVS is just a guess, some map areas won't exist in the new map + DevMsg( 2, "Suppressing %s\n", STRING(pEntInfo->classname) ); + UTIL_RemoveImmediate( pent ); + // Remove any entities that were removed using UTIL_Remove() as a result of the above calls to UTIL_RemoveImmediate() + gEntList.CleanupDeleteList(); + } + else + { + movedCount++; + pEntInfo->flags = FENTTABLE_REMOVED; + pEntInfo->restoreentityindex = pent->entindex(); + AddRestoredEntity( pent ); + } + } + + return movedCount; +} +#endif diff --git a/game_shared/sceneentity_shared.cpp b/game_shared/sceneentity_shared.cpp index 3afc85e4..a6b69c7d 100644 --- a/game_shared/sceneentity_shared.cpp +++ b/game_shared/sceneentity_shared.cpp @@ -1,129 +1,129 @@ -//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= -// -// Purpose: -// -//============================================================================= - -#include "cbase.h" -#include "sceneentity_shared.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -static ConVar scene_print( "scene_print", "0", FCVAR_REPLICATED, "When playing back a scene, print timing and event info to console." ); -ConVar scene_clientflex( "scene_clientflex", "1", FCVAR_REPLICATED, "Do client side flex animation." ); - -IFileSystem *SceneFileSystem() -{ - return filesystem; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pFormat - -// ... - -// Output : static void -//----------------------------------------------------------------------------- -void Scene_Printf( const char *pFormat, ... ) -{ - int val = scene_print.GetInt(); - if ( !val ) - return; - - if ( val >= 2 ) - { - if ( CBaseEntity::IsServer() && val != 2 ) - { - return; - } - else if ( !CBaseEntity::IsServer() && val != 3 ) - { - return; - } - } - - va_list marker; - char msg[8192]; - - va_start(marker, pFormat); - Q_vsnprintf(msg, sizeof(msg), pFormat, marker); - va_end(marker); - - Msg( "%8.3f[%d] %s: %s", gpGlobals->curtime, gpGlobals->tickcount, CBaseEntity::IsServer() ? "sv" : "cl", msg ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const char -//----------------------------------------------------------------------------- -const char *CSceneTokenProcessor::CurrentToken( void ) -{ - return m_szToken; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : crossline - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CSceneTokenProcessor::GetToken( bool crossline ) -{ - // NOTE: crossline is ignored here, may need to implement if needed - m_pBuffer = engine->ParseFile( m_pBuffer, m_szToken, sizeof( m_szToken ) ); - if ( strlen( m_szToken ) >= 0 ) - return true; - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CSceneTokenProcessor::TokenAvailable( void ) -{ - char const *search_p = m_pBuffer; - - while ( *search_p <= 32) - { - if (*search_p == '\n') - return false; - search_p++; - if ( !*search_p ) - return false; - - } - - if (*search_p == ';' || *search_p == '#' || // semicolon and # is comment field - (*search_p == '/' && *((search_p)+1) == '/')) // also make // a comment field - return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *fmt - -// ... - -//----------------------------------------------------------------------------- -void CSceneTokenProcessor::Error( const char *fmt, ... ) -{ - char string[ 2048 ]; - va_list argptr; - va_start( argptr, fmt ); - Q_vsnprintf( string, sizeof(string), fmt, argptr ); - va_end( argptr ); - - Warning( "%s", string ); - Assert(0); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *buffer - -//----------------------------------------------------------------------------- -void CSceneTokenProcessor::SetBuffer( char *buffer ) -{ - m_pBuffer = buffer; -} - -CSceneTokenProcessor g_TokenProcessor; \ No newline at end of file +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "sceneentity_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar scene_print( "scene_print", "0", FCVAR_REPLICATED, "When playing back a scene, print timing and event info to console." ); +ConVar scene_clientflex( "scene_clientflex", "1", FCVAR_REPLICATED, "Do client side flex animation." ); + +IFileSystem *SceneFileSystem() +{ + return filesystem; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFormat - +// ... - +// Output : static void +//----------------------------------------------------------------------------- +void Scene_Printf( const char *pFormat, ... ) +{ + int val = scene_print.GetInt(); + if ( !val ) + return; + + if ( val >= 2 ) + { + if ( CBaseEntity::IsServer() && val != 2 ) + { + return; + } + else if ( !CBaseEntity::IsServer() && val != 3 ) + { + return; + } + } + + va_list marker; + char msg[8192]; + + va_start(marker, pFormat); + Q_vsnprintf(msg, sizeof(msg), pFormat, marker); + va_end(marker); + + Msg( "%8.3f[%d] %s: %s", gpGlobals->curtime, gpGlobals->tickcount, CBaseEntity::IsServer() ? "sv" : "cl", msg ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CSceneTokenProcessor::CurrentToken( void ) +{ + return m_szToken; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : crossline - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSceneTokenProcessor::GetToken( bool crossline ) +{ + // NOTE: crossline is ignored here, may need to implement if needed + m_pBuffer = engine->ParseFile( m_pBuffer, m_szToken, sizeof( m_szToken ) ); + if ( strlen( m_szToken ) >= 0 ) + return true; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSceneTokenProcessor::TokenAvailable( void ) +{ + char const *search_p = m_pBuffer; + + while ( *search_p <= 32) + { + if (*search_p == '\n') + return false; + search_p++; + if ( !*search_p ) + return false; + + } + + if (*search_p == ';' || *search_p == '#' || // semicolon and # is comment field + (*search_p == '/' && *((search_p)+1) == '/')) // also make // a comment field + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fmt - +// ... - +//----------------------------------------------------------------------------- +void CSceneTokenProcessor::Error( const char *fmt, ... ) +{ + char string[ 2048 ]; + va_list argptr; + va_start( argptr, fmt ); + Q_vsnprintf( string, sizeof(string), fmt, argptr ); + va_end( argptr ); + + Warning( "%s", string ); + Assert(0); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *buffer - +//----------------------------------------------------------------------------- +void CSceneTokenProcessor::SetBuffer( char *buffer ) +{ + m_pBuffer = buffer; +} + +CSceneTokenProcessor g_TokenProcessor; diff --git a/game_shared/sdk/sdk_basegrenade_projectile.cpp b/game_shared/sdk/sdk_basegrenade_projectile.cpp index bf125658..5063e41b 100644 --- a/game_shared/sdk/sdk_basegrenade_projectile.cpp +++ b/game_shared/sdk/sdk_basegrenade_projectile.cpp @@ -120,7 +120,7 @@ END_NETWORK_TABLE() return; } - CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, GetAbsVelocity().Length( ), 0.2 ); + CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, static_cast(GetAbsVelocity().Length()), 0.2 ); SetNextThink( gpGlobals->curtime + 0.2 ); diff --git a/game_shared/sdk/sdk_gamerules.cpp b/game_shared/sdk/sdk_gamerules.cpp index 108c1ae2..0a65857d 100644 --- a/game_shared/sdk/sdk_gamerules.cpp +++ b/game_shared/sdk/sdk_gamerules.cpp @@ -154,7 +154,7 @@ IMPLEMENT_NETWORKCLASS_ALIASED( SDKGameRulesProxy, DT_SDKGameRulesProxy ) CSDKGameRules::CSDKGameRules() { // Create the team managers - for ( int i = 0; i < ARRAYSIZE( sTeamNames ); i++ ) + for ( int i = 0; i < static_cast(ARRAYSIZE( sTeamNames )); i++ ) { CTeam *pTeam = static_cast(CreateEntityByName( "sdk_team_manager" )); pTeam->Init( sTeamNames[i], i ); diff --git a/game_shared/sdk/sdk_playeranimstate.cpp b/game_shared/sdk/sdk_playeranimstate.cpp index 41faa598..09df257f 100644 --- a/game_shared/sdk/sdk_playeranimstate.cpp +++ b/game_shared/sdk/sdk_playeranimstate.cpp @@ -301,7 +301,7 @@ bool CSDKPlayerAnimState::IsOuterGrenadePrimed() } else { - return NULL; + return false; } } diff --git a/game_shared/sdk/weapon_sdkbase.cpp b/game_shared/sdk/weapon_sdkbase.cpp index d2434bbe..c0cf0283 100644 --- a/game_shared/sdk/weapon_sdkbase.cpp +++ b/game_shared/sdk/weapon_sdkbase.cpp @@ -1,163 +1,163 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "in_buttons.h" -#include "takedamageinfo.h" -#include "weapon_sdkbase.h" -#include "ammodef.h" - - -#if defined( CLIENT_DLL ) - - #include "c_sdk_player.h" - -#else - - #include "sdk_player.h" - -#endif - - -// ----------------------------------------------------------------------------- // -// Global functions. -// ----------------------------------------------------------------------------- // - -//-------------------------------------------------------------------------------------------------------- -static const char * s_WeaponAliasInfo[] = -{ - "none", // WEAPON_NONE - "mp5", // WEAPON_MP5 - "shotgun", // WEAPON_SHOTGUN - "grenade", // WEAPON_GRENADE - NULL, // WEAPON_NONE -}; - -//-------------------------------------------------------------------------------------------------------- -// -// Given an alias, return the associated weapon ID -// -int AliasToWeaponID( const char *alias ) -{ - if (alias) - { - for( int i=0; s_WeaponAliasInfo[i] != NULL; ++i ) - if (!Q_stricmp( s_WeaponAliasInfo[i], alias )) - return i; - } - - return WEAPON_NONE; -} - -//-------------------------------------------------------------------------------------------------------- -// -// Given a weapon ID, return its alias -// -const char *WeaponIDToAlias( int id ) -{ - if ( (id >= WEAPON_MAX) || (id < 0) ) - return NULL; - - return s_WeaponAliasInfo[id]; -} - -// ----------------------------------------------------------------------------- // -// CWeaponSDKBase tables. -// ----------------------------------------------------------------------------- // - -IMPLEMENT_NETWORKCLASS_ALIASED( WeaponSDKBase, DT_WeaponSDKBase ) - -BEGIN_NETWORK_TABLE( CWeaponSDKBase, DT_WeaponSDKBase ) -#ifdef CLIENT_DLL - -#else - // world weapon models have no animations - SendPropExclude( "DT_AnimTimeMustBeFirst", "m_flAnimTime" ), - SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), -#endif -END_NETWORK_TABLE() - -#ifdef CLIENT_DLL -BEGIN_PREDICTION_DATA( CWeaponSDKBase ) - DEFINE_PRED_FIELD( m_flTimeWeaponIdle, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_NOERRORCHECK ), -END_PREDICTION_DATA() -#endif - -LINK_ENTITY_TO_CLASS( weapon_sdk_base, CWeaponSDKBase ); - - -#ifdef GAME_DLL - - BEGIN_DATADESC( CWeaponSDKBase ) - - // New weapon Think and Touch Functions go here.. - - END_DATADESC() - -#endif - -// ----------------------------------------------------------------------------- // -// CWeaponCSBase implementation. -// ----------------------------------------------------------------------------- // -CWeaponSDKBase::CWeaponSDKBase() -{ - SetPredictionEligible( true ); - - AddSolidFlags( FSOLID_TRIGGER ); // Nothing collides with these but it gets touches. -} - -const CSDKWeaponInfo &CWeaponSDKBase::GetSDKWpnData() const -{ - const FileWeaponInfo_t *pWeaponInfo = &GetWpnData(); - const CSDKWeaponInfo *pSDKInfo; - - #ifdef _DEBUG - pSDKInfo = dynamic_cast< const CSDKWeaponInfo* >( pWeaponInfo ); - Assert( pSDKInfo ); - #else - pSDKInfo = static_cast< const CSDKWeaponInfo* >( pWeaponInfo ); - #endif - - return *pSDKInfo; -} - -bool CWeaponSDKBase::PlayEmptySound() -{ - CPASAttenuationFilter filter( this ); - filter.UsePredictionRules(); - - EmitSound( filter, entindex(), "Default.ClipEmpty_Rifle" ); - - return 0; -} - -CSDKPlayer* CWeaponSDKBase::GetPlayerOwner() const -{ - return dynamic_cast< CSDKPlayer* >( GetOwner() ); -} - -#ifdef GAME_DLL - -void CWeaponSDKBase::SendReloadEvents() -{ - CSDKPlayer *pPlayer = dynamic_cast< CSDKPlayer* >( GetOwner() ); - if ( !pPlayer ) - return; - - // Send a message to any clients that have this entity to play the reload. - CPASFilter filter( pPlayer->GetAbsOrigin() ); - filter.RemoveRecipient( pPlayer ); - - UserMessageBegin( filter, "ReloadEffect" ); - WRITE_SHORT( pPlayer->entindex() ); - MessageEnd(); - - // Make the player play his reload animation. - pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD ); -} - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "in_buttons.h" +#include "takedamageinfo.h" +#include "weapon_sdkbase.h" +#include "ammodef.h" + + +#if defined( CLIENT_DLL ) + + #include "c_sdk_player.h" + +#else + + #include "sdk_player.h" + +#endif + + +// ----------------------------------------------------------------------------- // +// Global functions. +// ----------------------------------------------------------------------------- // + +//-------------------------------------------------------------------------------------------------------- +static const char * s_WeaponAliasInfo[] = +{ + "none", // WEAPON_NONE + "mp5", // WEAPON_MP5 + "shotgun", // WEAPON_SHOTGUN + "grenade", // WEAPON_GRENADE + NULL, // WEAPON_NONE +}; + +//-------------------------------------------------------------------------------------------------------- +// +// Given an alias, return the associated weapon ID +// +int AliasToWeaponID( const char *alias ) +{ + if (alias) + { + for( int i=0; s_WeaponAliasInfo[i] != NULL; ++i ) + if (!Q_stricmp( s_WeaponAliasInfo[i], alias )) + return i; + } + + return WEAPON_NONE; +} + +//-------------------------------------------------------------------------------------------------------- +// +// Given a weapon ID, return its alias +// +const char *WeaponIDToAlias( int id ) +{ + if ( (id >= WEAPON_MAX) || (id < 0) ) + return NULL; + + return s_WeaponAliasInfo[id]; +} + +// ----------------------------------------------------------------------------- // +// CWeaponSDKBase tables. +// ----------------------------------------------------------------------------- // + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponSDKBase, DT_WeaponSDKBase ) + +BEGIN_NETWORK_TABLE( CWeaponSDKBase, DT_WeaponSDKBase ) +#ifdef CLIENT_DLL + +#else + // world weapon models have no animations + SendPropExclude( "DT_AnimTimeMustBeFirst", "m_flAnimTime" ), + SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponSDKBase ) + DEFINE_PRED_FIELD( m_flTimeWeaponIdle, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_NOERRORCHECK ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_sdk_base, CWeaponSDKBase ); + + +#ifdef GAME_DLL + + BEGIN_DATADESC( CWeaponSDKBase ) + + // New weapon Think and Touch Functions go here.. + + END_DATADESC() + +#endif + +// ----------------------------------------------------------------------------- // +// CWeaponCSBase implementation. +// ----------------------------------------------------------------------------- // +CWeaponSDKBase::CWeaponSDKBase() +{ + SetPredictionEligible( true ); + + AddSolidFlags( FSOLID_TRIGGER ); // Nothing collides with these but it gets touches. +} + +const CSDKWeaponInfo &CWeaponSDKBase::GetSDKWpnData() const +{ + const FileWeaponInfo_t *pWeaponInfo = &GetWpnData(); + const CSDKWeaponInfo *pSDKInfo; + + #ifdef _DEBUG + pSDKInfo = dynamic_cast< const CSDKWeaponInfo* >( pWeaponInfo ); + Assert( pSDKInfo ); + #else + pSDKInfo = static_cast< const CSDKWeaponInfo* >( pWeaponInfo ); + #endif + + return *pSDKInfo; +} + +bool CWeaponSDKBase::PlayEmptySound() +{ + CPASAttenuationFilter filter( this ); + filter.UsePredictionRules(); + + EmitSound( filter, entindex(), "Default.ClipEmpty_Rifle" ); + + return 0; +} + +CSDKPlayer* CWeaponSDKBase::GetPlayerOwner() const +{ + return dynamic_cast< CSDKPlayer* >( GetOwner() ); +} + +#ifdef GAME_DLL + +void CWeaponSDKBase::SendReloadEvents() +{ + CSDKPlayer *pPlayer = dynamic_cast< CSDKPlayer* >( GetOwner() ); + if ( !pPlayer ) + return; + + // Send a message to any clients that have this entity to play the reload. + CPASFilter filter( pPlayer->GetAbsOrigin() ); + filter.RemoveRecipient( pPlayer ); + + UserMessageBegin( filter, "ReloadEffect" ); + WRITE_SHORT( pPlayer->entindex() ); + MessageEnd(); + + // Make the player play his reload animation. + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD ); +} + +#endif diff --git a/game_shared/sheetsimulator.cpp b/game_shared/sheetsimulator.cpp index 1f986be8..79993f3b 100644 --- a/game_shared/sheetsimulator.cpp +++ b/game_shared/sheetsimulator.cpp @@ -398,7 +398,7 @@ void CSheetSimulator::InitPosition( int i ) // collision plane Vector delta; VectorSubtract( m_ControlPoints[i], m_Origin, delta ); - int maxdist = VectorNormalize( delta ); + int maxdist = static_cast(VectorNormalize( delta )); float dist = (m_pCollisionPlanes[i].dist - DotProduct( m_Origin, m_pCollisionPlanes[i].normal )) / DotProduct( delta, m_pCollisionPlanes[i].normal ); @@ -635,7 +635,7 @@ bool CIterativeSheetSimulator::Think( ) else { // Simulate it a bunch of times - Simulate(m_TimeStep, m_SubSteps); + Simulate(m_TimeStep, static_cast(m_SubSteps)); // Reset the collision point for collision detect m_CurrentCollisionPt = 0; diff --git a/game_shared/soundenvelope.cpp b/game_shared/soundenvelope.cpp index 6833ac89..2c3728eb 100644 --- a/game_shared/soundenvelope.cpp +++ b/game_shared/soundenvelope.cpp @@ -1041,7 +1041,7 @@ CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEn CSoundPatch *pSound = new CSoundPatch; // FIXME: This is done so we don't have to futz with the public interface - EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; + EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : 0; pSound->Init( &filter, hEnt.Get(), CHAN_AUTO, pSoundName, SNDLVL_NORM ); return pSound; @@ -1051,7 +1051,7 @@ CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEn const char *pSoundName, float attenuation ) { CSoundPatch *pSound = new CSoundPatch; - EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; + EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : 0; pSound->Init( &filter, hEnt.Get(), channel, pSoundName, ATTN_TO_SNDLVL( attenuation ) ); return pSound; @@ -1061,7 +1061,7 @@ CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEn const char *pSoundName, soundlevel_t soundlevel ) { CSoundPatch *pSound = new CSoundPatch; - EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; + EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : 0; pSound->Init( &filter, hEnt.Get(), channel, pSoundName, soundlevel ); return pSound; @@ -1072,7 +1072,7 @@ CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEn CSoundPatch *pSound = new CSoundPatch; // FIXME: This is done so we don't have to futz with the public interface - EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL; + EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : 0; pSound->Init( &filter, hEnt.Get(), es.m_nChannel, es.m_pSoundName, es.m_SoundLevel ); pSound->ChangeVolume( es.m_flVolume, 0 ); pSound->ChangePitch( es.m_nPitch, 0 ); diff --git a/game_shared/usercmd.cpp b/game_shared/usercmd.cpp index 6d8bba6d..469769f1 100644 --- a/game_shared/usercmd.cpp +++ b/game_shared/usercmd.cpp @@ -77,7 +77,7 @@ void WriteUsercmd( bf_write *buf, CUserCmd *to, CUserCmd *from ) if ( to->forwardmove != from->forwardmove ) { buf->WriteOneBit( 1 ); - buf->WriteSBitLong( to->forwardmove, 16 ); + buf->WriteSBitLong( static_cast(to->forwardmove), 16 ); } else { @@ -87,7 +87,7 @@ void WriteUsercmd( bf_write *buf, CUserCmd *to, CUserCmd *from ) if ( to->sidemove != from->sidemove ) { buf->WriteOneBit( 1 ); - buf->WriteSBitLong( to->sidemove, 16 ); + buf->WriteSBitLong( static_cast(to->sidemove), 16 ); } else { @@ -97,7 +97,7 @@ void WriteUsercmd( bf_write *buf, CUserCmd *to, CUserCmd *from ) if ( to->upmove != from->upmove ) { buf->WriteOneBit( 1 ); - buf->WriteSBitLong( to->upmove, 16 ); + buf->WriteSBitLong( static_cast(to->upmove), 16 ); } else { diff --git a/game_shared/voice_gamemgr.cpp b/game_shared/voice_gamemgr.cpp index 6122d4cb..c3e132df 100644 --- a/game_shared/voice_gamemgr.cpp +++ b/game_shared/voice_gamemgr.cpp @@ -48,7 +48,7 @@ CVoiceGameMgr g_VoiceGameMgr; // ------------------------------------------------------------------------ // // Find a player with a case-insensitive name search. -static CBasePlayer* FindPlayerByName(const char *pTestName) +/*static CBasePlayer* FindPlayerByName(const char *pTestName) { for(int i=1; i <= gpGlobals->maxClients; i++) { @@ -68,7 +68,7 @@ static CBasePlayer* FindPlayerByName(const char *pTestName) } return NULL; -} +}*/ static void VoiceServerDebug( const char *pFmt, ... ) { @@ -161,8 +161,8 @@ bool CVoiceGameMgr::ClientCommand(CBasePlayer *pPlayer, const char *cmd) { for(int i=1; i < engine->Cmd_Argc(); i++) { - unsigned long mask = 0; - sscanf(engine->Cmd_Argv(i), "%x", &mask); + unsigned int mask = 0; + sscanf(engine->Cmd_Argv(i), "%X", &mask); if(i <= VOICE_MAX_PLAYERS_DW) { diff --git a/game_shared/weapon_parse.cpp b/game_shared/weapon_parse.cpp index eff1c08b..dbfc2c36 100644 --- a/game_shared/weapon_parse.cpp +++ b/game_shared/weapon_parse.cpp @@ -65,7 +65,7 @@ static CUtlDict< FileWeaponInfo_t*, unsigned short > m_WeaponInfoDatabase; #ifdef _DEBUG // used to track whether or not two weapons have been mistakenly assigned the wrong slot -bool g_bUsedWeaponSlots[MAX_WEAPON_SLOTS][MAX_WEAPON_POSITIONS] = { 0 }; +bool g_bUsedWeaponSlots[MAX_WEAPON_SLOTS][MAX_WEAPON_POSITIONS] = { {false} }; #endif @@ -109,7 +109,7 @@ static FileWeaponInfo_t gNullWeaponInfo; //----------------------------------------------------------------------------- FileWeaponInfo_t *GetFileWeaponInfoFromHandle( WEAPON_FILE_INFO_HANDLE handle ) { - if ( handle < 0 || handle >= m_WeaponInfoDatabase.Count() ) + if ( handle >= m_WeaponInfoDatabase.Count() ) { return &gNullWeaponInfo; } @@ -379,7 +379,7 @@ void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponNam // Weapon scripts should use the flag names. iFlags = pKeyValuesData->GetInt( "item_flags", ITEM_FLAG_LIMITINWORLD ); - for ( int i=0; i < ARRAYSIZE( g_ItemFlags ); i++ ) + for ( size_t i = 0; i < ARRAYSIZE( g_ItemFlags ); i++ ) { int iVal = pKeyValuesData->GetInt( g_ItemFlags[i].m_pFlagName, -1 ); if ( iVal == 0 ) diff --git a/lib-vc7/public/choreoobjects.lib b/lib-vc7/public/choreoobjects.lib index 82af78f4742b3153974e1c04101a54016cc8aef4..b44b547415aaf613c5e69225b62186a75c43b31f 100644 GIT binary patch delta 208281 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.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�OS$pTOF23x*tt4sAY>uYD}7J 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 delta 198935 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

^Nv-YDVB2BFDo_A7gn_iwG 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Ȭ|PAU3s;U;lBKUSm|}Htw~=XMj{F!5rvS5 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�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

Wp38%~$?HgWrw7&m=_!cj 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

McQi7d|xBoy>7i^2`=2n4Vu;qC3qtx^ITNUJI3y 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≶h)fptvgA{;Y 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⁢ECyy^~JQ_C}wO`K(p}Zc_&omnB zc0I^cm~N8YQZ8#sp@%CD1d8zwQHjtL+oY}%o1b82f@#i6lzY4A3~OJpRziDA7W{5c zrAP-`5yx)eNK+N2B0LW$i;~0jcwy4oIVN9=4oir<-HBSl83lVhW=JOs*8YQiYE6n4B#tur;F>$;-0JRQ<$n*z%BPRWWrnj?Xl%I}nUq&y8JMpV3 zDDC>%CqTZ(LKo~#&_S8|twAOPI&!s_3! z_NiH8)fKn(D35JVc)5errSMg$#t&Des!#LVNwaW-(12!hK*W9#V7do8C>$i8e%k7$ zbM!Au;DfT&r!w1ibhC>A_7MMyw~7lxHdp5^zIdvv{N7)-{|R0g69UewQ0L{y+WLt> zk|4|tkjt0mr)RO(P-(g(@=c9Q(J)JhPImvR{N`yrdteVWB0Z>=^ImNbDBm?TO-SZ! z&f|Ao6E@80B~F*65 z5Vf;oQF$dCQXcg?d~`fG)Z#`J{c>x~9<`&Tm29Xo%avq1mzKFdU3h0IG zui{t-sbmGKSLD-`%|H-wV~#4*FY+WN1&PVHM%VF6Kx<_%myKUSxCUg81t^qdUHd^Z zJuEE`?(%V*??`*bgCqR>whO48H^pG2;N>viSkx9<;Z$3rem1KVoIZ;~*f(&~j)SnY#WcvF#)!oAR#ec`JwfoHQoI%w?K zIC^R)<#CShKYZxqr6w+f)ok%vp0s#V&Fl(R5E<1Tg^vTwcua~4Kq;r zo%f^Hs{GzJBx9%VvGnDYwZ=94%m<-X)UTkVm)hg|IQUVcmVn$vP<3yYL`kTMq%~(h zX$Z+GUpx@R3~$8#x`*vZ(dPKN({wh0!l!y8g5swt?kxr4B}kLuo0aC_wNEoum!KCm zCFy$#%N!amkn)7h`-K>f1u_+*J%tF1U#&9oa^Q)dTp-6KTe%Y->C8b(dm8UqiG*-R z9`z2!l2B_O#kFqFV=X#!<*M+}RFx{f4Nu^1gybnPH3OhK`W_y{2in?PWIb;}5LrWv zsQ{T5=pDFlW{x~46xJq?xWjMpK*->7Ml~5ck%zJM#f4$B(nx6E^!;F^;Mrl0MD_pq zWShMH1h~Q2lg0G))4fEbQ(-uUoe=)7@@D#?KnJ_Dk^jSIzP5FI`}Qgoa&`_v0FuOR z@>X{4G&sovD`vVxY(0|T)a=>0GAkq%7lJhvSF}Kq!1S+vvy2{6;s5*a=*!(g(~qfpwEbG|8)&#v~CO z`dxD{nZN@%C#ckVIz zVy`MUALB`*V9k!KNUEkOuzs{Yn!9J4gj{(}Nq(B9&K~Lv#T+oAQjH<(=-qxw31w)6!Usl?)=Y29 z=5;DwJc?M`%TAW)`*wZQ9n?={lc#A*iY1l9|T|4|$JUx7T{j$xC zpPXQ+R_6$$Uy_Ttn7Y+{+(2deoPzzPw-H*Ykp4}mnAffdQ_8*U?~Q!H{&I;l=cFia znCNKLf{MreW8MfA-MbfUFTT1~DoCvTV+eqO8|EaGUnfVcyz**pxxCS|v*S$FVQgrJ z0KI$FownFpq4~9Z6r5kpb1QR_E}t&$CpRj#56$@iLR04M8kfM3u|HLZP1o^q zeha2Fd@f0EMwt-~I4}c>ae6x4>lR0+mm#S;wSMAKQk4R8FJ<15zJRyQOMZ{~&1kej zonOqGU0KyGILpg_k-O@UbWqQ#60wm0Dq;ZADT&$3c|rhT`yRX<>|0pNkP(SO&#w({ zR|QBwcpi`8Haq4K38r?;QItxt;PuQ0+u-_JbyhKCXnFlM#7ZYZiw&+Qw&yt4N=O!% z40}dM!aD=d*Ld*ySsKTGqrh?V=T>dr zQ{AexAyg2S0{i-YOG6mk7NggTnQDD%c{S+mLO1!@P*kd{mQWoOz4+!8WbH(S&2Mg> z6+J2l@Js9wgbXc`ig(^jXI+u4jiO>Pa|#I;@vXAr%(X1k2_xyeaDKI%H1Y_e(H#XWpAhtMTeYOno% zNit$ybpN~YO2g*3vOuTK%PhC9^VEiVC+8{wa7EP-KPfoUtRc&I`UJU*QM_g6l;At(!AGX+V@ETm$$=z8WRl7uCBdKZwLJ zvJC*e!^+I!_}#m;vVc*)+{)&NrtrU*rUIIwFag%9_@dVTir6phr-Y6znLH&joZ%F> z*IGn$RZT|Nb)SnfT;fdX<3)R|*wi@)OQ)WkA%?ya*{%Z@&VQn^kMR2$sM=8ZE>Fd` zVfy1$$GuZ_in!*TemO!$N$T0|7a8ai{=);WVI=k<4b4$PpaLZff1XG4bC1WtarD5E z78GP;gfhynv%jtkSr<5raM_v0U(IqS4R`TB+=2@^ZFWOz`~&eQ+9AOF8y^CmyW%29 zI18Z&UxM7BVD$SNZW}k$*gN6ii1#micr>6%p!}z{hbJ z>;4rZqyTw3`B4ab9nZ1z3`!O$C#e(-%a4~6nM-!{d_KDENG+;Q-GaLtJ|E(?yywb% zs_X4rTYLp*sXHrO5Vc}HvM_9h?vYfLQ^|NGShQ^wY{C%;mWUki%1qq{hI=Bv&yX|k zYRS1CLN4LM7;!7`JQ8|e9>{(F9>kFY9`Q_J^>>-6I)vH*x*RrYM_1&dgY?2q(xCjA zw>>Q~ZBuU5)tw(Hg|OWsTDi|4z;7|>DZA}<=9WN{Jv&E$*RnRg%ti_t#>0ZO7Z~t5 z=0B^#eZR>(^VqeIjB*Uz_DeWoZmu*7RD}ThQ+Ss_98!9m%3AVCmp$s(E|GhZQzrR+ zyM!58Mb{Le%wE6XRzQ7Y8a%MBkju|AK1)60wP{FyF#OY;UyC5>Ytnjlh6z@hz`#tX z;&xjTp0~(bbC;cp#&fw6BtcbEizGFyO@oePtFz5=G^86xf z$%zl1SWkIazk7_SkyCqKXC+>;)6R`-c^MDs%rPGT)s-s|p7i4iK#y_tu*=RKfCMBi zT$0Im&F@SXB+`CxWq5ucN3o)GG~w_~*mUrabKv%v5xcb{Ik7!yXLljc!( z(8i$@QAzr?6)8?c;Chvk{aDf!sc4p?hM^_%XNfr?3#}}lix>hUS)IolJ?apcdA zkV9B4wir}|-|q)XbG?If1Rhh_N*m6u3dBO+uj=PIq!NF+xE2doYXT{p%(=j_h`C|< zS$WXc?zjkon0VQ^(vL^nK#2o(vCy$@fQnLcoW>Rl&y z#Vm8dsiP+jKldTs>F?4^IW$KQ+$t}xOhq})G_+D=BG-dW~8XuvG9*2vP z+T?>?vz;r_*|E-*$_sw>`x)(%1A702H+dK6jdm>=R*H__m{&(zzt*yfMiU+a{2Krz zWQP9_K^jO5e&$9lVCduiJM0_8<|si*0fQ{FYPVH!A{h7ch)`oau1d0c0!Rx5TD=H1 z8_8nOFr_k=0)>BLctxFA)ZtG0_nX1oPv?dBRYq!YB1IL~uJ)<&dViO)?&G@b;H*7?s{Hma=Id`?PEHc zeiUy1A|A<-I?sG(-_!Q-QT=btU>xFF9I!JOd($uE=beC5wcLhW=w1Bt_?;s7 zj!u$@aQSPDvVc|0>$yQSAhXezM>#DHJ&o6_`PJ$=N;Y4~xp(gTwsQmIDu~_dEoqA=c3mo(Y8v*OF2%D2o=r2`nnNg%L0FqxD``^l~r3zG#Jnm@&adXl4} zef% z;-_lNs3p&Gofs*p*$KbXh1{-@IF~dvwfs$TkkeOHCf&9MjOCrFnGo*k9;VK~wS@S~N?a-OU zxrK;OKh7&8EYxovt2}jTfZ&r9fsX1d4+m@18Qij6S9tJ83Vp21$bTw4%;Y+F-ul% zsFv3|Ld=g4l7xH23;de1s>|d!I%Os#TL`-kM&5REL&B0{6JY+8b4%r;Q2dZx@BOPt z3ZbBUD0re!4@eE9dgBwm!pB3)TE9;{P-tpg@09khDxpgqLEP5I}!WJ9i<1 zyuA5%MbIzt2~?P>F`Z>g;jdv70=933ZPQRmg+9;3w}0Z90dLJq))6iRpzlj0%eX$2 z8U{)prUhP1@RE_EUVa*0hEVv^KS!vl1wP?s7Q#g4?ck)Y`hU z*p_PO&i|I5?xgCPNDmJV1b!;uXMVG(!TP>8PY+a2&SKzJDDWB$* zVw$H(z4N83oo5^0M}@H9`B07iurGCqo8_TtDJcT~2YlKB6N}zd-sH#QQ6dOsR8*y| z`ThKWYumF#=)lN;BKW_jTa8&PCf;b~l*BgaO_?o6{SIFQysMshzS{XfJm#b90wg7y}%0~f5XyAyeUe)fj2#b&UQAkjqQ*B@&ZW5%Hnjs_K)BN@4AHe6mXyg zHXj1DD@YMirG5O>SlY9(dbZw0O{ftD|2W%MEty$ZBgT$EBOy4(jo11tcx;B?QW_M9 zPH+i5yCIsd@ty6!WfaVWAJT1G&1?)PDmcl?Zd(ewy@EmIuTc$+T+Jh)Hd{g+B*D)G z3wxt00+mw0RX|%;{i$Vy3Or>a7l_s5*~&Jmytw;fo;DslX=?F{W^@h{Iu$GakX3Li zh)-oEE14==flyiDVvvjD60gnrAFIJ{@ne;}qCO(T3tZNW(T5X>S7lo#X9RGPK>i%J z&thhU<1(#?2haECxbfJu#iUxfO;6)DeX_0m;_r5X6kGBY0=B=w?>ePtgF&IQiP=$_DW^BQW;$5W~uD@i#=DfT7cff6nru-!DLO$VlgpNjl<7Q&(nk4)F$L{IZGO8@b&T9*t@YjClI z$-bGDGG#9QSN%n?hVPY9U*f$qma@;t`I$cw*}-^J!~QM#*_*`=cDf02i!-Su%u+1HaeCq4$x z9T|NE+Mmm0ow27Gry-Xi2{PKB<%Mm&C9!>CejEFErC~h5cL#<3g67-#Fmefx8cuU(r z@pC6ktp!$1?XAGndoe#(rY)r+Dt+&2L`3C`fgQ5&j<(TVE{ww3S^W61=4a6s)Z6=E z{w^FmO-mOkk`mRpKqW1?Vd@taNXiyo6sP#Fvcj>1&Pw+8-v2@tSjtlUx{sJO0$0xRT#exM#wo;cK}K?#!Eg5ZGJxj(z7fS!^c_*`VKN#50t_02}ABQ9l zDqBdwzRWZhx=3hWAp4pc>Ft}(1HpJ>83rD)_f0O6;H<^`_4|B>zt)yRC_j#%rWrxX zvCS|(VmFvF@t`QzWSmx&CH)}4GJL%fUEa=yQ^`N4 zt*!8_j39(pSJ*f6hau7CCl@#}P-!R9us`N6^mS4%7UmEji&T6cIv{N~V8APsxy_Qj z;!2(WZYWLZUAcDY!XGk80ai1q06j>s#^|+bs~*&qycK0kZMj(vBePojJr*+~C9b7+ zlN!d$IfFRE(qn1V>0&GaOOX9u&UO$v?s3~ocfyi$Zln^!2S#F~8pT~9A~7_NWn+0@ zx8pL&^RT(uLJrKq-^t7?gpu#QD1*xWSGXH-yBE*MC*OB2j$4JY7xEN-O9(5#;K0f2 zaXg$@EP>{I#kJf2xI@U~6Bra_;A`;a`OMTuTfn6i9Ipe-X;z)pT>|ycawnU4He_Us zUo_c8M0_z2jsAm>ULHiRSWDlvoB6yQ+s1uI|CLu^DbMk6AM|3c?TP?8GZ!0ilbRtSlU~)y`JWTi4$6xqphblm`V?cRQM}C9ouekPHEE-fPKBs zPW0lQ;s+94m^xz>t`K1Nspf4Q)-!n0cWM$HFaGk2Jniavkk^?u-R|p|xRot3a1nN~ zwH;a1>EsPL;LkFRd@kBllr|^%X+Mp@4a7uYjM8CPv0~eJGgNQ!R)D}T{LcxVkk#oV z(1Mf$|0%x&LyCd0T;LWOt^N0PMxF5bo#2>I*SfCD`Sr~H1BXvq8-Fv};|Yeog9Siw z;OhVxu`22B10?9|q%xm>12YYHoIN0 zT}kt3$yt|qcYg2jgEM469NjoFed>}+Ha4rt$!D~~f6rkd*&=Fb8 zL%`e#ZbYFdmvWaZWfTKi!ooM1s%ign`ilGZJZ}PN8SkcD0l?=f(GJ4*!YrPIZMHA5 z3eA-<|Dc()VPxz3_RG@F;%10|2)(jaP^sW5n7Wv zw=Run;V^>!P5fGf4;9BV&SmIE4vkf)`wlTm>bRKKqT>>Y9=R6_L~gSMD$A6f&tNZRyJgj zB?#}R%91?p88F#0@usq#;(nP!i~8=%Z#23ml1I7mYzT7T0uB?J^JT7M5!>!qnUuN_ zqX?+JYyq)|nL;B!Ph&BI3@A_oy=w5b&Pu<;9rM3a`>Y*1oi7nLmS7s5J>6X&ac-EK%Ct;ad7kS zaZ@xsNfQ@XN0+-$eOmS)NowfLjUq#kl^8^lqmM_WonQ3wAQ(0(f=3X79Olh@a(?Of z5lWhOaZOT=KR~#eaHv%=sCV<#&{sc;jOMbh^Z<(sSl-enq4TeFe5`?w$Ng#mK4CF~ z>PaWE%~$aLSPfo*)Xa0Sx+R~7z97mh?oTdy*faaulE+u8eF;C_IkODGU%|{>HNjrQ z80AgI+T*&e-(uYJBvzZc!pjAfp~5fV32>SnioBxaFhwcHe($aHtM0=pyE;$NsAkCC zUPcGn8AyDd)50+c8U;38!gCqvGseamQmM{#j+o*CO`q|Ip6Nu8y(WKY3Q^2VncX;! z2Xdwu^$~8MSAK-A_XEtk=I|0>N9FPg6`#fw(NwBYj=1w=hUSIOgwaT178n_nr~%X? zlDd zy}yB;@bkeXSF+?^OUTIdoZU}ejS5CP9O3nDpmkg%*7j_esHRAV2I zB^iPi%R}GY#amO99?Hx)+bO6W!eD96=kbPdFmp#XE>m6?Sl$n2vq_vXF^wr@+&Ul8 zYB91mbRPa7vxM8OjboHdq*_|rizDAN+CGS*c_O{%oAZ&8f>^=bdV`v8Qt?eu>wS7t4AP4S8zzp}68zio+jn!jBTnFdi%DD!-dYbJi^I^rZs!`0L^n}T6NZd;a2=wSb&7*ie*9oB$(ynHp&(KQurO6wC4 zn4qPyb>1ebppsEnn`&DFx}KvCGhZEhLsg`(;mJ$T{)~63Hl4;_;jWmz)fP~9MgfVX z%)Oi8Eqd;`xq9o*EshnSvs{$M%0aoP5%n0eR(h2$b?TLqn;Z1U^Y5qn%Sxt;W%S9T zne<(1-fA^|Lx7|XzvL9X8Z0S$oFf=STE8j{bkoj5gVB_{IC9{`iCu>`HVEoNn9((u@)%eYV%*EZ(uV($t zG~Ohfrl%+)ybJ}XOxpCB9VrjF*t2+k3zsMNiOHMop~+Ccm*RMfF*(GG88R#wd=GD1 z>$BLihjn8{5O^o5J8OS4aiUpHSF zQ&7pd6KGDsa!){Bu)fK%zl#?w+%Qk17N_MSIt?Z60;D_c@8>oH9)ub!2 zVPnXIa(UZDCXBrm5XllB0@WXjVvh*=4PK2vFwCfwO8y;ZF`~7?RIA-39RDZ3n#UTx z_tepo=z^Kww3G3y6G_l<(@!64<=W>bb%+V~#k>Lm85 zgiiITiGvsy*Z6MVEFqz~ndJ&>JPmG^88YAZ-K$(-BT1OP7WPeeU_I2=^Ug){a|`xc zn8~5Okh#i`fE^l3Ldag`^u`_res*x(HCwAvI)B7WtwMTWaR#OaKN$tCh*g8M6@WQj z8~M{~`7P^@E0-b$>tgZzG_eZ!h|b1(DCqqnBs4J+5Jsgp8Rx6(cPP`XY1iybh==vUhAgZy#U2UzDk*Q#ee$&*M~?5AX!(tAzz;@^AYGDfzTV4Mwyg76 z(sEZPLjGRWl1;Qi66)*Hmw-Dg!BIc|->|ZTe3Hylnpk#lAAgQiFiHy)ai0qoro}dT zaw=L&1n%vtMl-o%olZf-+o-RS*_>lLO>BgCb1!}k0AYV9v)U-2G&OkQn-tmY2(}2( z=sJRV8u#WjD1-nOCPzaqaP#Z&Sr`|bg`;i8G07PB4TSF+hBlqN`Sbcha@x8p0egm`@KSj~>%Ts6n z?!4C!DkZefKkszi?xf^*^=)`gDJ?|Cp@X5LEhfTsc{MW72a%MCzLbc&gYmwlb+C7K znt)$#^9|fVht3#O*z{N)p>8!&Pssih)qjM&D7#2n%=}v28JYV`=ACrSNad`422eyW zaZO#FJF0~z;8;p_5DY7JYPW(>Vnjgjc1(<1IS9X!Zr+wr;H6SuIP?5-*_-iqHFxZYQ}k)*t@h+fG7gH=oC zzPHvF1IWPc@lLU)i(-Dzdo;PE#^oI0Nu(mTWK<6Td=TvUAM9|;Nu(uKAio0 zXYYh?JS37)qUxCaw1FDZOO<-O2c^=h<$OEBO2QNLDsYwiitI)u`=(D%Y#iL!-0TX{ ztzUBL=)vH89UV;1h>maY=CucTEBtKN?y&5Q*Wvf((eyQ;BV+#uql0QP8bZqx{AnIE z7a|~2T>h^Zi}j@=*=4ki?F8_wKpElxaR*OgRYid?aYRnOnj&73JTFa{>TgZB{kUt3 zph`uKp&;t*ebk2$t59WAaaQcP5TY_s?$XmXgQ{`e@Uc+}Rg4TgZIUUBlg0&eEzYKa zg}`XrAB4hf3+Y*oc~sD~So5P8(}YtjQ!=Vgftm&vNwsh(xY~Cz)P*tIWofOV{r{W8 z5FiGQ@(7K8!t1>yT`YK&%K2m^r24+iJIig15$z0RlaFxXptQ6pO>V}`f-;G(`v%Gp zEwVxyjN;)JgFqf(>9veXO)!iO%JWH>%}wFKfG*g%erw};rPnKYHvt*sactYa@)f+W zcjq+0@YSjH#eR06C%BY3re}eAs1%Kw*do520=o7XON>J#XLlImKYIPiW1WTAxGd2e z>5ts0gehOaCrtg^iB_Iys}_6|KjvCh*San82e@9r{Q{|4M__bP=@4pulZV41CT6P>|W>H zkU1K>MVfv(uV_e`IdJgOjfkA5i&7RTj{9~B>R2$0U?o;#_1jKll}l_JZ7XtL0|Z-| zWnaL2s}C>k>qJfnM@obvfzS`_gel@FY8J|dL9&;Pw(UXrqY%)Jf{cHGlWf` z{3vXTP)wyAHrLg_kjUA!ZIG*9n>8bXQ^2F7Sm9ch85t#!p3_H1`PJ(bPek;pCYup~ zy#MQ5%v)8M^t&Jn zeRIFk7RJgmhy~w54((7UBJR>k`qFkhw(+gA0iijO39gtLjvvj&hatJd zwHCL(kOj;x?OCMpwhqCd)zlehoN>vKV+T$)cjg7IW>Rk}nGX`PU_w{1eTne>dAZ%m z7X75Ew?fj+Sc`qdJ3PPM-?y=8^34V<^I}}~aIQC0TS}c4{EjdzB%{gI6-bVA!RIm8 zL_9$eowIom)8xo(IkY9HcAe!bQ@cV+t-#oKSQRL(NECN&pA(|t?L0nh% z=P{ga*rUf&gs_zN1+t%RSX32wu7G3>JZqzcRoTcN@X8-PlBa`)LPh)-$S(2IjMnH@b`4zhU z#owc-?#1DnZsF@Q(*>sjjdgyvND+d(!8yq4lhHXodI8M0&>6Yi^~B|$^Ce_ouXaq(?ld{@`KYYd+3MH^4@_kLZVOo0g?e(<9o$sZ6+9me|<;!w8 z%~PyJDb=S7byfaSYwwkIPxLi8jq%*Ng)ll6;w0!lowqw4*#!_(5TIB2YJS$a8Fj*m zf8Rpz>-y*|t)yh|`569Z)=nO=^z+7{1sb_ufcCz;%r$JBJo6rB;iG?0!WVz!fAHc1 zALs;zNFW`!p;c&ANhzsqwi1bd6v){bxxKqrvEDvWzDv3aj2jkhMdeXkbO|*(F_ft5 zNYbnB>L_8#kG~`{QeQAa($Y*At43uG6N}v+2ZpZ)W)om$OZ@`?ifn1#dmrk>qq> zdqf}PdkR!URLw8`Saa6oVk=S6*p^t&#F}UMxvY9nLs9Af89c~%WEobDyg5j}k!wCR zzs*ZqYS=mVQ+O^!5*;`%_J{im;ZNcLS??wgg=dsOw`|`zoC~zljmg+m;K|_cW;>}I zf6x7i4ASm|lJAiB()R;Emowf@cn(J!mN+Ha$1uvGBuw0{Zq3M|F3Y6|W5XZvimnTFuxw z_@cX<^M|MDQBtO-hwz3u%jj1%a9x&FZ63H7ee)0UjV_RAQ0B>)od6vlC$1S+o%gPV z7HFO@(h5)y;7OS%aTGwXhtm_{viHSNwdR0@F3Fw-9l6WhicCdIxZ zugvc~cC7DxCzE|ciSX1QuxO0v_h-;S1)}(CM1}9p;;nYo!A#z}A;X?g60Y6M6H#nk z6k@3XLqXl^yDW>l0+<(%(;2T&dON?PqqUy@b@&0>7-j-g@B-tc1`@_MlM~&V4pg@H zZ)vXDN2h{PNQiC7eSMwbQM)yDLj|M%WOSSK%JKkxo`>`(Fz$&sSY0NTbXq{_=1)cD zf8Ygp)1Ez^0A|INAlGLZjcmB3aEO=tH<@&1dbP1C9i!H>bv4)H;^TJMw{TJ8tEKKs z`>)OqJGpP%5u5AyBrk+(*gwxt-OUJq-rpd|RBI!F`4hZ+Vfcz~YNba6++t{PlX_pr zU9ZB(|A)A;E1VqLl_bU5prut!cHb7=9>WK!YlFzql~r>35A(bE8RMV2?ud>)qf8`t ztEn@jwKwPUa5mK0rw7e+>fS%dieS(9O!{UB(xlfhe;4CYrnpe}c)a{{a+W4Drh;lu z^_R_hMs>&*@DTpYld5>qAiYZ*J*p^e)fELEZ5L1G&1U6Trl@J6fe^I2%E4uu`U2O! zJTzTIetv8|QUSQVsi<0?dSxd^tFw-5r@#c~9*pEFBxqk8M4Sp@iwborF8pAwsD)D3 zg%O2+k~u>vRm(b=%a841UrbnJ5#HaCMd`;8ency#wF~3zbJIWJ(xAj?{8dH>ax<$+ zb}G_p*xcZy`PRimQ{{p`>f5X=&J!cp?J30ug|UbF4sCph0-GnVtJ5i3jr5&7Eu#r> z)PrOPZ)_&dCTz^gK=-So8u>7B2Z?PTgDRG&k_PQ{8rZc0JRZpuGt!WS=3qw;96myE zW;834ApF`nJma{aZAc%D6$c~l#e;3UETLzHSaBh6q!C?LsW45f6T>ku3)A;xnlPE> zYEvqO&xkm8^-+RmfWo~O!S6lXyD&OtRj9(beGMTeeJ_$aa>L@Tr7ctf5d7tc)R z(myQYQ_9B*N-vN(ItI}{1w^2%OH~wcF~lBLij~F%2u`a$BQ#x)=UhBI=faCFCWwgA z#j!+vn*}y=!SwE+Wrp95K0T&*RYJfwd|N&sRFbpa1sinkovwb+*?jS9ZvB6h^`>Jb zbnV|I0eVq)&CTI%Nk#Xw1Gj(8WJ8-Aw#Q0f`xFnvw%zc;T;SCVgdp^lc3j*l`(c!v zz^OA7>psa_=)TJ*H%IF4JT=T8zkU*~A<%hqMPkP;P9^C>JQ@kcO|Xf|QBjT`;5l1Z zSeUyIf|%Z}+>os55P8iou@H4(pE10T;6#D?~H*-0{_uWOE#`kYAR5Eq`dfA zHzd&{3LzT3nEwfMc%>rCF$A7o4H;5_L5onYT8*28y#J^0I8l|!Z%pS(iGl{ZNqe2A^F+o+ zhP)=w33O#-lD9sASLSCApE!QtKxvZpUK89+EK zS0e^A9~|K4oMi)HFP;Z(;Sqjy!mA{wKg3K8G8Gww+5UWbdNa?DK_ARSU1+~Ou*8gq zIc+D=j^a5z%L{8ML%)sM_db7tP#A4~wccq};@tl-BW0A?Qn3DJ6bX<`pbNrx_3AYK@ z^WOW&>Zlr0!)bqXn%rDh@nD`$%6o$Gk1{SzvigO+yxso=jEzDalwFyTFbh~0GA?`#W>sCA-6)ISu z1wj^fLxz_p^P;48H{ijpcv+g4{<;hxGti(Q97hWLZS9(!Gjkj7s-gz!iE$#W=+bxy zQfSdPoMf<>iU)J6Ukk}aX9R2z!R9cFRl3ABJ$p-~u=f?oj%>cl*pBZIng{vVl zypv|n;tT)ew>y?OFrFxU6xr;ICsM8*h;KbE0F`6i$ST=uGH$uTCH|hzBrCJi^*V;K z*fA7u`p=|KEksXj_;5C}e+5$|il7Jb>&V1C;RwlW1uBN5tyu8tQlnuXbc@oofodQN z_yFT!!IDCu#fEpGFYn0&K%c{?g zlK3b5r01O@r3N!ANr~7sBLuv`d5gAhDfR9ly_V-aR0g51%*c3dgrvk~F=N`ppemsx z<2+1Eqa2lUBAUN~-ze_5&)kCdq{!b%{L+;#@)B6(T;D=?1ZUO69-De3K%> z`aa|H6bi1$%5zc(SYxief}w_4pjpL;iV9U$ zQviOtGX$|l5f8bvr`1F$(i`}}>!Ic8DK(DZ8U%Q9_oo7A7rZVP`xUp-vdZkb63fce z=H`LRA{!;5Nc=r`02zmD zkXmYA75)?Oy_>(l!n?++(F1$yWpSCi@SFoQM5pWUBqZD+AL?vAco(oLP;tSuuqwH6 zA*YM+4fz$ZyM?O=#8TTG7cLXrJiBCsv+#L4CR3$q=}9Qlg5quRiQeBjNT^rf(E8ms zl)E*rLOUr)Z*qf7^OlVCb#}`w5{=aUb-g8TRG>Rj&&_yNM75&Ln&(9@dV{woehpo? zm5F{+eibLPyW0fwW@auwNF3NPa-o7WH{k_C%PH$euI3K3kLZPom&uymqM6Aza4QXR zT;kQZp=(kNcJ?Dfp z96ey;0hRX8`;t9JCT#Gs1bf4E4MNI&5yLB3om2nSpw#dxi!6MIpAj59v)jZ&Xh3fL zS*TxF7H%Mg4kxq7#vycx#?d==Z|mpX9;tguVMgG1S?4IxY>e`d^m?Xqe~?WM#Sm4Z_&B z*6x6qW}mw3$4l%}QN>ODGuJ1C4uDgH`~7^1Q>x6y@kCvJ_hMP^&e?g|BP`mu1Pg%z z#4GsS`0)01vCawXyo|S-r%FE9IiEa+7xM^hA=bhXFAVY0KTBkVB~x;)F?Ir&~67@!gm3T zMJiNQW?qbpIk(60>oWHlEtJ#QCU~YYM1GQYV<=hJsZJsxz04uuanS5dD)AV`x1-G> zSm!^|u5oeaLUJen02v)B`=hoZo@mbVQsy0*Nr^Y{JU19zw9ldpEU(J#5BVxBBa%rH z!B7wz!IxM0NJoe*?O67`8GD3dV$#SJjtYF5DnyDRFNZ)6!3=o>@w=`!r~tX^u#Q21Tm^WP zBT5Wi%+Dy61arT5Znje}G!)+nqJmdwb{-bn{6bEai`~-a)g>&r(zMeW*-X5_`HYw4 zbDK^bLdG}1$`18nQ%~4vMcwT1=Oy#mL}m?!NWN7kIUssiYWJQ@@}s;+mxLACiA>{j zlfhzvruV}F{)ZuHQp@`&LIpv=%j>CFi4kLF4#z%KcK8{>PS9(bRTWtr7HjqHCIW|Z z1Ga88V5vlavN#He=eT}2$8N}TkHrf?nICOL)CEF_a=J-|=${C!uSUNGOcl z2pC}($WeT=DlPu7+a>91bK~g2%P+a?WVPV?A-A!zLiv}|^qP!?Nhkt&k*JoW110_7 z^Bj}{4O4Gwv@+So?UAw78H(0>swqc8}{%u<`3jq zbGFO5e@)K@QrvWupDEyfU7+2Dew20VDh@9B|6TpXNPPxTpG2`x4pmIu4Mw2taO!(2 z-uFM5l=ufSEOzV}HAcWT%`&M4#ipeMU*#<-uoAPkirIC;ag=0n*nuY2!-&Fpub>{cvWIM7Uy;@t=e=m z0^Nrhw|i@&7rlKVD%}`qR6YHmZ<|WAkR5)2Ke?U0nVSIKVhmt4u~R1RCHA`WIK^T& z6|pf9LjI@BRVKAznIjp|yXq^0;%pn61gohAzk^4k;M1{K0WbgUJZ}79B7t6HdJ9j> z4%+*PJk);yoLFa{CtT{IRRROS%JXAE8=}HC6ex)KN40c_;Qn3nGrRWe4m`b zH_nYS0k;0+n`~hZVjXdPseK8dEi%dJp3cz! zfJ-Wl{ED;og{A*~ap1sLb^Jxfx|UNZiyt#J){Q1PABVhd}T}wjXy8vLPAUr&uGqxy|t0J9CRxZl;;=(ZKFetXhgJCtA{lTaeN3T6p)miuQ z+L&so%gS9Z@qmR@q*TFgj`dxL0wdBu5Q#K4^Lq}3rM_Xg~pVRR{8P< zXjfUu`x@m)}Hoe&EQtw0y(if^28UQm{YbrH0*)?WhuHHFpeOdCMFjCCQV7`RI!WiBh_pWpAr9=HBwAx;H_KU!=AaWWdwDE+8}>HD&(@&)U?3D|gb_Y?7lq3|y6R?J@?u4bS) zNU7Z|crY?{1RQSaD)sm}UJ(>Gb_igu_OwRY$q9@SyAe;g8>4~X$pz-YI3?vLD^x1c zw8+S=SBfBfeSR1@S6O_UR6W_kb$F)_Xv?eu_O~Mij1|Z9;&JSg~CR$nV-txuG! z_dqmnPt#!`EONcwghK1JcLwqM)0hdz8AqV^nk)+-l_^f`*#|w!rGLS7RdjA*mCB*u zM4$7CXo3!jy$FsRbLZbN+(W0DN2_b)iGJFjY}55bm<3uanGb=Ndi)h{){PT|cg}q} zsayDco=Q+*MSP^<-ZqUi&+s_@+z*yMvQ2oKvVb+NN5Kigy-C5Eu;^7vPxi^`BPKIZ)O#M& zt|QbhJ`@!%gvbN_5R7io#6t;N0OF@=OBCfG7%ZlhQnH`JIPkc0ZYg?+u=DFqIphWB z?+;CU8sm{Xn{qG+S{1FI!egZQVODdg)xYd~f#Wi04!r#YyNO!A=o=$TO}S`@iWCd( z!B!>&z^3gea(V<$bX{fDY&A#3@W?QqPD4`43tze%RT&NYlV$U*=XuAk|f0q-0Uu1 z-fD4C6PV)(M0}BWx7D)BRZ2rOgPi86>}h4!LMacP&qEkY({C$3bW}!q@%>;U4%kV_ zv-~3ur&B9xcxqUpj}a{E!ojSy*Z)yX z_3GNRD1P;dn1nyAMuw2zA1Ei!?OT&pC6dQQJ%_&T-oxT9g86h?{_wQ z@VFAo;w{b@=Djt_TWi+4dD1yg19orEY-6iIcGpXIZ*ahd$h7vHH(zs%M++!#<)^xf zyL4jl@G8c70yYSD%nkkt51Y&Qz>&41k2y*xUD)82JmBz^m@K0umpYC}q?0Cx|H^t^ z7K7pfz?&I09@EQtYX10!Vo5T`J%g7rn#&){K$nO-A;S3>IVeu^Y;6DL@siAcZ1eI9 zP8~gQ_|cSdIP>782R0uRBkJ!m=}4G?nHwcwQ7=i@P=*oWf4f!FX zN%(4j!AuwRn~XMfr?S;Mi^y|*y|)i-({fhG(MyKXvouZUh@ZB-_xiX+$U`+zH+j1E z9HR5wQ*#tFnFtP zY$?7W`o0zNS37D}!-0cZw9quZK7Sb@4pW;4FQw_#!IP&pH+F3tpbl})-j>n;vohTL z0*WO9oS1RY-yyFiFs~wo>h|RukF4K#E?cUNl(G8ZydkxR`Ykhw(N8!PoOnM^>Q<0} zVrXa)t>T8s<1NjwWseIUi;Tk8(#5VR;2ze3FDp<~AzIOKE(Vnc`2K{|V&w45O8$TD z-UYs{qS^!ACrO*M4K#(&7Az2;KzUgD;86vHgfPe}r7mA>OVgVoY`~TOP*?acRK1nLqd%y3S{PxbA znKf%>ty!~X&2v*BaQx~HUzycRgoQ4Z({6Aog2Sseusvw#9F2(Jt01s`NX1mxQD7-% zGI3jvOfpiEjsU1>GVYg%Wc9WLapCpS0}v7(bL*txeuU(#ii|~sqx&MrHVnuPwuUza z+qK6dvZ`iT_qx@~I%Y_>Qh*SSCM?(NaDsz$URjCjhs_GRw3$1(mX}gs4;A;;qVt;qjGICvBuvyn{)iZus1 zg2Nz~lhZT8?;w#m1zxqLOFzq?r|sFRy@puyN1~!%VX6H~4Mau1ifD91x+=n%sAleD zmJi)w zZxR*e+hoW?!M_FNrUckcdZgth6NY;gDxv!ul8dE}zZz))CPK_McR7cVjew8^Ht?&y zS$j1z4n%3dIqY}TjZTg&STL8T4f?0;r4=* zGTHcm&Hd8kDK-4|15_9)F8tw#4p#<_Vm^)NDqO2ov8-pAw1ST#plLBC_k>{^_-kH;@R|%e2fxe9ad#*hKnM1m$)*?H zJkW)RP!DSDOnWSmJt|d92LN?HA4s6p#wuKmKq@5N39LGICBiuN;Aba;JKot)i@3Uq z`K?)!wiR&m{O^CKW!$-pnZOF>7cSyY9Hjw)-IbwhI{s)Y(%G>^VLAh_T8H>8Xs#?2Wa|X6+&YULZXC>1OIC4t+twIYz zi{CkbOIk`1W#R(oWVu^^fSSUaC$JAc@}I_)!) zhz9PCqGV2G6!v7y$J!px2TMz%z+EAOQ{|vc+mXH_2#pq|=B%}$r5lC?0z)BmO}7+w z1*AjIV`Byaodm1;P@wTYfF)KoujA6?b}m+maiwSL4BC&03a+^FG7xl(?L|{;r!rC}gf=-gy?=O#I z0XU3wZ=s=r=c=pw{Q#YWq$gR8HhmaLWP(U0ZfW)TY}DsAIx+2wdjp`{JJm> z(AD8%80@Qf95cKuwvZpU;nd{eh(Z(OT{^7KKjWvJc~etq;Bg0Rb*Aic)499noEiNN zouZj0Y{WOiWL$^(rheQ3@F92FkO%(_Kzvzf?TWT_>#Fe~HW`9Fhd|k2#Y@ z<3I8tvMeWTT;?_vu~u{v6qoVRh^LVs-Wy;WI?kE1b>0I2$U*2*FjX^mAqLGBBB^WV zO*rkt`4BPL?$l%|KGOc#CT>UQz&4>vL7#!>ixmQz(%QaQk2^50qPGt~6L2*LRf+3i zsUE%xY#3Hx?S(qVeB*~448HgvHW;h&^MFmawykepfgx>co9etv5Xhr92D9i#?zYwg zU@$?h;j33?Hwt@@uR%IO zgA!eM58a#4Qk7cBZMPic>-OC9AmrGyV^5H%jZlqjqRKkBTFc)PAcjo^VCPlUlZG$7tLuiO z>)1r)^RzlA$1d|EW@ASEvYbz1@ez4p_H0D@Yo#-!Rh@t!*>%h-lf*hRHv-6o05pua zxS$hQB;yF;&p!Lk1XO>j{Wy=2Ct^=!sPh<;6@hHPG_>;u#rHvr>>(L+PaY-f4e5I8 z%4^H#%|{c`CFn`OijD=R7SZX6F5|7f1V0F)7`8N6=;#D1W{CQ~$r-Q(=P@v3u@n2Wg309+BHag;F6nab5>@=weCU{2(xf}&;zy~6zv4Ga7HpemdVXjX zRxR!mgkfQ4dbJhqKosw94PR@)(!7iq*@P*BA9ITu^}`EH64O;uKky5?5o3|P23Ojw zVK`u+jh4wX00>$%T9m&{ixu`1ljGa#xJ*LlE1+kSWmVOB?q4V0+&?|}7`cbw9tc+0Ss6y1ST>9h<}s>-3-t2%KH zT1Q8E+O%o<6&ijpf-=TRG#$SAUIcj0n4y zwykx-kE@X+7}vUX1^M}9#%c{lu1p|5j|Rk*0>Pd&?#?>1HAf0aQMp_S7^-(2xA1h4 zbF_$R>$3toZ+^9iZw9|8uGkwXO`s3IWx5Pj3N{p|u}J;$Nn&y|t!(8!AA!N7G86$$ z9m9gggcR46Mfb5I5(DGofR(Ss+FnTbbPmyS<_#aF;(8(%b2U>+b1!10ccmGvGZ8FH zH-d3F&q{nh68BA>tC!s^A*?Qjy4RBVYi-o(rx7N5=ftMkB4~JtikR;9&>p8F79a{M zbzBtn`zH!;Et3e-IORoUh$TM1Zcark*BUq%!kK0qd)BMRDiASnTZIUHDWj~qA*S?k zqFg?LaOgztlbKsrYYs`Ch``X{Yh7AC4#6A%$vR~vO4PWxwkf$tW~*l~3582O6lZ3j zS#X`pF_W>f_I(UM?FS*7Yq_V!-`Pb&_h>{zesmDSKI2FP_{NL$@4TQ)1_^9;SryR9#vS8uNIdXM| z_fCB`O>@{_jwC?G>k8S^*PCL7SPg~D<8gWbwQgpXq^ zo7?>oDMyKPM6>`{8s|~3P)#IdxV%-$21%0Xcl~5FlrG9Ahvif?u{ORBK(6SU`E@l@ z)ttPYINXV0PN`kbK-1^)PK`#nd9>WzXv3~|hKv?UIHN9ej1-jXm`X>Q#(L1XT8it{ zh|_M;*{mQF=ZFZ6b433gBkP~#mHZqJimiA0zK_W{sAW+x#Q7y8g+5T@odbW23#G5S z5)l>nZW&f$q;atWS3!tuj+cp#3}VfSUiq=Xx2Re9eTXu1nldkm%frmPHbxt>G8gP& zFv59_<$}wf@ROCeG{IEi(E_N(4BtHY34&YngKt16OKE!u8R*m#0oMjG@gPkiXsg(YKzpnP5dKt#xxd1nYLix(GC0i2s+oXD z*UL=797I;*c9s+c!E9&aDvK;iG-LY>+w~~`N>}sK8Ic3-Wd0>Gm*QNM=${6Zd8lI% zjjtyOZ)Xw@Pc!IXP5P+W;jrLrbFy`P`?|J9-0rQdlf-@)z!=(Q8GW&+M-iPrL~@2! zR6&1Z8#EXD%V_%$;)6(;-SC7dL6ALYDN|Sn5F@`K$ExF685e|Un5r_OK|hvg7>s7` zo|h6sily3vIO2x<9DeiI9(Eu>UF#*WBk&tygsU|d=?)c6Ys%HA=sTH!Gy3x&i2QgD zf1|_p=25Sh{nqDzOvj`^H2P4C@q-bW8P!wl-a|xleVU(|(kr+r$?qUASe+$X{)!L` zR&etdY*s4Z-y#!~GWsPA8OQ-PwCs@-M)n%jDx&Z_V8D2qgS_cn%6%i*j>&Z(e>0GnZ;Pck;UkwnQOK?~xnb5b9T^Afr%|o~0Jg5;v;al!O8f$; z(Q&Kpr^|W{Sz5*ANQ&=H)h1;vLDa-03@UG^!xes_%`uwdaVT_@<{QtHu?+yKuE&|+ z+9YoKGi{uGhW+ZETo;ZTA91Qt?1*(p03m>dY1U()>&e))2$%08L7*w4oyx! z@C09Dn}0#|Nr1l*->Tu(s}?NiuI&zQy*fJUo~)9ONbxvpD07I_QkS{-X&SV>9!ls& zq}R-@17EM>6c_3KC(FsrSk9P1l@E=s_!%o57QL$tC$x=E`8y3UrfG8mL^#AtrA2Yd z)I23|C!kCzImZn1O0QpdOH(7te#Dg9h(rQ*FiWUY=kg^Q6)zz;vRU6u1@l@aPPtU@ zQ|r}bSelf9>u(UOHiY!R=JqP|Rp@-3FI;nq@M&f#pPa^BlQhbL<|Yd%PfC2SebYs# zM@#__U3Cj7B`lw)HXCifJ z^+XSg)+Zru56G5$k%ZD*)=#R!-lHn!nH#pj$dMcB^Ua8oCC@=7yj7oWA|xv;s3@Zs z$pzUbC{Ohf-o^^dh1WZ4$ecs+Vn8+uI|e*o{!gIL9C|^1-f6 zYKFCvdtD8~B|twcphs&2-Eu&nlh-RWWk(HX3(iC+t~}s!Dci>B_(@}keX^OR5xuyO z;qGUzY4tTCFm?c=5mPf#V1aQwtht_(lo zZg~|glYvnwE?kz;=OLr1^#zW zMe)sG*_N&1Q9c>kNJ|qy7RsGgB|i^AjEuy16OI{iVkCw0?ou~9bVUVbr@iS@N8;4@ z++DqKlIJdJD-LkC0HiVYA#DfoM0f zxzDVJ`m<}?Rk{b_LtnRQt~KAe;%*2Zlr6IWpj0Il{8EEnrofgt62WH7VitGAW@A(y zq9Cc5^{;Nm*>#?;<5E6ZjE3UXmP7NFb7EMAv)Jd#iI~5Ghx14Ww7%KL@h&x=&fHYg zN)sE%Xi+jR1CYH7EFN6D#(Z8xh+%037GjJ=&3u0ABbA#mDW^{nxZD{Bn?aat33(Ec z1ML`N!K9nTc77Z%JW(lGKZYMDOytHz8UT+F4AX*G3Kt0`cef!7`Y}~wCVsJrn1X!| zg3SRO-J=@)!U}-7YuL2oaBf`Z4CqCJm5jMo4J=SXtT;`70crKrZji9>8b@Wtz$&ER)eMQk0KIroV2B)!%V6$ z{%i!Rfq?3zG;%o0?uy@U8ggFNKDaYQj0a1^VOEaWasA74}UDJ`4FCU3Qszi{8re0o4 zNl79IYXq*WQrXO5V%gr-h*r$)IuIMdaUx>uSr8~S>9UA6?p_=A^r>X6)D90r+erF^ItB19ez2#juvua}+{M^~}RvGElA3Q5gf0M5}>=Sz8%*>l~jXJ={;K zBXX@@B*$Wn?yI5gDYgt}@2%-cYh)ip_%l)_0XNXG#Yd{$nXl5LmtCk99v_({#?M%! zaF4I30XkrrUAnq_!18N|E0%SwlS3_({%jX*Nr!YlaNB@o61 z%;)5Mc75piDDA8ty(&j%bHzM*nb6wmBA57(&AjMMdqz`dk(=IF+7|&M^4qKiB&UR; z^FcJ(bY%&Rh<3ws08+KfrV(D<=GgF8l5ve+_0sS7lQmCkmW`MJp1&9tnXcgT;D6kT zwN5MjOfYgyLDv&-%>~Lr8T&89Z~@m1XV<_+tZM}_y<>K($Z+9KX2tVsERy{cr0$%& zgQ=*Z~i0=a)PlPJk`3waL-)xmcVScfUUYFuK0J=YYO>j@{@&gCO;{u}@%kQ?z$ zD;DfJW)ZznLJy8z!zdkJnmJh zxA<@>K06$~^pztQ!v6|p85)(0Zqyb&jy6!!YVQ3srNW#$qr6;2l+~~ z2f@xQA8d>tMG&W2abAi$#Ql5tgoSGmSyhiIk&3mw%RAbn`KJ}N)+9&dGA)wjgGXY} zEJO;)nZG`g?%+_k{bQ}-TUEfJW$Qm^El{HE}=uUWD!z|Gw zACYK&`l01mV^SNeHkwcuFh|&xD zq}Th4K;lCrs^au$v!i09{rnj~q9|Ih-8wi0M~=SEC{AeVzDnls3WC51Y@HGN?stUH zyCj;X`6en!;|J3?7Zluvcf71^Jdbc~l4g#VN6|R%ge%Lco3IRmX)#VpO4*(gB#Ix` zG*}auI`bEZ3Xg<@iJ}#$tmXPdonJ$f2=+oLD|B1gPl2O8{1GoIore+3Zfc(Cr`YQ} zKu}X5c5>Q#a-`?ol<*+9PaEH8FLU2^h_L5vL;r&I=XoPN`u>JzG9K$jS&#ES2Y$l7 zWx`JNiZB@&IHn;?0iK707}#R<`f00q%Q{;-+PY`Y9uNe@Ida^OkY;bMXLb8Jpcx5K z_Mmq>FW*~ad}F*N#y1-O!)`2oDkfE*Uk;tlZvr?Ec(FDeJ&#V;LR7#5V{ zo9CD+0w=-&!iUEO@&Wr34Ik)JKF}BNAv>*_Hbm=TABfgjgV6fCLu)y{NZHxe0V0i0 zsQcAG>S&T{Ez_se(HBsM9|K(W8mHuFEY-F8VDb$j4_bnj%v)P__KfJS%zG_o=GIL0 z4fld@#JBUOi7xG zcfQ}YD)7rXh1GUq!!)Jmp|#?xP3kDZ*5wGMpzSS))B$l42Xp-kIBZ;b(`rasYg;SY zAitK})~PKqwWfFWZ0x7#?$JRIhKR+b^(I~Q;I#&{czPpgq(bU9K^g~<=AqcTMQ`|1b-K9oLit~%Nk z#8CIRtWa&_h6pGAi4+lKGzYj@ zrCGoh6==}Zmn`f{7WE~IXKw9FPU=fe+;*CUfq0`MLIz|=u4wo?4#vC@u`Ix}V61al z&>UfbO+&1E?G6jDP`<9GyLW}O2oZr%NFEUZQlc!OPgz2rvc%?%0C5VPshFIX8P%Ux z+MoBy`}4fBZLH4#k`wKJDMfTqF1w_k6(QZGq57K_Cf!`rFti}B9q1?3KQenZ7a-Y9 zoqmS!Q5eo`u14r<-U&D zvwf;g4{F#_#o{Sd^eI*J#Z#rw5sCkTP#9OIRwNkbI=4s;1B@x}^fw28-yG|R5!)2HOq7cbHZz3p27P*)f$%C-eqDEr?fyDLJro2DX6_P^J- zTd44{h??KzF@AImD%Bt|(vpK^dc<$zl(lA+MszDK{ff1d-<{@l%KSu6*PILA^J zBibyaQbwOrMqfN-3LO#c2Zcty64INgnfC7}^SVfVyJ9{eOzq%ezFXqW@+%AIQx?z{ zFWw5h?P&n0i#OJ6MGe-fqq@qbA>Mleax2=jPY&5EAHld)ZPRlA*`?y|cS0$bO*DR?JjkRLDYfwAj@i^tV&y%A}*X*j-U{-5AW)jAy15q00O zA=%NfmfKZYMddpzvV9!sQl`+SOrcMiV#|gAaSFX{gcYGILlzr7n0Oo;VS`P>eC`d{ z;I{%%aK7|&jy@{=ER~W%pOQjfy!0!yU%%u1{6A>@)T-V{1K4+7wL9cBwGHEv$C&Rijm>M*7u42irsuvvo1E*)x`&%ATS z>pOsoj4%%IX`)YQqEBf8<{=&RQv6KV;PWCAkxtWcDolMPz7L)O!&ogVI9e(~KJ$_7 zg!0VKzIg?G^9r|(K#0z*?E&9O!trAGW{Gb)-ikN}dl6{+<3ktVG7(0*(E||{b<-CV$ET{Vf1qI0bH z2p14c5-??gn0#-`BR%<<#94`5^#$9QUR?sYlGHfHvEJjHE6mZ!T$_eihs(HzwvIMC zK#E}!3#&|`PnkrYGRbCW3#!nYUoTvkc`s8}0IHinwbzdsxrSZ!c^7Q$slW_f9>Pz~ zBw7%>CxbIZ2%OkAZe^x4Z)IOHFLO>II`@K1OJ09%Uj44UG7l}?K3B3zV?GbV@tIqV zhZUezWLZgh9f89nEOjZ~qrGyx$9VO4@8O+}_uk$zyvKR1;W4`xAZqwal_{D6A7f3X|_);&lfCy`8A4M9-)e z`LX$dJk%DdhLT%!|K~NM>sp?#5eQTVCR>pnzk5 zB~Id?Zh9|>8AL_Qo_*%J70Wu7b(gfFIrLy=6&|%>*LEKs<*nt~OcYS(PU+LRQ~H!^ zma>GD1#`{-05}C)Lhi7hGD(h+Iq9Db> zUhVPr14gZg68N+tD&f;LgY|$`$xL{&;LGuv@XqrV;yp$dy$VFXjFOf-M&vds^KF2o zlftW4WZlaA3KQnW+ZJG}@*@$MCr%cv70>q_V~uN8ZIRh?EF{2G*xi)^Qm)cYpGrG@ zT8WmX94Uq8DRd^$mn_+K5ep_R0o|Of!yDMqY8qF7qjTpr4YBUl#BoF0y0)H*<*Ve> z53RbGd1Dq^rItRGTKbe=o3$3w^As9$gI5W)EOpctqE3o)E`_y`dbMe&9-16o7Mv!b z5$+x$5u>~WGr|50VG8LY7Ap&>w9%)u(WkW8Ol@Hmntk$?tx)^utqZmb{e6{reYFKW z6L>bj^D?6|<1l5OyES)f{jP?*zLD$-3b2V#SF}fZH~Or}73G}o%|P6!*ha#}MdvmG zYDO|E9}gGJ=-l{d$MD4nGl`Qpj>A*tG?L&}D3g>I#l0wSYyuthHGb)Mk7&-Zh{a zY4OBY<8;K(xz*%W7ONWCUP=1ArQ4wBWb@2O z?>Rw2MV1g*$u)yxDx-XCWTFI7YU6w8qY)Ez#Kmlgn5>GrsMNMaln-$d2Xp-&Sq!M} z?Ov621%ULcY|^K)NuP2$ppPZ;gj-_K{~Mfsg$xnO{;xQF${?H`bkQN;praJ5QYBr= zLHd+~^u=>fp)-kGv~RTBcZDfr4AEt4tyE%kBE9PMf4^mZM>mbwG{m}q@~)1m&Kxhz zY7n@q4313@Kj};gw5igkO_e?+-!dEXYjntC*3Od2{oQ#O6O@6ms5n=Po-ERT6B)R4+jt;Vr?a^u zPz=(|=sjd$TO?cl#>sY_ePjqS(c{*XeZ5M&_w#D;K2Riz=4UG&>*}m0@}JVj?wRQa zI+ac4A04N0$YVS9Y|?7;WT|%3Kwq9b?3b8nlc*!M-djH@E@{y*MjY;r6jdI#^{ zoqZ)ygVi<}@;eX1ns(^?34qTP%_|9iD??EhLVR1H!KgLWq_cVq2M%Qe!XT%%99 zMqfPF6dDSHaM(DI@h;_HMtJ_**R4YrZ zs+B%ft@J6yHfNTQPNB1pP_m9U_vBW#%EG?NqQ1)F?UkI+@wQirZtbfri4I3wgq1S@ zljAMGJ20s$n9w*?b5_x5AiF=ZlB}xHpg~74t0Sx?PU7I)QPJ+0+(R|msJ7hO8Cb5e zn?@K&mUCR$j$x(jB(btdrl@Svr?N?()|6$29n>f^TJ#R!@`i0al6T{Qv>1n1ti`?p zzpWj~(#5OU=>&>6Di>m;nmr~G$DdI(+a9S6;v|k~cy_Ckk_JDR4vkiaWin}1Cex=( zrcaq1(5Pxtp|dxKfdlrDXaUV{*z4Gkllb`;O<3qBd6bwZ!k34K~8mLXQ~720%VhSeLVWFMkt zMw&q?Y6g?gd-m1h9~BeNdW`fwDYe4twxfI2O`h0yx!y;_PZ5t=9kl$S&TS61*e*=8 zo}x=p#7P{?bvOHgU_j+mg%x_zs0^l08BCutIG{v@UZEol9(oRdDkwwo--sEOA!Z8# z_LqTFt%%Q}KvoN}9mY40GYAxMF59`z7q>QC^fg4Jj%8y0x8eCy5nk%tMLy^BNo=r) z)~iSxSz{n;^1Zu&kiIeAkBm=6i}^f^@aV7_TL~qUg<1=#8z}@nB|fGZu6xSIa;T4G zhWN;;Lx6sy3=PLfSTVxRkuWNS-%DDG$s6J;f$x3sO@{AU@xkK-70S{ooiekY*Ox57 zED$MCmjEYRWkNjGS$-%E8>-dvmidW01I7h0wt14jsM;Z`jcW(dTC&?QJpMF2UsZNoV*QGs>_8v4Se(;xnj8x z-JxdF5bL``azW1TwbizrV~+1dIbu~J%dXVZr_|G@blW^_ITbqC-!pj|B~P8*ltLP! zBQ#23oO9{L$LB(|ZqtyLLpRpY&YpqIlf_gr=~FW4Q!;H_uC2j~k| zv=wuO-u!w2dcRC!=}EjramQE`O<7`I}D}o@jj3 zB8g6X5GQfS#9$zZv7G>xo7+~kV_g!bM^_9`Hns}hM=dVnW+Yn$Bv;K3`qccOPpg0h zzz^sR?Ijf2^1nB42DWfy5(}}xY*XPv-Cwr<<~d%^-l(63bgYS`+tLDz z@Wien8_po=bv3+lB-dPF`+jnYd4$Zk>T=O&nfsd{=D>b3aw?LM%zoZUNIzb-nN9Fc zg@2+<3GC-rQ!6ttuGV^-|LA}OmV!o#qs~?nYhXq+Qhu{y11%Zd705i9M-86DkkzQ0 z%Cgj>tu``ECtvB)$yfTc+5#e(t#+OH-uZXa9P@e#q;bsHif-b>WZ~E=wr*ppi2%iN ztR}#3yOu;9kS87WXlV%rG7UZ2XYORgTgRO{agOr;mWhb2CEGNd+#Z@aRqG66%)ay( ztA`}0QlU?k3Vq5No1HDDLW@KfLZghKblQQt=$J8-VM0%6u2VLi@&VkDO+g49Y;XGPF*F=~zwCvi-{Gjzw))YiQws-&%C zkwR4n^r=FiPuUlgow=#hn$ zhB$3Y<_G?{!V@LireWWdKV-{lZ0pq1435;;ZJN&kMVEXdl&X?t=)tp&N4zX&NRkQ` zeJWV=DQ9d^M4KV0LPM}NZz}Y9_Q}+eFZBi3fjf%+aZ7hCFM|5JztE#mY!x8oNR?i$ zIT26tqkYX}CV(r{fAS_0D?ueD+>MV)(Afd;>K0M#ISVk-gn zzp*D4ceVyxM2Xxzu@i!rEPeUVyI~=(k>TDo(kGmYyy#PY(Wm^1?uJ!ptN0eiO&tuO z4f{V!RtvjzNn^!9% zjJv{moc~P7;!;6h;)GkTZC;}gu-OKN{ujuvw@g@c?T2|X5B?uofrs`0`_lu_rf3@y zsa9iwJ~bBTQ^5$RW&SWW1$s3p)oOeo4>g#dd06TOLrbdja}le1b~&|B>@{mz#3W?L zc)(cOPKlcP!Q&hmYZ(*0C&i{=$Njy%{M0&@^e@Tbj#^7D%c6RkKGoCoDY+J(#iY59KSjj~o6A27qO)58&!WOaoW${AJnzKo8u#iBxz{9qaE`4^y8Q6P;D%b^#ED1 z?8I0nJ{n3Fh>wd--Qt@C-$%vAxhow$B|rl!f#zABj3P$JX7b<=n_D5}fMVfU0oGCE z`I`?1g3)-}`mGR0=Shf@IJg?{HjNc^S(JZWXZ^AjXSek@r-G5WN|=+hcw(a?m^ZdpK`^21IvjOjA(NsVOQ#4x(B zrQT@u%!K~J_5FDlLnjnqA97JIl^kC zQ)qPH3p1OFH}Bfj`{7LGq?0Q1dh^dpXvJ-sgy8D`XoB3Nqs}+tYn5B|K5oUtNSFAlxm5 z&{2KWMWI+~!s}7kV%cc(1^7nFdhX@oqgZJ3(Zk{SfFohl#$&}c+6&(fU}`SAO^`;m zpO=FF056UA!E&Z!vPUWol}k`cyz?ad6L?SdE|hTc`EPh!LF&VAI>uSY!tgf*X^RPT z<85JYpB(Y}CdxGL?C}&nx%m7#;7~ zU$*P2ci!8)H8J*<`(E37%huizcahGM?mus2?$(p;-}v{;c$`SEJA}_Wn8Yve4*&=B3Xki7Tr0)TMUT(Wr?mt1Y>^@ zi4v*KXw{!hbZ~CfG7DSvt0XoYZ>xR*;;im))7wy6_0h|7m?!g~pf-f7{@=TCgohDC z5}eZtR3I#rjGB7a_%c8SstnMlGC-f!v}K2_X@zcCnAteFXrVEEGJ8T3;L0Uv_QK8Y z?ZRo~-Z7b$qQ3gPzD-51jf0{ntm-ekII|#+K^yXLV081lyLt;ZS58dyZ7NLjxCwET z_HF!R0Rtt3`9FYi6npFxR6*y-%*jaEe=Ak%yk zWl?;R%!nS0Gn`mR@*40yP)?SrmN+C<7+L{EXDgNdQFyE{oXG$=;g5l9Sid*xZuoDazaY{;q}n=pp(miV849oYH^n`o50a3tk(M znb!Bw!pt4)(Y}KG{1H@BO?sybqMl;b8Bh63)t{R=5t2b!;?rjI)j% z0e|ydLY4+^q&JKuDv?sBv0Yn>;7}i9-_bUX`H^%$5@u|dE&LzBr6jz~P+*2@~ zUD&_*Qh?LM_f2>`L7M59T#|XPzdrF=Zhw_`aRK^|3m)lx=cxPpyAu8Pyo5w8#<;{t zYv-o17Z*>-jJ>Fsa3?*q@hy%J9@_jaz|^iuFFoUQ4hjln)>Q`utQZ{>P()rv;fuXB z@O@N#PvNaCF(I?pIihLuq+JGsfa?fk+{f{snjowVw9;5{tOz&$YSfONx#s*GtmsJm zD5wyf0#y@}WpmRrUM(XnXM0S%*-7n>l%tE2w#>aqyHn)}KHU2q+-B)d70jMp*C}6J z6VY(yL#>e^qQTFy@w?srEtXY4+a>Ani7;M&ImobsOjlW=Pi2Wdl_eIIY8Bfjq^Jtb z)y=cK`m?w@pJm^{7qrGW{QE1?7E9GKkSX`UI~Y_?mQa07G=5f<>-gKYv8b}PX^6Fr z8LAt4=Ns+>ZR^qVfINfcY`n6Q%Vg$?wY|7L&5p?^p*lxFpUzRxr_8ZM5zT_M3Jv8u zxp2!)-0aXhibFDVt%V+&o=&%A<`zPC7az6lsE4*}-LwxH(zwjn+(+vdp)qJ94+oG1TP3)x${sE8&) zHb@FJUx8PS_gHT}-s40|@9nLCe}Y&;6Gh`u8()COI%|};Q`KYwYl`Gs8_kW^gO1vO z(gu+mZ@1*IWYv-Q3n+AE;@nC?^!gK)iDg)gXT|PGf{p}hXq|U;v^ib4BNYR*uN@Ck zOgdrCu?>Cnw6{jyctOwoIL!a*Ip-)A}@)H)cv@3!c?( z6&`h8?)Li3CKm&?ge6^1&2;sk;*zd z4a8cs4n_29^+*%w##_sqbg%?;ZoJla(0-$%cjn1Fj>5B>?Uy#T?%or1=+spyMH zC7+7$0*MXB+fdsyqqjvdPv*f=tqmJOW_Ts%K4#DMhfU7G!fcgE#%N{Ir zR;EI4e!b{7*j>{2Yps)KpWQflQvdvkV}DRFdD8ih74|KeT-XP@r4Jq2vmKN3Uf781 zY~ioRj}mEd^*R@`W$V~2*MXSkmWBQECoPCEp3dHwY3#yyY;u3k8gKKquH2ouJ2QFX zGw0?l1ooo^8T9!sYybsNW*pvAF0M`#TvS*@&Axr(4u%L=bLLU`pD8cS?2XWgI2>1y z?mN49^W7Jr4BpZ+HjP5CT5bLe?5zoyTSZ)`+-f{z%#mQwNEwtL4qvfXfw03QYzcg7 zmC+zyhw3W!E{5+Ufx8X9L&bMDd^918z2Cy8)3>j{r#2fm2x?vD$nJwYuMxlVy)*GH zkoqt5ST%cytu@xW3I1{3op|r%-H-P^-m`d5@P3c?e%_z)F7e*R`vC79@e@5I`Z_$; z{R@SCVVA`-(}>iGm9FX zb$&B`)|vIE<{y1({?Vt6$}7F36u3N=9)W1nhX|RPYA`c!9>9x5mYDv7n}N{@9BX^+ z1KeM7_!%7U?d@FGzN)jWwIq7ZHx%&^-T)FZ49^8NvzU_IFk~Z9!;n5T4Czz0*}^4+ zbZt^RHvYeA7&c1{aQRdXL)OUuKMlj^b;7P;_{9$@8(B?7M^0?hl+hTccCwj@7os#> z158EMjGBt{si{bx3RBcnROsC|6;}cyR)FzVef|GFQ*mWvFNSL>e&9A9F&>@T%Vuwo zRcde0r}hSYS}jrIQKA2@jYn3pR`36JjK@@@+B*ydG#)<_YdnTExMM}03vi>mv(+j& zq_s+))+&8ktIV6uT9FEE7RE39JYr(j#%YUv@*$dRdbyu(l9@$u(x8?W2kdHTarko) zvMu(Gg->lM9<0!Lr-aP6>ojYgEW+xdDJ`K20vR_4|7r0^LKDViNB-t>9G+;rwe8yw5u300*AoEdVf-Zy8 zr_127u*Iy0%iPI6{a*&KcX9DVccv0+rAs-uGR%>p3aT8D-<-` z4wKk$ynaq37*t1hlCVrH17*o^!ExwB$Y(}TtYJWwa_E+?2 z691#YkPW~jI8qP0sjY3x zdsj{7&_IMUH?gBtzjqP9P3>trm*xQ-ha0(xQFs&Mv9CRk)lv$zi-*#Yo4As1JaoAt zUGy=IF4rrObh;Z8N&9TE2{@AA)V&# zQjyQlA*i74$xU1?>G6Wg_o%wKG^o7f;dAVgC zBQS$=UU-$*LP(R>7{AP%4yPM4pv;_K$W74jC-2nRGGAhT6DC~dJrNQvv*zWNne93^ zF&*y&YJT5uobXevL04L+$sl!n@`Ns@eH8;PH$gcM_+Wiy zCKX}A_0CaixZX7{x88Z!p{W44CRHv3%X07Ej;R~FtWug)o zzm|zBYbvLf*|NeMGn6{DysyBJw`_&0bGE%SkYn7Rnwz+mZ-T+?>(}z-hSGh7QqGSN zSMBHGC}mzs>5t$GoTZdnKwx}Ii7QBTUyuYoSNGCJ3{$ElbY)|bz_4|Yj?6=c-Z79; zPR{2h9ux?|@uqredaDyCHN%pPHEnCwc5fIU7sU9O;n*bM*cybeOs8KS$1#>!Id+6e zg#b9qG1iB`_#7jyAk~M%sT|qhs&bfPhEhj14ip%z^IY%9mhn3Wa;!vPZsQvd-GRPr z@TwgRVJr9;yo6cv`8Z0Mmr~juF0;{fOeruvrGALOcZ5?plpe%g2M#ncE0h@9U5u{-$2N)TEQS1@&&`1Lxp2s1V)yr<_jj+b2TK(tQ^}6U*K#j zVATtZ&oSc4n#z%l!vPoOn4#2Z1ycluc~JUCU%m!KWGUS~X;LJw-Tn12D2oAGtD)V=S|BOzi^v6lXccGy>yujJSeS-3o5ruBJT_VPTFL zN*#`Iw3eG7#_SxU9i5xFf^UMs9p$&CPnw{kgwpRLj<`y{7)L4d8j4bZ2~irP%1-Hr zfjLa6me6fYGTWBW)|9PJIY#i@$TuFksUg{T5+TEqjX_$|jRy5p;n;RyWSOR16~{4_ zSvdxy*l|`ih)Q65juBUo>MkHfW&^B^xsY=Cgd`p_?A!*dGuw z6vw75>FQY4gI()uVmo2)&j$5$;TUaj%Gmy2isKl|tQ>pIMk55Yo4$#G0^@UxxPnwX zeIGctHO)kDm}7=ghhrQydUpVEr*hgp<2_shGVlmax5PhS*An28pkn~ zd8pPTFh0kKD@c{SHJuE&Fvkp~j%=JFFsuXeBVIN>A~0VP2*UAj(+IrnD1cV=P#hbo zH5Cyd+$kP8HW3(Erk$}IW0{ANF@f@qe7aAA%aN*#`s3Jfu_9^z--k^_pWc-4W_Z7WA zNB>03uFaMbnD6k7hpr-2%NKy$*jjexT8EndX*X0TDj9M(S_EAeA#yL9VJCjzF0L zXSF(&fWY{2NNR&rqr#~iT{jPK;W{;S;mBc?z_84$v1eae^)Ka%Tc@WA%#{K`IBYcZ z<*?DvRYg80s!$f<`t~)JZkJN#rIeP0%j{@%f$=FNt{~OH;ZzQ#^8pv8R7>beZ?(Xj zg*RtSf4%C$e-5OyMqqM~kConPpVIRUrPUrOZ2=5%{q@>7N|~2ZdT6-JhSD*J7Z{&X z;tEpD3a4@?tp!||QZ1oNX`R4uE`nvNJ>sUX45YMPU>@Te4_%#4>0^e{I#Mce@0bv6 zO|Ipz0P*4SC?2;w4Fba%C$9J{9<$=-1IyDWFgNjyhpxddPhP$aL3t8qgEz$WBro<8 z=gJuKQqC8Kd1yG#)+8`K=ZPywH8z~e;d~R|!jx(WT}qn;rVZ~ASnWKn7N+7}6o3QDe*YJCUYAj|F2zF1(c;TsR#BB9;d09T*{vAx4?%o49P z{8{9YhZh4Q%XHPZEe~C3VVRXRB#+N!` z4^mNg1kRPZGZ7r-p`qR3;WB~YIGQ}X=z^c1BBSkexxl>6Hy*lWzK%N9(6vlx{~>56 zu6EwKBF>c-<~0R0YQ87;y!uc#_Qb#tr1cnv^IrjaA+g=>Vv5zt?@eP5%3$DwTjVa1( zgivqfgFxcC==L~DnU_-fbt3VdOR2#4loD5v>KZ=n)V!l83_ru99tjaSP~@|R%;qW(_U^k zwq7`P1TeBpi|(|K7m%uwoZY=giI z&asaP%=h`mL$|?iP0t%jHwdN28%lrs{WwaQ*HDxSOo-ATRdz~04$NUnwS;bK+9)up z=5oEyt+@4-fvxF0fw`J*JaijFve5vth9w(=XxhISj%^f<%>_o5>E$29ag1eFj%~2f zER?^R!%_;2&oSZ(Qk@@8<+LUkZ4r(cN*&qwgurl=%I(rG9Qv;#2XgFuf!W139=cD2 zIM#&{42xrf=$|R0EoN%$MZm~1O~2a`=e8!6SvmGCO=R9~YZ4frW5gAtx-p!};n*h; z6P7VUsl%~P2@I|69PpzybquBfSW43>8{zdfujX5d^(1;(e8xPnx71xes@^~MGG9i~)E=(eUU0&@!9l#SMpRopa?(hCJ9 zHxW>}B_tbP23fizCR1T#V11?Odme7@rjKG|MH(SBC8ovi)hX~zP z@EL(&k7p^(_>{hAD9s3^mm(=~rG8=y<<=?lQc8afU*OzUATU0q#1*8X=y3dAM!TAV|Z3Vlrb~9#-#asdn$zcx5 z)O(-h7-Nq%Fdn%o;0v6P_5>FfTzCQT);lm7d&z*1?}S3!6`xCiB-@J5y(Sf5!Ye+P zNvcA=@u;&U`;$pUm~g7kAtX031#crh0)ZFYfkXyJnDDF-Yf$B9KLYs9%1<6b1jd&i zayCeHke`AcSAH(X@38!sdUtff=LJT~o9lJ1I{}6aDRo=Q6#{cD-+1Ug@3)jw4PBp? zmO@oPT%X%!x#hML=B1P#K_tGjloGqZ_>>Y?km@Ku1wAgMU%>A$rCLI_rCcd6don3X z|Ki`4oHdZrFAB_;_$CFA1g3fItRZ z^k5vN%u6X-C1wAgMU%~G%rCLIl(yt1P)+tt?HopFcfs}qt zV6NjE58YRNO5ZY+epM)a$<*mbABv-tc`2o<1&e)IO4&{Y#;26!3R0bI6Ucv;(yt>V zOsSU8rSxinsm7Z(2bFy#4<%Eu zK`_=^zwbGR59Ef_c;XpC3Y_PC!{^3!L)SN?-U&cl$3J4Z;Zn-Hlv0jZ0%s{5iy(pV z>z%lQRMaVfb1D5Mg2R+*30+FB7nuF<=FDx~0X(q9Qo8ldaYlG|==DCOe>0R`FO=>9 zN{MS5)t5MzQs$+UQac3BQkst-f$=FNt{@fZ37kvmw-FqsR7>bmdV|1d&rslv$UU=S zAf-17%y;?5LwAEu=|Owi5K8!d03fbEJQ_zS^HNHuY9jNtlosH(!1$CBSCDFUIF)0a zeg`pOO0|S8r8f!8-hyLFI7*q9 zQhKYwqyH?WBk)sTd`gKcNOfm8l|!l2LTLZIme8g27NpiaOYC!h_}g1zC}p{FW#6Es z^cJ7ePDAM}Lg}#pm<;F>9*?7xc`2oL!xuQ0Qi1U)C9WXVPl6=yxza1O5TaB|=u%3X zEH`lg-Yi@E-i$j0<{`fE(B1A+daa@Kc2XM3{d<55OO&Q^b7w871{ek0KihFbYM?~P z>{;SPLgJyj)6ado$^A~@^Qi#10`b>AVfjqx9R?=o9sV8)aWx6cqjfrqB>6YKfrPq@2$ zZj2vmLr@oqg#aL~e>`cq;Zn-Hl+yP!k$Jn63XD%FaRsUV5l-c_uOA{NOsSU8rSu+w znTj{9f+tV==XC??UB>c>t%SrwcaKl$97E|nQtzz*Ag+J?B92n#rIgMgGks?%9f=Tu z@hK&)AXQm7l|$*hfD2QqC3GqMvA~q$Jpz<|uJ~yf!YrlR53&X4CVs~^9=add9~_hhy6S7v`9u)ENsuATWJ= z2C77kz0VZg-|a`h^57 zD!P|OzuTp`^OyY)3}M2v2Q0I4j5`+tXE`?Hi$s`l*Vd4C@fsth@ecp23 zl`)oCId(^wb~6XdIR}C9IYwMTs$YatIUM^b;KCd;lsYo@sKB)1oq&uTvA!6}kEL`u z_L#u9v(}G>IQGPFIX1|OMwj8(qr$Pffsti8r8XKND6`=hQ3;ICG2#kR ztq7-bIQCzF3vTv9Ff$71UXK2nkrpQl>YBm zomM)K(kBI`(V_H-kZk-O^u@NO-I&XL-f-**;n*|4$TD5^JIfNcHL=Xfv1<4NXF104 zg~0e6Bd#D-Ly!bMmt()c?=Z&OFs^fchY z95a+U9FzHw#Myx4H-azNi&s^$l&=2ymEfVhW;ynZ-Rav$NJR zSDC_;Y6)FRe%X3AKv@huba&>6dRa9Q%!MtQZ(s zrmyda;~2}V9LwG>3yjY(;tEn_@0VWyT$p2qQm0+KC@}xRdo*tCy7g-ZLH$HI_B(-D zf|{|i@nVQ$CuHN;Zs^{h8IHXu96K5qS*DuMQQCd?Mv%A?DI+XuKSWE7lMj9fVO%@& z_eiPt$q>aa=biCGn+Cg}Oe(@?9c99JoKp!e0!HiWe)s}s^%xD^7x4r$z@$V%z(LPr z9)WY)@*fZ!t}nx9r@mein87vwe+$euzVXn#6soV~+3IUIH2)QbKQBprodS$3)7gJg z?mhb{{Hszz%9aR`q@s=CYQH}U%;0+dHGy$$>_7UH zjwwbY+#iL~g`kwU{`F=YrOZny&93JK#;26Hf>hb{{Of=V*Qu7!rSwk%Gq|4rv%s*m z*;eqUP%Bsm`eH4{Xe$^*&zBmG{Yf~s8W>roU;ibJV=S|BEW4f;7@uRr6{O0p=ieZW zp?O+EsnZJH6qq@9)5gBL@vom$P3PJ_()%Yw3jvPq%@D^f$i}hV(DO?T$KDi8iKmIL0z7$G*+N_|9rt%D=$)93!qE)y;kidR$HW7yJ%$%uwoZ?5_fI0^U4Va?Wl4 z-93r zDAl^2?%owb^>ETvB~y!-a86P)sgb+ON}E}v6mI5D_Y4P%P?SvWW-4KJ8LlfeS$u%J z>vuPA(mXKh3L&IES;1)gy-Kp7t#ep>05`*2n2x#usutGevYs~HsomKAu@6eHt+c_> zf*1?98zi9ZCMn}#`J#kOu4q%)ii#gw#iIIXBGUbVrK&8IEcaW;#|HWM zD45^+LB*^rm9CYx5^w@M7TPOF?JzQ>fca<~)j2>VSiNGj-B7BF>J|!c?x1kZ$y9xE zQI%9oIyHRI69xr6zHETvqf$=~nQDOIL$U_Fzj$y}oiaMBxr=Q|mFJKtjM}z*rG!`2 zCtF*g)0H8^=iN-L^F@+w`k>4K%cW}k&$_#+9^WT-IutjiwAAz+Go-`OI%{ZKhr7d< zud3=?xpp{8**1Ph&9%ts>e7mqkg|tRx_WQUv{3ABHb0f*c~pDpbo-jFbb3{~3z}V8 zA><;Aob3TD5^%I1QE0>N^Q0w-ey#8AUiCqhD#{@}VGJ%UrNo9c>EQfKCM%?LF%>sR zLR)PO=!w_2fo;R7Yg_m^(?59Ki7E->G1bJFXt4&N#4nVo^J=*>mvSEML14fHVqj?) z5?l&ua1UM#BVcaT4xT`5BEy8iK~?ombJc(<8EM`mD@Hdv5ZSeIJWy3^Y*o!%2j2Plfo1zq@bO=hNaNM@KEI%U`$}>lL1{o zmJ|Z2G&y#+0}TGaxPZ+a{oE%~LLODK*0!Bvv(8cx!e+7DBPCU#mTU7vok*kz1S@Or zY;9Vb?A$O2AO;aER$1Gs_D(ZK7!*2L7Vd}~GR@$_VBxqZ+vvkZGL6iN4n>T7*d9+C z+dNgf+KmpUtmHw}Is*xNDm$yyP-<%HUXu+7MUY0;*w#5XqWZE$qD2W~+~t zqKebC^<@jn7S)$2X%UMkT&2D(6zammFxph64`@8ovo|%rt9LN@^`>E^f_6~Jb*%wW zBrtkHCX5w#K~>kOLqU87t*w21_L`yPM9aq+JBnlsAg3A^+6USLpZijMTi$WOlML#DD zgSNahJ*{khX?e1)I#peMYH4^(KIb^}*4-;R)}DhNxNOexb0#N~P=XxsnOr}>@K##@ zJH*{$$s*IhJ# zsfJESi%O^(SQ*B$GiKU~_1Op()sjpayV=l~3Y*a>bV#KX9A^|0%+VNqXM}3uvi6?J zwcU-Fmg;D$TYeV$>(W_}7SqtQaRq|?6g@ckDV4cgs<%A>0Q^> z>dlF-j<}-wl|6KIWJxa7ks`&}sv|DpAk_gD`lX8bP|_f^vnX0Qabp~obk4DAArCI6 zz&5QEz3MdD7{?rc;*1i{pfQXK6C6&;xnduU5J^nu z4ZdC@2snX$Y_V18}jFn_La&h$IsnEqu0^Q!#Mjm`IB!2JX9TQ1FEGD(*$-){is z4g;4h-%-GEuYq|;{>SJ0b-?`!@#kDNbiP*r=9>mCTfPOrcMD*Cfag9u+}sn#=L-S% z3&bz}+|c=+1DH=6xNP~7-pc^ugq`w3(ci`EMhY}c9zDEJ~0^)BpACK_mFH*ijz @Uxwh~%l8++ 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

>b-=#n-$ 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

wpNwcG19y&|6@Pv2j`b=l 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%0A<6F8KDxBv-d#= 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

&I|G*2VR{w-vgF{@Ks#e#JZmtiy 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 zðbu`XJn?^gY)?G)WI3*DeMyAXA@8N@KxMW^fSG=T4FR>_Z4XX$;kow}H53i9R6 zUA=(Bp$Bm5$GyS<>6K*c!M49nZJ1InZ%w{9kYX#jjoc2+<+6h+JoL!CfaLehh{CDT*;3&H4|| z8NreHXU-89BKnk!e{klzD{n}bJjsLqTQal7_AL@C?>9DOzpcwMhTTJ#EmB3i%W!#@ z;qoq{1b-+`GER$aybzJAUZWQWLl4{ii9fc2l{f38;eqe~Wl zt?X4Mxbc;}Q3d$Q{u~wKKsL=_IhFhm5)eOY<9~}ByQwzcbZuMgmkrOZ)T_&chm|@Y zs;(TQQYK|QPrs+gpJ>lDwH1*e%!2g~$l+2`I&_2cC%BwH!R2EUnqLurd+h)9v2k%Q z6THHDAzerZN@#$(@& z!kOb0X=cjZ-LtXy$bpyCF8iU~aH2iyIqUbIw!g1xe_zf1zS{kLbJzo~*xynWPTa_f zsHpGzTWTtTwWQ@=#*%5TDCW-M*FE#K^NY_de)HZ(rX46W%=wRZ7r(UUCzb#4uB-cs z|K}>5=%x~yb$z#7Ra4@DaM`7I7t7V4Uv)H~H+8RzHtOxI^F5YXX6fw&+rpk7?N->S zW=DNN^eS@`9TCjC^b5}&I8?f26??Lx@-5ASXb*mw@E;4toZVtuF^X==g|`VMRmOxh z3mAlbHa+3fV!|5Q>x?qtmO%btx?hr;?$nt&zE-~%XE1H{R-z|amje51q=v3AvYlz> zdvaZiUAq%-F}>^x*tlE)8<#_s^`E{fqT|&|QZ}&Iy<2e?AGfhL_kj*yz2XmF(&CTj zTQmtHIGGTeRAk{LuR2q4avPbn+5JwI>jes}w*GU!v*owd{FWr~2K609=j&HtBg*JE z;}t)F?Frxf*XTzwkoB|l%Res4OGjsAZgj|#Jf5xJk=Mb$YZ;{az6|2>Wf0fQ2*t$2 z%pqr*td?nWsDLOJOD_VtYR{u}-99Kz#uXo1_Csl~toV=Fd+$`lo{ih4b@%Sy)UZIm zdi@&oJ6pen50g)z7w0ge(PA;*?r!%Nw|ur_e+zmKtUbJbTZOA3d(gJCZ`ri?u*KeU zsA0uqVa`_aD}KSeijVzj+Oj{2wm-YOdt+C;K<_=+ca;or`f9DRYAov2I)o%WnaRXM z&|)4y;a)Yfnn@PjB$u1H-fgaC`MuO!8{~4V!L0Mp7s_>`h3=B;CFZ(Wt{cqt3c0>0 zfUNd;W`6IN-}UDDlw8-D>!4gt!hSPx?NW$8(`=?z8bwuQ*GW{HCYIClHPQ1G?I^Qh zJ2QH*+(+B-mSdui%RR?TL(H{Q%>rVVCxzG0#s)%m~g2?<`Ko5BLhqWc|X;c}FPYi2Zdjg>&T=ox6I z*ed7=m?*JUpR2qI+G<@WBz|a)`8hw7s;Jf*Kqo4yXJ=GYmv~&Mfn6$GvggneQJmEi zr=m;dY*^;x*&K0T=pao7WBfit_8lzhB!K+)R}@Yz6Exp~CeAGSnP}Crs>>?0ZCY}Y zFv&VqDuhk=oql!3U9rs68LIQsT^H$l(EM8U9xzvz-gHu3G$Pj>a#b79x*plf<@aXu z14!Or@!lcV>&@@3#P4%*`J{P3uG=m28*+Ir|DbPws!U~Kx^a=!(WUa7VOOSw>9Scq zGtCCe$wQ9Wuio?R4AIHazsh}*X}F!7OrvJ` zh364D7zqbl_nn2Ge*AwIcP>0kDteOVSvfE#PtyKWzyD6boO@$9UX+}o3hH6DGMAjN zu?fH|{*gd^=i2_^9cYiKA+8{1zNtQ$>A|2iOCu~SwKqDhzD+e_Ikhw#AL6np6{Zk_ z;Y$xLUwUw%M8+wk9ixT%{(2pr8Xl}S+uH>c-IL3cGK%qWg6(d-*Y_{zkVgHgtXrfV zVWr*lXuba9MdjlSXX^t2+bzuvGiL9-e45sc>U{@i?_DPs>wq86maA@kkWI`{_`XXC zzrBIEk(siluUXiBK*x*}5U+xo-+K;K_1v%Rko%h(X8PgV;=}uXJ$vr~#p>Qz{OZH1 zrtkFXlEvX3n;X?l_POW5fYV zFnMgTSLXk9x@o}!eR?d z)tJxjRCk^lDcpoeR`dZ$pWoYO1?^caBWSZX`Rusw(+pUZ}%R*l#*Eh}eX1Sbzc#mA@&pTH7hC-{OkI4@jvo89U9A7oR zAIjyp@UL?D>0Hh~eaS*O|Kt{a$m;>~dyZVo6t^zIT=FGWmmPBbRDQzHavidGx5#y` zx!xp~rzhjiwXB4U%?c{Q-y;!bKTMA10l)2W*^>K4;mg^(t z!WPO0&9zr9Z_)ep4N3{@PnGR6)Xy$rb~^UlXhiPwB2I;! zAKjt%1<}X#ULJi$?~9}VtM?_*FZEs-{YCGV=v39dHF~<Ch9%5goaKNwlywU7A~)s1s#VYultEGttUgUgCpIEK^z39{9(f%=4X6TWwg09{tPV z!)EP2O>AK-Yd=-k66Jf%s^PKm0;^tfwBu(XC}RUc05EIr!y&(9y+ca=`~Px7V!_T3 z>)g*n%xpbb=N?nQovP>NiIEXIwyYzuRxqm7!6aFHfDEs2VkCE_4B++6BwW6kglp!L z^waYCwjI7c?(82^du7*iJU%l#%(1X=f%C?$whb+Q9AW$Dpc%5qbX0m~rO%kXwdsmJIk5(4RXu`=PLrYb|xQPSQe06FPew?S`=CTfR5gXafWsA=4QMqkFu)sjWb6 z-Yc$Y_O(s>F`dZ`7_;{QSVbMURg~Ukf0V-4Gn~`QE-!jLipkMt=6AM-bknx zVGi)QNWdo%OoVglAhKUNMt=c|mut}&UkU(#6{^($xLNTw?Hs{&vKGhmK=a5?tDHFL^Y)_UbxKY@uGi26lQqig}RkLpr# zE65FhZL$p*-Jj9iU+7J9K)fN_6tF_}Iggx#!S@QVt#J-WU__UCs%Fvr@Mi z#Va9@N+G(XDSmIyUk*>ynMaSk!}PB0w$DJK*Y5k{m4z9xSZUco8G4D&!7fl%K7Y=U z%V*{axt>r5R~wTusINMsO?+k!>-!-!qb}Ml7adh+mJy$(2G!a1w=Hs2+x%W>es?LJ z^VT1d>k^eZIB!T5GTUJm>Bci&W#RO7?4< zC?lpx7=>{3G=Lau8;GaLtbAV?b#og&g2JnorXRyCdUM>Is@sXN?y}GMwyx#r9pHY{!1!hLgf>gDbXQ~$SO#j+c zDl9)nSL~N%Auh<*TP4O?h4wSIE#H@HiZ?dP8%fEV)mX$1+o-eoU1y zeC2&78$ribcnS^^$Ps$mMZwW~gpr1r6prbxyv?tIu|xV`2b!;Jm;4xxoGPrB@cX08 zbQt&hJQ6y_9xNGF8mDs1`Hs0~$DGnUn^b>%X$qEhQp>qKQ?F<6Y@f*L{{G$}B`QRb z4OOYw1W^x05W*e1FF}K##!M(xwcagl7p~?>xtXh`;A)dxogEdQz#!;yuvQJK+<-@b zLD$aLF%|DpttGD0at|jp=0v#$*3)-9L_u~<0UOnT?T@oHupd6hhcopIhK``pYH+3o zJo9ZDyK40ehE1r#t;U&ZE;r)r2;nzi&p547Y4mq>HXC+PP2L3AkfTao{7~CxIX6oa zq{CJCOX2;%<_ejiXjEMqeP7*mZa8}LZ}KGVWjOYCZs_JV{9==CfkyZJ7huKWa!fm< zG0^Wm1nX{Nwi47`t-J9|t-JATA4q|9G8`RpM#)ezg5I&48&UE1#-GPue7GK+(-jv^ zwLc4<&Q+w>0pCO#l>Na{_U39?aJ4qL+9p@qFPx0s-!295_6sLQhCc$2o5}U0>1qj1 z^|9PpZYDMlY z)YyVzB^I*2NU^JcR3ivv0rW7H7#3DIS#3_1m=l!?d-)v?NiK(g3wI(?FD5DPUW_#5 zp?qwnygOmjGB{J-%`Vn?vhwb;Y#O~*HKttdVLPoBdH7Ds?8qZXQ;q3waHeX(J2+E* z9)()!jKg$w#$mcTI|5~^JkWf``6ygZlrLvZ7B83*IR`2G$byOK?5{w|%*d6950>ZL zbLG?7heYl{%6$1??cuUD#SyaXbc}4bb8O^I+&w`SPv%RVc?;KFI85NshK6K)GJ)Ma zujz@iE8O?Dbmq2eqv=hjz#$jI==#Szna82{d%*kDKfmkeG@f$3g!-p<9b*$-u}ZQx z7(iVet2Y=n_k!MFn@RP1gS{F9uuRn(8az{PXz)zcZfZ=ctBtwg2ZEMgv8kajCatW> zp9f~=KTU@}uess@?i01DXa+_|TnW!&u{X(H|(e+BETGoK2FK{gz8w1MV)9v^>t# z(%`u`tCO_zZ5nl4BxyI;v<0}9HB&TjGjOJMcCV7&X_{;uxUX#5G)I2fHB%;r`^y^~ zb7Vk#fV7{pBi{t=oXEEjA0!*5R^AZh4%}z(eI5lQ*Zg}MZzA~j&D95x{%N^uQuKu$ z2pOLry{!QAB=6gC99YrkxftiVAbb^Y1doA?%fIYg-V9D?Gv#qSQy#}Nd5)8HoK~#h zuXydAMR>>)+)VZN^h$J z~N! zgbn;g9Ft_lm(9g&dE(hAXSX136)un3v!`*EL)s+Saa2{gKOjv#b9ftPDvgcwei)z5 zjx+(S8ukfzg^FjK8&8!ThW3%)eoT|sXlBWr^FUd@I!JaKnj856kZP6w1CgKM{DZP< z+L4jpB|Q4RL30+oo|sYe zqr02YKh|cRu=xu-b4L6zI=-H-)uPCoYFdl#I{`IC=IpS|#B zmHoY4Yt+_}Sg=JSr#5R~X{aj9GgXCo=I?eXBB(E;R_oudq#x2aP8i&d=5>aT% zmq9P~TI7{YXC9qPetqR{SH7KJGa2p|8CkIoJMOL9)VpXMc3fSD{aJg5)@|85(8J^s z9$2jF*)YFr`Lf#O3kJG2ME|A%}sH4ZS^`>j=}E+weDh3pxh| zK;c3hTdoc)=;@j`eSPFT@QFp@-KCD<1q<2Y>y!mu-0l>g8%1DhEP5tSgRnq8fKaoF ziD+kl`SH1|u6|g657u?)gqUDx5r)#n`~OXyF*d1!i7XiACq`7@omh0LT*M-&315K& zGdDkX6ap}8_DRoBC2uwvi?OLx78_;ry|frP9?A%zDpDPp5S@cF%JlM$l!alzhHiC& zm@=1WHhFP~e^pq}t1y&OFp&i%l|o*QO3bcltfmOcsmrteFwQWYP3!oRQZ)Uo)EWUN0jZCIq@OccuF#1CYN) zcWNLQt2?NXcyv%_?MZJ>@L@RnqVGgjDukxtNTBx z1f;N(m*a}OZn??mUMvobpzGVBV8F-;N2)3?Wx=3x`sqlHMX58xv)dneO3A~k#~2|H zqxf(@q5}C8i=CU!85-vF7C|_BK1NNIo&Qnpo5sa)k8RKA>Ypwb#t?69q6=icpUUz=WSi<)o=A|_KAm-=*b5x??nllo*OV&>vFNpo2}3x{YWkkm^f1x%V%#IIyKkwaDf3dA z{tRa>7){v=3C3zlUXJQHM}jk7Q;xYqO_hYcriTmW7~wH7a?AysI#GDPOsfUsYkIiV z^f0Z`;i4(qC-VB~t(7!oUP{wn;>-o3DZN)PR#WnFRR7~haOP`z1g=9(m4v>gM+$~* zDO-iE%cmVxt|^kh=L?JpLpaiEx?HPtq-e@H1bIEdF9%8Ry_b0@O<&e`HAauM*v<>a zYD!*?>Xnd6jcGGXticg#swDI^Jz6l65OXh*Srhi`Dc6*ijYW4b#=&s3)pV^^>1fgP zR@@`6$8OijXpx47NWpL{j+j|Vk`xzh*93wI+A&I~?DGc)$C>d2$|M+TA4=e;UJt1} z`^>|g&^}5p-#*6)CdC+@aLd)#!WdCp`96QVVD1nMB820tefUn6N?|+pAWq5atvjrJ zys_4Ya3>b!$d=*3`m=t(R$hhs^$9WI2<-%^)pMGPn6P)CUdpO7sg4cC zs`NU+SgTTQM|Dt0mcP()>s_s<;`~lW39pb9924`@~p8CxX>C( z3*Q=;5CS7e==!6p4*znwH5Lg56CrDjT5F9pTCZAIV?y*rV94vEcNuH=ql?ADhofkQ z;S0b1n6VC?acMrpgg&+eDQ*^!e5Jm{QC+90h*3G-t5c9bKHiw0nkqZ*2rcH>Suobl z)WlI;98!69t^+Q#v$BtG=cR(#gm@x)>Z2a`BTmWCugfP2=03(a7?xT)bC)KSQVl!5 ziBs~Le~+rVj`S>|d!q|t8wHSy|X`6HkQ$ss9A|)2(e2Ss3XTv`l8mvca zDq=#aCZxoo`;r9Volou9<*4q_RK%#7FkK&UBCKjDWCJ1M?&wa+wfUBk=CM;#z zS(WlSV?>hMmTeNw1!GIO!KA>ZSc46zyjt0eyJ0D-((zk1bk)GH$L9!t%E9|yR$j_E z!F-xA4u%$6%7;)qNr5$@s~{11EquU~p5Oj3FQsXl_IXk|P03v_R#WnFRHubho~Et9 zg_zaxf#p%a7B|>?E+O#sr{qb*Kg6`eq z{+gyDCiFwvN0knrpSfUKo2f_=j4d7N@2IwhR9@+<0xm2aWmUg)Rtsh#Vom{X``SZC zE1CV?;dH@V&lm^8>Y#Uc0A(^>?=aj`8mwJ6G(0%WcQ#!gs(7h*#$@$(wUqJ&;7FN@ z-#7LvZZ2)+4yy_!miI=m)I!doA`F!5n~? zR@{Bh*KR7e;ab65z?d+EHP*wA)w-_{-7kZ%=z9zMxmc988{1nxBAA_waWI@|H9bjddZuW~Po0w2T-lq1$ReDir*rcF z&m+VH<8bD#bk}YmDA$gGos22MQXZ4a(|sK%L*13kzV7s__tX8Og83)LI2g{dx}T7nMyS}gMTKF_{XZn2Gm`66Q+4BdgnUVsF9Ww9;t{nUyU`?9uJcZ3#u8XVbGc0E$b zVw71~jB_~`e2WRjT8z9L)qx?EXEFM0Xfds+XR#i^C>9eU`+RmYM5L5{FMPIO{FS$! zz+!&~`CeIU93Oi^TdXHSi@gkvl&R%EDp`y&kJloRMljZ5U!o@^Vyv45>VeoeNxO zF|DcR8}zIg7rGF}ve=MdKFOFcghA^Yr)o_HBh++%$UuH)K8%tw%ma(|-tjV;Pdi z^Nsg=ClHxOzM(Dl4`Q(#3`Lphp0F14=Ik|8vG@wZ2+(&5#!nh!DIy!%3= z#G<-Ek9_)>OCNGnb7*7)VnQk`QGv?z2S54Xi6=X%rJ9PEkZQY7O+w62_}=g0j;f%k zw&PAXb75I1PuPPq7fcVr^%lWcPoRw)72h;;!T0$cNDe(gm!RkK7YXKH5buNcHNN;a zE(nsL?+F(R<{id37%s9tf3DW+A}PTR6dZXS@KmJ|yaacgRUi7+-A_9ET&MXE6I%7- zLdAN{Fypc{x*g-yy_$*`mE)5-B|>=!iM$%5_^r)FxT`GRNR@3cmfsgCg0YsTZjS1V zkjkrr(%S~_Oe%}{bx`^jSr_H^ZHv#WNR(UtQsftlUe6c@!zZofC($t^r5cz%DEk3i zJ^mA%NbogfUP{xmLYZ}I!}=i@t0{Ros*NF)r|D(Dg+8Vv^fmpIU^XG_tBi3lTy5<W2VK_gisfY<{3T>!L@Zqp5bP1ABFt!9~BS-aGNadB_ zKLZ!GNXqhl30^0dC&A+=ywO&ecfzCPC5Q?SK25-walm!<>@Qlc>mt=C!Ka}vd42X5 zMsMFfvW}sCqXv%m{ntkUGPtB}V2Dv^7a`>i@Dd4;3a+LRVqU>B1@=bnz0(t>PKH#5 z^X;07n2-wY0;)J-hDCSQzu~AJ)l|fUR9{AlnKzM+!Q>TVM~Og;N;^cSs9u;az1kPp z%e1&2RPMRVuMWC+is6FI;WQs&LV0cwDl-~Pu09FN*hZdpnu?f^>MKH3i1TycEJVXEe9bZ@C32p|_|~_Pym+!PJ2xc2$W~-+9KC za&Ng!FnF)iddscWTaMpPvc)#O)$o=o$U~n_ZLriEN zN~r8JCDhbw`2}O`LkS!ezj^M0Z=XAm9NI_e<=f{@!E_^@h+qF5c>gER;e&lpo4zfW zCm7>kxYOF_e681=VxQRnkk_79D%s~Q+)?t7$2X3B;00%&?`l58g!Z9?%08bq*&_uK zYWtvhggn4l`%nT$W!~;XB7$$9yKx=bN9pC;=N`fQ2jXe?x9ovE75wL3!Ng%UBj7#O zJ}+s#?h*UU2Y|f#e_zQy|0;auB4&8`yvI*>_L(zdNc+G!-!+6+p<#NI3hN%x@gkS2PtdVF}WPsswK^211G}K{Vj9 zFNiBa+Q?CTFQoFuHs1j*EJ0;?zXb0WjA9WTW@%@$Y)^^lW zd#wA7^MvI&ZJ+za6HWz3%5?5)))TxolQJudt)MVA7@vo$$sQ`sV&pX@m1nUBfD0|A zHTBj&z9$&9Oc0B#JpHu?RVnMbvkJE(NCF?WX3Ux4d)DXo+25p8qs?pqfV@_{UP)8t zr8ND$BGR$Zl-vblYd3j0s@Fp*Pt)(?PN=Dp(D#iW2xgM-h(+#QdFKn|n(h+Jjf@3{ zFT_|)muXFZAZ5B10P#!xE7RrXKu`U=JRv}3+)`sNnsHmX}eqX>AC$yo~-K!Um3g!gF+_PZDb6@;u zxeXr^jEt{v#_*{1mVeQDJu13yh3@3F`%R;}?=8$rY077nE*MSOatp?4N?wj?s$nXm zzNY_)lu%P8q3zZj;q6Kmqs+=;e4oYz;~N~a3&vWEyd2d}Ln_Z=KLswdnAX(ujh_kT1jKxQwQ{@h zv|wZ;i8F?uS>I^Un*L0D<1T1QUg!R~lBUc{Y5E+Q*kCkec?ia8N?wlYc}u|)zZN`$ z>rhiAq3;_%7t9orLetN5-l4~bzHdA$nCk_B2;t{}Z+rla_+I(OIPFGBTkPjzv4@~E zWjgOKl`KY?mBr38w<(mJU(jNLu@)mQN3}hq@_a-5KUkyIntHy$Qi??vAdX_}aMUq3 zeY(6h{g+_=$QTF1bJjOLuQh#6G<^yH^7_JGjZl6I$-I=NwV^-jnH0wxg0Y&Cm!nz{ zQhA!vYeL^p68gUJ3&EU%n6;qm{PZp5n*LHS{!IKAR?{D8O@ASp{t}v!*N5M&q$%@K znzGGx!LJ2^v6_;Xqhif>!LJ4XjpR^MC84kB3xcUf%rd?CyVFlA*YsC{;fURo=?hlV zzi3Tg5KUhNfV>_yKN$>GS6;v!rRinH5J+*}%QAfdhwBT-OH!mXx#gA%zNY_!$yp*Qg zW6%Xtrkq6z#+E60IjV&rmFK-L0T*hjB=j|XSukfI=I=efzUuD5a!t|LfsfQ^oH4v? zHSN`!zATzfL>ot5*H3`T5`0aWm(p}pYpm-jX#`_6B`-(CTI7OXroTmUsHu|B*AxR6 zU=Bjedis?YZpVC&rt`~G#xT(z5{ZN1cUIGDwWhxlO=m$<^7^%1#`860UP{wZJ*=mx zV63L(<*1&8SGnM8`YMt`O_hYcrn?1m4&ur9cf)Nz(CaO}rks_A`x@;QP0h+c-TU-b zd-K-EGumRi#bQT+BW3#5L}OduVw71~48`IFQwt^|O)%DCa+(gf27=Ee-q5@jBzmh)%wQAwWfa+ zO?#m!c|~Rzr2k~(UEFYQI(_Q2t1nS`k>E?34>93t^WTMPUxE-0yXsmLpy|Z#(^SNS zYedvbS@kO-Q;q-8L3Ilrghlu>e)l_J%1#r4l<72@2CcQ0ntj{BoZlni|;;z2^Y{O$lD9 zRi4q>UWaheIl_C6Sp$(90;x`z2Tf>R6OxjQ#N2vf_P5 zeZRZ}Ckkc`W9E!dWlQj@TCb`o>=XSmN|3y6J-{G+`%Dr(hmZln^VjV9wX@Glnh!By zy_hUi(-l!QRpy8tiPTsfY=4pDI-Q zsca&DIq)`AA5*56X)0pEGNn~jncj#q7fhLQU?3P6E9F)|MAr-#|;NA^;d+M8BSsZ_`s4w3P2&oPfs!8g0 zG!-!+755WzecU(yw(skXYO|&yCZv)!B>F*}*LQyOMZKEJgiAFQF(K8# zl9wrqT^lZV(&hD_rXog_#UFHvDhqO+hS8A<)#U6wd1Osd_4G}gxnSz)JR}Lm)>GC5 zN0nDuYC^xB&b2Zd#;Sh#^_1;>EP52;S?E3I&-x%bD5Cw|Q~J#)ep=U8gkMh&BM0DAxcQE+v}Mlg-w zScMVv#XowrLZ5Q9VD;VL0?L%!G)#V|TXP;w59^ZeIUcPQ!s=NSxsjjYEr1_Q4p5A z?myljeNCB{()1Z~TZoOOoIVJ~YD!*?DrP!fMdfKapRxsGVoyr0oti(6WYJI z;)rrhIr@%8p8>`QxWH=KtuVd>BY2V>uj0cul1^n!Wz*$%9OlLtgTe0Ckmfh&11u>^J|=aChMXm zM(w=ag^y3D0M5ZEg)%FP9T)1ZEj9~eg0U8(OpdB0r1C7bOeFLzrZn{}wp=h6{=#09 zBgg;wk)`DpTOpXMfiZSiZY`G3nl6vhVjqFU$mFJ@&T2rp~3dU+m zUXJRcA(f};NoATU3H4ZOHAOjDFgczEp&J?$Hx@Ja%z^qicf;Ud{~*2sREpH&Yji71 zL&N=prMAHyeDbxUx4olxq!drB#wVlkfvon9!Fc?%M8n!*K7MAT9-mXJe7~uXNH+md zEcR?3C>DVOvH7qjnDlgP=q)AUwTmRniaZi)3(54E$fUhE)Hg8L(>L55kDrkc&iYP$ zF%>T^tZOb5a{0DwL%hBwl^Lq9N$2%RA`vI~mP+&sDZhNcqGIk1YuDnw%3yI*CXsDR zX0m<;!g~yJ6%)B!Jmr5F!#CP#{0YI=6Kj9+CE3F4xZ+~rq0m{;JJ>WVezb0*y^TlqsX63Lx6JMi_|B&y7)QwWKSPHTB8ln z?tMHe9*5RW=Myv8Y$936G{zeftqsX63S22J4sl{8)mUh5Ojw6VWjgUSbd7<}i$88F zb&d?v4fDvDS(f{(vH{g1M%>jow7S{U(bq*!UZN^PVX*&f?9-9j+?DI;D=E)n!HMF! zz?SDT`BYnTAs??_z6JXQihGnv&d5+_}oEhN^mu;5=(3HagYLdVe1z@|ZbmPO~+-cmYG3k6iCB_zeI4AiPPJaB%xuk; z1g?>fr_+g+bUK@f(=~cfck7qngTERCt7Q9z1guX_+CH1vt`woq3FVYhRRu6Xc^unHjr#m-VmP$6IGR@5`@iKC~7G8^+bY{2 z#naLNqitvVoZ^?j^!;|(^e3Kf9}NdVvZ0A^)S(`HFs0Nu*uQmE&-vw?v*@k~%nsob z#-%~@>AmMAdVBlPsg%0P=k=E*5 zOm*~j=1W^ONl$;D(woL+U{-LcTu0x=k&cZe_73B84YeYfs_@x(Q(L~VrMbCDY9j^& zX#c5hZ_oK1!^lVy7!4IA4*YFtOJg!uXiO&1ol5I#J5}MnB-7TCY)mJl;|U1s+VTx; z$pqZAjI&ouP(vGR&!ZJW!>UG0x`SBK*4)_Kl1?>31lMoru}C4^*4onCmXEtTQUO-> zY}_P+1P+@_R|bDw#kxTO`d~r797Cb#lV#%MI!5|BH?^a@yL+Sq_Xb6+@wVosY%bpd zLoJ6(^>3BoX<(Apc(RaeZD?vu#JAvsa$r)wq_j+b36GQHRmbZ`B=_%<8bjQYhWq@}g- zNX@EE{ad9ksad+Xb{Q*5)#)WLrZD#T|Bu4T*SDwjrO&7jki3q{yhQ z8$Kz$>xPcbbCg^iBb2VIgh_$oxe!G8WTGjBIxF^RFSd4^Q%Yzrk>5{HXbif$tV;0q`Eg6XQ`pn4JMw3n zOSUrOtIRi?19_cXB3?*i%$ZK4;|*&YQm3y@By$bLLSl83wxgF7v~XAHT+U2*DrZUm z@ckj#9B)V$a@lM%`Xg~>M4jDeJM>2q$0(uJc&@1#qs-0((tNfhT}U-q zNN^W97m`hljZKaIOfJY7o7gK~Q@-Jiti~HTVVaas>a9|l9%6-Rfd%q*={L8DNQ`SiA6bPrD1y~ zpwtI#-tqY>dk4lHpFoY6`_Qisw~q7<_YCxwk|pdJXo_ZhYK&j?guxhSe7sl;qg|8L z>i*Siw%*qYa0@S|(Tj9ywWoH}%o;Ru0}asL&NSeBG9G7}>1G(?tEBy7VVO|IFbl_! zOSSJya`}elOmky4K7bZ7XcqGE=45NCxiM+yuQL2nEx<~wR@mt;TRnD!g@nJ@patl{ zM`DLdt2gxwjhZh@(aR^&2m3cCN4mQ)f3qxQqNY|tz%|PT$F;5TxXkUviC7x@@C-O~6G_h(V(lb7YCd@nj;41+g{`!?@&8M-eyc)e$o~a#k+VHl9dh4Rtuosjv*{OrCCu zH>I1J@|hM-w9(u-D9HA0>A+|}c_xPY$!t?&CY5N8ryDYf({rnfYgT4g2feR?F3w*s27)HzqOkr~U zcrfENdD>^PsA!2qTcOZ~4^+!sdy$bkjgLszFWJ)7t=U4Mkpa26YPe$%=ibAav1(IU z8#8-7zwOY)lJ!8}vIATjS-Ii`?Vx5t;b`)`{zpszaBV^uGR_6-w zrhIEtD$&+z{kRvBy2-xkg!zbt8elEDG}ztIDV?zCG~q}o0E`t1><3*{DZ~qjhStVh zQ##)6EV!`t#PTxsA4H9BO&>Yho|tcEn;V*v4H#+4Lji1m4N1WvG` QJV8{xtUh;7NjQS|Umj>do&W#< diff --git a/lib/public/choreoobjects.lib b/lib/public/choreoobjects.lib index c5c3b7bbcf8efdd4e459471658fd48fad85f3f5c..d41a7b2f677dd7572506eee81f4f370cb4400a67 100644 GIT binary patch literal 2237702 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
#3v5@eV>$%d%LWSJ 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%0AAq;QY<_83N?!PV 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�jn3a9qct8oSW0p8AFKs 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

LO|ekB^R{{@Fafi81Y8hKP&xlRf4n&PA^ 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

tebmf(H4=xL_DOZWMzdSSx?EoXU=q@l#L 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�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�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%0A80MI77O;aok4V;d4@bvvBuIaG8X=IQp^OZ!jS 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�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 D}2D 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�^^DXz?6 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 z�VM>(`4lCs$0VJ=$I~u3B1j*53EMmiD8fZ9r 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

FU;zn9qM0+XCTx; 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

`&1CgGFn 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

P#>GGMPMNMD^i^wL|^ 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 z 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

NMBt|^MMe(;K!Z~;gh_Dsq~+%5;BJ)*XV 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 u_g~L97i%cL4l_nu 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%0DEga{UaM&*00z2g2oBe=6s@knfsFOQp%N^)^C!c zHiX`~e$!RVUOQL(apfUeyIF|`he#7}a?ylwppx6NEz!vxSs&Gt(QUq^cu#y{Oh;s~ zo!wRRHMP1wLJiK(WTn>o*)A}R1J{zSD&G3NBAC%Q8JP)rJ?q7F>LY!b3)Kx83;akOE z)fHbINAGMDV$437)d!5O&;-_ofWBC zn?LUT{Ay(rEGB2TW_m;k!@0LEen0^3SWyz(D+~&*2Zaw-cs<{V=iSeSIc4%?+4Yj_Pp}jVot6*TAadOGX&+n!*OSXLAg;iX5l2_!9km5 zxabrAJonLeCHy0-G=tgHZGJb2gLk$J4TXE?(DU`)VsEBt$^0_^k9_>a`)bQMZna zTfpci=}lMCoQlI3mA4*4%6La)&Jkd(Q!EJ0SFU`W-511bOc)J6luHxM3d@w+#Otip zNr+U|beAi`(oN}9%e62_PKTl1TxQQ&p}G*2)P1S)AtU*yb~4jVO@l0-sSHJc;9Q)^ z4c{2>JrqOE-9mTK&D&Q_oJ|O(G!KLUBU_{9`nAfzktp+qH=+vn?~Q zRjFzeOvo+cK_A1O^%|kl1WoP^R+5`|Q+x*6vtEbeNgg+=7*CFo5j*vO$wbaaZd})l zfrU-rB=m-RBP9gu&hRiYVe(|9<(_Pe`DRSy)+TX)P92K286Fi2 zS@NW4Y!#J_qS)Ymi>YB$E?s6*I8qHwr=(pGo2=|GO%guVOy~9C^vQ;`=ix64oSj7h z!SU1f6{F*W`wnK0o!~;CYSpGIoy84%d)bA;Mq;W4Uc-`#QWoGxwvn8fO6hXk(Pqxh zSCi~ay|Eo%sg$hXC#Xp9{tS2c^5BqO!o3+AY|vPkOBGMAMC1G)@4ranX~$gL5}v%D zJ|D}2lUzFF2MUi;2~O*4h3g3%;`Mu5G)$+ejz>}=`m-hR*|?#?hg)W4P3W~Ix-s92 zwo4X#rs5ps_y#%BJQxz`>!Hf9pj$R&%EwIHk#`!6uwYAOtrYK&Wt*9|TV^x$A3v~O z%;|W6L9>MMR6DRtnQJ@q?tZfona#jA`JGU$-7_SnWNI}HCvG_Ux{WPX&g$T;Sh2?n z|Nga#bS~erCA$U_b!(+YMlDd(4Pq}b86Ph$j5*gtF%#oE)_B6yMGCTV|E^?kWKjj^ zOtbVP8YM*)*EFMvZ_+FxIg3m$EOH%&5boQ#IC9=>x+%)5$=83=)9O=tRK@qbSOhF) zkD%4iTYr$vcWM4b&$q48EhN6Eln!Ep_a!Cg*D$iaM?dovo+8dal#<4dZpHq@J$w)=7936V4F#UcEFboXMS9&W9$u7^kJyrwac@uVFE+ zu<1HiQNPg7*ZG7wwspN5^Y>Kr+~#v_k@_f*ZC6WY7qB4hF3@|U%U5DmM_MeS=YAu5 z=?tQEcA>J_s&So7hayKdZ5`RPZmThi6R0-Wcoo$%A!jTv^&2;i(F&;VwUPOW{+fyIwUWfP`^~6Gj{FrN5&2Z zJU|cWcDR*mY^$42&!B0NC3YX7JNoD-v5l zvdys}gQ}@JpycPOv2(>n-C4M~f7761$!k@}X(HbCUF8W~;?B~hf@)#h;@_1~v59C) z@q3hKF}ibt?2!kp!qAQylg3?9116c9OD=S64rj)dh_*=>|1Ya56Qg0L87(|W(l08@ z9MAJLqw)05@rXI0iF%i>mEOZH$7hsUu&{Wn?<{T@FS{5O8h@|iVE~#oVJ|}v+Sx~V z+80HCvQL^bhOydC*>p5&;`rKk0?^Rw;h>swgYbg`Ylf&BItz4}S8N{G!rwa1<8Hjg+@- z?#Zq>K;CVHAp|=09d?-o$v8ld3zs2_fma4yn9f^E`=h81|7zxiY;_LJX86hY&rgkD zQiX}bV9X5srrn$^1mup>;F9e}H6`7FWWDrTo{-C*nL8fvG9hFn{r{y>+-g-95;^R8 zqDlWeD;1R{2KV{ui!Gmn69w%TRp6WsZs!TCk=EvTO-i@Op>$hCJ*)~gg06Thj z(T?t(SwGYRQOT_EjAh)jV-2?8f*W#zUK0#2TD54IE^FaT0V{fU#zBWHE0B=!ca+Xp z6TN1UyhrH&x0NQPBHYo9GE6_%an@=j@n-f_<=2(&@r}bx3m)Y8UzMCio#fsJI|56` zKcyTEopdRo6U+G$F+wu#R7SiQSYpo(rZ>j48C>qnaZXfck%ZblqU=2QNSCfyu?D|W$vx#$jPSsB zD5Jx+r?k{Vb$Y83q+sX*7L!77t*vplv=oh(T`@94-L5I&|m2_c7{4#?0!bejiuT))qT_JCWK{yH zwkX|%HB1xMAqNbCxa} z{W#6mri#DVdFRuvjKyQx`6xUY7!O7hHggRm#t3)u#S(ODP^^JUd$Yz{aHN-^&9H>KvJ#hx%F&;xl-8fgN!h7x|i(>x_ z)mK(7`c~ojiu%xIEw9`Wb?W+{Cizc99P@8 z1;KZO(PqDbIm5Ga*`Hky0A`<}=XsbzS$|61GW(oAxJqDcWoo~GjI1*(dn0f{_b)%%Q7{e>NAw-Fzt{H~sG1WIB0 zWwm!~60*xRKdz$UsETOAc_YN@hn3>@+Q|szJ508y zQvr^FC?crNS})cD#+I7qJZ6nOoC!|}ZQUj3(X94&(O8z))PAXc}deyjg`a^@2D;@r=dN{>>5!!`7S^QHU#;K#bMnXA=<7T%Z~6 zOY$uujgQ2iEcFb>?`=_*jgG<=Y5Lbc4q zZhMtb=O>ek2km)oF;e>cwoa{iy1Bpfa(B}6lxhn|xBMDW zWOgf$vJZA&LYL8@*tJ}wYiLa~Wr&|f3kstCDK5FrviD^!)u&tgp|?nE%rj1tiR*<} zlrG&)x3u800&cWWfiM>+JwMqao<^_clUPWF(>+nCNt_fpqz#6cn?(%Hq;D(v@2!*pZk`y}K<7~Kj|>!)bVfN6O93q9?oM7*+$_9~o_+sl6b(v)#=?;Im2f z<|bG<;(*VCl-SKxwMaIQZ|Y|wMRlKz9p17ONGzu>hO&VaIa)_q{v@SMCykkuyBh>Q z=qUfAW~XE(MMc_r49&x9{zsIU>ujeR;@@qKvsnNRkjV)Z+tJTY9h-JRBr^D; zEPfRGm5R|ZF`M=kGLLng?~aHo)Q-dvG2PK)hW^|hQ&==f{R`XM;(COY)bq52(=J+D zr(|!~N24+u*t*?8ns6^Iwc7=AvkN2mQ%iT_so&CMLl+qZl<_(hiBr$){N_X5Yd8_+ zYcl2!^sn#4R}Tk|A5o(#fLwaDQZiD@=Fv7iphP!`KYpqOKMBpJE;&B`)NBsmAG{>|dZ{J9L?!1BPrih-}mg>0ElJ$6x)N zO7`hs=J3Ggb=PjVa>Oqw{WR6~_A1=dBtaa_ZUtd27uji70M_6Nk-RD388i%MqueFu z4ChIASO!=(0nf$*?lit10=?aC1(vqA^olJei%Z^ioATgNw%Su0mWp5EHv39Y$pUDK zi}PA)%>7TO24h*tTHS5IupwIhQI&A|UDmrG9(_UE!VW#98JYq4M-_rlbhNQoK3n*v zKvnCc6Iujo`E-~gyQz{-;1JmAEyGs~4BJ(P>-x8jVE!NAXg2!U2~x*A*n>DHljfGJ zaeFuzOJ9F2julpFoab4gBcw>0;J^YtE6i^;uivBB6hPeimnoid>Lbgyj4*Hgfj0yt#q{xryya zEmJK5NQ~8LWW||;;O=CIyho-2Eu=Io%iS~kZ&j)MQgpgWu{2E^N6N`Su4XD!oU{QL z$E#IJX1o63Ywa#=8?;v{EnY*qVwo<7T%liaAB~6pS_R^ih(v{IlZqVEY#@vmlVM(M zF?u#qle^Ah$Wo?&aja{VC=X)!+;q2}uPg7dY9c2tlDfwJ>gxXC&2r)G8n|kJE0oX{ zhxG`zYX{DgSw(CiS>W_6iiar8c|X76W{ZAm;IfM|nH<64LvqaQsCFF9tp1-8f?bxm zUrp(-#wOL}=DjpP8m+(F-;#uN92l0jA&c(;W#y~Yg5w5H9>l_^-cuNoA%I#CLZu)Z z%+Y@3x(PSi;$kSyb8(!-67coxeOOE3IjkcJH+C|CD%hOO_ch)taoW`#ovj^9mlzeU zQYs^lzHR$5V(Yo7D(Ey^AJ3j*hJTC*6Meq^<@wUNZ1VR<8myAWUR@p`s9aaLf-dZNueqZQ0%w?fm%2ru2)KoWCjFHL&)H>dY}~h!WZUuS%!p;X0PqTndgK>Lb_8~viezvJ}Ef3 z*|81ciiQov$CZ;#LlbnzyX29OvBg_{BF&Zjab^3?H~UgXQH<&xVj>o6B`$_(`3F_M zqtdTyHXOqsZ&5~WmvJqRR&wZtboUo|2T7uHzgQf0itxb6?rqA^u;COX3GXw&CL=og zf1uu?It6docq0_l{T4lFy4@s4Y>^~yRGCZ|Sgpvm*D1BDrx2u$vMDZm;b)o>)s3)-4%~Uf`1vd2dPmET{LJw$rtG)J1E?TRynTGaNno$?ZL8Ju)uDs9G%PyOq`2va-Vlf4!Eh znop$z*B&*sO9jzvBa^W%I0Krm_Qhf1M6$c**(kK>Db;AU5t~B{&DlznTW@x|kxl%k zC@~K*W%A`qLb>L>S}pUNTwhmo7)_t4k}e1m4_~8~ZkF7W7%|;8d$OLzK`@eqZ7}#I zCw`G&gu}0P5|(*=lFI9@>CWzF5XcVeKLhe~3CC^@y23SW{vE7x>ZjrX$BLhvJ)$w5k0 z(U&<85{d@2x&gSbtOHBf_F@^ig`O4vo#R(~3$hbcZ?TCIIQVE0!5;e_e&^%}n&^UzCHa>+R_B#S0kt zsuG!P7Ca_$EBMJ)liqc43VmPb3wlbzBGpoT1M+j0VWC=C;e%2a!P6ymf>l@>Y)}?f zE|yQMv27_@x5S2??o_T8_?4IQ_$w#ul|=?_n0TjUNFz&coi(}*HLNTTa@avBQdTSCo(&ba;I9|G3MMcAjB8eF0a;e1c$PdAs%Na1f~)Vn-N@8 z+@TC8MVa|1;>>Y_GK{>X5%R9*>33Wa1~FnRjv1{UQl(HV^#KxUP zp-cGknh+)Y^THWhu&g@=RnpmYwU^<5@)3P$I4XA|U&oVLKcm4i!mklgXJ=0>Mjcq` zSE;_9rHi|5>R&=_a!Sw^s+;v;A-K-hd(M-y)*ddrTuTDXoU4@+H-n8i?9@t-n1#AE zxs$WN1sM*Ztk4r|lgDG}%)<+mma(YBQ`APVN10)(ynk@z<|{VK3(;g@n=*ua)Gs3T zA(e(|>bo|l4EZgd?-mQ^`l2_EWPzr0l}j7~+1xhk%49_#pr>am)ds&1;eec{DyJ;i zosvHP$T~*hGN<(jRVD}VaI7c`B*G!fbImAxrYgH z>c{d>(N+bzIUa7O!%xwV7mNF4TvpP(t#8$`u9ezB;ugGeUyj$4=Gx$0lrXoko~2|Y zWWm6IZZ?xQ)h{&B6sFlTvJfB>y*it>yS2#5_C=G!X&9L9Ql8rsCfj*5L~>vK@2zO& z1043`3$Bjf`?nM;&QCvtK-8YPpgqj^`I`OSqhRPv_VV#FU=N_L^GbPxv<$D?s4D{3 zjf@TtbZEy;TMP`!&sD(bL2GZVd8POE19Q1N$_Z3n0Br11S|Q}6OU{ofxwxApg#=CE!^+5SN0LfJ zFUF*!3q3Nv>cpRM9Y(+`%QB;YG=1Z@H(iwfs({6xaN2=X@(AG5FZ>#xVN# zN{V`kXh)9tD{|-fHz~7oY5(xnjs1fQjG|wu6c#8EYjK5wk$_0NinJb_5o~$+kL+=_ zT%{`yE+60uFx92)D%0g}QfY+(mK}v&zl3Ln^_+t+uf~)JWbNmKz|4p^N zb?gUE8PPEn7UGI)MsW9X(}~o%{CtjVi++y%Mm-cF6?9%Xig}!gTGl>l9}zx5~DEWvQ=`*GzeUwoW?;# z9*`p{P1L_g%B!SG`EDUdv0e2`L6Oo8buO@lPpi^86&#V!EFO0gM{a3`?#T_ae7q7e z>sX?5u}gppHwd%y?XcN-Dr}J}khsTFl^CS0lxV>T!Z1`3oge94b4csSZZR?SJ;{!eGxz7mi^~sn#%aF(iSUM5SD`#579S!ZY-)+p}7U9mLxPMg6foO6^OmTX2nel+Q@`|;xMH<3>#jlPw!!-!| zrGB@zDi*RP#h_gFEPM&R6F*f(LEJ`lHM;5}ewjT#R6+NXR2NYG7{dQlB23CXtep>k zS7|I^xVSgJ;sRA}wuCc|erMUU-%+^^JBZ2k2z|Cd{T45o^32ygDj~1cLU5T&;y@-2 zZxGYJsjQEou8pxxU|Y=p3omh%RW00TvDHOeo#8hxMCFRy7b+dgreR1RH z7nK}sLxcU76QYCXzb_u#9XH0z?2Dd^&=R(=aTAEdbhLnQpFYJnB4m|`llzh6aqJL1?XtIfVg0yQlI=zRA>V3{2*te4KrXnGwH z|J((Et=O8&bm8YLLLFY)2j z))8B4yg=!u(TK~4X>~wwsFe-ksl94dH0ZYg$*QzYMTm){QZci z3=&2V&#@;nwN$TNl}j-$98Fz=JSZpEw(Df$GF=gbP}@u)AJ%6iLqMxE}cwmBFW2EGsNIK~8DM(QrGF&L7it+|H^{ z<17MLBoy5t;_O$L0jBH|R6xX0xctO<&&kTsTo#?WyTA&a!y<5-iSiTuF{deG3QUxF zO0AzL&aJ}OxuJ-r|4!*Ku2{dK3KB7MY5$NJ z3cjg4HDpn%&ZD$ANpp=rj1sZHrs6-7;$+HPthd6@%&(|W9tJvWi{5S3n~os|QG4U( zElK$H$O15$Pg8&SfCeqUOBor@O?-0@E1K%XMNNxc+5CIF&y4an_Yc+wXgH^n121{{ zxn?;p#M)TZ*}fXLXiN_7K7F5FC66dA`z%_&DZ z?w0N6!589vjtYFmm+R7Jzt%?aDU%iA=fr)Tc+JUtnT|4fu=e-rSvP+N%YnOl9i?RV z@Z&s-Pk{IEnqY%4w+FMY$ZrN3e77>Qy<=C~-=QS)3MXussed6Fgav~@^J3G(4Q-gp zZ>Ijq_W^hSM(TVNtIDNjx`(a0RpnJ)fgN=Xea9to*dB^DcZGj1rcO|RD{Jf0TiI@vs zqKjA(;aXzP#(v^JVe_k~xUzf7CFPHg8VN;}!DrSXFDVWN3k!e7|6`Li2`|%7} zi-rB%Mz%~hQk^c8(;*_xnZgnjI^&%%!qqmo(L^6ldm9-&OV9KTTA))Khs(X?`LPC` z=gMoT%?gB>Sfu@(xI;3V7wZ^uNB`#a?4ug&PaCh_*$v{@pzDM-Lj~& zOHssic_NEJme(4oiWN2@`vlJ|2bl0zKV6yGIV$M~BU{#O86+y235x?YCQJmK(1z}9 zK~0ZN)Kh(Zwm|A^m7wU{tRR7dgMIVo&-bgoxOxZTV|0W15N_T9ht0~Zk9gs#z@wEX zd9}*u0zHzOAz2x$XQ87Z=&IYFvt0dI@MCZ=V9df2$sTSfX`D^;$SO*oURJ! z7<`zlfso39*1*EJ`_hQBV#z@<_}O$T+p(m}=z6zAhQ$e&2r1QwV!u3%Y z&;82qTSA-#o5TGtm6+SU#UXUMTuQpkV=-R0UDRXh%73dYL3=atWO({9)nW(Kjeu-< zvis~Wg_?h;T$9yk>V63fZTY^E73pb%ZuQ+dqCiRJ>3lb3-Flfu>C0RjVE133j2mpL zq!x4dGgb`&bZm7HX90$ge^QnXzveb-$unsGb{;XLVSs~6%1iQx{<7|H?DgGBY)uc@`_%<+=e^>ACVUpFMZIgK z>#nI`JO6tgfw=63&R`Fx;ci6=reRY#-lU@OCf=3Zco=0aaG0`QuWZqeuagiaf2V{@ z6*9q3h(23~Fz+T&-VKkh(xdKOp`E92_iny~{6EU&fMZXu&aLG{-&W1TIj5TSu;5KF=9)==`7Dp;}kC?^IKplg&XT*tr}ymB;qWpWl9 zBIOQho4mhqUOe2Jad zp_pA4T1MSE8X2)hStgasgHKP3Zh~exc!ZQ5`KJ-doi;Al;{|6m61gHTxscW64aQDy zoNb>#n!yUdUJ=1u!?+-otM-ae@Y?fYZpxQaw{8;FN+)4{HkY5Sl2N6ADfCHvz)ij? z#jlXABRb$=A?2$O%XE9PW!f1qUthgTwe)ttSE#$iR^(?`+eAsW8L-mA&}vPf_Sq>% zXHAU8$tp;ApI|?5{nho%$5BUWlE!LxG>Jjx5>JKjhND|sLo_2q?*e1>T-%_1)?5nM z;-Xn95ZAh%W#luHx_mVojvV_I5=4v>h6~Tc2LnMY1Qpj(HuQd*U+ZEnG*f3h4E8eH z(Ura}ZcCjHx+>|qKE?&DJxv1sMj36oGE%!mHsuiOP9CK*2WD~ksdB@vW9u0@dcMZ7 z2}%4|S;59?c2dQe3uet_E%GqYRtMHF#M<{&(v&3*`Fa3GDB?TRU9bZ9@uhz!9o;9` zy?kf-9#U<>h&gFXsu&{<8{Rim!i1(-alzl0c_!n|s?dhwZl$U@N*7;90_VfdKdU@h zN~#oaOc#W|1(~T?&NYv_cvXnusI?2WlRK=g%+hGPg||`6*&xf-E}0fP?58@s%8xP8 zm5-?yJidJ?Y?-cnL@7mkG^ro*a+(j(*%~(R0j1K)C^u|fbPE+oK4RRkiIUG?d03#O zy;mhNb4iF(lG*rduD&(Y5q5urk)yB2tPyWeW|v~~v_l9X;6A%Pwn?&5UPU1Abt;6j zq4XkI)tA^Jz^#LSOHLOT29+WiFf89TOHLA{GdtsHR@DlB+O&0`SE2BHHiL$771kPgl4Tq$YmDl$wN|o5gJmsdjTUaoFsXGt^@-TR zF$2n6r=)nI)(mLdsF9038C3T9*v>|g_36d};7Y0?Sv$t*u{Kk@Xw?qv|2D>$y$E1$sc3t&EjDeO%h-@$8}Tl}gO_mTA-lO3~s*=27XmFzhD@mue-9HN8Iwo^rOz z7B*;F43V7fw7axv<5N{eh(~=Sc90R^89XPsT?|M?q_PF%MDPD(f79;~!T)#VB%DspdzRKo$y_ z5%xhkOqoVEieUi@K19DwuNB+x>>-q)*rKr$Sv{D#MjT+0rM@JeA7zRweP622QYGOM z$9Bv}7Bvit`@Not6S=gA_U7O@O#!{vB;GIdTi(1nqd`yZ(=Xwnya;yh!bwk~+m!jK z9_^F;qs8_7==^Ya!hSgOtLFz0w z5}12I&_CpT3PpTE1-0Scv($#V*U~;}X*QTDxzygmO|N<22l;nE=>Mq1&K|DZQpA-J zyKoB)_Dv*vTtN|wTwla$?aY$Z{&&jFOr27|XvJ%krsdqyrJVPZe+;#}N@)lMUn?b1 zW^9k(6}%(7+ARMsw1iDV1Lku~rPrd>5m~__N|=0oj+N3~Ih?KR9mt}+v)C|?<-S3} zx$*cWO>pi_?QOI3%C)nVcKuutbshw};DVNhye?!A;F|ieSDN+G4}bOPXW#%aqL{t+ zVx_M{*689;jBDUx4UgiKmMlt~TcQ3}+sb_1wSp%R;DtMW=-;@|6Lcqy3G)!}Ou|iaoXz=Vi?LLgyXOL4iK7RBOwsT1DQNpXPqcgqC^^sJ z0gzT`HlCAJpuBTfU|iWHZ*=8(B8_~n&xMKa9na-O^{24mvj-?k%4}{wGos*b&K|!q zHY#!DEhE=FC3F<*d~{@^{44PxayEUh29c#fyq_CO?N6Zl=uw9zr>g+Cf}MC12sbgmUze=ZF*YvUI`;p8qXn*|zNz(FdHzew~sGSUymJ zR~iC~A#=mTBioyaK>hnk{cPpb;A-d(9)zyFPmQlYC?ej_%m9m^c zYsaq@Vg4C=`?95^TY_KfAtglo&h(s*tAw-y-&HebA!hJLm8!@WZ22-bKB%8)PYCf~ z2QprH3+YQ05lRyV^RpRm-lN3IwTv(FSUe_Nyv1BI-bzIkV&4y68Z$6)_GXo<}KGnvxD;}Z)Ev-+ z>zT?jO{KMKrJ@>eR+6PYGkLADx?9E8TQO_fvRk3aF(dUVr-Ob-qy$Ho3zXyUqnV6FPXU+w02G7uU6qyGf)~i6wxiut47j5@QOzKE>fcnrbd*SK zVD4*5^ug1bJ>9vc2VYU9dqLT4T!yPJcr9ytR`lT}=o>NA^l37RqndB?-=$3D!MWg_ z!kblgo0456(eOf|thiZ8e$?KA*-~;%7l9Qo+M;bARtcznRZFh|+OIGX_aUWm<9lLc zaccCvybn+*r^Q*5q1C>h^4|MWSS;}M9_30vFIS*2n&#);-k`+f+?!wF(5uk9GV?>e zD`bh*mgSaVU5m8UQsBiV#w|EDE3v;&9ky9%>%*`;1gm%*?YZX4!9Kg`%(&FeN;1;) zxXVz=tCXZ|kCay7K(FLsw;5?_-vY}`Kseb%n4TV`OYu$JM1nnjs)x~#p{Bw zmnpe#REMse8<_lGqBK*XmT3>7OYtn^&&)LGpnfM{0$68(rCirW6TspY1Nnhn?s?G+ zT%rhW(S+>!-+2^d3FNxA`vpGD5~Z(5y>D3(+Rog_Lo|ixluW0mP}-!LpZ?FSEDRGN z3SGE{F)zg^q_wB1j8XeaS8%a$l?_~Y(EHY!rW~rrDJtYlnrT|x-ltiQw-+zXNXMj9 zZhi7s_R843WUW*}cVQq7L_To2w&r%SW~&kI15f4`VbF!j2-Zr!p8#8Wlu)b|Dn94=2n=U zH;$L5StMs&t*qPQ<<^$OamKDj)M_W_Y~h%hjSRz_&MThc_1OBKRk{MD*-C=hg$-7u zl&&ek(w$09;=wRUCzOQkI4sSx2$E(D`J^&Cdt}vV+cJ9D;DE7(_j}bol2{DPvNOrm zW~TnUT~A=XH@5os{&>n*+?w?U&nQpA*D5(x{dHQ)kKMjp8MX;8kGo!bkAx4=JI*rQ z^`(~0y(g>A2pR>5d9lAm?ANwT!E+di<1jb}G|U#c;wp7($sE=PS$ON}7ypwUWAp z$61`m#Y|X?&7Y^MkTHr?@^IJISm2S6+SACw2w3WZK~AlVfM(S`poZ+Qa<>L(R_$kk1}*L90h(2ld0ZsU zvkd{7RePK^iykwLw37ogtM&p}aSyU*UlgEOwYLj;ilx0fK(lHu7XN?w44!>yfM(TB z-}Tg%OAmX+F1VIHGE~*>*WWyTgwH_{DE+7GeIf0<+H9FF@=tuGQAzi)zvZ706BBR6 zg$p~kF4t+*YgcUHp9W{^gXi@ z&5M-7ho+<-en&K)Sv#zN=J*V?{@QwDPc9!=cU2a5w6jNaG;_{_!tGJDA?}3N|AXgH z?QfN~d0^yf0+p^`Do@7@?KIiZ4&-PDaeF|9SmayVxouw4v(Ea(J9oCto4t|8dcvqQ?by{f+B4oYuWf8(Uej3D zyvFfg>|8&uHvW}U{zZiwXZ-WCYCU(g%>(T<6**>X#k}V5Y~zkyBL{4MVA|;H8Ojha za@cmgr#5=~B@1@h>reT`+4o-}0=FNhs)n=lH?y`t-)HJ?Zf&!^&oGEII$Nvx$TisL zbLxwZJ0i{z+*CVP--p#e!|d8K^!ga-_5nZ()Z!D zoAmvp+F$E?e(nG0`}o@H3}z_q*WZo$i=Q_Mprws5LH}tPW%>JC{iXS<_4~j;JIo5+ zQqG?qYNJ2%D)OD%-$?p9agB&lhdMV8 z$#gwO^f#i19--(~`rS3K*`_;M{}HvJWme+gIdqj$GImqqFI3N`cJJPOl&0YNtopDm z7uf?tc3|yReFGg_`&fWx)lPfjX-{NQ)@n2x0-RnunE7ZW5471nK6};pePeSrjGw28 z-_jrwY#Qrs+-+SOTRi`L(=NHa(dPYPJvOt}#?$thW|T2qqg<=a2=f~*ae$2gW0{w& z$t}`9=~yyG>b@FZlext|Y5w#umT~6mY;$4eQzv!s)?;B({+TT3u(+-4#)kEyRXQ-) zI8+BJ>B@B(YoHy!dBkL_fu0>$yGGwY2iLX*$XP?|>-4~zz4Z2<2-BLN<1;m17mu%) zcht_VdB;4frpZ40Sc?cee{+vc5G)7KMbXx`7At~ZRe%{%G``Y$d% z`>vS)K1>?3=CNs)DE0QH+WAUeslU0kRr+Q!&a5rhx1)NRQNaO0NOUW;(aIcL>lfTq zIdqpR2ckg?#}C66|DGwhobS2$H0mx59d+_NZ+-0E4Bg_v(XAU>)?LxMadh()Tg!v^ z$S~g?qpH?fi4|K^Slq~`)bUE+?2mc|573_zlrgBl)GEyRe-VYJpRV+we(t)}3gZz< zd_c4C=_G~*Q1iV?J6CD;MorhtM>gg{8=O0d06B>OIf=yn&j=*$rB|&U-!^ad_NUjz z?;DvH#`aSZ$#YL>y#6=8l%%=pP|;0O&+6slUob-GyF?={DICW634*9;uHlv04jYGQ zPdDN0Z)0k%*I)cRIRNSSMmDQ=8n2*8nm;SFn>WlSQ=c^dpulG1)YTR8DJh1j^eFGg_yE#BUgb{5stRr5=SZbrjsA1Lk{M8#Ynn!II zJLi`1`GCi47~6Ks^zF52*Pq46j5diI%w5&9JdWgC6Tv=y2xR|ElXzhK9;5FD6Ilmn zcbkSS)b~L)4&@Pb-48m*Ka37D4u*MJNKW%3B~lJL;*;h_F&sy9NohoRC(qKV6E!Kq zXL4pNRvYK@A8b0q++j^4`r9CdTa%e2`bE+vo6JJ4>)9aP#~aAU8_35yBK3ek=w5pJ zPntAqe*^oraYR|`**P+Aw*3+%%-+BR-g)&=<6n@J4H8yrcq(N3Ze^Z{fj3Qn!sqI5 zuE|xV?p)KfT>Ac{e$z$>f_^nfyMn=YSt`R@CWRiQQD^$$dCKzFp}%r|Bn2(%Ojj@G zM;$7Xf{r^DCO7Zo9kgm=9Vx6@+S8dIyiA1hPJQcgQu+%-Cp$sT_Z$7x4aac8=5wvOjo_thekN#GV zdXIp-M?l^q0h4~{R?GTO`pi&{)xbR?LVK^(^%++#T-YkN zype%bn}$b5{cy8kI+;InMM}Jm)44-Tw#_>5EshZu7^2r93+Hl#QfOx6kFLaIq(^^! zpn!a!fM(U|TF|_C%SiLq(V?L&!y^Ohn>TH4rqRu|tR8NL_inYuKT(h8DDnwetNDwP z5fFnseU}y4h5j&C5xH_)tF@|K5&>Dd0rAN&q$v;B(`)?#K|Z2p<|Af5SW{< zzpEq8K_~4r+AX1zK07*r9GyT#bjBI)r7<@~6Ig?oE{od#(%;Qn@xUt z?Dzw6`~f-sVyh$mymXGgwYN7I_Q25G+As9&pJ}RTGVDQ_e86n_Ntt73348GwYQ1WY z*ju@Ia8mq1OT6i^kB~G9D2|Mf_B$?s92Y>2i`WK)10OFPb)LJLs4MOK zQ!6@883tjp&iD2ctFYW}2K+&(?#HY8p-?0aIQ?*3068vza$N9E@2PC4R{Gx`{|1uo zX`EnEupuUVsGS~pEH=#tWkc(-NqdN)w5uOD;T z=luloegb*z@g1>QUb;NyPqaSr6|H$yeWZz`ryrh4`}mYnA9;o{jO|#`$HlUcq2LTJ zT&Odt!)`-;VSVptnfDIJdk5tGinYg4^wL^DpT0|$%?AB7YV|D3$$k8kQL`bCxxmI# z%<}aYs~W}*9b464OXag^s;%jwh&aK1W0>)60LHydfANEDqAV&{B!ClfA=Bc^`DG*Y znI)W2Cw1`FNpRM_*|+u*j2)`Ez%q7NNLi=5m*YI3Pi{2?a;qVbV~}^b0N{#hAm!m2Xg~k~G?o^$E!P1mt~+b*BG)K}~NRLR)l*Iph#@8&40(! z-r|#S_9u?Ypkp$qIfLe9&`B9|Y6i{EpffV)tPDCQg9Ox#o?~OYrD@_i;lMxBj6YSY zTIG2WpCLV3oBB%w7@uL{{wOEy(G|XTL;qfdW8&~*ON|W*W9yi{7yan z(+8i^Kt8rW)pOcQk58MpDeL-l*ZM;pMf(zs>riKw0sMazi=V6 zYZTk0QYDI|dEP4^?-h{uD!w8%%1bk%ug|)*zay&auM11pbVN_C98tQJ4y1LnIDW_1 zj~^}eo&tGKfvU&POKbezpLKM9$FIHA(TH5e@5z8!Y`}u9_{q* z0eSa;s!7;OPu!n%u3BG;*>OZ7D@!?RS+kALSlLI$Cu(g2TStaR*GUgdMasFh{ZDmY z7iA84zcWpc=6RohyiY*hr}(1y-%Crq9cyVCYneUPGFMBVmSe_Rj+&V13b0-?&Fnbt zJ2JzTD-E{5sMhTBQOWvYK-zzu123@P_zaV_EB=uEV$a&Jf@L_4(3g#j+v>-et~gGB z94A1Ill*<&U@wg!Yy6;{4d?A_xL%g5BQtDFHw;uu)wpRg)!1KA>U(?!74_Dlw=B9f{bVYpda3SAn!hqcR#iz4zZV>I5+Ej?!lE(LL{d6So{xVM9uqxQn%A* zURB-g0-Id8Fj}_HAKr3x@22OtPFf)cXsve<$h!#ST}geuG}3zpYD%d20SD}Z}TJIu|cM+(%i(b0K zy*62gp}qU{m(uCpaI7^YI*rf3&^Hykx(8J(MhAzJD*#7XoKm?`$0FXq*_&&@;_EjjPQE#|F$`W=cOPk`|Sa6raH`(vWL< zty_k!%~YAaQTH&{^4@PtW!gHtE|cZ7+))AKr~qPz?eg*@K0~d?SJFmcY@J&b-gjVFA@T>WZ5bTgvW|^On+S*Nv63x;_IhW5 zyt6>w+4#=bRxizX@A&k{9F2d|_FcRG=GVX3zH8cH@4aop31W1X3e4Wwa-1>*HJyFi zb7#)jb@pxBemn8@5_V?T5rkuOw18GTM&Gk^K#Sujr|Wxm?Wy`crgC;+HRqGtu>E(6 z!Ls2nK0~edN>M+UZb%JV0qJCNf;0pvpg4}eI7~S`!)ET7-mU5w)`2@?B zQbuRl3W_tv|Bzmh^m!GWTl=iOtM>{HAVVL7L;6XF{z;`S z#b@|`e?Ii9`mP+f^2#k&aa);>F@AdMwPw_ISr}L8jc^D2tau}Xx6W*iQM;q1-dP~; zERc6LzBy`nUYeP5cFQr(J#NRYZGWLP(%kc&+V--BolP$rJwS|Pw9$M=wPupPR735X zh6=hm-Hz+cup?K~?T}XWA}ad&Mq=SvJ}HXI+bQvxlT~hAj-tI-Kz435DdXEISYi`1 zNVUMFM~*TeM;VZ#EVd-lhnEi0rZGyJiJxU?@l#FJtTw9s%+d6dqM|He;xkOv#(V;l zB#gU`p)WU|oEsFrB-F*}5NT+s(ozXS%f0(R-hCkNetdiEo|m5ZUHm=CCR4=LRAWAs z(PUQM(QKv{HqWa27-s>^^F9H2pMbniv2pRgmmY6vm}rUiv%5&snTMnd-Tep!2BCMRuY)B*ZT?N{RHxU#&^c{dFf0$coc21&;Du$W%Eyz z)R6hx_Nud!s|gjors3XeAn!Gh_d32j^sVNlvtB>$kJ9U>SN6KRB$~X}^Xtc&hI_Ap zyw^b0z4p=*Psw__?-F^cdD>JlPm8_GM02yqG4i4~Pida_3CQ~dR6S3(>6x z(<@7|InEhga7X1J?u860SnulQabmxVc%VOy2O!4-Q1v|a(iugsq^4~imLto0Sg=t@s*_-#}AxvM|=bZxbP61VS%1bkSZ_N6&ztgvT zU^k8`FKDi$*sw0Go0S#IQJU%91M=7ZY@Z+T{6D7L5H&%XWXsT>7hFp z5viO*hStsrT8FwOZLP zfp_u_T6Nr1gUAZkYzc{_6g8KZ*}8T#U(kXWKf49wXSaZSFk?5J9&0r(4XwV8me^;1 zX>}@<@M)FZEKbFR3+srsLQSB(PP9Nyv_REF>!ttHMEfqoF<<}h5N+8RBPH5%_G&aU z>P?K`lTj}ia_R+g>IJH%UN2qndc%XcS_OQi{#i*Vg;Jec1Z9M%4j1)dG3{lsxHYSJtsl8bp&mVNjExj~m3&`C|sn z74%Vq@M-&qK?3SL&%8YmgVEt1l-#esc{~z6Wt$(#W%QL99M4b&WBw!_y{(x?w4p3< z?2S6(XViF~=s=D#AV*o=EM8Qrd1-B9{xA@ZdHZQHd{fFgLjZ*oM_-NX< zOH0~se1^VVQrWixe@;7D$T0V#thdjo?=8*t-U4}VfvS7!rA35q%CPurz^&as#Y5MaGu->tDEK$BB$Y<_ zztt%3H<0%mXwQ>AzHzGm?z&1h9D*@81Y>ZBS>h1m>ky2=p<^3b!AXb2Cc{-1gAfHaRyzQLF+SU zV+IXo&`<`AWY9Gkv@L_4pFuZf&qY6g8RgYLM}pP|;r&dt*IMw2P|=Oy|~^@bCjaJdKM zau3MmUTlcaVjnM^Vc?*wQ~Qg74O3v?!b%Jj)S9XFof1wGt*06%bjxu9+j*TD(gDWHF1H)# z*o>|~0$p(=067wX90{>Gu^V36kOttMk$jLGuJoKK|KXx-Yw( zq;&t#&Mnl9c*5%Ah|_NGF_8BdsCvY`bZNw|&$_<9#}?#7G$*2w{}JKHxbIcce}T78 z&Gb`;*M#L&%RKtzXaRDx096x$m)7AckAbLI#;4qV%AGrQU3cP+UtM>CsRJ{1&Dhnm zvtfZB_UPF;clpkSm&G73O+=eLL!ETbu9qYTovl=N0z5!_5;TQOrdFH&;^*lBXp9N^ z4@_D9Q#YS9-*fY+SKf7|PRjGvd-fOky&E@eu)`yAIo{eeu)+O~lVOPNWg~jqfLxPv z_cA4HPWDVdj>ifB@>l^tJ~9yzF<76M7Qr^|*u{>595X$JtoF4Oi%?rz##GqD(#Y5# z*;dy0oeT-CtiGA`IxuPIoT2c(jWswg~;;O?Up)$)XUoTS~LGg)E)4=@Z%s=zE=-o#u5roTbosofn86OWI z9}gfOkBEyH6xd5oJ|464@$fyx@<6-`EEWa=ci8HIIE|wEOg0cFdUhedJP_qw2I^!W z7{tRW2O@0dCiu4$Lwm9(xSwbT4~}*qM>~+CJ@!1J-Aii%Y`?Ctw(T(%jCpR{)wS*0 zxjrrKT5R~eWMYo`=cNB(OF{9SW<8%_Ys?m^~~T+NwI)pO+!bx|pD^yG4; zewW9f?Ce9G)WOKlnQ9DrMu#t-^pH6Hats1F27w%ddAnWZ_tN`8xOdXyKO*6lIY>&l zr&c1Zpi2@sAm<>(EF{Q=K6t8N1gATa90p$7ykmDdWA}XFn!RnLJbfS1inx-Glfqb^-?;Bf98$sAhQSeWRqshO0kU zO4_^A;W;HG%5J12I_Q)Hy=9My?bkEjL#j%pxB zHBdFGy|hs6KF`nGlg~3Wn)*D;G89+F!6dZf%Oi)CU7r(|XBe)~dX)1SQ zZLPkKwae_9YkdZ9)OT~G+I)p__#o8chvxE|NqTumO!MR7`2yud;g{ye)TnioeMWgF z@1Rw0sn+sJhr%+S(S+Yl<}(V0Tyq9;%^AptGWIumJbUSk|32H$t9I=-|1BGuBjjmTHbnpPVBDIY$Pn=Ez?9PeR)_81kWv|95D+tWl@5JyA{D4{-&P(SE|V zCZl~Y=(G>yv=3BG`(B!%UfeX^-M}4LO*`kFb@rF8YdYn&8F%%ByRfER*EBw}Z*1{w z4rE}j=9IwP+RL-i@|%+eW0P;bI-H!~&OZzgX zk$dbKPr37y+qJQC#EvgtH)s5y8DHqRV8&-I_yO(JJx1&q952wfab9m@Kvr>40}iVf zHSt_`=rg{@+C}y<-Oju0T(X9fn0o83;{V7Vk_eE)Ju=c^XeY!IX${xfAjPgKjmJg>9M!vlLYY@ILOcPlLSeq zhlLBn%|v#=6h2fxRk@NQ8IAJJ0C{JCyfg77u|ZyXyk*|R5BIAxY8x3j}bb~?j1l?rN zNrG-R2ou2D4Vo|Lg9e=;Xu=>&=64%}_sBO567avDKM@9Q)_eV=r9;q#L(qjo(1kdBxL8MG#Y zF3zA!GiZGVZOov-3>wOykqo*fgSKVR^E2qi40>S(y*PtjnnAC~pjT%Qz2#@=wlhQD}(OHpigDc=Q8N-4EkyYeJz9T$)Im%(DyRv2O0E} z47xXiewjfJWYB{dgr&-R{=a8TX4b0Cn2ZQRhh#i57i$Qrnh8U{w0^9mQEDC~{WDFH z1aa1&@3P_(f{xEXr$(6Z5yq0teGuGgrSjOcGGQG{7J&{7I*(5Y1ovtQRt2OT#+jvFAy zN^CId9v?65xVf|erJx!)yqnKd$SFG}$7lF|;JzS-OD)|!E5QyIg)$@~bA+AF%?}MZ zbkLCl9UAW)2l9>s6+0f;ikA)@pN+;mb;qZgI#W@{tbOTt@4&znWe!`{ zZ>&Dh--!N(wrte`)p^PevEZ$$V8yjVIbWfJjvOFI4p0#}aq4*KfSkE3BBn;pR8%n) zat_`XD)u`Pg_jQfK8A(TpHaWF@bi1qZ`$tt2J(IbRrlLV zhkhT$QgP~jNA^cKJ}DQNYP_5FWxT8UUCk;Hy)@0ZV(BJdI^_rfa)baC5#l2i znbL7-GwbB35i->Tc-YzE;=$3a8~4Z_qDDdEz2iXMaiC(y;|%c9q2u!~3ryYdsVJd* z+AEgj|DpRr2{JdX>tDCAPN{h4)Ii4^DL{@CpdwNtHF)WOl#{TK>^oA*j@$c<6cmN_ zYjk;wTVWIC@X*mh*BmWCjuxP5w0P-&mQyho?K@g7DOvLN8!Z(VtM3|+O1tQr0yT&2%QULGI(?70iE8M4^<=NEPN&n3Fd#=5P!VBK11}^r#P3LmtM{?}=BX{chx2Db+ zyLL9ch$F%mG%e5kNwzO;+Kpe&eN7_uGV0g;h0XYU@3nI;VY0(^qKJQv=3~Ad*lb`(@pBYYn;!QR&e&|@Y{=q%nx%F5>os-_l zN}W-AioP4{9N3K7xdua8*j-aHjOP=SoPAO<#4Wb`DU)IO7FOz{4&J)oM`Lo|<7CoD zqNCSNbU;pYK#uafZ?s0+%2qm{{2`vReWITAUQIJsxZJLvV)IlAl|Csp8JY5@jLow8 zL!H!t1K!@9vA8ZhxrN+Os0q7@`dpLri6d~tBfWDx0y!Rm9FKXg9FJal-|%?$9}SP4 zCFYpoan2FhC?vQFXTwwURUQtHSfekFH6X_tP&L-PbUc{WWq6vIbPcEcvNlVfFFRFy$ltA)R=1ri5DNkV`Gb(FPY#i9DAK>H`}@V*qq_p z4^lQryQht)60>TZ`p&NjOgY{&lvDns9IvCCso!OeR}K+Ooz%fwAKKaATv$|`3#N;X zbRb7MkRv_sh%-4ay>G<(ybPItXyX0bDVW^DbM_}C?)^Ik9GM8%@o@n1aRBmhC<_}e zt#NpWp7xi@ZEJ4rrO8OZ^Z%SY?aK?M)Z2K!wfB;@aiTj-=4}kB+}jw)y^VoAKIZJj4WkD>Ynn0X@#~IJ^3G+`#=4IgJ!T`%>^99~`yCBG*2$2abDuZf z_0q<%?zt>9pGSbB=j$LzZS;|2+h#XN$KTf2IR41-yZXjfHM7#(*)_Z2u4%Ayr5-fa zHM@{YAXLt!%(c@5x2V}OYnc1qrN5cA_vp8;E`6bYpvuuP;w2qs0VEz_E7`fWa5&QL zt~|QduG~l4Nx0){{etJ&$+O4SIIGiKd#=8ZuRYJwU!d>DW-HzG`V~K80g%MWKB zspd>@l9NU{igal2_{H;PpE7aET|0KI)DrQ!!*}ey?ywo3)j^mqW`>LL!Fk8*`02=z zr|(kcobf|nH20(#pVA$uXW#Y0xsTfR-_IPMxX501vU=$UmU;!biQ{%7Bg~sImJ2@E zN7cabfkrxwcDKx7wab(;$H?XI+H(XSX@qiAMj|&VEq-nYKvPW6e{jR_vtW)Skmmal zDYS`NZJYl1r1_2EAkHGCNAp9>C+cx59Z9IXg+ZHW1LS>Hr7?xYL3-xH77JWd(7Ac* z!0^aY2xMq*V9~&aEyHePO;qYSLrUroKu9%cjrN+ob;TC1Gov-e;lSFh$_F~Q_OSpt zMMZq-Fre8Ty!4+3MfowHoT9$?|1Cugm6WrTqBd7jRAKdyl2#$ZjuTBvV~JSXYZ|*) ztr@DLF-FE|49IB=sG7#Sbat@d^iS&=?Yl&kx^Ut=WBp?tP07gu=<>f**-Q)I{XK)e zt?!yir0FJ+s!tXiN1@VZx_nBYx#XbaT`pg8D?01Fx<> zMM8sJUjTA_0mw0=ckR~)_dxdx#}{s<@iQF0+|>{dsF@bSsM{b2>LP=%3gR0v)7E!5 zhs>0){@?QgLc#xEA1S0xABPyEuiDWDAvaDmh-r3)LC{BwLD0uV24NG}Xi&4DZiD6t znsYEUpCstf2AwMC2FpeBxyhh21dSSmbZxM5=LkB>pz{QM#Gp1ocNx?nsNJA0K{wm8 zJ%ZkD&W34<;cbhkm53i_r&>jnMDppAllZP1{ghJ$GHkf1pRjR-oyplbxp zH)xxnry2BoLC-MgMnS6$dZD282EAC&Db~`L3VODsy+Y7+2EAI)xIs4yx?7Xo&kg*@ zpf@Y+`LpdSdj!JwZAy2+q>1>J1W zF9p5bpa%qf(4YqeVLlQ|kp}f@>m}y~-eORrpj!-T67)%fxSQg025~pV*A3!siczbD zyD7eE?Q0hF9fRfx`k6s!Y2UPRrwaO=rExb!V-qdlZi*ue;%pmPjr z6VzwVb_n`~wV+E-r{!`t#j6bBZi-a~aW};}gSeYw$e>FFJ=dW1f?j0MMnP{jh;sw) zH;B6_K4DOPZr~cF{X$YJp!6R+%^ML4FdCHcHd7;C!@{THf<|)nPu;Md52oFYTJIzsop>ux#_-wVBdOzg^q{ zxwr)qM)mo>?7az`6~)y)+?N@K0Y-sjR8-Va2Sr6hL1l3P8F4_+VG~pY24--;VR05$ zBye$Dr(%KiEGqo7TglIM9rIIzUMios=KOh-@YT}eZTkr z`@N;NIn#Zr>O4=KTDrQr+S;gOD&<@RSiv_B^k#rr{|!Hv4$CF4xq*LMI^thgw{iII zRYmB)cT$(*!)9?N?0W|P9)MQ}>iOZb2q%qIoRkzNCB%;LA4F8a&~O#xY7zR{xHbq z#ec6ZQh?Li8(U7583B}&-p4sZ!L5x)%WYbyB}@M;Y?`jHDJg79K5Sc@s%>Cbuoq8< z&FgM(i}T=EHI~Gn%zuAe1U9R2+-WT$TBV36DI!WfB5Rdu8$|s%8rgJ26H*WrbI=)0 z?QIP!PSzJUoFcTSXqlp-q^Kw*qf%{ys*)qCbW~~XnO5z(*}jZlxiVf_X-y36Rjia0 zDSgSZrOvlRWZg71vzGt^224((RtOTPbCIPN3Mw+M4C@C~bJ~V5dY8z++ zIV|3EXz^}B5<`0Yw>#A>1r!ZaP?QuDrDRa5Z9om;2!GQ-P5ckkxD;SCPJvNUV3d-9 zskQ+&lD8k54(!M;0ZbQ1VXk;aV|~k_Ccdno>$ek{6)SK_zbFTI2q2{&vZ^ifl4O0oE)A~>em+GL`(fk(qfw_!cYn*DFu{#3hZE3wG9PR zc<;RF6r}Yw*L916a51an?p)^^`o#3c=K8iJCpEHS$lH!I*zpZZ0)Z$Al#~QY$t0+@ zA)$`b2I(ZE)r~X~t}jXgx0XpFfy?Ye5{_(c5d3!Sz2HBh@#K&c+Lu5Ifhj4JloU!n zDYkN`wjpIEXHe2f5d|ul;8u?KvPrAUe~Wp>gqFtow#J$zEwxJ;Ti7U`8gDi1bhL$> z<`~4G98gjYDES=N+M(KpgE^c#+H?-m`nzHc#H3c>m#3{XmDK(!46b2(?0&Oln#NNXZ*C{i_uj4xWuTrs1q1q=P-K44!U=eMd>Qe|bZ zi|IPrVDhs1Qzqc4TI;0ci<;XT5PxQ4%d#cQ>v4M=z935r7MpCssR1HZwkRoElzg^q zol$MW)&kBern4mqR5HP>Gil@58;h`oS9uO;U%a@nC45QJX;5u-3Ez~q&e9Gmmg~zE z)>#Na;ZsuhlzjNMx~R5+-@wV#bohkp1{Z$XE#^%{;7@8jQs0wKLv10_Ood2EAyV=o z+B+82HV~UQOT6h2Yf^>`#ULKKyy3{jNj*hg=~|q$RdG^MoRpGrssQ2bKD6w$rfvpd2{$_FLogObmOt=X!5XHXRc8_7lP z{OStGoolN51zRE4N-T20=4T=u0%;3KW%9&9Ez;LP%7VjjE%{>;({Y6cxFta>Zl#LfNHPv} znCi_%N@8Li+TFIrLMYQ@-D+!E(kdt5N71Gwc7QHrGjT=Bvidf8rz?1)tLq)aYHe!? z+gCnQ(NdtKr9i1qFt`w-2uh}m?&P+Qpl3@(;TTys=;Gay_1ny>7(kKmo#9c(@@jg?9EOSqf$gkDWa6@sH$x$ z?kRjcmCjFE8yuglr~M54_tql(%v#=DkRe-n2P2J@hD6&;(PBkQNzqdB(PqRBef$=B z!zw<4OGj%FnCM>RNNbLNS_JLHCCl;3V+$5-@x7)(&Mv*W+FcLQt6pgaXKZC1#X6f&h` z$f|81p9{kpz7*uRi@T72)(yzf&O6;JUs|hRDJfV=$zWC6fV}`<`G7pVeBI8@>e`2E z@z%=)`|~1T@oM*!6^rVd<(4|FN{4q`TbTA;I=v_^A579nI)AfG(HWcDW{*&nDN4!| zC7&rf8c}V-)Fnuo3kWuysrb&nE>m|DVQOM~v#>OCg*P{o${C`pmTCtx-LtODFc{6S8q;h=O-WHx z@=@DvU$qTty8>v_QKxnLcXb=}l*W@9^-GaXPiP-s9E)U{MZq>)T(qQ!DJfz~$%s|k zAhs)=HXU(VIsdWk6 z7yE`myjt5k+tDoU>5V70H*$L^+l#Y0gE}Iuz-W>4DbMWM+?3dHr#Y z#5Qvdj7$?)GN)XEsGL$#PAU1E;zceAs%<#^6;FvCd5SOavi(BZ;3)od!^Yvi_jZR< z+(9pC)$Qw?7S%rY`UGCoWV|SxaN@Md$@N%GWZN4;PzER|1C#_A{?=f}L#k~Ucm~(F z-ZkB;(rR8>D|=se7{C}28!F)Cp12<+l3*w8C$TQBtf_CqZW+dhWL80D{w*;?ro>QE zVkimVkQiItRND~qJSgGv-*jTqh8gi$Hd_w-_x|n>Gk)QUmhO>a@zP+$OG)uk5*#63 zJHSwFgV!!{-gLZaRiRsWv6)qy`FKjPs$c|(Kcr7&ArqC!UG)iJ}SeDi{)i%)Hz*#O%PluM~yJ=F{4~9B zakpx;1x$+-FeL>{Nq~fat@En30sIF*&I>Mw+tCM#k}ikOlh z$wRE#2Jw1eE>;@6Na)t zNm-yI=t353@1@#?g$=;ZH#pK+NOSbGa(}2hEZ~7;5(oB{=zVtJ9$eb1a49KVN&+K< zYu#M64cvh^%NIS;;qtm02;WX#OivjCt}g=DZ2Q=HYHM5LGPc!tS9{5#g17iIRB=*L zoRq}=5T`X%wGGaVILjAl(s8C0YuY5u!$ok;#?T5+?=U7=(cZGCF%RHY7;Z3cx_66F z^4?pg>580^BBvyXLgcoDRoftc59#v7p>*VF!?F0A`|haYks`=Z!g26*Eh~-($UG7f zX8P@8=y4!trH#guGSCvjw znv0}O{uX;CFrj|=qQ>SpSMhc*fg_8YrYmwviky-l3XxkEQEh|#6JX|hWa-G$TK=@k z@n{hjsl~H!TQiPmY|Lx>?U-H>;obe?8`}=Wjt!^cDLNK3SxSgW zDWRm4P!gyiCAJ$-Z9|EDp)Q@0G-rr!O77O04Mix?t(7M&TZS8AJjZT6b^J;7OPcE! zcJ~RnC4jF4Dgl&~07^aq)+bck5MbXU+;jrc`oYJF5P)Vw9{fyLv9g=X^ekk)`>2p9 zDP&5?kX742z8t>9S4B4+a@v$pv3HpG-22ED406NgV5tv&?D9m5n}#cHN{XA3;0b-* z7PV>{+(U4dFL7=5$Vp#qlY_p2v%L zclx@^7dXDthx>NJ5kAM__RVbmX%t&M{Ovx^BN`=xl9EA5K!#-4j!(7m*5!f*zU-Pz zgPq?>YvsnpXs~hkZ?T1UTwAzLyoW2i=ni%&Kw7K-DT(=bk5+?qa9#;lvy9@a@5vZ# zrUaM^!{#&dei@%SMH|lYFx7O3lY9?E$X}4mgq?jowW^0GVtlH|~3#z$6Kssh# zcLO=Fqx$&rHk%j!{Z$e7p42*{t+9TYlVq6FxG2( zGZ%&o-tIB0;rK0d(m9 zb$5tWgh|!ZdqJ=UkEBC4wM~}PRd{81fjrULM4kv#d7`8|QSy1RExT$Po^C~&+=VBd zr?lQaZ8o~tY>ZT6eU6GZh~wR!+tn|bxdKbDR^UsV(dHnuTVYdD*pz(OwyRfd1N&zH z%q@q~Ve`5h+G9R{7z`OygZK7$Vf~2G%Dfu9+m8xw(+C7ibLE5gzCgFli zn{RrqJ9PkyuEJs~&{ug6%30*nOv9Sxct12=l7R%1xLvTXRT9h18yD8a+9o02bb0xcMBZm7Te8d zrJRn$Vxw`2jgn%cl#ETa72ASw+&wKBnDt$D*=}%4Ek0XpV;=7CoC{{zipzpof5|l=wBeVwnzb!%{CK;o3rE%`;OuI8UW$#EVGE6{ZJJ@Xvmx~B}EE9Px#WJ@XAKIGYEWIXT zWs8!sMJbsr)i%}1?#7l(m@o+gIhJvQ>vd`KRxcDGjLVFR+yr{#g3!zCN0sfe8onO8 zxVip#{me3Xa%kQGG!wyMShI3v>uG#8(0r0EZ{|z=;qaE*(kYLWlt)T_)wRQ0)iyj< zq4VWlcIiCQFgJ9U#}|w6$fvvcJlZ=&_p|04U6Dmrzy^%RWGNDF8CY3)veka;oVJFq zA=}1{5S1ZH$`B==AzLq1+b~oOhPcOHIzv@daDvNF+6}-<-C&5Wl+RLfn?}b9y-g~h zbv0eoh1Wy^ZUaV^hU2&J2;79)7uOrZ58%lqI_t9fX4z$2?#6fi66`(pIRK0v^XgF#X%els{If*RZK1fJAmYMH@Jlm-!8(9Q*1OSpX$IDVfpqU z-lgQaxY$t1`WbChV3ZUXB?ac@Njp{hA?^{m>A>Q{0ymSQz!Xy&A{wWNC@CUJ$%s_j z)Ev9(WimD?P2jerOb!^_LaU2Ss@pjH_vIorhcDAkz*BKlo`%C4PsOXSSXvw}H#^C` z9Er8@Bj$E^%VwAxgJ546R==>RAFu0M|`+M7fakt$J?lqgC*QAEd}+J>lcAd36MrW3^x zgBx6;;*Fr|K(7@cN@nsGwM}2Kq78l|JHo^o!Mw_3=QTMRT+!af2Y1$oIPk&W(rc4L zOPA+tgp$M|Nl*?cDTkDN4()hHwGD@MH{Em&g=B|Bm&5qoip$~aML5J*nXL)87sD#3 z?%l1JcOhlB1GbeX!Wc5iUYvFsCOIIQ6x(Tq+XgF7l$0k*K2Nq%sdcdZRc`X7DlTNIYf2<_`5(?~>Pg+l7D}?&(56J3H5oGb@i=ytuWoZG7{Rt`UD~R7#m@G8@*uLHSV&k6jD+ODftv4Q3%nNXqdv&K{wpi`IF6Ok^-D2ZES!O)7e&zT@Sc{og zy=)zzeF~3~!lUHFv$mmymb47P6v0Xs32C1_5;N2t3#lM}iJ#peq8@HZUrYI>>lzgUa^;B)c zR1@5QyU%YrQ}Hi{+SY*o{-Fp{wx9L46DW2^ZTsE)S;B^pJ(=dRQ;?}sT|}?sQBv|K zC6lMxhCI7re>!=VcaqHDHW6uV_QxXR$@Cs}7FyoWn78V|P7OK>wQQU5(-1Eu#Y@S@ zYnw#XHhAr43DWVV-8RN=iCt&;Q}^(4U%*0LV>l$05~m^I8En908Q_-{w8SYX1C)FQ z?46=&^HU0A#}7JVtF`M^eKps+vvS<%nhQ(Y%I_pm_^959bvQhYL^Naq_NP69DjYNT zx*cVf``_@Z#?OYqMo9CK_{hS>VXBL^uqnm94tKzW*(+L3Xl&u2wrcmG7^CrvV%QOJ z3coeS+k>KCA}1Q9iIUPpNolfHSVvLq8MBVcy_9Pm*zxbK*=-Z>iMYz#go@6QCzbrY zkaU%Re-i`$hGZLjx^dIm_P5h5$le9 z5Z|(R6e0y?{3gt(490}g8~B?asvz0)q7wNkMo;ipRC3vsekuIi_uj*DIY9M?}8F8tf;s zE#C(GWy5GA@R|*T#lQJUjJ?cB_j<;ZFZK3JxNc|4?5#k&e3&QW(yAd}zI<%k&H(~% z%AJ;t+~~I>De1Q(DQQ7i>>1;as*Nv?J~~n0j#$@uME~r>^6bQlM`dA%IlmDZ1-VD& z$@Yba)dpO6}3Cn^HAo2COvfvX)e5`p#qfqiWk-;abI zA8q6Nk&yTZ7wr`!K0QQx1&J@dlm$Z}@xdF~D@c4`_5b?!+0F!xKGHs~AtI5^g>*R> zE(^{NCHyJl7yheVn)W@t0`$(({r3g(cfRp`beJa7;HSSYEzq( zZa`=0IDR9&u4{T}wk)^#qZ8q1L4eO(lhbm!al076%7VejEeQ6(FB2SuV{{8w!wV)@ znfU#j=x?n1gS0 zzwzr`kZ=5MN-Qaq=a93xC*brGouduE<6BmoER)SVNmM+E{d{!ghaE(bJS{#-T6~nW z_$+BX1SY=uF4`!#j@9LXDUnq>=F0Hl!pa=3@l%<(0VuLcZ)8gCTv>Kv|5ZB_(VX`X z$4^?WhlJ8Tk~F zZNZTfEjUVAaFn#*A}(k>YKLeSu6O;TdS@yei_4U7yuuxf#IzyI$VcFli{n5lA-EOF zt#BjsvUfdR9L=%fV46&Wm38~X;wbo;c8B`{I#QXOkG6mD%0yzcOekrYP|`B7gxJcY z+Of(sq;O@*m6;nol+3(`a+{+mbgRo0DHRrX54qDSk+Lb38;BBV7_}pccRB|nMm)IH z#+vQw#LFqZQk-cr4K~MBU#y&DsZhKqtkV;t5===6rlbT%NYv?7`#knBu~qYl9n5#8WKFFA&?55v|fpNol5}G)E}4DN}8Zm?u``jwtP{%Zz_8l#h};iDxdA z!MR~^^^(c{@?avg`UEH8*jI+t>}eYCv*A=DxK+t5y7q+Yw&?od+Tw)kwqky0t~JGq zmU%J{TJ=$4EtgZo&2Sw1#da(Ayx;%vfN&}BkoNfBwQL6+W-6@0efK%06AfB2MIG1i>BK> z)R-~Ru)*o5EagP4b&ZnNHA+f(eAuAc?0&HDsI)FOzGAvQ_d-RW8$O8J#meAHVUl1< z(bg9F{5o4V2G`~JH`_n!vd6}Fv2mE{eu=yk7{HDhgZHNGHo-Gj%)~}N@qrXqF)(j> z>GJkv{`1$FD`b-#%Nh~tgGEaEV3CrtX01e9AVIYYv-Y&GcFTW^wPh)+H7Bz61-vPl zN5``Y@i-SbuZ;0X%*rDr<&jb{kE%T*H@CWc?9qeHSiJU?RSP*8ax5o9M$he0+Qx^s zZT(C6?6z}N|I*Hy{^f0bb3^d>Hrsq#`MW)H!?Pz=6Om?BE+5yMjGS}5tZnicH4#1gyGSg8R)I#1;#!NfN_-Lo# zIGm{t7T~x|(1_y>!3j9-6r6}-b=VnPjlXQ52QttbZD8K~oBysfC+&4U!L{0)Rhk~V z&L_CGp3(ZriPvQgDcBX)#wT9q6P>7L#&2tAGqWM88DD6efDbQWU>6??ai&F zNi8LPQcFo)#!{CFGGec4+cDUPbsdjIqu}hsA=!!5*@;85M-0p!F$lxp?8K263CjrV z4B)5?o|SSR0=b8jc=9+nXDx_5db zy_3V7YCSE-0YAN`Rtr&05M_8k{w5W5%p@{t^b?hhl7B zuij_|l|f3%ASGomvRqwXwU3(38{f5ETqaY&eB1WTO)MSz+3Jei(|c@~X6}(Q_!GCt zxINZ;;{j{CR&Q1}t>o$Oj#w5ian0U^NWD^q33|WVpKUX`Nd6|{7^TWdbzoMu$@Qz#SrGnJZROJFV;R`^NI=Fjx62?*_RkN)L0}6 z*1JiA-o8@O+gD07AP6k{jQLx&|KnwIQ><*Z`(INw4Y7{hEt{2zWz+5UX3Q9`w49cw zaA}bkEiFn~T9lGYOSPr6`p-5`v~!zxmSTqJEIg{M==i(~b3*NXb4T^Z3{iG<|91y) z0%;PK0_vnu_SDkcxKgeNtmv3HFq2(fA=5`pV_bICS>-$_XPE!G%~-*Pg+*5?W2!Ql z5$r8fPJLxb?&h*`u%C=g2gu64ErWd#f2&|DjsxW(!yuV_+9@~^XLgo*gB@fI=T1Sr zoMCn2IIRIc8?0PzPvBqsfQl{P0(0KIUhY)Yc?s8h#j0w2)jjiM9<=I<&HQqb(V2O2 zrg+_jh6{CW%D!dE>MN5`w)T>3^#@Ao50um&EF*SuSG98wjCd@I$-vTVZU1a-Wwv%; zwsugqwklg&ovj^`tsR=J9iFWnnXMg_tvw)HJ1$#WldY}I)=tUR)@5sFW^3nUYv*Qb z8?v=c+1lo8?aFLzo7{fH2hOu#Z9K5OEyd5aeFw$r7voT8CVqvwoNn|uV}+MFNQ9D( zfGH_2kzveAb$SII?^WVPyn0ZstlxdLnU1%6v>n*-c1c_Beb&wESYL@Z)jHPo$Zf!k z>K2T%I5(+uadtCe$<>wJn$fdY@{&<+qTGoU5gRGw%5t?mpn2Z7K3P;foUh2@Ww;#Q zhz_Przl%EO)>ID?Wn3*4;ns{vYi^bSY`cSnzhpq}u=Sl8FzQ z7XPm)nMJWC-7T5_#E>a0D`&)1u&hXnmK7x}D@w^_rP|%Qd6V`zd3|7a@91`gXX(A& zNJv|6#^#T8B;GKN$2kVKK4HWh)~5sIs((FxZx^5YWSUIlApHJT;NGt2`oV;Iy;dRo z6r@T?n=ML8xJ6>u52`i@pPgG>+0oV2zGrUYps^E6+j}j|Xt%j~5Kea!!-KJ(oQt_`!Fi++| zt7Zvh5Q1*KuNHqSnm#zSZ_-XhChmh#lBHFTl2$!R>Is$-`(RYHXW%uq2^F(Pbm5+( z-+_4 zS2sZgSF;n_Rt*#tRIq48qg9!iT|I}XOi5Cil3iUldi6|P%}$(~oQkT9%dW0rDx;E8 z8I@gq!06TEa5X!zHaQhl8Jb-^oT*eNrBa<;J!JIip}2}E|KwCuWngyoAg0nkDV6@& z)s>@H55(2%#H!>}s1mMt?)X<%?%{rGPyTmT*SzCKpFXgySMG_8ZjHAxn{0(QX#?OXV za3vnx+WS)MIv;J>+RLtBL&9}idok#f1?g`3Epk_)qhOAQ&x^&^>;9vy`LRxyHq%C; z`eI99rfzAM zcoCk*uu)^}uMEC}qlS#!1Vp3DYI_0y;!Z|_+Z*x(|F(@`d`iv6A*n}yp~ldE_~I~N zAHKw!J%Z4OFO>AwlaexG4Y5V4+E~K#wuL~BDue5B)S$0H(kERKNLo^GNvMhWgpI?0 zyY+aE+u+GeV#1?18mKTSDNITV(`IE&RPB!UYVfdoHtv75C&z8BnG2s%UA2+AKjI-< zQ(cL5P`Fxk75|nixHF&IiC!BQYv4e0aJBLg{2kyhAcG`pab-%jt-9{fn(C3PLHKtR z>A>Qu&Z~vH^U9Izn#bBd-Qz(B&_5ve2(CJ(N4!4^SC*D`&a6HF*K4OE%R2j(bbq8c zlZ%#GCu7eO^)XdvrYZ8O_kZEf!U@(*`}&&Q+2ip=Z9vS zhG&~bW}8N3n-0h}jmtLGWSeTUO;fT>b=jtw*`_(!rn%XshHO()wy8PWv@+Y&mfOgZ z%QjaJWSL9~9O64q$*k?_Y`06)qsjgk_ISpQ-HK241O1%be&=P*EhFcTWY(-}{|GBD z=H?zkhPkf%{Hnj)!-d>_xhG<|&8!}pomKJZp$qz9bPuR=Xj8wvvzhF)QqC^Hp$4My z>pY@Dm_Bbx_3%dy?H!yu6_cqWqY|AzFZb|q$7-Rluysy{g*XY7tWsXYxm%>2agG{g z!H7^A1?iV^mGero@T&}7z)@rT4wB9XvC&a!6dELbE^-hgJvTX&WQ=0oDTz_MTk zBtBHC49EB!AUso5uA?W(BLc__YBU#aqob`(tU!< zahw=jiQ~wi2T<%6)Z@5+(2nEi;8`5U1aIMZK=2NZ2L>PFh?^~7J2?0%j^l&vahwqB zgyW&X-Z)MSZ~m@^y*BKRD>e9|+@8R{WQ0u{e|yHRzlVH!$rYU;+9P&-7XHTTgow$1 z@iJq=^`2%9j-!{s3D-+wTO-8pJ!ms+B&u$0jbJ`?!kM>;Ei0DIXl%hZ`{G?s_jgF3 z<~lTWoVsagJQ4d`wwcMdU*W7X`NpO1_u$kBjKfySWFPdn!OaIaxerfHXlh@6LMt9l z^d0a9&h3qJi+Bp#HXq;C)C%|SJ77I7Y)|Ex=EDe>DeODod7L$qdh)thIDbU1)-e+$ z9WznVu9MsifZaWUzA|P~?K^96k1j37j9Iyxs%PS_qjEp1o-6;pG%GvTH0F2=ib>@5 z{g0YuhUw-+1$OZ`YdsS|D}UxG&wwIRld8c{Jl7LcxjGZapkn!hOX)5XlMJNovX|Z==#dLF@;v6_Z33S z{r~ABIZzn*wf$l2z-j2PE2ksF1?9-gK+kQJd%Ei@fLO&nOu}?PA8A80eITFP3T^Tz z9~0xPJ7R>vufIQs>>Z(Gc7l@f8BJJ-KyzM7SL+zSU)H?wFXJP(WmoHwq|2LN76%A z^~imWq^kG$Z0zT4JLL}TKfT{CW>znrepDS2Ic56nX+D3|z%&lYP02l-J7ZJ{20pn5 z0Q%z}cOEw?w}Ggy&P|=tSxV=vxh?bSH-V0bIF6`666lw!(>%2s~P%BW!84H4bYab?Ct4!xFidM-KOzz2z zbgdDry~FVvi6r^&5F|Lnjvhc^XM2NP`p|J0s_)d5xot5B0hKdyuSpW9sB6~sTk}1n zhKz>Gx!0SkhyRG_cf8mC)$-qADeCLXf>+B?EIqqWJl{R*%`SNN?*>(uJeZr+|AEi? z)Op1LpGI*=N+{Ps7(ez8+IH7!xCfvg_z4m@Y3qoE5S-2wus!HNb?x`fv;NuD_4X7T z0IL$;80-%d2a>cZul`C_O*Gil_4TE(wtyYiR(SAQDK|xLVEj`l560M7RtBxO$|NoI4MH|52m|A^kz5KSO#8Qd#gmq&Gy`3|MC3FMS(+I;6^AHqL2!r$TyN;(i^{ zYZBu&NWT+_Qw^F5Z@anAOJ(p5q?hID2as-(l>5MQ_*ekzd_w6ENSuDI488{G1&Og1 zlE!!w((`g|V|Z?Nl-aX#Za5^>`xzv)`IS(56Vh1ZT^VeSdZhUdhP01h8_l{UR}X=- zBcw9fQS~WF`DC0^L>-X$0$ycs2_$9cPDq;DUqb0)NKZ)I&mpN*kK%6~q-NyVe-C*x zr%WCr_L9NPGqTd8Z!iOTo5>pF&E;X@Gcx&F87#;7ErYM)I55cKxJ~d49JiHW-*&+b zkar4xhU4Ji5gd03KE-j@;2$_X6L7${N6;6?p}_zg_maWiuY!?~pA5#}_;hdxj?V;# z%hl;P?j0P9gX3{L zAvgucWx@G4o*3MVW4jD4&k8<?`O4jsM|u&4TNunfoV1t;S8gWyga ze-u23h7<@%aYfBt&3r6Dj%ius99|%szaea`(aYL{U z$Gw8bal9>f8podnFW~s|;3FLG2ujd9{vs&D@fjI@{tfJ3jh_wuaD@Yqa(e>*?u;KI z%V6=pAh_3`fpr7rs`}2SvFl^;_f{sgN~A#_bmQ}SKQoYR#`Q}RuE+QIitimtn`t9a zb!#B2yM19V<4qsDa`<`A`(0g!a6~x+kBIREn!B*C!?~ScOEWeV8^7oT*^rOJ(mQd! z4d<8ZP<7@CT$JyQY`_H>mU37vNd$lI>Kcb+Wdz%*nw-Z9IJ=AU*d0P1QNMwho8#{# zD>)jMy@G#ba#>k_DtG(@EZVpy8VpNTO!uTKzJignGFFYu9)(? zrzc-ZK&|dO;6q3py1b#aYDkn*6+Z9KIL5oYTc02- z(jmKzLF|+2o0l|H85*jv6FhDMs_+I`m`7D>(~9=yhN^{)Rf8LL>$7sw^2IBv4wTD7 ztHN6ZbEc2b1jQ=s2VS*eF{EKt+@+t$8yl+l;29P*6fnbb1Wj1bzTEknF^3h+J<6%7 zYKS#;aD#6sVa#&VPSs2%Ok581H8h^Wg4j(cTZ|$SV)_LB=6FM@2Des0N6N5IQ0P5@ zF=N8Qaf^mkrmCfJQ6oN90AiL`Eo*OXTf)uws<4Yj`${C((ul98$KFWLx<>1U+5g9g zrE10CR=q)En{W}@k{-d%azCOT+3`SiYzp@yc%_C-pTdoQlgGB5{C5eeoe74Y_;QwT z{?=K%VkhotozCA<()n9TiqYm{Gf-{ZdN$+M?tLs~_`JR6y}Wqlfue$O_CSmcE8F@X zHKU1V?7*!;{<2S#xcTI+vpOEFoMFekAFTa*TUp2I1wq@uS?z3+4RyF-6AiOhWraK;sE`FuoG|Mx(yoxoMcNaRj!X81 z)KkuJ17viwbFc*G2g|!ryUM6_FL|+bXmA0}?-~3M$6>*baokJ# z!FZ2)9j-9I+%QmvbILlKtIb(^oe!q;0kztDW$gOrxZcO~s2E%amnB>;F}cxCzLRjh zM{H^+?!mN~_7K(5q8`leN=Z-ZcyRJG5NBMWR}!;;qeh3sOG0fH?I5Tw%-lEG1( zcy~|V=QjUz`WdX{!{>@gD4WEx}e>(*l=`-(uUxy_&8vuzWx>C!h!x`u?3woMTxT7fa5XwP7y z^ypz?`iNILkL;hFU!I*`@hDjg%=wL!J5L^%dn*65Y&9`3J8=*mi)ZImWastI&a2GM z8JS&I5{6Z05?R~UJa7&#W@&~-Y5=-Gz^B7$*wJXh{Px0 zdes)y8l17=cq=&nqNfRqs?j-5k(F6rSkubP=E@Dc?rwoQ&%Fql*XNv9Xr^XLkF10x9W& zp%+DBn}4@Rd=T@0yM2O${}%%4cHtdAPX_|*xQOFO(c>NGc##G|I##4XkeWoQf^>vP z)sP0Dfz};jw-;$B&Ydk+*-U&>B>GIdNc5Rf(PM+bPq>!_;~??lm}Szs^TUs2K`qYl z`{rfA6iAm#+&W0ti!>9`9U{$v#CEGpUU+;)qy;$lJCPb7y(3Z+q)$a^hLp79=O1yt zO!fx+Sfo`r9*BWLS#Tz#$s(~yn=R70kQzifAJQIT=>?EZlyjFrS}oFdAmv259MUsr z0Lp?ZAiX2f)sX%qSFeM_FOiqY=#8I*E(>nOIesO%OlE)hDdDobK z%oIrcSW=lhO67+x$^tIW;@5x5f;o`*SsVBtB!27#JHbQZcTviM21xvfNLkPXiQjp^ zjC=1#kZH8%I_#dQ?(XE#Ss~~+UWppN_U8R#<1F2lvckcLk1IkW! z{G2GQ{{=Y5dl@8z5Oulib3F2_?{brrZB z;fA0O<+fa!!HJ7##G{%Ll(2*PL#6mvZyYf1B^{kUx!+$*Js9Uy+82M@!Q3E7J>)re ziM$-uQzlC{lZ_I4<2f=t^)MX!%2M~u5{G?<<9a;6xNs~nDSlD@rnS+qZ(MBH$26J7 zWc>bX3mi1)owUB;@85p{g?6_@r^7i)I-H|~l0%4WW$Hno+Oh_Fpfaz+=fX>^IRM1u zHsb20tlL5=yqJ0jGeJl! zh;;Nr1g=YG2z(wiarX%66iKZ=q~qiq^GPPd?u*0gZb&1;#vur6N7~2C#g}jAHZ|`v7fbl(wzn;5Zk>A) zz7@;Ujrgv^+$9V5Y#2RipZZ}d8x{s14&#gn;;~!%cNconl34{)U^4Tm-+C&(gtBb@ z$?}DqVT)F-41#f-upYLgp=ZT{pbP1g1mhNCW_H-{;j~K63~RttZre%BP8zb3%rO+b zFsz|*Vf*pbGkB6SaV41{c#O)fGz!BO;)`x3u(2-5oM!*@{v}W4ypA8!;*&?MRuU^1 z*4B7Ro18aiGZ+LRWP)vfI(o6jaqZ!Vpd{1DBh?T}<)+$ln8g|8uy84)amW;h@qkbo zR0TaU10m6-Ugb8&VGCiH3B=b(dS!TFPU4?o&8ovoq7w{j)H6)0U@T>RBGW0uuSE7R zlH?^8(YuUc;0B#TaAeh~zN6m$&oF~+?Y!H*^glsUsDAv(o3+_#sjH+?)I<}sa85EyGqEHTxGdlW@<**WeicZL( zSQ*A*-H3wAA=*{g!+sj@B`9{6Lk|2(G9V5*WxWOg$l+H^ z(wmcw%wf33w07UjYEqjEU~TZRGk3*)rB#rBEp~D#Iw5wZ9>u!WkLA=RqV}Lc!LN=IR6su>LGNZblz)`i&hN5uUpr4gw_QR0__U%8ddM=+0 zj>+IH$j4%=4B7aj$-OcFuFN86GOcI(TTQp!U|x!}+_%}ZqHHfkCq&A$qF7vy5(O9O zmN@MrRSR9Dd||N!OVH@^y@G9?x_U%DQuYERnZuBewSTJ+>8_?yZzV|45zCF`gh;DNWbdaY>=`{MJGf`yP{b94oMVTq+8>(k5nymk!~Y8bv(lQ0S}*z)R>@KyLrP` zlKC&u!2w~L5GhyE=oyyjYKTnhx=M?b*S=?)xBxnyKQnj5J~`B34C~KqheapE&eWq= z)BIRY?dI6T$F4TJ*tZiMZR<0^3wPbRYknzjFFKd-h#$jtg58cTuE7}@U6d{wGk0an zisM`Am&x<2*15};ENWTNx?*u#)k#ZQ+v}UFTHD$imaM26wj7Vo_ZdEX#N3k>?LT+P z@L8YbVrQK3QE2Y2Csd0LdojK*TT^)eQp?~k7sX)Fd6`H2 z7^*!_;Oh@xcAUwV7&K*I&)f=;Htjml=E!TiIe+;4?1HN$nXyztxO7hSaN^OhpNT~s zzYY?+`p-A4vP7|UkP$^E^dH(3#k#?lX73H? zzT$Nd4vlp9#K79WSBSKn1%a@aAZ0yeT6Kf0ri+w$Dbg!_o1M{x=y*t@SU>h-Ic2&x zQudLmg)Y*49G#NjlfU+VBp>Mr(Rr0e{22BLksf4_?juMy;#z#GILeIypHoMqa`${q#pK~(x8?WuQFdsWdawVA? zc*Kuk-w^xN2G_oV{qMNOw4UL+USW8>Aa>@i*rQ&C(cHF9bVBS*J&N_4g4p+mo{wE^ zcCn8Nb%?C%seRxqOwjFRMvG34NBkH@33fZ);GI%pFSD|JVe^tjbK90QwhW)U7;Evk z*s>)WQfOjw?4?54AvpBUKAs-#AEgco)xlamugHy)GXqCJT_xc|79Ba8QVJj6r&k zAl(_ZFs;XSw3^gD#^_L1NB&xP2A<%J%xRCZ(eC4NS_#n!*ee>?uUrZ8Xoau7$35cHMqtHz?I=-C%y6~J6TP)+?kgmT@Ae`*m7qa(Fu_< ztteK9AIm{H0eU`Cwa`UcBRZ@L9OEzD?m;9RZxap`o!fY%8bVEo^eTh2CLq!SVGGmx z`Oa3;MasMsDc`P%f#CEGGA~7Xm2b20Ugj%0AyTFi#k$Uqksph9xgf;^GG#>$%53j^QXarV(t9ic#zP{jq)2Cn(+msyte)w!`S42 zzQH$cXwyY}<5+_08?;#&Tc8)s(fS4n5uK1Rrd2SO!`Kw)`HUH)PCGwUbkgr4j}V4jehl+G#_sDbV<|qiZ7-WLYvt`ok$L?ikulm_ z%*RA0WQ=J=u?qXx0_gdS8Ke$l^`evRV+%#cZO7_8#-8pjV<|p1#xPbNkg@ehkv6?N z)RvL!W3;)LkBLsm7}JVk751@3f|Pv)43J=uI*c{od`Tvqu}0Ckj7LcY>q|Yx-azu* zYR6K>Buflq4FMVZ4N{~{zvV6~5?sb;voiLnxoUKrc1(0U#-doC`>~wui`7ce2^nK5QLMtX za)}^yeas+r7+WejFc1M-TwlBl9c8?)JVA86$s^SemU@i!K!jpGHhk_%Jaor9q017x z%HRdV*ivEaQy4^>F5AoI==vCKR>t0?sbR3=UY-=4kTIqe#j-Q@8qw`5n{mqbF@w}$ zj8AMzGFwPwtkCW8&>!*{TP`|R@hEzHTb*NBd#CW#ZZKBtPGPfhO?sIyR*n>D)7jkk zK!VE{ZC1waF;|U_sg>+@MJHs8X+^Of@MAf(as{sVj2Wa3V=G0cpQKR|%yE#d8)UHA;_Q|4i8;|%goD?GEyB2z;3VqWEAf?WOBdn%d zrp!x`79L}ZPKcDLM6n9Dw5LGNN2(UONKX|VC96}+A|1f)otmT{XG39fI@W@U_)MZwkraxFR`V@#`H zET?5#13jNHgVbrm*NP7FU~iW^vN}g}?&Fba2x~pYz7BldVl1WCyTCBERv24_6lv4^ zQ8q`HG1^?r$3!P&jA=!&3j5gCq31JZkUET=D>~cZNIv@P{bF7II3X)KSBnPqxYJKh z9IcOiryGnF8;foj7X+^RA?8kC^>^$iC zj2Wa3WAe<=thFu)_IkDHfqcf!mvr1a#GJ?29o=9|YGsO#4e5(a5OTs8*A%b`Zy%Gy z7;RR@4yW^k!DUQzLdKX@6lLxDH36V0bDAp~0EC=ZY(DRY1 zg>H*|q3CE5h1#EsL`Pa7NDLQxzVT=`e4|)xT4NZyP#C)hDbl7t9+1QsZC1t#-`R;y z$QaX#Vimr#yBK;tV+N_?8<&UIGvtUiU5N`5YST4u{_sojq}+)i1r#8^b67uup1c@cei1E0L9Q7SXpo4!I3zw|c#l zIjf%=4! z%bXb?xt0XI=Dhs9e8zAJ>A1b#WgcUEsihcW#p>w?hOx_pu@Ar)ZF=M2B*tj7GFG_P z6P=JTrWM61-0NK~Nc-bxNi|3v#=b8)dc9D~CgbglwB*oc_`WbkX`c!nc?NYS^DiJ} zT2GHlLdv{~wMe29B4t`ptimnQ525G#hFa*h;a7-GDUQ$$9y&7v$Ah_EZTbj89_h3S3df_&hucwl*=&75GK>o=i*5h(6z7 zkAi#ec_mK!j2WbkZ(Joh>{-cJ@`y<4k$J;N6!C@->o~*Z2I*CTluIs|*2RY;A!S~Q zwD3qqbVA=?T2ZXRBbgsV&qt~jy1sFZ=%gdPR&?2hRU8L8Gj=Dvm`hJ7xxb^*dL2B;{Y5=QPEhyGP_TCJc zAY3nu@pgbVoy?>qxQx-}V(o_Lgp4t*C|2Qi<3{NDj2Wa(Ex1W^(%X%nh|Vv0#E;=7 zkFkZ_V60fXv7=$^CShzcQlw2s9BOlP8Kcd`+6~bO8Dm;etitWa&Cv51Ge{lAZV{c{ zIFgU#cH>sjc}O&<7X-J2wW-}8y+x4D20*5D?8GFb%&S;!5}gn!(~4phu1!CMo{v;5 zbZgUXqC;Pg1r;GVUv38^9 z<~C)H(9S`Mv}v?MALc2$FUg8lyhPy-G*xew#TYTdZC@`%iHkD!EhhJ>)@Aa6wV&}?_DA;Of@X4hL}-ZEUDkUToQU(#_$r}u@}k2SdN6YM_%WR}cbhgCI4JTH8&v znl4i2Rjf=!Cq&A$qF9B?^dac^NYz3Y>3Y#&f0My4`Q1RcIMTV4NBkJphe&^5kgg9< zbLAR8lBB_&;Wq;OGu$=SKeJ=rnQ?h%=I5Qc*gNCCNA?JkFZ3Sb?Isp={PmfSN-Xzo zRFS%jZ3uNB6nI1AG11BLNF{^~A!ARP+&2hg?)}c=(DTbnW4XCMAv&6BrodXqUy085 zc*Kw43CZ2|=G@`FTP=3Vs;ajPiYJI7^8zwrUfDXEqwC?cSv`CVjR}Kowdi@G6M8sP zDHzLXwVs5Y&zM2#wAfFHj@ng-v8P4nCp_ZE@Kjh{U!^<986y1y6sXfU&1$+xnO8BS zqT?Yg7|TKW3{v)ys)ep^JS#d|@rd7V-v1jW@K`5}kI7uzuSF+kbns8`Y>0G%LHewE zrt?T5#bY`WTY=kIuG!OAHz!_(x(m5mjr2F)O=bu z=@_f&dN1=ThE#Msq*1KGNMAw9K2o*NMf$4fXtPpi)zI%m=Vl)9V|Z1N+LmqC?zU_x zGs*uljJ+z14MK{vY07kehhyMk^aRX{i7h| zp5jbv<~*zEB4u91kcy6nG>TOi>7S9Zk5nymk!}>7bfj;Kj$5W1L!{MT!9|3Pf^?=q zdf5CVq|B=rQql2{MzIPbeFrJ~NYz3Y>ARw%Eo7lJIq!*%%;!L2c-O1%X8>Qf#(pU+ zib_r$NI*h z4WAyIU*G>KI#=?DAH!cl-?+vg{fi)NF}^XS-fFtFfO#p>Z%HclV39J8=!8gVR}||z zHiG=?)_3-+K2o*N^^FfjM}4DE@A;AF@E+OP|Dop_-|Mb#r1S-^8^%5q#yX5|9LC9I z2`*!_SsD8;bJgfL?+%Dg$QbcOvHtDHa(v@sT=5w*NFCq!M0DT~xby89Jox8@m>TSX zzYOy;Aj-ES{9Tnk34LSSSE<20;i_<7Z~>4qt?w*KLdv`p=`uQe7_4vf#2L{Ekut3) z7WaFOg6kW9$7vs_TIl-5r=l|oM}CFp>YC0I@{xWfI%L$^|EcF2&vn-~QdSLp&oK6> zF!m#)NSpR(v^l!IL7SB^&P+wYGKR?Vg}dmfBGZauJrYHMjr#_Dwj?9%EhL72gud~JLHZ9tdNYh=T0dKygp_#|Ln=C9>D@rew07b2z67^SnU^B{ zx9`uUzW2g)(Fu_$ANNJ~T~y)P&gog0yYwZBBuF}vD+ z2z+05oG{2RR+1rOFCj(R^!QSnqw5>ASsCLjSQKo1XB^Q98Dm;etY1Y@Ai2KLQ;@oh z8Kh2qFB6?3C5?*UCwon}KED=l50;Y5>&(^URTlcjLW8s{L!^HKQl_I`T&y;UPRJP3ieeS6O??Ha>l+5C;~N#CQ;rnL$3=^7 z#Z5`PFW5|UZW9er3@SXvs=CWq%E;<1!&pUzjFlrr+H~j&TSl&r(Pm}rRNq^ik(KC# zj4`b!)|q}R$Hz9$W6U6R80#lG9Kmp8b+YLlU%FqdsGOZ}q5)+X+oTap=y6btvBUlpC( zp=0g;isu`9ch@&kYSRUVv9Dyv*e*zsHhrbV=IHtcZC1t#_rjtRGA3z-u?qLXm4ei* zO$Mps8+=2$q{w_GS7nxD;4~p)TX>8e(p|<n>v{KK7wuY+!~yR%?80 zZd(##w7Hm%iB8BE@kOx;``9*lj2Wa3W7~Fvu|cBq9CEexZyUBh{kGK7+E$RxGDxrG zLJkRTZDL-TOi>2`TY)k3%Z*%g#2iZw< z?h_4B#JAi5DO|<08h>+96WDR3GX2@78Qlw3{o|42EZ7$Ysh)&2D(~4phZZ~!o zq;5-VkUF)XT6EHVW3cGRJq2=RsP-89W_KA&X*cdQj8$jI*czlroAx{{i80z-%*RA0 zWQ=J=u?qXxE_sX@qz+@diq0^k$X)bz-sPGL^J~)((fJTM*8W{R#`y88FY9A{wnFj< zyJpDPrAU!By>hzE(XExVSsB|KQWWg?rwpe=CuEFiMX|>Cv7GVGZi3YHF@w}$Yl8`blMcM!<3Kl6M2ci=q zWm-|J!Xv$*VxfyvEp*$TJw+$oH-?E0?@6uwdxl8QFi7{z5a}(ZOkY@SHQh30Ud75( zbV8)GD~eUPO!pECU8HKEi_}I2Zpf` z!q}5YktOu>SxJo1W@U_i76n@i7)NwM#+X(Vi}MIkaL1zi3R2fM3{t1Q?APz}#+(&O2Z=r7 zaGj;7!A^Z#Y0{yNzpm+EiFFE&^K2xk1^nY}6N@^C z9mN_<&LpJFOOY0CMMWnp zQ>IlgmVAWY5>>3#<3=HE<0%Dfb5 z;Z{_1JfsCknkGc`ndm_a&KkWMg2M_iDElzAyq zj;f;IdavksNDIbtkRF+bR4sIo)_F)jJESj$lku^{G|_Rrw=P6_jzLl66OxNmbUdU5V>w7?G+G2kTNetI=moK(eaQLjO8GmlZR9-bdetIA$|PuXFkeDdW`5i0qd;&M~6sf z8Kg%G(vuC+(R?9Dg4@e5FGVU(iH(L|rlR8^Ef~u|dTbt2wa`U6*F!q9<<~FgBRx)Z z+!kqWi1ZSJbZ&;;yT%}$d}$I==A}sY2~kpTy;pQRqy=L+Nay7tRSR9D^F5?f@3{YW z`A8Rt&eO2T+CM);`kX;JKSQJ!8l)4xWi{P0WnPMO1ObM@MJhTT(t@!Zr1g17)j}8P zLJ#TUfAoGnAL$~|c@~yg`xk~tw;H4dFU%0>4-C>d-?o}AQs$*dM}{aVxJX6ELs~GF zgOu|?{u4U2&_&9eOEPwh^tFQ;p36tNSaf~^o2>nfA=2Xv(#8yt-e{1n_>R?dkuonu zx-S8S!9^-M9@2ub9HhtRAyo@qq)i^u;R_od%}2`ll9J53u*}-u6e7LWAZ^MJ>754Y zdEd2~E>h;DNcRg-QgD%qj)$~hEC=b*Jfv!&i}VB!>2BAQuggc;EIPl2P1gPsLZqJ< zq$gyE^kIYa?#ryEiAv!zp zXv|w4A|11xo?4zE(q9{-&wbBox=5LqA{`aMgybR>9S>>2SPs&aVxfC4qZYbIPZS+n zPq!WR+*A2TTSVtI*ktWLF+}=ZgY-l}`no|n_WM@TMasMs>F9z;MaM&0FqVU~H4mv; z=pt(u+J2|I@_mD| zF(sKdpkwVnIrNQMPNX25oMA2a7mzZo+ka>^UEg3{ij=!^M!_QGOrGe3NSRg?Yn~s= z@r_gSkgA2QZ=4qD5XVjRfBknp($hufI^=5YKh5)vt-I?RDKpaFHjJH?A!C~(MV8RV zSJ)g~#%QxL)(27)EMtr#Iw50BD~h$5AIo8El^}I}!yt8h;|$T^njSv0yYAkKY59z; z79BJwA!BEFjP2fC#!_n2V}`LaGGuHAq)3~FU75rfZC1vzzPFgwS#Z!mbVA0MRuqdn zR7SzAO<&7n%pi3bJ5zKxx52NNC9n8BOLR6s$J&3U$5?H58B2LCyyXsx;miyf8;%rd z(>JcNIl4YZo0T!#d^o}NG0_PbV_H$HSDiR0701WU&ST6Vbr|dD24ibP=MLm!?e7TN zpMwq3jtr3=XpsK)YOCoYWnRUQijIdgid7it+B~Fcq1*nPBRbj(7h3E1bnp` zih^x_*kX%L$QaX#VvX}-IqlCk1gYD{8Ke$l-xQs6AG<(w%6T;AebZy?{O&TA;$trw z#=e(kRok5^hTSb%NT7|#`cC31=q(!CuEFiMX|>Cu^b<} zgc!YjjSNzUu}eiKz2*Ow=-h@}t^Jn@W3;ucibs6WJVT^E1X8AT(@j>>MasMsY2g-I zbV8&|D~eUP#r}34Qnk?S~^F`n~vmWK@!}ZG%gc+%wvGy4}CscsWwybttwFusN+9zCQ{{@ zv))m#9yI``L?`5#7DTalrx68r)bu@E@jc4$<9O8NqH{J9=n?b{7Wcj^VUF?pqH_q3 zCZo$eo?q=Q&nb0qn;kXj%QNJ8JyN7i-@4W2=z1n?R>qRw&FPDnq7yR4w4zwUd*IJ7 zxQzWkkh-mwLFzE}L(xfR>+k0fxwKDo!qTDLQLO1y4uk85*C1uT<{DNVKfG3Sc9!_Pf^8o^>xTT&xlVK* zhmPgy+RzWT+)1;(HbYkb23DEYw)a>~*AJPOBAuy`%+Vr6GiJ9 zL#h_Ket3iEFb|Ftw)w{5Px6u8C^}a`!`gpCh;+I^dP9atd!U`A-fWQGnjz9b25Gv-aN}B7NN;y*)#uyBVZq>#U}WlzA!A1rbb0E>h9)kQR*P zApLnBQnk=UdWVPf(sgs->~W;O5StI>C4h;%=L^u78PsTTj(O7wKK1!}`D$>HN}tx5`KQOVPOo8W!nYA=0r1>0KEjonVmO z{D9ST%anO3(nZWJ3@%d9@sJjb17XEO&2NiQlt$bN(wGg(eaQLjO8G`FAu3&=pwz}Lwap(2j+_7NY{zZ zH(`^t|Nao^_YKnfGemlvLE8S1)pU_EFGY%J6(_hzMaM&0FqVV#fjp#Yp^NlE59xj9 z|7GiZqz{SC#jwrV|6qvpw+88h86sV3kWOB2HC?34OOY;iaQbB`Iv&!3u^go9^N^~A zF4BiRq&3yYV)`OprjLlu>BEBbWP^0)hm(*pFGbo^5UJ>RNDIbtkUpA+ zR4sIoZt#$P<(~Zy&PV!~=(tZLwejB`|XjB^jD&DFRZinKM^8rHAtTjq!${bTR&? zEf~u|`eYtbwa`WSl!tWk@7A{DBYj$Q?st$r6(YUHAbm=Z{=gs|v>^#8^HQWI6htaI z9@2ub9Hh_WAyo@qq|bUtd!DrhjYYhk{#taNc91?BBK@^N`m7+m(IDONu_UC-OOZAg zL@GKS(t@!Zq|fCcRSR9D&wEG*9r|{CKGNTa&V3Hj=R>4j2I=#H^iG3xzsHl1GA~8C ztRPa+@sJjbOOYfm8nl4i2rAU#96I`UC;~^~=%R%~T9#XZ?Mfy7r>GeN7Zd^Xn z*F@(o*kCz{yri+w$Dbke=PJiSeIv&!3u^gnY=OI-KU8HY# zNN;T)epo)z-;2)o9i(rBNFOpt-w>ql8Kg^|N6>{-)j}8P zTOQKyjXvjye58L6ohu!rZ-q$THb~zRq@Nq4Q=U#j%DnzR*3JX4ilS@7Hzc78ief=P zP>NWP-c)i^fP@+dT_uKafk;Rogcl81wlb92-0kzfZ+e0 zGiUeCW@mQ6@89nmCf@VRyz|b^&d!$HD5p4;X`4QJnejNameQR5G27xgJ+7H*@|FY|y_Kt}yDRNJH#sFO<+N1u=^eeyc$`{GX->ELoT?V; zPPZ{b_ZJiKE>>Fo`&Rm${=&=$%%~=AbDgG((`^al^h7wNy5`d#5#ddL>am^caUxxx zzpIPVBCts6A%@Q9_d=Wu1SV(UOD@m~s1% zS{W~Oxl@31{r>6>ZnJ*=Qr`7G+{p}vGq!}t7qJ_LD-Ze|s$I;i2jTeN>Gt70;&f*M zd?hrM(elY^ht!>tmU4Op$+?^36yXRnu2X84wUjnmc5@4Lr>cdz(_fi!oc`OQHSWGu zTz&n<%u`Tx+W)KTbhSABHG!PA5vM!XIHc~Bw3O2;QCED^oigKbYAvNX{oUtOwNQ8Z zhsWubofqPEc15RuGNZrM@Q3TPMswBTKN84kia6cyg~=&tDW_M(ZQ>{0DKj3Y)>4|& zJwB(Zg}T$d%+z4zgvdWv&^2U5r++c?I2fn>dtIkp#OdAya@reCsjf|5I;6f&NlQ7M z1}47gPML9?QeD@QE;>UX-InP;Htw11!LG+&(VOCYDi z#OaE!98z~mTFU9wahv!_cgl>%skM~m^Z>U|cdA;bJN<_l$LX4|&L)=2$scNC_^j~Hir}<}W z?C*CJ8nCNlG_=Co3!WNGD2aaySoIh`g>zx>+dl(dx7YpqV1@i?`X z(wrvwoT?V;PLn-OZ?1I7U4EyPnfX9-n(R7lak^@BGCRFRoE}(ja!Ojt>2+48%y^ty zOKDE4_?)U1>Q1YAoc{Xgg?JqwA90j0NlQ7s z-s+SYk5g+Y&1rRRp+0(53w5V8m~lq$ybfJQ`kmHfMt54nb$Y8ft-(&`iPP;yr=+Ev z-e7ggjK`_9l;-p(ZlUf}wNQ6jiy6o1DKoaB<@m_6Pir&toVHJExlZ2_r?uGWOX76p zx2AncTFU8-R;SE(oLWn1PLJjm>P}S)b*INL<2cQ1ICGxgX&q*s)|?*WI{i(Y9>Y%G z6{kx#nw*lBayrB6lo^jxYbnj?u|B7&g}T$@JWh-Ec3bRsT9+AppC0ErZP!9I`Z#v_ zsW^RUlgTM*DW@~7PMPsIwU*MH9`AFiTBtic!Q-@3Uh_77r}QpWVuJ4U1lQ>$;`9V| zxM3?r!(C^gBJ7nGI;2)Bbv{)5pbW zJ$AZPoX*+okowAjw3JhNav$IHl>;*#r`A%M)A~NAs)f4K1|FxUuKH(@-)Td|&}vGW z*T8jp!WrtL2JCc?IDP+nht!>tmU4Pa+$Mg~oigKbYAvNXJ;mo#wNQ83$m4YC?Bvh= zP8&0$-*s%{I-M;}8?n<$)z!7kiS$R}c+;JdmU4>A2yN4yGUIV-Eu}d<)#p^TP1ph=wm7Z#lgTM*DW~*2JHF{onejNameQOy^*L27 z)SWi-IIZ^6Q{DVdn=^yckL$FV>$KmQs>RLNX?=0pc8kd=X(^|-$8F*#-6=C3r`A%M z)6;!URSR{eEj&(N7=C4Kztc0Ac}jEI!gczXIBmgBTZq%sel|HJE#>qMt5aq?POYUh zr)T<{sut=_&+<5Z@wuVx{7%niW|rpkEZ6B?ae5Xz?I2E%+iG%3TFNQa72ouI%8bXU zwUp+xrO&Bqq3-k?kJEo=ZkBtV`n^GV_ck#>->2uePTSx;1lrDFr|IH!x6vtSDW`X; zLh`Tflo^jxYbniXYoAlqLfvT_kJG0bOn=$GPunu{G}`6tsWz_D5^>swoo0*E@3uLl zKKGKAa*9hsZPT4H<8f*&r8#ZqbE;aXJ8kcAde4h5)b%^8d`y>HCx!k5g+Y&1uxbZQ>{0DKj3Y)>4|&be~hzLfvVG$LYo2 z{(XtxX*Xu{eVXAqZHQ|h+A`Sb!{T)HZj)2eQcmf2IpUk{lo^jxYbniXcb`+$LfvT( zkJFAlXaD4Pn#s&^G|g#$57%jqIPJkspB1Moe|1QGpOThxO7Hx|H{B^S9;en)n$s+w zQ`JJtmU8+~+$Mg~oigKb zYAvNX?d@}_TBtki<8iufWXnc=r+t}uMswQ7b^5M2?ZZx&iPP_XH#sFO<@8~zQ)WC) zt)(=l{d`VU3w5X29;X}bu6nZHX@6$0v)z4~?K<5hPP5tRYH_;e50g{UQcmfN72ouI z%8bXUwUp*`fX}IFq3$%t4|&LEOTlQ5SBE@TOX*I~~kS4UQ&6mag9OsNd-jX7CWs-KT?H zr~Sq0V0OAgoW8xsT!&Gk5K)NV&>ztfAExecmL`*U5V>%?g;J3UsMKJ>Rk>P|^ZIi;u0 z@lAKijK`_9l;$+g=Tx;&cRJGJw8oU-o%~MonbFrsBVDJ5#OX+O+DM$1>@ztfE#>ry zxJ~?|J7vb>)LKe&I?Cr%wNQ5&W5$`0GL|0G%kOkFGpo=hr~NV4Y0p-w(J^*PW#8Y zPA?ayW7%mJG>z(-e!wAhr=+Ev&KHj=)15NoI;Fa-r8K9Pa0~SrNwrXSI*u8~Y0gPo z@uC|(@|;f#nbF@z8s|EFMVyXfr#-~!-T#=Jl9qD%oYg5a9;en)n$z*zLfxrqq3*PZ z85$*Y);)WF>L|a{VrCA4aN1wwI{i_c7O~TTsEF#i|DZ$a`;@elQ<}%)o4!w(ah+0K z)>4|&2|lN)g}T#;9;dU5&e-R7TB7RFp36*hou*^1rfniS%@e1a4w;;imU6m46_S5- zr_6YqT1#n8OMOmN3w5Vu9;cU%KW)3;X*n~S&@QL_Wv4|&%ejU6jHFtqJH3J#G!R=tWXmV@r}>>; z$;>hkj?*h#r@7+v3U+$8I6W`XAHhrHm<8f*&r8&LI=Tx;&cRJ1E^rtRe za5BM1o_%^XGj!kGaXQU)`nous#!ep-r#+KQPDx8SeM$2PE$ym}8IMzIDb49M+(Lby zsut=_r!(X1(|4P$!EHNyQ1j`##uS!-O=_kztbC-!HrGiJDT5VO6uV<%k!70~O+2oY8 zl+#xb2bS5)f@u{;8G`d`uLsR&dk?P zb@u6PuG1I9>22)v2XXp#HIq})QcmB9+r&@0Q)WC)t)(=lcleyD7V1uCd7R#Vf6b+S zr*|^bU0V&#a-D7vr?c4Uuj2H(>L#b8rJT~$LVVMmGUIV-Eu}fVi(9CVUe!X~>D|o0 z1UCGg+dJv10RQ(Kr}r>J&r+OydbjKJWV|gv+uiK+Ae>TNv6?2Qq@|p`$7P&II6POYUhr?Y)dRSR{e4=_VVKDDv(r*&uf_vwSo=+kJIw*51;3E`V=$zmH0f@ z=^k-9kDc}sr_a|hIVCOS^i!)-W;{-Q0~WI8E-{7Ox%QBhOjpS!VWY z`}7&tY3mNE(a*5cq2lzbV@*y;OF8}A>XaFeQ)?;B>3nXXewI-!)SW)ZOaoRyFCx+bTjrJSy^I%URnN_AOFX-*gToT?V;P8Txc zoMnDF=7vJQ(-)X|R&%=0b^44rUC2&LWS{mt-sF_Dl+)E#r_8ucsa@7mn$s73PE`wa zr;9vJ{}_BJb{#(QjNX@+xkq!l$aVUiI9qJt5aq?POYUhr!R90 z_0g+Zs5^ay8E2o)efPv+ey6W8bC>4y71wFaj;hhGu+y8xX@e6@PDx8S{nF}`8IMzI zDb49?KBuaMy3^M^P6s^SznS0Z8_aChoWAZl?I2EHXQ%gz)BQ%Lq@|p$wK`?Stl#NkX8zQizU4ZdCQjdCr%#I0CiP7Fl(dvnEKjseUpX-2 zacV84IepvbRJBld`i{qGuWj>jvlAbAj?*Q~=x?XL<2rpuoW8?O7m3rnlTA)ZOF5-$ z-T0JF1JuWPE`war%Rb}j?=<~{OkQr-(!aE6C_IWmby-NiPNR*bcs0a zU*F`Ew3O5Jahv!_cgl>%skM~m^nIUG)k59rGG_3|A6s(dn|81C^E>^3nfV|br_0!> z^B0}IfxpB0i%tRGD1I`YP}8=Idu$bIq&AIjVCpeyv+A)1>T;7y#(ON8>jC5T7}XUo zb&9u?)?**~dQ6;Z-!S|UGx?~oQlwhsvi2|B^jPDgEIoDve@A|R z^w>w-V?UtBs7+fMIz5(KT2@k!KYju}#MTI{)t?-Z3)P48d-(Cq8Fl!@rwB7{A5!h{ zQk76XzUkj0vYfr^$Cr55`fvp^owlTiN=`xG8zSTrwNL4j)~X}%lfz8*eKObg$!ES#_V_-j*M;h* z&8a!+`y}T3fgZgIrnN5A=*P~ zbLgw39%8)TdA^EE-Hu~olvBvnSRkc{K`Ct{tmabsNKkuF9|>!iX^2|!L!@sD)7kS2 zX5L5K83}9Lk+4qG*SI6$fYuvSC!SW|3Ri87DzxxRu32xPs#$O0SIlIf0{ZJ(KkZz6 zziO-g`#WoyX^#GJTKJV`W*cpr*(Q{aEG)>s@NoXRUvqKs6**04jvA>==bf1(-t?oJ z+N_Rlx&sy8oTHms$&7n+Q(f^=6|U^GeY(!qW8zes$G&FfBGgz3{V^}+r4oOSt!L&C z7^la+cK2zCIQ^QPwu4iutKr!ur=+Evegh`H=}wt(ol;%#QXAt%@RL4zH~5^Y7V2Z; z8)nW&Ng5lYF3p(lcls?eiQ3rs#&!CpIQ@p5rot)J^;}DnQ_>3Cr_8ucsjhe_`##<1 zbE;aXJKe-g3Olv^Rle_-c?*oQPdB+vw~EtE?6fbOQeEk-OioElIi;s%@y*$%mGKcX zu2ZTjUg{QaDQ)y_<`(M5yK15C^m}H4_vsJJ=*Ro_uG15!D7Np}=>@V+A8T!LN?Kw2 zlo{74RUR*8-={zNoT?V;PJd!%2x_F!yQ%5HGyJ1>3o{AY=>3VEI;-4k;qQN4rQ|1IaMvx$HsPMrlM>mTmhX>*cxW=k<(*2ROt?8j@QPu|?Lz~B0L zOnPiP_t;F-NNrlw!PH~aX4PYFpiS}3=`p$jV#e(;sw-Y(@;$LVjb(@x^_H+DJ~PN}XJJDZ%6mU6nryLaRr zA*!7j*D2K%FGb%P9N+Y#;16!0zE4#Pb*Fza6YO*kGqjFyoc`%Loh(lOWT!8P)2&@h zPDv}wDKj3Y@ltlDdwot-3w5V|F;m7}RXcLYMQh*l@6*4TIU7CdIQ`3Wynh0J|LeKn z73s0RxW|^FMrzZw=b3tp+N^r)R{Rv-oa4PVK4Ql0F{&$GitfV4H+^jEW2gGq5U1Ml zzMq+MPyrnUgC4qPgTKcPFtZGd(_{NRJ+{%-V@Ghu;}_|%{oG?~P$RYJ@~);Hqc*D^ zyEkqeKRG={tz^dSF{&$GYPPqOHbeg7>oIYv_1HmXjzJyNV=4Rg_3-!DA!e3>aeC~a zr^n8yb(mu;;G9y6CPQooxyOD)k5QYROfmHswORET{oSVcruP^#ZjVu2@lwxwOKCm! zFFV!8m^jsXj5;AP;Ud&%zmiH|W<40E$0CVzrlW15IE^GW!ZqV>a7uNZmTGcJ#g$Y0 zl@v3sQ>rUoie|w0rXNj}xP`h?)k1y#Nn|G2X%aJsAc7xwOLUz+C{7dE=^=6YTAIlz zX@xmu#^W?z%I-9oTc|r#E!3S>X67ukpZ3y8Yu;MuKMJZagNsSmX=T^xhvKv{Id#st z-KjgO`qWh^UA-DJeL&NQn!V@e-~H;WeA7o%4WCohLfvW2!*F^OGy1i8P1or$QKemzot`32Q+k-3 zl2({gW;{;grR+{?`JAd2>P~AjGYZX3z)aR+(s7IZ`}AmLlF+00fw$W1)VYpJJKT9S z;Aurh)TtRGGock~q&Dr(H1!y@S@l@7k}9SCb;btGqRhBGMs>wY<$FtM^Xf6|R6hpA zsWz|HVFnj(yuCGjeV)I^j%5aqG29-jeF0>qXjg-u3hG%rw;YS6$Ek8f4pFN3e3ZOI+0D5#AFuQk!1x?d&i7M`V*Qkq9}Z z?O)F)`dvrzrS|Dl-n}Dh^*SicjJr=sFJ5Yex0JR|Pw@4s^oF)iPh=)|W;=+AHY-ec5e)noJx`|-^=3aFLLxIIR7#Y@q5 z)5kY`hOEa<^`k(XYCU!`Gr>JppBerB&&lplFj}0R%;RUQI4#LGIVG(yr_6Yq#!K0q zHt;!BE!2;=hRjfN>E2Kr>;ur>W!JX8p=t~ z-xJ-2TURvHWqgqs$IFvViwjnL$l>Wv>d~=*q z?aa7Nsjhe_`Wq7QP2Z<2xP`h?)k59r8O+dc`y^oWuPI&kli%r?%-jKn+CtkIo}++1 zG2US~g0tRC>9I5TD0mhU)sBL*ndyjsPl}u#+17a9bN(J{$qfJP9uYd*9UCu*)3e#>TX0Hs^~iOcQh|Sj ziJ#xArN>)L{&2o=KfxH|PtNyzqK`3Zv+A*hOgjHKW9)Q%z>M2t)TVf;y-oqn^&UG1 zocHfsE2sJxYsE}$Tv(kM$%(XyE;ig@pgY-#34>t-Kk(Md?Xio~olod!`k9U<)K#m% z=`sCDdK<3iM5$xzni}r?G`$CRrPNr#8d6FT<1N*ROZAsheIIUlcDz*MZt7!VR82$W6Z%|zGR6I* zk$iH$`w206&v#}m=QWX{&Z~ZPYKcVX8)a!DM%Db2TaQz7mF|j(A1N(7pXf&cjYD-5 zH1p1xGH=i+pBeWkpm7#2MSt%kzUfCn7j~h~8*23GN5Q$wI5X{ye>TTBrV6DVUT>Vo z%&qtoKk#<0dlbaP*SU%Cp71A1Q(Z|Dl7!Tql9qBhNOhdUXfq@;u2ZTjUW$&x_@+BO zpIfLqRV~z=c4g)%R8Rv~zDo|)J;CoZg_+*y0Q|sPSJ&wbaoUxg9);5#)wN@ykA7fG0axAspedWc@c#1@JODST!BQuptwW6xA{nE2F-U4#!sDc@cHe%Fg z{pAz-)LTy)*W(#FZaw3r6fr0@ax@j^jrvgS!3!Kw{@j_d=}Md#cMnn@#Y@ffmeSTg z>D(TD52~Km_h1Gy^vX^m{x^S4kKxLvK32Oi^8qvX2U~`_2d@=h8HtTBR_kF8Qe7j< zoIR*JB`xKYt~=wK;}l`k490azb;V0P5-)x7r-~*i7}` z#CXS8PcC&A23gE0EjK5@*m56n0Xis ze&DUQ+doC3-kbU-p%c7QUFTfsj0D1;5{7>E9tEVX#ztqS69{sjj8w`nldWh_8O+-P7~^ed?+d zt2ddN50S_KX2@YBoF^_yZjM(bDyru&GX@NP;B5e_=QioqT0THMjj#sjhn}ap9-rp) ze9QC!1Dgy@=i6-qxt^McJN2m2xlMA~IQ1~&nWLzMYVXiJr1<6>xisf7HRa98F~twg#H$ zTlC+`du|>s0}9mB*!a;M$#QDqy{ygQ)I!xiRC9dO`-d5~e~7b|()wqpuYZ&;y?=%= zL;IA*&mA`;PxklEa9{rnbNZ)))81j+KjYCqR9E>mTL18}ttvkCj}1~+-Wxd;ajtu3 z#`R9MTT5x)FYtL+Z5HpNNajLj)Uku7YsO>eBA@pQ9q;Y9say|^9sZB{-o31~XN0dk zsvf;Pxrfo7i+%0Mb=uR>X-_Wg3(riF=Tldus6;2UnMNxRanS>xG?M?SgdEQz@riTX zyK~e)ij3r%d()4e?E{oZ8IwC8HnBWbT9%a&jke89jpUBW9XTMDA1jy?8`Y=0u&f|I z55eq6N>=HBSl+0q1B)q(rVlAD85b+*UQif|M*F9A%k4XKcq9b{CuxPE(P61Wvr`AB zXJx038dVaFW~UBmH@HuB)`0S&KE!$j$8$D@!jgDTx)84IEoS{naXx(!H>}bZlz=xb(5*MdM0?Wrp@o9oDLK zkJQx6)Kqer>sC9k05ues7nM=E`0kN|3lA^(Rg}G(XC=H~PR_>TVr>l?J zz#Vof#?nwSmeMNAsgd^#LAkjT%1dJTd4+|vr#osuhcxWobnJ?<*x-Vavhut_9=bA& zor<)kp$9W)7{xokTS-ZAi8F#SdPjSv_3P8CPp_!2)|B=|#bqh&OUoxrC@v|BjY??` zZHx#~99S?u=JcXLwI@_tu{$288Qr64eS7yz>(;Y(RI9)*P*9Z89@WthE+v^EB?V=9 zBXMln4d(WBv~pC_4!2BhUv6RV;ygM?%L7H|;76wl7bV5qzT^)d=4v(YuC0g?H>jxa zaCCT<(S#fcPjQHtIE=OQsriL5byU$o&f_g7Z~O%ERv|W&h9~w#4yK%i-UP`4sX zwT92pfQ<5zyt0DgB5ypc8_iF8o2b26MU(Oh3r1xXjfzc)rZh<(R94u#pa`=p&25;K z2YaUT%xE+xHIvHW7)zZn!8t0No}nX4`y$*#G24Jp`i)csTt%%?OcymhPQIy zLA7<0+PYb4>*lQuc)YeuIs+U|x7*EB>=*oLW^n6{w4dT_#3(GDFx4I4hkI0DB5g$rRGd<~O;Iijr=}KSN=MH- z$FnxqIE!FRuIkg=^t{6SzOl(t26MHXVuqz+ai$hXDe;39-Oop725An?9UmJ%zIYPm zK>iTR5nhX+G2BguKb7<1TpcYH`k~^yFH#jhPEG5dDm|gKwjyOIOkVnykGFK2f22Ay z;9>N+v9P)hIgHXYpF4X@vxSejzqk)MXxzdd8C;I82Wy?=+g` z8PmyCUmlOioq%D_J345WtA6KGw!c;i;;f=ZYpZl;ft)KxnY%Wlr7D(YzBn!^GMxiO zU8b55b!n)s$jsI7nT@Abqe>Rd!UJQ4h33*6&WXjwnZrXfhqQ~Yl!&A29vX32Fk%^F zlvL;1+>ZXBEWj$P6L}G%08|GYqU&eZlgO+zBQl&OE6QvYdHK$kB1)F-O~ zL%W0bnbQdGlH@=49}j_wJBf!-&e(#{ywb(Or?hu$w6ps8&r;NKSr)WXOJ|w*D$1)P z&4|*hjVo9c%Pud$y)sW#Dy}9IuZWs_rKGp>f;-%PkPN_e+oTxI35T^VL_MZ<>0?4! z32u(w0TmnYpLdPZHqSmEP%vif|Km@DMt=T7qOAn|27l6mi_8PXsiXIz?e4AKA8 z_1rxFx!r7q4h54sUi`)-owWHV!~HxOIk+2@omVy%j}Scfk+8P} zNTHd=IVr?X`Pq4;rMN#{9v$pi2dD#>D#i-o$O>8)8b@4>cO^nMsomBdSzTC@(p^XY z0to(ije5k@@uLmC2dmb>|Dgi)e1t}EzX`D-8W2aYci;&3;OSm2H$tshkGKkS3g5@) zW%7S2ZCyq4!u*J^_~LfXaM+$_XtO)SA~@nXJXB zGJRPV(jVr!{?odM9op6?6-Pn%m9D#jv2=}T;o@`+-%@g>p~D&&P|H-08P((dG(4nD z?bo|o@ARHg@3XIz_P9PN8C_UB89O~KrGxt(KxwR`Odi(7(qf~FOPtH7fR!N{jJvux zFVgXrh38knRaiW86@2><4<_koQ;*-=)rWf%42vUU+`0y`(E&>xV-aUX6K`BJs=6CD zAB*rH504}b?M5+7>O9e*kFry7=u$`F?T-AAVtj=3FOQW>7CUNHpzb}}_69~d z)(Z4!svqt#TPQR{EwS+yLBG*~z2c&@vcXx#-q(1Zj{nmo2uS^I$yfNeA@(94PPt_6$tJvOn3|UCWY-o$s zy`S#og^bhtdw3rXulEHO=0Qw5ntBIv@-~}0`ctj4G?e6a6YWvDzKro=Ib?F-krba9 zLTDT(L-ICEQ%$^3@7Samp553wSv{T+0cM>E40BoU;xS_m zR~Hivb)cHV{zJjk(y2xHbWA#H5o??2e9%3IdAv@97mKLH*=d8wqSV6gdD6!?Dzj4u z35`=(SxGV8yvxm1&!Bvoc)@KHPDb?P`~OKkjV6$=Cg9j9D76o7vthF>H9N2|jAdND z;KkM>v|_H9Twp%oqlBwZ9f?=|{$Hx5al=)r-TD7pr@GBx8~?_xrE3voanz^*G3%61 zH@m`yR$+`@*0QOGR*)9M@ka-RwF;vFp1$LaqXVPDpvQUgKUC}5_0}kN8cO+XSK5cT zqf(G>pVs79RjYlC=PR)xc?D(Ni%T$ai_1&$WA<8{%MYkmER$VQJf?(Rk+yYwZeJXg zeX{XlHI2RWyrTSAVZ}UXL_aw{J)hFJea4>`ZP5?>Y%EOwU5);$Z^4d>o+4FhsA?3{Q2txV27P{l%f^VGFcAd4!b|SpMB0 zYA3BFw9k*I4sHZ)o72mA&YPcVK^=dmApUU8h^f2Om>MhWJ!+aWt45EgWW~8v&CC3J zHMkyjJjQJP)z$5PFB&rrv+G=($z#Td)?0j#TSYe7O-8 zP?Q=Wi!Td>6E6QIsI&L1&iULar%t&%wN%N|MM`cDTlc^c&sizC-NKQMM#TOE$J8d! zSRy*ZoZqVrZ(LjXwF2sxl-9;pV9==NobsCB|MFy2-YQIyr=0&&Rs4|F+a}ACRl{45 z!H@6MzV+5^c}(Xx5;*k9tiwwWg!5!mJw!A;e?0O>u>5qAemRilAFPPwH|%Ht=#QAC zgDkGOgK!P)+>RVrJP=pw-beqIo#8$i4JmF>rsDim6!-Y?tBZ&A9GIRUJEsJ7N->{` zc$lMECN~xPEBskEb+oykD-3^2N2!C(d!$%8MNrRq6!QErZgwB;FFiWlWG-USRy|AO z*fTyu2e$i$v$55gc}1fNwO=EpOZ^ybO{m>h@tm$s)~SQ#TLI$l)5x@zj_Xc*fk7dD z`<8xH7B`E=#TNqfd%w0gElw%~)y;8qk@J2S-Vq6zO?d1XO9b{Qom{jP5IyjBPAY~8 zA+*CYc@5}YY`I5$YSE~SSYfOzChy=L=F6e-rb)$PCcG{D%VZvR)Rcj-65Or|Iz)Nm zksC!cfpsq_9-k8{!OI)LwZ6uFYtCWkD>TeyLqyypo`A%gD-% z_Q>p)-MdeET9hsd*ppP$Go>w6f}4Q__@09ac~j|*K}!2Vyj1Eu^6k|t+9N$HvtQrr zjF95amw3omCMY-f4urnF`)2e=&FB$UoW59tT0_6(?x?5rjrQr4(KjtSwR_ZiX~7QI zyfTd5kPdZT15yp>o)+!aGoxF-UOn*kkY$Xb7uu?>f$*5zBKJI#Hnl87#kADdUo zJANeIW};^oG)2j6hiF^e4{~;5tMtCvebZ7i)3JV0;;n5%%5$PJpZ=Uvef)%B?mhQ< z#9R%|qR#n+-YGLf_G=O=@4YHfgUAf?kTYI{(D1`=mYS}6+S1%^3UF^br_i7t@ht$f zK5t!dZg*qeFD#Z9!z{jzIP$e-YUG@@9ojY0w$`oNb!ywD zW1F^}v=8xP>sGBgbm-8iQTw*-+jMA4v5TwsP8gkhUfq(6IX@&vUXricZ8?hGZ<^$O z{UE+DGLp2;^Tkp40=lD}@2=wS&ZFof*H?;+0C$kSMhhE#lUy`%bXv=irKS9V{NsG< znEu(cNUJ3+@qGq)+K2R=UoX(tZE0JSIPDvpDbyV4O>o~M?LeaM`EGP{_Qmr3h;I9y z26qVYc^9iX>EGzlXrJ~CVEBl?t#KKcFNLG;zGJR}ZaaLw3Cywh;;z<6iTHanCSCdt z*nWtAi$q(H?g210=vgqLhB0CjPoR;?m}=~5#J?jkZ#TV#7KQ4+HAV{h&2N< z1L*~%B>Ww6li!cP{fzj6QK~*ySNoP`(xGpg`$QP}Z`z_qN28|w@g=yQgtfJg#?@|N z{@~m^4jurP97~MML88rVpJ5!l4dzqfY`XYj;mA5+zU4f@*e`d1`vLLEqr-LY2J?h) zHeLEo#RbB=!@1e-P2g4`e&LvK-AlpD6fQ{jE@9?!Ud8BlHgrEleC*h8-Rr^JBb-gQ zA?lkW%-fus{eBGYbHwj22-kfT%!k6+bkCsv19JfBq)X^HH|s6{cP8TNk!YiTZS%|D zV5*K&T##-ZFy|o+M+(v{MEvB!aNW*evV^ng(zwbICZBV&-)q5@Bfd}AAipP!PmDB2 zqRpn;1+mt`jN{xqpS}og2IA)yh5H=_ra(BGF4a;h%p}e$8OQTXaMvJSvp8J$3@}}U zv*}hteHmc-Xq;(2y#d@1FdKy~2GR+{EyHnE~d0;evD@7v_1+&AQJ(_YK5Hm4xeF17?(cx(3-Q#laNS%m#li*YP8H?~&ds{dLw7pj zt;@r8`+><7E=adPnDLyOb#I1lDdPVMOX{}yw85mr$k|A=*>vf6>j);Qai-&K2DqMJ z)(Ge7Ryf{{1NR%4s3E~HY4eIBUu1JiuK%&j2Oa0syOedr?BwIhzdNLF7HAu7t>HbFbTp6rO^KK1c zYIAP(`yjZ+h%Xj4NOuF6?ZVmolI|X1_Hl03{es$eRbpfl5^X`c2f-XQE!ZzbPY|Xa z=Vskm;Lbw)ZDE6SzX7vDxFElKh1t)!S(oPfnpY=A-aw))NOv8Wt-=NA{w~a3&ds`X zi=pZ@iIMM+!gLRTsWn}3Hr;xtO`1#j{>s|n6oNzW>8dv4QOy%6{ zm)5a2A%6aKhoK8*oN$4ow|T$nRBH~W18+a6x_}H>#H}w)x0$^@&OWULH+zA zm_LQH>Eh2&L=Fja)NNFo+3&ZAoPzj2%mnM!yFD>-1`=&Sy6uEX=iIJ42=O*|g!|0~ zbFpwXU8?00Va9WA*8LdVRfuQK3fCPCW|D9=UE06Xgt?n@^ZtDY-0O%>xieh%9xzV| zXVazqyHJ>SI5+$K9^5L#7u^-Ey9&%^;evFx3$vedyKc?9aeo|%Hu~3g{MNh&_s5ZF zv*}W_nJ_IlH|u^1?p(yH-5ajk985>yY`V06Q-vAKxq1J-4enCJJKYzqI~dGp;cU9J zen;bgUO1aB)$*e-KXY!@ z{RG@T#6Nf-T=z#Xe+p;QrTu$Im}4KLI?el+u8%q*zJ{6L@%$T@gojl7Y`V06YY0=1 zbF*KXH_k$QyRcNN?KrCTFz%rv(H5k8nlNoRx9esgp7@BWFG#mBnAXDCbgBHg!gS@_ ztotmuY{U-;8>D;E99(xJ(Pq=7&)W*qj&t)m=WcLO#J?0aNcRsgiI0Zs9wkgI&ds`X zy-*MF6~YGTZUeJVxFElYkEymLacC$nuQJ9^ao9|yOCtbuB&kfhz0A{;zHeLFBk1%^VH~ak(T%{)x zBWsap3-Y@MO!AY8v+0uG+QKyC-0b%=a7_^ZL)akQW9B7B8Y9tW)1}YP6y_|>&3@N| z>x6inr&N7Gx~;%Og|q3B-=4w@WZZ9ww2xrr!&tt-j=G^S}Yj7oq z_j)E=w*bsk;cU9(ce*gMI5+#95AHt1GoB6C9SNpHIGZkgez`DLaBlYdD!3VlN9Tv@ zjsR06oK2UW8%z=AYR=8)h1uZlMEvaM!gYIrxj;CZE`1&oW;Ew!zmJ0}LA>ts;ks?X zqzh-$rTyDSmMGFF(oIXB<0`w-kB#4lYKu6rMt zr-ZZV()%vgikA(S{bMyXvl5`Qj?8R{1`@uXd zoK2Vdd66*Rac=JCmEaB`zI;)*?p82=3un`%ey;SAs_R(J&HcOtTvNn<5|&EX){}`Z z`e@a&GQtTGyR{_?N;4>HYyG@s(g*@_Upp4LLXarS)?w#CHoDq zxL=1vTae%8!ko^z*)KiEZio0rVS{x40aNp}U|pIwju)mW=Vrfi!F5D@gRnum`@mFt zJy@5<^RdE2IX92zr@@UtyviG@z98MEVA>03>u1`(DZ*rPZuU!m3wtEu{|XzV+u%){ zKagm%>C)#Nh3Uk(dH>!BE(7tE!UpO70%pH(HeGtIk@S{oTUE}@exCzZ5AhF$4buGy z%pT!vx^({iSD31csW!7NJzuDY_~*<7uVZ(CIV4<=Zq>Jy-(xs8>pl+M(-8ka*dV_@ zg85T8n=XBRNSJ>)H}Buuz}0vsF)|y8wjkZtz)^KjtrQ>%K;?tLe>plwR z1>tPEw13|e=6%l1{rnoZuMofW-EiIcVBQkWrc2}b17VhPZq{7|?i<9PTNC zY`Wz4Ct-GRZq}vil*oIyFNH)K{cAf9{tc$;`--#alHWSQ9M8E~_bYIxBYsHOAl>@Q z5+i3J(Pq;nza4~0;oPjd30!}~TYaGF3)1ZiW`uAyUGh6tm_p9Yx^(?}CE__BhU-oQ zbG2|bUGjUAFn4io*4+s1NyM-HC|vh3FfR&c($oCy52tt@kPQ0`CSEOvv4+D^1B_(kX3ls1Igx>o|nx-e8%cqHy-g(Uxw>m z17?n;SdL^zu+T?Z@&GiEL3UZg~e-=?VGUBtJn z3)fBhIx%t_5^XkJdhfHLFbv`!)BBS%@OevcuL?`G+K%5Z!TcngO_$CKyTSaeaVFjS z!Btw1_Zg6Ab9F1cZ(AR+*TH-#T+sXNCvQlMoQXu6t$p?heRLml zBH|6csn9;d?+cv^rk8Lw-3F*{kT4~jn~%58!Ci~^z;DBKOTb(soK2Um&uC$mN63jS_GaX0t{IVQO^&cwe8jkbR!DI**bezuwGg~-Y`$%oBF!MM!Kd+$Q3w#do z%pWVX&v2ZN1~W;xAl+%gOy}ILI|K11KZWb2ff*oNknV-T{rW zIhgB&3(~z!n1?wx>wXN~g@{l6JzV!*F!O{9(tSagC7j!JS0nzwAK|*MgZWrEn=aiS zUn5N9PpZ?r-d%x6L&Uc+qpiaXSx>gLG^C zofv6|L|c&G(}iitx!Laq)DcDe!x zj>6e=Y5%4Q(}Q!f- zCBGwt8O6ETFWo<#g!rI?;ku<@rVD4&rQ`P&VIJh%?3d=}#}F?(6s|iH%xvLoy5x7R zFwbyq_WL@xw-7J?H(d8lFpmpo(!yylBiCzU%K8u4)G<-1nX`Dvr9OeF8Tdi zm_wYK{nGWv(aA}XXOUvf9Y#tEian8-UbHKff_@L_Hx}{*I3un`%b=@ss-avXEDZ#R?+Xn7?#8=b^ z*WCtYpKvzany4?armCwp=Vre@f@_R;@=@Wsr-5lJoK3eA>N_7yInq@~HouKg!9$3@ zT`OGo8!$VBv*~t(?p`ow*G`IbM6w-6O;ABU#7mD3*S!PGqr%yA>G*vX%r{6|kZih# zz#T&TpJT#x>(xn$oPk7}O}7JL?Z8}uREA{JJslO?jQC5(hU=~d^Sy93-G0#BAET=xbr_X=mzrG9=?n1!61`}qTKOA)`ZLAdUdU|tc`sd9Q!71m+S&ak8n2Kaj36yhos0vq|1q_=`2phZ zo*%Be3CwQcY<^p!zI|YtcTI}4MoO^!Ui5F^1|i-(C0utDm@?sPx^&%rC75TCUPiK= ze`$VRgZScTxb6lp+l8~~lHWaGPD{nLD3Z-@A}Z*Q_`tMq-4Za@2xrqJzc+(<9qBzJ zo8N!HZASdt^l;q+U}|J2&ZbL#>w-x~>VstSdp0VVi1@?Z!gb#Qvr;&l?)lLD3e2(H zlOhd}Y<`=ef^!ki>k+Pd6`0$Ev+2_F?fZp!nsf6!`5w485x+7sT=zjR^Mwo2eMOiL zI5+FkI%gf?bF#v9-v;xka6!83gxSiuS(mPV_apvp&v4yMV0H@^q`Oa;YQ3mVvo5_y zdotonm+EZrf<04kHD-G&ZbMXd@sxv z&dv9|p9Hr9@mc-Cbr*tJB3zK}a$!E@+^jnnx?dnZB|BXA9xzV|XVa~N`W6balymd` zeTsAuzr26A?rbp62xrr6i27avvl{7FBznWs^!^LIM^R}2o@*e{=3f6+c&{aj*iT@Z z<|xkg{>v0JXc?IE2Ptj@%Gti>bK6kd-$kO$=A$}diNh3g6z66ipMX0B@g2gtJ`C>* zR3DCO86?_ly7Zn`6JgHe+&o_B`D$y#t6re$3({={rh{-c-KwZBDoiHlcHO>+H@z@i zHyuola6!5k2@~VotV_>z$06SHqHx`@V5SHcqQExmlO??+nB*&JEYS63nf_1?k==%pA_my7YaFPa;0_;&9zbV6GP~NcVPO?&I99 z`!M3!dEvT6V6GG{NcTozZs**rOYi&KgLvM^aNVoG+$LO*?)}0%%DGvW#??H;hv$du zP62bHa6!7Wgqh8`S@$#O&Oy9*RJiUfU>*`KNOztv3ph9Hu7d8%h+iKI*PRRICE#m0GiHQFqY|we3Y60%EBGDG4d#W&JaBkM6`>d@HA8<*y-w9x*2^XY0Q=uq@aH8i1_&v!*wqJGfp_0F5Ukt7v@UN&3@kncRS+!OTu-F!CWPrO_%Qf%@F23 z&ds_sZ#;we-KF8Wi@>}moK5!>)b|OP9Z1#7?CWuQ@1{QDuOiXr&KrjH_?KYnmMhM- z9-oE=y$q)PWW`;KI&JImJ(uFX-eroj`8W;bYYWqcbMyN&%fVfUc(cnZ_%N);Gr$ZK z&ZbN2@e#trI5*EjkAoYBc&#hKbO4w>(&*f0q17jo1uFe;tPZg()|R?x55Sa z{aKjZoSSuThwdK4ubURWe;)_4NVp)~#lkG(+^kFYBUT_j@#=8h+rT^`T#)Y5!o0w_ zS@&+}zKVG7Yr=Ie0duKvLAuupb2H~=-P@o$3-QL&!*x@@^b;;fcbG7FoSSv;gzgx` z_X`^|u1>r*DRMdzZ9%$igz3V$S@%KI5k>qPVS{w{gQlL)akQV{X7WKnl}6Q<%1#n|0qoC7lufTi77o z<8Q?I11U_ml`x$+H|x^%R#(K2nxX0o(mfN*xxxkYb2nl7aBkM6=a)H%A2Tyt_Z%=O z!UgGO3Dci*vo5Vqhai6JP2sw&!K4Zoq}xlF9L~+UTcJCQ>bp5yHy2E?a6!6Lg}It@ zvo5`Ve*@ytTf%imfGH9#NOy`b(>OQlJ_OzC5kKwLaNSfe{e=tCy+D|J&ds{?cLoX& zuW?(rZVND-g$vS67p52IW?fp>^+){V+rxD`gUJ%kraJ)j<$!qt={+R+OP;3t)bt+D z2E-q|qk?XQbuk^+?+Ak*d9(c;tfk;q3u|j1t^d9e=10!W{jwh1FNnWCt3vw>>%Z^8 z{4QLO?mxmL-AQ$tb)Q0{I^r)fqg^i;bUz34op3?AzX$Hx|^Z<5#mqZ7q0sun6<(M>24Ng2j^zp zZ=w4;;-B3guDcV=LE(aQtISq))#2Q%OY_T#h;I}&XnpVxn3@l$I)ikN7p5`iX5IUs z+Z^$CgbmXD7R*lJf^`29rqYAdM!RlR#OE**Ja4=W=2PK3%BA*PNSmpN8%x#BY2!T=z*ZuLu{UyF{23oSSvY?<&Nvd?Z}=K``@$ z3(|c>n0Gn1>wbuM*_?3QSzsO$E=YI2Ft2iM*4+TVixI!?(Qw^YzF{y&KA10r3)0;v%r?%=x^zEcH{$c33D;c?X1#Dh zx<3lDn{%@+-PhZL_yf;|>%I==W8s2y*9fzbbF=Ov(ESnd>GQ*N9|iM*a6!6n3iAQy zW?i~pyb|#%o(tD~0L-((1?j#l%o5Jcy5B+f1H_+vK3w;GFkc84q`Og=ZJe8R$?tB& z=PU@n;SdM7SW`<-&Z)xmovp_}zf`BQJ;RE(Y_7a5mj!)VEfcZ#XyW zJ`C=A#HYOyuKO^U=Y_NB(tVZJgjvG5S@(HxA0S@#YPjw!Fpmirq&r`jmpC`;(({iu z5YKuoTz3qZ$-)KcUM3PtV_=k z%Md?$X}E4nFkOWU(#;emhjX(oy~jNa@rLh(>z)Uuw{StagN4cC+^l;SbjKimK-i$` zqm$lGinKtYEl9VmFkLw}>(X=L48+$88>G7jO!Bf|U7D|I3v&YJX5D|mH9)-n2jTtP z1x!!jY<@|1pfDG5Zr0rjZY0(BVYu#CFjIuH>C$|4jWD-xZq_BgcOpLPqj24YV3r6M zq`O?0FF7~sehu9Xh`;o4xbA8&-wPL{yF-}2I5+EdM+FBFPhSzPn+Im1a5i1~{4!xK z=iID2oAw9d^;d@Lb^+5-YrI= zEl9V8Fzq-u>(Y9o3*w&&8>G7f%s;{f=~iB++I9@*X5G!uJpu8*g$>d@{%gFqghX4A zZYyEV<=m|MC+bK=y#4y{{hI?OU$`LM@xn~u+^kFY`L9I0>4tFKbTB!>1?gTS%vjFN zy7WAy2=V6MgzI(#Ge|g_F1;6>D@-xx=J&nd0e3ax(Qm_bM}R33E=YHZFgJ2;)_otk zvk@P$FeA0K} zx_5z@E1XUDEY$ZrnDt0MA|+UU&-E2>dl8?%Ib3%+nDxThbW>2@k6=#w9^Zd}Wcxi4 zI)3{j-tC8Q-Fz^m!r64m?-gL4Mp}eq^ScY&GGcxV*Ifl>vv5JW+rgah6W#?yvgv*V z-L{AyK%y;Zo;+y_t_P853({>1CKssy$)@`|>bMN?@jr*_-UQ|W;evFZ0J92d1CmX* z11i{w_-Wh1byLCg7tW^J6Fx2g^90iCNR=$#BSOEI`Yqxw{SvOb8qD{?*>q`K?ErJ) zc6|3LlFe@v6|_UVY)826EHIA=XVWFW^TF&zs=m|im)2J;5l`9`u6rt&Hp1C-$?th! z#v+v<+5C1z1=k|Jba%M!cVK=ME=YGjm}b8wMOq=*bZH%%hWL5Eh3gInbBS;^-Il1Y z49sIl^O0=xRSQ(`IpX_%57#~651glwXtU|k{f*YbbmrWAUU(5)FT@WD8#Hg6{3osl zk!TCjZ7bHU&E&Qc^>`|6^CO^E-wuR{9_@0Hfsk2wR0 zwjkZJz`TsK2Fa#-94hF10N=6mPq^+lFqa7zq#S@#J<&O&?@Gr{9|H<*8gv*|WNebs6x59e@hzHWR8 zTzkZ~2^(}ARjHXAIRS|_o9<}D8iTn3=>eolk!r@{w=*hOg?RN^s=grI)4_BS&ZgT7 zx@ll;LAn>o_C1g%pn`W1UspR^cQ2UAM+f_z0Nta(OhdX0$#(qGdFw00w;U6$n^Y$` zavTzEHos>g)(}jVa5Vdxe*c4>*G>d;*0B|IE1WN*;Ch0YDI8t@oBmGM?P$;$$0bMJ zsH^H5<#2}c+hZpsM_xpt&E}&aVsC+2FI*-3x5>x*_ z1x)jklOwH>Z0pHHRFIB%_xjthPfV3N_63$#Ezw5yzGysQ0ThQ^b z4@|X&!Tp?$I*tW180lgpo8MA2yae%|P6^jdY?K^17KyeXzYV}#igYWI&2LXsFbDBp z8i(svJvBLUB2thp?cYH9|NBYtX&aNQ`FY~gIWuToS1`p$ z(~)fBndbYu5r5*WaNYO7tP#$pOMbrv)9&o#NLM7A-;Sst8}S=jhU-27=4Ih*y5#pA zFux)ton!Y~3l$uXc#~G)x@lkr2xrqJzZZg8gtQpR=JyY9pCP`qb-3<#V15yP-!ZNqgtgUJ%krkjKMa=<)^^afI;hCKFpB=+> z>vY2Wf<&85_jtt40+WW+8_71VR)QOW_!*tUbu+;X6)s43B$z2k*C5$+>HDDWLcF9) zxbE#><_H(0`wW;RNXwCIx>ZrZH;DguZn$ox^DwTEXtU{-Ay!|Q+c~doT<2t=~8T}pD~ifFj*17MyNE=c!fVOpe8oo3xkR4^U!6=~tR z+raD-E=V^qUDY+2bF(ge&+#h6|H=s0t=la*(iDj{o9@Smodf1?qzk*_+;90F++T*Q>`2OpY{0qRpmzDq?4X z>4P)`$@cy2l~6%B;*$r3>)s9K3E_ft7l2uXv;oPcTLl&Dqq>HK>ox?_QaGC~-ACyJ z=0T(5fHxFN0|?4)@`XLQN zvYjutgByqV;No!IGBDQ)XVazg<*i^ABE5lR)1`i1iTLLe!gY6nIV4<=Zq`N3!YGLj~oC-!>^+_c<_&g$vUC5KQJ|ctNu1Hbw<^A^!4|aNRXveh@B5cPE&l zsW_)2*>oGBf;SLfe`&bx-(ad#`9Kk{aK4`m#<%Y?P{OJme< z9hfJDvpp}P>%2F>%(^B*6rNI|*{5&sd1wxIK6rE78j3W+wG?yHE^2eSq#`8p)a{6hD0S|C30`f%Ob zz&s+HO_%!lX)v!LEk&~VT>jKTz4^;PlOB7T?=Ll(yvH1UApgG>Bi*9N+jBX z`uP_y`-KbAO`3t{h)4~PY`RZD>m0<_B8BPh0h2saaW>uAsH8TSr;(1jiH>vgbJ$JCE%J#It$Zm*zQVP4BZeQygxy0qD@1FGDC_ePLtv$d~3VvWI+AYG4S zTPMbZ!|pH-oKhU4V`#t#{j02<~-JuA!2XhP3J4iJ0P1hd_z#TyRnR~-^ zKLWE(IGbO3&i*}^YWL$B38_Zpc%$wha0d|2exQPGh4FU++E*kD`K67|s};C@`20E_ z7mc>8!1V(6g^4qat5Y6Kj+}!;o6Rr17t|Td(?}m8C0dSiTCe?u_=yi!@N3ZR0H(We zHeLEVV%cCmdIa}X<|O0q(;aQ>=WgJN5MT6Yxb7-2n}xIK(({zDmS@FQ=g{hNeHwI@_vf~j#(7MnuPpq%X(AV+~~_zdQlXYrgc z($m;4&j+`U?jO7>jHAKxUj-in5d8QsxGvyMUSRN1p}xl8{*Sja0h6pM z?|y>|gQC)aAR;gdhzcSz1Be46)z#JA#Z*@nRn^@y1g$hP4YbU3kKH{C<8Dkeu8GEd zOUxJJmKZe}#T65c3mP}nxFs$zQ4{wVm$-bt|NlMr)~)Jl_@3{1lqVBs?!9&IIq!Lw z|GS*`%nW;OV7|Y{Jg(=tpT6-9BUisk@^;Um^L-l69scG!j6CCS2j)A3|6a>8H>T(K zS>WA)?}{$}z%w`V=QI2{3{5^vzdW1gzRLaOx7_x3pT#pTPtR@iUFCIO%`@-j&nNk_ zk-x8c$FFn$g>Sv>@4l61K9HW<=(|tgeIMtU`ri?wvk2-J@ehvUB74Gy94wS9ysc~gY+9tm%H)Ysp;8`#y7$H z?$0yJ{CO{bHf?}U-^+8KOyZ?>549 z$9<4`AO7gSjpp$H?w!LkJNff;{zx*1$;VAR_X6(EerRAGL)LeVXMR6Dx6yYMM}IcY zJmbUQ`;YKvvhx6wSLPanuL=kw=b{MksKs$O&l_s{s)z&wV~ugo*;^xQ_@eE~Nf&NDyyCw%^o zgWs{3!`AI_Jol&I8}o zdi-M8b3^EI_NVVKaxs7O-$rzKG&d%A=B8W7*?vZPV>pie3!Zxg_s2drK$juk{av2f zpPt+3yQN?D*m^kr!ZYvu=iC17=XvH^>A8)*t2)&m@yyZ}hz;=Ps14-k zcX@9A7l|u>>9)W7U7q<>dTyidK9U=oz6@6T7i=MaHf?}kKF@QH_*eYyuMT{7fDi5o zy=Kxg{MG+$6o*^pxhJG&HyYoSyy0m)^9}x-@HOeRBZscX$9Zm=`}g|#!1#vn!FHax zEIpThH=gUnh(|_h{H2qg$*x^Ga`f1P#w!!0nOdh-9ov4N0|yrOoU?cFs{3sIf4R(0 z%F~^JXB)NgdTF}SzRzYMY*jNnkw4=jhmHQ_mLENDX8V;Z3wy7;cJJ0JS66mld0=gE z@9LG;@7uq4Wou#Y!u;xj|IxjAaqq(Fq1Cm8ecgTY{Ljic%U3;ev#GK4)dVy)eJB``Y%}{K}dx zE-dc%ok#c|yhD$jryu-=Udt!1imzO_`GK4H>~HWpH+VMNw*JOH6cgHZL3~J4{4IZa zOZxP0oS5hRmS~xnqesf$)`x%d-wD?Et*zMkTYvL{7W{v~xxXdm*pejrx5*u`YQHT~ zuAkv=>&pdgd2Zy`Q^jna-~+8uGBj~@_732xLoRVTGg?cPQ_!=Yi<`eI+dNBk(1KHw~uS( z@lvPMZCA>jYNI}KO1eovOW)3rFR`se5mhT(4^U@v%Kw*`=y6B6(6qFnP;uW$O}*2KRtP5@yWus3y)?r zyHoMenYP|nDo<6$%Z=tPA__sXI~N~n^FkB$@|jAzGm??zF2!f2tJ4*&dTF*)t(C@V zm5~ReSKYPvm_Id9tySd7^q#$MdiHL`XHA{^;X7A8EE4okqD)E7wZxHu5h97Z=^V_z;5G5XnY1kKARn zm-D!$yEr@Dtj;d1T(!KqP+PcuVeiNT)9;Tj{(iR7s!miZpieD`{XE#ZN5W6>%7~zB*N^kJl=#k+akA?pb^eA?m=WzBksOWw}&0lcV8EXv&PwPA)!JovH7r zBYi#I$;AAW;$tDiQVzM8pIW@%D$QlI^>@B=j1;>Tr`wvTck7kTT%)z4GTxoWfv=WJ zuze;Pn~U#(8+Ps*uXG9;L$5k)*sI1`kk_x;oJer*;;TyKa;4d6mFkm~c0TC)6rU(J z8at{L34p_g z#Z%GcP1*Aj`-*jyE+3gLL&K@=1QhoOq%d^%PU$YAiko_D0-Om zLUrYr?~+}{zD$?$SKKwbTt&*W8Qd*h9B)jQs`Z?RN2klVS`&gXlRk0eiCz$YbKKTm zqaV#)o)I^_F#FCi>3*eME`e=?K>kvdx+7&&-ehG5Dx-^GpRP=gF%wCuZXZ^>Jr2a_ z0Q3BW;Ma^XX0tuhO(8#1TE9EI_`7zc;{YD46mPxVz};+FLxBsE=^b=#?hS3SQpXt9 zJYC&n`&RE}!lY83*}(5uydRHFV4;zA>CKxSqN!olTf4fIoy}^P*)FiAVtuFkho}1; z7QEY-=wkPfD}U5Xt7m8i=yxQ;5U}wc=>vC6HzhmMsN@)&es-sHr`iTi8{JgKk=oWS zGJi*me&%9*ytAv_tdzUCw$AR{F1wSV38Mb-o7-o1&?S>tXwgycVkbqs3mv(m1R;SS zX1|{)^Y+AFcTjqDK%}_xR1Dd%;tK!QUEH_7+r6e+-n+c}y7vBsg+2b*b5GXYazgYKVnqZVHmDUKepR&MIB>4BFLe8Fa`JOmAOi*i@3D0pHLr-)y^M*GkAoE z@A^lwFTfUh3zI<0)hE#Sosli+3vdPZI;|O&DzHR=0-j7D6M_O?KqxXK8WfDePbxcQ zUuqL5x=E?f#1LBh#!Sa-t%r~B!!z}EX#$+z=$6JBt&ZjUudG}<7NbgiX4(YS#luG8 zV$z@zM_p!t{p@sOdU8Nc{c9`BdY4h9z2d)Xx>t=auFhYzcVU7cdTICWh1FF`!bnEcAg{95zXz6w5c5z33V6|)>dJ;ShN^fw3xL z`Wz3VT}t)#bQMgL*_+QijA=jd*T0^SI(KxWTassnXO3uJ#CvYZb!vIq*{6O6-@bjd zyMNdXjMZI6x0*mAQl^jhPcQ5%?L{d=flZ@W!;zU<1D$p(|M~#(+N?~sz{0z5nx$Gc zjlTK8YmY;n);Xkh)95>)p53eNG?9T) zGJIkKhc`xf6}>N5*l0#=B!&MFKXBKL6hAt8JHT<{7$2dT&H8?7lEqwH$`1I_aB_(YHH|9hG{w z*{V*LKp2y)#!M4MyJ_@Kx9|nVYU?;vDPeeQ_V3EQ8}vgKsnVFh``k2o_j_fN*iqTl zEfL?T2!>aipz2QFbndTTQw`Pil>44(?Sd@hUA@6G7cCE(YNA-B)!8*xnly!fv@IB+ z(wS;ReCJL)dj}vw6)bTgf4?hyh_~9sL+<8rJ(+H9-H+6AWxBy+$4c#L*&l078_!*E z9-wy9=r8`%DBDU0F@j6L8FiT})(J}t z<7xHapOU1kDHI@nUKJH5N}bShZS-OrEwE{H|7NbY8vvm3@fKzaUIo0SrNCG<8$u=F zK$<@uPyDUZn4GLtAYR*o_A2;eYP>bu@`MhYmQScs*&%LbLSp2zahimQx_GtSoi)bN zQDCTT@%$JnvI*Wbg}PhjeDRLZzMEe}0>z-u#Beqy>WxlyVpnCSFYRGWFclGq+9a1G zajz3cRd&jS7Mw~bLwEiYt<)T=cBV^BQ3!4WR9QT>h!zt(+${U`IB@Y}ppG=gz4-NB zw2D}yJyof6nxMuK09YdFx!;==5PJ)y#7xdF;YM#7y&HqJ_Ni9rI+l-i{s!u~V zulDh*PmR@T)LFU~t21XY`^qUOj+vGsySpZ)L*~CAQd!Ob-44o#ERW&^)*me`p7~)X zI~}`am|0q?3Z_i4=u>(8sZxh&ph3$RuOpcE<$;SBA$ZjqM-)sNt{Vc5wSdr?Nfj&6 zZpKJ_;S7e{ysWWHO4B4;Vr0;(+^RsR#90!$IWyo_&Sr*ENfI5;`7i|h;Nr?crh?Ao zEBk^xnStaj4!VI@cThF3`#OOq^Z6e{N2MwXJGnnU-578(Ht@Hc@YFJuji(k|%;jwlqp=4!-UU-%bA#;gkn&1npu|wY> zi>jU^xKGLgq$>+AraKw^hVq)OHq3OQ%>lSm`biWnsKf5;mTt+*I|XgZ5G65`IrST2 zbCXLNnRm?wEv1kCPBPV#>z`^mb2w1CKZdI{YEx+Puq#kj_3NNP8(P#S<=(6Q2wTV-reHmexnz4nivuz2*KQ{c*&;G z1qNJgA6nXd?aK1f;-eQlgm*DKcw=s=TArHT;dTBMVpT)I)yG>E)~j48nK92mwqzob zHz%7uXXX3^R;5{+S#QSl(U-xs{7TSCC?{U^4A}`Nf_n&JOt?``Um==uLywOWST&(P z055__b|QMgyaY)hPTSOpm&yiz9-qn1^JJwVMYzk8-o+a`*Mgt+OdMF+-CeUN&FM2r zQb;TlB$9qV4F*)e0(A)*n3)!5maGG=LJ3!HSX)@yv#_VKva(DjkL+*f(Ef#0bLLBJ zMhM_`(PwSB2b)G;!|D{NWwSo3PZzmnqS2a@-`HZEC%hythyK&m`gCchxk93MElgf- z&SB+~lG*A}-APJD_Md3?w33qKiNb1zyu1ZV*5i+@UadE9EyGwgqJfv@%&f;+C;<-g zRgrU)h|_k`*2j*(7C>UdVxg%=@%76ZW3ha^Wn3X0E_5B<(byAQG5gFganTgGsaFPR zrSMMTHE_syWo%}0O1_3fLF%C8RkCQetCMw5bGt2R6Ua5AUM$NPhASBTe6Q{;SZQ8} z|2f^Bs>wxZdyT$vA^bnSvb?`Dzj6&(Sgp|q`8Y_)l*>PSxj!t!f+QBi&ksm(D(uS_ z3K3s%g2k`NZ~a27erKxFY5GTkOP6w|H93ZbhO(yi<9JPoK}QUa)u|D?sCW{8iZSY6 z@wpM%*1yO277eq+v^xeAZMO-V1#JY{ZUjp8DFJYi^veZc^ez#b@mjZPhhk}At-Y}O zz{=v8U_lwPbEur{YX%`w)9zggb7a-N$0r07X zyNdbsK;O2|E0Q?Yr6_bIugZV&)h=vZK>;V`Z#1b-#O91(-0e1*A1;nJI0)rfwWM!5 z7t=Zk97}u4CECI4&?5CV`m>7~$0l~iW+q6chXoRFJwUK9tWyL1=rBcCPFe2D^a8XU zDZmE*52#iK7uvjfFh>I{!lo8<)T}0sdZ$E9@lJFRiT1AM%pl z={)@)VYoSC{EyyKda&H78PZK=lQyRF)Jec-xy1-~5!=e4*#$nsriBY2ZEz&x?DCQb zLXzmPE)ZkR1kQQPeYG5(q+HE}oFuNV5NzmS*$h2s_D{zFGj@_bLce4tbBo7%cYc^l zNm!Bg!A0h6dcTPf8{AedIjOXbw>^P&Vzo4p2qN(pYe^^m7%K8=M@WD*Qx#N-Ro=@G2i92sh6SKdnD(Qr#%v=MpqR6) zPH|DvfKOMc=Wd;)@H>~->I3}7!tJyyoQZG~g9J%>DTM(CAm9^Nd0q+6Qm_FQX1F5o zea(}VQkzhl6hmTH&E$0~K0109qJtwfV!;1t+^Cp0wJNo7oc1$bvQ1HJH*3II&_Ot-0SlC=~SImwUM~Fn!PKpe->wf&=sjQHhEgWB*9-_#GmNRIc zt^m42(3?ifqCf<26m1O0TG04yyaI{nCjU}S;8tK7PED)a?RpF^0p&?`81<{X$FNc( z)O=5a=m|y1uuKxJ7B&BZmuL`SvBNM*o-c(P_ATyNTwPn)ch!NbuO^w@u)aq3rCOkXi-1zK;3>>l z*o}ww9tXWm#qw(s?}4$sn0fXkusu4`e0a*ydYO2ap|J~d)9A&#YD#i(I{&y31{;c8 zNq2x~2;>S_oefRp)rh~X1m6P;?H*WNSm~472l41dynWrB?b|bhKDW4JNGc0{ff3?% zeo&?2iOl8r^6mrssG68qnZHK=>u=+M8`LSVV$HCHA2iqbc%MZM19il>!viB`<}pi= zJ`h_4Tq+Zonba!f(JVvwLrACDpWt14iikMjJH$L`fqHmeZjqXEjRW+x)@)=vac-sG=YN@Sx&%${7D`sK6#Fuof`C z;PndT&2F{^!VnO)HIS0bKhUF0NT$99_f z*=R4!VY`G0cVs7~F3I{dY&StAQfJH73oWnjffluKId}38734izL+Wu(gj#EWtA6&; z^1c#CE+|w=ydYJ5i?vCuF{!9W1R4JalOY3{%(-+_3D6Fhua7(Yj{y{kZh~8iE?voF zc)a}o@OhM{CKl2xja6$@MiK2ikDr~T0V_(OkcH##owfo=EfNYnjFXIywXrCF8n11V zVj~WZy~EoSJK)$00^bfEtTkr=sD^7xJW5IkLn9a9bxe>-H_tCv;>#>ly;+`PDLTo} z%R-&TgHzRM(so@dZG;3rGgYOCaWY91bpC7XH^yLibeqarN$XjxD{#QTCE0SV!tuOV zPJbZZfIqLs-U;?*p*i#Tc_CDcQgAvhB<)un*D3}nL8Bym>uyb7Ox98G1b=J;7 zK@ra>^2*MV301Z7FH<_CSknWvE)iW%lfJys?#^!O&e>q!pW6Vph`QPlFwlk{t_3D!!qf5M36PB~8|8T%u2nrV=tnSScK5A!4yYz_0)L4nqrz{YxqIq`qSB0pki zWcBq+Y?Bw+pBHHkYBF|70;NC%k`j{rPsvR8Qd`0cH(-V$xDydr-WJM&jEzL?1oTgH zr$}R-S;@(cGMuee<`k@fx8VwXK!Q1iGaNx(E5WyH^+Ey5iMuo+KqT&~^I;_)z)VH% z+e+Y6w6BD|E)!pASEQ;+#wYJlren3+ZFNv3?8Do;u(r^+>X8e(*R<_!ZF$Aax!?L> zM#66A~8+e3A~P2L~yxNwO+>_y-sZcS~FQH!9qk-~LGLK`FJ>3y{{l6UgP;5(@!#eKS3_`5Yn62$94 zyvW*@7*b~OPH2k|cA`yW!eC)+qEhf?Olgf_L1b&9yYt5G^3uW$i)#dQ#9Dub-05vQ zJGqt60L7?%T&l(YU0KjxKyurh`RQ=EVZ0)<-_DgN*7YIX;H}c@nS+Ldo<%9zQ`M%I z`q^MC1u5z?a_z(pp+%21S2a{+sjqLBCKo7DS_Dz#Yid-kk&P43Q`gefi6 zIt67QK0QWnOmIJisk+28RoR@SkqNm6CMIM21VNoF=?@X z4=u>WP^HD7h<N;os7p>larSrk3~aVOy>q|K$l@V;6d$*us$-g%-)Rs4wqXFndVf zl8UT{!j)B#>ewVT(h&wuydI(6bc32cbg-S>aB@l8BxXdlAGKqXccr ze*1svoq~_(Vs@}(0jo^S2w=Pj5SDqL5l$2Xkusi+f=Y#xl0|U!Qr&d;I=iUg;urkV0{6|8Li`v(gf_I zEU=V`3bz7T46oc0OnjzvSvXkL25OaB2MRm%zP%8)PDq8Mc`L%Dy}XaopNibsLzeW1 zFIG&J2Wx%_`*4u<2Ujnz?3>5*5}lK3I@608^%5GzBVbBmTMa@4c_cf`oMtsehA~g) zy$u|chv^l;>5WQAk3{tp1#Qkp$?|<^X$gnZ5dsee#B=<_RPt#@-CodwAQLtvdWfpzeL|yzm z`na!>MoM}O4##w9xJ*)&cA}k-TnO|4lq*@Ye@aAZ%Uabd)IHR+)r+W9PPzaES?HT?&*L$7$tK1SvpJayDRW?e5yG!CSJhT+U5X^ z;^hIiZZ%*oZkpvYu|}>zz}iOEfXz_YGs;z^;T3m%1zgdUQs|30nu(M$sBN*={1H%p zQ(#jv5fwPyEs@Lo1^hEP4!DYvR-w2vk(aCYbXQ3nOAWs3XvR{biOE`LabJQ-5?+;9 z=jucv`T%B~sU|VQSCNza8NnG2lztZuEnw+ljb8ANq<*AK646^}eMF82y9wlOYh$*!1M`Zj8)V~@+Iojx9q)}|Y^($u(#YGOoU(cmue-KOpw1;& z+8JS0y@G90wWz|rgkwmyTF<=?;-m_z5=%4PQ%BN6TBS0JJ;Z~M&AL$|3aF}JP=wSR z+tpNmig+m4AqYg8xE1x2Z|67SzdE51oAmj z;3WU@ufQyo+OE5xIJwGI7Y2je^TEo*7f8%w~Z*^_Y_U%i{Yt-=_WHJxq zeebcH3;Qv4=i0L8?yNUXH%VX$;b{~@aM<> z|G!}Sc73q2mquU#p#{Hs=u0gB2((UGMMYo=QHyZ=53$NQ{xTvv64PO0A2wc~HYg)Y z^lGS(Vh_2%+C(+r#H=LU=s&9$eIR(LFNK}? z!nMp7mNdK`Lm*M`4oyn`LmGr^Zq~=M%ZkSc_hiY`W~mxw(4W8=Nv?|VzBJgk!9@L~ z6p=f4Dm0+#x#q$R0Tp6JgtPRqCm~C=^w`wclp(JcILTtDSSFV44Y(#~D{a>WW60xC zC0G=KWiIXMfV?W%X?8(REU%357hudx)x>FG%Z9(-4F43yH`gi8@7>#4SfvF3OCt~O zO^9Y5>!$LS(7jp*$n_2tEGhea&rM0jnz`f2{M%X5G!N@4rBg1&JGy>B<)Ql70siGZ zE6Hzt4chSX8QdKzPDu<#`0I@jCtOwFEVUnEjgKM4iOXG)+4C|2&-|XgkIPh8n+UzB z&IOy4inui_AyF;c>F5C2%c2`DpZE^}26<`H6+~oH@t)`TB+olQjTnF&a)dIjU?#GC z^aVg|sgBP&_KALeFgayzz5@KHVqg`6vd2|MSDP_l{UN(C#Ji7y_p*s3cO6EN6g=Kv z!99Y@qH#*@C26K_hoGvr9G~jnTk|*(E#xib8XnBk-{*47kuF+OfsFJQf$Kxhi=#`S3YiX#gVyBH7 z^uU)aR~DNmISs1sP&rzy3HWJrAHz$5k=SkDOB7h%apjnsJn)uDb9?MKVjO;p;*(Np ze~ut3+Gx6%SjRJ5vj>YsnSSUL%hRKVy>fmfE z7STnC!cLgFMVUQuy(`G-B|1rpRKFg9ks$@_`swGH2?EmFGBrm=-7IIOsyd(yFE+x9OO(V zTR0F$52zv~@{ot>v*h--ZHEX zE25jCxifWDfVjx27uWuh^l~3bE{u@4Hf-fL0=I{D(C5q_$Fr_*ylh3JLlpMBS~CyF zD^x-X&}4hTq_p3~GNh=zR1>Tl5#=9w)>%$9OxXewgacll76MG{DyBM??*tb(k2+{H zpD!27l`8|j&{x1hedYgKykP%@8tB>5%ml@bu@0YNo)i%gX|%>wPdBdM!x@{)v@Y;a zd1h%*vDOyQdwE}bjSVFGtYl_Ilnv0Il+eR3hSnSScH-zeGw5~fVD)>ow>W$+Rvm;(h*VaD`cXMk+mq$l5mk15YE zg23S&T0g%{(!UOU`gc-L$|B^3XWNK^n162pA46>_(-?(mJ&Y;rdoEI06(B6H-C$N! zpt{cHo(x+Xay$vel)?G#RHMUoqf{ZH(41B^EvekS`@jGS{5fP|{Hs{mJ|JjwrZiz% z-^?min`{MHSXn#NrqF?X57vn3fAB#sy6B(EY*Lj*>Q(9l>8=}Ci7v@3OQrL8O0hYz6!W-9{qS%o&D&?yflOp>C z|7?zpbCj%c)%;S^_x(&^(;oT?FGa}a?dSsuBlYQ7m{_#?$g#+K+Et92a+3Z=Of6RzHXDmoPB*>0)_B1BgcM3z7Boe;t*%N@Et zwLFxvx)_<^;ei5BEz%z%C%uZsNQm+V1)vS1?4n@2XPVBh3kCNorGzJUm^V^}&Q5XC zXBj?_ExwG0sxvElr)jCB1$iY_C9B9iFRiU4{>dkxI@z*2o+}M~|A8`}s)>?Rv@4gB zb5M?c&a(GhOn*0W5njY9E_aw(fouqwfMytSsl`h)m}YxoemDB!&=_^{g#EQnGfX_! zuqc(a<=xAB+XQ0Fy4vKN75-UNAE@_;{=`ct!`|xQbikneYI!9wgepcc6TOO2Eyq3h zC7$+_ys^wjh>X8I7zyJ*_DX754M$i+N_}nNG~yF@QC~EZ0N3i8Vw$4*vtj!w!u#V? za!M^_8_+>YRMN=YT#x!}t`9$p?cTwnMB}#p1QK|(r+$-=2 zYE9g*EDRfvbw4D~j)?u32>RRaUv|b-Z@QYr))1t$yOeWVdF$iaOijO(RDCE-rM=Y3 zxlLjh>0WZ1RTCPIs@9CIdqr#V84uUYU@f)xsV*(9Dfzy#yqB(5L?v!vtkTHhA4GWUB?dMT9Sy!$V1^MDH4xAkMB+I zU<2Iv3>H#Sa3;TQPlSS$?s+Tmc|_}U{!b9J9{@Fg)q6Ukt9yy++1t|kQiMKqitLkC8O~~ zJlkqxFRf&ndub$7+P$`TeX97hsNcwNl*`f1=cZYsB$Yc!VVxtF#(GKx{jfM|mG>C6 z7odPNv0u5}oWS-VKy%F$`<{NQbT=C?STG8>*daxETtA0dZTuZm&b>V6vb;%%aGMnT zGi-#0xXWUSdtly0S(m=`!YMyH1td{zm%(=tWQqOV-(^g-=!}sEI|JKAG3S2}K*>>x zr0ysolJb}}`p$?K$mJ%P$F3f6)adOQI$K>PytPIe;-phq9-K(3rg}kTBl6>!W`-b~ z11sbq1OuN2TFdS`IFloc!h=%m9|WEqWN#~RkHeQS!=rg~@tyBKa{H^_Fdi>kaQh+CMMYVo;ccN}Ie>5!W zgn551(6;p~z$Yc(X=kmPU_`xM-o`?&emz8AU$Cf=d;rlX@`=~*SlnDFuO!Am%}eY} z{bm|zb)uK=WvBudk4f(%*r9ZJ@}=#z36MiuJ5izPLFA(5T?F-1tHkahYT4`T_!kRa z?&r1dTlr}U8GwIgx+_H2JgBYtJqo@cw~aDVu`!tir4p>7Ri(;DvoclLf^R_l=(v?E z4!h8#kp7+TQU0s8M~Y2r%c9%oTKazNCFpthwx5qD2IqiJ-V`V%q{@dr^nRgf^L21c zzc+1nMr8Xiw`_3>wO8q%ItR?Yv%&KHqkB20BTo0a+cMK z9#12Zd3X0nrTlW~UWP)YFt>8Ob~6gwd_fV-LUc*2aU&`*&n!l#AO)Sau~fy-TBtPf zh!_3&NHP2?P}7)=GK2?_MJ`7Ym0Zh#`MvR7>3URXw2O0Z46)M|MO~aDR7>1&Rn$q6 zF=Di$=gSz#2$<-r?*m=bEk#kB9!dJck<27T&YC9Up2~41(L+-}8@4yFeQ<0>r4?pF zZT8CQ2jp7iyS+5B_sK8JSSyQ;nHQ(Sc*r`k)ydt0Y=j?9!awE`HN1JTvqye!pqb!I zhKs@%d?jqr1p8@6uPF2w)LrUKfL9@K`2{pYvLo4n1_7$$M|$L129!sARK^BKCwZ0? zS4zVFQ8|HZHlk)1Q4}jYL7+PvS}~v}_<9VF~{#A)~ zFbp}WVo~i`-2IB~4grD6^(ppY;7DgUBg>N+2B_F|K++mt=Es4RVf$UoFeNcOkMA2E z=XG7q%YGCYWh047dz93XP^N??-%+&3zkC{80P{$k%w{52iZH3Uls7s%(4;YZh&9e2 zS%$*|QY;MLV;%S&5rcfppNETL2>P<7=W$QkL z&sOqY*D2j?qkm+4tu~^`v_73QM^>mG+iCS*DDuwZX0CbU>R}#2@!=)Y7=%~0UcB|Ror!c%XQM(Sq zR^u(AUZ%9$Z3mYy1=&{r*i5U8V6H$0Q-V^4A7<66v~pTqP;zYV{2@`qbNU?b&#Hr> z=dz=Dn$s~5iIn)xW@qboqqFT?Baoxy9!B{y%8-^`u}epnLDz-mRnh0^K`69KE`oK$SUI4lBnj3%eZSvUrjL-$vgAc*|sLm{1x1`?x`qXE%7%D@n zHW&9Vj4vNh>p>xzV*n6@W7$W>_C_t`cK9YsRVt38U%wnJx^A1hxcDE%7DWq9L+-L+ z_^VFOM%H5(TS?z=|D3uYMd@U$EK@U2ql(J@=3N!qGTfw2tDcbZH zF9WPo%K}9B*U4vZ+>$LLR9jS1{+y?Q>^irp>%38KqqPcd5wUqtBr zQaMt1J@mrVC_kW?^Kb87M8BaQ)P+~LZ-VIzw0P4mhBmN@AnHy`f%$%*x02xEEs77J zPuJzCBg6VT3@b>Nn{v$%G3_u!hk z*(s&8E%Hj!{P_J4__iO-JCJPry7SGC+Ay2AYUrNXuNpSfo$$I;Qa^!};|DI;Hu%tt zbVdRvB!r7)EvM3sIwH_t+aXqb4-)_j_thOc{9^tYw$m+PmTf12(;aG;z(n)4cZ=Nw z^GckS#a0b>r0x|#JLV;~=SDr}^kXWL2xyh-a#_0Hv1Q2fr9@)I49B7fuHL`AN+p_@ z_?U12dP*zO*e`+I@H3;Oq8DFXLqievnuaP)_Z1{D!*v^n+VrXOcxPYuI)p-nn>mo^ zrRKf6=l>uSBzE`jCNh1)p~(e>rI*+|&Iu^m6tR3@Z6Z2VzfJU5-{&~B=+ct@otG&K zgUT2jP?@npk<{?8MK!fYiE_DV?x__>auq=+sN|73es$CF%(vI>;QzNIwph`6i32Ea z)f9S`E4^!he zk675lUpp5cSXy1YW@%y1=0(-=Dp~S}#?hTdGfE)~1{bAF8ID!!#3`uz)EmQ~SFP&O zu7QDGbqa@Ldq%EWUgr4UB}0+F^@6#OUy(*Nu@zELYgvcbx$SspER7g!5^o(*hCj3H ztew?2^CYPa20y+yzjyf>9j3ELp2IhV7kWf5Y$27;^5^_AxpT4E$idDO3R@s|VStr= z3@Stv^w$`MVs{EtE`sX)U30=;c_LIuz*O+hi)Qao0i`HF(8#|Jw2^P>YDEvBOHq-m zxZ<3>Qm+7@YP#L4{)%CBBa$L!AW6V3bnCei>@?n((cx~I(i>2Q(br1-bp##Uul-M< zMgQg8(x{p^?{#&aN3;j~oqv5>#34?^wP)d3$JnHMv%y~frV!0D7va7gnY!~e{M1yP zgv%XE7r;^b?_yg|!J^9&Yk~U0gRGFQpNqHAAHLk?m3x6=6+Pgdp8mtI=aff8trT^$ zmb{-Lr^8bf{>bkq-xWX)AlFPIod41!EyfW}mizHrnBH^)n5$hHC{pcN)QbF*kYmwr zDOnA^#N26@R^qNix#7Dgr3&)_Ucst~SF}GQVL?AFE%-Tzv_YWJ&!}!fNHb!QLx85x zoVM^mQCL=a*lZ7<(ejHKDWi$(Ulvi3oV+LzVzSmwu|zp_S6vv$M5M4VYr$T1O1fAl zBanzASo$><(!n~l2+%kbsLQQnPr`-TUucSjCCE2oN%XJAh>^^XdOVZq07@3e(uJH_ zZ7EU(i_n@`yhs?~UwCXPkh%u*EYkqrR~F>bAohgp+bI~N0STube#E!-q1j5Y6>Q+SrrP!@X?5$oP zwwL7=(>#aIDI&Z;Bl$J$D^k~zIuwoJDOf%n zugiS>fvt4Ux*7byuIAo9;?3R?;(a8kL8@k`D&SrbumnxBPQOfmShnWW9hZfG*7#||Zt{j+>#wo&7Z*NDTT zRgwNU#o<|u))gaWV`W~8X;%$f&A2kO5y0{po?)R1u^MgHCHGJFy$yEEXfPXOevKK< z;`4o-K&Q0ISXar$3B4e`I-y6iBa86U_@<8D%aUiQyhzf$icMF3EUOWTUKU=b=c9~; zK{7Dz0$9oI`yp4M@Lvn{bu1x|if}~O>8~Z#I2)+QH6Ln6CC+$^UYdKG66}N+R88^} z#+iqjBL*C`XOVo*54Y17--q>mUQZ{q~`^YdV!m?#LU4-tK+Go3BiRCuy2cD@s{B<&NK6Tjc^DEhWBy+U_ z5m>OSFB2<^j6@z|cj3K8_OLB+4@5}pmeLeig27hziRys7uD5yHFzDbLV!%kEexed9 zt)MC8V}?YJeHyj%e0gTeVlt1?uU3{0s1zH9^c!><6)RD2Go(AdN^oO+$UKRxA_9-p zC(_#XcfJy9ycq=b^ZPH(C))8b#L>xJVLB0Z>IC=IO! zmBlQytj$>rb=?ZdI=x2G)Ml~!vzJAUVb=GBiSX=eWv+*wl%iQRH39`LT-mVGSTpb4 zYX;RG_0&uv-=r>PnMIps#XG7Xn%0|4pa@S#g4@Wz_I6zhtl;MwKcmMaE$+g|JJbb^ z8PQ?BHr=T$mNr<7m%ZjY_ze}1(s7DjTHh8DSTk^7I4bga522%r>jpYSK3)9NTe@Fh z75ietKy6J@TQ-SLxJ8%9bsoSb+p$5lPVR&mYS>@ABkG;QQK+aSfvYt>Q-a(l$qxxZ zR%|$r^3X#^3jTIl$Rzbv;ZfBTqvJigPhO^yiM{}l0_Z~W zKY?}DimpO-K8LfUQP(0+c+I0VaGQyzqhL~8bD$vFx#1Vc9ZVRCwz#RsU^`P{f~S+3 zPdq|`Zi>3|vUd1qk4Vrb%yY@_h^h<$I@-mHkyQZDx49-G0C@!Ir@=5mcrulEXI8^# z?#TkU@SFerSLjnT5Jhw2W`U>XVxIOdieFBK6;VRM4P%unD7x8lLGE|Sg!E23aj5NE zr!ZYhucHDqG>jtIRHYOwu3`U-rF0wgyc|=L9rv&^7!Xk)j>05rzssUlXujoWfRxZ* zz~W)(G)SMuBEYhb*{IAui*M+opTQ=0lcQv4oM5d*;@-%2pBqdRE0Z>yHm4b7QPtm} zm-lqA&n@EQ>nN36Yf9Ap%1e1vMgvk$tN4pT5nq3%Q9e(_37?iqp_Pa7eA*{oSz6@7cn>q$Xroz1- zm7xXyvr|mY0%!@2QXnSQTa*uF{&p$jAqpaPq=?_}Xvh}`QeDEAHt?^KKP5Ly=0*~{ zPJngeYZqyu`VwGK1=E6=SizF}_LnT0#{uM19Ypf~`&dxxt1VSXovY2YhFPasd^G*@ zXq4YnRNjhr&SdHOyZj33+#3iy!QqK<)RxiDK}9OE#=Rw{NB++}8DGe4F&d#b+YH0< z8N;ReF3bBCSRoBuo$LGZh64FlKXhbdg>xI$7P1hO55SXUzgdio3F@z`5*WHtioCd+ zTI5u8dR=f@Qm8fngdvCa0T7F59R`;{kmj6V9bpzI!r>^zMzQ+gCs`CU4tOk!c1B;0 zKMJgx+I(T`#F|fmj$Dz9fZjB}XTjhDfg4JwH9v*adZ*|SQ`H@Z(tL&?Oblx}sKOL}vB`(PP6_ePWKMk`d-pc|*-@#zA|Ly)34XHL=QK2_Ki} zB%QxLA+1SgZ6dW)m$I@NS9F<9xLnF_KT;X>EqM@4v6f=TN|Wv;Xb}uJUS$__0wl^F z@4$@rC#+-FLLcKP;rCJnxzKu^+VE!PaeSjPG;%}xWH_AZyRm|f)yz%1BWRqp9JTWe zY%O!NTU0z%B@(I!MhI*Wd`CFNNoTPnnz#B2kxoUkjeA_mp7WA*33RcKj`*mrJL5yh z;Bcna+m?t}%2c!BSZm@B5kvn3v*OoLUK#aR&*FutwuAb2g$1T)CGFA<9i-uy(Q{CO zN%T_@Tb5nx8aKP?ky9=LT-v!f*fqmOLj3$zn4rhch5fNMjckhHE+?CC0=S|L{=DzNCmJ~+0}T#mJYW5#ErOJJ(2kZ#w`3hrc^E5s;QqI|PP)HUeoJ*M=HpM25QtK}Mr3oYMO3@t7~w|!hV|YDhaxEMj8 z(TM{Ep^8$T-6#;^7COmTFN&5sLLvXm#HEXhSR*xF-OoC^Ju_YueVuH}e++nXSEJhE zRj~{9lpf!*Agb2RBZ4wEzso{))jU(U;$%HbhJ+e?S`DLuR>We z7piU`9%dtdFa1)7ab;xCqMhW6D&Oj_T$ASer&D+nJ!{*|<-Lo$50z=>a}{T=s@t@M zSrzrgoaT9vJp2JFO+^Pmtn1s;a-OGizLoXLCRvXBDtQ7xUH=A;qwpy*w$qy|lZAuCXf1$Vz;oGx-|E zUnT1pe^KJ!rAb2P5giD-)HbIGsci_<#3^chR_pJ57SW6{?*y9W2M^c-{M_kQyIy?> z=q+edY%I>BHQIN183HQ3-VsKX2083Rt!`ewo7W~oHMQ^}m|fjNh+3fW8ss~cO%Yfk z^4{3+xm=Z|kkzehY(|A+aN?STOJvdpI(?ASRXf2 z^!-?UZV;mMHTF`S`7wH`mnkE^!=`v`nC|a#ISq+jcT%CTsK|d8zDYM}cYc?Hi;8v! z!OY(&x;s&5pwk$#9eQE@!DfO@cj8r;<(O)AtVJ>C1Tqr36X&BuVC#hZ^}I_ z7&Hc$01_csP8#uOm0?y^QXp=ZG4~NBLbeg2??_t#uZvj6TWMmW^}3_DAoO0^_?3kb z#R$iR6RjYa>hQwYh}ks(Dv+aXnkDYZJ*b|i`o-zbaA7ufEbGn^)G0WRdC`q%d7U(V z&*{bfOU0H@Ak13GB@$0cxNCryV)%-=Y#UTJTW+_D-Ig7B)>NgJ3Tn!9m5#lHHS7!6 zogcBTXF!3p>kL4NliR~AiU@uZBVEy^?T73ySf->y`W>o zphKKwQSkPt5qcAe+MLg4RIQ^J4re+;TQBrZcGs_e{7_Lyo3Yc0*5MR=1G%D-rV)p^ z97L2;;9v|jQE_|eEdT%Cenaux80nsj3*3w;9Mn#B{Ee6978V{d^m_VA0myqy(e0l5 zhzZtO{t(MpJh8?0@XLs>n!gwKmi%sxRR>p65_CAc(%q>;%rX;lxnih3Xiqw}zde(A zcQ=dNUD~_K;g72z1$iO9{*zNgB!GBt%e0FOKks6F<&*M6QomjiM~yI5=HF)(6ocy9 zQ|aWOGPG0bduLpG7hte)txhF!-0_DjKr=1RkS;3{n^(Xiob6ONh(^@@1o2VMe;{j` zTH2+khNR&Kdg(-6SL$UTW^x8%m&ymxU1Zb^wI~{T%v_skPg?@n#y5gZBRrDfTODXsc&2L)>= zAyj4AdZ8L!7D^T^wc#=eRdL6qm|ZoNj;chFrT9-rZn889o=HWVOGspj8Z!FRWjPG} zHqVUopSxNy>2SfUpums;Fs!;lqTZB=8rpbadaEUpeFh z?Jay_vtAXb;DHV$Oq@L6yXaIx3Hp5@f4zf#E_lECb_-x32QD*wgui2J)}7@|+hg$~gv;VMNuO;w(kZ@7r)f-fKX<(D=fh z1G^ViF23**E1Cb0Tn7;7)%wUZ$0x;sFJYwz+q?ud|D7M>=}@tr+>WT^dV$_KSYauV z)Mn){+W|41S1y)!nTPIyLCC>+5k^V3-C0&~2wazK-6?dPDa`)H>V>?Jtl0Y%GIv@6 zC6=0n=^#E!o!IJjEQLUF7O@QUvc5H zgXQs6E$&nJT`?rUR)2xDZ$Qst+XUqwH12@PgB9{K; zuxsEpJ7=dO6rqbd!-08gAmEJXNp>_Heq zZ(u@COgAx@0VvIC${ju)2#T&EA5yg_zml(JhiJ`aEx)85p1N5#R52&*uOqpg8&nQe z=#+g3G>MV_bEK`XtV%D0Dxg^%-x|Co637xJJ?w@~60R4LVIbl;sGj{&?~~1)mtN(p ziK{&jifZWGduFJzTrK5OLJgBo$_h2b<$uNm*}qQ-A2IYvF|tE3&p4SCrW!H)MDP7R^uVl?xPOcIySEyrM6#J>@gjUgw~p=7r8Ur8B8?H_!TOr zJf@B7IvJe`oWMihyGSfCPwt%a?bohd-lH{MHE(>FfK660n|b&mp=z<3Lnrvy%18A?;nnp|%pGZ>(PFSY_ zB80fn*+-7IfDFSIkJ=bXzxG=zfDy_|TKC^^K~fPtL1MQY2xw>8yu{ed>O*id-h-wqQ-7)Zjw|tB4V`6|hPrRnl@6@q(HY=R6z<2%4ftDdG&I zBULApLad{#hUvYktyH@wvcE6}TACmeiO`J+PuTr1+yc_BS^8wN`SQTF2nq4=Kso?u zp{bPA$A2R+g=02Nlg9%Tx(=>qSc=m<^opAyCGVzJ4TQ9o7SKEmxWHKGpy6R+JhxfWe`@b|ujYcP1 z6~b&=y)Ne<$GoEK?MJdv24s&ySES^0>I}oWQkQpNp-b197?e6?_1Rq}Ez|R3Q>j?X zHBcERZvKb&fst|yeiQNl@lY=F1HH(l*s!kBpL}~4y{en7! z=UVQ$F*moFm1Dr(ns}LZ=f$itOsVBD(VoL^ACe56X+(uU4JrhbO2qALm68J;#>BBxwP44xV_XN$F3`qSl@PZVEJ+VuH zZY%}X1x#t%T!;I((rXMF$nRTxNjfz<|29f1{=ujyE0VYK`Vw@CED8;o{S__W~QU>=<7ZMl-zgmHZXsb;b07&=p}fvl^tnh?EHMVyv)Z8{N*i$|+6LFUJJ< zNhk%V#*p#@;T|n&W=X5$+?jC?aU-2)&oXJ*3VPI2j(`;UBJF?Es9%O2Z59Ga7HeTQ zx{^9PVgzxJcPBwr{q>|;-09cPK?Wqm2KT}cx;VD;Js3SxDruEDY5%Kg^1cIAK%5TqVtx9_j6eY7|qSK z!iBgQ39U|p-j{c`_aE}|ydI{d=Bqx#a;dmzKcga;;g-~9fWbaV5p}y87EbDD00>A3 zcWW^vg#DYI#bsY-I{o`#N%u=KRJ5*gLlH8hK*58)*nw@tFIlPF>N)6|^{fgjV?MbN z|BWe(QNmVf%~i)cQ*C#@=!zh{9BKqj*Kak{K-C&Xq3=Rxm_QZ(#Q>yq@)WgZybR6Q z@Y7LYvwNm!>BFXek;P%g#rjOG*WRQnP%Bw?SLkEKIaC`xjuEjIsLil&;Xg2lV`Yr+L1$Sg`7WL0|`A0r@70z{Vm z%TTI7Cxg461x_s*xCkdp(W4F5X?mqV{tgLo`aX6cAdCH7JQw+G-i4NxqVaF1XA@Nj z)z6}7W7aRQsc?Kdb0IvR3qfjJgdspN;Wh(dY_K~vxP9%ygmRz20>sD%`k)voyaH<3 z6BY02pv>gs2+*lcxjUp#S+c&z^ib$itqe@Nd$5>UL^H1hl7w4>r2f!W6th?}*Yx<9 zUe}iIHjNZJiHO_q6IT_yze-OIX@HkvF;*d6p7B1jEFI)vgn9+J6=#Z7N`h1Qlvw2v zo)YU56>AYPi%qMlnCYuLBo!bQI#Da17o3EIJY3@|v;&l(R%K&h4MK?pRtORL5@KkI zI0+G_7h41b?i*48>&!(j6l5KAp%lnK`+R}^VI^oWshc!-C+B;aPD%dvVF}bbZ3A&^ zDPeB!7*S;2g_DM8!^$S5W1=WgYuL*g3A_yYKp|1jl4mre!>GQ+$vX^9F(DPh4+e^5 z_XPd=aG#j+o0yio(O*Po6mq~mq7B5+{|mNAG8`LY6JC==*okD(-cAvC92$jN-iv|= z0?E)bkgTVk=z4{g8UF7ttdqvN>%G!(&Df)8nW*D7Q;|uyaZ3P*flfjG;sz~ng4uTI zi_)Y~RpeFwh!kiaXI@}gypgq~OD!F*3(+cZZ0Tsdq}MB5RM~09FBZQkIT+sb$4*p4 ztPnxaLa*<gY&?*5dvG`&uatVB#7v>$`NbuI zSX%P;L&-vmYw_>H;orVsM$r75%p_2P%oM^!heC<{oM+#jv6pTbZ5yb_?=cWd#Xym- zd>Jw-<{2oti#==K#epCsFwjX@!}(W~-Bhks6dv_b&+^$6q2zo0ZQErOFV%H|1v5<8$GGJXH5ltklv)v@ z8kIRVrc~q_tt}l-y@La&3T>O;-@@94`K!8hui8;-+;0QtVG|1-N5mXAvnXwP!K(&^ z7YOhCJJC=yai3(^dY((9T9uwX@6a0K=TM$=MH*O&zB9R`m!B;hoE7tkB&e&X;7;il zEzs}qUcHko4sw~3gC(>0N5nEfSkB>@B_0aNB;WX#bJD)@P6cxeL_b5Ys<<0AveoVO z42VkKc6?yi>>^yd-OCiV$~(z|6Le%)Ue!h09C_+iYV6YI#3`sQb31 zp=i(o_Nlrz(QOrGoga1p}VZbuFD@280zrVLaqq8D}}uVKP)GFQ~- zkp0PG??=G3{URoEeF++Cby7US?5$9+N!#d5!5K2dX7AHA59QP-5oYp_ful@%idx*h#ylTYZ0mIxkFGzO z+Myy*^KTKn;oDnetZ(LScH~=_hi9S)32_%po5pjV)dvw)IE@{!ZJMqlPn!AP%LErJ zhtzK9`(ERK5e>2Jhmu5#>~k2BK#&gI%7XsdPRe>^tLlqi`5txDcsOke{f&)eVq!nl zEuaOL;rI4Q&;TJ0%>8`Cx5m*QaRRg$Dz*sxQsNo=u(ZdC%Ba?kaJf@GkB%)-HIJD1 z;TTo%lyVT(gd)H%V$9RiRd#3-!w};624t0q8#q!nRLD99IZF0^%zK6hQLWMy6zIhS zWDp-|ho`UTP46(`z7Y z29pNcxVcp*qZctAHDcREgEhA2N(?_0(o&w`1YmNg5Ka8>TCV0{`hfGNT5Hjfr;gQ# z6nvzPv>HlbB6Gb8WwE<959E|Ds#BM#ipFv)heWDPiWg!^3Wh{`TEJSx&|_!<(fLI( z@d0ar*jX22FMn2KNFE@iY*Q>z!l4r`L_8gDA<_B*#x)A(I0NaL1^1v>HM^;uSYIzO ziO4%-NPC{R!HrrFoz1-$vvh?xiPI+>pB85zJ6VzKq|`AuI^^j8;fy0K*bfk(;-=XM zR{sqP77m*8v|=8i5!R##NMa?`2qQcj(>3F~`ym=yLwhfUXu25YTaEb20lymOtUJ+G*4CT8=$gr1!R8p} z0!$F!v823|h*)t{7x?P87(g^7w)G~dA!qMaBRtk2kIC?YWll15w7dyOCt%=<+OnVx zxyiM^_5HYF=iWw;sF`>NbR_zuV+vDbPkiwKRjZ>qRgxXe6Hq={x-oz#X-PV}&6S!) zC7K&3rX-+Ue~DBK zo_(i&Ywu({v1-||SaefSlYeColTV}wKvP_kr8_}`eS49^YJSR-aK0vs5(L}LjV$qo z-N%Dw3T0yawKb;JR~N7An^hj@X?1qqrFHe%<(0Lw*Z)90vM$-aUA0x+)W`K>P%ybN zt|@c&R!zdQN9t{O*1Njhv5=|NohS2g$Q8(rkQhoOJOzFFHD3HI z#}}@ids$JEaGROv?n z5sG$QPTYhpQ1U@px%5J2*u_UL*3iOvqZ`21b2HwPdV7)Xk<_PlS9$Ab=dSs@53rHf zhY-aOv+UyGE^I-*EqdI)P`@s4rc{)UFeq$&!ER~{cU4|!egA0tQ17GQq+czV6K`ae zg~LbPoyMtsfloDJ?K?&)`brG%CK~As9n3{67=vIMzl#wCQ7X|*t9K}l2K)l1hA+DA z&~wq|Zs;+q=&Bo+nnSLIL;CVqZmf(5Sxd{t3_GPDiUj%&Qn7}9AE*v^M3O%{Oi0G@ z&QOHFMsh49kYhluUMN)|to}@ozWaVKL*c~0)G$$t|9mZ9(maw!>TUvAssE^QU5cz^ zEdNAzR8C2%MGW>jrlWu7Z4Uk# zN;5#lmXA+_cr0d65Md2DQW#S-8Z6l?z+Nmt<^W z_W#LO#fZ!GsZ! zmRoU-+#x3v1;S#R8g6@PK;TtYd(J-=Q~I&8+6*OypNEHWwjwa768#G_!{H8O>~m9) z!?xg-dueF$tMk4M(Z1z;=vpoTcB15W8iTCRTl>%|v7YwA!gZ{@R^&abB!^#@#%HTE z-;oadBLK&mvE14lZ$-?6_XCx|?7GI_j zWeF1Bg9w`zp7@0dEy7W=`>a@Ikb8YFQ&9FSoNs+dbu?j?0#FzX%9ZKcs(3*J?(oz` z4(=v&&%E`Lz3{xt)!G#blPPOVg#v!b%UlO7xRHm}f|X=*h0h4zruK2T9p`&y#|dqWFGh5q&cS z@qLnLo5PnoMli#CiV!4K2ZY{FHIT7mLhWRO(2gJ})|8e(objh{#?BhfBK9@D{&+O< zp2h2-pvJ%bo?eDFZCK{7S*Xn);#dbW!b@UeNzBbtkM@^}2SfYG_rRoGDx=|b9abXl zJLD((jy#u+9Biu+L7#Uctp#u-ntTb34RdfvojqnPyLL+f)AX7S2uXkjHz%Djp_rfA z_zoVW6{!K!^b z?Qrn={663qnUDt}l#keHtQSG%KHiZm>ZtMBkh8`$^;JzhzOC)n%FHAd6S~m`RV(5R z_NVF%kKzr5(#`N^4YUO)y39a|5G6hqZ7}Yk!DOJC_tx`tP{{=osx7+h(xUTu_xoYl z0t$~RBn(ow+E8;J^*jh*( zUY}?Dwc$@Pc^qJjR1ST#7{@Nc`M!E7TC>=+CA7F)h0Ifs&8YyAdKDd^A~8xT--)oN zkRH<42lgUrpjKBD^5~#3DRSFs$-pT9c&Ey&6AM+M8fzuPgRSwp_Yv4E%xc=!L;Xi@ z>las{qKbY?$oN-S4@Il9Ju!kcGrT2dx%R^D1F9D0Kv9(IiWTm+&_jJK-gN$#bY{vS zlxvMPZF(l@c53bVBWB1R;W)}Avg$vEPa_$mT?w*XB}WnuV%+7fDKXLJZ0fG|3W?3q zE^$MJdmODn(^$XWn5!FTe}mUerGS7!4%gj_$zrsmVnxs{aWFCG9T*8BA#-MNd7uau zG2H?m)B6I?-6g?io1!Wk8*uH1dh~+Jr(&`rn;Vu_8t4JMC1nq#P!*63yP*=NUXva> z@W+5P)%!ZbruzMdia9jThcXM@cqH=QUk*%fGfB?V#XRbHmV&=|IY((k!1PJt|AC~O z-)j^hYjIzO+|HZGN@kSfSBjs^v5o+Zedh1wI~Xy9rb=IdUj79)2#Zbb%F20Ype-V5 zqjGeGz1AJVEU`NNo|)^c(V3ZULNmKs)Eda;fQl^NN?=(F|1ji*G7Cp+M7YzLUQbUZ zXHH_W0zxUCwiy8BHTX**y@Lo+wE8qm8_E7HTQ9UAygx9dU}M(mNxY%%c|waxr+K9& zPz-~vR2q+da$neqFDu+6n{RSne=I-AO7y!~hvg9|?bF1sgZll}m9}R6Nyb~AMm^|Y zX!C%+eILE7(Bo7(_r=&O#*`00@zf zDQ?b8+jSKx5P$a2Q9#*~cwbhCv%GH~TW##(_jXB9IUuol4iaLSkq-=#vEI3x1_P;v zZi*4(pMi>H10<_1T6opZhJ~}OcEq_Vg<_Fm#d@LNwbhyBO1d{jz8m51t5}XcdB29o zhZRV=VniHj1O5v=6^Q0Gx>{<7J7~CF$*u{|marqP=n0 zU&{D97_UA|9ew?i5|Ge+_+I*q=F@ZeL(exigOJ3Xv$b8_V*WR{SBI>iim_&eQ&z;4 zKand@%o0gt{|EXiJJxPYbhP`#I#=fOy_ii(J5w!UMCr9Ymq17%n#P13OhwQ5d10Sn zFx*cWWS`d;jQ}_9+i9)--#t!i?kKk}#CLG=-=I7i$Nq>%hV8VEQZa|ZQ+GUfXOGIS{N{cj7TkH8>-^R%_b< zeeH?Nh?0$JRKyM0IAHyS9I%-Z*9QP%d#ipA^vw(K7Kruxuw%eX^jW2@6GQyl|3p_R zP-?yrv0o>cu_4;`WAcO40Bqo?6myDKi3QgO#AAQQzj(@Y*GDuaX+@KoLMWZ4gPqdy zr}L3TjWKos!T=R3=le+WQyfY7H9UVU#|Ie>T3770 z%C_qp#htygTe{Kb@ym?V`c)yD)I~voIy*u3TE3tsHoyX6%9ACA<@q(DM;yzm?jW|b zZ(&G?m-YhpH=4anRb4}rQ`GRCVaoL@m0)HG4&X?o+^LX5D)Kn&cD1syvb!mwn^~@%G!lULS z>Fjqhr(y&<^5|y(MYK+OW$~qoz;TVI@_> zQlHfdrCNbff6EfFl#utIiKQZN-KkN#VW*So04#>S^BKJX?vi}z!~)_$lH6N#IfY_1 zn__JCf$~wnZ(a0%N(05jy&Rk=-tB9>(4E z&yKRj^)bC+NNKi$4?4DYd@9jUpL;9()|a%RN4q{(2IDJ3L#lAk@VGohr=&Cof?5!|0bq2&&)ge4x74w=? zuH(@xF_A?s1gul|M`ZavZi`FtXnaA)Tj=6L{A#L2C7bQ(J`@oJQOj!*_o?}%J)BJ< z)bT-$nmmacLG=Y-G{^&J@abuI$$D8ylHX-E5glwdw;416$jK@6dKPLplGiC|A6Jl$ z2$}q&K9skFwxpx62xPG$O|RqocmhbqnUtmSRD|ts(vQNY7UWg05!o@nev-}aW0%>_ z{p$<$%U;Y?;=6=%Q7mH$C-=xe8IlgZVRg%~|kvGzr1SOMx@`a1ex zeM+()#?55uJ|vX>ySS;zZw29QqpU0m}V)Z>MX zg#hKu%v#@Q?a#xF2dhh*z4o)9C3GNO7|4QRQ;oNA5=x?VU};Iro|3rYiFbKiSZ6o+hEjed)==yET;l)J|I$g4ainzCMt+lX!?>y%OmzP(j_N>$oK)}V_ z;|o`_31!v9RlgmD;{$+rULV%#jJIY(i+eG}9}kHDP$9Wi>dtQQFV^odbJ+6b!OC(>pJ#W-djWFm_Lz~RxV~9LApvk<;4dJd#Q1Uh9$$iQqnU%A~=BP)ZVGb&V{UWzrpg>Xf@$23iK*&sWzzVFsPE&#J;am2s4)o%Y>b@I_S`Sc5MZKV= z_%jy%)+#GQLw+1LkII<-?WgjXU#l+;@pb|-5E3V`p`pG^Y=>@jfI{4G47LfagD|TU z+5K3w7>a{2zQG`vjM6XB=Tf1Kn!!tXs?&&b75*=2Zvv**S=apsL{JcAQC5Wl0tysq z3Z<03mB~z&A+wR0ByB~;G?})6GzpW0QrTn?WJgh6Kmic}Swv741w}v*5Lv7&0-`92 z2;wX9B8x2l&*%HQ&w0-C%%u2U|Lbb6cFsA^Irsg$fBPD7_(EVd8pJ+(V&8!yhY!sk zICwe4zzB9}?pd5ub1R{=mTy-SdKur`!_0P~efQzdm$)UQ%?6L`H78s71=}>qN{5}A zJkS^OO3Yhy-G%Mw?sGdsSl6KmnaaI;v(wX zKYL=AL!BY`@rZ!d*%Q=MH>rqiMLQ&dZ&3-@%}Ymf_b4R8I^DB)VH2uu^$U6#lr^14 zD<*n{_|$63GpG)&JOtIytu~ki)|^FI(6D%cTdgvM@o_O4;XlUbfE>FQWp&xLpsHCd z!=#q75bs#NSY@kxu#0f|CTCl+nOJ&tv*pYFI&Y%+)k9C=)??;7sgksZN|pGtxU;&n zuqy=p3_mz#4pf?1>`NUt?-MdAAc+N7PG-uqpLU+@>f}IMxBg^qpmwNXjWQ`@_8xE` zxtF-ej#YCXe9;-m*3O{l;E@}HSszh6<3TVH{t2!+IKlHM$YkP?j_TZ+)r4M*%V5l? z{+lgX*fX9iY1;JTe9b24V@O*sfIBdQF+yS|a$qd3Gfgrd-pyzZ4?-P?1R0F4;vihW zWB5q(vd$bkhzLD;GIp;du2c5?&(ObO=i+B7bd4*ElL_D1IHe;S%)8k<&5WetA^EgC{6Q0EvAkFHcT%?m5g2{rovEXAN$}yqGIRHGQXIb6)r98fuf?w69aOk)T zbdUNoIM2S|j}rgRx5f<8hXB8t$MLLOK99xBFYlVG^ilEh19(2zNpf=sDt5TSFCfJ? zQZV5#(r>p&Ialz}t|8^@Q3`m;pPh)a+LiLGh~`@&iHoaHuq01;2D=$=o2u(U4A5C>y?jo|?OTL73+t!IFTa84ptld!;yX zd+flcBj&BwQvX)<_(OFbdB3p{f&KGpb=BUjg;4&6RSQ0%^2q=W7;GES8`vNgCT(*= z-XA((fK2s!QpelMg%TKb{v)qbnCjW^CNiwZS|{dTekl7dj`fHuZ~J4Vr7)NDA{kEn z68`T&6(|bY%wa87f5)>MZ`V>9u)SzX10HWiBS4w?AM&low0wAP0On| zZmROzj{L)loqOf?5323xwAl%dog+CThkZ4up|gPF-jNsDetu+J2*h*XZjt;W<8`1; zxSyYKR+X12zPQJZ?gl1#nTV1_fXu$Q%ZFQ|v++SS$&YzvU?27Y00<9jEh| z$c%myHgEO*G#&6@A_mF4ya6bcV6vZg&L-`(%cc-J@9)(;Ye&}TyEcDd{lw)5j?~QN z6IqA>i%qoMs4^9=5V36IF$@ldoq5dGkpq^E5mhPpYZ1IdhiUm7?03;%IRUY{|00+^ zvI0*(h2*-uS01I@e{C+~Ux~d+_1$%M2pwA8B)o?#F{?{|_D< z%`h63%*naW4uw4;OGdm&M0CkP;qX`n-P2+Imj(yV6t5!gh zyP`8Lwu+@t zd$b+tKW?o38}I=g)Z|>30x3|x#zn4;ha_|o&9aLz`=#@y+C@+s@V_AY=|wGyo@9SD zE-ulcbDXF*Tn7?GsnWxUf54SE31d+7QKxu5xZnr7_Sh&|@%;S@D+HryLR@-*ulxj9 z)wyDe9R92JfjHKiYhM-DsCm%`jf+85h(DK}BYWuImgX)Od@pKI+ZLdhxX>0=BIPSWUVmBj65%GU~ z@uVKzh4G|FBwq09Tvkq9$94*JrWR5SxM!Bg$DTL}67QJVzZA4tmfkkewWI-C0FC@B zeQucN^;_U3s>Slk-b!N()%~r@8~QBRw(ogA*IUX^5{rry!h>&t+QwU8PkV_{DT1V- z_xhqipBwS}Z8|LKn5LI$OB%xt3ij}`qufI+N}=9IbW4Y6q?ClTN7ib(1QF7LA93QBt5>zwNgdG1WgMOY}6ZDLPR0;zXJK08 zSLiAa`5tht_wNh@O68GHj&2NA-5s@I)DEq!Kxn<&6%+>#-MQR9_u6kNvOr&er0SN%eMq-C3XvS2?tCB*Hxt{(gN~PSaDfAwkE^+vZ7!wo>nE zxM3XrLZr#NmOQK9?*<%BDTShsh8|{dgw{%4_elcVyJq0BnAxfdD;$@tDHGS6f%Rk! z&xU)W+LF2--P&BFiBu#_qCXKHqo`aGQ#S7fA%FT~pB{B>lYAb-!8cPDucFLMVH%3t z58KQwESENfFYNTUMK4v%T{~GF3W()ZZxOVEiz6M8@OMAm^6Zf8AG(Z!+)zOlwcf-n zePRROWL1v5ksH(dkF7ssZGGi(+>(bvo2OuBOO^r0FQ_X0Jl{R$o74i=|MyU}tfI2n zsneT|#yUSwM1K?GuQ^tfy^xr!mgk%zdDH^=<)({whh7thA%AU(gd)F~a z49-Ux4*}}=@)63Ip2I^44<0^xSw#m|3>4;-kK-P&GUAPgMQ!!5TtT`rq+?qYr)LjH zD=Bm_rNy~HkC)wf&`TQ6io`8h6k656|( zF%9Ug5M}3xA8$HK9!Mz4@~PFTpK|qIg6XP?P@F>bK~+9KQ|${%@6rfdvi3|SEKis- z)7-oH&G;zY2IdYQeaPA&N3WbeeYzPnV=cJOxXay0r@UaxA}_f!{NVr3OU^sb4{xVd zkXVr%_2*e}m&VapxzztY{4|v2O_469ERXrtVKQuFVG+L894Ax2TlLa_+!&FjPj#dFnaCIb+mibd4Sez@9+ zDz^){eUyWva0U@oWfhMHdstGj(|FsaX6{eCStBtA^9Y#j$iI};8Ay5qR#X=O?i8~w zvG+`&F9r^n{53QNkhZVz@k#}fi8c4&3jyPfrNv5fHz4)9u6|oL6|rLAE~zu%1j+;9 zT9df5Fo*v%<*?ChXw3 z%#^^{RXJ?uk9V3WR(L*-wX88W*nEN>jzyQNV^x=8+_hrzj_ z@%KVIvo;`3yya5Ta^hV50Vr^g%LQvO_%#HVWzyWO67aI z39~T94$2%WF={}=;d#6JI5#q0}sjYXs*V_)UlR33)QVlBzp@v<^DE zC6%c%kkqt1M>Q=Z%TU>JVOC{xd6<=50Dd=o8;*{k$buW_+_~d4S10CaYP6i%!gVsr zicj4h_`ndbIk}<*M@Fx5hh|6q2mp`5fvuK)qz_2EhQAOXMRPc54FN# z{~YZM4}RfCIHv!hW;cE0Fai3mvudZ>E%BCj{Xgi3o4#dCw-XfCHTOL6UL%K5GRohK zhDL@@Kf|H`bO4{w@M@XqXLt~ug4ptW#lLrnv?Hu(cf`NzaQG=7!O7c@N&LI5FB*3Q zOl>)f8@KU>t`IaIG`AUZvB^>1q};~>8$q4IDz@R7IF3dunZK z=p}^#CsrqF17r}_2VvT{irTW(#tReG#yY)BG*gSbhPf@J)SiJnit46gqt6s*ZVbid zr2wz5bASiwza~#%X>T=$xH$`$UB2q*!OPc~;r#P!)$&A?y!Wltw5P1kvm!aB${E+; zWqVa_;?(#ItF)v2WRrwI;cNgmy=|r0tBw5yWbc4IRN7(C+K>H!2h;m;D^K12Lv~hf z4T#)~VW-3gHb$M)QEsmXAkawCbg{r7PCleR_jPSwB;H0Z)=nfA6-3g z=+Jto@+qa?cY>xg`){c_#>O@yR@pOBGQ}LC>t$do%)iFNt!8hNaGZT!{>vv?&5iL3 zx73ZaN^~gM&l13HRl4ym|JsHQ65ec%u&A}^)SA6C|8o>|tH?%NXr;lDuv3wz!Mr-I}v z-EPq2TOZA*d0D^6HzTAtCJ zkpY6LG@sz7^E=c{bLO7fqfx8F0d{StRG0LD@@}6~p2$f{M+Xzfyv5ckWQ#~Y*TP9j z(<9F*R;$4@)3C;(X&%_CywR7uU{O^$fx^uvg?&^`xsl{ zCq#RCC)bK4_4)>yqL~O2uF!$nTx~|b7bK>b%Nqv8S@K{#Z zB|hz1Tv?jy<}eGPGq^biE}%k;onaW6o%2S<5smYN!sju}AcBs&?T-jN#cQ-adM|E` zDa&>gw|4||11ovQ`hhFfA_CqY>YZ@LWZ56#mE#s~B|qk83ut*PVqyD!b&2w>RJ_kd zG#ev_H6Z*e2qU5&j3}DgX(f9>vbBE`i>!#~C1Hl%di5s}$rwl+Qdd_j5q+5TU9(62 z>|o~KVYKPy(MnaNt?+N$zacHk%*3Os-EQI0C>Lzj&q01JfCmm=5Sa7_OU69Is0e>Auc)IlnZ@W*+HtPJlgJ1QE4BT`_}9y6_-=lh5KA>J+qFo4iHcb$ z)8}^XvB$XN=a|DNSEJ~+^!n$nK3G#>PpmDfE_6w~lrNq}97hr>seVu01OKe!uY^hG zu9C05UpoOL+beLkC*zm*H@DDK`FRi(2(5!!q3v8xW_Dp`AY zD!e40*TXPKHNtZqL?A;~tL?@B9K8o?-@j~6UEQgD`8(Xv$nvh5P>UB=w(oA$i^B6Q z{AlR+EcL!L!$0EQ?D8p!UoSgwvZjr{$~{3PFbtUn8g4mT-euGs2N{$ySBDZDx-C3= ztTyuOqTE;Tn$?5Ht~h>##(yHk8(G+(9;Ky>d*?d(2g(8AiWH^T4r484@%yObt)x_vKU{Y~Q@@I=L zKi*A8cWee=-k>0P9Cvn^12ETg_{aF@rV52x?Cm%{u@8X3H;iLf&D}?fVYDo$D$aWp zZ$i(D;uUpLPWm-4Z!@E|%LNffzE7NO<>BxXFWn2CQJ57%P_Ja`=AZPD!~e#m79-u% z<~ZO4>%&<@3+Zf}BqQlOKdR+e20N}Y$>Ck9iH3IqEak@MZ&`>x5U;-CRjBaN1G$-i zgb`yAyz*uZqRNuoU=fLn&x4jDZ}do@3evJEOtf+`!l&-XVUD$}#PQ}&RmnJr(`jWs z@;5B2az}vvO1RJ-8njqEj~_D3?fEdrzQcJua-D8{EGt*x+q1y9sG2`z$VRj~%FO{` zm(%0!t`vRSk1gW=!AZcdS^+6)GYaa^;Y%+CmsQSRiH%g+(8?}Sq!Rl+s|@N7;UViC1S}|tUX=&_Qf^gjUOdBV*|*n<_`v`XGq6VtYF)z+zbFSO;7!G{ zEdqN&S%fKtam^jRBpH`0z;0}sAK!-7(O3qqW0ml?fvI{TQjh8TI?X_WdNoX9tiBZF z6AYgw|F2@sBcDz@F#GD@>4%=9xP?NfkAFig%Aey987yg2kL|N%KDw*o8(cn&_>>R) z0boZ#NvpW9n-$M^e!8~>rNRerZFTM82iK0D#MsXFoe+?&dU!?yD-Zl%SXA?FQ|u#& zJOd4MGG13@H{Zs-+@&@E|Bre5N!+j!N_5!ao|>t!uazxy8BOl_MOqc3@z-u<5Y*^~NwlzrDOu1vKmO62lNDI9+g>CC*V1Rd-<$_aTchjYH(Pg?so6ZjaKuuL_YjldXD5ncH^)I zNRaNE96Dk=46MT%onao>-FQXwcoEs!iz>~8*&QZ{h2(xU+aZ5##YGE&b= z_ct`onnB*sBdmIlQDF-%)0rBXTyn~7LJBWqRiL&!S`<|QS4&tJd zO|InkZkff8>V3G% zH-s+1aSs5&F=H>l&IdhQ)ug_prHZ;9@kzq%uOjh8gaUTcRbl4cv=r*om0NXNtEte zTq5YM2el0x?NKei!ds|wo$LYRA=+h_MiDJTK-na*{9l*T1-`p;EPFCi_OIpF!@N&4 za8KdO{BCw>ZT$o}ZmrjO*u}@P0Mlq&zXgf+d`udS23#?0&~+GbQ6EMD;0~R5bo`l+ zrTcNyF`?u{HJ?~u;;)}B_lMiY!voD0s{i{LC;&zYRVUD8 z1LR*oz23%@yTB{7ZsPI{)?c^Bn&uMga^y99s@K?)IG8?MViUB~QP&(%H1QLY6*s<~u%&GDMzt6$O>N{ZPJG zm7TVyDo@)>c@PdpLdL)>`=Be>5byTLl7=$u1n27RcyBn?sG1iEHzdrOmPaj+8E-!0hCDsh{}KIB&oI>O-Pu$I&_cge64!Xq%&S>vGp zoSNdx`~plVc3Cv2mw08@{M2-@XqD5CcUE}sK&6{&-4?np<`=t0x^vo4e;X3`pJx%c z=*J*#Dh<80#a3;e3`Pr!W8ZAOwr5`8TP05r?>t~YRha_Mi36*s6aY0mDxg&Xmq zVgY{ZUtuo3VrA!azBLE;o?Ds->4+d!#z}`j--9miNr~d;y1uw2TFQrF$8vVv9_sKI z+r7&t){X^i@pJ8&9@MXSj0^@->h5k{%P_|4VF4vzP~8X^D_aF>PvBXH>@LpFv?dpA zET(+_=eapMO{V|Jw0y)JP`zj00~#gVT%dFxRehL+J%nEoj+_X4dI;ONBiAFzq2#F*Jdhp5f`Y)jh82NJa%=UElTGfQY!TImSXTH;rkd=b2~dj zyd0qqtW}K<70SLhZ#o@9x&gd&L5E}%C#l(bA{L!$B9O2`fv@DEZ7o|Z0+yY}dof?^ zoxQN4q|(SMhokUTPei2L6kR}4m?-it>1HE(^nVUSTV&jZo7zSICfh{>Mol677 zfKu=*+*>2Ol~wr0;bo6hzC7K*ley zB6RIpybix)ay$~?$fXPP$N>eBXR1`nvse(Gm{BC8-umXU+O!bST$&1@$Tz32)9EKt z`UFR7la6XPy;5C!US>#Q*xEX@{ll1_y1W;b2T9}#znf=07iyd2L6!W1#z4>Sj&IUw zR?(xI@rp^aBw_kXke6__#nNC_$=i_4d=^}X+tNL0z(v&!J_T}*LW4$|1nZ|*c@?LI zHF#pz^@G|uqb_G6!mV!!|C%T7rypDcp4pDqDP9<4mVJ~0e1# zdH6H~2jtn`7D7V}K=0W%xi~v>>WV9_3ImuuN+OwRuZQ@#o4Zc((OOX4{yGtVE3LVX zEb9r#nO-&^LeJ&ezx$<4%JjZ_N4(oWb>a!^wznOG^L+uCZYAZ1)IKx2uLu#mYEgo@ zod%G0*_*(VuzgtIyJ`7QH<|w5TxmxLZGHf{p6KwsoC^^*vctCT!9}>TYdK=__YvV^ z{9b8L84X|XWgBx`&=qL6+>k~Y^v5ikQJ0f>>t;rN-dAkmxv#K;n}W*lz{JvlO(tz85s;B*bj$qFu+^2dx~K&PMO9;ymbdX8bX z8bsv~YI^)*Fc`Ag)UK0-dD_}_0k_k)RVb}9JCK$RF>}u^LzgMpmkP@3bLr`DJUDF> zy1!!oT1HbV4u}!)-ocg-GnQ$ruI!3SZ{RbPQ-9Fk^xALd!IAaekKKE;%R9F%UKmT3 zNTC#D_dI8@@9XJ%)vF( zWUJtKW8OMXtMy%@UeCh#Ul0m&d(qph#u!C4TW>wN@$3C*wW_X7*1AF|+d#(_$ZySL zx`cmG@(=tadyeiWw|_9(m8N>rNwNQHvQhNw4bwTw^;Id`bCswqWB4FM+Y4IUe!lo} z)T#|!(W29o{l#;J$stu%l3|G-Fu2lfx8`>1TMpatYj%uMEVf|Wf!Z{ITi`K0fiJ`$sNAly z2~4Klst(ZN7Mm30m*cnV+tW0~r2C55Ka0(V?$Ng+9WkQl3yJosJ| znAJ;<9J}(w@wJ0Nod4zu5tr&b{pg&Lj$tbWrN8ZfPmRZJvys;_mx-`q_`s;?g-24B zaoS$U;~vF(TSjKJKE5FsIy+B3=&^&L`|9k_T9^vgk7?2uKix^)_R?}P&3>NuBw@59 z@p!hXKl>dc9_gITS?JVBNa8AfHeRpWO2O?{JF{mmlb`bXhw|LUafO$yC)LDry(MCC zm@-Xs$$JJ3{u27roO-~iwe_o(*G`rqy2ks+QKl>Bfg`IYPMYs20X|oGHcSvNJ4n7i z!7(ae`xTJUT;nd~7x#?D_|*vS%Fxi9bs4x;wd&ogR;_1fTRHB>{rE;My5vKUNz3gFP_fddYS9Fy1#ajr5G* z5cjOe>17IVMN(hD3$VPw5zJZ)MqjGVHm0zJ3bP7x!@8O*(d?_x=4SbmRp=VQ)p5Qq z7F~*PS0{dL!q3i#D1g%Uoh%2{^Hr{oCN^ODwAotbY?j+PsXWfN-4MUkZ zH8LvP#s)f#Kr!>b^#ns54cfgJ6rnVX1cT<>jjYuWH??C&%k1z(Zv%wkduh!X`nWw` z1}|+UkYIj(DXaMcHU$i2JBmjW<84lgU7Y*)TqUBtf=iGqiq}*&Y++?N_jEUo#6)Sv zmeT)NX$bTr^@(V1Mi+(y-W6B37IK>ywXrhDM(G1-J1USSB0mQBfDt=v(rFVnIo^K> zv+AzhnULyn2Fo^$mHC@HAu20|P;LIUR-VTm-C1E)eY>0R6rp_+-q#5)Ev@$h!*U?? zs!(}=Be8x+C`Gy3Ki(N$oO*d2(VI_yQSGx#dMs4~XO`E0fR`FO7>*N>-8~SakLeF2 zu`Uv;AsaOgHurgN^e!$ojV61MT5gaRVFOU@+9q4VWUY0`BSQL7y08E>D1uhCHGJ-$ zSeKH*lswC1?i=|%eNni>^?BzkS=wuttsgse^w7%sfukobMJbZQzaFiMDJ)moR|>ij zSq&cau>%QWLw#V*AL?Vv#Lwpgfk(n=O)WF{HP3KG2LDeM&-hV~NIb4AzHQh@k65dho6 z3=hAQmp%I(K5%j{0twu*3FTCRO*vic5MBs^Z%zs`0K6c5eOLLI-zG*>!AIEZR{d~vc+--0g{(Zq27lg6i z4z@CM#hhJ8_P1;{k-NP|A9D2Al}AH>r{#v_gZ@jQUN9b;HV2rc{jb0fhfuBBlFRx2 z@q<@fdEjAdmmXWc;sA%r_$=ku`+QyabRS?^&XR8ke@LyPXkE-q(ENqZ4_$!ggqytE zowQV?madsoe>J>GS=qN46OCHLH4{1|aL4?XhCETjFqH$_fpCU;H=nFffc&~{(cX`O z2vXGMY0bb_%8~FOdfGpLO#b07CWl`!W*m1OYSSb6hYD95e zi|D`YTXtkd42eYXAko=WwuVwb&`oe84m-u|_0xq|9jgiC=8p>Q} zVpF&TK2H1uL-2#W+7;j-r%yyqx^c@OHyv* zWE$&$_cNA-T0OFc%}f^HO9Mbro7 zS^xEcIIEjYv}~9rs@#?fgrBtJZK3uiSU^H!CCOa#6ImkElnSTd%@u6IH5ldMSh^Pc z9^4xd`llK@Mf6;;qSrfJy z3qa|hBy?_P6|_B0a4zH0pSO1+ZN<}YS7mE^-0Y9_eo@CV>vy(cQ)&i6)fu<7hXh^E z|u(*Ml0E@et;Nipl3Y0}gqct_6SgWt$}upjlI{dv)Q#io{Z@2vS=_QiAjyRe57Di9)AzOcbd?Qdi)-gfnH@Q<-d1` zNdp&hKLKE1M3|UHO75deFc9TG4h;aXk&6@e%iXws90?T6{H~3f+{>)HJgp_QG8a(n z;2}OWy|{Ey5Vfk^JYQ2=JvB}JfBZEVXo&2r$xY7U>L@C9v9liCpx z9PqZF#Y>P$B)n`R4TaNP*^YO($^xc%i5@*#?<{ZMzB>Z&r@2PMwoxXZ9PBs;f$U%| zj~Edz>iI69sGh1B60pN+Whh|}C0W$?j5M&l7HrUzdo@y|LtByRzPu__v7r+Gr?`_$ zI06qA&6|1igV0Hv2+;Y!VYuDEpmL=@UM(vo&=t58@XgI%ww_f#_GTMzoOd2QNHb!t zTr`PseiC*w@)7rL*|}ND55~~Q6TEwt2{G1im`M9Gxi(B7sJ40gvxRG`oRsA04fy!* zS7SBBym%2H&w#8H{YR&{Gp}qv<7_SHZ-J8%T(X$=phxr>&K(uUp)GdUOJrftOk|5x z;tZKt6Xy}ftXz$W$de8Qy;6DRt4-?KJrhc3!TeJTr#1cUK8n|Cb zbWJ^mlb*o~cFBLEOx%XQK*)=TURFPw2+`-TP(5TT*rt+FlRy4k>4C?p6*oLG6d~H! zV|Y1gMD%yaMmmh~^3x#ErR!^JtC!+#+On0Ge2Vw~kMe7qCqgm%qc_S*E0mGkWHp2( zK^E};C_90Kk(-EET&XjWFReLf^bIFds><$7CGOw^6a>%WNHNfNbarkzt#0N|3a;3( z-P9uy z9N5k$Z5+f;$%3M#(Nr|KUqc)=Q@l%tbuu{$bevOrV``dET&}eZ8O4G#WE2fJ81=1E zUWXZjKK1pwY-~!#^i}@Uh)8QC78dhR$09VIxqY2y(VgtX7BAU2@@fu^aRvKU;4;?4 zb$VO4>Syy#jY}FiR9wtbSJmtCtC^yBCxmFM+wYRy<%d3&{0OtTrIG`w2B+J?rAlGvM(p^1(-dGh3_UnyBF z3heXYU5UUI^HNBC2(Zzwg+yIA$-eE;NdKM%l_5XSV1qK6?XS{6mR!UbU4G?}wzm)h zozk$1_uUSLw$!REq~=fMrTbxOUEQQa|F^aG@Z}81{-lqML~Mm&F^I^POxqWi(dlX) zdq`$8=U*8c!M2l1D!k7A7NoVGfV|^u=%#%5d3)JO)4L;!Kfy94h1*aTTafng>_`J3 zNkAjFI(B2Zku@Bp>%Z~I)5LMLIp2(I98D39xodKe*2KsB>FEk&NuO?!U1twp`x_AJ zn3tXanCJch*CfhC-E#7&Cf;N-1sT|V&uUc`CCY1t;l~vjP4D%gyF-DA?sREdF8zxv zeXP?$*hhMTsXlgHK@~nwZf0;`g(rpCjDKs8Vp(Z7u_t-D+HYIj0GCy?? z4xTKn)TntMZ$3?vb&iv%U4H(pJfzD2-39-RT}EUR@wN7&CUEn*&*22ror?=}CJ?ta zPr5AY#1;#^w8Jp-gN^`CVq&+nU{K{&Pfwb#Nlto&%Ws0 z>K}S=Hg({*rJrSajR6s1M4gChE(q+fLJ28}osEW6*`E>htMZTg<`5Q;_pJL5_@%k} zxFK8T^Stl{fV5=;R23_DA*cTvy@?$4boUAt^oPPiGuKeaW*bJD; zFH!A}Jm<%vX%8#7D_i3CSx-wSn7U~1f*o75{QkhbKj74XBZp63wRmakd>*#^djVOF zX0=4aKpwQn!LVXznAqx@-v;aOY;a`_N`gEL`Xg>;qsPhnrm9rSCpNT@_4Vg2C9EiP%Db^ zDbxa4yJ1LGkvj;`4XtZI*DASwwQ6$Isbv6)!vYRQ{+0~!ZJP3YGI(o8QUUx;_cm5C z0PTyU&lB?VWbTs21|5@n6C8SqmzPf+KYnceWO{%^-4dq5gq2cV?hsXl;2VJtU+JX0 zffG%s=DHrmD@C3@&aDi6iL?{@ysTpPwQ*aLz$5rMr)}H&Hu1-o&Y^%WUw-Q3A!0Te zPVE9VHdYU_d^m5A5=YJ*A#)GK`$YGga2%V#jC#Q+IZyHe)I8iKgomYvbuO%KC_h&f zzo{@OkhpjyS*iD(C{y%Y2f;hCt0S01Mjc>Cw)N`eT%FxUe%nJxMw#P3xw!pA1D7A->P4@+)#iy~HZ?{u&G_^%2Fq{U`gf zo2>X8X}q)aqrN?m^?c@LBH{mYr1`rxcE2F1_6~MaJQW^~J7sPrsV*_9)PL^H{rC<` zG=xyj;o9=W^KHPQ()R(z>%a!>BL6iyfo>vN-jjzr%V4uI7FC0@d8Pzv&^z)sq?(Tp z%|dcWB&54@V>;DyVseD?R}dUDxy0)o&uPFXVV>eFcBwtDhJrs1jM*=a(~&?cPW1io zg^gQ~9(Q=0y<#Bhjw9l6mLi*ZWC_bNdqg!dVfV-gRos+m5aOu$~PBYRZN9G>ng z?<|KI7Eg8v8R*_Oe!ad)qo?>Zq+IVeA|-Urc6Ky*YQE!u_QzdktcT%DPyzARXfyZ< zGA1U%?snNyXA*RiC?o7#-fg)(n~db6hZc^VJbbWM5d{9Tbe(Y0ikwl-wqAjyai>5M zp5i#1p2NeGMZVS}m?Yn<>BhXELg5=Kbp7gM6ztn~EJx{ymsSAp0gyx6o^q8O=3im7 zU>c&*4(j8ir@u$YAW~>zS8XB$?n`;esKREt1+`{Pi_P#9N#R%A^q4$H&pQnT^qE)_=%&v_f`!H+sBrikb^gRcvgrlQ29 z{~#AvtA{I=+}WYJ*RiazRgnPT3EmEpN!yYRLzG!NW_OkXapxGI6{ePb+#sKW!_YRo zFLSSKu`h$ctb`o-*_N+d2JrRyN&B#P5V7QetRsiU3Y}4V1e0mqeQPb*{P%d_@R79> zSD~3-v3Pv#==O*2IC9{!6Ijwpmuqpq@MJt1gB(#4u=8y3&0+Id9j@PE#YsX4c{<%& zNicy^rPO^wWGvG%JycqDwenAz`x2x=~wchuvUdgs7(OP z6Z{gFdz-3xYM%_Ti$9~htdh<~LB~U&Ld|v>O%1K6L3zBwv0e5Vm@}16*oDsGIDs*u z8zp^7CGHcvn_fL2T{)+MbC2cZ1~|7hzJrgukJQ$#jd;QK z(BmuBk;jr}Kve1KY&SG^gL%v1m6SO@$>cS{+vV%c(x3LbTqxUqQv*^;c_okV*~n9bmd03T zwRzh-(dOS9I7{+H;ruNyCI%2VdI`*|Dd9?Kx5X@T&6DQ~PuXP3Msa{q6x&e}M; zH6)e}-UVmv^b&QFmBo<9*N|rMRd)qsr5&43o69YoHXQCZ<@>WUWJ@l~!{B_?&OpO` zXthfnk1I%^MHFa9Dn_ez@VhwO2bk|sJ|4_3QiIo4ztxR{8LaA+cCO30o8p2wzuhM2 zQL-M%hv|z&M|EnbTl>R7I5*k}-$wbkP|sH|{}KFVx~7^guU&EA_~m51#+&pIZcyQz zLY^+Ez^Wg=eov{4{h8X4mK2`THzVw7%jGYw)VEbslgUcps_=5G)5s~6GjYOO_)zOc z4d0fmZ4BF8_5{_p90Fd>twUKVY|l^@qSw`I^upA$mqK_3LYjt{?X=Eaqb`ZQ;L+J5 zsp?vZ<^tj47E_l+$Bq|`GM%Kbj}+z2Q*uX70ReT1E9=wZNILWlkHERsGn=j=yE5$# z_BSu=R@fHR)5Z-8%QC+co0IFd_*;KSUoqj zy6^0~=|A+4C6Rsm9`+uZah7B(X(Scu;*w)WYJ~B*-2cew@1J*`KU&~NmFyiQ+}gC0 zQMF7Wo5&%v?UBK2j*nBd){@>10b4&dbYh5MW}QW1v+I zuKDv_`n;UaYWu!Exf~ML=SO`3epwvPObUeI1qj#|KhaGJ=5+j(2<=BKgWK**3amA9 z*(1W959kU#q7>N=Dc7&Z^Rvq*54G~BnDbJQ-SOGTcsYchyjYQ;0vR`nJn~KK*`!nn zmVXE~k;xW*_!VBVS$L(qc{9QPFY^povTJcB`5V6QgN>L5L^fX<(!cw{0n_#Af8*L{ zwnrDL&_?(;cMcvqa%_F;J;VHfc@;fG4K)d|VyrN@jai72$w&QMAu|y)7-f*Ri+#(@ zZzS~sZktOG^(OW}k@Fn{+@gV*9crzOZj`chxpXN%sY87Gdwi-%CpyzE;hw z-p&g9LUtTbbf#@0Ch4($<`&5KUMcI;50B!JYW~xUv4)U{G_ZMx{C^T zwP`NnqwJ_MC4@vnkt%%+95(14xq2SZDeqDz1thlL0sS?N?N=qVe#vbc`bv`KYE5P` znD!S^{VJ{r6qJ(0YbP&0c64q2z)_-C>#O&;$33=gvHyz^P{J~K+iS980Reg^hDtn2 z`h7Jwh(f?6(;X&!JW@hxf0kIpR>Cv0 zYmusg!YGEYu0d-UPvseTXyQ~{=QDY0Er&CN-e#wlqYXSg`)`%ijju&axWZp(H|d+u z%E{w2d9)vy?LEsKe!_{!xa93*SjkOa3m%mk&;(+Kn#R)~#XGhwPwy!IEwnYK_32K>zCqaAaA zkL0WXlA*V`on2d`_d#e$+URF*sCYkutECr599!aXQI4Udnjp9cZPadv76e!yimsLX zLiLRCWPib8A1I^kQ3f4-?w5{(vV(!$N!vkIGBRX7%JK@IB^5A*AObYbvO!ZZ_q?2E zCT6>o&*PinXJ*F?L*;T^;wl@eI8t*l<3=YFOS&(^UXmvDQ@r8xMz}X=P*~+|Aj?32 z=KyTmIo4fM#(2LhK5-1vOf(696wc~6V80o&{veQTDsCJ*10kbw_|qTx&nbu7 z#<+U{B6MK3Ha~mdZ?h@XgRaw*idRyiGXgG-py#5f@bytTMdQG7!E>|tp_$og{Z{wV z$93ld<;ePy?#*MRU6e-CGPJWcTX_{$jyb+WmArnl)?BgM5E{eN!u`^6!{J z={Dm=)l z5Qnrl>Ci6q`BJM}Y(KCFBflv6(@nwcz00K&-cqO(L&5Bn#%|`7W-?4gi5~D9^@$(m zfk}_?n4T>;>__$OAZm~4dPgwuFeDf^QkS5vp|TSOQ})3qQy8hdNp_al0r5!AQo!a!u-^Q0i+UYrh=>pU(Gz@ zZ;`&DWI7EK6!fPz;j6ousYXMGBzK0raC-1I9Y8hmSRA;a%d3nEsAk$~nU!djH{eF_ zR=yo7bKGR3vXp2KJV_q01BV6;WAU8&(QEture{SraCL{!rg;+wgAq(OA{VkHKR2VP z9jWPPp4NZL#&Lxu&a!9ip{LeP*p5KAq_(pCe5#Y=%k?gn!)1ooG%6IIGtFTvA-MGL z|AGLFLO@tOrhca-kYLt9LpDWG0zDTZoZWfk*d+&!^wIEglC~0U#FYfdUB9DR-e)$v40z=Wqrz~jSW>! z|2}?!B54Ey6?Pgocvbv}{oDI)%7kq_jf!l)4|*{35of*QmDbizR^HN6IPgJg#?(dq z-fHH1@cWCbeQL&P2NieQ0*`Iaqx~V)-I{=@ANk=b>-#*vopiWIbO)WAVv#JBAmF!( z(SpNtkl1Dy=IxBZNp^}_+P~&)oAKH%H(Vfoj;Bg;!)jNyUwrXYMOQq(P~&HKU`(C3 zXQjs9BwD+Xeh4c>b_sH5adz7bZXlZESI)!J!B(B|<%9|ed{Ac(1+jEPuV986)>~Z0 zX``S@`~YS!-3ukf7e! zfyVr61l^VYR4M?W|Dw4*7MB`6$p0?kKt>ij(Km7M zcU?doelt@>=4PP(lH&3>IH%gW{j`1HbBLH|tqr{T|0{i4r`|BR2(%v4Jth9lQcWuu z`Eq^@T>Bg=PwbML!jHBcIAK~YF$>dDLi!(vkgI+pTFYkvPwir?2}!7ElN#l?CcedQ zTDFWxYae7G{adU8A-Qw=VgiAYKvpTP_GOAn_ZM=-sRCn_V7{i@KWF7t&*GuErP;Wk zLB_o58NLcgR<)9hm7Q!!`Ha+Y6aJW+UY?HC6|#FOKQPu<)i+H+kRCsPF0et7mNaY> z!r+k&okBzcGh4>eT0G4sa&dOdws#2+C`%+?~Nt$+>CPUPKP{x3cF_eT+5 z=kiD|i8h-SVV&U-t{|-+_fy-C9z1r)W@|~c-f_TwHCF{Xl>c5N z4%pQ>$dK@{|3j>TD`?kRWJ2>nd>GERu&>$nQPZJH5{fjgU{?Dgvk##Uus}u)&95Cj zwUahl$M+vTbn@~Ok;yjbMCskML#MjMPhz}~*vU7Qtd@-ME*8?pl2C_{tOLu6e#Tc- z%JB*j%#o5fR!*iRa=G!x&IQ_f0XC^XIhCKiHjD%}6#A~Z&xLL^AGHM9U~>eG0%X4* zZ63+ZP4s((mF)F7mcF#Sw}~G=^I_mcHyC;eYGeKqp_tLcvY^~;S~!8CUbu}cH?W^{ z*JPE)plLAy_r$O|Oio79MS}65_j1A!;B2^`cwWc=T5$qUZAmV0Y_%cdSZFA%>nFLOO=V z;^*J>=`MnvJl#3Uw*s-BVlSO2 zcj6Axn7mMxzUB=Ta6iV4M5^16q2Sq*q2;}3Vl64{EQsvF>Ft#MF`lI9R08q`_^k*C z4vZS92>63Qcwug=6Q0L+1D_A^O9dT)%Db=colTl*B+C*-BuRGHJKaMV>cvR#az(ye znpU%NHGq3VdhI%8|VQiD85k z_A=^0T8^}@YN50~FsRY%#|YBnsFZ+hhE^hStQVS~`Zh3co>`T$)}P!TJr@RA^SV?3 zw4*bbfjlDKkyo$GGxhVek_0hD8E%_GA`)Yj1(?cyZg!@j#UU~QC z?NZg7`4n~j@I#Gm9sGUIKN1fpAG6xmiG~F$89Sg zpCP3^$U8Z=C8zMj5?>~e<Vuf{6Ww8hJr*9 z#TS}A!Smbky3p+8%#rF5Z|2%qitn!8^ zpWn?doiv(S2%tru)+G090988qJ9#4fch4S7-2^#XXD})E1GouVSELpm>tps0=A^6O zg2AT1RXy9$rfB}VSKMt&9T%hD?gsS9Zqh7BjsLCOw`VZ{FVZ!RFm1LnO>rCanH$}7 zDKrKgb~Slf#qLe~7HVlUU4g;-tJEUB2Yyqj&JeMC-4G%63}4TCMurWcNH0D~Lu_=l zW#;o_*L-@I0r(ns(GljLe_iWBz=5AyJwYz$$*?OJb!B1T;gZe#An=3MI293-H#dijB)hmNeR=lt#hl*xvj*+(GMlDC+7 zXP~GP6&%+NI0eJqN8r7?cF*Mu&PLyE+_dzT8s*~pz6yIEkvmG7So|b6)Y+}g4(?1B zw1<$XWDg4B?vzzgEf+iV1-uR%ULv(ZRCp*4QN>I)vAg>iypB_e2@%!RYS*x-+ry!O zeVcDl-txymu`T#r9nGK6tx6C2po_+SJFA$ym=MIv712{mD_T72}-*LcqGX82^mnuK9Kr~fXa0^y2)@dvTgJVz!Gf>s^ z->Nq!AMOK!q_WONqfCJP1ty(sq5w9NK*dh6^s@pIn{Dp!(TC(bcW|K#a0zZHaceeI zJ}Q)fy-U59N-}yj_UvY<#R1-R;klc1Fk<)0}l&>>QXo0tNQZW>|P?n4yc zG?yt{atdH|p)$YlX~dkaz1^$3um~3Ev@xG!{pkQT4yzQ7)F{mV#nmo)(&D*&@T5V) z1R?R_K0X>CbY;bQRXzgbccJXXJ#h|?GhMKX<7-vWN4Mv3SwHQJF$s2*NGnZVpCoYR-aaW$3QjT>AS6v-d)T}qj-h}q5 zs>D#k3BIiJ<+8xXhnR|J4)auxp-@0}qq$QR0fK%PhZua1P8bz0&cC1Jg`ty5Ai2-I z9BeU{Nor?>0)9Mi-C<9smGHM4Lz0J&o}`Rbay^5;c7)scmD}^MtPo8FF<%nF>t}F% z()X!QcrWN5Eo;<+qWGR&=A(0r+!tW)u*tks{zW#G#j^nFcv=ojRTsrxxHYe-tax}# zkoEa|(cniFg_b+B)Q%06Wu-*?kgp&j+0yDOrRUFp8v9(B<`AdT@-mFCTD@H#*3ua% zTcprm0qr|<0gMx^vBcj$hOZ6%0~M(GK|ehAdZ)h0g}x6) zq~JHW5|@@v^_U`Xfl`V8i(9kXAC7y0JZ=Imw(>T`L@nh)gmu^buS?)Yz3jpO&tSQe zzQu%JKFWcdw%Vr6TZ4HN?+`KG#D~D=uICOq&RhukKhdT!Gy8(A^2;7W91J4z;adO7 zHUKJ(+@b0=FtUbIk zrr+b|syRdj6Lt}?UI&{N?#MW*Pu+sE8{Qw%wzB6>^Qmr5du{QTAWMUL8CEMQ-x0*%CDRP~ zU7U-8c0%EC4IR1!-XG`HeS*Fac?*|(^R8AvE)a4Ezo=XuDUyA+EcbnYEdtD>$Wn^tya>Q104k()17Xw67;IZPH)a)m;!hgzMYBN>W7V?R z$w0Lz@<>;YK`M;ii>go1C|NyWHm?B|@TE-^BBQ6fKn}eEj+U-z zG%@(eKua;z)ZFrF)n277n^_R&R#Gl3bZXjhn-`C3SswKhF@g%S%Fd$bW%qIv3R}VT zo!}z!E@9P>hl2Fzp|wNl5tI%Cc3n9U9qV;4#PJ4!)HvE<9+8#mt)Jr^(RP-X&PLHd zRIJR}K;K3x@fo?OpXH&7>kM<_&gPDdW5*L`F|zhdmHo4sY9zGnqdQb<&4fCc>t5RYa*rGqcmifV&&3Hf_BM^_O)h|+B3u9Sy|!gy+VoSaecta_NR&P zy}3Bi6=zU^bl!O!8By1zV@3texXT3r*=s@fzWYj-8ZLvmJ;XP1f)3#63BN(CUfvaT zkANtONV1b}?E;L}lbKYtg6E$)dgAb9N7oLWapc(1%P!dBVXVRg2kpRc-7Zeg68{PI zTb6|~Cn1McZ|p{+Mal1$Dl)qE_fUunzN=SeVQ(mvXsSkL$Q znsS&oE0K&)&%#B?zk1ZTo~uzr)>+VQ#Lr%bt9>m-AKCEquLOvC!xVB`3<`Y(cg1AB z2f}Bw&ZH)79@g?NfsrgD{y!{kpk?@(FY{Jv;Et_R0TLzkeOw@&DTQ_L)DRd;#c!Q9 zE1}W_5PEE$U}<9ygbhH4D;FV}qmDl+Y%j&mRYMlceJ@{VFpCM@Sqy$N)c)srV8WD6 zLar*_pLtguKcMw2KZE{JiS`vZ-E7idpgm9t@C1;AmP8_z%Rk|z1ICwizIn7xy2cVw zK|=SeRSoKocwly8*t$1&Yebn`^Tj+_ieZ_Kp`Z7G%%Vto{B&Yb%6(Cu9yn&v*yJw8 ziPPqzRsWO$>afP4vdzPO@{62NvCLMctk`>DwiKt}u-`~)$K7U)_^J2HvIf{%?|gw9 zwvm4~7)NK_Q?uGWRi)Wf(I1||Ernc{aL{=L^_ToMT(5I8has0t?cB_s^pp5`r*hTE zh}Y@nxW*a=7ymeZr`o=w(@lcdS8%=BW7FIALj2Hsqx%dCVqxtwpM=_MyPN z%Tj1NH^s0j2!wU{@OJF!9e8rnXlvbukka>Bs|9-hsDdIi~cw ztHmp2=a3eKvG)VDXz^V>l-sL>^^aYds#VWH5BJC}DJ=^Jx2DErzN~mb6BpIwkrt!j zl=BeKrj-ej#vy(TL+BHnW#T_q*G$q9maC+M$;}?*ce>2xz0@hDDp1eB&vK`6qhiFP z1;~Xi=JssT6&^5~H_hh*ZXy}1sE5Y2J)h?wKUhxJ{>n>Nv-9Fk{lU)V_xeN*A7$cC4* z(p2$WqmrqKW7DbXI3>W%jvl5)%M!M6trhCebQutC{Z=;;8n!F4|6SCH&&A3IaKImV z$S$YDRX@#jQ@hZctd$waLpj50*>IZ}oN;khiLPgCBBUA5Qwz&|yNhzOJO_-|I_#3| z>{@&6r7O5Z-p9|>Q;S70KPDY&E zrS@${u?UrLc2R35ax_dD{UvdiYa^77@CVT%nT3W4NIbAi6t*8@E#aQcIoNwQ6H0To z2=h&BWLZX@{*#FuU>qod>&~pYA->+#7LWGBSJydJEBWj~nesud!ZP2}CH zDf}m9f0(yl}KXx-<HNxN-n#R5^3$F4FEq)6;MH=a$azv<2G=ys zR+!p5Ri(fZ=-d5POBO1RToVbE9c}*2+jykJ^l2(Bedteo&n7XNO41=hW7fnU_)u4w zTalzT=u&cft#_L)SR8I^ME1w%_FX3!bN75T*Q-}a8PV0Bh&%X}N*EjA7YpI0&F z%a!U&%R9(5!0uTkt%253{4{*yH+jvNQinw^i*X3=Ut2z1!=uldYo=rT&zB3|mYZ5lX+ z6qSBu1H}#f6Q99FHNH_VmmJL<;5HjlGGzK+hI{p{VN^Kvjz0$5x}IsR09fQHPrgX} zbUwmXHgQLYP*37+llqhHBEM(>@34MXMdqOulZK?rnHMeY3E2vn#r8uP6j`~%Yl>0C z^YJ)-tVvqj$M60ES7sTZvTN-KRT?J}Wjp0kr#GpxtEd14LZFyT^RMCNpv{c9T`nVO z4pQm-#A3 zv|K}shrH311i9j_AN$C*m%%t1n|;WV$Oo`ysgC?UT<>ecqCpKpmTqX32h#n2tP;o` z(bDRJ>b0C-x~R(D_dztWXo7mXnNyxAa!LA?Q~V?6U0#oo@)EK2KtH2#lAy7z{|Fw3 za7)+i2lA=%24dfjq*wS?1IT&C{W>Psqx?9|0Vz6=RVGNiiYPrM#^3el>r*N|*daslCVpn|w>oJ(u4mBv3{5 zlapkFYnz2_a!5P*`6wh#6l;1n!B)|0J9lQWQ7&7jrPBh{kC$E*YW#lWHSL}%LowP= z^5yoXfK7TnmmiL6@B{o)xSo>-y17rAw5}DcuHZAP7*WSg+DE$FIa}_|jeEJ`i5r{e zecmVgu$?5{6a;2-VbM*}58Q+6z1w8jwpBzRH5qTl4N-N(ZoZEzoST_$QKW?Xb$zYQ zVa+MApu#tMvZ+0lwEJx|BhE*^PvwpJ+^114)lgc1dSq};>u9XHCZBs1 zA|yO(WfA=%6!T5q_b@B+AVN=XG}-JlW1!?%s%PWcnJD#MgwX=-L<<(^JR4XVrT!96 zmzTQh-hpWWUn?b1v1zl=u|q7kr#;Bk0N4W-{o>pr-XE#_)v)+oc&TmthvkL=uTvDL9-{slzK3_YhAwNVYRU#)Cs|54o!r@9i*;##KSM9#nTjvopO^1v z%=hZ<1#&D+2OEm;y!bc#K*>Z?{bYcI8(&j@8CA6p^P_F%RMO!R0fZr+NI#OR zR`FNh(y-jGB}s&#E%V#4THxv)Au_yPfm+sC{kqQ|HXm7Wbfuw!dZPQjcwJ-4z6~;ce8x3muFZ)kUDah=(tPR3xgZ8F~R%@>{A>>0{|({yLYS zXgou8O)xyq$0pkWdgHX?eSU59i?bjw)L0ZpwSi4l&hiHS0~~gPHoAYycTWZKiEr>@ zb-1i@B3Yi$7ftHq`yJjstB{0+`HKtBxd32U&)XS54Bh*l===0&V@D&)^Cw zeT~C@8h@A^Zkc)za8$Y@RX&m16IJud>ED1^U@2^W*xJ#`pT03v#f5oVW=1wVir?W$ zpzLLCJKAcom@mVGnC62tU#4w!b|h+4w}#ItjA9W6UF0WM@sdKw=IjCMP?ybdTrNx# zzz^f8n2Fl)&5IfZnKsZhE6q%kYe+QF8Sr3WmtbN>wvEQ*jMdmF&prhAs@7D2r?}?u z@d+Q(+0l7SzZmJH+BJ`diVO-kKz|fS z4$~)n!E{z3R^3+E|<@4&OOdyXHRrfB5o+L3LiF1>V(Ds++LuTYDCSMOYMA-C|=LmL!Y_xhW7 zcC4NLy{aquTeCngl$nfFS4nxRTc@(sAK_KiX@BYP@ip5p=QysLFH}WldCbKl=fv!~QCj#gp^Ug^<9oKN*G?)`nME{q&k8Fo1b+vb+Cb9_y%L}vAoTgSA zE}HIyXjG=ua^BozlEq|G4Y~S%@?13lrZKie2SK(eCPZ2P5rxYUH%3B z)?wdPG;BUII)Q)nn;YDIV!vH+IMnfRFJg(QMWBP+q9qnd6MyH|qK0UX(z6JY)=Ohe z6u(O@n5Hv4Fs3s({SwbR&tQF{31QJqUI;?1?pRukw=Wp|0XgRwJ>941>EkS62Iig$ zhnON2-uM{TyD&ir@55YBXbgw>em9%M!~cML#L?eu3)V1^F=aaa7)3+2ZtP9`5SEjK z`Q@dHvavUCEn&r7jG~NXw-da!_3%8-rsYLa%S=Wp+g7&y=?YS3_#MqJYPR)DT@tgT zDTWK)!g*W&nx=WW?qQ|`wRq!s5^h|qE|88;C~T*7-+La8rfEgo@ z2U?}%_#DAhazN-bt6Pci$Ltx5f>N*m(vF(iyJFeT#9tu!44@?a{~GV2p`M|>aQ4rm zbO8J+579YZzup<9IW}KJ)955iMW$aGbbdCkS~Uzlvv%U(`r+etEf=iTgRrQsB&51r zy;A=G@wFT;s)gm@asUPWc(UI)#XK*B+(zvf$=7F{Q@P1{0`b%mBg zS=KL!x61x78#J7S{VDYK$hNbu4F!_5+AI0-a7ewYUFRB&ZN`RhyRPG07oMcR)9HGt zrJTx=k8($QTEv%2JD&M zI?4+a)IY?X*`{3v$8|{6(ADBuIro>NnHGl}&Uewe{RT>P=`nFg;5sex^3+tghnjU@ zI|YFu%8H_tk0VN%ceYCbZu8!MyUh?5CKux-)nHzc8c2!Pz_L;Q%-9TpX7xjvo; z%c)Vpf|>i1JD{x`VeyyzDA^a|>6D)2UvR4cow_6NeP(fg7xI&ryq5w0rhH2xl`YgPOi%1pJOVfcPse{F zCxa!w!_C>{tBxKdbMolnpYKAG676qrD{~Q7NkJ41pT~v1Dl#e50pJ-ZQ*%o?7m*6v z3CiK0nvVi_Lj-n9^>UgRq++Ir!XpQNQ`Wk;qj4n9>wniu>)^}XrN;{NObS-{RX+*a z;#6Q-UCXjfo%M#Up2_cjwn1|D&yv$b*DnaJ_Wfd?c7jt&>CgZ8P#9Axrs2Z}4 z9loNsq~&w>@d{3qHnUEr-gSHrV~x{jL`!tn;kESx>jy8tYM^h$qJ}b7mt(&))ljwL zzqyJWLlX#*d=`bSm{9X<$WV=KNve)q9FgkQ7?fZ2_w6G4pt8E_o$Qy)(x`?+U*Wma zNgG%9W$s6TQU!>6K%V%|dAm(wI>)MZJ0A(E{u6g6_2;s|KjYWPhAQSjwvZxqn8ma2nH#9s93fKYt`1-Mj9X_qGz$Vxq>~=63wzv2Qx`4GF6x-(Oy~UpXV!MrYv=iB8?q0PQYGT>dQNM z&~_QKxQc_^7OuN%ML(<3-4D&CD^vT9tH~^Mli*MoeaIQJVc&N%}{r z6Z4s@V^d@i&+VIt00+=KbJ6VfIR}VTg80`# z#d1U7&&CfCR+v3`ia!UhanS z9UctzcIn2dg_m)4tT1vYE2Xo41?2}K@vLCu8oz33Y!k|zOSR$T&^7I~3-fi7fCz@} z+P*ak`{n4>Nm3QE`*;AhP3sL~6Qv-OTkuTX(H+oR)_3dSUr}w5?S6T`BB8PYoMK8! zuV;J#xA<_=QRF4)Akh6@6{e)*+I*ZEZjo5PyG^%cmHlo@VRXe7E3&nik6G z$pa7XM-~L{&! z-avzf6i{J`Qaqsl!|RNVj9FTxJKR``M0Vj+*E)O_%r7b41DVyD77Bn9k>lj};A$?r z+*NGji<%5oCs^qFZ+vngNsnpBB$)3Y!*>Qrn7miMz(esM<%%D;3_8ygjpen2r)cYc zRWR}wGaX#@Tk81D{pC(n11i)-1Br}BNYLk^=TwSSwQ6ZVbCEi7m}~Lv=XWL z3XY##O}W4O8=rSvCt@mdFl6Lv4#EPUEra%|?&%J$eq`%ViYui)gPp1f$&)O@W15-~ zp-=PP$%v5bk=T*L=%VGgrmj^Xt?iRuEem`b04|~DgpN_oNT~0Vyci{QkT)j7{RCG* zw5kf~#FdM_9I^z}J=W4J!VW*iU`GDC?Pi=Ua|&#;wsvTaX_M#M0`82v+zp4#1*w`o z6MlF4N6tIXAL%JWRrif=%X&|ndm`)B=Rg>wJPsORf68xK4_w0|AjiIv0I?YV#JeZf z4z8`QcU=lsFJ+Qb*iRh>^4C11xA3I-L--cbNQ<6Bdlz<-`qC%Ct1WCcakPA;7mIDw zr}Ul7V*BOyA_pfuK1$05eQaGN+wRny?=}`{nxOPDJ;?6(z!@NDnW$osDe%Q@(k`awHc|-aF6XRnW&+j2=6#8yds4w%;axAcZT z1=6ZeCC}yYMo4IjaglrzFQEM7aa`(h!JTQgx9TU}1~5dCL2^LO>4$*obO+Vdd6MU8 zY%jk3Jn!iP29n@Ybu&!%(W-@A$+Nw$aElt%hf&+%CkpgTl^XYc3Ttzgcw!=L>F@j< z#vcV7VKnPJB`X_m@2JeMW{4XDrt}Fy|IOSjFJ7dHI`>hYJ?%_c5$7-ukl+UzDRr;Q zUwd|LqspLC^1ma74{ROjVLu;Z`Nrr}@0#i*;IzW$P12^eI-bjO?G)Zs z%)_cL`&k5$oYQ_EesJ5yvI+;z|DSQM3&OrGW~pV?t0!%6p?ci^!6)g>L%MzWcvl0O zf8(mhn}(y6KYv5ix`xqF%0AB{`qrio9K3uj8u#gv#0Bj8eL8)e>|0097pt)qbEmN@ z>o!ex77Jbrjo3eEUkGVq%rgXILP~EOiD9ecqll0y#3OxJS!gcGc*Sz{GdTaooLe== zuN+fX^?dXI!f?&Q+X&jXyraQr(6TEWzLWA5P9K3bfR$BPW8Qd$2=Nv!fe;fGU-9ra za}O5o-pdfoR^PPBtE}lAh{M5-EH3v}S|2UHR|6}i5;QTDAyZ=wJgpfo?FlZDg zK||%HLO{fTl+ErYS+m(J+0BKEEK9N>37cfyY!DQw7_r19R=iZa)QZ)5Yqj25ZLM!$ z6~wAVv{qYr>sxJ$imzY=FRwQ5?|aTO^UU-7pS@&v0sVaXhyCaLXXcq_&Y3f3&YU@O zM((eue_AGn7%a%#3sJ#S9gsJFSoP!wh?hkFGti#tlfE=hzd_f=Eac=^iRi37KGZLS z-E=M9y1NH9txzl)jZ|b3eUynA>k17+wx&%jP!^*uBd8(tUZu%h%`S7x%!3|Icc#Qn zyy??dR)z2&jJEr=Y!O9m%-V8bO{X6=32IMK>+!2b!%VAnFG$_41}H8W+ps!SfVi%U z9Pfwt-$_AlNK3m6t8M0;X=@0(7g#fwq7Y#Vd%H^dgocgM&zhnSv^FovEkkcJ#8dZa zZ5p|P^J1aiyk>Q~e4E*AH38Tt5w=Cg&zBKAHLDcuh*eW9jEHmeVG=f3F17GF7e#-0Q+6G$T?Z7Uhe`X0qbBcIx2Z? z_zlvpQ-+y#bF*)1cVUUTk0OmLP1I_DD%?VLp)zbrj4j(OPeW|M75F?0TUT0Bw`UB_ z&k?2>Ddh90xrsFpN{3eIAac0y^DV+W?!F($e2J=+Y7mcbnQv%I<}k@%*lY!Cl`UPx z_65083y;S1S}mx!SB%T5N!x!N9;~Q#iITt1%r7l=?Xs?c-d>aUh1g3?dGMuH#R|Lj z^cWb)0*l*K9>oXRbRzD8+LfyN3N__1sC(&B*i>VGliu;%Y`&V}_M@Bcyn44@a+O|k z?_)zGN2F<2-!9y1=_H9k-(u8UrG+b2WRmNYYY*-9`<$C9B0Qs2sMlgMiwoj(dzb-@ttvTzj@=KC@^PKC`qlni2V6hm>K zsITC8Szgx4WUZH}*=4TQleEX2 zUThG~)y6<)FB^RwJ%d{tH*Z$|LdFYJo6CuoHSWoNo>F#f+q_LWUOy`D=55z7dhHzD zxyoZ|ZO1H7@)t#Lpv~KgFHz^6Sm)@;;I!|ze1_#uYT0MXVs9PaB-3BrJZVDddDP(Y zOeS-^pS=syIPi1fBE33C7drDSnjG)3a1;HTrPH|m#CIG2Lcg7DZBekULwsjm50Z!U zMO8c#o9EA=-3UkI;7rt7h=HP6O1 z!}$)a46@)Vnyk$>(=6fSx}!x&>@=%X?#LXrY3P)L=)`QG6wWikyqp&xHhX?xz?JZm zFxbZEg|y&(m<<~H`?IU~(DU+lC_L3}&5}Q&i0of@L1 zxS2lF$1sI8Xt>x!lRJ=<=1p8Xo+qK=8Yr>xdmenVAlX?I5V${WUomn&wEsAx z$Ig#2p_(!BVoAf!PPW7_ub8SasAg+cl(K+4vR&po>z$S)Ja`rcZrgr5pg;UnO93Sr%_T=@sl&2 z*hsU8GA*0=B}J)27b1OImj}rkhg++L8=bLEPYb8?+>CF+u?SeshDDQ+xBeKNZy8;r z=bP8-h7?~^N(ZsdYg2N56D8|=^rIIkwc=Q^+tgp6Ox=xw@1GpS@gX8e8==PZ6B+t^ z6_8+Gy&7`JQr&6e?rf!+idYxjhn_yuD%Ii2Y!<^2fGFFv=72QkxXYVwnKhcWa+*D8 zYOiQobUd)W;a*XjP`FwYN}ils(w6V(>s!4?_i&q(|E?B?dBk2A%WqtR<)5WmHriZ7 zYuR;@@>FL<1N)Oyvgm^VC%s2?uQYSFPI#CYvUW#XcM^FR(Y#wP@M~lkLLq`0)t{*6 zT5S_E+$c-xTU6eVL{{0t48lE4gbNC1a^IKBp^+}_X{>eY(FJ-9i*bc?*CCJkg?_%y zC&+T)icURMSmGCpaz7^{X)naK+qL86FwAyelKC_EpCtx)W{WQ>tJN>1FCB<9F8QRg z^N5Z*p*g7SvTd7ooAP~^PTVwA-W_T?Z?}(_t&2$G)ugtE^c=P1k47ur)VEE>cgor! zgi}=hzER6>-Lvy*D@w<|6Z|t{v_hk2?o+VsJCx%=oid>kj`>Tg^EcFLCPa&O>+OdmT02tXp3&*G_7eC1kLFdsiyOYbId2`&!NV%i#9Onk3e2Y5 zm^-|~ho_@pvpUoREiRZ*cQ@K_yh(MMI>;0rlwDr-9N{IN_8CaN5n%>GRKHRW>59E| z8_hugq;H^Un)K{aM>o?oht;da8An6EPK8=Kb(E;rb(qU}C|;k{-YPHTfRlxTWuRV0 z8C}mNKhstnJ`D@E?B-$b_Mm3DhjKF@bjNjg!?^tiLk%ML6Oi?hfp9-*LVi|u91*mhzR;k#7) zCqt^au-Kf?D&rHoV&+=_VV|LgT+h%g3LPj1yFw0xe zO<0y`e?d1xwde*p-KHg$QN$}_h#Oc!mba2vg3Y0oilyo2M==RW4rf8UV9z6zFHVw* zu<_zc2o85z`GIm%cY3X4lyA zk%6etc=OAu&&ZS+ZP2;DpfnN0=-ScJWZn6B-kVv5mbF~cD*sGI96R~4{As_ht(CdP z1Lc}C$Z-%bJ@p=r4XkT-L1vw2?Usk3}vFT?&$Iudk+q-{keKUZv@Oom77GC=7COkzBOMp_uJg zwmE&<;cdI-7@<&c?UqsJMitLob8`LL7GJ+nHcCC^9l8h~ST(rzsn2uKlC~V@brHx!vhh?fM<4klA zcs9}}%Aa;Utl^V|tup(T6c|Rv6;vA*Ai29av}B)DO-Z*(S=e356N-gvcA1O4Ocq%a z{MRYP&1-d$qqFT0| z=}z0#c-`grFO#Dk5DZ3TGVuF^CJDUJ%D&6 zF2djsCAxJxyBQC7!|hDmSWc4ABQ|n>M~`Qcu53Dm+m2Os9U zP|VyD6;%J9iD51K#Y)oGT4eO}Q(qOAzWcg`YZG;#TRb18ASB>JN>x&)x=V*y8kwM+D2Hd z{cYV>b5u1Ku)esg9NT83&0x8KLI)=mhs2lHB zs_35k7NgjINcEMKi|+OK3!^@~*II6rlK3cW-8y%jC<&3oPjXRr3}Rwk8x}pWdza+Y zTAN(orP`cu+yLYj1mC(wn|+};Jfmwq_s4q_pzLjWz9YuLA%%NP@@q`syEiMv=UAGzj%1y7t5;;P(~W<$(}hWKzo$#ZwdZdM zKT%b~DI5sP){z}6(_IHzocL66hB74jC2Dl!o-2#>kJb2Wo{*J)or>B* zGg`2HwmMb$A}`)`;krTiWU;*{SoE~tBy)$t(r_Zy;}V@C+RNrDJy;vNnWq^nj)Y#m zu|o$nn%8hz1MWRhm9aFFffgf*R`JmDckC6>ESUrvL2+R}B;H?oyoyTRDoPM%xDYu{ zQ;J_5C=-=$l-ZJ(>r$fuH@|7Yw5O*i&jqnNq-J|@FxddT$LM3u2pjl5JyhN7Vp~*4 zt2*;N0PO({*rulIHpvl`81FXL=BWTjYZO*h=hv5Od1DJv^MSL*z5;PT3GF~8XZ)=8 zxippqIJJLHs!?vjaS5&UP|a>+(y7FihTpevq=IYHh3i=+2Y;msqVhp(rf_a$bR@g!s8F!zObV{OW700s5A_ZnxvTcUgYPfB=KuX&EVnVTvR|b#vymG{fU>tpS)sqDjw{TM2pY|>WFW|WkGKa5dZq<%NJC|X} zB!M>aQ9V~ot`0Z2#CYc;DxGa=B1ywOC1m)Zva}pAohqL~O{%e#FNYffPT4?zKkrQS zKNHheD^m#tIyNDW(~ z?(MXopa?wQ=e2j(`=V3%S=N5|EifD7j1ziNnzMkOKKH%F(lXB*Xrr|ljQK}O&reH9 zrh(O)NGzlx>E58!B=(H_>iV+l!9nImdx0yZc|d+t#8+BQ5W>qFTidvwhHWe9pKc00 zR@1c@qg|1H{baJ4H;rz;uczJ6h;x}yr^2VokHKB1vSx{CTfw3S3A=|R_%XS36fJDG z(yN3v11xMVVmP@Z>@R}JpGdx@)`a}xD4VyTXTR#r4Ycwnf}T5+*v@Uh%xqQ^C83X^ zx)jI`#F;(`i{<=9S8gRm{^DV#f3DKDkj6;Ly(VP7A0dVt8q8J+=#bB;f{FC zm8vq==*}|2&zHqm111DDftXjQm^mioC%H}cAi5j!QT9NJU7}(jCSlXkCF5D!3iqe{ ztjL%6Q>Gh}jL`M=m_p5I*1x2sIjmM#XZ=8$n>GWRTQ&D)+ksT(M*_?DjnjlKT54A` z=0+KM@FGih(`vrpKfhnQ(rQ zTH}&3je|{hUdXHRdB8e_TgCBFb9KR&d+_E`+ONrERR}|B9Lup*gk65J6^{KX>Vd@> zT)q3M8dt!b&6~2xL)-qE$dV1AYynG<1LOyxyA|XE-Gj#5(b=gGS|ePNY*sB8>ocfWO=}Pht`Ny71erm@@R>?l<}&GIRgEyt+<#gA;}zt) zCGgwtT7lIqjhzyU$iQV=(%I$-VHOBrkRD+4EWF_yG;;_;< zK_#4j*K{rkCwQ>(VWl3`NKJ=4O@&|-9T)AC>lrRKaMhP3ax4tETvN;+-xl)El(yAj zrcw#U(^Tv!@!eybB?gsCC6KaZ8mErBbn2FgBy+QYTotxA8@5O5HQ|t7W`mrgB%c@3 zTY`*Fk5HDDp1QxFqj??UR0GZiv~9M{RIU(aZOV2+qVN*b!o5M>8|Mj&BAp*#XBbT5 z%@qR1?Q1t`F>FCoLU>zK3pqKFIb|6kFVy(ZI!RPt>;B^h#eVXh(HSg-A2ns0Nleyp zHB-^`gfw#DS1Ki&UC+REb{n~=_rFkDxT>{ljV{pa(yy?I#zT*)K;ZbmGiWa9jADul z{r(Uc77Zpu7-v%a5${MZgQK(VSEBr!- zy<9VeGWZQW!u{l#V2LIXTPNBe2^t?O;;&PhyWIU6ps(qtIxb!=%BaoUQZ!AO5!DXO znc?ts5!k0H_x>pzDfzPMa@$@SQ;tdO@BJ-FB+T9c`9!j|zDHTPKXbvogeM1C_tg6{ z!!pF2%jTF;ToQ)h=auXB+ql0)SA5WC+ZcYqm#c(sHNIzhDBNmFpHxUO8_u6`tRzS` ztZZ#sxq6jx;om5go=2UQ#nMj($4<#q76pZGWJ58#6#z{CO3(SdljtFF-3>0EB&&E0 zAPn>gC5zu%?Y>xo=b>?agtAx*v`M8XaSAS=Fnwk%_zUHD_tf7(ZKP3taj8M-pDEQu z8K=)|VImx7w$<4DHD8?@QHb|lEyL9qUGF5tugGVpUExlYa}6iSiQ(CFbheDqe6S) zslbYKNNM4&Lc8t694%x`$rSP{B$>5hPmVq7*Z?g5HDzRsi~CDFye;uhJO6-sU8MWQ zm6mLZ?kVetwA1CbJ}LPHx~t@kdQO3Gm~L_}3p(p%%5eLU?9l!;ma71$I{!|QU_QYdM{Q+8W3!QjkHTi9o>nVS}#$t!b(;O zjam$O~GOkr*nvBG8}sB@7uz&wewBYvhLp}Rh)I;?5z z4f?sLY#6xK$v9u5$5|25)6(h1NyheV)G9|wd3|aOMwNS2r%W``PvsqhG+3olgvV!M z);s(rKD1$R4TghM9VW`h3Tlnw+S;r!x1LimG|`-$m|?F_hv~KrlWog*W`jcQZ_su( z{PTMxr21VJfBS_>m(-AGT&;r=CYbu7qGC#Zrl7#^=TS*;QLx2D>fCN);%thU>MOLn8TiaxduQy25S}7T*brI9`0SWkMAJ8y_4W6q9O5s9$-F=#6XwuQ>wM{s4 z2(aK$rz%TEJwxc3f-@gG_CQ?Gu&y{!Iq5L8QGbiiJkne3Xlj0L&UN@llBw{KoayRH@mo&M=ROfCf%JyjwDHZ?w7A4PMJLLu$!wK zbsHX0n&Eu}*kD8@Z<7*cs#E5(8*a^lyN}R=rckp4s5l7BOu}*NI2l;2@cay=wsjQZ z8dLb7=829ZRDoAE)$Xh((2hsHBZ~K*sP$KIt}({`zpB#Q zT->#My+oCD`h&E~oyg5!%dJyX^dICfg5qNk%(8(CkN45Y09S8AHJZ(A(6dp_@22?Y?=|3-8lY8lh(TurWl} zyi5Vr)*}yNxU<=zn@EuN#9NX>Sh_0R2Wn2%byzb*}ZGRE5|FkJ%CcY19a%|@~E>hFR-!7FC zo(iEPCp$A2x#8eurHk2Dw5+k!6^!#5s!Zpgb2ga3eWlV(6VBE#i8nDa<9cOl;gJSp zXsm(s%c#!=iQ8>WtrKzclRy$#wLRt-!6<$y<#StO#`dn|I61I@F&!2`NVu){1d7I*) zV0XCa7?Si*Q$ZMNs^q=h$|wYqV~oF8iL9i}+M?FTOVGTg)$baSwv4`1MU0ZtDC%yf zL3nXjL)idglScXNn#fHUz^(lCV z^pVWytNpWJ#A5lx8rznlbxUmc=~U%vEm?V=k-w7r8Oj3OC~=f!NFz&c<~6a9ta{{= zXn{%@s!!4b*hU$puA|bvBb4f+Be9*CBtj2hc{keXjy~sF1`$3R?XHXcN@@<#uk8Ga z2U7B#(32#x%*HLQT5Xcy=PK%9f5VouZ+1VbbX|y(mo%gDwi|c9^!+Vs^)R!zgwSz?)?&e9Y zpV46H;Rek!@r0~}^@A#(SE=rf)yvy$=-)|g@;T5Bsax$4AqSLL?>0})-`T^3mupGD zF7w|iC+zi_aQHb3izd5Sx|I{lg*hBX`IMeu7d#xEXB>V~X;DOF$Fz+57`>lRX0{XF z)3aXmjEFmBDNx^@|{f%+vif;jXO^Or*h0i&c9HYwQ9i9ciis4Q;3VMMVh2N@5 zb2UiQ5&jYcW8}V9C=j$CB>$UKSnWg-vU@0TXo%dZ||W6&IhwQ&?2B>{_!-E;hV+9}g96Rgn7+lx%0%F>wD((l1I`&F;({ic(*_YY z1(tDnjh-~m1xHbnxsBGHN@j*E7#Psaw(}+*N$ElpO`+GGDTb6}+-_2O-1bb4&|zRY zjp|0-STsUn= z+z2k!gWat=sGF_c8ftQSl1pcy~!LGQBBt9%eW-^H$%y&Z02D zHy0>XQ{hS#Y|Lx}5bJyjqh)wVaz=2A*?LZ;e954aZ~al?!Tm}-4z*pJ83qd!t%5w5sqgm(WesetB^_&CY7=%?);&_h`~ zh1MPYe$9`^tzdTiwsJMEnOy&8!J55%0|-TZ9d6@xWg61qpOh`V0fS9$DDe&Y`;?_Q zvt>_ zAmf2W@#2S&eiNLih>>Md~(dzlxhtl7ef-nNmR;Z zt4yP`$y@Kry%RKA!(lF%=;eB&E0*TlX(JegbC1$PRH0?&7NWBiCI(;{t`gETr}?mW zkI=J~*Kai@-c^_lTC`izCW;k(tuV`9O>tZOh@_eKtnF}}Y5L1nWw**8*xWPNVM_GEk(zpr#h5~EOIYu7q{(7 zW2Le}ZO=?q@$4XirR z?85hG$qbxam&nKUtTRm2JDun14iKa2o$3u?*^nHk21RZs(KwD9jXIml5I9N6PP*bL zFeBhIcu#7{&_i%T$#2^#`W=-GmYJXqNQrja($lD@-gAtBynrk@r>xQjC8xb@oWY0d zG{U7&>1yW|7e3&36s0x3Tqh2SIQ5(=okXfb;R*+9>=|Mj4^bdPh1icCoTF;uZHlJ8 z5q{7;4cKv&#N{L-E6Br?VSU-As5<5c=o_+PdfQ9k-7BO_mht&9$GPE*lkz0TSg4T^`9yv_V;6; z(iV*&{@tF8+NWN-KA)_Vx5@$;esnj|xWW2Y{lwAG@z46*qY6(7^(2paso3hJ7= zU7K_;rcFojO_m-YF&uSV+k2Ju#9u4X+|LN4Z6paxB?jbSYD?5{^G%sX+wS><9&KuD zH6ieE!I|2cnDJ4luwI(A9qVPIO&0G{Qg#-{)x~#{AV*V)tZZcoao(lWL_`>EpOcOs zQ)%3ss*vgE`m6x$Jqfax|93r|uQdrL$HRJA3--gD{-m z%CVI^{Q@(PpH2f{2&pDwgq96BH)C0FC+N#GGNdJIb!Yb&xDrw&_Ve&}(M7@(q-Uu9 zm949rJYd1`_O|G<>1OiG<>6TJe1*GK*J1mGKMIzaECkQd^yJKIuHtKST*-sZKV8qd zl{+&KxL4O+#=BHk? z97K_Pgr;OD>gWrhPLd4`)aZxpZIvFaaQ%-A7x{JCc(ULg?w<4j88UiD!%(seMDzOh_v}E zk5tUYP=xE@OZq5t5Zi)5_bi=U@SsqCr2_em7k}kAFF8fRK-G?Df@MvTMi=Oul}Gi<7p`A2ZOsd?dl^$n@tukQJ*jnbP5~3w{rO>1?Ca}35;QI)g8As(Onu1iB!Y(3Q7v2**rz8oU#&O3 zK#%0kOxUa_n?`a$G!}uVpYBRzXOm54e<|GDrCgh<(bWAi z8`^S_k`>Kqi|#wt#W>e`NT>5C>qg2nWMK4QK-|B8G9%AgjN#{5H3Z4A)j=2qEUwQ` zmJYbGJsT%3c>mcvVnoBvhLn^$SnQiql*^t7#um)mI>PQ@0iUUidS4P_`ig5KOzIr3 zMD_HLHb5pF)iS|l7aHDX_pyD!A>uhuDh{@iF`hfz+85BX=e7O z;chI-EWoC69I2w=j^cIgI1)uqH!N0jlr8@8brO@w5lT=Kefen{$$*Zpu8I| zOxL6CM6tFd-dP!!kkgdQF~yEfoj&pFCtbne-d8oDa3;)is@7~yoL}mhjqJnVu#`CO z;W_dPe^`AI6u`-&OF%$^JrTi46c4#pK%2|!;%=kZaOfv#e@8^X8Iv@;y-7L zKc>)(YnYZY=UOAlf2g4IYhjNu3xEDy<%-rJI0mo$PJCr@-Wn$5LW($17g(EI4y&5h zp9fjWZb)ameObA1yfJmRRAv%~IDoyjXRxM}>5{~b_f)c<_dd$cqm`KA$To;LoSN)#N`NaRMr9@WZe6^reRC6N%YGOdu5kFz(sil!Y;3K3-z-pNl25$C^(K zw&GYMy@*?I&o!C#66*3LZl-9s-5Acs0mEn1#Q%cXxo&}FUGJA4HZAA=H`A-p-aoD5 zXp-P2ji~b~)hW^8+A$UfsgX7^=UY@}%>PuDh`tyAS2 zotm)3dSzvXS2La}z+7hQT+=A;5^Zx3teCm(B9&BI+%3o>FTxRR)Lk$Q_{pRfk&Z%? z8N+;6dIhOAU?j1$9aRhthhcBIO4v+lrd{awLY|2tTNPSYoTpSZN9ht4Noap${5i^# z`J_rK$DToeUWl36>RcUJ!mG02jatxPP}2l>qGc*apcuSyUn5Y!$-M zP-}Ua+E>}iz)glT$!X(Cqf(Fpy5&V?-buo98ma8sboSgH67%cYJ)`=sSekKk#Fv6L z(aoUlJS{(;-njXiZM%Crbo`%h}*9#lG&JwCRw(QbXXu`2i`)sQTUOM0Zuv@u%s4eF>e zC?Fm5zM z!5&qT-B8(rZOOug!Esxa5e9N;W$n$uYnlRjug!Ru>$g04bv^@3F4Hg3p}aEoLvAA; zG`AtMQIB@ZDAH&fA|_qBNNQTUMnNEL63}{0YK*SVRk%dMl1c9Z6>96y3Gens#f!`Y z08>r49oDW6P%u&L8=F#ct{xDFNQccfl{7`b?Pn|9Zom__!@y_p_FV7SV0y=MNX(X8 znO!nJKP9PWo~LlcDJrPV_6`E-axpb&ziVmMnJT&3-onMWc}xfScEIS1l-SzArCrQ`NmMoG^H^q_yQ^k zAY&T`|1G8oueR-gpIE}S{$BGp=0)FSE1sYU9xTG<*Y8;=-R8r&%Ff;xkDe8Xc`JAK z5yy?kzor4Mn3Ua}&2|*IcAnDSiupJX(_L^sOM`M3G6=y<{TMOLr0EC0?$OUo0)$Xv z_TIaczK*#@x1!1pX|%YLM{!6?Rw^#7Slv|{(0noWS)OD9FWf=|evgPenJmEUVnige zyh&QyUVuA4leZF+w=^|(z5i>a>uDhnZtlio%DCdgyi)Qv3Dm{(`ava^jzDCVY4^Wo zeu@r<$WaAJh$ud*2X=9Tjg7c|B#F6OO(peV3``rX>N$Io;Cz;Iy5<5R=|z* zgpy!#!%aYvXoVl1qG|1xl1`b7?$wrx=AFfG^q%K<6XUXUx(S)TQdzc_JBt0?Wo#2A z>9D+`GFb@~%vaL7eobpL2kbZ)Y18=m)~4cG%@mWR%IH*VZaHSP^<1}WkKfq#e5Hy@ zSX(84B!R!Lw)@6W>RF)Y@(m&Zp?mFd)^I<6(ZMkt$J zfDT|*vY;3E_+m;UM4&{)8r{%gt8|l#KXZ8nJD5hoT(~*ETf&<7pvq*9bW4|Jp;mTo zlP|LAR}WaVppaUIq0xUR$5x2MQp#4sTHz@mf^{^(`^6qodI?T|iO1~*%rN*lr?U*r zQjCV$o^C&t2H$)`T_6jlCR4ND{;M9#cJVB5#Yk~q7_8hJ<-4{kH3T5ex0F&Cl#Kar zC{csboFuai<7hC%^mwoGIfNds*IU<~U9^WANG!^*|9cfhPBn8ej7Q%B) zgA$Q#Dyb*DNw5#7AXArAEJgT3V*II6GLklH&d&6E-gA4b)UIQN%WtD_Q?g#q7K`Rr z^JZn%D^8ip)V<8TJ;6$Q5__nOhY}xYC zrc+<7=}mqnxDA4_@1z7+8adM#A0Cg`<_0Y=mO%-b9#CTHIK?uJgq+?DMg_U)1- zTKky026S=LZc9NH+ca)bu~~`TB!;vdOj{pjdq#+gBWTaHJNmlqE;f@=GnHhl=>eDF zlxa%RUP#KTNT7r0gl;p^(8kG8Y-4r&LOJ1VjwBs}_UB5fnrFMwEm|7et#>%-p53q% zj=H`4UG7H#KSG$*XNvj=cQWPF!;Ju$7hSJ%BNBl}yjnxeD5WT`9H$dfW#rT43|ILdRb zDPdn!zOQ{HYvsuAig2CxfeFxImF0%kO+Ec`ht<&|#>7Zb-fV6m7FQ`07mWRBC3h2k z(WQaS|DRNv1%h6qO}jS5vXCz`!{lR14-PW8A*Z6R z_o|Df61ralF(9h;@!d}LTB{N1qc0MLKd<8S zAx+Ao-cs96oznM8)VK2ib2h09;s!z0-a157v+myH3{C(qD<8ZxO3-#*qXWefv zU$-PgGj=t?Ro81jW{9}hD9p(`89X%(*jJ^;{T7$!DP2L*Y$bt|#0D!?O4pQ7=?o<& zv0xaa&sGw)<7{ajgF~9qe~L0&J7m>q-nHk-zFrdx$9mP?lUNMQvhy^h2I-VU$1?Tj z$;!igZ(?$LZ1@*#4^xh7wJ zY1v$Ovi|H$qku4vDw#2sg!6~mB(W|$3*OiYiZlcxm8d3fX%GfZf8T8u%TxuDKO=cp>Z&rSXTL)#!%KgWjP-~6*Zlp}{zCT3L?<<+fZDlsQF(# zC(WcBmSCXSdg)QM=`(*lecER)+Wzb94f~rH&+FJe^P>G-ix+I4zV@!>#mCk3b^Kuu zsD|3!&n+6(DG2EaHAxleboxxw-%K%It#)`#7NOcB`dh5O@X;lWmQG&y@C?5n&Jvzi zJ4!~0{KGSpq1G;;nSDBYcW#b19kzDJvOjbFL)zGSa;=|>(nb3@^QrbnO2e;P8FS|U z>fphH)8zotuUpi62PMy@tGUpF<{w_$m_c)Dmt~M>qEBeTG;2sGz5S)@GeXR|>A;>7 zw)dnz=WRbN{W)#;Q{XU6i%!ws0yQTiEdzwS@9VM zQgkn<%?rgWGyI^|tDw4nn!@!9-QfbkrAu37>XK1;=KRen_vG5()@{4hQ_e@rl++)J zs?ePIGy*+xOMfH?xHv(8oFG6>kkFFpO5x+B_dYmx{m{dEZ#cO&xc1;Nq<`zcH@7VO zw*1CwwSWE2qn|tM<>wBqU+}HV7j7AP5{TYT#@4s3)tmRVEuK5ne)7Jq#Yb+RtzkNM zU$YXXtv%Q=_wv1;n>&2}H|-x#U!lL5#^=iog1?YYGiysCv`i2w@EoI}K`KP8c9;#} zxwUfyA8BLcC>tL~>f%-EpiPhHuUUWLvq=Coq#x>~EPt2kubl53o(qE3Qb$rJb@0~f zl8)4o)}6cM%Y{?d>Z=BpE?qXTt7r43p522T1HHQyXt>w<^xC{n=c)I8Dp7Egk~Y~f z%OD4!z~QxP^$j$qc6|oT){YXNfBZcq)M&{J0BetzJ-l{*uH0)RO4zAv|X<*mx-c7r9Zr-ik#bwuNvjet%c}{|WFb#H&1#*rB za*hozRf2uIw1)7k;VqUx0s4Ni{qnEFGI+E>s$&mfG+62I4E3Fx%OtiLd$cLBXlL(B zO@NcXM}OVjTXqfX=;|S}Ia=XuVIX?xp*!wYiCXRM3wcXz_@#WS zM*5PGQr(x#wdD`bP)F#?vPj|?%24Yic?C`RAqZXLoBy!BTd#7NND4^OKt~eDkpyxi z^LiakFMZLzxr>M9E6RRU>stXVCziqwgn~JYHzI<*w!( zMlAgsKGc;W|AJCi^9*GWN+TiP*~8iUGTujP*fN82h^s#<5@_*i)#kJSa#{f8wBVio z=3325+f+1fsBQ7A)8?-q>TfvqB@IfNx39lp-@MC*Z?*FN46-=y{^pod(*oW4$dM@dJT+#tK!2(({tRc0UmmX?t7&>D8_S)79mmQ@m+8$8x z+7K3_D+5KXPRj~)ucT&6iWV`DY!v@ z{QJx#JqO9nN8Yj$X{nQE(YLmYK-!W3H7?w&vZc`n5>7xMCm@g$FmF7stJS=8B;W%Q zf4oHP-(BCYr zz8a($@>3$JA(Pl9t&WreJ@?r44f~GVx4ywNyf3I`*GZ)d{|Nw;1F2?b8h zA3)9@Ku$s4h47Xg`_rs+qwbvO@8T(g==+vy>N>5aSuj@OMi!Ge~MQ z?~$^i1(G2+RL>H$%vl1+Spvur%G9$CFw%M`fQHe0l_|-{(TGxWwQ*Z1^o+pML|=H|Y>H9PyRi{k}cy)%HkGl0A^LaS#eg^!ocNpIa$%JHOP8>6G`*WXe-axgf7wBW#o2 z+v+F1ric@e;{@b51=dLa^{I=&>Cott9Z%--`E;wu-&vqc$D}B;d=$!b=aSmCIAPjI zo??6tqtWtwP75M=b)*rH(+Fr}8X217{5mvxRw)lUGnDnkq6jUH(8&=xJwnXA{@$}A zbZ&&skC1@6-g9c8y5Drq;C$H_=I%Xk@U1`l#ohzcj{3+Q!`LCTgD_hK=Iw7hK^cPP zo_EJ9X3jWp-W}Kfa`;kF&Og&kTbrTXx0UfQ|sWxhv)DrTvw(h`qRQQB*X*fMvw4)*SdGa|%s5d-9`2IQ<38V`be zy!7`0Ri?I6Ukhq|fR*}TuoRA_A3nDL{F^dIS)zt#5L1sX2v4LThApzNjPml8Flq5al`1VGXr5yQtbnSq^3stp->HHNYVXkZ=!^;5q@NV4 z(HQg0T*j~O-mQacn|Ac8dz>*@d+g?9dcG9dy(A7e8s&HaIbJ|T9`t#uW+)t98)}?8 z{9!|e#!hx`WKC(rfixew`e`|0$w4ESFg;&nOu0x(wu>q@N4N;3#Fw<#F$Ho=fgIED zR(rL+yfm2p?UZ5q2cuwmd{OaUuVBo(2F`D)+YZx`L$R87_O6vVZeVjtg|W)&;8jYY z1$tqLLSX6?0&)rgRa3}IYvg_@5@@m`x1~g&Fxp|i)kOkThCELM)9=-zM>`!oAV&|V zdI)=I7~uYh-(*?6r-WZ%I`F))(jd%KWP>e^lP;tL!ccOb_dsJd&tG`Rm@ z%5WbV1^1T}`!+EDn5*lQAY-q^OH|rg&mmyzR047;0aa7UOS`^z{gcjXocoGd<`;5j zL+z{s`ubTyJB+Z?<%)#3M2o1Nv!AXUK7lX%SJ9gJh7ujaGt@fp_|dU-x0$Zoo`J+o z$f>PHGw%$lN!#K~138?yKu%mBCvHF^(9BB@H7*>U6(*la&osRy(giXz{61PF-NcMM z`TXbhFIMXSrJ>N%IjJI+9k zGf*|oURpa1`x@u&Yn->Qae-VF8jss2b?cop(mvD7xn{ZM zVt>Ph`x|bQYX6Z)r0GVOYGV~1fqX`j+OH~+Bs_z~@Wa)tGT}W)h|;24aJt!$4BnWR zzS!vW7dcB+tM8!fR0DFV0Xfw|vxD67(j}^WCQ>c16O?&Ge~a}OKB20KQtcy;Bh~(N zEUJOBQw_+e22@QoFD+4YPo&gjMbY&o{z^sB#~({dMPZfDC>Xv$(BQ~|0MR$jVN zSp9RP)6@&AffD6{U_e-XA^bQ5VO70csFzp=RCh_I1Wl(GkkbpOT4H(W(%Ac9q|{_3 zR@sUf#w!x*tCQoXY7&hmw(cEk0D`xZ4#-IdR6PK_bcu97kEENd*cy#=_dVXEyK5}c zfwz+m$VmrOO*$_v*>zYDF}2Cct{Y1uFjP!`eB&`ER}^3gp@IMdM<)%ClLn|-fO+Xk z0rr%DlZ;#V7Eh(NRrd1b0<3ziUoXENte0P4>Er@(asgG#FE3phgwKkE znymbKO^K^g`Ss&TaaA>iMiXEEcdRi8+D zFa?8x;|=6^16AYgrAwmwPNTv^t#i9d_@~p@r;A;j%-thyGFBa?(AgYaS%@)HXAjDT z-s%|*oSZB`P8J|1OK4*lgkF01YeonL{=_BN%_VY#S5WJRs@b79s~-Yo3UhuX!&I2# zK+-7#F3Wj!Q3 zgUmU9H^Qty!7x3Xt{kR&KBE8VT;+5TR2osG!=^o z&~V&=9Cx7VPWRGh9e6?GtfSs>V0d|?fuEbHcA?DAS_O*vzdRheeszfw;TdzkFHSev z0LhjVt-E#6qiG&}*JWp5dfnlz;v59+b!34YSs+I?yfd`YOAl|Lo%X3lFO20_EGy0L zfL@ui!!s03d`Y5tWK1vf3r2M&p~;RXkmCtd?9|W*FI^hRTWP+1JXBRR?qP(+!r3$Y z4!B=c>e}!Oqx$w@*CwO-5EQIbc}RQeEbIcb2LG(gp3-AhB7^BRwP#R)eZxc*qV z94xr-17rt8u&&P<%F@9B!G4@_oRG(KBteBy)t(|!jc zmJdURXQ=h#Q$fT!=i$U(w3?D2>XZa>N&-10Ljwbod1)wl(}BTR9Mv0|zi-~~Ya-Iq z?95y>o6XNCGTLDrT^$mNS?TbDlWm(5S-PwLx@4G*xyV~|YChCXR5ihGD;z2NV-cSt zWx*%|LQWMRrwWi$B{T|yGaoNK{KiOyiMy+HmGBRQ=QUe${G+eocm`NC>H$?+?Wh7d zsz8ovcz^iUOCQoudQT+FR1T%`9$es5hSI-IU?^3q?selS@jb2{Phjih1afi$RgWhx zT{oUS8A&$T@l-Zy2G(UfZO!?qX`JJ!M5Uawo>4KV?kNo_uyWD>Icb2Z2bGr|mQPsr znfRb83zfjkRJ=VW!Z-%ixbP1ni>5oiK#ng^^~mzlb*%6Y5&y}u!i!6yC1r)aoC@P) zh0=;8=Y*3hIN^~JCxDbw2FNJ`RLu!qdidLs5)lhcv(Z zGLmL0=a+J*k92-HXMAL;n>><34b_tes5q^FoK`?itI&i%D=%FqG3JEx?b%uiey zJ8u+;(Vvf&qQvlpd@c{RSSX_29lHjuQ|viEDm@_Cld4-Z+3^N)yn(9aftMa$81bCA zJSfMM45Vdd7|1b<@?bRN!f2;mjtr0^15`cQy|f}84xbY7ne1uUD@y$q1_;K}>x#S^ z>{Mg#f3@$~QCyk#siu-UB`7%RK#n?)qaK<8art=ZXoff^!Jj2UHQ$z(_g0g>sQlsV zNBKNjc!pYEK2iKfHc?a%0$iLBKu!oCCq!sZ*4&zxj)Z8MGD7IK%`nT`yTXeh#QLI= zJi?aX$XHh#524|XHIQQsRE@Ql9`1_RPJ9@b0}-afc(7s^7teS{Bg`T(QCJy<(MEWI!z-eu%?^2wuslaLMm0&KD?^O_oGNQEE5e0HYfvOSp(vf8cBEpk3 zAzocVJ7w9oR*&5=SO40*ADNF|TQA1I#z_I>qyVbsV=s*a|NVpt$l7WNRu+Rn3ZxS3 z)g>x~XQ=gWs;Mx}ord~7-@-0Kos>_GuY@51nob@dCl8R5Cp0yn<)tI1+>+2}a#Fs$ zkC$@FXC6x$jm%3G!~$O@7LXGQsG66&G`#fIgie#=rG2CD(*2X8Q)xMTx<_OjouQJ0 z1%aW!)oBFeGydz8FO^%_;0U%O_`q#&tP$P3y1;s$xDF);e1FGgKFAZ0H zJR#QPxT@@X5r#dr{=Z2PtF*Kp7gHtf-hrt=)ky^8Bm$~tDlZ+G>OexL$uZTfrO}r% z)vq6GI*rUz6$ArkCm4_u45*r?yfi%Z<%Cv~aS8P2{M>R|Q<4S&j>k;{xQkgcpQ=z4Y+U zrvR7oohIQG;F7FNa$F8!X+rxPDIiA*s2V9RJ$#s7w4m2dyn{MRGA%_aS(@ZXO?GWU zqa9Nq#}ud1Nxk#>Uk*Lxru+6S zZy0=Pb|c9V8-`xcvG(AGx!wC_UamceAu|gM%^sTHad2&er!}dDlhmG>wM!!OO#Svy zf-|h0b7c!6U@F007t|W{{nXl8eIH+2r|(5}A=dEGL5q2$phGPTt8oXw|Pr>qI==%@;QDa>tg*%Letgb59;<6~Y`| zyG^M;b87F%An(k8rt_nhA?{$X6&AWQs(N@9(WA6~NDpY1y>P-a)Y>&Jt4|%% zRv3F|uQM%>Gc8ax(|YMAnrSaE8bgQQ9;PivbV-@Eb6mX{xi@j;ABlURkaI7Pb1zUe z_j+l#ca>2VUYsoV>cXai%`)ZQw^S?4<1ql$v2=yq2PB-Oft;m*s#)4gN0z>HEIAB? zQaLOs9AvPOU*9rkSwRjDz5gO>FB?$IDIB)&X;NpenJ#^G-ZcujT zX%B5U>!2=an!E41hJDxTpX+sXT=U{Z@*P{0UDmX}ZSewK(YfgL%3Gkng-M>Sa9VJh zu5r>X=fY!k>)ZyvRBi18ygM5{7i*)uGhC{6-dzKq>b_LBJ62 zp$u%YHDQ2o*-~3X2eL|Gn@SA_rIgmD$KV@0`d!_E6PU_&TV|7QLE}|W&2f)KcF_4R5An$>^<-DtA zSRYg^t5?9HR=^ z4_bWE{5h7JkGit?gF2~$w{9PuLpytWuP#$0o!a|%?bedh{Yh4xB8IsdlPd)EhZ!T1RtjGn}5qgwM+<}q0Eu` zyLxm&boKUefp6iu?SLZ4L?b;BtI+;|${dC;v(!XZUVE>*$yXS=_ zU`W}`^)`XI!NO?*4QlS{Z{0a6QNY590^~#ia-xLR2cmfCphKC;@kBfW;fHaWOyBnA^XLeG_`MUkYQJ5#GbseejLzj#}>%31**o@ zOKY582-DT>wjR!Y3~NiM=GWlO5wt{=%+zLCt@d1lc%aPDNcZw*teL^*8EsGK!3L`L zPix^?y!?la|6fSpl{olxHOlb@a=d}Y9{=Huqy76`WxDWl2pQoJGQuHbghTYNL&yk+ z9@{JN^qT+qJkAvGeq|Uq^qa&J7sggKM`&e)+9K2up{@vRjL>Bfx*|fGBeXR_eG%%9 z&|rkFjnMTGdS!%O9if{ebW4O@AE7rz=(Y&4Mdq}BSA^aZq4!7V&m;7g5&A@gJ{6(Q zMCd?-J{O@cMCeNqx;H{!kI*+G^z8_JCqmzg&;t>AFhUPS=%*3-d4zryAr{gZ|3zq4 zgyu$QUW67zXkmnoi_oG7EsoI15js6WXGG}i2%Q_D^CNU&gqkC?GD2+;VtR2rx+1hO zLYGD8iU@6v(AEg`MW{bQB9$#R_}8`UTFvK(p=mQ@hL@H11)3iG!^$DG@GK4)`ZpNV z8j>_Xeg`eBeEK##L#+!AO_M1_flqX`>Z3m(;qxAl&wD^V?}dhlEcWrzkpj!7i~?mB ztngweFt8wdy9W_F^o!I=G)c6cYMOwS(*($A0#r>CFRfKydTwq*q|9W`&6T5k1mntF z5oJmOAe5*xW()*SbLs#&b%2~Yp?QI0ymW~=o2QIAWv8|9dZ_c|V+wqe`0I`A7gX(8 zTq4n!F{Z%JNd)910;(pFmoAa$B~wPCou#EjN}`_~I*F>?WJ?rE?un`%VxZ>~0&)rg zRa3}Imnd|@<3gcdJVq26GujmBIfa0nLO|6N^3qUf+@VH>BK@ZF{Az!R+rr_|Q#BUn z6gS+*Ga#$Et?q>DnEVE^PE8=ECXiDzG%^S+FJ0od+akRtE7HsRXX%I>=TYd=cpLLH zG>v!sfgFFJYHsqQWh?qb&k1H+8ty0qIm$qea(H=Yw3n9M=C2H2+FiCz%m`&m|E4&WBdUjIuwoor z6eZzO)G?00rly=RF9|Ey>1OJEB`XKWIZc3^CO}S;(Aa>Vmv%N7HnNa2tu{F}cwI?C zgr1<*`Hv5Y>e#1(R3Pl60&-FTRkM$m7W>?5bebspjAr)RGg&n_n*A!S6Q$*Hy1q6u!Doz9-CjwA)mwRb-`45Z&6YcWXmaqfcUX`aW0o%3gfFd^8X)L&0dl$kRWpZ|cINn*k!C8GqkKp%l^VT|ADQad zsDfM|?c@S-asgGdk(Vy%8nb3WvnrMfx&nFWhu^`RFxsN%uZy%Aj}BIUbhj!_=9m%R zz{$x13g)HZqD8~U7&X9lvU5*45MIhfQ*vKn9e*Ut=^$o6#|Z)CgaE4M z4=-Kfk7pP~Cdwb9MQ8r*1o)$R(OktOV}>yUF{cfX(*~%TNxZZ($#ab)Q^6$V5N|1y zOx-w~|Kxu63tCk>F3 z2B?}*ytFe)t5IYs7^S?{O&Mj%?>Clf-fm_ym-k7(h-8plUwx(j`7wZ&aBmpOhoi z25F8NZs~}8QaCnSB`(GcTt%xLaUe$=sG1ABv~$5G!*?pUpzLCia=|z+Hmd96WIZ4H zmUQoIvi)3FKrCnENe% z@+GsqmqVPIyA3{Cp7n7KM!fuh#G$iU@eg~zvETY0Q z)Ov_}Y-t?A#PC#Y=>`KIw?IB_fgJtNilFv->AM>doVDRUiP+DyfpmBrekt~`5YHr^ z6#Meg>F^B0^N?+CJ9b^utMM|D;f+>1#z2lSkYgO)92)AShZ^VV$jvM~$7h{(*J*d& zbl`?#Zo2P=`9nv{_)5o&udco50m|?F++3v8GX(Aw#TL~5Jk~bdj;+ivU&zCZ+pEV( zF!jaA{|hO6m;w1P19GYdw5Ml?dFi2Pniv}PH+(_6*`Edj`-Ce?Aqiq8 z%$23($239 zcGBUrJ5Rey{JQ9-M{hVz{Ca@@I`%g#+23$S$NsrrEwC>A;H-OBLhpG>4Xpb-!PTsL zu5$Rq2QwJjlNS0o%?~{l`sR%#)}>C$^VXvtpT4PP`J|V|&GL}YSsut)9>_Z`kSnmf zmmXOqdP=SmWqoLY2+E4lKS3_z|DYJ51XL?V&!?z9_K1TvwbNrr-l`rePs*<>^%!+B zlFruO)}m@X{-BzP(lq9tf{FR$!nsK*RM5{m4ahqU$U7}iEGSf7dgM+UQ;+?g(H??_ z?s{zX*z$p6XAak6n~DQ$gwU5s-xOVUts|1N^poreDB~Rgy0z?j_Sd4j=g{6!E<^=etGbe;~7#XgXb*$y{3BbjBPI0p#r&G zhru>>dF!|2p!g*F4H|mC0eQaxdA|j!g?{tWLygC2C9-1u-me_od*I-;C+ZTC8TZY& zZ{5%-`vy;)HFV~_<|Pe75AA(m+TI_`z3IR!9(eKHjke~JBC4~t2k&m2=l?8(=9dpo z4{EEH7WC+0Y6cF@WIw6)JNlkyTEqMrY;=qTXIWSylEBky%k};ATD!hah}!IC{R*Eo z0Vw?&W;vd(|B&kkVZ!miP-cSxv@+LF%7J>rJ89JvnSoOsxxxkv1^q}ekRM3~@=grx zQFBlYYF>Ke!S~eC;FJDQId?tT`YjcPOH_OH@C(zYQ;t^n(BWz3E*9o|(){x9`$B2% zqE4ECw()`L;a3c-HnOAE);&9~X7_*5si#*&e@qAHRMcogIJszXl0%q&@F5K3Lm0?= zKTtjlVK1%TPaO;wLr42e)*Gd~Cz9Ikrp|>f)yNY|ceO?ia;}jBxke7;^a%|N8o8IA zVi9*{Lf_v;5m)y53nB#(H9*|2splXTdrArd8(>fKAH(7~Sj`bY` z3QPSd^Y=^zUb1CVE%3n21s;$KJfLcU=cT7u;9ZfB_qS2t?I?}GRNzhFW?U-ol5P$1 z4l26519Eu>R4wnk^yKB;ORVo6KY4d=iPouJ_k&5vyJ~;m$%?x%Yc*+?TxvT>X-9v! zv;%T!2UIQXyz~?g=T{j^xU~Fj9L}psES3)Eaqp_8gSBEy6&m?q1@gfPR6SU|^yCNY z8?9#^zrlJ-iIVAHo%K{W#6BZy5ru8~aSp|ip0R0x35Th}mvT*ClzN`)31C+N#dS8Q^epiK^4 zVjDL(zB=O@b$c8b=6sLiqmjhv_FRM6t`CdWpXTsZ{TuARX@h_nF8o_%^egXiys6Y3 z)XB40%%6xojxFq%IavzILg;7u7B|_$q^G z#UuLLtiSNto&n^A54H1~U7sykVwxZJ9y^p5Ru5@@NPReawtOWw@8lh{>e}j&HMXu9 z=87|iSaxy ztEPK=-Dakr&sO#tHA_9klAx)$(F$RMIF@5{+~unF3*5rU1E20Wx~m z0-1tL@zPHS2V86|`t9O?vM!c#z_%yI0pn1KBO4?d@knd{b)5}>oDG1C=yh!1rJoQs z*kUdE?P7znLvG3jznl~sj6)@lT##twBXI%5buIvME&wu`*KvWDenPllx3%cEiwiCv zWja_mxg|mD<77oJQXfM)?_(hEV<4kwT_1aC`gr-=q4tIiL%JAe|AKSR`|1sIPrGBr z-5o<;c|fySZQ2cUhh}!~TdqqR8tnMlGo=hJsJ+@MrJJV7r<2+AM13D_8t1Ii@xXL; z4xWPP>`N_Sc0N@wpEN(r&b%^QnULm(IXh^vuqlnL_D-wx zH0LM-m5+~v?&BUGP{+pykdF@_Ct7)YcJtli(xXvY&5T)4mCq7Iw!vxBvq2JoHx&-**-M%GfLRL?UnR1P2Jh429m^9*(K zN%O;;A7=5}N1O%VGJfcHtsP99eoS}`4|Lp@&=lPIm$~%o7PbCjl`M$ zh8GqMrb{Ck$<}8FMU-3AlKgj|Z#lwRc!pX(R;2I50z-8S^j@=V*Ph*`IgJ?+3dG!A z1?2WBAjdZ}F4(KQbeTBc^}WAdr_X6X@4o zcnS#gqLTay?}tFo8<9W-U%3MMW5&Os>5e{-qYva5h1Uo4z4YD(kLz5w{;Yq|<*4&E z3_ZN>GF^Up+2Vz|9CewDwsF%CK<%4YQ8j(9pT{0Zs2&AcicY$u^xx zS{H9Xoq5!&_6gDbU5k%Xh5Rkr*Hx%=`|N`@ko4-s%I5Fn@_UtaTJz%L^!}5N)#a?6 zcJb;(9V&LZE^9sG0llH}0@i5?Y@<6~6BN2HfAOVzKX=^lg?i%M`deUU58owdX01!# zw_Dop>wDfD(*BnqFZX#t@6zAQ+Sl~$<&IPtQjo&TNq)ob%-R)#XV+e??>V)-`kq_+ zu)dG1{j)i5Ap`>l$Q|M1#3^Tz_hwF};`Og7D+4+aM?%u$Us*hqS5>>0!bwJEVnYP;}pxm=#9a`YDLGrAt@z z4ssLG&K}`Qn4;TMMJYlr?e!52~#4O1sPb9PWe)I0f4AJM?pF3IFg6 z%>IF~`#I~^=AOYG!}$Gqzs^hIw``;HBS+JErvZ@D0LW<&TA)#FA1_^f)7IS=no?c5 zaI0N)`Gs4n3|gUYzb0$CnbfNHMmfA){!CXwO08>4)Z!V+9IwAM$pScpbjxqw>gdt^ z&szufoZp(K$wjkioF$F8FVY)Ig9g-`lt4~OASb0-HL1^4s>v_!8Z-!E-)In8nj5zKNz%F(6dPekZb z5&BGo4n*j45&A-ez7(N*BlPtMeKSJej?i}^^t}i@5TOSn^iYI;8lj&@=vNWKts&#T z2+fMn|6JfTvo!ZZD#gCWSyCk^>7QwuNhsBz@3JZp<`eSL z55I#dQQj*F&rs_{3*uAbo${70B|2r8H_H>?GcxUBnG}wG;``^*YJ4PU(4gp(B#=*% zK;FR6Oi|4~UV5mtVQAXHMa+P%0+cD@O1EFG?hpERN|Ey3TzH0m*G_~Y@rLI_6&TkI z&zSAz>aAP@y(QJfK+tIe|8*F=c3roVS* zc?^Xkh^_N9h8REXEYF@SR^c_4x~6|nfnj%RCGY%WiN3%IqYFfxNI*^`plTv{>5NEo zQ6Wbs5*(9$#>+wLCPJj9zFoV0eVTAkuz1%E9UAY513BVAMZ|+x@zNRM^U#<_M|`xQ zGa7fSp9tcey}ehLxuXtmHfvyvLH+gb+HH(ND_2Vd?^Xp}*Y)RXJP>r^06B4hio^*+ z$4h6#S->J8`cE=mY@dhg59hkyPXLujSLg_aM@6K&6 z8~@qmw)&Iq!Ib@J#08EpWJHnC1fEH3ZcvwQ29;vpbT;~jAzM;xe#co+d* zIzxOhW`WTWAB_{r2OC0J{=H-(oS-d8M(#U87(q0uI& zp=St_mj_Z6!2%H|$0wbE%4ecOS^m9j zA_U8<>b5&HX+{D!rwfqN1*k}uAbPxXMwfFjx{gklg%n6WDP2aB$1k1;U0D7y%EmXk z*npr5BC}Woc`$ml_86|*vqk%&bLMO9V;b$w)jiiPyKb;|w_>@@nNRBm^|!NguxDU! z+s>=VGL096ADS->c*{<^IV0|BKr5#+kkc8cNM|-E`FQD!&gWy&9-YosfI`_PrE__& zF6F<=CqidiNlzuAgKhwxP9q?v5m1pvVT^j|%rQ^*xZt%t@3cEkyIV2mpK{Z|8;%}& z%8W0{q3Z=5(P#LS8|HR%QNr(vOzzKho>7h}53eoK_pI6yedm#1H|tmUJTC(R0r|<3 z5F3T%O#Sgm^Mk)Ovk`CQljetC!Ae#h6ugsn(5gf9C|=u3=o{Ngzy z{USG1Tr?}cR%P$j65~p%)LHTbrOvQ%U(JSRE1yqF^pp+JUihT>L1KhX zxw*uK)JYvkn`i%j_Pz$buA$b)p7xyl@64Pz^E~s+%ri63%$$OUULGs=eI%0nXMA(8UX5@C6$ zp|ge|bH9RKm|*1)k#dMgIfQS$ zs*BS*f3o*@RV@6Ye))5dG?kC92%?^QR1hBKK|w5LPY7ZS{3}6J_hK-;jEY_7DT0!4 zdqsRJf$l`%j+&W3-x4$%=t2p{7Id*7wktCP%?G+o!Yu~+nV=;=SL5SRQ4>>ws^Ruu z;#&i>THdV%+9YTLP_v*bfO-U73A9_#R-l^%wE%rsP&?3JL7hOi3F-m5Q&0-%0YN)~ z9uqVG^m9Rbfqo-sKTu>UQ+gxNG(k55JtQeT2y~XX-2yaA&_{sg3;Gz)ZV7h?=)-~z z1AS0@Zv`3_x6cBdEzM%lmL3KpmLxY5=#ZpK1p8^=zW4Jf&N+$ zEs1ZEaI=AK6}LG+|0HN0(A|RO13fBeG0^>jmH<61Xa!JQP&LpYNkI+JOX5}wv|<`_ ze*;j7peumN1YHSqrl74r=L>29dYhnjp!I?}fwl-$@KcZp#z@<|6fJ^6ULE73BnnGGeT8h``%^95*Qw!+Ckt&pF=a{>ky1qzOqGUqsY+0@3a2VeJj06J80(j% zJw5*13aet6UZs^tX(h_1)ykxXc4)6XqXpJ{H1sgbp)K^{q|4&F!4~)cN;-YK5s(3W=0L zqF@R&v`gV^cDlkTl(NV`?lsn;_O}J9|D`Q0IokX+i(O2Vo6Dm`A{i(yYLRGXhvWEC zQ%g&pJsRm!o)9Tdi1K;DVOL%n+U3a{c0a>;Vsnz=czJo^KhML4<=?+6z!S~&SIHJz zO_2;`0ggY}u=cJ{1+lsh z^rN%K&YY~Pg&?^@#=+KD0oA6J0#czIAW{wx1#>_{yBxTZV`kwT2rC+4Rpj3nC>ksj zG?dxUU7zZOp@yFgb^$;S#IA=ydN56&Gi2V;v~ziTQ*YnuuIA4E7QDZqwRcBHR}(mz zHE?DM+T71%hXzQoa*Ig0MU>AiTV^!0%dM>(R}AM?SeXgaXFpSbTQJ31*5B6F+M6|U z^;D>~xMVHet+4bXwOwi)V}*qzDEUN6K2bjTwzz0$m;4qEriPQxD#CkttBkNN=4T6# zzq)Uo+PQ~OZAoM@l|&*Xktm-;+hfttE{W|NB_2PCD?)0;LL{#2YFXD7)KZMdT}vm^ zs&o=5okYQOYG{|vPL6?x(-~Io!}`{rE0C461;bt|BZfz8A5_Zwy8cv8e@Y56OH>NC zE%4aRVU#6Nq)GWfr2HVt=Z7uX8u|{CH0CKpZ=9Pe}^!!Xu=D3B9XwWxPfZNgB-$+DGd@8}Z; zvOQa$SOs13rmDMlM^j2{^33DI!#~GceW~2G7o1bkTp-e1AewB>?L(Yk79WUSH`3Ng z80-@-qpw?24G9^>1cn-{IeX90RQ(N_hy$OtX?qq$CpsldPd#l5a%9IFCGjlKmp?CHaeE zAUU^k(o^4s5X;mmu|!HNQ82L@+9mcN$m9fhII-Tw&W5#fPX5}3(|}<7 zoLcWwM7^B)QUOj?^>+%FHgxBW&4ls{uk>&Lhm~WNXZH2%B4k&M!*){Hx{+immxz>0 zM8RCr&@Pug0{+mrBAiQM?Sp?@*UP2b3vdbYv|RTB%XZ|K`n#}{Al6qRLD^v0!HfWQ zkRD|Rk+Op*pB=WT(9kYBFvBl)7UAq5ySx{ZXN#A=H}7Rfq49fBCAn6;RTg@TI>b#& z5G9aE2_(uV(56sBy97o+Ak9bq2MNp#=#EMtQ>X+IDS<@61ZrrPz!(UmRZKX6VGW>v z^4gn&cN7q4j4(7b^>$$-rK-QHc{EXOKrC261s%!jI*Mu}`g{amEnGnKl4~B$y z`lzNcno>=qR1*bLt)X4252G~G#xI=eFr_Dq>VGbj*L}OXnmOQ-x7Ns`>DkWA0%mtz z>%00}xjfjm;%uP0@?4qF7pEaCY?V^I-JN~e8h}J9kBF2W+qXd1S5a!g-V+ z6Frx=8K~$a+DNl*=dCIL-d1+{ur{9JH?LNbK8m1iiXE(e&{g<)u6g}t;cl!a4#MybT z7a&FLt`ubloH%T9T@$Q{Y<)u#lmkS{0it{kSba!CyBv4`anas2oC9GcFRYfmb1WQy zj#$^)*WWp!10Bc&J7~X}WwA$g=4A836w0WAjQm^1kTPWqkuruTpE0($X=s-*4xNqZcwG)@mEElhU9^75{+kOuv!uC>jtCW8nfOB!hThyF|Za1D;1N)2rkXDY-=XK59NbfYTzA`!K3U$!x*D70bTp6J#b-j5P9mj~D4$N7Pz~+U`4n8akS3hYuxt$* zr1^RQIvb!`VfGF>Nq2v5Gj`1Iskf&=-Qc$=-o0+1PVgeDjxp&(xpUyy+`R)E zIbfp^Tk=V%GKxqUMU>AdtDb3Smr>6mPOhs8XH=Mogbn@{niE*w)YaVD>F1SSPy%>l zDQD7^aw4UiD4%jGA{yGI`~}d=b!6d`ht>RHh2v-e5vj&pxGfoLTU$re{kDZ?;b=!# zDHvx!2$-N0?zZmUt6Nhmuf-Y=%%a1f$+CnrDNBfyB}DlwvCV*nc3JW=;^l(6aF&D# zgMU*wudaSy0hZ|2SgUvJK!+If*qytUUfa~sncXJKquSYV1Zf!ADC8|`AqC0+B4q$k zJ_D>wXlR!Kb{*mP84%VEexm>bP;JP>&+_h`F`DVwJm=a+C7DP`CJH85L%Sq@47tsv zqT?qyY{;n48Ya$ruj_@9o6SXEBN3j}Ot{ibq;wMn)2*Rhx@STFxQKK7bcbd2Sm+)f z$xO47Or#_e1(U3yU6K!hSgr*QCpoN9TpH3SezSmdhudBLkouiY?%NGyIgiEdw^;o% zQLOdw+dH2}YLpp7$_%1>X4r;LLu1wD))p>y4Q7KK-wLbcmiX9U-{IdvhIh0ryvX0e zjc#<8C6Fmr0*RDB`(0a?(I;!oGM`J|gK4ylS{RM~Qf=Si-$E6ce?7tot;gHC^DEjoQksdBW}6O6vxZg)-&)BH0>WvgzxN^!tXA(gZ?oUx-)|QX@6~TSZ3s?{x7WrRsrvWb*zqI|M#UTSE!wz?Tna<7A6%B^A%R$GM)zI?X; zEDk#dGeIb+GQRhNc#1!6I`vIu62S7I5) zUS28p_g%g3++Tn#YnpnmQR0^Ea;Iv<*3j2#=Z##r%wTtqpPGYvoA+H??n#w$gh)9; zlrJk*an;Z+N2~m&@N*?3kU8q6VBMMx;)J4;$O$YSfwzinC2}IT!AU+EQf8C zC1n*-ne`x_nA$)-kx=Cmk@ATspHH@C*U&DXK94xL3r{$oC~)4(D}Z66(S=51q!{Zw zD(0j8Lg;PkJ2rH~1gjhSE$21|VY-!UA|;zBpKROIYiO73FM=9wITTK|mt-5(YHne+ zb%l({^2wG0I6`5yRWwtnL=q{HMEOM8lxk>~NW14zIFYvLu~B&QHf+4<`(r5sFuKZS z+bC^S-b6XGT&iiLrVHys{aMZ}oRk|x$_=7?ZrB!EL%ZDgDx&0$QsLa7zxVQTBdq)V z;F!44-xt(k*{o*jlsFr2c5;p`_Zjv>A;=)?vF%gHc#eRE;DwyHxjWBUa z8vGsbGzR ztsTGNRnKWK%UV3Zd&gT`?7o_Qa%#p7hS8~YG{{_Ni@FP%PyRc z5`cl+Ypi^Hr~n^1yE~GPwx`I&e?0-~Fk7IHi7Ap@he_G$zwx-GkG&{o_eQ6-H? zIYgAtAzLmrw9BDNaEN>Sg>xv$0G`Xsp|B3X!(-qOMQJ3Lg6lL2R#w`i09sK~qb@8H zG28}>1kJ&{Z7n*XcHw$!_5cI%qOk60>Qp;KevaJ$TaI%F9#mM@5eFV5;S+e^0r{!f zL8RG1l%E~8y427|xT#`rCRhcM{@%-*3I6ROyzdm!24#zOoan>VhgeH#PptOLK3O?q z8kI02C5%W3^L~@*)X+z`N96bk^Q#5kj}#;FiZrxaa_p{`!L->r z+U7EOz{{IywZ1`h`wsv9s6ff#V%p`H6-VJ|xx953mcqic*x$I>LG~_0mf|6FR=s64 z%#A^?FAR%cwyNbWp2`j)Wd~8dkOa zUPRJPYb-lRm6aFU>nBh07{&AkF^ZHbqllDIMEQ&&b-XmR%cvz_)YFVNba`r+&f~8H zy#n;_1sEkG`OT@i?(P)iNM_f4{lyW5$&PDM8|>~+apKO(5EUQ%mR6fQBv~VPNK%4$ zNG2!`iIj&#`8>4xj)r!5Xm`^M=V4eO^!HZ0Jp6G19zs`UZNlxvUR9NFP|PS z%qLg*SiKIjOxWC^3HB}{YHzAG{3O*qH3YPybNz7XS=ZLq*NSBn9anc5+piN3@=Bc< zoXjLb@hd znz`+^(oCc@6XgquRm?QBOY>H^ac}wSep*0YS%f!qkF^ln)?+_nW$VH299xh4?woCjp9KR(oKnZF=ao}L$|<6JPTAtA zp#pG5c8o=E?9Lb{6VtX&qtpV21{O2ts_ju<(QdrI$$Q zCCaDQR*4$grPm%y5KeDcx6$7v_NpHLF*bU$2WWaa`L=0XjUwHX9o)3tByqt8Ot#kK z$qLE=BIN*4J_l@1QA1}>b#hkhi}6?|gx9MW?KC&6Vz&Af2V290i9n05{MdIG>laSZ zZ{W!Tudp@RHLchntuM8B`I;u2H`FU@cbfq>wD+Rc!LdWj+xxq&S>4svojiXwG_y+= zF3j%I^EZgiom#N1B`cYZEMrKNGKNSQL!^wc3A3W1q5X_WA2-HygfQmc$HAD+4wyty z$q(kvHw$rxlqq+JlsiPh+|kf}?tJLDai=|mJCjd6X0gc%5*aj7bY3dN91^C?AyVcL z1v5uO`7$>q z>7KB;vwend^=Ap9L<9Cu?%rkh!H>pWl^teGHJ#LUlxt60r`y(3qxE)aX^(-S# z`~jtZ?kl*Lxvb>@rPK1OwN0`K@Zv(JJ$lbPEEHCbLChU&>NyzFwvObDW(<*L43TCG zQ>&L|jD{YcIR7A<3$L{}@AL88E6&^V>jPJu3-U2v)C&feV$hPSN-Vd@1LZc6a+@f) z7-;D6ar>tyjNAUB)4kkYnJ?ikxARZx%x7)BpYZXrTx(?cNa~c2M9N2^U_NT-@$r#8 zuoF`W9|)1!8-t~`pjPOsq6@2$mkP(QGaNmnJe9N|~qieqyVa%Q9|HB+VA$JRs-!j51(ooT!n&&g&rp7Cto z%B$x58GQT~W3g&ZwciuJ{c6s3Uw$)A#=)=NmtRwjsqYos#mF_cLnA9LlCK#;q!~h_ z8ImJXn_CS%zG8HCmeH?ubNhWCTfGgycVAsG3g)m!@W)w}3dSo1>fVtBid@zFBhvgM z(){!5u^RgL7O1Vh0yQU_&nLb>`TOkN0`mPmnIE8zT8`T!g4PY zl6#YRY`I>Zj}$q(f`k#e3WnDZJMoOi0?&e}+WGf)}N zB<3&r-k$iZyGoAMI^Pvtp=eLsi8iLI%C=z0lrxNHBD#cW^zfdQLc~nRbE2ulGbUpU zld?^I3*78=feYBg+soVJpYJ+){cV#^I_w;$*I)Oy&&}iB?8_~Fli%RT$6NAiu-B@4 zzN)wTI*nU0%hN~aToDs5b3^?*vIDnd=tT1o_z+DoA9Imro@HB+5TaownxjkO2xX3R zN@fs75eMVp7hc3&iQ8TrOAopj_Bq$1)yF#f4rA@iA2f1Vm)E!R4rAnb$e2a;tUTe# zwvhu$q(g_{h;$f^NO@q>VrkIOmIu3z8xP(e!h_ae@hRA6^D<@(9kwL;6ZzcQj1T*Y zJ1JIf5h=Hbg1M!kQQRAxJ&D2L;r_QcRTYb>BK;G$7fC^P_Ef+*v#uz;wxYkHom?Ik zY6tI$`~r1ACUJvPb1;@(n{ZCeL~bYk7;!I5s#A42=&e6v7rX=f% zOifwjXc4;)aGjK{Da-R^rCEcNCS)x0{YZ9HOlEHubQ7N14Jel5Q6?fB2|EMNQgarb zZ;<0v66S5drJGW0cicKi=Ebq|3@a zm=hms+B^qe+kwq1J375vK#}uW4H0QIL^RnqdzI|QMmTMq-PcW5akG506_m#Fx)Qf{ zj4Ez_{YNcsy&>ZEqj88^a6fCL%z1js;W9_|tIQFp%n=1kmWF;^NmkpalBH)QvSRpS ztjOLKB3aS=PR&>Q3Wfnzcf;8%5#0yaIqn8Rcwe0xzMni96|EcW2u=8C1But9O8@Iq5ReM&W zbgIznnp*E%Sy?vg$gHCWhBl_xmhCzD!0?{Zl5f?nFS&ocC}o59#GTH{iUYq)oi=v} zp3|JEHzj74+&4H>wCL!KiPHyueU)=$vwZA)R5IsaGk}Dt2kOE!al+`ni8O;L5NlT^hhFT4yB!ru zhgS{fZ|Rn-?&@ppO-bd0T^^UUVxvFZ4R;daadmEMea~JS_RHOoH5jKu7rJqjDAFV5 zOB=d1&TQw7`7qJk0e_+?=BqAJiL(5}iFsn>prKzM5_PFhqIB6ibMcQ&qIyCk>b>lK z=;e{9ydJJswDLT{Wz5qH9J_R_{E2Z{7BZ0DoJzv2L2~^AD=q{>mqbOcT?PDI@bmC^IrUI8y&yFo_&Y0 z{$qZ|j78raY3I2b=UsDjSO&-n+Io^m9RMQI0U#pfo=u!Io+Ls-kBNJKKv2DoC-?lr z9yWpG-jBz@z2JUm9t#T^gpyKaA(66>D42yBdQ2>wlGQ#KH2{goFNVt-Kt(WK<63^(^xEQ_!@OWGH`#ls`nl z{L#?PT~1A9#90z?zIWH}N^Xi24?Z#RD=f#~|H+KIUwY__q2D}&I_^ASP*7d=Cb#r@ z>#}0FdF!%w_(X~CFw8l)4@%4cuvs6qWyvdkG*NGD>602wDzEn=3RiayHdJTol`llf z7b4}0rJzXCWQ5jM8OwH_4&#@-+FTH`4){9(H}i&BybU)m z>mNs^M8sF~XRG+;{8R8RaYJ(uc}sx5jy-Am+1=x1LL{o%f@0eLHbM@x*iJc zZ=KusBIw{BhEucHy>?cmGV`t#0f|KGtwu^gNI2_KPHJilLHi$W+;Y*-W|XSNEuSyq z-%7-xQm`DKSNe+}6Xf>W@r;@!cutf_hlr^b%sixCu^+d+E_VU_?B&gGN)RvW@6Ye$ z>YeBB&F`fF{x-j@-m<}0)|n=zfvLVzsFO%WE9=Zp_e9A0{+=G5s?gEhCB@v6yLL=P zdca(QkhhBtnW&l`k!pHGnwyqMMUpH?mW1BuJngKUiHzE|U}fdCS2-)=gD(%KHgeD; zj*K48EM9`5TrylT?97|E==s#_y?eHr)SCwHJV|1!E8$688vUgQ%6uH ziy*|s;B2Z{5BE~@4m``u4m=Z*aekrbfe$a6>BKC&{LMo1`UmYS$93f+@Z|cvUF7}T@^pdD%rU1 zlDE#iTaM6?>qn#L>S3lG5l3ax@Lht=fm;_)E^Ai6#a^>4@ci{=&3PVo3vJd|)~@xH zH^#|0&d2>UzU*Ghm*L!{O>1gbv$WL9=`>jVHPTTy`xM^uO#oj7C$;w=QhN^~<)Zz{ zc>i`Frl?3@yPy~%+Bb!6l%uP~joH&Ch!tb2pmLxVK@~vFf|5Yn1XThx37QGCRnTmp zmn7ynKqZJiYUTlz3F2sAql9B~yhRYepRUR2tu^`IJ3_+9^?ol5V^^Ln$ zYg5hr0HcOQoqOFyrSapI&QYl?SIfJ#@ZBV615mS|D}Z_gT?w>X&{m+E1hoKtSWr9A zVL_chw+ZS2x>HaJ=m9}HfgTey0Q7S~dx3rI9JUS<8 zUIyYBGEwv2Ks;In)jcYE9wv_Z3W%p}M~yvh8}$|3ctUg3*rS=F#vWH3HTG;W$REOS z|Gub60&$)-6K-^sn7Arv9l?(M15I3ZX8oT2XqMC^c!5{D9>Unqz2I5%>%s)&$03jrkBVEV%Kd?!t@V?d5HJ zxf2U7ufMYfN>RbX6q1HkK-LWF*X)>`bd2u8X9j zP87D6N2`!w#*mpY8PC*p7k@6~P`fCS$vkBRmCY3l?q2Mz@QUdlc}~KUOr%}kTye?w z_cPT$@WeP7$3omENh%}~&*aFc(`|*r2&X3@R#)HQ#u-d(3is78tdwnX=tzp_h?9tQ z&q}Lbk};YmM4Bf=nkSYaYFlrNhDL61U5v}>F)WW^Xvv*{v!yeM`Wsm^Nir^xrmj6J9+3L8OJk_*xgw$9&S_EuJT6Ik~r3 zzsBNXs{`^^g@8zffGD^+(9ok-2glTI{%U&Qk~!`_m@>oGbw_QFf%X}%F@z7Ykttr~jtd^;sqjjIVk!CL%2$-Ft>Taone zf@^U$v%I{uw6_+Yuikn7{#yJ=U*<7R#&H?$=HOa<)Y%eiqwUR`(T3J?8vyyUx$qmu z9KQ&JXGZ_n1%er(0zsq#L8Jm9t9JTmP^~$wlximND7f=1yMKxPfou8cj;psCS;fUrIjWlM=>BDcOMgqxn)YKBE9u z4&!-Ka@J$`f9yF+CTh+SY0eU9&RXW#oYl~_BRIx2=39&J;up6Sik)B@yI=CS&v8d*>;n>(M%!IOd--t z$?=3#7^9(I^WyfrWY~!)ZlCaFn76pyJB|#?o5s#=9W~}M7xcOmJkRQfV=s7Qr{*z{ z<}p!l!PC&=DtNk+L$2VV6yiD&1@Du-eDoH)=y4Um zt-hA;jgpHeq5yu}my6y4c-;1R7<2g>OE%i#M;>YB5NYNR1s6XJ{hAlQH%rEyh~jsp zFXOz$@2s(zu=xs?$GlC7j%TIJu@@aOLUV^mbB8Fn=xFG16`l9^iq1UAffG@5uJYx8 zx9B9tmIMBR5t0Q~>`9+8mPi>(6fE`{8jN)pA=MUPXNQw!-G!Ayc58>hs(6tz75*G6 zO;yHb-8=YH(Se~or|E!dCjMOQ`oX(mxs^yVmvcH41l2>>Ry;ztZBkIG+nKAt zcE(6UBO|@D6EjEObdx1#}TZ{UyDDv`G>k}R-O2LGn;%F#(@-O*rm8%_3je6 zErmS8TbAXdT3Jq{EGJTyGaY&{HJCt_(8rDCjfhSOJ@G7eLRfwRH?zoPj%TRJW)AtI znM0(RLlm4j8k+4njC&)s&eC|D8g*i76qDJq!Y<(vrwFDIStGGbyokFJx4mp#?H&K2 zAHzQ92J&x(Y2RV2=klQ!o7C`mv4sn9bh+p`SZ27PdqY!)?#GW?7lV=~hp?i6E zPwx0%78o_4CQ<`xBITM*WwDz)4Lv&7xE?HFcHw=!UM<)BQ;s%0WZ3@fSG-e>V;;gD zjgOvUKN=rX_%c=@v{^i{4SOyY0Xu1KXTwvWT!`Xu*}mkrbB& zq{<>N<8H`cE1JxTvRG=0GZR+d>CP|2elfwBlYUp+nL~^4a_2&Ij-=0S+>$OITv|R1 zqyLC;PD`&Z^SON(Y0{CzVskdoB(Z&&qDBug_fRT&PC{(SPL*-yvklep8L|wZ(rg8O zqiMzSY}tiohO9-b%+^JR;bX6q3t0Eqi=&wQ&A&aaqxGkuxYeI%Wm4z!SHpg-*_ir2 z3-lMe5?S&`cvgZ9@xssf8=S(_)NSWebk1z!|6-Ydm`yQvGksJdQaIST-s$aD=N#7IZH zpMVnVzT^6j@{Yu}=xDlXW-9KKwf_|q73aR;x>zIT>^EbBn)@<9hDVS zpMV>78pZRF`0l{-G~B20`+JBlVeZG1dp=OmsVz+u zoh>HjqmyJun#p1zA2*+a?@8v1c$S*G@SK*_*7oCcJ~3(tB#sRddpN zW~Zw<(|f8_K+{$2nX1}a_s5=@={=Q{wa=P=6n(E10Ht^?dT=L++ZE^{xaWlm5_hYCcnvVychQ;J-Itt zcQ4Rga@w;<&*m&ypj%!M>6TYSnzK1_HD5LK(@2O=OWVAhJ%1&g-D`pczQ5#wm16%n zMfM&34VB_OenUmS7+d0gEyP`c_=vQ>NuG3%Ya`tc_&O|JBV7)zxtC`c@E8WPWH!jn=`p`1;_eyrvKdF7 z=x{2L4yO`nmNDIWX<63LCp_a4CoJQB7Lsu%a0J|&#V5dw(+UImtinK~!ax)(3>sQ? z1&ebpLw7Aj>SCuVvgl>(1b_dm`__9lhUXrJ+`N$6fK9{RUMJ=4T!?K6BNz&^+Y(Ya z(CNbIlKI8>T`a>=IpfS06-{}raWN}SdokJk%|>>$>*)3OZpF1Kz~64gWg8#=IqLi- zzwu|d&+|00V;J18Z%r-l?uWL-b?}}seY@*L3Q;yo0Gl^tgEqQE+UOE#-dOUB+}|4d zxaW;-@R!S*3tl&Q<3Cl^n>RPU+8Q@6w>*{T=($D4Xl@Z{ZV?6NmWDgpin{%6McF?aZr@?7`?6p0&bYh=)zHF) zd5rZ;5RA@PvOpCLB2_eql(Ci!mUvzoT8lx0yXe%}lyIhEPx|zRedq%xq~77%9Pmw; z8X;b58?3j5>dy}6L>bc087H*B&0egtczM}P)r`NH4!fw+>#s2}8;vYv$Dh3SC%T!0FmDofX5gsAKn({Ol0e03&%le0cTr(F?jGsaDD;)7M?MHoOr;lZ+2#IYnj zurEeOamf{@wrp1FF>g_^+n*$bYKk=XQ)RD)QkgBACOhSvYA#0hpJo=}SuW-t6^7PB zGYsu~&XKvRvkjNB>d6h~NYQx_zrDrMV(llx6PLGGZWWjO)g0bpxh25gUo8Ct$^0h2 z(S-Y>Lb*iZGoUM6lV&Z;Ibi6q7xENNd*C}2g>AYEG0Q8drITzsY*hzlf{F=|iV2a5 ziDi^snW3RoOq}Ny?cwH`!`M8NlCof6A2!cyaFseI>WU3c`^ZENrmG_mp0#lZ5k%t` z(=v(Gcq#q?XMHSP8%ytsIZI>*C0BUL@`NXn6`mxLuXS;su#k0RL~6+iVvaG$4*n*CbbyuAK;nvT|9%m`jye{3}&ZnkFk*DCxbLoijxO@x-V;dBpCH9ZpK zZDX4xz^DSmj8Fk0QUM}T0kUkeBa#~WMAb9eWbE~biZ}yFN!sKb=*1*jmMSbysFMXULkiL%& z-c|IQ-)_O2ZsZZ%I@$Z!=H%vm_mGuG*kW=REGpajcF8&jL`YnvdrL45#=o|GpuE@q3s(?8$zEf zdrO`sO4jKzM;14nS~|nLAJ3%u5S}y4-{4tkeu(G!<|la0HowF30y7ogo@Xw{^Wtpp zU_X59RR=X)C}fa-~05pie5Dk)98&~{dJDls(#CT z#gA!X?}4cfoy)3gZEC4)YQCm5)#snZ;=Zac{C;?N3F2w%T;AWip<_oYMvyQ?3)dp# zX-7|0Z}$#l>Xd2b55vRz;ndQUY65jSZOgpUX~J!7Q$?kv?rN;Q#EZBtFSy3>iOyF#p&uL z>FO2f>gsfLO}e@^UA-Y)eMP$Z%5-&0y1G4G-I=cLNmr-R)jQMG1L+(0;w$MJZ;>Y} z==6;r!E^AN!|8+j`Sak7@|3Cl8$Tui4$1SdxZf(z&&u=j;(mubzbMbI;u$EBw4m@` zeu?xirQTjyRQK>gpl*u7q1JChz zub2$1>G57m!DwxmpU7sK?m#}l)WdJ0jELw=*o*k>&A@j2=H)H_-VC&1z5d=gwQc%( z-uwPJwR&F$@|*ldEAI0>g=!3QYFsw3w70iumy{*Xq?%PlWwUV&;T`J8%T;nobCpPQ zl}K~da-`V(t)Y*5dGk-R{R#f zy6`01E5d9tyvPvdDz{2Hx>vdTmzA0K;%}?MWPHrB?=aSV*{__4NwzFoH{-Le#xp8g z$pD>VBhn^^NZD$MU=7YoLu(Pp&MJgXvBlZZke0JZCa`i0wiOEfXYhf`%V6*L{qdMq z^RH0hH~Ed@Hm#<|a!Y;m!C=nk4b+n}D$qnK&_v34%Mm-VrlH5ic^xG^{dL26|B4PT z=a1iHnuq;EV_-koqwFVA_7erOUqjPU^*Ef7UZ1JBq2!U0N74f)M=-4d1MBoaNz9qr zfZfXK>~zZ|CC_h8#b`hXVPfWOnUwgzTMZ`4325vnCAQ{AVex4L@FSbQ^l@9vxfd(ARwHu zNtn4HM6>nvCm{an;x!VGG0u~XCLZLViU*O32T`ziXz164c#MA7J?jt7(Im{0*Rgo` zj~4fe$M?ojf8>o6WhdiDR3e@>#da}L;FmicZ>5BNa&IL1msA9ZR0N2E zML-oyx)WV=6o*Be#FX1J2V`ZH6K%xCKc6qUYVe-< zN9woFpF8wr&WLBQ{7;5Dp8pJHuCR`8z4O>pe0lJmvI9e<2mTT87bTh1=D;I)M&A#? z===M#9CVdXbWLS4 zy`i#FKFTDlcV2cAfa4sCQkNbW?tkm{`I1IV=_Te4Z7;GZFR$Bogi7x7pYbGle;p>* zHaA{0B$VQ_Nf;`@Jl*bYwYkw{0=s^C_CgI%ANU;=%b^~igt-ZBIE>VXJj47Oo)vN&&RK?250xg0*xxKi(abjI0AFCBu>YdhJ8L?& zE$`kTM+RbkYJz>IZsmqL96$=4oC!+7xn~7utZczyn~MVkv4S!>ErKD#a?{;U!}=-H z?n9<8h0FDQSrwkV*HT5KrHW{>@VZ;vI9EV4*}V0Fw_Y$;QpYs2Xu!ycvzwBK6Ah}| z%-V>^S@2ml6A@IWgc3lbpJg+hapEF{_VV_!&U78U{U<%dyT(?DGTJPF)!;M>!FY`W&W-tY12VAA{}HS zQbyVILFnYt&;g8MA2VS-jHh1zI~bMS&B`+8ATM#C=FfDgI!m}=RU$C zKzCrnpHf&TJM;D}dfw^W7lXNTYTbe@@${Cmfit*P=}2mtBSPRFq?AZ+iKMs05Cp#1 zKYpNGg5)=!5@eGI?chC;!QT%%U+@3$ma7&tmZv5<_lx_2#u(b(iFn!H_MV+?j5rh1 zjTJ{%B~V8Vr&lE#Hy@dYJk++QA&a;KS*wjfAl-ramaOk#$#}|Fu^3%MWW;)cc{f~Q z(qiOhb`HbMUbkD|`O8>%Q|A83<4#CZ23>5zFX8<90Do)#N!ji6`G0^ByGz_zeQpo* zmx?tzdj4+T=sDBOXugE|`0e>Dd(AEFYFWWUyjoXww(e-{a?L6{-8(w~M1H$_z5uy< zz5uB(=C~RWQZ#fxsb!}wVQ#}yuUA`Y{WgEL`e60;Z{w)mg8RSui-0}cu|?P4((7dg+rKY1VwRqHKGq@j^&n!xkIvt99D-dg`J{GRx2YkjL}`FA_!H~9_z9fy(3 zp0QcUkbc$biBzj6Qi;jYEzOY8&?r2yOkxSk&?cv+rEPFvQ?Y5tEZ)S;Z~8A8Tspg` zzV!CSNTz3Jy_G3+RTJ1ukV^;ATTMV~b-tV4l8_v4ORtJ-ZW~-xtZSey1i|i-LLgOW zpO$RGPc*VDNF_4UsLNVna#GMFnVX4Am-8fH+=~d;!dKj@(fu2<4*1JxNgC_>THL(6 z{<<%ThNHmo^7`9fHWU53iSV2J#+A7LrEpJTbyst*w4=*;kWpQ0TW+bdXEzbsp_3!O z#RQ6js7w~+zeC;?%SBT40KVFzPPQ#3Gd0hNG|!1N&n=sZCE++wRYJ>-SsPhbP$k^# z%-)CWFQ*i&qyW?wo6Nl0bgDAZKeZiSHEK`{{jP0aRq^m}+u)vJ)4y(T)$HL#jh+3c zmfjBSaaB+MNzTTdnK(r0rdWDaPwDMjBAML~8r1@wRSD;`Onh@1zA}^np}9ZZXoaQ< z^#nGRjLAX8ze6gc<|2Ig<~I=a0I3jVfK=Ik31L)AD>4X#5?Fy}L>ACRWej1Gh|*+P zG#3{Gtuk{3!n{GOYECtKflo6Z#q;#6h#kf+d$AVfr8+uA1k0VJC`d(q9@mAt;Sw|lzEy3H?G zC5hgtpT#z(*!B2cwu`kz7^}M}1{1OU-{}9%th>3b`%~nQ^8jRBa%rr+#5--F~V`Eeeq>4rqzpXG<0u{?ynI&c|unrUX>#y~E z0T+eIUS8o^<2ri%t#Faew*K<^`x_y@Q5nCRHy=#6<@>cT7%cbw#Ar6_IA4<%%^I)X*4S z67~m=E%EDY%1*>N$^MnD98==h(iyn_Z()w+x8h?sIyt}+HX#h$i4PLFo0DW2Xw8A_ z(2|A*12F>4QGmDBH=qp2Qe|iHK3HI0sRIFJZY|v)jAl>%m0{;d{|_2B5AG>9=*kDE zkCn4J&ZVo8TaHlK(|no@q@8(6@m7qVCS(U-RRzxmG0N5a&irDy z#zX>?b)V&T^;Q8wvJLJHD|FUC;R8BAOd&+N0ajv?(-q#J_H~EeIxL*){0F0Ox z8LgJf7l-WJSx8^p(}A)DitN$?MWh9aNDEYsDNH6zx+FB@VQ^24Nxj*rYInDLbJo^u zA6~Bwy|byNPSMe-8ouRJ&9TA0d$ei}hiXJlj#ka2_tC0K{@HSb%G|~HLR1RGJVEEe zZLXk8fG!fW0*EO|m^pab%;q?{-TIsJ4^cT5BPPen#bpEOGO2Q>iLksus!mp`jLlwb z-~E?Y2sZgW@!JZ4Rr{9tGMRBQjt1Og*hFqaYwr$!Eon20SBKY~T*lc2RNxN=_vV|e{%Ex00?w{ZA%5~mj2}6+YzlP9yrZe$$d2|YS-t-h?%^yOnMl2PXJvmhzwA-2Z2`Mj+&q1SuMUV&`r?qfHd3`sCX;HEdfMbB4N$|S}tx& z@ucLi(Pzb0BHJ{aZ2RjS)V&z6R zrgpl|f64@=k-kRK{uBK5zTXFbtASEHzY*xKUwa4qjt2URI+h*LT;nTWOf%ES`MS>) zZba90x&5i;){bjib*90ika$f{+MtiDUI9zrT!*Sz2KC^`a7?$Smt{*SGgtLhBGp%k zps&i?$#YwhS9hn9j6QiTrus*#;C)G5nC$4fwyCqDCE3g#A%3;A?o7@+x8=NKTX*lP zh)xos@nBI~QfWjgwOS#6@gFjJl4OLEx3K{wIqp2YXm`c#aun!RaHn@ee^!Cdy;lj)32L5x)=0?6T8)3A3 zUIe6N?43YNO~P!()0QW;7+RhtOAk)VQ%t(_lVmw$ZiCVNe9gUj+`lpFfWO3)XVTw^ zo0s+fB?^RhpNj43o#$`&saOWTnF0Q`q}jH^zm}M3;`eso9z%a@T^ChooU8PzMBB{+~Ymb%dQe+_vEQORk+yRh5m$SD1a@|B16U;vBu}<&v8svHcI=vyZm53> zUIwtd^!D|a3?407^t*v?4ep6UDG^~GI0p^UfuYnX`DP#PaZg99m^p^w5sY(O$wFau zt_w6Ip(St38xaFN91=rflIbpzSq2;CD~n=e+JWz5m1*Nu;BBSZoSHm%uWiq^4BnGK z@u2)`bMpltZEn~Is7^c)3{b>MQgvsV9H>3}4j^ro+8GTB4+VZ2o;oNpK~$)Ta{9(3 zIc4J{vlqV8WC-G9^BLe%WC!Ha#a!ZzW-VfzU>fmEnyc}gZhG;YAw`KQm$%sMgiiu) z!b$w)MMaRsjw)S8fPYkc*_0V`iO>I+o+jsf_|FUU*CxjnyINn9!Za}rO!eo-)TBto zzM`YAxx1^YwOKbL^t3A8BmJ^X$k}eYG6!GUwyL*j+w$HHE@kYjM-1#Mvm5*Zl;zym zo-LZpC@q>qS~Q8YXfpZaLa`|oA=c0voo5E0e0lJha-7{FGrOHSp*wpYhH5J=dIT0j zNmsSH5+&?_6hU#`KpyR97LfrY1c0(8J3rT&?p*R7aulC7tD>h5kL0LB;UY z!Dq@I!p~h*V_?;pHg5_oi?r${r<5^)|!gGc>8?m2bF2J+WT!!a)*>YA1 ze|xQl56hXoHernMEGT*`XSN*p*MagK4hb;UFJ;R?(P;Y0^7hu|YgEVKf_c~v>B@Rf zLEw1LGvMYD#F|@KPeJGA=Q%;A%YVZ~)T_0vJ98VNlY=^~MWoYOM3c>At6udbQ}}#K z5(!9lcUi?KIkOWd5cZ`OCeOX#qPFCP$>#oE#phkXa&u^@mXJFRC5?Fs4n_*f({qP2 zH&^b(PZX0LamTP;HGc4}Il7{92{jVn6=Cj35&V4U>D;as6W+bF z?c>bPhd(BM&drs#;Fnru$RPl2Na6qr-5z*-7GB#YgO&l<2o5;YYKQCMzbJm}AtbOI zN#o5vV;Lwab1549?(l;C&i*r;mGSh-PG@DOjQJZBS!dsVM3-LKlewzL+1R;YZt6^D z4MG^_WbA$C#BX#~@606XoWuvvG#B?TcJ;jdL#YWMIMt%?vyEolBb&?P-O3Ex$a<=!%sq+13}J4<1Etd ztW$NxUH>^b^M00)MfZ^t103Wn%T&$&3Y!n$4d7gD`pU{H5zQ53JX8z>Y=JV6sV)MV znHw%%0ga`)2x@K_+_yT{)|C8a)=}t4opXox?w)@`CKW3~jP2lh0Cu?>|QP_aRI*x-EAx$CZ{i!)!X{5-s$ z|CDpz;1ea!A4Yaw0q-ka&xGG|D?D2=i_2X9#rggNc)6ys9br04UM~5a^F8^38)$nO zzPWJ@K9gBFuNI%eE->58CP?=XQn20~6k2p|=`BNZhkx;=hKB8B4Ct(!(*U8Hxqf>D zR;ZnI?pVe;;!Nl2{U2TAzk7Y;hM_%EYP?YGvg8aOj{4EsGL6w-5WS9J_7?3?x7#Gpk*e4&X!{D>X+CX za#Djm{DUbNygPZ|Um<2QvDwT_=f2(1M@7GR^ybP#ApT)YvX_Ww_^i8F=d6&g%pHc$JZDXNZLVTEu>F-`Tg6GB#<@doOGh!k2Yu^= zv{m<``A8TJf$7lPcA%+Kq*Vh_?Qei}&I%R7Loa7RU3q=pqW596tb zhQO@J6Xw@I`tFxeEdL5_QS&t*ozB|^1@*(?b`#Kt1lVLi27s=NbpkdFCLV7n={^ z`8M-+crG;mfalxIeRwW2KgM%~c@ob*Gf(4LW1h!zjd=x6%=#b)>P!^R4Q48y8_k>W zyuzG^=R3?Lcy2Ze@x0P3!*h#Sjc222#&esw7S9&56VGeR=ke?@ci{Oh^G!Va%+K&d z3y&P>H#6|O)-1vEI@5;dPIC>O*PCuU519Ai`Cjvvcsk~zcxKEYJl|(Njpsr0H9Y^1 z`3au?*Zd04L*~En{G@ayK4qpNH$H93@%*frgXce(#dv(Bs|=Udex~uy~;mi#dk0wD6#%iVZADA8?~XG zvzAZ}qtiS()v3^x4*qy}*g;IaP;qKHaN06PVq*PMLNHj^Dux+xpx3q+8MshlA)`P|@l|?{mtut?qmH zQdg?AW!Wz1TId!|nbu-G#Hk*JftW(awn>i<5C8lP*yXjeQ|!>$MM@zk=lnW**7RMy ztiKHsr_+8cG^+FwsnSQJ{T*YTT{?L~dq-b#N0Xc^{pP-8)3%Pzj?}JXsyn&E?hpg- zUCC{&$!)#e*R*zBkgV-&ZR%^qr0}+G497KfwIox$yOK>;H+6JDBb@AG`@&>H{Zb?! zXLuy9>qxb87;hV?O!k4M>@U3OWkK@NBzMN>ZkvfYXQ4ECo>^Zn>&+9+suj+|&bs-v znd@rSLyu5v1~f`&^xo*7Qfu6TlYB6uUS==~kTmzM+6py*y=kshsc^oI`RSataW|UF z%yl@bS0~<_Q=RW%V!9af(t~$%YWlyVkHmK}EtS4W?aX!M-uF1^o%ib8N783ItFB0| zn(eGxkzO~)Sv7mYFv{6oPffn-@yU3?hUb#f+lT6FVcu1reur)$H}{td8hiF!EDK(o z>sq*lT*>owZRra*KfV(xPi5QDD%1ng1Py+@s4ZO;89WkQ)ZH`i02(dq%wmrg!hA;A zRnEFOCF^E46kT5F?iD9Y>962AmO9vm=mv6wM{4Vlnt9qrm)^c=UgH*F;TyrXYh+NK zwXe=Aat7~1Qg817;#M;W^QkO)7D)GxKNGc~8j)~WhqimQK(#0!3DXL+Mq=&*(w5_} z)cLGEicvSSI(i$P6J*eTt_)I6k^286*-Uq;>GSFWx z(+_8u7vNGMC>5?-Vdho9Dr{O3JKhWX@9^&rN3eI( zn%dRE%Uz&w$HIl#Nz5N0sBdpLrcUc}BCX4blt%jHy1~9@h?mxe~OqP8x%nfc6GtLta>q}(=T@CGsGoro?` zB7e=s(TR-h=qfV8+5nHymbY=s5fo#Yl23?6uhaG{lixwIbwAAaOrn5Z3^yH*UJs-e z9L-s`Zy~gbv+mJS!af{oOO7P!$S$is5 z=o1HilkxAt{mBvH;MW}%!UPw4)V22dJpA>J&3y7e%`%A8EQ3fhKSwW9V2p+qv&>C8 z-B$1XR>}y+SxfWh^<(Wrctw5hi9iIVIQPjf=hL3~mXTKj7kqglr}MgrLpYSXzKs zp(V`2cxt|t;1{_}8JZydq>0jhjEN!UWZ8Qqx8KTszUE$C?q609_F^xS{Gvo`cO9)i zjW4H)FDc4Nm)C!iTV1e-Yzufl=C)wA3iVeYOcT?<#YkT$EHTTW>!(^bb@ZnCn>ty! z`eh~Db}qNVlS(-0n1QqtDt_ zwz9qJn{L=RG;Ug5w_)Sbn#QWtH7n~IYnN`QZbbdZ%Vt%HFb88Ak{*a)M5GcXldM*# z?I+ALcL>HX!kJT_YQj{Fzc7>2{74BNJ+)1}uw`UN z%ob{wxJb7toHQ%9%pV-u}vEF%Z?(n@Rp>Fv_>7LHhMV2^=Z`ymq zR*VpS9dG95ToJUa=q8N9uB~>az4Xu-L+5;5hrjA^7_9TufnWBYF&{y^6N)g1(SAmZ z{fsixe^&bt;tZ-UUPgT}EKzUFG|nHctEhF=Zl_@dor^ZsIVaZ*mnj~ujU!XbFsN0* zItGJW_5=+1j`OlN72KAI=0hCdEm}aJtRQ+w?q6AI6WnC|)k>YoXBjLuqQx710R|23r2pKZ%-XVaRV-Z<&$C*U5|UY4KGHFLF`_d(3YSd_ zyNXenuup+GQJG+Sm!Natt6PH4209s|gKCXdCTKp~9!GnaaJTxV?<9^OO2b+#R%5Ev zFwxWzzQ1Ofc%dL44$bCP3}8P5EvQP)vHc$fzT zQD&YHM49=OAj(V()o4@}7o8#~3Ab0omojrF6vC)zw%-yo8*Ud$IM!o}1+fj9A&4?_ zn}nmx{7ldipsVrms1)OrplZ1Nm-yBIt(JFdfi?-+0Msn#3ZNcAR|4%8v=!(kK`lTZ z7Ss-OSWqX>ZGw7$?i7>)Vq2i+d;dYu0G>Y=-@QP;5wssDGL_+O1ezx3W}t^8r3Zn| z61Q7`W(oQT(0oB31KKU&4gq~w&|#ntitnvJ!{YW?AP$a1Wh0U|3Hl=3<_fw4=m~k_ zt3YeSZ3yU{g6;u|Ni6pPog(PlK%bL09sv4^pa+4D3VI0WK|zlKEsUpw)t20NNzzWuRt3{|(e5h;7kIi6sJ5CnyHgD2R3^or21Mb_t@v z@p}c81I>_FDuDJ$`jSBJ6I2QG*MepO-6Y{=1Klca97p^oLA0R4s55vvALvm*i-GPJ zv;^pBK`Vgbf~tWINeXI!UJ^Gpdn=$8L}fg(M9>v*D-(1j(3yg^0-Z0Y1?X*p+JV*! z>IB*%s0Zj8K`EejNz1GF`O+M{ z66S1tLB}|Q8}ak$?Yq2 z;rF3MOX$6CmmfNi0qX%Au=v|l*{1yQi}?2I)QA%EBnO82&)VMP{aLzwmgi?`HM$$+ z&bo+GRJW;qxHjfId*G#1-FBUlfT0AtDyq)9aY#WsKRbakkm0CIUA$FdxeadAViV?pY`DjO zekb9c1G)g=5~2p-2d)yCo{WfiPL%G}WEpUqV%p#vmjR)v(%C7M%6vMeW7+ZeD4x@0 zZ9+o2F{j8RM!9r?PRGP5P1n9AVeZDW!h93Yv&?_udA4*_l5+6ObkR(y#qP&#FLoz* zu?X2K0T96*!~I{bqxI*Ij+(dW`haHw{OwVcn1?j~5#V3!evLfh%sn7oV@iYy}^SxcFBQEE3E6GHE5p<7Lz#dT!Zb^P=nP$93maWAwp^6 zS2;j4JAaU-xw*BkFWKJIhZ)heE)|Lx|1j}n3fKvr|qVdi&JS_6WN>s1# znFgCDHZ=3h^3=aqAoG$J|2|%jrz6Iq#>QaI@9+a7N6Auf4HzXOr8;&)q+>TkU@$J5 zX06P$aRI-csX({!DF(oZ$DHkEd-3+-bk$6E!JAG>qWVZz&FMd_oxxnEdmSJ5av`gjB@R)P_-?z)4wY!XP4ey9Vz`% zZOQu5FI5-U0Tq>g2{u_?6WGCf%Cp~|>c7<4G=~O6v)6A=whs?O1%aW_;%wUc z&vL&pO8U7>WKO1PUaegk8q-%1V{AQ)!{jxRmabX>E4pNk3)tO8&ujhito8V90iQZ{(&3BjjEN#!ina3aif`!#A84Lk^_RL$nhG=cvo1 zE4h! zEuNQ&(ze(f0Dim4;CZQ-D_xdt4O$SZy=cNzfgjKD>?a1euJvwr$*FFzQN#y3s-l+dd6p~W2^gRcpW3eeSI+L>4%7EZ8RN`cXl^7 zb=G$Gra+pG+dqresR!d@y~qNcS!NxzqiN@w)*aowyEgVUUEPY@oigoN41awS5jQa` z<9rGZMR1TfB?gXAKMB_vh@s^=wI{+jyqzLo#M_qjb~m-~L?x&sIx0e8(%C2?osA+I zeTsmzV?ID@XZ=jFtqHROSX34~6Q=yaJAF8it-JY}?w-_4ENqi^XvG6(8t!Pyk~_Lv zS_{Us4I_(a`C2Zp{};k!$HrOkZ6=~Mmju6fII|CK$}F#KpcQDjR*BmyrP7@{43!Z(&7Pdz z8Azz`WP9%@1vOxYZBX zmXzIo1`&G{MoWwIa|0iv@vGaC4ep-9C0er^x4<6wsvBuU9e0_D+R;^QmjUVQz-k~> zhOYx!g*yscKlIAc0ZW)404>4nIre)%Uh1|mTsxtA3(-nOX9s$av)Vf(&vdOA*s@aF9s>fxpGUS-}eR#gX{0Ps4`5B&Z^DLgH zN{{$-Sub^l=|&39H0;x!m96*=Bb2>pTSIlqTk+8tfR$bhH+#rl|BcW|?93*WJFeyM z`p=>`y9&(Pfc`&eX99QC)cx^$pQl-hlv$ogNOOivsbqMKky$jS(xk~;5sE})o~O(r zQ>KW_AtE9o^VBQ6#uxq9@3;2eXYI~8_dfs6ySe8%o%>sBukYH!+4tOY@5SfB($C|F z+y*a?)|t<1G=Ab-^J9#N88L$Dp7Il?doSS7fhUcV&uipOp^*cts{>8l=>hI)Ph?!aM-QWxyK83=!C+dqevJ96)!u*K7qVFkS zPv29(p3WJd$HK5iVST1a)p$bzClmRkA)2m`buEEIF#BsuYKlp9E->JHuNN3P$e!Nb zhWha8s=GXV*~9HH7PqfK>$@RndKCM-KcD($m#-IIS5>-&Px#nP9-pV0J-&=kjc+qC zubTXcX)PxH6!DKey~uz)O}uCF-)5p}nXLK29+|-8drfK$Egrdg1}+?(|MUamM?UQ% zYiNE71D}Dx8*2CMdIB(S{$b`Jk(7)v~8uHnK9z$#4BO9HDRvaRq zP1sG|X6{muP1l;V8!X}bH;k*q!2?C-;s7~*i#!_3edKbbP9C)iePnyHJR0-e)bpkB zYVqUFQ@J-;p3#N$8(|OkIZlyhv>AD&VT&d3^R9+3I<_ zlK0%}FXz@SRLRjgoYm@b{$88Q-3I);T|<7+LLzBhfVc{DV$@rd2d`NJ#OrrheCUUHJ@#)x=qQY`8%*JSKosF@lvoZE` zQb6RGlq%fU8K`>l_pN#!KJ6PB1)Zi98$R8mIQiS+5l6~LV%KSS4lZpySs|P4-X7al zj*qP`eYyjdbFFZpP(EAJaBh!^Lk^vOpbi_~S+BQa$!)vp=9!lH737QWAb;!aVxCLY5P`8A60AGi!wJg z#{JvZilgKXZh5)geY@uv?#`Rxnd!sY70+15-4^Cvw#y_WlbT#Z=zi#thachZr^HHB z26q?v+e-d+=R_!9$!pmH z&YtppPqKF<_i*)I`?0jB>{S-p%eFn=Y3<0b{b|M7rX%ke+mtVHHd=uPe8Pg*p<)dVF!)n4iK~ zztC5XY*@g17uok#50z(H(SvMU*;xJ}$$0cxq3MLAAW>Xx~>pNil37mPHRctd zyJb(mClixk<3bO4R*&5&|JHS%{mD zal7;yJ#bjB!NWTAl3%mxHE!&XVPkrYA3kab4&&2Ey@vE_Jz(cf9d>RrYCyljk8RND z#10Mjk1PvMs60H^1Kjk6ehi0Q@8P`RxK002qY8yC{pBY?+6);`vwoifF5*@ex(t%r zOWU+-hp6;On*pQcizmjTI`UK&edFMWmNo+h_8WIX^Pcz@PKlMp8_%ndp=(Qk4f6kFGy~C0g{p82J8g!k23|lqu+V0p9)Wirp%wm>6vw_dj?|V% zWf8Lwh4R<2n;c?bRu;F%R-S$S=H0hT=ddm{7f~Y{DRo#s_FlzCN*#&5ve;7g;H!OK z&)$2?*wOt5kA_6bn(U}u*`-=jJUb|<3GDEmShY4bk}>VQWJm9c*)?X{e>hDS^)+pc z!uww|UZ}m^_8+XQD~k_c>*`u7u;Zm2%39zm?j(Eg)dC-2X0fe|x|H-p_pH;nRz~lp zCXm8cVuiI#QY*~|1EoPA#VavA z0#aOogReRLy|T^dWO$>$RTj}kC@WU3F9QN|3Cf|)4>WoVr%PY-$fR&Y>WW4RC^U!l zg9f&~x1gU=6LN?s6Dv;9FkrDcM6ToBqU<(@m<9b@S6SGk{ly)W!S)teZiog;cAuF> zA%_>Zm>U&@tB6?*ZT|BBue7}dbt!4nOj6iT6G-7Jv8sx#%(!SQx|mcWw4^v2RTeS! z&`aKX`~jI_&^DG7Yt72yW!TCCr3p#>yoyr<=hYhhcj+Y`hdM#-mgnaO#`GFKr2puV zV@3`dTQz>j82Ru+)tIs41`HWl)nrsYQh}iFrxfuppU`k5EPoj;j)($-&^2{nvg^I%C#~%#F`||p<_4msw@?p1{qrY zMu+z49O82?mBne;$^&J?D2FTMNand|)VO}bhV<_>cF4fd?RpK8bpSu7ourArbhFT9 zY(r-3R5^&T_Y6LI&tfx%m^EWRxo4fmoQbFj8H2CH>OZlS$rxT;#2Is>CS$n1qZ*vK z9vO4=lj)4%+*(<@23vWcY!b%Tr;cPex-r^_vpTi$8WV{{`D?KuN+_2<`?Foob z^W)D=s`EVoHBl4tg9s9*WKvoN(yJY5%K}JWmFM8=#Nl3PNl}-QPKjf7 zq?j?N38e6qSX~rb8B$zBib*v>ON!NZWl`g*C=593``y!|ZK$~bTX~?g4y1D&X={?c zC(l7MeuP(AQq-lSx5hC$Qn;cfkitu1b!Tj4NZX1oCe;WnX*+7LzJ#0Yzgu>6np95k z;%5rB@<3@9NI!9;?Lf-sesM<%JYv_+9prg^67w4|s@N#BoSHa(o0h%~YKAht53JB#O-R3o%~ z1M`A^byF-H{=h~*YOk|>V^?Y{=`K;?Y`5VK~ikL)GIpV7pab7ykp1io^uOvZK-U7RsTYWjvuc4afxnHqcU z-aYgUd}|rX?j*(QJVbZlm@HD%RgRRJ&^O>K*UFIYA-b4UBeZ>^3pMbF-k;q!x>7S6 zTgaqz38bynRE}W0Z|6wQ9P5>K6m${Ga-`Hmq`6jxw3~R2Ni{-Cx+gWINxM^XA-3{B z*)xz%bfkNd6l-T37(UJ`Eh*|MM@mgZnrmf9_Y%)BsYYl?@nWU2So(~%4>jjwD-V>t z1L@t4bZ?Lr?D=b7(Nz{3%Ql&iFiWG%cUI8E=dXXskvL|p%f`GPHKk+TpBlU-@-goh z;&{i^zF#Wl18CJnwpa;j@iF6D{!pNaV?I!h#4&4KHs*tXkx8auiVi*k6Cgjha@ayBo~ z>ke``JPi3MP7;rZ>4MXJ4sEYP%$l)LNKPm|W3ZtnWGoz^espYQGIoSm#u;;@CSylZ zqjBYBOfFuCADgiwnK3zB816`qEPxcZx5HPTGt{Guck_-9>Qd7DSK`z}q=^;o0Zod1 z6(BE5)*#gg?TmB`H3&Cu>8G;HO8a!k}W?v@D3_l?ry>q3{YW0_HN$M7t!{d;j;sfFlC#!}Ne2uo(A!g0k zdPq(vK4Y+hj6ti)*Q>oPWoeGK20jF|q+%ot)W*T<*{8H2CHD!-56U9C7{j?`pq z2sNcMHk6vB*vbQCNEl5s9O)2}e(y+sJn>! zWsCE}ssr}fJw4M7r$!dmfpl0Pt?4eJ4I}9vLJD6UCTEeNE+ySebV>0uB&Hy00x5hY zR-4CG=A1G@bTO$$Xh}y>Mx|H-cdhr`eN=-zXSpDG*_}7w-l_N2!MrixS zIBH6h;w`Vr;-lEg17%zwZB)k}!MUKhBR%Cjue7A7s~jmc5ouzTPkOR=j!88_OL_`5 zrAbev<`Hb=fpSV9J>HR?!ZvO1NY9w!m6jBBl_RAlB2BFFNlz2cF{wsqNl&MyH0c@C z*sB?*2hzJ7>FFfx>_{h0%_2oz_my6T*i1czQh{x3zxC8n6W=e7HwY7dq&^5NjxH^uc!MO?Px;Gnz5(U$Zh?Mjh8&sgp9#gV)blnWoGP) z<(W8Rj@0yx8PqhSk4pJ6(5)UyXY3McKEjqd{H6_FQ}}b-yVcEDi5YvW%h(L|u{q)q zF|B>EuhC`A)!7hOR7<1+>uYC^`~E3w)>wlW#xj1i3{M`|+0RZX#U#x9fkDvLH_ zGs9@wWGx0;m`T#79qFDkvPe;vl5QKvZ0-o6CL&F&@TvHu*wy>xayafA8lfFcS5TvI z<;@#cQu7M7@<6#FkUr-~uOR6{A%$l6C0=RU6m^xGH>e4u@RC^N&l^{XE+*9oE$P+N zl%6-Pq2?KE<$-c_Gzz||?kFfRZ=6`)d&a!+p?E}0mtX2@v>8Lp<>n1)LdM`Lv3gdk za$DOsu9ahP#vG{`1-w37W)xgcKc8ajYPl}TSR*<7zpnJ=yNq4OK87E^#>jkTrmxXv z3^A9RH>e32gRjIYf8Mx3baBQUsma)l)Rdk#ZlcC!?8Y#fPFh=@lyW0U|8%5(UY13Q zy2{NP)I_9-RsOtjvv`h4H9|X@ZlOlw%9}TCrREK6<$-cbAl;&YKY~$ETLjQ-dxclp zHbq_K<_&5BDZC_B`SZqYqKip2LQBe5G5Twa_+)?PdgFHbc^X@Jpv;O!!IpBkTBD%E zys^Y(Y!>^*hT;)p{fH}ljW%P5Su^$&f(gZ+J+a=PCS(l0602u}1$OMb!PR4Q_H?9X z6x=B`m1X9QyQo=#tvpcfj52mmbu(6Cy|JwhMDdJ1wyk)?fr(f78g0f9vt}%Ry+KXL z76kZam{CVR6(Z!@1p(W*Y-7@pWgY<*5kB@&&Gz#!B&Hr`Y=(Mi5mokTa<1q1v zvA*7QzDAod#9VIPpeAGtz7nfv79A)h3 z>SnCOyfM&a>|tgMAK*ev8(;5hv>8Lp<>n1)LdM`LvC5w}9u-}jF-K}LHkX>x8GDQx zo3XiJG`;9Z=aLjRvcgw~8?s1ISGjqEnus*9%AYqLXM`BoMR}lTgmyIXwY>zBxPSv++u!7sw)dhgB^?AOq4;$|g&d(Kkiu7DH9A;e z$BuXQw`iT95n9suVpCZxP5LY~m|=YU^8@Kaj&yzj=K`GU;H$ySUTI0^%QNLjsfkDv zt9;Vu#B-aqzg!TyCbb}OBN~WDo099M4DLTlfEFH zV^WRKlDymI>{HB?e3 zzm5@Q?8oY6ti)^2ZjIITB4+GM@ran3Vj^O(8ABxH`WQ7KWAK%0W%}4#qKh-;NKM8T zQ-d}rn$3z?L~QN}82{O^72E|;;z%-C;I5n^g}hp*9Q3^AAMW7LFc)>X75%Gi3<%~**(wrLY}y@VO7BOcK&j=nQ1 zV~Dw2AEPE@48C%$OdoqsbaBQUsma(zu@Zd@-%X9OoEh8I^|3SW@ip3vA?9*@jGB-!@FiCHee4s_#Tj#? zCSxn8sViILqwR--pGD@vtCF5L{wbb|zN2FWGlu6TIMNj)-B(EA>#TdTNKu!P?q7th zFaC8CY^VvO@Re8{7+aaX@tNpiQjO5AJU^#KUF5w&{DPWWv6TnP=TYAnP>sG(?tYT{ zT*f{xRLNTXXxBFe-REnxeFHIT#y&=)gyJ)XgVcnKfiJOI5nGvzeJQ#)V~*7HjjyP| z_(VRk*HK?n^Bfh>$*YbiV`mFrwN{=b<{x|&qIyQghKfhT)N6KD#t^e+46h%O;xh&t zYC^`~E3v{FG%0pf@QoafGv-K5#=fPdH67F}y#Gnv3F*=F9W^guOP&1gUKmZMInr<0 zH%=B(_`3Xlue5ywbt&ok+Nqs4Pg+y{O-&$$uf%Gj*vj;c??o4rYJ_$)t)xaTgjE)5 zAN9etY0`gF^CY(NKv@~}jhm{`H_DAB{3@GzM&Fn!9ud=m2Yih-V~ANZ_C-7{%sm6t zgp9#gVukmylVbbE4{|upm?JfPV-+=;^t}1!M`~`zRvsv;Lf_bBLw^LMmkTL;y)wru zEh*|!()@cJsR^X;m00E9hxU``Vp5IJ_KlyZX)Rma;h#MU{zDB$n~(qJsBg@vM&Bs+ z%>($ZVw9f?$k^@T5ix!8ps&$p3^A7*P1J;p!B=9HKbn3KU7RsTYWl{n)RZ1gzfm(E zTX~@T8fEO8YGkb3X!^-z>{n)Nu6RUD>pkpiv>8LpFiB*1^{=*2@ku7qA zq7hos)pE4lm2H?;7Vm}0uQ)&V!;!dpc+Z31P z<2Ka@EolWcrB_y!)Z8Yu`uHpO4UYaQXl*&c|F0{n$6UrLipUtg7XdMC^_Z{GW(+ZF z#$FXoQv7-YHq?ZS!B=9Hzp|>4%9tZH8zQL;8yiuQ;sZy(t zf346rIyus{NV>6*in!3}Nw2hh19d5B{>qA)Knh=pRsOuOHY2n%q(*4_MgwZ3^Gc~K zESOZ3j)z65TZ{I@@rKmgM+N>Z@9jl>qenIRM!7ru4|5r7P(F zb&DYFB!}Uv&b%yA)TN~R%U)6}DK&ul^rde|%2S2yNeJOigK0d~&yv-@57J zZyZR!b)=1pAl=K6u8UhkSnMc3UFArriAWQxd{WG~F{wsqNjIQIHd6A|ej8GAu_%1} z8$_dEgz%L+dzPCaH`br)BebOWHtXcuY_iu0TT}C{A#D*zpK+uuNP4Yn)Aezi1dAm_ zUFF)8nm~#+O|0_Ubej~ZMrcWKH*UG{-jbS+rB)yRwt=)wQzhEAh&H{`wdo!&c%>yp zUFF)8nm~#+O|0_Ubh{L(MrcV}QB!)Hwx$N}*!uWe1=2elX)BUG;@WiBi(Y9-QCGP( zr6!OfuEZ+8P1~eMH9||;mYUMzv>i3e4Qbmz+GKM%ETwG`ZTg&R)5~7UB1K*0+LW3= zintQ1{5EZ$BGm{jX$NXbw`oUe9+Fyp{2c=6bVu5Oq>Ege&VSh}ZJVO5a&1aYAVpk> zReqappCZ)=E$I%_ly1`tf?hr`dailwt^h4LCFTRpRin_|RDK&u#zD6ZQnr5 znz8)%8L0^wgRjIY|9!??NNUf^j?}C`cBKX$^E0*^H8Vuvf~DMnT?A=u z=_T+r;x(_dq^PSLDK!ykVwF$Yg%Mg(jnI;IrKa@E){UA=guut&H5vs2t1$}7t-~L! z68BQN7Llf`So`o^*OU zGOs6xs0r-wo>;vdTbZjgdoyOst})x*vJW+-d&|DmTqCu5qJ08;{bm~RK1Hy1bgf+b zO|P`AjJnFTGBtq|aV1vyt^6-WXh}6fOS&I5rCS+Sk1C6w8q)m&Y5QhMx*tiqxK=*# ztt?X1Rjy5`38aWCvC41L0~nzt)d(#q&Z6aBgB(Q7{f6|wKzeX9B|VU&2f8+WVR05I z>MGZ!)C5wrX=0V%rUx@ZOR5oC(jL?d5syU~x%b`Cymxx!9zqS@!&R(8k3f25Gd(7T# zW3NB=B&lE96l>?KZT=s>H?g!CLn!ntB4Z~?#t_p>xG9~*j(5bY8N+!iDSl>?<%0MT zO~@F0C03Z@l49@rI9#5IudX;!Gpip#jlNS;uW;R}OP@|>>_}?1!q!!EMCco+2yNduI%rUH_V1$| zL(Tc}ypR9rK)T5mu#GqCvWD%rG zg%rMeev(Csx|9?nAt|=^QWHqwE3ulMn8=GS6Nj+nKN?ZUYI#*Zd>x!ULLXc1NY5yOw6Pt(p)P;Iw3`>5!${n zu^Ob4sIjk!CI-?5TWQ1-iy&?0NPB;gMT)x0wJ9|bX|9zaJu5}35n9r-so6lnMQlq} z{&EGzQkZFZE|^S>UCEpsNIN^yvq2jD!qhpm>LR|-tF-9MQYakmte}al&ZQM*yyLtP zmFf%H08`;?-P7I&34 zg|DtFv)UAOX`8NulTdu`g$*^KP2nrCS`{p?W6yf$r${wIOL{>yNT*Trta$hFUl2%N zb)***LAtLa-SgjGX~!w*Do099M4DLTlU~ROZJTO@mUKEbe$6&5^Hbp$QS*W!ola6e zhtCqea(4}t`&7d3E@RV~v7^N!Vmj`Jtc)RM%^2RNPm1ju)P#(|S7Nm^F_AsnH!e

^Nv-YDVB2BFDo_A7gn_iwG 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Ȭ|PAU3s;U;lBKUSm|}Htw~=XMj{F!5rvS5 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�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

Wp38%~$?HgWrw7&m=_!cj zLInbg17WLlIHXJg{#GfB29Ab~7F1g?G1gOny~#75hgKPwFwPbM=`B|{1Dgo5M;cQV zhU584dJ3v9oozv4w@O!d&K48rx{4+V1;)t)xT~X(Y+>D#D7ECYd?c)AzSBH#JP491Zg6iSleb=JdV+-|CQbs9@5-PAV3^9mByB74orLe+~ z_q=p#U|SkjFBNBN`7^nvZ+h>7o@~$lJ#&l&2NepS)X@uH<4;h@)nA|2bWnfKf`XhQ z;?6-?WzUY~Y8Cn+ig`q$8L4cYf=5k%+bkLMMJuzjXGYI#wzZ8(_05q3-_(LY%wb)m zJOb>>rDodma95_7%;)h`4Qubba;+-m%dt9$eEY&V{n!%=`Ul!3FHa3jr7Rg~Zf&V< zEp>KvFmsCCxz0)H=2FFdWwxAPcom1knmVzk<>fLi5)40L8D@?~#=YJIL2FO{AQ7-M z@6i{Du56CUvI770G@X-Aw|8PqPGyNFo~>^D7CYWO?uU!scc@I1|Yk^zkD^*j2K{0MQ2~6QL3+JT7Qpu z#P*stqX#)PnwHhXxrF|OEs}VC}t+3tS}(EkQytJ zdC(3Radd2XN#1Rhzhr0PLh`CRN+FN(&gW2Y^$i#EKA&nYw3gBxlhavNea-wOV@bZk z9Adb>p?4e3CH2g0Yt81G%o$$aFcm|i)Xdm5mzsEMwd!0g;nkTel1$1;vrMaHUL-oj z+@y3zDc_lCPoi&IJ9AxmIJmyyqOp`ly-dWmrISc6Q-aHocfP*JfTVLRteol_CUCmL zRd`Z*YAK25AuXdclgGv^4yyVOKwq*3C0p|bVLGSM1(!ocR-TEUm7IXBq{lqmln}nI9naqk}Nt0eE}zN!6>(`$onDtB#4{( zhDB(y9}JvX`J9xSTW}dS9J>YVnQLv$rs=){mT!H-Gc=;DxpQ)-6@0|{l_sUrlf=6; zNo>391np8}vn5u6r72meRFu=AV3YAhW>+5j&-$n|w%cCTQ|ygCYgLpR%G=on2- z3*dapiZAP07!~K0mJjs}bCDrpl0sWLUCd)eB{2v!0j=%ZFc4yJ?L@eiY?EZ!2~jF- zvW9JhW%=*Vs4*kf(N4diS?e2ipjKH1E!V*ab}~raEWGL){==>oizNAYr?#}a8UC)i z%Ir;yP;)7$Th62alNOyCJY$QKFqTRw*jX|u(@`q>t=9p5nuMRPsOw8tU-PbQ1Yi<1 z#OyKoq8{|}tmF(`Rta>pT5=15tdMs#u@_0IoZlrkRU*>5>@;E64(IXe##*g!IE4bblbIss4JJ^hcG{XSgKbht8GJ)uNjx&ts(8{08v)1WUGHdQ ztZ&qLC#glL_LLS%Fc~XxxqoGbATl+r=OGJADTx`aEKAAG&g4`pF}p6UwY5WMWjIv5 zoe}8>AICU;Jdo2W)!6w&Ykw}AHfNs3qBAGkwZ35l^El&ebyFz4eI`I=;X$S&3f?!Z zs->qmrn-fthYZMe%4tDBi~pt=fqascT`}#%uawc{o0&SKjc)i_pv>5tyVlK7MJwW$ zXWIkgLhB|mS4{bff-)qIx{tTg2qUKIin?&e1Dy#WEWyw9tSJ@0TsaZ5;gZ?PJ5X0 zq}4YZuWzHLTGOSbToSFX3%6#@UDb3U>c*JaxE>0>Qol}+S*}#@FD{M zI4AcpY_S=o{tAl5>n!oJr7U)NKzC{CccQzDroLVpYiw|v4Qe~S7HZ%oY@_@N6itOQ zg@fkJ*t@TH-t2>WtO^fOJ$RA1cz-T+@>4?3N|p$vC&zkX(znYtMAFC#M)!XzKkOf!UGo zM*n-^ql-T38$Pt2!7{+l+paB8_BNb0b@Sy`G~57xG}>t0z`L^bWy;lAQr#w)v6d4u ze$4=uEmcIk?UHM9LjXGq*Iv{?r7TNBag0 zzHv@=XjTmoLrG%7>l+p(P~Wx;vQgjH;mliM-dW9~*}U|lAuCvNSWBEf+JHRhC?E+- z2xPQdsi~`ljdQ0*J^0mJO2fr=MmcUIu$_C z$|f_`RA+vPp^UyASfz+NTg53U1zE^C5`!oE`lcOZ1H1Uv`hvI8y3LCJdUZ*(R8hV( zhNs+Uv)ZibY)NQPHQcL-Ikjk+iUo#taz?)9{2K^^5`{Q#6|Q}o@nXL&XUQBz6iMDS zQcFi~8);HLz8d{FV6B$z0M+I+7FlK;N$Ma)%F1u7)iuKxD|5l=gTeu`X}9(F`>PmF zrINPIBF#G2w@vd^Tp}U%F&zTunI8gMqb-dLFd6k*_?O|*e>0ji*6G`iX{Q#{C@%R9 zx@~f8#vqA(S%;-NDoZh&Hftc$U_h*~Cbvp7wjQUYTt0`{XQLv+Y=~X9fI*~4*pC1R z3|FeFzG^h~N+%0Mc4;Pi&QnXCXB-i#lj%QGB<7;g*{GB1?1eP1IaApCVLCT39tS z1vy0vIyYA;3x3lZj+Hey*V2cjLc6R)aib0J^5_W-PPwp~ZnWKEmfvA$s+^?>uOz>t_94D-U6NOO&e)~R$P?o%}Xgnm2fK7X);aCU3cUs=4Q_%MzL@1bfPiX zsVmVyq9U4jV~{t@R|+%1ezY?k{su z;mcJWW~(zYs92`4P0tcO)GmdFVQ$e_cJyG2R+yt|gd}XF2gT(TRVaq+G3GMa#rT@Z zoy$ zK~Zg*7aQ;&6JW4Na}pOfCJ4TJty$Nn6SdS3f15me6x*=0a6wPspn1C$(CL&uEN8-m zvIg#+J!X6b3Pmru|DtT1^&OfpR23;AIS zufH)5otfb3?UqHjxU9~Si9M(t?3)x`lva&W#rb4*^^cjI#{T%)3T*^ndMAcvM{_3u z7ir_JEOtLGs#MV}=DVp2XT2PwO3}0bq)q2QOIzXh~ z$HXJ7wmF?M>%%!=RZf?NP2G<@Obij5;FVfs=^Dz>tWr;!k-%`8-^hY!lg;Cd;9ysl zvx#>>6uB-y)2FRW>D61E2+Sh_v#yV;mZGxD`bV1tLJ?q`nN6fgUbI?zDWDN@)5^^A zN0|j#Vk6os`|uSOQaB%`>KZa(t5-QmW8vH2trj9%ienqc+oZgJ24|bKtkTL%j@N?d z4zP!Fe`Ya;2J?u<@KOwAnJ!5`;F8Cmn8?WHTIFwWZofMEMOx77Yzd+)rZgsYN3*%# z(Q@jC$6<-;#Z=PTda_pDZY83fRJ;)RWK$+fqz(SZt@&D~DlGqioCW6pE*9k4>BONw zV3T+aV=`9VyB)>m*+|C^j-A9hPh-xKS|;mA7%iKhj6rGU)sa96Ge(=pFn@ps5*KCd zbL0LLqtTI1b<^KPug#@~b^_RzX(w*HWRjVgzzWaDO;k@enYx#wq!IDa9%mwGH6?f0 z5z5{=@d{g`um%Ndx$P`fXX8AJQj?ilc9t0$xSL@NFR1g{$!)`IV^nNNMra`~Yb$GmFlIE}m22_6El>6*3<5f}*l0FkH~ZqL zuKqcM!u0kXG!Z+xZ^uLt*gpqn2$P*XZ{MDoSv2~@{d;H69KA`YG-GDZj5(YBU=tQl zo9nxy*;rrDKYG(2Y|fhm2`49BZ;G}jfap#jX$ zbM$qgwJA5nWRml)XRRkNN?_nYs}YUQMc4v)i47A{a<8kTA}on6&9x}*47Z+NwztAX zcp~y$a(A(&$2>rHV}S@D^|?KysHnW3PK8b4MW%b&?0(xGoR2G3uuWheEv0PFOSR}l zmUiT^fjf#+pZ}X3QV)l-sVhGvYRXu_D2YpsQN3-U;Vcx|%VvTk#H-TS0~=5>9rnO_ zcFq^)Ii)lwQrK(Jg!~Gzm7k(sFwVNH(!@ecM>bI%llO!&@6| zOt8@LndF=UM?5+X!rC;zW~=7WMjV?qU`}+W@QoDG5@<;?RsM*bS6WaibrumgoM@iY z)8CVuz7KKBI=&8+JA?Vk}>WIIMcCAjP6m@9Q^bwgJ&IwQ@ zf=VWat*18H0DuiNGOkL>yiS|(9XpY2Jk*;8)Ll5IQVVu z`8BTe&f;+8pjo44e8;S5^9~zRL_63lLFze?%3?)(g5M!w+`Pt9#10lLjONsl4P#81 z98peQlUEZdvD7=S=fK(hIAg?G*J4(vv?;}kE>Q)=sDrpvgFjt*cEDjcuG{#jPAf8s zU}0@LDz?WVKh>X8V8 zmyvOtoVow(1wC!Ob7m5>FZecaK0~H>UDofwHEYI0+OIo06s|7?ypnp%%~OB;iO@EV zxyb^#eHHSMZ*o{B?akL1>vV(M8O5BHxh!1@A^aIloVsc=242xd0ZS5D%o&uj8>%d3 zWQH6(fi`=a)42l0Ky3B4yTCY2mwHXGOv$Y@Bcya@K<^19AjBSJbbQ4v)BQs@fQ36JGm*%c_oALfazCeAoC*)lB*J)x6&_MHqFr zQSO@Otd83e7=s+!&Mv*)Oh~6BZ4SDP^U(>GLpl6TN#{|~d?vOn#g07w2{X6N5L8D* z_k(oUN9Y&^ko`z?pbg(De1TWPoS52eUI~*#`dsdRcb?9~N|j>+)viWPL$%w6o-NY7 z($s9+f)ccoVv>k0I=#D6V^w)M&ZOrU+5@9BS6*Y93A+sao530&gvow2Ml<#vh?UrU z7?2xNVIdn{o1Pf>k`8a+dK zs!a1fmtRV@jj%#%OmwNJMF2UUtlOkUbEz>J$4SW+*hky3k{XJmawbG{AeHRFMDMZmS)wv){ZrwMG+jpeKC9W z_RXD!j>RJ<(Uh{mbO8;j#UZ1q1-y-0pyG&j`b@2AW2;XaWM%{3fx%Ppvde2TP%D{q z>=?HZUuK&|QgPqyM9ES`+G=1Ol4>jFw3C4^PJaQ^4U6RY4?2$IXQg9z)wlbm*Wnc+U&N)$nk4XX0j<|bZWN+4}|h{8wW&UqTX<0O5n@&dLc|wl8hMS zb~K*SzDIVfZCqT3B+kpo)D&?MGo`U^|8YYowKikWyF=6xgLf_m@htZ~^%rN)_12Fj zZwuEyRm6#==(;qsESkg&U%=@ROR{muZNWEOjjn`wunSBrUW#L&Q0!{*8~hsGe=)jR zmpR!*#JX*Yn!{P z%+9j>aJyC_cqS(5U>PvpP1ogQj91pQRn9?8{tK}U5x8tcT5ywWv7OA|YR6$@n}%*~ zx;5~pSa-Hqt4IV>e;cmjUJnF6x~P+HBu0$uD$4+8@~PjhWX8A&sSz{pf^3~4W7<49 zsnZf}P+!ApW4*v?lkHx&LHCG!!>!q=Y`?l4S#x8W-~LU_W`h6RLRdXt(q6Fm*9ICH zu#0qS$Uh+z>T|C}Vd+{H{QE21s*5Vc;gB z?xwjuN-0Taw+~1Xi4gUB5fK$(r%lq<&6f$K>V4u}CcBxJp?LEGtc)+>BR ztkZ$J!Zh=YZ%kj<-(#o4>|mKx>dSm7)!egJZvFyul#SkWN2}W&npxE` zRwfI~#r48K-Eypky!%(4oadEO-U6?f|7;hhCRw4EOjHe>9Z(!wu>-^$k05%hBTBYT znqK;=N;G+iuZz_JGR&<^%oe;Af~U%_E$r>!M9BC0zkdtD{r(t#0x2MAeyd!vVs$p~y%e+L6q?Er5m%E%b7F~l7|Lr?fFl0(6^u*+74OuHSq^JxGQ6wvm0VOx-{dYYpG3lF*R+@oX(yF99Us!BVy(`*` zcupM7I%chPq!UWw)N8CI+9k`HHoltwwrxINYa0P=6|k&H?BAoHLw#)_|7GVP{37fo zB+9N`*zUwJvgzeqG^f!OZrz13hCfiO@g(M&XXYU$vs3z4i9Ys!)krw^kODP^SOeHm z8l>cCdVJ8lbV~IUGR)4)1V^)+PPA|d0HZ!ENal;eGBX3KhrB-qZ797 z7BLc+@?Q?NTa00}gS##>+V<-inqfiX>V}ypdile&ncqY}tt(Y7niUbBxD~a9t_zNs zIp(W1+3_Zk<{al8HaeVwBv74GcC0xdon%u=5+)9@Ol*f#o9>r;U{6xrWA_q106+cn>ur z{R)naS1i{ev5J|jOd-|oRg69uiL=fF?rodzSVXi<%gIjbnw8OoOk-z+%h@K$dkYg$ zt~2Ww>#TLDuCWJ$Mew(iDK?)@N2!&4rFP_Iz_oSy05@ylG~fm`+vSsWs0rJ3=C*}? zc>`CBdm759gI#Ugs&$6=Vn}cWy2u}*9S6cfRn;>g?=(RR2d$*Yba7Lvd}EAmX%b9f zgJoshZCgQq$Lw(vp)=K3N;R40DIZqT6h2rcH+?JZ>fObh_CswmCZoQiGM()<$eo7HdOe zT-=KEWtbDS=8ZqITr$j+>YY3Zf4A1^>ShYskesFgvz3JutC?|}Buttut(BqId_$bV z&LWWz*<}ZjW#4*Sqw}w7*d0T^&+r)oF?PlDZo9z^VhJ^vdr$Cd%QQ8Qq=koZQorO~ zlU*H(Zm=jJw`LcAzbCm|7QK>GrSZhl`LkRr9R}{~Z<>w-+1}sY1 zzAG>*GcpKM{RbK=LOjvIk&&`(Z6?QF2nusm$?9&)6*Lm1>G)2R9()sksx7(i|- z+rc(#SB{>;FhTQO$rkJDzcEUgJckc@`uYzlusu(ty~)I@ac$TX!ZdP`)E3C0O$&>} zyBam8~>O6sYJtaX$e_M0eG_2IiC080?7Jgd?Nlh-}8}F0DUI z>$ts(*BR9E@l=F{bwZP5ue>XYrK(zmb#oevXe{0uyBw@K8K&wa7~+a0i7oCC2bzn- zSC?I$Vu<_Ds+%#E?i8zj{~&y`9RWr%c_A z(O0OZTsgC7ysbor#d}JKJVXaq1%sO1SB@4{*Ui%acpF`A6XLCg$i2iy~ zjoQ_d`CLfviZ@)F$=x=uPDibmoR{3B&{USwPL0ZdrX#%(>6ElY-W*jXUuc;&16gp8 zzfw=*G>3*)q$dKSzjsFOoC4utwjp%O@)B=u-DJY+M=D4CPb3&I!i`p)BK9TU>h1Fk zp-4wmz(Ay)DyRB(korrmdY_mw@>he_vkvLS-UaE1Lf(H5Se4yHn zR+rzPm)r0|H~Y}V@=<22AYwbDN{&V}=7)P##deP}=q>=S?8`P@mj1SFqG9S=b8i=B zkj*`<6^N&_#hqR7p}j(}%S4K0E^xq0+Zyf`fI%aq{@EEKna=q$Qh1rTC&mwv#_nZJ zr9u05Y-`&r!QRj2Sz?u)j?NrCoGwtf{(QtgE1&tjwl%1d4rQ=a1qo!X8f!AaEaEzT zJN)#mHirs`M=mOf-QHJy6h~#2^FSDt)`MfzGFWQ{_rqr9&DKR1{e8W2xKD`N0bmtW zvHASIU|RCGTh*hL{r0Hs7UzqmcJ^GmYKegLEvg%8bOxWclu@xQt_pvIG$TjB&oZ!bdH9Yn3`cuBd{bbT-J&<)E-B`jRNq20-P!RA zPuYo(RB4N@!mw}*Q>Uu9!o%E> zR9=2-%kL3vjF9zVi`xTU4U0>&!Mt>Aj4JNg)^>N|kZ>B?;sWuq*fy1mf!9RcLz+Gq z3M933yn3+ExKqfHQ<#?2e^bxNqW5=>@(PqP%Yhcddl z)rxd6o4?AEkgT^NCm64B90rp+YdyA>zN?*I&L6-i1TAquh>)-$Dn_F4W|~}CBibgA z9g4wOClzv*skvchkDE2$V3w+9Ex2pI8MD$l7$=!R3hU6hXxy3OHK|k9-QrMjj5^EE z1u(9H)mSldG_Ti?k&|jNozdgG4O+0X-Nm66v1CIXRp+Efi=8Zy4B8ITx*CcAq5+q* z+4HN?%(9JQdrDP10Wu=b!~iVkn`@_H!&Cv)Utdy98jXLEfa$$cjVYd5PZ`^{>BjXcIu1%D+qSTi9Z&z+GRvtE40-j~2pNnD&EbFyG1?uR zLf1TdWCdPVyxVHVPZ$CAcabZKm1T>e;tE%gC1sbREG5?8VR7xQb@ED!^Hh>8D^d-j zS9RNtoYXpeXSbZ%L|PibqeZbUt+5eRNe60INpiDt7mcg8_VWjs`s1w&{)N#qcv)?W z9re!N$ujj^7187^qKu|6!w!R#&lPEWmVv*jrz#hfvI@`yc7~g~pmpFRrd&uwv*3pC z_mzvUpJiq{{epYYU$X0bQ<@o5u>uE$k`yw#Rc%xmm+i(7Sy);DHy08e397|%c{9sh zE%Z7Cxr_aLB)S%SH?L#irsdn%iH(qJv&zjR4tNjgR@q5(7u3l6)QqaH_OUtXB;9B# z_Aye^mqDaFHZzx-6VF$I%r#Ar0M~WOgls*l8QNUu?5bHPBfLb*0m0+9BER`KG7nw%vxnPnHhdZI6WXm$9W#Rq{a;W61(TFEp7v|62faW z-w5kYmPy&faSO-a!4`Q8MIA;}4_$@i^2g;d5@m@#jv(+Vp?v4Qv%uY#1UZ)qnWI7O z9|dP&(POEULk6WU{-Vu+a%`DEV3OLdto(AxcH55qJ9CtC5DIV6?v5s<-`RwYd_nfj^H>MLJep3%X6i2Q zYSNuXc4W75@!KPeaoM^mn^dwlY%+|7WuS+9!2)$u=X%pbs)8t+08_nNZGF5@R*c=z z=lx>WAe-oS3*An3rmRr88!C2%+dy6h0k0563&k>!?x?G}qE73kB`UpD5t&IYATse- zZ+V&SPA*4GH&ZLO#U??!H0;LFJeDaLM5g(nO)6gxpLB4OTzS*QIQ}Q_G+if=sY@tQ zj^h>JBe|*D`Kp$ej^CcAd@V2cp6O13i)X&WCh;*tT3=G1$j$ACP` zGB2$`(n|EmP_?AOf20}4MIGr*{^d~gz@| zF|Xa=DwA5$QTOOnpZshzHdtb!zJMWO0U4$>UPFHSzU60h3g(Goja)vo?oNzqYp^0f zhVBhY7~aLGWw@5$#;4?qjm^0tzqlYZ zvz%6X)psDdLneBSp!VVocJ2TF#JGEoDgIKa)bC}$*pSB&{5V?guR-&{{`KAV^ixiq0 zb($(^zG0m-iaBT`w)_Kyure**=vNgGqo$8S>AI$y*~GD4*DYE>lwjxqeQoX<9k?Q< zrb=^bqMwac9CS2+hzd3L3f^<&?y7{^ zm|i(+LU~whZHf}w*5qIYw#{_S+jpL>Xcwsw8VtpBW%9G<_cZq|)QxwtWR}N(z<0|e z;dGvca!2^4yl1H^-^8q+i{+~oF}DV%%&5M8gK`B1_`;MSZP&U}6FFbl%@$G%F}GZ3 zjI7325V_vM;OdWL`Km`b2G*@GV!n+lV$3YY*Xqy6Yc#J!k;#bmt4wJEREF;3P!oQG zn51Ylo!eW~EV+z#)z>p%3#j>@G3((TnGxN#qUqMshjj`!EvlBEspb5)ov}lVAqI4# zt4jPK%^7l0n7$EtMUr!@&P3uHdk?wSM^@o$atR?ehwV$dZGk?j1Xqia0ja z7~L_jMyZN4ms89uw{FfvYhOwtSXbNGX`$ueG#^`*lw(;J>)QQYW#_jSeB$rB7#6BO z=rS8wSn_^P;@(w@5;vF0X4(OAVekGiyMM3J0sXoYOL8XkKBi-;I0w6?guB4&eKC~mHT?)0CkgDz>4At|^FwL<^0sg_E(Vs$Dw&9{?118b z)o`=GOzbtkcLA|wV$-#qB|jj2+{W}3Fcb?$clDmh65TrKB-+5Glk%P2$6PBLXkqu4 zNVQSsperLC+biO-Vi62%w%g&88(@33YUj_uAAR6K?$%}h<4cO!?_Jp6;_fwmQ~fdg zrd)pklm_h$5E!#MppR=(QvCo0-^x8mR1OCt{f!+i04_(KZl~~TK1>Z{t9|QrQ5w(` zZq?^^QUdbYVcfHtJPJ)2d_y~=y2=DTNFGs(JRbIlZ&=+at5toJj1xsV36h?7|triugn`M-Y?ADVHop3;-8XoSf3e@FCX;KNbk)ahS^T#`qQz_ zSNSf=p!$jgQ+VSMMqHkW@vheY3X_nv@xbwUcQ*Y&KvG?krXty$%i$YrZw|+D^1!dt zMf+W7YZhtmabPtr#{U{#ksA%i6;fRa^ai!cDk^hj;OK?Z7OKp#$g^sv75*AYlwrpx zjmsu8Zi8=faVw8r8tqL?8FDu4w$77l$i)<>=HFtZXq9OJh*kx~2}x1Q`8W{0=tXg^7}gaV({yVGEsnV z<^H9utw!@v(G}|BoJV0J7U_&Li$k7sm|1CM4p#%SiJ&Cnt;8^xYJ$s zW$qhyQv9|4VIlH;?OI6&$MO>-c6|WT6(;*M$|VpyETG; zT-(3wz;Ew+OAUOEku^V@F7;MDDLLkaV+%n1nS8R=rg%LTril7Z4K-`+7KALnyhRp=>W>+VBUsBbVAy1 zCscyd_qt0S@M;+=>wYENA=URQiNkd-1(6li z7gMPHBeAM&-`SaWTAsM$nyb;v3yGf{*xweLTnIV2cYE|pAYyObps^c$=&C2rceGV5 z4R*h&`T+%J1`rHt{CYkm+d&UI9pbOxR0|c`I}qBdN^F9aX7{F-ohahfy0sPPrk~B@ z0$w{oRj}^Zf_B3|_yE}q6=%DE%ikawy+hp}2r2MuS#Tx2$JrfqJBCJLaxTlSdpf5& zf|WIu`z3($sIA3{7~^V3w0b*K)z10H$Zn(Z#$u}OOtH1%_4=q3D?b<}ikG0kwI(q0R%WtTV+A}1GRz7G&7Iymr)XE` zEAsvn3Lf%)!UXrLO?j+GxLeEJ#^K*4fTGA8uAqURTxuunPGu&-TGPBk}zu}M%ToNxn(JfID zX8ckOwhP;Hc0rU_Pcg4hQKpl0KI~QBI*CfVdf`;z8h?etOtwzngOR}cV?M%Rp0zX> z2WvG~ZD8Sk(a4=hNtLkECN3lpPyN-JUA|q_`1uHRlan@+r!<#KmesF2W{IJ;OJ+3# ze?yB*Ojt~aT5wn(GOW|U2aJfc05p0`k8)cUb7m7wJa{av*pk%agZN5yb2 zx1X4DG#xAwegp^KwIl|(8!_li3Aba({wf=+gus}wp(-B4_Z3BE%Ud5qrXilA_f;3} zyB$60jtNw0*hom%T!mo_SLm0zk+|~0m_S_xJz=vPTS#Qx9Cx166Qph+39pQg6S z6=CrMQ_Og*+z4yzrnA!i_JYb`X)(nQk&WdL8O7=RSS49UIM1=$d$t`1Qy0T7S909B z2VdikLoQEqD%*7LzDPWEIMTK`Wy85UFoG8)q~v5YfmyYk)Wy2OEjAR}_cMfS^^jn- zGr=sXW?)nr)|SCq{WAzIj#DQoaWdF_PwJsnP(kH`6okQ47E)#_m{qzBwdI#E2!>=k zRRjJ*&BQ@daeJEy)0nwcr=;Aj{jwT6KW$A0yKo`bJAYu=!S9eC(Am@P?oZ!};^|KB z7vNXW6=mj5Q{|^ESbznwaAIcQor}M$if%T6IMs#{x}t?7#nxY($z6e%5SbetKmp`*Vb1kg+UL;BRUAru1%vwv64@ z=7<(celcBkqi8E&QHEq_tg06EKHkUiw5q)_rlsAdT?w63W4o-{WyS5BKm3j!-gI}A zy6Q$JTf$4wJO)oY)uc+=Z*9oLIE2E?lfZAWv_*#%=-k7y@1o7dd7B6kHU1wdD~D~yUb4SRpM+2!V0(9Z+f zG?abO0p1_WPS5M-#|=7r_|bA=HG8bHhu=+W12$(x`C=;f8@f}Zm*Ajdz|3|Sa(}-R zVwx^!b1MM$6g0wju5JBSWmh;BlDot$ht<_iLfOQ~6j%cJ zC687@(jl6o!=|V*KL4RcX3&O{i#nMLn=xcbyYW3~b8&lYZ69~wSLuUzP6Mj5OiVKi z1=paJ?;vFvrT<(L@z&;|Ud1fyFjj-Fj+Hgih?UyHFXFjhR5AnQX!#RtKYE1U z!Kl7hR9}39K|#b|OqE@b?ox@9oH^+)i^BUO);yJdXHMb`hr92x`8v>%^8j#dy_*VdJ2kq@CLCvQZR_|wDa~9)0wM(vbN07W{FmVgXXDQ z)Bc+(nUjRyO_#^e{raj4Y41jX(hl%azW^cqK7r~FocUuJBK^y#qJnV)IB)h}XcgafA&V}?=}rpt z@e+iGYjxhnMZy*?p`TeW*UkTP_9kF@o@L#B*f&{9*-8P+rtHuK3I!^Y$s{vmCKEH0 zq%Fb_l4%>7CSj7478F@N5k&+M6>&j8sq6>{f{G#nin7QmiwepjyQ1)cBH#Z!f7ksy z@AJMhDIWjhpa+@feU|&a?rUHF4ktz<@0J+WWgpaR&ot9XSS22PmOt5mBAj2G{c8?- zgtLR<)98{93q+WE!Ej@pGNQ(>Yc3>XZsZ7xvhs&$BPjG6^-Pz@AIiJ>)}2(_+r$+H4Fj zG532s0;q>`ll<(j=mYK9C*IR2J>DA!wEb?&80{NweVr{I;g-2nU46jItPo9{K1^TkUhwJ-`@RHA`0t zd@NE;nMccyX>B}HJ0gHSB>#^&7kW28nkp$uRl<^Aq*FM*`ix-%bjoQM$cE>Q#30j-Y|%5==3P1;6O3yCkw z8H$IY+m;kriq-Vb7>0Y7rgAXqi0V=&Ri(rcJ8R94x7I3;!ZyBv^4NEhkCOfwxFg0g z_FFhqYvU|8URG9)j;oz(t+>RDs+ z3|&KN|9yGO-GaBerP7DOmNzHsLm@q|#n_!58($acX-ogEQ}ykSvnLn-pxu-#q^kAlHL`5OSw zK@oPBY%T)d*)u=M#8|-|wlHSR$h!WV7aDCc3l1jOl~k%byjXj0Fn)^xvkLW>Lnv_Y zmeidUzluwP?m;?G16KiWrT;Y+`r8AtkHhuuvzk$TC*i3>0Tx~MYu+n`Z{?es)GcaR z(}grqI}0@|@8)QFNS%>tpyF^k6uWMKTr@|)e@DJam*8~t*5rg4ry*Tufea0h3eek5 zzbiK@agIi%vU7W0HR`Le-aDu>@&U10#bX6jatOyAH#!*~g}m-(nM`Wc(FA_h*Q{&t zQ5ngu{HLJnp@LJH0{;LhifSWXSVo=mCe%v%bIC{)$4!W7zYKyFr)&xjZcj!<5F4y<-q2^}1Z-z0*@UuqIEbmJreL{P@8SMhxHT}^?K;!*-0~> z*U-Db=~SK2_;DSV8IvYg9q&yP`EY<^T{(Q{7^C}+p&MvN2#cvY&zQoo_ z#qJ|Ckd#G*(9#L()LztWDpdv)&06H#)0>Wa9g~wy4^UXM;8*5{hku z8OJl29U`8dZP(xN^&& z`ToQ6Txt3hp61a#kP)3NmHSbh-zr3la|KU3ei`1cG<+l4Xt^&s-PLh)!Gjia+Dv&X z4bPTmg9kRE>86XU96TJ#?ptG26Fer6Mi%@PLLv&tI*Y3qr?~d!Te6E%-IEoPZ?NV~ z%o5v0-!;;%x5RdNlOP<3W;&f$(*uwA20Q_Z4|1@}eRo%|oYoUvwM!*h-5sp$m2be_ z#s3vw?@+GMz6aZ20Max72v1TcHI_AUrFdDrSXf$Q*obOaWhkm3-Ax@Zfe0Rce%$V)wPE` z`1~-6|0`{l_#UOTC{XK@V$rKPt;t>{1yJI9gafPw(|;>&apm%UkRqa#>uvw7lx}uCcw}b3hW7>2R5>I8f2oG7-Ygt2Btp^h z^=yPfal-L9n74^}@p!!Wj+`W!a%GO8v@md_S|r{Sxb|BrR`GPscjS__SxsQ{ZJcLK zLK&R1&UYq;$sHi6`JJu%bwawGZWcLyBNKNvTOmDz&Pj4fDIuFwgr@fqC1z^l@k@^! znC2{x1qVKcQ>Dx1>n_7XHm5>jS% zX3N5t{S>x9bGTy0P{gz^narQjAIR4@c&gOP2!K;fy+Q?jb}D2dY{~Kz8#Qz%bX! zKggK6;-E9jWH+b7`RF#-nridSo-n#WzrNOazEF_XSldD&kL^Ilm_u>ClTDk6Cj@P+ ztmH}@kug{ctZSk8nw<5vpnuB($5ESd_)@_Fn^$I?4yCCf%GqW}4(%HP9>|9Z!haB_ z5x$xw9Y-ylNOl)mAMiVQK$IP{(&Hhe6$qx_340|Uu{0SupdtgiVcL$~qx_|opeD$F zzZO|T*rzVstiF(UDuwy-L|VU8{@+3CVQQ|d^#37tt##=L*_ZzkqJJgs>`psk1cR^m z9v(&BO>-2$7Iv-rcURz2)%)pYAyTfJs8*iL$AY_qW3J(dcV=DT_LJPMn4L%XA-n&}!{X5Z=;i=D4j}S6Dd3&9G_dL( zUS?XrV0nb&CGzQ8_(0R<38&@a>)~L0Un`}09EexqBdNt=t zPTGmp6?zWVqHMo3Mq_MR7ubIWN)?VQ;WUvICZqh_!Zn;#B7VgsD|q}2z%#vTacQZn zzftt5p|Ci97wB@-S#E<__0L8`b`^{|w(+I)z2$xj!+NN0R}-AaplJ?I1xL7M&(DJw z&Fs;J>^xPX`q?Uvu&C`c0ifi&(7Id3*8q&%Y4>%aw;Two@wQnBYM{2 z;}J>h?LI$xWmRasy4)e{}w{g+-f2wkh}JVN$VBYy)=6?;uzZO;;#C0&93~r=Fl=$Iagi5Bd;0m|dQNXjdYT zeo@E)p=SDsj!*WIrVjnA=ytxd2q!xKbNoq@dmY6Hi46vER2=UY0-|C0zufvo$WL~v zU#YosB)j*3ty~pf&|6SW!myf|F0*|#+F)^iOv{|goj}mhk=7@bCXU`iuV}iD&PDt8 zBYg(lD>}Ae^eJsAgxt5(KX;RF+1jzjCjAWjprm1!fiva{5&$nj(#ET>vitz!Hw$!U z34Z>25k633N&Qf*XA8|PK-5gKAnwoeLmLs!F{#-NcxAR+ZXb9;E6H-zuYp(Y9Jp{o zsA^;MW*$vJeirX;#Atr;HhZJ_^ednN2{oF$dV&{E;prw>R$`YaJRgMJ!3hCY`uE`P zpV=OCUqPC!>#bJJrMoOVcXBbu2fhW?O6S)i1uB9Y-sV?-h9$D*oP6|pzxoV;g)%$4 zbWt5>aq_2o`3H#S=j&Fwbah|yDgt1eXF8~+vIr%+=jqs4($Il7u3$sa=gH;WnD=K2 zZi@DD=~GbX+yW~6k^M_YugdYf7NjK`YR7Jl%e0%P@f0qIbW08byq*sZyYiI3>lCCI zkgV;GguO&~+{2Uz#|v}CvM;cm@mfuS=u(DB*(Tcw$f5#G{xOR`T>r-IwUbkf~;Zn%!q8+>>N2%FrS%Cd(R$J;+XiD&!vrJNKk zXP2vf!4oivXdFdb<*b%s6&8gI6f0+ZQl~1(e zNNoAVKr6PTxaY9JX1Qoso29$5L7J|#ej@PDVQF=KV0Tjvv-3mt`885_I7n+K^Dm6s!fgJucN4a?Y0`^WD zK+IJVc$cKrJu~zD)@c`be#xnom&RRb7do>ZR!8rcknRoCy(|M zs9b{{$l~c^z|*l&?_~y`CKg5;P#i%-^u3e3FU{_kcE{q5x*S=jE9aH~%LJQ>{{1er z=<^}fQS_o}ufBI4KhABLXhZ9AZVm)zd+wnxG)Qy_pKYFN`q-~AZD4gRiDW8x%aVOk zZmT8E&KP?#Pa13WGd+cu?o#;v1|HK@*uEa&v4(oJ*yRqBdM7f8CLx5S0At5AADT|J zT~oC^bsKokPjv8B%>B66aqsSCYncbuE1iWx2Vqk~r89a~ykv$1kJB14KF{07$*IRW znEmIRVhOalw0^ZSu}tG_Xl_WkL7xlJ;I)8%iMHToaPR`<=U?No)Jry`f$Z`70F!AI zi!k}IDNeG~-&C#Za$ej!No19{+`=Nl{9-)V8jF}*TD#3QQlh;QR|CpXf@V489l%DC z#WdV2Z<3I16#PCc)_7V zZH`B9=fPRbu0_U#)NHsIq-Ih8f`H1f3|zT%rXxL?fb|%I8;B zo_H}qy#pjfKj<9ThYsj&`Uf( z+)5Jf_dv(${ZL^PsGmokR9Tqd3*yPXZkYX#D_EQ$Be{wby^QtPVJpS0KmJF#ZTFjI~_HP)v}5_!8)bYwwCS+TdxefaGUIJ$vy2Z#b}3etIpW%&^q6oXmg2ft6vEh^fe<&cT@s)s8wzdoq;O z3(0%rowcQodV@&7Yw&DV%Bj<7&G;J|Pb+u(JRs$(^y_9;eK5>aJaBa)74Lr(#9Qgx z7854wJh?}O-S>o=hM6MNdrI9e!i1TdI&j6oW2+fCC8GWpubJ%>oSL%p(ucUTMXL!_ zXAT0dYxLA=IA4lUwX$`|_-Rqk)+wsrc5bG-`d0ih*~dq3JHDz1fs5 z0uCB!*XWrE5BZr+SqRNZL)X(f0CZ{vxoBFyWu(m{zve1Z(fKpzi&ZR92@kIYj(Ck6 zbywm%Ol3G~y=wenJ|5k49^Xrou)R||V;B4*n)SN0$8r*KZ!7h|08EQ607dg-jcqD@$)6)X_f62OcZQD%gNjc{>L%prF%c$zC z$it}fSSmzi&VTEUrDqQf8amyOIj_#}Y^Y*#oSKci-o|?B)VeuHbosCqym7o{`DnRc zV;w+A&411&HqavXZaT~xuDT6c4h1<26t}Nk^)UarYqLF-^3Oq|Ch|chnkC(*@1%L` z(j#lf@3XFh)3nW(&Oh%w6QEY;J}TjV5(qXsCW&8(tlCPt3ycbuB`jhv$;0!%beWK~ ze^#vNk|zwvPK^}B@!kR~ji)}B{&4#e(v3nL3k}q{n0KqJ(z0_0*?!r;c^MZwopVbT{aXf(vDrz(EeM? zw5rSE-SR*c{?#jwqe!dYeh%fb34-p~Qls`}5b-in+2M8uzmv(BYpf?)UwTpgY;J#+<7oxSRYY3P+#6foDQH;^_6i1nS7YQNbtRd+68|-p0({ePmT?N!epJQ`{sw2Y2 z%_%Ds;WQ@vxCn*7_&uzsfHG6fokrtRT=2`;8tUe{IoZPIKT$=)rsvWJGib9Hv>A&* zrS&g#6;7?3F^rVrg|Rr2f^Tf(M!2+NXHb#)-fNWz@O5}7o{eT)NTrWK?Haj3bxVy$ zY5eZ@(HjP1;#xO84}XKcdZk_2&eAxQu9Ys?BG@{S##D*7pJQ3;iprY6cLBdEmb7^C znK*~ga0pANFucrP%!edQB7(h}?f7?J#Q%I=4Yi`Y8P6ndr-c8r5QTEh`E6Djd0bOy z>mkW#a>S~t*I4lDr8!DzB8Ra8`}2`o3*$wKBx-opScbAOXyiCu#IPH3f}-~1V5U+F zJ*&Y{1$V88$#NzOVvIQ45|Q1qFS;~WsJj(adoKHMY;0YMR@WR3@CEtQ#jr^8{7jpD z4Fp>FTfyn|laIZN0@F?iouo^q2>5nnga}=MUaweMra%(x1+J+=`h_iX<^3<0TbZ#} za5vx@b`fx+f5}FxEXOFu#PBuM)SprURlYtGQ#jr+-X$oWaf-M__s@L?w(dK2eBlIK zGMH#-LL2p3@=uG;GUq`&ZR8+MWPiIRQG$WS462+9)4i4C=ag-x^E)DCZcBM>lLEcV z7lW%jgC@nF=MFbEBC8%uM@Iz2Mm~wynYRP3(^Dusl`{I)TXFV%=dz{M)yp`0PvqSw z>4JCb_*S9AOq%wrWIL}&8#$08pO9J~TBxyZ+} zD`4G*r_b-Zis1k`ZATff`bNt^oS)B^hC_vO=kI`tD(91MH6UBZmcaDwAmJv?& z3@PuppZfr)G61_FxI*y?89Sdc_GGif zg|&|MQk(~#c8#%vYduu6=8Omb5{dXzJ+r6w+AS15i z;mSR+xAL&Ha*V;B?NO-2Z=+S$d~Im4GzHTa8@OHbL*V_1nw8Q>EYv!LoX=}d59(-Y zsNX|duJulUc?@?XxM!|IsY(TO>(6lO^=)g9jlVP$Q-!g1Gu-whrJKItX}n>OKUs!r zRBW||ee6JP2dCbgNXFvrTz98A^WGhO&}S+c4JQBm;M|`F^6tL{We2Kur3sM^w=%bA z=Plci7{MB<+$uI-xC8uWzN$H7!1v{EGQP-y(Kq+&+Sa`ws>1h?AR4Bqh@h9V*e*qn z(8l8KT{Lv#5V2@v&zJFYGt{ZEo!ONfrm*M7w_l2>hoS4*iMQH=bntl}lDCddn*1*K zj@z9UP^{xZ{PwwOA@{I@NwI3}fzBLIu*{(ejZ!X(MIw33uPOhg>KxQJrqPvqz-~<_ z$7Ai9q=9>)r6(X3o;XP3bghr=ls-qX%M8N%FLvAEL*ql0ec}KtYG|Akjem{v&@J6n zuBf}L@|GUumR9$lFlCqdlKU

McQi7d|xBoy>7i^2`=2n4Vu;qC3qtx^ITNUJI3y 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≶h)fptvgA{;Y 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⁢ECyy^~JQ_C}wO`K(p}Zc_&omnB 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 @Uxwh~%l8++ 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

>b-=#n-$ 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

wpNwcG19y&|6@Pv2j`b=l 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%0A<6F8KDxBv-d#= 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

&I|G*2VR{w-vgF{@Ks#e#JZmtiy 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 zð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 QJV8{xtUh;7NjQS|Umj>do&W#< diff --git a/linux_sdk/Makefile b/linux_sdk/Makefile index b718f366..12bac042 100644 --- a/linux_sdk/Makefile +++ b/linux_sdk/Makefile @@ -11,40 +11,36 @@ NAME=server # the location of the vcproj that builds the mod -MOD_PROJ=../dlls/server_scratch-2003.vcproj +MOD_PROJ=../dlls/server_scratch-2005.vcproj # the name of the mod configuration (typically _) MOD_CONFIG=server_sdk_ReleaseSDKWin32 # the directory the base binaries (tier0_i486.so, etc) are located -#GAME_DIR=../../ -#GAME_DIR=~/valve/hl2bin/ -GAME_DIR=~/valve/steam +GAME_DIR=~/srcds # compiler options (gcc 3.4.1 or above is required) -CC=/usr/local/bin/gcc -CPLUS=/usr/local/bin/g++ -CLINK=/usr/local/bin/gcc -CPP_LIB="/usr/local/lib/libstdc++.a /usr/local/lib/libgcc_eh.a" +CC=/usr/bin/gcc +CPLUS=/usr/bin/g++ +CLINK=/usr/bin/gcc +CPP_LIB="/usr/lib/libstdc++.a /usr/lib/libgcc_eh.a" # put any compiler flags you want passed here USER_CFLAGS= # link flags for your mod, make sure to include any special libraries here -LDFLAGS="-lm -ldl $(GAME_DIR)/bin/tier0_i486.so $(GAME_DIR)/bin/vstdlib_i486.so mathlib_i486.a choreoobjects_i486.a tier1_i486.a" +LDFLAGS="-lm -ldl mathlib_i486.a choreoobjects_i486.a tier1_i486.a $(GAME_DIR)/bin/tier0_i486.so $(GAME_DIR)/bin/vstdlib_i486.so" # XERCES 2.6.0 or above ( http://xml.apache.org/xerces-c/ ) is used by the vcproj to makefile converter # it must be installed before being able to run this makefile -XERCES_INC_DIR=/home/alfred/tmp/xerces-c-src_2_6_0/include -XERCES_LIB_DIR=/home/alfred/tmp/xerces-c-src_2_6_0/lib -# if you have xerces installed already you should be able to use the two lines below -#XERCES_INC_DIR=/usr/include -#XERCES_LIB_DIR=/usr/lib +XERCES_INC_DIR=/usr/include +XERCES_LIB_DIR=/usr/lib ############################################################################# # Things below here shouldn't need to be altered ############################################################################# MAKE=make +AR="ar rvs" # the dir we want to put binaries we build into BUILD_DIR=. @@ -54,21 +50,25 @@ BUILD_OBJ_DIR=$(BUILD_DIR)/obj # the location of the source code SOURCE_DIR=.. +# Locations of static library projects +TIER1_PROJ=../tier1/tier1-2005.vcproj +MATHLIB_PROJ=../mathlib/mathlib-2005.vcproj +CHOREO_PROJ=../choreoobjects/choreoobjects-2005.vcproj + # the CPU target for the build, must be i486 for now ARCH=i486 ARCH_CFLAGS=-mtune=i686 -march=pentium3 -mmmx -O3 -# -fpermissive is so gcc 3.4.x doesn't complain about some template stuff -BASE_CFLAGS=-fpermissive -D_LINUX -DNDEBUG -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp +BASE_CFLAGS=-D_LINUX -DNDEBUG -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp -Wall -Wno-non-virtual-dtor -Wno-invalid-offsetof SHLIBEXT=so SHLIBCFLAGS=-fPIC SHLIBLDFLAGS=-shared -Wl,-Map,$@_map.txt -Wl #flags passed to the c compiler -CFLAGS="$(USER_CFLAGS) $(DEFINES) $(ARCH_CFLAGS) $(BASE_CFLAGS) -Usprintf=use_Q_snprintf_instead_of_sprintf -Ustrncpy=use_Q_strncpy_instead -UPROTECTED_THINGS_ENABLE" +CFLAGS="$(USER_CFLAGS) $(DEFINES) $(ARCH_CFLAGS) $(BASE_CFLAGS) -Usprintf -Ustrncpy -UPROTECTED_THINGS_ENABLE" # define list passed to make for the sub makefile -BASE_DEFINES=CC=$(CC) CPLUS=$(CPLUS) CPP_LIB=$(CPP_LIB) \ +BASE_DEFINES=CC=$(CC) AR=$(AR) CPLUS=$(CPLUS) CPP_LIB=$(CPP_LIB) \ BUILD_DIR=$(BUILD_DIR) BUILD_OBJ_DIR=$(BUILD_OBJ_DIR) \ SOURCE_DIR=$(SOURCE_DIR) SHLIBLDFLAGS=$(SHLIBLDFLAGS) SHLIBEXT=$(SHLIBEXT) \ CLINK=$(CLINK) CFLAGS=$(CFLAGS) LDFLAGS=$(LDFLAGS) \ @@ -76,18 +76,23 @@ BASE_DEFINES=CC=$(CC) CPLUS=$(CPLUS) CPP_LIB=$(CPP_LIB) \ XERCES_INC_DIR=$(XERCES_INC_DIR) XERCES_LIB_DIR=$(XERCES_LIB_DIR) # Project Makefile -MAKE_MOD=Makefile.mod +MAKE_MOD=Makefile.game MAKE_VCPM=Makefile.vcpm MAKE_PLUGIN=Makefile.plugin +MAKE_TIER1=Makefile.tier1 +MAKE_MATH=Makefile.mathlib +MAKE_CHOREO=Makefile.choreoobjects all: check vcpm mod check: if [ -z "$(CC)" ]; then echo "Compiler not defined."; exit; fi - if [ ! -d $(BUILD_DIR) ];then mkdir $(BUILD_DIR);fi + if [ ! -d $(BUILD_DIR) ];then mkdir -p $(BUILD_DIR);fi cd $(BUILD_DIR) vcpm: + if [ ! -f "tier0_i486.so" ]; then ln -s $(GAME_DIR)/bin/tier0_i486.so .; fi + if [ ! -f "vstdlib_i486.so" ]; then ln -s $(GAME_DIR)/bin/vstdlib_i486.so .; fi $(MAKE) -f $(MAKE_VCPM) $(BASE_DEFINES) mod: vcpm @@ -97,10 +102,23 @@ mod: vcpm $(MAKE) -f $(MAKE_MOD) $(BASE_DEFINES) plugin: + if [ ! -f "tier0_i486.so" ]; then ln -s $(GAME_DIR)/bin/tier0_i486.so .; fi + if [ ! -f "vstdlib_i486.so" ]; then ln -s $(GAME_DIR)/bin/vstdlib_i486.so .; fi $(MAKE) -f $(MAKE_PLUGIN) $(BASE_DEFINES) + +tier1: + $(MAKE) -f $(MAKE_TIER1) $(BASE_DEFINES) + +mathlib: + $(MAKE) -f $(MAKE_MATH) $(BASE_DEFINES) + +choreoobjects: + $(MAKE) -f $(MAKE_CHOREO) $(BASE_DEFINES) clean: - $(MAKE) -f $(MAKE_VCPM) $(BASE_DEFINES) clean - $(MAKE) -f $(MAKE_PLUGIN) $(BASE_DEFINES) clean - $(MAKE) -f $(MAKE_MOD) $(BASE_DEFINES) clean - + $(MAKE) -f $(MAKE_VCPM) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_PLUGIN) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_MOD) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_TIER1) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_MATH) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_CHOREO) $(BASE_DEFINES) clean diff --git a/linux_sdk/Makefile.choreoobjects b/linux_sdk/Makefile.choreoobjects new file mode 100644 index 00000000..ab93231e --- /dev/null +++ b/linux_sdk/Makefile.choreoobjects @@ -0,0 +1,45 @@ +# +# Choreoobjects Static Library Makefile +# + +CHOREOOBJECTS_SRC_DIR=$(SOURCE_DIR)/choreoobjects +GAMESHARED_SRC_DIR=$(SOURCE_DIR)/game_shared +PUBLIC_SRC_DIR=$(SOURCE_DIR)/public +TIER1_PUBLIC_SRC_DIR=$(SOURCE_DIR)/public/tier1 + +CHOREOOBJECTS_OBJ_DIR=$(BUILD_OBJ_DIR)/choreoobjects_$(ARCH) + +# Extension of linux static library +override SHLIBEXT=a + +CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) +#DEBUG = -g -ggdb +#CFLAGS+= $(DEBUG) + +INCLUDEDIRS=-I$(GAMESHARED_SRC_DIR) -I$(PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -D_LIB -DCHOREOOBJECTS_STATIC_LIB + +DO_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) -DARCH=$(ARCH) -o $@ -c $< + +##################################################################### + +CHOREOOBJECTS_OBJS= \ + $(CHOREOOBJECTS_OBJ_DIR)/choreoactor.o \ + $(CHOREOOBJECTS_OBJ_DIR)/choreochannel.o \ + $(CHOREOOBJECTS_OBJ_DIR)/choreoevent.o \ + $(CHOREOOBJECTS_OBJ_DIR)/choreoscene.o \ + +all: dirs choreoobjects_$(ARCH).$(SHLIBEXT) + +dirs: + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(CHOREOOBJECTS_OBJ_DIR) + $(CHECK_DSP) $(SOURCE_DSP) + +choreoobjects_$(ARCH).$(SHLIBEXT): $(CHOREOOBJECTS_OBJS) + $(AR) $(BUILD_DIR)/$@ $(CHOREOOBJECTS_OBJS) + +$(CHOREOOBJECTS_OBJ_DIR)/%.o: $(CHOREOOBJECTS_SRC_DIR)/%.cpp + $(DO_CC) + +clean: + -rm -rf $(CHOREOOBJECTS_OBJ_DIR) diff --git a/linux_sdk/Makefile.game b/linux_sdk/Makefile.game index 80f8eebe..2a7cdad7 100644 --- a/linux_sdk/Makefile.game +++ b/linux_sdk/Makefile.game @@ -7,14 +7,14 @@ # PROJECT MAKEFILES ############################################################################# MAKE_FILE=Makefile.$(MOD_CONFIG) -include $(MAKE_FILE) +-include $(MAKE_FILE) ############################################################################# -# The compiler command lne for each src code file to compile +# The compiler command line for each src code file to compile ############################################################################# -DO_CC=$(CPLUS) -w $(INCLUDES) $(CFLAGS) -o $@ -c $< +DO_CC=$(CPLUS) $(INCLUDES) $(CFLAGS) -o $@ -c $< clean: - rm -rf obj/$(NAME) + rm -rf obj/$(NAME)_$(ARCH) rm -f $(NAME)_$(ARCH).$(SHLIBEXT) diff --git a/linux_sdk/Makefile.mathlib b/linux_sdk/Makefile.mathlib new file mode 100644 index 00000000..f1d8c413 --- /dev/null +++ b/linux_sdk/Makefile.mathlib @@ -0,0 +1,45 @@ +# +# Mathlin Static Library Makefile +# + +MATHLIB_SRC_DIR=$(SOURCE_DIR)/mathlib +PUBLIC_SRC_DIR=$(SOURCE_DIR)/public +MATHLIB_PUBLIC_SRC_DIR=$(SOURCE_DIR)/public/mathlib + +MATHLIB_OBJ_DIR=$(BUILD_OBJ_DIR)/mathlib_$(ARCH) + +# Extension of linux static library +override SHLIBEXT=a + +CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) +#DEBUG = -g -ggdb +#CFLAGS+= $(DEBUG) + +INCLUDEDIRS=-I$(PUBLIC_SRC_DIR) -I$(MATHLIB_PUBLIC_SRC_DIR) -D_LIB + +DO_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) -DARCH=$(ARCH) -o $@ -c $< + +##################################################################### + +MATHLIB_OBJS= \ + $(MATHLIB_OBJ_DIR)/halton.o \ + $(MATHLIB_OBJ_DIR)/lightdesc.o \ + $(MATHLIB_OBJ_DIR)/mathlib_base.o \ + $(MATHLIB_OBJ_DIR)/powsse.o \ + $(MATHLIB_OBJ_DIR)/sseconst.o \ + +all: dirs mathlib_$(ARCH).$(SHLIBEXT) + +dirs: + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(MATHLIB_OBJ_DIR) + $(CHECK_DSP) $(SOURCE_DSP) + +mathlib_$(ARCH).$(SHLIBEXT): $(MATHLIB_OBJS) + $(AR) $(BUILD_DIR)/$@ $(MATHLIB_OBJS) + +$(MATHLIB_OBJ_DIR)/%.o: $(MATHLIB_SRC_DIR)/%.cpp + $(DO_CC) + +clean: + -rm -rf $(MATHLIB_OBJ_DIR) diff --git a/linux_sdk/Makefile.plugin b/linux_sdk/Makefile.plugin index 67589549..fff36d39 100644 --- a/linux_sdk/Makefile.plugin +++ b/linux_sdk/Makefile.plugin @@ -17,9 +17,9 @@ CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) #DEBUG = -g -ggdb #CFLAGS+= $(DEBUG) -INCLUDEDIRS=-I$(PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -Dstrcmpi=strcasecmp -D_alloca=alloca +INCLUDEDIRS=-I$(PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -Dstrcmpi=strcasecmp -D_alloca=alloca -DO_CC=$(CPLUS) $(INCLUDEDIRS) -w $(CFLAGS) -DARCH=$(ARCH) -o $@ -c $< +DO_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) -DARCH=$(ARCH) -o $@ -c $< ##################################################################### @@ -29,15 +29,15 @@ PLUGIN_OBJS = \ $(PLUGIN_OBJ_DIR)/serverplugin_bot.o \ TIER0_OBJS = \ - $(TIER0_OBJ_DIR)/memoverride-vc7.o \ + $(TIER0_OBJ_DIR)/memoverride.o \ all: dirs serverplugin_empty_$(ARCH).$(SHLIBEXT) dirs: - -mkdir $(BUILD_OBJ_DIR) - -mkdir $(PLUGIN_OBJ_DIR) - -mkdir $(PUBLIC_OBJ_DIR) - -mkdir $(TIER0_OBJ_DIR) + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(PLUGIN_OBJ_DIR) + -mkdir -p $(PUBLIC_OBJ_DIR) + -mkdir -p $(TIER0_OBJ_DIR) $(CHECK_DSP) $(SOURCE_DSP) serverplugin_empty_$(ARCH).$(SHLIBEXT): $(PLUGIN_OBJS) $(TIER0_OBJS) @@ -51,4 +51,4 @@ $(TIER0_OBJ_DIR)/%.o: $(TIER0_PUBLIC_SRC_DIR)/%.cpp clean: -rm -rf $(PLUGIN_OBJ_DIR) - -rm -f plugin_$(ARCH).$(SHLIBEXT) + -rm -f serverplugin_empty_$(ARCH).$(SHLIBEXT) diff --git a/linux_sdk/Makefile.tier1 b/linux_sdk/Makefile.tier1 new file mode 100644 index 00000000..a0c08789 --- /dev/null +++ b/linux_sdk/Makefile.tier1 @@ -0,0 +1,66 @@ +# +# Tier1 Static Library Makefile +# + +TIER1_SRC_DIR=$(SOURCE_DIR)/tier1 +COMMON_SRC_DIR=$(SOURCE_DIR)/common +PUBLIC_SRC_DIR=$(SOURCE_DIR)/public +TIER1_PUBLIC_SRC_DIR=$(SOURCE_DIR)/public/tier1 + +TIER1_OBJ_DIR=$(BUILD_OBJ_DIR)/tier1_$(ARCH) + +# Extension of linux static library +override SHLIBEXT=a + +CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) +#DEBUG = -g -ggdb +#CFLAGS+= $(DEBUG) + +INCLUDEDIRS=-I$(COMMON_SRC_DIR) -I$(PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -D_LIB -DTIER1_STATIC_LIB + +DO_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) -DARCH=$(ARCH) -o $@ -c $< + +##################################################################### + +TIER1_OBJS= \ + $(TIER1_OBJ_DIR)/bitbuf.o \ + $(TIER1_OBJ_DIR)/byteswap.o \ + $(TIER1_OBJ_DIR)/characterset.o \ + $(TIER1_OBJ_DIR)/checksum_crc.o \ + $(TIER1_OBJ_DIR)/checksum_md5.o \ + $(TIER1_OBJ_DIR)/convar.o \ + $(TIER1_OBJ_DIR)/datamanager.o \ + $(TIER1_OBJ_DIR)/diff.o \ + $(TIER1_OBJ_DIR)/generichash.o \ + $(TIER1_OBJ_DIR)/interface.o \ + $(TIER1_OBJ_DIR)/jobthread.o \ + $(TIER1_OBJ_DIR)/KeyValues.o \ + $(TIER1_OBJ_DIR)/mempool.o \ + $(TIER1_OBJ_DIR)/memstack.o \ + $(TIER1_OBJ_DIR)/NetAdr.o \ + $(TIER1_OBJ_DIR)/processor_detect.o \ + $(TIER1_OBJ_DIR)/rangecheckedvar.o \ + $(TIER1_OBJ_DIR)/stringpool.o \ + $(TIER1_OBJ_DIR)/strtools.o \ + $(TIER1_OBJ_DIR)/tier1.o \ + $(TIER1_OBJ_DIR)/tokenreader.o \ + $(TIER1_OBJ_DIR)/utlbuffer.o \ + $(TIER1_OBJ_DIR)/utlstring.o \ + $(TIER1_OBJ_DIR)/utlsymbol.o \ + $(TIER1_OBJ_DIR)/xboxstubs.o \ + +all: dirs tier1_$(ARCH).$(SHLIBEXT) + +dirs: + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(TIER1_OBJ_DIR) + $(CHECK_DSP) $(SOURCE_DSP) + +tier1_$(ARCH).$(SHLIBEXT): $(TIER1_OBJS) + $(AR) $(BUILD_DIR)/$@ $(TIER1_OBJS) + +$(TIER1_OBJ_DIR)/%.o: $(TIER1_SRC_DIR)/%.cpp + $(DO_CC) + +clean: + -rm -rf $(TIER1_OBJ_DIR) diff --git a/linux_sdk/Makefile.vcpm b/linux_sdk/Makefile.vcpm index 10446a58..f85cf4dd 100644 --- a/linux_sdk/Makefile.vcpm +++ b/linux_sdk/Makefile.vcpm @@ -14,12 +14,12 @@ VCPM_OBJ_DIR=$(BUILD_OBJ_DIR)/vcpm TIER1_OBJ_DIR=$(BUILD_OBJ_DIR)/vcpm/public #we use custome CFLAGS because the base ones interfere with XERCES -CFLAGS= -w -fpermissive -D_LINUX -DNDEBUG -D_alloca=alloca -D_snprintf=snprintf -D_vsnprintf=vsnprintf $(ARCH_CFLAGS) +CFLAGS=-D_LINUX -DNDEBUG -D_alloca=alloca -D_snprintf=snprintf -D_vsnprintf=vsnprintf -Wall -Wno-non-virtual-dtor -Wno-invalid-offsetof -Werror $(ARCH_CFLAGS) #DEBUG = -g -ggdb #CFLAGS+= $(DEBUG) INCLUDEDIRS=-I$(PUBLIC_SRC_DIR) -I$(XERCES_INC_DIR) -I$(UTIL_COMMON_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -LDFLAGS_VC=-lm -ldl -L$(XERCES_LIB_DIR) -lxerces-c $(GAME_DIR)/bin/tier0_i486.so $(GAME_DIR)/bin/vstdlib_i486.so +LDFLAGS_VC=-lm -ldl -L$(XERCES_LIB_DIR) -lxerces-c tier1_i486.a $(GAME_DIR)/bin/tier0_i486.so $(GAME_DIR)/bin/vstdlib_i486.so DO_CC=$(CPLUS) $(INCLUDEDIRS) -w $(CFLAGS) -DARCH=$(ARCH) -o $@ -c $< @@ -30,31 +30,18 @@ VCPM_OBJS = \ $(VCPM_OBJ_DIR)/vprojtomake.o \ $(VCPM_OBJ_DIR)/vcprojconvert.o \ -TIER1_OBJS = \ - $(TIER1_OBJ_DIR)/characterset.o \ - $(TIER1_OBJ_DIR)/interface.o \ - $(TIER1_OBJ_DIR)/generichash.o \ - $(TIER1_OBJ_DIR)/KeyValues.o \ - $(TIER1_OBJ_DIR)/stringpool.o \ - $(TIER1_OBJ_DIR)/utlbuffer.o \ - $(TIER1_OBJ_DIR)/utlsymbol.o \ - all: dirs vcpm dirs: - -mkdir $(BUILD_OBJ_DIR) - -mkdir $(VCPM_OBJ_DIR) - -mkdir $(TIER1_OBJ_DIR) + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(VCPM_OBJ_DIR) -vcpm: $(VCPM_OBJS) $(TIER1_OBJS) - $(CLINK) $(DEBUG) -o $(BUILD_DIR)/$@ $(VCPM_OBJS) $(TIER1_OBJS) $(CPP_LIB) $(LDFLAGS_VC) +vcpm: $(VCPM_OBJS) + $(CLINK) $(DEBUG) -o $(BUILD_DIR)/$@ $(VCPM_OBJS) $(CPP_LIB) $(LDFLAGS_VC) $(VCPM_OBJ_DIR)/%.o: $(VCPM_SRC_DIR)/%.cpp $(DO_CC) -$(TIER1_OBJ_DIR)/%.o: $(TIER1_SRC_DIR)/%.cpp - $(DO_CC) -Dstricmp=strcasecmp -Dstrcmpi=strcasecmp - clean: -rm -rf $(VCPM_OBJ_DIR) -rm -f vcpm diff --git a/materialsystem/stdshaders/stdshader_dx8-2003.vcproj b/materialsystem/stdshaders/stdshader_dx8-2003.vcproj index 16ca7b37..3dfeebbe 100644 --- a/materialsystem/stdshaders/stdshader_dx8-2003.vcproj +++ b/materialsystem/stdshaders/stdshader_dx8-2003.vcproj @@ -42,12 +42,12 @@ CompileAs="0"/> + Outputs=""bin\game_shader_dx8.dll""/> + Outputs=""bin\game_shader_dx8.dll""/> + Outputs=""bin\game_shader_dx9.dll""/> + Outputs=""bin\game_shader_dx9.dll""/> (255 * pow ( i/255.f, g1 )); if (inf < 0) inf = 0; if (inf > 255) @@ -3035,7 +3043,7 @@ void BuildGammaTable( float gamma, float texGamma, float brightness, int overbri f = 0.125 + ((f - g3) / (1.0 - g3)) * 0.875; // convert linear space to desired gamma space - inf = 255 * pow ( f, g ); + inf = static_cast(255 * pow ( f, g )); if (inf < 0) inf = 0; @@ -3069,7 +3077,7 @@ void BuildGammaTable( float gamma, float texGamma, float brightness, int overbri for (i=0 ; i<1024 ; i++) { // convert from linear space (0..1) to nonlinear texture space (0..255) - lineartotexture[i] = pow( i / 1023.0, 1.0 / texGamma ) * 255; + lineartotexture[i] = static_cast(pow( i / 1023.0, 1.0 / texGamma ) * 255); } BuildExponentTable(); @@ -3186,7 +3194,7 @@ int LinearToTexture( float f ) { Assert( s_bMathlibInitialized ); int i; - i = f * 1023; // assume 0..1 range + i = static_cast(f * 1023); // assume 0..1 range if (i < 0) i = 0; if (i > 1023) @@ -3201,7 +3209,7 @@ int LinearToScreenGamma( float f ) { Assert( s_bMathlibInitialized ); int i; - i = f * 1023; // assume 0..1 range + i = static_cast(f * 1023); // assume 0..1 range if (i < 0) i = 0; if (i > 1023) @@ -4303,7 +4311,9 @@ void Hermite_SplineBasis( float t, float basis[4] ) //----------------------------------------------------------------------------- // BUG: the VectorSubtract()'s calls go away if the global optimizer is enabled +#ifdef _MSC_VER #pragma optimize( "g", off ) +#endif void Hermite_Spline( const Vector &p0, const Vector &p1, const Vector &p2, float t, Vector& output ) { @@ -4313,7 +4323,9 @@ void Hermite_Spline( const Vector &p0, const Vector &p1, const Vector &p2, float Hermite_Spline( p1, p2, e10, e21, t, output ); } +#ifdef _MSC_VER #pragma optimize( "", on ) +#endif float Hermite_Spline( float p0, float p1, float p2, float t ) { @@ -5060,7 +5072,9 @@ bool CalcLineToLineIntersectionSegment( return true; } +#ifdef _MSC_VER #pragma optimize( "", off ) +#endif // stuff from windows.h #ifndef _XBOX @@ -5071,8 +5085,9 @@ typedef unsigned int DWORD; #define EXCEPTION_EXECUTE_HANDLER 1 #endif - +#ifdef _MSC_VER #pragma optimize( "", on ) +#endif static bool s_b3DNowEnabled = false; static bool s_bMMXEnabled = false; diff --git a/notes.txt b/notes.txt new file mode 100644 index 00000000..14da7cd3 --- /dev/null +++ b/notes.txt @@ -0,0 +1,18 @@ +********* +* About * +********* +This version of the Source/Half-Life 2 SDK attempts to provide support for GCC 4.x and remove as many +warnings as possible when compiling under Windows or Linux. In addition, the -fpermissive option has +been removed from the Linux makefiles and -Wall has been added. + +********* +* Notes * +********* +1. The file "Makefile.mod" located in the linux_sdk directory has been renamed to "Makefile.game" in + order to avoid potential conflicts with the Modula-2 compiler. GNU Make may issue an error about the + file with its original name and try to execute m2c (the Modula-2 compiler program). + +2. Makefiles for choreoobjects_i486.a, mathlib_i486.a, and tier1_i486.a have been added in the linux_sdk + directory to ease compiling of these static libraries on Linux if it ever becomes necessary. + +3. The SDK does not currently compile with GCC 4.2. This will be addressed in the future. diff --git a/public/IceKey.cpp b/public/IceKey.cpp index 54ac5c46..e41e87d0 100644 --- a/public/IceKey.cpp +++ b/public/IceKey.cpp @@ -6,8 +6,9 @@ #include "IceKey.H" +#ifdef _MSC_VER #pragma warning(disable: 4244) - +#endif /* Structure of a single round subkey */ class IceSubkey { diff --git a/public/ScratchPadUtils.h b/public/ScratchPadUtils.h index 9f2e8cc2..ed47c65d 100644 --- a/public/ScratchPadUtils.h +++ b/public/ScratchPadUtils.h @@ -64,6 +64,7 @@ private: class CLineInfo { public: + CLineInfo() : m_flLastTime(0.0f), m_flLastValue(0.0f) { }; bool m_bFirst; float m_flLastTime; float m_flLastValue; diff --git a/public/SoundEmitterSystem/isoundemittersystembase.h b/public/SoundEmitterSystem/isoundemittersystembase.h index 0092bcde..7deb861d 100644 --- a/public/SoundEmitterSystem/isoundemittersystembase.h +++ b/public/SoundEmitterSystem/isoundemittersystembase.h @@ -106,8 +106,8 @@ struct sound_interval_t T range; interval_t &ToInterval( interval_t &dest ) const { dest.start = start; dest.range = range; return dest; } - void FromInterval( const interval_t &from ) { start = from.start; range = from.range; } - float Random() const { interval_t temp = { start, range }; return RandomInterval( temp ); } + void FromInterval( const interval_t &from ) { start = static_cast(from.start); range = static_cast(from.range); } + float Random() const { interval_t temp = { start, range }; return this->RandomInterval( temp ); } }; @@ -125,7 +125,7 @@ struct CSoundParametersInternal void CopyFrom( const CSoundParametersInternal& src ); - bool CSoundParametersInternal::operator == ( const CSoundParametersInternal& other ) const; + bool operator == ( const CSoundParametersInternal& other ) const; const char *VolumeToString( void ) const; const char *ChannelToString( void ) const; @@ -148,9 +148,9 @@ struct CSoundParametersInternal bool ShouldPreload() const { return m_bShouldPreload; } void SetChannel( int newChannel ) { channel = newChannel; } - void SetVolume( float start, float range = 0.0 ) { volume.start = start; volume.range = range; } - void SetPitch( float start, float range = 0.0 ) { pitch.start = start; pitch.range = range; } - void SetSoundLevel( float start, float range = 0.0 ) { soundlevel.start = start; soundlevel.range = range; } + void SetVolume( float start, float range = 0.0 ) { volume.start = static_cast(start); volume.range = static_cast(range); } + void SetPitch( float start, float range = 0.0 ) { pitch.start = static_cast(start); pitch.range = static_cast(range); } + void SetSoundLevel( float start, float range = 0.0 ) { soundlevel.start = static_cast(start); soundlevel.range = static_cast(range); } void SetDelayMsec( int delay ) { delay_msec = delay; } void SetShouldPreload( bool bShouldPreload ) { m_bShouldPreload = bShouldPreload; } void SetOnlyPlayToOwner( bool b ) { play_to_owner_only = b; } diff --git a/public/SoundEmitterSystem/isoundemittersystembasev1.h b/public/SoundEmitterSystem/isoundemittersystembasev1.h index d16158b2..5883a0ba 100644 --- a/public/SoundEmitterSystem/isoundemittersystembasev1.h +++ b/public/SoundEmitterSystem/isoundemittersystembasev1.h @@ -84,7 +84,7 @@ struct CSoundParametersInternal // CSoundParametersInternal( const CSoundParametersInternal& src ); bool CompareInterval( const interval_t& i1, const interval_t& i2 ) const; - bool CSoundParametersInternal::operator == ( const CSoundParametersInternal& other ) const; + bool operator == ( const CSoundParametersInternal& other ) const; const char *VolumeToString( void ); const char *ChannelToString( void ); diff --git a/public/UnicodeFileHelpers.cpp b/public/UnicodeFileHelpers.cpp index b01c1c03..dee10a10 100644 --- a/public/UnicodeFileHelpers.cpp +++ b/public/UnicodeFileHelpers.cpp @@ -1,240 +1,240 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include -#include -#include "UtlBuffer.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: Advances until non-whitespace hit -//----------------------------------------------------------------------------- -wchar_t *AdvanceOverWhitespace(wchar_t *Start) -{ - while (*Start != 0 && iswspace(*Start)) - { - Start++; - } - - return Start; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -wchar_t *ReadUnicodeToken(wchar_t *start, wchar_t *token, int tokenBufferSize, bool "ed) -{ - // skip over any whitespace - start = AdvanceOverWhitespace(start); - quoted = false; - *token = 0; - - if (!*start) - { - return start; - } - - // check to see if it's a quoted string - if (*start == '\"') - { - quoted = true; - // copy out the string until we hit an end quote - start++; - int count = 0; - while (*start && *start != '\"' && count < tokenBufferSize) - { - // check for special characters - if (*start == '\\' && *(start+1) == 'n') - { - start++; - *token = '\n'; - } - else if (*start == '\\' && *(start+1) == '\"') - { - start++; - *token = '\"'; - } - else - { - *token = *start; - } - - start++; - token++; - count++; - } - - if (*start == '\"') - { - start++; - } - } - else - { - // copy out the string until we hit a whitespace - int count = 0; - while (*start && !iswspace(*start) && count < tokenBufferSize) - { - // no checking for special characters if it's not a quoted string - *token = *start; - - start++; - token++; - count++; - } - } - - *token = 0; - return start; -} - -//----------------------------------------------------------------------------- -// Purpose: Same as above but no translation of \n -//----------------------------------------------------------------------------- -wchar_t *ReadUnicodeTokenNoSpecial(wchar_t *start, wchar_t *token, int tokenBufferSize, bool "ed) -{ - // skip over any whitespace - start = AdvanceOverWhitespace(start); - quoted = false; - *token = 0; - - if (!*start) - { - return start; - } - - // check to see if it's a quoted string - if (*start == '\"') - { - quoted = true; - // copy out the string until we hit an end quote - start++; - int count = 0; - while (*start && *start != '\"' && count < tokenBufferSize) - { - // check for special characters - /* - if (*start == '\\' && *(start+1) == 'n') - { - start++; - *token = '\n'; - } - else - */ - if (*start == '\\' && *(start+1) == '\"') - { - start++; - *token = '\"'; - } - else - { - *token = *start; - } - - start++; - token++; - count++; - } - - if (*start == '\"') - { - start++; - } - } - else - { - // copy out the string until we hit a whitespace - int count = 0; - while (*start && !iswspace(*start) && count < tokenBufferSize) - { - // no checking for special characters if it's not a quoted string - *token = *start; - - start++; - token++; - count++; - } - } - - *token = 0; - return start; -} - -//----------------------------------------------------------------------------- -// Purpose: Returns the first character after the next EOL characters -//----------------------------------------------------------------------------- -wchar_t *ReadToEndOfLine(wchar_t *start) -{ - if (!*start) - return start; - - while (*start) - { - if (*start == 0x0D || *start== 0x0A) - break; - start++; - } - - while (*start == 0x0D || *start== 0x0A) - start++; - - return start; -} - -//----------------------------------------------------------------------------- -// Purpose: file writing -//----------------------------------------------------------------------------- -void WriteUnicodeString(CUtlBuffer &buf, const wchar_t *string, bool addQuotes) -{ - if (addQuotes) - { - buf.PutUnsignedShort('\"'); - } - - for (const wchar_t *ws = string; *ws != 0; ws++) - { - // handle special characters - if (addQuotes && *ws == '\"') - { - buf.PutUnsignedShort('\\'); - } - // write the character - buf.PutUnsignedShort(*ws); - } - - if (addQuotes) - { - buf.PutUnsignedShort('\"'); - } -} - -//----------------------------------------------------------------------------- -// Purpose: file writing -//----------------------------------------------------------------------------- -void WriteAsciiStringAsUnicode(CUtlBuffer &buf, const char *string, bool addQuotes) -{ - if (addQuotes) - { - buf.PutUnsignedShort('\"'); - } - - for (const char *sz = string; *sz != 0; sz++) - { - // handle special characters - if (addQuotes && *sz == '\"') - { - buf.PutUnsignedShort('\\'); - } - buf.PutUnsignedShort(*sz); - } - - if (addQuotes) - { - buf.PutUnsignedShort('\"'); - } -} +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include +#include +#include "utlbuffer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Advances until non-whitespace hit +//----------------------------------------------------------------------------- +wchar_t *AdvanceOverWhitespace(wchar_t *Start) +{ + while (*Start != 0 && iswspace(*Start)) + { + Start++; + } + + return Start; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +wchar_t *ReadUnicodeToken(wchar_t *start, wchar_t *token, int tokenBufferSize, bool "ed) +{ + // skip over any whitespace + start = AdvanceOverWhitespace(start); + quoted = false; + *token = 0; + + if (!*start) + { + return start; + } + + // check to see if it's a quoted string + if (*start == '\"') + { + quoted = true; + // copy out the string until we hit an end quote + start++; + int count = 0; + while (*start && *start != '\"' && count < tokenBufferSize) + { + // check for special characters + if (*start == '\\' && *(start+1) == 'n') + { + start++; + *token = '\n'; + } + else if (*start == '\\' && *(start+1) == '\"') + { + start++; + *token = '\"'; + } + else + { + *token = *start; + } + + start++; + token++; + count++; + } + + if (*start == '\"') + { + start++; + } + } + else + { + // copy out the string until we hit a whitespace + int count = 0; + while (*start && !iswspace(*start) && count < tokenBufferSize) + { + // no checking for special characters if it's not a quoted string + *token = *start; + + start++; + token++; + count++; + } + } + + *token = 0; + return start; +} + +//----------------------------------------------------------------------------- +// Purpose: Same as above but no translation of \n +//----------------------------------------------------------------------------- +wchar_t *ReadUnicodeTokenNoSpecial(wchar_t *start, wchar_t *token, int tokenBufferSize, bool "ed) +{ + // skip over any whitespace + start = AdvanceOverWhitespace(start); + quoted = false; + *token = 0; + + if (!*start) + { + return start; + } + + // check to see if it's a quoted string + if (*start == '\"') + { + quoted = true; + // copy out the string until we hit an end quote + start++; + int count = 0; + while (*start && *start != '\"' && count < tokenBufferSize) + { + // check for special characters + /* + if (*start == '\\' && *(start+1) == 'n') + { + start++; + *token = '\n'; + } + else + */ + if (*start == '\\' && *(start+1) == '\"') + { + start++; + *token = '\"'; + } + else + { + *token = *start; + } + + start++; + token++; + count++; + } + + if (*start == '\"') + { + start++; + } + } + else + { + // copy out the string until we hit a whitespace + int count = 0; + while (*start && !iswspace(*start) && count < tokenBufferSize) + { + // no checking for special characters if it's not a quoted string + *token = *start; + + start++; + token++; + count++; + } + } + + *token = 0; + return start; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the first character after the next EOL characters +//----------------------------------------------------------------------------- +wchar_t *ReadToEndOfLine(wchar_t *start) +{ + if (!*start) + return start; + + while (*start) + { + if (*start == 0x0D || *start== 0x0A) + break; + start++; + } + + while (*start == 0x0D || *start== 0x0A) + start++; + + return start; +} + +//----------------------------------------------------------------------------- +// Purpose: file writing +//----------------------------------------------------------------------------- +void WriteUnicodeString(CUtlBuffer &buf, const wchar_t *string, bool addQuotes) +{ + if (addQuotes) + { + buf.PutUnsignedShort('\"'); + } + + for (const wchar_t *ws = string; *ws != 0; ws++) + { + // handle special characters + if (addQuotes && *ws == '\"') + { + buf.PutUnsignedShort('\\'); + } + // write the character + buf.PutUnsignedShort(*ws); + } + + if (addQuotes) + { + buf.PutUnsignedShort('\"'); + } +} + +//----------------------------------------------------------------------------- +// Purpose: file writing +//----------------------------------------------------------------------------- +void WriteAsciiStringAsUnicode(CUtlBuffer &buf, const char *string, bool addQuotes) +{ + if (addQuotes) + { + buf.PutUnsignedShort('\"'); + } + + for (const char *sz = string; *sz != 0; sz++) + { + // handle special characters + if (addQuotes && *sz == '\"') + { + buf.PutUnsignedShort('\\'); + } + buf.PutUnsignedShort(*sz); + } + + if (addQuotes) + { + buf.PutUnsignedShort('\"'); + } +} diff --git a/public/UtlCachedFileData.h b/public/UtlCachedFileData.h index 8c3a00b4..84594187 100644 --- a/public/UtlCachedFileData.h +++ b/public/UtlCachedFileData.h @@ -61,13 +61,13 @@ public: m_pszRepositoryFileName( repositoryFileName ), m_nVersion( version ), m_pfnMetaChecksum( checksumfunc ), - m_bDirty( false ), - m_bInitialized( false ), m_uCurrentMetaChecksum( 0u ), m_fileCheckType( fileCheckType ), m_bNeverCheckDisk( nevercheckdisk ), m_bReadOnly( readonly ), - m_bSaveManifest( savemanifest ) + m_bSaveManifest( savemanifest ), + m_bDirty( false ), + m_bInitialized( false ) { Assert( m_pszRepositoryFileName && m_pszRepositoryFileName[ 0 ] ); } diff --git a/public/anorms.cpp b/public/anorms.cpp index 7117785e..86db7aeb 100644 --- a/public/anorms.cpp +++ b/public/anorms.cpp @@ -1,181 +1,181 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// -#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) - - -#include "vector.h" -#include "anorms.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -Vector g_anorms[NUMVERTEXNORMALS] = -{ - Vector(-0.525731, 0.000000, 0.850651), - Vector(-0.442863, 0.238856, 0.864188), - Vector(-0.295242, 0.000000, 0.955423), - Vector(-0.309017, 0.500000, 0.809017), - Vector(-0.162460, 0.262866, 0.951056), - Vector(0.000000, 0.000000, 1.000000), - Vector(0.000000, 0.850651, 0.525731), - Vector(-0.147621, 0.716567, 0.681718), - Vector(0.147621, 0.716567, 0.681718), - Vector(0.000000, 0.525731, 0.850651), - Vector(0.309017, 0.500000, 0.809017), - Vector(0.525731, 0.000000, 0.850651), - Vector(0.295242, 0.000000, 0.955423), - Vector(0.442863, 0.238856, 0.864188), - Vector(0.162460, 0.262866, 0.951056), - Vector(-0.681718, 0.147621, 0.716567), - Vector(-0.809017, 0.309017, 0.500000), - Vector(-0.587785, 0.425325, 0.688191), - Vector(-0.850651, 0.525731, 0.000000), - Vector(-0.864188, 0.442863, 0.238856), - Vector(-0.716567, 0.681718, 0.147621), - Vector(-0.688191, 0.587785, 0.425325), - Vector(-0.500000, 0.809017, 0.309017), - Vector(-0.238856, 0.864188, 0.442863), - Vector(-0.425325, 0.688191, 0.587785), - Vector(-0.716567, 0.681718, -0.147621), - Vector(-0.500000, 0.809017, -0.309017), - Vector(-0.525731, 0.850651, 0.000000), - Vector(0.000000, 0.850651, -0.525731), - Vector(-0.238856, 0.864188, -0.442863), - Vector(0.000000, 0.955423, -0.295242), - Vector(-0.262866, 0.951056, -0.162460), - Vector(0.000000, 1.000000, 0.000000), - Vector(0.000000, 0.955423, 0.295242), - Vector(-0.262866, 0.951056, 0.162460), - Vector(0.238856, 0.864188, 0.442863), - Vector(0.262866, 0.951056, 0.162460), - Vector(0.500000, 0.809017, 0.309017), - Vector(0.238856, 0.864188, -0.442863), - Vector(0.262866, 0.951056, -0.162460), - Vector(0.500000, 0.809017, -0.309017), - Vector(0.850651, 0.525731, 0.000000), - Vector(0.716567, 0.681718, 0.147621), - Vector(0.716567, 0.681718, -0.147621), - Vector(0.525731, 0.850651, 0.000000), - Vector(0.425325, 0.688191, 0.587785), - Vector(0.864188, 0.442863, 0.238856), - Vector(0.688191, 0.587785, 0.425325), - Vector(0.809017, 0.309017, 0.500000), - Vector(0.681718, 0.147621, 0.716567), - Vector(0.587785, 0.425325, 0.688191), - Vector(0.955423, 0.295242, 0.000000), - Vector(1.000000, 0.000000, 0.000000), - Vector(0.951056, 0.162460, 0.262866), - Vector(0.850651, -0.525731, 0.000000), - Vector(0.955423, -0.295242, 0.000000), - Vector(0.864188, -0.442863, 0.238856), - Vector(0.951056, -0.162460, 0.262866), - Vector(0.809017, -0.309017, 0.500000), - Vector(0.681718, -0.147621, 0.716567), - Vector(0.850651, 0.000000, 0.525731), - Vector(0.864188, 0.442863, -0.238856), - Vector(0.809017, 0.309017, -0.500000), - Vector(0.951056, 0.162460, -0.262866), - Vector(0.525731, 0.000000, -0.850651), - Vector(0.681718, 0.147621, -0.716567), - Vector(0.681718, -0.147621, -0.716567), - Vector(0.850651, 0.000000, -0.525731), - Vector(0.809017, -0.309017, -0.500000), - Vector(0.864188, -0.442863, -0.238856), - Vector(0.951056, -0.162460, -0.262866), - Vector(0.147621, 0.716567, -0.681718), - Vector(0.309017, 0.500000, -0.809017), - Vector(0.425325, 0.688191, -0.587785), - Vector(0.442863, 0.238856, -0.864188), - Vector(0.587785, 0.425325, -0.688191), - Vector(0.688191, 0.587785, -0.425325), - Vector(-0.147621, 0.716567, -0.681718), - Vector(-0.309017, 0.500000, -0.809017), - Vector(0.000000, 0.525731, -0.850651), - Vector(-0.525731, 0.000000, -0.850651), - Vector(-0.442863, 0.238856, -0.864188), - Vector(-0.295242, 0.000000, -0.955423), - Vector(-0.162460, 0.262866, -0.951056), - Vector(0.000000, 0.000000, -1.000000), - Vector(0.295242, 0.000000, -0.955423), - Vector(0.162460, 0.262866, -0.951056), - Vector(-0.442863, -0.238856, -0.864188), - Vector(-0.309017, -0.500000, -0.809017), - Vector(-0.162460, -0.262866, -0.951056), - Vector(0.000000, -0.850651, -0.525731), - Vector(-0.147621, -0.716567, -0.681718), - Vector(0.147621, -0.716567, -0.681718), - Vector(0.000000, -0.525731, -0.850651), - Vector(0.309017, -0.500000, -0.809017), - Vector(0.442863, -0.238856, -0.864188), - Vector(0.162460, -0.262866, -0.951056), - Vector(0.238856, -0.864188, -0.442863), - Vector(0.500000, -0.809017, -0.309017), - Vector(0.425325, -0.688191, -0.587785), - Vector(0.716567, -0.681718, -0.147621), - Vector(0.688191, -0.587785, -0.425325), - Vector(0.587785, -0.425325, -0.688191), - Vector(0.000000, -0.955423, -0.295242), - Vector(0.000000, -1.000000, 0.000000), - Vector(0.262866, -0.951056, -0.162460), - Vector(0.000000, -0.850651, 0.525731), - Vector(0.000000, -0.955423, 0.295242), - Vector(0.238856, -0.864188, 0.442863), - Vector(0.262866, -0.951056, 0.162460), - Vector(0.500000, -0.809017, 0.309017), - Vector(0.716567, -0.681718, 0.147621), - Vector(0.525731, -0.850651, 0.000000), - Vector(-0.238856, -0.864188, -0.442863), - Vector(-0.500000, -0.809017, -0.309017), - Vector(-0.262866, -0.951056, -0.162460), - Vector(-0.850651, -0.525731, 0.000000), - Vector(-0.716567, -0.681718, -0.147621), - Vector(-0.716567, -0.681718, 0.147621), - Vector(-0.525731, -0.850651, 0.000000), - Vector(-0.500000, -0.809017, 0.309017), - Vector(-0.238856, -0.864188, 0.442863), - Vector(-0.262866, -0.951056, 0.162460), - Vector(-0.864188, -0.442863, 0.238856), - Vector(-0.809017, -0.309017, 0.500000), - Vector(-0.688191, -0.587785, 0.425325), - Vector(-0.681718, -0.147621, 0.716567), - Vector(-0.442863, -0.238856, 0.864188), - Vector(-0.587785, -0.425325, 0.688191), - Vector(-0.309017, -0.500000, 0.809017), - Vector(-0.147621, -0.716567, 0.681718), - Vector(-0.425325, -0.688191, 0.587785), - Vector(-0.162460, -0.262866, 0.951056), - Vector(0.442863, -0.238856, 0.864188), - Vector(0.162460, -0.262866, 0.951056), - Vector(0.309017, -0.500000, 0.809017), - Vector(0.147621, -0.716567, 0.681718), - Vector(0.000000, -0.525731, 0.850651), - Vector(0.425325, -0.688191, 0.587785), - Vector(0.587785, -0.425325, 0.688191), - Vector(0.688191, -0.587785, 0.425325), - Vector(-0.955423, 0.295242, 0.000000), - Vector(-0.951056, 0.162460, 0.262866), - Vector(-1.000000, 0.000000, 0.000000), - Vector(-0.850651, 0.000000, 0.525731), - Vector(-0.955423, -0.295242, 0.000000), - Vector(-0.951056, -0.162460, 0.262866), - Vector(-0.864188, 0.442863, -0.238856), - Vector(-0.951056, 0.162460, -0.262866), - Vector(-0.809017, 0.309017, -0.500000), - Vector(-0.864188, -0.442863, -0.238856), - Vector(-0.951056, -0.162460, -0.262866), - Vector(-0.809017, -0.309017, -0.500000), - Vector(-0.681718, 0.147621, -0.716567), - Vector(-0.681718, -0.147621, -0.716567), - Vector(-0.850651, 0.000000, -0.525731), - Vector(-0.688191, 0.587785, -0.425325), - Vector(-0.587785, 0.425325, -0.688191), - Vector(-0.425325, 0.688191, -0.587785), - Vector(-0.425325, -0.688191, -0.587785), - Vector(-0.587785, -0.425325, -0.688191), - Vector(-0.688191, -0.587785, -0.425325) -}; - -#endif // !_STATIC_LINKED || _SHARED_LIB \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + + +#include "vector.h" +#include "anorms.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +Vector g_anorms[NUMVERTEXNORMALS] = +{ + Vector(-0.525731, 0.000000, 0.850651), + Vector(-0.442863, 0.238856, 0.864188), + Vector(-0.295242, 0.000000, 0.955423), + Vector(-0.309017, 0.500000, 0.809017), + Vector(-0.162460, 0.262866, 0.951056), + Vector(0.000000, 0.000000, 1.000000), + Vector(0.000000, 0.850651, 0.525731), + Vector(-0.147621, 0.716567, 0.681718), + Vector(0.147621, 0.716567, 0.681718), + Vector(0.000000, 0.525731, 0.850651), + Vector(0.309017, 0.500000, 0.809017), + Vector(0.525731, 0.000000, 0.850651), + Vector(0.295242, 0.000000, 0.955423), + Vector(0.442863, 0.238856, 0.864188), + Vector(0.162460, 0.262866, 0.951056), + Vector(-0.681718, 0.147621, 0.716567), + Vector(-0.809017, 0.309017, 0.500000), + Vector(-0.587785, 0.425325, 0.688191), + Vector(-0.850651, 0.525731, 0.000000), + Vector(-0.864188, 0.442863, 0.238856), + Vector(-0.716567, 0.681718, 0.147621), + Vector(-0.688191, 0.587785, 0.425325), + Vector(-0.500000, 0.809017, 0.309017), + Vector(-0.238856, 0.864188, 0.442863), + Vector(-0.425325, 0.688191, 0.587785), + Vector(-0.716567, 0.681718, -0.147621), + Vector(-0.500000, 0.809017, -0.309017), + Vector(-0.525731, 0.850651, 0.000000), + Vector(0.000000, 0.850651, -0.525731), + Vector(-0.238856, 0.864188, -0.442863), + Vector(0.000000, 0.955423, -0.295242), + Vector(-0.262866, 0.951056, -0.162460), + Vector(0.000000, 1.000000, 0.000000), + Vector(0.000000, 0.955423, 0.295242), + Vector(-0.262866, 0.951056, 0.162460), + Vector(0.238856, 0.864188, 0.442863), + Vector(0.262866, 0.951056, 0.162460), + Vector(0.500000, 0.809017, 0.309017), + Vector(0.238856, 0.864188, -0.442863), + Vector(0.262866, 0.951056, -0.162460), + Vector(0.500000, 0.809017, -0.309017), + Vector(0.850651, 0.525731, 0.000000), + Vector(0.716567, 0.681718, 0.147621), + Vector(0.716567, 0.681718, -0.147621), + Vector(0.525731, 0.850651, 0.000000), + Vector(0.425325, 0.688191, 0.587785), + Vector(0.864188, 0.442863, 0.238856), + Vector(0.688191, 0.587785, 0.425325), + Vector(0.809017, 0.309017, 0.500000), + Vector(0.681718, 0.147621, 0.716567), + Vector(0.587785, 0.425325, 0.688191), + Vector(0.955423, 0.295242, 0.000000), + Vector(1.000000, 0.000000, 0.000000), + Vector(0.951056, 0.162460, 0.262866), + Vector(0.850651, -0.525731, 0.000000), + Vector(0.955423, -0.295242, 0.000000), + Vector(0.864188, -0.442863, 0.238856), + Vector(0.951056, -0.162460, 0.262866), + Vector(0.809017, -0.309017, 0.500000), + Vector(0.681718, -0.147621, 0.716567), + Vector(0.850651, 0.000000, 0.525731), + Vector(0.864188, 0.442863, -0.238856), + Vector(0.809017, 0.309017, -0.500000), + Vector(0.951056, 0.162460, -0.262866), + Vector(0.525731, 0.000000, -0.850651), + Vector(0.681718, 0.147621, -0.716567), + Vector(0.681718, -0.147621, -0.716567), + Vector(0.850651, 0.000000, -0.525731), + Vector(0.809017, -0.309017, -0.500000), + Vector(0.864188, -0.442863, -0.238856), + Vector(0.951056, -0.162460, -0.262866), + Vector(0.147621, 0.716567, -0.681718), + Vector(0.309017, 0.500000, -0.809017), + Vector(0.425325, 0.688191, -0.587785), + Vector(0.442863, 0.238856, -0.864188), + Vector(0.587785, 0.425325, -0.688191), + Vector(0.688191, 0.587785, -0.425325), + Vector(-0.147621, 0.716567, -0.681718), + Vector(-0.309017, 0.500000, -0.809017), + Vector(0.000000, 0.525731, -0.850651), + Vector(-0.525731, 0.000000, -0.850651), + Vector(-0.442863, 0.238856, -0.864188), + Vector(-0.295242, 0.000000, -0.955423), + Vector(-0.162460, 0.262866, -0.951056), + Vector(0.000000, 0.000000, -1.000000), + Vector(0.295242, 0.000000, -0.955423), + Vector(0.162460, 0.262866, -0.951056), + Vector(-0.442863, -0.238856, -0.864188), + Vector(-0.309017, -0.500000, -0.809017), + Vector(-0.162460, -0.262866, -0.951056), + Vector(0.000000, -0.850651, -0.525731), + Vector(-0.147621, -0.716567, -0.681718), + Vector(0.147621, -0.716567, -0.681718), + Vector(0.000000, -0.525731, -0.850651), + Vector(0.309017, -0.500000, -0.809017), + Vector(0.442863, -0.238856, -0.864188), + Vector(0.162460, -0.262866, -0.951056), + Vector(0.238856, -0.864188, -0.442863), + Vector(0.500000, -0.809017, -0.309017), + Vector(0.425325, -0.688191, -0.587785), + Vector(0.716567, -0.681718, -0.147621), + Vector(0.688191, -0.587785, -0.425325), + Vector(0.587785, -0.425325, -0.688191), + Vector(0.000000, -0.955423, -0.295242), + Vector(0.000000, -1.000000, 0.000000), + Vector(0.262866, -0.951056, -0.162460), + Vector(0.000000, -0.850651, 0.525731), + Vector(0.000000, -0.955423, 0.295242), + Vector(0.238856, -0.864188, 0.442863), + Vector(0.262866, -0.951056, 0.162460), + Vector(0.500000, -0.809017, 0.309017), + Vector(0.716567, -0.681718, 0.147621), + Vector(0.525731, -0.850651, 0.000000), + Vector(-0.238856, -0.864188, -0.442863), + Vector(-0.500000, -0.809017, -0.309017), + Vector(-0.262866, -0.951056, -0.162460), + Vector(-0.850651, -0.525731, 0.000000), + Vector(-0.716567, -0.681718, -0.147621), + Vector(-0.716567, -0.681718, 0.147621), + Vector(-0.525731, -0.850651, 0.000000), + Vector(-0.500000, -0.809017, 0.309017), + Vector(-0.238856, -0.864188, 0.442863), + Vector(-0.262866, -0.951056, 0.162460), + Vector(-0.864188, -0.442863, 0.238856), + Vector(-0.809017, -0.309017, 0.500000), + Vector(-0.688191, -0.587785, 0.425325), + Vector(-0.681718, -0.147621, 0.716567), + Vector(-0.442863, -0.238856, 0.864188), + Vector(-0.587785, -0.425325, 0.688191), + Vector(-0.309017, -0.500000, 0.809017), + Vector(-0.147621, -0.716567, 0.681718), + Vector(-0.425325, -0.688191, 0.587785), + Vector(-0.162460, -0.262866, 0.951056), + Vector(0.442863, -0.238856, 0.864188), + Vector(0.162460, -0.262866, 0.951056), + Vector(0.309017, -0.500000, 0.809017), + Vector(0.147621, -0.716567, 0.681718), + Vector(0.000000, -0.525731, 0.850651), + Vector(0.425325, -0.688191, 0.587785), + Vector(0.587785, -0.425325, 0.688191), + Vector(0.688191, -0.587785, 0.425325), + Vector(-0.955423, 0.295242, 0.000000), + Vector(-0.951056, 0.162460, 0.262866), + Vector(-1.000000, 0.000000, 0.000000), + Vector(-0.850651, 0.000000, 0.525731), + Vector(-0.955423, -0.295242, 0.000000), + Vector(-0.951056, -0.162460, 0.262866), + Vector(-0.864188, 0.442863, -0.238856), + Vector(-0.951056, 0.162460, -0.262866), + Vector(-0.809017, 0.309017, -0.500000), + Vector(-0.864188, -0.442863, -0.238856), + Vector(-0.951056, -0.162460, -0.262866), + Vector(-0.809017, -0.309017, -0.500000), + Vector(-0.681718, 0.147621, -0.716567), + Vector(-0.681718, -0.147621, -0.716567), + Vector(-0.850651, 0.000000, -0.525731), + Vector(-0.688191, 0.587785, -0.425325), + Vector(-0.587785, 0.425325, -0.688191), + Vector(-0.425325, 0.688191, -0.587785), + Vector(-0.425325, -0.688191, -0.587785), + Vector(-0.587785, -0.425325, -0.688191), + Vector(-0.688191, -0.587785, -0.425325) +}; + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/public/bitmap/imageformat.h b/public/bitmap/imageformat.h index dd5129f6..b74e23b2 100644 --- a/public/bitmap/imageformat.h +++ b/public/bitmap/imageformat.h @@ -22,7 +22,9 @@ // don't bitch that inline functions aren't used!!!! +#ifdef _MSC_VER #pragma warning(disable : 4514) +#endif enum ImageFormat { @@ -266,8 +268,8 @@ enum struct ResampleInfo_t { - ResampleInfo_t() : m_flColorScale( 1.0f ), m_nFlags(0), m_flAlphaThreshhold(0.4f), - m_flAlphaHiFreqThreshhold(0.4f), m_nSrcDepth(1), m_nDestDepth(1) {} + ResampleInfo_t() : m_nSrcDepth(1), m_nDestDepth(1), m_flColorScale( 1.0f ), m_flAlphaThreshhold(0.4f), + m_flAlphaHiFreqThreshhold(0.4f), m_nFlags(0) {} unsigned char *m_pSrc; unsigned char *m_pDest; diff --git a/public/blockingudpsocket.cpp b/public/blockingudpsocket.cpp index e2c6ea95..dfb3f3fd 100644 --- a/public/blockingudpsocket.cpp +++ b/public/blockingudpsocket.cpp @@ -1,158 +1,158 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) - - -#if defined(_WIN32) && !defined(_XBOX) -#include -#elif _LINUX -#define INVALID_SOCKET -1 -#define SOCKET_ERROR -1 -#include -#include -#include -#include -#define closesocket close -#elif defined(_XBOX) -#include "xbox/xbox_platform.h" -#include "xbox/xbox_win32stubs.h" -#endif - -#include "blockingudpsocket.h" -#include "tier0/vcrmode.h" - -class CBlockingUDPSocket::CImpl -{ -public: - struct sockaddr_in m_SocketIP; - fd_set m_FDSet; -}; - -CBlockingUDPSocket::CBlockingUDPSocket() : - m_cserIP(), - m_Socket( 0 ), - m_pImpl( new CImpl ) -{ - CreateSocket(); -} - -CBlockingUDPSocket::~CBlockingUDPSocket() -{ - delete m_pImpl; - closesocket( static_cast( m_Socket )); -} - -bool CBlockingUDPSocket::CreateSocket (void) -{ - struct sockaddr_in address; - - m_Socket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ); - if ( m_Socket == INVALID_SOCKET ) - { - return false; - } - - address = m_pImpl->m_SocketIP; - - if ( SOCKET_ERROR == bind( m_Socket, ( struct sockaddr * )&address, sizeof( address ) ) ) - { - return false; - } - -#ifdef _WIN32 - if ( m_pImpl->m_SocketIP.sin_addr.S_un.S_addr == INADDR_ANY ) - { - m_pImpl->m_SocketIP.sin_addr.S_un.S_addr = 0L; - } -#elif _LINUX - if ( m_pImpl->m_SocketIP.sin_addr.s_addr == INADDR_ANY ) - { - m_pImpl->m_SocketIP.sin_addr.s_addr = 0L; - } -#endif - - return true; -} - -bool CBlockingUDPSocket::WaitForMessage( float timeOutInSeconds ) -{ - struct timeval tv; - - FD_ZERO( &m_pImpl->m_FDSet ); - FD_SET( m_Socket, &m_pImpl->m_FDSet );//lint !e717 - - tv.tv_sec = (int)timeOutInSeconds; - float remainder = timeOutInSeconds - (int)timeOutInSeconds; - tv.tv_usec = (int)( remainder * 1000000 + 0.5f ); /* micro seconds */ - - if ( SOCKET_ERROR == select( ( int )m_Socket + 1, &m_pImpl->m_FDSet, NULL, NULL, &tv ) ) - { - return false; - } - - if ( FD_ISSET( m_Socket, &m_pImpl->m_FDSet) ) - { - return true; - } - - // Timed out - return false; -} - -unsigned int CBlockingUDPSocket::ReceiveSocketMessage( struct sockaddr_in *packet_from, unsigned char *buf, size_t bufsize ) -{ - memset( packet_from, 0, sizeof( *packet_from ) ); - - struct sockaddr fromaddress; - int fromlen = sizeof( fromaddress ); - - int packet_length = VCRHook_recvfrom - ( - m_Socket, - (char *)buf, - (int)bufsize, - 0, - &fromaddress, - &fromlen - ); - - if ( SOCKET_ERROR == packet_length ) - { - return 0; - } - - // In case it's parsed as a string - buf[ packet_length ] = 0; - - // Copy over the receive address - *packet_from = *( struct sockaddr_in * )&fromaddress; - - return ( unsigned int )packet_length; -} - -bool CBlockingUDPSocket::SendSocketMessage( const struct sockaddr_in & rRecipient, const unsigned char *buf, size_t bufsize ) -{ - // Send data - int bytesSent = sendto - ( - m_Socket, - (const char *)buf, - (int)bufsize, - 0, - reinterpret_cast< const sockaddr * >( &rRecipient ), - sizeof( rRecipient ) - ); - - if ( SOCKET_ERROR == bytesSent ) - { - return false; - } - - return true; -} - -#endif // !_STATIC_LINKED || _SHARED_LIB \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + + +#if defined(_WIN32) && !defined(_XBOX) +#include +#elif _LINUX +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#include +#include +#include +#include +#define closesocket close +#elif defined(_XBOX) +#include "xbox/xbox_platform.h" +#include "xbox/xbox_win32stubs.h" +#endif + +#include "blockingudpsocket.h" +#include "tier0/vcrmode.h" + +class CBlockingUDPSocket::CImpl +{ +public: + struct sockaddr_in m_SocketIP; + fd_set m_FDSet; +}; + +CBlockingUDPSocket::CBlockingUDPSocket() : + m_pImpl( new CImpl ), + m_cserIP(), + m_Socket( 0 ) +{ + CreateSocket(); +} + +CBlockingUDPSocket::~CBlockingUDPSocket() +{ + delete m_pImpl; + closesocket( static_cast( m_Socket )); +} + +bool CBlockingUDPSocket::CreateSocket (void) +{ + struct sockaddr_in address; + + m_Socket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ); + if ( m_Socket == INVALID_SOCKET ) + { + return false; + } + + address = m_pImpl->m_SocketIP; + + if ( SOCKET_ERROR == bind( m_Socket, ( struct sockaddr * )&address, sizeof( address ) ) ) + { + return false; + } + +#ifdef _WIN32 + if ( m_pImpl->m_SocketIP.sin_addr.S_un.S_addr == INADDR_ANY ) + { + m_pImpl->m_SocketIP.sin_addr.S_un.S_addr = 0L; + } +#elif _LINUX + if ( m_pImpl->m_SocketIP.sin_addr.s_addr == INADDR_ANY ) + { + m_pImpl->m_SocketIP.sin_addr.s_addr = 0L; + } +#endif + + return true; +} + +bool CBlockingUDPSocket::WaitForMessage( float timeOutInSeconds ) +{ + struct timeval tv; + + FD_ZERO( &m_pImpl->m_FDSet ); + FD_SET( m_Socket, &m_pImpl->m_FDSet );//lint !e717 + + tv.tv_sec = (int)timeOutInSeconds; + float remainder = timeOutInSeconds - (int)timeOutInSeconds; + tv.tv_usec = (int)( remainder * 1000000 + 0.5f ); /* micro seconds */ + + if ( SOCKET_ERROR == select( ( int )m_Socket + 1, &m_pImpl->m_FDSet, NULL, NULL, &tv ) ) + { + return false; + } + + if ( FD_ISSET( m_Socket, &m_pImpl->m_FDSet) ) + { + return true; + } + + // Timed out + return false; +} + +unsigned int CBlockingUDPSocket::ReceiveSocketMessage( struct sockaddr_in *packet_from, unsigned char *buf, size_t bufsize ) +{ + memset( packet_from, 0, sizeof( *packet_from ) ); + + struct sockaddr fromaddress; + int fromlen = sizeof( fromaddress ); + + int packet_length = VCRHook_recvfrom + ( + m_Socket, + (char *)buf, + (int)bufsize, + 0, + &fromaddress, + &fromlen + ); + + if ( SOCKET_ERROR == packet_length ) + { + return 0; + } + + // In case it's parsed as a string + buf[ packet_length ] = 0; + + // Copy over the receive address + *packet_from = *( struct sockaddr_in * )&fromaddress; + + return ( unsigned int )packet_length; +} + +bool CBlockingUDPSocket::SendSocketMessage( const struct sockaddr_in & rRecipient, const unsigned char *buf, size_t bufsize ) +{ + // Send data + int bytesSent = sendto + ( + m_Socket, + (const char *)buf, + (int)bufsize, + 0, + reinterpret_cast< const sockaddr * >( &rRecipient ), + sizeof( rRecipient ) + ); + + if ( SOCKET_ERROR == bytesSent ) + { + return false; + } + + return true; +} + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/public/blockingudpsocket.h b/public/blockingudpsocket.h index 2adb3383..b1dbdb8b 100644 --- a/public/blockingudpsocket.h +++ b/public/blockingudpsocket.h @@ -1,39 +1,39 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef BLOCKINGUDPSOCKET_H -#define BLOCKINGUDPSOCKET_H -#ifdef _WIN32 -#pragma once -#endif - -#include "netadr.h" - -class CBlockingUDPSocket -{ -public: - explicit CBlockingUDPSocket(); - virtual ~CBlockingUDPSocket(); - - bool WaitForMessage( float timeOutInSeconds ); - unsigned int ReceiveSocketMessage( struct sockaddr_in *packet_from, unsigned char *buf, size_t bufsize ); - bool SendSocketMessage( const struct sockaddr_in& rRecipient, const unsigned char *buf, size_t bufsize ); - - bool IsValid() const { return m_Socket != 0; } - -protected: - bool CreateSocket (void); - - class CImpl; - CImpl *m_pImpl; - - netadr_t m_cserIP; - unsigned int m_Socket; - - -}; - -#endif // BLOCKINGUDPSOCKET_H +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef BLOCKINGUDPSOCKET_H +#define BLOCKINGUDPSOCKET_H +#ifdef _WIN32 +#pragma once +#endif + +#include "netadr.h" + +class CBlockingUDPSocket +{ +public: + explicit CBlockingUDPSocket(); + virtual ~CBlockingUDPSocket(); + + bool WaitForMessage( float timeOutInSeconds ); + unsigned int ReceiveSocketMessage( struct sockaddr_in *packet_from, unsigned char *buf, size_t bufsize ); + bool SendSocketMessage( const struct sockaddr_in& rRecipient, const unsigned char *buf, size_t bufsize ); + + bool IsValid() const { return m_Socket != 0; } + +protected: + bool CreateSocket (void); + + class CImpl; + CImpl *m_pImpl; + + netadr_t m_cserIP; + int m_Socket; + + +}; + +#endif // BLOCKINGUDPSOCKET_H diff --git a/public/bone_setup.cpp b/public/bone_setup.cpp index 6528f187..3724dd1e 100644 --- a/public/bone_setup.cpp +++ b/public/bone_setup.cpp @@ -1,4965 +1,4966 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "tier0/dbg.h" -#include "mathlib.h" -#include "bone_setup.h" -#include - -#include "collisionutils.h" -#include "vstdlib/random.h" -#include "tier0/vprof.h" -#include "bone_accessor.h" -#include "bitvec.h" -#include "datamanager.h" -#include "convar.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// ----------------------------------------------------------------- -CBoneCache *CBoneCache::CreateResource( const bonecacheparams_t ¶ms ) -{ - short studioToCachedIndex[MAXSTUDIOBONES]; - short cachedToStudioIndex[MAXSTUDIOBONES]; - int cachedBoneCount = 0; - for ( int i = 0; i < params.pStudioHdr->numbones(); i++ ) - { - // skip bones that aren't part of the boneMask (and aren't the root bone) - if (i != 0 && !(params.pStudioHdr->pBone(i)->flags & params.boneMask)) - { - studioToCachedIndex[i] = -1; - continue; - } - studioToCachedIndex[i] = cachedBoneCount; - cachedToStudioIndex[cachedBoneCount] = i; - cachedBoneCount++; - } - int tableSizeStudio = sizeof(short) * params.pStudioHdr->numbones(); - int tableSizeCached = sizeof(short) * cachedBoneCount; - int matrixSize = sizeof(matrix3x4_t) * cachedBoneCount; - int size = sizeof(CBoneCache) + tableSizeStudio + tableSizeCached + matrixSize; - - CBoneCache *pMem = (CBoneCache *)malloc( size ); - Construct( pMem ); - pMem->Init( params, size, studioToCachedIndex, cachedToStudioIndex, cachedBoneCount ); - return pMem; -} - -unsigned int CBoneCache::EstimatedSize( const bonecacheparams_t ¶ms ) -{ - // conservative estimate - max size - return params.pStudioHdr->numbones() * (sizeof(short) + sizeof(short) + sizeof(matrix3x4_t)); -} - -void CBoneCache::DestroyResource() -{ - free( this ); -} - - -CBoneCache::CBoneCache() -{ - m_size = 0; - m_cachedBoneCount = 0; -} - -void CBoneCache::Init( const bonecacheparams_t ¶ms, unsigned int size, short *pStudioToCached, short *pCachedToStudio, int cachedBoneCount ) -{ - m_cachedBoneCount = cachedBoneCount; - m_size = size; - m_timeValid = params.curtime; - m_boneMask = params.boneMask; - - int studioTableSize = params.pStudioHdr->numbones() * sizeof(short); - m_cachedToStudioOffset = studioTableSize; - memcpy( StudioToCached(), pStudioToCached, studioTableSize ); - - int cachedTableSize = cachedBoneCount * sizeof(short); - memcpy( CachedToStudio(), pCachedToStudio, cachedTableSize ); - - m_matrixOffset = m_cachedToStudioOffset + cachedTableSize; - - UpdateBones( params.pBoneToWorld, params.pStudioHdr->numbones(), params.curtime ); -} - -void CBoneCache::UpdateBones( const matrix3x4_t *pBoneToWorld, int numbones, float curtime ) -{ - matrix3x4_t *pBones = BoneArray(); - const short *pCachedToStudio = CachedToStudio(); - - for ( int i = 0; i < m_cachedBoneCount; i++ ) - { - int index = pCachedToStudio[i]; - MatrixCopy( pBoneToWorld[index], pBones[i] ); - } - m_timeValid = curtime; -} - -matrix3x4_t *CBoneCache::GetCachedBone( int studioIndex ) -{ - int cachedIndex = StudioToCached()[studioIndex]; - if ( cachedIndex >= 0 ) - { - return BoneArray() + cachedIndex; - } - return NULL; -} - -void CBoneCache::ReadCachedBones( matrix3x4_t *pBoneToWorld ) -{ - matrix3x4_t *pBones = BoneArray(); - const short *pCachedToStudio = CachedToStudio(); - for ( int i = 0; i < m_cachedBoneCount; i++ ) - { - MatrixCopy( pBones[i], pBoneToWorld[pCachedToStudio[i]] ); - } -} - -void CBoneCache::ReadCachedBonePointers( matrix3x4_t **bones, int numbones ) -{ - memset( bones, 0, sizeof(matrix3x4_t *) * numbones ); - matrix3x4_t *pBones = BoneArray(); - const short *pCachedToStudio = CachedToStudio(); - for ( int i = 0; i < m_cachedBoneCount; i++ ) - { - bones[pCachedToStudio[i]] = pBones + i; - } -} - -bool CBoneCache::IsValid( float time, float dt ) -{ - if ( time - m_timeValid <= dt ) - return true; - return false; -} - - -// private functions -matrix3x4_t *CBoneCache::BoneArray() -{ - return (matrix3x4_t *)( (char *)(this+1) + m_matrixOffset ); -} - -short *CBoneCache::StudioToCached() -{ - return (short *)( (char *)(this+1) ); -} - -short *CBoneCache::CachedToStudio() -{ - return (short *)( (char *)(this+1) + m_cachedToStudioOffset ); -} - -// Construct a singleton -static CDataManager g_StudioBoneCache( 16 * 1024L ); - -CBoneCache *Studio_GetBoneCache( memhandle_t cacheHandle ) -{ - return g_StudioBoneCache.GetResource_NoLock( cacheHandle ); -} - -memhandle_t Studio_CreateBoneCache( bonecacheparams_t ¶ms ) -{ - return g_StudioBoneCache.CreateResource( params ); -} - -void Studio_DestroyBoneCache( memhandle_t cacheHandle ) -{ - g_StudioBoneCache.DestroyResource( cacheHandle ); -} - -void Studio_InvalidateBoneCache( memhandle_t cacheHandle ) -{ - CBoneCache *pCache = g_StudioBoneCache.GetResource_NoLock( cacheHandle ); - if ( pCache ) - { - pCache->m_timeValid = -1.0f; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -void BuildBoneChain( - const CStudioHdr *pStudioHdr, - const matrix3x4_t &rootxform, - const Vector pos[], - const Quaternion q[], - int iBone, - matrix3x4_t *pBoneToWorld ) -{ - CBoneBitList boneComputed; - BuildBoneChain( pStudioHdr, rootxform, pos, q, iBone, pBoneToWorld, boneComputed ); - return; -} - - - -//----------------------------------------------------------------------------- -// Purpose: return a sub frame rotation for a single bone -//----------------------------------------------------------------------------- - - -void ExtractAnimValue( int frame, - mstudioanimvalue_t *panimvalue, - float scale, - float &v1, float &v2 ) -{ - if (!panimvalue) - { - v1 = v2 = 0; - return; - } - - int k = frame; - - while (panimvalue->num.total <= k) - { - k -= panimvalue->num.total; - panimvalue += panimvalue->num.valid + 1; - if ( panimvalue->num.total == 0 ) - { - v1 = v2 = 0; - return; - } - } - // Bah, missing blend! - if (panimvalue->num.valid > k) - { - v1 = panimvalue[k+1].value * scale; - - if (panimvalue->num.valid > k + 1) - { - v2 = panimvalue[k+2].value * scale; - } - else - { - if (panimvalue->num.total > k + 1) - v2 = v1; - else - v2 = panimvalue[panimvalue->num.valid+2].value * scale; - } - } - else - { - v1 = panimvalue[panimvalue->num.valid].value * scale; - if (panimvalue->num.total > k + 1) - { - v2 = v1; - } - else - { - v2 = panimvalue[panimvalue->num.valid + 2].value * scale; - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: return a sub frame rotation for a single bone -//----------------------------------------------------------------------------- -void CalcBoneQuaternion( int frame, float s, - const mstudiobone_t *pbone, const mstudioanim_t *panim, Quaternion &q ) -{ - if (panim->flags & STUDIO_ANIM_RAWROT) - { - q = *(panim->pQuat()); - Assert( q.IsValid() ); - return; - } - else if (!(panim->flags & STUDIO_ANIM_ANIMROT)) - { - if (panim->flags & STUDIO_ANIM_DELTA) - { - q.Init( 0.0f, 0.0f, 0.0f, 1.0f ); - } - else - { - q = pbone->quat; - } - return; - } - - Quaternion q1, q2; - RadianEuler angle1, angle2; - mstudioanim_valueptr_t *pValuesPtr = panim->pRotV(); - - ExtractAnimValue( frame, pValuesPtr->pAnimvalue( 0 ), pbone->rotscale.x, angle1.x, angle2.x ); - ExtractAnimValue( frame, pValuesPtr->pAnimvalue( 1 ), pbone->rotscale.y, angle1.y, angle2.y ); - ExtractAnimValue( frame, pValuesPtr->pAnimvalue( 2 ), pbone->rotscale.z, angle1.z, angle2.z ); - - if (!(panim->flags & STUDIO_ANIM_DELTA)) - { - angle1.x = angle1.x + pbone->rot.x; - angle1.y = angle1.y + pbone->rot.y; - angle1.z = angle1.z + pbone->rot.z; - angle2.x = angle2.x + pbone->rot.x; - angle2.y = angle2.y + pbone->rot.y; - angle2.z = angle2.z + pbone->rot.z; - } - - Assert( angle1.IsValid() && angle2.IsValid() ); - if (angle1.x != angle2.x || angle1.y != angle2.y || angle1.z != angle2.z) - { - AngleQuaternion( angle1, q1 ); - AngleQuaternion( angle2, q2 ); - QuaternionBlend( q1, q2, s, q ); - } - else - { - AngleQuaternion( angle1, q ); - } - - Assert( q.IsValid() ); - - // align to unified bone - if (!(panim->flags & STUDIO_ANIM_DELTA) && (pbone->flags & BONE_FIXED_ALIGNMENT)) - { - QuaternionAlign( pbone->qAlignment, q, q ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: return a sub frame position for a single bone -//----------------------------------------------------------------------------- -void CalcBonePosition( int frame, float s, - const mstudiobone_t *pbone, const mstudioanim_t *panim, Vector &pos ) -{ - if (panim->flags & STUDIO_ANIM_RAWPOS) - { - pos = *(panim->pPos()); - Assert( pos.IsValid() ); - - return; - } - else if (!(panim->flags & STUDIO_ANIM_ANIMPOS)) - { - if (panim->flags & STUDIO_ANIM_DELTA) - { - pos.Init( 0.0f, 0.0f, 0.0f ); - } - else - { - pos = pbone->pos; - } - return; - } - - mstudioanim_valueptr_t *pPosV = panim->pPosV(); - int j; - float v1, v2; - - for (j = 0; j < 3; j++) - { - ExtractAnimValue( frame, pPosV->pAnimvalue( j ), pbone->posscale[j], v1, v2 ); - pos[j] = v1 * (1.0 - s) + v2 * s; - } - - if (!(panim->flags & STUDIO_ANIM_DELTA)) - { - pos.x = pos.x + pbone->pos.x; - pos.y = pos.y + pbone->pos.y; - pos.z = pos.z + pbone->pos.z; - } - - Assert( pos.IsValid() ); -} - - -void SetupSingleBoneMatrix( - CStudioHdr *pOwnerHdr, - int nSequence, - int iFrame, - int iBone, - matrix3x4_t &mBoneLocal ) -{ - mstudioseqdesc_t &seqdesc = pOwnerHdr->pSeqdesc( nSequence ); - mstudioanimdesc_t &animdesc = pOwnerHdr->pAnimdesc( seqdesc.anim( 0, 0 ) ); - mstudioanim_t *panim = animdesc.pAnim( ); - float s = 0; - mstudiobone_t *pbone = pOwnerHdr->pBone( iBone ); - - Quaternion boneQuat; - Vector bonePos; - - // search for bone - while (panim && panim->bone != iBone) - { - panim = panim->pNext(); - } - - // look up animation if found, if not, initialize - if (panim && seqdesc.weight(iBone) > 0) - { - CalcBoneQuaternion( iFrame, s, pbone, panim, boneQuat ); - CalcBonePosition ( iFrame, s, pbone, panim, bonePos ); - } - else if (animdesc.flags & STUDIO_DELTA) - { - boneQuat.Init( 0.0f, 0.0f, 0.0f, 1.0f ); - bonePos.Init( 0.0f, 0.0f, 0.0f ); - } - else - { - boneQuat = pbone->quat; - bonePos = pbone->pos; - } - - QuaternionMatrix( boneQuat, bonePos, mBoneLocal ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -static void CalcVirtualAnimation( virtualmodel_t *pVModel, const CStudioHdr *pStudioHdr, Vector *pos, Quaternion *q, - mstudioseqdesc_t &seqdesc, int sequence, int animation, - float cycle, int boneMask ) -{ - int i, j, k; - - const mstudiobone_t *pbone; - const virtualgroup_t *pSeqGroup; - const studiohdr_t *pSeqStudioHdr; - const mstudiobone_t *pSeqbone; - const mstudioanim_t *panim; - const studiohdr_t *pAnimStudioHdr; - const mstudiobone_t *pAnimbone; - const virtualgroup_t *pAnimGroup; - - pSeqGroup = pVModel->pSeqGroup( sequence ); - int baseanimation = pStudioHdr->iRelativeAnim( sequence, animation ); - mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( baseanimation ); - pSeqStudioHdr = pStudioHdr->pSeqStudioHdr( sequence ); - pSeqbone = pSeqStudioHdr->pBone( 0 ); - pAnimGroup = pVModel->pAnimGroup( baseanimation ); - panim = animdesc.pAnim( ); - pAnimStudioHdr = pStudioHdr->pAnimStudioHdr( baseanimation ); - pAnimbone = pAnimStudioHdr->pBone( 0 ); - - int iFrame; - float s; - - float fFrame = cycle * (animdesc.numframes - 1); - - iFrame = (int)fFrame; - s = (fFrame - iFrame); - - float *pweight = seqdesc.pBoneweight( 0 ); - pbone = pStudioHdr->pBone( 0 ); - - for (i = 0; i < pStudioHdr->numbones(); i++) - { - if (pbone[i].flags & boneMask) - { - int j = pSeqGroup->boneMap[i]; - if (j >= 0 && pweight[j] > 0.0f) - { - if (animdesc.flags & STUDIO_DELTA) - { - q[i].Init( 0.0f, 0.0f, 0.0f, 1.0f ); - pos[i].Init( 0.0f, 0.0f, 0.0f ); - } - else - { - q[i] = pSeqbone[j].quat; - pos[i] = pSeqbone[j].pos; - } - } - } - } - - // if the animation isn't available, look for the zero frame cache - if (!panim) - { - byte *pData = pAnimStudioHdr->pZeroframeCache( pVModel->m_anim[baseanimation].index ); - - if (pData) - { - // Msg("zeroframe %s\n", animdesc.pszName() ); - for (j = 0; j < pAnimStudioHdr->numbones; j++) - { - i = pAnimGroup->masterBone[j]; - - if (pAnimbone[j].flags & BONE_HAS_SAVEFRAME_POS) - { - if ((i >= 0) && (pbone[i].flags & boneMask)) - { - pos[i] = *(Vector48 *)pData; - } - pData += sizeof( Vector48 ); - } - if (!(animdesc.flags & STUDIO_DELTA) && (pAnimbone[j].flags & BONE_HAS_SAVEFRAME_ROT) != 0) - { - if ((i >= 0) && (pbone[i].flags & boneMask)) - { - q[i] = *(Quaternion32 *)pData; - } - pData += sizeof( Quaternion32 ); - } - } - return; - } - } - - // FIXME: change encoding so that bone -1 is never the case - while (panim && panim->bone < 255) - { - j = pAnimGroup->masterBone[panim->bone]; - if (j >= 0 && (pbone[j].flags & boneMask)) - { - k = pSeqGroup->boneMap[j]; - - if (k >= 0 && pweight[k] > 0.0f) - { - CalcBoneQuaternion( iFrame, s, &pAnimbone[panim->bone], panim, q[j] ); - CalcBonePosition ( iFrame, s, &pAnimbone[panim->bone], panim, pos[j] ); - } - } - panim = panim->pNext(); - } -} - - - - -static void CalcAnimation( const CStudioHdr *pStudioHdr, Vector *pos, Quaternion *q, - mstudioseqdesc_t &seqdesc, - int sequence, int animation, - float cycle, int boneMask ) -{ - int i; - - virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); - - if (pVModel) - { - CalcVirtualAnimation( pVModel, pStudioHdr, pos, q, seqdesc, sequence, animation, cycle, boneMask ); - return; - } - - mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( animation ); - mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); - mstudioanim_t *panim = animdesc.pAnim( ); - - int iFrame; - float s; - - float fFrame = cycle * (animdesc.numframes - 1); - - iFrame = (int)fFrame; - s = (fFrame - iFrame); - - float *pweight = seqdesc.pBoneweight( 0 ); - - // if the animation isn't available, look for the zero frame cache - if (!panim) - { - byte *pData = pStudioHdr->pZeroframeCache( animation ); - - if (pData) - { - // Msg("zeroframe %s\n", animdesc.pszName() ); - for (i = 0; i < pStudioHdr->numbones(); i++, pbone++, pweight++) - { - if (*pweight > 0 && (pbone->flags & boneMask)) - { - if (animdesc.flags & STUDIO_DELTA) - { - q[i].Init( 0.0f, 0.0f, 0.0f, 1.0f ); - pos[i].Init( 0.0f, 0.0f, 0.0f ); - } - else - { - q[i] = pbone->quat; - pos[i] = pbone->pos; - } - } - - if (pbone->flags & BONE_HAS_SAVEFRAME_POS) - { - if (*pweight > 0 && (pbone->flags & boneMask)) - { - pos[i] = *(Vector48 *)pData; - Assert( pos[i].IsValid() ); - } - pData += sizeof( Vector48 ); - } - if (!(animdesc.flags & STUDIO_DELTA) && (pbone->flags & BONE_HAS_SAVEFRAME_ROT) != 0) - { - if (*pweight > 0 && (pbone->flags & boneMask)) - { - q[i] = *(Quaternion32 *)pData; - Assert( q[i].IsValid() ); - } - pData += sizeof( Quaternion32 ); - } - } - return; - } - } - - // BUGBUG: the sequence, the anim, and the model can have all different bone mappings. - for (i = 0; i < pStudioHdr->numbones(); i++, pbone++, pweight++) - { - if (panim && panim->bone == i) - { - if (*pweight > 0 && (pbone->flags & boneMask)) - { - CalcBoneQuaternion( iFrame, s, pbone, panim, q[i] ); - CalcBonePosition ( iFrame, s, pbone, panim, pos[i] ); - } - panim = panim->pNext(); - } - else if (*pweight > 0 && (pbone->flags & boneMask)) - { - if (animdesc.flags & STUDIO_DELTA) - { - q[i].Init( 0.0f, 0.0f, 0.0f, 1.0f ); - pos[i].Init( 0.0f, 0.0f, 0.0f ); - } - else - { - q[i] = pbone->quat; - pos[i] = pbone->pos; - } - } - } -} - - - -// qt = ( s * p ) * q -void QuaternionSM( float s, const Quaternion &p, const Quaternion &q, Quaternion &qt ) -{ - Quaternion p1, q1; - - QuaternionScale( p, s, p1 ); - QuaternionMult( p1, q, q1 ); - QuaternionNormalize( q1 ); - qt[0] = q1[0]; - qt[1] = q1[1]; - qt[2] = q1[2]; - qt[3] = q1[3]; -} - -// qt = p * ( s * q ) -void QuaternionMA( const Quaternion &p, float s, const Quaternion &q, Quaternion &qt ) -{ - Quaternion p1, q1; - - QuaternionScale( q, s, q1 ); - QuaternionMult( p, q1, p1 ); - QuaternionNormalize( p1 ); - qt[0] = p1[0]; - qt[1] = p1[1]; - qt[2] = p1[2]; - qt[3] = p1[3]; -} - - -// qt = p * ( s * q ) -void QuaternionAccumulate( const Quaternion &p, float s, const Quaternion &q, Quaternion &qt ) -{ - Quaternion q2; - QuaternionAlign( p, q, q2 ); - - qt[0] = p[0] + s * q2[0]; - qt[1] = p[1] + s * q2[1]; - qt[2] = p[2] + s * q2[2]; - qt[3] = p[3] + s * q2[3]; -} - - - -//----------------------------------------------------------------------------- -// Purpose: blend together in world space q1,pos1 with q2,pos2. Return result in q1,pos1. -// 0 returns q1, pos1. 1 returns q2, pos2 -//----------------------------------------------------------------------------- - -void WorldSpaceSlerp( - const CStudioHdr *pStudioHdr, - Quaternion q1[MAXSTUDIOBONES], - Vector pos1[MAXSTUDIOBONES], - mstudioseqdesc_t &seqdesc, - int sequence, - const Quaternion q2[MAXSTUDIOBONES], - const Vector pos2[MAXSTUDIOBONES], - float s, - int boneMask ) -{ - int i, j; - float s1; // weight of parent for q2, pos2 - float s2; // weight for q2, pos2 - - // make fake root transform - matrix3x4_t rootXform; - SetIdentityMatrix( rootXform ); - - // matrices for q2, pos2 - static matrix3x4_t srcBoneToWorld[MAXSTUDIOBONES]; - CBoneBitList srcBoneComputed; - - static matrix3x4_t destBoneToWorld[MAXSTUDIOBONES]; - CBoneBitList destBoneComputed; - - static matrix3x4_t targetBoneToWorld[MAXSTUDIOBONES]; - CBoneBitList targetBoneComputed; - - virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); - const virtualgroup_t *pSeqGroup = NULL; - if (pVModel) - { - pSeqGroup = pVModel->pSeqGroup( sequence ); - } - - mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); - - for (i = 0; i < pStudioHdr->numbones(); i++) - { - // skip unused bones - if (!(pbone[i].flags & boneMask)) - { - continue; - } - - int n = pbone[i].parent; - s1 = 0.0; - if (pSeqGroup) - { - j = pSeqGroup->boneMap[i]; - if (j >= 0) - { - s2 = s * seqdesc.weight( j ); // blend in based on this bones weight - if (n != -1) - { - s1 = s * seqdesc.weight( pSeqGroup->boneMap[n] ); - } - } - else - { - s2 = 0.0; - } - } - else - { - s2 = s * seqdesc.weight( i ); // blend in based on this bones weight - if (n != -1) - { - s1 = s * seqdesc.weight( n ); - } - } - - if (s1 == 1.0 && s2 == 1.0) - { - pos1[i] = pos2[i]; - q1[i] = q2[i]; - } - else if (s2 > 0.0) - { - Quaternion srcQ, destQ; - Vector srcPos, destPos; - Quaternion targetQ; - Vector targetPos; - Vector tmp; - - BuildBoneChain( pStudioHdr, rootXform, pos1, q1, i, destBoneToWorld, destBoneComputed ); - BuildBoneChain( pStudioHdr, rootXform, pos2, q2, i, srcBoneToWorld, srcBoneComputed ); - - MatrixAngles( destBoneToWorld[i], destQ, destPos ); - MatrixAngles( srcBoneToWorld[i], srcQ, srcPos ); - - QuaternionSlerp( destQ, srcQ, s2, targetQ ); - AngleMatrix( targetQ, destPos, targetBoneToWorld[i] ); - - // back solve - if (n == -1) - { - MatrixAngles( targetBoneToWorld[i], q1[i], tmp ); - } - else - { - matrix3x4_t worldToBone; - MatrixInvert( targetBoneToWorld[n], worldToBone ); - - matrix3x4_t local; - ConcatTransforms( worldToBone, targetBoneToWorld[i], local ); - MatrixAngles( local, q1[i], tmp ); - - // blend bone lengths (local space) - pos1[i] = Lerp( s2, pos1[i], pos2[i] ); - } - } - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1. -// 0 returns q1, pos1. 1 returns q2, pos2 -//----------------------------------------------------------------------------- -void SlerpBones( - const CStudioHdr *pStudioHdr, - Quaternion q1[MAXSTUDIOBONES], - Vector pos1[MAXSTUDIOBONES], - mstudioseqdesc_t &seqdesc, // source of q2 and pos2 - int sequence, - const Quaternion q2[MAXSTUDIOBONES], - const Vector pos2[MAXSTUDIOBONES], - float s, - int boneMask ) -{ - if (s <= 0.0f) - { - return; - } - else if (s > 1.0f) - { - s = 1.0f; - } - - if (seqdesc.flags & STUDIO_WORLD) - { - WorldSpaceSlerp( pStudioHdr, q1, pos1, seqdesc, sequence, q2, pos2, s, boneMask ); - return; - } - - int i, j; - Quaternion q3, q4; - float s1, s2; - - virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); - const virtualgroup_t *pSeqGroup = NULL; - if (pVModel) - { - pSeqGroup = pVModel->pSeqGroup( sequence ); - } - - mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); - - if (seqdesc.flags & STUDIO_DELTA) - { - for (i = 0; i < pStudioHdr->numbones(); i++) - { - // skip unused bones - if (!(pbone[i].flags & boneMask)) - { - continue; - } - - if (pSeqGroup) - { - j = pSeqGroup->boneMap[i]; - if (j >= 0) - { - s2 = s * seqdesc.weight( j ); // blend in based on this bones weight - } - else - { - s2 = 0.0; - } - } - else - { - s2 = s * seqdesc.weight( i ); // blend in based on this bones weight - } - - if (s2 > 0.0) - { - if (seqdesc.flags & STUDIO_POST) - { - QuaternionMA( q1[i], s2, q2[i], q1[i] ); - - // FIXME: are these correct? - pos1[i][0] = pos1[i][0] + pos2[i][0] * s2; - pos1[i][1] = pos1[i][1] + pos2[i][1] * s2; - pos1[i][2] = pos1[i][2] + pos2[i][2] * s2; - } - else - { - QuaternionSM( s2, q2[i], q1[i], q1[i] ); - - // FIXME: are these correct? - pos1[i][0] = pos1[i][0] + pos2[i][0] * s2; - pos1[i][1] = pos1[i][1] + pos2[i][1] * s2; - pos1[i][2] = pos1[i][2] + pos2[i][2] * s2; - } - } - } - } - else - { - for (i = 0; i < pStudioHdr->numbones(); i++) - { - // skip unused bones - if (!(pbone[i].flags & boneMask)) - { - continue; - } - - if (pSeqGroup) - { - j = pSeqGroup->boneMap[i]; - if (j >= 0) - { - s2 = s * seqdesc.weight( j ); // blend in based on this bones weight - } - else - { - s2 = 0.0; - } - } - else - { - s2 = s * seqdesc.weight( i ); // blend in based on this animations weights - } - if (s2 > 0.0) - { - s1 = 1.0 - s2; - - if (pbone[i].flags & BONE_FIXED_ALIGNMENT) - { - QuaternionSlerpNoAlign( q2[i], q1[i], s1, q3 ); - } - else - { - QuaternionSlerp( q2[i], q1[i], s1, q3 ); - } - q1[i][0] = q3[0]; - q1[i][1] = q3[1]; - q1[i][2] = q3[2]; - q1[i][3] = q3[3]; - pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s2; - pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s2; - pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s2; - } - } - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: Inter-animation blend. Assumes both types are identical. -// blend together q1,pos1 with q2,pos2. Return result in q1,pos1. -// 0 returns q1, pos1. 1 returns q2, pos2 -//----------------------------------------------------------------------------- -void BlendBones( - const CStudioHdr *pStudioHdr, - Quaternion q1[MAXSTUDIOBONES], - Vector pos1[MAXSTUDIOBONES], - mstudioseqdesc_t &seqdesc, - int sequence, - const Quaternion q2[MAXSTUDIOBONES], - const Vector pos2[MAXSTUDIOBONES], - float s, - int boneMask ) -{ - int i, j; - Quaternion q3; - - virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); - const virtualgroup_t *pSeqGroup = NULL; - if (pVModel) - { - pSeqGroup = pVModel->pSeqGroup( sequence ); - } - - mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); - - if (s <= 0) - { - Assert(0); // shouldn't have been called - return; - } - else if (s >= 1.0) - { - Assert(0); // shouldn't have been called - for (i = 0; i < pStudioHdr->numbones(); i++) - { - // skip unused bones - if (!(pbone[i].flags & boneMask)) - { - continue; - } - - if (pSeqGroup) - { - j = pSeqGroup->boneMap[i]; - } - else - { - j = i; - } - - if (j >= 0 && seqdesc.weight( j ) > 0.0) - { - q1[i] = q2[i]; - pos1[i] = pos2[i]; - } - } - return; - } - - float s2 = s; - float s1 = 1.0 - s2; - - for (i = 0; i < pStudioHdr->numbones(); i++) - { - // skip unused bones - if (!(pbone[i].flags & boneMask)) - { - continue; - } - - if (pSeqGroup) - { - j = pSeqGroup->boneMap[i]; - } - else - { - j = i; - } - - if (j >= 0 && seqdesc.weight( j ) > 0.0) - { - if (pbone[i].flags & BONE_FIXED_ALIGNMENT) - { - QuaternionBlendNoAlign( q2[i], q1[i], s1, q3 ); - } - else - { - QuaternionBlend( q2[i], q1[i], s1, q3 ); - } - q1[i][0] = q3[0]; - q1[i][1] = q3[1]; - q1[i][2] = q3[2]; - q1[i][3] = q3[3]; - pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s2; - pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s2; - pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s2; - } - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: Scale a set of bones. Must be of type delta -//----------------------------------------------------------------------------- -void ScaleBones( - const CStudioHdr *pStudioHdr, - Quaternion q1[MAXSTUDIOBONES], - Vector pos1[MAXSTUDIOBONES], - int sequence, - float s, - int boneMask ) -{ - int i, j; - Quaternion q3; - - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequence ); - - virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); - const virtualgroup_t *pSeqGroup = NULL; - if (pVModel) - { - pSeqGroup = pVModel->pSeqGroup( sequence ); - } - - mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); - - float s2 = s; - float s1 = 1.0 - s2; - - for (i = 0; i < pStudioHdr->numbones(); i++) - { - // skip unused bones - if (!(pbone[i].flags & boneMask)) - { - continue; - } - - if (pSeqGroup) - { - j = pSeqGroup->boneMap[i]; - } - else - { - j = i; - } - - if (j >= 0 && seqdesc.weight( j ) > 0.0) - { - QuaternionIdentityBlend( q1[i], s1, q1[i] ); - VectorScale( pos1[i], s2, pos1[i] ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: resolve a global pose parameter to the specific setting for this sequence -//----------------------------------------------------------------------------- -void Studio_LocalPoseParameter( const CStudioHdr *pStudioHdr, const float poseParameter[], mstudioseqdesc_t &seqdesc, int iSequence, int iLocalIndex, float &flSetting, int &index ) -{ - int iPose = pStudioHdr->GetSharedPoseParameter( iSequence, seqdesc.paramindex[iLocalIndex] ); - - if (iPose == -1) - { - flSetting = 0; - index = 0; - return; - } - - const mstudioposeparamdesc_t &Pose = pStudioHdr->pPoseParameter( iPose ); - - float flValue = poseParameter[iPose]; - - if (Pose.loop) - { - float wrap = (Pose.start + Pose.end) / 2.0 + Pose.loop / 2.0; - float shift = Pose.loop - wrap; - - flValue = flValue - Pose.loop * floor((flValue + shift) / Pose.loop); - } - - if (seqdesc.posekeyindex == 0) - { - float flLocalStart = ((float)seqdesc.paramstart[iLocalIndex] - Pose.start) / (Pose.end - Pose.start); - float flLocalEnd = ((float)seqdesc.paramend[iLocalIndex] - Pose.start) / (Pose.end - Pose.start); - - // convert into local range - flSetting = (flValue - flLocalStart) / (flLocalEnd - flLocalStart); - - // clamp. This shouldn't ever need to happen if it's looping. - if (flSetting < 0) - flSetting = 0; - if (flSetting > 1) - flSetting = 1; - - index = 0; - if (seqdesc.groupsize[iLocalIndex] > 2 ) - { - // estimate index - index = (int)(flSetting * (seqdesc.groupsize[iLocalIndex] - 1)); - if (index == seqdesc.groupsize[iLocalIndex] - 1) index = seqdesc.groupsize[iLocalIndex] - 2; - flSetting = flSetting * (seqdesc.groupsize[iLocalIndex] - 1) - index; - } - } - else - { - flValue = flValue * (Pose.end - Pose.start) + Pose.start; - index = 0; - - // FIXME: this needs to be 2D - // FIXME: this shouldn't be a linear search - - while (1) - { - flSetting = (flValue - seqdesc.poseKey( iLocalIndex, index )) / (seqdesc.poseKey( iLocalIndex, index + 1 ) - seqdesc.poseKey( iLocalIndex, index )); - /* - if (index > 0 && flSetting < 0.0) - { - index--; - continue; - } - else - */ - if (index < seqdesc.groupsize[iLocalIndex] - 2 && flSetting > 1.0) - { - index++; - continue; - } - break; - } - - // clamp. - if (flSetting < 0.0f) - flSetting = 0.0f; - if (flSetting > 1.0f) - flSetting = 1.0f; - } -} - -void Studio_CalcBoneToBoneTransform( const CStudioHdr *pStudioHdr, int inputBoneIndex, int outputBoneIndex, matrix3x4_t& matrixOut ) -{ - mstudiobone_t *pbone = pStudioHdr->pBone( inputBoneIndex ); - - matrix3x4_t inputToPose; - MatrixInvert( pbone->poseToBone, inputToPose ); - ConcatTransforms( pStudioHdr->pBone( outputBoneIndex )->poseToBone, inputToPose, matrixOut ); -} - -//----------------------------------------------------------------------------- -// Purpose: calculate a pose for a single sequence -//----------------------------------------------------------------------------- -void InitPose( - const CStudioHdr *pStudioHdr, - Vector pos[], - Quaternion q[] - ) -{ - for (int i = 0; i < pStudioHdr->numbones(); i++) - { - mstudiobone_t *pbone = pStudioHdr->pBone( i ); - - pos[i] = pbone->pos; - q[i] = pbone->quat; - } -} - - -inline bool PoseIsAllZeros( - const CStudioHdr *pStudioHdr, - int sequence, - mstudioseqdesc_t &seqdesc, - int i0, - int i1 - ) -{ - int baseanim; - - // remove "zero" positional blends - baseanim = pStudioHdr->iRelativeAnim( sequence, seqdesc.anim(i0 ,i1 ) ); - mstudioanimdesc_t &anim = pStudioHdr->pAnimdesc( baseanim ); - return (anim.flags & STUDIO_ALLZEROS) != 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: calculate a pose for a single sequence -//----------------------------------------------------------------------------- -bool CalcPoseSingle( - const CStudioHdr *pStudioHdr, - Vector pos[], - Quaternion q[], - mstudioseqdesc_t &seqdesc, - int sequence, - float cycle, - const float poseParameter[], - int boneMask, - float flTime - ) -{ - ASSERT_NO_REENTRY(); - - static Vector pos2[MAXSTUDIOBONES]; - static Quaternion q2[MAXSTUDIOBONES]; - static Vector pos3[MAXSTUDIOBONES]; - static Quaternion q3[MAXSTUDIOBONES]; - - if (sequence >= pStudioHdr->GetNumSeq()) - { - sequence = 0; - seqdesc = pStudioHdr->pSeqdesc( sequence ); - } - - - int i0 = 0, i1 = 0; - float s0 = 0, s1 = 0; - - Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, sequence, 0, s0, i0 ); - Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, sequence, 1, s1, i1 ); - - - if (seqdesc.flags & STUDIO_REALTIME) - { - float cps = Studio_CPS( pStudioHdr, seqdesc, sequence, poseParameter ); - cycle = flTime * cps; - cycle = cycle - (int)cycle; - } - else if (cycle < 0 || cycle >= 1) - { - if (seqdesc.flags & STUDIO_LOOPING) - { - cycle = cycle - (int)cycle; - if (cycle < 0) cycle += 1; - } - else - { - cycle = max( 0.0, min( cycle, 0.9999 ) ); - } - } - - if (s0 < 0.001) - { - if (s1 < 0.001) - { - if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0, i1 )) - return false; - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1 ), cycle, boneMask ); - } - else if (s1 > 0.999) - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1+1 ), cycle, boneMask ); - } - else - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1 ), cycle, boneMask ); - CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0 , i1+1 ), cycle, boneMask ); - BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); - } - } - else if (s0 > 0.999) - { - if (s1 < 0.001) - { - if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0+1, i1 )) - return false; - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1 ), cycle, boneMask ); - } - else if (s1 > 0.999) - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1+1 ), cycle, boneMask ); - } - else - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1 ), cycle, boneMask ); - CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1, i1+1 ), cycle, boneMask ); - BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); - } - } - else - { - if (s1 < 0.001) - { - if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0+1, i1 )) - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); - ScaleBones( pStudioHdr, q, pos, sequence, 1.0 - s0, boneMask ); - } - else if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0, i1 )) - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1 ,i1 ), cycle, boneMask ); - ScaleBones( pStudioHdr, q, pos, sequence, s0, boneMask ); - } - else - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); - CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1 ), cycle, boneMask ); - - BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask ); - } - } - else if (s1 > 0.999) - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1+1 ), cycle, boneMask ); - CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1+1 ), cycle, boneMask ); - BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask ); - } - else - { - CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); - CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1 ), cycle, boneMask ); - BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask ); - - CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0 , i1+1), cycle, boneMask ); - CalcAnimation( pStudioHdr, pos3, q3, seqdesc, sequence, seqdesc.anim( i0+1, i1+1), cycle, boneMask ); - BlendBones( pStudioHdr, q2, pos2, seqdesc, sequence, q3, pos3, s0, boneMask ); - - BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); - } - } - - return true; -} - - - - -//----------------------------------------------------------------------------- -// Purpose: calculate a pose for a single sequence -// adds autolayers, runs local ik rukes -//----------------------------------------------------------------------------- -void AddSequenceLayers( - const CStudioHdr *pStudioHdr, - CIKContext *pIKContext, - Vector pos[], - Quaternion q[], - mstudioseqdesc_t &seqdesc, - int sequence, - float cycle, - const float poseParameter[], - int boneMask, - float flWeight, - float flTime - ) -{ - for (int i = 0; i < seqdesc.numautolayers; i++) - { - mstudioautolayer_t *pLayer = seqdesc.pAutolayer( i ); - - if (pLayer->flags & STUDIO_AL_LOCAL) - continue; - - float layerCycle = cycle; - float layerWeight = flWeight; - - if (pLayer->start != pLayer->end) - { - float s = 1.0; - float index; - - if (!(pLayer->flags & STUDIO_AL_POSE)) - { - index = cycle; - } - else - { - int iPose = pStudioHdr->GetSharedPoseParameter( pLayer->iSequence, pLayer->iPose ); - if (iPose != -1) - { - const mstudioposeparamdesc_t &Pose = pStudioHdr->pPoseParameter( iPose ); - index = poseParameter[ iPose ] * (Pose.end - Pose.start) + Pose.start; - } - else - { - index = 0; - } - } - - if (index < pLayer->start) - continue; - if (index >= pLayer->end) - continue; - - if (index < pLayer->peak && pLayer->start != pLayer->peak) - { - s = (index - pLayer->start) / (pLayer->peak - pLayer->start); - } - else if (index > pLayer->tail && pLayer->end != pLayer->tail) - { - s = (pLayer->end - index) / (pLayer->end - pLayer->tail); - } - - if (pLayer->flags & STUDIO_AL_SPLINE) - { - s = SimpleSpline( s ); - } - - if ((pLayer->flags & STUDIO_AL_XFADE) && (index > pLayer->tail)) - { - layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight ); - } - else if (pLayer->flags & STUDIO_AL_NOBLEND) - { - layerWeight = s; - } - else - { - layerWeight = flWeight * s; - } - - if (!(pLayer->flags & STUDIO_AL_POSE)) - { - layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start); - } - } - - int iSequence = pStudioHdr->iRelativeSeq( sequence, pLayer->iSequence ); - AccumulatePose( pStudioHdr, pIKContext, pos, q, iSequence, layerCycle, poseParameter, boneMask, layerWeight, flTime ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: calculate a pose for a single sequence -// adds autolayers, runs local ik rukes -//----------------------------------------------------------------------------- -void AddLocalLayers( - const CStudioHdr *pStudioHdr, - CIKContext *pIKContext, - Vector pos[], - Quaternion q[], - mstudioseqdesc_t &seqdesc, - int sequence, - float cycle, - const float poseParameter[], - int boneMask, - float flWeight, - float flTime - ) -{ - if (!(seqdesc.flags & STUDIO_LOCAL)) - { - return; - } - - for (int i = 0; i < seqdesc.numautolayers; i++) - { - mstudioautolayer_t *pLayer = seqdesc.pAutolayer( i ); - - if (!(pLayer->flags & STUDIO_AL_LOCAL)) - continue; - - float layerCycle = cycle; - float layerWeight = flWeight; - - if (pLayer->start != pLayer->end) - { - float s = 1.0; - - if (cycle < pLayer->start) - continue; - if (cycle >= pLayer->end) - continue; - - if (cycle < pLayer->peak && pLayer->start != pLayer->peak) - { - s = (cycle - pLayer->start) / (pLayer->peak - pLayer->start); - } - else if (cycle > pLayer->tail && pLayer->end != pLayer->tail) - { - s = (pLayer->end - cycle) / (pLayer->end - pLayer->tail); - } - - if (pLayer->flags & STUDIO_AL_SPLINE) - { - s = SimpleSpline( s ); - } - - if ((pLayer->flags & STUDIO_AL_XFADE) && (cycle > pLayer->tail)) - { - layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight ); - } - else if (pLayer->flags & STUDIO_AL_NOBLEND) - { - layerWeight = s; - } - else - { - layerWeight = flWeight * s; - } - - layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start); - } - - int iSequence = pStudioHdr->iRelativeSeq( sequence, pLayer->iSequence ); - AccumulatePose( pStudioHdr, pIKContext, pos, q, iSequence, layerCycle, poseParameter, boneMask, layerWeight, flTime ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: calculate a pose for a single sequence -// adds autolayers, runs local ik rukes -//----------------------------------------------------------------------------- -void CalcPose( - const CStudioHdr *pStudioHdr, - CIKContext *pIKContext, - Vector pos[], - Quaternion q[], - int sequence, - float cycle, - const float poseParameter[], - int boneMask, - float flWeight, - float flTime - ) -{ - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequence ); - - Assert( flWeight >= 0.0f && flWeight <= 1.0f ); - // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up - flWeight = clamp( flWeight, 0.0f, 1.0f ); - - // add any IK locks to prevent numautolayers from moving extremities - CIKContext seq_ik; - if (seqdesc.numiklocks) - { - seq_ik.Init( pStudioHdr, vec3_angle, vec3_origin, 0.0, 0, boneMask ); // local space relative so absolute position doesn't mater - seq_ik.AddSequenceLocks( seqdesc, pos, q ); - } - - CalcPoseSingle( pStudioHdr, pos, q, seqdesc, sequence, cycle, poseParameter, boneMask, flTime ); - - if ( pIKContext ) - { - pIKContext->AddDependencies( seqdesc, sequence, cycle, poseParameter, flWeight ); - } - - AddSequenceLayers( pStudioHdr, pIKContext, pos, q, seqdesc, sequence, cycle, poseParameter, boneMask, flWeight, flTime ); - - if (seqdesc.numiklocks) - { - seq_ik.SolveSequenceLocks( seqdesc, pos, q ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: accumulate a pose for a single sequence on top of existing animation -// adds autolayers, runs local ik rukes -//----------------------------------------------------------------------------- -void AccumulatePose( - const CStudioHdr *pStudioHdr, - CIKContext *pIKContext, - Vector pos[], - Quaternion q[], - int sequence, - float cycle, - const float poseParameter[], - int boneMask, - float flWeight, - float flTime - ) -{ - Vector pos2[MAXSTUDIOBONES]; - Quaternion q2[MAXSTUDIOBONES]; - - Assert( flWeight >= 0.0f && flWeight <= 1.0f ); - // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up - flWeight = clamp( flWeight, 0.0f, 1.0f ); - - if ( sequence < 0 ) - return; - - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequence ); - - // add any IK locks to prevent extremities from moving - CIKContext seq_ik; - if (seqdesc.numiklocks) - { - seq_ik.Init( pStudioHdr, vec3_angle, vec3_origin, 0.0, 0, boneMask ); // local space relative so absolute position doesn't mater - seq_ik.AddSequenceLocks( seqdesc, pos, q ); - } - - if (seqdesc.flags & STUDIO_LOCAL) - { - InitPose( pStudioHdr, pos2, q2 ); - } - - if (CalcPoseSingle( pStudioHdr, pos2, q2, seqdesc, sequence, cycle, poseParameter, boneMask, flTime )) - { - // this weight is wrong, the IK rules won't composite at the correct intensity - AddLocalLayers( pStudioHdr, pIKContext, pos2, q2, seqdesc, sequence, cycle, poseParameter, boneMask, 1.0, flTime ); - SlerpBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, flWeight, boneMask ); - } - - if ( pIKContext ) - { - pIKContext->AddDependencies( seqdesc, sequence, cycle, poseParameter, flWeight ); - } - - AddSequenceLayers( pStudioHdr, pIKContext, pos, q, seqdesc, sequence, cycle, poseParameter, boneMask, flWeight, flTime ); - - if (seqdesc.numiklocks) - { - seq_ik.SolveSequenceLocks( seqdesc, pos, q ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1. -// 0 returns q1, pos1. 1 returns q2, pos2 -//----------------------------------------------------------------------------- -void CalcBoneAdj( - const CStudioHdr *pStudioHdr, - Vector pos[], - Quaternion q[], - const float controllers[], - int boneMask - ) -{ - int i, j, k; - float value; - mstudiobonecontroller_t *pbonecontroller; - Vector p0; - RadianEuler a0; - Quaternion q0; - - for (j = 0; j < pStudioHdr->numbonecontrollers(); j++) - { - pbonecontroller = pStudioHdr->pBonecontroller( j ); - k = pbonecontroller->bone; - - if (pStudioHdr->pBone( k )->flags & boneMask) - { - i = pbonecontroller->inputfield; - value = controllers[i]; - if (value < 0) value = 0; - if (value > 1.0) value = 1.0; - value = (1.0 - value) * pbonecontroller->start + value * pbonecontroller->end; - - switch(pbonecontroller->type & STUDIO_TYPES) - { - case STUDIO_XR: - a0.Init( value * (M_PI / 180.0), 0, 0 ); - AngleQuaternion( a0, q0 ); - QuaternionSM( 1.0, q0, q[k], q[k] ); - break; - case STUDIO_YR: - a0.Init( 0, value * (M_PI / 180.0), 0 ); - AngleQuaternion( a0, q0 ); - QuaternionSM( 1.0, q0, q[k], q[k] ); - break; - case STUDIO_ZR: - a0.Init( 0, 0, value * (M_PI / 180.0) ); - AngleQuaternion( a0, q0 ); - QuaternionSM( 1.0, q0, q[k], q[k] ); - break; - case STUDIO_X: - pos[k].x += value; - break; - case STUDIO_Y: - pos[k].y += value; - break; - case STUDIO_Z: - pos[k].z += value; - break; - } - } - } -} - - -void CalcBoneDerivatives( Vector &velocity, AngularImpulse &angVel, const matrix3x4_t &prev, const matrix3x4_t ¤t, float dt ) -{ - float scale = 1.0; - if ( dt > 0 ) - { - scale = 1.0 / dt; - } - - Vector endPosition, startPosition, deltaAxis; - QAngle endAngles, startAngles; - float deltaAngle; - - MatrixAngles( prev, startAngles, startPosition ); - MatrixAngles( current, endAngles, endPosition ); - - velocity.x = (endPosition.x - startPosition.x) * scale; - velocity.y = (endPosition.y - startPosition.y) * scale; - velocity.z = (endPosition.z - startPosition.z) * scale; - RotationDeltaAxisAngle( startAngles, endAngles, deltaAxis, deltaAngle ); - VectorScale( deltaAxis, (deltaAngle * scale), angVel ); -} - -void CalcBoneVelocityFromDerivative( const QAngle &vecAngles, Vector &velocity, AngularImpulse &angVel, const matrix3x4_t ¤t ) -{ - Vector vecLocalVelocity; - AngularImpulse LocalAngVel; - Quaternion q; - float angle; - MatrixAngles( current, q, vecLocalVelocity ); - QuaternionAxisAngle( q, LocalAngVel, angle ); - LocalAngVel *= angle; - - matrix3x4_t matAngles; - AngleMatrix( vecAngles, matAngles ); - VectorTransform( vecLocalVelocity, matAngles, velocity ); - VectorTransform( LocalAngVel, matAngles, angVel ); -} - - - - -class ik -{ -public: -//-------- SOLVE TWO LINK INVERSE KINEMATICS ------------- -// Author: Ken Perlin -// -// Given a two link joint from [0,0,0] to end effector position P, -// let link lengths be a and b, and let norm |P| = c. Clearly a+b <= c. -// -// Problem: find a "knee" position Q such that |Q| = a and |P-Q| = b. -// -// In the case of a point on the x axis R = [c,0,0], there is a -// closed form solution S = [d,e,0], where |S| = a and |R-S| = b: -// -// d2+e2 = a2 -- because |S| = a -// (c-d)2+e2 = b2 -- because |R-S| = b -// -// c2-2cd+d2+e2 = b2 -- combine the two equations -// c2-2cd = b2 - a2 -// c-2d = (b2-a2)/c -// d - c/2 = (a2-b2)/c / 2 -// -// d = (c + (a2-b2/c) / 2 -- to solve for d and e. -// e = sqrt(a2-d2) - - static float findD(float a, float b, float c) { - return (c + (a*a-b*b)/c) / 2; - } - static float findE(float a, float d) { return sqrt(a*a-d*d); } - -// This leads to a solution to the more general problem: -// -// (1) R = Mfwd(P) -- rotate P onto the x axis -// (2) Solve for S -// (3) Q = Minv(S) -- rotate back again - - static float Mfwd[3][3]; - static float Minv[3][3]; - - static bool solve(float A, float B, float const P[], float const D[], float Q[]) { - float R[3]; - defineM(P,D); - rot(Minv,P,R); - float r = length(R); - float d = findD(A,B,r); - float e = findE(A,d); - float S[3] = {d,e,0}; - rot(Mfwd,S,Q); - return d > (r - B) && d < A; - } - -// If "knee" position Q needs to be as close as possible to some point D, -// then choose M such that M(D) is in the y>0 half of the z=0 plane. -// -// Given that constraint, define the forward and inverse of M as follows: - - static void defineM(float const P[], float const D[]) { - float *X = Minv[0], *Y = Minv[1], *Z = Minv[2]; - -// Minv defines a coordinate system whose x axis contains P, so X = unit(P). - int i; - for (i = 0 ; i < 3 ; i++) - X[i] = P[i]; - normalize(X); - -// Its y axis is perpendicular to P, so Y = unit( E - X(E·X) ). - - float dDOTx = dot(D,X); - for (i = 0 ; i < 3 ; i++) - Y[i] = D[i] - dDOTx * X[i]; - normalize(Y); - -// Its z axis is perpendicular to both X and Y, so Z = X×Y. - - cross(X,Y,Z); - -// Mfwd = (Minv)T, since transposing inverts a rotation matrix. - - for (i = 0 ; i < 3 ; i++) { - Mfwd[i][0] = Minv[0][i]; - Mfwd[i][1] = Minv[1][i]; - Mfwd[i][2] = Minv[2][i]; - } - } - -//------------ GENERAL VECTOR MATH SUPPORT ----------- - - static float dot(float const a[], float const b[]) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; } - - static float length(float const v[]) { return sqrt( dot(v,v) ); } - - static void normalize(float v[]) { - float norm = length(v); - for (int i = 0 ; i < 3 ; i++) - v[i] /= norm; - } - - static void cross(float const a[], float const b[], float c[]) { - c[0] = a[1] * b[2] - a[2] * b[1]; - c[1] = a[2] * b[0] - a[0] * b[2]; - c[2] = a[0] * b[1] - a[1] * b[0]; - } - - static void rot(float const M[3][3], float const src[], float dst[]) { - for (int i = 0 ; i < 3 ; i++) - dst[i] = dot(M[i],src); - } -}; - -float ik::Mfwd[3][3]; -float ik::Minv[3][3]; - - - -//----------------------------------------------------------------------------- -// Purpose: visual debugging code -//----------------------------------------------------------------------------- -#if 1 -inline void debugLine(const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration) { }; -#else -extern void drawLine( const Vector &p1, const Vector &p2, int r = 0, int g = 0, int b = 1, bool noDepthTest = true, float duration = 0.1 ); -void debugLine(const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration) -{ - drawLine( origin, dest, r, g, b, noDepthTest, duration ); -} -#endif - - -//----------------------------------------------------------------------------- -// Purpose: for a 2 bone chain, find the IK solution and reset the matrices -//----------------------------------------------------------------------------- -bool Studio_SolveIK( mstudioikchain_t *pikchain, Vector &targetFoot, matrix3x4_t *pBoneToWorld ) -{ - if (pikchain->pLink(0)->kneeDir.LengthSqr() > 0.0) - { - Vector targetKneeDir, targetKneePos; - // FIXME: knee length should be as long as the legs - Vector tmp = pikchain->pLink( 0 )->kneeDir; - VectorRotate( tmp, pBoneToWorld[ pikchain->pLink( 0 )->bone ], targetKneeDir ); - MatrixPosition( pBoneToWorld[ pikchain->pLink( 1 )->bone ], targetKneePos ); - return Studio_SolveIK( pikchain->pLink( 0 )->bone, pikchain->pLink( 1 )->bone, pikchain->pLink( 2 )->bone, targetFoot, targetKneePos, targetKneeDir, pBoneToWorld ); - } - else - { - return Studio_SolveIK( pikchain->pLink( 0 )->bone, pikchain->pLink( 1 )->bone, pikchain->pLink( 2 )->bone, targetFoot, pBoneToWorld ); - } -} - - -#define KNEEMAX_EPSILON 0.9998 // (0.9998 is about 1 degree) - -//----------------------------------------------------------------------------- -// Purpose: Solve Knee position for a known hip and foot location, but no specific knee direction preference -//----------------------------------------------------------------------------- - -bool Studio_SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, matrix3x4_t *pBoneToWorld ) -{ - Vector worldFoot, worldKnee, worldThigh; - - MatrixPosition( pBoneToWorld[ iThigh ], worldThigh ); - MatrixPosition( pBoneToWorld[ iKnee ], worldKnee ); - MatrixPosition( pBoneToWorld[ iFoot ], worldFoot ); - - //debugLine( worldThigh, worldKnee, 0, 0, 255, true, 0 ); - //debugLine( worldKnee, worldFoot, 0, 0, 255, true, 0 ); - - Vector ikFoot, ikKnee; - - ikFoot = targetFoot - worldThigh; - ikKnee = worldKnee - worldThigh; - - float l1 = (worldKnee-worldThigh).Length(); - float l2 = (worldFoot-worldKnee).Length(); - float l3 = (worldFoot-worldThigh).Length(); - - // leg too straight to figure out knee? - if (l3 > (l1 + l2) * KNEEMAX_EPSILON) - { - return false; - } - - Vector ikHalf = (worldFoot-worldThigh) * (l1 / l3); - - // FIXME: what to do when the knee completely straight? - Vector ikKneeDir = ikKnee - ikHalf; - VectorNormalize( ikKneeDir ); - - return Studio_SolveIK( iThigh, iKnee, iFoot, targetFoot, worldKnee, ikKneeDir, pBoneToWorld ); -} - -//----------------------------------------------------------------------------- -// Purpose: Realign the matrix so that its X axis points along the desired axis. -//----------------------------------------------------------------------------- -void Studio_AlignIKMatrix( matrix3x4_t &mMat, const Vector &vAlignTo ) -{ - Vector tmp1, tmp2, tmp3; - - // Column 0 (X) becomes the vector. - tmp1 = vAlignTo; - VectorNormalize( tmp1 ); - MatrixSetColumn( tmp1, 0, mMat ); - - // Column 1 (Y) is the cross of the vector and column 2 (Z). - MatrixGetColumn( mMat, 2, tmp3 ); - tmp2 = tmp3.Cross( tmp1 ); - VectorNormalize( tmp2 ); - // FIXME: check for X being too near to Z - MatrixSetColumn( tmp2, 1, mMat ); - - // Column 2 (Z) is the cross of columns 0 (X) and 1 (Y). - tmp3 = tmp1.Cross( tmp2 ); - MatrixSetColumn( tmp3, 2, mMat ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Solve Knee position for a known hip and foot location, and a known knee direction -//----------------------------------------------------------------------------- - -bool Studio_SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, Vector &targetKneePos, Vector &targetKneeDir, matrix3x4_t *pBoneToWorld ) -{ - Vector worldFoot, worldKnee, worldThigh; - - MatrixPosition( pBoneToWorld[ iThigh ], worldThigh ); - MatrixPosition( pBoneToWorld[ iKnee ], worldKnee ); - MatrixPosition( pBoneToWorld[ iFoot ], worldFoot ); - - //debugLine( worldThigh, worldKnee, 0, 0, 255, true, 0 ); - //debugLine( worldThigh, worldThigh + targetKneeDir, 0, 0, 255, true, 0 ); - // debugLine( worldKnee, targetKnee, 0, 0, 255, true, 0 ); - - Vector ikFoot, ikTargetKnee, ikKnee; - - ikFoot = targetFoot - worldThigh; - ikKnee = targetKneePos - worldThigh; - - float l1 = (worldKnee-worldThigh).Length(); - float l2 = (worldFoot-worldKnee).Length(); - - // exaggerate knee targets for legs that are nearly straight - // FIXME: should be configurable, and the ikKnee should be from the original animation, not modifed - float d = (targetFoot-worldThigh).Length() - min( l1, l2 ); - d = max( l1 + l2, d ); - // FIXME: too short knee directions cause trouble - d = d * 100; - - ikTargetKnee = ikKnee + targetKneeDir * d; - - // debugLine( worldKnee, worldThigh + ikTargetKnee, 0, 0, 255, true, 0 ); - - int color[3] = { 0, 255, 0 }; - - // too far away? (0.9998 is about 1 degree) - if (ikFoot.Length() > (l1 + l2) * KNEEMAX_EPSILON) - { - VectorNormalize( ikFoot ); - VectorScale( ikFoot, (l1 + l2) * KNEEMAX_EPSILON, ikFoot ); - color[0] = 255; color[1] = 0; color[2] = 0; - } - - // too close? - if (ikFoot.Length() < fabs(l1 - l2) * 1.05) - { - VectorNormalize( ikFoot ); - VectorScale( ikFoot, fabs(l1 - l2) * 1.05, ikFoot ); - } - - if (ik::solve( l1, l2, ikFoot.Base(), ikTargetKnee.Base(), ikKnee.Base() )) - { - matrix3x4_t& mWorldThigh = pBoneToWorld[ iThigh ]; - matrix3x4_t& mWorldKnee = pBoneToWorld[ iKnee ]; - matrix3x4_t& mWorldFoot = pBoneToWorld[ iFoot ]; - - //debugLine( worldThigh, ikKnee + worldThigh, 255, 0, 0, true, 0 ); - //debugLine( ikKnee + worldThigh, ikFoot + worldThigh, 255, 0, 0, true,0 ); - - // debugLine( worldThigh, ikKnee + worldThigh, color[0], color[1], color[2], true, 0 ); - // debugLine( ikKnee + worldThigh, ikFoot + worldThigh, color[0], color[1], color[2], true,0 ); - - - // build transformation matrix for thigh - Studio_AlignIKMatrix( mWorldThigh, ikKnee ); - Studio_AlignIKMatrix( mWorldKnee, ikFoot - ikKnee ); - - - mWorldKnee[0][3] = ikKnee.x + worldThigh.x; - mWorldKnee[1][3] = ikKnee.y + worldThigh.y; - mWorldKnee[2][3] = ikKnee.z + worldThigh.z; - - mWorldFoot[0][3] = ikFoot.x + worldThigh.x; - mWorldFoot[1][3] = ikFoot.y + worldThigh.y; - mWorldFoot[2][3] = ikFoot.z + worldThigh.z; - - return true; - } - else - { - /* - debugLine( worldThigh, worldThigh + ikKnee, 255, 0, 0, true, 0 ); - debugLine( worldThigh + ikKnee, worldThigh + ikFoot, 255, 0, 0, true, 0 ); - debugLine( worldThigh + ikFoot, worldThigh, 255, 0, 0, true, 0 ); - debugLine( worldThigh + ikKnee, worldThigh + ikTargetKnee, 255, 0, 0, true, 0 ); - */ - return false; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -float Studio_IKRuleWeight( mstudioikrule_t &ikRule, const mstudioanimdesc_t *panim, float flCycle, int &iFrame, float &fraq ) -{ - if (ikRule.end > 1.0f && flCycle < ikRule.start) - { - flCycle = flCycle + 1.0f; - } - - float value = 0.0f; - fraq = (panim->numframes - 1) * (flCycle - ikRule.start) + ikRule.iStart; - iFrame = (int)fraq; - fraq = fraq - iFrame; - - if (flCycle < ikRule.start) - { - iFrame = ikRule.iStart; - fraq = 0.0f; - return 0.0f; - } - else if (flCycle < ikRule.peak ) - { - value = (flCycle - ikRule.start) / (ikRule.peak - ikRule.start); - } - else if (flCycle < ikRule.tail ) - { - return 1.0f; - } - else if (flCycle < ikRule.end ) - { - value = 1.0f - ((flCycle - ikRule.tail) / (ikRule.end - ikRule.tail)); - } - else - { - fraq = (panim->numframes - 1) * (ikRule.end - ikRule.start) + ikRule.iStart; - iFrame = (int)fraq; - fraq = fraq - iFrame; - } - return SimpleSpline( value ); -} - - -float Studio_IKRuleWeight( ikcontextikrule_t &ikRule, float flCycle ) -{ - if (ikRule.end > 1.0f && flCycle < ikRule.start) - { - flCycle = flCycle + 1.0f; - } - - float value = 0.0f; - if (flCycle < ikRule.start) - { - return 0.0f; - } - else if (flCycle < ikRule.peak ) - { - value = (flCycle - ikRule.start) / (ikRule.peak - ikRule.start); - } - else if (flCycle < ikRule.tail ) - { - return 1.0f; - } - else if (flCycle < ikRule.end ) - { - value = 1.0f - ((flCycle - ikRule.tail) / (ikRule.end - ikRule.tail)); - } - return 3.0f * value * value - 2.0f * value * value * value; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -bool Studio_IKShouldLatch( ikcontextikrule_t &ikRule, float flCycle ) -{ - if (ikRule.end > 1.0f && flCycle < ikRule.start) - { - flCycle = flCycle + 1.0f; - } - - if (flCycle < ikRule.peak ) - { - return false; - } - else if (flCycle < ikRule.end ) - { - return true; - } - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -float Studio_IKTail( ikcontextikrule_t &ikRule, float flCycle ) -{ - if (ikRule.end > 1.0f && flCycle < ikRule.start) - { - flCycle = flCycle + 1.0f; - } - - if (flCycle <= ikRule.tail ) - { - return 0.0f; - } - else if (flCycle < ikRule.end ) - { - return ((flCycle - ikRule.tail) / (ikRule.end - ikRule.tail)); - } - return 0.0; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -bool Studio_IKAnimationError( const CStudioHdr *pStudioHdr, mstudioikrule_t *pRule, const mstudioanimdesc_t *panim, float flCycle, Vector &pos, Quaternion &q, float &flWeight ) -{ - float fraq; - int iFrame; - - flWeight = Studio_IKRuleWeight( *pRule, panim, flCycle, iFrame, fraq ); - Assert( fraq >= 0.0 && fraq < 1.0 ); - Assert( flWeight >= 0.0f && flWeight <= 1.0f ); - - // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up - flWeight = clamp( flWeight, 0.0f, 1.0f ); - - if (pRule->type != IK_GROUND && flWeight < 0.0001) - return false; - - mstudioikerror_t *pError = pRule->pError( iFrame ); - if (pError != NULL) - { - if (fraq < 0.001) - { - q = pError[0].q; - pos = pError[0].pos; - } - else - { - QuaternionBlend( pError[0].q, pError[1].q, fraq, q ); - pos = pError[0].pos * (1.0f - fraq) + pError[1].pos * fraq; - } - return true; - } - - mstudiocompressedikerror_t *pCompressed = pRule->pCompressedError(); - if (pCompressed != NULL) - { - iFrame = iFrame - pRule->iStart; - - Vector p1, p2; - ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 0 ), pCompressed->scale[0], p1.x, p2.x ); - ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 1 ), pCompressed->scale[1], p1.y, p2.y ); - ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 2 ), pCompressed->scale[2], p1.z, p2.z ); - pos = p1 * (1 - fraq) + p2 * fraq; - - Quaternion q1, q2; - RadianEuler angle1, angle2; - ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 3 ), pCompressed->scale[3], angle1.x, angle2.x ); - ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 4 ), pCompressed->scale[4], angle1.y, angle2.y ); - ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 5 ), pCompressed->scale[5], angle1.z, angle2.z ); - - if (angle1.x != angle2.x || angle1.y != angle2.y || angle1.z != angle2.z) - { - AngleQuaternion( angle1, q1 ); - AngleQuaternion( angle2, q2 ); - QuaternionBlend( q1, q2, fraq, q ); - } - else - { - AngleQuaternion( angle1, q ); - } - return true; - } - // no data, disable IK rule - Assert( 0 ); - flWeight = 0.0f; - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: For a specific sequence:rule, find where it starts, stops, and what -// the estimated offset from the connection point is. -// return true if the rule is within bounds. -//----------------------------------------------------------------------------- - -bool Studio_IKSequenceError( const CStudioHdr *pStudioHdr, mstudioseqdesc_t &seqdesc, int iSequence, float flCycle, int iRule, const float poseParameter[], mstudioanimdesc_t *panim[4], float weight[4], ikcontextikrule_t &ikRule ) -{ - int i; - - memset( &ikRule, 0, sizeof(ikRule) ); - ikRule.start = ikRule.peak = ikRule.tail = ikRule.end = 0; - - - mstudioikrule_t *prevRule = NULL; - - // find overall influence - for (i = 0; i < 4; i++) - { - if (weight[i]) - { - if (iRule >= panim[i]->numikrules || panim[i]->numikrules != panim[0]->numikrules) - { - Assert( 0 ); - return false; - } - - mstudioikrule_t *pRule = panim[i]->pIKRule( iRule ); - if (pRule == NULL) - return false; - - float dt = 0.0; - if (prevRule != NULL) - { - if (pRule->start - prevRule->start > 0.5) - { - dt = -1.0; - } - else if (pRule->start - prevRule->start < -0.5) - { - dt = 1.0; - } - } - else - { - prevRule = pRule; - } - - ikRule.start += (pRule->start + dt) * weight[i]; - ikRule.peak += (pRule->peak + dt) * weight[i]; - ikRule.tail += (pRule->tail + dt) * weight[i]; - ikRule.end += (pRule->end + dt) * weight[i]; - } - } - if (ikRule.start > 1.0) - { - ikRule.start -= 1.0; - ikRule.peak -= 1.0; - ikRule.tail -= 1.0; - ikRule.end -= 1.0; - } - else if (ikRule.start < 0.0) - { - ikRule.start += 1.0; - ikRule.peak += 1.0; - ikRule.tail += 1.0; - ikRule.end += 1.0; - } - - ikRule.flWeight = Studio_IKRuleWeight( ikRule, flCycle ); - if (ikRule.flWeight <= 0.001f) - { - // go ahead and allow IK_GROUND rules a virtual looping section - if (panim[0]->pIKRule( iRule )->type == IK_GROUND && ikRule.end - ikRule.start > 0.75 ) - { - ikRule.flWeight = 0.001; - flCycle = ikRule.end - 0.001; - } - else - { - return false; - } - } - - Assert( ikRule.flWeight > 0.0f ); - - ikRule.pos.Init(); - ikRule.q.Init(); - - // FIXME: add "latched" value - ikRule.commit = Studio_IKShouldLatch( ikRule, flCycle ); - - // find target error - float total = 0.0f; - for (i = 0; i < 4; i++) - { - if (weight[i]) - { - Vector pos1; - Quaternion q1; - float w; - - mstudioikrule_t *pRule = panim[i]->pIKRule( iRule ); - if (pRule == NULL) - return false; - - ikRule.chain = pRule->chain; // FIXME: this is anim local - ikRule.bone = pRule->bone; // FIXME: this is anim local - ikRule.type = pRule->type; - ikRule.slot = pRule->slot; - - ikRule.height += pRule->height * weight[i]; - ikRule.floor += pRule->floor * weight[i]; - ikRule.radius += pRule->radius * weight[i]; - - // keep track of tail condition - ikRule.release += Studio_IKTail( ikRule, flCycle ) * weight[i]; - - // only check rules with error values - switch( ikRule.type ) - { - case IK_SELF: - case IK_WORLD: - case IK_GROUND: - case IK_ATTACHMENT: - { - int bResult = Studio_IKAnimationError( pStudioHdr, pRule, panim[i], flCycle, pos1, q1, w ); - - if (bResult) - { - ikRule.pos = ikRule.pos + pos1 * weight[i]; - QuaternionAccumulate( ikRule.q, weight[i], q1, ikRule.q ); - total += weight[i]; - } - } - break; - default: - total += weight[i]; - break; - } - - ikRule.latched = Studio_IKShouldLatch( ikRule, flCycle ) * ikRule.flWeight; - - if (ikRule.type == IK_ATTACHMENT) - { - ikRule.szLabel = pRule->pszAttachment(); - } - } - } - - if (total <= 0.0001f) - { - return false; - } - - if (total < 0.999f) - { - VectorScale( ikRule.pos, 1.0f / total, ikRule.pos ); - QuaternionScale( ikRule.q, 1.0f / total, ikRule.q ); - } - - if (ikRule.type == IK_SELF && ikRule.bone != -1) - { - // FIXME: this is anim local, not seq local! - ikRule.bone = pStudioHdr->RemapSeqBone( iSequence, ikRule.bone ); - if (ikRule.bone == -1) - return false; - } - - QuaternionNormalize( ikRule.q ); - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - - -CIKContext::CIKContext() -{ - m_target.EnsureCapacity( 12 ); // FIXME: this sucks, shouldn't it be grown? - m_iFramecounter = -1; - m_pStudioHdr = NULL; - m_flTime = -1.0f; - m_target.SetSize( 0 ); -} - - -void CIKContext::Init( const CStudioHdr *pStudioHdr, const QAngle &angles, const Vector &pos, float flTime, int iFramecounter, int boneMask ) -{ - m_pStudioHdr = pStudioHdr; - m_ikChainRule.RemoveAll(); // m_numikrules = 0; - if (pStudioHdr->numikchains()) - { - m_ikChainRule.SetSize( pStudioHdr->numikchains() ); - - // FIXME: Brutal hackery to prevent a crash - if (m_target.Count() == 0) - { - m_target.SetSize(12); - memset( m_target.Base(), 0, sizeof(m_target[0])*m_target.Count() ); - ClearTargets(); - } - - } - else - { - m_target.SetSize( 0 ); - } - AngleMatrix( angles, pos, m_rootxform ); - m_iFramecounter = iFramecounter; - m_flTime = flTime; - m_boneMask = boneMask; -} - -void CIKContext::AddDependencies( mstudioseqdesc_t &seqdesc, int iSequence, float flCycle, const float poseParameters[], float flWeight ) -{ - int i; - - if (seqdesc.numikrules == 0) - return; - - ikcontextikrule_t ikrule; - - Assert( flWeight >= 0.0f && flWeight <= 1.0f ); - // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up - flWeight = clamp( flWeight, 0.0f, 1.0f ); - - // unify this - if (seqdesc.flags & STUDIO_REALTIME) - { - float cps = Studio_CPS( m_pStudioHdr, seqdesc, iSequence, poseParameters ); - flCycle = m_flTime * cps; - flCycle = flCycle - (int)flCycle; - } - else if (flCycle < 0 || flCycle >= 1) - { - if (seqdesc.flags & STUDIO_LOOPING) - { - flCycle = flCycle - (int)flCycle; - if (flCycle < 0) flCycle += 1; - } - else - { - flCycle = max( 0.0, min( flCycle, 0.9999 ) ); - } - } - - mstudioanimdesc_t *panim[4]; - float weight[4]; - - Studio_SeqAnims( m_pStudioHdr, seqdesc, iSequence, poseParameters, panim, weight ); - - // FIXME: add proper number of rules!!! - for (i = 0; i < seqdesc.numikrules; i++) - { - if ( !Studio_IKSequenceError( m_pStudioHdr, seqdesc, iSequence, flCycle, i, poseParameters, panim, weight, ikrule ) ) - continue; - - // don't add rule if the bone isn't going to be calculated - int bone = m_pStudioHdr->pIKChain( ikrule.chain )->pLink( 2 )->bone; - if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) - continue; - - // or if its relative bone isn't going to be calculated - if ( ikrule.bone >= 0 && !(m_pStudioHdr->pBone( ikrule.bone )->flags & m_boneMask)) - continue; - - // FIXME: Brutal hackery to prevent a crash - if (m_target.Count() == 0) - { - m_target.SetSize(12); - memset( m_target.Base(), 0, sizeof(m_target[0])*m_target.Count() ); - ClearTargets(); - } - - ikrule.flRuleWeight = flWeight; - - if (ikrule.flRuleWeight * ikrule.flWeight > 0.999) - { - if ( ikrule.type != IK_UNLATCH) - { - // clear out chain if rule is 100% - m_ikChainRule.Element( ikrule.chain ).RemoveAll( ); - if ( ikrule.type == IK_RELEASE) - { - continue; - } - } - } - - int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); - m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -void CIKContext::AddAutoplayLocks( Vector pos[], Quaternion q[] ) -{ - // skip all array access if no autoplay locks. - if (m_pStudioHdr->GetNumIKAutoplayLocks() == 0) - { - return; - } - - static matrix3x4_t boneToWorld[MAXSTUDIOBONES]; - CBoneBitList boneComputed; - - int ikOffset = m_ikLock.AddMultipleToTail( m_pStudioHdr->GetNumIKAutoplayLocks() ); - memset( &m_ikLock[ikOffset], 0, sizeof(ikcontextikrule_t)*m_pStudioHdr->GetNumIKAutoplayLocks() ); - - for (int i = 0; i < m_pStudioHdr->GetNumIKAutoplayLocks(); i++) - { - const mstudioiklock_t &lock = m_pStudioHdr->pIKAutoplayLock( i ); - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( lock.chain ); - int bone = pchain->pLink( 2 )->bone; - - // don't bother with iklock if the bone isn't going to be calculated - if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) - continue; - - // eval current ik'd bone - BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); - - ikcontextikrule_t &ikrule = m_ikLock[ i + ikOffset ]; - - ikrule.chain = lock.chain; - ikrule.slot = i; - ikrule.type = IK_WORLD; - - MatrixAngles( boneToWorld[bone], ikrule.q, ikrule.pos ); - - // save off current knee direction - if (pchain->pLink(0)->kneeDir.LengthSqr() > 0.0) - { - Vector tmp = pchain->pLink( 0 )->kneeDir; - VectorRotate( pchain->pLink( 0 )->kneeDir, boneToWorld[ pchain->pLink( 0 )->bone ], ikrule.kneeDir ); - MatrixPosition( boneToWorld[ pchain->pLink( 1 )->bone ], ikrule.kneePos ); - } - else - { - ikrule.kneeDir.Init( ); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -void CIKContext::AddSequenceLocks( mstudioseqdesc_t &seqdesc, Vector pos[], Quaternion q[] ) -{ - if ( seqdesc.numiklocks == 0 ) - { - return; - } - - static matrix3x4_t boneToWorld[MAXSTUDIOBONES]; - CBoneBitList boneComputed; - - int ikOffset = m_ikLock.AddMultipleToTail( seqdesc.numiklocks ); - memset( &m_ikLock[ikOffset], 0, sizeof(ikcontextikrule_t) * seqdesc.numiklocks ); - - for (int i = 0; i < seqdesc.numiklocks; i++) - { - mstudioiklock_t *plock = seqdesc.pIKLock( i ); - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( plock->chain ); - int bone = pchain->pLink( 2 )->bone; - - // don't bother with iklock if the bone isn't going to be calculated - if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) - continue; - - // eval current ik'd bone - BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); - - ikcontextikrule_t &ikrule = m_ikLock[i+ikOffset]; - ikrule.chain = i; - ikrule.slot = i; - ikrule.type = IK_WORLD; - - MatrixAngles( boneToWorld[bone], ikrule.q, ikrule.pos ); - - // save off current knee direction - if (pchain->pLink(0)->kneeDir.LengthSqr() > 0.0) - { - VectorRotate( pchain->pLink( 0 )->kneeDir, boneToWorld[ pchain->pLink( 0 )->bone ], ikrule.kneeDir ); - } - else - { - ikrule.kneeDir.Init( ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: build boneToWorld transforms for a specific bone -//----------------------------------------------------------------------------- -void CIKContext::BuildBoneChain( - const Vector pos[], - const Quaternion q[], - int iBone, - matrix3x4_t *pBoneToWorld, - CBoneBitList &boneComputed ) -{ - Assert( m_pStudioHdr->pBone( iBone )->flags & m_boneMask ); - ::BuildBoneChain( m_pStudioHdr, m_rootxform, pos, q, iBone, pBoneToWorld, boneComputed ); -} - - - -//----------------------------------------------------------------------------- -// Purpose: build boneToWorld transforms for a specific bone -//----------------------------------------------------------------------------- -void BuildBoneChain( - const CStudioHdr *pStudioHdr, - const matrix3x4_t &rootxform, - const Vector pos[], - const Quaternion q[], - int iBone, - matrix3x4_t *pBoneToWorld, - CBoneBitList &boneComputed ) -{ - if ( boneComputed.IsBoneMarked(iBone) ) - return; - - matrix3x4_t bonematrix; - QuaternionMatrix( q[iBone], pos[iBone], bonematrix ); - - int parent = pStudioHdr->pBone( iBone )->parent; - if (parent == -1) - { - ConcatTransforms( rootxform, bonematrix, pBoneToWorld[iBone] ); - } - else - { - // evil recursive!!! - BuildBoneChain( pStudioHdr, rootxform, pos, q, parent, pBoneToWorld, boneComputed ); - ConcatTransforms( pBoneToWorld[parent], bonematrix, pBoneToWorld[iBone]); - } - boneComputed.MarkBone(iBone); -} - - -//----------------------------------------------------------------------------- -// Purpose: turn a specific bones boneToWorld transform into a pos and q in parents bonespace -//----------------------------------------------------------------------------- -void SolveBone( - const CStudioHdr *pStudioHdr, - int iBone, - matrix3x4_t *pBoneToWorld, - Vector pos[], - Quaternion q[] - ) -{ - int iParent = pStudioHdr->pBone( iBone )->parent; - - matrix3x4_t worldToBone; - MatrixInvert( pBoneToWorld[iParent], worldToBone ); - - matrix3x4_t local; - ConcatTransforms( worldToBone, pBoneToWorld[iBone], local ); - - MatrixAngles( local, q[iBone], pos[iBone] ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -void CIKTarget::SetOwner( int entindex, const Vector &pos, const QAngle &angles ) -{ - latched.owner = entindex; - latched.absOrigin = pos; - latched.absAngles = angles; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -void CIKTarget::ClearOwner( void ) -{ - latched.owner = -1; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -int CIKTarget::GetOwner( void ) -{ - return latched.owner; -} - -//----------------------------------------------------------------------------- -// Purpose: update the latched IK values that are in a moving frame of reference -//----------------------------------------------------------------------------- - -void CIKTarget::UpdateOwner( int entindex, const Vector &pos, const QAngle &angles ) -{ - if (pos == latched.absOrigin && angles == latched.absAngles) - return; - - matrix3x4_t in, out; - AngleMatrix( angles, pos, in ); - AngleIMatrix( latched.absAngles, latched.absOrigin, out ); - - matrix3x4_t tmp1, tmp2; - QuaternionMatrix( latched.q, latched.pos, tmp1 ); - ConcatTransforms( out, tmp1, tmp2 ); - ConcatTransforms( in, tmp2, tmp1 ); - MatrixAngles( tmp1, latched.q, latched.pos ); -} - - -//----------------------------------------------------------------------------- -// Purpose: sets the ground position of an ik target -//----------------------------------------------------------------------------- - -void CIKTarget::SetPos( const Vector &pos ) -{ - est.pos = pos; -} - -//----------------------------------------------------------------------------- -// Purpose: sets the ground "identity" orientation of an ik target -//----------------------------------------------------------------------------- - -void CIKTarget::SetAngles( const QAngle &angles ) -{ - AngleQuaternion( angles, est.q ); -} - -//----------------------------------------------------------------------------- -// Purpose: sets the ground "identity" orientation of an ik target -//----------------------------------------------------------------------------- - -void CIKTarget::SetQuaternion( const Quaternion &q ) -{ - est.q = q; -} - -//----------------------------------------------------------------------------- -// Purpose: calculates a ground "identity" orientation based on the surface -// normal of the ground and the desired ground identity orientation -//----------------------------------------------------------------------------- - -void CIKTarget::SetNormal( const Vector &normal ) -{ - // recalculate foot angle based on slope of surface - matrix3x4_t m1; - Vector forward, right; - QuaternionMatrix( est.q, m1 ); - - MatrixGetColumn( m1, 1, right ); - forward = CrossProduct( right, normal ); - right = CrossProduct( normal, forward ); - MatrixSetColumn( forward, 0, m1 ); - MatrixSetColumn( right, 1, m1 ); - MatrixSetColumn( normal, 2, m1 ); - QAngle a1; - Vector p1; - MatrixAngles( m1, est.q, p1 ); -} - - -//----------------------------------------------------------------------------- -// Purpose: estimates the ground impact at the center location assuming a the edge of -// an Z axis aligned disc collided with it the surface. -//----------------------------------------------------------------------------- - -void CIKTarget::SetPosWithNormalOffset( const Vector &pos, const Vector &normal ) -{ - // assume it's a disc edge intersecting with the floor, so try to estimate the z location of the center - est.pos = pos; - if (normal.z > 0.9999) - { - return; - } - // clamp at 45 degrees - else if (normal.z > 0.707) - { - // tan == sin / cos - float tan = sqrt( 1 - normal.z * normal.z ) / normal.z; - est.pos.z = est.pos.z - est.radius * tan; - } - else - { - est.pos.z = est.pos.z - est.radius; - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -bool CIKTarget::IsActive() -{ - return (est.flWeight > 0.0f); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -void CIKTarget::IKFailed( void ) -{ - latched.deltaPos.Init(); - latched.deltaQ.Init(); - latched.pos = ideal.pos; - latched.q = ideal.q; - est.latched = 0.0; - est.flWeight = 0.0; -} - - -//----------------------------------------------------------------------------- -// Purpose: Invalidate any IK locks. -//----------------------------------------------------------------------------- - -void CIKContext::ClearTargets( void ) -{ - int i; - for (i = 0; i < m_target.Count(); i++) - { - m_target[i].latched.iFramecounter = -9999; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Run through the rules that survived and turn a specific bones boneToWorld -// transform into a pos and q in parents bonespace -//----------------------------------------------------------------------------- - -void CIKContext::UpdateTargets( Vector pos[], Quaternion q[], matrix3x4_t boneToWorld[], CBoneBitList &boneComputed ) -{ - int i, j; - - for (i = 0; i < m_target.Count(); i++) - { - m_target[i].est.flWeight = 0.0f; - m_target[i].est.latched = 1.0f; - m_target[i].est.release = 1.0f; - m_target[i].est.height = 0.0f; - m_target[i].est.floor = 0.0f; - m_target[i].est.radius = 0.0f; - m_target[i].offset.pos.Init(); - m_target[i].offset.q.Init(); - } - - AutoIKRelease( ); - - for (j = 0; j < m_ikChainRule.Count(); j++) - { - for (i = 0; i < m_ikChainRule.Element( j ).Count(); i++) - { - ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i ); - - // ikchainresult_t *pChainRule = &chainRule[ m_ikRule[i].chain ]; - - switch( pRule->type ) - { - case IK_ATTACHMENT: - case IK_GROUND: - // case IK_SELF: - { - matrix3x4_t footTarget; - CIKTarget *pTarget = &m_target[pRule->slot]; - pTarget->chain = pRule->chain; - pTarget->type = pRule->type; - - if (pRule->type == IK_ATTACHMENT) - { - pTarget->offset.pAttachmentName = pRule->szLabel; - } - else - { - pTarget->offset.pAttachmentName = NULL; - } - - if (pRule->flRuleWeight == 1.0f || pTarget->est.flWeight == 0.0f) - { - pTarget->offset.q = pRule->q; - pTarget->offset.pos = pRule->pos; - pTarget->est.height = pRule->height; - pTarget->est.floor = pRule->floor; - pTarget->est.radius = pRule->radius; - pTarget->est.latched = pRule->latched * pRule->flRuleWeight; - pTarget->est.release = pRule->release; - pTarget->est.flWeight = pRule->flWeight * pRule->flRuleWeight; - } - else - { - QuaternionSlerp( pTarget->offset.q, pRule->q, pRule->flRuleWeight, pTarget->offset.q ); - pTarget->offset.pos = Lerp( pRule->flRuleWeight, pTarget->offset.pos, pRule->pos ); - pTarget->est.height = Lerp( pRule->flRuleWeight, pTarget->est.height, pRule->height ); - pTarget->est.floor = Lerp( pRule->flRuleWeight, pTarget->est.floor, pRule->floor ); - pTarget->est.radius = Lerp( pRule->flRuleWeight, pTarget->est.radius, pRule->radius ); - //pTarget->est.latched = Lerp( pRule->flRuleWeight, pTarget->est.latched, pRule->latched ); - pTarget->est.latched = min( pTarget->est.latched, pRule->latched ); - pTarget->est.release = Lerp( pRule->flRuleWeight, pTarget->est.release, pRule->release ); - pTarget->est.flWeight = Lerp( pRule->flRuleWeight, pTarget->est.flWeight, pRule->flWeight ); - } - - if ( pRule->type == IK_GROUND ) - { - pTarget->latched.deltaPos.z = 0; - pTarget->est.pos.z = pTarget->est.floor + m_rootxform[2][3]; - } - } - break; - case IK_UNLATCH: - { - CIKTarget *pTarget = &m_target[pRule->slot]; - if (pRule->latched > 0.0) - pTarget->est.latched = 0.0; - else - pTarget->est.latched = min( pTarget->est.latched, 1.0f - pRule->flWeight ); - } - break; - case IK_RELEASE: - { - CIKTarget *pTarget = &m_target[pRule->slot]; - if (pRule->latched > 0.0) - pTarget->est.latched = 0.0; - else - pTarget->est.latched = min( pTarget->est.latched, 1.0f - pRule->flWeight ); - - pTarget->est.flWeight = (pTarget->est.flWeight) * (1 - pRule->flWeight * pRule->flRuleWeight); - } - break; - } - } - } - - for (i = 0; i < m_target.Count(); i++) - { - CIKTarget *pTarget = &m_target[i]; - if (pTarget->est.flWeight > 0.0) - { - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pTarget->chain ); - // ikchainresult_t *pChainRule = &chainRule[ i ]; - int bone = pchain->pLink( 2 )->bone; - - // eval current ik'd bone - BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); - - // xform IK target error into world space - matrix3x4_t local; - matrix3x4_t worldFootpad; - QuaternionMatrix( pTarget->offset.q, pTarget->offset.pos, local ); - MatrixInvert( local, local ); - ConcatTransforms( boneToWorld[bone], local, worldFootpad ); - - if (pTarget->est.latched == 1.0) - { - pTarget->latched.bNeedsLatch = true; - } - else - { - pTarget->latched.bNeedsLatch = false; - } - - // disable latched position if it looks invalid - if (m_iFramecounter < 0 || pTarget->latched.iFramecounter < m_iFramecounter - 1 || pTarget->latched.iFramecounter > m_iFramecounter) - { - pTarget->latched.bHasLatch = false; - pTarget->latched.influence = 0.0; - } - pTarget->latched.iFramecounter = m_iFramecounter; - - // find ideal contact position - MatrixAngles( worldFootpad, pTarget->ideal.q, pTarget->ideal.pos ); - pTarget->est.q = pTarget->ideal.q; - pTarget->est.pos = pTarget->ideal.pos; - - float latched = pTarget->est.latched; - - if (pTarget->latched.bHasLatch) - { - if (pTarget->est.latched == 1.0) - { - // keep track of latch position error from ideal contact position - pTarget->latched.deltaPos = pTarget->latched.pos - pTarget->est.pos; - QuaternionSM( -1, pTarget->est.q, pTarget->latched.q, pTarget->latched.deltaQ ); - pTarget->est.q = pTarget->latched.q; - pTarget->est.pos = pTarget->latched.pos; - } - else if (pTarget->est.latched > 0.0) - { - // ramp out latch differences during decay phase of rule - if (latched > 0 && latched < pTarget->latched.influence) - { - // latching has decreased - float dt = pTarget->latched.influence - latched; - if (pTarget->latched.influence > 0.0) - dt = dt / pTarget->latched.influence; - - VectorScale( pTarget->latched.deltaPos, (1-dt), pTarget->latched.deltaPos ); - QuaternionScale( pTarget->latched.deltaQ, (1-dt), pTarget->latched.deltaQ ); - } - - // move ideal contact position by latched error factor - pTarget->est.pos = pTarget->est.pos + pTarget->latched.deltaPos; - QuaternionMA( pTarget->est.q, 1, pTarget->latched.deltaQ, pTarget->est.q ); - pTarget->latched.q = pTarget->est.q; - pTarget->latched.pos = pTarget->est.pos; - } - else - { - pTarget->latched.bHasLatch = false; - pTarget->latched.q = pTarget->est.q; - pTarget->latched.pos = pTarget->est.pos; - pTarget->latched.deltaPos.Init(); - pTarget->latched.deltaQ.Init(); - } - pTarget->latched.influence = latched; - } - - // check for illegal requests - Vector p1, p2, p3; - MatrixPosition( boneToWorld[pchain->pLink( 0 )->bone], p1 ); // hip - MatrixPosition( boneToWorld[pchain->pLink( 1 )->bone], p2 ); // knee - MatrixPosition( boneToWorld[pchain->pLink( 2 )->bone], p3 ); // foot - - float d1 = (p2 - p1).Length(); - float d2 = (p3 - p2).Length(); - - if (pTarget->latched.bHasLatch) - { - //float d3 = (p3 - p1).Length(); - float d4 = (p3 + pTarget->latched.deltaPos - p1).Length(); - - // unstick feet when distance is too great - if ((d4 < fabs( d1 - d2 ) || d4 * 0.95 > d1 + d2) && pTarget->est.latched > 0.2) - { - pTarget->error.flTime = m_flTime; - } - - // unstick feet when angle is too great - if (pTarget->est.latched > 0.2) - { - float d = fabs( pTarget->latched.deltaQ.w ) * 2.0f - 1.0f; // QuaternionDotProduct( pTarget->latched.q, pTarget->est.q ); - - // FIXME: cos(45), make property of chain - if (d < 0.707) - { - pTarget->error.flTime = m_flTime; - } - } - } - - Vector dt = pTarget->est.pos - p1; - VectorNormalize( dt ); - - pTarget->trace.p1 = p1 + dt * (fabs( d1 - d2 ) * 1.01); - pTarget->trace.p2 = p1 + dt * (d1 + d2) * 0.99; - pTarget->trace.p3 = p1 + Vector( 0, 0, -1 ) * (d1 + d2); - // pTarget->trace.endpos = pTarget->est.pos; - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: insert release rules if the ik rules were in error -//----------------------------------------------------------------------------- - -void CIKContext::AutoIKRelease( void ) -{ - int i; - - for (i = 0; i < m_target.Count(); i++) - { - CIKTarget *pTarget = &m_target[i]; - - float dt = m_flTime - pTarget->error.flTime; - if (pTarget->error.bInError || dt < 0.5) - { - if (!pTarget->error.bInError) - { - pTarget->error.ramp = 0.0; - pTarget->error.flErrorTime = pTarget->error.flTime; - pTarget->error.bInError = true; - } - - float ft = m_flTime - pTarget->error.flErrorTime; - if (dt < 0.25) - { - pTarget->error.ramp = min( pTarget->error.ramp + ft * 4.0, 1.0 ); - } - else - { - pTarget->error.ramp = max( pTarget->error.ramp - ft * 4.0, 0.0 ); - } - if (pTarget->error.ramp > 0.0) - { - ikcontextikrule_t ikrule; - - ikrule.chain = pTarget->chain; - ikrule.bone = 0; - ikrule.type = IK_RELEASE; - ikrule.slot = i; - ikrule.flWeight = SimpleSpline( pTarget->error.ramp ); - ikrule.flRuleWeight = 1.0; - ikrule.latched = dt < 0.25 ? 0.0 : ikrule.flWeight; - - // don't bother with AutoIKRelease if the bone isn't going to be calculated - // this code is crashing for some unknown reason. - if ( pTarget->chain >= 0 && pTarget->chain < m_pStudioHdr->numikchains()) - { - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pTarget->chain ); - if (pchain != NULL) - { - int bone = pchain->pLink( 2 )->bone; - if (bone >= 0 && bone < m_pStudioHdr->numbones()) - { - mstudiobone_t *pBone = m_pStudioHdr->pBone( bone ); - if (pBone != NULL) - { - if ( !(pBone->flags & m_boneMask)) - { - pTarget->error.bInError = false; - continue; - } - /* - char buf[256]; - sprintf( buf, "dt %.4f ft %.4f weight %.4f latched %.4f\n", dt, ft, ikrule.flWeight, ikrule.latched ); - OutputDebugString( buf ); - */ - - int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); - m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; - } - else - { - DevWarning( 1, "AutoIKRelease (%s) got a NULL pBone %d\n", m_pStudioHdr->name(), bone ); - } - } - else - { - DevWarning( 1, "AutoIKRelease (%s) got an out of range bone %d (%d)\n", m_pStudioHdr->name(), bone, m_pStudioHdr->numbones() ); - } - } - else - { - DevWarning( 1, "AutoIKRelease (%s) got a NULL pchain %d\n", m_pStudioHdr->name(), pTarget->chain ); - } - } - else - { - DevWarning( 1, "AutoIKRelease (%s) got an out of range chain %d (%d)\n", m_pStudioHdr->name(), pTarget->chain, m_pStudioHdr->numikchains()); - } - } - else - { - pTarget->error.bInError = false; - } - pTarget->error.flErrorTime = m_flTime; - } - } -} - - - -void CIKContext::SolveDependencies( Vector pos[], Quaternion q[], matrix3x4_t boneToWorld[], CBoneBitList &boneComputed ) -{ - ASSERT_NO_REENTRY(); - - matrix3x4_t worldTarget; - int i, j; - - ikchainresult_t chainResult[32]; // allocate!!! - - // init chain rules - for (i = 0; i < m_pStudioHdr->numikchains(); i++) - { - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( i ); - ikchainresult_t *pChainResult = &chainResult[ i ]; - int bone = pchain->pLink( 2 )->bone; - - pChainResult->target = -1; - pChainResult->flWeight = 0.0; - - // don't bother with chain if the bone isn't going to be calculated - if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) - continue; - - // eval current ik'd bone - BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); - - MatrixAngles( boneToWorld[bone], pChainResult->q, pChainResult->pos ); - } - - for (j = 0; j < m_ikChainRule.Count(); j++) - { - for (i = 0; i < m_ikChainRule.Element( j ).Count(); i++) - { - ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i ); - ikchainresult_t *pChainResult = &chainResult[ pRule->chain ]; - pChainResult->target = -1; - - - switch( pRule->type ) - { - case IK_SELF: - { - // xform IK target error into world space - matrix3x4_t local; - QuaternionMatrix( pRule->q, pRule->pos, local ); - // eval target bone space - if (pRule->bone != -1) - { - BuildBoneChain( pos, q, pRule->bone, boneToWorld, boneComputed ); - ConcatTransforms( boneToWorld[pRule->bone], local, worldTarget ); - } - else - { - ConcatTransforms( m_rootxform, local, worldTarget ); - } - - float flWeight = pRule->flWeight * pRule->flRuleWeight; - pChainResult->flWeight = pChainResult->flWeight * (1 - flWeight) + flWeight; - - Vector p2; - Quaternion q2; - - // target p and q - MatrixAngles( worldTarget, q2, p2 ); - - // debugLine( pChainResult->pos, p2, 0, 0, 255, true, 0.1 ); - - // blend in position and angles - pChainResult->pos = pChainResult->pos * (1.0 - flWeight) + p2 * flWeight; - QuaternionSlerp( pChainResult->q, q2, flWeight, pChainResult->q ); - } - break; - case IK_WORLD: - Assert( 0 ); - break; - - case IK_ATTACHMENT: - break; - - case IK_GROUND: - break; - - case IK_RELEASE: - { - // move target back towards original location - float flWeight = pRule->flWeight * pRule->flRuleWeight; - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pRule->chain ); - int bone = pchain->pLink( 2 )->bone; - - Vector p2; - Quaternion q2; - - BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); - MatrixAngles( boneToWorld[bone], q2, p2 ); - - // blend in position and angles - pChainResult->pos = pChainResult->pos * (1.0 - flWeight) + p2 * flWeight; - QuaternionSlerp( pChainResult->q, q2, flWeight, pChainResult->q ); - } - break; - case IK_UNLATCH: - { - /* - pChainResult->flWeight = pChainResult->flWeight * (1 - pRule->flWeight) + pRule->flWeight; - - pChainResult->pos = pChainResult->pos * (1.0 - pRule->flWeight ) + pChainResult->local.pos * pRule->flWeight; - QuaternionSlerp( pChainResult->q, pChainResult->local.q, pRule->flWeight, pChainResult->q ); - */ - } - break; - } - } - } - - for (i = 0; i < m_target.Count(); i++) - { - CIKTarget *pTarget = &m_target[i]; - - if (m_target[i].est.flWeight > 0.0) - { - matrix3x4_t worldFootpad; - matrix3x4_t local; - //mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( m_target[i].chain ); - ikchainresult_t *pChainResult = &chainResult[ pTarget->chain ]; - - AngleMatrix(pTarget->offset.q, pTarget->offset.pos, local ); - - AngleMatrix( pTarget->est.q, pTarget->est.pos, worldFootpad ); - - ConcatTransforms( worldFootpad, local, worldTarget ); - - Vector p2; - Quaternion q2; - // target p and q - MatrixAngles( worldTarget, q2, p2 ); - // MatrixAngles( worldTarget, pChainResult->q, pChainResult->pos ); - - // blend in position and angles - pChainResult->flWeight = pTarget->est.flWeight; - pChainResult->pos = pChainResult->pos * (1.0 - pChainResult->flWeight ) + p2 * pChainResult->flWeight; - QuaternionSlerp( pChainResult->q, q2, pChainResult->flWeight, pChainResult->q ); - } - - if (pTarget->latched.bNeedsLatch) - { - // keep track of latch position - pTarget->latched.bHasLatch = true; - pTarget->latched.q = pTarget->est.q; - pTarget->latched.pos = pTarget->est.pos; - } - } - - for (i = 0; i < m_pStudioHdr->numikchains(); i++) - { - ikchainresult_t *pChainResult = &chainResult[ i ]; - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( i ); - - if (pChainResult->flWeight > 0.0) - { - Vector tmp; - MatrixPosition( boneToWorld[pchain->pLink( 2 )->bone], tmp ); - // debugLine( pChainResult->pos, tmp, 255, 255, 255, true, 0.1 ); - - // do exact IK solution - // FIXME: once per link! - if (Studio_SolveIK(pchain, pChainResult->pos, boneToWorld )) - { - Vector p3; - MatrixGetColumn( boneToWorld[pchain->pLink( 2 )->bone], 3, p3 ); - QuaternionMatrix( pChainResult->q, p3, boneToWorld[pchain->pLink( 2 )->bone] ); - - // rebuild chain - // FIXME: is this needed if everyone past this uses the boneToWorld array? - SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); - SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); - SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); - } - else - { - // FIXME: need to invalidate the targets that forced this... - if (pChainResult->target != -1) - { - CIKTarget *pTarget = &m_target[pChainResult->target]; - VectorScale( pTarget->latched.deltaPos, 0.8, pTarget->latched.deltaPos ); - QuaternionScale( pTarget->latched.deltaQ, 0.8, pTarget->latched.deltaQ ); - } - } - } - } - -#if 0 - Vector p1, p2, p3; - Quaternion q1, q2, q3; - - // current p and q - MatrixAngles( boneToWorld[bone], q1, p1 ); - - - // target p and q - MatrixAngles( worldTarget, q2, p2 ); - - // blend in position and angles - p3 = p1 * (1.0 - m_ikRule[i].flWeight ) + p2 * m_ikRule[i].flWeight; - - // do exact IK solution - // FIXME: once per link! - Studio_SolveIK(pchain, p3, boneToWorld ); - - // force angle (bad?) - QuaternionSlerp( q1, q2, m_ikRule[i].flWeight, q3 ); - MatrixGetColumn( boneToWorld[bone], 3, p3 ); - QuaternionMatrix( q3, p3, boneToWorld[bone] ); - - // rebuild chain - SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); - SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); - SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); -#endif -} - - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -void CIKContext::SolveAutoplayLocks( - Vector pos[], - Quaternion q[] - ) -{ - ASSERT_NO_REENTRY(); - - static matrix3x4_t boneToWorld[MAXSTUDIOBONES]; - CBoneBitList boneComputed; - int i; - - for (i = 0; i < m_ikLock.Count(); i++) - { - const mstudioiklock_t &lock = m_pStudioHdr->pIKAutoplayLock( i ); - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( lock.chain ); - int bone = pchain->pLink( 2 )->bone; - - // don't bother with iklock if the bone isn't going to be calculated - if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) - continue; - - // eval current ik'd bone - BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); - - Vector p1, p2, p3; - Quaternion q2, q3; - - // current p and q - MatrixPosition( boneToWorld[bone], p1 ); - - // blend in position - p3 = p1 * (1.0 - lock.flPosWeight ) + m_ikLock[i].pos * lock.flPosWeight; - - // do exact IK solution - if (m_ikLock[i].kneeDir.LengthSqr() > 0) - { - Studio_SolveIK(pchain->pLink( 0 )->bone, pchain->pLink( 1 )->bone, pchain->pLink( 2 )->bone, p3, m_ikLock[i].kneePos, m_ikLock[i].kneeDir, boneToWorld ); - } - else - { - Studio_SolveIK(pchain, p3, boneToWorld ); - } - - // slam orientation - MatrixPosition( boneToWorld[bone], p3 ); - QuaternionMatrix( m_ikLock[i].q, p3, boneToWorld[bone] ); - - // rebuild chain - q2 = q[ bone ]; - SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); - QuaternionSlerp( q[bone], q2, lock.flLocalQWeight, q[bone] ); - - SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); - SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -void CIKContext::SolveSequenceLocks( - mstudioseqdesc_t &seqdesc, - Vector pos[], - Quaternion q[] - ) -{ - ASSERT_NO_REENTRY(); - - static matrix3x4_t boneToWorld[MAXSTUDIOBONES]; - CBoneBitList boneComputed; - int i; - - for (i = 0; i < m_ikLock.Count(); i++) - { - mstudioiklock_t *plock = seqdesc.pIKLock( i ); - mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( plock->chain ); - int bone = pchain->pLink( 2 )->bone; - - // don't bother with iklock if the bone isn't going to be calculated - if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) - continue; - - // eval current ik'd bone - BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); - - Vector p1, p2, p3; - Quaternion q2, q3; - - // current p and q - MatrixPosition( boneToWorld[bone], p1 ); - - // blend in position - p3 = p1 * (1.0 - plock->flPosWeight ) + m_ikLock[i].pos * plock->flPosWeight; - - // do exact IK solution - if (m_ikLock[i].kneeDir.LengthSqr() > 0) - { - Studio_SolveIK(pchain->pLink( 0 )->bone, pchain->pLink( 1 )->bone, pchain->pLink( 2 )->bone, p3, m_ikLock[i].kneePos, m_ikLock[i].kneeDir, boneToWorld ); - } - else - { - Studio_SolveIK(pchain, p3, boneToWorld ); - } - - // slam orientation - MatrixPosition( boneToWorld[bone], p3 ); - QuaternionMatrix( m_ikLock[i].q, p3, boneToWorld[bone] ); - - // rebuild chain - q2 = q[ bone ]; - SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); - QuaternionSlerp( q[bone], q2, plock->flLocalQWeight, q[bone] ); - - SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); - SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: run all animations that automatically play and are driven off of poseParameters -//----------------------------------------------------------------------------- -void CalcAutoplaySequences( - const CStudioHdr *pStudioHdr, - CIKContext *pIKContext, - Vector pos[], - Quaternion q[], - const float poseParameters[], - int boneMask, - float realTime - ) -{ - ASSERT_NO_REENTRY(); - - int i; - if ( pIKContext ) - { - pIKContext->AddAutoplayLocks( pos, q ); - } - - unsigned short *pList = NULL; - int count = pStudioHdr->GetAutoplayList( &pList ); - for (i = 0; i < count; i++) - { - int sequenceIndex = pList[i]; - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequenceIndex ); - if (seqdesc.flags & STUDIO_AUTOPLAY) - { - float cycle = 0; - float cps = Studio_CPS( pStudioHdr, seqdesc, sequenceIndex, poseParameters ); - cycle = realTime * cps; - cycle = cycle - (int)cycle; - - AccumulatePose( pStudioHdr, NULL, pos, q, sequenceIndex, cycle, poseParameters, boneMask, 1.0, realTime ); - } - } - - if ( pIKContext ) - { - pIKContext->SolveAutoplayLocks( pos, q ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Studio_BuildMatrices( - const CStudioHdr *pStudioHdr, - const QAngle& angles, - const Vector& origin, - const Vector pos[], - const Quaternion q[], - int iBone, - matrix3x4_t bonetoworld[MAXSTUDIOBONES], - int boneMask - ) -{ - int i, j; - - int chain[MAXSTUDIOBONES]; - int chainlength = 0; - - if (iBone < -1 || iBone >= pStudioHdr->numbones()) - iBone = 0; - - mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); - - // build list of what bones to use - if (iBone == -1) - { - // all bones - chainlength = pStudioHdr->numbones(); - for (i = 0; i < pStudioHdr->numbones(); i++) - { - chain[chainlength - i - 1] = i; - } - } - else - { - // only the parent bones - i = iBone; - while (i != -1) - { - chain[chainlength++] = i; - i = pbones[i].parent; - } - } - - matrix3x4_t bonematrix; - matrix3x4_t rotationmatrix; // model to world transformation - AngleMatrix( angles, origin, rotationmatrix); - - for (j = chainlength - 1; j >= 0; j--) - { - i = chain[j]; - if (pbones[i].flags & boneMask) - { - QuaternionMatrix( q[i], pos[i], bonematrix ); - - if (pbones[i].parent == -1) - { - ConcatTransforms (rotationmatrix, bonematrix, bonetoworld[i]); - } - else - { - ConcatTransforms (bonetoworld[pbones[i].parent], bonematrix, bonetoworld[i]); - } - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: look at single column vector of another bones local transformation -// and generate a procedural transformation based on how that column -// points down the 6 cardinal axis (all negative weights are clamped to 0). -//----------------------------------------------------------------------------- - -void DoAxisInterpBone( - mstudiobone_t *pbones, - int ibone, - CBoneAccessor &bonetoworld - ) -{ - matrix3x4_t bonematrix; - Vector control; - - mstudioaxisinterpbone_t *pProc = (mstudioaxisinterpbone_t *)pbones[ibone].pProcedure( ); - const matrix3x4_t &controlBone = bonetoworld.GetBone( pProc->control ); - if (pProc && pbones[pProc->control].parent != -1) - { - Vector tmp; - // pull out the control column - tmp.x = controlBone[0][pProc->axis]; - tmp.y = controlBone[1][pProc->axis]; - tmp.z = controlBone[2][pProc->axis]; - - // invert it back into parent's space. - VectorIRotate( tmp, bonetoworld.GetBone( pbones[pProc->control].parent ), control ); -#if 0 - matrix3x4_t tmpmatrix; - matrix3x4_t controlmatrix; - MatrixInvert( bonetoworld.GetBone( pbones[pProc->control].parent ), tmpmatrix ); - ConcatTransforms( tmpmatrix, bonetoworld.GetBone( pProc->control ), controlmatrix ); - - // pull out the control column - control.x = controlmatrix[0][pProc->axis]; - control.y = controlmatrix[1][pProc->axis]; - control.z = controlmatrix[2][pProc->axis]; -#endif - } - else - { - // pull out the control column - control.x = controlBone[0][pProc->axis]; - control.y = controlBone[1][pProc->axis]; - control.z = controlBone[2][pProc->axis]; - } - - Quaternion *q1, *q2, *q3; - Vector *p1, *p2, *p3; - - // find axial control inputs - float a1 = control.x; - float a2 = control.y; - float a3 = control.z; - if (a1 >= 0) - { - q1 = &pProc->quat[0]; - p1 = &pProc->pos[0]; - } - else - { - a1 = -a1; - q1 = &pProc->quat[1]; - p1 = &pProc->pos[1]; - } - - if (a2 >= 0) - { - q2 = &pProc->quat[2]; - p2 = &pProc->pos[2]; - } - else - { - a2 = -a2; - q2 = &pProc->quat[3]; - p2 = &pProc->pos[3]; - } - - if (a3 >= 0) - { - q3 = &pProc->quat[4]; - p3 = &pProc->pos[4]; - } - else - { - a3 = -a3; - q3 = &pProc->quat[5]; - p3 = &pProc->pos[5]; - } - - // do a three-way blend - Vector p; - Quaternion v, tmp; - if (a1 + a2 > 0) - { - float t = 1.0 / (a1 + a2 + a3); - // FIXME: do a proper 3-way Quat blend! - QuaternionSlerp( *q2, *q1, a1 / (a1 + a2), tmp ); - QuaternionSlerp( tmp, *q3, a3 * t, v ); - VectorScale( *p1, a1 * t, p ); - VectorMA( p, a2 * t, *p2, p ); - VectorMA( p, a3 * t, *p3, p ); - } - else - { - QuaternionSlerp( *q3, *q3, 0, v ); // ??? no quat copy? - p = *p3; - } - - QuaternionMatrix( v, p, bonematrix ); - - ConcatTransforms (bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone )); -} - - - -//----------------------------------------------------------------------------- -// Purpose: Generate a procedural transformation based on how that another bones -// local transformation matches a set of target orientations. -//----------------------------------------------------------------------------- -void DoQuatInterpBone( - mstudiobone_t *pbones, - int ibone, - CBoneAccessor &bonetoworld - ) -{ - matrix3x4_t bonematrix; - Vector control; - - mstudioquatinterpbone_t *pProc = (mstudioquatinterpbone_t *)pbones[ibone].pProcedure( ); - if (pProc && pbones[pProc->control].parent != -1) - { - Quaternion src; - float weight[32]; - float scale = 0.0; - Quaternion quat; - Vector pos; - - matrix3x4_t tmpmatrix; - matrix3x4_t controlmatrix; - MatrixInvert( bonetoworld.GetBone( pbones[pProc->control].parent), tmpmatrix ); - ConcatTransforms( tmpmatrix, bonetoworld.GetBone( pProc->control ), controlmatrix ); - - MatrixAngles( controlmatrix, src, pos ); // FIXME: make a version without pos - - int i; - for (i = 0; i < pProc->numtriggers; i++) - { - float dot = fabs( QuaternionDotProduct( pProc->pTrigger( i )->trigger, src ) ); - // FIXME: a fast acos should be acceptable - dot = clamp( dot, -1, 1 ); - weight[i] = 1 - (2 * acos( dot ) * pProc->pTrigger( i )->inv_tolerance ); - weight[i] = max( 0, weight[i] ); - scale += weight[i]; - } - - if (scale <= 0.001) // EPSILON? - { - AngleMatrix( pProc->pTrigger( 0 )->quat, pProc->pTrigger( 0 )->pos, bonematrix ); - ConcatTransforms ( bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone ) ); - return; - } - - scale = 1.0 / scale; - - quat.Init( 0, 0, 0, 0); - pos.Init( ); - - for (i = 0; i < pProc->numtriggers; i++) - { - if (weight[i]) - { - float s = weight[i] * scale; - mstudioquatinterpinfo_t *pTrigger = pProc->pTrigger( i ); - - QuaternionAlign( pTrigger->quat, quat, quat ); - - quat.x = quat.x + s * pTrigger->quat.x; - quat.y = quat.y + s * pTrigger->quat.y; - quat.z = quat.z + s * pTrigger->quat.z; - quat.w = quat.w + s * pTrigger->quat.w; - pos.x = pos.x + s * pTrigger->pos.x; - pos.y = pos.y + s * pTrigger->pos.y; - pos.z = pos.z + s * pTrigger->pos.z; - } - } - Assert( QuaternionNormalize( quat ) != 0); - QuaternionMatrix( quat, pos, bonematrix ); - } - - ConcatTransforms (bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone )); -} - -/* - * This is for DoAimAtBone below, was just for testing, not needed in general - * but to turn it back on, uncomment this and the section in DoAimAtBone() below - * - -static ConVar aim_constraint( "aim_constraint", "1", FCVAR_REPLICATED, "Toggle Helper Bones" ); - -*/ - -//----------------------------------------------------------------------------- -// Purpose: Generate a procedural transformation so that one bone points at -// another point on the model -//----------------------------------------------------------------------------- -void DoAimAtBone( - mstudiobone_t *pBones, - int iBone, - CBoneAccessor &bonetoworld, - const CStudioHdr *pStudioHdr - ) -{ - mstudioaimatbone_t *pProc = (mstudioaimatbone_t *)pBones[iBone].pProcedure(); - - if ( !pProc ) - { - return; - } - - /* - * Uncomment this if the ConVar above is uncommented - * - - if ( !aim_constraint.GetBool() ) - { - // If the aim constraint is turned off then just copy the parent transform - // plus the offset value - - matrix3x4_t boneToWorldSpace; - MatrixCopy ( bonetoworld.GetBone( pProc->parent ), boneToWorldSpace ); - Vector boneWorldPosition; - VectorTransform( pProc->basepos, boneToWorldSpace, boneWorldPosition ); - MatrixSetColumn( boneWorldPosition, 3, boneToWorldSpace ); - MatrixCopy( boneToWorldSpace, bonetoworld.GetBoneForWrite( iBone ) ); - - return; - } - - */ - - // The world matrix of the bone to change - matrix3x4_t boneMatrix; - - // Guaranteed to be unit length - const Vector &userAimVector( pProc->aimvector ); - - // Guaranteed to be unit length - const Vector &userUpVector( pProc->upvector ); - - // Get to get position of bone but also for up reference - matrix3x4_t parentSpace; - MatrixCopy ( bonetoworld.GetBone( pProc->parent ), parentSpace ); - - // World space position of the bone to aim - Vector aimWorldPosition; - VectorTransform( pProc->basepos, parentSpace, aimWorldPosition ); - - // The worldspace matrix of the bone to aim at - matrix3x4_t aimAtSpace; - if ( pStudioHdr ) - { - // This means it's AIMATATTACH - const mstudioattachment_t &attachment( pStudioHdr->pAttachment( pProc->aim ) ); - ConcatTransforms( - bonetoworld.GetBone( attachment.localbone ), - attachment.local, - aimAtSpace ); - } - else - { - MatrixCopy( bonetoworld.GetBone( pProc->aim ), aimAtSpace ); - } - - Vector aimAtWorldPosition; - MatrixGetColumn( aimAtSpace, 3, aimAtWorldPosition ); - - Vector aimVector; - VectorSubtract( aimAtWorldPosition, aimWorldPosition, aimVector ); - VectorNormalizeFast( aimVector ); - - Vector axis; - CrossProduct( userAimVector, aimVector, axis ); - VectorNormalizeFast( axis ); - float angle( acosf( DotProduct( userAimVector, aimVector ) ) ); - Quaternion aimRotation; - AxisAngleQuaternion( axis, RAD2DEG( angle ), aimRotation ); - - if ( ( 1.0f - fabs( DotProduct( userUpVector, userAimVector ) ) ) > FLT_EPSILON ) - { - matrix3x4_t aimRotationMatrix; - QuaternionMatrix( aimRotation, aimRotationMatrix ); - - Vector tmpV; - - Vector tmp_pUp; - VectorRotate( userUpVector, aimRotationMatrix, tmp_pUp ); - VectorScale( aimVector, DotProduct( aimVector, tmp_pUp ), tmpV ); - Vector pUp; - VectorSubtract( tmp_pUp, tmpV, pUp ); - VectorNormalizeFast( pUp ); - - Vector tmp_pParentUp; - VectorRotate( userUpVector, parentSpace, tmp_pParentUp ); - VectorScale( aimVector, DotProduct( aimVector, tmp_pParentUp ), tmpV ); - Vector pParentUp; - VectorSubtract( tmp_pParentUp, tmpV, pParentUp ); - VectorNormalizeFast( pParentUp ); - - angle = acos( DotProduct( pUp, pParentUp ) ); - CrossProduct( pUp, pParentUp, axis ); - VectorNormalizeFast( axis ); - Quaternion upRotation; - AxisAngleQuaternion( axis, RAD2DEG( angle ), upRotation ); - - Quaternion boneRotation; - QuaternionMult( upRotation, aimRotation, boneRotation ); - QuaternionMatrix( boneRotation, aimWorldPosition, boneMatrix ); - } - else - { - QuaternionMatrix( aimRotation, aimWorldPosition, boneMatrix ); - } - - MatrixCopy( boneMatrix, bonetoworld.GetBoneForWrite( iBone ) ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- - -bool CalcProceduralBone( - const CStudioHdr *pStudioHdr, - int iBone, - CBoneAccessor &bonetoworld - ) -{ - mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); - - if ( pbones[iBone].flags & BONE_ALWAYS_PROCEDURAL ) - { - switch( pbones[iBone].proctype ) - { - case STUDIO_PROC_AXISINTERP: - DoAxisInterpBone( pbones, iBone, bonetoworld ); - return true; - - case STUDIO_PROC_QUATINTERP: - DoQuatInterpBone( pbones, iBone, bonetoworld ); - return true; - - case STUDIO_PROC_AIMATBONE: - DoAimAtBone( pbones, iBone, bonetoworld, NULL ); - return true; - - case STUDIO_PROC_AIMATATTACH: - DoAimAtBone( pbones, iBone, bonetoworld, pStudioHdr ); - return true; - - default: - return false; - } - } - return false; -} - - - -//----------------------------------------------------------------------------- -// Purpose: Lookup a bone controller -//----------------------------------------------------------------------------- - - - -static mstudiobonecontroller_t* FindController( const CStudioHdr *pStudioHdr, int iController) -{ - // find first controller that matches the index - for (int i = 0; i < pStudioHdr->numbonecontrollers(); i++) - { - if (pStudioHdr->pBonecontroller( i )->inputfield == iController) - return pStudioHdr->pBonecontroller( i ); - } - - return NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: converts a ranged bone controller value into a 0..1 encoded value -// Output: ctlValue contains 0..1 encoding. -// returns clamped ranged value -//----------------------------------------------------------------------------- - -float Studio_SetController( const CStudioHdr *pStudioHdr, int iController, float flValue, float &ctlValue ) -{ - if (! pStudioHdr) - return flValue; - - mstudiobonecontroller_t *pbonecontroller = FindController(pStudioHdr, iController); - if(!pbonecontroller) - { - ctlValue = 0; - 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; - } - } - - ctlValue = (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); - if (ctlValue < 0) ctlValue = 0; - if (ctlValue > 1) ctlValue = 1; - - float flReturnVal = ((1.0 - ctlValue)*pbonecontroller->start + ctlValue *pbonecontroller->end); - - // ugly hack, invert value if a rotational controller and end < start - if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR) && - pbonecontroller->end < pbonecontroller->start ) - { - flReturnVal *= -1; - } - - return flReturnVal; -} - - -//----------------------------------------------------------------------------- -// Purpose: converts a 0..1 encoded bone controller value into a ranged value -// Output: returns ranged value -//----------------------------------------------------------------------------- - -float Studio_GetController( const CStudioHdr *pStudioHdr, int iController, float ctlValue ) -{ - if (!pStudioHdr) - return 0.0; - - mstudiobonecontroller_t *pbonecontroller = FindController(pStudioHdr, iController); - if(!pbonecontroller) - return 0; - - return ctlValue * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; -} - - -//----------------------------------------------------------------------------- -// Purpose: converts a ranged pose parameter value into a 0..1 encoded value -// Output: ctlValue contains 0..1 encoding. -// returns clamped ranged value -//----------------------------------------------------------------------------- - -float Studio_SetPoseParameter( const CStudioHdr *pStudioHdr, int iParameter, float flValue, float &ctlValue ) -{ - if (iParameter < 0 || iParameter >= pStudioHdr->GetNumPoseParameters()) - { - return 0; - } - - const mstudioposeparamdesc_t &PoseParam = pStudioHdr->pPoseParameter( iParameter ); - - Assert( IsFinite( flValue ) ); - - if (PoseParam.loop) - { - float wrap = (PoseParam.start + PoseParam.end) / 2.0 + PoseParam.loop / 2.0; - float shift = PoseParam.loop - wrap; - - flValue = flValue - PoseParam.loop * floor((flValue + shift) / PoseParam.loop); - } - - ctlValue = (flValue - PoseParam.start) / (PoseParam.end - PoseParam.start); - - if (ctlValue < 0) ctlValue = 0; - if (ctlValue > 1) ctlValue = 1; - - Assert( IsFinite( ctlValue ) ); - - return ctlValue * (PoseParam.end - PoseParam.start) + PoseParam.start; -} - - -//----------------------------------------------------------------------------- -// Purpose: converts a 0..1 encoded pose parameter value into a ranged value -// Output: returns ranged value -//----------------------------------------------------------------------------- - -float Studio_GetPoseParameter( const CStudioHdr *pStudioHdr, int iParameter, float ctlValue ) -{ - if (iParameter < 0 || iParameter >= pStudioHdr->GetNumPoseParameters()) - { - return 0; - } - - const mstudioposeparamdesc_t &PoseParam = pStudioHdr->pPoseParameter( iParameter ); - - return ctlValue * (PoseParam.end - PoseParam.start) + PoseParam.start; -} - - -#pragma warning (disable : 4701) - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -static int ClipRayToHitbox( const Ray_t &ray, mstudiobbox_t *pbox, matrix3x4_t& matrix, trace_t &tr ) -{ - // scale by current t so hits shorten the ray and increase the likelihood of early outs - Vector delta2; - VectorScale( ray.m_Delta, (0.5f * tr.fraction), delta2 ); - - // OPTIMIZE: Store this in the box instead of computing it here - // compute center in local space - Vector boxextents; - boxextents.x = (pbox->bbmin.x + pbox->bbmax.x) * 0.5; - boxextents.y = (pbox->bbmin.y + pbox->bbmax.y) * 0.5; - boxextents.z = (pbox->bbmin.z + pbox->bbmax.z) * 0.5; - Vector boxCenter; - // transform to world space - VectorTransform( boxextents, matrix, boxCenter ); - // calc extents from local center - boxextents.x = pbox->bbmax.x - boxextents.x; - boxextents.y = pbox->bbmax.y - boxextents.y; - boxextents.z = pbox->bbmax.z - boxextents.z; - // OPTIMIZE: This is optimized for world space. If the transform is fast enough, it may make more - // sense to just xform and call UTIL_ClipToBox() instead. MEASURE THIS. - - // save the extents of the ray along - Vector extent, uextent; - Vector segmentCenter; - segmentCenter.x = ray.m_Start.x + delta2.x - boxCenter.x; - segmentCenter.y = ray.m_Start.y + delta2.y - boxCenter.y; - segmentCenter.z = ray.m_Start.z + delta2.z - boxCenter.z; - - extent.Init(); - - // check box axes for separation - for ( int j = 0; j < 3; j++ ) - { - extent[j] = delta2.x * matrix[0][j] + delta2.y * matrix[1][j] + delta2.z * matrix[2][j]; - uextent[j] = fabsf(extent[j]); - float coord = segmentCenter.x * matrix[0][j] + segmentCenter.y * matrix[1][j] + segmentCenter.z * matrix[2][j]; - coord = fabsf(coord); - - if ( coord > (boxextents[j] + uextent[j]) ) - return -1; - } - - // now check cross axes for separation - float tmp, cextent; - Vector cross; - CrossProduct( delta2, segmentCenter, cross ); - cextent = cross.x * matrix[0][0] + cross.y * matrix[1][0] + cross.z * matrix[2][0]; - cextent = fabsf(cextent); - tmp = boxextents[1]*uextent[2] + boxextents[2]*uextent[1]; - if ( cextent > tmp ) - return -1; - - cextent = cross.x * matrix[0][1] + cross.y * matrix[1][1] + cross.z * matrix[2][1]; - cextent = fabsf(cextent); - tmp = boxextents[0]*uextent[2] + boxextents[2]*uextent[0]; - if ( cextent > tmp ) - return -1; - - cextent = cross.x * matrix[0][2] + cross.y * matrix[1][2] + cross.z * matrix[2][2]; - cextent = fabsf(cextent); - tmp = boxextents[0]*uextent[1] + boxextents[1]*uextent[0]; - if ( cextent > tmp ) - return -1; - - // !!! We hit this box !!! compute intersection point and return - Vector start; - // Compute ray start in bone space - VectorITransform( ray.m_Start, matrix, start ); - // extent is delta2 in bone space, recompute delta in bone space - VectorScale( extent, 2, extent ); - - // delta was prescaled by the current t, so no need to see if this intersection - // is closer - trace_t boxTrace; - if ( !IntersectRayWithBox( start, extent, pbox->bbmin, pbox->bbmax, 0.0f, &boxTrace ) ) - return -1; - Assert( IsFinite(boxTrace.fraction) ); - tr.fraction *= boxTrace.fraction; - tr.startsolid = boxTrace.startsolid; - int hitside = boxTrace.plane.type; - if ( boxTrace.plane.normal[hitside] >= 0 ) - { - hitside += 3; - } - return hitside; -} - -#pragma warning (default : 4701) - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool SweepBoxToStudio( const Ray_t& ray, CStudioHdr *pStudioHdr, mstudiohitboxset_t *set, - matrix3x4_t **hitboxbones, int fContentsMask, trace_t &tr ) -{ - tr.fraction = 1.0; - tr.startsolid = false; - - // OPTIMIZE: Partition these? - Ray_t clippedRay = ray; - int hitbox = -1; - for ( int i = 0; i < set->numhitboxes; i++ ) - { - mstudiobbox_t *pbox = set->pHitbox(i); - - // Filter based on contents mask - int fBoneContents = pStudioHdr->pBone( pbox->bone )->contents; - if ( ( fBoneContents & fContentsMask ) == 0 ) - continue; - - trace_t obbTrace; - if ( IntersectRayWithOBB( clippedRay, *hitboxbones[pbox->bone], pbox->bbmin, pbox->bbmax, 0.0f, &obbTrace ) ) - { - tr.startpos = obbTrace.startpos; - tr.endpos = obbTrace.endpos; - tr.plane = obbTrace.plane; - tr.startsolid = obbTrace.startsolid; - tr.allsolid = obbTrace.allsolid; - - // This logic here is to shorten the ray each time to get more early outs - tr.fraction *= obbTrace.fraction; - clippedRay.m_Delta *= obbTrace.fraction; - hitbox = i; - if (tr.startsolid) - break; - } - } - - if ( hitbox >= 0 ) - { - tr.hitgroup = set->pHitbox(hitbox)->group; - tr.hitbox = hitbox; - tr.contents = pStudioHdr->pBone( set->pHitbox(hitbox)->bone )->contents | CONTENTS_HITBOX; - tr.physicsbone = pStudioHdr->pBone( set->pHitbox(hitbox)->bone )->physicsbone; - Assert( tr.physicsbone >= 0 ); - return true; - } - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool TraceToStudio( const Ray_t& ray, CStudioHdr *pStudioHdr, mstudiohitboxset_t *set, - matrix3x4_t **hitboxbones, int fContentsMask, trace_t &tr ) -{ - if ( !ray.m_IsRay ) - { - return SweepBoxToStudio( ray, pStudioHdr, set, hitboxbones, fContentsMask, tr ); - } - - tr.fraction = 1.0; - tr.startsolid = false; - - // no hit yet - int hitbox = -1; - int hitside = -1; - - // OPTIMIZE: Partition these? - for ( int i = 0; i < set->numhitboxes; i++ ) - { - mstudiobbox_t *pbox = set->pHitbox(i); - - // Filter based on contents mask - int fBoneContents = pStudioHdr->pBone( pbox->bone )->contents; - if ( ( fBoneContents & fContentsMask ) == 0 ) - continue; - - // columns are axes of the bones in world space, translation is in world space - matrix3x4_t& matrix = *hitboxbones[pbox->bone]; - - int side = ClipRayToHitbox( ray, pbox, matrix, tr ); - if ( side >= 0 ) - { - hitbox = i; - hitside = side; - } - } - - if ( hitbox >= 0 ) - { - mstudiobbox_t *pbox = set->pHitbox(hitbox); - VectorMA( ray.m_Start, tr.fraction, ray.m_Delta, tr.endpos ); - tr.hitgroup = set->pHitbox(hitbox)->group; - tr.hitbox = hitbox; - tr.contents = pStudioHdr->pBone( pbox->bone )->contents | CONTENTS_HITBOX; - tr.physicsbone = pStudioHdr->pBone( pbox->bone )->physicsbone; - Assert( tr.physicsbone >= 0 ); - matrix3x4_t& matrix = *hitboxbones[pbox->bone]; - if ( hitside >= 3 ) - { - hitside -= 3; - tr.plane.normal[0] = matrix[0][hitside]; - tr.plane.normal[1] = matrix[1][hitside]; - tr.plane.normal[2] = matrix[2][hitside]; - //tr.plane.dist = DotProduct( tr.plane.normal, Vector(matrix[0][3], matrix[1][3], matrix[2][3] ) ) + pbox->bbmax[hitside]; - } - else - { - tr.plane.normal[0] = -matrix[0][hitside]; - tr.plane.normal[1] = -matrix[1][hitside]; - tr.plane.normal[2] = -matrix[2][hitside]; - //tr.plane.dist = DotProduct( tr.plane.normal, Vector(matrix[0][3], matrix[1][3], matrix[2][3] ) ) - pbox->bbmin[hitside]; - } - // simpler plane constant equation - tr.plane.dist = DotProduct( tr.endpos, tr.plane.normal ); - tr.plane.type = 3; - return true; - } - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: returns array of animations and weightings for a sequence based on current pose parameters -//----------------------------------------------------------------------------- - -void Studio_SeqAnims( const CStudioHdr *pStudioHdr, mstudioseqdesc_t &seqdesc, int iSequence, const float poseParameter[], mstudioanimdesc_t *panim[4], float *weight ) -{ -#if _DEBUG - VPROF_INCREMENT_COUNTER("SEQ_ANIMS",1); -#endif - if (!pStudioHdr || iSequence >= pStudioHdr->GetNumSeq()) - { - weight[0] = weight[1] = weight[2] = weight[3] = 0.0; - return; - } - - int i0 = 0, i1 = 0; - float s0 = 0, s1 = 0; - - Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, iSequence, 0, s0, i0 ); - Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, iSequence, 1, s1, i1 ); - - panim[0] = &pStudioHdr->pAnimdesc( pStudioHdr->iRelativeAnim( iSequence, seqdesc.anim( i0 , i1 ) ) ); - weight[0] = (1 - s0) * (1 - s1); - - panim[1] = &pStudioHdr->pAnimdesc( pStudioHdr->iRelativeAnim( iSequence, seqdesc.anim( i0+1, i1 ) ) ); - weight[1] = (s0) * (1 - s1); - - panim[2] = &pStudioHdr->pAnimdesc( pStudioHdr->iRelativeAnim( iSequence, seqdesc.anim( i0 , i1+1 ) ) ); - weight[2] = (1 - s0) * (s1); - - panim[3] = &pStudioHdr->pAnimdesc( pStudioHdr->iRelativeAnim( iSequence, seqdesc.anim( i0+1, i1+1 ) ) ); - weight[3] = (s0) * (s1); - - Assert( weight[0] >= 0.0f && weight[1] >= 0.0f && weight[2] >= 0.0f && weight[3] >= 0.0f ); -} - -//----------------------------------------------------------------------------- -// Purpose: returns max frame number for a sequence -//----------------------------------------------------------------------------- - -int Studio_MaxFrame( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[] ) -{ - mstudioanimdesc_t *panim[4]; - float weight[4]; - - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); - Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); - - float maxFrame = 0; - for (int i = 0; i < 4; i++) - { - if (weight[i] > 0) - { - maxFrame += panim[i]->numframes * weight[i]; - } - } - - if ( maxFrame > 1 ) - maxFrame -= 1; - - - // FIXME: why does the weights sometimes not exactly add it 1.0 and this sometimes rounds down? - return (maxFrame + 0.01); -} - - -//----------------------------------------------------------------------------- -// Purpose: returns frames per second of a sequence -//----------------------------------------------------------------------------- - -float Studio_FPS( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[] ) -{ - mstudioanimdesc_t *panim[4]; - float weight[4]; - - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); - Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); - - float t = 0; - - for (int i = 0; i < 4; i++) - { - if (weight[i] > 0) - { - t += panim[i]->fps * weight[i]; - } - } - return t; -} - - -//----------------------------------------------------------------------------- -// Purpose: returns cycles per second of a sequence (cycles/second) -//----------------------------------------------------------------------------- - -float Studio_CPS( const CStudioHdr *pStudioHdr, mstudioseqdesc_t &seqdesc, int iSequence, const float poseParameter[] ) -{ - mstudioanimdesc_t *panim[4]; - float weight[4]; - - Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); - - float t = 0; - - for (int i = 0; i < 4; i++) - { - if (weight[i] > 0 && panim[i]->numframes > 1) - { - t += (panim[i]->fps / (panim[i]->numframes - 1)) * weight[i]; - } - } - return t; -} - -//----------------------------------------------------------------------------- -// Purpose: returns length (in seconds) of a sequence (seconds/cycle) -//----------------------------------------------------------------------------- - -float Studio_Duration( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[] ) -{ - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); - float cps = Studio_CPS( pStudioHdr, seqdesc, iSequence, poseParameter ); - - if( cps == 0 ) - return 0.0f; - - return 1.0f/cps; -} - - -//----------------------------------------------------------------------------- -// Purpose: calculate changes in position and angle relative to the start of an animations cycle -// Output: updated position and angle, relative to the origin -// returns false if animation is not a movement animation -//----------------------------------------------------------------------------- - -bool Studio_AnimPosition( mstudioanimdesc_t *panim, float flCycle, Vector &vecPos, QAngle &vecAngle ) -{ - float prevframe = 0; - vecPos.Init( ); - vecAngle.Init( ); - - if (panim->nummovements == 0) - return false; - - int iLoops = 0; - if (flCycle > 1.0) - { - iLoops = (int)flCycle; - } - else if (flCycle < 0.0) - { - iLoops = (int)flCycle - 1; - } - flCycle = flCycle - iLoops; - - float flFrame = flCycle * (panim->numframes - 1); - - for (int i = 0; i < panim->nummovements; i++) - { - mstudiomovement_t *pmove = panim->pMovement( i ); - - if (pmove->endframe >= flFrame) - { - float f = (flFrame - prevframe) / (pmove->endframe - prevframe); - - float d = pmove->v0 * f + 0.5 * (pmove->v1 - pmove->v0) * f * f; - - vecPos = vecPos + d * pmove->vector; - vecAngle.y = vecAngle.y * (1 - f) + pmove->angle * f; - if (iLoops != 0) - { - mstudiomovement_t *pmove = panim->pMovement( panim->nummovements - 1 ); - vecPos = vecPos + iLoops * pmove->position; - vecAngle.y = vecAngle.y + iLoops * pmove->angle; - } - return true; - } - else - { - prevframe = pmove->endframe; - vecPos = pmove->position; - vecAngle.y = pmove->angle; - } - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: calculate instantaneous velocity in ips at a given point -// in the animations cycle -// Output: velocity vector, relative to identity orientation -// returns false if animation is not a movement animation -//----------------------------------------------------------------------------- - -bool Studio_AnimVelocity( mstudioanimdesc_t *panim, float flCycle, Vector &vecVelocity ) -{ - float prevframe = 0; - - float flFrame = flCycle * (panim->numframes - 1); - flFrame = flFrame - (int)(flFrame / (panim->numframes - 1)); - - for (int i = 0; i < panim->nummovements; i++) - { - mstudiomovement_t *pmove = panim->pMovement( i ); - - if (pmove->endframe >= flFrame) - { - float f = (flFrame - prevframe) / (pmove->endframe - prevframe); - - float vel = pmove->v0 * (1 - f) + pmove->v1 * f; - // scale from per block to per sec velocity - vel = vel * panim->fps / (pmove->endframe - prevframe); - - vecVelocity = pmove->vector * vel; - return true; - } - else - { - prevframe = pmove->endframe; - } - } - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: calculate changes in position and angle between two points in an animation cycle -// Output: updated position and angle, relative to CycleFrom being at the origin -// returns false if animation is not a movement animation -//----------------------------------------------------------------------------- - -bool Studio_AnimMovement( mstudioanimdesc_t *panim, float flCycleFrom, float flCycleTo, Vector &deltaPos, QAngle &deltaAngle ) -{ - if (panim->nummovements == 0) - return false; - - Vector startPos; - QAngle startA; - Studio_AnimPosition( panim, flCycleFrom, startPos, startA ); - - Vector endPos; - QAngle endA; - Studio_AnimPosition( panim, flCycleTo, endPos, endA ); - - Vector tmp = endPos - startPos; - deltaAngle.y = endA.y - startA.y; - VectorYawRotate( tmp, -startA.y, deltaPos ); - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: finds how much of an animation to play to move given linear distance -//----------------------------------------------------------------------------- - -float Studio_FindAnimDistance( mstudioanimdesc_t *panim, float flDist ) -{ - float prevframe = 0; - - if (flDist <= 0) - return 0.0; - - for (int i = 0; i < panim->nummovements; i++) - { - mstudiomovement_t *pmove = panim->pMovement( i ); - - float flMove = (pmove->v0 + pmove->v1) * 0.5; - - if (flMove >= flDist) - { - float root1, root2; - - // d = V0 * t + 1/2 (V1-V0) * t^2 - if (SolveQuadratic( 0.5 * (pmove->v1 - pmove->v0), pmove->v0, -flDist, root1, root2 )) - { - float cpf = 1.0 / (panim->numframes - 1); // cycles per frame - - return (prevframe + root1 * (pmove->endframe - prevframe)) * cpf; - } - return 0.0; - } - else - { - flDist -= flMove; - prevframe = pmove->endframe; - } - } - return 1.0; -} - - -//----------------------------------------------------------------------------- -// Purpose: calculate changes in position and angle between two points in a sequences cycle -// Output: updated position and angle, relative to CycleFrom being at the origin -// returns false if sequence is not a movement sequence -//----------------------------------------------------------------------------- - -bool Studio_SeqMovement( const CStudioHdr *pStudioHdr, int iSequence, float flCycleFrom, float flCycleTo, const float poseParameter[], Vector &deltaPos, QAngle &deltaAngles ) -{ - mstudioanimdesc_t *panim[4]; - float weight[4]; - - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); - - Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); - - deltaPos.Init( ); - deltaAngles.Init( ); - - bool found = false; - - for (int i = 0; i < 4; i++) - { - if (weight[i]) - { - Vector localPos; - QAngle localAngles; - - localPos.Init(); - localAngles.Init(); - - if (Studio_AnimMovement( panim[i], flCycleFrom, flCycleTo, localPos, localAngles )) - { - found = true; - deltaPos = deltaPos + localPos * weight[i]; - // FIXME: this makes no sense - deltaAngles = deltaAngles + localAngles * weight[i]; - } - else if (!(panim[i]->flags & STUDIO_DELTA) && panim[i]->nummovements == 0 && seqdesc.weight(0) > 0.0) - { - found = true; - } - } - } - return found; -} - - -//----------------------------------------------------------------------------- -// Purpose: calculate instantaneous velocity in ips at a given point in the sequence's cycle -// Output: velocity vector, relative to identity orientation -// returns false if sequence is not a movement sequence -//----------------------------------------------------------------------------- - -bool Studio_SeqVelocity( const CStudioHdr *pStudioHdr, int iSequence, float flCycle, const float poseParameter[], Vector &vecVelocity ) -{ - mstudioanimdesc_t *panim[4]; - float weight[4]; - - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); - Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); - - vecVelocity.Init( ); - - bool found = false; - - for (int i = 0; i < 4; i++) - { - if (weight[i]) - { - Vector vecLocalVelocity; - - if (Studio_AnimVelocity( panim[i], flCycle, vecLocalVelocity )) - { - vecVelocity = vecVelocity + vecLocalVelocity * weight[i]; - found = true; - } - } - } - return found; -} - -//----------------------------------------------------------------------------- -// Purpose: finds how much of an sequence to play to move given linear distance -//----------------------------------------------------------------------------- - -float Studio_FindSeqDistance( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[], float flDist ) -{ - mstudioanimdesc_t *panim[4]; - float weight[4]; - - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); - Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); - - float flCycle = 0; - - for (int i = 0; i < 4; i++) - { - if (weight[i]) - { - float flLocalCycle = Studio_FindAnimDistance( panim[i], flDist ); - flCycle = flCycle + flLocalCycle * weight[i]; - } - } - return flCycle; -} - -//----------------------------------------------------------------------------- -// Purpose: lookup attachment by name -//----------------------------------------------------------------------------- - -int Studio_FindAttachment( const CStudioHdr *pStudioHdr, const char *pAttachmentName ) -{ - if ( pStudioHdr && pStudioHdr->SequencesAvailable() ) - { - // Extract the bone index from the name - for (int i = 0; i < pStudioHdr->GetNumAttachments(); i++) - { - if (!stricmp(pAttachmentName,pStudioHdr->pAttachment(i).pszName( ))) - { - return i; - } - } - } - - return -1; -} - -//----------------------------------------------------------------------------- -// Purpose: lookup attachments by substring. Randomly return one of the matching attachments. -//----------------------------------------------------------------------------- - -int Studio_FindRandomAttachment( const CStudioHdr *pStudioHdr, const char *pAttachmentName ) -{ - if ( pStudioHdr ) - { - // First move them all matching attachments into a list - CUtlVector matchingAttachments; - - // Extract the bone index from the name - for (int i = 0; i < pStudioHdr->GetNumAttachments(); i++) - { - if ( strstr( pStudioHdr->pAttachment(i).pszName(), pAttachmentName ) ) - { - matchingAttachments.AddToTail(i); - } - } - - // Then randomly return one of the attachments - if ( matchingAttachments.Size() > 0 ) - return matchingAttachments[ RandomInt( 0, matchingAttachments.Size()-1 ) ]; - } - - return -1; -} - -//----------------------------------------------------------------------------- -// Purpose: lookup bone by name -//----------------------------------------------------------------------------- - -int Studio_BoneIndexByName( const CStudioHdr *pStudioHdr, const char *pName ) -{ - // binary search for the bone matching pName - int start = 0, end = pStudioHdr->numbones()-1; - const byte *pBoneTable = pStudioHdr->GetBoneTableSortedByName(); - mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); - while (start <= end) - { - int mid = (start + end) >> 1; - int cmp = Q_stricmp( pbones[pBoneTable[mid]].pszName(), pName ); - - if ( cmp < 0 ) - { - start = mid + 1; - } - else if ( cmp > 0 ) - { - end = mid - 1; - } - else - { - return pBoneTable[mid]; - } - } - return -1; -} - -const char *Studio_GetDefaultSurfaceProps( CStudioHdr *pstudiohdr ) -{ - return pstudiohdr->pszSurfaceProp(); -} - -float Studio_GetMass( CStudioHdr *pstudiohdr ) -{ - return pstudiohdr->mass(); -} - -//----------------------------------------------------------------------------- -// Purpose: return pointer to sequence key value buffer -//----------------------------------------------------------------------------- - -const char *Studio_GetKeyValueText( const CStudioHdr *pStudioHdr, int iSequence ) -{ - if (pStudioHdr && pStudioHdr->SequencesAvailable()) - { - if (iSequence >= 0 && iSequence < pStudioHdr->GetNumSeq()) - { - return pStudioHdr->pSeqdesc( iSequence ).KeyValueText(); - } - } - return NULL; -} - -bool Studio_PrefetchSequence( const CStudioHdr *pStudioHdr, int iSequence ) -{ - bool pendingload = false; - mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); - int size0 = seqdesc.groupsize[ 0 ]; - int size1 = seqdesc.groupsize[ 1 ]; - for ( int i = 0; i < size0; ++i ) - { - for ( int j = 0; j < size1; ++j ) - { - mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( seqdesc.anim( i, j ) ); - mstudioanim_t *panim = animdesc.pAnim(); - if ( !panim ) - { - pendingload = true; - } - } - } - - // Everything for this sequence is resident? - return !pendingload; -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "tier0/dbg.h" +#include "mathlib.h" +#include "bone_setup.h" +#include + +#include "collisionutils.h" +#include "vstdlib/random.h" +#include "tier0/vprof.h" +#include "bone_accessor.h" +#include "bitvec.h" +#include "datamanager.h" +#include "convar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ----------------------------------------------------------------- +CBoneCache *CBoneCache::CreateResource( const bonecacheparams_t ¶ms ) +{ + short studioToCachedIndex[MAXSTUDIOBONES]; + short cachedToStudioIndex[MAXSTUDIOBONES]; + int cachedBoneCount = 0; + for ( int i = 0; i < params.pStudioHdr->numbones(); i++ ) + { + // skip bones that aren't part of the boneMask (and aren't the root bone) + if (i != 0 && !(params.pStudioHdr->pBone(i)->flags & params.boneMask)) + { + studioToCachedIndex[i] = -1; + continue; + } + studioToCachedIndex[i] = cachedBoneCount; + cachedToStudioIndex[cachedBoneCount] = i; + cachedBoneCount++; + } + int tableSizeStudio = sizeof(short) * params.pStudioHdr->numbones(); + int tableSizeCached = sizeof(short) * cachedBoneCount; + int matrixSize = sizeof(matrix3x4_t) * cachedBoneCount; + int size = sizeof(CBoneCache) + tableSizeStudio + tableSizeCached + matrixSize; + + CBoneCache *pMem = (CBoneCache *)malloc( size ); + Construct( pMem ); + pMem->Init( params, size, studioToCachedIndex, cachedToStudioIndex, cachedBoneCount ); + return pMem; +} + +unsigned int CBoneCache::EstimatedSize( const bonecacheparams_t ¶ms ) +{ + // conservative estimate - max size + return params.pStudioHdr->numbones() * (sizeof(short) + sizeof(short) + sizeof(matrix3x4_t)); +} + +void CBoneCache::DestroyResource() +{ + free( this ); +} + + +CBoneCache::CBoneCache() +{ + m_size = 0; + m_cachedBoneCount = 0; +} + +void CBoneCache::Init( const bonecacheparams_t ¶ms, unsigned int size, short *pStudioToCached, short *pCachedToStudio, int cachedBoneCount ) +{ + m_cachedBoneCount = cachedBoneCount; + m_size = size; + m_timeValid = params.curtime; + m_boneMask = params.boneMask; + + int studioTableSize = params.pStudioHdr->numbones() * sizeof(short); + m_cachedToStudioOffset = studioTableSize; + memcpy( StudioToCached(), pStudioToCached, studioTableSize ); + + int cachedTableSize = cachedBoneCount * sizeof(short); + memcpy( CachedToStudio(), pCachedToStudio, cachedTableSize ); + + m_matrixOffset = m_cachedToStudioOffset + cachedTableSize; + + UpdateBones( params.pBoneToWorld, params.pStudioHdr->numbones(), params.curtime ); +} + +void CBoneCache::UpdateBones( const matrix3x4_t *pBoneToWorld, int numbones, float curtime ) +{ + matrix3x4_t *pBones = BoneArray(); + const short *pCachedToStudio = CachedToStudio(); + + for ( int i = 0; i < m_cachedBoneCount; i++ ) + { + int index = pCachedToStudio[i]; + MatrixCopy( pBoneToWorld[index], pBones[i] ); + } + m_timeValid = curtime; +} + +matrix3x4_t *CBoneCache::GetCachedBone( int studioIndex ) +{ + int cachedIndex = StudioToCached()[studioIndex]; + if ( cachedIndex >= 0 ) + { + return BoneArray() + cachedIndex; + } + return NULL; +} + +void CBoneCache::ReadCachedBones( matrix3x4_t *pBoneToWorld ) +{ + matrix3x4_t *pBones = BoneArray(); + const short *pCachedToStudio = CachedToStudio(); + for ( int i = 0; i < m_cachedBoneCount; i++ ) + { + MatrixCopy( pBones[i], pBoneToWorld[pCachedToStudio[i]] ); + } +} + +void CBoneCache::ReadCachedBonePointers( matrix3x4_t **bones, int numbones ) +{ + memset( bones, 0, sizeof(matrix3x4_t *) * numbones ); + matrix3x4_t *pBones = BoneArray(); + const short *pCachedToStudio = CachedToStudio(); + for ( int i = 0; i < m_cachedBoneCount; i++ ) + { + bones[pCachedToStudio[i]] = pBones + i; + } +} + +bool CBoneCache::IsValid( float time, float dt ) +{ + if ( time - m_timeValid <= dt ) + return true; + return false; +} + + +// private functions +matrix3x4_t *CBoneCache::BoneArray() +{ + return (matrix3x4_t *)( (char *)(this+1) + m_matrixOffset ); +} + +short *CBoneCache::StudioToCached() +{ + return (short *)( (char *)(this+1) ); +} + +short *CBoneCache::CachedToStudio() +{ + return (short *)( (char *)(this+1) + m_cachedToStudioOffset ); +} + +// Construct a singleton +static CDataManager g_StudioBoneCache( 16 * 1024L ); + +CBoneCache *Studio_GetBoneCache( memhandle_t cacheHandle ) +{ + return g_StudioBoneCache.GetResource_NoLock( cacheHandle ); +} + +memhandle_t Studio_CreateBoneCache( bonecacheparams_t ¶ms ) +{ + return g_StudioBoneCache.CreateResource( params ); +} + +void Studio_DestroyBoneCache( memhandle_t cacheHandle ) +{ + g_StudioBoneCache.DestroyResource( cacheHandle ); +} + +void Studio_InvalidateBoneCache( memhandle_t cacheHandle ) +{ + CBoneCache *pCache = g_StudioBoneCache.GetResource_NoLock( cacheHandle ); + if ( pCache ) + { + pCache->m_timeValid = -1.0f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void BuildBoneChain( + const CStudioHdr *pStudioHdr, + const matrix3x4_t &rootxform, + const Vector pos[], + const Quaternion q[], + int iBone, + matrix3x4_t *pBoneToWorld ) +{ + CBoneBitList boneComputed; + BuildBoneChain( pStudioHdr, rootxform, pos, q, iBone, pBoneToWorld, boneComputed ); + return; +} + + + +//----------------------------------------------------------------------------- +// Purpose: return a sub frame rotation for a single bone +//----------------------------------------------------------------------------- + + +void ExtractAnimValue( int frame, + mstudioanimvalue_t *panimvalue, + float scale, + float &v1, float &v2 ) +{ + if (!panimvalue) + { + v1 = v2 = 0; + return; + } + + int k = frame; + + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + if ( panimvalue->num.total == 0 ) + { + v1 = v2 = 0; + return; + } + } + // Bah, missing blend! + if (panimvalue->num.valid > k) + { + v1 = panimvalue[k+1].value * scale; + + if (panimvalue->num.valid > k + 1) + { + v2 = panimvalue[k+2].value * scale; + } + else + { + if (panimvalue->num.total > k + 1) + v2 = v1; + else + v2 = panimvalue[panimvalue->num.valid+2].value * scale; + } + } + else + { + v1 = panimvalue[panimvalue->num.valid].value * scale; + if (panimvalue->num.total > k + 1) + { + v2 = v1; + } + else + { + v2 = panimvalue[panimvalue->num.valid + 2].value * scale; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: return a sub frame rotation for a single bone +//----------------------------------------------------------------------------- +void CalcBoneQuaternion( int frame, float s, + const mstudiobone_t *pbone, const mstudioanim_t *panim, Quaternion &q ) +{ + if (panim->flags & STUDIO_ANIM_RAWROT) + { + q = *(panim->pQuat()); + Assert( q.IsValid() ); + return; + } + else if (!(panim->flags & STUDIO_ANIM_ANIMROT)) + { + if (panim->flags & STUDIO_ANIM_DELTA) + { + q.Init( 0.0f, 0.0f, 0.0f, 1.0f ); + } + else + { + q = pbone->quat; + } + return; + } + + Quaternion q1, q2; + RadianEuler angle1, angle2; + mstudioanim_valueptr_t *pValuesPtr = panim->pRotV(); + + ExtractAnimValue( frame, pValuesPtr->pAnimvalue( 0 ), pbone->rotscale.x, angle1.x, angle2.x ); + ExtractAnimValue( frame, pValuesPtr->pAnimvalue( 1 ), pbone->rotscale.y, angle1.y, angle2.y ); + ExtractAnimValue( frame, pValuesPtr->pAnimvalue( 2 ), pbone->rotscale.z, angle1.z, angle2.z ); + + if (!(panim->flags & STUDIO_ANIM_DELTA)) + { + angle1.x = angle1.x + pbone->rot.x; + angle1.y = angle1.y + pbone->rot.y; + angle1.z = angle1.z + pbone->rot.z; + angle2.x = angle2.x + pbone->rot.x; + angle2.y = angle2.y + pbone->rot.y; + angle2.z = angle2.z + pbone->rot.z; + } + + Assert( angle1.IsValid() && angle2.IsValid() ); + if (angle1.x != angle2.x || angle1.y != angle2.y || angle1.z != angle2.z) + { + AngleQuaternion( angle1, q1 ); + AngleQuaternion( angle2, q2 ); + QuaternionBlend( q1, q2, s, q ); + } + else + { + AngleQuaternion( angle1, q ); + } + + Assert( q.IsValid() ); + + // align to unified bone + if (!(panim->flags & STUDIO_ANIM_DELTA) && (pbone->flags & BONE_FIXED_ALIGNMENT)) + { + QuaternionAlign( pbone->qAlignment, q, q ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: return a sub frame position for a single bone +//----------------------------------------------------------------------------- +void CalcBonePosition( int frame, float s, + const mstudiobone_t *pbone, const mstudioanim_t *panim, Vector &pos ) +{ + if (panim->flags & STUDIO_ANIM_RAWPOS) + { + pos = *(panim->pPos()); + Assert( pos.IsValid() ); + + return; + } + else if (!(panim->flags & STUDIO_ANIM_ANIMPOS)) + { + if (panim->flags & STUDIO_ANIM_DELTA) + { + pos.Init( 0.0f, 0.0f, 0.0f ); + } + else + { + pos = pbone->pos; + } + return; + } + + mstudioanim_valueptr_t *pPosV = panim->pPosV(); + int j; + float v1, v2; + + for (j = 0; j < 3; j++) + { + ExtractAnimValue( frame, pPosV->pAnimvalue( j ), pbone->posscale[j], v1, v2 ); + pos[j] = v1 * (1.0 - s) + v2 * s; + } + + if (!(panim->flags & STUDIO_ANIM_DELTA)) + { + pos.x = pos.x + pbone->pos.x; + pos.y = pos.y + pbone->pos.y; + pos.z = pos.z + pbone->pos.z; + } + + Assert( pos.IsValid() ); +} + + +void SetupSingleBoneMatrix( + CStudioHdr *pOwnerHdr, + int nSequence, + int iFrame, + int iBone, + matrix3x4_t &mBoneLocal ) +{ + mstudioseqdesc_t &seqdesc = pOwnerHdr->pSeqdesc( nSequence ); + mstudioanimdesc_t &animdesc = pOwnerHdr->pAnimdesc( seqdesc.anim( 0, 0 ) ); + mstudioanim_t *panim = animdesc.pAnim( ); + float s = 0; + mstudiobone_t *pbone = pOwnerHdr->pBone( iBone ); + + Quaternion boneQuat; + Vector bonePos; + + // search for bone + while (panim && panim->bone != iBone) + { + panim = panim->pNext(); + } + + // look up animation if found, if not, initialize + if (panim && seqdesc.weight(iBone) > 0) + { + CalcBoneQuaternion( iFrame, s, pbone, panim, boneQuat ); + CalcBonePosition ( iFrame, s, pbone, panim, bonePos ); + } + else if (animdesc.flags & STUDIO_DELTA) + { + boneQuat.Init( 0.0f, 0.0f, 0.0f, 1.0f ); + bonePos.Init( 0.0f, 0.0f, 0.0f ); + } + else + { + boneQuat = pbone->quat; + bonePos = pbone->pos; + } + + QuaternionMatrix( boneQuat, bonePos, mBoneLocal ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void CalcVirtualAnimation( virtualmodel_t *pVModel, const CStudioHdr *pStudioHdr, Vector *pos, Quaternion *q, + mstudioseqdesc_t &seqdesc, int sequence, int animation, + float cycle, int boneMask ) +{ + int i, j, k; + + const mstudiobone_t *pbone; + const virtualgroup_t *pSeqGroup; + const studiohdr_t *pSeqStudioHdr; + const mstudiobone_t *pSeqbone; + const mstudioanim_t *panim; + const studiohdr_t *pAnimStudioHdr; + const mstudiobone_t *pAnimbone; + const virtualgroup_t *pAnimGroup; + + pSeqGroup = pVModel->pSeqGroup( sequence ); + int baseanimation = pStudioHdr->iRelativeAnim( sequence, animation ); + mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( baseanimation ); + pSeqStudioHdr = pStudioHdr->pSeqStudioHdr( sequence ); + pSeqbone = pSeqStudioHdr->pBone( 0 ); + pAnimGroup = pVModel->pAnimGroup( baseanimation ); + panim = animdesc.pAnim( ); + pAnimStudioHdr = pStudioHdr->pAnimStudioHdr( baseanimation ); + pAnimbone = pAnimStudioHdr->pBone( 0 ); + + int iFrame; + float s; + + float fFrame = cycle * (animdesc.numframes - 1); + + iFrame = (int)fFrame; + s = (fFrame - iFrame); + + float *pweight = seqdesc.pBoneweight( 0 ); + pbone = pStudioHdr->pBone( 0 ); + + for (i = 0; i < pStudioHdr->numbones(); i++) + { + if (pbone[i].flags & boneMask) + { + int j = pSeqGroup->boneMap[i]; + if (j >= 0 && pweight[j] > 0.0f) + { + if (animdesc.flags & STUDIO_DELTA) + { + q[i].Init( 0.0f, 0.0f, 0.0f, 1.0f ); + pos[i].Init( 0.0f, 0.0f, 0.0f ); + } + else + { + q[i] = pSeqbone[j].quat; + pos[i] = pSeqbone[j].pos; + } + } + } + } + + // if the animation isn't available, look for the zero frame cache + if (!panim) + { + byte *pData = pAnimStudioHdr->pZeroframeCache( pVModel->m_anim[baseanimation].index ); + + if (pData) + { + // Msg("zeroframe %s\n", animdesc.pszName() ); + for (j = 0; j < pAnimStudioHdr->numbones; j++) + { + i = pAnimGroup->masterBone[j]; + + if (pAnimbone[j].flags & BONE_HAS_SAVEFRAME_POS) + { + if ((i >= 0) && (pbone[i].flags & boneMask)) + { + pos[i] = *(Vector48 *)pData; + } + pData += sizeof( Vector48 ); + } + if (!(animdesc.flags & STUDIO_DELTA) && (pAnimbone[j].flags & BONE_HAS_SAVEFRAME_ROT) != 0) + { + if ((i >= 0) && (pbone[i].flags & boneMask)) + { + q[i] = *(Quaternion32 *)pData; + } + pData += sizeof( Quaternion32 ); + } + } + return; + } + } + + // FIXME: change encoding so that bone -1 is never the case + while (panim && panim->bone < 255) + { + j = pAnimGroup->masterBone[panim->bone]; + if (j >= 0 && (pbone[j].flags & boneMask)) + { + k = pSeqGroup->boneMap[j]; + + if (k >= 0 && pweight[k] > 0.0f) + { + CalcBoneQuaternion( iFrame, s, &pAnimbone[panim->bone], panim, q[j] ); + CalcBonePosition ( iFrame, s, &pAnimbone[panim->bone], panim, pos[j] ); + } + } + panim = panim->pNext(); + } +} + + + + +static void CalcAnimation( const CStudioHdr *pStudioHdr, Vector *pos, Quaternion *q, + mstudioseqdesc_t &seqdesc, + int sequence, int animation, + float cycle, int boneMask ) +{ + int i; + + virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); + + if (pVModel) + { + CalcVirtualAnimation( pVModel, pStudioHdr, pos, q, seqdesc, sequence, animation, cycle, boneMask ); + return; + } + + mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( animation ); + mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); + mstudioanim_t *panim = animdesc.pAnim( ); + + int iFrame; + float s; + + float fFrame = cycle * (animdesc.numframes - 1); + + iFrame = (int)fFrame; + s = (fFrame - iFrame); + + float *pweight = seqdesc.pBoneweight( 0 ); + + // if the animation isn't available, look for the zero frame cache + if (!panim) + { + byte *pData = pStudioHdr->pZeroframeCache( animation ); + + if (pData) + { + // Msg("zeroframe %s\n", animdesc.pszName() ); + for (i = 0; i < pStudioHdr->numbones(); i++, pbone++, pweight++) + { + if (*pweight > 0 && (pbone->flags & boneMask)) + { + if (animdesc.flags & STUDIO_DELTA) + { + q[i].Init( 0.0f, 0.0f, 0.0f, 1.0f ); + pos[i].Init( 0.0f, 0.0f, 0.0f ); + } + else + { + q[i] = pbone->quat; + pos[i] = pbone->pos; + } + } + + if (pbone->flags & BONE_HAS_SAVEFRAME_POS) + { + if (*pweight > 0 && (pbone->flags & boneMask)) + { + pos[i] = *(Vector48 *)pData; + Assert( pos[i].IsValid() ); + } + pData += sizeof( Vector48 ); + } + if (!(animdesc.flags & STUDIO_DELTA) && (pbone->flags & BONE_HAS_SAVEFRAME_ROT) != 0) + { + if (*pweight > 0 && (pbone->flags & boneMask)) + { + q[i] = *(Quaternion32 *)pData; + Assert( q[i].IsValid() ); + } + pData += sizeof( Quaternion32 ); + } + } + return; + } + } + + // BUGBUG: the sequence, the anim, and the model can have all different bone mappings. + for (i = 0; i < pStudioHdr->numbones(); i++, pbone++, pweight++) + { + if (panim && panim->bone == i) + { + if (*pweight > 0 && (pbone->flags & boneMask)) + { + CalcBoneQuaternion( iFrame, s, pbone, panim, q[i] ); + CalcBonePosition ( iFrame, s, pbone, panim, pos[i] ); + } + panim = panim->pNext(); + } + else if (*pweight > 0 && (pbone->flags & boneMask)) + { + if (animdesc.flags & STUDIO_DELTA) + { + q[i].Init( 0.0f, 0.0f, 0.0f, 1.0f ); + pos[i].Init( 0.0f, 0.0f, 0.0f ); + } + else + { + q[i] = pbone->quat; + pos[i] = pbone->pos; + } + } + } +} + + + +// qt = ( s * p ) * q +void QuaternionSM( float s, const Quaternion &p, const Quaternion &q, Quaternion &qt ) +{ + Quaternion p1, q1; + + QuaternionScale( p, s, p1 ); + QuaternionMult( p1, q, q1 ); + QuaternionNormalize( q1 ); + qt[0] = q1[0]; + qt[1] = q1[1]; + qt[2] = q1[2]; + qt[3] = q1[3]; +} + +// qt = p * ( s * q ) +void QuaternionMA( const Quaternion &p, float s, const Quaternion &q, Quaternion &qt ) +{ + Quaternion p1, q1; + + QuaternionScale( q, s, q1 ); + QuaternionMult( p, q1, p1 ); + QuaternionNormalize( p1 ); + qt[0] = p1[0]; + qt[1] = p1[1]; + qt[2] = p1[2]; + qt[3] = p1[3]; +} + + +// qt = p * ( s * q ) +void QuaternionAccumulate( const Quaternion &p, float s, const Quaternion &q, Quaternion &qt ) +{ + Quaternion q2; + QuaternionAlign( p, q, q2 ); + + qt[0] = p[0] + s * q2[0]; + qt[1] = p[1] + s * q2[1]; + qt[2] = p[2] + s * q2[2]; + qt[3] = p[3] + s * q2[3]; +} + + + +//----------------------------------------------------------------------------- +// Purpose: blend together in world space q1,pos1 with q2,pos2. Return result in q1,pos1. +// 0 returns q1, pos1. 1 returns q2, pos2 +//----------------------------------------------------------------------------- + +void WorldSpaceSlerp( + const CStudioHdr *pStudioHdr, + Quaternion q1[MAXSTUDIOBONES], + Vector pos1[MAXSTUDIOBONES], + mstudioseqdesc_t &seqdesc, + int sequence, + const Quaternion q2[MAXSTUDIOBONES], + const Vector pos2[MAXSTUDIOBONES], + float s, + int boneMask ) +{ + int i, j; + float s1; // weight of parent for q2, pos2 + float s2; // weight for q2, pos2 + + // make fake root transform + matrix3x4_t rootXform; + SetIdentityMatrix( rootXform ); + + // matrices for q2, pos2 + static matrix3x4_t srcBoneToWorld[MAXSTUDIOBONES]; + CBoneBitList srcBoneComputed; + + static matrix3x4_t destBoneToWorld[MAXSTUDIOBONES]; + CBoneBitList destBoneComputed; + + static matrix3x4_t targetBoneToWorld[MAXSTUDIOBONES]; + CBoneBitList targetBoneComputed; + + virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); + const virtualgroup_t *pSeqGroup = NULL; + if (pVModel) + { + pSeqGroup = pVModel->pSeqGroup( sequence ); + } + + mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); + + for (i = 0; i < pStudioHdr->numbones(); i++) + { + // skip unused bones + if (!(pbone[i].flags & boneMask)) + { + continue; + } + + int n = pbone[i].parent; + s1 = 0.0; + if (pSeqGroup) + { + j = pSeqGroup->boneMap[i]; + if (j >= 0) + { + s2 = s * seqdesc.weight( j ); // blend in based on this bones weight + if (n != -1) + { + s1 = s * seqdesc.weight( pSeqGroup->boneMap[n] ); + } + } + else + { + s2 = 0.0; + } + } + else + { + s2 = s * seqdesc.weight( i ); // blend in based on this bones weight + if (n != -1) + { + s1 = s * seqdesc.weight( n ); + } + } + + if (s1 == 1.0 && s2 == 1.0) + { + pos1[i] = pos2[i]; + q1[i] = q2[i]; + } + else if (s2 > 0.0) + { + Quaternion srcQ, destQ; + Vector srcPos, destPos; + Quaternion targetQ; + Vector targetPos; + Vector tmp; + + BuildBoneChain( pStudioHdr, rootXform, pos1, q1, i, destBoneToWorld, destBoneComputed ); + BuildBoneChain( pStudioHdr, rootXform, pos2, q2, i, srcBoneToWorld, srcBoneComputed ); + + MatrixAngles( destBoneToWorld[i], destQ, destPos ); + MatrixAngles( srcBoneToWorld[i], srcQ, srcPos ); + + QuaternionSlerp( destQ, srcQ, s2, targetQ ); + AngleMatrix( targetQ, destPos, targetBoneToWorld[i] ); + + // back solve + if (n == -1) + { + MatrixAngles( targetBoneToWorld[i], q1[i], tmp ); + } + else + { + matrix3x4_t worldToBone; + MatrixInvert( targetBoneToWorld[n], worldToBone ); + + matrix3x4_t local; + ConcatTransforms( worldToBone, targetBoneToWorld[i], local ); + MatrixAngles( local, q1[i], tmp ); + + // blend bone lengths (local space) + pos1[i] = Lerp( s2, pos1[i], pos2[i] ); + } + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1. +// 0 returns q1, pos1. 1 returns q2, pos2 +//----------------------------------------------------------------------------- +void SlerpBones( + const CStudioHdr *pStudioHdr, + Quaternion q1[MAXSTUDIOBONES], + Vector pos1[MAXSTUDIOBONES], + mstudioseqdesc_t &seqdesc, // source of q2 and pos2 + int sequence, + const Quaternion q2[MAXSTUDIOBONES], + const Vector pos2[MAXSTUDIOBONES], + float s, + int boneMask ) +{ + if (s <= 0.0f) + { + return; + } + else if (s > 1.0f) + { + s = 1.0f; + } + + if (seqdesc.flags & STUDIO_WORLD) + { + WorldSpaceSlerp( pStudioHdr, q1, pos1, seqdesc, sequence, q2, pos2, s, boneMask ); + return; + } + + int i, j; + Quaternion q3, q4; + float s1, s2; + + virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); + const virtualgroup_t *pSeqGroup = NULL; + if (pVModel) + { + pSeqGroup = pVModel->pSeqGroup( sequence ); + } + + mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); + + if (seqdesc.flags & STUDIO_DELTA) + { + for (i = 0; i < pStudioHdr->numbones(); i++) + { + // skip unused bones + if (!(pbone[i].flags & boneMask)) + { + continue; + } + + if (pSeqGroup) + { + j = pSeqGroup->boneMap[i]; + if (j >= 0) + { + s2 = s * seqdesc.weight( j ); // blend in based on this bones weight + } + else + { + s2 = 0.0; + } + } + else + { + s2 = s * seqdesc.weight( i ); // blend in based on this bones weight + } + + if (s2 > 0.0) + { + if (seqdesc.flags & STUDIO_POST) + { + QuaternionMA( q1[i], s2, q2[i], q1[i] ); + + // FIXME: are these correct? + pos1[i][0] = pos1[i][0] + pos2[i][0] * s2; + pos1[i][1] = pos1[i][1] + pos2[i][1] * s2; + pos1[i][2] = pos1[i][2] + pos2[i][2] * s2; + } + else + { + QuaternionSM( s2, q2[i], q1[i], q1[i] ); + + // FIXME: are these correct? + pos1[i][0] = pos1[i][0] + pos2[i][0] * s2; + pos1[i][1] = pos1[i][1] + pos2[i][1] * s2; + pos1[i][2] = pos1[i][2] + pos2[i][2] * s2; + } + } + } + } + else + { + for (i = 0; i < pStudioHdr->numbones(); i++) + { + // skip unused bones + if (!(pbone[i].flags & boneMask)) + { + continue; + } + + if (pSeqGroup) + { + j = pSeqGroup->boneMap[i]; + if (j >= 0) + { + s2 = s * seqdesc.weight( j ); // blend in based on this bones weight + } + else + { + s2 = 0.0; + } + } + else + { + s2 = s * seqdesc.weight( i ); // blend in based on this animations weights + } + if (s2 > 0.0) + { + s1 = 1.0 - s2; + + if (pbone[i].flags & BONE_FIXED_ALIGNMENT) + { + QuaternionSlerpNoAlign( q2[i], q1[i], s1, q3 ); + } + else + { + QuaternionSlerp( q2[i], q1[i], s1, q3 ); + } + q1[i][0] = q3[0]; + q1[i][1] = q3[1]; + q1[i][2] = q3[2]; + q1[i][3] = q3[3]; + pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s2; + pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s2; + pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s2; + } + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Inter-animation blend. Assumes both types are identical. +// blend together q1,pos1 with q2,pos2. Return result in q1,pos1. +// 0 returns q1, pos1. 1 returns q2, pos2 +//----------------------------------------------------------------------------- +void BlendBones( + const CStudioHdr *pStudioHdr, + Quaternion q1[MAXSTUDIOBONES], + Vector pos1[MAXSTUDIOBONES], + mstudioseqdesc_t &seqdesc, + int sequence, + const Quaternion q2[MAXSTUDIOBONES], + const Vector pos2[MAXSTUDIOBONES], + float s, + int boneMask ) +{ + int i, j; + Quaternion q3; + + virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); + const virtualgroup_t *pSeqGroup = NULL; + if (pVModel) + { + pSeqGroup = pVModel->pSeqGroup( sequence ); + } + + mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); + + if (s <= 0) + { + Assert(0); // shouldn't have been called + return; + } + else if (s >= 1.0) + { + Assert(0); // shouldn't have been called + for (i = 0; i < pStudioHdr->numbones(); i++) + { + // skip unused bones + if (!(pbone[i].flags & boneMask)) + { + continue; + } + + if (pSeqGroup) + { + j = pSeqGroup->boneMap[i]; + } + else + { + j = i; + } + + if (j >= 0 && seqdesc.weight( j ) > 0.0) + { + q1[i] = q2[i]; + pos1[i] = pos2[i]; + } + } + return; + } + + float s2 = s; + float s1 = 1.0 - s2; + + for (i = 0; i < pStudioHdr->numbones(); i++) + { + // skip unused bones + if (!(pbone[i].flags & boneMask)) + { + continue; + } + + if (pSeqGroup) + { + j = pSeqGroup->boneMap[i]; + } + else + { + j = i; + } + + if (j >= 0 && seqdesc.weight( j ) > 0.0) + { + if (pbone[i].flags & BONE_FIXED_ALIGNMENT) + { + QuaternionBlendNoAlign( q2[i], q1[i], s1, q3 ); + } + else + { + QuaternionBlend( q2[i], q1[i], s1, q3 ); + } + q1[i][0] = q3[0]; + q1[i][1] = q3[1]; + q1[i][2] = q3[2]; + q1[i][3] = q3[3]; + pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s2; + pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s2; + pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s2; + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Scale a set of bones. Must be of type delta +//----------------------------------------------------------------------------- +void ScaleBones( + const CStudioHdr *pStudioHdr, + Quaternion q1[MAXSTUDIOBONES], + Vector pos1[MAXSTUDIOBONES], + int sequence, + float s, + int boneMask ) +{ + int i, j; + Quaternion q3; + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequence ); + + virtualmodel_t *pVModel = pStudioHdr->GetVirtualModel(); + const virtualgroup_t *pSeqGroup = NULL; + if (pVModel) + { + pSeqGroup = pVModel->pSeqGroup( sequence ); + } + + mstudiobone_t *pbone = pStudioHdr->pBone( 0 ); + + float s2 = s; + float s1 = 1.0 - s2; + + for (i = 0; i < pStudioHdr->numbones(); i++) + { + // skip unused bones + if (!(pbone[i].flags & boneMask)) + { + continue; + } + + if (pSeqGroup) + { + j = pSeqGroup->boneMap[i]; + } + else + { + j = i; + } + + if (j >= 0 && seqdesc.weight( j ) > 0.0) + { + QuaternionIdentityBlend( q1[i], s1, q1[i] ); + VectorScale( pos1[i], s2, pos1[i] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: resolve a global pose parameter to the specific setting for this sequence +//----------------------------------------------------------------------------- +void Studio_LocalPoseParameter( const CStudioHdr *pStudioHdr, const float poseParameter[], mstudioseqdesc_t &seqdesc, int iSequence, int iLocalIndex, float &flSetting, int &index ) +{ + int iPose = pStudioHdr->GetSharedPoseParameter( iSequence, seqdesc.paramindex[iLocalIndex] ); + + if (iPose == -1) + { + flSetting = 0; + index = 0; + return; + } + + const mstudioposeparamdesc_t &Pose = pStudioHdr->pPoseParameter( iPose ); + + float flValue = poseParameter[iPose]; + + if (Pose.loop) + { + float wrap = (Pose.start + Pose.end) / 2.0 + Pose.loop / 2.0; + float shift = Pose.loop - wrap; + + flValue = flValue - Pose.loop * floor((flValue + shift) / Pose.loop); + } + + if (seqdesc.posekeyindex == 0) + { + float flLocalStart = ((float)seqdesc.paramstart[iLocalIndex] - Pose.start) / (Pose.end - Pose.start); + float flLocalEnd = ((float)seqdesc.paramend[iLocalIndex] - Pose.start) / (Pose.end - Pose.start); + + // convert into local range + flSetting = (flValue - flLocalStart) / (flLocalEnd - flLocalStart); + + // clamp. This shouldn't ever need to happen if it's looping. + if (flSetting < 0) + flSetting = 0; + if (flSetting > 1) + flSetting = 1; + + index = 0; + if (seqdesc.groupsize[iLocalIndex] > 2 ) + { + // estimate index + index = (int)(flSetting * (seqdesc.groupsize[iLocalIndex] - 1)); + if (index == seqdesc.groupsize[iLocalIndex] - 1) index = seqdesc.groupsize[iLocalIndex] - 2; + flSetting = flSetting * (seqdesc.groupsize[iLocalIndex] - 1) - index; + } + } + else + { + flValue = flValue * (Pose.end - Pose.start) + Pose.start; + index = 0; + + // FIXME: this needs to be 2D + // FIXME: this shouldn't be a linear search + + while (1) + { + flSetting = (flValue - seqdesc.poseKey( iLocalIndex, index )) / (seqdesc.poseKey( iLocalIndex, index + 1 ) - seqdesc.poseKey( iLocalIndex, index )); + /* + if (index > 0 && flSetting < 0.0) + { + index--; + continue; + } + else + */ + if (index < seqdesc.groupsize[iLocalIndex] - 2 && flSetting > 1.0) + { + index++; + continue; + } + break; + } + + // clamp. + if (flSetting < 0.0f) + flSetting = 0.0f; + if (flSetting > 1.0f) + flSetting = 1.0f; + } +} + +void Studio_CalcBoneToBoneTransform( const CStudioHdr *pStudioHdr, int inputBoneIndex, int outputBoneIndex, matrix3x4_t& matrixOut ) +{ + mstudiobone_t *pbone = pStudioHdr->pBone( inputBoneIndex ); + + matrix3x4_t inputToPose; + MatrixInvert( pbone->poseToBone, inputToPose ); + ConcatTransforms( pStudioHdr->pBone( outputBoneIndex )->poseToBone, inputToPose, matrixOut ); +} + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +//----------------------------------------------------------------------------- +void InitPose( + const CStudioHdr *pStudioHdr, + Vector pos[], + Quaternion q[] + ) +{ + for (int i = 0; i < pStudioHdr->numbones(); i++) + { + mstudiobone_t *pbone = pStudioHdr->pBone( i ); + + pos[i] = pbone->pos; + q[i] = pbone->quat; + } +} + + +inline bool PoseIsAllZeros( + const CStudioHdr *pStudioHdr, + int sequence, + mstudioseqdesc_t &seqdesc, + int i0, + int i1 + ) +{ + int baseanim; + + // remove "zero" positional blends + baseanim = pStudioHdr->iRelativeAnim( sequence, seqdesc.anim(i0 ,i1 ) ); + mstudioanimdesc_t &anim = pStudioHdr->pAnimdesc( baseanim ); + return (anim.flags & STUDIO_ALLZEROS) != 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +//----------------------------------------------------------------------------- +bool CalcPoseSingle( + const CStudioHdr *pStudioHdr, + Vector pos[], + Quaternion q[], + mstudioseqdesc_t &seqdesc, + int sequence, + float cycle, + const float poseParameter[], + int boneMask, + float flTime + ) +{ + ASSERT_NO_REENTRY(); + + static Vector pos2[MAXSTUDIOBONES]; + static Quaternion q2[MAXSTUDIOBONES]; + static Vector pos3[MAXSTUDIOBONES]; + static Quaternion q3[MAXSTUDIOBONES]; + + if (sequence >= pStudioHdr->GetNumSeq()) + { + sequence = 0; + seqdesc = pStudioHdr->pSeqdesc( sequence ); + } + + + int i0 = 0, i1 = 0; + float s0 = 0, s1 = 0; + + Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, sequence, 0, s0, i0 ); + Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, sequence, 1, s1, i1 ); + + + if (seqdesc.flags & STUDIO_REALTIME) + { + float cps = Studio_CPS( pStudioHdr, seqdesc, sequence, poseParameter ); + cycle = flTime * cps; + cycle = cycle - (int)cycle; + } + else if (cycle < 0 || cycle >= 1) + { + if (seqdesc.flags & STUDIO_LOOPING) + { + cycle = cycle - (int)cycle; + if (cycle < 0) cycle += 1; + } + else + { + cycle = max( 0.0, min( cycle, 0.9999 ) ); + } + } + + if (s0 < 0.001) + { + if (s1 < 0.001) + { + if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0, i1 )) + return false; + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1 ), cycle, boneMask ); + } + else if (s1 > 0.999) + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1+1 ), cycle, boneMask ); + } + else + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1 ), cycle, boneMask ); + CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0 , i1+1 ), cycle, boneMask ); + BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); + } + } + else if (s0 > 0.999) + { + if (s1 < 0.001) + { + if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0+1, i1 )) + return false; + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1 ), cycle, boneMask ); + } + else if (s1 > 0.999) + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1+1 ), cycle, boneMask ); + } + else + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1 ), cycle, boneMask ); + CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1, i1+1 ), cycle, boneMask ); + BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); + } + } + else + { + if (s1 < 0.001) + { + if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0+1, i1 )) + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); + ScaleBones( pStudioHdr, q, pos, sequence, 1.0 - s0, boneMask ); + } + else if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0, i1 )) + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1 ,i1 ), cycle, boneMask ); + ScaleBones( pStudioHdr, q, pos, sequence, s0, boneMask ); + } + else + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); + CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1 ), cycle, boneMask ); + + BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask ); + } + } + else if (s1 > 0.999) + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1+1 ), cycle, boneMask ); + CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1+1 ), cycle, boneMask ); + BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask ); + } + else + { + CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); + CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1 ), cycle, boneMask ); + BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask ); + + CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0 , i1+1), cycle, boneMask ); + CalcAnimation( pStudioHdr, pos3, q3, seqdesc, sequence, seqdesc.anim( i0+1, i1+1), cycle, boneMask ); + BlendBones( pStudioHdr, q2, pos2, seqdesc, sequence, q3, pos3, s0, boneMask ); + + BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); + } + } + + return true; +} + + + + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +// adds autolayers, runs local ik rukes +//----------------------------------------------------------------------------- +void AddSequenceLayers( + const CStudioHdr *pStudioHdr, + CIKContext *pIKContext, + Vector pos[], + Quaternion q[], + mstudioseqdesc_t &seqdesc, + int sequence, + float cycle, + const float poseParameter[], + int boneMask, + float flWeight, + float flTime + ) +{ + for (int i = 0; i < seqdesc.numautolayers; i++) + { + mstudioautolayer_t *pLayer = seqdesc.pAutolayer( i ); + + if (pLayer->flags & STUDIO_AL_LOCAL) + continue; + + float layerCycle = cycle; + float layerWeight = flWeight; + + if (pLayer->start != pLayer->end) + { + float s = 1.0; + float index; + + if (!(pLayer->flags & STUDIO_AL_POSE)) + { + index = cycle; + } + else + { + int iPose = pStudioHdr->GetSharedPoseParameter( pLayer->iSequence, pLayer->iPose ); + if (iPose != -1) + { + const mstudioposeparamdesc_t &Pose = pStudioHdr->pPoseParameter( iPose ); + index = poseParameter[ iPose ] * (Pose.end - Pose.start) + Pose.start; + } + else + { + index = 0; + } + } + + if (index < pLayer->start) + continue; + if (index >= pLayer->end) + continue; + + if (index < pLayer->peak && pLayer->start != pLayer->peak) + { + s = (index - pLayer->start) / (pLayer->peak - pLayer->start); + } + else if (index > pLayer->tail && pLayer->end != pLayer->tail) + { + s = (pLayer->end - index) / (pLayer->end - pLayer->tail); + } + + if (pLayer->flags & STUDIO_AL_SPLINE) + { + s = SimpleSpline( s ); + } + + if ((pLayer->flags & STUDIO_AL_XFADE) && (index > pLayer->tail)) + { + layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight ); + } + else if (pLayer->flags & STUDIO_AL_NOBLEND) + { + layerWeight = s; + } + else + { + layerWeight = flWeight * s; + } + + if (!(pLayer->flags & STUDIO_AL_POSE)) + { + layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start); + } + } + + int iSequence = pStudioHdr->iRelativeSeq( sequence, pLayer->iSequence ); + AccumulatePose( pStudioHdr, pIKContext, pos, q, iSequence, layerCycle, poseParameter, boneMask, layerWeight, flTime ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +// adds autolayers, runs local ik rukes +//----------------------------------------------------------------------------- +void AddLocalLayers( + const CStudioHdr *pStudioHdr, + CIKContext *pIKContext, + Vector pos[], + Quaternion q[], + mstudioseqdesc_t &seqdesc, + int sequence, + float cycle, + const float poseParameter[], + int boneMask, + float flWeight, + float flTime + ) +{ + if (!(seqdesc.flags & STUDIO_LOCAL)) + { + return; + } + + for (int i = 0; i < seqdesc.numautolayers; i++) + { + mstudioautolayer_t *pLayer = seqdesc.pAutolayer( i ); + + if (!(pLayer->flags & STUDIO_AL_LOCAL)) + continue; + + float layerCycle = cycle; + float layerWeight = flWeight; + + if (pLayer->start != pLayer->end) + { + float s = 1.0; + + if (cycle < pLayer->start) + continue; + if (cycle >= pLayer->end) + continue; + + if (cycle < pLayer->peak && pLayer->start != pLayer->peak) + { + s = (cycle - pLayer->start) / (pLayer->peak - pLayer->start); + } + else if (cycle > pLayer->tail && pLayer->end != pLayer->tail) + { + s = (pLayer->end - cycle) / (pLayer->end - pLayer->tail); + } + + if (pLayer->flags & STUDIO_AL_SPLINE) + { + s = SimpleSpline( s ); + } + + if ((pLayer->flags & STUDIO_AL_XFADE) && (cycle > pLayer->tail)) + { + layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight ); + } + else if (pLayer->flags & STUDIO_AL_NOBLEND) + { + layerWeight = s; + } + else + { + layerWeight = flWeight * s; + } + + layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start); + } + + int iSequence = pStudioHdr->iRelativeSeq( sequence, pLayer->iSequence ); + AccumulatePose( pStudioHdr, pIKContext, pos, q, iSequence, layerCycle, poseParameter, boneMask, layerWeight, flTime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +// adds autolayers, runs local ik rukes +//----------------------------------------------------------------------------- +void CalcPose( + const CStudioHdr *pStudioHdr, + CIKContext *pIKContext, + Vector pos[], + Quaternion q[], + int sequence, + float cycle, + const float poseParameter[], + int boneMask, + float flWeight, + float flTime + ) +{ + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequence ); + + Assert( flWeight >= 0.0f && flWeight <= 1.0f ); + // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up + flWeight = clamp( flWeight, 0.0f, 1.0f ); + + // add any IK locks to prevent numautolayers from moving extremities + CIKContext seq_ik; + if (seqdesc.numiklocks) + { + seq_ik.Init( pStudioHdr, vec3_angle, vec3_origin, 0.0, 0, boneMask ); // local space relative so absolute position doesn't mater + seq_ik.AddSequenceLocks( seqdesc, pos, q ); + } + + CalcPoseSingle( pStudioHdr, pos, q, seqdesc, sequence, cycle, poseParameter, boneMask, flTime ); + + if ( pIKContext ) + { + pIKContext->AddDependencies( seqdesc, sequence, cycle, poseParameter, flWeight ); + } + + AddSequenceLayers( pStudioHdr, pIKContext, pos, q, seqdesc, sequence, cycle, poseParameter, boneMask, flWeight, flTime ); + + if (seqdesc.numiklocks) + { + seq_ik.SolveSequenceLocks( seqdesc, pos, q ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: accumulate a pose for a single sequence on top of existing animation +// adds autolayers, runs local ik rukes +//----------------------------------------------------------------------------- +void AccumulatePose( + const CStudioHdr *pStudioHdr, + CIKContext *pIKContext, + Vector pos[], + Quaternion q[], + int sequence, + float cycle, + const float poseParameter[], + int boneMask, + float flWeight, + float flTime + ) +{ + Vector pos2[MAXSTUDIOBONES]; + Quaternion q2[MAXSTUDIOBONES]; + + Assert( flWeight >= 0.0f && flWeight <= 1.0f ); + // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up + flWeight = clamp( flWeight, 0.0f, 1.0f ); + + if ( sequence < 0 ) + return; + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequence ); + + // add any IK locks to prevent extremities from moving + CIKContext seq_ik; + if (seqdesc.numiklocks) + { + seq_ik.Init( pStudioHdr, vec3_angle, vec3_origin, 0.0, 0, boneMask ); // local space relative so absolute position doesn't mater + seq_ik.AddSequenceLocks( seqdesc, pos, q ); + } + + if (seqdesc.flags & STUDIO_LOCAL) + { + InitPose( pStudioHdr, pos2, q2 ); + } + + if (CalcPoseSingle( pStudioHdr, pos2, q2, seqdesc, sequence, cycle, poseParameter, boneMask, flTime )) + { + // this weight is wrong, the IK rules won't composite at the correct intensity + AddLocalLayers( pStudioHdr, pIKContext, pos2, q2, seqdesc, sequence, cycle, poseParameter, boneMask, 1.0, flTime ); + SlerpBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, flWeight, boneMask ); + } + + if ( pIKContext ) + { + pIKContext->AddDependencies( seqdesc, sequence, cycle, poseParameter, flWeight ); + } + + AddSequenceLayers( pStudioHdr, pIKContext, pos, q, seqdesc, sequence, cycle, poseParameter, boneMask, flWeight, flTime ); + + if (seqdesc.numiklocks) + { + seq_ik.SolveSequenceLocks( seqdesc, pos, q ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1. +// 0 returns q1, pos1. 1 returns q2, pos2 +//----------------------------------------------------------------------------- +void CalcBoneAdj( + const CStudioHdr *pStudioHdr, + Vector pos[], + Quaternion q[], + const float controllers[], + int boneMask + ) +{ + int i, j, k; + float value; + mstudiobonecontroller_t *pbonecontroller; + Vector p0; + RadianEuler a0; + Quaternion q0; + + for (j = 0; j < pStudioHdr->numbonecontrollers(); j++) + { + pbonecontroller = pStudioHdr->pBonecontroller( j ); + k = pbonecontroller->bone; + + if (pStudioHdr->pBone( k )->flags & boneMask) + { + i = pbonecontroller->inputfield; + value = controllers[i]; + if (value < 0) value = 0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller->start + value * pbonecontroller->end; + + switch(pbonecontroller->type & STUDIO_TYPES) + { + case STUDIO_XR: + a0.Init( value * (M_PI / 180.0), 0, 0 ); + AngleQuaternion( a0, q0 ); + QuaternionSM( 1.0, q0, q[k], q[k] ); + break; + case STUDIO_YR: + a0.Init( 0, value * (M_PI / 180.0), 0 ); + AngleQuaternion( a0, q0 ); + QuaternionSM( 1.0, q0, q[k], q[k] ); + break; + case STUDIO_ZR: + a0.Init( 0, 0, value * (M_PI / 180.0) ); + AngleQuaternion( a0, q0 ); + QuaternionSM( 1.0, q0, q[k], q[k] ); + break; + case STUDIO_X: + pos[k].x += value; + break; + case STUDIO_Y: + pos[k].y += value; + break; + case STUDIO_Z: + pos[k].z += value; + break; + } + } + } +} + + +void CalcBoneDerivatives( Vector &velocity, AngularImpulse &angVel, const matrix3x4_t &prev, const matrix3x4_t ¤t, float dt ) +{ + float scale = 1.0; + if ( dt > 0 ) + { + scale = 1.0 / dt; + } + + Vector endPosition, startPosition, deltaAxis; + QAngle endAngles, startAngles; + float deltaAngle; + + MatrixAngles( prev, startAngles, startPosition ); + MatrixAngles( current, endAngles, endPosition ); + + velocity.x = (endPosition.x - startPosition.x) * scale; + velocity.y = (endPosition.y - startPosition.y) * scale; + velocity.z = (endPosition.z - startPosition.z) * scale; + RotationDeltaAxisAngle( startAngles, endAngles, deltaAxis, deltaAngle ); + VectorScale( deltaAxis, (deltaAngle * scale), angVel ); +} + +void CalcBoneVelocityFromDerivative( const QAngle &vecAngles, Vector &velocity, AngularImpulse &angVel, const matrix3x4_t ¤t ) +{ + Vector vecLocalVelocity; + AngularImpulse LocalAngVel; + Quaternion q; + float angle; + MatrixAngles( current, q, vecLocalVelocity ); + QuaternionAxisAngle( q, LocalAngVel, angle ); + LocalAngVel *= angle; + + matrix3x4_t matAngles; + AngleMatrix( vecAngles, matAngles ); + VectorTransform( vecLocalVelocity, matAngles, velocity ); + VectorTransform( LocalAngVel, matAngles, angVel ); +} + + + + +class ik +{ +public: +//-------- SOLVE TWO LINK INVERSE KINEMATICS ------------- +// Author: Ken Perlin +// +// Given a two link joint from [0,0,0] to end effector position P, +// let link lengths be a and b, and let norm |P| = c. Clearly a+b <= c. +// +// Problem: find a "knee" position Q such that |Q| = a and |P-Q| = b. +// +// In the case of a point on the x axis R = [c,0,0], there is a +// closed form solution S = [d,e,0], where |S| = a and |R-S| = b: +// +// d2+e2 = a2 -- because |S| = a +// (c-d)2+e2 = b2 -- because |R-S| = b +// +// c2-2cd+d2+e2 = b2 -- combine the two equations +// c2-2cd = b2 - a2 +// c-2d = (b2-a2)/c +// d - c/2 = (a2-b2)/c / 2 +// +// d = (c + (a2-b2/c) / 2 -- to solve for d and e. +// e = sqrt(a2-d2) + + static float findD(float a, float b, float c) { + return (c + (a*a-b*b)/c) / 2; + } + static float findE(float a, float d) { return sqrt(a*a-d*d); } + +// This leads to a solution to the more general problem: +// +// (1) R = Mfwd(P) -- rotate P onto the x axis +// (2) Solve for S +// (3) Q = Minv(S) -- rotate back again + + static float Mfwd[3][3]; + static float Minv[3][3]; + + static bool solve(float A, float B, float const P[], float const D[], float Q[]) { + float R[3]; + defineM(P,D); + rot(Minv,P,R); + float r = length(R); + float d = findD(A,B,r); + float e = findE(A,d); + float S[3] = {d,e,0}; + rot(Mfwd,S,Q); + return d > (r - B) && d < A; + } + +// If "knee" position Q needs to be as close as possible to some point D, +// then choose M such that M(D) is in the y>0 half of the z=0 plane. +// +// Given that constraint, define the forward and inverse of M as follows: + + static void defineM(float const P[], float const D[]) { + float *X = Minv[0], *Y = Minv[1], *Z = Minv[2]; + +// Minv defines a coordinate system whose x axis contains P, so X = unit(P). + int i; + for (i = 0 ; i < 3 ; i++) + X[i] = P[i]; + normalize(X); + +// Its y axis is perpendicular to P, so Y = unit( E - X(E·X) ). + + float dDOTx = dot(D,X); + for (i = 0 ; i < 3 ; i++) + Y[i] = D[i] - dDOTx * X[i]; + normalize(Y); + +// Its z axis is perpendicular to both X and Y, so Z = X×Y. + + cross(X,Y,Z); + +// Mfwd = (Minv)T, since transposing inverts a rotation matrix. + + for (i = 0 ; i < 3 ; i++) { + Mfwd[i][0] = Minv[0][i]; + Mfwd[i][1] = Minv[1][i]; + Mfwd[i][2] = Minv[2][i]; + } + } + +//------------ GENERAL VECTOR MATH SUPPORT ----------- + + static float dot(float const a[], float const b[]) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; } + + static float length(float const v[]) { return sqrt( dot(v,v) ); } + + static void normalize(float v[]) { + float norm = length(v); + for (int i = 0 ; i < 3 ; i++) + v[i] /= norm; + } + + static void cross(float const a[], float const b[], float c[]) { + c[0] = a[1] * b[2] - a[2] * b[1]; + c[1] = a[2] * b[0] - a[0] * b[2]; + c[2] = a[0] * b[1] - a[1] * b[0]; + } + + static void rot(float const M[3][3], float const src[], float dst[]) { + for (int i = 0 ; i < 3 ; i++) + dst[i] = dot(M[i],src); + } +}; + +float ik::Mfwd[3][3]; +float ik::Minv[3][3]; + + + +//----------------------------------------------------------------------------- +// Purpose: visual debugging code +//----------------------------------------------------------------------------- +#if 1 +inline void debugLine(const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration) { }; +#else +extern void drawLine( const Vector &p1, const Vector &p2, int r = 0, int g = 0, int b = 1, bool noDepthTest = true, float duration = 0.1 ); +void debugLine(const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration) +{ + drawLine( origin, dest, r, g, b, noDepthTest, duration ); +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: for a 2 bone chain, find the IK solution and reset the matrices +//----------------------------------------------------------------------------- +bool Studio_SolveIK( mstudioikchain_t *pikchain, Vector &targetFoot, matrix3x4_t *pBoneToWorld ) +{ + if (pikchain->pLink(0)->kneeDir.LengthSqr() > 0.0) + { + Vector targetKneeDir, targetKneePos; + // FIXME: knee length should be as long as the legs + Vector tmp = pikchain->pLink( 0 )->kneeDir; + VectorRotate( tmp, pBoneToWorld[ pikchain->pLink( 0 )->bone ], targetKneeDir ); + MatrixPosition( pBoneToWorld[ pikchain->pLink( 1 )->bone ], targetKneePos ); + return Studio_SolveIK( pikchain->pLink( 0 )->bone, pikchain->pLink( 1 )->bone, pikchain->pLink( 2 )->bone, targetFoot, targetKneePos, targetKneeDir, pBoneToWorld ); + } + else + { + return Studio_SolveIK( pikchain->pLink( 0 )->bone, pikchain->pLink( 1 )->bone, pikchain->pLink( 2 )->bone, targetFoot, pBoneToWorld ); + } +} + + +#define KNEEMAX_EPSILON 0.9998 // (0.9998 is about 1 degree) + +//----------------------------------------------------------------------------- +// Purpose: Solve Knee position for a known hip and foot location, but no specific knee direction preference +//----------------------------------------------------------------------------- + +bool Studio_SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, matrix3x4_t *pBoneToWorld ) +{ + Vector worldFoot, worldKnee, worldThigh; + + MatrixPosition( pBoneToWorld[ iThigh ], worldThigh ); + MatrixPosition( pBoneToWorld[ iKnee ], worldKnee ); + MatrixPosition( pBoneToWorld[ iFoot ], worldFoot ); + + //debugLine( worldThigh, worldKnee, 0, 0, 255, true, 0 ); + //debugLine( worldKnee, worldFoot, 0, 0, 255, true, 0 ); + + Vector ikFoot, ikKnee; + + ikFoot = targetFoot - worldThigh; + ikKnee = worldKnee - worldThigh; + + float l1 = (worldKnee-worldThigh).Length(); + float l2 = (worldFoot-worldKnee).Length(); + float l3 = (worldFoot-worldThigh).Length(); + + // leg too straight to figure out knee? + if (l3 > (l1 + l2) * KNEEMAX_EPSILON) + { + return false; + } + + Vector ikHalf = (worldFoot-worldThigh) * (l1 / l3); + + // FIXME: what to do when the knee completely straight? + Vector ikKneeDir = ikKnee - ikHalf; + VectorNormalize( ikKneeDir ); + + return Studio_SolveIK( iThigh, iKnee, iFoot, targetFoot, worldKnee, ikKneeDir, pBoneToWorld ); +} + +//----------------------------------------------------------------------------- +// Purpose: Realign the matrix so that its X axis points along the desired axis. +//----------------------------------------------------------------------------- +void Studio_AlignIKMatrix( matrix3x4_t &mMat, const Vector &vAlignTo ) +{ + Vector tmp1, tmp2, tmp3; + + // Column 0 (X) becomes the vector. + tmp1 = vAlignTo; + VectorNormalize( tmp1 ); + MatrixSetColumn( tmp1, 0, mMat ); + + // Column 1 (Y) is the cross of the vector and column 2 (Z). + MatrixGetColumn( mMat, 2, tmp3 ); + tmp2 = tmp3.Cross( tmp1 ); + VectorNormalize( tmp2 ); + // FIXME: check for X being too near to Z + MatrixSetColumn( tmp2, 1, mMat ); + + // Column 2 (Z) is the cross of columns 0 (X) and 1 (Y). + tmp3 = tmp1.Cross( tmp2 ); + MatrixSetColumn( tmp3, 2, mMat ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Solve Knee position for a known hip and foot location, and a known knee direction +//----------------------------------------------------------------------------- + +bool Studio_SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, Vector &targetKneePos, Vector &targetKneeDir, matrix3x4_t *pBoneToWorld ) +{ + Vector worldFoot, worldKnee, worldThigh; + + MatrixPosition( pBoneToWorld[ iThigh ], worldThigh ); + MatrixPosition( pBoneToWorld[ iKnee ], worldKnee ); + MatrixPosition( pBoneToWorld[ iFoot ], worldFoot ); + + //debugLine( worldThigh, worldKnee, 0, 0, 255, true, 0 ); + //debugLine( worldThigh, worldThigh + targetKneeDir, 0, 0, 255, true, 0 ); + // debugLine( worldKnee, targetKnee, 0, 0, 255, true, 0 ); + + Vector ikFoot, ikTargetKnee, ikKnee; + + ikFoot = targetFoot - worldThigh; + ikKnee = targetKneePos - worldThigh; + + float l1 = (worldKnee-worldThigh).Length(); + float l2 = (worldFoot-worldKnee).Length(); + + // exaggerate knee targets for legs that are nearly straight + // FIXME: should be configurable, and the ikKnee should be from the original animation, not modifed + float d = (targetFoot-worldThigh).Length() - min( l1, l2 ); + d = max( l1 + l2, d ); + // FIXME: too short knee directions cause trouble + d = d * 100; + + ikTargetKnee = ikKnee + targetKneeDir * d; + + // debugLine( worldKnee, worldThigh + ikTargetKnee, 0, 0, 255, true, 0 ); + + int color[3] = { 0, 255, 0 }; + + // too far away? (0.9998 is about 1 degree) + if (ikFoot.Length() > (l1 + l2) * KNEEMAX_EPSILON) + { + VectorNormalize( ikFoot ); + VectorScale( ikFoot, (l1 + l2) * KNEEMAX_EPSILON, ikFoot ); + color[0] = 255; color[1] = 0; color[2] = 0; + } + + // too close? + if (ikFoot.Length() < fabs(l1 - l2) * 1.05) + { + VectorNormalize( ikFoot ); + VectorScale( ikFoot, fabs(l1 - l2) * 1.05, ikFoot ); + } + + if (ik::solve( l1, l2, ikFoot.Base(), ikTargetKnee.Base(), ikKnee.Base() )) + { + matrix3x4_t& mWorldThigh = pBoneToWorld[ iThigh ]; + matrix3x4_t& mWorldKnee = pBoneToWorld[ iKnee ]; + matrix3x4_t& mWorldFoot = pBoneToWorld[ iFoot ]; + + //debugLine( worldThigh, ikKnee + worldThigh, 255, 0, 0, true, 0 ); + //debugLine( ikKnee + worldThigh, ikFoot + worldThigh, 255, 0, 0, true,0 ); + + // debugLine( worldThigh, ikKnee + worldThigh, color[0], color[1], color[2], true, 0 ); + // debugLine( ikKnee + worldThigh, ikFoot + worldThigh, color[0], color[1], color[2], true,0 ); + + + // build transformation matrix for thigh + Studio_AlignIKMatrix( mWorldThigh, ikKnee ); + Studio_AlignIKMatrix( mWorldKnee, ikFoot - ikKnee ); + + + mWorldKnee[0][3] = ikKnee.x + worldThigh.x; + mWorldKnee[1][3] = ikKnee.y + worldThigh.y; + mWorldKnee[2][3] = ikKnee.z + worldThigh.z; + + mWorldFoot[0][3] = ikFoot.x + worldThigh.x; + mWorldFoot[1][3] = ikFoot.y + worldThigh.y; + mWorldFoot[2][3] = ikFoot.z + worldThigh.z; + + return true; + } + else + { + /* + debugLine( worldThigh, worldThigh + ikKnee, 255, 0, 0, true, 0 ); + debugLine( worldThigh + ikKnee, worldThigh + ikFoot, 255, 0, 0, true, 0 ); + debugLine( worldThigh + ikFoot, worldThigh, 255, 0, 0, true, 0 ); + debugLine( worldThigh + ikKnee, worldThigh + ikTargetKnee, 255, 0, 0, true, 0 ); + */ + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +float Studio_IKRuleWeight( mstudioikrule_t &ikRule, const mstudioanimdesc_t *panim, float flCycle, int &iFrame, float &fraq ) +{ + if (ikRule.end > 1.0f && flCycle < ikRule.start) + { + flCycle = flCycle + 1.0f; + } + + float value = 0.0f; + fraq = (panim->numframes - 1) * (flCycle - ikRule.start) + ikRule.iStart; + iFrame = (int)fraq; + fraq = fraq - iFrame; + + if (flCycle < ikRule.start) + { + iFrame = ikRule.iStart; + fraq = 0.0f; + return 0.0f; + } + else if (flCycle < ikRule.peak ) + { + value = (flCycle - ikRule.start) / (ikRule.peak - ikRule.start); + } + else if (flCycle < ikRule.tail ) + { + return 1.0f; + } + else if (flCycle < ikRule.end ) + { + value = 1.0f - ((flCycle - ikRule.tail) / (ikRule.end - ikRule.tail)); + } + else + { + fraq = (panim->numframes - 1) * (ikRule.end - ikRule.start) + ikRule.iStart; + iFrame = (int)fraq; + fraq = fraq - iFrame; + } + return SimpleSpline( value ); +} + + +float Studio_IKRuleWeight( ikcontextikrule_t &ikRule, float flCycle ) +{ + if (ikRule.end > 1.0f && flCycle < ikRule.start) + { + flCycle = flCycle + 1.0f; + } + + float value = 0.0f; + if (flCycle < ikRule.start) + { + return 0.0f; + } + else if (flCycle < ikRule.peak ) + { + value = (flCycle - ikRule.start) / (ikRule.peak - ikRule.start); + } + else if (flCycle < ikRule.tail ) + { + return 1.0f; + } + else if (flCycle < ikRule.end ) + { + value = 1.0f - ((flCycle - ikRule.tail) / (ikRule.end - ikRule.tail)); + } + return 3.0f * value * value - 2.0f * value * value * value; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +bool Studio_IKShouldLatch( ikcontextikrule_t &ikRule, float flCycle ) +{ + if (ikRule.end > 1.0f && flCycle < ikRule.start) + { + flCycle = flCycle + 1.0f; + } + + if (flCycle < ikRule.peak ) + { + return false; + } + else if (flCycle < ikRule.end ) + { + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +float Studio_IKTail( ikcontextikrule_t &ikRule, float flCycle ) +{ + if (ikRule.end > 1.0f && flCycle < ikRule.start) + { + flCycle = flCycle + 1.0f; + } + + if (flCycle <= ikRule.tail ) + { + return 0.0f; + } + else if (flCycle < ikRule.end ) + { + return ((flCycle - ikRule.tail) / (ikRule.end - ikRule.tail)); + } + return 0.0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +bool Studio_IKAnimationError( const CStudioHdr *pStudioHdr, mstudioikrule_t *pRule, const mstudioanimdesc_t *panim, float flCycle, Vector &pos, Quaternion &q, float &flWeight ) +{ + float fraq; + int iFrame; + + flWeight = Studio_IKRuleWeight( *pRule, panim, flCycle, iFrame, fraq ); + Assert( fraq >= 0.0 && fraq < 1.0 ); + Assert( flWeight >= 0.0f && flWeight <= 1.0f ); + + // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up + flWeight = clamp( flWeight, 0.0f, 1.0f ); + + if (pRule->type != IK_GROUND && flWeight < 0.0001) + return false; + + mstudioikerror_t *pError = pRule->pError( iFrame ); + if (pError != NULL) + { + if (fraq < 0.001) + { + q = pError[0].q; + pos = pError[0].pos; + } + else + { + QuaternionBlend( pError[0].q, pError[1].q, fraq, q ); + pos = pError[0].pos * (1.0f - fraq) + pError[1].pos * fraq; + } + return true; + } + + mstudiocompressedikerror_t *pCompressed = pRule->pCompressedError(); + if (pCompressed != NULL) + { + iFrame = iFrame - pRule->iStart; + + Vector p1, p2; + ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 0 ), pCompressed->scale[0], p1.x, p2.x ); + ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 1 ), pCompressed->scale[1], p1.y, p2.y ); + ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 2 ), pCompressed->scale[2], p1.z, p2.z ); + pos = p1 * (1 - fraq) + p2 * fraq; + + Quaternion q1, q2; + RadianEuler angle1, angle2; + ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 3 ), pCompressed->scale[3], angle1.x, angle2.x ); + ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 4 ), pCompressed->scale[4], angle1.y, angle2.y ); + ExtractAnimValue( iFrame, pCompressed->pAnimvalue( 5 ), pCompressed->scale[5], angle1.z, angle2.z ); + + if (angle1.x != angle2.x || angle1.y != angle2.y || angle1.z != angle2.z) + { + AngleQuaternion( angle1, q1 ); + AngleQuaternion( angle2, q2 ); + QuaternionBlend( q1, q2, fraq, q ); + } + else + { + AngleQuaternion( angle1, q ); + } + return true; + } + // no data, disable IK rule + Assert( 0 ); + flWeight = 0.0f; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: For a specific sequence:rule, find where it starts, stops, and what +// the estimated offset from the connection point is. +// return true if the rule is within bounds. +//----------------------------------------------------------------------------- + +bool Studio_IKSequenceError( const CStudioHdr *pStudioHdr, mstudioseqdesc_t &seqdesc, int iSequence, float flCycle, int iRule, const float poseParameter[], mstudioanimdesc_t *panim[4], float weight[4], ikcontextikrule_t &ikRule ) +{ + int i; + + memset( &ikRule, 0, sizeof(ikRule) ); + ikRule.start = ikRule.peak = ikRule.tail = ikRule.end = 0; + + + mstudioikrule_t *prevRule = NULL; + + // find overall influence + for (i = 0; i < 4; i++) + { + if (weight[i]) + { + if (iRule >= panim[i]->numikrules || panim[i]->numikrules != panim[0]->numikrules) + { + Assert( 0 ); + return false; + } + + mstudioikrule_t *pRule = panim[i]->pIKRule( iRule ); + if (pRule == NULL) + return false; + + float dt = 0.0; + if (prevRule != NULL) + { + if (pRule->start - prevRule->start > 0.5) + { + dt = -1.0; + } + else if (pRule->start - prevRule->start < -0.5) + { + dt = 1.0; + } + } + else + { + prevRule = pRule; + } + + ikRule.start += (pRule->start + dt) * weight[i]; + ikRule.peak += (pRule->peak + dt) * weight[i]; + ikRule.tail += (pRule->tail + dt) * weight[i]; + ikRule.end += (pRule->end + dt) * weight[i]; + } + } + if (ikRule.start > 1.0) + { + ikRule.start -= 1.0; + ikRule.peak -= 1.0; + ikRule.tail -= 1.0; + ikRule.end -= 1.0; + } + else if (ikRule.start < 0.0) + { + ikRule.start += 1.0; + ikRule.peak += 1.0; + ikRule.tail += 1.0; + ikRule.end += 1.0; + } + + ikRule.flWeight = Studio_IKRuleWeight( ikRule, flCycle ); + if (ikRule.flWeight <= 0.001f) + { + // go ahead and allow IK_GROUND rules a virtual looping section + if (panim[0]->pIKRule( iRule )->type == IK_GROUND && ikRule.end - ikRule.start > 0.75 ) + { + ikRule.flWeight = 0.001; + flCycle = ikRule.end - 0.001; + } + else + { + return false; + } + } + + Assert( ikRule.flWeight > 0.0f ); + + ikRule.pos.Init(); + ikRule.q.Init(); + + // FIXME: add "latched" value + ikRule.commit = Studio_IKShouldLatch( ikRule, flCycle ); + + // find target error + float total = 0.0f; + for (i = 0; i < 4; i++) + { + if (weight[i]) + { + Vector pos1; + Quaternion q1; + float w; + + mstudioikrule_t *pRule = panim[i]->pIKRule( iRule ); + if (pRule == NULL) + return false; + + ikRule.chain = pRule->chain; // FIXME: this is anim local + ikRule.bone = pRule->bone; // FIXME: this is anim local + ikRule.type = pRule->type; + ikRule.slot = pRule->slot; + + ikRule.height += pRule->height * weight[i]; + ikRule.floor += pRule->floor * weight[i]; + ikRule.radius += pRule->radius * weight[i]; + + // keep track of tail condition + ikRule.release += Studio_IKTail( ikRule, flCycle ) * weight[i]; + + // only check rules with error values + switch( ikRule.type ) + { + case IK_SELF: + case IK_WORLD: + case IK_GROUND: + case IK_ATTACHMENT: + { + int bResult = Studio_IKAnimationError( pStudioHdr, pRule, panim[i], flCycle, pos1, q1, w ); + + if (bResult) + { + ikRule.pos = ikRule.pos + pos1 * weight[i]; + QuaternionAccumulate( ikRule.q, weight[i], q1, ikRule.q ); + total += weight[i]; + } + } + break; + default: + total += weight[i]; + break; + } + + ikRule.latched = Studio_IKShouldLatch( ikRule, flCycle ) * ikRule.flWeight; + + if (ikRule.type == IK_ATTACHMENT) + { + ikRule.szLabel = pRule->pszAttachment(); + } + } + } + + if (total <= 0.0001f) + { + return false; + } + + if (total < 0.999f) + { + VectorScale( ikRule.pos, 1.0f / total, ikRule.pos ); + QuaternionScale( ikRule.q, 1.0f / total, ikRule.q ); + } + + if (ikRule.type == IK_SELF && ikRule.bone != -1) + { + // FIXME: this is anim local, not seq local! + ikRule.bone = pStudioHdr->RemapSeqBone( iSequence, ikRule.bone ); + if (ikRule.bone == -1) + return false; + } + + QuaternionNormalize( ikRule.q ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + + +CIKContext::CIKContext() +{ + m_target.EnsureCapacity( 12 ); // FIXME: this sucks, shouldn't it be grown? + m_iFramecounter = -1; + m_pStudioHdr = NULL; + m_flTime = -1.0f; + m_target.SetSize( 0 ); +} + + +void CIKContext::Init( const CStudioHdr *pStudioHdr, const QAngle &angles, const Vector &pos, float flTime, int iFramecounter, int boneMask ) +{ + m_pStudioHdr = pStudioHdr; + m_ikChainRule.RemoveAll(); // m_numikrules = 0; + if (pStudioHdr->numikchains()) + { + m_ikChainRule.SetSize( pStudioHdr->numikchains() ); + + // FIXME: Brutal hackery to prevent a crash + if (m_target.Count() == 0) + { + m_target.SetSize(12); + memset( m_target.Base(), 0, sizeof(m_target[0])*m_target.Count() ); + ClearTargets(); + } + + } + else + { + m_target.SetSize( 0 ); + } + AngleMatrix( angles, pos, m_rootxform ); + m_iFramecounter = iFramecounter; + m_flTime = flTime; + m_boneMask = boneMask; +} + +void CIKContext::AddDependencies( mstudioseqdesc_t &seqdesc, int iSequence, float flCycle, const float poseParameters[], float flWeight ) +{ + int i; + + if (seqdesc.numikrules == 0) + return; + + ikcontextikrule_t ikrule; + + Assert( flWeight >= 0.0f && flWeight <= 1.0f ); + // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up + flWeight = clamp( flWeight, 0.0f, 1.0f ); + + // unify this + if (seqdesc.flags & STUDIO_REALTIME) + { + float cps = Studio_CPS( m_pStudioHdr, seqdesc, iSequence, poseParameters ); + flCycle = m_flTime * cps; + flCycle = flCycle - (int)flCycle; + } + else if (flCycle < 0 || flCycle >= 1) + { + if (seqdesc.flags & STUDIO_LOOPING) + { + flCycle = flCycle - (int)flCycle; + if (flCycle < 0) flCycle += 1; + } + else + { + flCycle = max( 0.0, min( flCycle, 0.9999 ) ); + } + } + + mstudioanimdesc_t *panim[4]; + float weight[4]; + + Studio_SeqAnims( m_pStudioHdr, seqdesc, iSequence, poseParameters, panim, weight ); + + // FIXME: add proper number of rules!!! + for (i = 0; i < seqdesc.numikrules; i++) + { + if ( !Studio_IKSequenceError( m_pStudioHdr, seqdesc, iSequence, flCycle, i, poseParameters, panim, weight, ikrule ) ) + continue; + + // don't add rule if the bone isn't going to be calculated + int bone = m_pStudioHdr->pIKChain( ikrule.chain )->pLink( 2 )->bone; + if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) + continue; + + // or if its relative bone isn't going to be calculated + if ( ikrule.bone >= 0 && !(m_pStudioHdr->pBone( ikrule.bone )->flags & m_boneMask)) + continue; + + // FIXME: Brutal hackery to prevent a crash + if (m_target.Count() == 0) + { + m_target.SetSize(12); + memset( m_target.Base(), 0, sizeof(m_target[0])*m_target.Count() ); + ClearTargets(); + } + + ikrule.flRuleWeight = flWeight; + + if (ikrule.flRuleWeight * ikrule.flWeight > 0.999) + { + if ( ikrule.type != IK_UNLATCH) + { + // clear out chain if rule is 100% + m_ikChainRule.Element( ikrule.chain ).RemoveAll( ); + if ( ikrule.type == IK_RELEASE) + { + continue; + } + } + } + + int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); + m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void CIKContext::AddAutoplayLocks( Vector pos[], Quaternion q[] ) +{ + // skip all array access if no autoplay locks. + if (m_pStudioHdr->GetNumIKAutoplayLocks() == 0) + { + return; + } + + static matrix3x4_t boneToWorld[MAXSTUDIOBONES]; + CBoneBitList boneComputed; + + int ikOffset = m_ikLock.AddMultipleToTail( m_pStudioHdr->GetNumIKAutoplayLocks() ); + memset( &m_ikLock[ikOffset], 0, sizeof(ikcontextikrule_t)*m_pStudioHdr->GetNumIKAutoplayLocks() ); + + for (int i = 0; i < m_pStudioHdr->GetNumIKAutoplayLocks(); i++) + { + const mstudioiklock_t &lock = m_pStudioHdr->pIKAutoplayLock( i ); + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( lock.chain ); + int bone = pchain->pLink( 2 )->bone; + + // don't bother with iklock if the bone isn't going to be calculated + if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); + + ikcontextikrule_t &ikrule = m_ikLock[ i + ikOffset ]; + + ikrule.chain = lock.chain; + ikrule.slot = i; + ikrule.type = IK_WORLD; + + MatrixAngles( boneToWorld[bone], ikrule.q, ikrule.pos ); + + // save off current knee direction + if (pchain->pLink(0)->kneeDir.LengthSqr() > 0.0) + { + Vector tmp = pchain->pLink( 0 )->kneeDir; + VectorRotate( pchain->pLink( 0 )->kneeDir, boneToWorld[ pchain->pLink( 0 )->bone ], ikrule.kneeDir ); + MatrixPosition( boneToWorld[ pchain->pLink( 1 )->bone ], ikrule.kneePos ); + } + else + { + ikrule.kneeDir.Init( ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void CIKContext::AddSequenceLocks( mstudioseqdesc_t &seqdesc, Vector pos[], Quaternion q[] ) +{ + if ( seqdesc.numiklocks == 0 ) + { + return; + } + + static matrix3x4_t boneToWorld[MAXSTUDIOBONES]; + CBoneBitList boneComputed; + + int ikOffset = m_ikLock.AddMultipleToTail( seqdesc.numiklocks ); + memset( &m_ikLock[ikOffset], 0, sizeof(ikcontextikrule_t) * seqdesc.numiklocks ); + + for (int i = 0; i < seqdesc.numiklocks; i++) + { + mstudioiklock_t *plock = seqdesc.pIKLock( i ); + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( plock->chain ); + int bone = pchain->pLink( 2 )->bone; + + // don't bother with iklock if the bone isn't going to be calculated + if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); + + ikcontextikrule_t &ikrule = m_ikLock[i+ikOffset]; + ikrule.chain = i; + ikrule.slot = i; + ikrule.type = IK_WORLD; + + MatrixAngles( boneToWorld[bone], ikrule.q, ikrule.pos ); + + // save off current knee direction + if (pchain->pLink(0)->kneeDir.LengthSqr() > 0.0) + { + VectorRotate( pchain->pLink( 0 )->kneeDir, boneToWorld[ pchain->pLink( 0 )->bone ], ikrule.kneeDir ); + } + else + { + ikrule.kneeDir.Init( ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: build boneToWorld transforms for a specific bone +//----------------------------------------------------------------------------- +void CIKContext::BuildBoneChain( + const Vector pos[], + const Quaternion q[], + int iBone, + matrix3x4_t *pBoneToWorld, + CBoneBitList &boneComputed ) +{ + Assert( m_pStudioHdr->pBone( iBone )->flags & m_boneMask ); + ::BuildBoneChain( m_pStudioHdr, m_rootxform, pos, q, iBone, pBoneToWorld, boneComputed ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: build boneToWorld transforms for a specific bone +//----------------------------------------------------------------------------- +void BuildBoneChain( + const CStudioHdr *pStudioHdr, + const matrix3x4_t &rootxform, + const Vector pos[], + const Quaternion q[], + int iBone, + matrix3x4_t *pBoneToWorld, + CBoneBitList &boneComputed ) +{ + if ( boneComputed.IsBoneMarked(iBone) ) + return; + + matrix3x4_t bonematrix; + QuaternionMatrix( q[iBone], pos[iBone], bonematrix ); + + int parent = pStudioHdr->pBone( iBone )->parent; + if (parent == -1) + { + ConcatTransforms( rootxform, bonematrix, pBoneToWorld[iBone] ); + } + else + { + // evil recursive!!! + BuildBoneChain( pStudioHdr, rootxform, pos, q, parent, pBoneToWorld, boneComputed ); + ConcatTransforms( pBoneToWorld[parent], bonematrix, pBoneToWorld[iBone]); + } + boneComputed.MarkBone(iBone); +} + + +//----------------------------------------------------------------------------- +// Purpose: turn a specific bones boneToWorld transform into a pos and q in parents bonespace +//----------------------------------------------------------------------------- +void SolveBone( + const CStudioHdr *pStudioHdr, + int iBone, + matrix3x4_t *pBoneToWorld, + Vector pos[], + Quaternion q[] + ) +{ + int iParent = pStudioHdr->pBone( iBone )->parent; + + matrix3x4_t worldToBone; + MatrixInvert( pBoneToWorld[iParent], worldToBone ); + + matrix3x4_t local; + ConcatTransforms( worldToBone, pBoneToWorld[iBone], local ); + + MatrixAngles( local, q[iBone], pos[iBone] ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void CIKTarget::SetOwner( int entindex, const Vector &pos, const QAngle &angles ) +{ + latched.owner = entindex; + latched.absOrigin = pos; + latched.absAngles = angles; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void CIKTarget::ClearOwner( void ) +{ + latched.owner = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +int CIKTarget::GetOwner( void ) +{ + return latched.owner; +} + +//----------------------------------------------------------------------------- +// Purpose: update the latched IK values that are in a moving frame of reference +//----------------------------------------------------------------------------- + +void CIKTarget::UpdateOwner( int entindex, const Vector &pos, const QAngle &angles ) +{ + if (pos == latched.absOrigin && angles == latched.absAngles) + return; + + matrix3x4_t in, out; + AngleMatrix( angles, pos, in ); + AngleIMatrix( latched.absAngles, latched.absOrigin, out ); + + matrix3x4_t tmp1, tmp2; + QuaternionMatrix( latched.q, latched.pos, tmp1 ); + ConcatTransforms( out, tmp1, tmp2 ); + ConcatTransforms( in, tmp2, tmp1 ); + MatrixAngles( tmp1, latched.q, latched.pos ); +} + + +//----------------------------------------------------------------------------- +// Purpose: sets the ground position of an ik target +//----------------------------------------------------------------------------- + +void CIKTarget::SetPos( const Vector &pos ) +{ + est.pos = pos; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the ground "identity" orientation of an ik target +//----------------------------------------------------------------------------- + +void CIKTarget::SetAngles( const QAngle &angles ) +{ + AngleQuaternion( angles, est.q ); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the ground "identity" orientation of an ik target +//----------------------------------------------------------------------------- + +void CIKTarget::SetQuaternion( const Quaternion &q ) +{ + est.q = q; +} + +//----------------------------------------------------------------------------- +// Purpose: calculates a ground "identity" orientation based on the surface +// normal of the ground and the desired ground identity orientation +//----------------------------------------------------------------------------- + +void CIKTarget::SetNormal( const Vector &normal ) +{ + // recalculate foot angle based on slope of surface + matrix3x4_t m1; + Vector forward, right; + QuaternionMatrix( est.q, m1 ); + + MatrixGetColumn( m1, 1, right ); + forward = CrossProduct( right, normal ); + right = CrossProduct( normal, forward ); + MatrixSetColumn( forward, 0, m1 ); + MatrixSetColumn( right, 1, m1 ); + MatrixSetColumn( normal, 2, m1 ); + QAngle a1; + Vector p1; + MatrixAngles( m1, est.q, p1 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: estimates the ground impact at the center location assuming a the edge of +// an Z axis aligned disc collided with it the surface. +//----------------------------------------------------------------------------- + +void CIKTarget::SetPosWithNormalOffset( const Vector &pos, const Vector &normal ) +{ + // assume it's a disc edge intersecting with the floor, so try to estimate the z location of the center + est.pos = pos; + if (normal.z > 0.9999) + { + return; + } + // clamp at 45 degrees + else if (normal.z > 0.707) + { + // tan == sin / cos + float tan = sqrt( 1 - normal.z * normal.z ) / normal.z; + est.pos.z = est.pos.z - est.radius * tan; + } + else + { + est.pos.z = est.pos.z - est.radius; + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +bool CIKTarget::IsActive() +{ + return (est.flWeight > 0.0f); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void CIKTarget::IKFailed( void ) +{ + latched.deltaPos.Init(); + latched.deltaQ.Init(); + latched.pos = ideal.pos; + latched.q = ideal.q; + est.latched = 0.0; + est.flWeight = 0.0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Invalidate any IK locks. +//----------------------------------------------------------------------------- + +void CIKContext::ClearTargets( void ) +{ + int i; + for (i = 0; i < m_target.Count(); i++) + { + m_target[i].latched.iFramecounter = -9999; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Run through the rules that survived and turn a specific bones boneToWorld +// transform into a pos and q in parents bonespace +//----------------------------------------------------------------------------- + +void CIKContext::UpdateTargets( Vector pos[], Quaternion q[], matrix3x4_t boneToWorld[], CBoneBitList &boneComputed ) +{ + int i, j; + + for (i = 0; i < m_target.Count(); i++) + { + m_target[i].est.flWeight = 0.0f; + m_target[i].est.latched = 1.0f; + m_target[i].est.release = 1.0f; + m_target[i].est.height = 0.0f; + m_target[i].est.floor = 0.0f; + m_target[i].est.radius = 0.0f; + m_target[i].offset.pos.Init(); + m_target[i].offset.q.Init(); + } + + AutoIKRelease( ); + + for (j = 0; j < m_ikChainRule.Count(); j++) + { + for (i = 0; i < m_ikChainRule.Element( j ).Count(); i++) + { + ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i ); + + // ikchainresult_t *pChainRule = &chainRule[ m_ikRule[i].chain ]; + + switch( pRule->type ) + { + case IK_ATTACHMENT: + case IK_GROUND: + // case IK_SELF: + { + matrix3x4_t footTarget; + CIKTarget *pTarget = &m_target[pRule->slot]; + pTarget->chain = pRule->chain; + pTarget->type = pRule->type; + + if (pRule->type == IK_ATTACHMENT) + { + pTarget->offset.pAttachmentName = pRule->szLabel; + } + else + { + pTarget->offset.pAttachmentName = NULL; + } + + if (pRule->flRuleWeight == 1.0f || pTarget->est.flWeight == 0.0f) + { + pTarget->offset.q = pRule->q; + pTarget->offset.pos = pRule->pos; + pTarget->est.height = pRule->height; + pTarget->est.floor = pRule->floor; + pTarget->est.radius = pRule->radius; + pTarget->est.latched = pRule->latched * pRule->flRuleWeight; + pTarget->est.release = pRule->release; + pTarget->est.flWeight = pRule->flWeight * pRule->flRuleWeight; + } + else + { + QuaternionSlerp( pTarget->offset.q, pRule->q, pRule->flRuleWeight, pTarget->offset.q ); + pTarget->offset.pos = Lerp( pRule->flRuleWeight, pTarget->offset.pos, pRule->pos ); + pTarget->est.height = Lerp( pRule->flRuleWeight, pTarget->est.height, pRule->height ); + pTarget->est.floor = Lerp( pRule->flRuleWeight, pTarget->est.floor, pRule->floor ); + pTarget->est.radius = Lerp( pRule->flRuleWeight, pTarget->est.radius, pRule->radius ); + //pTarget->est.latched = Lerp( pRule->flRuleWeight, pTarget->est.latched, pRule->latched ); + pTarget->est.latched = min( pTarget->est.latched, pRule->latched ); + pTarget->est.release = Lerp( pRule->flRuleWeight, pTarget->est.release, pRule->release ); + pTarget->est.flWeight = Lerp( pRule->flRuleWeight, pTarget->est.flWeight, pRule->flWeight ); + } + + if ( pRule->type == IK_GROUND ) + { + pTarget->latched.deltaPos.z = 0; + pTarget->est.pos.z = pTarget->est.floor + m_rootxform[2][3]; + } + } + break; + case IK_UNLATCH: + { + CIKTarget *pTarget = &m_target[pRule->slot]; + if (pRule->latched > 0.0) + pTarget->est.latched = 0.0; + else + pTarget->est.latched = min( pTarget->est.latched, 1.0f - pRule->flWeight ); + } + break; + case IK_RELEASE: + { + CIKTarget *pTarget = &m_target[pRule->slot]; + if (pRule->latched > 0.0) + pTarget->est.latched = 0.0; + else + pTarget->est.latched = min( pTarget->est.latched, 1.0f - pRule->flWeight ); + + pTarget->est.flWeight = (pTarget->est.flWeight) * (1 - pRule->flWeight * pRule->flRuleWeight); + } + break; + } + } + } + + for (i = 0; i < m_target.Count(); i++) + { + CIKTarget *pTarget = &m_target[i]; + if (pTarget->est.flWeight > 0.0) + { + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pTarget->chain ); + // ikchainresult_t *pChainRule = &chainRule[ i ]; + int bone = pchain->pLink( 2 )->bone; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); + + // xform IK target error into world space + matrix3x4_t local; + matrix3x4_t worldFootpad; + QuaternionMatrix( pTarget->offset.q, pTarget->offset.pos, local ); + MatrixInvert( local, local ); + ConcatTransforms( boneToWorld[bone], local, worldFootpad ); + + if (pTarget->est.latched == 1.0) + { + pTarget->latched.bNeedsLatch = true; + } + else + { + pTarget->latched.bNeedsLatch = false; + } + + // disable latched position if it looks invalid + if (m_iFramecounter < 0 || pTarget->latched.iFramecounter < m_iFramecounter - 1 || pTarget->latched.iFramecounter > m_iFramecounter) + { + pTarget->latched.bHasLatch = false; + pTarget->latched.influence = 0.0; + } + pTarget->latched.iFramecounter = m_iFramecounter; + + // find ideal contact position + MatrixAngles( worldFootpad, pTarget->ideal.q, pTarget->ideal.pos ); + pTarget->est.q = pTarget->ideal.q; + pTarget->est.pos = pTarget->ideal.pos; + + float latched = pTarget->est.latched; + + if (pTarget->latched.bHasLatch) + { + if (pTarget->est.latched == 1.0) + { + // keep track of latch position error from ideal contact position + pTarget->latched.deltaPos = pTarget->latched.pos - pTarget->est.pos; + QuaternionSM( -1, pTarget->est.q, pTarget->latched.q, pTarget->latched.deltaQ ); + pTarget->est.q = pTarget->latched.q; + pTarget->est.pos = pTarget->latched.pos; + } + else if (pTarget->est.latched > 0.0) + { + // ramp out latch differences during decay phase of rule + if (latched > 0 && latched < pTarget->latched.influence) + { + // latching has decreased + float dt = pTarget->latched.influence - latched; + if (pTarget->latched.influence > 0.0) + dt = dt / pTarget->latched.influence; + + VectorScale( pTarget->latched.deltaPos, (1-dt), pTarget->latched.deltaPos ); + QuaternionScale( pTarget->latched.deltaQ, (1-dt), pTarget->latched.deltaQ ); + } + + // move ideal contact position by latched error factor + pTarget->est.pos = pTarget->est.pos + pTarget->latched.deltaPos; + QuaternionMA( pTarget->est.q, 1, pTarget->latched.deltaQ, pTarget->est.q ); + pTarget->latched.q = pTarget->est.q; + pTarget->latched.pos = pTarget->est.pos; + } + else + { + pTarget->latched.bHasLatch = false; + pTarget->latched.q = pTarget->est.q; + pTarget->latched.pos = pTarget->est.pos; + pTarget->latched.deltaPos.Init(); + pTarget->latched.deltaQ.Init(); + } + pTarget->latched.influence = latched; + } + + // check for illegal requests + Vector p1, p2, p3; + MatrixPosition( boneToWorld[pchain->pLink( 0 )->bone], p1 ); // hip + MatrixPosition( boneToWorld[pchain->pLink( 1 )->bone], p2 ); // knee + MatrixPosition( boneToWorld[pchain->pLink( 2 )->bone], p3 ); // foot + + float d1 = (p2 - p1).Length(); + float d2 = (p3 - p2).Length(); + + if (pTarget->latched.bHasLatch) + { + //float d3 = (p3 - p1).Length(); + float d4 = (p3 + pTarget->latched.deltaPos - p1).Length(); + + // unstick feet when distance is too great + if ((d4 < fabs( d1 - d2 ) || d4 * 0.95 > d1 + d2) && pTarget->est.latched > 0.2) + { + pTarget->error.flTime = m_flTime; + } + + // unstick feet when angle is too great + if (pTarget->est.latched > 0.2) + { + float d = fabs( pTarget->latched.deltaQ.w ) * 2.0f - 1.0f; // QuaternionDotProduct( pTarget->latched.q, pTarget->est.q ); + + // FIXME: cos(45), make property of chain + if (d < 0.707) + { + pTarget->error.flTime = m_flTime; + } + } + } + + Vector dt = pTarget->est.pos - p1; + VectorNormalize( dt ); + + pTarget->trace.p1 = p1 + dt * (fabs( d1 - d2 ) * 1.01); + pTarget->trace.p2 = p1 + dt * (d1 + d2) * 0.99; + pTarget->trace.p3 = p1 + Vector( 0, 0, -1 ) * (d1 + d2); + // pTarget->trace.endpos = pTarget->est.pos; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: insert release rules if the ik rules were in error +//----------------------------------------------------------------------------- + +void CIKContext::AutoIKRelease( void ) +{ + int i; + + for (i = 0; i < m_target.Count(); i++) + { + CIKTarget *pTarget = &m_target[i]; + + float dt = m_flTime - pTarget->error.flTime; + if (pTarget->error.bInError || dt < 0.5) + { + if (!pTarget->error.bInError) + { + pTarget->error.ramp = 0.0; + pTarget->error.flErrorTime = pTarget->error.flTime; + pTarget->error.bInError = true; + } + + float ft = m_flTime - pTarget->error.flErrorTime; + if (dt < 0.25) + { + pTarget->error.ramp = min( pTarget->error.ramp + ft * 4.0, 1.0 ); + } + else + { + pTarget->error.ramp = max( pTarget->error.ramp - ft * 4.0, 0.0 ); + } + if (pTarget->error.ramp > 0.0) + { + ikcontextikrule_t ikrule; + + ikrule.chain = pTarget->chain; + ikrule.bone = 0; + ikrule.type = IK_RELEASE; + ikrule.slot = i; + ikrule.flWeight = SimpleSpline( pTarget->error.ramp ); + ikrule.flRuleWeight = 1.0; + ikrule.latched = dt < 0.25 ? 0.0 : ikrule.flWeight; + + // don't bother with AutoIKRelease if the bone isn't going to be calculated + // this code is crashing for some unknown reason. + if ( pTarget->chain >= 0 && pTarget->chain < m_pStudioHdr->numikchains()) + { + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pTarget->chain ); + if (pchain != NULL) + { + int bone = pchain->pLink( 2 )->bone; + if (bone >= 0 && bone < m_pStudioHdr->numbones()) + { + mstudiobone_t *pBone = m_pStudioHdr->pBone( bone ); + if (pBone != NULL) + { + if ( !(pBone->flags & m_boneMask)) + { + pTarget->error.bInError = false; + continue; + } + /* + char buf[256]; + sprintf( buf, "dt %.4f ft %.4f weight %.4f latched %.4f\n", dt, ft, ikrule.flWeight, ikrule.latched ); + OutputDebugString( buf ); + */ + + int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); + m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; + } + else + { + DevWarning( 1, "AutoIKRelease (%s) got a NULL pBone %d\n", m_pStudioHdr->name(), bone ); + } + } + else + { + DevWarning( 1, "AutoIKRelease (%s) got an out of range bone %d (%d)\n", m_pStudioHdr->name(), bone, m_pStudioHdr->numbones() ); + } + } + else + { + DevWarning( 1, "AutoIKRelease (%s) got a NULL pchain %d\n", m_pStudioHdr->name(), pTarget->chain ); + } + } + else + { + DevWarning( 1, "AutoIKRelease (%s) got an out of range chain %d (%d)\n", m_pStudioHdr->name(), pTarget->chain, m_pStudioHdr->numikchains()); + } + } + else + { + pTarget->error.bInError = false; + } + pTarget->error.flErrorTime = m_flTime; + } + } +} + + + +void CIKContext::SolveDependencies( Vector pos[], Quaternion q[], matrix3x4_t boneToWorld[], CBoneBitList &boneComputed ) +{ + ASSERT_NO_REENTRY(); + + matrix3x4_t worldTarget; + int i, j; + + ikchainresult_t chainResult[32]; // allocate!!! + + // init chain rules + for (i = 0; i < m_pStudioHdr->numikchains(); i++) + { + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( i ); + ikchainresult_t *pChainResult = &chainResult[ i ]; + int bone = pchain->pLink( 2 )->bone; + + pChainResult->target = -1; + pChainResult->flWeight = 0.0; + + // don't bother with chain if the bone isn't going to be calculated + if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); + + MatrixAngles( boneToWorld[bone], pChainResult->q, pChainResult->pos ); + } + + for (j = 0; j < m_ikChainRule.Count(); j++) + { + for (i = 0; i < m_ikChainRule.Element( j ).Count(); i++) + { + ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i ); + ikchainresult_t *pChainResult = &chainResult[ pRule->chain ]; + pChainResult->target = -1; + + + switch( pRule->type ) + { + case IK_SELF: + { + // xform IK target error into world space + matrix3x4_t local; + QuaternionMatrix( pRule->q, pRule->pos, local ); + // eval target bone space + if (pRule->bone != -1) + { + BuildBoneChain( pos, q, pRule->bone, boneToWorld, boneComputed ); + ConcatTransforms( boneToWorld[pRule->bone], local, worldTarget ); + } + else + { + ConcatTransforms( m_rootxform, local, worldTarget ); + } + + float flWeight = pRule->flWeight * pRule->flRuleWeight; + pChainResult->flWeight = pChainResult->flWeight * (1 - flWeight) + flWeight; + + Vector p2; + Quaternion q2; + + // target p and q + MatrixAngles( worldTarget, q2, p2 ); + + // debugLine( pChainResult->pos, p2, 0, 0, 255, true, 0.1 ); + + // blend in position and angles + pChainResult->pos = pChainResult->pos * (1.0 - flWeight) + p2 * flWeight; + QuaternionSlerp( pChainResult->q, q2, flWeight, pChainResult->q ); + } + break; + case IK_WORLD: + Assert( 0 ); + break; + + case IK_ATTACHMENT: + break; + + case IK_GROUND: + break; + + case IK_RELEASE: + { + // move target back towards original location + float flWeight = pRule->flWeight * pRule->flRuleWeight; + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pRule->chain ); + int bone = pchain->pLink( 2 )->bone; + + Vector p2; + Quaternion q2; + + BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); + MatrixAngles( boneToWorld[bone], q2, p2 ); + + // blend in position and angles + pChainResult->pos = pChainResult->pos * (1.0 - flWeight) + p2 * flWeight; + QuaternionSlerp( pChainResult->q, q2, flWeight, pChainResult->q ); + } + break; + case IK_UNLATCH: + { + /* + pChainResult->flWeight = pChainResult->flWeight * (1 - pRule->flWeight) + pRule->flWeight; + + pChainResult->pos = pChainResult->pos * (1.0 - pRule->flWeight ) + pChainResult->local.pos * pRule->flWeight; + QuaternionSlerp( pChainResult->q, pChainResult->local.q, pRule->flWeight, pChainResult->q ); + */ + } + break; + } + } + } + + for (i = 0; i < m_target.Count(); i++) + { + CIKTarget *pTarget = &m_target[i]; + + if (m_target[i].est.flWeight > 0.0) + { + matrix3x4_t worldFootpad; + matrix3x4_t local; + //mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( m_target[i].chain ); + ikchainresult_t *pChainResult = &chainResult[ pTarget->chain ]; + + AngleMatrix(pTarget->offset.q, pTarget->offset.pos, local ); + + AngleMatrix( pTarget->est.q, pTarget->est.pos, worldFootpad ); + + ConcatTransforms( worldFootpad, local, worldTarget ); + + Vector p2; + Quaternion q2; + // target p and q + MatrixAngles( worldTarget, q2, p2 ); + // MatrixAngles( worldTarget, pChainResult->q, pChainResult->pos ); + + // blend in position and angles + pChainResult->flWeight = pTarget->est.flWeight; + pChainResult->pos = pChainResult->pos * (1.0 - pChainResult->flWeight ) + p2 * pChainResult->flWeight; + QuaternionSlerp( pChainResult->q, q2, pChainResult->flWeight, pChainResult->q ); + } + + if (pTarget->latched.bNeedsLatch) + { + // keep track of latch position + pTarget->latched.bHasLatch = true; + pTarget->latched.q = pTarget->est.q; + pTarget->latched.pos = pTarget->est.pos; + } + } + + for (i = 0; i < m_pStudioHdr->numikchains(); i++) + { + ikchainresult_t *pChainResult = &chainResult[ i ]; + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( i ); + + if (pChainResult->flWeight > 0.0) + { + Vector tmp; + MatrixPosition( boneToWorld[pchain->pLink( 2 )->bone], tmp ); + // debugLine( pChainResult->pos, tmp, 255, 255, 255, true, 0.1 ); + + // do exact IK solution + // FIXME: once per link! + if (Studio_SolveIK(pchain, pChainResult->pos, boneToWorld )) + { + Vector p3; + MatrixGetColumn( boneToWorld[pchain->pLink( 2 )->bone], 3, p3 ); + QuaternionMatrix( pChainResult->q, p3, boneToWorld[pchain->pLink( 2 )->bone] ); + + // rebuild chain + // FIXME: is this needed if everyone past this uses the boneToWorld array? + SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); + SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); + SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); + } + else + { + // FIXME: need to invalidate the targets that forced this... + if (pChainResult->target != -1) + { + CIKTarget *pTarget = &m_target[pChainResult->target]; + VectorScale( pTarget->latched.deltaPos, 0.8, pTarget->latched.deltaPos ); + QuaternionScale( pTarget->latched.deltaQ, 0.8, pTarget->latched.deltaQ ); + } + } + } + } + +#if 0 + Vector p1, p2, p3; + Quaternion q1, q2, q3; + + // current p and q + MatrixAngles( boneToWorld[bone], q1, p1 ); + + + // target p and q + MatrixAngles( worldTarget, q2, p2 ); + + // blend in position and angles + p3 = p1 * (1.0 - m_ikRule[i].flWeight ) + p2 * m_ikRule[i].flWeight; + + // do exact IK solution + // FIXME: once per link! + Studio_SolveIK(pchain, p3, boneToWorld ); + + // force angle (bad?) + QuaternionSlerp( q1, q2, m_ikRule[i].flWeight, q3 ); + MatrixGetColumn( boneToWorld[bone], 3, p3 ); + QuaternionMatrix( q3, p3, boneToWorld[bone] ); + + // rebuild chain + SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); + SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); + SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); +#endif +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void CIKContext::SolveAutoplayLocks( + Vector pos[], + Quaternion q[] + ) +{ + ASSERT_NO_REENTRY(); + + static matrix3x4_t boneToWorld[MAXSTUDIOBONES]; + CBoneBitList boneComputed; + int i; + + for (i = 0; i < m_ikLock.Count(); i++) + { + const mstudioiklock_t &lock = m_pStudioHdr->pIKAutoplayLock( i ); + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( lock.chain ); + int bone = pchain->pLink( 2 )->bone; + + // don't bother with iklock if the bone isn't going to be calculated + if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); + + Vector p1, p2, p3; + Quaternion q2, q3; + + // current p and q + MatrixPosition( boneToWorld[bone], p1 ); + + // blend in position + p3 = p1 * (1.0 - lock.flPosWeight ) + m_ikLock[i].pos * lock.flPosWeight; + + // do exact IK solution + if (m_ikLock[i].kneeDir.LengthSqr() > 0) + { + Studio_SolveIK(pchain->pLink( 0 )->bone, pchain->pLink( 1 )->bone, pchain->pLink( 2 )->bone, p3, m_ikLock[i].kneePos, m_ikLock[i].kneeDir, boneToWorld ); + } + else + { + Studio_SolveIK(pchain, p3, boneToWorld ); + } + + // slam orientation + MatrixPosition( boneToWorld[bone], p3 ); + QuaternionMatrix( m_ikLock[i].q, p3, boneToWorld[bone] ); + + // rebuild chain + q2 = q[ bone ]; + SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); + QuaternionSlerp( q[bone], q2, lock.flLocalQWeight, q[bone] ); + + SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); + SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +void CIKContext::SolveSequenceLocks( + mstudioseqdesc_t &seqdesc, + Vector pos[], + Quaternion q[] + ) +{ + ASSERT_NO_REENTRY(); + + static matrix3x4_t boneToWorld[MAXSTUDIOBONES]; + CBoneBitList boneComputed; + int i; + + for (i = 0; i < m_ikLock.Count(); i++) + { + mstudioiklock_t *plock = seqdesc.pIKLock( i ); + mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( plock->chain ); + int bone = pchain->pLink( 2 )->bone; + + // don't bother with iklock if the bone isn't going to be calculated + if ( !(m_pStudioHdr->pBone( bone )->flags & m_boneMask)) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); + + Vector p1, p2, p3; + Quaternion q2, q3; + + // current p and q + MatrixPosition( boneToWorld[bone], p1 ); + + // blend in position + p3 = p1 * (1.0 - plock->flPosWeight ) + m_ikLock[i].pos * plock->flPosWeight; + + // do exact IK solution + if (m_ikLock[i].kneeDir.LengthSqr() > 0) + { + Studio_SolveIK(pchain->pLink( 0 )->bone, pchain->pLink( 1 )->bone, pchain->pLink( 2 )->bone, p3, m_ikLock[i].kneePos, m_ikLock[i].kneeDir, boneToWorld ); + } + else + { + Studio_SolveIK(pchain, p3, boneToWorld ); + } + + // slam orientation + MatrixPosition( boneToWorld[bone], p3 ); + QuaternionMatrix( m_ikLock[i].q, p3, boneToWorld[bone] ); + + // rebuild chain + q2 = q[ bone ]; + SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); + QuaternionSlerp( q[bone], q2, plock->flLocalQWeight, q[bone] ); + + SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); + SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: run all animations that automatically play and are driven off of poseParameters +//----------------------------------------------------------------------------- +void CalcAutoplaySequences( + const CStudioHdr *pStudioHdr, + CIKContext *pIKContext, + Vector pos[], + Quaternion q[], + const float poseParameters[], + int boneMask, + float realTime + ) +{ + ASSERT_NO_REENTRY(); + + int i; + if ( pIKContext ) + { + pIKContext->AddAutoplayLocks( pos, q ); + } + + unsigned short *pList = NULL; + int count = pStudioHdr->GetAutoplayList( &pList ); + for (i = 0; i < count; i++) + { + int sequenceIndex = pList[i]; + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequenceIndex ); + if (seqdesc.flags & STUDIO_AUTOPLAY) + { + float cycle = 0; + float cps = Studio_CPS( pStudioHdr, seqdesc, sequenceIndex, poseParameters ); + cycle = realTime * cps; + cycle = cycle - (int)cycle; + + AccumulatePose( pStudioHdr, NULL, pos, q, sequenceIndex, cycle, poseParameters, boneMask, 1.0, realTime ); + } + } + + if ( pIKContext ) + { + pIKContext->SolveAutoplayLocks( pos, q ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Studio_BuildMatrices( + const CStudioHdr *pStudioHdr, + const QAngle& angles, + const Vector& origin, + const Vector pos[], + const Quaternion q[], + int iBone, + matrix3x4_t bonetoworld[MAXSTUDIOBONES], + int boneMask + ) +{ + int i, j; + + int chain[MAXSTUDIOBONES]; + int chainlength = 0; + + if (iBone < -1 || iBone >= pStudioHdr->numbones()) + iBone = 0; + + mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); + + // build list of what bones to use + if (iBone == -1) + { + // all bones + chainlength = pStudioHdr->numbones(); + for (i = 0; i < pStudioHdr->numbones(); i++) + { + chain[chainlength - i - 1] = i; + } + } + else + { + // only the parent bones + i = iBone; + while (i != -1) + { + chain[chainlength++] = i; + i = pbones[i].parent; + } + } + + matrix3x4_t bonematrix; + matrix3x4_t rotationmatrix; // model to world transformation + AngleMatrix( angles, origin, rotationmatrix); + + for (j = chainlength - 1; j >= 0; j--) + { + i = chain[j]; + if (pbones[i].flags & boneMask) + { + QuaternionMatrix( q[i], pos[i], bonematrix ); + + if (pbones[i].parent == -1) + { + ConcatTransforms (rotationmatrix, bonematrix, bonetoworld[i]); + } + else + { + ConcatTransforms (bonetoworld[pbones[i].parent], bonematrix, bonetoworld[i]); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: look at single column vector of another bones local transformation +// and generate a procedural transformation based on how that column +// points down the 6 cardinal axis (all negative weights are clamped to 0). +//----------------------------------------------------------------------------- + +void DoAxisInterpBone( + mstudiobone_t *pbones, + int ibone, + CBoneAccessor &bonetoworld + ) +{ + matrix3x4_t bonematrix; + Vector control; + + mstudioaxisinterpbone_t *pProc = (mstudioaxisinterpbone_t *)pbones[ibone].pProcedure( ); + const matrix3x4_t &controlBone = bonetoworld.GetBone( pProc->control ); + if (pProc && pbones[pProc->control].parent != -1) + { + Vector tmp; + // pull out the control column + tmp.x = controlBone[0][pProc->axis]; + tmp.y = controlBone[1][pProc->axis]; + tmp.z = controlBone[2][pProc->axis]; + + // invert it back into parent's space. + VectorIRotate( tmp, bonetoworld.GetBone( pbones[pProc->control].parent ), control ); +#if 0 + matrix3x4_t tmpmatrix; + matrix3x4_t controlmatrix; + MatrixInvert( bonetoworld.GetBone( pbones[pProc->control].parent ), tmpmatrix ); + ConcatTransforms( tmpmatrix, bonetoworld.GetBone( pProc->control ), controlmatrix ); + + // pull out the control column + control.x = controlmatrix[0][pProc->axis]; + control.y = controlmatrix[1][pProc->axis]; + control.z = controlmatrix[2][pProc->axis]; +#endif + } + else + { + // pull out the control column + control.x = controlBone[0][pProc->axis]; + control.y = controlBone[1][pProc->axis]; + control.z = controlBone[2][pProc->axis]; + } + + Quaternion *q1, *q2, *q3; + Vector *p1, *p2, *p3; + + // find axial control inputs + float a1 = control.x; + float a2 = control.y; + float a3 = control.z; + if (a1 >= 0) + { + q1 = &pProc->quat[0]; + p1 = &pProc->pos[0]; + } + else + { + a1 = -a1; + q1 = &pProc->quat[1]; + p1 = &pProc->pos[1]; + } + + if (a2 >= 0) + { + q2 = &pProc->quat[2]; + p2 = &pProc->pos[2]; + } + else + { + a2 = -a2; + q2 = &pProc->quat[3]; + p2 = &pProc->pos[3]; + } + + if (a3 >= 0) + { + q3 = &pProc->quat[4]; + p3 = &pProc->pos[4]; + } + else + { + a3 = -a3; + q3 = &pProc->quat[5]; + p3 = &pProc->pos[5]; + } + + // do a three-way blend + Vector p; + Quaternion v, tmp; + if (a1 + a2 > 0) + { + float t = 1.0 / (a1 + a2 + a3); + // FIXME: do a proper 3-way Quat blend! + QuaternionSlerp( *q2, *q1, a1 / (a1 + a2), tmp ); + QuaternionSlerp( tmp, *q3, a3 * t, v ); + VectorScale( *p1, a1 * t, p ); + VectorMA( p, a2 * t, *p2, p ); + VectorMA( p, a3 * t, *p3, p ); + } + else + { + QuaternionSlerp( *q3, *q3, 0, v ); // ??? no quat copy? + p = *p3; + } + + QuaternionMatrix( v, p, bonematrix ); + + ConcatTransforms (bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone )); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Generate a procedural transformation based on how that another bones +// local transformation matches a set of target orientations. +//----------------------------------------------------------------------------- +void DoQuatInterpBone( + mstudiobone_t *pbones, + int ibone, + CBoneAccessor &bonetoworld + ) +{ + matrix3x4_t bonematrix; + Vector control; + + mstudioquatinterpbone_t *pProc = (mstudioquatinterpbone_t *)pbones[ibone].pProcedure( ); + if (pProc && pbones[pProc->control].parent != -1) + { + Quaternion src; + float weight[32]; + float scale = 0.0; + Quaternion quat; + Vector pos; + + matrix3x4_t tmpmatrix; + matrix3x4_t controlmatrix; + MatrixInvert( bonetoworld.GetBone( pbones[pProc->control].parent), tmpmatrix ); + ConcatTransforms( tmpmatrix, bonetoworld.GetBone( pProc->control ), controlmatrix ); + + MatrixAngles( controlmatrix, src, pos ); // FIXME: make a version without pos + + int i; + for (i = 0; i < pProc->numtriggers; i++) + { + float dot = fabs( QuaternionDotProduct( pProc->pTrigger( i )->trigger, src ) ); + // FIXME: a fast acos should be acceptable + dot = clamp( dot, -1, 1 ); + weight[i] = 1 - (2 * acos( dot ) * pProc->pTrigger( i )->inv_tolerance ); + weight[i] = max( 0, weight[i] ); + scale += weight[i]; + } + + if (scale <= 0.001) // EPSILON? + { + AngleMatrix( pProc->pTrigger( 0 )->quat, pProc->pTrigger( 0 )->pos, bonematrix ); + ConcatTransforms ( bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone ) ); + return; + } + + scale = 1.0 / scale; + + quat.Init( 0, 0, 0, 0); + pos.Init( ); + + for (i = 0; i < pProc->numtriggers; i++) + { + if (weight[i]) + { + float s = weight[i] * scale; + mstudioquatinterpinfo_t *pTrigger = pProc->pTrigger( i ); + + QuaternionAlign( pTrigger->quat, quat, quat ); + + quat.x = quat.x + s * pTrigger->quat.x; + quat.y = quat.y + s * pTrigger->quat.y; + quat.z = quat.z + s * pTrigger->quat.z; + quat.w = quat.w + s * pTrigger->quat.w; + pos.x = pos.x + s * pTrigger->pos.x; + pos.y = pos.y + s * pTrigger->pos.y; + pos.z = pos.z + s * pTrigger->pos.z; + } + } + Assert( QuaternionNormalize( quat ) != 0); + QuaternionMatrix( quat, pos, bonematrix ); + } + + ConcatTransforms (bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone )); +} + +/* + * This is for DoAimAtBone below, was just for testing, not needed in general + * but to turn it back on, uncomment this and the section in DoAimAtBone() below + * + +static ConVar aim_constraint( "aim_constraint", "1", FCVAR_REPLICATED, "Toggle Helper Bones" ); + +*/ + +//----------------------------------------------------------------------------- +// Purpose: Generate a procedural transformation so that one bone points at +// another point on the model +//----------------------------------------------------------------------------- +void DoAimAtBone( + mstudiobone_t *pBones, + int iBone, + CBoneAccessor &bonetoworld, + const CStudioHdr *pStudioHdr + ) +{ + mstudioaimatbone_t *pProc = (mstudioaimatbone_t *)pBones[iBone].pProcedure(); + + if ( !pProc ) + { + return; + } + + /* + * Uncomment this if the ConVar above is uncommented + * + + if ( !aim_constraint.GetBool() ) + { + // If the aim constraint is turned off then just copy the parent transform + // plus the offset value + + matrix3x4_t boneToWorldSpace; + MatrixCopy ( bonetoworld.GetBone( pProc->parent ), boneToWorldSpace ); + Vector boneWorldPosition; + VectorTransform( pProc->basepos, boneToWorldSpace, boneWorldPosition ); + MatrixSetColumn( boneWorldPosition, 3, boneToWorldSpace ); + MatrixCopy( boneToWorldSpace, bonetoworld.GetBoneForWrite( iBone ) ); + + return; + } + + */ + + // The world matrix of the bone to change + matrix3x4_t boneMatrix; + + // Guaranteed to be unit length + const Vector &userAimVector( pProc->aimvector ); + + // Guaranteed to be unit length + const Vector &userUpVector( pProc->upvector ); + + // Get to get position of bone but also for up reference + matrix3x4_t parentSpace; + MatrixCopy ( bonetoworld.GetBone( pProc->parent ), parentSpace ); + + // World space position of the bone to aim + Vector aimWorldPosition; + VectorTransform( pProc->basepos, parentSpace, aimWorldPosition ); + + // The worldspace matrix of the bone to aim at + matrix3x4_t aimAtSpace; + if ( pStudioHdr ) + { + // This means it's AIMATATTACH + const mstudioattachment_t &attachment( pStudioHdr->pAttachment( pProc->aim ) ); + ConcatTransforms( + bonetoworld.GetBone( attachment.localbone ), + attachment.local, + aimAtSpace ); + } + else + { + MatrixCopy( bonetoworld.GetBone( pProc->aim ), aimAtSpace ); + } + + Vector aimAtWorldPosition; + MatrixGetColumn( aimAtSpace, 3, aimAtWorldPosition ); + + Vector aimVector; + VectorSubtract( aimAtWorldPosition, aimWorldPosition, aimVector ); + VectorNormalizeFast( aimVector ); + + Vector axis; + CrossProduct( userAimVector, aimVector, axis ); + VectorNormalizeFast( axis ); + float angle( acosf( DotProduct( userAimVector, aimVector ) ) ); + Quaternion aimRotation; + AxisAngleQuaternion( axis, RAD2DEG( angle ), aimRotation ); + + if ( ( 1.0f - fabs( DotProduct( userUpVector, userAimVector ) ) ) > FLT_EPSILON ) + { + matrix3x4_t aimRotationMatrix; + QuaternionMatrix( aimRotation, aimRotationMatrix ); + + Vector tmpV; + + Vector tmp_pUp; + VectorRotate( userUpVector, aimRotationMatrix, tmp_pUp ); + VectorScale( aimVector, DotProduct( aimVector, tmp_pUp ), tmpV ); + Vector pUp; + VectorSubtract( tmp_pUp, tmpV, pUp ); + VectorNormalizeFast( pUp ); + + Vector tmp_pParentUp; + VectorRotate( userUpVector, parentSpace, tmp_pParentUp ); + VectorScale( aimVector, DotProduct( aimVector, tmp_pParentUp ), tmpV ); + Vector pParentUp; + VectorSubtract( tmp_pParentUp, tmpV, pParentUp ); + VectorNormalizeFast( pParentUp ); + + angle = acos( DotProduct( pUp, pParentUp ) ); + CrossProduct( pUp, pParentUp, axis ); + VectorNormalizeFast( axis ); + Quaternion upRotation; + AxisAngleQuaternion( axis, RAD2DEG( angle ), upRotation ); + + Quaternion boneRotation; + QuaternionMult( upRotation, aimRotation, boneRotation ); + QuaternionMatrix( boneRotation, aimWorldPosition, boneMatrix ); + } + else + { + QuaternionMatrix( aimRotation, aimWorldPosition, boneMatrix ); + } + + MatrixCopy( boneMatrix, bonetoworld.GetBoneForWrite( iBone ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +bool CalcProceduralBone( + const CStudioHdr *pStudioHdr, + int iBone, + CBoneAccessor &bonetoworld + ) +{ + mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); + + if ( pbones[iBone].flags & BONE_ALWAYS_PROCEDURAL ) + { + switch( pbones[iBone].proctype ) + { + case STUDIO_PROC_AXISINTERP: + DoAxisInterpBone( pbones, iBone, bonetoworld ); + return true; + + case STUDIO_PROC_QUATINTERP: + DoQuatInterpBone( pbones, iBone, bonetoworld ); + return true; + + case STUDIO_PROC_AIMATBONE: + DoAimAtBone( pbones, iBone, bonetoworld, NULL ); + return true; + + case STUDIO_PROC_AIMATATTACH: + DoAimAtBone( pbones, iBone, bonetoworld, pStudioHdr ); + return true; + + default: + return false; + } + } + return false; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Lookup a bone controller +//----------------------------------------------------------------------------- + + + +static mstudiobonecontroller_t* FindController( const CStudioHdr *pStudioHdr, int iController) +{ + // find first controller that matches the index + for (int i = 0; i < pStudioHdr->numbonecontrollers(); i++) + { + if (pStudioHdr->pBonecontroller( i )->inputfield == iController) + return pStudioHdr->pBonecontroller( i ); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: converts a ranged bone controller value into a 0..1 encoded value +// Output: ctlValue contains 0..1 encoding. +// returns clamped ranged value +//----------------------------------------------------------------------------- + +float Studio_SetController( const CStudioHdr *pStudioHdr, int iController, float flValue, float &ctlValue ) +{ + if (! pStudioHdr) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = FindController(pStudioHdr, iController); + if(!pbonecontroller) + { + ctlValue = 0; + 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; + } + } + + ctlValue = (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); + if (ctlValue < 0) ctlValue = 0; + if (ctlValue > 1) ctlValue = 1; + + float flReturnVal = ((1.0 - ctlValue)*pbonecontroller->start + ctlValue *pbonecontroller->end); + + // ugly hack, invert value if a rotational controller and end < start + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR) && + pbonecontroller->end < pbonecontroller->start ) + { + flReturnVal *= -1; + } + + return flReturnVal; +} + + +//----------------------------------------------------------------------------- +// Purpose: converts a 0..1 encoded bone controller value into a ranged value +// Output: returns ranged value +//----------------------------------------------------------------------------- + +float Studio_GetController( const CStudioHdr *pStudioHdr, int iController, float ctlValue ) +{ + if (!pStudioHdr) + return 0.0; + + mstudiobonecontroller_t *pbonecontroller = FindController(pStudioHdr, iController); + if(!pbonecontroller) + return 0; + + return ctlValue * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +//----------------------------------------------------------------------------- +// Purpose: converts a ranged pose parameter value into a 0..1 encoded value +// Output: ctlValue contains 0..1 encoding. +// returns clamped ranged value +//----------------------------------------------------------------------------- + +float Studio_SetPoseParameter( const CStudioHdr *pStudioHdr, int iParameter, float flValue, float &ctlValue ) +{ + if (iParameter < 0 || iParameter >= pStudioHdr->GetNumPoseParameters()) + { + return 0; + } + + const mstudioposeparamdesc_t &PoseParam = pStudioHdr->pPoseParameter( iParameter ); + + Assert( IsFinite( flValue ) ); + + if (PoseParam.loop) + { + float wrap = (PoseParam.start + PoseParam.end) / 2.0 + PoseParam.loop / 2.0; + float shift = PoseParam.loop - wrap; + + flValue = flValue - PoseParam.loop * floor((flValue + shift) / PoseParam.loop); + } + + ctlValue = (flValue - PoseParam.start) / (PoseParam.end - PoseParam.start); + + if (ctlValue < 0) ctlValue = 0; + if (ctlValue > 1) ctlValue = 1; + + Assert( IsFinite( ctlValue ) ); + + return ctlValue * (PoseParam.end - PoseParam.start) + PoseParam.start; +} + + +//----------------------------------------------------------------------------- +// Purpose: converts a 0..1 encoded pose parameter value into a ranged value +// Output: returns ranged value +//----------------------------------------------------------------------------- + +float Studio_GetPoseParameter( const CStudioHdr *pStudioHdr, int iParameter, float ctlValue ) +{ + if (iParameter < 0 || iParameter >= pStudioHdr->GetNumPoseParameters()) + { + return 0; + } + + const mstudioposeparamdesc_t &PoseParam = pStudioHdr->pPoseParameter( iParameter ); + + return ctlValue * (PoseParam.end - PoseParam.start) + PoseParam.start; +} + +#ifdef _MSC_VER +#pragma warning (disable : 4701) +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static int ClipRayToHitbox( const Ray_t &ray, mstudiobbox_t *pbox, matrix3x4_t& matrix, trace_t &tr ) +{ + // scale by current t so hits shorten the ray and increase the likelihood of early outs + Vector delta2; + VectorScale( ray.m_Delta, (0.5f * tr.fraction), delta2 ); + + // OPTIMIZE: Store this in the box instead of computing it here + // compute center in local space + Vector boxextents; + boxextents.x = (pbox->bbmin.x + pbox->bbmax.x) * 0.5; + boxextents.y = (pbox->bbmin.y + pbox->bbmax.y) * 0.5; + boxextents.z = (pbox->bbmin.z + pbox->bbmax.z) * 0.5; + Vector boxCenter; + // transform to world space + VectorTransform( boxextents, matrix, boxCenter ); + // calc extents from local center + boxextents.x = pbox->bbmax.x - boxextents.x; + boxextents.y = pbox->bbmax.y - boxextents.y; + boxextents.z = pbox->bbmax.z - boxextents.z; + // OPTIMIZE: This is optimized for world space. If the transform is fast enough, it may make more + // sense to just xform and call UTIL_ClipToBox() instead. MEASURE THIS. + + // save the extents of the ray along + Vector extent, uextent; + Vector segmentCenter; + segmentCenter.x = ray.m_Start.x + delta2.x - boxCenter.x; + segmentCenter.y = ray.m_Start.y + delta2.y - boxCenter.y; + segmentCenter.z = ray.m_Start.z + delta2.z - boxCenter.z; + + extent.Init(); + + // check box axes for separation + for ( int j = 0; j < 3; j++ ) + { + extent[j] = delta2.x * matrix[0][j] + delta2.y * matrix[1][j] + delta2.z * matrix[2][j]; + uextent[j] = fabsf(extent[j]); + float coord = segmentCenter.x * matrix[0][j] + segmentCenter.y * matrix[1][j] + segmentCenter.z * matrix[2][j]; + coord = fabsf(coord); + + if ( coord > (boxextents[j] + uextent[j]) ) + return -1; + } + + // now check cross axes for separation + float tmp, cextent; + Vector cross; + CrossProduct( delta2, segmentCenter, cross ); + cextent = cross.x * matrix[0][0] + cross.y * matrix[1][0] + cross.z * matrix[2][0]; + cextent = fabsf(cextent); + tmp = boxextents[1]*uextent[2] + boxextents[2]*uextent[1]; + if ( cextent > tmp ) + return -1; + + cextent = cross.x * matrix[0][1] + cross.y * matrix[1][1] + cross.z * matrix[2][1]; + cextent = fabsf(cextent); + tmp = boxextents[0]*uextent[2] + boxextents[2]*uextent[0]; + if ( cextent > tmp ) + return -1; + + cextent = cross.x * matrix[0][2] + cross.y * matrix[1][2] + cross.z * matrix[2][2]; + cextent = fabsf(cextent); + tmp = boxextents[0]*uextent[1] + boxextents[1]*uextent[0]; + if ( cextent > tmp ) + return -1; + + // !!! We hit this box !!! compute intersection point and return + Vector start; + // Compute ray start in bone space + VectorITransform( ray.m_Start, matrix, start ); + // extent is delta2 in bone space, recompute delta in bone space + VectorScale( extent, 2, extent ); + + // delta was prescaled by the current t, so no need to see if this intersection + // is closer + trace_t boxTrace; + if ( !IntersectRayWithBox( start, extent, pbox->bbmin, pbox->bbmax, 0.0f, &boxTrace ) ) + return -1; + Assert( IsFinite(boxTrace.fraction) ); + tr.fraction *= boxTrace.fraction; + tr.startsolid = boxTrace.startsolid; + int hitside = boxTrace.plane.type; + if ( boxTrace.plane.normal[hitside] >= 0 ) + { + hitside += 3; + } + return hitside; +} + +#ifdef _MSC_VER +#pragma warning (default : 4701) +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool SweepBoxToStudio( const Ray_t& ray, CStudioHdr *pStudioHdr, mstudiohitboxset_t *set, + matrix3x4_t **hitboxbones, int fContentsMask, trace_t &tr ) +{ + tr.fraction = 1.0; + tr.startsolid = false; + + // OPTIMIZE: Partition these? + Ray_t clippedRay = ray; + int hitbox = -1; + for ( int i = 0; i < set->numhitboxes; i++ ) + { + mstudiobbox_t *pbox = set->pHitbox(i); + + // Filter based on contents mask + int fBoneContents = pStudioHdr->pBone( pbox->bone )->contents; + if ( ( fBoneContents & fContentsMask ) == 0 ) + continue; + + trace_t obbTrace; + if ( IntersectRayWithOBB( clippedRay, *hitboxbones[pbox->bone], pbox->bbmin, pbox->bbmax, 0.0f, &obbTrace ) ) + { + tr.startpos = obbTrace.startpos; + tr.endpos = obbTrace.endpos; + tr.plane = obbTrace.plane; + tr.startsolid = obbTrace.startsolid; + tr.allsolid = obbTrace.allsolid; + + // This logic here is to shorten the ray each time to get more early outs + tr.fraction *= obbTrace.fraction; + clippedRay.m_Delta *= obbTrace.fraction; + hitbox = i; + if (tr.startsolid) + break; + } + } + + if ( hitbox >= 0 ) + { + tr.hitgroup = set->pHitbox(hitbox)->group; + tr.hitbox = hitbox; + tr.contents = pStudioHdr->pBone( set->pHitbox(hitbox)->bone )->contents | CONTENTS_HITBOX; + tr.physicsbone = pStudioHdr->pBone( set->pHitbox(hitbox)->bone )->physicsbone; + Assert( tr.physicsbone >= 0 ); + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool TraceToStudio( const Ray_t& ray, CStudioHdr *pStudioHdr, mstudiohitboxset_t *set, + matrix3x4_t **hitboxbones, int fContentsMask, trace_t &tr ) +{ + if ( !ray.m_IsRay ) + { + return SweepBoxToStudio( ray, pStudioHdr, set, hitboxbones, fContentsMask, tr ); + } + + tr.fraction = 1.0; + tr.startsolid = false; + + // no hit yet + int hitbox = -1; + int hitside = -1; + + // OPTIMIZE: Partition these? + for ( int i = 0; i < set->numhitboxes; i++ ) + { + mstudiobbox_t *pbox = set->pHitbox(i); + + // Filter based on contents mask + int fBoneContents = pStudioHdr->pBone( pbox->bone )->contents; + if ( ( fBoneContents & fContentsMask ) == 0 ) + continue; + + // columns are axes of the bones in world space, translation is in world space + matrix3x4_t& matrix = *hitboxbones[pbox->bone]; + + int side = ClipRayToHitbox( ray, pbox, matrix, tr ); + if ( side >= 0 ) + { + hitbox = i; + hitside = side; + } + } + + if ( hitbox >= 0 ) + { + mstudiobbox_t *pbox = set->pHitbox(hitbox); + VectorMA( ray.m_Start, tr.fraction, ray.m_Delta, tr.endpos ); + tr.hitgroup = set->pHitbox(hitbox)->group; + tr.hitbox = hitbox; + tr.contents = pStudioHdr->pBone( pbox->bone )->contents | CONTENTS_HITBOX; + tr.physicsbone = pStudioHdr->pBone( pbox->bone )->physicsbone; + Assert( tr.physicsbone >= 0 ); + matrix3x4_t& matrix = *hitboxbones[pbox->bone]; + if ( hitside >= 3 ) + { + hitside -= 3; + tr.plane.normal[0] = matrix[0][hitside]; + tr.plane.normal[1] = matrix[1][hitside]; + tr.plane.normal[2] = matrix[2][hitside]; + //tr.plane.dist = DotProduct( tr.plane.normal, Vector(matrix[0][3], matrix[1][3], matrix[2][3] ) ) + pbox->bbmax[hitside]; + } + else + { + tr.plane.normal[0] = -matrix[0][hitside]; + tr.plane.normal[1] = -matrix[1][hitside]; + tr.plane.normal[2] = -matrix[2][hitside]; + //tr.plane.dist = DotProduct( tr.plane.normal, Vector(matrix[0][3], matrix[1][3], matrix[2][3] ) ) - pbox->bbmin[hitside]; + } + // simpler plane constant equation + tr.plane.dist = DotProduct( tr.endpos, tr.plane.normal ); + tr.plane.type = 3; + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns array of animations and weightings for a sequence based on current pose parameters +//----------------------------------------------------------------------------- + +void Studio_SeqAnims( const CStudioHdr *pStudioHdr, mstudioseqdesc_t &seqdesc, int iSequence, const float poseParameter[], mstudioanimdesc_t *panim[4], float *weight ) +{ +#if _DEBUG + VPROF_INCREMENT_COUNTER("SEQ_ANIMS",1); +#endif + if (!pStudioHdr || iSequence >= pStudioHdr->GetNumSeq()) + { + weight[0] = weight[1] = weight[2] = weight[3] = 0.0; + return; + } + + int i0 = 0, i1 = 0; + float s0 = 0, s1 = 0; + + Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, iSequence, 0, s0, i0 ); + Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, iSequence, 1, s1, i1 ); + + panim[0] = &pStudioHdr->pAnimdesc( pStudioHdr->iRelativeAnim( iSequence, seqdesc.anim( i0 , i1 ) ) ); + weight[0] = (1 - s0) * (1 - s1); + + panim[1] = &pStudioHdr->pAnimdesc( pStudioHdr->iRelativeAnim( iSequence, seqdesc.anim( i0+1, i1 ) ) ); + weight[1] = (s0) * (1 - s1); + + panim[2] = &pStudioHdr->pAnimdesc( pStudioHdr->iRelativeAnim( iSequence, seqdesc.anim( i0 , i1+1 ) ) ); + weight[2] = (1 - s0) * (s1); + + panim[3] = &pStudioHdr->pAnimdesc( pStudioHdr->iRelativeAnim( iSequence, seqdesc.anim( i0+1, i1+1 ) ) ); + weight[3] = (s0) * (s1); + + Assert( weight[0] >= 0.0f && weight[1] >= 0.0f && weight[2] >= 0.0f && weight[3] >= 0.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns max frame number for a sequence +//----------------------------------------------------------------------------- + +int Studio_MaxFrame( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[] ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); + Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); + + float maxFrame = 0; + for (int i = 0; i < 4; i++) + { + if (weight[i] > 0) + { + maxFrame += panim[i]->numframes * weight[i]; + } + } + + if ( maxFrame > 1 ) + maxFrame -= 1; + + + // FIXME: why does the weights sometimes not exactly add it 1.0 and this sometimes rounds down? + return static_cast(maxFrame + 0.01); +} + + +//----------------------------------------------------------------------------- +// Purpose: returns frames per second of a sequence +//----------------------------------------------------------------------------- + +float Studio_FPS( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[] ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); + Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); + + float t = 0; + + for (int i = 0; i < 4; i++) + { + if (weight[i] > 0) + { + t += panim[i]->fps * weight[i]; + } + } + return t; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns cycles per second of a sequence (cycles/second) +//----------------------------------------------------------------------------- + +float Studio_CPS( const CStudioHdr *pStudioHdr, mstudioseqdesc_t &seqdesc, int iSequence, const float poseParameter[] ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + + Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); + + float t = 0; + + for (int i = 0; i < 4; i++) + { + if (weight[i] > 0 && panim[i]->numframes > 1) + { + t += (panim[i]->fps / (panim[i]->numframes - 1)) * weight[i]; + } + } + return t; +} + +//----------------------------------------------------------------------------- +// Purpose: returns length (in seconds) of a sequence (seconds/cycle) +//----------------------------------------------------------------------------- + +float Studio_Duration( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[] ) +{ + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); + float cps = Studio_CPS( pStudioHdr, seqdesc, iSequence, poseParameter ); + + if( cps == 0 ) + return 0.0f; + + return 1.0f/cps; +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate changes in position and angle relative to the start of an animations cycle +// Output: updated position and angle, relative to the origin +// returns false if animation is not a movement animation +//----------------------------------------------------------------------------- + +bool Studio_AnimPosition( mstudioanimdesc_t *panim, float flCycle, Vector &vecPos, QAngle &vecAngle ) +{ + float prevframe = 0; + vecPos.Init( ); + vecAngle.Init( ); + + if (panim->nummovements == 0) + return false; + + int iLoops = 0; + if (flCycle > 1.0) + { + iLoops = (int)flCycle; + } + else if (flCycle < 0.0) + { + iLoops = (int)flCycle - 1; + } + flCycle = flCycle - iLoops; + + float flFrame = flCycle * (panim->numframes - 1); + + for (int i = 0; i < panim->nummovements; i++) + { + mstudiomovement_t *pmove = panim->pMovement( i ); + + if (pmove->endframe >= flFrame) + { + float f = (flFrame - prevframe) / (pmove->endframe - prevframe); + + float d = pmove->v0 * f + 0.5 * (pmove->v1 - pmove->v0) * f * f; + + vecPos = vecPos + d * pmove->vector; + vecAngle.y = vecAngle.y * (1 - f) + pmove->angle * f; + if (iLoops != 0) + { + mstudiomovement_t *pmove = panim->pMovement( panim->nummovements - 1 ); + vecPos = vecPos + iLoops * pmove->position; + vecAngle.y = vecAngle.y + iLoops * pmove->angle; + } + return true; + } + else + { + prevframe = pmove->endframe; + vecPos = pmove->position; + vecAngle.y = pmove->angle; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate instantaneous velocity in ips at a given point +// in the animations cycle +// Output: velocity vector, relative to identity orientation +// returns false if animation is not a movement animation +//----------------------------------------------------------------------------- + +bool Studio_AnimVelocity( mstudioanimdesc_t *panim, float flCycle, Vector &vecVelocity ) +{ + float prevframe = 0; + + float flFrame = flCycle * (panim->numframes - 1); + flFrame = flFrame - (int)(flFrame / (panim->numframes - 1)); + + for (int i = 0; i < panim->nummovements; i++) + { + mstudiomovement_t *pmove = panim->pMovement( i ); + + if (pmove->endframe >= flFrame) + { + float f = (flFrame - prevframe) / (pmove->endframe - prevframe); + + float vel = pmove->v0 * (1 - f) + pmove->v1 * f; + // scale from per block to per sec velocity + vel = vel * panim->fps / (pmove->endframe - prevframe); + + vecVelocity = pmove->vector * vel; + return true; + } + else + { + prevframe = pmove->endframe; + } + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate changes in position and angle between two points in an animation cycle +// Output: updated position and angle, relative to CycleFrom being at the origin +// returns false if animation is not a movement animation +//----------------------------------------------------------------------------- + +bool Studio_AnimMovement( mstudioanimdesc_t *panim, float flCycleFrom, float flCycleTo, Vector &deltaPos, QAngle &deltaAngle ) +{ + if (panim->nummovements == 0) + return false; + + Vector startPos; + QAngle startA; + Studio_AnimPosition( panim, flCycleFrom, startPos, startA ); + + Vector endPos; + QAngle endA; + Studio_AnimPosition( panim, flCycleTo, endPos, endA ); + + Vector tmp = endPos - startPos; + deltaAngle.y = endA.y - startA.y; + VectorYawRotate( tmp, -startA.y, deltaPos ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: finds how much of an animation to play to move given linear distance +//----------------------------------------------------------------------------- + +float Studio_FindAnimDistance( mstudioanimdesc_t *panim, float flDist ) +{ + float prevframe = 0; + + if (flDist <= 0) + return 0.0; + + for (int i = 0; i < panim->nummovements; i++) + { + mstudiomovement_t *pmove = panim->pMovement( i ); + + float flMove = (pmove->v0 + pmove->v1) * 0.5; + + if (flMove >= flDist) + { + float root1, root2; + + // d = V0 * t + 1/2 (V1-V0) * t^2 + if (SolveQuadratic( 0.5 * (pmove->v1 - pmove->v0), pmove->v0, -flDist, root1, root2 )) + { + float cpf = 1.0 / (panim->numframes - 1); // cycles per frame + + return (prevframe + root1 * (pmove->endframe - prevframe)) * cpf; + } + return 0.0; + } + else + { + flDist -= flMove; + prevframe = pmove->endframe; + } + } + return 1.0; +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate changes in position and angle between two points in a sequences cycle +// Output: updated position and angle, relative to CycleFrom being at the origin +// returns false if sequence is not a movement sequence +//----------------------------------------------------------------------------- + +bool Studio_SeqMovement( const CStudioHdr *pStudioHdr, int iSequence, float flCycleFrom, float flCycleTo, const float poseParameter[], Vector &deltaPos, QAngle &deltaAngles ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); + + Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); + + deltaPos.Init( ); + deltaAngles.Init( ); + + bool found = false; + + for (int i = 0; i < 4; i++) + { + if (weight[i]) + { + Vector localPos; + QAngle localAngles; + + localPos.Init(); + localAngles.Init(); + + if (Studio_AnimMovement( panim[i], flCycleFrom, flCycleTo, localPos, localAngles )) + { + found = true; + deltaPos = deltaPos + localPos * weight[i]; + // FIXME: this makes no sense + deltaAngles = deltaAngles + localAngles * weight[i]; + } + else if (!(panim[i]->flags & STUDIO_DELTA) && panim[i]->nummovements == 0 && seqdesc.weight(0) > 0.0) + { + found = true; + } + } + } + return found; +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate instantaneous velocity in ips at a given point in the sequence's cycle +// Output: velocity vector, relative to identity orientation +// returns false if sequence is not a movement sequence +//----------------------------------------------------------------------------- + +bool Studio_SeqVelocity( const CStudioHdr *pStudioHdr, int iSequence, float flCycle, const float poseParameter[], Vector &vecVelocity ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); + Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); + + vecVelocity.Init( ); + + bool found = false; + + for (int i = 0; i < 4; i++) + { + if (weight[i]) + { + Vector vecLocalVelocity; + + if (Studio_AnimVelocity( panim[i], flCycle, vecLocalVelocity )) + { + vecVelocity = vecVelocity + vecLocalVelocity * weight[i]; + found = true; + } + } + } + return found; +} + +//----------------------------------------------------------------------------- +// Purpose: finds how much of an sequence to play to move given linear distance +//----------------------------------------------------------------------------- + +float Studio_FindSeqDistance( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[], float flDist ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); + Studio_SeqAnims( pStudioHdr, seqdesc, iSequence, poseParameter, panim, weight ); + + float flCycle = 0; + + for (int i = 0; i < 4; i++) + { + if (weight[i]) + { + float flLocalCycle = Studio_FindAnimDistance( panim[i], flDist ); + flCycle = flCycle + flLocalCycle * weight[i]; + } + } + return flCycle; +} + +//----------------------------------------------------------------------------- +// Purpose: lookup attachment by name +//----------------------------------------------------------------------------- + +int Studio_FindAttachment( const CStudioHdr *pStudioHdr, const char *pAttachmentName ) +{ + if ( pStudioHdr && pStudioHdr->SequencesAvailable() ) + { + // Extract the bone index from the name + for (int i = 0; i < pStudioHdr->GetNumAttachments(); i++) + { + if (!stricmp(pAttachmentName,pStudioHdr->pAttachment(i).pszName( ))) + { + return i; + } + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: lookup attachments by substring. Randomly return one of the matching attachments. +//----------------------------------------------------------------------------- + +int Studio_FindRandomAttachment( const CStudioHdr *pStudioHdr, const char *pAttachmentName ) +{ + if ( pStudioHdr ) + { + // First move them all matching attachments into a list + CUtlVector matchingAttachments; + + // Extract the bone index from the name + for (int i = 0; i < pStudioHdr->GetNumAttachments(); i++) + { + if ( strstr( pStudioHdr->pAttachment(i).pszName(), pAttachmentName ) ) + { + matchingAttachments.AddToTail(i); + } + } + + // Then randomly return one of the attachments + if ( matchingAttachments.Size() > 0 ) + return matchingAttachments[ RandomInt( 0, matchingAttachments.Size()-1 ) ]; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: lookup bone by name +//----------------------------------------------------------------------------- + +int Studio_BoneIndexByName( const CStudioHdr *pStudioHdr, const char *pName ) +{ + // binary search for the bone matching pName + int start = 0, end = pStudioHdr->numbones()-1; + const byte *pBoneTable = pStudioHdr->GetBoneTableSortedByName(); + mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); + while (start <= end) + { + int mid = (start + end) >> 1; + int cmp = Q_stricmp( pbones[pBoneTable[mid]].pszName(), pName ); + + if ( cmp < 0 ) + { + start = mid + 1; + } + else if ( cmp > 0 ) + { + end = mid - 1; + } + else + { + return pBoneTable[mid]; + } + } + return -1; +} + +const char *Studio_GetDefaultSurfaceProps( CStudioHdr *pstudiohdr ) +{ + return pstudiohdr->pszSurfaceProp(); +} + +float Studio_GetMass( CStudioHdr *pstudiohdr ) +{ + return pstudiohdr->mass(); +} + +//----------------------------------------------------------------------------- +// Purpose: return pointer to sequence key value buffer +//----------------------------------------------------------------------------- + +const char *Studio_GetKeyValueText( const CStudioHdr *pStudioHdr, int iSequence ) +{ + if (pStudioHdr && pStudioHdr->SequencesAvailable()) + { + if (iSequence >= 0 && iSequence < pStudioHdr->GetNumSeq()) + { + return pStudioHdr->pSeqdesc( iSequence ).KeyValueText(); + } + } + return NULL; +} + +bool Studio_PrefetchSequence( const CStudioHdr *pStudioHdr, int iSequence ) +{ + bool pendingload = false; + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( iSequence ); + int size0 = seqdesc.groupsize[ 0 ]; + int size1 = seqdesc.groupsize[ 1 ]; + for ( int i = 0; i < size0; ++i ) + { + for ( int j = 0; j < size1; ++j ) + { + mstudioanimdesc_t &animdesc = pStudioHdr->pAnimdesc( seqdesc.anim( i, j ) ); + mstudioanim_t *panim = animdesc.pAnim(); + if ( !panim ) + { + pendingload = true; + } + } + } + + // Everything for this sequence is resident? + return !pendingload; +} diff --git a/public/bspfile.h b/public/bspfile.h index c1b68580..92e2f188 100644 --- a/public/bspfile.h +++ b/public/bspfile.h @@ -916,7 +916,7 @@ public: inline void doverlay_t::SetFaceCount( unsigned short count ) { - Assert( count >= 0 && (count & OVERLAY_RENDER_ORDER_MASK) == 0 ); + Assert( (count & OVERLAY_RENDER_ORDER_MASK) == 0 ); m_nFaceCountAndRenderOrder &= OVERLAY_RENDER_ORDER_MASK; m_nFaceCountAndRenderOrder |= (count & ~OVERLAY_RENDER_ORDER_MASK); } @@ -928,7 +928,7 @@ inline unsigned short doverlay_t::GetFaceCount() const inline void doverlay_t::SetRenderOrder( unsigned short order ) { - Assert( order >= 0 && order < OVERLAY_NUM_RENDER_ORDERS ); + Assert( order < OVERLAY_NUM_RENDER_ORDERS ); m_nFaceCountAndRenderOrder &= ~OVERLAY_RENDER_ORDER_MASK; m_nFaceCountAndRenderOrder |= (order << (16 - OVERLAY_RENDER_ORDER_NUM_BITS)); // leave 2 bits for render order. } @@ -969,7 +969,7 @@ public: inline void dwateroverlay_t::SetFaceCount( unsigned short count ) { - Assert( count >= 0 && (count & WATEROVERLAY_RENDER_ORDER_MASK) == 0 ); + Assert((count & WATEROVERLAY_RENDER_ORDER_MASK) == 0 ); m_nFaceCountAndRenderOrder &= WATEROVERLAY_RENDER_ORDER_MASK; m_nFaceCountAndRenderOrder |= (count & ~WATEROVERLAY_RENDER_ORDER_MASK); } @@ -981,7 +981,7 @@ inline unsigned short dwateroverlay_t::GetFaceCount() const inline void dwateroverlay_t::SetRenderOrder( unsigned short order ) { - Assert( order >= 0 && order < WATEROVERLAY_NUM_RENDER_ORDERS ); + Assert( order < WATEROVERLAY_NUM_RENDER_ORDERS ); m_nFaceCountAndRenderOrder &= ~WATEROVERLAY_RENDER_ORDER_MASK; m_nFaceCountAndRenderOrder |= ( order << ( 16 - WATEROVERLAY_RENDER_ORDER_NUM_BITS ) ); // leave 2 bits for render order. } diff --git a/public/builddisp.cpp b/public/builddisp.cpp index caac04f7..8f3fe2f9 100644 --- a/public/builddisp.cpp +++ b/public/builddisp.cpp @@ -1,2995 +1,2998 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -//#include -#include -#include -#include "builddisp.h" -#include "collisionutils.h" -#include "vstdlib/strtools.h" -#include "tier0/dbg.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CalcBarycentricCooefs( Vector const &v0, Vector const &v1, Vector const &v2, - Vector const &pt, float &c0, float &c1, float &c2 ) -{ - Vector vSeg0, vSeg1, vCross; - vSeg0 = v1 - v0; - vSeg1 = v2 - v0; - - // get the area of the triangle - vCross = vSeg0.Cross( vSeg1 ); - float totalArea = vCross.Length() * 0.5f; - float ooTotalArea = totalArea ? 1.0f / totalArea : 0.0f; - - // get the area for cooeficient 0 (pt, v1, v2) - vSeg0 = v1 - pt; - vSeg1 = v2 - pt; - vCross = vSeg0.Cross( vSeg1 ); - float subArea = vCross.Length() * 0.5f; - c0 = subArea * ooTotalArea; - - // get the area for cooeficient 1 (v0, pt, v2) - vSeg0 = v2 - pt; - vSeg1 = v0 - pt; - vCross = vSeg0.Cross( vSeg1 ); - subArea = vCross.Length() * 0.5f; - c1 = subArea * ooTotalArea; - - // get the area for cooeficient 2 (v0, v1, pt) - vSeg0 = v0 - pt; - vSeg1 = v1 - pt; - vCross = vSeg0.Cross( vSeg1 ); - subArea = vCross.Length() * 0.5f; - c2 = subArea * ooTotalArea; - - float cTotal = c0 + c1 + c2; - if ( FloatMakePositive( 1.0f - cTotal ) < 1e-3 ) - return true; - - return false; -} - -// For some reason, the global optimizer screws up the recursion here. disable the global optimizations to fix this. -// IN VC++ 6.0 -#pragma optimize( "g", off ) - -CCoreDispSurface::CCoreDispSurface() -{ - Init(); -} - - -//============================================================================= -// -// CDispSurface Functions -// - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispSurface::Init( void ) -{ - m_Index = -1; - - m_PointCount = 0; - int i; - for( i = 0; i < QUAD_POINT_COUNT; i++ ) - { - VectorClear( m_Points[i] ); - VectorClear( m_Normals[i] ); - Vector2DClear( m_TexCoords[i] ); - - for( int j = 0; j < NUM_BUMP_VECTS+1; j++ ) - { - Vector2DClear( m_LuxelCoords[i][j] ); - } - - m_Alphas[i] = 1.0f; - } - - m_PointStartIndex = -1; - VectorClear( m_PointStart ); - VectorClear( sAxis ); - VectorClear( tAxis ); - - for( i = 0; i < 4; i++ ) - { - m_EdgeNeighbors[i].SetInvalid(); - m_CornerNeighbors[i].SetInvalid(); - } - - m_Flags = 0; - m_Contents = 0; -} - - -void CCoreDispSurface::SetNeighborData( const CDispNeighbor edgeNeighbors[4], const CDispCornerNeighbors cornerNeighbors[4] ) -{ - for ( int i=0; i < 4; i++ ) - { - m_EdgeNeighbors[i] = edgeNeighbors[i]; - m_CornerNeighbors[i] = cornerNeighbors[i]; - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispSurface::GeneratePointStartIndexFromMappingAxes( Vector const &sAxis, Vector const &tAxis ) -{ - if( m_PointStartIndex != -1 ) - return; - - int numIndices = 0; - int indices[4]; - int offsetIndex; - - // - // project all points on to the v-axis first and find the minimum - // - float minValue = DotProduct( tAxis, m_Points[0] ); - indices[numIndices] = 0; - numIndices++; - - int i; - for( i = 1; i < m_PointCount; i++ ) - { - float value = DotProduct( tAxis, m_Points[i] ); - float delta = ( value - minValue ); - delta = FloatMakePositive( delta ); - if( delta < 0.1 ) - { - indices[numIndices] = i; - numIndices++; - } - else if( value < minValue ) - { - minValue = value; - indices[0] = i; - numIndices = 1; - } - } - - // - // break ties with the u-axis projection - // - minValue = DotProduct( sAxis, m_Points[indices[0]] ); - offsetIndex = indices[0]; - - for( i = 1; i < numIndices; i++ ) - { - float value = DotProduct( sAxis, m_Points[indices[i]] ); - if( ( value < minValue ) ) - { - minValue = value; - offsetIndex = indices[i]; - } - } - - m_PointStartIndex = offsetIndex; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CCoreDispSurface::GenerateSurfPointStartIndex( void ) -{ - // - // get the minimum surface component values - // - Vector bMin; - VectorFill( bMin, 99999.0f ); - - int i; - for( i = 0; i < QUAD_POINT_COUNT; i++ ) - { - for( int j = 0; j < 3; j++ ) - { - if( m_Points[i][j] < bMin[j] ) - { - bMin[j] = m_Points[i][j]; - } - } - } - - // - // find the point closest to the minimum, that is the start point - // - int minIndex = -1; - float minDistance = 999999999.0f; - for( i = 0; i < QUAD_POINT_COUNT; i++ ) - { - Vector segment; - segment = m_Points[i] - bMin; - float distanceSq = segment.LengthSqr(); - if( distanceSq < minDistance ) - { - minDistance = distanceSq; - minIndex = i; - } - } - - m_PointStartIndex = minIndex; - - return minIndex; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CCoreDispSurface::FindSurfPointStartIndex( void ) -{ - if( m_PointStartIndex != -1 ) - return m_PointStartIndex; - - int minIndex = -1; - float minDistance = 999999999.0f; - - for( int i = 0; i < QUAD_POINT_COUNT; i++ ) - { - Vector segment; - VectorSubtract( m_PointStart, m_Points[i], segment ); - float distanceSq = segment.LengthSqr(); - if( distanceSq < minDistance ) - { - minDistance = distanceSq; - minIndex = i; - } - } - - m_PointStartIndex = minIndex; - - return minIndex; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispSurface::AdjustSurfPointData( void ) -{ - Vector tmpPoints[4]; - Vector tmpNormals[4]; - Vector2D tmpTexCoords[4]; - Vector2D tmpLuxelCoords[4][4]; - float tmpAlphas[4]; - - int i; - for( i = 0; i < QUAD_POINT_COUNT; i++ ) - { - VectorCopy( m_Points[i], tmpPoints[i] ); - VectorCopy( m_Normals[i], tmpNormals[i] ); - Vector2DCopy( m_TexCoords[i], tmpTexCoords[i] ); - - for( int j = 0; j < ( NUM_BUMP_VECTS + 1 ); j++ ) - { - Vector2DCopy( m_LuxelCoords[j][i], tmpLuxelCoords[j][i] ); - } - - tmpAlphas[i] = m_Alphas[i]; - } - - for( i = 0; i < QUAD_POINT_COUNT; i++ ) - { - VectorCopy( tmpPoints[(i+m_PointStartIndex)%4], m_Points[i] ); - VectorCopy( tmpNormals[(i+m_PointStartIndex)%4], m_Normals[i] ); - Vector2DCopy( tmpTexCoords[(i+m_PointStartIndex)%4], m_TexCoords[i] ); - -// for( int j = 0; j < ( NUM_BUMP_VECTS + 1 ); j++ ) -// { -// Vector2DCopy( tmpLuxelCoords[j][(i+m_PointStartIndex)%4], m_LuxelCoords[j][i] ); -// } - - m_Alphas[i] = tmpAlphas[i]; - } -} - - -//============================================================================= -// -// CDispNode Functions -// - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispNode::Init( void ) -{ - VectorClear( m_BBox[0] ); - VectorClear( m_BBox[1] ); - - m_ErrorTerm = 0.0f; - - m_VertIndex = -1; - - int j; - for( j = 0; j < MAX_NEIGHBOR_NODE_COUNT; j++ ) - { - m_NeighborVertIndices[j] = -1; - } - - for( j = 0; j < MAX_SURF_AT_NODE_COUNT; j++ ) - { - VectorClear( m_SurfBBoxes[j][0] ); - VectorClear( m_SurfBBoxes[j][1] ); - VectorClear( m_SurfPlanes[j].normal ); - m_SurfPlanes[j].dist = 0.0f; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void GetDispNodeTriVerts( CCoreDispInfo *pDisp, int nodeIndex, int triIndex, Vector& v1, Vector& v2, Vector& v3 ) -{ - // get the node - CCoreDispNode *pNode = pDisp->GetNode( nodeIndex ); - - switch( triIndex ) - { - case 0: - { - pDisp->GetVert( pNode->GetNeighborVertIndex( 4 ), v1 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 0 ), v2 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 3 ), v3 ); - return; - } - case 1: - { - pDisp->GetVert( pNode->GetNeighborVertIndex( 3 ), v1 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 0 ), v2 ); - pDisp->GetVert( pNode->GetCenterVertIndex(), v3 ); - return; - } - case 2: - { - pDisp->GetVert( pNode->GetNeighborVertIndex( 3 ), v1 ); - pDisp->GetVert( pNode->GetCenterVertIndex(), v2 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 5 ), v3 ); - return; - } - case 3: - { - pDisp->GetVert( pNode->GetNeighborVertIndex( 5 ), v1 ); - pDisp->GetVert( pNode->GetCenterVertIndex(), v2 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 2 ), v3 ); - return; - } - case 4: - { - pDisp->GetVert( pNode->GetNeighborVertIndex( 0 ), v1 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 6 ), v2 ); - pDisp->GetVert( pNode->GetCenterVertIndex(), v3 ); - return; - } - case 5: - { - pDisp->GetVert( pNode->GetCenterVertIndex(), v1 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 6 ), v2 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 1 ), v3 ); - return; - } - case 6: - { - pDisp->GetVert( pNode->GetCenterVertIndex(), v1 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 1 ), v2 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 2 ), v3 ); - return; - } - case 7: - { - pDisp->GetVert( pNode->GetNeighborVertIndex( 2 ), v1 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 1 ), v2 ); - pDisp->GetVert( pNode->GetNeighborVertIndex( 7 ), v3 ); - return; - } - default: { return; } - } -} - - -//============================================================================= -// -// CCoreDispInfo Functions -// - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CCoreDispInfo::CCoreDispInfo() -{ - m_pVerts = NULL; - m_RenderIndices = NULL; - m_Nodes = NULL; - m_pTris = NULL; - - // initialize the base surface data - m_Surf.Init(); - - // - // initialize the disp info - // - m_Power = 0; - m_Elevation = 0.0f; - m_RenderIndexCount = 0; - m_RenderCounter = 0; - m_bTouched = false; - - m_pNext = NULL; - - m_ppListBase = NULL; - m_ListSize = 0; - m_nListIndex = -1; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CCoreDispInfo::~CCoreDispInfo() -{ - if (m_pVerts) - delete [] m_pVerts; - if (m_RenderIndices) - delete [] m_RenderIndices; - if (m_Nodes) - delete [] m_Nodes; - if (m_pTris) - delete [] m_pTris; -} - - -#if 0 -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::InitSurf( int parentIndex, Vector points[4], Vector normals[4], - Vector2D texCoords[4], Vector2D lightCoords[4][4], int contents, int flags, - bool bGenerateSurfPointStart, Vector& startPoint, - bool bHasMappingAxes, Vector& uAxis, Vector& vAxis ) -{ - // save the "parent" index - m_Surf.m_Index = parentIndex; - - // - // save the surface points and point normals, texture coordinates, and - // lightmap coordinates - // - m_Surf.m_PointCount = CSurface::QUAD_POINT_COUNT; - for( int i = 0; i < CSurface::QUAD_POINT_COUNT; i++ ) - { - VectorCopy( points[i], m_Surf.m_Points[i] ); - - if( normals ) - { - VectorCopy( normals[i], m_Surf.m_pVerts[i].m_Normal ); - } - - if( texCoords ) - { - Vector2DCopy( texCoords[i], m_Surf.m_TexCoords[i] ); - } - - if( lightCoords ) - { - Assert( NUM_BUMP_VECTS == 3 ); - Vector2DCopy( lightCoords[0][i], m_Surf.m_LightCoords[i][0] ); - Vector2DCopy( lightCoords[1][i], m_Surf.m_LightCoords[i][1] ); - Vector2DCopy( lightCoords[2][i], m_Surf.m_LightCoords[i][2] ); - Vector2DCopy( lightCoords[3][i], m_Surf.m_LightCoords[i][3] ); - } - } - - // save the starting point - if( startPoint ) - { - VectorCopy( startPoint, m_Surf.m_PointStart ); - } - - // - // save the surface contents and flags - // - m_Contents = contents; - m_Flags = flags; - - // - // adjust surface points, texture coordinates, etc.... - // - if( bHasMappingAxes && ( m_Surf.m_PointStartIndex == -1 ) ) - { - GeneratePointStartIndexFromMappingAxes( uAxis, vAxis ); - } - else - { - // - // adjust the surf data - // - if( bGenerateSurfPointStart ) - { - GenerateSurfPointStartIndex(); - } - else - { - FindSurfPointStartIndex(); - } - } - - AdjustSurfPointData(); -} -#endif - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::InitDispInfo( int power, int minTess, float smoothingAngle, - float *alphas, Vector *dispVectorField, float *dispDistances ) -{ - Assert( power >= MIN_MAP_DISP_POWER && power <= MAX_MAP_DISP_POWER ); - - // - // general displacement data - // - m_Power = power; - - if ( ( minTess & 0x80000000 ) != 0 ) - { - // If the high bit is set, this represents FLAGS (SURF_NOPHYSICS_COLL, etc.) flags. - int nFlags = minTess; - nFlags &= ~0x80000000; - GetSurface()->SetFlags( nFlags ); - } - - // Allocate + initialize verts - int size = GetSize(); - m_pVerts = new CoreDispVert_t[size]; - - int nIndexCount = size * 2 * 3; - m_RenderIndices = new unsigned short[nIndexCount]; - - int nNodeCount = GetNodeCount(power); - m_Nodes = new CCoreDispNode[nNodeCount]; - - int i; - for( i = 0; i < size; i++ ) - { - m_pVerts[i].m_FieldVector.Init(); - m_pVerts[i].m_SubdivPos.Init(); - m_pVerts[i].m_SubdivNormal.Init(); - - m_pVerts[i].m_FieldDistance = 0.0f; - - m_pVerts[i].m_Vert.Init(); - m_pVerts[i].m_FlatVert.Init(); - m_pVerts[i].m_Normal.Init(); - m_pVerts[i].m_TangentS.Init(); - m_pVerts[i].m_TangentT.Init(); - m_pVerts[i].m_TexCoord.Init(); - - for( int j = 0; j < ( NUM_BUMP_VECTS + 1 ); j++ ) - { - m_pVerts[i].m_LuxelCoords[j].Init(); - } - - m_pVerts[i].m_Alpha = 0.0f; - } - - for( i = 0; i < nIndexCount; i++ ) - { - m_RenderIndices[i] = 0; - } - - for( i = 0; i < nNodeCount; i++ ) - { - m_Nodes[i].Init(); - } - - // - // save the displacement vector field and distances within the field - // offset have been combined with fieldvectors at this point!!! - // - if (alphas && dispVectorField && dispDistances) - { - for( i = 0; i < size; i++ ) - { - VectorCopy( dispVectorField[i], m_pVerts[i].m_FieldVector ); - m_pVerts[i].m_FieldDistance = dispDistances[i]; - m_pVerts[i].m_Alpha = alphas[i]; - } - } - - // Init triangle information. - int nTriCount = GetTriCount(); - if ( nTriCount != 0 ) - { - m_pTris = new CoreDispTri_t[nTriCount]; - if ( m_pTris ) - { - InitTris(); - } - } -} - - -void CCoreDispInfo::InitDispInfo( int power, int minTess, float smoothingAngle, const CDispVert *pVerts, - const CDispTri *pTris ) -{ - Vector vectors[MAX_DISPVERTS]; - float dists[MAX_DISPVERTS]; - float alphas[MAX_DISPVERTS]; - - int nVerts = NUM_DISP_POWER_VERTS( power ); - for ( int i=0; i < nVerts; i++ ) - { - vectors[i] = pVerts[i].m_vVector; - dists[i] = pVerts[i].m_flDist; - alphas[i] = pVerts[i].m_flAlpha; - } - - InitDispInfo( power, minTess, smoothingAngle, alphas, vectors, dists ); - - int nTris = NUM_DISP_POWER_TRIS( power ); - for ( int iTri = 0; iTri < nTris; ++iTri ) - { - m_pTris[iTri].m_uiTags = pTris[iTri].m_uiTags; - } -} - - -void CCoreDispInfo::SetDispUtilsHelperInfo( CCoreDispInfo **ppListBase, int listSize ) -{ - m_ppListBase = ppListBase; - m_ListSize = listSize; -} - -const CPowerInfo* CCoreDispInfo::GetPowerInfo() const -{ - return ::GetPowerInfo( GetPower() ); -} - -CDispNeighbor* CCoreDispInfo::GetEdgeNeighbor( int index ) -{ - return GetSurface()->GetEdgeNeighbor( index ); -} - -CDispCornerNeighbors* CCoreDispInfo::GetCornerNeighbors( int index ) -{ - return GetSurface()->GetCornerNeighbors( index ); -} - -CDispUtilsHelper* CCoreDispInfo::GetDispUtilsByIndex( int index ) -{ - Assert( m_ppListBase ); - return index == 0xFFFF ? 0 : m_ppListBase[index]; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::BuildTriTLtoBR( int ndx ) -{ - // get width and height of displacement maps - int nWidth = ( ( 1 << m_Power ) + 1 ); - - m_RenderIndices[m_RenderIndexCount] = ndx; - m_RenderIndices[m_RenderIndexCount+1] = ndx + nWidth; - m_RenderIndices[m_RenderIndexCount+2] = ndx + 1; - m_RenderIndexCount += 3; - - m_RenderIndices[m_RenderIndexCount] = ndx + 1; - m_RenderIndices[m_RenderIndexCount+1] = ndx + nWidth; - m_RenderIndices[m_RenderIndexCount+2] = ndx + nWidth + 1; - m_RenderIndexCount += 3; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::BuildTriBLtoTR( int ndx ) -{ - // get width and height of displacement maps - int nWidth = ( ( 1 << m_Power ) + 1 ); - - m_RenderIndices[m_RenderIndexCount] = ndx; - m_RenderIndices[m_RenderIndexCount+1] = ndx + nWidth; - m_RenderIndices[m_RenderIndexCount+2] = ndx + nWidth + 1; - m_RenderIndexCount += 3; - - m_RenderIndices[m_RenderIndexCount] = ndx; - m_RenderIndices[m_RenderIndexCount+1] = ndx + nWidth + 1; - m_RenderIndices[m_RenderIndexCount+2] = ndx + 1; - m_RenderIndexCount += 3; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::GenerateCollisionSurface( void ) -{ - // get width and height of displacement maps - int nWidth = ( ( 1 << m_Power ) + 1 ); - int nHeight = ( ( 1 << m_Power ) + 1 ); - - // - // generate a fan tesselated (at quadtree node) rendering index list - // - m_RenderIndexCount = 0; - for ( int iV = 0; iV < ( nHeight - 1 ); iV++ ) - { - for ( int iU = 0; iU < ( nWidth - 1 ); iU++ ) - { - int ndx = ( iV * nWidth ) + iU; - - // test whether or not the index is odd - bool bOdd = ( ( ndx %2 ) == 1 ); - - // Top Left to Bottom Right - if( bOdd ) - { - BuildTriTLtoBR( ndx ); - } - // Bottom Left to Top Right - else - { - BuildTriBLtoTR( ndx ); - } - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::GenerateCollisionData( void ) -{ - GenerateCollisionSurface(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcTriSurfPlanes( int nodeIndex, int indices[8][3] ) -{ - // - // calculate plane info for each face - // - for( int i = 0; i < 8; i++ ) - { - Vector v[3]; - VectorCopy( m_pVerts[indices[i][0]].m_Vert, v[0] ); - VectorCopy( m_pVerts[indices[i][1]].m_Vert, v[1] ); - VectorCopy( m_pVerts[indices[i][2]].m_Vert, v[2] ); - - Vector seg[2]; - VectorSubtract( v[1], v[0], seg[0] ); - VectorSubtract( v[2], v[0], seg[1] ); - - Vector normal; - CrossProduct( seg[1], seg[0], normal ); - VectorNormalize( normal ); - float dist = DotProduct( v[0], normal ); - - m_Nodes[nodeIndex].SetTriPlane( i, normal, dist ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcRayBoundingBoxes( int nodeIndex, int indices[8][3] ) -{ - Vector triMin, triMax; - - for( int i = 0; i < 4; i++ ) - { - triMin[0] = triMax[0] = m_pVerts[indices[(i*2)][0]].m_Vert[0]; - triMin[1] = triMax[1] = m_pVerts[indices[(i*2)][0]].m_Vert[1]; - triMin[2] = triMax[2] = m_pVerts[indices[(i*2)][0]].m_Vert[2]; - - for( int j = 0; j < 3; j++ ) - { - // - // minimum - // - if( triMin[0] > m_pVerts[indices[(i*2)][j]].m_Vert[0] ) - triMin[0] = m_pVerts[indices[(i*2)][j]].m_Vert[0]; - if( triMin[0] > m_pVerts[indices[(i*2+1)][j]].m_Vert[0] ) - triMin[0] = m_pVerts[indices[(i*2+1)][j]].m_Vert[0]; - - if( triMin[1] > m_pVerts[indices[(i*2)][j]].m_Vert[1] ) - triMin[1] = m_pVerts[indices[(i*2)][j]].m_Vert[1]; - if( triMin[1] > m_pVerts[indices[(i*2+1)][j]].m_Vert[1] ) - triMin[1] = m_pVerts[indices[(i*2+1)][j]].m_Vert[1]; - - if( triMin[2] > m_pVerts[indices[(i*2)][j]].m_Vert[2] ) - triMin[2] = m_pVerts[indices[(i*2)][j]].m_Vert[2]; - if( triMin[2] > m_pVerts[indices[(i*2+1)][j]].m_Vert[2] ) - triMin[2] = m_pVerts[indices[(i*2+1)][j]].m_Vert[2]; - - // - // maximum - // - if( triMax[0] < m_pVerts[indices[(i*2)][j]].m_Vert[0] ) - triMax[0] = m_pVerts[indices[(i*2)][j]].m_Vert[0]; - if( triMax[0] < m_pVerts[indices[(i*2+1)][j]].m_Vert[0] ) - triMax[0] = m_pVerts[indices[(i*2+1)][j]].m_Vert[0]; - - if( triMax[1] < m_pVerts[indices[(i*2)][j]].m_Vert[1] ) - triMax[1] = m_pVerts[indices[(i*2)][j]].m_Vert[1]; - if( triMax[1] < m_pVerts[indices[(i*2+1)][j]].m_Vert[1] ) - triMax[1] = m_pVerts[indices[(i*2+1)][j]].m_Vert[1]; - - if( triMax[2] < m_pVerts[indices[(i*2)][j]].m_Vert[2] ) - triMax[2] = m_pVerts[indices[(i*2)][j]].m_Vert[2]; - if( triMax[2] < m_pVerts[indices[(i*2+1)][j]].m_Vert[2] ) - triMax[2] = m_pVerts[indices[(i*2+1)][j]].m_Vert[2]; - } - - m_Nodes[nodeIndex].SetRayBoundingBox( i, triMin, triMax ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcTriSurfBoundingBoxes( int nodeIndex, int indices[8][3] ) -{ - Vector triMin, triMax; - - for( int i = 0; i < 8; i++ ) - { - m_Nodes[nodeIndex].GetTriBoundingBox( i, triMin, triMax ); - - for( int j = 0; j < 3; j++ ) - { - // - // minimum - // - if( triMin[0] > m_pVerts[indices[i][j]].m_Vert[0] ) - triMin[0] = m_pVerts[indices[i][j]].m_Vert[0]; - - if( triMin[1] > m_pVerts[indices[i][j]].m_Vert[1] ) - triMin[1] = m_pVerts[indices[i][j]].m_Vert[1]; - - if( triMin[2] > m_pVerts[indices[i][j]].m_Vert[2] ) - triMin[2] = m_pVerts[indices[i][j]].m_Vert[2]; - - // - // maximum - // - if( triMax[0] < m_pVerts[indices[i][j]].m_Vert[0] ) - triMax[0] = m_pVerts[indices[i][j]].m_Vert[0]; - - if( triMax[1] < m_pVerts[indices[i][j]].m_Vert[1] ) - triMax[1] = m_pVerts[indices[i][j]].m_Vert[1]; - - if( triMax[2] < m_pVerts[indices[i][j]].m_Vert[2] ) - triMax[2] = m_pVerts[indices[i][j]].m_Vert[2]; - } - - m_Nodes[nodeIndex].SetTriBoundingBox( i, triMin, triMax ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcTriSurfIndices( int nodeIndex, int indices[8][3] ) -{ - indices[0][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 4 ); - indices[0][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 0 ); - indices[0][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 3 ); - - indices[1][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 3 ); - indices[1][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 0 ); - indices[1][2] = m_Nodes[nodeIndex].GetCenterVertIndex(); - - indices[2][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 3 ); - indices[2][1] = m_Nodes[nodeIndex].GetCenterVertIndex(); - indices[2][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 5 ); - - indices[3][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 5 ); - indices[3][1] = m_Nodes[nodeIndex].GetCenterVertIndex(); - indices[3][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 2 ); - - indices[4][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 0 ); - indices[4][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 6 ); - indices[4][2] = m_Nodes[nodeIndex].GetCenterVertIndex(); - - indices[5][0] = m_Nodes[nodeIndex].GetCenterVertIndex(); - indices[5][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 6 ); - indices[5][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 1 ); - - indices[6][0] = m_Nodes[nodeIndex].GetCenterVertIndex(); - indices[6][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 1 ); - indices[6][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 2 ); - - indices[7][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 2 ); - indices[7][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 1 ); - indices[7][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 7 ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcTriSurfInfoAtNode( int nodeIndex ) -{ - int indices[8][3]; - - CalcTriSurfIndices( nodeIndex, indices ); - CalcTriSurfBoundingBoxes( nodeIndex, indices ); - CalcRayBoundingBoxes( nodeIndex, indices ); - CalcTriSurfPlanes( nodeIndex, indices ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcMinMaxBoundingBoxAtNode( int nodeIndex, Vector& bMin, Vector& bMax ) -{ - // get the child node index - int childNodeIndex = GetNodeChild( m_Power, nodeIndex, 4 ); - - // get initial bounding box values - m_Nodes[childNodeIndex].GetBoundingBox( bMin, bMax ); - - Vector nodeMin, nodeMax; - for( int i = 1, j = 5; i < 4; i++, j++ ) - { - // - // get the child node bounding box - // - childNodeIndex = GetNodeChild( m_Power, nodeIndex, j ); - m_Nodes[childNodeIndex].GetBoundingBox( nodeMin, nodeMax ); - - // minimum - if( bMin[0] > nodeMin[0] ) - bMin[0] = nodeMin[0]; - - if( bMin[1] > nodeMin[1] ) - bMin[1] = nodeMin[1]; - - if( bMin[2] > nodeMin[2] ) - bMin[2] = nodeMin[2]; - - // maximum - if( bMax[0] < nodeMax[0] ) - bMax[0] = nodeMax[0]; - - if( bMax[1] < nodeMax[1] ) - bMax[1] = nodeMax[1]; - - if( bMax[2] < nodeMax[2] ) - bMax[2] = nodeMax[2]; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcBoundingBoxAtNode( int nodeIndex ) -{ - Vector bMin, bMax; - - // - // initialize the minimum and maximum values for the bounding box - // - int level = GetNodeLevel( nodeIndex ); - - int vertIndex = m_Nodes[nodeIndex].GetCenterVertIndex(); - if( level == m_Power ) - { - VectorCopy( m_pVerts[vertIndex].m_Vert, bMin ); - VectorCopy( m_pVerts[vertIndex].m_Vert, bMax ); - } - else - { - CalcMinMaxBoundingBoxAtNode( nodeIndex, bMin, bMax ); - - if( bMin[0] > m_pVerts[vertIndex].m_Vert[0] ) - bMin[0] = m_pVerts[vertIndex].m_Vert[0]; - - if( bMin[1] > m_pVerts[vertIndex].m_Vert[1] ) - bMin[1] = m_pVerts[vertIndex].m_Vert[1]; - - if( bMin[2] > m_pVerts[vertIndex].m_Vert[2] ) - bMin[2] = m_pVerts[vertIndex].m_Vert[2]; - - - if( bMax[0] < m_pVerts[vertIndex].m_Vert[0] ) - bMax[0] = m_pVerts[vertIndex].m_Vert[0]; - - if( bMax[1] < m_pVerts[vertIndex].m_Vert[1] ) - bMax[1] = m_pVerts[vertIndex].m_Vert[1]; - - if( bMax[2] < m_pVerts[vertIndex].m_Vert[2] ) - bMax[2] = m_pVerts[vertIndex].m_Vert[2]; - } - - for( int i = 0; i < 8; i++ ) - { - int neighborVertIndex = m_Nodes[nodeIndex].GetNeighborVertIndex( i ); - - // - // minimum - // - if( bMin[0] > m_pVerts[neighborVertIndex].m_Vert[0] ) - bMin[0] = m_pVerts[neighborVertIndex].m_Vert[0]; - - if( bMin[1] > m_pVerts[neighborVertIndex].m_Vert[1] ) - bMin[1] = m_pVerts[neighborVertIndex].m_Vert[1]; - - if( bMin[2] > m_pVerts[neighborVertIndex].m_Vert[2] ) - bMin[2] = m_pVerts[neighborVertIndex].m_Vert[2]; - - // - // maximum - // - if( bMax[0] < m_pVerts[neighborVertIndex].m_Vert[0] ) - bMax[0] = m_pVerts[neighborVertIndex].m_Vert[0]; - - if( bMax[1] < m_pVerts[neighborVertIndex].m_Vert[1] ) - bMax[1] = m_pVerts[neighborVertIndex].m_Vert[1]; - - if( bMax[2] < m_pVerts[neighborVertIndex].m_Vert[2] ) - bMax[2] = m_pVerts[neighborVertIndex].m_Vert[2]; - } - - m_Nodes[nodeIndex].SetBoundingBox( bMin, bMax ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -float CCoreDispInfo::GetMaxErrorFromChildren( int nodeIndex, int level ) -{ - // - // check for children nodes - // - if( level == m_Power ) - return 0.0f; - - // - // get the child's error term and save the greatest error -- SW, SE, NW, NE - // - float errorTerm = 0.0f; - for( int i = 4; i < 8; i++ ) - { - int childIndex = GetNodeChild( m_Power, nodeIndex, i ); - - float nodeErrorTerm = m_Nodes[childIndex].GetErrorTerm(); - if( errorTerm < nodeErrorTerm ) - { - errorTerm = nodeErrorTerm; - } - } - - return errorTerm; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcErrorTermAtNode( int nodeIndex, int level ) -{ - if( level == m_Power ) - return; - - // - // get the vertex indices - // - int neighborVertIndices[9]; - for( int i = 0; i < 8; i++ ) - { - neighborVertIndices[i] = m_Nodes[nodeIndex].GetNeighborVertIndex( i ); - } - neighborVertIndices[8] = m_Nodes[nodeIndex].GetCenterVertIndex(); - - - // - // calculate the error terms - // - Vector segment; - Vector v; - - VectorAdd( m_pVerts[neighborVertIndices[5]].m_Vert, m_pVerts[neighborVertIndices[4]].m_Vert, v ); - VectorScale( v, 0.5f, v ); - VectorSubtract( m_pVerts[neighborVertIndices[0]].m_Vert, v, segment ); - float errorTerm = ( float )VectorLength( segment ); - - VectorAdd( m_pVerts[neighborVertIndices[5]].m_Vert, m_pVerts[neighborVertIndices[6]].m_Vert, v ); - VectorScale( v, 0.5f, v ); - VectorSubtract( m_pVerts[neighborVertIndices[1]].m_Vert, v, segment ); - if( errorTerm < ( float )VectorLength( segment ) ) - errorTerm = ( float )VectorLength( segment ); - - VectorAdd( m_pVerts[neighborVertIndices[6]].m_Vert, m_pVerts[neighborVertIndices[7]].m_Vert, v ); - VectorScale( v, 0.5f, v ); - VectorSubtract( m_pVerts[neighborVertIndices[2]].m_Vert, v, segment ); - if( errorTerm < ( float )VectorLength( segment ) ) - errorTerm = ( float )VectorLength( segment ); - - VectorAdd( m_pVerts[neighborVertIndices[7]].m_Vert, m_pVerts[neighborVertIndices[4]].m_Vert, v ); - VectorScale( v, 0.5f, v ); - VectorSubtract( m_pVerts[neighborVertIndices[3]].m_Vert, v, segment ); - if( errorTerm < ( float )VectorLength( segment ) ) - errorTerm = ( float )VectorLength( segment ); - - VectorAdd( m_pVerts[neighborVertIndices[4]].m_Vert, m_pVerts[neighborVertIndices[6]].m_Vert, v ); - VectorScale( v, 0.5f, v ); - VectorSubtract( m_pVerts[neighborVertIndices[8]].m_Vert, v, segment ); - if( errorTerm < ( float )VectorLength( segment ) ) - errorTerm = ( float )VectorLength( segment ); - - VectorAdd( m_pVerts[neighborVertIndices[5]].m_Vert, m_pVerts[neighborVertIndices[7]].m_Vert, v ); - VectorScale( v, 0.5f, v ); - VectorSubtract( m_pVerts[neighborVertIndices[8]].m_Vert, v, segment ); - if( errorTerm < ( float )VectorLength( segment ) ) - errorTerm = ( float )VectorLength( segment ); - - // - // add the max child's error term - // - errorTerm += GetMaxErrorFromChildren( nodeIndex, level ); - - // set the error term - m_Nodes[nodeIndex].SetErrorTerm( errorTerm ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcNeighborVertIndicesAtNode( int nodeIndex, int level ) -{ - // calculate the shift in direction in the matrix - int shift = ( 1 << ( m_Power - level ) ); - - // calculate the width, height of the displacement surface (are uniform) - int extent = ( ( 1 << m_Power ) + 1 ); - - // - // get the neighbor vertex indices (defining the surface at the node level) - // - for( int direction = 0; direction < 8; direction++ ) - { - // - // get the parent vertex index in component form - // - int posX = m_Nodes[nodeIndex].GetCenterVertIndex() % extent; - int posY = m_Nodes[nodeIndex].GetCenterVertIndex() / extent; - - // - // calculate the neighboring vertex indices for surface rendering - // - bool bError = false; - switch( direction ) - { - case WEST: { posX -= shift; break; } - case NORTH: { posY += shift; break; } - case EAST: { posX += shift; break; } - case SOUTH: { posY -= shift; break; } - case SOUTHWEST: { posX -= shift; posY -= shift; break; } - case SOUTHEAST: { posX += shift; posY -= shift; break; } - case NORTHWEST: { posX -= shift; posY += shift; break; } - case NORTHEAST: { posX += shift; posY += shift; break; } - default: { bError = true; break; } - } - - if( bError ) - { - m_Nodes[nodeIndex].SetNeighborVertIndex( direction, -99999 ); - } - else - { - m_Nodes[nodeIndex].SetNeighborVertIndex( direction, ( ( posY * extent ) + posX ) ); - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcNodeInfo( int nodeIndex, int terminationLevel ) -{ - // get the level of the current node - int level = GetNodeLevel( nodeIndex ); - - // - // get the node data at the termination level - // - if( level == terminationLevel ) - { - // get the neighbor vertex indices (used to create surface at node level) - CalcNeighborVertIndicesAtNode( nodeIndex, level ); - - // get the neighbor node indices - //CalcNeighborNodeIndicesAtNode( nodeIndex, level ); - - // calculate the error term at the node - CalcErrorTermAtNode( nodeIndex, level ); - - // calcluate the axial-aligned bounding box at the node - CalcBoundingBoxAtNode( nodeIndex ); - - // calculate the triangular surface info at the node - CalcTriSurfInfoAtNode( nodeIndex ); - - return; - } - - // - // continue recursion (down to nodes "children") - // - for( int i = 4; i < 8; i++ ) - { - int childIndex = GetNodeChild( m_Power, nodeIndex, i ); - CalcNodeInfo( childIndex, terminationLevel ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CCoreDispInfo::GetNodeVertIndexFromParentIndex( int level, int parentVertIndex, int direction ) -{ - // calculate the "shift" - int shift = ( 1 << ( m_Power - ( level + 1 ) ) ); - - // calculate the width and height of displacement (is uniform) - int extent = ( ( 1 << m_Power ) + 1 ); - - // get the parent vertex index in component form - int posX = parentVertIndex % extent; - int posY = parentVertIndex / extent; - - // - // calculate the child index based on the parent index and child - // direction - // - switch( direction ) - { - case SOUTHWEST: { posX -= shift; posY -= shift; break; } - case SOUTHEAST: { posX += shift; posY -= shift; break; } - case NORTHWEST: { posX -= shift; posY += shift; break; } - case NORTHEAST: { posX += shift; posY += shift; break; } - default: return -99999; - } - - // return the child vertex index - return ( ( posY * extent ) + posX ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcVertIndicesAtNodes( int nodeIndex ) -{ - // - // check for recursion termination ( node level = power ) - // - int level = GetNodeLevel( nodeIndex ); - if( level == m_Power ) - return; - - // - // get the children indices - SW, SE, NW, NE - // - int childIndices[4]; - int i, j; - for( i = 0, j = 4; i < 4; i++, j++ ) - { - childIndices[i] = GetNodeChild( m_Power, nodeIndex, j ); - int centerIndex = GetNodeVertIndexFromParentIndex( level, m_Nodes[nodeIndex].GetCenterVertIndex(), j ); - m_Nodes[childIndices[i]].SetCenterVertIndex( centerIndex ); - } - - // - // calculate the children's node vertex indices - // - for( i = 0; i < 4; i++ ) - { - CalcVertIndicesAtNodes( childIndices[i] ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::GenerateLODTree( void ) -{ - // - // calculate the displacement surface's vertex index at each quad-tree node - // centroid - // - int size = GetSize(); - int initialIndex = ( ( size - 1 ) >> 1 ); - m_Nodes[0].SetCenterVertIndex( initialIndex ); - CalcVertIndicesAtNodes( 0 ); - - // - // calculate the error terms, bounding boxes, and neighboring vertex indices - // at each node - // - for( int i = m_Power; i > 0; i-- ) - { - CalcNodeInfo( 0, i ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcDispSurfCoords( bool bLightMap, int lightmapID ) -{ - // - // get base surface texture coords - // - Vector2D texCoords[4]; - Vector2D luxelCoords[4]; - CCoreDispSurface *pSurf = GetSurface(); - - int i; - for( i = 0; i < 4; i++ ) - { - pSurf->GetTexCoord( i, texCoords[i] ); - pSurf->GetLuxelCoord( lightmapID, i, luxelCoords[i] ); - } - - // - // get images width and intervals along the edge - // - int postSpacing = GetPostSpacing(); - float ooInt = ( 1.0f / ( float )( postSpacing - 1 ) ); - - // - // calculate the parallel edge intervals - // - Vector2D edgeInt[2]; - if( !bLightMap ) - { - Vector2DSubtract( texCoords[1], texCoords[0], edgeInt[0] ); - Vector2DSubtract( texCoords[2], texCoords[3], edgeInt[1] ); - } - else - { - Vector2DSubtract( luxelCoords[1], luxelCoords[0], edgeInt[0] ); - Vector2DSubtract( luxelCoords[2], luxelCoords[3], edgeInt[1] ); - } - Vector2DMultiply( edgeInt[0], ooInt, edgeInt[0] ); - Vector2DMultiply( edgeInt[1], ooInt, edgeInt[1] ); - - // - // calculate the displacement points - // - for( i = 0; i < postSpacing; i++ ) - { - // - // position along parallel edges (start and end for a perpendicular segment) - // - Vector2D endPts[2]; - Vector2DMultiply( edgeInt[0], ( float )i, endPts[0] ); - Vector2DMultiply( edgeInt[1], ( float )i, endPts[1] ); - if( !bLightMap ) - { - Vector2DAdd( endPts[0], texCoords[0], endPts[0] ); - Vector2DAdd( endPts[1], texCoords[3], endPts[1] ); - } - else - { - Vector2DAdd( endPts[0], luxelCoords[0], endPts[0] ); - Vector2DAdd( endPts[1], luxelCoords[3], endPts[1] ); - } - - // - // interval length for perpendicular edge - // - Vector2D seg, segInt; - Vector2DSubtract( endPts[1], endPts[0], seg ); - Vector2DMultiply( seg, ooInt, segInt ); - - // - // calculate the material (texture or light) coordinate at each point - // - for( int j = 0; j < postSpacing; j++ ) - { - Vector2DMultiply( segInt, ( float )j, seg ); - - if( !bLightMap ) - { - Vector2DAdd( endPts[0], seg, m_pVerts[i*postSpacing+j].m_TexCoord ); - } - else - { - Vector2DAdd( endPts[0], seg, m_pVerts[i*postSpacing+j].m_LuxelCoords[lightmapID] ); - } - } - } -} - -#if 0 -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcDispSurfAlphas( void ) -{ - // - // get images width and intervals along the edge - // - int postSpacing = GetPostSpacing(); - float ooInt = ( 1.0f / ( float )( postSpacing - 1 ) ); - - // - // calculate the parallel edge intervals - // - float edgeInt[2]; - edgeInt[0] = m_Surf.m_Alpha[1] - m_Surf.m_Alpha[0]; - edgeInt[1] = m_Surf.m_Alpha[2] - m_Surf.m_Alpha[3]; - edgeInt[0] *= ooInt; - edgeInt[1] *= ooInt; - - // - // calculate the displacement points - // - for( int i = 0; i < postSpacing; i++ ) - { - // - // position along parallel edges (start and end for a perpendicular segment) - // - float endValues[2]; - - endValues[0] = edgeInt[0] * ( float )i; - endValues[1] = edgeInt[1] * ( float )i; - endValues[0] += m_Surf.m_Alpha[0]; - endValues[1] += m_Surf.m_Alpha[3]; - - // - // interval length for perpendicular edge - // - float seg, segInt; - seg = endValues[1] - endValues[0]; - segInt = seg * ooInt; - - // - // calculate the alpha value at each point - // - for( int j = 0; j < postSpacing; j++ ) - { - seg = segInt * ( float )j; - m_Alphas[i*postSpacing+j] = endValues[0] + seg; - } - } -} -#endif - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::GenerateDispSurfTangentSpaces( void ) -{ - // - // get texture axes from base surface - // - CCoreDispSurface *pSurf = GetSurface(); - Vector sAxis, tAxis; - pSurf->GetSAxis( sAxis ); - pSurf->GetTAxis( tAxis ); - - // - // calculate the tangent spaces - // - int size = GetSize(); - for( int i = 0; i < size; i++ ) - { - // - // create the axes - normals, tangents, and binormals - // - VectorCopy( tAxis, m_pVerts[i].m_TangentT ); - VectorNormalize( m_pVerts[i].m_TangentT ); - CrossProduct( m_pVerts[i].m_Normal, m_pVerts[i].m_TangentT, m_pVerts[i].m_TangentS ); - VectorNormalize( m_pVerts[i].m_TangentS ); - CrossProduct( m_pVerts[i].m_TangentS, m_pVerts[i].m_Normal, m_pVerts[i].m_TangentT ); - VectorNormalize( m_pVerts[i].m_TangentT ); - - Vector tmpVect; - Vector planeNormal; - pSurf->GetNormal( planeNormal ); - CrossProduct( sAxis, tAxis, tmpVect ); - if( DotProduct( planeNormal, tmpVect ) > 0.0f ) - { - VectorScale( m_pVerts[i].m_TangentS, -1.0f, m_pVerts[i].m_TangentS ); - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::CalcNormalFromEdges( int indexRow, int indexCol, bool bIsEdge[4], - Vector& normal ) -{ - // get the post spacing (size/interval of displacement surface) - int postSpacing = ( ( 1 << m_Power ) + 1 ); - - // initialize the normal accumulator - counter - Vector accumNormal; - int normalCount = 0; - - VectorClear( accumNormal ); - - Vector tmpVect[2]; - Vector tmpNormal; - - // - // check quadrant I (posX, posY) - // - if( bIsEdge[1] && bIsEdge[2] ) - { - // tri i - VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+indexRow].m_Vert, m_pVerts[indexCol*postSpacing+indexRow].m_Vert, tmpVect[0] ); - VectorSubtract( m_pVerts[indexCol*postSpacing+(indexRow+1)].m_Vert, m_pVerts[indexCol*postSpacing+indexRow].m_Vert, tmpVect[1] ); - CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); - VectorNormalize( tmpNormal ); - VectorAdd( accumNormal, tmpNormal, accumNormal ); - normalCount++; - - // tri 2 - VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+indexRow].m_Vert, m_pVerts[indexCol*postSpacing+(indexRow+1)].m_Vert, tmpVect[0] ); - VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+(indexRow+1)].m_Vert, m_pVerts[indexCol*postSpacing+(indexRow+1)].m_Vert, tmpVect[1] ); - CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); - VectorNormalize( tmpNormal ); - VectorAdd( accumNormal, tmpNormal, accumNormal ); - normalCount++; - } - - // - // check quadrant II (negX, posY) - // - if( bIsEdge[0] && bIsEdge[1] ) - { - // tri i - VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+(indexRow-1)].m_Vert, m_pVerts[indexCol*postSpacing+(indexRow-1)].m_Vert, tmpVect[0] ); - VectorSubtract( m_pVerts[indexCol*postSpacing+indexRow].m_Vert, m_pVerts[indexCol*postSpacing+(indexRow-1)].m_Vert, tmpVect[1] ); - CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); - VectorNormalize( tmpNormal ); - VectorAdd( accumNormal, tmpNormal, accumNormal ); - normalCount++; - - // tri 2 - VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+(indexRow-1)].m_Vert, m_pVerts[indexCol*postSpacing+indexRow].m_Vert, tmpVect[0] ); - VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+indexRow].m_Vert, m_pVerts[indexCol*postSpacing+indexRow].m_Vert, tmpVect[1] ); - CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); - VectorNormalize( tmpNormal ); - VectorAdd( accumNormal, tmpNormal, accumNormal ); - normalCount++; - } - - // - // check quadrant III (negX, negY) - // - if( bIsEdge[0] && bIsEdge[3] ) - { - // tri i - VectorSubtract( m_pVerts[indexCol*postSpacing+(indexRow-1)].m_Vert, m_pVerts[(indexCol-1)*postSpacing+(indexRow-1)].m_Vert, tmpVect[0] ); - VectorSubtract( m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, m_pVerts[(indexCol-1)*postSpacing+(indexRow-1)].m_Vert, tmpVect[1] ); - CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); - VectorNormalize( tmpNormal ); - VectorAdd( accumNormal, tmpNormal, accumNormal ); - normalCount++; - - // tri 2 - VectorSubtract( m_pVerts[indexCol*postSpacing+(indexRow-1)].m_Vert, m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, tmpVect[0] ); - VectorSubtract( m_pVerts[indexCol*postSpacing+indexRow].m_Vert, m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, tmpVect[1] ); - CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); - VectorNormalize( tmpNormal ); - VectorAdd( accumNormal, tmpNormal, accumNormal ); - normalCount++; - } - - // - // check quadrant IV (posX, negY) - // - if( bIsEdge[2] && bIsEdge[3] ) - { - // tri i - VectorSubtract( m_pVerts[indexCol*postSpacing+indexRow].m_Vert, m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, tmpVect[0] ); - VectorSubtract( m_pVerts[(indexCol-1)*postSpacing+(indexRow+1)].m_Vert, m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, tmpVect[1] ); - CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); - VectorNormalize( tmpNormal ); - VectorAdd( accumNormal, tmpNormal, accumNormal ); - normalCount++; - - // tri 2 - VectorSubtract( m_pVerts[indexCol*postSpacing+indexRow].m_Vert, m_pVerts[(indexCol-1)*postSpacing+(indexRow+1)].m_Vert, tmpVect[0] ); - VectorSubtract( m_pVerts[indexCol*postSpacing+(indexRow+1)].m_Vert, m_pVerts[(indexCol-1)*postSpacing+(indexRow+1)].m_Vert, tmpVect[1] ); - CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); - VectorNormalize( tmpNormal ); - VectorAdd( accumNormal, tmpNormal, accumNormal ); - normalCount++; - } - - VectorScale( accumNormal, ( 1.0f / ( float )normalCount ), normal ); -} - - -//----------------------------------------------------------------------------- -// Purpose: This function determines if edges exist in each of the directions -// off of the given point (given in component form). We know ahead of -// time that there are only 4 possibilities. -// -// 1 "directions" -// 0 + 2 -// 3 -// -// Input: indexRow - row position -// indexCol - col position -// direction - the direction (edge) currently being evaluated -// postSpacing - the number of intervals in the row and col directions -// Output: the edge existed? (true/false) -//----------------------------------------------------------------------------- -bool CCoreDispInfo::DoesEdgeExist( int indexRow, int indexCol, int direction, int postSpacing ) -{ - switch( direction ) - { - case 0: - // left edge - if( ( indexRow - 1 ) < 0 ) - return false; - return true; - case 1: - // top edge - if( ( indexCol + 1 ) > ( postSpacing - 1 ) ) - return false; - return true; - case 2: - // right edge - if( ( indexRow + 1 ) > ( postSpacing - 1 ) ) - return false; - return true; - case 3: - // bottom edge - if( ( indexCol - 1 ) < 0 ) - return false; - return true; - default: - return false; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::GenerateDispSurfNormals( void ) -{ - // get the post spacing (size/interval of displacement surface) - int postSpacing = GetPostSpacing(); - - // - // generate the normals at each displacement surface vertex - // - for( int i = 0; i < postSpacing; i++ ) - { - for( int j = 0; j < postSpacing; j++ ) - { - bool bIsEdge[4]; - - // edges - for( int k = 0; k < 4; k++ ) - { - bIsEdge[k] = DoesEdgeExist( j, i, k, postSpacing ); - } - - Vector normal; - CalcNormalFromEdges( j, i, bIsEdge, normal ); - - // save generated normal - VectorCopy( normal, m_pVerts[i*postSpacing+j].m_Normal ); - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::GenerateDispSurf( void ) -{ - int i; - CCoreDispSurface *pSurf = GetSurface(); - Vector points[4]; - for( i = 0; i < 4; i++ ) - { - pSurf->GetPoint( i, points[i] ); - } - - // - // get the spacing (interval = width/height, are equal because it is uniform) along the edge - // - int postSpacing = GetPostSpacing(); - float ooInt = 1.0f / ( float )( postSpacing - 1 ); - - // - // calculate the opposite edge intervals - // - Vector edgeInt[2]; - VectorSubtract( points[1], points[0], edgeInt[0] ); - VectorScale( edgeInt[0], ooInt, edgeInt[0] ); - VectorSubtract( points[2], points[3], edgeInt[1] ); - VectorScale( edgeInt[1], ooInt, edgeInt[1] ); - - Vector elevNormal; - elevNormal.Init(); - if( m_Elevation != 0.0f ) - { - pSurf->GetNormal( elevNormal ); - VectorScale( elevNormal, m_Elevation, elevNormal ); - } - - // - // calculate the displaced vertices - // - for( i = 0; i < postSpacing; i++ ) - { - // - // calculate segment interval between opposite edges - // - Vector endPts[2]; - VectorScale( edgeInt[0], ( float )i, endPts[0] ); - VectorAdd( endPts[0], points[0], endPts[0] ); - VectorScale( edgeInt[1], ( float )i, endPts[1] ); - VectorAdd( endPts[1], points[3], endPts[1] ); - - Vector seg, segInt; - VectorSubtract( endPts[1], endPts[0], seg ); - VectorScale( seg, ooInt, segInt ); - - // - // calculate the surface vertices - // - for( int j = 0; j < postSpacing; j++ ) - { - int ndx = i * postSpacing + j; - - CoreDispVert_t *pVert = &m_pVerts[ndx]; - - // calculate the flat surface position -- saved separately - pVert->m_FlatVert = endPts[0] + ( segInt * ( float )j ); - - // start with the base surface position - pVert->m_Vert = pVert->m_FlatVert; - - // add the elevation vector -- if it exists - if( m_Elevation != 0.0f ) - { - pVert->m_Vert += elevNormal; - } - - // add the subdivision surface position - pVert->m_Vert += pVert->m_SubdivPos; - - // add the displacement field direction(normalized) and distance - pVert->m_Vert += pVert->m_FieldVector * pVert->m_FieldDistance; - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -//bool CCoreDispInfo::Create( int creationFlags ) -bool CCoreDispInfo::Create( void ) -{ - // sanity check - CCoreDispSurface *pSurf = GetSurface(); - if( pSurf->GetPointCount() != 4 ) - return false; - - // generate the displacement surface - GenerateDispSurf(); - - GenerateDispSurfNormals(); - - GenerateDispSurfTangentSpaces(); - - CalcDispSurfCoords( false, 0 ); - - for( int bumpID = 0; bumpID < ( NUM_BUMP_VECTS + 1 ); bumpID++ ) - { - CalcDispSurfCoords( true, bumpID ); - } - - GenerateLODTree(); - - GenerateCollisionData(); - - CreateTris(); - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Create a displacement surface without generating the LOD for it. -//----------------------------------------------------------------------------- -bool CCoreDispInfo::CreateWithoutLOD( void ) -{ - // sanity check - CCoreDispSurface *pSurf = GetSurface(); - if( pSurf->GetPointCount() != 4 ) - return false; - - GenerateDispSurf(); - - GenerateDispSurfNormals(); - - GenerateDispSurfTangentSpaces(); - - CalcDispSurfCoords( false, 0 ); - - for( int bumpID = 0; bumpID < ( NUM_BUMP_VECTS + 1 ); bumpID++ ) - { - CalcDispSurfCoords( true, bumpID ); - } - GenerateCollisionData(); - - CreateTris(); - - return true; -} - - -// -// Node Functions (friend functions) -// - -//----------------------------------------------------------------------------- -// should make this more programatic and extensible! -//----------------------------------------------------------------------------- -int GetNodeLevel( int index ) -{ - // root - if( index == 0 ) - return 1; - - // [1...4] - if( index < 5 ) - return 2; - - // [5....20] - if( index < 21 ) - return 3; - - // [21....84] - if( index < 85 ) - return 4; - - // error!!! - return -1; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int GetNodeCount( int power ) -{ - return ( ( 1 << ( power << 1 ) ) / 3 ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int GetNodeParent( int index ) -{ - // ( index - 1 ) / 4 - return ( ( index - 1 ) >> 2 ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int GetNodeChild( int power, int index, int direction ) -{ - // ( index * 4 ) + direction - return ( ( index << 2 ) + ( direction - 3 ) ); -} - - -//----------------------------------------------------------------------------- -// Purpose: This function calculates the neighbor node index given the base -// node and direction of the neighbor node in the tree. -// Input: power - the size in one dimension of the displacement map (2^power + 1 ) -// index - the "base" node index -// direction - the direction of the neighbor { W = 1, N = 2, E = 3, S = 4 } -// Output: returns the index of the neighbor node -//----------------------------------------------------------------------------- -int GetNodeNeighborNode( int power, int index, int direction, int level ) -{ - // adjust the index to range [0...?] - int minNodeIndex = GetNodeMinNodeAtLevel( level ); - - // get node extent (uniform: height = width) - int nodeExtent = ( 1 << ( level - 1 ) ); - - // - // get node's component positions in quad-tree - // - int posX, posY; - GetComponentsFromNodeIndex( ( index - minNodeIndex ), &posX, &posY ); - - // - // find the neighbor in the "direction" - // - switch( direction ) - { - case CCoreDispInfo::WEST: - { - if( ( posX - 1 ) < 0 ) - { - return -( CCoreDispInfo::WEST + 1 ); - } - else - { - return ( GetNodeIndexFromComponents( ( posX - 1 ), posY ) + minNodeIndex ); - } - } - case CCoreDispInfo::NORTH: - { - if( ( posY + 1 ) == nodeExtent ) - { - return -( CCoreDispInfo::NORTH + 1 ); - } - else - { - return ( GetNodeIndexFromComponents( posX, ( posY + 1 ) ) + minNodeIndex ); - } - } - case CCoreDispInfo::EAST: - { - if( ( posX + 1 ) == nodeExtent ) - { - return -( CCoreDispInfo::EAST + 1 ); - } - else - { - return ( GetNodeIndexFromComponents( ( posX + 1 ), posY ) + minNodeIndex ); - } - } - case CCoreDispInfo::SOUTH: - { - if( ( posY - 1 ) < 0 ) - { - return -( CCoreDispInfo::SOUTH + 1 ); - } - else - { - return ( GetNodeIndexFromComponents( posX, ( posY - 1 ) ) + minNodeIndex ); - } - } - default: - { - return -99999; - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int GetNodeNeighborNodeFromNeighborSurf( int power, int index, int direction, int level, int neighborOrient ) -{ - // adjust the index to range [0...?] - int minNodeIndex = GetNodeMinNodeAtLevel( level ); - - // get node extent (uniform: height = width) - int nodeExtent = ( 1 << ( level - 1 ) ); - - // - // get node's component positions in quad-tree - // - int posX, posY; - GetComponentsFromNodeIndex( ( index - minNodeIndex ), &posX, &posY ); - - switch( direction ) - { - case CCoreDispInfo::WEST: - { - switch( neighborOrient ) - { - case CCoreDispInfo::WEST: return -( ( GetNodeIndexFromComponents( posX, ( ( nodeExtent - 1 ) - posY ) ) ) + minNodeIndex ); - case CCoreDispInfo::NORTH: return -( ( GetNodeIndexFromComponents( ( nodeExtent - 1 ) - posY, ( nodeExtent - 1 ) ) ) + minNodeIndex ); - case CCoreDispInfo::EAST: return -( ( GetNodeIndexFromComponents( ( nodeExtent - 1 ), posY ) ) + minNodeIndex ); - case CCoreDispInfo::SOUTH: return -( ( GetNodeIndexFromComponents( posY, posX ) ) + minNodeIndex ); - default: return -99999; - } - } - case CCoreDispInfo::NORTH: - { - switch( neighborOrient ) - { - case CCoreDispInfo::WEST: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posY ), ( ( nodeExtent - 1 ) - posX ) ) ) + minNodeIndex ); - case CCoreDispInfo::NORTH: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posX ), posY ) ) + minNodeIndex ); - case CCoreDispInfo::EAST: return -( ( GetNodeIndexFromComponents( posY, posX ) ) + minNodeIndex ); - case CCoreDispInfo::SOUTH: return -( ( GetNodeIndexFromComponents( posX, ( ( nodeExtent - 1 ) - posY ) ) ) + minNodeIndex ); - default: return -99999; - } - } - case CCoreDispInfo::EAST: - { - switch( neighborOrient ) - { - case CCoreDispInfo::WEST: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posX ), posY ) ) + minNodeIndex ); - case CCoreDispInfo::NORTH: return -( ( GetNodeIndexFromComponents( posY, posX ) ) + minNodeIndex ); - case CCoreDispInfo::EAST: return -( ( GetNodeIndexFromComponents( posX, ( ( nodeExtent - 1 ) - posY ) ) ) + minNodeIndex ); - case CCoreDispInfo::SOUTH: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posY ), ( ( nodeExtent - 1 ) - posX ) ) ) + minNodeIndex ); - default: return -99999; - } - } - case CCoreDispInfo::SOUTH: - { - switch( neighborOrient ) - { - case CCoreDispInfo::WEST: return -( ( GetNodeIndexFromComponents( posY, posX ) ) + minNodeIndex ); - case CCoreDispInfo::NORTH: return -( ( GetNodeIndexFromComponents( posX, ( nodeExtent - 1 ) ) ) + minNodeIndex ); - case CCoreDispInfo::EAST: return -( ( GetNodeIndexFromComponents( ( nodeExtent - 1 ), ( ( nodeExtent - 1 ) - posX ) ) ) + minNodeIndex ); - case CCoreDispInfo::SOUTH: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posX ), posY ) ) + minNodeIndex ); - default: return -99999; - } - } - default: - { - return -99999; - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int GetNodeMinNodeAtLevel( int level ) -{ - switch( level ) - { - case 1: return 0; - case 2: return 1; - case 3: return 5; - case 4: return 21; - default: return -99999; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void GetComponentsFromNodeIndex( int index, int *x, int *y ) -{ - *x = 0; - *y = 0; - - for( int shift = 0; index != 0; shift++ ) - { - *x |= ( index & 1 ) << shift; - index >>= 1; - - *y |= ( index & 1 ) << shift; - index >>= 1; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int GetNodeIndexFromComponents( int x, int y ) -{ - int index = 0; - - // Interleave bits from the x and y values to create the index: - - int shift; - for( shift = 0; x != 0; shift += 2, x >>= 1 ) - { - index |= ( x & 1 ) << shift; - } - - for( shift = 1; y != 0; shift += 2, y >>= 1 ) - { - index |= ( y & 1 ) << shift; - } - - return index; -} - - -// Turn the optimizer back on -#pragma optimize( "", on ) - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::GetPositionOnSurface( float u, float v, Vector &vPos, - Vector *pNormal, float *pAlpha ) -{ - Vector2D dispUV( u, v ); - DispUVToSurf( dispUV, vPos, pNormal, pAlpha ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::BaseFacePlaneToDispUV( Vector const &planePt, Vector2D &dispUV ) -{ - // Get the base surface points. - CCoreDispSurface *pSurf = GetSurface(); - Vector vecPoints[4]; - for( int iPoint = 0; iPoint < 4; ++iPoint ) - { - pSurf->GetPoint( iPoint, vecPoints[iPoint] ); - } - - PointInQuadToBarycentric( vecPoints[0], vecPoints[3], vecPoints[2], vecPoints[1], planePt, dispUV ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::DispUVToSurf_TriTLToBR_1( const Vector &vecIntersectPoint, - int nSnapU, int nNextU, int nSnapV, int nNextV, - Vector &vecPoint, Vector *pNormal, float *pAlpha, - bool bBackup ) -{ - int nWidth = GetWidth(); - - int nIndices[3]; - nIndices[0] = nNextV * nWidth + nSnapU; - nIndices[1] = nNextV * nWidth + nNextU; - nIndices[2] = nSnapV * nWidth + nNextU; - - Vector vecFlatVerts[3], vecVerts[3]; - float flAlphas[3]; - for ( int iVert = 0; iVert < 3; ++iVert ) - { - vecFlatVerts[iVert] = m_pVerts[nIndices[iVert]].m_FlatVert; - vecVerts[iVert] = m_pVerts[nIndices[iVert]].m_Vert; - flAlphas[iVert] = m_pVerts[nIndices[iVert]].m_Alpha; - } - - if ( nSnapU == nNextU ) - { - if ( nSnapV == nNextV ) - { - vecPoint = vecVerts[0]; - *pAlpha = flAlphas[0]; - } - else - { - float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); - vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); - - if ( pAlpha ) - { - *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); - } - } - - if( pNormal ) - { - Vector edgeU = vecVerts[0] - vecVerts[1]; - Vector edgeV = vecVerts[2] - vecVerts[1]; - *pNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( *pNormal ); - } - } - else if ( nSnapV == nNextV ) - { - if ( nSnapU == nNextU ) - { - vecPoint = vecVerts[0]; - *pAlpha = flAlphas[0]; - } - else - { - float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); - vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); - - if ( pAlpha ) - { - *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); - } - } - - if( pNormal ) - { - Vector edgeU = vecVerts[0] - vecVerts[1]; - Vector edgeV = vecVerts[2] - vecVerts[1]; - *pNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( *pNormal ); - } - } - else - { - float flCfs[3]; - if ( CalcBarycentricCooefs( vecFlatVerts[0], vecFlatVerts[1], vecFlatVerts[2], vecIntersectPoint, flCfs[0], flCfs[1], flCfs[2] ) ) - { - vecPoint = ( vecVerts[0] * flCfs[0] ) + ( vecVerts[1] * flCfs[1] ) + ( vecVerts[2] * flCfs[2] ); - - if( pAlpha ) - { - *pAlpha = ( flAlphas[0] * flCfs[0] ) + ( flAlphas[1] * flCfs[1] ) + ( flAlphas[2] * flCfs[2] ); - } - - if( pNormal ) - { - Vector edgeU = vecVerts[0] - vecVerts[1]; - Vector edgeV = vecVerts[2] - vecVerts[1]; - *pNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( *pNormal ); - } - } - else - { - if ( !bBackup ) - { - DispUVToSurf_TriTLToBR_2( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, true ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::DispUVToSurf_TriTLToBR_2( const Vector &vecIntersectPoint, - int nSnapU, int nNextU, int nSnapV, int nNextV, - Vector &vecPoint, Vector *pNormal, float *pAlpha, - bool bBackup ) -{ - int nWidth = GetWidth(); - - int nIndices[3]; - nIndices[0] = nSnapV * nWidth + nSnapU; - nIndices[1] = nNextV * nWidth + nSnapU; - nIndices[2] = nSnapV * nWidth + nNextU; - - Vector vecFlatVerts[3], vecVerts[3]; - float flAlphas[3]; - for ( int iVert = 0; iVert < 3; ++iVert ) - { - vecFlatVerts[iVert] = m_pVerts[nIndices[iVert]].m_FlatVert; - vecVerts[iVert] = m_pVerts[nIndices[iVert]].m_Vert; - flAlphas[iVert] = m_pVerts[nIndices[iVert]].m_Alpha; - } - - if ( nSnapU == nNextU ) - { - if ( nSnapV == nNextV ) - { - vecPoint = vecVerts[0]; - *pAlpha = flAlphas[0]; - } - else - { - float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[1] - vecFlatVerts[0] ).Length(); - vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[1] - vecVerts[0] ) ); - - if ( pAlpha ) - { - *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[1] - flAlphas[0] ) ); - } - } - - if( pNormal ) - { - Vector edgeU = vecVerts[2] - vecVerts[0]; - Vector edgeV = vecVerts[1] - vecVerts[0]; - *pNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( *pNormal ); - } - } - else if ( nSnapV == nNextV ) - { - if ( nSnapU == nNextU ) - { - vecPoint = vecVerts[0]; - *pAlpha = flAlphas[0]; - } - else - { - float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); - vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); - - if ( pAlpha ) - { - *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); - } - } - - if( pNormal ) - { - Vector edgeU = vecVerts[2] - vecVerts[0]; - Vector edgeV = vecVerts[1] - vecVerts[0]; - *pNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( *pNormal ); - } - } - else - { - float flCfs[3]; - if ( CalcBarycentricCooefs( vecFlatVerts[0], vecFlatVerts[1], vecFlatVerts[2], vecIntersectPoint, flCfs[0], flCfs[1], flCfs[2] ) ) - { - vecPoint = ( vecVerts[0] * flCfs[0] ) + ( vecVerts[1] * flCfs[1] ) + ( vecVerts[2] * flCfs[2] ); - - if( pAlpha ) - { - *pAlpha = ( flAlphas[0] * flCfs[0] ) + ( flAlphas[1] * flCfs[1] ) + ( flAlphas[2] * flCfs[2] ); - } - - if( pNormal ) - { - Vector edgeU = vecVerts[2] - vecVerts[0]; - Vector edgeV = vecVerts[1] - vecVerts[0]; - *pNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( *pNormal ); - } - } - else - { - if ( !bBackup ) - { - DispUVToSurf_TriTLToBR_1( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, true ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::DispUVToSurf_TriTLToBR( Vector &vecPoint, Vector *pNormal, float *pAlpha, - float flU, float flV, const Vector &vecIntersectPoint ) -{ - const float TRIEDGE_EPSILON = 0.00001f; - - int nWidth = GetWidth(); - int nHeight = GetHeight(); - - int nSnapU = static_cast( flU ); - int nSnapV = static_cast( flV ); - int nNextU = nSnapU + 1; - int nNextV = nSnapV + 1; - if ( nNextU == nWidth) { --nNextU; } - if ( nNextV == nHeight ) { --nNextV; } - - float flFracU = flU - static_cast( nSnapU ); - float flFracV = flV - static_cast( nSnapV ); - - if ( ( flFracU + flFracV ) >= ( 1.0f + TRIEDGE_EPSILON ) ) - { - DispUVToSurf_TriTLToBR_1( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, false ); - } - else - { - DispUVToSurf_TriTLToBR_2( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, false ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::DispUVToSurf_TriBLToTR_1( const Vector &vecIntersectPoint, - int nSnapU, int nNextU, int nSnapV, int nNextV, - Vector &vecPoint, Vector *pNormal, float *pAlpha, - bool bBackup ) -{ - int nWidth = GetWidth(); - - int nIndices[3]; - nIndices[0] = nSnapV * nWidth + nSnapU; - nIndices[1] = nNextV * nWidth + nSnapU; - nIndices[2] = nNextV * nWidth + nNextU; - - Vector vecFlatVerts[3], vecVerts[3]; - float flAlphas[3]; - for ( int iVert = 0; iVert < 3; ++iVert ) - { - vecFlatVerts[iVert] = m_pVerts[nIndices[iVert]].m_FlatVert; - vecVerts[iVert] = m_pVerts[nIndices[iVert]].m_Vert; - flAlphas[iVert] = m_pVerts[nIndices[iVert]].m_Alpha; - } - - if ( nSnapU == nNextU ) - { - if ( nSnapV == nNextV ) - { - vecPoint = vecVerts[0]; - *pAlpha = flAlphas[0]; - } - else - { - float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); - vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); - - if ( pAlpha ) - { - *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); - } - } - - if( pNormal ) - { - Vector edgeU = vecVerts[2] - vecVerts[1]; - Vector edgeV = vecVerts[0] - vecVerts[1]; - *pNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( *pNormal ); - } - } - else if ( nSnapV == nNextV ) - { - if ( nSnapU == nNextU ) - { - vecPoint = vecVerts[0]; - *pAlpha = flAlphas[0]; - } - else - { - float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); - vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); - - if ( pAlpha ) - { - *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); - } - } - - if( pNormal ) - { - Vector edgeU = vecVerts[2] - vecVerts[1]; - Vector edgeV = vecVerts[0] - vecVerts[1]; - *pNormal = CrossProduct( edgeV, edgeU ); - VectorNormalize( *pNormal ); - } - } - else - { - float flCfs[3]; - if ( CalcBarycentricCooefs( vecFlatVerts[0], vecFlatVerts[1], vecFlatVerts[2], vecIntersectPoint, flCfs[0], flCfs[1], flCfs[2] ) ) - { - vecPoint = ( vecVerts[0] * flCfs[0] ) + ( vecVerts[1] * flCfs[1] ) + ( vecVerts[2] * flCfs[2] ); - - if( pAlpha ) - { - *pAlpha = ( flAlphas[0] * flCfs[0] ) + ( flAlphas[1] * flCfs[1] ) + ( flAlphas[2] * flCfs[2] ); - } - - if( pNormal ) - { - Vector edgeU = vecVerts[2] - vecVerts[1]; - Vector edgeV = vecVerts[0] - vecVerts[1]; - *pNormal = CrossProduct( edgeV, edgeU ); - VectorNormalize( *pNormal ); - } - } - else - { - if ( !bBackup ) - { - DispUVToSurf_TriBLToTR_2( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, true ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::DispUVToSurf_TriBLToTR_2( const Vector &vecIntersectPoint, - int nSnapU, int nNextU, int nSnapV, int nNextV, - Vector &vecPoint, Vector *pNormal, float *pAlpha, - bool bBackup ) -{ - int nWidth = GetWidth(); - - int nIndices[3]; - nIndices[0] = nSnapV * nWidth + nSnapU; - nIndices[1] = nNextV * nWidth + nNextU; - nIndices[2] = nSnapV * nWidth + nNextU; - - Vector vecFlatVerts[3], vecVerts[3]; - float flAlphas[3]; - for ( int iVert = 0; iVert < 3; ++iVert ) - { - vecFlatVerts[iVert] = m_pVerts[nIndices[iVert]].m_FlatVert; - vecVerts[iVert] = m_pVerts[nIndices[iVert]].m_Vert; - flAlphas[iVert] = m_pVerts[nIndices[iVert]].m_Alpha; - } - - if ( nSnapU == nNextU ) - { - if ( nSnapV == nNextV ) - { - vecPoint = vecVerts[0]; - *pAlpha = flAlphas[0]; - } - else - { - float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[1] - vecFlatVerts[0] ).Length(); - vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[1] - vecVerts[0] ) ); - - if ( pAlpha ) - { - *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[1] - flAlphas[0] ) ); - } - } - - if( pNormal ) - { - Vector edgeU = vecVerts[0] - vecVerts[2]; - Vector edgeV = vecVerts[1] - vecVerts[2]; - *pNormal = CrossProduct( edgeV, edgeU ); - VectorNormalize( *pNormal ); - } - } - else if ( nSnapV == nNextV ) - { - if ( nSnapU == nNextU ) - { - vecPoint = vecVerts[0]; - *pAlpha = flAlphas[0]; - } - else - { - float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); - vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); - - if ( pAlpha ) - { - *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); - } - } - - if( pNormal ) - { - Vector edgeU = vecVerts[0] - vecVerts[2]; - Vector edgeV = vecVerts[1] - vecVerts[2]; - *pNormal = CrossProduct( edgeV, edgeU ); - VectorNormalize( *pNormal ); - } - } - else - { - float flCfs[3]; - if ( CalcBarycentricCooefs( vecFlatVerts[0], vecFlatVerts[1], vecFlatVerts[2], vecIntersectPoint, flCfs[0], flCfs[1], flCfs[2] ) ) - { - vecPoint = ( vecVerts[0] * flCfs[0] ) + ( vecVerts[1] * flCfs[1] ) + ( vecVerts[2] * flCfs[2] ); - - if( pAlpha ) - { - *pAlpha = ( flAlphas[0] * flCfs[0] ) + ( flAlphas[1] * flCfs[1] ) + ( flAlphas[2] * flCfs[2] ); - } - - if( pNormal ) - { - Vector edgeU = vecVerts[0] - vecVerts[2]; - Vector edgeV = vecVerts[1] - vecVerts[2]; - *pNormal = CrossProduct( edgeV, edgeU ); - VectorNormalize( *pNormal ); - } - } - else - { - if ( !bBackup ) - { - DispUVToSurf_TriBLToTR_1( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, true ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::DispUVToSurf_TriBLToTR( Vector &vecPoint, Vector *pNormal, float *pAlpha, - float flU, float flV, const Vector &vecIntersectPoint ) -{ - int nWidth = GetWidth(); - int nHeight = GetHeight(); - - int nSnapU = static_cast( flU ); - int nSnapV = static_cast( flV ); - int nNextU = nSnapU + 1; - int nNextV = nSnapV + 1; - if ( nNextU == nWidth) { --nNextU; } - if ( nNextV == nHeight ) { --nNextV; } - - float flFracU = flU - static_cast( nSnapU ); - float flFracV = flV - static_cast( nSnapV ); - - if( flFracU < flFracV ) - { - DispUVToSurf_TriBLToTR_1( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, false ); - } - else - { - DispUVToSurf_TriBLToTR_2( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, false ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::DispUVToSurf( Vector2D const &dispUV, Vector &vecPoint, - Vector *pNormal, float *pAlpha ) -{ - // Check to see that the point is on the surface. - if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) - return; - - // Get the base surface points. - Vector vecIntersectPoint; - CCoreDispSurface *pSurf = GetSurface(); - PointInQuadFromBarycentric( pSurf->GetPoint( 0 ), pSurf->GetPoint( 3 ), pSurf->GetPoint( 2 ), pSurf->GetPoint( 1 ), dispUV, vecIntersectPoint ); - - // Get the displacement power. - int nWidth = GetWidth(); - int nHeight = GetHeight(); - - // Scale the U, V coordinates to the displacement grid size. - float flU = dispUV.x * ( static_cast( nWidth ) - 1.000001f ); - float flV = dispUV.y * ( static_cast( nHeight ) - 1.000001f ); - - // Find the base U, V. - int nSnapU = static_cast( flU ); - int nSnapV = static_cast( flV ); - - // Use this to get the triangle orientation. - bool bOdd = ( ( ( nSnapV * nWidth ) + nSnapU ) % 2 == 1 ); - - // Top Left to Bottom Right - if( bOdd ) - { - DispUVToSurf_TriTLToBR( vecPoint, pNormal, pAlpha, flU, flV, vecIntersectPoint ); - } - // Bottom Left to Top Right - else - { - DispUVToSurf_TriBLToTR( vecPoint, pNormal, pAlpha, flU, flV, vecIntersectPoint ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Create bounding boxes around pairs of triangles (in a grid-like) -// fashion; used for culling -//----------------------------------------------------------------------------- -void CCoreDispInfo::CreateBoundingBoxes( CoreDispBBox_t *pBBox, int count ) -{ - // - // Initialize the bounding boxes. - // - int iBox; - for( iBox = 0; iBox < count; ++iBox ) - { - pBBox[iBox].vMin.Init( FLT_MAX, FLT_MAX, FLT_MAX ); - pBBox[iBox].vMax.Init( FLT_MIN, FLT_MIN, FLT_MIN ); - } - - // Get the width and height of the displacement surface. - int nHeight = GetHeight(); - int nWidth = GetWidth(); - - // Find bounding box of every two consecutive triangles - iBox = 0; - int nIndex = 0; - for( int iHgt = 0; iHgt < ( nHeight - 1 ); ++iHgt ) - { - for( int iWid = 0; iWid < ( nWidth - 1 ); ++iWid ) - { - for( int iPoint = 0; iPoint < 4; ++iPoint ) - { - switch( iPoint ) - { - case 0: { nIndex = ( nHeight * iHgt ) + iWid; break; } - case 1: { nIndex = ( nHeight * ( iHgt + 1 ) ) + iWid; break; } - case 2: { nIndex = ( nHeight * ( iHgt + 1 ) ) + ( iWid + 1 ); break; } - case 3: { nIndex = ( nHeight * iHgt ) + ( iWid + 1 ); break; } - default: { break; } - } - - Vector vecPoint; - GetVert( nIndex, vecPoint ); - if( vecPoint[0] < pBBox[iBox].vMin[0] ) { pBBox[iBox].vMin[0] = vecPoint[0]; } - if( vecPoint[1] < pBBox[iBox].vMin[1] ) { pBBox[iBox].vMin[1] = vecPoint[1]; } - if( vecPoint[2] < pBBox[iBox].vMin[2] ) { pBBox[iBox].vMin[2] = vecPoint[2]; } - - if( vecPoint[0] > pBBox[iBox].vMax[0] ) { pBBox[iBox].vMax[0] = vecPoint[0]; } - if( vecPoint[1] > pBBox[iBox].vMax[1] ) { pBBox[iBox].vMax[1] = vecPoint[1]; } - if( vecPoint[2] > pBBox[iBox].vMax[2] ) { pBBox[iBox].vMax[2] = vecPoint[2]; } - } - - iBox++; - } - } - - // Verify. - Assert( iBox == count ); - - // Bloat. - for ( iBox = 0; iBox < count; ++iBox ) - { - for( int iAxis = 0; iAxis < 3; ++iAxis ) - { - pBBox[iBox].vMin[iAxis] -= 1.0f; - pBBox[iBox].vMax[iAxis] += 1.0f; - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline bool PointInDispBBox( CoreDispBBox_t *pBox, const Vector &vecPoint ) -{ - // Check to see if point lies in box - if( ( vecPoint.x < pBox->vMin.x ) || ( vecPoint.x > pBox->vMax.x ) ) - return false; - - if( ( vecPoint.y < pBox->vMin.y ) || ( vecPoint.y > pBox->vMax.y ) ) - return false; - - if( ( vecPoint.z < pBox->vMin.z ) || ( vecPoint.z > pBox->vMax.z ) ) - return false; - - return true; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CCoreDispInfo::GetTriangleIndicesForDispBBox( int nIndex, int nTris[2][3] ) -{ - // Test to see whether or not the index is odd. - bool bOdd = ( ( nIndex % 2 ) == 1 ); - - int nWidth = GetWidth(); - - // Tris for TLtoBR - if ( bOdd ) - { - nTris[0][0] = nIndex; - nTris[0][1] = nIndex + nWidth; - nTris[0][2] = nIndex + 1; - - nTris[1][0] = nIndex + 1; - nTris[1][1] = nIndex + nWidth; - nTris[1][2] = nIndex + nWidth + 1; - } - // Tris for BLtoTR - else - { - nTris[0][0] = nIndex; - nTris[0][1] = nIndex + nWidth; - nTris[0][2] = nIndex + nWidth + 1; - - nTris[1][0] = nIndex; - nTris[1][1] = nIndex + nWidth + 1; - nTris[1][2] = nIndex + 1; - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CCoreDispInfo::SurfToBaseFacePlane( Vector const &surfPt, Vector &planePt ) -{ - // Create bounding boxes - int nBoxCount = ( GetHeight() - 1 ) * ( GetWidth() - 1 ); - CoreDispBBox_t *pBBox = new CoreDispBBox_t[nBoxCount]; - CreateBoundingBoxes( pBBox, nBoxCount ); - - // Use the boxes as a first-pass culling mechanism. - for( int iBox = 0; iBox < nBoxCount; ++iBox ) - { - // Get the current displacement triangle-pair bounding-box. - CoreDispBBox_t *pBox = &pBBox[iBox]; - if( !pBox ) - continue; - - // Check the point against the current displacement bounding-box. - if ( !PointInDispBBox( pBox, surfPt ) ) - continue; - - // Point lies within the bounding box. - int nIndex = iBox + ( iBox / ( GetWidth() - 1 ) ); - - // Get the triangle coordinates for this box. - int aTris[2][3]; - GetTriangleIndicesForDispBBox( nIndex, aTris ); - - // Barycentrically test the triangles on the displacement surface. - Vector vecPoints[3]; - for ( int iTri = 0; iTri < 2; ++iTri ) - { - for ( int iVert = 0; iVert < 3; ++iVert ) - { - GetVert( aTris[iTri][iVert], vecPoints[iVert] ); - } - - float c[3]; - if ( CalcBarycentricCooefs( vecPoints[0], vecPoints[1], vecPoints[2], surfPt, c[0], c[1], c[2] ) ) - { - Vector vecFlatPoints[3]; - for ( int iVert = 0; iVert < 3; ++iVert ) - { - GetFlatVert( aTris[iTri][iVert], vecFlatPoints[iVert] ); - } - - planePt = ( vecFlatPoints[0] * c[0] ) + ( vecFlatPoints[1] * c[1] ) + ( vecFlatPoints[2] * c[2] ); - - // Delete temporary memory. - delete [] pBBox; - return true; - } - } - } - - // Delete temporary memory - delete [] pBBox; - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CCoreDispInfo::GetTriCount( void ) -{ - return ( ( GetHeight() - 1 ) * ( GetWidth() -1 ) * 2 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::GetTriIndices( int iTri, unsigned short &v1, unsigned short &v2, unsigned short &v3 ) -{ - // Verify we have the correct data (only build when collision data is built). - if ( !m_pTris || ( iTri < 0 ) || ( iTri >= GetTriCount() ) ) - { - Assert( iTri >= 0 ); - Assert( iTri < GetTriCount() ); - Assert( m_pTris ); - return; - } - - CoreDispTri_t *pTri = &m_pTris[iTri]; - v1 = pTri->m_iIndex[0]; - v2 = pTri->m_iIndex[1]; - v3 = pTri->m_iIndex[2]; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::SetTriIndices( int iTri, unsigned short v1, unsigned short v2, unsigned short v3 ) -{ - // Verify we have the correct data (only build when collision data is built). - if ( !m_pTris || ( iTri < 0 ) || ( iTri >= GetTriCount() ) ) - { - Assert( iTri >= 0 ); - Assert( iTri < GetTriCount() ); - Assert( m_pTris ); - return; - } - - CoreDispTri_t *pTri = &m_pTris[iTri]; - pTri->m_iIndex[0] = v1; - pTri->m_iIndex[1] = v2; - pTri->m_iIndex[2] = v3; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::GetTriPos( int iTri, Vector &v1, Vector &v2, Vector &v3 ) -{ - // Verify we have the correct data (only build when collision data is built). - if ( !m_pTris || ( iTri < 0 ) || ( iTri >= GetTriCount() ) ) - { - Assert( iTri >= 0 ); - Assert( iTri < GetTriCount() ); - Assert( m_pTris ); - return; - } - - CoreDispTri_t *pTri = &m_pTris[iTri]; - v1 = m_pVerts[pTri->m_iIndex[0]].m_Vert; - v2 = m_pVerts[pTri->m_iIndex[1]].m_Vert; - v3 = m_pVerts[pTri->m_iIndex[2]].m_Vert; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::InitTris( void ) -{ - // Verify we have the correct data (only build when collision data is built). - if ( !m_pTris ) - { - Assert( m_pTris ); - return; - } - - int nTriCount = GetTriCount(); - for ( int iTri = 0; iTri < nTriCount; ++iTri ) - { - m_pTris[iTri].m_uiTags = 0; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::CreateTris( void ) -{ - // Verify we have the correct data (only build when collision data is built). - if ( !m_pTris ) - { - Assert( m_pTris ); - return; - } - - // Extra sanity check if wanted! - Assert( GetTriCount() == ( m_RenderIndexCount / 3 ) ); - - int nTriCount = GetTriCount(); - for ( int iTri = 0, iRender = 0; iTri < nTriCount; ++iTri, iRender += 3 ) - { - m_pTris[iTri].m_iIndex[0] = m_RenderIndices[iRender]; - m_pTris[iTri].m_iIndex[1] = m_RenderIndices[iRender+1]; - m_pTris[iTri].m_iIndex[2] = m_RenderIndices[iRender+2]; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CCoreDispInfo::IsTriWalkable( int iTri ) -{ - if ( IsTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_BIT ) ) - { - return IsTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_VAL ); - } - - return IsTriTag( iTri, COREDISPTRI_TAG_WALKABLE ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CCoreDispInfo::IsTriBuildable( int iTri ) -{ - if ( IsTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_BIT ) ) - { - return IsTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_VAL ); - } - - return IsTriTag( iTri, COREDISPTRI_TAG_BUILDABLE ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCoreDispInfo::Position_Update( int iVert, Vector vecPos ) -{ - Vector vSPos, vFlat; - GetFlatVert( iVert, vFlat ); - GetSubdivPosition( iVert, vSPos ); - - Vector vSeg; - vSeg = vecPos - vFlat; - vSeg -= vSPos; - - // Subtract out the elevation. - float elev = GetElevation(); - if( elev != 0.0 ) - { - Vector vNormal; - GetSurface()->GetNormal( vNormal ); - vNormal *= elev; - - vSeg -= vNormal; - } - - float flDistance = VectorNormalize( vSeg ); - - SetFieldVector( iVert, vSeg ); - SetFieldDistance( iVert, flDistance ); -} +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +//#include +#include +#include +#include "builddisp.h" +#include "collisionutils.h" +#include "vstdlib/strtools.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CalcBarycentricCooefs( Vector const &v0, Vector const &v1, Vector const &v2, + Vector const &pt, float &c0, float &c1, float &c2 ) +{ + Vector vSeg0, vSeg1, vCross; + vSeg0 = v1 - v0; + vSeg1 = v2 - v0; + + // get the area of the triangle + vCross = vSeg0.Cross( vSeg1 ); + float totalArea = vCross.Length() * 0.5f; + float ooTotalArea = totalArea ? 1.0f / totalArea : 0.0f; + + // get the area for cooeficient 0 (pt, v1, v2) + vSeg0 = v1 - pt; + vSeg1 = v2 - pt; + vCross = vSeg0.Cross( vSeg1 ); + float subArea = vCross.Length() * 0.5f; + c0 = subArea * ooTotalArea; + + // get the area for cooeficient 1 (v0, pt, v2) + vSeg0 = v2 - pt; + vSeg1 = v0 - pt; + vCross = vSeg0.Cross( vSeg1 ); + subArea = vCross.Length() * 0.5f; + c1 = subArea * ooTotalArea; + + // get the area for cooeficient 2 (v0, v1, pt) + vSeg0 = v0 - pt; + vSeg1 = v1 - pt; + vCross = vSeg0.Cross( vSeg1 ); + subArea = vCross.Length() * 0.5f; + c2 = subArea * ooTotalArea; + + float cTotal = c0 + c1 + c2; + if ( FloatMakePositive( 1.0f - cTotal ) < 1e-3 ) + return true; + + return false; +} + +// For some reason, the global optimizer screws up the recursion here. disable the global optimizations to fix this. +// IN VC++ 6.0 +#ifdef _MSC_VER +#pragma optimize( "g", off ) +#endif + +// +// Node Functions (friend functions) +// + +//----------------------------------------------------------------------------- +// should make this more programatic and extensible! +//----------------------------------------------------------------------------- +int GetNodeLevel( int index ) +{ + // root + if( index == 0 ) + return 1; + + // [1...4] + if( index < 5 ) + return 2; + + // [5....20] + if( index < 21 ) + return 3; + + // [21....84] + if( index < 85 ) + return 4; + + // error!!! + return -1; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetNodeCount( int power ) +{ + return ( ( 1 << ( power << 1 ) ) / 3 ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetNodeParent( int index ) +{ + // ( index - 1 ) / 4 + return ( ( index - 1 ) >> 2 ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetNodeChild( int power, int index, int direction ) +{ + // ( index * 4 ) + direction + return ( ( index << 2 ) + ( direction - 3 ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetNodeMinNodeAtLevel( int level ) +{ + switch( level ) + { + case 1: return 0; + case 2: return 1; + case 3: return 5; + case 4: return 21; + default: return -99999; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void GetComponentsFromNodeIndex( int index, int *x, int *y ) +{ + *x = 0; + *y = 0; + + for( int shift = 0; index != 0; shift++ ) + { + *x |= ( index & 1 ) << shift; + index >>= 1; + + *y |= ( index & 1 ) << shift; + index >>= 1; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetNodeIndexFromComponents( int x, int y ) +{ + int index = 0; + + // Interleave bits from the x and y values to create the index: + + int shift; + for( shift = 0; x != 0; shift += 2, x >>= 1 ) + { + index |= ( x & 1 ) << shift; + } + + for( shift = 1; y != 0; shift += 2, y >>= 1 ) + { + index |= ( y & 1 ) << shift; + } + + return index; +} + + +//----------------------------------------------------------------------------- +// Purpose: This function calculates the neighbor node index given the base +// node and direction of the neighbor node in the tree. +// Input: power - the size in one dimension of the displacement map (2^power + 1 ) +// index - the "base" node index +// direction - the direction of the neighbor { W = 1, N = 2, E = 3, S = 4 } +// Output: returns the index of the neighbor node +//----------------------------------------------------------------------------- +int GetNodeNeighborNode( int power, int index, int direction, int level ) +{ + // adjust the index to range [0...?] + int minNodeIndex = GetNodeMinNodeAtLevel( level ); + + // get node extent (uniform: height = width) + int nodeExtent = ( 1 << ( level - 1 ) ); + + // + // get node's component positions in quad-tree + // + int posX, posY; + GetComponentsFromNodeIndex( ( index - minNodeIndex ), &posX, &posY ); + + // + // find the neighbor in the "direction" + // + switch( direction ) + { + case CCoreDispInfo::WEST: + { + if( ( posX - 1 ) < 0 ) + { + return -( CCoreDispInfo::WEST + 1 ); + } + else + { + return ( GetNodeIndexFromComponents( ( posX - 1 ), posY ) + minNodeIndex ); + } + } + case CCoreDispInfo::NORTH: + { + if( ( posY + 1 ) == nodeExtent ) + { + return -( CCoreDispInfo::NORTH + 1 ); + } + else + { + return ( GetNodeIndexFromComponents( posX, ( posY + 1 ) ) + minNodeIndex ); + } + } + case CCoreDispInfo::EAST: + { + if( ( posX + 1 ) == nodeExtent ) + { + return -( CCoreDispInfo::EAST + 1 ); + } + else + { + return ( GetNodeIndexFromComponents( ( posX + 1 ), posY ) + minNodeIndex ); + } + } + case CCoreDispInfo::SOUTH: + { + if( ( posY - 1 ) < 0 ) + { + return -( CCoreDispInfo::SOUTH + 1 ); + } + else + { + return ( GetNodeIndexFromComponents( posX, ( posY - 1 ) ) + minNodeIndex ); + } + } + default: + { + return -99999; + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetNodeNeighborNodeFromNeighborSurf( int power, int index, int direction, int level, int neighborOrient ) +{ + // adjust the index to range [0...?] + int minNodeIndex = GetNodeMinNodeAtLevel( level ); + + // get node extent (uniform: height = width) + int nodeExtent = ( 1 << ( level - 1 ) ); + + // + // get node's component positions in quad-tree + // + int posX, posY; + GetComponentsFromNodeIndex( ( index - minNodeIndex ), &posX, &posY ); + + switch( direction ) + { + case CCoreDispInfo::WEST: + { + switch( neighborOrient ) + { + case CCoreDispInfo::WEST: return -( ( GetNodeIndexFromComponents( posX, ( ( nodeExtent - 1 ) - posY ) ) ) + minNodeIndex ); + case CCoreDispInfo::NORTH: return -( ( GetNodeIndexFromComponents( ( nodeExtent - 1 ) - posY, ( nodeExtent - 1 ) ) ) + minNodeIndex ); + case CCoreDispInfo::EAST: return -( ( GetNodeIndexFromComponents( ( nodeExtent - 1 ), posY ) ) + minNodeIndex ); + case CCoreDispInfo::SOUTH: return -( ( GetNodeIndexFromComponents( posY, posX ) ) + minNodeIndex ); + default: return -99999; + } + } + case CCoreDispInfo::NORTH: + { + switch( neighborOrient ) + { + case CCoreDispInfo::WEST: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posY ), ( ( nodeExtent - 1 ) - posX ) ) ) + minNodeIndex ); + case CCoreDispInfo::NORTH: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posX ), posY ) ) + minNodeIndex ); + case CCoreDispInfo::EAST: return -( ( GetNodeIndexFromComponents( posY, posX ) ) + minNodeIndex ); + case CCoreDispInfo::SOUTH: return -( ( GetNodeIndexFromComponents( posX, ( ( nodeExtent - 1 ) - posY ) ) ) + minNodeIndex ); + default: return -99999; + } + } + case CCoreDispInfo::EAST: + { + switch( neighborOrient ) + { + case CCoreDispInfo::WEST: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posX ), posY ) ) + minNodeIndex ); + case CCoreDispInfo::NORTH: return -( ( GetNodeIndexFromComponents( posY, posX ) ) + minNodeIndex ); + case CCoreDispInfo::EAST: return -( ( GetNodeIndexFromComponents( posX, ( ( nodeExtent - 1 ) - posY ) ) ) + minNodeIndex ); + case CCoreDispInfo::SOUTH: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posY ), ( ( nodeExtent - 1 ) - posX ) ) ) + minNodeIndex ); + default: return -99999; + } + } + case CCoreDispInfo::SOUTH: + { + switch( neighborOrient ) + { + case CCoreDispInfo::WEST: return -( ( GetNodeIndexFromComponents( posY, posX ) ) + minNodeIndex ); + case CCoreDispInfo::NORTH: return -( ( GetNodeIndexFromComponents( posX, ( nodeExtent - 1 ) ) ) + minNodeIndex ); + case CCoreDispInfo::EAST: return -( ( GetNodeIndexFromComponents( ( nodeExtent - 1 ), ( ( nodeExtent - 1 ) - posX ) ) ) + minNodeIndex ); + case CCoreDispInfo::SOUTH: return -( ( GetNodeIndexFromComponents( ( ( nodeExtent - 1 ) - posX ), posY ) ) + minNodeIndex ); + default: return -99999; + } + } + default: + { + return -99999; + } + } +} + + +CCoreDispSurface::CCoreDispSurface() +{ + Init(); +} + + +//============================================================================= +// +// CDispSurface Functions +// + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispSurface::Init( void ) +{ + m_Index = -1; + + m_PointCount = 0; + int i; + for( i = 0; i < QUAD_POINT_COUNT; i++ ) + { + VectorClear( m_Points[i] ); + VectorClear( m_Normals[i] ); + Vector2DClear( m_TexCoords[i] ); + + for( int j = 0; j < NUM_BUMP_VECTS+1; j++ ) + { + Vector2DClear( m_LuxelCoords[i][j] ); + } + + m_Alphas[i] = 1.0f; + } + + m_PointStartIndex = -1; + VectorClear( m_PointStart ); + VectorClear( sAxis ); + VectorClear( tAxis ); + + for( i = 0; i < 4; i++ ) + { + m_EdgeNeighbors[i].SetInvalid(); + m_CornerNeighbors[i].SetInvalid(); + } + + m_Flags = 0; + m_Contents = 0; +} + + +void CCoreDispSurface::SetNeighborData( const CDispNeighbor edgeNeighbors[4], const CDispCornerNeighbors cornerNeighbors[4] ) +{ + for ( int i=0; i < 4; i++ ) + { + m_EdgeNeighbors[i] = edgeNeighbors[i]; + m_CornerNeighbors[i] = cornerNeighbors[i]; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispSurface::GeneratePointStartIndexFromMappingAxes( Vector const &sAxis, Vector const &tAxis ) +{ + if( m_PointStartIndex != -1 ) + return; + + int numIndices = 0; + int indices[4]; + int offsetIndex; + + // + // project all points on to the v-axis first and find the minimum + // + float minValue = DotProduct( tAxis, m_Points[0] ); + indices[numIndices] = 0; + numIndices++; + + int i; + for( i = 1; i < m_PointCount; i++ ) + { + float value = DotProduct( tAxis, m_Points[i] ); + float delta = ( value - minValue ); + delta = FloatMakePositive( delta ); + if( delta < 0.1 ) + { + indices[numIndices] = i; + numIndices++; + } + else if( value < minValue ) + { + minValue = value; + indices[0] = i; + numIndices = 1; + } + } + + // + // break ties with the u-axis projection + // + minValue = DotProduct( sAxis, m_Points[indices[0]] ); + offsetIndex = indices[0]; + + for( i = 1; i < numIndices; i++ ) + { + float value = DotProduct( sAxis, m_Points[indices[i]] ); + if( ( value < minValue ) ) + { + minValue = value; + offsetIndex = indices[i]; + } + } + + m_PointStartIndex = offsetIndex; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CCoreDispSurface::GenerateSurfPointStartIndex( void ) +{ + // + // get the minimum surface component values + // + Vector bMin; + VectorFill( bMin, 99999.0f ); + + int i; + for( i = 0; i < QUAD_POINT_COUNT; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + if( m_Points[i][j] < bMin[j] ) + { + bMin[j] = m_Points[i][j]; + } + } + } + + // + // find the point closest to the minimum, that is the start point + // + int minIndex = -1; + float minDistance = 999999999.0f; + for( i = 0; i < QUAD_POINT_COUNT; i++ ) + { + Vector segment; + segment = m_Points[i] - bMin; + float distanceSq = segment.LengthSqr(); + if( distanceSq < minDistance ) + { + minDistance = distanceSq; + minIndex = i; + } + } + + m_PointStartIndex = minIndex; + + return minIndex; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CCoreDispSurface::FindSurfPointStartIndex( void ) +{ + if( m_PointStartIndex != -1 ) + return m_PointStartIndex; + + int minIndex = -1; + float minDistance = 999999999.0f; + + for( int i = 0; i < QUAD_POINT_COUNT; i++ ) + { + Vector segment; + VectorSubtract( m_PointStart, m_Points[i], segment ); + float distanceSq = segment.LengthSqr(); + if( distanceSq < minDistance ) + { + minDistance = distanceSq; + minIndex = i; + } + } + + m_PointStartIndex = minIndex; + + return minIndex; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispSurface::AdjustSurfPointData( void ) +{ + Vector tmpPoints[4]; + Vector tmpNormals[4]; + Vector2D tmpTexCoords[4]; + Vector2D tmpLuxelCoords[4][4]; + float tmpAlphas[4]; + + int i; + for( i = 0; i < QUAD_POINT_COUNT; i++ ) + { + VectorCopy( m_Points[i], tmpPoints[i] ); + VectorCopy( m_Normals[i], tmpNormals[i] ); + Vector2DCopy( m_TexCoords[i], tmpTexCoords[i] ); + + for( int j = 0; j < ( NUM_BUMP_VECTS + 1 ); j++ ) + { + Vector2DCopy( m_LuxelCoords[j][i], tmpLuxelCoords[j][i] ); + } + + tmpAlphas[i] = m_Alphas[i]; + } + + for( i = 0; i < QUAD_POINT_COUNT; i++ ) + { + VectorCopy( tmpPoints[(i+m_PointStartIndex)%4], m_Points[i] ); + VectorCopy( tmpNormals[(i+m_PointStartIndex)%4], m_Normals[i] ); + Vector2DCopy( tmpTexCoords[(i+m_PointStartIndex)%4], m_TexCoords[i] ); + +// for( int j = 0; j < ( NUM_BUMP_VECTS + 1 ); j++ ) +// { +// Vector2DCopy( tmpLuxelCoords[j][(i+m_PointStartIndex)%4], m_LuxelCoords[j][i] ); +// } + + m_Alphas[i] = tmpAlphas[i]; + } +} + + +//============================================================================= +// +// CDispNode Functions +// + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispNode::Init( void ) +{ + VectorClear( m_BBox[0] ); + VectorClear( m_BBox[1] ); + + m_ErrorTerm = 0.0f; + + m_VertIndex = -1; + + int j; + for( j = 0; j < MAX_NEIGHBOR_NODE_COUNT; j++ ) + { + m_NeighborVertIndices[j] = -1; + } + + for( j = 0; j < MAX_SURF_AT_NODE_COUNT; j++ ) + { + VectorClear( m_SurfBBoxes[j][0] ); + VectorClear( m_SurfBBoxes[j][1] ); + VectorClear( m_SurfPlanes[j].normal ); + m_SurfPlanes[j].dist = 0.0f; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void GetDispNodeTriVerts( CCoreDispInfo *pDisp, int nodeIndex, int triIndex, Vector& v1, Vector& v2, Vector& v3 ) +{ + // get the node + CCoreDispNode *pNode = pDisp->GetNode( nodeIndex ); + + switch( triIndex ) + { + case 0: + { + pDisp->GetVert( pNode->GetNeighborVertIndex( 4 ), v1 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 0 ), v2 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 3 ), v3 ); + return; + } + case 1: + { + pDisp->GetVert( pNode->GetNeighborVertIndex( 3 ), v1 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 0 ), v2 ); + pDisp->GetVert( pNode->GetCenterVertIndex(), v3 ); + return; + } + case 2: + { + pDisp->GetVert( pNode->GetNeighborVertIndex( 3 ), v1 ); + pDisp->GetVert( pNode->GetCenterVertIndex(), v2 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 5 ), v3 ); + return; + } + case 3: + { + pDisp->GetVert( pNode->GetNeighborVertIndex( 5 ), v1 ); + pDisp->GetVert( pNode->GetCenterVertIndex(), v2 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 2 ), v3 ); + return; + } + case 4: + { + pDisp->GetVert( pNode->GetNeighborVertIndex( 0 ), v1 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 6 ), v2 ); + pDisp->GetVert( pNode->GetCenterVertIndex(), v3 ); + return; + } + case 5: + { + pDisp->GetVert( pNode->GetCenterVertIndex(), v1 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 6 ), v2 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 1 ), v3 ); + return; + } + case 6: + { + pDisp->GetVert( pNode->GetCenterVertIndex(), v1 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 1 ), v2 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 2 ), v3 ); + return; + } + case 7: + { + pDisp->GetVert( pNode->GetNeighborVertIndex( 2 ), v1 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 1 ), v2 ); + pDisp->GetVert( pNode->GetNeighborVertIndex( 7 ), v3 ); + return; + } + default: { return; } + } +} + + +//============================================================================= +// +// CCoreDispInfo Functions +// + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CCoreDispInfo::CCoreDispInfo() +{ + m_pVerts = NULL; + m_RenderIndices = NULL; + m_Nodes = NULL; + m_pTris = NULL; + + // initialize the base surface data + m_Surf.Init(); + + // + // initialize the disp info + // + m_Power = 0; + m_Elevation = 0.0f; + m_RenderIndexCount = 0; + m_RenderCounter = 0; + m_bTouched = false; + + m_pNext = NULL; + + m_ppListBase = NULL; + m_ListSize = 0; + m_nListIndex = -1; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CCoreDispInfo::~CCoreDispInfo() +{ + if (m_pVerts) + delete [] m_pVerts; + if (m_RenderIndices) + delete [] m_RenderIndices; + if (m_Nodes) + delete [] m_Nodes; + if (m_pTris) + delete [] m_pTris; +} + + +#if 0 +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::InitSurf( int parentIndex, Vector points[4], Vector normals[4], + Vector2D texCoords[4], Vector2D lightCoords[4][4], int contents, int flags, + bool bGenerateSurfPointStart, Vector& startPoint, + bool bHasMappingAxes, Vector& uAxis, Vector& vAxis ) +{ + // save the "parent" index + m_Surf.m_Index = parentIndex; + + // + // save the surface points and point normals, texture coordinates, and + // lightmap coordinates + // + m_Surf.m_PointCount = CSurface::QUAD_POINT_COUNT; + for( int i = 0; i < CSurface::QUAD_POINT_COUNT; i++ ) + { + VectorCopy( points[i], m_Surf.m_Points[i] ); + + if( normals ) + { + VectorCopy( normals[i], m_Surf.m_pVerts[i].m_Normal ); + } + + if( texCoords ) + { + Vector2DCopy( texCoords[i], m_Surf.m_TexCoords[i] ); + } + + if( lightCoords ) + { + Assert( NUM_BUMP_VECTS == 3 ); + Vector2DCopy( lightCoords[0][i], m_Surf.m_LightCoords[i][0] ); + Vector2DCopy( lightCoords[1][i], m_Surf.m_LightCoords[i][1] ); + Vector2DCopy( lightCoords[2][i], m_Surf.m_LightCoords[i][2] ); + Vector2DCopy( lightCoords[3][i], m_Surf.m_LightCoords[i][3] ); + } + } + + // save the starting point + if( startPoint ) + { + VectorCopy( startPoint, m_Surf.m_PointStart ); + } + + // + // save the surface contents and flags + // + m_Contents = contents; + m_Flags = flags; + + // + // adjust surface points, texture coordinates, etc.... + // + if( bHasMappingAxes && ( m_Surf.m_PointStartIndex == -1 ) ) + { + GeneratePointStartIndexFromMappingAxes( uAxis, vAxis ); + } + else + { + // + // adjust the surf data + // + if( bGenerateSurfPointStart ) + { + GenerateSurfPointStartIndex(); + } + else + { + FindSurfPointStartIndex(); + } + } + + AdjustSurfPointData(); +} +#endif + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::InitDispInfo( int power, int minTess, float smoothingAngle, + float *alphas, Vector *dispVectorField, float *dispDistances ) +{ + Assert( power >= MIN_MAP_DISP_POWER && power <= MAX_MAP_DISP_POWER ); + + // + // general displacement data + // + m_Power = power; + + if ( ( minTess & 0x80000000 ) != 0 ) + { + // If the high bit is set, this represents FLAGS (SURF_NOPHYSICS_COLL, etc.) flags. + int nFlags = minTess; + nFlags &= ~0x80000000; + GetSurface()->SetFlags( nFlags ); + } + + // Allocate + initialize verts + int size = GetSize(); + m_pVerts = new CoreDispVert_t[size]; + + int nIndexCount = size * 2 * 3; + m_RenderIndices = new unsigned short[nIndexCount]; + + int nNodeCount = GetNodeCount(power); + m_Nodes = new CCoreDispNode[nNodeCount]; + + int i; + for( i = 0; i < size; i++ ) + { + m_pVerts[i].m_FieldVector.Init(); + m_pVerts[i].m_SubdivPos.Init(); + m_pVerts[i].m_SubdivNormal.Init(); + + m_pVerts[i].m_FieldDistance = 0.0f; + + m_pVerts[i].m_Vert.Init(); + m_pVerts[i].m_FlatVert.Init(); + m_pVerts[i].m_Normal.Init(); + m_pVerts[i].m_TangentS.Init(); + m_pVerts[i].m_TangentT.Init(); + m_pVerts[i].m_TexCoord.Init(); + + for( int j = 0; j < ( NUM_BUMP_VECTS + 1 ); j++ ) + { + m_pVerts[i].m_LuxelCoords[j].Init(); + } + + m_pVerts[i].m_Alpha = 0.0f; + } + + for( i = 0; i < nIndexCount; i++ ) + { + m_RenderIndices[i] = 0; + } + + for( i = 0; i < nNodeCount; i++ ) + { + m_Nodes[i].Init(); + } + + // + // save the displacement vector field and distances within the field + // offset have been combined with fieldvectors at this point!!! + // + if (alphas && dispVectorField && dispDistances) + { + for( i = 0; i < size; i++ ) + { + VectorCopy( dispVectorField[i], m_pVerts[i].m_FieldVector ); + m_pVerts[i].m_FieldDistance = dispDistances[i]; + m_pVerts[i].m_Alpha = alphas[i]; + } + } + + // Init triangle information. + int nTriCount = GetTriCount(); + if ( nTriCount != 0 ) + { + m_pTris = new CoreDispTri_t[nTriCount]; + if ( m_pTris ) + { + InitTris(); + } + } +} + + +void CCoreDispInfo::InitDispInfo( int power, int minTess, float smoothingAngle, const CDispVert *pVerts, + const CDispTri *pTris ) +{ + Vector vectors[MAX_DISPVERTS]; + float dists[MAX_DISPVERTS]; + float alphas[MAX_DISPVERTS]; + + int nVerts = NUM_DISP_POWER_VERTS( power ); + for ( int i=0; i < nVerts; i++ ) + { + vectors[i] = pVerts[i].m_vVector; + dists[i] = pVerts[i].m_flDist; + alphas[i] = pVerts[i].m_flAlpha; + } + + InitDispInfo( power, minTess, smoothingAngle, alphas, vectors, dists ); + + int nTris = NUM_DISP_POWER_TRIS( power ); + for ( int iTri = 0; iTri < nTris; ++iTri ) + { + m_pTris[iTri].m_uiTags = pTris[iTri].m_uiTags; + } +} + + +void CCoreDispInfo::SetDispUtilsHelperInfo( CCoreDispInfo **ppListBase, int listSize ) +{ + m_ppListBase = ppListBase; + m_ListSize = listSize; +} + +const CPowerInfo* CCoreDispInfo::GetPowerInfo() const +{ + return ::GetPowerInfo( GetPower() ); +} + +CDispNeighbor* CCoreDispInfo::GetEdgeNeighbor( int index ) +{ + return GetSurface()->GetEdgeNeighbor( index ); +} + +CDispCornerNeighbors* CCoreDispInfo::GetCornerNeighbors( int index ) +{ + return GetSurface()->GetCornerNeighbors( index ); +} + +CDispUtilsHelper* CCoreDispInfo::GetDispUtilsByIndex( int index ) +{ + Assert( m_ppListBase ); + return index == 0xFFFF ? 0 : m_ppListBase[index]; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::BuildTriTLtoBR( int ndx ) +{ + // get width and height of displacement maps + int nWidth = ( ( 1 << m_Power ) + 1 ); + + m_RenderIndices[m_RenderIndexCount] = ndx; + m_RenderIndices[m_RenderIndexCount+1] = ndx + nWidth; + m_RenderIndices[m_RenderIndexCount+2] = ndx + 1; + m_RenderIndexCount += 3; + + m_RenderIndices[m_RenderIndexCount] = ndx + 1; + m_RenderIndices[m_RenderIndexCount+1] = ndx + nWidth; + m_RenderIndices[m_RenderIndexCount+2] = ndx + nWidth + 1; + m_RenderIndexCount += 3; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::BuildTriBLtoTR( int ndx ) +{ + // get width and height of displacement maps + int nWidth = ( ( 1 << m_Power ) + 1 ); + + m_RenderIndices[m_RenderIndexCount] = ndx; + m_RenderIndices[m_RenderIndexCount+1] = ndx + nWidth; + m_RenderIndices[m_RenderIndexCount+2] = ndx + nWidth + 1; + m_RenderIndexCount += 3; + + m_RenderIndices[m_RenderIndexCount] = ndx; + m_RenderIndices[m_RenderIndexCount+1] = ndx + nWidth + 1; + m_RenderIndices[m_RenderIndexCount+2] = ndx + 1; + m_RenderIndexCount += 3; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::GenerateCollisionSurface( void ) +{ + // get width and height of displacement maps + int nWidth = ( ( 1 << m_Power ) + 1 ); + int nHeight = ( ( 1 << m_Power ) + 1 ); + + // + // generate a fan tesselated (at quadtree node) rendering index list + // + m_RenderIndexCount = 0; + for ( int iV = 0; iV < ( nHeight - 1 ); iV++ ) + { + for ( int iU = 0; iU < ( nWidth - 1 ); iU++ ) + { + int ndx = ( iV * nWidth ) + iU; + + // test whether or not the index is odd + bool bOdd = ( ( ndx %2 ) == 1 ); + + // Top Left to Bottom Right + if( bOdd ) + { + BuildTriTLtoBR( ndx ); + } + // Bottom Left to Top Right + else + { + BuildTriBLtoTR( ndx ); + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::GenerateCollisionData( void ) +{ + GenerateCollisionSurface(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcTriSurfPlanes( int nodeIndex, int indices[8][3] ) +{ + // + // calculate plane info for each face + // + for( int i = 0; i < 8; i++ ) + { + Vector v[3]; + VectorCopy( m_pVerts[indices[i][0]].m_Vert, v[0] ); + VectorCopy( m_pVerts[indices[i][1]].m_Vert, v[1] ); + VectorCopy( m_pVerts[indices[i][2]].m_Vert, v[2] ); + + Vector seg[2]; + VectorSubtract( v[1], v[0], seg[0] ); + VectorSubtract( v[2], v[0], seg[1] ); + + Vector normal; + CrossProduct( seg[1], seg[0], normal ); + VectorNormalize( normal ); + float dist = DotProduct( v[0], normal ); + + m_Nodes[nodeIndex].SetTriPlane( i, normal, dist ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcRayBoundingBoxes( int nodeIndex, int indices[8][3] ) +{ + Vector triMin, triMax; + + for( int i = 0; i < 4; i++ ) + { + triMin[0] = triMax[0] = m_pVerts[indices[(i*2)][0]].m_Vert[0]; + triMin[1] = triMax[1] = m_pVerts[indices[(i*2)][0]].m_Vert[1]; + triMin[2] = triMax[2] = m_pVerts[indices[(i*2)][0]].m_Vert[2]; + + for( int j = 0; j < 3; j++ ) + { + // + // minimum + // + if( triMin[0] > m_pVerts[indices[(i*2)][j]].m_Vert[0] ) + triMin[0] = m_pVerts[indices[(i*2)][j]].m_Vert[0]; + if( triMin[0] > m_pVerts[indices[(i*2+1)][j]].m_Vert[0] ) + triMin[0] = m_pVerts[indices[(i*2+1)][j]].m_Vert[0]; + + if( triMin[1] > m_pVerts[indices[(i*2)][j]].m_Vert[1] ) + triMin[1] = m_pVerts[indices[(i*2)][j]].m_Vert[1]; + if( triMin[1] > m_pVerts[indices[(i*2+1)][j]].m_Vert[1] ) + triMin[1] = m_pVerts[indices[(i*2+1)][j]].m_Vert[1]; + + if( triMin[2] > m_pVerts[indices[(i*2)][j]].m_Vert[2] ) + triMin[2] = m_pVerts[indices[(i*2)][j]].m_Vert[2]; + if( triMin[2] > m_pVerts[indices[(i*2+1)][j]].m_Vert[2] ) + triMin[2] = m_pVerts[indices[(i*2+1)][j]].m_Vert[2]; + + // + // maximum + // + if( triMax[0] < m_pVerts[indices[(i*2)][j]].m_Vert[0] ) + triMax[0] = m_pVerts[indices[(i*2)][j]].m_Vert[0]; + if( triMax[0] < m_pVerts[indices[(i*2+1)][j]].m_Vert[0] ) + triMax[0] = m_pVerts[indices[(i*2+1)][j]].m_Vert[0]; + + if( triMax[1] < m_pVerts[indices[(i*2)][j]].m_Vert[1] ) + triMax[1] = m_pVerts[indices[(i*2)][j]].m_Vert[1]; + if( triMax[1] < m_pVerts[indices[(i*2+1)][j]].m_Vert[1] ) + triMax[1] = m_pVerts[indices[(i*2+1)][j]].m_Vert[1]; + + if( triMax[2] < m_pVerts[indices[(i*2)][j]].m_Vert[2] ) + triMax[2] = m_pVerts[indices[(i*2)][j]].m_Vert[2]; + if( triMax[2] < m_pVerts[indices[(i*2+1)][j]].m_Vert[2] ) + triMax[2] = m_pVerts[indices[(i*2+1)][j]].m_Vert[2]; + } + + m_Nodes[nodeIndex].SetRayBoundingBox( i, triMin, triMax ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcTriSurfBoundingBoxes( int nodeIndex, int indices[8][3] ) +{ + Vector triMin, triMax; + + for( int i = 0; i < 8; i++ ) + { + m_Nodes[nodeIndex].GetTriBoundingBox( i, triMin, triMax ); + + for( int j = 0; j < 3; j++ ) + { + // + // minimum + // + if( triMin[0] > m_pVerts[indices[i][j]].m_Vert[0] ) + triMin[0] = m_pVerts[indices[i][j]].m_Vert[0]; + + if( triMin[1] > m_pVerts[indices[i][j]].m_Vert[1] ) + triMin[1] = m_pVerts[indices[i][j]].m_Vert[1]; + + if( triMin[2] > m_pVerts[indices[i][j]].m_Vert[2] ) + triMin[2] = m_pVerts[indices[i][j]].m_Vert[2]; + + // + // maximum + // + if( triMax[0] < m_pVerts[indices[i][j]].m_Vert[0] ) + triMax[0] = m_pVerts[indices[i][j]].m_Vert[0]; + + if( triMax[1] < m_pVerts[indices[i][j]].m_Vert[1] ) + triMax[1] = m_pVerts[indices[i][j]].m_Vert[1]; + + if( triMax[2] < m_pVerts[indices[i][j]].m_Vert[2] ) + triMax[2] = m_pVerts[indices[i][j]].m_Vert[2]; + } + + m_Nodes[nodeIndex].SetTriBoundingBox( i, triMin, triMax ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcTriSurfIndices( int nodeIndex, int indices[8][3] ) +{ + indices[0][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 4 ); + indices[0][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 0 ); + indices[0][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 3 ); + + indices[1][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 3 ); + indices[1][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 0 ); + indices[1][2] = m_Nodes[nodeIndex].GetCenterVertIndex(); + + indices[2][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 3 ); + indices[2][1] = m_Nodes[nodeIndex].GetCenterVertIndex(); + indices[2][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 5 ); + + indices[3][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 5 ); + indices[3][1] = m_Nodes[nodeIndex].GetCenterVertIndex(); + indices[3][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 2 ); + + indices[4][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 0 ); + indices[4][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 6 ); + indices[4][2] = m_Nodes[nodeIndex].GetCenterVertIndex(); + + indices[5][0] = m_Nodes[nodeIndex].GetCenterVertIndex(); + indices[5][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 6 ); + indices[5][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 1 ); + + indices[6][0] = m_Nodes[nodeIndex].GetCenterVertIndex(); + indices[6][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 1 ); + indices[6][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 2 ); + + indices[7][0] = m_Nodes[nodeIndex].GetNeighborVertIndex( 2 ); + indices[7][1] = m_Nodes[nodeIndex].GetNeighborVertIndex( 1 ); + indices[7][2] = m_Nodes[nodeIndex].GetNeighborVertIndex( 7 ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcTriSurfInfoAtNode( int nodeIndex ) +{ + int indices[8][3]; + + CalcTriSurfIndices( nodeIndex, indices ); + CalcTriSurfBoundingBoxes( nodeIndex, indices ); + CalcRayBoundingBoxes( nodeIndex, indices ); + CalcTriSurfPlanes( nodeIndex, indices ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcMinMaxBoundingBoxAtNode( int nodeIndex, Vector& bMin, Vector& bMax ) +{ + // get the child node index + int childNodeIndex = GetNodeChild( m_Power, nodeIndex, 4 ); + + // get initial bounding box values + m_Nodes[childNodeIndex].GetBoundingBox( bMin, bMax ); + + Vector nodeMin, nodeMax; + for( int i = 1, j = 5; i < 4; i++, j++ ) + { + // + // get the child node bounding box + // + childNodeIndex = GetNodeChild( m_Power, nodeIndex, j ); + m_Nodes[childNodeIndex].GetBoundingBox( nodeMin, nodeMax ); + + // minimum + if( bMin[0] > nodeMin[0] ) + bMin[0] = nodeMin[0]; + + if( bMin[1] > nodeMin[1] ) + bMin[1] = nodeMin[1]; + + if( bMin[2] > nodeMin[2] ) + bMin[2] = nodeMin[2]; + + // maximum + if( bMax[0] < nodeMax[0] ) + bMax[0] = nodeMax[0]; + + if( bMax[1] < nodeMax[1] ) + bMax[1] = nodeMax[1]; + + if( bMax[2] < nodeMax[2] ) + bMax[2] = nodeMax[2]; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcBoundingBoxAtNode( int nodeIndex ) +{ + Vector bMin, bMax; + + // + // initialize the minimum and maximum values for the bounding box + // + int level = GetNodeLevel( nodeIndex ); + + int vertIndex = m_Nodes[nodeIndex].GetCenterVertIndex(); + if( level == m_Power ) + { + VectorCopy( m_pVerts[vertIndex].m_Vert, bMin ); + VectorCopy( m_pVerts[vertIndex].m_Vert, bMax ); + } + else + { + CalcMinMaxBoundingBoxAtNode( nodeIndex, bMin, bMax ); + + if( bMin[0] > m_pVerts[vertIndex].m_Vert[0] ) + bMin[0] = m_pVerts[vertIndex].m_Vert[0]; + + if( bMin[1] > m_pVerts[vertIndex].m_Vert[1] ) + bMin[1] = m_pVerts[vertIndex].m_Vert[1]; + + if( bMin[2] > m_pVerts[vertIndex].m_Vert[2] ) + bMin[2] = m_pVerts[vertIndex].m_Vert[2]; + + + if( bMax[0] < m_pVerts[vertIndex].m_Vert[0] ) + bMax[0] = m_pVerts[vertIndex].m_Vert[0]; + + if( bMax[1] < m_pVerts[vertIndex].m_Vert[1] ) + bMax[1] = m_pVerts[vertIndex].m_Vert[1]; + + if( bMax[2] < m_pVerts[vertIndex].m_Vert[2] ) + bMax[2] = m_pVerts[vertIndex].m_Vert[2]; + } + + for( int i = 0; i < 8; i++ ) + { + int neighborVertIndex = m_Nodes[nodeIndex].GetNeighborVertIndex( i ); + + // + // minimum + // + if( bMin[0] > m_pVerts[neighborVertIndex].m_Vert[0] ) + bMin[0] = m_pVerts[neighborVertIndex].m_Vert[0]; + + if( bMin[1] > m_pVerts[neighborVertIndex].m_Vert[1] ) + bMin[1] = m_pVerts[neighborVertIndex].m_Vert[1]; + + if( bMin[2] > m_pVerts[neighborVertIndex].m_Vert[2] ) + bMin[2] = m_pVerts[neighborVertIndex].m_Vert[2]; + + // + // maximum + // + if( bMax[0] < m_pVerts[neighborVertIndex].m_Vert[0] ) + bMax[0] = m_pVerts[neighborVertIndex].m_Vert[0]; + + if( bMax[1] < m_pVerts[neighborVertIndex].m_Vert[1] ) + bMax[1] = m_pVerts[neighborVertIndex].m_Vert[1]; + + if( bMax[2] < m_pVerts[neighborVertIndex].m_Vert[2] ) + bMax[2] = m_pVerts[neighborVertIndex].m_Vert[2]; + } + + m_Nodes[nodeIndex].SetBoundingBox( bMin, bMax ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +float CCoreDispInfo::GetMaxErrorFromChildren( int nodeIndex, int level ) +{ + // + // check for children nodes + // + if( level == m_Power ) + return 0.0f; + + // + // get the child's error term and save the greatest error -- SW, SE, NW, NE + // + float errorTerm = 0.0f; + for( int i = 4; i < 8; i++ ) + { + int childIndex = GetNodeChild( m_Power, nodeIndex, i ); + + float nodeErrorTerm = m_Nodes[childIndex].GetErrorTerm(); + if( errorTerm < nodeErrorTerm ) + { + errorTerm = nodeErrorTerm; + } + } + + return errorTerm; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcErrorTermAtNode( int nodeIndex, int level ) +{ + if( level == m_Power ) + return; + + // + // get the vertex indices + // + int neighborVertIndices[9]; + for( int i = 0; i < 8; i++ ) + { + neighborVertIndices[i] = m_Nodes[nodeIndex].GetNeighborVertIndex( i ); + } + neighborVertIndices[8] = m_Nodes[nodeIndex].GetCenterVertIndex(); + + + // + // calculate the error terms + // + Vector segment; + Vector v; + + VectorAdd( m_pVerts[neighborVertIndices[5]].m_Vert, m_pVerts[neighborVertIndices[4]].m_Vert, v ); + VectorScale( v, 0.5f, v ); + VectorSubtract( m_pVerts[neighborVertIndices[0]].m_Vert, v, segment ); + float errorTerm = ( float )VectorLength( segment ); + + VectorAdd( m_pVerts[neighborVertIndices[5]].m_Vert, m_pVerts[neighborVertIndices[6]].m_Vert, v ); + VectorScale( v, 0.5f, v ); + VectorSubtract( m_pVerts[neighborVertIndices[1]].m_Vert, v, segment ); + if( errorTerm < ( float )VectorLength( segment ) ) + errorTerm = ( float )VectorLength( segment ); + + VectorAdd( m_pVerts[neighborVertIndices[6]].m_Vert, m_pVerts[neighborVertIndices[7]].m_Vert, v ); + VectorScale( v, 0.5f, v ); + VectorSubtract( m_pVerts[neighborVertIndices[2]].m_Vert, v, segment ); + if( errorTerm < ( float )VectorLength( segment ) ) + errorTerm = ( float )VectorLength( segment ); + + VectorAdd( m_pVerts[neighborVertIndices[7]].m_Vert, m_pVerts[neighborVertIndices[4]].m_Vert, v ); + VectorScale( v, 0.5f, v ); + VectorSubtract( m_pVerts[neighborVertIndices[3]].m_Vert, v, segment ); + if( errorTerm < ( float )VectorLength( segment ) ) + errorTerm = ( float )VectorLength( segment ); + + VectorAdd( m_pVerts[neighborVertIndices[4]].m_Vert, m_pVerts[neighborVertIndices[6]].m_Vert, v ); + VectorScale( v, 0.5f, v ); + VectorSubtract( m_pVerts[neighborVertIndices[8]].m_Vert, v, segment ); + if( errorTerm < ( float )VectorLength( segment ) ) + errorTerm = ( float )VectorLength( segment ); + + VectorAdd( m_pVerts[neighborVertIndices[5]].m_Vert, m_pVerts[neighborVertIndices[7]].m_Vert, v ); + VectorScale( v, 0.5f, v ); + VectorSubtract( m_pVerts[neighborVertIndices[8]].m_Vert, v, segment ); + if( errorTerm < ( float )VectorLength( segment ) ) + errorTerm = ( float )VectorLength( segment ); + + // + // add the max child's error term + // + errorTerm += GetMaxErrorFromChildren( nodeIndex, level ); + + // set the error term + m_Nodes[nodeIndex].SetErrorTerm( errorTerm ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcNeighborVertIndicesAtNode( int nodeIndex, int level ) +{ + // calculate the shift in direction in the matrix + int shift = ( 1 << ( m_Power - level ) ); + + // calculate the width, height of the displacement surface (are uniform) + int extent = ( ( 1 << m_Power ) + 1 ); + + // + // get the neighbor vertex indices (defining the surface at the node level) + // + for( int direction = 0; direction < 8; direction++ ) + { + // + // get the parent vertex index in component form + // + int posX = m_Nodes[nodeIndex].GetCenterVertIndex() % extent; + int posY = m_Nodes[nodeIndex].GetCenterVertIndex() / extent; + + // + // calculate the neighboring vertex indices for surface rendering + // + bool bError = false; + switch( direction ) + { + case WEST: { posX -= shift; break; } + case NORTH: { posY += shift; break; } + case EAST: { posX += shift; break; } + case SOUTH: { posY -= shift; break; } + case SOUTHWEST: { posX -= shift; posY -= shift; break; } + case SOUTHEAST: { posX += shift; posY -= shift; break; } + case NORTHWEST: { posX -= shift; posY += shift; break; } + case NORTHEAST: { posX += shift; posY += shift; break; } + default: { bError = true; break; } + } + + if( bError ) + { + m_Nodes[nodeIndex].SetNeighborVertIndex( direction, -99999 ); + } + else + { + m_Nodes[nodeIndex].SetNeighborVertIndex( direction, ( ( posY * extent ) + posX ) ); + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcNodeInfo( int nodeIndex, int terminationLevel ) +{ + // get the level of the current node + int level = GetNodeLevel( nodeIndex ); + + // + // get the node data at the termination level + // + if( level == terminationLevel ) + { + // get the neighbor vertex indices (used to create surface at node level) + CalcNeighborVertIndicesAtNode( nodeIndex, level ); + + // get the neighbor node indices + //CalcNeighborNodeIndicesAtNode( nodeIndex, level ); + + // calculate the error term at the node + CalcErrorTermAtNode( nodeIndex, level ); + + // calcluate the axial-aligned bounding box at the node + CalcBoundingBoxAtNode( nodeIndex ); + + // calculate the triangular surface info at the node + CalcTriSurfInfoAtNode( nodeIndex ); + + return; + } + + // + // continue recursion (down to nodes "children") + // + for( int i = 4; i < 8; i++ ) + { + int childIndex = GetNodeChild( m_Power, nodeIndex, i ); + CalcNodeInfo( childIndex, terminationLevel ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CCoreDispInfo::GetNodeVertIndexFromParentIndex( int level, int parentVertIndex, int direction ) +{ + // calculate the "shift" + int shift = ( 1 << ( m_Power - ( level + 1 ) ) ); + + // calculate the width and height of displacement (is uniform) + int extent = ( ( 1 << m_Power ) + 1 ); + + // get the parent vertex index in component form + int posX = parentVertIndex % extent; + int posY = parentVertIndex / extent; + + // + // calculate the child index based on the parent index and child + // direction + // + switch( direction ) + { + case SOUTHWEST: { posX -= shift; posY -= shift; break; } + case SOUTHEAST: { posX += shift; posY -= shift; break; } + case NORTHWEST: { posX -= shift; posY += shift; break; } + case NORTHEAST: { posX += shift; posY += shift; break; } + default: return -99999; + } + + // return the child vertex index + return ( ( posY * extent ) + posX ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcVertIndicesAtNodes( int nodeIndex ) +{ + // + // check for recursion termination ( node level = power ) + // + int level = GetNodeLevel( nodeIndex ); + if( level == m_Power ) + return; + + // + // get the children indices - SW, SE, NW, NE + // + int childIndices[4]; + int i, j; + for( i = 0, j = 4; i < 4; i++, j++ ) + { + childIndices[i] = GetNodeChild( m_Power, nodeIndex, j ); + int centerIndex = GetNodeVertIndexFromParentIndex( level, m_Nodes[nodeIndex].GetCenterVertIndex(), j ); + m_Nodes[childIndices[i]].SetCenterVertIndex( centerIndex ); + } + + // + // calculate the children's node vertex indices + // + for( i = 0; i < 4; i++ ) + { + CalcVertIndicesAtNodes( childIndices[i] ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::GenerateLODTree( void ) +{ + // + // calculate the displacement surface's vertex index at each quad-tree node + // centroid + // + int size = GetSize(); + int initialIndex = ( ( size - 1 ) >> 1 ); + m_Nodes[0].SetCenterVertIndex( initialIndex ); + CalcVertIndicesAtNodes( 0 ); + + // + // calculate the error terms, bounding boxes, and neighboring vertex indices + // at each node + // + for( int i = m_Power; i > 0; i-- ) + { + CalcNodeInfo( 0, i ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcDispSurfCoords( bool bLightMap, int lightmapID ) +{ + // + // get base surface texture coords + // + Vector2D texCoords[4]; + Vector2D luxelCoords[4]; + CCoreDispSurface *pSurf = GetSurface(); + + int i; + for( i = 0; i < 4; i++ ) + { + pSurf->GetTexCoord( i, texCoords[i] ); + pSurf->GetLuxelCoord( lightmapID, i, luxelCoords[i] ); + } + + // + // get images width and intervals along the edge + // + int postSpacing = GetPostSpacing(); + float ooInt = ( 1.0f / ( float )( postSpacing - 1 ) ); + + // + // calculate the parallel edge intervals + // + Vector2D edgeInt[2]; + if( !bLightMap ) + { + Vector2DSubtract( texCoords[1], texCoords[0], edgeInt[0] ); + Vector2DSubtract( texCoords[2], texCoords[3], edgeInt[1] ); + } + else + { + Vector2DSubtract( luxelCoords[1], luxelCoords[0], edgeInt[0] ); + Vector2DSubtract( luxelCoords[2], luxelCoords[3], edgeInt[1] ); + } + Vector2DMultiply( edgeInt[0], ooInt, edgeInt[0] ); + Vector2DMultiply( edgeInt[1], ooInt, edgeInt[1] ); + + // + // calculate the displacement points + // + for( i = 0; i < postSpacing; i++ ) + { + // + // position along parallel edges (start and end for a perpendicular segment) + // + Vector2D endPts[2]; + Vector2DMultiply( edgeInt[0], ( float )i, endPts[0] ); + Vector2DMultiply( edgeInt[1], ( float )i, endPts[1] ); + if( !bLightMap ) + { + Vector2DAdd( endPts[0], texCoords[0], endPts[0] ); + Vector2DAdd( endPts[1], texCoords[3], endPts[1] ); + } + else + { + Vector2DAdd( endPts[0], luxelCoords[0], endPts[0] ); + Vector2DAdd( endPts[1], luxelCoords[3], endPts[1] ); + } + + // + // interval length for perpendicular edge + // + Vector2D seg, segInt; + Vector2DSubtract( endPts[1], endPts[0], seg ); + Vector2DMultiply( seg, ooInt, segInt ); + + // + // calculate the material (texture or light) coordinate at each point + // + for( int j = 0; j < postSpacing; j++ ) + { + Vector2DMultiply( segInt, ( float )j, seg ); + + if( !bLightMap ) + { + Vector2DAdd( endPts[0], seg, m_pVerts[i*postSpacing+j].m_TexCoord ); + } + else + { + Vector2DAdd( endPts[0], seg, m_pVerts[i*postSpacing+j].m_LuxelCoords[lightmapID] ); + } + } + } +} + +#if 0 +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcDispSurfAlphas( void ) +{ + // + // get images width and intervals along the edge + // + int postSpacing = GetPostSpacing(); + float ooInt = ( 1.0f / ( float )( postSpacing - 1 ) ); + + // + // calculate the parallel edge intervals + // + float edgeInt[2]; + edgeInt[0] = m_Surf.m_Alpha[1] - m_Surf.m_Alpha[0]; + edgeInt[1] = m_Surf.m_Alpha[2] - m_Surf.m_Alpha[3]; + edgeInt[0] *= ooInt; + edgeInt[1] *= ooInt; + + // + // calculate the displacement points + // + for( int i = 0; i < postSpacing; i++ ) + { + // + // position along parallel edges (start and end for a perpendicular segment) + // + float endValues[2]; + + endValues[0] = edgeInt[0] * ( float )i; + endValues[1] = edgeInt[1] * ( float )i; + endValues[0] += m_Surf.m_Alpha[0]; + endValues[1] += m_Surf.m_Alpha[3]; + + // + // interval length for perpendicular edge + // + float seg, segInt; + seg = endValues[1] - endValues[0]; + segInt = seg * ooInt; + + // + // calculate the alpha value at each point + // + for( int j = 0; j < postSpacing; j++ ) + { + seg = segInt * ( float )j; + m_Alphas[i*postSpacing+j] = endValues[0] + seg; + } + } +} +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::GenerateDispSurfTangentSpaces( void ) +{ + // + // get texture axes from base surface + // + CCoreDispSurface *pSurf = GetSurface(); + Vector sAxis, tAxis; + pSurf->GetSAxis( sAxis ); + pSurf->GetTAxis( tAxis ); + + // + // calculate the tangent spaces + // + int size = GetSize(); + for( int i = 0; i < size; i++ ) + { + // + // create the axes - normals, tangents, and binormals + // + VectorCopy( tAxis, m_pVerts[i].m_TangentT ); + VectorNormalize( m_pVerts[i].m_TangentT ); + CrossProduct( m_pVerts[i].m_Normal, m_pVerts[i].m_TangentT, m_pVerts[i].m_TangentS ); + VectorNormalize( m_pVerts[i].m_TangentS ); + CrossProduct( m_pVerts[i].m_TangentS, m_pVerts[i].m_Normal, m_pVerts[i].m_TangentT ); + VectorNormalize( m_pVerts[i].m_TangentT ); + + Vector tmpVect; + Vector planeNormal; + pSurf->GetNormal( planeNormal ); + CrossProduct( sAxis, tAxis, tmpVect ); + if( DotProduct( planeNormal, tmpVect ) > 0.0f ) + { + VectorScale( m_pVerts[i].m_TangentS, -1.0f, m_pVerts[i].m_TangentS ); + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::CalcNormalFromEdges( int indexRow, int indexCol, bool bIsEdge[4], + Vector& normal ) +{ + // get the post spacing (size/interval of displacement surface) + int postSpacing = ( ( 1 << m_Power ) + 1 ); + + // initialize the normal accumulator - counter + Vector accumNormal; + int normalCount = 0; + + VectorClear( accumNormal ); + + Vector tmpVect[2]; + Vector tmpNormal; + + // + // check quadrant I (posX, posY) + // + if( bIsEdge[1] && bIsEdge[2] ) + { + // tri i + VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+indexRow].m_Vert, m_pVerts[indexCol*postSpacing+indexRow].m_Vert, tmpVect[0] ); + VectorSubtract( m_pVerts[indexCol*postSpacing+(indexRow+1)].m_Vert, m_pVerts[indexCol*postSpacing+indexRow].m_Vert, tmpVect[1] ); + CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); + VectorNormalize( tmpNormal ); + VectorAdd( accumNormal, tmpNormal, accumNormal ); + normalCount++; + + // tri 2 + VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+indexRow].m_Vert, m_pVerts[indexCol*postSpacing+(indexRow+1)].m_Vert, tmpVect[0] ); + VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+(indexRow+1)].m_Vert, m_pVerts[indexCol*postSpacing+(indexRow+1)].m_Vert, tmpVect[1] ); + CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); + VectorNormalize( tmpNormal ); + VectorAdd( accumNormal, tmpNormal, accumNormal ); + normalCount++; + } + + // + // check quadrant II (negX, posY) + // + if( bIsEdge[0] && bIsEdge[1] ) + { + // tri i + VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+(indexRow-1)].m_Vert, m_pVerts[indexCol*postSpacing+(indexRow-1)].m_Vert, tmpVect[0] ); + VectorSubtract( m_pVerts[indexCol*postSpacing+indexRow].m_Vert, m_pVerts[indexCol*postSpacing+(indexRow-1)].m_Vert, tmpVect[1] ); + CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); + VectorNormalize( tmpNormal ); + VectorAdd( accumNormal, tmpNormal, accumNormal ); + normalCount++; + + // tri 2 + VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+(indexRow-1)].m_Vert, m_pVerts[indexCol*postSpacing+indexRow].m_Vert, tmpVect[0] ); + VectorSubtract( m_pVerts[(indexCol+1)*postSpacing+indexRow].m_Vert, m_pVerts[indexCol*postSpacing+indexRow].m_Vert, tmpVect[1] ); + CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); + VectorNormalize( tmpNormal ); + VectorAdd( accumNormal, tmpNormal, accumNormal ); + normalCount++; + } + + // + // check quadrant III (negX, negY) + // + if( bIsEdge[0] && bIsEdge[3] ) + { + // tri i + VectorSubtract( m_pVerts[indexCol*postSpacing+(indexRow-1)].m_Vert, m_pVerts[(indexCol-1)*postSpacing+(indexRow-1)].m_Vert, tmpVect[0] ); + VectorSubtract( m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, m_pVerts[(indexCol-1)*postSpacing+(indexRow-1)].m_Vert, tmpVect[1] ); + CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); + VectorNormalize( tmpNormal ); + VectorAdd( accumNormal, tmpNormal, accumNormal ); + normalCount++; + + // tri 2 + VectorSubtract( m_pVerts[indexCol*postSpacing+(indexRow-1)].m_Vert, m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, tmpVect[0] ); + VectorSubtract( m_pVerts[indexCol*postSpacing+indexRow].m_Vert, m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, tmpVect[1] ); + CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); + VectorNormalize( tmpNormal ); + VectorAdd( accumNormal, tmpNormal, accumNormal ); + normalCount++; + } + + // + // check quadrant IV (posX, negY) + // + if( bIsEdge[2] && bIsEdge[3] ) + { + // tri i + VectorSubtract( m_pVerts[indexCol*postSpacing+indexRow].m_Vert, m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, tmpVect[0] ); + VectorSubtract( m_pVerts[(indexCol-1)*postSpacing+(indexRow+1)].m_Vert, m_pVerts[(indexCol-1)*postSpacing+indexRow].m_Vert, tmpVect[1] ); + CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); + VectorNormalize( tmpNormal ); + VectorAdd( accumNormal, tmpNormal, accumNormal ); + normalCount++; + + // tri 2 + VectorSubtract( m_pVerts[indexCol*postSpacing+indexRow].m_Vert, m_pVerts[(indexCol-1)*postSpacing+(indexRow+1)].m_Vert, tmpVect[0] ); + VectorSubtract( m_pVerts[indexCol*postSpacing+(indexRow+1)].m_Vert, m_pVerts[(indexCol-1)*postSpacing+(indexRow+1)].m_Vert, tmpVect[1] ); + CrossProduct( tmpVect[1], tmpVect[0], tmpNormal ); + VectorNormalize( tmpNormal ); + VectorAdd( accumNormal, tmpNormal, accumNormal ); + normalCount++; + } + + VectorScale( accumNormal, ( 1.0f / ( float )normalCount ), normal ); +} + + +//----------------------------------------------------------------------------- +// Purpose: This function determines if edges exist in each of the directions +// off of the given point (given in component form). We know ahead of +// time that there are only 4 possibilities. +// +// 1 "directions" +// 0 + 2 +// 3 +// +// Input: indexRow - row position +// indexCol - col position +// direction - the direction (edge) currently being evaluated +// postSpacing - the number of intervals in the row and col directions +// Output: the edge existed? (true/false) +//----------------------------------------------------------------------------- +bool CCoreDispInfo::DoesEdgeExist( int indexRow, int indexCol, int direction, int postSpacing ) +{ + switch( direction ) + { + case 0: + // left edge + if( ( indexRow - 1 ) < 0 ) + return false; + return true; + case 1: + // top edge + if( ( indexCol + 1 ) > ( postSpacing - 1 ) ) + return false; + return true; + case 2: + // right edge + if( ( indexRow + 1 ) > ( postSpacing - 1 ) ) + return false; + return true; + case 3: + // bottom edge + if( ( indexCol - 1 ) < 0 ) + return false; + return true; + default: + return false; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::GenerateDispSurfNormals( void ) +{ + // get the post spacing (size/interval of displacement surface) + int postSpacing = GetPostSpacing(); + + // + // generate the normals at each displacement surface vertex + // + for( int i = 0; i < postSpacing; i++ ) + { + for( int j = 0; j < postSpacing; j++ ) + { + bool bIsEdge[4]; + + // edges + for( int k = 0; k < 4; k++ ) + { + bIsEdge[k] = DoesEdgeExist( j, i, k, postSpacing ); + } + + Vector normal; + CalcNormalFromEdges( j, i, bIsEdge, normal ); + + // save generated normal + VectorCopy( normal, m_pVerts[i*postSpacing+j].m_Normal ); + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::GenerateDispSurf( void ) +{ + int i; + CCoreDispSurface *pSurf = GetSurface(); + Vector points[4]; + for( i = 0; i < 4; i++ ) + { + pSurf->GetPoint( i, points[i] ); + } + + // + // get the spacing (interval = width/height, are equal because it is uniform) along the edge + // + int postSpacing = GetPostSpacing(); + float ooInt = 1.0f / ( float )( postSpacing - 1 ); + + // + // calculate the opposite edge intervals + // + Vector edgeInt[2]; + VectorSubtract( points[1], points[0], edgeInt[0] ); + VectorScale( edgeInt[0], ooInt, edgeInt[0] ); + VectorSubtract( points[2], points[3], edgeInt[1] ); + VectorScale( edgeInt[1], ooInt, edgeInt[1] ); + + Vector elevNormal; + elevNormal.Init(); + if( m_Elevation != 0.0f ) + { + pSurf->GetNormal( elevNormal ); + VectorScale( elevNormal, m_Elevation, elevNormal ); + } + + // + // calculate the displaced vertices + // + for( i = 0; i < postSpacing; i++ ) + { + // + // calculate segment interval between opposite edges + // + Vector endPts[2]; + VectorScale( edgeInt[0], ( float )i, endPts[0] ); + VectorAdd( endPts[0], points[0], endPts[0] ); + VectorScale( edgeInt[1], ( float )i, endPts[1] ); + VectorAdd( endPts[1], points[3], endPts[1] ); + + Vector seg, segInt; + VectorSubtract( endPts[1], endPts[0], seg ); + VectorScale( seg, ooInt, segInt ); + + // + // calculate the surface vertices + // + for( int j = 0; j < postSpacing; j++ ) + { + int ndx = i * postSpacing + j; + + CoreDispVert_t *pVert = &m_pVerts[ndx]; + + // calculate the flat surface position -- saved separately + pVert->m_FlatVert = endPts[0] + ( segInt * ( float )j ); + + // start with the base surface position + pVert->m_Vert = pVert->m_FlatVert; + + // add the elevation vector -- if it exists + if( m_Elevation != 0.0f ) + { + pVert->m_Vert += elevNormal; + } + + // add the subdivision surface position + pVert->m_Vert += pVert->m_SubdivPos; + + // add the displacement field direction(normalized) and distance + pVert->m_Vert += pVert->m_FieldVector * pVert->m_FieldDistance; + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//bool CCoreDispInfo::Create( int creationFlags ) +bool CCoreDispInfo::Create( void ) +{ + // sanity check + CCoreDispSurface *pSurf = GetSurface(); + if( pSurf->GetPointCount() != 4 ) + return false; + + // generate the displacement surface + GenerateDispSurf(); + + GenerateDispSurfNormals(); + + GenerateDispSurfTangentSpaces(); + + CalcDispSurfCoords( false, 0 ); + + for( int bumpID = 0; bumpID < ( NUM_BUMP_VECTS + 1 ); bumpID++ ) + { + CalcDispSurfCoords( true, bumpID ); + } + + GenerateLODTree(); + + GenerateCollisionData(); + + CreateTris(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Create a displacement surface without generating the LOD for it. +//----------------------------------------------------------------------------- +bool CCoreDispInfo::CreateWithoutLOD( void ) +{ + // sanity check + CCoreDispSurface *pSurf = GetSurface(); + if( pSurf->GetPointCount() != 4 ) + return false; + + GenerateDispSurf(); + + GenerateDispSurfNormals(); + + GenerateDispSurfTangentSpaces(); + + CalcDispSurfCoords( false, 0 ); + + for( int bumpID = 0; bumpID < ( NUM_BUMP_VECTS + 1 ); bumpID++ ) + { + CalcDispSurfCoords( true, bumpID ); + } + GenerateCollisionData(); + + CreateTris(); + + return true; +} + + +// Turn the optimizer back on +#ifdef _MSC_VER +#pragma optimize( "", on ) +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::GetPositionOnSurface( float u, float v, Vector &vPos, + Vector *pNormal, float *pAlpha ) +{ + Vector2D dispUV( u, v ); + DispUVToSurf( dispUV, vPos, pNormal, pAlpha ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::BaseFacePlaneToDispUV( Vector const &planePt, Vector2D &dispUV ) +{ + // Get the base surface points. + CCoreDispSurface *pSurf = GetSurface(); + Vector vecPoints[4]; + for( int iPoint = 0; iPoint < 4; ++iPoint ) + { + pSurf->GetPoint( iPoint, vecPoints[iPoint] ); + } + + PointInQuadToBarycentric( vecPoints[0], vecPoints[3], vecPoints[2], vecPoints[1], planePt, dispUV ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::DispUVToSurf_TriTLToBR_1( const Vector &vecIntersectPoint, + int nSnapU, int nNextU, int nSnapV, int nNextV, + Vector &vecPoint, Vector *pNormal, float *pAlpha, + bool bBackup ) +{ + int nWidth = GetWidth(); + + int nIndices[3]; + nIndices[0] = nNextV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nNextU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector vecFlatVerts[3], vecVerts[3]; + float flAlphas[3]; + for ( int iVert = 0; iVert < 3; ++iVert ) + { + vecFlatVerts[iVert] = m_pVerts[nIndices[iVert]].m_FlatVert; + vecVerts[iVert] = m_pVerts[nIndices[iVert]].m_Vert; + flAlphas[iVert] = m_pVerts[nIndices[iVert]].m_Alpha; + } + + if ( nSnapU == nNextU ) + { + if ( nSnapV == nNextV ) + { + vecPoint = vecVerts[0]; + *pAlpha = flAlphas[0]; + } + else + { + float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); + vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); + + if ( pAlpha ) + { + *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); + } + } + + if( pNormal ) + { + Vector edgeU = vecVerts[0] - vecVerts[1]; + Vector edgeV = vecVerts[2] - vecVerts[1]; + *pNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( *pNormal ); + } + } + else if ( nSnapV == nNextV ) + { + if ( nSnapU == nNextU ) + { + vecPoint = vecVerts[0]; + *pAlpha = flAlphas[0]; + } + else + { + float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); + vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); + + if ( pAlpha ) + { + *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); + } + } + + if( pNormal ) + { + Vector edgeU = vecVerts[0] - vecVerts[1]; + Vector edgeV = vecVerts[2] - vecVerts[1]; + *pNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( *pNormal ); + } + } + else + { + float flCfs[3]; + if ( CalcBarycentricCooefs( vecFlatVerts[0], vecFlatVerts[1], vecFlatVerts[2], vecIntersectPoint, flCfs[0], flCfs[1], flCfs[2] ) ) + { + vecPoint = ( vecVerts[0] * flCfs[0] ) + ( vecVerts[1] * flCfs[1] ) + ( vecVerts[2] * flCfs[2] ); + + if( pAlpha ) + { + *pAlpha = ( flAlphas[0] * flCfs[0] ) + ( flAlphas[1] * flCfs[1] ) + ( flAlphas[2] * flCfs[2] ); + } + + if( pNormal ) + { + Vector edgeU = vecVerts[0] - vecVerts[1]; + Vector edgeV = vecVerts[2] - vecVerts[1]; + *pNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( *pNormal ); + } + } + else + { + if ( !bBackup ) + { + DispUVToSurf_TriTLToBR_2( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, true ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::DispUVToSurf_TriTLToBR_2( const Vector &vecIntersectPoint, + int nSnapU, int nNextU, int nSnapV, int nNextV, + Vector &vecPoint, Vector *pNormal, float *pAlpha, + bool bBackup ) +{ + int nWidth = GetWidth(); + + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nSnapU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector vecFlatVerts[3], vecVerts[3]; + float flAlphas[3]; + for ( int iVert = 0; iVert < 3; ++iVert ) + { + vecFlatVerts[iVert] = m_pVerts[nIndices[iVert]].m_FlatVert; + vecVerts[iVert] = m_pVerts[nIndices[iVert]].m_Vert; + flAlphas[iVert] = m_pVerts[nIndices[iVert]].m_Alpha; + } + + if ( nSnapU == nNextU ) + { + if ( nSnapV == nNextV ) + { + vecPoint = vecVerts[0]; + *pAlpha = flAlphas[0]; + } + else + { + float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[1] - vecFlatVerts[0] ).Length(); + vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[1] - vecVerts[0] ) ); + + if ( pAlpha ) + { + *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[1] - flAlphas[0] ) ); + } + } + + if( pNormal ) + { + Vector edgeU = vecVerts[2] - vecVerts[0]; + Vector edgeV = vecVerts[1] - vecVerts[0]; + *pNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( *pNormal ); + } + } + else if ( nSnapV == nNextV ) + { + if ( nSnapU == nNextU ) + { + vecPoint = vecVerts[0]; + *pAlpha = flAlphas[0]; + } + else + { + float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); + vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); + + if ( pAlpha ) + { + *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); + } + } + + if( pNormal ) + { + Vector edgeU = vecVerts[2] - vecVerts[0]; + Vector edgeV = vecVerts[1] - vecVerts[0]; + *pNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( *pNormal ); + } + } + else + { + float flCfs[3]; + if ( CalcBarycentricCooefs( vecFlatVerts[0], vecFlatVerts[1], vecFlatVerts[2], vecIntersectPoint, flCfs[0], flCfs[1], flCfs[2] ) ) + { + vecPoint = ( vecVerts[0] * flCfs[0] ) + ( vecVerts[1] * flCfs[1] ) + ( vecVerts[2] * flCfs[2] ); + + if( pAlpha ) + { + *pAlpha = ( flAlphas[0] * flCfs[0] ) + ( flAlphas[1] * flCfs[1] ) + ( flAlphas[2] * flCfs[2] ); + } + + if( pNormal ) + { + Vector edgeU = vecVerts[2] - vecVerts[0]; + Vector edgeV = vecVerts[1] - vecVerts[0]; + *pNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( *pNormal ); + } + } + else + { + if ( !bBackup ) + { + DispUVToSurf_TriTLToBR_1( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, true ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::DispUVToSurf_TriTLToBR( Vector &vecPoint, Vector *pNormal, float *pAlpha, + float flU, float flV, const Vector &vecIntersectPoint ) +{ + const float TRIEDGE_EPSILON = 0.00001f; + + int nWidth = GetWidth(); + int nHeight = GetHeight(); + + int nSnapU = static_cast( flU ); + int nSnapV = static_cast( flV ); + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast( nSnapU ); + float flFracV = flV - static_cast( nSnapV ); + + if ( ( flFracU + flFracV ) >= ( 1.0f + TRIEDGE_EPSILON ) ) + { + DispUVToSurf_TriTLToBR_1( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, false ); + } + else + { + DispUVToSurf_TriTLToBR_2( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::DispUVToSurf_TriBLToTR_1( const Vector &vecIntersectPoint, + int nSnapU, int nNextU, int nSnapV, int nNextV, + Vector &vecPoint, Vector *pNormal, float *pAlpha, + bool bBackup ) +{ + int nWidth = GetWidth(); + + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nSnapU; + nIndices[2] = nNextV * nWidth + nNextU; + + Vector vecFlatVerts[3], vecVerts[3]; + float flAlphas[3]; + for ( int iVert = 0; iVert < 3; ++iVert ) + { + vecFlatVerts[iVert] = m_pVerts[nIndices[iVert]].m_FlatVert; + vecVerts[iVert] = m_pVerts[nIndices[iVert]].m_Vert; + flAlphas[iVert] = m_pVerts[nIndices[iVert]].m_Alpha; + } + + if ( nSnapU == nNextU ) + { + if ( nSnapV == nNextV ) + { + vecPoint = vecVerts[0]; + *pAlpha = flAlphas[0]; + } + else + { + float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); + vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); + + if ( pAlpha ) + { + *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); + } + } + + if( pNormal ) + { + Vector edgeU = vecVerts[2] - vecVerts[1]; + Vector edgeV = vecVerts[0] - vecVerts[1]; + *pNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( *pNormal ); + } + } + else if ( nSnapV == nNextV ) + { + if ( nSnapU == nNextU ) + { + vecPoint = vecVerts[0]; + *pAlpha = flAlphas[0]; + } + else + { + float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); + vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); + + if ( pAlpha ) + { + *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); + } + } + + if( pNormal ) + { + Vector edgeU = vecVerts[2] - vecVerts[1]; + Vector edgeV = vecVerts[0] - vecVerts[1]; + *pNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( *pNormal ); + } + } + else + { + float flCfs[3]; + if ( CalcBarycentricCooefs( vecFlatVerts[0], vecFlatVerts[1], vecFlatVerts[2], vecIntersectPoint, flCfs[0], flCfs[1], flCfs[2] ) ) + { + vecPoint = ( vecVerts[0] * flCfs[0] ) + ( vecVerts[1] * flCfs[1] ) + ( vecVerts[2] * flCfs[2] ); + + if( pAlpha ) + { + *pAlpha = ( flAlphas[0] * flCfs[0] ) + ( flAlphas[1] * flCfs[1] ) + ( flAlphas[2] * flCfs[2] ); + } + + if( pNormal ) + { + Vector edgeU = vecVerts[2] - vecVerts[1]; + Vector edgeV = vecVerts[0] - vecVerts[1]; + *pNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( *pNormal ); + } + } + else + { + if ( !bBackup ) + { + DispUVToSurf_TriBLToTR_2( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, true ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::DispUVToSurf_TriBLToTR_2( const Vector &vecIntersectPoint, + int nSnapU, int nNextU, int nSnapV, int nNextV, + Vector &vecPoint, Vector *pNormal, float *pAlpha, + bool bBackup ) +{ + int nWidth = GetWidth(); + + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nNextU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector vecFlatVerts[3], vecVerts[3]; + float flAlphas[3]; + for ( int iVert = 0; iVert < 3; ++iVert ) + { + vecFlatVerts[iVert] = m_pVerts[nIndices[iVert]].m_FlatVert; + vecVerts[iVert] = m_pVerts[nIndices[iVert]].m_Vert; + flAlphas[iVert] = m_pVerts[nIndices[iVert]].m_Alpha; + } + + if ( nSnapU == nNextU ) + { + if ( nSnapV == nNextV ) + { + vecPoint = vecVerts[0]; + *pAlpha = flAlphas[0]; + } + else + { + float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[1] - vecFlatVerts[0] ).Length(); + vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[1] - vecVerts[0] ) ); + + if ( pAlpha ) + { + *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[1] - flAlphas[0] ) ); + } + } + + if( pNormal ) + { + Vector edgeU = vecVerts[0] - vecVerts[2]; + Vector edgeV = vecVerts[1] - vecVerts[2]; + *pNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( *pNormal ); + } + } + else if ( nSnapV == nNextV ) + { + if ( nSnapU == nNextU ) + { + vecPoint = vecVerts[0]; + *pAlpha = flAlphas[0]; + } + else + { + float flFrac = ( vecIntersectPoint - vecFlatVerts[0] ).Length() / ( vecFlatVerts[2] - vecFlatVerts[0] ).Length(); + vecPoint = vecVerts[0] + ( flFrac * ( vecVerts[2] - vecVerts[0] ) ); + + if ( pAlpha ) + { + *pAlpha = flAlphas[0] + ( flFrac * ( flAlphas[2] - flAlphas[0] ) ); + } + } + + if( pNormal ) + { + Vector edgeU = vecVerts[0] - vecVerts[2]; + Vector edgeV = vecVerts[1] - vecVerts[2]; + *pNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( *pNormal ); + } + } + else + { + float flCfs[3]; + if ( CalcBarycentricCooefs( vecFlatVerts[0], vecFlatVerts[1], vecFlatVerts[2], vecIntersectPoint, flCfs[0], flCfs[1], flCfs[2] ) ) + { + vecPoint = ( vecVerts[0] * flCfs[0] ) + ( vecVerts[1] * flCfs[1] ) + ( vecVerts[2] * flCfs[2] ); + + if( pAlpha ) + { + *pAlpha = ( flAlphas[0] * flCfs[0] ) + ( flAlphas[1] * flCfs[1] ) + ( flAlphas[2] * flCfs[2] ); + } + + if( pNormal ) + { + Vector edgeU = vecVerts[0] - vecVerts[2]; + Vector edgeV = vecVerts[1] - vecVerts[2]; + *pNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( *pNormal ); + } + } + else + { + if ( !bBackup ) + { + DispUVToSurf_TriBLToTR_1( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, true ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::DispUVToSurf_TriBLToTR( Vector &vecPoint, Vector *pNormal, float *pAlpha, + float flU, float flV, const Vector &vecIntersectPoint ) +{ + int nWidth = GetWidth(); + int nHeight = GetHeight(); + + int nSnapU = static_cast( flU ); + int nSnapV = static_cast( flV ); + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast( nSnapU ); + float flFracV = flV - static_cast( nSnapV ); + + if( flFracU < flFracV ) + { + DispUVToSurf_TriBLToTR_1( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, false ); + } + else + { + DispUVToSurf_TriBLToTR_2( vecIntersectPoint, nSnapU, nNextU, nSnapV, nNextV, vecPoint, pNormal, pAlpha, false ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::DispUVToSurf( Vector2D const &dispUV, Vector &vecPoint, + Vector *pNormal, float *pAlpha ) +{ + // Check to see that the point is on the surface. + if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) + return; + + // Get the base surface points. + Vector vecIntersectPoint; + CCoreDispSurface *pSurf = GetSurface(); + PointInQuadFromBarycentric( pSurf->GetPoint( 0 ), pSurf->GetPoint( 3 ), pSurf->GetPoint( 2 ), pSurf->GetPoint( 1 ), dispUV, vecIntersectPoint ); + + // Get the displacement power. + int nWidth = GetWidth(); + int nHeight = GetHeight(); + + // Scale the U, V coordinates to the displacement grid size. + float flU = dispUV.x * ( static_cast( nWidth ) - 1.000001f ); + float flV = dispUV.y * ( static_cast( nHeight ) - 1.000001f ); + + // Find the base U, V. + int nSnapU = static_cast( flU ); + int nSnapV = static_cast( flV ); + + // Use this to get the triangle orientation. + bool bOdd = ( ( ( nSnapV * nWidth ) + nSnapU ) % 2 == 1 ); + + // Top Left to Bottom Right + if( bOdd ) + { + DispUVToSurf_TriTLToBR( vecPoint, pNormal, pAlpha, flU, flV, vecIntersectPoint ); + } + // Bottom Left to Top Right + else + { + DispUVToSurf_TriBLToTR( vecPoint, pNormal, pAlpha, flU, flV, vecIntersectPoint ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create bounding boxes around pairs of triangles (in a grid-like) +// fashion; used for culling +//----------------------------------------------------------------------------- +void CCoreDispInfo::CreateBoundingBoxes( CoreDispBBox_t *pBBox, int count ) +{ + // + // Initialize the bounding boxes. + // + int iBox; + for( iBox = 0; iBox < count; ++iBox ) + { + pBBox[iBox].vMin.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + pBBox[iBox].vMax.Init( FLT_MIN, FLT_MIN, FLT_MIN ); + } + + // Get the width and height of the displacement surface. + int nHeight = GetHeight(); + int nWidth = GetWidth(); + + // Find bounding box of every two consecutive triangles + iBox = 0; + int nIndex = 0; + for( int iHgt = 0; iHgt < ( nHeight - 1 ); ++iHgt ) + { + for( int iWid = 0; iWid < ( nWidth - 1 ); ++iWid ) + { + for( int iPoint = 0; iPoint < 4; ++iPoint ) + { + switch( iPoint ) + { + case 0: { nIndex = ( nHeight * iHgt ) + iWid; break; } + case 1: { nIndex = ( nHeight * ( iHgt + 1 ) ) + iWid; break; } + case 2: { nIndex = ( nHeight * ( iHgt + 1 ) ) + ( iWid + 1 ); break; } + case 3: { nIndex = ( nHeight * iHgt ) + ( iWid + 1 ); break; } + default: { break; } + } + + Vector vecPoint; + GetVert( nIndex, vecPoint ); + if( vecPoint[0] < pBBox[iBox].vMin[0] ) { pBBox[iBox].vMin[0] = vecPoint[0]; } + if( vecPoint[1] < pBBox[iBox].vMin[1] ) { pBBox[iBox].vMin[1] = vecPoint[1]; } + if( vecPoint[2] < pBBox[iBox].vMin[2] ) { pBBox[iBox].vMin[2] = vecPoint[2]; } + + if( vecPoint[0] > pBBox[iBox].vMax[0] ) { pBBox[iBox].vMax[0] = vecPoint[0]; } + if( vecPoint[1] > pBBox[iBox].vMax[1] ) { pBBox[iBox].vMax[1] = vecPoint[1]; } + if( vecPoint[2] > pBBox[iBox].vMax[2] ) { pBBox[iBox].vMax[2] = vecPoint[2]; } + } + + iBox++; + } + } + + // Verify. + Assert( iBox == count ); + + // Bloat. + for ( iBox = 0; iBox < count; ++iBox ) + { + for( int iAxis = 0; iAxis < 3; ++iAxis ) + { + pBBox[iBox].vMin[iAxis] -= 1.0f; + pBBox[iBox].vMax[iAxis] += 1.0f; + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline bool PointInDispBBox( CoreDispBBox_t *pBox, const Vector &vecPoint ) +{ + // Check to see if point lies in box + if( ( vecPoint.x < pBox->vMin.x ) || ( vecPoint.x > pBox->vMax.x ) ) + return false; + + if( ( vecPoint.y < pBox->vMin.y ) || ( vecPoint.y > pBox->vMax.y ) ) + return false; + + if( ( vecPoint.z < pBox->vMin.z ) || ( vecPoint.z > pBox->vMax.z ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCoreDispInfo::GetTriangleIndicesForDispBBox( int nIndex, int nTris[2][3] ) +{ + // Test to see whether or not the index is odd. + bool bOdd = ( ( nIndex % 2 ) == 1 ); + + int nWidth = GetWidth(); + + // Tris for TLtoBR + if ( bOdd ) + { + nTris[0][0] = nIndex; + nTris[0][1] = nIndex + nWidth; + nTris[0][2] = nIndex + 1; + + nTris[1][0] = nIndex + 1; + nTris[1][1] = nIndex + nWidth; + nTris[1][2] = nIndex + nWidth + 1; + } + // Tris for BLtoTR + else + { + nTris[0][0] = nIndex; + nTris[0][1] = nIndex + nWidth; + nTris[0][2] = nIndex + nWidth + 1; + + nTris[1][0] = nIndex; + nTris[1][1] = nIndex + nWidth + 1; + nTris[1][2] = nIndex + 1; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CCoreDispInfo::SurfToBaseFacePlane( Vector const &surfPt, Vector &planePt ) +{ + // Create bounding boxes + int nBoxCount = ( GetHeight() - 1 ) * ( GetWidth() - 1 ); + CoreDispBBox_t *pBBox = new CoreDispBBox_t[nBoxCount]; + CreateBoundingBoxes( pBBox, nBoxCount ); + + // Use the boxes as a first-pass culling mechanism. + for( int iBox = 0; iBox < nBoxCount; ++iBox ) + { + // Get the current displacement triangle-pair bounding-box. + CoreDispBBox_t *pBox = &pBBox[iBox]; + if( !pBox ) + continue; + + // Check the point against the current displacement bounding-box. + if ( !PointInDispBBox( pBox, surfPt ) ) + continue; + + // Point lies within the bounding box. + int nIndex = iBox + ( iBox / ( GetWidth() - 1 ) ); + + // Get the triangle coordinates for this box. + int aTris[2][3]; + GetTriangleIndicesForDispBBox( nIndex, aTris ); + + // Barycentrically test the triangles on the displacement surface. + Vector vecPoints[3]; + for ( int iTri = 0; iTri < 2; ++iTri ) + { + for ( int iVert = 0; iVert < 3; ++iVert ) + { + GetVert( aTris[iTri][iVert], vecPoints[iVert] ); + } + + float c[3]; + if ( CalcBarycentricCooefs( vecPoints[0], vecPoints[1], vecPoints[2], surfPt, c[0], c[1], c[2] ) ) + { + Vector vecFlatPoints[3]; + for ( int iVert = 0; iVert < 3; ++iVert ) + { + GetFlatVert( aTris[iTri][iVert], vecFlatPoints[iVert] ); + } + + planePt = ( vecFlatPoints[0] * c[0] ) + ( vecFlatPoints[1] * c[1] ) + ( vecFlatPoints[2] * c[2] ); + + // Delete temporary memory. + delete [] pBBox; + return true; + } + } + } + + // Delete temporary memory + delete [] pBBox; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CCoreDispInfo::GetTriCount( void ) +{ + return ( ( GetHeight() - 1 ) * ( GetWidth() -1 ) * 2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::GetTriIndices( int iTri, unsigned short &v1, unsigned short &v2, unsigned short &v3 ) +{ + // Verify we have the correct data (only build when collision data is built). + if ( !m_pTris || ( iTri < 0 ) || ( iTri >= GetTriCount() ) ) + { + Assert( iTri >= 0 ); + Assert( iTri < GetTriCount() ); + Assert( m_pTris ); + return; + } + + CoreDispTri_t *pTri = &m_pTris[iTri]; + v1 = pTri->m_iIndex[0]; + v2 = pTri->m_iIndex[1]; + v3 = pTri->m_iIndex[2]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::SetTriIndices( int iTri, unsigned short v1, unsigned short v2, unsigned short v3 ) +{ + // Verify we have the correct data (only build when collision data is built). + if ( !m_pTris || ( iTri < 0 ) || ( iTri >= GetTriCount() ) ) + { + Assert( iTri >= 0 ); + Assert( iTri < GetTriCount() ); + Assert( m_pTris ); + return; + } + + CoreDispTri_t *pTri = &m_pTris[iTri]; + pTri->m_iIndex[0] = v1; + pTri->m_iIndex[1] = v2; + pTri->m_iIndex[2] = v3; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::GetTriPos( int iTri, Vector &v1, Vector &v2, Vector &v3 ) +{ + // Verify we have the correct data (only build when collision data is built). + if ( !m_pTris || ( iTri < 0 ) || ( iTri >= GetTriCount() ) ) + { + Assert( iTri >= 0 ); + Assert( iTri < GetTriCount() ); + Assert( m_pTris ); + return; + } + + CoreDispTri_t *pTri = &m_pTris[iTri]; + v1 = m_pVerts[pTri->m_iIndex[0]].m_Vert; + v2 = m_pVerts[pTri->m_iIndex[1]].m_Vert; + v3 = m_pVerts[pTri->m_iIndex[2]].m_Vert; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::InitTris( void ) +{ + // Verify we have the correct data (only build when collision data is built). + if ( !m_pTris ) + { + Assert( m_pTris ); + return; + } + + int nTriCount = GetTriCount(); + for ( int iTri = 0; iTri < nTriCount; ++iTri ) + { + m_pTris[iTri].m_uiTags = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::CreateTris( void ) +{ + // Verify we have the correct data (only build when collision data is built). + if ( !m_pTris ) + { + Assert( m_pTris ); + return; + } + + // Extra sanity check if wanted! + Assert( GetTriCount() == ( m_RenderIndexCount / 3 ) ); + + int nTriCount = GetTriCount(); + for ( int iTri = 0, iRender = 0; iTri < nTriCount; ++iTri, iRender += 3 ) + { + m_pTris[iTri].m_iIndex[0] = m_RenderIndices[iRender]; + m_pTris[iTri].m_iIndex[1] = m_RenderIndices[iRender+1]; + m_pTris[iTri].m_iIndex[2] = m_RenderIndices[iRender+2]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCoreDispInfo::IsTriWalkable( int iTri ) +{ + if ( IsTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_BIT ) ) + { + return IsTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_VAL ); + } + + return IsTriTag( iTri, COREDISPTRI_TAG_WALKABLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCoreDispInfo::IsTriBuildable( int iTri ) +{ + if ( IsTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_BIT ) ) + { + return IsTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_VAL ); + } + + return IsTriTag( iTri, COREDISPTRI_TAG_BUILDABLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCoreDispInfo::Position_Update( int iVert, Vector vecPos ) +{ + Vector vSPos, vFlat; + GetFlatVert( iVert, vFlat ); + GetSubdivPosition( iVert, vSPos ); + + Vector vSeg; + vSeg = vecPos - vFlat; + vSeg -= vSPos; + + // Subtract out the elevation. + float elev = GetElevation(); + if( elev != 0.0 ) + { + Vector vNormal; + GetSurface()->GetNormal( vNormal ); + vNormal *= elev; + + vSeg -= vNormal; + } + + float flDistance = VectorNormalize( vSeg ); + + SetFieldVector( iVert, vSeg ); + SetFieldDistance( iVert, flDistance ); +} diff --git a/public/builddisp.h b/public/builddisp.h index 120fc757..a134146a 100644 --- a/public/builddisp.h +++ b/public/builddisp.h @@ -1,1432 +1,1432 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#ifndef BUILDDISP_H -#define BUILDDISP_H - -#ifdef _WIN32 -#pragma once -#endif - -#include "commonmacros.h" -#include "tier0/dbg.h" -#include "bspfile.h" -#include "mathlib.h" -#include "bumpvects.h" -#include "disp_common.h" -#include "bitvec.h" - -#define DISP_ALPHA_PROP_DELTA 382.5f - -class CCoreDispInfo; - -struct CoreDispBBox_t -{ - Vector vMin, vMax; -}; - -//========================================================================= -// -// Surface Class - interfacing class (fill in with MapFace, dface_t, and -// msurface_t) -// -class CCoreDispSurface -{ -public: - - enum { QUAD_POINT_COUNT = 4 }; - enum { MAX_CORNER_NEIGHBOR_COUNT = 16 }; - - CCoreDispSurface(); - - //========================================================================= - // - // initialization - // - void Init( void ); - - //========================================================================= - // - // parent surface id - index to CMapFace, dface_t, or msurface_t - // - inline void SetHandle( int handle ); - inline int GetHandle( void ); - - //========================================================================= - // - // vertex data - pos, normal, texture, lightmap, alpha, etc... - // - inline void SetPointCount( int count ); - inline int GetPointCount( void ) const; - - inline void SetPoint( int index, Vector const &pt ); - inline void GetPoint( int index, Vector& pt ) const; - inline Vector const& GetPoint( int index ) const; - - inline void SetPointNormal( int index, Vector const &normal ); - inline void GetPointNormal( int index, Vector &normal ); - inline void SetTexCoord( int index, Vector2D const& texCoord ); - inline void GetTexCoord( int index, Vector2D& texCoord ) const; - - inline void SetLuxelCoord( int bumpIndex, int index, Vector2D const& luxelCoord ); - inline void GetLuxelCoord( int bumpIndex, int index, Vector2D& luxelCoord ) const; - inline void SetLuxelCoords( int bumpIndex, Vector2D const coords[4] ); - inline void GetLuxelCoords( int bumpIndex, Vector2D coords[4] ) const; - - inline void SetAlpha( int index, float alpha ); - inline float GetAlpha( int const index ) const; - - //========================================================================= - // - // utils - // - inline void GetNormal( Vector& normal ); - inline void SetFlags( int flag ); - inline int GetFlags( void ); - inline void SetContents( int contents ); - inline int GetContents( void ); - - //========================================================================= - // - // create utils (texture axis not use anymore but here to support older maps) - // - inline void SetSAxis( Vector const &axis ); - inline void GetSAxis( Vector &axis ); - inline void SetTAxis( Vector const &axis ); - inline void GetTAxis( Vector &axis ); - - inline void SetPointStartIndex( int index ); - inline int GetPointStartIndex( void ); - inline void SetPointStart( Vector const &pt ); - inline void GetPointStart( Vector &pt ); - - // Used by the tools to set the neighbor data from the BSP file. - void SetNeighborData( const CDispNeighbor edgeNeighbors[4], const CDispCornerNeighbors cornerNeighbors[4] ); - - void GeneratePointStartIndexFromMappingAxes( Vector const &sAxis, Vector const &tAxis ); - int GenerateSurfPointStartIndex( void ); - int FindSurfPointStartIndex( void ); - void AdjustSurfPointData( void ); - - // Indexed by CORNER_ defines. - CDispCornerNeighbors* GetCornerNeighbors( int iCorner ) { Assert( iCorner >= 0 && iCorner < ARRAYSIZE( m_CornerNeighbors ) ); return &m_CornerNeighbors[iCorner]; } - const CDispCornerNeighbors* GetCornerNeighbors( int iCorner ) const { Assert( iCorner >= 0 && iCorner < ARRAYSIZE( m_CornerNeighbors ) ); return &m_CornerNeighbors[iCorner]; } - - // Indexed by CORNER_ defines. - int GetCornerNeighborCount( int iCorner ) const { return GetCornerNeighbors( iCorner )->m_nNeighbors; } - int GetCornerNeighbor( int iCorner, int iNeighbor ) const { Assert( iNeighbor >= 0 && iNeighbor < GetCornerNeighbors(iCorner)->m_nNeighbors ); return GetCornerNeighbors( iCorner )->m_Neighbors[iNeighbor]; } - - CDispNeighbor* GetEdgeNeighbor( int iEdge ) { Assert( iEdge >= 0 && iEdge < ARRAYSIZE( m_EdgeNeighbors ) ); return &m_EdgeNeighbors[iEdge]; } - const CDispNeighbor* GetEdgeNeighbor( int iEdge ) const { Assert( iEdge >= 0 && iEdge < ARRAYSIZE( m_EdgeNeighbors ) ); return &m_EdgeNeighbors[iEdge]; } - - -protected: - - int m_Index; // parent face (CMapFace, dface_t, msurface_t) index "handle" - - int m_PointCount; // number of points in the face (should be 4!) - Vector m_Points[QUAD_POINT_COUNT]; // points - Vector m_Normals[QUAD_POINT_COUNT]; // normals at points - Vector2D m_TexCoords[QUAD_POINT_COUNT]; // texture coordinates at points - Vector2D m_LuxelCoords[NUM_BUMP_VECTS+1][QUAD_POINT_COUNT]; // lightmap coordinates at points - float m_Alphas[QUAD_POINT_COUNT]; // alpha at points - - // Straight from the BSP file. - CDispNeighbor m_EdgeNeighbors[4]; - CDispCornerNeighbors m_CornerNeighbors[4]; - - int m_Flags; // surface flags - inherited from the "parent" face - int m_Contents; // contents flags - inherited from the "parent" face - - Vector sAxis; // used to generate start disp orientation (old method) - Vector tAxis; // used to generate start disp orientation (old method) - int m_PointStartIndex; // index to the starting point -- for saving starting point - Vector m_PointStart; // starting point used to determine the orientation of the displacement map on the surface -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetHandle( int handle ) -{ - m_Index = handle; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispSurface::GetHandle( void ) -{ - return m_Index; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetPointCount( int count ) -{ - // quad only -- currently! - if( count != 4 ) - return; - m_PointCount = count; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispSurface::GetPointCount( void ) const -{ - return m_PointCount; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetPoint( int index, Vector const &pt ) -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - VectorCopy( pt, m_Points[index] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetPoint( int index, Vector &pt ) const -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - VectorCopy( m_Points[index], pt ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline Vector const& CCoreDispSurface::GetPoint( int index ) const -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - return m_Points[index]; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetPointNormal( int index, Vector const &normal ) -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - VectorCopy( normal, m_Normals[index] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetPointNormal( int index, Vector& normal ) -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - VectorCopy( m_Normals[index], normal ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetTexCoord( int index, Vector2D const& texCoord ) -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - Vector2DCopy( texCoord, m_TexCoords[index] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetTexCoord( int index, Vector2D& texCoord ) const -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - Vector2DCopy( m_TexCoords[index], texCoord ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetLuxelCoord( int bumpIndex, int index, Vector2D const& luxelCoord ) -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - Assert( bumpIndex >= 0 ); - Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); - Vector2DCopy( luxelCoord, m_LuxelCoords[bumpIndex][index] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetLuxelCoord( int bumpIndex, int index, Vector2D& luxelCoord ) const -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - Assert( bumpIndex >= 0 ); - Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); - Vector2DCopy( m_LuxelCoords[bumpIndex][index], luxelCoord ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetLuxelCoords( int bumpIndex, Vector2D const luxelCoords[4] ) -{ - Assert( bumpIndex >= 0 ); - Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); - for( int i=0; i < 4; i++ ) - Vector2DCopy( luxelCoords[i], m_LuxelCoords[bumpIndex][i] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetLuxelCoords( int bumpIndex, Vector2D luxelCoords[4] ) const -{ - Assert( bumpIndex >= 0 ); - Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); - for( int i=0; i < 4; i++ ) - Vector2DCopy( m_LuxelCoords[bumpIndex][i], luxelCoords[i] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetAlpha( int index, float alpha ) -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - m_Alphas[index] = alpha; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline float CCoreDispSurface::GetAlpha( int const index ) const -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - return m_Alphas[index]; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetFlags( int flag ) -{ - m_Flags = flag; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispSurface::GetFlags( void ) -{ - return m_Flags; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetContents( int contents ) -{ - m_Contents = contents; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispSurface::GetContents( void ) -{ - return m_Contents; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetSAxis( Vector const &axis ) -{ - VectorCopy( axis, sAxis ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetSAxis( Vector& axis ) -{ - VectorCopy( sAxis, axis ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetTAxis( Vector const &axis ) -{ - VectorCopy( axis, tAxis ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetTAxis( Vector& axis ) -{ - VectorCopy( tAxis, axis ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetPointStartIndex( int index ) -{ - Assert( index >= 0 ); - Assert( index < QUAD_POINT_COUNT ); - m_PointStartIndex = index; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispSurface::GetPointStartIndex( void ) -{ - return m_PointStartIndex; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::SetPointStart( Vector const& pt ) -{ - VectorCopy( pt, m_PointStart ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetPointStart( Vector& pt ) -{ - VectorCopy( m_PointStart, pt ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispSurface::GetNormal( Vector& normal ) -{ - // - // calculate the displacement surface normal - // - Vector tmp[2]; - VectorSubtract( m_Points[1], m_Points[0], tmp[0] ); - VectorSubtract( m_Points[3], m_Points[0], tmp[1] ); - CrossProduct( tmp[1], tmp[0], normal ); - VectorNormalize( normal ); -} - - -//========================================================================= -// -// Node Class (for displacement quad-tree) -// -class CCoreDispNode -{ -public: - - enum { MAX_NEIGHBOR_NODE_COUNT = 4 }; - enum { MAX_NEIGHBOR_VERT_COUNT = 8 }; - enum { MAX_SURF_AT_NODE_COUNT = 8 }; - - //========================================================================= - // - // Initialization - // - void Init( void ); - - //========================================================================= - // - // - // - inline void SetBoundingBox( Vector const& bMin, Vector const& bMax ); - inline void GetBoundingBox( Vector& bMin, Vector& bMax ); - - inline void SetErrorTerm( float errorTerm ); - inline float GetErrorTerm( void ); - - inline void SetNeighborNodeIndex( int dir, int index ); - inline int GetNeighborNodeIndex( int dir ); - - inline void SetCenterVertIndex( int index ); - inline int GetCenterVertIndex( void ); - inline void SetNeighborVertIndex( int dir, int index ); - inline int GetNeighborVertIndex( int dir ); - - inline void SetTriBoundingBox( int index, Vector const& bMin, Vector const& bMax ); - inline void GetTriBoundingBox( int index, Vector& bMin, Vector& bMax ); - inline void SetTriPlane( int index, Vector const& normal, float dist ); - inline void GetTriPlane( int index, cplane_t *plane ); - - inline void SetRayBoundingBox( int index, Vector const& bMin, Vector const& bMax ); - inline void GetRayBoundingBox( int index, Vector& bMin, Vector& bMax ); - - //========================================================================= - // - // Node Functions (friend functions) - // - friend int GetNodeLevel( int index ); - friend int GetNodeCount( int power ); - friend int GetNodeParent( int index ); - friend int GetNodeChild( int power, int index, int direction ); - friend int GetNodeNeighborNode( int power, int index, int direction, int level ); - friend int GetNodeNeighborNodeFromNeighborSurf( int power, int index, int direction, int level, int neighborOrient ); - friend int GetNodeMinNodeAtLevel( int level ); - - friend void GetDispNodeTriVerts( CCoreDispInfo *pDisp, int nodeIndex, int triIndex, float *v1, float *v2, float *v3 ); - - friend void GetComponentsFromNodeIndex( int index, int *x, int *y ); - friend int GetNodeIndexFromComponents( int x, int y ); - -protected: - - Vector m_BBox[2]; // displacement node bounding box (take into account size of children) - float m_ErrorTerm; // LOD error term (the "precision" of the representation of the surface at this node's level) - int m_VertIndex; // the node's vertex index (center vertex of node) - int m_NeighborVertIndices[MAX_NEIGHBOR_VERT_COUNT]; // all other vertex indices in node (maximally creates 8 trianglar surfaces) - Vector m_SurfBBoxes[MAX_SURF_AT_NODE_COUNT][2]; // surface bounding boxes - old method - cplane_t m_SurfPlanes[MAX_SURF_AT_NODE_COUNT]; // surface plane info - old method - - Vector m_RayBBoxes[4][2]; // bounding boxes for ray traces -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::SetBoundingBox( Vector const& bMin, Vector const& bMax ) -{ - VectorCopy( bMin, m_BBox[0] ); - VectorCopy( bMax, m_BBox[1] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::GetBoundingBox( Vector& bMin, Vector& bMax ) -{ - VectorCopy( m_BBox[0], bMin ); - VectorCopy( m_BBox[1], bMax ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::SetErrorTerm( float errorTerm ) -{ - m_ErrorTerm = errorTerm; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline float CCoreDispNode::GetErrorTerm( void ) -{ - return m_ErrorTerm; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::SetCenterVertIndex( int index ) -{ - m_VertIndex = index; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispNode::GetCenterVertIndex( void ) -{ - return m_VertIndex; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::SetNeighborVertIndex( int dir, int index ) -{ - Assert( dir >= 0 ); - Assert( dir < MAX_NEIGHBOR_VERT_COUNT ); - m_NeighborVertIndices[dir] = index; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispNode::GetNeighborVertIndex( int dir ) -{ - Assert( dir >= 0 ); - Assert( dir < MAX_NEIGHBOR_VERT_COUNT ); - return m_NeighborVertIndices[dir]; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::SetTriBoundingBox( int index, Vector const& bMin, Vector const& bMax ) -{ - Assert( index >= 0 ); - Assert( index < MAX_SURF_AT_NODE_COUNT ); - VectorCopy( bMin, m_SurfBBoxes[index][0] ); - VectorCopy( bMax, m_SurfBBoxes[index][1] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::GetTriBoundingBox( int index, Vector& bMin, Vector& bMax ) -{ - Assert( index >= 0 ); - Assert( index < MAX_SURF_AT_NODE_COUNT ); - VectorCopy( m_SurfBBoxes[index][0], bMin ); - VectorCopy( m_SurfBBoxes[index][1], bMax ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::SetTriPlane( int index, Vector const &normal, float dist ) -{ - Assert( index >= 0 ); - Assert( index < MAX_SURF_AT_NODE_COUNT ); - VectorCopy( normal, m_SurfPlanes[index].normal ); - m_SurfPlanes[index].dist = dist; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::GetTriPlane( int index, cplane_t *plane ) -{ - Assert( index >= 0 ); - Assert( index < MAX_SURF_AT_NODE_COUNT ); - VectorCopy( m_SurfPlanes[index].normal, plane->normal ); - plane->dist = m_SurfPlanes[index].dist; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::SetRayBoundingBox( int index, Vector const &bMin, Vector const &bMax ) -{ - Assert( index >= 0 ); - Assert( index < 4 ); - VectorCopy( bMin, m_RayBBoxes[index][0] ); - VectorCopy( bMax, m_RayBBoxes[index][1] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispNode::GetRayBoundingBox( int index, Vector& bMin, Vector& bMax ) -{ - Assert( index >= 0 ); - Assert( index < 4 ); - VectorCopy( m_RayBBoxes[index][0], bMin ); - VectorCopy( m_RayBBoxes[index][1], bMax ); -} - - -//============================================================================= -// -// CCoreInfoBuilder - the primary data necessay to derive a displacement surface -// used by WorldCraft (CMapFace, CMapDisp), VRAD (dface_t, ddispinto_t), -// and the engine (msurface_t, CDispInfo) -// - -struct CoreDispVert_t -{ - Vector m_FieldVector; // displacement vector field - float m_FieldDistance; // the distances along the displacement vector normal - - Vector m_SubdivNormal; - Vector m_SubdivPos; // used the create curvature of displacements - - // generated displacement surface data - Vector m_Vert; // displacement surface vertices - Vector m_FlatVert; - Vector m_Normal; // displacement surface normals - Vector m_TangentS; // use in calculating the tangent space axes - Vector m_TangentT; // use in calculating the tangent space axes - Vector2D m_TexCoord; // displacement surface texture coordinates - Vector2D m_LuxelCoords[NUM_BUMP_VECTS+1]; // displacement surface lightmap coordinates - - // additional per-vertex data - float m_Alpha; // displacement alpha values (per displacement vertex) -}; - -// New, need to use this at the node level -#define COREDISPTRI_TAG_WALKABLE (1<<0) -#define COREDISPTRI_TAG_FORCE_WALKABLE_BIT (1<<1) -#define COREDISPTRI_TAG_FORCE_WALKABLE_VAL (1<<2) -#define COREDISPTRI_TAG_BUILDABLE (1<<3) -#define COREDISPTRI_TAG_FORCE_BUILDABLE_BIT (1<<4) -#define COREDISPTRI_TAG_FORCE_BUILDABLE_VAL (1<<5) - -struct CoreDispTri_t -{ - unsigned short m_iIndex[3]; // the three indices that make up a triangle - unsigned short m_uiTags; // walkable, buildable, etc. -}; - -class CCoreDispInfo : public CDispUtilsHelper -{ -public: - - // - // tree and displacement surface directions - // - enum { WEST = 0, - NORTH = 1, - EAST = 2, - SOUTH = 3, - SOUTHWEST = 4, - SOUTHEAST = 5, - NORTHWEST = 6, - NORTHEAST = 7 }; - -#if 0 - // - // building parameters - // - enum { BUILD_NORMALS = 0x1, - BUILD_TEXCOORDS = 0x2, - BUILD_LIGHTCOORDS = 0x4, - BUILD_LODTREE = 0x8, - BUILD_COLLISION = 0x10, - BUILD_TANGENTSPACE = 0x20 }; -#endif - - // - // surface info flags - // - enum { SURF_BUMPED = 0x1, - SURF_NOPHYSICS_COLL = 0x2, - SURF_NOHULL_COLL = 0x4, - SURF_NORAY_COLL = 0x8 }; - - enum { MAX_DISP_POWER = MAX_MAP_DISP_POWER }; - enum { MAX_VERT_COUNT = MAX_DISPVERTS }; - enum { MAX_NODE_COUNT = 85 }; - - -// Convert from a CDispUtilsHelper. -public: - - static CCoreDispInfo* FromDispUtils( CDispUtilsHelper *p ) { return (CCoreDispInfo*)p; } - - -// CDispUtilsHelper implementation. -public: - - virtual CDispNeighbor* GetEdgeNeighbor( int index ); - virtual CDispCornerNeighbors* GetCornerNeighbors( int index ); - virtual const CPowerInfo* GetPowerInfo() const; - virtual CDispUtilsHelper* GetDispUtilsByIndex( int index ); - - -public: - - //========================================================================= - // - // Creation/Destruction - // - CCoreDispInfo(); - ~CCoreDispInfo(); - - void InitSurf( int parentIndex, Vector points[4], Vector normals[4], - Vector2D texCoords[4], Vector2D lightCoords[4][4], int contents, int flags, - bool bGenerateSurfPointStart, Vector& startPoint, - bool bHasMappingAxes, Vector& uAxis, Vector& vAxis ); - - void InitDispInfo( int power, int minTess, float smoothingAngle, - float *alphas, Vector *dispVectorField, float *dispDistances ); - - // This just unpacks the contents of the verts into arrays and calls InitDispInfo. - void InitDispInfo( int power, int minTess, float smoothingAngle, const CDispVert *pVerts, const CDispTri *pTris ); - -// bool Create( int creationFlags ); - bool Create( void ); - bool CreateWithoutLOD( void ); - - //========================================================================= - // - // Parameter "Wrappers" - // - CCoreDispSurface* GetSurface() { return &m_Surf; } - const CCoreDispSurface* GetSurface() const { return &m_Surf; } - - inline CCoreDispNode *GetNode( int index ); - - inline void SetPower( int power ); - inline int GetPower( void ) const; - inline int GetPostSpacing( void ); - inline int GetWidth( void ); - inline int GetHeight( void ); - inline int GetSize( void ) const; - - // Use this disp as a CDispUtils. - void SetDispUtilsHelperInfo( CCoreDispInfo **ppListBase, int listSize ); - - void SetNeighborData( const CDispNeighbor edgeNeighbors[4], const CDispCornerNeighbors cornerNeighbors[4] ) { GetSurface()->SetNeighborData( edgeNeighbors, cornerNeighbors ); } - - // Get a corner point. Indexed by the CORNER_ defines. - const CVertIndex& GetCornerPointIndex( int index ) const { return GetPowerInfo()->GetCornerPointIndex( index ); } - const Vector& GetCornerPoint( int index ) const { return GetVert( VertIndexToInt( GetCornerPointIndex( index ) ) ); } - - inline void SetVert( int index, Vector const& vert ); - inline void GetVert( int index, Vector& vert ) const; - - inline const Vector& GetVert( int index ) const; - inline const Vector& GetVert( const CVertIndex &index ) const; - - inline void GetFlatVert( int index, Vector& vert ) const; - inline void SetFlatVert( int index, const Vector &vert ); - - inline void GetNormal( int index, Vector& normal ) const; - inline const Vector& GetNormal( int index ) const; - inline const Vector& GetNormal( const CVertIndex &index ) const; - inline void SetNormal( int index, Vector const& normal ); - inline void SetNormal( const CVertIndex &index, Vector const& normal ); - - inline void GetTangentS( int index, Vector& tangentS ) const; - inline void GetTangentT( int index, Vector& tangentT ) const; - inline void SetTexCoord( int index, Vector2D const& texCoord ); - inline void GetTexCoord( int index, Vector2D& texCoord ) const; - - inline void SetLuxelCoord( int bumpIndex, int index, Vector2D const& luxelCoord ); - inline void GetLuxelCoord( int bumpIndex, int index, Vector2D& luxelCoord ) const; - - inline void SetAlpha( int index, float alpha ); - inline float GetAlpha( int index ); - - int GetTriCount( void ); - void GetTriIndices( int iTri, unsigned short &v1, unsigned short &v2, unsigned short &v3 ); - void SetTriIndices( int iTri, unsigned short v1, unsigned short v2, unsigned short v3 ); - void GetTriPos( int iTri, Vector &v1, Vector &v2, Vector &v3 ); - inline void SetTriTag( int iTri, unsigned short nTag ) { m_pTris[iTri].m_uiTags |= nTag; } - inline void ResetTriTag( int iTri, unsigned short nTag ) { m_pTris[iTri].m_uiTags &= ~nTag; } - inline void ToggleTriTag( int iTri, unsigned short nTag ) { m_pTris[iTri].m_uiTags ^= nTag; } - inline bool IsTriTag( int iTri, unsigned short nTag ) { return ( ( m_pTris[iTri].m_uiTags & nTag ) != 0 ); } - inline unsigned short GetTriTagValue( int iTri ) { return m_pTris[iTri].m_uiTags; } - inline void SetTriTagValue( int iTri, unsigned short nVal ) { m_pTris[iTri].m_uiTags = nVal; } - - bool IsTriWalkable( int iTri ); - bool IsTriBuildable( int iTri ); - - inline void SetElevation( float elevation ); - inline float GetElevation( void ); - - inline void ResetFieldVectors( void ); - inline void SetFieldVector( int index, Vector const &v ); - inline void GetFieldVector( int index, Vector& v ); - inline void ResetFieldDistances( void ); - inline void SetFieldDistance( int index, float dist ); - inline float GetFieldDistance( int index ); - - inline void ResetSubdivPositions( void ); - inline void SetSubdivPosition( int ndx, Vector const &v ); - inline void GetSubdivPosition( int ndx, Vector& v ); - - inline void ResetSubdivNormals( void ); - inline void SetSubdivNormal( int ndx, Vector const &v ); - inline void GetSubdivNormal( int ndx, Vector &v ); - - inline void SetRenderIndexCount( int count ); - inline int GetRenderIndexCount( void ); - inline void SetRenderIndex( int index, int triIndex ); - inline int GetRenderIndex( int index ); - - inline CoreDispVert_t *GetDispVert( int iVert ) { return &m_pVerts[iVert]; } - inline CoreDispVert_t *GetDispVertList(); - inline unsigned short *GetRenderIndexList( void ); - - inline void SetTouched( bool touched ); - inline bool IsTouched( void ); - - void CalcDispSurfCoords( bool bLightMap, int lightmapID ); - void GetPositionOnSurface( float u, float v, Vector &vPos, Vector *pNormal, float *pAlpha ); - - void DispUVToSurf( Vector2D const &dispUV, Vector &vecPoint, Vector *pNormal, float *pAlpha ); - void BaseFacePlaneToDispUV( Vector const &planePt, Vector2D &dispUV ); - bool SurfToBaseFacePlane( Vector const &surfPt, Vector &planePt ); - - const CDispCornerNeighbors* GetCornerNeighbors( int iCorner ) const { return GetSurface()->GetCornerNeighbors( iCorner ); } - const CDispNeighbor* GetEdgeNeighbor( int iEdge ) const { return GetSurface()->GetEdgeNeighbor( iEdge ); } - - void SetListIndex( int nIndex ) { m_nListIndex = nIndex; } - int GetListIndex( void ) { return m_nListIndex; } - - CBitVec& GetAllowedVerts() { return m_AllowedVerts; } - const CBitVec& GetAllowedVerts() const { return m_AllowedVerts; } - void AllowedVerts_Clear( void ) { m_AllowedVerts.SetAll(); } - int AllowedVerts_GetNumDWords() const { return m_AllowedVerts.GetNumDWords(); } - unsigned long AllowedVerts_GetDWord(int i) const { return m_AllowedVerts.GetDWord( i ); } - void AllowedVerts_SetDWord(int i, unsigned long val) { m_AllowedVerts.SetDWord( i, val ); } - - - void Position_Update( int iVert, Vector vecPos ); - - //========================================================================= - // - // friend functions - // - friend void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppCoreDispInfoList, int listSize ); - -private: - // be changed to match the paint normal next pass) - // LOD/collision node data - CCoreDispNode *m_Nodes; // LOD quad-tree nodes - - float m_Elevation; // distance along the subdivision normal (should - - // defines the size of the displacement surface - int m_Power; // "size" of the displacement map - - // base surface data - CCoreDispSurface m_Surf; // surface containing displacement data - // be changed to match the paint normal next pass) - // Vertex data.. - CoreDispVert_t *m_pVerts; - - // Triangle data.. - CoreDispTri_t *m_pTris; - - // render specific data - int m_RenderIndexCount; // number of indices used in rendering - unsigned short *m_RenderIndices; // rendering index list (list of triangles) - int m_RenderCounter; // counter to verify surfaces are renderered/collided with only once per frame - - // utility data - bool m_bTouched; // touched flag - CCoreDispInfo *m_pNext; // used for chaining - - // The list that this disp is in (used for CDispUtils::IHelper implementation). - CCoreDispInfo **m_ppListBase; - int m_ListSize; - - CBitVec m_AllowedVerts; // Built in VBSP. Defines which verts are allowed to exist based on what the neighbors are. - - int m_nListIndex; - - //========================================================================= - // - // Creation Functions - // - - void GenerateDispSurf( void ); - void GenerateDispSurfNormals( void ); - void GenerateDispSurfTangentSpaces( void ); - bool DoesEdgeExist( int indexRow, int indexCol, int direction, int postSpacing ); - void CalcNormalFromEdges( int indexRow, int indexCol, bool bIsEdge[4], Vector& normal ); - void CalcDispSurfAlphas( void ); - void GenerateLODTree( void ); - void CalcVertIndicesAtNodes( int nodeIndex ); - int GetNodeVertIndexFromParentIndex( int level, int parentVertIndex, int direction ); - void CalcNodeInfo( int nodeIndex, int terminationLevel ); - void CalcNeighborVertIndicesAtNode( int nodeIndex, int level ); - void CalcNeighborNodeIndicesAtNode( int nodeIndex, int level ); - void CalcErrorTermAtNode( int nodeIndex, int level ); - float GetMaxErrorFromChildren( int nodeIndex, int level ); - void CalcBoundingBoxAtNode( int nodeIndex ); - void CalcMinMaxBoundingBoxAtNode( int nodeIndex, Vector& bMin, Vector& bMax ); - void CalcTriSurfInfoAtNode( int nodeIndex ); - void CalcTriSurfIndices( int nodeIndex, int indices[8][3] ); - void CalcTriSurfBoundingBoxes( int nodeIndex, int indices[8][3] ); - void CalcRayBoundingBoxes( int nodeIndex, int indices[8][3] ); - void CalcTriSurfPlanes( int nodeIndex, int indices[8][3] ); - void GenerateCollisionData( void ); - void GenerateCollisionSurface( void ); - - void CreateBoundingBoxes( CoreDispBBox_t *pBBox, int count ); - - void DispUVToSurf_TriTLToBR( Vector &vecPoint, Vector *pNormal, float *pAlpha, float flU, float flV, const Vector &vecIntersectPoint ); - void DispUVToSurf_TriBLToTR( Vector &vecPoint, Vector *pNormal, float *pAlpha, float flU, float flV, const Vector &vecIntersectPoint ); - void DispUVToSurf_TriTLToBR_1( const Vector &vecIntersectPoint, int nSnapU, int nNextU, int nSnapV, int nNextV, Vector &vecPoint, Vector *pNormal, float *pAlpha, bool bBackup ); - void DispUVToSurf_TriTLToBR_2( const Vector &vecIntersectPoint, int nSnapU, int nNextU, int nSnapV, int nNextV, Vector &vecPoint, Vector *pNormal, float *pAlpha, bool bBackup ); - void DispUVToSurf_TriBLToTR_1( const Vector &vecIntersectPoint, int nSnapU, int nNextU, int nSnapV, int nNextV, Vector &vecPoint, Vector *pNormal, float *pAlpha, bool bBackup ); - void DispUVToSurf_TriBLToTR_2( const Vector &vecIntersectPoint, int nSnapU, int nNextU, int nSnapV, int nNextV, Vector &vecPoint, Vector *pNormal, float *pAlpha, bool bBackup ); - - void GetTriangleIndicesForDispBBox( int nIndex, int nTris[2][3] ); - - void BuildTriTLtoBR( int ndx ); - void BuildTriBLtoTR( int ndx ); - - void InitTris( void ); - void CreateTris( void ); -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetPower( int power ) -{ - m_Power = power; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispInfo::GetPower( void ) const -{ - return m_Power; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispInfo::GetPostSpacing( void ) -{ - return ( ( 1 << m_Power ) + 1 ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispInfo::GetWidth( void ) -{ - return ( ( 1 << m_Power ) + 1 ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispInfo::GetHeight( void ) -{ - return ( ( 1 << m_Power ) + 1 ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispInfo::GetSize( void ) const -{ - return ( ( ( 1 << m_Power ) + 1 ) * ( ( 1 << m_Power ) + 1 ) ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetVert( int index, Vector const &vert ) -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - VectorCopy( vert, m_pVerts[index].m_Vert ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetVert( int index, Vector& vert ) const -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - VectorCopy( m_pVerts[index].m_Vert, vert ); -} - - -inline const Vector& CCoreDispInfo::GetVert( int index ) const -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - return m_pVerts[index].m_Vert; -} - -inline const Vector& CCoreDispInfo::GetVert( const CVertIndex &index ) const -{ - return GetVert( VertIndexToInt( index ) ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetFlatVert( int index, Vector& vert ) const -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - VectorCopy( m_pVerts[index].m_FlatVert, vert ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetFlatVert( int index, const Vector &vert ) -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - VectorCopy( vert, m_pVerts[index].m_FlatVert ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetNormal( int index, Vector const &normal ) -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - VectorCopy( normal, m_pVerts[index].m_Normal ); -} - - -inline void CCoreDispInfo::SetNormal( const CVertIndex &index, Vector const &normal ) -{ - SetNormal( VertIndexToInt( index ), normal ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetNormal( int index, Vector& normal ) const -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - VectorCopy( m_pVerts[index].m_Normal, normal ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline const Vector& CCoreDispInfo::GetNormal( int index ) const -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - return m_pVerts[index].m_Normal; -} - - -inline const Vector& CCoreDispInfo::GetNormal( const CVertIndex &index ) const -{ - return GetNormal( VertIndexToInt( index ) ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetTangentS( int index, Vector& tangentS ) const -{ - Assert( index >= 0 ); - Assert( index < GetSize() ); - VectorCopy( m_pVerts[index].m_TangentS, tangentS ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetTangentT( int index, Vector& tangentT ) const -{ - Assert( index >= 0 ); - Assert( index < GetSize() ); - VectorCopy( m_pVerts[index].m_TangentT, tangentT ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetTexCoord( int index, Vector2D const& texCoord ) -{ - Assert( index >= 0 ); - Assert( index < GetSize() ); - Vector2DCopy( texCoord, m_pVerts[index].m_TexCoord ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetTexCoord( int index, Vector2D& texCoord ) const -{ - Assert( index >= 0 ); - Assert( index < GetSize() ); - Vector2DCopy( m_pVerts[index].m_TexCoord, texCoord ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetLuxelCoord( int bumpIndex, int index, Vector2D const& luxelCoord ) -{ - Assert( index >= 0 ); - Assert( index < GetSize() ); - Assert( bumpIndex >= 0 ); - Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); - Vector2DCopy( luxelCoord, m_pVerts[index].m_LuxelCoords[bumpIndex] ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetLuxelCoord( int bumpIndex, int index, Vector2D& luxelCoord ) const -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - Assert( bumpIndex >= 0 ); - Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); - Vector2DCopy( m_pVerts[index].m_LuxelCoords[bumpIndex], luxelCoord ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetAlpha( int index, float alpha ) -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - m_pVerts[index].m_Alpha = alpha; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline float CCoreDispInfo::GetAlpha( int index ) -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - return m_pVerts[index].m_Alpha; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetElevation( float elevation ) -{ - m_Elevation = elevation; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline float CCoreDispInfo::GetElevation( void ) -{ - return m_Elevation; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::ResetFieldVectors( void ) -{ -// Vector normal; -// m_Surf.GetNormal( normal ); - - int size = GetSize(); - for( int i = 0; i < size; i++ ) - { - m_pVerts[i].m_FieldVector.Init(); -// m_FieldVectors[i] = normal; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetFieldVector( int index, Vector const &v ) -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - VectorCopy( v, m_pVerts[index].m_FieldVector ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetFieldVector( int index, Vector& v ) -{ - Assert( index >= 0 ); - Assert( index < MAX_VERT_COUNT ); - VectorCopy( m_pVerts[index].m_FieldVector, v ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::ResetSubdivPositions( void ) -{ - int size = GetSize(); - for( int i = 0; i < size; i++ ) - { - m_pVerts[i].m_SubdivPos.Init(); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetSubdivPosition( int ndx, Vector const &v ) -{ - Assert( ndx >= 0 ); - Assert( ndx < MAX_VERT_COUNT ); - m_pVerts[ndx].m_SubdivPos = v; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetSubdivPosition( int ndx, Vector& v ) -{ - Assert( ndx >= 0 ); - Assert( ndx < MAX_VERT_COUNT ); - v = m_pVerts[ndx].m_SubdivPos; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::ResetSubdivNormals( void ) -{ - Vector normal; - m_Surf.GetNormal( normal ); - - int size = GetSize(); - for( int i = 0; i < size; i++ ) - { - m_pVerts[i].m_SubdivNormal = normal; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetSubdivNormal( int ndx, Vector const &v ) -{ - Assert( ndx >= 0 ); - Assert( ndx < MAX_VERT_COUNT ); - m_pVerts[ndx].m_SubdivNormal = v; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::GetSubdivNormal( int ndx, Vector &v ) -{ - Assert( ndx >= 0 ); - Assert( ndx < MAX_VERT_COUNT ); - v = m_pVerts[ndx].m_SubdivNormal; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::ResetFieldDistances( void ) -{ - int size = GetSize(); - for( int i = 0; i < size; i++ ) - { - m_pVerts[i].m_FieldDistance = 0.0f; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetFieldDistance( int index, float dist ) -{ - Assert( index >= 0 ); - Assert( index < GetSize() ); - m_pVerts[index].m_FieldDistance = dist; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline float CCoreDispInfo::GetFieldDistance( int index ) -{ - Assert( index >= 0 ); - Assert( index < GetSize() ); - return m_pVerts[index].m_FieldDistance; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetRenderIndexCount( int count ) -{ - m_RenderIndexCount = count; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispInfo::GetRenderIndexCount( void ) -{ - return m_RenderIndexCount; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetRenderIndex( int index, int triIndex ) -{ - Assert( index >= 0 ); - Assert( index < ( MAX_VERT_COUNT*2*3) ); - m_RenderIndices[index] = triIndex; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CCoreDispInfo::GetRenderIndex( int index ) -{ - Assert( index >= 0 ); - Assert( index < ( MAX_VERT_COUNT*2*3) ); - return m_RenderIndices[index]; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline CoreDispVert_t *CCoreDispInfo::GetDispVertList() -{ - return m_pVerts; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline unsigned short *CCoreDispInfo::GetRenderIndexList( void ) -{ - return &m_RenderIndices[0]; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CCoreDispInfo::SetTouched( bool touched ) -{ - m_bTouched = touched; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline bool CCoreDispInfo::IsTouched( void ) -{ - return m_bTouched; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline CCoreDispNode *CCoreDispInfo::GetNode( int index ) -{ - Assert( index >= 0 ); - Assert( index < MAX_NODE_COUNT ); - return &m_Nodes[index]; -} - -bool CalcBarycentricCooefs( Vector const &v0, Vector const &v1, Vector const &v2, - Vector const &pt, float &c0, float &c1, float &c2 ); - -#endif // BUILDDISP_H +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef BUILDDISP_H +#define BUILDDISP_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "commonmacros.h" +#include "tier0/dbg.h" +#include "bspfile.h" +#include "mathlib.h" +#include "bumpvects.h" +#include "disp_common.h" +#include "bitvec.h" + +#define DISP_ALPHA_PROP_DELTA 382.5f + +class CCoreDispInfo; + +struct CoreDispBBox_t +{ + Vector vMin, vMax; +}; + +//========================================================================= +// +// Surface Class - interfacing class (fill in with MapFace, dface_t, and +// msurface_t) +// +class CCoreDispSurface +{ +public: + + enum { QUAD_POINT_COUNT = 4 }; + enum { MAX_CORNER_NEIGHBOR_COUNT = 16 }; + + CCoreDispSurface(); + + //========================================================================= + // + // initialization + // + void Init( void ); + + //========================================================================= + // + // parent surface id - index to CMapFace, dface_t, or msurface_t + // + inline void SetHandle( int handle ); + inline int GetHandle( void ); + + //========================================================================= + // + // vertex data - pos, normal, texture, lightmap, alpha, etc... + // + inline void SetPointCount( int count ); + inline int GetPointCount( void ) const; + + inline void SetPoint( int index, Vector const &pt ); + inline void GetPoint( int index, Vector& pt ) const; + inline Vector const& GetPoint( int index ) const; + + inline void SetPointNormal( int index, Vector const &normal ); + inline void GetPointNormal( int index, Vector &normal ); + inline void SetTexCoord( int index, Vector2D const& texCoord ); + inline void GetTexCoord( int index, Vector2D& texCoord ) const; + + inline void SetLuxelCoord( int bumpIndex, int index, Vector2D const& luxelCoord ); + inline void GetLuxelCoord( int bumpIndex, int index, Vector2D& luxelCoord ) const; + inline void SetLuxelCoords( int bumpIndex, Vector2D const coords[4] ); + inline void GetLuxelCoords( int bumpIndex, Vector2D coords[4] ) const; + + inline void SetAlpha( int index, float alpha ); + inline float GetAlpha( int const index ) const; + + //========================================================================= + // + // utils + // + inline void GetNormal( Vector& normal ); + inline void SetFlags( int flag ); + inline int GetFlags( void ); + inline void SetContents( int contents ); + inline int GetContents( void ); + + //========================================================================= + // + // create utils (texture axis not use anymore but here to support older maps) + // + inline void SetSAxis( Vector const &axis ); + inline void GetSAxis( Vector &axis ); + inline void SetTAxis( Vector const &axis ); + inline void GetTAxis( Vector &axis ); + + inline void SetPointStartIndex( int index ); + inline int GetPointStartIndex( void ); + inline void SetPointStart( Vector const &pt ); + inline void GetPointStart( Vector &pt ); + + // Used by the tools to set the neighbor data from the BSP file. + void SetNeighborData( const CDispNeighbor edgeNeighbors[4], const CDispCornerNeighbors cornerNeighbors[4] ); + + void GeneratePointStartIndexFromMappingAxes( Vector const &sAxis, Vector const &tAxis ); + int GenerateSurfPointStartIndex( void ); + int FindSurfPointStartIndex( void ); + void AdjustSurfPointData( void ); + + // Indexed by CORNER_ defines. + CDispCornerNeighbors* GetCornerNeighbors( int iCorner ) { Assert( iCorner >= 0 && iCorner < ARRAYSIZE( m_CornerNeighbors ) ); return &m_CornerNeighbors[iCorner]; } + const CDispCornerNeighbors* GetCornerNeighbors( int iCorner ) const { Assert( iCorner >= 0 && iCorner < ARRAYSIZE( m_CornerNeighbors ) ); return &m_CornerNeighbors[iCorner]; } + + // Indexed by CORNER_ defines. + int GetCornerNeighborCount( int iCorner ) const { return GetCornerNeighbors( iCorner )->m_nNeighbors; } + int GetCornerNeighbor( int iCorner, int iNeighbor ) const { Assert( iNeighbor >= 0 && iNeighbor < GetCornerNeighbors(iCorner)->m_nNeighbors ); return GetCornerNeighbors( iCorner )->m_Neighbors[iNeighbor]; } + + CDispNeighbor* GetEdgeNeighbor( int iEdge ) { Assert( iEdge >= 0 && iEdge < ARRAYSIZE( m_EdgeNeighbors ) ); return &m_EdgeNeighbors[iEdge]; } + const CDispNeighbor* GetEdgeNeighbor( int iEdge ) const { Assert( iEdge >= 0 && iEdge < ARRAYSIZE( m_EdgeNeighbors ) ); return &m_EdgeNeighbors[iEdge]; } + + +protected: + + int m_Index; // parent face (CMapFace, dface_t, msurface_t) index "handle" + + int m_PointCount; // number of points in the face (should be 4!) + Vector m_Points[QUAD_POINT_COUNT]; // points + Vector m_Normals[QUAD_POINT_COUNT]; // normals at points + Vector2D m_TexCoords[QUAD_POINT_COUNT]; // texture coordinates at points + Vector2D m_LuxelCoords[NUM_BUMP_VECTS+1][QUAD_POINT_COUNT]; // lightmap coordinates at points + float m_Alphas[QUAD_POINT_COUNT]; // alpha at points + + // Straight from the BSP file. + CDispNeighbor m_EdgeNeighbors[4]; + CDispCornerNeighbors m_CornerNeighbors[4]; + + int m_Flags; // surface flags - inherited from the "parent" face + int m_Contents; // contents flags - inherited from the "parent" face + + Vector sAxis; // used to generate start disp orientation (old method) + Vector tAxis; // used to generate start disp orientation (old method) + int m_PointStartIndex; // index to the starting point -- for saving starting point + Vector m_PointStart; // starting point used to determine the orientation of the displacement map on the surface +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetHandle( int handle ) +{ + m_Index = handle; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispSurface::GetHandle( void ) +{ + return m_Index; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetPointCount( int count ) +{ + // quad only -- currently! + if( count != 4 ) + return; + m_PointCount = count; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispSurface::GetPointCount( void ) const +{ + return m_PointCount; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetPoint( int index, Vector const &pt ) +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + VectorCopy( pt, m_Points[index] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetPoint( int index, Vector &pt ) const +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + VectorCopy( m_Points[index], pt ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline Vector const& CCoreDispSurface::GetPoint( int index ) const +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + return m_Points[index]; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetPointNormal( int index, Vector const &normal ) +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + VectorCopy( normal, m_Normals[index] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetPointNormal( int index, Vector& normal ) +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + VectorCopy( m_Normals[index], normal ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetTexCoord( int index, Vector2D const& texCoord ) +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + Vector2DCopy( texCoord, m_TexCoords[index] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetTexCoord( int index, Vector2D& texCoord ) const +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + Vector2DCopy( m_TexCoords[index], texCoord ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetLuxelCoord( int bumpIndex, int index, Vector2D const& luxelCoord ) +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + Assert( bumpIndex >= 0 ); + Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); + Vector2DCopy( luxelCoord, m_LuxelCoords[bumpIndex][index] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetLuxelCoord( int bumpIndex, int index, Vector2D& luxelCoord ) const +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + Assert( bumpIndex >= 0 ); + Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); + Vector2DCopy( m_LuxelCoords[bumpIndex][index], luxelCoord ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetLuxelCoords( int bumpIndex, Vector2D const luxelCoords[4] ) +{ + Assert( bumpIndex >= 0 ); + Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); + for( int i=0; i < 4; i++ ) + Vector2DCopy( luxelCoords[i], m_LuxelCoords[bumpIndex][i] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetLuxelCoords( int bumpIndex, Vector2D luxelCoords[4] ) const +{ + Assert( bumpIndex >= 0 ); + Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); + for( int i=0; i < 4; i++ ) + Vector2DCopy( m_LuxelCoords[bumpIndex][i], luxelCoords[i] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetAlpha( int index, float alpha ) +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + m_Alphas[index] = alpha; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline float CCoreDispSurface::GetAlpha( int const index ) const +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + return m_Alphas[index]; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetFlags( int flag ) +{ + m_Flags = flag; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispSurface::GetFlags( void ) +{ + return m_Flags; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetContents( int contents ) +{ + m_Contents = contents; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispSurface::GetContents( void ) +{ + return m_Contents; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetSAxis( Vector const &axis ) +{ + VectorCopy( axis, sAxis ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetSAxis( Vector& axis ) +{ + VectorCopy( sAxis, axis ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetTAxis( Vector const &axis ) +{ + VectorCopy( axis, tAxis ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetTAxis( Vector& axis ) +{ + VectorCopy( tAxis, axis ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetPointStartIndex( int index ) +{ + Assert( index >= 0 ); + Assert( index < QUAD_POINT_COUNT ); + m_PointStartIndex = index; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispSurface::GetPointStartIndex( void ) +{ + return m_PointStartIndex; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::SetPointStart( Vector const& pt ) +{ + VectorCopy( pt, m_PointStart ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetPointStart( Vector& pt ) +{ + VectorCopy( m_PointStart, pt ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispSurface::GetNormal( Vector& normal ) +{ + // + // calculate the displacement surface normal + // + Vector tmp[2]; + VectorSubtract( m_Points[1], m_Points[0], tmp[0] ); + VectorSubtract( m_Points[3], m_Points[0], tmp[1] ); + CrossProduct( tmp[1], tmp[0], normal ); + VectorNormalize( normal ); +} + + +//========================================================================= +// +// Node Class (for displacement quad-tree) +// +class CCoreDispNode +{ +public: + + enum { MAX_NEIGHBOR_NODE_COUNT = 4 }; + enum { MAX_NEIGHBOR_VERT_COUNT = 8 }; + enum { MAX_SURF_AT_NODE_COUNT = 8 }; + + //========================================================================= + // + // Initialization + // + void Init( void ); + + //========================================================================= + // + // + // + inline void SetBoundingBox( Vector const& bMin, Vector const& bMax ); + inline void GetBoundingBox( Vector& bMin, Vector& bMax ); + + inline void SetErrorTerm( float errorTerm ); + inline float GetErrorTerm( void ); + + inline void SetNeighborNodeIndex( int dir, int index ); + inline int GetNeighborNodeIndex( int dir ); + + inline void SetCenterVertIndex( int index ); + inline int GetCenterVertIndex( void ); + inline void SetNeighborVertIndex( int dir, int index ); + inline int GetNeighborVertIndex( int dir ); + + inline void SetTriBoundingBox( int index, Vector const& bMin, Vector const& bMax ); + inline void GetTriBoundingBox( int index, Vector& bMin, Vector& bMax ); + inline void SetTriPlane( int index, Vector const& normal, float dist ); + inline void GetTriPlane( int index, cplane_t *plane ); + + inline void SetRayBoundingBox( int index, Vector const& bMin, Vector const& bMax ); + inline void GetRayBoundingBox( int index, Vector& bMin, Vector& bMax ); + + //========================================================================= + // + // Node Functions (friend functions) + // + friend int GetNodeLevel( int index ); + friend int GetNodeCount( int power ); + friend int GetNodeParent( int index ); + friend int GetNodeChild( int power, int index, int direction ); + friend int GetNodeNeighborNode( int power, int index, int direction, int level ); + friend int GetNodeNeighborNodeFromNeighborSurf( int power, int index, int direction, int level, int neighborOrient ); + friend int GetNodeMinNodeAtLevel( int level ); + + friend void GetDispNodeTriVerts( CCoreDispInfo *pDisp, int nodeIndex, int triIndex, float *v1, float *v2, float *v3 ); + + friend void GetComponentsFromNodeIndex( int index, int *x, int *y ); + friend int GetNodeIndexFromComponents( int x, int y ); + +protected: + + Vector m_BBox[2]; // displacement node bounding box (take into account size of children) + float m_ErrorTerm; // LOD error term (the "precision" of the representation of the surface at this node's level) + int m_VertIndex; // the node's vertex index (center vertex of node) + int m_NeighborVertIndices[MAX_NEIGHBOR_VERT_COUNT]; // all other vertex indices in node (maximally creates 8 trianglar surfaces) + Vector m_SurfBBoxes[MAX_SURF_AT_NODE_COUNT][2]; // surface bounding boxes - old method + cplane_t m_SurfPlanes[MAX_SURF_AT_NODE_COUNT]; // surface plane info - old method + + Vector m_RayBBoxes[4][2]; // bounding boxes for ray traces +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::SetBoundingBox( Vector const& bMin, Vector const& bMax ) +{ + VectorCopy( bMin, m_BBox[0] ); + VectorCopy( bMax, m_BBox[1] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::GetBoundingBox( Vector& bMin, Vector& bMax ) +{ + VectorCopy( m_BBox[0], bMin ); + VectorCopy( m_BBox[1], bMax ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::SetErrorTerm( float errorTerm ) +{ + m_ErrorTerm = errorTerm; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline float CCoreDispNode::GetErrorTerm( void ) +{ + return m_ErrorTerm; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::SetCenterVertIndex( int index ) +{ + m_VertIndex = index; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispNode::GetCenterVertIndex( void ) +{ + return m_VertIndex; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::SetNeighborVertIndex( int dir, int index ) +{ + Assert( dir >= 0 ); + Assert( dir < MAX_NEIGHBOR_VERT_COUNT ); + m_NeighborVertIndices[dir] = index; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispNode::GetNeighborVertIndex( int dir ) +{ + Assert( dir >= 0 ); + Assert( dir < MAX_NEIGHBOR_VERT_COUNT ); + return m_NeighborVertIndices[dir]; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::SetTriBoundingBox( int index, Vector const& bMin, Vector const& bMax ) +{ + Assert( index >= 0 ); + Assert( index < MAX_SURF_AT_NODE_COUNT ); + VectorCopy( bMin, m_SurfBBoxes[index][0] ); + VectorCopy( bMax, m_SurfBBoxes[index][1] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::GetTriBoundingBox( int index, Vector& bMin, Vector& bMax ) +{ + Assert( index >= 0 ); + Assert( index < MAX_SURF_AT_NODE_COUNT ); + VectorCopy( m_SurfBBoxes[index][0], bMin ); + VectorCopy( m_SurfBBoxes[index][1], bMax ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::SetTriPlane( int index, Vector const &normal, float dist ) +{ + Assert( index >= 0 ); + Assert( index < MAX_SURF_AT_NODE_COUNT ); + VectorCopy( normal, m_SurfPlanes[index].normal ); + m_SurfPlanes[index].dist = dist; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::GetTriPlane( int index, cplane_t *plane ) +{ + Assert( index >= 0 ); + Assert( index < MAX_SURF_AT_NODE_COUNT ); + VectorCopy( m_SurfPlanes[index].normal, plane->normal ); + plane->dist = m_SurfPlanes[index].dist; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::SetRayBoundingBox( int index, Vector const &bMin, Vector const &bMax ) +{ + Assert( index >= 0 ); + Assert( index < 4 ); + VectorCopy( bMin, m_RayBBoxes[index][0] ); + VectorCopy( bMax, m_RayBBoxes[index][1] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispNode::GetRayBoundingBox( int index, Vector& bMin, Vector& bMax ) +{ + Assert( index >= 0 ); + Assert( index < 4 ); + VectorCopy( m_RayBBoxes[index][0], bMin ); + VectorCopy( m_RayBBoxes[index][1], bMax ); +} + + +//============================================================================= +// +// CCoreInfoBuilder - the primary data necessay to derive a displacement surface +// used by WorldCraft (CMapFace, CMapDisp), VRAD (dface_t, ddispinto_t), +// and the engine (msurface_t, CDispInfo) +// + +struct CoreDispVert_t +{ + Vector m_FieldVector; // displacement vector field + float m_FieldDistance; // the distances along the displacement vector normal + + Vector m_SubdivNormal; + Vector m_SubdivPos; // used the create curvature of displacements + + // generated displacement surface data + Vector m_Vert; // displacement surface vertices + Vector m_FlatVert; + Vector m_Normal; // displacement surface normals + Vector m_TangentS; // use in calculating the tangent space axes + Vector m_TangentT; // use in calculating the tangent space axes + Vector2D m_TexCoord; // displacement surface texture coordinates + Vector2D m_LuxelCoords[NUM_BUMP_VECTS+1]; // displacement surface lightmap coordinates + + // additional per-vertex data + float m_Alpha; // displacement alpha values (per displacement vertex) +}; + +// New, need to use this at the node level +#define COREDISPTRI_TAG_WALKABLE (1<<0) +#define COREDISPTRI_TAG_FORCE_WALKABLE_BIT (1<<1) +#define COREDISPTRI_TAG_FORCE_WALKABLE_VAL (1<<2) +#define COREDISPTRI_TAG_BUILDABLE (1<<3) +#define COREDISPTRI_TAG_FORCE_BUILDABLE_BIT (1<<4) +#define COREDISPTRI_TAG_FORCE_BUILDABLE_VAL (1<<5) + +struct CoreDispTri_t +{ + unsigned short m_iIndex[3]; // the three indices that make up a triangle + unsigned short m_uiTags; // walkable, buildable, etc. +}; + +class CCoreDispInfo : public CDispUtilsHelper +{ +public: + + // + // tree and displacement surface directions + // + enum { WEST = 0, + NORTH = 1, + EAST = 2, + SOUTH = 3, + SOUTHWEST = 4, + SOUTHEAST = 5, + NORTHWEST = 6, + NORTHEAST = 7 }; + +#if 0 + // + // building parameters + // + enum { BUILD_NORMALS = 0x1, + BUILD_TEXCOORDS = 0x2, + BUILD_LIGHTCOORDS = 0x4, + BUILD_LODTREE = 0x8, + BUILD_COLLISION = 0x10, + BUILD_TANGENTSPACE = 0x20 }; +#endif + + // + // surface info flags + // + enum { SURF_BUMPED = 0x1, + SURF_NOPHYSICS_COLL = 0x2, + SURF_NOHULL_COLL = 0x4, + SURF_NORAY_COLL = 0x8 }; + + enum { MAX_DISP_POWER = MAX_MAP_DISP_POWER }; + enum { MAX_VERT_COUNT = MAX_DISPVERTS }; + enum { MAX_NODE_COUNT = 85 }; + + +// Convert from a CDispUtilsHelper. +public: + + static CCoreDispInfo* FromDispUtils( CDispUtilsHelper *p ) { return (CCoreDispInfo*)p; } + + +// CDispUtilsHelper implementation. +public: + + virtual CDispNeighbor* GetEdgeNeighbor( int index ); + virtual CDispCornerNeighbors* GetCornerNeighbors( int index ); + virtual const CPowerInfo* GetPowerInfo() const; + virtual CDispUtilsHelper* GetDispUtilsByIndex( int index ); + + +public: + + //========================================================================= + // + // Creation/Destruction + // + CCoreDispInfo(); + ~CCoreDispInfo(); + + void InitSurf( int parentIndex, Vector points[4], Vector normals[4], + Vector2D texCoords[4], Vector2D lightCoords[4][4], int contents, int flags, + bool bGenerateSurfPointStart, Vector& startPoint, + bool bHasMappingAxes, Vector& uAxis, Vector& vAxis ); + + void InitDispInfo( int power, int minTess, float smoothingAngle, + float *alphas, Vector *dispVectorField, float *dispDistances ); + + // This just unpacks the contents of the verts into arrays and calls InitDispInfo. + void InitDispInfo( int power, int minTess, float smoothingAngle, const CDispVert *pVerts, const CDispTri *pTris ); + +// bool Create( int creationFlags ); + bool Create( void ); + bool CreateWithoutLOD( void ); + + //========================================================================= + // + // Parameter "Wrappers" + // + CCoreDispSurface* GetSurface() { return &m_Surf; } + const CCoreDispSurface* GetSurface() const { return &m_Surf; } + + inline CCoreDispNode *GetNode( int index ); + + inline void SetPower( int power ); + inline int GetPower( void ) const; + inline int GetPostSpacing( void ); + inline int GetWidth( void ); + inline int GetHeight( void ); + inline int GetSize( void ) const; + + // Use this disp as a CDispUtils. + void SetDispUtilsHelperInfo( CCoreDispInfo **ppListBase, int listSize ); + + void SetNeighborData( const CDispNeighbor edgeNeighbors[4], const CDispCornerNeighbors cornerNeighbors[4] ) { GetSurface()->SetNeighborData( edgeNeighbors, cornerNeighbors ); } + + // Get a corner point. Indexed by the CORNER_ defines. + const CVertIndex& GetCornerPointIndex( int index ) const { return GetPowerInfo()->GetCornerPointIndex( index ); } + const Vector& GetCornerPoint( int index ) const { return GetVert( VertIndexToInt( GetCornerPointIndex( index ) ) ); } + + inline void SetVert( int index, Vector const& vert ); + inline void GetVert( int index, Vector& vert ) const; + + inline const Vector& GetVert( int index ) const; + inline const Vector& GetVert( const CVertIndex &index ) const; + + inline void GetFlatVert( int index, Vector& vert ) const; + inline void SetFlatVert( int index, const Vector &vert ); + + inline void GetNormal( int index, Vector& normal ) const; + inline const Vector& GetNormal( int index ) const; + inline const Vector& GetNormal( const CVertIndex &index ) const; + inline void SetNormal( int index, Vector const& normal ); + inline void SetNormal( const CVertIndex &index, Vector const& normal ); + + inline void GetTangentS( int index, Vector& tangentS ) const; + inline void GetTangentT( int index, Vector& tangentT ) const; + inline void SetTexCoord( int index, Vector2D const& texCoord ); + inline void GetTexCoord( int index, Vector2D& texCoord ) const; + + inline void SetLuxelCoord( int bumpIndex, int index, Vector2D const& luxelCoord ); + inline void GetLuxelCoord( int bumpIndex, int index, Vector2D& luxelCoord ) const; + + inline void SetAlpha( int index, float alpha ); + inline float GetAlpha( int index ); + + int GetTriCount( void ); + void GetTriIndices( int iTri, unsigned short &v1, unsigned short &v2, unsigned short &v3 ); + void SetTriIndices( int iTri, unsigned short v1, unsigned short v2, unsigned short v3 ); + void GetTriPos( int iTri, Vector &v1, Vector &v2, Vector &v3 ); + inline void SetTriTag( int iTri, unsigned short nTag ) { m_pTris[iTri].m_uiTags |= nTag; } + inline void ResetTriTag( int iTri, unsigned short nTag ) { m_pTris[iTri].m_uiTags &= ~nTag; } + inline void ToggleTriTag( int iTri, unsigned short nTag ) { m_pTris[iTri].m_uiTags ^= nTag; } + inline bool IsTriTag( int iTri, unsigned short nTag ) { return ( ( m_pTris[iTri].m_uiTags & nTag ) != 0 ); } + inline unsigned short GetTriTagValue( int iTri ) { return m_pTris[iTri].m_uiTags; } + inline void SetTriTagValue( int iTri, unsigned short nVal ) { m_pTris[iTri].m_uiTags = nVal; } + + bool IsTriWalkable( int iTri ); + bool IsTriBuildable( int iTri ); + + inline void SetElevation( float elevation ); + inline float GetElevation( void ); + + inline void ResetFieldVectors( void ); + inline void SetFieldVector( int index, Vector const &v ); + inline void GetFieldVector( int index, Vector& v ); + inline void ResetFieldDistances( void ); + inline void SetFieldDistance( int index, float dist ); + inline float GetFieldDistance( int index ); + + inline void ResetSubdivPositions( void ); + inline void SetSubdivPosition( int ndx, Vector const &v ); + inline void GetSubdivPosition( int ndx, Vector& v ); + + inline void ResetSubdivNormals( void ); + inline void SetSubdivNormal( int ndx, Vector const &v ); + inline void GetSubdivNormal( int ndx, Vector &v ); + + inline void SetRenderIndexCount( int count ); + inline int GetRenderIndexCount( void ); + inline void SetRenderIndex( int index, int triIndex ); + inline int GetRenderIndex( int index ); + + inline CoreDispVert_t *GetDispVert( int iVert ) { return &m_pVerts[iVert]; } + inline CoreDispVert_t *GetDispVertList(); + inline unsigned short *GetRenderIndexList( void ); + + inline void SetTouched( bool touched ); + inline bool IsTouched( void ); + + void CalcDispSurfCoords( bool bLightMap, int lightmapID ); + void GetPositionOnSurface( float u, float v, Vector &vPos, Vector *pNormal, float *pAlpha ); + + void DispUVToSurf( Vector2D const &dispUV, Vector &vecPoint, Vector *pNormal, float *pAlpha ); + void BaseFacePlaneToDispUV( Vector const &planePt, Vector2D &dispUV ); + bool SurfToBaseFacePlane( Vector const &surfPt, Vector &planePt ); + + const CDispCornerNeighbors* GetCornerNeighbors( int iCorner ) const { return GetSurface()->GetCornerNeighbors( iCorner ); } + const CDispNeighbor* GetEdgeNeighbor( int iEdge ) const { return GetSurface()->GetEdgeNeighbor( iEdge ); } + + void SetListIndex( int nIndex ) { m_nListIndex = nIndex; } + int GetListIndex( void ) { return m_nListIndex; } + + CBitVec& GetAllowedVerts() { return m_AllowedVerts; } + const CBitVec& GetAllowedVerts() const { return m_AllowedVerts; } + void AllowedVerts_Clear( void ) { m_AllowedVerts.SetAll(); } + int AllowedVerts_GetNumDWords() const { return m_AllowedVerts.GetNumDWords(); } + unsigned long AllowedVerts_GetDWord(int i) const { return m_AllowedVerts.GetDWord( i ); } + void AllowedVerts_SetDWord(int i, unsigned long val) { m_AllowedVerts.SetDWord( i, val ); } + + + void Position_Update( int iVert, Vector vecPos ); + + //========================================================================= + // + // friend functions + // + friend void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppCoreDispInfoList, int listSize ); + +private: + // be changed to match the paint normal next pass) + // LOD/collision node data + CCoreDispNode *m_Nodes; // LOD quad-tree nodes + + float m_Elevation; // distance along the subdivision normal (should + + // defines the size of the displacement surface + int m_Power; // "size" of the displacement map + + // base surface data + CCoreDispSurface m_Surf; // surface containing displacement data + // be changed to match the paint normal next pass) + // Vertex data.. + CoreDispVert_t *m_pVerts; + + // Triangle data.. + CoreDispTri_t *m_pTris; + + // render specific data + int m_RenderIndexCount; // number of indices used in rendering + unsigned short *m_RenderIndices; // rendering index list (list of triangles) + int m_RenderCounter; // counter to verify surfaces are renderered/collided with only once per frame + + // utility data + bool m_bTouched; // touched flag + CCoreDispInfo *m_pNext; // used for chaining + + // The list that this disp is in (used for CDispUtils::IHelper implementation). + CCoreDispInfo **m_ppListBase; + int m_ListSize; + + CBitVec m_AllowedVerts; // Built in VBSP. Defines which verts are allowed to exist based on what the neighbors are. + + int m_nListIndex; + + //========================================================================= + // + // Creation Functions + // + + void GenerateDispSurf( void ); + void GenerateDispSurfNormals( void ); + void GenerateDispSurfTangentSpaces( void ); + bool DoesEdgeExist( int indexRow, int indexCol, int direction, int postSpacing ); + void CalcNormalFromEdges( int indexRow, int indexCol, bool bIsEdge[4], Vector& normal ); + void CalcDispSurfAlphas( void ); + void GenerateLODTree( void ); + void CalcVertIndicesAtNodes( int nodeIndex ); + int GetNodeVertIndexFromParentIndex( int level, int parentVertIndex, int direction ); + void CalcNodeInfo( int nodeIndex, int terminationLevel ); + void CalcNeighborVertIndicesAtNode( int nodeIndex, int level ); + void CalcNeighborNodeIndicesAtNode( int nodeIndex, int level ); + void CalcErrorTermAtNode( int nodeIndex, int level ); + float GetMaxErrorFromChildren( int nodeIndex, int level ); + void CalcBoundingBoxAtNode( int nodeIndex ); + void CalcMinMaxBoundingBoxAtNode( int nodeIndex, Vector& bMin, Vector& bMax ); + void CalcTriSurfInfoAtNode( int nodeIndex ); + void CalcTriSurfIndices( int nodeIndex, int indices[8][3] ); + void CalcTriSurfBoundingBoxes( int nodeIndex, int indices[8][3] ); + void CalcRayBoundingBoxes( int nodeIndex, int indices[8][3] ); + void CalcTriSurfPlanes( int nodeIndex, int indices[8][3] ); + void GenerateCollisionData( void ); + void GenerateCollisionSurface( void ); + + void CreateBoundingBoxes( CoreDispBBox_t *pBBox, int count ); + + void DispUVToSurf_TriTLToBR( Vector &vecPoint, Vector *pNormal, float *pAlpha, float flU, float flV, const Vector &vecIntersectPoint ); + void DispUVToSurf_TriBLToTR( Vector &vecPoint, Vector *pNormal, float *pAlpha, float flU, float flV, const Vector &vecIntersectPoint ); + void DispUVToSurf_TriTLToBR_1( const Vector &vecIntersectPoint, int nSnapU, int nNextU, int nSnapV, int nNextV, Vector &vecPoint, Vector *pNormal, float *pAlpha, bool bBackup ); + void DispUVToSurf_TriTLToBR_2( const Vector &vecIntersectPoint, int nSnapU, int nNextU, int nSnapV, int nNextV, Vector &vecPoint, Vector *pNormal, float *pAlpha, bool bBackup ); + void DispUVToSurf_TriBLToTR_1( const Vector &vecIntersectPoint, int nSnapU, int nNextU, int nSnapV, int nNextV, Vector &vecPoint, Vector *pNormal, float *pAlpha, bool bBackup ); + void DispUVToSurf_TriBLToTR_2( const Vector &vecIntersectPoint, int nSnapU, int nNextU, int nSnapV, int nNextV, Vector &vecPoint, Vector *pNormal, float *pAlpha, bool bBackup ); + + void GetTriangleIndicesForDispBBox( int nIndex, int nTris[2][3] ); + + void BuildTriTLtoBR( int ndx ); + void BuildTriBLtoTR( int ndx ); + + void InitTris( void ); + void CreateTris( void ); +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetPower( int power ) +{ + m_Power = power; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispInfo::GetPower( void ) const +{ + return m_Power; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispInfo::GetPostSpacing( void ) +{ + return ( ( 1 << m_Power ) + 1 ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispInfo::GetWidth( void ) +{ + return ( ( 1 << m_Power ) + 1 ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispInfo::GetHeight( void ) +{ + return ( ( 1 << m_Power ) + 1 ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispInfo::GetSize( void ) const +{ + return ( ( ( 1 << m_Power ) + 1 ) * ( ( 1 << m_Power ) + 1 ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetVert( int index, Vector const &vert ) +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + VectorCopy( vert, m_pVerts[index].m_Vert ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetVert( int index, Vector& vert ) const +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + VectorCopy( m_pVerts[index].m_Vert, vert ); +} + + +inline const Vector& CCoreDispInfo::GetVert( int index ) const +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + return m_pVerts[index].m_Vert; +} + +inline const Vector& CCoreDispInfo::GetVert( const CVertIndex &index ) const +{ + return GetVert( VertIndexToInt( index ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetFlatVert( int index, Vector& vert ) const +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + VectorCopy( m_pVerts[index].m_FlatVert, vert ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetFlatVert( int index, const Vector &vert ) +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + VectorCopy( vert, m_pVerts[index].m_FlatVert ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetNormal( int index, Vector const &normal ) +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + VectorCopy( normal, m_pVerts[index].m_Normal ); +} + + +inline void CCoreDispInfo::SetNormal( const CVertIndex &index, Vector const &normal ) +{ + SetNormal( VertIndexToInt( index ), normal ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetNormal( int index, Vector& normal ) const +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + VectorCopy( m_pVerts[index].m_Normal, normal ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline const Vector& CCoreDispInfo::GetNormal( int index ) const +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + return m_pVerts[index].m_Normal; +} + + +inline const Vector& CCoreDispInfo::GetNormal( const CVertIndex &index ) const +{ + return GetNormal( VertIndexToInt( index ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetTangentS( int index, Vector& tangentS ) const +{ + Assert( index >= 0 ); + Assert( index < GetSize() ); + VectorCopy( m_pVerts[index].m_TangentS, tangentS ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetTangentT( int index, Vector& tangentT ) const +{ + Assert( index >= 0 ); + Assert( index < GetSize() ); + VectorCopy( m_pVerts[index].m_TangentT, tangentT ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetTexCoord( int index, Vector2D const& texCoord ) +{ + Assert( index >= 0 ); + Assert( index < GetSize() ); + Vector2DCopy( texCoord, m_pVerts[index].m_TexCoord ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetTexCoord( int index, Vector2D& texCoord ) const +{ + Assert( index >= 0 ); + Assert( index < GetSize() ); + Vector2DCopy( m_pVerts[index].m_TexCoord, texCoord ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetLuxelCoord( int bumpIndex, int index, Vector2D const& luxelCoord ) +{ + Assert( index >= 0 ); + Assert( index < GetSize() ); + Assert( bumpIndex >= 0 ); + Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); + Vector2DCopy( luxelCoord, m_pVerts[index].m_LuxelCoords[bumpIndex] ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetLuxelCoord( int bumpIndex, int index, Vector2D& luxelCoord ) const +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + Assert( bumpIndex >= 0 ); + Assert( bumpIndex < NUM_BUMP_VECTS + 1 ); + Vector2DCopy( m_pVerts[index].m_LuxelCoords[bumpIndex], luxelCoord ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetAlpha( int index, float alpha ) +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + m_pVerts[index].m_Alpha = alpha; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline float CCoreDispInfo::GetAlpha( int index ) +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + return m_pVerts[index].m_Alpha; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetElevation( float elevation ) +{ + m_Elevation = elevation; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline float CCoreDispInfo::GetElevation( void ) +{ + return m_Elevation; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::ResetFieldVectors( void ) +{ +// Vector normal; +// m_Surf.GetNormal( normal ); + + int size = GetSize(); + for( int i = 0; i < size; i++ ) + { + m_pVerts[i].m_FieldVector.Init(); +// m_FieldVectors[i] = normal; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetFieldVector( int index, Vector const &v ) +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + VectorCopy( v, m_pVerts[index].m_FieldVector ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetFieldVector( int index, Vector& v ) +{ + Assert( index >= 0 ); + Assert( index < MAX_VERT_COUNT ); + VectorCopy( m_pVerts[index].m_FieldVector, v ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::ResetSubdivPositions( void ) +{ + int size = GetSize(); + for( int i = 0; i < size; i++ ) + { + m_pVerts[i].m_SubdivPos.Init(); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetSubdivPosition( int ndx, Vector const &v ) +{ + Assert( ndx >= 0 ); + Assert( ndx < MAX_VERT_COUNT ); + m_pVerts[ndx].m_SubdivPos = v; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetSubdivPosition( int ndx, Vector& v ) +{ + Assert( ndx >= 0 ); + Assert( ndx < MAX_VERT_COUNT ); + v = m_pVerts[ndx].m_SubdivPos; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::ResetSubdivNormals( void ) +{ + Vector normal; + m_Surf.GetNormal( normal ); + + int size = GetSize(); + for( int i = 0; i < size; i++ ) + { + m_pVerts[i].m_SubdivNormal = normal; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetSubdivNormal( int ndx, Vector const &v ) +{ + Assert( ndx >= 0 ); + Assert( ndx < MAX_VERT_COUNT ); + m_pVerts[ndx].m_SubdivNormal = v; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::GetSubdivNormal( int ndx, Vector &v ) +{ + Assert( ndx >= 0 ); + Assert( ndx < MAX_VERT_COUNT ); + v = m_pVerts[ndx].m_SubdivNormal; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::ResetFieldDistances( void ) +{ + int size = GetSize(); + for( int i = 0; i < size; i++ ) + { + m_pVerts[i].m_FieldDistance = 0.0f; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetFieldDistance( int index, float dist ) +{ + Assert( index >= 0 ); + Assert( index < GetSize() ); + m_pVerts[index].m_FieldDistance = dist; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline float CCoreDispInfo::GetFieldDistance( int index ) +{ + Assert( index >= 0 ); + Assert( index < GetSize() ); + return m_pVerts[index].m_FieldDistance; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetRenderIndexCount( int count ) +{ + m_RenderIndexCount = count; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispInfo::GetRenderIndexCount( void ) +{ + return m_RenderIndexCount; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetRenderIndex( int index, int triIndex ) +{ + Assert( index >= 0 ); + Assert( index < ( MAX_VERT_COUNT*2*3) ); + m_RenderIndices[index] = triIndex; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CCoreDispInfo::GetRenderIndex( int index ) +{ + Assert( index >= 0 ); + Assert( index < ( MAX_VERT_COUNT*2*3) ); + return m_RenderIndices[index]; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline CoreDispVert_t *CCoreDispInfo::GetDispVertList() +{ + return m_pVerts; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline unsigned short *CCoreDispInfo::GetRenderIndexList( void ) +{ + return &m_RenderIndices[0]; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CCoreDispInfo::SetTouched( bool touched ) +{ + m_bTouched = touched; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline bool CCoreDispInfo::IsTouched( void ) +{ + return m_bTouched; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline CCoreDispNode *CCoreDispInfo::GetNode( int index ) +{ + Assert( index >= 0 ); + Assert( index < MAX_NODE_COUNT ); + return &m_Nodes[index]; +} + +bool CalcBarycentricCooefs( Vector const &v0, Vector const &v1, Vector const &v2, + Vector const &pt, float &c0, float &c1, float &c2 ); + +#endif // BUILDDISP_H diff --git a/public/bumpvects.cpp b/public/bumpvects.cpp index d3ad4188..7a2b94a9 100644 --- a/public/bumpvects.cpp +++ b/public/bumpvects.cpp @@ -1,69 +1,69 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) - - -#ifdef QUIVER -#include "r_local.h" -#endif -#include "bumpvects.h" -#include "vector.h" -#include - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// z is coming out of the face. - -void GetBumpNormals( const Vector& sVect, const Vector& tVect, const Vector& flatNormal, - const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ) -{ - Vector tmpNormal; - bool leftHanded; - int i; - - assert( NUM_BUMP_VECTS == 3 ); - - // Are we left or right handed? - CrossProduct( sVect, tVect, tmpNormal ); - if( DotProduct( flatNormal, tmpNormal ) < 0.0f ) - { - leftHanded = true; - } - else - { - leftHanded = false; - } - - // Build a basis for the face around the phong normal - matrix3x4_t smoothBasis; - CrossProduct( phongNormal.Base(), sVect.Base(), smoothBasis[1] ); - VectorNormalize( smoothBasis[1] ); - CrossProduct( smoothBasis[1], phongNormal.Base(), smoothBasis[0] ); - VectorNormalize( smoothBasis[0] ); - VectorCopy( phongNormal.Base(), smoothBasis[2] ); - - if( leftHanded ) - { - VectorNegate( smoothBasis[1] ); - } - - // move the g_localBumpBasis into world space to create bumpNormals - for( i = 0; i < 3; i++ ) - { - VectorIRotate( g_localBumpBasis[i], smoothBasis, bumpNormals[i] ); - } -} - -#endif // !_STATIC_LINKED || _SHARED_LIB \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + + +#ifdef QUIVER +#include "r_local.h" +#endif +#include "bumpvects.h" +#include "vector.h" +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// z is coming out of the face. + +void GetBumpNormals( const Vector& sVect, const Vector& tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ) +{ + Vector tmpNormal; + bool leftHanded; + int i; + + assert( NUM_BUMP_VECTS == 3 ); + + // Are we left or right handed? + CrossProduct( sVect, tVect, tmpNormal ); + if( DotProduct( flatNormal, tmpNormal ) < 0.0f ) + { + leftHanded = true; + } + else + { + leftHanded = false; + } + + // Build a basis for the face around the phong normal + matrix3x4_t smoothBasis; + CrossProduct( phongNormal.Base(), sVect.Base(), smoothBasis[1] ); + VectorNormalize( smoothBasis[1] ); + CrossProduct( smoothBasis[1], phongNormal.Base(), smoothBasis[0] ); + VectorNormalize( smoothBasis[0] ); + VectorCopy( phongNormal.Base(), smoothBasis[2] ); + + if( leftHanded ) + { + VectorNegate( smoothBasis[1] ); + } + + // move the g_localBumpBasis into world space to create bumpNormals + for( i = 0; i < 3; i++ ) + { + VectorIRotate( g_localBumpBasis[i], smoothBasis, bumpNormals[i] ); + } +} + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/public/chunkfile.cpp b/public/chunkfile.cpp index 95c7b657..2d70296a 100644 --- a/public/chunkfile.cpp +++ b/public/chunkfile.cpp @@ -1,989 +1,1001 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Implements an interface for reading and writing heirarchical -// text files of key value pairs. The format of the file is as follows: -// -// chunkname0 -// { -// "key0" "value0" -// "key1" "value1" -// ... -// "keyN" "valueN" -// chunkname1 -// { -// "key0" "value0" -// "key1" "value1" -// ... -// "keyN" "valueN" -// } -// } -// ... -// chunknameN -// { -// "key0" "value0" -// "key1" "value1" -// ... -// "keyN" "valueN" -// } -// -// The chunk names are not necessarily unique, nor are the key names, unless the -// parsing application requires them to be. -// -// $NoKeywords: $ -//=============================================================================// - -#include -#include -#include -#include -#include -#include -#include -#include "ChunkFile.h" -#include "Vector.h" -#include "Vector4D.h" -#include "vstdlib/strtools.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// -// Fixes an infinite loop that occurs when loading certain VMFs. The bug -// occurs with Worldcraft built with DevStudio SP4. -// -#pragma optimize("g", off) - - -//----------------------------------------------------------------------------- -// Purpose: Constructor. -//----------------------------------------------------------------------------- -CChunkHandlerMap::CChunkHandlerMap(void) -{ - m_pHandlers = NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: Destructor. Frees handler list. -//----------------------------------------------------------------------------- -CChunkHandlerMap::~CChunkHandlerMap(void) -{ - ChunkHandlerInfoNode_t *pNode = m_pHandlers; - while (pNode != NULL) - { - ChunkHandlerInfoNode_t *pPrev = pNode; - pNode = pNode->pNext; - - delete pPrev; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Adds a chunk handler to the handler list. -// Input : pszChunkName - Name of chunk to be handled. -// pfnHandler - Address of handler callback function. -// pData - Data to pass to the handler callback. -//----------------------------------------------------------------------------- -void CChunkHandlerMap::AddHandler(const char *pszChunkName, ChunkHandler_t pfnHandler, void *pData) -{ - ChunkHandlerInfoNode_t *pNew = new ChunkHandlerInfoNode_t; - - Q_strncpy(pNew->Handler.szChunkName, pszChunkName, sizeof( pNew->Handler.szChunkName )); - pNew->Handler.pfnHandler = pfnHandler; - pNew->Handler.pData = pData; - pNew->pNext = NULL; - - if (m_pHandlers == NULL) - { - m_pHandlers = pNew; - } - else - { - ChunkHandlerInfoNode_t *pNode = m_pHandlers; - while (pNode->pNext != NULL) - { - pNode = pNode->pNext; - } - pNode->pNext = pNew; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Sets the callback for error handling within this chunk's scope. -// Input : pfnHandler - -// pData - -//----------------------------------------------------------------------------- -void CChunkHandlerMap::SetErrorHandler(ChunkErrorHandler_t pfnHandler, void *pData) -{ - m_pfnErrorHandler = pfnHandler; - m_pErrorData = pData; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : ppData - -// Output : ChunkErrorHandler_t -//----------------------------------------------------------------------------- -ChunkErrorHandler_t CChunkHandlerMap::GetErrorHandler(void **ppData) -{ - *ppData = m_pErrorData; - return(m_pfnErrorHandler); -} - - -//----------------------------------------------------------------------------- -// Purpose: Gets the handler for a given chunk name, if one has been set. -// Input : pszChunkName - Name of chunk. -// ppfnHandler - Receives the address of the callback function. -// ppData - Receives the context data for the given chunk. -// Output : Returns true if a handler was found, false if not. -//----------------------------------------------------------------------------- -ChunkHandler_t CChunkHandlerMap::GetHandler(const char *pszChunkName, void **ppData) -{ - ChunkHandlerInfoNode_t *pNode = m_pHandlers; - while (pNode != NULL) - { - if (!stricmp(pNode->Handler.szChunkName, pszChunkName)) - { - *ppData = pNode->Handler.pData; - return(pNode->Handler.pfnHandler); - } - - pNode = pNode->pNext; - } - - return(false); -} - - -//----------------------------------------------------------------------------- -// Purpose: Constructor. Initializes data members. -//----------------------------------------------------------------------------- -CChunkFile::CChunkFile(void) -{ - m_hFile = NULL; - m_nCurrentDepth = 0; - m_szIndent[0] = '\0'; - m_nHandlerStackDepth = 0; - m_DefaultChunkHandler = 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: Destructor. Closes the file if it is currently open. -//----------------------------------------------------------------------------- -CChunkFile::~CChunkFile(void) -{ - if (m_hFile != NULL) - { - fclose(m_hFile); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pszChunkName - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::BeginChunk(const char *pszChunkName) -{ - // - // Write the chunk name and open curly. - // - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf(szBuf, sizeof( szBuf ), "%s\r\n%s{", pszChunkName, m_szIndent); - ChunkFileResult_t eResult = WriteLine(szBuf); - - // - // Update the indentation depth. - // - if (eResult == ChunkFile_Ok) - { - m_nCurrentDepth++; - BuildIndentString(m_szIndent, m_nCurrentDepth); - } - - return(eResult); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CChunkFile::BuildIndentString(char *pszDest, int nDepth) -{ - if (nDepth >= 0) - { - for (int i = 0; i < nDepth; i++) - { - pszDest[i] = '\t'; - } - - pszDest[nDepth] = '\0'; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::Close(void) -{ - if (m_hFile != NULL) - { - fclose(m_hFile); - m_hFile = NULL; - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::EndChunk(void) -{ - if (m_nCurrentDepth > 0) - { - m_nCurrentDepth--; - BuildIndentString(m_szIndent, m_nCurrentDepth); - } - - WriteLine("}"); - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: Returns a string explaining the last error that occurred. -//----------------------------------------------------------------------------- -const char *CChunkFile::GetErrorText(ChunkFileResult_t eResult) -{ - static char szError[MAX_KEYVALUE_LEN]; - - switch (eResult) - { - case ChunkFile_UnexpectedEOF: - { - Q_strncpy(szError, "unexpected end of file", sizeof( szError ) ); - break; - } - - case ChunkFile_UnexpectedSymbol: - { - Q_snprintf(szError, sizeof( szError ), "unexpected symbol '%s'", m_szErrorToken); - break; - } - - case ChunkFile_OpenFail: - { - Q_snprintf(szError, sizeof( szError ), "%s", strerror(errno)) ; - break; - } - - case ChunkFile_StringTooLong: - { - Q_strncpy(szError, "unterminated string or string too long", sizeof( szError ) ); - break; - } - - default: - { - Q_snprintf(szError, sizeof( szError ), "error %d", eResult); - } - } - - return(m_TokenReader.Error(szError)); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : eError - -//----------------------------------------------------------------------------- -void CChunkFile::HandleError(const char *szChunkName, ChunkFileResult_t eError) -{ - // UNDONE: dispatch errors to the error handler. - // - keep track of current chunkname for reporting errors - // - use the last non-NULL handler that was pushed onto the stack? - // - need a return code to determine whether to abort parsing? -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::HandleChunk(const char *szChunkName) -{ - // See if the default handler wants it? - if( m_DefaultChunkHandler ) - { - ChunkFileResult_t testResult = m_DefaultChunkHandler( this, m_pDefaultChunkHandlerData, szChunkName ); - if( testResult == ChunkFile_Ok ) - { - return ChunkFile_Ok; - } - } - - // - // If there is an active handler map... - // - if (m_nHandlerStackDepth > 0) - { - CChunkHandlerMap *pHandler = m_HandlerStack[m_nHandlerStackDepth - 1]; - - // - // If a chunk handler was found in the handler map... - // - void *pData; - ChunkHandler_t pfnHandler = pHandler->GetHandler(szChunkName, &pData); - if (pfnHandler != NULL) - { - // Dispatch this chunk to the handler. - ChunkFileResult_t eResult = pfnHandler(this, pData); - if (eResult != ChunkFile_Ok) - { - return(eResult); - } - } - else - { - // - // No handler for this chunk. Skip to the matching close curly brace. - // - int nDepth = 1; - ChunkFileResult_t eResult; - - do - { - ChunkType_t eChunkType; - char szKey[MAX_KEYVALUE_LEN]; - char szValue[MAX_KEYVALUE_LEN]; - - while ((eResult = ReadNext(szKey, szValue, sizeof(szValue), eChunkType)) == ChunkFile_Ok) - { - if (eChunkType == ChunkType_Chunk) - { - nDepth++; - } - } - - if (eResult == ChunkFile_EndOfChunk) - { - eResult = ChunkFile_Ok; - nDepth--; - } - else if (eResult == ChunkFile_EOF) - { - return(ChunkFile_UnexpectedEOF); - } - - } while ((nDepth) && (eResult == ChunkFile_Ok)); - } - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: Opens the chunk file for reading or writing. -// Input : pszFileName - Path of file to open. -// eMode - ChunkFile_Read or ChunkFile_Write. -// Output : Returns ChunkFile_Ok on success, ChunkFile_Fail on failure. -// UNDONE: boolean return value? -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::Open(const char *pszFileName, ChunkFileOpenMode_t eMode) -{ - if (eMode == ChunkFile_Read) - { - // UNDONE: TokenReader encapsulates file - unify reading and writing to use the same file I/O. - // UNDONE: Support in-memory parsing. - if (m_TokenReader.Open(pszFileName)) - { - m_nCurrentDepth = 0; - } - else - { - return(ChunkFile_OpenFail); - } - } - else if (eMode == ChunkFile_Write) - { - m_hFile = fopen(pszFileName, "wb"); - - if (m_hFile == NULL) - { - return(ChunkFile_OpenFail); - } - - m_nCurrentDepth = 0; - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: Removes the topmost set of chunk handlers. -//----------------------------------------------------------------------------- -void CChunkFile::PopHandlers(void) -{ - if (m_nHandlerStackDepth > 0) - { - m_nHandlerStackDepth--; - } -} - - -void CChunkFile::SetDefaultChunkHandler( DefaultChunkHandler_t pHandler, void *pData ) -{ - m_DefaultChunkHandler = pHandler; - m_pDefaultChunkHandlerData = pData;} - - -//----------------------------------------------------------------------------- -// Purpose: Adds a set of chunk handlers to the top of the handler stack. -// Input : pHandlerMap - Object containing the list of chunk handlers. -//----------------------------------------------------------------------------- -void CChunkFile::PushHandlers(CChunkHandlerMap *pHandlerMap) -{ - if (m_nHandlerStackDepth < MAX_INDENT_DEPTH) - { - m_HandlerStack[m_nHandlerStackDepth] = pHandlerMap; - m_nHandlerStackDepth++; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Reads the next term from the chunk file. The type of term read is -// returned in the eChunkType parameter. -// Input : szName - Name of key or chunk. -// szValue - If eChunkType is ChunkType_Key, contains the value of the key. -// nValueSize - Size of the buffer pointed to by szValue. -// eChunkType - ChunkType_Key or ChunkType_Chunk. -// Output : Returns ChunkFile_Ok on success, an error code if a parsing error occurs. -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::ReadNext(char *szName, char *szValue, int nValueSize, ChunkType_t &eChunkType) -{ - // HACK: pass in buffer sizes? - trtoken_t eTokenType = m_TokenReader.NextToken(szName, MAX_KEYVALUE_LEN); - - if (eTokenType != TOKENEOF) - { - switch (eTokenType) - { - case IDENT: - case STRING: - { - char szNext[MAX_KEYVALUE_LEN]; - trtoken_t eNextTokenType; - - // - // Read the next token to determine what we have. - // - eNextTokenType = m_TokenReader.NextToken(szNext, sizeof(szNext)); - - switch (eNextTokenType) - { - case OPERATOR: - { - if (!stricmp(szNext, "{")) - { - // Beginning of new chunk. - m_nCurrentDepth++; - eChunkType = ChunkType_Chunk; - szValue[0] = '\0'; - return(ChunkFile_Ok); - } - else - { - // Unexpected symbol. - Q_strncpy(m_szErrorToken, szNext, sizeof( m_szErrorToken ) ); - return(ChunkFile_UnexpectedSymbol); - } - } - - case STRING: - case IDENT: - { - // Key value pair. - Q_strncpy(szValue, szNext, nValueSize ); - eChunkType = ChunkType_Key; - return(ChunkFile_Ok); - } - - case TOKENEOF: - { - // Unexpected end of file. - return(ChunkFile_UnexpectedEOF); - } - - case TOKENSTRINGTOOLONG: - { - // String too long or unterminated string. - return ChunkFile_StringTooLong; - } - } - } - - case OPERATOR: - { - if (!stricmp(szName, "}")) - { - // End of current chunk. - m_nCurrentDepth--; - return(ChunkFile_EndOfChunk); - } - else - { - // Unexpected symbol. - Q_strncpy(m_szErrorToken, szName, sizeof( m_szErrorToken ) ); - return(ChunkFile_UnexpectedSymbol); - } - } - - case TOKENSTRINGTOOLONG: - { - return ChunkFile_StringTooLong; - } - } - } - - if (m_nCurrentDepth != 0) - { - // End of file while within the scope of a chunk. - return(ChunkFile_UnexpectedEOF); - } - - return(ChunkFile_EOF); -} - - -//----------------------------------------------------------------------------- -// Purpose: Reads the current chunk and dispatches keys and sub-chunks to the -// appropriate handler callbacks. -// Input : pfnKeyHandler - Callback for any key values in this chunk. -// pData - Data to pass to the key value callback function. -// Output : Normally returns ChunkFile_Ok or ChunkFile_EOF. Otherwise, returns -// a ChunkFile_xxx error code. -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::ReadChunk(KeyHandler_t pfnKeyHandler, void *pData) -{ - // - // Read the keys and sub-chunks. - // - ChunkFileResult_t eResult; - do - { - char szName[MAX_KEYVALUE_LEN]; - char szValue[MAX_KEYVALUE_LEN]; - ChunkType_t eChunkType; - - eResult = ReadNext(szName, szValue, sizeof(szValue), eChunkType); - - if (eResult == ChunkFile_Ok) - { - if (eChunkType == ChunkType_Chunk) - { - // - // Dispatch sub-chunks to the appropriate handler. - // - eResult = HandleChunk(szName); - } - else if ((eChunkType == ChunkType_Key) && (pfnKeyHandler != NULL)) - { - // - // Dispatch keys to the key value handler. - // - eResult = pfnKeyHandler(szName, szValue, pData); - } - } - } while (eResult == ChunkFile_Ok); - - // - // Cover up ChunkFile_EndOfChunk results because the caller doesn't want to see them. - // - if (eResult == ChunkFile_EndOfChunk) - { - eResult = ChunkFile_Ok; - } - - // - // Dispatch errors to the handler. - // - if ((eResult != ChunkFile_Ok) && (eResult != ChunkFile_EOF)) - { - //HandleError("chunkname", eResult); - } - - return(eResult); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszValue - -// pbBool - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CChunkFile::ReadKeyValueBool(const char *pszValue, bool &bBool) -{ - int nValue = atoi(pszValue); - - if (nValue > 0) - { - bBool = true; - } - else - { - bBool = false; - } - - return(true); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszValue - -// pfFloat - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CChunkFile::ReadKeyValueFloat(const char *pszValue, float &flFloat) -{ - flFloat = (float)atof(pszValue); - return(true); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszValue - -// pnInt - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CChunkFile::ReadKeyValueInt(const char *pszValue, int &nInt) -{ - nInt = atoi(pszValue); - return(true); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszKey - -// r - -// g - -// b - -// Output : -//----------------------------------------------------------------------------- -bool CChunkFile::ReadKeyValueColor(const char *pszValue, unsigned char &chRed, unsigned char &chGreen, unsigned char &chBlue) -{ - if (pszValue != NULL) - { - int r; - int g; - int b; - - if (sscanf(pszValue, "%d %d %d", &r, &g, &b) == 3) - { - chRed = r; - chGreen = g; - chBlue = b; - - return(true); - } - } - - return(false); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszValue - -// pfPoint - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CChunkFile::ReadKeyValuePoint(const char *pszValue, Vector &Point) -{ - if (pszValue != NULL) - { - return(sscanf(pszValue, "(%f %f %f)", &Point.x, &Point.y, &Point.z) == 3); - } - - return(false); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszValue - -// pfVector - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CChunkFile::ReadKeyValueVector2(const char *pszValue, Vector2D &vec) -{ - if (pszValue != NULL) - { - return ( sscanf( pszValue, "[%f %f]", &vec.x, &vec.y) == 2 ); - } - - return(false); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszValue - -// pfVector - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CChunkFile::ReadKeyValueVector3(const char *pszValue, Vector &vec) -{ - if (pszValue != NULL) - { - return(sscanf(pszValue, "[%f %f %f]", &vec.x, &vec.y, &vec.z) == 3); - } - - return(false); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszValue - -// pfVector - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CChunkFile::ReadKeyValueVector4(const char *pszValue, Vector4D &vec) -{ - if( pszValue != NULL ) - { - return(sscanf(pszValue, "[%f %f %f %f]", &vec[0], &vec[1], &vec[2], &vec[3]) == 4); - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszLine - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValue(const char *pszKey, const char *pszValue) -{ - if ((pszKey != NULL) && (pszValue != NULL)) - { - char szTemp[MAX_KEYVALUE_LEN]; - Q_snprintf(szTemp, sizeof( szTemp ), "\"%s\" \"%s\"", pszKey, pszValue); - return(WriteLine(szTemp)); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszKey - -// bValue - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValueBool(const char *pszKey, bool bValue) -{ - if (pszKey != NULL) - { - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"%d\"", pszKey, (int)bValue); - return(WriteLine(szBuf)); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszKey - -// nValue - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValueInt(const char *pszKey, int nValue) -{ - if (pszKey != NULL) - { - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"%d\"", pszKey, nValue); - return(WriteLine(szBuf)); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszKey - -// fValue - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValueFloat(const char *pszKey, float fValue) -{ - if (pszKey != NULL) - { - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"%g\"", pszKey, (double)fValue); - return(WriteLine(szBuf)); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszKey - -// r - -// g - -// b - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValueColor(const char *pszKey, unsigned char r, unsigned char g, unsigned char b) -{ - if (pszKey != NULL) - { - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"%d %d %d\"", pszKey, (int)r, (int)g, (int)b); - return(WriteLine(szBuf)); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszKey - -// fVector - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValuePoint(const char *pszKey, const Vector &Point) -{ - if (pszKey != NULL) - { - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"(%g %g %g)\"", pszKey, (double)Point[0], (double)Point[1], (double)Point[2]); - return(WriteLine(szBuf)); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValueVector2(const char *pszKey, const Vector2D &vec) -{ - if (pszKey != NULL) - { - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf( szBuf, sizeof( szBuf ), "\"%s\" \"[%g %g]\"", pszKey, (double)vec.x, (double)vec.y ); - return(WriteLine(szBuf)); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValueVector3(const char *pszKey, const Vector &vec) -{ - if (pszKey != NULL) - { - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"[%g %g %g]\"", pszKey, (double)vec.x, (double)vec.y, (double)vec.z); - return(WriteLine(szBuf)); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pszKey - -// fVector - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteKeyValueVector4(const char *pszKey, const Vector4D &vec) -{ - if (pszKey != NULL) - { - char szBuf[MAX_KEYVALUE_LEN]; - Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"[%g %g %g %g]\"", pszKey, (double)vec.x, (double)vec.y, (double)vec.z, (double)vec.w); - return( WriteLine( szBuf ) ); - } - - return( ChunkFile_Ok ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pszLine - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CChunkFile::WriteLine(const char *pszLine) -{ - if (pszLine != NULL) - { - // - // Write the indentation string. - // - if (m_nCurrentDepth > 0) - { - int nWritten = fwrite(m_szIndent, 1, m_nCurrentDepth, m_hFile); - if (nWritten != m_nCurrentDepth) - { - return(ChunkFile_Fail); - } - } - - // - // Write the string. - // - int nLen = strlen(pszLine); - int nWritten = fwrite(pszLine, 1, nLen, m_hFile); - if (nWritten != nLen) - { - return(ChunkFile_Fail); - } - - // - // Write the linefeed. - // - if (fwrite("\r\n", 1, 2, m_hFile) != 2) - { - return(ChunkFile_Fail); - } - } - - return(ChunkFile_Ok); -} - +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Implements an interface for reading and writing heirarchical +// text files of key value pairs. The format of the file is as follows: +// +// chunkname0 +// { +// "key0" "value0" +// "key1" "value1" +// ... +// "keyN" "valueN" +// chunkname1 +// { +// "key0" "value0" +// "key1" "value1" +// ... +// "keyN" "valueN" +// } +// } +// ... +// chunknameN +// { +// "key0" "value0" +// "key1" "value1" +// ... +// "keyN" "valueN" +// } +// +// The chunk names are not necessarily unique, nor are the key names, unless the +// parsing application requires them to be. +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include +#ifdef _LINUX +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include "chunkfile.h" +#include "vector.h" +#include "vector4d.h" +#include "vstdlib/strtools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// +// Fixes an infinite loop that occurs when loading certain VMFs. The bug +// occurs with Worldcraft built with DevStudio SP4. +// +#ifdef _MSC_VER +#pragma optimize("g", off) +#endif + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CChunkHandlerMap::CChunkHandlerMap(void) +{ + m_pHandlers = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor. Frees handler list. +//----------------------------------------------------------------------------- +CChunkHandlerMap::~CChunkHandlerMap(void) +{ + ChunkHandlerInfoNode_t *pNode = m_pHandlers; + while (pNode != NULL) + { + ChunkHandlerInfoNode_t *pPrev = pNode; + pNode = pNode->pNext; + + delete pPrev; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds a chunk handler to the handler list. +// Input : pszChunkName - Name of chunk to be handled. +// pfnHandler - Address of handler callback function. +// pData - Data to pass to the handler callback. +//----------------------------------------------------------------------------- +void CChunkHandlerMap::AddHandler(const char *pszChunkName, ChunkHandler_t pfnHandler, void *pData) +{ + ChunkHandlerInfoNode_t *pNew = new ChunkHandlerInfoNode_t; + + Q_strncpy(pNew->Handler.szChunkName, pszChunkName, sizeof( pNew->Handler.szChunkName )); + pNew->Handler.pfnHandler = pfnHandler; + pNew->Handler.pData = pData; + pNew->pNext = NULL; + + if (m_pHandlers == NULL) + { + m_pHandlers = pNew; + } + else + { + ChunkHandlerInfoNode_t *pNode = m_pHandlers; + while (pNode->pNext != NULL) + { + pNode = pNode->pNext; + } + pNode->pNext = pNew; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the callback for error handling within this chunk's scope. +// Input : pfnHandler - +// pData - +//----------------------------------------------------------------------------- +void CChunkHandlerMap::SetErrorHandler(ChunkErrorHandler_t pfnHandler, void *pData) +{ + m_pfnErrorHandler = pfnHandler; + m_pErrorData = pData; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ppData - +// Output : ChunkErrorHandler_t +//----------------------------------------------------------------------------- +ChunkErrorHandler_t CChunkHandlerMap::GetErrorHandler(void **ppData) +{ + *ppData = m_pErrorData; + return(m_pfnErrorHandler); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets the handler for a given chunk name, if one has been set. +// Input : pszChunkName - Name of chunk. +// ppfnHandler - Receives the address of the callback function. +// ppData - Receives the context data for the given chunk. +// Output : Returns true if a handler was found, false if not. +//----------------------------------------------------------------------------- +ChunkHandler_t CChunkHandlerMap::GetHandler(const char *pszChunkName, void **ppData) +{ + ChunkHandlerInfoNode_t *pNode = m_pHandlers; + while (pNode != NULL) + { + if (!stricmp(pNode->Handler.szChunkName, pszChunkName)) + { + *ppData = pNode->Handler.pData; + return(pNode->Handler.pfnHandler); + } + + pNode = pNode->pNext; + } + + return(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor. Initializes data members. +//----------------------------------------------------------------------------- +CChunkFile::CChunkFile(void) +{ + m_hFile = NULL; + m_nCurrentDepth = 0; + m_szIndent[0] = '\0'; + m_nHandlerStackDepth = 0; + m_DefaultChunkHandler = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor. Closes the file if it is currently open. +//----------------------------------------------------------------------------- +CChunkFile::~CChunkFile(void) +{ + if (m_hFile != NULL) + { + fclose(m_hFile); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszChunkName - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::BeginChunk(const char *pszChunkName) +{ + // + // Write the chunk name and open curly. + // + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf(szBuf, sizeof( szBuf ), "%s\r\n%s{", pszChunkName, m_szIndent); + ChunkFileResult_t eResult = WriteLine(szBuf); + + // + // Update the indentation depth. + // + if (eResult == ChunkFile_Ok) + { + m_nCurrentDepth++; + BuildIndentString(m_szIndent, m_nCurrentDepth); + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChunkFile::BuildIndentString(char *pszDest, int nDepth) +{ + if (nDepth >= 0) + { + for (int i = 0; i < nDepth; i++) + { + pszDest[i] = '\t'; + } + + pszDest[nDepth] = '\0'; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::Close(void) +{ + if (m_hFile != NULL) + { + fclose(m_hFile); + m_hFile = NULL; + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::EndChunk(void) +{ + if (m_nCurrentDepth > 0) + { + m_nCurrentDepth--; + BuildIndentString(m_szIndent, m_nCurrentDepth); + } + + WriteLine("}"); + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a string explaining the last error that occurred. +//----------------------------------------------------------------------------- +const char *CChunkFile::GetErrorText(ChunkFileResult_t eResult) +{ + static char szError[MAX_KEYVALUE_LEN]; + + switch (eResult) + { + case ChunkFile_UnexpectedEOF: + { + Q_strncpy(szError, "unexpected end of file", sizeof( szError ) ); + break; + } + + case ChunkFile_UnexpectedSymbol: + { + Q_snprintf(szError, sizeof( szError ), "unexpected symbol '%s'", m_szErrorToken); + break; + } + + case ChunkFile_OpenFail: + { + Q_snprintf(szError, sizeof( szError ), "%s", strerror(errno)) ; + break; + } + + case ChunkFile_StringTooLong: + { + Q_strncpy(szError, "unterminated string or string too long", sizeof( szError ) ); + break; + } + + default: + { + Q_snprintf(szError, sizeof( szError ), "error %d", eResult); + } + } + + return(m_TokenReader.Error(szError)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : eError - +//----------------------------------------------------------------------------- +void CChunkFile::HandleError(const char *szChunkName, ChunkFileResult_t eError) +{ + // UNDONE: dispatch errors to the error handler. + // - keep track of current chunkname for reporting errors + // - use the last non-NULL handler that was pushed onto the stack? + // - need a return code to determine whether to abort parsing? +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::HandleChunk(const char *szChunkName) +{ + // See if the default handler wants it? + if( m_DefaultChunkHandler ) + { + ChunkFileResult_t testResult = m_DefaultChunkHandler( this, m_pDefaultChunkHandlerData, szChunkName ); + if( testResult == ChunkFile_Ok ) + { + return ChunkFile_Ok; + } + } + + // + // If there is an active handler map... + // + if (m_nHandlerStackDepth > 0) + { + CChunkHandlerMap *pHandler = m_HandlerStack[m_nHandlerStackDepth - 1]; + + // + // If a chunk handler was found in the handler map... + // + void *pData; + ChunkHandler_t pfnHandler = pHandler->GetHandler(szChunkName, &pData); + if (pfnHandler != NULL) + { + // Dispatch this chunk to the handler. + ChunkFileResult_t eResult = pfnHandler(this, pData); + if (eResult != ChunkFile_Ok) + { + return(eResult); + } + } + else + { + // + // No handler for this chunk. Skip to the matching close curly brace. + // + int nDepth = 1; + ChunkFileResult_t eResult; + + do + { + ChunkType_t eChunkType; + char szKey[MAX_KEYVALUE_LEN]; + char szValue[MAX_KEYVALUE_LEN]; + + while ((eResult = ReadNext(szKey, szValue, sizeof(szValue), eChunkType)) == ChunkFile_Ok) + { + if (eChunkType == ChunkType_Chunk) + { + nDepth++; + } + } + + if (eResult == ChunkFile_EndOfChunk) + { + eResult = ChunkFile_Ok; + nDepth--; + } + else if (eResult == ChunkFile_EOF) + { + return(ChunkFile_UnexpectedEOF); + } + + } while ((nDepth) && (eResult == ChunkFile_Ok)); + } + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: Opens the chunk file for reading or writing. +// Input : pszFileName - Path of file to open. +// eMode - ChunkFile_Read or ChunkFile_Write. +// Output : Returns ChunkFile_Ok on success, ChunkFile_Fail on failure. +// UNDONE: boolean return value? +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::Open(const char *pszFileName, ChunkFileOpenMode_t eMode) +{ + if (eMode == ChunkFile_Read) + { + // UNDONE: TokenReader encapsulates file - unify reading and writing to use the same file I/O. + // UNDONE: Support in-memory parsing. + if (m_TokenReader.Open(pszFileName)) + { + m_nCurrentDepth = 0; + } + else + { + return(ChunkFile_OpenFail); + } + } + else if (eMode == ChunkFile_Write) + { + m_hFile = fopen(pszFileName, "wb"); + + if (m_hFile == NULL) + { + return(ChunkFile_OpenFail); + } + + m_nCurrentDepth = 0; + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: Removes the topmost set of chunk handlers. +//----------------------------------------------------------------------------- +void CChunkFile::PopHandlers(void) +{ + if (m_nHandlerStackDepth > 0) + { + m_nHandlerStackDepth--; + } +} + + +void CChunkFile::SetDefaultChunkHandler( DefaultChunkHandler_t pHandler, void *pData ) +{ + m_DefaultChunkHandler = pHandler; + m_pDefaultChunkHandlerData = pData;} + + +//----------------------------------------------------------------------------- +// Purpose: Adds a set of chunk handlers to the top of the handler stack. +// Input : pHandlerMap - Object containing the list of chunk handlers. +//----------------------------------------------------------------------------- +void CChunkFile::PushHandlers(CChunkHandlerMap *pHandlerMap) +{ + if (m_nHandlerStackDepth < MAX_INDENT_DEPTH) + { + m_HandlerStack[m_nHandlerStackDepth] = pHandlerMap; + m_nHandlerStackDepth++; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Reads the next term from the chunk file. The type of term read is +// returned in the eChunkType parameter. +// Input : szName - Name of key or chunk. +// szValue - If eChunkType is ChunkType_Key, contains the value of the key. +// nValueSize - Size of the buffer pointed to by szValue. +// eChunkType - ChunkType_Key or ChunkType_Chunk. +// Output : Returns ChunkFile_Ok on success, an error code if a parsing error occurs. +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::ReadNext(char *szName, char *szValue, int nValueSize, ChunkType_t &eChunkType) +{ + // HACK: pass in buffer sizes? + trtoken_t eTokenType = m_TokenReader.NextToken(szName, MAX_KEYVALUE_LEN); + + if (eTokenType != TOKENEOF) + { + switch (eTokenType) + { + case IDENT: + case STRING: + { + char szNext[MAX_KEYVALUE_LEN]; + trtoken_t eNextTokenType; + + // + // Read the next token to determine what we have. + // + eNextTokenType = m_TokenReader.NextToken(szNext, sizeof(szNext)); + + switch (eNextTokenType) + { + case OPERATOR: + { + if (!stricmp(szNext, "{")) + { + // Beginning of new chunk. + m_nCurrentDepth++; + eChunkType = ChunkType_Chunk; + szValue[0] = '\0'; + return(ChunkFile_Ok); + } + else + { + // Unexpected symbol. + Q_strncpy(m_szErrorToken, szNext, sizeof( m_szErrorToken ) ); + return(ChunkFile_UnexpectedSymbol); + } + } + + case STRING: + case IDENT: + { + // Key value pair. + Q_strncpy(szValue, szNext, nValueSize ); + eChunkType = ChunkType_Key; + return(ChunkFile_Ok); + } + + case TOKENEOF: + { + // Unexpected end of file. + return(ChunkFile_UnexpectedEOF); + } + + case TOKENSTRINGTOOLONG: + { + // String too long or unterminated string. + return ChunkFile_StringTooLong; + } + + default: + break; + } + } + + case OPERATOR: + { + if (!stricmp(szName, "}")) + { + // End of current chunk. + m_nCurrentDepth--; + return(ChunkFile_EndOfChunk); + } + else + { + // Unexpected symbol. + Q_strncpy(m_szErrorToken, szName, sizeof( m_szErrorToken ) ); + return(ChunkFile_UnexpectedSymbol); + } + } + + case TOKENSTRINGTOOLONG: + { + return ChunkFile_StringTooLong; + } + + default: + break; + } + } + + if (m_nCurrentDepth != 0) + { + // End of file while within the scope of a chunk. + return(ChunkFile_UnexpectedEOF); + } + + return(ChunkFile_EOF); +} + + +//----------------------------------------------------------------------------- +// Purpose: Reads the current chunk and dispatches keys and sub-chunks to the +// appropriate handler callbacks. +// Input : pfnKeyHandler - Callback for any key values in this chunk. +// pData - Data to pass to the key value callback function. +// Output : Normally returns ChunkFile_Ok or ChunkFile_EOF. Otherwise, returns +// a ChunkFile_xxx error code. +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::ReadChunk(KeyHandler_t pfnKeyHandler, void *pData) +{ + // + // Read the keys and sub-chunks. + // + ChunkFileResult_t eResult; + do + { + char szName[MAX_KEYVALUE_LEN]; + char szValue[MAX_KEYVALUE_LEN]; + ChunkType_t eChunkType; + + eResult = ReadNext(szName, szValue, sizeof(szValue), eChunkType); + + if (eResult == ChunkFile_Ok) + { + if (eChunkType == ChunkType_Chunk) + { + // + // Dispatch sub-chunks to the appropriate handler. + // + eResult = HandleChunk(szName); + } + else if ((eChunkType == ChunkType_Key) && (pfnKeyHandler != NULL)) + { + // + // Dispatch keys to the key value handler. + // + eResult = pfnKeyHandler(szName, szValue, pData); + } + } + } while (eResult == ChunkFile_Ok); + + // + // Cover up ChunkFile_EndOfChunk results because the caller doesn't want to see them. + // + if (eResult == ChunkFile_EndOfChunk) + { + eResult = ChunkFile_Ok; + } + + // + // Dispatch errors to the handler. + // + if ((eResult != ChunkFile_Ok) && (eResult != ChunkFile_EOF)) + { + //HandleError("chunkname", eResult); + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszValue - +// pbBool - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChunkFile::ReadKeyValueBool(const char *pszValue, bool &bBool) +{ + int nValue = atoi(pszValue); + + if (nValue > 0) + { + bBool = true; + } + else + { + bBool = false; + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszValue - +// pfFloat - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChunkFile::ReadKeyValueFloat(const char *pszValue, float &flFloat) +{ + flFloat = (float)atof(pszValue); + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszValue - +// pnInt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChunkFile::ReadKeyValueInt(const char *pszValue, int &nInt) +{ + nInt = atoi(pszValue); + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszKey - +// r - +// g - +// b - +// Output : +//----------------------------------------------------------------------------- +bool CChunkFile::ReadKeyValueColor(const char *pszValue, unsigned char &chRed, unsigned char &chGreen, unsigned char &chBlue) +{ + if (pszValue != NULL) + { + int r; + int g; + int b; + + if (sscanf(pszValue, "%d %d %d", &r, &g, &b) == 3) + { + chRed = r; + chGreen = g; + chBlue = b; + + return(true); + } + } + + return(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszValue - +// pfPoint - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChunkFile::ReadKeyValuePoint(const char *pszValue, Vector &Point) +{ + if (pszValue != NULL) + { + return(sscanf(pszValue, "(%f %f %f)", &Point.x, &Point.y, &Point.z) == 3); + } + + return(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszValue - +// pfVector - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChunkFile::ReadKeyValueVector2(const char *pszValue, Vector2D &vec) +{ + if (pszValue != NULL) + { + return ( sscanf( pszValue, "[%f %f]", &vec.x, &vec.y) == 2 ); + } + + return(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszValue - +// pfVector - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChunkFile::ReadKeyValueVector3(const char *pszValue, Vector &vec) +{ + if (pszValue != NULL) + { + return(sscanf(pszValue, "[%f %f %f]", &vec.x, &vec.y, &vec.z) == 3); + } + + return(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszValue - +// pfVector - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChunkFile::ReadKeyValueVector4(const char *pszValue, Vector4D &vec) +{ + if( pszValue != NULL ) + { + return(sscanf(pszValue, "[%f %f %f %f]", &vec[0], &vec[1], &vec[2], &vec[3]) == 4); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszLine - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValue(const char *pszKey, const char *pszValue) +{ + if ((pszKey != NULL) && (pszValue != NULL)) + { + char szTemp[MAX_KEYVALUE_LEN]; + Q_snprintf(szTemp, sizeof( szTemp ), "\"%s\" \"%s\"", pszKey, pszValue); + return(WriteLine(szTemp)); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszKey - +// bValue - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValueBool(const char *pszKey, bool bValue) +{ + if (pszKey != NULL) + { + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"%d\"", pszKey, (int)bValue); + return(WriteLine(szBuf)); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszKey - +// nValue - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValueInt(const char *pszKey, int nValue) +{ + if (pszKey != NULL) + { + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"%d\"", pszKey, nValue); + return(WriteLine(szBuf)); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszKey - +// fValue - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValueFloat(const char *pszKey, float fValue) +{ + if (pszKey != NULL) + { + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"%g\"", pszKey, (double)fValue); + return(WriteLine(szBuf)); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszKey - +// r - +// g - +// b - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValueColor(const char *pszKey, unsigned char r, unsigned char g, unsigned char b) +{ + if (pszKey != NULL) + { + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"%d %d %d\"", pszKey, (int)r, (int)g, (int)b); + return(WriteLine(szBuf)); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszKey - +// fVector - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValuePoint(const char *pszKey, const Vector &Point) +{ + if (pszKey != NULL) + { + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"(%g %g %g)\"", pszKey, (double)Point[0], (double)Point[1], (double)Point[2]); + return(WriteLine(szBuf)); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValueVector2(const char *pszKey, const Vector2D &vec) +{ + if (pszKey != NULL) + { + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf( szBuf, sizeof( szBuf ), "\"%s\" \"[%g %g]\"", pszKey, (double)vec.x, (double)vec.y ); + return(WriteLine(szBuf)); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValueVector3(const char *pszKey, const Vector &vec) +{ + if (pszKey != NULL) + { + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"[%g %g %g]\"", pszKey, (double)vec.x, (double)vec.y, (double)vec.z); + return(WriteLine(szBuf)); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszKey - +// fVector - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteKeyValueVector4(const char *pszKey, const Vector4D &vec) +{ + if (pszKey != NULL) + { + char szBuf[MAX_KEYVALUE_LEN]; + Q_snprintf(szBuf, sizeof( szBuf ), "\"%s\" \"[%g %g %g %g]\"", pszKey, (double)vec.x, (double)vec.y, (double)vec.z, (double)vec.w); + return( WriteLine( szBuf ) ); + } + + return( ChunkFile_Ok ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszLine - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CChunkFile::WriteLine(const char *pszLine) +{ + if (pszLine != NULL) + { + // + // Write the indentation string. + // + if (m_nCurrentDepth > 0) + { + int nWritten = fwrite(m_szIndent, 1, m_nCurrentDepth, m_hFile); + if (nWritten != m_nCurrentDepth) + { + return(ChunkFile_Fail); + } + } + + // + // Write the string. + // + int nLen = strlen(pszLine); + int nWritten = fwrite(pszLine, 1, nLen, m_hFile); + if (nWritten != nLen) + { + return(ChunkFile_Fail); + } + + // + // Write the linefeed. + // + if (fwrite("\r\n", 1, 2, m_hFile) != 2) + { + return(ChunkFile_Fail); + } + } + + return(ChunkFile_Ok); +} + diff --git a/public/chunkfile.h b/public/chunkfile.h index e62491ea..f18c985f 100644 --- a/public/chunkfile.h +++ b/public/chunkfile.h @@ -1,197 +1,197 @@ -//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// -// -// Purpose: -// -// $NoKeywords: $ -//===========================================================================// - -#ifndef CHUNKFILE_H -#define CHUNKFILE_H - -#ifdef _WIN32 -#pragma once -#endif - -#include -#include "TokenReader.h" - - -#define MAX_INDENT_DEPTH 80 -#define MAX_KEYVALUE_LEN 1024 - - -class CChunkFile; -class Vector2D; -class Vector; -class Vector4D; - - -// -// Modes for Open. -// -enum ChunkFileOpenMode_t -{ - ChunkFile_Read = 0, - ChunkFile_Write, -}; - - -// -// Return codes. -// -enum ChunkFileResult_t -{ - ChunkFile_Ok = 0, - ChunkFile_Fail, - ChunkFile_OpenFail, - ChunkFile_EndOfChunk, - ChunkFile_EOF, - ChunkFile_UnexpectedEOF, - ChunkFile_UnexpectedSymbol, - ChunkFile_OutOfMemory, - ChunkFile_StringTooLong, - ChunkFile_NotHandled -}; - - -enum ChunkType_t -{ - ChunkType_Key = 0, - ChunkType_Chunk, -}; - - -typedef ChunkFileResult_t (*DefaultChunkHandler_t)(CChunkFile *pFile, void *pData, char const *pChunkName); - -typedef ChunkFileResult_t (*ChunkHandler_t)(CChunkFile *pFile, void *pData); -typedef ChunkFileResult_t (*KeyHandler_t)(const char *szKey, const char *szValue, void *pData); -typedef bool (*ChunkErrorHandler_t)(CChunkFile *pFile, const char *szChunkName, void *pData); - - -struct ChunkHandlerInfo_t -{ - char szChunkName[80]; - ChunkHandler_t pfnHandler; - void *pData; -}; - - -struct ChunkHandlerInfoNode_t -{ - ChunkHandlerInfo_t Handler; - struct ChunkHandlerInfoNode_t *pNext; -}; - - -// -// Consider handling chunks with handler objects instead of callbacks. -// -//class CChunkHandler -//{ -// virtual ChunkFileResult_t HandleChunk(const char *szName); -// virtual ChunkFileResult_t HandleKey(const char *szKey, const char *szValue); -// virtual bool HandleError(const char *szChunkName, ChunkFileResult_t eError); -//}; - - -class CChunkHandlerMap -{ - public: - - CChunkHandlerMap(void); - ~CChunkHandlerMap(void); - - void AddHandler(const char *pszChunkName, ChunkHandler_t pfnHandler, void *pData); - ChunkHandler_t GetHandler(const char *pszChunkName, void **pData); - - void SetErrorHandler(ChunkErrorHandler_t pfnHandler, void *pData); - ChunkErrorHandler_t GetErrorHandler(void **pData); - - protected: - - ChunkHandlerInfoNode_t *m_pHandlers; - ChunkErrorHandler_t m_pfnErrorHandler; - void *m_pErrorData; -}; - - -class CChunkFile -{ - public: - - CChunkFile(void); - ~CChunkFile(void); - - ChunkFileResult_t Open(const char *pszFileName, ChunkFileOpenMode_t eMode); - ChunkFileResult_t Close(void); - const char *GetErrorText(ChunkFileResult_t eResult); - - // - // Functions for writing chunk files. - // - ChunkFileResult_t BeginChunk(const char *pszChunkName); - ChunkFileResult_t EndChunk(void); - - ChunkFileResult_t WriteKeyValue(const char *pszKey, const char *pszValue); - ChunkFileResult_t WriteKeyValueBool(const char *pszKey, bool bValue); - ChunkFileResult_t WriteKeyValueColor(const char *pszKey, unsigned char r, unsigned char g, unsigned char b); - ChunkFileResult_t WriteKeyValueFloat(const char *pszKey, float fValue); - ChunkFileResult_t WriteKeyValueInt(const char *pszKey, int nValue); - ChunkFileResult_t WriteKeyValuePoint(const char *pszKey, const Vector &Point); - ChunkFileResult_t WriteKeyValueVector2(const char *pszKey, const Vector2D &vec); - ChunkFileResult_t WriteKeyValueVector3(const char *pszKey, const Vector &vec); - ChunkFileResult_t WriteKeyValueVector4( const char *pszKey, const Vector4D &vec); - - ChunkFileResult_t WriteLine(const char *pszLine); - - // - // Functions for reading chunk files. - // - ChunkFileResult_t ReadChunk(KeyHandler_t pfnKeyHandler = NULL, void *pData = NULL); - ChunkFileResult_t ReadNext(char *szKey, char *szValue, int nValueSize, ChunkType_t &eChunkType); - ChunkFileResult_t HandleChunk(const char *szChunkName); - void HandleError(const char *szChunkName, ChunkFileResult_t eError); - - // These functions should more really be named Parsexxx and possibly moved elsewhere. - static bool ReadKeyValueBool(const char *pszValue, bool &bBool); - static bool ReadKeyValueColor(const char *pszValue, unsigned char &chRed, unsigned char &chGreen, unsigned char &chBlue); - static bool ReadKeyValueInt(const char *pszValue, int &nInt); - static bool ReadKeyValueFloat(const char *pszValue, float &flFloat); - static bool ReadKeyValuePoint(const char *pszValue, Vector &Point); - static bool ReadKeyValueVector2(const char *pszValue, Vector2D &vec); - static bool ReadKeyValueVector3(const char *pszValue, Vector &vec); - static bool ReadKeyValueVector4( const char *pszValue, Vector4D &vec); - - // The default chunk handler gets called before any other chunk handlers. - // - // If the handler returns ChunkFile_Ok, then it goes into the chunk. - // If the handler returns ChunkFile_NotHandled, then the chunk is - // passed to the regular handlers. - // - // If you pass NULL in here, then it disables the default chunk handler. - void SetDefaultChunkHandler( DefaultChunkHandler_t pHandler, void *pData ); - - void PushHandlers(CChunkHandlerMap *pHandlerMap); - void PopHandlers(void); - - protected: - - void BuildIndentString(char *pszDest, int nDepth); - - TokenReader m_TokenReader; - - FILE *m_hFile; - char m_szErrorToken[80]; - char m_szIndent[MAX_INDENT_DEPTH]; - int m_nCurrentDepth; - - // See SetDefaultChunkHandler.. - DefaultChunkHandler_t m_DefaultChunkHandler; - void *m_pDefaultChunkHandlerData; - - CChunkHandlerMap *m_HandlerStack[MAX_INDENT_DEPTH]; - int m_nHandlerStackDepth; -}; - - -#endif // CHUNKFILE_H +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef CHUNKFILE_H +#define CHUNKFILE_H + +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "tokenreader.h" + + +#define MAX_INDENT_DEPTH 80 +#define MAX_KEYVALUE_LEN 1024 + + +class CChunkFile; +class Vector2D; +class Vector; +class Vector4D; + + +// +// Modes for Open. +// +enum ChunkFileOpenMode_t +{ + ChunkFile_Read = 0, + ChunkFile_Write, +}; + + +// +// Return codes. +// +enum ChunkFileResult_t +{ + ChunkFile_Ok = 0, + ChunkFile_Fail, + ChunkFile_OpenFail, + ChunkFile_EndOfChunk, + ChunkFile_EOF, + ChunkFile_UnexpectedEOF, + ChunkFile_UnexpectedSymbol, + ChunkFile_OutOfMemory, + ChunkFile_StringTooLong, + ChunkFile_NotHandled +}; + + +enum ChunkType_t +{ + ChunkType_Key = 0, + ChunkType_Chunk, +}; + + +typedef ChunkFileResult_t (*DefaultChunkHandler_t)(CChunkFile *pFile, void *pData, char const *pChunkName); + +typedef ChunkFileResult_t (*ChunkHandler_t)(CChunkFile *pFile, void *pData); +typedef ChunkFileResult_t (*KeyHandler_t)(const char *szKey, const char *szValue, void *pData); +typedef bool (*ChunkErrorHandler_t)(CChunkFile *pFile, const char *szChunkName, void *pData); + + +struct ChunkHandlerInfo_t +{ + char szChunkName[80]; + ChunkHandler_t pfnHandler; + void *pData; +}; + + +struct ChunkHandlerInfoNode_t +{ + ChunkHandlerInfo_t Handler; + struct ChunkHandlerInfoNode_t *pNext; +}; + + +// +// Consider handling chunks with handler objects instead of callbacks. +// +//class CChunkHandler +//{ +// virtual ChunkFileResult_t HandleChunk(const char *szName); +// virtual ChunkFileResult_t HandleKey(const char *szKey, const char *szValue); +// virtual bool HandleError(const char *szChunkName, ChunkFileResult_t eError); +//}; + + +class CChunkHandlerMap +{ + public: + + CChunkHandlerMap(void); + ~CChunkHandlerMap(void); + + void AddHandler(const char *pszChunkName, ChunkHandler_t pfnHandler, void *pData); + ChunkHandler_t GetHandler(const char *pszChunkName, void **pData); + + void SetErrorHandler(ChunkErrorHandler_t pfnHandler, void *pData); + ChunkErrorHandler_t GetErrorHandler(void **pData); + + protected: + + ChunkHandlerInfoNode_t *m_pHandlers; + ChunkErrorHandler_t m_pfnErrorHandler; + void *m_pErrorData; +}; + + +class CChunkFile +{ + public: + + CChunkFile(void); + ~CChunkFile(void); + + ChunkFileResult_t Open(const char *pszFileName, ChunkFileOpenMode_t eMode); + ChunkFileResult_t Close(void); + const char *GetErrorText(ChunkFileResult_t eResult); + + // + // Functions for writing chunk files. + // + ChunkFileResult_t BeginChunk(const char *pszChunkName); + ChunkFileResult_t EndChunk(void); + + ChunkFileResult_t WriteKeyValue(const char *pszKey, const char *pszValue); + ChunkFileResult_t WriteKeyValueBool(const char *pszKey, bool bValue); + ChunkFileResult_t WriteKeyValueColor(const char *pszKey, unsigned char r, unsigned char g, unsigned char b); + ChunkFileResult_t WriteKeyValueFloat(const char *pszKey, float fValue); + ChunkFileResult_t WriteKeyValueInt(const char *pszKey, int nValue); + ChunkFileResult_t WriteKeyValuePoint(const char *pszKey, const Vector &Point); + ChunkFileResult_t WriteKeyValueVector2(const char *pszKey, const Vector2D &vec); + ChunkFileResult_t WriteKeyValueVector3(const char *pszKey, const Vector &vec); + ChunkFileResult_t WriteKeyValueVector4( const char *pszKey, const Vector4D &vec); + + ChunkFileResult_t WriteLine(const char *pszLine); + + // + // Functions for reading chunk files. + // + ChunkFileResult_t ReadChunk(KeyHandler_t pfnKeyHandler = NULL, void *pData = NULL); + ChunkFileResult_t ReadNext(char *szKey, char *szValue, int nValueSize, ChunkType_t &eChunkType); + ChunkFileResult_t HandleChunk(const char *szChunkName); + void HandleError(const char *szChunkName, ChunkFileResult_t eError); + + // These functions should more really be named Parsexxx and possibly moved elsewhere. + static bool ReadKeyValueBool(const char *pszValue, bool &bBool); + static bool ReadKeyValueColor(const char *pszValue, unsigned char &chRed, unsigned char &chGreen, unsigned char &chBlue); + static bool ReadKeyValueInt(const char *pszValue, int &nInt); + static bool ReadKeyValueFloat(const char *pszValue, float &flFloat); + static bool ReadKeyValuePoint(const char *pszValue, Vector &Point); + static bool ReadKeyValueVector2(const char *pszValue, Vector2D &vec); + static bool ReadKeyValueVector3(const char *pszValue, Vector &vec); + static bool ReadKeyValueVector4( const char *pszValue, Vector4D &vec); + + // The default chunk handler gets called before any other chunk handlers. + // + // If the handler returns ChunkFile_Ok, then it goes into the chunk. + // If the handler returns ChunkFile_NotHandled, then the chunk is + // passed to the regular handlers. + // + // If you pass NULL in here, then it disables the default chunk handler. + void SetDefaultChunkHandler( DefaultChunkHandler_t pHandler, void *pData ); + + void PushHandlers(CChunkHandlerMap *pHandlerMap); + void PopHandlers(void); + + protected: + + void BuildIndentString(char *pszDest, int nDepth); + + TokenReader m_TokenReader; + + FILE *m_hFile; + char m_szErrorToken[80]; + char m_szIndent[MAX_INDENT_DEPTH]; + int m_nCurrentDepth; + + // See SetDefaultChunkHandler.. + DefaultChunkHandler_t m_DefaultChunkHandler; + void *m_pDefaultChunkHandlerData; + + CChunkHandlerMap *m_HandlerStack[MAX_INDENT_DEPTH]; + int m_nHandlerStackDepth; +}; + + +#endif // CHUNKFILE_H diff --git a/public/collisionutils.cpp b/public/collisionutils.cpp index 07097a15..af7590d2 100644 --- a/public/collisionutils.cpp +++ b/public/collisionutils.cpp @@ -1,2665 +1,2665 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Common collision utility methods -// -// $Header: $ -// $NoKeywords: $ -//=============================================================================// - -#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) - -#include "collisionutils.h" -#include "cmodel.h" -#include "mathlib.h" -#include "vector.h" -#include "tier0/dbg.h" -#include -#include "vector4d.h" -#include "trace.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define UNINIT -99999.0 - -//----------------------------------------------------------------------------- -// Clears the trace -//----------------------------------------------------------------------------- -static void Collision_ClearTrace( const Vector &vecRayStart, const Vector &vecRayDelta, CBaseTrace *pTrace ) -{ - pTrace->startpos = vecRayStart; - pTrace->endpos = vecRayStart; - pTrace->endpos += vecRayDelta; - pTrace->startsolid = false; - pTrace->allsolid = false; - pTrace->fraction = 1.0f; - pTrace->contents = 0; -} - - -//----------------------------------------------------------------------------- -// Compute the offset in t along the ray that we'll use for the collision -//----------------------------------------------------------------------------- -static float ComputeBoxOffset( const Ray_t& ray ) -{ - if (ray.m_IsRay) - return 1e-3f; - - // Find the projection of the box diagonal along the ray... - float offset = FloatMakePositive(ray.m_Extents[0] * ray.m_Delta[0]) + - FloatMakePositive(ray.m_Extents[1] * ray.m_Delta[1]) + - FloatMakePositive(ray.m_Extents[2] * ray.m_Delta[2]); - - // We need to divide twice: Once to normalize the computation above - // so we get something in units of extents, and the second to normalize - // that with respect to the entire raycast. - offset *= InvRSquared( ray.m_Delta ); - - // 1e-3 is an epsilon - return offset + 1e-3; -} - - -//----------------------------------------------------------------------------- -// Intersects a swept box against a triangle -//----------------------------------------------------------------------------- -float IntersectRayWithTriangle( const Ray_t& ray, - const Vector& v1, const Vector& v2, const Vector& v3, bool oneSided ) -{ - // This is cute: Use barycentric coordinates to represent the triangle - // Vo(1-u-v) + V1u + V2v and intersect that with a line Po + Dt - // This gives us 3 equations + 3 unknowns, which we can solve with - // Cramer's rule... - // E1x u + E2x v - Dx t = Pox - Vox - // There's a couple of other optimizations, Cramer's rule involves - // computing the determinant of a matrix which has been constructed - // by three vectors. It turns out that - // det | A B C | = -( A x C ) dot B or -(C x B) dot A - // which we'll use below.. - - Vector edge1, edge2, org; - VectorSubtract( v2, v1, edge1 ); - VectorSubtract( v3, v1, edge2 ); - - // Cull out one-sided stuff - if (oneSided) - { - Vector normal; - CrossProduct( edge1, edge2, normal ); - if (DotProduct( normal, ray.m_Delta ) >= 0.0f) - return -1.0f; - } - - // FIXME: This is inaccurate, but fast for boxes - // We want to do a fast separating axis implementation here - // with a swept triangle along the reverse direction of the ray. - - // Compute some intermediary terms - Vector dirCrossEdge2, orgCrossEdge1; - CrossProduct( ray.m_Delta, edge2, dirCrossEdge2 ); - - // Compute the denominator of Cramer's rule: - // | -Dx E1x E2x | - // det | -Dy E1y E2y | = (D x E2) dot E1 - // | -Dz E1z E2z | - float denom = DotProduct( dirCrossEdge2, edge1 ); - if( FloatMakePositive( denom ) < 1e-6 ) - return -1.0f; - denom = 1.0f / denom; - - // Compute u. It's gotta lie in the range of 0 to 1. - // | -Dx orgx E2x | - // u = denom * det | -Dy orgy E2y | = (D x E2) dot org - // | -Dz orgz E2z | - VectorSubtract( ray.m_Start, v1, org ); - float u = DotProduct( dirCrossEdge2, org ) * denom; - if ((u < 0.0f) || (u > 1.0f)) - return -1.0f; - - // Compute t and v the same way... - // In barycentric coords, u + v < 1 - CrossProduct( org, edge1, orgCrossEdge1 ); - float v = DotProduct( orgCrossEdge1, ray.m_Delta ) * denom; - if ((v < 0.0f) || (v + u > 1.0f)) - return -1.0f; - - // Compute the distance along the ray direction that we need to fudge - // when using swept boxes - float boxt = ComputeBoxOffset( ray ); - float t = DotProduct( orgCrossEdge1, edge2 ) * denom; - if ((t < -boxt) || (t > 1.0f + boxt)) - return -1.0f; - - return clamp( t, 0, 1 ); -} - -//----------------------------------------------------------------------------- -// computes the barycentric coordinates of an intersection -//----------------------------------------------------------------------------- - -bool ComputeIntersectionBarycentricCoordinates( const Ray_t& ray, - const Vector& v1, const Vector& v2, const Vector& v3, float& u, float& v, - float *t ) -{ - Vector edge1, edge2, org; - VectorSubtract( v2, v1, edge1 ); - VectorSubtract( v3, v1, edge2 ); - - // Compute some intermediary terms - Vector dirCrossEdge2, orgCrossEdge1; - CrossProduct( ray.m_Delta, edge2, dirCrossEdge2 ); - - // Compute the denominator of Cramer's rule: - // | -Dx E1x E2x | - // det | -Dy E1y E2y | = (D x E2) dot E1 - // | -Dz E1z E2z | - float denom = DotProduct( dirCrossEdge2, edge1 ); - if( FloatMakePositive( denom ) < 1e-6 ) - return false; - denom = 1.0f / denom; - - // Compute u. It's gotta lie in the range of 0 to 1. - // | -Dx orgx E2x | - // u = denom * det | -Dy orgy E2y | = (D x E2) dot org - // | -Dz orgz E2z | - VectorSubtract( ray.m_Start, v1, org ); - u = DotProduct( dirCrossEdge2, org ) * denom; - - // Compute t and v the same way... - // In barycentric coords, u + v < 1 - CrossProduct( org, edge1, orgCrossEdge1 ); - v = DotProduct( orgCrossEdge1, ray.m_Delta ) * denom; - - // Compute the distance along the ray direction that we need to fudge - // when using swept boxes - if( t ) - { - float boxt = ComputeBoxOffset( ray ); - *t = DotProduct( orgCrossEdge1, edge2 ) * denom; - if( ( *t < -boxt ) || ( *t > 1.0f + boxt ) ) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Intersects a plane with a triangle (requires barycentric definition) -//----------------------------------------------------------------------------- - -int IntersectTriangleWithPlaneBarycentric( const Vector& org, const Vector& edgeU, - const Vector& edgeV, const Vector4D& plane, Vector2D* pIntersection ) -{ - // This uses a barycentric method, since we need that to determine - // interpolated points, alphas, and normals - // Given the plane equation P dot N + d = 0 - // and the barycentric coodinate equation P = Org + EdgeU * u + EdgeV * v - // Plug em in. Intersection occurs at u = 0 or v = 0 or u + v = 1 - - float orgDotNormal = DotProduct( org, plane.AsVector3D() ); - float edgeUDotNormal = DotProduct( edgeU, plane.AsVector3D() ); - float edgeVDotNormal = DotProduct( edgeV, plane.AsVector3D() ); - - int ptIdx = 0; - - // u = 0 - if ( edgeVDotNormal != 0.0f ) - { - pIntersection[ptIdx].x = 0.0f; - pIntersection[ptIdx].y = - ( orgDotNormal - plane.w ) / edgeVDotNormal; - if ((pIntersection[ptIdx].y >= 0.0f) && (pIntersection[ptIdx].y <= 1.0f)) - ++ptIdx; - } - - // v = 0 - if ( edgeUDotNormal != 0.0f ) - { - pIntersection[ptIdx].x = - ( orgDotNormal - plane.w ) / edgeUDotNormal; - pIntersection[ptIdx].y = 0.0f; - if ((pIntersection[ptIdx].x >= 0.0f) && (pIntersection[ptIdx].x <= 1.0f)) - ++ptIdx; - } - - // u + v = 1 - if (ptIdx == 2) - return ptIdx; - - if ( edgeVDotNormal != edgeUDotNormal ) - { - pIntersection[ptIdx].x = - ( orgDotNormal - plane.w + edgeVDotNormal) / - ( edgeUDotNormal - edgeVDotNormal); - pIntersection[ptIdx].y = 1.0f - pIntersection[ptIdx].x;; - if ((pIntersection[ptIdx].x >= 0.0f) && (pIntersection[ptIdx].x <= 1.0f) && - (pIntersection[ptIdx].y >= 0.0f) && (pIntersection[ptIdx].y <= 1.0f)) - ++ptIdx; - } - - Assert( ptIdx < 3 ); - return ptIdx; -} - - -//----------------------------------------------------------------------------- -// Returns true if a box intersects with a sphere -//----------------------------------------------------------------------------- -bool IsSphereIntersectingSphere( const Vector& center1, float radius1, - const Vector& center2, float radius2 ) -{ - Vector delta; - VectorSubtract( center2, center1, delta ); - float distSq = delta.LengthSqr(); - float radiusSum = radius1 + radius2; - return (distSq <= (radiusSum * radiusSum)); -} - - -//----------------------------------------------------------------------------- -// Returns true if a box intersects with a sphere -//----------------------------------------------------------------------------- -bool IsBoxIntersectingSphere( const Vector& boxMin, const Vector& boxMax, - const Vector& center, float radius ) -{ - // See Graphics Gems, box-sphere intersection - float dmin = 0.0f; - float flDelta; - - // Unrolled the loop.. this is a big cycle stealer... - if (center[0] < boxMin[0]) - { - flDelta = center[0] - boxMin[0]; - dmin += flDelta * flDelta; - } - else if (center[0] > boxMax[0]) - { - flDelta = boxMax[0] - center[0]; - dmin += flDelta * flDelta; - } - - if (center[1] < boxMin[1]) - { - flDelta = center[1] - boxMin[1]; - dmin += flDelta * flDelta; - } - else if (center[1] > boxMax[1]) - { - flDelta = boxMax[1] - center[1]; - dmin += flDelta * flDelta; - } - - if (center[2] < boxMin[2]) - { - flDelta = center[2] - boxMin[2]; - dmin += flDelta * flDelta; - } - else if (center[2] > boxMax[2]) - { - flDelta = boxMax[2] - center[2]; - dmin += flDelta * flDelta; - } - - return dmin < radius * radius; -} - -bool IsBoxIntersectingSphereExtents( const Vector& boxCenter, const Vector& boxHalfDiag, - const Vector& center, float radius ) -{ - // See Graphics Gems, box-sphere intersection - float dmin = 0.0f; - float flDelta, flDiff; - - // Unrolled the loop.. this is a big cycle stealer... - flDiff = FloatMakePositive( center.x - boxCenter.x ); - if (flDiff > boxHalfDiag.x) - { - flDelta = flDiff - boxHalfDiag.x; - dmin += flDelta * flDelta; - } - - flDiff = FloatMakePositive( center.y - boxCenter.y ); - if (flDiff > boxHalfDiag.y) - { - flDelta = flDiff - boxHalfDiag.y; - dmin += flDelta * flDelta; - } - - flDiff = FloatMakePositive( center.z - boxCenter.z ); - if (flDiff > boxHalfDiag.z) - { - flDelta = flDiff - boxHalfDiag.z; - dmin += flDelta * flDelta; - } - - return dmin < radius * radius; -} - - -//----------------------------------------------------------------------------- -// Returns true if a rectangle intersects with a circle -//----------------------------------------------------------------------------- -bool IsCircleIntersectingRectangle( const Vector2D& boxMin, const Vector2D& boxMax, - const Vector2D& center, float radius ) -{ - // See Graphics Gems, box-sphere intersection - float dmin = 0.0f; - float flDelta; - - if (center[0] < boxMin[0]) - { - flDelta = center[0] - boxMin[0]; - dmin += flDelta * flDelta; - } - else if (center[0] > boxMax[0]) - { - flDelta = boxMax[0] - center[0]; - dmin += flDelta * flDelta; - } - - if (center[1] < boxMin[1]) - { - flDelta = center[1] - boxMin[1]; - dmin += flDelta * flDelta; - } - else if (center[1] > boxMax[1]) - { - flDelta = boxMax[1] - center[1]; - dmin += flDelta * flDelta; - } - - return dmin < radius * radius; -} - - -//----------------------------------------------------------------------------- -// returns true if there's an intersection between ray and sphere -//----------------------------------------------------------------------------- -bool IsRayIntersectingSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, - const Vector& vecCenter, float flRadius, float flTolerance ) -{ - // For this algorithm, find a point on the ray which is closest to the sphere origin - // Do this by making a plane passing through the sphere origin - // whose normal is parallel to the ray. Intersect that plane with the ray. - // Plane: N dot P = I, N = D (ray direction), I = C dot N = C dot D - // Ray: P = O + D * t - // D dot ( O + D * t ) = C dot D - // D dot O + D dot D * t = C dot D - // t = (C - O) dot D / D dot D - // Clamp t to (0,1) - // Find distance of the point on the ray to the sphere center. - Assert( flTolerance >= 0.0f ); - flRadius += flTolerance; - - Vector vecRayToSphere; - VectorSubtract( vecCenter, vecRayOrigin, vecRayToSphere ); - float flNumerator = DotProduct( vecRayToSphere, vecRayDelta ); - - float t; - if (flNumerator <= 0.0f) - { - t = 0.0f; - } - else - { - float flDenominator = DotProduct( vecRayDelta, vecRayDelta ); - if ( flNumerator > flDenominator ) - t = 1.0f; - else - t = flNumerator / flDenominator; - } - - Vector vecClosestPoint; - VectorMA( vecRayOrigin, t, vecRayDelta, vecClosestPoint ); - return ( vecClosestPoint.DistToSqr( vecCenter ) <= flRadius * flRadius ); - - // NOTE: This in an alternate algorithm which I didn't use because I'd have to use a sqrt - // So it's probably faster to do this other algorithm. I'll leave the comments here - // for how to go back if we want to - - // Solve using the ray equation + the sphere equation - // P = o + dt - // (x - xc)^2 + (y - yc)^2 + (z - zc)^2 = r^2 - // (ox + dx * t - xc)^2 + (oy + dy * t - yc)^2 + (oz + dz * t - zc)^2 = r^2 - // (ox - xc)^2 + 2 * (ox-xc) * dx * t + dx^2 * t^2 + - // (oy - yc)^2 + 2 * (oy-yc) * dy * t + dy^2 * t^2 + - // (oz - zc)^2 + 2 * (oz-zc) * dz * t + dz^2 * t^2 = r^2 - // (dx^2 + dy^2 + dz^2) * t^2 + 2 * ((ox-xc)dx + (oy-yc)dy + (oz-zc)dz) t + - // (ox-xc)^2 + (oy-yc)^2 + (oz-zc)^2 - r^2 = 0 - // or, t = (-b +/- sqrt( b^2 - 4ac)) / 2a - // a = DotProduct( vecRayDelta, vecRayDelta ); - // b = 2 * DotProduct( vecRayOrigin - vecCenter, vecRayDelta ) - // c = DotProduct(vecRayOrigin - vecCenter, vecRayOrigin - vecCenter) - flRadius * flRadius; - // Valid solutions are possible only if b^2 - 4ac >= 0 - // Therefore, compute that value + see if we got it -} - - -//----------------------------------------------------------------------------- -// -// IntersectInfiniteRayWithSphere -// -// Returns whether or not there was an intersection. -// Returns the two intersection points -// -//----------------------------------------------------------------------------- -bool IntersectInfiniteRayWithSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, - const Vector &vecSphereCenter, float flRadius, float *pT1, float *pT2 ) -{ - // Solve using the ray equation + the sphere equation - // P = o + dt - // (x - xc)^2 + (y - yc)^2 + (z - zc)^2 = r^2 - // (ox + dx * t - xc)^2 + (oy + dy * t - yc)^2 + (oz + dz * t - zc)^2 = r^2 - // (ox - xc)^2 + 2 * (ox-xc) * dx * t + dx^2 * t^2 + - // (oy - yc)^2 + 2 * (oy-yc) * dy * t + dy^2 * t^2 + - // (oz - zc)^2 + 2 * (oz-zc) * dz * t + dz^2 * t^2 = r^2 - // (dx^2 + dy^2 + dz^2) * t^2 + 2 * ((ox-xc)dx + (oy-yc)dy + (oz-zc)dz) t + - // (ox-xc)^2 + (oy-yc)^2 + (oz-zc)^2 - r^2 = 0 - // or, t = (-b +/- sqrt( b^2 - 4ac)) / 2a - // a = DotProduct( vecRayDelta, vecRayDelta ); - // b = 2 * DotProduct( vecRayOrigin - vecCenter, vecRayDelta ) - // c = DotProduct(vecRayOrigin - vecCenter, vecRayOrigin - vecCenter) - flRadius * flRadius; - - Vector vecSphereToRay; - VectorSubtract( vecRayOrigin, vecSphereCenter, vecSphereToRay ); - - float a = DotProduct( vecRayDelta, vecRayDelta ); - - // This would occur in the case of a zero-length ray - if ( a == 0.0f ) - { - *pT1 = *pT2 = 0.0f; - return vecSphereToRay.LengthSqr() <= flRadius * flRadius; - } - - float b = 2 * DotProduct( vecSphereToRay, vecRayDelta ); - float c = DotProduct( vecSphereToRay, vecSphereToRay ) - flRadius * flRadius; - float flDiscrim = b * b - 4 * a * c; - if ( flDiscrim < 0.0f ) - return false; - - flDiscrim = sqrt( flDiscrim ); - float oo2a = 0.5f / a; - *pT1 = ( - b - flDiscrim ) * oo2a; - *pT2 = ( - b + flDiscrim ) * oo2a; - return true; -} - - - -//----------------------------------------------------------------------------- -// -// IntersectRayWithSphere -// -// Returns whether or not there was an intersection. -// Returns the two intersection points, clamped to (0,1) -// -//----------------------------------------------------------------------------- -bool IntersectRayWithSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, - const Vector &vecSphereCenter, float flRadius, float *pT1, float *pT2 ) -{ - if ( !IntersectInfiniteRayWithSphere( vecRayOrigin, vecRayDelta, vecSphereCenter, flRadius, pT1, pT2 ) ) - return false; - - if (( *pT1 > 1.0f ) || ( *pT2 < 0.0f )) - return false; - - // Clamp it! - if ( *pT1 < 0.0f ) - *pT1 = 0.0f; - if ( *pT2 > 1.0f ) - *pT2 = 1.0f; - - return true; -} - - -//----------------------------------------------------------------------------- -// returns true if the point is in the box -//----------------------------------------------------------------------------- -bool IsPointInBox( const Vector& pt, const Vector& boxMin, const Vector& boxMax ) -{ - Assert( boxMin[0] <= boxMax[0] ); - Assert( boxMin[1] <= boxMax[1] ); - Assert( boxMin[2] <= boxMax[2] ); - - if ( (pt[0] > boxMax[0]) || (pt[0] < boxMin[0]) ) - return false; - if ( (pt[1] > boxMax[1]) || (pt[1] < boxMin[1]) ) - return false; - if ( (pt[2] > boxMax[2]) || (pt[2] < boxMin[2]) ) - return false; - return true; -} - - -bool IsPointInCone( const Vector &pt, const Vector &origin, const Vector &axis, float cosAngle, float length ) -{ - Vector delta = pt - origin; - float dist = VectorNormalize( delta ); - float dot = DotProduct( delta, axis ); - if ( dot < cosAngle ) - return false; - if ( dist * dot > length ) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -// returns true if there's an intersection between two boxes -//----------------------------------------------------------------------------- -bool IsBoxIntersectingBox( const Vector& boxMin1, const Vector& boxMax1, - const Vector& boxMin2, const Vector& boxMax2 ) -{ - Assert( boxMin1[0] <= boxMax1[0] ); - Assert( boxMin1[1] <= boxMax1[1] ); - Assert( boxMin1[2] <= boxMax1[2] ); - Assert( boxMin2[0] <= boxMax2[0] ); - Assert( boxMin2[1] <= boxMax2[1] ); - Assert( boxMin2[2] <= boxMax2[2] ); - - if ( (boxMin1[0] > boxMax2[0]) || (boxMax1[0] < boxMin2[0]) ) - return false; - if ( (boxMin1[1] > boxMax2[1]) || (boxMax1[1] < boxMin2[1]) ) - return false; - if ( (boxMin1[2] > boxMax2[2]) || (boxMax1[2] < boxMin2[2]) ) - return false; - return true; -} - -bool IsBoxIntersectingBoxExtents( const Vector& boxCenter1, const Vector& boxHalfDiagonal1, - const Vector& boxCenter2, const Vector& boxHalfDiagonal2 ) -{ - Vector vecDelta, vecSize; - VectorSubtract( boxCenter1, boxCenter2, vecDelta ); - VectorAdd( boxHalfDiagonal1, boxHalfDiagonal2, vecSize ); - return ( FloatMakePositive( vecDelta.x ) <= vecSize.x ) && - ( FloatMakePositive( vecDelta.y ) <= vecSize.y ) && - ( FloatMakePositive( vecDelta.z ) <= vecSize.z ); -} - - -//----------------------------------------------------------------------------- -// -// IsOBBIntersectingOBB -// -// returns true if there's an intersection between two OBBs -// -//----------------------------------------------------------------------------- -bool IsOBBIntersectingOBB( const Vector &vecOrigin1, const QAngle &vecAngles1, const Vector& boxMin1, const Vector& boxMax1, - const Vector &vecOrigin2, const QAngle &vecAngles2, const Vector& boxMin2, const Vector& boxMax2, float flTolerance ) -{ - // FIXME: Simple case AABB check doesn't work because the min and max extents are not oriented based on the angle - // this fast check would only be good for cubes. - /*if ( vecAngles1 == vecAngles2 ) - { - const Vector &vecDelta = vecOrigin2 - vecOrigin1; - Vector vecOtherMins, vecOtherMaxs; - VectorAdd( boxMin2, vecDelta, vecOtherMins ); - VectorAdd( boxMax2, vecDelta, vecOtherMaxs ); - return IsBoxIntersectingBox( boxMin1, boxMax1, vecOtherMins, vecOtherMaxs ); - }*/ - - // OBB test... - cplane_t plane; - bool bFoundPlane = ComputeSeparatingPlane( vecOrigin1, vecAngles1, boxMin1, boxMax1, - vecOrigin2, vecAngles2, boxMin2, boxMax2, flTolerance, &plane ); - return (bFoundPlane == false); -} - -//----------------------------------------------------------------------------- -// returns true if there's an intersection between box and ray -//----------------------------------------------------------------------------- -bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, - const Vector& origin, const Vector& delta, float flTolerance ) -{ - Assert( boxMin[0] <= boxMax[0] ); - Assert( boxMin[1] <= boxMax[1] ); - Assert( boxMin[2] <= boxMax[2] ); - - // FIXME: Surely there's a faster way - float tmin = -FLT_MAX; - float tmax = FLT_MAX; - - for (int i = 0; i < 3; ++i) - { - // Parallel case... - if (FloatMakePositive(delta[i]) < 1e-8) - { - // Check that origin is in the box - // if not, then it doesn't intersect.. - if ( (origin[i] < boxMin[i] - flTolerance) || (origin[i] > boxMax[i] + flTolerance) ) - return false; - - continue; - } - - // non-parallel case - // Find the t's corresponding to the entry and exit of - // the ray along x, y, and z. The find the furthest entry - // point, and the closest exit point. Once that is done, - // we know we don't collide if the closest exit point - // is behind the starting location. We also don't collide if - // the closest exit point is in front of the furthest entry point - - float invDelta = 1.0f / delta[i]; - float t1 = (boxMin[i] - flTolerance - origin[i]) * invDelta; - float t2 = (boxMax[i] + flTolerance - origin[i]) * invDelta; - if (t1 > t2) - { - float temp = t1; - t1 = t2; - t2 = temp; - } - if (t1 > tmin) - tmin = t1; - if (t2 < tmax) - tmax = t2; - if (tmin > tmax) - return false; - if (tmax < 0) - return false; - if (tmin > 1) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// returns true if there's an intersection between box and ray -//----------------------------------------------------------------------------- -bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, - const Vector& origin, const Vector& delta, - const Vector& invDelta, float flTolerance ) -{ - Assert( boxMin[0] <= boxMax[0] ); - Assert( boxMin[1] <= boxMax[1] ); - Assert( boxMin[2] <= boxMax[2] ); - - // FIXME: Surely there's a faster way - float tmin = -FLT_MAX; - float tmax = FLT_MAX; - - for ( int i = 0; i < 3; ++i ) - { - // Parallel case... - if ( FloatMakePositive( delta[i] ) < 1e-8 ) - { - // Check that origin is in the box, if not, then it doesn't intersect.. - if ( ( origin[i] < boxMin[i] - flTolerance ) || ( origin[i] > boxMax[i] + flTolerance ) ) - return false; - - continue; - } - - // Non-parallel case - // Find the t's corresponding to the entry and exit of - // the ray along x, y, and z. The find the furthest entry - // point, and the closest exit point. Once that is done, - // we know we don't collide if the closest exit point - // is behind the starting location. We also don't collide if - // the closest exit point is in front of the furthest entry point - float t1 = ( boxMin[i] - flTolerance - origin[i] ) * invDelta[i]; - float t2 = ( boxMax[i] + flTolerance - origin[i] ) * invDelta[i]; - if ( t1 > t2 ) - { - float temp = t1; - t1 = t2; - t2 = temp; - } - - if (t1 > tmin) - tmin = t1; - - if (t2 < tmax) - tmax = t2; - - if (tmin > tmax) - return false; - - if (tmax < 0) - return false; - - if (tmin > 1) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Intersects a ray with a aabb, return true if they intersect -//----------------------------------------------------------------------------- -bool FASTCALL IsBoxIntersectingRay( const Vector& vecBoxMin, const Vector& vecBoxMax, const Ray_t& ray, float flTolerance ) -{ - if ( !ray.m_IsSwept ) - { - Vector rayMins, rayMaxs; - VectorSubtract( ray.m_Start, ray.m_Extents, rayMins ); - VectorAdd( ray.m_Start, ray.m_Extents, rayMaxs ); - if ( flTolerance != 0.0f ) - { - rayMins.x -= flTolerance; rayMins.y -= flTolerance; rayMins.z -= flTolerance; - rayMaxs.x += flTolerance; rayMaxs.y += flTolerance; rayMaxs.z += flTolerance; - } - return IsBoxIntersectingBox( vecBoxMin, vecBoxMax, rayMins, rayMaxs ); - } - - Vector vecExpandedBoxMin, vecExpandedBoxMax; - VectorSubtract( vecBoxMin, ray.m_Extents, vecExpandedBoxMin ); - VectorAdd( vecBoxMax, ray.m_Extents, vecExpandedBoxMax ); - return IsBoxIntersectingRay( vecExpandedBoxMin, vecExpandedBoxMax, ray.m_Start, ray.m_Delta, flTolerance ); -} - - -//----------------------------------------------------------------------------- -// Intersects a ray with a ray, return true if they intersect -// t, s = parameters of closest approach (if not intersecting!) -//----------------------------------------------------------------------------- -bool IntersectRayWithRay( const Ray_t &ray0, const Ray_t &ray1, float &t, float &s ) -{ - Assert( ray0.m_IsRay && ray1.m_IsRay ); - - // - // r0 = p0 + v0t - // r1 = p1 + v1s - // - // intersection : r0 = r1 :: p0 + v0t = p1 + v1s - // NOTE: v(0,1) are unit direction vectors - // - // subtract p0 from both sides and cross with v1 (NOTE: v1 x v1 = 0) - // (v0 x v1)t = ((p1 - p0 ) x v1) - // - // dotting with (v0 x v1) and dividing by |v0 x v1|^2 - // t = Det | (p1 - p0) , v1 , (v0 x v1) | / |v0 x v1|^2 - // s = Det | (p1 - p0) , v0 , (v0 x v1) | / |v0 x v1|^2 - // - // Det | A B C | = -( A x C ) dot B or -( C x B ) dot A - // - // NOTE: if |v0 x v1|^2 = 0, then the lines are parallel - // - Vector v0( ray0.m_Delta ); - Vector v1( ray1.m_Delta ); - VectorNormalize( v0 ); - VectorNormalize( v1 ); - - Vector v0xv1 = v0.Cross( v1 ); - float length = v0xv1.Length(); - if( length == 0.0f ) - { - t = 0; s = 0; - return false; // parallel - } - - Vector p1p0 = ray1.m_Start - ray0.m_Start; - - Vector AxC = p1p0.Cross( v0xv1 ); - AxC.Negate(); - float detT = AxC.Dot( v1 ); - - AxC = p1p0.Cross( v0xv1 ); - AxC.Negate(); - float detS = AxC.Dot( v0 ); - - t = detT / ( length * length ); - s = detS / ( length * length ); - - // intersection???? - Vector i0, i1; - i0 = v0 * t; - i1 = v1 * s; - i0 += ray0.m_Start; - i1 += ray1.m_Start; - if( i0.x == i1.x && i0.y == i1.y && i0.z == i1.z ) - return true; - - return false; -} - - -//----------------------------------------------------------------------------- -// Intersects a ray with a plane, returns distance t along ray. -//----------------------------------------------------------------------------- -float IntersectRayWithPlane( const Ray_t& ray, const cplane_t& plane ) -{ - float denom = DotProduct( ray.m_Delta, plane.normal ); - if (denom == 0.0f) - return 0.0f; - - denom = 1.0f / denom; - return (plane.dist - DotProduct( ray.m_Start, plane.normal )) * denom; -} - -float IntersectRayWithPlane( const Vector& org, const Vector& dir, const cplane_t& plane ) -{ - float denom = DotProduct( dir, plane.normal ); - if (denom == 0.0f) - return 0.0f; - - denom = 1.0f / denom; - return (plane.dist - DotProduct( org, plane.normal )) * denom; -} - -float IntersectRayWithPlane( const Vector& org, const Vector& dir, const Vector& normal, float dist ) -{ - float denom = DotProduct( dir, normal ); - if (denom == 0.0f) - return 0.0f; - - denom = 1.0f / denom; - return (dist - DotProduct( org, normal )) * denom; -} - -float IntersectRayWithAAPlane( const Vector& vecStart, const Vector& vecEnd, int nAxis, float flSign, float flDist ) -{ - float denom = flSign * (vecEnd[nAxis] - vecStart[nAxis]); - if (denom == 0.0f) - return 0.0f; - - denom = 1.0f / denom; - return (flDist - flSign * vecStart[nAxis]) * denom; -} - - -//----------------------------------------------------------------------------- -// Intersects a ray against a box -//----------------------------------------------------------------------------- -bool IntersectRayWithBox( const Vector &vecRayStart, const Vector &vecRayDelta, - const Vector &boxMins, const Vector &boxMaxs, float flTolerance, BoxTraceInfo_t *pTrace ) -{ - int i; - float d1, d2; - float f; - - pTrace->t1 = -1.0f; - pTrace->t2 = 1.0f; - pTrace->hitside = -1; - - // UNDONE: This makes this code a little messy - pTrace->startsolid = true; - - for ( i = 0; i < 6; ++i ) - { - if ( i >= 3 ) - { - d1 = vecRayStart[i-3] - boxMaxs[i-3]; - d2 = d1 + vecRayDelta[i-3]; - } - else - { - d1 = -vecRayStart[i] + boxMins[i]; - d2 = d1 - vecRayDelta[i]; - } - - // if completely in front of face, no intersection - if (d1 > 0 && d2 > 0) - { - // UNDONE: Have to revert this in case it's still set - // UNDONE: Refactor to have only 2 return points (true/false) from this function - pTrace->startsolid = false; - return false; - } - - // completely inside, check next face - if (d1 <= 0 && d2 <= 0) - continue; - - if (d1 > 0) - { - pTrace->startsolid = false; - } - - // crosses face - if (d1 > d2) - { - f = d1 - flTolerance; - if ( f < 0 ) - { - f = 0; - } - f = f / (d1-d2); - if (f > pTrace->t1) - { - pTrace->t1 = f; - pTrace->hitside = i; - } - } - else - { - // leave - f = (d1 + flTolerance) / (d1-d2); - if (f < pTrace->t2) - { - pTrace->t2 = f; - } - } - } - - return pTrace->startsolid || (pTrace->t1 < pTrace->t2 && pTrace->t1 >= 0.0f); -} - - -//----------------------------------------------------------------------------- -// Intersects a ray against a box -//----------------------------------------------------------------------------- -bool IntersectRayWithBox( const Vector &vecRayStart, const Vector &vecRayDelta, - const Vector &boxMins, const Vector &boxMaxs, float flTolerance, CBaseTrace *pTrace ) -{ - Collision_ClearTrace( vecRayStart, vecRayDelta, pTrace ); - - BoxTraceInfo_t trace; - - if ( IntersectRayWithBox( vecRayStart, vecRayDelta, boxMins, boxMaxs, flTolerance, &trace ) ) - { - pTrace->startsolid = trace.startsolid; - if (trace.t1 < trace.t2 && trace.t1 >= 0.0f) - { - pTrace->fraction = trace.t1; - VectorMA( pTrace->startpos, trace.t1, vecRayDelta, pTrace->endpos ); - pTrace->contents = CONTENTS_SOLID; - pTrace->plane.normal = vec3_origin; - if ( trace.hitside >= 3 ) - { - trace.hitside -= 3; - pTrace->plane.dist = boxMaxs[trace.hitside]; - pTrace->plane.normal[trace.hitside] = 1.0f; - pTrace->plane.type = trace.hitside; - } - else - { - pTrace->plane.dist = -boxMins[trace.hitside]; - pTrace->plane.normal[trace.hitside] = -1.0f; - pTrace->plane.type = trace.hitside; - } - return true; - } - - if ( pTrace->startsolid ) - { - pTrace->allsolid = (trace.t2 <= 0.0f) || (trace.t2 >= 1.0f); - pTrace->fraction = 0; - pTrace->endpos = pTrace->startpos; - pTrace->contents = CONTENTS_SOLID; - pTrace->plane.dist = pTrace->startpos[0]; - pTrace->plane.normal.Init( 1.0f, 0.0f, 0.0f ); - pTrace->plane.type = 0; - return true; - } - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Intersects a ray against a box -//----------------------------------------------------------------------------- -bool IntersectRayWithBox( const Ray_t &ray, const Vector &boxMins, const Vector &boxMaxs, - float flTolerance, CBaseTrace *pTrace ) -{ - Vector vecExpandedMins = boxMins; - Vector vecExpandedMaxs = boxMaxs; - - if ( !ray.m_IsRay ) - { - vecExpandedMins -= ray.m_Extents; - vecExpandedMaxs += ray.m_Extents; - } - - bool bIntersects = IntersectRayWithBox( ray.m_Start, ray.m_Delta, vecExpandedMins, vecExpandedMaxs, flTolerance, pTrace ); - - if ( !ray.m_IsRay ) - { - pTrace->startpos += ray.m_StartOffset; - pTrace->endpos += ray.m_StartOffset; - } - - return bIntersects; -} - - -//----------------------------------------------------------------------------- -// Intersects a ray against an OBB, returns t1 and t2 -//----------------------------------------------------------------------------- -bool IntersectRayWithOBB( const Vector &vecRayStart, const Vector &vecRayDelta, - const matrix3x4_t &matOBBToWorld, const Vector &vecOBBMins, const Vector &vecOBBMaxs, - float flTolerance, BoxTraceInfo_t *pTrace ) -{ - // FIXME: Two transforms is pretty expensive. Should we optimize this? - Vector start, delta; - VectorITransform( vecRayStart, matOBBToWorld, start ); - VectorIRotate( vecRayDelta, matOBBToWorld, delta ); - - return IntersectRayWithBox( start, delta, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); -} - - - -//----------------------------------------------------------------------------- -// Intersects a ray against an OBB -//----------------------------------------------------------------------------- -bool IntersectRayWithOBB( const Vector &vecRayStart, const Vector &vecRayDelta, - const matrix3x4_t &matOBBToWorld, const Vector &vecOBBMins, const Vector &vecOBBMaxs, - float flTolerance, CBaseTrace *pTrace ) -{ - Collision_ClearTrace( vecRayStart, vecRayDelta, pTrace ); - - // FIXME: Make it work with tolerance - Assert( flTolerance == 0.0f ); - - // OPTIMIZE: Store this in the box instead of computing it here - // compute center in local space - Vector vecBoxExtents = (vecOBBMins + vecOBBMaxs) * 0.5; - Vector vecBoxCenter; - - // transform to world space - VectorTransform( vecBoxExtents, matOBBToWorld, vecBoxCenter ); - - // calc extents from local center - vecBoxExtents = vecOBBMaxs - vecBoxExtents; - - // OPTIMIZE: This is optimized for world space. If the transform is fast enough, it may make more - // sense to just xform and call UTIL_ClipToBox() instead. MEASURE THIS. - - // save the extents of the ray along - Vector extent, uextent; - Vector segmentCenter = vecRayStart + vecRayDelta - vecBoxCenter; - - extent.Init(); - - // check box axes for separation - for ( int j = 0; j < 3; j++ ) - { - extent[j] = vecRayDelta.x * matOBBToWorld[0][j] + vecRayDelta.y * matOBBToWorld[1][j] + vecRayDelta.z * matOBBToWorld[2][j]; - uextent[j] = fabsf(extent[j]); - float coord = segmentCenter.x * matOBBToWorld[0][j] + segmentCenter.y * matOBBToWorld[1][j] + segmentCenter.z * matOBBToWorld[2][j]; - coord = fabsf(coord); - - if ( coord > (vecBoxExtents[j] + uextent[j]) ) - return false; - } - - // now check cross axes for separation - float tmp, cextent; - Vector cross = vecRayDelta.Cross( segmentCenter ); - cextent = cross.x * matOBBToWorld[0][0] + cross.y * matOBBToWorld[1][0] + cross.z * matOBBToWorld[2][0]; - cextent = fabsf(cextent); - tmp = vecBoxExtents[1]*uextent[2] + vecBoxExtents[2]*uextent[1]; - if ( cextent > tmp ) - return false; - - cextent = cross.x * matOBBToWorld[0][1] + cross.y * matOBBToWorld[1][1] + cross.z * matOBBToWorld[2][1]; - cextent = fabsf(cextent); - tmp = vecBoxExtents[0]*uextent[2] + vecBoxExtents[2]*uextent[0]; - if ( cextent > tmp ) - return false; - - cextent = cross.x * matOBBToWorld[0][2] + cross.y * matOBBToWorld[1][2] + cross.z * matOBBToWorld[2][2]; - cextent = fabsf(cextent); - tmp = vecBoxExtents[0]*uextent[1] + vecBoxExtents[1]*uextent[0]; - if ( cextent > tmp ) - return false; - - // !!! We hit this box !!! compute intersection point and return - // Compute ray start in bone space - Vector start; - VectorITransform( vecRayStart, matOBBToWorld, start ); - - // extent is ray.m_Delta in bone space, recompute delta in bone space - extent *= 2.0f; - - // delta was prescaled by the current t, so no need to see if this intersection - // is closer - trace_t boxTrace; - if ( !IntersectRayWithBox( start, extent, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ) ) - return false; - - // Fix up the plane information - float flSign = pTrace->plane.normal[ pTrace->plane.type ]; - pTrace->plane.normal[0] = flSign * matOBBToWorld[0][pTrace->plane.type]; - pTrace->plane.normal[1] = flSign * matOBBToWorld[1][pTrace->plane.type]; - pTrace->plane.normal[2] = flSign * matOBBToWorld[2][pTrace->plane.type]; - pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal ); - pTrace->plane.type = 3; - return true; -} - - -//----------------------------------------------------------------------------- -// Intersects a ray against an OBB -//----------------------------------------------------------------------------- -bool IntersectRayWithOBB( const Vector &vecRayOrigin, const Vector &vecRayDelta, - const Vector &vecBoxOrigin, const QAngle &angBoxRotation, - const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ) -{ - if (angBoxRotation == vec3_angle) - { - Vector vecAbsMins, vecAbsMaxs; - VectorAdd( vecBoxOrigin, vecOBBMins, vecAbsMins ); - VectorAdd( vecBoxOrigin, vecOBBMaxs, vecAbsMaxs ); - return IntersectRayWithBox( vecRayOrigin, vecRayDelta, vecAbsMins, vecAbsMaxs, flTolerance, pTrace ); - } - - matrix3x4_t obbToWorld; - AngleMatrix( angBoxRotation, vecBoxOrigin, obbToWorld ); - return IntersectRayWithOBB( vecRayOrigin, vecRayDelta, obbToWorld, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); -} - - -//----------------------------------------------------------------------------- -// Box support map -//----------------------------------------------------------------------------- -inline void ComputeSupportMap( const Vector &vecDirection, const Vector &vecBoxMins, - const Vector &vecBoxMaxs, float pDist[2] ) -{ - int nIndex = (vecDirection.x > 0.0f); - pDist[nIndex] = vecBoxMaxs.x * vecDirection.x; - pDist[1 - nIndex] = vecBoxMins.x * vecDirection.x; - - nIndex = (vecDirection.y > 0.0f); - pDist[nIndex] += vecBoxMaxs.y * vecDirection.y; - pDist[1 - nIndex] += vecBoxMins.y * vecDirection.y; - - nIndex = (vecDirection.z > 0.0f); - pDist[nIndex] += vecBoxMaxs.z * vecDirection.z; - pDist[1 - nIndex] += vecBoxMins.z * vecDirection.z; -} - -inline void ComputeSupportMap( const Vector &vecDirection, int i1, int i2, - const Vector &vecBoxMins, const Vector &vecBoxMaxs, float pDist[2] ) -{ - int nIndex = (vecDirection[i1] > 0.0f); - pDist[nIndex] = vecBoxMaxs[i1] * vecDirection[i1]; - pDist[1 - nIndex] = vecBoxMins[i1] * vecDirection[i1]; - - nIndex = (vecDirection[i2] > 0.0f); - pDist[nIndex] += vecBoxMaxs[i2] * vecDirection[i2]; - pDist[1 - nIndex] += vecBoxMins[i2] * vecDirection[i2]; -} - -//----------------------------------------------------------------------------- -// Intersects a ray against an OBB -//----------------------------------------------------------------------------- -static int s_ExtIndices[3][2] = -{ - { 2, 1 }, - { 0, 2 }, - { 0, 1 }, -}; - -static int s_MatIndices[3][2] = -{ - { 1, 2 }, - { 2, 0 }, - { 1, 0 }, -}; - -bool IntersectRayWithOBB( const Ray_t &ray, const matrix3x4_t &matOBBToWorld, - const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ) -{ - if ( ray.m_IsRay ) - { - return IntersectRayWithOBB( ray.m_Start, ray.m_Delta, matOBBToWorld, - vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); - } - - Collision_ClearTrace( ray.m_Start + ray.m_StartOffset, ray.m_Delta, pTrace ); - - // Compute a bounding sphere around the bloated OBB - Vector vecOBBCenter; - VectorAdd( vecOBBMins, vecOBBMaxs, vecOBBCenter ); - vecOBBCenter *= 0.5f; - vecOBBCenter.x += matOBBToWorld[0][3]; - vecOBBCenter.y += matOBBToWorld[1][3]; - vecOBBCenter.z += matOBBToWorld[2][3]; - - Vector vecOBBHalfDiagonal; - VectorSubtract( vecOBBMaxs, vecOBBMins, vecOBBHalfDiagonal ); - vecOBBHalfDiagonal *= 0.5f; - - float flRadius = vecOBBHalfDiagonal.Length() + ray.m_Extents.Length(); - if ( !IsRayIntersectingSphere( ray.m_Start, ray.m_Delta, vecOBBCenter, flRadius, flTolerance ) ) - return false; - - // Ok, we passed the trivial reject, so lets do the dirty deed. - // Basically we're going to do the GJK thing explicitly. We'll shrink the ray down - // to a point, and bloat the OBB by the ray's extents. This will generate facet - // planes which are perpendicular to all of the separating axes typically seen in - // a standard seperating axis implementation. - - // We're going to create a number of planes through various vertices in the OBB - // which represent all of the separating planes. Then we're going to bloat the planes - // by the ray extents. - - // We're going to do all work in OBB-space because it's easier to do the - // support-map in this case - - // First, transform the ray into the space of the OBB - Vector vecLocalRayOrigin, vecLocalRayDirection; - VectorITransform( ray.m_Start, matOBBToWorld, vecLocalRayOrigin ); - VectorIRotate( ray.m_Delta, matOBBToWorld, vecLocalRayDirection ); - - // Next compute all separating planes - Vector pPlaneNormal[15]; - float ppPlaneDist[15][2]; - - int i; - for ( i = 0; i < 3; ++i ) - { - // Each plane needs to be bloated an amount = to the abs dot product of - // the ray extents with the plane normal - // For the OBB planes, do it in world space; - // and use the direction of the OBB (the ith column of matOBBToWorld) in world space vs extents - pPlaneNormal[i].Init( ); - pPlaneNormal[i][i] = 1.0f; - - float flExtentDotNormal = - FloatMakePositive( matOBBToWorld[0][i] * ray.m_Extents.x ) + - FloatMakePositive( matOBBToWorld[1][i] * ray.m_Extents.y ) + - FloatMakePositive( matOBBToWorld[2][i] * ray.m_Extents.z ); - - ppPlaneDist[i][0] = vecOBBMins[i] - flExtentDotNormal; - ppPlaneDist[i][1] = vecOBBMaxs[i] + flExtentDotNormal; - - // For the ray-extents planes, they are bloated by the extents - // Use the support map to determine which - VectorCopy( matOBBToWorld[i], pPlaneNormal[i+3].Base() ); - ComputeSupportMap( pPlaneNormal[i+3], vecOBBMins, vecOBBMaxs, ppPlaneDist[i+3] ); - ppPlaneDist[i+3][0] -= ray.m_Extents[i]; - ppPlaneDist[i+3][1] += ray.m_Extents[i]; - - // Now the edge cases... (take the cross product of x,y,z axis w/ ray extent axes - // given by the rows of the obb to world matrix. - // Compute the ray extent bloat in world space because it's easier... - - // These are necessary to compute the world-space versions of - // the edges so we can compute the extent dot products - float flRayExtent0 = ray.m_Extents[s_ExtIndices[i][0]]; - float flRayExtent1 = ray.m_Extents[s_ExtIndices[i][1]]; - const float *pMatRow0 = matOBBToWorld[s_MatIndices[i][0]]; - const float *pMatRow1 = matOBBToWorld[s_MatIndices[i][1]]; - - // x axis of the OBB + world ith axis - pPlaneNormal[i+6].Init( 0.0f, -matOBBToWorld[i][2], matOBBToWorld[i][1] ); - ComputeSupportMap( pPlaneNormal[i+6], 1, 2, vecOBBMins, vecOBBMaxs, ppPlaneDist[i+6] ); - flExtentDotNormal = - FloatMakePositive( pMatRow0[0] ) * flRayExtent0 + - FloatMakePositive( pMatRow1[0] ) * flRayExtent1; - ppPlaneDist[i+6][0] -= flExtentDotNormal; - ppPlaneDist[i+6][1] += flExtentDotNormal; - - // y axis of the OBB + world ith axis - pPlaneNormal[i+9].Init( matOBBToWorld[i][2], 0.0f, -matOBBToWorld[i][0] ); - ComputeSupportMap( pPlaneNormal[i+9], 0, 2, vecOBBMins, vecOBBMaxs, ppPlaneDist[i+9] ); - flExtentDotNormal = - FloatMakePositive( pMatRow0[1] ) * flRayExtent0 + - FloatMakePositive( pMatRow1[1] ) * flRayExtent1; - ppPlaneDist[i+9][0] -= flExtentDotNormal; - ppPlaneDist[i+9][1] += flExtentDotNormal; - - // z axis of the OBB + world ith axis - pPlaneNormal[i+12].Init( -matOBBToWorld[i][1], matOBBToWorld[i][0], 0.0f ); - ComputeSupportMap( pPlaneNormal[i+12], 0, 1, vecOBBMins, vecOBBMaxs, ppPlaneDist[i+12] ); - flExtentDotNormal = - FloatMakePositive( pMatRow0[2] ) * flRayExtent0 + - FloatMakePositive( pMatRow1[2] ) * flRayExtent1; - ppPlaneDist[i+12][0] -= flExtentDotNormal; - ppPlaneDist[i+12][1] += flExtentDotNormal; - } - - float enterfrac, leavefrac; - float d1[2], d2[2]; - float f; - - int hitplane = -1; - int hitside = -1; - enterfrac = -1.0f; - leavefrac = 1.0f; - - pTrace->startsolid = true; - - Vector vecLocalRayEnd; - VectorAdd( vecLocalRayOrigin, vecLocalRayDirection, vecLocalRayEnd ); - - for ( i = 0; i < 15; ++i ) - { - // FIXME: Not particularly optimal since there's a lot of 0's in the plane normals - float flStartDot = DotProduct( pPlaneNormal[i], vecLocalRayOrigin ); - float flEndDot = DotProduct( pPlaneNormal[i], vecLocalRayEnd ); - - // NOTE: Negative here is because the plane normal + dist - // are defined in negative terms for the far plane (plane dist index 0) - d1[0] = -(flStartDot - ppPlaneDist[i][0]); - d2[0] = -(flEndDot - ppPlaneDist[i][0]); - - d1[1] = flStartDot - ppPlaneDist[i][1]; - d2[1] = flEndDot - ppPlaneDist[i][1]; - - int j; - for ( j = 0; j < 2; ++j ) - { - // if completely in front near plane or behind far plane no intersection - if (d1[j] > 0 && d2[j] > 0) - return false; - - // completely inside, check next plane set - if (d1[j] <= 0 && d2[j] <= 0) - continue; - - if (d1[j] > 0) - { - pTrace->startsolid = false; - } - - // crosses face - float flDenom = 1.0f / (d1[j] - d2[j]); - if (d1[j] > d2[j]) - { - f = d1[j] - flTolerance; - if ( f < 0 ) - { - f = 0; - } - f *= flDenom; - if (f > enterfrac) - { - enterfrac = f; - hitplane = i; - hitside = j; - } - } - else - { - // leave - f = (d1[j] + flTolerance) * flDenom; - if (f < leavefrac) - { - leavefrac = f; - } - } - } - } - - if (enterfrac < leavefrac && enterfrac >= 0.0f) - { - pTrace->fraction = enterfrac; - VectorMA( pTrace->startpos, enterfrac, ray.m_Delta, pTrace->endpos ); - pTrace->contents = CONTENTS_SOLID; - - // Need to transform the plane into world space... - cplane_t temp; - temp.normal = pPlaneNormal[hitplane]; - temp.dist = ppPlaneDist[hitplane][hitside]; - if (hitside == 0) - { - temp.normal *= -1.0f; - temp.dist *= -1.0f; - } - temp.type = 3; - - MatrixITransformPlane( matOBBToWorld, temp, pTrace->plane ); - return true; - } - - if ( pTrace->startsolid ) - { - pTrace->allsolid = (leavefrac <= 0.0f) || (leavefrac >= 1.0f); - pTrace->fraction = 0; - pTrace->endpos = pTrace->startpos; - pTrace->contents = CONTENTS_SOLID; - pTrace->plane.dist = pTrace->startpos[0]; - pTrace->plane.normal.Init( 1.0f, 0.0f, 0.0f ); - pTrace->plane.type = 0; - return true; - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Intersects a ray against an OBB -//----------------------------------------------------------------------------- -bool IntersectRayWithOBB( const Ray_t &ray, const Vector &vecBoxOrigin, const QAngle &angBoxRotation, - const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ) -{ - if ( angBoxRotation == vec3_angle ) - { - Vector vecWorldMins, vecWorldMaxs; - VectorAdd( vecBoxOrigin, vecOBBMins, vecWorldMins ); - VectorAdd( vecBoxOrigin, vecOBBMaxs, vecWorldMaxs ); - return IntersectRayWithBox( ray, vecWorldMins, vecWorldMaxs, flTolerance, pTrace ); - } - - if ( ray.m_IsRay ) - { - return IntersectRayWithOBB( ray.m_Start, ray.m_Delta, vecBoxOrigin, angBoxRotation, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); - } - - matrix3x4_t matOBBToWorld; - AngleMatrix( angBoxRotation, vecBoxOrigin, matOBBToWorld ); - return IntersectRayWithOBB( ray, matOBBToWorld, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); -} - - -//----------------------------------------------------------------------------- -// -//----------------------------------------------------------------------------- -void GetNonMajorAxes( const Vector &vNormal, Vector2D &axes ) -{ - axes[0] = 0; - axes[1] = 1; - - if( FloatMakePositive( vNormal.x ) > FloatMakePositive( vNormal.y ) ) - { - if( FloatMakePositive( vNormal.x ) > FloatMakePositive( vNormal.z ) ) - { - axes[0] = 1; - axes[1] = 2; - } - } - else - { - if( FloatMakePositive( vNormal.y ) > FloatMakePositive( vNormal.z ) ) - { - axes[0] = 0; - axes[1] = 2; - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -QuadBarycentricRetval_t QuadWithParallelEdges( const Vector &vecOrigin, - const Vector &vecU, float lengthU, const Vector &vecV, float lengthV, - const Vector &pt, Vector2D &vecUV ) -{ - Ray_t rayAxis; - Ray_t rayPt; - - // - // handle the u axis - // - rayAxis.m_Start = vecOrigin; - rayAxis.m_Delta = vecU; - rayAxis.m_IsRay = true; - - rayPt.m_Start = pt; - rayPt.m_Delta = vecV * -( lengthV * 10.0f ); - rayPt.m_IsRay = true; - - float s, t; - IntersectRayWithRay( rayAxis, rayPt, t, s ); - vecUV[0] = t / lengthU; - - // - // handle the v axis - // - rayAxis.m_Delta = vecV; - - rayPt.m_Delta = vecU * -( lengthU * 10.0f ); - - IntersectRayWithRay( rayAxis, rayPt, t, s ); - vecUV[1] = t / lengthV; - - // inside of the quad?? - if( ( vecUV[0] < 0.0f ) || ( vecUV[0] > 1.0f ) || - ( vecUV[1] < 0.0f ) || ( vecUV[1] > 1.0f ) ) - return BARY_QUADRATIC_FALSE; - - return BARY_QUADRATIC_TRUE; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void ResolveQuadratic( double tPlus, double tMinus, - const Vector axisU0, const Vector axisU1, - const Vector axisV0, const Vector axisV1, - const Vector axisOrigin, const Vector pt, - int projU, double &s, double &t ) -{ - // calculate the sPlus, sMinus pair(s) - double sDenomPlus = ( axisU0[projU] * ( 1 - tPlus ) ) + ( axisU1[projU] * tPlus ); - double sDenomMinus = ( axisU0[projU] * ( 1 - tMinus ) ) + ( axisU1[projU] * tMinus ); - - double sPlus = UNINIT, sMinus = UNINIT; - if( FloatMakePositive( sDenomPlus ) >= 1e-5 ) - { - sPlus = ( pt[projU] - axisOrigin[projU] - ( axisV0[projU] * tPlus ) ) / sDenomPlus; - } - - if( FloatMakePositive( sDenomMinus ) >= 1e-5 ) - { - sMinus = ( pt[projU] - axisOrigin[projU] - ( axisV0[projU] * tMinus ) ) / sDenomMinus; - } - - if( ( tPlus >= 0.0 ) && ( tPlus <= 1.0 ) && ( sPlus >= 0.0 ) && ( sPlus <= 1.0 ) ) - { - s = sPlus; - t = tPlus; - return; - } - - if( ( tMinus >= 0.0 ) && ( tMinus <= 1.0 ) && ( sMinus >= 0.0 ) && ( sMinus <= 1.0 ) ) - { - s = sMinus; - t = tMinus; - return; - } - - double s0, t0, s1, t1; - - s0 = sPlus; - t0 = tPlus; - if( s0 >= 1.0 ) { s0 -= 1.0; } - if( t0 >= 1.0 ) { t0 -= 1.0; } - - s1 = sMinus; - t1 = tMinus; - if( s1 >= 1.0 ) { s1 -= 1.0; } - if( t1 >= 1.0 ) { t1 -= 1.0; } - - s0 = FloatMakePositive( s0 ); - t0 = FloatMakePositive( t0 ); - s1 = FloatMakePositive( s1 ); - t1 = FloatMakePositive( t1 ); - - double max0, max1; - max0 = s0; - if( t0 > max0 ) { max0 = t0; } - max1 = s1; - if( t1 > max1 ) { max1 = t1; } - - if( max0 > max1 ) - { - s = sMinus; - t = tMinus; - } - else - { - s = sPlus; - t = tPlus; - } -} - - -//----------------------------------------------------------------------------- -// -//----------------------------------------------------------------------------- - -QuadBarycentricRetval_t PointInQuadToBarycentric( const Vector &v1, const Vector &v2, - const Vector &v3, const Vector &v4, const Vector &point, Vector2D &uv ) -{ -#define PIQ_TEXTURE_EPSILON 0.001 -#define PIQ_PLANE_EPSILON 0.1 -#define PIQ_DOT_EPSILON 0.99f - - // - // Think of a quad with points v1, v2, v3, v4 and u, v line segments - // u0 = v2 - v1 - // u1 = v3 - v4 - // v0 = v4 - v1 - // v1 = v3 - v2 - // - Vector axisU[2], axisV[2]; - Vector axisUNorm[2], axisVNorm[2]; - axisU[0] = axisUNorm[0] = v2 - v1; - axisU[1] = axisUNorm[1] = v3 - v4; - axisV[0] = axisVNorm[0] = v4 - v1; - axisV[1] = axisVNorm[1] = v3 - v2; - - float lengthU[2], lengthV[2]; - lengthU[0] = VectorNormalize( axisUNorm[0] ); - lengthU[1] = VectorNormalize( axisUNorm[1] ); - lengthV[0] = VectorNormalize( axisVNorm[0] ); - lengthV[1] = VectorNormalize( axisVNorm[1] ); - - // - // check for an early out - parallel opposite edges! - // NOTE: quad property if 1 set of opposite edges is parallel and equal - // in length, then the other set of edges is as well - // - if( axisUNorm[0].Dot( axisUNorm[1] ) > PIQ_DOT_EPSILON ) - { - if( FloatMakePositive( lengthU[0] - lengthU[1] ) < PIQ_PLANE_EPSILON ) - { - return QuadWithParallelEdges( v1, axisUNorm[0], lengthU[0], axisVNorm[0], lengthV[0], point, uv ); - } - } - - // - // since we are solving for s in our equations below we need to ensure that - // the v axes are non-parallel - // - bool bFlipped = false; - if( axisVNorm[0].Dot( axisVNorm[1] ) > PIQ_DOT_EPSILON ) - { - Vector tmp[2]; - tmp[0] = axisV[0]; - tmp[1] = axisV[1]; - axisV[0] = axisU[0]; - axisV[1] = axisU[1]; - axisU[0] = tmp[0]; - axisU[1] = tmp[1]; - bFlipped = true; - } - - // - // get the "projection" axes - // - Vector2D projAxes; - Vector vNormal = axisU[0].Cross( axisV[0] ); - GetNonMajorAxes( vNormal, projAxes ); - - // - // NOTE: axisU[0][projAxes[0]] < axisU[0][projAxes[1]], - // this is done to decrease error when dividing later - // - if( FloatMakePositive( axisU[0][projAxes[0]] ) < FloatMakePositive( axisU[0][projAxes[1]] ) ) - { - int tmp = projAxes[0]; - projAxes[0] = projAxes[1]; - projAxes[1] = tmp; - } - - // Here's how we got these equations: - // - // Given the points and u,v line segments above... - // - // Then: - // - // (1.0) PT = P0 + U0 * s + V * t - // - // where - // - // (1.1) V = V0 + s * (V1 - V0) - // (1.2) U = U0 + t * (U1 - U0) - // - // Therefore (from 1.1 + 1.0): - // PT - P0 = U0 * s + (V0 + s * (V1-V0)) * t - // Group s's: - // PT - P0 - t * V0 = s * (U0 + t * (V1-V0)) - // Two equations and two unknowns in x and y get you the following quadratic: - // - // solve the quadratic - // - double s = 0.0, t = 0.0; - double A, negB, C; - - A = ( axisU[0][projAxes[1]] * axisV[0][projAxes[0]] ) - - ( axisU[0][projAxes[0]] * axisV[0][projAxes[1]] ) - - ( axisU[1][projAxes[1]] * axisV[0][projAxes[0]] ) + - ( axisU[1][projAxes[0]] * axisV[0][projAxes[1]] ); - C = ( v1[projAxes[1]] * axisU[0][projAxes[0]] ) - - ( point[projAxes[1]] * axisU[0][projAxes[0]] ) - - ( v1[projAxes[0]] * axisU[0][projAxes[1]] ) + - ( point[projAxes[0]] * axisU[0][projAxes[1]] ); - negB = C - - ( v1[projAxes[1]] * axisU[1][projAxes[0]] ) + - ( point[projAxes[1]] * axisU[1][projAxes[0]] ) + - ( v1[projAxes[0]] * axisU[1][projAxes[1]] ) - - ( point[projAxes[0]] * axisU[1][projAxes[1]] ) + - ( axisU[0][projAxes[1]] * axisV[0][projAxes[0]] ) - - ( axisU[0][projAxes[0]] * axisV[0][projAxes[1]] ); - - if( ( A > -PIQ_PLANE_EPSILON ) && ( A < PIQ_PLANE_EPSILON ) ) - { - // shouldn't be here -- this should have been take care of in the "early out" -// Assert( 0 ); - - Vector vecUAvg, vecVAvg; - vecUAvg = ( axisUNorm[0] + axisUNorm[1] ) * 0.5f; - vecVAvg = ( axisVNorm[0] + axisVNorm[1] ) * 0.5f; - - float fLengthUAvg = ( lengthU[0] + lengthU[1] ) * 0.5f; - float fLengthVAvg = ( lengthV[0] + lengthV[1] ) * 0.5f; - - return QuadWithParallelEdges( v1, vecUAvg, fLengthUAvg, vecVAvg, fLengthVAvg, point, uv ); - -#if 0 - // legacy code -- kept here for completeness! - - // not a quadratic -- solve linearly - t = C / negB; - - // See (1.2) above - float ui = axisU[0][projAxes[0]] + t * ( axisU[1][projAxes[0]] - axisU[0][projAxes[0]] ); - if( FloatMakePositive( ui ) >= 1e-5 ) - { - // See (1.0) above - s = ( point[projAxes[0]] - v1[projAxes[0]] - axisV[0][projAxes[0]] * t ) / ui; - } -#endif - } - else - { - // (-b +/- sqrt( b^2 - 4ac )) / 2a - double discriminant = (negB*negB) - (4.0f * A * C); - if( discriminant < 0.0f ) - { - uv[0] = -99999.0f; - uv[1] = -99999.0f; - return BARY_QUADRATIC_NEGATIVE_DISCRIMINANT; - } - - double quad = sqrt( discriminant ); - double QPlus = ( negB + quad ) / ( 2.0f * A ); - double QMinus = ( negB - quad ) / ( 2.0f * A ); - - ResolveQuadratic( QPlus, QMinus, axisU[0], axisU[1], axisV[0], axisV[1], v1, point, projAxes[0], s, t ); - } - - if( !bFlipped ) - { - uv[0] = ( float )s; - uv[1] = ( float )t; - } - else - { - uv[0] = ( float )t; - uv[1] = ( float )s; - } - - // inside of the quad?? - if( ( uv[0] < 0.0f ) || ( uv[0] > 1.0f ) || ( uv[1] < 0.0f ) || ( uv[1] > 1.0f ) ) - return BARY_QUADRATIC_FALSE; - - return BARY_QUADRATIC_TRUE; - -#undef PIQ_TEXTURE_EPSILON -#undef PIQ_PLANE_EPSILON -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void PointInQuadFromBarycentric( const Vector &v1, const Vector &v2, const Vector &v3, const Vector &v4, - const Vector2D &uv, Vector &point ) -{ - // - // Think of a quad with points v1, v2, v3, v4 and u, v line segments - // find the ray from v0 edge to v1 edge at v - // - Vector vPts[2]; - VectorLerp( v1, v4, uv[1], vPts[0] ); - VectorLerp( v2, v3, uv[1], vPts[1] ); - VectorLerp( vPts[0], vPts[1], uv[0], point ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void TexCoordInQuadFromBarycentric( const Vector2D &v1, const Vector2D &v2, const Vector2D &v3, const Vector2D &v4, - const Vector2D &uv, Vector2D &texCoord ) -{ - // - // Think of a quad with points v1, v2, v3, v4 and u, v line segments - // find the ray from v0 edge to v1 edge at v - // - Vector2D vCoords[2]; - Vector2DLerp( v1, v4, uv[1], vCoords[0] ); - Vector2DLerp( v2, v3, uv[1], vCoords[1] ); - Vector2DLerp( vCoords[0], vCoords[1], uv[0], texCoord ); -} - - -//----------------------------------------------------------------------------- -// Compute point from barycentric specification -// Edge u goes from v0 to v1, edge v goes from v0 to v2 -//----------------------------------------------------------------------------- -void ComputePointFromBarycentric( const Vector& v0, const Vector& v1, const Vector& v2, - float u, float v, Vector& pt ) -{ - Vector edgeU, edgeV; - VectorSubtract( v1, v0, edgeU ); - VectorSubtract( v2, v0, edgeV ); - VectorMA( v0, u, edgeU, pt ); - VectorMA( pt, v, edgeV, pt ); -} - -void ComputePointFromBarycentric( const Vector2D& v0, const Vector2D& v1, const Vector2D& v2, - float u, float v, Vector2D& pt ) -{ - Vector2D edgeU, edgeV; - Vector2DSubtract( v1, v0, edgeU ); - Vector2DSubtract( v2, v0, edgeV ); - Vector2DMA( v0, u, edgeU, pt ); - Vector2DMA( pt, v, edgeV, pt ); -} - - -//----------------------------------------------------------------------------- -// Compute a matrix that has the correct orientation but which has an origin at -// the center of the bounds -//----------------------------------------------------------------------------- -static void ComputeCenterMatrix( const Vector& origin, const QAngle& angles, - const Vector& mins, const Vector& maxs, matrix3x4_t& matrix ) -{ - Vector centroid; - VectorAdd( mins, maxs, centroid ); - centroid *= 0.5f; - AngleMatrix( angles, matrix ); - - Vector worldCentroid; - VectorRotate( centroid, matrix, worldCentroid ); - worldCentroid += origin; - MatrixSetColumn( worldCentroid, 3, matrix ); -} - -static void ComputeCenterIMatrix( const Vector& origin, const QAngle& angles, - const Vector& mins, const Vector& maxs, matrix3x4_t& matrix ) -{ - Vector centroid; - VectorAdd( mins, maxs, centroid ); - centroid *= -0.5f; - AngleIMatrix( angles, matrix ); - - // For the translational component here, note that the origin in world space - // is T = R * C + O, (R = rotation matrix, C = centroid in local space, O = origin in world space) - // The IMatrix translation = - transpose(R) * T = -C - transpose(R) * 0 - Vector localOrigin; - VectorRotate( origin, matrix, localOrigin ); - centroid -= localOrigin; - MatrixSetColumn( centroid, 3, matrix ); -} - - -//----------------------------------------------------------------------------- -// Compute a matrix which is the absolute value of another -//----------------------------------------------------------------------------- -static inline void ComputeAbsMatrix( const matrix3x4_t& in, matrix3x4_t& out ) -{ - FloatBits(out[0][0]) = FloatAbsBits(in[0][0]); - FloatBits(out[0][1]) = FloatAbsBits(in[0][1]); - FloatBits(out[0][2]) = FloatAbsBits(in[0][2]); - FloatBits(out[1][0]) = FloatAbsBits(in[1][0]); - FloatBits(out[1][1]) = FloatAbsBits(in[1][1]); - FloatBits(out[1][2]) = FloatAbsBits(in[1][2]); - FloatBits(out[2][0]) = FloatAbsBits(in[2][0]); - FloatBits(out[2][1]) = FloatAbsBits(in[2][1]); - FloatBits(out[2][2]) = FloatAbsBits(in[2][2]); -} - - -//----------------------------------------------------------------------------- -// Compute a separating plane between two boxes (expensive!) -// Returns false if no separating plane exists -//----------------------------------------------------------------------------- -static bool ComputeSeparatingPlane( const matrix3x4_t &worldToBox1, const matrix3x4_t &box2ToWorld, - const Vector& box1Size, const Vector& box2Size, float tolerance, cplane_t* pPlane ) -{ - // The various separating planes can be either - // 1) A plane parallel to one of the box face planes - // 2) A plane parallel to the cross-product of an edge from each box - - // First, compute the basis of second box in the space of the first box - // NOTE: These basis place the origin at the centroid of each box! - matrix3x4_t box2ToBox1; - ConcatTransforms( worldToBox1, box2ToWorld, box2ToBox1 ); - - // We're going to be using the origin of box2 in the space of box1 alot, - // lets extract it from the matrix.... - Vector box2Origin; - MatrixGetColumn( box2ToBox1, 3, box2Origin ); - - // Next get the absolute values of these entries and store in absbox2ToBox1. - matrix3x4_t absBox2ToBox1; - ComputeAbsMatrix( box2ToBox1, absBox2ToBox1 ); - - // There are 15 tests to make. The first 3 involve trying planes parallel - // to the faces of the first box. - - // NOTE: The algorithm here involves finding the projections of the two boxes - // onto a particular line. If the projections on the line do not overlap, - // that means that there's a plane perpendicular to the line which separates - // the two boxes; and we've therefore found a separating plane. - - // The way we check for overlay is we find the projections of the two boxes - // onto the line, and add them up. We compare the sum with the projection - // of the relative center of box2 onto the same line. - - Vector tmp; - float boxProjectionSum; - float originProjection; - - // NOTE: For these guys, we're taking advantage of the fact that the ith - // row of the box2ToBox1 is the direction of the box1 (x,y,z)-axis - // transformed into the space of box2. - - // First side of box 1 - boxProjectionSum = box1Size.x + MatrixRowDotProduct( absBox2ToBox1, 0, box2Size ); - originProjection = FloatMakePositive( box2Origin.x ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - VectorCopy( worldToBox1[0], pPlane->normal.Base() ); - return true; - } - - // Second side of box 1 - boxProjectionSum = box1Size.y + MatrixRowDotProduct( absBox2ToBox1, 1, box2Size ); - originProjection = FloatMakePositive( box2Origin.y ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - VectorCopy( worldToBox1[1], pPlane->normal.Base() ); - return true; - } - - // Third side of box 1 - boxProjectionSum = box1Size.z + MatrixRowDotProduct( absBox2ToBox1, 2, box2Size ); - originProjection = FloatMakePositive( box2Origin.z ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - VectorCopy( worldToBox1[2], pPlane->normal.Base() ); - return true; - } - - // The next three involve checking splitting planes parallel to the - // faces of the second box. - - // NOTE: For these guys, we're taking advantage of the fact that the 0th - // column of the box2ToBox1 is the direction of the box2 x-axis - // transformed into the space of box1. - // Here, we're determining the distance of box2's center from box1's center - // by projecting it onto a line parallel to box2's axis - - // First side of box 2 - boxProjectionSum = box2Size.x + MatrixColumnDotProduct( absBox2ToBox1, 0, box1Size ); - originProjection = FloatMakePositive( MatrixColumnDotProduct( box2ToBox1, 0, box2Origin ) ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 0, pPlane->normal ); - return true; - } - - // Second side of box 2 - boxProjectionSum = box2Size.y + MatrixColumnDotProduct( absBox2ToBox1, 1, box1Size ); - originProjection = FloatMakePositive( MatrixColumnDotProduct( box2ToBox1, 1, box2Origin ) ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 1, pPlane->normal ); - return true; - } - - // Third side of box 2 - boxProjectionSum = box2Size.z + MatrixColumnDotProduct( absBox2ToBox1, 2, box1Size ); - originProjection = FloatMakePositive( MatrixColumnDotProduct( box2ToBox1, 2, box2Origin ) ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 2, pPlane->normal ); - return true; - } - - // Next check the splitting planes which are orthogonal to the pairs - // of edges, one from box1 and one from box2. As only direction matters, - // there are 9 pairs since each box has 3 distinct edge directions. - - // Here, we take advantage of the fact that the edges from box 1 are all - // axis aligned; therefore the crossproducts are simplified. Let's walk through - // the example of b1e1 x b2e1: - - // In this example, the line to check is perpendicular to b1e1 + b2e2 - // we can compute this line by taking the cross-product: - // - // [ i j k ] - // [ 1 0 0 ] = - ez j + ey k = l1 - // [ ex ey ez ] - - // Where ex, ey, ez is the components of box2's x axis in the space of box 1, - // which is == to the 0th column of of box2toBox1 - - // The projection of box1 onto this line = the absolute dot product of the box size - // against the line, which = - // AbsDot( box1Size, l1 ) = abs( -ez * box1.y ) + abs( ey * box1.z ) - - // To compute the projection of box2 onto this line, we'll do it in the space of box 2 - // - // [ i j k ] - // [ fx fy fz ] = fz j - fy k = l2 - // [ 1 0 0 ] - - // Where fx, fy, fz is the components of box1's x axis in the space of box 2, - // which is == to the 0th row of of box2toBox1 - - // The projection of box2 onto this line = the absolute dot product of the box size - // against the line, which = - // AbsDot( box2Size, l2 ) = abs( fz * box2.y ) + abs ( fy * box2.z ) - - // The projection of the relative origin position on this line is done in the - // space of box 1: - // - // originProjection = DotProduct( <-ez j + ey k>, box2Origin ) = - // -ez * box2Origin.y + ey * box2Origin.z - - // b1e1 x b2e1 - boxProjectionSum = - box1Size.y * absBox2ToBox1[2][0] + box1Size.z * absBox2ToBox1[1][0] + - box2Size.y * absBox2ToBox1[0][2] + box2Size.z * absBox2ToBox1[0][1]; - originProjection = FloatMakePositive( -box2Origin.y * box2ToBox1[2][0] + box2Origin.z * box2ToBox1[1][0] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 0, tmp ); - CrossProduct( worldToBox1[0], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - // b1e1 x b2e2 - boxProjectionSum = - box1Size.y * absBox2ToBox1[2][1] + box1Size.z * absBox2ToBox1[1][1] + - box2Size.x * absBox2ToBox1[0][2] + box2Size.z * absBox2ToBox1[0][0]; - originProjection = FloatMakePositive( -box2Origin.y * box2ToBox1[2][1] + box2Origin.z * box2ToBox1[1][1] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 1, tmp ); - CrossProduct( worldToBox1[0], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - // b1e1 x b2e3 - boxProjectionSum = - box1Size.y * absBox2ToBox1[2][2] + box1Size.z * absBox2ToBox1[1][2] + - box2Size.x * absBox2ToBox1[0][1] + box2Size.y * absBox2ToBox1[0][0]; - originProjection = FloatMakePositive( -box2Origin.y * box2ToBox1[2][2] + box2Origin.z * box2ToBox1[1][2] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 2, tmp ); - CrossProduct( worldToBox1[0], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - // b1e2 x b2e1 - boxProjectionSum = - box1Size.x * absBox2ToBox1[2][0] + box1Size.z * absBox2ToBox1[0][0] + - box2Size.y * absBox2ToBox1[1][2] + box2Size.z * absBox2ToBox1[1][1]; - originProjection = FloatMakePositive( box2Origin.x * box2ToBox1[2][0] - box2Origin.z * box2ToBox1[0][0] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 0, tmp ); - CrossProduct( worldToBox1[1], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - // b1e2 x b2e2 - boxProjectionSum = - box1Size.x * absBox2ToBox1[2][1] + box1Size.z * absBox2ToBox1[0][1] + - box2Size.x * absBox2ToBox1[1][2] + box2Size.z * absBox2ToBox1[1][0]; - originProjection = FloatMakePositive( box2Origin.x * box2ToBox1[2][1] - box2Origin.z * box2ToBox1[0][1] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 1, tmp ); - CrossProduct( worldToBox1[1], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - // b1e2 x b2e3 - boxProjectionSum = - box1Size.x * absBox2ToBox1[2][2] + box1Size.z * absBox2ToBox1[0][2] + - box2Size.x * absBox2ToBox1[1][1] + box2Size.y * absBox2ToBox1[1][0]; - originProjection = FloatMakePositive( box2Origin.x * box2ToBox1[2][2] - box2Origin.z * box2ToBox1[0][2] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 2, tmp ); - CrossProduct( worldToBox1[1], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - // b1e3 x b2e1 - boxProjectionSum = - box1Size.x * absBox2ToBox1[1][0] + box1Size.y * absBox2ToBox1[0][0] + - box2Size.y * absBox2ToBox1[2][2] + box2Size.z * absBox2ToBox1[2][1]; - originProjection = FloatMakePositive( -box2Origin.x * box2ToBox1[1][0] + box2Origin.y * box2ToBox1[0][0] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 0, tmp ); - CrossProduct( worldToBox1[2], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - // b1e3 x b2e2 - boxProjectionSum = - box1Size.x * absBox2ToBox1[1][1] + box1Size.y * absBox2ToBox1[0][1] + - box2Size.x * absBox2ToBox1[2][2] + box2Size.z * absBox2ToBox1[2][0]; - originProjection = FloatMakePositive( -box2Origin.x * box2ToBox1[1][1] + box2Origin.y * box2ToBox1[0][1] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 1, tmp ); - CrossProduct( worldToBox1[2], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - // b1e3 x b2e3 - boxProjectionSum = - box1Size.x * absBox2ToBox1[1][2] + box1Size.y * absBox2ToBox1[0][2] + - box2Size.x * absBox2ToBox1[2][1] + box2Size.y * absBox2ToBox1[2][0]; - originProjection = FloatMakePositive( -box2Origin.x * box2ToBox1[1][2] + box2Origin.y * box2ToBox1[0][2] ) + tolerance; - if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) - { - MatrixGetColumn( box2ToWorld, 2, tmp ); - CrossProduct( worldToBox1[2], tmp.Base(), pPlane->normal.Base() ); - return true; - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Compute a separating plane between two boxes (expensive!) -// Returns false if no separating plane exists -//----------------------------------------------------------------------------- -bool ComputeSeparatingPlane( const Vector& org1, const QAngle& angles1, const Vector& min1, const Vector& max1, - const Vector& org2, const QAngle& angles2, const Vector& min2, const Vector& max2, - float tolerance, cplane_t* pPlane ) -{ - matrix3x4_t worldToBox1, box2ToWorld; - ComputeCenterIMatrix( org1, angles1, min1, max1, worldToBox1 ); - ComputeCenterMatrix( org2, angles2, min2, max2, box2ToWorld ); - - // Then compute the size of the two boxes - Vector box1Size, box2Size; - VectorSubtract( max1, min1, box1Size ); - VectorSubtract( max2, min2, box2Size ); - box1Size *= 0.5f; - box2Size *= 0.5f; - - return ComputeSeparatingPlane( worldToBox1, box2ToWorld, box1Size, box2Size, tolerance, pPlane ); -} - - -//----------------------------------------------------------------------------- -// Swept OBB test -//----------------------------------------------------------------------------- -bool IsRayIntersectingOBB( const Ray_t &ray, const Vector& org, const QAngle& angles, - const Vector& mins, const Vector& maxs ) -{ - if ( angles == vec3_angle ) - { - Vector vecWorldMins, vecWorldMaxs; - VectorAdd( org, mins, vecWorldMins ); - VectorAdd( org, maxs, vecWorldMaxs ); - return IsBoxIntersectingRay( vecWorldMins, vecWorldMaxs, ray ); - } - - if ( ray.m_IsRay ) - { - matrix3x4_t worldToBox; - AngleIMatrix( angles, org, worldToBox ); - - Ray_t rotatedRay; - VectorTransform( ray.m_Start, worldToBox, rotatedRay.m_Start ); - VectorRotate( ray.m_Delta, worldToBox, rotatedRay.m_Delta ); - rotatedRay.m_StartOffset = vec3_origin; - rotatedRay.m_Extents = vec3_origin; - rotatedRay.m_IsRay = ray.m_IsRay; - rotatedRay.m_IsSwept = ray.m_IsSwept; - - return IsBoxIntersectingRay( mins, maxs, rotatedRay ); - } - - if ( !ray.m_IsSwept ) - { - cplane_t plane; - return ComputeSeparatingPlane( ray.m_Start, vec3_angle, -ray.m_Extents, ray.m_Extents, - org, angles, mins, maxs, 0.0f, &plane ) == false; - } - - // NOTE: See the comments in ComputeSeparatingPlane to understand this math - - // First, compute the basis of box in the space of the ray - // NOTE: These basis place the origin at the centroid of each box! - matrix3x4_t worldToBox1, box2ToWorld; - ComputeCenterMatrix( org, angles, mins, maxs, box2ToWorld ); - - // Find the center + extents of an AABB surrounding the ray - Vector vecRayCenter; - VectorMA( ray.m_Start, 0.5, ray.m_Delta, vecRayCenter ); - vecRayCenter *= -1.0f; - SetIdentityMatrix( worldToBox1 ); - MatrixSetColumn( vecRayCenter, 3, worldToBox1 ); - - Vector box1Size; - box1Size.x = ray.m_Extents.x + FloatMakePositive( ray.m_Delta.x ) * 0.5f; - box1Size.y = ray.m_Extents.y + FloatMakePositive( ray.m_Delta.y ) * 0.5f; - box1Size.z = ray.m_Extents.z + FloatMakePositive( ray.m_Delta.z ) * 0.5f; - - // Then compute the size of the box - Vector box2Size; - VectorSubtract( maxs, mins, box2Size ); - box2Size *= 0.5f; - - // Do an OBB test of the box with the AABB surrounding the ray - cplane_t plane; - if ( ComputeSeparatingPlane( worldToBox1, box2ToWorld, box1Size, box2Size, 0.0f, &plane ) ) - return false; - - // Now deal with the planes which are the cross products of the ray sweep direction vs box edges - Vector vecRayDirection = ray.m_Delta; - VectorNormalize( vecRayDirection ); - - // Need a vector between ray center vs box center measured in the space of the ray (world) - Vector vecCenterDelta; - vecCenterDelta.x = box2ToWorld[0][3] - ray.m_Start.x; - vecCenterDelta.y = box2ToWorld[1][3] - ray.m_Start.y; - vecCenterDelta.z = box2ToWorld[2][3] - ray.m_Start.z; - - // Rotate the ray direction into the space of the OBB - Vector vecAbsRayDirBox2; - VectorIRotate( vecRayDirection, box2ToWorld, vecAbsRayDirBox2 ); - - // Make abs versions of the ray in world space + ray in box2 space - VectorAbs( vecAbsRayDirBox2, vecAbsRayDirBox2 ); - - // Now do the work for the planes which are perpendicular to the edges of the AABB - // and the sweep direction edges... - - // In this example, the line to check is perpendicular to box edge x + ray delta - // we can compute this line by taking the cross-product: - // - // [ i j k ] - // [ 1 0 0 ] = - dz j + dy k = l1 - // [ dx dy dz ] - - // Where dx, dy, dz is the ray delta (normalized) - - // The projection of the box onto this line = the absolute dot product of the box size - // against the line, which = - // AbsDot( vecBoxHalfDiagonal, l1 ) = abs( -dz * vecBoxHalfDiagonal.y ) + abs( dy * vecBoxHalfDiagonal.z ) - - // Because the plane contains the sweep direction, the sweep will produce - // no extra projection onto the line normal to the plane. - // Therefore all we need to do is project the ray extents onto this line also: - // AbsDot( ray.m_Extents, l1 ) = abs( -dz * ray.m_Extents.y ) + abs( dy * ray.m_Extents.z ) - - Vector vecPlaneNormal; - - // box x x ray delta - CrossProduct( vecRayDirection, Vector( box2ToWorld[0][0], box2ToWorld[1][0], box2ToWorld[2][0] ), vecPlaneNormal ); - float flCenterDeltaProjection = FloatMakePositive( DotProduct( vecPlaneNormal, vecCenterDelta ) ); - float flBoxProjectionSum = - vecAbsRayDirBox2.z * box2Size.y + vecAbsRayDirBox2.y * box2Size.z + - DotProductAbs( vecPlaneNormal, ray.m_Extents ); - if ( FloatBits(flCenterDeltaProjection) > FloatBits(flBoxProjectionSum) ) - return false; - - // box y x ray delta - CrossProduct( vecRayDirection, Vector( box2ToWorld[0][1], box2ToWorld[1][1], box2ToWorld[2][1] ), vecPlaneNormal ); - flCenterDeltaProjection = FloatMakePositive( DotProduct( vecPlaneNormal, vecCenterDelta ) ); - flBoxProjectionSum = - vecAbsRayDirBox2.z * box2Size.x + vecAbsRayDirBox2.x * box2Size.z + - DotProductAbs( vecPlaneNormal, ray.m_Extents ); - if ( FloatBits(flCenterDeltaProjection) > FloatBits(flBoxProjectionSum) ) - return false; - - // box z x ray delta - CrossProduct( vecRayDirection, Vector( box2ToWorld[0][2], box2ToWorld[1][2], box2ToWorld[2][2] ), vecPlaneNormal ); - flCenterDeltaProjection = FloatMakePositive( DotProduct( vecPlaneNormal, vecCenterDelta ) ); - flBoxProjectionSum = - vecAbsRayDirBox2.y * box2Size.x + vecAbsRayDirBox2.x * box2Size.y + - DotProductAbs( vecPlaneNormal, ray.m_Extents ); - if ( FloatBits(flCenterDeltaProjection) > FloatBits(flBoxProjectionSum) ) - return false; - - return true; -} - -//-------------------------------------------------------------------------- -// Purpose: -// -// NOTE: -// triangle points are given in clockwise order (aabb-triangle test) -// -// 1 edge0 = 1 - 0 -// | \ edge1 = 2 - 1 -// | \ edge2 = 0 - 2 -// | \ -// | \ -// 0-----2 -// -//-------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// Purpose: find the minima and maxima of the 3 given values -//----------------------------------------------------------------------------- -inline void FindMinMax( float v1, float v2, float v3, float &min, float &max ) -{ - min = max = v1; - if ( v2 < min ) { min = v2; } - if ( v2 > max ) { max = v2; } - if ( v3 < min ) { min = v3; } - if ( v3 > max ) { max = v3; } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline bool AxisTestEdgeCrossX2( float flEdgeZ, float flEdgeY, float flAbsEdgeZ, float flAbsEdgeY, - const Vector &p1, const Vector &p3, const Vector &vecExtents, - float flTolerance ) -{ - // Cross Product( axialX(1,0,0) x edge ): x = 0.0f, y = edge.z, z = -edge.y - // Triangle Point Distances: dist(x) = normal.y * pt(x).y + normal.z * pt(x).z - float flDist1 = flEdgeZ * p1.y - flEdgeY * p1.z; - float flDist3 = flEdgeZ * p3.y - flEdgeY * p3.z; - - // Extents are symmetric: dist = abs( normal.y ) * extents.y + abs( normal.z ) * extents.z - float flDistBox = flAbsEdgeZ * vecExtents.y + flAbsEdgeY * vecExtents.z; - - // Either dist1, dist3 is the closest point to the box, determine which and test of overlap with box(AABB). - if ( flDist1 < flDist3 ) - { - if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist3 < -( flDistBox + flTolerance ) ) ) - return false; - } - else - { - if ( ( flDist3 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) - return false; - } - - return true; -} - -//-------------------------------------------------------------------------- -// Purpose: -//-------------------------------------------------------------------------- -inline bool AxisTestEdgeCrossX3( float flEdgeZ, float flEdgeY, float flAbsEdgeZ, float flAbsEdgeY, - const Vector &p1, const Vector &p2, const Vector &vecExtents, - float flTolerance ) -{ - // Cross Product( axialX(1,0,0) x edge ): x = 0.0f, y = edge.z, z = -edge.y - // Triangle Point Distances: dist(x) = normal.y * pt(x).y + normal.z * pt(x).z - float flDist1 = flEdgeZ * p1.y - flEdgeY * p1.z; - float flDist2 = flEdgeZ * p2.y - flEdgeY * p2.z; - - // Extents are symmetric: dist = abs( normal.y ) * extents.y + abs( normal.z ) * extents.z - float flDistBox = flAbsEdgeZ * vecExtents.y + flAbsEdgeY * vecExtents.z; - - // Either dist1, dist2 is the closest point to the box, determine which and test of overlap with box(AABB). - if ( flDist1 < flDist2 ) - { - if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist2 < -( flDistBox + flTolerance ) ) ) - return false; - } - else - { - if ( ( flDist2 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) - return false; - } - - return true; -} - -//-------------------------------------------------------------------------- -//-------------------------------------------------------------------------- -inline bool AxisTestEdgeCrossY2( float flEdgeZ, float flEdgeX, float flAbsEdgeZ, float flAbsEdgeX, - const Vector &p1, const Vector &p3, const Vector &vecExtents, - float flTolerance ) -{ - // Cross Product( axialY(0,1,0) x edge ): x = -edge.z, y = 0.0f, z = edge.x - // Triangle Point Distances: dist(x) = normal.x * pt(x).x + normal.z * pt(x).z - float flDist1 = -flEdgeZ * p1.x + flEdgeX * p1.z; - float flDist3 = -flEdgeZ * p3.x + flEdgeX * p3.z; - - // Extents are symmetric: dist = abs( normal.x ) * extents.x + abs( normal.z ) * extents.z - float flDistBox = flAbsEdgeZ * vecExtents.x + flAbsEdgeX * vecExtents.z; - - // Either dist1, dist3 is the closest point to the box, determine which and test of overlap with box(AABB). - if ( flDist1 < flDist3 ) - { - if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist3 < -( flDistBox + flTolerance ) ) ) - return false; - } - else - { - if ( ( flDist3 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) - return false; - } - - return true; -} - -//-------------------------------------------------------------------------- -//-------------------------------------------------------------------------- -inline bool AxisTestEdgeCrossY3( float flEdgeZ, float flEdgeX, float flAbsEdgeZ, float flAbsEdgeX, - const Vector &p1, const Vector &p2, const Vector &vecExtents, - float flTolerance ) -{ - // Cross Product( axialY(0,1,0) x edge ): x = -edge.z, y = 0.0f, z = edge.x - // Triangle Point Distances: dist(x) = normal.x * pt(x).x + normal.z * pt(x).z - float flDist1 = -flEdgeZ * p1.x + flEdgeX * p1.z; - float flDist2 = -flEdgeZ * p2.x + flEdgeX * p2.z; - - // Extents are symmetric: dist = abs( normal.x ) * extents.x + abs( normal.z ) * extents.z - float flDistBox = flAbsEdgeZ * vecExtents.x + flAbsEdgeX * vecExtents.z; - - // Either dist1, dist2 is the closest point to the box, determine which and test of overlap with box(AABB). - if ( flDist1 < flDist2 ) - { - if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist2 < -( flDistBox + flTolerance ) ) ) - return false; - } - else - { - if ( ( flDist2 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) - return false; - } - - return true; -} - -//-------------------------------------------------------------------------- -//-------------------------------------------------------------------------- -inline bool AxisTestEdgeCrossZ1( float flEdgeY, float flEdgeX, float flAbsEdgeY, float flAbsEdgeX, - const Vector &p2, const Vector &p3, const Vector &vecExtents, - float flTolerance ) -{ - // Cross Product( axialZ(0,0,1) x edge ): x = edge.y, y = -edge.x, z = 0.0f - // Triangle Point Distances: dist(x) = normal.x * pt(x).x + normal.y * pt(x).y - float flDist2 = flEdgeY * p2.x - flEdgeX * p2.y; - float flDist3 = flEdgeY * p3.x - flEdgeX * p3.y; - - // Extents are symmetric: dist = abs( normal.x ) * extents.x + abs( normal.y ) * extents.y - float flDistBox = flAbsEdgeY * vecExtents.x + flAbsEdgeX * vecExtents.y; - - // Either dist2, dist3 is the closest point to the box, determine which and test of overlap with box(AABB). - if ( flDist3 < flDist2 ) - { - if ( ( flDist3 > ( flDistBox + flTolerance ) ) || ( flDist2 < -( flDistBox + flTolerance ) ) ) - return false; - } - else - { - if ( ( flDist2 > ( flDistBox + flTolerance ) ) || ( flDist3 < -( flDistBox + flTolerance ) ) ) - return false; - } - - return true; -} - -//-------------------------------------------------------------------------- -//-------------------------------------------------------------------------- -inline bool AxisTestEdgeCrossZ2( float flEdgeY, float flEdgeX, float flAbsEdgeY, float flAbsEdgeX, - const Vector &p1, const Vector &p3, const Vector &vecExtents, - float flTolerance ) -{ - // Cross Product( axialZ(0,0,1) x edge ): x = edge.y, y = -edge.x, z = 0.0f - // Triangle Point Distances: dist(x) = normal.x * pt(x).x + normal.y * pt(x).y - float flDist1 = flEdgeY * p1.x - flEdgeX * p1.y; - float flDist3 = flEdgeY * p3.x - flEdgeX * p3.y; - - // Extents are symmetric: dist = abs( normal.x ) * extents.x + abs( normal.y ) * extents.y - float flDistBox = flAbsEdgeY * vecExtents.x + flAbsEdgeX * vecExtents.y; - - // Either dist1, dist3 is the closest point to the box, determine which and test of overlap with box(AABB). - if ( flDist1 < flDist3 ) - { - if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist3 < -( flDistBox + flTolerance ) ) ) - return false; - } - else - { - if ( ( flDist3 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Test for an intersection (overlap) between an axial-aligned bounding -// box (AABB) and a triangle. -// -// Using the "Separating-Axis Theorem" to test for intersections between -// a triangle and an axial-aligned bounding box (AABB). -// 1. 3 Axis Planes - x, y, z -// 2. 9 Edge Planes Tests - the 3 edges of the triangle crossed with all 3 axial -// planes (x, y, z) -// 3. 1 Face Plane - the triangle plane (cplane_t plane below) -// Output: false = separating axis (no intersection) -// true = intersection -//----------------------------------------------------------------------------- -bool IsBoxIntersectingTriangle( const Vector &vecBoxCenter, const Vector &vecBoxExtents, - const Vector &v1, const Vector &v2, const Vector &v3, - const cplane_t &plane, float flTolerance ) -{ - // Test the axial planes (x,y,z) against the min, max of the triangle. - float flMin, flMax; - Vector p1, p2, p3; - - // x plane - p1.x = v1.x - vecBoxCenter.x; - p2.x = v2.x - vecBoxCenter.x; - p3.x = v3.x - vecBoxCenter.x; - FindMinMax( p1.x, p2.x, p3.x, flMin, flMax ); - if ( ( flMin > ( vecBoxExtents.x + flTolerance ) ) || ( flMax < -( vecBoxExtents.x + flTolerance ) ) ) - return false; - - // y plane - p1.y = v1.y - vecBoxCenter.y; - p2.y = v2.y - vecBoxCenter.y; - p3.y = v3.y - vecBoxCenter.y; - FindMinMax( p1.y, p2.y, p3.y, flMin, flMax ); - if ( ( flMin > ( vecBoxExtents.y + flTolerance ) ) || ( flMax < -( vecBoxExtents.y + flTolerance ) ) ) - return false; - - // z plane - p1.z = v1.z - vecBoxCenter.z; - p2.z = v2.z - vecBoxCenter.z; - p3.z = v3.z - vecBoxCenter.z; - FindMinMax( p1.z, p2.z, p3.z, flMin, flMax ); - if ( ( flMin > ( vecBoxExtents.z + flTolerance ) ) || ( flMax < -( vecBoxExtents.z + flTolerance ) ) ) - return false; - - // Test the 9 edge cases. - Vector vecEdge, vecAbsEdge; - - // edge 0 (cross x,y,z) - vecEdge = p2 - p1; - vecAbsEdge.y = FloatMakePositive( vecEdge.y ); - vecAbsEdge.z = FloatMakePositive( vecEdge.z ); - if ( !AxisTestEdgeCrossX2( vecEdge.z, vecEdge.y, vecAbsEdge.z, vecAbsEdge.y, p1, p3, vecBoxExtents, flTolerance ) ) - return false; - - vecAbsEdge.x = FloatMakePositive( vecEdge.x ); - if ( !AxisTestEdgeCrossY2( vecEdge.z, vecEdge.x, vecAbsEdge.z, vecAbsEdge.x, p1, p3, vecBoxExtents, flTolerance ) ) - return false; - - if ( !AxisTestEdgeCrossZ1( vecEdge.y, vecEdge.x, vecAbsEdge.y, vecAbsEdge.x, p2, p3, vecBoxExtents, flTolerance ) ) - return false; - - // edge 1 (cross x,y,z) - vecEdge = p3 - p2; - vecAbsEdge.y = FloatMakePositive( vecEdge.y ); - vecAbsEdge.z = FloatMakePositive( vecEdge.z ); - if ( !AxisTestEdgeCrossX2( vecEdge.z, vecEdge.y, vecAbsEdge.z, vecAbsEdge.y, p1, p2, vecBoxExtents, flTolerance ) ) - return false; - - vecAbsEdge.x = FloatMakePositive( vecEdge.x ); - if ( !AxisTestEdgeCrossY2( vecEdge.z, vecEdge.x, vecAbsEdge.z, vecAbsEdge.x, p1, p2, vecBoxExtents, flTolerance ) ) - return false; - - if ( !AxisTestEdgeCrossZ2( vecEdge.y, vecEdge.x, vecAbsEdge.y, vecAbsEdge.x, p1, p3, vecBoxExtents, flTolerance ) ) - return false; - - // edge 2 (cross x,y,z) - vecEdge = p1 - p3; - vecAbsEdge.y = FloatMakePositive( vecEdge.y ); - vecAbsEdge.z = FloatMakePositive( vecEdge.z ); - if ( !AxisTestEdgeCrossX3( vecEdge.z, vecEdge.y, vecAbsEdge.z, vecAbsEdge.y, p1, p2, vecBoxExtents, flTolerance ) ) - return false; - - vecAbsEdge.x = FloatMakePositive( vecEdge.x ); - if ( !AxisTestEdgeCrossY3( vecEdge.z, vecEdge.x, vecAbsEdge.z, vecAbsEdge.x, p1, p2, vecBoxExtents, flTolerance ) ) - return false; - - if ( !AxisTestEdgeCrossZ1( vecEdge.y, vecEdge.x, vecAbsEdge.y, vecAbsEdge.x, p2, p3, vecBoxExtents, flTolerance ) ) - return false; - - // Test against the triangle face plane. - Vector vecMin, vecMax; - VectorSubtract( vecBoxCenter, vecBoxExtents, vecMin ); - VectorAdd( vecBoxCenter, vecBoxExtents, vecMax ); - if ( BoxOnPlaneSide( vecMin, vecMax, &plane ) != 3 ) - return false; - - return true; -} - -// NOTE: JAY: This is untested code based on Real-time Collision Detection by Ericson -#if 0 -Vector CalcClosestPointOnTriangle( const Vector &P, const Vector &v0, const Vector &v1, const Vector &v2 ) -{ - Vector e0 = v1 - v0; - Vector e1 = v2 - v0; - Vector p0 = P - v0; - - // voronoi region of v0 - float d1 = DotProduct( e0, p0 ); - float d2 = DotProduct( e1, p0 ); - if (d1 <= 0.0f && d2 <= 0.0f) - return v0; - - // voronoi region of v1 - Vector p1 = P - v1; - float d3 = DotProduct( e0, p1 ); - float d4 = DotProduct( e1, p1 ); - if (d3 >=0.0f && d4 <= d3) - return v1; - - // voronoi region of e0 (v0-v1) - float ve2 = d1*d4 - d3*d2; - if ( ve2 <= 0.0f && d1 >= 0.0f && d3 <= 0.0f ) - { - float v = d1 / (d1-d3); - return v0 + v * e0; - } - // voronoi region of v2 - Vector p2 = P - v2; - float d5 = DotProduct( e0, p2 ); - float d6 = DotProduct( e1, p2 ); - if (d6 >= 0.0f && d5 <= d6) - return v2; - // voronoi region of e1 - float ve1 = d5*d2 - d1*d6; - if (ve1 <= 0.0f && d2 >= 0.0f && d6 >= 0.0f) - { - float w = d2 / (d2-d6); - return v0 + w * e1; - } - // voronoi region on e2 - float ve0 = d3*d6 - d5*d4; - if ( ve0 <= 0.0f && (d4-d3) >= 0.0f && (d5-d6) >= 0.0f ) - { - float w = (d4-d3)/((d4-d3) + (d5-d6)); - return v1 + w * (v2-v1); - } - // voronoi region of v0v1v2 triangle - float denom = 1.0f / (ve0+ve1+ve2); - float v = ve1*denom; - float w = ve2 * denom; - return v0 + e0 * v + e1 * w; -} -#endif - -#endif // !_STATIC_LINKED || _SHARED_LIB \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Common collision utility methods +// +// $Header: $ +// $NoKeywords: $ +//=============================================================================// + +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + +#include "collisionutils.h" +#include "cmodel.h" +#include "mathlib.h" +#include "vector.h" +#include "tier0/dbg.h" +#include +#include "vector4d.h" +#include "trace.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define UNINIT -99999.0 + +//----------------------------------------------------------------------------- +// Clears the trace +//----------------------------------------------------------------------------- +static void Collision_ClearTrace( const Vector &vecRayStart, const Vector &vecRayDelta, CBaseTrace *pTrace ) +{ + pTrace->startpos = vecRayStart; + pTrace->endpos = vecRayStart; + pTrace->endpos += vecRayDelta; + pTrace->startsolid = false; + pTrace->allsolid = false; + pTrace->fraction = 1.0f; + pTrace->contents = 0; +} + + +//----------------------------------------------------------------------------- +// Compute the offset in t along the ray that we'll use for the collision +//----------------------------------------------------------------------------- +static float ComputeBoxOffset( const Ray_t& ray ) +{ + if (ray.m_IsRay) + return 1e-3f; + + // Find the projection of the box diagonal along the ray... + float offset = FloatMakePositive(ray.m_Extents[0] * ray.m_Delta[0]) + + FloatMakePositive(ray.m_Extents[1] * ray.m_Delta[1]) + + FloatMakePositive(ray.m_Extents[2] * ray.m_Delta[2]); + + // We need to divide twice: Once to normalize the computation above + // so we get something in units of extents, and the second to normalize + // that with respect to the entire raycast. + offset *= InvRSquared( ray.m_Delta ); + + // 1e-3 is an epsilon + return offset + 1e-3; +} + + +//----------------------------------------------------------------------------- +// Intersects a swept box against a triangle +//----------------------------------------------------------------------------- +float IntersectRayWithTriangle( const Ray_t& ray, + const Vector& v1, const Vector& v2, const Vector& v3, bool oneSided ) +{ + // This is cute: Use barycentric coordinates to represent the triangle + // Vo(1-u-v) + V1u + V2v and intersect that with a line Po + Dt + // This gives us 3 equations + 3 unknowns, which we can solve with + // Cramer's rule... + // E1x u + E2x v - Dx t = Pox - Vox + // There's a couple of other optimizations, Cramer's rule involves + // computing the determinant of a matrix which has been constructed + // by three vectors. It turns out that + // det | A B C | = -( A x C ) dot B or -(C x B) dot A + // which we'll use below.. + + Vector edge1, edge2, org; + VectorSubtract( v2, v1, edge1 ); + VectorSubtract( v3, v1, edge2 ); + + // Cull out one-sided stuff + if (oneSided) + { + Vector normal; + CrossProduct( edge1, edge2, normal ); + if (DotProduct( normal, ray.m_Delta ) >= 0.0f) + return -1.0f; + } + + // FIXME: This is inaccurate, but fast for boxes + // We want to do a fast separating axis implementation here + // with a swept triangle along the reverse direction of the ray. + + // Compute some intermediary terms + Vector dirCrossEdge2, orgCrossEdge1; + CrossProduct( ray.m_Delta, edge2, dirCrossEdge2 ); + + // Compute the denominator of Cramer's rule: + // | -Dx E1x E2x | + // det | -Dy E1y E2y | = (D x E2) dot E1 + // | -Dz E1z E2z | + float denom = DotProduct( dirCrossEdge2, edge1 ); + if( FloatMakePositive( denom ) < 1e-6 ) + return -1.0f; + denom = 1.0f / denom; + + // Compute u. It's gotta lie in the range of 0 to 1. + // | -Dx orgx E2x | + // u = denom * det | -Dy orgy E2y | = (D x E2) dot org + // | -Dz orgz E2z | + VectorSubtract( ray.m_Start, v1, org ); + float u = DotProduct( dirCrossEdge2, org ) * denom; + if ((u < 0.0f) || (u > 1.0f)) + return -1.0f; + + // Compute t and v the same way... + // In barycentric coords, u + v < 1 + CrossProduct( org, edge1, orgCrossEdge1 ); + float v = DotProduct( orgCrossEdge1, ray.m_Delta ) * denom; + if ((v < 0.0f) || (v + u > 1.0f)) + return -1.0f; + + // Compute the distance along the ray direction that we need to fudge + // when using swept boxes + float boxt = ComputeBoxOffset( ray ); + float t = DotProduct( orgCrossEdge1, edge2 ) * denom; + if ((t < -boxt) || (t > 1.0f + boxt)) + return -1.0f; + + return clamp( t, 0, 1 ); +} + +//----------------------------------------------------------------------------- +// computes the barycentric coordinates of an intersection +//----------------------------------------------------------------------------- + +bool ComputeIntersectionBarycentricCoordinates( const Ray_t& ray, + const Vector& v1, const Vector& v2, const Vector& v3, float& u, float& v, + float *t ) +{ + Vector edge1, edge2, org; + VectorSubtract( v2, v1, edge1 ); + VectorSubtract( v3, v1, edge2 ); + + // Compute some intermediary terms + Vector dirCrossEdge2, orgCrossEdge1; + CrossProduct( ray.m_Delta, edge2, dirCrossEdge2 ); + + // Compute the denominator of Cramer's rule: + // | -Dx E1x E2x | + // det | -Dy E1y E2y | = (D x E2) dot E1 + // | -Dz E1z E2z | + float denom = DotProduct( dirCrossEdge2, edge1 ); + if( FloatMakePositive( denom ) < 1e-6 ) + return false; + denom = 1.0f / denom; + + // Compute u. It's gotta lie in the range of 0 to 1. + // | -Dx orgx E2x | + // u = denom * det | -Dy orgy E2y | = (D x E2) dot org + // | -Dz orgz E2z | + VectorSubtract( ray.m_Start, v1, org ); + u = DotProduct( dirCrossEdge2, org ) * denom; + + // Compute t and v the same way... + // In barycentric coords, u + v < 1 + CrossProduct( org, edge1, orgCrossEdge1 ); + v = DotProduct( orgCrossEdge1, ray.m_Delta ) * denom; + + // Compute the distance along the ray direction that we need to fudge + // when using swept boxes + if( t ) + { + float boxt = ComputeBoxOffset( ray ); + *t = DotProduct( orgCrossEdge1, edge2 ) * denom; + if( ( *t < -boxt ) || ( *t > 1.0f + boxt ) ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Intersects a plane with a triangle (requires barycentric definition) +//----------------------------------------------------------------------------- + +int IntersectTriangleWithPlaneBarycentric( const Vector& org, const Vector& edgeU, + const Vector& edgeV, const Vector4D& plane, Vector2D* pIntersection ) +{ + // This uses a barycentric method, since we need that to determine + // interpolated points, alphas, and normals + // Given the plane equation P dot N + d = 0 + // and the barycentric coodinate equation P = Org + EdgeU * u + EdgeV * v + // Plug em in. Intersection occurs at u = 0 or v = 0 or u + v = 1 + + float orgDotNormal = DotProduct( org, plane.AsVector3D() ); + float edgeUDotNormal = DotProduct( edgeU, plane.AsVector3D() ); + float edgeVDotNormal = DotProduct( edgeV, plane.AsVector3D() ); + + int ptIdx = 0; + + // u = 0 + if ( edgeVDotNormal != 0.0f ) + { + pIntersection[ptIdx].x = 0.0f; + pIntersection[ptIdx].y = - ( orgDotNormal - plane.w ) / edgeVDotNormal; + if ((pIntersection[ptIdx].y >= 0.0f) && (pIntersection[ptIdx].y <= 1.0f)) + ++ptIdx; + } + + // v = 0 + if ( edgeUDotNormal != 0.0f ) + { + pIntersection[ptIdx].x = - ( orgDotNormal - plane.w ) / edgeUDotNormal; + pIntersection[ptIdx].y = 0.0f; + if ((pIntersection[ptIdx].x >= 0.0f) && (pIntersection[ptIdx].x <= 1.0f)) + ++ptIdx; + } + + // u + v = 1 + if (ptIdx == 2) + return ptIdx; + + if ( edgeVDotNormal != edgeUDotNormal ) + { + pIntersection[ptIdx].x = - ( orgDotNormal - plane.w + edgeVDotNormal) / + ( edgeUDotNormal - edgeVDotNormal); + pIntersection[ptIdx].y = 1.0f - pIntersection[ptIdx].x;; + if ((pIntersection[ptIdx].x >= 0.0f) && (pIntersection[ptIdx].x <= 1.0f) && + (pIntersection[ptIdx].y >= 0.0f) && (pIntersection[ptIdx].y <= 1.0f)) + ++ptIdx; + } + + Assert( ptIdx < 3 ); + return ptIdx; +} + + +//----------------------------------------------------------------------------- +// Returns true if a box intersects with a sphere +//----------------------------------------------------------------------------- +bool IsSphereIntersectingSphere( const Vector& center1, float radius1, + const Vector& center2, float radius2 ) +{ + Vector delta; + VectorSubtract( center2, center1, delta ); + float distSq = delta.LengthSqr(); + float radiusSum = radius1 + radius2; + return (distSq <= (radiusSum * radiusSum)); +} + + +//----------------------------------------------------------------------------- +// Returns true if a box intersects with a sphere +//----------------------------------------------------------------------------- +bool IsBoxIntersectingSphere( const Vector& boxMin, const Vector& boxMax, + const Vector& center, float radius ) +{ + // See Graphics Gems, box-sphere intersection + float dmin = 0.0f; + float flDelta; + + // Unrolled the loop.. this is a big cycle stealer... + if (center[0] < boxMin[0]) + { + flDelta = center[0] - boxMin[0]; + dmin += flDelta * flDelta; + } + else if (center[0] > boxMax[0]) + { + flDelta = boxMax[0] - center[0]; + dmin += flDelta * flDelta; + } + + if (center[1] < boxMin[1]) + { + flDelta = center[1] - boxMin[1]; + dmin += flDelta * flDelta; + } + else if (center[1] > boxMax[1]) + { + flDelta = boxMax[1] - center[1]; + dmin += flDelta * flDelta; + } + + if (center[2] < boxMin[2]) + { + flDelta = center[2] - boxMin[2]; + dmin += flDelta * flDelta; + } + else if (center[2] > boxMax[2]) + { + flDelta = boxMax[2] - center[2]; + dmin += flDelta * flDelta; + } + + return dmin < radius * radius; +} + +bool IsBoxIntersectingSphereExtents( const Vector& boxCenter, const Vector& boxHalfDiag, + const Vector& center, float radius ) +{ + // See Graphics Gems, box-sphere intersection + float dmin = 0.0f; + float flDelta, flDiff; + + // Unrolled the loop.. this is a big cycle stealer... + flDiff = FloatMakePositive( center.x - boxCenter.x ); + if (flDiff > boxHalfDiag.x) + { + flDelta = flDiff - boxHalfDiag.x; + dmin += flDelta * flDelta; + } + + flDiff = FloatMakePositive( center.y - boxCenter.y ); + if (flDiff > boxHalfDiag.y) + { + flDelta = flDiff - boxHalfDiag.y; + dmin += flDelta * flDelta; + } + + flDiff = FloatMakePositive( center.z - boxCenter.z ); + if (flDiff > boxHalfDiag.z) + { + flDelta = flDiff - boxHalfDiag.z; + dmin += flDelta * flDelta; + } + + return dmin < radius * radius; +} + + +//----------------------------------------------------------------------------- +// Returns true if a rectangle intersects with a circle +//----------------------------------------------------------------------------- +bool IsCircleIntersectingRectangle( const Vector2D& boxMin, const Vector2D& boxMax, + const Vector2D& center, float radius ) +{ + // See Graphics Gems, box-sphere intersection + float dmin = 0.0f; + float flDelta; + + if (center[0] < boxMin[0]) + { + flDelta = center[0] - boxMin[0]; + dmin += flDelta * flDelta; + } + else if (center[0] > boxMax[0]) + { + flDelta = boxMax[0] - center[0]; + dmin += flDelta * flDelta; + } + + if (center[1] < boxMin[1]) + { + flDelta = center[1] - boxMin[1]; + dmin += flDelta * flDelta; + } + else if (center[1] > boxMax[1]) + { + flDelta = boxMax[1] - center[1]; + dmin += flDelta * flDelta; + } + + return dmin < radius * radius; +} + + +//----------------------------------------------------------------------------- +// returns true if there's an intersection between ray and sphere +//----------------------------------------------------------------------------- +bool IsRayIntersectingSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, + const Vector& vecCenter, float flRadius, float flTolerance ) +{ + // For this algorithm, find a point on the ray which is closest to the sphere origin + // Do this by making a plane passing through the sphere origin + // whose normal is parallel to the ray. Intersect that plane with the ray. + // Plane: N dot P = I, N = D (ray direction), I = C dot N = C dot D + // Ray: P = O + D * t + // D dot ( O + D * t ) = C dot D + // D dot O + D dot D * t = C dot D + // t = (C - O) dot D / D dot D + // Clamp t to (0,1) + // Find distance of the point on the ray to the sphere center. + Assert( flTolerance >= 0.0f ); + flRadius += flTolerance; + + Vector vecRayToSphere; + VectorSubtract( vecCenter, vecRayOrigin, vecRayToSphere ); + float flNumerator = DotProduct( vecRayToSphere, vecRayDelta ); + + float t; + if (flNumerator <= 0.0f) + { + t = 0.0f; + } + else + { + float flDenominator = DotProduct( vecRayDelta, vecRayDelta ); + if ( flNumerator > flDenominator ) + t = 1.0f; + else + t = flNumerator / flDenominator; + } + + Vector vecClosestPoint; + VectorMA( vecRayOrigin, t, vecRayDelta, vecClosestPoint ); + return ( vecClosestPoint.DistToSqr( vecCenter ) <= flRadius * flRadius ); + + // NOTE: This in an alternate algorithm which I didn't use because I'd have to use a sqrt + // So it's probably faster to do this other algorithm. I'll leave the comments here + // for how to go back if we want to + + // Solve using the ray equation + the sphere equation + // P = o + dt + // (x - xc)^2 + (y - yc)^2 + (z - zc)^2 = r^2 + // (ox + dx * t - xc)^2 + (oy + dy * t - yc)^2 + (oz + dz * t - zc)^2 = r^2 + // (ox - xc)^2 + 2 * (ox-xc) * dx * t + dx^2 * t^2 + + // (oy - yc)^2 + 2 * (oy-yc) * dy * t + dy^2 * t^2 + + // (oz - zc)^2 + 2 * (oz-zc) * dz * t + dz^2 * t^2 = r^2 + // (dx^2 + dy^2 + dz^2) * t^2 + 2 * ((ox-xc)dx + (oy-yc)dy + (oz-zc)dz) t + + // (ox-xc)^2 + (oy-yc)^2 + (oz-zc)^2 - r^2 = 0 + // or, t = (-b +/- sqrt( b^2 - 4ac)) / 2a + // a = DotProduct( vecRayDelta, vecRayDelta ); + // b = 2 * DotProduct( vecRayOrigin - vecCenter, vecRayDelta ) + // c = DotProduct(vecRayOrigin - vecCenter, vecRayOrigin - vecCenter) - flRadius * flRadius; + // Valid solutions are possible only if b^2 - 4ac >= 0 + // Therefore, compute that value + see if we got it +} + + +//----------------------------------------------------------------------------- +// +// IntersectInfiniteRayWithSphere +// +// Returns whether or not there was an intersection. +// Returns the two intersection points +// +//----------------------------------------------------------------------------- +bool IntersectInfiniteRayWithSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, + const Vector &vecSphereCenter, float flRadius, float *pT1, float *pT2 ) +{ + // Solve using the ray equation + the sphere equation + // P = o + dt + // (x - xc)^2 + (y - yc)^2 + (z - zc)^2 = r^2 + // (ox + dx * t - xc)^2 + (oy + dy * t - yc)^2 + (oz + dz * t - zc)^2 = r^2 + // (ox - xc)^2 + 2 * (ox-xc) * dx * t + dx^2 * t^2 + + // (oy - yc)^2 + 2 * (oy-yc) * dy * t + dy^2 * t^2 + + // (oz - zc)^2 + 2 * (oz-zc) * dz * t + dz^2 * t^2 = r^2 + // (dx^2 + dy^2 + dz^2) * t^2 + 2 * ((ox-xc)dx + (oy-yc)dy + (oz-zc)dz) t + + // (ox-xc)^2 + (oy-yc)^2 + (oz-zc)^2 - r^2 = 0 + // or, t = (-b +/- sqrt( b^2 - 4ac)) / 2a + // a = DotProduct( vecRayDelta, vecRayDelta ); + // b = 2 * DotProduct( vecRayOrigin - vecCenter, vecRayDelta ) + // c = DotProduct(vecRayOrigin - vecCenter, vecRayOrigin - vecCenter) - flRadius * flRadius; + + Vector vecSphereToRay; + VectorSubtract( vecRayOrigin, vecSphereCenter, vecSphereToRay ); + + float a = DotProduct( vecRayDelta, vecRayDelta ); + + // This would occur in the case of a zero-length ray + if ( a == 0.0f ) + { + *pT1 = *pT2 = 0.0f; + return vecSphereToRay.LengthSqr() <= flRadius * flRadius; + } + + float b = 2 * DotProduct( vecSphereToRay, vecRayDelta ); + float c = DotProduct( vecSphereToRay, vecSphereToRay ) - flRadius * flRadius; + float flDiscrim = b * b - 4 * a * c; + if ( flDiscrim < 0.0f ) + return false; + + flDiscrim = sqrt( flDiscrim ); + float oo2a = 0.5f / a; + *pT1 = ( - b - flDiscrim ) * oo2a; + *pT2 = ( - b + flDiscrim ) * oo2a; + return true; +} + + + +//----------------------------------------------------------------------------- +// +// IntersectRayWithSphere +// +// Returns whether or not there was an intersection. +// Returns the two intersection points, clamped to (0,1) +// +//----------------------------------------------------------------------------- +bool IntersectRayWithSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, + const Vector &vecSphereCenter, float flRadius, float *pT1, float *pT2 ) +{ + if ( !IntersectInfiniteRayWithSphere( vecRayOrigin, vecRayDelta, vecSphereCenter, flRadius, pT1, pT2 ) ) + return false; + + if (( *pT1 > 1.0f ) || ( *pT2 < 0.0f )) + return false; + + // Clamp it! + if ( *pT1 < 0.0f ) + *pT1 = 0.0f; + if ( *pT2 > 1.0f ) + *pT2 = 1.0f; + + return true; +} + + +//----------------------------------------------------------------------------- +// returns true if the point is in the box +//----------------------------------------------------------------------------- +bool IsPointInBox( const Vector& pt, const Vector& boxMin, const Vector& boxMax ) +{ + Assert( boxMin[0] <= boxMax[0] ); + Assert( boxMin[1] <= boxMax[1] ); + Assert( boxMin[2] <= boxMax[2] ); + + if ( (pt[0] > boxMax[0]) || (pt[0] < boxMin[0]) ) + return false; + if ( (pt[1] > boxMax[1]) || (pt[1] < boxMin[1]) ) + return false; + if ( (pt[2] > boxMax[2]) || (pt[2] < boxMin[2]) ) + return false; + return true; +} + + +bool IsPointInCone( const Vector &pt, const Vector &origin, const Vector &axis, float cosAngle, float length ) +{ + Vector delta = pt - origin; + float dist = VectorNormalize( delta ); + float dot = DotProduct( delta, axis ); + if ( dot < cosAngle ) + return false; + if ( dist * dot > length ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// returns true if there's an intersection between two boxes +//----------------------------------------------------------------------------- +bool IsBoxIntersectingBox( const Vector& boxMin1, const Vector& boxMax1, + const Vector& boxMin2, const Vector& boxMax2 ) +{ + Assert( boxMin1[0] <= boxMax1[0] ); + Assert( boxMin1[1] <= boxMax1[1] ); + Assert( boxMin1[2] <= boxMax1[2] ); + Assert( boxMin2[0] <= boxMax2[0] ); + Assert( boxMin2[1] <= boxMax2[1] ); + Assert( boxMin2[2] <= boxMax2[2] ); + + if ( (boxMin1[0] > boxMax2[0]) || (boxMax1[0] < boxMin2[0]) ) + return false; + if ( (boxMin1[1] > boxMax2[1]) || (boxMax1[1] < boxMin2[1]) ) + return false; + if ( (boxMin1[2] > boxMax2[2]) || (boxMax1[2] < boxMin2[2]) ) + return false; + return true; +} + +bool IsBoxIntersectingBoxExtents( const Vector& boxCenter1, const Vector& boxHalfDiagonal1, + const Vector& boxCenter2, const Vector& boxHalfDiagonal2 ) +{ + Vector vecDelta, vecSize; + VectorSubtract( boxCenter1, boxCenter2, vecDelta ); + VectorAdd( boxHalfDiagonal1, boxHalfDiagonal2, vecSize ); + return ( FloatMakePositive( vecDelta.x ) <= vecSize.x ) && + ( FloatMakePositive( vecDelta.y ) <= vecSize.y ) && + ( FloatMakePositive( vecDelta.z ) <= vecSize.z ); +} + + +//----------------------------------------------------------------------------- +// +// IsOBBIntersectingOBB +// +// returns true if there's an intersection between two OBBs +// +//----------------------------------------------------------------------------- +bool IsOBBIntersectingOBB( const Vector &vecOrigin1, const QAngle &vecAngles1, const Vector& boxMin1, const Vector& boxMax1, + const Vector &vecOrigin2, const QAngle &vecAngles2, const Vector& boxMin2, const Vector& boxMax2, float flTolerance ) +{ + // FIXME: Simple case AABB check doesn't work because the min and max extents are not oriented based on the angle + // this fast check would only be good for cubes. + /*if ( vecAngles1 == vecAngles2 ) + { + const Vector &vecDelta = vecOrigin2 - vecOrigin1; + Vector vecOtherMins, vecOtherMaxs; + VectorAdd( boxMin2, vecDelta, vecOtherMins ); + VectorAdd( boxMax2, vecDelta, vecOtherMaxs ); + return IsBoxIntersectingBox( boxMin1, boxMax1, vecOtherMins, vecOtherMaxs ); + }*/ + + // OBB test... + cplane_t plane; + bool bFoundPlane = ComputeSeparatingPlane( vecOrigin1, vecAngles1, boxMin1, boxMax1, + vecOrigin2, vecAngles2, boxMin2, boxMax2, flTolerance, &plane ); + return (bFoundPlane == false); +} + +//----------------------------------------------------------------------------- +// returns true if there's an intersection between box and ray +//----------------------------------------------------------------------------- +bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, + const Vector& origin, const Vector& delta, float flTolerance ) +{ + Assert( boxMin[0] <= boxMax[0] ); + Assert( boxMin[1] <= boxMax[1] ); + Assert( boxMin[2] <= boxMax[2] ); + + // FIXME: Surely there's a faster way + float tmin = -FLT_MAX; + float tmax = FLT_MAX; + + for (int i = 0; i < 3; ++i) + { + // Parallel case... + if (FloatMakePositive(delta[i]) < 1e-8) + { + // Check that origin is in the box + // if not, then it doesn't intersect.. + if ( (origin[i] < boxMin[i] - flTolerance) || (origin[i] > boxMax[i] + flTolerance) ) + return false; + + continue; + } + + // non-parallel case + // Find the t's corresponding to the entry and exit of + // the ray along x, y, and z. The find the furthest entry + // point, and the closest exit point. Once that is done, + // we know we don't collide if the closest exit point + // is behind the starting location. We also don't collide if + // the closest exit point is in front of the furthest entry point + + float invDelta = 1.0f / delta[i]; + float t1 = (boxMin[i] - flTolerance - origin[i]) * invDelta; + float t2 = (boxMax[i] + flTolerance - origin[i]) * invDelta; + if (t1 > t2) + { + float temp = t1; + t1 = t2; + t2 = temp; + } + if (t1 > tmin) + tmin = t1; + if (t2 < tmax) + tmax = t2; + if (tmin > tmax) + return false; + if (tmax < 0) + return false; + if (tmin > 1) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// returns true if there's an intersection between box and ray +//----------------------------------------------------------------------------- +bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, + const Vector& origin, const Vector& delta, + const Vector& invDelta, float flTolerance ) +{ + Assert( boxMin[0] <= boxMax[0] ); + Assert( boxMin[1] <= boxMax[1] ); + Assert( boxMin[2] <= boxMax[2] ); + + // FIXME: Surely there's a faster way + float tmin = -FLT_MAX; + float tmax = FLT_MAX; + + for ( int i = 0; i < 3; ++i ) + { + // Parallel case... + if ( FloatMakePositive( delta[i] ) < 1e-8 ) + { + // Check that origin is in the box, if not, then it doesn't intersect.. + if ( ( origin[i] < boxMin[i] - flTolerance ) || ( origin[i] > boxMax[i] + flTolerance ) ) + return false; + + continue; + } + + // Non-parallel case + // Find the t's corresponding to the entry and exit of + // the ray along x, y, and z. The find the furthest entry + // point, and the closest exit point. Once that is done, + // we know we don't collide if the closest exit point + // is behind the starting location. We also don't collide if + // the closest exit point is in front of the furthest entry point + float t1 = ( boxMin[i] - flTolerance - origin[i] ) * invDelta[i]; + float t2 = ( boxMax[i] + flTolerance - origin[i] ) * invDelta[i]; + if ( t1 > t2 ) + { + float temp = t1; + t1 = t2; + t2 = temp; + } + + if (t1 > tmin) + tmin = t1; + + if (t2 < tmax) + tmax = t2; + + if (tmin > tmax) + return false; + + if (tmax < 0) + return false; + + if (tmin > 1) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Intersects a ray with a aabb, return true if they intersect +//----------------------------------------------------------------------------- +bool FASTCALL IsBoxIntersectingRay( const Vector& vecBoxMin, const Vector& vecBoxMax, const Ray_t& ray, float flTolerance ) +{ + if ( !ray.m_IsSwept ) + { + Vector rayMins, rayMaxs; + VectorSubtract( ray.m_Start, ray.m_Extents, rayMins ); + VectorAdd( ray.m_Start, ray.m_Extents, rayMaxs ); + if ( flTolerance != 0.0f ) + { + rayMins.x -= flTolerance; rayMins.y -= flTolerance; rayMins.z -= flTolerance; + rayMaxs.x += flTolerance; rayMaxs.y += flTolerance; rayMaxs.z += flTolerance; + } + return IsBoxIntersectingBox( vecBoxMin, vecBoxMax, rayMins, rayMaxs ); + } + + Vector vecExpandedBoxMin, vecExpandedBoxMax; + VectorSubtract( vecBoxMin, ray.m_Extents, vecExpandedBoxMin ); + VectorAdd( vecBoxMax, ray.m_Extents, vecExpandedBoxMax ); + return IsBoxIntersectingRay( vecExpandedBoxMin, vecExpandedBoxMax, ray.m_Start, ray.m_Delta, flTolerance ); +} + + +//----------------------------------------------------------------------------- +// Intersects a ray with a ray, return true if they intersect +// t, s = parameters of closest approach (if not intersecting!) +//----------------------------------------------------------------------------- +bool IntersectRayWithRay( const Ray_t &ray0, const Ray_t &ray1, float &t, float &s ) +{ + Assert( ray0.m_IsRay && ray1.m_IsRay ); + + // + // r0 = p0 + v0t + // r1 = p1 + v1s + // + // intersection : r0 = r1 :: p0 + v0t = p1 + v1s + // NOTE: v(0,1) are unit direction vectors + // + // subtract p0 from both sides and cross with v1 (NOTE: v1 x v1 = 0) + // (v0 x v1)t = ((p1 - p0 ) x v1) + // + // dotting with (v0 x v1) and dividing by |v0 x v1|^2 + // t = Det | (p1 - p0) , v1 , (v0 x v1) | / |v0 x v1|^2 + // s = Det | (p1 - p0) , v0 , (v0 x v1) | / |v0 x v1|^2 + // + // Det | A B C | = -( A x C ) dot B or -( C x B ) dot A + // + // NOTE: if |v0 x v1|^2 = 0, then the lines are parallel + // + Vector v0( ray0.m_Delta ); + Vector v1( ray1.m_Delta ); + VectorNormalize( v0 ); + VectorNormalize( v1 ); + + Vector v0xv1 = v0.Cross( v1 ); + float length = v0xv1.Length(); + if( length == 0.0f ) + { + t = 0; s = 0; + return false; // parallel + } + + Vector p1p0 = ray1.m_Start - ray0.m_Start; + + Vector AxC = p1p0.Cross( v0xv1 ); + AxC.Negate(); + float detT = AxC.Dot( v1 ); + + AxC = p1p0.Cross( v0xv1 ); + AxC.Negate(); + float detS = AxC.Dot( v0 ); + + t = detT / ( length * length ); + s = detS / ( length * length ); + + // intersection???? + Vector i0, i1; + i0 = v0 * t; + i1 = v1 * s; + i0 += ray0.m_Start; + i1 += ray1.m_Start; + if( i0.x == i1.x && i0.y == i1.y && i0.z == i1.z ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Intersects a ray with a plane, returns distance t along ray. +//----------------------------------------------------------------------------- +float IntersectRayWithPlane( const Ray_t& ray, const cplane_t& plane ) +{ + float denom = DotProduct( ray.m_Delta, plane.normal ); + if (denom == 0.0f) + return 0.0f; + + denom = 1.0f / denom; + return (plane.dist - DotProduct( ray.m_Start, plane.normal )) * denom; +} + +float IntersectRayWithPlane( const Vector& org, const Vector& dir, const cplane_t& plane ) +{ + float denom = DotProduct( dir, plane.normal ); + if (denom == 0.0f) + return 0.0f; + + denom = 1.0f / denom; + return (plane.dist - DotProduct( org, plane.normal )) * denom; +} + +float IntersectRayWithPlane( const Vector& org, const Vector& dir, const Vector& normal, float dist ) +{ + float denom = DotProduct( dir, normal ); + if (denom == 0.0f) + return 0.0f; + + denom = 1.0f / denom; + return (dist - DotProduct( org, normal )) * denom; +} + +float IntersectRayWithAAPlane( const Vector& vecStart, const Vector& vecEnd, int nAxis, float flSign, float flDist ) +{ + float denom = flSign * (vecEnd[nAxis] - vecStart[nAxis]); + if (denom == 0.0f) + return 0.0f; + + denom = 1.0f / denom; + return (flDist - flSign * vecStart[nAxis]) * denom; +} + + +//----------------------------------------------------------------------------- +// Intersects a ray against a box +//----------------------------------------------------------------------------- +bool IntersectRayWithBox( const Vector &vecRayStart, const Vector &vecRayDelta, + const Vector &boxMins, const Vector &boxMaxs, float flTolerance, BoxTraceInfo_t *pTrace ) +{ + int i; + float d1, d2; + float f; + + pTrace->t1 = -1.0f; + pTrace->t2 = 1.0f; + pTrace->hitside = -1; + + // UNDONE: This makes this code a little messy + pTrace->startsolid = true; + + for ( i = 0; i < 6; ++i ) + { + if ( i >= 3 ) + { + d1 = vecRayStart[i-3] - boxMaxs[i-3]; + d2 = d1 + vecRayDelta[i-3]; + } + else + { + d1 = -vecRayStart[i] + boxMins[i]; + d2 = d1 - vecRayDelta[i]; + } + + // if completely in front of face, no intersection + if (d1 > 0 && d2 > 0) + { + // UNDONE: Have to revert this in case it's still set + // UNDONE: Refactor to have only 2 return points (true/false) from this function + pTrace->startsolid = false; + return false; + } + + // completely inside, check next face + if (d1 <= 0 && d2 <= 0) + continue; + + if (d1 > 0) + { + pTrace->startsolid = false; + } + + // crosses face + if (d1 > d2) + { + f = d1 - flTolerance; + if ( f < 0 ) + { + f = 0; + } + f = f / (d1-d2); + if (f > pTrace->t1) + { + pTrace->t1 = f; + pTrace->hitside = i; + } + } + else + { + // leave + f = (d1 + flTolerance) / (d1-d2); + if (f < pTrace->t2) + { + pTrace->t2 = f; + } + } + } + + return pTrace->startsolid || (pTrace->t1 < pTrace->t2 && pTrace->t1 >= 0.0f); +} + + +//----------------------------------------------------------------------------- +// Intersects a ray against a box +//----------------------------------------------------------------------------- +bool IntersectRayWithBox( const Vector &vecRayStart, const Vector &vecRayDelta, + const Vector &boxMins, const Vector &boxMaxs, float flTolerance, CBaseTrace *pTrace ) +{ + Collision_ClearTrace( vecRayStart, vecRayDelta, pTrace ); + + BoxTraceInfo_t trace; + + if ( IntersectRayWithBox( vecRayStart, vecRayDelta, boxMins, boxMaxs, flTolerance, &trace ) ) + { + pTrace->startsolid = trace.startsolid; + if (trace.t1 < trace.t2 && trace.t1 >= 0.0f) + { + pTrace->fraction = trace.t1; + VectorMA( pTrace->startpos, trace.t1, vecRayDelta, pTrace->endpos ); + pTrace->contents = CONTENTS_SOLID; + pTrace->plane.normal = vec3_origin; + if ( trace.hitside >= 3 ) + { + trace.hitside -= 3; + pTrace->plane.dist = boxMaxs[trace.hitside]; + pTrace->plane.normal[trace.hitside] = 1.0f; + pTrace->plane.type = trace.hitside; + } + else + { + pTrace->plane.dist = -boxMins[trace.hitside]; + pTrace->plane.normal[trace.hitside] = -1.0f; + pTrace->plane.type = trace.hitside; + } + return true; + } + + if ( pTrace->startsolid ) + { + pTrace->allsolid = (trace.t2 <= 0.0f) || (trace.t2 >= 1.0f); + pTrace->fraction = 0; + pTrace->endpos = pTrace->startpos; + pTrace->contents = CONTENTS_SOLID; + pTrace->plane.dist = pTrace->startpos[0]; + pTrace->plane.normal.Init( 1.0f, 0.0f, 0.0f ); + pTrace->plane.type = 0; + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Intersects a ray against a box +//----------------------------------------------------------------------------- +bool IntersectRayWithBox( const Ray_t &ray, const Vector &boxMins, const Vector &boxMaxs, + float flTolerance, CBaseTrace *pTrace ) +{ + Vector vecExpandedMins = boxMins; + Vector vecExpandedMaxs = boxMaxs; + + if ( !ray.m_IsRay ) + { + vecExpandedMins -= ray.m_Extents; + vecExpandedMaxs += ray.m_Extents; + } + + bool bIntersects = IntersectRayWithBox( ray.m_Start, ray.m_Delta, vecExpandedMins, vecExpandedMaxs, flTolerance, pTrace ); + + if ( !ray.m_IsRay ) + { + pTrace->startpos += ray.m_StartOffset; + pTrace->endpos += ray.m_StartOffset; + } + + return bIntersects; +} + + +//----------------------------------------------------------------------------- +// Intersects a ray against an OBB, returns t1 and t2 +//----------------------------------------------------------------------------- +bool IntersectRayWithOBB( const Vector &vecRayStart, const Vector &vecRayDelta, + const matrix3x4_t &matOBBToWorld, const Vector &vecOBBMins, const Vector &vecOBBMaxs, + float flTolerance, BoxTraceInfo_t *pTrace ) +{ + // FIXME: Two transforms is pretty expensive. Should we optimize this? + Vector start, delta; + VectorITransform( vecRayStart, matOBBToWorld, start ); + VectorIRotate( vecRayDelta, matOBBToWorld, delta ); + + return IntersectRayWithBox( start, delta, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); +} + + + +//----------------------------------------------------------------------------- +// Intersects a ray against an OBB +//----------------------------------------------------------------------------- +bool IntersectRayWithOBB( const Vector &vecRayStart, const Vector &vecRayDelta, + const matrix3x4_t &matOBBToWorld, const Vector &vecOBBMins, const Vector &vecOBBMaxs, + float flTolerance, CBaseTrace *pTrace ) +{ + Collision_ClearTrace( vecRayStart, vecRayDelta, pTrace ); + + // FIXME: Make it work with tolerance + Assert( flTolerance == 0.0f ); + + // OPTIMIZE: Store this in the box instead of computing it here + // compute center in local space + Vector vecBoxExtents = (vecOBBMins + vecOBBMaxs) * 0.5; + Vector vecBoxCenter; + + // transform to world space + VectorTransform( vecBoxExtents, matOBBToWorld, vecBoxCenter ); + + // calc extents from local center + vecBoxExtents = vecOBBMaxs - vecBoxExtents; + + // OPTIMIZE: This is optimized for world space. If the transform is fast enough, it may make more + // sense to just xform and call UTIL_ClipToBox() instead. MEASURE THIS. + + // save the extents of the ray along + Vector extent, uextent; + Vector segmentCenter = vecRayStart + vecRayDelta - vecBoxCenter; + + extent.Init(); + + // check box axes for separation + for ( int j = 0; j < 3; j++ ) + { + extent[j] = vecRayDelta.x * matOBBToWorld[0][j] + vecRayDelta.y * matOBBToWorld[1][j] + vecRayDelta.z * matOBBToWorld[2][j]; + uextent[j] = fabsf(extent[j]); + float coord = segmentCenter.x * matOBBToWorld[0][j] + segmentCenter.y * matOBBToWorld[1][j] + segmentCenter.z * matOBBToWorld[2][j]; + coord = fabsf(coord); + + if ( coord > (vecBoxExtents[j] + uextent[j]) ) + return false; + } + + // now check cross axes for separation + float tmp, cextent; + Vector cross = vecRayDelta.Cross( segmentCenter ); + cextent = cross.x * matOBBToWorld[0][0] + cross.y * matOBBToWorld[1][0] + cross.z * matOBBToWorld[2][0]; + cextent = fabsf(cextent); + tmp = vecBoxExtents[1]*uextent[2] + vecBoxExtents[2]*uextent[1]; + if ( cextent > tmp ) + return false; + + cextent = cross.x * matOBBToWorld[0][1] + cross.y * matOBBToWorld[1][1] + cross.z * matOBBToWorld[2][1]; + cextent = fabsf(cextent); + tmp = vecBoxExtents[0]*uextent[2] + vecBoxExtents[2]*uextent[0]; + if ( cextent > tmp ) + return false; + + cextent = cross.x * matOBBToWorld[0][2] + cross.y * matOBBToWorld[1][2] + cross.z * matOBBToWorld[2][2]; + cextent = fabsf(cextent); + tmp = vecBoxExtents[0]*uextent[1] + vecBoxExtents[1]*uextent[0]; + if ( cextent > tmp ) + return false; + + // !!! We hit this box !!! compute intersection point and return + // Compute ray start in bone space + Vector start; + VectorITransform( vecRayStart, matOBBToWorld, start ); + + // extent is ray.m_Delta in bone space, recompute delta in bone space + extent *= 2.0f; + + // delta was prescaled by the current t, so no need to see if this intersection + // is closer + trace_t boxTrace; + if ( !IntersectRayWithBox( start, extent, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ) ) + return false; + + // Fix up the plane information + float flSign = pTrace->plane.normal[ pTrace->plane.type ]; + pTrace->plane.normal[0] = flSign * matOBBToWorld[0][pTrace->plane.type]; + pTrace->plane.normal[1] = flSign * matOBBToWorld[1][pTrace->plane.type]; + pTrace->plane.normal[2] = flSign * matOBBToWorld[2][pTrace->plane.type]; + pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal ); + pTrace->plane.type = 3; + return true; +} + + +//----------------------------------------------------------------------------- +// Intersects a ray against an OBB +//----------------------------------------------------------------------------- +bool IntersectRayWithOBB( const Vector &vecRayOrigin, const Vector &vecRayDelta, + const Vector &vecBoxOrigin, const QAngle &angBoxRotation, + const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ) +{ + if (angBoxRotation == vec3_angle) + { + Vector vecAbsMins, vecAbsMaxs; + VectorAdd( vecBoxOrigin, vecOBBMins, vecAbsMins ); + VectorAdd( vecBoxOrigin, vecOBBMaxs, vecAbsMaxs ); + return IntersectRayWithBox( vecRayOrigin, vecRayDelta, vecAbsMins, vecAbsMaxs, flTolerance, pTrace ); + } + + matrix3x4_t obbToWorld; + AngleMatrix( angBoxRotation, vecBoxOrigin, obbToWorld ); + return IntersectRayWithOBB( vecRayOrigin, vecRayDelta, obbToWorld, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); +} + + +//----------------------------------------------------------------------------- +// Box support map +//----------------------------------------------------------------------------- +inline void ComputeSupportMap( const Vector &vecDirection, const Vector &vecBoxMins, + const Vector &vecBoxMaxs, float pDist[2] ) +{ + int nIndex = (vecDirection.x > 0.0f); + pDist[nIndex] = vecBoxMaxs.x * vecDirection.x; + pDist[1 - nIndex] = vecBoxMins.x * vecDirection.x; + + nIndex = (vecDirection.y > 0.0f); + pDist[nIndex] += vecBoxMaxs.y * vecDirection.y; + pDist[1 - nIndex] += vecBoxMins.y * vecDirection.y; + + nIndex = (vecDirection.z > 0.0f); + pDist[nIndex] += vecBoxMaxs.z * vecDirection.z; + pDist[1 - nIndex] += vecBoxMins.z * vecDirection.z; +} + +inline void ComputeSupportMap( const Vector &vecDirection, int i1, int i2, + const Vector &vecBoxMins, const Vector &vecBoxMaxs, float pDist[2] ) +{ + int nIndex = (vecDirection[i1] > 0.0f); + pDist[nIndex] = vecBoxMaxs[i1] * vecDirection[i1]; + pDist[1 - nIndex] = vecBoxMins[i1] * vecDirection[i1]; + + nIndex = (vecDirection[i2] > 0.0f); + pDist[nIndex] += vecBoxMaxs[i2] * vecDirection[i2]; + pDist[1 - nIndex] += vecBoxMins[i2] * vecDirection[i2]; +} + +//----------------------------------------------------------------------------- +// Intersects a ray against an OBB +//----------------------------------------------------------------------------- +static int s_ExtIndices[3][2] = +{ + { 2, 1 }, + { 0, 2 }, + { 0, 1 }, +}; + +static int s_MatIndices[3][2] = +{ + { 1, 2 }, + { 2, 0 }, + { 1, 0 }, +}; + +bool IntersectRayWithOBB( const Ray_t &ray, const matrix3x4_t &matOBBToWorld, + const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ) +{ + if ( ray.m_IsRay ) + { + return IntersectRayWithOBB( ray.m_Start, ray.m_Delta, matOBBToWorld, + vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); + } + + Collision_ClearTrace( ray.m_Start + ray.m_StartOffset, ray.m_Delta, pTrace ); + + // Compute a bounding sphere around the bloated OBB + Vector vecOBBCenter; + VectorAdd( vecOBBMins, vecOBBMaxs, vecOBBCenter ); + vecOBBCenter *= 0.5f; + vecOBBCenter.x += matOBBToWorld[0][3]; + vecOBBCenter.y += matOBBToWorld[1][3]; + vecOBBCenter.z += matOBBToWorld[2][3]; + + Vector vecOBBHalfDiagonal; + VectorSubtract( vecOBBMaxs, vecOBBMins, vecOBBHalfDiagonal ); + vecOBBHalfDiagonal *= 0.5f; + + float flRadius = vecOBBHalfDiagonal.Length() + ray.m_Extents.Length(); + if ( !IsRayIntersectingSphere( ray.m_Start, ray.m_Delta, vecOBBCenter, flRadius, flTolerance ) ) + return false; + + // Ok, we passed the trivial reject, so lets do the dirty deed. + // Basically we're going to do the GJK thing explicitly. We'll shrink the ray down + // to a point, and bloat the OBB by the ray's extents. This will generate facet + // planes which are perpendicular to all of the separating axes typically seen in + // a standard seperating axis implementation. + + // We're going to create a number of planes through various vertices in the OBB + // which represent all of the separating planes. Then we're going to bloat the planes + // by the ray extents. + + // We're going to do all work in OBB-space because it's easier to do the + // support-map in this case + + // First, transform the ray into the space of the OBB + Vector vecLocalRayOrigin, vecLocalRayDirection; + VectorITransform( ray.m_Start, matOBBToWorld, vecLocalRayOrigin ); + VectorIRotate( ray.m_Delta, matOBBToWorld, vecLocalRayDirection ); + + // Next compute all separating planes + Vector pPlaneNormal[15]; + float ppPlaneDist[15][2]; + + int i; + for ( i = 0; i < 3; ++i ) + { + // Each plane needs to be bloated an amount = to the abs dot product of + // the ray extents with the plane normal + // For the OBB planes, do it in world space; + // and use the direction of the OBB (the ith column of matOBBToWorld) in world space vs extents + pPlaneNormal[i].Init( ); + pPlaneNormal[i][i] = 1.0f; + + float flExtentDotNormal = + FloatMakePositive( matOBBToWorld[0][i] * ray.m_Extents.x ) + + FloatMakePositive( matOBBToWorld[1][i] * ray.m_Extents.y ) + + FloatMakePositive( matOBBToWorld[2][i] * ray.m_Extents.z ); + + ppPlaneDist[i][0] = vecOBBMins[i] - flExtentDotNormal; + ppPlaneDist[i][1] = vecOBBMaxs[i] + flExtentDotNormal; + + // For the ray-extents planes, they are bloated by the extents + // Use the support map to determine which + VectorCopy( matOBBToWorld[i], pPlaneNormal[i+3].Base() ); + ComputeSupportMap( pPlaneNormal[i+3], vecOBBMins, vecOBBMaxs, ppPlaneDist[i+3] ); + ppPlaneDist[i+3][0] -= ray.m_Extents[i]; + ppPlaneDist[i+3][1] += ray.m_Extents[i]; + + // Now the edge cases... (take the cross product of x,y,z axis w/ ray extent axes + // given by the rows of the obb to world matrix. + // Compute the ray extent bloat in world space because it's easier... + + // These are necessary to compute the world-space versions of + // the edges so we can compute the extent dot products + float flRayExtent0 = ray.m_Extents[s_ExtIndices[i][0]]; + float flRayExtent1 = ray.m_Extents[s_ExtIndices[i][1]]; + const float *pMatRow0 = matOBBToWorld[s_MatIndices[i][0]]; + const float *pMatRow1 = matOBBToWorld[s_MatIndices[i][1]]; + + // x axis of the OBB + world ith axis + pPlaneNormal[i+6].Init( 0.0f, -matOBBToWorld[i][2], matOBBToWorld[i][1] ); + ComputeSupportMap( pPlaneNormal[i+6], 1, 2, vecOBBMins, vecOBBMaxs, ppPlaneDist[i+6] ); + flExtentDotNormal = + FloatMakePositive( pMatRow0[0] ) * flRayExtent0 + + FloatMakePositive( pMatRow1[0] ) * flRayExtent1; + ppPlaneDist[i+6][0] -= flExtentDotNormal; + ppPlaneDist[i+6][1] += flExtentDotNormal; + + // y axis of the OBB + world ith axis + pPlaneNormal[i+9].Init( matOBBToWorld[i][2], 0.0f, -matOBBToWorld[i][0] ); + ComputeSupportMap( pPlaneNormal[i+9], 0, 2, vecOBBMins, vecOBBMaxs, ppPlaneDist[i+9] ); + flExtentDotNormal = + FloatMakePositive( pMatRow0[1] ) * flRayExtent0 + + FloatMakePositive( pMatRow1[1] ) * flRayExtent1; + ppPlaneDist[i+9][0] -= flExtentDotNormal; + ppPlaneDist[i+9][1] += flExtentDotNormal; + + // z axis of the OBB + world ith axis + pPlaneNormal[i+12].Init( -matOBBToWorld[i][1], matOBBToWorld[i][0], 0.0f ); + ComputeSupportMap( pPlaneNormal[i+12], 0, 1, vecOBBMins, vecOBBMaxs, ppPlaneDist[i+12] ); + flExtentDotNormal = + FloatMakePositive( pMatRow0[2] ) * flRayExtent0 + + FloatMakePositive( pMatRow1[2] ) * flRayExtent1; + ppPlaneDist[i+12][0] -= flExtentDotNormal; + ppPlaneDist[i+12][1] += flExtentDotNormal; + } + + float enterfrac, leavefrac; + float d1[2], d2[2]; + float f; + + int hitplane = -1; + int hitside = -1; + enterfrac = -1.0f; + leavefrac = 1.0f; + + pTrace->startsolid = true; + + Vector vecLocalRayEnd; + VectorAdd( vecLocalRayOrigin, vecLocalRayDirection, vecLocalRayEnd ); + + for ( i = 0; i < 15; ++i ) + { + // FIXME: Not particularly optimal since there's a lot of 0's in the plane normals + float flStartDot = DotProduct( pPlaneNormal[i], vecLocalRayOrigin ); + float flEndDot = DotProduct( pPlaneNormal[i], vecLocalRayEnd ); + + // NOTE: Negative here is because the plane normal + dist + // are defined in negative terms for the far plane (plane dist index 0) + d1[0] = -(flStartDot - ppPlaneDist[i][0]); + d2[0] = -(flEndDot - ppPlaneDist[i][0]); + + d1[1] = flStartDot - ppPlaneDist[i][1]; + d2[1] = flEndDot - ppPlaneDist[i][1]; + + int j; + for ( j = 0; j < 2; ++j ) + { + // if completely in front near plane or behind far plane no intersection + if (d1[j] > 0 && d2[j] > 0) + return false; + + // completely inside, check next plane set + if (d1[j] <= 0 && d2[j] <= 0) + continue; + + if (d1[j] > 0) + { + pTrace->startsolid = false; + } + + // crosses face + float flDenom = 1.0f / (d1[j] - d2[j]); + if (d1[j] > d2[j]) + { + f = d1[j] - flTolerance; + if ( f < 0 ) + { + f = 0; + } + f *= flDenom; + if (f > enterfrac) + { + enterfrac = f; + hitplane = i; + hitside = j; + } + } + else + { + // leave + f = (d1[j] + flTolerance) * flDenom; + if (f < leavefrac) + { + leavefrac = f; + } + } + } + } + + if (enterfrac < leavefrac && enterfrac >= 0.0f) + { + pTrace->fraction = enterfrac; + VectorMA( pTrace->startpos, enterfrac, ray.m_Delta, pTrace->endpos ); + pTrace->contents = CONTENTS_SOLID; + + // Need to transform the plane into world space... + cplane_t temp; + temp.normal = pPlaneNormal[hitplane]; + temp.dist = ppPlaneDist[hitplane][hitside]; + if (hitside == 0) + { + temp.normal *= -1.0f; + temp.dist *= -1.0f; + } + temp.type = 3; + + MatrixITransformPlane( matOBBToWorld, temp, pTrace->plane ); + return true; + } + + if ( pTrace->startsolid ) + { + pTrace->allsolid = (leavefrac <= 0.0f) || (leavefrac >= 1.0f); + pTrace->fraction = 0; + pTrace->endpos = pTrace->startpos; + pTrace->contents = CONTENTS_SOLID; + pTrace->plane.dist = pTrace->startpos[0]; + pTrace->plane.normal.Init( 1.0f, 0.0f, 0.0f ); + pTrace->plane.type = 0; + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Intersects a ray against an OBB +//----------------------------------------------------------------------------- +bool IntersectRayWithOBB( const Ray_t &ray, const Vector &vecBoxOrigin, const QAngle &angBoxRotation, + const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ) +{ + if ( angBoxRotation == vec3_angle ) + { + Vector vecWorldMins, vecWorldMaxs; + VectorAdd( vecBoxOrigin, vecOBBMins, vecWorldMins ); + VectorAdd( vecBoxOrigin, vecOBBMaxs, vecWorldMaxs ); + return IntersectRayWithBox( ray, vecWorldMins, vecWorldMaxs, flTolerance, pTrace ); + } + + if ( ray.m_IsRay ) + { + return IntersectRayWithOBB( ray.m_Start, ray.m_Delta, vecBoxOrigin, angBoxRotation, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); + } + + matrix3x4_t matOBBToWorld; + AngleMatrix( angBoxRotation, vecBoxOrigin, matOBBToWorld ); + return IntersectRayWithOBB( ray, matOBBToWorld, vecOBBMins, vecOBBMaxs, flTolerance, pTrace ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void GetNonMajorAxes( const Vector &vNormal, Vector2D &axes ) +{ + axes[0] = 0; + axes[1] = 1; + + if( FloatMakePositive( vNormal.x ) > FloatMakePositive( vNormal.y ) ) + { + if( FloatMakePositive( vNormal.x ) > FloatMakePositive( vNormal.z ) ) + { + axes[0] = 1; + axes[1] = 2; + } + } + else + { + if( FloatMakePositive( vNormal.y ) > FloatMakePositive( vNormal.z ) ) + { + axes[0] = 0; + axes[1] = 2; + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +QuadBarycentricRetval_t QuadWithParallelEdges( const Vector &vecOrigin, + const Vector &vecU, float lengthU, const Vector &vecV, float lengthV, + const Vector &pt, Vector2D &vecUV ) +{ + Ray_t rayAxis; + Ray_t rayPt; + + // + // handle the u axis + // + rayAxis.m_Start = vecOrigin; + rayAxis.m_Delta = vecU; + rayAxis.m_IsRay = true; + + rayPt.m_Start = pt; + rayPt.m_Delta = vecV * -( lengthV * 10.0f ); + rayPt.m_IsRay = true; + + float s, t; + IntersectRayWithRay( rayAxis, rayPt, t, s ); + vecUV[0] = t / lengthU; + + // + // handle the v axis + // + rayAxis.m_Delta = vecV; + + rayPt.m_Delta = vecU * -( lengthU * 10.0f ); + + IntersectRayWithRay( rayAxis, rayPt, t, s ); + vecUV[1] = t / lengthV; + + // inside of the quad?? + if( ( vecUV[0] < 0.0f ) || ( vecUV[0] > 1.0f ) || + ( vecUV[1] < 0.0f ) || ( vecUV[1] > 1.0f ) ) + return BARY_QUADRATIC_FALSE; + + return BARY_QUADRATIC_TRUE; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void ResolveQuadratic( double tPlus, double tMinus, + const Vector axisU0, const Vector axisU1, + const Vector axisV0, const Vector axisV1, + const Vector axisOrigin, const Vector pt, + int projU, double &s, double &t ) +{ + // calculate the sPlus, sMinus pair(s) + double sDenomPlus = ( axisU0[projU] * ( 1 - tPlus ) ) + ( axisU1[projU] * tPlus ); + double sDenomMinus = ( axisU0[projU] * ( 1 - tMinus ) ) + ( axisU1[projU] * tMinus ); + + double sPlus = UNINIT, sMinus = UNINIT; + if( FloatMakePositive( sDenomPlus ) >= 1e-5 ) + { + sPlus = ( pt[projU] - axisOrigin[projU] - ( axisV0[projU] * tPlus ) ) / sDenomPlus; + } + + if( FloatMakePositive( sDenomMinus ) >= 1e-5 ) + { + sMinus = ( pt[projU] - axisOrigin[projU] - ( axisV0[projU] * tMinus ) ) / sDenomMinus; + } + + if( ( tPlus >= 0.0 ) && ( tPlus <= 1.0 ) && ( sPlus >= 0.0 ) && ( sPlus <= 1.0 ) ) + { + s = sPlus; + t = tPlus; + return; + } + + if( ( tMinus >= 0.0 ) && ( tMinus <= 1.0 ) && ( sMinus >= 0.0 ) && ( sMinus <= 1.0 ) ) + { + s = sMinus; + t = tMinus; + return; + } + + double s0, t0, s1, t1; + + s0 = sPlus; + t0 = tPlus; + if( s0 >= 1.0 ) { s0 -= 1.0; } + if( t0 >= 1.0 ) { t0 -= 1.0; } + + s1 = sMinus; + t1 = tMinus; + if( s1 >= 1.0 ) { s1 -= 1.0; } + if( t1 >= 1.0 ) { t1 -= 1.0; } + + s0 = FloatMakePositive( s0 ); + t0 = FloatMakePositive( t0 ); + s1 = FloatMakePositive( s1 ); + t1 = FloatMakePositive( t1 ); + + double max0, max1; + max0 = s0; + if( t0 > max0 ) { max0 = t0; } + max1 = s1; + if( t1 > max1 ) { max1 = t1; } + + if( max0 > max1 ) + { + s = sMinus; + t = tMinus; + } + else + { + s = sPlus; + t = tPlus; + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +QuadBarycentricRetval_t PointInQuadToBarycentric( const Vector &v1, const Vector &v2, + const Vector &v3, const Vector &v4, const Vector &point, Vector2D &uv ) +{ +#define PIQ_TEXTURE_EPSILON 0.001 +#define PIQ_PLANE_EPSILON 0.1 +#define PIQ_DOT_EPSILON 0.99f + + // + // Think of a quad with points v1, v2, v3, v4 and u, v line segments + // u0 = v2 - v1 + // u1 = v3 - v4 + // v0 = v4 - v1 + // v1 = v3 - v2 + // + Vector axisU[2], axisV[2]; + Vector axisUNorm[2], axisVNorm[2]; + axisU[0] = axisUNorm[0] = v2 - v1; + axisU[1] = axisUNorm[1] = v3 - v4; + axisV[0] = axisVNorm[0] = v4 - v1; + axisV[1] = axisVNorm[1] = v3 - v2; + + float lengthU[2], lengthV[2]; + lengthU[0] = VectorNormalize( axisUNorm[0] ); + lengthU[1] = VectorNormalize( axisUNorm[1] ); + lengthV[0] = VectorNormalize( axisVNorm[0] ); + lengthV[1] = VectorNormalize( axisVNorm[1] ); + + // + // check for an early out - parallel opposite edges! + // NOTE: quad property if 1 set of opposite edges is parallel and equal + // in length, then the other set of edges is as well + // + if( axisUNorm[0].Dot( axisUNorm[1] ) > PIQ_DOT_EPSILON ) + { + if( FloatMakePositive( lengthU[0] - lengthU[1] ) < PIQ_PLANE_EPSILON ) + { + return QuadWithParallelEdges( v1, axisUNorm[0], lengthU[0], axisVNorm[0], lengthV[0], point, uv ); + } + } + + // + // since we are solving for s in our equations below we need to ensure that + // the v axes are non-parallel + // + bool bFlipped = false; + if( axisVNorm[0].Dot( axisVNorm[1] ) > PIQ_DOT_EPSILON ) + { + Vector tmp[2]; + tmp[0] = axisV[0]; + tmp[1] = axisV[1]; + axisV[0] = axisU[0]; + axisV[1] = axisU[1]; + axisU[0] = tmp[0]; + axisU[1] = tmp[1]; + bFlipped = true; + } + + // + // get the "projection" axes + // + Vector2D projAxes; + Vector vNormal = axisU[0].Cross( axisV[0] ); + GetNonMajorAxes( vNormal, projAxes ); + + // + // NOTE: axisU[0][projAxes[0]] < axisU[0][projAxes[1]], + // this is done to decrease error when dividing later + // + if( FloatMakePositive( axisU[0][static_cast(projAxes[0])] ) < FloatMakePositive( axisU[0][static_cast(projAxes[1])] ) ) + { + int tmp = static_cast(projAxes[0]); + projAxes[0] = projAxes[1]; + projAxes[1] = tmp; + } + + // Here's how we got these equations: + // + // Given the points and u,v line segments above... + // + // Then: + // + // (1.0) PT = P0 + U0 * s + V * t + // + // where + // + // (1.1) V = V0 + s * (V1 - V0) + // (1.2) U = U0 + t * (U1 - U0) + // + // Therefore (from 1.1 + 1.0): + // PT - P0 = U0 * s + (V0 + s * (V1-V0)) * t + // Group s's: + // PT - P0 - t * V0 = s * (U0 + t * (V1-V0)) + // Two equations and two unknowns in x and y get you the following quadratic: + // + // solve the quadratic + // + double s = 0.0, t = 0.0; + double A, negB, C; + + A = ( axisU[0][static_cast(projAxes[1])] * axisV[0][static_cast(projAxes[0])] ) - + ( axisU[0][static_cast(projAxes[0])] * axisV[0][static_cast(projAxes[1])] ) - + ( axisU[1][static_cast(projAxes[1])] * axisV[0][static_cast(projAxes[0])] ) + + ( axisU[1][static_cast(projAxes[0])] * axisV[0][static_cast(projAxes[1])] ); + C = ( v1[static_cast(projAxes[1])] * axisU[0][static_cast(projAxes[0])] ) - + ( point[static_cast(projAxes[1])] * axisU[0][static_cast(projAxes[0])] ) - + ( v1[static_cast(projAxes[0])] * axisU[0][static_cast(projAxes[1])] ) + + ( point[static_cast(projAxes[0])] * axisU[0][static_cast(projAxes[1])] ); + negB = C - + ( v1[static_cast(projAxes[1])] * axisU[1][static_cast(projAxes[0])] ) + + ( point[static_cast(projAxes[1])] * axisU[1][static_cast(projAxes[0])] ) + + ( v1[static_cast(projAxes[0])] * axisU[1][static_cast(projAxes[1])] ) - + ( point[static_cast(projAxes[0])] * axisU[1][static_cast(projAxes[1])] ) + + ( axisU[0][static_cast(projAxes[1])] * axisV[0][static_cast(projAxes[0])] ) - + ( axisU[0][static_cast(projAxes[0])] * axisV[0][static_cast(projAxes[1])] ); + + if( ( A > -PIQ_PLANE_EPSILON ) && ( A < PIQ_PLANE_EPSILON ) ) + { + // shouldn't be here -- this should have been take care of in the "early out" +// Assert( 0 ); + + Vector vecUAvg, vecVAvg; + vecUAvg = ( axisUNorm[0] + axisUNorm[1] ) * 0.5f; + vecVAvg = ( axisVNorm[0] + axisVNorm[1] ) * 0.5f; + + float fLengthUAvg = ( lengthU[0] + lengthU[1] ) * 0.5f; + float fLengthVAvg = ( lengthV[0] + lengthV[1] ) * 0.5f; + + return QuadWithParallelEdges( v1, vecUAvg, fLengthUAvg, vecVAvg, fLengthVAvg, point, uv ); + +#if 0 + // legacy code -- kept here for completeness! + + // not a quadratic -- solve linearly + t = C / negB; + + // See (1.2) above + float ui = axisU[0][projAxes[0]] + t * ( axisU[1][projAxes[0]] - axisU[0][projAxes[0]] ); + if( FloatMakePositive( ui ) >= 1e-5 ) + { + // See (1.0) above + s = ( point[projAxes[0]] - v1[projAxes[0]] - axisV[0][projAxes[0]] * t ) / ui; + } +#endif + } + else + { + // (-b +/- sqrt( b^2 - 4ac )) / 2a + double discriminant = (negB*negB) - (4.0f * A * C); + if( discriminant < 0.0f ) + { + uv[0] = -99999.0f; + uv[1] = -99999.0f; + return BARY_QUADRATIC_NEGATIVE_DISCRIMINANT; + } + + double quad = sqrt( discriminant ); + double QPlus = ( negB + quad ) / ( 2.0f * A ); + double QMinus = ( negB - quad ) / ( 2.0f * A ); + + ResolveQuadratic( QPlus, QMinus, axisU[0], axisU[1], axisV[0], axisV[1], v1, point, static_cast(projAxes[0]), s, t ); + } + + if( !bFlipped ) + { + uv[0] = ( float )s; + uv[1] = ( float )t; + } + else + { + uv[0] = ( float )t; + uv[1] = ( float )s; + } + + // inside of the quad?? + if( ( uv[0] < 0.0f ) || ( uv[0] > 1.0f ) || ( uv[1] < 0.0f ) || ( uv[1] > 1.0f ) ) + return BARY_QUADRATIC_FALSE; + + return BARY_QUADRATIC_TRUE; + +#undef PIQ_TEXTURE_EPSILON +#undef PIQ_PLANE_EPSILON +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void PointInQuadFromBarycentric( const Vector &v1, const Vector &v2, const Vector &v3, const Vector &v4, + const Vector2D &uv, Vector &point ) +{ + // + // Think of a quad with points v1, v2, v3, v4 and u, v line segments + // find the ray from v0 edge to v1 edge at v + // + Vector vPts[2]; + VectorLerp( v1, v4, uv[1], vPts[0] ); + VectorLerp( v2, v3, uv[1], vPts[1] ); + VectorLerp( vPts[0], vPts[1], uv[0], point ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void TexCoordInQuadFromBarycentric( const Vector2D &v1, const Vector2D &v2, const Vector2D &v3, const Vector2D &v4, + const Vector2D &uv, Vector2D &texCoord ) +{ + // + // Think of a quad with points v1, v2, v3, v4 and u, v line segments + // find the ray from v0 edge to v1 edge at v + // + Vector2D vCoords[2]; + Vector2DLerp( v1, v4, uv[1], vCoords[0] ); + Vector2DLerp( v2, v3, uv[1], vCoords[1] ); + Vector2DLerp( vCoords[0], vCoords[1], uv[0], texCoord ); +} + + +//----------------------------------------------------------------------------- +// Compute point from barycentric specification +// Edge u goes from v0 to v1, edge v goes from v0 to v2 +//----------------------------------------------------------------------------- +void ComputePointFromBarycentric( const Vector& v0, const Vector& v1, const Vector& v2, + float u, float v, Vector& pt ) +{ + Vector edgeU, edgeV; + VectorSubtract( v1, v0, edgeU ); + VectorSubtract( v2, v0, edgeV ); + VectorMA( v0, u, edgeU, pt ); + VectorMA( pt, v, edgeV, pt ); +} + +void ComputePointFromBarycentric( const Vector2D& v0, const Vector2D& v1, const Vector2D& v2, + float u, float v, Vector2D& pt ) +{ + Vector2D edgeU, edgeV; + Vector2DSubtract( v1, v0, edgeU ); + Vector2DSubtract( v2, v0, edgeV ); + Vector2DMA( v0, u, edgeU, pt ); + Vector2DMA( pt, v, edgeV, pt ); +} + + +//----------------------------------------------------------------------------- +// Compute a matrix that has the correct orientation but which has an origin at +// the center of the bounds +//----------------------------------------------------------------------------- +static void ComputeCenterMatrix( const Vector& origin, const QAngle& angles, + const Vector& mins, const Vector& maxs, matrix3x4_t& matrix ) +{ + Vector centroid; + VectorAdd( mins, maxs, centroid ); + centroid *= 0.5f; + AngleMatrix( angles, matrix ); + + Vector worldCentroid; + VectorRotate( centroid, matrix, worldCentroid ); + worldCentroid += origin; + MatrixSetColumn( worldCentroid, 3, matrix ); +} + +static void ComputeCenterIMatrix( const Vector& origin, const QAngle& angles, + const Vector& mins, const Vector& maxs, matrix3x4_t& matrix ) +{ + Vector centroid; + VectorAdd( mins, maxs, centroid ); + centroid *= -0.5f; + AngleIMatrix( angles, matrix ); + + // For the translational component here, note that the origin in world space + // is T = R * C + O, (R = rotation matrix, C = centroid in local space, O = origin in world space) + // The IMatrix translation = - transpose(R) * T = -C - transpose(R) * 0 + Vector localOrigin; + VectorRotate( origin, matrix, localOrigin ); + centroid -= localOrigin; + MatrixSetColumn( centroid, 3, matrix ); +} + + +//----------------------------------------------------------------------------- +// Compute a matrix which is the absolute value of another +//----------------------------------------------------------------------------- +static inline void ComputeAbsMatrix( const matrix3x4_t& in, matrix3x4_t& out ) +{ + FloatBits(out[0][0]) = FloatAbsBits(in[0][0]); + FloatBits(out[0][1]) = FloatAbsBits(in[0][1]); + FloatBits(out[0][2]) = FloatAbsBits(in[0][2]); + FloatBits(out[1][0]) = FloatAbsBits(in[1][0]); + FloatBits(out[1][1]) = FloatAbsBits(in[1][1]); + FloatBits(out[1][2]) = FloatAbsBits(in[1][2]); + FloatBits(out[2][0]) = FloatAbsBits(in[2][0]); + FloatBits(out[2][1]) = FloatAbsBits(in[2][1]); + FloatBits(out[2][2]) = FloatAbsBits(in[2][2]); +} + + +//----------------------------------------------------------------------------- +// Compute a separating plane between two boxes (expensive!) +// Returns false if no separating plane exists +//----------------------------------------------------------------------------- +static bool ComputeSeparatingPlane( const matrix3x4_t &worldToBox1, const matrix3x4_t &box2ToWorld, + const Vector& box1Size, const Vector& box2Size, float tolerance, cplane_t* pPlane ) +{ + // The various separating planes can be either + // 1) A plane parallel to one of the box face planes + // 2) A plane parallel to the cross-product of an edge from each box + + // First, compute the basis of second box in the space of the first box + // NOTE: These basis place the origin at the centroid of each box! + matrix3x4_t box2ToBox1; + ConcatTransforms( worldToBox1, box2ToWorld, box2ToBox1 ); + + // We're going to be using the origin of box2 in the space of box1 alot, + // lets extract it from the matrix.... + Vector box2Origin; + MatrixGetColumn( box2ToBox1, 3, box2Origin ); + + // Next get the absolute values of these entries and store in absbox2ToBox1. + matrix3x4_t absBox2ToBox1; + ComputeAbsMatrix( box2ToBox1, absBox2ToBox1 ); + + // There are 15 tests to make. The first 3 involve trying planes parallel + // to the faces of the first box. + + // NOTE: The algorithm here involves finding the projections of the two boxes + // onto a particular line. If the projections on the line do not overlap, + // that means that there's a plane perpendicular to the line which separates + // the two boxes; and we've therefore found a separating plane. + + // The way we check for overlay is we find the projections of the two boxes + // onto the line, and add them up. We compare the sum with the projection + // of the relative center of box2 onto the same line. + + Vector tmp; + float boxProjectionSum; + float originProjection; + + // NOTE: For these guys, we're taking advantage of the fact that the ith + // row of the box2ToBox1 is the direction of the box1 (x,y,z)-axis + // transformed into the space of box2. + + // First side of box 1 + boxProjectionSum = box1Size.x + MatrixRowDotProduct( absBox2ToBox1, 0, box2Size ); + originProjection = FloatMakePositive( box2Origin.x ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + VectorCopy( worldToBox1[0], pPlane->normal.Base() ); + return true; + } + + // Second side of box 1 + boxProjectionSum = box1Size.y + MatrixRowDotProduct( absBox2ToBox1, 1, box2Size ); + originProjection = FloatMakePositive( box2Origin.y ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + VectorCopy( worldToBox1[1], pPlane->normal.Base() ); + return true; + } + + // Third side of box 1 + boxProjectionSum = box1Size.z + MatrixRowDotProduct( absBox2ToBox1, 2, box2Size ); + originProjection = FloatMakePositive( box2Origin.z ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + VectorCopy( worldToBox1[2], pPlane->normal.Base() ); + return true; + } + + // The next three involve checking splitting planes parallel to the + // faces of the second box. + + // NOTE: For these guys, we're taking advantage of the fact that the 0th + // column of the box2ToBox1 is the direction of the box2 x-axis + // transformed into the space of box1. + // Here, we're determining the distance of box2's center from box1's center + // by projecting it onto a line parallel to box2's axis + + // First side of box 2 + boxProjectionSum = box2Size.x + MatrixColumnDotProduct( absBox2ToBox1, 0, box1Size ); + originProjection = FloatMakePositive( MatrixColumnDotProduct( box2ToBox1, 0, box2Origin ) ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 0, pPlane->normal ); + return true; + } + + // Second side of box 2 + boxProjectionSum = box2Size.y + MatrixColumnDotProduct( absBox2ToBox1, 1, box1Size ); + originProjection = FloatMakePositive( MatrixColumnDotProduct( box2ToBox1, 1, box2Origin ) ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 1, pPlane->normal ); + return true; + } + + // Third side of box 2 + boxProjectionSum = box2Size.z + MatrixColumnDotProduct( absBox2ToBox1, 2, box1Size ); + originProjection = FloatMakePositive( MatrixColumnDotProduct( box2ToBox1, 2, box2Origin ) ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 2, pPlane->normal ); + return true; + } + + // Next check the splitting planes which are orthogonal to the pairs + // of edges, one from box1 and one from box2. As only direction matters, + // there are 9 pairs since each box has 3 distinct edge directions. + + // Here, we take advantage of the fact that the edges from box 1 are all + // axis aligned; therefore the crossproducts are simplified. Let's walk through + // the example of b1e1 x b2e1: + + // In this example, the line to check is perpendicular to b1e1 + b2e2 + // we can compute this line by taking the cross-product: + // + // [ i j k ] + // [ 1 0 0 ] = - ez j + ey k = l1 + // [ ex ey ez ] + + // Where ex, ey, ez is the components of box2's x axis in the space of box 1, + // which is == to the 0th column of of box2toBox1 + + // The projection of box1 onto this line = the absolute dot product of the box size + // against the line, which = + // AbsDot( box1Size, l1 ) = abs( -ez * box1.y ) + abs( ey * box1.z ) + + // To compute the projection of box2 onto this line, we'll do it in the space of box 2 + // + // [ i j k ] + // [ fx fy fz ] = fz j - fy k = l2 + // [ 1 0 0 ] + + // Where fx, fy, fz is the components of box1's x axis in the space of box 2, + // which is == to the 0th row of of box2toBox1 + + // The projection of box2 onto this line = the absolute dot product of the box size + // against the line, which = + // AbsDot( box2Size, l2 ) = abs( fz * box2.y ) + abs ( fy * box2.z ) + + // The projection of the relative origin position on this line is done in the + // space of box 1: + // + // originProjection = DotProduct( <-ez j + ey k>, box2Origin ) = + // -ez * box2Origin.y + ey * box2Origin.z + + // b1e1 x b2e1 + boxProjectionSum = + box1Size.y * absBox2ToBox1[2][0] + box1Size.z * absBox2ToBox1[1][0] + + box2Size.y * absBox2ToBox1[0][2] + box2Size.z * absBox2ToBox1[0][1]; + originProjection = FloatMakePositive( -box2Origin.y * box2ToBox1[2][0] + box2Origin.z * box2ToBox1[1][0] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 0, tmp ); + CrossProduct( worldToBox1[0], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + // b1e1 x b2e2 + boxProjectionSum = + box1Size.y * absBox2ToBox1[2][1] + box1Size.z * absBox2ToBox1[1][1] + + box2Size.x * absBox2ToBox1[0][2] + box2Size.z * absBox2ToBox1[0][0]; + originProjection = FloatMakePositive( -box2Origin.y * box2ToBox1[2][1] + box2Origin.z * box2ToBox1[1][1] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 1, tmp ); + CrossProduct( worldToBox1[0], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + // b1e1 x b2e3 + boxProjectionSum = + box1Size.y * absBox2ToBox1[2][2] + box1Size.z * absBox2ToBox1[1][2] + + box2Size.x * absBox2ToBox1[0][1] + box2Size.y * absBox2ToBox1[0][0]; + originProjection = FloatMakePositive( -box2Origin.y * box2ToBox1[2][2] + box2Origin.z * box2ToBox1[1][2] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 2, tmp ); + CrossProduct( worldToBox1[0], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + // b1e2 x b2e1 + boxProjectionSum = + box1Size.x * absBox2ToBox1[2][0] + box1Size.z * absBox2ToBox1[0][0] + + box2Size.y * absBox2ToBox1[1][2] + box2Size.z * absBox2ToBox1[1][1]; + originProjection = FloatMakePositive( box2Origin.x * box2ToBox1[2][0] - box2Origin.z * box2ToBox1[0][0] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 0, tmp ); + CrossProduct( worldToBox1[1], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + // b1e2 x b2e2 + boxProjectionSum = + box1Size.x * absBox2ToBox1[2][1] + box1Size.z * absBox2ToBox1[0][1] + + box2Size.x * absBox2ToBox1[1][2] + box2Size.z * absBox2ToBox1[1][0]; + originProjection = FloatMakePositive( box2Origin.x * box2ToBox1[2][1] - box2Origin.z * box2ToBox1[0][1] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 1, tmp ); + CrossProduct( worldToBox1[1], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + // b1e2 x b2e3 + boxProjectionSum = + box1Size.x * absBox2ToBox1[2][2] + box1Size.z * absBox2ToBox1[0][2] + + box2Size.x * absBox2ToBox1[1][1] + box2Size.y * absBox2ToBox1[1][0]; + originProjection = FloatMakePositive( box2Origin.x * box2ToBox1[2][2] - box2Origin.z * box2ToBox1[0][2] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 2, tmp ); + CrossProduct( worldToBox1[1], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + // b1e3 x b2e1 + boxProjectionSum = + box1Size.x * absBox2ToBox1[1][0] + box1Size.y * absBox2ToBox1[0][0] + + box2Size.y * absBox2ToBox1[2][2] + box2Size.z * absBox2ToBox1[2][1]; + originProjection = FloatMakePositive( -box2Origin.x * box2ToBox1[1][0] + box2Origin.y * box2ToBox1[0][0] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 0, tmp ); + CrossProduct( worldToBox1[2], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + // b1e3 x b2e2 + boxProjectionSum = + box1Size.x * absBox2ToBox1[1][1] + box1Size.y * absBox2ToBox1[0][1] + + box2Size.x * absBox2ToBox1[2][2] + box2Size.z * absBox2ToBox1[2][0]; + originProjection = FloatMakePositive( -box2Origin.x * box2ToBox1[1][1] + box2Origin.y * box2ToBox1[0][1] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 1, tmp ); + CrossProduct( worldToBox1[2], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + // b1e3 x b2e3 + boxProjectionSum = + box1Size.x * absBox2ToBox1[1][2] + box1Size.y * absBox2ToBox1[0][2] + + box2Size.x * absBox2ToBox1[2][1] + box2Size.y * absBox2ToBox1[2][0]; + originProjection = FloatMakePositive( -box2Origin.x * box2ToBox1[1][2] + box2Origin.y * box2ToBox1[0][2] ) + tolerance; + if ( FloatBits(originProjection) > FloatBits(boxProjectionSum) ) + { + MatrixGetColumn( box2ToWorld, 2, tmp ); + CrossProduct( worldToBox1[2], tmp.Base(), pPlane->normal.Base() ); + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Compute a separating plane between two boxes (expensive!) +// Returns false if no separating plane exists +//----------------------------------------------------------------------------- +bool ComputeSeparatingPlane( const Vector& org1, const QAngle& angles1, const Vector& min1, const Vector& max1, + const Vector& org2, const QAngle& angles2, const Vector& min2, const Vector& max2, + float tolerance, cplane_t* pPlane ) +{ + matrix3x4_t worldToBox1, box2ToWorld; + ComputeCenterIMatrix( org1, angles1, min1, max1, worldToBox1 ); + ComputeCenterMatrix( org2, angles2, min2, max2, box2ToWorld ); + + // Then compute the size of the two boxes + Vector box1Size, box2Size; + VectorSubtract( max1, min1, box1Size ); + VectorSubtract( max2, min2, box2Size ); + box1Size *= 0.5f; + box2Size *= 0.5f; + + return ComputeSeparatingPlane( worldToBox1, box2ToWorld, box1Size, box2Size, tolerance, pPlane ); +} + + +//----------------------------------------------------------------------------- +// Swept OBB test +//----------------------------------------------------------------------------- +bool IsRayIntersectingOBB( const Ray_t &ray, const Vector& org, const QAngle& angles, + const Vector& mins, const Vector& maxs ) +{ + if ( angles == vec3_angle ) + { + Vector vecWorldMins, vecWorldMaxs; + VectorAdd( org, mins, vecWorldMins ); + VectorAdd( org, maxs, vecWorldMaxs ); + return IsBoxIntersectingRay( vecWorldMins, vecWorldMaxs, ray ); + } + + if ( ray.m_IsRay ) + { + matrix3x4_t worldToBox; + AngleIMatrix( angles, org, worldToBox ); + + Ray_t rotatedRay; + VectorTransform( ray.m_Start, worldToBox, rotatedRay.m_Start ); + VectorRotate( ray.m_Delta, worldToBox, rotatedRay.m_Delta ); + rotatedRay.m_StartOffset = vec3_origin; + rotatedRay.m_Extents = vec3_origin; + rotatedRay.m_IsRay = ray.m_IsRay; + rotatedRay.m_IsSwept = ray.m_IsSwept; + + return IsBoxIntersectingRay( mins, maxs, rotatedRay ); + } + + if ( !ray.m_IsSwept ) + { + cplane_t plane; + return ComputeSeparatingPlane( ray.m_Start, vec3_angle, -ray.m_Extents, ray.m_Extents, + org, angles, mins, maxs, 0.0f, &plane ) == false; + } + + // NOTE: See the comments in ComputeSeparatingPlane to understand this math + + // First, compute the basis of box in the space of the ray + // NOTE: These basis place the origin at the centroid of each box! + matrix3x4_t worldToBox1, box2ToWorld; + ComputeCenterMatrix( org, angles, mins, maxs, box2ToWorld ); + + // Find the center + extents of an AABB surrounding the ray + Vector vecRayCenter; + VectorMA( ray.m_Start, 0.5, ray.m_Delta, vecRayCenter ); + vecRayCenter *= -1.0f; + SetIdentityMatrix( worldToBox1 ); + MatrixSetColumn( vecRayCenter, 3, worldToBox1 ); + + Vector box1Size; + box1Size.x = ray.m_Extents.x + FloatMakePositive( ray.m_Delta.x ) * 0.5f; + box1Size.y = ray.m_Extents.y + FloatMakePositive( ray.m_Delta.y ) * 0.5f; + box1Size.z = ray.m_Extents.z + FloatMakePositive( ray.m_Delta.z ) * 0.5f; + + // Then compute the size of the box + Vector box2Size; + VectorSubtract( maxs, mins, box2Size ); + box2Size *= 0.5f; + + // Do an OBB test of the box with the AABB surrounding the ray + cplane_t plane; + if ( ComputeSeparatingPlane( worldToBox1, box2ToWorld, box1Size, box2Size, 0.0f, &plane ) ) + return false; + + // Now deal with the planes which are the cross products of the ray sweep direction vs box edges + Vector vecRayDirection = ray.m_Delta; + VectorNormalize( vecRayDirection ); + + // Need a vector between ray center vs box center measured in the space of the ray (world) + Vector vecCenterDelta; + vecCenterDelta.x = box2ToWorld[0][3] - ray.m_Start.x; + vecCenterDelta.y = box2ToWorld[1][3] - ray.m_Start.y; + vecCenterDelta.z = box2ToWorld[2][3] - ray.m_Start.z; + + // Rotate the ray direction into the space of the OBB + Vector vecAbsRayDirBox2; + VectorIRotate( vecRayDirection, box2ToWorld, vecAbsRayDirBox2 ); + + // Make abs versions of the ray in world space + ray in box2 space + VectorAbs( vecAbsRayDirBox2, vecAbsRayDirBox2 ); + + // Now do the work for the planes which are perpendicular to the edges of the AABB + // and the sweep direction edges... + + // In this example, the line to check is perpendicular to box edge x + ray delta + // we can compute this line by taking the cross-product: + // + // [ i j k ] + // [ 1 0 0 ] = - dz j + dy k = l1 + // [ dx dy dz ] + + // Where dx, dy, dz is the ray delta (normalized) + + // The projection of the box onto this line = the absolute dot product of the box size + // against the line, which = + // AbsDot( vecBoxHalfDiagonal, l1 ) = abs( -dz * vecBoxHalfDiagonal.y ) + abs( dy * vecBoxHalfDiagonal.z ) + + // Because the plane contains the sweep direction, the sweep will produce + // no extra projection onto the line normal to the plane. + // Therefore all we need to do is project the ray extents onto this line also: + // AbsDot( ray.m_Extents, l1 ) = abs( -dz * ray.m_Extents.y ) + abs( dy * ray.m_Extents.z ) + + Vector vecPlaneNormal; + + // box x x ray delta + CrossProduct( vecRayDirection, Vector( box2ToWorld[0][0], box2ToWorld[1][0], box2ToWorld[2][0] ), vecPlaneNormal ); + float flCenterDeltaProjection = FloatMakePositive( DotProduct( vecPlaneNormal, vecCenterDelta ) ); + float flBoxProjectionSum = + vecAbsRayDirBox2.z * box2Size.y + vecAbsRayDirBox2.y * box2Size.z + + DotProductAbs( vecPlaneNormal, ray.m_Extents ); + if ( FloatBits(flCenterDeltaProjection) > FloatBits(flBoxProjectionSum) ) + return false; + + // box y x ray delta + CrossProduct( vecRayDirection, Vector( box2ToWorld[0][1], box2ToWorld[1][1], box2ToWorld[2][1] ), vecPlaneNormal ); + flCenterDeltaProjection = FloatMakePositive( DotProduct( vecPlaneNormal, vecCenterDelta ) ); + flBoxProjectionSum = + vecAbsRayDirBox2.z * box2Size.x + vecAbsRayDirBox2.x * box2Size.z + + DotProductAbs( vecPlaneNormal, ray.m_Extents ); + if ( FloatBits(flCenterDeltaProjection) > FloatBits(flBoxProjectionSum) ) + return false; + + // box z x ray delta + CrossProduct( vecRayDirection, Vector( box2ToWorld[0][2], box2ToWorld[1][2], box2ToWorld[2][2] ), vecPlaneNormal ); + flCenterDeltaProjection = FloatMakePositive( DotProduct( vecPlaneNormal, vecCenterDelta ) ); + flBoxProjectionSum = + vecAbsRayDirBox2.y * box2Size.x + vecAbsRayDirBox2.x * box2Size.y + + DotProductAbs( vecPlaneNormal, ray.m_Extents ); + if ( FloatBits(flCenterDeltaProjection) > FloatBits(flBoxProjectionSum) ) + return false; + + return true; +} + +/*-------------------------------------------------------------------------- +Purpose: + +NOTE: +triangle points are given in clockwise order (aabb-triangle test) + + 1 edge0 = 1 - 0 + | \ edge1 = 2 - 1 + | \ edge2 = 0 - 2 + | \ + | \ + 0-----2 + +--------------------------------------------------------------------------*/ + +//----------------------------------------------------------------------------- +// Purpose: find the minima and maxima of the 3 given values +//----------------------------------------------------------------------------- +inline void FindMinMax( float v1, float v2, float v3, float &min, float &max ) +{ + min = max = v1; + if ( v2 < min ) { min = v2; } + if ( v2 > max ) { max = v2; } + if ( v3 < min ) { min = v3; } + if ( v3 > max ) { max = v3; } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline bool AxisTestEdgeCrossX2( float flEdgeZ, float flEdgeY, float flAbsEdgeZ, float flAbsEdgeY, + const Vector &p1, const Vector &p3, const Vector &vecExtents, + float flTolerance ) +{ + // Cross Product( axialX(1,0,0) x edge ): x = 0.0f, y = edge.z, z = -edge.y + // Triangle Point Distances: dist(x) = normal.y * pt(x).y + normal.z * pt(x).z + float flDist1 = flEdgeZ * p1.y - flEdgeY * p1.z; + float flDist3 = flEdgeZ * p3.y - flEdgeY * p3.z; + + // Extents are symmetric: dist = abs( normal.y ) * extents.y + abs( normal.z ) * extents.z + float flDistBox = flAbsEdgeZ * vecExtents.y + flAbsEdgeY * vecExtents.z; + + // Either dist1, dist3 is the closest point to the box, determine which and test of overlap with box(AABB). + if ( flDist1 < flDist3 ) + { + if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist3 < -( flDistBox + flTolerance ) ) ) + return false; + } + else + { + if ( ( flDist3 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) + return false; + } + + return true; +} + +//-------------------------------------------------------------------------- +// Purpose: +//-------------------------------------------------------------------------- +inline bool AxisTestEdgeCrossX3( float flEdgeZ, float flEdgeY, float flAbsEdgeZ, float flAbsEdgeY, + const Vector &p1, const Vector &p2, const Vector &vecExtents, + float flTolerance ) +{ + // Cross Product( axialX(1,0,0) x edge ): x = 0.0f, y = edge.z, z = -edge.y + // Triangle Point Distances: dist(x) = normal.y * pt(x).y + normal.z * pt(x).z + float flDist1 = flEdgeZ * p1.y - flEdgeY * p1.z; + float flDist2 = flEdgeZ * p2.y - flEdgeY * p2.z; + + // Extents are symmetric: dist = abs( normal.y ) * extents.y + abs( normal.z ) * extents.z + float flDistBox = flAbsEdgeZ * vecExtents.y + flAbsEdgeY * vecExtents.z; + + // Either dist1, dist2 is the closest point to the box, determine which and test of overlap with box(AABB). + if ( flDist1 < flDist2 ) + { + if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist2 < -( flDistBox + flTolerance ) ) ) + return false; + } + else + { + if ( ( flDist2 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) + return false; + } + + return true; +} + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +inline bool AxisTestEdgeCrossY2( float flEdgeZ, float flEdgeX, float flAbsEdgeZ, float flAbsEdgeX, + const Vector &p1, const Vector &p3, const Vector &vecExtents, + float flTolerance ) +{ + // Cross Product( axialY(0,1,0) x edge ): x = -edge.z, y = 0.0f, z = edge.x + // Triangle Point Distances: dist(x) = normal.x * pt(x).x + normal.z * pt(x).z + float flDist1 = -flEdgeZ * p1.x + flEdgeX * p1.z; + float flDist3 = -flEdgeZ * p3.x + flEdgeX * p3.z; + + // Extents are symmetric: dist = abs( normal.x ) * extents.x + abs( normal.z ) * extents.z + float flDistBox = flAbsEdgeZ * vecExtents.x + flAbsEdgeX * vecExtents.z; + + // Either dist1, dist3 is the closest point to the box, determine which and test of overlap with box(AABB). + if ( flDist1 < flDist3 ) + { + if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist3 < -( flDistBox + flTolerance ) ) ) + return false; + } + else + { + if ( ( flDist3 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) + return false; + } + + return true; +} + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +inline bool AxisTestEdgeCrossY3( float flEdgeZ, float flEdgeX, float flAbsEdgeZ, float flAbsEdgeX, + const Vector &p1, const Vector &p2, const Vector &vecExtents, + float flTolerance ) +{ + // Cross Product( axialY(0,1,0) x edge ): x = -edge.z, y = 0.0f, z = edge.x + // Triangle Point Distances: dist(x) = normal.x * pt(x).x + normal.z * pt(x).z + float flDist1 = -flEdgeZ * p1.x + flEdgeX * p1.z; + float flDist2 = -flEdgeZ * p2.x + flEdgeX * p2.z; + + // Extents are symmetric: dist = abs( normal.x ) * extents.x + abs( normal.z ) * extents.z + float flDistBox = flAbsEdgeZ * vecExtents.x + flAbsEdgeX * vecExtents.z; + + // Either dist1, dist2 is the closest point to the box, determine which and test of overlap with box(AABB). + if ( flDist1 < flDist2 ) + { + if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist2 < -( flDistBox + flTolerance ) ) ) + return false; + } + else + { + if ( ( flDist2 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) + return false; + } + + return true; +} + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +inline bool AxisTestEdgeCrossZ1( float flEdgeY, float flEdgeX, float flAbsEdgeY, float flAbsEdgeX, + const Vector &p2, const Vector &p3, const Vector &vecExtents, + float flTolerance ) +{ + // Cross Product( axialZ(0,0,1) x edge ): x = edge.y, y = -edge.x, z = 0.0f + // Triangle Point Distances: dist(x) = normal.x * pt(x).x + normal.y * pt(x).y + float flDist2 = flEdgeY * p2.x - flEdgeX * p2.y; + float flDist3 = flEdgeY * p3.x - flEdgeX * p3.y; + + // Extents are symmetric: dist = abs( normal.x ) * extents.x + abs( normal.y ) * extents.y + float flDistBox = flAbsEdgeY * vecExtents.x + flAbsEdgeX * vecExtents.y; + + // Either dist2, dist3 is the closest point to the box, determine which and test of overlap with box(AABB). + if ( flDist3 < flDist2 ) + { + if ( ( flDist3 > ( flDistBox + flTolerance ) ) || ( flDist2 < -( flDistBox + flTolerance ) ) ) + return false; + } + else + { + if ( ( flDist2 > ( flDistBox + flTolerance ) ) || ( flDist3 < -( flDistBox + flTolerance ) ) ) + return false; + } + + return true; +} + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +inline bool AxisTestEdgeCrossZ2( float flEdgeY, float flEdgeX, float flAbsEdgeY, float flAbsEdgeX, + const Vector &p1, const Vector &p3, const Vector &vecExtents, + float flTolerance ) +{ + // Cross Product( axialZ(0,0,1) x edge ): x = edge.y, y = -edge.x, z = 0.0f + // Triangle Point Distances: dist(x) = normal.x * pt(x).x + normal.y * pt(x).y + float flDist1 = flEdgeY * p1.x - flEdgeX * p1.y; + float flDist3 = flEdgeY * p3.x - flEdgeX * p3.y; + + // Extents are symmetric: dist = abs( normal.x ) * extents.x + abs( normal.y ) * extents.y + float flDistBox = flAbsEdgeY * vecExtents.x + flAbsEdgeX * vecExtents.y; + + // Either dist1, dist3 is the closest point to the box, determine which and test of overlap with box(AABB). + if ( flDist1 < flDist3 ) + { + if ( ( flDist1 > ( flDistBox + flTolerance ) ) || ( flDist3 < -( flDistBox + flTolerance ) ) ) + return false; + } + else + { + if ( ( flDist3 > ( flDistBox + flTolerance ) ) || ( flDist1 < -( flDistBox + flTolerance ) ) ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Test for an intersection (overlap) between an axial-aligned bounding +// box (AABB) and a triangle. +// +// Using the "Separating-Axis Theorem" to test for intersections between +// a triangle and an axial-aligned bounding box (AABB). +// 1. 3 Axis Planes - x, y, z +// 2. 9 Edge Planes Tests - the 3 edges of the triangle crossed with all 3 axial +// planes (x, y, z) +// 3. 1 Face Plane - the triangle plane (cplane_t plane below) +// Output: false = separating axis (no intersection) +// true = intersection +//----------------------------------------------------------------------------- +bool IsBoxIntersectingTriangle( const Vector &vecBoxCenter, const Vector &vecBoxExtents, + const Vector &v1, const Vector &v2, const Vector &v3, + const cplane_t &plane, float flTolerance ) +{ + // Test the axial planes (x,y,z) against the min, max of the triangle. + float flMin, flMax; + Vector p1, p2, p3; + + // x plane + p1.x = v1.x - vecBoxCenter.x; + p2.x = v2.x - vecBoxCenter.x; + p3.x = v3.x - vecBoxCenter.x; + FindMinMax( p1.x, p2.x, p3.x, flMin, flMax ); + if ( ( flMin > ( vecBoxExtents.x + flTolerance ) ) || ( flMax < -( vecBoxExtents.x + flTolerance ) ) ) + return false; + + // y plane + p1.y = v1.y - vecBoxCenter.y; + p2.y = v2.y - vecBoxCenter.y; + p3.y = v3.y - vecBoxCenter.y; + FindMinMax( p1.y, p2.y, p3.y, flMin, flMax ); + if ( ( flMin > ( vecBoxExtents.y + flTolerance ) ) || ( flMax < -( vecBoxExtents.y + flTolerance ) ) ) + return false; + + // z plane + p1.z = v1.z - vecBoxCenter.z; + p2.z = v2.z - vecBoxCenter.z; + p3.z = v3.z - vecBoxCenter.z; + FindMinMax( p1.z, p2.z, p3.z, flMin, flMax ); + if ( ( flMin > ( vecBoxExtents.z + flTolerance ) ) || ( flMax < -( vecBoxExtents.z + flTolerance ) ) ) + return false; + + // Test the 9 edge cases. + Vector vecEdge, vecAbsEdge; + + // edge 0 (cross x,y,z) + vecEdge = p2 - p1; + vecAbsEdge.y = FloatMakePositive( vecEdge.y ); + vecAbsEdge.z = FloatMakePositive( vecEdge.z ); + if ( !AxisTestEdgeCrossX2( vecEdge.z, vecEdge.y, vecAbsEdge.z, vecAbsEdge.y, p1, p3, vecBoxExtents, flTolerance ) ) + return false; + + vecAbsEdge.x = FloatMakePositive( vecEdge.x ); + if ( !AxisTestEdgeCrossY2( vecEdge.z, vecEdge.x, vecAbsEdge.z, vecAbsEdge.x, p1, p3, vecBoxExtents, flTolerance ) ) + return false; + + if ( !AxisTestEdgeCrossZ1( vecEdge.y, vecEdge.x, vecAbsEdge.y, vecAbsEdge.x, p2, p3, vecBoxExtents, flTolerance ) ) + return false; + + // edge 1 (cross x,y,z) + vecEdge = p3 - p2; + vecAbsEdge.y = FloatMakePositive( vecEdge.y ); + vecAbsEdge.z = FloatMakePositive( vecEdge.z ); + if ( !AxisTestEdgeCrossX2( vecEdge.z, vecEdge.y, vecAbsEdge.z, vecAbsEdge.y, p1, p2, vecBoxExtents, flTolerance ) ) + return false; + + vecAbsEdge.x = FloatMakePositive( vecEdge.x ); + if ( !AxisTestEdgeCrossY2( vecEdge.z, vecEdge.x, vecAbsEdge.z, vecAbsEdge.x, p1, p2, vecBoxExtents, flTolerance ) ) + return false; + + if ( !AxisTestEdgeCrossZ2( vecEdge.y, vecEdge.x, vecAbsEdge.y, vecAbsEdge.x, p1, p3, vecBoxExtents, flTolerance ) ) + return false; + + // edge 2 (cross x,y,z) + vecEdge = p1 - p3; + vecAbsEdge.y = FloatMakePositive( vecEdge.y ); + vecAbsEdge.z = FloatMakePositive( vecEdge.z ); + if ( !AxisTestEdgeCrossX3( vecEdge.z, vecEdge.y, vecAbsEdge.z, vecAbsEdge.y, p1, p2, vecBoxExtents, flTolerance ) ) + return false; + + vecAbsEdge.x = FloatMakePositive( vecEdge.x ); + if ( !AxisTestEdgeCrossY3( vecEdge.z, vecEdge.x, vecAbsEdge.z, vecAbsEdge.x, p1, p2, vecBoxExtents, flTolerance ) ) + return false; + + if ( !AxisTestEdgeCrossZ1( vecEdge.y, vecEdge.x, vecAbsEdge.y, vecAbsEdge.x, p2, p3, vecBoxExtents, flTolerance ) ) + return false; + + // Test against the triangle face plane. + Vector vecMin, vecMax; + VectorSubtract( vecBoxCenter, vecBoxExtents, vecMin ); + VectorAdd( vecBoxCenter, vecBoxExtents, vecMax ); + if ( BoxOnPlaneSide( vecMin, vecMax, &plane ) != 3 ) + return false; + + return true; +} + +// NOTE: JAY: This is untested code based on Real-time Collision Detection by Ericson +#if 0 +Vector CalcClosestPointOnTriangle( const Vector &P, const Vector &v0, const Vector &v1, const Vector &v2 ) +{ + Vector e0 = v1 - v0; + Vector e1 = v2 - v0; + Vector p0 = P - v0; + + // voronoi region of v0 + float d1 = DotProduct( e0, p0 ); + float d2 = DotProduct( e1, p0 ); + if (d1 <= 0.0f && d2 <= 0.0f) + return v0; + + // voronoi region of v1 + Vector p1 = P - v1; + float d3 = DotProduct( e0, p1 ); + float d4 = DotProduct( e1, p1 ); + if (d3 >=0.0f && d4 <= d3) + return v1; + + // voronoi region of e0 (v0-v1) + float ve2 = d1*d4 - d3*d2; + if ( ve2 <= 0.0f && d1 >= 0.0f && d3 <= 0.0f ) + { + float v = d1 / (d1-d3); + return v0 + v * e0; + } + // voronoi region of v2 + Vector p2 = P - v2; + float d5 = DotProduct( e0, p2 ); + float d6 = DotProduct( e1, p2 ); + if (d6 >= 0.0f && d5 <= d6) + return v2; + // voronoi region of e1 + float ve1 = d5*d2 - d1*d6; + if (ve1 <= 0.0f && d2 >= 0.0f && d6 >= 0.0f) + { + float w = d2 / (d2-d6); + return v0 + w * e1; + } + // voronoi region on e2 + float ve0 = d3*d6 - d5*d4; + if ( ve0 <= 0.0f && (d4-d3) >= 0.0f && (d5-d6) >= 0.0f ) + { + float w = (d4-d3)/((d4-d3) + (d5-d6)); + return v1 + w * (v2-v1); + } + // voronoi region of v0v1v2 triangle + float denom = 1.0f / (ve0+ve1+ve2); + float v = ve1*denom; + float w = ve2 * denom; + return v0 + e0 * v + e1 * w; +} +#endif + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/public/collisionutils.h b/public/collisionutils.h index 69006419..55437b87 100644 --- a/public/collisionutils.h +++ b/public/collisionutils.h @@ -1,348 +1,348 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Common collision utility methods -// -// $Header: $ -// $NoKeywords: $ -//=============================================================================// - -#ifndef COLLISIONUTILS_H -#define COLLISIONUTILS_H - -#include "tier0/platform.h" - -#ifdef _WIN32 -#pragma once -#endif - - -//----------------------------------------------------------------------------- -// forward declarations -//----------------------------------------------------------------------------- -struct Ray_t; -class Vector; -class Vector2D; -class Vector4D; -struct cplane_t; -class QAngle; -class CBaseTrace; -struct matrix3x4_t; - - -//----------------------------------------------------------------------------- -// -// IntersectRayWithTriangle -// -// Intersects a ray with a triangle, returns distance t along ray. -// t will be less than zero if no intersection occurred -// oneSided will cull collisions which approach the triangle from the back -// side, assuming the vertices are specified in counter-clockwise order -// The vertices need not be specified in that order if oneSided is not used -// -//----------------------------------------------------------------------------- -float IntersectRayWithTriangle( const Ray_t& ray, - const Vector& v1, const Vector& v2, const Vector& v3, - bool oneSided ); - -//----------------------------------------------------------------------------- -// -// ComputeIntersectionBarycentricCoordinates -// -// Figures out the barycentric coordinates (u,v) where a ray hits a -// triangle. Note that this will ignore the ray extents, and it also ignores -// the ray length. Note that the edge from v1->v2 represents u (v2: u = 1), -// and the edge from v1->v3 represents v (v3: v = 1). It returns false -// if the ray is parallel to the triangle (or when t is specified if t is less -// than zero). -// -//----------------------------------------------------------------------------- -bool ComputeIntersectionBarycentricCoordinates( const Ray_t& ray, - const Vector& v1, const Vector& v2, const Vector& v3, float& u, float& v, - float *t = 0 ); - -//----------------------------------------------------------------------------- -// -// IntersectRayWithRay -// -// Returns whether or not there was an intersection. The "t" paramter is the -// distance along ray0 and the "s" parameter is the distance along ray1. If -// the two lines to not intersect the "t" and "s" represent the closest approach. -// "t" and "s" will not change if the rays are parallel. -// -//----------------------------------------------------------------------------- -bool IntersectRayWithRay( const Ray_t &ray0, const Ray_t &ray1, float &t, float &s ); - - -//----------------------------------------------------------------------------- -// -// IntersectRayWithSphere -// -// Returns whether or not there was an intersection. Returns the two intersection points. -// NOTE: The point of closest approach can be found at the average t value. -// -//----------------------------------------------------------------------------- -bool IntersectRayWithSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, const Vector &vecSphereCenter, float flRadius, float *pT1, float *pT2 ); - - -//----------------------------------------------------------------------------- -// -// IntersectInfiniteRayWithSphere -// -// Returns whether or not there was an intersection of a sphere against an infinitely -// extending ray. -// Returns the two intersection points -// -//----------------------------------------------------------------------------- -bool IntersectInfiniteRayWithSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, - const Vector &vecSphereCenter, float flRadius, float *pT1, float *pT2 ); - - -//----------------------------------------------------------------------------- -// -// IntersectRayWithPlane -// -// Intersects a ray with a plane, returns distance t along ray. -// t will be less than zero the intersection occurs in the opposite direction of the ray. -// -//----------------------------------------------------------------------------- -float IntersectRayWithPlane( const Ray_t& ray, const cplane_t& plane ); -float IntersectRayWithPlane( const Vector& org, const Vector& dir, const cplane_t& plane ); -float IntersectRayWithPlane( const Vector& org, const Vector& dir, const Vector& normal, float dist ); - -// This version intersects a ray with an axis-aligned plane -float IntersectRayWithAAPlane( const Vector& vecStart, const Vector& vecEnd, int nAxis, float flSign, float flDist ); - - -//----------------------------------------------------------------------------- -// IntersectRayWithBox -// -// Purpose: Computes the intersection of a ray with a box (AABB) -// Output : Returns true if there is an intersection + trace information -//----------------------------------------------------------------------------- -bool IntersectRayWithBox( const Vector &rayStart, const Vector &rayDelta, const Vector &boxMins, const Vector &boxMaxs, float epsilon, CBaseTrace *pTrace ); -bool IntersectRayWithBox( const Ray_t &ray, const Vector &boxMins, const Vector &boxMaxs, float epsilon, CBaseTrace *pTrace ); - -//----------------------------------------------------------------------------- -// Intersects a ray against a box -//----------------------------------------------------------------------------- -struct BoxTraceInfo_t -{ - float t1; - float t2; - int hitside; - bool startsolid; -}; - -bool IntersectRayWithBox( const Vector &vecRayStart, const Vector &vecRayDelta, - const Vector &boxMins, const Vector &boxMaxs, float flTolerance, BoxTraceInfo_t *pTrace ); - - -//----------------------------------------------------------------------------- -// IntersectRayWithOBB -// -// Purpose: Computes the intersection of a ray with a oriented box (OBB) -// Output : Returns true if there is an intersection + trace information -//----------------------------------------------------------------------------- -bool IntersectRayWithOBB( const Vector &vecRayStart, const Vector &vecRayDelta, - const matrix3x4_t &matOBBToWorld, const Vector &vecOBBMins, const Vector &vecOBBMaxs, - float flTolerance, CBaseTrace *pTrace ); - -bool IntersectRayWithOBB( const Vector &vecRayOrigin, const Vector &vecRayDelta, - const Vector &vecBoxOrigin, const QAngle &angBoxRotation, - const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ); - -bool IntersectRayWithOBB( const Ray_t &ray, const Vector &vecBoxOrigin, const QAngle &angBoxRotation, - const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ); - -bool IntersectRayWithOBB( const Ray_t &ray, const matrix3x4_t &matOBBToWorld, - const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ); - -bool IntersectRayWithOBB( const Vector &vecRayStart, const Vector &vecRayDelta, - const matrix3x4_t &matOBBToWorld, const Vector &vecOBBMins, const Vector &vecOBBMaxs, - float flTolerance, BoxTraceInfo_t *pTrace ); - -//----------------------------------------------------------------------------- -// -// IsSphereIntersectingSphere -// -// returns true if there's an intersection between sphere and sphere -// -//----------------------------------------------------------------------------- -bool IsSphereIntersectingSphere( const Vector& center1, float radius1, - const Vector& center2, float radius2 ); - - -//----------------------------------------------------------------------------- -// -// IsBoxIntersectingSphere -// -// returns true if there's an intersection between box and sphere -// -//----------------------------------------------------------------------------- -bool IsBoxIntersectingSphere( const Vector& boxMin, const Vector& boxMax, - const Vector& center, float radius ); - -bool IsBoxIntersectingSphereExtents( const Vector& boxCenter, const Vector& boxHalfDiag, - const Vector& center, float radius ); - -//----------------------------------------------------------------------------- -// returns true if there's an intersection between ray and sphere -//----------------------------------------------------------------------------- -bool IsRayIntersectingSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, - const Vector &vecSphereCenter, float flRadius, float flTolerance = 0.0f ); - - -//----------------------------------------------------------------------------- -// -// IsCircleIntersectingRectangle -// -// returns true if there's an intersection between rectangle and circle -// -//----------------------------------------------------------------------------- -bool IsCircleIntersectingRectangle( const Vector2D& boxMin, const Vector2D& boxMax, - const Vector2D& center, float radius ); - - -//----------------------------------------------------------------------------- -// -// IsBoxIntersectingBox -// -// returns true if there's an intersection between two boxes -// -//----------------------------------------------------------------------------- -bool IsBoxIntersectingBox( const Vector& boxMin1, const Vector& boxMax1, - const Vector& boxMin2, const Vector& boxMax2 ); - -bool IsBoxIntersectingBoxExtents( const Vector& boxCenter1, const Vector& boxHalfDiagonal1, - const Vector& boxCenter2, const Vector& boxHalfDiagonal2 ); - - -//----------------------------------------------------------------------------- -// -// IsOBBIntersectingOBB -// -// returns true if there's an intersection between two OBBs -// -//----------------------------------------------------------------------------- -bool IsOBBIntersectingOBB( const Vector &vecOrigin1, const QAngle &vecAngles1, const Vector& boxMin1, const Vector& boxMax1, - const Vector &vecOrigin2, const QAngle &vecAngles2, const Vector& boxMin2, const Vector& boxMax2, float flTolerance = 0.0f ); - - -//----------------------------------------------------------------------------- -// -// IsBoxIntersectingRay -// -// returns true if there's an intersection between box and ray -// -//----------------------------------------------------------------------------- -bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, - const Vector& origin, const Vector& delta, float flTolerance = 0.0f ); - -bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, - const Ray_t& ray, float flTolerance = 0.0f ); - -bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, - const Vector& origin, const Vector& delta, - const Vector& invDelta, float flTolerance = 0.0f ); - -//----------------------------------------------------------------------------- -// -// IsPointInBox -// -// returns true if the point is in the box -// -//----------------------------------------------------------------------------- -bool IsPointInBox( const Vector& pt, const Vector& boxMin, const Vector& boxMax ); - - -//----------------------------------------------------------------------------- -// Purpose: returns true if pt intersects the truncated cone -// origin - cone tip, axis unit cone axis, cosAngle - cosine of cone axis to surface angle -//----------------------------------------------------------------------------- -bool IsPointInCone( const Vector &pt, const Vector &origin, const Vector &axis, float cosAngle, float length ); - -//----------------------------------------------------------------------------- -// Intersects a plane with a triangle (using barycentric definition) -// The return value, in pIntersection, is an array of barycentric coordinates -// describing at most 2 intersection points. -// The return value is the number of intersection points -//----------------------------------------------------------------------------- -int IntersectTriangleWithPlaneBarycentric( const Vector& org, const Vector& edgeU, const Vector& edgeV, - const Vector4D& plane, Vector2D* pIntersection ); - -//----------------------------------------------------------------------------- -// -// PointInQuadBarycentric -// -// Given a point and a quad in a plane return the u and v (barycentric) positions -// of the point relative to the quad. The points (v1,v2,v3,v4) should be given -// in a counter-clockwise order with v1 acting as the primary corner (u=0, v=0). -// Thus, u0 = v2 - v1, and v0 = v4 - v1. -// -//----------------------------------------------------------------------------- - -enum QuadBarycentricRetval_t -{ - BARY_QUADRATIC_FALSE = 0, - BARY_QUADRATIC_TRUE = 1, - BARY_QUADRATIC_NEGATIVE_DISCRIMINANT = 2 -}; - -QuadBarycentricRetval_t PointInQuadToBarycentric( const Vector &v1, const Vector &v2, - const Vector &v3, const Vector &v4, const Vector &point, Vector2D &uv ); - - -void PointInQuadFromBarycentric( const Vector &v1, const Vector &v2, const Vector &v3, const Vector &v4, - const Vector2D &uv, Vector &point ); -void TexCoordInQuadFromBarycentric( const Vector2D &v1, const Vector2D &v2, const Vector2D &v3, const Vector2D &v4, - const Vector2D &uv, Vector2D &texCoord ); - - -//----------------------------------------------------------------------------- -// Compute point from barycentric specification -// Edge u goes from v0 to v1, edge v goes from v0 to v2 -//----------------------------------------------------------------------------- -void ComputePointFromBarycentric( const Vector& v0, const Vector& v1, const Vector& v2, - float u, float v, Vector& pt ); -void ComputePointFromBarycentric( const Vector2D& v0, const Vector2D& v1, const Vector2D& v2, - float u, float v, Vector2D& pt ); - - -//----------------------------------------------------------------------------- -// Swept OBB test -//----------------------------------------------------------------------------- -bool IsRayIntersectingOBB( const Ray_t &ray, const Vector& org, const QAngle& angles, - const Vector& mins, const Vector& maxs ); - - -//----------------------------------------------------------------------------- -// Compute a separating plane between two boxes (expensive!) -// Returns false if no separating plane exists -//----------------------------------------------------------------------------- -bool ComputeSeparatingPlane( const Vector& org1, const QAngle& angles1, const Vector& min1, const Vector& max1, - const Vector& org2, const QAngle& angles2, const Vector& min2, const Vector& max2, - float tolerance, cplane_t* pPlane ); - -//----------------------------------------------------------------------------- -// IsBoxIntersectingTriangle -// -// Test for an intersection (overlap) between an axial-aligned bounding -// box (AABB) and a triangle. -// -// Triangle points are in counter-clockwise order with the normal facing "out." -// -// Using the "Separating-Axis Theorem" to test for intersections between -// a triangle and an axial-aligned bounding box (AABB). -// 1. 3 Axis Plane Tests - x, y, z -// 2. 9 Edge Planes Tests - the 3 edges of the triangle crossed with all 3 axial -// planes (x, y, z) -// 3. 1 Face Plane Test - the plane the triangle resides in (cplane_t plane) -//----------------------------------------------------------------------------- -bool IsBoxIntersectingTriangle( const Vector &vecBoxCenter, const Vector &vecBoxExtents, - const Vector &v1, const Vector &v2, const Vector &v3, - const cplane_t &plane, float flTolerance ); - - -Vector CalcClosestPointOnTriangle( const Vector &P, const Vector &v0, const Vector &v1, const Vector &v2 ); - -#endif // COLLISIONUTILS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Common collision utility methods +// +// $Header: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef COLLISIONUTILS_H +#define COLLISIONUTILS_H + +#include "tier0/platform.h" + +#ifdef _WIN32 +#pragma once +#endif + + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- +struct Ray_t; +class Vector; +class Vector2D; +class Vector4D; +struct cplane_t; +class QAngle; +class CBaseTrace; +struct matrix3x4_t; + + +//----------------------------------------------------------------------------- +// +// IntersectRayWithTriangle +// +// Intersects a ray with a triangle, returns distance t along ray. +// t will be less than zero if no intersection occurred +// oneSided will cull collisions which approach the triangle from the back +// side, assuming the vertices are specified in counter-clockwise order +// The vertices need not be specified in that order if oneSided is not used +// +//----------------------------------------------------------------------------- +float IntersectRayWithTriangle( const Ray_t& ray, + const Vector& v1, const Vector& v2, const Vector& v3, + bool oneSided ); + +//----------------------------------------------------------------------------- +// +// ComputeIntersectionBarycentricCoordinates +// +// Figures out the barycentric coordinates (u,v) where a ray hits a +// triangle. Note that this will ignore the ray extents, and it also ignores +// the ray length. Note that the edge from v1->v2 represents u (v2: u = 1), +// and the edge from v1->v3 represents v (v3: v = 1). It returns false +// if the ray is parallel to the triangle (or when t is specified if t is less +// than zero). +// +//----------------------------------------------------------------------------- +bool ComputeIntersectionBarycentricCoordinates( const Ray_t& ray, + const Vector& v1, const Vector& v2, const Vector& v3, float& u, float& v, + float *t = 0 ); + +//----------------------------------------------------------------------------- +// +// IntersectRayWithRay +// +// Returns whether or not there was an intersection. The "t" paramter is the +// distance along ray0 and the "s" parameter is the distance along ray1. If +// the two lines to not intersect the "t" and "s" represent the closest approach. +// "t" and "s" will not change if the rays are parallel. +// +//----------------------------------------------------------------------------- +bool IntersectRayWithRay( const Ray_t &ray0, const Ray_t &ray1, float &t, float &s ); + + +//----------------------------------------------------------------------------- +// +// IntersectRayWithSphere +// +// Returns whether or not there was an intersection. Returns the two intersection points. +// NOTE: The point of closest approach can be found at the average t value. +// +//----------------------------------------------------------------------------- +bool IntersectRayWithSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, const Vector &vecSphereCenter, float flRadius, float *pT1, float *pT2 ); + + +//----------------------------------------------------------------------------- +// +// IntersectInfiniteRayWithSphere +// +// Returns whether or not there was an intersection of a sphere against an infinitely +// extending ray. +// Returns the two intersection points +// +//----------------------------------------------------------------------------- +bool IntersectInfiniteRayWithSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, + const Vector &vecSphereCenter, float flRadius, float *pT1, float *pT2 ); + + +//----------------------------------------------------------------------------- +// +// IntersectRayWithPlane +// +// Intersects a ray with a plane, returns distance t along ray. +// t will be less than zero the intersection occurs in the opposite direction of the ray. +// +//----------------------------------------------------------------------------- +float IntersectRayWithPlane( const Ray_t& ray, const cplane_t& plane ); +float IntersectRayWithPlane( const Vector& org, const Vector& dir, const cplane_t& plane ); +float IntersectRayWithPlane( const Vector& org, const Vector& dir, const Vector& normal, float dist ); + +// This version intersects a ray with an axis-aligned plane +float IntersectRayWithAAPlane( const Vector& vecStart, const Vector& vecEnd, int nAxis, float flSign, float flDist ); + + +//----------------------------------------------------------------------------- +// IntersectRayWithBox +// +// Purpose: Computes the intersection of a ray with a box (AABB) +// Output : Returns true if there is an intersection + trace information +//----------------------------------------------------------------------------- +bool IntersectRayWithBox( const Vector &rayStart, const Vector &rayDelta, const Vector &boxMins, const Vector &boxMaxs, float epsilon, CBaseTrace *pTrace ); +bool IntersectRayWithBox( const Ray_t &ray, const Vector &boxMins, const Vector &boxMaxs, float epsilon, CBaseTrace *pTrace ); + +//----------------------------------------------------------------------------- +// Intersects a ray against a box +//----------------------------------------------------------------------------- +struct BoxTraceInfo_t +{ + float t1; + float t2; + int hitside; + bool startsolid; +}; + +bool IntersectRayWithBox( const Vector &vecRayStart, const Vector &vecRayDelta, + const Vector &boxMins, const Vector &boxMaxs, float flTolerance, BoxTraceInfo_t *pTrace ); + + +//----------------------------------------------------------------------------- +// IntersectRayWithOBB +// +// Purpose: Computes the intersection of a ray with a oriented box (OBB) +// Output : Returns true if there is an intersection + trace information +//----------------------------------------------------------------------------- +bool IntersectRayWithOBB( const Vector &vecRayStart, const Vector &vecRayDelta, + const matrix3x4_t &matOBBToWorld, const Vector &vecOBBMins, const Vector &vecOBBMaxs, + float flTolerance, CBaseTrace *pTrace ); + +bool IntersectRayWithOBB( const Vector &vecRayOrigin, const Vector &vecRayDelta, + const Vector &vecBoxOrigin, const QAngle &angBoxRotation, + const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ); + +bool IntersectRayWithOBB( const Ray_t &ray, const Vector &vecBoxOrigin, const QAngle &angBoxRotation, + const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ); + +bool IntersectRayWithOBB( const Ray_t &ray, const matrix3x4_t &matOBBToWorld, + const Vector &vecOBBMins, const Vector &vecOBBMaxs, float flTolerance, CBaseTrace *pTrace ); + +bool IntersectRayWithOBB( const Vector &vecRayStart, const Vector &vecRayDelta, + const matrix3x4_t &matOBBToWorld, const Vector &vecOBBMins, const Vector &vecOBBMaxs, + float flTolerance, BoxTraceInfo_t *pTrace ); + +//----------------------------------------------------------------------------- +// +// IsSphereIntersectingSphere +// +// returns true if there's an intersection between sphere and sphere +// +//----------------------------------------------------------------------------- +bool IsSphereIntersectingSphere( const Vector& center1, float radius1, + const Vector& center2, float radius2 ); + + +//----------------------------------------------------------------------------- +// +// IsBoxIntersectingSphere +// +// returns true if there's an intersection between box and sphere +// +//----------------------------------------------------------------------------- +bool IsBoxIntersectingSphere( const Vector& boxMin, const Vector& boxMax, + const Vector& center, float radius ); + +bool IsBoxIntersectingSphereExtents( const Vector& boxCenter, const Vector& boxHalfDiag, + const Vector& center, float radius ); + +//----------------------------------------------------------------------------- +// returns true if there's an intersection between ray and sphere +//----------------------------------------------------------------------------- +bool IsRayIntersectingSphere( const Vector &vecRayOrigin, const Vector &vecRayDelta, + const Vector &vecSphereCenter, float flRadius, float flTolerance = 0.0f ); + + +//----------------------------------------------------------------------------- +// +// IsCircleIntersectingRectangle +// +// returns true if there's an intersection between rectangle and circle +// +//----------------------------------------------------------------------------- +bool IsCircleIntersectingRectangle( const Vector2D& boxMin, const Vector2D& boxMax, + const Vector2D& center, float radius ); + + +//----------------------------------------------------------------------------- +// +// IsBoxIntersectingBox +// +// returns true if there's an intersection between two boxes +// +//----------------------------------------------------------------------------- +bool IsBoxIntersectingBox( const Vector& boxMin1, const Vector& boxMax1, + const Vector& boxMin2, const Vector& boxMax2 ); + +bool IsBoxIntersectingBoxExtents( const Vector& boxCenter1, const Vector& boxHalfDiagonal1, + const Vector& boxCenter2, const Vector& boxHalfDiagonal2 ); + + +//----------------------------------------------------------------------------- +// +// IsOBBIntersectingOBB +// +// returns true if there's an intersection between two OBBs +// +//----------------------------------------------------------------------------- +bool IsOBBIntersectingOBB( const Vector &vecOrigin1, const QAngle &vecAngles1, const Vector& boxMin1, const Vector& boxMax1, + const Vector &vecOrigin2, const QAngle &vecAngles2, const Vector& boxMin2, const Vector& boxMax2, float flTolerance = 0.0f ); + + +//----------------------------------------------------------------------------- +// +// IsBoxIntersectingRay +// +// returns true if there's an intersection between box and ray +// +//----------------------------------------------------------------------------- +bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, + const Vector& origin, const Vector& delta, float flTolerance = 0.0f ); + +bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, + const Ray_t& ray, float flTolerance = 0.0f ); + +bool FASTCALL IsBoxIntersectingRay( const Vector& boxMin, const Vector& boxMax, + const Vector& origin, const Vector& delta, + const Vector& invDelta, float flTolerance = 0.0f ); + +//----------------------------------------------------------------------------- +// +// IsPointInBox +// +// returns true if the point is in the box +// +//----------------------------------------------------------------------------- +bool IsPointInBox( const Vector& pt, const Vector& boxMin, const Vector& boxMax ); + + +//----------------------------------------------------------------------------- +// Purpose: returns true if pt intersects the truncated cone +// origin - cone tip, axis unit cone axis, cosAngle - cosine of cone axis to surface angle +//----------------------------------------------------------------------------- +bool IsPointInCone( const Vector &pt, const Vector &origin, const Vector &axis, float cosAngle, float length ); + +//----------------------------------------------------------------------------- +// Intersects a plane with a triangle (using barycentric definition) +// The return value, in pIntersection, is an array of barycentric coordinates +// describing at most 2 intersection points. +// The return value is the number of intersection points +//----------------------------------------------------------------------------- +int IntersectTriangleWithPlaneBarycentric( const Vector& org, const Vector& edgeU, const Vector& edgeV, + const Vector4D& plane, Vector2D* pIntersection ); + +//----------------------------------------------------------------------------- +// +// PointInQuadBarycentric +// +// Given a point and a quad in a plane return the u and v (barycentric) positions +// of the point relative to the quad. The points (v1,v2,v3,v4) should be given +// in a counter-clockwise order with v1 acting as the primary corner (u=0, v=0). +// Thus, u0 = v2 - v1, and v0 = v4 - v1. +// +//----------------------------------------------------------------------------- + +enum QuadBarycentricRetval_t +{ + BARY_QUADRATIC_FALSE = 0, + BARY_QUADRATIC_TRUE = 1, + BARY_QUADRATIC_NEGATIVE_DISCRIMINANT = 2 +}; + +QuadBarycentricRetval_t PointInQuadToBarycentric( const Vector &v1, const Vector &v2, + const Vector &v3, const Vector &v4, const Vector &point, Vector2D &uv ); + + +void PointInQuadFromBarycentric( const Vector &v1, const Vector &v2, const Vector &v3, const Vector &v4, + const Vector2D &uv, Vector &point ); +void TexCoordInQuadFromBarycentric( const Vector2D &v1, const Vector2D &v2, const Vector2D &v3, const Vector2D &v4, + const Vector2D &uv, Vector2D &texCoord ); + + +//----------------------------------------------------------------------------- +// Compute point from barycentric specification +// Edge u goes from v0 to v1, edge v goes from v0 to v2 +//----------------------------------------------------------------------------- +void ComputePointFromBarycentric( const Vector& v0, const Vector& v1, const Vector& v2, + float u, float v, Vector& pt ); +void ComputePointFromBarycentric( const Vector2D& v0, const Vector2D& v1, const Vector2D& v2, + float u, float v, Vector2D& pt ); + + +//----------------------------------------------------------------------------- +// Swept OBB test +//----------------------------------------------------------------------------- +bool IsRayIntersectingOBB( const Ray_t &ray, const Vector& org, const QAngle& angles, + const Vector& mins, const Vector& maxs ); + + +//----------------------------------------------------------------------------- +// Compute a separating plane between two boxes (expensive!) +// Returns false if no separating plane exists +//----------------------------------------------------------------------------- +bool ComputeSeparatingPlane( const Vector& org1, const QAngle& angles1, const Vector& min1, const Vector& max1, + const Vector& org2, const QAngle& angles2, const Vector& min2, const Vector& max2, + float tolerance, cplane_t* pPlane ); + +//----------------------------------------------------------------------------- +// IsBoxIntersectingTriangle +// +// Test for an intersection (overlap) between an axial-aligned bounding +// box (AABB) and a triangle. +// +// Triangle points are in counter-clockwise order with the normal facing "out." +// +// Using the "Separating-Axis Theorem" to test for intersections between +// a triangle and an axial-aligned bounding box (AABB). +// 1. 3 Axis Plane Tests - x, y, z +// 2. 9 Edge Planes Tests - the 3 edges of the triangle crossed with all 3 axial +// planes (x, y, z) +// 3. 1 Face Plane Test - the plane the triangle resides in (cplane_t plane) +//----------------------------------------------------------------------------- +bool IsBoxIntersectingTriangle( const Vector &vecBoxCenter, const Vector &vecBoxExtents, + const Vector &v1, const Vector &v2, const Vector &v3, + const cplane_t &plane, float flTolerance ); + + +Vector CalcClosestPointOnTriangle( const Vector &P, const Vector &v0, const Vector &v1, const Vector &v2 ); + +#endif // COLLISIONUTILS_H diff --git a/public/coordsize.h b/public/coordsize.h index 1c0addb6..2aebeab8 100644 --- a/public/coordsize.h +++ b/public/coordsize.h @@ -1,92 +1,92 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#ifndef COORDSIZE_H -#define COORDSIZE_H -#pragma once - -#include "worldsize.h" - -// OVERALL Coordinate Size Limits used in COMMON.C MSG_*BitCoord() Routines (and someday the HUD) -#define COORD_INTEGER_BITS 14 -#define COORD_FRACTIONAL_BITS 5 -#define COORD_DENOMINATOR (1<<(COORD_FRACTIONAL_BITS)) -#define COORD_RESOLUTION (1.0/(COORD_DENOMINATOR)) - -#define NORMAL_FRACTIONAL_BITS 11 -#define NORMAL_DENOMINATOR ( (1<<(NORMAL_FRACTIONAL_BITS)) - 1 ) -#define NORMAL_RESOLUTION (1.0/(NORMAL_DENOMINATOR)) - -// this is limited by the network fractional bits used for coords -// because net coords will be only be accurate to 5 bits fractional -// Standard collision test epsilon -// 1/32nd inch collision epsilon -#define DIST_EPSILON (0.03125) - -// Verify that coordsize.h and worldsize.h are consistently defined -#if (MAX_COORD_INTEGER != (1<m) #endif @@ -175,7 +175,7 @@ DECLARE_FIELD_SIZE( FIELD_MATERIALINDEX, sizeof(int) ) // INPUTS #define DEFINE_INPUT( name, fieldtype, inputname ) { fieldtype, #name, { offsetof(classNameTypedef, name), 0 }, 1, FTYPEDESC_INPUT | FTYPEDESC_SAVE | FTYPEDESC_KEY, inputname, NULL, NULL, NULL, sizeof( ((classNameTypedef *)0)->name ) } -#define DEFINE_INPUTFUNC( fieldtype, inputname, inputfunc ) { fieldtype, #inputfunc, { NULL, NULL }, 1, FTYPEDESC_INPUT, inputname, NULL, static_cast (&classNameTypedef::inputfunc) } +#define DEFINE_INPUTFUNC( fieldtype, inputname, inputfunc ) { fieldtype, #inputfunc, { 0, 0 }, 1, FTYPEDESC_INPUT, inputname, NULL, static_cast (&classNameTypedef::inputfunc) } // OUTPUTS // the variable 'name' MUST BE derived from CBaseOutput @@ -185,7 +185,7 @@ extern ISaveRestoreOps *eventFuncs; #define DEFINE_OUTPUT( name, outputname ) { FIELD_CUSTOM, #name, { offsetof(classNameTypedef, name), 0 }, 1, FTYPEDESC_OUTPUT | FTYPEDESC_SAVE | FTYPEDESC_KEY, outputname, eventFuncs } // replaces EXPORT table for portability and non-DLL based systems (xbox) -#define DEFINE_FUNCTION_RAW( function, func_type ) { FIELD_VOID, nameHolder.GenerateName(#function), { NULL, NULL }, 1, FTYPEDESC_FUNCTIONTABLE, NULL, NULL, (inputfunc_t)((func_type)(&classNameTypedef::function)) } +#define DEFINE_FUNCTION_RAW( function, func_type ) { FIELD_VOID, nameHolder.GenerateName(#function), { 0, 0 }, 1, FTYPEDESC_FUNCTIONTABLE, NULL, NULL, (inputfunc_t)((func_type)(&classNameTypedef::function)) } #define DEFINE_FUNCTION( function ) DEFINE_FUNCTION_RAW( function, inputfunc_t ) diff --git a/public/datamodel/idatamodel.h b/public/datamodel/idatamodel.h index 89e3b1fa..c57aa707 100644 --- a/public/datamodel/idatamodel.h +++ b/public/datamodel/idatamodel.h @@ -237,8 +237,8 @@ abstract_class IUndoElement { protected: explicit IUndoElement( char const *desc ) : - m_pDesc( desc ), - m_bEndOfStream( false ) + m_bEndOfStream( false ), + m_pDesc( desc ) { } public: diff --git a/public/disp_powerinfo.cpp b/public/disp_powerinfo.cpp index 2f43d4db..ed3aca78 100644 --- a/public/disp_powerinfo.cpp +++ b/public/disp_powerinfo.cpp @@ -1,580 +1,580 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "disp_powerinfo.h" -#include "disp_common.h" -#include "commonmacros.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// ------------------------------------------------------------------------ // -// Internal classes. -// ------------------------------------------------------------------------ // - -// These point at the vertices connecting to each of the [north,south,east,west] vertices. -class CVertCorners -{ -public: - short m_Corner1[2]; - short m_Corner2[2]; -}; - - -// ------------------------------------------------------------------------ // -// Globals. -// ------------------------------------------------------------------------ // - -// This points at vertices to the side of a node (north, south, east, west). -static short g_SideVertMul[4][2] = { {1,0}, {0,1}, {-1,0}, {0,-1} }; - -static CVertCorners g_SideVertCorners[4] = -{ - { {1,-1}, {1,1} }, - { {1,1}, {-1,1} }, - { {-1,1}, {-1,-1} }, - { {-1,-1}, {1,-1} } -}; - -// This is used in loops on child nodes. The indices point at the nodes: -// 0 = upper-right -// 1 = upper-left -// 2 = lower-left -// 3 = lower-right -static CVertIndex g_ChildNodeIndexMul[4] = -{ - CVertIndex(1,1), - CVertIndex(-1,1), - CVertIndex(-1,-1), - CVertIndex(1,-1) -}; - -// These are multipliers on vertMul (not nodeMul). -static CVertIndex g_ChildNodeDependencies[4][2] = -{ - { CVertIndex(1,0), CVertIndex(0,1) }, - { CVertIndex(0,1), CVertIndex(-1,0) }, - { CVertIndex(-1,0), CVertIndex(0,-1) }, - { CVertIndex(0,-1), CVertIndex(1,0) } -}; - -// 2x2 rotation matrices for each orientation. -static int g_OrientationRotations[4][2][2] = -{ - {{1, 0}, // CCW_0 - {0, 1}}, - - {{0, 1}, // CCW_90 - {-1,0}}, - - {{-1,0}, // CCW_180 - {0,-1}}, - - {{0, -1}, // CCW_270 - {1, 0}} -}; - - -// ------------------------------------------------------------------------ // -// Helper functions. -// ------------------------------------------------------------------------ // - -// Apply a 2D rotation to the specified CVertIndex around the specified centerpoint. -static CVertIndex Transform2D( - int const mat[2][2], - CVertIndex const &vert, - CVertIndex const ¢erPoint ) -{ - CVertIndex translated = vert - centerPoint; - - CVertIndex transformed( - translated.x*mat[0][0] + translated.y*mat[0][1], - translated.x*mat[1][0] + translated.y*mat[1][1] ); - - return transformed + centerPoint; -} - - -// Rotate a given CVertIndex with a specified orientation. -// Do this with a lookup table eventually! -static void GetEdgeVertIndex( int sideLength, int iEdge, int iVert, CVertIndex &out ) -{ - if( iEdge == NEIGHBOREDGE_RIGHT ) - { - out.x = sideLength - 1; - out.y = iVert; - } - else if( iEdge == NEIGHBOREDGE_TOP ) - { - out.x = iVert; - out.y = sideLength - 1; - } - else if( iEdge == NEIGHBOREDGE_LEFT ) - { - out.x = 0; - out.y = iVert; - } - else - { - out.x = iVert; - out.y = 0; - } -} - - -// Generate an index given a CVertIndex and the size of the displacement it resides in. -static int VertIndex( CVertIndex const &vert, int iMaxPower ) -{ - return vert.y * ((1 << iMaxPower) + 1) + vert.x; -} - - -static CVertIndex WrapVertIndex( CVertIndex const &in, int sideLength ) -{ - int out[2]; - - for( int i=0; i < 2; i++ ) - { - if( in[i] < 0 ) - out[i] = sideLength - 1 - (-in[i] % sideLength); - else if( in[i] >= sideLength ) - out[i] = in[i] % sideLength; - else - out[i] = in[i]; - } - - return CVertIndex( out[0], out[1] ); -} - - -static int GetFreeDependency( CVertDependency *pDep, int nElements ) -{ - for( int i=0; i < nElements; i++ ) - { - if( !pDep[i].IsValid() ) - return i; - } - - Assert( false ); - return 0; -} - - -static void AddDependency( - CVertInfo *dependencies, - int sideLength, - CVertIndex const &nodeIndex, - CVertIndex const &dependency, - int iMaxPower, - bool bCheckNeighborDependency, - bool bAddReverseDependency ) -{ - int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); - CVertInfo *pNode = &dependencies[iNodeIndex]; - - int iDep = GetFreeDependency( pNode->m_Dependencies, sizeof(pNode->m_Dependencies)/sizeof(pNode->m_Dependencies[0]) ); - pNode->m_Dependencies[iDep].m_iVert = dependency; - pNode->m_Dependencies[iDep].m_iNeighbor = -1; - - if( bAddReverseDependency ) - { - CVertInfo *pDep = &dependencies[VertIndex( dependency, iMaxPower )]; - iDep = GetFreeDependency( pDep->m_ReverseDependencies, CVertInfo::NUM_REVERSE_DEPENDENCIES ); - pDep->m_ReverseDependencies[iDep].m_iVert = nodeIndex; - pDep->m_ReverseDependencies[iDep].m_iNeighbor = -1; - } - - // Edge verts automatically add a dependency for the neighbor. - // Internal verts wind up in here twice anyway so it doesn't need to - if( bCheckNeighborDependency ) - { - int iConnection = GetEdgeIndexFromPoint( nodeIndex, iMaxPower ); - if( iConnection != -1 ) - { - Assert( !pNode->m_Dependencies[1].IsValid() ); - - CVertIndex delta( nodeIndex.x - dependency.x, nodeIndex.y - dependency.y ); - CVertIndex newIndex( nodeIndex.x + delta.x, nodeIndex.y + delta.y ); - - int fullSideLength = (1 << iMaxPower) + 1; - pNode->m_Dependencies[1].m_iVert = WrapVertIndex( CVertIndex( newIndex.x, newIndex.y ), fullSideLength ); - pNode->m_Dependencies[1].m_iNeighbor = iConnection; - } - } -} - - -// --------------------------------------------------------------------------------- // -// CTesselateWinding stuff. -// --------------------------------------------------------------------------------- // - -CTesselateVert::CTesselateVert( CVertIndex const &index, int iNode ) - : m_Index( index ) -{ - m_iNode = iNode; -} - - -CVertInfo::CVertInfo() -{ - int i; - for( i=0; i < sizeof(m_Dependencies)/sizeof(m_Dependencies[0]); i++ ) - { - m_Dependencies[i].m_iVert = CVertIndex( -1, -1 ); - m_Dependencies[i].m_iNeighbor = -1; - } - - for( i=0; i < sizeof(m_ReverseDependencies)/sizeof(m_ReverseDependencies[0]); i++ ) - { - m_ReverseDependencies[i].m_iVert = CVertIndex( -1, -1 ); - m_ReverseDependencies[i].m_iNeighbor = -1; - } - - m_iParent.x = m_iParent.y = -1; - m_iNodeLevel = -1; -} - - -CTesselateVert g_TesselateVerts[] = -{ - CTesselateVert( CVertIndex(1,-1), CHILDNODE_LOWER_RIGHT), - CTesselateVert( CVertIndex(0,-1), -1), - CTesselateVert( CVertIndex(-1,-1), CHILDNODE_LOWER_LEFT), - CTesselateVert( CVertIndex(-1, 0), -1), - CTesselateVert( CVertIndex(-1, 1), CHILDNODE_UPPER_LEFT), - CTesselateVert( CVertIndex(0, 1), -1), - CTesselateVert( CVertIndex(1, 1), CHILDNODE_UPPER_RIGHT), - CTesselateVert( CVertIndex(1, 0), -1), - CTesselateVert( CVertIndex(1,-1), CHILDNODE_LOWER_RIGHT) -}; - -CTesselateWinding g_TWinding = -{ - g_TesselateVerts, - sizeof( g_TesselateVerts ) / sizeof( g_TesselateVerts[0] ) -}; - - - -// --------------------------------------------------------------------------------- // -// CPowerInfo stuff. -// --------------------------------------------------------------------------------- // - -// Precalculated info about each particular displacement size. -#define DECLARE_TABLES( size ) \ - static CVertInfo g_VertInfo_##size##x##size[ size*size ]; \ - static CFourVerts g_SideVerts_##size##x##size[ size*size ]; \ - static CFourVerts g_ChildVerts_##size##x##size[ size*size ]; \ - static CFourVerts g_SideVertCorners_##size##x##size[ size*size ]; \ - static CTwoUShorts g_ErrorEdges_##size##x##size[ size*size ]; \ - static CTriInfo g_TriInfos_##size##x##size[ (size-1)*(size-1)*2 ]; \ - static CPowerInfo g_PowerInfo_##size##x##size( \ - g_VertInfo_##size##x##size, \ - g_SideVerts_##size##x##size, \ - g_ChildVerts_##size##x##size, \ - g_SideVertCorners_##size##x##size,\ - g_ErrorEdges_##size##x##size, \ - g_TriInfos_##size##x##size \ - ) - -#define POWERINFO_ENTRY( size ) \ - (&g_PowerInfo_##size##x##size) - -DECLARE_TABLES( 5 ); -DECLARE_TABLES( 9 ); -DECLARE_TABLES( 17 ); - - -// Index by m_Power. -CPowerInfo *g_PowerInfos[NUM_POWERINFOS] = -{ - NULL, - NULL, - POWERINFO_ENTRY(5), - POWERINFO_ENTRY(9), - POWERINFO_ENTRY(17) -}; - - -CPowerInfo::CPowerInfo( - CVertInfo *pVertInfo, - CFourVerts *pSideVerts, - CFourVerts *pChildVerts, - CFourVerts *pSideVertCorners, - CTwoUShorts *pErrorEdges, - CTriInfo *pTriInfos ) -{ - m_pVertInfo = pVertInfo; - m_pSideVerts = pSideVerts; - m_pChildVerts = pChildVerts; - m_pSideVertCorners = pSideVertCorners; - m_pErrorEdges = pErrorEdges; - m_pTriInfos = pTriInfos; -} - -static void InitPowerInfoTriInfos_R( - CPowerInfo *pInfo, - CVertIndex const &nodeIndex, - CTriInfo* &pTriInfo, - int iMaxPower, - int iLevel ) -{ - int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); - - if( iLevel+1 < iMaxPower ) - { - // Recurse into children. - for( int iChild=0; iChild < 4; iChild++ ) - { - InitPowerInfoTriInfos_R( - pInfo, - pInfo->m_pChildVerts[iNodeIndex].m_Verts[iChild], - pTriInfo, - iMaxPower, - iLevel+1 ); - } - } - else - { - unsigned short indices[3]; - - int vertInc = 1 << ((iMaxPower - iLevel) - 1); - - // We're at a leaf, generate the tris. - CTesselateWinding *pWinding = &g_TWinding; - - // Starting at the bottom-left, wind clockwise picking up vertices and - // generating triangles. - int iCurTriVert = 0; - for( int iVert=0; iVert < pWinding->m_nVerts; iVert++ ) - { - CVertIndex sideVert = BuildOffsetVertIndex( nodeIndex, pWinding->m_Verts[iVert].m_Index, vertInc ); - - if( iCurTriVert == 1 ) - { - // Add this vert and finish the tri. - pTriInfo->m_Indices[0] = indices[0]; - pTriInfo->m_Indices[1] = VertIndex( sideVert, iMaxPower ); - pTriInfo->m_Indices[2] = iNodeIndex; - ++pTriInfo; - } - - indices[0] = VertIndex( sideVert, iMaxPower ); - iCurTriVert = 1; - } - } -} - - -static void InitPowerInfo_R( - CPowerInfo *pPowerInfo, - int iMaxPower, - CVertIndex const &nodeIndex, - CVertIndex const &dependency1, - CVertIndex const &dependency2, - CVertIndex const &nodeEdge1, - CVertIndex const &nodeEdge2, - CVertIndex const &iParent, - int iLevel ) -{ - int sideLength = ((1 << iMaxPower) + 1); - int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); - - pPowerInfo->m_pVertInfo[iNodeIndex].m_iParent = iParent; - pPowerInfo->m_pVertInfo[iNodeIndex].m_iNodeLevel = iLevel + 1; - - pPowerInfo->m_pErrorEdges[iNodeIndex].m_Values[0] = (unsigned short)(VertIndex( nodeEdge1, iMaxPower )); - pPowerInfo->m_pErrorEdges[iNodeIndex].m_Values[1] = (unsigned short)(VertIndex( nodeEdge2, iMaxPower )); - - // Add this node's dependencies. - AddDependency( pPowerInfo->m_pVertInfo, sideLength, nodeIndex, dependency1, iMaxPower, false, true ); - AddDependency( pPowerInfo->m_pVertInfo, sideLength, nodeIndex, dependency2, iMaxPower, false, true ); - - // The 4 side vertices depend on this node. - int iPower = iMaxPower - iLevel; - int vertInc = 1 << (iPower - 1); - - for( int iSide=0; iSide < 4; iSide++ ) - { - // Store the side vert index. - CVertIndex sideVert( nodeIndex.x + g_SideVertMul[iSide][0]*vertInc, nodeIndex.y + g_SideVertMul[iSide][1]*vertInc ); - int iSideVert = VertIndex( sideVert, iMaxPower ); - - pPowerInfo->m_pSideVerts[iNodeIndex].m_Verts[iSide] = sideVert; - - // Store the side vert corners. - CVertIndex sideVertCorner0 = CVertIndex( nodeIndex.x + g_SideVertCorners[iSide].m_Corner1[0]*vertInc, nodeIndex.y + g_SideVertCorners[iSide].m_Corner1[1]*vertInc ); - CVertIndex sideVertCorner1 = CVertIndex( nodeIndex.x + g_SideVertCorners[iSide].m_Corner2[0]*vertInc, nodeIndex.y + g_SideVertCorners[iSide].m_Corner2[1]*vertInc ); - - pPowerInfo->m_pSideVertCorners[iNodeIndex].m_Verts[iSide] = sideVertCorner0; - - // Write the side vert corners into the error-edges list. - pPowerInfo->m_pErrorEdges[iSideVert].m_Values[0] = (unsigned short)VertIndex( sideVertCorner0, iMaxPower ); - pPowerInfo->m_pErrorEdges[iSideVert].m_Values[1] = (unsigned short)VertIndex( sideVertCorner1, iMaxPower ); - - AddDependency( - pPowerInfo->m_pVertInfo, - sideLength, - sideVert, - nodeIndex, - iMaxPower, - true, - true ); - } - - // Recurse into the children. - int nodeInc = vertInc >> 1; - if( nodeInc ) - { - for( int iChild=0; iChild < 4; iChild++ ) - { - CVertIndex childVert( nodeIndex.x + g_ChildNodeIndexMul[iChild].x * nodeInc, nodeIndex.y + g_ChildNodeIndexMul[iChild].y * nodeInc ); - - pPowerInfo->m_pChildVerts[iNodeIndex].m_Verts[iChild] = childVert; - - InitPowerInfo_R( pPowerInfo, - iMaxPower, - childVert, - - CVertIndex(nodeIndex.x + g_ChildNodeDependencies[iChild][0].x*vertInc, nodeIndex.y + g_ChildNodeDependencies[iChild][0].y*vertInc), - CVertIndex(nodeIndex.x + g_ChildNodeDependencies[iChild][1].x*vertInc, nodeIndex.y + g_ChildNodeDependencies[iChild][1].y*vertInc), - - nodeIndex, - CVertIndex( nodeIndex.x + g_ChildNodeIndexMul[iChild].x * vertInc, nodeIndex.y + g_ChildNodeIndexMul[iChild].y * vertInc ), - - nodeIndex, - iLevel + 1 ); - } - } -} - - -void InitPowerInfo( CPowerInfo *pInfo, int iMaxPower ) -{ - int sideLength = (1 << iMaxPower) + 1; - - // Precalculate the dependency graph. - CVertIndex nodeDependency1( sideLength-1, sideLength-1 ); - CVertIndex nodeDependency2( 0, 0 ); - - pInfo->m_RootNode = CVertIndex( sideLength/2, sideLength/2 ); - pInfo->m_SideLength = sideLength; - pInfo->m_SideLengthM1 = sideLength - 1; - pInfo->m_MidPoint = sideLength / 2; - pInfo->m_MaxVerts = sideLength * sideLength; - - // Setup the corner indices. - pInfo->m_CornerPointIndices[CORNER_LOWER_LEFT].Init( 0, 0 ); - pInfo->m_CornerPointIndices[CORNER_UPPER_LEFT].Init( 0, sideLength-1 ); - pInfo->m_CornerPointIndices[CORNER_UPPER_RIGHT].Init( sideLength-1, sideLength-1 ); - pInfo->m_CornerPointIndices[CORNER_LOWER_RIGHT].Init( sideLength-1, 0 ); - - InitPowerInfo_R( - pInfo, - iMaxPower, - pInfo->m_RootNode, - - nodeDependency1, // dependencies - nodeDependency2, - - CVertIndex(0,0), // error edge - CVertIndex(sideLength-1, sideLength-1), - - CVertIndex(-1,-1), // parent - 0 ); - - pInfo->m_Power = iMaxPower; - - CTriInfo *pTriInfo = pInfo->m_pTriInfos; - InitPowerInfoTriInfos_R( pInfo, pInfo->m_RootNode, pTriInfo, iMaxPower, 0 ); - - for( int iEdge=0; iEdge < 4; iEdge++ ) - { - // Figure out the start vert and increment. - CVertIndex nextVert; - GetEdgeVertIndex( sideLength, iEdge, 0, pInfo->m_EdgeStartVerts[iEdge] ); - GetEdgeVertIndex( sideLength, iEdge, 1, nextVert ); - pInfo->m_EdgeIncrements[iEdge] = nextVert - pInfo->m_EdgeStartVerts[iEdge]; - - // Now get the neighbor's start vert and increment. - CVertIndex nbStartVert, nbNextVert, nbDelta; - GetEdgeVertIndex( sideLength, (iEdge+2)&3, 0, nbStartVert ); - GetEdgeVertIndex( sideLength, (iEdge+2)&3, 1, nbNextVert ); - nbDelta = nbNextVert - nbStartVert; - - // Rotate it for each orientation. - for( int orient=0; orient < 4; orient++ ) - { - pInfo->m_NeighborStartVerts[iEdge][orient] = Transform2D( - g_OrientationRotations[orient], - nbStartVert, - CVertIndex( sideLength/2, sideLength/2 ) ); - - pInfo->m_NeighborIncrements[iEdge][orient] = Transform2D( - g_OrientationRotations[orient], - nbDelta, - CVertIndex(0,0) ); - } - } - - - // Init the node index increments. - int curPowerOf4 = 1; - int curTotal = 0; - for( int i=0; i < iMaxPower-1; i++ ) - { - curTotal += curPowerOf4; - - pInfo->m_NodeIndexIncrements[iMaxPower-i-2] = curTotal; - - curPowerOf4 *= 4; - } - - // Store off the total node count - pInfo->m_NodeCount = curTotal + curPowerOf4; - - pInfo->m_nTriInfos = Square( 1 << iMaxPower ) * 2; -} - -class CPowerInfoInitializer -{ -public: - CPowerInfoInitializer() - { - Assert( MAX_MAP_DISP_POWER+1 == NUM_POWERINFOS ); - - for( int i=0; i <= MAX_MAP_DISP_POWER; i++ ) - { - if( g_PowerInfos[i] ) - { - InitPowerInfo( g_PowerInfos[i], i ); - } - } - } -}; - -static CPowerInfoInitializer g_PowerInfoInitializer; - - -const CPowerInfo* GetPowerInfo( int iPower ) -{ - Assert( iPower >= 0 && iPower < ARRAYSIZE( g_PowerInfos ) ); - Assert( g_PowerInfos[iPower] ); - return g_PowerInfos[iPower]; -} - - -// ------------------------------------------------------------------------------------------------ // -// CPowerInfo member function initialization. -// ------------------------------------------------------------------------------------------------ // - -const CVertIndex& CPowerInfo::GetCornerPointIndex( int iCorner ) const -{ - Assert( iCorner >= 0 && iCorner < 4 ); - return m_CornerPointIndices[iCorner]; -} - +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "disp_powerinfo.h" +#include "disp_common.h" +#include "commonmacros.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ------------------------------------------------------------------------ // +// Internal classes. +// ------------------------------------------------------------------------ // + +// These point at the vertices connecting to each of the [north,south,east,west] vertices. +class CVertCorners +{ +public: + short m_Corner1[2]; + short m_Corner2[2]; +}; + + +// ------------------------------------------------------------------------ // +// Globals. +// ------------------------------------------------------------------------ // + +// This points at vertices to the side of a node (north, south, east, west). +static short g_SideVertMul[4][2] = { {1,0}, {0,1}, {-1,0}, {0,-1} }; + +static CVertCorners g_SideVertCorners[4] = +{ + { {1,-1}, {1,1} }, + { {1,1}, {-1,1} }, + { {-1,1}, {-1,-1} }, + { {-1,-1}, {1,-1} } +}; + +// This is used in loops on child nodes. The indices point at the nodes: +// 0 = upper-right +// 1 = upper-left +// 2 = lower-left +// 3 = lower-right +static CVertIndex g_ChildNodeIndexMul[4] = +{ + CVertIndex(1,1), + CVertIndex(-1,1), + CVertIndex(-1,-1), + CVertIndex(1,-1) +}; + +// These are multipliers on vertMul (not nodeMul). +static CVertIndex g_ChildNodeDependencies[4][2] = +{ + { CVertIndex(1,0), CVertIndex(0,1) }, + { CVertIndex(0,1), CVertIndex(-1,0) }, + { CVertIndex(-1,0), CVertIndex(0,-1) }, + { CVertIndex(0,-1), CVertIndex(1,0) } +}; + +// 2x2 rotation matrices for each orientation. +static int g_OrientationRotations[4][2][2] = +{ + {{1, 0}, // CCW_0 + {0, 1}}, + + {{0, 1}, // CCW_90 + {-1,0}}, + + {{-1,0}, // CCW_180 + {0,-1}}, + + {{0, -1}, // CCW_270 + {1, 0}} +}; + + +// ------------------------------------------------------------------------ // +// Helper functions. +// ------------------------------------------------------------------------ // + +// Apply a 2D rotation to the specified CVertIndex around the specified centerpoint. +static CVertIndex Transform2D( + int const mat[2][2], + CVertIndex const &vert, + CVertIndex const ¢erPoint ) +{ + CVertIndex translated = vert - centerPoint; + + CVertIndex transformed( + translated.x*mat[0][0] + translated.y*mat[0][1], + translated.x*mat[1][0] + translated.y*mat[1][1] ); + + return transformed + centerPoint; +} + + +// Rotate a given CVertIndex with a specified orientation. +// Do this with a lookup table eventually! +static void GetEdgeVertIndex( int sideLength, int iEdge, int iVert, CVertIndex &out ) +{ + if( iEdge == NEIGHBOREDGE_RIGHT ) + { + out.x = sideLength - 1; + out.y = iVert; + } + else if( iEdge == NEIGHBOREDGE_TOP ) + { + out.x = iVert; + out.y = sideLength - 1; + } + else if( iEdge == NEIGHBOREDGE_LEFT ) + { + out.x = 0; + out.y = iVert; + } + else + { + out.x = iVert; + out.y = 0; + } +} + + +// Generate an index given a CVertIndex and the size of the displacement it resides in. +static int VertIndex( CVertIndex const &vert, int iMaxPower ) +{ + return vert.y * ((1 << iMaxPower) + 1) + vert.x; +} + + +static CVertIndex WrapVertIndex( CVertIndex const &in, int sideLength ) +{ + int out[2]; + + for( int i=0; i < 2; i++ ) + { + if( in[i] < 0 ) + out[i] = sideLength - 1 - (-in[i] % sideLength); + else if( in[i] >= sideLength ) + out[i] = in[i] % sideLength; + else + out[i] = in[i]; + } + + return CVertIndex( out[0], out[1] ); +} + + +static int GetFreeDependency( CVertDependency *pDep, int nElements ) +{ + for( int i=0; i < nElements; i++ ) + { + if( !pDep[i].IsValid() ) + return i; + } + + Assert( false ); + return 0; +} + + +static void AddDependency( + CVertInfo *dependencies, + int sideLength, + CVertIndex const &nodeIndex, + CVertIndex const &dependency, + int iMaxPower, + bool bCheckNeighborDependency, + bool bAddReverseDependency ) +{ + int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); + CVertInfo *pNode = &dependencies[iNodeIndex]; + + int iDep = GetFreeDependency( pNode->m_Dependencies, sizeof(pNode->m_Dependencies)/sizeof(pNode->m_Dependencies[0]) ); + pNode->m_Dependencies[iDep].m_iVert = dependency; + pNode->m_Dependencies[iDep].m_iNeighbor = -1; + + if( bAddReverseDependency ) + { + CVertInfo *pDep = &dependencies[VertIndex( dependency, iMaxPower )]; + iDep = GetFreeDependency( pDep->m_ReverseDependencies, CVertInfo::NUM_REVERSE_DEPENDENCIES ); + pDep->m_ReverseDependencies[iDep].m_iVert = nodeIndex; + pDep->m_ReverseDependencies[iDep].m_iNeighbor = -1; + } + + // Edge verts automatically add a dependency for the neighbor. + // Internal verts wind up in here twice anyway so it doesn't need to + if( bCheckNeighborDependency ) + { + int iConnection = GetEdgeIndexFromPoint( nodeIndex, iMaxPower ); + if( iConnection != -1 ) + { + Assert( !pNode->m_Dependencies[1].IsValid() ); + + CVertIndex delta( nodeIndex.x - dependency.x, nodeIndex.y - dependency.y ); + CVertIndex newIndex( nodeIndex.x + delta.x, nodeIndex.y + delta.y ); + + int fullSideLength = (1 << iMaxPower) + 1; + pNode->m_Dependencies[1].m_iVert = WrapVertIndex( CVertIndex( newIndex.x, newIndex.y ), fullSideLength ); + pNode->m_Dependencies[1].m_iNeighbor = iConnection; + } + } +} + + +// --------------------------------------------------------------------------------- // +// CTesselateWinding stuff. +// --------------------------------------------------------------------------------- // + +CTesselateVert::CTesselateVert( CVertIndex const &index, int iNode ) + : m_Index( index ) +{ + m_iNode = iNode; +} + + +CVertInfo::CVertInfo() +{ + size_t i; + for( i=0; i < sizeof(m_Dependencies)/sizeof(m_Dependencies[0]); i++ ) + { + m_Dependencies[i].m_iVert = CVertIndex( -1, -1 ); + m_Dependencies[i].m_iNeighbor = -1; + } + + for( i=0; i < sizeof(m_ReverseDependencies)/sizeof(m_ReverseDependencies[0]); i++ ) + { + m_ReverseDependencies[i].m_iVert = CVertIndex( -1, -1 ); + m_ReverseDependencies[i].m_iNeighbor = -1; + } + + m_iParent.x = m_iParent.y = -1; + m_iNodeLevel = -1; +} + + +CTesselateVert g_TesselateVerts[] = +{ + CTesselateVert( CVertIndex(1,-1), CHILDNODE_LOWER_RIGHT), + CTesselateVert( CVertIndex(0,-1), -1), + CTesselateVert( CVertIndex(-1,-1), CHILDNODE_LOWER_LEFT), + CTesselateVert( CVertIndex(-1, 0), -1), + CTesselateVert( CVertIndex(-1, 1), CHILDNODE_UPPER_LEFT), + CTesselateVert( CVertIndex(0, 1), -1), + CTesselateVert( CVertIndex(1, 1), CHILDNODE_UPPER_RIGHT), + CTesselateVert( CVertIndex(1, 0), -1), + CTesselateVert( CVertIndex(1,-1), CHILDNODE_LOWER_RIGHT) +}; + +CTesselateWinding g_TWinding = +{ + g_TesselateVerts, + sizeof( g_TesselateVerts ) / sizeof( g_TesselateVerts[0] ) +}; + + + +// --------------------------------------------------------------------------------- // +// CPowerInfo stuff. +// --------------------------------------------------------------------------------- // + +// Precalculated info about each particular displacement size. +#define DECLARE_TABLES( size ) \ + static CVertInfo g_VertInfo_##size##x##size[ size*size ]; \ + static CFourVerts g_SideVerts_##size##x##size[ size*size ]; \ + static CFourVerts g_ChildVerts_##size##x##size[ size*size ]; \ + static CFourVerts g_SideVertCorners_##size##x##size[ size*size ]; \ + static CTwoUShorts g_ErrorEdges_##size##x##size[ size*size ]; \ + static CTriInfo g_TriInfos_##size##x##size[ (size-1)*(size-1)*2 ]; \ + static CPowerInfo g_PowerInfo_##size##x##size( \ + g_VertInfo_##size##x##size, \ + g_SideVerts_##size##x##size, \ + g_ChildVerts_##size##x##size, \ + g_SideVertCorners_##size##x##size,\ + g_ErrorEdges_##size##x##size, \ + g_TriInfos_##size##x##size \ + ) + +#define POWERINFO_ENTRY( size ) \ + (&g_PowerInfo_##size##x##size) + +DECLARE_TABLES( 5 ); +DECLARE_TABLES( 9 ); +DECLARE_TABLES( 17 ); + + +// Index by m_Power. +CPowerInfo *g_PowerInfos[NUM_POWERINFOS] = +{ + NULL, + NULL, + POWERINFO_ENTRY(5), + POWERINFO_ENTRY(9), + POWERINFO_ENTRY(17) +}; + + +CPowerInfo::CPowerInfo( + CVertInfo *pVertInfo, + CFourVerts *pSideVerts, + CFourVerts *pChildVerts, + CFourVerts *pSideVertCorners, + CTwoUShorts *pErrorEdges, + CTriInfo *pTriInfos ) +{ + m_pVertInfo = pVertInfo; + m_pSideVerts = pSideVerts; + m_pChildVerts = pChildVerts; + m_pSideVertCorners = pSideVertCorners; + m_pErrorEdges = pErrorEdges; + m_pTriInfos = pTriInfos; +} + +static void InitPowerInfoTriInfos_R( + CPowerInfo *pInfo, + CVertIndex const &nodeIndex, + CTriInfo* &pTriInfo, + int iMaxPower, + int iLevel ) +{ + int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); + + if( iLevel+1 < iMaxPower ) + { + // Recurse into children. + for( int iChild=0; iChild < 4; iChild++ ) + { + InitPowerInfoTriInfos_R( + pInfo, + pInfo->m_pChildVerts[iNodeIndex].m_Verts[iChild], + pTriInfo, + iMaxPower, + iLevel+1 ); + } + } + else + { + unsigned short indices[3] = {0, 0, 0}; + + int vertInc = 1 << ((iMaxPower - iLevel) - 1); + + // We're at a leaf, generate the tris. + CTesselateWinding *pWinding = &g_TWinding; + + // Starting at the bottom-left, wind clockwise picking up vertices and + // generating triangles. + int iCurTriVert = 0; + for( int iVert=0; iVert < pWinding->m_nVerts; iVert++ ) + { + CVertIndex sideVert = BuildOffsetVertIndex( nodeIndex, pWinding->m_Verts[iVert].m_Index, vertInc ); + + if( iCurTriVert == 1 ) + { + // Add this vert and finish the tri. + pTriInfo->m_Indices[0] = indices[0]; + pTriInfo->m_Indices[1] = VertIndex( sideVert, iMaxPower ); + pTriInfo->m_Indices[2] = iNodeIndex; + ++pTriInfo; + } + + indices[0] = VertIndex( sideVert, iMaxPower ); + iCurTriVert = 1; + } + } +} + + +static void InitPowerInfo_R( + CPowerInfo *pPowerInfo, + int iMaxPower, + CVertIndex const &nodeIndex, + CVertIndex const &dependency1, + CVertIndex const &dependency2, + CVertIndex const &nodeEdge1, + CVertIndex const &nodeEdge2, + CVertIndex const &iParent, + int iLevel ) +{ + int sideLength = ((1 << iMaxPower) + 1); + int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); + + pPowerInfo->m_pVertInfo[iNodeIndex].m_iParent = iParent; + pPowerInfo->m_pVertInfo[iNodeIndex].m_iNodeLevel = iLevel + 1; + + pPowerInfo->m_pErrorEdges[iNodeIndex].m_Values[0] = (unsigned short)(VertIndex( nodeEdge1, iMaxPower )); + pPowerInfo->m_pErrorEdges[iNodeIndex].m_Values[1] = (unsigned short)(VertIndex( nodeEdge2, iMaxPower )); + + // Add this node's dependencies. + AddDependency( pPowerInfo->m_pVertInfo, sideLength, nodeIndex, dependency1, iMaxPower, false, true ); + AddDependency( pPowerInfo->m_pVertInfo, sideLength, nodeIndex, dependency2, iMaxPower, false, true ); + + // The 4 side vertices depend on this node. + int iPower = iMaxPower - iLevel; + int vertInc = 1 << (iPower - 1); + + for( int iSide=0; iSide < 4; iSide++ ) + { + // Store the side vert index. + CVertIndex sideVert( nodeIndex.x + g_SideVertMul[iSide][0]*vertInc, nodeIndex.y + g_SideVertMul[iSide][1]*vertInc ); + int iSideVert = VertIndex( sideVert, iMaxPower ); + + pPowerInfo->m_pSideVerts[iNodeIndex].m_Verts[iSide] = sideVert; + + // Store the side vert corners. + CVertIndex sideVertCorner0 = CVertIndex( nodeIndex.x + g_SideVertCorners[iSide].m_Corner1[0]*vertInc, nodeIndex.y + g_SideVertCorners[iSide].m_Corner1[1]*vertInc ); + CVertIndex sideVertCorner1 = CVertIndex( nodeIndex.x + g_SideVertCorners[iSide].m_Corner2[0]*vertInc, nodeIndex.y + g_SideVertCorners[iSide].m_Corner2[1]*vertInc ); + + pPowerInfo->m_pSideVertCorners[iNodeIndex].m_Verts[iSide] = sideVertCorner0; + + // Write the side vert corners into the error-edges list. + pPowerInfo->m_pErrorEdges[iSideVert].m_Values[0] = (unsigned short)VertIndex( sideVertCorner0, iMaxPower ); + pPowerInfo->m_pErrorEdges[iSideVert].m_Values[1] = (unsigned short)VertIndex( sideVertCorner1, iMaxPower ); + + AddDependency( + pPowerInfo->m_pVertInfo, + sideLength, + sideVert, + nodeIndex, + iMaxPower, + true, + true ); + } + + // Recurse into the children. + int nodeInc = vertInc >> 1; + if( nodeInc ) + { + for( int iChild=0; iChild < 4; iChild++ ) + { + CVertIndex childVert( nodeIndex.x + g_ChildNodeIndexMul[iChild].x * nodeInc, nodeIndex.y + g_ChildNodeIndexMul[iChild].y * nodeInc ); + + pPowerInfo->m_pChildVerts[iNodeIndex].m_Verts[iChild] = childVert; + + InitPowerInfo_R( pPowerInfo, + iMaxPower, + childVert, + + CVertIndex(nodeIndex.x + g_ChildNodeDependencies[iChild][0].x*vertInc, nodeIndex.y + g_ChildNodeDependencies[iChild][0].y*vertInc), + CVertIndex(nodeIndex.x + g_ChildNodeDependencies[iChild][1].x*vertInc, nodeIndex.y + g_ChildNodeDependencies[iChild][1].y*vertInc), + + nodeIndex, + CVertIndex( nodeIndex.x + g_ChildNodeIndexMul[iChild].x * vertInc, nodeIndex.y + g_ChildNodeIndexMul[iChild].y * vertInc ), + + nodeIndex, + iLevel + 1 ); + } + } +} + + +void InitPowerInfo( CPowerInfo *pInfo, int iMaxPower ) +{ + int sideLength = (1 << iMaxPower) + 1; + + // Precalculate the dependency graph. + CVertIndex nodeDependency1( sideLength-1, sideLength-1 ); + CVertIndex nodeDependency2( 0, 0 ); + + pInfo->m_RootNode = CVertIndex( sideLength/2, sideLength/2 ); + pInfo->m_SideLength = sideLength; + pInfo->m_SideLengthM1 = sideLength - 1; + pInfo->m_MidPoint = sideLength / 2; + pInfo->m_MaxVerts = sideLength * sideLength; + + // Setup the corner indices. + pInfo->m_CornerPointIndices[CORNER_LOWER_LEFT].Init( 0, 0 ); + pInfo->m_CornerPointIndices[CORNER_UPPER_LEFT].Init( 0, sideLength-1 ); + pInfo->m_CornerPointIndices[CORNER_UPPER_RIGHT].Init( sideLength-1, sideLength-1 ); + pInfo->m_CornerPointIndices[CORNER_LOWER_RIGHT].Init( sideLength-1, 0 ); + + InitPowerInfo_R( + pInfo, + iMaxPower, + pInfo->m_RootNode, + + nodeDependency1, // dependencies + nodeDependency2, + + CVertIndex(0,0), // error edge + CVertIndex(sideLength-1, sideLength-1), + + CVertIndex(-1,-1), // parent + 0 ); + + pInfo->m_Power = iMaxPower; + + CTriInfo *pTriInfo = pInfo->m_pTriInfos; + InitPowerInfoTriInfos_R( pInfo, pInfo->m_RootNode, pTriInfo, iMaxPower, 0 ); + + for( int iEdge=0; iEdge < 4; iEdge++ ) + { + // Figure out the start vert and increment. + CVertIndex nextVert; + GetEdgeVertIndex( sideLength, iEdge, 0, pInfo->m_EdgeStartVerts[iEdge] ); + GetEdgeVertIndex( sideLength, iEdge, 1, nextVert ); + pInfo->m_EdgeIncrements[iEdge] = nextVert - pInfo->m_EdgeStartVerts[iEdge]; + + // Now get the neighbor's start vert and increment. + CVertIndex nbStartVert, nbNextVert, nbDelta; + GetEdgeVertIndex( sideLength, (iEdge+2)&3, 0, nbStartVert ); + GetEdgeVertIndex( sideLength, (iEdge+2)&3, 1, nbNextVert ); + nbDelta = nbNextVert - nbStartVert; + + // Rotate it for each orientation. + for( int orient=0; orient < 4; orient++ ) + { + pInfo->m_NeighborStartVerts[iEdge][orient] = Transform2D( + g_OrientationRotations[orient], + nbStartVert, + CVertIndex( sideLength/2, sideLength/2 ) ); + + pInfo->m_NeighborIncrements[iEdge][orient] = Transform2D( + g_OrientationRotations[orient], + nbDelta, + CVertIndex(0,0) ); + } + } + + + // Init the node index increments. + int curPowerOf4 = 1; + int curTotal = 0; + for( int i=0; i < iMaxPower-1; i++ ) + { + curTotal += curPowerOf4; + + pInfo->m_NodeIndexIncrements[iMaxPower-i-2] = curTotal; + + curPowerOf4 *= 4; + } + + // Store off the total node count + pInfo->m_NodeCount = curTotal + curPowerOf4; + + pInfo->m_nTriInfos = Square( 1 << iMaxPower ) * 2; +} + +class CPowerInfoInitializer +{ +public: + CPowerInfoInitializer() + { + Assert( MAX_MAP_DISP_POWER+1 == NUM_POWERINFOS ); + + for( int i=0; i <= MAX_MAP_DISP_POWER; i++ ) + { + if( g_PowerInfos[i] ) + { + InitPowerInfo( g_PowerInfos[i], i ); + } + } + } +}; + +static CPowerInfoInitializer g_PowerInfoInitializer; + + +const CPowerInfo* GetPowerInfo( int iPower ) +{ + Assert( iPower >= 0 && iPower < ARRAYSIZE( g_PowerInfos ) ); + Assert( g_PowerInfos[iPower] ); + return g_PowerInfos[iPower]; +} + + +// ------------------------------------------------------------------------------------------------ // +// CPowerInfo member function initialization. +// ------------------------------------------------------------------------------------------------ // + +const CVertIndex& CPowerInfo::GetCornerPointIndex( int iCorner ) const +{ + Assert( iCorner >= 0 && iCorner < 4 ); + return m_CornerPointIndices[iCorner]; +} + diff --git a/public/dispcoll.cpp b/public/dispcoll.cpp index b80cc63c..38e8263c 100644 --- a/public/dispcoll.cpp +++ b/public/dispcoll.cpp @@ -1,1994 +1,1994 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "BuildDisp.h" -#include "DispColl.h" -#include "tier0/dbg.h" - -//============================================================================= - -const float CDispCollTree::COLLISION_EPSILON = 0.01f; -const float CDispCollTree::ONE_MINUS_COLLISION_EPSILON = 1.0f - COLLISION_EPSILON; - -//============================================================================= -// -// Displacement Collision Triangle Functions -// - - -//----------------------------------------------------------------------------- -// Purpose: initialize the displacement triangles -//----------------------------------------------------------------------------- -void CDispCollTri::Init( void ) -{ - for( int i = 0; i < 3; i++ ) - { - m_Points[i].x = 0.0f; m_Points[i].y = 0.0f; m_Points[i].z = 0.0f; - m_PointNormals[i].x = 0.0f; m_PointNormals[i].y = 0.0f; m_PointNormals[i].z = 0.0f; - } - - m_Normal.x = 0.0f; m_Normal.y = 0.0f; m_Normal.z = 0.0f; - m_Distance = 0.0f; - - m_ProjAxes[0] = -1; - m_ProjAxes[1] = -1; - - m_bIntersect = false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline void CDispCollTri::SetPoint( int index, Vector const &vert ) -{ - Assert( index >= 0 ); - Assert( index < 3 ); - - m_Points[index].x = vert[0]; - m_Points[index].y = vert[1]; - m_Points[index].z = vert[2]; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline void CDispCollTri::SetPointNormal( int index, Vector const &normal ) -{ - Assert( index >= 0 ); - Assert( index < 3 ); - - m_PointNormals[index].x = normal[0]; - m_PointNormals[index].y = normal[1]; - m_PointNormals[index].z = normal[2]; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTri::CalcPlane( void ) -{ - // - // calculate the plane normal and distance - // - Vector segment1, segment2, cross; - - segment1 = m_Points[1] - m_Points[0]; - segment2 = m_Points[2] - m_Points[0]; - cross = segment1.Cross( segment2 ); - m_Normal = cross; - VectorNormalize(m_Normal); - - m_Distance = m_Normal.Dot( m_Points[0] ); - - // - // calculate the projection axes - // - if( FloatMakePositive( m_Normal[0] ) > FloatMakePositive( m_Normal[1] ) ) - { - if( FloatMakePositive( m_Normal[0] ) > FloatMakePositive( m_Normal[2] ) ) - { - m_ProjAxes[0] = 1; - m_ProjAxes[1] = 2; - } - else - { - m_ProjAxes[0] = 0; - m_ProjAxes[1] = 1; - } - } - else - { - if( FloatMakePositive( m_Normal[1] ) > FloatMakePositive( m_Normal[2] ) ) - { - m_ProjAxes[0] = 0; - m_ProjAxes[1] = 2; - } - else - { - m_ProjAxes[0] = 0; - m_ProjAxes[1] = 1; - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline void CDispCollTri::SetIntersect( bool bIntersect ) -{ - m_bIntersect = bIntersect; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline bool CDispCollTri::IsIntersect( void ) -{ - return m_bIntersect; -} - - -//============================================================================= -// -// Displacement Collision Node Functions -// - - -//----------------------------------------------------------------------------- -// Purpose: constructor -//----------------------------------------------------------------------------- -CDispCollNode::CDispCollNode() -{ - m_Bounds[0].x = m_Bounds[0].y = m_Bounds[0].z = 99999.9f; - m_Bounds[1].x = m_Bounds[1].y = m_Bounds[1].z = -99999.9f; - - m_Tris[0].Init(); - m_Tris[1].Init(); - - m_bIsLeaf = false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline bool CDispCollNode::IsLeaf( void ) -{ - return m_bIsLeaf; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline void CDispCollNode::SetBounds( Vector const &bMin, Vector const &bMax ) -{ - m_Bounds[0] = bMin; - m_Bounds[1] = bMax; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline void CDispCollNode::GetBounds( Vector &bMin, Vector &bMax ) -{ - bMin = m_Bounds[0]; - bMax = m_Bounds[1]; -} - - -//============================================================================= -// -// Displacement Collision Tree Functions -// - -//----------------------------------------------------------------------------- -// Purpose: constructor -//----------------------------------------------------------------------------- -CDispCollTree::CDispCollTree() -{ - m_Power = 0; - - m_NodeCount = 0; - m_pNodes = NULL; - - InitAABBData(); -} - - -//----------------------------------------------------------------------------- -// Purpose: deconstructor -//----------------------------------------------------------------------------- -CDispCollTree::~CDispCollTree() -{ - FreeNodes(); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTree::InitAABBData( void ) -{ - m_AABBNormals[0].x = -1.0f; m_AABBNormals[0].y = 0.0f; m_AABBNormals[0].z = 0.0f; - m_AABBNormals[1].x = 1.0f; m_AABBNormals[1].y = 0.0f; m_AABBNormals[1].z = 0.0f; - - m_AABBNormals[2].x = 0.0f; m_AABBNormals[2].y = -1.0f; m_AABBNormals[2].z = 0.0f; - m_AABBNormals[3].x = 0.0f; m_AABBNormals[3].y = 1.0f; m_AABBNormals[3].z = 0.0f; - - m_AABBNormals[4].x = 0.0f; m_AABBNormals[4].y = 0.0f; m_AABBNormals[4].z = -1.0f; - m_AABBNormals[5].x = 0.0f; m_AABBNormals[5].y = 0.0f; m_AABBNormals[5].z = 1.0f; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTree::CalcBounds( CDispCollNode *pNode, int nodeIndex ) -{ - Vector bounds[2]; - bounds[0].Init( 99999.9f, 99999.9f, 99999.9f ); - bounds[1].Init( -99999.9f, -99999.9f, -99999.9f ); - - // - // handle leaves differently -- bounding volume defined by triangles - // - if( pNode->IsLeaf() ) - { - for( int i = 0; i < 2; i++ ) - { - for( int j = 0; j < 3; j++ ) - { - // - // minimum - // - if( bounds[0].x > pNode->m_Tris[i].m_Points[j].x ) { bounds[0].x = pNode->m_Tris[i].m_Points[j].x; } - if( bounds[0].y > pNode->m_Tris[i].m_Points[j].y ) { bounds[0].y = pNode->m_Tris[i].m_Points[j].y; } - if( bounds[0].z > pNode->m_Tris[i].m_Points[j].z ) { bounds[0].z = pNode->m_Tris[i].m_Points[j].z; } - - // - // maximum - // - if( bounds[1].x < pNode->m_Tris[i].m_Points[j].x ) { bounds[1].x = pNode->m_Tris[i].m_Points[j].x; } - if( bounds[1].y < pNode->m_Tris[i].m_Points[j].y ) { bounds[1].y = pNode->m_Tris[i].m_Points[j].y; } - if( bounds[1].z < pNode->m_Tris[i].m_Points[j].z ) { bounds[1].z = pNode->m_Tris[i].m_Points[j].z; } - } - } - } - // - // bounding volume defined by maxima and minima of children volumes - // - else - { - for( int i = 0; i < 4; i++ ) - { - int childIndex = GetChildNode( nodeIndex, i ); - CDispCollNode *pChildNode = &m_pNodes[childIndex]; - - Vector childBounds[2]; - pChildNode->GetBounds( childBounds[0], childBounds[1] ); - - // - // minimum - // - if( bounds[0].x > childBounds[0].x ) { bounds[0].x = childBounds[0].x; } - if( bounds[0].y > childBounds[0].y ) { bounds[0].y = childBounds[0].y; } - if( bounds[0].z > childBounds[0].z ) { bounds[0].z = childBounds[0].z; } - - // - // maximum - // - if( bounds[1].x < childBounds[1].x ) { bounds[1].x = childBounds[1].x; } - if( bounds[1].y < childBounds[1].y ) { bounds[1].y = childBounds[1].y; } - if( bounds[1].z < childBounds[1].z ) { bounds[1].z = childBounds[1].z; } - } - } - - pNode->SetBounds( bounds[0], bounds[1] ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTree::CreateNodes_r( CCoreDispInfo *pDisp, int nodeIndex, int termLevel ) -{ - int nodeLevel = GetNodeLevel( nodeIndex ); - - // - // terminating condition -- set node info (leaf or otherwise) - // - if( nodeLevel == termLevel ) - { - CDispCollNode *pNode = &m_pNodes[nodeIndex]; - CalcBounds( pNode, nodeIndex ); - - return; - } - - // - // recurse into children - // - for( int i = 0; i < 4; i++ ) - { - CreateNodes_r( pDisp, GetChildNode( nodeIndex, i ), termLevel ); - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTree::CreateNodes( CCoreDispInfo *pDisp ) -{ - // - // create all nodes in tree - // - int power = pDisp->GetPower() + 1; - for( int level = power; level > 0; level-- ) - { - CreateNodes_r( pDisp, 0 /* rootIndex */, level ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CDispCollTree::GetNodeIndexFromComponents( int x, int y ) -{ - int index = 0; - - // Interleave bits from the x and y values to create the index: - - for( int shift = 0; x != 0; shift += 2, x >>= 1 ) - { - index |= ( x & 1 ) << shift; - } - - for( shift = 1; y != 0; shift += 2, y >>= 1 ) - { - index |= ( y & 1 ) << shift; - } - - return index; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTree::InitLeaves( CCoreDispInfo *pDisp ) -{ - // - // get power and width and displacement surface - // - int power = pDisp->GetPower(); - int width = pDisp->GetWidth(); - - // - // get leaf indices - // - int startIndex = CalcNodeCount( power - 1 ); - int endIndex = CalcNodeCount( power ); - - for( int index = startIndex; index < endIndex; index++ ) - { - // - // create triangles at leaves - // - int x = ( index - startIndex ) % ( width - 1 ); - int y = ( index - startIndex ) / ( width - 1 ); - - int nodeIndex = GetNodeIndexFromComponents( x, y ); - nodeIndex += startIndex; - - Vector vert; - Vector normal; - - // - // tri 1 - // - pDisp->GetVert( x + ( y * width ), vert ); - pDisp->GetNormal( x + ( y * width ), normal ); - m_pNodes[nodeIndex].m_Tris[0].SetPoint( 0, vert ); - m_pNodes[nodeIndex].m_Tris[0].SetPointNormal( 0, normal ); - - pDisp->GetVert( x + ( ( y + 1 ) * width ), vert ); - pDisp->GetNormal( x + ( ( y + 1 ) * width ), normal ); - m_pNodes[nodeIndex].m_Tris[0].SetPoint( 1, vert ); - m_pNodes[nodeIndex].m_Tris[0].SetPointNormal( 1, normal ); - - pDisp->GetVert( ( x + 1 ) + ( y * width ), vert ); - pDisp->GetNormal( ( x + 1 ) + ( y * width ), normal ); - m_pNodes[nodeIndex].m_Tris[0].SetPoint( 2, vert ); - m_pNodes[nodeIndex].m_Tris[0].SetPointNormal( 2, normal ); - - m_pNodes[nodeIndex].m_Tris[0].CalcPlane(); - - // - // tri 2 - // - pDisp->GetVert( ( x + 1 ) + ( y * width ), vert ); - pDisp->GetNormal( ( x + 1 ) + ( y * width ), normal ); - m_pNodes[nodeIndex].m_Tris[1].SetPoint( 0, vert ); - m_pNodes[nodeIndex].m_Tris[1].SetPointNormal( 0, normal ); - - pDisp->GetVert( x + ( ( y + 1 ) * width ), vert ); - pDisp->GetNormal( x + ( ( y + 1 ) * width ), normal ); - m_pNodes[nodeIndex].m_Tris[1].SetPoint( 1, vert ); - m_pNodes[nodeIndex].m_Tris[1].SetPointNormal( 1, normal ); - - pDisp->GetVert( ( x + 1 ) + ( ( y + 1 ) * width ), vert ); - pDisp->GetNormal( ( x + 1 ) + ( ( y + 1 ) * width ), normal ); - m_pNodes[nodeIndex].m_Tris[1].SetPoint( 2, vert ); - m_pNodes[nodeIndex].m_Tris[1].SetPointNormal( 2, normal ); - - m_pNodes[nodeIndex].m_Tris[1].CalcPlane(); - - // set node as leaf - m_pNodes[nodeIndex].m_bIsLeaf = true; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: allocate and initialize the displacement collision tree -// Input: power - size of the displacement surface -// Output: bool - success? (true/false) -//----------------------------------------------------------------------------- -bool CDispCollTree::Create( CCoreDispInfo *pDisp ) -{ - // - // calculate the number of nodes needed given the size of the displacement - // - m_Power = pDisp->GetPower(); - m_NodeCount = CalcNodeCount( m_Power ); - - // - // allocate tree space - // - if( !AllocNodes( m_NodeCount ) ) - return false; - - // initialize leaves - InitLeaves( pDisp ); - - // create tree nodes - CreateNodes( pDisp ); - - // tree successfully created! - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: allocate memory for the displacement collision tree -// Input: nodeCount - number of nodes to allocate -// Output: bool - success? (true/false) -//----------------------------------------------------------------------------- -bool CDispCollTree::AllocNodes( int nodeCount ) -{ - // sanity check - Assert( nodeCount != 0 ); - - m_pNodes = new CDispCollNode[nodeCount]; - if( !m_pNodes ) - return false; - - // tree successfully allocated! - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: release allocated memory for displacement collision tree -//----------------------------------------------------------------------------- -void CDispCollTree::FreeNodes( void ) -{ - if( m_pNodes ) - { - delete [] m_pNodes; - m_pNodes = NULL; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: calculate the number of tree nodes given the size of the -// displacement surface -// Input: power - size of the displacement surface -// Output: int - the number of tree nodes -//----------------------------------------------------------------------------- -inline int CDispCollTree::CalcNodeCount( int power ) -{ - // power range [2...4] - Assert( power > 0 ); - Assert( power < 5 ); - - return ( ( 1 << ( ( power + 1 ) << 1 ) ) / 3 ); -} - - -//----------------------------------------------------------------------------- -// Purpose: get the parent node index given the current node -// Input: nodeIndex - current node index -// Output: int - the index of the parent node -//----------------------------------------------------------------------------- -inline int CDispCollTree::GetParentNode( int nodeIndex ) -{ - // node range [0...m_NodeCount) - Assert( nodeIndex >= 0 ); - Assert( nodeIndex < m_NodeCount ); - - // ( nodeIndex - 1 ) / 4 - return ( ( nodeIndex - 1 ) >> 2 ); -} - - -//----------------------------------------------------------------------------- -// Purpose: get the child node index given the current node index and direction -// of the child (1 of 4) -// Input: nodeIndex - current node index -// direction - direction of the child ( [0...3] - SW, SE, NW, NE ) -// Output: int - the index of the child node -//----------------------------------------------------------------------------- -inline int CDispCollTree::GetChildNode( int nodeIndex, int direction ) -{ - // node range [0...m_NodeCount) - Assert( nodeIndex >= 0 ); - Assert( nodeIndex < m_NodeCount ); - - // ( nodeIndex * 4 ) + ( direction + 1 ) - return ( ( nodeIndex << 2 ) + ( direction + 1 ) ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -inline int CDispCollTree::GetNodeLevel( int nodeIndex ) -{ - // node range [0...m_NodeCount) - Assert( nodeIndex >= 0 ); - Assert( nodeIndex < m_NodeCount ); - - // level = 2^n + 1 - if( nodeIndex == 0 ) { return 1; } - if( nodeIndex < 5 ) { return 2; } - if( nodeIndex < 21 ) { return 3; } - if( nodeIndex < 85 ) { return 4; } - if( nodeIndex < 341 ) { return 5; } - - return -1; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CDispCollTree::RayTriTest( Vector const &rayStart, Vector const &rayDir, float const rayLength, - CDispCollTri const *pTri, float *fraction ) -{ - const float DET_EPSILON = 0.001f; - const float DIST_EPSILON = 0.001f; - - // - // calculate the edges - // - Vector edge1 = pTri->m_Points[1] - pTri->m_Points[0]; - Vector edge2 = pTri->m_Points[2] - pTri->m_Points[0]; - -// Vector faceNormal = edge1.Cross( edge2 ); -// Vector normNormal = faceNormal.Normalize(); - - // - // calculate the triangle's determinant - // - Vector pVec = rayDir.Cross( edge2 ); - float det = pVec.Dot( edge1 ); - - // if determinant is zero -- ray lies in plane - if( ( det > -DET_EPSILON ) && ( det < DET_EPSILON ) ) - return false; - - // - // utility calculations - inverse determinant and distance from v0 to ray start - // - double invDet = 1.0f / det; - Vector tVec = rayStart - pTri->m_Points[0]; - - // - // calculate the U parameter and test bounds - // - double u = pVec.Dot( tVec ) * invDet; - if( ( u < 0.0f ) || ( u > 1.0f ) ) - return false; - - Vector qVec = tVec.Cross( edge1 ); - - // - // calculate the V parameter and test bounds - // - double v = qVec.Dot( rayDir ) * invDet; - if( ( v < 0.0f ) || ( ( u + v ) > 1.0f ) ) - return false; - - // calculate where ray intersects triangle - *fraction = qVec.Dot( edge2 ) * invDet; - *fraction /= rayLength; - - if( ( *fraction < DIST_EPSILON ) || ( *fraction > ( 1.0f - DIST_EPSILON ) ) ) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CDispCollTree::RayTriListTest( CDispCollTreeTempData *pTemp, CDispCollData *pData ) -{ - // save starting fraction -- to test for collision - float startFraction = pData->m_Fraction; - - // - // calculate the ray - // - Vector seg = pData->m_EndPos - pData->m_StartPos; - Vector rayDir = seg; - float rayLength = VectorNormalize( rayDir ); - - // - // test ray against all triangles in list - // - for( int i = 0; i < pTemp->m_TriListCount; i++ ) - { - float fraction = 1.0f; - bool bResult = RayTriTest( pData->m_StartPos, rayDir, rayLength, pTemp->m_ppTriList[i], &fraction ); - if( !bResult ) - continue; - - if( pData->m_bOcclude ) - { - return true; - } - - if( fraction < pData->m_Fraction ) - { - pData->m_Fraction = fraction; - pData->m_Normal = pTemp->m_ppTriList[i]->m_Normal; - pData->m_Distance = pTemp->m_ppTriList[i]->m_Distance; - } - } - - // collision! - if( pData->m_Fraction < startFraction ) - return true; - - // no collision! - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::RayAABBTest( CDispCollTreeTempData *pTemp, Vector &rayStart, Vector &rayEnd ) -{ - const float MY_DIST_EPSILON = 0.01f; - - for( int i = 0; i < 6; i++ ) - { - float dist1 = m_AABBNormals[i].Dot( rayStart ) - pTemp->m_AABBDistances[i]; - float dist2 = m_AABBNormals[i].Dot( rayEnd ) - pTemp->m_AABBDistances[i]; - - // - // entry intersection point - move ray start up to intersection - // - if( ( dist1 > MY_DIST_EPSILON ) && ( dist2 < -MY_DIST_EPSILON ) ) - { - float fraction = ( dist1 / ( dist1 - dist2 ) ); - - Vector segment, increment; - segment = ( rayEnd - rayStart ) * fraction; - increment = segment; - VectorNormalize(increment); - segment += increment; - rayStart += segment; - } - else if( ( dist1 > MY_DIST_EPSILON ) && ( dist2 > MY_DIST_EPSILON ) ) - { - return false; - } - } - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTree::CreatePlanesFromBounds( CDispCollTreeTempData *pTemp, Vector const &bbMin, Vector const &bbMax ) -{ - // - // note -- these never change! - // -// m_AABBNormals[0].x = -1; -// m_AABBNormals[1].x = 1; - -// m_AABBNormals[2].y = -1; -// m_AABBNormals[3].y = 1; - -// m_AABBNormals[4].z = -1; -// m_AABBNormals[5].z = 1; - - pTemp->m_AABBDistances[0] = -bbMin.x; - pTemp->m_AABBDistances[1] = bbMax.x; - - pTemp->m_AABBDistances[2] = -bbMin.y; - pTemp->m_AABBDistances[3] = bbMax.y; - - pTemp->m_AABBDistances[4] = -bbMin.z; - pTemp->m_AABBDistances[5] = bbMax.z; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTree::RayNodeTest_r( CDispCollTreeTempData *pTemp, int nodeIndex, Vector rayStart, Vector rayEnd ) -{ - // get the current node - CDispCollNode *pNode = &m_pNodes[nodeIndex]; - - // - // get node bounding box and create collision planes - // - Vector bounds[2]; - pNode->GetBounds( bounds[0], bounds[1] ); - CreatePlanesFromBounds( pTemp, bounds[0], bounds[1] ); - - bool bIntersect = RayAABBTest( pTemp, rayStart, rayEnd ); - if( bIntersect ) - { - // done -- add triangles to triangle list - if( pNode->IsLeaf() ) - { - // Assert for now -- flush cache later!!!!! - Assert( pTemp->m_TriListCount >= 0 ); - Assert( pTemp->m_TriListCount < TRILIST_CACHE_SIZE ); - - pTemp->m_ppTriList[pTemp->m_TriListCount] = &pNode->m_Tris[0]; - pTemp->m_ppTriList[pTemp->m_TriListCount+1] = &pNode->m_Tris[1]; - pTemp->m_TriListCount += 2; - } - // continue recursion - else - { - for( int i = 0; i < 4; i++ ) - { - RayNodeTest_r( pTemp, GetChildNode( nodeIndex, i ), rayStart, rayEnd ); - } - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::RayTestAllTris( CDispCollData *pData, int power ) -{ - // - // get leaf indices - // - int startIndex = CalcNodeCount( power - 1 ); - int endIndex = CalcNodeCount( power ); - - // save incoming fraction - float startFraction = pData->m_Fraction; - float fraction = pData->m_Fraction; - - Vector ray = pData->m_EndPos - pData->m_StartPos; - Vector rayDir = ray; - float rayLength = VectorNormalize(rayDir); - - // - // test ray against all triangles in list - // - for( int index = startIndex; index < endIndex; index++ ) - { - for( int j = 0; j < 2; j++ ) - { - bool bResult = RayTriTest( pData->m_StartPos, rayDir, rayLength, &m_pNodes[index].m_Tris[j], &fraction ); - if( !bResult ) - continue; - - if( pData->m_bOcclude ) - { - return true; - } - - if( fraction < pData->m_Fraction ) - { - pData->m_Fraction = fraction; - pData->m_Normal = m_pNodes[index].m_Tris[j].m_Normal; - pData->m_Distance = m_pNodes[index].m_Tris[j].m_Distance; - } - } - } - - // collision! - if( pData->m_Fraction < startFraction ) - return true; - - // no collision! - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::RayTest( CDispCollData *pData ) -{ - // reset the triangle list count - CDispCollTreeTempData tmp; - tmp.m_TriListCount = 0; - - // trace against nodes (copy start, end because they change) - RayNodeTest_r( &tmp, 0, pData->m_StartPos, pData->m_EndPos ); - - // - // trace against tris (if need be) - // - if( tmp.m_TriListCount != 0 ) - { - bool result = RayTriListTest( &tmp, pData ); - return result; - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::SweptAABBTriIntersect( Vector &rayStart, Vector &rayEnd, Vector &extents, - CDispCollTri const *pTri, Vector &plNormal, float *plDist, - float *fraction ) -{ - - // - // PUT A COPY HERE OF START AND END -- SINCE I CHANGE THEM!!!!!! - // - - - - - - int dir, ptIndex; - float closeValue; - float distStart, distEnd; - float t; - Vector rayPt; - - // get ray direction - Vector rayDir = rayEnd - rayStart; - - // initialize fraction - *fraction = 1.0f; - - // - // test for collision with axial planes (x, y, z) - // - for( dir = 0; dir < 3; dir++ ) - { - if( rayDir[dir] < 0.0f ) - { - closeValue = -99999.9f; - for( ptIndex = 0; ptIndex < 3; ptIndex++ ) - { - if( pTri->m_Points[ptIndex][dir] > closeValue ) - { - closeValue = pTri->m_Points[ptIndex][dir]; - } - } - - closeValue += extents[dir]; - - distStart = rayStart[dir] - closeValue; - distEnd = rayEnd[dir] - closeValue; - } - else - { - closeValue = 99999.9f; - for( ptIndex = 0; ptIndex < 3; ptIndex++ ) - { - if( pTri->m_Points[ptIndex][dir] < closeValue ) - { - closeValue = pTri->m_Points[ptIndex][dir]; - } - } - - closeValue -= extents[dir]; - - distStart = -( rayStart[dir] - closeValue ); - distEnd = -( rayEnd[dir] - closeValue ); - } - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal.Init(); - plNormal[dir] = 1.0f; - *plDist = closeValue; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - } - - // - // check for an early out - // - if( ( pTri->m_Normal[0] > ONE_MINUS_COLLISION_EPSILON ) || - ( pTri->m_Normal[1] > ONE_MINUS_COLLISION_EPSILON ) || - ( pTri->m_Normal[2] > ONE_MINUS_COLLISION_EPSILON ) ) - { - if( *fraction == 1.0f ) - return false; - - return true; - } - - // - // handle 9 edge tests - // - Vector normal; - Vector edge; - float dist; - - // find the closest box point - Vector boxPt( 0.0f, 0.0f, 0.0f ); - for( dir = 0; dir < 3; dir++ ) - { - if( rayDir[dir] < 0.0f ) - { - boxPt[dir] = extents[dir]; - } - else - { - boxPt[dir] = -extents[dir]; - } - } - - // - // edge 0 - // - edge = pTri->m_Points[1] - pTri->m_Points[0]; - - // cross x-edge - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - - // extents adjusted dist - dist = ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); - - // find distances from plane (start, end) - distStart = ( normal.y * rayStart.y ) + ( normal.z * rayStart.z ) - dist; - distEnd = ( normal.y * rayEnd.y ) + ( normal.z * rayEnd.z ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // cross y-edge - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - - // extents adjusted dist - dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); - - // find distances from plane (start, end) - distStart = ( normal.x * rayStart.x ) + ( normal.z * rayStart.z ) - dist; - distEnd = ( normal.x * rayEnd.x ) + ( normal.z * rayEnd.z ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // cross z-edge - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - - // extents adjusted dist - dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ); - - // find distances from plane (start, end) - distStart = ( normal.x * rayStart.x ) + ( normal.y * rayStart.y ) - dist; - distEnd = ( normal.x * rayEnd.x ) + ( normal.y * rayEnd.y ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // - // edge 1 - // - edge = pTri->m_Points[2] - pTri->m_Points[1]; - - // cross x-edge - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - - // extents adjusted dist - dist = ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); - - // find distances from plane (start, end) - distStart = ( normal.y * rayStart.y ) + ( normal.z * rayStart.z ) - dist; - distEnd = ( normal.y * rayEnd.y ) + ( normal.z * rayEnd.z ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // cross y-edge - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - - // extents adjusted dist - dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); - - // find distances from plane (start, end) - distStart = ( normal.x * rayStart.x ) + ( normal.z * rayStart.z ) - dist; - distEnd = ( normal.x * rayEnd.x ) + ( normal.z * rayEnd.z ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // cross z-edge - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - - // extents adjusted dist - dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ); - - // find distances from plane (start, end) - distStart = ( normal.x * rayStart.x ) + ( normal.y * rayStart.y ) - dist; - distEnd = ( normal.x * rayEnd.x ) + ( normal.y * rayEnd.y ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // - // edge 2 - // - edge = pTri->m_Points[0] - pTri->m_Points[2]; - - // cross x-edge - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - - // extents adjusted dist - dist = ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); - - // find distances from plane (start, end) - distStart = ( normal.y * rayStart.y ) + ( normal.z * rayStart.z ) - dist; - distEnd = ( normal.y * rayEnd.y ) + ( normal.z * rayEnd.z ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // cross y-edge - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - - // extents adjusted dist - dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); - - // find distances from plane (start, end) - distStart = ( normal.x * rayStart.x ) + ( normal.z * rayStart.z ) - dist; - distEnd = ( normal.x * rayEnd.x ) + ( normal.z * rayEnd.z ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // cross z-edge - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - - // extents adjusted dist - dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ); - - // find distances from plane (start, end) - distStart = ( normal.x * rayStart.x ) + ( normal.y * rayStart.y ) - dist; - distEnd = ( normal.x * rayEnd.x ) + ( normal.y * rayEnd.y ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // - // test face plane - // - dist = ( pTri->m_Normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + - ( pTri->m_Normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + - ( pTri->m_Normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); - - distStart = pTri->m_Normal.Dot( rayStart ) - dist; - distEnd = pTri->m_Normal.Dot( rayEnd ) - dist; - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); - if( t > *fraction ) - { - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayStart ); - *fraction = t; - plNormal = normal; - *plDist = dist; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); - VectorScale( rayDir, t, rayPt ); - VectorAdd( rayStart, rayPt, rayEnd ); - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - if( *fraction == 1.0f ) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::AABBTriIntersect( CDispCollTreeTempData *pTemp, CDispCollData *pData ) -{ - bool bResult = false; - - Vector normal; - float fraction, dist; - - // - // sweep ABB against all triangles in list - // - for( int i = 0; i < pTemp->m_TriListCount; i++ ) - { - if( pTemp->m_ppTriList[i]->IsIntersect() ) - { - bResult = SweptAABBTriIntersect( pData->m_StartPos, pData->m_EndPos, pData->m_Extents, - pTemp->m_ppTriList[i], normal, &dist, &fraction ); - if( bResult ) - { - if( fraction < pData->m_Fraction ) - { - pData->m_Fraction = fraction; - pData->m_Normal = normal; - pData->m_Distance = dist; - } - } - } - } - - return bResult; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::IntersectAABBTriTest( Vector &rayStart, Vector &extents, - CDispCollTri const *pTri ) -{ - int dir, ptIndex; - float dist; - - // - // test axail planes (x, y, z) - // - - for( dir = 0; dir < 3; dir++ ) - { - // - // negative axial plane, component = dir - // - dist = rayStart[dir] - extents[dir]; - for( ptIndex = 0; ptIndex < 3; ptIndex++ ) - { - if( pTri->m_Points[ptIndex][dir] > dist ) - break; - } - - if( ptIndex == 3 ) - return false; - - // - // positive axial plane, component = dir - // - dist = rayStart[dir] + extents[dir]; - for( ptIndex = 0; ptIndex < 3; ptIndex++ ) - { - if( pTri->m_Points[ptIndex][dir] < dist ) - break; - } - - if( ptIndex == 3 ) - return false; - } - - // - // add a test here to see if triangle face normal is close to axial -- done if so!!! - // - if( ( pTri->m_Normal[0] > ONE_MINUS_COLLISION_EPSILON ) || - ( pTri->m_Normal[1] > ONE_MINUS_COLLISION_EPSILON ) || - ( pTri->m_Normal[2] > ONE_MINUS_COLLISION_EPSILON ) ) - return true; - - // find the closest point on the box (use negated tri face noraml) - Vector boxPt( 0.0f, 0.0f, 0.0f ); - for( dir = 0; dir < 3; dir++ ) - { - if( pTri->m_Normal[dir] < 0.0f ) - { - boxPt[dir] = extents[dir]; - } - else - { - boxPt[dir] = -extents[dir]; - } - } - - // - // triangle plane test - // - // do the opposite because the ray has been negated - if( ( ( pTri->m_Normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + - ( pTri->m_Normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + - ( pTri->m_Normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) - return false; - - // - // test edge planes - 9 of them - // - Vector normal; - Vector edge; - - // - // edge 0 - // - edge = pTri->m_Points[1] - pTri->m_Points[0]; - - // cross x - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - if( ( ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) - return false; - - // cross y - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) - return false; - - // cross z - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) ) > 0.0f ) - return false; - - // - // edge 1 - // - edge = pTri->m_Points[2] - pTri->m_Points[1]; - - // cross x - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - if( ( ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) - return false; - - // cross y - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) - return false; - - // cross z - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) ) > 0.0f ) - return false; - - // - // edge 2 - // - edge = pTri->m_Points[0] - pTri->m_Points[2]; - - // cross x - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - if( ( ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) - return false; - - // cross y - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) - return false; - - // cross z - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) ) > 0.0f ) - return false; - - return true; -} - - - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::SweptAABBTriTest( Vector &rayStart, Vector &rayEnd, Vector &extents, - CDispCollTri const *pTri ) -{ - // get ray direction - Vector rayDir = rayEnd - rayStart; - - // - // quick and dirty test -- test to see if the object is traveling away from triangle surface??? - // - if( pTri->m_Normal.Dot( rayDir ) > 0.0f ) - return false; - - // - // calc the swept triangle face (negate the ray -- opposite direction of box travel) - // - rayDir.Negate(); - - Vector points[3]; - points[0] = pTri->m_Points[0] + rayDir; - points[1] = pTri->m_Points[1] + rayDir; - points[2] = pTri->m_Points[2] + rayDir; - - // - // handle 4 faces tests (3 axial planes and triangle face) - // - int dir; - float dist; - - // - // axial planes tests (x, y, z) - // - for( dir = 0; dir < 3; dir++ ) - { - bool bOutside = true; - - if( rayDir[dir] < 0.0f ) - { - dist = rayStart[dir] - extents[dir]; - for( int ptIndex = 0; ptIndex < 3; ptIndex ) - { - if( points[ptIndex][dir] > dist ) - { - bOutside = false; - break; - } - } - } - else - { - dist = rayStart[dir] + extents[dir]; - for( int ptIndex = 0; ptIndex < 3; ptIndex ) - { - if( pTri->m_Points[ptIndex][dir] < dist ) - { - bOutside = false; - break; - } - } - } - - if( bOutside ) - return false; - } - - // - // add a test here to see if triangle face normal is close to axial -- done if so!!! - // - if( ( pTri->m_Normal[0] > ONE_MINUS_COLLISION_EPSILON ) || - ( pTri->m_Normal[1] > ONE_MINUS_COLLISION_EPSILON ) || - ( pTri->m_Normal[2] > ONE_MINUS_COLLISION_EPSILON ) ) - return true; - - // - // handle 9 edge tests - always use the newly swept face for this - // - Vector normal; - Vector edge; - - // find the closest box point - (is written opposite to normal due to negating ray) - Vector boxPt( 0.0f, 0.0f, 0.0f ); - for( dir = 0; dir < 3; dir++ ) - { - if( rayDir[dir] < 0.0f ) - { - boxPt[dir] = rayStart[dir] - extents[dir]; - } - else - { - boxPt[dir] = rayStart[dir] + extents[dir]; - } - } - - // - // edge 0 - // - edge = points[1] - points[0]; - - // cross x-edge - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - if( ( ( normal.y * ( boxPt.y - points[0].y ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) - return false; - - // cross, y-edge - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) - return false; - - // cross z-edge - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.y * ( boxPt.y - points[0].y ) ) ) > 0.0f ) - return false; - - // - // edge 1 - // - edge = points[2] - points[1]; - - // cross x-edge - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - if( ( ( normal.y * ( boxPt.y - points[0].y ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) - return false; - - // cross, y-edge - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) - return false; - - // cross z-edge - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.y * ( boxPt.y - points[0].y ) ) ) > 0.0f ) - return false; - - // - // edge 2 - // - edge = points[0] - points[2]; - - // cross x-edge - normal.x = 0.0f; - normal.y = -edge.z; - normal.z = edge.y; - if( ( ( normal.y * ( boxPt.y - points[0].y ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) - return false; - - // cross, y-edge - normal.x = edge.z; - normal.y = 0.0f; - normal.z = edge.y; - if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) - return false; - - // cross z-edge - normal.x = -edge.y; - normal.y = edge.x; - normal.z = 0.0f; - if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.y * ( boxPt.y - points[0].y ) ) ) > 0.0f ) - return false; - - // - // triangle plane test - // - // do the opposite because the ray has been negated - if( ( ( pTri->m_Normal.x * ( boxPt.x - points[0].x ) ) + - ( pTri->m_Normal.y * ( boxPt.y - points[0].y ) ) + - ( pTri->m_Normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::CullTriList( CDispCollTreeTempData *pTemp, Vector &rayStart, Vector &rayEnd, Vector &extents, bool bIntersect ) -{ - // - // intersect AABB with all triangles in list - // - if( bIntersect ) - { - for( int i = 0; i < pTemp->m_TriListCount; i++ ) - { - if( IntersectAABBTriTest( rayStart, extents, pTemp->m_ppTriList[i] ) ) - return true; - } - - return false; - } - // - // sweep AABB against all triangles in list - // - else - { - bool bResult = false; - - for( int i = 0; i < pTemp->m_TriListCount; i++ ) - { - if( SweptAABBTriTest( rayStart, rayEnd, extents, pTemp->m_ppTriList[i] ) ) - { - pTemp->m_ppTriList[i]->SetIntersect( true ); - bResult = true; - } - } - - return bResult; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::IntersectAABBAABBTest( CDispCollTreeTempData *pTemp, const Vector &pos, const Vector &extents ) -{ - float dist; - - for( int dir = 0; dir < 3; dir++ ) - { - // negative direction - dist = -( pos[dir] - ( pTemp->m_AABBDistances[(dir>>1)] - extents[dir] ) ); - if( dist > COLLISION_EPSILON ) - return false; - - // positive direction - dist = pos[dir] - ( pTemp->m_AABBDistances[(dir>>1)+1] + extents[dir] ); - if( dist > COLLISION_EPSILON ) - return false; - } - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::SweptAABBAABBTest( CDispCollTreeTempData *pTemp, const Vector &rayStart, const Vector &rayEnd, const Vector &extents ) -{ - int dir; - float distStart, distEnd; - float fraction; - float deltas[3]; - float scalers[3]; - - // - // enter and exit fractions - // - float enterFraction = 0.0f; - float exitFraction = 0.0f; - - // - // de-normalize the paramter space so that we don't have to divide - // to find the fractional amount later (clamped for precision) - // - deltas[0] = rayEnd.x - rayStart.x; - deltas[1] = rayEnd.y - rayStart.y; - deltas[2] = rayEnd.z - rayStart.z; - if( ( deltas[0] < COLLISION_EPSILON ) && ( deltas[0] > -COLLISION_EPSILON ) ) { deltas[0] = 1.0f; } - if( ( deltas[1] < COLLISION_EPSILON ) && ( deltas[1] > -COLLISION_EPSILON ) ) { deltas[0] = 1.0f; } - if( ( deltas[2] < COLLISION_EPSILON ) && ( deltas[2] > -COLLISION_EPSILON ) ) { deltas[0] = 1.0f; } - scalers[0] = deltas[1] * deltas[2]; - scalers[1] = deltas[0] * deltas[2]; - scalers[2] = deltas[0] * deltas[1]; - - for( dir = 0; dir < 3; dir++ ) - { - // - // negative direction - // - distStart = -( rayStart[dir] - ( pTemp->m_AABBDistances[(dir>>1)] - extents[dir] ) ); - distEnd = -( rayEnd[dir] - ( pTemp->m_AABBDistances[(dir>>1)] - extents[dir] ) ); - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - fraction = distStart * scalers[dir]; - if( fraction > enterFraction ) - { - enterFraction = fraction; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - fraction = distStart * scalers[dir]; - if( fraction < exitFraction ) - { - exitFraction = fraction; - } - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - - // - // positive direction - // - distStart = rayStart[dir] - ( pTemp->m_AABBDistances[(dir>>1)+1] + extents[dir] ); - distEnd = rayEnd[dir] - ( pTemp->m_AABBDistances[(dir>>1)+1] + extents[dir] ); - - if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) - { - fraction = distStart * scalers[dir]; - if( fraction > enterFraction ) - { - enterFraction = fraction; - } - } - else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - fraction = distStart * scalers[dir]; - if( fraction < exitFraction ) - { - exitFraction = fraction; - } - } - else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) - { - return false; - } - } - - if( exitFraction < enterFraction ) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDispCollTree::BuildTriList_r( CDispCollTreeTempData *pTemp, int nodeIndex, Vector &rayStart, Vector &rayEnd, Vector &extents, - bool bIntersect ) -{ - // - // get the current nodes bounds and create collision test planes - // (saved in the in class cache m_AABBNormals, m_AABBDistances) - // - Vector bounds[2]; - CDispCollNode *pNode = &m_pNodes[nodeIndex]; - pNode->GetBounds( bounds[0], bounds[1] ); - CreatePlanesFromBounds( pTemp, bounds[0], bounds[1] ); - - // - // interesect/sweep test - // - bool bResult; - if( bIntersect ) - { - bResult = IntersectAABBAABBTest( pTemp, rayStart, extents ); - } - else - { - bResult = SweptAABBAABBTest( pTemp, rayStart, rayEnd, extents ); - } - - if( bResult ) - { - // if leaf node -- add triangles to interstection test list - if( pNode->IsLeaf() ) - { - // Assert for now -- flush cache later!!!!! - Assert( pTemp->m_TriListCount >= 0 ); - Assert( pTemp->m_TriListCount < TRILIST_CACHE_SIZE ); - - pTemp->m_ppTriList[pTemp->m_TriListCount] = &pNode->m_Tris[0]; - pTemp->m_ppTriList[pTemp->m_TriListCount+1] = &pNode->m_Tris[1]; - pTemp->m_TriListCount += 2; - } - // continue recursion - else - { - BuildTriList_r( pTemp, GetChildNode( nodeIndex, 0 ), rayStart, rayEnd, extents, bIntersect ); - BuildTriList_r( pTemp, GetChildNode( nodeIndex, 1 ), rayStart, rayEnd, extents, bIntersect ); - BuildTriList_r( pTemp, GetChildNode( nodeIndex, 2 ), rayStart, rayEnd, extents, bIntersect ); - BuildTriList_r( pTemp, GetChildNode( nodeIndex, 3 ), rayStart, rayEnd, extents, bIntersect ); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::AABBSweep( CDispCollData *pData ) -{ - // reset the triangle lists counts - CDispCollTreeTempData tmp; - tmp.m_TriListCount = 0; - - // sweep the AABB against the tree - BuildTriList_r( &tmp, 0, pData->m_StartPos, pData->m_EndPos, pData->m_Extents, false ); - - // find collision triangles - if( CullTriList( &tmp, pData->m_StartPos, pData->m_EndPos, pData->m_Extents, false ) ) - { - // find closest intersection - return AABBTriIntersect( &tmp, pData ); - } - - return false; -} - - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CDispCollTree::AABBIntersect( CDispCollData *pData ) -{ - // reset the triangle lists counts - CDispCollTreeTempData tmp; - tmp.m_TriListCount = 0; - - // sweep the AABB against the tree - BuildTriList_r( &tmp, 0, pData->m_StartPos, pData->m_StartPos, pData->m_Extents, true ); - - // find collision triangles - return CullTriList( &tmp, pData->m_StartPos, pData->m_StartPos, pData->m_Extents, true ); -} +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "builddisp.h" +#include "dispcoll.h" +#include "tier0/dbg.h" + +//============================================================================= + +const float CDispCollTree::COLLISION_EPSILON = 0.01f; +const float CDispCollTree::ONE_MINUS_COLLISION_EPSILON = 1.0f - COLLISION_EPSILON; + +//============================================================================= +// +// Displacement Collision Triangle Functions +// + + +//----------------------------------------------------------------------------- +// Purpose: initialize the displacement triangles +//----------------------------------------------------------------------------- +void CDispCollTri::Init( void ) +{ + for( int i = 0; i < 3; i++ ) + { + m_Points[i].x = 0.0f; m_Points[i].y = 0.0f; m_Points[i].z = 0.0f; + m_PointNormals[i].x = 0.0f; m_PointNormals[i].y = 0.0f; m_PointNormals[i].z = 0.0f; + } + + m_Normal.x = 0.0f; m_Normal.y = 0.0f; m_Normal.z = 0.0f; + m_Distance = 0.0f; + + m_ProjAxes[0] = -1; + m_ProjAxes[1] = -1; + + m_bIntersect = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline void CDispCollTri::SetPoint( int index, Vector const &vert ) +{ + Assert( index >= 0 ); + Assert( index < 3 ); + + m_Points[index].x = vert[0]; + m_Points[index].y = vert[1]; + m_Points[index].z = vert[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline void CDispCollTri::SetPointNormal( int index, Vector const &normal ) +{ + Assert( index >= 0 ); + Assert( index < 3 ); + + m_PointNormals[index].x = normal[0]; + m_PointNormals[index].y = normal[1]; + m_PointNormals[index].z = normal[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTri::CalcPlane( void ) +{ + // + // calculate the plane normal and distance + // + Vector segment1, segment2, cross; + + segment1 = m_Points[1] - m_Points[0]; + segment2 = m_Points[2] - m_Points[0]; + cross = segment1.Cross( segment2 ); + m_Normal = cross; + VectorNormalize(m_Normal); + + m_Distance = m_Normal.Dot( m_Points[0] ); + + // + // calculate the projection axes + // + if( FloatMakePositive( m_Normal[0] ) > FloatMakePositive( m_Normal[1] ) ) + { + if( FloatMakePositive( m_Normal[0] ) > FloatMakePositive( m_Normal[2] ) ) + { + m_ProjAxes[0] = 1; + m_ProjAxes[1] = 2; + } + else + { + m_ProjAxes[0] = 0; + m_ProjAxes[1] = 1; + } + } + else + { + if( FloatMakePositive( m_Normal[1] ) > FloatMakePositive( m_Normal[2] ) ) + { + m_ProjAxes[0] = 0; + m_ProjAxes[1] = 2; + } + else + { + m_ProjAxes[0] = 0; + m_ProjAxes[1] = 1; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline void CDispCollTri::SetIntersect( bool bIntersect ) +{ + m_bIntersect = bIntersect; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline bool CDispCollTri::IsIntersect( void ) +{ + return m_bIntersect; +} + + +//============================================================================= +// +// Displacement Collision Node Functions +// + + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +CDispCollNode::CDispCollNode() +{ + m_Bounds[0].x = m_Bounds[0].y = m_Bounds[0].z = 99999.9f; + m_Bounds[1].x = m_Bounds[1].y = m_Bounds[1].z = -99999.9f; + + m_Tris[0].Init(); + m_Tris[1].Init(); + + m_bIsLeaf = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline bool CDispCollNode::IsLeaf( void ) +{ + return m_bIsLeaf; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline void CDispCollNode::SetBounds( Vector const &bMin, Vector const &bMax ) +{ + m_Bounds[0] = bMin; + m_Bounds[1] = bMax; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline void CDispCollNode::GetBounds( Vector &bMin, Vector &bMax ) +{ + bMin = m_Bounds[0]; + bMax = m_Bounds[1]; +} + + +//============================================================================= +// +// Displacement Collision Tree Functions +// + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +CDispCollTree::CDispCollTree() +{ + m_Power = 0; + + m_NodeCount = 0; + m_pNodes = NULL; + + InitAABBData(); +} + + +//----------------------------------------------------------------------------- +// Purpose: deconstructor +//----------------------------------------------------------------------------- +CDispCollTree::~CDispCollTree() +{ + FreeNodes(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTree::InitAABBData( void ) +{ + m_AABBNormals[0].x = -1.0f; m_AABBNormals[0].y = 0.0f; m_AABBNormals[0].z = 0.0f; + m_AABBNormals[1].x = 1.0f; m_AABBNormals[1].y = 0.0f; m_AABBNormals[1].z = 0.0f; + + m_AABBNormals[2].x = 0.0f; m_AABBNormals[2].y = -1.0f; m_AABBNormals[2].z = 0.0f; + m_AABBNormals[3].x = 0.0f; m_AABBNormals[3].y = 1.0f; m_AABBNormals[3].z = 0.0f; + + m_AABBNormals[4].x = 0.0f; m_AABBNormals[4].y = 0.0f; m_AABBNormals[4].z = -1.0f; + m_AABBNormals[5].x = 0.0f; m_AABBNormals[5].y = 0.0f; m_AABBNormals[5].z = 1.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTree::CalcBounds( CDispCollNode *pNode, int nodeIndex ) +{ + Vector bounds[2]; + bounds[0].Init( 99999.9f, 99999.9f, 99999.9f ); + bounds[1].Init( -99999.9f, -99999.9f, -99999.9f ); + + // + // handle leaves differently -- bounding volume defined by triangles + // + if( pNode->IsLeaf() ) + { + for( int i = 0; i < 2; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + // + // minimum + // + if( bounds[0].x > pNode->m_Tris[i].m_Points[j].x ) { bounds[0].x = pNode->m_Tris[i].m_Points[j].x; } + if( bounds[0].y > pNode->m_Tris[i].m_Points[j].y ) { bounds[0].y = pNode->m_Tris[i].m_Points[j].y; } + if( bounds[0].z > pNode->m_Tris[i].m_Points[j].z ) { bounds[0].z = pNode->m_Tris[i].m_Points[j].z; } + + // + // maximum + // + if( bounds[1].x < pNode->m_Tris[i].m_Points[j].x ) { bounds[1].x = pNode->m_Tris[i].m_Points[j].x; } + if( bounds[1].y < pNode->m_Tris[i].m_Points[j].y ) { bounds[1].y = pNode->m_Tris[i].m_Points[j].y; } + if( bounds[1].z < pNode->m_Tris[i].m_Points[j].z ) { bounds[1].z = pNode->m_Tris[i].m_Points[j].z; } + } + } + } + // + // bounding volume defined by maxima and minima of children volumes + // + else + { + for( int i = 0; i < 4; i++ ) + { + int childIndex = GetChildNode( nodeIndex, i ); + CDispCollNode *pChildNode = &m_pNodes[childIndex]; + + Vector childBounds[2]; + pChildNode->GetBounds( childBounds[0], childBounds[1] ); + + // + // minimum + // + if( bounds[0].x > childBounds[0].x ) { bounds[0].x = childBounds[0].x; } + if( bounds[0].y > childBounds[0].y ) { bounds[0].y = childBounds[0].y; } + if( bounds[0].z > childBounds[0].z ) { bounds[0].z = childBounds[0].z; } + + // + // maximum + // + if( bounds[1].x < childBounds[1].x ) { bounds[1].x = childBounds[1].x; } + if( bounds[1].y < childBounds[1].y ) { bounds[1].y = childBounds[1].y; } + if( bounds[1].z < childBounds[1].z ) { bounds[1].z = childBounds[1].z; } + } + } + + pNode->SetBounds( bounds[0], bounds[1] ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTree::CreateNodes_r( CCoreDispInfo *pDisp, int nodeIndex, int termLevel ) +{ + int nodeLevel = GetNodeLevel( nodeIndex ); + + // + // terminating condition -- set node info (leaf or otherwise) + // + if( nodeLevel == termLevel ) + { + CDispCollNode *pNode = &m_pNodes[nodeIndex]; + CalcBounds( pNode, nodeIndex ); + + return; + } + + // + // recurse into children + // + for( int i = 0; i < 4; i++ ) + { + CreateNodes_r( pDisp, GetChildNode( nodeIndex, i ), termLevel ); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTree::CreateNodes( CCoreDispInfo *pDisp ) +{ + // + // create all nodes in tree + // + int power = pDisp->GetPower() + 1; + for( int level = power; level > 0; level-- ) + { + CreateNodes_r( pDisp, 0 /* rootIndex */, level ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CDispCollTree::GetNodeIndexFromComponents( int x, int y ) +{ + int index = 0; + + // Interleave bits from the x and y values to create the index: + + for( int shift = 0; x != 0; shift += 2, x >>= 1 ) + { + index |= ( x & 1 ) << shift; + } + + for( int shift = 1; y != 0; shift += 2, y >>= 1 ) + { + index |= ( y & 1 ) << shift; + } + + return index; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTree::InitLeaves( CCoreDispInfo *pDisp ) +{ + // + // get power and width and displacement surface + // + int power = pDisp->GetPower(); + int width = pDisp->GetWidth(); + + // + // get leaf indices + // + int startIndex = CalcNodeCount( power - 1 ); + int endIndex = CalcNodeCount( power ); + + for( int index = startIndex; index < endIndex; index++ ) + { + // + // create triangles at leaves + // + int x = ( index - startIndex ) % ( width - 1 ); + int y = ( index - startIndex ) / ( width - 1 ); + + int nodeIndex = GetNodeIndexFromComponents( x, y ); + nodeIndex += startIndex; + + Vector vert; + Vector normal; + + // + // tri 1 + // + pDisp->GetVert( x + ( y * width ), vert ); + pDisp->GetNormal( x + ( y * width ), normal ); + m_pNodes[nodeIndex].m_Tris[0].SetPoint( 0, vert ); + m_pNodes[nodeIndex].m_Tris[0].SetPointNormal( 0, normal ); + + pDisp->GetVert( x + ( ( y + 1 ) * width ), vert ); + pDisp->GetNormal( x + ( ( y + 1 ) * width ), normal ); + m_pNodes[nodeIndex].m_Tris[0].SetPoint( 1, vert ); + m_pNodes[nodeIndex].m_Tris[0].SetPointNormal( 1, normal ); + + pDisp->GetVert( ( x + 1 ) + ( y * width ), vert ); + pDisp->GetNormal( ( x + 1 ) + ( y * width ), normal ); + m_pNodes[nodeIndex].m_Tris[0].SetPoint( 2, vert ); + m_pNodes[nodeIndex].m_Tris[0].SetPointNormal( 2, normal ); + + m_pNodes[nodeIndex].m_Tris[0].CalcPlane(); + + // + // tri 2 + // + pDisp->GetVert( ( x + 1 ) + ( y * width ), vert ); + pDisp->GetNormal( ( x + 1 ) + ( y * width ), normal ); + m_pNodes[nodeIndex].m_Tris[1].SetPoint( 0, vert ); + m_pNodes[nodeIndex].m_Tris[1].SetPointNormal( 0, normal ); + + pDisp->GetVert( x + ( ( y + 1 ) * width ), vert ); + pDisp->GetNormal( x + ( ( y + 1 ) * width ), normal ); + m_pNodes[nodeIndex].m_Tris[1].SetPoint( 1, vert ); + m_pNodes[nodeIndex].m_Tris[1].SetPointNormal( 1, normal ); + + pDisp->GetVert( ( x + 1 ) + ( ( y + 1 ) * width ), vert ); + pDisp->GetNormal( ( x + 1 ) + ( ( y + 1 ) * width ), normal ); + m_pNodes[nodeIndex].m_Tris[1].SetPoint( 2, vert ); + m_pNodes[nodeIndex].m_Tris[1].SetPointNormal( 2, normal ); + + m_pNodes[nodeIndex].m_Tris[1].CalcPlane(); + + // set node as leaf + m_pNodes[nodeIndex].m_bIsLeaf = true; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: allocate and initialize the displacement collision tree +// Input: power - size of the displacement surface +// Output: bool - success? (true/false) +//----------------------------------------------------------------------------- +bool CDispCollTree::Create( CCoreDispInfo *pDisp ) +{ + // + // calculate the number of nodes needed given the size of the displacement + // + m_Power = pDisp->GetPower(); + m_NodeCount = CalcNodeCount( m_Power ); + + // + // allocate tree space + // + if( !AllocNodes( m_NodeCount ) ) + return false; + + // initialize leaves + InitLeaves( pDisp ); + + // create tree nodes + CreateNodes( pDisp ); + + // tree successfully created! + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: allocate memory for the displacement collision tree +// Input: nodeCount - number of nodes to allocate +// Output: bool - success? (true/false) +//----------------------------------------------------------------------------- +bool CDispCollTree::AllocNodes( int nodeCount ) +{ + // sanity check + Assert( nodeCount != 0 ); + + m_pNodes = new CDispCollNode[nodeCount]; + if( !m_pNodes ) + return false; + + // tree successfully allocated! + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: release allocated memory for displacement collision tree +//----------------------------------------------------------------------------- +void CDispCollTree::FreeNodes( void ) +{ + if( m_pNodes ) + { + delete [] m_pNodes; + m_pNodes = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate the number of tree nodes given the size of the +// displacement surface +// Input: power - size of the displacement surface +// Output: int - the number of tree nodes +//----------------------------------------------------------------------------- +inline int CDispCollTree::CalcNodeCount( int power ) +{ + // power range [2...4] + Assert( power > 0 ); + Assert( power < 5 ); + + return ( ( 1 << ( ( power + 1 ) << 1 ) ) / 3 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: get the parent node index given the current node +// Input: nodeIndex - current node index +// Output: int - the index of the parent node +//----------------------------------------------------------------------------- +inline int CDispCollTree::GetParentNode( int nodeIndex ) +{ + // node range [0...m_NodeCount) + Assert( nodeIndex >= 0 ); + Assert( nodeIndex < m_NodeCount ); + + // ( nodeIndex - 1 ) / 4 + return ( ( nodeIndex - 1 ) >> 2 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: get the child node index given the current node index and direction +// of the child (1 of 4) +// Input: nodeIndex - current node index +// direction - direction of the child ( [0...3] - SW, SE, NW, NE ) +// Output: int - the index of the child node +//----------------------------------------------------------------------------- +inline int CDispCollTree::GetChildNode( int nodeIndex, int direction ) +{ + // node range [0...m_NodeCount) + Assert( nodeIndex >= 0 ); + Assert( nodeIndex < m_NodeCount ); + + // ( nodeIndex * 4 ) + ( direction + 1 ) + return ( ( nodeIndex << 2 ) + ( direction + 1 ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CDispCollTree::GetNodeLevel( int nodeIndex ) +{ + // node range [0...m_NodeCount) + Assert( nodeIndex >= 0 ); + Assert( nodeIndex < m_NodeCount ); + + // level = 2^n + 1 + if( nodeIndex == 0 ) { return 1; } + if( nodeIndex < 5 ) { return 2; } + if( nodeIndex < 21 ) { return 3; } + if( nodeIndex < 85 ) { return 4; } + if( nodeIndex < 341 ) { return 5; } + + return -1; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CDispCollTree::RayTriTest( Vector const &rayStart, Vector const &rayDir, float const rayLength, + CDispCollTri const *pTri, float *fraction ) +{ + const float DET_EPSILON = 0.001f; + const float DIST_EPSILON = 0.001f; + + // + // calculate the edges + // + Vector edge1 = pTri->m_Points[1] - pTri->m_Points[0]; + Vector edge2 = pTri->m_Points[2] - pTri->m_Points[0]; + +// Vector faceNormal = edge1.Cross( edge2 ); +// Vector normNormal = faceNormal.Normalize(); + + // + // calculate the triangle's determinant + // + Vector pVec = rayDir.Cross( edge2 ); + float det = pVec.Dot( edge1 ); + + // if determinant is zero -- ray lies in plane + if( ( det > -DET_EPSILON ) && ( det < DET_EPSILON ) ) + return false; + + // + // utility calculations - inverse determinant and distance from v0 to ray start + // + double invDet = 1.0f / det; + Vector tVec = rayStart - pTri->m_Points[0]; + + // + // calculate the U parameter and test bounds + // + double u = pVec.Dot( tVec ) * invDet; + if( ( u < 0.0f ) || ( u > 1.0f ) ) + return false; + + Vector qVec = tVec.Cross( edge1 ); + + // + // calculate the V parameter and test bounds + // + double v = qVec.Dot( rayDir ) * invDet; + if( ( v < 0.0f ) || ( ( u + v ) > 1.0f ) ) + return false; + + // calculate where ray intersects triangle + *fraction = qVec.Dot( edge2 ) * invDet; + *fraction /= rayLength; + + if( ( *fraction < DIST_EPSILON ) || ( *fraction > ( 1.0f - DIST_EPSILON ) ) ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CDispCollTree::RayTriListTest( CDispCollTreeTempData *pTemp, CDispCollData *pData ) +{ + // save starting fraction -- to test for collision + float startFraction = pData->m_Fraction; + + // + // calculate the ray + // + Vector seg = pData->m_EndPos - pData->m_StartPos; + Vector rayDir = seg; + float rayLength = VectorNormalize( rayDir ); + + // + // test ray against all triangles in list + // + for( int i = 0; i < pTemp->m_TriListCount; i++ ) + { + float fraction = 1.0f; + bool bResult = RayTriTest( pData->m_StartPos, rayDir, rayLength, pTemp->m_ppTriList[i], &fraction ); + if( !bResult ) + continue; + + if( pData->m_bOcclude ) + { + return true; + } + + if( fraction < pData->m_Fraction ) + { + pData->m_Fraction = fraction; + pData->m_Normal = pTemp->m_ppTriList[i]->m_Normal; + pData->m_Distance = pTemp->m_ppTriList[i]->m_Distance; + } + } + + // collision! + if( pData->m_Fraction < startFraction ) + return true; + + // no collision! + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::RayAABBTest( CDispCollTreeTempData *pTemp, Vector &rayStart, Vector &rayEnd ) +{ + const float MY_DIST_EPSILON = 0.01f; + + for( int i = 0; i < 6; i++ ) + { + float dist1 = m_AABBNormals[i].Dot( rayStart ) - pTemp->m_AABBDistances[i]; + float dist2 = m_AABBNormals[i].Dot( rayEnd ) - pTemp->m_AABBDistances[i]; + + // + // entry intersection point - move ray start up to intersection + // + if( ( dist1 > MY_DIST_EPSILON ) && ( dist2 < -MY_DIST_EPSILON ) ) + { + float fraction = ( dist1 / ( dist1 - dist2 ) ); + + Vector segment, increment; + segment = ( rayEnd - rayStart ) * fraction; + increment = segment; + VectorNormalize(increment); + segment += increment; + rayStart += segment; + } + else if( ( dist1 > MY_DIST_EPSILON ) && ( dist2 > MY_DIST_EPSILON ) ) + { + return false; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTree::CreatePlanesFromBounds( CDispCollTreeTempData *pTemp, Vector const &bbMin, Vector const &bbMax ) +{ + // + // note -- these never change! + // +// m_AABBNormals[0].x = -1; +// m_AABBNormals[1].x = 1; + +// m_AABBNormals[2].y = -1; +// m_AABBNormals[3].y = 1; + +// m_AABBNormals[4].z = -1; +// m_AABBNormals[5].z = 1; + + pTemp->m_AABBDistances[0] = -bbMin.x; + pTemp->m_AABBDistances[1] = bbMax.x; + + pTemp->m_AABBDistances[2] = -bbMin.y; + pTemp->m_AABBDistances[3] = bbMax.y; + + pTemp->m_AABBDistances[4] = -bbMin.z; + pTemp->m_AABBDistances[5] = bbMax.z; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTree::RayNodeTest_r( CDispCollTreeTempData *pTemp, int nodeIndex, Vector rayStart, Vector rayEnd ) +{ + // get the current node + CDispCollNode *pNode = &m_pNodes[nodeIndex]; + + // + // get node bounding box and create collision planes + // + Vector bounds[2]; + pNode->GetBounds( bounds[0], bounds[1] ); + CreatePlanesFromBounds( pTemp, bounds[0], bounds[1] ); + + bool bIntersect = RayAABBTest( pTemp, rayStart, rayEnd ); + if( bIntersect ) + { + // done -- add triangles to triangle list + if( pNode->IsLeaf() ) + { + // Assert for now -- flush cache later!!!!! + Assert( pTemp->m_TriListCount >= 0 ); + Assert( pTemp->m_TriListCount < TRILIST_CACHE_SIZE ); + + pTemp->m_ppTriList[pTemp->m_TriListCount] = &pNode->m_Tris[0]; + pTemp->m_ppTriList[pTemp->m_TriListCount+1] = &pNode->m_Tris[1]; + pTemp->m_TriListCount += 2; + } + // continue recursion + else + { + for( int i = 0; i < 4; i++ ) + { + RayNodeTest_r( pTemp, GetChildNode( nodeIndex, i ), rayStart, rayEnd ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::RayTestAllTris( CDispCollData *pData, int power ) +{ + // + // get leaf indices + // + int startIndex = CalcNodeCount( power - 1 ); + int endIndex = CalcNodeCount( power ); + + // save incoming fraction + float startFraction = pData->m_Fraction; + float fraction = pData->m_Fraction; + + Vector ray = pData->m_EndPos - pData->m_StartPos; + Vector rayDir = ray; + float rayLength = VectorNormalize(rayDir); + + // + // test ray against all triangles in list + // + for( int index = startIndex; index < endIndex; index++ ) + { + for( int j = 0; j < 2; j++ ) + { + bool bResult = RayTriTest( pData->m_StartPos, rayDir, rayLength, &m_pNodes[index].m_Tris[j], &fraction ); + if( !bResult ) + continue; + + if( pData->m_bOcclude ) + { + return true; + } + + if( fraction < pData->m_Fraction ) + { + pData->m_Fraction = fraction; + pData->m_Normal = m_pNodes[index].m_Tris[j].m_Normal; + pData->m_Distance = m_pNodes[index].m_Tris[j].m_Distance; + } + } + } + + // collision! + if( pData->m_Fraction < startFraction ) + return true; + + // no collision! + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::RayTest( CDispCollData *pData ) +{ + // reset the triangle list count + CDispCollTreeTempData tmp; + tmp.m_TriListCount = 0; + + // trace against nodes (copy start, end because they change) + RayNodeTest_r( &tmp, 0, pData->m_StartPos, pData->m_EndPos ); + + // + // trace against tris (if need be) + // + if( tmp.m_TriListCount != 0 ) + { + bool result = RayTriListTest( &tmp, pData ); + return result; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::SweptAABBTriIntersect( Vector &rayStart, Vector &rayEnd, Vector &extents, + CDispCollTri const *pTri, Vector &plNormal, float *plDist, + float *fraction ) +{ + + // + // PUT A COPY HERE OF START AND END -- SINCE I CHANGE THEM!!!!!! + // + + + + + + int dir, ptIndex; + float closeValue; + float distStart, distEnd; + float t; + Vector rayPt; + + // get ray direction + Vector rayDir = rayEnd - rayStart; + + // initialize fraction + *fraction = 1.0f; + + // + // test for collision with axial planes (x, y, z) + // + for( dir = 0; dir < 3; dir++ ) + { + if( rayDir[dir] < 0.0f ) + { + closeValue = -99999.9f; + for( ptIndex = 0; ptIndex < 3; ptIndex++ ) + { + if( pTri->m_Points[ptIndex][dir] > closeValue ) + { + closeValue = pTri->m_Points[ptIndex][dir]; + } + } + + closeValue += extents[dir]; + + distStart = rayStart[dir] - closeValue; + distEnd = rayEnd[dir] - closeValue; + } + else + { + closeValue = 99999.9f; + for( ptIndex = 0; ptIndex < 3; ptIndex++ ) + { + if( pTri->m_Points[ptIndex][dir] < closeValue ) + { + closeValue = pTri->m_Points[ptIndex][dir]; + } + } + + closeValue -= extents[dir]; + + distStart = -( rayStart[dir] - closeValue ); + distEnd = -( rayEnd[dir] - closeValue ); + } + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal.Init(); + plNormal[dir] = 1.0f; + *plDist = closeValue; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + } + + // + // check for an early out + // + if( ( pTri->m_Normal[0] > ONE_MINUS_COLLISION_EPSILON ) || + ( pTri->m_Normal[1] > ONE_MINUS_COLLISION_EPSILON ) || + ( pTri->m_Normal[2] > ONE_MINUS_COLLISION_EPSILON ) ) + { + if( *fraction == 1.0f ) + return false; + + return true; + } + + // + // handle 9 edge tests + // + Vector normal; + Vector edge; + float dist; + + // find the closest box point + Vector boxPt( 0.0f, 0.0f, 0.0f ); + for( dir = 0; dir < 3; dir++ ) + { + if( rayDir[dir] < 0.0f ) + { + boxPt[dir] = extents[dir]; + } + else + { + boxPt[dir] = -extents[dir]; + } + } + + // + // edge 0 + // + edge = pTri->m_Points[1] - pTri->m_Points[0]; + + // cross x-edge + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + + // extents adjusted dist + dist = ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); + + // find distances from plane (start, end) + distStart = ( normal.y * rayStart.y ) + ( normal.z * rayStart.z ) - dist; + distEnd = ( normal.y * rayEnd.y ) + ( normal.z * rayEnd.z ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // cross y-edge + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + + // extents adjusted dist + dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); + + // find distances from plane (start, end) + distStart = ( normal.x * rayStart.x ) + ( normal.z * rayStart.z ) - dist; + distEnd = ( normal.x * rayEnd.x ) + ( normal.z * rayEnd.z ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // cross z-edge + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + + // extents adjusted dist + dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ); + + // find distances from plane (start, end) + distStart = ( normal.x * rayStart.x ) + ( normal.y * rayStart.y ) - dist; + distEnd = ( normal.x * rayEnd.x ) + ( normal.y * rayEnd.y ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // + // edge 1 + // + edge = pTri->m_Points[2] - pTri->m_Points[1]; + + // cross x-edge + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + + // extents adjusted dist + dist = ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); + + // find distances from plane (start, end) + distStart = ( normal.y * rayStart.y ) + ( normal.z * rayStart.z ) - dist; + distEnd = ( normal.y * rayEnd.y ) + ( normal.z * rayEnd.z ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // cross y-edge + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + + // extents adjusted dist + dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); + + // find distances from plane (start, end) + distStart = ( normal.x * rayStart.x ) + ( normal.z * rayStart.z ) - dist; + distEnd = ( normal.x * rayEnd.x ) + ( normal.z * rayEnd.z ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // cross z-edge + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + + // extents adjusted dist + dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ); + + // find distances from plane (start, end) + distStart = ( normal.x * rayStart.x ) + ( normal.y * rayStart.y ) - dist; + distEnd = ( normal.x * rayEnd.x ) + ( normal.y * rayEnd.y ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // + // edge 2 + // + edge = pTri->m_Points[0] - pTri->m_Points[2]; + + // cross x-edge + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + + // extents adjusted dist + dist = ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); + + // find distances from plane (start, end) + distStart = ( normal.y * rayStart.y ) + ( normal.z * rayStart.z ) - dist; + distEnd = ( normal.y * rayEnd.y ) + ( normal.z * rayEnd.z ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // cross y-edge + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + + // extents adjusted dist + dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); + + // find distances from plane (start, end) + distStart = ( normal.x * rayStart.x ) + ( normal.z * rayStart.z ) - dist; + distEnd = ( normal.x * rayEnd.x ) + ( normal.z * rayEnd.z ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // cross z-edge + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + + // extents adjusted dist + dist = ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ); + + // find distances from plane (start, end) + distStart = ( normal.x * rayStart.x ) + ( normal.y * rayStart.y ) - dist; + distEnd = ( normal.x * rayEnd.x ) + ( normal.y * rayEnd.y ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // + // test face plane + // + dist = ( pTri->m_Normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + + ( pTri->m_Normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + + ( pTri->m_Normal.z * ( boxPt.z - pTri->m_Points[0].z ) ); + + distStart = pTri->m_Normal.Dot( rayStart ) - dist; + distEnd = pTri->m_Normal.Dot( rayEnd ) - dist; + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + t = ( distStart - COLLISION_EPSILON ) / ( distStart - distEnd ); + if( t > *fraction ) + { + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayStart ); + *fraction = t; + plNormal = normal; + *plDist = dist; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + t = ( distStart + COLLISION_EPSILON ) / ( distStart - distEnd ); + VectorScale( rayDir, t, rayPt ); + VectorAdd( rayStart, rayPt, rayEnd ); + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + if( *fraction == 1.0f ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::AABBTriIntersect( CDispCollTreeTempData *pTemp, CDispCollData *pData ) +{ + bool bResult = false; + + Vector normal; + float fraction, dist; + + // + // sweep ABB against all triangles in list + // + for( int i = 0; i < pTemp->m_TriListCount; i++ ) + { + if( pTemp->m_ppTriList[i]->IsIntersect() ) + { + bResult = SweptAABBTriIntersect( pData->m_StartPos, pData->m_EndPos, pData->m_Extents, + pTemp->m_ppTriList[i], normal, &dist, &fraction ); + if( bResult ) + { + if( fraction < pData->m_Fraction ) + { + pData->m_Fraction = fraction; + pData->m_Normal = normal; + pData->m_Distance = dist; + } + } + } + } + + return bResult; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::IntersectAABBTriTest( Vector &rayStart, Vector &extents, + CDispCollTri const *pTri ) +{ + int dir, ptIndex; + float dist; + + // + // test axail planes (x, y, z) + // + + for( dir = 0; dir < 3; dir++ ) + { + // + // negative axial plane, component = dir + // + dist = rayStart[dir] - extents[dir]; + for( ptIndex = 0; ptIndex < 3; ptIndex++ ) + { + if( pTri->m_Points[ptIndex][dir] > dist ) + break; + } + + if( ptIndex == 3 ) + return false; + + // + // positive axial plane, component = dir + // + dist = rayStart[dir] + extents[dir]; + for( ptIndex = 0; ptIndex < 3; ptIndex++ ) + { + if( pTri->m_Points[ptIndex][dir] < dist ) + break; + } + + if( ptIndex == 3 ) + return false; + } + + // + // add a test here to see if triangle face normal is close to axial -- done if so!!! + // + if( ( pTri->m_Normal[0] > ONE_MINUS_COLLISION_EPSILON ) || + ( pTri->m_Normal[1] > ONE_MINUS_COLLISION_EPSILON ) || + ( pTri->m_Normal[2] > ONE_MINUS_COLLISION_EPSILON ) ) + return true; + + // find the closest point on the box (use negated tri face noraml) + Vector boxPt( 0.0f, 0.0f, 0.0f ); + for( dir = 0; dir < 3; dir++ ) + { + if( pTri->m_Normal[dir] < 0.0f ) + { + boxPt[dir] = extents[dir]; + } + else + { + boxPt[dir] = -extents[dir]; + } + } + + // + // triangle plane test + // + // do the opposite because the ray has been negated + if( ( ( pTri->m_Normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + + ( pTri->m_Normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + + ( pTri->m_Normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) + return false; + + // + // test edge planes - 9 of them + // + Vector normal; + Vector edge; + + // + // edge 0 + // + edge = pTri->m_Points[1] - pTri->m_Points[0]; + + // cross x + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + if( ( ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) + return false; + + // cross y + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) + return false; + + // cross z + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) ) > 0.0f ) + return false; + + // + // edge 1 + // + edge = pTri->m_Points[2] - pTri->m_Points[1]; + + // cross x + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + if( ( ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) + return false; + + // cross y + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) + return false; + + // cross z + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) ) > 0.0f ) + return false; + + // + // edge 2 + // + edge = pTri->m_Points[0] - pTri->m_Points[2]; + + // cross x + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + if( ( ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) + return false; + + // cross y + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.z * ( boxPt.z - pTri->m_Points[0].z ) ) ) > 0.0f ) + return false; + + // cross z + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + if( ( ( normal.x * ( boxPt.x - pTri->m_Points[0].x ) ) + ( normal.y * ( boxPt.y - pTri->m_Points[0].y ) ) ) > 0.0f ) + return false; + + return true; +} + + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::SweptAABBTriTest( Vector &rayStart, Vector &rayEnd, Vector &extents, + CDispCollTri const *pTri ) +{ + // get ray direction + Vector rayDir = rayEnd - rayStart; + + // + // quick and dirty test -- test to see if the object is traveling away from triangle surface??? + // + if( pTri->m_Normal.Dot( rayDir ) > 0.0f ) + return false; + + // + // calc the swept triangle face (negate the ray -- opposite direction of box travel) + // + rayDir.Negate(); + + Vector points[3]; + points[0] = pTri->m_Points[0] + rayDir; + points[1] = pTri->m_Points[1] + rayDir; + points[2] = pTri->m_Points[2] + rayDir; + + // + // handle 4 faces tests (3 axial planes and triangle face) + // + int dir; + float dist; + + // + // axial planes tests (x, y, z) + // + for( dir = 0; dir < 3; dir++ ) + { + bool bOutside = true; + + if( rayDir[dir] < 0.0f ) + { + dist = rayStart[dir] - extents[dir]; + for( int ptIndex = 0; ptIndex < 3; ) + { + if( points[ptIndex][dir] > dist ) + { + bOutside = false; + break; + } + } + } + else + { + dist = rayStart[dir] + extents[dir]; + for( int ptIndex = 0; ptIndex < 3; ) + { + if( pTri->m_Points[ptIndex][dir] < dist ) + { + bOutside = false; + break; + } + } + } + + if( bOutside ) + return false; + } + + // + // add a test here to see if triangle face normal is close to axial -- done if so!!! + // + if( ( pTri->m_Normal[0] > ONE_MINUS_COLLISION_EPSILON ) || + ( pTri->m_Normal[1] > ONE_MINUS_COLLISION_EPSILON ) || + ( pTri->m_Normal[2] > ONE_MINUS_COLLISION_EPSILON ) ) + return true; + + // + // handle 9 edge tests - always use the newly swept face for this + // + Vector normal; + Vector edge; + + // find the closest box point - (is written opposite to normal due to negating ray) + Vector boxPt( 0.0f, 0.0f, 0.0f ); + for( dir = 0; dir < 3; dir++ ) + { + if( rayDir[dir] < 0.0f ) + { + boxPt[dir] = rayStart[dir] - extents[dir]; + } + else + { + boxPt[dir] = rayStart[dir] + extents[dir]; + } + } + + // + // edge 0 + // + edge = points[1] - points[0]; + + // cross x-edge + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + if( ( ( normal.y * ( boxPt.y - points[0].y ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) + return false; + + // cross, y-edge + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) + return false; + + // cross z-edge + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.y * ( boxPt.y - points[0].y ) ) ) > 0.0f ) + return false; + + // + // edge 1 + // + edge = points[2] - points[1]; + + // cross x-edge + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + if( ( ( normal.y * ( boxPt.y - points[0].y ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) + return false; + + // cross, y-edge + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) + return false; + + // cross z-edge + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.y * ( boxPt.y - points[0].y ) ) ) > 0.0f ) + return false; + + // + // edge 2 + // + edge = points[0] - points[2]; + + // cross x-edge + normal.x = 0.0f; + normal.y = -edge.z; + normal.z = edge.y; + if( ( ( normal.y * ( boxPt.y - points[0].y ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) + return false; + + // cross, y-edge + normal.x = edge.z; + normal.y = 0.0f; + normal.z = edge.y; + if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) + return false; + + // cross z-edge + normal.x = -edge.y; + normal.y = edge.x; + normal.z = 0.0f; + if( ( ( normal.x * ( boxPt.x - points[0].x ) ) + ( normal.y * ( boxPt.y - points[0].y ) ) ) > 0.0f ) + return false; + + // + // triangle plane test + // + // do the opposite because the ray has been negated + if( ( ( pTri->m_Normal.x * ( boxPt.x - points[0].x ) ) + + ( pTri->m_Normal.y * ( boxPt.y - points[0].y ) ) + + ( pTri->m_Normal.z * ( boxPt.z - points[0].z ) ) ) > 0.0f ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::CullTriList( CDispCollTreeTempData *pTemp, Vector &rayStart, Vector &rayEnd, Vector &extents, bool bIntersect ) +{ + // + // intersect AABB with all triangles in list + // + if( bIntersect ) + { + for( int i = 0; i < pTemp->m_TriListCount; i++ ) + { + if( IntersectAABBTriTest( rayStart, extents, pTemp->m_ppTriList[i] ) ) + return true; + } + + return false; + } + // + // sweep AABB against all triangles in list + // + else + { + bool bResult = false; + + for( int i = 0; i < pTemp->m_TriListCount; i++ ) + { + if( SweptAABBTriTest( rayStart, rayEnd, extents, pTemp->m_ppTriList[i] ) ) + { + pTemp->m_ppTriList[i]->SetIntersect( true ); + bResult = true; + } + } + + return bResult; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::IntersectAABBAABBTest( CDispCollTreeTempData *pTemp, const Vector &pos, const Vector &extents ) +{ + float dist; + + for( int dir = 0; dir < 3; dir++ ) + { + // negative direction + dist = -( pos[dir] - ( pTemp->m_AABBDistances[(dir>>1)] - extents[dir] ) ); + if( dist > COLLISION_EPSILON ) + return false; + + // positive direction + dist = pos[dir] - ( pTemp->m_AABBDistances[(dir>>1)+1] + extents[dir] ); + if( dist > COLLISION_EPSILON ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::SweptAABBAABBTest( CDispCollTreeTempData *pTemp, const Vector &rayStart, const Vector &rayEnd, const Vector &extents ) +{ + int dir; + float distStart, distEnd; + float fraction; + float deltas[3]; + float scalers[3]; + + // + // enter and exit fractions + // + float enterFraction = 0.0f; + float exitFraction = 0.0f; + + // + // de-normalize the paramter space so that we don't have to divide + // to find the fractional amount later (clamped for precision) + // + deltas[0] = rayEnd.x - rayStart.x; + deltas[1] = rayEnd.y - rayStart.y; + deltas[2] = rayEnd.z - rayStart.z; + if( ( deltas[0] < COLLISION_EPSILON ) && ( deltas[0] > -COLLISION_EPSILON ) ) { deltas[0] = 1.0f; } + if( ( deltas[1] < COLLISION_EPSILON ) && ( deltas[1] > -COLLISION_EPSILON ) ) { deltas[0] = 1.0f; } + if( ( deltas[2] < COLLISION_EPSILON ) && ( deltas[2] > -COLLISION_EPSILON ) ) { deltas[0] = 1.0f; } + scalers[0] = deltas[1] * deltas[2]; + scalers[1] = deltas[0] * deltas[2]; + scalers[2] = deltas[0] * deltas[1]; + + for( dir = 0; dir < 3; dir++ ) + { + // + // negative direction + // + distStart = -( rayStart[dir] - ( pTemp->m_AABBDistances[(dir>>1)] - extents[dir] ) ); + distEnd = -( rayEnd[dir] - ( pTemp->m_AABBDistances[(dir>>1)] - extents[dir] ) ); + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + fraction = distStart * scalers[dir]; + if( fraction > enterFraction ) + { + enterFraction = fraction; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + fraction = distStart * scalers[dir]; + if( fraction < exitFraction ) + { + exitFraction = fraction; + } + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + + // + // positive direction + // + distStart = rayStart[dir] - ( pTemp->m_AABBDistances[(dir>>1)+1] + extents[dir] ); + distEnd = rayEnd[dir] - ( pTemp->m_AABBDistances[(dir>>1)+1] + extents[dir] ); + + if( ( distStart > COLLISION_EPSILON ) && ( distEnd < -COLLISION_EPSILON ) ) + { + fraction = distStart * scalers[dir]; + if( fraction > enterFraction ) + { + enterFraction = fraction; + } + } + else if( ( distStart < -COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + fraction = distStart * scalers[dir]; + if( fraction < exitFraction ) + { + exitFraction = fraction; + } + } + else if( ( distStart > COLLISION_EPSILON ) && ( distEnd > COLLISION_EPSILON ) ) + { + return false; + } + } + + if( exitFraction < enterFraction ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDispCollTree::BuildTriList_r( CDispCollTreeTempData *pTemp, int nodeIndex, Vector &rayStart, Vector &rayEnd, Vector &extents, + bool bIntersect ) +{ + // + // get the current nodes bounds and create collision test planes + // (saved in the in class cache m_AABBNormals, m_AABBDistances) + // + Vector bounds[2]; + CDispCollNode *pNode = &m_pNodes[nodeIndex]; + pNode->GetBounds( bounds[0], bounds[1] ); + CreatePlanesFromBounds( pTemp, bounds[0], bounds[1] ); + + // + // interesect/sweep test + // + bool bResult; + if( bIntersect ) + { + bResult = IntersectAABBAABBTest( pTemp, rayStart, extents ); + } + else + { + bResult = SweptAABBAABBTest( pTemp, rayStart, rayEnd, extents ); + } + + if( bResult ) + { + // if leaf node -- add triangles to interstection test list + if( pNode->IsLeaf() ) + { + // Assert for now -- flush cache later!!!!! + Assert( pTemp->m_TriListCount >= 0 ); + Assert( pTemp->m_TriListCount < TRILIST_CACHE_SIZE ); + + pTemp->m_ppTriList[pTemp->m_TriListCount] = &pNode->m_Tris[0]; + pTemp->m_ppTriList[pTemp->m_TriListCount+1] = &pNode->m_Tris[1]; + pTemp->m_TriListCount += 2; + } + // continue recursion + else + { + BuildTriList_r( pTemp, GetChildNode( nodeIndex, 0 ), rayStart, rayEnd, extents, bIntersect ); + BuildTriList_r( pTemp, GetChildNode( nodeIndex, 1 ), rayStart, rayEnd, extents, bIntersect ); + BuildTriList_r( pTemp, GetChildNode( nodeIndex, 2 ), rayStart, rayEnd, extents, bIntersect ); + BuildTriList_r( pTemp, GetChildNode( nodeIndex, 3 ), rayStart, rayEnd, extents, bIntersect ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::AABBSweep( CDispCollData *pData ) +{ + // reset the triangle lists counts + CDispCollTreeTempData tmp; + tmp.m_TriListCount = 0; + + // sweep the AABB against the tree + BuildTriList_r( &tmp, 0, pData->m_StartPos, pData->m_EndPos, pData->m_Extents, false ); + + // find collision triangles + if( CullTriList( &tmp, pData->m_StartPos, pData->m_EndPos, pData->m_Extents, false ) ) + { + // find closest intersection + return AABBTriIntersect( &tmp, pData ); + } + + return false; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CDispCollTree::AABBIntersect( CDispCollData *pData ) +{ + // reset the triangle lists counts + CDispCollTreeTempData tmp; + tmp.m_TriListCount = 0; + + // sweep the AABB against the tree + BuildTriList_r( &tmp, 0, pData->m_StartPos, pData->m_StartPos, pData->m_Extents, true ); + + // find collision triangles + return CullTriList( &tmp, pData->m_StartPos, pData->m_StartPos, pData->m_Extents, true ); +} diff --git a/public/dispcoll.h b/public/dispcoll.h index 645a60c7..95ca4887 100644 --- a/public/dispcoll.h +++ b/public/dispcoll.h @@ -1,249 +1,249 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef DISPCOLL_H -#define DISPCOLL_H -#pragma once - -#include "Vector.h" - -class CCoreDispInfo; - -//============================================================================= -// -// Displacement Collision Triangle Data -// -class CDispCollTri -{ -public: - - void Init( void ); - inline void SetPoint( int index, Vector const& vert ); - inline void SetPointNormal( int index, Vector const& normal ); - void CalcPlane( void ); - - inline void SetIntersect( bool bIntersect ); - inline bool IsIntersect( void ); - - Vector m_Points[3]; // polygon points - Vector m_PointNormals[3]; // polygon point normals - Vector m_Normal; // plane normal - float m_Distance; // plane distance - short m_ProjAxes[2]; // projection axes (2 minor axes) - bool m_bIntersect; // intersected triangle??? -}; - -//============================================================================= -// -// Displacement Collision Node Data -// -class CDispCollNode -{ -public: - - CDispCollNode(); - inline bool IsLeaf( void ); - inline void SetBounds( Vector const &bMin, Vector const &bMax ); - inline void GetBounds( Vector &bMin, Vector &bMax ); - - Vector m_Bounds[2]; // node minimum and maximum - - bool m_bIsLeaf; // is the node a leaf? ( may have to make this an int for alignment!) - CDispCollTri m_Tris[2]; // two triangles contained in leaf node -}; - - -//============================================================================= -// -// Displacement Collision Data -// -class CDispCollData -{ -public: - - Vector m_StartPos; - Vector m_EndPos; - Vector m_Extents; - float m_Fraction; - int m_Contents; - Vector m_Normal; - float m_Distance; - bool m_bOcclude; -}; - - - -// HACKHACK: JAY: Moved this out of CDispCollTree to be thread safe in vrad -enum { TRILIST_CACHE_SIZE = 128 }; - -class CDispCollTreeTempData -{ -public: - // - // temps - // - int m_TriListCount; - CDispCollTri *m_ppTriList[TRILIST_CACHE_SIZE]; - - // collision tree node cache - float m_AABBDistances[6]; -}; - - -//============================================================================= -// -// Displacement Collision Tree -// -class CDispCollTree -{ -public: - - static const float COLLISION_EPSILON; - static const float ONE_MINUS_COLLISION_EPSILON; - - //========================================================================= - // - // Creation/Destruction - // - CDispCollTree(); - ~CDispCollTree(); - - virtual bool Create( CCoreDispInfo *pDisp ); - - //========================================================================= - // - // Collision Functions - // - bool RayTest( CDispCollData *pData ); - bool RayTestAllTris( CDispCollData *pData, int power ); - - bool AABBIntersect( CDispCollData *pData ); - bool AABBSweep( CDispCollData *pData ); - - //========================================================================= - // - // Attrib Functions - // - inline void SetPower( int power ); - inline int GetPower( void ); - - inline void SetCheckCount( int count ); - inline int GetCheckCount( void ); - - inline void GetBounds( Vector& boundMin, Vector& boundMax ); - -protected: - - int m_Power; - - int m_NodeCount; - CDispCollNode *m_pNodes; - - int m_CheckCount; - - // collision tree node cache - Vector m_AABBNormals[6]; - //========================================================================= - // - // Creation/Destruction - // - void InitAABBData( void ); - void InitLeaves( CCoreDispInfo *pDisp ); - void CreateNodes( CCoreDispInfo *pDisp ); - void CreateNodes_r( CCoreDispInfo *pDisp, int nodeIndex, int termLevel ); - void CalcBounds( CDispCollNode *pNode, int nodeIndex ); - - //========================================================================= - // - // Collision Functions - // - void CreatePlanesFromBounds( CDispCollTreeTempData *pTemp, Vector const &bbMin, Vector const &bbMax ); - -// void RayNodeTest_r( int nodeIndex, Vector &rayStart, Vector &rayEnd ); - void RayNodeTest_r( CDispCollTreeTempData *pTemp, int nodeIndex, Vector rayStart, Vector rayEnd ); - bool RayAABBTest( CDispCollTreeTempData *pTemp, Vector &rayStart, Vector &rayEnd ); - bool RayTriListTest( CDispCollTreeTempData *pTemp, CDispCollData *pData ); - bool RayTriTest( Vector const &rayStart, Vector const &rayDir, float const rayLength, CDispCollTri const *pTri, float *fraction ); - - void BuildTriList_r( CDispCollTreeTempData *pTemp, int nodeIndex, Vector &rayStart, Vector &rayEnd, Vector &extents, bool bIntersect ); - bool IntersectAABBAABBTest( CDispCollTreeTempData *pTemp, const Vector &pos, const Vector &extents ); - bool SweptAABBAABBTest( CDispCollTreeTempData *pTemp, const Vector &rayStart, const Vector &rayEnd, const Vector &extents ); - - bool CullTriList( CDispCollTreeTempData *pTemp, Vector &rayStart, Vector &rayEnd, Vector &extents, bool bIntersect ); - bool SweptAABBTriTest( Vector &rayStart, Vector &rayEnd, Vector &extents, CDispCollTri const *pTri ); - bool AABBTriIntersect( CDispCollTreeTempData *pTemp, CDispCollData *pData ); - bool IntersectAABBTriTest( Vector &rayStart, Vector &extents, CDispCollTri const *pTri ); - bool SweptAABBTriIntersect( Vector &rayStart, Vector &rayEnd, Vector &extents, - CDispCollTri const *pTri, Vector &plNormal, float *plDist, - float *fraction ); - - //========================================================================= - // - // Memory Functions - // - bool AllocNodes( int nodeCount ); - void FreeNodes( void ); - - //========================================================================= - // - // Utility Functions - // - inline int CalcNodeCount( int power ); - inline int GetParentNode( int nodeIndex ); - inline int GetChildNode( int nodeIndex, int direction ); - inline int GetNodeLevel( int nodeIndex ); - int GetNodeIndexFromComponents( int x, int y ); -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CDispCollTree::SetPower( int power ) -{ - m_Power = power; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CDispCollTree::GetPower( void ) -{ - return m_Power; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CDispCollTree::SetCheckCount( int count ) -{ - m_CheckCount = count; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline int CDispCollTree::GetCheckCount( void ) -{ - return m_CheckCount; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void CDispCollTree::GetBounds( Vector& boundMin, Vector& boundMax ) -{ - boundMin[0] = m_pNodes[0].m_Bounds[0].x; - boundMin[1] = m_pNodes[0].m_Bounds[0].y; - boundMin[2] = m_pNodes[0].m_Bounds[0].z; - - boundMax[0] = m_pNodes[0].m_Bounds[1].x; - boundMax[1] = m_pNodes[0].m_Bounds[1].y; - boundMax[2] = m_pNodes[0].m_Bounds[1].z; -} - - -#endif // DISPCOLL_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DISPCOLL_H +#define DISPCOLL_H +#pragma once + +#include "vector.h" + +class CCoreDispInfo; + +//============================================================================= +// +// Displacement Collision Triangle Data +// +class CDispCollTri +{ +public: + + void Init( void ); + inline void SetPoint( int index, Vector const& vert ); + inline void SetPointNormal( int index, Vector const& normal ); + void CalcPlane( void ); + + inline void SetIntersect( bool bIntersect ); + inline bool IsIntersect( void ); + + Vector m_Points[3]; // polygon points + Vector m_PointNormals[3]; // polygon point normals + Vector m_Normal; // plane normal + float m_Distance; // plane distance + short m_ProjAxes[2]; // projection axes (2 minor axes) + bool m_bIntersect; // intersected triangle??? +}; + +//============================================================================= +// +// Displacement Collision Node Data +// +class CDispCollNode +{ +public: + + CDispCollNode(); + inline bool IsLeaf( void ); + inline void SetBounds( Vector const &bMin, Vector const &bMax ); + inline void GetBounds( Vector &bMin, Vector &bMax ); + + Vector m_Bounds[2]; // node minimum and maximum + + bool m_bIsLeaf; // is the node a leaf? ( may have to make this an int for alignment!) + CDispCollTri m_Tris[2]; // two triangles contained in leaf node +}; + + +//============================================================================= +// +// Displacement Collision Data +// +class CDispCollData +{ +public: + + Vector m_StartPos; + Vector m_EndPos; + Vector m_Extents; + float m_Fraction; + int m_Contents; + Vector m_Normal; + float m_Distance; + bool m_bOcclude; +}; + + + +// HACKHACK: JAY: Moved this out of CDispCollTree to be thread safe in vrad +enum { TRILIST_CACHE_SIZE = 128 }; + +class CDispCollTreeTempData +{ +public: + // + // temps + // + int m_TriListCount; + CDispCollTri *m_ppTriList[TRILIST_CACHE_SIZE]; + + // collision tree node cache + float m_AABBDistances[6]; +}; + + +//============================================================================= +// +// Displacement Collision Tree +// +class CDispCollTree +{ +public: + + static const float COLLISION_EPSILON; + static const float ONE_MINUS_COLLISION_EPSILON; + + //========================================================================= + // + // Creation/Destruction + // + CDispCollTree(); + ~CDispCollTree(); + + virtual bool Create( CCoreDispInfo *pDisp ); + + //========================================================================= + // + // Collision Functions + // + bool RayTest( CDispCollData *pData ); + bool RayTestAllTris( CDispCollData *pData, int power ); + + bool AABBIntersect( CDispCollData *pData ); + bool AABBSweep( CDispCollData *pData ); + + //========================================================================= + // + // Attrib Functions + // + inline void SetPower( int power ); + inline int GetPower( void ); + + inline void SetCheckCount( int count ); + inline int GetCheckCount( void ); + + inline void GetBounds( Vector& boundMin, Vector& boundMax ); + +protected: + + int m_Power; + + int m_NodeCount; + CDispCollNode *m_pNodes; + + int m_CheckCount; + + // collision tree node cache + Vector m_AABBNormals[6]; + //========================================================================= + // + // Creation/Destruction + // + void InitAABBData( void ); + void InitLeaves( CCoreDispInfo *pDisp ); + void CreateNodes( CCoreDispInfo *pDisp ); + void CreateNodes_r( CCoreDispInfo *pDisp, int nodeIndex, int termLevel ); + void CalcBounds( CDispCollNode *pNode, int nodeIndex ); + + //========================================================================= + // + // Collision Functions + // + void CreatePlanesFromBounds( CDispCollTreeTempData *pTemp, Vector const &bbMin, Vector const &bbMax ); + +// void RayNodeTest_r( int nodeIndex, Vector &rayStart, Vector &rayEnd ); + void RayNodeTest_r( CDispCollTreeTempData *pTemp, int nodeIndex, Vector rayStart, Vector rayEnd ); + bool RayAABBTest( CDispCollTreeTempData *pTemp, Vector &rayStart, Vector &rayEnd ); + bool RayTriListTest( CDispCollTreeTempData *pTemp, CDispCollData *pData ); + bool RayTriTest( Vector const &rayStart, Vector const &rayDir, float const rayLength, CDispCollTri const *pTri, float *fraction ); + + void BuildTriList_r( CDispCollTreeTempData *pTemp, int nodeIndex, Vector &rayStart, Vector &rayEnd, Vector &extents, bool bIntersect ); + bool IntersectAABBAABBTest( CDispCollTreeTempData *pTemp, const Vector &pos, const Vector &extents ); + bool SweptAABBAABBTest( CDispCollTreeTempData *pTemp, const Vector &rayStart, const Vector &rayEnd, const Vector &extents ); + + bool CullTriList( CDispCollTreeTempData *pTemp, Vector &rayStart, Vector &rayEnd, Vector &extents, bool bIntersect ); + bool SweptAABBTriTest( Vector &rayStart, Vector &rayEnd, Vector &extents, CDispCollTri const *pTri ); + bool AABBTriIntersect( CDispCollTreeTempData *pTemp, CDispCollData *pData ); + bool IntersectAABBTriTest( Vector &rayStart, Vector &extents, CDispCollTri const *pTri ); + bool SweptAABBTriIntersect( Vector &rayStart, Vector &rayEnd, Vector &extents, + CDispCollTri const *pTri, Vector &plNormal, float *plDist, + float *fraction ); + + //========================================================================= + // + // Memory Functions + // + bool AllocNodes( int nodeCount ); + void FreeNodes( void ); + + //========================================================================= + // + // Utility Functions + // + inline int CalcNodeCount( int power ); + inline int GetParentNode( int nodeIndex ); + inline int GetChildNode( int nodeIndex, int direction ); + inline int GetNodeLevel( int nodeIndex ); + int GetNodeIndexFromComponents( int x, int y ); +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CDispCollTree::SetPower( int power ) +{ + m_Power = power; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CDispCollTree::GetPower( void ) +{ + return m_Power; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CDispCollTree::SetCheckCount( int count ) +{ + m_CheckCount = count; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CDispCollTree::GetCheckCount( void ) +{ + return m_CheckCount; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void CDispCollTree::GetBounds( Vector& boundMin, Vector& boundMax ) +{ + boundMin[0] = m_pNodes[0].m_Bounds[0].x; + boundMin[1] = m_pNodes[0].m_Bounds[0].y; + boundMin[2] = m_pNodes[0].m_Bounds[0].z; + + boundMax[0] = m_pNodes[0].m_Bounds[1].x; + boundMax[1] = m_pNodes[0].m_Bounds[1].y; + boundMax[2] = m_pNodes[0].m_Bounds[1].z; +} + + +#endif // DISPCOLL_H diff --git a/public/dlight.h b/public/dlight.h index 912442ea..4c1dae48 100644 --- a/public/dlight.h +++ b/public/dlight.h @@ -1,84 +1,84 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#if !defined ( DLIGHTH ) -#define DLIGHTH -#ifdef _WIN32 -#pragma once -#endif - -#include "vector.h" - -//----------------------------------------------------------------------------- -// Dynamic light structure -//----------------------------------------------------------------------------- - -enum -{ - DLIGHT_NO_WORLD_ILLUMINATION = 0x1, - DLIGHT_NO_MODEL_ILLUMINATION = 0x2, - - // NOTE: These two features are used to dynamically tweak the alpha on displacements - // which is a special effect for selecting which texture to use. If - // we ever change how alpha is stored for displacements, we'll have to kill this feature - DLIGHT_ADD_DISPLACEMENT_ALPHA = 0x4, - DLIGHT_SUBTRACT_DISPLACEMENT_ALPHA = 0x8, - DLIGHT_DISPLACEMENT_MASK = (DLIGHT_ADD_DISPLACEMENT_ALPHA | DLIGHT_SUBTRACT_DISPLACEMENT_ALPHA), -}; - -// This is the lighting value that is used to determine when something can be -// culle from lighting because it is close enough to black to be virtually black. -//#define MIN_LIGHTING_VALUE (1.0f/256.0f) - -// This is the broken value of MIN_LIGHTING_VALUE that we have to take into consideration -// to make sure that the lighting for dlights look the same as they did in HL2. -// We'll use the real MIN_LIGHTING_VALUE above to calculate larger radii for dynamic -// light sources. -//#define HL2_BROKEN_MIN_LIGHTING_VALUE (20.0f/256.0f) - -struct dlight_t -{ - int flags; - Vector origin; - float radius; - ColorRGBExp32 color; // Light color with exponent - float die; // stop lighting after this time - float decay; // drop this each second - float minlight; // don't add when contributing less - int key; - int style; // lightstyle - - // For spotlights. Use m_OuterAngle == 0 for point lights - Vector m_Direction; // center of the light cone - float m_InnerAngle; - float m_OuterAngle; - - // see comments above about HL2_BROKEN_MIN_LIGHTING_VALUE and MIN_LIGHTING_VALUE - // THIS SHOULD ONLY GET CALLED FROM THE ENGINE - float GetRadius() const - { -// return FastSqrt( radius * radius * ( HL2_BROKEN_MIN_LIGHTING_VALUE / MIN_LIGHTING_VALUE ) ); - return radius; - } - - // see comments above about HL2_BROKEN_MIN_LIGHTING_VALUE and MIN_LIGHTING_VALUE - // THIS SHOULD ONLY GET CALLED FROM THE ENGINE - float GetRadiusSquared() const - { -// return radius * radius * ( HL2_BROKEN_MIN_LIGHTING_VALUE / MIN_LIGHTING_VALUE ); - return radius * radius; - } - - // THIS SHOULD ONLY GET CALLED FROM THE ENGINE - float IsRadiusGreaterThanZero() const - { - // don't bother calculating the new radius if you just want to know if it is greater than zero. - return radius > 0.0f; - } -}; - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined ( DLIGHTH ) +#define DLIGHTH +#ifdef _WIN32 +#pragma once +#endif + +#include "vector.h" + +//----------------------------------------------------------------------------- +// Dynamic light structure +//----------------------------------------------------------------------------- + +enum +{ + DLIGHT_NO_WORLD_ILLUMINATION = 0x1, + DLIGHT_NO_MODEL_ILLUMINATION = 0x2, + + // NOTE: These two features are used to dynamically tweak the alpha on displacements + // which is a special effect for selecting which texture to use. If + // we ever change how alpha is stored for displacements, we'll have to kill this feature + DLIGHT_ADD_DISPLACEMENT_ALPHA = 0x4, + DLIGHT_SUBTRACT_DISPLACEMENT_ALPHA = 0x8, + DLIGHT_DISPLACEMENT_MASK = (DLIGHT_ADD_DISPLACEMENT_ALPHA | DLIGHT_SUBTRACT_DISPLACEMENT_ALPHA), +}; + +// This is the lighting value that is used to determine when something can be +// culle from lighting because it is close enough to black to be virtually black. +//#define MIN_LIGHTING_VALUE (1.0f/256.0f) + +// This is the broken value of MIN_LIGHTING_VALUE that we have to take into consideration +// to make sure that the lighting for dlights look the same as they did in HL2. +// We'll use the real MIN_LIGHTING_VALUE above to calculate larger radii for dynamic +// light sources. +//#define HL2_BROKEN_MIN_LIGHTING_VALUE (20.0f/256.0f) + +struct dlight_t +{ + int flags; + Vector origin; + float radius; + ColorRGBExp32 color; // Light color with exponent + float die; // stop lighting after this time + float decay; // drop this each second + float minlight; // don't add when contributing less + int key; + int style; // lightstyle + + // For spotlights. Use m_OuterAngle == 0 for point lights + Vector m_Direction; // center of the light cone + float m_InnerAngle; + float m_OuterAngle; + + // see comments above about HL2_BROKEN_MIN_LIGHTING_VALUE and MIN_LIGHTING_VALUE + // THIS SHOULD ONLY GET CALLED FROM THE ENGINE + float GetRadius() const + { +// return FastSqrt( radius * radius * ( HL2_BROKEN_MIN_LIGHTING_VALUE / MIN_LIGHTING_VALUE ) ); + return radius; + } + + // see comments above about HL2_BROKEN_MIN_LIGHTING_VALUE and MIN_LIGHTING_VALUE + // THIS SHOULD ONLY GET CALLED FROM THE ENGINE + float GetRadiusSquared() const + { +// return radius * radius * ( HL2_BROKEN_MIN_LIGHTING_VALUE / MIN_LIGHTING_VALUE ); + return radius * radius; + } + + // THIS SHOULD ONLY GET CALLED FROM THE ENGINE + float IsRadiusGreaterThanZero() const + { + // don't bother calculating the new radius if you just want to know if it is greater than zero. + return radius > 0.0f; + } +}; + +#endif diff --git a/public/dt_recv.cpp b/public/dt_recv.cpp index 61147148..247670c5 100644 --- a/public/dt_recv.cpp +++ b/public/dt_recv.cpp @@ -1,405 +1,406 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - - -#include "dt_recv.h" -#include "vector.h" -#include "vstdlib/strtools.h" -#include "dt_utlvector_common.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL) - -char *s_ClientElementNames[MAX_ARRAY_ELEMENTS] = -{ - "000", "001", "002", "003", "004", "005", "006", "007", "008", "009", - "010", "011", "012", "013", "014", "015", "016", "017", "018", "019", - "020", "021", "022", "023", "024", "025", "026", "027", "028", "029", - "030", "031", "032", "033", "034", "035", "036", "037", "038", "039", - "040", "041", "042", "043", "044", "045", "046", "047", "048", "049", - "050", "051", "052", "053", "054", "055", "056", "057", "058", "059", - "060", "061", "062", "063", "064", "065", "066", "067", "068", "069", - "070", "071", "072", "073", "074", "075", "076", "077", "078", "079", - "080", "081", "082", "083", "084", "085", "086", "087", "088", "089", - "090", "091", "092", "093", "094", "095", "096", "097", "098", "099", - "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", - "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", - "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", - "130", "131", "132", "133", "134", "135", "136", "137", "138", "139", - "140", "141", "142", "143", "144", "145", "146", "147", "148", "149", - "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", - "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", - "170", "171", "172", "173", "174", "175", "176", "177", "178", "179", - "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", - "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", - "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", - "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", - "220", "221", "222", "223", "224", "225", "226", "227", "228", "229", - "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", - "240", "241", "242", "243", "244", "245", "246", "247", "248", "249", - "250", "251", "252", "253", "254", "255" -}; - -CStandardRecvProxies::CStandardRecvProxies() -{ - m_Int32ToInt8 = RecvProxy_Int32ToInt8; - m_Int32ToInt16 = RecvProxy_Int32ToInt16; - m_Int32ToInt32 = RecvProxy_Int32ToInt32; - m_FloatToFloat = RecvProxy_FloatToFloat; - m_VectorToVector = RecvProxy_VectorToVector; -} - -CStandardRecvProxies g_StandardRecvProxies; - - -// ---------------------------------------------------------------------- // -// RecvProp. -// ---------------------------------------------------------------------- // -RecvProp::RecvProp() -{ - m_pExtraData = NULL; - m_pVarName = NULL; - m_Offset = 0; - m_RecvType = DPT_Int; - m_Flags = 0; - m_ProxyFn = NULL; - m_DataTableProxyFn = NULL; - m_pDataTable = NULL; - m_nElements = 1; - m_ElementStride = -1; - m_pArrayProp = NULL; - m_ArrayLengthProxy = NULL; - m_bInsideArray = false; -} - -// ---------------------------------------------------------------------- // -// RecvTable. -// ---------------------------------------------------------------------- // -RecvTable::RecvTable() -{ - Construct( NULL, 0, NULL ); -} - -RecvTable::RecvTable(RecvProp *pProps, int nProps, char *pNetTableName) -{ - Construct( pProps, nProps, pNetTableName ); -} - -RecvTable::~RecvTable() -{ -} - -void RecvTable::Construct( RecvProp *pProps, int nProps, char *pNetTableName ) -{ - m_pProps = pProps; - m_nProps = nProps; - m_pDecoder = NULL; - m_pNetTableName = pNetTableName; - m_bInitialized = false; - m_bInMainList = false; -} - - -// ---------------------------------------------------------------------- // -// Prop setup functions (for building tables). -// ---------------------------------------------------------------------- // - -RecvProp RecvPropFloat( - char *pVarName, - int offset, - int sizeofVar, - int flags, - RecvVarProxyFn varProxy - ) -{ - RecvProp ret; - - // Debug type checks. - if ( varProxy == RecvProxy_FloatToFloat ) - { - Assert( sizeofVar == 0 || sizeofVar == 4 ); - } - - ret.m_pVarName = pVarName; - ret.SetOffset( offset ); - ret.m_RecvType = DPT_Float; - ret.m_Flags = flags; - ret.SetProxyFn( varProxy ); - - return ret; -} - -RecvProp RecvPropVector( - char *pVarName, - int offset, - int sizeofVar, - int flags, - RecvVarProxyFn varProxy - ) -{ - RecvProp ret; - - // Debug type checks. - #ifdef _DEBUG - if(varProxy == RecvProxy_VectorToVector) - { - Assert(sizeofVar == sizeof(Vector)); - } - #endif - - ret.m_pVarName = pVarName; - ret.SetOffset( offset ); - ret.m_RecvType = DPT_Vector; - ret.m_Flags = flags; - ret.SetProxyFn( varProxy ); - - return ret; -} - -#if 0 // We can't ship this since it changes the size of DTVariant to be 20 bytes instead of 16 and that breaks MODs!!! - -RecvProp RecvPropQuaternion( - char *pVarName, - int offset, - int sizeofVar, // Handled by RECVINFO macro, but set to SIZEOF_IGNORE if you don't want to bother. - int flags, - RecvVarProxyFn varProxy - ) -{ - RecvProp ret; - - // Debug type checks. - #ifdef _DEBUG - if(varProxy == RecvProxy_QuaternionToQuaternion) - { - Assert(sizeofVar == sizeof(Quaternion)); - } - #endif - - ret.m_pVarName = pVarName; - ret.SetOffset( offset ); - ret.m_RecvType = DPT_Quaternion; - ret.m_Flags = flags; - ret.SetProxyFn( varProxy ); - - return ret; -} -#endif - -RecvProp RecvPropInt( - char *pVarName, - int offset, - int sizeofVar, - int flags, - RecvVarProxyFn varProxy - ) -{ - RecvProp ret; - - // If they didn't specify a proxy, then figure out what type we're writing to. - if(varProxy == NULL) - { - if(sizeofVar == 1) - { - varProxy = RecvProxy_Int32ToInt8; - } - else if(sizeofVar == 2) - { - varProxy = RecvProxy_Int32ToInt16; - } - else if(sizeofVar == 4) - { - varProxy = RecvProxy_Int32ToInt32; - } - else - { - Assert(!"RecvPropInt var has invalid size"); - varProxy = RecvProxy_Int32ToInt8; // safest one... - } - } - - ret.m_pVarName = pVarName; - ret.SetOffset( offset ); - ret.m_RecvType = DPT_Int; - ret.m_Flags = flags; - ret.SetProxyFn( varProxy ); - - return ret; -} - -RecvProp RecvPropString( - char *pVarName, - int offset, - int bufferSize, - int flags, - RecvVarProxyFn varProxy - ) -{ - RecvProp ret; - - ret.m_pVarName = pVarName; - ret.SetOffset( offset ); - ret.m_RecvType = DPT_String; - ret.m_Flags = flags; - ret.m_StringBufferSize = bufferSize; - ret.SetProxyFn( varProxy ); - - return ret; -} - -RecvProp RecvPropDataTable( - char *pVarName, - int offset, - int flags, - RecvTable *pTable, - DataTableRecvVarProxyFn varProxy - ) -{ - RecvProp ret; - - ret.m_pVarName = pVarName; - ret.SetOffset( offset ); - ret.m_RecvType = DPT_DataTable; - ret.m_Flags = flags; - ret.SetDataTableProxyFn( varProxy ); - ret.SetDataTable( pTable ); - - return ret; -} - -RecvProp RecvPropArray3( - char *pVarName, - int offset, - int sizeofVar, - int elements, - RecvProp pArrayProp, - DataTableRecvVarProxyFn varProxy - ) -{ - RecvProp ret; - - Assert( elements <= MAX_ARRAY_ELEMENTS ); - - ret.m_pVarName = pVarName; - ret.SetOffset( offset ); - ret.m_RecvType = DPT_DataTable; - ret.SetDataTableProxyFn( varProxy ); - - RecvProp *pProps = new RecvProp[elements]; // TODO free that again - - const char *pParentArrayPropName = AllocateStringHelper( "%s", pVarName ); - - for ( int i=0; i < elements; i++ ) - { - pProps[i] = pArrayProp; // copy basic property settings - pProps[i].SetOffset( i * sizeofVar ); // adjust offset - pProps[i].m_pVarName = s_ClientElementNames[i]; // give unique name - pProps[i].SetParentArrayPropName( pParentArrayPropName ); // For debugging... - } - - RecvTable *pTable = new RecvTable( pProps, elements, pVarName ); // TODO free that again - - ret.SetDataTable( pTable ); - - return ret; -} - -RecvProp InternalRecvPropArray( - const int elementCount, - const int elementStride, - char *pName, - ArrayLengthRecvProxyFn proxy - ) -{ - RecvProp ret; - - ret.InitArray( elementCount, elementStride ); - ret.m_pVarName = pName; - ret.SetArrayLengthProxy( proxy ); - - return ret; -} - - -// ---------------------------------------------------------------------- // -// Proxies. -// ---------------------------------------------------------------------- // - -void RecvProxy_FloatToFloat( const CRecvProxyData *pData, void *pStruct, void *pOut ) -{ - Assert( IsFinite( pData->m_Value.m_Float ) ); - *((float*)pOut) = pData->m_Value.m_Float; -} - -void RecvProxy_VectorToVector( const CRecvProxyData *pData, void *pStruct, void *pOut ) -{ - const float *v = pData->m_Value.m_Vector; - - Assert( IsFinite( v[0] ) && IsFinite( v[1] ) && IsFinite( v[2] ) ); - ((float*)pOut)[0] = v[0]; - ((float*)pOut)[1] = v[1]; - ((float*)pOut)[2] = v[2]; -} - -void RecvProxy_QuaternionToQuaternion( const CRecvProxyData *pData, void *pStruct, void *pOut ) -{ - const float *v = pData->m_Value.m_Vector; - - Assert( IsFinite( v[0] ) && IsFinite( v[1] ) && IsFinite( v[2] ) && IsFinite( v[3] ) ); - ((float*)pOut)[0] = v[0]; - ((float*)pOut)[1] = v[1]; - ((float*)pOut)[2] = v[2]; - ((float*)pOut)[3] = v[3]; -} - -void RecvProxy_Int32ToInt8( const CRecvProxyData *pData, void *pStruct, void *pOut ) -{ - *((unsigned char*)pOut) = *((unsigned char*)&pData->m_Value.m_Int); -} - -void RecvProxy_Int32ToInt16( const CRecvProxyData *pData, void *pStruct, void *pOut ) -{ - *((unsigned short*)pOut) = *((unsigned short*)&pData->m_Value.m_Int); -} - -void RecvProxy_Int32ToInt32( const CRecvProxyData *pData, void *pStruct, void *pOut ) -{ - *((unsigned long*)pOut) = *((unsigned long*)&pData->m_Value.m_Int); -} - -void RecvProxy_StringToString( const CRecvProxyData *pData, void *pStruct, void *pOut ) -{ - char *pStrOut = (char*)pOut; - if ( pData->m_pRecvProp->m_StringBufferSize <= 0 ) - { - return; - } - - for ( int i=0; i < pData->m_pRecvProp->m_StringBufferSize; i++ ) - { - pStrOut[i] = pData->m_Value.m_pString[i]; - if(pStrOut[i] == 0) - break; - } - - pStrOut[pData->m_pRecvProp->m_StringBufferSize-1] = 0; -} - -void DataTableRecvProxy_StaticDataTable( const RecvProp *pProp, void **pOut, void *pData, int objectID ) -{ - *pOut = pData; -} - -void DataTableRecvProxy_PointerDataTable( const RecvProp *pProp, void **pOut, void *pData, int objectID ) -{ - *pOut = *((void**)pData); -} - -#endif +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + + +#include "dt_recv.h" +#include "vector.h" +#include "vstdlib/strtools.h" +#include "dt_utlvector_common.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL) + +char *s_ClientElementNames[MAX_ARRAY_ELEMENTS] = +{ + "000", "001", "002", "003", "004", "005", "006", "007", "008", "009", + "010", "011", "012", "013", "014", "015", "016", "017", "018", "019", + "020", "021", "022", "023", "024", "025", "026", "027", "028", "029", + "030", "031", "032", "033", "034", "035", "036", "037", "038", "039", + "040", "041", "042", "043", "044", "045", "046", "047", "048", "049", + "050", "051", "052", "053", "054", "055", "056", "057", "058", "059", + "060", "061", "062", "063", "064", "065", "066", "067", "068", "069", + "070", "071", "072", "073", "074", "075", "076", "077", "078", "079", + "080", "081", "082", "083", "084", "085", "086", "087", "088", "089", + "090", "091", "092", "093", "094", "095", "096", "097", "098", "099", + "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", + "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", + "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", + "130", "131", "132", "133", "134", "135", "136", "137", "138", "139", + "140", "141", "142", "143", "144", "145", "146", "147", "148", "149", + "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", + "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", + "170", "171", "172", "173", "174", "175", "176", "177", "178", "179", + "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", + "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", + "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", + "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", + "220", "221", "222", "223", "224", "225", "226", "227", "228", "229", + "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", + "240", "241", "242", "243", "244", "245", "246", "247", "248", "249", + "250", "251", "252", "253", "254", "255" +}; + +CStandardRecvProxies::CStandardRecvProxies() +{ + m_Int32ToInt8 = RecvProxy_Int32ToInt8; + m_Int32ToInt16 = RecvProxy_Int32ToInt16; + m_Int32ToInt32 = RecvProxy_Int32ToInt32; + m_FloatToFloat = RecvProxy_FloatToFloat; + m_VectorToVector = RecvProxy_VectorToVector; +} + +CStandardRecvProxies g_StandardRecvProxies; + + +// ---------------------------------------------------------------------- // +// RecvProp. +// ---------------------------------------------------------------------- // +RecvProp::RecvProp() +{ + m_pExtraData = NULL; + m_pVarName = NULL; + m_Offset = 0; + m_RecvType = DPT_Int; + m_Flags = 0; + m_ProxyFn = NULL; + m_DataTableProxyFn = NULL; + m_pDataTable = NULL; + m_nElements = 1; + m_ElementStride = -1; + m_pArrayProp = NULL; + m_ArrayLengthProxy = NULL; + m_bInsideArray = false; +} + +// ---------------------------------------------------------------------- // +// RecvTable. +// ---------------------------------------------------------------------- // +RecvTable::RecvTable() +{ + Construct( NULL, 0, NULL ); +} + +RecvTable::RecvTable(RecvProp *pProps, int nProps, char *pNetTableName) +{ + Construct( pProps, nProps, pNetTableName ); +} + +RecvTable::~RecvTable() +{ +} + +void RecvTable::Construct( RecvProp *pProps, int nProps, char *pNetTableName ) +{ + m_pProps = pProps; + m_nProps = nProps; + m_pDecoder = NULL; + m_pNetTableName = pNetTableName; + m_bInitialized = false; + m_bInMainList = false; +} + + +// ---------------------------------------------------------------------- // +// Prop setup functions (for building tables). +// ---------------------------------------------------------------------- // + +RecvProp RecvPropFloat( + char *pVarName, + int offset, + int sizeofVar, + int flags, + RecvVarProxyFn varProxy + ) +{ + RecvProp ret; + + // Debug type checks. + if ( varProxy == RecvProxy_FloatToFloat ) + { + Assert( sizeofVar == 0 || sizeofVar == 4 ); + } + + ret.m_pVarName = pVarName; + ret.SetOffset( offset ); + ret.m_RecvType = DPT_Float; + ret.m_Flags = flags; + ret.SetProxyFn( varProxy ); + + return ret; +} + +RecvProp RecvPropVector( + char *pVarName, + int offset, + int sizeofVar, + int flags, + RecvVarProxyFn varProxy + ) +{ + RecvProp ret; + + // Debug type checks. + #ifdef _DEBUG + if(varProxy == RecvProxy_VectorToVector) + { + Assert(sizeofVar == sizeof(Vector)); + } + #endif + + ret.m_pVarName = pVarName; + ret.SetOffset( offset ); + ret.m_RecvType = DPT_Vector; + ret.m_Flags = flags; + ret.SetProxyFn( varProxy ); + + return ret; +} + +#if 0 // We can't ship this since it changes the size of DTVariant to be 20 bytes instead of 16 and that breaks MODs!!! + +RecvProp RecvPropQuaternion( + char *pVarName, + int offset, + int sizeofVar, // Handled by RECVINFO macro, but set to SIZEOF_IGNORE if you don't want to bother. + int flags, + RecvVarProxyFn varProxy + ) +{ + RecvProp ret; + + // Debug type checks. + #ifdef _DEBUG + if(varProxy == RecvProxy_QuaternionToQuaternion) + { + Assert(sizeofVar == sizeof(Quaternion)); + } + #endif + + ret.m_pVarName = pVarName; + ret.SetOffset( offset ); + ret.m_RecvType = DPT_Quaternion; + ret.m_Flags = flags; + ret.SetProxyFn( varProxy ); + + return ret; +} +#endif + +RecvProp RecvPropInt( + char *pVarName, + int offset, + int sizeofVar, + int flags, + RecvVarProxyFn varProxy + ) +{ + RecvProp ret; + + // If they didn't specify a proxy, then figure out what type we're writing to. + if(varProxy == NULL) + { + if(sizeofVar == 1) + { + varProxy = RecvProxy_Int32ToInt8; + } + else if(sizeofVar == 2) + { + varProxy = RecvProxy_Int32ToInt16; + } + else if(sizeofVar == 4) + { + varProxy = RecvProxy_Int32ToInt32; + } + else + { + Assert(!"RecvPropInt var has invalid size"); + varProxy = RecvProxy_Int32ToInt8; // safest one... + } + } + + ret.m_pVarName = pVarName; + ret.SetOffset( offset ); + ret.m_RecvType = DPT_Int; + ret.m_Flags = flags; + ret.SetProxyFn( varProxy ); + + return ret; +} + +RecvProp RecvPropString( + char *pVarName, + int offset, + int bufferSize, + int flags, + RecvVarProxyFn varProxy + ) +{ + RecvProp ret; + + ret.m_pVarName = pVarName; + ret.SetOffset( offset ); + ret.m_RecvType = DPT_String; + ret.m_Flags = flags; + ret.m_StringBufferSize = bufferSize; + ret.SetProxyFn( varProxy ); + + return ret; +} + +RecvProp RecvPropDataTable( + char *pVarName, + int offset, + int flags, + RecvTable *pTable, + DataTableRecvVarProxyFn varProxy + ) +{ + RecvProp ret; + + ret.m_pVarName = pVarName; + ret.SetOffset( offset ); + ret.m_RecvType = DPT_DataTable; + ret.m_Flags = flags; + ret.SetDataTableProxyFn( varProxy ); + ret.SetDataTable( pTable ); + + return ret; +} + +RecvProp RecvPropArray3( + char *pVarName, + int offset, + int sizeofVar, + int elements, + RecvProp pArrayProp, + DataTableRecvVarProxyFn varProxy + ) +{ + RecvProp ret; + + Assert( elements <= MAX_ARRAY_ELEMENTS ); + + ret.m_pVarName = pVarName; + ret.SetOffset( offset ); + ret.m_RecvType = DPT_DataTable; + ret.SetDataTableProxyFn( varProxy ); + + RecvProp *pProps = new RecvProp[elements]; // TODO free that again + + const char *pParentArrayPropName = AllocateStringHelper( "%s", pVarName ); + + for ( int i=0; i < elements; i++ ) + { + pProps[i] = pArrayProp; // copy basic property settings + pProps[i].SetOffset( i * sizeofVar ); // adjust offset + pProps[i].m_pVarName = s_ClientElementNames[i]; // give unique name + pProps[i].SetParentArrayPropName( pParentArrayPropName ); // For debugging... + } + + RecvTable *pTable = new RecvTable( pProps, elements, pVarName ); // TODO free that again + + ret.SetDataTable( pTable ); + + return ret; +} + +RecvProp InternalRecvPropArray( + const int elementCount, + const int elementStride, + char *pName, + ArrayLengthRecvProxyFn proxy + ) +{ + RecvProp ret; + + ret.InitArray( elementCount, elementStride ); + ret.m_pVarName = pName; + ret.SetArrayLengthProxy( proxy ); + + return ret; +} + + +// ---------------------------------------------------------------------- // +// Proxies. +// ---------------------------------------------------------------------- // + +void RecvProxy_FloatToFloat( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + Assert( IsFinite( pData->m_Value.m_Float ) ); + *((float*)pOut) = pData->m_Value.m_Float; +} + +void RecvProxy_VectorToVector( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + const float *v = pData->m_Value.m_Vector; + + Assert( IsFinite( v[0] ) && IsFinite( v[1] ) && IsFinite( v[2] ) ); + ((float*)pOut)[0] = v[0]; + ((float*)pOut)[1] = v[1]; + ((float*)pOut)[2] = v[2]; +} + +void RecvProxy_QuaternionToQuaternion( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + const float *v = pData->m_Value.m_Vector; + + Assert( IsFinite( v[0] ) && IsFinite( v[1] ) && IsFinite( v[2] ) && IsFinite( v[3] ) ); + ((float*)pOut)[0] = v[0]; + ((float*)pOut)[1] = v[1]; + ((float*)pOut)[2] = v[2]; + ((float*)pOut)[3] = v[3]; +} + +void RecvProxy_Int32ToInt8( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + *((unsigned char*)pOut) = *((unsigned char*)&pData->m_Value.m_Int); +} + +void RecvProxy_Int32ToInt16( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + const void *addr = &pData->m_Value.m_Int; + *((unsigned short*)pOut) = *((unsigned short*)addr); +} + +void RecvProxy_Int32ToInt32( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + *((unsigned long*)pOut) = *((unsigned long*)&pData->m_Value.m_Int); +} + +void RecvProxy_StringToString( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + char *pStrOut = (char*)pOut; + if ( pData->m_pRecvProp->m_StringBufferSize <= 0 ) + { + return; + } + + for ( int i=0; i < pData->m_pRecvProp->m_StringBufferSize; i++ ) + { + pStrOut[i] = pData->m_Value.m_pString[i]; + if(pStrOut[i] == 0) + break; + } + + pStrOut[pData->m_pRecvProp->m_StringBufferSize-1] = 0; +} + +void DataTableRecvProxy_StaticDataTable( const RecvProp *pProp, void **pOut, void *pData, int objectID ) +{ + *pOut = pData; +} + +void DataTableRecvProxy_PointerDataTable( const RecvProp *pProp, void **pOut, void *pData, int objectID ) +{ + *pOut = *((void**)pData); +} + +#endif diff --git a/public/dt_send.cpp b/public/dt_send.cpp index c2ff4fed..2c0ea383 100644 --- a/public/dt_send.cpp +++ b/public/dt_send.cpp @@ -225,7 +225,7 @@ float AssignRangeMultiplier( int nBits, double range ) // Squeeze it down smaller and smaller until it's going to produce an integer // in the valid range when given the highest value. float multipliers[] = { 0.9999, 0.99, 0.9, 0.8, 0.7 }; - int i; + size_t i; for ( i=0; i < ARRAYSIZE( multipliers ); i++ ) { float fHighLowMul = (float)( iHighValue / range ) * multipliers[i]; diff --git a/public/dt_shared.cpp b/public/dt_shared.cpp index 488a38a2..df39326f 100644 --- a/public/dt_shared.cpp +++ b/public/dt_shared.cpp @@ -1,113 +1,113 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "dt_shared.h" - -#if !defined (CLIENT_DLL) -#include "sendproxy.h" -#else -#include "recvproxy.h" -#endif - - -// ------------------------------------------------------------------------ // -// Just wrappers to make shared code look easier... -// ------------------------------------------------------------------------ // - -// Use these functions to setup your data tables. -DataTableProp PropFloat( - char *pVarName, // Variable name. - int offset, // Offset into container structure. - int sizeofVar, - int nBits, // Number of bits to use when encoding. - int flags, - float fLowValue, // For floating point, low and high values. - float fHighValue // High value. If HIGH_DEFAULT, it's (1< *g_STDict = 0; static CUtlDict *g_RTDict = 0; - +#endif char* AllocateStringHelper2( const char *pFormat, va_list marker ) { diff --git a/public/dt_utlvector_recv.h b/public/dt_utlvector_recv.h index 82fe93af..5252466b 100644 --- a/public/dt_utlvector_recv.h +++ b/public/dt_utlvector_recv.h @@ -1,60 +1,60 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Player for HL1. -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef DT_UTLVECTOR_RECV_H -#define DT_UTLVECTOR_RECV_H -#pragma once - - -#include "dt_recv.h" -#include "dt_utlvector_common.h" - - - -#define RECVINFO_UTLVECTOR( varName ) #varName, \ - offsetof(currentRecvDTClass, varName), \ - sizeof(((currentRecvDTClass*)0)->varName[0]), \ - GetResizeUtlVectorTemplate( ((currentRecvDTClass*)0)->varName ), \ - GetEnsureCapacityTemplate( ((currentRecvDTClass*)0)->varName ) - -// Use this macro to specify a utlvector where you specify the function -// that gets called to make sure the size of the utlvector is correct. -// The size function looks like this: void ResizeUtlVector( void *pVoid, int len ) -#define RECVINFO_UTLVECTOR_SIZEFN( varName, resizeFn ) #varName, \ - offsetof(currentRecvDTClass, varName), \ - sizeof(((currentRecvDTClass*)0)->varName[0]), \ - resizeFn, \ - GetEnsureCapacityTemplate( ((currentRecvDTClass*)0)->varName ) - - -#define RecvPropUtlVectorDataTable( varName, nMaxElements, dataTableName ) \ - RecvPropUtlVector( RECVINFO_UTLVECTOR( varName ), nMaxElements, RecvPropDataTable(NULL,0,0, &REFERENCE_RECV_TABLE( dataTableName ) ) ) - - -// -// Receive a property sent with SendPropUtlVector. -// -// Example usage: -// -// RecvPropUtlVectorDataTable( m_StructArray, 11, DT_StructArray ) -// -// RecvPropUtlVector( RECVINFO_UTLVECTOR( m_FloatArray ), 16, RecvPropFloat(NULL,0,0) ) -// -RecvProp RecvPropUtlVector( - char *pVarName, // Use RECVINFO_UTLVECTOR to generate these first 5 parameters. - int offset, - int sizeofVar, - ResizeUtlVectorFn fn, - EnsureCapacityFn ensureFn, - - int nMaxElements, // Max # of elements in the array. Keep this as low as possible. - RecvProp pArrayProp // The definition of the property you're receiving into. - // You can leave all of its parameters at 0 (name, offset, size, etc). - ); - - -#endif // DT_UTLVECTOR_RECV_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Player for HL1. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DT_UTLVECTOR_RECV_H +#define DT_UTLVECTOR_RECV_H +#pragma once + + +#include "dt_recv.h" +#include "dt_utlvector_common.h" + + + +#define RECVINFO_UTLVECTOR( varName ) #varName, \ + offsetof(currentRecvDTClass, varName), \ + sizeof(((currentRecvDTClass*)0)->varName[0]), \ + GetResizeUtlVectorTemplate( ((currentRecvDTClass*)0)->varName ), \ + GetEnsureCapacityTemplate( ((currentRecvDTClass*)0)->varName ) + +// Use this macro to specify a utlvector where you specify the function +// that gets called to make sure the size of the utlvector is correct. +// The size function looks like this: void ResizeUtlVector( void *pVoid, int len ) +#define RECVINFO_UTLVECTOR_SIZEFN( varName, resizeFn ) #varName, \ + offsetof(currentRecvDTClass, varName), \ + sizeof(((currentRecvDTClass*)0)->varName[0]), \ + resizeFn, \ + GetEnsureCapacityTemplate( ((currentRecvDTClass*)0)->varName ) + + +#define RecvPropUtlVectorDataTable( varName, nMaxElements, dataTableName ) \ + RecvPropUtlVector( RECVINFO_UTLVECTOR( varName ), nMaxElements, RecvPropDataTable(NULL,0,0, &REFERENCE_RECV_TABLE( dataTableName ) ) ) + + +// +// Receive a property sent with SendPropUtlVector. +// +// Example usage: +// +// RecvPropUtlVectorDataTable( m_StructArray, 11, DT_StructArray ) +// +// RecvPropUtlVector( RECVINFO_UTLVECTOR( m_FloatArray ), 16, RecvPropFloat(NULL,0,0) ) +// +RecvProp RecvPropUtlVector( + char *pVarName, // Use RECVINFO_UTLVECTOR to generate these first 5 parameters. + int offset, + int sizeofVar, + ResizeUtlVectorFn fn, + EnsureCapacityFn ensureFn, + + int nMaxElements, // Max # of elements in the array. Keep this as low as possible. + RecvProp pArrayProp // The definition of the property you're receiving into. + // You can leave all of its parameters at 0 (name, offset, size, etc). + ); + + +#endif // DT_UTLVECTOR_RECV_H diff --git a/public/eiface.h b/public/eiface.h index 3e3036d1..d9d7c679 100644 --- a/public/eiface.h +++ b/public/eiface.h @@ -343,11 +343,21 @@ public: // Returns true if the engine is an internal build. i.e. is using the internal bugreporter. virtual bool IsInternalBuild( void ) = 0; - virtual IChangeInfoAccessor *GetChangeAccessor( const edict_t *pEdict ) = 0; + virtual IChangeInfoAccessor *GetChangeAccessor( const edict_t *pEdict ) = 0; + + // Call this to find out the value of a cvar on the client. + // + // It is an asynchronous query, and it will call IServerGameDLL::OnQueryCvarValueFinished when + // the value comes in from the client. + // + // Store the return value if you want to match this specific query to the OnQueryCvarValueFinished call. + // Returns InvalidQueryCvarCookie if the entity is invalid. + virtual QueryCvarCookie_t StartQueryCvarValue( edict_t *pPlayerEntity, const char *pName ) = 0; }; #define INTERFACEVERSION_SERVERGAMEDLL_VERSION_4 "ServerGameDLL004" -#define INTERFACEVERSION_SERVERGAMEDLL "ServerGameDLL005" +#define INTERFACEVERSION_SERVERGAMEDLL_VERSION_5 "ServerGameDLL005" +#define INTERFACEVERSION_SERVERGAMEDLL "ServerGameDLL006" //----------------------------------------------------------------------------- // Purpose: These are the interfaces that the game .dll exposes to the engine @@ -437,6 +447,15 @@ public: #ifdef _XBOX virtual void GetTitleName( const char *pMapName, char* pTitleBuff, int titleBuffSize ) = 0; #endif + + // * This function is new with version 6 of the interface. + // + // This is called when a query from IVEngineServer::StartQueryCvarValue is finished. + // iCookie is the value returned by IVEngineServer::StartQueryCvarValue. + // Added with version 2 of the interface. + virtual void OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) + { + } }; //----------------------------------------------------------------------------- diff --git a/public/engine/iserverplugin.h b/public/engine/iserverplugin.h index 7f658c66..699b1dcb 100644 --- a/public/engine/iserverplugin.h +++ b/public/engine/iserverplugin.h @@ -28,8 +28,19 @@ typedef enum PLUGIN_STOP, // don't run the game dll function at all } PLUGIN_RESULT; +typedef enum +{ + eQueryCvarValueStatus_ValueIntact=0, // It got the value fine. + eQueryCvarValueStatus_CvarNotFound=1, + eQueryCvarValueStatus_NotACvar=2, // There's a ConCommand, but it's not a ConVar. + eQueryCvarValueStatus_CvarProtected=3 // The cvar was marked with FCVAR_SERVER_CAN_NOT_QUERY, so the server is not allowed to have its value. +} EQueryCvarValueStatus; -#define INTERFACEVERSION_ISERVERPLUGINCALLBACKS "ISERVERPLUGINCALLBACKS001" +typedef int QueryCvarCookie_t; + +#define InvalidQueryCvarCookie -1 +#define INTERFACEVERSION_ISERVERPLUGINCALLBACKS_VERSION_1 "ISERVERPLUGINCALLBACKS001" +#define INTERFACEVERSION_ISERVERPLUGINCALLBACKS "ISERVERPLUGINCALLBACKS002" //----------------------------------------------------------------------------- // Purpose: callbacks the engine exposes to the 3rd party plugins (ala MetaMod) //----------------------------------------------------------------------------- @@ -88,6 +99,13 @@ public: // A user has had their network id setup and validated virtual PLUGIN_RESULT NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) = 0; + + // This is called when a query from IServerPluginHelpers::StartQueryCvarValue is finished. + // iCookie is the value returned by IServerPluginHelpers::StartQueryCvarValue. + // Added with version 2 of the interface. + virtual void OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) + { + } }; #define INTERFACEVERSION_ISERVERPLUGINHELPERS "ISERVERPLUGINHELPERS001" @@ -121,6 +139,15 @@ public: // virtual void CreateMessage( edict_t *pEntity, DIALOG_TYPE type, KeyValues *data, IServerPluginCallbacks *plugin ) = 0; virtual void ClientCommand( edict_t *pEntity, const char *cmd ) = 0; + + // Call this to find out the value of a cvar on the client. + // + // It is an asynchronous query, and it will call IServerPluginCallbacks::OnQueryCvarValueFinished when + // the value comes in from the client. + // + // Store the return value if you want to match this specific query to the OnQueryCvarValueFinished call. + // Returns InvalidQueryCvarCookie if the entity is invalid. + virtual QueryCvarCookie_t StartQueryCvarValue( edict_t *pEntity, const char *pName ) = 0; }; #endif //ISERVERPLUGIN_H diff --git a/public/engine/ivmodelinfo.h b/public/engine/ivmodelinfo.h index aefa2e6f..b55403be 100644 --- a/public/engine/ivmodelinfo.h +++ b/public/engine/ivmodelinfo.h @@ -1,121 +1,121 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef IVMODELINFO_H -#define IVMODELINFO_H - -#ifdef _WIN32 -#pragma once -#endif - - -//----------------------------------------------------------------------------- -// Forward declarations -//----------------------------------------------------------------------------- -class IMaterial; -class KeyValues; -struct vcollide_t; -struct model_t; -class Vector; -class QAngle; -class CGameTrace; -typedef CGameTrace trace_t; -struct studiohdr_t; -struct virtualmodel_t; -typedef unsigned char byte; -struct virtualterrainparams_t; -class CPhysCollide; -//----------------------------------------------------------------------------- -// Model info interface -//----------------------------------------------------------------------------- - -// change this when the new version is incompatable with the old -#define VMODELINFO_CLIENT_INTERFACE_VERSION "VModelInfoClient003" -#define VMODELINFO_SERVER_INTERFACE_VERSION "VModelInfoServer001" - -class IVModelInfo -{ -public: - virtual ~IVModelInfo( void ) { } - - virtual const model_t *GetModel( int modelindex ) const = 0; - // Returns index of model by name - virtual int GetModelIndex( const char *name ) const = 0; - - // Returns name of model - virtual const char *GetModelName( const model_t *model ) const = 0; - virtual vcollide_t *GetVCollide( const model_t *model ) const = 0; - virtual vcollide_t *GetVCollide( int modelindex ) const = 0; - virtual void GetModelBounds( const model_t *model, Vector& mins, Vector& maxs ) const = 0; - virtual void GetModelRenderBounds( const model_t *model, Vector& mins, Vector& maxs ) const = 0; - virtual int GetModelFrameCount( const model_t *model ) const = 0; - virtual int GetModelType( const model_t *model ) const = 0; - virtual void *GetModelExtraData( const model_t *model ) = 0; - virtual bool ModelHasMaterialProxy( const model_t *model ) const = 0; - virtual bool IsTranslucent( model_t const* model ) const = 0; - virtual bool IsTranslucentTwoPass( const model_t *model ) const = 0; - virtual void RecomputeTranslucency( const model_t *model ) = 0; - virtual int GetModelMaterialCount( const model_t* model ) const = 0; - virtual void GetModelMaterials( const model_t *model, int count, IMaterial** ppMaterial ) = 0; - virtual bool IsModelVertexLit( const model_t *model ) const = 0; - virtual const char *GetModelKeyValueText( const model_t *model ) = 0; - virtual float GetModelRadius( const model_t *model ) = 0; - - virtual const studiohdr_t *FindModel( const studiohdr_t *pStudioHdr, void **cache, const char *modelname ) const = 0; - virtual const studiohdr_t *FindModel( void *cache ) const = 0; - virtual virtualmodel_t *GetVirtualModel( const studiohdr_t *pStudioHdr ) const = 0; - virtual byte *GetAnimBlock( const studiohdr_t *pStudioHdr, int iBlock ) const = 0; - - // Available on client only!!! - virtual void GetModelMaterialColorAndLighting( const model_t *model, Vector const& origin, - QAngle const& angles, trace_t* pTrace, - Vector& lighting, Vector& matColor ) = 0; - virtual void GetIlluminationPoint( const model_t *model, Vector const& origin, - QAngle const& angles, Vector* pLightingCenter ) = 0; - - virtual int GetModelContents( int modelIndex ) const = 0; - virtual studiohdr_t *GetStudiomodel( const model_t *mod ) = 0; - virtual int GetModelSpriteWidth( const model_t *model ) const = 0; - virtual int GetModelSpriteHeight( const model_t *model ) const = 0; - - // Sets/gets a map-specified fade range (client only) - virtual void SetLevelScreenFadeRange( float flMinSize, float flMaxSize ) = 0; - virtual void GetLevelScreenFadeRange( float *pMinArea, float *pMaxArea ) const = 0; - - // Sets/gets a map-specified per-view fade range (client only) - virtual void SetViewScreenFadeRange( float flMinSize, float flMaxSize ) = 0; - - // Computes fade alpha based on distance fade + screen fade (client only) - virtual unsigned char ComputeLevelScreenFade( const Vector &vecAbsOrigin, float flRadius, float flFadeScale ) const = 0; - virtual unsigned char ComputeViewScreenFade( const Vector &vecAbsOrigin, float flRadius, float flFadeScale ) const = 0; - - // both client and server - virtual int GetAutoplayList( const studiohdr_t *pStudioHdr, unsigned short **pAutoplayList ) const = 0; - - // Gets a virtual terrain collision model (creates if necessary) - // NOTE: This may return NULL if the terrain model cannot be virtualized - virtual CPhysCollide *GetCollideForVirtualTerrain( const virtualterrainparams_t ¶ms ) = 0; - - virtual bool IsUsingFBTexture( const model_t *model ) const = 0; - - virtual const model_t *FindOrLoadModel( const char *name ) const = 0; -}; - - -class IVModelInfoClient : public IVModelInfo -{ -public: -}; - - -struct virtualterrainparams_t -{ - // UNDONE: Add grouping here, specified in BSP file? (test grouping to see if this is necessary) - int index; -}; - -#endif // IVMODELINFO_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef IVMODELINFO_H +#define IVMODELINFO_H + +#ifdef _WIN32 +#pragma once +#endif + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IMaterial; +class KeyValues; +struct vcollide_t; +struct model_t; +class Vector; +class QAngle; +class CGameTrace; +typedef CGameTrace trace_t; +struct studiohdr_t; +struct virtualmodel_t; +typedef unsigned char byte; +struct virtualterrainparams_t; +class CPhysCollide; +//----------------------------------------------------------------------------- +// Model info interface +//----------------------------------------------------------------------------- + +// change this when the new version is incompatable with the old +#define VMODELINFO_CLIENT_INTERFACE_VERSION "VModelInfoClient003" +#define VMODELINFO_SERVER_INTERFACE_VERSION "VModelInfoServer001" + +class IVModelInfo +{ +public: + virtual ~IVModelInfo( void ) { } + + virtual const model_t *GetModel( int modelindex ) const = 0; + // Returns index of model by name + virtual int GetModelIndex( const char *name ) const = 0; + + // Returns name of model + virtual const char *GetModelName( const model_t *model ) const = 0; + virtual vcollide_t *GetVCollide( const model_t *model ) const = 0; + virtual vcollide_t *GetVCollide( int modelindex ) const = 0; + virtual void GetModelBounds( const model_t *model, Vector& mins, Vector& maxs ) const = 0; + virtual void GetModelRenderBounds( const model_t *model, Vector& mins, Vector& maxs ) const = 0; + virtual int GetModelFrameCount( const model_t *model ) const = 0; + virtual int GetModelType( const model_t *model ) const = 0; + virtual void *GetModelExtraData( const model_t *model ) = 0; + virtual bool ModelHasMaterialProxy( const model_t *model ) const = 0; + virtual bool IsTranslucent( model_t const* model ) const = 0; + virtual bool IsTranslucentTwoPass( const model_t *model ) const = 0; + virtual void RecomputeTranslucency( const model_t *model ) = 0; + virtual int GetModelMaterialCount( const model_t* model ) const = 0; + virtual void GetModelMaterials( const model_t *model, int count, IMaterial** ppMaterial ) = 0; + virtual bool IsModelVertexLit( const model_t *model ) const = 0; + virtual const char *GetModelKeyValueText( const model_t *model ) = 0; + virtual float GetModelRadius( const model_t *model ) = 0; + + virtual const studiohdr_t *FindModel( const studiohdr_t *pStudioHdr, void **cache, const char *modelname ) const = 0; + virtual const studiohdr_t *FindModel( void *cache ) const = 0; + virtual virtualmodel_t *GetVirtualModel( const studiohdr_t *pStudioHdr ) const = 0; + virtual byte *GetAnimBlock( const studiohdr_t *pStudioHdr, int iBlock ) const = 0; + + // Available on client only!!! + virtual void GetModelMaterialColorAndLighting( const model_t *model, Vector const& origin, + QAngle const& angles, trace_t* pTrace, + Vector& lighting, Vector& matColor ) = 0; + virtual void GetIlluminationPoint( const model_t *model, Vector const& origin, + QAngle const& angles, Vector* pLightingCenter ) = 0; + + virtual int GetModelContents( int modelIndex ) const = 0; + virtual studiohdr_t *GetStudiomodel( const model_t *mod ) = 0; + virtual int GetModelSpriteWidth( const model_t *model ) const = 0; + virtual int GetModelSpriteHeight( const model_t *model ) const = 0; + + // Sets/gets a map-specified fade range (client only) + virtual void SetLevelScreenFadeRange( float flMinSize, float flMaxSize ) = 0; + virtual void GetLevelScreenFadeRange( float *pMinArea, float *pMaxArea ) const = 0; + + // Sets/gets a map-specified per-view fade range (client only) + virtual void SetViewScreenFadeRange( float flMinSize, float flMaxSize ) = 0; + + // Computes fade alpha based on distance fade + screen fade (client only) + virtual unsigned char ComputeLevelScreenFade( const Vector &vecAbsOrigin, float flRadius, float flFadeScale ) const = 0; + virtual unsigned char ComputeViewScreenFade( const Vector &vecAbsOrigin, float flRadius, float flFadeScale ) const = 0; + + // both client and server + virtual int GetAutoplayList( const studiohdr_t *pStudioHdr, unsigned short **pAutoplayList ) const = 0; + + // Gets a virtual terrain collision model (creates if necessary) + // NOTE: This may return NULL if the terrain model cannot be virtualized + virtual CPhysCollide *GetCollideForVirtualTerrain( const virtualterrainparams_t ¶ms ) = 0; + + virtual bool IsUsingFBTexture( const model_t *model ) const = 0; + + virtual const model_t *FindOrLoadModel( const char *name ) const = 0; +}; + + +class IVModelInfoClient : public IVModelInfo +{ +public: +}; + + +struct virtualterrainparams_t +{ + // UNDONE: Add grouping here, specified in BSP file? (test grouping to see if this is necessary) + int index; +}; + +#endif // IVMODELINFO_H diff --git a/public/filesystem_init.cpp b/public/filesystem_init.cpp index 8ea24e50..b12a62d4 100644 --- a/public/filesystem_init.cpp +++ b/public/filesystem_init.cpp @@ -1,1143 +1,1145 @@ -//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. ======= -// -// Purpose: -// -//============================================================================= - -#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) - -#undef PROTECTED_THINGS_ENABLE -#undef PROTECT_FILEIO_FUNCTIONS -#undef fopen - -#ifdef _XBOX -#include "xbox/xbox_platform.h" -#include "xbox/xbox_win32stubs.h" -#include "xbox/xbox_core.h" -#endif -#if defined(_WIN32) && !defined(_XBOX) -#include -#include -#include // _chmod -#include -#elif _LINUX -#include -#define _putenv putenv -#define _chdir chdir -#define _access access -#endif -#include -#include -#include "vstdlib/strtools.h" -#include "filesystem_init.h" -#include "vstdlib/ICommandLine.h" -#include "KeyValues.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include - -#define GAMEINFO_FILENAME "gameinfo.txt" - -static char g_FileSystemError[256]; -static FSErrorMode_t g_FileSystemErrorMode = FS_ERRORMODE_VCONFIG; - -// This class lets you modify environment variables, and it restores the original value -// when it goes out of scope. -#ifndef _XBOX -class CTempEnvVar -{ -public: - CTempEnvVar( const char *pVarName ) - { - m_bRestoreOriginalValue = true; - m_pVarName = pVarName; - - const char *pValue = NULL; - -#ifdef _WIN32 - // Use GetEnvironmentVariable instead of getenv because getenv doesn't pick up changes - // to the process environment after the DLL was loaded. - char szBuf[ 4096 ]; - if ( GetEnvironmentVariable( m_pVarName, szBuf, sizeof( szBuf ) ) != 0) - { - pValue = szBuf; - } -#else - // LINUX BUG: see above - pValue = getenv( pVarName ); -#endif // _WIN32 - - if ( pValue ) - { - m_bExisted = true; - m_OriginalValue.SetSize( strlen( pValue ) + 1 ); - memcpy( m_OriginalValue.Base(), pValue, m_OriginalValue.Count() ); - } - else - { - m_bExisted = false; - } - } - - ~CTempEnvVar() - { - if ( m_bRestoreOriginalValue ) - { - // Restore the original value. - if ( m_bExisted ) - { - SetValue( "%s", m_OriginalValue.Base() ); - } - else - { - ClearValue(); - } - } - } - - void SetRestoreOriginalValue( bool bRestore ) - { - m_bRestoreOriginalValue = bRestore; - } - - int GetValue(char *pszBuf, int nBufSize ) - { - if ( !pszBuf || ( nBufSize <= 0 ) ) - return 0; - -#ifdef _WIN32 - // Use GetEnvironmentVariable instead of getenv because getenv doesn't pick up changes - // to the process environment after the DLL was loaded. - return GetEnvironmentVariable( m_pVarName, pszBuf, nBufSize ); -#else - // LINUX BUG: see above - const char *pszOut = getenv( m_pVarName ); - if ( !pszOut ) - { - *pszBuf = '\0'; - return 0; - } - - Q_strncpy( pszBuf, pszOut, nBufSize ); - return Q_strlen( pszBuf ); -#endif // _WIN32 - } - - void SetValue( const char *pValue, ... ) - { - char valueString[4096]; - va_list marker; - va_start( marker, pValue ); - Q_vsnprintf( valueString, sizeof( valueString ), pValue, marker ); - va_end( marker ); - - char str[4096]; - Q_snprintf( str, sizeof( str ), "%s=%s", m_pVarName, valueString ); - _putenv( str ); - } - - void ClearValue() - { - char str[512]; - Q_snprintf( str, sizeof( str ), "%s=", m_pVarName ); - _putenv( str ); - } - -private: - bool m_bRestoreOriginalValue; - const char *m_pVarName; - bool m_bExisted; - CUtlVector m_OriginalValue; -}; - - -class CSteamEnvVars -{ -public: - CSteamEnvVars() : - m_SteamAppId( "SteamAppId" ), - m_SteamUserPassphrase( "SteamUserPassphrase" ), - m_SteamAppUser( "SteamAppUser" ), - m_Path( "path" ) - { - } - - void SetRestoreOriginalValue_ALL( bool bRestore ) - { - m_SteamAppId.SetRestoreOriginalValue( bRestore ); - m_SteamUserPassphrase.SetRestoreOriginalValue( bRestore ); - m_SteamAppUser.SetRestoreOriginalValue( bRestore ); - m_Path.SetRestoreOriginalValue( bRestore ); - } - - CTempEnvVar m_SteamAppId; - CTempEnvVar m_SteamUserPassphrase; - CTempEnvVar m_SteamAppUser; - CTempEnvVar m_Path; -}; -#endif - - - -// ---------------------------------------------------------------------------------------------------- // -// Helpers. -// ---------------------------------------------------------------------------------------------------- // -#ifndef _XBOX -void Q_getwd( char *out, int outSize ) -{ -#if defined( _WIN32 ) || defined( WIN32 ) - _getcwd( out, outSize ); - Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); -#else - getcwd(out, outSize); - strcat(out, "/"); -#endif - Q_FixSlashes( out ); -} -#endif - -// ---------------------------------------------------------------------------------------------------- // -// Module interface. -// ---------------------------------------------------------------------------------------------------- // - -CFSSearchPathsInit::CFSSearchPathsInit() -{ - m_pDirectoryName = NULL; - m_pLanguage = NULL; - m_ModPath[0] = 0; -} - - -CFSSteamSetupInfo::CFSSteamSetupInfo() -{ - m_pDirectoryName = NULL; - m_bOnlyUseDirectoryName = false; - m_bSteam = false; - m_bToolsMode = true; - m_bNoGameInfo = false; -} - - -CFSLoadModuleInfo::CFSLoadModuleInfo() -{ - m_pFileSystemDLLName = NULL; - m_pFileSystem = NULL; - m_pModule = NULL; -} - - -CFSMountContentInfo::CFSMountContentInfo() -{ - m_bToolsMode = true; - m_pDirectoryName = NULL; - m_pFileSystem = NULL; -} - - -const char *FileSystem_GetLastErrorString() -{ - return g_FileSystemError; -} - - -void AddLanguageGameDir( IFileSystem *pFileSystem, const char *pLocation, const char *pLanguage ) -{ -#if !defined(SWDS) && !defined(_XBOX) - char temp[MAX_PATH]; - Q_snprintf( temp, sizeof(temp), "%s_%s", pLocation, pLanguage ); - pFileSystem->AddSearchPath( temp, "GAME", PATH_ADD_TO_TAIL ); - - if ( !pFileSystem->IsSteam() ) - { - // also look in "..\localization\" if not running Steam - char baseDir[MAX_PATH]; - char *tempPtr = NULL, *gameDir = NULL; - - Q_strncpy( baseDir, pLocation, sizeof(baseDir) ); - tempPtr = Q_strstr( baseDir, "\\game\\" ); - - if ( tempPtr ) - { - gameDir = tempPtr + Q_strlen( "\\game\\" ); - *tempPtr = 0; - Q_snprintf( temp, sizeof(temp), "%s%clocalization%c%s_%s", baseDir, CORRECT_PATH_SEPARATOR, CORRECT_PATH_SEPARATOR, gameDir, pLanguage ); - pFileSystem->AddSearchPath( temp, "GAME", PATH_ADD_TO_TAIL ); - } - } -#endif -} - - -void AddGameBinDir( IFileSystem *pFileSystem, const char *pLocation ) -{ -#ifndef _XBOX - char temp[MAX_PATH]; - Q_snprintf( temp, sizeof(temp), "%s%cbin", pLocation, CORRECT_PATH_SEPARATOR ); - pFileSystem->AddSearchPath( temp, "GAMEBIN", PATH_ADD_TO_TAIL ); -#endif -} - -#ifndef _XBOX -KeyValues* ReadKeyValuesFile( const char *pFilename ) -{ - // Read in the gameinfo.txt file and null-terminate it. - FILE *fp = fopen( pFilename, "rb" ); - if ( !fp ) - return NULL; - CUtlVector buf; - fseek( fp, 0, SEEK_END ); - buf.SetSize( ftell( fp ) + 1 ); - fseek( fp, 0, SEEK_SET ); - fread( buf.Base(), 1, buf.Count()-1, fp ); - fclose( fp ); - buf[buf.Count()-1] = 0; - - KeyValues *kv = new KeyValues( "" ); - if ( !kv->LoadFromBuffer( pFilename, buf.Base() ) ) - { - kv->deleteThis(); - return NULL; - } - - return kv; -} -#endif - -static int Sys_GetExecutableName( char *out, int len ) -{ -#ifdef _WIN32 - if ( !::GetModuleFileName( ( HINSTANCE )GetModuleHandle( NULL ), out, len ) ) - { - return 0; - } -#else - if ( CommandLine()->GetParm(0) ) - { - Q_MakeAbsolutePath( out, len, CommandLine()->GetParm(0) ); - } - else - { - return 0; - } -#endif - return 1; -} - -bool FileSystem_GetExecutableDir( char *exedir, int exeDirLen ) -{ - exedir[ 0 ] = 0; - if ( !Sys_GetExecutableName( exedir, exeDirLen ) ) - return false; - - Q_StripFilename( exedir ); - Q_FixSlashes( exedir ); - - // Return the bin directory as the executable dir if it's not in there - // because that's really where we're running from... - char ext[MAX_PATH]; - Q_StrRight( exedir, 4, ext, sizeof( ext ) ); - if ( ext[0] != CORRECT_PATH_SEPARATOR || Q_stricmp( ext+1, "bin" ) != 0 ) - { - Q_strncat( exedir, "\\bin", exeDirLen, COPY_ALL_CHARACTERS ); - Q_FixSlashes( exedir ); - } - return true; -} - -#ifndef _XBOX -static bool FileSystem_GetBaseDir( char *baseDir, int baseDirLen ) -{ - if ( FileSystem_GetExecutableDir( baseDir, baseDirLen ) ) - { - Q_StripFilename( baseDir ); - return true; - } - else - { - return false; - } -} -#endif - -#ifndef _XBOX -void LaunchVConfig() -{ -#ifdef _WIN32 - char vconfigExe[MAX_PATH]; - FileSystem_GetExecutableDir( vconfigExe, sizeof( vconfigExe ) ); - Q_AppendSlash( vconfigExe, sizeof( vconfigExe ) ); - Q_strncat( vconfigExe, "vconfig.exe", sizeof( vconfigExe ), COPY_ALL_CHARACTERS ); - - char *argv[] = - { - vconfigExe, - "-allowdebug", - NULL - }; - - _spawnv( _P_NOWAIT, vconfigExe, argv ); -#endif -} -#endif - -#ifndef _XBOX -const char* GetVProjectCmdLineValue() -{ - return CommandLine()->ParmValue( "-vproject", CommandLine()->ParmValue( "-game" ) ); -} -#endif - -FSReturnCode_t SetupFileSystemError( bool bRunVConfig, FSReturnCode_t retVal, const char *pMsg, ... ) -{ - va_list marker; - va_start( marker, pMsg ); - Q_vsnprintf( g_FileSystemError, sizeof( g_FileSystemError ), pMsg, marker ); - va_end( marker ); - - Warning( "%s", g_FileSystemError ); - -#ifndef _XBOX - // Run vconfig? - // Don't do it if they specifically asked for it not to, or if they manually specified a vconfig with -game or -vproject. - if ( bRunVConfig && g_FileSystemErrorMode == FS_ERRORMODE_VCONFIG && !CommandLine()->FindParm( CMDLINEOPTION_NOVCONFIG ) && !GetVProjectCmdLineValue() ) - LaunchVConfig(); -#endif - - if ( g_FileSystemErrorMode == FS_ERRORMODE_AUTO || g_FileSystemErrorMode == FS_ERRORMODE_VCONFIG ) - Error( "%s\n", g_FileSystemError ); - - return retVal; -} - - -#ifndef _XBOX -FSReturnCode_t LoadGameInfoFile( - const char *pDirectoryName, - KeyValues *&pMainFile, - KeyValues *&pFileSystemInfo, - KeyValues *&pSearchPaths ) -{ - // If GameInfo.txt exists under pBaseDir, then this is their game directory. - // All the filesystem mappings will be in this file. - char gameinfoFilename[MAX_PATH]; - Q_strncpy( gameinfoFilename, pDirectoryName, sizeof( gameinfoFilename ) ); - Q_AppendSlash( gameinfoFilename, sizeof( gameinfoFilename ) ); - Q_strncat( gameinfoFilename, GAMEINFO_FILENAME, sizeof( gameinfoFilename ), COPY_ALL_CHARACTERS ); - Q_FixSlashes( gameinfoFilename ); - pMainFile = ReadKeyValuesFile( gameinfoFilename ); - if ( !pMainFile ) - return SetupFileSystemError( true, FS_MISSING_GAMEINFO_FILE, "%s is missing.", gameinfoFilename ); - - pFileSystemInfo = pMainFile->FindKey( "FileSystem" ); - if ( !pFileSystemInfo ) - { - pMainFile->deleteThis(); - return SetupFileSystemError( true, FS_INVALID_GAMEINFO_FILE, "%s is not a valid format.", gameinfoFilename ); - } - - // Now read in all the search paths. - pSearchPaths = pFileSystemInfo->FindKey( "SearchPaths" ); - if ( !pSearchPaths ) - { - pMainFile->deleteThis(); - return SetupFileSystemError( true, FS_INVALID_GAMEINFO_FILE, "%s is not a valid format.", gameinfoFilename ); - } - return FS_OK; -} -#endif - -// checks the registry for the low violence setting -// Check "HKEY_CURRENT_USER\Software\Valve\Source\Settings" and "User Token 2" or "User Token 3" -#ifndef _XBOX -bool IsLowViolenceBuild( void ) -{ -#if defined(_WIN32) - HKEY hKey; - char szValue[64]; - unsigned long len = sizeof(szValue) - 1; - bool retVal = false; - - if ( RegOpenKeyEx( HKEY_CURRENT_USER, "Software\\Valve\\Source\\Settings", NULL, KEY_READ, &hKey) == ERROR_SUCCESS ) - { - // User Token 2 - if ( RegQueryValueEx( hKey, "User Token 2", NULL, NULL, (unsigned char*)szValue, &len ) == ERROR_SUCCESS ) - { - if ( Q_strlen( szValue ) > 0 ) - { - retVal = true; - } - } - - if ( !retVal ) - { - // reset "len" for the next check - len = sizeof(szValue) - 1; - - // User Token 3 - if ( RegQueryValueEx( hKey, "User Token 3", NULL, NULL, (unsigned char*)szValue, &len ) == ERROR_SUCCESS ) - { - if ( Q_strlen( szValue ) > 0 ) - { - retVal = true; - } - } - } - - RegCloseKey(hKey); - } - - return retVal; -#elif _LINUX - return false; -#elif -#error "Fix me" -#endif -} -#endif - -FSReturnCode_t FileSystem_LoadSearchPaths( CFSSearchPathsInit &initInfo ) -{ -#ifndef _XBOX - bool bLowViolence = IsLowViolenceBuild(); - - if ( !initInfo.m_pFileSystem || !initInfo.m_pDirectoryName ) - return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_LoadSearchPaths: Invalid parameters specified." ); - - KeyValues *pMainFile, *pFileSystemInfo, *pSearchPaths; - FSReturnCode_t retVal = LoadGameInfoFile( initInfo.m_pDirectoryName, pMainFile, pFileSystemInfo, pSearchPaths ); - if ( retVal != FS_OK ) - return retVal; - - // All paths except those marked with |gameinfo_path| are relative to the base dir. - char baseDir[MAX_PATH]; - if ( !FileSystem_GetBaseDir( baseDir, sizeof( baseDir ) ) ) - return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetBaseDir failed." ); - - initInfo.m_ModPath[0] = 0; - - bool bFirstGamePath = true; - for ( KeyValues *pCur=pSearchPaths->GetFirstValue(); pCur; pCur=pCur->GetNextValue() ) - { - const char *pPathID = pCur->GetName(); - - char fullLocationPath[MAX_PATH]; - const char *pLocation = pCur->GetString(); - if ( Q_stristr( pLocation, "|gameinfo_path|" ) == pLocation ) - Q_MakeAbsolutePath( fullLocationPath, sizeof( fullLocationPath ), pLocation + strlen( "|gameinfo_path|" ), initInfo.m_pDirectoryName ); - else - Q_MakeAbsolutePath( fullLocationPath, sizeof( fullLocationPath ), pLocation, baseDir ); - - // Add language, mod, and gamebin search paths automatically. - if ( Q_stricmp( pPathID, "game" ) == 0 ) - { - // add the low violence path - if ( bLowViolence ) - { - char szPath[MAX_PATH]; - Q_snprintf( szPath, sizeof(szPath), "%s_lv", fullLocationPath ); - initInfo.m_pFileSystem->AddSearchPath( szPath, pPathID, PATH_ADD_TO_TAIL ); - } - - // add the language path - if ( initInfo.m_pLanguage ) - { - AddLanguageGameDir( initInfo.m_pFileSystem, fullLocationPath, initInfo.m_pLanguage ); - } - -#ifndef _XBOX - if ( CommandLine()->FindParm( "-tempcontent" ) != 0 ) - { - char szPath[MAX_PATH]; - Q_snprintf( szPath, sizeof(szPath), "%s_tempcontent", fullLocationPath ); - initInfo.m_pFileSystem->AddSearchPath( szPath, pPathID, PATH_ADD_TO_TAIL ); - } -#endif - - // mark the first "game" dir as the "MOD" dir - if ( bFirstGamePath ) - { - bFirstGamePath = false; - initInfo.m_pFileSystem->AddSearchPath( fullLocationPath, "MOD", PATH_ADD_TO_TAIL ); - Q_strncpy( initInfo.m_ModPath, fullLocationPath, sizeof( initInfo.m_ModPath ) ); - } - - // add the game bin - AddGameBinDir( initInfo.m_pFileSystem, fullLocationPath ); - } - - initInfo.m_pFileSystem->AddSearchPath( fullLocationPath, pPathID, PATH_ADD_TO_TAIL ); - } - - pMainFile->deleteThis(); - - // Also, mark specific path IDs as "by request only". That way, we won't waste time searching in them - // when people forget to specify a search path. - initInfo.m_pFileSystem->MarkPathIDByRequestOnly( "executable_path", true ); - initInfo.m_pFileSystem->MarkPathIDByRequestOnly( "gamebin", true ); - initInfo.m_pFileSystem->MarkPathIDByRequestOnly( "mod", true ); - if ( initInfo.m_ModPath[0] != 0 ) - { - // Add the write path last. - initInfo.m_pFileSystem->AddSearchPath( initInfo.m_ModPath, "DEFAULT_WRITE_PATH", PATH_ADD_TO_TAIL ); - } - -#ifdef _DEBUG - initInfo.m_pFileSystem->PrintSearchPaths(); -#endif - -#endif // _XBOX - return FS_OK; -} - -#ifndef _XBOX -bool DoesFileExistIn( const char *pDirectoryName, const char *pFilename ) -{ - char filename[MAX_PATH]; - Q_strncpy( filename, pDirectoryName, sizeof( filename ) ); - Q_AppendSlash( filename, sizeof( filename ) ); - Q_strncat( filename, pFilename, sizeof( filename ), COPY_ALL_CHARACTERS ); - Q_FixSlashes( filename ); - return ( _access( filename, 0 ) == 0 ); -} -#endif - -#ifndef _XBOX -FSReturnCode_t LocateGameInfoFile( const CFSSteamSetupInfo &fsInfo, char *pOutDir, int outDirLen ) -{ - // Engine and Hammer don't want to search around for it. - if ( fsInfo.m_bOnlyUseDirectoryName ) - { - if ( !fsInfo.m_pDirectoryName ) - return SetupFileSystemError( false, FS_MISSING_GAMEINFO_FILE, "bOnlyUseDirectoryName=1 and pDirectoryName=NULL." ); - - if ( !DoesFileExistIn( fsInfo.m_pDirectoryName, GAMEINFO_FILENAME ) ) - return SetupFileSystemError( true, FS_MISSING_GAMEINFO_FILE, "%s doesn't exist in %s.", GAMEINFO_FILENAME, fsInfo.m_pDirectoryName ); - - Q_strncpy( pOutDir, fsInfo.m_pDirectoryName, outDirLen ); - return FS_OK; - } - - // First, check for overrides on the command line or environment variables. - const char *pProject = GetVProjectCmdLineValue(); - if ( !pProject ) - { - // Check their registry. - pProject = getenv( GAMEDIR_TOKEN ); - } - - if ( pProject ) - { - if ( DoesFileExistIn( pProject, GAMEINFO_FILENAME ) ) - { - Q_MakeAbsolutePath( pOutDir, outDirLen, pProject ); - return FS_OK; - } - else if ( fsInfo.m_bNoGameInfo ) - { - // fsInfo.m_bNoGameInfo is set by the Steam dedicated server, before it knows which mod to use. - // Steam dedicated server doesn't need a gameinfo.txt, because we'll ask which mod to use, even if - // -game is supplied on the command line. - Q_strncpy( pOutDir, "", outDirLen ); - return FS_OK; - } - else - { - // They either specified vproject on the command line or it's in their registry. Either way, - // we don't want to continue if they've specified it but it's not valid. - goto ShowError; - } - } - - if ( fsInfo.m_bNoGameInfo ) - { - Q_strncpy( pOutDir, "", outDirLen ); - return FS_OK; - } - - Warning( "Warning: falling back to auto detection of vproject directory.\n" ); - - // Now look for it in the directory they passed in. - if ( fsInfo.m_pDirectoryName ) - Q_MakeAbsolutePath( pOutDir, outDirLen, fsInfo.m_pDirectoryName ); - else - Q_MakeAbsolutePath( pOutDir, outDirLen, "." ); - - do - { - if ( DoesFileExistIn( pOutDir, GAMEINFO_FILENAME ) ) - return FS_OK; - } while ( Q_StripLastDir( pOutDir, outDirLen ) ); - - // use the cwd and hunt down the tree until we find something - Q_getwd( pOutDir, outDirLen ); - do - { - if ( DoesFileExistIn( pOutDir, GAMEINFO_FILENAME ) ) - return FS_OK; - } while ( Q_StripLastDir( pOutDir, outDirLen ) ); - -ShowError: - return SetupFileSystemError( true, FS_MISSING_GAMEINFO_FILE, - "Unable to find %s. Solutions:\n\n" - "1. Read http://www.valve-erc.com/srcsdk/faq.html#NoGameDir\n" - "2. Run vconfig to specify which game you're working on.\n" - "3. Add -game on the command line where is the directory that %s is in.\n", - GAMEINFO_FILENAME, GAMEINFO_FILENAME ); -} -#endif - -#ifndef _XBOX -bool DoesPathExistAlready( const char *pPathEnvVar, const char *pTestPath ) -{ - // Fix the slashes in the input arguments. - char correctedPathEnvVar[8192], correctedTestPath[MAX_PATH]; - Q_strncpy( correctedPathEnvVar, pPathEnvVar, sizeof( correctedPathEnvVar ) ); - Q_FixSlashes( correctedPathEnvVar ); - pPathEnvVar = correctedPathEnvVar; - - Q_strncpy( correctedTestPath, pTestPath, sizeof( correctedTestPath ) ); - Q_FixSlashes( correctedTestPath ); - if ( strlen( correctedTestPath ) > 0 && PATHSEPARATOR( correctedTestPath[strlen(correctedTestPath)-1] ) ) - correctedTestPath[ strlen(correctedTestPath) - 1 ] = 0; - - pTestPath = correctedTestPath; - - const char *pCurPos = pPathEnvVar; - while ( 1 ) - { - const char *pTestPos = Q_stristr( pCurPos, pTestPath ); - if ( !pTestPos ) - return false; - - // Ok, we found pTestPath in the path, but it's only valid if it's followed by an optional slash and a semicolon. - pTestPos += strlen( pTestPath ); - if ( pTestPos[0] == 0 || pTestPos[0] == ';' || (PATHSEPARATOR( pTestPos[0] ) && pTestPos[1] == ';') ) - return true; - - // Advance our marker.. - pCurPos = pTestPos; - } -} -#endif - -#ifndef _XBOX -FSReturnCode_t SetSteamInstallPath( char *steamInstallPath, int steamInstallPathLen, CSteamEnvVars &steamEnvVars, bool bErrorsAsWarnings ) -{ - // Start at our bin directory and move up until we find a directory with steam.dll in it. - char executablePath[MAX_PATH]; - if ( !FileSystem_GetExecutableDir( executablePath, sizeof( executablePath ) ) ) - { - if ( bErrorsAsWarnings ) - { - Warning( "SetSteamInstallPath: FileSystem_GetExecutableDir failed." ); - return FS_INVALID_PARAMETERS; - } - else - { - return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetExecutableDir failed." ); - } - } - - Q_strncpy( steamInstallPath, executablePath, steamInstallPathLen ); - while ( 1 ) - { - // Ignore steamapp.cfg here in case they're debugging. We still need to know the real steam path so we can find their username. - // find - if ( DoesFileExistIn( steamInstallPath, "steam.dll" ) && !DoesFileExistIn( steamInstallPath, "steamapp.cfg" ) ) - break; - - if ( !Q_StripLastDir( steamInstallPath, steamInstallPathLen ) ) - { - if ( bErrorsAsWarnings ) - { - Warning( "Can't find steam.dll relative to executable path: %s.", executablePath ); - return FS_MISSING_STEAM_DLL; - } - else - { - return SetupFileSystemError( false, FS_MISSING_STEAM_DLL, "Can't find steam.dll relative to executable path: %s.", executablePath ); - } - } - } - - // Also, add the install path to their PATH environment variable, so filesystem_steam.dll can get to steam.dll. - char szPath[ 8192 ]; - steamEnvVars.m_Path.GetValue( szPath, sizeof( szPath ) ); - if ( !DoesPathExistAlready( szPath, steamInstallPath ) ) - { - steamEnvVars.m_Path.SetValue( "%s;%s", szPath, steamInstallPath ); - } - return FS_OK; -} -#endif - -#ifndef _XBOX -FSReturnCode_t GetSteamCfgPath( char *steamCfgPath, int steamCfgPathLen ) -{ - steamCfgPath[0] = 0; - char executablePath[MAX_PATH]; - if ( !FileSystem_GetExecutableDir( executablePath, sizeof( executablePath ) ) ) - { - return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetExecutableDir failed." ); - } - Q_strncpy( steamCfgPath, executablePath, steamCfgPathLen ); - while ( 1 ) - { - if ( DoesFileExistIn( steamCfgPath, "steam.cfg" ) ) - break; - - if ( !Q_StripLastDir( steamCfgPath, steamCfgPathLen) ) - { - // the file isnt found, thats ok, its not mandatory - return FS_OK; - } - } - Q_AppendSlash( steamCfgPath, steamCfgPathLen ); - Q_strncat( steamCfgPath, "steam.cfg", steamCfgPathLen, COPY_ALL_CHARACTERS ); - - return FS_OK; -} -#endif - -#ifndef _XBOX -void SetSteamAppUser( KeyValues *pSteamInfo, const char *steamInstallPath, CSteamEnvVars &steamEnvVars ) -{ - // Always inherit the Steam user if it's already set, since it probably means we (or the - // the app that launched us) were launched from Steam. - char appUser[MAX_PATH]; - if ( steamEnvVars.m_SteamAppUser.GetValue( appUser, sizeof( appUser ) ) ) - return; - - const char *pTempAppUser = NULL; - if ( pSteamInfo && (pTempAppUser = pSteamInfo->GetString( "SteamAppUser", NULL )) != NULL ) - { - Q_strncpy( appUser, pTempAppUser, sizeof( appUser ) ); - } - else - { - // They don't have SteamInfo.txt, or it's missing SteamAppUser. Try to figure out the user - // by looking in \config\SteamAppData.vdf. - char fullFilename[MAX_PATH]; - Q_strncpy( fullFilename, steamInstallPath, sizeof( fullFilename ) ); - Q_AppendSlash( fullFilename, sizeof( fullFilename ) ); - Q_strncat( fullFilename, "config\\SteamAppData.vdf", sizeof( fullFilename ), COPY_ALL_CHARACTERS ); - - KeyValues *pSteamAppData = ReadKeyValuesFile( fullFilename ); - if ( !pSteamAppData || (pTempAppUser = pSteamAppData->GetString( "AutoLoginUser", NULL )) == NULL ) - { - Error( "Can't find steam app user info." ); - } - Q_strncpy( appUser, pTempAppUser, sizeof( appUser ) ); - - pSteamAppData->deleteThis(); - } - - Q_strlower( appUser ); - steamEnvVars.m_SteamAppUser.SetValue( "%s", appUser ); -} -#endif - -#ifndef _XBOX -void SetSteamUserPassphrase( KeyValues *pSteamInfo, CSteamEnvVars &steamEnvVars ) -{ - // Always inherit the passphrase if it's already set, since it probably means we (or the - // the app that launched us) were launched from Steam. - char szPassPhrase[ MAX_PATH ]; - if ( steamEnvVars.m_SteamUserPassphrase.GetValue( szPassPhrase, sizeof( szPassPhrase ) ) ) - return; - - // SteamUserPassphrase. - const char *pStr; - if ( pSteamInfo && (pStr = pSteamInfo->GetString( "SteamUserPassphrase", NULL )) != NULL ) - { - steamEnvVars.m_SteamUserPassphrase.SetValue( "%s", pStr ); - } -} -#endif - -#ifndef _XBOX -void SetSteamAppId( KeyValues *pFileSystemInfo, const char *pGameInfoDirectory, CSteamEnvVars &steamEnvVars ) -{ - // SteamAppId is in gameinfo.txt->FileSystem->FileSystemInfo_Steam->SteamAppId. - int iAppId = pFileSystemInfo->GetInt( "SteamAppId", -1 ); - if ( iAppId == -1 ) - Error( "Missing SteamAppId in %s\\%s.", pGameInfoDirectory, GAMEINFO_FILENAME ); - - steamEnvVars.m_SteamAppId.SetValue( "%d", iAppId ); -} -#endif - -#ifndef _XBOX -FSReturnCode_t SetupSteamStartupEnvironment( KeyValues *pFileSystemInfo, const char *pGameInfoDirectory, CSteamEnvVars &steamEnvVars ) -{ - // Ok, we're going to run Steam. See if they have SteamInfo.txt. If not, we'll try to deduce what we can. - char steamInfoFile[MAX_PATH]; - Q_strncpy( steamInfoFile, pGameInfoDirectory, sizeof( steamInfoFile ) ); - Q_AppendSlash( steamInfoFile, sizeof( steamInfoFile ) ); - Q_strncat( steamInfoFile, "steaminfo.txt", sizeof( steamInfoFile ), COPY_ALL_CHARACTERS ); - KeyValues *pSteamInfo = ReadKeyValuesFile( steamInfoFile ); - - char steamInstallPath[MAX_PATH]; - FSReturnCode_t ret = SetSteamInstallPath( steamInstallPath, sizeof( steamInstallPath ), steamEnvVars, false ); - if ( ret != FS_OK ) - return ret; - - SetSteamAppUser( pSteamInfo, steamInstallPath, steamEnvVars ); - SetSteamUserPassphrase( pSteamInfo, steamEnvVars ); - SetSteamAppId( pFileSystemInfo, pGameInfoDirectory, steamEnvVars ); - - if ( pSteamInfo ) - pSteamInfo->deleteThis(); - - return FS_OK; -} -#endif - -#ifndef _XBOX -FSReturnCode_t GetSteamExtraAppId( const char *pDirectoryName, int *nExtraAppId ) -{ - // Now, load gameinfo.txt (to make sure it's there) - KeyValues *pMainFile, *pFileSystemInfo, *pSearchPaths; - FSReturnCode_t ret = LoadGameInfoFile( pDirectoryName, pMainFile, pFileSystemInfo, pSearchPaths ); - if ( ret != FS_OK ) - return ret; - - *nExtraAppId = pFileSystemInfo->GetInt( "ToolsAppId", -1 ); - pMainFile->deleteThis(); - return FS_OK; -} -#endif - -#ifndef _XBOX -FSReturnCode_t FileSystem_SetBasePaths( IFileSystem *pFileSystem ) -{ - pFileSystem->RemoveSearchPaths( "EXECUTABLE_PATH" ); - - char executablePath[MAX_PATH]; - if ( !FileSystem_GetExecutableDir( executablePath, sizeof( executablePath ) ) ) - return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetExecutableDir failed." ); - - pFileSystem->AddSearchPath( executablePath, "EXECUTABLE_PATH" ); - return FS_OK; -} -#endif - -//----------------------------------------------------------------------------- -// Returns the name of the file system DLL to use -//----------------------------------------------------------------------------- -FSReturnCode_t FileSystem_GetFileSystemDLLName( char *pFileSystemDLL, int nMaxLen, bool &bSteam ) -{ - bSteam = false; - - // Inside of here, we don't have a filesystem yet, so we have to assume that the filesystem_stdio or filesystem_steam - // is in this same directory with us. - char executablePath[MAX_PATH]; - if ( !FileSystem_GetExecutableDir( executablePath, sizeof( executablePath ) ) ) - return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetExecutableDir failed." ); - -#ifdef _WIN32 - Q_snprintf( pFileSystemDLL, nMaxLen, "%s%cfilesystem_stdio.dll", executablePath, CORRECT_PATH_SEPARATOR ); - - // If filesystem_stdio.dll is missing or -steam is specified, then load filesystem_steam.dll. - // There are two command line parameters for Steam: - // 1) -steam (runs Steam in remote filesystem mode; requires Steam backend) - // 2) -steamlocal (runs Steam in local filesystem mode (all content off HDD) -#ifndef _XBOX - if ( CommandLine()->FindParm( "-steam" ) || CommandLine()->FindParm( "-steamlocal" ) || _access( pFileSystemDLL, 0 ) != 0 ) - { - Q_snprintf( pFileSystemDLL, nMaxLen, "%s%cfilesystem_steam.dll", executablePath, CORRECT_PATH_SEPARATOR ); - bSteam = true; - } -#endif -#elif _LINUX - Q_snprintf( pFileSystemDLL, nMaxLen, "%s%cfilesystem_i486.so", executablePath, CORRECT_PATH_SEPARATOR ); -#else - #error "define a filesystem dll name" -#endif - - return FS_OK; -} - -//----------------------------------------------------------------------------- -// Sets up the steam.dll install path in our PATH env var (so you can then just -// LoadLibrary() on filesystem_steam.dll without having to copy steam.dll anywhere special ) -//----------------------------------------------------------------------------- -FSReturnCode_t FileSystem_SetupSteamInstallPath() -{ -#ifndef _XBOX - CSteamEnvVars steamEnvVars; - char steamInstallPath[MAX_PATH]; - FSReturnCode_t ret = SetSteamInstallPath( steamInstallPath, sizeof( steamInstallPath ), steamEnvVars, true ); - steamEnvVars.m_Path.SetRestoreOriginalValue( false ); // We want to keep the change to the path going forward. - return ret; -#else - return FS_OK; -#endif -} - -//----------------------------------------------------------------------------- -// Sets up the steam environment + gets back the gameinfo.txt path -//----------------------------------------------------------------------------- -FSReturnCode_t FileSystem_SetupSteamEnvironment( CFSSteamSetupInfo &fsInfo ) -{ -#ifndef _XBOX - // First, locate the directory with gameinfo.txt. - FSReturnCode_t ret = LocateGameInfoFile( fsInfo, fsInfo.m_GameInfoPath, sizeof( fsInfo.m_GameInfoPath ) ); - if ( ret != FS_OK ) - return ret; - - CSteamEnvVars steamEnvVars; - if ( fsInfo.m_bSteam ) - { - if ( fsInfo.m_bToolsMode ) - { - // Now, load gameinfo.txt (to make sure it's there) - KeyValues *pMainFile, *pFileSystemInfo, *pSearchPaths; - ret = LoadGameInfoFile( fsInfo.m_GameInfoPath, pMainFile, pFileSystemInfo, pSearchPaths ); - if ( ret != FS_OK ) - return ret; - - // If filesystem_stdio.dll is missing or -steam is specified, then load filesystem_steam.dll. - // There are two command line parameters for Steam: - // 1) -steam (runs Steam in remote filesystem mode; requires Steam backend) - // 2) -steamlocal (runs Steam in local filesystem mode (all content off HDD) - - // Setup all the environment variables related to Steam so filesystem_steam.dll knows how to initialize Steam. - ret = SetupSteamStartupEnvironment( pFileSystemInfo, fsInfo.m_GameInfoPath, steamEnvVars ); - if ( ret != FS_OK ) - return ret; - - steamEnvVars.m_SteamAppId.SetRestoreOriginalValue( false ); // We want to keep the change to the path going forward. - - // We're done with main file - pMainFile->deleteThis(); - } - else if ( fsInfo.m_bSetSteamDLLPath ) - { - // This is used by the engine to automatically set the path to their steam.dll when running the engine, - // so they can debug it without having to copy steam.dll up into their hl2.exe folder. - char steamInstallPath[MAX_PATH]; - ret = SetSteamInstallPath( steamInstallPath, sizeof( steamInstallPath ), steamEnvVars, true ); - steamEnvVars.m_Path.SetRestoreOriginalValue( false ); // We want to keep the change to the path going forward. - } - } -#endif - - return FS_OK; -} - - -//----------------------------------------------------------------------------- -// Loads the file system module -//----------------------------------------------------------------------------- -FSReturnCode_t FileSystem_LoadFileSystemModule( CFSLoadModuleInfo &fsInfo ) -{ - // First, locate the directory with gameinfo.txt. - FSReturnCode_t ret = FileSystem_SetupSteamEnvironment( fsInfo ); - if ( ret != FS_OK ) - return ret; - - // Now that the environment is setup, load the filesystem module. - if ( !Sys_LoadInterface( - fsInfo.m_pFileSystemDLLName, - FILESYSTEM_INTERFACE_VERSION, - &fsInfo.m_pModule, - (void**)&fsInfo.m_pFileSystem ) ) - { - return SetupFileSystemError( false, FS_UNABLE_TO_INIT, "Can't load %s.", fsInfo.m_pFileSystemDLLName ); - } - - if ( !fsInfo.m_pFileSystem->Connect( fsInfo.m_ConnectFactory ) ) - return SetupFileSystemError( false, FS_UNABLE_TO_INIT, "%s IFileSystem::Connect failed.", fsInfo.m_pFileSystemDLLName ); - - if ( fsInfo.m_pFileSystem->Init() != INIT_OK ) - return SetupFileSystemError( false, FS_UNABLE_TO_INIT, "%s IFileSystem::Init failed.", fsInfo.m_pFileSystemDLLName ); - - return FS_OK; -} - - -//----------------------------------------------------------------------------- -// Mounds a particular steam cache -//----------------------------------------------------------------------------- -#ifndef _XBOX -FSReturnCode_t FileSystem_MountContent( CFSMountContentInfo &mountContentInfo ) -{ - // This part is Steam-only. - if ( mountContentInfo.m_pFileSystem->IsSteam() ) - { - // Find out the "extra app id". This is for tools, which want to mount a base app's filesystem - // like HL2, then mount the SDK content (tools materials and models, etc) in addition. - int nExtraAppId = -1; - if ( mountContentInfo.m_bToolsMode ) - { - FSReturnCode_t ret = GetSteamExtraAppId( mountContentInfo.m_pDirectoryName, &nExtraAppId ); - if ( ret != FS_OK ) - return ret; - } - - // Set our working directory temporarily so Steam can remember it. - // This is what Steam strips off absolute filenames like c:\program files\valve\steam\steamapps\username\sourcesdk - // to get to the relative part of the path. - char baseDir[MAX_PATH], oldWorkingDir[MAX_PATH]; - if ( !FileSystem_GetBaseDir( baseDir, sizeof( baseDir ) ) ) - return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetBaseDir failed." ); - - Q_getwd( oldWorkingDir, sizeof( oldWorkingDir ) ); - _chdir( baseDir ); - - // Filesystem_tools needs to add dependencies in here beforehand. - FilesystemMountRetval_t retVal = mountContentInfo.m_pFileSystem->MountSteamContent( nExtraAppId ); - - _chdir( oldWorkingDir ); - - if ( retVal != FILESYSTEM_MOUNT_OK ) - return SetupFileSystemError( true, FS_UNABLE_TO_INIT, "Unable to mount Steam content in the file system" ); - } - - return FileSystem_SetBasePaths( mountContentInfo.m_pFileSystem ); -} -#endif - -void FileSystem_SetErrorMode( FSErrorMode_t errorMode ) -{ - g_FileSystemErrorMode = errorMode; -} - -#ifndef _XBOX -void FileSystem_ClearSteamEnvVars() -{ - CSteamEnvVars envVars; - - // Change the values and don't restore the originals. - envVars.m_SteamAppId.SetValue( "" ); - envVars.m_SteamUserPassphrase.SetValue( "" ); - envVars.m_SteamAppUser.SetValue( "" ); - - envVars.SetRestoreOriginalValue_ALL( false ); -} -#endif - -#endif // !_STATIC_LINKED || _SHARED_LIB - - -//----------------------------------------------------------------------------- -// Adds the platform folder to the search path. -//----------------------------------------------------------------------------- -void FileSystem_AddSearchPath_Platform( IFileSystem *pFileSystem, const char *szGameInfoPath ) -{ - char platform[MAX_PATH]; - if ( pFileSystem->IsSteam() ) - { - // Steam doesn't support relative paths - Q_strncpy( platform, "platform", MAX_PATH ); - } - else - { - Q_strncpy( platform, szGameInfoPath, MAX_PATH ); - Q_StripTrailingSlash( platform ); - Q_strncat( platform, "/../platform", MAX_PATH, MAX_PATH ); - } - - pFileSystem->AddSearchPath( platform, "PLATFORM" ); -} +//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + +#undef PROTECTED_THINGS_ENABLE +#undef PROTECT_FILEIO_FUNCTIONS +#undef fopen + +#ifdef _XBOX +#include "xbox/xbox_platform.h" +#include "xbox/xbox_win32stubs.h" +#include "xbox/xbox_core.h" +#endif +#if defined(_WIN32) && !defined(_XBOX) +#include +#include +#include // _chmod +#include +#elif _LINUX +#include +#define _putenv putenv +#define _chdir chdir +#define _access access +#endif +#include +#include +#include "vstdlib/strtools.h" +#include "filesystem_init.h" +#include "vstdlib/ICommandLine.h" +#include "KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include + +#define GAMEINFO_FILENAME "gameinfo.txt" + +static char g_FileSystemError[256]; +static FSErrorMode_t g_FileSystemErrorMode = FS_ERRORMODE_VCONFIG; + +// This class lets you modify environment variables, and it restores the original value +// when it goes out of scope. +#ifndef _XBOX +class CTempEnvVar +{ +public: + CTempEnvVar( const char *pVarName ) + { + m_bRestoreOriginalValue = true; + m_pVarName = pVarName; + + const char *pValue = NULL; + +#ifdef _WIN32 + // Use GetEnvironmentVariable instead of getenv because getenv doesn't pick up changes + // to the process environment after the DLL was loaded. + char szBuf[ 4096 ]; + if ( GetEnvironmentVariable( m_pVarName, szBuf, sizeof( szBuf ) ) != 0) + { + pValue = szBuf; + } +#else + // LINUX BUG: see above + pValue = getenv( pVarName ); +#endif // _WIN32 + + if ( pValue ) + { + m_bExisted = true; + m_OriginalValue.SetSize( strlen( pValue ) + 1 ); + memcpy( m_OriginalValue.Base(), pValue, m_OriginalValue.Count() ); + } + else + { + m_bExisted = false; + } + } + + ~CTempEnvVar() + { + if ( m_bRestoreOriginalValue ) + { + // Restore the original value. + if ( m_bExisted ) + { + SetValue( "%s", m_OriginalValue.Base() ); + } + else + { + ClearValue(); + } + } + } + + void SetRestoreOriginalValue( bool bRestore ) + { + m_bRestoreOriginalValue = bRestore; + } + + int GetValue(char *pszBuf, int nBufSize ) + { + if ( !pszBuf || ( nBufSize <= 0 ) ) + return 0; + +#ifdef _WIN32 + // Use GetEnvironmentVariable instead of getenv because getenv doesn't pick up changes + // to the process environment after the DLL was loaded. + return GetEnvironmentVariable( m_pVarName, pszBuf, nBufSize ); +#else + // LINUX BUG: see above + const char *pszOut = getenv( m_pVarName ); + if ( !pszOut ) + { + *pszBuf = '\0'; + return 0; + } + + Q_strncpy( pszBuf, pszOut, nBufSize ); + return Q_strlen( pszBuf ); +#endif // _WIN32 + } + + void SetValue( const char *pValue, ... ) + { + char valueString[4096]; + va_list marker; + va_start( marker, pValue ); + Q_vsnprintf( valueString, sizeof( valueString ), pValue, marker ); + va_end( marker ); + + char str[4096]; + Q_snprintf( str, sizeof( str ), "%s=%s", m_pVarName, valueString ); + _putenv( str ); + } + + void ClearValue() + { + char str[512]; + Q_snprintf( str, sizeof( str ), "%s=", m_pVarName ); + _putenv( str ); + } + +private: + bool m_bRestoreOriginalValue; + const char *m_pVarName; + bool m_bExisted; + CUtlVector m_OriginalValue; +}; + + +class CSteamEnvVars +{ +public: + CSteamEnvVars() : + m_SteamAppId( "SteamAppId" ), + m_SteamUserPassphrase( "SteamUserPassphrase" ), + m_SteamAppUser( "SteamAppUser" ), + m_Path( "path" ) + { + } + + void SetRestoreOriginalValue_ALL( bool bRestore ) + { + m_SteamAppId.SetRestoreOriginalValue( bRestore ); + m_SteamUserPassphrase.SetRestoreOriginalValue( bRestore ); + m_SteamAppUser.SetRestoreOriginalValue( bRestore ); + m_Path.SetRestoreOriginalValue( bRestore ); + } + + CTempEnvVar m_SteamAppId; + CTempEnvVar m_SteamUserPassphrase; + CTempEnvVar m_SteamAppUser; + CTempEnvVar m_Path; +}; +#endif + + + +// ---------------------------------------------------------------------------------------------------- // +// Helpers. +// ---------------------------------------------------------------------------------------------------- // +#ifndef _XBOX +void Q_getwd( char *out, int outSize ) +{ +#if defined( _WIN32 ) || defined( WIN32 ) + _getcwd( out, outSize ); + Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); +#else + getcwd(out, outSize); + strcat(out, "/"); +#endif + Q_FixSlashes( out ); +} +#endif + +// ---------------------------------------------------------------------------------------------------- // +// Module interface. +// ---------------------------------------------------------------------------------------------------- // + +CFSSearchPathsInit::CFSSearchPathsInit() +{ + m_pDirectoryName = NULL; + m_pLanguage = NULL; + m_ModPath[0] = 0; +} + + +CFSSteamSetupInfo::CFSSteamSetupInfo() +{ + m_pDirectoryName = NULL; + m_bOnlyUseDirectoryName = false; + m_bSteam = false; + m_bToolsMode = true; + m_bNoGameInfo = false; +} + + +CFSLoadModuleInfo::CFSLoadModuleInfo() +{ + m_pFileSystemDLLName = NULL; + m_pFileSystem = NULL; + m_pModule = NULL; +} + + +CFSMountContentInfo::CFSMountContentInfo() +{ + m_bToolsMode = true; + m_pDirectoryName = NULL; + m_pFileSystem = NULL; +} + + +const char *FileSystem_GetLastErrorString() +{ + return g_FileSystemError; +} + + +void AddLanguageGameDir( IFileSystem *pFileSystem, const char *pLocation, const char *pLanguage ) +{ +#if !defined(SWDS) && !defined(_XBOX) + char temp[MAX_PATH]; + Q_snprintf( temp, sizeof(temp), "%s_%s", pLocation, pLanguage ); + pFileSystem->AddSearchPath( temp, "GAME", PATH_ADD_TO_TAIL ); + + if ( !pFileSystem->IsSteam() ) + { + // also look in "..\localization\" if not running Steam + char baseDir[MAX_PATH]; + char *tempPtr = NULL, *gameDir = NULL; + + Q_strncpy( baseDir, pLocation, sizeof(baseDir) ); + tempPtr = Q_strstr( baseDir, "\\game\\" ); + + if ( tempPtr ) + { + gameDir = tempPtr + Q_strlen( "\\game\\" ); + *tempPtr = 0; + Q_snprintf( temp, sizeof(temp), "%s%clocalization%c%s_%s", baseDir, CORRECT_PATH_SEPARATOR, CORRECT_PATH_SEPARATOR, gameDir, pLanguage ); + pFileSystem->AddSearchPath( temp, "GAME", PATH_ADD_TO_TAIL ); + } + } +#endif +} + + +void AddGameBinDir( IFileSystem *pFileSystem, const char *pLocation ) +{ +#ifndef _XBOX + char temp[MAX_PATH]; + Q_snprintf( temp, sizeof(temp), "%s%cbin", pLocation, CORRECT_PATH_SEPARATOR ); + pFileSystem->AddSearchPath( temp, "GAMEBIN", PATH_ADD_TO_TAIL ); +#endif +} + +#ifndef _XBOX +KeyValues* ReadKeyValuesFile( const char *pFilename ) +{ + // Read in the gameinfo.txt file and null-terminate it. + FILE *fp = fopen( pFilename, "rb" ); + if ( !fp ) + return NULL; + CUtlVector buf; + fseek( fp, 0, SEEK_END ); + buf.SetSize( ftell( fp ) + 1 ); + fseek( fp, 0, SEEK_SET ); + fread( buf.Base(), 1, buf.Count()-1, fp ); + fclose( fp ); + buf[buf.Count()-1] = 0; + + KeyValues *kv = new KeyValues( "" ); + if ( !kv->LoadFromBuffer( pFilename, buf.Base() ) ) + { + kv->deleteThis(); + return NULL; + } + + return kv; +} +#endif + +static int Sys_GetExecutableName( char *out, int len ) +{ +#ifdef _WIN32 + if ( !::GetModuleFileName( ( HINSTANCE )GetModuleHandle( NULL ), out, len ) ) + { + return 0; + } +#else + if ( CommandLine()->GetParm(0) ) + { + Q_MakeAbsolutePath( out, len, CommandLine()->GetParm(0) ); + } + else + { + return 0; + } +#endif + return 1; +} + +bool FileSystem_GetExecutableDir( char *exedir, int exeDirLen ) +{ + exedir[ 0 ] = 0; + if ( !Sys_GetExecutableName( exedir, exeDirLen ) ) + return false; + + Q_StripFilename( exedir ); + Q_FixSlashes( exedir ); + + // Return the bin directory as the executable dir if it's not in there + // because that's really where we're running from... + char ext[MAX_PATH]; + Q_StrRight( exedir, 4, ext, sizeof( ext ) ); + if ( ext[0] != CORRECT_PATH_SEPARATOR || Q_stricmp( ext+1, "bin" ) != 0 ) + { + Q_strncat( exedir, "\\bin", exeDirLen, COPY_ALL_CHARACTERS ); + Q_FixSlashes( exedir ); + } + return true; +} + +#ifndef _XBOX +static bool FileSystem_GetBaseDir( char *baseDir, int baseDirLen ) +{ + if ( FileSystem_GetExecutableDir( baseDir, baseDirLen ) ) + { + Q_StripFilename( baseDir ); + return true; + } + else + { + return false; + } +} +#endif + +#ifndef _XBOX +void LaunchVConfig() +{ +#ifdef _WIN32 + char vconfigExe[MAX_PATH]; + FileSystem_GetExecutableDir( vconfigExe, sizeof( vconfigExe ) ); + Q_AppendSlash( vconfigExe, sizeof( vconfigExe ) ); + Q_strncat( vconfigExe, "vconfig.exe", sizeof( vconfigExe ), COPY_ALL_CHARACTERS ); + + char *argv[] = + { + vconfigExe, + "-allowdebug", + NULL + }; + + _spawnv( _P_NOWAIT, vconfigExe, argv ); +#endif +} +#endif + +#ifndef _XBOX +const char* GetVProjectCmdLineValue() +{ + return CommandLine()->ParmValue( "-vproject", CommandLine()->ParmValue( "-game" ) ); +} +#endif + +FSReturnCode_t SetupFileSystemError( bool bRunVConfig, FSReturnCode_t retVal, const char *pMsg, ... ) +{ + va_list marker; + va_start( marker, pMsg ); + Q_vsnprintf( g_FileSystemError, sizeof( g_FileSystemError ), pMsg, marker ); + va_end( marker ); + + Warning( "%s", g_FileSystemError ); + +#ifndef _XBOX + // Run vconfig? + // Don't do it if they specifically asked for it not to, or if they manually specified a vconfig with -game or -vproject. + if ( bRunVConfig && g_FileSystemErrorMode == FS_ERRORMODE_VCONFIG && !CommandLine()->FindParm( CMDLINEOPTION_NOVCONFIG ) && !GetVProjectCmdLineValue() ) + LaunchVConfig(); +#endif + + if ( g_FileSystemErrorMode == FS_ERRORMODE_AUTO || g_FileSystemErrorMode == FS_ERRORMODE_VCONFIG ) + Error( "%s\n", g_FileSystemError ); + + return retVal; +} + + +#ifndef _XBOX +FSReturnCode_t LoadGameInfoFile( + const char *pDirectoryName, + KeyValues *&pMainFile, + KeyValues *&pFileSystemInfo, + KeyValues *&pSearchPaths ) +{ + // If GameInfo.txt exists under pBaseDir, then this is their game directory. + // All the filesystem mappings will be in this file. + char gameinfoFilename[MAX_PATH]; + Q_strncpy( gameinfoFilename, pDirectoryName, sizeof( gameinfoFilename ) ); + Q_AppendSlash( gameinfoFilename, sizeof( gameinfoFilename ) ); + Q_strncat( gameinfoFilename, GAMEINFO_FILENAME, sizeof( gameinfoFilename ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( gameinfoFilename ); + pMainFile = ReadKeyValuesFile( gameinfoFilename ); + if ( !pMainFile ) + return SetupFileSystemError( true, FS_MISSING_GAMEINFO_FILE, "%s is missing.", gameinfoFilename ); + + pFileSystemInfo = pMainFile->FindKey( "FileSystem" ); + if ( !pFileSystemInfo ) + { + pMainFile->deleteThis(); + return SetupFileSystemError( true, FS_INVALID_GAMEINFO_FILE, "%s is not a valid format.", gameinfoFilename ); + } + + // Now read in all the search paths. + pSearchPaths = pFileSystemInfo->FindKey( "SearchPaths" ); + if ( !pSearchPaths ) + { + pMainFile->deleteThis(); + return SetupFileSystemError( true, FS_INVALID_GAMEINFO_FILE, "%s is not a valid format.", gameinfoFilename ); + } + return FS_OK; +} +#endif + +// checks the registry for the low violence setting +// Check "HKEY_CURRENT_USER\Software\Valve\Source\Settings" and "User Token 2" or "User Token 3" +#ifndef _XBOX +bool IsLowViolenceBuild( void ) +{ +#if defined(_WIN32) + HKEY hKey; + char szValue[64]; + unsigned long len = sizeof(szValue) - 1; + bool retVal = false; + + if ( RegOpenKeyEx( HKEY_CURRENT_USER, "Software\\Valve\\Source\\Settings", NULL, KEY_READ, &hKey) == ERROR_SUCCESS ) + { + // User Token 2 + if ( RegQueryValueEx( hKey, "User Token 2", NULL, NULL, (unsigned char*)szValue, &len ) == ERROR_SUCCESS ) + { + if ( Q_strlen( szValue ) > 0 ) + { + retVal = true; + } + } + + if ( !retVal ) + { + // reset "len" for the next check + len = sizeof(szValue) - 1; + + // User Token 3 + if ( RegQueryValueEx( hKey, "User Token 3", NULL, NULL, (unsigned char*)szValue, &len ) == ERROR_SUCCESS ) + { + if ( Q_strlen( szValue ) > 0 ) + { + retVal = true; + } + } + } + + RegCloseKey(hKey); + } + + return retVal; +#elif _LINUX + return false; +#elif +#error "Fix me" +#endif +} +#endif + +FSReturnCode_t FileSystem_LoadSearchPaths( CFSSearchPathsInit &initInfo ) +{ +#ifndef _XBOX + bool bLowViolence = IsLowViolenceBuild(); + + if ( !initInfo.m_pFileSystem || !initInfo.m_pDirectoryName ) + return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_LoadSearchPaths: Invalid parameters specified." ); + + KeyValues *pMainFile, *pFileSystemInfo, *pSearchPaths; + FSReturnCode_t retVal = LoadGameInfoFile( initInfo.m_pDirectoryName, pMainFile, pFileSystemInfo, pSearchPaths ); + if ( retVal != FS_OK ) + return retVal; + + // All paths except those marked with |gameinfo_path| are relative to the base dir. + char baseDir[MAX_PATH]; + if ( !FileSystem_GetBaseDir( baseDir, sizeof( baseDir ) ) ) + return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetBaseDir failed." ); + + initInfo.m_ModPath[0] = 0; + + bool bFirstGamePath = true; + for ( KeyValues *pCur=pSearchPaths->GetFirstValue(); pCur; pCur=pCur->GetNextValue() ) + { + const char *pPathID = pCur->GetName(); + + char fullLocationPath[MAX_PATH]; + const char *pLocation = pCur->GetString(); + if ( Q_stristr( pLocation, "|gameinfo_path|" ) == pLocation ) + Q_MakeAbsolutePath( fullLocationPath, sizeof( fullLocationPath ), pLocation + strlen( "|gameinfo_path|" ), initInfo.m_pDirectoryName ); + else + Q_MakeAbsolutePath( fullLocationPath, sizeof( fullLocationPath ), pLocation, baseDir ); + + // Add language, mod, and gamebin search paths automatically. + if ( Q_stricmp( pPathID, "game" ) == 0 ) + { + // add the low violence path + if ( bLowViolence ) + { + char szPath[MAX_PATH]; + Q_snprintf( szPath, sizeof(szPath), "%s_lv", fullLocationPath ); + initInfo.m_pFileSystem->AddSearchPath( szPath, pPathID, PATH_ADD_TO_TAIL ); + } + + // add the language path + if ( initInfo.m_pLanguage ) + { + AddLanguageGameDir( initInfo.m_pFileSystem, fullLocationPath, initInfo.m_pLanguage ); + } + +#ifndef _XBOX + if ( CommandLine()->FindParm( "-tempcontent" ) != 0 ) + { + char szPath[MAX_PATH]; + Q_snprintf( szPath, sizeof(szPath), "%s_tempcontent", fullLocationPath ); + initInfo.m_pFileSystem->AddSearchPath( szPath, pPathID, PATH_ADD_TO_TAIL ); + } +#endif + + // mark the first "game" dir as the "MOD" dir + if ( bFirstGamePath ) + { + bFirstGamePath = false; + initInfo.m_pFileSystem->AddSearchPath( fullLocationPath, "MOD", PATH_ADD_TO_TAIL ); + Q_strncpy( initInfo.m_ModPath, fullLocationPath, sizeof( initInfo.m_ModPath ) ); + } + + // add the game bin + AddGameBinDir( initInfo.m_pFileSystem, fullLocationPath ); + } + + initInfo.m_pFileSystem->AddSearchPath( fullLocationPath, pPathID, PATH_ADD_TO_TAIL ); + } + + pMainFile->deleteThis(); + + // Also, mark specific path IDs as "by request only". That way, we won't waste time searching in them + // when people forget to specify a search path. + initInfo.m_pFileSystem->MarkPathIDByRequestOnly( "executable_path", true ); + initInfo.m_pFileSystem->MarkPathIDByRequestOnly( "gamebin", true ); + initInfo.m_pFileSystem->MarkPathIDByRequestOnly( "mod", true ); + if ( initInfo.m_ModPath[0] != 0 ) + { + // Add the write path last. + initInfo.m_pFileSystem->AddSearchPath( initInfo.m_ModPath, "DEFAULT_WRITE_PATH", PATH_ADD_TO_TAIL ); + } + +#ifdef _DEBUG + initInfo.m_pFileSystem->PrintSearchPaths(); +#endif + +#endif // _XBOX + return FS_OK; +} + +#ifndef _XBOX +bool DoesFileExistIn( const char *pDirectoryName, const char *pFilename ) +{ + char filename[MAX_PATH]; + Q_strncpy( filename, pDirectoryName, sizeof( filename ) ); + Q_AppendSlash( filename, sizeof( filename ) ); + Q_strncat( filename, pFilename, sizeof( filename ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( filename ); + return ( _access( filename, 0 ) == 0 ); +} +#endif + +#ifndef _XBOX +FSReturnCode_t LocateGameInfoFile( const CFSSteamSetupInfo &fsInfo, char *pOutDir, int outDirLen ) +{ + // Engine and Hammer don't want to search around for it. + if ( fsInfo.m_bOnlyUseDirectoryName ) + { + if ( !fsInfo.m_pDirectoryName ) + return SetupFileSystemError( false, FS_MISSING_GAMEINFO_FILE, "bOnlyUseDirectoryName=1 and pDirectoryName=NULL." ); + + if ( !DoesFileExistIn( fsInfo.m_pDirectoryName, GAMEINFO_FILENAME ) ) + return SetupFileSystemError( true, FS_MISSING_GAMEINFO_FILE, "%s doesn't exist in %s.", GAMEINFO_FILENAME, fsInfo.m_pDirectoryName ); + + Q_strncpy( pOutDir, fsInfo.m_pDirectoryName, outDirLen ); + return FS_OK; + } + + // First, check for overrides on the command line or environment variables. + const char *pProject = GetVProjectCmdLineValue(); + if ( !pProject ) + { + // Check their registry. + pProject = getenv( GAMEDIR_TOKEN ); + } + + if ( pProject ) + { + if ( DoesFileExistIn( pProject, GAMEINFO_FILENAME ) ) + { + Q_MakeAbsolutePath( pOutDir, outDirLen, pProject ); + return FS_OK; + } + else if ( fsInfo.m_bNoGameInfo ) + { + // fsInfo.m_bNoGameInfo is set by the Steam dedicated server, before it knows which mod to use. + // Steam dedicated server doesn't need a gameinfo.txt, because we'll ask which mod to use, even if + // -game is supplied on the command line. + Q_strncpy( pOutDir, "", outDirLen ); + return FS_OK; + } + else + { + // They either specified vproject on the command line or it's in their registry. Either way, + // we don't want to continue if they've specified it but it's not valid. + goto ShowError; + } + } + + if ( fsInfo.m_bNoGameInfo ) + { + Q_strncpy( pOutDir, "", outDirLen ); + return FS_OK; + } + + Warning( "Warning: falling back to auto detection of vproject directory.\n" ); + + // Now look for it in the directory they passed in. + if ( fsInfo.m_pDirectoryName ) + Q_MakeAbsolutePath( pOutDir, outDirLen, fsInfo.m_pDirectoryName ); + else + Q_MakeAbsolutePath( pOutDir, outDirLen, "." ); + + do + { + if ( DoesFileExistIn( pOutDir, GAMEINFO_FILENAME ) ) + return FS_OK; + } while ( Q_StripLastDir( pOutDir, outDirLen ) ); + + // use the cwd and hunt down the tree until we find something + Q_getwd( pOutDir, outDirLen ); + do + { + if ( DoesFileExistIn( pOutDir, GAMEINFO_FILENAME ) ) + return FS_OK; + } while ( Q_StripLastDir( pOutDir, outDirLen ) ); + +ShowError: + return SetupFileSystemError( true, FS_MISSING_GAMEINFO_FILE, + "Unable to find %s. Solutions:\n\n" + "1. Read http://www.valve-erc.com/srcsdk/faq.html#NoGameDir\n" + "2. Run vconfig to specify which game you're working on.\n" + "3. Add -game on the command line where is the directory that %s is in.\n", + GAMEINFO_FILENAME, GAMEINFO_FILENAME ); +} +#endif + +#ifndef _XBOX +bool DoesPathExistAlready( const char *pPathEnvVar, const char *pTestPath ) +{ + // Fix the slashes in the input arguments. + char correctedPathEnvVar[8192], correctedTestPath[MAX_PATH]; + Q_strncpy( correctedPathEnvVar, pPathEnvVar, sizeof( correctedPathEnvVar ) ); + Q_FixSlashes( correctedPathEnvVar ); + pPathEnvVar = correctedPathEnvVar; + + Q_strncpy( correctedTestPath, pTestPath, sizeof( correctedTestPath ) ); + Q_FixSlashes( correctedTestPath ); + if ( strlen( correctedTestPath ) > 0 && PATHSEPARATOR( correctedTestPath[strlen(correctedTestPath)-1] ) ) + correctedTestPath[ strlen(correctedTestPath) - 1 ] = 0; + + pTestPath = correctedTestPath; + + const char *pCurPos = pPathEnvVar; + while ( 1 ) + { + const char *pTestPos = Q_stristr( pCurPos, pTestPath ); + if ( !pTestPos ) + return false; + + // Ok, we found pTestPath in the path, but it's only valid if it's followed by an optional slash and a semicolon. + pTestPos += strlen( pTestPath ); + if ( pTestPos[0] == 0 || pTestPos[0] == ';' || (PATHSEPARATOR( pTestPos[0] ) && pTestPos[1] == ';') ) + return true; + + // Advance our marker.. + pCurPos = pTestPos; + } +} +#endif + +#ifndef _XBOX +FSReturnCode_t SetSteamInstallPath( char *steamInstallPath, int steamInstallPathLen, CSteamEnvVars &steamEnvVars, bool bErrorsAsWarnings ) +{ + // Start at our bin directory and move up until we find a directory with steam.dll in it. + char executablePath[MAX_PATH]; + if ( !FileSystem_GetExecutableDir( executablePath, sizeof( executablePath ) ) ) + { + if ( bErrorsAsWarnings ) + { + Warning( "SetSteamInstallPath: FileSystem_GetExecutableDir failed." ); + return FS_INVALID_PARAMETERS; + } + else + { + return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetExecutableDir failed." ); + } + } + + Q_strncpy( steamInstallPath, executablePath, steamInstallPathLen ); + while ( 1 ) + { + // Ignore steamapp.cfg here in case they're debugging. We still need to know the real steam path so we can find their username. + // find + if ( DoesFileExistIn( steamInstallPath, "steam.dll" ) && !DoesFileExistIn( steamInstallPath, "steamapp.cfg" ) ) + break; + + if ( !Q_StripLastDir( steamInstallPath, steamInstallPathLen ) ) + { + if ( bErrorsAsWarnings ) + { + Warning( "Can't find steam.dll relative to executable path: %s.", executablePath ); + return FS_MISSING_STEAM_DLL; + } + else + { + return SetupFileSystemError( false, FS_MISSING_STEAM_DLL, "Can't find steam.dll relative to executable path: %s.", executablePath ); + } + } + } + + // Also, add the install path to their PATH environment variable, so filesystem_steam.dll can get to steam.dll. + char szPath[ 8192 ]; + steamEnvVars.m_Path.GetValue( szPath, sizeof( szPath ) ); + if ( !DoesPathExistAlready( szPath, steamInstallPath ) ) + { + steamEnvVars.m_Path.SetValue( "%s;%s", szPath, steamInstallPath ); + } + return FS_OK; +} +#endif + +#ifndef _XBOX +FSReturnCode_t GetSteamCfgPath( char *steamCfgPath, int steamCfgPathLen ) +{ + steamCfgPath[0] = 0; + char executablePath[MAX_PATH]; + if ( !FileSystem_GetExecutableDir( executablePath, sizeof( executablePath ) ) ) + { + return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetExecutableDir failed." ); + } + Q_strncpy( steamCfgPath, executablePath, steamCfgPathLen ); + while ( 1 ) + { + if ( DoesFileExistIn( steamCfgPath, "steam.cfg" ) ) + break; + + if ( !Q_StripLastDir( steamCfgPath, steamCfgPathLen) ) + { + // the file isnt found, thats ok, its not mandatory + return FS_OK; + } + } + Q_AppendSlash( steamCfgPath, steamCfgPathLen ); + Q_strncat( steamCfgPath, "steam.cfg", steamCfgPathLen, COPY_ALL_CHARACTERS ); + + return FS_OK; +} +#endif + +#ifndef _XBOX +void SetSteamAppUser( KeyValues *pSteamInfo, const char *steamInstallPath, CSteamEnvVars &steamEnvVars ) +{ + // Always inherit the Steam user if it's already set, since it probably means we (or the + // the app that launched us) were launched from Steam. + char appUser[MAX_PATH]; + if ( steamEnvVars.m_SteamAppUser.GetValue( appUser, sizeof( appUser ) ) ) + return; + + const char *pTempAppUser = NULL; + if ( pSteamInfo && (pTempAppUser = pSteamInfo->GetString( "SteamAppUser", NULL )) != NULL ) + { + Q_strncpy( appUser, pTempAppUser, sizeof( appUser ) ); + } + else + { + // They don't have SteamInfo.txt, or it's missing SteamAppUser. Try to figure out the user + // by looking in \config\SteamAppData.vdf. + char fullFilename[MAX_PATH]; + Q_strncpy( fullFilename, steamInstallPath, sizeof( fullFilename ) ); + Q_AppendSlash( fullFilename, sizeof( fullFilename ) ); + Q_strncat( fullFilename, "config\\SteamAppData.vdf", sizeof( fullFilename ), COPY_ALL_CHARACTERS ); + + KeyValues *pSteamAppData = ReadKeyValuesFile( fullFilename ); + if ( !pSteamAppData || (pTempAppUser = pSteamAppData->GetString( "AutoLoginUser", NULL )) == NULL ) + { + Error( "Can't find steam app user info." ); + } + Q_strncpy( appUser, pTempAppUser, sizeof( appUser ) ); + + pSteamAppData->deleteThis(); + } + + Q_strlower( appUser ); + steamEnvVars.m_SteamAppUser.SetValue( "%s", appUser ); +} +#endif + +#ifndef _XBOX +void SetSteamUserPassphrase( KeyValues *pSteamInfo, CSteamEnvVars &steamEnvVars ) +{ + // Always inherit the passphrase if it's already set, since it probably means we (or the + // the app that launched us) were launched from Steam. + char szPassPhrase[ MAX_PATH ]; + if ( steamEnvVars.m_SteamUserPassphrase.GetValue( szPassPhrase, sizeof( szPassPhrase ) ) ) + return; + + // SteamUserPassphrase. + const char *pStr; + if ( pSteamInfo && (pStr = pSteamInfo->GetString( "SteamUserPassphrase", NULL )) != NULL ) + { + steamEnvVars.m_SteamUserPassphrase.SetValue( "%s", pStr ); + } +} +#endif + +#ifndef _XBOX +void SetSteamAppId( KeyValues *pFileSystemInfo, const char *pGameInfoDirectory, CSteamEnvVars &steamEnvVars ) +{ + // SteamAppId is in gameinfo.txt->FileSystem->FileSystemInfo_Steam->SteamAppId. + int iAppId = pFileSystemInfo->GetInt( "SteamAppId", -1 ); + if ( iAppId == -1 ) + Error( "Missing SteamAppId in %s\\%s.", pGameInfoDirectory, GAMEINFO_FILENAME ); + + steamEnvVars.m_SteamAppId.SetValue( "%d", iAppId ); +} +#endif + +#ifndef _XBOX +FSReturnCode_t SetupSteamStartupEnvironment( KeyValues *pFileSystemInfo, const char *pGameInfoDirectory, CSteamEnvVars &steamEnvVars ) +{ + // Ok, we're going to run Steam. See if they have SteamInfo.txt. If not, we'll try to deduce what we can. + char steamInfoFile[MAX_PATH]; + Q_strncpy( steamInfoFile, pGameInfoDirectory, sizeof( steamInfoFile ) ); + Q_AppendSlash( steamInfoFile, sizeof( steamInfoFile ) ); + Q_strncat( steamInfoFile, "steaminfo.txt", sizeof( steamInfoFile ), COPY_ALL_CHARACTERS ); + KeyValues *pSteamInfo = ReadKeyValuesFile( steamInfoFile ); + + char steamInstallPath[MAX_PATH]; + FSReturnCode_t ret = SetSteamInstallPath( steamInstallPath, sizeof( steamInstallPath ), steamEnvVars, false ); + if ( ret != FS_OK ) + return ret; + + SetSteamAppUser( pSteamInfo, steamInstallPath, steamEnvVars ); + SetSteamUserPassphrase( pSteamInfo, steamEnvVars ); + SetSteamAppId( pFileSystemInfo, pGameInfoDirectory, steamEnvVars ); + + if ( pSteamInfo ) + pSteamInfo->deleteThis(); + + return FS_OK; +} +#endif + +#ifndef _XBOX +FSReturnCode_t GetSteamExtraAppId( const char *pDirectoryName, int *nExtraAppId ) +{ + // Now, load gameinfo.txt (to make sure it's there) + KeyValues *pMainFile, *pFileSystemInfo, *pSearchPaths; + FSReturnCode_t ret = LoadGameInfoFile( pDirectoryName, pMainFile, pFileSystemInfo, pSearchPaths ); + if ( ret != FS_OK ) + return ret; + + *nExtraAppId = pFileSystemInfo->GetInt( "ToolsAppId", -1 ); + pMainFile->deleteThis(); + return FS_OK; +} +#endif + +#ifndef _XBOX +FSReturnCode_t FileSystem_SetBasePaths( IFileSystem *pFileSystem ) +{ + pFileSystem->RemoveSearchPaths( "EXECUTABLE_PATH" ); + + char executablePath[MAX_PATH]; + if ( !FileSystem_GetExecutableDir( executablePath, sizeof( executablePath ) ) ) + return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetExecutableDir failed." ); + + pFileSystem->AddSearchPath( executablePath, "EXECUTABLE_PATH" ); + return FS_OK; +} +#endif + +//----------------------------------------------------------------------------- +// Returns the name of the file system DLL to use +//----------------------------------------------------------------------------- +FSReturnCode_t FileSystem_GetFileSystemDLLName( char *pFileSystemDLL, int nMaxLen, bool &bSteam ) +{ + bSteam = false; + + // Inside of here, we don't have a filesystem yet, so we have to assume that the filesystem_stdio or filesystem_steam + // is in this same directory with us. + char executablePath[MAX_PATH]; + if ( !FileSystem_GetExecutableDir( executablePath, sizeof( executablePath ) ) ) + return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetExecutableDir failed." ); + +#ifdef _WIN32 + Q_snprintf( pFileSystemDLL, nMaxLen, "%s%cfilesystem_stdio.dll", executablePath, CORRECT_PATH_SEPARATOR ); + + // If filesystem_stdio.dll is missing or -steam is specified, then load filesystem_steam.dll. + // There are two command line parameters for Steam: + // 1) -steam (runs Steam in remote filesystem mode; requires Steam backend) + // 2) -steamlocal (runs Steam in local filesystem mode (all content off HDD) +#ifndef _XBOX + if ( CommandLine()->FindParm( "-steam" ) || CommandLine()->FindParm( "-steamlocal" ) || _access( pFileSystemDLL, 0 ) != 0 ) + { + Q_snprintf( pFileSystemDLL, nMaxLen, "%s%cfilesystem_steam.dll", executablePath, CORRECT_PATH_SEPARATOR ); + bSteam = true; + } +#endif +#elif _LINUX + Q_snprintf( pFileSystemDLL, nMaxLen, "%s%cfilesystem_i486.so", executablePath, CORRECT_PATH_SEPARATOR ); +#else + #error "define a filesystem dll name" +#endif + + return FS_OK; +} + +//----------------------------------------------------------------------------- +// Sets up the steam.dll install path in our PATH env var (so you can then just +// LoadLibrary() on filesystem_steam.dll without having to copy steam.dll anywhere special ) +//----------------------------------------------------------------------------- +FSReturnCode_t FileSystem_SetupSteamInstallPath() +{ +#ifndef _XBOX + CSteamEnvVars steamEnvVars; + char steamInstallPath[MAX_PATH]; + FSReturnCode_t ret = SetSteamInstallPath( steamInstallPath, sizeof( steamInstallPath ), steamEnvVars, true ); + steamEnvVars.m_Path.SetRestoreOriginalValue( false ); // We want to keep the change to the path going forward. + return ret; +#else + return FS_OK; +#endif +} + +//----------------------------------------------------------------------------- +// Sets up the steam environment + gets back the gameinfo.txt path +//----------------------------------------------------------------------------- +FSReturnCode_t FileSystem_SetupSteamEnvironment( CFSSteamSetupInfo &fsInfo ) +{ +#ifndef _XBOX + // First, locate the directory with gameinfo.txt. + FSReturnCode_t ret = LocateGameInfoFile( fsInfo, fsInfo.m_GameInfoPath, sizeof( fsInfo.m_GameInfoPath ) ); + if ( ret != FS_OK ) + return ret; + + CSteamEnvVars steamEnvVars; + if ( fsInfo.m_bSteam ) + { + if ( fsInfo.m_bToolsMode ) + { + // Now, load gameinfo.txt (to make sure it's there) + KeyValues *pMainFile, *pFileSystemInfo, *pSearchPaths; + ret = LoadGameInfoFile( fsInfo.m_GameInfoPath, pMainFile, pFileSystemInfo, pSearchPaths ); + if ( ret != FS_OK ) + return ret; + + // If filesystem_stdio.dll is missing or -steam is specified, then load filesystem_steam.dll. + // There are two command line parameters for Steam: + // 1) -steam (runs Steam in remote filesystem mode; requires Steam backend) + // 2) -steamlocal (runs Steam in local filesystem mode (all content off HDD) + + // Setup all the environment variables related to Steam so filesystem_steam.dll knows how to initialize Steam. + ret = SetupSteamStartupEnvironment( pFileSystemInfo, fsInfo.m_GameInfoPath, steamEnvVars ); + if ( ret != FS_OK ) + return ret; + + steamEnvVars.m_SteamAppId.SetRestoreOriginalValue( false ); // We want to keep the change to the path going forward. + + // We're done with main file + pMainFile->deleteThis(); + } + else if ( fsInfo.m_bSetSteamDLLPath ) + { + // This is used by the engine to automatically set the path to their steam.dll when running the engine, + // so they can debug it without having to copy steam.dll up into their hl2.exe folder. + char steamInstallPath[MAX_PATH]; + ret = SetSteamInstallPath( steamInstallPath, sizeof( steamInstallPath ), steamEnvVars, true ); + steamEnvVars.m_Path.SetRestoreOriginalValue( false ); // We want to keep the change to the path going forward. + } + } +#endif + + return FS_OK; +} + + +//----------------------------------------------------------------------------- +// Loads the file system module +//----------------------------------------------------------------------------- +FSReturnCode_t FileSystem_LoadFileSystemModule( CFSLoadModuleInfo &fsInfo ) +{ + // First, locate the directory with gameinfo.txt. + FSReturnCode_t ret = FileSystem_SetupSteamEnvironment( fsInfo ); + if ( ret != FS_OK ) + return ret; + + void *fs = &fsInfo.m_pFileSystem; + + // Now that the environment is setup, load the filesystem module. + if ( !Sys_LoadInterface( + fsInfo.m_pFileSystemDLLName, + FILESYSTEM_INTERFACE_VERSION, + &fsInfo.m_pModule, + (void**)fs ) ) + { + return SetupFileSystemError( false, FS_UNABLE_TO_INIT, "Can't load %s.", fsInfo.m_pFileSystemDLLName ); + } + + if ( !fsInfo.m_pFileSystem->Connect( fsInfo.m_ConnectFactory ) ) + return SetupFileSystemError( false, FS_UNABLE_TO_INIT, "%s IFileSystem::Connect failed.", fsInfo.m_pFileSystemDLLName ); + + if ( fsInfo.m_pFileSystem->Init() != INIT_OK ) + return SetupFileSystemError( false, FS_UNABLE_TO_INIT, "%s IFileSystem::Init failed.", fsInfo.m_pFileSystemDLLName ); + + return FS_OK; +} + + +//----------------------------------------------------------------------------- +// Mounds a particular steam cache +//----------------------------------------------------------------------------- +#ifndef _XBOX +FSReturnCode_t FileSystem_MountContent( CFSMountContentInfo &mountContentInfo ) +{ + // This part is Steam-only. + if ( mountContentInfo.m_pFileSystem->IsSteam() ) + { + // Find out the "extra app id". This is for tools, which want to mount a base app's filesystem + // like HL2, then mount the SDK content (tools materials and models, etc) in addition. + int nExtraAppId = -1; + if ( mountContentInfo.m_bToolsMode ) + { + FSReturnCode_t ret = GetSteamExtraAppId( mountContentInfo.m_pDirectoryName, &nExtraAppId ); + if ( ret != FS_OK ) + return ret; + } + + // Set our working directory temporarily so Steam can remember it. + // This is what Steam strips off absolute filenames like c:\program files\valve\steam\steamapps\username\sourcesdk + // to get to the relative part of the path. + char baseDir[MAX_PATH], oldWorkingDir[MAX_PATH]; + if ( !FileSystem_GetBaseDir( baseDir, sizeof( baseDir ) ) ) + return SetupFileSystemError( false, FS_INVALID_PARAMETERS, "FileSystem_GetBaseDir failed." ); + + Q_getwd( oldWorkingDir, sizeof( oldWorkingDir ) ); + _chdir( baseDir ); + + // Filesystem_tools needs to add dependencies in here beforehand. + FilesystemMountRetval_t retVal = mountContentInfo.m_pFileSystem->MountSteamContent( nExtraAppId ); + + _chdir( oldWorkingDir ); + + if ( retVal != FILESYSTEM_MOUNT_OK ) + return SetupFileSystemError( true, FS_UNABLE_TO_INIT, "Unable to mount Steam content in the file system" ); + } + + return FileSystem_SetBasePaths( mountContentInfo.m_pFileSystem ); +} +#endif + +void FileSystem_SetErrorMode( FSErrorMode_t errorMode ) +{ + g_FileSystemErrorMode = errorMode; +} + +#ifndef _XBOX +void FileSystem_ClearSteamEnvVars() +{ + CSteamEnvVars envVars; + + // Change the values and don't restore the originals. + envVars.m_SteamAppId.SetValue( "" ); + envVars.m_SteamUserPassphrase.SetValue( "" ); + envVars.m_SteamAppUser.SetValue( "" ); + + envVars.SetRestoreOriginalValue_ALL( false ); +} +#endif + +#endif // !_STATIC_LINKED || _SHARED_LIB + + +//----------------------------------------------------------------------------- +// Adds the platform folder to the search path. +//----------------------------------------------------------------------------- +void FileSystem_AddSearchPath_Platform( IFileSystem *pFileSystem, const char *szGameInfoPath ) +{ + char platform[MAX_PATH]; + if ( pFileSystem->IsSteam() ) + { + // Steam doesn't support relative paths + Q_strncpy( platform, "platform", MAX_PATH ); + } + else + { + Q_strncpy( platform, szGameInfoPath, MAX_PATH ); + Q_StripTrailingSlash( platform ); + Q_strncat( platform, "/../platform", MAX_PATH, MAX_PATH ); + } + + pFileSystem->AddSearchPath( platform, "PLATFORM" ); +} diff --git a/public/iclient.h b/public/iclient.h index 5b8b5677..5dfb0b1e 100644 --- a/public/iclient.h +++ b/public/iclient.h @@ -12,10 +12,10 @@ #include #include "tier0/platform.h" -#include "userid.h" class IServer; class INetMessage; +struct USERID_t; abstract_class IClient : public INetChannelHandler { diff --git a/public/igameevents.h b/public/igameevents.h index 5c72d078..50be8517 100644 --- a/public/igameevents.h +++ b/public/igameevents.h @@ -1,194 +1,191 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#if !defined( IGAMEEVENTS_H ) -#define IGAMEEVENTS_H -#ifdef _WIN32 -#pragma once -#endif - -#include "tier1/interface.h" - -#define INTERFACEVERSION_GAMEEVENTSMANAGER "GAMEEVENTSMANAGER001" // old game event manager, don't use it! -#define INTERFACEVERSION_GAMEEVENTSMANAGER2 "GAMEEVENTSMANAGER002" // new game event manager, - -//----------------------------------------------------------------------------- -// Purpose: Engine interface into global game event management -//----------------------------------------------------------------------------- - -/* - -The GameEventManager keeps track and fires of all global game events. Game events -are fired by game.dll for events like player death or team wins. Each event has a -unique name and comes with a KeyValue structure providing informations about this -event. Some events are generated also by the engine. - -Events are networked to connected clients and invoked there to. Therefore you -have to specify all data fields and there data types in an public resource -file which is parsed by server and broadcasted to it's clients. A typical game -event is defined like this: - - "game_start" // a new game starts - { - "roundslimit" "long" // max round - "timelimit" "long" // time limit - "fraglimit" "long" // frag limit - "objective" "string" // round objective - } - -All events must have unique names (case sensitive) and may have a list -of data fields. each data field must specify a data type, so the engine -knows how to serialize/unserialize that event for network transmission. -Valid data types are string, float, long, short, byte & bool. If a -data field should not be broadcasted to clients, use the type "local". -*/ - - -#define MAX_EVENT_NAME_LENGTH 32 // max game event name length -#define MAX_EVENT_BITS 9 // max bits needed for an event index -#define MAX_EVENT_NUMBER (1<name ) ) - return slot->type; - } - - Assert( !"Interpolator_InterpolatorForName failed!!!" ); - return INTERPOLATE_DEFAULT; -} - -char const *Interpolator_NameForInterpolator( int type, bool printname ) -{ - int i = (int)type; - int c = ARRAYSIZE( g_InterpolatorNameMap ); - if ( i < 0 || i >= c ) - { - Assert( "!Interpolator_NameForInterpolator: bogus type!" ); - // returns "unspecified!!!"; - return printname ? g_InterpolatorNameMap[ 0 ].printname : g_InterpolatorNameMap[ 0 ].name; - } - - return printname ? g_InterpolatorNameMap[ i ].printname : g_InterpolatorNameMap[ i ].name; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -struct CurveNameMap_t -{ - int type; - int hotkey; -}; - -static CurveNameMap_t g_CurveNameMap[] = -{ - { CURVE_CATMULL_ROM_TO_CATMULL_ROM, '1' }, - { CURVE_EASE_IN_TO_EASE_OUT, '2' }, - { CURVE_EASE_IN_TO_EASE_IN, '3' }, - { CURVE_EASE_OUT_TO_EASE_OUT, '4' }, - { CURVE_BSPLINE_TO_BSPLINE, '5' }, - { CURVE_LINEAR_INTERP_TO_LINEAR_INTERP, '6' }, - { CURVE_KOCHANEK_BARTELS_TO_KOCHANEK_BARTELS, '7' }, - { CURVE_KOCHANEK_BARTELS_EARLY_TO_KOCHANEK_BARTELS_EARLY, '8' }, - { CURVE_KOCHANEK_BARTELS_LATE_TO_KOCHANEK_BARTELS_LATE, '9' }, - { CURVE_SIMPLE_CUBIC_TO_SIMPLE_CUBIC, '0' }, -}; - -// Turn enum into string and vice versa -int Interpolator_CurveTypeForName( const char *name ) -{ - char sz[ 128 ]; - Q_strncpy( sz, name, sizeof( sz ) ); - - int leftcurve = 0; - int rightcurve = 0; - - int skip = Q_strlen( "curve_" ); - - if ( !Q_strnicmp( sz, "curve_", skip ) ) - { - char *p = sz + skip; - char *second = Q_stristr( p, "_to_curve_" ); - - char save = *second; - *second = 0; - - leftcurve = Interpolator_InterpolatorForName( p ); - - *second = save; - - p = second + Q_strlen( "_to_curve_" ); - - rightcurve = Interpolator_InterpolatorForName( p ); - } - - return MAKE_CURVE_TYPE( leftcurve, rightcurve ); -} - -const char *Interpolator_NameForCurveType( int type, bool printname ) -{ - static char outname[ 256 ]; - - int leftside = GET_LEFT_CURVE( type ); - int rightside = GET_RIGHT_CURVE( type ); - - if ( !printname ) - { - Q_snprintf( outname, sizeof( outname ), "curve_%s_to_curve_%s", - Interpolator_NameForInterpolator( leftside, printname ), - Interpolator_NameForInterpolator( rightside, printname ) ); - } - else - { - Q_snprintf( outname, sizeof( outname ), "%s <-> %s", - Interpolator_NameForInterpolator( leftside, printname ), - Interpolator_NameForInterpolator( rightside, printname ) ); - } - - return outname; -} - -void Interpolator_CurveInterpolatorsForType( int type, int& inbound, int& outbound ) -{ - inbound = GET_LEFT_CURVE( type ); - outbound = GET_RIGHT_CURVE( type ); -} - -int Interpolator_CurveTypeForHotkey( int key ) -{ - int c = ARRAYSIZE( g_CurveNameMap ); - for ( int i = 0; i < c; ++i ) - { - CurveNameMap_t *slot = &g_CurveNameMap[ i ]; - if ( slot->hotkey == key ) - return slot->type; - } - - return -1; -} - -void Interpolator_GetKochanekBartelsParams( int interpolationType, float& tension, float& bias, float& continuity ) -{ - switch ( interpolationType ) - { - default: - tension = 0.0f; - bias = 0.0f; - continuity = 0.0f; - Assert( 0 ); - break; - case INTERPOLATE_KOCHANEK_BARTELS: - tension = 0.77f; - bias = 0.0f; - continuity = 0.77f; - break; - case INTERPOLATE_KOCHANEK_BARTELS_EARLY: - tension = 0.77f; - bias = -1.0f; - continuity = 0.77f; - break; - case INTERPOLATE_KOCHANEK_BARTELS_LATE: - tension = 0.77f; - bias = 1.0f; - continuity = 0.77f; - break; - } -} - -void Interpolator_CurveInterpolate( int interpolationType, - const Vector &vPre, - const Vector &vStart, - const Vector &vEnd, - const Vector &vNext, - float f, - Vector &vOut ) -{ - vOut.Init(); - - switch ( interpolationType ) - { - default: - Warning( "Unknown interpolation type %d\n", - (int)interpolationType ); - // break; // Fall through and use catmull_rom as default - case INTERPOLATE_DEFAULT: - case INTERPOLATE_CATMULL_ROM_NORMALIZEX: - Catmull_Rom_Spline_NormalizeX( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_CATMULL_ROM: - Catmull_Rom_Spline( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_CATMULL_ROM_NORMALIZE: - Catmull_Rom_Spline_Normalize( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_CATMULL_ROM_TANGENT: - Catmull_Rom_Spline_Tangent( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_EASE_IN: - { - f = sin( M_PI * f * 0.5f ); - // Fixme, since this ignores vPre and vNext we could omit computing them aove - VectorLerp( vStart, vEnd, f, vOut ); - } - break; - case INTERPOLATE_EASE_OUT: - { - f = 1.0f - sin( M_PI * f * 0.5f + 0.5f * M_PI ); - // Fixme, since this ignores vPre and vNext we could omit computing them aove - VectorLerp( vStart, vEnd, f, vOut ); - } - break; - case INTERPOLATE_EASE_INOUT: - { - f = SimpleSpline( f ); - // Fixme, since this ignores vPre and vNext we could omit computing them aove - VectorLerp( vStart, vEnd, f, vOut ); - } - break; - case INTERPOLATE_LINEAR_INTERP: - // Fixme, since this ignores vPre and vNext we could omit computing them aove - VectorLerp( vStart, vEnd, f, vOut ); - break; - case INTERPOLATE_KOCHANEK_BARTELS: - case INTERPOLATE_KOCHANEK_BARTELS_EARLY: - case INTERPOLATE_KOCHANEK_BARTELS_LATE: - { - float t, b, c; - Interpolator_GetKochanekBartelsParams( interpolationType, t, b, c ); - Kochanek_Bartels_Spline_NormalizeX - ( - t, b, c, - vPre, - vStart, - vEnd, - vNext, - f, - vOut - ); - } - break; - case INTERPOLATE_SIMPLE_CUBIC: - Cubic_Spline_NormalizeX( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_BSPLINE: - BSpline( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_EXPONENTIAL_DECAY: - { - float dt = vEnd.x - vStart.x; - if ( dt > 0.0f ) - { - float val = 1.0f - ExponentialDecay( 0.001, dt, f * dt ); - vOut.y = vStart.y + val * ( vEnd.y - vStart.y ); - } - else - { - vOut.y = vStart.y; - } - } - break; - case INTERPOLATE_HOLD: - { - vOut.y = vStart.y; - } - break; - } -} - -void Interpolator_CurveInterpolate_NonNormalized( int interpolationType, - const Vector &vPre, - const Vector &vStart, - const Vector &vEnd, - const Vector &vNext, - float f, - Vector &vOut ) -{ - vOut.Init(); - - switch ( interpolationType ) - { - default: - Warning( "Unknown interpolation type %d\n", - (int)interpolationType ); - // break; // Fall through and use catmull_rom as default - case INTERPOLATE_CATMULL_ROM_NORMALIZEX: - case INTERPOLATE_DEFAULT: - case INTERPOLATE_CATMULL_ROM: - case INTERPOLATE_CATMULL_ROM_NORMALIZE: - case INTERPOLATE_CATMULL_ROM_TANGENT: - Catmull_Rom_Spline( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_EASE_IN: - { - f = sin( M_PI * f * 0.5f ); - // Fixme, since this ignores vPre and vNext we could omit computing them aove - VectorLerp( vStart, vEnd, f, vOut ); - } - break; - case INTERPOLATE_EASE_OUT: - { - f = 1.0f - sin( M_PI * f * 0.5f + 0.5f * M_PI ); - // Fixme, since this ignores vPre and vNext we could omit computing them aove - VectorLerp( vStart, vEnd, f, vOut ); - } - break; - case INTERPOLATE_EASE_INOUT: - { - f = SimpleSpline( f ); - // Fixme, since this ignores vPre and vNext we could omit computing them aove - VectorLerp( vStart, vEnd, f, vOut ); - } - break; - case INTERPOLATE_LINEAR_INTERP: - // Fixme, since this ignores vPre and vNext we could omit computing them aove - VectorLerp( vStart, vEnd, f, vOut ); - break; - case INTERPOLATE_KOCHANEK_BARTELS: - case INTERPOLATE_KOCHANEK_BARTELS_EARLY: - case INTERPOLATE_KOCHANEK_BARTELS_LATE: - { - float t, b, c; - Interpolator_GetKochanekBartelsParams( interpolationType, t, b, c ); - Kochanek_Bartels_Spline - ( - t, b, c, - vPre, - vStart, - vEnd, - vNext, - f, - vOut - ); - } - break; - case INTERPOLATE_SIMPLE_CUBIC: - Cubic_Spline( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_BSPLINE: - BSpline( - vPre, - vStart, - vEnd, - vNext, - f, - vOut ); - break; - case INTERPOLATE_EXPONENTIAL_DECAY: - { - float dt = vEnd.x - vStart.x; - if ( dt > 0.0f ) - { - float val = 1.0f - ExponentialDecay( 0.001, dt, f * dt ); - vOut.y = vStart.y + val * ( vEnd.y - vStart.y ); - } - else - { - vOut.y = vStart.y; - } - } - break; - case INTERPOLATE_HOLD: - { - vOut.y = vStart.y; - } - break; - } -} \ No newline at end of file +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= +#include "basetypes.h" +#include "vstdlib/strtools.h" +#include "interpolatortypes.h" +#include "tier0/dbg.h" +#include "mathlib.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +struct InterpolatorNameMap_t +{ + int type; + char const *name; + char const *printname; +}; + +static InterpolatorNameMap_t g_InterpolatorNameMap[] = +{ + { INTERPOLATE_DEFAULT, "default", "Default" }, + { INTERPOLATE_CATMULL_ROM_NORMALIZEX, "catmullrom_normalize_x", "Catmull-Rom (Norm X)" }, + { INTERPOLATE_EASE_IN, "easein", "Ease In" }, + { INTERPOLATE_EASE_OUT, "easeout", "Ease Out" }, + { INTERPOLATE_EASE_INOUT, "easeinout", "Ease In/Out" }, + { INTERPOLATE_BSPLINE, "bspline", "B-Spline" }, + { INTERPOLATE_LINEAR_INTERP, "linear_interp", "Linear Interp." }, + { INTERPOLATE_KOCHANEK_BARTELS, "kochanek", "Kochanek-Bartels" }, + { INTERPOLATE_KOCHANEK_BARTELS_EARLY, "kochanek_early", "Kochanek-Bartels Early" }, + { INTERPOLATE_KOCHANEK_BARTELS_LATE, "kochanek_late", "Kochanek-Bartels Late" }, + { INTERPOLATE_SIMPLE_CUBIC, "simple_cubic", "Simple Cubic" }, + { INTERPOLATE_CATMULL_ROM, "catmullrom", "Catmull-Rom" }, + { INTERPOLATE_CATMULL_ROM_NORMALIZE, "catmullrom_normalize", "Catmull-Rom (Norm)" }, + { INTERPOLATE_CATMULL_ROM_TANGENT, "catmullrom_tangent", "Catmull-Rom (Tangent)" }, + { INTERPOLATE_EXPONENTIAL_DECAY, "exponential_decay", "Exponential Decay" }, + { INTERPOLATE_HOLD, "hold", "Hold" }, +}; + +int Interpolator_InterpolatorForName( char const *name ) +{ + for ( int i = 0; i < NUM_INTERPOLATE_TYPES; ++i ) + { + InterpolatorNameMap_t *slot = &g_InterpolatorNameMap[ i ]; + if ( !Q_stricmp( name, slot->name ) ) + return slot->type; + } + + Assert( !"Interpolator_InterpolatorForName failed!!!" ); + return INTERPOLATE_DEFAULT; +} + +char const *Interpolator_NameForInterpolator( int type, bool printname ) +{ + int i = (int)type; + int c = ARRAYSIZE( g_InterpolatorNameMap ); + if ( i < 0 || i >= c ) + { + Assert( "!Interpolator_NameForInterpolator: bogus type!" ); + // returns "unspecified!!!"; + return printname ? g_InterpolatorNameMap[ 0 ].printname : g_InterpolatorNameMap[ 0 ].name; + } + + return printname ? g_InterpolatorNameMap[ i ].printname : g_InterpolatorNameMap[ i ].name; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CurveNameMap_t +{ + int type; + int hotkey; +}; + +static CurveNameMap_t g_CurveNameMap[] = +{ + { CURVE_CATMULL_ROM_TO_CATMULL_ROM, '1' }, + { CURVE_EASE_IN_TO_EASE_OUT, '2' }, + { CURVE_EASE_IN_TO_EASE_IN, '3' }, + { CURVE_EASE_OUT_TO_EASE_OUT, '4' }, + { CURVE_BSPLINE_TO_BSPLINE, '5' }, + { CURVE_LINEAR_INTERP_TO_LINEAR_INTERP, '6' }, + { CURVE_KOCHANEK_BARTELS_TO_KOCHANEK_BARTELS, '7' }, + { CURVE_KOCHANEK_BARTELS_EARLY_TO_KOCHANEK_BARTELS_EARLY, '8' }, + { CURVE_KOCHANEK_BARTELS_LATE_TO_KOCHANEK_BARTELS_LATE, '9' }, + { CURVE_SIMPLE_CUBIC_TO_SIMPLE_CUBIC, '0' }, +}; + +// Turn enum into string and vice versa +int Interpolator_CurveTypeForName( const char *name ) +{ + char sz[ 128 ]; + Q_strncpy( sz, name, sizeof( sz ) ); + + int leftcurve = 0; + int rightcurve = 0; + + int skip = Q_strlen( "curve_" ); + + if ( !Q_strnicmp( sz, "curve_", skip ) ) + { + char *p = sz + skip; + char *second = Q_stristr( p, "_to_curve_" ); + + char save = *second; + *second = 0; + + leftcurve = Interpolator_InterpolatorForName( p ); + + *second = save; + + p = second + Q_strlen( "_to_curve_" ); + + rightcurve = Interpolator_InterpolatorForName( p ); + } + + return MAKE_CURVE_TYPE( leftcurve, rightcurve ); +} + +const char *Interpolator_NameForCurveType( int type, bool printname ) +{ + static char outname[ 256 ]; + + int leftside = GET_LEFT_CURVE( type ); + int rightside = GET_RIGHT_CURVE( type ); + + if ( !printname ) + { + Q_snprintf( outname, sizeof( outname ), "curve_%s_to_curve_%s", + Interpolator_NameForInterpolator( leftside, printname ), + Interpolator_NameForInterpolator( rightside, printname ) ); + } + else + { + Q_snprintf( outname, sizeof( outname ), "%s <-> %s", + Interpolator_NameForInterpolator( leftside, printname ), + Interpolator_NameForInterpolator( rightside, printname ) ); + } + + return outname; +} + +void Interpolator_CurveInterpolatorsForType( int type, int& inbound, int& outbound ) +{ + inbound = GET_LEFT_CURVE( type ); + outbound = GET_RIGHT_CURVE( type ); +} + +int Interpolator_CurveTypeForHotkey( int key ) +{ + int c = ARRAYSIZE( g_CurveNameMap ); + for ( int i = 0; i < c; ++i ) + { + CurveNameMap_t *slot = &g_CurveNameMap[ i ]; + if ( slot->hotkey == key ) + return slot->type; + } + + return -1; +} + +void Interpolator_GetKochanekBartelsParams( int interpolationType, float& tension, float& bias, float& continuity ) +{ + switch ( interpolationType ) + { + default: + tension = 0.0f; + bias = 0.0f; + continuity = 0.0f; + Assert( 0 ); + break; + case INTERPOLATE_KOCHANEK_BARTELS: + tension = 0.77f; + bias = 0.0f; + continuity = 0.77f; + break; + case INTERPOLATE_KOCHANEK_BARTELS_EARLY: + tension = 0.77f; + bias = -1.0f; + continuity = 0.77f; + break; + case INTERPOLATE_KOCHANEK_BARTELS_LATE: + tension = 0.77f; + bias = 1.0f; + continuity = 0.77f; + break; + } +} + +void Interpolator_CurveInterpolate( int interpolationType, + const Vector &vPre, + const Vector &vStart, + const Vector &vEnd, + const Vector &vNext, + float f, + Vector &vOut ) +{ + vOut.Init(); + + switch ( interpolationType ) + { + default: + Warning( "Unknown interpolation type %d\n", + (int)interpolationType ); + // break; // Fall through and use catmull_rom as default + case INTERPOLATE_DEFAULT: + case INTERPOLATE_CATMULL_ROM_NORMALIZEX: + Catmull_Rom_Spline_NormalizeX( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_CATMULL_ROM: + Catmull_Rom_Spline( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_CATMULL_ROM_NORMALIZE: + Catmull_Rom_Spline_Normalize( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_CATMULL_ROM_TANGENT: + Catmull_Rom_Spline_Tangent( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_EASE_IN: + { + f = sin( M_PI * f * 0.5f ); + // Fixme, since this ignores vPre and vNext we could omit computing them aove + VectorLerp( vStart, vEnd, f, vOut ); + } + break; + case INTERPOLATE_EASE_OUT: + { + f = 1.0f - sin( M_PI * f * 0.5f + 0.5f * M_PI ); + // Fixme, since this ignores vPre and vNext we could omit computing them aove + VectorLerp( vStart, vEnd, f, vOut ); + } + break; + case INTERPOLATE_EASE_INOUT: + { + f = SimpleSpline( f ); + // Fixme, since this ignores vPre and vNext we could omit computing them aove + VectorLerp( vStart, vEnd, f, vOut ); + } + break; + case INTERPOLATE_LINEAR_INTERP: + // Fixme, since this ignores vPre and vNext we could omit computing them aove + VectorLerp( vStart, vEnd, f, vOut ); + break; + case INTERPOLATE_KOCHANEK_BARTELS: + case INTERPOLATE_KOCHANEK_BARTELS_EARLY: + case INTERPOLATE_KOCHANEK_BARTELS_LATE: + { + float t, b, c; + Interpolator_GetKochanekBartelsParams( interpolationType, t, b, c ); + Kochanek_Bartels_Spline_NormalizeX + ( + t, b, c, + vPre, + vStart, + vEnd, + vNext, + f, + vOut + ); + } + break; + case INTERPOLATE_SIMPLE_CUBIC: + Cubic_Spline_NormalizeX( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_BSPLINE: + BSpline( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_EXPONENTIAL_DECAY: + { + float dt = vEnd.x - vStart.x; + if ( dt > 0.0f ) + { + float val = 1.0f - ExponentialDecay( 0.001, dt, f * dt ); + vOut.y = vStart.y + val * ( vEnd.y - vStart.y ); + } + else + { + vOut.y = vStart.y; + } + } + break; + case INTERPOLATE_HOLD: + { + vOut.y = vStart.y; + } + break; + } +} + +void Interpolator_CurveInterpolate_NonNormalized( int interpolationType, + const Vector &vPre, + const Vector &vStart, + const Vector &vEnd, + const Vector &vNext, + float f, + Vector &vOut ) +{ + vOut.Init(); + + switch ( interpolationType ) + { + default: + Warning( "Unknown interpolation type %d\n", + (int)interpolationType ); + // break; // Fall through and use catmull_rom as default + case INTERPOLATE_CATMULL_ROM_NORMALIZEX: + case INTERPOLATE_DEFAULT: + case INTERPOLATE_CATMULL_ROM: + case INTERPOLATE_CATMULL_ROM_NORMALIZE: + case INTERPOLATE_CATMULL_ROM_TANGENT: + Catmull_Rom_Spline( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_EASE_IN: + { + f = sin( M_PI * f * 0.5f ); + // Fixme, since this ignores vPre and vNext we could omit computing them aove + VectorLerp( vStart, vEnd, f, vOut ); + } + break; + case INTERPOLATE_EASE_OUT: + { + f = 1.0f - sin( M_PI * f * 0.5f + 0.5f * M_PI ); + // Fixme, since this ignores vPre and vNext we could omit computing them aove + VectorLerp( vStart, vEnd, f, vOut ); + } + break; + case INTERPOLATE_EASE_INOUT: + { + f = SimpleSpline( f ); + // Fixme, since this ignores vPre and vNext we could omit computing them aove + VectorLerp( vStart, vEnd, f, vOut ); + } + break; + case INTERPOLATE_LINEAR_INTERP: + // Fixme, since this ignores vPre and vNext we could omit computing them aove + VectorLerp( vStart, vEnd, f, vOut ); + break; + case INTERPOLATE_KOCHANEK_BARTELS: + case INTERPOLATE_KOCHANEK_BARTELS_EARLY: + case INTERPOLATE_KOCHANEK_BARTELS_LATE: + { + float t, b, c; + Interpolator_GetKochanekBartelsParams( interpolationType, t, b, c ); + Kochanek_Bartels_Spline + ( + t, b, c, + vPre, + vStart, + vEnd, + vNext, + f, + vOut + ); + } + break; + case INTERPOLATE_SIMPLE_CUBIC: + Cubic_Spline( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_BSPLINE: + BSpline( + vPre, + vStart, + vEnd, + vNext, + f, + vOut ); + break; + case INTERPOLATE_EXPONENTIAL_DECAY: + { + float dt = vEnd.x - vStart.x; + if ( dt > 0.0f ) + { + float val = 1.0f - ExponentialDecay( 0.001, dt, f * dt ); + vOut.y = vStart.y + val * ( vEnd.y - vStart.y ); + } + else + { + vOut.y = vStart.y; + } + } + break; + case INTERPOLATE_HOLD: + { + vOut.y = vStart.y; + } + break; + } +} diff --git a/public/kevvaluescompiler.cpp b/public/kevvaluescompiler.cpp index 432e1afb..08f7dab7 100644 --- a/public/kevvaluescompiler.cpp +++ b/public/kevvaluescompiler.cpp @@ -1,436 +1,436 @@ -//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= -// -// Purpose: -// -//============================================================================= -#include "keyvaluescompiler.h" -#include "FileSystem.h" -#include "tier1/KeyValues.h" - -extern IFileSystem *g_pFullFileSystem; - -bool CRunTimeKeyValuesStringTable::ReadStringTable( int numStrings, CUtlBuffer& buf ) -{ - Assert( m_Strings.Count() == 0 ); - - CUtlVector< int > offsets; - offsets.EnsureCapacity( numStrings ); - - offsets.CopyArray( (int *)( buf.PeekGet() ), numStrings ); - - // Skip over data - buf.SeekGet( CUtlBuffer::SEEK_HEAD, buf.TellGet() + numStrings * sizeof( int ) ); - - int stringSize = buf.GetInt(); - - // Read in the string table - m_Strings.EnsureCapacity( numStrings ); - int i; - for ( i = 0 ; i < numStrings; ++i ) - { - m_Strings.AddToTail( (const char *)buf.PeekGet( offsets[ i ] ) ); - } - - buf.SeekGet( CUtlBuffer::SEEK_HEAD, buf.TellGet() + stringSize ); - - return true; -} - -void CCompiledKeyValuesWriter::BuildKVData_R( KeyValues *kv, int parent ) -{ - // Add self - KVInfo_t info; - info.key = m_StringTable.AddString( kv->GetName() ); - info.value = m_StringTable.AddString( kv->GetString() ); - - info.SetSubTree( kv->GetFirstSubKey() != NULL ? true : false ); - info.SetParent( parent ); - - int newParent = m_Data.AddToTail( info ); - - // Then add children - for ( KeyValues *sub = kv->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) - { - BuildKVData_R( sub, newParent ); - } - - // Then add peers - if ( parent == -1 ) - { - if ( kv->GetNextKey() ) - { - BuildKVData_R( kv->GetNextKey(), parent ); - } - } -} - -void CCompiledKeyValuesWriter::Describe( const KVFile_t& file ) -{ - Msg( "file( %s )\n", m_StringTable.String( file.filename ) ); - - int c = file.numElements; - for ( int i = 0; i < c; ++i ) - { - KVInfo_t &info = m_Data[ file.firstElement + i ]; - if ( info.IsSubTree() ) - { - Msg( "%d: %s -> subtree at parent %i\n", - file.firstElement + i, - m_StringTable.String( info.key ), - info.GetParent() ); - } - else - { - Msg( "%d: %s -> %s at parent %i\n", - file.firstElement + i, - m_StringTable.String( info.key ), - m_StringTable.String( info.value ), - info.GetParent() ); - } - } -} - -void CCompiledKeyValuesWriter::AppendKeyValuesFile( char const *filename ) -{ - KVFile_t kvf; - kvf.filename = m_StringTable.AddString( filename ); - kvf.firstElement = m_Data.Count(); - - KeyValues *kv = new KeyValues( filename ); - if ( kv->LoadFromFile( g_pFullFileSystem, filename ) ) - { - // Add to dictionary - // do a depth first traversal of the keyvalues - BuildKVData_R( kv, -1 ); - } - kv->deleteThis(); - - kvf.numElements = m_Data.Count() - kvf.firstElement; - -// Describe( kvf ); - - m_Files.AddToTail( kvf ); -} - -void CCompiledKeyValuesWriter::WriteData( CUtlBuffer& buf ) -{ - int c = m_Data.Count(); - buf.PutInt( c ); - for ( int i = 0; i < c; ++i ) - { - KVInfo_t &info = m_Data[ i ]; - buf.PutShort( info.key ); - buf.PutShort( info.value ); - buf.PutShort( info.GetParent() ); - buf.PutChar( info.IsSubTree() ? 1 : 0 ); - } -} - -void CCompiledKeyValuesWriter::WriteFiles( CUtlBuffer &buf ) -{ - int c = m_Files.Count(); - buf.PutInt( c ); - for ( int i = 0; i < c; ++i ) - { - KVFile_t &file = m_Files[ i ]; - buf.PutShort( file.filename ); - buf.PutShort( file.firstElement ); - buf.PutShort( file.numElements ); - } -} - -void CCompiledKeyValuesWriter::WriteStringTable( CUtlBuffer& buf ) -{ - int i; - CUtlVector< int > offsets; - - CUtlBuffer stringBuffer; - - offsets.AddToTail( stringBuffer.TellPut() ); - - stringBuffer.PutString( "" ); - // save all the rest - int c = m_StringTable.GetNumStrings(); - for ( i = 1; i < c; i++) - { - offsets.AddToTail( stringBuffer.TellPut() ); - stringBuffer.PutString( m_StringTable.String( i ) ); - } - - buf.Put( offsets.Base(), offsets.Count() * sizeof( int ) ); - - buf.PutInt( stringBuffer.TellPut() ); - buf.Put( stringBuffer.Base(), stringBuffer.TellPut() ); -} - -void CCompiledKeyValuesWriter::WriteFile( char const *outfile ) -{ - CUtlBuffer buf; - - // Write the data file out - KVHeader_t header; - header.fileid = COMPILED_KEYVALUES_ID; - header.version = COMPILED_KEYVALUES_VERSION; - header.numStrings = m_StringTable.GetNumStrings(); - - buf.Put( &header, sizeof( header ) ); - - WriteStringTable( buf ); - WriteData( buf ); - WriteFiles( buf ); - - g_pFullFileSystem->WriteFile( outfile, NULL, buf ); -} - -CCompiledKeyValuesReader::CCompiledKeyValuesReader() - : m_Dict( 0, 0, FileInfo_t::Less ) -{ -} - -int CCompiledKeyValuesReader::First() const -{ - return m_Dict.FirstInorder(); -} - -int CCompiledKeyValuesReader::Next( int i ) const -{ - return m_Dict.NextInorder( i ); -} - -int CCompiledKeyValuesReader::InvalidIndex() const -{ - return m_Dict.InvalidIndex(); -} - -void CCompiledKeyValuesReader::GetFileName( int index, char *buf, size_t bufsize ) -{ - Assert( buf ); - buf[ 0 ] = 0; - FileNameHandle_t& handle = m_Dict[ index ].hFile; - g_pFullFileSystem->String( handle, buf, bufsize ); -} - -bool CCompiledKeyValuesReader::LoadFile( char const *filename ) -{ - int i; - m_LoadBuffer.Purge(); - - g_pFullFileSystem->ReadFile( filename, NULL, m_LoadBuffer ); - - KVHeader_t header; - m_LoadBuffer.Get( &header, sizeof( header ) ); - - if ( header.fileid != COMPILED_KEYVALUES_ID ) - { - return false; - } - - if ( header.version != COMPILED_KEYVALUES_VERSION ) - { - return false; - } - - if ( !m_StringTable.ReadStringTable( header.numStrings, m_LoadBuffer ) ) - { - return false; - } - - // Now parse the data - int dataCount = m_LoadBuffer.GetInt(); - m_Data.EnsureCapacity( dataCount ); - for ( i = 0; i < dataCount; ++i ) - { - KVInfo_t info; - info.key = m_LoadBuffer.GetShort(); - info.value = m_LoadBuffer.GetShort(); - info.SetParent( m_LoadBuffer.GetShort() ); - info.SetSubTree( m_LoadBuffer.GetChar() == 1 ? true : false ); - m_Data.AddToTail( info ); - } - - int fileCount = m_LoadBuffer.GetInt(); - for ( i = 0; i < fileCount; ++i ) - { - FileInfo_t kvf; - short fileNameString = m_LoadBuffer.GetShort(); - - kvf.hFile = g_pFullFileSystem->FindOrAddFileName( m_StringTable.Lookup( fileNameString ) ); - kvf.nFirstIndex = m_LoadBuffer.GetShort(); - kvf.nCount = m_LoadBuffer.GetShort(); - - m_Dict.Insert( kvf ); - } - - return true; -} - -struct CreateHelper_t -{ - int index; - KeyValues *kv; - KeyValues *tail; - - static bool Less( const CreateHelper_t& lhs, const CreateHelper_t& rhs ) - { - return lhs.index < rhs.index; - } -}; - -KeyValues *CCompiledKeyValuesReader::CreateFromData( const FileInfo_t& info ) -{ - KeyValues *head = new KeyValues( "" ); - if ( CreateInPlaceFromData( *head, info ) ) - { - return head; - } - else - { - head->deleteThis(); - return NULL; - } -} - -bool CCompiledKeyValuesReader::CreateInPlaceFromData( KeyValues& head, const FileInfo_t& info ) -{ - int first = info.nFirstIndex; - int num = info.nCount; - - KeyValues *root = NULL; - KeyValues *tail = NULL; - - CUtlRBTree< CreateHelper_t, int > helper( 0, 0, CreateHelper_t::Less ); - - for ( int i = 0; i < num; ++i ) - { - int offset = first + i; - KVInfo_t& info = m_Data[ offset ]; - - if ( info.GetParent() != -1 ) - { - CreateHelper_t search; - search.index = info.GetParent(); - int idx = helper.Find( search ); - if ( idx == helper.InvalidIndex() ) - { - return NULL; - } - - KeyValues *parent = helper[ idx ].kv; - Assert( parent ); - - KeyValues *sub = new KeyValues( m_StringTable.Lookup( info.key ) ); - - if ( !info.IsSubTree() ) - { - sub->SetStringValue(m_StringTable.Lookup( info.value ) ); - } - - if ( !parent->GetFirstSubKey() ) - { - parent->AddSubKey( sub ); - } - else - { - KeyValues *last = helper[ idx ].tail; - last->SetNextKey( sub ); - } - - helper[ idx ].tail = sub; - - CreateHelper_t insert; - insert.index = offset; - insert.kv = sub; - insert.tail = NULL; - helper.Insert( insert ); - } - else - { - if ( !root ) - { - root = &head; - root->SetName( m_StringTable.Lookup( info.key ) ); - tail = root; - - CreateHelper_t insert; - insert.index = offset; - insert.kv = root; - insert.tail = NULL; - helper.Insert( insert ); - } - else - { - CreateHelper_t insert; - insert.index = offset; - insert.kv = new KeyValues( m_StringTable.Lookup( info.key ) ); - insert.tail = NULL; - helper.Insert( insert ); - - tail->SetNextKey( insert.kv ); - tail = insert.kv; - } - } - } - return true; -} - - -bool CCompiledKeyValuesReader::InstanceInPlace( KeyValues& head, char const *kvfilename ) -{ - char sz[ 512 ]; - Q_strncpy( sz, kvfilename, sizeof( sz ) ); - Q_FixSlashes( sz ); - - FileInfo_t search; - search.hFile = g_pFullFileSystem->FindOrAddFileName( sz ); - - int idx = m_Dict.Find( search ); - if ( idx == m_Dict.InvalidIndex() ) - { - return false; - } - - const FileInfo_t& info = m_Dict[ idx ]; - - return CreateInPlaceFromData( head, info ); -} - -KeyValues *CCompiledKeyValuesReader::Instance( char const *kvfilename ) -{ - char sz[ 512 ]; - Q_strncpy( sz, kvfilename, sizeof( sz ) ); - Q_FixSlashes( sz ); - - FileInfo_t search; - search.hFile = g_pFullFileSystem->FindOrAddFileName( sz ); - - int idx = m_Dict.Find( search ); - if ( idx == m_Dict.InvalidIndex() ) - { - return NULL; - } - - const FileInfo_t& info = m_Dict[ idx ]; - - return CreateFromData( info ); -} - -bool CCompiledKeyValuesReader::LookupKeyValuesRootKeyName( char const *kvfilename, char *outbuf, size_t bufsize ) -{ - char sz[ 512 ]; - Q_strncpy( sz, kvfilename, sizeof( sz ) ); - Q_FixSlashes( sz ); - - FileInfo_t search; - search.hFile = g_pFullFileSystem->FindOrAddFileName( sz ); - - int idx = m_Dict.Find( search ); - if ( idx == m_Dict.InvalidIndex() ) - { - return false; - } - - const FileInfo_t& info = m_Dict[ idx ]; - - Q_strncpy( outbuf, m_StringTable.Lookup( m_Data[ info.nFirstIndex ].key ), bufsize ); - return true; -} +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= +#include "keyvaluescompiler.h" +#include "filesystem.h" +#include "tier1/KeyValues.h" + +extern IFileSystem *g_pFullFileSystem; + +bool CRunTimeKeyValuesStringTable::ReadStringTable( int numStrings, CUtlBuffer& buf ) +{ + Assert( m_Strings.Count() == 0 ); + + CUtlVector< int > offsets; + offsets.EnsureCapacity( numStrings ); + + offsets.CopyArray( (int *)( buf.PeekGet() ), numStrings ); + + // Skip over data + buf.SeekGet( CUtlBuffer::SEEK_HEAD, buf.TellGet() + numStrings * sizeof( int ) ); + + int stringSize = buf.GetInt(); + + // Read in the string table + m_Strings.EnsureCapacity( numStrings ); + int i; + for ( i = 0 ; i < numStrings; ++i ) + { + m_Strings.AddToTail( (const char *)buf.PeekGet( offsets[ i ] ) ); + } + + buf.SeekGet( CUtlBuffer::SEEK_HEAD, buf.TellGet() + stringSize ); + + return true; +} + +void CCompiledKeyValuesWriter::BuildKVData_R( KeyValues *kv, int parent ) +{ + // Add self + KVInfo_t info; + info.key = m_StringTable.AddString( kv->GetName() ); + info.value = m_StringTable.AddString( kv->GetString() ); + + info.SetSubTree( kv->GetFirstSubKey() != NULL ? true : false ); + info.SetParent( parent ); + + int newParent = m_Data.AddToTail( info ); + + // Then add children + for ( KeyValues *sub = kv->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) + { + BuildKVData_R( sub, newParent ); + } + + // Then add peers + if ( parent == -1 ) + { + if ( kv->GetNextKey() ) + { + BuildKVData_R( kv->GetNextKey(), parent ); + } + } +} + +void CCompiledKeyValuesWriter::Describe( const KVFile_t& file ) +{ + Msg( "file( %s )\n", m_StringTable.String( file.filename ) ); + + int c = file.numElements; + for ( int i = 0; i < c; ++i ) + { + KVInfo_t &info = m_Data[ file.firstElement + i ]; + if ( info.IsSubTree() ) + { + Msg( "%d: %s -> subtree at parent %i\n", + file.firstElement + i, + m_StringTable.String( info.key ), + info.GetParent() ); + } + else + { + Msg( "%d: %s -> %s at parent %i\n", + file.firstElement + i, + m_StringTable.String( info.key ), + m_StringTable.String( info.value ), + info.GetParent() ); + } + } +} + +void CCompiledKeyValuesWriter::AppendKeyValuesFile( char const *filename ) +{ + KVFile_t kvf; + kvf.filename = m_StringTable.AddString( filename ); + kvf.firstElement = m_Data.Count(); + + KeyValues *kv = new KeyValues( filename ); + if ( kv->LoadFromFile( g_pFullFileSystem, filename ) ) + { + // Add to dictionary + // do a depth first traversal of the keyvalues + BuildKVData_R( kv, -1 ); + } + kv->deleteThis(); + + kvf.numElements = m_Data.Count() - kvf.firstElement; + +// Describe( kvf ); + + m_Files.AddToTail( kvf ); +} + +void CCompiledKeyValuesWriter::WriteData( CUtlBuffer& buf ) +{ + int c = m_Data.Count(); + buf.PutInt( c ); + for ( int i = 0; i < c; ++i ) + { + KVInfo_t &info = m_Data[ i ]; + buf.PutShort( info.key ); + buf.PutShort( info.value ); + buf.PutShort( info.GetParent() ); + buf.PutChar( info.IsSubTree() ? 1 : 0 ); + } +} + +void CCompiledKeyValuesWriter::WriteFiles( CUtlBuffer &buf ) +{ + int c = m_Files.Count(); + buf.PutInt( c ); + for ( int i = 0; i < c; ++i ) + { + KVFile_t &file = m_Files[ i ]; + buf.PutShort( file.filename ); + buf.PutShort( file.firstElement ); + buf.PutShort( file.numElements ); + } +} + +void CCompiledKeyValuesWriter::WriteStringTable( CUtlBuffer& buf ) +{ + int i; + CUtlVector< int > offsets; + + CUtlBuffer stringBuffer; + + offsets.AddToTail( stringBuffer.TellPut() ); + + stringBuffer.PutString( "" ); + // save all the rest + int c = m_StringTable.GetNumStrings(); + for ( i = 1; i < c; i++) + { + offsets.AddToTail( stringBuffer.TellPut() ); + stringBuffer.PutString( m_StringTable.String( i ) ); + } + + buf.Put( offsets.Base(), offsets.Count() * sizeof( int ) ); + + buf.PutInt( stringBuffer.TellPut() ); + buf.Put( stringBuffer.Base(), stringBuffer.TellPut() ); +} + +void CCompiledKeyValuesWriter::WriteFile( char const *outfile ) +{ + CUtlBuffer buf; + + // Write the data file out + KVHeader_t header; + header.fileid = COMPILED_KEYVALUES_ID; + header.version = COMPILED_KEYVALUES_VERSION; + header.numStrings = m_StringTable.GetNumStrings(); + + buf.Put( &header, sizeof( header ) ); + + WriteStringTable( buf ); + WriteData( buf ); + WriteFiles( buf ); + + g_pFullFileSystem->WriteFile( outfile, NULL, buf ); +} + +CCompiledKeyValuesReader::CCompiledKeyValuesReader() + : m_Dict( 0, 0, FileInfo_t::Less ) +{ +} + +int CCompiledKeyValuesReader::First() const +{ + return m_Dict.FirstInorder(); +} + +int CCompiledKeyValuesReader::Next( int i ) const +{ + return m_Dict.NextInorder( i ); +} + +int CCompiledKeyValuesReader::InvalidIndex() const +{ + return m_Dict.InvalidIndex(); +} + +void CCompiledKeyValuesReader::GetFileName( int index, char *buf, size_t bufsize ) +{ + Assert( buf ); + buf[ 0 ] = 0; + FileNameHandle_t& handle = m_Dict[ index ].hFile; + g_pFullFileSystem->String( handle, buf, bufsize ); +} + +bool CCompiledKeyValuesReader::LoadFile( char const *filename ) +{ + int i; + m_LoadBuffer.Purge(); + + g_pFullFileSystem->ReadFile( filename, NULL, m_LoadBuffer ); + + KVHeader_t header; + m_LoadBuffer.Get( &header, sizeof( header ) ); + + if ( header.fileid != COMPILED_KEYVALUES_ID ) + { + return false; + } + + if ( header.version != COMPILED_KEYVALUES_VERSION ) + { + return false; + } + + if ( !m_StringTable.ReadStringTable( header.numStrings, m_LoadBuffer ) ) + { + return false; + } + + // Now parse the data + int dataCount = m_LoadBuffer.GetInt(); + m_Data.EnsureCapacity( dataCount ); + for ( i = 0; i < dataCount; ++i ) + { + KVInfo_t info; + info.key = m_LoadBuffer.GetShort(); + info.value = m_LoadBuffer.GetShort(); + info.SetParent( m_LoadBuffer.GetShort() ); + info.SetSubTree( m_LoadBuffer.GetChar() == 1 ? true : false ); + m_Data.AddToTail( info ); + } + + int fileCount = m_LoadBuffer.GetInt(); + for ( i = 0; i < fileCount; ++i ) + { + FileInfo_t kvf; + short fileNameString = m_LoadBuffer.GetShort(); + + kvf.hFile = g_pFullFileSystem->FindOrAddFileName( m_StringTable.Lookup( fileNameString ) ); + kvf.nFirstIndex = m_LoadBuffer.GetShort(); + kvf.nCount = m_LoadBuffer.GetShort(); + + m_Dict.Insert( kvf ); + } + + return true; +} + +struct CreateHelper_t +{ + int index; + KeyValues *kv; + KeyValues *tail; + + static bool Less( const CreateHelper_t& lhs, const CreateHelper_t& rhs ) + { + return lhs.index < rhs.index; + } +}; + +KeyValues *CCompiledKeyValuesReader::CreateFromData( const FileInfo_t& info ) +{ + KeyValues *head = new KeyValues( "" ); + if ( CreateInPlaceFromData( *head, info ) ) + { + return head; + } + else + { + head->deleteThis(); + return NULL; + } +} + +bool CCompiledKeyValuesReader::CreateInPlaceFromData( KeyValues& head, const FileInfo_t& info ) +{ + int first = info.nFirstIndex; + int num = info.nCount; + + KeyValues *root = NULL; + KeyValues *tail = NULL; + + CUtlRBTree< CreateHelper_t, int > helper( 0, 0, CreateHelper_t::Less ); + + for ( int i = 0; i < num; ++i ) + { + int offset = first + i; + KVInfo_t& info = m_Data[ offset ]; + + if ( info.GetParent() != -1 ) + { + CreateHelper_t search; + search.index = info.GetParent(); + int idx = helper.Find( search ); + if ( idx == helper.InvalidIndex() ) + { + return false; + } + + KeyValues *parent = helper[ idx ].kv; + Assert( parent ); + + KeyValues *sub = new KeyValues( m_StringTable.Lookup( info.key ) ); + + if ( !info.IsSubTree() ) + { + sub->SetStringValue(m_StringTable.Lookup( info.value ) ); + } + + if ( !parent->GetFirstSubKey() ) + { + parent->AddSubKey( sub ); + } + else + { + KeyValues *last = helper[ idx ].tail; + last->SetNextKey( sub ); + } + + helper[ idx ].tail = sub; + + CreateHelper_t insert; + insert.index = offset; + insert.kv = sub; + insert.tail = NULL; + helper.Insert( insert ); + } + else + { + if ( !root ) + { + root = &head; + root->SetName( m_StringTable.Lookup( info.key ) ); + tail = root; + + CreateHelper_t insert; + insert.index = offset; + insert.kv = root; + insert.tail = NULL; + helper.Insert( insert ); + } + else + { + CreateHelper_t insert; + insert.index = offset; + insert.kv = new KeyValues( m_StringTable.Lookup( info.key ) ); + insert.tail = NULL; + helper.Insert( insert ); + + tail->SetNextKey( insert.kv ); + tail = insert.kv; + } + } + } + return true; +} + + +bool CCompiledKeyValuesReader::InstanceInPlace( KeyValues& head, char const *kvfilename ) +{ + char sz[ 512 ]; + Q_strncpy( sz, kvfilename, sizeof( sz ) ); + Q_FixSlashes( sz ); + + FileInfo_t search; + search.hFile = g_pFullFileSystem->FindOrAddFileName( sz ); + + int idx = m_Dict.Find( search ); + if ( idx == m_Dict.InvalidIndex() ) + { + return false; + } + + const FileInfo_t& info = m_Dict[ idx ]; + + return CreateInPlaceFromData( head, info ); +} + +KeyValues *CCompiledKeyValuesReader::Instance( char const *kvfilename ) +{ + char sz[ 512 ]; + Q_strncpy( sz, kvfilename, sizeof( sz ) ); + Q_FixSlashes( sz ); + + FileInfo_t search; + search.hFile = g_pFullFileSystem->FindOrAddFileName( sz ); + + int idx = m_Dict.Find( search ); + if ( idx == m_Dict.InvalidIndex() ) + { + return NULL; + } + + const FileInfo_t& info = m_Dict[ idx ]; + + return CreateFromData( info ); +} + +bool CCompiledKeyValuesReader::LookupKeyValuesRootKeyName( char const *kvfilename, char *outbuf, size_t bufsize ) +{ + char sz[ 512 ]; + Q_strncpy( sz, kvfilename, sizeof( sz ) ); + Q_FixSlashes( sz ); + + FileInfo_t search; + search.hFile = g_pFullFileSystem->FindOrAddFileName( sz ); + + int idx = m_Dict.Find( search ); + if ( idx == m_Dict.InvalidIndex() ) + { + return false; + } + + const FileInfo_t& info = m_Dict[ idx ]; + + Q_strncpy( outbuf, m_StringTable.Lookup( m_Data[ info.nFirstIndex ].key ), bufsize ); + return true; +} diff --git a/public/keyframe/keyframe.cpp b/public/keyframe/keyframe.cpp index 2710c826..35e1381b 100644 --- a/public/keyframe/keyframe.cpp +++ b/public/keyframe/keyframe.cpp @@ -13,7 +13,10 @@ #include typedef unsigned char byte; + +#ifdef _MSC_VER #pragma warning(disable:4244) +#endif #include "tier0/dbg.h" #include "vector.h" diff --git a/public/keyvaluescompiler.h b/public/keyvaluescompiler.h index fef2c482..c98a366f 100644 --- a/public/keyvaluescompiler.h +++ b/public/keyvaluescompiler.h @@ -1,187 +1,187 @@ -//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= -// -// Purpose: -// -//============================================================================= - -#ifndef KEYVALUESCOMPILER_H -#define KEYVALUESCOMPILER_H -#ifdef _WIN32 -#pragma once -#endif - -#include "tier1/UtlBuffer.h" -#include "tier1/UtlSymbol.h" -#include "tier1/UtlDict.h" - -class KeyValues; - -#define COMPILED_KEYVALUES_ID MAKEID( 'V', 'K', 'V', 'F' ) - -#define COMPILED_KEYVALUES_VERSION 1 - -struct KVHeader_t -{ - int fileid; - int version; - int numStrings; -}; - -#pragma pack(1) -struct KVFile_t -{ - KVFile_t() : - filename( 0 ), - firstElement( 0 ), - numElements( 0 ) - { - } - short filename; - short firstElement; - short numElements; -}; - -struct KVInfo_t -{ - KVInfo_t() : - key( 0 ), - value( 0 ), - parentIndex( -1 ), - issubtree( false ) - { - } - - inline void SetParent( int index ) - { - Assert( index <= 32768 ); - parentIndex = index; - } - - inline short GetParent() const - { - return parentIndex; - } - - inline void SetSubTree( bool state ) - { - issubtree = state; - } - - inline bool IsSubTree() const - { - return issubtree; - } - - short key; - short value; - -private: - - short parentIndex; - bool issubtree; -}; -#pragma pack() - -//----------------------------------------------------------------------------- -// Purpose: stringtable is a session global string table. -//----------------------------------------------------------------------------- -class CCompiledKeyValuesWriter -{ -public: - - CCompiledKeyValuesWriter() - { - m_StringTable.AddString( "" ); - } - - void AppendKeyValuesFile( char const *filename ); - void WriteFile( char const *outfile ); - -private: - - void Describe( const KVFile_t& file ); - - void BuildKVData_R( KeyValues *kv, int parent ); - - void WriteStringTable( CUtlBuffer& buf ); - void WriteData( CUtlBuffer& buf ); - void WriteFiles( CUtlBuffer &buf ); - - CUtlVector< KVFile_t > m_Files; - CUtlVector< KVInfo_t > m_Data; - - CUtlSymbolTable m_StringTable; -}; - - -class CRunTimeKeyValuesStringTable -{ -public: - - bool ReadStringTable( int numStrings, CUtlBuffer& buf ); - - inline int CRunTimeKeyValuesStringTable::Count() const - { - return m_Strings.Count(); - } - - inline char const *Lookup( short index ) - { - return m_Strings[ index ]; - } - -private: - CUtlVector< const char * > m_Strings; -}; - -class CCompiledKeyValuesReader -{ -public: - - CCompiledKeyValuesReader(); - - bool LoadFile( char const *filename ); - - KeyValues *Instance( char const *kvfilename ); - bool InstanceInPlace( KeyValues& head, char const *kvfilename ); - bool LookupKeyValuesRootKeyName( char const *kvfilename, char *outbuf, size_t bufsize ); - - int First() const; - int Next( int i ) const; - int InvalidIndex() const; - - void GetFileName( int index, char *buf, size_t bufsize ); - -private: - - struct FileInfo_t - { - FileInfo_t() : - hFile( 0 ), - nFirstIndex( 0 ), - nCount( 0 ) - { - } - FileNameHandle_t hFile; - short nFirstIndex; - short nCount; - - static bool Less( const FileInfo_t& lhs, const FileInfo_t& rhs ) - { - return lhs.hFile < rhs.hFile; - } - }; - - KeyValues *CreateFromData( const FileInfo_t& info ); - bool CreateInPlaceFromData( KeyValues& head, const FileInfo_t& info ); - - // Now get the actual files - CUtlRBTree< FileInfo_t, unsigned short > m_Dict; - CUtlVector< KVInfo_t > m_Data; - - CRunTimeKeyValuesStringTable m_StringTable; - - CUtlBuffer m_LoadBuffer; -}; - -#endif // KEYVALUESCOMPILER_H +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#ifndef KEYVALUESCOMPILER_H +#define KEYVALUESCOMPILER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlbuffer.h" +#include "tier1/utlsymbol.h" +#include "tier1/utldict.h" + +class KeyValues; + +#define COMPILED_KEYVALUES_ID MAKEID( 'V', 'K', 'V', 'F' ) + +#define COMPILED_KEYVALUES_VERSION 1 + +struct KVHeader_t +{ + int fileid; + int version; + int numStrings; +}; + +#pragma pack(1) +struct KVFile_t +{ + KVFile_t() : + filename( 0 ), + firstElement( 0 ), + numElements( 0 ) + { + } + short filename; + short firstElement; + short numElements; +}; + +struct KVInfo_t +{ + KVInfo_t() : + key( 0 ), + value( 0 ), + parentIndex( -1 ), + issubtree( false ) + { + } + + inline void SetParent( int index ) + { + Assert( index <= 32768 ); + parentIndex = index; + } + + inline short GetParent() const + { + return parentIndex; + } + + inline void SetSubTree( bool state ) + { + issubtree = state; + } + + inline bool IsSubTree() const + { + return issubtree; + } + + short key; + short value; + +private: + + short parentIndex; + bool issubtree; +}; +#pragma pack() + +//----------------------------------------------------------------------------- +// Purpose: stringtable is a session global string table. +//----------------------------------------------------------------------------- +class CCompiledKeyValuesWriter +{ +public: + + CCompiledKeyValuesWriter() + { + m_StringTable.AddString( "" ); + } + + void AppendKeyValuesFile( char const *filename ); + void WriteFile( char const *outfile ); + +private: + + void Describe( const KVFile_t& file ); + + void BuildKVData_R( KeyValues *kv, int parent ); + + void WriteStringTable( CUtlBuffer& buf ); + void WriteData( CUtlBuffer& buf ); + void WriteFiles( CUtlBuffer &buf ); + + CUtlVector< KVFile_t > m_Files; + CUtlVector< KVInfo_t > m_Data; + + CUtlSymbolTable m_StringTable; +}; + + +class CRunTimeKeyValuesStringTable +{ +public: + + bool ReadStringTable( int numStrings, CUtlBuffer& buf ); + + inline int Count() const + { + return m_Strings.Count(); + } + + inline char const *Lookup( short index ) + { + return m_Strings[ index ]; + } + +private: + CUtlVector< const char * > m_Strings; +}; + +class CCompiledKeyValuesReader +{ +public: + + CCompiledKeyValuesReader(); + + bool LoadFile( char const *filename ); + + KeyValues *Instance( char const *kvfilename ); + bool InstanceInPlace( KeyValues& head, char const *kvfilename ); + bool LookupKeyValuesRootKeyName( char const *kvfilename, char *outbuf, size_t bufsize ); + + int First() const; + int Next( int i ) const; + int InvalidIndex() const; + + void GetFileName( int index, char *buf, size_t bufsize ); + +private: + + struct FileInfo_t + { + FileInfo_t() : + hFile( 0 ), + nFirstIndex( 0 ), + nCount( 0 ) + { + } + FileNameHandle_t hFile; + short nFirstIndex; + short nCount; + + static bool Less( const FileInfo_t& lhs, const FileInfo_t& rhs ) + { + return lhs.hFile < rhs.hFile; + } + }; + + KeyValues *CreateFromData( const FileInfo_t& info ); + bool CreateInPlaceFromData( KeyValues& head, const FileInfo_t& info ); + + // Now get the actual files + CUtlRBTree< FileInfo_t, unsigned short > m_Dict; + CUtlVector< KVInfo_t > m_Data; + + CRunTimeKeyValuesStringTable m_StringTable; + + CUtlBuffer m_LoadBuffer; +}; + +#endif // KEYVALUESCOMPILER_H diff --git a/public/loadcmdline.cpp b/public/loadcmdline.cpp index 8810787e..9a29443c 100644 --- a/public/loadcmdline.cpp +++ b/public/loadcmdline.cpp @@ -1,123 +1,123 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: loads additional command line options from a config file -// -// $NoKeywords: $ -//=============================================================================// - -#include "KeyValues.h" -#include "vstdlib/strtools.h" -#include "FileSystem_Tools.h" -#include "tier1/utlstring.h" - -// So we know whether or not we own argv's memory -static bool sFoundConfigArgs = false; - -//----------------------------------------------------------------------------- -// Purpose: Parses arguments and adds them to argv and argc -//----------------------------------------------------------------------------- -static void AddArguments( int &argc, char **&argv, const char *str ) -{ - char **args = 0; - char *argList = 0; - int argCt = argc; - - argList = new char[ Q_strlen( str ) + 1 ]; - Q_strcpy( argList, str ); - - // Parse the arguments out of the string - char *token = strtok( argList, " " ); - while( token ) - { - ++argCt; - token = strtok( NULL, " " ); - } - - // Make sure someting was actually found in the file - if( argCt > argc ) - { - sFoundConfigArgs = true; - - // Allocate a new array for argv - args = new char*[ argCt ]; - - // Copy original arguments, up to the last one - int i; - for( i = 0; i < argc - 1; ++i ) - { - args[ i ] = new char[ Q_strlen( argv[ i ] ) + 1 ]; - Q_strcpy( args[ i ], argv[ i ] ); - } - - // copy new arguments - Q_strcpy( argList, str ); - token = strtok( argList, " " ); - for( ; i < argCt - 1; ++i ) - { - args[ i ] = new char[ Q_strlen( token ) + 1 ]; - Q_strcpy( args[ i ], token ); - token = strtok( NULL, " " ); - } - - // Copy the last original argument - args[ i ] = new char[ Q_strlen( argv[ argc - 1 ] ) + 1 ]; - Q_strcpy( args[ i ], argv[ argc - 1 ] ); - - argc = argCt; - argv = args; - } - - delete [] argList; -} - -//----------------------------------------------------------------------------- -// Purpose: Loads additional commandline arguments from a config file for an app. -// Filesystem must be initialized before calling this function. -// keyname: Name of the block containing the key/args pairs (ie map or model name) -// appname: Keyname for the commandline arguments to be loaded - typically the exe name. -//----------------------------------------------------------------------------- -void LoadCmdLineFromFile( int &argc, char **&argv, const char *keyname, const char *appname ) -{ - sFoundConfigArgs = false; - - assert( g_pFileSystem ); - if( !g_pFileSystem ) - return; - - // Load the cfg file, and find the keyname - KeyValues *kv = new KeyValues( "CommandLine" ); - - char filename[512]; - Q_snprintf( filename, sizeof( filename ), "%s/cfg/commandline.cfg", gamedir ); - - if ( kv->LoadFromFile( g_pFileSystem, filename ) ) - { - // Load the commandline arguments for this app - KeyValues *appKey = kv->FindKey( keyname ); - if( appKey ) - { - const char *str = appKey->GetString( appname ); - Msg( "Command Line found: %s\n", str ); - - AddArguments( argc, argv, str ); - } - } - - kv->deleteThis(); -} - -//----------------------------------------------------------------------------- -// Purpose: Cleans up any memory allocated for the new argv. Pass in the app's -// argc and argv - this is safe even if no extra arguments were loaded. -//----------------------------------------------------------------------------- -void DeleteCmdLine( int argc, char **argv ) -{ - if( !sFoundConfigArgs ) - return; - - for( int i = 0; i < argc; ++i ) - { - delete [] argv[i]; - } - delete [] argv; -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: loads additional command line options from a config file +// +// $NoKeywords: $ +//=============================================================================// + +#include "KeyValues.h" +#include "vstdlib/strtools.h" +#include "filesystem_tools.h" +#include "tier1/utlstring.h" + +// So we know whether or not we own argv's memory +static bool sFoundConfigArgs = false; + +//----------------------------------------------------------------------------- +// Purpose: Parses arguments and adds them to argv and argc +//----------------------------------------------------------------------------- +static void AddArguments( int &argc, char **&argv, const char *str ) +{ + char **args = 0; + char *argList = 0; + int argCt = argc; + + argList = new char[ Q_strlen( str ) + 1 ]; + Q_strcpy( argList, str ); + + // Parse the arguments out of the string + char *token = strtok( argList, " " ); + while( token ) + { + ++argCt; + token = strtok( NULL, " " ); + } + + // Make sure someting was actually found in the file + if( argCt > argc ) + { + sFoundConfigArgs = true; + + // Allocate a new array for argv + args = new char*[ argCt ]; + + // Copy original arguments, up to the last one + int i; + for( i = 0; i < argc - 1; ++i ) + { + args[ i ] = new char[ Q_strlen( argv[ i ] ) + 1 ]; + Q_strcpy( args[ i ], argv[ i ] ); + } + + // copy new arguments + Q_strcpy( argList, str ); + token = strtok( argList, " " ); + for( ; i < argCt - 1; ++i ) + { + args[ i ] = new char[ Q_strlen( token ) + 1 ]; + Q_strcpy( args[ i ], token ); + token = strtok( NULL, " " ); + } + + // Copy the last original argument + args[ i ] = new char[ Q_strlen( argv[ argc - 1 ] ) + 1 ]; + Q_strcpy( args[ i ], argv[ argc - 1 ] ); + + argc = argCt; + argv = args; + } + + delete [] argList; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads additional commandline arguments from a config file for an app. +// Filesystem must be initialized before calling this function. +// keyname: Name of the block containing the key/args pairs (ie map or model name) +// appname: Keyname for the commandline arguments to be loaded - typically the exe name. +//----------------------------------------------------------------------------- +void LoadCmdLineFromFile( int &argc, char **&argv, const char *keyname, const char *appname ) +{ + sFoundConfigArgs = false; + + assert( g_pFileSystem ); + if( !g_pFileSystem ) + return; + + // Load the cfg file, and find the keyname + KeyValues *kv = new KeyValues( "CommandLine" ); + + char filename[512]; + Q_snprintf( filename, sizeof( filename ), "%s/cfg/commandline.cfg", gamedir ); + + if ( kv->LoadFromFile( g_pFileSystem, filename ) ) + { + // Load the commandline arguments for this app + KeyValues *appKey = kv->FindKey( keyname ); + if( appKey ) + { + const char *str = appKey->GetString( appname ); + Msg( "Command Line found: %s\n", str ); + + AddArguments( argc, argv, str ); + } + } + + kv->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: Cleans up any memory allocated for the new argv. Pass in the app's +// argc and argv - this is safe even if no extra arguments were loaded. +//----------------------------------------------------------------------------- +void DeleteCmdLine( int argc, char **argv ) +{ + if( !sFoundConfigArgs ) + return; + + for( int i = 0; i < argc; ++i ) + { + delete [] argv[i]; + } + delete [] argv; +} diff --git a/public/materialsystem/shader_vcs_version.h b/public/materialsystem/shader_vcs_version.h index 29eacb2a..d23a797e 100644 --- a/public/materialsystem/shader_vcs_version.h +++ b/public/materialsystem/shader_vcs_version.h @@ -1,64 +1,63 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef SHADER_VCS_VERSION_H -#define SHADER_VCS_VERSION_H -#ifdef _WIN32 -#pragma once -#endif - -// 1=hl2 shipped. 2=compressed with diffs version (lostcoast) -#define SHADER_VCS_VERSION_NUMBER 2 - -#pragma pack(1) -struct ShaderHeader_t_V1 -{ - int m_nVersion; - int m_nTotalCombos; - int m_nDynamicCombos; - unsigned int m_nFlags; - unsigned int m_nCentroidMask; -}; - -struct ShaderHeader_t : ShaderHeader_t_V1 -{ - int m_ReferenceComboSizeForDiffs; -}; - -struct ShaderDictionaryEntry_t -{ - int m_Offset; - int m_Size; -}; -#pragma pack() - -// 2=xbox shipped. -#define SHADER_XCS_VERSION_NUMBER 2 - -// xcs file format -#pragma pack(1) -struct XShaderDictionaryEntry_t -{ - int m_Offset; - unsigned short m_PackedSize; - unsigned short m_Size; -}; -struct XShaderHeader_t -{ - int m_nVersion; - int m_nTotalCombos; - int m_nDynamicCombos; - unsigned int m_nFlags; - unsigned int m_nCentroidMask; - int m_nReferenceShader; - - bool IsValid() { return m_nVersion == SHADER_XCS_VERSION_NUMBER; } - unsigned int BytesToPreload() { return sizeof( XShaderHeader_t ) + sizeof( XShaderDictionaryEntry_t ) * m_nTotalCombos; } -}; -#pragma pack() - -#endif // SHADER_VCS_VERSION_H - \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef SHADER_VCS_VERSION_H +#define SHADER_VCS_VERSION_H +#ifdef _WIN32 +#pragma once +#endif + +// 1=hl2 shipped. 2=compressed with diffs version (lostcoast) +#define SHADER_VCS_VERSION_NUMBER 2 + +#pragma pack(1) +struct ShaderHeader_t_V1 +{ + int m_nVersion; + int m_nTotalCombos; + int m_nDynamicCombos; + unsigned int m_nFlags; + unsigned int m_nCentroidMask; +}; + +struct ShaderHeader_t : ShaderHeader_t_V1 +{ + int m_ReferenceComboSizeForDiffs; +}; + +struct ShaderDictionaryEntry_t +{ + int m_Offset; + int m_Size; +}; +#pragma pack() + +// 2=xbox shipped. +#define SHADER_XCS_VERSION_NUMBER 2 + +// xcs file format +#pragma pack(1) +struct XShaderDictionaryEntry_t +{ + int m_Offset; + unsigned short m_PackedSize; + unsigned short m_Size; +}; +struct XShaderHeader_t +{ + int m_nVersion; + int m_nTotalCombos; + int m_nDynamicCombos; + unsigned int m_nFlags; + unsigned int m_nCentroidMask; + int m_nReferenceShader; + + bool IsValid() { return m_nVersion == SHADER_XCS_VERSION_NUMBER; } + unsigned int BytesToPreload() { return sizeof( XShaderHeader_t ) + sizeof( XShaderDictionaryEntry_t ) * m_nTotalCombos; } +}; +#pragma pack() + +#endif // SHADER_VCS_VERSION_H diff --git a/public/mathlib/math_base.h b/public/mathlib/math_base.h index 6b41e6c1..0b394834 100644 --- a/public/mathlib/math_base.h +++ b/public/mathlib/math_base.h @@ -13,6 +13,8 @@ #include "vector.h" #include "vector2d.h" #include "tier0/dbg.h" + +#undef MINMAX_H #include "minmax.h" #ifdef _WIN32 @@ -504,7 +506,7 @@ inline float RemapValClamped( float val, float A, float B, float C, float D) template FORCEINLINE_MATH T Lerp( float flPercent, T const &A, T const &B ) { - return A + (B - A) * flPercent; + return static_cast(A + (B - A) * flPercent); } // 5-argument floating point linear interpolation. @@ -1071,11 +1073,12 @@ inline unsigned long RoundFloatToUnsignedLong(float f) // Fast, accurate ftol: inline int Float2Int( float a ) { - int CtrlwdHolder; - int CtrlwdSetter; int RetVal; #ifdef _WIN32 + int CtrlwdHolder; + int CtrlwdSetter; + __asm { fld a // push 'a' onto the FP stack @@ -1098,11 +1101,12 @@ inline int Float2Int( float a ) // Over 15x faster than: (int)floor(value) inline int Floor2Int( float a ) { - int CtrlwdHolder; - int CtrlwdSetter; int RetVal; #ifdef _WIN32 + int CtrlwdHolder; + int CtrlwdSetter; + __asm { fld a // push 'a' onto the FP stack @@ -1146,11 +1150,12 @@ inline float ClampToMsec( float in ) // Over 15x faster than: (int)ceil(value) inline int Ceil2Int( float a ) { - int CtrlwdHolder; - int CtrlwdSetter; int RetVal; #ifdef _WIN32 + int CtrlwdHolder; + int CtrlwdSetter; + __asm { fld a // push 'a' onto the FP stack diff --git a/public/mathlib/ssemath.h b/public/mathlib/ssemath.h index c7dc3c6d..a73d935f 100644 --- a/public/mathlib/ssemath.h +++ b/public/mathlib/ssemath.h @@ -1,400 +1,403 @@ -// $Id$ - - -// ssemath.h - defines sse-based "structure of arrays" classes and functions. - - -#ifndef SSEMATH_H -#define SSEMATH_H - -//#ifdef _WIN32 - -#include -#include - -#ifdef _LINUX -typedef int int32; -typedef union __attribute__ ((aligned (16))) - { - float m128_f32[4]; - } l_m128; - -#endif - -// I thought about defining a class/union for the sse packed floats instead of using __m128, -// but decided against it because (a) the nature of sse code which includes comparisons is to blur -// the relationship between packed floats and packed integer types and (b) not sure that the -// compiler would handle generating good code for the intrinsics. - - -// useful constants in SSE packed float format: -extern __m128 Four_Zeros; // 0 0 0 0 -extern __m128 Four_Ones; // 1 1 1 1 -extern __m128 Four_PointFives; // .5 .5 .5 .5 -extern __m128 Four_Epsilons; // FLT_EPSILON FLT_EPSILON FLT_EPSILON FLT_EPSILON - - -/// replicate a single floating point value to all 4 components of an sse packed float -static inline __m128 MMReplicate(float f) -{ - __m128 value=_mm_set_ss(f); - return _mm_shuffle_ps(value,value,0); -} - -/// replicate a single 32 bit integer value to all 4 components of an m128 -static inline __m128 MMReplicateI(int i) -{ - __m128 value=_mm_set_ss(*((float *)&i));; - return _mm_shuffle_ps(value,value,0); -} - -/// 1/x for all 4 values. uses reciprocal approximation instruction plus newton iteration. -/// No error checking! -static inline __m128 MMReciprocal(__m128 x) -{ - __m128 ret=_mm_rcp_ps(x); - // newton iteration is Y(n+1)=2*Y(N)-x*Y(n)^2 - ret=_mm_sub_ps(_mm_add_ps(ret,ret),_mm_mul_ps(x,_mm_mul_ps(ret,ret))); - return ret; -} -/// 1/x for all 4 values. uses reciprocal approximation instruction plus newton iteration. -/// 1/0 will result in a big but NOT infinite result -static inline __m128 MMReciprocalSaturate(__m128 x) -{ - __m128 zero_mask=_mm_cmpeq_ps(x,Four_Zeros); - x=_mm_or_ps(x,_mm_and_ps(Four_Epsilons,zero_mask)); - __m128 ret=_mm_rcp_ps(x); - // newton iteration is Y(n+1)=2*Y(N)-x*Y(n)^2 - ret=_mm_sub_ps(_mm_add_ps(ret,ret),_mm_mul_ps(x,_mm_mul_ps(ret,ret))); - return ret; -} - -/// class FourVectors stores 4 independent vectors for use by sse processing. These vectors are -/// stored in the format x x x x y y y y z z z z so that they can be efficiently accelerated by -/// SSE. -class FourVectors -{ -public: - __m128 x,y,z; // x x x x y y y y z z z z - - inline void DuplicateVector(Vector const &v) //< set all 4 vectors to the same vector value - { - x=MMReplicate(v.x); - y=MMReplicate(v.y); - z=MMReplicate(v.z); - } - - inline __m128 const & operator[](int idx) const - { - return *((&x)+idx); - } - - inline __m128 & operator[](int idx) - { - return *((&x)+idx); - } - - inline void operator+=(FourVectors const &b) //< add 4 vectors to another 4 vectors - { - x=_mm_add_ps(x,b.x); - y=_mm_add_ps(y,b.y); - z=_mm_add_ps(z,b.z); - } - inline void operator-=(FourVectors const &b) //< subtract 4 vectors from another 4 - { - x=_mm_sub_ps(x,b.x); - y=_mm_sub_ps(y,b.y); - z=_mm_sub_ps(z,b.z); - } - - inline void operator*=(FourVectors const &b) //< scale all four vectors per component scale - { - x=_mm_mul_ps(x,b.x); - y=_mm_mul_ps(y,b.y); - z=_mm_mul_ps(z,b.z); - } - - inline void operator*=(float scale) //< uniformly scale all 4 vectors - { - __m128 scalepacked=MMReplicate(scale); - x=_mm_mul_ps(x,scalepacked); - y=_mm_mul_ps(y,scalepacked); - z=_mm_mul_ps(z,scalepacked); - } - - inline void operator*=(__m128 scale) //< scale - { - x=_mm_mul_ps(x,scale); - y=_mm_mul_ps(y,scale); - z=_mm_mul_ps(z,scale); - } - - inline __m128 operator*(FourVectors const &b) const //< 4 dot products - { - __m128 dot=_mm_mul_ps(x,b.x); - dot=_mm_add_ps(dot,_mm_mul_ps(y,b.y)); - dot=_mm_add_ps(dot,_mm_mul_ps(z,b.z)); - return dot; - } - - inline __m128 operator*(Vector const &b) const //< dot product all 4 vectors with 1 vector - { - __m128 dot=_mm_mul_ps(x,MMReplicate(b.x)); - dot=_mm_add_ps(dot,_mm_mul_ps(y,MMReplicate(b.y))); - dot=_mm_add_ps(dot,_mm_mul_ps(z,MMReplicate(b.z))); - return dot; - } - - - inline void VProduct(FourVectors const &b) //< component by component mul - { - x=_mm_mul_ps(x,b.x); - y=_mm_mul_ps(y,b.y); - z=_mm_mul_ps(z,b.z); - } - inline void MakeReciprocal(void) //< (x,y,z)=(1/x,1/y,1/z) - { - x=MMReciprocal(x); - y=MMReciprocal(y); - z=MMReciprocal(z); - } - - inline void MakeReciprocalSaturate(void) //< (x,y,z)=(1/x,1/y,1/z), 1/0=1.0e23 - { - x=MMReciprocalSaturate(x); - y=MMReciprocalSaturate(y); - z=MMReciprocalSaturate(z); - } - - // X(),Y(),Z() - get at the desired component of the i'th (0..3) vector. - inline float const &X(int idx) const - { -#ifdef _LINUX - return ((l_m128*)&x)->m128_f32[idx]; -#else - return x.m128_f32[idx]; -#endif - } - - inline float const &Y(int idx) const - { -#ifdef _LINUX - return ((l_m128*)&y)->m128_f32[idx]; -#else - return y.m128_f32[idx]; -#endif - } - inline float const &Z(int idx) const - { -#ifdef _LINUX - return ((l_m128*)&z)->m128_f32[idx]; -#else - return z.m128_f32[idx]; -#endif - } - - // X(),Y(),Z() - get at the desired component of the i'th (0..3) vector. - inline float &X(int idx) - { -#ifdef _LINUX - return ((l_m128*)&x)->m128_f32[idx]; -#else - return x.m128_f32[idx]; -#endif - } - - inline float &Y(int idx) - { -#ifdef _LINUX - return ((l_m128*)&y)->m128_f32[idx]; -#else - return y.m128_f32[idx]; -#endif - } - inline float &Z(int idx) - { -#ifdef _LINUX - return ((l_m128*)&z)->m128_f32[idx]; -#else - return z.m128_f32[idx]; -#endif - } - - inline Vector Vec(int idx) const //< unpack one of the vectors - { - return Vector(X(idx),Y(idx),Z(idx)); - } - - FourVectors(void) - { - } - - /// LoadAndSwizzle - load 4 Vectors into a FourVectors, perofrming transpose op - inline void LoadAndSwizzle(Vector const &a, Vector const &b, Vector const &c, Vector const &d) - { - __m128 row0=_mm_loadu_ps(&(a.x)); - __m128 row1=_mm_loadu_ps(&(b.x)); - __m128 row2=_mm_loadu_ps(&(c.x)); - __m128 row3=_mm_loadu_ps(&(d.x)); - // now, matrix is: - // x y z ? - // x y z ? - // x y z ? - // x y z ? - _MM_TRANSPOSE4_PS(row0,row1,row2,row3); - x=row0; - y=row1; - z=row2; - } - - /// LoadAndSwizzleAligned - load 4 Vectors into a FourVectors, perofrming transpose op. - /// all 4 vectors muyst be 128 bit boundary - inline void LoadAndSwizzleAligned(Vector const &a, Vector const &b, Vector const &c, Vector const &d) - { - __m128 row0=_mm_load_ps(&(a.x)); - __m128 row1=_mm_load_ps(&(b.x)); - __m128 row2=_mm_load_ps(&(c.x)); - __m128 row3=_mm_load_ps(&(d.x)); - // now, matrix is: - // x y z ? - // x y z ? - // x y z ? - // x y z ? - _MM_TRANSPOSE4_PS(row0,row1,row2,row3); - x=row0; - y=row1; - z=row2; - } - - /// return the squared length of all 4 vectors - inline __m128 length2(void) const - { - return (*this)*(*this); - } - - /// return the approximate length of all 4 vectors. uses the sse sqrt approximation instr - inline __m128 length(void) const - { - return _mm_sqrt_ps(length2()); - } - - /// normalize all 4 vectors in place. not mega-accurate (uses sse reciprocal approximation instr) - inline void VectorNormalizeFast(void) - { - __m128 mag_sq=(*this)*(*this); // length^2 - (*this) *= _mm_rsqrt_ps(mag_sq); // *(1.0/sqrt(length^2)) - } - - /// normnalize all 4 vectors in place. uses newton iteration for higher precision results than - /// from VectorNormalizeFast. - inline void VectorNormalize(void) - { - static __m128 FourHalves={0.5,0.5,0.5,0.5}; - static __m128 FourThrees={3.,3.,3.,3.}; - __m128 mag_sq=(*this)*(*this); // length^2 - __m128 guess=_mm_rsqrt_ps(mag_sq); - // newton iteration for 1/sqrt(x) : y(n+1)=1/2 (y(n)*(3-x*y(n)^2)); - guess=_mm_mul_ps(guess,_mm_sub_ps(FourThrees,_mm_mul_ps(mag_sq,_mm_mul_ps(guess,guess)))); - guess=_mm_mul_ps(Four_PointFives,guess); - (*this) *= guess; - } - - /// construct a FourVectors from 4 separate Vectors - inline FourVectors(Vector const &a, Vector const &b, Vector const &c, Vector const &d) - { - LoadAndSwizzle(a,b,c,d); - } - -}; - -/// form 4 cross products -inline FourVectors operator ^(const FourVectors &a, const FourVectors &b) -{ - FourVectors ret; - ret.x=_mm_sub_ps(_mm_mul_ps(a.y,b.z),_mm_mul_ps(a.z,b.y)); - ret.y=_mm_sub_ps(_mm_mul_ps(a.z,b.x),_mm_mul_ps(a.x,b.z)); - ret.z=_mm_sub_ps(_mm_mul_ps(a.x,b.y),_mm_mul_ps(a.y,b.x)); - return ret; -} - -/// component-by-componentwise MAX operator -inline FourVectors maximum(const FourVectors &a, const FourVectors &b) -{ - FourVectors ret; - ret.x=_mm_max_ps(a.x,b.x); - ret.y=_mm_max_ps(a.y,b.y); - ret.z=_mm_max_ps(a.z,b.z); - return ret; -} - -/// component-by-componentwise MIN operator -inline FourVectors minimum(const FourVectors &a, const FourVectors &b) -{ - FourVectors ret; - ret.x=_mm_min_ps(a.x,b.x); - ret.y=_mm_min_ps(a.y,b.y); - ret.z=_mm_min_ps(a.z,b.z); - return ret; -} - - -/// check an m128 for all zeros. Not sure if this is the best way -inline bool IsAllZeros(__m128 var) -{ -#ifdef _WIN32 - __declspec(align(16)) int32 myints[4]; -#elif _LINUX - int32 myints[4] __attribute__ ((aligned (16))); -#endif - _mm_store_ps((float *) myints,var); - return (myints[0]|myints[1]|myints[2]|myints[3])==0; -} - -/// calculate the absolute value of a packed single -inline __m128 fabs(__m128 x) -{ -#ifdef _WIN32 - static __declspec(align(16)) int32 clear_signmask[4]= - {0x7fffffff,0x7fffffff,0x7fffffff,0x7fffffff}; -#elif _LINUX - static int32 clear_signmask[4] __attribute__ ((aligned (16)))= - {0x7fffffff,0x7fffffff,0x7fffffff,0x7fffffff}; -#endif - return _mm_and_ps(x,_mm_load_ps((float *) clear_signmask)); -} - -/// negate all four components of an sse packed single -inline __m128 fnegate(__m128 x) -{ -#ifdef _WIN32 - static __declspec(align(16)) int32 signmask[4]= - {0x80000000,0x80000000,0x80000000,0x80000000}; -#elif _LINUX - static int32 signmask[4] __attribute__ ((aligned (16)))= - {0x80000000,0x80000000,0x80000000,0x80000000}; -#endif - return _mm_xor_ps(x,_mm_load_ps((float *) signmask)); -} - -__m128 PowSSE_FixedPoint_Exponent(__m128 x, int exponent); - -// PowSSE - raise an sse register to a power. This is analogous to the C pow() function, with some -// restictions: fractional exponents are only handled with 2 bits of precision. Basically, -// fractions of 0,.25,.5, and .75 are handled. PowSSE(x,.30) will be the same as PowSSE(x,.25). -// negative and fractional powers are handled by the SSE reciprocal and square root approximation -// instructions and so are not especially accurate ----Note that this routine does not raise -// numeric exceptions because it uses SSE--- This routine is O(log2(exponent)). -inline __m128 PowSSE(__m128 x, float exponent) -{ - return PowSSE_FixedPoint_Exponent(x,(int) (4.0*exponent)); -} - -#define SSE1234(x) ((x).m128_f32[0]),((x).m128_f32[1]),((x).m128_f32[2]),((x).m128_f32[3]) - -#endif // WIN32 - - - - - -//#endif +// $Id$ + + +// ssemath.h - defines sse-based "structure of arrays" classes and functions. + + +#ifndef SSEMATH_H +#define SSEMATH_H + +//#ifdef _WIN32 + +#include +#include + +#ifdef _LINUX +typedef int int32; +typedef union __attribute__ ((aligned (16))) + { + float m128_f32[4]; + } l_m128; + +#endif + +// I thought about defining a class/union for the sse packed floats instead of using __m128, +// but decided against it because (a) the nature of sse code which includes comparisons is to blur +// the relationship between packed floats and packed integer types and (b) not sure that the +// compiler would handle generating good code for the intrinsics. + + +// useful constants in SSE packed float format: +extern __m128 Four_Zeros; // 0 0 0 0 +extern __m128 Four_Ones; // 1 1 1 1 +extern __m128 Four_PointFives; // .5 .5 .5 .5 +extern __m128 Four_Epsilons; // FLT_EPSILON FLT_EPSILON FLT_EPSILON FLT_EPSILON + + +/// replicate a single floating point value to all 4 components of an sse packed float +static inline __m128 MMReplicate(float f) +{ + __m128 value=_mm_set_ss(f); + return _mm_shuffle_ps(value,value,0); +} + +/// replicate a single 32 bit integer value to all 4 components of an m128 +static inline __m128 MMReplicateI(int i) +{ + void *t = &i; + __m128 value = _mm_set_ss(*reinterpret_cast(t)); + return _mm_shuffle_ps(value,value,0); +} + +/// 1/x for all 4 values. uses reciprocal approximation instruction plus newton iteration. +/// No error checking! +static inline __m128 MMReciprocal(__m128 x) +{ + __m128 ret=_mm_rcp_ps(x); + // newton iteration is Y(n+1)=2*Y(N)-x*Y(n)^2 + ret=_mm_sub_ps(_mm_add_ps(ret,ret),_mm_mul_ps(x,_mm_mul_ps(ret,ret))); + return ret; +} +/// 1/x for all 4 values. uses reciprocal approximation instruction plus newton iteration. +/// 1/0 will result in a big but NOT infinite result +static inline __m128 MMReciprocalSaturate(__m128 x) +{ + __m128 zero_mask=_mm_cmpeq_ps(x,Four_Zeros); + x=_mm_or_ps(x,_mm_and_ps(Four_Epsilons,zero_mask)); + __m128 ret=_mm_rcp_ps(x); + // newton iteration is Y(n+1)=2*Y(N)-x*Y(n)^2 + ret=_mm_sub_ps(_mm_add_ps(ret,ret),_mm_mul_ps(x,_mm_mul_ps(ret,ret))); + return ret; +} + +/// class FourVectors stores 4 independent vectors for use by sse processing. These vectors are +/// stored in the format x x x x y y y y z z z z so that they can be efficiently accelerated by +/// SSE. +class FourVectors +{ +public: + __m128 x,y,z; // x x x x y y y y z z z z + + inline void DuplicateVector(Vector const &v) //< set all 4 vectors to the same vector value + { + x=MMReplicate(v.x); + y=MMReplicate(v.y); + z=MMReplicate(v.z); + } + + inline __m128 const & operator[](int idx) const + { + return *((&x)+idx); + } + + inline __m128 & operator[](int idx) + { + return *((&x)+idx); + } + + inline void operator+=(FourVectors const &b) //< add 4 vectors to another 4 vectors + { + x=_mm_add_ps(x,b.x); + y=_mm_add_ps(y,b.y); + z=_mm_add_ps(z,b.z); + } + inline void operator-=(FourVectors const &b) //< subtract 4 vectors from another 4 + { + x=_mm_sub_ps(x,b.x); + y=_mm_sub_ps(y,b.y); + z=_mm_sub_ps(z,b.z); + } + + inline void operator*=(FourVectors const &b) //< scale all four vectors per component scale + { + x=_mm_mul_ps(x,b.x); + y=_mm_mul_ps(y,b.y); + z=_mm_mul_ps(z,b.z); + } + + inline void operator*=(float scale) //< uniformly scale all 4 vectors + { + __m128 scalepacked=MMReplicate(scale); + x=_mm_mul_ps(x,scalepacked); + y=_mm_mul_ps(y,scalepacked); + z=_mm_mul_ps(z,scalepacked); + } + + inline void operator*=(__m128 scale) //< scale + { + x=_mm_mul_ps(x,scale); + y=_mm_mul_ps(y,scale); + z=_mm_mul_ps(z,scale); + } + + inline __m128 operator*(FourVectors const &b) const //< 4 dot products + { + __m128 dot=_mm_mul_ps(x,b.x); + dot=_mm_add_ps(dot,_mm_mul_ps(y,b.y)); + dot=_mm_add_ps(dot,_mm_mul_ps(z,b.z)); + return dot; + } + + inline __m128 operator*(Vector const &b) const //< dot product all 4 vectors with 1 vector + { + __m128 dot=_mm_mul_ps(x,MMReplicate(b.x)); + dot=_mm_add_ps(dot,_mm_mul_ps(y,MMReplicate(b.y))); + dot=_mm_add_ps(dot,_mm_mul_ps(z,MMReplicate(b.z))); + return dot; + } + + + inline void VProduct(FourVectors const &b) //< component by component mul + { + x=_mm_mul_ps(x,b.x); + y=_mm_mul_ps(y,b.y); + z=_mm_mul_ps(z,b.z); + } + inline void MakeReciprocal(void) //< (x,y,z)=(1/x,1/y,1/z) + { + x=MMReciprocal(x); + y=MMReciprocal(y); + z=MMReciprocal(z); + } + + inline void MakeReciprocalSaturate(void) //< (x,y,z)=(1/x,1/y,1/z), 1/0=1.0e23 + { + x=MMReciprocalSaturate(x); + y=MMReciprocalSaturate(y); + z=MMReciprocalSaturate(z); + } + + // X(),Y(),Z() - get at the desired component of the i'th (0..3) vector. + inline float const &X(int idx) const + { +#ifdef _LINUX + return ((l_m128*)&x)->m128_f32[idx]; +#else + return x.m128_f32[idx]; +#endif + } + + inline float const &Y(int idx) const + { +#ifdef _LINUX + return ((l_m128*)&y)->m128_f32[idx]; +#else + return y.m128_f32[idx]; +#endif + } + inline float const &Z(int idx) const + { +#ifdef _LINUX + return ((l_m128*)&z)->m128_f32[idx]; +#else + return z.m128_f32[idx]; +#endif + } + + // X(),Y(),Z() - get at the desired component of the i'th (0..3) vector. + inline float &X(int idx) + { +#ifdef _LINUX + return ((l_m128*)&x)->m128_f32[idx]; +#else + return x.m128_f32[idx]; +#endif + } + + inline float &Y(int idx) + { +#ifdef _LINUX + return ((l_m128*)&y)->m128_f32[idx]; +#else + return y.m128_f32[idx]; +#endif + } + inline float &Z(int idx) + { +#ifdef _LINUX + return ((l_m128*)&z)->m128_f32[idx]; +#else + return z.m128_f32[idx]; +#endif + } + + inline Vector Vec(int idx) const //< unpack one of the vectors + { + return Vector(X(idx),Y(idx),Z(idx)); + } + + FourVectors(void) + { + } + + /// LoadAndSwizzle - load 4 Vectors into a FourVectors, perofrming transpose op + inline void LoadAndSwizzle(Vector const &a, Vector const &b, Vector const &c, Vector const &d) + { + __m128 row0=_mm_loadu_ps(&(a.x)); + __m128 row1=_mm_loadu_ps(&(b.x)); + __m128 row2=_mm_loadu_ps(&(c.x)); + __m128 row3=_mm_loadu_ps(&(d.x)); + // now, matrix is: + // x y z ? + // x y z ? + // x y z ? + // x y z ? + _MM_TRANSPOSE4_PS(row0,row1,row2,row3); + x=row0; + y=row1; + z=row2; + } + + /// LoadAndSwizzleAligned - load 4 Vectors into a FourVectors, perofrming transpose op. + /// all 4 vectors muyst be 128 bit boundary + inline void LoadAndSwizzleAligned(Vector const &a, Vector const &b, Vector const &c, Vector const &d) + { + __m128 row0=_mm_load_ps(&(a.x)); + __m128 row1=_mm_load_ps(&(b.x)); + __m128 row2=_mm_load_ps(&(c.x)); + __m128 row3=_mm_load_ps(&(d.x)); + // now, matrix is: + // x y z ? + // x y z ? + // x y z ? + // x y z ? + _MM_TRANSPOSE4_PS(row0,row1,row2,row3); + x=row0; + y=row1; + z=row2; + } + + /// return the squared length of all 4 vectors + inline __m128 length2(void) const + { + return (*this)*(*this); + } + + /// return the approximate length of all 4 vectors. uses the sse sqrt approximation instr + inline __m128 length(void) const + { + return _mm_sqrt_ps(length2()); + } + + /// normalize all 4 vectors in place. not mega-accurate (uses sse reciprocal approximation instr) + inline void VectorNormalizeFast(void) + { + __m128 mag_sq=(*this)*(*this); // length^2 + (*this) *= _mm_rsqrt_ps(mag_sq); // *(1.0/sqrt(length^2)) + } + + /// normnalize all 4 vectors in place. uses newton iteration for higher precision results than + /// from VectorNormalizeFast. + inline void VectorNormalize(void) + { + static __m128 FourThrees={3.,3.,3.,3.}; + __m128 mag_sq=(*this)*(*this); // length^2 + __m128 guess=_mm_rsqrt_ps(mag_sq); + // newton iteration for 1/sqrt(x) : y(n+1)=1/2 (y(n)*(3-x*y(n)^2)); + guess=_mm_mul_ps(guess,_mm_sub_ps(FourThrees,_mm_mul_ps(mag_sq,_mm_mul_ps(guess,guess)))); + guess=_mm_mul_ps(Four_PointFives,guess); + (*this) *= guess; + } + + /// construct a FourVectors from 4 separate Vectors + inline FourVectors(Vector const &a, Vector const &b, Vector const &c, Vector const &d) + { + LoadAndSwizzle(a,b,c,d); + } + +}; + +/// form 4 cross products +inline FourVectors operator ^(const FourVectors &a, const FourVectors &b) +{ + FourVectors ret; + ret.x=_mm_sub_ps(_mm_mul_ps(a.y,b.z),_mm_mul_ps(a.z,b.y)); + ret.y=_mm_sub_ps(_mm_mul_ps(a.z,b.x),_mm_mul_ps(a.x,b.z)); + ret.z=_mm_sub_ps(_mm_mul_ps(a.x,b.y),_mm_mul_ps(a.y,b.x)); + return ret; +} + +/// component-by-componentwise MAX operator +inline FourVectors maximum(const FourVectors &a, const FourVectors &b) +{ + FourVectors ret; + ret.x=_mm_max_ps(a.x,b.x); + ret.y=_mm_max_ps(a.y,b.y); + ret.z=_mm_max_ps(a.z,b.z); + return ret; +} + +/// component-by-componentwise MIN operator +inline FourVectors minimum(const FourVectors &a, const FourVectors &b) +{ + FourVectors ret; + ret.x=_mm_min_ps(a.x,b.x); + ret.y=_mm_min_ps(a.y,b.y); + ret.z=_mm_min_ps(a.z,b.z); + return ret; +} + + +/// check an m128 for all zeros. Not sure if this is the best way +inline bool IsAllZeros(__m128 var) +{ +#ifdef _WIN32 + __declspec(align(16)) int32 myints[4]; +#elif _LINUX + int32 myints[4] __attribute__ ((aligned (16))); +#endif + void *ints = &myints; + _mm_store_ps(static_cast(ints),var); + return (myints[0]|myints[1]|myints[2]|myints[3])==0; +} + +/// calculate the absolute value of a packed single +inline __m128 fabs(__m128 x) +{ +#ifdef _WIN32 + static __declspec(align(16)) int32 clear_signmask[4]= + {0x7fffffff,0x7fffffff,0x7fffffff,0x7fffffff}; +#elif _LINUX + static int32 clear_signmask[4] __attribute__ ((aligned (16)))= + {0x7fffffff,0x7fffffff,0x7fffffff,0x7fffffff}; +#endif + void *mask = &clear_signmask; + return _mm_and_ps(x,_mm_load_ps(static_cast(mask))); +} + +/// negate all four components of an sse packed single +inline __m128 fnegate(__m128 x) +{ +#ifdef _WIN32 + static __declspec(align(16)) int32 signmask[4]= + {0x80000000,0x80000000,0x80000000,0x80000000}; +#elif _LINUX + static int32 signmask[4] __attribute__ ((aligned (16)))= + {0x80000000,0x80000000,0x80000000,0x80000000}; +#endif + void *mask = &signmask; + return _mm_xor_ps(x,_mm_load_ps(static_cast(mask))); +} + +__m128 PowSSE_FixedPoint_Exponent(__m128 x, int exponent); + +// PowSSE - raise an sse register to a power. This is analogous to the C pow() function, with some +// restictions: fractional exponents are only handled with 2 bits of precision. Basically, +// fractions of 0,.25,.5, and .75 are handled. PowSSE(x,.30) will be the same as PowSSE(x,.25). +// negative and fractional powers are handled by the SSE reciprocal and square root approximation +// instructions and so are not especially accurate ----Note that this routine does not raise +// numeric exceptions because it uses SSE--- This routine is O(log2(exponent)). +inline __m128 PowSSE(__m128 x, float exponent) +{ + return PowSSE_FixedPoint_Exponent(x,(int) (4.0*exponent)); +} + +#define SSE1234(x) ((x).m128_f32[0]),((x).m128_f32[1]),((x).m128_f32[2]),((x).m128_f32[3]) + +#endif // WIN32 + + + + + +//#endif diff --git a/public/measure_section.cpp b/public/measure_section.cpp index a27ccf20..441192d6 100644 --- a/public/measure_section.cpp +++ b/public/measure_section.cpp @@ -1,271 +1,271 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// -#include -#include -#include "basetypes.h" -#include "measure_section.h" -#include "convar.h" - - -// Static members -CMeasureSection *CMeasureSection::s_pSections = 0; -int CMeasureSection::s_nCount = 0; -double CMeasureSection::m_dNextResort = 0.0; - -ConVar measure_resort( "measure_resort", "1.0", 0, "How often to re-sort profiling sections\n" ); -ConVar game_speeds( "game_speeds","0" ); -extern ConVar host_speeds; - -//----------------------------------------------------------------------------- -// Purpose: Creates a profiling section -// Input : *name - name of the section ( allocated on stack hopefully ) -//----------------------------------------------------------------------------- -CMeasureSection::CMeasureSection( const char *name ) -{ - // Just point at name since it's static - m_pszName = name; - - // Clear accumulators - Reset(); - SortReset(); - m_dMaxTime.Init(); - - // Link into master list - m_pNext = s_pSections; - s_pSections = this; - // Update count - s_nCount++; -} - -//----------------------------------------------------------------------------- -// Purpose: Destroys the object -//----------------------------------------------------------------------------- -CMeasureSection::~CMeasureSection( void ) -{ - m_pNext = NULL; - s_nCount--; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CMeasureSection::UpdateMax( void ) -{ - if (m_dMaxTime.IsLessThan(m_dAccumulatedTime)) - { - m_dMaxTime.Init(); - CCycleCount::Add(m_dMaxTime,m_dAccumulatedTime,m_dMaxTime); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CMeasureSection::Reset( void ) -{ - m_dAccumulatedTime.Init(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CMeasureSection::SortReset( void ) -{ - m_dTotalTime.Init(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const char -//----------------------------------------------------------------------------- -const char *CMeasureSection::GetName( void ) -{ - return m_pszName; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : -//----------------------------------------------------------------------------- -CCycleCount const& CMeasureSection::GetTotalTime( void ) -{ - return m_dTotalTime; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : -//----------------------------------------------------------------------------- -CCycleCount const& CMeasureSection::GetTime( void ) -{ - return m_dAccumulatedTime; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : -//----------------------------------------------------------------------------- -CCycleCount const& CMeasureSection::GetMaxTime( void ) -{ - return m_dMaxTime; -} - -//----------------------------------------------------------------------------- -// Purpose: Accumulates a timeslice -// Input : time - -//----------------------------------------------------------------------------- -void CMeasureSection::AddTime( CCycleCount const &rCount ) -{ - CCycleCount::Add(m_dAccumulatedTime, rCount, m_dAccumulatedTime); - CCycleCount::Add(m_dTotalTime, rCount, m_dTotalTime); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : CMeasureSection -//----------------------------------------------------------------------------- -CMeasureSection *CMeasureSection::GetNext( void ) -{ - return m_pNext; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : CMeasureSection -//----------------------------------------------------------------------------- -CMeasureSection *CMeasureSection::GetList( void ) -{ - return s_pSections; -} - -//----------------------------------------------------------------------------- -// Purpose: Compares accumulated time for two sections -// Input : ppms1 - -// ppms2 - -// Output : static int -//----------------------------------------------------------------------------- -static int SectionCompare( const void* ppms1,const void* ppms2 ) -{ - CMeasureSection* pms1 = *(CMeasureSection**)ppms1; - CMeasureSection* pms2 = *(CMeasureSection**)ppms2; - - if ( pms1->GetTotalTime().IsLessThan(pms2->GetTotalTime()) ) - return 1; - else - return -1; -} - -//----------------------------------------------------------------------------- -// Purpose: Sorts sections by time usage -//----------------------------------------------------------------------------- -void CMeasureSection::SortSections( void ) -{ - // Not enough to be sortable - if ( s_nCount <= 1 ) - return; - - CMeasureSection *sortarray[ 128 ]; - CMeasureSection *ms; - - memset(sortarray,sizeof(CMeasureSection*)*128,0); - - ms = GetList(); - int i; - int c = 0; - while ( ms ) - { - sortarray[ c++ ] = ms; - ms = ms->GetNext(); - } - - // Sort the array alphabetically - qsort( sortarray, c , sizeof( CMeasureSection * ), SectionCompare ); - - // Fix next pointers - for ( i = 0; i < c-1; i++ ) - { - sortarray[ i ]->m_pNext = sortarray[ i + 1 ]; - } - sortarray[i]->m_pNext = NULL; - - // Point head of list at it - s_pSections = sortarray[ 0 ]; -} - -//----------------------------------------------------------------------------- -// Purpose: Creates an instance for timing a section -// Input : *ms - -//----------------------------------------------------------------------------- -CMeasureSectionInstance::CMeasureSectionInstance( CMeasureSection *ms ) -{ - // Remember where to put accumulated time - m_pMS = ms; - - if ( host_speeds.GetInt() < 3 && !game_speeds.GetInt()) - return; - - // Get initial timestamp - m_Timer.Start(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CMeasureSectionInstance::~CMeasureSectionInstance( void ) -{ - if ( host_speeds.GetInt() < 3 && !game_speeds.GetInt()) - return; - - // Get final timestamp - m_Timer.End(); - - // Add time to section - m_pMS->AddTime( m_Timer.GetDuration() ); -} - -//----------------------------------------------------------------------------- -// Purpose: Re-sort all data and determine whether sort keys should be reset too ( after -// re-doing sort if needed ). -//----------------------------------------------------------------------------- -void ResetTimeMeasurements( void ) -{ -#if defined( _DEBUG ) || defined( FORCE_MEASURE ) - bool sort_reset = false; - - // Time to redo sort? - if ( measure_resort.GetFloat() > 0.0 && - GetRealTime() >= CMeasureSection::m_dNextResort ) - { - // Redo it - CMeasureSection::SortSections(); - // Set next time - CMeasureSection::m_dNextResort = GetRealTime() + measure_resort.GetFloat(); - // Flag to reset sort accumulator, too - sort_reset = true; - } - - // Iterate through the sections now - CMeasureSection *p = CMeasureSection::GetList(); - while ( p ) - { - // Reset regular accum. - p->Reset(); - // Reset sort accum less often - if ( sort_reset ) - { - p->SortReset(); - } - p = p->GetNext(); - } -#endif -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// +#include +#include +#include "basetypes.h" +#include "measure_section.h" +#include "convar.h" + + +// Static members +CMeasureSection *CMeasureSection::s_pSections = 0; +int CMeasureSection::s_nCount = 0; +double CMeasureSection::m_dNextResort = 0.0; + +ConVar measure_resort( "measure_resort", "1.0", 0, "How often to re-sort profiling sections\n" ); +ConVar game_speeds( "game_speeds","0" ); +extern ConVar host_speeds; + +//----------------------------------------------------------------------------- +// Purpose: Creates a profiling section +// Input : *name - name of the section ( allocated on stack hopefully ) +//----------------------------------------------------------------------------- +CMeasureSection::CMeasureSection( const char *name ) +{ + // Just point at name since it's static + m_pszName = name; + + // Clear accumulators + Reset(); + SortReset(); + m_dMaxTime.Init(); + + // Link into master list + m_pNext = s_pSections; + s_pSections = this; + // Update count + s_nCount++; +} + +//----------------------------------------------------------------------------- +// Purpose: Destroys the object +//----------------------------------------------------------------------------- +CMeasureSection::~CMeasureSection( void ) +{ + m_pNext = NULL; + s_nCount--; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMeasureSection::UpdateMax( void ) +{ + if (m_dMaxTime.IsLessThan(m_dAccumulatedTime)) + { + m_dMaxTime.Init(); + CCycleCount::Add(m_dMaxTime,m_dAccumulatedTime,m_dMaxTime); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMeasureSection::Reset( void ) +{ + m_dAccumulatedTime.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMeasureSection::SortReset( void ) +{ + m_dTotalTime.Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CMeasureSection::GetName( void ) +{ + return m_pszName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : +//----------------------------------------------------------------------------- +CCycleCount const& CMeasureSection::GetTotalTime( void ) +{ + return m_dTotalTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : +//----------------------------------------------------------------------------- +CCycleCount const& CMeasureSection::GetTime( void ) +{ + return m_dAccumulatedTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : +//----------------------------------------------------------------------------- +CCycleCount const& CMeasureSection::GetMaxTime( void ) +{ + return m_dMaxTime; +} + +//----------------------------------------------------------------------------- +// Purpose: Accumulates a timeslice +// Input : time - +//----------------------------------------------------------------------------- +void CMeasureSection::AddTime( CCycleCount const &rCount ) +{ + CCycleCount::Add(m_dAccumulatedTime, rCount, m_dAccumulatedTime); + CCycleCount::Add(m_dTotalTime, rCount, m_dTotalTime); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CMeasureSection +//----------------------------------------------------------------------------- +CMeasureSection *CMeasureSection::GetNext( void ) +{ + return m_pNext; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CMeasureSection +//----------------------------------------------------------------------------- +CMeasureSection *CMeasureSection::GetList( void ) +{ + return s_pSections; +} + +//----------------------------------------------------------------------------- +// Purpose: Compares accumulated time for two sections +// Input : ppms1 - +// ppms2 - +// Output : static int +//----------------------------------------------------------------------------- +static int SectionCompare( const void* ppms1,const void* ppms2 ) +{ + CMeasureSection* pms1 = *(CMeasureSection**)ppms1; + CMeasureSection* pms2 = *(CMeasureSection**)ppms2; + + if ( pms1->GetTotalTime().IsLessThan(pms2->GetTotalTime()) ) + return 1; + else + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Sorts sections by time usage +//----------------------------------------------------------------------------- +void CMeasureSection::SortSections( void ) +{ + // Not enough to be sortable + if ( s_nCount <= 1 ) + return; + + CMeasureSection *sortarray[ 128 ]; + CMeasureSection *ms; + + memset(sortarray,sizeof(CMeasureSection*)*128,0); + + ms = GetList(); + int i; + int c = 0; + while ( ms ) + { + sortarray[ c++ ] = ms; + ms = ms->GetNext(); + } + + // Sort the array alphabetically + qsort( sortarray, c , sizeof( CMeasureSection * ), SectionCompare ); + + // Fix next pointers + for ( i = 0; i < c-1; i++ ) + { + sortarray[ i ]->m_pNext = sortarray[ i + 1 ]; + } + sortarray[i]->m_pNext = NULL; + + // Point head of list at it + s_pSections = sortarray[ 0 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates an instance for timing a section +// Input : *ms - +//----------------------------------------------------------------------------- +CMeasureSectionInstance::CMeasureSectionInstance( CMeasureSection *ms ) +{ + // Remember where to put accumulated time + m_pMS = ms; + + if ( host_speeds.GetInt() < 3 && !game_speeds.GetInt()) + return; + + // Get initial timestamp + m_Timer.Start(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMeasureSectionInstance::~CMeasureSectionInstance( void ) +{ + if ( host_speeds.GetInt() < 3 && !game_speeds.GetInt()) + return; + + // Get final timestamp + m_Timer.End(); + + // Add time to section + m_pMS->AddTime( m_Timer.GetDuration() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Re-sort all data and determine whether sort keys should be reset too ( after +// re-doing sort if needed ). +//----------------------------------------------------------------------------- +void ResetTimeMeasurements( void ) +{ +#if defined( _DEBUG ) || defined( FORCE_MEASURE ) + bool sort_reset = false; + + // Time to redo sort? + if ( measure_resort.GetFloat() > 0.0 && + GetRealTime() >= CMeasureSection::m_dNextResort ) + { + // Redo it + CMeasureSection::SortSections(); + // Set next time + CMeasureSection::m_dNextResort = GetRealTime() + measure_resort.GetFloat(); + // Flag to reset sort accumulator, too + sort_reset = true; + } + + // Iterate through the sections now + CMeasureSection *p = CMeasureSection::GetList(); + while ( p ) + { + // Reset regular accum. + p->Reset(); + // Reset sort accum less often + if ( sort_reset ) + { + p->SortReset(); + } + p = p->GetNext(); + } +#endif +} diff --git a/public/measure_section.h b/public/measure_section.h index 619063eb..6922b6a7 100644 --- a/public/measure_section.h +++ b/public/measure_section.h @@ -1,127 +1,127 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#if !defined( MEASURE_SECTION_H ) -#define MEASURE_SECTION_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "tier0/fasttimer.h" -#include "convar.h" - - -// This is the macro to use in your code to measure until the code goes -// out of scope -#if defined( _DEBUG ) || defined( FORCE_MEASURE ) -#define MEASURECODE( description ) \ - static CMeasureSection _xxx_ms( description ); \ - CMeasureSectionInstance _xxx_ms_inst( &_xxx_ms ); -#else -#define MEASURECODE( description ) -#endif - - -// ------------------------------------------------------------------------------------ // -// These things must exist in the executable for the CMeasureSection code to work. -// ------------------------------------------------------------------------------------ // -float GetRealTime(); // Get the clock's time. - -extern ConVar game_speeds; -extern ConVar measure_resort; -// ------------------------------------------------------------------------------------ // - - - -// Called once per frame to allow any necessary measurements to latch -void ResetTimeMeasurements( void ); - -//----------------------------------------------------------------------------- -// Purpose: Accumulates time for the named section -//----------------------------------------------------------------------------- -class CMeasureSection -{ -public: - // Allows for measuring named section - CMeasureSection( const char *name ); - virtual ~CMeasureSection( void ); - - - // Update max value hit - void UpdateMax( void ); - // Reset totals - void Reset( void ); - // Reset sortable totals - void SortReset( void ); - // Get static name of section - const char *GetName( void ); - - // Get accumulated time - CCycleCount const& GetTotalTime( void ); - - CCycleCount const& GetTime(); - - CCycleCount const& GetMaxTime(); - - // Add in some time - void AddTime( CCycleCount const &rCount ); - - // Get next section in chain - CMeasureSection *GetNext( void ); - - // Get head of list of all sections - static CMeasureSection *GetList( void ); - // Sort all sections by most time consuming - static void SortSections( void ); - -public: - // Time when list should be sorted again - static double m_dNextResort; - -private: - // Accumulated time for section - CCycleCount m_dAccumulatedTime; - - // Max time for section - CCycleCount m_dMaxTime; - - // Elapsed time for section - CCycleCount m_dTotalTime; - - // Name of section - const char *m_pszName; - // Next section in chain - CMeasureSection *m_pNext; - // Head of section list - static CMeasureSection *s_pSections; - // Quick total for doing sorts faster - static int s_nCount; -}; - -//----------------------------------------------------------------------------- -// Purpose: On construction marks time and on destruction adds time to -// parent CMeasureSection object -//----------------------------------------------------------------------------- -class CMeasureSectionInstance -{ -public: - // Constructor: Points to object to accumulate time into - CMeasureSectionInstance( CMeasureSection *ms ); - // Destructor: Latches accumulated time - virtual ~CMeasureSectionInstance( void ); - -private: - // Time of construction - CFastTimer m_Timer; - - // Where to place elapsed time - CMeasureSection *m_pMS; -}; - -#endif // MEASURE_SECTION_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( MEASURE_SECTION_H ) +#define MEASURE_SECTION_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier0/fasttimer.h" +#include "convar.h" + + +// This is the macro to use in your code to measure until the code goes +// out of scope +#if defined( _DEBUG ) || defined( FORCE_MEASURE ) +#define MEASURECODE( description ) \ + static CMeasureSection _xxx_ms( description ); \ + CMeasureSectionInstance _xxx_ms_inst( &_xxx_ms ); +#else +#define MEASURECODE( description ) +#endif + + +// ------------------------------------------------------------------------------------ // +// These things must exist in the executable for the CMeasureSection code to work. +// ------------------------------------------------------------------------------------ // +float GetRealTime(); // Get the clock's time. + +extern ConVar game_speeds; +extern ConVar measure_resort; +// ------------------------------------------------------------------------------------ // + + + +// Called once per frame to allow any necessary measurements to latch +void ResetTimeMeasurements( void ); + +//----------------------------------------------------------------------------- +// Purpose: Accumulates time for the named section +//----------------------------------------------------------------------------- +class CMeasureSection +{ +public: + // Allows for measuring named section + CMeasureSection( const char *name ); + virtual ~CMeasureSection( void ); + + + // Update max value hit + void UpdateMax( void ); + // Reset totals + void Reset( void ); + // Reset sortable totals + void SortReset( void ); + // Get static name of section + const char *GetName( void ); + + // Get accumulated time + CCycleCount const& GetTotalTime( void ); + + CCycleCount const& GetTime(); + + CCycleCount const& GetMaxTime(); + + // Add in some time + void AddTime( CCycleCount const &rCount ); + + // Get next section in chain + CMeasureSection *GetNext( void ); + + // Get head of list of all sections + static CMeasureSection *GetList( void ); + // Sort all sections by most time consuming + static void SortSections( void ); + +public: + // Time when list should be sorted again + static double m_dNextResort; + +private: + // Accumulated time for section + CCycleCount m_dAccumulatedTime; + + // Max time for section + CCycleCount m_dMaxTime; + + // Elapsed time for section + CCycleCount m_dTotalTime; + + // Name of section + const char *m_pszName; + // Next section in chain + CMeasureSection *m_pNext; + // Head of section list + static CMeasureSection *s_pSections; + // Quick total for doing sorts faster + static int s_nCount; +}; + +//----------------------------------------------------------------------------- +// Purpose: On construction marks time and on destruction adds time to +// parent CMeasureSection object +//----------------------------------------------------------------------------- +class CMeasureSectionInstance +{ +public: + // Constructor: Points to object to accumulate time into + CMeasureSectionInstance( CMeasureSection *ms ); + // Destructor: Latches accumulated time + virtual ~CMeasureSectionInstance( void ); + +private: + // Time of construction + CFastTimer m_Timer; + + // Where to place elapsed time + CMeasureSection *m_pMS; +}; + +#endif // MEASURE_SECTION_H diff --git a/public/model_types.h b/public/model_types.h index ef5b05c7..7573577b 100644 --- a/public/model_types.h +++ b/public/model_types.h @@ -1,37 +1,37 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// -#if !defined( MODEL_TYPES_H ) -#define MODEL_TYPES_H -#ifdef _WIN32 -#pragma once -#endif - -#define STUDIO_NONE 0x00000000 -#define STUDIO_RENDER 0x00000001 -#define STUDIO_VIEWXFORMATTACHMENTS 0x00000002 -#define STUDIO_DRAWTRANSLUCENTSUBMODELS 0x00000004 -#define STUDIO_TWOPASS 0x00000008 -#define STUDIO_STATIC_LIGHTING 0x00000010 -#define STUDIO_WIREFRAME 0x00000020 -#define STUDIO_ITEM_BLINK 0x00000040 -#define STUDIO_NOSHADOWS 0x00000080 - -// Not a studio flag, but used to flag model as a non-sorting brush model -#define STUDIO_TRANSPARENCY 0x80000000 - - -enum modtype_t -{ - mod_bad = 0, - mod_brush, - mod_sprite, - mod_studio -}; - -#endif // MODEL_TYPES_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( MODEL_TYPES_H ) +#define MODEL_TYPES_H +#ifdef _WIN32 +#pragma once +#endif + +#define STUDIO_NONE 0x00000000 +#define STUDIO_RENDER 0x00000001 +#define STUDIO_VIEWXFORMATTACHMENTS 0x00000002 +#define STUDIO_DRAWTRANSLUCENTSUBMODELS 0x00000004 +#define STUDIO_TWOPASS 0x00000008 +#define STUDIO_STATIC_LIGHTING 0x00000010 +#define STUDIO_WIREFRAME 0x00000020 +#define STUDIO_ITEM_BLINK 0x00000040 +#define STUDIO_NOSHADOWS 0x00000080 + +// Not a studio flag, but used to flag model as a non-sorting brush model +#define STUDIO_TRANSPARENCY 0x80000000 + + +enum modtype_t +{ + mod_bad = 0, + mod_brush, + mod_sprite, + mod_studio +}; + +#endif // MODEL_TYPES_H diff --git a/public/networkvar.h b/public/networkvar.h index d19258e9..da360bb8 100644 --- a/public/networkvar.h +++ b/public/networkvar.h @@ -19,8 +19,9 @@ #endif +#ifdef _MSC_VER #pragma warning( disable : 4284 ) // warning C4284: return type for 'CNetworkVarT::operator ->' is 'int *' (ie; not a UDT or reference to a UDT. Will produce errors if applied using infix notation) - +#endif #define MyOffsetOf( type, var ) ( (int)&((type*)0)->var ) @@ -352,7 +353,7 @@ protected: { if ( out != in ) { - NetworkStateChanged(); + this->NetworkStateChanged(); out = in; } } @@ -436,7 +437,7 @@ private: { if ( out != in ) { - NetworkStateChanged(); + this->NetworkStateChanged(); out = in; } } @@ -523,7 +524,7 @@ private: { if ( out != in ) { - NetworkStateChanged(); + this->NetworkStateChanged(); out = in; } } @@ -561,7 +562,7 @@ private: { if ( CNetworkHandleBase::m_Value != val ) { - NetworkStateChanged(); + this->NetworkStateChanged(); CNetworkHandleBase::m_Value = val; } return val; diff --git a/public/resourcetags.cpp b/public/resourcetags.cpp index e4685953..278e5d1a 100644 --- a/public/resourcetags.cpp +++ b/public/resourcetags.cpp @@ -1,28 +1,28 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// -#include "resourcetags.h" -#include "tier0/dbg.h" -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#ifdef _XBOX -static int s_resourceTag = RESOURCE_TAG_NONE; - -void Sys_SetResourceTag( int tag ) -{ - s_resourceTag = tag; -} - -int Sys_GetResourceTag( void ) -{ -#if 0 - // control flow issue, no resource should be instanced during an indeterminate state - Assert( s_resourceTag != RESOURCE_TAG_NONE ); -#endif - - return s_resourceTag; -} -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "resourcetags.h" +#include "tier0/dbg.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef _XBOX +static int s_resourceTag = RESOURCE_TAG_NONE; + +void Sys_SetResourceTag( int tag ) +{ + s_resourceTag = tag; +} + +int Sys_GetResourceTag( void ) +{ +#if 0 + // control flow issue, no resource should be instanced during an indeterminate state + Assert( s_resourceTag != RESOURCE_TAG_NONE ); +#endif + + return s_resourceTag; +} +#endif diff --git a/public/riff.cpp b/public/riff.cpp index f0064868..c7e55a28 100644 --- a/public/riff.cpp +++ b/public/riff.cpp @@ -1,504 +1,506 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// Avoid these warnings: -#pragma warning(disable : 4512) // warning C4512: 'InFileRIFF' : assignment operator could not be generated -#pragma warning(disable : 4514) // warning C4514: 'RIFFName' : unreferenced inline function has been removed - -#include "riff.h" -#include -#include -#include "tier0/dbg.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#if 0 -//----------------------------------------------------------------------------- -// Purpose: Test code that implements the interface on stdio -//----------------------------------------------------------------------------- -class StdIOReadBinary : public IFileReadBinary -{ -public: - int open( const char *pFileName ) - { - return (int)fopen( pFileName, "rb" ); - } - - int read( void *pOutput, int size, int file ) - { - FILE *fp = (FILE *)file; - - return fread( pOutput, size, 1, fp ); - } - - void seek( int file, int pos ) - { - fseek( (FILE *)file, pos, SEEK_SET ); - } - - unsigned int tell( int file ) - { - return ftell( (FILE *)file ); - } - - unsigned int size( int file ) - { - FILE *fp = (FILE *)file; - if ( !fp ) - return 0; - - unsigned int pos = ftell( fp ); - fseek( fp, 0, SEEK_END ); - unsigned int size = ftell( fp ); - - fseek( fp, pos, SEEK_SET ); - return size; - } - - void close( int file ) - { - FILE *fp = (FILE *)file; - - fclose( fp ); - } -}; -#endif - - -#define RIFF_ID MAKEID('R','I','F','F') - - -//----------------------------------------------------------------------------- -// Purpose: Opens a RIFF file using the given I/O mechanism -// Input : *pFileName -// &io - I/O interface -//----------------------------------------------------------------------------- -InFileRIFF::InFileRIFF( const char *pFileName, IFileReadBinary &io ) : m_io(io) -{ - m_file = m_io.open( pFileName ); - - int riff = 0; - if ( !m_file ) - { - m_riffSize = 0; - m_riffName = 0; - return; - } - m_io.read( &riff, 4, m_file ); - if ( riff != RIFF_ID ) - { - printf("Not a RIFF File\n" ); - m_riffSize = 0; - } - else - { - // we store size as size of all chunks - // subtract off the RIFF form type (e.g. 'WAVE', 4 bytes) - m_riffSize = ReadInt() - 4; - m_riffName = ReadInt(); - - // HACKHACK: LWV files don't obey the RIFF format!!! - // Do this or miss the linguistic chunks at the end. Lame! - // subtract off 12 bytes for (RIFF, size, WAVE) - m_riffSize = m_io.size( m_file ) - 12; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Close the file -//----------------------------------------------------------------------------- -InFileRIFF::~InFileRIFF( void ) -{ - m_io.close( m_file ); -} - - -//----------------------------------------------------------------------------- -// Purpose: read a 4-byte int out of the stream -// Output : int = read value, default is zero -//----------------------------------------------------------------------------- -int InFileRIFF::ReadInt( void ) -{ - int tmp = 0; - m_io.read( &tmp, sizeof(int), m_file ); - - return tmp; -} - -//----------------------------------------------------------------------------- -// Purpose: Read a block of binary data -// Input : *pOutput - pointer to destination memory -// dataSize - size of block to read -// Output : int - number of bytes read -//----------------------------------------------------------------------------- -int InFileRIFF::ReadData( void *pOutput, int dataSize ) -{ - int count = m_io.read( pOutput, dataSize, m_file ); - - return count; -} - - -//----------------------------------------------------------------------------- -// Purpose: Gets the file position -// Output : int (bytes from start of file) -//----------------------------------------------------------------------------- -int InFileRIFF::PositionGet( void ) -{ - return m_io.tell( m_file ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Seek to file position -// Input : position - bytes from start of file -//----------------------------------------------------------------------------- -void InFileRIFF::PositionSet( int position ) -{ - m_io.seek( m_file, position ); -} - -//----------------------------------------------------------------------------- -// Purpose: Used to write a RIFF format file -//----------------------------------------------------------------------------- -OutFileRIFF::OutFileRIFF( const char *pFileName, IFileWriteBinary &io ) : m_io( io ) -{ - m_file = m_io.create( pFileName ); - - if ( !m_file ) - return; - - int riff = RIFF_ID; - m_io.write( &riff, 4, m_file ); - - m_riffSize = 0; - m_nNamePos = m_io.tell( m_file ); - - // Save room for the size and name now - WriteInt( 0 ); - - // Write out the name - WriteInt( RIFF_WAVE ); - - m_bUseIncorrectLISETLength = false; - m_nLISETSize = 0; -} - -OutFileRIFF::~OutFileRIFF( void ) -{ - if ( !IsValid() ) - return; - - unsigned int size = m_io.tell( m_file ) -8; - m_io.seek( m_file, m_nNamePos ); - - if ( m_bUseIncorrectLISETLength ) - { - size = m_nLISETSize - 8; - } - - WriteInt( size ); - m_io.close( m_file ); -} - -void OutFileRIFF::HasLISETData( int position ) -{ - m_bUseIncorrectLISETLength = true; - m_nLISETSize = position; -} - -bool OutFileRIFF::WriteInt( int number ) -{ - if ( !IsValid() ) - return false; - - m_io.write( &number, sizeof( int ), m_file ); - return true; -} - -bool OutFileRIFF::WriteData( void *pOutput, int dataSize ) -{ - if ( !IsValid() ) - return false; - - m_io.write( pOutput, dataSize, m_file ); - return true; -} - -int OutFileRIFF::PositionGet( void ) -{ - if ( !IsValid() ) - return 0; - - return m_io.tell( m_file ); -} - -void OutFileRIFF::PositionSet( int position ) -{ - if ( !IsValid() ) - return; - - m_io.seek( m_file, position ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Create an iterator for the given file -// Input : &riff - riff file -// size - size of file or sub-chunk -//----------------------------------------------------------------------------- -IterateRIFF::IterateRIFF( InFileRIFF &riff, int size ) - : m_riff(riff), m_size(size) -{ - if ( !m_riff.RIFFSize() ) - { - // bad file, just be an empty iterator - ChunkClear(); - return; - } - - // get the position and parse a chunk - m_start = riff.PositionGet(); - ChunkSetup(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Set up a sub-chunk iterator -// Input : &parent - parent iterator -//----------------------------------------------------------------------------- -IterateRIFF::IterateRIFF( IterateRIFF &parent ) - : m_riff(parent.m_riff), m_size(parent.ChunkSize()) -{ - m_start = parent.ChunkFilePosition(); - ChunkSetup(); -} - -//----------------------------------------------------------------------------- -// Purpose: Parse the chunk at the current file position -// This object will iterate over the sub-chunks of this chunk. -// This makes for easy hierarchical parsing -//----------------------------------------------------------------------------- -void IterateRIFF::ChunkSetup( void ) -{ - m_chunkPosition = m_riff.PositionGet(); - - m_chunkName = m_riff.ReadInt(); - m_chunkSize = m_riff.ReadInt(); -} - -//----------------------------------------------------------------------------- -// Purpose: clear chunk setup, ChunkAvailable will return false -//----------------------------------------------------------------------------- -void IterateRIFF::ChunkClear( void ) -{ - m_chunkSize = -1; -} - -//----------------------------------------------------------------------------- -// Purpose: If there are chunks left to read beyond this one, return true -//----------------------------------------------------------------------------- -bool IterateRIFF::ChunkAvailable( void ) -{ - if ( m_chunkSize != -1 && m_chunkSize < 0x10000000 ) - return true; - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Go to the next chunk in the file, return true if there is one. -//----------------------------------------------------------------------------- -bool IterateRIFF::ChunkNext( void ) -{ - if ( !ChunkAvailable() ) - return false; - - int nextPos = m_chunkPosition + 8 + m_chunkSize; - - // chunks are aligned - nextPos += m_chunkSize & 1; - - if ( nextPos >= (m_start + m_size) ) - { - ChunkClear(); - return false; - } - - m_riff.PositionSet( nextPos ); - - ChunkSetup(); - return ChunkAvailable(); - -} - - -//----------------------------------------------------------------------------- -// Purpose: get the chunk FOURCC as an int -// Output : unsigned int -//----------------------------------------------------------------------------- -unsigned int IterateRIFF::ChunkName( void ) -{ - return m_chunkName; -} - - -//----------------------------------------------------------------------------- -// Purpose: get the size of this chunk -// Output : unsigned int -//----------------------------------------------------------------------------- -unsigned int IterateRIFF::ChunkSize( void ) -{ - return m_chunkSize; -} - -//----------------------------------------------------------------------------- -// Purpose: Read the entire chunk into a buffer -// Input : *pOutput - dest buffer -// Output : int bytes read -//----------------------------------------------------------------------------- -int IterateRIFF::ChunkRead( void *pOutput ) -{ - return m_riff.ReadData( pOutput, ChunkSize() ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Read a partial chunk (updates file position for subsequent partial reads). -// Input : *pOutput - dest buffer -// dataSize - partial size -// Output : int - bytes read -//----------------------------------------------------------------------------- -int IterateRIFF::ChunkReadPartial( void *pOutput, int dataSize ) -{ - return m_riff.ReadData( pOutput, dataSize ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Read a 4-byte int -// Output : int - read int -//----------------------------------------------------------------------------- -int IterateRIFF::ChunkReadInt( void ) -{ - return m_riff.ReadInt(); -} - -//----------------------------------------------------------------------------- -// Purpose: Used to iterate over an InFileRIFF -//----------------------------------------------------------------------------- -IterateOutputRIFF::IterateOutputRIFF( OutFileRIFF &riff ) -: m_riff( riff ) -{ - if ( !m_riff.IsValid() ) - return; - - m_start = m_riff.PositionGet(); - m_chunkPosition = m_start; - m_chunkStart = -1; -} - -IterateOutputRIFF::IterateOutputRIFF( IterateOutputRIFF &parent ) - : m_riff(parent.m_riff) -{ - m_start = parent.ChunkFilePosition(); - m_chunkPosition = m_start; - m_chunkStart = -1; -} - -void IterateOutputRIFF::ChunkWrite( unsigned int chunkname, void *pOutput, int size ) -{ - m_chunkPosition = m_riff.PositionGet(); - - m_chunkName = chunkname; - m_chunkSize = size; - - m_riff.WriteInt( chunkname ); - m_riff.WriteInt( size ); - m_riff.WriteData( pOutput, size ); - - m_chunkPosition = m_riff.PositionGet(); - - m_chunkPosition += m_chunkPosition & 1; - - m_riff.PositionSet( m_chunkPosition ); - - m_chunkStart = -1; -} - -void IterateOutputRIFF::ChunkWriteInt( int number ) -{ - m_riff.WriteInt( number ); -} - -void IterateOutputRIFF::ChunkWriteData( void *pOutput, int size ) -{ - m_riff.WriteData( pOutput, size ); -} - -void IterateOutputRIFF::ChunkFinish( void ) -{ - Assert( m_chunkStart != -1 ); - - m_chunkPosition = m_riff.PositionGet(); - - int size = m_chunkPosition - m_chunkStart - 8; - - m_chunkPosition += m_chunkPosition & 1; - - m_riff.PositionSet( m_chunkStart + sizeof( int ) ); - - m_riff.WriteInt( size ); - - m_riff.PositionSet( m_chunkPosition ); - - m_chunkStart = -1; -} - -void IterateOutputRIFF::ChunkStart( unsigned int chunkname ) -{ - Assert( m_chunkStart == -1 ); - - m_chunkStart = m_riff.PositionGet(); - - m_riff.WriteInt( chunkname ); - m_riff.WriteInt( 0 ); -} - -void IterateOutputRIFF::ChunkSetPosition( int position ) -{ - m_riff.PositionSet( position ); -} - -unsigned int IterateOutputRIFF::ChunkGetPosition( void ) -{ - return m_riff.PositionGet(); -} - -void IterateOutputRIFF::CopyChunkData( IterateRIFF& input ) -{ - if ( input.ChunkSize() > 0 ) - { - char *buffer = new char[ input.ChunkSize() ]; - Assert( buffer ); - - input.ChunkRead( buffer ); - - // Don't copy/write the name or size, just the data itself - ChunkWriteData( buffer, input.ChunkSize() ); - - delete[] buffer; - } -} - -void IterateOutputRIFF::SetLISETData( int position ) -{ - m_riff.HasLISETData( position ); -} \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// Avoid these warnings: +#ifdef _MSC_VER +#pragma warning(disable : 4512) // warning C4512: 'InFileRIFF' : assignment operator could not be generated +#pragma warning(disable : 4514) // warning C4514: 'RIFFName' : unreferenced inline function has been removed +#endif + +#include "riff.h" +#include +#include +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if 0 +//----------------------------------------------------------------------------- +// Purpose: Test code that implements the interface on stdio +//----------------------------------------------------------------------------- +class StdIOReadBinary : public IFileReadBinary +{ +public: + int open( const char *pFileName ) + { + return (int)fopen( pFileName, "rb" ); + } + + int read( void *pOutput, int size, int file ) + { + FILE *fp = (FILE *)file; + + return fread( pOutput, size, 1, fp ); + } + + void seek( int file, int pos ) + { + fseek( (FILE *)file, pos, SEEK_SET ); + } + + unsigned int tell( int file ) + { + return ftell( (FILE *)file ); + } + + unsigned int size( int file ) + { + FILE *fp = (FILE *)file; + if ( !fp ) + return 0; + + unsigned int pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + unsigned int size = ftell( fp ); + + fseek( fp, pos, SEEK_SET ); + return size; + } + + void close( int file ) + { + FILE *fp = (FILE *)file; + + fclose( fp ); + } +}; +#endif + + +#define RIFF_ID MAKEID('R','I','F','F') + + +//----------------------------------------------------------------------------- +// Purpose: Opens a RIFF file using the given I/O mechanism +// Input : *pFileName +// &io - I/O interface +//----------------------------------------------------------------------------- +InFileRIFF::InFileRIFF( const char *pFileName, IFileReadBinary &io ) : m_io(io) +{ + m_file = m_io.open( pFileName ); + + int riff = 0; + if ( !m_file ) + { + m_riffSize = 0; + m_riffName = 0; + return; + } + m_io.read( &riff, 4, m_file ); + if ( riff != RIFF_ID ) + { + printf("Not a RIFF File\n" ); + m_riffSize = 0; + } + else + { + // we store size as size of all chunks + // subtract off the RIFF form type (e.g. 'WAVE', 4 bytes) + m_riffSize = ReadInt() - 4; + m_riffName = ReadInt(); + + // HACKHACK: LWV files don't obey the RIFF format!!! + // Do this or miss the linguistic chunks at the end. Lame! + // subtract off 12 bytes for (RIFF, size, WAVE) + m_riffSize = m_io.size( m_file ) - 12; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Close the file +//----------------------------------------------------------------------------- +InFileRIFF::~InFileRIFF( void ) +{ + m_io.close( m_file ); +} + + +//----------------------------------------------------------------------------- +// Purpose: read a 4-byte int out of the stream +// Output : int = read value, default is zero +//----------------------------------------------------------------------------- +int InFileRIFF::ReadInt( void ) +{ + int tmp = 0; + m_io.read( &tmp, sizeof(int), m_file ); + + return tmp; +} + +//----------------------------------------------------------------------------- +// Purpose: Read a block of binary data +// Input : *pOutput - pointer to destination memory +// dataSize - size of block to read +// Output : int - number of bytes read +//----------------------------------------------------------------------------- +int InFileRIFF::ReadData( void *pOutput, int dataSize ) +{ + int count = m_io.read( pOutput, dataSize, m_file ); + + return count; +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets the file position +// Output : int (bytes from start of file) +//----------------------------------------------------------------------------- +int InFileRIFF::PositionGet( void ) +{ + return m_io.tell( m_file ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Seek to file position +// Input : position - bytes from start of file +//----------------------------------------------------------------------------- +void InFileRIFF::PositionSet( int position ) +{ + m_io.seek( m_file, position ); +} + +//----------------------------------------------------------------------------- +// Purpose: Used to write a RIFF format file +//----------------------------------------------------------------------------- +OutFileRIFF::OutFileRIFF( const char *pFileName, IFileWriteBinary &io ) : m_io( io ) +{ + m_file = m_io.create( pFileName ); + + if ( !m_file ) + return; + + int riff = RIFF_ID; + m_io.write( &riff, 4, m_file ); + + m_riffSize = 0; + m_nNamePos = m_io.tell( m_file ); + + // Save room for the size and name now + WriteInt( 0 ); + + // Write out the name + WriteInt( RIFF_WAVE ); + + m_bUseIncorrectLISETLength = false; + m_nLISETSize = 0; +} + +OutFileRIFF::~OutFileRIFF( void ) +{ + if ( !IsValid() ) + return; + + unsigned int size = m_io.tell( m_file ) -8; + m_io.seek( m_file, m_nNamePos ); + + if ( m_bUseIncorrectLISETLength ) + { + size = m_nLISETSize - 8; + } + + WriteInt( size ); + m_io.close( m_file ); +} + +void OutFileRIFF::HasLISETData( int position ) +{ + m_bUseIncorrectLISETLength = true; + m_nLISETSize = position; +} + +bool OutFileRIFF::WriteInt( int number ) +{ + if ( !IsValid() ) + return false; + + m_io.write( &number, sizeof( int ), m_file ); + return true; +} + +bool OutFileRIFF::WriteData( void *pOutput, int dataSize ) +{ + if ( !IsValid() ) + return false; + + m_io.write( pOutput, dataSize, m_file ); + return true; +} + +int OutFileRIFF::PositionGet( void ) +{ + if ( !IsValid() ) + return 0; + + return m_io.tell( m_file ); +} + +void OutFileRIFF::PositionSet( int position ) +{ + if ( !IsValid() ) + return; + + m_io.seek( m_file, position ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Create an iterator for the given file +// Input : &riff - riff file +// size - size of file or sub-chunk +//----------------------------------------------------------------------------- +IterateRIFF::IterateRIFF( InFileRIFF &riff, int size ) + : m_riff(riff), m_size(size) +{ + if ( !m_riff.RIFFSize() ) + { + // bad file, just be an empty iterator + ChunkClear(); + return; + } + + // get the position and parse a chunk + m_start = riff.PositionGet(); + ChunkSetup(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set up a sub-chunk iterator +// Input : &parent - parent iterator +//----------------------------------------------------------------------------- +IterateRIFF::IterateRIFF( IterateRIFF &parent ) + : m_riff(parent.m_riff), m_size(parent.ChunkSize()) +{ + m_start = parent.ChunkFilePosition(); + ChunkSetup(); +} + +//----------------------------------------------------------------------------- +// Purpose: Parse the chunk at the current file position +// This object will iterate over the sub-chunks of this chunk. +// This makes for easy hierarchical parsing +//----------------------------------------------------------------------------- +void IterateRIFF::ChunkSetup( void ) +{ + m_chunkPosition = m_riff.PositionGet(); + + m_chunkName = m_riff.ReadInt(); + m_chunkSize = m_riff.ReadInt(); +} + +//----------------------------------------------------------------------------- +// Purpose: clear chunk setup, ChunkAvailable will return false +//----------------------------------------------------------------------------- +void IterateRIFF::ChunkClear( void ) +{ + m_chunkSize = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: If there are chunks left to read beyond this one, return true +//----------------------------------------------------------------------------- +bool IterateRIFF::ChunkAvailable( void ) +{ + if ( m_chunkSize != -1 && m_chunkSize < 0x10000000 ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Go to the next chunk in the file, return true if there is one. +//----------------------------------------------------------------------------- +bool IterateRIFF::ChunkNext( void ) +{ + if ( !ChunkAvailable() ) + return false; + + int nextPos = m_chunkPosition + 8 + m_chunkSize; + + // chunks are aligned + nextPos += m_chunkSize & 1; + + if ( nextPos >= (m_start + m_size) ) + { + ChunkClear(); + return false; + } + + m_riff.PositionSet( nextPos ); + + ChunkSetup(); + return ChunkAvailable(); + +} + + +//----------------------------------------------------------------------------- +// Purpose: get the chunk FOURCC as an int +// Output : unsigned int +//----------------------------------------------------------------------------- +unsigned int IterateRIFF::ChunkName( void ) +{ + return m_chunkName; +} + + +//----------------------------------------------------------------------------- +// Purpose: get the size of this chunk +// Output : unsigned int +//----------------------------------------------------------------------------- +unsigned int IterateRIFF::ChunkSize( void ) +{ + return m_chunkSize; +} + +//----------------------------------------------------------------------------- +// Purpose: Read the entire chunk into a buffer +// Input : *pOutput - dest buffer +// Output : int bytes read +//----------------------------------------------------------------------------- +int IterateRIFF::ChunkRead( void *pOutput ) +{ + return m_riff.ReadData( pOutput, ChunkSize() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Read a partial chunk (updates file position for subsequent partial reads). +// Input : *pOutput - dest buffer +// dataSize - partial size +// Output : int - bytes read +//----------------------------------------------------------------------------- +int IterateRIFF::ChunkReadPartial( void *pOutput, int dataSize ) +{ + return m_riff.ReadData( pOutput, dataSize ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Read a 4-byte int +// Output : int - read int +//----------------------------------------------------------------------------- +int IterateRIFF::ChunkReadInt( void ) +{ + return m_riff.ReadInt(); +} + +//----------------------------------------------------------------------------- +// Purpose: Used to iterate over an InFileRIFF +//----------------------------------------------------------------------------- +IterateOutputRIFF::IterateOutputRIFF( OutFileRIFF &riff ) +: m_riff( riff ) +{ + if ( !m_riff.IsValid() ) + return; + + m_start = m_riff.PositionGet(); + m_chunkPosition = m_start; + m_chunkStart = -1; +} + +IterateOutputRIFF::IterateOutputRIFF( IterateOutputRIFF &parent ) + : m_riff(parent.m_riff) +{ + m_start = parent.ChunkFilePosition(); + m_chunkPosition = m_start; + m_chunkStart = -1; +} + +void IterateOutputRIFF::ChunkWrite( unsigned int chunkname, void *pOutput, int size ) +{ + m_chunkPosition = m_riff.PositionGet(); + + m_chunkName = chunkname; + m_chunkSize = size; + + m_riff.WriteInt( chunkname ); + m_riff.WriteInt( size ); + m_riff.WriteData( pOutput, size ); + + m_chunkPosition = m_riff.PositionGet(); + + m_chunkPosition += m_chunkPosition & 1; + + m_riff.PositionSet( m_chunkPosition ); + + m_chunkStart = -1; +} + +void IterateOutputRIFF::ChunkWriteInt( int number ) +{ + m_riff.WriteInt( number ); +} + +void IterateOutputRIFF::ChunkWriteData( void *pOutput, int size ) +{ + m_riff.WriteData( pOutput, size ); +} + +void IterateOutputRIFF::ChunkFinish( void ) +{ + Assert( m_chunkStart != -1 ); + + m_chunkPosition = m_riff.PositionGet(); + + int size = m_chunkPosition - m_chunkStart - 8; + + m_chunkPosition += m_chunkPosition & 1; + + m_riff.PositionSet( m_chunkStart + sizeof( int ) ); + + m_riff.WriteInt( size ); + + m_riff.PositionSet( m_chunkPosition ); + + m_chunkStart = -1; +} + +void IterateOutputRIFF::ChunkStart( unsigned int chunkname ) +{ + Assert( m_chunkStart == -1 ); + + m_chunkStart = m_riff.PositionGet(); + + m_riff.WriteInt( chunkname ); + m_riff.WriteInt( 0 ); +} + +void IterateOutputRIFF::ChunkSetPosition( int position ) +{ + m_riff.PositionSet( position ); +} + +unsigned int IterateOutputRIFF::ChunkGetPosition( void ) +{ + return m_riff.PositionGet(); +} + +void IterateOutputRIFF::CopyChunkData( IterateRIFF& input ) +{ + if ( input.ChunkSize() > 0 ) + { + char *buffer = new char[ input.ChunkSize() ]; + Assert( buffer ); + + input.ChunkRead( buffer ); + + // Don't copy/write the name or size, just the data itself + ChunkWriteData( buffer, input.ChunkSize() ); + + delete[] buffer; + } +} + +void IterateOutputRIFF::SetLISETData( int position ) +{ + m_riff.HasLISETData( position ); +} diff --git a/public/scratchpad3d.cpp b/public/scratchpad3d.cpp index 55f46d2c..6747834c 100644 --- a/public/scratchpad3d.cpp +++ b/public/scratchpad3d.cpp @@ -12,7 +12,7 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -#ifdef _LINUX +#if defined _LINUX && !defined __stdcall #define __stdcall #endif diff --git a/public/sentence.cpp b/public/sentence.cpp index 3ba746c0..d5ff7c5d 100644 --- a/public/sentence.cpp +++ b/public/sentence.cpp @@ -1,1567 +1,1567 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) - -#include -#include "commonmacros.h" -#include "basetypes.h" -#include "sentence.h" -#include "utlbuffer.h" -#include -#include "vector.h" -#include "mathlib.h" -#include -#include "checksum_crc.h" -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Purpose: converts an english string to unicode -//----------------------------------------------------------------------------- -int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize); - -#if PHONEME_EDITOR -void CEmphasisSample::SetSelected( bool isSelected ) -{ - selected = isSelected; -} -void CPhonemeTag::SetSelected( bool isSelected ) -{ - m_bSelected = isSelected; -} -bool CPhonemeTag::GetSelected() const -{ - return m_bSelected; -} -void CPhonemeTag::SetStartAndEndBytes( unsigned int start, unsigned int end ) -{ - m_uiStartByte = start; - m_uiEndByte = end; -} -unsigned int CPhonemeTag::GetStartByte() const -{ - return m_uiStartByte; -} -unsigned int CPhonemeTag::GetEndByte() const -{ - return m_uiEndByte; -} -void CWordTag::SetSelected( bool isSelected ) -{ - m_bSelected = isSelected; -} -bool CWordTag::GetSelected() const -{ - return m_bSelected; -} -void CWordTag::SetStartAndEndBytes( unsigned int start, unsigned int end ) -{ - m_uiStartByte = start; - m_uiEndByte = end; -} -unsigned int CWordTag::GetStartByte() const -{ - return m_uiStartByte; -} -unsigned int CWordTag::GetEndByte() const -{ - return m_uiEndByte; -} -#else -// xbox doesn't store this data -void CEmphasisSample::SetSelected( bool isSelected ) {} -void CPhonemeTag::SetSelected( bool isSelected ) {} -bool CPhonemeTag::GetSelected() const { return false; } -void CPhonemeTag::SetStartAndEndBytes( unsigned int start, unsigned int end ) {} -unsigned int CPhonemeTag::GetStartByte() const { return 0; } -unsigned int CPhonemeTag::GetEndByte() const { return 0; } -void CWordTag::SetSelected( bool isSelected ) {} -bool CWordTag::GetSelected() const { return false; } -void CWordTag::SetStartAndEndBytes( unsigned int start, unsigned int end ) {} -unsigned int CWordTag::GetStartByte() const { return 0; } -unsigned int CWordTag::GetEndByte() const { return 0; } -#endif - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CWordTag::CWordTag( void ) -{ - m_pszWord = NULL; - - SetStartAndEndBytes( 0, 0 ); - - m_flStartTime = 0.0f; - m_flEndTime = 0.0f; - - SetSelected( false ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : from - -//----------------------------------------------------------------------------- -CWordTag::CWordTag( const CWordTag& from ) -{ - m_pszWord = NULL; - SetWord( from.m_pszWord ); - - SetStartAndEndBytes( from.GetStartByte(), from.GetEndByte() ); - - m_flStartTime = from.m_flStartTime; - m_flEndTime = from.m_flEndTime; - - SetSelected( from.GetSelected() ); - - for ( int p = 0; p < from.m_Phonemes.Size(); p++ ) - { - CPhonemeTag *newPhoneme = new CPhonemeTag( *from.m_Phonemes[ p ] ); - m_Phonemes.AddToTail( newPhoneme ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *word - -//----------------------------------------------------------------------------- -CWordTag::CWordTag( const char *word ) -{ - SetStartAndEndBytes( 0, 0 ); - - m_flStartTime = 0.0f; - m_flEndTime = 0.0f; - - m_pszWord = NULL; - - SetSelected( false ); - - SetWord( word ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CWordTag::~CWordTag( void ) -{ - delete[] m_pszWord; - - while ( m_Phonemes.Size() > 0 ) - { - delete m_Phonemes[ 0 ]; - m_Phonemes.Remove( 0 ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *tag - -// Output : int -//----------------------------------------------------------------------------- -int CWordTag::IndexOfPhoneme( CPhonemeTag *tag ) -{ - for ( int i = 0 ; i < m_Phonemes.Size(); i++ ) - { - CPhonemeTag *p = m_Phonemes[ i ]; - if ( p == tag ) - return i; - } - return -1; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *word - -//----------------------------------------------------------------------------- -void CWordTag::SetWord( const char *word ) -{ - delete[] m_pszWord; - m_pszWord = NULL; - if ( !word || !word[ 0 ] ) - return; - - int len = strlen( word ) + 1; - m_pszWord = new char[ len ]; - Assert( m_pszWord ); - Q_strncpy( m_pszWord, word, len ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const char -//----------------------------------------------------------------------------- -const char *CWordTag::GetWord() const -{ - return m_pszWord ? m_pszWord : ""; -} - - -unsigned int CWordTag::ComputeDataCheckSum() -{ - int i; - int c; - CRC32_t crc; - CRC32_Init( &crc ); - - // Checksum the text - if ( m_pszWord != NULL ) - { - CRC32_ProcessBuffer( &crc, m_pszWord, Q_strlen( m_pszWord ) ); - } - // Checksum phonemes - c = m_Phonemes.Count(); - for ( i = 0; i < c; ++i ) - { - CPhonemeTag *phoneme = m_Phonemes[ i ]; - unsigned int phonemeCheckSum = phoneme->ComputeDataCheckSum(); - CRC32_ProcessBuffer( &crc, &phonemeCheckSum, sizeof( unsigned int ) ); - } - // Checksum timestamps - CRC32_ProcessBuffer( &crc, &m_flStartTime, sizeof( float ) ); - CRC32_ProcessBuffer( &crc, &m_flEndTime, sizeof( float ) ); - - CRC32_Final( &crc ); - - return ( unsigned int )crc; -} - -CBasePhonemeTag::CBasePhonemeTag() -{ - m_flStartTime = 0; - m_flEndTime = 0; - - m_nPhonemeCode = 0; -} - -CBasePhonemeTag::CBasePhonemeTag( const CBasePhonemeTag& from ) -{ - memcpy( this, &from, sizeof(*this) ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CPhonemeTag::CPhonemeTag( void ) -{ - m_szPhoneme = NULL; - - SetStartAndEndBytes( 0, 0 ); - - SetSelected( false ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : from - -//----------------------------------------------------------------------------- -CPhonemeTag::CPhonemeTag( const CPhonemeTag& from ) : - BaseClass( from ) -{ - SetStartAndEndBytes( from.GetStartByte(), from.GetEndByte() ); - - SetSelected( from.GetSelected() ); - - m_szPhoneme = NULL; - SetTag( from.GetTag() ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *phoneme - -//----------------------------------------------------------------------------- -CPhonemeTag::CPhonemeTag( const char *phoneme ) -{ - SetStartAndEndBytes( 0, 0 ); - - SetStartTime( 0.0f ); - SetEndTime( 0.0f ); - - SetSelected( false ); - - SetPhonemeCode( 0 ); - - m_szPhoneme = NULL; - SetTag( phoneme ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CPhonemeTag::~CPhonemeTag( void ) -{ - delete[] m_szPhoneme; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *phoneme - -//----------------------------------------------------------------------------- -void CPhonemeTag::SetTag( const char *phoneme ) -{ - delete m_szPhoneme; - m_szPhoneme = NULL; - if ( !phoneme || !phoneme [ 0 ] ) - return; - - int len = Q_strlen( phoneme ) + 1; - m_szPhoneme = new char[ len ]; - Assert( m_szPhoneme ); - Q_strncpy( m_szPhoneme, phoneme, len ); -} - -char const *CPhonemeTag::GetTag() const -{ - return m_szPhoneme ? m_szPhoneme : ""; -} - - -unsigned int CPhonemeTag::ComputeDataCheckSum() -{ - CRC32_t crc; - CRC32_Init( &crc ); - - // Checksum the text - CRC32_ProcessBuffer( &crc, m_szPhoneme, Q_strlen( m_szPhoneme ) ); - int phonemeCode = GetPhonemeCode(); - CRC32_ProcessBuffer( &crc, &phonemeCode, sizeof( int ) ); - - // Checksum timestamps - float startTime = GetStartTime(); - float endTime = GetEndTime(); - CRC32_ProcessBuffer( &crc, &startTime, sizeof( float ) ); - CRC32_ProcessBuffer( &crc, &endTime, sizeof( float ) ); - - CRC32_Final( &crc ); - - return ( unsigned int )crc; -} - -//----------------------------------------------------------------------------- -// Purpose: Simple language to string and string to language lookup dictionary -//----------------------------------------------------------------------------- -#pragma pack(1) - -struct CCLanguage -{ - int type; - char const *name; - unsigned char r, g, b; // For faceposer, indicator color for this language -}; - -static CCLanguage g_CCLanguageLookup[] = -{ - { CC_ENGLISH, "english", 0, 0, 0 }, - { CC_FRENCH, "french", 150, 0, 0 }, - { CC_GERMAN, "german", 0, 150, 0 }, - { CC_ITALIAN, "italian", 0, 150, 150 }, - { CC_KOREAN, "korean", 150, 0, 150 }, - { CC_SCHINESE, "schinese", 150, 0, 150 }, - { CC_SPANISH, "spanish", 0, 0, 150 }, - { CC_TCHINESE, "tchinese", 150, 0, 150 }, - { CC_JAPANESE, "japanese", 250, 150, 0 }, - { CC_RUSSIAN, "russian", 0, 250, 150 }, - { CC_THAI, "thai", 0 , 150, 250 }, - { CC_PORTUGUESE,"portuguese", 0 , 0, 150 }, -}; - -#pragma pack() - -void CSentence::ColorForLanguage( int language, unsigned char& r, unsigned char& g, unsigned char& b ) -{ - r = g = b = 0; - - if ( language < 0 || language >= CC_NUM_LANGUAGES ) - { - return; - } - - r = g_CCLanguageLookup[ language ].r; - g = g_CCLanguageLookup[ language ].g; - b = g_CCLanguageLookup[ language ].b; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : language - -// Output : char const -//----------------------------------------------------------------------------- -char const *CSentence::NameForLanguage( int language ) -{ - if ( language < 0 || language >= CC_NUM_LANGUAGES ) - return "unknown_language"; - - CCLanguage *entry = &g_CCLanguageLookup[ language ]; - Assert( entry->type == language ); - return entry->name; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -// Output : int -//----------------------------------------------------------------------------- -int CSentence::LanguageForName( char const *name ) -{ - int l; - for ( l = 0; l < CC_NUM_LANGUAGES; l++ ) - { - CCLanguage *entry = &g_CCLanguageLookup[ l ]; - Assert( entry->type == l ); - if ( !stricmp( entry->name, name ) ) - return l; - } - return -1; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CSentence::CSentence( void ) -{ -#if PHONEME_EDITOR - m_nResetWordBase = 0; - m_szText = 0; - m_uCheckSum = 0; -#endif - m_bShouldVoiceDuck = false; - m_bStoreCheckSum = false; - m_bIsValid = false; - m_bIsCached = false; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CSentence::~CSentence( void ) -{ - Reset(); -#if PHONEME_EDITOR - delete[] m_szText; -#endif -} - - -void CSentence::ParsePlaintext( CUtlBuffer& buf ) -{ - char token[ 4096 ]; - char text[ 4096 ]; - text[ 0 ] = 0; - while ( 1 ) - { - buf.GetString( token ); - if ( !stricmp( token, "}" ) ) - break; - - Q_strncat( text, token, sizeof( text ), COPY_ALL_CHARACTERS ); - Q_strncat( text, " ", sizeof( text ), COPY_ALL_CHARACTERS ); - } - - SetText( text ); -} - -void CSentence::ParseWords( CUtlBuffer& buf ) -{ - char token[ 4096 ]; - char word[ 256 ]; - float start, end; - - while ( 1 ) - { - buf.GetString( token ); - if ( !stricmp( token, "}" ) ) - break; - - if ( stricmp( token, "WORD" ) ) - break; - - buf.GetString( token ); - Q_strncpy( word, token, sizeof( word ) ); - - buf.GetString( token ); - start = atof( token ); - buf.GetString( token ); - end = atof( token ); - - CWordTag *wt = new CWordTag( word ); - assert( wt ); - wt->m_flStartTime = start; - wt->m_flEndTime = end; - - AddWordTag( wt ); - - buf.GetString( token ); - if ( stricmp( token, "{" ) ) - break; - - while ( 1 ) - { - buf.GetString( token ); - if ( !stricmp( token, "}" ) ) - break; - - // Parse phoneme - int code; - char phonemename[ 256 ]; - float start, end; - float volume; - - code = atoi( token ); - - buf.GetString( token ); - Q_strncpy( phonemename, token, sizeof( phonemename ) ); - buf.GetString( token ); - start = atof( token ); - buf.GetString( token ); - end = atof( token ); - buf.GetString( token ); - volume = atof( token ); - - CPhonemeTag *pt = new CPhonemeTag(); - assert( pt ); - pt->SetPhonemeCode( code ); - pt->SetTag( phonemename ); - pt->SetStartTime( start ); - pt->SetEndTime( end ); - - AddPhonemeTag( wt, pt ); - } - } -} - -void CSentence::ParseEmphasis( CUtlBuffer& buf ) -{ - char token[ 4096 ]; - while ( 1 ) - { - buf.GetString( token ); - if ( !stricmp( token, "}" ) ) - break; - - char t[ 256 ]; - Q_strncpy( t, token, sizeof( t ) ); - buf.GetString( token ); - - char value[ 256 ]; - Q_strncpy( value, token, sizeof( value ) ); - - CEmphasisSample sample; - sample.SetSelected( false ); - sample.time = atof( t ); - sample.value = atof( value ); - - - m_EmphasisSamples.AddToTail( sample ); - - } -} - -// This is obsolete, so it doesn't do anything with the data which is parsed. -void CSentence::ParseCloseCaption( CUtlBuffer& buf ) -{ - char token[ 4096 ]; - while ( 1 ) - { - // Format is - // language_name - // { - // PHRASE char streamlength "streambytes" starttime endtime - // PHRASE unicode streamlength "streambytes" starttime endtime - // } - buf.GetString( token ); - if ( !stricmp( token, "}" ) ) - break; - - buf.GetString( token ); - if ( stricmp( token, "{" ) ) - break; - - buf.GetString( token ); - while ( 1 ) - { - - if ( !stricmp( token, "}" ) ) - break; - - if ( stricmp( token, "PHRASE" ) ) - break; - - char cc_type[32]; - char cc_stream[ 4096 ]; - int cc_length; - - memset( cc_stream, 0, sizeof( cc_stream ) ); - - buf.GetString( token ); - Q_strncpy( cc_type, token, sizeof( cc_type ) ); - - bool unicode = false; - if ( !stricmp( cc_type, "unicode" ) ) - { - unicode = true; - } - else if ( stricmp( cc_type, "char" ) ) - { - Assert( 0 ); - } - - buf.GetString( token ); - cc_length = atoi( token ); - Assert( cc_length >= 0 && cc_length < sizeof( cc_stream ) ); - // Skip space - buf.GetChar(); - buf.Get( cc_stream, cc_length ); - cc_stream[ cc_length ] = 0; - - // Skip space - buf.GetChar(); - buf.GetString( token ); - buf.GetString( token ); - - - buf.GetString( token ); - } - - } -} - -void CSentence::ParseOptions( CUtlBuffer& buf ) -{ - char token[ 4096 ]; - while ( 1 ) - { - buf.GetString( token ); - if ( !stricmp( token, "}" ) ) - break; - - if ( Q_strlen( token ) == 0 ) - break; - - char key[ 256 ]; - Q_strncpy( key, token, sizeof( key ) ); - char value[ 256 ]; - buf.GetString( token ); - Q_strncpy( value, token, sizeof( value ) ); - - if ( !strcmpi( key, "voice_duck" ) ) - { - SetVoiceDuck( atoi(value) ? true : false ); - } - else if ( !strcmpi( key, "checksum" ) ) - { - SetDataCheckSum( (unsigned int)atoi( value ) ); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: VERSION 1.0 parser, need to implement new ones if -// file format changes!!! -// Input : buf - -//----------------------------------------------------------------------------- -void CSentence::ParseDataVersionOnePointZero( CUtlBuffer& buf ) -{ - char token[ 4096 ]; - - while ( 1 ) - { - buf.GetString( token ); - if ( strlen( token ) <= 0 ) - break; - - char section[ 256 ]; - Q_strncpy( section, token, sizeof( section ) ); - - buf.GetString( token ); - if ( stricmp( token, "{" ) ) - break; - - if ( !stricmp( section, "PLAINTEXT" ) ) - { - ParsePlaintext( buf ); - } - else if ( !stricmp( section, "WORDS" ) ) - { - ParseWords( buf ); - } - else if ( !stricmp( section, "EMPHASIS" ) ) - { - ParseEmphasis( buf ); - } - else if ( !stricmp( section, "CLOSECAPTION" ) ) - { - // NOTE: CLOSECAPTION IS NO LONGER VALID - // This just skips the section of data. - ParseCloseCaption( buf ); - } - else if ( !stricmp( section, "OPTIONS" ) ) - { - ParseOptions( buf ); - } - } -} - -#define CACHED_SENTENCE_VERSION 1 - -// This is a compressed save of just the data needed to drive phonemes in the engine (no word / sentence text, etc ) -//----------------------------------------------------------------------------- -// Purpose: -// Input : buf - -//----------------------------------------------------------------------------- -void CSentence::CacheSaveToBuffer( CUtlBuffer& buf ) -{ - Assert( !buf.IsText() ); - Assert( m_bIsCached ); - - buf.PutChar( CACHED_SENTENCE_VERSION ); - - int i; - unsigned short pcount = GetRuntimePhonemeCount(); - - buf.PutShort( pcount ); - - for ( i = 0; i < pcount; ++i ) - { - const CBasePhonemeTag *phoneme = GetRuntimePhoneme( i ); - Assert( phoneme ); - - buf.PutShort( phoneme->GetPhonemeCode() ); - buf.PutFloat( phoneme->GetStartTime() ); - buf.PutFloat( phoneme->GetEndTime() ); - } - - // Now save out emphasis samples - int c = m_EmphasisSamples.Count(); - Assert( c <= 32767 ); - buf.PutShort( c ); - - for ( i = 0; i < c; i++ ) - { - CEmphasisSample *sample = &m_EmphasisSamples[ i ]; - Assert( sample ); - - buf.PutFloat( sample->time ); - short scaledValue = clamp( (short)( sample->value * 32767 ), 0, 32767 ); - - buf.PutShort( scaledValue ); - } - - // And voice duck - buf.PutChar( GetVoiceDuck() ? 1 : 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : buf - -//----------------------------------------------------------------------------- -void CSentence::CacheRestoreFromBuffer( CUtlBuffer& buf ) -{ - Assert( !buf.IsText() ); - - Reset(); - - m_bIsCached = true; - - int version = buf.GetChar(); - if ( version != CACHED_SENTENCE_VERSION ) - { - // Uh oh, version changed... - m_bIsValid = false; - return; - } - - unsigned short pcount = (unsigned short)buf.GetShort(); - - CPhonemeTag pt; - int i; - - for ( i = 0; i < pcount; ++i ) - { - unsigned short code = buf.GetShort(); - float st = buf.GetFloat(); - float et = buf.GetFloat(); - - pt.SetPhonemeCode( code ); - pt.SetStartTime( st ); - pt.SetEndTime( et ); - - AddRuntimePhoneme( &pt ); - } - - // Now read emphasis samples - int c = buf.GetShort(); - - for ( i = 0; i < c; i++ ) - { - CEmphasisSample sample; - sample.SetSelected( false ); - - sample.time = buf.GetFloat(); - sample.value = (float)buf.GetShort() / 32767.0f; - - m_EmphasisSamples.AddToTail( sample ); - } - - // And voice duck - SetVoiceDuck( buf.GetChar() == 0 ? false : true ); - m_bIsValid = true; -} - -int CSentence::GetRuntimePhonemeCount() const -{ - return m_RunTimePhonemes.Count(); -} - -const CBasePhonemeTag *CSentence::GetRuntimePhoneme( int i ) const -{ - Assert( m_bIsCached ); - return m_RunTimePhonemes[ i ]; -} - -void CSentence::ClearRuntimePhonemes() -{ - while ( m_RunTimePhonemes.Count() > 0 ) - { - CBasePhonemeTag *tag = m_RunTimePhonemes[ 0 ]; - delete tag; - m_RunTimePhonemes.Remove( 0 ); - } -} - -void CSentence::AddRuntimePhoneme( const CPhonemeTag *src ) -{ - Assert( m_bIsCached ); - - CBasePhonemeTag *tag = new CBasePhonemeTag(); - *tag = *src; - - m_RunTimePhonemes.AddToTail( tag ); -} - -void CSentence::MakeRuntimeOnly() -{ - m_bIsCached = true; -#if PHONEME_EDITOR - delete m_szText; - m_szText = NULL; - - int c = m_Words.Count(); - for ( int i = 0; i < c; ++i ) - { - CWordTag *word = m_Words[ i ]; - Assert( word ); - int pcount = word->m_Phonemes.Count(); - for ( int j = 0; j < pcount; ++j ) - { - CPhonemeTag *phoneme = word->m_Phonemes[ j ]; - assert( phoneme ); - - AddRuntimePhoneme( phoneme ); - } - } - - // Remove all existing words - while ( m_Words.Count() > 0 ) - { - CWordTag *word = m_Words[ 0 ]; - delete word; - m_Words.Remove( 0 ); - } -#endif - m_bIsValid = true; -} - - -void CSentence::SaveToBuffer( CUtlBuffer& buf ) -{ -#if PHONEME_EDITOR - Assert( !m_bIsCached ); - - int i, j; - - buf.Printf( "VERSION 1.0\n" ); - - buf.Printf( "PLAINTEXT\n" ); - buf.Printf( "{\n" ); - buf.Printf( "%s\n", GetText() ); - buf.Printf( "}\n" ); - buf.Printf( "WORDS\n" ); - buf.Printf( "{\n" ); - for ( i = 0; i < m_Words.Size(); i++ ) - { - CWordTag *word = m_Words[ i ]; - Assert( word ); - - buf.Printf( "WORD %s %.3f %.3f\n", - word->GetWord(), - word->m_flStartTime, - word->m_flEndTime ); - - buf.Printf( "{\n" ); - for ( j = 0; j < word->m_Phonemes.Size(); j++ ) - { - CPhonemeTag *phoneme = word->m_Phonemes[ j ]; - Assert( phoneme ); - - buf.Printf( "%i %s %.3f %.3f 1\n", - phoneme->GetPhonemeCode(), - phoneme->GetTag(), - phoneme->GetStartTime(), - phoneme->GetEndTime() ); - } - - buf.Printf( "}\n" ); - } - buf.Printf( "}\n" ); - buf.Printf( "EMPHASIS\n" ); - buf.Printf( "{\n" ); - int c = m_EmphasisSamples.Count(); - for ( i = 0; i < c; i++ ) - { - CEmphasisSample *sample = &m_EmphasisSamples[ i ]; - Assert( sample ); - - buf.Printf( "%f %f\n", sample->time, sample->value ); - } - - buf.Printf( "}\n" ); - buf.Printf( "OPTIONS\n" ); - buf.Printf( "{\n" ); - buf.Printf( "voice_duck %d\n", GetVoiceDuck() ? 1 : 0 ); - if ( m_bStoreCheckSum ) - { - buf.Printf( "checksum %d\n", m_uCheckSum ); - } - buf.Printf( "}\n" ); -#else - Assert( 0 ); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *data - -// size - -//----------------------------------------------------------------------------- -void CSentence::InitFromDataChunk( void *data, int size ) -{ - CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); - buf.EnsureCapacity( size ); - buf.Put( data, size ); - buf.SeekPut( CUtlBuffer::SEEK_HEAD, size ); - - InitFromBuffer( buf ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : buf - -//----------------------------------------------------------------------------- -void CSentence::InitFromBuffer( CUtlBuffer& buf ) -{ - Assert( buf.IsText() ); - - Reset(); - - char token[ 4096 ]; - buf.GetString( token ); - - if ( stricmp( token, "VERSION" ) ) - return; - - buf.GetString( token ); - if ( atof( token ) == 1.0f ) - { - ParseDataVersionOnePointZero( buf ); - m_bIsValid = true; - } - else - { - assert( 0 ); - return; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : int -//----------------------------------------------------------------------------- -int CSentence::GetWordBase( void ) -{ -#if PHONEME_EDITOR - return m_nResetWordBase; -#else - Assert( 0 ); - return 0; -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CSentence::ResetToBase( void ) -{ -#if PHONEME_EDITOR - // Delete everything after m_nResetWordBase - while ( m_Words.Size() > m_nResetWordBase ) - { - delete m_Words[ m_Words.Size() - 1 ]; - m_Words.Remove( m_Words.Size() - 1 ); - } -#endif - ClearRuntimePhonemes(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CSentence::MarkNewPhraseBase( void ) -{ -#if PHONEME_EDITOR - m_nResetWordBase = max( m_Words.Size(), 0 ); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CSentence::Reset( void ) -{ -#if PHONEME_EDITOR - m_nResetWordBase = 0; - - while ( m_Words.Size() > 0 ) - { - delete m_Words[ 0 ]; - m_Words.Remove( 0 ); - } -#endif - m_EmphasisSamples.RemoveAll(); - - ClearRuntimePhonemes(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *tag - -//----------------------------------------------------------------------------- -void CSentence::AddPhonemeTag( CWordTag *word, CPhonemeTag *tag ) -{ - word->m_Phonemes.AddToTail( tag ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *tag - -//----------------------------------------------------------------------------- -void CSentence::AddWordTag( CWordTag *tag ) -{ -#if PHONEME_EDITOR - m_Words.AddToTail( tag ); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : int -//----------------------------------------------------------------------------- -int CSentence::CountPhonemes( void ) -{ - int c = 0; -#if PHONEME_EDITOR - for( int i = 0; i < m_Words.Size(); i++ ) - { - CWordTag *word = m_Words[ i ]; - c += word->m_Phonemes.Size(); - } -#endif - return c; -} - -//----------------------------------------------------------------------------- -// Purpose: // For legacy loading, try to find a word that contains the time -// Input : time - -// Output : CWordTag -//----------------------------------------------------------------------------- -CWordTag *CSentence::EstimateBestWord( float time ) -{ -#if PHONEME_EDITOR - CWordTag *bestWord = NULL; - - for( int i = 0; i < m_Words.Size(); i++ ) - { - CWordTag *word = m_Words[ i ]; - if ( !word ) - continue; - - if ( word->m_flStartTime <= time && word->m_flEndTime >= time ) - return word; - - if ( time < word->m_flStartTime ) - { - bestWord = word; - } - - if ( time > word->m_flEndTime && bestWord ) - return bestWord; - } - - // return best word if we found one - if ( bestWord ) - { - return bestWord; - } - - // Return last word - if ( m_Words.Size() >= 1 ) - { - return m_Words[ m_Words.Size() - 1 ]; - } -#endif - // Oh well - return NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *phoneme - -// Output : CWordTag -//----------------------------------------------------------------------------- -CWordTag *CSentence::GetWordForPhoneme( CPhonemeTag *phoneme ) -{ -#if PHONEME_EDITOR - for( int i = 0; i < m_Words.Size(); i++ ) - { - CWordTag *word = m_Words[ i ]; - if ( !word ) - continue; - - for ( int j = 0 ; j < word->m_Phonemes.Size() ; j++ ) - { - CPhonemeTag *p = word->m_Phonemes[ j ]; - if ( p == phoneme ) - { - return word; - } - } - - } -#endif - return NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Assignment operator -// Input : src - -// Output : CSentence& -//----------------------------------------------------------------------------- -CSentence& CSentence::operator=( const CSentence& src ) -{ - int i; - - // Clear current stuff - Reset(); - - int c; - -#if PHONEME_EDITOR - // Copy everything - for ( i = 0 ; i < src.m_Words.Size(); i++ ) - { - CWordTag *word = src.m_Words[ i ]; - - CWordTag *newWord = new CWordTag( *word ); - - AddWordTag( newWord ); - } - - SetText( src.GetText() ); - m_nResetWordBase = src.m_nResetWordBase; - - c = src.m_EmphasisSamples.Size(); - for ( i = 0; i < c; i++ ) - { - CEmphasisSample s = src.m_EmphasisSamples[ i ]; - m_EmphasisSamples.AddToTail( s ); - } -#endif - - m_bIsCached = src.m_bIsCached; - - c = src.GetRuntimePhonemeCount(); - for ( i = 0; i < c; i++ ) - { - Assert( m_bIsCached ); - - const CBasePhonemeTag *tag = src.GetRuntimePhoneme( i ); - CPhonemeTag full; - ((CBasePhonemeTag &)(full)) = *tag; - - AddRuntimePhoneme( &full ); - } - - m_bShouldVoiceDuck = src.m_bShouldVoiceDuck; -#if PHONEME_EDITOR - m_bStoreCheckSum = src.m_bStoreCheckSum; - m_uCheckSum = src.m_uCheckSum; -#endif - m_bIsValid = src.m_bIsValid; - - return (*this); -} - -void CSentence::Append( float starttime, const CSentence& src ) -{ -#if PHONEME_EDITOR - int i; - // Combine - for ( i = 0 ; i < src.m_Words.Size(); i++ ) - { - CWordTag *word = src.m_Words[ i ]; - - CWordTag *newWord = new CWordTag( *word ); - - newWord->m_flStartTime += starttime; - newWord->m_flEndTime += starttime; - - // Offset times - int c = newWord->m_Phonemes.Count(); - for ( int i = 0; i < c; ++i ) - { - CPhonemeTag *tag = newWord->m_Phonemes[ i ]; - tag->AddStartTime( starttime ); - tag->AddEndTime( starttime ); - } - - AddWordTag( newWord ); - } - - if ( src.GetText()[ 0 ] ) - { - char fulltext[ 4096 ]; - if ( GetText()[ 0 ] ) - { - Q_snprintf( fulltext, sizeof( fulltext ), "%s %s", GetText(), src.GetText() ); - } - else - { - Q_strncpy( fulltext, src.GetText(), sizeof( fulltext ) ); - } - SetText( fulltext ); - } - - int c = src.m_EmphasisSamples.Size(); - for ( i = 0; i < c; i++ ) - { - CEmphasisSample s = src.m_EmphasisSamples[ i ]; - - s.time += starttime; - - m_EmphasisSamples.AddToTail( s ); - } - - // Or in voice duck settings - m_bShouldVoiceDuck |= src.m_bShouldVoiceDuck; -#else - Assert( 0 ); -#endif -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *text - -//----------------------------------------------------------------------------- -void CSentence::SetText( const char *text ) -{ -#if PHONEME_EDITOR - delete[] m_szText; - m_szText = NULL; - - if ( !text || !text[ 0 ] ) - { - return; - } - - int len = Q_strlen( text ) + 1; - - m_szText = new char[ len ]; - Assert( m_szText ); - Q_strncpy( m_szText, text, len ); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const char -//----------------------------------------------------------------------------- -const char *CSentence::GetText( void ) const -{ -#if PHONEME_EDITOR - return m_szText ? m_szText : ""; -#else - return ""; -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CSentence::SetTextFromWords( void ) -{ -#if PHONEME_EDITOR - char fulltext[ 1024 ]; - fulltext[ 0 ] = 0; - for ( int i = 0 ; i < m_Words.Size(); i++ ) - { - CWordTag *word = m_Words[ i ]; - - Q_strncat( fulltext, word->GetWord(), sizeof( fulltext ), COPY_ALL_CHARACTERS ); - - if ( i != m_Words.Size() ) - { - Q_strncat( fulltext, " ", sizeof( fulltext ), COPY_ALL_CHARACTERS ); - } - } - - SetText( fulltext ); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CSentence::Resort( void ) -{ - int c = m_EmphasisSamples.Size(); - for ( int i = 0; i < c; i++ ) - { - for ( int j = i + 1; j < c; j++ ) - { - CEmphasisSample src = m_EmphasisSamples[ i ]; - CEmphasisSample dest = m_EmphasisSamples[ j ]; - - if ( src.time > dest.time ) - { - m_EmphasisSamples[ i ] = dest; - m_EmphasisSamples[ j ] = src; - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : number - -// Output : CEmphasisSample -//----------------------------------------------------------------------------- -CEmphasisSample *CSentence::GetBoundedSample( int number, float endtime ) -{ - // Search for two samples which span time f - static CEmphasisSample nullstart; - nullstart.time = 0.0f; - nullstart.value = 0.5f; - static CEmphasisSample nullend; - nullend.time = endtime; - nullend.value = 0.5f; - - if ( number < 0 ) - { - return &nullstart; - } - else if ( number >= GetNumSamples() ) - { - return &nullend; - } - - return GetSample( number ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : time - -// type - -// Output : float -//----------------------------------------------------------------------------- -float CSentence::GetIntensity( float time, float endtime ) -{ - float zeroValue = 0.5f; - - int c = GetNumSamples(); - - if ( c <= 0 ) - { - return zeroValue; - } - - int i; - for ( i = -1 ; i < c; i++ ) - { - CEmphasisSample *s = GetBoundedSample( i, endtime ); - CEmphasisSample *n = GetBoundedSample( i + 1, endtime ); - if ( !s || !n ) - continue; - - if ( time >= s->time && time <= n->time ) - { - break; - } - } - - int prev = i - 1; - int start = i; - int end = i + 1; - int next = i + 2; - - prev = max( -1, prev ); - start = max( -1, start ); - end = min( end, GetNumSamples() ); - next = min( next, GetNumSamples() ); - - CEmphasisSample *esPre = GetBoundedSample( prev, endtime ); - CEmphasisSample *esStart = GetBoundedSample( start, endtime ); - CEmphasisSample *esEnd = GetBoundedSample( end, endtime ); - CEmphasisSample *esNext = GetBoundedSample( next, endtime ); - - float dt = esEnd->time - esStart->time; - dt = clamp( dt, 0.01f, 1.0f ); - - Vector vPre( esPre->time, esPre->value, 0 ); - Vector vStart( esStart->time, esStart->value, 0 ); - Vector vEnd( esEnd->time, esEnd->value, 0 ); - Vector vNext( esNext->time, esNext->value, 0 ); - - float f2 = ( time - esStart->time ) / ( dt ); - f2 = clamp( f2, 0.0f, 1.0f ); - - Vector vOut; - Catmull_Rom_Spline( - vPre, - vStart, - vEnd, - vNext, - f2, - vOut ); - - float retval = clamp( vOut.y, 0.0f, 1.0f ); - return retval; -} - -int CSentence::GetNumSamples( void ) -{ - return m_EmphasisSamples.Count(); -} - -CEmphasisSample *CSentence::GetSample( int index ) -{ - if ( index < 0 || index >= GetNumSamples() ) - return NULL; - - return &m_EmphasisSamples[ index ]; -} - -void CSentence::GetEstimatedTimes( float& start, float &end ) -{ -#if PHONEME_EDITOR - float beststart = 100000.0f; - float bestend = -100000.0f; - - int c = m_Words.Count(); - if ( !c ) - { - start = end = 0.0f; - return; - } - - for ( int i = 0; i< c; i++ ) - { - CWordTag *w = m_Words[ i ]; - Assert( w ); - if ( w->m_flStartTime < beststart ) - { - beststart = w->m_flStartTime; - } - if ( w->m_flEndTime > bestend ) - { - bestend = w->m_flEndTime; - } - } - - if ( beststart == 100000.0f ) - { - Assert( 0 ); - beststart = 0.0f; - } - if ( bestend == -100000.0f ) - { - Assert( 0 ); - bestend = 1.0f; - } - start = beststart; - end = bestend; -#endif -} - -void CSentence::SetDataCheckSum( unsigned int chk ) -{ -#if PHONEME_EDITOR - m_bStoreCheckSum = true; - m_uCheckSum = chk; -#endif -} - -unsigned int CSentence::ComputeDataCheckSum() -{ -#if PHONEME_EDITOR - int i; - int c; - CRC32_t crc; - CRC32_Init( &crc ); - - // Checksum the text - CRC32_ProcessBuffer( &crc, GetText(), Q_strlen( GetText() ) ); - // Checsum words and phonemes - c = m_Words.Count(); - for ( i = 0; i < c; ++i ) - { - CWordTag *word = m_Words[ i ]; - unsigned int wordCheckSum = word->ComputeDataCheckSum(); - CRC32_ProcessBuffer( &crc, &wordCheckSum, sizeof( unsigned int ) ); - } - - // Checksum emphasis data - c = m_EmphasisSamples.Count(); - for ( i = 0; i < c; ++i ) - { - CRC32_ProcessBuffer( &crc, &m_EmphasisSamples[ i ].time, sizeof( float ) ); - CRC32_ProcessBuffer( &crc, &m_EmphasisSamples[ i ].value, sizeof( float ) ); - } - - CRC32_Final( &crc ); - - return ( unsigned int )crc; -#else - Assert( 0 ); - return 0; -#endif -} - -unsigned int CSentence::GetDataCheckSum() const -{ -#if PHONEME_EDITOR - Assert( m_bStoreCheckSum ); - Assert( m_uCheckSum != 0 ); - return m_uCheckSum; -#else - Assert( 0 ); - return 0; -#endif -} - -#endif // !_STATIC_LINKED || _SHARED_LIB \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + +#include +#include "commonmacros.h" +#include "basetypes.h" +#include "sentence.h" +#include "utlbuffer.h" +#include +#include "vector.h" +#include "mathlib.h" +#include +#include "checksum_crc.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: converts an english string to unicode +//----------------------------------------------------------------------------- +int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize); + +#if PHONEME_EDITOR +void CEmphasisSample::SetSelected( bool isSelected ) +{ + selected = isSelected; +} +void CPhonemeTag::SetSelected( bool isSelected ) +{ + m_bSelected = isSelected; +} +bool CPhonemeTag::GetSelected() const +{ + return m_bSelected; +} +void CPhonemeTag::SetStartAndEndBytes( unsigned int start, unsigned int end ) +{ + m_uiStartByte = start; + m_uiEndByte = end; +} +unsigned int CPhonemeTag::GetStartByte() const +{ + return m_uiStartByte; +} +unsigned int CPhonemeTag::GetEndByte() const +{ + return m_uiEndByte; +} +void CWordTag::SetSelected( bool isSelected ) +{ + m_bSelected = isSelected; +} +bool CWordTag::GetSelected() const +{ + return m_bSelected; +} +void CWordTag::SetStartAndEndBytes( unsigned int start, unsigned int end ) +{ + m_uiStartByte = start; + m_uiEndByte = end; +} +unsigned int CWordTag::GetStartByte() const +{ + return m_uiStartByte; +} +unsigned int CWordTag::GetEndByte() const +{ + return m_uiEndByte; +} +#else +// xbox doesn't store this data +void CEmphasisSample::SetSelected( bool isSelected ) {} +void CPhonemeTag::SetSelected( bool isSelected ) {} +bool CPhonemeTag::GetSelected() const { return false; } +void CPhonemeTag::SetStartAndEndBytes( unsigned int start, unsigned int end ) {} +unsigned int CPhonemeTag::GetStartByte() const { return 0; } +unsigned int CPhonemeTag::GetEndByte() const { return 0; } +void CWordTag::SetSelected( bool isSelected ) {} +bool CWordTag::GetSelected() const { return false; } +void CWordTag::SetStartAndEndBytes( unsigned int start, unsigned int end ) {} +unsigned int CWordTag::GetStartByte() const { return 0; } +unsigned int CWordTag::GetEndByte() const { return 0; } +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWordTag::CWordTag( void ) +{ + m_pszWord = NULL; + + SetStartAndEndBytes( 0, 0 ); + + m_flStartTime = 0.0f; + m_flEndTime = 0.0f; + + SetSelected( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : from - +//----------------------------------------------------------------------------- +CWordTag::CWordTag( const CWordTag& from ) +{ + m_pszWord = NULL; + SetWord( from.m_pszWord ); + + SetStartAndEndBytes( from.GetStartByte(), from.GetEndByte() ); + + m_flStartTime = from.m_flStartTime; + m_flEndTime = from.m_flEndTime; + + SetSelected( from.GetSelected() ); + + for ( int p = 0; p < from.m_Phonemes.Size(); p++ ) + { + CPhonemeTag *newPhoneme = new CPhonemeTag( *from.m_Phonemes[ p ] ); + m_Phonemes.AddToTail( newPhoneme ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *word - +//----------------------------------------------------------------------------- +CWordTag::CWordTag( const char *word ) +{ + SetStartAndEndBytes( 0, 0 ); + + m_flStartTime = 0.0f; + m_flEndTime = 0.0f; + + m_pszWord = NULL; + + SetSelected( false ); + + SetWord( word ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWordTag::~CWordTag( void ) +{ + delete[] m_pszWord; + + while ( m_Phonemes.Size() > 0 ) + { + delete m_Phonemes[ 0 ]; + m_Phonemes.Remove( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tag - +// Output : int +//----------------------------------------------------------------------------- +int CWordTag::IndexOfPhoneme( CPhonemeTag *tag ) +{ + for ( int i = 0 ; i < m_Phonemes.Size(); i++ ) + { + CPhonemeTag *p = m_Phonemes[ i ]; + if ( p == tag ) + return i; + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *word - +//----------------------------------------------------------------------------- +void CWordTag::SetWord( const char *word ) +{ + delete[] m_pszWord; + m_pszWord = NULL; + if ( !word || !word[ 0 ] ) + return; + + int len = strlen( word ) + 1; + m_pszWord = new char[ len ]; + Assert( m_pszWord ); + Q_strncpy( m_pszWord, word, len ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CWordTag::GetWord() const +{ + return m_pszWord ? m_pszWord : ""; +} + + +unsigned int CWordTag::ComputeDataCheckSum() +{ + int i; + int c; + CRC32_t crc; + CRC32_Init( &crc ); + + // Checksum the text + if ( m_pszWord != NULL ) + { + CRC32_ProcessBuffer( &crc, m_pszWord, Q_strlen( m_pszWord ) ); + } + // Checksum phonemes + c = m_Phonemes.Count(); + for ( i = 0; i < c; ++i ) + { + CPhonemeTag *phoneme = m_Phonemes[ i ]; + unsigned int phonemeCheckSum = phoneme->ComputeDataCheckSum(); + CRC32_ProcessBuffer( &crc, &phonemeCheckSum, sizeof( unsigned int ) ); + } + // Checksum timestamps + CRC32_ProcessBuffer( &crc, &m_flStartTime, sizeof( float ) ); + CRC32_ProcessBuffer( &crc, &m_flEndTime, sizeof( float ) ); + + CRC32_Final( &crc ); + + return ( unsigned int )crc; +} + +CBasePhonemeTag::CBasePhonemeTag() +{ + m_flStartTime = 0; + m_flEndTime = 0; + + m_nPhonemeCode = 0; +} + +CBasePhonemeTag::CBasePhonemeTag( const CBasePhonemeTag& from ) +{ + memcpy( this, &from, sizeof(*this) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPhonemeTag::CPhonemeTag( void ) +{ + m_szPhoneme = NULL; + + SetStartAndEndBytes( 0, 0 ); + + SetSelected( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : from - +//----------------------------------------------------------------------------- +CPhonemeTag::CPhonemeTag( const CPhonemeTag& from ) : + BaseClass( from ) +{ + SetStartAndEndBytes( from.GetStartByte(), from.GetEndByte() ); + + SetSelected( from.GetSelected() ); + + m_szPhoneme = NULL; + SetTag( from.GetTag() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *phoneme - +//----------------------------------------------------------------------------- +CPhonemeTag::CPhonemeTag( const char *phoneme ) +{ + SetStartAndEndBytes( 0, 0 ); + + SetStartTime( 0.0f ); + SetEndTime( 0.0f ); + + SetSelected( false ); + + SetPhonemeCode( 0 ); + + m_szPhoneme = NULL; + SetTag( phoneme ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPhonemeTag::~CPhonemeTag( void ) +{ + delete[] m_szPhoneme; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *phoneme - +//----------------------------------------------------------------------------- +void CPhonemeTag::SetTag( const char *phoneme ) +{ + delete m_szPhoneme; + m_szPhoneme = NULL; + if ( !phoneme || !phoneme [ 0 ] ) + return; + + int len = Q_strlen( phoneme ) + 1; + m_szPhoneme = new char[ len ]; + Assert( m_szPhoneme ); + Q_strncpy( m_szPhoneme, phoneme, len ); +} + +char const *CPhonemeTag::GetTag() const +{ + return m_szPhoneme ? m_szPhoneme : ""; +} + + +unsigned int CPhonemeTag::ComputeDataCheckSum() +{ + CRC32_t crc; + CRC32_Init( &crc ); + + // Checksum the text + CRC32_ProcessBuffer( &crc, m_szPhoneme, Q_strlen( m_szPhoneme ) ); + int phonemeCode = GetPhonemeCode(); + CRC32_ProcessBuffer( &crc, &phonemeCode, sizeof( int ) ); + + // Checksum timestamps + float startTime = GetStartTime(); + float endTime = GetEndTime(); + CRC32_ProcessBuffer( &crc, &startTime, sizeof( float ) ); + CRC32_ProcessBuffer( &crc, &endTime, sizeof( float ) ); + + CRC32_Final( &crc ); + + return ( unsigned int )crc; +} + +//----------------------------------------------------------------------------- +// Purpose: Simple language to string and string to language lookup dictionary +//----------------------------------------------------------------------------- +#pragma pack(1) + +struct CCLanguage +{ + int type; + char const *name; + unsigned char r, g, b; // For faceposer, indicator color for this language +}; + +static CCLanguage g_CCLanguageLookup[] = +{ + { CC_ENGLISH, "english", 0, 0, 0 }, + { CC_FRENCH, "french", 150, 0, 0 }, + { CC_GERMAN, "german", 0, 150, 0 }, + { CC_ITALIAN, "italian", 0, 150, 150 }, + { CC_KOREAN, "korean", 150, 0, 150 }, + { CC_SCHINESE, "schinese", 150, 0, 150 }, + { CC_SPANISH, "spanish", 0, 0, 150 }, + { CC_TCHINESE, "tchinese", 150, 0, 150 }, + { CC_JAPANESE, "japanese", 250, 150, 0 }, + { CC_RUSSIAN, "russian", 0, 250, 150 }, + { CC_THAI, "thai", 0 , 150, 250 }, + { CC_PORTUGUESE,"portuguese", 0 , 0, 150 }, +}; + +#pragma pack() + +void CSentence::ColorForLanguage( int language, unsigned char& r, unsigned char& g, unsigned char& b ) +{ + r = g = b = 0; + + if ( language < 0 || language >= CC_NUM_LANGUAGES ) + { + return; + } + + r = g_CCLanguageLookup[ language ].r; + g = g_CCLanguageLookup[ language ].g; + b = g_CCLanguageLookup[ language ].b; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : language - +// Output : char const +//----------------------------------------------------------------------------- +char const *CSentence::NameForLanguage( int language ) +{ + if ( language < 0 || language >= CC_NUM_LANGUAGES ) + return "unknown_language"; + + CCLanguage *entry = &g_CCLanguageLookup[ language ]; + Assert( entry->type == language ); + return entry->name; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : int +//----------------------------------------------------------------------------- +int CSentence::LanguageForName( char const *name ) +{ + int l; + for ( l = 0; l < CC_NUM_LANGUAGES; l++ ) + { + CCLanguage *entry = &g_CCLanguageLookup[ l ]; + Assert( entry->type == l ); + if ( !stricmp( entry->name, name ) ) + return l; + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CSentence::CSentence( void ) +{ +#if PHONEME_EDITOR + m_nResetWordBase = 0; + m_szText = 0; + m_uCheckSum = 0; +#endif + m_bShouldVoiceDuck = false; + m_bStoreCheckSum = false; + m_bIsValid = false; + m_bIsCached = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CSentence::~CSentence( void ) +{ + Reset(); +#if PHONEME_EDITOR + delete[] m_szText; +#endif +} + + +void CSentence::ParsePlaintext( CUtlBuffer& buf ) +{ + char token[ 4096 ]; + char text[ 4096 ]; + text[ 0 ] = 0; + while ( 1 ) + { + buf.GetString( token ); + if ( !stricmp( token, "}" ) ) + break; + + Q_strncat( text, token, sizeof( text ), COPY_ALL_CHARACTERS ); + Q_strncat( text, " ", sizeof( text ), COPY_ALL_CHARACTERS ); + } + + SetText( text ); +} + +void CSentence::ParseWords( CUtlBuffer& buf ) +{ + char token[ 4096 ]; + char word[ 256 ]; + float start, end; + + while ( 1 ) + { + buf.GetString( token ); + if ( !stricmp( token, "}" ) ) + break; + + if ( stricmp( token, "WORD" ) ) + break; + + buf.GetString( token ); + Q_strncpy( word, token, sizeof( word ) ); + + buf.GetString( token ); + start = atof( token ); + buf.GetString( token ); + end = atof( token ); + + CWordTag *wt = new CWordTag( word ); + assert( wt ); + wt->m_flStartTime = start; + wt->m_flEndTime = end; + + AddWordTag( wt ); + + buf.GetString( token ); + if ( stricmp( token, "{" ) ) + break; + + while ( 1 ) + { + buf.GetString( token ); + if ( !stricmp( token, "}" ) ) + break; + + // Parse phoneme + int code; + char phonemename[ 256 ]; + float start, end; + float volume; + + code = atoi( token ); + + buf.GetString( token ); + Q_strncpy( phonemename, token, sizeof( phonemename ) ); + buf.GetString( token ); + start = atof( token ); + buf.GetString( token ); + end = atof( token ); + buf.GetString( token ); + volume = atof( token ); + + CPhonemeTag *pt = new CPhonemeTag(); + assert( pt ); + pt->SetPhonemeCode( code ); + pt->SetTag( phonemename ); + pt->SetStartTime( start ); + pt->SetEndTime( end ); + + AddPhonemeTag( wt, pt ); + } + } +} + +void CSentence::ParseEmphasis( CUtlBuffer& buf ) +{ + char token[ 4096 ]; + while ( 1 ) + { + buf.GetString( token ); + if ( !stricmp( token, "}" ) ) + break; + + char t[ 256 ]; + Q_strncpy( t, token, sizeof( t ) ); + buf.GetString( token ); + + char value[ 256 ]; + Q_strncpy( value, token, sizeof( value ) ); + + CEmphasisSample sample; + sample.SetSelected( false ); + sample.time = atof( t ); + sample.value = atof( value ); + + + m_EmphasisSamples.AddToTail( sample ); + + } +} + +// This is obsolete, so it doesn't do anything with the data which is parsed. +void CSentence::ParseCloseCaption( CUtlBuffer& buf ) +{ + char token[ 4096 ]; + while ( 1 ) + { + // Format is + // language_name + // { + // PHRASE char streamlength "streambytes" starttime endtime + // PHRASE unicode streamlength "streambytes" starttime endtime + // } + buf.GetString( token ); + if ( !stricmp( token, "}" ) ) + break; + + buf.GetString( token ); + if ( stricmp( token, "{" ) ) + break; + + buf.GetString( token ); + while ( 1 ) + { + + if ( !stricmp( token, "}" ) ) + break; + + if ( stricmp( token, "PHRASE" ) ) + break; + + char cc_type[32]; + char cc_stream[ 4096 ]; + int cc_length; + + memset( cc_stream, 0, sizeof( cc_stream ) ); + + buf.GetString( token ); + Q_strncpy( cc_type, token, sizeof( cc_type ) ); + + bool unicode = false; + if ( !stricmp( cc_type, "unicode" ) ) + { + unicode = true; + } + else if ( stricmp( cc_type, "char" ) ) + { + Assert( 0 ); + } + + buf.GetString( token ); + cc_length = atoi( token ); + Assert( cc_length >= 0 && cc_length < sizeof( cc_stream ) ); + // Skip space + buf.GetChar(); + buf.Get( cc_stream, cc_length ); + cc_stream[ cc_length ] = 0; + + // Skip space + buf.GetChar(); + buf.GetString( token ); + buf.GetString( token ); + + + buf.GetString( token ); + } + + } +} + +void CSentence::ParseOptions( CUtlBuffer& buf ) +{ + char token[ 4096 ]; + while ( 1 ) + { + buf.GetString( token ); + if ( !stricmp( token, "}" ) ) + break; + + if ( Q_strlen( token ) == 0 ) + break; + + char key[ 256 ]; + Q_strncpy( key, token, sizeof( key ) ); + char value[ 256 ]; + buf.GetString( token ); + Q_strncpy( value, token, sizeof( value ) ); + + if ( !strcmpi( key, "voice_duck" ) ) + { + SetVoiceDuck( atoi(value) ? true : false ); + } + else if ( !strcmpi( key, "checksum" ) ) + { + SetDataCheckSum( (unsigned int)atoi( value ) ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: VERSION 1.0 parser, need to implement new ones if +// file format changes!!! +// Input : buf - +//----------------------------------------------------------------------------- +void CSentence::ParseDataVersionOnePointZero( CUtlBuffer& buf ) +{ + char token[ 4096 ]; + + while ( 1 ) + { + buf.GetString( token ); + if ( strlen( token ) <= 0 ) + break; + + char section[ 256 ]; + Q_strncpy( section, token, sizeof( section ) ); + + buf.GetString( token ); + if ( stricmp( token, "{" ) ) + break; + + if ( !stricmp( section, "PLAINTEXT" ) ) + { + ParsePlaintext( buf ); + } + else if ( !stricmp( section, "WORDS" ) ) + { + ParseWords( buf ); + } + else if ( !stricmp( section, "EMPHASIS" ) ) + { + ParseEmphasis( buf ); + } + else if ( !stricmp( section, "CLOSECAPTION" ) ) + { + // NOTE: CLOSECAPTION IS NO LONGER VALID + // This just skips the section of data. + ParseCloseCaption( buf ); + } + else if ( !stricmp( section, "OPTIONS" ) ) + { + ParseOptions( buf ); + } + } +} + +#define CACHED_SENTENCE_VERSION 1 + +// This is a compressed save of just the data needed to drive phonemes in the engine (no word / sentence text, etc ) +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +//----------------------------------------------------------------------------- +void CSentence::CacheSaveToBuffer( CUtlBuffer& buf ) +{ + Assert( !buf.IsText() ); + Assert( m_bIsCached ); + + buf.PutChar( CACHED_SENTENCE_VERSION ); + + int i; + unsigned short pcount = GetRuntimePhonemeCount(); + + buf.PutShort( pcount ); + + for ( i = 0; i < pcount; ++i ) + { + const CBasePhonemeTag *phoneme = GetRuntimePhoneme( i ); + Assert( phoneme ); + + buf.PutShort( phoneme->GetPhonemeCode() ); + buf.PutFloat( phoneme->GetStartTime() ); + buf.PutFloat( phoneme->GetEndTime() ); + } + + // Now save out emphasis samples + int c = m_EmphasisSamples.Count(); + Assert( c <= 32767 ); + buf.PutShort( c ); + + for ( i = 0; i < c; i++ ) + { + CEmphasisSample *sample = &m_EmphasisSamples[ i ]; + Assert( sample ); + + buf.PutFloat( sample->time ); + short scaledValue = static_cast(clamp( sample->value * 32767, 0, 32767 )); + + buf.PutShort( scaledValue ); + } + + // And voice duck + buf.PutChar( GetVoiceDuck() ? 1 : 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +//----------------------------------------------------------------------------- +void CSentence::CacheRestoreFromBuffer( CUtlBuffer& buf ) +{ + Assert( !buf.IsText() ); + + Reset(); + + m_bIsCached = true; + + int version = buf.GetChar(); + if ( version != CACHED_SENTENCE_VERSION ) + { + // Uh oh, version changed... + m_bIsValid = false; + return; + } + + unsigned short pcount = (unsigned short)buf.GetShort(); + + CPhonemeTag pt; + int i; + + for ( i = 0; i < pcount; ++i ) + { + unsigned short code = buf.GetShort(); + float st = buf.GetFloat(); + float et = buf.GetFloat(); + + pt.SetPhonemeCode( code ); + pt.SetStartTime( st ); + pt.SetEndTime( et ); + + AddRuntimePhoneme( &pt ); + } + + // Now read emphasis samples + int c = buf.GetShort(); + + for ( i = 0; i < c; i++ ) + { + CEmphasisSample sample; + sample.SetSelected( false ); + + sample.time = buf.GetFloat(); + sample.value = (float)buf.GetShort() / 32767.0f; + + m_EmphasisSamples.AddToTail( sample ); + } + + // And voice duck + SetVoiceDuck( buf.GetChar() == 0 ? false : true ); + m_bIsValid = true; +} + +int CSentence::GetRuntimePhonemeCount() const +{ + return m_RunTimePhonemes.Count(); +} + +const CBasePhonemeTag *CSentence::GetRuntimePhoneme( int i ) const +{ + Assert( m_bIsCached ); + return m_RunTimePhonemes[ i ]; +} + +void CSentence::ClearRuntimePhonemes() +{ + while ( m_RunTimePhonemes.Count() > 0 ) + { + CBasePhonemeTag *tag = m_RunTimePhonemes[ 0 ]; + delete tag; + m_RunTimePhonemes.Remove( 0 ); + } +} + +void CSentence::AddRuntimePhoneme( const CPhonemeTag *src ) +{ + Assert( m_bIsCached ); + + CBasePhonemeTag *tag = new CBasePhonemeTag(); + *tag = *src; + + m_RunTimePhonemes.AddToTail( tag ); +} + +void CSentence::MakeRuntimeOnly() +{ + m_bIsCached = true; +#if PHONEME_EDITOR + delete m_szText; + m_szText = NULL; + + int c = m_Words.Count(); + for ( int i = 0; i < c; ++i ) + { + CWordTag *word = m_Words[ i ]; + Assert( word ); + int pcount = word->m_Phonemes.Count(); + for ( int j = 0; j < pcount; ++j ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + assert( phoneme ); + + AddRuntimePhoneme( phoneme ); + } + } + + // Remove all existing words + while ( m_Words.Count() > 0 ) + { + CWordTag *word = m_Words[ 0 ]; + delete word; + m_Words.Remove( 0 ); + } +#endif + m_bIsValid = true; +} + + +void CSentence::SaveToBuffer( CUtlBuffer& buf ) +{ +#if PHONEME_EDITOR + Assert( !m_bIsCached ); + + int i, j; + + buf.Printf( "VERSION 1.0\n" ); + + buf.Printf( "PLAINTEXT\n" ); + buf.Printf( "{\n" ); + buf.Printf( "%s\n", GetText() ); + buf.Printf( "}\n" ); + buf.Printf( "WORDS\n" ); + buf.Printf( "{\n" ); + for ( i = 0; i < m_Words.Size(); i++ ) + { + CWordTag *word = m_Words[ i ]; + Assert( word ); + + buf.Printf( "WORD %s %.3f %.3f\n", + word->GetWord(), + word->m_flStartTime, + word->m_flEndTime ); + + buf.Printf( "{\n" ); + for ( j = 0; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + Assert( phoneme ); + + buf.Printf( "%i %s %.3f %.3f 1\n", + phoneme->GetPhonemeCode(), + phoneme->GetTag(), + phoneme->GetStartTime(), + phoneme->GetEndTime() ); + } + + buf.Printf( "}\n" ); + } + buf.Printf( "}\n" ); + buf.Printf( "EMPHASIS\n" ); + buf.Printf( "{\n" ); + int c = m_EmphasisSamples.Count(); + for ( i = 0; i < c; i++ ) + { + CEmphasisSample *sample = &m_EmphasisSamples[ i ]; + Assert( sample ); + + buf.Printf( "%f %f\n", sample->time, sample->value ); + } + + buf.Printf( "}\n" ); + buf.Printf( "OPTIONS\n" ); + buf.Printf( "{\n" ); + buf.Printf( "voice_duck %d\n", GetVoiceDuck() ? 1 : 0 ); + if ( m_bStoreCheckSum ) + { + buf.Printf( "checksum %d\n", m_uCheckSum ); + } + buf.Printf( "}\n" ); +#else + Assert( 0 ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *data - +// size - +//----------------------------------------------------------------------------- +void CSentence::InitFromDataChunk( void *data, int size ) +{ + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + buf.EnsureCapacity( size ); + buf.Put( data, size ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, size ); + + InitFromBuffer( buf ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : buf - +//----------------------------------------------------------------------------- +void CSentence::InitFromBuffer( CUtlBuffer& buf ) +{ + Assert( buf.IsText() ); + + Reset(); + + char token[ 4096 ]; + buf.GetString( token ); + + if ( stricmp( token, "VERSION" ) ) + return; + + buf.GetString( token ); + if ( atof( token ) == 1.0f ) + { + ParseDataVersionOnePointZero( buf ); + m_bIsValid = true; + } + else + { + assert( 0 ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CSentence::GetWordBase( void ) +{ +#if PHONEME_EDITOR + return m_nResetWordBase; +#else + Assert( 0 ); + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSentence::ResetToBase( void ) +{ +#if PHONEME_EDITOR + // Delete everything after m_nResetWordBase + while ( m_Words.Size() > m_nResetWordBase ) + { + delete m_Words[ m_Words.Size() - 1 ]; + m_Words.Remove( m_Words.Size() - 1 ); + } +#endif + ClearRuntimePhonemes(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSentence::MarkNewPhraseBase( void ) +{ +#if PHONEME_EDITOR + m_nResetWordBase = max( m_Words.Size(), 0 ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSentence::Reset( void ) +{ +#if PHONEME_EDITOR + m_nResetWordBase = 0; + + while ( m_Words.Size() > 0 ) + { + delete m_Words[ 0 ]; + m_Words.Remove( 0 ); + } +#endif + m_EmphasisSamples.RemoveAll(); + + ClearRuntimePhonemes(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tag - +//----------------------------------------------------------------------------- +void CSentence::AddPhonemeTag( CWordTag *word, CPhonemeTag *tag ) +{ + word->m_Phonemes.AddToTail( tag ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tag - +//----------------------------------------------------------------------------- +void CSentence::AddWordTag( CWordTag *tag ) +{ +#if PHONEME_EDITOR + m_Words.AddToTail( tag ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CSentence::CountPhonemes( void ) +{ + int c = 0; +#if PHONEME_EDITOR + for( int i = 0; i < m_Words.Size(); i++ ) + { + CWordTag *word = m_Words[ i ]; + c += word->m_Phonemes.Size(); + } +#endif + return c; +} + +//----------------------------------------------------------------------------- +// Purpose: // For legacy loading, try to find a word that contains the time +// Input : time - +// Output : CWordTag +//----------------------------------------------------------------------------- +CWordTag *CSentence::EstimateBestWord( float time ) +{ +#if PHONEME_EDITOR + CWordTag *bestWord = NULL; + + for( int i = 0; i < m_Words.Size(); i++ ) + { + CWordTag *word = m_Words[ i ]; + if ( !word ) + continue; + + if ( word->m_flStartTime <= time && word->m_flEndTime >= time ) + return word; + + if ( time < word->m_flStartTime ) + { + bestWord = word; + } + + if ( time > word->m_flEndTime && bestWord ) + return bestWord; + } + + // return best word if we found one + if ( bestWord ) + { + return bestWord; + } + + // Return last word + if ( m_Words.Size() >= 1 ) + { + return m_Words[ m_Words.Size() - 1 ]; + } +#endif + // Oh well + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *phoneme - +// Output : CWordTag +//----------------------------------------------------------------------------- +CWordTag *CSentence::GetWordForPhoneme( CPhonemeTag *phoneme ) +{ +#if PHONEME_EDITOR + for( int i = 0; i < m_Words.Size(); i++ ) + { + CWordTag *word = m_Words[ i ]; + if ( !word ) + continue; + + for ( int j = 0 ; j < word->m_Phonemes.Size() ; j++ ) + { + CPhonemeTag *p = word->m_Phonemes[ j ]; + if ( p == phoneme ) + { + return word; + } + } + + } +#endif + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Assignment operator +// Input : src - +// Output : CSentence& +//----------------------------------------------------------------------------- +CSentence& CSentence::operator=( const CSentence& src ) +{ + int i; + + // Clear current stuff + Reset(); + + int c; + +#if PHONEME_EDITOR + // Copy everything + for ( i = 0 ; i < src.m_Words.Size(); i++ ) + { + CWordTag *word = src.m_Words[ i ]; + + CWordTag *newWord = new CWordTag( *word ); + + AddWordTag( newWord ); + } + + SetText( src.GetText() ); + m_nResetWordBase = src.m_nResetWordBase; + + c = src.m_EmphasisSamples.Size(); + for ( i = 0; i < c; i++ ) + { + CEmphasisSample s = src.m_EmphasisSamples[ i ]; + m_EmphasisSamples.AddToTail( s ); + } +#endif + + m_bIsCached = src.m_bIsCached; + + c = src.GetRuntimePhonemeCount(); + for ( i = 0; i < c; i++ ) + { + Assert( m_bIsCached ); + + const CBasePhonemeTag *tag = src.GetRuntimePhoneme( i ); + CPhonemeTag full; + ((CBasePhonemeTag &)(full)) = *tag; + + AddRuntimePhoneme( &full ); + } + + m_bShouldVoiceDuck = src.m_bShouldVoiceDuck; +#if PHONEME_EDITOR + m_bStoreCheckSum = src.m_bStoreCheckSum; + m_uCheckSum = src.m_uCheckSum; +#endif + m_bIsValid = src.m_bIsValid; + + return (*this); +} + +void CSentence::Append( float starttime, const CSentence& src ) +{ +#if PHONEME_EDITOR + int i; + // Combine + for ( i = 0 ; i < src.m_Words.Size(); i++ ) + { + CWordTag *word = src.m_Words[ i ]; + + CWordTag *newWord = new CWordTag( *word ); + + newWord->m_flStartTime += starttime; + newWord->m_flEndTime += starttime; + + // Offset times + int c = newWord->m_Phonemes.Count(); + for ( int i = 0; i < c; ++i ) + { + CPhonemeTag *tag = newWord->m_Phonemes[ i ]; + tag->AddStartTime( starttime ); + tag->AddEndTime( starttime ); + } + + AddWordTag( newWord ); + } + + if ( src.GetText()[ 0 ] ) + { + char fulltext[ 4096 ]; + if ( GetText()[ 0 ] ) + { + Q_snprintf( fulltext, sizeof( fulltext ), "%s %s", GetText(), src.GetText() ); + } + else + { + Q_strncpy( fulltext, src.GetText(), sizeof( fulltext ) ); + } + SetText( fulltext ); + } + + int c = src.m_EmphasisSamples.Size(); + for ( i = 0; i < c; i++ ) + { + CEmphasisSample s = src.m_EmphasisSamples[ i ]; + + s.time += starttime; + + m_EmphasisSamples.AddToTail( s ); + } + + // Or in voice duck settings + m_bShouldVoiceDuck |= src.m_bShouldVoiceDuck; +#else + Assert( 0 ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *text - +//----------------------------------------------------------------------------- +void CSentence::SetText( const char *text ) +{ +#if PHONEME_EDITOR + delete[] m_szText; + m_szText = NULL; + + if ( !text || !text[ 0 ] ) + { + return; + } + + int len = Q_strlen( text ) + 1; + + m_szText = new char[ len ]; + Assert( m_szText ); + Q_strncpy( m_szText, text, len ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CSentence::GetText( void ) const +{ +#if PHONEME_EDITOR + return m_szText ? m_szText : ""; +#else + return ""; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSentence::SetTextFromWords( void ) +{ +#if PHONEME_EDITOR + char fulltext[ 1024 ]; + fulltext[ 0 ] = 0; + for ( int i = 0 ; i < m_Words.Size(); i++ ) + { + CWordTag *word = m_Words[ i ]; + + Q_strncat( fulltext, word->GetWord(), sizeof( fulltext ), COPY_ALL_CHARACTERS ); + + if ( i != m_Words.Size() ) + { + Q_strncat( fulltext, " ", sizeof( fulltext ), COPY_ALL_CHARACTERS ); + } + } + + SetText( fulltext ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSentence::Resort( void ) +{ + int c = m_EmphasisSamples.Size(); + for ( int i = 0; i < c; i++ ) + { + for ( int j = i + 1; j < c; j++ ) + { + CEmphasisSample src = m_EmphasisSamples[ i ]; + CEmphasisSample dest = m_EmphasisSamples[ j ]; + + if ( src.time > dest.time ) + { + m_EmphasisSamples[ i ] = dest; + m_EmphasisSamples[ j ] = src; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : number - +// Output : CEmphasisSample +//----------------------------------------------------------------------------- +CEmphasisSample *CSentence::GetBoundedSample( int number, float endtime ) +{ + // Search for two samples which span time f + static CEmphasisSample nullstart; + nullstart.time = 0.0f; + nullstart.value = 0.5f; + static CEmphasisSample nullend; + nullend.time = endtime; + nullend.value = 0.5f; + + if ( number < 0 ) + { + return &nullstart; + } + else if ( number >= GetNumSamples() ) + { + return &nullend; + } + + return GetSample( number ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// type - +// Output : float +//----------------------------------------------------------------------------- +float CSentence::GetIntensity( float time, float endtime ) +{ + float zeroValue = 0.5f; + + int c = GetNumSamples(); + + if ( c <= 0 ) + { + return zeroValue; + } + + int i; + for ( i = -1 ; i < c; i++ ) + { + CEmphasisSample *s = GetBoundedSample( i, endtime ); + CEmphasisSample *n = GetBoundedSample( i + 1, endtime ); + if ( !s || !n ) + continue; + + if ( time >= s->time && time <= n->time ) + { + break; + } + } + + int prev = i - 1; + int start = i; + int end = i + 1; + int next = i + 2; + + prev = max( -1, prev ); + start = max( -1, start ); + end = min( end, GetNumSamples() ); + next = min( next, GetNumSamples() ); + + CEmphasisSample *esPre = GetBoundedSample( prev, endtime ); + CEmphasisSample *esStart = GetBoundedSample( start, endtime ); + CEmphasisSample *esEnd = GetBoundedSample( end, endtime ); + CEmphasisSample *esNext = GetBoundedSample( next, endtime ); + + float dt = esEnd->time - esStart->time; + dt = clamp( dt, 0.01f, 1.0f ); + + Vector vPre( esPre->time, esPre->value, 0 ); + Vector vStart( esStart->time, esStart->value, 0 ); + Vector vEnd( esEnd->time, esEnd->value, 0 ); + Vector vNext( esNext->time, esNext->value, 0 ); + + float f2 = ( time - esStart->time ) / ( dt ); + f2 = clamp( f2, 0.0f, 1.0f ); + + Vector vOut; + Catmull_Rom_Spline( + vPre, + vStart, + vEnd, + vNext, + f2, + vOut ); + + float retval = clamp( vOut.y, 0.0f, 1.0f ); + return retval; +} + +int CSentence::GetNumSamples( void ) +{ + return m_EmphasisSamples.Count(); +} + +CEmphasisSample *CSentence::GetSample( int index ) +{ + if ( index < 0 || index >= GetNumSamples() ) + return NULL; + + return &m_EmphasisSamples[ index ]; +} + +void CSentence::GetEstimatedTimes( float& start, float &end ) +{ +#if PHONEME_EDITOR + float beststart = 100000.0f; + float bestend = -100000.0f; + + int c = m_Words.Count(); + if ( !c ) + { + start = end = 0.0f; + return; + } + + for ( int i = 0; i< c; i++ ) + { + CWordTag *w = m_Words[ i ]; + Assert( w ); + if ( w->m_flStartTime < beststart ) + { + beststart = w->m_flStartTime; + } + if ( w->m_flEndTime > bestend ) + { + bestend = w->m_flEndTime; + } + } + + if ( beststart == 100000.0f ) + { + Assert( 0 ); + beststart = 0.0f; + } + if ( bestend == -100000.0f ) + { + Assert( 0 ); + bestend = 1.0f; + } + start = beststart; + end = bestend; +#endif +} + +void CSentence::SetDataCheckSum( unsigned int chk ) +{ +#if PHONEME_EDITOR + m_bStoreCheckSum = true; + m_uCheckSum = chk; +#endif +} + +unsigned int CSentence::ComputeDataCheckSum() +{ +#if PHONEME_EDITOR + int i; + int c; + CRC32_t crc; + CRC32_Init( &crc ); + + // Checksum the text + CRC32_ProcessBuffer( &crc, GetText(), Q_strlen( GetText() ) ); + // Checsum words and phonemes + c = m_Words.Count(); + for ( i = 0; i < c; ++i ) + { + CWordTag *word = m_Words[ i ]; + unsigned int wordCheckSum = word->ComputeDataCheckSum(); + CRC32_ProcessBuffer( &crc, &wordCheckSum, sizeof( unsigned int ) ); + } + + // Checksum emphasis data + c = m_EmphasisSamples.Count(); + for ( i = 0; i < c; ++i ) + { + CRC32_ProcessBuffer( &crc, &m_EmphasisSamples[ i ].time, sizeof( float ) ); + CRC32_ProcessBuffer( &crc, &m_EmphasisSamples[ i ].value, sizeof( float ) ); + } + + CRC32_Final( &crc ); + + return ( unsigned int )crc; +#else + Assert( 0 ); + return 0; +#endif +} + +unsigned int CSentence::GetDataCheckSum() const +{ +#if PHONEME_EDITOR + Assert( m_bStoreCheckSum ); + Assert( m_uCheckSum != 0 ); + return m_uCheckSum; +#else + Assert( 0 ); + return 0; +#endif +} + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/public/shattersurfacetypes.h b/public/shattersurfacetypes.h index de088318..ffdee42e 100644 --- a/public/shattersurfacetypes.h +++ b/public/shattersurfacetypes.h @@ -1,21 +1,21 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#if !defined ( SHATTERSURFACETYPES_H ) -#define SHATTERSURFACETYPES_H -#ifdef _WIN32 -#pragma once -#endif - -enum ShatterSurface_t -{ - // Note: This much match with the client entity - SHATTERSURFACE_GLASS = 0, - SHATTERSURFACE_TILE = 1, -}; - -#endif \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined ( SHATTERSURFACETYPES_H ) +#define SHATTERSURFACETYPES_H +#ifdef _WIN32 +#pragma once +#endif + +enum ShatterSurface_t +{ + // Note: This much match with the client entity + SHATTERSURFACE_GLASS = 0, + SHATTERSURFACE_TILE = 1, +}; + +#endif diff --git a/public/string_t.h b/public/string_t.h index 22b46971..c0a76aaa 100644 --- a/public/string_t.h +++ b/public/string_t.h @@ -41,7 +41,7 @@ typedef int string_t; //----------------------------------------------------------------------------- -#define IDENT_STRINGS( s1, s2 ) ( *((void **)&(s1)) == *((void **)&(s2)) ) +#define IDENT_STRINGS( s1, s2 ) ( s1 == s2 ) //----------------------------------------------------------------------------- @@ -92,7 +92,7 @@ struct castable_string_t : public string_t // string_t is used in unions, hence, //----------------------------------------------------------------------------- -#define IDENT_STRINGS( s1, s2 ) ( *((void **)&(s1)) == *((void **)&(s2)) ) +#define IDENT_STRINGS( s1, s2 ) ( s1 == s2 ) //----------------------------------------------------------------------------- diff --git a/public/studio.h b/public/studio.h index 1552195b..37d6cf31 100644 --- a/public/studio.h +++ b/public/studio.h @@ -694,15 +694,15 @@ public: } inline void SetDeltaFixed( const Vector vInput ) { - delta[0] = vInput.x * g_VertAnimFixedPointScaleInv; - delta[1] = vInput.y * g_VertAnimFixedPointScaleInv; - delta[2] = vInput.z * g_VertAnimFixedPointScaleInv; + delta[0] = static_cast(vInput.x * g_VertAnimFixedPointScaleInv); + delta[1] = static_cast(vInput.y * g_VertAnimFixedPointScaleInv); + delta[2] = static_cast(vInput.z * g_VertAnimFixedPointScaleInv); } inline void SetNDeltaFixed( const Vector vInputNormal ) { - ndelta[0] = vInputNormal.x * g_VertAnimFixedPointScaleInv; - ndelta[1] = vInputNormal.y * g_VertAnimFixedPointScaleInv; - ndelta[2] = vInputNormal.z * g_VertAnimFixedPointScaleInv; + ndelta[0] = static_cast(vInputNormal.x * g_VertAnimFixedPointScaleInv); + ndelta[1] = static_cast(vInputNormal.y * g_VertAnimFixedPointScaleInv); + ndelta[2] = static_cast(vInputNormal.z * g_VertAnimFixedPointScaleInv); } // Ick...can also force fp16 data into this structure for writing to file in legacy format... diff --git a/public/studio_generic_io.cpp b/public/studio_generic_io.cpp index 1c1f73a9..e7892540 100644 --- a/public/studio_generic_io.cpp +++ b/public/studio_generic_io.cpp @@ -1,156 +1,156 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include - -#include "studio.h" -#include "UtlRBTree.h" - -extern studiohdr_t *FindOrLoadGroupFile( char const *modelname ); - -virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const -{ - if (numincludemodels == 0) - { - return NULL; - } - - virtualmodel_t *pVModel = (virtualmodel_t *)virtualModel; - - if (pVModel == NULL) - { - pVModel = new virtualmodel_t; - - // !!! Set cache handle? Set pointer to local virtual model?? - virtualModel = (void *)pVModel; - - int group = pVModel->m_group.AddToTail( ); - pVModel->m_group[ group ].cache = (void *)this; - pVModel->AppendModels( 0, this ); - } - - return pVModel; -} - - -const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *modelname ) const -{ - studiohdr_t *hdr = (studiohdr_t *)(*cache); - - if (hdr) - { - return hdr; - } - - hdr = FindOrLoadGroupFile( modelname ); - - *cache = (void *)hdr; - - return hdr; -} - -const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const -{ - return (studiohdr_t *)cache; -} - - -byte *studiohdr_t::GetAnimBlock( int i ) const -{ - byte *hdr = (byte *)animblockModel; - - if (!hdr) - { - hdr = (byte *)FindOrLoadGroupFile( pszAnimBlockName() ); - animblockModel = hdr; - } - - return hdr + pAnimBlock( i )->datastart; -} - -//----------------------------------------------------------------------------- -// Purpose: Builds up a dictionary of autoplay indices by studiohdr_t * -// NOTE: This list never gets freed even if the model gets unloaded, but we're in a tool so we can probably live with that -//----------------------------------------------------------------------------- -struct AutoPlayGeneric_t -{ -public: - - AutoPlayGeneric_t() : - hdr( 0 ) - { - } - - // Implement copy constructor - AutoPlayGeneric_t( const AutoPlayGeneric_t& src ) - { - hdr = src.hdr; - autoplaylist.EnsureCount( src.autoplaylist.Count() ); - autoplaylist.CopyArray( src.autoplaylist.Base(), src.autoplaylist.Count() ); - } - - static bool AutoPlayGenericLessFunc( const AutoPlayGeneric_t& lhs, const AutoPlayGeneric_t& rhs ) - { - return lhs.hdr < rhs.hdr; - } - -public: - // Data - const studiohdr_t *hdr; - CUtlVector< unsigned short > autoplaylist; -}; - -// A global array to track this data -static CUtlRBTree< AutoPlayGeneric_t, int > g_AutoPlayGeneric( 0, 0, AutoPlayGeneric_t::AutoPlayGenericLessFunc ); - -int studiohdr_t::GetAutoplayList( unsigned short **pAutoplayList ) const -{ - virtualmodel_t *pVirtualModel = GetVirtualModel(); - if ( pVirtualModel ) - { - if ( pAutoplayList && pVirtualModel->m_autoplaySequences.Count() ) - { - *pAutoplayList = pVirtualModel->m_autoplaySequences.Base(); - } - return pVirtualModel->m_autoplaySequences.Count(); - } - - AutoPlayGeneric_t *pData = NULL; - - // Search for this studiohdr_t ptr in the global list - AutoPlayGeneric_t search; - search.hdr = this; - int index = g_AutoPlayGeneric.Find( search ); - if ( index == g_AutoPlayGeneric.InvalidIndex() ) - { - // Not there, so add it - index = g_AutoPlayGeneric.Insert( search ); - pData = &g_AutoPlayGeneric[ index ]; - // And compute the autoplay info this one time - int autoPlayCount = CountAutoplaySequences(); - pData->autoplaylist.EnsureCount( autoPlayCount ); - CopyAutoplaySequences( pData->autoplaylist.Base(), autoPlayCount ); - } - else - { - // Refer to existing data - pData = &g_AutoPlayGeneric[ index ]; - } - - // Oops!!! - if ( !pData ) - { - return 0; - } - - // Give back data if it's being requested - if ( pAutoplayList ) - { - *pAutoplayList = pData->autoplaylist.Base(); - } - return pData->autoplaylist.Count(); -} +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include + +#include "studio.h" +#include "utlrbtree.h" + +extern studiohdr_t *FindOrLoadGroupFile( char const *modelname ); + +virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const +{ + if (numincludemodels == 0) + { + return NULL; + } + + virtualmodel_t *pVModel = (virtualmodel_t *)virtualModel; + + if (pVModel == NULL) + { + pVModel = new virtualmodel_t; + + // !!! Set cache handle? Set pointer to local virtual model?? + virtualModel = (void *)pVModel; + + int group = pVModel->m_group.AddToTail( ); + pVModel->m_group[ group ].cache = (void *)this; + pVModel->AppendModels( 0, this ); + } + + return pVModel; +} + + +const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *modelname ) const +{ + studiohdr_t *hdr = (studiohdr_t *)(*cache); + + if (hdr) + { + return hdr; + } + + hdr = FindOrLoadGroupFile( modelname ); + + *cache = (void *)hdr; + + return hdr; +} + +const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const +{ + return (studiohdr_t *)cache; +} + + +byte *studiohdr_t::GetAnimBlock( int i ) const +{ + byte *hdr = (byte *)animblockModel; + + if (!hdr) + { + hdr = (byte *)FindOrLoadGroupFile( pszAnimBlockName() ); + animblockModel = hdr; + } + + return hdr + pAnimBlock( i )->datastart; +} + +//----------------------------------------------------------------------------- +// Purpose: Builds up a dictionary of autoplay indices by studiohdr_t * +// NOTE: This list never gets freed even if the model gets unloaded, but we're in a tool so we can probably live with that +//----------------------------------------------------------------------------- +struct AutoPlayGeneric_t +{ +public: + + AutoPlayGeneric_t() : + hdr( 0 ) + { + } + + // Implement copy constructor + AutoPlayGeneric_t( const AutoPlayGeneric_t& src ) + { + hdr = src.hdr; + autoplaylist.EnsureCount( src.autoplaylist.Count() ); + autoplaylist.CopyArray( src.autoplaylist.Base(), src.autoplaylist.Count() ); + } + + static bool AutoPlayGenericLessFunc( const AutoPlayGeneric_t& lhs, const AutoPlayGeneric_t& rhs ) + { + return lhs.hdr < rhs.hdr; + } + +public: + // Data + const studiohdr_t *hdr; + CUtlVector< unsigned short > autoplaylist; +}; + +// A global array to track this data +static CUtlRBTree< AutoPlayGeneric_t, int > g_AutoPlayGeneric( 0, 0, AutoPlayGeneric_t::AutoPlayGenericLessFunc ); + +int studiohdr_t::GetAutoplayList( unsigned short **pAutoplayList ) const +{ + virtualmodel_t *pVirtualModel = GetVirtualModel(); + if ( pVirtualModel ) + { + if ( pAutoplayList && pVirtualModel->m_autoplaySequences.Count() ) + { + *pAutoplayList = pVirtualModel->m_autoplaySequences.Base(); + } + return pVirtualModel->m_autoplaySequences.Count(); + } + + AutoPlayGeneric_t *pData = NULL; + + // Search for this studiohdr_t ptr in the global list + AutoPlayGeneric_t search; + search.hdr = this; + int index = g_AutoPlayGeneric.Find( search ); + if ( index == g_AutoPlayGeneric.InvalidIndex() ) + { + // Not there, so add it + index = g_AutoPlayGeneric.Insert( search ); + pData = &g_AutoPlayGeneric[ index ]; + // And compute the autoplay info this one time + int autoPlayCount = CountAutoplaySequences(); + pData->autoplaylist.EnsureCount( autoPlayCount ); + CopyAutoplaySequences( pData->autoplaylist.Base(), autoPlayCount ); + } + else + { + // Refer to existing data + pData = &g_AutoPlayGeneric[ index ]; + } + + // Oops!!! + if ( !pData ) + { + return 0; + } + + // Give back data if it's being requested + if ( pAutoplayList ) + { + *pAutoplayList = pData->autoplaylist.Base(); + } + return pData->autoplaylist.Count(); +} diff --git a/public/tier0/basetypes.h b/public/tier0/basetypes.h index 6d5750cf..a30fc515 100644 --- a/public/tier0/basetypes.h +++ b/public/tier0/basetypes.h @@ -135,7 +135,8 @@ inline unsigned long const& FloatBits( vec_t const& f ) inline vec_t BitsToFloat( unsigned long i ) { - return *reinterpret_cast(&i); + void *bits = &i; + return *reinterpret_cast(bits); } inline bool IsFinite( vec_t f ) diff --git a/public/tier0/commonmacros.h b/public/tier0/commonmacros.h index dc2fc289..3645c5b0 100644 --- a/public/tier0/commonmacros.h +++ b/public/tier0/commonmacros.h @@ -1,46 +1,46 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#ifndef COMMONMACROS_H -#define COMMONMACROS_H - -#ifdef _WIN32 -#pragma once -#endif - - -// ------------------------------------------------------- -// -// commonmacros.h -// -// This should contain ONLY general purpose macros that are -// appropriate for use in engine/launcher/all tools -// -// ------------------------------------------------------- - -// Makes a 4-byte "packed ID" int out of 4 characters -#define MAKEID(d,c,b,a) ( ((int)(a) << 24) | ((int)(b) << 16) | ((int)(c) << 8) | ((int)(d)) ) - -// Compares a string with a 4-byte packed ID constant -#define STRING_MATCHES_ID( p, id ) ( (*((int *)(p)) == (id) ) ? true : false ) -#define ID_TO_STRING( id, p ) ( (p)[3] = (((id)>>24) & 0xFF), (p)[2] = (((id)>>16) & 0xFF), (p)[1] = (((id)>>8) & 0xFF), (p)[0] = (((id)>>0) & 0xFF) ) - -#ifdef ARRAYSIZE -#undef ARRAYSIZE -#endif -#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) - -#define SETBITS(iBitVector, bits) ((iBitVector) |= (bits)) -#define CLEARBITS(iBitVector, bits) ((iBitVector) &= ~(bits)) -#define FBitSet(iBitVector, bit) ((iBitVector) & (bit)) - -inline bool IsPowerOfTwo( int value ) -{ - return (value & ( value - 1 )) == 0; -} - -#endif // COMMONMACROS_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef COMMONMACROS_H +#define COMMONMACROS_H + +#ifdef _WIN32 +#pragma once +#endif + + +// ------------------------------------------------------- +// +// commonmacros.h +// +// This should contain ONLY general purpose macros that are +// appropriate for use in engine/launcher/all tools +// +// ------------------------------------------------------- + +// Makes a 4-byte "packed ID" int out of 4 characters +#define MAKEID(d,c,b,a) ( ((int)(a) << 24) | ((int)(b) << 16) | ((int)(c) << 8) | ((int)(d)) ) + +// Compares a string with a 4-byte packed ID constant +#define STRING_MATCHES_ID( p, id ) ( (*((int *)(p)) == (id) ) ? true : false ) +#define ID_TO_STRING( id, p ) ( (p)[3] = (((id)>>24) & 0xFF), (p)[2] = (((id)>>16) & 0xFF), (p)[1] = (((id)>>8) & 0xFF), (p)[0] = (((id)>>0) & 0xFF) ) + +#ifdef ARRAYSIZE +#undef ARRAYSIZE +#endif +#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +#define SETBITS(iBitVector, bits) ((iBitVector) |= (bits)) +#define CLEARBITS(iBitVector, bits) ((iBitVector) &= ~(bits)) +#define FBitSet(iBitVector, bit) ((iBitVector) & (bit)) + +inline bool IsPowerOfTwo( int value ) +{ + return (value & ( value - 1 )) == 0; +} + +#endif // COMMONMACROS_H diff --git a/public/tier0/dbg.h b/public/tier0/dbg.h index adc25ffc..f6b698ad 100644 --- a/public/tier0/dbg.h +++ b/public/tier0/dbg.h @@ -458,11 +458,22 @@ public: // Macro to assist in asserting constant invariants during compilation #ifdef _DEBUG -#define COMPILE_TIME_ASSERT( pred ) switch(0){case 0:case pred:;} -#define ASSERT_INVARIANT( pred ) static void UNIQUE_ID() { COMPILE_TIME_ASSERT( pred ) } + #define COMPILE_TIME_ASSERT( pred ) switch(0){case 0:case pred:;} + #ifdef _MSC_VER + #define ASSERT_INVARIANT( pred ) static void UNIQUE_ID() { COMPILE_TIME_ASSERT( pred ) } + #elif defined __GNUC__ + #define ASSERT_INVARIANT( pred ) __attribute__((unused)) static void UNIQUE_ID() { COMPILE_TIME_ASSERT( pred ) } + #endif #else -#define COMPILE_TIME_ASSERT( pred ) -#define ASSERT_INVARIANT( pred ) + #define COMPILE_TIME_ASSERT( pred ) + #define ASSERT_INVARIANT( pred ) +#endif + +// Member function pointer size +#ifdef _MSC_VER + #define MFP_SIZE 4 +#elif __GNUC__ + #define MFP_SIZE 8 #endif #ifdef _DEBUG @@ -527,6 +538,7 @@ private: // #include "tier0/valve_off.h" + class CDbgFmtMsg { public: diff --git a/public/tier0/fasttimer.h b/public/tier0/fasttimer.h index dfa9d7f0..b63d2f52 100644 --- a/public/tier0/fasttimer.h +++ b/public/tier0/fasttimer.h @@ -256,7 +256,7 @@ inline void CCycleCount::Init() inline void CCycleCount::Init( float initTimeMsec ) { if ( g_ClockSpeedMillisecondsMultiplier > 0 ) - m_Int64 = initTimeMsec / g_ClockSpeedMillisecondsMultiplier; + m_Int64 = static_cast(initTimeMsec / g_ClockSpeedMillisecondsMultiplier); else m_Int64 = 0; } @@ -268,7 +268,7 @@ inline void CCycleCount::Init( int64 cycles ) inline void CCycleCount::Sample() { - unsigned long* pSample = (unsigned long *)&m_Int64; + void* pSample = &m_Int64; #ifdef _WIN32 __asm { diff --git a/public/tier0/icommandline.h b/public/tier0/icommandline.h index f0e0bf07..134c640f 100644 --- a/public/tier0/icommandline.h +++ b/public/tier0/icommandline.h @@ -1,56 +1,56 @@ -//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// -// -// Purpose: -// -// $Workfile: $ -// $NoKeywords: $ -//===========================================================================// - -#ifndef TIER0_ICOMMANDLINE_H -#define TIER0_ICOMMANDLINE_H -#ifdef _WIN32 -#pragma once -#endif - -#include "tier0/platform.h" - - -//----------------------------------------------------------------------------- -// Purpose: Interface to engine command line -//----------------------------------------------------------------------------- -abstract_class ICommandLine -{ -public: - virtual void CreateCmdLine( const char *commandline ) = 0; - virtual void CreateCmdLine( int argc, char **argv ) = 0; - virtual const char *GetCmdLine( void ) const = 0; - - // Check whether a particular parameter exists - virtual const char *CheckParm( const char *psz, const char **ppszValue = 0 ) const = 0; - virtual void RemoveParm( const char *parm ) = 0; - virtual void AppendParm( const char *pszParm, const char *pszValues ) = 0; - - // Returns the argument after the one specified, or the default if not found - virtual const char *ParmValue( const char *psz, const char *pDefaultVal = 0 ) const = 0; - virtual int ParmValue( const char *psz, int nDefaultVal ) const = 0; - virtual float ParmValue( const char *psz, float flDefaultVal ) const = 0; - - // Gets at particular parameters - virtual int ParmCount() const = 0; - virtual int FindParm( const char *psz ) const = 0; // Returns 0 if not found. - virtual const char* GetParm( int nIndex ) const = 0; -}; - - -//----------------------------------------------------------------------------- -// Gets a singleton to the commandline interface -// NOTE: The #define trickery here is necessary for backwards compat: -// this interface used to lie in the vstdlib library. -//----------------------------------------------------------------------------- -PLATFORM_INTERFACE ICommandLine *CommandLine_Tier0(); - -#if !defined( VSTDLIB_BACKWARD_COMPAT ) -#define CommandLine CommandLine_Tier0 -#endif - -#endif // TIER0_ICOMMANDLINE_H \ No newline at end of file +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + +#ifndef TIER0_ICOMMANDLINE_H +#define TIER0_ICOMMANDLINE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/platform.h" + + +//----------------------------------------------------------------------------- +// Purpose: Interface to engine command line +//----------------------------------------------------------------------------- +abstract_class ICommandLine +{ +public: + virtual void CreateCmdLine( const char *commandline ) = 0; + virtual void CreateCmdLine( int argc, char **argv ) = 0; + virtual const char *GetCmdLine( void ) const = 0; + + // Check whether a particular parameter exists + virtual const char *CheckParm( const char *psz, const char **ppszValue = 0 ) const = 0; + virtual void RemoveParm( const char *parm ) = 0; + virtual void AppendParm( const char *pszParm, const char *pszValues ) = 0; + + // Returns the argument after the one specified, or the default if not found + virtual const char *ParmValue( const char *psz, const char *pDefaultVal = 0 ) const = 0; + virtual int ParmValue( const char *psz, int nDefaultVal ) const = 0; + virtual float ParmValue( const char *psz, float flDefaultVal ) const = 0; + + // Gets at particular parameters + virtual int ParmCount() const = 0; + virtual int FindParm( const char *psz ) const = 0; // Returns 0 if not found. + virtual const char* GetParm( int nIndex ) const = 0; +}; + + +//----------------------------------------------------------------------------- +// Gets a singleton to the commandline interface +// NOTE: The #define trickery here is necessary for backwards compat: +// this interface used to lie in the vstdlib library. +//----------------------------------------------------------------------------- +PLATFORM_INTERFACE ICommandLine *CommandLine_Tier0(); + +#if !defined( VSTDLIB_BACKWARD_COMPAT ) +#define CommandLine CommandLine_Tier0 +#endif + +#endif // TIER0_ICOMMANDLINE_H diff --git a/public/tier0/l2cache.h b/public/tier0/l2cache.h index 77636599..4ca2dc59 100644 --- a/public/tier0/l2cache.h +++ b/public/tier0/l2cache.h @@ -1,46 +1,46 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// -#ifndef CL2CACHE_H -#define CL2CACHE_H -#ifdef _WIN32 -#pragma once -#endif - -class P4Event_BSQ_cache_reference; - -class CL2Cache -{ -public: - - CL2Cache(); - ~CL2Cache(); - - void Start( void ); - void End( void ); - - //------------------------------------------------------------------------- - // GetL2CacheMisses - //------------------------------------------------------------------------- - int GetL2CacheMisses( void ) - { - return m_iL2CacheMissCount; - } - -#ifdef DBGFLAG_VALIDATE - void Validate( CValidator &validator, tchar *pchName ); // Validate our internal structures -#endif // DBGFLAG_VALIDATE - -private: - - int m_nID; - - P4Event_BSQ_cache_reference *m_pL2CacheEvent; - int64 m_i64Start; - int64 m_i64End; - int m_iL2CacheMissCount; -}; - -#endif // CL2CACHE_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#ifndef CL2CACHE_H +#define CL2CACHE_H +#ifdef _WIN32 +#pragma once +#endif + +class P4Event_BSQ_cache_reference; + +class CL2Cache +{ +public: + + CL2Cache(); + ~CL2Cache(); + + void Start( void ); + void End( void ); + + //------------------------------------------------------------------------- + // GetL2CacheMisses + //------------------------------------------------------------------------- + int GetL2CacheMisses( void ) + { + return m_iL2CacheMissCount; + } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, tchar *pchName ); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + +private: + + int m_nID; + + P4Event_BSQ_cache_reference *m_pL2CacheEvent; + int64 m_i64Start; + int64 m_i64End; + int m_iL2CacheMissCount; +}; + +#endif // CL2CACHE_H diff --git a/public/tier0/memalloc.h b/public/tier0/memalloc.h index c851c111..c28c7674 100644 --- a/public/tier0/memalloc.h +++ b/public/tier0/memalloc.h @@ -299,7 +299,7 @@ struct MemAllocFileLine_t #define MEM_ALLOC_CREDIT_CLASS() #define MEM_ALLOC_CLASSNAME(type) NULL -#endif !STEAM && NO_MALLOC_OVERRIDE +#endif /* !STEAM && NO_MALLOC_OVERRIDE */ //----------------------------------------------------------------------------- diff --git a/public/tier0/memdbgon.h b/public/tier0/memdbgon.h index 897b8338..f8c8e429 100644 --- a/public/tier0/memdbgon.h +++ b/public/tier0/memdbgon.h @@ -11,7 +11,7 @@ // to include this potentially multiple times (since we can deactivate debugging // by including memdbgoff.h) -#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) && defined(_WIN32) // SPECIAL NOTE #2: This must be the final include in a .cpp or .h file!!! @@ -172,7 +172,7 @@ inline char *MemAlloc_StrDup(const char *pString) if (!pString) return NULL; - int len = strlen(pString) + 1; + size_t len = strlen(pString) + 1; if ((pMemory = (char *)g_pMemAlloc->Alloc(len)) != NULL) { return strcpy( pMemory, pString ); @@ -188,7 +188,7 @@ inline wchar_t *MemAlloc_WcStrDup(const wchar_t *pString) if (!pString) return NULL; - int len = (wcslen(pString) + 1); + size_t len = (wcslen(pString) + 1); if ((pMemory = (wchar_t *)g_pMemAlloc->Alloc(len * sizeof(wchar_t))) != NULL) { return wcscpy( pMemory, pString ); diff --git a/public/tier0/memoverride-vc7.cpp b/public/tier0/memoverride-vc7.cpp index 23e1e883..8a315f2d 100644 --- a/public/tier0/memoverride-vc7.cpp +++ b/public/tier0/memoverride-vc7.cpp @@ -1,578 +1,578 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Insert this file into all projects using the memory system -// It will cause that project to use the shader memory allocator -// -// $NoKeywords: $ -//=============================================================================// - - -#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) - -#undef PROTECTED_THINGS_ENABLE // allow use of _vsnprintf - -#if defined(_WIN32) && !defined(_XBOX) -#define WIN_32_LEAN_AND_MEAN -#include -#endif - - - -#include "tier0/dbg.h" -#include "tier0/memalloc.h" -#include -#include -#include "memdbgoff.h" - -// Tags this DLL as debug -#if _DEBUG -DLL_EXPORT void BuiltDebug() {} -#endif - -#ifdef _WIN32 -// ARG: crtdbg is necessary for certain definitions below, -// but it also redefines malloc as a macro in release. -// To disable this, we gotta define _DEBUG before including it.. BLEAH! -#define _DEBUG 1 -#include "crtdbg.h" -#ifdef NDEBUG -#undef _DEBUG -#endif - -// Turn this back off in release mode. -#ifdef NDEBUG -#undef _DEBUG -#endif -#elif _LINUX -#define __cdecl -#endif - - -#if defined(_WIN32) && !defined(_XBOX) -const char *MakeModuleFileName() -{ - if ( g_pMemAlloc->IsDebugHeap() ) - { - char *pszModuleName = (char *)HeapAlloc( GetProcessHeap(), 0, MAX_PATH ); // small leak, debug only - - MEMORY_BASIC_INFORMATION mbi; - static int dummy; - VirtualQuery( &dummy, &mbi, sizeof(mbi) ); - - GetModuleFileName( reinterpret_cast(mbi.AllocationBase), pszModuleName, MAX_PATH ); - char *pDot = strrchr( pszModuleName, '.' ); - if ( pDot ) - { - char *pSlash = strrchr( pszModuleName, '\\' ); - if ( pSlash ) - { - pszModuleName = pSlash + 1; - *pDot = 0; - } - } - - return pszModuleName; - } - return NULL; -} - -static void *AllocUnattributed( size_t nSize ) -{ - static const char *pszOwner = MakeModuleFileName(); - - if ( !pszOwner ) - return g_pMemAlloc->Alloc(nSize); - else - return g_pMemAlloc->Alloc(nSize, pszOwner, 0); -} - -static void *ReallocUnattributed( void *pMem, size_t nSize ) -{ - static const char *pszOwner = MakeModuleFileName(); - - if ( !pszOwner ) - return g_pMemAlloc->Realloc(pMem, nSize); - else - return g_pMemAlloc->Realloc(pMem, nSize, pszOwner, 0); -} - -#else -#define MakeModuleFileName() NULL -inline void *AllocUnattributed( size_t nSize ) -{ - return g_pMemAlloc->Alloc(nSize); -} - -inline void *ReallocUnattributed( void *pMem, size_t nSize ) -{ - return g_pMemAlloc->Realloc(pMem, nSize); -} -#endif - -//----------------------------------------------------------------------------- -// Standard functions in the CRT that we're going to override to call our allocator -//----------------------------------------------------------------------------- -#if defined(_WIN32) && !defined(_STATIC_LINKED) -// this magic only works under win32 -// under linux this malloc() overrides the libc malloc() and so we -// end up in a recursion (as g_pMemAlloc->Alloc() calls malloc) -#if _MSC_VER >= 1400 -#define ALLOC_CALL _CRTNOALIAS _CRTRESTRICT -#define FREE_CALL _CRTNOALIAS -#else -#define ALLOC_CALL -#define FREE_CALL -#endif - -extern "C" -{ - -ALLOC_CALL void *malloc( size_t nSize ) -{ - return AllocUnattributed( nSize ); -} - -FREE_CALL void free( void *pMem ) -{ - g_pMemAlloc->Free(pMem); -} - -ALLOC_CALL void *realloc( void *pMem, size_t nSize ) -{ - return ReallocUnattributed( pMem, nSize ); -} - -ALLOC_CALL void *calloc( size_t nCount, size_t nElementSize ) -{ - void *pMem = AllocUnattributed( nElementSize * nCount ); - memset(pMem, 0, nElementSize * nCount); - return pMem; -} - -} // end extern "C" - -//----------------------------------------------------------------------------- -// Non-standard MSVC functions that we're going to override to call our allocator -//----------------------------------------------------------------------------- -extern "C" -{ - -void *_malloc_base( size_t nSize ) -{ - return AllocUnattributed( nSize ); -} - -void _free_base( void *pMem ) -{ - g_pMemAlloc->Free(pMem); -} - -void *_realloc_base( void *pMem, size_t nSize ) -{ - return ReallocUnattributed( pMem, nSize ); -} - -int _heapchk() -{ - return g_pMemAlloc->heapchk(); -} - -int _heapmin() -{ - return 1; -} - -size_t _msize( void *pMem ) -{ - return g_pMemAlloc->GetSize(pMem); -} - -size_t msize( void *pMem ) -{ - return g_pMemAlloc->GetSize(pMem); -} - -void *__cdecl _heap_alloc( size_t nSize ) -{ - return AllocUnattributed( nSize ); -} - -void *__cdecl _nh_malloc( size_t nSize, int ) -{ - return AllocUnattributed( nSize ); -} - -void *__cdecl _expand( void *pMem, size_t nSize ) -{ - Assert( 0 ); - return NULL; -} - -unsigned int _amblksiz = 16; //BYTES_PER_PARA; - -size_t __cdecl _get_sbh_threshold( void ) -{ - return 0; -} - -int __cdecl _set_sbh_threshold( size_t ) -{ - return 0; -} - -int __cdecl _heapadd( void *, size_t ) -{ - return 0; -} - -int __cdecl _heapset( unsigned int ) -{ - return 0; -} - -size_t __cdecl _heapused( size_t *, size_t * ) -{ - return 0; -} - -#ifdef _WIN32 -int __cdecl _heapwalk( _HEAPINFO * ) -{ - return 0; -} -#endif - -} // end extern "C" - - -//----------------------------------------------------------------------------- -// Debugging functions that we're going to override to call our allocator -// NOTE: These have to be here for release + debug builds in case we -// link to a debug static lib!!! -//----------------------------------------------------------------------------- - -extern "C" -{ - -void *malloc_db( size_t nSize, const char *pFileName, int nLine ) -{ - return g_pMemAlloc->Alloc(nSize, pFileName, nLine); -} - -void free_db( void *pMem, const char *pFileName, int nLine ) -{ - g_pMemAlloc->Free(pMem, pFileName, nLine); -} - -void *realloc_db( void *pMem, size_t nSize, const char *pFileName, int nLine ) -{ - return g_pMemAlloc->Realloc(pMem, nSize, pFileName, nLine); -} - -} // end extern "C" - -//----------------------------------------------------------------------------- -// These methods are standard MSVC heap initialization + shutdown methods -//----------------------------------------------------------------------------- -extern "C" -{ - - int __cdecl _heap_init() - { - return g_pMemAlloc != NULL; - } - - void __cdecl _heap_term() - { - } - -} - -#endif - - -//----------------------------------------------------------------------------- -// Prevents us from using an inappropriate new or delete method, -// ensures they are here even when linking against debug or release static libs -//----------------------------------------------------------------------------- -#ifndef NO_MEMOVERRIDE_NEW_DELETE -void *__cdecl operator new( unsigned int nSize ) -{ - return AllocUnattributed( nSize ); -} - -void *__cdecl operator new( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) -{ - return g_pMemAlloc->Alloc(nSize, pFileName, nLine); -} - -void __cdecl operator delete( void *pMem ) -{ - g_pMemAlloc->Free( pMem ); -} - -void *__cdecl operator new[] ( unsigned int nSize ) -{ - return AllocUnattributed( nSize ); -} - -void *__cdecl operator new[] ( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) -{ - return g_pMemAlloc->Alloc(nSize, pFileName, nLine); -} - -void __cdecl operator delete[] ( void *pMem ) -{ - g_pMemAlloc->Free( pMem ); -} -#endif - - -//----------------------------------------------------------------------------- -// Override some debugging allocation methods in MSVC -// NOTE: These have to be here for release + debug builds in case we -// link to a debug static lib!!! -//----------------------------------------------------------------------------- -#ifndef _STATIC_LINKED -#ifdef _WIN32 - -// This here just hides the internal file names, etc of allocations -// made in the c runtime library -#define CRT_INTERNAL_FILE_NAME "C-runtime internal" - -class CAttibCRT -{ -public: - CAttibCRT(int nBlockUse) : m_nBlockUse(nBlockUse) - { - if (m_nBlockUse == _CRT_BLOCK) - { - g_pMemAlloc->PushAllocDbgInfo(CRT_INTERNAL_FILE_NAME, 0); - } - } - - ~CAttibCRT() - { - if (m_nBlockUse == _CRT_BLOCK) - { - g_pMemAlloc->PopAllocDbgInfo(); - } - } - -private: - int m_nBlockUse; -}; - - -#define AttribIfCrt() CAttibCRT _attrib(nBlockUse) -#elif defined(_LINUX) -#define AttribIfCrt() -#endif // _WIN32 - - -extern "C" -{ - -void *__cdecl _nh_malloc_dbg( size_t nSize, int nFlag, int nBlockUse, - const char *pFileName, int nLine ) -{ - AttribIfCrt(); - return g_pMemAlloc->Alloc(nSize, pFileName, nLine); -} - -void *__cdecl _malloc_dbg( size_t nSize, int nBlockUse, - const char *pFileName, int nLine ) -{ - AttribIfCrt(); - return g_pMemAlloc->Alloc(nSize, pFileName, nLine); -} - -void *__cdecl _calloc_dbg( size_t nNum, size_t nSize, int nBlockUse, - const char *pFileName, int nLine ) -{ - AttribIfCrt(); - void *pMem = g_pMemAlloc->Alloc(nSize * nNum, pFileName, nLine); - memset(pMem, 0, nSize * nNum); - return pMem; -} - -void *__cdecl _realloc_dbg( void *pMem, size_t nNewSize, int nBlockUse, - const char *pFileName, int nLine ) -{ - AttribIfCrt(); - return g_pMemAlloc->Realloc(pMem, nNewSize, pFileName, nLine); -} - -void *__cdecl _expand_dbg( void *pMem, size_t nNewSize, int nBlockUse, - const char *pFileName, int nLine ) -{ - Assert( 0 ); - return NULL; -} - -void __cdecl _free_dbg( void *pMem, int nBlockUse ) -{ - AttribIfCrt(); - g_pMemAlloc->Free(pMem); -} - -size_t __cdecl _msize_dbg( void *pMem, int nBlockUse ) -{ -#ifdef _WIN32 - return _msize(pMem); -#elif _LINUX - Assert( "_msize_dbg unsupported" ); - return 0; -#endif -} - - -} // end extern "C" - - -//----------------------------------------------------------------------------- -// Override some the _CRT debugging allocation methods in MSVC -//----------------------------------------------------------------------------- -#ifdef _WIN32 - -extern "C" -{ - -int _CrtDumpMemoryLeaks(void) -{ - return 0; -} - -_CRT_DUMP_CLIENT _CrtSetDumpClient( _CRT_DUMP_CLIENT dumpClient ) -{ - return NULL; -} - -int _CrtSetDbgFlag( int nNewFlag ) -{ - return g_pMemAlloc->CrtSetDbgFlag( nNewFlag ); -} - -long _crtBreakAlloc; /* Break on this allocation */ -int _crtDbgFlag = _CRTDBG_ALLOC_MEM_DF; - -void __cdecl _CrtSetDbgBlockType( void *pMem, int nBlockUse ) -{ - DebuggerBreak(); -} - -_CRT_ALLOC_HOOK __cdecl _CrtSetAllocHook( _CRT_ALLOC_HOOK pfnNewHook ) -{ - DebuggerBreak(); - return NULL; -} - -long __cdecl _CrtSetBreakAlloc( long lNewBreakAlloc ) -{ - return g_pMemAlloc->CrtSetBreakAlloc( lNewBreakAlloc ); -} - -int __cdecl _CrtIsValidHeapPointer( const void *pMem ) -{ - return g_pMemAlloc->CrtIsValidHeapPointer( pMem ); -} - -int __cdecl _CrtIsValidPointer( const void *pMem, unsigned int size, int access ) -{ - return g_pMemAlloc->CrtIsValidPointer( pMem, size, access ); -} - -int __cdecl _CrtCheckMemory( void ) -{ - // FIXME: Remove this when we re-implement the heap - return g_pMemAlloc->CrtCheckMemory( ); -} - -int __cdecl _CrtIsMemoryBlock( const void *pMem, unsigned int nSize, - long *plRequestNumber, char **ppFileName, int *pnLine ) -{ - DebuggerBreak(); - return 1; -} - -int __cdecl _CrtMemDifference( _CrtMemState *pState, const _CrtMemState * oldState, const _CrtMemState * newState ) -{ - DebuggerBreak(); - return FALSE; -} - -void __cdecl _CrtMemDumpStatistics( const _CrtMemState *pState ) -{ - DebuggerBreak(); -} - -void __cdecl _CrtMemCheckpoint( _CrtMemState *pState ) -{ - // FIXME: Remove this when we re-implement the heap - g_pMemAlloc->CrtMemCheckpoint( pState ); -} - -void __cdecl _CrtMemDumpAllObjectsSince( const _CrtMemState *pState ) -{ - DebuggerBreak(); -} - -void __cdecl _CrtDoForAllClientObjects( void (*pfn)(void *, void *), void * pContext ) -{ - DebuggerBreak(); -} - - -//----------------------------------------------------------------------------- -// Methods in dbgrpt.cpp -//----------------------------------------------------------------------------- -long _crtAssertBusy = -1; - -int __cdecl _CrtSetReportMode( int nReportType, int nReportMode ) -{ - return g_pMemAlloc->CrtSetReportMode( nReportType, nReportMode ); -} - -_HFILE __cdecl _CrtSetReportFile( int nRptType, _HFILE hFile ) -{ - return (_HFILE)g_pMemAlloc->CrtSetReportFile( nRptType, hFile ); -} - -_CRT_REPORT_HOOK __cdecl _CrtSetReportHook( _CRT_REPORT_HOOK pfnNewHook ) -{ - return (_CRT_REPORT_HOOK)g_pMemAlloc->CrtSetReportHook( pfnNewHook ); -} - -int __cdecl _CrtDbgReport( int nRptType, const char * szFile, - int nLine, const char * szModule, const char * szFormat, ... ) -{ - static char output[1024]; - va_list args; - va_start( args, szFormat ); - _vsnprintf( output, sizeof( output )-1, szFormat, args ); - va_end( args ); - - return g_pMemAlloc->CrtDbgReport( nRptType, szFile, nLine, szModule, output ); -} - -int __cdecl _CrtReportBlockType(const void * pUserData) -{ - return 0; -} - - -} // end extern "C" -#endif // _WIN32 - -// Most files include this file, so when it's used it adds an extra .ValveDbg section, -// to help identify debug binaries. -#ifdef _WIN32 - #ifndef NDEBUG // _DEBUG - #pragma data_seg("ValveDBG") - volatile const char* DBG = "*** DEBUG STUB ***"; - #endif -#endif - -#endif - -#endif // !STEAM && !NO_MALLOC_OVERRIDE \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Insert this file into all projects using the memory system +// It will cause that project to use the shader memory allocator +// +// $NoKeywords: $ +//=============================================================================// + + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +#undef PROTECTED_THINGS_ENABLE // allow use of _vsnprintf + +#if defined(_WIN32) && !defined(_XBOX) +#define WIN_32_LEAN_AND_MEAN +#include +#endif + + + +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include +#include +#include "memdbgoff.h" + +// Tags this DLL as debug +#if _DEBUG +DLL_EXPORT void BuiltDebug() {} +#endif + +#ifdef _WIN32 +// ARG: crtdbg is necessary for certain definitions below, +// but it also redefines malloc as a macro in release. +// To disable this, we gotta define _DEBUG before including it.. BLEAH! +#define _DEBUG 1 +#include "crtdbg.h" +#ifdef NDEBUG +#undef _DEBUG +#endif + +// Turn this back off in release mode. +#ifdef NDEBUG +#undef _DEBUG +#endif +#elif _LINUX +#define __cdecl +#endif + + +#if defined(_WIN32) && !defined(_XBOX) +const char *MakeModuleFileName() +{ + if ( g_pMemAlloc->IsDebugHeap() ) + { + char *pszModuleName = (char *)HeapAlloc( GetProcessHeap(), 0, MAX_PATH ); // small leak, debug only + + MEMORY_BASIC_INFORMATION mbi; + static int dummy; + VirtualQuery( &dummy, &mbi, sizeof(mbi) ); + + GetModuleFileName( reinterpret_cast(mbi.AllocationBase), pszModuleName, MAX_PATH ); + char *pDot = strrchr( pszModuleName, '.' ); + if ( pDot ) + { + char *pSlash = strrchr( pszModuleName, '\\' ); + if ( pSlash ) + { + pszModuleName = pSlash + 1; + *pDot = 0; + } + } + + return pszModuleName; + } + return NULL; +} + +static void *AllocUnattributed( size_t nSize ) +{ + static const char *pszOwner = MakeModuleFileName(); + + if ( !pszOwner ) + return g_pMemAlloc->Alloc(nSize); + else + return g_pMemAlloc->Alloc(nSize, pszOwner, 0); +} + +static void *ReallocUnattributed( void *pMem, size_t nSize ) +{ + static const char *pszOwner = MakeModuleFileName(); + + if ( !pszOwner ) + return g_pMemAlloc->Realloc(pMem, nSize); + else + return g_pMemAlloc->Realloc(pMem, nSize, pszOwner, 0); +} + +#else +#define MakeModuleFileName() NULL +inline void *AllocUnattributed( size_t nSize ) +{ + return g_pMemAlloc->Alloc(nSize); +} + +inline void *ReallocUnattributed( void *pMem, size_t nSize ) +{ + return g_pMemAlloc->Realloc(pMem, nSize); +} +#endif + +//----------------------------------------------------------------------------- +// Standard functions in the CRT that we're going to override to call our allocator +//----------------------------------------------------------------------------- +#if defined(_WIN32) && !defined(_STATIC_LINKED) +// this magic only works under win32 +// under linux this malloc() overrides the libc malloc() and so we +// end up in a recursion (as g_pMemAlloc->Alloc() calls malloc) +#if _MSC_VER >= 1400 +#define ALLOC_CALL _CRTNOALIAS _CRTRESTRICT +#define FREE_CALL _CRTNOALIAS +#else +#define ALLOC_CALL +#define FREE_CALL +#endif + +extern "C" +{ + +ALLOC_CALL void *malloc( size_t nSize ) +{ + return AllocUnattributed( nSize ); +} + +FREE_CALL void free( void *pMem ) +{ + g_pMemAlloc->Free(pMem); +} + +ALLOC_CALL void *realloc( void *pMem, size_t nSize ) +{ + return ReallocUnattributed( pMem, nSize ); +} + +ALLOC_CALL void *calloc( size_t nCount, size_t nElementSize ) +{ + void *pMem = AllocUnattributed( nElementSize * nCount ); + memset(pMem, 0, nElementSize * nCount); + return pMem; +} + +} // end extern "C" + +//----------------------------------------------------------------------------- +// Non-standard MSVC functions that we're going to override to call our allocator +//----------------------------------------------------------------------------- +extern "C" +{ + +void *_malloc_base( size_t nSize ) +{ + return AllocUnattributed( nSize ); +} + +void _free_base( void *pMem ) +{ + g_pMemAlloc->Free(pMem); +} + +void *_realloc_base( void *pMem, size_t nSize ) +{ + return ReallocUnattributed( pMem, nSize ); +} + +int _heapchk() +{ + return g_pMemAlloc->heapchk(); +} + +int _heapmin() +{ + return 1; +} + +size_t _msize( void *pMem ) +{ + return g_pMemAlloc->GetSize(pMem); +} + +size_t msize( void *pMem ) +{ + return g_pMemAlloc->GetSize(pMem); +} + +void *__cdecl _heap_alloc( size_t nSize ) +{ + return AllocUnattributed( nSize ); +} + +void *__cdecl _nh_malloc( size_t nSize, int ) +{ + return AllocUnattributed( nSize ); +} + +void *__cdecl _expand( void *pMem, size_t nSize ) +{ + Assert( 0 ); + return NULL; +} + +unsigned int _amblksiz = 16; //BYTES_PER_PARA; + +size_t __cdecl _get_sbh_threshold( void ) +{ + return 0; +} + +int __cdecl _set_sbh_threshold( size_t ) +{ + return 0; +} + +int __cdecl _heapadd( void *, size_t ) +{ + return 0; +} + +int __cdecl _heapset( unsigned int ) +{ + return 0; +} + +size_t __cdecl _heapused( size_t *, size_t * ) +{ + return 0; +} + +#ifdef _WIN32 +int __cdecl _heapwalk( _HEAPINFO * ) +{ + return 0; +} +#endif + +} // end extern "C" + + +//----------------------------------------------------------------------------- +// Debugging functions that we're going to override to call our allocator +// NOTE: These have to be here for release + debug builds in case we +// link to a debug static lib!!! +//----------------------------------------------------------------------------- + +extern "C" +{ + +void *malloc_db( size_t nSize, const char *pFileName, int nLine ) +{ + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void free_db( void *pMem, const char *pFileName, int nLine ) +{ + g_pMemAlloc->Free(pMem, pFileName, nLine); +} + +void *realloc_db( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + return g_pMemAlloc->Realloc(pMem, nSize, pFileName, nLine); +} + +} // end extern "C" + +//----------------------------------------------------------------------------- +// These methods are standard MSVC heap initialization + shutdown methods +//----------------------------------------------------------------------------- +extern "C" +{ + + int __cdecl _heap_init() + { + return g_pMemAlloc != NULL; + } + + void __cdecl _heap_term() + { + } + +} + +#endif + + +//----------------------------------------------------------------------------- +// Prevents us from using an inappropriate new or delete method, +// ensures they are here even when linking against debug or release static libs +//----------------------------------------------------------------------------- +#ifndef NO_MEMOVERRIDE_NEW_DELETE +void *__cdecl operator new( unsigned int nSize ) +{ + return AllocUnattributed( nSize ); +} + +void *__cdecl operator new( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void __cdecl operator delete( void *pMem ) +{ + g_pMemAlloc->Free( pMem ); +} + +void *__cdecl operator new[] ( unsigned int nSize ) +{ + return AllocUnattributed( nSize ); +} + +void *__cdecl operator new[] ( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void __cdecl operator delete[] ( void *pMem ) +{ + g_pMemAlloc->Free( pMem ); +} +#endif + + +//----------------------------------------------------------------------------- +// Override some debugging allocation methods in MSVC +// NOTE: These have to be here for release + debug builds in case we +// link to a debug static lib!!! +//----------------------------------------------------------------------------- +#ifndef _STATIC_LINKED +#ifdef _WIN32 + +// This here just hides the internal file names, etc of allocations +// made in the c runtime library +#define CRT_INTERNAL_FILE_NAME "C-runtime internal" + +class CAttibCRT +{ +public: + CAttibCRT(int nBlockUse) : m_nBlockUse(nBlockUse) + { + if (m_nBlockUse == _CRT_BLOCK) + { + g_pMemAlloc->PushAllocDbgInfo(CRT_INTERNAL_FILE_NAME, 0); + } + } + + ~CAttibCRT() + { + if (m_nBlockUse == _CRT_BLOCK) + { + g_pMemAlloc->PopAllocDbgInfo(); + } + } + +private: + int m_nBlockUse; +}; + + +#define AttribIfCrt() CAttibCRT _attrib(nBlockUse) +#elif defined(_LINUX) +#define AttribIfCrt() +#endif // _WIN32 + + +extern "C" +{ + +void *__cdecl _nh_malloc_dbg( size_t nSize, int nFlag, int nBlockUse, + const char *pFileName, int nLine ) +{ + AttribIfCrt(); + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void *__cdecl _malloc_dbg( size_t nSize, int nBlockUse, + const char *pFileName, int nLine ) +{ + AttribIfCrt(); + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void *__cdecl _calloc_dbg( size_t nNum, size_t nSize, int nBlockUse, + const char *pFileName, int nLine ) +{ + AttribIfCrt(); + void *pMem = g_pMemAlloc->Alloc(nSize * nNum, pFileName, nLine); + memset(pMem, 0, nSize * nNum); + return pMem; +} + +void *__cdecl _realloc_dbg( void *pMem, size_t nNewSize, int nBlockUse, + const char *pFileName, int nLine ) +{ + AttribIfCrt(); + return g_pMemAlloc->Realloc(pMem, nNewSize, pFileName, nLine); +} + +void *__cdecl _expand_dbg( void *pMem, size_t nNewSize, int nBlockUse, + const char *pFileName, int nLine ) +{ + Assert( 0 ); + return NULL; +} + +void __cdecl _free_dbg( void *pMem, int nBlockUse ) +{ + AttribIfCrt(); + g_pMemAlloc->Free(pMem); +} + +size_t __cdecl _msize_dbg( void *pMem, int nBlockUse ) +{ +#ifdef _WIN32 + return _msize(pMem); +#elif _LINUX + Assert( "_msize_dbg unsupported" ); + return 0; +#endif +} + + +} // end extern "C" + + +//----------------------------------------------------------------------------- +// Override some the _CRT debugging allocation methods in MSVC +//----------------------------------------------------------------------------- +#ifdef _WIN32 + +extern "C" +{ + +int _CrtDumpMemoryLeaks(void) +{ + return 0; +} + +_CRT_DUMP_CLIENT _CrtSetDumpClient( _CRT_DUMP_CLIENT dumpClient ) +{ + return NULL; +} + +int _CrtSetDbgFlag( int nNewFlag ) +{ + return g_pMemAlloc->CrtSetDbgFlag( nNewFlag ); +} + +long _crtBreakAlloc; /* Break on this allocation */ +int _crtDbgFlag = _CRTDBG_ALLOC_MEM_DF; + +void __cdecl _CrtSetDbgBlockType( void *pMem, int nBlockUse ) +{ + DebuggerBreak(); +} + +_CRT_ALLOC_HOOK __cdecl _CrtSetAllocHook( _CRT_ALLOC_HOOK pfnNewHook ) +{ + DebuggerBreak(); + return NULL; +} + +long __cdecl _CrtSetBreakAlloc( long lNewBreakAlloc ) +{ + return g_pMemAlloc->CrtSetBreakAlloc( lNewBreakAlloc ); +} + +int __cdecl _CrtIsValidHeapPointer( const void *pMem ) +{ + return g_pMemAlloc->CrtIsValidHeapPointer( pMem ); +} + +int __cdecl _CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ + return g_pMemAlloc->CrtIsValidPointer( pMem, size, access ); +} + +int __cdecl _CrtCheckMemory( void ) +{ + // FIXME: Remove this when we re-implement the heap + return g_pMemAlloc->CrtCheckMemory( ); +} + +int __cdecl _CrtIsMemoryBlock( const void *pMem, unsigned int nSize, + long *plRequestNumber, char **ppFileName, int *pnLine ) +{ + DebuggerBreak(); + return 1; +} + +int __cdecl _CrtMemDifference( _CrtMemState *pState, const _CrtMemState * oldState, const _CrtMemState * newState ) +{ + DebuggerBreak(); + return FALSE; +} + +void __cdecl _CrtMemDumpStatistics( const _CrtMemState *pState ) +{ + DebuggerBreak(); +} + +void __cdecl _CrtMemCheckpoint( _CrtMemState *pState ) +{ + // FIXME: Remove this when we re-implement the heap + g_pMemAlloc->CrtMemCheckpoint( pState ); +} + +void __cdecl _CrtMemDumpAllObjectsSince( const _CrtMemState *pState ) +{ + DebuggerBreak(); +} + +void __cdecl _CrtDoForAllClientObjects( void (*pfn)(void *, void *), void * pContext ) +{ + DebuggerBreak(); +} + + +//----------------------------------------------------------------------------- +// Methods in dbgrpt.cpp +//----------------------------------------------------------------------------- +long _crtAssertBusy = -1; + +int __cdecl _CrtSetReportMode( int nReportType, int nReportMode ) +{ + return g_pMemAlloc->CrtSetReportMode( nReportType, nReportMode ); +} + +_HFILE __cdecl _CrtSetReportFile( int nRptType, _HFILE hFile ) +{ + return (_HFILE)g_pMemAlloc->CrtSetReportFile( nRptType, hFile ); +} + +_CRT_REPORT_HOOK __cdecl _CrtSetReportHook( _CRT_REPORT_HOOK pfnNewHook ) +{ + return (_CRT_REPORT_HOOK)g_pMemAlloc->CrtSetReportHook( pfnNewHook ); +} + +int __cdecl _CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * szFormat, ... ) +{ + static char output[1024]; + va_list args; + va_start( args, szFormat ); + _vsnprintf( output, sizeof( output )-1, szFormat, args ); + va_end( args ); + + return g_pMemAlloc->CrtDbgReport( nRptType, szFile, nLine, szModule, output ); +} + +int __cdecl _CrtReportBlockType(const void * pUserData) +{ + return 0; +} + + +} // end extern "C" +#endif // _WIN32 + +// Most files include this file, so when it's used it adds an extra .ValveDbg section, +// to help identify debug binaries. +#ifdef _WIN32 + #ifndef NDEBUG // _DEBUG + #pragma data_seg("ValveDBG") + volatile const char* DBG = "*** DEBUG STUB ***"; + #endif +#endif + +#endif + +#endif // !STEAM && !NO_MALLOC_OVERRIDE diff --git a/public/tier0/platform.h b/public/tier0/platform.h index 6a200aa5..cc3b6f29 100644 --- a/public/tier0/platform.h +++ b/public/tier0/platform.h @@ -22,7 +22,11 @@ // need this for _alloca #include +#ifdef _MSC_VER #include +#elif defined __GNUC__ +#include +#endif // need this for memset #include @@ -343,13 +347,13 @@ typedef void * HINSTANCE; #define NO_DEFAULT default: UNREACHABLE(); #ifdef _WIN32 -// Alloca defined for this platform -#define stackalloc( _size ) _alloca( _size ) -#define stackfree( _p ) 0 + // Alloca defined for this platform + #define stackalloc( _size ) _alloca( _size ) + #define stackfree( _p ) #elif _LINUX -// Alloca defined for this platform -#define stackalloc( _size ) alloca( _size ) -#define stackfree( _p ) 0 + // Alloca defined for this platform + #define stackalloc( _size ) alloca( _size ) + #define stackfree( _p ) #endif #ifdef _WIN32 @@ -384,7 +388,7 @@ typedef void * HINSTANCE; #endif // When we port to 64 bit, we'll have to resolve the int, ptr vs size_t 32/64 bit problems... -#if !defined( _WIN64 ) +#if !defined( _WIN64 ) && defined _MSC_VER #pragma warning( disable : 4267 ) // conversion from 'size_t' to 'int', possible loss of data #pragma warning( disable : 4311 ) // pointer truncation from 'char *' to 'int' #pragma warning( disable : 4312 ) // conversion from 'unsigned int' to 'memhandle_t' of greater size @@ -528,11 +532,11 @@ inline float DWordSwapAsm( float f ) // The typically used methods. //------------------------------------- -#if defined(__i386__) || defined(_XBOX) +#if (defined(__i386__) || defined(_XBOX)) && !defined LITTLE_ENDIAN #define LITTLE_ENDIAN 1 #endif -#ifdef _SGI_SOURCE +#if defined _SGI_SOURCE && !defined BIG_ENDIAN #define BIG_ENDIAN 1 #endif diff --git a/public/tier0/threadtools.h b/public/tier0/threadtools.h index 17e8c556..a6e806ab 100644 --- a/public/tier0/threadtools.h +++ b/public/tier0/threadtools.h @@ -190,14 +190,14 @@ template class CThreadLocalInt : public CThreadLocal { public: - operator const T() const { return Get(); } + operator const T() const { return this->Get(); } int operator=( T i ) { Set( i ); return i; } - T operator++() { T i = Get(); Set( ++i ); return i; } - T operator++(int) { T i = Get(); Set( i + 1 ); return i; } + T operator++() { T i = this->Get(); Set( ++i ); return i; } + T operator++(int) { T i = this->Get(); Set( i + 1 ); return i; } - T operator--() { T i = Get(); Set( --i ); return i; } - T operator--(int) { T i = Get(); Set( i - 1 ); return i; } + T operator--() { T i = this->Get(); Set( --i ); return i; } + T operator--(int) { T i = this->Get(); Set( i - 1 ); return i; } }; //--------------------------------------------------------- @@ -430,7 +430,8 @@ public: private: bool TryLock( const uint32 threadId ) volatile { - if ( threadId != m_ownerID && ThreadInterlockedCompareExchange( (volatile long *)&m_ownerID, (long)threadId, 0 ) != 0 ) + volatile void *owner = &m_ownerID; + if ( threadId != m_ownerID && ThreadInterlockedCompareExchange( reinterpret_cast(owner), (long)threadId, 0 ) != 0 ) return false; m_depth++; diff --git a/public/tier0/vprof.h b/public/tier0/vprof.h index 5e8d19fe..39265a1a 100644 --- a/public/tier0/vprof.h +++ b/public/tier0/vprof.h @@ -263,8 +263,6 @@ private: int m_iCurL2CacheMiss; int m_iTotalL2CacheMiss; #endif - - int m_nRecursions; unsigned m_nCurFrameCalls; CCycleCount m_CurFrameTime; @@ -277,6 +275,8 @@ private: CCycleCount m_PeakTime; + int m_nRecursions; + CVProfNode *m_pParent; CVProfNode *m_pChild; CVProfNode *m_pSibling; @@ -285,6 +285,7 @@ private: int m_iClientData; int m_iUniqueNodeID; + #if !defined(_WIN32) || defined(_XBOX) void *operator new( size_t ); diff --git a/public/tier0/wchartypes.h b/public/tier0/wchartypes.h index 79bc5f53..229b770c 100644 --- a/public/tier0/wchartypes.h +++ b/public/tier0/wchartypes.h @@ -20,9 +20,12 @@ // Temporarily turn off Valve defines #include "tier0/valve_off.h" -#ifndef _WCHAR_T_DEFINED +#if defined _WIN32 && !defined _WCHAR_T_DEFINED typedef unsigned short wchar_t; #define _WCHAR_T_DEFINED +#elif defined _LINUX && !defined __WCHAR_TYPE__ +typedef unsigned short wchar_t; +#define __WCHAR_TYPE__ #endif // char8 diff --git a/public/tier1/UtlSortVector.h b/public/tier1/UtlSortVector.h index 6dfc0ae6..63136e32 100644 --- a/public/tier1/UtlSortVector.h +++ b/public/tier1/UtlSortVector.h @@ -94,13 +94,13 @@ private: //----------------------------------------------------------------------------- template CUtlSortVector::CUtlSortVector( int nGrowSize, int initSize ) : - m_pLessContext(NULL), CUtlVector( nGrowSize, initSize ), m_bNeedsSort( false ) + CUtlVector( nGrowSize, initSize ), m_pLessContext(NULL), m_bNeedsSort( false ) { } template CUtlSortVector::CUtlSortVector( T* pMemory, int numElements ) : - m_pLessContext(NULL), CUtlVector( pMemory, numElements ), m_bNeedsSort( false ) + CUtlVector( pMemory, numElements ), m_pLessContext(NULL), m_bNeedsSort( false ) { } @@ -122,9 +122,9 @@ int CUtlSortVector::Insert( const T& src ) AssertFatal( !m_bNeedsSort ); int pos = FindLessOrEqual( src ) + 1; - GrowVector(); - ShiftElementsRight(pos); - CopyConstruct( &Element(pos), src ); + this->GrowVector(); + this->ShiftElementsRight(pos); + CopyConstruct( &this->Element(pos), src ); return pos; } @@ -134,18 +134,18 @@ int CUtlSortVector::InsertNoSort( const T& src ) m_bNeedsSort = true; int lastElement = CUtlVector::m_Size; // Just stick the new element at the end of the vector, but don't do a sort - GrowVector(); - ShiftElementsRight(lastElement); - CopyConstruct( &Element(lastElement), src ); + this->GrowVector(); + this->ShiftElementsRight(lastElement); + CopyConstruct( &this->Element(lastElement), src ); return lastElement; } template void CUtlSortVector::Swap( int L, int R ) { - T temp = Element( L ); - Element( L ) = Element( R ); - Element( R ) = temp; + T temp = this->Element( L ); + this->Element( L ) = this->Element( R ); + this->Element( R ) = temp; } template @@ -165,13 +165,13 @@ int CUtlSortVector::SplitList( LessFunc& less, int nLower, int nUpp int nLeft = nLower + 1; int nRight = nUpper; - const T& val = Element( nLower ); + const T& val = this->Element( nLower ); while ( nLeft <= nRight ) { - while ( nLeft <= nRight && !less.Less( val, Element( nLeft ), m_pLessContext ) ) + while ( nLeft <= nRight && !less.Less( val, this->Element( nLeft ), m_pLessContext ) ) ++nLeft; - while ( nLeft <= nRight && !less.Less( Element( nRight ), val, m_pLessContext ) ) + while ( nLeft <= nRight && !less.Less( this->Element( nRight ), val, m_pLessContext ) ) --nRight; if ( nLeft < nRight ) @@ -194,7 +194,7 @@ void CUtlSortVector::RedoSort( bool bForceSort /*= false */ ) m_bNeedsSort = false; LessFunc less; - QuickSort( less, 0, Count() - 1 ); + QuickSort( less, 0, this->Count() - 1 ); } //----------------------------------------------------------------------------- @@ -207,15 +207,15 @@ int CUtlSortVector::Find( const T& src ) const LessFunc less; - int start = 0, end = Count() - 1; + int start = 0, end = this->Count() - 1; while (start <= end) { int mid = (start + end) >> 1; - if ( less.Less( Element(mid), src, m_pLessContext ) ) + if ( less.Less( this->Element(mid), src, m_pLessContext ) ) { start = mid + 1; } - else if ( less.Less( src, Element(mid), m_pLessContext ) ) + else if ( less.Less( src, this->Element(mid), m_pLessContext ) ) { end = mid - 1; } @@ -237,15 +237,15 @@ int CUtlSortVector::FindLessOrEqual( const T& src ) const AssertFatal( !m_bNeedsSort ); LessFunc less; - int start = 0, end = Count() - 1; + int start = 0, end = this->Count() - 1; while (start <= end) { int mid = (start + end) >> 1; - if ( less.Less( Element(mid), src, m_pLessContext ) ) + if ( less.Less( this->Element(mid), src, m_pLessContext ) ) { start = mid + 1; } - else if ( less.Less( src, Element(mid), m_pLessContext ) ) + else if ( less.Less( src, this->Element(mid), m_pLessContext ) ) { end = mid - 1; } diff --git a/public/tier1/bitbuf.h b/public/tier1/bitbuf.h index 620f49f9..15e7cce4 100644 --- a/public/tier1/bitbuf.h +++ b/public/tier1/bitbuf.h @@ -584,7 +584,8 @@ inline float bf_read::ReadBitFloat() if (bit != 0) val |= ((int)m_pData[byte + 4]) << (32 - bit); m_iCurBit += 32; - return *((float*)&val); + void *bits = &val; + return *reinterpret_cast(bits); } diff --git a/public/tier1/interface.h b/public/tier1/interface.h index 0a17b95e..d744602b 100644 --- a/public/tier1/interface.h +++ b/public/tier1/interface.h @@ -1,205 +1,207 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -// This header defines the interface convention used in the valve engine. -// To make an interface and expose it: -// 1. The interface must be ALL pure virtuals, and have no data members. -// 2. Define a name for it. -// 3. In its implementation file, use EXPOSE_INTERFACE or EXPOSE_SINGLE_INTERFACE. - -// Versioning -// There are two versioning cases that are handled by this: -// 1. You add functions to the end of an interface, so it is binary compatible with the previous interface. In this case, -// you need two EXPOSE_INTERFACEs: one to expose your class as the old interface and one to expose it as the new interface. -// 2. You update an interface so it's not compatible anymore (but you still want to be able to expose the old interface -// for legacy code). In this case, you need to make a new version name for your new interface, and make a wrapper interface and -// expose it for the old interface. - -// Static Linking: -// Must mimic unique seperate class 'InterfaceReg' constructors per subsystem. -// Each subsystem can then import and export interfaces as expected. -// This is achieved through unique namespacing 'InterfaceReg' via symbol _SUBSYSTEM. -// Static Linking also needs to generate unique symbols per interface so as to -// provide a 'stitching' method whereby these interface symbols can be referenced -// via the lib's primary module (usually the lib's interface exposure) -// therby stitching all of that lib's code/data together for eventual final exe link inclusion. - -#ifndef INTERFACE_H -#define INTERFACE_H - -#ifdef _WIN32 -#pragma once -#endif - -#ifdef _LINUX -#include // dlopen,dlclose, et al -#include - -#define HMODULE void * -#define GetProcAddress dlsym - -#define _snprintf snprintf -#endif - -// TODO: move interface.cpp into tier0 library. -#include "tier0/platform.h" - -// All interfaces derive from this. -class IBaseInterface -{ -public: - virtual ~IBaseInterface() {} -}; - - -#define CREATEINTERFACE_PROCNAME "CreateInterface" -typedef void* (*CreateInterfaceFn)(const char *pName, int *pReturnCode); -typedef void* (*InstantiateInterfaceFn)(); - - - -// Used internally to register classes. -class InterfaceReg -{ -public: - InterfaceReg(InstantiateInterfaceFn fn, const char *pName); - -public: - InstantiateInterfaceFn m_CreateFn; - const char *m_pName; - - InterfaceReg *m_pNext; // For the global list. - static InterfaceReg *s_pInterfaceRegs; -}; - -#if defined(_STATIC_LINKED) && defined(_SUBSYSTEM) - -#endif - -// Use this to expose an interface that can have multiple instances. -// e.g.: -// EXPOSE_INTERFACE( CInterfaceImp, IInterface, "MyInterface001" ) -// This will expose a class called CInterfaceImp that implements IInterface (a pure class) -// clients can receive a pointer to this class by calling CreateInterface( "MyInterface001" ) -// -// In practice, the shared header file defines the interface (IInterface) and version name ("MyInterface001") -// so that each component can use these names/vtables to communicate -// -// A single class can support multiple interfaces through multiple inheritance -// -// Use this if you want to write the factory function. -#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) -#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ - static InterfaceReg __g_Create##interfaceName##_reg(functionName, versionName); -#else -#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ - namespace _SUBSYSTEM \ - { \ - static InterfaceReg __g_Create##interfaceName##_reg(functionName, versionName); \ - } -#endif - -#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) -#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ - static void* __Create##className##_interface() {return (interfaceName *)new className;} \ - static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName ); -#else -#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ - namespace _SUBSYSTEM \ - { \ - static void* __Create##className##_interface() {return (interfaceName *)new className;} \ - static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName ); \ - } -#endif - -// Use this to expose a singleton interface with a global variable you've created. -#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) -#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \ - static void* __Create##className##interfaceName##_interface() {return (interfaceName *)&globalVarName;} \ - static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName); -#else -#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \ - namespace _SUBSYSTEM \ - { \ - static void* __Create##className##interfaceName##_interface() {return (interfaceName *)&globalVarName;} \ - static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName); \ - } -#endif - -// Use this to expose a singleton interface. This creates the global variable for you automatically. -#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) -#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ - static className __g_##className##_singleton; \ - EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) -#else -#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ - namespace _SUBSYSTEM \ - { \ - static className __g_##className##_singleton; \ - } \ - EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) -#endif - -// This function is automatically exported and allows you to access any interfaces exposed with the above macros. -// if pReturnCode is set, it will return one of the following values -// extend this for other error conditions/code - -// load/unload components -class CSysModule; - -// interface return status -enum -{ - IFACE_OK = 0, - IFACE_FAILED -}; - -#if defined(_STATIC_LINKED) && defined(_SUBSYSTEM) - -#endif - -//----------------------------------------------------------------------------- -// This function is automatically exported and allows you to access any interfaces exposed with the above macros. -// if pReturnCode is set, it will return one of the following values (IFACE_OK, IFACE_FAILED) -// extend this for other error conditions/code -//----------------------------------------------------------------------------- -DLL_EXPORT void* CreateInterface(const char *pName, int *pReturnCode); - -extern CreateInterfaceFn Sys_GetFactoryThis( void ); - - -//----------------------------------------------------------------------------- -// UNDONE: This is obsolete, use the module load/unload/get instead!!! -//----------------------------------------------------------------------------- -extern CreateInterfaceFn Sys_GetFactory( const char *pModuleName ); - - -//----------------------------------------------------------------------------- -// Load & Unload should be called in exactly one place for each module -// The factory for that module should be passed on to dependent components for -// proper versioning. -//----------------------------------------------------------------------------- -extern CSysModule *Sys_LoadModule( const char *pModuleName ); -extern void Sys_UnloadModule( CSysModule *pModule ); - -extern CreateInterfaceFn Sys_GetFactory( CSysModule *pModule ); - -// This is a helper function to load a module, get its factory, and get a specific interface. -// You are expected to free all of these things. -// Returns false and cleans up if any of the steps fail. -bool Sys_LoadInterface( - const char *pModuleName, - const char *pInterfaceVersionName, - CSysModule **pOutModule, - void **pOutInterface ); - - -#endif - - - +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +// This header defines the interface convention used in the valve engine. +// To make an interface and expose it: +// 1. The interface must be ALL pure virtuals, and have no data members. +// 2. Define a name for it. +// 3. In its implementation file, use EXPOSE_INTERFACE or EXPOSE_SINGLE_INTERFACE. + +// Versioning +// There are two versioning cases that are handled by this: +// 1. You add functions to the end of an interface, so it is binary compatible with the previous interface. In this case, +// you need two EXPOSE_INTERFACEs: one to expose your class as the old interface and one to expose it as the new interface. +// 2. You update an interface so it's not compatible anymore (but you still want to be able to expose the old interface +// for legacy code). In this case, you need to make a new version name for your new interface, and make a wrapper interface and +// expose it for the old interface. + +// Static Linking: +// Must mimic unique seperate class 'InterfaceReg' constructors per subsystem. +// Each subsystem can then import and export interfaces as expected. +// This is achieved through unique namespacing 'InterfaceReg' via symbol _SUBSYSTEM. +// Static Linking also needs to generate unique symbols per interface so as to +// provide a 'stitching' method whereby these interface symbols can be referenced +// via the lib's primary module (usually the lib's interface exposure) +// therby stitching all of that lib's code/data together for eventual final exe link inclusion. + +#ifndef INTERFACE_H +#define INTERFACE_H + +#ifdef _WIN32 +#pragma once +#endif + +#ifdef _LINUX +#include // dlopen,dlclose, et al +#include + +#define HMODULE void * +#define GetProcAddress dlsym + +#ifndef _snprintf +#define _snprintf snprintf +#endif +#endif + +// TODO: move interface.cpp into tier0 library. +#include "tier0/platform.h" + +// All interfaces derive from this. +class IBaseInterface +{ +public: + virtual ~IBaseInterface() {} +}; + + +#define CREATEINTERFACE_PROCNAME "CreateInterface" +typedef void* (*CreateInterfaceFn)(const char *pName, int *pReturnCode); +typedef void* (*InstantiateInterfaceFn)(); + + + +// Used internally to register classes. +class InterfaceReg +{ +public: + InterfaceReg(InstantiateInterfaceFn fn, const char *pName); + +public: + InstantiateInterfaceFn m_CreateFn; + const char *m_pName; + + InterfaceReg *m_pNext; // For the global list. + static InterfaceReg *s_pInterfaceRegs; +}; + +#if defined(_STATIC_LINKED) && defined(_SUBSYSTEM) + +#endif + +// Use this to expose an interface that can have multiple instances. +// e.g.: +// EXPOSE_INTERFACE( CInterfaceImp, IInterface, "MyInterface001" ) +// This will expose a class called CInterfaceImp that implements IInterface (a pure class) +// clients can receive a pointer to this class by calling CreateInterface( "MyInterface001" ) +// +// In practice, the shared header file defines the interface (IInterface) and version name ("MyInterface001") +// so that each component can use these names/vtables to communicate +// +// A single class can support multiple interfaces through multiple inheritance +// +// Use this if you want to write the factory function. +#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) +#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ + static InterfaceReg __g_Create##interfaceName##_reg(functionName, versionName); +#else +#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ + namespace _SUBSYSTEM \ + { \ + static InterfaceReg __g_Create##interfaceName##_reg(functionName, versionName); \ + } +#endif + +#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) +#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ + static void* __Create##className##_interface() {return (interfaceName *)new className;} \ + static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName ); +#else +#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ + namespace _SUBSYSTEM \ + { \ + static void* __Create##className##_interface() {return new className;} \ + static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName ); \ + } +#endif + +// Use this to expose a singleton interface with a global variable you've created. +#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) +#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \ + static void* __Create##className##interfaceName##_interface() {return &globalVarName;} \ + static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName); +#else +#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \ + namespace _SUBSYSTEM \ + { \ + static void* __Create##className##interfaceName##_interface() {return &globalVarName;} \ + static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName); \ + } +#endif + +// Use this to expose a singleton interface. This creates the global variable for you automatically. +#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) +#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ + static className __g_##className##_singleton; \ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) +#else +#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ + namespace _SUBSYSTEM \ + { \ + static className __g_##className##_singleton; \ + } \ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) +#endif + +// This function is automatically exported and allows you to access any interfaces exposed with the above macros. +// if pReturnCode is set, it will return one of the following values +// extend this for other error conditions/code + +// load/unload components +class CSysModule; + +// interface return status +enum +{ + IFACE_OK = 0, + IFACE_FAILED +}; + +#if defined(_STATIC_LINKED) && defined(_SUBSYSTEM) + +#endif + +//----------------------------------------------------------------------------- +// This function is automatically exported and allows you to access any interfaces exposed with the above macros. +// if pReturnCode is set, it will return one of the following values (IFACE_OK, IFACE_FAILED) +// extend this for other error conditions/code +//----------------------------------------------------------------------------- +DLL_EXPORT void* CreateInterface(const char *pName, int *pReturnCode); + +extern CreateInterfaceFn Sys_GetFactoryThis( void ); + + +//----------------------------------------------------------------------------- +// UNDONE: This is obsolete, use the module load/unload/get instead!!! +//----------------------------------------------------------------------------- +extern CreateInterfaceFn Sys_GetFactory( const char *pModuleName ); + + +//----------------------------------------------------------------------------- +// Load & Unload should be called in exactly one place for each module +// The factory for that module should be passed on to dependent components for +// proper versioning. +//----------------------------------------------------------------------------- +extern CSysModule *Sys_LoadModule( const char *pModuleName ); +extern void Sys_UnloadModule( CSysModule *pModule ); + +extern CreateInterfaceFn Sys_GetFactory( CSysModule *pModule ); + +// This is a helper function to load a module, get its factory, and get a specific interface. +// You are expected to free all of these things. +// Returns false and cleans up if any of the steps fail. +bool Sys_LoadInterface( + const char *pModuleName, + const char *pInterfaceVersionName, + CSysModule **pOutModule, + void **pOutInterface ); + + +#endif + + + diff --git a/public/tier1/jobthread.h b/public/tier1/jobthread.h index 43441f62..926da978 100644 --- a/public/tier1/jobthread.h +++ b/public/tier1/jobthread.h @@ -196,9 +196,9 @@ abstract_class CAsyncJob : public CRefCounted1 public: CAsyncJob( AsyncJobPriority_t priority = AJP_NORMAL ) : m_status( ASYNC_STATUS_UNSERVICED ), + m_priority( priority ), m_queueID( -1 ), - m_pFulfiller( NULL ), - m_priority( priority ) + m_pFulfiller( NULL ) { } diff --git a/public/tier1/stringpool.h b/public/tier1/stringpool.h index 37b9edd8..9362f18d 100644 --- a/public/tier1/stringpool.h +++ b/public/tier1/stringpool.h @@ -32,7 +32,7 @@ public: void FreeAll(); // searches for a string already in the pool - const char * CStringPool::Find( const char *pszValue ); + const char * Find( const char *pszValue ); protected: typedef CUtlRBTree CStrSet; diff --git a/public/tier1/tier1.h b/public/tier1/tier1.h index ca0921c3..aeb545ef 100644 --- a/public/tier1/tier1.h +++ b/public/tier1/tier1.h @@ -63,7 +63,7 @@ public: if ( !BaseClass::Connect( factory ) ) return false; - if ( IsPrimaryAppSystem() ) + if ( this->IsPrimaryAppSystem() ) { ConnectTier1Libraries( &factory, 1 ); if ( ConVarFlag && !g_pCVar ) @@ -77,7 +77,7 @@ public: virtual void Disconnect() { - if ( IsPrimaryAppSystem() ) + if ( this->IsPrimaryAppSystem() ) { DisconnectTier1Libraries(); } @@ -90,7 +90,7 @@ public: if ( nRetVal != INIT_OK ) return nRetVal; - if ( g_pCVar && ConVarFlag && IsPrimaryAppSystem() ) + if ( g_pCVar && ConVarFlag && this->IsPrimaryAppSystem() ) { ConCommandBaseMgr::OneTimeInit( this ); } @@ -99,7 +99,7 @@ public: virtual void Shutdown() { - if ( g_pCVar && ConVarFlag && IsPrimaryAppSystem() ) + if ( g_pCVar && ConVarFlag && this->IsPrimaryAppSystem() ) { g_pCVar->UnlinkVariables( ConVarFlag ); } diff --git a/public/tier1/tokenreader.h b/public/tier1/tokenreader.h index 86328e76..67c2d55a 100644 --- a/public/tier1/tokenreader.h +++ b/public/tier1/tokenreader.h @@ -66,18 +66,20 @@ public: inline int GetErrorCount(void); +#ifndef _LINUX inline TokenReader(TokenReader const &) { // prevent vc7 warning. compiler can't generate a copy constructor since descended from // std::ifstream assert(0); } - inline TokenReader& operator=(TokenReader const &) + inline TokenReader operator=(TokenReader const &) { // prevent vc7 warning. compiler can't generate an assignment operator since descended from // std::ifstream assert(0); } +#endif private: trtoken_t GetString(char *pszStore, int nSize); diff --git a/public/tier1/utlfixedlinkedlist.h b/public/tier1/utlfixedlinkedlist.h index c4255cb0..41992a90 100644 --- a/public/tier1/utlfixedlinkedlist.h +++ b/public/tier1/utlfixedlinkedlist.h @@ -506,7 +506,7 @@ void CUtlFixedLinkedList::RemoveAll() // This doesn't actually deallocate the memory... purge does that m_Memory.RemoveAll(); m_TotalElements = 0; - m_FirstFree = NULL; + m_FirstFree = 0; // Clear everything else out m_Head = InvalidIndex(); diff --git a/public/tier1/utlfixedmemory.h b/public/tier1/utlfixedmemory.h index 8a4e05e3..fc8fa8b1 100644 --- a/public/tier1/utlfixedmemory.h +++ b/public/tier1/utlfixedmemory.h @@ -19,14 +19,15 @@ #include "tier0/dbg.h" #include -#include +#include #include "tier0/platform.h" #include "tier0/memdbgon.h" +#ifdef _MSC_VER #pragma warning (disable:4100) #pragma warning (disable:4514) - +#endif //----------------------------------------------------------------------------- // The CUtlFixedMemory class: @@ -92,9 +93,9 @@ private: private: BlockHeader_t *m_pMemory; + T *m_pFirstFree; int m_nAllocationCount; int m_nGrowSize; - T *m_pFirstFree; }; diff --git a/public/tier1/utllinkedlist.h b/public/tier1/utllinkedlist.h index 1dda7f3f..236f13a2 100644 --- a/public/tier1/utllinkedlist.h +++ b/public/tier1/utllinkedlist.h @@ -1,909 +1,909 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Linked list container class -// -// $Revision: $ -// $NoKeywords: $ -//=============================================================================// - -#ifndef UTLLINKEDLIST_H -#define UTLLINKEDLIST_H - -#ifdef _WIN32 -#pragma once -#endif - -#include "basetypes.h" -#include "utlmemory.h" -#include "tier0/dbg.h" - - -// This is a useful macro to iterate from head to tail in a linked list. -#define FOR_EACH_LL( listName, iteratorName ) \ - for( int iteratorName=listName.Head(); iteratorName != listName.InvalidIndex(); iteratorName = listName.Next( iteratorName ) ) - -#define INVALID_LLIST_IDX ((I)~0) - -//----------------------------------------------------------------------------- -// class CUtlLinkedList: -// description: -// A lovely index-based linked list! T is the class type, I is the index -// type, which usually should be an unsigned short or smaller. -//----------------------------------------------------------------------------- - -template -class CUtlLinkedList -{ -public: - typedef T ElemType_t; - typedef I IndexType_t; - - // constructor, destructor - CUtlLinkedList( int growSize = 0, int initSize = 0 ); - CUtlLinkedList( void *pMemory, int memsize ); - ~CUtlLinkedList( ); - - // gets particular elements - T& Element( I i ); - T const& Element( I i ) const; - T& operator[]( I i ); - T const& operator[]( I i ) const; - - // Make sure we have a particular amount of memory - void EnsureCapacity( int num ); - - // Memory deallocation - void Purge(); - - // Delete all the elements then call Purge. - void PurgeAndDeleteElements(); - - // Insertion methods.... - I InsertBefore( I before ); - I InsertAfter( I after ); - I AddToHead( ); - I AddToTail( ); - - I InsertBefore( I before, T const& src ); - I InsertAfter( I after, T const& src ); - I AddToHead( T const& src ); - I AddToTail( T const& src ); - - // Find an element and return its index or InvalidIndex() if it couldn't be found. - I Find( const T &src ) const; - - // Look for the element. If it exists, remove it and return true. Otherwise, return false. - bool FindAndRemove( const T &src ); - - // Removal methods - void Remove( I elem ); - void RemoveAll(); - - // Allocation/deallocation methods - // If multilist == true, then list list may contain many - // non-connected lists, and IsInList and Head + Tail are meaningless... - I Alloc( bool multilist = false ); - void Free( I elem ); - - // list modification - void LinkBefore( I before, I elem ); - void LinkAfter( I after, I elem ); - void Unlink( I elem ); - void LinkToHead( I elem ); - void LinkToTail( I elem ); - - // invalid index - inline static I InvalidIndex() { return INVALID_LLIST_IDX; } - inline static size_t ElementSize() { return sizeof(ListElem_t); } - - // list statistics - int Count() const; - I MaxElementIndex() const; - - // Traversing the list - I Head() const; - I Tail() const; - I Previous( I i ) const; - I Next( I i ) const; - - // Are nodes in the list or valid? - bool IsValidIndex( I i ) const; - bool IsInList( I i ) const; - -protected: - // What the linked list element looks like - struct ListElem_t - { - T m_Element; - I m_Previous; - I m_Next; - - private: - // No copy constructor for these... - ListElem_t( const ListElem_t& ); - }; - - // constructs the class - I AllocInternal( bool multilist = false ); - void ConstructList(); - - // Gets at the list element.... - ListElem_t& InternalElement( I i ) { return m_Memory[i]; } - ListElem_t const& InternalElement( I i ) const { return m_Memory[i]; } - - // copy constructors not allowed - CUtlLinkedList( CUtlLinkedList const& list ) { Assert(0); } - - CUtlMemory m_Memory; - I m_Head; - I m_Tail; - I m_FirstFree; - I m_ElementCount; // The number actually in the list - I m_TotalElements; // The number allocated - -#if !defined(_XBOX) || defined(_DEBUG) - // For debugging purposes; - // it's in release builds so this can be used in libraries correctly - ListElem_t *m_pElements; - - void ResetDbgInfo() - { - m_pElements = m_Memory.Base(); - } - -#else - void ResetDbgInfo() {} -#endif -}; - - -//----------------------------------------------------------------------------- -// constructor, destructor -//----------------------------------------------------------------------------- - -template -CUtlLinkedList::CUtlLinkedList( int growSize, int initSize ) : - m_Memory(growSize, initSize) -{ - ConstructList(); - ResetDbgInfo(); -} - -template -CUtlLinkedList::CUtlLinkedList( void* pMemory, int memsize ) : - m_Memory((ListElem_t *)pMemory, memsize/sizeof(ListElem_t)) -{ - ConstructList(); - ResetDbgInfo(); -} - -template -CUtlLinkedList::~CUtlLinkedList( ) -{ - RemoveAll(); -} - -template -void CUtlLinkedList::ConstructList() -{ - m_Head = InvalidIndex(); - m_Tail = InvalidIndex(); - m_FirstFree = InvalidIndex(); - m_ElementCount = m_TotalElements = 0; -} - - -//----------------------------------------------------------------------------- -// gets particular elements -//----------------------------------------------------------------------------- - -template -inline T& CUtlLinkedList::Element( I i ) -{ - return m_Memory[i].m_Element; -} - -template -inline T const& CUtlLinkedList::Element( I i ) const -{ - return m_Memory[i].m_Element; -} - -template -inline T& CUtlLinkedList::operator[]( I i ) -{ - return m_Memory[i].m_Element; -} - -template -inline T const& CUtlLinkedList::operator[]( I i ) const -{ - return m_Memory[i].m_Element; -} - -//----------------------------------------------------------------------------- -// list statistics -//----------------------------------------------------------------------------- - -template -inline int CUtlLinkedList::Count() const -{ - return m_ElementCount; -} - -template -inline I CUtlLinkedList::MaxElementIndex() const -{ - return m_Memory.NumAllocated(); -} - - -//----------------------------------------------------------------------------- -// Traversing the list -//----------------------------------------------------------------------------- - -template -inline I CUtlLinkedList::Head() const -{ - return m_Head; -} - -template -inline I CUtlLinkedList::Tail() const -{ - return m_Tail; -} - -template -inline I CUtlLinkedList::Previous( I i ) const -{ - Assert( IsValidIndex(i) ); - return InternalElement(i).m_Previous; -} - -template -inline I CUtlLinkedList::Next( I i ) const -{ - Assert( IsValidIndex(i) ); - return InternalElement(i).m_Next; -} - - -//----------------------------------------------------------------------------- -// Are nodes in the list or valid? -//----------------------------------------------------------------------------- - -template -inline bool CUtlLinkedList::IsValidIndex( I i ) const -{ - return (i < m_TotalElements) && (i >= 0) && - ((m_Memory[i].m_Previous != i) || (m_Memory[i].m_Next == i)); -} - -template -inline bool CUtlLinkedList::IsInList( I i ) const -{ - return (i < m_TotalElements) && (i >= 0) && (Previous(i) != i); -} - -//----------------------------------------------------------------------------- -// Makes sure we have enough memory allocated to store a requested # of elements -//----------------------------------------------------------------------------- - -template< class T, class I > -void CUtlLinkedList::EnsureCapacity( int num ) -{ - MEM_ALLOC_CREDIT_CLASS(); - m_Memory.EnsureCapacity(num); - ResetDbgInfo(); -} - - -//----------------------------------------------------------------------------- -// Deallocate memory -//----------------------------------------------------------------------------- - -template -void CUtlLinkedList::Purge() -{ - // Prevent reentrant calls to Purge() - if( m_ElementCount ) - { - RemoveAll(); - } - - m_Memory.Purge( ); - m_FirstFree = InvalidIndex(); - m_TotalElements = 0; - ResetDbgInfo(); - -} - - -template -void CUtlLinkedList::PurgeAndDeleteElements() -{ - int iNext; - for( int i=Head(); i != InvalidIndex(); i=iNext ) - { - iNext = Next(i); - delete Element(i); - } - - Purge(); -} - - -//----------------------------------------------------------------------------- -// Node allocation/deallocation -//----------------------------------------------------------------------------- -template -I CUtlLinkedList::AllocInternal( bool multilist ) -{ - I elem; - if (m_FirstFree == InvalidIndex()) - { - // Nothing in the free list; add. - // Since nothing is in the free list, m_TotalElements == total # of elements - // the list knows about. - if ((int)m_TotalElements == m_Memory.NumAllocated()) - { - MEM_ALLOC_CREDIT_CLASS(); - m_Memory.Grow(); - } - - Assert( m_TotalElements != InvalidIndex() ); - - elem = (I)m_TotalElements; - ++m_TotalElements; - - if ( elem == InvalidIndex() ) - { - Error("CUtlLinkedList overflow!\n"); - } - } - else - { - elem = m_FirstFree; - m_FirstFree = InternalElement(m_FirstFree).m_Next; - } - - if (!multilist) - InternalElement(elem).m_Next = InternalElement(elem).m_Previous = elem; - else - InternalElement(elem).m_Next = InternalElement(elem).m_Previous = InvalidIndex(); - - ResetDbgInfo(); - - return elem; -} - -template -I CUtlLinkedList::Alloc( bool multilist ) -{ - I elem = AllocInternal( multilist ); - Construct( &Element(elem) ); - - return elem; -} - -template -void CUtlLinkedList::Free( I elem ) -{ - Assert( IsValidIndex(elem) ); - Unlink(elem); - - ListElem_t &internalElem = InternalElement(elem); - Destruct( &internalElem.m_Element ); - internalElem.m_Next = m_FirstFree; - m_FirstFree = elem; -} - -//----------------------------------------------------------------------------- -// Insertion methods; allocates and links (uses default constructor) -//----------------------------------------------------------------------------- - -template -I CUtlLinkedList::InsertBefore( I before ) -{ - // Make a new node - I newNode = AllocInternal(); - - // Link it in - LinkBefore( before, newNode ); - - // Construct the data - Construct( &Element(newNode) ); - - return newNode; -} - -template -I CUtlLinkedList::InsertAfter( I after ) -{ - // Make a new node - I newNode = AllocInternal(); - - // Link it in - LinkAfter( after, newNode ); - - // Construct the data - Construct( &Element(newNode) ); - - return newNode; -} - -template -inline I CUtlLinkedList::AddToHead( ) -{ - return InsertAfter( InvalidIndex() ); -} - -template -inline I CUtlLinkedList::AddToTail( ) -{ - return InsertBefore( InvalidIndex() ); -} - - -//----------------------------------------------------------------------------- -// Insertion methods; allocates and links (uses copy constructor) -//----------------------------------------------------------------------------- - -template -I CUtlLinkedList::InsertBefore( I before, T const& src ) -{ - // Make a new node - I newNode = AllocInternal(); - - // Link it in - LinkBefore( before, newNode ); - - // Construct the data - CopyConstruct( &Element(newNode), src ); - - return newNode; -} - -template -I CUtlLinkedList::InsertAfter( I after, T const& src ) -{ - // Make a new node - I newNode = AllocInternal(); - - // Link it in - LinkAfter( after, newNode ); - - // Construct the data - CopyConstruct( &Element(newNode), src ); - - return newNode; -} - -template -inline I CUtlLinkedList::AddToHead( T const& src ) -{ - return InsertAfter( InvalidIndex(), src ); -} - -template -inline I CUtlLinkedList::AddToTail( T const& src ) -{ - return InsertBefore( InvalidIndex(), src ); -} - - -//----------------------------------------------------------------------------- -// Removal methods -//----------------------------------------------------------------------------- - -template -I CUtlLinkedList::Find( const T &src ) const -{ - for ( I i=Head(); i != InvalidIndex(); i = Next( i ) ) - { - if ( Element( i ) == src ) - return i; - } - return InvalidIndex(); -} - - -template -bool CUtlLinkedList::FindAndRemove( const T &src ) -{ - I i = Find( src ); - if ( i == InvalidIndex() ) - { - return false; - } - else - { - Remove( i ); - return true; - } -} - - -template -void CUtlLinkedList::Remove( I elem ) -{ - Free( elem ); -} - -template -void CUtlLinkedList::RemoveAll() -{ - if (m_TotalElements == 0) - return; - - // Put everything into the free list - I prev = InvalidIndex(); - for (int i = (int)m_TotalElements; --i >= 0; ) - { - // Invoke the destructor - if (IsValidIndex((I)i)) - Destruct( &Element((I)i) ); - - // next points to the next free list item - InternalElement((I)i).m_Next = prev; - - // Indicates it's in the free list - InternalElement((I)i).m_Previous = (I)i; - prev = (I)i; - } - - // First free points to the first element - m_FirstFree = 0; - - // Clear everything else out - m_Head = InvalidIndex(); - m_Tail = InvalidIndex(); - m_ElementCount = 0; - -#ifdef _XBOX - //Purge(); -#endif -} - - -//----------------------------------------------------------------------------- -// list modification -//----------------------------------------------------------------------------- - -template -void CUtlLinkedList::LinkBefore( I before, I elem ) -{ - Assert( IsValidIndex(elem) ); - - // Unlink it if it's in the list at the moment - Unlink(elem); - - ListElem_t& newElem = InternalElement(elem); - - // The element *after* our newly linked one is the one we linked before. - newElem.m_Next = before; - - if (before == InvalidIndex()) - { - // In this case, we're linking to the end of the list, so reset the tail - newElem.m_Previous = m_Tail; - m_Tail = elem; - } - else - { - // Here, we're not linking to the end. Set the prev pointer to point to - // the element we're linking. - Assert( IsInList(before) ); - ListElem_t& beforeElem = InternalElement(before); - newElem.m_Previous = beforeElem.m_Previous; - beforeElem.m_Previous = elem; - } - - // Reset the head if we linked to the head of the list - if (newElem.m_Previous == InvalidIndex()) - m_Head = elem; - else - InternalElement(newElem.m_Previous).m_Next = elem; - - // one more element baby - ++m_ElementCount; -} - -template -void CUtlLinkedList::LinkAfter( I after, I elem ) -{ - Assert( IsValidIndex(elem) ); - - // Unlink it if it's in the list at the moment - if ( IsInList(elem) ) - Unlink(elem); - - ListElem_t& newElem = InternalElement(elem); - - // The element *before* our newly linked one is the one we linked after - newElem.m_Previous = after; - if (after == InvalidIndex()) - { - // In this case, we're linking to the head of the list, reset the head - newElem.m_Next = m_Head; - m_Head = elem; - } - else - { - // Here, we're not linking to the end. Set the next pointer to point to - // the element we're linking. - Assert( IsInList(after) ); - ListElem_t& afterElem = InternalElement(after); - newElem.m_Next = afterElem.m_Next; - afterElem.m_Next = elem; - } - - // Reset the tail if we linked to the tail of the list - if (newElem.m_Next == InvalidIndex()) - m_Tail = elem; - else - InternalElement(newElem.m_Next).m_Previous = elem; - - // one more element baby - ++m_ElementCount; -} - -template -void CUtlLinkedList::Unlink( I elem ) -{ - Assert( IsValidIndex(elem) ); - if (IsInList(elem)) - { - ListElem_t *pBase = m_Memory.Base(); - ListElem_t *pOldElem = &pBase[elem]; - - // If we're the first guy, reset the head - // otherwise, make our previous node's next pointer = our next - if ( pOldElem->m_Previous != INVALID_LLIST_IDX ) - { - pBase[pOldElem->m_Previous].m_Next = pOldElem->m_Next; - } - else - { - m_Head = pOldElem->m_Next; - } - - // If we're the last guy, reset the tail - // otherwise, make our next node's prev pointer = our prev - if ( pOldElem->m_Next != INVALID_LLIST_IDX ) - { - pBase[pOldElem->m_Next].m_Previous = pOldElem->m_Previous; - } - else - { - m_Tail = pOldElem->m_Previous; - } - - // This marks this node as not in the list, - // but not in the free list either - pOldElem->m_Previous = pOldElem->m_Next = elem; - - // One less puppy - --m_ElementCount; - } -} - -template -inline void CUtlLinkedList::LinkToHead( I elem ) -{ - LinkAfter( InvalidIndex(), elem ); -} - -template -inline void CUtlLinkedList::LinkToTail( I elem ) -{ - LinkBefore( InvalidIndex(), elem ); -} - - -//----------------------------------------------------------------------------- -// Class to drop in to replace a CUtlLinkedList that needs to be more memory agressive -//----------------------------------------------------------------------------- - -DECLARE_POINTER_HANDLE( UtlPtrLinkedListIndex_t ); // to enforce correct usage - -template < typename T > -class CUtlPtrLinkedList -{ -public: - CUtlPtrLinkedList() - : m_pFirst( NULL ), - m_nElems( 0 ) - { - COMPILE_TIME_ASSERT( sizeof(IndexType_t) == sizeof(Node_t *) ); - } - - ~CUtlPtrLinkedList() - { - RemoveAll(); - } - - typedef UtlPtrLinkedListIndex_t IndexType_t; - - T &operator[]( IndexType_t i ) - { - return (( Node_t * )i)->elem; - } - - const T &operator[]( IndexType_t i ) const - { - return (( Node_t * )i)->elem; - } - - IndexType_t AddToTail() - { - return DoInsertBefore( (IndexType_t)m_pFirst, NULL ); - } - - IndexType_t AddToTail( T const& src ) - { - return DoInsertBefore( (IndexType_t)m_pFirst, &src ); - } - - IndexType_t AddToHead() - { - IndexType_t result = DoInsertBefore( (IndexType_t)m_pFirst, NULL ); - m_pFirst = ((Node_t *)result); - return result; - } - - IndexType_t AddToHead( T const& src ) - { - IndexType_t result = DoInsertBefore( (IndexType_t)m_pFirst, &src ); - m_pFirst = ((Node_t *)result); - return result; - } - - IndexType_t InsertBefore( IndexType_t before ) - { - return DoInsertBefore( before, NULL ); - } - - IndexType_t InsertAfter( IndexType_t after ) - { - Node_t *pBefore = ((Node_t *)after)->next; - return DoInsertBefore( pBefore, NULL ); - } - - IndexType_t InsertBefore( IndexType_t before, T const& src ) - { - return DoInsertBefore( before, &src ); - } - - IndexType_t InsertAfter( IndexType_t after, T const& src ) - { - Node_t *pBefore = ((Node_t *)after)->next; - return DoInsertBefore( pBefore, &src ); - } - - void Remove( IndexType_t elem ) - { - Node_t *p = (Node_t *)elem; - - if ( p->pNext == p ) - { - m_pFirst = NULL; - } - else - { - if ( m_pFirst == p ) - { - m_pFirst = p->pNext; - } - p->pNext->pPrev = p->pPrev; - p->pPrev->pNext = p->pNext; - } - - delete p; - m_nElems--; - } - - void RemoveAll() - { - Node_t *p = m_pFirst; - if ( p ) - { - do - { - Node_t *pNext = p->pNext; - delete p; - p = pNext; - } while( p != m_pFirst ); - } - - m_pFirst = NULL; - m_nElems = 0; - } - - int Count() const - { - return m_nElems; - } - - IndexType_t Head() const - { - return (IndexType_t)m_pFirst; - } - - IndexType_t Next( IndexType_t i ) const - { - Node_t *p = ((Node_t *)i)->pNext; - if ( p != m_pFirst ) - { - return (IndexType_t)p; - } - return NULL; - } - - bool IsValidIndex( IndexType_t i ) const - { - Node_t *p = ((Node_t *)i); - return ( p && p->pNext && p->pPrev ); - } - - inline static IndexType_t InvalidIndex() - { - return NULL; - } -private: - - struct Node_t - { - Node_t() {} - Node_t( const T &elem ) : elem( elem ) {} - - T elem; - Node_t *pPrev, *pNext; - }; - - Node_t *AllocNode( const T *pCopyFrom ) - { - MEM_ALLOC_CREDIT_CLASS(); - Node_t *p; - - if ( !pCopyFrom ) - { - p = new Node_t; - } - else - { - p = new Node_t( *pCopyFrom ); - } - - return p; - } - - IndexType_t DoInsertBefore( IndexType_t before, const T *pCopyFrom ) - { - Node_t *p = AllocNode( pCopyFrom ); - Node_t *pBefore = (Node_t *)before; - if ( pBefore ) - { - p->pNext = pBefore; - p->pPrev = pBefore->pPrev; - pBefore->pPrev = p; - p->pPrev->pNext = p; - } - else - { - Assert( !m_pFirst ); - m_pFirst = p->pNext = p->pPrev = p; - } - - m_nElems++; - return (IndexType_t)p; - } - - Node_t *m_pFirst; - unsigned m_nElems; -}; - -//----------------------------------------------------------------------------- - -#endif // UTLLINKEDLIST_H +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Linked list container class +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef UTLLINKEDLIST_H +#define UTLLINKEDLIST_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "basetypes.h" +#include "utlmemory.h" +#include "tier0/dbg.h" + + +// This is a useful macro to iterate from head to tail in a linked list. +#define FOR_EACH_LL( listName, iteratorName ) \ + for( int iteratorName=listName.Head(); iteratorName != listName.InvalidIndex(); iteratorName = listName.Next( iteratorName ) ) + +#define INVALID_LLIST_IDX ((I)~0) + +//----------------------------------------------------------------------------- +// class CUtlLinkedList: +// description: +// A lovely index-based linked list! T is the class type, I is the index +// type, which usually should be an unsigned short or smaller. +//----------------------------------------------------------------------------- + +template +class CUtlLinkedList +{ +public: + typedef T ElemType_t; + typedef I IndexType_t; + + // constructor, destructor + CUtlLinkedList( int growSize = 0, int initSize = 0 ); + CUtlLinkedList( void *pMemory, int memsize ); + ~CUtlLinkedList( ); + + // gets particular elements + T& Element( I i ); + T const& Element( I i ) const; + T& operator[]( I i ); + T const& operator[]( I i ) const; + + // Make sure we have a particular amount of memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // Delete all the elements then call Purge. + void PurgeAndDeleteElements(); + + // Insertion methods.... + I InsertBefore( I before ); + I InsertAfter( I after ); + I AddToHead( ); + I AddToTail( ); + + I InsertBefore( I before, T const& src ); + I InsertAfter( I after, T const& src ); + I AddToHead( T const& src ); + I AddToTail( T const& src ); + + // Find an element and return its index or InvalidIndex() if it couldn't be found. + I Find( const T &src ) const; + + // Look for the element. If it exists, remove it and return true. Otherwise, return false. + bool FindAndRemove( const T &src ); + + // Removal methods + void Remove( I elem ); + void RemoveAll(); + + // Allocation/deallocation methods + // If multilist == true, then list list may contain many + // non-connected lists, and IsInList and Head + Tail are meaningless... + I Alloc( bool multilist = false ); + void Free( I elem ); + + // list modification + void LinkBefore( I before, I elem ); + void LinkAfter( I after, I elem ); + void Unlink( I elem ); + void LinkToHead( I elem ); + void LinkToTail( I elem ); + + // invalid index + inline static I InvalidIndex() { return INVALID_LLIST_IDX; } + inline static size_t ElementSize() { return sizeof(ListElem_t); } + + // list statistics + int Count() const; + I MaxElementIndex() const; + + // Traversing the list + I Head() const; + I Tail() const; + I Previous( I i ) const; + I Next( I i ) const; + + // Are nodes in the list or valid? + bool IsValidIndex( I i ) const; + bool IsInList( I i ) const; + +protected: + // What the linked list element looks like + struct ListElem_t + { + T m_Element; + I m_Previous; + I m_Next; + + private: + // No copy constructor for these... + ListElem_t( const ListElem_t& ); + }; + + // constructs the class + I AllocInternal( bool multilist = false ); + void ConstructList(); + + // Gets at the list element.... + ListElem_t& InternalElement( I i ) { return m_Memory[i]; } + ListElem_t const& InternalElement( I i ) const { return m_Memory[i]; } + + // copy constructors not allowed + CUtlLinkedList( CUtlLinkedList const& list ) { Assert(0); } + + CUtlMemory m_Memory; + I m_Head; + I m_Tail; + I m_FirstFree; + I m_ElementCount; // The number actually in the list + I m_TotalElements; // The number allocated + +#if !defined(_XBOX) || defined(_DEBUG) + // For debugging purposes; + // it's in release builds so this can be used in libraries correctly + ListElem_t *m_pElements; + + void ResetDbgInfo() + { + m_pElements = m_Memory.Base(); + } + +#else + void ResetDbgInfo() {} +#endif +}; + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlLinkedList::CUtlLinkedList( int growSize, int initSize ) : + m_Memory(growSize, initSize) +{ + ConstructList(); + ResetDbgInfo(); +} + +template +CUtlLinkedList::CUtlLinkedList( void* pMemory, int memsize ) : + m_Memory((ListElem_t *)pMemory, memsize/sizeof(ListElem_t)) +{ + ConstructList(); + ResetDbgInfo(); +} + +template +CUtlLinkedList::~CUtlLinkedList( ) +{ + RemoveAll(); +} + +template +void CUtlLinkedList::ConstructList() +{ + m_Head = InvalidIndex(); + m_Tail = InvalidIndex(); + m_FirstFree = InvalidIndex(); + m_ElementCount = m_TotalElements = 0; +} + + +//----------------------------------------------------------------------------- +// gets particular elements +//----------------------------------------------------------------------------- + +template +inline T& CUtlLinkedList::Element( I i ) +{ + return m_Memory[i].m_Element; +} + +template +inline T const& CUtlLinkedList::Element( I i ) const +{ + return m_Memory[i].m_Element; +} + +template +inline T& CUtlLinkedList::operator[]( I i ) +{ + return m_Memory[i].m_Element; +} + +template +inline T const& CUtlLinkedList::operator[]( I i ) const +{ + return m_Memory[i].m_Element; +} + +//----------------------------------------------------------------------------- +// list statistics +//----------------------------------------------------------------------------- + +template +inline int CUtlLinkedList::Count() const +{ + return m_ElementCount; +} + +template +inline I CUtlLinkedList::MaxElementIndex() const +{ + return m_Memory.NumAllocated(); +} + + +//----------------------------------------------------------------------------- +// Traversing the list +//----------------------------------------------------------------------------- + +template +inline I CUtlLinkedList::Head() const +{ + return m_Head; +} + +template +inline I CUtlLinkedList::Tail() const +{ + return m_Tail; +} + +template +inline I CUtlLinkedList::Previous( I i ) const +{ + Assert( IsValidIndex(i) ); + return InternalElement(i).m_Previous; +} + +template +inline I CUtlLinkedList::Next( I i ) const +{ + Assert( IsValidIndex(i) ); + return InternalElement(i).m_Next; +} + + +//----------------------------------------------------------------------------- +// Are nodes in the list or valid? +//----------------------------------------------------------------------------- + +template +inline bool CUtlLinkedList::IsValidIndex( I i ) const +{ + return (i < m_TotalElements) && (i > 0) && + ((m_Memory[i].m_Previous != i) || (m_Memory[i].m_Next == i)); +} + +template +inline bool CUtlLinkedList::IsInList( I i ) const +{ + return (i < m_TotalElements) && (i > 0) && (Previous(i) != i); +} + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- + +template< class T, class I > +void CUtlLinkedList::EnsureCapacity( int num ) +{ + MEM_ALLOC_CREDIT_CLASS(); + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// Deallocate memory +//----------------------------------------------------------------------------- + +template +void CUtlLinkedList::Purge() +{ + // Prevent reentrant calls to Purge() + if( m_ElementCount ) + { + RemoveAll(); + } + + m_Memory.Purge( ); + m_FirstFree = InvalidIndex(); + m_TotalElements = 0; + ResetDbgInfo(); + +} + + +template +void CUtlLinkedList::PurgeAndDeleteElements() +{ + int iNext; + for( int i=Head(); i != InvalidIndex(); i=iNext ) + { + iNext = Next(i); + delete Element(i); + } + + Purge(); +} + + +//----------------------------------------------------------------------------- +// Node allocation/deallocation +//----------------------------------------------------------------------------- +template +I CUtlLinkedList::AllocInternal( bool multilist ) +{ + I elem; + if (m_FirstFree == InvalidIndex()) + { + // Nothing in the free list; add. + // Since nothing is in the free list, m_TotalElements == total # of elements + // the list knows about. + if ((int)m_TotalElements == m_Memory.NumAllocated()) + { + MEM_ALLOC_CREDIT_CLASS(); + m_Memory.Grow(); + } + + Assert( m_TotalElements != InvalidIndex() ); + + elem = (I)m_TotalElements; + ++m_TotalElements; + + if ( elem == InvalidIndex() ) + { + Error("CUtlLinkedList overflow!\n"); + } + } + else + { + elem = m_FirstFree; + m_FirstFree = InternalElement(m_FirstFree).m_Next; + } + + if (!multilist) + InternalElement(elem).m_Next = InternalElement(elem).m_Previous = elem; + else + InternalElement(elem).m_Next = InternalElement(elem).m_Previous = InvalidIndex(); + + ResetDbgInfo(); + + return elem; +} + +template +I CUtlLinkedList::Alloc( bool multilist ) +{ + I elem = AllocInternal( multilist ); + Construct( &Element(elem) ); + + return elem; +} + +template +void CUtlLinkedList::Free( I elem ) +{ + Assert( IsValidIndex(elem) ); + Unlink(elem); + + ListElem_t &internalElem = InternalElement(elem); + Destruct( &internalElem.m_Element ); + internalElem.m_Next = m_FirstFree; + m_FirstFree = elem; +} + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses default constructor) +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::InsertBefore( I before ) +{ + // Make a new node + I newNode = AllocInternal(); + + // Link it in + LinkBefore( before, newNode ); + + // Construct the data + Construct( &Element(newNode) ); + + return newNode; +} + +template +I CUtlLinkedList::InsertAfter( I after ) +{ + // Make a new node + I newNode = AllocInternal(); + + // Link it in + LinkAfter( after, newNode ); + + // Construct the data + Construct( &Element(newNode) ); + + return newNode; +} + +template +inline I CUtlLinkedList::AddToHead( ) +{ + return InsertAfter( InvalidIndex() ); +} + +template +inline I CUtlLinkedList::AddToTail( ) +{ + return InsertBefore( InvalidIndex() ); +} + + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses copy constructor) +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::InsertBefore( I before, T const& src ) +{ + // Make a new node + I newNode = AllocInternal(); + + // Link it in + LinkBefore( before, newNode ); + + // Construct the data + CopyConstruct( &Element(newNode), src ); + + return newNode; +} + +template +I CUtlLinkedList::InsertAfter( I after, T const& src ) +{ + // Make a new node + I newNode = AllocInternal(); + + // Link it in + LinkAfter( after, newNode ); + + // Construct the data + CopyConstruct( &Element(newNode), src ); + + return newNode; +} + +template +inline I CUtlLinkedList::AddToHead( T const& src ) +{ + return InsertAfter( InvalidIndex(), src ); +} + +template +inline I CUtlLinkedList::AddToTail( T const& src ) +{ + return InsertBefore( InvalidIndex(), src ); +} + + +//----------------------------------------------------------------------------- +// Removal methods +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::Find( const T &src ) const +{ + for ( I i=Head(); i != InvalidIndex(); i = Next( i ) ) + { + if ( Element( i ) == src ) + return i; + } + return InvalidIndex(); +} + + +template +bool CUtlLinkedList::FindAndRemove( const T &src ) +{ + I i = Find( src ); + if ( i == InvalidIndex() ) + { + return false; + } + else + { + Remove( i ); + return true; + } +} + + +template +void CUtlLinkedList::Remove( I elem ) +{ + Free( elem ); +} + +template +void CUtlLinkedList::RemoveAll() +{ + if (m_TotalElements == 0) + return; + + // Put everything into the free list + I prev = InvalidIndex(); + for (int i = (int)m_TotalElements; --i >= 0; ) + { + // Invoke the destructor + if (IsValidIndex((I)i)) + Destruct( &Element((I)i) ); + + // next points to the next free list item + InternalElement((I)i).m_Next = prev; + + // Indicates it's in the free list + InternalElement((I)i).m_Previous = (I)i; + prev = (I)i; + } + + // First free points to the first element + m_FirstFree = 0; + + // Clear everything else out + m_Head = InvalidIndex(); + m_Tail = InvalidIndex(); + m_ElementCount = 0; + +#ifdef _XBOX + //Purge(); +#endif +} + + +//----------------------------------------------------------------------------- +// list modification +//----------------------------------------------------------------------------- + +template +void CUtlLinkedList::LinkBefore( I before, I elem ) +{ + Assert( IsValidIndex(elem) ); + + // Unlink it if it's in the list at the moment + Unlink(elem); + + ListElem_t& newElem = InternalElement(elem); + + // The element *after* our newly linked one is the one we linked before. + newElem.m_Next = before; + + if (before == InvalidIndex()) + { + // In this case, we're linking to the end of the list, so reset the tail + newElem.m_Previous = m_Tail; + m_Tail = elem; + } + else + { + // Here, we're not linking to the end. Set the prev pointer to point to + // the element we're linking. + Assert( IsInList(before) ); + ListElem_t& beforeElem = InternalElement(before); + newElem.m_Previous = beforeElem.m_Previous; + beforeElem.m_Previous = elem; + } + + // Reset the head if we linked to the head of the list + if (newElem.m_Previous == InvalidIndex()) + m_Head = elem; + else + InternalElement(newElem.m_Previous).m_Next = elem; + + // one more element baby + ++m_ElementCount; +} + +template +void CUtlLinkedList::LinkAfter( I after, I elem ) +{ + Assert( IsValidIndex(elem) ); + + // Unlink it if it's in the list at the moment + if ( IsInList(elem) ) + Unlink(elem); + + ListElem_t& newElem = InternalElement(elem); + + // The element *before* our newly linked one is the one we linked after + newElem.m_Previous = after; + if (after == InvalidIndex()) + { + // In this case, we're linking to the head of the list, reset the head + newElem.m_Next = m_Head; + m_Head = elem; + } + else + { + // Here, we're not linking to the end. Set the next pointer to point to + // the element we're linking. + Assert( IsInList(after) ); + ListElem_t& afterElem = InternalElement(after); + newElem.m_Next = afterElem.m_Next; + afterElem.m_Next = elem; + } + + // Reset the tail if we linked to the tail of the list + if (newElem.m_Next == InvalidIndex()) + m_Tail = elem; + else + InternalElement(newElem.m_Next).m_Previous = elem; + + // one more element baby + ++m_ElementCount; +} + +template +void CUtlLinkedList::Unlink( I elem ) +{ + Assert( IsValidIndex(elem) ); + if (IsInList(elem)) + { + ListElem_t *pBase = m_Memory.Base(); + ListElem_t *pOldElem = &pBase[elem]; + + // If we're the first guy, reset the head + // otherwise, make our previous node's next pointer = our next + if ( pOldElem->m_Previous != INVALID_LLIST_IDX ) + { + pBase[pOldElem->m_Previous].m_Next = pOldElem->m_Next; + } + else + { + m_Head = pOldElem->m_Next; + } + + // If we're the last guy, reset the tail + // otherwise, make our next node's prev pointer = our prev + if ( pOldElem->m_Next != INVALID_LLIST_IDX ) + { + pBase[pOldElem->m_Next].m_Previous = pOldElem->m_Previous; + } + else + { + m_Tail = pOldElem->m_Previous; + } + + // This marks this node as not in the list, + // but not in the free list either + pOldElem->m_Previous = pOldElem->m_Next = elem; + + // One less puppy + --m_ElementCount; + } +} + +template +inline void CUtlLinkedList::LinkToHead( I elem ) +{ + LinkAfter( InvalidIndex(), elem ); +} + +template +inline void CUtlLinkedList::LinkToTail( I elem ) +{ + LinkBefore( InvalidIndex(), elem ); +} + + +//----------------------------------------------------------------------------- +// Class to drop in to replace a CUtlLinkedList that needs to be more memory agressive +//----------------------------------------------------------------------------- + +DECLARE_POINTER_HANDLE( UtlPtrLinkedListIndex_t ); // to enforce correct usage + +template < typename T > +class CUtlPtrLinkedList +{ +public: + CUtlPtrLinkedList() + : m_pFirst( NULL ), + m_nElems( 0 ) + { + COMPILE_TIME_ASSERT( sizeof(IndexType_t) == sizeof(Node_t *) ); + } + + ~CUtlPtrLinkedList() + { + RemoveAll(); + } + + typedef UtlPtrLinkedListIndex_t IndexType_t; + + T &operator[]( IndexType_t i ) + { + return (( Node_t * )i)->elem; + } + + const T &operator[]( IndexType_t i ) const + { + return (( Node_t * )i)->elem; + } + + IndexType_t AddToTail() + { + return DoInsertBefore( (IndexType_t)m_pFirst, NULL ); + } + + IndexType_t AddToTail( T const& src ) + { + return DoInsertBefore( (IndexType_t)m_pFirst, &src ); + } + + IndexType_t AddToHead() + { + IndexType_t result = DoInsertBefore( (IndexType_t)m_pFirst, NULL ); + m_pFirst = ((Node_t *)result); + return result; + } + + IndexType_t AddToHead( T const& src ) + { + IndexType_t result = DoInsertBefore( (IndexType_t)m_pFirst, &src ); + m_pFirst = ((Node_t *)result); + return result; + } + + IndexType_t InsertBefore( IndexType_t before ) + { + return DoInsertBefore( before, NULL ); + } + + IndexType_t InsertAfter( IndexType_t after ) + { + Node_t *pBefore = ((Node_t *)after)->next; + return DoInsertBefore( pBefore, NULL ); + } + + IndexType_t InsertBefore( IndexType_t before, T const& src ) + { + return DoInsertBefore( before, &src ); + } + + IndexType_t InsertAfter( IndexType_t after, T const& src ) + { + Node_t *pBefore = ((Node_t *)after)->next; + return DoInsertBefore( pBefore, &src ); + } + + void Remove( IndexType_t elem ) + { + Node_t *p = (Node_t *)elem; + + if ( p->pNext == p ) + { + m_pFirst = NULL; + } + else + { + if ( m_pFirst == p ) + { + m_pFirst = p->pNext; + } + p->pNext->pPrev = p->pPrev; + p->pPrev->pNext = p->pNext; + } + + delete p; + m_nElems--; + } + + void RemoveAll() + { + Node_t *p = m_pFirst; + if ( p ) + { + do + { + Node_t *pNext = p->pNext; + delete p; + p = pNext; + } while( p != m_pFirst ); + } + + m_pFirst = NULL; + m_nElems = 0; + } + + int Count() const + { + return m_nElems; + } + + IndexType_t Head() const + { + return (IndexType_t)m_pFirst; + } + + IndexType_t Next( IndexType_t i ) const + { + Node_t *p = ((Node_t *)i)->pNext; + if ( p != m_pFirst ) + { + return (IndexType_t)p; + } + return NULL; + } + + bool IsValidIndex( IndexType_t i ) const + { + Node_t *p = ((Node_t *)i); + return ( p && p->pNext && p->pPrev ); + } + + inline static IndexType_t InvalidIndex() + { + return NULL; + } +private: + + struct Node_t + { + Node_t() {} + Node_t( const T &elem ) : elem( elem ) {} + + T elem; + Node_t *pPrev, *pNext; + }; + + Node_t *AllocNode( const T *pCopyFrom ) + { + MEM_ALLOC_CREDIT_CLASS(); + Node_t *p; + + if ( !pCopyFrom ) + { + p = new Node_t; + } + else + { + p = new Node_t( *pCopyFrom ); + } + + return p; + } + + IndexType_t DoInsertBefore( IndexType_t before, const T *pCopyFrom ) + { + Node_t *p = AllocNode( pCopyFrom ); + Node_t *pBefore = (Node_t *)before; + if ( pBefore ) + { + p->pNext = pBefore; + p->pPrev = pBefore->pPrev; + pBefore->pPrev = p; + p->pPrev->pNext = p; + } + else + { + Assert( !m_pFirst ); + m_pFirst = p->pNext = p->pPrev = p; + } + + m_nElems++; + return (IndexType_t)p; + } + + Node_t *m_pFirst; + unsigned m_nElems; +}; + +//----------------------------------------------------------------------------- + +#endif // UTLLINKEDLIST_H diff --git a/public/tier1/utlmemory.h b/public/tier1/utlmemory.h index a4249249..acbab9dd 100644 --- a/public/tier1/utlmemory.h +++ b/public/tier1/utlmemory.h @@ -21,8 +21,10 @@ #include "tier0/memalloc.h" #include "tier0/memdbgon.h" +#ifdef _MSC_VER #pragma warning (disable:4100) #pragma warning (disable:4514) +#endif //----------------------------------------------------------------------------- @@ -627,11 +629,11 @@ CUtlMemoryAligned::CUtlMemoryAligned( int nGrowSize, int nInitAll CUtlMemory::m_pMemory = 0; CUtlMemory::m_nAllocationCount = nInitAllocationCount; CUtlMemory::m_nGrowSize = nGrowSize; - ValidateGrowSize(); + this->ValidateGrowSize(); // Alignment must be a power of two COMPILE_TIME_ASSERT( (nAlignment & (nAlignment-1)) == 0 ); - Assert( (nGrowSize >= 0) && (nGrowSize != EXTERNAL_BUFFER_MARKER) ); + Assert( (nGrowSize >= 0) && (nGrowSize != this->EXTERNAL_BUFFER_MARKER) ); if ( CUtlMemory::m_nAllocationCount ) { UTLMEMORY_TRACK_ALLOC(); @@ -708,7 +710,7 @@ void CUtlMemoryAligned::Grow( int num ) { Assert( num > 0 ); - if ( IsExternallyAllocated() ) + if ( this->IsExternallyAllocated() ) { // Can't grow a buffer whose memory was externally allocated Assert(0); @@ -750,7 +752,7 @@ inline void CUtlMemoryAligned::EnsureCapacity( int num ) if ( CUtlMemory::m_nAllocationCount >= num ) return; - if ( IsExternallyAllocated() ) + if ( this->IsExternallyAllocated() ) { // Can't grow a buffer whose memory was externally allocated Assert(0); @@ -783,7 +785,7 @@ inline void CUtlMemoryAligned::EnsureCapacity( int num ) template< class T, int nAlignment > void CUtlMemoryAligned::Purge() { - if ( !IsExternallyAllocated() ) + if ( !this->IsExternallyAllocated() ) { if (m_pMemoryBase) { diff --git a/public/tier1/utlmultilist.h b/public/tier1/utlmultilist.h index a5ac7293..29d8735f 100644 --- a/public/tier1/utlmultilist.h +++ b/public/tier1/utlmultilist.h @@ -1,729 +1,729 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: Multiple linked list container class -// -// $Revision: $ -// $NoKeywords: $ -//=============================================================================// - -#ifndef UTLMULTILIST_H -#define UTLMULTILIST_H - -#ifdef _WIN32 -#pragma once -#endif - -#include "utllinkedlist.h" - -// memdbgon must be the last include file in a .h file!!! -#include "tier0/memdbgon.h" - - -// This is a useful macro to iterate from head to tail in a linked list. -#define FOR_EACH_LL( listName, iteratorName ) \ - for( int iteratorName=listName.Head(); iteratorName != listName.InvalidIndex(); iteratorName = listName.Next( iteratorName ) ) - -//----------------------------------------------------------------------------- -// class CUtlMultiList: -// description: -// A lovely index-based linked list! T is the class type, I is the index -// type, which usually should be an unsigned short or smaller. -// This list can contain multiple lists -//----------------------------------------------------------------------------- -template -class CUtlMultiList -{ -public: - typedef I ListHandle_t; - - // constructor, destructor - CUtlMultiList( int growSize = 0, int initSize = 0 ); - CUtlMultiList( void *pMemory, int memsize ); - ~CUtlMultiList( ); - - // gets particular elements - T& Element( I i ); - T const& Element( I i ) const; - T& operator[]( I i ); - T const& operator[]( I i ) const; - - // Make sure we have a particular amount of memory - void EnsureCapacity( int num ); - - // Memory deallocation - void Purge(); - - // List Creation/deletion - ListHandle_t CreateList(); - void DestroyList( ListHandle_t list ); - bool IsValidList( ListHandle_t list ) const; - - // Insertion methods (call default constructor).... - I InsertBefore( ListHandle_t list, I before ); - I InsertAfter( ListHandle_t list, I after ); - I AddToHead( ListHandle_t list ); - I AddToTail( ListHandle_t list ); - - // Insertion methods (call copy constructor).... - I InsertBefore( ListHandle_t list, I before, T const& src ); - I InsertAfter( ListHandle_t list, I after, T const& src ); - I AddToHead( ListHandle_t list, T const& src ); - I AddToTail( ListHandle_t list, T const& src ); - - // Removal methods - void Remove( ListHandle_t list, I elem ); - - // Removes all items in a single list - void RemoveAll( ListHandle_t list ); - - // Removes all items in all lists - void RemoveAll(); - - // Allocation/deallocation methods - // NOTE: To free, it must *not* be in a list! - I Alloc( ); - void Free( I elem ); - - // list modification - void LinkBefore( ListHandle_t list, I before, I elem ); - void LinkAfter( ListHandle_t list, I after, I elem ); - void Unlink( ListHandle_t list, I elem ); - void LinkToHead( ListHandle_t list, I elem ); - void LinkToTail( ListHandle_t list, I elem ); - - // invalid index - static I InvalidIndex() { return (I)~0; } - static size_t ElementSize() { return sizeof(ListElem_t); } - - // list statistics - int Count( ListHandle_t list ) const; - int TotalCount( ) const; - I MaxElementIndex() const; - - // Traversing the list - I Head( ListHandle_t list ) const; - I Tail( ListHandle_t list ) const; - I Previous( I element ) const; - I Next( I element ) const; - - // Are nodes in a list or valid? - bool IsValidIndex( I i ) const; - bool IsInList( I i ) const; - -protected: - // What the linked list element looks like - struct ListElem_t - { - T m_Element; - I m_Previous; - I m_Next; - }; - - struct List_t - { - I m_Head; - I m_Tail; - I m_Count; - }; - - // constructs the class - void ConstructList( ); - - // Gets at the list element.... - ListElem_t& InternalElement( I i ) { return m_Memory[i]; } - ListElem_t const& InternalElement( I i ) const { return m_Memory[i]; } - - // A test for debug mode only... - bool IsElementInList( ListHandle_t list, I elem ) const; - - // copy constructors not allowed - CUtlMultiList( CUtlMultiList const& list ) { Assert(0); } - - CUtlMemory m_Memory; - CUtlLinkedList m_List; - I* m_pElementList; - - I m_FirstFree; - I m_TotalElements; - I m_MaxElementIndex; // The number allocated - -#if !defined(_XBOX) || defined(_DEBUG) - void ResetDbgInfo() - { - m_pElements = m_Memory.Base(); - -#ifdef _DEBUG - // Allocate space for the element list (which list is each element in) - if (m_Memory.NumAllocated() > 0) - { - if (!m_pElementList) - { - m_pElementList = (I*)malloc( m_Memory.NumAllocated() * sizeof(I) ); - } - else - { - m_pElementList = (I*)realloc( m_pElementList, m_Memory.NumAllocated() * sizeof(I) ); - } - } -#endif - } - - // For debugging purposes; - // it's in release builds so this can be used in libraries correctly - ListElem_t *m_pElements; -#else - void ResetDbgInfo() {} -#endif -}; - - -//----------------------------------------------------------------------------- -// constructor, destructor -//----------------------------------------------------------------------------- - -template -CUtlMultiList::CUtlMultiList( int growSize, int initSize ) : - m_Memory(growSize, initSize), m_pElementList(0) -{ - ConstructList(); -} - -template -CUtlMultiList::CUtlMultiList( void* pMemory, int memsize ) : - m_Memory((ListElem_t *)pMemory, memsize/sizeof(ListElem_t)), m_pElementList(0) -{ - ConstructList(); -} - -template -CUtlMultiList::~CUtlMultiList( ) -{ - RemoveAll(); - if (m_pElementList) - free(m_pElementList); -} - -template -void CUtlMultiList::ConstructList( ) -{ - m_FirstFree = InvalidIndex(); - m_TotalElements = 0; - m_MaxElementIndex = 0; - ResetDbgInfo(); -} - - -//----------------------------------------------------------------------------- -// gets particular elements -//----------------------------------------------------------------------------- -template -inline T& CUtlMultiList::Element( I i ) -{ - return m_Memory[i].m_Element; -} - -template -inline T const& CUtlMultiList::Element( I i ) const -{ - return m_Memory[i].m_Element; -} - -template -inline T& CUtlMultiList::operator[]( I i ) -{ - return m_Memory[i].m_Element; -} - -template -inline T const& CUtlMultiList::operator[]( I i ) const -{ - return m_Memory[i].m_Element; -} - - -//----------------------------------------------------------------------------- -// list creation/destruction -//----------------------------------------------------------------------------- -template -typename CUtlMultiList::ListHandle_t CUtlMultiList::CreateList() -{ - ListHandle_t l = m_List.AddToTail(); - m_List[l].m_Head = m_List[l].m_Tail = InvalidIndex(); - m_List[l].m_Count = 0; - return l; -} - -template -void CUtlMultiList::DestroyList( ListHandle_t list ) -{ - Assert( IsValidList(list) ); - RemoveAll( list ); - m_List.Remove(list); -} - -template -bool CUtlMultiList::IsValidList( ListHandle_t list ) const -{ - return m_List.IsValidIndex(list); -} - - -//----------------------------------------------------------------------------- -// list statistics -//----------------------------------------------------------------------------- -template -inline int CUtlMultiList::TotalCount() const -{ - return m_TotalElements; -} - -template -inline int CUtlMultiList::Count( ListHandle_t list ) const -{ - Assert( IsValidList(list) ); - return m_List[list].m_Count; -} - -template -inline I CUtlMultiList::MaxElementIndex() const -{ - return m_MaxElementIndex; -} - - -//----------------------------------------------------------------------------- -// Traversing the list -//----------------------------------------------------------------------------- -template -inline I CUtlMultiList::Head(ListHandle_t list) const -{ - Assert( IsValidList(list) ); - return m_List[list].m_Head; -} - -template -inline I CUtlMultiList::Tail(ListHandle_t list) const -{ - Assert( IsValidList(list) ); - return m_List[list].m_Tail; -} - -template -inline I CUtlMultiList::Previous( I i ) const -{ - Assert( IsValidIndex(i) ); - return InternalElement(i).m_Previous; -} - -template -inline I CUtlMultiList::Next( I i ) const -{ - Assert( IsValidIndex(i) ); - return InternalElement(i).m_Next; -} - - -//----------------------------------------------------------------------------- -// Are nodes in the list or valid? -//----------------------------------------------------------------------------- -template -inline bool CUtlMultiList::IsValidIndex( I i ) const -{ - return (i < m_MaxElementIndex) && (i >= 0) && - ((m_Memory[i].m_Previous != i) || (m_Memory[i].m_Next == i)); -} - -template -inline bool CUtlMultiList::IsInList( I i ) const -{ - return (i < m_MaxElementIndex) && (i >= 0) && (Previous(i) != i); -} - - -//----------------------------------------------------------------------------- -// Makes sure we have enough memory allocated to store a requested # of elements -//----------------------------------------------------------------------------- -template< class T, class I > -void CUtlMultiList::EnsureCapacity( int num ) -{ - m_Memory.EnsureCapacity(num); - ResetDbgInfo(); -} - - -//----------------------------------------------------------------------------- -// Deallocate memory -//----------------------------------------------------------------------------- -template -void CUtlMultiList::Purge() -{ - RemoveAll(); - m_List.Purge(); - m_Memory.Purge( ); - m_List.Purge(); - m_FirstFree = InvalidIndex(); - m_TotalElements = 0; - m_MaxElementIndex = 0; - ResetDbgInfo(); -} - - -//----------------------------------------------------------------------------- -// Node allocation/deallocation -//----------------------------------------------------------------------------- -template -I CUtlMultiList::Alloc( ) -{ - I elem; - if (m_FirstFree == InvalidIndex()) - { - // Nothing in the free list; add. - // Since nothing is in the free list, m_TotalElements == total # of elements - // the list knows about. - if (m_MaxElementIndex == m_Memory.NumAllocated()) - { - m_Memory.Grow(); - ResetDbgInfo(); - } - - elem = (I)m_MaxElementIndex; - ++m_MaxElementIndex; - } - else - { - elem = m_FirstFree; - m_FirstFree = InternalElement(m_FirstFree).m_Next; - } - - // Mark the element as not being in a list - InternalElement(elem).m_Next = InternalElement(elem).m_Previous = elem; - - ++m_TotalElements; - - Construct( &Element(elem) ); - - return elem; -} - -template -void CUtlMultiList::Free( I elem ) -{ - Assert( IsValidIndex(elem) && !IsInList(elem) ); - Destruct( &Element(elem) ); - InternalElement(elem).m_Next = m_FirstFree; - m_FirstFree = elem; - --m_TotalElements; -} - - -//----------------------------------------------------------------------------- -// A test for debug mode only... -//----------------------------------------------------------------------------- -template -inline bool CUtlMultiList::IsElementInList( ListHandle_t list, I elem ) const -{ - if (!m_pElementList) - return true; - - return m_pElementList[elem] == list; -} - - -//----------------------------------------------------------------------------- -// list modification -//----------------------------------------------------------------------------- -template -void CUtlMultiList::LinkBefore( ListHandle_t list, I before, I elem ) -{ - Assert( IsValidIndex(elem) && IsValidList(list) ); - - // Unlink it if it's in the list at the moment - Unlink(list, elem); - - ListElem_t& newElem = InternalElement(elem); - - // The element *after* our newly linked one is the one we linked before. - newElem.m_Next = before; - - if (before == InvalidIndex()) - { - // In this case, we're linking to the end of the list, so reset the tail - newElem.m_Previous = m_List[list].m_Tail; - m_List[list].m_Tail = elem; - } - else - { - // Here, we're not linking to the end. Set the prev pointer to point to - // the element we're linking. - Assert( IsInList(before) ); - ListElem_t& beforeElem = InternalElement(before); - newElem.m_Previous = beforeElem.m_Previous; - beforeElem.m_Previous = elem; - } - - // Reset the head if we linked to the head of the list - if (newElem.m_Previous == InvalidIndex()) - m_List[list].m_Head = elem; - else - InternalElement(newElem.m_Previous).m_Next = elem; - - // one more element baby - ++m_List[list].m_Count; - - // Store the element into the list - if (m_pElementList) - m_pElementList[elem] = list; -} - -template -void CUtlMultiList::LinkAfter( ListHandle_t list, I after, I elem ) -{ - Assert( IsValidIndex(elem) ); - - // Unlink it if it's in the list at the moment - Unlink(list, elem); - - ListElem_t& newElem = InternalElement(elem); - - // The element *before* our newly linked one is the one we linked after - newElem.m_Previous = after; - if (after == InvalidIndex()) - { - // In this case, we're linking to the head of the list, reset the head - newElem.m_Next = m_List[list].m_Head; - m_List[list].m_Head = elem; - } - else - { - // Here, we're not linking to the end. Set the next pointer to point to - // the element we're linking. - Assert( IsInList(after) ); - ListElem_t& afterElem = InternalElement(after); - newElem.m_Next = afterElem.m_Next; - afterElem.m_Next = elem; - } - - // Reset the tail if we linked to the tail of the list - if (newElem.m_Next == InvalidIndex()) - m_List[list].m_Tail = elem; - else - InternalElement(newElem.m_Next).m_Previous = elem; - - // one more element baby - ++m_List[list].m_Count; - - // Store the element into the list - if (m_pElementList) - m_pElementList[elem] = list; -} - -template -void CUtlMultiList::Unlink( ListHandle_t list, I elem ) -{ - Assert( IsValidIndex(elem) && IsValidList(list) ); - - if (IsInList(elem)) - { - // Make sure the element is in the right list - Assert( IsElementInList( list, elem ) ); - ListElem_t& oldElem = InternalElement(elem); - - // If we're the first guy, reset the head - // otherwise, make our previous node's next pointer = our next - if (oldElem.m_Previous != InvalidIndex()) - InternalElement(oldElem.m_Previous).m_Next = oldElem.m_Next; - else - m_List[list].m_Head = oldElem.m_Next; - - // If we're the last guy, reset the tail - // otherwise, make our next node's prev pointer = our prev - if (oldElem.m_Next != InvalidIndex()) - InternalElement(oldElem.m_Next).m_Previous = oldElem.m_Previous; - else - m_List[list].m_Tail = oldElem.m_Previous; - - // This marks this node as not in the list, - // but not in the free list either - oldElem.m_Previous = oldElem.m_Next = elem; - - // One less puppy - --m_List[list].m_Count; - - // Store the element into the list - if (m_pElementList) - m_pElementList[elem] = m_List.InvalidIndex(); - } -} - -template -inline void CUtlMultiList::LinkToHead( ListHandle_t list, I elem ) -{ - LinkAfter( list, InvalidIndex(), elem ); -} - -template -inline void CUtlMultiList::LinkToTail( ListHandle_t list, I elem ) -{ - LinkBefore( list, InvalidIndex(), elem ); -} - - -//----------------------------------------------------------------------------- -// Insertion methods; allocates and links (uses default constructor) -//----------------------------------------------------------------------------- -template -I CUtlMultiList::InsertBefore( ListHandle_t list, I before ) -{ - // Make a new node - I newNode = Alloc(); - - // Link it in - LinkBefore( list, before, newNode ); - - // Construct the data - Construct( &Element(newNode) ); - - return newNode; -} - -template -I CUtlMultiList::InsertAfter( ListHandle_t list, I after ) -{ - // Make a new node - I newNode = Alloc(); - - // Link it in - LinkAfter( list, after, newNode ); - - // Construct the data - Construct( &Element(newNode) ); - - return newNode; -} - -template -inline I CUtlMultiList::AddToHead( ListHandle_t list ) -{ - return InsertAfter( list, InvalidIndex() ); -} - -template -inline I CUtlMultiList::AddToTail( ListHandle_t list ) -{ - return InsertBefore( list, InvalidIndex() ); -} - - -//----------------------------------------------------------------------------- -// Insertion methods; allocates and links (uses copy constructor) -//----------------------------------------------------------------------------- -template -I CUtlMultiList::InsertBefore( ListHandle_t list, I before, T const& src ) -{ - // Make a new node - I newNode = Alloc(); - - // Link it in - LinkBefore( list, before, newNode ); - - // Construct the data - CopyConstruct( &Element(newNode), src ); - - return newNode; -} - -template -I CUtlMultiList::InsertAfter( ListHandle_t list, I after, T const& src ) -{ - // Make a new node - I newNode = Alloc(); - - // Link it in - LinkAfter( list, after, newNode ); - - // Construct the data - CopyConstruct( &Element(newNode), src ); - - return newNode; -} - -template -inline I CUtlMultiList::AddToHead( ListHandle_t list, T const& src ) -{ - return InsertAfter( list, InvalidIndex(), src ); -} - -template -inline I CUtlMultiList::AddToTail( ListHandle_t list, T const& src ) -{ - return InsertBefore( list, InvalidIndex(), src ); -} - - -//----------------------------------------------------------------------------- -// Removal methods -//----------------------------------------------------------------------------- -template -void CUtlMultiList::Remove( ListHandle_t list, I elem ) -{ - if (IsInList(elem)) - Unlink(list, elem); - Free( elem ); -} - -// Removes all items in a single list -template -void CUtlMultiList::RemoveAll( ListHandle_t list ) -{ - Assert( IsValidList(list) ); - I i = Head(list); - I next; - while( i != InvalidIndex() ) - { - next = Next(i); - Remove(list, i); - i = next; - } -} - - -template -void CUtlMultiList::RemoveAll() -{ - if (m_MaxElementIndex == 0) - return; - - // Put everything into the free list - I prev = InvalidIndex(); - for (int i = (int)m_MaxElementIndex; --i >= 0; ) - { - // Invoke the destructor - if (IsValidIndex((I)i)) - Destruct( &Element((I)i) ); - - // next points to the next free list item - InternalElement((I)i).m_Next = prev; - - // Indicates it's in the free list - InternalElement((I)i).m_Previous = (I)i; - prev = (I)i; - } - - // First free points to the first element - m_FirstFree = 0; - - // Clear everything else out - for (I list = m_List.Head(); list != m_List.InvalidIndex(); list = m_List.Next(list) ) - { - m_List[list].m_Head = InvalidIndex(); - m_List[list].m_Tail = InvalidIndex(); - m_List[list].m_Count = 0; - } - - m_TotalElements = 0; -} - - -#include "tier0/memdbgoff.h" - -#endif // UTLMULTILIST_H +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Multiple linked list container class +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef UTLMULTILIST_H +#define UTLMULTILIST_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "utllinkedlist.h" + +// memdbgon must be the last include file in a .h file!!! +#include "tier0/memdbgon.h" + + +// This is a useful macro to iterate from head to tail in a linked list. +#define FOR_EACH_LL( listName, iteratorName ) \ + for( int iteratorName=listName.Head(); iteratorName != listName.InvalidIndex(); iteratorName = listName.Next( iteratorName ) ) + +//----------------------------------------------------------------------------- +// class CUtlMultiList: +// description: +// A lovely index-based linked list! T is the class type, I is the index +// type, which usually should be an unsigned short or smaller. +// This list can contain multiple lists +//----------------------------------------------------------------------------- +template +class CUtlMultiList +{ +public: + typedef I ListHandle_t; + + // constructor, destructor + CUtlMultiList( int growSize = 0, int initSize = 0 ); + CUtlMultiList( void *pMemory, int memsize ); + ~CUtlMultiList( ); + + // gets particular elements + T& Element( I i ); + T const& Element( I i ) const; + T& operator[]( I i ); + T const& operator[]( I i ) const; + + // Make sure we have a particular amount of memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // List Creation/deletion + ListHandle_t CreateList(); + void DestroyList( ListHandle_t list ); + bool IsValidList( ListHandle_t list ) const; + + // Insertion methods (call default constructor).... + I InsertBefore( ListHandle_t list, I before ); + I InsertAfter( ListHandle_t list, I after ); + I AddToHead( ListHandle_t list ); + I AddToTail( ListHandle_t list ); + + // Insertion methods (call copy constructor).... + I InsertBefore( ListHandle_t list, I before, T const& src ); + I InsertAfter( ListHandle_t list, I after, T const& src ); + I AddToHead( ListHandle_t list, T const& src ); + I AddToTail( ListHandle_t list, T const& src ); + + // Removal methods + void Remove( ListHandle_t list, I elem ); + + // Removes all items in a single list + void RemoveAll( ListHandle_t list ); + + // Removes all items in all lists + void RemoveAll(); + + // Allocation/deallocation methods + // NOTE: To free, it must *not* be in a list! + I Alloc( ); + void Free( I elem ); + + // list modification + void LinkBefore( ListHandle_t list, I before, I elem ); + void LinkAfter( ListHandle_t list, I after, I elem ); + void Unlink( ListHandle_t list, I elem ); + void LinkToHead( ListHandle_t list, I elem ); + void LinkToTail( ListHandle_t list, I elem ); + + // invalid index + static I InvalidIndex() { return (I)~0; } + static size_t ElementSize() { return sizeof(ListElem_t); } + + // list statistics + int Count( ListHandle_t list ) const; + int TotalCount( ) const; + I MaxElementIndex() const; + + // Traversing the list + I Head( ListHandle_t list ) const; + I Tail( ListHandle_t list ) const; + I Previous( I element ) const; + I Next( I element ) const; + + // Are nodes in a list or valid? + bool IsValidIndex( I i ) const; + bool IsInList( I i ) const; + +protected: + // What the linked list element looks like + struct ListElem_t + { + T m_Element; + I m_Previous; + I m_Next; + }; + + struct List_t + { + I m_Head; + I m_Tail; + I m_Count; + }; + + // constructs the class + void ConstructList( ); + + // Gets at the list element.... + ListElem_t& InternalElement( I i ) { return m_Memory[i]; } + ListElem_t const& InternalElement( I i ) const { return m_Memory[i]; } + + // A test for debug mode only... + bool IsElementInList( ListHandle_t list, I elem ) const; + + // copy constructors not allowed + CUtlMultiList( CUtlMultiList const& list ) { Assert(0); } + + CUtlMemory m_Memory; + CUtlLinkedList m_List; + I* m_pElementList; + + I m_FirstFree; + I m_TotalElements; + I m_MaxElementIndex; // The number allocated + +#if !defined(_XBOX) || defined(_DEBUG) + void ResetDbgInfo() + { + m_pElements = m_Memory.Base(); + +#ifdef _DEBUG + // Allocate space for the element list (which list is each element in) + if (m_Memory.NumAllocated() > 0) + { + if (!m_pElementList) + { + m_pElementList = (I*)malloc( m_Memory.NumAllocated() * sizeof(I) ); + } + else + { + m_pElementList = (I*)realloc( m_pElementList, m_Memory.NumAllocated() * sizeof(I) ); + } + } +#endif + } + + // For debugging purposes; + // it's in release builds so this can be used in libraries correctly + ListElem_t *m_pElements; +#else + void ResetDbgInfo() {} +#endif +}; + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlMultiList::CUtlMultiList( int growSize, int initSize ) : + m_Memory(growSize, initSize), m_pElementList(0) +{ + ConstructList(); +} + +template +CUtlMultiList::CUtlMultiList( void* pMemory, int memsize ) : + m_Memory((ListElem_t *)pMemory, memsize/sizeof(ListElem_t)), m_pElementList(0) +{ + ConstructList(); +} + +template +CUtlMultiList::~CUtlMultiList( ) +{ + RemoveAll(); + if (m_pElementList) + free(m_pElementList); +} + +template +void CUtlMultiList::ConstructList( ) +{ + m_FirstFree = InvalidIndex(); + m_TotalElements = 0; + m_MaxElementIndex = 0; + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// gets particular elements +//----------------------------------------------------------------------------- +template +inline T& CUtlMultiList::Element( I i ) +{ + return m_Memory[i].m_Element; +} + +template +inline T const& CUtlMultiList::Element( I i ) const +{ + return m_Memory[i].m_Element; +} + +template +inline T& CUtlMultiList::operator[]( I i ) +{ + return m_Memory[i].m_Element; +} + +template +inline T const& CUtlMultiList::operator[]( I i ) const +{ + return m_Memory[i].m_Element; +} + + +//----------------------------------------------------------------------------- +// list creation/destruction +//----------------------------------------------------------------------------- +template +typename CUtlMultiList::ListHandle_t CUtlMultiList::CreateList() +{ + ListHandle_t l = m_List.AddToTail(); + m_List[l].m_Head = m_List[l].m_Tail = InvalidIndex(); + m_List[l].m_Count = 0; + return l; +} + +template +void CUtlMultiList::DestroyList( ListHandle_t list ) +{ + Assert( IsValidList(list) ); + RemoveAll( list ); + m_List.Remove(list); +} + +template +bool CUtlMultiList::IsValidList( ListHandle_t list ) const +{ + return m_List.IsValidIndex(list); +} + + +//----------------------------------------------------------------------------- +// list statistics +//----------------------------------------------------------------------------- +template +inline int CUtlMultiList::TotalCount() const +{ + return m_TotalElements; +} + +template +inline int CUtlMultiList::Count( ListHandle_t list ) const +{ + Assert( IsValidList(list) ); + return m_List[list].m_Count; +} + +template +inline I CUtlMultiList::MaxElementIndex() const +{ + return m_MaxElementIndex; +} + + +//----------------------------------------------------------------------------- +// Traversing the list +//----------------------------------------------------------------------------- +template +inline I CUtlMultiList::Head(ListHandle_t list) const +{ + Assert( IsValidList(list) ); + return m_List[list].m_Head; +} + +template +inline I CUtlMultiList::Tail(ListHandle_t list) const +{ + Assert( IsValidList(list) ); + return m_List[list].m_Tail; +} + +template +inline I CUtlMultiList::Previous( I i ) const +{ + Assert( IsValidIndex(i) ); + return InternalElement(i).m_Previous; +} + +template +inline I CUtlMultiList::Next( I i ) const +{ + Assert( IsValidIndex(i) ); + return InternalElement(i).m_Next; +} + + +//----------------------------------------------------------------------------- +// Are nodes in the list or valid? +//----------------------------------------------------------------------------- +template +inline bool CUtlMultiList::IsValidIndex( I i ) const +{ + return (i < m_MaxElementIndex) && (i > 0) && + ((m_Memory[i].m_Previous != i) || (m_Memory[i].m_Next == i)); +} + +template +inline bool CUtlMultiList::IsInList( I i ) const +{ + return (i < m_MaxElementIndex) && (i > 0) && (Previous(i) != i); +} + + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMultiList::EnsureCapacity( int num ) +{ + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// Deallocate memory +//----------------------------------------------------------------------------- +template +void CUtlMultiList::Purge() +{ + RemoveAll(); + m_List.Purge(); + m_Memory.Purge( ); + m_List.Purge(); + m_FirstFree = InvalidIndex(); + m_TotalElements = 0; + m_MaxElementIndex = 0; + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// Node allocation/deallocation +//----------------------------------------------------------------------------- +template +I CUtlMultiList::Alloc( ) +{ + I elem; + if (m_FirstFree == InvalidIndex()) + { + // Nothing in the free list; add. + // Since nothing is in the free list, m_TotalElements == total # of elements + // the list knows about. + if (m_MaxElementIndex == m_Memory.NumAllocated()) + { + m_Memory.Grow(); + ResetDbgInfo(); + } + + elem = (I)m_MaxElementIndex; + ++m_MaxElementIndex; + } + else + { + elem = m_FirstFree; + m_FirstFree = InternalElement(m_FirstFree).m_Next; + } + + // Mark the element as not being in a list + InternalElement(elem).m_Next = InternalElement(elem).m_Previous = elem; + + ++m_TotalElements; + + Construct( &Element(elem) ); + + return elem; +} + +template +void CUtlMultiList::Free( I elem ) +{ + Assert( IsValidIndex(elem) && !IsInList(elem) ); + Destruct( &Element(elem) ); + InternalElement(elem).m_Next = m_FirstFree; + m_FirstFree = elem; + --m_TotalElements; +} + + +//----------------------------------------------------------------------------- +// A test for debug mode only... +//----------------------------------------------------------------------------- +template +inline bool CUtlMultiList::IsElementInList( ListHandle_t list, I elem ) const +{ + if (!m_pElementList) + return true; + + return m_pElementList[elem] == list; +} + + +//----------------------------------------------------------------------------- +// list modification +//----------------------------------------------------------------------------- +template +void CUtlMultiList::LinkBefore( ListHandle_t list, I before, I elem ) +{ + Assert( IsValidIndex(elem) && IsValidList(list) ); + + // Unlink it if it's in the list at the moment + Unlink(list, elem); + + ListElem_t& newElem = InternalElement(elem); + + // The element *after* our newly linked one is the one we linked before. + newElem.m_Next = before; + + if (before == InvalidIndex()) + { + // In this case, we're linking to the end of the list, so reset the tail + newElem.m_Previous = m_List[list].m_Tail; + m_List[list].m_Tail = elem; + } + else + { + // Here, we're not linking to the end. Set the prev pointer to point to + // the element we're linking. + Assert( IsInList(before) ); + ListElem_t& beforeElem = InternalElement(before); + newElem.m_Previous = beforeElem.m_Previous; + beforeElem.m_Previous = elem; + } + + // Reset the head if we linked to the head of the list + if (newElem.m_Previous == InvalidIndex()) + m_List[list].m_Head = elem; + else + InternalElement(newElem.m_Previous).m_Next = elem; + + // one more element baby + ++m_List[list].m_Count; + + // Store the element into the list + if (m_pElementList) + m_pElementList[elem] = list; +} + +template +void CUtlMultiList::LinkAfter( ListHandle_t list, I after, I elem ) +{ + Assert( IsValidIndex(elem) ); + + // Unlink it if it's in the list at the moment + Unlink(list, elem); + + ListElem_t& newElem = InternalElement(elem); + + // The element *before* our newly linked one is the one we linked after + newElem.m_Previous = after; + if (after == InvalidIndex()) + { + // In this case, we're linking to the head of the list, reset the head + newElem.m_Next = m_List[list].m_Head; + m_List[list].m_Head = elem; + } + else + { + // Here, we're not linking to the end. Set the next pointer to point to + // the element we're linking. + Assert( IsInList(after) ); + ListElem_t& afterElem = InternalElement(after); + newElem.m_Next = afterElem.m_Next; + afterElem.m_Next = elem; + } + + // Reset the tail if we linked to the tail of the list + if (newElem.m_Next == InvalidIndex()) + m_List[list].m_Tail = elem; + else + InternalElement(newElem.m_Next).m_Previous = elem; + + // one more element baby + ++m_List[list].m_Count; + + // Store the element into the list + if (m_pElementList) + m_pElementList[elem] = list; +} + +template +void CUtlMultiList::Unlink( ListHandle_t list, I elem ) +{ + Assert( IsValidIndex(elem) && IsValidList(list) ); + + if (IsInList(elem)) + { + // Make sure the element is in the right list + Assert( IsElementInList( list, elem ) ); + ListElem_t& oldElem = InternalElement(elem); + + // If we're the first guy, reset the head + // otherwise, make our previous node's next pointer = our next + if (oldElem.m_Previous != InvalidIndex()) + InternalElement(oldElem.m_Previous).m_Next = oldElem.m_Next; + else + m_List[list].m_Head = oldElem.m_Next; + + // If we're the last guy, reset the tail + // otherwise, make our next node's prev pointer = our prev + if (oldElem.m_Next != InvalidIndex()) + InternalElement(oldElem.m_Next).m_Previous = oldElem.m_Previous; + else + m_List[list].m_Tail = oldElem.m_Previous; + + // This marks this node as not in the list, + // but not in the free list either + oldElem.m_Previous = oldElem.m_Next = elem; + + // One less puppy + --m_List[list].m_Count; + + // Store the element into the list + if (m_pElementList) + m_pElementList[elem] = m_List.InvalidIndex(); + } +} + +template +inline void CUtlMultiList::LinkToHead( ListHandle_t list, I elem ) +{ + LinkAfter( list, InvalidIndex(), elem ); +} + +template +inline void CUtlMultiList::LinkToTail( ListHandle_t list, I elem ) +{ + LinkBefore( list, InvalidIndex(), elem ); +} + + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses default constructor) +//----------------------------------------------------------------------------- +template +I CUtlMultiList::InsertBefore( ListHandle_t list, I before ) +{ + // Make a new node + I newNode = Alloc(); + + // Link it in + LinkBefore( list, before, newNode ); + + // Construct the data + Construct( &Element(newNode) ); + + return newNode; +} + +template +I CUtlMultiList::InsertAfter( ListHandle_t list, I after ) +{ + // Make a new node + I newNode = Alloc(); + + // Link it in + LinkAfter( list, after, newNode ); + + // Construct the data + Construct( &Element(newNode) ); + + return newNode; +} + +template +inline I CUtlMultiList::AddToHead( ListHandle_t list ) +{ + return InsertAfter( list, InvalidIndex() ); +} + +template +inline I CUtlMultiList::AddToTail( ListHandle_t list ) +{ + return InsertBefore( list, InvalidIndex() ); +} + + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses copy constructor) +//----------------------------------------------------------------------------- +template +I CUtlMultiList::InsertBefore( ListHandle_t list, I before, T const& src ) +{ + // Make a new node + I newNode = Alloc(); + + // Link it in + LinkBefore( list, before, newNode ); + + // Construct the data + CopyConstruct( &Element(newNode), src ); + + return newNode; +} + +template +I CUtlMultiList::InsertAfter( ListHandle_t list, I after, T const& src ) +{ + // Make a new node + I newNode = Alloc(); + + // Link it in + LinkAfter( list, after, newNode ); + + // Construct the data + CopyConstruct( &Element(newNode), src ); + + return newNode; +} + +template +inline I CUtlMultiList::AddToHead( ListHandle_t list, T const& src ) +{ + return InsertAfter( list, InvalidIndex(), src ); +} + +template +inline I CUtlMultiList::AddToTail( ListHandle_t list, T const& src ) +{ + return InsertBefore( list, InvalidIndex(), src ); +} + + +//----------------------------------------------------------------------------- +// Removal methods +//----------------------------------------------------------------------------- +template +void CUtlMultiList::Remove( ListHandle_t list, I elem ) +{ + if (IsInList(elem)) + Unlink(list, elem); + Free( elem ); +} + +// Removes all items in a single list +template +void CUtlMultiList::RemoveAll( ListHandle_t list ) +{ + Assert( IsValidList(list) ); + I i = Head(list); + I next; + while( i != InvalidIndex() ) + { + next = Next(i); + Remove(list, i); + i = next; + } +} + + +template +void CUtlMultiList::RemoveAll() +{ + if (m_MaxElementIndex == 0) + return; + + // Put everything into the free list + I prev = InvalidIndex(); + for (int i = (int)m_MaxElementIndex; --i >= 0; ) + { + // Invoke the destructor + if (IsValidIndex((I)i)) + Destruct( &Element((I)i) ); + + // next points to the next free list item + InternalElement((I)i).m_Next = prev; + + // Indicates it's in the free list + InternalElement((I)i).m_Previous = (I)i; + prev = (I)i; + } + + // First free points to the first element + m_FirstFree = 0; + + // Clear everything else out + for (I list = m_List.Head(); list != m_List.InvalidIndex(); list = m_List.Next(list) ) + { + m_List[list].m_Head = InvalidIndex(); + m_List[list].m_Tail = InvalidIndex(); + m_List[list].m_Count = 0; + } + + m_TotalElements = 0; +} + + +#include "tier0/memdbgoff.h" + +#endif // UTLMULTILIST_H diff --git a/public/tier1/utlrbtree.h b/public/tier1/utlrbtree.h index 658f0cac..334071c0 100644 --- a/public/tier1/utlrbtree.h +++ b/public/tier1/utlrbtree.h @@ -10,6 +10,8 @@ #define UTLRBTREE_H #include "tier1/utlmemory.h" +#undef MINMAX_H +#include "minmax.h" //----------------------------------------------------------------------------- // Tool to generate a default compare function for any type that implements @@ -42,7 +44,7 @@ void SetDefLessFunc( RBTREE_T &RBTree ) { #ifdef _WIN32 RBTree.SetLessFunc( DefLessFunc( RBTREE_T::KeyType_t ) ); -#elif _LINUX +#elif defined _LINUX RBTree.SetLessFunc( DefLessFunc( typename RBTREE_T::KeyType_t ) ); #endif } @@ -250,22 +252,23 @@ protected: template inline CUtlRBTree::CUtlRBTree( int growSize, int initSize, const LessFunc_t &lessfunc ) : -m_Elements( growSize, initSize ), m_LessFunc( lessfunc ), +m_Elements( growSize, initSize ), m_Root( InvalidIndex() ), -m_NumElements( 0 ), m_TotalElements( 0 ), -m_FirstFree( InvalidIndex() ) +m_NumElements( 0 ), +m_FirstFree( InvalidIndex() ), +m_TotalElements( 0 ) { ResetDbgInfo(); } template inline CUtlRBTree::CUtlRBTree( const LessFunc_t &lessfunc ) : -m_Elements( 0, 0 ), m_LessFunc( lessfunc ), -m_Root( InvalidIndex() ), -m_NumElements( 0 ), m_TotalElements( 0 ), -m_FirstFree( InvalidIndex() ) +m_Elements( 0, 0 ), +m_Root( InvalidIndex() ), +m_FirstFree( InvalidIndex() ), +m_NumElements( 0 ), m_TotalElements( 0 ) { ResetDbgInfo(); } @@ -1189,6 +1192,7 @@ int CUtlRBTree::Depth( I node ) const int depthright = Depth( RightChild(node) ); int depthleft = Depth( LeftChild(node) ); + return max(depthright, depthleft) + 1; } diff --git a/public/tools/bonelist.cpp b/public/tools/bonelist.cpp index d42810de..fd1bb94d 100644 --- a/public/tools/bonelist.cpp +++ b/public/tools/bonelist.cpp @@ -1,72 +1,72 @@ -//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= -// -// Purpose: -// -//============================================================================= - -#include "cbase.h" -#include "bonelist.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -CBoneList::CBoneList() -{ - m_bShouldDelete = false; - m_nBones = 0; - Q_memset( m_vecPos, 0, sizeof( m_vecPos ) ); - Q_memset( m_quatRot, 0, sizeof( m_quatRot ) ); -} - -void CBoneList::Release() -{ - if (m_bShouldDelete ) - { - delete this; - } - else - { - Warning( "Called Release() on CBoneList not allocated via Alloc() method\n" ); - } -} - -CBoneList *CBoneList::Alloc() -{ - CBoneList *newList = new CBoneList; - Assert( newList ); - if ( newList ) - { - newList->m_bShouldDelete = true; - } - return newList; -} - -CFlexList::CFlexList() -{ - m_bShouldDelete = false; - m_nNumFlexes = 0; - Q_memset( m_flexWeights, 0, sizeof( m_flexWeights ) ); -} - -void CFlexList::Release() -{ - if (m_bShouldDelete ) - { - delete this; - } - else - { - Warning( "Called Release() on CFlexList not allocated via Alloc() method\n" ); - } -} - -CFlexList *CFlexList::Alloc() -{ - CFlexList *newList = new CFlexList; - Assert( newList ); - if ( newList ) - { - newList->m_bShouldDelete = true; - } - return newList; -} \ No newline at end of file +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "bonelist.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CBoneList::CBoneList() +{ + m_bShouldDelete = false; + m_nBones = 0; + Q_memset( m_vecPos, 0, sizeof( m_vecPos ) ); + Q_memset( m_quatRot, 0, sizeof( m_quatRot ) ); +} + +void CBoneList::Release() +{ + if (m_bShouldDelete ) + { + delete this; + } + else + { + Warning( "Called Release() on CBoneList not allocated via Alloc() method\n" ); + } +} + +CBoneList *CBoneList::Alloc() +{ + CBoneList *newList = new CBoneList; + Assert( newList ); + if ( newList ) + { + newList->m_bShouldDelete = true; + } + return newList; +} + +CFlexList::CFlexList() +{ + m_bShouldDelete = false; + m_nNumFlexes = 0; + Q_memset( m_flexWeights, 0, sizeof( m_flexWeights ) ); +} + +void CFlexList::Release() +{ + if (m_bShouldDelete ) + { + delete this; + } + else + { + Warning( "Called Release() on CFlexList not allocated via Alloc() method\n" ); + } +} + +CFlexList *CFlexList::Alloc() +{ + CFlexList *newList = new CFlexList; + Assert( newList ); + if ( newList ) + { + newList->m_bShouldDelete = true; + } + return newList; +} diff --git a/public/trace.h b/public/trace.h index 3c21bddb..ec2dc072 100644 --- a/public/trace.h +++ b/public/trace.h @@ -1,69 +1,69 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef TRACE_H -#define TRACE_H - -#ifdef _WIN32 -#pragma once -#endif - - -#include "mathlib.h" - -// Note: These flags need to match the bspfile.h DISPTRI_TAG_* flags. -#define DISPSURF_FLAG_SURFACE (1<<0) -#define DISPSURF_FLAG_WALKABLE (1<<1) -#define DISPSURF_FLAG_BUILDABLE (1<<2) -#define DISPSURF_FLAG_SURFPROP1 (1<<3) -#define DISPSURF_FLAG_SURFPROP2 (1<<4) - -//============================================================================= -// Base Trace Structure -// - shared between engine/game dlls and tools (vrad) -//============================================================================= - -class CBaseTrace -{ -public: - - // Displacement flags tests. - bool IsDispSurface( void ) { return ( ( dispFlags & DISPSURF_FLAG_SURFACE ) != 0 ); } - bool IsDispSurfaceWalkable( void ) { return ( ( dispFlags & DISPSURF_FLAG_WALKABLE ) != 0 ); } - bool IsDispSurfaceBuildable( void ) { return ( ( dispFlags & DISPSURF_FLAG_BUILDABLE ) != 0 ); } - bool IsDispSurfaceProp1( void ) { return ( ( dispFlags & DISPSURF_FLAG_SURFPROP1 ) != 0 ); } - bool IsDispSurfaceProp2( void ) { return ( ( dispFlags & DISPSURF_FLAG_SURFPROP2 ) != 0 ); } - -public: - - // these members are aligned!! - Vector startpos; // start position - Vector endpos; // final position - cplane_t plane; // surface normal at impact - - float fraction; // time completed, 1.0 = didn't hit anything - - int contents; // contents on other side of surface hit - unsigned short dispFlags; // displacement flags for marking surfaces with data - - bool allsolid; // if true, plane is not valid - bool startsolid; // if true, the initial point was in a solid area - - CBaseTrace() {} - -private: - // No copy constructors allowed - CBaseTrace(const CBaseTrace& vOther); -}; - -#endif // TRACE_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TRACE_H +#define TRACE_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib.h" + +// Note: These flags need to match the bspfile.h DISPTRI_TAG_* flags. +#define DISPSURF_FLAG_SURFACE (1<<0) +#define DISPSURF_FLAG_WALKABLE (1<<1) +#define DISPSURF_FLAG_BUILDABLE (1<<2) +#define DISPSURF_FLAG_SURFPROP1 (1<<3) +#define DISPSURF_FLAG_SURFPROP2 (1<<4) + +//============================================================================= +// Base Trace Structure +// - shared between engine/game dlls and tools (vrad) +//============================================================================= + +class CBaseTrace +{ +public: + + // Displacement flags tests. + bool IsDispSurface( void ) { return ( ( dispFlags & DISPSURF_FLAG_SURFACE ) != 0 ); } + bool IsDispSurfaceWalkable( void ) { return ( ( dispFlags & DISPSURF_FLAG_WALKABLE ) != 0 ); } + bool IsDispSurfaceBuildable( void ) { return ( ( dispFlags & DISPSURF_FLAG_BUILDABLE ) != 0 ); } + bool IsDispSurfaceProp1( void ) { return ( ( dispFlags & DISPSURF_FLAG_SURFPROP1 ) != 0 ); } + bool IsDispSurfaceProp2( void ) { return ( ( dispFlags & DISPSURF_FLAG_SURFPROP2 ) != 0 ); } + +public: + + // these members are aligned!! + Vector startpos; // start position + Vector endpos; // final position + cplane_t plane; // surface normal at impact + + float fraction; // time completed, 1.0 = didn't hit anything + + int contents; // contents on other side of surface hit + unsigned short dispFlags; // displacement flags for marking surfaces with data + + bool allsolid; // if true, plane is not valid + bool startsolid; // if true, the initial point was in a solid area + + CBaseTrace() {} + +private: + // No copy constructors allowed + CBaseTrace(const CBaseTrace& vOther); +}; + +#endif // TRACE_H diff --git a/public/vector.h b/public/vector.h index 334a9ed2..40e51f90 100644 --- a/public/vector.h +++ b/public/vector.h @@ -54,7 +54,7 @@ #ifdef VECTOR_PARANOIA #define CHECK_VALID( _v) Assert( (_v).IsValid() ) #else -#define CHECK_VALID( _v) 0 +#define CHECK_VALID( _v) #endif #ifdef __cplusplus @@ -231,8 +231,8 @@ public: // Initialization void Init(short ix = 0, short iy = 0, short iz = 0, short iw = 0 ); - __m64 &AsM64() { return *(__m64*)&x; } - const __m64 &AsM64() const { return *(const __m64*)&x; } + __m64 &AsM64() { void *addr = &x; return *reinterpret_cast<__m64 *>(addr); } + const __m64 &AsM64() const { const void *addr = &x; return *reinterpret_cast(addr); } // Setter void Set( const ShortVector& vOther ); @@ -405,12 +405,9 @@ Vector RandomVector( vec_t minVal, vec_t maxVal ); //----------------------------------------------------------------------------- inline Vector::Vector(void) { -#ifdef _DEBUG -#ifdef VECTOR_PARANOIA // Initialize to NAN to catch errors x = y = z = VEC_T_NAN; -#endif -#endif + } inline Vector::Vector(vec_t X, vec_t Y, vec_t Z) @@ -724,10 +721,10 @@ FORCEINLINE_VECTOR ShortVector& ShortVector::operator-=(const ShortVector& v) FORCEINLINE_VECTOR ShortVector& ShortVector::operator*=(float fl) { - x *= fl; - y *= fl; - z *= fl; - w *= fl; + x *= static_cast(fl); + y *= static_cast(fl); + z *= static_cast(fl); + w *= static_cast(fl); return *this; } @@ -744,10 +741,10 @@ FORCEINLINE_VECTOR ShortVector& ShortVector::operator/=(float fl) { Assert( fl != 0.0f ); float oofl = 1.0f / fl; - x *= oofl; - y *= oofl; - z *= oofl; - w *= oofl; + x *= static_cast(oofl); + y *= static_cast(oofl); + z *= static_cast(oofl); + w *= static_cast(oofl); return *this; } @@ -764,10 +761,10 @@ FORCEINLINE_VECTOR ShortVector& ShortVector::operator/=(const ShortVector& v) FORCEINLINE_VECTOR void ShortVectorMultiply( const ShortVector& src, float fl, ShortVector& res ) { Assert( IsFinite(fl) ); - res.x = src.x * fl; - res.y = src.y * fl; - res.z = src.z * fl; - res.w = src.w * fl; + res.x = src.x * static_cast(fl); + res.y = src.y * static_cast(fl); + res.z = src.z * static_cast(fl); + res.w = src.w * static_cast(fl); } FORCEINLINE_VECTOR ShortVector ShortVector::operator*(float fl) const diff --git a/public/vector2d.h b/public/vector2d.h index a3335c32..f18e0d7a 100644 --- a/public/vector2d.h +++ b/public/vector2d.h @@ -206,10 +206,8 @@ void Vector2DLerp(const Vector2D& src1, const Vector2D& src2, vec_t t, Vector2D& inline Vector2D::Vector2D(void) { -#ifdef _DEBUG // Initialize to NAN to catch errors x = y = VEC_T_NAN; -#endif } inline Vector2D::Vector2D(vec_t X, vec_t Y) diff --git a/public/vector4d.h b/public/vector4d.h index b95b367f..a1d5e459 100644 --- a/public/vector4d.h +++ b/public/vector4d.h @@ -219,10 +219,8 @@ void Vector4DLerp(Vector4D const& src1, Vector4D const& src2, vec_t t, Vector4D& inline Vector4D::Vector4D(void) { -#ifdef _DEBUG // Initialize to NAN to catch errors x = y = z = w = VEC_T_NAN; -#endif } inline Vector4D::Vector4D(vec_t X, vec_t Y, vec_t Z, vec_t W ) diff --git a/public/vgui_controls/MessageMap.h b/public/vgui_controls/MessageMap.h index ddee80cc..ed768a96 100644 --- a/public/vgui_controls/MessageMap.h +++ b/public/vgui_controls/MessageMap.h @@ -258,10 +258,10 @@ PanelMessageMap *FindOrAddPanelMessageMap( char const *className ); /////////////////////////////////////////////////////////////////////////////////////////////////////////// // no parameters -#define MAP_MESSAGE( type, name, func ) { name, (vgui::MessageFunc_t)(type::func), 0 } +#define MAP_MESSAGE( type, name, func ) { name, (vgui::MessageFunc_t)(&type::func), 0 } // implicit single parameter (params is the data store) -#define MAP_MESSAGE_PARAMS( type, name, func ) { name, (vgui::MessageFunc_t)(type::func), 1, vgui::DATATYPE_KEYVALUES, NULL } +#define MAP_MESSAGE_PARAMS( type, name, func ) { name, (vgui::MessageFunc_t)(&type::func), 1, vgui::DATATYPE_KEYVALUES, NULL } // single parameter #define MAP_MESSAGE_PTR( type, name, func, param1 ) { name, (vgui::MessageFunc_t)(&type::func), 1, vgui::DATATYPE_PTR, param1 } @@ -273,12 +273,12 @@ PanelMessageMap *FindOrAddPanelMessageMap( char const *className ); #define MAP_MESSAGE_CONSTWCHARPTR( type, name, func, param1) { name, (vgui::MessageFunc_t)(&type::func), 1, vgui::DATATYPE_CONSTWCHARPTR, param1 } // two parameters -#define MAP_MESSAGE_INT_INT( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)&type::func, 2, vgui::DATATYPE_INT, param1, vgui::DATATYPE_INT, param2 } -#define MAP_MESSAGE_PTR_INT( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)&type::func, 2, vgui::DATATYPE_PTR, param1, vgui::DATATYPE_INT, param2 } -#define MAP_MESSAGE_INT_CONSTCHARPTR( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)&type::func, 2, vgui::DATATYPE_INT, param1, vgui::DATATYPE_CONSTCHARPTR, param2 } -#define MAP_MESSAGE_PTR_CONSTCHARPTR( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)&type::func, 2, vgui::DATATYPE_PTR, param1, vgui::DATATYPE_CONSTCHARPTR, param2 } -#define MAP_MESSAGE_PTR_CONSTWCHARPTR( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)&type::func, 2, vgui::DATATYPE_PTR, param1, vgui::DATATYPE_CONSTWCHARPTR, param2 } -#define MAP_MESSAGE_CONSTCHARPTR_CONSTCHARPTR( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)&type::func, 2, vgui::DATATYPE_CONSTCHARPTR, param1, vgui::DATATYPE_CONSTCHARPTR, param2 } +#define MAP_MESSAGE_INT_INT( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)(&type::func), 2, vgui::DATATYPE_INT, param1, vgui::DATATYPE_INT, param2 } +#define MAP_MESSAGE_PTR_INT( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)(&type::func), 2, vgui::DATATYPE_PTR, param1, vgui::DATATYPE_INT, param2 } +#define MAP_MESSAGE_INT_CONSTCHARPTR( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)(&type::func), 2, vgui::DATATYPE_INT, param1, vgui::DATATYPE_CONSTCHARPTR, param2 } +#define MAP_MESSAGE_PTR_CONSTCHARPTR( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)(&type::func), 2, vgui::DATATYPE_PTR, param1, vgui::DATATYPE_CONSTCHARPTR, param2 } +#define MAP_MESSAGE_PTR_CONSTWCHARPTR( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)(&type::func), 2, vgui::DATATYPE_PTR, param1, vgui::DATATYPE_CONSTWCHARPTR, param2 } +#define MAP_MESSAGE_CONSTCHARPTR_CONSTCHARPTR( type, name, func, param1, param2 ) { name, (vgui::MessageFunc_t)(&type::func), 2, vgui::DATATYPE_CONSTCHARPTR, param1, vgui::DATATYPE_CONSTCHARPTR, param2 } // if more parameters are needed, just use MAP_MESSAGE_PARAMS() and pass the keyvalue set into the function diff --git a/public/vmatrix.cpp b/public/vmatrix.cpp index 19118554..6640a197 100644 --- a/public/vmatrix.cpp +++ b/public/vmatrix.cpp @@ -18,7 +18,9 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef _MSC_VER #pragma warning (disable : 4700) // local variable 'x' used without having been initialized +#endif // ------------------------------------------------------------------------------------------- // // Helper functions. diff --git a/public/vphysics/collision_set.h b/public/vphysics/collision_set.h index b5db3c79..249aef40 100644 --- a/public/vphysics/collision_set.h +++ b/public/vphysics/collision_set.h @@ -1,20 +1,20 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -// A set of collision rules -// NOTE: Defaults to all indices disabled -class IPhysicsCollisionSet -{ -public: - ~IPhysicsCollisionSet() {} - - virtual void EnableCollisions( int index0, int index1 ) = 0; - virtual void DisableCollisions( int index0, int index1 ) = 0; - - virtual bool ShouldCollide( int index0, int index1 ) = 0; -}; \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +// A set of collision rules +// NOTE: Defaults to all indices disabled +class IPhysicsCollisionSet +{ +public: + ~IPhysicsCollisionSet() {} + + virtual void EnableCollisions( int index0, int index1 ) = 0; + virtual void DisableCollisions( int index0, int index1 ) = 0; + + virtual bool ShouldCollide( int index0, int index1 ) = 0; +}; diff --git a/public/worldsize.h b/public/worldsize.h index e16522e2..4f9bba0c 100644 --- a/public/worldsize.h +++ b/public/worldsize.h @@ -1,42 +1,42 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// worldsize.h -- extent of world and resolution/size of coordinate messages used in engine - -#ifndef WORLDSIZE_H -#define WORLDSIZE_H -#pragma once - - -// These definitions must match the coordinate message sizes in coordsize.h - -// Following values should be +16384, -16384, +15/16, -15/16 -// NOTE THAT IF THIS GOES ANY BIGGER THEN DISK NODES/LEAVES CANNOT USE SHORTS TO STORE THE BOUNDS -#define MAX_COORD_INTEGER (16384) -#define MIN_COORD_INTEGER (-MAX_COORD_INTEGER) -#define MAX_COORD_FRACTION (1.0-(1.0/16.0)) -#define MIN_COORD_FRACTION (-1.0+(1.0/16.0)) - -#define MAX_COORD_FLOAT (16384.0f) -#define MIN_COORD_FLOAT (-MAX_COORD_FLOAT) - -// Width of the coord system, which is TOO BIG to send as a client/server coordinate value -#define COORD_EXTENT (2*MAX_COORD_INTEGER) - -// Maximum traceable distance ( assumes cubic world and trace from one corner to opposite ) -// COORD_EXTENT * sqrt(3) -#define MAX_TRACE_LENGTH ( 1.732050807569 * COORD_EXTENT ) - -// This value is the LONGEST possible range (limited by max valid coordinate number, not 2x) -#define MAX_COORD_RANGE (MAX_COORD_INTEGER) - -#define ASSERT_COORD( v ) Assert( (v.x>=MIN_COORD_INTEGER*2) && (v.x<=MAX_COORD_INTEGER*2) && \ - (v.y>=MIN_COORD_INTEGER*2) && (v.y<=MAX_COORD_INTEGER*2) && \ - (v.z>=MIN_COORD_INTEGER*2) && (v.z<=MAX_COORD_INTEGER*2) ); \ - - -#endif // WORLDSIZE_H \ No newline at end of file +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// worldsize.h -- extent of world and resolution/size of coordinate messages used in engine + +#ifndef WORLDSIZE_H +#define WORLDSIZE_H +#pragma once + + +// These definitions must match the coordinate message sizes in coordsize.h + +// Following values should be +16384, -16384, +15/16, -15/16 +// NOTE THAT IF THIS GOES ANY BIGGER THEN DISK NODES/LEAVES CANNOT USE SHORTS TO STORE THE BOUNDS +#define MAX_COORD_INTEGER (16384) +#define MIN_COORD_INTEGER (-MAX_COORD_INTEGER) +#define MAX_COORD_FRACTION (1.0-(1.0/16.0)) +#define MIN_COORD_FRACTION (-1.0+(1.0/16.0)) + +#define MAX_COORD_FLOAT (16384.0f) +#define MIN_COORD_FLOAT (-MAX_COORD_FLOAT) + +// Width of the coord system, which is TOO BIG to send as a client/server coordinate value +#define COORD_EXTENT (2*MAX_COORD_INTEGER) + +// Maximum traceable distance ( assumes cubic world and trace from one corner to opposite ) +// COORD_EXTENT * sqrt(3) +#define MAX_TRACE_LENGTH ( 1.732050807569 * COORD_EXTENT ) + +// This value is the LONGEST possible range (limited by max valid coordinate number, not 2x) +#define MAX_COORD_RANGE (MAX_COORD_INTEGER) + +#define ASSERT_COORD( v ) Assert( (v.x>=MIN_COORD_INTEGER*2) && (v.x<=MAX_COORD_INTEGER*2) && \ + (v.y>=MIN_COORD_INTEGER*2) && (v.y<=MAX_COORD_INTEGER*2) && \ + (v.z>=MIN_COORD_INTEGER*2) && (v.z<=MAX_COORD_INTEGER*2) ); \ + + +#endif // WORLDSIZE_H diff --git a/tier1/KeyValues.cpp b/tier1/KeyValues.cpp index 76d7da88..fea17654 100644 --- a/tier1/KeyValues.cpp +++ b/tier1/KeyValues.cpp @@ -373,7 +373,9 @@ int KeyValues::GetNameSymbol() const //----------------------------------------------------------------------------- // Purpose: Read a single token from buffer (0 terminated) //----------------------------------------------------------------------------- +#ifdef _MSC_VER #pragma warning (disable:4706) +#endif const char *KeyValues::ReadToken( CUtlBuffer &buf, bool &wasQuoted ) { wasQuoted = false; @@ -418,7 +420,7 @@ const char *KeyValues::ReadToken( CUtlBuffer &buf, bool &wasQuoted ) // read in the token until we hit a whitespace or a control character bool bReportedError = false; int nCount = 0; - while ( c = (const char*)buf.PeekGet( sizeof(char), 0 ) ) + while (( c = (const char*)buf.PeekGet( sizeof(char), 0 )) ) { // end of file if ( *c == 0 ) @@ -447,8 +449,9 @@ const char *KeyValues::ReadToken( CUtlBuffer &buf, bool &wasQuoted ) s_pTokenBuf[ nCount ] = 0; return s_pTokenBuf; } +#ifdef _MSC_VER #pragma warning (default:4706) - +#endif //----------------------------------------------------------------------------- @@ -466,8 +469,9 @@ void KeyValues::UsesEscapeSequences(bool state) bool KeyValues::LoadFromFile( IBaseFileSystem *filesystem, const char *resourceName, const char *pathID ) { Assert(filesystem); + #ifndef _LINUX Assert( IsXbox() || ( IsPC() && _heapchk() == _HEAPOK ) ); - + #endif FileHandle_t f = filesystem->Open(resourceName, "rb", pathID); if (!f) return false; @@ -1146,8 +1150,8 @@ const char *KeyValues::GetString( const char *keyName, const char *defaultValue const wchar_t *KeyValues::GetWString( const char *keyName, const wchar_t *defaultValue) { - KeyValues *dat = FindKey( keyName, false ); #ifdef _WIN32 + KeyValues *dat = FindKey( keyName, false ); if ( dat ) { wchar_t wbuf[64]; @@ -1215,7 +1219,7 @@ Color KeyValues::GetColor( const char *keyName ) } else if ( dat->m_iDataType == TYPE_FLOAT ) { - color[0] = dat->m_flValue; + color[0] = static_cast(dat->m_flValue); } else if ( dat->m_iDataType == TYPE_INT ) { diff --git a/tier1/NetAdr.cpp b/tier1/NetAdr.cpp index 603aef67..6700e32b 100644 --- a/tier1/NetAdr.cpp +++ b/tier1/NetAdr.cpp @@ -297,7 +297,7 @@ void netadr_t::SetFromSocket( int hSocket ) struct sockaddr address; int namelen = sizeof(address); - if ( getsockname( hSocket, (struct sockaddr *)&address, (int *)&namelen) == 0 ) + if ( getsockname( hSocket, (struct sockaddr *)&address, (socklen_t *)&namelen) == 0 ) { SetFromSockadr( &address ); } diff --git a/tier1/bitbuf.cpp b/tier1/bitbuf.cpp index 6e3c792d..d986d85c 100644 --- a/tier1/bitbuf.cpp +++ b/tier1/bitbuf.cpp @@ -330,7 +330,7 @@ void bf_write::WriteBitCoord (const float f) VPROF( "bf_write::WriteBitCoord" ); #endif int signbit = (f <= -COORD_RESOLUTION); - int intval = (int)abs(f); + int intval = abs(static_cast(f)); int fractval = abs((int)(f*COORD_DENOMINATOR)) & (COORD_DENOMINATOR-1); @@ -366,7 +366,8 @@ void bf_write::WriteBitFloat(float val) Assert(sizeof(long) == sizeof(float)); Assert(sizeof(float) == 4); - intVal = *((long*)&val); + void *v = &val; + intVal = *reinterpret_cast(v); WriteUBitLong( intVal, 32 ); } diff --git a/tier1/byteswap.cpp b/tier1/byteswap.cpp index 2415f3d9..344acfd1 100644 --- a/tier1/byteswap.cpp +++ b/tier1/byteswap.cpp @@ -102,7 +102,7 @@ template T LowLevelByteSwap( T input ) { T output = input; // To solve the "output may not have been initialized" warning. - for( int i = 0; i < sizeof(T); i++ ) + for( unsigned int i = 0; i < sizeof(T); i++ ) { ((unsigned char* )&output)[i] = ((unsigned char*)&input)[sizeof(T)-(i+1)]; } diff --git a/tier1/characterset.cpp b/tier1/characterset.cpp index 0fa3fced..3914ae0a 100644 --- a/tier1/characterset.cpp +++ b/tier1/characterset.cpp @@ -34,7 +34,7 @@ void CharacterSetBuild( characterset_t *pSetBuffer, const char *pszSetString ) while ( pszSetString[i] ) { - pSetBuffer->set[ pszSetString[i] ] = 1; + pSetBuffer->set[ static_cast(pszSetString[i]) ] = 1; i++; } diff --git a/tier1/generichash.cpp b/tier1/generichash.cpp index 54acefa3..7c07ec39 100644 --- a/tier1/generichash.cpp +++ b/tier1/generichash.cpp @@ -9,7 +9,14 @@ #include "tier0/basetypes.h" #include "tier0/platform.h" #include "generichash.h" + +#if defined _WIN32 && !defined __CTYPE_H_ #include +#endif + +#ifdef _LINUX +#include +#endif //----------------------------------------------------------------------------- // diff --git a/tier1/mempool.cpp b/tier1/mempool.cpp index ab9e1908..8251ba96 100644 --- a/tier1/mempool.cpp +++ b/tier1/mempool.cpp @@ -41,7 +41,7 @@ CMemoryPool::CMemoryPool(int blockSize, int numElements, int growMode, const cha } #endif - m_BlockSize = blockSize < sizeof(void*) ? sizeof(void*) : blockSize; + m_BlockSize = blockSize < static_cast(sizeof(void*)) ? sizeof(void*) : blockSize; m_BlocksPerBlob = numElements; m_PeakAlloc = 0; m_GrowMode = growMode; diff --git a/tier1/memstack.cpp b/tier1/memstack.cpp index d1ea4d4c..5e1a1008 100644 --- a/tier1/memstack.cpp +++ b/tier1/memstack.cpp @@ -37,16 +37,18 @@ inline T MemAlign( T val, unsigned alignment ) //----------------------------------------------------------------------------- CMemoryStack::CMemoryStack() - : m_pBase( NULL ), - m_pNextAlloc( NULL ), - m_pAllocLimit( NULL ), + : m_pNextAlloc( NULL ), m_pCommitLimit( NULL ), + m_pAllocLimit( NULL ), + m_pBase( NULL ), + m_maxSize( 0 ), +#if defined (_LINUX) + m_alignment( 16 ) +#elif defined(_WIN32) m_alignment( 16 ), -#if defined(_WIN32) m_commitSize( 0 ), - m_minCommit( 0 ), + m_minCommit( 0 ) #endif - m_maxSize( 0 ) { } diff --git a/tier1/strtools.cpp b/tier1/strtools.cpp index 9ecdf0db..374b7b67 100644 --- a/tier1/strtools.cpp +++ b/tier1/strtools.cpp @@ -183,7 +183,7 @@ char *_V_strstr(const char* file, int line, const char *s1, const char *search AssertValidStringPtr( s1 ); AssertValidStringPtr( search ); - // Casting the args to (char*) in order to get the overload of strstr() that returns a (char*) instead of (const char *). This is a changed brought about by VS2005. + // Casting the args to (char*) in order to get the overload of strstr() that returns a (char*) instead of (const char *). This is a change brought about by VS2005. return strstr( (char*)s1, (char*)search ); } @@ -829,52 +829,52 @@ char *V_pretifynum( int64 value ) } // Render quadrillions - if ( value >= 1000000000000 ) + if ( value >= 1000000000000LL ) { char *pchRender = out + V_strlen( out ); - V_snprintf( pchRender, 32, "%d,", value / 1000000000000 ); + V_snprintf( pchRender, 32, "%d,", value / 1000000000000LL ); } // Render trillions - if ( value >= 1000000000000 ) + if ( value >= 1000000000000LL ) { char *pchRender = out + V_strlen( out ); - V_snprintf( pchRender, 32, "%d,", value / 1000000000000 ); + V_snprintf( pchRender, 32, "%d,", value / 1000000000000LL ); } // Render billions - if ( value >= 1000000000 ) + if ( value >= 1000000000LL ) { char *pchRender = out + V_strlen( out ); - V_snprintf( pchRender, 32, "%d,", value / 1000000000 ); + V_snprintf( pchRender, 32, "%d,", value / 1000000000LL ); } // Render millions - if ( value >= 1000000 ) + if ( value >= 1000000LL ) { char *pchRender = out + V_strlen( out ); if ( value >= 1000000000 ) - V_snprintf( pchRender, 32, "%03d,", ( value / 1000000 ) % 1000 ); + V_snprintf( pchRender, 32, "%03d,", ( value / 1000000LL ) % 1000LL ); else - V_snprintf( pchRender, 32, "%d,", ( value / 1000000 ) % 1000 ); + V_snprintf( pchRender, 32, "%d,", ( value / 1000000LL ) % 1000LL ); } // Render thousands - if ( value >= 1000 ) + if ( value >= 1000LL ) { char *pchRender = out + V_strlen( out ); if ( value >= 1000000 ) - V_snprintf( pchRender, 32, "%03d,", ( value / 1000 ) % 1000 ); + V_snprintf( pchRender, 32, "%03d,", ( value / 1000LL ) % 1000LL ); else - V_snprintf( pchRender, 32, "%d,", ( value / 1000 ) % 1000 ); + V_snprintf( pchRender, 32, "%d,", ( value / 1000LL ) % 1000LL ); } // Render units char *pchRender = out + V_strlen( out ); - if ( value > 1000 ) - V_snprintf( pchRender, 32, "%03d", value % 1000 ); + if ( value > 1000LL ) + V_snprintf( pchRender, 32, "%03d", value % 1000LL ); else - V_snprintf( pchRender, 32, "%d", value % 1000 ); + V_snprintf( pchRender, 32, "%d", value % 1000LL ); return out; } @@ -1338,7 +1338,7 @@ bool V_ExtractFilePath (const char *path, char *dest, int destSize ) //----------------------------------------------------------------------------- void V_ExtractFileExtension( const char *path, char *dest, int destSize ) { - *dest = NULL; + *dest = '\0'; const char * extension = V_GetFileExtension( path ); if ( NULL != extension ) V_strncpy( dest, extension, destSize ); diff --git a/tier1/utlbuffer.cpp b/tier1/utlbuffer.cpp index deae90f8..cb10a3b9 100644 --- a/tier1/utlbuffer.cpp +++ b/tier1/utlbuffer.cpp @@ -6,7 +6,7 @@ // Serialization buffer //===========================================================================// -#ifndef _XBOX +#if !defined _XBOX && defined _MSC_VER #pragma warning (disable : 4514) #endif #include "utlbuffer.h" @@ -92,14 +92,14 @@ CUtlCStringConversion::CUtlCStringConversion( char nEscapeChar, const char *pDel memset( m_pConversion, 0x0, sizeof(m_pConversion) ); for ( int i = 0; i < nCount; ++i ) { - m_pConversion[ pArray[i].m_pReplacementString[0] ] = pArray[i].m_nActualChar; + m_pConversion[ static_cast(pArray[i].m_pReplacementString[0]) ] = pArray[i].m_nActualChar; } } // Finds a conversion for the passed-in string, returns length char CUtlCStringConversion::FindConversion( const char *pString, int *pLength ) { - char c = m_pConversion[ pString[0] ]; + char c = m_pConversion[ static_cast(pString[0]) ]; *pLength = (c != '\0') ? 1 : 0; return c; } @@ -122,7 +122,7 @@ CUtlCharConversion::CUtlCharConversion( char nEscapeChar, const char *pDelimiter for ( int i = 0; i < nCount; ++i ) { m_pList[i] = pArray[i].m_nActualChar; - ConversionInfo_t &info = m_pReplacements[ m_pList[i] ]; + ConversionInfo_t &info = m_pReplacements[ static_cast(m_pList[i]) ]; Assert( info.m_pReplacementString == 0 ); info.m_pReplacementString = pArray[i].m_pReplacementString; info.m_nLength = Q_strlen( info.m_pReplacementString ); @@ -158,12 +158,12 @@ int CUtlCharConversion::GetDelimiterLength() const //----------------------------------------------------------------------------- const char *CUtlCharConversion::GetConversionString( char c ) const { - return m_pReplacements[ c ].m_pReplacementString; + return m_pReplacements[ static_cast(c) ].m_pReplacementString; } int CUtlCharConversion::GetConversionLength( char c ) const { - return m_pReplacements[ c ].m_nLength; + return m_pReplacements[ static_cast(c) ].m_nLength; } int CUtlCharConversion::MaxConversionLength() const @@ -179,9 +179,9 @@ char CUtlCharConversion::FindConversion( const char *pString, int *pLength ) { for ( int i = 0; i < m_nCount; ++i ) { - if ( !Q_strcmp( pString, m_pReplacements[ m_pList[i] ].m_pReplacementString ) ) + if ( !Q_strcmp( pString, m_pReplacements[ static_cast(m_pList[i]) ].m_pReplacementString ) ) { - *pLength = m_pReplacements[ m_pList[i] ].m_nLength; + *pLength = m_pReplacements[ static_cast(m_pList[i]) ].m_nLength; return m_pList[i]; } } @@ -872,7 +872,9 @@ void CUtlBuffer::SeekGet( SeekType_t type, int offset ) // Parse... //----------------------------------------------------------------------------- +#ifdef _MSC_VER #pragma warning ( disable : 4706 ) +#endif int CUtlBuffer::VaScanf( const char* pFmt, va_list list ) { @@ -884,7 +886,7 @@ int CUtlBuffer::VaScanf( const char* pFmt, va_list list ) int nLength; char c; char* pEnd; - while ( c = *pFmt++ ) + while (( c = *pFmt++ )) { // Stop if we hit the end of the buffer if ( m_Get >= TellMaxPut() ) @@ -1043,7 +1045,9 @@ int CUtlBuffer::VaScanf( const char* pFmt, va_list list ) return numScanned; } +#ifdef _MSC_VER #pragma warning ( default : 4706 ) +#endif int CUtlBuffer::Scanf( const char* pFmt, ... ) { diff --git a/tier1/utlsymbol.cpp b/tier1/utlsymbol.cpp index 4c8e98fa..92b2f2a5 100644 --- a/tier1/utlsymbol.cpp +++ b/tier1/utlsymbol.cpp @@ -6,7 +6,7 @@ // $NoKeywords: $ //=============================================================================// -#ifndef _XBOX +#if !defined _XBOX && defined _MSC_VER #pragma warning (disable:4514) #endif @@ -300,7 +300,8 @@ FileNameHandle_t CUtlFilenameSymbolTable::FindOrAddFileName( char const *pFileNa handle.path = g_CountedStringPool.ReferenceStringHandle(basepath); handle.file = g_CountedStringPool.ReferenceStringHandle(filename ); - return *( FileNameHandle_t * )( &handle ); + void *h = &handle; + return *reinterpret_cast(h); } FileNameHandle_t CUtlFilenameSymbolTable::FindFileName( char const *pFileName ) @@ -329,10 +330,11 @@ FileNameHandle_t CUtlFilenameSymbolTable::FindFileName( char const *pFileName ) handle.path = g_CountedStringPool.FindStringHandle(basepath); handle.file = g_CountedStringPool.FindStringHandle(filename); - if( handle.path == NULL || handle.file == NULL ) + if( handle.path == 0 || handle.file == 0 ) return NULL; - return *( FileNameHandle_t * )( &handle ); + void *h = &handle; + return *reinterpret_cast(h); } //----------------------------------------------------------------------------- diff --git a/utils/demoinfo/demoinfo-2003.vcproj b/utils/demoinfo/demoinfo-2003.vcproj index 4212cfa0..10a46b88 100644 --- a/utils/demoinfo/demoinfo-2003.vcproj +++ b/utils/demoinfo/demoinfo-2003.vcproj @@ -37,19 +37,19 @@ CompileAs="0"/> + Outputs="..\..\..\bin\demoinfo.exe;..\..\..\bin\demoinfo.pdb"/> @@ -105,12 +105,12 @@ copy "$(TargetPath)" ..\..\..\game\bin\"$(TargetName)".pdb CompileAs="0"/> + Outputs="..\..\..\bin\demoinfo.exe;..\..\..\bin\demoinfo.pdb"/> + Name="Link Libraries" + Filter=""> + + + + + + + + - - - - - - - - diff --git a/utils/demoinfo/demoinfo-2005.vcproj b/utils/demoinfo/demoinfo-2005.vcproj index e9421391..82cd263a 100644 --- a/utils/demoinfo/demoinfo-2005.vcproj +++ b/utils/demoinfo/demoinfo-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="demoinfo" ProjectGUID="{4FE3FDCA-9571-44B3-A521-C81448434490}" + RootNamespace="demoinfo" > + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - diff --git a/utils/glview/glview-2003.vcproj b/utils/glview/glview-2003.vcproj index 53ab2fc3..65869660 100644 --- a/utils/glview/glview-2003.vcproj +++ b/utils/glview/glview-2003.vcproj @@ -37,21 +37,21 @@ CompileAs="0"/> + Outputs="..\..\..\bin\glview.exe;..\..\..\bin\glview.pdb"/> + Outputs="..\..\..\bin\glview.exe;..\..\..\bin\glview.pdb"/> + + + + + + + + + + - - - - - - - - - - diff --git a/utils/glview/glview-2005.vcproj b/utils/glview/glview-2005.vcproj index a701a1f4..18b810c6 100644 --- a/utils/glview/glview-2005.vcproj +++ b/utils/glview/glview-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="glview" ProjectGUID="{BD1604CA-F401-4C4B-821C-251F5AE157FE}" + RootNamespace="glview" > + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> - - diff --git a/utils/height2normal/height2normal-2005.vcproj b/utils/height2normal/height2normal-2005.vcproj index 5ab57e89..a3506458 100644 --- a/utils/height2normal/height2normal-2005.vcproj +++ b/utils/height2normal/height2normal-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="height2normal" ProjectGUID="{0FDD99E4-130F-493C-8202-4C0236CC47EA}" + RootNamespace="height2normal" > - - diff --git a/utils/hlmv/controlpanel.cpp b/utils/hlmv/controlpanel.cpp index 04efc749..94581b59 100644 --- a/utils/hlmv/controlpanel.cpp +++ b/utils/hlmv/controlpanel.cpp @@ -22,6 +22,11 @@ // email: mete@swissquake.ch // web: http://www.swissquake.ch/chumbalum-soft/ // +#include +#include +#include +#include +#include #include "ControlPanel.h" #include "ViewerSettings.h" #include "StudioModel.h" @@ -29,11 +34,6 @@ #include "vphysics/constraints.h" #include "physmesh.h" #include "sys.h" -#include -#include -#include -#include -#include #include "vphysics_interface.h" #include "UtlVector.h" #include "UtlSymbol.h" diff --git a/utils/hlmv/hlmv-2005.vcproj b/utils/hlmv/hlmv-2005.vcproj index c10527d2..bc5f2217 100644 --- a/utils/hlmv/hlmv-2005.vcproj +++ b/utils/hlmv/hlmv-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="hlmv" ProjectGUID="{F3704DBF-8055-413C-B027-399E5DBCA4DD}" + RootNamespace="hlmv" > #include #include +#include "mx/mx.h" #include "StudioModel.h" #include "vphysics/constraints.h" #include "physmesh.h" @@ -21,7 +22,6 @@ #include "ViewerSettings.h" #include "bone_setup.h" #include "UtlMemory.h" -#include "mx/mx.h" #include "filesystem.h" #include "IStudioRender.h" #include "materialsystem/IMaterialSystemHardwareConfig.h" diff --git a/utils/hlmv/viewersettings.cpp b/utils/hlmv/viewersettings.cpp index fdf8d25b..451ecb86 100644 --- a/utils/hlmv/viewersettings.cpp +++ b/utils/hlmv/viewersettings.cpp @@ -22,12 +22,12 @@ // email: mete@swissquake.ch // web: http://www.swissquake.ch/chumbalum-soft/ // -#include "ViewerSettings.h" -#include "studiomodel.h" #include #include #include -#include "windows.h" +#include +#include "ViewerSettings.h" +#include "studiomodel.h" ViewerSettings g_viewerSettings; diff --git a/utils/motionmapper/motionmapper-2003.vcproj b/utils/motionmapper/motionmapper-2003.vcproj index 1777b6b1..0248d36b 100644 --- a/utils/motionmapper/motionmapper-2003.vcproj +++ b/utils/motionmapper/motionmapper-2003.vcproj @@ -36,12 +36,12 @@ + Outputs="..\..\..\bin\motionmapper.exe;..\..\..\bin\motionmapper.pdb"/> + Outputs="..\..\..\bin\motionmapper.exe;..\..\..\bin\motionmapper.pdb"/> @@ -285,42 +286,38 @@ if exist "$(TargetDir)""$(TargetName)".map copy "$(Targ + Name="Link Libraries" + Filter=""> + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - diff --git a/utils/phonemeextractor/phonemeextractor-2005.vcproj b/utils/phonemeextractor/phonemeextractor-2005.vcproj index 49a15c96..99489bed 100644 --- a/utils/phonemeextractor/phonemeextractor-2005.vcproj +++ b/utils/phonemeextractor/phonemeextractor-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="phonemeextractor" ProjectGUID="{8A35F55B-B5C2-47A0-8C4A-5857A8E20385}" + RootNamespace="phonemeextractor" > - - - - - - - - - - - - - - - - @@ -270,18 +239,6 @@ RelativePath="..\common\filesystem_tools.cpp" > - - - - - - @@ -290,10 +247,6 @@ RelativePath=".\phonemeconverter.cpp" > - - @@ -314,6 +267,121 @@ /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -334,10 +402,6 @@ RelativePath="..\sapi51\Include\sapiddk.h" > - - @@ -395,70 +459,6 @@ > - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/utils/qc_eyes/QC_Eyes-2003.vcproj b/utils/qc_eyes/QC_Eyes-2003.vcproj index 0866fc2a..11289d4d 100644 --- a/utils/qc_eyes/QC_Eyes-2003.vcproj +++ b/utils/qc_eyes/QC_Eyes-2003.vcproj @@ -39,17 +39,18 @@ + Outputs="..\..\..\bin\qc_eyes.exe;..\..\..\bin\qc_eyes.pdb"/> @@ -108,17 +109,18 @@ copy "$(TargetPath)" ..\..\..\game\bin\"$(TargetName)".pdb + Outputs="..\..\..\bin\qc_eyes.exe;..\..\..\bin\qc_eyes.pdb"/> - - diff --git a/utils/qc_eyes/QC_Eyes-2005.vcproj b/utils/qc_eyes/QC_Eyes-2005.vcproj index 46903ba4..da1ad193 100644 --- a/utils/qc_eyes/QC_Eyes-2005.vcproj +++ b/utils/qc_eyes/QC_Eyes-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="QC_Eyes" ProjectGUID="{D373436F-7DBF-468B-A3E4-601FB8556544}" + RootNamespace="QC_Eyes" Keyword="MFCProj" > @@ -29,8 +30,8 @@ /> - - diff --git a/utils/qc_eyes/QC_Eyes.cpp b/utils/qc_eyes/QC_Eyes.cpp index 16e06ed5..b50bcc5f 100644 --- a/utils/qc_eyes/QC_Eyes.cpp +++ b/utils/qc_eyes/QC_Eyes.cpp @@ -43,17 +43,6 @@ BOOL CQC_EyesApp::InitInstance() { AfxEnableControlContainer(); - // Standard initialization - // If you are not using these features and wish to reduce the size - // of your final executable, you should remove from the following - // the specific initialization routines you do not need. - -#ifdef _AFXDLL - Enable3dControls(); // Call this when using MFC in a shared DLL -#else - Enable3dControlsStatic(); // Call this when linking to MFC statically -#endif - CQC_EyesDlg dlg; m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); diff --git a/utils/serverplugin_sample/serverplugin_bot.cpp b/utils/serverplugin_sample/serverplugin_bot.cpp index db58a68e..67e44db7 100644 --- a/utils/serverplugin_sample/serverplugin_bot.cpp +++ b/utils/serverplugin_sample/serverplugin_bot.cpp @@ -185,7 +185,7 @@ void Bot_UpdateDirection( CPluginBot *pBot ) { float angledelta = 15.0; - int maxtries = (int)360.0/angledelta; + int maxtries = static_cast(360.0 / angledelta); if ( pBot->m_bLastTurnToRight ) { diff --git a/utils/serverplugin_sample/serverplugin_empty-2003.vcproj b/utils/serverplugin_sample/serverplugin_empty-2003.vcproj index 51fe3cba..ab715fc7 100644 --- a/utils/serverplugin_sample/serverplugin_empty-2003.vcproj +++ b/utils/serverplugin_sample/serverplugin_empty-2003.vcproj @@ -40,10 +40,10 @@ CompileAs="0"/> + Outputs="..\..\..\bin\serverplugin_empty.dll"/> + Outputs="..\..\..\bin\serverplugin_empty.dll"/> + Outputs="..\..\..\bin\studiomdl.exe;..\..\..\bin\studiomdl.pdb"/> + Outputs="..\..\..\bin\studiomdl.exe;..\..\..\bin\studiomdl.pdb"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> @@ -116,18 +116,18 @@ copy "$(TargetPath)" ..\..\..\game\bin\"$(TargetName)".pdb CompileAs="0"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> - - diff --git a/utils/vbsp/vbsp-2005.vcproj b/utils/vbsp/vbsp-2005.vcproj index c4c884c0..01b8509d 100644 --- a/utils/vbsp/vbsp-2005.vcproj +++ b/utils/vbsp/vbsp-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="vbsp" ProjectGUID="{B78B6271-B19A-4CF6-926E-40B643548E23}" + RootNamespace="vbsp" > - - diff --git a/utils/vice/vice-2003.vcproj b/utils/vice/vice-2003.vcproj index da86e1d4..82592d9a 100644 --- a/utils/vice/vice-2003.vcproj +++ b/utils/vice/vice-2003.vcproj @@ -39,18 +39,18 @@ CompileAs="0"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> + Outputs="..\..\..\bin\$(TargetName).exe..\..\..\bin\$(TargetName).pdb"/> @@ -190,10 +190,6 @@ copy "$(TargetPath)" ..\..\..\game\bin\"$(TargetName)".pdb RelativePath="..\..\public\IceKey.H"> - - @@ -210,9 +206,6 @@ copy "$(TargetPath)" ..\..\..\game\bin\"$(TargetName)".pdb RelativePath="..\..\lib-vc7\public\vstdlib.lib"> - - diff --git a/utils/vice/vice-2005.vcproj b/utils/vice/vice-2005.vcproj index 4ab6caeb..99199e84 100644 --- a/utils/vice/vice-2005.vcproj +++ b/utils/vice/vice-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="vice" ProjectGUID="{3CE6E7A9-89EC-4304-8D72-5B602B4DBD09}" + RootNamespace="vice" > - - @@ -312,10 +308,6 @@ - - diff --git a/utils/vice/vice.cpp b/utils/vice/vice.cpp index 4cd1fe77..b24bf1ca 100644 --- a/utils/vice/vice.cpp +++ b/utils/vice/vice.cpp @@ -8,10 +8,13 @@ // vice.cpp : Defines the entry point for the console application. // +#undef GetCurrentDirectory + #include #include #include #include +#include "windows.h" #include "vstdlib/strtools.h" #include #include "conio.h" @@ -21,7 +24,6 @@ #include "tier0/dbg.h" #include "cmdlib.h" #include "vstdlib/icommandline.h" -#include "windows.h" #include #include @@ -29,8 +31,6 @@ #define FF_TRYAGAIN 1 #define FF_DONTPROCESS 2 -#undef GetCurrentDirectory - static bool g_NoPause = false; static bool g_Quiet = false; static bool g_Encrypt = false; diff --git a/utils/vprojtomake/makefilecreator.cpp b/utils/vprojtomake/makefilecreator.cpp index 2d2c9f55..33d6f1ba 100644 --- a/utils/vprojtomake/makefilecreator.cpp +++ b/utils/vprojtomake/makefilecreator.cpp @@ -1,279 +1,279 @@ -//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// -#ifdef _WIN32 -#include -#endif -#include "stdafx.h" -#include "makefilecreator.h" -#include "cmdlib.h" - -//----------------------------------------------------------------------------- -// Purpose: constructor -//----------------------------------------------------------------------------- -CMakefileCreator::CMakefileCreator() { - m_FileToBaseDirMapping.SetLessFunc( DefLessFunc( int ) ); -} - -//----------------------------------------------------------------------------- -// Purpose: destructor -//----------------------------------------------------------------------------- -CMakefileCreator::~CMakefileCreator() -{ -} - -void CMakefileCreator::CreateMakefiles( CVCProjConvert & proj ) -{ - m_ProjName = proj.GetName(); - m_BaseDir = proj.GetBaseDir(); - for ( int i = 0; i < proj.GetNumConfigurations(); i++ ) - { - m_FileToBaseDirMapping.RemoveAll(); - m_BuildDirectories.RemoveAll(); - m_BaseDirs.RemoveAll(); - - CreateMakefileName( proj.GetName().String(), proj.GetConfiguration(i) ); - CreateBaseDirs( proj.GetConfiguration(i) ); - FileHandle_t f = g_pFileSystem->Open( m_MakefileName.String(), "w+" ); - if ( !f ) - { - Warning( "failed to open %s for writing.\n", m_MakefileName.String() ); - continue; - } - OutputDirs(f); - OutputIncludes( proj.GetConfiguration(i), f ); - OutputObjLists( proj.GetConfiguration(i), f ); - OutputMainBuilder(f); - OutputBuildTarget(f); - g_pFileSystem->Close(f); - } -} - - -void CMakefileCreator::CreateBaseDirs( CVCProjConvert::CConfiguration & config ) -{ -// m_BaseDirs.Insert( "" ); - for ( int i = 0; i < config.GetNumFileNames(); i++ ) - { - if ( config.GetFileType(i) == CVCProjConvert::CConfiguration::FILE_SOURCE ) - { - char basedir[ MAX_PATH ]; - char fulldir[ MAX_PATH ]; - Q_snprintf( fulldir, sizeof(fulldir), "%s/%s", m_BaseDir.String(), config.GetFileName(i) ); - if ( Q_ExtractFilePath( fulldir, basedir, sizeof(basedir) ) ) - { - Q_FixSlashes( basedir ); - Q_StripTrailingSlash( basedir ); - int index = m_BaseDirs.Find( basedir ); - if ( index == m_BaseDirs.InvalidIndex() ) - { - index = m_BaseDirs.Insert( basedir ); - } - m_FileToBaseDirMapping.Insert(i, index ); - } - else - { - m_FileToBaseDirMapping.Insert(i, 0 ); - } - } - } -} - -void CMakefileCreator::CleanupFileName( char *name ) -{ - for ( int i = Q_strlen( name ) - 1; i >= 0; --i ) - { - if ( name[i] == ' ' || name[i] == '|' || name[i] == '\\' || name[i] == '/' || ( name[i] == '.' && i>=1 && name[i-1] == '.' )) - { - Q_memmove( &name[i], &name[i+1], Q_strlen( name ) - i - 1 ); - name[ Q_strlen( name ) - 1 ] = 0; - } - } -} - -void CMakefileCreator::CreateMakefileName( const char *projectName, CVCProjConvert::CConfiguration & config ) -{ - char makefileName[ MAX_PATH ]; - Q_snprintf( makefileName, sizeof(makefileName), "Makefile.%s_%s", projectName, config.GetName().String() ); - CleanupFileName( makefileName ); - m_MakefileName = makefileName; -} - -void CMakefileCreator::CreateDirectoryFriendlyName( const char *dirName, char *friendlyDirName, int friendlyDirNameSize ) -{ - Q_strncpy( friendlyDirName, dirName, friendlyDirNameSize ); - - int i; - for ( i = Q_strlen( friendlyDirName ) - 1; i >= 0; --i ) - { - if ( friendlyDirName[i] == '/' || friendlyDirName[i] == '\\' ) - { - friendlyDirName[i] = '_'; - } - if ( isalpha( friendlyDirName[i] ) ) - { - friendlyDirName[i] = toupper(friendlyDirName[i]); - } - if ( friendlyDirName[i] == '.' ) - { - Q_memmove( &friendlyDirName[i], &friendlyDirName[i+1], Q_strlen( friendlyDirName ) - i - 1 ); - friendlyDirName[ Q_strlen( friendlyDirName ) - 1 ] = 0; - } - } - - // strip any leading/trailing underscores - while ( friendlyDirName[0] == '_' && Q_strlen(friendlyDirName)>0 ) - { - Q_memmove( &friendlyDirName[0], &friendlyDirName[1], Q_strlen( friendlyDirName )- 1 ); - friendlyDirName[ Q_strlen( friendlyDirName ) - 1 ] = 0; - } - while ( Q_strlen(friendlyDirName)>0 && friendlyDirName[Q_strlen(friendlyDirName)-1] == '_' ) - { - friendlyDirName[ Q_strlen( friendlyDirName ) - 1 ] = 0; - } - - CleanupFileName( friendlyDirName ); -} - -void CMakefileCreator::CreateObjDirectoryFriendlyName ( char *name ) -{ -#ifdef _WIN32 - char *updir = "..\\"; -#else - char *updir = "../"; -#endif - - char *sep = Q_strstr( name, updir ); - while ( sep ) - { - Q_strcpy( sep, sep + strlen(updir) ); - sep = Q_strstr( sep, updir ); - } -} - -void CMakefileCreator::FileWrite( FileHandle_t f, const char *fmt, ... ) -{ - va_list args; - va_start(args, fmt); - char stringBuf[ 4096 ]; - Q_vsnprintf( stringBuf, sizeof(stringBuf), fmt, args ); - va_end(args); - g_pFileSystem->Write( stringBuf, Q_strlen(stringBuf), f ); -} - -void CMakefileCreator::OutputIncludes( CVCProjConvert::CConfiguration & config, FileHandle_t f ) -{ - FileWrite( f, "INCLUDES=" ); - for ( int i = 0; i < config.GetNumIncludes(); i++ ) - { - FileWrite( f, "-I%s ", config.GetInclude(i) ); - } - - for ( int i = 0; i < config.GetNumDefines(); i++ ) - { - FileWrite( f, "-D%s ", config.GetDefine(i) ); - } - FileWrite( f, "\n\n" ); -} - -void CMakefileCreator::OutputDirs( FileHandle_t f ) -{ - for ( int i = m_BaseDirs.First(); i != m_BaseDirs.InvalidIndex(); i = m_BaseDirs.Next(i) ) - { - const char *dirName = m_BaseDirs.GetElementName(i); - if ( !dirName || !Q_strlen(dirName) ) - { - dirName = m_BaseDir.String(); - } - - char friendlyDirName[ MAX_PATH ]; - CreateDirectoryFriendlyName( dirName, friendlyDirName, sizeof(friendlyDirName) ); - int dirLen = Q_strlen(friendlyDirName); - Q_strncat( friendlyDirName, "_SRC_DIR", sizeof(friendlyDirName), COPY_ALL_CHARACTERS ); - struct OutputDirMapping_t dirs; - dirs.m_SrcDir = friendlyDirName; - dirs.m_iBaseDirIndex = i; - friendlyDirName[ dirLen ] = 0; - Q_strncat( friendlyDirName, "_OBJ_DIR", sizeof(friendlyDirName), COPY_ALL_CHARACTERS ); - dirs.m_ObjDir = friendlyDirName; - friendlyDirName[ dirLen ] = 0; - Q_strncat( friendlyDirName, "_OBJS", sizeof(friendlyDirName), COPY_ALL_CHARACTERS ); - dirs.m_ObjName = friendlyDirName; - - char objDirName[ MAX_PATH ]; - Q_snprintf( objDirName, sizeof(objDirName) , "obj%c$(NAME)_$(ARCH)%c", CORRECT_PATH_SEPARATOR, CORRECT_PATH_SEPARATOR ); - Q_strncat( objDirName, dirName, sizeof(objDirName), COPY_ALL_CHARACTERS ); - CreateObjDirectoryFriendlyName( objDirName ); - dirs.m_ObjOutputDir = objDirName; - - m_BuildDirectories.AddToTail( dirs ); - - FileWrite( f, "%s=%s\n", dirs.m_SrcDir.String(), dirName ); - FileWrite( f, "%s=%s\n", dirs.m_ObjDir.String(), objDirName ); - } - FileWrite( f, "\n\n" ); -} - -void CMakefileCreator::OutputMainBuilder( FileHandle_t f ) -{ - int i; - FileWrite( f, "\n\nall: dirs $(NAME)_$(ARCH).$(SHLIBEXT)\n\n" ); - FileWrite( f, "dirs:\n" ); - for ( i = 0; i < m_BuildDirectories.Count(); i++ ) - { - FileWrite( f, "\t-mkdir -p $(%s)\n", m_BuildDirectories[i].m_ObjDir.String() ); - } - FileWrite( f, "\n\n" ); - - - FileWrite( f, "\n\n$(NAME)_$(ARCH).$(SHLIBEXT): " ); - for ( i = 0; i < m_BuildDirectories.Count(); i++ ) - { - FileWrite( f, "$(%s) ", m_BuildDirectories[i].m_ObjName.String() ); - } - FileWrite( f, "\n\t$(CLINK) $(SHLIBLDFLAGS) $(DEBUG) -o $(BUILD_DIR)/$@ " ); - for ( i = 0; i < m_BuildDirectories.Count(); i++ ) - { - FileWrite( f, "$(%s) ", m_BuildDirectories[i].m_ObjName.String() ); - } - FileWrite( f, "$(LDFLAGS) $(CPP_LIB)\n\n" ); -} - -void CMakefileCreator::OutputObjLists( CVCProjConvert::CConfiguration & config, FileHandle_t f ) -{ - for ( int buildDirIndex = 0; buildDirIndex < m_BuildDirectories.Count(); buildDirIndex++ ) - { - struct OutputDirMapping_t & dirs = m_BuildDirectories[buildDirIndex]; - FileWrite( f, "%s= \\\n", dirs.m_ObjName.String() ); - - for ( int j = m_FileToBaseDirMapping.FirstInorder(); j != m_FileToBaseDirMapping.InvalidIndex(); j = m_FileToBaseDirMapping.NextInorder(j) ) - { - if ( dirs.m_iBaseDirIndex == m_FileToBaseDirMapping[j] ) - { - char baseName[ MAX_PATH ]; - const char *fileName = config.GetFileName(m_FileToBaseDirMapping.Key(j)); - Q_FileBase( fileName, baseName, sizeof(baseName) ); - Q_SetExtension( baseName, ".o", sizeof(baseName) ); - - FileWrite( f, "\t$(%s)/%s \\\n", dirs.m_ObjDir.String(), baseName ); - } - } - FileWrite( f, "\n\n" ); - } - -} - -void CMakefileCreator::OutputBuildTarget( FileHandle_t f ) -{ - for( int i = 0; i < m_BuildDirectories.Count(); i++ ) - { - struct OutputDirMapping_t & dirs = m_BuildDirectories[i]; - FileWrite( f, "$(%s)/%%.o: $(%s)/%%.cpp\n", dirs.m_ObjDir.String(), dirs.m_SrcDir.String() ); - FileWrite( f, "\t$(DO_CC)\n\n"); - } - -} - +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifdef _WIN32 +#include +#endif +#include "stdafx.h" +#include "makefilecreator.h" +#include "cmdlib.h" + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +CMakefileCreator::CMakefileCreator() { + m_FileToBaseDirMapping.SetLessFunc( DefLessFunc( int ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: destructor +//----------------------------------------------------------------------------- +CMakefileCreator::~CMakefileCreator() +{ +} + +void CMakefileCreator::CreateMakefiles( CVCProjConvert & proj ) +{ + m_ProjName = proj.GetName(); + m_BaseDir = proj.GetBaseDir(); + for ( int i = 0; i < proj.GetNumConfigurations(); i++ ) + { + m_FileToBaseDirMapping.RemoveAll(); + m_BuildDirectories.RemoveAll(); + m_BaseDirs.RemoveAll(); + + CreateMakefileName( proj.GetName().String(), proj.GetConfiguration(i) ); + CreateBaseDirs( proj.GetConfiguration(i) ); + FileHandle_t f = g_pFileSystem->Open( m_MakefileName.String(), "w+" ); + if ( !f ) + { + Warning( "failed to open %s for writing.\n", m_MakefileName.String() ); + continue; + } + OutputDirs(f); + OutputIncludes( proj.GetConfiguration(i), f ); + OutputObjLists( proj.GetConfiguration(i), f ); + OutputMainBuilder(f); + OutputBuildTarget(f); + g_pFileSystem->Close(f); + } +} + + +void CMakefileCreator::CreateBaseDirs( CVCProjConvert::CConfiguration & config ) +{ +// m_BaseDirs.Insert( "" ); + for ( int i = 0; i < config.GetNumFileNames(); i++ ) + { + if ( config.GetFileType(i) == CVCProjConvert::CConfiguration::FILE_SOURCE ) + { + char basedir[ MAX_PATH ]; + char fulldir[ MAX_PATH ]; + Q_snprintf( fulldir, sizeof(fulldir), "%s/%s", m_BaseDir.String(), config.GetFileName(i) ); + if ( Q_ExtractFilePath( fulldir, basedir, sizeof(basedir) ) ) + { + Q_FixSlashes( basedir ); + Q_StripTrailingSlash( basedir ); + int index = m_BaseDirs.Find( basedir ); + if ( index == m_BaseDirs.InvalidIndex() ) + { + index = m_BaseDirs.Insert( basedir ); + } + m_FileToBaseDirMapping.Insert(i, index ); + } + else + { + m_FileToBaseDirMapping.Insert(i, 0 ); + } + } + } +} + +void CMakefileCreator::CleanupFileName( char *name ) +{ + for ( int i = Q_strlen( name ) - 1; i >= 0; --i ) + { + if ( name[i] == ' ' || name[i] == '|' || name[i] == '\\' || name[i] == '/' || ( name[i] == '.' && i>=1 && name[i-1] == '.' )) + { + Q_memmove( &name[i], &name[i+1], Q_strlen( name ) - i - 1 ); + name[ Q_strlen( name ) - 1 ] = 0; + } + } +} + +void CMakefileCreator::CreateMakefileName( const char *projectName, CVCProjConvert::CConfiguration & config ) +{ + char makefileName[ MAX_PATH ]; + Q_snprintf( makefileName, sizeof(makefileName), "Makefile.%s_%s", projectName, config.GetName().String() ); + CleanupFileName( makefileName ); + m_MakefileName = makefileName; +} + +void CMakefileCreator::CreateDirectoryFriendlyName( const char *dirName, char *friendlyDirName, int friendlyDirNameSize ) +{ + Q_strncpy( friendlyDirName, dirName, friendlyDirNameSize ); + + int i; + for ( i = Q_strlen( friendlyDirName ) - 1; i >= 0; --i ) + { + if ( friendlyDirName[i] == '/' || friendlyDirName[i] == '\\' ) + { + friendlyDirName[i] = '_'; + } + if ( isalpha( friendlyDirName[i] ) ) + { + friendlyDirName[i] = toupper(friendlyDirName[i]); + } + if ( friendlyDirName[i] == '.' ) + { + Q_memmove( &friendlyDirName[i], &friendlyDirName[i+1], Q_strlen( friendlyDirName ) - i - 1 ); + friendlyDirName[ Q_strlen( friendlyDirName ) - 1 ] = 0; + } + } + + // strip any leading/trailing underscores + while ( friendlyDirName[0] == '_' && Q_strlen(friendlyDirName)>0 ) + { + Q_memmove( &friendlyDirName[0], &friendlyDirName[1], Q_strlen( friendlyDirName )- 1 ); + friendlyDirName[ Q_strlen( friendlyDirName ) - 1 ] = 0; + } + while ( Q_strlen(friendlyDirName)>0 && friendlyDirName[Q_strlen(friendlyDirName)-1] == '_' ) + { + friendlyDirName[ Q_strlen( friendlyDirName ) - 1 ] = 0; + } + + CleanupFileName( friendlyDirName ); +} + +void CMakefileCreator::CreateObjDirectoryFriendlyName ( char *name ) +{ +#ifdef _WIN32 + char *updir = "..\\"; +#else + char *updir = "../"; +#endif + + char *sep = Q_strstr( name, updir ); + while ( sep ) + { + Q_strcpy( sep, sep + strlen(updir) ); + sep = Q_strstr( sep, updir ); + } +} + +void CMakefileCreator::FileWrite( FileHandle_t f, const char *fmt, ... ) +{ + va_list args; + va_start(args, fmt); + char stringBuf[ 4096 ]; + Q_vsnprintf( stringBuf, sizeof(stringBuf), fmt, args ); + va_end(args); + g_pFileSystem->Write( stringBuf, Q_strlen(stringBuf), f ); +} + +void CMakefileCreator::OutputIncludes( CVCProjConvert::CConfiguration & config, FileHandle_t f ) +{ + FileWrite( f, "INCLUDES=" ); + for ( int i = 0; i < config.GetNumIncludes(); i++ ) + { + FileWrite( f, " -I%s", config.GetInclude(i) ); + } + + for ( int i = 0; i < config.GetNumDefines(); i++ ) + { + FileWrite( f, " -D%s", config.GetDefine(i) ); + } + FileWrite( f, "\n\n" ); +} + +void CMakefileCreator::OutputDirs( FileHandle_t f ) +{ + for ( int i = m_BaseDirs.First(); i != m_BaseDirs.InvalidIndex(); i = m_BaseDirs.Next(i) ) + { + const char *dirName = m_BaseDirs.GetElementName(i); + if ( !dirName || !Q_strlen(dirName) ) + { + dirName = m_BaseDir.String(); + } + + char friendlyDirName[ MAX_PATH ]; + CreateDirectoryFriendlyName( dirName, friendlyDirName, sizeof(friendlyDirName) ); + int dirLen = Q_strlen(friendlyDirName); + Q_strncat( friendlyDirName, "_SRC_DIR", sizeof(friendlyDirName), COPY_ALL_CHARACTERS ); + struct OutputDirMapping_t dirs; + dirs.m_SrcDir = friendlyDirName; + dirs.m_iBaseDirIndex = i; + friendlyDirName[ dirLen ] = 0; + Q_strncat( friendlyDirName, "_OBJ_DIR", sizeof(friendlyDirName), COPY_ALL_CHARACTERS ); + dirs.m_ObjDir = friendlyDirName; + friendlyDirName[ dirLen ] = 0; + Q_strncat( friendlyDirName, "_OBJS", sizeof(friendlyDirName), COPY_ALL_CHARACTERS ); + dirs.m_ObjName = friendlyDirName; + + char objDirName[ MAX_PATH ]; + Q_snprintf( objDirName, sizeof(objDirName) , "obj%c$(NAME)_$(ARCH)%c", CORRECT_PATH_SEPARATOR, CORRECT_PATH_SEPARATOR ); + Q_strncat( objDirName, dirName, sizeof(objDirName), COPY_ALL_CHARACTERS ); + CreateObjDirectoryFriendlyName( objDirName ); + dirs.m_ObjOutputDir = objDirName; + + m_BuildDirectories.AddToTail( dirs ); + + FileWrite( f, "%s=%s\n", dirs.m_SrcDir.String(), dirName ); + FileWrite( f, "%s=%s\n", dirs.m_ObjDir.String(), objDirName ); + } + FileWrite( f, "\n\n" ); +} + +void CMakefileCreator::OutputMainBuilder( FileHandle_t f ) +{ + int i; + FileWrite( f, "\n\nall: dirs $(NAME)_$(ARCH).$(SHLIBEXT)\n\n" ); + FileWrite( f, "dirs:\n" ); + for ( i = 0; i < m_BuildDirectories.Count(); i++ ) + { + FileWrite( f, "\t-mkdir -p $(%s)\n", m_BuildDirectories[i].m_ObjDir.String() ); + } + FileWrite( f, "\n\n" ); + + + FileWrite( f, "\n\n$(NAME)_$(ARCH).$(SHLIBEXT): " ); + for ( i = 0; i < m_BuildDirectories.Count(); i++ ) + { + FileWrite( f, "$(%s) ", m_BuildDirectories[i].m_ObjName.String() ); + } + FileWrite( f, "\n\t$(CLINK) $(SHLIBLDFLAGS) $(DEBUG) -o $(BUILD_DIR)/$@ " ); + for ( i = 0; i < m_BuildDirectories.Count(); i++ ) + { + FileWrite( f, "$(%s) ", m_BuildDirectories[i].m_ObjName.String() ); + } + FileWrite( f, "$(LDFLAGS) $(CPP_LIB)\n\n" ); +} + +void CMakefileCreator::OutputObjLists( CVCProjConvert::CConfiguration & config, FileHandle_t f ) +{ + for ( int buildDirIndex = 0; buildDirIndex < m_BuildDirectories.Count(); buildDirIndex++ ) + { + struct OutputDirMapping_t & dirs = m_BuildDirectories[buildDirIndex]; + FileWrite( f, "%s= \\\n", dirs.m_ObjName.String() ); + + for ( int j = m_FileToBaseDirMapping.FirstInorder(); j != m_FileToBaseDirMapping.InvalidIndex(); j = m_FileToBaseDirMapping.NextInorder(j) ) + { + if ( dirs.m_iBaseDirIndex == m_FileToBaseDirMapping[j] ) + { + char baseName[ MAX_PATH ]; + const char *fileName = config.GetFileName(m_FileToBaseDirMapping.Key(j)); + Q_FileBase( fileName, baseName, sizeof(baseName) ); + Q_SetExtension( baseName, ".o", sizeof(baseName) ); + + FileWrite( f, "\t$(%s)/%s \\\n", dirs.m_ObjDir.String(), baseName ); + } + } + FileWrite( f, "\n\n" ); + } + +} + +void CMakefileCreator::OutputBuildTarget( FileHandle_t f ) +{ + for( int i = 0; i < m_BuildDirectories.Count(); i++ ) + { + struct OutputDirMapping_t & dirs = m_BuildDirectories[i]; + FileWrite( f, "$(%s)/%%.o: $(%s)/%%.cpp\n", dirs.m_ObjDir.String(), dirs.m_SrcDir.String() ); + FileWrite( f, "\t$(DO_CC)\n\n"); + } + +} + diff --git a/utils/vprojtomake/vcprojconvert.cpp b/utils/vprojtomake/vcprojconvert.cpp index 4b8ca511..1e016ee2 100644 --- a/utils/vprojtomake/vcprojconvert.cpp +++ b/utils/vprojtomake/vcprojconvert.cpp @@ -163,17 +163,17 @@ bool CVCProjConvert::LoadProject( const char *project ) char* message = XMLString::transcode(toCatch.getMessage()); Error( "Exception message is: %s\n", message ); XMLString::release(&message); - return; + return false; } catch (const DOMException& toCatch) { char* message = XMLString::transcode(toCatch.msg); Error( "Exception message is: %s\n", message ); XMLString::release(&message); - return; + return false; } catch (...) { Error( "Unexpected Exception \n" ); - return; + return false; } DOMDocument *pXMLDoc = parser->getDocument(); @@ -263,7 +263,7 @@ CUtlSymbol CVCProjConvert::GetXMLAttribValue( IXMLDOMElement *p, const char *att CUtlSymbol name( static_cast( _bstr_t( vtValue.bstrVal ) ) ); ::SysFreeString(vtValue.bstrVal); #elif _LINUX - const XMLCh *xAttrib = XMLString::transcode( attribName ); + XMLCh *xAttrib = XMLString::transcode( attribName ); const XMLCh *value = p->getAttribute( xAttrib ); if ( value == NULL ) { @@ -346,7 +346,7 @@ bool CVCProjConvert::ExtractProjectName( IXMLDOMDocument *pDoc ) DOMNode *node = nodes->item(0); if ( node ) { - m_Name = GetXMLAttribValue( node, "Name" ); + m_Name = GetXMLAttribValue( static_cast(node), "Name" ); } } } @@ -401,13 +401,13 @@ bool CVCProjConvert::ExtractConfigurations( IXMLDOMDocument *pDoc ) DOMNode *node = nodes->item(i); if (node) { - CUtlSymbol configName = GetXMLAttribValue( node, "Name" ); + CUtlSymbol configName = GetXMLAttribValue( static_cast(node), "Name" ); if ( configName.IsValid() ) { int newIndex = m_Configurations.AddToTail(); CConfiguration & config = m_Configurations[newIndex]; config.SetName( configName ); - ExtractIncludes( node, config ); + ExtractIncludes( static_cast(node), config ); } } } @@ -501,10 +501,10 @@ bool CVCProjConvert::ExtractIncludes( IXMLDOMElement *pDoc, CConfiguration & con DOMNode *node = nodes->item(i); if (node) { - CUtlSymbol toolName = GetXMLAttribValue( node, "Name" ); + CUtlSymbol toolName = GetXMLAttribValue( static_cast(node), "Name" ); if ( toolName == "VCCLCompilerTool" ) { - CUtlSymbol defines = GetXMLAttribValue( node, "PreprocessorDefinitions" ); + CUtlSymbol defines = GetXMLAttribValue( static_cast(node), "PreprocessorDefinitions" ); char *str = (char *)_alloca( Q_strlen( defines.String() + 1 )); Assert( str ); Q_strcpy( str, defines.String() ); @@ -529,7 +529,7 @@ bool CVCProjConvert::ExtractIncludes( IXMLDOMElement *pDoc, CConfiguration & con config.AddDefine( curpos ); } - CUtlSymbol includes = GetXMLAttribValue( node, "AdditionalIncludeDirectories" ); + CUtlSymbol includes = GetXMLAttribValue( static_cast(node), "AdditionalIncludeDirectories" ); char *str2 = (char *)_alloca( Q_strlen( includes.String() + 1 )); Assert( str2 ); Q_strcpy( str2, includes.String() ); @@ -614,8 +614,8 @@ bool CVCProjConvert::IterateFileConfigurations( IXMLDOMElement *pFile, CUtlSymbo DOMNode *node = nodes->item(i); if (node) { - CUtlSymbol configName = GetXMLAttribValue( node, "Name"); - CUtlSymbol excluded = GetXMLAttribValue( node ,"ExcludedFromBuild"); + CUtlSymbol configName = GetXMLAttribValue( static_cast(node), "Name"); + CUtlSymbol excluded = GetXMLAttribValue( static_cast(node) ,"ExcludedFromBuild"); if ( configName.IsValid() && excluded.IsValid() ) { int index = FindConfiguration( configName ); @@ -683,7 +683,7 @@ bool CVCProjConvert::ExtractFiles( IXMLDOMDocument *pDoc ) DOMNode *node = nodes->item(i); if (node) { - CUtlSymbol fileName = GetXMLAttribValue(node,"RelativePath"); + CUtlSymbol fileName = GetXMLAttribValue(static_cast(node),"RelativePath"); if ( fileName.IsValid() ) { char fixedFileName[ MAX_PATH ]; @@ -702,7 +702,7 @@ bool CVCProjConvert::ExtractFiles( IXMLDOMDocument *pDoc ) CConfiguration & config = m_Configurations[i]; config.InsertFile( fileEntry ); } - IterateFileConfigurations( node, fixedFileName ); // now remove the excluded ones + IterateFileConfigurations( static_cast(node), fixedFileName ); // now remove the excluded ones } } }//for diff --git a/utils/vprojtomake/vprojtomake-2003.vcproj b/utils/vprojtomake/vprojtomake-2003.vcproj index 4ca9044a..8fa830c4 100644 --- a/utils/vprojtomake/vprojtomake-2003.vcproj +++ b/utils/vprojtomake/vprojtomake-2003.vcproj @@ -41,10 +41,10 @@ CompileAs="0"/> + Outputs="..\..\..\bin\vcpm.exe"/> @@ -110,10 +110,10 @@ if exist "$(TargetPath)" copy "$(TargetPath)" ..\..\..\game\ CompileAs="0"/> + Outputs="..\..\..\bin\vcpm.exe"/> + + @@ -400,24 +403,21 @@ if exist "$(TargetPath)" copy "$(TargetPath)" ..\..\..\game\ + Name="Link Libraries" + Filter=""> + + + + + + + + - - - - - - - - - - diff --git a/utils/vprojtomake/vprojtomake-2005.vcproj b/utils/vprojtomake/vprojtomake-2005.vcproj index 2ee442a3..2b1da322 100644 --- a/utils/vprojtomake/vprojtomake-2005.vcproj +++ b/utils/vprojtomake/vprojtomake-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="vprojtomake" ProjectGUID="{EA55446E-BC04-491C-A9F0-605DFCBB213A}" + RootNamespace="vprojtomake" > + + @@ -513,66 +518,61 @@ + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - diff --git a/utils/vrad/vrad-2003.vcproj b/utils/vrad/vrad-2003.vcproj index e77d4071..14dd6f4d 100644 --- a/utils/vrad/vrad-2003.vcproj +++ b/utils/vrad/vrad-2003.vcproj @@ -43,16 +43,16 @@ + Outputs="..\..\..\bin\$(TargetName).dll;..\..\..\bin\$(TargetName).pdb"/> + Outputs="..\..\..\bin\$(TargetName).dll;..\..\..\bin\$(TargetName).pdb"/> - - diff --git a/utils/vrad/vrad-2005.vcproj b/utils/vrad/vrad-2005.vcproj index 1abf42a2..cb88f69a 100644 --- a/utils/vrad/vrad-2005.vcproj +++ b/utils/vrad/vrad-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="vrad" ProjectGUID="{FC0F5DE3-F09F-4EF6-98F9-BA762FFF268D}" + RootNamespace="vrad" > - - diff --git a/utils/vrad_launcher/vrad_launcher-2003.vcproj b/utils/vrad_launcher/vrad_launcher-2003.vcproj index b644efc8..ebaff50a 100644 --- a/utils/vrad_launcher/vrad_launcher-2003.vcproj +++ b/utils/vrad_launcher/vrad_launcher-2003.vcproj @@ -49,16 +49,16 @@ CompileAs="0"/> + Outputs="..\..\..\bin\$(TargetName).exe"/> + Outputs="..\..\..\bin\$(TargetName).exe"/> + Name="Link Libraries" + Filter=""> + + + + + + - - - - - - diff --git a/utils/vrad_launcher/vrad_launcher-2005.vcproj b/utils/vrad_launcher/vrad_launcher-2005.vcproj index 6f368a5f..aa653550 100644 --- a/utils/vrad_launcher/vrad_launcher-2005.vcproj +++ b/utils/vrad_launcher/vrad_launcher-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="vrad_launcher" ProjectGUID="{914F19DF-64EC-4E7D-8B01-76477BF06479}" + RootNamespace="vrad_launcher" > + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - diff --git a/utils/vtf2tga/vtf2tga-2003.vcproj b/utils/vtf2tga/vtf2tga-2003.vcproj index 24784200..f493f989 100644 --- a/utils/vtf2tga/vtf2tga-2003.vcproj +++ b/utils/vtf2tga/vtf2tga-2003.vcproj @@ -49,18 +49,18 @@ CompileAs="0"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> - - diff --git a/utils/vtf2tga/vtf2tga-2005.vcproj b/utils/vtf2tga/vtf2tga-2005.vcproj index f1f687f3..a971cfa4 100644 --- a/utils/vtf2tga/vtf2tga-2005.vcproj +++ b/utils/vtf2tga/vtf2tga-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="vtf2tga" ProjectGUID="{2A1F656C-053C-46A4-AE33-304C1D865E0C}" + RootNamespace="vtf2tga" > - - diff --git a/utils/vtfdiff/vtfdiff-2003.vcproj b/utils/vtfdiff/vtfdiff-2003.vcproj index 96ae5ed3..7392124c 100644 --- a/utils/vtfdiff/vtfdiff-2003.vcproj +++ b/utils/vtfdiff/vtfdiff-2003.vcproj @@ -40,18 +40,18 @@ CompileAs="0"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> + Outputs="..\..\..\bin\$(TargetName).exe;..\..\..\bin\$(TargetName).pdb"/> @@ -169,14 +169,6 @@ copy "$(TargetPath)" ..\..\..\game\bin\"$(TargetName)".pdb RelativePath="vtfdiff.cpp"> - - - - diff --git a/utils/vtfdiff/vtfdiff-2005.vcproj b/utils/vtfdiff/vtfdiff-2005.vcproj index 9afc39c3..1c0492c2 100644 --- a/utils/vtfdiff/vtfdiff-2005.vcproj +++ b/utils/vtfdiff/vtfdiff-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="vtfdiff" ProjectGUID="{0A368DE7-D34A-48D3-B517-996BFF2D0D5D}" + RootNamespace="vtfdiff" > - - - - diff --git a/utils/vvis/mpivis.cpp b/utils/vvis/mpivis.cpp index 66f15808..39c802ee 100644 --- a/utils/vvis/mpivis.cpp +++ b/utils/vvis/mpivis.cpp @@ -535,7 +535,7 @@ void RunMPIPortalFlow() g_PortalMCAddr.ip[3] = (unsigned char)RandomInt( 3, 255 ); g_pPortalMCSocket = CreateIPSocket(); - int i = 0; + int i; for ( i=0; i < 5; i++ ) { if ( g_pPortalMCSocket->BindToAny( randomStream.RandomInt( 20000, 30000 ) ) ) diff --git a/utils/vvis/vvis-2003.vcproj b/utils/vvis/vvis-2003.vcproj index 9441d699..58cff9d7 100644 --- a/utils/vvis/vvis-2003.vcproj +++ b/utils/vvis/vvis-2003.vcproj @@ -44,16 +44,16 @@ + Outputs="..\..\..\bin\$(TargetName).dll;..\..\..\bin\$(TargetName).pdb"/> + Outputs="..\..\..\bin\$(TargetName).dll;..\..\..\bin\$(TargetName).pdb"/> - - + RelativePath="..\..\lib-vc7\public\mathlib.lib"> - - - - - - + RelativePath="..\..\lib-vc7\public\tier0.lib"> + RelativePath="..\..\lib-vc7\public\tier1.lib"> + RelativePath="..\..\lib-vc7\public\tier2.lib"> - - - - - - + RelativePath="..\..\lib-vc7\public\vmpi.lib"> - - - - - - + RelativePath="..\..\lib-vc7\public\vstdlib.lib"> diff --git a/utils/vvis/vvis-2005.vcproj b/utils/vvis/vvis-2005.vcproj index f1e3f399..1ad51bbe 100644 --- a/utils/vvis/vvis-2005.vcproj +++ b/utils/vvis/vvis-2005.vcproj @@ -30,8 +30,8 @@ - - diff --git a/utils/vvis_launcher/vvis_launcher-2003.vcproj b/utils/vvis_launcher/vvis_launcher-2003.vcproj index 846749ae..df046ead 100644 --- a/utils/vvis_launcher/vvis_launcher-2003.vcproj +++ b/utils/vvis_launcher/vvis_launcher-2003.vcproj @@ -41,15 +41,16 @@ CompileAs="0"/> + Outputs="..\..\..\bin\$(TargetName).exe"/> + Outputs="..\..\..\bin\$(TargetName).exe"/> @@ -204,21 +205,18 @@ if exist "$(TargetPath)" copy "$(TargetPath)" ..\..\..\game\ + Name="Link Libraries" + Filter=""> + + + + + + - - - - - - - - diff --git a/utils/vvis_launcher/vvis_launcher-2005.vcproj b/utils/vvis_launcher/vvis_launcher-2005.vcproj index e6a93c0e..6b6cd1cf 100644 --- a/utils/vvis_launcher/vvis_launcher-2005.vcproj +++ b/utils/vvis_launcher/vvis_launcher-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="vvis_launcher" ProjectGUID="{4C0B9915-E8FF-4089-8927-FC934BFC1E4A}" + RootNamespace="vvis_launcher" > + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - diff --git a/utils/xwad/goldsrc_standin.h b/utils/xwad/goldsrc_standin.h index a2ea5e72..842a314e 100644 --- a/utils/xwad/goldsrc_standin.h +++ b/utils/xwad/goldsrc_standin.h @@ -10,6 +10,14 @@ #pragma once #endif +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + + #pragma warning(disable : 4996) // functions declared deprecated +#endif + typedef float vec_t; typedef float vec3_t[3]; diff --git a/utils/xwad/wadlib.h b/utils/xwad/wadlib.h index e73505bc..3d4bc237 100644 --- a/utils/xwad/wadlib.h +++ b/utils/xwad/wadlib.h @@ -12,6 +12,14 @@ // wad reading // +#if defined _MSC_VER && _MSC_VER >= 1400 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + + #pragma warning(disable : 4996) // functions declared deprecated +#endif + #define CMP_NONE 0 #define CMP_LZSS 1 diff --git a/utils/xwad/xwad-2003.vcproj b/utils/xwad/xwad-2003.vcproj index 2c6d37b6..4d5a6cd1 100644 --- a/utils/xwad/xwad-2003.vcproj +++ b/utils/xwad/xwad-2003.vcproj @@ -26,7 +26,7 @@ Optimization="0" PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE" BasicRuntimeChecks="3" - RuntimeLibrary="5" + RuntimeLibrary="1" UsePrecompiledHeader="2" PrecompiledHeaderFile=".\Debug/xwad.pch" AssemblerListingLocation=".\Debug/" @@ -37,17 +37,18 @@ DebugInformationFormat="4"/> + Outputs="..\..\..\bin\xwad.exe;..\..\..\bin\xwad.pdb"/> + Outputs="..\..\..\bin\xwad.exe;..\..\..\bin\xwad.pdb"/> @@ -148,38 +150,34 @@ copy "$(TargetPath)" ..\..\..\game\bin\"$(TargetName)".pdb - - - - - - - - - - + Filter=""> + + + + + + + + diff --git a/utils/xwad/xwad-2005.vcproj b/utils/xwad/xwad-2005.vcproj index aae62fd7..b001827a 100644 --- a/utils/xwad/xwad-2005.vcproj +++ b/utils/xwad/xwad-2005.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="xwad" ProjectGUID="{B850012C-98A2-42F7-B023-9F65C448D938}" + RootNamespace="xwad" > - - - - - - - - @@ -238,11 +225,22 @@ Name="Header Files" Filter="h;hpp;hxx;hm;inl" > - - + + + + + + + +

7NFyU_DQc=gA8 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

7NFyU_DQc=gA8 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

tLY$)9{~_(Y%7s{md({#NQ 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 RCP>fBNMB#7xTjRH{{#DslEnZ3 diff --git a/lib-vc7/public/vmpi.lib b/lib-vc7/public/vmpi.lib index 5a1be32e294b247bede7c87d56c4ccf2d1aac74a..5a062e625e3e810a29994f2f2b3832a0e04cdba1 100644 GIT binary patch delta 329366 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ⅆ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 u_g~L97i%cL4l_nu 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%0DEga{UaM&*00z2g2oBe=6s@knfsFOQp%N^)^C!c zHiX`~e$!RVUOQL(apfUeyIF|`he#7}a?ylwppx6NEz!vxSs&Gt(QUq^cu#y{Oh;s~ zo!wRRHMP1wLJiK(WTn>o*)A}R1J{zSD&G3NBAC%Q8JP)rJ?q7F>LY!b3)Kx83;akOE z)fHbINAGMDV$437)d!5O&;-_ofWBC zn?LUT{Ay(rEGB2TW_m;k!@0LEen0^3SWyz(D+~&*2Zaw-cs<{V=iSeSIc4%?+4Yj_Pp}jVot6*TAadOGX&+n!*OSXLAg;iX5l2_!9km5 zxabrAJonLeCHy0-G=tgHZGJb2gLk$J4TXE?(DU`)VsEBt$^0_^k9_>a`)bQMZna zTfpci=}lMCoQlI3mA4*4%6La)&Jkd(Q!EJ0SFU`W-511bOc)J6luHxM3d@w+#Otip zNr+U|beAi`(oN}9%e62_PKTl1TxQQ&p}G*2)P1S)AtU*yb~4jVO@l0-sSHJc;9Q)^ z4c{2>JrqOE-9mTK&D&Q_oJ|O(G!KLUBU_{9`nAfzktp+qH=+vn?~Q zRjFzeOvo+cK_A1O^%|kl1WoP^R+5`|Q+x*6vtEbeNgg+=7*CFo5j*vO$wbaaZd})l zfrU-rB=m-RBP9gu&hRiYVe(|9<(_Pe`DRSy)+TX)P92K286Fi2 zS@NW4Y!#J_qS)Ymi>YB$E?s6*I8qHwr=(pGo2=|GO%guVOy~9C^vQ;`=ix64oSj7h z!SU1f6{F*W`wnK0o!~;CYSpGIoy84%d)bA;Mq;W4Uc-`#QWoGxwvn8fO6hXk(Pqxh zSCi~ay|Eo%sg$hXC#Xp9{tS2c^5BqO!o3+AY|vPkOBGMAMC1G)@4ranX~$gL5}v%D zJ|D}2lUzFF2MUi;2~O*4h3g3%;`Mu5G)$+ejz>}=`m-hR*|?#?hg)W4P3W~Ix-s92 zwo4X#rs5ps_y#%BJQxz`>!Hf9pj$R&%EwIHk#`!6uwYAOtrYK&Wt*9|TV^x$A3v~O z%;|W6L9>MMR6DRtnQJ@q?tZfona#jA`JGU$-7_SnWNI}HCvG_Ux{WPX&g$T;Sh2?n z|Nga#bS~erCA$U_b!(+YMlDd(4Pq}b86Ph$j5*gtF%#oE)_B6yMGCTV|E^?kWKjj^ zOtbVP8YM*)*EFMvZ_+FxIg3m$EOH%&5boQ#IC9=>x+%)5$=83=)9O=tRK@qbSOhF) zkD%4iTYr$vcWM4b&$q48EhN6Eln!Ep_a!Cg*D$iaM?dovo+8dal#<4dZpHq@J$w)=7936V4F#UcEFboXMS9&W9$u7^kJyrwac@uVFE+ zu<1HiQNPg7*ZG7wwspN5^Y>Kr+~#v_k@_f*ZC6WY7qB4hF3@|U%U5DmM_MeS=YAu5 z=?tQEcA>J_s&So7hayKdZ5`RPZmThi6R0-Wcoo$%A!jTv^&2;i(F&;VwUPOW{+fyIwUWfP`^~6Gj{FrN5&2Z zJU|cWcDR*mY^$42&!B0NC3YX7JNoD-v5l zvdys}gQ}@JpycPOv2(>n-C4M~f7761$!k@}X(HbCUF8W~;?B~hf@)#h;@_1~v59C) z@q3hKF}ibt?2!kp!qAQylg3?9116c9OD=S64rj)dh_*=>|1Ya56Qg0L87(|W(l08@ z9MAJLqw)05@rXI0iF%i>mEOZH$7hsUu&{Wn?<{T@FS{5O8h@|iVE~#oVJ|}v+Sx~V z+80HCvQL^bhOydC*>p5&;`rKk0?^Rw;h>swgYbg`Ylf&BItz4}S8N{G!rwa1<8Hjg+@- z?#Zq>K;CVHAp|=09d?-o$v8ld3zs2_fma4yn9f^E`=h81|7zxiY;_LJX86hY&rgkD zQiX}bV9X5srrn$^1mup>;F9e}H6`7FWWDrTo{-C*nL8fvG9hFn{r{y>+-g-95;^R8 zqDlWeD;1R{2KV{ui!Gmn69w%TRp6WsZs!TCk=EvTO-i@Op>$hCJ*)~gg06Thj z(T?t(SwGYRQOT_EjAh)jV-2?8f*W#zUK0#2TD54IE^FaT0V{fU#zBWHE0B=!ca+Xp z6TN1UyhrH&x0NQPBHYo9GE6_%an@=j@n-f_<=2(&@r}bx3m)Y8UzMCio#fsJI|56` zKcyTEopdRo6U+G$F+wu#R7SiQSYpo(rZ>j48C>qnaZXfck%ZblqU=2QNSCfyu?D|W$vx#$jPSsB zD5Jx+r?k{Vb$Y83q+sX*7L!77t*vplv=oh(T`@94-L5I&|m2_c7{4#?0!bejiuT))qT_JCWK{yH zwkX|%HB1xMAqNbCxa} z{W#6mri#DVdFRuvjKyQx`6xUY7!O7hHggRm#t3)u#S(ODP^^JUd$Yz{aHN-^&9H>KvJ#hx%F&;xl-8fgN!h7x|i(>x_ z)mK(7`c~ojiu%xIEw9`Wb?W+{Cizc99P@8 z1;KZO(PqDbIm5Ga*`Hky0A`<}=XsbzS$|61GW(oAxJqDcWoo~GjI1*(dn0f{_b)%%Q7{e>NAw-Fzt{H~sG1WIB0 zWwm!~60*xRKdz$UsETOAc_YN@hn3>@+Q|szJ508y zQvr^FC?crNS})cD#+I7qJZ6nOoC!|}ZQUj3(X94&(O8z))PAXc}deyjg`a^@2D;@r=dN{>>5!!`7S^QHU#;K#bMnXA=<7T%Z~6 zOY$uujgQ2iEcFb>?`=_*jgG<=Y5Lbc4q zZhMtb=O>ek2km)oF;e>cwoa{iy1Bpfa(B}6lxhn|xBMDW zWOgf$vJZA&LYL8@*tJ}wYiLa~Wr&|f3kstCDK5FrviD^!)u&tgp|?nE%rj1tiR*<} zlrG&)x3u800&cWWfiM>+JwMqao<^_clUPWF(>+nCNt_fpqz#6cn?(%Hq;D(v@2!*pZk`y}K<7~Kj|>!)bVfN6O93q9?oM7*+$_9~o_+sl6b(v)#=?;Im2f z<|bG<;(*VCl-SKxwMaIQZ|Y|wMRlKz9p17ONGzu>hO&VaIa)_q{v@SMCykkuyBh>Q z=qUfAW~XE(MMc_r49&x9{zsIU>ujeR;@@qKvsnNRkjV)Z+tJTY9h-JRBr^D; zEPfRGm5R|ZF`M=kGLLng?~aHo)Q-dvG2PK)hW^|hQ&==f{R`XM;(COY)bq52(=J+D zr(|!~N24+u*t*?8ns6^Iwc7=AvkN2mQ%iT_so&CMLl+qZl<_(hiBr$){N_X5Yd8_+ zYcl2!^sn#4R}Tk|A5o(#fLwaDQZiD@=Fv7iphP!`KYpqOKMBpJE;&B`)NBsmAG{>|dZ{J9L?!1BPrih-}mg>0ElJ$6x)N zO7`hs=J3Ggb=PjVa>Oqw{WR6~_A1=dBtaa_ZUtd27uji70M_6Nk-RD388i%MqueFu z4ChIASO!=(0nf$*?lit10=?aC1(vqA^olJei%Z^ioATgNw%Su0mWp5EHv39Y$pUDK zi}PA)%>7TO24h*tTHS5IupwIhQI&A|UDmrG9(_UE!VW#98JYq4M-_rlbhNQoK3n*v zKvnCc6Iujo`E-~gyQz{-;1JmAEyGs~4BJ(P>-x8jVE!NAXg2!U2~x*A*n>DHljfGJ zaeFuzOJ9F2julpFoab4gBcw>0;J^YtE6i^;uivBB6hPeimnoid>Lbgyj4*Hgfj0yt#q{xryya zEmJK5NQ~8LWW||;;O=CIyho-2Eu=Io%iS~kZ&j)MQgpgWu{2E^N6N`Su4XD!oU{QL z$E#IJX1o63Ywa#=8?;v{EnY*qVwo<7T%liaAB~6pS_R^ih(v{IlZqVEY#@vmlVM(M zF?u#qle^Ah$Wo?&aja{VC=X)!+;q2}uPg7dY9c2tlDfwJ>gxXC&2r)G8n|kJE0oX{ zhxG`zYX{DgSw(CiS>W_6iiar8c|X76W{ZAm;IfM|nH<64LvqaQsCFF9tp1-8f?bxm zUrp(-#wOL}=DjpP8m+(F-;#uN92l0jA&c(;W#y~Yg5w5H9>l_^-cuNoA%I#CLZu)Z z%+Y@3x(PSi;$kSyb8(!-67coxeOOE3IjkcJH+C|CD%hOO_ch)taoW`#ovj^9mlzeU zQYs^lzHR$5V(Yo7D(Ey^AJ3j*hJTC*6Meq^<@wUNZ1VR<8myAWUR@p`s9aaLf-dZNueqZQ0%w?fm%2ru2)KoWCjFHL&)H>dY}~h!WZUuS%!p;X0PqTndgK>Lb_8~viezvJ}Ef3 z*|81ciiQov$CZ;#LlbnzyX29OvBg_{BF&Zjab^3?H~UgXQH<&xVj>o6B`$_(`3F_M zqtdTyHXOqsZ&5~WmvJqRR&wZtboUo|2T7uHzgQf0itxb6?rqA^u;COX3GXw&CL=og zf1uu?It6docq0_l{T4lFy4@s4Y>^~yRGCZ|Sgpvm*D1BDrx2u$vMDZm;b)o>)s3)-4%~Uf`1vd2dPmET{LJw$rtG)J1E?TRynTGaNno$?ZL8Ju)uDs9G%PyOq`2va-Vlf4!Eh znop$z*B&*sO9jzvBa^W%I0Krm_Qhf1M6$c**(kK>Db;AU5t~B{&DlznTW@x|kxl%k zC@~K*W%A`qLb>L>S}pUNTwhmo7)_t4k}e1m4_~8~ZkF7W7%|;8d$OLzK`@eqZ7}#I zCw`G&gu}0P5|(*=lFI9@>CWzF5XcVeKLhe~3CC^@y23SW{vE7x>ZjrX$BLhvJ)$w5k0 z(U&<85{d@2x&gSbtOHBf_F@^ig`O4vo#R(~3$hbcZ?TCIIQVE0!5;e_e&^%}n&^UzCHa>+R_B#S0kt zsuG!P7Ca_$EBMJ)liqc43VmPb3wlbzBGpoT1M+j0VWC=C;e%2a!P6ymf>l@>Y)}?f zE|yQMv27_@x5S2??o_T8_?4IQ_$w#ul|=?_n0TjUNFz&coi(}*HLNTTa@avBQdTSCo(&ba;I9|G3MMcAjB8eF0a;e1c$PdAs%Na1f~)Vn-N@8 z+@TC8MVa|1;>>Y_GK{>X5%R9*>33Wa1~FnRjv1{UQl(HV^#KxUP zp-cGknh+)Y^THWhu&g@=RnpmYwU^<5@)3P$I4XA|U&oVLKcm4i!mklgXJ=0>Mjcq` zSE;_9rHi|5>R&=_a!Sw^s+;v;A-K-hd(M-y)*ddrTuTDXoU4@+H-n8i?9@t-n1#AE zxs$WN1sM*Ztk4r|lgDG}%)<+mma(YBQ`APVN10)(ynk@z<|{VK3(;g@n=*ua)Gs3T zA(e(|>bo|l4EZgd?-mQ^`l2_EWPzr0l}j7~+1xhk%49_#pr>am)ds&1;eec{DyJ;i zosvHP$T~*hGN<(jRVD}VaI7c`B*G!fbImAxrYgH z>c{d>(N+bzIUa7O!%xwV7mNF4TvpP(t#8$`u9ezB;ugGeUyj$4=Gx$0lrXoko~2|Y zWWm6IZZ?xQ)h{&B6sFlTvJfB>y*it>yS2#5_C=G!X&9L9Ql8rsCfj*5L~>vK@2zO& z1043`3$Bjf`?nM;&QCvtK-8YPpgqj^`I`OSqhRPv_VV#FU=N_L^GbPxv<$D?s4D{3 zjf@TtbZEy;TMP`!&sD(bL2GZVd8POE19Q1N$_Z3n0Br11S|Q}6OU{ofxwxApg#=CE!^+5SN0LfJ zFUF*!3q3Nv>cpRM9Y(+`%QB;YG=1Z@H(iwfs({6xaN2=X@(AG5FZ>#xVN# zN{V`kXh)9tD{|-fHz~7oY5(xnjs1fQjG|wu6c#8EYjK5wk$_0NinJb_5o~$+kL+=_ zT%{`yE+60uFx92)D%0g}QfY+(mK}v&zl3Ln^_+t+uf~)JWbNmKz|4p^N zb?gUE8PPEn7UGI)MsW9X(}~o%{CtjVi++y%Mm-cF6?9%Xig}!gTGl>l9}zx5~DEWvQ=`*GzeUwoW?;# z9*`p{P1L_g%B!SG`EDUdv0e2`L6Oo8buO@lPpi^86&#V!EFO0gM{a3`?#T_ae7q7e z>sX?5u}gppHwd%y?XcN-Dr}J}khsTFl^CS0lxV>T!Z1`3oge94b4csSZZR?SJ;{!eGxz7mi^~sn#%aF(iSUM5SD`#579S!ZY-)+p}7U9mLxPMg6foO6^OmTX2nel+Q@`|;xMH<3>#jlPw!!-!| zrGB@zDi*RP#h_gFEPM&R6F*f(LEJ`lHM;5}ewjT#R6+NXR2NYG7{dQlB23CXtep>k zS7|I^xVSgJ;sRA}wuCc|erMUU-%+^^JBZ2k2z|Cd{T45o^32ygDj~1cLU5T&;y@-2 zZxGYJsjQEou8pxxU|Y=p3omh%RW00TvDHOeo#8hxMCFRy7b+dgreR1RH z7nK}sLxcU76QYCXzb_u#9XH0z?2Dd^&=R(=aTAEdbhLnQpFYJnB4m|`llzh6aqJL1?XtIfVg0yQlI=zRA>V3{2*te4KrXnGwH z|J((Et=O8&bm8YLLLFY)2j z))8B4yg=!u(TK~4X>~wwsFe-ksl94dH0ZYg$*QzYMTm){QZci z3=&2V&#@;nwN$TNl}j-$98Fz=JSZpEw(Df$GF=gbP}@u)AJ%6iLqMxE}cwmBFW2EGsNIK~8DM(QrGF&L7it+|H^{ z<17MLBoy5t;_O$L0jBH|R6xX0xctO<&&kTsTo#?WyTA&a!y<5-iSiTuF{deG3QUxF zO0AzL&aJ}OxuJ-r|4!*Ku2{dK3KB7MY5$NJ z3cjg4HDpn%&ZD$ANpp=rj1sZHrs6-7;$+HPthd6@%&(|W9tJvWi{5S3n~os|QG4U( zElK$H$O15$Pg8&SfCeqUOBor@O?-0@E1K%XMNNxc+5CIF&y4an_Yc+wXgH^n121{{ zxn?;p#M)TZ*}fXLXiN_7K7F5FC66dA`z%_&DZ z?w0N6!589vjtYFmm+R7Jzt%?aDU%iA=fr)Tc+JUtnT|4fu=e-rSvP+N%YnOl9i?RV z@Z&s-Pk{IEnqY%4w+FMY$ZrN3e77>Qy<=C~-=QS)3MXussed6Fgav~@^J3G(4Q-gp zZ>Ijq_W^hSM(TVNtIDNjx`(a0RpnJ)fgN=Xea9to*dB^DcZGj1rcO|RD{Jf0TiI@vs zqKjA(;aXzP#(v^JVe_k~xUzf7CFPHg8VN;}!DrSXFDVWN3k!e7|6`Li2`|%7} zi-rB%Mz%~hQk^c8(;*_xnZgnjI^&%%!qqmo(L^6ldm9-&OV9KTTA))Khs(X?`LPC` z=gMoT%?gB>Sfu@(xI;3V7wZ^uNB`#a?4ug&PaCh_*$v{@pzDM-Lj~& zOHssic_NEJme(4oiWN2@`vlJ|2bl0zKV6yGIV$M~BU{#O86+y235x?YCQJmK(1z}9 zK~0ZN)Kh(Zwm|A^m7wU{tRR7dgMIVo&-bgoxOxZTV|0W15N_T9ht0~Zk9gs#z@wEX zd9}*u0zHzOAz2x$XQ87Z=&IYFvt0dI@MCZ=V9df2$sTSfX`D^;$SO*oURJ! z7<`zlfso39*1*EJ`_hQBV#z@<_}O$T+p(m}=z6zAhQ$e&2r1QwV!u3%Y z&;82qTSA-#o5TGtm6+SU#UXUMTuQpkV=-R0UDRXh%73dYL3=atWO({9)nW(Kjeu-< zvis~Wg_?h;T$9yk>V63fZTY^E73pb%ZuQ+dqCiRJ>3lb3-Flfu>C0RjVE133j2mpL zq!x4dGgb`&bZm7HX90$ge^QnXzveb-$unsGb{;XLVSs~6%1iQx{<7|H?DgGBY)uc@`_%<+=e^>ACVUpFMZIgK z>#nI`JO6tgfw=63&R`Fx;ci6=reRY#-lU@OCf=3Zco=0aaG0`QuWZqeuagiaf2V{@ z6*9q3h(23~Fz+T&-VKkh(xdKOp`E92_iny~{6EU&fMZXu&aLG{-&W1TIj5TSu;5KF=9)==`7Dp;}kC?^IKplg&XT*tr}ymB;qWpWl9 zBIOQho4mhqUOe2Jad zp_pA4T1MSE8X2)hStgasgHKP3Zh~exc!ZQ5`KJ-doi;Al;{|6m61gHTxscW64aQDy zoNb>#n!yUdUJ=1u!?+-otM-ae@Y?fYZpxQaw{8;FN+)4{HkY5Sl2N6ADfCHvz)ij? z#jlXABRb$=A?2$O%XE9PW!f1qUthgTwe)ttSE#$iR^(?`+eAsW8L-mA&}vPf_Sq>% zXHAU8$tp;ApI|?5{nho%$5BUWlE!LxG>Jjx5>JKjhND|sLo_2q?*e1>T-%_1)?5nM z;-Xn95ZAh%W#luHx_mVojvV_I5=4v>h6~Tc2LnMY1Qpj(HuQd*U+ZEnG*f3h4E8eH z(Ura}ZcCjHx+>|qKE?&DJxv1sMj36oGE%!mHsuiOP9CK*2WD~ksdB@vW9u0@dcMZ7 z2}%4|S;59?c2dQe3uet_E%GqYRtMHF#M<{&(v&3*`Fa3GDB?TRU9bZ9@uhz!9o;9` zy?kf-9#U<>h&gFXsu&{<8{Rim!i1(-alzl0c_!n|s?dhwZl$U@N*7;90_VfdKdU@h zN~#oaOc#W|1(~T?&NYv_cvXnusI?2WlRK=g%+hGPg||`6*&xf-E}0fP?58@s%8xP8 zm5-?yJidJ?Y?-cnL@7mkG^ro*a+(j(*%~(R0j1K)C^u|fbPE+oK4RRkiIUG?d03#O zy;mhNb4iF(lG*rduD&(Y5q5urk)yB2tPyWeW|v~~v_l9X;6A%Pwn?&5UPU1Abt;6j zq4XkI)tA^Jz^#LSOHLOT29+WiFf89TOHLA{GdtsHR@DlB+O&0`SE2BHHiL$771kPgl4Tq$YmDl$wN|o5gJmsdjTUaoFsXGt^@-TR zF$2n6r=)nI)(mLdsF9038C3T9*v>|g_36d};7Y0?Sv$t*u{Kk@Xw?qv|2D>$y$E1$sc3t&EjDeO%h-@$8}Tl}gO_mTA-lO3~s*=27XmFzhD@mue-9HN8Iwo^rOz z7B*;F43V7fw7axv<5N{eh(~=Sc90R^89XPsT?|M?q_PF%MDPD(f79;~!T)#VB%DspdzRKo$y_ z5%xhkOqoVEieUi@K19DwuNB+x>>-q)*rKr$Sv{D#MjT+0rM@JeA7zRweP622QYGOM z$9Bv}7Bvit`@Not6S=gA_U7O@O#!{vB;GIdTi(1nqd`yZ(=Xwnya;yh!bwk~+m!jK z9_^F;qs8_7==^Ya!hSgOtLFz0w z5}12I&_CpT3PpTE1-0Scv($#V*U~;}X*QTDxzygmO|N<22l;nE=>Mq1&K|DZQpA-J zyKoB)_Dv*vTtN|wTwla$?aY$Z{&&jFOr27|XvJ%krsdqyrJVPZe+;#}N@)lMUn?b1 zW^9k(6}%(7+ARMsw1iDV1Lku~rPrd>5m~__N|=0oj+N3~Ih?KR9mt}+v)C|?<-S3} zx$*cWO>pi_?QOI3%C)nVcKuutbshw};DVNhye?!A;F|ieSDN+G4}bOPXW#%aqL{t+ zVx_M{*689;jBDUx4UgiKmMlt~TcQ3}+sb_1wSp%R;DtMW=-;@|6Lcqy3G)!}Ou|iaoXz=Vi?LLgyXOL4iK7RBOwsT1DQNpXPqcgqC^^sJ z0gzT`HlCAJpuBTfU|iWHZ*=8(B8_~n&xMKa9na-O^{24mvj-?k%4}{wGos*b&K|!q zHY#!DEhE=FC3F<*d~{@^{44PxayEUh29c#fyq_CO?N6Zl=uw9zr>g+Cf}MC12sbgmUze=ZF*YvUI`;p8qXn*|zNz(FdHzew~sGSUymJ zR~iC~A#=mTBioyaK>hnk{cPpb;A-d(9)zyFPmQlYC?ej_%m9m^c zYsaq@Vg4C=`?95^TY_KfAtglo&h(s*tAw-y-&HebA!hJLm8!@WZ22-bKB%8)PYCf~ z2QprH3+YQ05lRyV^RpRm-lN3IwTv(FSUe_Nyv1BI-bzIkV&4y68Z$6)_GXo<}KGnvxD;}Z)Ev-+ z>zT?jO{KMKrJ@>eR+6PYGkLADx?9E8TQO_fvRk3aF(dUVr-Ob-qy$Ho3zXyUqnV6FPXU+w02G7uU6qyGf)~i6wxiut47j5@QOzKE>fcnrbd*SK zVD4*5^ug1bJ>9vc2VYU9dqLT4T!yPJcr9ytR`lT}=o>NA^l37RqndB?-=$3D!MWg_ z!kblgo0456(eOf|thiZ8e$?KA*-~;%7l9Qo+M;bARtcznRZFh|+OIGX_aUWm<9lLc zaccCvybn+*r^Q*5q1C>h^4|MWSS;}M9_30vFIS*2n&#);-k`+f+?!wF(5uk9GV?>e zD`bh*mgSaVU5m8UQsBiV#w|EDE3v;&9ky9%>%*`;1gm%*?YZX4!9Kg`%(&FeN;1;) zxXVz=tCXZ|kCay7K(FLsw;5?_-vY}`Kseb%n4TV`OYu$JM1nnjs)x~#p{Bw zmnpe#REMse8<_lGqBK*XmT3>7OYtn^&&)LGpnfM{0$68(rCirW6TspY1Nnhn?s?G+ zT%rhW(S+>!-+2^d3FNxA`vpGD5~Z(5y>D3(+Rog_Lo|ixluW0mP}-!LpZ?FSEDRGN z3SGE{F)zg^q_wB1j8XeaS8%a$l?_~Y(EHY!rW~rrDJtYlnrT|x-ltiQw-+zXNXMj9 zZhi7s_R843WUW*}cVQq7L_To2w&r%SW~&kI15f4`VbF!j2-Zr!p8#8Wlu)b|Dn94=2n=U zH;$L5StMs&t*qPQ<<^$OamKDj)M_W_Y~h%hjSRz_&MThc_1OBKRk{MD*-C=hg$-7u zl&&ek(w$09;=wRUCzOQkI4sSx2$E(D`J^&Cdt}vV+cJ9D;DE7(_j}bol2{DPvNOrm zW~TnUT~A=XH@5os{&>n*+?w?U&nQpA*D5(x{dHQ)kKMjp8MX;8kGo!bkAx4=JI*rQ z^`(~0y(g>A2pR>5d9lAm?ANwT!E+di<1jb}G|U#c;wp7($sE=PS$ON}7ypwUWAp z$61`m#Y|X?&7Y^MkTHr?@^IJISm2S6+SACw2w3WZK~AlVfM(S`poZ+Qa<>L(R_$kk1}*L90h(2ld0ZsU zvkd{7RePK^iykwLw37ogtM&p}aSyU*UlgEOwYLj;ilx0fK(lHu7XN?w44!>yfM(TB z-}Tg%OAmX+F1VIHGE~*>*WWyTgwH_{DE+7GeIf0<+H9FF@=tuGQAzi)zvZ706BBR6 zg$p~kF4t+*YgcUHp9W{^gXi@ z&5M-7ho+<-en&K)Sv#zN=J*V?{@QwDPc9!=cU2a5w6jNaG;_{_!tGJDA?}3N|AXgH z?QfN~d0^yf0+p^`Do@7@?KIiZ4&-PDaeF|9SmayVxouw4v(Ea(J9oCto4t|8dcvqQ?by{f+B4oYuWf8(Uej3D zyvFfg>|8&uHvW}U{zZiwXZ-WCYCU(g%>(T<6**>X#k}V5Y~zkyBL{4MVA|;H8Ojha za@cmgr#5=~B@1@h>reT`+4o-}0=FNhs)n=lH?y`t-)HJ?Zf&!^&oGEII$Nvx$TisL zbLxwZJ0i{z+*CVP--p#e!|d8K^!ga-_5nZ()Z!D zoAmvp+F$E?e(nG0`}o@H3}z_q*WZo$i=Q_Mprws5LH}tPW%>JC{iXS<_4~j;JIo5+ zQqG?qYNJ2%D)OD%-$?p9agB&lhdMV8 z$#gwO^f#i19--(~`rS3K*`_;M{}HvJWme+gIdqj$GImqqFI3N`cJJPOl&0YNtopDm z7uf?tc3|yReFGg_`&fWx)lPfjX-{NQ)@n2x0-RnunE7ZW5471nK6};pePeSrjGw28 z-_jrwY#Qrs+-+SOTRi`L(=NHa(dPYPJvOt}#?$thW|T2qqg<=a2=f~*ae$2gW0{w& z$t}`9=~yyG>b@FZlext|Y5w#umT~6mY;$4eQzv!s)?;B({+TT3u(+-4#)kEyRXQ-) zI8+BJ>B@B(YoHy!dBkL_fu0>$yGGwY2iLX*$XP?|>-4~zz4Z2<2-BLN<1;m17mu%) zcht_VdB;4frpZ40Sc?cee{+vc5G)7KMbXx`7At~ZRe%{%G``Y$d% z`>vS)K1>?3=CNs)DE0QH+WAUeslU0kRr+Q!&a5rhx1)NRQNaO0NOUW;(aIcL>lfTq zIdqpR2ckg?#}C66|DGwhobS2$H0mx59d+_NZ+-0E4Bg_v(XAU>)?LxMadh()Tg!v^ z$S~g?qpH?fi4|K^Slq~`)bUE+?2mc|573_zlrgBl)GEyRe-VYJpRV+we(t)}3gZz< zd_c4C=_G~*Q1iV?J6CD;MorhtM>gg{8=O0d06B>OIf=yn&j=*$rB|&U-!^ad_NUjz z?;DvH#`aSZ$#YL>y#6=8l%%=pP|;0O&+6slUob-GyF?={DICW634*9;uHlv04jYGQ zPdDN0Z)0k%*I)cRIRNSSMmDQ=8n2*8nm;SFn>WlSQ=c^dpulG1)YTR8DJh1j^eFGg_yE#BUgb{5stRr5=SZbrjsA1Lk{M8#Ynn!II zJLi`1`GCi47~6Ks^zF52*Pq46j5diI%w5&9JdWgC6Tv=y2xR|ElXzhK9;5FD6Ilmn zcbkSS)b~L)4&@Pb-48m*Ka37D4u*MJNKW%3B~lJL;*;h_F&sy9NohoRC(qKV6E!Kq zXL4pNRvYK@A8b0q++j^4`r9CdTa%e2`bE+vo6JJ4>)9aP#~aAU8_35yBK3ek=w5pJ zPntAqe*^oraYR|`**P+Aw*3+%%-+BR-g)&=<6n@J4H8yrcq(N3Ze^Z{fj3Qn!sqI5 zuE|xV?p)KfT>Ac{e$z$>f_^nfyMn=YSt`R@CWRiQQD^$$dCKzFp}%r|Bn2(%Ojj@G zM;$7Xf{r^DCO7Zo9kgm=9Vx6@+S8dIyiA1hPJQcgQu+%-Cp$sT_Z$7x4aac8=5wvOjo_thekN#GV zdXIp-M?l^q0h4~{R?GTO`pi&{)xbR?LVK^(^%++#T-YkN zype%bn}$b5{cy8kI+;InMM}Jm)44-Tw#_>5EshZu7^2r93+Hl#QfOx6kFLaIq(^^! zpn!a!fM(U|TF|_C%SiLq(V?L&!y^Ohn>TH4rqRu|tR8NL_inYuKT(h8DDnwetNDwP z5fFnseU}y4h5j&C5xH_)tF@|K5&>Dd0rAN&q$v;B(`)?#K|Z2p<|Af5SW{< zzpEq8K_~4r+AX1zK07*r9GyT#bjBI)r7<@~6Ig?oE{od#(%;Qn@xUt z?Dzw6`~f-sVyh$mymXGgwYN7I_Q25G+As9&pJ}RTGVDQ_e86n_Ntt73348GwYQ1WY z*ju@Ia8mq1OT6i^kB~G9D2|Mf_B$?s92Y>2i`WK)10OFPb)LJLs4MOK zQ!6@883tjp&iD2ctFYW}2K+&(?#HY8p-?0aIQ?*3068vza$N9E@2PC4R{Gx`{|1uo zX`EnEupuUVsGS~pEH=#tWkc(-NqdN)w5uOD;T z=luloegb*z@g1>QUb;NyPqaSr6|H$yeWZz`ryrh4`}mYnA9;o{jO|#`$HlUcq2LTJ zT&Odt!)`-;VSVptnfDIJdk5tGinYg4^wL^DpT0|$%?AB7YV|D3$$k8kQL`bCxxmI# z%<}aYs~W}*9b464OXag^s;%jwh&aK1W0>)60LHydfANEDqAV&{B!ClfA=Bc^`DG*Y znI)W2Cw1`FNpRM_*|+u*j2)`Ez%q7NNLi=5m*YI3Pi{2?a;qVbV~}^b0N{#hAm!m2Xg~k~G?o^$E!P1mt~+b*BG)K}~NRLR)l*Iph#@8&40(! z-r|#S_9u?Ypkp$qIfLe9&`B9|Y6i{EpffV)tPDCQg9Ox#o?~OYrD@_i;lMxBj6YSY zTIG2WpCLV3oBB%w7@uL{{wOEy(G|XTL;qfdW8&~*ON|W*W9yi{7yan z(+8i^Kt8rW)pOcQk58MpDeL-l*ZM;pMf(zs>riKw0sMazi=V6 zYZTk0QYDI|dEP4^?-h{uD!w8%%1bk%ug|)*zay&auM11pbVN_C98tQJ4y1LnIDW_1 zj~^}eo&tGKfvU&POKbezpLKM9$FIHA(TH5e@5z8!Y`}u9_{q* z0eSa;s!7;OPu!n%u3BG;*>OZ7D@!?RS+kALSlLI$Cu(g2TStaR*GUgdMasFh{ZDmY z7iA84zcWpc=6RohyiY*hr}(1y-%Crq9cyVCYneUPGFMBVmSe_Rj+&V13b0-?&Fnbt zJ2JzTD-E{5sMhTBQOWvYK-zzu123@P_zaV_EB=uEV$a&Jf@L_4(3g#j+v>-et~gGB z94A1Ill*<&U@wg!Yy6;{4d?A_xL%g5BQtDFHw;uu)wpRg)!1KA>U(?!74_Dlw=B9f{bVYpda3SAn!hqcR#iz4zZV>I5+Ej?!lE(LL{d6So{xVM9uqxQn%A* zURB-g0-Id8Fj}_HAKr3x@22OtPFf)cXsve<$h!#ST}geuG}3zpYD%d20SD}Z}TJIu|cM+(%i(b0K zy*62gp}qU{m(uCpaI7^YI*rf3&^Hykx(8J(MhAzJD*#7XoKm?`$0FXq*_&&@;_EjjPQE#|F$`W=cOPk`|Sa6raH`(vWL< zty_k!%~YAaQTH&{^4@PtW!gHtE|cZ7+))AKr~qPz?eg*@K0~d?SJFmcY@J&b-gjVFA@T>WZ5bTgvW|^On+S*Nv63x;_IhW5 zyt6>w+4#=bRxizX@A&k{9F2d|_FcRG=GVX3zH8cH@4aop31W1X3e4Wwa-1>*HJyFi zb7#)jb@pxBemn8@5_V?T5rkuOw18GTM&Gk^K#Sujr|Wxm?Wy`crgC;+HRqGtu>E(6 z!Ls2nK0~edN>M+UZb%JV0qJCNf;0pvpg4}eI7~S`!)ET7-mU5w)`2@?B zQbuRl3W_tv|Bzmh^m!GWTl=iOtM>{HAVVL7L;6XF{z;`S z#b@|`e?Ii9`mP+f^2#k&aa);>F@AdMwPw_ISr}L8jc^D2tau}Xx6W*iQM;q1-dP~; zERc6LzBy`nUYeP5cFQr(J#NRYZGWLP(%kc&+V--BolP$rJwS|Pw9$M=wPupPR735X zh6=hm-Hz+cup?K~?T}XWA}ad&Mq=SvJ}HXI+bQvxlT~hAj-tI-Kz435DdXEISYi`1 zNVUMFM~*TeM;VZ#EVd-lhnEi0rZGyJiJxU?@l#FJtTw9s%+d6dqM|He;xkOv#(V;l zB#gU`p)WU|oEsFrB-F*}5NT+s(ozXS%f0(R-hCkNetdiEo|m5ZUHm=CCR4=LRAWAs z(PUQM(QKv{HqWa27-s>^^F9H2pMbniv2pRgmmY6vm}rUiv%5&snTMnd-Tep!2BCMRuY)B*ZT?N{RHxU#&^c{dFf0$coc21&;Du$W%Eyz z)R6hx_Nud!s|gjors3XeAn!Gh_d32j^sVNlvtB>$kJ9U>SN6KRB$~X}^Xtc&hI_Ap zyw^b0z4p=*Psw__?-F^cdD>JlPm8_GM02yqG4i4~Pida_3CQ~dR6S3(>6x z(<@7|InEhga7X1J?u860SnulQabmxVc%VOy2O!4-Q1v|a(iugsq^4~imLto0Sg=t@s*_-#}AxvM|=bZxbP61VS%1bkSZ_N6&ztgvT zU^k8`FKDi$*sw0Go0S#IQJU%91M=7ZY@Z+T{6D7L5H&%XWXsT>7hFp z5viO*hStsrT8FwOZLP zfp_u_T6Nr1gUAZkYzc{_6g8KZ*}8T#U(kXWKf49wXSaZSFk?5J9&0r(4XwV8me^;1 zX>}@<@M)FZEKbFR3+srsLQSB(PP9Nyv_REF>!ttHMEfqoF<<}h5N+8RBPH5%_G&aU z>P?K`lTj}ia_R+g>IJH%UN2qndc%XcS_OQi{#i*Vg;Jec1Z9M%4j1)dG3{lsxHYSJtsl8bp&mVNjExj~m3&`C|sn z74%Vq@M-&qK?3SL&%8YmgVEt1l-#esc{~z6Wt$(#W%QL99M4b&WBw!_y{(x?w4p3< z?2S6(XViF~=s=D#AV*o=EM8Qrd1-B9{xA@ZdHZQHd{fFgLjZ*oM_-NX< zOH0~se1^VVQrWixe@;7D$T0V#thdjo?=8*t-U4}VfvS7!rA35q%CPurz^&as#Y5MaGu->tDEK$BB$Y<_ zztt%3H<0%mXwQ>AzHzGm?z&1h9D*@81Y>ZBS>h1m>ky2=p<^3b!AXb2Cc{-1gAfHaRyzQLF+SU zV+IXo&`<`AWY9Gkv@L_4pFuZf&qY6g8RgYLM}pP|;r&dt*IMw2P|=Oy|~^@bCjaJdKM zau3MmUTlcaVjnM^Vc?*wQ~Qg74O3v?!b%Jj)S9XFof1wGt*06%bjxu9+j*TD(gDWHF1H)# z*o>|~0$p(=067wX90{>Gu^V36kOttMk$jLGuJoKK|KXx-Yw( zq;&t#&Mnl9c*5%Ah|_NGF_8BdsCvY`bZNw|&$_<9#}?#7G$*2w{}JKHxbIcce}T78 z&Gb`;*M#L&%RKtzXaRDx096x$m)7AckAbLI#;4qV%AGrQU3cP+UtM>CsRJ{1&Dhnm zvtfZB_UPF;clpkSm&G73O+=eLL!ETbu9qYTovl=N0z5!_5;TQOrdFH&;^*lBXp9N^ z4@_D9Q#YS9-*fY+SKf7|PRjGvd-fOky&E@eu)`yAIo{eeu)+O~lVOPNWg~jqfLxPv z_cA4HPWDVdj>ifB@>l^tJ~9yzF<76M7Qr^|*u{>595X$JtoF4Oi%?rz##GqD(#Y5# z*;dy0oeT-CtiGA`IxuPIoT2c(jWswg~;;O?Up)$)XUoTS~LGg)E)4=@Z%s=zE=-o#u5roTbosofn86OWI z9}gfOkBEyH6xd5oJ|464@$fyx@<6-`EEWa=ci8HIIE|wEOg0cFdUhedJP_qw2I^!W z7{tRW2O@0dCiu4$Lwm9(xSwbT4~}*qM>~+CJ@!1J-Aii%Y`?Ctw(T(%jCpR{)wS*0 zxjrrKT5R~eWMYo`=cNB(OF{9SW<8%_Ys?m^~~T+NwI)pO+!bx|pD^yG4; zewW9f?Ce9G)WOKlnQ9DrMu#t-^pH6Hats1F27w%ddAnWZ_tN`8xOdXyKO*6lIY>&l zr&c1Zpi2@sAm<>(EF{Q=K6t8N1gATa90p$7ykmDdWA}XFn!RnLJbfS1inx-Glfqb^-?;Bf98$sAhQSeWRqshO0kU zO4_^A;W;HG%5J12I_Q)Hy=9My?bkEjL#j%pxB zHBdFGy|hs6KF`nGlg~3Wn)*D;G89+F!6dZf%Oi)CU7r(|XBe)~dX)1SQ zZLPkKwae_9YkdZ9)OT~G+I)p__#o8chvxE|NqTumO!MR7`2yud;g{ye)TnioeMWgF z@1Rw0sn+sJhr%+S(S+Yl<}(V0Tyq9;%^AptGWIumJbUSk|32H$t9I=-|1BGuBjjmTHbnpPVBDIY$Pn=Ez?9PeR)_81kWv|95D+tWl@5JyA{D4{-&P(SE|V zCZl~Y=(G>yv=3BG`(B!%UfeX^-M}4LO*`kFb@rF8YdYn&8F%%ByRfER*EBw}Z*1{w z4rE}j=9IwP+RL-i@|%+eW0P;bI-H!~&OZzgX zk$dbKPr37y+qJQC#EvgtH)s5y8DHqRV8&-I_yO(JJx1&q952wfab9m@Kvr>40}iVf zHSt_`=rg{@+C}y<-Oju0T(X9fn0o83;{V7Vk_eE)Ju=c^XeY!IX${xfAjPgKjmJg>9M!vlLYY@ILOcPlLSeq zhlLBn%|v#=6h2fxRk@NQ8IAJJ0C{JCyfg77u|ZyXyk*|R5BIAxY8x3j}bb~?j1l?rN zNrG-R2ou2D4Vo|Lg9e=;Xu=>&=64%}_sBO567avDKM@9Q)_eV=r9;q#L(qjo(1kdBxL8MG#Y zF3zA!GiZGVZOov-3>wOykqo*fgSKVR^E2qi40>S(y*PtjnnAC~pjT%Qz2#@=wlhQD}(OHpigDc=Q8N-4EkyYeJz9T$)Im%(DyRv2O0E} z47xXiewjfJWYB{dgr&-R{=a8TX4b0Cn2ZQRhh#i57i$Qrnh8U{w0^9mQEDC~{WDFH z1aa1&@3P_(f{xEXr$(6Z5yq0teGuGgrSjOcGGQG{7J&{7I*(5Y1ovtQRt2OT#+jvFAy zN^CId9v?65xVf|erJx!)yqnKd$SFG}$7lF|;JzS-OD)|!E5QyIg)$@~bA+AF%?}MZ zbkLCl9UAW)2l9>s6+0f;ikA)@pN+;mb;qZgI#W@{tbOTt@4&znWe!`{ zZ>&Dh--!N(wrte`)p^PevEZ$$V8yjVIbWfJjvOFI4p0#}aq4*KfSkE3BBn;pR8%n) zat_`XD)u`Pg_jQfK8A(TpHaWF@bi1qZ`$tt2J(IbRrlLV zhkhT$QgP~jNA^cKJ}DQNYP_5FWxT8UUCk;Hy)@0ZV(BJdI^_rfa)baC5#l2i znbL7-GwbB35i->Tc-YzE;=$3a8~4Z_qDDdEz2iXMaiC(y;|%c9q2u!~3ryYdsVJd* z+AEgj|DpRr2{JdX>tDCAPN{h4)Ii4^DL{@CpdwNtHF)WOl#{TK>^oA*j@$c<6cmN_ zYjk;wTVWIC@X*mh*BmWCjuxP5w0P-&mQyho?K@g7DOvLN8!Z(VtM3|+O1tQr0yT&2%QULGI(?70iE8M4^<=NEPN&n3Fd#=5P!VBK11}^r#P3LmtM{?}=BX{chx2Db+ zyLL9ch$F%mG%e5kNwzO;+Kpe&eN7_uGV0g;h0XYU@3nI;VY0(^qKJQv=3~Ad*lb`(@pBYYn;!QR&e&|@Y{=q%nx%F5>os-_l zN}W-AioP4{9N3K7xdua8*j-aHjOP=SoPAO<#4Wb`DU)IO7FOz{4&J)oM`Lo|<7CoD zqNCSNbU;pYK#uafZ?s0+%2qm{{2`vReWITAUQIJsxZJLvV)IlAl|Csp8JY5@jLow8 zL!H!t1K!@9vA8ZhxrN+Os0q7@`dpLri6d~tBfWDx0y!Rm9FKXg9FJal-|%?$9}SP4 zCFYpoan2FhC?vQFXTwwURUQtHSfekFH6X_tP&L-PbUc{WWq6vIbPcEcvNlVfFFRFy$ltA)R=1ri5DNkV`Gb(FPY#i9DAK>H`}@V*qq_p z4^lQryQht)60>TZ`p&NjOgY{&lvDns9IvCCso!OeR}K+Ooz%fwAKKaATv$|`3#N;X zbRb7MkRv_sh%-4ay>G<(ybPItXyX0bDVW^DbM_}C?)^Ik9GM8%@o@n1aRBmhC<_}e zt#NpWp7xi@ZEJ4rrO8OZ^Z%SY?aK?M)Z2K!wfB;@aiTj-=4}kB+}jw)y^VoAKIZJj4WkD>Ynn0X@#~IJ^3G+`#=4IgJ!T`%>^99~`yCBG*2$2abDuZf z_0q<%?zt>9pGSbB=j$LzZS;|2+h#XN$KTf2IR41-yZXjfHM7#(*)_Z2u4%Ayr5-fa zHM@{YAXLt!%(c@5x2V}OYnc1qrN5cA_vp8;E`6bYpvuuP;w2qs0VEz_E7`fWa5&QL zt~|QduG~l4Nx0){{etJ&$+O4SIIGiKd#=8ZuRYJwU!d>DW-HzG`V~K80g%MWKB zspd>@l9NU{igal2_{H;PpE7aET|0KI)DrQ!!*}ey?ywo3)j^mqW`>LL!Fk8*`02=z zr|(kcobf|nH20(#pVA$uXW#Y0xsTfR-_IPMxX501vU=$UmU;!biQ{%7Bg~sImJ2@E zN7cabfkrxwcDKx7wab(;$H?XI+H(XSX@qiAMj|&VEq-nYKvPW6e{jR_vtW)Skmmal zDYS`NZJYl1r1_2EAkHGCNAp9>C+cx59Z9IXg+ZHW1LS>Hr7?xYL3-xH77JWd(7Ac* z!0^aY2xMq*V9~&aEyHePO;qYSLrUroKu9%cjrN+ob;TC1Gov-e;lSFh$_F~Q_OSpt zMMZq-Fre8Ty!4+3MfowHoT9$?|1Cugm6WrTqBd7jRAKdyl2#$ZjuTBvV~JSXYZ|*) ztr@DLF-FE|49IB=sG7#Sbat@d^iS&=?Yl&kx^Ut=WBp?tP07gu=<>f**-Q)I{XK)e zt?!yir0FJ+s!tXiN1@VZx_nBYx#XbaT`pg8D?01Fx<> zMM8sJUjTA_0mw0=ckR~)_dxdx#}{s<@iQF0+|>{dsF@bSsM{b2>LP=%3gR0v)7E!5 zhs>0){@?QgLc#xEA1S0xABPyEuiDWDAvaDmh-r3)LC{BwLD0uV24NG}Xi&4DZiD6t znsYEUpCstf2AwMC2FpeBxyhh21dSSmbZxM5=LkB>pz{QM#Gp1ocNx?nsNJA0K{wm8 zJ%ZkD&W34<;cbhkm53i_r&>jnMDppAllZP1{ghJ$GHkf1pRjR-oyplbxp zH)xxnry2BoLC-MgMnS6$dZD282EAC&Db~`L3VODsy+Y7+2EAI)xIs4yx?7Xo&kg*@ zpf@Y+`LpdSdj!JwZAy2+q>1>J1W zF9p5bpa%qf(4YqeVLlQ|kp}f@>m}y~-eORrpj!-T67)%fxSQg025~pV*A3!siczbD zyD7eE?Q0hF9fRfx`k6s!Y2UPRrwaO=rExb!V-qdlZi*ue;%pmPjr z6VzwVb_n`~wV+E-r{!`t#j6bBZi-a~aW};}gSeYw$e>FFJ=dW1f?j0MMnP{jh;sw) zH;B6_K4DOPZr~cF{X$YJp!6R+%^ML4FdCHcHd7;C!@{THf<|)nPu;Md52oFYTJIzsop>ux#_-wVBdOzg^q{ zxwr)qM)mo>?7az`6~)y)+?N@K0Y-sjR8-Va2Sr6hL1l3P8F4_+VG~pY24--;VR05$ zBye$Dr(%KiEGqo7TglIM9rIIzUMios=KOh-@YT}eZTkr z`@N;NIn#Zr>O4=KTDrQr+S;gOD&<@RSiv_B^k#rr{|!Hv4$CF4xq*LMI^thgw{iII zRYmB)cT$(*!)9?N?0W|P9)MQ}>iOZb2q%qIoRkzNCB%;LA4F8a&~O#xY7zR{xHbq z#ec6ZQh?Li8(U7583B}&-p4sZ!L5x)%WYbyB}@M;Y?`jHDJg79K5Sc@s%>Cbuoq8< z&FgM(i}T=EHI~Gn%zuAe1U9R2+-WT$TBV36DI!WfB5Rdu8$|s%8rgJ26H*WrbI=)0 z?QIP!PSzJUoFcTSXqlp-q^Kw*qf%{ys*)qCbW~~XnO5z(*}jZlxiVf_X-y36Rjia0 zDSgSZrOvlRWZg71vzGt^224((RtOTPbCIPN3Mw+M4C@C~bJ~V5dY8z++ zIV|3EXz^}B5<`0Yw>#A>1r!ZaP?QuDrDRa5Z9om;2!GQ-P5ckkxD;SCPJvNUV3d-9 zskQ+&lD8k54(!M;0ZbQ1VXk;aV|~k_Ccdno>$ek{6)SK_zbFTI2q2{&vZ^ifl4O0oE)A~>em+GL`(fk(qfw_!cYn*DFu{#3hZE3wG9PR zc<;RF6r}Yw*L916a51an?p)^^`o#3c=K8iJCpEHS$lH!I*zpZZ0)Z$Al#~QY$t0+@ zA)$`b2I(ZE)r~X~t}jXgx0XpFfy?Ye5{_(c5d3!Sz2HBh@#K&c+Lu5Ifhj4JloU!n zDYkN`wjpIEXHe2f5d|ul;8u?KvPrAUe~Wp>gqFtow#J$zEwxJ;Ti7U`8gDi1bhL$> z<`~4G98gjYDES=N+M(KpgE^c#+H?-m`nzHc#H3c>m#3{XmDK(!46b2(?0&Oln#NNXZ*C{i_uj4xWuTrs1q1q=P-K44!U=eMd>Qe|bZ zi|IPrVDhs1Qzqc4TI;0ci<;XT5PxQ4%d#cQ>v4M=z935r7MpCssR1HZwkRoElzg^q zol$MW)&kBern4mqR5HP>Gil@58;h`oS9uO;U%a@nC45QJX;5u-3Ez~q&e9Gmmg~zE z)>#Na;ZsuhlzjNMx~R5+-@wV#bohkp1{Z$XE#^%{;7@8jQs0wKLv10_Ood2EAyV=o z+B+82HV~UQOT6h2Yf^>`#ULKKyy3{jNj*hg=~|q$RdG^MoRpGrssQ2bKD6w$rfvpd2{$_FLogObmOt=X!5XHXRc8_7lP z{OStGoolN51zRE4N-T20=4T=u0%;3KW%9&9Ez;LP%7VjjE%{>;({Y6cxFta>Zl#LfNHPv} znCi_%N@8Li+TFIrLMYQ@-D+!E(kdt5N71Gwc7QHrGjT=Bvidf8rz?1)tLq)aYHe!? z+gCnQ(NdtKr9i1qFt`w-2uh}m?&P+Qpl3@(;TTys=;Gay_1ny>7(kKmo#9c(@@jg?9EOSqf$gkDWa6@sH$x$ z?kRjcmCjFE8yuglr~M54_tql(%v#=DkRe-n2P2J@hD6&;(PBkQNzqdB(PqRBef$=B z!zw<4OGj%FnCM>RNNbLNS_JLHCCl;3V+$5-@x7)(&Mv*W+FcLQt6pgaXKZC1#X6f&h` z$f|81p9{kpz7*uRi@T72)(yzf&O6;JUs|hRDJfV=$zWC6fV}`<`G7pVeBI8@>e`2E z@z%=)`|~1T@oM*!6^rVd<(4|FN{4q`TbTA;I=v_^A579nI)AfG(HWcDW{*&nDN4!| zC7&rf8c}V-)Fnuo3kWuysrb&nE>m|DVQOM~v#>OCg*P{o${C`pmTCtx-LtODFc{6S8q;h=O-WHx z@=@DvU$qTty8>v_QKxnLcXb=}l*W@9^-GaXPiP-s9E)U{MZq>)T(qQ!DJfz~$%s|k zAhs)=HXU(VIsdWk6 z7yE`myjt5k+tDoU>5V70H*$L^+l#Y0gE}Iuz-W>4DbMWM+?3dHr#Y z#5Qvdj7$?)GN)XEsGL$#PAU1E;zceAs%<#^6;FvCd5SOavi(BZ;3)od!^Yvi_jZR< z+(9pC)$Qw?7S%rY`UGCoWV|SxaN@Md$@N%GWZN4;PzER|1C#_A{?=f}L#k~Ucm~(F z-ZkB;(rR8>D|=se7{C}28!F)Cp12<+l3*w8C$TQBtf_CqZW+dhWL80D{w*;?ro>QE zVkimVkQiItRND~qJSgGv-*jTqh8gi$Hd_w-_x|n>Gk)QUmhO>a@zP+$OG)uk5*#63 zJHSwFgV!!{-gLZaRiRsWv6)qy`FKjPs$c|(Kcr7&ArqC!UG)iJ}SeDi{)i%)Hz*#O%PluM~yJ=F{4~9B zakpx;1x$+-FeL>{Nq~fat@En30sIF*&I>Mw+tCM#k}ikOlh z$wRE#2Jw1eE>;@6Na)t zNm-yI=t353@1@#?g$=;ZH#pK+NOSbGa(}2hEZ~7;5(oB{=zVtJ9$eb1a49KVN&+K< zYu#M64cvh^%NIS;;qtm02;WX#OivjCt}g=DZ2Q=HYHM5LGPc!tS9{5#g17iIRB=*L zoRq}=5T`X%wGGaVILjAl(s8C0YuY5u!$ok;#?T5+?=U7=(cZGCF%RHY7;Z3cx_66F z^4?pg>580^BBvyXLgcoDRoftc59#v7p>*VF!?F0A`|haYks`=Z!g26*Eh~-($UG7f zX8P@8=y4!trH#guGSCvjw znv0}O{uX;CFrj|=qQ>SpSMhc*fg_8YrYmwviky-l3XxkEQEh|#6JX|hWa-G$TK=@k z@n{hjsl~H!TQiPmY|Lx>?U-H>;obe?8`}=Wjt!^cDLNK3SxSgW zDWRm4P!gyiCAJ$-Z9|EDp)Q@0G-rr!O77O04Mix?t(7M&TZS8AJjZT6b^J;7OPcE! zcJ~RnC4jF4Dgl&~07^aq)+bck5MbXU+;jrc`oYJF5P)Vw9{fyLv9g=X^ekk)`>2p9 zDP&5?kX742z8t>9S4B4+a@v$pv3HpG-22ED406NgV5tv&?D9m5n}#cHN{XA3;0b-* z7PV>{+(U4dFL7=5$Vp#qlY_p2v%L zclx@^7dXDthx>NJ5kAM__RVbmX%t&M{Ovx^BN`=xl9EA5K!#-4j!(7m*5!f*zU-Pz zgPq?>YvsnpXs~hkZ?T1UTwAzLyoW2i=ni%&Kw7K-DT(=bk5+?qa9#;lvy9@a@5vZ# zrUaM^!{#&dei@%SMH|lYFx7O3lY9?E$X}4mgq?jowW^0GVtlH|~3#z$6Kssh# zcLO=Fqx$&rHk%j!{Z$e7p42*{t+9TYlVq6FxG2( zGZ%&o-tIB0;rK0d(m9 zb$5tWgh|!ZdqJ=UkEBC4wM~}PRd{81fjrULM4kv#d7`8|QSy1RExT$Po^C~&+=VBd zr?lQaZ8o~tY>ZT6eU6GZh~wR!+tn|bxdKbDR^UsV(dHnuTVYdD*pz(OwyRfd1N&zH z%q@q~Ve`5h+G9R{7z`OygZK7$Vf~2G%Dfu9+m8xw(+C7ibLE5gzCgFli zn{RrqJ9PkyuEJs~&{ug6%30*nOv9Sxct12=l7R%1xLvTXRT9h18yD8a+9o02bb0xcMBZm7Te8d zrJRn$Vxw`2jgn%cl#ETa72ASw+&wKBnDt$D*=}%4Ek0XpV;=7CoC{{zipzpof5|l=wBeVwnzb!%{CK;o3rE%`;OuI8UW$#EVGE6{ZJJ@Xvmx~B}EE9Px#WJ@XAKIGYEWIXT zWs8!sMJbsr)i%}1?#7l(m@o+gIhJvQ>vd`KRxcDGjLVFR+yr{#g3!zCN0sfe8onO8 zxVip#{me3Xa%kQGG!wyMShI3v>uG#8(0r0EZ{|z=;qaE*(kYLWlt)T_)wRQ0)iyj< zq4VWlcIiCQFgJ9U#}|w6$fvvcJlZ=&_p|04U6Dmrzy^%RWGNDF8CY3)veka;oVJFq zA=}1{5S1ZH$`B==AzLq1+b~oOhPcOHIzv@daDvNF+6}-<-C&5Wl+RLfn?}b9y-g~h zbv0eoh1Wy^ZUaV^hU2&J2;79)7uOrZ58%lqI_t9fX4z$2?#6fi66`(pIRK0v^XgF#X%els{If*RZK1fJAmYMH@Jlm-!8(9Q*1OSpX$IDVfpqU z-lgQaxY$t1`WbChV3ZUXB?ac@Njp{hA?^{m>A>Q{0ymSQz!Xy&A{wWNC@CUJ$%s_j z)Ev9(WimD?P2jerOb!^_LaU2Ss@pjH_vIorhcDAkz*BKlo`%C4PsOXSSXvw}H#^C` z9Er8@Bj$E^%VwAxgJ546R==>RAFu0M|`+M7fakt$J?lqgC*QAEd}+J>lcAd36MrW3^x zgBx6;;*Fr|K(7@cN@nsGwM}2Kq78l|JHo^o!Mw_3=QTMRT+!af2Y1$oIPk&W(rc4L zOPA+tgp$M|Nl*?cDTkDN4()hHwGD@MH{Em&g=B|Bm&5qoip$~aML5J*nXL)87sD#3 z?%l1JcOhlB1GbeX!Wc5iUYvFsCOIIQ6x(Tq+XgF7l$0k*K2Nq%sdcdZRc`X7DlTNIYf2<_`5(?~>Pg+l7D}?&(56J3H5oGb@i=ytuWoZG7{Rt`UD~R7#m@G8@*uLHSV&k6jD+ODftv4Q3%nNXqdv&K{wpi`IF6Ok^-D2ZES!O)7e&zT@Sc{og zy=)zzeF~3~!lUHFv$mmymb47P6v0Xs32C1_5;N2t3#lM}iJ#peq8@HZUrYI>>lzgUa^;B)c zR1@5QyU%YrQ}Hi{+SY*o{-Fp{wx9L46DW2^ZTsE)S;B^pJ(=dRQ;?}sT|}?sQBv|K zC6lMxhCI7re>!=VcaqHDHW6uV_QxXR$@Cs}7FyoWn78V|P7OK>wQQU5(-1Eu#Y@S@ zYnw#XHhAr43DWVV-8RN=iCt&;Q}^(4U%*0LV>l$05~m^I8En908Q_-{w8SYX1C)FQ z?46=&^HU0A#}7JVtF`M^eKps+vvS<%nhQ(Y%I_pm_^959bvQhYL^Naq_NP69DjYNT zx*cVf``_@Z#?OYqMo9CK_{hS>VXBL^uqnm94tKzW*(+L3Xl&u2wrcmG7^CrvV%QOJ z3coeS+k>KCA}1Q9iIUPpNolfHSVvLq8MBVcy_9Pm*zxbK*=-Z>iMYz#go@6QCzbrY zkaU%Re-i`$hGZLjx^dIm_P5h5$le9 z5Z|(R6e0y?{3gt(490}g8~B?asvz0)q7wNkMo;ipRC3vsekuIi_uj*DIY9M?}8F8tf;s zE#C(GWy5GA@R|*T#lQJUjJ?cB_j<;ZFZK3JxNc|4?5#k&e3&QW(yAd}zI<%k&H(~% z%AJ;t+~~I>De1Q(DQQ7i>>1;as*Nv?J~~n0j#$@uME~r>^6bQlM`dA%IlmDZ1-VD& z$@Yba)dpO6}3Cn^HAo2COvfvX)e5`p#qfqiWk-;abI zA8q6Nk&yTZ7wr`!K0QQx1&J@dlm$Z}@xdF~D@c4`_5b?!+0F!xKGHs~AtI5^g>*R> zE(^{NCHyJl7yheVn)W@t0`$(({r3g(cfRp`beJa7;HSSYEzq( zZa`=0IDR9&u4{T}wk)^#qZ8q1L4eO(lhbm!al076%7VejEeQ6(FB2SuV{{8w!wV)@ znfU#j=x?n1gS0 zzwzr`kZ=5MN-Qaq=a93xC*brGouduE<6BmoER)SVNmM+E{d{!ghaE(bJS{#-T6~nW z_$+BX1SY=uF4`!#j@9LXDUnq>=F0Hl!pa=3@l%<(0VuLcZ)8gCTv>Kv|5ZB_(VX`X z$4^?WhlJ8Tk~F zZNZTfEjUVAaFn#*A}(k>YKLeSu6O;TdS@yei_4U7yuuxf#IzyI$VcFli{n5lA-EOF zt#BjsvUfdR9L=%fV46&Wm38~X;wbo;c8B`{I#QXOkG6mD%0yzcOekrYP|`B7gxJcY z+Of(sq;O@*m6;nol+3(`a+{+mbgRo0DHRrX54qDSk+Lb38;BBV7_}pccRB|nMm)IH z#+vQw#LFqZQk-cr4K~MBU#y&DsZhKqtkV;t5===6rlbT%NYv?7`#knBu~qYl9n5#8WKFFA&?55v|fpNol5}G)E}4DN}8Zm?u``jwtP{%Zz_8l#h};iDxdA z!MR~^^^(c{@?avg`UEH8*jI+t>}eYCv*A=DxK+t5y7q+Yw&?od+Tw)kwqky0t~JGq zmU%J{TJ=$4EtgZo&2Sw1#da(Ayx;%vfN&}BkoNfBwQL6+W-6@0efK%06AfB2MIG1i>BK> z)R-~Ru)*o5EagP4b&ZnNHA+f(eAuAc?0&HDsI)FOzGAvQ_d-RW8$O8J#meAHVUl1< z(bg9F{5o4V2G`~JH`_n!vd6}Fv2mE{eu=yk7{HDhgZHNGHo-Gj%)~}N@qrXqF)(j> z>GJkv{`1$FD`b-#%Nh~tgGEaEV3CrtX01e9AVIYYv-Y&GcFTW^wPh)+H7Bz61-vPl zN5``Y@i-SbuZ;0X%*rDr<&jb{kE%T*H@CWc?9qeHSiJU?RSP*8ax5o9M$he0+Qx^s zZT(C6?6z}N|I*Hy{^f0bb3^d>Hrsq#`MW)H!?Pz=6Om?BE+5yMjGS}5tZnicH4#1gyGSg8R)I#1;#!NfN_-Lo# zIGm{t7T~x|(1_y>!3j9-6r6}-b=VnPjlXQ52QttbZD8K~oBysfC+&4U!L{0)Rhk~V z&L_CGp3(ZriPvQgDcBX)#wT9q6P>7L#&2tAGqWM88DD6efDbQWU>6??ai&F zNi8LPQcFo)#!{CFGGec4+cDUPbsdjIqu}hsA=!!5*@;85M-0p!F$lxp?8K263CjrV z4B)5?o|SSR0=b8jc=9+nXDx_5db zy_3V7YCSE-0YAN`Rtr&05M_8k{w5W5%p@{t^b?hhl7B zuij_|l|f3%ASGomvRqwXwU3(38{f5ETqaY&eB1WTO)MSz+3Jei(|c@~X6}(Q_!GCt zxINZ;;{j{CR&Q1}t>o$Oj#w5ian0U^NWD^q33|WVpKUX`Nd6|{7^TWdbzoMu$@Qz#SrGnJZROJFV;R`^NI=Fjx62?*_RkN)L0}6 z*1JiA-o8@O+gD07AP6k{jQLx&|KnwIQ><*Z`(INw4Y7{hEt{2zWz+5UX3Q9`w49cw zaA}bkEiFn~T9lGYOSPr6`p-5`v~!zxmSTqJEIg{M==i(~b3*NXb4T^Z3{iG<|91y) z0%;PK0_vnu_SDkcxKgeNtmv3HFq2(fA=5`pV_bICS>-$_XPE!G%~-*Pg+*5?W2!Ql z5$r8fPJLxb?&h*`u%C=g2gu64ErWd#f2&|DjsxW(!yuV_+9@~^XLgo*gB@fI=T1Sr zoMCn2IIRIc8?0PzPvBqsfQl{P0(0KIUhY)Yc?s8h#j0w2)jjiM9<=I<&HQqb(V2O2 zrg+_jh6{CW%D!dE>MN5`w)T>3^#@Ao50um&EF*SuSG98wjCd@I$-vTVZU1a-Wwv%; zwsugqwklg&ovj^`tsR=J9iFWnnXMg_tvw)HJ1$#WldY}I)=tUR)@5sFW^3nUYv*Qb z8?v=c+1lo8?aFLzo7{fH2hOu#Z9K5OEyd5aeFw$r7voT8CVqvwoNn|uV}+MFNQ9D( zfGH_2kzveAb$SII?^WVPyn0ZstlxdLnU1%6v>n*-c1c_Beb&wESYL@Z)jHPo$Zf!k z>K2T%I5(+uadtCe$<>wJn$fdY@{&<+qTGoU5gRGw%5t?mpn2Z7K3P;foUh2@Ww;#Q zhz_Przl%EO)>ID?Wn3*4;ns{vYi^bSY`cSnzhpq}u=Sl8FzQ z7XPm)nMJWC-7T5_#E>a0D`&)1u&hXnmK7x}D@w^_rP|%Qd6V`zd3|7a@91`gXX(A& zNJv|6#^#T8B;GKN$2kVKK4HWh)~5sIs((FxZx^5YWSUIlApHJT;NGt2`oV;Iy;dRo z6r@T?n=ML8xJ6>u52`i@pPgG>+0oV2zGrUYps^E6+j}j|Xt%j~5Kea!!-KJ(oQt_`!Fi++| zt7Zvh5Q1*KuNHqSnm#zSZ_-XhChmh#lBHFTl2$!R>Is$-`(RYHXW%uq2^F(Pbm5+( z-+_4 zS2sZgSF;n_Rt*#tRIq48qg9!iT|I}XOi5Cil3iUldi6|P%}$(~oQkT9%dW0rDx;E8 z8I@gq!06TEa5X!zHaQhl8Jb-^oT*eNrBa<;J!JIip}2}E|KwCuWngyoAg0nkDV6@& z)s>@H55(2%#H!>}s1mMt?)X<%?%{rGPyTmT*SzCKpFXgySMG_8ZjHAxn{0(QX#?OXV za3vnx+WS)MIv;J>+RLtBL&9}idok#f1?g`3Epk_)qhOAQ&x^&^>;9vy`LRxyHq%C; z`eI99rfzAM zcoCk*uu)^}uMEC}qlS#!1Vp3DYI_0y;!Z|_+Z*x(|F(@`d`iv6A*n}yp~ldE_~I~N zAHKw!J%Z4OFO>AwlaexG4Y5V4+E~K#wuL~BDue5B)S$0H(kERKNLo^GNvMhWgpI?0 zyY+aE+u+GeV#1?18mKTSDNITV(`IE&RPB!UYVfdoHtv75C&z8BnG2s%UA2+AKjI-< zQ(cL5P`Fxk75|nixHF&IiC!BQYv4e0aJBLg{2kyhAcG`pab-%jt-9{fn(C3PLHKtR z>A>Qu&Z~vH^U9Izn#bBd-Qz(B&_5ve2(CJ(N4!4^SC*D`&a6HF*K4OE%R2j(bbq8c zlZ%#GCu7eO^)XdvrYZ8O_kZEf!U@(*`}&&Q+2ip=Z9vS zhG&~bW}8N3n-0h}jmtLGWSeTUO;fT>b=jtw*`_(!rn%XshHO()wy8PWv@+Y&mfOgZ z%QjaJWSL9~9O64q$*k?_Y`06)qsjgk_ISpQ-HK241O1%be&=P*EhFcTWY(-}{|GBD z=H?zkhPkf%{Hnj)!-d>_xhG<|&8!}pomKJZp$qz9bPuR=Xj8wvvzhF)QqC^Hp$4My z>pY@Dm_Bbx_3%dy?H!yu6_cqWqY|AzFZb|q$7-Rluysy{g*XY7tWsXYxm%>2agG{g z!H7^A1?iV^mGero@T&}7z)@rT4wB9XvC&a!6dELbE^-hgJvTX&WQ=0oDTz_MTk zBtBHC49EB!AUso5uA?W(BLc__YBU#aqob`(tU!< zahw=jiQ~wi2T<%6)Z@5+(2nEi;8`5U1aIMZK=2NZ2L>PFh?^~7J2?0%j^l&vahwqB zgyW&X-Z)MSZ~m@^y*BKRD>e9|+@8R{WQ0u{e|yHRzlVH!$rYU;+9P&-7XHTTgow$1 z@iJq=^`2%9j-!{s3D-+wTO-8pJ!ms+B&u$0jbJ`?!kM>;Ei0DIXl%hZ`{G?s_jgF3 z<~lTWoVsagJQ4d`wwcMdU*W7X`NpO1_u$kBjKfySWFPdn!OaIaxerfHXlh@6LMt9l z^d0a9&h3qJi+Bp#HXq;C)C%|SJ77I7Y)|Ex=EDe>DeODod7L$qdh)thIDbU1)-e+$ z9WznVu9MsifZaWUzA|P~?K^96k1j37j9Iyxs%PS_qjEp1o-6;pG%GvTH0F2=ib>@5 z{g0YuhUw-+1$OZ`YdsS|D}UxG&wwIRld8c{Jl7LcxjGZapkn!hOX)5XlMJNovX|Z==#dLF@;v6_Z33S z{r~ABIZzn*wf$l2z-j2PE2ksF1?9-gK+kQJd%Ei@fLO&nOu}?PA8A80eITFP3T^Tz z9~0xPJ7R>vufIQs>>Z(Gc7l@f8BJJ-KyzM7SL+zSU)H?wFXJP(WmoHwq|2LN76%A z^~imWq^kG$Z0zT4JLL}TKfT{CW>znrepDS2Ic56nX+D3|z%&lYP02l-J7ZJ{20pn5 z0Q%z}cOEw?w}Ggy&P|=tSxV=vxh?bSH-V0bIF6`666lw!(>%2s~P%BW!84H4bYab?Ct4!xFidM-KOzz2z zbgdDry~FVvi6r^&5F|Lnjvhc^XM2NP`p|J0s_)d5xot5B0hKdyuSpW9sB6~sTk}1n zhKz>Gx!0SkhyRG_cf8mC)$-qADeCLXf>+B?EIqqWJl{R*%`SNN?*>(uJeZr+|AEi? z)Op1LpGI*=N+{Ps7(ez8+IH7!xCfvg_z4m@Y3qoE5S-2wus!HNb?x`fv;NuD_4X7T z0IL$;80-%d2a>cZul`C_O*Gil_4TE(wtyYiR(SAQDK|xLVEj`l560M7RtBxO$|NoI4MH|52m|A^kz5KSO#8Qd#gmq&Gy`3|MC3FMS(+I;6^AHqL2!r$TyN;(i^{ zYZBu&NWT+_Qw^F5Z@anAOJ(p5q?hID2as-(l>5MQ_*ekzd_w6ENSuDI488{G1&Og1 zlE!!w((`g|V|Z?Nl-aX#Za5^>`xzv)`IS(56Vh1ZT^VeSdZhUdhP01h8_l{UR}X=- zBcw9fQS~WF`DC0^L>-X$0$ycs2_$9cPDq;DUqb0)NKZ)I&mpN*kK%6~q-NyVe-C*x zr%WCr_L9NPGqTd8Z!iOTo5>pF&E;X@Gcx&F87#;7ErYM)I55cKxJ~d49JiHW-*&+b zkar4xhU4Ji5gd03KE-j@;2$_X6L7${N6;6?p}_zg_maWiuY!?~pA5#}_;hdxj?V;# z%hl;P?j0P9gX3{L zAvgucWx@G4o*3MVW4jD4&k8<?`O4jsM|u&4TNunfoV1t;S8gWyga ze-u23h7<@%aYfBt&3r6Dj%ius99|%szaea`(aYL{U z$Gw8bal9>f8podnFW~s|;3FLG2ujd9{vs&D@fjI@{tfJ3jh_wuaD@Yqa(e>*?u;KI z%V6=pAh_3`fpr7rs`}2SvFl^;_f{sgN~A#_bmQ}SKQoYR#`Q}RuE+QIitimtn`t9a zb!#B2yM19V<4qsDa`<`A`(0g!a6~x+kBIREn!B*C!?~ScOEWeV8^7oT*^rOJ(mQd! z4d<8ZP<7@CT$JyQY`_H>mU37vNd$lI>Kcb+Wdz%*nw-Z9IJ=AU*d0P1QNMwho8#{# zD>)jMy@G#ba#>k_DtG(@EZVpy8VpNTO!uTKzJignGFFYu9)(? zrzc-ZK&|dO;6q3py1b#aYDkn*6+Z9KIL5oYTc02- z(jmKzLF|+2o0l|H85*jv6FhDMs_+I`m`7D>(~9=yhN^{)Rf8LL>$7sw^2IBv4wTD7 ztHN6ZbEc2b1jQ=s2VS*eF{EKt+@+t$8yl+l;29P*6fnbb1Wj1bzTEknF^3h+J<6%7 zYKS#;aD#6sVa#&VPSs2%Ok581H8h^Wg4j(cTZ|$SV)_LB=6FM@2Des0N6N5IQ0P5@ zF=N8Qaf^mkrmCfJQ6oN90AiL`Eo*OXTf)uws<4Yj`${C((ul98$KFWLx<>1U+5g9g zrE10CR=q)En{W}@k{-d%azCOT+3`SiYzp@yc%_C-pTdoQlgGB5{C5eeoe74Y_;QwT z{?=K%VkhotozCA<()n9TiqYm{Gf-{ZdN$+M?tLs~_`JR6y}Wqlfue$O_CSmcE8F@X zHKU1V?7*!;{<2S#xcTI+vpOEFoMFekAFTa*TUp2I1wq@uS?z3+4RyF-6AiOhWraK;sE`FuoG|Mx(yoxoMcNaRj!X81 z)KkuJ17viwbFc*G2g|!ryUM6_FL|+bXmA0}?-~3M$6>*baokJ# z!FZ2)9j-9I+%QmvbILlKtIb(^oe!q;0kztDW$gOrxZcO~s2E%amnB>;F}cxCzLRjh zM{H^+?!mN~_7K(5q8`leN=Z-ZcyRJG5NBMWR}!;;qeh3sOG0fH?I5Tw%-lEG1( zcy~|V=QjUz`WdX{!{>@gD4WEx}e>(*l=`-(uUxy_&8vuzWx>C!h!x`u?3woMTxT7fa5XwP7y z^ypz?`iNILkL;hFU!I*`@hDjg%=wL!J5L^%dn*65Y&9`3J8=*mi)ZImWastI&a2GM z8JS&I5{6Z05?R~UJa7&#W@&~-Y5=-Gz^B7$*wJXh{Px0 zdes)y8l17=cq=&nqNfRqs?j-5k(F6rSkubP=E@Dc?rwoQ&%Fql*XNv9Xr^XLkF10x9W& zp%+DBn}4@Rd=T@0yM2O${}%%4cHtdAPX_|*xQOFO(c>NGc##G|I##4XkeWoQf^>vP z)sP0Dfz};jw-;$B&Ydk+*-U&>B>GIdNc5Rf(PM+bPq>!_;~??lm}Szs^TUs2K`qYl z`{rfA6iAm#+&W0ti!>9`9U{$v#CEGpUU+;)qy;$lJCPb7y(3Z+q)$a^hLp79=O1yt zO!fx+Sfo`r9*BWLS#Tz#$s(~yn=R70kQzifAJQIT=>?EZlyjFrS}oFdAmv259MUsr z0Lp?ZAiX2f)sX%qSFeM_FOiqY=#8I*E(>nOIesO%OlE)hDdDobK z%oIrcSW=lhO67+x$^tIW;@5x5f;o`*SsVBtB!27#JHbQZcTviM21xvfNLkPXiQjp^ zjC=1#kZH8%I_#dQ?(XE#Ss~~+UWppN_U8R#<1F2lvckcLk1IkW! z{G2GQ{{=Y5dl@8z5Oulib3F2_?{brrZB z;fA0O<+fa!!HJ7##G{%Ll(2*PL#6mvZyYf1B^{kUx!+$*Js9Uy+82M@!Q3E7J>)re ziM$-uQzlC{lZ_I4<2f=t^)MX!%2M~u5{G?<<9a;6xNs~nDSlD@rnS+qZ(MBH$26J7 zWc>bX3mi1)owUB;@85p{g?6_@r^7i)I-H|~l0%4WW$Hno+Oh_Fpfaz+=fX>^IRM1u zHsb20tlL5=yqJ0jGeJl! zh;;Nr1g=YG2z(wiarX%66iKZ=q~qiq^GPPd?u*0gZb&1;#vur6N7~2C#g}jAHZ|`v7fbl(wzn;5Zk>A) zz7@;Ujrgv^+$9V5Y#2RipZZ}d8x{s14&#gn;;~!%cNconl34{)U^4Tm-+C&(gtBb@ z$?}DqVT)F-41#f-upYLgp=ZT{pbP1g1mhNCW_H-{;j~K63~RttZre%BP8zb3%rO+b zFsz|*Vf*pbGkB6SaV41{c#O)fGz!BO;)`x3u(2-5oM!*@{v}W4ypA8!;*&?MRuU^1 z*4B7Ro18aiGZ+LRWP)vfI(o6jaqZ!Vpd{1DBh?T}<)+$ln8g|8uy84)amW;h@qkbo zR0TaU10m6-Ugb8&VGCiH3B=b(dS!TFPU4?o&8ovoq7w{j)H6)0U@T>RBGW0uuSE7R zlH?^8(YuUc;0B#TaAeh~zN6m$&oF~+?Y!H*^glsUsDAv(o3+_#sjH+?)I<}sa85EyGqEHTxGdlW@<**WeicZL( zSQ*A*-H3wAA=*{g!+sj@B`9{6Lk|2(G9V5*WxWOg$l+H^ z(wmcw%wf33w07UjYEqjEU~TZRGk3*)rB#rBEp~D#Iw5wZ9>u!WkLA=RqV}Lc!LN=IR6su>LGNZblz)`i&hN5uUpr4gw_QR0__U%8ddM=+0 zj>+IH$j4%=4B7aj$-OcFuFN86GOcI(TTQp!U|x!}+_%}ZqHHfkCq&A$qF7vy5(O9O zmN@MrRSR9Dd||N!OVH@^y@G9?x_U%DQuYERnZuBewSTJ+>8_?yZzV|45zCF`gh;DNWbdaY>=`{MJGf`yP{b94oMVTq+8>(k5nymk!~Y8bv(lQ0S}*z)R>@KyLrP` zlKC&u!2w~L5GhyE=oyyjYKTnhx=M?b*S=?)xBxnyKQnj5J~`B34C~KqheapE&eWq= z)BIRY?dI6T$F4TJ*tZiMZR<0^3wPbRYknzjFFKd-h#$jtg58cTuE7}@U6d{wGk0an zisM`Am&x<2*15};ENWTNx?*u#)k#ZQ+v}UFTHD$imaM26wj7Vo_ZdEX#N3k>?LT+P z@L8YbVrQK3QE2Y2Csd0LdojK*TT^)eQp?~k7sX)Fd6`H2 z7^*!_;Oh@xcAUwV7&K*I&)f=;Htjml=E!TiIe+;4?1HN$nXyztxO7hSaN^OhpNT~s zzYY?+`p-A4vP7|UkP$^E^dH(3#k#?lX73H? zzT$Nd4vlp9#K79WSBSKn1%a@aAZ0yeT6Kf0ri+w$Dbg!_o1M{x=y*t@SU>h-Ic2&x zQudLmg)Y*49G#NjlfU+VBp>Mr(Rr0e{22BLksf4_?juMy;#z#GILeIypHoMqa`${q#pK~(x8?WuQFdsWdawVA? zc*Kuk-w^xN2G_oV{qMNOw4UL+USW8>Aa>@i*rQ&C(cHF9bVBS*J&N_4g4p+mo{wE^ zcCn8Nb%?C%seRxqOwjFRMvG34NBkH@33fZ);GI%pFSD|JVe^tjbK90QwhW)U7;Evk z*s>)WQfOjw?4?54AvpBUKAs-#AEgco)xlamugHy)GXqCJT_xc|79Ba8QVJj6r&k zAl(_ZFs;XSw3^gD#^_L1NB&xP2A<%J%xRCZ(eC4NS_#n!*ee>?uUrZ8Xoau7$35cHMqtHz?I=-C%y6~J6TP)+?kgmT@Ae`*m7qa(Fu_< ztteK9AIm{H0eU`Cwa`UcBRZ@L9OEzD?m;9RZxap`o!fY%8bVEo^eTh2CLq!SVGGmx z`Oa3;MasMsDc`P%f#CEGGA~7Xm2b20Ugj%0AyTFi#k$Uqksph9xgf;^GG#>$%53j^QXarV(t9ic#zP{jq)2Cn(+msyte)w!`S42 zzQH$cXwyY}<5+_08?;#&Tc8)s(fS4n5uK1Rrd2SO!`Kw)`HUH)PCGwUbkgr4j}V4jehl+G#_sDbV<|qiZ7-WLYvt`ok$L?ikulm_ z%*RA0WQ=J=u?qXx0_gdS8Ke$l^`evRV+%#cZO7_8#-8pjV<|p1#xPbNkg@ehkv6?N z)RvL!W3;)LkBLsm7}JVk751@3f|Pv)43J=uI*c{od`Tvqu}0Ckj7LcY>q|Yx-azu* zYR6K>Buflq4FMVZ4N{~{zvV6~5?sb;voiLnxoUKrc1(0U#-doC`>~wui`7ce2^nK5QLMtX za)}^yeas+r7+WejFc1M-TwlBl9c8?)JVA86$s^SemU@i!K!jpGHhk_%Jaor9q017x z%HRdV*ivEaQy4^>F5AoI==vCKR>t0?sbR3=UY-=4kTIqe#j-Q@8qw`5n{mqbF@w}$ zj8AMzGFwPwtkCW8&>!*{TP`|R@hEzHTb*NBd#CW#ZZKBtPGPfhO?sIyR*n>D)7jkk zK!VE{ZC1waF;|U_sg>+@MJHs8X+^Of@MAf(as{sVj2Wa3V=G0cpQKR|%yE#d8)UHA;_Q|4i8;|%goD?GEyB2z;3VqWEAf?WOBdn%d zrp!x`79L}ZPKcDLM6n9Dw5LGNN2(UONKX|VC96}+A|1f)otmT{XG39fI@W@U_)MZwkraxFR`V@#`H zET?5#13jNHgVbrm*NP7FU~iW^vN}g}?&Fba2x~pYz7BldVl1WCyTCBERv24_6lv4^ zQ8q`HG1^?r$3!P&jA=!&3j5gCq31JZkUET=D>~cZNIv@P{bF7II3X)KSBnPqxYJKh z9IcOiryGnF8;foj7X+^RA?8kC^>^$iC zj2Wa3WAe<=thFu)_IkDHfqcf!mvr1a#GJ?29o=9|YGsO#4e5(a5OTs8*A%b`Zy%Gy z7;RR@4yW^k!DUQzLdKX@6lLxDH36V0bDAp~0EC=ZY(DRY1 zg>H*|q3CE5h1#EsL`Pa7NDLQxzVT=`e4|)xT4NZyP#C)hDbl7t9+1QsZC1t#-`R;y z$QaX#Vimr#yBK;tV+N_?8<&UIGvtUiU5N`5YST4u{_sojq}+)i1r#8^b67uup1c@cei1E0L9Q7SXpo4!I3zw|c#l zIjf%=4! z%bXb?xt0XI=Dhs9e8zAJ>A1b#WgcUEsihcW#p>w?hOx_pu@Ar)ZF=M2B*tj7GFG_P z6P=JTrWM61-0NK~Nc-bxNi|3v#=b8)dc9D~CgbglwB*oc_`WbkX`c!nc?NYS^DiJ} zT2GHlLdv{~wMe29B4t`ptimnQ525G#hFa*h;a7-GDUQ$$9y&7v$Ah_EZTbj89_h3S3df_&hucwl*=&75GK>o=i*5h(6z7 zkAi#ec_mK!j2WbkZ(Joh>{-cJ@`y<4k$J;N6!C@->o~*Z2I*CTluIs|*2RY;A!S~Q zwD3qqbVA=?T2ZXRBbgsV&qt~jy1sFZ=%gdPR&?2hRU8L8Gj=Dvm`hJ7xxb^*dL2B;{Y5=QPEhyGP_TCJc zAY3nu@pgbVoy?>qxQx-}V(o_Lgp4t*C|2Qi<3{NDj2Wa(Ex1W^(%X%nh|Vv0#E;=7 zkFkZ_V60fXv7=$^CShzcQlw2s9BOlP8Kcd`+6~bO8Dm;etitWa&Cv51Ge{lAZV{c{ zIFgU#cH>sjc}O&<7X-J2wW-}8y+x4D20*5D?8GFb%&S;!5}gn!(~4phu1!CMo{v;5 zbZgUXqC;Pg1r;GVUv38^9 z<~C)H(9S`Mv}v?MALc2$FUg8lyhPy-G*xew#TYTdZC@`%iHkD!EhhJ>)@Aa6wV&}?_DA;Of@X4hL}-ZEUDkUToQU(#_$r}u@}k2SdN6YM_%WR}cbhgCI4JTH8&v znl4i2Rjf=!Cq&A$qF9B?^dac^NYz3Y>3Y#&f0My4`Q1RcIMTV4NBkJphe&^5kgg9< zbLAR8lBB_&;Wq;OGu$=SKeJ=rnQ?h%=I5Qc*gNCCNA?JkFZ3Sb?Isp={PmfSN-Xzo zRFS%jZ3uNB6nI1AG11BLNF{^~A!ARP+&2hg?)}c=(DTbnW4XCMAv&6BrodXqUy085 zc*Kw43CZ2|=G@`FTP=3Vs;ajPiYJI7^8zwrUfDXEqwC?cSv`CVjR}Kowdi@G6M8sP zDHzLXwVs5Y&zM2#wAfFHj@ng-v8P4nCp_ZE@Kjh{U!^<986y1y6sXfU&1$+xnO8BS zqT?Yg7|TKW3{v)ys)ep^JS#d|@rd7V-v1jW@K`5}kI7uzuSF+kbns8`Y>0G%LHewE zrt?T5#bY`WTY=kIuG!OAHz!_(x(m5mjr2F)O=bu z=@_f&dN1=ThE#Msq*1KGNMAw9K2o*NMf$4fXtPpi)zI%m=Vl)9V|Z1N+LmqC?zU_x zGs*uljJ+z14MK{vY07kehhyMk^aRX{i7h| zp5jbv<~*zEB4u91kcy6nG>TOi>7S9Zk5nymk!}>7bfj;Kj$5W1L!{MT!9|3Pf^?=q zdf5CVq|B=rQql2{MzIPbeFrJ~NYz3Y>ARw%Eo7lJIq!*%%;!L2c-O1%X8>Qf#(pU+ zib_r$NI*h z4WAyIU*G>KI#=?DAH!cl-?+vg{fi)NF}^XS-fFtFfO#p>Z%HclV39J8=!8gVR}||z zHiG=?)_3-+K2o*N^^FfjM}4DE@A;AF@E+OP|Dop_-|Mb#r1S-^8^%5q#yX5|9LC9I z2`*!_SsD8;bJgfL?+%Dg$QbcOvHtDHa(v@sT=5w*NFCq!M0DT~xby89Jox8@m>TSX zzYOy;Aj-ES{9Tnk34LSSSE<20;i_<7Z~>4qt?w*KLdv`p=`uQe7_4vf#2L{Ekut3) z7WaFOg6kW9$7vs_TIl-5r=l|oM}CFp>YC0I@{xWfI%L$^|EcF2&vn-~QdSLp&oK6> zF!m#)NSpR(v^l!IL7SB^&P+wYGKR?Vg}dmfBGZauJrYHMjr#_Dwj?9%EhL72gud~JLHZ9tdNYh=T0dKygp_#|Ln=C9>D@rew07b2z67^SnU^B{ zx9`uUzW2g)(Fu_$ANNJ~T~y)P&gog0yYwZBBuF}vD+ z2z+05oG{2RR+1rOFCj(R^!QSnqw5>ASsCLjSQKo1XB^Q98Dm;etY1Y@Ai2KLQ;@oh z8Kh2qFB6?3C5?*UCwon}KED=l50;Y5>&(^URTlcjLW8s{L!^HKQl_I`T&y;UPRJP3ieeS6O??Ha>l+5C;~N#CQ;rnL$3=^7 z#Z5`PFW5|UZW9er3@SXvs=CWq%E;<1!&pUzjFlrr+H~j&TSl&r(Pm}rRNq^ik(KC# zj4`b!)|q}R$Hz9$W6U6R80#lG9Kmp8b+YLlU%FqdsGOZ}q5)+X+oTap=y6btvBUlpC( zp=0g;isu`9ch@&kYSRUVv9Dyv*e*zsHhrbV=IHtcZC1t#_rjtRGA3z-u?qLXm4ei* zO$Mps8+=2$q{w_GS7nxD;4~p)TX>8e(p|<n>v{KK7wuY+!~yR%?80 zZd(##w7Hm%iB8BE@kOx;``9*lj2Wa3W7~Fvu|cBq9CEexZyUBh{kGK7+E$RxGDxrG zLJkRTZDL-TOi>2`TY)k3%Z*%g#2iZw< z?h_4B#JAi5DO|<08h>+96WDR3GX2@78Qlw3{o|42EZ7$Ysh)&2D(~4phZZ~!o zq;5-VkUF)XT6EHVW3cGRJq2=RsP-89W_KA&X*cdQj8$jI*czlroAx{{i80z-%*RA0 zWQ=J=u?qXxE_sX@qz+@diq0^k$X)bz-sPGL^J~)((fJTM*8W{R#`y88FY9A{wnFj< zyJpDPrAU!By>hzE(XExVSsB|KQWWg?rwpe=CuEFiMX|>Cv7GVGZi3YHF@w}$Yl8`blMcM!<3Kl6M2ci=q zWm-|J!Xv$*VxfyvEp*$TJw+$oH-?E0?@6uwdxl8QFi7{z5a}(ZOkY@SHQh30Ud75( zbV8)GD~eUPO!pECU8HKEi_}I2Zpf` z!q}5YktOu>SxJo1W@U_i76n@i7)NwM#+X(Vi}MIkaL1zi3R2fM3{t1Q?APz}#+(&O2Z=r7 zaGj;7!A^Z#Y0{yNzpm+EiFFE&^K2xk1^nY}6N@^C z9mN_<&LpJFOOY0CMMWnp zQ>IlgmVAWY5>>3#<3=HE<0%Dfb5 z;Z{_1JfsCknkGc`ndm_a&KkWMg2M_iDElzAyq zj;f;IdavksNDIbtkRF+bR4sIo)_F)jJESj$lku^{G|_Rrw=P6_jzLl66OxNmbUdU5V>w7?G+G2kTNetI=moK(eaQLjO8GmlZR9-bdetIA$|PuXFkeDdW`5i0qd;&M~6sf z8Kg%G(vuC+(R?9Dg4@e5FGVU(iH(L|rlR8^Ef~u|dTbt2wa`U6*F!q9<<~FgBRx)Z z+!kqWi1ZSJbZ&;;yT%}$d}$I==A}sY2~kpTy;pQRqy=L+Nay7tRSR9D^F5?f@3{YW z`A8Rt&eO2T+CM);`kX;JKSQJ!8l)4xWi{P0WnPMO1ObM@MJhTT(t@!Zr1g17)j}8P zLJ#TUfAoGnAL$~|c@~yg`xk~tw;H4dFU%0>4-C>d-?o}AQs$*dM}{aVxJX6ELs~GF zgOu|?{u4U2&_&9eOEPwh^tFQ;p36tNSaf~^o2>nfA=2Xv(#8yt-e{1n_>R?dkuonu zx-S8S!9^-M9@2ub9HhtRAyo@qq)i^u;R_od%}2`ll9J53u*}-u6e7LWAZ^MJ>754Y zdEd2~E>h;DNcRg-QgD%qj)$~hEC=b*Jfv!&i}VB!>2BAQuggc;EIPl2P1gPsLZqJ< zq$gyE^kIYa?#ryEiAv!zp zXv|w4A|11xo?4zE(q9{-&wbBox=5LqA{`aMgybR>9S>>2SPs&aVxfC4qZYbIPZS+n zPq!WR+*A2TTSVtI*ktWLF+}=ZgY-l}`no|n_WM@TMasMs>F9z;MaM&0FqVU~H4mv; z=pt(u+J2|I@_mD| zF(sKdpkwVnIrNQMPNX25oMA2a7mzZo+ka>^UEg3{ij=!^M!_QGOrGe3NSRg?Yn~s= z@r_gSkgA2QZ=4qD5XVjRfBknp($hufI^=5YKh5)vt-I?RDKpaFHjJH?A!C~(MV8RV zSJ)g~#%QxL)(27)EMtr#Iw50BD~h$5AIo8El^}I}!yt8h;|$T^njSv0yYAkKY59z; z79BJwA!BEFjP2fC#!_n2V}`LaGGuHAq)3~FU75rfZC1vzzPFgwS#Z!mbVA0MRuqdn zR7SzAO<&7n%pi3bJ5zKxx52NNC9n8BOLR6s$J&3U$5?H58B2LCyyXsx;miyf8;%rd z(>JcNIl4YZo0T!#d^o}NG0_PbV_H$HSDiR0701WU&ST6Vbr|dD24ibP=MLm!?e7TN zpMwq3jtr3=XpsK)YOCoYWnRUQijIdgid7it+B~Fcq1*nPBRbj(7h3E1bnp` zih^x_*kX%L$QaX#VvX}-IqlCk1gYD{8Ke$l-xQs6AG<(w%6T;AebZy?{O&TA;$trw z#=e(kRok5^hTSb%NT7|#`cC31=q(!CuEFiMX|>Cu^b<} zgc!YjjSNzUu}eiKz2*Ow=-h@}t^Jn@W3;ucibs6WJVT^E1X8AT(@j>>MasMsY2g-I zbV8&|D~eUP#r}34Qnk?S~^F`n~vmWK@!}ZG%gc+%wvGy4}CscsWwybttwFusN+9zCQ{{@ zv))m#9yI``L?`5#7DTalrx68r)bu@E@jc4$<9O8NqH{J9=n?b{7Wcj^VUF?pqH_q3 zCZo$eo?q=Q&nb0qn;kXj%QNJ8JyN7i-@4W2=z1n?R>qRw&FPDnq7yR4w4zwUd*IJ7 zxQzWkkh-mwLFzE}L(xfR>+k0fxwKDo!qTDLQLO1y4uk85*C1uT<{DNVKfG3Sc9!_Pf^8o^>xTT&xlVK* zhmPgy+RzWT+)1;(HbYkb23DEYw)a>~*AJPOBAuy`%+Vr6GiJ9 zL#h_Ket3iEFb|Ftw)w{5Px6u8C^}a`!`gpCh;+I^dP9atd!U`A-fWQGnjz9b25Gv-aN}B7NN;y*)#uyBVZq>#U}WlzA!A1rbb0E>h9)kQR*P zApLnBQnk=UdWVPf(sgs->~W;O5StI>C4h;%=L^u78PsTTj(O7wKK1!}`D$>HN}tx5`KQOVPOo8W!nYA=0r1>0KEjonVmO z{D9ST%anO3(nZWJ3@%d9@sJjb17XEO&2NiQlt$bN(wGg(eaQLjO8G`FAu3&=pwz}Lwap(2j+_7NY{zZ zH(`^t|Nao^_YKnfGemlvLE8S1)pU_EFGY%J6(_hzMaM&0FqVV#fjp#Yp^NlE59xj9 z|7GiZqz{SC#jwrV|6qvpw+88h86sV3kWOB2HC?34OOY;iaQbB`Iv&!3u^go9^N^~A zF4BiRq&3yYV)`OprjLlu>BEBbWP^0)hm(*pFGbo^5UJ>RNDIbtkUpA+ zR4sIoZt#$P<(~Zy&PV!~=(tZLwejB`|XjB^jD&DFRZinKM^8rHAtTjq!${bTR&? zEf~u|`eYtbwa`WSl!tWk@7A{DBYj$Q?st$r6(YUHAbm=Z{=gs|v>^#8^HQWI6htaI z9@2ub9Hh_WAyo@qq|bUtd!DrhjYYhk{#taNc91?BBK@^N`m7+m(IDONu_UC-OOZAg zL@GKS(t@!Zq|fCcRSR9D&wEG*9r|{CKGNTa&V3Hj=R>4j2I=#H^iG3xzsHl1GA~8C ztRPa+@sJjbOOYfm8nl4i2rAU#96I`UC;~^~=%R%~T9#XZ?Mfy7r>GeN7Zd^Xn z*F@(o*kCz{yri+w$Dbke=PJiSeIv&!3u^gnY=OI-KU8HY# zNN;T)epo)z-;2)o9i(rBNFOpt-w>ql8Kg^|N6>{-)j}8P zTOQKyjXvjye58L6ohu!rZ-q$THb~zRq@Nq4Q=U#j%DnzR*3JX4ilS@7Hzc78ief=P zP>NWP-c)i^fP@+dT_uKafk;Rogcl81wlb92-0kzfZ+e0 zGiUeCW@mQ6@89nmCf@VRyz|b^&d!$HD5p4;X`4QJnejNameQR5G27xgJ+7H*@|FY|y_Kt}yDRNJH#sFO<+N1u=^eeyc$`{GX->ELoT?V; zPPZ{b_ZJiKE>>Fo`&Rm${=&=$%%~=AbDgG((`^al^h7wNy5`d#5#ddL>am^caUxxx zzpIPVBCts6A%@Q9_d=Wu1SV(UOD@m~s1% zS{W~Oxl@31{r>6>ZnJ*=Qr`7G+{p}vGq!}t7qJ_LD-Ze|s$I;i2jTeN>Gt70;&f*M zd?hrM(elY^ht!>tmU4Op$+?^36yXRnu2X84wUjnmc5@4Lr>cdz(_fi!oc`OQHSWGu zTz&n<%u`Tx+W)KTbhSABHG!PA5vM!XIHc~Bw3O2;QCED^oigKbYAvNX{oUtOwNQ8Z zhsWubofqPEc15RuGNZrM@Q3TPMswBTKN84kia6cyg~=&tDW_M(ZQ>{0DKj3Y)>4|& zJwB(Zg}T$d%+z4zgvdWv&^2U5r++c?I2fn>dtIkp#OdAya@reCsjf|5I;6f&NlQ7M z1}47gPML9?QeD@QE;>UX-InP;Htw11!LG+&(VOCYDi z#OaE!98z~mTFU9wahv!_cgl>%skM~m^Z>U|cdA;bJN<_l$LX4|&L)=2$scNC_^j~Hir}<}W z?C*CJ8nCNlG_=Co3!WNGD2aaySoIh`g>zx>+dl(dx7YpqV1@i?`X z(wrvwoT?V;PLn-OZ?1I7U4EyPnfX9-n(R7lak^@BGCRFRoE}(ja!Ojt>2+48%y^ty zOKDE4_?)U1>Q1YAoc{Xgg?JqwA90j0NlQ7s z-s+SYk5g+Y&1rRRp+0(53w5V8m~lq$ybfJQ`kmHfMt54nb$Y8ft-(&`iPP;yr=+Ev z-e7ggjK`_9l;-p(ZlUf}wNQ6jiy6o1DKoaB<@m_6Pir&toVHJExlZ2_r?uGWOX76p zx2AncTFU8-R;SE(oLWn1PLJjm>P}S)b*INL<2cQ1ICGxgX&q*s)|?*WI{i(Y9>Y%G z6{kx#nw*lBayrB6lo^jxYbnj?u|B7&g}T$@JWh-Ec3bRsT9+AppC0ErZP!9I`Z#v_ zsW^RUlgTM*DW@~7PMPsIwU*MH9`AFiTBtic!Q-@3Uh_77r}QpWVuJ4U1lQ>$;`9V| zxM3?r!(C^gBJ7nGI;2)Bbv{)5pbW zJ$AZPoX*+okowAjw3JhNav$IHl>;*#r`A%M)A~NAs)f4K1|FxUuKH(@-)Td|&}vGW z*T8jp!WrtL2JCc?IDP+nht!>tmU4Pa+$Mg~oigKbYAvNXJ;mo#wNQ83$m4YC?Bvh= zP8&0$-*s%{I-M;}8?n<$)z!7kiS$R}c+;JdmU4>A2yN4yGUIV-Eu}d<)#p^TP1ph=wm7Z#lgTM*DW~*2JHF{onejNameQOy^*L27 z)SWi-IIZ^6Q{DVdn=^yckL$FV>$KmQs>RLNX?=0pc8kd=X(^|-$8F*#-6=C3r`A%M z)6;!URSR{eEj&(N7=C4Kztc0Ac}jEI!gczXIBmgBTZq%sel|HJE#>qMt5aq?POYUh zr)T<{sut=_&+<5Z@wuVx{7%niW|rpkEZ6B?ae5Xz?I2E%+iG%3TFNQa72ouI%8bXU zwUp+xrO&Bqq3-k?kJEo=ZkBtV`n^GV_ck#>->2uePTSx;1lrDFr|IH!x6vtSDW`X; zLh`Tflo^jxYbniXYoAlqLfvT_kJG0bOn=$GPunu{G}`6tsWz_D5^>swoo0*E@3uLl zKKGKAa*9hsZPT4H<8f*&r8#ZqbE;aXJ8kcAde4h5)b%^8d`y>HCx!k5g+Y&1uxbZQ>{0DKj3Y)>4|&be~hzLfvVG$LYo2 z{(XtxX*Xu{eVXAqZHQ|h+A`Sb!{T)HZj)2eQcmf2IpUk{lo^jxYbniXcb`+$LfvT( zkJFAlXaD4Pn#s&^G|g#$57%jqIPJkspB1Moe|1QGpOThxO7Hx|H{B^S9;en)n$s+w zQ`JJtmU8+~+$Mg~oigKb zYAvNX?d@}_TBtki<8iufWXnc=r+t}uMswQ7b^5M2?ZZx&iPP_XH#sFO<@8~zQ)WC) zt)(=l{d`VU3w5X29;X}bu6nZHX@6$0v)z4~?K<5hPP5tRYH_;e50g{UQcmfN72ouI z%8bXUwUp*`fX}IFq3$%t4|&LEOTlQ5SBE@TOX*I~~kS4UQ&6mag9OsNd-jX7CWs-KT?H zr~Sq0V0OAgoW8xsT!&Gk5K)NV&>ztfAExecmL`*U5V>%?g;J3UsMKJ>Rk>P|^ZIi;u0 z@lAKijK`_9l;$+g=Tx;&cRJGJw8oU-o%~MonbFrsBVDJ5#OX+O+DM$1>@ztfE#>ry zxJ~?|J7vb>)LKe&I?Cr%wNQ5&W5$`0GL|0G%kOkFGpo=hr~NV4Y0p-w(J^*PW#8Y zPA?ayW7%mJG>z(-e!wAhr=+Ev&KHj=)15NoI;Fa-r8K9Pa0~SrNwrXSI*u8~Y0gPo z@uC|(@|;f#nbF@z8s|EFMVyXfr#-~!-T#=Jl9qD%oYg5a9;en)n$z*zLfxrqq3*PZ z85$*Y);)WF>L|a{VrCA4aN1wwI{i_c7O~TTsEF#i|DZ$a`;@elQ<}%)o4!w(ah+0K z)>4|&2|lN)g}T#;9;dU5&e-R7TB7RFp36*hou*^1rfniS%@e1a4w;;imU6m46_S5- zr_6YqT1#n8OMOmN3w5Vu9;cU%KW)3;X*n~S&@QL_Wv4|&%ejU6jHFtqJH3J#G!R=tWXmV@r}>>; z$;>hkj?*h#r@7+v3U+$8I6W`XAHhrHm<8f*&r8&LI=Tx;&cRJ1E^rtRe za5BM1o_%^XGj!kGaXQU)`nous#!ep-r#+KQPDx8SeM$2PE$ym}8IMzIDb49M+(Lby zsut=_r!(X1(|4P$!EHNyQ1j`##uS!-O=_kztbC-!HrGiJDT5VO6uV<%k!70~O+2oY8 zl+#xb2bS5)f@u{;8G`d`uLsR&dk?P zb@u6PuG1I9>22)v2XXp#HIq})QcmB9+r&@0Q)WC)t)(=lcleyD7V1uCd7R#Vf6b+S zr*|^bU0V&#a-D7vr?c4Uuj2H(>L#b8rJT~$LVVMmGUIV-Eu}fVi(9CVUe!X~>D|o0 z1UCGg+dJv10RQ(Kr}r>J&r+OydbjKJWV|gv+uiK+Ae>TNv6?2Qq@|p`$7P&II6POYUhr?Y)dRSR{e4=_VVKDDv(r*&uf_vwSo=+kJIw*51;3E`V=$zmH0f@ z=^k-9kDc}sr_a|hIVCOS^i!)-W;{-Q0~WI8E-{7Ox%QBhOjpS!VWY z`}7&tY3mNE(a*5cq2lzbV@*y;OF8}A>XaFeQ)?;B>3nXXewI-!)SW)ZOaoRyFCx+bTjrJSy^I%URnN_AOFX-*gToT?V;P8Txc zoMnDF=7vJQ(-)X|R&%=0b^44rUC2&LWS{mt-sF_Dl+)E#r_8ucsa@7mn$s73PE`wa zr;9vJ{}_BJb{#(QjNX@+xkq!l$aVUiI9qJt5aq?POYUhr!R90 z_0g+Zs5^ay8E2o)efPv+ey6W8bC>4y71wFaj;hhGu+y8xX@e6@PDx8S{nF}`8IMzI zDb49?KBuaMy3^M^P6s^SznS0Z8_aChoWAZl?I2EHXQ%gz)BQ%Lq@|p$wK`?Stl#NkX8zQizU4ZdCQjdCr%#I0CiP7Fl(dvnEKjseUpX-2 zacV84IepvbRJBld`i{qGuWj>jvlAbAj?*Q~=x?XL<2rpuoW8?O7m3rnlTA)ZOF5-$ z-T0JF1JuWPE`war%Rb}j?=<~{OkQr-(!aE6C_IWmby-NiPNR*bcs0a zU*F`Ew3O5Jahv!_cgl>%skM~m^nIUG)k59rGG_3|A6s(dn|81C^E>^3nfV|br_0!> z^B0}IfxpB0i%tRGD1I`YP}8=Idu$bIq&AIjVCpeyv+A)1>T;7y#(ON8>jC5T7}XUo zb&9u?)?**~dQ6;Z-!S|UGx?~oQlwhsvi2|B^jPDgEIoDve@A|R z^w>w-V?UtBs7+fMIz5(KT2@k!KYju}#MTI{)t?-Z3)P48d-(Cq8Fl!@rwB7{A5!h{ zQk76XzUkj0vYfr^$Cr55`fvp^owlTiN=`xG8zSTrwNL4j)~X}%lfz8*eKObg$!ES#_V_-j*M;h* z&8a!+`y}T3fgZgIrnN5A=*P~ zbLgw39%8)TdA^EE-Hu~olvBvnSRkc{K`Ct{tmabsNKkuF9|>!iX^2|!L!@sD)7kS2 zX5L5K83}9Lk+4qG*SI6$fYuvSC!SW|3Ri87DzxxRu32xPs#$O0SIlIf0{ZJ(KkZz6 zziO-g`#WoyX^#GJTKJV`W*cpr*(Q{aEG)>s@NoXRUvqKs6**04jvA>==bf1(-t?oJ z+N_Rlx&sy8oTHms$&7n+Q(f^=6|U^GeY(!qW8zes$G&FfBGgz3{V^}+r4oOSt!L&C z7^la+cK2zCIQ^QPwu4iutKr!ur=+Evegh`H=}wt(ol;%#QXAt%@RL4zH~5^Y7V2Z; z8)nW&Ng5lYF3p(lcls?eiQ3rs#&!CpIQ@p5rot)J^;}DnQ_>3Cr_8ucsjhe_`##<1 zbE;aXJKe-g3Olv^Rle_-c?*oQPdB+vw~EtE?6fbOQeEk-OioElIi;s%@y*$%mGKcX zu2ZTjUg{QaDQ)y_<`(M5yK15C^m}H4_vsJJ=*Ro_uG15!D7Np}=>@V+A8T!LN?Kw2 zlo{74RUR*8-={zNoT?V;PJd!%2x_F!yQ%5HGyJ1>3o{AY=>3VEI;-4k;qQN4rQ|1IaMvx$HsPMrlM>mTmhX>*cxW=k<(*2ROt?8j@QPu|?Lz~B0L zOnPiP_t;F-NNrlw!PH~aX4PYFpiS}3=`p$jV#e(;sw-Y(@;$LVjb(@x^_H+DJ~PN}XJJDZ%6mU6nryLaRr zA*!7j*D2K%FGb%P9N+Y#;16!0zE4#Pb*Fza6YO*kGqjFyoc`%Loh(lOWT!8P)2&@h zPDv}wDKj3Y@ltlDdwot-3w5V|F;m7}RXcLYMQh*l@6*4TIU7CdIQ`3Wynh0J|LeKn z73s0RxW|^FMrzZw=b3tp+N^r)R{Rv-oa4PVK4Ql0F{&$GitfV4H+^jEW2gGq5U1Ml zzMq+MPyrnUgC4qPgTKcPFtZGd(_{NRJ+{%-V@Ghu;}_|%{oG?~P$RYJ@~);Hqc*D^ zyEkqeKRG={tz^dSF{&$GYPPqOHbeg7>oIYv_1HmXjzJyNV=4Rg_3-!DA!e3>aeC~a zr^n8yb(mu;;G9y6CPQooxyOD)k5QYROfmHswORET{oSVcruP^#ZjVu2@lwxwOKCm! zFFV!8m^jsXj5;AP;Ud&%zmiH|W<40E$0CVzrlW15IE^GW!ZqV>a7uNZmTGcJ#g$Y0 zl@v3sQ>rUoie|w0rXNj}xP`h?)k1y#Nn|G2X%aJsAc7xwOLUz+C{7dE=^=6YTAIlz zX@xmu#^W?z%I-9oTc|r#E!3S>X67ukpZ3y8Yu;MuKMJZagNsSmX=T^xhvKv{Id#st z-KjgO`qWh^UA-DJeL&NQn!V@e-~H;WeA7o%4WCohLfvW2!*F^OGy1i8P1or$QKemzot`32Q+k-3 zl2({gW;{;grR+{?`JAd2>P~AjGYZX3z)aR+(s7IZ`}AmLlF+00fw$W1)VYpJJKT9S z;Aurh)TtRGGock~q&Dr(H1!y@S@l@7k}9SCb;btGqRhBGMs>wY<$FtM^Xf6|R6hpA zsWz|HVFnj(yuCGjeV)I^j%5aqG29-jeF0>qXjg-u3hG%rw;YS6$Ek8f4pFN3e3ZOI+0D5#AFuQk!1x?d&i7M`V*Qkq9}Z z?O)F)`dvrzrS|Dl-n}Dh^*SicjJr=sFJ5Yex0JR|Pw@4s^oF)iPh=)|W;=+AHY-ec5e)noJx`|-^=3aFLLxIIR7#Y@q5 z)5kY`hOEa<^`k(XYCU!`Gr>JppBerB&&lplFj}0R%;RUQI4#LGIVG(yr_6Yq#!K0q zHt;!BE!2;=hRjfN>E2Kr>;ur>W!JX8p=t~ z-xJ-2TURvHWqgqs$IFvViwjnL$l>Wv>d~=*q z?aa7Nsjhe_`Wq7QP2Z<2xP`h?)k59r8O+dc`y^oWuPI&kli%r?%-jKn+CtkIo}++1 zG2US~g0tRC>9I5TD0mhU)sBL*ndyjsPl}u#+17a9bN(J{$qfJP9uYd*9UCu*)3e#>TX0Hs^~iOcQh|Sj ziJ#xArN>)L{&2o=KfxH|PtNyzqK`3Zv+A*hOgjHKW9)Q%z>M2t)TVf;y-oqn^&UG1 zocHfsE2sJxYsE}$Tv(kM$%(XyE;ig@pgY-#34>t-Kk(Md?Xio~olod!`k9U<)K#m% z=`sCDdK<3iM5$xzni}r?G`$CRrPNr#8d6FT<1N*ROZAsheIIUlcDz*MZt7!VR82$W6Z%|zGR6I* zk$iH$`w206&v#}m=QWX{&Z~ZPYKcVX8)a!DM%Db2TaQz7mF|j(A1N(7pXf&cjYD-5 zH1p1xGH=i+pBeWkpm7#2MSt%kzUfCn7j~h~8*23GN5Q$wI5X{ye>TTBrV6DVUT>Vo z%&qtoKk#<0dlbaP*SU%Cp71A1Q(Z|Dl7!Tql9qBhNOhdUXfq@;u2ZTjUW$&x_@+BO zpIfLqRV~z=c4g)%R8Rv~zDo|)J;CoZg_+*y0Q|sPSJ&wbaoUxg9);5#)wN@ykA7fG0axAspedWc@c#1@JODST!BQuptwW6xA{nE2F-U4#!sDc@cHe%Fg z{pAz-)LTy)*W(#FZaw3r6fr0@ax@j^jrvgS!3!Kw{@j_d=}Md#cMnn@#Y@ffmeSTg z>D(TD52~Km_h1Gy^vX^m{x^S4kKxLvK32Oi^8qvX2U~`_2d@=h8HtTBR_kF8Qe7j< zoIR*JB`xKYt~=wK;}l`k490azb;V0P5-)x7r-~*i7}` z#CXS8PcC&A23gE0EjK5@*m56n0Xis ze&DUQ+doC3-kbU-p%c7QUFTfsj0D1;5{7>E9tEVX#ztqS69{sjj8w`nldWh_8O+-P7~^ed?+d zt2ddN50S_KX2@YBoF^_yZjM(bDyru&GX@NP;B5e_=QioqT0THMjj#sjhn}ap9-rp) ze9QC!1Dgy@=i6-qxt^McJN2m2xlMA~IQ1~&nWLzMYVXiJr1<6>xisf7HRa98F~twg#H$ zTlC+`du|>s0}9mB*!a;M$#QDqy{ygQ)I!xiRC9dO`-d5~e~7b|()wqpuYZ&;y?=%= zL;IA*&mA`;PxklEa9{rnbNZ)))81j+KjYCqR9E>mTL18}ttvkCj}1~+-Wxd;ajtu3 z#`R9MTT5x)FYtL+Z5HpNNajLj)Uku7YsO>eBA@pQ9q;Y9say|^9sZB{-o31~XN0dk zsvf;Pxrfo7i+%0Mb=uR>X-_Wg3(riF=Tldus6;2UnMNxRanS>xG?M?SgdEQz@riTX zyK~e)ij3r%d()4e?E{oZ8IwC8HnBWbT9%a&jke89jpUBW9XTMDA1jy?8`Y=0u&f|I z55eq6N>=HBSl+0q1B)q(rVlAD85b+*UQif|M*F9A%k4XKcq9b{CuxPE(P61Wvr`AB zXJx038dVaFW~UBmH@HuB)`0S&KE!$j$8$D@!jgDTx)84IEoS{naXx(!H>}bZlz=xb(5*MdM0?Wrp@o9oDLK zkJQx6)Kqer>sC9k05ues7nM=E`0kN|3lA^(Rg}G(XC=H~PR_>TVr>l?J zz#Vof#?nwSmeMNAsgd^#LAkjT%1dJTd4+|vr#osuhcxWobnJ?<*x-Vavhut_9=bA& zor<)kp$9W)7{xokTS-ZAi8F#SdPjSv_3P8CPp_!2)|B=|#bqh&OUoxrC@v|BjY??` zZHx#~99S?u=JcXLwI@_tu{$288Qr64eS7yz>(;Y(RI9)*P*9Z89@WthE+v^EB?V=9 zBXMln4d(WBv~pC_4!2BhUv6RV;ygM?%L7H|;76wl7bV5qzT^)d=4v(YuC0g?H>jxa zaCCT<(S#fcPjQHtIE=OQsriL5byU$o&f_g7Z~O%ERv|W&h9~w#4yK%i-UP`4sX zwT92pfQ<5zyt0DgB5ypc8_iF8o2b26MU(Oh3r1xXjfzc)rZh<(R94u#pa`=p&25;K z2YaUT%xE+xHIvHW7)zZn!8t0No}nX4`y$*#G24Jp`i)csTt%%?OcymhPQIy zLA7<0+PYb4>*lQuc)YeuIs+U|x7*EB>=*oLW^n6{w4dT_#3(GDFx4I4hkI0DB5g$rRGd<~O;Iijr=}KSN=MH- z$FnxqIE!FRuIkg=^t{6SzOl(t26MHXVuqz+ai$hXDe;39-Oop725An?9UmJ%zIYPm zK>iTR5nhX+G2BguKb7<1TpcYH`k~^yFH#jhPEG5dDm|gKwjyOIOkVnykGFK2f22Ay z;9>N+v9P)hIgHXYpF4X@vxSejzqk)MXxzdd8C;I82Wy?=+g` z8PmyCUmlOioq%D_J345WtA6KGw!c;i;;f=ZYpZl;ft)KxnY%Wlr7D(YzBn!^GMxiO zU8b55b!n)s$jsI7nT@Abqe>Rd!UJQ4h33*6&WXjwnZrXfhqQ~Yl!&A29vX32Fk%^F zlvL;1+>ZXBEWj$P6L}G%08|GYqU&eZlgO+zBQl&OE6QvYdHK$kB1)F-O~ zL%W0bnbQdGlH@=49}j_wJBf!-&e(#{ywb(Or?hu$w6ps8&r;NKSr)WXOJ|w*D$1)P z&4|*hjVo9c%Pud$y)sW#Dy}9IuZWs_rKGp>f;-%PkPN_e+oTxI35T^VL_MZ<>0?4! z32u(w0TmnYpLdPZHqSmEP%vif|Km@DMt=T7qOAn|27l6mi_8PXsiXIz?e4AKA8 z_1rxFx!r7q4h54sUi`)-owWHV!~HxOIk+2@omVy%j}Scfk+8P} zNTHd=IVr?X`Pq4;rMN#{9v$pi2dD#>D#i-o$O>8)8b@4>cO^nMsomBdSzTC@(p^XY z0to(ije5k@@uLmC2dmb>|Dgi)e1t}EzX`D-8W2aYci;&3;OSm2H$tshkGKkS3g5@) zW%7S2ZCyq4!u*J^_~LfXaM+$_XtO)SA~@nXJXB zGJRPV(jVr!{?odM9op6?6-Pn%m9D#jv2=}T;o@`+-%@g>p~D&&P|H-08P((dG(4nD z?bo|o@ARHg@3XIz_P9PN8C_UB89O~KrGxt(KxwR`Odi(7(qf~FOPtH7fR!N{jJvux zFVgXrh38knRaiW86@2><4<_koQ;*-=)rWf%42vUU+`0y`(E&>xV-aUX6K`BJs=6CD zAB*rH504}b?M5+7>O9e*kFry7=u$`F?T-AAVtj=3FOQW>7CUNHpzb}}_69~d z)(Z4!svqt#TPQR{EwS+yLBG*~z2c&@vcXx#-q(1Zj{nmo2uS^I$yfNeA@(94PPt_6$tJvOn3|UCWY-o$s zy`S#og^bhtdw3rXulEHO=0Qw5ntBIv@-~}0`ctj4G?e6a6YWvDzKro=Ib?F-krba9 zLTDT(L-ICEQ%$^3@7Samp553wSv{T+0cM>E40BoU;xS_m zR~Hivb)cHV{zJjk(y2xHbWA#H5o??2e9%3IdAv@97mKLH*=d8wqSV6gdD6!?Dzj4u z35`=(SxGV8yvxm1&!Bvoc)@KHPDb?P`~OKkjV6$=Cg9j9D76o7vthF>H9N2|jAdND z;KkM>v|_H9Twp%oqlBwZ9f?=|{$Hx5al=)r-TD7pr@GBx8~?_xrE3voanz^*G3%61 zH@m`yR$+`@*0QOGR*)9M@ka-RwF;vFp1$LaqXVPDpvQUgKUC}5_0}kN8cO+XSK5cT zqf(G>pVs79RjYlC=PR)xc?D(Ni%T$ai_1&$WA<8{%MYkmER$VQJf?(Rk+yYwZeJXg zeX{XlHI2RWyrTSAVZ}UXL_aw{J)hFJea4>`ZP5?>Y%EOwU5);$Z^4d>o+4FhsA?3{Q2txV27P{l%f^VGFcAd4!b|SpMB0 zYA3BFw9k*I4sHZ)o72mA&YPcVK^=dmApUU8h^f2Om>MhWJ!+aWt45EgWW~8v&CC3J zHMkyjJjQJP)z$5PFB&rrv+G=($z#Td)?0j#TSYe7O-8 zP?Q=Wi!Td>6E6QIsI&L1&iULar%t&%wN%N|MM`cDTlc^c&sizC-NKQMM#TOE$J8d! zSRy*ZoZqVrZ(LjXwF2sxl-9;pV9==NobsCB|MFy2-YQIyr=0&&Rs4|F+a}ACRl{45 z!H@6MzV+5^c}(Xx5;*k9tiwwWg!5!mJw!A;e?0O>u>5qAemRilAFPPwH|%Ht=#QAC zgDkGOgK!P)+>RVrJP=pw-beqIo#8$i4JmF>rsDim6!-Y?tBZ&A9GIRUJEsJ7N->{` zc$lMECN~xPEBskEb+oykD-3^2N2!C(d!$%8MNrRq6!QErZgwB;FFiWlWG-USRy|AO z*fTyu2e$i$v$55gc}1fNwO=EpOZ^ybO{m>h@tm$s)~SQ#TLI$l)5x@zj_Xc*fk7dD z`<8xH7B`E=#TNqfd%w0gElw%~)y;8qk@J2S-Vq6zO?d1XO9b{Qom{jP5IyjBPAY~8 zA+*CYc@5}YY`I5$YSE~SSYfOzChy=L=F6e-rb)$PCcG{D%VZvR)Rcj-65Or|Iz)Nm zksC!cfpsq_9-k8{!OI)LwZ6uFYtCWkD>TeyLqyypo`A%gD-% z_Q>p)-MdeET9hsd*ppP$Go>w6f}4Q__@09ac~j|*K}!2Vyj1Eu^6k|t+9N$HvtQrr zjF95amw3omCMY-f4urnF`)2e=&FB$UoW59tT0_6(?x?5rjrQr4(KjtSwR_ZiX~7QI zyfTd5kPdZT15yp>o)+!aGoxF-UOn*kkY$Xb7uu?>f$*5zBKJI#Hnl87#kADdUo zJANeIW};^oG)2j6hiF^e4{~;5tMtCvebZ7i)3JV0;;n5%%5$PJpZ=Uvef)%B?mhQ< z#9R%|qR#n+-YGLf_G=O=@4YHfgUAf?kTYI{(D1`=mYS}6+S1%^3UF^br_i7t@ht$f zK5t!dZg*qeFD#Z9!z{jzIP$e-YUG@@9ojY0w$`oNb!ywD zW1F^}v=8xP>sGBgbm-8iQTw*-+jMA4v5TwsP8gkhUfq(6IX@&vUXricZ8?hGZ<^$O z{UE+DGLp2;^Tkp40=lD}@2=wS&ZFof*H?;+0C$kSMhhE#lUy`%bXv=irKS9V{NsG< znEu(cNUJ3+@qGq)+K2R=UoX(tZE0JSIPDvpDbyV4O>o~M?LeaM`EGP{_Qmr3h;I9y z26qVYc^9iX>EGzlXrJ~CVEBl?t#KKcFNLG;zGJR}ZaaLw3Cywh;;z<6iTHanCSCdt z*nWtAi$q(H?g210=vgqLhB0CjPoR;?m}=~5#J?jkZ#TV#7KQ4+HAV{h&2N< z1L*~%B>Ww6li!cP{fzj6QK~*ySNoP`(xGpg`$QP}Z`z_qN28|w@g=yQgtfJg#?@|N z{@~m^4jurP97~MML88rVpJ5!l4dzqfY`XYj;mA5+zU4f@*e`d1`vLLEqr-LY2J?h) zHeLEo#RbB=!@1e-P2g4`e&LvK-AlpD6fQ{jE@9?!Ud8BlHgrEleC*h8-Rr^JBb-gQ zA?lkW%-fus{eBGYbHwj22-kfT%!k6+bkCsv19JfBq)X^HH|s6{cP8TNk!YiTZS%|D zV5*K&T##-ZFy|o+M+(v{MEvB!aNW*evV^ng(zwbICZBV&-)q5@Bfd}AAipP!PmDB2 zqRpn;1+mt`jN{xqpS}og2IA)yh5H=_ra(BGF4a;h%p}e$8OQTXaMvJSvp8J$3@}}U zv*}hteHmc-Xq;(2y#d@1FdKy~2GR+{EyHnE~d0;evD@7v_1+&AQJ(_YK5Hm4xeF17?(cx(3-Q#laNS%m#li*YP8H?~&ds{dLw7pj zt;@r8`+><7E=adPnDLyOb#I1lDdPVMOX{}yw85mr$k|A=*>vf6>j);Qai-&K2DqMJ z)(Ge7Ryf{{1NR%4s3E~HY4eIBUu1JiuK%&j2Oa0syOedr?BwIhzdNLF7HAu7t>HbFbTp6rO^KK1c zYIAP(`yjZ+h%Xj4NOuF6?ZVmolI|X1_Hl03{es$eRbpfl5^X`c2f-XQE!ZzbPY|Xa z=Vskm;Lbw)ZDE6SzX7vDxFElKh1t)!S(oPfnpY=A-aw))NOv8Wt-=NA{w~a3&ds`X zi=pZ@iIMM+!gLRTsWn}3Hr;xtO`1#j{>s|n6oNzW>8dv4QOy%6{ zm)5a2A%6aKhoK8*oN$4ow|T$nRBH~W18+a6x_}H>#H}w)x0$^@&OWULH+zA zm_LQH>Eh2&L=Fja)NNFo+3&ZAoPzj2%mnM!yFD>-1`=&Sy6uEX=iIJ42=O*|g!|0~ zbFpwXU8?00Va9WA*8LdVRfuQK3fCPCW|D9=UE06Xgt?n@^ZtDY-0O%>xieh%9xzV| zXVazqyHJ>SI5+$K9^5L#7u^-Ey9&%^;evFx3$vedyKc?9aeo|%Hu~3g{MNh&_s5ZF zv*}W_nJ_IlH|u^1?p(yH-5ajk985>yY`V06Q-vAKxq1J-4enCJJKYzqI~dGp;cU9J zen;bgUO1aB)$*e-KXY!@ z{RG@T#6Nf-T=z#Xe+p;QrTu$Im}4KLI?el+u8%q*zJ{6L@%$T@gojl7Y`V06YY0=1 zbF*KXH_k$QyRcNN?KrCTFz%rv(H5k8nlNoRx9esgp7@BWFG#mBnAXDCbgBHg!gS@_ ztotmuY{U-;8>D;E99(xJ(Pq=7&)W*qj&t)m=WcLO#J?0aNcRsgiI0Zs9wkgI&ds`X zy-*MF6~YGTZUeJVxFElYkEymLacC$nuQJ9^ao9|yOCtbuB&kfhz0A{;zHeLFBk1%^VH~ak(T%{)x zBWsap3-Y@MO!AY8v+0uG+QKyC-0b%=a7_^ZL)akQW9B7B8Y9tW)1}YP6y_|>&3@N| z>x6inr&N7Gx~;%Og|q3B-=4w@WZZ9ww2xrr!&tt-j=G^S}Yj7oq z_j)E=w*bsk;cU9(ce*gMI5+#95AHt1GoB6C9SNpHIGZkgez`DLaBlYdD!3VlN9Tv@ zjsR06oK2UW8%z=AYR=8)h1uZlMEvaM!gYIrxj;CZE`1&oW;Ew!zmJ0}LA>ts;ks?X zqzh-$rTyDSmMGFF(oIXB<0`w-kB#4lYKu6rMt zr-ZZV()%vgikA(S{bMyXvl5`Qj?8R{1`@uXd zoK2Vdd66*Rac=JCmEaB`zI;)*?p82=3un`%ey;SAs_R(J&HcOtTvNn<5|&EX){}`Z z`e@a&GQtTGyR{_?N;4>HYyG@s(g*@_Upp4LLXarS)?w#CHoDq zxL=1vTae%8!ko^z*)KiEZio0rVS{x40aNp}U|pIwju)mW=Vrfi!F5D@gRnum`@mFt zJy@5<^RdE2IX92zr@@UtyviG@z98MEVA>03>u1`(DZ*rPZuU!m3wtEu{|XzV+u%){ zKagm%>C)#Nh3Uk(dH>!BE(7tE!UpO70%pH(HeGtIk@S{oTUE}@exCzZ5AhF$4buGy z%pT!vx^({iSD31csW!7NJzuDY_~*<7uVZ(CIV4<=Zq>Jy-(xs8>pl+M(-8ka*dV_@ zg85T8n=XBRNSJ>)H}Buuz}0vsF)|y8wjkZtz)^KjtrQ>%K;?tLe>plwR z1>tPEw13|e=6%l1{rnoZuMofW-EiIcVBQkWrc2}b17VhPZq{7|?i<9PTNC zY`Wz4Ct-GRZq}vil*oIyFNH)K{cAf9{tc$;`--#alHWSQ9M8E~_bYIxBYsHOAl>@Q z5+i3J(Pq;nza4~0;oPjd30!}~TYaGF3)1ZiW`uAyUGh6tm_p9Yx^(?}CE__BhU-oQ zbG2|bUGjUAFn4io*4+s1NyM-HC|vh3FfR&c($oCy52tt@kPQ0`CSEOvv4+D^1B_(kX3ls1Igx>o|nx-e8%cqHy-g(Uxw>m z17?n;SdL^zu+T?Z@&GiEL3UZg~e-=?VGUBtJn z3)fBhIx%t_5^XkJdhfHLFbv`!)BBS%@OevcuL?`G+K%5Z!TcngO_$CKyTSaeaVFjS z!Btw1_Zg6Ab9F1cZ(AR+*TH-#T+sXNCvQlMoQXu6t$p?heRLml zBH|6csn9;d?+cv^rk8Lw-3F*{kT4~jn~%58!Ci~^z;DBKOTb(soK2Um&uC$mN63jS_GaX0t{IVQO^&cwe8jkbR!DI**bezuwGg~-Y`$%oBF!MM!Kd+$Q3w#do z%pWVX&v2ZN1~W;xAl+%gOy}ILI|K11KZWb2ff*oNknV-T{rW zIhgB&3(~z!n1?wx>wXN~g@{l6JzV!*F!O{9(tSagC7j!JS0nzwAK|*MgZWrEn=aiS zUn5N9PpZ?r-d%x6L&Uc+qpiaXSx>gLG^C zofv6|L|c&G(}iitx!Laq)DcDe!x zj>6e=Y5%4Q(}Q!f- zCBGwt8O6ETFWo<#g!rI?;ku<@rVD4&rQ`P&VIJh%?3d=}#}F?(6s|iH%xvLoy5x7R zFwbyq_WL@xw-7J?H(d8lFpmpo(!yylBiCzU%K8u4)G<-1nX`Dvr9OeF8Tdi zm_wYK{nGWv(aA}XXOUvf9Y#tEian8-UbHKff_@L_Hx}{*I3un`%b=@ss-avXEDZ#R?+Xn7?#8=b^ z*WCtYpKvzany4?armCwp=Vre@f@_R;@=@Wsr-5lJoK3eA>N_7yInq@~HouKg!9$3@ zT`OGo8!$VBv*~t(?p`ow*G`IbM6w-6O;ABU#7mD3*S!PGqr%yA>G*vX%r{6|kZih# zz#T&TpJT#x>(xn$oPk7}O}7JL?Z8}uREA{JJslO?jQC5(hU=~d^Sy93-G0#BAET=xbr_X=mzrG9=?n1!61`}qTKOA)`ZLAdUdU|tc`sd9Q!71m+S&ak8n2Kaj36yhos0vq|1q_=`2phZ zo*%Be3CwQcY<^p!zI|YtcTI}4MoO^!Ui5F^1|i-(C0utDm@?sPx^&%rC75TCUPiK= ze`$VRgZScTxb6lp+l8~~lHWaGPD{nLD3Z-@A}Z*Q_`tMq-4Za@2xrqJzc+(<9qBzJ zo8N!HZASdt^l;q+U}|J2&ZbL#>w-x~>VstSdp0VVi1@?Z!gb#Qvr;&l?)lLD3e2(H zlOhd}Y<`=ef^!ki>k+Pd6`0$Ev+2_F?fZp!nsf6!`5w485x+7sT=zjR^Mwo2eMOiL zI5+FkI%gf?bF#v9-v;xka6!83gxSiuS(mPV_apvp&v4yMV0H@^q`Oa;YQ3mVvo5_y zdotonm+EZrf<04kHD-G&ZbMXd@sxv z&dv9|p9Hr9@mc-Cbr*tJB3zK}a$!E@+^jnnx?dnZB|BXA9xzV|XVa~N`W6balymd` zeTsAuzr26A?rbp62xrr6i27avvl{7FBznWs^!^LIM^R}2o@*e{=3f6+c&{aj*iT@Z z<|xkg{>v0JXc?IE2Ptj@%Gti>bK6kd-$kO$=A$}diNh3g6z66ipMX0B@g2gtJ`C>* zR3DCO86?_ly7Zn`6JgHe+&o_B`D$y#t6re$3({={rh{-c-KwZBDoiHlcHO>+H@z@i zHyuola6!5k2@~VotV_>z$06SHqHx`@V5SHcqQExmlO??+nB*&JEYS63nf_1?k==%pA_my7YaFPa;0_;&9zbV6GP~NcVPO?&I99 z`!M3!dEvT6V6GG{NcTozZs**rOYi&KgLvM^aNVoG+$LO*?)}0%%DGvW#??H;hv$du zP62bHa6!7Wgqh8`S@$#O&Oy9*RJiUfU>*`KNOztv3ph9Hu7d8%h+iKI*PRRICE#m0GiHQFqY|we3Y60%EBGDG4d#W&JaBkM6`>d@HA8<*y-w9x*2^XY0Q=uq@aH8i1_&v!*wqJGfp_0F5Ukt7v@UN&3@kncRS+!OTu-F!CWPrO_%Qf%@F23 z&ds_sZ#;we-KF8Wi@>}moK5!>)b|OP9Z1#7?CWuQ@1{QDuOiXr&KrjH_?KYnmMhM- z9-oE=y$q)PWW`;KI&JImJ(uFX-eroj`8W;bYYWqcbMyN&%fVfUc(cnZ_%N);Gr$ZK z&ZbN2@e#trI5*EjkAoYBc&#hKbO4w>(&*f0q17jo1uFe;tPZg()|R?x55Sa z{aKjZoSSuThwdK4ubURWe;)_4NVp)~#lkG(+^kFYBUT_j@#=8h+rT^`T#)Y5!o0w_ zS@&+}zKVG7Yr=Ie0duKvLAuupb2H~=-P@o$3-QL&!*x@@^b;;fcbG7FoSSv;gzgx` z_X`^|u1>r*DRMdzZ9%$igz3V$S@%KI5k>qPVS{w{gQlL)akQV{X7WKnl}6Q<%1#n|0qoC7lufTi77o z<8Q?I11U_ml`x$+H|x^%R#(K2nxX0o(mfN*xxxkYb2nl7aBkM6=a)H%A2Tyt_Z%=O z!UgGO3Dci*vo5Vqhai6JP2sw&!K4Zoq}xlF9L~+UTcJCQ>bp5yHy2E?a6!6Lg}It@ zvo5`Ve*@ytTf%imfGH9#NOy`b(>OQlJ_OzC5kKwLaNSfe{e=tCy+D|J&ds{?cLoX& zuW?(rZVND-g$vS67p52IW?fp>^+){V+rxD`gUJ%kraJ)j<$!qt={+R+OP;3t)bt+D z2E-q|qk?XQbuk^+?+Ak*d9(c;tfk;q3u|j1t^d9e=10!W{jwh1FNnWCt3vw>>%Z^8 z{4QLO?mxmL-AQ$tb)Q0{I^r)fqg^i;bUz34op3?AzX$Hx|^Z<5#mqZ7q0sun6<(M>24Ng2j^zp zZ=w4;;-B3guDcV=LE(aQtISq))#2Q%OY_T#h;I}&XnpVxn3@l$I)ikN7p5`iX5IUs z+Z^$CgbmXD7R*lJf^`29rqYAdM!RlR#OE**Ja4=W=2PK3%BA*PNSmpN8%x#BY2!T=z*ZuLu{UyF{23oSSvY?<&Nvd?Z}=K``@$ z3(|c>n0Gn1>wbuM*_?3QSzsO$E=YI2Ft2iM*4+TVixI!?(Qw^YzF{y&KA10r3)0;v%r?%=x^zEcH{$c33D;c?X1#Dh zx<3lDn{%@+-PhZL_yf;|>%I==W8s2y*9fzbbF=Ov(ESnd>GQ*N9|iM*a6!6n3iAQy zW?i~pyb|#%o(tD~0L-((1?j#l%o5Jcy5B+f1H_+vK3w;GFkc84q`Og=ZJe8R$?tB& z=PU@n;SdM7SW`<-&Z)xmovp_}zf`BQJ;RE(Y_7a5mj!)VEfcZ#XyW zJ`C=A#HYOyuKO^U=Y_NB(tVZJgjvG5S@(HxA0S@#YPjw!Fpmirq&r`jmpC`;(({iu z5YKuoTz3qZ$-)KcUM3PtV_=k z%Md?$X}E4nFkOWU(#;emhjX(oy~jNa@rLh(>z)Uuw{StagN4cC+^l;SbjKimK-i$` zqm$lGinKtYEl9VmFkLw}>(X=L48+$88>G7jO!Bf|U7D|I3v&YJX5D|mH9)-n2jTtP z1x!!jY<@|1pfDG5Zr0rjZY0(BVYu#CFjIuH>C$|4jWD-xZq_BgcOpLPqj24YV3r6M zq`O?0FF7~sehu9Xh`;o4xbA8&-wPL{yF-}2I5+EdM+FBFPhSzPn+Im1a5i1~{4!xK z=iID2oAw9d^;d@Lb^+5-YrI= zEl9V8Fzq-u>(Y9o3*w&&8>G7f%s;{f=~iB++I9@*X5G!uJpu8*g$>d@{%gFqghX4A zZYyEV<=m|MC+bK=y#4y{{hI?OU$`LM@xn~u+^kFY`L9I0>4tFKbTB!>1?gTS%vjFN zy7WAy2=V6MgzI(#Ge|g_F1;6>D@-xx=J&nd0e3ax(Qm_bM}R33E=YHZFgJ2;)_otk zvk@P$FeA0K} zx_5z@E1XUDEY$ZrnDt0MA|+UU&-E2>dl8?%Ib3%+nDxThbW>2@k6=#w9^Zd}Wcxi4 zI)3{j-tC8Q-Fz^m!r64m?-gL4Mp}eq^ScY&GGcxV*Ifl>vv5JW+rgah6W#?yvgv*V z-L{AyK%y;Zo;+y_t_P853({>1CKssy$)@`|>bMN?@jr*_-UQ|W;evFZ0J92d1CmX* z11i{w_-Wh1byLCg7tW^J6Fx2g^90iCNR=$#BSOEI`Yqxw{SvOb8qD{?*>q`K?ErJ) zc6|3LlFe@v6|_UVY)826EHIA=XVWFW^TF&zs=m|im)2J;5l`9`u6rt&Hp1C-$?th! z#v+v<+5C1z1=k|Jba%M!cVK=ME=YGjm}b8wMOq=*bZH%%hWL5Eh3gInbBS;^-Il1Y z49sIl^O0=xRSQ(`IpX_%57#~651glwXtU|k{f*YbbmrWAUU(5)FT@WD8#Hg6{3osl zk!TCjZ7bHU&E&Qc^>`|6^CO^E-wuR{9_@0Hfsk2wR0 zwjkZJz`TsK2Fa#-94hF10N=6mPq^+lFqa7zq#S@#J<&O&?@Gr{9|H<*8gv*|WNebs6x59e@hzHWR8 zTzkZ~2^(}ARjHXAIRS|_o9<}D8iTn3=>eolk!r@{w=*hOg?RN^s=grI)4_BS&ZgT7 zx@ll;LAn>o_C1g%pn`W1UspR^cQ2UAM+f_z0Nta(OhdX0$#(qGdFw00w;U6$n^Y$` zavTzEHos>g)(}jVa5Vdxe*c4>*G>d;*0B|IE1WN*;Ch0YDI8t@oBmGM?P$;$$0bMJ zsH^H5<#2}c+hZpsM_xpt&E}&aVsC+2FI*-3x5>x*_ z1x)jklOwH>Z0pHHRFIB%_xjthPfV3N_63$#Ezw5yzGysQ0ThQ^b z4@|X&!Tp?$I*tW180lgpo8MA2yae%|P6^jdY?K^17KyeXzYV}#igYWI&2LXsFbDBp z8i(svJvBLUB2thp?cYH9|NBYtX&aNQ`FY~gIWuToS1`p$ z(~)fBndbYu5r5*WaNYO7tP#$pOMbrv)9&o#NLM7A-;Sst8}S=jhU-27=4Ih*y5#pA zFux)ton!Y~3l$uXc#~G)x@lkr2xrqJzZZg8gtQpR=JyY9pCP`qb-3<#V15yP-!ZNqgtgUJ%krkjKMa=<)^^afI;hCKFpB=+> z>vY2Wf<&85_jtt40+WW+8_71VR)QOW_!*tUbu+;X6)s43B$z2k*C5$+>HDDWLcF9) zxbE#><_H(0`wW;RNXwCIx>ZrZH;DguZn$ox^DwTEXtU{-Ay!|Q+c~doT<2t=~8T}pD~ifFj*17MyNE=c!fVOpe8oo3xkR4^U!6=~tR z+raD-E=V^qUDY+2bF(ge&+#h6|H=s0t=la*(iDj{o9@Smodf1?qzk*_+;90F++T*Q>`2OpY{0qRpmzDq?4X z>4P)`$@cy2l~6%B;*$r3>)s9K3E_ft7l2uXv;oPcTLl&Dqq>HK>ox?_QaGC~-ACyJ z=0T(5fHxFN0|?4)@`XLQN zvYjutgByqV;No!IGBDQ)XVazg<*i^ABE5lR)1`i1iTLLe!gY6nIV4<=Zq`N3!YGLj~oC-!>^+_c<_&g$vUC5KQJ|ctNu1Hbw<^A^!4|aNRXveh@B5cPE&l zsW_)2*>oGBf;SLfe`&bx-(ad#`9Kk{aK4`m#<%Y?P{OJme< z9hfJDvpp}P>%2F>%(^B*6rNI|*{5&sd1wxIK6rE78j3W+wG?yHE^2eSq#`8p)a{6hD0S|C30`f%Ob zz&s+HO_%!lX)v!LEk&~VT>jKTz4^;PlOB7T?=Ll(yvH1UApgG>Bi*9N+jBX z`uP_y`-KbAO`3t{h)4~PY`RZD>m0<_B8BPh0h2saaW>uAsH8TSr;(1jiH>vgbJ$JCE%J#It$Zm*zQVP4BZeQygxy0qD@1FGDC_ePLtv$d~3VvWI+AYG4S zTPMbZ!|pH-oKhU4V`#t#{j02<~-JuA!2XhP3J4iJ0P1hd_z#TyRnR~-^ zKLWE(IGbO3&i*}^YWL$B38_Zpc%$wha0d|2exQPGh4FU++E*kD`K67|s};C@`20E_ z7mc>8!1V(6g^4qat5Y6Kj+}!;o6Rr17t|Td(?}m8C0dSiTCe?u_=yi!@N3ZR0H(We zHeLEVV%cCmdIa}X<|O0q(;aQ>=WgJN5MT6Yxb7-2n}xIK(({zDmS@FQ=g{hNeHwI@_vf~j#(7MnuPpq%X(AV+~~_zdQlXYrgc z($m;4&j+`U?jO7>jHAKxUj-in5d8QsxGvyMUSRN1p}xl8{*Sja0h6pM z?|y>|gQC)aAR;gdhzcSz1Be46)z#JA#Z*@nRn^@y1g$hP4YbU3kKH{C<8Dkeu8GEd zOUxJJmKZe}#T65c3mP}nxFs$zQ4{wVm$-bt|NlMr)~)Jl_@3{1lqVBs?!9&IIq!Lw z|GS*`%nW;OV7|Y{Jg(=tpT6-9BUisk@^;Um^L-l69scG!j6CCS2j)A3|6a>8H>T(K zS>WA)?}{$}z%w`V=QI2{3{5^vzdW1gzRLaOx7_x3pT#pTPtR@iUFCIO%`@-j&nNk_ zk-x8c$FFn$g>Sv>@4l61K9HW<=(|tgeIMtU`ri?wvk2-J@ehvUB74Gy94wS9ysc~gY+9tm%H)Ysp;8`#y7$H z?$0yJ{CO{bHf?}U-^+8KOyZ?>549 z$9<4`AO7gSjpp$H?w!LkJNff;{zx*1$;VAR_X6(EerRAGL)LeVXMR6Dx6yYMM}IcY zJmbUQ`;YKvvhx6wSLPanuL=kw=b{MksKs$O&l_s{s)z&wV~ugo*;^xQ_@eE~Nf&NDyyCw%^o zgWs{3!`AI_Jol&I8}o zdi-M8b3^EI_NVVKaxs7O-$rzKG&d%A=B8W7*?vZPV>pie3!Zxg_s2drK$juk{av2f zpPt+3yQN?D*m^kr!ZYvu=iC17=XvH^>A8)*t2)&m@yyZ}hz;=Ps14-k zcX@9A7l|u>>9)W7U7q<>dTyidK9U=oz6@6T7i=MaHf?}kKF@QH_*eYyuMT{7fDi5o zy=Kxg{MG+$6o*^pxhJG&HyYoSyy0m)^9}x-@HOeRBZscX$9Zm=`}g|#!1#vn!FHax zEIpThH=gUnh(|_h{H2qg$*x^Ga`f1P#w!!0nOdh-9ov4N0|yrOoU?cFs{3sIf4R(0 z%F~^JXB)NgdTF}SzRzYMY*jNnkw4=jhmHQ_mLENDX8V;Z3wy7;cJJ0JS66mld0=gE z@9LG;@7uq4Wou#Y!u;xj|IxjAaqq(Fq1Cm8ecgTY{Ljic%U3;ev#GK4)dVy)eJB``Y%}{K}dx zE-dc%ok#c|yhD$jryu-=Udt!1imzO_`GK4H>~HWpH+VMNw*JOH6cgHZL3~J4{4IZa zOZxP0oS5hRmS~xnqesf$)`x%d-wD?Et*zMkTYvL{7W{v~xxXdm*pejrx5*u`YQHT~ zuAkv=>&pdgd2Zy`Q^jna-~+8uGBj~@_732xLoRVTGg?cPQ_!=Yi<`eI+dNBk(1KHw~uS( z@lvPMZCA>jYNI}KO1eovOW)3rFR`se5mhT(4^U@v%Kw*`=y6B6(6qFnP;uW$O}*2KRtP5@yWus3y)?r zyHoMenYP|nDo<6$%Z=tPA__sXI~N~n^FkB$@|jAzGm??zF2!f2tJ4*&dTF*)t(C@V zm5~ReSKYPvm_Id9tySd7^q#$MdiHL`XHA{^;X7A8EE4okqD)E7wZxHu5h97Z=^V_z;5G5XnY1kKARn zm-D!$yEr@Dtj;d1T(!KqP+PcuVeiNT)9;Tj{(iR7s!miZpieD`{XE#ZN5W6>%7~zB*N^kJl=#k+akA?pb^eA?m=WzBksOWw}&0lcV8EXv&PwPA)!JovH7r zBYi#I$;AAW;$tDiQVzM8pIW@%D$QlI^>@B=j1;>Tr`wvTck7kTT%)z4GTxoWfv=WJ zuze;Pn~U#(8+Ps*uXG9;L$5k)*sI1`kk_x;oJer*;;TyKa;4d6mFkm~c0TC)6rU(J z8at{L34p_g z#Z%GcP1*Aj`-*jyE+3gLL&K@=1QhoOq%d^%PU$YAiko_D0-Om zLUrYr?~+}{zD$?$SKKwbTt&*W8Qd*h9B)jQs`Z?RN2klVS`&gXlRk0eiCz$YbKKTm zqaV#)o)I^_F#FCi>3*eME`e=?K>kvdx+7&&-ehG5Dx-^GpRP=gF%wCuZXZ^>Jr2a_ z0Q3BW;Ma^XX0tuhO(8#1TE9EI_`7zc;{YD46mPxVz};+FLxBsE=^b=#?hS3SQpXt9 zJYC&n`&RE}!lY83*}(5uydRHFV4;zA>CKxSqN!olTf4fIoy}^P*)FiAVtuFkho}1; z7QEY-=wkPfD}U5Xt7m8i=yxQ;5U}wc=>vC6HzhmMsN@)&es-sHr`iTi8{JgKk=oWS zGJi*me&%9*ytAv_tdzUCw$AR{F1wSV38Mb-o7-o1&?S>tXwgycVkbqs3mv(m1R;SS zX1|{)^Y+AFcTjqDK%}_xR1Dd%;tK!QUEH_7+r6e+-n+c}y7vBsg+2b*b5GXYazgYKVnqZVHmDUKepR&MIB>4BFLe8Fa`JOmAOi*i@3D0pHLr-)y^M*GkAoE z@A^lwFTfUh3zI<0)hE#Sosli+3vdPZI;|O&DzHR=0-j7D6M_O?KqxXK8WfDePbxcQ zUuqL5x=E?f#1LBh#!Sa-t%r~B!!z}EX#$+z=$6JBt&ZjUudG}<7NbgiX4(YS#luG8 zV$z@zM_p!t{p@sOdU8Nc{c9`BdY4h9z2d)Xx>t=auFhYzcVU7cdTICWh1FF`!bnEcAg{95zXz6w5c5z33V6|)>dJ;ShN^fw3xL z`Wz3VT}t)#bQMgL*_+QijA=jd*T0^SI(KxWTassnXO3uJ#CvYZb!vIq*{6O6-@bjd zyMNdXjMZI6x0*mAQl^jhPcQ5%?L{d=flZ@W!;zU<1D$p(|M~#(+N?~sz{0z5nx$Gc zjlTK8YmY;n);Xkh)95>)p53eNG?9T) zGJIkKhc`xf6}>N5*l0#=B!&MFKXBKL6hAt8JHT<{7$2dT&H8?7lEqwH$`1I_aB_(YHH|9hG{w z*{V*LKp2y)#!M4MyJ_@Kx9|nVYU?;vDPeeQ_V3EQ8}vgKsnVFh``k2o_j_fN*iqTl zEfL?T2!>aipz2QFbndTTQw`Pil>44(?Sd@hUA@6G7cCE(YNA-B)!8*xnly!fv@IB+ z(wS;ReCJL)dj}vw6)bTgf4?hyh_~9sL+<8rJ(+H9-H+6AWxBy+$4c#L*&l078_!*E z9-wy9=r8`%DBDU0F@j6L8FiT})(J}t z<7xHapOU1kDHI@nUKJH5N}bShZS-OrEwE{H|7NbY8vvm3@fKzaUIo0SrNCG<8$u=F zK$<@uPyDUZn4GLtAYR*o_A2;eYP>bu@`MhYmQScs*&%LbLSp2zahimQx_GtSoi)bN zQDCTT@%$JnvI*Wbg}PhjeDRLZzMEe}0>z-u#Beqy>WxlyVpnCSFYRGWFclGq+9a1G zajz3cRd&jS7Mw~bLwEiYt<)T=cBV^BQ3!4WR9QT>h!zt(+${U`IB@Y}ppG=gz4-NB zw2D}yJyof6nxMuK09YdFx!;==5PJ)y#7xdF;YM#7y&HqJ_Ni9rI+l-i{s!u~V zulDh*PmR@T)LFU~t21XY`^qUOj+vGsySpZ)L*~CAQd!Ob-44o#ERW&^)*me`p7~)X zI~}`am|0q?3Z_i4=u>(8sZxh&ph3$RuOpcE<$;SBA$ZjqM-)sNt{Vc5wSdr?Nfj&6 zZpKJ_;S7e{ysWWHO4B4;Vr0;(+^RsR#90!$IWyo_&Sr*ENfI5;`7i|h;Nr?crh?Ao zEBk^xnStaj4!VI@cThF3`#OOq^Z6e{N2MwXJGnnU-578(Ht@Hc@YFJuji(k|%;jwlqp=4!-UU-%bA#;gkn&1npu|wY> zi>jU^xKGLgq$>+AraKw^hVq)OHq3OQ%>lSm`biWnsKf5;mTt+*I|XgZ5G65`IrST2 zbCXLNnRm?wEv1kCPBPV#>z`^mb2w1CKZdI{YEx+Puq#kj_3NNP8(P#S<=(6Q2wTV-reHmexnz4nivuz2*KQ{c*&;G z1qNJgA6nXd?aK1f;-eQlgm*DKcw=s=TArHT;dTBMVpT)I)yG>E)~j48nK92mwqzob zHz%7uXXX3^R;5{+S#QSl(U-xs{7TSCC?{U^4A}`Nf_n&JOt?``Um==uLywOWST&(P z055__b|QMgyaY)hPTSOpm&yiz9-qn1^JJwVMYzk8-o+a`*Mgt+OdMF+-CeUN&FM2r zQb;TlB$9qV4F*)e0(A)*n3)!5maGG=LJ3!HSX)@yv#_VKva(DjkL+*f(Ef#0bLLBJ zMhM_`(PwSB2b)G;!|D{NWwSo3PZzmnqS2a@-`HZEC%hythyK&m`gCchxk93MElgf- z&SB+~lG*A}-APJD_Md3?w33qKiNb1zyu1ZV*5i+@UadE9EyGwgqJfv@%&f;+C;<-g zRgrU)h|_k`*2j*(7C>UdVxg%=@%76ZW3ha^Wn3X0E_5B<(byAQG5gFganTgGsaFPR zrSMMTHE_syWo%}0O1_3fLF%C8RkCQetCMw5bGt2R6Ua5AUM$NPhASBTe6Q{;SZQ8} z|2f^Bs>wxZdyT$vA^bnSvb?`Dzj6&(Sgp|q`8Y_)l*>PSxj!t!f+QBi&ksm(D(uS_ z3K3s%g2k`NZ~a27erKxFY5GTkOP6w|H93ZbhO(yi<9JPoK}QUa)u|D?sCW{8iZSY6 z@wpM%*1yO277eq+v^xeAZMO-V1#JY{ZUjp8DFJYi^veZc^ez#b@mjZPhhk}At-Y}O zz{=v8U_lwPbEur{YX%`w)9zggb7a-N$0r07X zyNdbsK;O2|E0Q?Yr6_bIugZV&)h=vZK>;V`Z#1b-#O91(-0e1*A1;nJI0)rfwWM!5 z7t=Zk97}u4CECI4&?5CV`m>7~$0l~iW+q6chXoRFJwUK9tWyL1=rBcCPFe2D^a8XU zDZmE*52#iK7uvjfFh>I{!lo8<)T}0sdZ$E9@lJFRiT1AM%pl z={)@)VYoSC{EyyKda&H78PZK=lQyRF)Jec-xy1-~5!=e4*#$nsriBY2ZEz&x?DCQb zLXzmPE)ZkR1kQQPeYG5(q+HE}oFuNV5NzmS*$h2s_D{zFGj@_bLce4tbBo7%cYc^l zNm!Bg!A0h6dcTPf8{AedIjOXbw>^P&Vzo4p2qN(pYe^^m7%K8=M@WD*Qx#N-Ro=@G2i92sh6SKdnD(Qr#%v=MpqR6) zPH|DvfKOMc=Wd;)@H>~->I3}7!tJyyoQZG~g9J%>DTM(CAm9^Nd0q+6Qm_FQX1F5o zea(}VQkzhl6hmTH&E$0~K0109qJtwfV!;1t+^Cp0wJNo7oc1$bvQ1HJH*3II&_Ot-0SlC=~SImwUM~Fn!PKpe->wf&=sjQHhEgWB*9-_#GmNRIc zt^m42(3?ifqCf<26m1O0TG04yyaI{nCjU}S;8tK7PED)a?RpF^0p&?`81<{X$FNc( z)O=5a=m|y1uuKxJ7B&BZmuL`SvBNM*o-c(P_ATyNTwPn)ch!NbuO^w@u)aq3rCOkXi-1zK;3>>l z*o}ww9tXWm#qw(s?}4$sn0fXkusu4`e0a*ydYO2ap|J~d)9A&#YD#i(I{&y31{;c8 zNq2x~2;>S_oefRp)rh~X1m6P;?H*WNSm~472l41dynWrB?b|bhKDW4JNGc0{ff3?% zeo&?2iOl8r^6mrssG68qnZHK=>u=+M8`LSVV$HCHA2iqbc%MZM19il>!viB`<}pi= zJ`h_4Tq+Zonba!f(JVvwLrACDpWt14iikMjJH$L`fqHmeZjqXEjRW+x)@)=vac-sG=YN@Sx&%${7D`sK6#Fuof`C z;PndT&2F{^!VnO)HIS0bKhUF0NT$99_f z*=R4!VY`G0cVs7~F3I{dY&StAQfJH73oWnjffluKId}38734izL+Wu(gj#EWtA6&; z^1c#CE+|w=ydYJ5i?vCuF{!9W1R4JalOY3{%(-+_3D6Fhua7(Yj{y{kZh~8iE?voF zc)a}o@OhM{CKl2xja6$@MiK2ikDr~T0V_(OkcH##owfo=EfNYnjFXIywXrCF8n11V zVj~WZy~EoSJK)$00^bfEtTkr=sD^7xJW5IkLn9a9bxe>-H_tCv;>#>ly;+`PDLTo} z%R-&TgHzRM(so@dZG;3rGgYOCaWY91bpC7XH^yLibeqarN$XjxD{#QTCE0SV!tuOV zPJbZZfIqLs-U;?*p*i#Tc_CDcQgAvhB<)un*D3}nL8Bym>uyb7Ox98G1b=J;7 zK@ra>^2*MV301Z7FH<_CSknWvE)iW%lfJys?#^!O&e>q!pW6Vph`QPlFwlk{t_3D!!qf5M36PB~8|8T%u2nrV=tnSScK5A!4yYz_0)L4nqrz{YxqIq`qSB0pki zWcBq+Y?Bw+pBHHkYBF|70;NC%k`j{rPsvR8Qd`0cH(-V$xDydr-WJM&jEzL?1oTgH zr$}R-S;@(cGMuee<`k@fx8VwXK!Q1iGaNx(E5WyH^+Ey5iMuo+KqT&~^I;_)z)VH% z+e+Y6w6BD|E)!pASEQ;+#wYJlren3+ZFNv3?8Do;u(r^+>X8e(*R<_!ZF$Aax!?L> zM#66A~8+e3A~P2L~yxNwO+>_y-sZcS~FQH!9qk-~LGLK`FJ>3y{{l6UgP;5(@!#eKS3_`5Yn62$94 zyvW*@7*b~OPH2k|cA`yW!eC)+qEhf?Olgf_L1b&9yYt5G^3uW$i)#dQ#9Dub-05vQ zJGqt60L7?%T&l(YU0KjxKyurh`RQ=EVZ0)<-_DgN*7YIX;H}c@nS+Ldo<%9zQ`M%I z`q^MC1u5z?a_z(pp+%21S2a{+sjqLBCKo7DS_Dz#Yid-kk&P43Q`gefi6 zIt67QK0QWnOmIJisk+28RoR@SkqNm6CMIM21VNoF=?@X z4=u>WP^HD7h<N;os7p>larSrk3~aVOy>q|K$l@V;6d$*us$-g%-)Rs4wqXFndVf zl8UT{!j)B#>ewVT(h&wuydI(6bc32cbg-S>aB@l8BxXdlAGKqXccr ze*1svoq~_(Vs@}(0jo^S2w=Pj5SDqL5l$2Xkusi+f=Y#xl0|U!Qr&d;I=iUg;urkV0{6|8Li`v(gf_I zEU=V`3bz7T46oc0OnjzvSvXkL25OaB2MRm%zP%8)PDq8Mc`L%Dy}XaopNibsLzeW1 zFIG&J2Wx%_`*4u<2Ujnz?3>5*5}lK3I@608^%5GzBVbBmTMa@4c_cf`oMtsehA~g) zy$u|chv^l;>5WQAk3{tp1#Qkp$?|<^X$gnZ5dsee#B=<_RPt#@-CodwAQLtvdWfpzeL|yzm z`na!>MoM}O4##w9xJ*)&cA}k-TnO|4lq*@Ye@aAZ%Uabd)IHR+)r+W9PPzaES?HT?&*L$7$tK1SvpJayDRW?e5yG!CSJhT+U5X^ z;^hIiZZ%*oZkpvYu|}>zz}iOEfXz_YGs;z^;T3m%1zgdUQs|30nu(M$sBN*={1H%p zQ(#jv5fwPyEs@Lo1^hEP4!DYvR-w2vk(aCYbXQ3nOAWs3XvR{biOE`LabJQ-5?+;9 z=jucv`T%B~sU|VQSCNza8NnG2lztZuEnw+ljb8ANq<*AK646^}eMF82y9wlOYh$*!1M`Zj8)V~@+Iojx9q)}|Y^($u(#YGOoU(cmue-KOpw1;& z+8JS0y@G90wWz|rgkwmyTF<=?;-m_z5=%4PQ%BN6TBS0JJ;Z~M&AL$|3aF}JP=wSR z+tpNmig+m4AqYg8xE1x2Z|67SzdE51oAmj z;3WU@ufQyo+OE5xIJwGI7Y2je^TEo*7f8%w~Z*^_Y_U%i{Yt-=_WHJxq zeebcH3;Qv4=i0L8?yNUXH%VX$;b{~@aM<> z|G!}Sc73q2mquU#p#{Hs=u0gB2((UGMMYo=QHyZ=53$NQ{xTvv64PO0A2wc~HYg)Y z^lGS(Vh_2%+C(+r#H=LU=s&9$eIR(LFNK}? z!nMp7mNdK`Lm*M`4oyn`LmGr^Zq~=M%ZkSc_hiY`W~mxw(4W8=Nv?|VzBJgk!9@L~ z6p=f4Dm0+#x#q$R0Tp6JgtPRqCm~C=^w`wclp(JcILTtDSSFV44Y(#~D{a>WW60xC zC0G=KWiIXMfV?W%X?8(REU%357hudx)x>FG%Z9(-4F43yH`gi8@7>#4SfvF3OCt~O zO^9Y5>!$LS(7jp*$n_2tEGhea&rM0jnz`f2{M%X5G!N@4rBg1&JGy>B<)Ql70siGZ zE6Hzt4chSX8QdKzPDu<#`0I@jCtOwFEVUnEjgKM4iOXG)+4C|2&-|XgkIPh8n+UzB z&IOy4inui_AyF;c>F5C2%c2`DpZE^}26<`H6+~oH@t)`TB+olQjTnF&a)dIjU?#GC z^aVg|sgBP&_KALeFgayzz5@KHVqg`6vd2|MSDP_l{UN(C#Ji7y_p*s3cO6EN6g=Kv z!99Y@qH#*@C26K_hoGvr9G~jnTk|*(E#xib8XnBk-{*47kuF+OfsFJQf$Kxhi=#`S3YiX#gVyBH7 z^uU)aR~DNmISs1sP&rzy3HWJrAHz$5k=SkDOB7h%apjnsJn)uDb9?MKVjO;p;*(Np ze~ut3+Gx6%SjRJ5vj>YsnSSUL%hRKVy>fmfE z7STnC!cLgFMVUQuy(`G-B|1rpRKFg9ks$@_`swGH2?EmFGBrm=-7IIOsyd(yFE+x9OO(V zTR0F$52zv~@{ot>v*h--ZHEX zE25jCxifWDfVjx27uWuh^l~3bE{u@4Hf-fL0=I{D(C5q_$Fr_*ylh3JLlpMBS~CyF zD^x-X&}4hTq_p3~GNh=zR1>Tl5#=9w)>%$9OxXewgacll76MG{DyBM??*tb(k2+{H zpD!27l`8|j&{x1hedYgKykP%@8tB>5%ml@bu@0YNo)i%gX|%>wPdBdM!x@{)v@Y;a zd1h%*vDOyQdwE}bjSVFGtYl_Ilnv0Il+eR3hSnSScH-zeGw5~fVD)>ow>W$+Rvm;(h*VaD`cXMk+mq$l5mk15YE zg23S&T0g%{(!UOU`gc-L$|B^3XWNK^n162pA46>_(-?(mJ&Y;rdoEI06(B6H-C$N! zpt{cHo(x+Xay$vel)?G#RHMUoqf{ZH(41B^EvekS`@jGS{5fP|{Hs{mJ|JjwrZiz% z-^?min`{MHSXn#NrqF?X57vn3fAB#sy6B(EY*Lj*>Q(9l>8=}Ci7v@3OQrL8O0hYz6!W-9{qS%o&D&?yflOp>C z|7?zpbCj%c)%;S^_x(&^(;oT?FGa}a?dSsuBlYQ7m{_#?$g#+K+Et92a+3Z=Of6RzHXDmoPB*>0)_B1BgcM3z7Boe;t*%N@Et zwLFxvx)_<^;ei5BEz%z%C%uZsNQm+V1)vS1?4n@2XPVBh3kCNorGzJUm^V^}&Q5XC zXBj?_ExwG0sxvElr)jCB1$iY_C9B9iFRiU4{>dkxI@z*2o+}M~|A8`}s)>?Rv@4gB zb5M?c&a(GhOn*0W5njY9E_aw(fouqwfMytSsl`h)m}YxoemDB!&=_^{g#EQnGfX_! zuqc(a<=xAB+XQ0Fy4vKN75-UNAE@_;{=`ct!`|xQbikneYI!9wgepcc6TOO2Eyq3h zC7$+_ys^wjh>X8I7zyJ*_DX754M$i+N_}nNG~yF@QC~EZ0N3i8Vw$4*vtj!w!u#V? za!M^_8_+>YRMN=YT#x!}t`9$p?cTwnMB}#p1QK|(r+$-=2 zYE9g*EDRfvbw4D~j)?u32>RRaUv|b-Z@QYr))1t$yOeWVdF$iaOijO(RDCE-rM=Y3 zxlLjh>0WZ1RTCPIs@9CIdqr#V84uUYU@f)xsV*(9Dfzy#yqB(5L?v!vtkTHhA4GWUB?dMT9Sy!$V1^MDH4xAkMB+I zU<2Iv3>H#Sa3;TQPlSS$?s+Tmc|_}U{!b9J9{@Fg)q6Ukt9yy++1t|kQiMKqitLkC8O~~ zJlkqxFRf&ndub$7+P$`TeX97hsNcwNl*`f1=cZYsB$Yc!VVxtF#(GKx{jfM|mG>C6 z7odPNv0u5}oWS-VKy%F$`<{NQbT=C?STG8>*daxETtA0dZTuZm&b>V6vb;%%aGMnT zGi-#0xXWUSdtly0S(m=`!YMyH1td{zm%(=tWQqOV-(^g-=!}sEI|JKAG3S2}K*>>x zr0ysolJb}}`p$?K$mJ%P$F3f6)adOQI$K>PytPIe;-phq9-K(3rg}kTBl6>!W`-b~ z11sbq1OuN2TFdS`IFloc!h=%m9|WEqWN#~RkHeQS!=rg~@tyBKa{H^_Fdi>kaQh+CMMYVo;ccN}Ie>5!W zgn551(6;p~z$Yc(X=kmPU_`xM-o`?&emz8AU$Cf=d;rlX@`=~*SlnDFuO!Am%}eY} z{bm|zb)uK=WvBudk4f(%*r9ZJ@}=#z36MiuJ5izPLFA(5T?F-1tHkahYT4`T_!kRa z?&r1dTlr}U8GwIgx+_H2JgBYtJqo@cw~aDVu`!tir4p>7Ri(;DvoclLf^R_l=(v?E z4!h8#kp7+TQU0s8M~Y2r%c9%oTKazNCFpthwx5qD2IqiJ-V`V%q{@dr^nRgf^L21c zzc+1nMr8Xiw`_3>wO8q%ItR?Yv%&KHqkB20BTo0a+cMK z9#12Zd3X0nrTlW~UWP)YFt>8Ob~6gwd_fV-LUc*2aU&`*&n!l#AO)Sau~fy-TBtPf zh!_3&NHP2?P}7)=GK2?_MJ`7Ym0Zh#`MvR7>3URXw2O0Z46)M|MO~aDR7>1&Rn$q6 zF=Di$=gSz#2$<-r?*m=bEk#kB9!dJck<27T&YC9Up2~41(L+-}8@4yFeQ<0>r4?pF zZT8CQ2jp7iyS+5B_sK8JSSyQ;nHQ(Sc*r`k)ydt0Y=j?9!awE`HN1JTvqye!pqb!I zhKs@%d?jqr1p8@6uPF2w)LrUKfL9@K`2{pYvLo4n1_7$$M|$L129!sARK^BKCwZ0? zS4zVFQ8|HZHlk)1Q4}jYL7+PvS}~v}_<9VF~{#A)~ zFbp}WVo~i`-2IB~4grD6^(ppY;7DgUBg>N+2B_F|K++mt=Es4RVf$UoFeNcOkMA2E z=XG7q%YGCYWh047dz93XP^N??-%+&3zkC{80P{$k%w{52iZH3Uls7s%(4;YZh&9e2 zS%$*|QY;MLV;%S&5rcfppNETL2>P<7=W$QkL z&sOqY*D2j?qkm+4tu~^`v_73QM^>mG+iCS*DDuwZX0CbU>R}#2@!=)Y7=%~0UcB|Ror!c%XQM(Sq zR^u(AUZ%9$Z3mYy1=&{r*i5U8V6H$0Q-V^4A7<66v~pTqP;zYV{2@`qbNU?b&#Hr> z=dz=Dn$s~5iIn)xW@qboqqFT?Baoxy9!B{y%8-^`u}epnLDz-mRnh0^K`69KE`oK$SUI4lBnj3%eZSvUrjL-$vgAc*|sLm{1x1`?x`qXE%7%D@n zHW&9Vj4vNh>p>xzV*n6@W7$W>_C_t`cK9YsRVt38U%wnJx^A1hxcDE%7DWq9L+-L+ z_^VFOM%H5(TS?z=|D3uYMd@U$EK@U2ql(J@=3N!qGTfw2tDcbZH zF9WPo%K}9B*U4vZ+>$LLR9jS1{+y?Q>^irp>%38KqqPcd5wUqtBr zQaMt1J@mrVC_kW?^Kb87M8BaQ)P+~LZ-VIzw0P4mhBmN@AnHy`f%$%*x02xEEs77J zPuJzCBg6VT3@b>Nn{v$%G3_u!hk z*(s&8E%Hj!{P_J4__iO-JCJPry7SGC+Ay2AYUrNXuNpSfo$$I;Qa^!};|DI;Hu%tt zbVdRvB!r7)EvM3sIwH_t+aXqb4-)_j_thOc{9^tYw$m+PmTf12(;aG;z(n)4cZ=Nw z^GckS#a0b>r0x|#JLV;~=SDr}^kXWL2xyh-a#_0Hv1Q2fr9@)I49B7fuHL`AN+p_@ z_?U12dP*zO*e`+I@H3;Oq8DFXLqievnuaP)_Z1{D!*v^n+VrXOcxPYuI)p-nn>mo^ zrRKf6=l>uSBzE`jCNh1)p~(e>rI*+|&Iu^m6tR3@Z6Z2VzfJU5-{&~B=+ct@otG&K zgUT2jP?@npk<{?8MK!fYiE_DV?x__>auq=+sN|73es$CF%(vI>;QzNIwph`6i32Ea z)f9S`E4^!he zk675lUpp5cSXy1YW@%y1=0(-=Dp~S}#?hTdGfE)~1{bAF8ID!!#3`uz)EmQ~SFP&O zu7QDGbqa@Ldq%EWUgr4UB}0+F^@6#OUy(*Nu@zELYgvcbx$SspER7g!5^o(*hCj3H ztew?2^CYPa20y+yzjyf>9j3ELp2IhV7kWf5Y$27;^5^_AxpT4E$idDO3R@s|VStr= z3@Stv^w$`MVs{EtE`sX)U30=;c_LIuz*O+hi)Qao0i`HF(8#|Jw2^P>YDEvBOHq-m zxZ<3>Qm+7@YP#L4{)%CBBa$L!AW6V3bnCei>@?n((cx~I(i>2Q(br1-bp##Uul-M< zMgQg8(x{p^?{#&aN3;j~oqv5>#34?^wP)d3$JnHMv%y~frV!0D7va7gnY!~e{M1yP zgv%XE7r;^b?_yg|!J^9&Yk~U0gRGFQpNqHAAHLk?m3x6=6+Pgdp8mtI=aff8trT^$ zmb{-Lr^8bf{>bkq-xWX)AlFPIod41!EyfW}mizHrnBH^)n5$hHC{pcN)QbF*kYmwr zDOnA^#N26@R^qNix#7Dgr3&)_Ucst~SF}GQVL?AFE%-Tzv_YWJ&!}!fNHb!QLx85x zoVM^mQCL=a*lZ7<(ejHKDWi$(Ulvi3oV+LzVzSmwu|zp_S6vv$M5M4VYr$T1O1fAl zBanzASo$><(!n~l2+%kbsLQQnPr`-TUucSjCCE2oN%XJAh>^^XdOVZq07@3e(uJH_ zZ7EU(i_n@`yhs?~UwCXPkh%u*EYkqrR~F>bAohgp+bI~N0STube#E!-q1j5Y6>Q+SrrP!@X?5$oP zwwL7=(>#aIDI&Z;Bl$J$D^k~zIuwoJDOf%n zugiS>fvt4Ux*7byuIAo9;?3R?;(a8kL8@k`D&SrbumnxBPQOfmShnWW9hZfG*7#||Zt{j+>#wo&7Z*NDTT zRgwNU#o<|u))gaWV`W~8X;%$f&A2kO5y0{po?)R1u^MgHCHGJFy$yEEXfPXOevKK< z;`4o-K&Q0ISXar$3B4e`I-y6iBa86U_@<8D%aUiQyhzf$icMF3EUOWTUKU=b=c9~; zK{7Dz0$9oI`yp4M@Lvn{bu1x|if}~O>8~Z#I2)+QH6Ln6CC+$^UYdKG66}N+R88^} z#+iqjBL*C`XOVo*54Y17--q>mUQZ{q~`^YdV!m?#LU4-tK+Go3BiRCuy2cD@s{B<&NK6Tjc^DEhWBy+U_ z5m>OSFB2<^j6@z|cj3K8_OLB+4@5}pmeLeig27hziRys7uD5yHFzDbLV!%kEexed9 zt)MC8V}?YJeHyj%e0gTeVlt1?uU3{0s1zH9^c!><6)RD2Go(AdN^oO+$UKRxA_9-p zC(_#XcfJy9ycq=b^ZPH(C))8b#L>xJVLB0Z>IC=IO! zmBlQytj$>rb=?ZdI=x2G)Ml~!vzJAUVb=GBiSX=eWv+*wl%iQRH39`LT-mVGSTpb4 zYX;RG_0&uv-=r>PnMIps#XG7Xn%0|4pa@S#g4@Wz_I6zhtl;MwKcmMaE$+g|JJbb^ z8PQ?BHr=T$mNr<7m%ZjY_ze}1(s7DjTHh8DSTk^7I4bga522%r>jpYSK3)9NTe@Fh z75ietKy6J@TQ-SLxJ8%9bsoSb+p$5lPVR&mYS>@ABkG;QQK+aSfvYt>Q-a(l$qxxZ zR%|$r^3X#^3jTIl$Rzbv;ZfBTqvJigPhO^yiM{}l0_Z~W zKY?}DimpO-K8LfUQP(0+c+I0VaGQyzqhL~8bD$vFx#1Vc9ZVRCwz#RsU^`P{f~S+3 zPdq|`Zi>3|vUd1qk4Vrb%yY@_h^h<$I@-mHkyQZDx49-G0C@!Ir@=5mcrulEXI8^# z?#TkU@SFerSLjnT5Jhw2W`U>XVxIOdieFBK6;VRM4P%unD7x8lLGE|Sg!E23aj5NE zr!ZYhucHDqG>jtIRHYOwu3`U-rF0wgyc|=L9rv&^7!Xk)j>05rzssUlXujoWfRxZ* zz~W)(G)SMuBEYhb*{IAui*M+opTQ=0lcQv4oM5d*;@-%2pBqdRE0Z>yHm4b7QPtm} zm-lqA&n@EQ>nN36Yf9Ap%1e1vMgvk$tN4pT5nq3%Q9e(_37?iqp_Pa7eA*{oSz6@7cn>q$Xroz1- zm7xXyvr|mY0%!@2QXnSQTa*uF{&p$jAqpaPq=?_}Xvh}`QeDEAHt?^KKP5Ly=0*~{ zPJngeYZqyu`VwGK1=E6=SizF}_LnT0#{uM19Ypf~`&dxxt1VSXovY2YhFPasd^G*@ zXq4YnRNjhr&SdHOyZj33+#3iy!QqK<)RxiDK}9OE#=Rw{NB++}8DGe4F&d#b+YH0< z8N;ReF3bBCSRoBuo$LGZh64FlKXhbdg>xI$7P1hO55SXUzgdio3F@z`5*WHtioCd+ zTI5u8dR=f@Qm8fngdvCa0T7F59R`;{kmj6V9bpzI!r>^zMzQ+gCs`CU4tOk!c1B;0 zKMJgx+I(T`#F|fmj$Dz9fZjB}XTjhDfg4JwH9v*adZ*|SQ`H@Z(tL&?Oblx}sKOL}vB`(PP6_ePWKMk`d-pc|*-@#zA|Ly)34XHL=QK2_Ki} zB%QxLA+1SgZ6dW)m$I@NS9F<9xLnF_KT;X>EqM@4v6f=TN|Wv;Xb}uJUS$__0wl^F z@4$@rC#+-FLLcKP;rCJnxzKu^+VE!PaeSjPG;%}xWH_AZyRm|f)yz%1BWRqp9JTWe zY%O!NTU0z%B@(I!MhI*Wd`CFNNoTPnnz#B2kxoUkjeA_mp7WA*33RcKj`*mrJL5yh z;Bcna+m?t}%2c!BSZm@B5kvn3v*OoLUK#aR&*FutwuAb2g$1T)CGFA<9i-uy(Q{CO zN%T_@Tb5nx8aKP?ky9=LT-v!f*fqmOLj3$zn4rhch5fNMjckhHE+?CC0=S|L{=DzNCmJ~+0}T#mJYW5#ErOJJ(2kZ#w`3hrc^E5s;QqI|PP)HUeoJ*M=HpM25QtK}Mr3oYMO3@t7~w|!hV|YDhaxEMj8 z(TM{Ep^8$T-6#;^7COmTFN&5sLLvXm#HEXhSR*xF-OoC^Ju_YueVuH}e++nXSEJhE zRj~{9lpf!*Agb2RBZ4wEzso{))jU(U;$%HbhJ+e?S`DLuR>We z7piU`9%dtdFa1)7ab;xCqMhW6D&Oj_T$ASer&D+nJ!{*|<-Lo$50z=>a}{T=s@t@M zSrzrgoaT9vJp2JFO+^Pmtn1s;a-OGizLoXLCRvXBDtQ7xUH=A;qwpy*w$qy|lZAuCXf1$Vz;oGx-|E zUnT1pe^KJ!rAb2P5giD-)HbIGsci_<#3^chR_pJ57SW6{?*y9W2M^c-{M_kQyIy?> z=q+edY%I>BHQIN183HQ3-VsKX2083Rt!`ewo7W~oHMQ^}m|fjNh+3fW8ss~cO%Yfk z^4{3+xm=Z|kkzehY(|A+aN?STOJvdpI(?ASRXf2 z^!-?UZV;mMHTF`S`7wH`mnkE^!=`v`nC|a#ISq+jcT%CTsK|d8zDYM}cYc?Hi;8v! z!OY(&x;s&5pwk$#9eQE@!DfO@cj8r;<(O)AtVJ>C1Tqr36X&BuVC#hZ^}I_ z7&Hc$01_csP8#uOm0?y^QXp=ZG4~NBLbeg2??_t#uZvj6TWMmW^}3_DAoO0^_?3kb z#R$iR6RjYa>hQwYh}ks(Dv+aXnkDYZJ*b|i`o-zbaA7ufEbGn^)G0WRdC`q%d7U(V z&*{bfOU0H@Ak13GB@$0cxNCryV)%-=Y#UTJTW+_D-Ig7B)>NgJ3Tn!9m5#lHHS7!6 zogcBTXF!3p>kL4NliR~AiU@uZBVEy^?T73ySf->y`W>o zphKKwQSkPt5qcAe+MLg4RIQ^J4re+;TQBrZcGs_e{7_Lyo3Yc0*5MR=1G%D-rV)p^ z97L2;;9v|jQE_|eEdT%Cenaux80nsj3*3w;9Mn#B{Ee6978V{d^m_VA0myqy(e0l5 zhzZtO{t(MpJh8?0@XLs>n!gwKmi%sxRR>p65_CAc(%q>;%rX;lxnih3Xiqw}zde(A zcQ=dNUD~_K;g72z1$iO9{*zNgB!GBt%e0FOKks6F<&*M6QomjiM~yI5=HF)(6ocy9 zQ|aWOGPG0bduLpG7hte)txhF!-0_DjKr=1RkS;3{n^(Xiob6ONh(^@@1o2VMe;{j` zTH2+khNR&Kdg(-6SL$UTW^x8%m&ymxU1Zb^wI~{T%v_skPg?@n#y5gZBRrDfTODXsc&2L)>= zAyj4AdZ8L!7D^T^wc#=eRdL6qm|ZoNj;chFrT9-rZn889o=HWVOGspj8Z!FRWjPG} zHqVUopSxNy>2SfUpums;Fs!;lqTZB=8rpbadaEUpeFh z?Jay_vtAXb;DHV$Oq@L6yXaIx3Hp5@f4zf#E_lECb_-x32QD*wgui2J)}7@|+hg$~gv;VMNuO;w(kZ@7r)f-fKX<(D=fh z1G^ViF23**E1Cb0Tn7;7)%wUZ$0x;sFJYwz+q?ud|D7M>=}@tr+>WT^dV$_KSYauV z)Mn){+W|41S1y)!nTPIyLCC>+5k^V3-C0&~2wazK-6?dPDa`)H>V>?Jtl0Y%GIv@6 zC6=0n=^#E!o!IJjEQLUF7O@QUvc5H zgXQs6E$&nJT`?rUR)2xDZ$Qst+XUqwH12@PgB9{K; zuxsEpJ7=dO6rqbd!-08gAmEJXNp>_Heq zZ(u@COgAx@0VvIC${ju)2#T&EA5yg_zml(JhiJ`aEx)85p1N5#R52&*uOqpg8&nQe z=#+g3G>MV_bEK`XtV%D0Dxg^%-x|Co637xJJ?w@~60R4LVIbl;sGj{&?~~1)mtN(p ziK{&jifZWGduFJzTrK5OLJgBo$_h2b<$uNm*}qQ-A2IYvF|tE3&p4SCrW!H)MDP7R^uVl?xPOcIySEyrM6#J>@gjUgw~p=7r8Ur8B8?H_!TOr zJf@B7IvJe`oWMihyGSfCPwt%a?bohd-lH{MHE(>FfK660n|b&mp=z<3Lnrvy%18A?;nnp|%pGZ>(PFSY_ zB80fn*+-7IfDFSIkJ=bXzxG=zfDy_|TKC^^K~fPtL1MQY2xw>8yu{ed>O*id-h-wqQ-7)Zjw|tB4V`6|hPrRnl@6@q(HY=R6z<2%4ftDdG&I zBULApLad{#hUvYktyH@wvcE6}TACmeiO`J+PuTr1+yc_BS^8wN`SQTF2nq4=Kso?u zp{bPA$A2R+g=02Nlg9%Tx(=>qSc=m<^opAyCGVzJ4TQ9o7SKEmxWHKGpy6R+JhxfWe`@b|ujYcP1 z6~b&=y)Ne<$GoEK?MJdv24s&ySES^0>I}oWQkQpNp-b197?e6?_1Rq}Ez|R3Q>j?X zHBcERZvKb&fst|yeiQNl@lY=F1HH(l*s!kBpL}~4y{en7! z=UVQ$F*moFm1Dr(ns}LZ=f$itOsVBD(VoL^ACe56X+(uU4JrhbO2qALm68J;#>BBxwP44xV_XN$F3`qSl@PZVEJ+VuH zZY%}X1x#t%T!;I((rXMF$nRTxNjfz<|29f1{=ujyE0VYK`Vw@CED8;o{S__W~QU>=<7ZMl-zgmHZXsb;b07&=p}fvl^tnh?EHMVyv)Z8{N*i$|+6LFUJJ< zNhk%V#*p#@;T|n&W=X5$+?jC?aU-2)&oXJ*3VPI2j(`;UBJF?Es9%O2Z59Ga7HeTQ zx{^9PVgzxJcPBwr{q>|;-09cPK?Wqm2KT}cx;VD;Js3SxDruEDY5%Kg^1cIAK%5TqVtx9_j6eY7|qSK z!iBgQ39U|p-j{c`_aE}|ydI{d=Bqx#a;dmzKcga;;g-~9fWbaV5p}y87EbDD00>A3 zcWW^vg#DYI#bsY-I{o`#N%u=KRJ5*gLlH8hK*58)*nw@tFIlPF>N)6|^{fgjV?MbN z|BWe(QNmVf%~i)cQ*C#@=!zh{9BKqj*Kak{K-C&Xq3=Rxm_QZ(#Q>yq@)WgZybR6Q z@Y7LYvwNm!>BFXek;P%g#rjOG*WRQnP%Bw?SLkEKIaC`xjuEjIsLil&;Xg2lV`Yr+L1$Sg`7WL0|`A0r@70z{Vm z%TTI7Cxg461x_s*xCkdp(W4F5X?mqV{tgLo`aX6cAdCH7JQw+G-i4NxqVaF1XA@Nj z)z6}7W7aRQsc?Kdb0IvR3qfjJgdspN;Wh(dY_K~vxP9%ygmRz20>sD%`k)voyaH<3 z6BY02pv>gs2+*lcxjUp#S+c&z^ib$itqe@Nd$5>UL^H1hl7w4>r2f!W6th?}*Yx<9 zUe}iIHjNZJiHO_q6IT_yze-OIX@HkvF;*d6p7B1jEFI)vgn9+J6=#Z7N`h1Qlvw2v zo)YU56>AYPi%qMlnCYuLBo!bQI#Da17o3EIJY3@|v;&l(R%K&h4MK?pRtORL5@KkI zI0+G_7h41b?i*48>&!(j6l5KAp%lnK`+R}^VI^oWshc!-C+B;aPD%dvVF}bbZ3A&^ zDPeB!7*S;2g_DM8!^$S5W1=WgYuL*g3A_yYKp|1jl4mre!>GQ+$vX^9F(DPh4+e^5 z_XPd=aG#j+o0yio(O*Po6mq~mq7B5+{|mNAG8`LY6JC==*okD(-cAvC92$jN-iv|= z0?E)bkgTVk=z4{g8UF7ttdqvN>%G!(&Df)8nW*D7Q;|uyaZ3P*flfjG;sz~ng4uTI zi_)Y~RpeFwh!kiaXI@}gypgq~OD!F*3(+cZZ0Tsdq}MB5RM~09FBZQkIT+sb$4*p4 ztPnxaLa*<gY&?*5dvG`&uatVB#7v>$`NbuI zSX%P;L&-vmYw_>H;orVsM$r75%p_2P%oM^!heC<{oM+#jv6pTbZ5yb_?=cWd#Xym- zd>Jw-<{2oti#==K#epCsFwjX@!}(W~-Bhks6dv_b&+^$6q2zo0ZQErOFV%H|1v5<8$GGJXH5ltklv)v@ z8kIRVrc~q_tt}l-y@La&3T>O;-@@94`K!8hui8;-+;0QtVG|1-N5mXAvnXwP!K(&^ z7YOhCJJC=yai3(^dY((9T9uwX@6a0K=TM$=MH*O&zB9R`m!B;hoE7tkB&e&X;7;il zEzs}qUcHko4sw~3gC(>0N5nEfSkB>@B_0aNB;WX#bJD)@P6cxeL_b5Ys<<0AveoVO z42VkKc6?yi>>^yd-OCiV$~(z|6Le%)Ue!h09C_+iYV6YI#3`sQb31 zp=i(o_Nlrz(QOrGoga1p}VZbuFD@280zrVLaqq8D}}uVKP)GFQ~- zkp0PG??=G3{URoEeF++Cby7US?5$9+N!#d5!5K2dX7AHA59QP-5oYp_ful@%idx*h#ylTYZ0mIxkFGzO z+Myy*^KTKn;oDnetZ(LScH~=_hi9S)32_%po5pjV)dvw)IE@{!ZJMqlPn!AP%LErJ zhtzK9`(ERK5e>2Jhmu5#>~k2BK#&gI%7XsdPRe>^tLlqi`5txDcsOke{f&)eVq!nl zEuaOL;rI4Q&;TJ0%>8`Cx5m*QaRRg$Dz*sxQsNo=u(ZdC%Ba?kaJf@GkB%)-HIJD1 z;TTo%lyVT(gd)H%V$9RiRd#3-!w};624t0q8#q!nRLD99IZF0^%zK6hQLWMy6zIhS zWDp-|ho`UTP46(`z7Y z29pNcxVcp*qZctAHDcREgEhA2N(?_0(o&w`1YmNg5Ka8>TCV0{`hfGNT5Hjfr;gQ# z6nvzPv>HlbB6Gb8WwE<959E|Ds#BM#ipFv)heWDPiWg!^3Wh{`TEJSx&|_!<(fLI( z@d0ar*jX22FMn2KNFE@iY*Q>z!l4r`L_8gDA<_B*#x)A(I0NaL1^1v>HM^;uSYIzO ziO4%-NPC{R!HrrFoz1-$vvh?xiPI+>pB85zJ6VzKq|`AuI^^j8;fy0K*bfk(;-=XM zR{sqP77m*8v|=8i5!R##NMa?`2qQcj(>3F~`ym=yLwhfUXu25YTaEb20lymOtUJ+G*4CT8=$gr1!R8p} z0!$F!v823|h*)t{7x?P87(g^7w)G~dA!qMaBRtk2kIC?YWll15w7dyOCt%=<+OnVx zxyiM^_5HYF=iWw;sF`>NbR_zuV+vDbPkiwKRjZ>qRgxXe6Hq={x-oz#X-PV}&6S!) zC7K&3rX-+Ue~DBK zo_(i&Ywu({v1-||SaefSlYeColTV}wKvP_kr8_}`eS49^YJSR-aK0vs5(L}LjV$qo z-N%Dw3T0yawKb;JR~N7An^hj@X?1qqrFHe%<(0Lw*Z)90vM$-aUA0x+)W`K>P%ybN zt|@c&R!zdQN9t{O*1Njhv5=|NohS2g$Q8(rkQhoOJOzFFHD3HI z#}}@ids$JEaGROv?n z5sG$QPTYhpQ1U@px%5J2*u_UL*3iOvqZ`21b2HwPdV7)Xk<_PlS9$Ab=dSs@53rHf zhY-aOv+UyGE^I-*EqdI)P`@s4rc{)UFeq$&!ER~{cU4|!egA0tQ17GQq+czV6K`ae zg~LbPoyMtsfloDJ?K?&)`brG%CK~As9n3{67=vIMzl#wCQ7X|*t9K}l2K)l1hA+DA z&~wq|Zs;+q=&Bo+nnSLIL;CVqZmf(5Sxd{t3_GPDiUj%&Qn7}9AE*v^M3O%{Oi0G@ z&QOHFMsh49kYhluUMN)|to}@ozWaVKL*c~0)G$$t|9mZ9(maw!>TUvAssE^QU5cz^ zEdNAzR8C2%MGW>jrlWu7Z4Uk# zN;5#lmXA+_cr0d65Md2DQW#S-8Z6l?z+Nmt<^W z_W#LO#fZ!GsZ! zmRoU-+#x3v1;S#R8g6@PK;TtYd(J-=Q~I&8+6*OypNEHWwjwa768#G_!{H8O>~m9) z!?xg-dueF$tMk4M(Z1z;=vpoTcB15W8iTCRTl>%|v7YwA!gZ{@R^&abB!^#@#%HTE z-;oadBLK&mvE14lZ$-?6_XCx|?7GI_j zWeF1Bg9w`zp7@0dEy7W=`>a@Ikb8YFQ&9FSoNs+dbu?j?0#FzX%9ZKcs(3*J?(oz` z4(=v&&%E`Lz3{xt)!G#blPPOVg#v!b%UlO7xRHm}f|X=*h0h4zruK2T9p`&y#|dqWFGh5q&cS z@qLnLo5PnoMli#CiV!4K2ZY{FHIT7mLhWRO(2gJ})|8e(objh{#?BhfBK9@D{&+O< zp2h2-pvJ%bo?eDFZCK{7S*Xn);#dbW!b@UeNzBbtkM@^}2SfYG_rRoGDx=|b9abXl zJLD((jy#u+9Biu+L7#Uctp#u-ntTb34RdfvojqnPyLL+f)AX7S2uXkjHz%Djp_rfA z_zoVW6{!K!^b z?Qrn={663qnUDt}l#keHtQSG%KHiZm>ZtMBkh8`$^;JzhzOC)n%FHAd6S~m`RV(5R z_NVF%kKzr5(#`N^4YUO)y39a|5G6hqZ7}Yk!DOJC_tx`tP{{=osx7+h(xUTu_xoYl z0t$~RBn(ow+E8;J^*jh*( zUY}?Dwc$@Pc^qJjR1ST#7{@Nc`M!E7TC>=+CA7F)h0Ifs&8YyAdKDd^A~8xT--)oN zkRH<42lgUrpjKBD^5~#3DRSFs$-pT9c&Ey&6AM+M8fzuPgRSwp_Yv4E%xc=!L;Xi@ z>las{qKbY?$oN-S4@Il9Ju!kcGrT2dx%R^D1F9D0Kv9(IiWTm+&_jJK-gN$#bY{vS zlxvMPZF(l@c53bVBWB1R;W)}Avg$vEPa_$mT?w*XB}WnuV%+7fDKXLJZ0fG|3W?3q zE^$MJdmODn(^$XWn5!FTe}mUerGS7!4%gj_$zrsmVnxs{aWFCG9T*8BA#-MNd7uau zG2H?m)B6I?-6g?io1!Wk8*uH1dh~+Jr(&`rn;Vu_8t4JMC1nq#P!*63yP*=NUXva> z@W+5P)%!ZbruzMdia9jThcXM@cqH=QUk*%fGfB?V#XRbHmV&=|IY((k!1PJt|AC~O z-)j^hYjIzO+|HZGN@kSfSBjs^v5o+Zedh1wI~Xy9rb=IdUj79)2#Zbb%F20Ype-V5 zqjGeGz1AJVEU`NNo|)^c(V3ZULNmKs)Eda;fQl^NN?=(F|1ji*G7Cp+M7YzLUQbUZ zXHH_W0zxUCwiy8BHTX**y@Lo+wE8qm8_E7HTQ9UAygx9dU}M(mNxY%%c|waxr+K9& zPz-~vR2q+da$neqFDu+6n{RSne=I-AO7y!~hvg9|?bF1sgZll}m9}R6Nyb~AMm^|Y zX!C%+eILE7(Bo7(_r=&O#*`00@zf zDQ?b8+jSKx5P$a2Q9#*~cwbhCv%GH~TW##(_jXB9IUuol4iaLSkq-=#vEI3x1_P;v zZi*4(pMi>H10<_1T6opZhJ~}OcEq_Vg<_Fm#d@LNwbhyBO1d{jz8m51t5}XcdB29o zhZRV=VniHj1O5v=6^Q0Gx>{<7J7~CF$*u{|marqP=n0 zU&{D97_UA|9ew?i5|Ge+_+I*q=F@ZeL(exigOJ3Xv$b8_V*WR{SBI>iim_&eQ&z;4 zKand@%o0gt{|EXiJJxPYbhP`#I#=fOy_ii(J5w!UMCr9Ymq17%n#P13OhwQ5d10Sn zFx*cWWS`d;jQ}_9+i9)--#t!i?kKk}#CLG=-=I7i$Nq>%hV8VEQZa|ZQ+GUfXOGIS{N{cj7TkH8>-^R%_b< zeeH?Nh?0$JRKyM0IAHyS9I%-Z*9QP%d#ipA^vw(K7Kruxuw%eX^jW2@6GQyl|3p_R zP-?yrv0o>cu_4;`WAcO40Bqo?6myDKi3QgO#AAQQzj(@Y*GDuaX+@KoLMWZ4gPqdy zr}L3TjWKos!T=R3=le+WQyfY7H9UVU#|Ie>T3770 z%C_qp#htygTe{Kb@ym?V`c)yD)I~voIy*u3TE3tsHoyX6%9ACA<@q(DM;yzm?jW|b zZ(&G?m-YhpH=4anRb4}rQ`GRCVaoL@m0)HG4&X?o+^LX5D)Kn&cD1syvb!mwn^~@%G!lULS z>Fjqhr(y&<^5|y(MYK+OW$~qoz;TVI@_> zQlHfdrCNbff6EfFl#utIiKQZN-KkN#VW*So04#>S^BKJX?vi}z!~)_$lH6N#IfY_1 zn__JCf$~wnZ(a0%N(05jy&Rk=-tB9>(4E z&yKRj^)bC+NNKi$4?4DYd@9jUpL;9()|a%RN4q{(2IDJ3L#lAk@VGohr=&Cof?5!|0bq2&&)ge4x74w=? zuH(@xF_A?s1gul|M`ZavZi`FtXnaA)Tj=6L{A#L2C7bQ(J`@oJQOj!*_o?}%J)BJ< z)bT-$nmmacLG=Y-G{^&J@abuI$$D8ylHX-E5glwdw;416$jK@6dKPLplGiC|A6Jl$ z2$}q&K9skFwxpx62xPG$O|RqocmhbqnUtmSRD|ts(vQNY7UWg05!o@nev-}aW0%>_ z{p$<$%U;Y?;=6=%Q7mH$C-=xe8IlgZVRg%~|kvGzr1SOMx@`a1ex zeM+()#?55uJ|vX>ySS;zZw29QqpU0m}V)Z>MX zg#hKu%v#@Q?a#xF2dhh*z4o)9C3GNO7|4QRQ;oNA5=x?VU};Iro|3rYiFbKiSZ6o+hEjed)==yET;l)J|I$g4ainzCMt+lX!?>y%OmzP(j_N>$oK)}V_ z;|o`_31!v9RlgmD;{$+rULV%#jJIY(i+eG}9}kHDP$9Wi>dtQQFV^odbJ+6b!OC(>pJ#W-djWFm_Lz~RxV~9LApvk<;4dJd#Q1Uh9$$iQqnU%A~=BP)ZVGb&V{UWzrpg>Xf@$23iK*&sWzzVFsPE&#J;am2s4)o%Y>b@I_S`Sc5MZKV= z_%jy%)+#GQLw+1LkII<-?WgjXU#l+;@pb|-5E3V`p`pG^Y=>@jfI{4G47LfagD|TU z+5K3w7>a{2zQG`vjM6XB=Tf1Kn!!tXs?&&b75*=2Zvv**S=apsL{JcAQC5Wl0tysq z3Z<03mB~z&A+wR0ByB~;G?})6GzpW0QrTn?WJgh6Kmic}Swv741w}v*5Lv7&0-`92 z2;wX9B8x2l&*%HQ&w0-C%%u2U|Lbb6cFsA^Irsg$fBPD7_(EVd8pJ+(V&8!yhY!sk zICwe4zzB9}?pd5ub1R{=mTy-SdKur`!_0P~efQzdm$)UQ%?6L`H78s71=}>qN{5}A zJkS^OO3Yhy-G%Mw?sGdsSl6KmnaaI;v(wX zKYL=AL!BY`@rZ!d*%Q=MH>rqiMLQ&dZ&3-@%}Ymf_b4R8I^DB)VH2uu^$U6#lr^14 zD<*n{_|$63GpG)&JOtIytu~ki)|^FI(6D%cTdgvM@o_O4;XlUbfE>FQWp&xLpsHCd z!=#q75bs#NSY@kxu#0f|CTCl+nOJ&tv*pYFI&Y%+)k9C=)??;7sgksZN|pGtxU;&n zuqy=p3_mz#4pf?1>`NUt?-MdAAc+N7PG-uqpLU+@>f}IMxBg^qpmwNXjWQ`@_8xE` zxtF-ej#YCXe9;-m*3O{l;E@}HSszh6<3TVH{t2!+IKlHM$YkP?j_TZ+)r4M*%V5l? z{+lgX*fX9iY1;JTe9b24V@O*sfIBdQF+yS|a$qd3Gfgrd-pyzZ4?-P?1R0F4;vihW zWB5q(vd$bkhzLD;GIp;du2c5?&(ObO=i+B7bd4*ElL_D1IHe;S%)8k<&5WetA^EgC{6Q0EvAkFHcT%?m5g2{rovEXAN$}yqGIRHGQXIb6)r98fuf?w69aOk)T zbdUNoIM2S|j}rgRx5f<8hXB8t$MLLOK99xBFYlVG^ilEh19(2zNpf=sDt5TSFCfJ? zQZV5#(r>p&Ialz}t|8^@Q3`m;pPh)a+LiLGh~`@&iHoaHuq01;2D=$=o2u(U4A5C>y?jo|?OTL73+t!IFTa84ptld!;yX zd+flcBj&BwQvX)<_(OFbdB3p{f&KGpb=BUjg;4&6RSQ0%^2q=W7;GES8`vNgCT(*= z-XA((fK2s!QpelMg%TKb{v)qbnCjW^CNiwZS|{dTekl7dj`fHuZ~J4Vr7)NDA{kEn z68`T&6(|bY%wa87f5)>MZ`V>9u)SzX10HWiBS4w?AM&low0wAP0On| zZmROzj{L)loqOf?5323xwAl%dog+CThkZ4up|gPF-jNsDetu+J2*h*XZjt;W<8`1; zxSyYKR+X12zPQJZ?gl1#nTV1_fXu$Q%ZFQ|v++SS$&YzvU?27Y00<9jEh| z$c%myHgEO*G#&6@A_mF4ya6bcV6vZg&L-`(%cc-J@9)(;Ye&}TyEcDd{lw)5j?~QN z6IqA>i%qoMs4^9=5V36IF$@ldoq5dGkpq^E5mhPpYZ1IdhiUm7?03;%IRUY{|00+^ zvI0*(h2*-uS01I@e{C+~Ux~d+_1$%M2pwA8B)o?#F{?{|_D< z%`h63%*naW4uw4;OGdm&M0CkP;qX`n-P2+Imj(yV6t5!gh zyP`8Lwu+@t zd$b+tKW?o38}I=g)Z|>30x3|x#zn4;ha_|o&9aLz`=#@y+C@+s@V_AY=|wGyo@9SD zE-ulcbDXF*Tn7?GsnWxUf54SE31d+7QKxu5xZnr7_Sh&|@%;S@D+HryLR@-*ulxj9 z)wyDe9R92JfjHKiYhM-DsCm%`jf+85h(DK}BYWuImgX)Od@pKI+ZLdhxX>0=BIPSWUVmBj65%GU~ z@uVKzh4G|FBwq09Tvkq9$94*JrWR5SxM!Bg$DTL}67QJVzZA4tmfkkewWI-C0FC@B zeQucN^;_U3s>Slk-b!N()%~r@8~QBRw(ogA*IUX^5{rry!h>&t+QwU8PkV_{DT1V- z_xhqipBwS}Z8|LKn5LI$OB%xt3ij}`qufI+N}=9IbW4Y6q?ClTN7ib(1QF7LA93QBt5>zwNgdG1WgMOY}6ZDLPR0;zXJK08 zSLiAa`5tht_wNh@O68GHj&2NA-5s@I)DEq!Kxn<&6%+>#-MQR9_u6kNvOr&er0SN%eMq-C3XvS2?tCB*Hxt{(gN~PSaDfAwkE^+vZ7!wo>nE zxM3XrLZr#NmOQK9?*<%BDTShsh8|{dgw{%4_elcVyJq0BnAxfdD;$@tDHGS6f%Rk! z&xU)W+LF2--P&BFiBu#_qCXKHqo`aGQ#S7fA%FT~pB{B>lYAb-!8cPDucFLMVH%3t z58KQwESENfFYNTUMK4v%T{~GF3W()ZZxOVEiz6M8@OMAm^6Zf8AG(Z!+)zOlwcf-n zePRROWL1v5ksH(dkF7ssZGGi(+>(bvo2OuBOO^r0FQ_X0Jl{R$o74i=|MyU}tfI2n zsneT|#yUSwM1K?GuQ^tfy^xr!mgk%zdDH^=<)({whh7thA%AU(gd)F~a z49-Ux4*}}=@)63Ip2I^44<0^xSw#m|3>4;-kK-P&GUAPgMQ!!5TtT`rq+?qYr)LjH zD=Bm_rNy~HkC)wf&`TQ6io`8h6k656|( zF%9Ug5M}3xA8$HK9!Mz4@~PFTpK|qIg6XP?P@F>bK~+9KQ|${%@6rfdvi3|SEKis- z)7-oH&G;zY2IdYQeaPA&N3WbeeYzPnV=cJOxXay0r@UaxA}_f!{NVr3OU^sb4{xVd zkXVr%_2*e}m&VapxzztY{4|v2O_469ERXrtVKQuFVG+L894Ax2TlLa_+!&FjPj#dFnaCIb+mibd4Sez@9+ zDz^){eUyWva0U@oWfhMHdstGj(|FsaX6{eCStBtA^9Y#j$iI};8Ay5qR#X=O?i8~w zvG+`&F9r^n{53QNkhZVz@k#}fi8c4&3jyPfrNv5fHz4)9u6|oL6|rLAE~zu%1j+;9 zT9df5Fo*v%<*?ChXw3 z%#^^{RXJ?uk9V3WR(L*-wX88W*nEN>jzyQNV^x=8+_hrzj_ z@%KVIvo;`3yya5Ta^hV50Vr^g%LQvO_%#HVWzyWO67aI z39~T94$2%WF={}=;d#6JI5#q0}sjYXs*V_)UlR33)QVlBzp@v<^DE zC6%c%kkqt1M>Q=Z%TU>JVOC{xd6<=50Dd=o8;*{k$buW_+_~d4S10CaYP6i%!gVsr zicj4h_`ndbIk}<*M@Fx5hh|6q2mp`5fvuK)qz_2EhQAOXMRPc54FN# z{~YZM4}RfCIHv!hW;cE0Fai3mvudZ>E%BCj{Xgi3o4#dCw-XfCHTOL6UL%K5GRohK zhDL@@Kf|H`bO4{w@M@XqXLt~ug4ptW#lLrnv?Hu(cf`NzaQG=7!O7c@N&LI5FB*3Q zOl>)f8@KU>t`IaIG`AUZvB^>1q};~>8$q4IDz@R7IF3dunZK z=p}^#CsrqF17r}_2VvT{irTW(#tReG#yY)BG*gSbhPf@J)SiJnit46gqt6s*ZVbid zr2wz5bASiwza~#%X>T=$xH$`$UB2q*!OPc~;r#P!)$&A?y!Wltw5P1kvm!aB${E+; zWqVa_;?(#ItF)v2WRrwI;cNgmy=|r0tBw5yWbc4IRN7(C+K>H!2h;m;D^K12Lv~hf z4T#)~VW-3gHb$M)QEsmXAkawCbg{r7PCleR_jPSwB;H0Z)=nfA6-3g z=+Jto@+qa?cY>xg`){c_#>O@yR@pOBGQ}LC>t$do%)iFNt!8hNaGZT!{>vv?&5iL3 zx73ZaN^~gM&l13HRl4ym|JsHQ65ec%u&A}^)SA6C|8o>|tH?%NXr;lDuv3wz!Mr-I}v z-EPq2TOZA*d0D^6HzTAtCJ zkpY6LG@sz7^E=c{bLO7fqfx8F0d{StRG0LD@@}6~p2$f{M+Xzfyv5ckWQ#~Y*TP9j z(<9F*R;$4@)3C;(X&%_CywR7uU{O^$fx^uvg?&^`xsl{ zCq#RCC)bK4_4)>yqL~O2uF!$nTx~|b7bK>b%Nqv8S@K{#Z zB|hz1Tv?jy<}eGPGq^biE}%k;onaW6o%2S<5smYN!sju}AcBs&?T-jN#cQ-adM|E` zDa&>gw|4||11ovQ`hhFfA_CqY>YZ@LWZ56#mE#s~B|qk83ut*PVqyD!b&2w>RJ_kd zG#ev_H6Z*e2qU5&j3}DgX(f9>vbBE`i>!#~C1Hl%di5s}$rwl+Qdd_j5q+5TU9(62 z>|o~KVYKPy(MnaNt?+N$zacHk%*3Os-EQI0C>Lzj&q01JfCmm=5Sa7_OU69Is0e>Auc)IlnZ@W*+HtPJlgJ1QE4BT`_}9y6_-=lh5KA>J+qFo4iHcb$ z)8}^XvB$XN=a|DNSEJ~+^!n$nK3G#>PpmDfE_6w~lrNq}97hr>seVu01OKe!uY^hG zu9C05UpoOL+beLkC*zm*H@DDK`FRi(2(5!!q3v8xW_Dp`AY zD!e40*TXPKHNtZqL?A;~tL?@B9K8o?-@j~6UEQgD`8(Xv$nvh5P>UB=w(oA$i^B6Q z{AlR+EcL!L!$0EQ?D8p!UoSgwvZjr{$~{3PFbtUn8g4mT-euGs2N{$ySBDZDx-C3= ztTyuOqTE;Tn$?5Ht~h>##(yHk8(G+(9;Ky>d*?d(2g(8AiWH^T4r484@%yObt)x_vKU{Y~Q@@I=L zKi*A8cWee=-k>0P9Cvn^12ETg_{aF@rV52x?Cm%{u@8X3H;iLf&D}?fVYDo$D$aWp zZ$i(D;uUpLPWm-4Z!@E|%LNffzE7NO<>BxXFWn2CQJ57%P_Ja`=AZPD!~e#m79-u% z<~ZO4>%&<@3+Zf}BqQlOKdR+e20N}Y$>Ck9iH3IqEak@MZ&`>x5U;-CRjBaN1G$-i zgb`yAyz*uZqRNuoU=fLn&x4jDZ}do@3evJEOtf+`!l&-XVUD$}#PQ}&RmnJr(`jWs z@;5B2az}vvO1RJ-8njqEj~_D3?fEdrzQcJua-D8{EGt*x+q1y9sG2`z$VRj~%FO{` zm(%0!t`vRSk1gW=!AZcdS^+6)GYaa^;Y%+CmsQSRiH%g+(8?}Sq!Rl+s|@N7;UViC1S}|tUX=&_Qf^gjUOdBV*|*n<_`v`XGq6VtYF)z+zbFSO;7!G{ zEdqN&S%fKtam^jRBpH`0z;0}sAK!-7(O3qqW0ml?fvI{TQjh8TI?X_WdNoX9tiBZF z6AYgw|F2@sBcDz@F#GD@>4%=9xP?NfkAFig%Aey987yg2kL|N%KDw*o8(cn&_>>R) z0boZ#NvpW9n-$M^e!8~>rNRerZFTM82iK0D#MsXFoe+?&dU!?yD-Zl%SXA?FQ|u#& zJOd4MGG13@H{Zs-+@&@E|Bre5N!+j!N_5!ao|>t!uazxy8BOl_MOqc3@z-u<5Y*^~NwlzrDOu1vKmO62lNDI9+g>CC*V1Rd-<$_aTchjYH(Pg?so6ZjaKuuL_YjldXD5ncH^)I zNRaNE96Dk=46MT%onao>-FQXwcoEs!iz>~8*&QZ{h2(xU+aZ5##YGE&b= z_ct`onnB*sBdmIlQDF-%)0rBXTyn~7LJBWqRiL&!S`<|QS4&tJd zO|InkZkff8>V3G% zH-s+1aSs5&F=H>l&IdhQ)ug_prHZ;9@kzq%uOjh8gaUTcRbl4cv=r*om0NXNtEte zTq5YM2el0x?NKei!ds|wo$LYRA=+h_MiDJTK-na*{9l*T1-`p;EPFCi_OIpF!@N&4 za8KdO{BCw>ZT$o}ZmrjO*u}@P0Mlq&zXgf+d`udS23#?0&~+GbQ6EMD;0~R5bo`l+ zrTcNyF`?u{HJ?~u;;)}B_lMiY!voD0s{i{LC;&zYRVUD8 z1LR*oz23%@yTB{7ZsPI{)?c^Bn&uMga^y99s@K?)IG8?MViUB~QP&(%H1QLY6*s<~u%&GDMzt6$O>N{ZPJG zm7TVyDo@)>c@PdpLdL)>`=Be>5byTLl7=$u1n27RcyBn?sG1iEHzdrOmPaj+8E-!0hCDsh{}KIB&oI>O-Pu$I&_cge64!Xq%&S>vGp zoSNdx`~plVc3Cv2mw08@{M2-@XqD5CcUE}sK&6{&-4?np<`=t0x^vo4e;X3`pJx%c z=*J*#Dh<80#a3;e3`Pr!W8ZAOwr5`8TP05r?>t~YRha_Mi36*s6aY0mDxg&Xmq zVgY{ZUtuo3VrA!azBLE;o?Ds->4+d!#z}`j--9miNr~d;y1uw2TFQrF$8vVv9_sKI z+r7&t){X^i@pJ8&9@MXSj0^@->h5k{%P_|4VF4vzP~8X^D_aF>PvBXH>@LpFv?dpA zET(+_=eapMO{V|Jw0y)JP`zj00~#gVT%dFxRehL+J%nEoj+_X4dI;ONBiAFzq2#F*Jdhp5f`Y)jh82NJa%=UElTGfQY!TImSXTH;rkd=b2~dj zyd0qqtW}K<70SLhZ#o@9x&gd&L5E}%C#l(bA{L!$B9O2`fv@DEZ7o|Z0+yY}dof?^ zoxQN4q|(SMhokUTPei2L6kR}4m?-it>1HE(^nVUSTV&jZo7zSICfh{>Mol677 zfKu=*+*>2Ol~wr0;bo6hzC7K*ley zB6RIpybix)ay$~?$fXPP$N>eBXR1`nvse(Gm{BC8-umXU+O!bST$&1@$Tz32)9EKt z`UFR7la6XPy;5C!US>#Q*xEX@{ll1_y1W;b2T9}#znf=07iyd2L6!W1#z4>Sj&IUw zR?(xI@rp^aBw_kXke6__#nNC_$=i_4d=^}X+tNL0z(v&!J_T}*LW4$|1nZ|*c@?LI zHF#pz^@G|uqb_G6!mV!!|C%T7rypDcp4pDqDP9<4mVJ~0e1# zdH6H~2jtn`7D7V}K=0W%xi~v>>WV9_3ImuuN+OwRuZQ@#o4Zc((OOX4{yGtVE3LVX zEb9r#nO-&^LeJ&ezx$<4%JjZ_N4(oWb>a!^wznOG^L+uCZYAZ1)IKx2uLu#mYEgo@ zod%G0*_*(VuzgtIyJ`7QH<|w5TxmxLZGHf{p6KwsoC^^*vctCT!9}>TYdK=__YvV^ z{9b8L84X|XWgBx`&=qL6+>k~Y^v5ikQJ0f>>t;rN-dAkmxv#K;n}W*lz{JvlO(tz85s;B*bj$qFu+^2dx~K&PMO9;ymbdX8bX z8bsv~YI^)*Fc`Ag)UK0-dD_}_0k_k)RVb}9JCK$RF>}u^LzgMpmkP@3bLr`DJUDF> zy1!!oT1HbV4u}!)-ocg-GnQ$ruI!3SZ{RbPQ-9Fk^xALd!IAaekKKE;%R9F%UKmT3 zNTC#D_dI8@@9XJ%)vF( zWUJtKW8OMXtMy%@UeCh#Ul0m&d(qph#u!C4TW>wN@$3C*wW_X7*1AF|+d#(_$ZySL zx`cmG@(=tadyeiWw|_9(m8N>rNwNQHvQhNw4bwTw^;Id`bCswqWB4FM+Y4IUe!lo} z)T#|!(W29o{l#;J$stu%l3|G-Fu2lfx8`>1TMpatYj%uMEVf|Wf!Z{ITi`K0fiJ`$sNAly z2~4Klst(ZN7Mm30m*cnV+tW0~r2C55Ka0(V?$Ng+9WkQl3yJosJ| znAJ;<9J}(w@wJ0Nod4zu5tr&b{pg&Lj$tbWrN8ZfPmRZJvys;_mx-`q_`s;?g-24B zaoS$U;~vF(TSjKJKE5FsIy+B3=&^&L`|9k_T9^vgk7?2uKix^)_R?}P&3>NuBw@59 z@p!hXKl>dc9_gITS?JVBNa8AfHeRpWO2O?{JF{mmlb`bXhw|LUafO$yC)LDry(MCC zm@-Xs$$JJ3{u27roO-~iwe_o(*G`rqy2ks+QKl>Bfg`IYPMYs20X|oGHcSvNJ4n7i z!7(ae`xTJUT;nd~7x#?D_|*vS%Fxi9bs4x;wd&ogR;_1fTRHB>{rE;My5vKUNz3gFP_fddYS9Fy1#ajr5G* z5cjOe>17IVMN(hD3$VPw5zJZ)MqjGVHm0zJ3bP7x!@8O*(d?_x=4SbmRp=VQ)p5Qq z7F~*PS0{dL!q3i#D1g%Uoh%2{^Hr{oCN^ODwAotbY?j+PsXWfN-4MUkZ zH8LvP#s)f#Kr!>b^#ns54cfgJ6rnVX1cT<>jjYuWH??C&%k1z(Zv%wkduh!X`nWw` z1}|+UkYIj(DXaMcHU$i2JBmjW<84lgU7Y*)TqUBtf=iGqiq}*&Y++?N_jEUo#6)Sv zmeT)NX$bTr^@(V1Mi+(y-W6B37IK>ywXrhDM(G1-J1USSB0mQBfDt=v(rFVnIo^K> zv+AzhnULyn2Fo^$mHC@HAu20|P;LIUR-VTm-C1E)eY>0R6rp_+-q#5)Ev@$h!*U?? zs!(}=Be8x+C`Gy3Ki(N$oO*d2(VI_yQSGx#dMs4~XO`E0fR`FO7>*N>-8~SakLeF2 zu`Uv;AsaOgHurgN^e!$ojV61MT5gaRVFOU@+9q4VWUY0`BSQL7y08E>D1uhCHGJ-$ zSeKH*lswC1?i=|%eNni>^?BzkS=wuttsgse^w7%sfukobMJbZQzaFiMDJ)moR|>ij zSq&cau>%QWLw#V*AL?Vv#Lwpgfk(n=O)WF{HP3KG2LDeM&-hV~NIb4AzHQh@k65dho6 z3=hAQmp%I(K5%j{0twu*3FTCRO*vic5MBs^Z%zs`0K6c5eOLLI-zG*>!AIEZR{d~vc+--0g{(Zq27lg6i z4z@CM#hhJ8_P1;{k-NP|A9D2Al}AH>r{#v_gZ@jQUN9b;HV2rc{jb0fhfuBBlFRx2 z@q<@fdEjAdmmXWc;sA%r_$=ku`+QyabRS?^&XR8ke@LyPXkE-q(ENqZ4_$!ggqytE zowQV?madsoe>J>GS=qN46OCHLH4{1|aL4?XhCETjFqH$_fpCU;H=nFffc&~{(cX`O z2vXGMY0bb_%8~FOdfGpLO#b07CWl`!W*m1OYSSb6hYD95e zi|D`YTXtkd42eYXAko=WwuVwb&`oe84m-u|_0xq|9jgiC=8p>Q} zVpF&TK2H1uL-2#W+7;j-r%yyqx^c@OHyv* zWE$&$_cNA-T0OFc%}f^HO9Mbro7 zS^xEcIIEjYv}~9rs@#?fgrBtJZK3uiSU^H!CCOa#6ImkElnSTd%@u6IH5ldMSh^Pc z9^4xd`llK@Mf6;;qSrfJy z3qa|hBy?_P6|_B0a4zH0pSO1+ZN<}YS7mE^-0Y9_eo@CV>vy(cQ)&i6)fu<7hXh^E z|u(*Ml0E@et;Nipl3Y0}gqct_6SgWt$}upjlI{dv)Q#io{Z@2vS=_QiAjyRe57Di9)AzOcbd?Qdi)-gfnH@Q<-d1` zNdp&hKLKE1M3|UHO75deFc9TG4h;aXk&6@e%iXws90?T6{H~3f+{>)HJgp_QG8a(n z;2}OWy|{Ey5Vfk^JYQ2=JvB}JfBZEVXo&2r$xY7U>L@C9v9liCpx z9PqZF#Y>P$B)n`R4TaNP*^YO($^xc%i5@*#?<{ZMzB>Z&r@2PMwoxXZ9PBs;f$U%| zj~Edz>iI69sGh1B60pN+Whh|}C0W$?j5M&l7HrUzdo@y|LtByRzPu__v7r+Gr?`_$ zI06qA&6|1igV0Hv2+;Y!VYuDEpmL=@UM(vo&=t58@XgI%ww_f#_GTMzoOd2QNHb!t zTr`PseiC*w@)7rL*|}ND55~~Q6TEwt2{G1im`M9Gxi(B7sJ40gvxRG`oRsA04fy!* zS7SBBym%2H&w#8H{YR&{Gp}qv<7_SHZ-J8%T(X$=phxr>&K(uUp)GdUOJrftOk|5x z;tZKt6Xy}ftXz$W$de8Qy;6DRt4-?KJrhc3!TeJTr#1cUK8n|Cb zbWJ^mlb*o~cFBLEOx%XQK*)=TURFPw2+`-TP(5TT*rt+FlRy4k>4C?p6*oLG6d~H! zV|Y1gMD%yaMmmh~^3x#ErR!^JtC!+#+On0Ge2Vw~kMe7qCqgm%qc_S*E0mGkWHp2( zK^E};C_90Kk(-EET&XjWFReLf^bIFds><$7CGOw^6a>%WNHNfNbarkzt#0N|3a;3( z-P9uy z9N5k$Z5+f;$%3M#(Nr|KUqc)=Q@l%tbuu{$bevOrV``dET&}eZ8O4G#WE2fJ81=1E zUWXZjKK1pwY-~!#^i}@Uh)8QC78dhR$09VIxqY2y(VgtX7BAU2@@fu^aRvKU;4;?4 zb$VO4>Syy#jY}FiR9wtbSJmtCtC^yBCxmFM+wYRy<%d3&{0OtTrIG`w2B+J?rAlGvM(p^1(-dGh3_UnyBF z3heXYU5UUI^HNBC2(Zzwg+yIA$-eE;NdKM%l_5XSV1qK6?XS{6mR!UbU4G?}wzm)h zozk$1_uUSLw$!REq~=fMrTbxOUEQQa|F^aG@Z}81{-lqML~Mm&F^I^POxqWi(dlX) zdq`$8=U*8c!M2l1D!k7A7NoVGfV|^u=%#%5d3)JO)4L;!Kfy94h1*aTTafng>_`J3 zNkAjFI(B2Zku@Bp>%Z~I)5LMLIp2(I98D39xodKe*2KsB>FEk&NuO?!U1twp`x_AJ zn3tXanCJch*CfhC-E#7&Cf;N-1sT|V&uUc`CCY1t;l~vjP4D%gyF-DA?sREdF8zxv zeXP?$*hhMTsXlgHK@~nwZf0;`g(rpCjDKs8Vp(Z7u_t-D+HYIj0GCy?? z4xTKn)TntMZ$3?vb&iv%U4H(pJfzD2-39-RT}EUR@wN7&CUEn*&*22ror?=}CJ?ta zPr5AY#1;#^w8Jp-gN^`CVq&+nU{K{&Pfwb#Nlto&%Ws0 z>K}S=Hg({*rJrSajR6s1M4gChE(q+fLJ28}osEW6*`E>htMZTg<`5Q;_pJL5_@%k} zxFK8T^Stl{fV5=;R23_DA*cTvy@?$4boUAt^oPPiGuKeaW*bJD; zFH!A}Jm<%vX%8#7D_i3CSx-wSn7U~1f*o75{QkhbKj74XBZp63wRmakd>*#^djVOF zX0=4aKpwQn!LVXznAqx@-v;aOY;a`_N`gEL`Xg>;qsPhnrm9rSCpNT@_4Vg2C9EiP%Db^ zDbxa4yJ1LGkvj;`4XtZI*DASwwQ6$Isbv6)!vYRQ{+0~!ZJP3YGI(o8QUUx;_cm5C z0PTyU&lB?VWbTs21|5@n6C8SqmzPf+KYnceWO{%^-4dq5gq2cV?hsXl;2VJtU+JX0 zffG%s=DHrmD@C3@&aDi6iL?{@ysTpPwQ*aLz$5rMr)}H&Hu1-o&Y^%WUw-Q3A!0Te zPVE9VHdYU_d^m5A5=YJ*A#)GK`$YGga2%V#jC#Q+IZyHe)I8iKgomYvbuO%KC_h&f zzo{@OkhpjyS*iD(C{y%Y2f;hCt0S01Mjc>Cw)N`eT%FxUe%nJxMw#P3xw!pA1D7A->P4@+)#iy~HZ?{u&G_^%2Fq{U`gf zo2>X8X}q)aqrN?m^?c@LBH{mYr1`rxcE2F1_6~MaJQW^~J7sPrsV*_9)PL^H{rC<` zG=xyj;o9=W^KHPQ()R(z>%a!>BL6iyfo>vN-jjzr%V4uI7FC0@d8Pzv&^z)sq?(Tp z%|dcWB&54@V>;DyVseD?R}dUDxy0)o&uPFXVV>eFcBwtDhJrs1jM*=a(~&?cPW1io zg^gQ~9(Q=0y<#Bhjw9l6mLi*ZWC_bNdqg!dVfV-gRos+m5aOu$~PBYRZN9G>ng z?<|KI7Eg8v8R*_Oe!ad)qo?>Zq+IVeA|-Urc6Ky*YQE!u_QzdktcT%DPyzARXfyZ< zGA1U%?snNyXA*RiC?o7#-fg)(n~db6hZc^VJbbWM5d{9Tbe(Y0ikwl-wqAjyai>5M zp5i#1p2NeGMZVS}m?Yn<>BhXELg5=Kbp7gM6ztn~EJx{ymsSAp0gyx6o^q8O=3im7 zU>c&*4(j8ir@u$YAW~>zS8XB$?n`;esKREt1+`{Pi_P#9N#R%A^q4$H&pQnT^qE)_=%&v_f`!H+sBrikb^gRcvgrlQ29 z{~#AvtA{I=+}WYJ*RiazRgnPT3EmEpN!yYRLzG!NW_OkXapxGI6{ePb+#sKW!_YRo zFLSSKu`h$ctb`o-*_N+d2JrRyN&B#P5V7QetRsiU3Y}4V1e0mqeQPb*{P%d_@R79> zSD~3-v3Pv#==O*2IC9{!6Ijwpmuqpq@MJt1gB(#4u=8y3&0+Id9j@PE#YsX4c{<%& zNicy^rPO^wWGvG%JycqDwenAz`x2x=~wchuvUdgs7(OP z6Z{gFdz-3xYM%_Ti$9~htdh<~LB~U&Ld|v>O%1K6L3zBwv0e5Vm@}16*oDsGIDs*u z8zp^7CGHcvn_fL2T{)+MbC2cZ1~|7hzJrgukJQ$#jd;QK z(BmuBk;jr}Kve1KY&SG^gL%v1m6SO@$>cS{+vV%c(x3LbTqxUqQv*^;c_okV*~n9bmd03T zwRzh-(dOS9I7{+H;ruNyCI%2VdI`*|Dd9?Kx5X@T&6DQ~PuXP3Msa{q6x&e}M; zH6)e}-UVmv^b&QFmBo<9*N|rMRd)qsr5&43o69YoHXQCZ<@>WUWJ@l~!{B_?&OpO` zXthfnk1I%^MHFa9Dn_ez@VhwO2bk|sJ|4_3QiIo4ztxR{8LaA+cCO30o8p2wzuhM2 zQL-M%hv|z&M|EnbTl>R7I5*k}-$wbkP|sH|{}KFVx~7^guU&EA_~m51#+&pIZcyQz zLY^+Ez^Wg=eov{4{h8X4mK2`THzVw7%jGYw)VEbslgUcps_=5G)5s~6GjYOO_)zOc z4d0fmZ4BF8_5{_p90Fd>twUKVY|l^@qSw`I^upA$mqK_3LYjt{?X=Eaqb`ZQ;L+J5 zsp?vZ<^tj47E_l+$Bq|`GM%Kbj}+z2Q*uX70ReT1E9=wZNILWlkHERsGn=j=yE5$# z_BSu=R@fHR)5Z-8%QC+co0IFd_*;KSUoqj zy6^0~=|A+4C6Rsm9`+uZah7B(X(Scu;*w)WYJ~B*-2cew@1J*`KU&~NmFyiQ+}gC0 zQMF7Wo5&%v?UBK2j*nBd){@>10b4&dbYh5MW}QW1v+I zuKDv_`n;UaYWu!Exf~ML=SO`3epwvPObUeI1qj#|KhaGJ=5+j(2<=BKgWK**3amA9 z*(1W959kU#q7>N=Dc7&Z^Rvq*54G~BnDbJQ-SOGTcsYchyjYQ;0vR`nJn~KK*`!nn zmVXE~k;xW*_!VBVS$L(qc{9QPFY^povTJcB`5V6QgN>L5L^fX<(!cw{0n_#Af8*L{ zwnrDL&_?(;cMcvqa%_F;J;VHfc@;fG4K)d|VyrN@jai72$w&QMAu|y)7-f*Ri+#(@ zZzS~sZktOG^(OW}k@Fn{+@gV*9crzOZj`chxpXN%sY87Gdwi-%CpyzE;hw z-p&g9LUtTbbf#@0Ch4($<`&5KUMcI;50B!JYW~xUv4)U{G_ZMx{C^T zwP`NnqwJ_MC4@vnkt%%+95(14xq2SZDeqDz1thlL0sS?N?N=qVe#vbc`bv`KYE5P` znD!S^{VJ{r6qJ(0YbP&0c64q2z)_-C>#O&;$33=gvHyz^P{J~K+iS980Reg^hDtn2 z`h7Jwh(f?6(;X&!JW@hxf0kIpR>Cv0 zYmusg!YGEYu0d-UPvseTXyQ~{=QDY0Er&CN-e#wlqYXSg`)`%ijju&axWZp(H|d+u z%E{w2d9)vy?LEsKe!_{!xa93*SjkOa3m%mk&;(+Kn#R)~#XGhwPwy!IEwnYK_32K>zCqaAaA zkL0WXlA*V`on2d`_d#e$+URF*sCYkutECr599!aXQI4Udnjp9cZPadv76e!yimsLX zLiLRCWPib8A1I^kQ3f4-?w5{(vV(!$N!vkIGBRX7%JK@IB^5A*AObYbvO!ZZ_q?2E zCT6>o&*PinXJ*F?L*;T^;wl@eI8t*l<3=YFOS&(^UXmvDQ@r8xMz}X=P*~+|Aj?32 z=KyTmIo4fM#(2LhK5-1vOf(696wc~6V80o&{veQTDsCJ*10kbw_|qTx&nbu7 z#<+U{B6MK3Ha~mdZ?h@XgRaw*idRyiGXgG-py#5f@bytTMdQG7!E>|tp_$og{Z{wV z$93ld<;ePy?#*MRU6e-CGPJWcTX_{$jyb+WmArnl)?BgM5E{eN!u`^6!{J z={Dm=)l z5Qnrl>Ci6q`BJM}Y(KCFBflv6(@nwcz00K&-cqO(L&5Bn#%|`7W-?4gi5~D9^@$(m zfk}_?n4T>;>__$OAZm~4dPgwuFeDf^QkS5vp|TSOQ})3qQy8hdNp_al0r5!AQo!a!u-^Q0i+UYrh=>pU(Gz@ zZ;`&DWI7EK6!fPz;j6ousYXMGBzK0raC-1I9Y8hmSRA;a%d3nEsAk$~nU!djH{eF_ zR=yo7bKGR3vXp2KJV_q01BV6;WAU8&(QEture{SraCL{!rg;+wgAq(OA{VkHKR2VP z9jWPPp4NZL#&Lxu&a!9ip{LeP*p5KAq_(pCe5#Y=%k?gn!)1ooG%6IIGtFTvA-MGL z|AGLFLO@tOrhca-kYLt9LpDWG0zDTZoZWfk*d+&!^wIEglC~0U#FYfdUB9DR-e)$v40z=Wqrz~jSW>! z|2}?!B54Ey6?Pgocvbv}{oDI)%7kq_jf!l)4|*{35of*QmDbizR^HN6IPgJg#?(dq z-fHH1@cWCbeQL&P2NieQ0*`Iaqx~V)-I{=@ANk=b>-#*vopiWIbO)WAVv#JBAmF!( z(SpNtkl1Dy=IxBZNp^}_+P~&)oAKH%H(Vfoj;Bg;!)jNyUwrXYMOQq(P~&HKU`(C3 zXQjs9BwD+Xeh4c>b_sH5adz7bZXlZESI)!J!B(B|<%9|ed{Ac(1+jEPuV986)>~Z0 zX``S@`~YS!-3ukf7e! zfyVr61l^VYR4M?W|Dw4*7MB`6$p0?kKt>ij(Km7M zcU?doelt@>=4PP(lH&3>IH%gW{j`1HbBLH|tqr{T|0{i4r`|BR2(%v4Jth9lQcWuu z`Eq^@T>Bg=PwbML!jHBcIAK~YF$>dDLi!(vkgI+pTFYkvPwir?2}!7ElN#l?CcedQ zTDFWxYae7G{adU8A-Qw=VgiAYKvpTP_GOAn_ZM=-sRCn_V7{i@KWF7t&*GuErP;Wk zLB_o58NLcgR<)9hm7Q!!`Ha+Y6aJW+UY?HC6|#FOKQPu<)i+H+kRCsPF0et7mNaY> z!r+k&okBzcGh4>eT0G4sa&dOdws#2+C`%+?~Nt$+>CPUPKP{x3cF_eT+5 z=kiD|i8h-SVV&U-t{|-+_fy-C9z1r)W@|~c-f_TwHCF{Xl>c5N z4%pQ>$dK@{|3j>TD`?kRWJ2>nd>GERu&>$nQPZJH5{fjgU{?Dgvk##Uus}u)&95Cj zwUahl$M+vTbn@~Ok;yjbMCskML#MjMPhz}~*vU7Qtd@-ME*8?pl2C_{tOLu6e#Tc- z%JB*j%#o5fR!*iRa=G!x&IQ_f0XC^XIhCKiHjD%}6#A~Z&xLL^AGHM9U~>eG0%X4* zZ63+ZP4s((mF)F7mcF#Sw}~G=^I_mcHyC;eYGeKqp_tLcvY^~;S~!8CUbu}cH?W^{ z*JPE)plLAy_r$O|Oio79MS}65_j1A!;B2^`cwWc=T5$qUZAmV0Y_%cdSZFA%>nFLOO=V z;^*J>=`MnvJl#3Uw*s-BVlSO2 zcj6Axn7mMxzUB=Ta6iV4M5^16q2Sq*q2;}3Vl64{EQsvF>Ft#MF`lI9R08q`_^k*C z4vZS92>63Qcwug=6Q0L+1D_A^O9dT)%Db=colTl*B+C*-BuRGHJKaMV>cvR#az(ye znpU%NHGq3VdhI%8|VQiD85k z_A=^0T8^}@YN50~FsRY%#|YBnsFZ+hhE^hStQVS~`Zh3co>`T$)}P!TJr@RA^SV?3 zw4*bbfjlDKkyo$GGxhVek_0hD8E%_GA`)Yj1(?cyZg!@j#UU~QC z?NZg7`4n~j@I#Gm9sGUIKN1fpAG6xmiG~F$89Sg zpCP3^$U8Z=C8zMj5?>~e<Vuf{6Ww8hJr*9 z#TS}A!Smbky3p+8%#rF5Z|2%qitn!8^ zpWn?doiv(S2%tru)+G090988qJ9#4fch4S7-2^#XXD})E1GouVSELpm>tps0=A^6O zg2AT1RXy9$rfB}VSKMt&9T%hD?gsS9Zqh7BjsLCOw`VZ{FVZ!RFm1LnO>rCanH$}7 zDKrKgb~Slf#qLe~7HVlUU4g;-tJEUB2Yyqj&JeMC-4G%63}4TCMurWcNH0D~Lu_=l zW#;o_*L-@I0r(ns(GljLe_iWBz=5AyJwYz$$*?OJb!B1T;gZe#An=3MI293-H#dijB)hmNeR=lt#hl*xvj*+(GMlDC+7 zXP~GP6&%+NI0eJqN8r7?cF*Mu&PLyE+_dzT8s*~pz6yIEkvmG7So|b6)Y+}g4(?1B zw1<$XWDg4B?vzzgEf+iV1-uR%ULv(ZRCp*4QN>I)vAg>iypB_e2@%!RYS*x-+ry!O zeVcDl-txymu`T#r9nGK6tx6C2po_+SJFA$ym=MIv712{mD_T72}-*LcqGX82^mnuK9Kr~fXa0^y2)@dvTgJVz!Gf>s^ z->Nq!AMOK!q_WONqfCJP1ty(sq5w9NK*dh6^s@pIn{Dp!(TC(bcW|K#a0zZHaceeI zJ}Q)fy-U59N-}yj_UvY<#R1-R;klc1Fk<)0}l&>>QXo0tNQZW>|P?n4yc zG?yt{atdH|p)$YlX~dkaz1^$3um~3Ev@xG!{pkQT4yzQ7)F{mV#nmo)(&D*&@T5V) z1R?R_K0X>CbY;bQRXzgbccJXXJ#h|?GhMKX<7-vWN4Mv3SwHQJF$s2*NGnZVpCoYR-aaW$3QjT>AS6v-d)T}qj-h}q5 zs>D#k3BIiJ<+8xXhnR|J4)auxp-@0}qq$QR0fK%PhZua1P8bz0&cC1Jg`ty5Ai2-I z9BeU{Nor?>0)9Mi-C<9smGHM4Lz0J&o}`Rbay^5;c7)scmD}^MtPo8FF<%nF>t}F% z()X!QcrWN5Eo;<+qWGR&=A(0r+!tW)u*tks{zW#G#j^nFcv=ojRTsrxxHYe-tax}# zkoEa|(cniFg_b+B)Q%06Wu-*?kgp&j+0yDOrRUFp8v9(B<`AdT@-mFCTD@H#*3ua% zTcprm0qr|<0gMx^vBcj$hOZ6%0~M(GK|ehAdZ)h0g}x6) zq~JHW5|@@v^_U`Xfl`V8i(9kXAC7y0JZ=Imw(>T`L@nh)gmu^buS?)Yz3jpO&tSQe zzQu%JKFWcdw%Vr6TZ4HN?+`KG#D~D=uICOq&RhukKhdT!Gy8(A^2;7W91J4z;adO7 zHUKJ(+@b0=FtUbIk zrr+b|syRdj6Lt}?UI&{N?#MW*Pu+sE8{Qw%wzB6>^Qmr5du{QTAWMUL8CEMQ-x0*%CDRP~ zU7U-8c0%EC4IR1!-XG`HeS*Fac?*|(^R8AvE)a4Ezo=XuDUyA+EcbnYEdtD>$Wn^tya>Q104k()17Xw67;IZPH)a)m;!hgzMYBN>W7V?R z$w0Lz@<>;YK`M;ii>go1C|NyWHm?B|@TE-^BBQ6fKn}eEj+U-z zG%@(eKua;z)ZFrF)n277n^_R&R#Gl3bZXjhn-`C3SswKhF@g%S%Fd$bW%qIv3R}VT zo!}z!E@9P>hl2Fzp|wNl5tI%Cc3n9U9qV;4#PJ4!)HvE<9+8#mt)Jr^(RP-X&PLHd zRIJR}K;K3x@fo?OpXH&7>kM<_&gPDdW5*L`F|zhdmHo4sY9zGnqdQb<&4fCc>t5RYa*rGqcmifV&&3Hf_BM^_O)h|+B3u9Sy|!gy+VoSaecta_NR&P zy}3Bi6=zU^bl!O!8By1zV@3texXT3r*=s@fzWYj-8ZLvmJ;XP1f)3#63BN(CUfvaT zkANtONV1b}?E;L}lbKYtg6E$)dgAb9N7oLWapc(1%P!dBVXVRg2kpRc-7Zeg68{PI zTb6|~Cn1McZ|p{+Mal1$Dl)qE_fUunzN=SeVQ(mvXsSkL$Q znsS&oE0K&)&%#B?zk1ZTo~uzr)>+VQ#Lr%bt9>m-AKCEquLOvC!xVB`3<`Y(cg1AB z2f}Bw&ZH)79@g?NfsrgD{y!{kpk?@(FY{Jv;Et_R0TLzkeOw@&DTQ_L)DRd;#c!Q9 zE1}W_5PEE$U}<9ygbhH4D;FV}qmDl+Y%j&mRYMlceJ@{VFpCM@Sqy$N)c)srV8WD6 zLar*_pLtguKcMw2KZE{JiS`vZ-E7idpgm9t@C1;AmP8_z%Rk|z1ICwizIn7xy2cVw zK|=SeRSoKocwly8*t$1&Yebn`^Tj+_ieZ_Kp`Z7G%%Vto{B&Yb%6(Cu9yn&v*yJw8 ziPPqzRsWO$>afP4vdzPO@{62NvCLMctk`>DwiKt}u-`~)$K7U)_^J2HvIf{%?|gw9 zwvm4~7)NK_Q?uGWRi)Wf(I1||Ernc{aL{=L^_ToMT(5I8has0t?cB_s^pp5`r*hTE zh}Y@nxW*a=7ymeZr`o=w(@lcdS8%=BW7FIALj2Hsqx%dCVqxtwpM=_MyPN z%Tj1NH^s0j2!wU{@OJF!9e8rnXlvbukka>Bs|9-hsDdIi~cw ztHmp2=a3eKvG)VDXz^V>l-sL>^^aYds#VWH5BJC}DJ=^Jx2DErzN~mb6BpIwkrt!j zl=BeKrj-ej#vy(TL+BHnW#T_q*G$q9maC+M$;}?*ce>2xz0@hDDp1eB&vK`6qhiFP z1;~Xi=JssT6&^5~H_hh*ZXy}1sE5Y2J)h?wKUhxJ{>n>Nv-9Fk{lU)V_xeN*A7$cC4* z(p2$WqmrqKW7DbXI3>W%jvl5)%M!M6trhCebQutC{Z=;;8n!F4|6SCH&&A3IaKImV z$S$YDRX@#jQ@hZctd$waLpj50*>IZ}oN;khiLPgCBBUA5Qwz&|yNhzOJO_-|I_#3| z>{@&6r7O5Z-p9|>Q;S70KPDY&E zrS@${u?UrLc2R35ax_dD{UvdiYa^77@CVT%nT3W4NIbAi6t*8@E#aQcIoNwQ6H0To z2=h&BWLZX@{*#FuU>qod>&~pYA->+#7LWGBSJydJEBWj~nesud!ZP2}CH zDf}m9f0(yl}KXx-<HNxN-n#R5^3$F4FEq)6;MH=a$azv<2G=ys zR+!p5Ri(fZ=-d5POBO1RToVbE9c}*2+jykJ^l2(Bedteo&n7XNO41=hW7fnU_)u4w zTalzT=u&cft#_L)SR8I^ME1w%_FX3!bN75T*Q-}a8PV0Bh&%X}N*EjA7YpI0&F z%a!U&%R9(5!0uTkt%253{4{*yH+jvNQinw^i*X3=Ut2z1!=uldYo=rT&zB3|mYZ5lX+ z6qSBu1H}#f6Q99FHNH_VmmJL<;5HjlGGzK+hI{p{VN^Kvjz0$5x}IsR09fQHPrgX} zbUwmXHgQLYP*37+llqhHBEM(>@34MXMdqOulZK?rnHMeY3E2vn#r8uP6j`~%Yl>0C z^YJ)-tVvqj$M60ES7sTZvTN-KRT?J}Wjp0kr#GpxtEd14LZFyT^RMCNpv{c9T`nVO z4pQm-#A3 zv|K}shrH311i9j_AN$C*m%%t1n|;WV$Oo`ysgC?UT<>ecqCpKpmTqX32h#n2tP;o` z(bDRJ>b0C-x~R(D_dztWXo7mXnNyxAa!LA?Q~V?6U0#oo@)EK2KtH2#lAy7z{|Fw3 za7)+i2lA=%24dfjq*wS?1IT&C{W>Psqx?9|0Vz6=RVGNiiYPrM#^3el>r*N|*daslCVpn|w>oJ(u4mBv3{5 zlapkFYnz2_a!5P*`6wh#6l;1n!B)|0J9lQWQ7&7jrPBh{kC$E*YW#lWHSL}%LowP= z^5yoXfK7TnmmiL6@B{o)xSo>-y17rAw5}DcuHZAP7*WSg+DE$FIa}_|jeEJ`i5r{e zecmVgu$?5{6a;2-VbM*}58Q+6z1w8jwpBzRH5qTl4N-N(ZoZEzoST_$QKW?Xb$zYQ zVa+MApu#tMvZ+0lwEJx|BhE*^PvwpJ+^114)lgc1dSq};>u9XHCZBs1 zA|yO(WfA=%6!T5q_b@B+AVN=XG}-JlW1!?%s%PWcnJD#MgwX=-L<<(^JR4XVrT!96 zmzTQh-hpWWUn?b1v1zl=u|q7kr#;Bk0N4W-{o>pr-XE#_)v)+oc&TmthvkL=uTvDL9-{slzK3_YhAwNVYRU#)Cs|54o!r@9i*;##KSM9#nTjvopO^1v z%=hZ<1#&D+2OEm;y!bc#K*>Z?{bYcI8(&j@8CA6p^P_F%RMO!R0fZr+NI#OR zR`FNh(y-jGB}s&#E%V#4THxv)Au_yPfm+sC{kqQ|HXm7Wbfuw!dZPQjcwJ-4z6~;ce8x3muFZ)kUDah=(tPR3xgZ8F~R%@>{A>>0{|({yLYS zXgou8O)xyq$0pkWdgHX?eSU59i?bjw)L0ZpwSi4l&hiHS0~~gPHoAYycTWZKiEr>@ zb-1i@B3Yi$7ftHq`yJjstB{0+`HKtBxd32U&)XS54Bh*l===0&V@D&)^Cw zeT~C@8h@A^Zkc)za8$Y@RX&m16IJud>ED1^U@2^W*xJ#`pT03v#f5oVW=1wVir?W$ zpzLLCJKAcom@mVGnC62tU#4w!b|h+4w}#ItjA9W6UF0WM@sdKw=IjCMP?ybdTrNx# zzz^f8n2Fl)&5IfZnKsZhE6q%kYe+QF8Sr3WmtbN>wvEQ*jMdmF&prhAs@7D2r?}?u z@d+Q(+0l7SzZmJH+BJ`diVO-kKz|fS z4$~)n!E{z3R^3+E|<@4&OOdyXHRrfB5o+L3LiF1>V(Ds++LuTYDCSMOYMA-C|=LmL!Y_xhW7 zcC4NLy{aquTeCngl$nfFS4nxRTc@(sAK_KiX@BYP@ip5p=QysLFH}WldCbKl=fv!~QCj#gp^Ug^<9oKN*G?)`nME{q&k8Fo1b+vb+Cb9_y%L}vAoTgSA zE}HIyXjG=ua^BozlEq|G4Y~S%@?13lrZKie2SK(eCPZ2P5rxYUH%3B z)?wdPG;BUII)Q)nn;YDIV!vH+IMnfRFJg(QMWBP+q9qnd6MyH|qK0UX(z6JY)=Ohe z6u(O@n5Hv4Fs3s({SwbR&tQF{31QJqUI;?1?pRukw=Wp|0XgRwJ>941>EkS62Iig$ zhnON2-uM{TyD&ir@55YBXbgw>em9%M!~cML#L?eu3)V1^F=aaa7)3+2ZtP9`5SEjK z`Q@dHvavUCEn&r7jG~NXw-da!_3%8-rsYLa%S=Wp+g7&y=?YS3_#MqJYPR)DT@tgT zDTWK)!g*W&nx=WW?qQ|`wRq!s5^h|qE|88;C~T*7-+La8rfEgo@ z2U?}%_#DAhazN-bt6Pci$Ltx5f>N*m(vF(iyJFeT#9tu!44@?a{~GV2p`M|>aQ4rm zbO8J+579YZzup<9IW}KJ)955iMW$aGbbdCkS~Uzlvv%U(`r+etEf=iTgRrQsB&51r zy;A=G@wFT;s)gm@asUPWc(UI)#XK*B+(zvf$=7F{Q@P1{0`b%mBg zS=KL!x61x78#J7S{VDYK$hNbu4F!_5+AI0-a7ewYUFRB&ZN`RhyRPG07oMcR)9HGt zrJTx=k8($QTEv%2JD&M zI?4+a)IY?X*`{3v$8|{6(ADBuIro>NnHGl}&Uewe{RT>P=`nFg;5sex^3+tghnjU@ zI|YFu%8H_tk0VN%ceYCbZu8!MyUh?5CKux-)nHzc8c2!Pz_L;Q%-9TpX7xjvo; z%c)Vpf|>i1JD{x`VeyyzDA^a|>6D)2UvR4cow_6NeP(fg7xI&ryq5w0rhH2xl`YgPOi%1pJOVfcPse{F zCxa!w!_C>{tBxKdbMolnpYKAG676qrD{~Q7NkJ41pT~v1Dl#e50pJ-ZQ*%o?7m*6v z3CiK0nvVi_Lj-n9^>UgRq++Ir!XpQNQ`Wk;qj4n9>wniu>)^}XrN;{NObS-{RX+*a z;#6Q-UCXjfo%M#Up2_cjwn1|D&yv$b*DnaJ_Wfd?c7jt&>CgZ8P#9Axrs2Z}4 z9loNsq~&w>@d{3qHnUEr-gSHrV~x{jL`!tn;kESx>jy8tYM^h$qJ}b7mt(&))ljwL zzqyJWLlX#*d=`bSm{9X<$WV=KNve)q9FgkQ7?fZ2_w6G4pt8E_o$Qy)(x`?+U*Wma zNgG%9W$s6TQU!>6K%V%|dAm(wI>)MZJ0A(E{u6g6_2;s|KjYWPhAQSjwvZxqn8ma2nH#9s93fKYt`1-Mj9X_qGz$Vxq>~=63wzv2Qx`4GF6x-(Oy~UpXV!MrYv=iB8?q0PQYGT>dQNM z&~_QKxQc_^7OuN%ML(<3-4D&CD^vT9tH~^Mli*MoeaIQJVc&N%}{r z6Z4s@V^d@i&+VIt00+=KbJ6VfIR}VTg80`# z#d1U7&&CfCR+v3`ia!UhanS z9UctzcIn2dg_m)4tT1vYE2Xo41?2}K@vLCu8oz33Y!k|zOSR$T&^7I~3-fi7fCz@} z+P*ak`{n4>Nm3QE`*;AhP3sL~6Qv-OTkuTX(H+oR)_3dSUr}w5?S6T`BB8PYoMK8! zuV;J#xA<_=QRF4)Akh6@6{e)*+I*ZEZjo5PyG^%cmHlo@VRXe7E3&nik6G z$pa7XM-~L{&! z-avzf6i{J`Qaqsl!|RNVj9FTxJKR``M0Vj+*E)O_%r7b41DVyD77Bn9k>lj};A$?r z+*NGji<%5oCs^qFZ+vngNsnpBB$)3Y!*>Qrn7miMz(esM<%%D;3_8ygjpen2r)cYc zRWR}wGaX#@Tk81D{pC(n11i)-1Br}BNYLk^=TwSSwQ6ZVbCEi7m}~Lv=XWL z3XY##O}W4O8=rSvCt@mdFl6Lv4#EPUEra%|?&%J$eq`%ViYui)gPp1f$&)O@W15-~ zp-=PP$%v5bk=T*L=%VGgrmj^Xt?iRuEem`b04|~DgpN_oNT~0Vyci{QkT)j7{RCG* zw5kf~#FdM_9I^z}J=W4J!VW*iU`GDC?Pi=Ua|&#;wsvTaX_M#M0`82v+zp4#1*w`o z6MlF4N6tIXAL%JWRrif=%X&|ndm`)B=Rg>wJPsORf68xK4_w0|AjiIv0I?YV#JeZf z4z8`QcU=lsFJ+Qb*iRh>^4C11xA3I-L--cbNQ<6Bdlz<-`qC%Ct1WCcakPA;7mIDw zr}Ul7V*BOyA_pfuK1$05eQaGN+wRny?=}`{nxOPDJ;?6(z!@NDnW$osDe%Q@(k`awHc|-aF6XRnW&+j2=6#8yds4w%;axAcZT z1=6ZeCC}yYMo4IjaglrzFQEM7aa`(h!JTQgx9TU}1~5dCL2^LO>4$*obO+Vdd6MU8 zY%jk3Jn!iP29n@Ybu&!%(W-@A$+Nw$aElt%hf&+%CkpgTl^XYc3Ttzgcw!=L>F@j< z#vcV7VKnPJB`X_m@2JeMW{4XDrt}Fy|IOSjFJ7dHI`>hYJ?%_c5$7-ukl+UzDRr;Q zUwd|LqspLC^1ma74{ROjVLu;Z`Nrr}@0#i*;IzW$P12^eI-bjO?G)Zs z%)_cL`&k5$oYQ_EesJ5yvI+;z|DSQM3&OrGW~pV?t0!%6p?ci^!6)g>L%MzWcvl0O zf8(mhn}(y6KYv5ix`xqF%0AB{`qrio9K3uj8u#gv#0Bj8eL8)e>|0097pt)qbEmN@ z>o!ex77Jbrjo3eEUkGVq%rgXILP~EOiD9ecqll0y#3OxJS!gcGc*Sz{GdTaooLe== zuN+fX^?dXI!f?&Q+X&jXyraQr(6TEWzLWA5P9K3bfR$BPW8Qd$2=Nv!fe;fGU-9ra za}O5o-pdfoR^PPBtE}lAh{M5-EH3v}S|2UHR|6}i5;QTDAyZ=wJgpfo?FlZDg zK||%HLO{fTl+ErYS+m(J+0BKEEK9N>37cfyY!DQw7_r19R=iZa)QZ)5Yqj25ZLM!$ z6~wAVv{qYr>sxJ$imzY=FRwQ5?|aTO^UU-7pS@&v0sVaXhyCaLXXcq_&Y3f3&YU@O zM((eue_AGn7%a%#3sJ#S9gsJFSoP!wh?hkFGti#tlfE=hzd_f=Eac=^iRi37KGZLS z-E=M9y1NH9txzl)jZ|b3eUynA>k17+wx&%jP!^*uBd8(tUZu%h%`S7x%!3|Icc#Qn zyy??dR)z2&jJEr=Y!O9m%-V8bO{X6=32IMK>+!2b!%VAnFG$_41}H8W+ps!SfVi%U z9Pfwt-$_AlNK3m6t8M0;X=@0(7g#fwq7Y#Vd%H^dgocgM&zhnSv^FovEkkcJ#8dZa zZ5p|P^J1aiyk>Q~e4E*AH38Tt5w=Cg&zBKAHLDcuh*eW9jEHmeVG=f3F17GF7e#-0Q+6G$T?Z7Uhe`X0qbBcIx2Z? z_zlvpQ-+y#bF*)1cVUUTk0OmLP1I_DD%?VLp)zbrj4j(OPeW|M75F?0TUT0Bw`UB_ z&k?2>Ddh90xrsFpN{3eIAac0y^DV+W?!F($e2J=+Y7mcbnQv%I<}k@%*lY!Cl`UPx z_65083y;S1S}mx!SB%T5N!x!N9;~Q#iITt1%r7l=?Xs?c-d>aUh1g3?dGMuH#R|Lj z^cWb)0*l*K9>oXRbRzD8+LfyN3N__1sC(&B*i>VGliu;%Y`&V}_M@Bcyn44@a+O|k z?_)zGN2F<2-!9y1=_H9k-(u8UrG+b2WRmNYYY*-9`<$C9B0Qs2sMlgMiwoj(dzb-@ttvTzj@=KC@^PKC`qlni2V6hm>K zsITC8Szgx4WUZH}*=4TQleEX2 zUThG~)y6<)FB^RwJ%d{tH*Z$|LdFYJo6CuoHSWoNo>F#f+q_LWUOy`D=55z7dhHzD zxyoZ|ZO1H7@)t#Lpv~KgFHz^6Sm)@;;I!|ze1_#uYT0MXVs9PaB-3BrJZVDddDP(Y zOeS-^pS=syIPi1fBE33C7drDSnjG)3a1;HTrPH|m#CIG2Lcg7DZBekULwsjm50Z!U zMO8c#o9EA=-3UkI;7rt7h=HP6O1 z!}$)a46@)Vnyk$>(=6fSx}!x&>@=%X?#LXrY3P)L=)`QG6wWikyqp&xHhX?xz?JZm zFxbZEg|y&(m<<~H`?IU~(DU+lC_L3}&5}Q&i0of@L1 zxS2lF$1sI8Xt>x!lRJ=<=1p8Xo+qK=8Yr>xdmenVAlX?I5V${WUomn&wEsAx z$Ig#2p_(!BVoAf!PPW7_ub8SasAg+cl(K+4vR&po>z$S)Ja`rcZrgr5pg;UnO93Sr%_T=@sl&2 z*hsU8GA*0=B}J)27b1OImj}rkhg++L8=bLEPYb8?+>CF+u?SeshDDQ+xBeKNZy8;r z=bP8-h7?~^N(ZsdYg2N56D8|=^rIIkwc=Q^+tgp6Ox=xw@1GpS@gX8e8==PZ6B+t^ z6_8+Gy&7`JQr&6e?rf!+idYxjhn_yuD%Ii2Y!<^2fGFFv=72QkxXYVwnKhcWa+*D8 zYOiQobUd)W;a*XjP`FwYN}ils(w6V(>s!4?_i&q(|E?B?dBk2A%WqtR<)5WmHriZ7 zYuR;@@>FL<1N)Oyvgm^VC%s2?uQYSFPI#CYvUW#XcM^FR(Y#wP@M~lkLLq`0)t{*6 zT5S_E+$c-xTU6eVL{{0t48lE4gbNC1a^IKBp^+}_X{>eY(FJ-9i*bc?*CCJkg?_%y zC&+T)icURMSmGCpaz7^{X)naK+qL86FwAyelKC_EpCtx)W{WQ>tJN>1FCB<9F8QRg z^N5Z*p*g7SvTd7ooAP~^PTVwA-W_T?Z?}(_t&2$G)ugtE^c=P1k47ur)VEE>cgor! zgi}=hzER6>-Lvy*D@w<|6Z|t{v_hk2?o+VsJCx%=oid>kj`>Tg^EcFLCPa&O>+OdmT02tXp3&*G_7eC1kLFdsiyOYbId2`&!NV%i#9Onk3e2Y5 zm^-|~ho_@pvpUoREiRZ*cQ@K_yh(MMI>;0rlwDr-9N{IN_8CaN5n%>GRKHRW>59E| z8_hugq;H^Un)K{aM>o?oht;da8An6EPK8=Kb(E;rb(qU}C|;k{-YPHTfRlxTWuRV0 z8C}mNKhstnJ`D@E?B-$b_Mm3DhjKF@bjNjg!?^tiLk%ML6Oi?hfp9-*LVi|u91*mhzR;k#7) zCqt^au-Kf?D&rHoV&+=_VV|LgT+h%g3LPj1yFw0xe zO<0y`e?d1xwde*p-KHg$QN$}_h#Oc!mba2vg3Y0oilyo2M==RW4rf8UV9z6zFHVw* zu<_zc2o85z`GIm%cY3X4lyA zk%6etc=OAu&&ZS+ZP2;DpfnN0=-ScJWZn6B-kVv5mbF~cD*sGI96R~4{As_ht(CdP z1Lc}C$Z-%bJ@p=r4XkT-L1vw2?Usk3}vFT?&$Iudk+q-{keKUZv@Oom77GC=7COkzBOMp_uJg zwmE&<;cdI-7@<&c?UqsJMitLob8`LL7GJ+nHcCC^9l8h~ST(rzsn2uKlC~V@brHx!vhh?fM<4klA zcs9}}%Aa;Utl^V|tup(T6c|Rv6;vA*Ai29av}B)DO-Z*(S=e356N-gvcA1O4Ocq%a z{MRYP&1-d$qqFT0| z=}z0#c-`grFO#Dk5DZ3TGVuF^CJDUJ%D&6 zF2djsCAxJxyBQC7!|hDmSWc4ABQ|n>M~`Qcu53Dm+m2Os9U zP|VyD6;%J9iD51K#Y)oGT4eO}Q(qOAzWcg`YZG;#TRb18ASB>JN>x&)x=V*y8kwM+D2Hd z{cYV>b5u1Ku)esg9NT83&0x8KLI)=mhs2lHB zs_35k7NgjINcEMKi|+OK3!^@~*II6rlK3cW-8y%jC<&3oPjXRr3}Rwk8x}pWdza+Y zTAN(orP`cu+yLYj1mC(wn|+};Jfmwq_s4q_pzLjWz9YuLA%%NP@@q`syEiMv=UAGzj%1y7t5;;P(~W<$(}hWKzo$#ZwdZdM zKT%b~DI5sP){z}6(_IHzocL66hB74jC2Dl!o-2#>kJb2Wo{*J)or>B* zGg`2HwmMb$A}`)`;krTiWU;*{SoE~tBy)$t(r_Zy;}V@C+RNrDJy;vNnWq^nj)Y#m zu|o$nn%8hz1MWRhm9aFFffgf*R`JmDckC6>ESUrvL2+R}B;H?oyoyTRDoPM%xDYu{ zQ;J_5C=-=$l-ZJ(>r$fuH@|7Yw5O*i&jqnNq-J|@FxddT$LM3u2pjl5JyhN7Vp~*4 zt2*;N0PO({*rulIHpvl`81FXL=BWTjYZO*h=hv5Od1DJv^MSL*z5;PT3GF~8XZ)=8 zxippqIJJLHs!?vjaS5&UP|a>+(y7FihTpevq=IYHh3i=+2Y;msqVhp(rf_a$bR@g!s8F!zObV{OW700s5A_ZnxvTcUgYPfB=KuX&EVnVTvR|b#vymG{fU>tpS)sqDjw{TM2pY|>WFW|WkGKa5dZq<%NJC|X} zB!M>aQ9V~ot`0Z2#CYc;DxGa=B1ywOC1m)Zva}pAohqL~O{%e#FNYffPT4?zKkrQS zKNHheD^m#tIyNDW(~ z?(MXopa?wQ=e2j(`=V3%S=N5|EifD7j1ziNnzMkOKKH%F(lXB*Xrr|ljQK}O&reH9 zrh(O)NGzlx>E58!B=(H_>iV+l!9nImdx0yZc|d+t#8+BQ5W>qFTidvwhHWe9pKc00 zR@1c@qg|1H{baJ4H;rz;uczJ6h;x}yr^2VokHKB1vSx{CTfw3S3A=|R_%XS36fJDG z(yN3v11xMVVmP@Z>@R}JpGdx@)`a}xD4VyTXTR#r4Ycwnf}T5+*v@Uh%xqQ^C83X^ zx)jI`#F;(`i{<=9S8gRm{^DV#f3DKDkj6;Ly(VP7A0dVt8q8J+=#bB;f{FC zm8vq==*}|2&zHqm111DDftXjQm^mioC%H}cAi5j!QT9NJU7}(jCSlXkCF5D!3iqe{ ztjL%6Q>Gh}jL`M=m_p5I*1x2sIjmM#XZ=8$n>GWRTQ&D)+ksT(M*_?DjnjlKT54A` z=0+KM@FGih(`vrpKfhnQ(rQ zTH}&3je|{hUdXHRdB8e_TgCBFb9KR&d+_E`+ONrERR}|B9Lup*gk65J6^{KX>Vd@> zT)q3M8dt!b&6~2xL)-qE$dV1AYynG<1LOyxyA|XE-Gj#5(b=gGS|ePNY*sB8>ocfWO=}Pht`Ny71erm@@R>?l<}&GIRgEyt+<#gA;}zt) zCGgwtT7lIqjhzyU$iQV=(%I$-VHOBrkRD+4EWF_yG;;_;< zK_#4j*K{rkCwQ>(VWl3`NKJ=4O@&|-9T)AC>lrRKaMhP3ax4tETvN;+-xl)El(yAj zrcw#U(^Tv!@!eybB?gsCC6KaZ8mErBbn2FgBy+QYTotxA8@5O5HQ|t7W`mrgB%c@3 zTY`*Fk5HDDp1QxFqj??UR0GZiv~9M{RIU(aZOV2+qVN*b!o5M>8|Mj&BAp*#XBbT5 z%@qR1?Q1t`F>FCoLU>zK3pqKFIb|6kFVy(ZI!RPt>;B^h#eVXh(HSg-A2ns0Nleyp zHB-^`gfw#DS1Ki&UC+REb{n~=_rFkDxT>{ljV{pa(yy?I#zT*)K;ZbmGiWa9jADul z{r(Uc77Zpu7-v%a5${MZgQK(VSEBr!- zy<9VeGWZQW!u{l#V2LIXTPNBe2^t?O;;&PhyWIU6ps(qtIxb!=%BaoUQZ!AO5!DXO znc?ts5!k0H_x>pzDfzPMa@$@SQ;tdO@BJ-FB+T9c`9!j|zDHTPKXbvogeM1C_tg6{ z!!pF2%jTF;ToQ)h=auXB+ql0)SA5WC+ZcYqm#c(sHNIzhDBNmFpHxUO8_u6`tRzS` ztZZ#sxq6jx;om5go=2UQ#nMj($4<#q76pZGWJ58#6#z{CO3(SdljtFF-3>0EB&&E0 zAPn>gC5zu%?Y>xo=b>?agtAx*v`M8XaSAS=Fnwk%_zUHD_tf7(ZKP3taj8M-pDEQu z8K=)|VImx7w$<4DHD8?@QHb|lEyL9qUGF5tugGVpUExlYa}6iSiQ(CFbheDqe6S) zslbYKNNM4&Lc8t694%x`$rSP{B$>5hPmVq7*Z?g5HDzRsi~CDFye;uhJO6-sU8MWQ zm6mLZ?kVetwA1CbJ}LPHx~t@kdQO3Gm~L_}3p(p%%5eLU?9l!;ma71$I{!|QU_QYdM{Q+8W3!QjkHTi9o>nVS}#$t!b(;O zjam$O~GOkr*nvBG8}sB@7uz&wewBYvhLp}Rh)I;?5z z4f?sLY#6xK$v9u5$5|25)6(h1NyheV)G9|wd3|aOMwNS2r%W``PvsqhG+3olgvV!M z);s(rKD1$R4TghM9VW`h3Tlnw+S;r!x1LimG|`-$m|?F_hv~KrlWog*W`jcQZ_su( z{PTMxr21VJfBS_>m(-AGT&;r=CYbu7qGC#Zrl7#^=TS*;QLx2D>fCN);%thU>MOLn8TiaxduQy25S}7T*brI9`0SWkMAJ8y_4W6q9O5s9$-F=#6XwuQ>wM{s4 z2(aK$rz%TEJwxc3f-@gG_CQ?Gu&y{!Iq5L8QGbiiJkne3Xlj0L&UN@llBw{KoayRH@mo&M=ROfCf%JyjwDHZ?w7A4PMJLLu$!wK zbsHX0n&Eu}*kD8@Z<7*cs#E5(8*a^lyN}R=rckp4s5l7BOu}*NI2l;2@cay=wsjQZ z8dLb7=829ZRDoAE)$Xh((2hsHBZ~K*sP$KIt}({`zpB#Q zT->#My+oCD`h&E~oyg5!%dJyX^dICfg5qNk%(8(CkN45Y09S8AHJZ(A(6dp_@22?Y?=|3-8lY8lh(TurWl} zyi5Vr)*}yNxU<=zn@EuN#9NX>Sh_0R2Wn2%byzb*}ZGRE5|FkJ%CcY19a%|@~E>hFR-!7FC zo(iEPCp$A2x#8eurHk2Dw5+k!6^!#5s!Zpgb2ga3eWlV(6VBE#i8nDa<9cOl;gJSp zXsm(s%c#!=iQ8>WtrKzclRy$#wLRt-!6<$y<#StO#`dn|I61I@F&!2`NVu){1d7I*) zV0XCa7?Si*Q$ZMNs^q=h$|wYqV~oF8iL9i}+M?FTOVGTg)$baSwv4`1MU0ZtDC%yf zL3nXjL)idglScXNn#fHUz^(lCV z^pVWytNpWJ#A5lx8rznlbxUmc=~U%vEm?V=k-w7r8Oj3OC~=f!NFz&c<~6a9ta{{= zXn{%@s!!4b*hU$puA|bvBb4f+Be9*CBtj2hc{keXjy~sF1`$3R?XHXcN@@<#uk8Ga z2U7B#(32#x%*HLQT5Xcy=PK%9f5VouZ+1VbbX|y(mo%gDwi|c9^!+Vs^)R!zgwSz?)?&e9Y zpV46H;Rek!@r0~}^@A#(SE=rf)yvy$=-)|g@;T5Bsax$4AqSLL?>0})-`T^3mupGD zF7w|iC+zi_aQHb3izd5Sx|I{lg*hBX`IMeu7d#xEXB>V~X;DOF$Fz+57`>lRX0{XF z)3aXmjEFmBDNx^@|{f%+vif;jXO^Or*h0i&c9HYwQ9i9ciis4Q;3VMMVh2N@5 zb2UiQ5&jYcW8}V9C=j$CB>$UKSnWg-vU@0TXo%dZ||W6&IhwQ&?2B>{_!-E;hV+9}g96Rgn7+lx%0%F>wD((l1I`&F;({ic(*_YY z1(tDnjh-~m1xHbnxsBGHN@j*E7#Psaw(}+*N$ElpO`+GGDTb6}+-_2O-1bb4&|zRY zjp|0-STsUn= z+z2k!gWat=sGF_c8ftQSl1pcy~!LGQBBt9%eW-^H$%y&Z02D zHy0>XQ{hS#Y|Lx}5bJyjqh)wVaz=2A*?LZ;e954aZ~al?!Tm}-4z*pJ83qd!t%5w5sqgm(WesetB^_&CY7=%?);&_h`~ zh1MPYe$9`^tzdTiwsJMEnOy&8!J55%0|-TZ9d6@xWg61qpOh`V0fS9$DDe&Y`;?_Q zvt>_ zAmf2W@#2S&eiNLih>>Md~(dzlxhtl7ef-nNmR;Z zt4yP`$y@Kry%RKA!(lF%=;eB&E0*TlX(JegbC1$PRH0?&7NWBiCI(;{t`gETr}?mW zkI=J~*Kai@-c^_lTC`izCW;k(tuV`9O>tZOh@_eKtnF}}Y5L1nWw**8*xWPNVM_GEk(zpr#h5~EOIYu7q{(7 zW2Le}ZO=?q@$4XirR z?85hG$qbxam&nKUtTRm2JDun14iKa2o$3u?*^nHk21RZs(KwD9jXIml5I9N6PP*bL zFeBhIcu#7{&_i%T$#2^#`W=-GmYJXqNQrja($lD@-gAtBynrk@r>xQjC8xb@oWY0d zG{U7&>1yW|7e3&36s0x3Tqh2SIQ5(=okXfb;R*+9>=|Mj4^bdPh1icCoTF;uZHlJ8 z5q{7;4cKv&#N{L-E6Br?VSU-As5<5c=o_+PdfQ9k-7BO_mht&9$GPE*lkz0TSg4T^`9yv_V;6; z(iV*&{@tF8+NWN-KA)_Vx5@$;esnj|xWW2Y{lwAG@z46*qY6(7^(2paso3hJ7= zU7K_;rcFojO_m-YF&uSV+k2Ju#9u4X+|LN4Z6paxB?jbSYD?5{^G%sX+wS><9&KuD zH6ieE!I|2cnDJ4luwI(A9qVPIO&0G{Qg#-{)x~#{AV*V)tZZcoao(lWL_`>EpOcOs zQ)%3ss*vgE`m6x$Jqfax|93r|uQdrL$HRJA3--gD{-m z%CVI^{Q@(PpH2f{2&pDwgq96BH)C0FC+N#GGNdJIb!Yb&xDrw&_Ve&}(M7@(q-Uu9 zm949rJYd1`_O|G<>1OiG<>6TJe1*GK*J1mGKMIzaECkQd^yJKIuHtKST*-sZKV8qd zl{+&KxL4O+#=BHk? z97K_Pgr;OD>gWrhPLd4`)aZxpZIvFaaQ%-A7x{JCc(ULg?w<4j88UiD!%(seMDzOh_v}E zk5tUYP=xE@OZq5t5Zi)5_bi=U@SsqCr2_em7k}kAFF8fRK-G?Df@MvTMi=Oul}Gi<7p`A2ZOsd?dl^$n@tukQJ*jnbP5~3w{rO>1?Ca}35;QI)g8As(Onu1iB!Y(3Q7v2**rz8oU#&O3 zK#%0kOxUa_n?`a$G!}uVpYBRzXOm54e<|GDrCgh<(bWAi z8`^S_k`>Kqi|#wt#W>e`NT>5C>qg2nWMK4QK-|B8G9%AgjN#{5H3Z4A)j=2qEUwQ` zmJYbGJsT%3c>mcvVnoBvhLn^$SnQiql*^t7#um)mI>PQ@0iUUidS4P_`ig5KOzIr3 zMD_HLHb5pF)iS|l7aHDX_pyD!A>uhuDh{@iF`hfz+85BX=e7O z;chI-EWoC69I2w=j^cIgI1)uqH!N0jlr8@8brO@w5lT=Kefen{$$*Zpu8I| zOxL6CM6tFd-dP!!kkgdQF~yEfoj&pFCtbne-d8oDa3;)is@7~yoL}mhjqJnVu#`CO z;W_dPe^`AI6u`-&OF%$^JrTi46c4#pK%2|!;%=kZaOfv#e@8^X8Iv@;y-7L zKc>)(YnYZY=UOAlf2g4IYhjNu3xEDy<%-rJI0mo$PJCr@-Wn$5LW($17g(EI4y&5h zp9fjWZb)ameObA1yfJmRRAv%~IDoyjXRxM}>5{~b_f)c<_dd$cqm`KA$To;LoSN)#N`NaRMr9@WZe6^reRC6N%YGOdu5kFz(sil!Y;3K3-z-pNl25$C^(K zw&GYMy@*?I&o!C#66*3LZl-9s-5Acs0mEn1#Q%cXxo&}FUGJA4HZAA=H`A-p-aoD5 zXp-P2ji~b~)hW^8+A$UfsgX7^=UY@}%>PuDh`tyAS2 zotm)3dSzvXS2La}z+7hQT+=A;5^Zx3teCm(B9&BI+%3o>FTxRR)Lk$Q_{pRfk&Z%? z8N+;6dIhOAU?j1$9aRhthhcBIO4v+lrd{awLY|2tTNPSYoTpSZN9ht4Noap${5i^# z`J_rK$DToeUWl36>RcUJ!mG02jatxPP}2l>qGc*apcuSyUn5Y!$-M zP-}Ua+E>}iz)glT$!X(Cqf(Fpy5&V?-buo98ma8sboSgH67%cYJ)`=sSekKk#Fv6L z(aoUlJS{(;-njXiZM%Crbo`%h}*9#lG&JwCRw(QbXXu`2i`)sQTUOM0Zuv@u%s4eF>e zC?Fm5zM z!5&qT-B8(rZOOug!Esxa5e9N;W$n$uYnlRjug!Ru>$g04bv^@3F4Hg3p}aEoLvAA; zG`AtMQIB@ZDAH&fA|_qBNNQTUMnNEL63}{0YK*SVRk%dMl1c9Z6>96y3Gens#f!`Y z08>r49oDW6P%u&L8=F#ct{xDFNQccfl{7`b?Pn|9Zom__!@y_p_FV7SV0y=MNX(X8 znO!nJKP9PWo~LlcDJrPV_6`E-axpb&ziVmMnJT&3-onMWc}xfScEIS1l-SzArCrQ`NmMoG^H^q_yQ^k zAY&T`|1G8oueR-gpIE}S{$BGp=0)FSE1sYU9xTG<*Y8;=-R8r&%Ff;xkDe8Xc`JAK z5yy?kzor4Mn3Ua}&2|*IcAnDSiupJX(_L^sOM`M3G6=y<{TMOLr0EC0?$OUo0)$Xv z_TIaczK*#@x1!1pX|%YLM{!6?Rw^#7Slv|{(0noWS)OD9FWf=|evgPenJmEUVnige zyh&QyUVuA4leZF+w=^|(z5i>a>uDhnZtlio%DCdgyi)Qv3Dm{(`ava^jzDCVY4^Wo zeu@r<$WaAJh$ud*2X=9Tjg7c|B#F6OO(peV3``rX>N$Io;Cz;Iy5<5R=|z* zgpy!#!%aYvXoVl1qG|1xl1`b7?$wrx=AFfG^q%K<6XUXUx(S)TQdzc_JBt0?Wo#2A z>9D+`GFb@~%vaL7eobpL2kbZ)Y18=m)~4cG%@mWR%IH*VZaHSP^<1}WkKfq#e5Hy@ zSX(84B!R!Lw)@6W>RF)Y@(m&Zp?mFd)^I<6(ZMkt$J zfDT|*vY;3E_+m;UM4&{)8r{%gt8|l#KXZ8nJD5hoT(~*ETf&<7pvq*9bW4|Jp;mTo zlP|LAR}WaVppaUIq0xUR$5x2MQp#4sTHz@mf^{^(`^6qodI?T|iO1~*%rN*lr?U*r zQjCV$o^C&t2H$)`T_6jlCR4ND{;M9#cJVB5#Yk~q7_8hJ<-4{kH3T5ex0F&Cl#Kar zC{csboFuai<7hC%^mwoGIfNds*IU<~U9^WANG!^*|9cfhPBn8ej7Q%B) zgA$Q#Dyb*DNw5#7AXArAEJgT3V*II6GLklH&d&6E-gA4b)UIQN%WtD_Q?g#q7K`Rr z^JZn%D^8ip)V<8TJ;6$Q5__nOhY}xYC zrc+<7=}mqnxDA4_@1z7+8adM#A0Cg`<_0Y=mO%-b9#CTHIK?uJgq+?DMg_U)1- zTKky026S=LZc9NH+ca)bu~~`TB!;vdOj{pjdq#+gBWTaHJNmlqE;f@=GnHhl=>eDF zlxa%RUP#KTNT7r0gl;p^(8kG8Y-4r&LOJ1VjwBs}_UB5fnrFMwEm|7et#>%-p53q% zj=H`4UG7H#KSG$*XNvj=cQWPF!;Ju$7hSJ%BNBl}yjnxeD5WT`9H$dfW#rT43|ILdRb zDPdn!zOQ{HYvsuAig2CxfeFxImF0%kO+Ec`ht<&|#>7Zb-fV6m7FQ`07mWRBC3h2k z(WQaS|DRNv1%h6qO}jS5vXCz`!{lR14-PW8A*Z6R z_o|Df61ralF(9h;@!d}LTB{N1qc0MLKd<8S zAx+Ao-cs96oznM8)VK2ib2h09;s!z0-a157v+myH3{C(qD<8ZxO3-#*qXWefv zU$-PgGj=t?Ro81jW{9}hD9p(`89X%(*jJ^;{T7$!DP2L*Y$bt|#0D!?O4pQ7=?o<& zv0xaa&sGw)<7{ajgF~9qe~L0&J7m>q-nHk-zFrdx$9mP?lUNMQvhy^h2I-VU$1?Tj z$;!igZ(?$LZ1@*#4^xh7wJ zY1v$Ovi|H$qku4vDw#2sg!6~mB(W|$3*OiYiZlcxm8d3fX%GfZf8T8u%TxuDKO=cp>Z&rSXTL)#!%KgWjP-~6*Zlp}{zCT3L?<<+fZDlsQF(# zC(WcBmSCXSdg)QM=`(*lecER)+Wzb94f~rH&+FJe^P>G-ix+I4zV@!>#mCk3b^Kuu zsD|3!&n+6(DG2EaHAxleboxxw-%K%It#)`#7NOcB`dh5O@X;lWmQG&y@C?5n&Jvzi zJ4!~0{KGSpq1G;;nSDBYcW#b19kzDJvOjbFL)zGSa;=|>(nb3@^QrbnO2e;P8FS|U z>fphH)8zotuUpi62PMy@tGUpF<{w_$m_c)Dmt~M>qEBeTG;2sGz5S)@GeXR|>A;>7 zw)dnz=WRbN{W)#;Q{XU6i%!ws0yQTiEdzwS@9VM zQgkn<%?rgWGyI^|tDw4nn!@!9-QfbkrAu37>XK1;=KRen_vG5()@{4hQ_e@rl++)J zs?ePIGy*+xOMfH?xHv(8oFG6>kkFFpO5x+B_dYmx{m{dEZ#cO&xc1;Nq<`zcH@7VO zw*1CwwSWE2qn|tM<>wBqU+}HV7j7AP5{TYT#@4s3)tmRVEuK5ne)7Jq#Yb+RtzkNM zU$YXXtv%Q=_wv1;n>&2}H|-x#U!lL5#^=iog1?YYGiysCv`i2w@EoI}K`KP8c9;#} zxwUfyA8BLcC>tL~>f%-EpiPhHuUUWLvq=Coq#x>~EPt2kubl53o(qE3Qb$rJb@0~f zl8)4o)}6cM%Y{?d>Z=BpE?qXTt7r43p522T1HHQyXt>w<^xC{n=c)I8Dp7Egk~Y~f z%OD4!z~QxP^$j$qc6|oT){YXNfBZcq)M&{J0BetzJ-l{*uH0)RO4zAv|X<*mx-c7r9Zr-ik#bwuNvjet%c}{|WFb#H&1#*rB za*hozRf2uIw1)7k;VqUx0s4Ni{qnEFGI+E>s$&mfG+62I4E3Fx%OtiLd$cLBXlL(B zO@NcXM}OVjTXqfX=;|S}Ia=XuVIX?xp*!wYiCXRM3wcXz_@#WS zM*5PGQr(x#wdD`bP)F#?vPj|?%24Yic?C`RAqZXLoBy!BTd#7NND4^OKt~eDkpyxi z^LiakFMZLzxr>M9E6RRU>stXVCziqwgn~JYHzI<*w!( zMlAgsKGc;W|AJCi^9*GWN+TiP*~8iUGTujP*fN82h^s#<5@_*i)#kJSa#{f8wBVio z=3325+f+1fsBQ7A)8?-q>TfvqB@IfNx39lp-@MC*Z?*FN46-=y{^pod(*oW4$dM@dJT+#tK!2(({tRc0UmmX?t7&>D8_S)79mmQ@m+8$8x z+7K3_D+5KXPRj~)ucT&6iWV`DY!v@ z{QJx#JqO9nN8Yj$X{nQE(YLmYK-!W3H7?w&vZc`n5>7xMCm@g$FmF7stJS=8B;W%Q zf4oHP-(BCYr zz8a($@>3$JA(Pl9t&WreJ@?r44f~GVx4ywNyf3I`*GZ)d{|Nw;1F2?b8h zA3)9@Ku$s4h47Xg`_rs+qwbvO@8T(g==+vy>N>5aSuj@OMi!Ge~MQ z?~$^i1(G2+RL>H$%vl1+Spvur%G9$CFw%M`fQHe0l_|-{(TGxWwQ*Z1^o+pML|=H|Y>H9PyRi{k}cy)%HkGl0A^LaS#eg^!ocNpIa$%JHOP8>6G`*WXe-axgf7wBW#o2 z+v+F1ric@e;{@b51=dLa^{I=&>Cott9Z%--`E;wu-&vqc$D}B;d=$!b=aSmCIAPjI zo??6tqtWtwP75M=b)*rH(+Fr}8X217{5mvxRw)lUGnDnkq6jUH(8&=xJwnXA{@$}A zbZ&&skC1@6-g9c8y5Drq;C$H_=I%Xk@U1`l#ohzcj{3+Q!`LCTgD_hK=Iw7hK^cPP zo_EJ9X3jWp-W}Kfa`;kF&Og&kTbrTXx0UfQ|sWxhv)DrTvw(h`qRQQB*X*fMvw4)*SdGa|%s5d-9`2IQ<38V`be zy!7`0Ri?I6Ukhq|fR*}TuoRA_A3nDL{F^dIS)zt#5L1sX2v4LThApzNjPml8Flq5al`1VGXr5yQtbnSq^3stp->HHNYVXkZ=!^;5q@NV4 z(HQg0T*j~O-mQacn|Ac8dz>*@d+g?9dcG9dy(A7e8s&HaIbJ|T9`t#uW+)t98)}?8 z{9!|e#!hx`WKC(rfixew`e`|0$w4ESFg;&nOu0x(wu>q@N4N;3#Fw<#F$Ho=fgIED zR(rL+yfm2p?UZ5q2cuwmd{OaUuVBo(2F`D)+YZx`L$R87_O6vVZeVjtg|W)&;8jYY z1$tqLLSX6?0&)rgRa3}IYvg_@5@@m`x1~g&Fxp|i)kOkThCELM)9=-zM>`!oAV&|V zdI)=I7~uYh-(*?6r-WZ%I`F))(jd%KWP>e^lP;tL!ccOb_dsJd&tG`Rm@ z%5WbV1^1T}`!+EDn5*lQAY-q^OH|rg&mmyzR047;0aa7UOS`^z{gcjXocoGd<`;5j zL+z{s`ubTyJB+Z?<%)#3M2o1Nv!AXUK7lX%SJ9gJh7ujaGt@fp_|dU-x0$Zoo`J+o z$f>PHGw%$lN!#K~138?yKu%mBCvHF^(9BB@H7*>U6(*la&osRy(giXz{61PF-NcMM z`TXbhFIMXSrJ>N%IjJI+9k zGf*|oURpa1`x@u&Yn->Qae-VF8jss2b?cop(mvD7xn{ZM zVt>Ph`x|bQYX6Z)r0GVOYGV~1fqX`j+OH~+Bs_z~@Wa)tGT}W)h|;24aJt!$4BnWR zzS!vW7dcB+tM8!fR0DFV0Xfw|vxD67(j}^WCQ>c16O?&Ge~a}OKB20KQtcy;Bh~(N zEUJOBQw_+e22@QoFD+4YPo&gjMbY&o{z^sB#~({dMPZfDC>Xv$(BQ~|0MR$jVN zSp9RP)6@&AffD6{U_e-XA^bQ5VO70csFzp=RCh_I1Wl(GkkbpOT4H(W(%Ac9q|{_3 zR@sUf#w!x*tCQoXY7&hmw(cEk0D`xZ4#-IdR6PK_bcu97kEENd*cy#=_dVXEyK5}c zfwz+m$VmrOO*$_v*>zYDF}2Cct{Y1uFjP!`eB&`ER}^3gp@IMdM<)%ClLn|-fO+Xk z0rr%DlZ;#V7Eh(NRrd1b0<3ziUoXENte0P4>Er@(asgG#FE3phgwKkE znymbKO^K^g`Ss&TaaA>iMiXEEcdRi8+D zFa?8x;|=6^16AYgrAwmwPNTv^t#i9d_@~p@r;A;j%-thyGFBa?(AgYaS%@)HXAjDT z-s%|*oSZB`P8J|1OK4*lgkF01YeonL{=_BN%_VY#S5WJRs@b79s~-Yo3UhuX!&I2# zK+-7#F3Wj!Q3 zgUmU9H^Qty!7x3Xt{kR&KBE8VT;+5TR2osG!=^o z&~V&=9Cx7VPWRGh9e6?GtfSs>V0d|?fuEbHcA?DAS_O*vzdRheeszfw;TdzkFHSev z0LhjVt-E#6qiG&}*JWp5dfnlz;v59+b!34YSs+I?yfd`YOAl|Lo%X3lFO20_EGy0L zfL@ui!!s03d`Y5tWK1vf3r2M&p~;RXkmCtd?9|W*FI^hRTWP+1JXBRR?qP(+!r3$Y z4!B=c>e}!Oqx$w@*CwO-5EQIbc}RQeEbIcb2LG(gp3-AhB7^BRwP#R)eZxc*qV z94xr-17rt8u&&P<%F@9B!G4@_oRG(KBteBy)t(|!jc zmJdURXQ=h#Q$fT!=i$U(w3?D2>XZa>N&-10Ljwbod1)wl(}BTR9Mv0|zi-~~Ya-Iq z?95y>o6XNCGTLDrT^$mNS?TbDlWm(5S-PwLx@4G*xyV~|YChCXR5ihGD;z2NV-cSt zWx*%|LQWMRrwWi$B{T|yGaoNK{KiOyiMy+HmGBRQ=QUe${G+eocm`NC>H$?+?Wh7d zsz8ovcz^iUOCQoudQT+FR1T%`9$es5hSI-IU?^3q?selS@jb2{Phjih1afi$RgWhx zT{oUS8A&$T@l-Zy2G(UfZO!?qX`JJ!M5Uawo>4KV?kNo_uyWD>Icb2Z2bGr|mQPsr znfRb83zfjkRJ=VW!Z-%ixbP1ni>5oiK#ng^^~mzlb*%6Y5&y}u!i!6yC1r)aoC@P) zh0=;8=Y*3hIN^~JCxDbw2FNJ`RLu!qdidLs5)lhcv(Z zGLmL0=a+J*k92-HXMAL;n>><34b_tes5q^FoK`?itI&i%D=%FqG3JEx?b%uiey zJ8u+;(Vvf&qQvlpd@c{RSSX_29lHjuQ|viEDm@_Cld4-Z+3^N)yn(9aftMa$81bCA zJSfMM45Vdd7|1b<@?bRN!f2;mjtr0^15`cQy|f}84xbY7ne1uUD@y$q1_;K}>x#S^ z>{Mg#f3@$~QCyk#siu-UB`7%RK#n?)qaK<8art=ZXoff^!Jj2UHQ$z(_g0g>sQlsV zNBKNjc!pYEK2iKfHc?a%0$iLBKu!oCCq!sZ*4&zxj)Z8MGD7IK%`nT`yTXeh#QLI= zJi?aX$XHh#524|XHIQQsRE@Ql9`1_RPJ9@b0}-afc(7s^7teS{Bg`T(QCJy<(MEWI!z-eu%?^2wuslaLMm0&KD?^O_oGNQEE5e0HYfvOSp(vf8cBEpk3 zAzocVJ7w9oR*&5=SO40*ADNF|TQA1I#z_I>qyVbsV=s*a|NVpt$l7WNRu+Rn3ZxS3 z)g>x~XQ=gWs;Mx}ord~7-@-0Kos>_GuY@51nob@dCl8R5Cp0yn<)tI1+>+2}a#Fs$ zkC$@FXC6x$jm%3G!~$O@7LXGQsG66&G`#fIgie#=rG2CD(*2X8Q)xMTx<_OjouQJ0 z1%aW!)oBFeGydz8FO^%_;0U%O_`q#&tP$P3y1;s$xDF);e1FGgKFAZ0H zJR#QPxT@@X5r#dr{=Z2PtF*Kp7gHtf-hrt=)ky^8Bm$~tDlZ+G>OexL$uZTfrO}r% z)vq6GI*rUz6$ArkCm4_u45*r?yfi%Z<%Cv~aS8P2{M>R|Q<4S&j>k;{xQkgcpQ=z4Y+U zrvR7oohIQG;F7FNa$F8!X+rxPDIiA*s2V9RJ$#s7w4m2dyn{MRGA%_aS(@ZXO?GWU zqa9Nq#}ud1Nxk#>Uk*Lxru+6S zZy0=Pb|c9V8-`xcvG(AGx!wC_UamceAu|gM%^sTHad2&er!}dDlhmG>wM!!OO#Svy zf-|h0b7c!6U@F007t|W{{nXl8eIH+2r|(5}A=dEGL5q2$phGPTt8oXw|Pr>qI==%@;QDa>tg*%Letgb59;<6~Y`| zyG^M;b87F%An(k8rt_nhA?{$X6&AWQs(N@9(WA6~NDpY1y>P-a)Y>&Jt4|%% zRv3F|uQM%>Gc8ax(|YMAnrSaE8bgQQ9;PivbV-@Eb6mX{xi@j;ABlURkaI7Pb1zUe z_j+l#ca>2VUYsoV>cXai%`)ZQw^S?4<1ql$v2=yq2PB-Oft;m*s#)4gN0z>HEIAB? zQaLOs9AvPOU*9rkSwRjDz5gO>FB?$IDIB)&X;NpenJ#^G-ZcujT zX%B5U>!2=an!E41hJDxTpX+sXT=U{Z@*P{0UDmX}ZSewK(YfgL%3Gkng-M>Sa9VJh zu5r>X=fY!k>)ZyvRBi18ygM5{7i*)uGhC{6-dzKq>b_LBJ62 zp$u%YHDQ2o*-~3X2eL|Gn@SA_rIgmD$KV@0`d!_E6PU_&TV|7QLE}|W&2f)KcF_4R5An$>^<-DtA zSRYg^t5?9HR=^ z4_bWE{5h7JkGit?gF2~$w{9PuLpytWuP#$0o!a|%?bedh{Yh4xB8IsdlPd)EhZ!T1RtjGn}5qgwM+<}q0Eu` zyLxm&boKUefp6iu?SLZ4L?b;BtI+;|${dC;v(!XZUVE>*$yXS=_ zU`W}`^)`XI!NO?*4QlS{Z{0a6QNY590^~#ia-xLR2cmfCphKC;@kBfW;fHaWOyBnA^XLeG_`MUkYQJ5#GbseejLzj#}>%31**o@ zOKY582-DT>wjR!Y3~NiM=GWlO5wt{=%+zLCt@d1lc%aPDNcZw*teL^*8EsGK!3L`L zPix^?y!?la|6fSpl{olxHOlb@a=d}Y9{=Huqy76`WxDWl2pQoJGQuHbghTYNL&yk+ z9@{JN^qT+qJkAvGeq|Uq^qa&J7sggKM`&e)+9K2up{@vRjL>Bfx*|fGBeXR_eG%%9 z&|rkFjnMTGdS!%O9if{ebW4O@AE7rz=(Y&4Mdq}BSA^aZq4!7V&m;7g5&A@gJ{6(Q zMCd?-J{O@cMCeNqx;H{!kI*+G^z8_JCqmzg&;t>AFhUPS=%*3-d4zryAr{gZ|3zq4 zgyu$QUW67zXkmnoi_oG7EsoI15js6WXGG}i2%Q_D^CNU&gqkC?GD2+;VtR2rx+1hO zLYGD8iU@6v(AEg`MW{bQB9$#R_}8`UTFvK(p=mQ@hL@H11)3iG!^$DG@GK4)`ZpNV z8j>_Xeg`eBeEK##L#+!AO_M1_flqX`>Z3m(;qxAl&wD^V?}dhlEcWrzkpj!7i~?mB ztngweFt8wdy9W_F^o!I=G)c6cYMOwS(*($A0#r>CFRfKydTwq*q|9W`&6T5k1mntF z5oJmOAe5*xW()*SbLs#&b%2~Yp?QI0ymW~=o2QIAWv8|9dZ_c|V+wqe`0I`A7gX(8 zTq4n!F{Z%JNd)910;(pFmoAa$B~wPCou#EjN}`_~I*F>?WJ?rE?un`%VxZ>~0&)rg zRa3}Imnd|@<3gcdJVq26GujmBIfa0nLO|6N^3qUf+@VH>BK@ZF{Az!R+rr_|Q#BUn z6gS+*Ga#$Et?q>DnEVE^PE8=ECXiDzG%^S+FJ0od+akRtE7HsRXX%I>=TYd=cpLLH zG>v!sfgFFJYHsqQWh?qb&k1H+8ty0qIm$qea(H=Yw3n9M=C2H2+FiCz%m`&m|E4&WBdUjIuwoor z6eZzO)G?00rly=RF9|Ey>1OJEB`XKWIZc3^CO}S;(Aa>Vmv%N7HnNa2tu{F}cwI?C zgr1<*`Hv5Y>e#1(R3Pl60&-FTRkM$m7W>?5bebspjAr)RGg&n_n*A!S6Q$*Hy1q6u!Doz9-CjwA)mwRb-`45Z&6YcWXmaqfcUX`aW0o%3gfFd^8X)L&0dl$kRWpZ|cINn*k!C8GqkKp%l^VT|ADQad zsDfM|?c@S-asgGdk(Vy%8nb3WvnrMfx&nFWhu^`RFxsN%uZy%Aj}BIUbhj!_=9m%R zz{$x13g)HZqD8~U7&X9lvU5*45MIhfQ*vKn9e*Ut=^$o6#|Z)CgaE4M z4=-Kfk7pP~Cdwb9MQ8r*1o)$R(OktOV}>yUF{cfX(*~%TNxZZ($#ab)Q^6$V5N|1y zOx-w~|Kxu63tCk>F3 z2B?}*ytFe)t5IYs7^S?{O&Mj%?>Clf-fm_ym-k7(h-8plUwx(j`7wZ&aBmpOhoi z25F8NZs~}8QaCnSB`(GcTt%xLaUe$=sG1ABv~$5G!*?pUpzLCia=|z+Hmd96WIZ4H zmUQoIvi)3FKrCnENe% z@+GsqmqVPIyA3{Cp7n7KM!fuh#G$iU@eg~zvETY0Q z)Ov_}Y-t?A#PC#Y=>`KIw?IB_fgJtNilFv->AM>doVDRUiP+DyfpmBrekt~`5YHr^ z6#Meg>F^B0^N?+CJ9b^utMM|D;f+>1#z2lSkYgO)92)AShZ^VV$jvM~$7h{(*J*d& zbl`?#Zo2P=`9nv{_)5o&udco50m|?F++3v8GX(Aw#TL~5Jk~bdj;+ivU&zCZ+pEV( zF!jaA{|hO6m;w1P19GYdw5Ml?dFi2Pniv}PH+(_6*`Edj`-Ce?Aqiq8 z%$23($239 zcGBUrJ5Rey{JQ9-M{hVz{Ca@@I`%g#+23$S$NsrrEwC>A;H-OBLhpG>4Xpb-!PTsL zu5$Rq2QwJjlNS0o%?~{l`sR%#)}>C$^VXvtpT4PP`J|V|&GL}YSsut)9>_Z`kSnmf zmmXOqdP=SmWqoLY2+E4lKS3_z|DYJ51XL?V&!?z9_K1TvwbNrr-l`rePs*<>^%!+B zlFruO)}m@X{-BzP(lq9tf{FR$!nsK*RM5{m4ahqU$U7}iEGSf7dgM+UQ;+?g(H??_ z?s{zX*z$p6XAak6n~DQ$gwU5s-xOVUts|1N^poreDB~Rgy0z?j_Sd4j=g{6!E<^=etGbe;~7#XgXb*$y{3BbjBPI0p#r&G zhru>>dF!|2p!g*F4H|mC0eQaxdA|j!g?{tWLygC2C9-1u-me_od*I-;C+ZTC8TZY& zZ{5%-`vy;)HFV~_<|Pe75AA(m+TI_`z3IR!9(eKHjke~JBC4~t2k&m2=l?8(=9dpo z4{EEH7WC+0Y6cF@WIw6)JNlkyTEqMrY;=qTXIWSylEBky%k};ATD!hah}!IC{R*Eo z0Vw?&W;vd(|B&kkVZ!miP-cSxv@+LF%7J>rJ89JvnSoOsxxxkv1^q}ekRM3~@=grx zQFBlYYF>Ke!S~eC;FJDQId?tT`YjcPOH_OH@C(zYQ;t^n(BWz3E*9o|(){x9`$B2% zqE4ECw()`L;a3c-HnOAE);&9~X7_*5si#*&e@qAHRMcogIJszXl0%q&@F5K3Lm0?= zKTtjlVK1%TPaO;wLr42e)*Gd~Cz9Ikrp|>f)yNY|ceO?ia;}jBxke7;^a%|N8o8IA zVi9*{Lf_v;5m)y53nB#(H9*|2splXTdrArd8(>fKAH(7~Sj`bY` z3QPSd^Y=^zUb1CVE%3n21s;$KJfLcU=cT7u;9ZfB_qS2t?I?}GRNzhFW?U-ol5P$1 z4l26519Eu>R4wnk^yKB;ORVo6KY4d=iPouJ_k&5vyJ~;m$%?x%Yc*+?TxvT>X-9v! zv;%T!2UIQXyz~?g=T{j^xU~Fj9L}psES3)Eaqp_8gSBEy6&m?q1@gfPR6SU|^yCNY z8?9#^zrlJ-iIVAHo%K{W#6BZy5ru8~aSp|ip0R0x35Th}mvT*ClzN`)31C+N#dS8Q^epiK^4 zVjDL(zB=O@b$c8b=6sLiqmjhv_FRM6t`CdWpXTsZ{TuARX@h_nF8o_%^egXiys6Y3 z)XB40%%6xojxFq%IavzILg;7u7B|_$q^G z#UuLLtiSNto&n^A54H1~U7sykVwxZJ9y^p5Ru5@@NPReawtOWw@8lh{>e}j&HMXu9 z=87|iSaxy ztEPK=-Dakr&sO#tHA_9klAx)$(F$RMIF@5{+~unF3*5rU1E20Wx~m z0-1tL@zPHS2V86|`t9O?vM!c#z_%yI0pn1KBO4?d@knd{b)5}>oDG1C=yh!1rJoQs z*kUdE?P7znLvG3jznl~sj6)@lT##twBXI%5buIvME&wu`*KvWDenPllx3%cEiwiCv zWja_mxg|mD<77oJQXfM)?_(hEV<4kwT_1aC`gr-=q4tIiL%JAe|AKSR`|1sIPrGBr z-5o<;c|fySZQ2cUhh}!~TdqqR8tnMlGo=hJsJ+@MrJJV7r<2+AM13D_8t1Ii@xXL; z4xWPP>`N_Sc0N@wpEN(r&b%^QnULm(IXh^vuqlnL_D-wx zH0LM-m5+~v?&BUGP{+pykdF@_Ct7)YcJtli(xXvY&5T)4mCq7Iw!vxBvq2JoHx&-**-M%GfLRL?UnR1P2Jh429m^9*(K zN%O;;A7=5}N1O%VGJfcHtsP99eoS}`4|Lp@&=lPIm$~%o7PbCjl`M$ zh8GqMrb{Ck$<}8FMU-3AlKgj|Z#lwRc!pX(R;2I50z-8S^j@=V*Ph*`IgJ?+3dG!A z1?2WBAjdZ}F4(KQbeTBc^}WAdr_X6X@4o zcnS#gqLTay?}tFo8<9W-U%3MMW5&Os>5e{-qYva5h1Uo4z4YD(kLz5w{;Yq|<*4&E z3_ZN>GF^Up+2Vz|9CewDwsF%CK<%4YQ8j(9pT{0Zs2&AcicY$u^xx zS{H9Xoq5!&_6gDbU5k%Xh5Rkr*Hx%=`|N`@ko4-s%I5Fn@_UtaTJz%L^!}5N)#a?6 zcJb;(9V&LZE^9sG0llH}0@i5?Y@<6~6BN2HfAOVzKX=^lg?i%M`deUU58owdX01!# zw_Dop>wDfD(*BnqFZX#t@6zAQ+Sl~$<&IPtQjo&TNq)ob%-R)#XV+e??>V)-`kq_+ zu)dG1{j)i5Ap`>l$Q|M1#3^Tz_hwF};`Og7D+4+aM?%u$Us*hqS5>>0!bwJEVnYP;}pxm=#9a`YDLGrAt@z z4ssLG&K}`Qn4;TMMJYlr?e!52~#4O1sPb9PWe)I0f4AJM?pF3IFg6 z%>IF~`#I~^=AOYG!}$Gqzs^hIw``;HBS+JErvZ@D0LW<&TA)#FA1_^f)7IS=no?c5 zaI0N)`Gs4n3|gUYzb0$CnbfNHMmfA){!CXwO08>4)Z!V+9IwAM$pScpbjxqw>gdt^ z&szufoZp(K$wjkioF$F8FVY)Ig9g-`lt4~OASb0-HL1^4s>v_!8Z-!E-)In8nj5zKNz%F(6dPekZb z5&BGo4n*j45&A-ez7(N*BlPtMeKSJej?i}^^t}i@5TOSn^iYI;8lj&@=vNWKts&#T z2+fMn|6JfTvo!ZZD#gCWSyCk^>7QwuNhsBz@3JZp<`eSL z55I#dQQj*F&rs_{3*uAbo${70B|2r8H_H>?GcxUBnG}wG;``^*YJ4PU(4gp(B#=*% zK;FR6Oi|4~UV5mtVQAXHMa+P%0+cD@O1EFG?hpERN|Ey3TzH0m*G_~Y@rLI_6&TkI z&zSAz>aAP@y(QJfK+tIe|8*F=c3roVS* zc?^Xkh^_N9h8REXEYF@SR^c_4x~6|nfnj%RCGY%WiN3%IqYFfxNI*^`plTv{>5NEo zQ6Wbs5*(9$#>+wLCPJj9zFoV0eVTAkuz1%E9UAY513BVAMZ|+x@zNRM^U#<_M|`xQ zGa7fSp9tcey}ehLxuXtmHfvyvLH+gb+HH(ND_2Vd?^Xp}*Y)RXJP>r^06B4hio^*+ z$4h6#S->J8`cE=mY@dhg59hkyPXLujSLg_aM@6K&6 z8~@qmw)&Iq!Ib@J#08EpWJHnC1fEH3ZcvwQ29;vpbT;~jAzM;xe#co+d* zIzxOhW`WTWAB_{r2OC0J{=H-(oS-d8M(#U87(q0uI& zp=St_mj_Z6!2%H|$0wbE%4ecOS^m9j zA_U8<>b5&HX+{D!rwfqN1*k}uAbPxXMwfFjx{gklg%n6WDP2aB$1k1;U0D7y%EmXk z*npr5BC}Woc`$ml_86|*vqk%&bLMO9V;b$w)jiiPyKb;|w_>@@nNRBm^|!NguxDU! z+s>=VGL096ADS->c*{<^IV0|BKr5#+kkc8cNM|-E`FQD!&gWy&9-YosfI`_PrE__& zF6F<=CqidiNlzuAgKhwxP9q?v5m1pvVT^j|%rQ^*xZt%t@3cEkyIV2mpK{Z|8;%}& z%8W0{q3Z=5(P#LS8|HR%QNr(vOzzKho>7h}53eoK_pI6yedm#1H|tmUJTC(R0r|<3 z5F3T%O#Sgm^Mk)Ovk`CQljetC!Ae#h6ugsn(5gf9C|=u3=o{Ngzy z{USG1Tr?}cR%P$j65~p%)LHTbrOvQ%U(JSRE1yqF^pp+JUihT>L1KhX zxw*uK)JYvkn`i%j_Pz$buA$b)p7xyl@64Pz^E~s+%ri63%$$OUULGs=eI%0nXMA(8UX5@C6$ zp|ge|bH9RKm|*1)k#dMgIfQS$ zs*BS*f3o*@RV@6Ye))5dG?kC92%?^QR1hBKK|w5LPY7ZS{3}6J_hK-;jEY_7DT0!4 zdqsRJf$l`%j+&W3-x4$%=t2p{7Id*7wktCP%?G+o!Yu~+nV=;=SL5SRQ4>>ws^Ruu z;#&i>THdV%+9YTLP_v*bfO-U73A9_#R-l^%wE%rsP&?3JL7hOi3F-m5Q&0-%0YN)~ z9uqVG^m9Rbfqo-sKTu>UQ+gxNG(k55JtQeT2y~XX-2yaA&_{sg3;Gz)ZV7h?=)-~z z1AS0@Zv`3_x6cBdEzM%lmL3KpmLxY5=#ZpK1p8^=zW4Jf&N+$ zEs1ZEaI=AK6}LG+|0HN0(A|RO13fBeG0^>jmH<61Xa!JQP&LpYNkI+JOX5}wv|<`_ ze*;j7peumN1YHSqrl74r=L>29dYhnjp!I?}fwl-$@KcZp#z@<|6fJ^6ULE73BnnGGeT8h``%^95*Qw!+Ckt&pF=a{>ky1qzOqGUqsY+0@3a2VeJj06J80(j% zJw5*13aet6UZs^tX(h_1)ykxXc4)6XqXpJ{H1sgbp)K^{q|4&F!4~)cN;-YK5s(3W=0L zqF@R&v`gV^cDlkTl(NV`?lsn;_O}J9|D`Q0IokX+i(O2Vo6Dm`A{i(yYLRGXhvWEC zQ%g&pJsRm!o)9Tdi1K;DVOL%n+U3a{c0a>;Vsnz=czJo^KhML4<=?+6z!S~&SIHJz zO_2;`0ggY}u=cJ{1+lsh z^rN%K&YY~Pg&?^@#=+KD0oA6J0#czIAW{wx1#>_{yBxTZV`kwT2rC+4Rpj3nC>ksj zG?dxUU7zZOp@yFgb^$;S#IA=ydN56&Gi2V;v~ziTQ*YnuuIA4E7QDZqwRcBHR}(mz zHE?DM+T71%hXzQoa*Ig0MU>AiTV^!0%dM>(R}AM?SeXgaXFpSbTQJ31*5B6F+M6|U z^;D>~xMVHet+4bXwOwi)V}*qzDEUN6K2bjTwzz0$m;4qEriPQxD#CkttBkNN=4T6# zzq)Uo+PQ~OZAoM@l|&*Xktm-;+hfttE{W|NB_2PCD?)0;LL{#2YFXD7)KZMdT}vm^ zs&o=5okYQOYG{|vPL6?x(-~Io!}`{rE0C461;bt|BZfz8A5_Zwy8cv8e@Y56OH>NC zE%4aRVU#6Nq)GWfr2HVt=Z7uX8u|{CH0CKpZ=9Pe}^!!Xu=D3B9XwWxPfZNgB-$+DGd@8}Z; zvOQa$SOs13rmDMlM^j2{^33DI!#~GceW~2G7o1bkTp-e1AewB>?L(Yk79WUSH`3Ng z80-@-qpw?24G9^>1cn-{IeX90RQ(N_hy$OtX?qq$CpsldPd#l5a%9IFCGjlKmp?CHaeE zAUU^k(o^4s5X;mmu|!HNQ82L@+9mcN$m9fhII-Tw&W5#fPX5}3(|}<7 zoLcWwM7^B)QUOj?^>+%FHgxBW&4ls{uk>&Lhm~WNXZH2%B4k&M!*){Hx{+immxz>0 zM8RCr&@Pug0{+mrBAiQM?Sp?@*UP2b3vdbYv|RTB%XZ|K`n#}{Al6qRLD^v0!HfWQ zkRD|Rk+Op*pB=WT(9kYBFvBl)7UAq5ySx{ZXN#A=H}7Rfq49fBCAn6;RTg@TI>b#& z5G9aE2_(uV(56sBy97o+Ak9bq2MNp#=#EMtQ>X+IDS<@61ZrrPz!(UmRZKX6VGW>v z^4gn&cN7q4j4(7b^>$$-rK-QHc{EXOKrC261s%!jI*Mu}`g{amEnGnKl4~B$y z`lzNcno>=qR1*bLt)X4252G~G#xI=eFr_Dq>VGbj*L}OXnmOQ-x7Ns`>DkWA0%mtz z>%00}xjfjm;%uP0@?4qF7pEaCY?V^I-JN~e8h}J9kBF2W+qXd1S5a!g-V+ z6Frx=8K~$a+DNl*=dCIL-d1+{ur{9JH?LNbK8m1iiXE(e&{g<)u6g}t;cl!a4#MybT z7a&FLt`ubloH%T9T@$Q{Y<)u#lmkS{0it{kSba!CyBv4`anas2oC9GcFRYfmb1WQy zj#$^)*WWp!10Bc&J7~X}WwA$g=4A836w0WAjQm^1kTPWqkuruTpE0($X=s-*4xNqZcwG)@mEElhU9^75{+kOuv!uC>jtCW8nfOB!hThyF|Za1D;1N)2rkXDY-=XK59NbfYTzA`!K3U$!x*D70bTp6J#b-j5P9mj~D4$N7Pz~+U`4n8akS3hYuxt$* zr1^RQIvb!`VfGF>Nq2v5Gj`1Iskf&=-Qc$=-o0+1PVgeDjxp&(xpUyy+`R)E zIbfp^Tk=V%GKxqUMU>AdtDb3Smr>6mPOhs8XH=Mogbn@{niE*w)YaVD>F1SSPy%>l zDQD7^aw4UiD4%jGA{yGI`~}d=b!6d`ht>RHh2v-e5vj&pxGfoLTU$re{kDZ?;b=!# zDHvx!2$-N0?zZmUt6Nhmuf-Y=%%a1f$+CnrDNBfyB}DlwvCV*nc3JW=;^l(6aF&D# zgMU*wudaSy0hZ|2SgUvJK!+If*qytUUfa~sncXJKquSYV1Zf!ADC8|`AqC0+B4q$k zJ_D>wXlR!Kb{*mP84%VEexm>bP;JP>&+_h`F`DVwJm=a+C7DP`CJH85L%Sq@47tsv zqT?qyY{;n48Ya$ruj_@9o6SXEBN3j}Ot{ibq;wMn)2*Rhx@STFxQKK7bcbd2Sm+)f z$xO47Or#_e1(U3yU6K!hSgr*QCpoN9TpH3SezSmdhudBLkouiY?%NGyIgiEdw^;o% zQLOdw+dH2}YLpp7$_%1>X4r;LLu1wD))p>y4Q7KK-wLbcmiX9U-{IdvhIh0ryvX0e zjc#<8C6Fmr0*RDB`(0a?(I;!oGM`J|gK4ylS{RM~Qf=Si-$E6ce?7tot;gHC^DEjoQksdBW}6O6vxZg)-&)BH0>WvgzxN^!tXA(gZ?oUx-)|QX@6~TSZ3s?{x7WrRsrvWb*zqI|M#UTSE!wz?Tna<7A6%B^A%R$GM)zI?X; zEDk#dGeIb+GQRhNc#1!6I`vIu62S7I5) zUS28p_g%g3++Tn#YnpnmQR0^Ea;Iv<*3j2#=Z##r%wTtqpPGYvoA+H??n#w$gh)9; zlrJk*an;Z+N2~m&@N*?3kU8q6VBMMx;)J4;$O$YSfwzinC2}IT!AU+EQf8C zC1n*-ne`x_nA$)-kx=Cmk@ATspHH@C*U&DXK94xL3r{$oC~)4(D}Z66(S=51q!{Zw zD(0j8Lg;PkJ2rH~1gjhSE$21|VY-!UA|;zBpKROIYiO73FM=9wITTK|mt-5(YHne+ zb%l({^2wG0I6`5yRWwtnL=q{HMEOM8lxk>~NW14zIFYvLu~B&QHf+4<`(r5sFuKZS z+bC^S-b6XGT&iiLrVHys{aMZ}oRk|x$_=7?ZrB!EL%ZDgDx&0$QsLa7zxVQTBdq)V z;F!44-xt(k*{o*jlsFr2c5;p`_Zjv>A;=)?vF%gHc#eRE;DwyHxjWBUa z8vGsbGzR ztsTGNRnKWK%UV3Zd&gT`?7o_Qa%#p7hS8~YG{{_Ni@FP%PyRc z5`cl+Ypi^Hr~n^1yE~GPwx`I&e?0-~Fk7IHi7Ap@he_G$zwx-GkG&{o_eQ6-H? zIYgAtAzLmrw9BDNaEN>Sg>xv$0G`Xsp|B3X!(-qOMQJ3Lg6lL2R#w`i09sK~qb@8H zG28}>1kJ&{Z7n*XcHw$!_5cI%qOk60>Qp;KevaJ$TaI%F9#mM@5eFV5;S+e^0r{!f zL8RG1l%E~8y427|xT#`rCRhcM{@%-*3I6ROyzdm!24#zOoan>VhgeH#PptOLK3O?q z8kI02C5%W3^L~@*)X+z`N96bk^Q#5kj}#;FiZrxaa_p{`!L->r z+U7EOz{{IywZ1`h`wsv9s6ff#V%p`H6-VJ|xx953mcqic*x$I>LG~_0mf|6FR=s64 z%#A^?FAR%cwyNbWp2`j)Wd~8dkOa zUPRJPYb-lRm6aFU>nBh07{&AkF^ZHbqllDIMEQ&&b-XmR%cvz_)YFVNba`r+&f~8H zy#n;_1sEkG`OT@i?(P)iNM_f4{lyW5$&PDM8|>~+apKO(5EUQ%mR6fQBv~VPNK%4$ zNG2!`iIj&#`8>4xj)r!5Xm`^M=V4eO^!HZ0Jp6G19zs`UZNlxvUR9NFP|PS z%qLg*SiKIjOxWC^3HB}{YHzAG{3O*qH3YPybNz7XS=ZLq*NSBn9anc5+piN3@=Bc< zoXjLb@hd znz`+^(oCc@6XgquRm?QBOY>H^ac}wSep*0YS%f!qkF^ln)?+_nW$VH299xh4?woCjp9KR(oKnZF=ao}L$|<6JPTAtA zp#pG5c8o=E?9Lb{6VtX&qtpV21{O2ts_ju<(QdrI$$Q zCCaDQR*4$grPm%y5KeDcx6$7v_NpHLF*bU$2WWaa`L=0XjUwHX9o)3tByqt8Ot#kK z$qLE=BIN*4J_l@1QA1}>b#hkhi}6?|gx9MW?KC&6Vz&Af2V290i9n05{MdIG>laSZ zZ{W!Tudp@RHLchntuM8B`I;u2H`FU@cbfq>wD+Rc!LdWj+xxq&S>4svojiXwG_y+= zF3j%I^EZgiom#N1B`cYZEMrKNGKNSQL!^wc3A3W1q5X_WA2-HygfQmc$HAD+4wyty z$q(kvHw$rxlqq+JlsiPh+|kf}?tJLDai=|mJCjd6X0gc%5*aj7bY3dN91^C?AyVcL z1v5uO`7$>q z>7KB;vwend^=Ap9L<9Cu?%rkh!H>pWl^teGHJ#LUlxt60r`y(3qxE)aX^(-S# z`~jtZ?kl*Lxvb>@rPK1OwN0`K@Zv(JJ$lbPEEHCbLChU&>NyzFwvObDW(<*L43TCG zQ>&L|jD{YcIR7A<3$L{}@AL88E6&^V>jPJu3-U2v)C&feV$hPSN-Vd@1LZc6a+@f) z7-;D6ar>tyjNAUB)4kkYnJ?ikxARZx%x7)BpYZXrTx(?cNa~c2M9N2^U_NT-@$r#8 zuoF`W9|)1!8-t~`pjPOsq6@2$mkP(QGaNmnJe9N|~qieqyVa%Q9|HB+VA$JRs-!j51(ooT!n&&g&rp7Cto z%B$x58GQT~W3g&ZwciuJ{c6s3Uw$)A#=)=NmtRwjsqYos#mF_cLnA9LlCK#;q!~h_ z8ImJXn_CS%zG8HCmeH?ubNhWCTfGgycVAsG3g)m!@W)w}3dSo1>fVtBid@zFBhvgM z(){!5u^RgL7O1Vh0yQU_&nLb>`TOkN0`mPmnIE8zT8`T!g4PY zl6#YRY`I>Zj}$q(f`k#e3WnDZJMoOi0?&e}+WGf)}N zB<3&r-k$iZyGoAMI^Pvtp=eLsi8iLI%C=z0lrxNHBD#cW^zfdQLc~nRbE2ulGbUpU zld?^I3*78=feYBg+soVJpYJ+){cV#^I_w;$*I)Oy&&}iB?8_~Fli%RT$6NAiu-B@4 zzN)wTI*nU0%hN~aToDs5b3^?*vIDnd=tT1o_z+DoA9Imro@HB+5TaownxjkO2xX3R zN@fs75eMVp7hc3&iQ8TrOAopj_Bq$1)yF#f4rA@iA2f1Vm)E!R4rAnb$e2a;tUTe# zwvhu$q(g_{h;$f^NO@q>VrkIOmIu3z8xP(e!h_ae@hRA6^D<@(9kwL;6ZzcQj1T*Y zJ1JIf5h=Hbg1M!kQQRAxJ&D2L;r_QcRTYb>BK;G$7fC^P_Ef+*v#uz;wxYkHom?Ik zY6tI$`~r1ACUJvPb1;@(n{ZCeL~bYk7;!I5s#A42=&e6v7rX=f% zOifwjXc4;)aGjK{Da-R^rCEcNCS)x0{YZ9HOlEHubQ7N14Jel5Q6?fB2|EMNQgarb zZ;<0v66S5drJGW0cicKi=Ebq|3@a zm=hms+B^qe+kwq1J375vK#}uW4H0QIL^RnqdzI|QMmTMq-PcW5akG506_m#Fx)Qf{ zj4Ez_{YNcsy&>ZEqj88^a6fCL%z1js;W9_|tIQFp%n=1kmWF;^NmkpalBH)QvSRpS ztjOLKB3aS=PR&>Q3Wfnzcf;8%5#0yaIqn8Rcwe0xzMni96|EcW2u=8C1But9O8@Iq5ReM&W zbgIznnp*E%Sy?vg$gHCWhBl_xmhCzD!0?{Zl5f?nFS&ocC}o59#GTH{iUYq)oi=v} zp3|JEHzj74+&4H>wCL!KiPHyueU)=$vwZA)R5IsaGk}Dt2kOE!al+`ni8O;L5NlT^hhFT4yB!ru zhgS{fZ|Rn-?&@ppO-bd0T^^UUVxvFZ4R;daadmEMea~JS_RHOoH5jKu7rJqjDAFV5 zOB=d1&TQw7`7qJk0e_+?=BqAJiL(5}iFsn>prKzM5_PFhqIB6ibMcQ&qIyCk>b>lK z=;e{9ydJJswDLT{Wz5qH9J_R_{E2Z{7BZ0DoJzv2L2~^AD=q{>mqbOcT?PDI@bmC^IrUI8y&yFo_&Y0 z{$qZ|j78raY3I2b=UsDjSO&-n+Io^m9RMQI0U#pfo=u!Io+Ls-kBNJKKv2DoC-?lr z9yWpG-jBz@z2JUm9t#T^gpyKaA(66>D42yBdQ2>wlGQ#KH2{goFNVt-Kt(WK<63^(^xEQ_!@OWGH`#ls`nl z{L#?PT~1A9#90z?zIWH}N^Xi24?Z#RD=f#~|H+KIUwY__q2D}&I_^ASP*7d=Cb#r@ z>#}0FdF!%w_(X~CFw8l)4@%4cuvs6qWyvdkG*NGD>602wDzEn=3RiayHdJTol`llf z7b4}0rJzXCWQ5jM8OwH_4&#@-+FTH`4){9(H}i&BybU)m z>mNs^M8sF~XRG+;{8R8RaYJ(uc}sx5jy-Am+1=x1LL{o%f@0eLHbM@x*iJc zZ=KusBIw{BhEucHy>?cmGV`t#0f|KGtwu^gNI2_KPHJilLHi$W+;Y*-W|XSNEuSyq z-%7-xQm`DKSNe+}6Xf>W@r;@!cutf_hlr^b%sixCu^+d+E_VU_?B&gGN)RvW@6Ye$ z>YeBB&F`fF{x-j@-m<}0)|n=zfvLVzsFO%WE9=Zp_e9A0{+=G5s?gEhCB@v6yLL=P zdca(QkhhBtnW&l`k!pHGnwyqMMUpH?mW1BuJngKUiHzE|U}fdCS2-)=gD(%KHgeD; zj*K48EM9`5TrylT?97|E==s#_y?eHr)SCwHJV|1!E8$688vUgQ%6uH ziy*|s;B2Z{5BE~@4m``u4m=Z*aekrbfe$a6>BKC&{LMo1`UmYS$93f+@Z|cvUF7}T@^pdD%rU1 zlDE#iTaM6?>qn#L>S3lG5l3ax@Lht=fm;_)E^Ai6#a^>4@ci{=&3PVo3vJd|)~@xH zH^#|0&d2>UzU*Ghm*L!{O>1gbv$WL9=`>jVHPTTy`xM^uO#oj7C$;w=QhN^~<)Zz{ zc>i`Frl?3@yPy~%+Bb!6l%uP~joH&Ch!tb2pmLxVK@~vFf|5Yn1XThx37QGCRnTmp zmn7ynKqZJiYUTlz3F2sAql9B~yhRYepRUR2tu^`IJ3_+9^?ol5V^^Ln$ zYg5hr0HcOQoqOFyrSapI&QYl?SIfJ#@ZBV615mS|D}Z_gT?w>X&{m+E1hoKtSWr9A zVL_chw+ZS2x>HaJ=m9}HfgTey0Q7S~dx3rI9JUS<8 zUIyYBGEwv2Ks;In)jcYE9wv_Z3W%p}M~yvh8}$|3ctUg3*rS=F#vWH3HTG;W$REOS z|Gub60&$)-6K-^sn7Arv9l?(M15I3ZX8oT2XqMC^c!5{D9>Unqz2I5%>%s)&$03jrkBVEV%Kd?!t@V?d5HJ zxf2U7ufMYfN>RbX6q1HkK-LWF*X)>`bd2u8X9j zP87D6N2`!w#*mpY8PC*p7k@6~P`fCS$vkBRmCY3l?q2Mz@QUdlc}~KUOr%}kTye?w z_cPT$@WeP7$3omENh%}~&*aFc(`|*r2&X3@R#)HQ#u-d(3is78tdwnX=tzp_h?9tQ z&q}Lbk};YmM4Bf=nkSYaYFlrNhDL61U5v}>F)WW^Xvv*{v!yeM`Wsm^Nir^xrmj6J9+3L8OJk_*xgw$9&S_EuJT6Ik~r3 zzsBNXs{`^^g@8zffGD^+(9ok-2glTI{%U&Qk~!`_m@>oGbw_QFf%X}%F@z7Ykttr~jtd^;sqjjIVk!CL%2$-Ft>Taone zf@^U$v%I{uw6_+Yuikn7{#yJ=U*<7R#&H?$=HOa<)Y%eiqwUR`(T3J?8vyyUx$qmu z9KQ&JXGZ_n1%er(0zsq#L8Jm9t9JTmP^~$wlximND7f=1yMKxPfou8cj;psCS;fUrIjWlM=>BDcOMgqxn)YKBE9u z4&!-Ka@J$`f9yF+CTh+SY0eU9&RXW#oYl~_BRIx2=39&J;up6Sik)B@yI=CS&v8d*>;n>(M%!IOd--t z$?=3#7^9(I^WyfrWY~!)ZlCaFn76pyJB|#?o5s#=9W~}M7xcOmJkRQfV=s7Qr{*z{ z<}p!l!PC&=DtNk+L$2VV6yiD&1@Du-eDoH)=y4Um zt-hA;jgpHeq5yu}my6y4c-;1R7<2g>OE%i#M;>YB5NYNR1s6XJ{hAlQH%rEyh~jsp zFXOz$@2s(zu=xs?$GlC7j%TIJu@@aOLUV^mbB8Fn=xFG16`l9^iq1UAffG@5uJYx8 zx9B9tmIMBR5t0Q~>`9+8mPi>(6fE`{8jN)pA=MUPXNQw!-G!Ayc58>hs(6tz75*G6 zO;yHb-8=YH(Se~or|E!dCjMOQ`oX(mxs^yVmvcH41l2>>Ry;ztZBkIG+nKAt zcE(6UBO|@D6EjEObdx1#}TZ{UyDDv`G>k}R-O2LGn;%F#(@-O*rm8%_3je6 zErmS8TbAXdT3Jq{EGJTyGaY&{HJCt_(8rDCjfhSOJ@G7eLRfwRH?zoPj%TRJW)AtI znM0(RLlm4j8k+4njC&)s&eC|D8g*i76qDJq!Y<(vrwFDIStGGbyokFJx4mp#?H&K2 zAHzQ92J&x(Y2RV2=klQ!o7C`mv4sn9bh+p`SZ27PdqY!)?#GW?7lV=~hp?i6E zPwx0%78o_4CQ<`xBITM*WwDz)4Lv&7xE?HFcHw=!UM<)BQ;s%0WZ3@fSG-e>V;;gD zjgOvUKN=rX_%c=@v{^i{4SOyY0Xu1KXTwvWT!`Xu*}mkrbB& zq{<>N<8H`cE1JxTvRG=0GZR+d>CP|2elfwBlYUp+nL~^4a_2&Ij-=0S+>$OITv|R1 zqyLC;PD`&Z^SON(Y0{CzVskdoB(Z&&qDBug_fRT&PC{(SPL*-yvklep8L|wZ(rg8O zqiMzSY}tiohO9-b%+^JR;bX6q3t0Eqi=&wQ&A&aaqxGkuxYeI%Wm4z!SHpg-*_ir2 z3-lMe5?S&`cvgZ9@xssf8=S(_)NSWebk1z!|6-Ydm`yQvGksJdQaIST-s$aD=N#7IZH zpMVnVzT^6j@{Yu}=xDlXW-9KKwf_|q73aR;x>zIT>^EbBn)@<9hDVS zpMV>78pZRF`0l{-G~B20`+JBlVeZG1dp=OmsVz+u zoh>HjqmyJun#p1zA2*+a?@8v1c$S*G@SK*_*7oCcJ~3(tB#sRddpN zW~Zw<(|f8_K+{$2nX1}a_s5=@={=Q{wa=P=6n(E10Ht^?dT=L++ZE^{xaWlm5_hYCcnvVychQ;J-Itt zcQ4Rga@w;<&*m&ypj%!M>6TYSnzK1_HD5LK(@2O=OWVAhJ%1&g-D`pczQ5#wm16%n zMfM&34VB_OenUmS7+d0gEyP`c_=vQ>NuG3%Ya`tc_&O|JBV7)zxtC`c@E8WPWH!jn=`p`1;_eyrvKdF7 z=x{2L4yO`nmNDIWX<63LCp_a4CoJQB7Lsu%a0J|&#V5dw(+UImtinK~!ax)(3>sQ? z1&ebpLw7Aj>SCuVvgl>(1b_dm`__9lhUXrJ+`N$6fK9{RUMJ=4T!?K6BNz&^+Y(Ya z(CNbIlKI8>T`a>=IpfS06-{}raWN}SdokJk%|>>$>*)3OZpF1Kz~64gWg8#=IqLi- zzwu|d&+|00V;J18Z%r-l?uWL-b?}}seY@*L3Q;yo0Gl^tgEqQE+UOE#-dOUB+}|4d zxaW;-@R!S*3tl&Q<3Cl^n>RPU+8Q@6w>*{T=($D4Xl@Z{ZV?6NmWDgpin{%6McF?aZr@?7`?6p0&bYh=)zHF) zd5rZ;5RA@PvOpCLB2_eql(Ci!mUvzoT8lx0yXe%}lyIhEPx|zRedq%xq~77%9Pmw; z8X;b58?3j5>dy}6L>bc087H*B&0egtczM}P)r`NH4!fw+>#s2}8;vYv$Dh3SC%T!0FmDofX5gsAKn({Ol0e03&%le0cTr(F?jGsaDD;)7M?MHoOr;lZ+2#IYnj zurEeOamf{@wrp1FF>g_^+n*$bYKk=XQ)RD)QkgBACOhSvYA#0hpJo=}SuW-t6^7PB zGYsu~&XKvRvkjNB>d6h~NYQx_zrDrMV(llx6PLGGZWWjO)g0bpxh25gUo8Ct$^0h2 z(S-Y>Lb*iZGoUM6lV&Z;Ibi6q7xENNd*C}2g>AYEG0Q8drITzsY*hzlf{F=|iV2a5 ziDi^snW3RoOq}Ny?cwH`!`M8NlCof6A2!cyaFseI>WU3c`^ZENrmG_mp0#lZ5k%t` z(=v(Gcq#q?XMHSP8%ytsIZI>*C0BUL@`NXn6`mxLuXS;su#k0RL~6+iVvaG$4*n*CbbyuAK;nvT|9%m`jye{3}&ZnkFk*DCxbLoijxO@x-V;dBpCH9ZpK zZDX4xz^DSmj8Fk0QUM}T0kUkeBa#~WMAb9eWbE~biZ}yFN!sKb=*1*jmMSbysFMXULkiL%& z-c|IQ-)_O2ZsZZ%I@$Z!=H%vm_mGuG*kW=REGpajcF8&jL`YnvdrL45#=o|GpuE@q3s(?8$zEf zdrO`sO4jKzM;14nS~|nLAJ3%u5S}y4-{4tkeu(G!<|la0HowF30y7ogo@Xw{^Wtpp zU_X59RR=X)C}fa-~05pie5Dk)98&~{dJDls(#CT z#gA!X?}4cfoy)3gZEC4)YQCm5)#snZ;=Zac{C;?N3F2w%T;AWip<_oYMvyQ?3)dp# zX-7|0Z}$#l>Xd2b55vRz;ndQUY65jSZOgpUX~J!7Q$?kv?rN;Q#EZBtFSy3>iOyF#p&uL z>FO2f>gsfLO}e@^UA-Y)eMP$Z%5-&0y1G4G-I=cLNmr-R)jQMG1L+(0;w$MJZ;>Y} z==6;r!E^AN!|8+j`Sak7@|3Cl8$Tui4$1SdxZf(z&&u=j;(mubzbMbI;u$EBw4m@` zeu?xirQTjyRQK>gpl*u7q1JChz zub2$1>G57m!DwxmpU7sK?m#}l)WdJ0jELw=*o*k>&A@j2=H)H_-VC&1z5d=gwQc%( z-uwPJwR&F$@|*ldEAI0>g=!3QYFsw3w70iumy{*Xq?%PlWwUV&;T`J8%T;nobCpPQ zl}K~da-`V(t)Y*5dGk-R{R#f zy6`01E5d9tyvPvdDz{2Hx>vdTmzA0K;%}?MWPHrB?=aSV*{__4NwzFoH{-Le#xp8g z$pD>VBhn^^NZD$MU=7YoLu(Pp&MJgXvBlZZke0JZCa`i0wiOEfXYhf`%V6*L{qdMq z^RH0hH~Ed@Hm#<|a!Y;m!C=nk4b+n}D$qnK&_v34%Mm-VrlH5ic^xG^{dL26|B4PT z=a1iHnuq;EV_-koqwFVA_7erOUqjPU^*Ef7UZ1JBq2!U0N74f)M=-4d1MBoaNz9qr zfZfXK>~zZ|CC_h8#b`hXVPfWOnUwgzTMZ`4325vnCAQ{AVex4L@FSbQ^l@9vxfd(ARwHu zNtn4HM6>nvCm{an;x!VGG0u~XCLZLViU*O32T`ziXz164c#MA7J?jt7(Im{0*Rgo` zj~4fe$M?ojf8>o6WhdiDR3e@>#da}L;FmicZ>5BNa&IL1msA9ZR0N2E zML-oyx)WV=6o*Be#FX1J2V`ZH6K%xCKc6qUYVe-< zN9woFpF8wr&WLBQ{7;5Dp8pJHuCR`8z4O>pe0lJmvI9e<2mTT87bTh1=D;I)M&A#? z===M#9CVdXbWLS4 zy`i#FKFTDlcV2cAfa4sCQkNbW?tkm{`I1IV=_Te4Z7;GZFR$Bogi7x7pYbGle;p>* zHaA{0B$VQ_Nf;`@Jl*bYwYkw{0=s^C_CgI%ANU;=%b^~igt-ZBIE>VXJj47Oo)vN&&RK?250xg0*xxKi(abjI0AFCBu>YdhJ8L?& zE$`kTM+RbkYJz>IZsmqL96$=4oC!+7xn~7utZczyn~MVkv4S!>ErKD#a?{;U!}=-H z?n9<8h0FDQSrwkV*HT5KrHW{>@VZ;vI9EV4*}V0Fw_Y$;QpYs2Xu!ycvzwBK6Ah}| z%-V>^S@2ml6A@IWgc3lbpJg+hapEF{_VV_!&U78U{U<%dyT(?DGTJPF)!;M>!FY`W&W-tY12VAA{}HS zQbyVILFnYt&;g8MA2VS-jHh1zI~bMS&B`+8ATM#C=FfDgI!m}=RU$C zKzCrnpHf&TJM;D}dfw^W7lXNTYTbe@@${Cmfit*P=}2mtBSPRFq?AZ+iKMs05Cp#1 zKYpNGg5)=!5@eGI?chC;!QT%%U+@3$ma7&tmZv5<_lx_2#u(b(iFn!H_MV+?j5rh1 zjTJ{%B~V8Vr&lE#Hy@dYJk++QA&a;KS*wjfAl-ramaOk#$#}|Fu^3%MWW;)cc{f~Q z(qiOhb`HbMUbkD|`O8>%Q|A83<4#CZ23>5zFX8<90Do)#N!ji6`G0^ByGz_zeQpo* zmx?tzdj4+T=sDBOXugE|`0e>Dd(AEFYFWWUyjoXww(e-{a?L6{-8(w~M1H$_z5uy< zz5uB(=C~RWQZ#fxsb!}wVQ#}yuUA`Y{WgEL`e60;Z{w)mg8RSui-0}cu|?P4((7dg+rKY1VwRqHKGq@j^&n!xkIvt99D-dg`J{GRx2YkjL}`FA_!H~9_z9fy(3 zp0QcUkbc$biBzj6Qi;jYEzOY8&?r2yOkxSk&?cv+rEPFvQ?Y5tEZ)S;Z~8A8Tspg` zzV!CSNTz3Jy_G3+RTJ1ukV^;ATTMV~b-tV4l8_v4ORtJ-ZW~-xtZSey1i|i-LLgOW zpO$RGPc*VDNF_4UsLNVna#GMFnVX4Am-8fH+=~d;!dKj@(fu2<4*1JxNgC_>THL(6 z{<<%ThNHmo^7`9fHWU53iSV2J#+A7LrEpJTbyst*w4=*;kWpQ0TW+bdXEzbsp_3!O z#RQ6js7w~+zeC;?%SBT40KVFzPPQ#3Gd0hNG|!1N&n=sZCE++wRYJ>-SsPhbP$k^# z%-)CWFQ*i&qyW?wo6Nl0bgDAZKeZiSHEK`{{jP0aRq^m}+u)vJ)4y(T)$HL#jh+3c zmfjBSaaB+MNzTTdnK(r0rdWDaPwDMjBAML~8r1@wRSD;`Onh@1zA}^np}9ZZXoaQ< z^#nGRjLAX8ze6gc<|2Ig<~I=a0I3jVfK=Ik31L)AD>4X#5?Fy}L>ACRWej1Gh|*+P zG#3{Gtuk{3!n{GOYECtKflo6Z#q;#6h#kf+d$AVfr8+uA1k0VJC`d(q9@mAt;Sw|lzEy3H?G zC5hgtpT#z(*!B2cwu`kz7^}M}1{1OU-{}9%th>3b`%~nQ^8jRBa%rr+#5--F~V`Eeeq>4rqzpXG<0u{?ynI&c|unrUX>#y~E z0T+eIUS8o^<2ri%t#Faew*K<^`x_y@Q5nCRHy=#6<@>cT7%cbw#Ar6_IA4<%%^I)X*4S z67~m=E%EDY%1*>N$^MnD98==h(iyn_Z()w+x8h?sIyt}+HX#h$i4PLFo0DW2Xw8A_ z(2|A*12F>4QGmDBH=qp2Qe|iHK3HI0sRIFJZY|v)jAl>%m0{;d{|_2B5AG>9=*kDE zkCn4J&ZVo8TaHlK(|no@q@8(6@m7qVCS(U-RRzxmG0N5a&irDy z#zX>?b)V&T^;Q8wvJLJHD|FUC;R8BAOd&+N0ajv?(-q#J_H~EeIxL*){0F0Ox z8LgJf7l-WJSx8^p(}A)DitN$?MWh9aNDEYsDNH6zx+FB@VQ^24Nxj*rYInDLbJo^u zA6~Bwy|byNPSMe-8ouRJ&9TA0d$ei}hiXJlj#ka2_tC0K{@HSb%G|~HLR1RGJVEEe zZLXk8fG!fW0*EO|m^pab%;q?{-TIsJ4^cT5BPPen#bpEOGO2Q>iLksus!mp`jLlwb z-~E?Y2sZgW@!JZ4Rr{9tGMRBQjt1Og*hFqaYwr$!Eon20SBKY~T*lc2RNxN=_vV|e{%Ex00?w{ZA%5~mj2}6+YzlP9yrZe$$d2|YS-t-h?%^yOnMl2PXJvmhzwA-2Z2`Mj+&q1SuMUV&`r?qfHd3`sCX;HEdfMbB4N$|S}tx& z@ucLi(Pzb0BHJ{aZ2RjS)V&z6R zrgpl|f64@=k-kRK{uBK5zTXFbtASEHzY*xKUwa4qjt2URI+h*LT;nTWOf%ES`MS>) zZba90x&5i;){bjib*90ika$f{+MtiDUI9zrT!*Sz2KC^`a7?$Smt{*SGgtLhBGp%k zps&i?$#YwhS9hn9j6QiTrus*#;C)G5nC$4fwyCqDCE3g#A%3;A?o7@+x8=NKTX*lP zh)xos@nBI~QfWjgwOS#6@gFjJl4OLEx3K{wIqp2YXm`c#aun!RaHn@ee^!Cdy;lj)32L5x)=0?6T8)3A3 zUIe6N?43YNO~P!()0QW;7+RhtOAk)VQ%t(_lVmw$ZiCVNe9gUj+`lpFfWO3)XVTw^ zo0s+fB?^RhpNj43o#$`&saOWTnF0Q`q}jH^zm}M3;`eso9z%a@T^ChooU8PzMBB{+~Ymb%dQe+_vEQORk+yRh5m$SD1a@|B16U;vBu}<&v8svHcI=vyZm53> zUIwtd^!D|a3?407^t*v?4ep6UDG^~GI0p^UfuYnX`DP#PaZg99m^p^w5sY(O$wFau zt_w6Ip(St38xaFN91=rflIbpzSq2;CD~n=e+JWz5m1*Nu;BBSZoSHm%uWiq^4BnGK z@u2)`bMpltZEn~Is7^c)3{b>MQgvsV9H>3}4j^ro+8GTB4+VZ2o;oNpK~$)Ta{9(3 zIc4J{vlqV8WC-G9^BLe%WC!Ha#a!ZzW-VfzU>fmEnyc}gZhG;YAw`KQm$%sMgiiu) z!b$w)MMaRsjw)S8fPYkc*_0V`iO>I+o+jsf_|FUU*CxjnyINn9!Za}rO!eo-)TBto zzM`YAxx1^YwOKbL^t3A8BmJ^X$k}eYG6!GUwyL*j+w$HHE@kYjM-1#Mvm5*Zl;zym zo-LZpC@q>qS~Q8YXfpZaLa`|oA=c0voo5E0e0lJha-7{FGrOHSp*wpYhH5J=dIT0j zNmsSH5+&?_6hU#`KpyR97LfrY1c0(8J3rT&?p*R7aulC7tD>h5kL0LB;UY z!Dq@I!p~h*V_?;pHg5_oi?r${r<5^)|!gGc>8?m2bF2J+WT!!a)*>YA1 ze|xQl56hXoHernMEGT*`XSN*p*MagK4hb;UFJ;R?(P;Y0^7hu|YgEVKf_c~v>B@Rf zLEw1LGvMYD#F|@KPeJGA=Q%;A%YVZ~)T_0vJ98VNlY=^~MWoYOM3c>At6udbQ}}#K z5(!9lcUi?KIkOWd5cZ`OCeOX#qPFCP$>#oE#phkXa&u^@mXJFRC5?Fs4n_*f({qP2 zH&^b(PZX0LamTP;HGc4}Il7{92{jVn6=Cj35&V4U>D;as6W+bF z?c>bPhd(BM&drs#;Fnru$RPl2Na6qr-5z*-7GB#YgO&l<2o5;YYKQCMzbJm}AtbOI zN#o5vV;Lwab1549?(l;C&i*r;mGSh-PG@DOjQJZBS!dsVM3-LKlewzL+1R;YZt6^D z4MG^_WbA$C#BX#~@606XoWuvvG#B?TcJ;jdL#YWMIMt%?vyEolBb&?P-O3Ex$a<=!%sq+13}J4<1Etd ztW$NxUH>^b^M00)MfZ^t103Wn%T&$&3Y!n$4d7gD`pU{H5zQ53JX8z>Y=JV6sV)MV znHw%%0ga`)2x@K_+_yT{)|C8a)=}t4opXox?w)@`CKW3~jP2lh0Cu?>|QP_aRI*x-EAx$CZ{i!)!X{5-s$ z|CDpz;1ea!A4Yaw0q-ka&xGG|D?D2=i_2X9#rggNc)6ys9br04UM~5a^F8^38)$nO zzPWJ@K9gBFuNI%eE->58CP?=XQn20~6k2p|=`BNZhkx;=hKB8B4Ct(!(*U8Hxqf>D zR;ZnI?pVe;;!Nl2{U2TAzk7Y;hM_%EYP?YGvg8aOj{4EsGL6w-5WS9J_7?3?x7#Gpk*e4&X!{D>X+CX za#Djm{DUbNygPZ|Um<2QvDwT_=f2(1M@7GR^ybP#ApT)YvX_Ww_^i8F=d6&g%pHc$JZDXNZLVTEu>F-`Tg6GB#<@doOGh!k2Yu^= zv{m<``A8TJf$7lPcA%+Kq*Vh_?Qei}&I%R7Loa7RU3q=pqW596tb zhQO@J6Xw@I`tFxeEdL5_QS&t*ozB|^1@*(?b`#Kt1lVLi27s=NbpkdFCLV7n={^ z`8M-+crG;mfalxIeRwW2KgM%~c@ob*Gf(4LW1h!zjd=x6%=#b)>P!^R4Q48y8_k>W zyuzG^=R3?Lcy2Ze@x0P3!*h#Sjc222#&esw7S9&56VGeR=ke?@ci{Oh^G!Va%+K&d z3y&P>H#6|O)-1vEI@5;dPIC>O*PCuU519Ai`Cjvvcsk~zcxKEYJl|(Njpsr0H9Y^1 z`3au?*Zd04L*~En{G@ayK4qpNH$H93@%*frgXce(#dv(Bs|=Udex~uy~;mi#dk0wD6#%iVZADA8?~XG zvzAZ}qtiS()v3^x4*qy}*g;IaP;qKHaN06PVq*PMLNHj^Dux+xpx3q+8MshlA)`P|@l|?{mtut?qmH zQdg?AW!Wz1TId!|nbu-G#Hk*JftW(awn>i<5C8lP*yXjeQ|!>$MM@zk=lnW**7RMy ztiKHsr_+8cG^+FwsnSQJ{T*YTT{?L~dq-b#N0Xc^{pP-8)3%Pzj?}JXsyn&E?hpg- zUCC{&$!)#e*R*zBkgV-&ZR%^qr0}+G497KfwIox$yOK>;H+6JDBb@AG`@&>H{Zb?! zXLuy9>qxb87;hV?O!k4M>@U3OWkK@NBzMN>ZkvfYXQ4ECo>^Zn>&+9+suj+|&bs-v znd@rSLyu5v1~f`&^xo*7Qfu6TlYB6uUS==~kTmzM+6py*y=kshsc^oI`RSataW|UF z%yl@bS0~<_Q=RW%V!9af(t~$%YWlyVkHmK}EtS4W?aX!M-uF1^o%ib8N783ItFB0| zn(eGxkzO~)Sv7mYFv{6oPffn-@yU3?hUb#f+lT6FVcu1reur)$H}{td8hiF!EDK(o z>sq*lT*>owZRra*KfV(xPi5QDD%1ng1Py+@s4ZO;89WkQ)ZH`i02(dq%wmrg!hA;A zRnEFOCF^E46kT5F?iD9Y>962AmO9vm=mv6wM{4Vlnt9qrm)^c=UgH*F;TyrXYh+NK zwXe=Aat7~1Qg817;#M;W^QkO)7D)GxKNGc~8j)~WhqimQK(#0!3DXL+Mq=&*(w5_} z)cLGEicvSSI(i$P6J*eTt_)I6k^286*-Uq;>GSFWx z(+_8u7vNGMC>5?-Vdho9Dr{O3JKhWX@9^&rN3eI( zn%dRE%Uz&w$HIl#Nz5N0sBdpLrcUc}BCX4blt%jHy1~9@h?mxe~OqP8x%nfc6GtLta>q}(=T@CGsGoro?` zB7e=s(TR-h=qfV8+5nHymbY=s5fo#Yl23?6uhaG{lixwIbwAAaOrn5Z3^yH*UJs-e z9L-s`Zy~gbv+mJS!af{oOO7P!$S$is5 z=o1HilkxAt{mBvH;MW}%!UPw4)V22dJpA>J&3y7e%`%A8EQ3fhKSwW9V2p+qv&>C8 z-B$1XR>}y+SxfWh^<(Wrctw5hi9iIVIQPjf=hL3~mXTKj7kqglr}MgrLpYSXzKs zp(V`2cxt|t;1{_}8JZydq>0jhjEN!UWZ8Qqx8KTszUE$C?q609_F^xS{Gvo`cO9)i zjW4H)FDc4Nm)C!iTV1e-Yzufl=C)wA3iVeYOcT?<#YkT$EHTTW>!(^bb@ZnCn>ty! z`eh~Db}qNVlS(-0n1QqtDt_ zwz9qJn{L=RG;Ug5w_)Sbn#QWtH7n~IYnN`QZbbdZ%Vt%HFb88Ak{*a)M5GcXldM*# z?I+ALcL>HX!kJT_YQj{Fzc7>2{74BNJ+)1}uw`UN z%ob{wxJb7toHQ%9%pV-u}vEF%Z?(n@Rp>Fv_>7LHhMV2^=Z`ymq zR*VpS9dG95ToJUa=q8N9uB~>az4Xu-L+5;5hrjA^7_9TufnWBYF&{y^6N)g1(SAmZ z{fsixe^&bt;tZ-UUPgT}EKzUFG|nHctEhF=Zl_@dor^ZsIVaZ*mnj~ujU!XbFsN0* zItGJW_5=+1j`OlN72KAI=0hCdEm}aJtRQ+w?q6AI6WnC|)k>YoXBjLuqQx710R|23r2pKZ%-XVaRV-Z<&$C*U5|UY4KGHFLF`_d(3YSd_ zyNXenuup+GQJG+Sm!Natt6PH4209s|gKCXdCTKp~9!GnaaJTxV?<9^OO2b+#R%5Ev zFwxWzzQ1Ofc%dL44$bCP3}8P5EvQP)vHc$fzT zQD&YHM49=OAj(V()o4@}7o8#~3Ab0omojrF6vC)zw%-yo8*Ud$IM!o}1+fj9A&4?_ zn}nmx{7ldipsVrms1)OrplZ1Nm-yBIt(JFdfi?-+0Msn#3ZNcAR|4%8v=!(kK`lTZ z7Ss-OSWqX>ZGw7$?i7>)Vq2i+d;dYu0G>Y=-@QP;5wssDGL_+O1ezx3W}t^8r3Zn| z61Q7`W(oQT(0oB31KKU&4gq~w&|#ntitnvJ!{YW?AP$a1Wh0U|3Hl=3<_fw4=m~k_ zt3YeSZ3yU{g6;u|Ni6pPog(PlK%bL09sv4^pa+4D3VI0WK|zlKEsUpw)t20NNzzWuRt3{|(e5h;7kIi6sJ5CnyHgD2R3^or21Mb_t@v z@p}c81I>_FDuDJ$`jSBJ6I2QG*MepO-6Y{=1Klca97p^oLA0R4s55vvALvm*i-GPJ zv;^pBK`Vgbf~tWINeXI!UJ^Gpdn=$8L}fg(M9>v*D-(1j(3yg^0-Z0Y1?X*p+JV*! z>IB*%s0Zj8K`EejNz1GF`O+M{ z66S1tLB}|Q8}ak$?Yq2 z;rF3MOX$6CmmfNi0qX%Au=v|l*{1yQi}?2I)QA%EBnO82&)VMP{aLzwmgi?`HM$$+ z&bo+GRJW;qxHjfId*G#1-FBUlfT0AtDyq)9aY#WsKRbakkm0CIUA$FdxeadAViV?pY`DjO zekb9c1G)g=5~2p-2d)yCo{WfiPL%G}WEpUqV%p#vmjR)v(%C7M%6vMeW7+ZeD4x@0 zZ9+o2F{j8RM!9r?PRGP5P1n9AVeZDW!h93Yv&?_udA4*_l5+6ObkR(y#qP&#FLoz* zu?X2K0T96*!~I{bqxI*Ij+(dW`haHw{OwVcn1?j~5#V3!evLfh%sn7oV@iYy}^SxcFBQEE3E6GHE5p<7Lz#dT!Zb^P=nP$93maWAwp^6 zS2;j4JAaU-xw*BkFWKJIhZ)heE)|Lx|1j}n3fKvr|qVdi&JS_6WN>s1# znFgCDHZ=3h^3=aqAoG$J|2|%jrz6Iq#>QaI@9+a7N6Auf4HzXOr8;&)q+>TkU@$J5 zX06P$aRI-csX({!DF(oZ$DHkEd-3+-bk$6E!JAG>qWVZz&FMd_oxxnEdmSJ5av`gjB@R)P_-?z)4wY!XP4ey9Vz`% zZOQu5FI5-U0Tq>g2{u_?6WGCf%Cp~|>c7<4G=~O6v)6A=whs?O1%aW_;%wUc z&vL&pO8U7>WKO1PUaegk8q-%1V{AQ)!{jxRmabX>E4pNk3)tO8&ujhito8V90iQZ{(&3BjjEN#!ina3aif`!#A84Lk^_RL$nhG=cvo1 zE4h! zEuNQ&(ze(f0Dim4;CZQ-D_xdt4O$SZy=cNzfgjKD>?a1euJvwr$*FFzQN#y3s-l+dd6p~W2^gRcpW3eeSI+L>4%7EZ8RN`cXl^7 zb=G$Gra+pG+dqresR!d@y~qNcS!NxzqiN@w)*aowyEgVUUEPY@oigoN41awS5jQa` z<9rGZMR1TfB?gXAKMB_vh@s^=wI{+jyqzLo#M_qjb~m-~L?x&sIx0e8(%C2?osA+I zeTsmzV?ID@XZ=jFtqHROSX34~6Q=yaJAF8it-JY}?w-_4ENqi^XvG6(8t!Pyk~_Lv zS_{Us4I_(a`C2Zp{};k!$HrOkZ6=~Mmju6fII|CK$}F#KpcQDjR*BmyrP7@{43!Z(&7Pdz z8Azz`WP9%@1vOxYZBX zmXzIo1`&G{MoWwIa|0iv@vGaC4ep-9C0er^x4<6wsvBuU9e0_D+R;^QmjUVQz-k~> zhOYx!g*yscKlIAc0ZW)404>4nIre)%Uh1|mTsxtA3(-nOX9s$av)Vf(&vdOA*s@aF9s>fxpGUS-}eR#gX{0Ps4`5B&Z^DLgH zN{{$-Sub^l=|&39H0;x!m96*=Bb2>pTSIlqTk+8tfR$bhH+#rl|BcW|?93*WJFeyM z`p=>`y9&(Pfc`&eX99QC)cx^$pQl-hlv$ogNOOivsbqMKky$jS(xk~;5sE})o~O(r zQ>KW_AtE9o^VBQ6#uxq9@3;2eXYI~8_dfs6ySe8%o%>sBukYH!+4tOY@5SfB($C|F z+y*a?)|t<1G=Ab-^J9#N88L$Dp7Il?doSS7fhUcV&uipOp^*cts{>8l=>hI)Ph?!aM-QWxyK83=!C+dqevJ96)!u*K7qVFkS zPv29(p3WJd$HK5iVST1a)p$bzClmRkA)2m`buEEIF#BsuYKlp9E->JHuNN3P$e!Nb zhWha8s=GXV*~9HH7PqfK>$@RndKCM-KcD($m#-IIS5>-&Px#nP9-pV0J-&=kjc+qC zubTXcX)PxH6!DKey~uz)O}uCF-)5p}nXLK29+|-8drfK$Egrdg1}+?(|MUamM?UQ% zYiNE71D}Dx8*2CMdIB(S{$b`Jk(7)v~8uHnK9z$#4BO9HDRvaRq zP1sG|X6{muP1l;V8!X}bH;k*q!2?C-;s7~*i#!_3edKbbP9C)iePnyHJR0-e)bpkB zYVqUFQ@J-;p3#N$8(|OkIZlyhv>AD&VT&d3^R9+3I<_ zlK0%}FXz@SRLRjgoYm@b{$88Q-3I);T|<7+LLzBhfVc{DV$@rd2d`NJ#OrrheCUUHJ@#)x=qQY`8%*JSKosF@lvoZE` zQb6RGlq%fU8K`>l_pN#!KJ6PB1)Zi98$R8mIQiS+5l6~LV%KSS4lZpySs|P4-X7al zj*qP`eYyjdbFFZpP(EAJaBh!^Lk^vOpbi_~S+BQa$!)vp=9!lH737QWAb;!aVxCLY5P`8A60AGi!wJg z#{JvZilgKXZh5)geY@uv?#`Rxnd!sY70+15-4^Cvw#y_WlbT#Z=zi#thachZr^HHB z26q?v+e-d+=R_!9$!pmH z&YtppPqKF<_i*)I`?0jB>{S-p%eFn=Y3<0b{b|M7rX%ke+mtVHHd=uPe8Pg*p<)dVF!)n4iK~ zztC5XY*@g17uok#50z(H(SvMU*;xJ}$$0cxq3MLAAW>Xx~>pNil37mPHRctd zyJb(mClixk<3bO4R*&5&|JHS%{mD zal7;yJ#bjB!NWTAl3%mxHE!&XVPkrYA3kab4&&2Ey@vE_Jz(cf9d>RrYCyljk8RND z#10Mjk1PvMs60H^1Kjk6ehi0Q@8P`RxK002qY8yC{pBY?+6);`vwoifF5*@ex(t%r zOWU+-hp6;On*pQcizmjTI`UK&edFMWmNo+h_8WIX^Pcz@PKlMp8_%ndp=(Qk4f6kFGy~C0g{p82J8g!k23|lqu+V0p9)Wirp%wm>6vw_dj?|V% zWf8Lwh4R<2n;c?bRu;F%R-S$S=H0hT=ddm{7f~Y{DRo#s_FlzCN*#&5ve;7g;H!OK z&)$2?*wOt5kA_6bn(U}u*`-=jJUb|<3GDEmShY4bk}>VQWJm9c*)?X{e>hDS^)+pc z!uww|UZ}m^_8+XQD~k_c>*`u7u;Zm2%39zm?j(Eg)dC-2X0fe|x|H-p_pH;nRz~lp zCXm8cVuiI#QY*~|1EoPA#VavA z0#aOogReRLy|T^dWO$>$RTj}kC@WU3F9QN|3Cf|)4>WoVr%PY-$fR&Y>WW4RC^U!l zg9f&~x1gU=6LN?s6Dv;9FkrDcM6ToBqU<(@m<9b@S6SGk{ly)W!S)teZiog;cAuF> zA%_>Zm>U&@tB6?*ZT|BBue7}dbt!4nOj6iT6G-7Jv8sx#%(!SQx|mcWw4^v2RTeS! z&`aKX`~jI_&^DG7Yt72yW!TCCr3p#>yoyr<=hYhhcj+Y`hdM#-mgnaO#`GFKr2puV zV@3`dTQz>j82Ru+)tIs41`HWl)nrsYQh}iFrxfuppU`k5EPoj;j)($-&^2{nvg^I%C#~%#F`||p<_4msw@?p1{qrY zMu+z49O82?mBne;$^&J?D2FTMNand|)VO}bhV<_>cF4fd?RpK8bpSu7ourArbhFT9 zY(r-3R5^&T_Y6LI&tfx%m^EWRxo4fmoQbFj8H2CH>OZlS$rxT;#2Is>CS$n1qZ*vK z9vO4=lj)4%+*(<@23vWcY!b%Tr;cPex-r^_vpTi$8WV{{`D?KuN+_2<`?Foob z^W)D=s`EVoHBl4tg9s9*WKvoN(yJY5%K}JWmFM8=#Nl3PNl}-QPKjf7 zq?j?N38e6qSX~rb8B$zBib*v>ON!NZWl`g*C=593``y!|ZK$~bTX~?g4y1D&X={?c zC(l7MeuP(AQq-lSx5hC$Qn;cfkitu1b!Tj4NZX1oCe;WnX*+7LzJ#0Yzgu>6np95k z;%5rB@<3@9NI!9;?Lf-sesM<%JYv_+9prg^67w4|s@N#BoSHa(o0h%~YKAht53JB#O-R3o%~ z1M`A^byF-H{=h~*YOk|>V^?Y{=`K;?Y`5VK~ikL)GIpV7pab7ykp1io^uOvZK-U7RsTYWjvuc4afxnHqcU z-aYgUd}|rX?j*(QJVbZlm@HD%RgRRJ&^O>K*UFIYA-b4UBeZ>^3pMbF-k;q!x>7S6 zTgaqz38bynRE}W0Z|6wQ9P5>K6m${Ga-`Hmq`6jxw3~R2Ni{-Cx+gWINxM^XA-3{B z*)xz%bfkNd6l-T37(UJ`Eh*|MM@mgZnrmf9_Y%)BsYYl?@nWU2So(~%4>jjwD-V>t z1L@t4bZ?Lr?D=b7(Nz{3%Ql&iFiWG%cUI8E=dXXskvL|p%f`GPHKk+TpBlU-@-goh z;&{i^zF#Wl18CJnwpa;j@iF6D{!pNaV?I!h#4&4KHs*tXkx8auiVi*k6Cgjha@ayBo~ z>ke``JPi3MP7;rZ>4MXJ4sEYP%$l)LNKPm|W3ZtnWGoz^espYQGIoSm#u;;@CSylZ zqjBYBOfFuCADgiwnK3zB816`qEPxcZx5HPTGt{Guck_-9>Qd7DSK`z}q=^;o0Zod1 z6(BE5)*#gg?TmB`H3&Cu>8G;HO8a!k}W?v@D3_l?ry>q3{YW0_HN$M7t!{d;j;sfFlC#!}Ne2uo(A!g0k zdPq(vK4Y+hj6ti)*Q>oPWoeGK20jF|q+%ot)W*T<*{8H2CHD!-56U9C7{j?`pq z2sNcMHk6vB*vbQCNEl5s9O)2}e(y+sJn>! zWsCE}ssr}fJw4M7r$!dmfpl0Pt?4eJ4I}9vLJD6UCTEeNE+ySebV>0uB&Hy00x5hY zR-4CG=A1G@bTO$$Xh}y>Mx|H-cdhr`eN=-zXSpDG*_}7w-l_N2!MrixS zIBH6h;w`Vr;-lEg17%zwZB)k}!MUKhBR%Cjue7A7s~jmc5ouzTPkOR=j!88_OL_`5 zrAbev<`Hb=fpSV9J>HR?!ZvO1NY9w!m6jBBl_RAlB2BFFNlz2cF{wsqNl&MyH0c@C z*sB?*2hzJ7>FFfx>_{h0%_2oz_my6T*i1czQh{x3zxC8n6W=e7HwY7dq&^5NjxH^uc!MO?Px;Gnz5(U$Zh?Mjh8&sgp9#gV)blnWoGP) z<(W8Rj@0yx8PqhSk4pJ6(5)UyXY3McKEjqd{H6_FQ}}b-yVcEDi5YvW%h(L|u{q)q zF|B>EuhC`A)!7hOR7<1+>uYC^`~E3w)>wlW#xj1i3{M`|+0RZX#U#x9fkDvLH_ zGs9@wWGx0;m`T#79qFDkvPe;vl5QKvZ0-o6CL&F&@TvHu*wy>xayafA8lfFcS5TvI z<;@#cQu7M7@<6#FkUr-~uOR6{A%$l6C0=RU6m^xGH>e4u@RC^N&l^{XE+*9oE$P+N zl%6-Pq2?KE<$-c_Gzz||?kFfRZ=6`)d&a!+p?E}0mtX2@v>8Lp<>n1)LdM`Lv3gdk za$DOsu9ahP#vG{`1-w37W)xgcKc8ajYPl}TSR*<7zpnJ=yNq4OK87E^#>jkTrmxXv z3^A9RH>e32gRjIYf8Mx3baBQUsma)l)Rdk#ZlcC!?8Y#fPFh=@lyW0U|8%5(UY13Q zy2{NP)I_9-RsOtjvv`h4H9|X@ZlOlw%9}TCrREK6<$-cbAl;&YKY~$ETLjQ-dxclp zHbq_K<_&5BDZC_B`SZqYqKip2LQBe5G5Twa_+)?PdgFHbc^X@Jpv;O!!IpBkTBD%E zys^Y(Y!>^*hT;)p{fH}ljW%P5Su^$&f(gZ+J+a=PCS(l0602u}1$OMb!PR4Q_H?9X z6x=B`m1X9QyQo=#tvpcfj52mmbu(6Cy|JwhMDdJ1wyk)?fr(f78g0f9vt}%Ry+KXL z76kZam{CVR6(Z!@1p(W*Y-7@pWgY<*5kB@&&Gz#!B&Hr`Y=(Mi5mokTa<1q1v zvA*7QzDAod#9VIPpeAGtz7nfv79A)h3 z>SnCOyfM&a>|tgMAK*ev8(;5hv>8Lp<>n1)LdM`LvC5w}9u-}jF-K}LHkX>x8GDQx zo3XiJG`;9Z=aLjRvcgw~8?s1ISGjqEnus*9%AYqLXM`BoMR}lTgmyIXwY>zBxPSv++u!7sw)dhgB^?AOq4;$|g&d(Kkiu7DH9A;e z$BuXQw`iT95n9suVpCZxP5LY~m|=YU^8@Kaj&yzj=K`GU;H$ySUTI0^%QNLjsfkDv zt9;Vu#B-aqzg!TyCbb}OBN~WDo099M4DLTlfEFH zV^WRKlDymI>{HB?e3 zzm5@Q?8oY6ti)^2ZjIITB4+GM@ran3Vj^O(8ABxH`WQ7KWAK%0W%}4#qKh-;NKM8T zQ-d}rn$3z?L~QN}82{O^72E|;;z%-C;I5n^g}hp*9Q3^AAMW7LFc)>X75%Gi3<%~**(wrLY}y@VO7BOcK&j=nQ1 zV~Dw2AEPE@48C%$OdoqsbaBQUsma(zu@Zd@-%X9OoEh8I^|3SW@ip3vA?9*@jGB-!@FiCHee4s_#Tj#? zCSxn8sViILqwR--pGD@vtCF5L{wbb|zN2FWGlu6TIMNj)-B(EA>#TdTNKu!P?q7th zFaC8CY^VvO@Re8{7+aaX@tNpiQjO5AJU^#KUF5w&{DPWWv6TnP=TYAnP>sG(?tYT{ zT*f{xRLNTXXxBFe-REnxeFHIT#y&=)gyJ)XgVcnKfiJOI5nGvzeJQ#)V~*7HjjyP| z_(VRk*HK?n^Bfh>$*YbiV`mFrwN{=b<{x|&qIyQghKfhT)N6KD#t^e+46h%O;xh&t zYC^`~E3v{FG%0pf@QoafGv-K5#=fPdH67F}y#Gnv3F*=F9W^guOP&1gUKmZMInr<0 zH%=B(_`3Xlue5ywbt&ok+Nqs4Pg+y{O-&$$uf%Gj*vj;c??o4rYJ_$)t)xaTgjE)5 zAN9etY0`gF^CY(NKv@~}jhm{`H_DAB{3@GzM&Fn!9ud=m2Yih-V~ANZ_C-7{%sm6t zgp9#gVukmylVbbE4{|upm?JfPV-+=;^t}1!M`~`zRvsv;Lf_bBLw^LMmkTL;y)wru zEh*|!()@cJsR^X;m00E9hxU``Vp5IJ_KlyZX)Rma;h#MU{zDB$n~(qJsBg@vM&Bs+ z%>($ZVw9f?$k^@T5ix!8ps&$p3^A7*P1J;p!B=9HKbn3KU7RsTYWl{n)RZ1gzfm(E zTX~@T8fEO8YGkb3X!^-z>{n)Nu6RUD>pkpiv>8LpFiB*1^{=*2@ku7qA zq7hos)pE4lm2H?;7Vm}0uQ)&V!;!dpc+Z31P z<2Ka@EolWcrB_y!)Z8Yu`uHpO4UYaQXl*&c|F0{n$6UrLipUtg7XdMC^_Z{GW(+ZF z#$FXoQv7-YHq?ZS!B=9Hzp|>4%9tZH8zQL;8yiuQ;sZy(t zf346rIyus{NV>6*in!3}Nw2hh19d5B{>qA)Knh=pRsOuOHY2n%q(*4_MgwZ3^Gc~K zESOZ3j)z65TZ{I@@rKmgM+N>Z@9jl>qenIRM!7ru4|5r7P(F zb&DYFB!}Uv&b%yA)TN~R%U)6}DK&ul^rde|%2S2yNeJOigK0d~&yv-@57J zZyZR!b)=1pAl=K6u8UhkSnMc3UFArriAWQxd{WG~F{wsqNjIQIHd6A|ej8GAu_%1} z8$_dEgz%L+dzPCaH`br)BebOWHtXcuY_iu0TT}C{A#D*zpK+uuNP4Yn)Aezi1dAm_ zUFF)8nm~#+O|0_Ubej~ZMrcWKH*UG{-jbS+rB)yRwt=)wQzhEAh&H{`wdo!&c%>yp zUFF)8nm~#+O|0_Ubh{L(MrcV}QB!)Hwx$N}*!uWe1=2elX)BUG;@WiBi(Y9-QCGP( zr6!OfuEZ+8P1~eMH9||;mYUMzv>i3e4Qbmz+GKM%ETwG`ZTg&R)5~7UB1K*0+LW3= zintQ1{5EZ$BGm{jX$NXbw`oUe9+Fyp{2c=6bVu5Oq>Ege&VSh}ZJVO5a&1aYAVpk> zReqappCZ)=E$I%_ly1`tf?hr`dailwt^h4LCFTRpRin_|RDK&u#zD6ZQnr5 znz8)%8L0^wgRjIY|9!??NNUf^j?}C`cBKX$^E0*^H8Vuvf~DMnT?A=u z=_T+r;x(_dq^PSLDK!ykVwF$Yg%Mg(jnI;IrKa@E){UA=guut&H5vs2t1$}7t-~L! z68BQN7Llf`So`o^*OU zGOs6xs0r-wo>;vdTbZjgdoyOst})x*vJW+-d&|DmTqCu5qJ08;{bm~RK1Hy1bgf+b zO|P`AjJnFTGBtq|aV1vyt^6-WXh}6fOS&I5rCS+Sk1C6w8q)m&Y5QhMx*tiqxK=*# ztt?X1Rjy5`38aWCvC41L0~nzt)d(#q&Z6aBgB(Q7{f6|wKzeX9B|VU&2f8+WVR05I z>MGZ!)C5wrX=0V%rUx@ZOR5oC(jL?d5syU~x%b`Cymxx!9zqS@!&R(8k3f25Gd(7T# zW3NB=B&lE96l>?KZT=s>H?g!CLn!ntB4Z~?#t_p>xG9~*j(5bY8N+!iDSl>?<%0MT zO~@F0C03Z@l49@rI9#5IudX;!Gpip#jlNS;uW;R}OP@|>>_}?1!q!!EMCco+2yNduI%rUH_V1$| zL(Tc}ypR9rK)T5mu#GqCvWD%rG zg%rMeev(Csx|9?nAt|=^QWHqwE3ulMn8=GS6Nj+nKN?ZUYI#*Zd>x!ULLXc1NY5yOw6Pt(p)P;Iw3`>5!${n zu^Ob4sIjk!CI-?5TWQ1-iy&?0NPB;gMT)x0wJ9|bX|9zaJu5}35n9r-so6lnMQlq} z{&EGzQkZFZE|^S>UCEpsNIN^yvq2jD!qhpm>LR|-tF-9MQYakmte}al&ZQM*yyLtP zmFf%H08`;?-P7I&34 zg|DtFv)UAOX`8NulTdu`g$*^KP2nrCS`{p?W6yf$r${wIOL{>yNT*Trta$hFUl2%N zb)***LAtLa-SgjGX~!w*Do099M4DLTlU~ROZJTO@mUKEbe$6&5^Hbp$QS*W!ola6e zhtCqea(4}t`&7d3E@RV~v7^N!Vmj`Jtc)RM%^2RNPm1ju)P#(|S7Nm^F_AsnH!e