#include "precompiled.h" TYPEDESCRIPTION CPathCorner::m_SaveData[] = { DEFINE_FIELD(CPathCorner, m_flWait, FIELD_FLOAT), }; LINK_ENTITY_TO_CLASS(path_corner, CPathCorner, CCSPathCorner) IMPLEMENT_SAVERESTORE(CPathCorner, CPointEntity) void CPathCorner::KeyValue(KeyValueData *pkvd) { if (FStrEq(pkvd->szKeyName, "wait")) { m_flWait = Q_atof(pkvd->szValue); pkvd->fHandled = TRUE; } else { CPointEntity::KeyValue(pkvd); } } void CPathCorner::Spawn() { DbgAssertMsg(!pev->targetname.IsNull(), "path_corner without a targetname"); } 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, CCSPathTrack) void CPathTrack::KeyValue(KeyValueData *pkvd) { if (FStrEq(pkvd->szKeyName, "altpath")) { m_altName = ALLOC_STRING(pkvd->szValue); pkvd->fHandled = TRUE; } else { CPointEntity::KeyValue(pkvd); } } void CPathTrack::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) { int on; // Use toggles between two paths if (m_paltpath) { on = !(pev->spawnflags & SF_PATH_ALTERNATE); if (ShouldToggle(useType, on)) { pev->spawnflags ^= SF_PATH_ALTERNATE; } } else // Use toggles between enabled/disabled { on = !(pev->spawnflags & SF_PATH_DISABLED); if (ShouldToggle(useType, on)) { pev->spawnflags ^= SF_PATH_DISABLED; } } } void CPathTrack::Link() { edict_t *pentTarget; if (!FStringNull(pev->target)) { pentTarget = FIND_ENTITY_BY_TARGETNAME(nullptr, pev->target); if (!FNullEnt(pentTarget)) { m_pnext = CPathTrack::Instance(pentTarget); // If no next pointer, this is the end of a path if (m_pnext) { 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(nullptr, m_altName); if (!FNullEnt(pentTarget)) { m_paltpath = CPathTrack::Instance(pentTarget); // If no next pointer, this is the end of a path if (m_paltpath) { m_paltpath->SetPrevious(this); } } } } void CPathTrack::Spawn() { pev->solid = SOLID_TRIGGER; UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); m_pnext = nullptr; m_pprevious = nullptr; } void CPathTrack::Activate() { // Link to next, and back-link if (!FStringNull(pev->targetname)) { Link(); } } CPathTrack *CPathTrack::ValidPath(CPathTrack *ppath, int testFlag) { if (!ppath) return nullptr; if (testFlag && (ppath->pev->spawnflags & SF_PATH_DISABLED)) return nullptr; 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 && (pev->spawnflags & SF_PATH_ALTERNATE) && !(pev->spawnflags & SF_PATH_ALTREVERSE)) { return m_paltpath; } return m_pnext; } CPathTrack *CPathTrack::GetPrevious() { if (m_paltpath && (pev->spawnflags & SF_PATH_ALTERNATE) && (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(pprev->pev->targetname, m_altName)) { m_pprevious = pprev; } } // Assumes this is ALWAYS enabled CPathTrack *CPathTrack::LookAhead(Vector *origin, float dist, int move) { CPathTrack *pcurrent; float originalDist = dist; pcurrent = this; Vector currentPos = *origin; // Travelling backwards through path if (dist < 0) { dist = -dist; while (dist > 0) { Vector dir = pcurrent->pev->origin - currentPos; real_t length = dir.Length(); if (!length) { // If there is no previous node, or it's disabled, return now. if (!ValidPath(pcurrent->GetPrevious(), move)) { if (!move) { Project(pcurrent->GetNext(), pcurrent, origin, dist); } return nullptr; } pcurrent = pcurrent->GetPrevious(); } // enough left in this path to move else if (length > dist) { *origin = currentPos + (dir * float(dist / length)); return pcurrent; } else { dist -= length; currentPos = pcurrent->pev->origin; *origin = currentPos; // If there is no previous node, or it's disabled, return now. if (!ValidPath(pcurrent->GetPrevious(), move)) { return nullptr; } pcurrent = pcurrent->GetPrevious(); } } *origin = currentPos; return pcurrent; } else { // #96 line while (dist > 0) { // If there is no next node, or it's disabled, return now. if (!ValidPath(pcurrent->GetNext(), move)) { if (!move) { Project(pcurrent->GetPrevious(), pcurrent, origin, dist); } return nullptr; } Vector dir = pcurrent->GetNext()->pev->origin - currentPos; real_t length = dir.Length(); if (!length && !ValidPath(pcurrent->GetNext()->GetNext(), move)) { // HACK: up against a dead end if (dist == originalDist) return nullptr; return pcurrent; } // enough left in this path to move if (length > dist) { *origin = currentPos + (dir * float(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) { if (++deadCount > 9999) { ALERT(at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname)); return nullptr; } 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 *pEdict) { if (FClassnameIs(pEdict, "path_track")) { return GET_PRIVATE(pEdict); } return nullptr; }