/* * 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. */ void NSPhysicsEntity::NSPhysicsEntity(void) { mass = 1.0f; m_flInertiaScale = 1.0f; m_iEnabled = 0i; m_iShape = PHYSM_BOX; m_iMaterial = 0i; m_iFlags = 0i; cvar_set("physics_ode_iterationsperframe", "1"); cvar_set("physics_ode_movelimit", "0.1"); } #ifdef SERVER void NSPhysicsEntity::Save(float handle) { super::Save(handle); SaveInt(handle, "m_iEnabled", m_iEnabled); SaveInt(handle, "m_iShape", m_iShape); SaveInt(handle, "m_iMaterial", m_iMaterial); SaveInt(handle, "m_iFlags", m_iFlags); SaveFloat(handle, "m_flInertiaScale", m_flInertiaScale); } void NSPhysicsEntity::Restore(string strKey, string strValue) { switch (strKey) { case "m_iEnabled": m_iEnabled = ReadInt(strValue); break; case "m_iShape": m_iShape = ReadInt(strValue); break; case "m_iMaterial": m_iMaterial = ReadInt(strValue); break; case "m_iFlags": m_iFlags = ReadInt(strValue); break; case "m_flInertiaScale": m_flInertiaScale = ReadFloat(strValue); break; default: super::Restore(strKey, strValue); } } #endif #define ODE_MODE 1 #ifdef CLIENT bool physics_supported(void) { return true; } #endif /* all the documented phys vars...*/ .float geomtype; .float friction; .float erp; .float jointtype; .float mass; .float bouncefactor; .float bouncestop; #ifdef SERVER void NSPhysicsEntity::EvaluateEntity(void) { #if 0 origin[0] = rint(origin[0]); origin[1] = rint(origin[1]); origin[2] = rint(origin[2]); angles[0] = rint(angles[0]); angles[1] = rint(angles[1]); angles[2] = rint(angles[2]); #endif if (origin_x != origin_net_x) { SetSendFlags(PHYENT_CHANGED_ORIGIN_X); } if (origin_y != origin_net_y) { SetSendFlags(PHYENT_CHANGED_ORIGIN_Y); } if (origin_z != origin_net_z) { SetSendFlags(PHYENT_CHANGED_ORIGIN_Z); } if (angles_x != angles_net_x) { SetSendFlags(PHYENT_CHANGED_ANGLES_X); } if (angles_y != angles_net_y) { SetSendFlags(PHYENT_CHANGED_ANGLES_Y); } if (angles_z != angles_net_z) { SetSendFlags(PHYENT_CHANGED_ANGLES_Z); } if (ATTR_CHANGED(modelindex)) { SetSendFlags(PHYENT_CHANGED_MODELINDEX); } if (ATTR_CHANGED(solid)) { SetSendFlags(PHYENT_CHANGED_SOLID); } if (ATTR_CHANGED(movetype)) { SetSendFlags(PHYENT_CHANGED_MOVETYPE); } if (ATTR_CHANGED(size)) { SetSendFlags(PHYENT_CHANGED_SIZE); } if (ATTR_CHANGED(frame)) { SetSendFlags(PHYENT_CHANGED_FRAME); } if (ATTR_CHANGED(skin)) { SetSendFlags(PHYENT_CHANGED_SKIN); } if (ATTR_CHANGED(effects)) { SetSendFlags(PHYENT_CHANGED_EFFECTS); } if (ATTR_CHANGED(m_iBody)) { SetSendFlags(PHYENT_CHANGED_BODY); } if (ATTR_CHANGED(scale)) { SetSendFlags(PHYENT_CHANGED_SCALE); } if (ATTR_CHANGED(velocity)) { SetSendFlags(PHYENT_CHANGED_VELOCITY); } SAVE_STATE(origin) SAVE_STATE(angles) SAVE_STATE(modelindex) SAVE_STATE(solid) SAVE_STATE(movetype) SAVE_STATE(size) SAVE_STATE(frame) SAVE_STATE(skin) SAVE_STATE(effects) SAVE_STATE(m_iBody) SAVE_STATE(scale) SAVE_STATE(velocity) if (ATTR_CHANGED(m_iRenderMode)) { SetSendFlags(PHYENT_CHANGED_RENDERMODE); } if (ATTR_CHANGED(m_iRenderFX)) { SetSendFlags(PHYENT_CHANGED_RENDERMODE); } if (ATTR_CHANGED(m_vecRenderColor)) { SetSendFlags(PHYENT_CHANGED_RENDERCOLOR); } if (ATTR_CHANGED(m_flRenderAmt)) { SetSendFlags(PHYENT_CHANGED_RENDERAMT); } } float NSPhysicsEntity::SendEntity(entity ePEnt, float fChanged) { if (!modelindex) return (0); if (clienttype(ePEnt) != CLIENTTYPE_REAL) return (0); WriteByte(MSG_ENTITY, ENT_PHYSICS); /* newly popped into the PVS, sadly this is the only hacky way to check * for this right now. convince the engine maintainer to make this more sensible */ if (fChanged == 0xFFFFFF) { /* check for defaults. if these are predictable fields, don't even bother * networking them! you're just wasting bandwidth. */ if (frame == 0) fChanged &= ~PHYENT_CHANGED_FRAME; if (skin == 0) fChanged &= ~PHYENT_CHANGED_SKIN; if (effects == 0) fChanged &= ~PHYENT_CHANGED_EFFECTS; if (m_iBody == 0) fChanged &= ~PHYENT_CHANGED_BODY; if (scale == 0.0 || scale == 1.0) fChanged &= ~PHYENT_CHANGED_SCALE; if (origin[0] == 0) fChanged &= ~PHYENT_CHANGED_ORIGIN_X; if (origin[1] == 0) fChanged &= ~PHYENT_CHANGED_ORIGIN_Y; if (origin[2] == 0) fChanged &= ~PHYENT_CHANGED_ORIGIN_Z; if (angles[0] == 0) fChanged &= ~PHYENT_CHANGED_ANGLES_X; if (angles[1] == 0) fChanged &= ~PHYENT_CHANGED_ANGLES_Y; if (angles[2] == 0) fChanged &= ~PHYENT_CHANGED_ANGLES_Z; if (velocity == [0,0,0]) fChanged &= ~PHYENT_CHANGED_VELOCITY; if (mins == [0,0,0] && maxs == [0,0,0]) fChanged &= ~PHYENT_CHANGED_SIZE; if (solid == SOLID_NOT) fChanged &= ~PHYENT_CHANGED_SOLID; if (movetype == MOVETYPE_NONE) fChanged &= ~PHYENT_CHANGED_MOVETYPE; if (m_iRenderMode == RM_NORMAL) fChanged &= ~PHYENT_CHANGED_RENDERMODE; } /* broadcast how much data is expected to be read */ WriteFloat(MSG_ENTITY, fChanged); /* really trying to get our moneys worth with 23 bits of mantissa */ if (fChanged & PHYENT_CHANGED_ORIGIN_X) { WriteCoord(MSG_ENTITY, origin[0]); } if (fChanged & PHYENT_CHANGED_ORIGIN_Y) { WriteCoord(MSG_ENTITY, origin[1]); } if (fChanged & PHYENT_CHANGED_ORIGIN_Z) { WriteCoord(MSG_ENTITY, origin[2]); } if (fChanged & PHYENT_CHANGED_ANGLES_X) { WriteShort(MSG_ENTITY, angles[0] * 32767 / 360); } if (fChanged & PHYENT_CHANGED_ANGLES_Y) { WriteShort(MSG_ENTITY, angles[1] * 32767 / 360); } if (fChanged & PHYENT_CHANGED_ANGLES_Z) { WriteShort(MSG_ENTITY, angles[2] * 32767 / 360); } if (fChanged & PHYENT_CHANGED_MODELINDEX) { WriteShort(MSG_ENTITY, modelindex); } if (fChanged & PHYENT_CHANGED_SOLID) { WriteByte(MSG_ENTITY, solid); } if (fChanged & PHYENT_CHANGED_MOVETYPE) { WriteByte(MSG_ENTITY, movetype); } if (fChanged & PHYENT_CHANGED_SIZE) { WriteCoord(MSG_ENTITY, mins[0]); WriteCoord(MSG_ENTITY, mins[1]); WriteCoord(MSG_ENTITY, mins[2]); WriteCoord(MSG_ENTITY, maxs[0]); WriteCoord(MSG_ENTITY, maxs[1]); WriteCoord(MSG_ENTITY, maxs[2]); } if (fChanged & PHYENT_CHANGED_FRAME) { WriteByte(MSG_ENTITY, frame); WriteByte(MSG_ENTITY, frame1time); } if (fChanged & PHYENT_CHANGED_SKIN) { WriteByte(MSG_ENTITY, skin + 128); } if (fChanged & PHYENT_CHANGED_EFFECTS) { WriteFloat(MSG_ENTITY, effects); } if (fChanged & PHYENT_CHANGED_BODY) { WriteByte(MSG_ENTITY, m_iBody); } if (fChanged & PHYENT_CHANGED_SCALE) { WriteFloat(MSG_ENTITY, scale); } if (fChanged & PHYENT_CHANGED_VELOCITY) { WriteFloat(MSG_ENTITY, velocity[0]); WriteFloat(MSG_ENTITY, velocity[1]); WriteFloat(MSG_ENTITY, velocity[2]); } if (fChanged & PHYENT_CHANGED_RENDERMODE) { WriteByte(MSG_ENTITY, m_iRenderMode); WriteByte(MSG_ENTITY, m_iRenderFX); } if (fChanged & PHYENT_CHANGED_RENDERCOLOR) { WriteFloat(MSG_ENTITY, m_vecRenderColor[0]); WriteFloat(MSG_ENTITY, m_vecRenderColor[1]); WriteFloat(MSG_ENTITY, m_vecRenderColor[2]); WriteFloat(MSG_ENTITY, glowmod[0]); WriteFloat(MSG_ENTITY, glowmod[1]); WriteFloat(MSG_ENTITY, glowmod[2]); } if (fChanged & PHYENT_CHANGED_RENDERAMT) { WriteFloat(MSG_ENTITY, m_flRenderAmt); } return (1); } #else /* ============ NSPhysicsEntity::ReceiveEntity ============ */ void NSPhysicsEntity::ReceiveEntity(float flNew, float flChanged) { if (flChanged & PHYENT_CHANGED_ORIGIN_X) { origin[0] = readcoord(); } if (flChanged & PHYENT_CHANGED_ORIGIN_Y) { origin[1] = readcoord(); } if (flChanged & PHYENT_CHANGED_ORIGIN_Z) { origin[2] = readcoord(); } if (flChanged & PHYENT_CHANGED_ANGLES_X) { angles[0] = readshort() / (32767 / 360); } if (flChanged & PHYENT_CHANGED_ANGLES_Y) { angles[1] = readshort() / (32767 / 360); } if (flChanged & PHYENT_CHANGED_ANGLES_Z) { angles[2] = readshort() / (32767 / 360); } if (flChanged & PHYENT_CHANGED_MODELINDEX) { setmodelindex(this, readshort()); } if (flChanged & PHYENT_CHANGED_SOLID) { solid = readbyte(); } if (flChanged & PHYENT_CHANGED_MOVETYPE) { movetype = readbyte(); if (movetype == MOVETYPE_PHYSICS) { movetype = MOVETYPE_NONE; } } if (flChanged & PHYENT_CHANGED_SIZE) { mins[0] = readcoord(); mins[1] = readcoord(); mins[2] = readcoord(); maxs[0] = readcoord(); maxs[1] = readcoord(); maxs[2] = readcoord(); } if (flChanged & PHYENT_CHANGED_FRAME) { frame = readbyte(); frame1time = frame2time = readbyte(); } if (flChanged & PHYENT_CHANGED_SKIN) { skin = readbyte() - 128; } if (flChanged & PHYENT_CHANGED_EFFECTS) { effects = readfloat(); } if (flChanged & PHYENT_CHANGED_BODY) { m_iBody = readbyte(); setcustomskin(this, "", sprintf("geomset 0 %i\ngeomset 1 %i\n", m_iBody, m_iBody)); } if (flChanged & PHYENT_CHANGED_SCALE) { scale = readfloat(); } if (flChanged & PHYENT_CHANGED_VELOCITY) { velocity[0] = readfloat(); velocity[1] = readfloat(); velocity[2] = readfloat(); } if (flChanged & PHYENT_CHANGED_RENDERMODE) { m_iRenderMode = readbyte(); m_iRenderFX = readbyte(); } if (flChanged & PHYENT_CHANGED_RENDERCOLOR) { m_vecRenderColor[0] = readfloat(); m_vecRenderColor[1] = readfloat(); m_vecRenderColor[2] = readfloat(); glowmod[0] = readfloat(); glowmod[1] = readfloat(); glowmod[2] = readfloat(); } if (flChanged & PHYENT_CHANGED_RENDERAMT) { m_flRenderAmt = readfloat(); } if (modelindex) { drawmask = MASK_ENGINE; } else { drawmask = 0; } if (scale == 0.0) scale = 1.0f; if (flChanged & PHYENT_CHANGED_SIZE) setsize(this, mins * scale, maxs * scale); setorigin(this, origin); } #endif void NSPhysicsEntity::PhysicsEnable(void) { if (physics_supported() == TRUE) { SetMovetype(MOVETYPE_PHYSICS); SetSolid(SOLID_PHYSICS_BOX + m_iShape); physics_enable(this, TRUE); } else { SetMovetype(MOVETYPE_BOUNCE); SetSolid(SOLID_CORPSE); } m_iEnabled = TRUE; } void NSPhysicsEntity::PhysicsDisable(void) { if (physics_supported() == TRUE) { physics_enable(this, FALSE); SetMovetype(MOVETYPE_NONE); } else { SetMovetype(MOVETYPE_NONE); SetSolid(SOLID_BBOX); } m_iEnabled = FALSE; } void NSPhysicsEntity::SetMass(float val) { mass = val; } float NSPhysicsEntity::GetMass(void) { return mass; } void NSPhysicsEntity::SetFriction(float val) { friction = val; } float NSPhysicsEntity::GetFriction(void) { return friction; } void NSPhysicsEntity::SetBounceFactor(float val) { bouncefactor = val; } float NSPhysicsEntity::GetBounceFactor(void) { return bouncefactor; } void NSPhysicsEntity::SetBounceStop(float val) { bouncestop = val; } float NSPhysicsEntity::GetBounceStop(void) { return bouncestop; } void NSPhysicsEntity::SetInertia(float val) { m_flInertiaScale = val; } float NSPhysicsEntity::GetInertia(void) { return m_flInertiaScale; } float NSPhysicsEntity::CalculateImpactDamage(int iDamage, int dmgType) { int filter = 0i; /* if we're any of these dmg types, we don't transfer any kinetic energy */ filter |= (dmgType & DMG_BURN); filter |= (dmgType & DMG_ELECTRO); filter |= (dmgType & DMG_SOUND); filter |= (dmgType & DMG_ENERGYBEAM); filter |= (dmgType & DMG_DROWN); filter |= (dmgType & DMG_POISON); filter |= (dmgType & DMG_RADIATION); filter |= (dmgType & DMG_ACID); filter |= (dmgType & DMG_ACID); filter |= (dmgType & DMG_SLOWFREEZE); if (filter == 0i) return (float)iDamage * 100; else return 0.0f; } void NSPhysicsEntity::ApplyForceCenter(vector vecForce) { if (physics_supported() == TRUE) { physics_addforce(this, vecForce, [0,0,0]); } else { velocity = vecForce; } /* make sure touch think is called */ nextthink = time; } void NSPhysicsEntity::ApplyForceOffset(vector vecForce, vector vecOffset) { if (physics_supported() == TRUE) { physics_addforce(this, vecForce, vecOffset); } else { velocity = vecForce; } /* make sure touch think is called */ nextthink = time; } void NSPhysicsEntity::ApplyTorqueCenter(vector vecTorque) { if (physics_supported() == TRUE) physics_addtorque(this, vecTorque * m_flInertiaScale); else { avelocity = vecTorque; velocity = vecTorque; velocity[2] = 96; } /* make sure touch think is called */ nextthink = time; } void NSPhysicsEntity::_TouchThink(void) { if (!m_iEnabled) { return; } if (physics_supported() == FALSE) { /* let players collide */ dimension_solid = 255; dimension_hit = 255; tracebox(origin, mins, maxs, origin, FALSE, this); /* stuck */ if (trace_startsolid) { if (trace_ent.flags & FL_CLIENT) { if (trace_ent.absmin[2] < absmax[2]) { PhysicsEnable(); makevectors(vectoangles(origin - trace_ent.origin)); ApplyTorqueCenter(v_forward * 240); } else { PhysicsDisable(); velocity = [0,0,0]; } } } } /* If we barely move, disable the physics simulator */ if (vlen(velocity) <= 1) { if (m_iEnabled) { PhysicsDisable(); velocity = [0,0,0]; avelocity = [0,0,0]; } if (physics_supported() == FALSE) { vector wantangle; vector newangle; wantangle[0] = (int)((angles[0] + 45) / 90) * 90; wantangle[1] = angles[1]; wantangle[2] = (int)((angles[2] + 45) / 90) * 90; makevectors(angles); angles = v_forward; makevectors(wantangle); newangle[0] = Math_Lerp(angles[0], v_forward[0], frametime * 5.0f); newangle[1] = Math_Lerp(angles[1], v_forward[1], frametime * 5.0f); newangle[2] = Math_Lerp(angles[2], v_forward[2], frametime * 5.0f); angles = vectoangles(newangle); } } if (physics_supported() == FALSE) { /* don't let players collide */ dimension_solid = 1; dimension_hit = 1; } /* continue testing next frame */ nextthink = time; effects &= ~EF_NOSHADOW; } #ifdef SERVER void NSPhysicsEntity::Pain(void) { float force; if (m_iFlags & BPHY_NODMGPUSH) return; PhysicsEnable(); makevectors(vectoangles(origin - trace_endpos)); force = CalculateImpactDamage(g_dmg_iDamage, g_dmg_iFlags); if (force > 0.0f) ApplyForceOffset(v_forward * force, origin - trace_endpos); /* if we don't have prop data, don't consider death */ if (HasPropData() == false) health = 10000; /* make sure touch think is called */ nextthink = time; } void NSPhysicsEntity::Death(void) { Pain(); super::Death(); if (takedamage != DAMAGE_YES) { takedamage = (DAMAGE_YES); } health = 1000; /* make sure touch think is called */ nextthink = time; } #endif void NSPhysicsEntity::Respawn(void) { SetMovetype(MOVETYPE_PHYSICS); SetSolid(SOLID_PHYSICS_BOX + m_iShape); SetModel(GetSpawnModel()); geomtype = GEOMTYPE_TRIMESH; #ifdef SERVER SetTakedamage(DAMAGE_YES); #endif #ifndef ODE_MODE PhysicsDisable(); SetFriction(4.0f); SetBounceFactor(0.05f); SetMass(1.0f); #else PhysicsDisable(); SetMass(1.0f); SetFriction(1.0f); SetBounceFactor(0.1f); #endif SetOrigin(GetSpawnOrigin()); if (physics_supported() == FALSE) { /* don't let players collide */ dimension_solid = 1; dimension_hit = 1; } think = _TouchThink; nextthink = time + 0.1f; effects &= ~EF_NOSHADOW; #ifdef SERVER if (HasPropData()) { health = GetPropData(PROPINFO_HEALTH); } else { health = 100000; } #endif } void NSPhysicsEntity::SpawnKey(string strKey, string strValue) { switch (strKey) { case "physmodel": m_iShape = stoi(strValue); if (m_iShape > PHYSM_CYLINDER) m_iShape = 0; break; case "massscale": mass = stof(strValue); break; case "inertiascale": m_flInertiaScale = stof(strValue); break; case "physdamagescale": break; case "material": m_iMaterial = stof(strValue); break; case "nodamageforces": if (strValue == "1") m_iFlags |= BPHY_NODMGPUSH; break; case "Damagetype": if (strValue == "1") m_iFlags |= BPHY_SHARP; break; default: super::SpawnKey(strKey, strValue); break; } } #ifdef CLIENT void NSPhysicsEntity_ReadEntity(bool new) { float fl; NSPhysicsEntity rend = (NSPhysicsEntity)self; if (new) { spawnfunc_NSPhysicsEntity(); } fl = readfloat(); rend.ReceiveEntity(new, fl); //print(sprintf("physics ent update: %d %x %d %v\n", self.entnum, fl, self.origin, vlen(self.velocity))); } #endif