/* * Copyright (c) 2016-2022 Vera Visions LLC. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ enumflags { OCAMFL_CHANGED_ORIGIN, OCAMFL_CHANGED_ANGLES, OCAMFL_CHANGED_WAIT, OCAMFL_CHANGED_VIEWER }; /*!QUAKED trigger_camera (.5 .5 .5) (-8 -8 -8) (8 8 8) # OVERVIEW Forces a camera change when triggered. # KEYS - "targetname" : Name - "target" : Camera aims at this target. - "moveto" : First path_corner entity, if present. - "wait" : The total time the camera effect is active. # NOTES The client who triggers it will get its camera perspective changed to this trigger_camera. It is capable of following a track in case "moveto" is set. The movement finishes independently of the "wait" key. In case you are follow a path, the triggers of the respective path_corners will be respected. # TRIVIA This entity was introduced in Half-Life (1998). */ class trigger_camera:NSPointTrigger { public: void trigger_camera(void); #ifdef CLIENT virtual void ReceiveEntity(float,float); #else virtual void Save(float); virtual void Restore(string, string); virtual void NextPath(void); virtual void GoToTarget(void); virtual void Trigger(entity, triggermode_t); virtual void Respawn(void); virtual void SpawnKey(string,string); virtual float SendEntity(entity,float); virtual void EvaluateEntity(void); nonvirtual void DeactivateCamera(void); #endif private: NETWORKED_FLOAT(m_flWait) entity m_eLooker; #ifdef SERVER string m_strAimAt; string m_strMoveTo; entity m_eLooker_net; float m_flSpeed; float m_flInitialSpeed; #endif }; void trigger_camera::trigger_camera(void) { m_flWait = 0.0f; m_eLooker = __NULL__; #ifndef CLIENT m_strAimAt = __NULL__; m_strMoveTo = __NULL__; #endif } #ifdef CLIENT void trigger_camera::ReceiveEntity(float flNew, float flFlags) { bool deactiveLocalCam = false; //if (flFlags & OCAMFL_CHANGED_ORIGIN) { origin[0] = readcoord(); origin[1] = readcoord(); origin[2] = readcoord(); setorigin(this, origin); //} //if (flFlags & OCAMFL_CHANGED_ANGLES) { angles[0] = readfloat(); angles[1] = readfloat(); angles[2] = readfloat(); //} if (m_eLooker == pSeat->m_ePlayer) { deactiveLocalCam = true; } //if (flFlags & OCAMFL_CHANGED_VIEWER) { m_eLooker = findfloat(world, ::entnum, readentitynum()); //} //if (flFlags & OCAMFL_CHANGED_WAIT) { m_flWait = readfloat(); //} int s = (float)getproperty(VF_ACTIVESEAT); pSeat = &g_seats[s]; if (!m_eLooker && deactiveLocalCam == true) { pSeat->m_flCameraTime = 0.0; } /* not us */ if (m_eLooker != pSeat->m_ePlayer) return; pSeat->m_vecCameraOrigin = origin; pSeat->m_vecCameraAngle = angles; //if (flFlags & OCAMFL_CHANGED_WAIT) { if (m_flWait == -1) pSeat->m_flCameraTime = -1; else pSeat->m_flCameraTime = time + m_flWait; g_view.SetCameraAngle(angles); g_view.SetClientAngle(angles); //} classname = "trigger_camera"; } #else void trigger_camera::SpawnKey(string strKey, string strValue) { switch (strKey) { case "target": m_strAimAt = ReadString(strValue); break; case "moveto": m_strMoveTo = ReadString(strValue); break; case "wait": m_flWait = ReadFloat(strValue); break; case "speed": m_flSpeed = ReadFloat(strValue); break; default: super::SpawnKey(strKey, strValue); } } void trigger_camera::Respawn(void) { SetSize([0,0,0], [0,0,0]); SetSolid(SOLID_NOT); SetMovetype(MOVETYPE_PUSH); SetModel(GetSpawnModel()); SetOrigin(GetSpawnOrigin()); m_eLooker = world; } void trigger_camera::Save(float handle) { super::Save(handle); SaveFloat(handle, "m_flWait", m_flWait); SaveEntity(handle, "m_eLooker", m_eLooker); SaveString(handle, "m_strAimAt", m_strAimAt); SaveString(handle, "m_strMoveTo", m_strMoveTo); SaveFloat(handle, "m_flSpeed", m_flSpeed); } void trigger_camera::Restore(string strKey, string strValue) { switch (strKey) { case "m_flWait": m_flWait = ReadFloat(strValue); break; case "m_eLooker": m_eLooker = ReadEntity(strValue); break; case "m_strAimAt": m_strAimAt = ReadString(strValue); break; case "m_strMoveTo": m_strMoveTo = ReadString(strValue); break; case "m_flSpeed": m_flInitialSpeed = ReadFloat(strValue); break; default: super::Restore(strKey, strValue); } } float trigger_camera::SendEntity(entity ePEnt, float flFlags) { WriteByte(MSG_ENTITY, ENT_OLDCAMERA); WriteFloat(MSG_ENTITY, flFlags); //if (flFlags & OCAMFL_CHANGED_ORIGIN) { WriteCoord(MSG_ENTITY, origin[0]); WriteCoord(MSG_ENTITY, origin[1]); WriteCoord(MSG_ENTITY, origin[2]); //} //if (flFlags & OCAMFL_CHANGED_ANGLES) { WriteFloat(MSG_ENTITY, angles[0]); WriteFloat(MSG_ENTITY, angles[1]); WriteFloat(MSG_ENTITY, angles[2]); // } //if (flFlags & OCAMFL_CHANGED_VIEWER) { WriteEntity(MSG_ENTITY, m_eLooker); //} //if (flFlags & OCAMFL_CHANGED_WAIT) { WriteFloat(MSG_ENTITY, m_flWait); //} return (1); } void trigger_camera::EvaluateEntity(void) { NSEntity aimingAt; if (!m_eLooker) return; aimingAt = (NSEntity)find(world, ::targetname, m_strAimAt); if (aimingAt) { TurnToPos(aimingAt.GetOrigin()); } else { //NSEntity targetNode = (NSEntity)find(world, ::targetname, target); //TurnToPos(targetNode.GetOrigin()); } if (ATTR_CHANGED(origin)) { SetSendFlags(OCAMFL_CHANGED_ORIGIN); } if (ATTR_CHANGED(angles)) { SetSendFlags(OCAMFL_CHANGED_ANGLES); } if (ATTR_CHANGED(m_eLooker)) { SetSendFlags(OCAMFL_CHANGED_VIEWER); } if (ATTR_CHANGED(m_flWait)) { SetSendFlags(OCAMFL_CHANGED_WAIT); } SAVE_STATE(origin) SAVE_STATE(angles) SAVE_STATE(m_eLooker) SAVE_STATE(m_flWait) } void trigger_camera::GoToTarget(void) { float travelTime; vector camVelocity; path_corner targetNode; targetNode = (path_corner)find(world, ::targetname, target); if (!targetNode) { EntError("Cannot find target %S", target); return; } camVelocity = (targetNode.GetOrigin() - GetOrigin()); if (targetNode.m_flSpeed) { m_flSpeed = targetNode.m_flSpeed; } travelTime = (vlen(camVelocity) / m_flSpeed); EntLog("Moving to path_corner %S at %d within %f secs (%v)", target, m_flSpeed, travelTime, camVelocity); if (!travelTime) { EntWarning("Distance too short, going to next."); NextPath(); return; } if (!m_strAimAt) { vector vecAngleDiff = targetNode.GetOrigin() - GetOrigin(); vector vecAngleDest = vectoangles(vecAngleDiff); vecAngleDiff = Math_AngleDiff(vecAngleDest, angles); SetAngularVelocity(vecAngleDiff * (1 / travelTime)); } SetVelocity(camVelocity * (1 / travelTime)); ScheduleThink(NextPath, travelTime); } void trigger_camera::NextPath(void) { path_corner nextNode; if (target == "") { return; } nextNode = (path_corner)find(world, ::targetname, target); if (!nextNode) { NSError("Node %S suddenly missing from map!", target); return; } EntLog("Arrived at target node %S.", target); /* fire the path_corners' target */ nextNode.PathPassTrigger(this, TRIG_TOGGLE); if (target != m_strMoveTo) m_strAimAt = __NULL__; SetOrigin(nextNode.origin - (mins + maxs) * 0.5); SetTriggerTarget(nextNode.target); ClearVelocity(); /* warp next frame */ if (nextNode.HasSpawnFlags(PC_TELEPORT)) { EntLog("Node %S wants %S to teleport.", nextNode.targetname, targetname); ScheduleThink(NextPath, 0.0f); return; } /* stop until triggered again */ if (nextNode.HasSpawnFlags(PC_WAIT)) { return; } if (nextNode.m_flWait > 0) { ScheduleThink(GoToTarget, nextNode.m_flWait); } else { GoToTarget(); } } /* TODO: Handle state? */ void trigger_camera::Trigger(entity act, triggermode_t state) { /* HACK: if we don't have a valid activator... pick the first player we can find */ if (!(act.flags & FL_CLIENT)) { act = find(world, ::classname, "player"); } EntLog("Controlling camera of %S", act.netname); /* kill the other cams the player may be attached to */ for (trigger_camera cam = __NULL__; (cam = (trigger_camera)find(cam, ::classname, "trigger_camera"));) { if (cam.m_eLooker == act) { cam.m_eLooker = __NULL__; cam.SetSendFlags(-1); } } if (act == m_eLooker) { DeactivateCamera(); return; } m_eLooker = act; m_eLooker.view2 = this; m_flSpeed = m_flInitialSpeed; if (!m_flSpeed) { m_flSpeed = 100.0f; } /* Reset to the spawn values */ SetMovetype(MOVETYPE_PUSH); SetSolid(SOLID_NOT); SetOrigin(GetSpawnOrigin()); SetTriggerTarget(m_strMoveTo); NextPath(); GoToTarget(); SetSendFlags(0xFFFFFF); pvsflags = PVSF_IGNOREPVS; } void trigger_camera::DeactivateCamera(void) { EntLog("Turning it off on %S", m_eLooker.netname); m_eLooker.view2 = __NULL__; m_eLooker = __NULL__; SetSendFlags(-1); } #endif