prey-sdk/examples/03 - New Entities/healthzone/game_zone.cpp
2006-10-09 00:00:00 +00:00

1223 lines
38 KiB
C++

#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_SetGravityVector("setgravity", "v");
const idEventDef EV_SetGravityFactor("setgravityfactor", "f");
const idEventDef EV_DeactivateZone("<deactivatezone>", NULL);
//-----------------------------------------------------------------------
//
// hhZone
//
// NOTE: You do not get a EntityLeaving() callback for entities that are
// removed. Could possibly use hhSafeEntitys to tell when they are removed
// but what's the point of calling EntityLeaving() with an invalid pointer.
//-----------------------------------------------------------------------
ABSTRACT_DECLARATION(hhTrigger, hhZone)
EVENT( EV_DeactivateZone, hhZone::Event_TurnOff )
EVENT( EV_Enable, hhZone::Event_Enable )
EVENT( EV_Disable, hhZone::Event_Disable )
EVENT( EV_Touch, hhZone::Event_Touch )
END_CLASS
//NOTE: If this works, this entity can cease inheriting from trigger, just need to take the
// tracemodel creation logic, isSimpleBox variable, make our own enable/disable functions
// touch goes away, triggeraction goes away, much simpler interface
#define ZONES_ALWAYS_ACTIVE 1 // testing: want to be able to use dormancy to turn off --pdm
void hhZone::Spawn(void) {
slop = 0.0f; // Extra slop for bounds check
#if !ZONES_ALWAYS_ACTIVE
fl.neverDormant = true;
#endif
#if ZONES_ALWAYS_ACTIVE
fl.neverDormant = false;
BecomeActive(TH_THINK);
bActive = true;
bEnabled = true;
#endif
}
void hhZone::Save(idSaveGame *savefile) const {
savefile->WriteInt(zoneList.Num()); // idList<int>
for (int i=0; i<zoneList.Num(); i++) {
savefile->WriteInt(zoneList[i]);
}
savefile->WriteFloat(slop);
}
void hhZone::Restore( idRestoreGame *savefile ) {
int num;
zoneList.Clear(); // idList<int>
savefile->ReadInt(num);
zoneList.SetNum(num);
for (int i=0; i<num; i++) {
savefile->ReadInt(zoneList[i]);
}
savefile->ReadFloat(slop);
}
bool hhZone::ValidEntity(idEntity *ent) {
return (ent && ent!=this &&
ent->GetPhysics() &&
!ent->GetPhysics()->IsType(idPhysics_Static::Type) &&
ent->GetPhysics()->GetContents() != 0);
}
void hhZone::Empty() {
}
bool hhZone::ContainsEntityOfType(const idTypeInfo &t) {
idEntity *touch[ MAX_GENTITIES ];
idBounds clipBounds;
clipBounds.FromTransformedBounds( GetPhysics()->GetBounds(), GetOrigin(), GetAxis() );
int num = gameLocal.clip.EntitiesTouchingBounds( clipBounds.Expand(slop), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES );
for (int i=0; i<num; i++) {
if (touch[i] && touch[i]->IsType(t)) {
gameLocal.Printf("Contains a %s\n", t.classname);
return true;
}
}
gameLocal.Printf("Doesn't contain a %s\n", t.classname);
return false;
}
bool PointerInList(idEntity *target, idEntity **list, int num) {
for (int j=0; j < num; j++ ) {
if (list[j] == target) {
return true;
}
}
return false;
}
void hhZone::ResetZoneList() {
// Call Leaving for anything previously entered
idEntity *previouslyInZone;
for (int i=0; i < zoneList.Num(); i++ ) {
previouslyInZone = gameLocal.entities[zoneList[i]];
if (previouslyInZone) {
EntityLeaving(previouslyInZone);
}
}
zoneList.Clear();
}
void hhZone::TriggerAction(idEntity *activator) {
CancelEvents(&EV_DeactivateZone);
// Turn on until all encroachers are gone
BecomeActive(TH_THINK);
}
void hhZone::ApplyToEncroachers() {
idEntity *touch[ MAX_GENTITIES ];
idEntity *previouslyInZone;
idEntity *encroacher;
int i, num;
idBounds clipBounds;
clipBounds.FromTransformedBounds( GetPhysics()->GetBounds(), GetOrigin(), GetAxis() );
// Find all encroachers
if (isSimpleBox) {
num = gameLocal.clip.EntitiesTouchingBounds( clipBounds.Expand(slop), MASK_SHOT_BOUNDINGBOX | CONTENTS_PROJECTILE | CONTENTS_TRIGGER, touch, MAX_GENTITIES ); // CONTENTS_TRIGGER for walkthrough movables
}
else {
num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, MASK_SHOT_BOUNDINGBOX | CONTENTS_TRIGGER );
}
// for anything previously applied, but no longer encroaching, call EntityLeaving()
for (i=0; i < zoneList.Num(); i++ ) {
previouslyInZone = gameLocal.entities[zoneList[i]];
if (previouslyInZone) {
if (!ValidEntity(previouslyInZone) || !PointerInList(previouslyInZone, touch, num)) {
// We've applied before, but it's no longer encroaching
EntityLeaving(previouslyInZone);
// NOTE: Rather than removing and dealing with the list shifting, we reconstruct the list
// from the touch list later
}
}
}
// Check touch list for any newly entered encroachers
for (i = 0; i < num; i++ ) {
encroacher = touch[i];
if (ValidEntity(encroacher)) {
if (zoneList.FindIndex(encroacher->entityNumber) == -1) {
EntityEntered(encroacher);
}
}
}
// Call all encroachers and rebuild list
zoneList.Clear(); //fixme: could make a version of clear() that doesn't deallocate the memory
for (i = 0; i < num; i++ ) {
encroacher = touch[i];
if (ValidEntity(encroacher)) {
zoneList.Append(encroacher->entityNumber);
EntityEncroaching(encroacher);
}
}
// Deactivate if no encroachers left
if (!zoneList.Num()) {
Empty();
#if !ZONES_ALWAYS_ACTIVE
PostEventMS(&EV_DeactivateZone, 0);
#endif
}
}
void hhZone::Think() {
if (thinkFlags & TH_THINK) {
ApplyToEncroachers();
}
}
void hhZone::Event_TurnOff() {
BecomeInactive(TH_THINK);
bActive = false;
}
void hhZone::Event_Enable( void ) {
hhTrigger::Event_Enable();
TriggerAction(this);
}
void hhZone::Event_Disable( void ) {
BecomeInactive(TH_THINK);
ResetZoneList();
hhTrigger::Event_Disable();
}
void hhZone::Event_Touch( idEntity *other, trace_t *trace ) {
CancelEvents(&EV_DeactivateZone);
// Turn on until all encroachers are gone
BecomeActive(TH_THINK);
bActive = true;
}
//healthzone begin
//let's specify our new class with its parent class. this hooks us in with the rest of
//the idClasses.
CLASS_DECLARATION(hhZone, hhHealthZone)
END_CLASS
//our spawn function is called in sequence with spawn functions for any parent classes.
//in here, we can optionally parse any extra spawn args and do other on-spawn logic.
void hhHealthZone::Spawn() {
regenAmount = spawnArgs.GetInt("regenAmount"); //the amount to regen each entity, each frame
}
//write all of our necessary state data for savegames
void hhHealthZone::Save( idSaveGame *savefile ) const {
savefile->WriteInt(regenAmount);
}
//loading a savegame, so load state data back in
void hhHealthZone::Restore( idRestoreGame *savefile ) {
savefile->ReadInt(regenAmount);
}
//let's only accept players who aren't dead
bool hhHealthZone::ValidEntity(idEntity *ent) {
if (!ent || !ent->IsType(hhPlayer::Type) || ent->health <= 0) {
return false;
}
return true;
}
//a player is in the zone, let's give him some health
void hhHealthZone::EntityEncroaching(idEntity *ent) {
if ((ent->health+regenAmount) > 100) {
return; //let's not go over 100 health
}
ent->health += regenAmount;
}
//healthzone end
//-----------------------------------------------------------------------
//
// hhTriggerZone
//
// Zone used for precise trigger/untrigger mechanic. Fires trigger once
// upon a valid entity entering, and again when a valid entity leaves. Also,
// optionally calls a function for each entity in the volume each tick.
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhZone, hhTriggerZone)
END_CLASS
void hhTriggerZone::Spawn() {
funcRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("inCallRef") );
}
void hhTriggerZone::Save(idSaveGame *savefile) const {
savefile->WriteStaticObject( funcRefInfo );
}
void hhTriggerZone::Restore( idRestoreGame *savefile ) {
savefile->ReadStaticObject( funcRefInfo );
}
bool hhTriggerZone::ValidEntity(idEntity *ent) {
return (hhZone::ValidEntity(ent) && !IsType(hhProjectile::Type));
}
void hhTriggerZone::EntityEntered(idEntity *ent) {
ActivateTargets(ent);
}
void hhTriggerZone::EntityLeaving(idEntity *ent) {
ActivateTargets(ent);
}
void hhTriggerZone::EntityEncroaching( idEntity *ent ) {
if (funcRefInfo.GetFunction() != NULL) {
funcRefInfo.SetParm_Entity( ent, 0 );
funcRefInfo.Verify();
funcRefInfo.CallFunction( spawnArgs );
}
}
//-----------------------------------------------------------------------
//
// hhGravityZoneBase
//
//-----------------------------------------------------------------------
ABSTRACT_DECLARATION(hhZone, hhGravityZoneBase)
END_CLASS
void hhGravityZoneBase::Spawn(void) {
bReorient = spawnArgs.GetBool("reorient");
bShowVector = spawnArgs.GetBool("showVector");
bKillsMonsters = spawnArgs.GetBool("killmonsters");
//rww - avoid dictionary lookup post-spawn
gravityOriginOffset = vec3_origin;
if (spawnArgs.GetVector("override_origin", gravityOriginOffset.ToString(), gravityOriginOffset)) {
gravityOriginOffset -= GetOrigin();
}
//rww - sync over network
fl.networkSync = true;
}
void hhGravityZoneBase::Save(idSaveGame *savefile) const {
savefile->WriteBool(bReorient);
savefile->WriteBool(bKillsMonsters);
savefile->WriteBool(bShowVector);
savefile->WriteVec3(gravityOriginOffset);
}
void hhGravityZoneBase::Restore( idRestoreGame *savefile ) {
savefile->ReadBool(bReorient);
savefile->ReadBool(bKillsMonsters);
savefile->ReadBool(bShowVector);
savefile->ReadVec3(gravityOriginOffset);
}
//rww - network code
void hhGravityZoneBase::WriteToSnapshot( idBitMsgDelta &msg ) const {
msg.WriteBits(bReorient, 1);
msg.WriteFloat(slop);
}
void hhGravityZoneBase::ReadFromSnapshot( const idBitMsgDelta &msg ) {
bReorient = !!msg.ReadBits(1);
slop = msg.ReadFloat();
}
void hhGravityZoneBase::ClientPredictionThink( void ) {
Think();
}
//rww - end network code
const idVec3 hhGravityZoneBase::GetGravityOrigin() const {
return GetOrigin()+gravityOriginOffset;
}
bool hhGravityZoneBase::ValidEntity(idEntity *ent) {
if (!ent) {
return false;
}
if (ent->fl.ignoreGravityZones) {
return false;
}
if (ent->IsType(hhProjectile::Type) && ent->GetPhysics()->GetGravity() == vec3_origin) {
return false; // Projectiles with zero gravity
}
if (ent->IsType(hhPlayer::Type)) {
hhPlayer *pl = static_cast<hhPlayer*>(ent);
if (pl->noclip) {
return false; // Noclipping players
}
else if (pl->spectating) {
return false; //spectating players
}
else if (gameLocal.isMultiplayer && ent->health <= 0) {
return false; //dead mp players (only the prox ragdoll needs gravity)
}
}
if (ent->IsType(hhVehicle::Type)) {
if (static_cast<hhVehicle*>(ent)->IsNoClipping()) {
return false; // Noclipping vehicles
}
if (ent->IsType(hhShuttle::Type) && static_cast<hhShuttle*>(ent)->IsConsole()) {
return false; // unpiloted shuttles
}
}
if (ent->IsType(hhPortal::Type) ) {
return true; // Portals are always valid entities in zones
}
if (!hhZone::ValidEntity( ent )) {
return false;
}
return true;
}
bool hhGravityZoneBase::TouchingOtherZones(idEntity *ent, bool traceCheck, idVec3 &otherInfluence) { //rww
if (!ent->GetPhysics()) {
return false;
}
bool hitAny = false;
otherInfluence.Zero();
idBounds clipBounds;
idEntity *touch[ MAX_GENTITIES ];
clipBounds.FromTransformedBounds( ent->GetPhysics()->GetBounds(), ent->GetOrigin(), ent->GetAxis() );
int num = gameLocal.clip.EntitiesTouchingBounds( clipBounds, GetPhysics()->GetContents(), touch, MAX_GENTITIES );
for (int i = 0; i < num; i++) {
if (touch[i] && touch[i]->entityNumber != entityNumber && touch[i]->IsType(hhGravityZoneBase::Type)) {
//touching the object, isn't me, and seems to be another gravity zone
bool touchValid = false;
if (traceCheck) { //let's perform a trace from the ent's origin to see which zone is hit first. (this is not an ideal solution, but it works)
trace_t tr;
const int checkContents = GetPhysics()->GetContents();
const idVec3 &start = ent->GetOrigin();
const float testLength = 512.0f;
idVec3 end;
//first trace against the other
end = (touch[i]->GetPhysics()->GetBounds().GetCenter()-start).Normalize()*testLength;
gameLocal.clip.TracePoint(tr, start, end, checkContents, ent);
if (tr.c.entityNum == touch[i]->entityNumber) { //if the trace actually hit the other one
float otherFrac = tr.fraction;
//now trace against me
end = (GetPhysics()->GetBounds().GetCenter()-start).Normalize()*testLength;
gameLocal.clip.TracePoint(tr, start, GetPhysics()->GetBounds().GetCenter(), checkContents, ent);
if (tr.c.entityNum != entityNumber || tr.fraction >= otherFrac) { //if the impact was further away (or same, don't want fighting), i lose.
touchValid = true;
}
}
}
else {
touchValid = true;
}
if (touchValid) {
//accumulate force from other zones
hhGravityZoneBase *zone = static_cast<hhGravityZoneBase *>(touch[i]);
if (zone->isSimpleBox || ent->GetPhysics()->ClipContents(zone->GetPhysics()->GetClipModel())) { //if not simple box perform a clip check
idVec3 grav = zone->GetCurrentGravity(ent->GetOrigin());
hitAny = true;
grav.Normalize();
otherInfluence += grav;
otherInfluence.Normalize();
}
}
}
}
return hitAny;
}
void hhGravityZoneBase::EntityEntered( idEntity *ent ) {
if( ent->RespondsTo(EV_ShouldRemainAlignedToAxial) ) {
ent->ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)false );
}
if( ent->RespondsTo(EV_OrientToGravity) ) {
ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient );
}
}
void hhGravityZoneBase::EntityLeaving( idEntity *ent ) {
if( ent->RespondsTo(EV_ShouldRemainAlignedToAxial) ) {
ent->ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)true );
}
// Instead of reseting gravity here, post a message to do it, so if we are transitioning
// to another gravity zone or wallwalk, there won't be any discontinuities
if (gameLocal.isClient && !ent->fl.clientEvents && !ent->fl.clientEntity && ent->IsType(hhProjectile::Type)) {
ent->fl.clientEvents = true; //hackery to let normal projectiles reset their gravity for prediction
ent->PostEventMS( &EV_ResetGravity, 200 );
ent->fl.clientEvents = false;
}
else {
ent->PostEventMS( &EV_ResetGravity, 200 );
}
}
void hhGravityZoneBase::EntityEncroaching( idEntity *ent ) {
// Cancel any pending gravity resets from other zones
ent->CancelEvents( &EV_ResetGravity );
idVec3 curGravity = GetCurrentGravity( ent->GetOrigin() );
idVec3 otherGravity;
if (TouchingOtherZones(ent, false, otherGravity)) { //factor in gravity for all other zones being touched to avoid back-and-forth behaviour
float l = curGravity.Normalize();
curGravity += otherGravity;
curGravity *= l*0.5f;
}
if (ent->GetPhysics()->IsAtRest() && ent->GetGravity() != curGravity) {
ent->SetGravity( curGravity );
ent->GetPhysics()->Activate();
}
else {
ent->SetGravity( curGravity );
}
if (ent->IsType( hhMonsterAI::Type )) {
if (bKillsMonsters && ent->health > 0 &&
!static_cast<hhMonsterAI*>(ent)->OverrideKilledByGravityZones() &&
!ent->IsType(hhCrawler::Type) &&
(idMath::Fabs(curGravity.x) > 0.01f || idMath::Fabs(curGravity.y) > 0.01f || curGravity.z >= 0.0f) &&
static_cast<hhMonsterAI*>(ent)->IsActive() ) {
const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage");
ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0);
}
}
}
//-----------------------------------------------------------------------
//
// hhGravityZone
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhGravityZoneBase, hhGravityZone)
EVENT( EV_SetGravityVector, hhGravityZone::Event_SetNewGravity )
END_CLASS
void hhGravityZone::Spawn(void) {
zeroGravOnChange = spawnArgs.GetBool("zeroGravOnChange");
idVec3 startGravity( spawnArgs.GetVector("gravity") );
interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime"));
gravityInterpolator.Init( gameLocal.time, 0, startGravity, startGravity );
if (startGravity != gameLocal.GetGravity()) {
if (!gameLocal.isMultiplayer) { //don't play sound in mp
StartSound("snd_gravity_loop_on", SND_CHANNEL_MISC1, 0, true);
}
}
}
void hhGravityZone::Save(idSaveGame *savefile) const {
savefile->WriteFloat( gravityInterpolator.GetStartTime() ); // idInterpolate<idVec3>
savefile->WriteFloat( gravityInterpolator.GetDuration() );
savefile->WriteVec3( gravityInterpolator.GetStartValue() );
savefile->WriteVec3( gravityInterpolator.GetEndValue() );
savefile->WriteInt(interpolationTime);
savefile->WriteBool(zeroGravOnChange);
}
void hhGravityZone::Restore( idRestoreGame *savefile ) {
float set;
idVec3 vec;
savefile->ReadFloat( set ); // idInterpolate<idVec3>
gravityInterpolator.SetStartTime( set );
savefile->ReadFloat( set );
gravityInterpolator.SetDuration( set );
savefile->ReadVec3( vec );
gravityInterpolator.SetStartValue( vec );
savefile->ReadVec3( vec );
gravityInterpolator.SetEndValue( vec );
savefile->ReadInt(interpolationTime);
savefile->ReadBool(zeroGravOnChange);
}
void hhGravityZone::Think() {
hhGravityZoneBase::Think();
if (thinkFlags & TH_THINK) {
if (bShowVector) {
gameRenderWorld->DebugArrow(colorGreen, renderEntity.origin, renderEntity.origin + GetCurrentGravity(vec3_origin), 10);
}
}
}
const idVec3 hhGravityZone::GetDestinationGravity() const {
return gravityInterpolator.GetEndValue();
}
const idVec3 hhGravityZone::GetCurrentGravity(const idVec3 &location) const {
return gravityInterpolator.GetCurrentValue( gameLocal.time );
}
void hhGravityZone::SetGravityOnZone( idVec3 &newGravity ) {
idVec3 startGrav;
if (!gameLocal.isMultiplayer) { //don't play sound in mp
if ( newGravity.Compare(gameLocal.GetGravity(), VECTOR_EPSILON) ) {
StartSound("snd_gravity_off", SND_CHANNEL_ANY);
StopSound(SND_CHANNEL_MISC1, true);
StartSound("snd_gravity_loop_off", SND_CHANNEL_MISC1, 0, true);
}
else {
StartSound("snd_gravity_on", SND_CHANNEL_ANY);
StopSound(SND_CHANNEL_MISC1, true);
StartSound("snd_gravity_loop_on", SND_CHANNEL_MISC1, 0, true);
}
}
if ( zeroGravOnChange ) { // nla
startGrav = vec3_origin;
}
else {
startGrav = GetCurrentGravity(vec3_origin);
}
// Interpolate to new gravity
gravityInterpolator.Init( gameLocal.time, interpolationTime, startGrav, newGravity );
}
//rww - network code
void hhGravityZone::WriteToSnapshot( idBitMsgDelta &msg ) const {
hhGravityZoneBase::WriteToSnapshot(msg);
msg.WriteFloat(gravityInterpolator.GetStartTime());
msg.WriteFloat(gravityInterpolator.GetDuration());
idVec3 vecStart = gravityInterpolator.GetStartValue();
msg.WriteFloat(vecStart.x);
msg.WriteFloat(vecStart.y);
msg.WriteFloat(vecStart.z);
idVec3 vecEnd = gravityInterpolator.GetEndValue();
msg.WriteDeltaFloat(vecStart.x, vecEnd.x);
msg.WriteDeltaFloat(vecStart.y, vecEnd.y);
msg.WriteDeltaFloat(vecStart.z, vecEnd.z);
}
void hhGravityZone::ReadFromSnapshot( const idBitMsgDelta &msg ) {
hhGravityZoneBase::ReadFromSnapshot(msg);
gravityInterpolator.SetStartTime(msg.ReadFloat());
gravityInterpolator.SetDuration(msg.ReadFloat());
idVec3 vecStart;
vecStart.x = msg.ReadFloat();
vecStart.y = msg.ReadFloat();
vecStart.z = msg.ReadFloat();
gravityInterpolator.SetStartValue(vecStart);
idVec3 vecEnd;
vecEnd.x = msg.ReadDeltaFloat(vecStart.x);
vecEnd.y = msg.ReadDeltaFloat(vecStart.y);
vecEnd.z = msg.ReadDeltaFloat(vecStart.z);
gravityInterpolator.SetEndValue(vecEnd);
}
void hhGravityZone::ClientPredictionThink( void ) {
hhGravityZoneBase::ClientPredictionThink();
}
//rww - end network code
void hhGravityZone::Event_SetNewGravity( idVec3 &newGravity ) {
SetGravityOnZone( newGravity );
}
//-----------------------------------------------------------------------
//
// hhGravityZoneInward
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhGravityZoneBase, hhGravityZoneInward)
EVENT( EV_SetGravityFactor, hhGravityZoneInward::Event_SetNewGravityFactor )
END_CLASS
void hhGravityZoneInward::Spawn(void) {
float startFactor = spawnArgs.GetFloat("factor", "50000");
monsterGravityFactor = spawnArgs.GetFloat("monsterGravFactor", "1");
interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime"));
factorInterpolator.Init( gameLocal.time, 0, startFactor, startFactor );
}
void hhGravityZoneInward::Save(idSaveGame *savefile) const {
savefile->WriteFloat( factorInterpolator.GetStartTime() ); // idInterpolate<float>
savefile->WriteFloat( factorInterpolator.GetDuration() );
savefile->WriteFloat( factorInterpolator.GetStartValue() );
savefile->WriteFloat( factorInterpolator.GetEndValue() );
savefile->WriteInt(interpolationTime);
savefile->WriteFloat(monsterGravityFactor);
}
void hhGravityZoneInward::Restore( idRestoreGame *savefile ) {
float set;
savefile->ReadFloat( set ); // idInterpolate<float>
factorInterpolator.SetStartTime( set );
savefile->ReadFloat( set );
factorInterpolator.SetDuration( set );
savefile->ReadFloat( set );
factorInterpolator.SetStartValue(set);
savefile->ReadFloat( set );
factorInterpolator.SetEndValue( set );
savefile->ReadInt(interpolationTime);
savefile->ReadFloat(monsterGravityFactor);
}
void hhGravityZoneInward::EntityEntered(idEntity *ent) {
hhGravityZoneBase::EntityEntered(ent);
if ( ent && ent->IsType( hhMonsterAI::Type ) ) {
static_cast<hhMonsterAI*>(ent)->GravClipModelAxis( true );
}
// Disallow slope checking, it makes us stutter when walking on convex surfaces
// aob - commented this because it allows the player to walk up vertical walls while in inward gravity zone
//Didn't see any studdering when thia was commented out. Do we still need it?
//if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) {
// static_cast<hhPhysics_Player*>(ent->GetPhysics())->SetSlopeCheck(false);
//}
//now done constantly while in a gravity zone
/*
if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) {
static_cast<hhPhysics_Player*>(ent->GetPhysics())->SetInwardGravity(1);
}
*/
}
void hhGravityZoneInward::EntityLeaving(idEntity *ent) {
hhGravityZoneBase::EntityLeaving(ent);
if ( ent && ent->IsType( hhMonsterAI::Type ) ) {
static_cast<hhMonsterAI*>(ent)->GravClipModelAxis( false );
}
// Re-enable slope checking
// aob - commented this because it allows the player to walk up vertical walls while in inward gravity zone
//Didn't see any studdering when thia was commented out. Do we still need it?
//if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) {
// static_cast<hhPhysics_Player*>(ent->GetPhysics())->SetSlopeCheck(true);
//}
if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) {
static_cast<hhPhysics_Player*>(ent->GetPhysics())->SetInwardGravity(0);
}
}
// This is actually called each tick if for entities inside
void hhGravityZoneInward::EntityEncroaching( idEntity *ent ) {
// Cancel any pending gravity resets from other zones
ent->CancelEvents( &EV_ResetGravity );
idVec3 curGravity = GetCurrentGravity( ent->GetOrigin() );
idVec3 otherGravity;
if (TouchingOtherZones(ent, false, otherGravity)) { //factor in gravity for all other zones being touched to avoid back-and-forth behaviour
float l = curGravity.Normalize();
curGravity += otherGravity;
curGravity *= l*0.5f;
}
if (ent->GetPhysics()->IsAtRest() && ent->GetGravity() != curGravity) {
ent->SetGravity( curGravity );
ent->GetPhysics()->Activate();
}
else {
ent->SetGravity( curGravity );
}
if (ent->IsType( idAI::Type )) {
if (bKillsMonsters && ent->health > 0 &&
!ent->IsType(hhCrawler::Type) &&
(curGravity.x != 0.0f || curGravity.y != 0.0f || curGravity.z >= 0.0f) &&
!static_cast<hhMonsterAI*>(ent)->OverrideKilledByGravityZones() &&
static_cast<idAI*>(ent)->IsActive() ) {
const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage");
ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0);
}
}
//rww
else if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) {
static_cast<hhPhysics_Player*>(ent->GetPhysics())->SetInwardGravity(1);
}
if( ent->IsType(idAI::Type) && ent->health > 0 ) {
ent->GetPhysics()->SetGravity( ent->GetPhysics()->GetGravity() * monsterGravityFactor );
ent->GetPhysics()->Activate();
}
if( bShowVector ) {
hhUtils::DebugCross( colorBlue, GetOrigin(), 100, 10 );
idVec3 newGrav = GetCurrentGravity( ent->GetOrigin() );
gameRenderWorld->DebugArrow( colorGreen, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->origin + newGrav, 10 );
}
}
const idVec3 hhGravityZoneInward::GetCurrentGravity( const idVec3 &location ) const {
idVec3 grav;
idVec3 origin = GetGravityOrigin();
float factor = factorInterpolator.GetCurrentValue( gameLocal.GetTime() );
idVec3 inward = origin - location;
inward.Normalize();
grav = inward * DEFAULT_GRAVITY * factor;
return grav;
}
void hhGravityZoneInward::Event_SetNewGravityFactor( float newFactor ) {
// Interpolate to new gravity factor
float curFactor = factorInterpolator.GetCurrentValue( gameLocal.GetTime() );
factorInterpolator.Init( gameLocal.GetTime(), interpolationTime, curFactor, newFactor );
}
//-----------------------------------------------------------------------
//
// hhAIWallwalkZone
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhGravityZone, hhAIWallwalkZone)
END_CLASS
bool hhAIWallwalkZone::ValidEntity(idEntity *ent) {
// allow AI that isnt dead
return ent->IsType(idAI::Type) && ent->health > 0;
}
void hhAIWallwalkZone::EntityEncroaching( idEntity *ent ) {
// Cancel any pending gravity resets from other zones
ent->CancelEvents( &EV_ResetGravity );
trace_t TraceInfo;
gameLocal.clip.TracePoint(TraceInfo, ent->GetOrigin(), ent->GetOrigin() + (idVec3(0,0,-300)*ent->GetRenderEntity()->axis), ent->GetPhysics()->GetClipMask(), ent);
if( TraceInfo.fraction < 1.0f ) { // && ent->health > 0 ) {
ent->SetGravity( -TraceInfo.c.normal );
ent->GetPhysics()->Activate();
}
}
//-----------------------------------------------------------------------
//
// hhGravityZoneSinkhole
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhGravityZoneInward, hhGravityZoneSinkhole)
EVENT( EV_SetGravityFactor, hhGravityZoneSinkhole::Event_SetNewGravityFactor )
END_CLASS
void hhGravityZoneSinkhole::Spawn(void) {
bReorient = false;
maxMagnitude = spawnArgs.GetFloat("maxMagnitude", "10000");
minMagnitude = spawnArgs.GetFloat("minMagnitude", "0");
}
void hhGravityZoneSinkhole::Save(idSaveGame *savefile) const {
savefile->WriteFloat(maxMagnitude);
savefile->WriteFloat(minMagnitude);
}
void hhGravityZoneSinkhole::Restore( idRestoreGame *savefile ) {
savefile->ReadFloat(maxMagnitude);
savefile->ReadFloat(minMagnitude);
}
// Still have this in case we want to do something mass based
const idVec3 hhGravityZoneSinkhole::GetCurrentGravityEntity(const idEntity *ent) const {
idVec3 grav = vec3_origin;
if (ent) {
// precalc mass product / G as a constant and expose that as the fudge factor
idVec3 origin = GetGravityOrigin();
float factor = factorInterpolator.GetCurrentValue( gameLocal.time );
idVec3 inward = origin - ent->GetOrigin();
float distance = inward.Normalize();
float distanceSquared = distance*distance;
// Some different gravitational fields
// float gravMag = (mass * ent->GetPhysics()->GetMass() * GRAVITATIONAL_CONSTANT) / distanceSquared;
// float gravMag = factor*factor / distanceSquared; // Inverse squared distance
// float gravMag = factor / sqrt(distance); // Inverse sqrt distance
// float gravMag = factor * sqrt(distance); // sqrt distance
float gravMag = factor*factor / 2 + 0.2f * distanceSquared; // Inverse squared distance
//gameLocal.Printf("factor=%.0f gravity magnitude=%.2f\n", factor, gravMag);
gravMag = hhMath::ClampFloat(minMagnitude, maxMagnitude, gravMag); // This will cut off extremely large forces
grav = inward * gravMag;
}
return grav;
}
const idVec3 hhGravityZoneSinkhole::GetCurrentGravity(const idVec3 &location) const {
idVec3 grav;
// precalc mass product / G as a constant and expose that as the fudge factor
idVec3 origin = GetGravityOrigin();
float factor = factorInterpolator.GetCurrentValue( gameLocal.time );
idVec3 inward = origin - location;
float distance = inward.Normalize();
float distanceSquared = distance*distance;
// Some different gravitational fields
float gravMag = factor*factor / 2 + 0.2f * distanceSquared; // Inverse squared distance
gravMag = hhMath::ClampFloat(minMagnitude, maxMagnitude, gravMag); // This will cut off extremely large forces
grav = inward * gravMag;
return grav;
}
void hhGravityZoneSinkhole::Event_SetNewGravityFactor( float newFactor ) {
// Interpolate to new gravity factor
float curFactor = factorInterpolator.GetCurrentValue(gameLocal.time);
factorInterpolator.Init( gameLocal.time, interpolationTime, curFactor, newFactor );
}
//-----------------------------------------------------------------------
//
// hhVelocityZone
//
//-----------------------------------------------------------------------
const idEventDef EV_SetVelocityVector("setvelocity", "v");
CLASS_DECLARATION(hhZone, hhVelocityZone)
EVENT( EV_SetVelocityVector, hhVelocityZone::Event_SetNewVelocity )
END_CLASS
void hhVelocityZone::Spawn(void) {
bReorient = spawnArgs.GetBool("reorient");
interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime"));
idVec3 startVelocity = spawnArgs.GetVector("velocity");
//slop = 25.0f; // we use a slightly larger bounds to catch things that are rotated by bReorient
bShowVector = spawnArgs.GetBool("showVector");
bKillsMonsters = spawnArgs.GetBool("killmonsters");
velocityInterpolator.Init(gameLocal.time, 0, startVelocity, startVelocity);
}
void hhVelocityZone::Save(idSaveGame *savefile) const {
savefile->WriteFloat( velocityInterpolator.GetStartTime() ); // idInterpolate<idVec3>
savefile->WriteFloat( velocityInterpolator.GetDuration() );
savefile->WriteVec3( velocityInterpolator.GetStartValue() );
savefile->WriteVec3( velocityInterpolator.GetEndValue() );
savefile->WriteBool(bKillsMonsters);
savefile->WriteBool(bReorient);
savefile->WriteBool(bShowVector);
savefile->WriteInt(interpolationTime);
}
void hhVelocityZone::Restore( idRestoreGame *savefile ) {
float set;
idVec3 vec;
savefile->ReadFloat( set ); // idInterpolate<idVec3>
velocityInterpolator.SetStartTime( set );
savefile->ReadFloat( set );
velocityInterpolator.SetDuration( set );
savefile->ReadVec3( vec );
velocityInterpolator.SetStartValue( vec );
savefile->ReadVec3( vec );
velocityInterpolator.SetEndValue( vec );
savefile->ReadBool(bKillsMonsters);
savefile->ReadBool(bReorient);
savefile->ReadBool(bShowVector);
savefile->ReadInt(interpolationTime);
}
void hhVelocityZone::EntityLeaving(idEntity *ent) {
ent->GetPhysics()->SetLinearVelocity(idVec3(0, 0, 0));
if( ent->RespondsTo(EV_OrientToGravity) ) {
ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient );
}
}
void hhVelocityZone::EntityEncroaching(idEntity *ent) {
idVec3 baseVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time);
idVec3 baseVelocityDirection = baseVelocity;
baseVelocityDirection.Normalize();
idVec3 curVelocity;
curVelocity = ent->GetPhysics()->GetLinearVelocity();
curVelocity.ProjectOntoPlane(baseVelocityDirection);
ent->GetPhysics()->SetLinearVelocity( curVelocity + baseVelocity );
if( ent->RespondsTo(EV_OrientToGravity) ) {
ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient );
}
else if (ent->IsType( idAI::Type )) {
if (bKillsMonsters && ent->health > 0 &&
!static_cast<idAI*>(ent)->IsFlying()) {
const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage");
ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0);
}
}
}
void hhVelocityZone::Think() {
hhZone::Think();
if (thinkFlags & TH_THINK) {
if (bShowVector) {
idVec3 baseVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time);
gameRenderWorld->DebugArrow(colorGreen, renderEntity.origin, renderEntity.origin+baseVelocity, 10);
}
}
}
void hhVelocityZone::Event_SetNewVelocity( idVec3 &newVelocity ) {
// Interpolate to new velocity
idVec3 currentVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time);
velocityInterpolator.Init(gameLocal.time, interpolationTime, currentVelocity, newVelocity);
}
//-----------------------------------------------------------------------
//
// hhShuttleRecharge
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhZone, hhShuttleRecharge)
END_CLASS
void hhShuttleRecharge::Spawn(void) {
amountHealth = spawnArgs.GetInt("amounthealth");
amountPower = spawnArgs.GetInt("amountpower");
}
void hhShuttleRecharge::Save(idSaveGame *savefile) const {
savefile->WriteInt(amountHealth);
savefile->WriteInt(amountPower);
}
void hhShuttleRecharge::Restore( idRestoreGame *savefile ) {
savefile->ReadInt(amountHealth);
savefile->ReadInt(amountPower);
}
bool hhShuttleRecharge::ValidEntity(idEntity *ent) {
return ent && ent->IsType(hhShuttle::Type);
}
void hhShuttleRecharge::EntityEntered(idEntity *ent) {
//static_cast<hhShuttle *>(ent)->SetRecharging(true);
}
void hhShuttleRecharge::EntityLeaving(idEntity *ent) {
//static_cast<hhShuttle *>(ent)->SetRecharging(false);
}
void hhShuttleRecharge::EntityEncroaching(idEntity *ent) {
if (ent->IsType(hhVehicle::Type)) {
hhVehicle *vehicle = static_cast<hhVehicle *>(ent);
//HUMANHEAD bjk PCF (4-27-06) - shuttle recharge was slow
if(USERCMD_HZ == 30) {
vehicle->GiveHealth(2*amountHealth);
vehicle->GivePower(2*amountPower);
} else {
vehicle->GiveHealth(amountHealth);
vehicle->GivePower(amountPower);
}
}
}
//-----------------------------------------------------------------------
//
// hhDockingZone
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhZone, hhDockingZone)
END_CLASS
void hhDockingZone::Spawn(void) {
dock = NULL;
triggerBehavior = TB_PLAYER_MONSTERS_FRIENDLIES; // Allow all actors to trigger it
fl.networkSync = true;
}
void hhDockingZone::Save(idSaveGame *savefile) const {
dock.Save(savefile);
}
void hhDockingZone::Restore( idRestoreGame *savefile ) {
dock.Restore(savefile);
}
void hhDockingZone::RegisterDock(hhDock *d) {
dock = d;
}
bool hhDockingZone::ValidEntity(idEntity *ent) {
if (ent) {
if (dock.IsValid() && dock->ValidEntity(ent)) {
return true;
}
if (ent->IsType(idActor::Type)) { //FIXME: Is this causing the shuttleCount to go wrong
return hhShuttle::ValidPilot(static_cast<idActor*>(ent));
}
}
return false;
}
void hhDockingZone::EntityEncroaching(idEntity *ent) {
if (dock.IsValid()) {
dock->EntityEncroaching(ent);
}
}
void hhDockingZone::EntityEntered(idEntity *ent) {
if (dock.IsValid()) {
dock->EntityEntered(ent);
}
}
void hhDockingZone::EntityLeaving(idEntity *ent) {
if (dock.IsValid()) {
dock->EntityLeaving(ent);
}
}
void hhDockingZone::WriteToSnapshot( idBitMsgDelta &msg ) const {
GetPhysics()->WriteToSnapshot(msg);
msg.WriteBits(dock.GetSpawnId(), 32);
}
void hhDockingZone::ReadFromSnapshot( const idBitMsgDelta &msg ) {
GetPhysics()->ReadFromSnapshot(msg);
dock.SetSpawnId(msg.ReadBits(32));
}
void hhDockingZone::ClientPredictionThink( void ) {
if (!gameLocal.isNewFrame) {
return;
}
Think();
}
//-----------------------------------------------------------------------
//
// hhShuttleDisconnect
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhZone, hhShuttleDisconnect)
END_CLASS
void hhShuttleDisconnect::Spawn(void) {
}
bool hhShuttleDisconnect::ValidEntity(idEntity *ent) {
return ent && ent->IsType(hhShuttle::Type);
}
void hhShuttleDisconnect::EntityEntered(idEntity *ent) {
static_cast<hhShuttle*>(ent)->AllowTractor(false);
}
void hhShuttleDisconnect::EntityEncroaching(idEntity *ent) {
}
void hhShuttleDisconnect::EntityLeaving(idEntity *ent) {
static_cast<hhShuttle*>(ent)->AllowTractor(true);
}
//-----------------------------------------------------------------------
//
// hhShuttleSlingshot
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhZone, hhShuttleSlingshot)
END_CLASS
void hhShuttleSlingshot::Spawn(void) {
}
bool hhShuttleSlingshot::ValidEntity(idEntity *ent) {
return ent && ent->IsType(hhShuttle::Type);
}
void hhShuttleSlingshot::EntityEntered(idEntity *ent) {
}
void hhShuttleSlingshot::EntityEncroaching(idEntity *ent) {
float factor = spawnArgs.GetFloat("BoostFactor");
hhShuttle *shuttle = static_cast<hhShuttle*>(ent);
shuttle->ApplyBoost( 255.0f * factor);
// CJR: Alter the player's view when zooming through a slingshot zone
shuttle->GetPilot()->PostEventMS( &EV_SetOverlayMaterial, 0, spawnArgs.GetString( "mtr_speedView" ), -1, false );
}
void hhShuttleSlingshot::EntityLeaving(idEntity *ent) {
// CJR: Reset the player's view after zooming through a slingshot zone
hhShuttle *shuttle = static_cast<hhShuttle*>(ent);
shuttle->GetPilot()->PostEventMS( &EV_SetOverlayMaterial, 0, "", -1, false );
}
//-----------------------------------------------------------------------
//
// hhRemovalVolume
//
//-----------------------------------------------------------------------
CLASS_DECLARATION(hhZone, hhRemovalVolume)
END_CLASS
void hhRemovalVolume::Spawn(void) {
}
bool hhRemovalVolume::ValidEntity(idEntity *ent) {
return ent && (
ent->IsType(idMoveable::Type) ||
ent->IsType(idItem::Type) ||
ent->IsType(hhAFEntity::Type) ||
ent->IsType(hhAFEntity_WithAttachedHead::Type) ||
(ent->IsType(hhMonsterAI::Type) && ent->health<=0 && !ent->fl.isTractored) );
}
void hhRemovalVolume::EntityEntered(idEntity *ent) {
ent->PostEventMS(&EV_Remove, 0);
}
void hhRemovalVolume::EntityEncroaching(idEntity *ent) {
}
void hhRemovalVolume::EntityLeaving(idEntity *ent) {
}