/*** * * Copyright (c) 2001, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "squadmonster.h" #include "player.h" #include "weapons.h" #include "decals.h" #include "gamerules.h" #include "effects.h" #include "saverestore.h" #define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) ) class CRopeSegment : public CBaseEntity { public: int Save(CSave &save); int Restore(CRestore &restore); static TYPEDESCRIPTION m_SaveData[]; void Spawn(void); void EXPORT SegmentThink(void); void EXPORT SegmentTouch(CBaseEntity* pOther); CRopeSegment* m_pNext; CRopeSegment* m_pPrev; Vector m_vecJointPos; }; LINK_ENTITY_TO_CLASS(rope_segment, CRopeSegment); TYPEDESCRIPTION CRopeSegment::m_SaveData[] = { DEFINE_FIELD(CRopeSegment, m_pNext, FIELD_CLASSPTR), DEFINE_FIELD(CRopeSegment, m_pPrev, FIELD_CLASSPTR), DEFINE_FIELD(CRopeSegment, m_vecJointPos, FIELD_POSITION_VECTOR), }; IMPLEMENT_SAVERESTORE(CRopeSegment, CBaseEntity); void CRopeSegment::Spawn(void) { SET_MODEL(ENT(pev), "models/rope16.mdl"); pev->solid = SOLID_NOT; pev->movetype = MOVETYPE_NONE; pev->gravity = 0.0f; m_pPrev = NULL; m_pNext = NULL; m_vecJointPos = Vector(0, 0, 0); SetTouch(&CRopeSegment::Touch); SetThink(&CRopeSegment::SegmentThink); pev->nextthink = gpGlobals->time + 0.1f; } void CRopeSegment::SegmentThink(void) { pev->nextthink = gpGlobals->time + 0.1f; } void CRopeSegment::SegmentTouch(CBaseEntity* pOther) { ALERT(at_console, "Touched segment!\n"); SetTouch(NULL); } #define MAX_ROPE_SEGMENTS 64 class CRope : public CBaseEntity { public: int Save(CSave &save); int Restore(CRestore &restore); static TYPEDESCRIPTION m_SaveData[]; void KeyValue(KeyValueData* pkvd); void Spawn(void); void Precache(void); void EXPORT StartupThink(void); void EXPORT RopeThink(void); void CreateSegments(); void DestroySegments(); int FindClosestSegment(Vector& vecTo, float epsilon, int iMin, int iMax); CRopeSegment* m_pSegments[MAX_ROPE_SEGMENTS]; int m_nSegments; BOOL m_fEnabled; string_t m_iszBodyModel; string_t m_iszEndingModel; void ApplyFunctor(void(*functor)(CRopeSegment* pSegment), int startIndex, int endIndex); void ApplyFunctor(void(*functor)(CRopeSegment* pSegment)); }; LINK_ENTITY_TO_CLASS(env_rope, CRope); TYPEDESCRIPTION CRope::m_SaveData[] = { DEFINE_ARRAY(CRope, m_pSegments, FIELD_CLASSPTR, MAX_ROPE_SEGMENTS), DEFINE_FIELD(CRope, m_nSegments, FIELD_INTEGER), DEFINE_FIELD(CRope, m_fEnabled, FIELD_BOOLEAN), DEFINE_FIELD(CRope, m_iszBodyModel, FIELD_STRING), DEFINE_FIELD(CRope, m_iszEndingModel, FIELD_STRING), }; IMPLEMENT_SAVERESTORE(CRope, CBaseEntity); void CRope::KeyValue(KeyValueData* pkvd) { if (FStrEq(pkvd->szKeyName, "segments")) { m_nSegments = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "disable")) { m_fEnabled = !atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "endingmodel")) { m_iszEndingModel = ALLOC_STRING(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "bodymodel")) { m_iszBodyModel = ALLOC_STRING(pkvd->szValue); pkvd->fHandled = TRUE; } else CBaseEntity::KeyValue(pkvd); } void CRope::Spawn(void) { Precache(); pev->solid = SOLID_NOT; pev->movetype = MOVETYPE_NONE; pev->effects = 0; for (int i = 0; i < MAX_ROPE_SEGMENTS; i++) m_pSegments[i] = NULL; SetThink(&CRope::StartupThink); pev->nextthink = gpGlobals->time + 0.1; } void CRope::Precache(void) { PRECACHE_MODEL("models/rope16.mdl"); PRECACHE_MODEL("models/rope24.mdl"); PRECACHE_MODEL("models/rope32.mdl"); UTIL_PrecacheOther("rope_segment"); } void CRope::StartupThink(void) { pev->nextthink = gpGlobals->time + 0.1; CreateSegments(); SetThink(&CRope::RopeThink); } //=============================================== // Use binary search for closest point. //=============================================== int CRope::FindClosestSegment(Vector& vecTo, float epsilon, int iMin, int iMax) { if (iMax < iMin) return -1; else { int middle = iMin + ((iMax - iMin) / 2); if (vecTo.z > (m_pSegments[middle]->pev->origin.z + epsilon)) { return FindClosestSegment(vecTo, epsilon, iMin, middle - 1); // Higher ropes are at vector head. } else if (vecTo.z < (m_pSegments[middle]->pev->origin.z - epsilon)) { return FindClosestSegment(vecTo, epsilon, middle + 1, iMax); // Lower ropes are at vector tail. } else { return middle; } } } static void Functor_SetSegmentFxGlow(CRopeSegment* pSegment) { pSegment->pev->renderfx = kRenderFxGlowShell; } static void Functor_SetSegmentFxNormal(CRopeSegment* pSegment) { pSegment->pev->renderfx = kRenderFxNone; } void CRope::RopeThink(void) { pev->nextthink = gpGlobals->time + 0.1; CBaseEntity* pEntity = UTIL_PlayerByIndex(1); if (pEntity) { if ((pEntity->pev->origin - pev->origin).Length() > 384) return; ApplyFunctor(Functor_SetSegmentFxNormal); Vector v = ((CBasePlayer*)pEntity)->GetGunPosition(); int index = FindClosestSegment(v, 16, 0, m_nSegments - 1); if (index >= 0 && index < m_nSegments) { CRopeSegment* pClosestSegment = m_pSegments[index]; if (pClosestSegment) { pClosestSegment->SetTouch(&CRopeSegment::SegmentTouch); pClosestSegment->pev->renderfx = kRenderFxGlowShell; pClosestSegment->pev->rendercolor = Vector(255, 0, 0); ALERT(at_console, "Rope joint closest is %d at %f\n", index, pClosestSegment->pev->origin.z); } } } } //=============================================== // Create all rope segments. //=============================================== void CRope::CreateSegments() { CRopeSegment* pRopeSegment = NULL; Vector down = Vector(0, 0, -1); Vector angles = Vector(0, 0, -90); Vector joint; joint = pev->origin + (down * 16); m_pSegments[0] = (CRopeSegment*)CBaseEntity::Create( "rope_segment", joint, angles); m_pSegments[0]->m_vecJointPos = joint; m_pSegments[0]->m_pPrev = NULL; int i; for (i = 1; i < m_nSegments; i++) { joint = pev->origin + (down * 16 * i); m_pSegments[i] = (CRopeSegment*)CBaseEntity::Create("rope_segment", joint, angles); m_pSegments[i]->m_vecJointPos = joint; m_pSegments[i - 1]->m_pNext = m_pSegments[i]; m_pSegments[i]->m_pPrev = m_pSegments[i - 1]; m_pSegments[i]->m_pNext = NULL; } } //=============================================== // Destroy all rope segments //=============================================== void CRope::DestroySegments(void) { int i; for (i = 0; i < m_nSegments; i++) { if (m_pSegments[i]) { UTIL_Remove(m_pSegments[i]); m_pSegments[i] = NULL; } } } //=============================================== // Apply a functor to a range of segments. //=============================================== void CRope::ApplyFunctor(void(*functor)(CRopeSegment* pSegment), int startIndex, int endIndex) { // No functor? Return. if (!functor) return; // To pass -1 as value for endindex means to apply the functor to all segments. int endindex = (endIndex != -1) ? endIndex : (m_nSegments - 1); // Apply functor to all segments. int i; for (i = 0; i <= endindex; i++) { // Apply functor. if (m_pSegments[i]) functor(m_pSegments[i]); } } //=============================================== // Apply a functor to all segments. //=============================================== void CRope::ApplyFunctor(void(*functor)(CRopeSegment* pSegment)) { ApplyFunctor(functor, 0, -1); }