/*** * * Copyright (c) 1996-2001, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ // // ========================== PATH_CORNER =========================== // #include "extdll.h" #include "util.h" #include "cbase.h" #include "trains.h" #include "saverestore.h" class CPathCorner : public CPointEntity { public: void Spawn() override; bool KeyValue(KeyValueData* pkvd) override; float GetDelay() override { return m_flWait; } // void Touch( CBaseEntity *pOther ) override; bool Save(CSave& save) override; bool Restore(CRestore& restore) override; static TYPEDESCRIPTION m_SaveData[]; private: float m_flWait; }; LINK_ENTITY_TO_CLASS(path_corner, CPathCorner); // Global Savedata for Delay TYPEDESCRIPTION CPathCorner::m_SaveData[] = { DEFINE_FIELD(CPathCorner, m_flWait, FIELD_FLOAT), }; IMPLEMENT_SAVERESTORE(CPathCorner, CPointEntity); // // Cache user-entity-field values until spawn is called. // bool CPathCorner::KeyValue(KeyValueData* pkvd) { if (FStrEq(pkvd->szKeyName, "wait")) { m_flWait = atof(pkvd->szValue); return true; } return CPointEntity::KeyValue(pkvd); } void CPathCorner::Spawn() { ASSERTSZ(!FStringNull(pev->targetname), "path_corner without a targetname"); } #if 0 void CPathCorner:: Touch( CBaseEntity *pOther ) { entvars_t* pevToucher = pOther->pev; if ( FBitSet ( pevToucher->flags, FL_MONSTER ) ) {// monsters don't navigate path corners based on touch anymore return; } // If OTHER isn't explicitly looking for this path_corner, bail out if ( pOther->m_pGoalEnt != this ) { return; } // If OTHER has an enemy, this touch is incidental, ignore if ( !FNullEnt(pevToucher->enemy) ) { return; // fighting, not following a path } // UNDONE: support non-zero flWait /* if (m_flWait != 0) ALERT(at_warning, "Non-zero path-cornder waits NYI"); */ // Find the next "stop" on the path, make it the goal of the "toucher". if (FStringNull(pev->target)) { ALERT(at_warning, "PathCornerTouch: no next stop specified"); } pOther->m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ) ); // If "next spot" was not found (does not exist - level design error) if ( !pOther->m_pGoalEnt ) { ALERT(at_console, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING(pev->classname), STRING(pev->target)); return; } // Turn towards the next stop in the path. pevToucher->ideal_yaw = UTIL_VecToYaw ( pOther->m_pGoalEnt->pev->origin - pevToucher->origin ); } #endif TYPEDESCRIPTION CPathTrack::m_SaveData[] = { DEFINE_FIELD(CPathTrack, m_length, FIELD_FLOAT), DEFINE_FIELD(CPathTrack, m_pnext, FIELD_CLASSPTR), DEFINE_FIELD(CPathTrack, m_paltpath, FIELD_CLASSPTR), DEFINE_FIELD(CPathTrack, m_pprevious, FIELD_CLASSPTR), DEFINE_FIELD(CPathTrack, m_altName, FIELD_STRING), }; IMPLEMENT_SAVERESTORE(CPathTrack, CBaseEntity); LINK_ENTITY_TO_CLASS(path_track, CPathTrack); // // Cache user-entity-field values until spawn is called. // bool CPathTrack::KeyValue(KeyValueData* pkvd) { if (FStrEq(pkvd->szKeyName, "altpath")) { m_altName = ALLOC_STRING(pkvd->szValue); return true; } return CPointEntity::KeyValue(pkvd); } void CPathTrack::Use(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value) { bool on; // Use toggles between two paths if (m_paltpath) { on = !FBitSet(pev->spawnflags, SF_PATH_ALTERNATE); if (ShouldToggle(useType, on)) { if (on) SetBits(pev->spawnflags, SF_PATH_ALTERNATE); else ClearBits(pev->spawnflags, SF_PATH_ALTERNATE); } } else // Use toggles between enabled/disabled { on = !FBitSet(pev->spawnflags, SF_PATH_DISABLED); if (ShouldToggle(useType, on)) { if (on) SetBits(pev->spawnflags, SF_PATH_DISABLED); else ClearBits(pev->spawnflags, SF_PATH_DISABLED); } } } void CPathTrack::Link() { edict_t* pentTarget; if (!FStringNull(pev->target)) { pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->target)); if (!FNullEnt(pentTarget)) { m_pnext = CPathTrack::Instance(pentTarget); if (m_pnext) // If no next pointer, this is the end of a path { m_pnext->SetPrevious(this); } } else ALERT(at_console, "Dead end link %s\n", STRING(pev->target)); } // Find "alternate" path if (!FStringNull(m_altName)) { pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_altName)); if (!FNullEnt(pentTarget)) { m_paltpath = CPathTrack::Instance(pentTarget); if (m_paltpath) // If no next pointer, this is the end of a path { m_paltpath->SetPrevious(this); } } } } void CPathTrack::Spawn() { pev->solid = SOLID_TRIGGER; UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); m_pnext = NULL; m_pprevious = NULL; // DEBUGGING CODE #if PATH_SPARKLE_DEBUG SetThink(Sparkle); pev->nextthink = gpGlobals->time + 0.5; #endif } void CPathTrack::Activate() { if (!FStringNull(pev->targetname)) // Link to next, and back-link Link(); } CPathTrack* CPathTrack::ValidPath(CPathTrack* ppath, bool testFlag) { if (!ppath) return NULL; if (testFlag && FBitSet(ppath->pev->spawnflags, SF_PATH_DISABLED)) return NULL; return ppath; } void CPathTrack::Project(CPathTrack* pstart, CPathTrack* pend, Vector* origin, float dist) { if (pstart && pend) { Vector dir = (pend->pev->origin - pstart->pev->origin); dir = dir.Normalize(); *origin = pend->pev->origin + dir * dist; } } CPathTrack* CPathTrack::GetNext() { if (m_paltpath && FBitSet(pev->spawnflags, SF_PATH_ALTERNATE) && !FBitSet(pev->spawnflags, SF_PATH_ALTREVERSE)) return m_paltpath; return m_pnext; } CPathTrack* CPathTrack::GetPrevious() { if (m_paltpath && FBitSet(pev->spawnflags, SF_PATH_ALTERNATE) && FBitSet(pev->spawnflags, SF_PATH_ALTREVERSE)) return m_paltpath; return m_pprevious; } void CPathTrack::SetPrevious(CPathTrack* pprev) { // Only set previous if this isn't my alternate path if (pprev && !FStrEq(STRING(pprev->pev->targetname), STRING(m_altName))) m_pprevious = pprev; } // Assumes this is ALWAYS enabled CPathTrack* CPathTrack::LookAhead(Vector* origin, float dist, bool move) { CPathTrack* pcurrent; float originalDist = dist; pcurrent = this; Vector currentPos = *origin; if (dist < 0) // Travelling backwards through path { dist = -dist; while (dist > 0) { Vector dir = pcurrent->pev->origin - currentPos; float length = dir.Length(); if (0 == length) { if (!ValidPath(pcurrent->GetPrevious(), move)) // If there is no previous node, or it's disabled, return now. { if (!move) Project(pcurrent->GetNext(), pcurrent, origin, dist); return NULL; } pcurrent = pcurrent->GetPrevious(); } else if (length > dist) // enough left in this path to move { *origin = currentPos + (dir * (dist / length)); return pcurrent; } else { dist -= length; currentPos = pcurrent->pev->origin; *origin = currentPos; if (!ValidPath(pcurrent->GetPrevious(), move)) // If there is no previous node, or it's disabled, return now. return NULL; pcurrent = pcurrent->GetPrevious(); } } *origin = currentPos; return pcurrent; } else { while (dist > 0) { if (!ValidPath(pcurrent->GetNext(), move)) // If there is no next node, or it's disabled, return now. { if (!move) Project(pcurrent->GetPrevious(), pcurrent, origin, dist); return NULL; } Vector dir = pcurrent->GetNext()->pev->origin - currentPos; float length = dir.Length(); if (0 == length && !ValidPath(pcurrent->GetNext()->GetNext(), move)) { if (dist == originalDist) // HACK -- up against a dead end return NULL; return pcurrent; } if (length > dist) // enough left in this path to move { *origin = currentPos + (dir * (dist / length)); return pcurrent; } else { dist -= length; currentPos = pcurrent->GetNext()->pev->origin; pcurrent = pcurrent->GetNext(); *origin = currentPos; } } *origin = currentPos; } return pcurrent; } // Assumes this is ALWAYS enabled CPathTrack* CPathTrack::Nearest(Vector origin) { int deadCount; float minDist, dist; Vector delta; CPathTrack *ppath, *pnearest; delta = origin - pev->origin; delta.z = 0; minDist = delta.Length(); pnearest = this; ppath = GetNext(); // Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :) deadCount = 0; while (ppath && ppath != this) { deadCount++; if (deadCount > 9999) { ALERT(at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname)); return NULL; } delta = origin - ppath->pev->origin; delta.z = 0; dist = delta.Length(); if (dist < minDist) { minDist = dist; pnearest = ppath; } ppath = ppath->GetNext(); } return pnearest; } CPathTrack* CPathTrack::Instance(edict_t* pent) { if (FClassnameIs(pent, "path_track")) return (CPathTrack*)GET_PRIVATE(pent); return NULL; } // DEBUGGING CODE #if PATH_SPARKLE_DEBUG void CPathTrack::Sparkle() { pev->nextthink = gpGlobals->time + 0.2; if (FBitSet(pev->spawnflags, SF_PATH_DISABLED)) UTIL_ParticleEffect(pev->origin, Vector(0, 0, 100), 210, 10); else UTIL_ParticleEffect(pev->origin, Vector(0, 0, 100), 84, 10); } #endif