/* * 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. */ typedef enumflags { PUSH_CHANGED_ORIGIN_X, PUSH_CHANGED_ORIGIN_Y, PUSH_CHANGED_ORIGIN_Z, PUSH_CHANGED_ANGLES_X, PUSH_CHANGED_ANGLES_Y, PUSH_CHANGED_ANGLES_Z, PUSH_CHANGED_MODELINDEX, PUSH_CHANGED_SIZE, PUSH_CHANGED_FRAME, PUSH_CHANGED_SPAWNFLAGS, PUSH_CHANGED_SOLIDMOVETYPE, PUSH_CHANGED_VELOCITY, PUSH_CHANGED_ANGULARVELOCITY, PUSH_CHANGED_RENDERCOLOR, PUSH_CHANGED_RENDERAMT, PUSH_CHANGED_RENDERMODE, PUSH_CHANGED_SPEED, PUSH_CHANGED_MOVEDIR } trigger_push_changed_t; enumflags { TP_ONCE, TP_STARTOFF }; /*!QUAKED trigger_push (.5 .5 .5) ? TP_ONCE TP_STARTOFF # OVERVIEW Pushes anything in its volume into a direction of your choosing. # KEYS - "targetname" : Name - "speed" : The speed (units per second) it'll apply to touchers. - "angles" : Sets the direction of the push. # SPAWNFLAGS - TP_ONCE (1) : Only emit a single push once before disabling itself. - TP_STARTOFF (2) : Needs to be triggered first in order to function. # TRIVIA This entity was introduced in Quake (1996). */ class trigger_push:NSBrushTrigger { public: void trigger_push(void); virtual void Touch(entity); #ifdef SERVER virtual void Save(float); virtual void Restore(string,string); virtual void SpawnKey(string,string); virtual void Respawn(void); virtual void Trigger(entity,triggermode_t); virtual void SetMovementDirection(void); virtual float SendEntity(entity,float); virtual void EvaluateEntity(void); #endif #ifdef CLIENT virtual void ReceiveEntity(float, float); #endif private: PREDICTED_FLOAT(m_flSpeed) PREDICTED_VECTOR(m_vecMoveDir) PREDICTED_BOOL(m_bHasTarget) }; void trigger_push::trigger_push(void) { m_vecMoveDir = [0,0,0]; m_flSpeed = 100; m_bHasTarget = false; } #ifdef SERVER void trigger_push::Save(float handle) { super::Save(handle); SaveVector(handle, "m_vecMoveDir", m_vecMoveDir); SaveFloat(handle, "m_flSpeed", m_flSpeed); SaveBool(handle, "m_bHasTarget", m_bHasTarget); } void trigger_push::Restore(string strKey, string strValue) { switch (strKey) { case "m_vecMoveDir": m_vecMoveDir = ReadVector(strValue); break; case "m_flSpeed": m_flSpeed = ReadFloat(strValue); break; case "m_bHasTarget": m_bHasTarget = ReadFloat(strValue); break; default: super::Restore(strKey, strValue); } } void trigger_push::SpawnKey(string strKey, string strValue) { switch (strKey) { case "speed": m_flSpeed = stof(strValue); break; default: super::SpawnKey(strKey, strValue); } } void trigger_push::Respawn(void) { static void JumpDestCheck(void) { /* this ent has a destination target */ m_bHasTarget = false; if (target) { entity targetPos = find(world, ::targetname, target); if (targetPos) { m_bHasTarget = true; m_vecMoveDir = targetPos.origin; } } } InitBrushTrigger(); RestoreAngles(); SetMovementDirection(); ClearAngles(); if (HasSpawnFlags(TP_STARTOFF)) { SetSolid(SOLID_NOT); } if (target) ScheduleThink(JumpDestCheck, 0.5f); } void trigger_push::Trigger(entity act, triggermode_t state) { switch (state) { case TRIG_OFF: SetSolid(SOLID_NOT); break; case TRIG_ON: SetSolid(SOLID_TRIGGER); break; default: SetSolid(solid == SOLID_NOT ? SOLID_TRIGGER : SOLID_NOT); } } void trigger_push::SetMovementDirection(void) { if (GetSpawnAngles() == [0,-1,0]) { m_vecMoveDir = [0,0,1]; } else if (angles == [0,-2,0]) { m_vecMoveDir = [0,0,-1]; } else { makevectors(GetSpawnAngles()); m_vecMoveDir = v_forward; } } void trigger_push::EvaluateEntity(void) { EVALUATE_VECTOR(origin, 0, PUSH_CHANGED_ORIGIN_X) EVALUATE_VECTOR(origin, 1, PUSH_CHANGED_ORIGIN_Y) EVALUATE_VECTOR(origin, 2, PUSH_CHANGED_ORIGIN_Z) EVALUATE_VECTOR(angles, 0, PUSH_CHANGED_ANGLES_X) EVALUATE_VECTOR(angles, 1, PUSH_CHANGED_ANGLES_Y) EVALUATE_VECTOR(angles, 2, PUSH_CHANGED_ANGLES_Z) EVALUATE_FIELD(modelindex, PUSH_CHANGED_MODELINDEX) EVALUATE_FIELD(solid, PUSH_CHANGED_SOLIDMOVETYPE) EVALUATE_FIELD(movetype, PUSH_CHANGED_SOLIDMOVETYPE) EVALUATE_VECTOR(mins, 0, PUSH_CHANGED_SIZE) EVALUATE_VECTOR(mins, 1, PUSH_CHANGED_SIZE) EVALUATE_VECTOR(mins, 2, PUSH_CHANGED_SIZE) EVALUATE_VECTOR(maxs, 0, PUSH_CHANGED_SIZE) EVALUATE_VECTOR(maxs, 1, PUSH_CHANGED_SIZE) EVALUATE_VECTOR(maxs, 2, PUSH_CHANGED_SIZE) EVALUATE_FIELD(frame, PUSH_CHANGED_FRAME) EVALUATE_VECTOR(velocity, 0, PUSH_CHANGED_VELOCITY) EVALUATE_VECTOR(velocity, 1, PUSH_CHANGED_VELOCITY) EVALUATE_VECTOR(velocity, 2, PUSH_CHANGED_VELOCITY) EVALUATE_VECTOR(avelocity, 0, PUSH_CHANGED_ANGULARVELOCITY) EVALUATE_VECTOR(avelocity, 1, PUSH_CHANGED_ANGULARVELOCITY) EVALUATE_VECTOR(avelocity, 2, PUSH_CHANGED_ANGULARVELOCITY) EVALUATE_FIELD(m_flSpeed, PUSH_CHANGED_SPEED) EVALUATE_VECTOR(m_vecMoveDir, 0, PUSH_CHANGED_MOVEDIR) EVALUATE_VECTOR(m_vecMoveDir, 1, PUSH_CHANGED_MOVEDIR) EVALUATE_VECTOR(m_vecMoveDir, 2, PUSH_CHANGED_MOVEDIR) EVALUATE_FIELD(m_bHasTarget, PUSH_CHANGED_MOVEDIR) } float trigger_push::SendEntity(entity ePEnt, float flChanged) { if (!modelindex) return (0); if (clienttype(ePEnt) != CLIENTTYPE_REAL) return (0); WriteByte(MSG_ENTITY, ENT_PUSH); /* optimisation */ { /* we'll never network these if we aren't moving. */ if (movetype == MOVETYPE_NONE) { flChanged &= ~PUSH_CHANGED_VELOCITY; flChanged &= ~PUSH_CHANGED_ANGULARVELOCITY; } } /* broadcast how much data is expected to be read */ WriteFloat(MSG_ENTITY, flChanged); SENDENTITY_COORD(origin[0], PUSH_CHANGED_ORIGIN_X) SENDENTITY_COORD(origin[1], PUSH_CHANGED_ORIGIN_Y) SENDENTITY_COORD(origin[2], PUSH_CHANGED_ORIGIN_Z) SENDENTITY_ANGLE(angles[0], PUSH_CHANGED_ANGLES_X) SENDENTITY_ANGLE(angles[1], PUSH_CHANGED_ANGLES_Y) SENDENTITY_ANGLE(angles[2], PUSH_CHANGED_ANGLES_Z) SENDENTITY_SHORT(modelindex, PUSH_CHANGED_MODELINDEX) SENDENTITY_BYTE(solid, PUSH_CHANGED_SOLIDMOVETYPE) SENDENTITY_BYTE(movetype, PUSH_CHANGED_SOLIDMOVETYPE) SENDENTITY_COORD(mins[0], PUSH_CHANGED_SIZE) SENDENTITY_COORD(mins[1], PUSH_CHANGED_SIZE) SENDENTITY_COORD(mins[2], PUSH_CHANGED_SIZE) SENDENTITY_COORD(maxs[0], PUSH_CHANGED_SIZE) SENDENTITY_COORD(maxs[1], PUSH_CHANGED_SIZE) SENDENTITY_COORD(maxs[2], PUSH_CHANGED_SIZE) SENDENTITY_BYTE(frame, PUSH_CHANGED_FRAME) SENDENTITY_COORD(velocity[0], PUSH_CHANGED_VELOCITY) SENDENTITY_COORD(velocity[1], PUSH_CHANGED_VELOCITY) SENDENTITY_COORD(velocity[2], PUSH_CHANGED_VELOCITY) SENDENTITY_COORD(avelocity[0], PUSH_CHANGED_ANGULARVELOCITY) SENDENTITY_COORD(avelocity[1], PUSH_CHANGED_ANGULARVELOCITY) SENDENTITY_COORD(avelocity[2], PUSH_CHANGED_ANGULARVELOCITY) SENDENTITY_FLOAT(m_flSpeed, PUSH_CHANGED_SPEED) SENDENTITY_FLOAT(m_vecMoveDir[0], PUSH_CHANGED_MOVEDIR) SENDENTITY_FLOAT(m_vecMoveDir[1], PUSH_CHANGED_MOVEDIR) SENDENTITY_FLOAT(m_vecMoveDir[2], PUSH_CHANGED_MOVEDIR) SENDENTITY_BYTE(m_bHasTarget, PUSH_CHANGED_MOVEDIR) return true; } #endif #ifdef CLIENT void trigger_push::ReceiveEntity(float flNew, float flChanged) { READENTITY_COORD(origin[0], PUSH_CHANGED_ORIGIN_X) READENTITY_COORD(origin[1], PUSH_CHANGED_ORIGIN_Y) READENTITY_COORD(origin[2], PUSH_CHANGED_ORIGIN_Z) READENTITY_ANGLE(angles[0], PUSH_CHANGED_ANGLES_X) READENTITY_ANGLE(angles[1], PUSH_CHANGED_ANGLES_Y) READENTITY_ANGLE(angles[2], PUSH_CHANGED_ANGLES_Z) READENTITY_SHORT(modelindex, PUSH_CHANGED_MODELINDEX) READENTITY_BYTE(solid, PUSH_CHANGED_SOLIDMOVETYPE) READENTITY_BYTE(movetype, PUSH_CHANGED_SOLIDMOVETYPE) READENTITY_COORD(mins[0], PUSH_CHANGED_SIZE) READENTITY_COORD(mins[1], PUSH_CHANGED_SIZE) READENTITY_COORD(mins[2], PUSH_CHANGED_SIZE) READENTITY_COORD(maxs[0], PUSH_CHANGED_SIZE) READENTITY_COORD(maxs[1], PUSH_CHANGED_SIZE) READENTITY_COORD(maxs[2], PUSH_CHANGED_SIZE) READENTITY_BYTE(frame, PUSH_CHANGED_FRAME) READENTITY_COORD(velocity[0], PUSH_CHANGED_VELOCITY) READENTITY_COORD(velocity[1], PUSH_CHANGED_VELOCITY) READENTITY_COORD(velocity[2], PUSH_CHANGED_VELOCITY) READENTITY_COORD(avelocity[0], PUSH_CHANGED_ANGULARVELOCITY) READENTITY_COORD(avelocity[1], PUSH_CHANGED_ANGULARVELOCITY) READENTITY_COORD(avelocity[2], PUSH_CHANGED_ANGULARVELOCITY) READENTITY_FLOAT(m_flSpeed, PUSH_CHANGED_SPEED) READENTITY_FLOAT(m_vecMoveDir[0], PUSH_CHANGED_MOVEDIR) READENTITY_FLOAT(m_vecMoveDir[1], PUSH_CHANGED_MOVEDIR) READENTITY_FLOAT(m_vecMoveDir[2], PUSH_CHANGED_MOVEDIR) READENTITY_BYTE(m_bHasTarget, PUSH_CHANGED_MOVEDIR) if (flChanged & PUSH_CHANGED_SIZE) setsize(this, mins, maxs); setorigin(this, origin); } void trigger_push_ReadEntity(bool new) { float fl; trigger_push rend = (trigger_push)self; if (new) { spawnfunc_trigger_push(); } fl = readfloat(); rend.ReceiveEntity(new, fl); } #endif void trigger_push::Touch(entity eToucher) { #ifdef SERVER eActivator = (NSEntity)eToucher; #endif switch(eToucher.movetype) { case MOVETYPE_NONE: case MOVETYPE_PUSH: case MOVETYPE_NOCLIP: case MOVETYPE_FOLLOW: return; } if (m_bHasTarget) { if (eToucher.flags & FL_ONGROUND) eToucher.velocity = Route_GetJumpVelocity(WorldSpaceCenter(), m_vecMoveDir, 1.0); return; } /* trigger_push is not supposed to work underwater */ if (eToucher.waterlevel > 1) return; if (eToucher.solid != SOLID_NOT && eToucher.solid != SOLID_BSP) { vector vecPush; vecPush = (m_flSpeed * m_vecMoveDir); if (HasSpawnFlags(TP_ONCE)) { //crossprint(sprintf("one push %v (%v)\n", eToucher.basevelocity, eToucher.velocity)); eToucher.velocity += vecPush; if (eToucher.velocity[2] > 0) { eToucher.flags &= ~FL_ONGROUND; } SetSolid(SOLID_NOT); } else { if (eToucher.flags & FL_ONGROUND) { eToucher.basevelocity = vecPush * 0.25; //crossprint(sprintf("basevel push %v (%v)\n", eToucher.basevelocity, eToucher.velocity)); } } } }