/* =========================================================================== Copyright (C) 2024 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // DESCRIPTION: // Special Effects code #include "cg_local.h" #include "cg_commands.h" #include "surfaceflags.h" #include "cg_specialfx.h" extern refEntity_t *current_entity; extern dtiki_t *current_tiki; static vec3_t g_vFootstepMins = {-4, -4, 0}; static vec3_t g_vFootstepMaxs = {4, 4, 2}; static vec3_t g_vLadderstepMins; static vec3_t g_vLadderstepMaxs; ClientSpecialEffectsManager sfxManager; Event EV_SFX_EffectDelay ( "effectdelay", EV_DEFAULT, "iivvvvv", "iEffect iCurrEmitter vPos vAngles vAxisA vAxisB vAxisC", "Resumes the execution of iEffect effect from its iCurrEmitter emitter." ); CLASS_DECLARATION(Listener, ClientSpecialEffectsManager, NULL) { {&EV_SFX_EffectDelay, &ClientSpecialEffectsManager::ContinueEffectExecution}, {NULL, NULL } }; specialeffectcommand_t::specialeffectcommand_t() { emitter = NULL; fCommandTime = 0.0f; endfcn = NULL; pEvent = NULL; } specialeffect_t::specialeffect_t() { m_iCommandCount = 0; } specialeffectcommand_t *specialeffect_t::AddNewCommand() { if (m_iCommandCount == MAX_SPECIAL_EFFECT_COMMANDS - 1) { return NULL; } m_commands[m_iCommandCount] = new specialeffectcommand_t; return m_commands[m_iCommandCount++]; } ClientSpecialEffectsManager::ClientSpecialEffectsManager() { m_bEffectsLoaded = 0; m_iNumPendingEvents = 0; } void ClientSpecialEffectsManager::LoadEffects() { int i, j, k; const char *szEffectModel; float axis[3][3]; specialeffect_t *pEffect; if (m_bEffectsLoaded) { return; } cgi.DPrintf("Loading Special Effects...\n"); AxisClear(axis); for (i = 0; i < SFX_COUNT; i++) { switch (i) { case SFX_BHIT_PAPER_LITE: szEffectModel = "models/fx/bh_paper_lite.tik"; break; case SFX_BHIT_PAPER_HARD: szEffectModel = "models/fx/bh_paper_hard.tik"; break; case SFX_BHIT_WOOD_LITE: szEffectModel = "models/fx/bh_wood_lite.tik"; break; case SFX_BHIT_WOOD_HARD: szEffectModel = "models/fx/bh_wood_hard.tik"; break; case SFX_BHIT_METAL_LITE: szEffectModel = "models/fx/bh_metal_lite.tik"; break; case SFX_BHIT_METAL_HARD: szEffectModel = "models/fx/bh_metal_hard.tik"; break; case SFX_BHIT_STONE_LITE: szEffectModel = "models/fx/bh_stone_lite.tik"; break; case SFX_BHIT_STONE_HARD: szEffectModel = "models/fx/bh_stone_hard.tik"; break; case SFX_BHIT_DIRT_LITE: szEffectModel = "models/fx/bh_dirt_lite.tik"; break; case SFX_BHIT_DIRT_HARD: szEffectModel = "models/fx/bh_dirt_hard.tik"; break; case SFX_BHIT_GRILL_LITE: szEffectModel = "models/fx/bh_metal_lite.tik"; break; case SFX_BHIT_GRILL_HARD: szEffectModel = "models/fx/bh_metal_hard.tik"; break; case SFX_BHIT_GRASS_LITE: szEffectModel = "models/fx/bh_grass_lite.tik"; break; case SFX_BHIT_GRASS_HARD: szEffectModel = "models/fx/bh_grass_hard.tik"; break; case SFX_BHIT_MUD_LITE: szEffectModel = "models/fx/bh_mud_lite.tik"; break; case SFX_BHIT_MUD_HARD: szEffectModel = "models/fx/bh_mud_hard.tik"; break; case SFX_BHIT_PUDDLE_LITE: szEffectModel = "models/fx/bh_water_lite.tik"; break; case SFX_BHIT_PUDDLE_HARD: szEffectModel = "models/fx/bh_water_hard.tik"; break; case SFX_BHIT_GLASS_LITE: szEffectModel = "models/fx/bh_glass_lite.tik"; break; case SFX_BHIT_GLASS_HARD: szEffectModel = "models/fx/bh_glass_hard.tik"; break; case SFX_BHIT_GRAVEL_LITE: szEffectModel = "models/fx/bh_stone_lite.tik"; break; case SFX_BHIT_GRAVEL_HARD: szEffectModel = "models/fx/bh_stone_hard.tik"; break; case SFX_BHIT_SAND_LITE: szEffectModel = "models/fx/bh_sand_lite.tik"; break; case SFX_BHIT_SAND_HARD: szEffectModel = "models/fx/bh_sand_hard.tik"; break; case SFX_BHIT_FOLIAGE_LITE: szEffectModel = "models/fx/bh_foliage_lite.tik"; break; case SFX_BHIT_FOLIAGE_HARD: szEffectModel = "models/fx/bh_foliage_hard.tik"; break; case SFX_BHIT_SNOW_LITE: szEffectModel = "models/fx/bh_snow_lite.tik"; break; case SFX_BHIT_SNOW_HARD: szEffectModel = "models/fx/bh_snow_hard.tik"; break; case SFX_BHIT_CARPET_LITE: szEffectModel = "models/fx/bh_carpet_lite.tik"; break; case SFX_BHIT_CARPET_HARD: szEffectModel = "models/fx/bh_carpet_hard.tik"; break; case SFX_BHIT_HUMAN_UNIFORM_LITE: szEffectModel = "models/fx/bh_human_uniform_lite.tik"; break; case SFX_BHIT_HUMAN_UNIFORM_HARD: szEffectModel = "models/fx/bh_human_uniform_hard.tik"; break; case SFX_WATER_TRAIL_BUBBLE: szEffectModel = "models/fx/water_trail_bubble.tik"; break; case SFX_VFX_PAPER_LITE: case SFX_VFX_PAPER_HARD: case SFX_VFX_WOOD_LITE: case SFX_VFX_WOOD_HARD: case SFX_VFX_METAL_LITE: case SFX_VFX_METAL_HARD: case SFX_VFX_STONE_LITE: case SFX_VFX_STONE_HARD: case SFX_VFX_DIRT_LITE: case SFX_VFX_DIRT_HARD: case SFX_VFX_GRILL_LITE: case SFX_VFX_GRILL_HARD: case SFX_VFX_GRASS_LITE: case SFX_VFX_GRASS_HARD: case SFX_VFX_MUD_LITE: case SFX_VFX_MUD_HARD: case SFX_VFX_PUDDLE_LITE: case SFX_VFX_PUDDLE_HARD: case SFX_VFX_GLASS_LITE: case SFX_VFX_GLASS_HARD: case SFX_VFX_GRAVEL_LITE: case SFX_VFX_GRAVEL_HARD: case SFX_VFX_SAND_LITE: case SFX_VFX_SAND_HARD: case SFX_VFX_FOLIAGE_LITE: case SFX_VFX_FOLIAGE_HARD: case SFX_VFX_SNOW_LITE: case SFX_VFX_SNOW_HARD: case SFX_VFX_CARPET_LITE: case SFX_VFX_CARPET_HARD: szEffectModel = "models/fx/bh_stone_hard.tik"; break; case SFX_EXP_GREN_BASE: szEffectModel = "models/fx/grenexp_base.tik"; break; case SFX_EXP_BAZOOKA_BASE: szEffectModel = "models/fx/bazookaexp_base.tik"; break; case SFX_EXP_HEAVYSHELL_BASE: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/heavyshellexp_base.tik"; break; case SFX_EXP_TANK_BASE: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/tankexp_base.tik"; break; case SFX_EXP_GREN_PAPER: szEffectModel = "models/fx/grenexp_paper.tik"; break; case SFX_EXP_GREN_WOOD: szEffectModel = "models/fx/grenexp_wood.tik"; break; case SFX_EXP_GREN_METAL: szEffectModel = "models/fx/grenexp_metal.tik"; break; case SFX_EXP_GREN_STONE: szEffectModel = "models/fx/grenexp_stone.tik"; break; case SFX_EXP_GREN_DIRT: szEffectModel = "models/fx/grenexp_dirt.tik"; break; case SFX_EXP_GREN_GRILL: szEffectModel = "models/fx/grenexp_metal.tik"; break; case SFX_EXP_GREN_GRASS: szEffectModel = "models/fx/grenexp_grass.tik"; break; case SFX_EXP_GREN_MUD: szEffectModel = "models/fx/grenexp_mud.tik"; break; case SFX_EXP_GREN_PUDDLE: szEffectModel = "models/fx/grenexp_water.tik"; break; case SFX_EXP_GREN_GLASS: case SFX_TEST_EFFECT: continue; case SFX_EXP_GREN_GRAVEL: szEffectModel = "models/fx/grenexp_gravel.tik"; break; case SFX_EXP_GREN_SAND: szEffectModel = "models/fx/grenexp_sand.tik"; break; case SFX_EXP_GREN_FOLIAGE: szEffectModel = "models/fx/grenexp_foliage.tik"; break; case SFX_EXP_GREN_SNOW: szEffectModel = "models/fx/grenexp_snow.tik"; break; case SFX_EXP_GREN_CARPET: szEffectModel = "models/fx/grenexp_carpet.tik"; break; case SFX_EXP_HEAVYSHELL_DIRT: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/heavyshellexp_dirt.tik"; break; case SFX_EXP_HEAVYSHELL_STONE: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/heavyshellexp_stone.tik"; break; case SFX_EXP_HEAVYSHELL_SNOW: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/heavyshellexp_snow.tik"; break; case SFX_EXP_TANK_DIRT: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/tankexp_dirt.tik"; break; case SFX_EXP_TANK_STONE: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/tankexp_stone.tik"; break; case SFX_EXP_TANK_SNOW: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/tankexp_snow.tik"; break; case SFX_EXP_BAZOOKA_DIRT: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/bazookaexp_dirt.tik"; break; case SFX_EXP_BAZOOKA_STONE: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/bazookaexp_stone.tik"; break; case SFX_EXP_BAZOOKA_SNOW: if (cg_protocol < protocol_e::PROTOCOL_MOHTA_MIN) { continue; } szEffectModel = "models/fx/bazookaexp_snow.tik"; break; case SFX_WATER_RIPPLE_STILL: szEffectModel = "models/fx/water_ripple_still.tik"; break; case SFX_WATER_RIPPLE_MOVING: szEffectModel = "models/fx/water_ripple_moving.tik"; break; case SFX_OIL_LEAK_BIG: szEffectModel = "models/fx/barrel_oil_leak_big.tik"; break; case SFX_OIL_LEAK_MEDIUM: szEffectModel = "models/fx/barrel_oil_leak_medium.tik"; break; case SFX_OIL_LEAK_SMALL: szEffectModel = "models/fx/barrel_oil_leak_small.tik"; break; case SFX_OIL_LEAK_SPLAT: szEffectModel = "models/fx/barrel_oil_leak_splat.tik"; break; case SFX_WATER_LEAK_BIG: szEffectModel = "models/fx/barrel_water_leak_big.tik"; break; case SFX_WATER_LEAK_MEDIUM: szEffectModel = "models/fx/barrel_water_leak_medium.tik"; break; case SFX_WATER_LEAK_SMALL: szEffectModel = "models/fx/barrel_water_leak_small.tik"; break; case SFX_WATER_LEAK_SPLAT: szEffectModel = "models/fx/barrel_water_leak_splat.tik"; break; case SFX_FOOT_LIGHT_DUST: szEffectModel = "models/fx/fs_light_dust.tik"; break; case SFX_FOOT_HEAVY_DUST: szEffectModel = "models/fx/fs_heavy_dust.tik"; break; case SFX_FOOT_DIRT: szEffectModel = "models/fx/fs_dirt.tik"; break; case SFX_FOOT_GRASS: szEffectModel = "models/fx/fs_grass.tik"; break; case SFX_FOOT_MUD: szEffectModel = "models/fx/fs_mud.tik"; break; case SFX_FOOT_PUDDLE: szEffectModel = "models/fx/fs_puddle.tik"; break; case SFX_FOOT_SAND: szEffectModel = "models/fx/fs_sand.tik"; break; case SFX_FOOT_SNOW: szEffectModel = "models/fx/fs_snow.tik"; break; case SFX_FENCE_WOOD: szEffectModel = "models/fx/fx_fence_wood.tik"; break; default: szEffectModel = "models/fx/bh_stone_hard.tik"; break; } pEffect = &m_effects[i]; commandManager.SetCurrentSFX(pEffect); cgi.R_SpawnEffectModel(szEffectModel, vec_zero, axis); commandManager.ClearCurrentSFX(); for (j = 0; j < pEffect->m_iCommandCount - 1; j++) { for (k = 0; k < j; k++) { if (pEffect->m_commands[k]->fCommandTime > pEffect->m_commands[k + 1]->fCommandTime) { specialeffectcommand_t *pCur = pEffect->m_commands[k]; pEffect->m_commands[k] = pEffect->m_commands[k + 1]; pEffect->m_commands[k + 1] = pCur; } } } } m_bEffectsLoaded = qtrue; } void CG_InitializeSpecialEffectsManager() { sfxManager.LoadEffects(); } void CG_AddPendingEffects() { if (sfxManager.EffectsPending()) { sfxManager.ProcessPendingEvents(); } } void ClientSpecialEffectsManager::ContinueEffectExecution(Event *ev) { Vector norm; float axis[3][3]; norm = ev->GetVector(5); VectorCopy(norm, axis[0]); norm = ev->GetVector(6); VectorCopy(norm, axis[1]); norm = ev->GetVector(7); VectorCopy(norm, axis[2]); ExecuteEffect(ev->GetInteger(1), ev->GetInteger(2), ev->GetVector(3), ev->GetVector(4), axis); } void ClientSpecialEffectsManager::ExecuteEffect( int iEffect, int iStartCommand, Vector vPos, Vector vAngles, float axis[3][3] ) { int i; int iCommandCount; float fStartCommandTime; specialeffect_t *pEffect; specialeffectcommand_t *pCommand; refEntity_t *old_entity; dtiki_t *old_tiki; refEntity_t tmpEntity; memset(&tmpEntity, 0, sizeof(tmpEntity)); VectorCopy(((const float *)vPos), tmpEntity.origin); tmpEntity.scale = 1.0; tmpEntity.renderfx = 0; tmpEntity.shaderRGBA[3] = -1; pEffect = &this->m_effects[iEffect]; iCommandCount = pEffect->m_iCommandCount; if (pEffect->m_iCommandCount) { old_entity = current_entity; old_tiki = current_tiki; current_entity = NULL; current_tiki = NULL; pCommand = pEffect->m_commands[iStartCommand]; fStartCommandTime = pCommand->fCommandTime; for (i = iStartCommand; i < iCommandCount; i++) { pCommand = pEffect->m_commands[i]; if (pCommand->fCommandTime > fStartCommandTime) { Event ev1(EV_SFX_EffectDelay); ev1.AddInteger(iEffect); ev1.AddInteger(i); ev1.AddVector(vPos); ev1.AddVector(vAngles); ev1.AddVector(axis[0]); ev1.AddVector(axis[1]); ev1.AddVector(axis[2]); PostEvent(ev1, pCommand->fCommandTime - fStartCommandTime); m_iNumPendingEvents++; break; } if (pCommand->pEvent) { current_entity = &tmpEntity; current_tiki = tmpEntity.tiki; // give a reference to it so the event doesn't get deleted commandManager.ProcessEvent(*pCommand->pEvent); } else if (pCommand->emitter && pCommand->endfcn) { current_entity = NULL; current_tiki = NULL; pCommand->emitter->cgd.origin = vPos; if (!(pCommand->emitter->cgd.flags & T_ANGLES)) { pCommand->emitter->cgd.angles = vAngles; } AxisCopy(axis, pCommand->emitter->axis); AxisCopy(axis, pCommand->emitter->tag_axis); pCommand->emitter->cgd.createTime = cg.time; commandManager.SetSpawnthing(pCommand->emitter); (commandManager.*pCommand->endfcn)(); } } current_entity = old_entity; current_tiki = old_tiki; } } void ClientSpecialEffectsManager::MakeEffect_Normal(int iEffect, Vector vPos, Vector vNormal) { Vector vAngles; float axis[3][3]; vAngles = vNormal.toAngles(); AnglesToAxis(vAngles, axis); ClientSpecialEffectsManager::ExecuteEffect(iEffect, 0, vPos, vAngles, axis); } void ClientSpecialEffectsManager::MakeEffect_Angles(int iEffect, Vector vPos, Vector vAngles) { float axis[3][3]; AnglesToAxis((const float *)vAngles, axis); ClientSpecialEffectsManager::ExecuteEffect(iEffect, 0, vPos, vAngles, axis); } void ClientSpecialEffectsManager::MakeEffect_Axis(int iEffect, Vector vPos, float axis[3][3]) { Vector vAngles; MatrixToEulerAngles(axis, (float *)vAngles); ClientSpecialEffectsManager::ExecuteEffect(iEffect, 0, vPos, vAngles, axis); } /* ============================================================== FOOTSTEP CODE ============================================================== */ #define GROUND_DISTANCE 8 #define WATER_NO_SPLASH_HEIGHT 16 static void CG_FootstepMain(trace_t *trace, int iRunning, int iEquipment) { int contents; int surftype; int iEffectNum; float fVolume; vec3_t vPos; vec3_t midlegs; str sSoundName; iEffectNum = -1; VectorCopy(trace->endpos, vPos); sSoundName = "snd_step_"; contents = CG_PointContents(trace->endpos, -1); if (contents & MASK_WATER) { // take our ground position and trace upwards VectorCopy(trace->endpos, midlegs); midlegs[2] += WATER_NO_SPLASH_HEIGHT; contents = CG_PointContents(midlegs, -1); if (contents & MASK_WATER) { sSoundName += "wade"; } else { sSoundName += "puddle"; iEffectNum = SFX_FOOT_PUDDLE; } } else { surftype = trace->surfaceFlags & MASK_SURF_TYPE; switch (surftype) { case SURF_FOLIAGE: sSoundName += "foliage"; iEffectNum = SFX_FOOT_GRASS; break; case SURF_SNOW: sSoundName += "snow"; iEffectNum = SFX_FOOT_SNOW; break; case SURF_CARPET: sSoundName += "carpet"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_SAND: sSoundName += "sand"; iEffectNum = SFX_FOOT_SAND; break; case SURF_PUDDLE: sSoundName += "puddle"; iEffectNum = SFX_FOOT_PUDDLE; break; case SURF_GLASS: sSoundName += "glass"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_GRAVEL: sSoundName += "gravel"; iEffectNum = SFX_FOOT_HEAVY_DUST; break; case SURF_MUD: sSoundName += "mud"; iEffectNum = SFX_FOOT_MUD; break; case SURF_DIRT: sSoundName += "dirt"; iEffectNum = SFX_FOOT_DIRT; break; case SURF_GRILL: sSoundName += "grill"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_GRASS: sSoundName += "grass"; iEffectNum = SFX_FOOT_GRASS; break; case SURF_ROCK: sSoundName += "stone"; iEffectNum = SFX_FOOT_HEAVY_DUST; break; case SURF_PAPER: sSoundName += "paper"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_WOOD: sSoundName += "wood"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_METAL: sSoundName += "metal"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; default: sSoundName += "stone"; iEffectNum = SFX_FOOT_HEAVY_DUST; break; } } if (cg_debugFootsteps->integer) { cgi.DPrintf("Footstep: %s running = %i effect = %i\n", sSoundName.c_str(), iRunning, surftype); } if (iRunning) { if (iRunning == -1) { fVolume = 0.5; } else { fVolume = 1.0; } } else { fVolume = 0.25; } if (!iRunning && cgs.gametype == GT_SINGLE_PLAYER) { return; } commandManager.PlaySound(sSoundName, vPos, -1, fVolume, -1, -1, 1); if (iEquipment && random() < 0.3) { // also play equipment sound commandManager.PlaySound("snd_step_equipment", vPos, -1, fVolume, -1, -1, 1); } } void CG_Footstep(const char *szTagName, centity_t *ent, refEntity_t *pREnt, int iRunning, int iEquipment) { int i; int iTagNum; vec3_t vStart, vEnd; vec3_t midlegs; vec3_t vMins, vMaxs; str sSoundName; trace_t trace; orientation_t oTag; // send a trace down from the player to the ground VectorCopy(ent->lerpOrigin, vStart); vStart[2] += GROUND_DISTANCE; if (szTagName) { iTagNum = cgi.Tag_NumForName(pREnt->tiki, szTagName); if (iTagNum != -1) { oTag = cgi.TIKI_Orientation(pREnt, iTagNum); for (i = 0; i < 2; i++) { VectorMA(vStart, oTag.origin[i], pREnt->axis[i], vStart); } } } if (iRunning == -1) { AngleVectors(ent->lerpAngles, midlegs, NULL, NULL); VectorMA(vStart, -16, midlegs, vStart); VectorMA(vStart, 64, midlegs, vEnd); VectorSet(vMins, -2, -2, -8); VectorSet(vMaxs, 2, 2, 8); } else { VectorSet(vMins, -4, -4, 0); VectorSet(vMaxs, 4, 4, 2); // add 16 units above feets vStart[2] += 16.0; VectorCopy(vStart, vEnd); vEnd[2] -= 64.0; } if (ent->currentState.eType == ET_PLAYER) { CG_Trace( &trace, vStart, vMins, vMaxs, vEnd, ent->currentState.number, MASK_PLAYERSOLID, qtrue, qtrue, "Player Footsteps" ); } else { CG_Trace( &trace, vStart, vMins, vMaxs, vEnd, ent->currentState.number, MASK_MONSTERSOLID, qfalse, qfalse, "Monster Footsteps" ); } if (trace.fraction == 1.0f) { if (cg_debugFootsteps->integer) { cgi.DPrintf("Footstep: missed floor\n"); } return; } CG_FootstepMain(&trace, iRunning, iEquipment); } void CG_MeleeImpact(vec3_t vStart, vec3_t vEnd) { vec3_t vMins, vMaxs; trace_t trace; VectorSet(vMins, -4, -4, 0); VectorSet(vMaxs, 4, 4, 2); CG_Trace(&trace, vStart, vMins, vMaxs, vEnd, ENTITYNUM_NONE, MASK_PLAYERSOLID, qtrue, qtrue, "CG_MeleeImpact"); if (trace.fraction != 1.0) { CG_FootstepMain(&trace, qtrue, qfalse); } } void CG_LandingSound(centity_t *ent, refEntity_t *pREnt, float volume, int iEquipment) { int contents; int surftype; int iEffectNum; vec3_t vStart, vEnd; vec3_t midlegs; str sSoundName; trace_t trace; iEffectNum = -1; if (ent->iNextLandTime > cg.time) { ent->iNextLandTime = cg.time + 200; return; } ent->iNextLandTime = cg.time + 200; VectorCopy(ent->lerpOrigin, vStart); vStart[2] += GROUND_DISTANCE; VectorCopy(vStart, vEnd); vEnd[2] -= 64.0; if (ent->currentState.eType == ET_PLAYER) { CG_Trace( &trace, vStart, g_vFootstepMins, g_vFootstepMaxs, vEnd, ent->currentState.number, MASK_PLAYERSOLID, qtrue, qtrue, "Player Footsteps" ); } else { CG_Trace( &trace, vStart, g_vFootstepMins, g_vFootstepMaxs, vEnd, ent->currentState.number, MASK_MONSTERSOLID, qfalse, qfalse, "Monster Footsteps" ); } if (trace.fraction == 1.0) { return; } sSoundName += "snd_landing_"; contents = CG_PointContents(trace.endpos, -1); if (contents & MASK_WATER) { // take our ground position and trace upwards VectorCopy(trace.endpos, midlegs); midlegs[2] += WATER_NO_SPLASH_HEIGHT; contents = CG_PointContents(midlegs, -1); if (contents & MASK_WATER) { sSoundName += "wade"; } else { sSoundName += "puddle"; iEffectNum = SFX_FOOT_PUDDLE; } } else { surftype = trace.surfaceFlags & MASK_SURF_TYPE; switch (surftype) { case SURF_FOLIAGE: sSoundName += "foliage"; iEffectNum = SFX_FOOT_GRASS; break; case SURF_SNOW: sSoundName += "snow"; iEffectNum = SFX_FOOT_SNOW; break; case SURF_CARPET: sSoundName += "carpet"; break; case SURF_SAND: sSoundName += "sand"; iEffectNum = SFX_FOOT_SAND; break; case SURF_PUDDLE: sSoundName += "puddle"; iEffectNum = SFX_FOOT_PUDDLE; break; case SURF_GLASS: sSoundName += "glass"; break; case SURF_GRAVEL: sSoundName += "gravel"; iEffectNum = SFX_FOOT_HEAVY_DUST; break; case SURF_MUD: sSoundName += "mud"; iEffectNum = SFX_FOOT_MUD; break; case SURF_DIRT: sSoundName += "dirt"; iEffectNum = SFX_FOOT_DIRT; break; case SURF_GRILL: sSoundName += "grill"; break; case SURF_GRASS: sSoundName += "grass"; iEffectNum = SFX_FOOT_GRASS; break; case SURF_ROCK: sSoundName += "stone"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_PAPER: sSoundName += "paper"; break; case SURF_WOOD: sSoundName += "wood"; break; case SURF_METAL: sSoundName += "metal"; break; default: sSoundName += "stone"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; } } if (cg_debugFootsteps->integer) { cgi.DPrintf("Landing: %s volume: %.2f effect = %i\n", sSoundName.c_str(), volume, contents); } commandManager.PlaySound(sSoundName, trace.endpos, -1, volume, -1, -1, 1); if (iEquipment && random() < 0.5) { commandManager.PlaySound("snd_step_equipment", ent->lerpOrigin, -1, volume, -1, -1, 1); } if (iEffectNum != -1) { sfxManager.MakeEffect_Angles(iEffectNum, trace.endpos, Vector(270, 0, 0)); } } void CG_BodyFallSound(centity_t *ent, refEntity_t *pREnt, float volume) { int contents; int surftype; int iEffectNum = -1; vec3_t vStart, vEnd; vec3_t midlegs; str sSoundName; trace_t trace; VectorCopy(ent->lerpOrigin, vStart); vStart[2] += 8; VectorCopy(vStart, vEnd); vEnd[2] -= 64; if (ent->currentState.eType == ET_PLAYER) { CG_Trace( &trace, vStart, g_vFootstepMins, g_vFootstepMaxs, vEnd, ent->currentState.number, MASK_PLAYERSOLID, qtrue, qtrue, "Player Landing" ); } else { CG_Trace( &trace, vStart, g_vFootstepMins, g_vFootstepMaxs, vEnd, ent->currentState.number, MASK_MONSTERSOLID, qfalse, qfalse, "Monster Landing" ); } if (trace.fraction == 1) { return; } sSoundName = "snd_bodyfall_"; contents = CG_PointContents(trace.endpos, -1); if (contents & MASK_WATER) { // take our ground position and trace upwards VectorCopy(trace.endpos, midlegs); midlegs[2] += WATER_NO_SPLASH_HEIGHT; contents = CG_PointContents(midlegs, -1); if (contents & MASK_WATER) { sSoundName += "wade"; } else { sSoundName += "puddle"; iEffectNum = SFX_FOOT_PUDDLE; } } else { surftype = trace.surfaceFlags & MASK_SURF_TYPE; switch (surftype) { case SURF_FOLIAGE: sSoundName += "foliage"; iEffectNum = SFX_FOOT_GRASS; break; case SURF_SNOW: sSoundName += "snow"; iEffectNum = SFX_FOOT_SNOW; break; case SURF_CARPET: sSoundName += "carpet"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_SAND: sSoundName += "sand"; iEffectNum = SFX_FOOT_SAND; break; case SURF_PUDDLE: sSoundName += "puddle"; iEffectNum = SFX_FOOT_PUDDLE; break; case SURF_GLASS: sSoundName += "glass"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_GRAVEL: sSoundName += "gravel"; iEffectNum = SFX_FOOT_HEAVY_DUST; break; case SURF_MUD: sSoundName += "mud"; iEffectNum = SFX_FOOT_MUD; break; case SURF_DIRT: sSoundName += "dirt"; iEffectNum = SFX_FOOT_DIRT; break; case SURF_GRILL: sSoundName += "grill"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_GRASS: sSoundName += "grass"; iEffectNum = SFX_FOOT_GRASS; break; case SURF_ROCK: sSoundName += "stone"; iEffectNum = SFX_FOOT_HEAVY_DUST; break; case SURF_PAPER: sSoundName += "paper"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_WOOD: sSoundName += "wood"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; case SURF_METAL: sSoundName += "metal"; iEffectNum = SFX_FOOT_LIGHT_DUST; break; default: sSoundName += "stone"; iEffectNum = SFX_FOOT_HEAVY_DUST; break; } } if (cg_debugFootsteps->integer) { cgi.DPrintf("BodyFall: %s volume: %.2f effect = %i\n", sSoundName.c_str(), volume, iEffectNum); } commandManager.PlaySound(sSoundName, trace.endpos, -1, volume, -1, -1, 1); if (iEffectNum != -1) { sfxManager.MakeEffect_Angles(iEffectNum, trace.endpos, Vector(270, 0, 0)); } } /* =============== CG_Splash Draw a mark at the water surface =============== */ void CG_Splash(centity_t *cent) { vec3_t start, end, diff; trace_t trace; int contents; float dist; float time_required; spawnthing_t m_ripple; if (!cg_shadows->integer) { return; } VectorSubtract(cent->currentState.origin, cent->nextState.origin, diff); diff[2] = 0; dist = VectorNormalize(diff); // See if enough time has passed to add another ripple if (dist >= 1) { time_required = 60 - dist; } else { time_required = 175; } if (time_required < 5) { time_required = 5; } if (cent->splash_last_spawn_time + time_required > cg.time) { return; } // Save the current time cent->splash_last_spawn_time = cg.time; // Make sure the entity is moving if (dist < 1) { if (cent->splash_still_count >= 0) { cent->splash_still_count++; if (cent->splash_still_count > 2) { cent->splash_still_count = 0; } else { return; } } else { return; } } VectorCopy(cent->lerpOrigin, end); // if the feet aren't in liquid, don't make a mark // this won't handle moving water brushes, but they wouldn't draw right anyway... contents = cgi.CM_PointContents(end, 0); if (!(contents & (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA))) { return; } VectorCopy(cent->lerpOrigin, start); start[2] += 72; // if the head isn't out of liquid, don't make a mark contents = cgi.CM_PointContents(start, 0); if (contents & (CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA)) { return; } // trace down to find the surface cgi.CM_BoxTrace( &trace, start, end, vec3_origin, vec3_origin, 0, (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA), qfalse ); if (trace.fraction == 1.0) { return; } if (dist > 0) { vectoangles(diff, start); start[0] = -90.0; dist /= cg.frametime; if (dist < 0) { dist = 0; } else if (dist > 8192) { dist = 8192; } VectorMA(trace.endpos, dist, diff, end); sfxManager.MakeEffect_Angles(SFX_WATER_RIPPLE_MOVING, end, start); } else { start[0] = 90; start[1] = 0; start[2] = 0; sfxManager.MakeEffect_Angles(SFX_WATER_RIPPLE_STILL, trace.endpos, start); } } qboolean ClientSpecialEffectsManager::EffectsPending() { return sfxManager.m_iNumPendingEvents > 0; } specialeffect_t *ClientSpecialEffectsManager::GetTestEffectPointer() { return &m_effects[SFX_TEST_EFFECT]; }