mirror of
https://github.com/tribes2/engine.git
synced 2026-01-20 03:34:48 +00:00
1281 lines
43 KiB
C++
1281 lines
43 KiB
C++
//-----------------------------------------------------------------------------
|
|
// V12 Engine
|
|
//
|
|
// Copyright (c) 2001 GarageGames.Com
|
|
// Portions Copyright (c) 2001 by Sierra Online, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "console/consoleTypes.h"
|
|
#include "core/bitStream.h"
|
|
#include "math/mathIO.h"
|
|
#include "sim/netConnection.h"
|
|
#include "game/shapeBase.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "game/explosion.h"
|
|
#include "terrain/waterBlock.h"
|
|
#include "sim/decalManager.h"
|
|
#include "game/linearProjectile.h"
|
|
#include "scenegraph/sceneGraph.h"
|
|
#include "game/splash.h"
|
|
|
|
#include "dgl/dgl.h"
|
|
#include "platformWIN32/platformGL.h"
|
|
#include "scenegraph/sceneState.h"
|
|
#include "math/mathUtils.h"
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(LinearProjectileData);
|
|
IMPLEMENT_CO_NETOBJECT_V1(LinearProjectile);
|
|
|
|
const U32 LinearProjectile::csmDecalMask = TerrainObjectType | InteriorObjectType;
|
|
|
|
//--------------------------------------------------------------------------
|
|
//--------------------------------------
|
|
//
|
|
LinearProjectileData::LinearProjectileData()
|
|
{
|
|
dryVelocity = 5.0;
|
|
wetVelocity = 5.0;
|
|
|
|
fizzleTimeMS = 1000;
|
|
lifetimeMS = 1000;
|
|
explodeOnDeath = false;
|
|
|
|
reflectOnWaterImpactAngle = 0.0;
|
|
deflectionOnWaterImpact = 0.0;
|
|
fizzleUnderwaterMS = -1;
|
|
|
|
activateDelayMS = -1;
|
|
|
|
activateSeq = -1;
|
|
maintainSeq = -1;
|
|
|
|
doDynamicClientHits = false;
|
|
}
|
|
|
|
LinearProjectileData::~LinearProjectileData()
|
|
{
|
|
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void LinearProjectileData::initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
addField("dryVelocity", TypeF32, Offset(dryVelocity, LinearProjectileData));
|
|
addField("wetVelocity", TypeF32, Offset(wetVelocity, LinearProjectileData));
|
|
addField("fizzleTimeMS", TypeS32, Offset(fizzleTimeMS, LinearProjectileData));
|
|
addField("lifetimeMS", TypeS32, Offset(lifetimeMS, LinearProjectileData));
|
|
addField("explodeOnDeath", TypeBool, Offset(explodeOnDeath, LinearProjectileData));
|
|
addField("reflectOnWaterImpactAngle", TypeF32, Offset(reflectOnWaterImpactAngle, LinearProjectileData));
|
|
addField("deflectionOnWaterImpact", TypeF32, Offset(deflectionOnWaterImpact, LinearProjectileData));
|
|
|
|
addField("fizzleUnderwaterMS", TypeS32, Offset(fizzleUnderwaterMS, LinearProjectileData));
|
|
addField("activateDelayMS", TypeS32, Offset(activateDelayMS, LinearProjectileData));
|
|
addField("doDynamicClientHits", TypeBool, Offset(doDynamicClientHits, LinearProjectileData));
|
|
}
|
|
|
|
void LinearProjectileData::consoleInit()
|
|
{
|
|
//
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool LinearProjectileData::calculateAim(const Point3F& targetPos,
|
|
const Point3F& targetVel,
|
|
const Point3F& sourcePos,
|
|
const Point3F& sourceVel,
|
|
Point3F* outputVectorMin,
|
|
F32* outputMinTime,
|
|
Point3F* outputVectorMax,
|
|
F32* outputMaxTime)
|
|
{
|
|
// Not implemented: underwater shots...
|
|
|
|
Point3F effTargetPos = targetPos - sourcePos;
|
|
Point3F effTargetVel = targetVel - sourceVel * velInheritFactor;
|
|
|
|
// Without underwater aiming, this is a straightforward law of cosines
|
|
// calculation. We're not being especially efficient here, but hey.
|
|
//
|
|
Point3F normPos = effTargetPos;
|
|
Point3F normVel = effTargetVel;
|
|
if (normPos.isZero() == false)
|
|
normPos.normalize();
|
|
if (normVel.isZero() == false)
|
|
normVel.normalize();
|
|
|
|
F32 a = effTargetVel.lenSquared() - (dryVelocity*dryVelocity);
|
|
F32 b = 2 * effTargetPos.len() * effTargetVel.len() * mDot(normPos, normVel);
|
|
F32 c = effTargetPos.lenSquared();
|
|
|
|
F32 det = b*b - 4*a*c;
|
|
if (det < 0.0) {
|
|
// No solution is possible in the real numbers
|
|
outputVectorMin->set(0, 0, 1);
|
|
outputVectorMax->set(0, 0, 1);
|
|
*outputMinTime = -1;
|
|
*outputMaxTime = -1;
|
|
return false;
|
|
}
|
|
|
|
F32 sol1 = (-b + mSqrt(det)) / (2 * a);
|
|
F32 sol2 = (-b - mSqrt(det)) / (2 * a);
|
|
|
|
F32 t;
|
|
if (sol2 > 0.0) {
|
|
t = sol2;
|
|
} else {
|
|
t = sol1;
|
|
}
|
|
if (t < 0.0) {
|
|
outputVectorMin->set(0, 0, 1);
|
|
outputVectorMax->set(0, 0, 1);
|
|
*outputMinTime = -1;
|
|
*outputMaxTime = -1;
|
|
return false;
|
|
}
|
|
|
|
// Once we know how long the projectile's path will take, it's straightforward to
|
|
// find out where it should go...
|
|
Point3F finalAnswer = (effTargetPos / (dryVelocity * t)) + (effTargetVel / dryVelocity);
|
|
finalAnswer.normalize();
|
|
|
|
*outputVectorMin = finalAnswer;
|
|
*outputVectorMax = finalAnswer;
|
|
*outputMinTime = t;
|
|
*outputMaxTime = t;
|
|
|
|
return (t * 1000.0) <= lifetimeMS;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool LinearProjectileData::onAdd()
|
|
{
|
|
if(!Parent::onAdd())
|
|
return false;
|
|
|
|
// Check for validity, and fix what we can...
|
|
//
|
|
if (dryVelocity < 0.1) {
|
|
Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: dryVelocity < .1, resetting", getName());
|
|
dryVelocity = 0.1;
|
|
}
|
|
if (wetVelocity < 0.1 && wetVelocity != -1) {
|
|
Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: wetVelocity < .1, resetting", getName());
|
|
wetVelocity = 0.1;
|
|
}
|
|
if (lifetimeMS < TickMs || lifetimeMS > ((LinearProjectile::MaxLivingTicks-1) * TickMs)) {
|
|
Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: lifetime out of range [%d, %d]", getName(), TickMs, ((LinearProjectile::MaxLivingTicks-1) * TickMs));
|
|
lifetimeMS = lifetimeMS < TickMs ? TickMs : ((LinearProjectile::MaxLivingTicks-1) * TickMs);
|
|
}
|
|
if (fizzleTimeMS < 0) {
|
|
Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: fizzle time < 0", getName());
|
|
fizzleTimeMS = 0;
|
|
}
|
|
if (fizzleTimeMS > lifetimeMS) {
|
|
Con::warnf(ConsoleLogEntry::General, "LinearProjectileData(%s)::onAdd: fizzleTimeMS > lifetimeMS", getName());
|
|
fizzleTimeMS = lifetimeMS;
|
|
}
|
|
|
|
// Bound the angle variables
|
|
if (reflectOnWaterImpactAngle > 90.0f)
|
|
reflectOnWaterImpactAngle = 90.0f;
|
|
if (reflectOnWaterImpactAngle < 0.0f)
|
|
reflectOnWaterImpactAngle = -1.0f;
|
|
if (deflectionOnWaterImpact > 90.0f)
|
|
deflectionOnWaterImpact = 90.0f;
|
|
if (deflectionOnWaterImpact < 0.0f)
|
|
deflectionOnWaterImpact = 0.0f;
|
|
|
|
reflectOnWaterImpactAngle = U32(reflectOnWaterImpactAngle);
|
|
deflectionOnWaterImpact = U32(deflectionOnWaterImpact);
|
|
|
|
if (fizzleUnderwaterMS < 0 || fizzleUnderwaterMS > lifetimeMS)
|
|
fizzleUnderwaterMS = -1;
|
|
else
|
|
fizzleUnderwaterMS = (fizzleUnderwaterMS + TickMs - 1) & ~(TickMs - 1);
|
|
|
|
if (activateDelayMS < 0 || activateDelayMS > lifetimeMS)
|
|
activateDelayMS = -1;
|
|
|
|
// Make sure lifetimeMS falls on a tick boundary
|
|
lifetimeMS = (lifetimeMS + TickMs - 1) & ~(TickMs - 1);
|
|
fizzleTimeMS = (fizzleTimeMS + TickMs - 1) & ~(TickMs - 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LinearProjectileData::preload(bool server, char errorBuffer[256])
|
|
{
|
|
if (Parent::preload(server, errorBuffer) == false)
|
|
return false;
|
|
|
|
if (bool(projectileShape) && activateDelayMS != -1) {
|
|
activateSeq = projectileShape->findSequence("activate");
|
|
maintainSeq = projectileShape->findSequence("maintain");
|
|
|
|
if (activateSeq == -1 || maintainSeq == -1) {
|
|
Con::warnf(ConsoleLogEntry::General,
|
|
"LinearProjectileData(%s)::load: activateDelayMS != -1, but no activate/maintain sequence on shape %s",
|
|
getName(), projectileShapeName);
|
|
activateDelayMS = -1;
|
|
activateSeq = -1;
|
|
maintainSeq = -1;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void LinearProjectileData::packData(BitStream* stream)
|
|
{
|
|
Parent::packData(stream);
|
|
|
|
stream->write(dryVelocity);
|
|
stream->write(wetVelocity);
|
|
stream->write(fizzleTimeMS);
|
|
stream->write(lifetimeMS);
|
|
stream->writeFlag(explodeOnDeath);
|
|
stream->writeRangedU32(reflectOnWaterImpactAngle, 0, 90);
|
|
stream->writeRangedU32(deflectionOnWaterImpact, 0, 90);
|
|
stream->write(fizzleUnderwaterMS);
|
|
stream->write(activateDelayMS);
|
|
stream->writeFlag(doDynamicClientHits);
|
|
}
|
|
|
|
void LinearProjectileData::unpackData(BitStream* stream)
|
|
{
|
|
Parent::unpackData(stream);
|
|
|
|
stream->read(&dryVelocity);
|
|
stream->read(&wetVelocity);
|
|
stream->read(&fizzleTimeMS);
|
|
stream->read(&lifetimeMS);
|
|
explodeOnDeath = stream->readFlag();
|
|
reflectOnWaterImpactAngle = stream->readRangedU32(0, 90);
|
|
deflectionOnWaterImpact = stream->readRangedU32(0, 90);
|
|
stream->read(&fizzleUnderwaterMS);
|
|
stream->read(&activateDelayMS);
|
|
doDynamicClientHits = stream->readFlag();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
//--------------------------------------
|
|
//
|
|
LinearProjectile::LinearProjectile()
|
|
{
|
|
mActivateThread = NULL;
|
|
mMaintainThread = NULL;
|
|
|
|
mSplashTick = 999999999999;
|
|
mPlayedSplash = false;
|
|
|
|
mEndedWithDecal = false;
|
|
mWetStart = false;
|
|
mHitWater = false;
|
|
|
|
mCurrBackDelta.set( 0.0f, 0.0f, 0.0f );
|
|
mCurrDeltaBase.set( 0.0f, 0.0f, 0.0f );
|
|
}
|
|
|
|
LinearProjectile::~LinearProjectile()
|
|
{
|
|
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void LinearProjectile::initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
//
|
|
}
|
|
|
|
|
|
void LinearProjectile::consoleInit()
|
|
{
|
|
//
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool LinearProjectile::calculateImpact(float simTime,
|
|
Point3F& pointOfImpact,
|
|
float& impactTime)
|
|
{
|
|
AssertFatal(isServerObject() == true, "Error, bad assumption. This should only be called on the server...");
|
|
|
|
if (mHidden == true) {
|
|
impactTime = 0;
|
|
pointOfImpact.set(0, 0, 0);
|
|
return false;
|
|
}
|
|
|
|
Point3F startPt;
|
|
getTransform().getColumn(3, &startPt);
|
|
|
|
U32 forwardTicks = U32((simTime * 1000.0f) / TickMs);
|
|
if ((mCurrTick + forwardTicks) * TickMs > mSegments[0].msEnd) {
|
|
// Hit a wall or the terrain...
|
|
U32 currMS = mCurrTick * TickMs;
|
|
impactTime = (F32(mSegments[0].msEnd) - F32(currMS)) / 1000.0;
|
|
if (impactTime < 0.0)
|
|
impactTime = 0.0;
|
|
pointOfImpact = mSegments[0].end;
|
|
return true;
|
|
}
|
|
|
|
Point3F endPt = deriveExactPosition(mCurrTick + forwardTicks);
|
|
|
|
U32 mask = PlayerObjectType | TerrainObjectType | InteriorObjectType | WaterObjectType;
|
|
RayInfo rayInfo;
|
|
if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo))
|
|
{
|
|
impactTime = 0;
|
|
pointOfImpact.set(0, 0, 0);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Point3F currVel = deriveExactVelocity(mCurrTick);
|
|
|
|
pointOfImpact = rayInfo.point;
|
|
float distToImpact = (pointOfImpact - startPt).len();
|
|
if (distToImpact > 1.0f && !currVel.isZero())
|
|
impactTime = distToImpact / currVel.len();
|
|
else
|
|
impactTime = 0.0f;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool LinearProjectile::determineWetStart()
|
|
{
|
|
SimpleQueryList sql;
|
|
if (isServerObject())
|
|
gServerSceneGraph->getWaterObjectList(sql);
|
|
else
|
|
gClientSceneGraph->getWaterObjectList(sql);
|
|
|
|
for (U32 i = 0; i < sql.mList.size(); i++)
|
|
{
|
|
WaterBlock* pBlock = dynamic_cast<WaterBlock*>(sql.mList[i]);
|
|
if (pBlock && pBlock->isPointSubmergedSimple(mInitialPosition))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool LinearProjectile::createSegments()
|
|
{
|
|
// Assumption: a linear projectile can have at most two segments.
|
|
// Not handled in this version: compressed intial position
|
|
|
|
// First, check to see if the fire point is wet...
|
|
mWetStart = determineWetStart();
|
|
|
|
Point3F compressedVelocity(0, 0, 0);
|
|
if (isServerObject()) {
|
|
if (mWetStart == false)
|
|
compressedVelocity += (BitStream::dumbDownNormal(mInitialDirection, InitialDirectionBits) *
|
|
mDataBlock->dryVelocity);
|
|
else
|
|
compressedVelocity += (BitStream::dumbDownNormal(mInitialDirection, InitialDirectionBits) *
|
|
mDataBlock->wetVelocity);
|
|
compressedVelocity += mExcessDirDumb * F32(mExcessVel);
|
|
} else {
|
|
if (mWetStart == false)
|
|
compressedVelocity += mInitialDirection * mDataBlock->dryVelocity;
|
|
else
|
|
compressedVelocity += mInitialDirection * mDataBlock->wetVelocity;
|
|
compressedVelocity += mExcessDirDumb * F32(mExcessVel);
|
|
}
|
|
|
|
mSegments[0].start = mInitialPosition;
|
|
mSegments[0].end = mInitialPosition + compressedVelocity * (F32(mDataBlock->lifetimeMS) / 1000.0);
|
|
mSegments[0].segmentVel = compressedVelocity;
|
|
|
|
RayInfo rInfo;
|
|
rInfo.object = NULL;
|
|
Container* pContainer = isServerObject() ? &gServerContainer : &gClientContainer;
|
|
if (pContainer->castRay(mSegments[0].start, mSegments[0].end,
|
|
csmStaticCollisionMask, &rInfo) == false) {
|
|
// Didn't hit anything, lifetime goes to the end...
|
|
mSegments[0].msStart = 0;
|
|
mSegments[0].msEnd = mDataBlock->lifetimeMS;
|
|
|
|
mSegments[0].cutShort = false;
|
|
mSegments[0].endNormal = compressedVelocity;
|
|
mSegments[0].endNormal.normalize();
|
|
mSegments[0].endNormal.neg();
|
|
} else {
|
|
// Hit something, cut the segment short...
|
|
mSegments[0].msStart = 0;
|
|
mSegments[0].msEnd = U32(mDataBlock->lifetimeMS * rInfo.t);
|
|
mSegments[0].end = rInfo.point;
|
|
|
|
mSegments[0].cutShort = true;
|
|
mSegments[0].endNormal = rInfo.normal;
|
|
mSegments[0].endTypeMask = rInfo.object->getTypeMask();
|
|
}
|
|
|
|
mSegments[0].hitObj = rInfo.object;
|
|
|
|
mNumSegments = 1;
|
|
|
|
if (mWetStart == false) {
|
|
// If we started in air, then we potentially have to modify the segments if
|
|
// we collide with a water block...
|
|
if (pContainer->castRay(mSegments[0].start, mSegments[0].end, WaterObjectType, &rInfo)) {
|
|
// We're going to have to fix this...
|
|
|
|
if (mDataBlock->explodeOnWaterImpact) {
|
|
|
|
// check if we actually hit water
|
|
bool hitWater = false;
|
|
|
|
WaterBlock* waterBlock = dynamic_cast<WaterBlock*>(rInfo.object);
|
|
if( waterBlock )
|
|
{
|
|
if( waterBlock->isWater( waterBlock->getLiquidType() ) )
|
|
{
|
|
hitWater = true;
|
|
}
|
|
}
|
|
|
|
// Check to see if we explode or bounce off...
|
|
Point3F normVel = compressedVelocity;
|
|
normVel.normalize();
|
|
if (mFabs(mDot(normVel, rInfo.normal)) >= mCos(mDegToRad(90 - mDataBlock->reflectOnWaterImpactAngle)) || !hitWater ) {
|
|
// Explode
|
|
mHitWater = true;
|
|
mSegments[0].msEnd = (U32)(mSegments[0].msEnd * rInfo.t);
|
|
mSegments[0].end = rInfo.point;
|
|
mSegments[0].cutShort = true;
|
|
mSegments[0].endNormal = rInfo.normal;
|
|
mSegments[0].endTypeMask = rInfo.object->getTypeMask();
|
|
|
|
mDeleteTick = (mSegments[0].msEnd / TickMs) + DeleteWaitTicks;
|
|
} else {
|
|
mSegments[0].msEnd = (U32)(mSegments[0].msEnd * rInfo.t);
|
|
mSegments[0].end = rInfo.point;
|
|
mSegments[0].endNormal = rInfo.normal;
|
|
mSegments[0].cutShort = false;
|
|
|
|
Point3F n = rInfo.normal;
|
|
MatrixF refMatrix(true);
|
|
F32* ra = refMatrix;
|
|
ra[0] = 1.0f - 2.0f*(n.x*n.x); ra[1] = 0.0f - 2.0f*(n.x*n.y); ra[2] = 0.0f - 2.0f*(n.x*n.z); ra[3] = 0.0f;
|
|
ra[4] = 0.0f - 2.0f*(n.y*n.x); ra[5] = 1.0f - 2.0f*(n.y*n.y); ra[6] = 0.0f - 2.0f*(n.y*n.z); ra[7] = 0.0f;
|
|
ra[8] = 0.0f - 2.0f*(n.z*n.x); ra[9] = 0.0f - 2.0f*(n.z*n.y); ra[10] = 1.0f - 2.0f*(n.z*n.z); ra[11] = 0.0f;
|
|
ra[12] = 0;
|
|
ra[13] = 0;
|
|
ra[14] = 0;
|
|
ra[15] = 1.0f;
|
|
// the GGems series (as of v1) uses row vectors (arg)
|
|
refMatrix.transpose();
|
|
|
|
// Reflect the velocity around the normal...
|
|
Point3F refVelocity = compressedVelocity;
|
|
refMatrix.mulV(refVelocity);
|
|
|
|
|
|
Point3F newStart = mSegments[0].end;
|
|
Point3F newEnd = newStart;
|
|
newEnd += refVelocity * (F32(mDataBlock->lifetimeMS - mSegments[0].msEnd) / 1000);
|
|
|
|
if (pContainer->castRay(newStart, newEnd,
|
|
csmStaticCollisionMask, &rInfo) == false) {
|
|
// Didn't hit anything, lifetime goes to the end...
|
|
mSegments[1].msStart = mSegments[0].msEnd;
|
|
mSegments[1].msEnd = mDataBlock->lifetimeMS;
|
|
mSegments[1].start = mSegments[0].end;
|
|
mSegments[1].end = newEnd;
|
|
|
|
mSegments[1].cutShort = false;
|
|
mSegments[1].endNormal = refVelocity;
|
|
mSegments[1].endNormal.normalize();
|
|
mSegments[1].endNormal.neg();
|
|
mSegments[1].segmentVel = refVelocity;
|
|
} else {
|
|
// Hit something, cut the segment short...
|
|
mSegments[1].msStart = mSegments[0].msEnd;
|
|
mSegments[1].msEnd = (U32)(mSegments[1].msStart + (mDataBlock->lifetimeMS - mSegments[0].msEnd) * rInfo.t);
|
|
mSegments[1].start = mSegments[0].end;
|
|
mSegments[1].end = rInfo.point;
|
|
|
|
mSegments[1].cutShort = true;
|
|
mSegments[1].endNormal = rInfo.normal;
|
|
mSegments[1].endTypeMask = rInfo.object->getTypeMask();
|
|
mSegments[1].segmentVel = refVelocity;
|
|
}
|
|
|
|
mDeleteTick = (mSegments[1].msEnd / TickMs) + DeleteWaitTicks;
|
|
mNumSegments = 2;
|
|
}
|
|
} else {
|
|
// Continue through, at wetVelocity...
|
|
mSegments[1].start = rInfo.point;
|
|
mSegments[0].end = rInfo.point;
|
|
mSegments[0].msEnd *= rInfo.t;
|
|
mSegments[0].endNormal = rInfo.normal;
|
|
mSegments[0].cutShort = false;
|
|
|
|
compressedVelocity.set(0, 0, 0);
|
|
if (isServerObject()) {
|
|
compressedVelocity += (BitStream::dumbDownNormal(mInitialDirection, InitialDirectionBits) *
|
|
mDataBlock->wetVelocity);
|
|
compressedVelocity += mExcessDirDumb * F32(mExcessVel);
|
|
} else {
|
|
compressedVelocity += mInitialDirection * mDataBlock->wetVelocity;
|
|
compressedVelocity += mExcessDirDumb * F32(mExcessVel);
|
|
}
|
|
|
|
Point3F newEnd = mSegments[1].start;
|
|
newEnd += compressedVelocity * (F32(mDataBlock->lifetimeMS - mSegments[0].msEnd) / 1000.0);
|
|
|
|
if (pContainer->castRay(mSegments[1].start, newEnd,
|
|
csmStaticCollisionMask, &rInfo) == false) {
|
|
// Didn't hit anything, lifetime goes to the end...
|
|
mSegments[1].msStart = mSegments[0].msEnd;
|
|
mSegments[1].msEnd = mDataBlock->lifetimeMS;
|
|
mSegments[1].end = newEnd;
|
|
|
|
mSegments[1].cutShort = false;
|
|
mSegments[1].endNormal = compressedVelocity;
|
|
mSegments[1].endNormal.normalize();
|
|
mSegments[1].endNormal.neg();
|
|
} else {
|
|
// Hit something, cut the segment short...
|
|
mSegments[1].msStart = mSegments[0].msEnd;
|
|
mSegments[1].msEnd = (U32)(mSegments[1].msStart + (mDataBlock->lifetimeMS - mSegments[0].msEnd) * rInfo.t);
|
|
mSegments[1].end = rInfo.point;
|
|
|
|
mSegments[1].cutShort = true;
|
|
mSegments[1].endNormal = rInfo.normal;
|
|
mSegments[1].endTypeMask = rInfo.object->getTypeMask();
|
|
}
|
|
|
|
mSegments[1].hitObj = rInfo.object;
|
|
mSegments[1].segmentVel = compressedVelocity;
|
|
|
|
mNumSegments = 2;
|
|
mDeleteTick = (mSegments[1].msEnd / TickMs) + DeleteWaitTicks;
|
|
}
|
|
} else {
|
|
// No water collision, we're cool...
|
|
mDeleteTick = (mSegments[0].msEnd / TickMs) + DeleteWaitTicks;
|
|
}
|
|
} else {
|
|
mDeleteTick = (mSegments[0].msEnd / TickMs) + DeleteWaitTicks;
|
|
}
|
|
|
|
AssertFatal(mSegments[0].msEnd >= mSegments[0].msStart, "Error, 1st segment is bad!");
|
|
if (mNumSegments > 1) {
|
|
AssertFatal(mSegments[1].msStart == mSegments[0].msEnd, "Error, 2nd segment is bad!");
|
|
AssertFatal(mSegments[1].msEnd >= mSegments[1].msStart, "Error, 2nd segment is bad!");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LinearProjectile::onAdd()
|
|
{
|
|
if(!Parent::onAdd())
|
|
return false;
|
|
|
|
if (isServerObject()) {
|
|
// Set the initial position
|
|
MatrixF xform(true);
|
|
xform.setColumn(3, mInitialPosition);
|
|
setTransform(xform);
|
|
|
|
// Ok, we have all the variables we need to forecast our own demise. Do that now
|
|
if (createSegments() == false)
|
|
{
|
|
return false;
|
|
}
|
|
} else {
|
|
if (mHidden == false) {
|
|
createSegments();
|
|
calcSplash();
|
|
|
|
if (bool(mSourceObject)) {
|
|
// Setup the warping. We have to take into account the fact that we might be
|
|
// in the middle of a tick.
|
|
mWarpTicksRemaining = smProjectileWarpTicks;
|
|
mWarpEnd = deriveExactPosition(mCurrTick + smProjectileWarpTicks);
|
|
mSourceObject->getMuzzlePoint(mSourceObjectSlot, &mWarpStart);
|
|
} else {
|
|
mWarpTicksRemaining = 0;
|
|
}
|
|
} else {
|
|
setPosition( mInitialPosition );
|
|
|
|
mWetStart = determineWetStart();
|
|
calcSplash();
|
|
|
|
if( mCurrTick >= mSplashTick )
|
|
{
|
|
createSplash();
|
|
}
|
|
|
|
// All we need to do is explode...
|
|
if (mDataBlock->explosion) {
|
|
Explosion* pExplosion = new Explosion;
|
|
pExplosion->onNewDataBlock(mDataBlock->explosion);
|
|
|
|
MatrixF xform(true);
|
|
xform.setColumn(3, mExplosionPosition);
|
|
pExplosion->setTransform(xform);
|
|
pExplosion->setInitialState(mExplosionPosition, mExplosionNormal, 1.0);
|
|
if (pExplosion->registerObject() == false) {
|
|
Con::errorf(ConsoleLogEntry::General, "LinearProjectile(%s)::explode: couldn't register explosion(%s)",
|
|
mDataBlock->getName(), mDataBlock->explosion->getName());
|
|
delete pExplosion;
|
|
pExplosion = NULL;
|
|
} else {
|
|
if (mEndedWithDecal == true && gClientSceneGraph->getCurrentDecalManager() && mDataBlock->numDecals != 0) {
|
|
// DMM: randomly choose from [0 .. numDecals - 1];
|
|
if (mDataBlock->decalData[0]) {
|
|
gClientSceneGraph->getCurrentDecalManager()->addDecal(mExplosionPosition,
|
|
mExplosionNormal,
|
|
mDataBlock->decalData[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
addToScene();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void LinearProjectile::onRemove()
|
|
{
|
|
if (isClientObject())
|
|
updateSound(Point3F(0, 0, 0), Point3F(0, 0, 0), false);
|
|
removeFromScene();
|
|
|
|
Parent::onRemove();
|
|
}
|
|
|
|
|
|
bool LinearProjectile::onNewDataBlock(GameBaseData* dptr)
|
|
{
|
|
mDataBlock = dynamic_cast<LinearProjectileData*>(dptr);
|
|
if (!mDataBlock || !Parent::onNewDataBlock(dptr))
|
|
return false;
|
|
|
|
scriptOnNewDataBlock();
|
|
return true;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool LinearProjectile::updatePos(const Point3F& oldPos,
|
|
const Point3F& newPos,
|
|
F32* hitDelta,
|
|
Point3F* hitPos,
|
|
Point3F* hitNormal,
|
|
SceneObject*& hitObject)
|
|
{
|
|
if (mSourceIdTimeoutTicks && bool(mSourceObject))
|
|
mSourceObject->disableCollision();
|
|
if(mSourceIdTimeoutTicks && bool(mVehicleObject))
|
|
mVehicleObject->disableCollision();
|
|
|
|
RayInfo rInfo;
|
|
if (mContainer->castRay(oldPos, newPos, csmDynamicCollisionMask, &rInfo) == true) {
|
|
*hitPos = rInfo.point;
|
|
*hitNormal = rInfo.normal;
|
|
*hitDelta = rInfo.t;
|
|
hitObject = rInfo.object;
|
|
|
|
if (mSourceIdTimeoutTicks && bool(mSourceObject))
|
|
mSourceObject->enableCollision();
|
|
if(mSourceIdTimeoutTicks && bool(mVehicleObject))
|
|
mVehicleObject->enableCollision();
|
|
return false;
|
|
} else {
|
|
*hitDelta = 1.0;
|
|
hitObject = NULL;
|
|
|
|
if (mSourceIdTimeoutTicks && bool(mSourceObject))
|
|
mSourceObject->enableCollision();
|
|
if(mSourceIdTimeoutTicks && bool(mVehicleObject))
|
|
mVehicleObject->enableCollision();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
void LinearProjectile::explode(const Point3F& p, const Point3F& n, const bool dynamicObject)
|
|
{
|
|
// don't explode if projectile dies on water impact - play big splash instead
|
|
if( mHitWater && !mDataBlock->explodeOnWaterImpact )
|
|
{
|
|
mHidden = true;
|
|
return;
|
|
}
|
|
|
|
// Already blown up
|
|
if (mHidden == true)
|
|
return;
|
|
mHidden = true;
|
|
|
|
if (isServerObject()) {
|
|
mExplosionPosition = p + (n * 0.01);
|
|
mExplosionNormal = n;
|
|
|
|
// do radius damage if appropriate
|
|
char posBuffer[128];
|
|
char fadeBuffer[128];
|
|
dSprintf(posBuffer, sizeof(posBuffer), "%f %f %f", mExplosionPosition.x,
|
|
mExplosionPosition.y,
|
|
mExplosionPosition.z);
|
|
dSprintf(fadeBuffer, sizeof(fadeBuffer), "%f", mFadeValue);
|
|
|
|
Con::executef(mDataBlock, 4, "onExplode", scriptThis(), posBuffer, fadeBuffer);
|
|
|
|
if (dynamicObject)
|
|
setMaskBits(ExplosionMask);
|
|
} else {
|
|
// Client just plays the explosion at the right place...
|
|
//
|
|
Explosion* pExplosion = new Explosion;
|
|
|
|
F32 waterHeight;
|
|
if( pointInWater( (Point3F &)p, &waterHeight ) && mDataBlock->underwaterExplosion )
|
|
{
|
|
F32 depth = waterHeight - p.z;
|
|
if( depth >= mDataBlock->depthTolerance )
|
|
{
|
|
pExplosion->onNewDataBlock(mDataBlock->underwaterExplosion);
|
|
}
|
|
else
|
|
{
|
|
pExplosion->onNewDataBlock(mDataBlock->explosion);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mDataBlock->explosion)
|
|
{
|
|
pExplosion->onNewDataBlock(mDataBlock->explosion);
|
|
}
|
|
}
|
|
|
|
if( pExplosion )
|
|
{
|
|
MatrixF xform(true);
|
|
xform.setPosition(p);
|
|
pExplosion->setTransform(xform);
|
|
pExplosion->setInitialState(p, n);
|
|
if (pExplosion->registerObject() == false)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "LinearProjectile(%s)::explode: couldn't register explosion",
|
|
mDataBlock->getName() );
|
|
delete pExplosion;
|
|
pExplosion = NULL;
|
|
}
|
|
|
|
if (mEndedWithDecal == true && gClientSceneGraph->getCurrentDecalManager() && mDataBlock->numDecals != 0) {
|
|
// DMM: randomly choose from [0 .. numDecals - 1];
|
|
if (mDataBlock->decalData[0])
|
|
gClientSceneGraph->getCurrentDecalManager()->addDecal(p, n, mDataBlock->decalData[0]);
|
|
}
|
|
}
|
|
|
|
// Client object
|
|
updateSound(Point3F(0, 0, 0), Point3F(0, 0, 0), false);
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
Point3F LinearProjectile::getVelocity() const
|
|
{
|
|
return deriveExactVelocity(mCurrTick);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
void LinearProjectile::processTick(const Move* move)
|
|
{
|
|
Parent::processTick(move);
|
|
AssertFatal(mCurrTick >= 1, "Hm, bad assumption somewhere");
|
|
|
|
if (isServerObject()) {
|
|
if (mCurrTick >= mDeleteTick) {
|
|
deleteObject();
|
|
return;
|
|
}
|
|
else if (mHidden == true) {
|
|
// Already exploded
|
|
return;
|
|
}
|
|
else if (((mCurrTick - 1) * TickMs) >= mSegments[mNumSegments - 1].msEnd) {
|
|
if (mSegments[mNumSegments - 1].cutShort == true && (mSegments[mNumSegments - 1].endTypeMask & csmDecalMask))
|
|
mEndedWithDecal = true;
|
|
|
|
if (mSegments[mNumSegments - 1].cutShort == true || mDataBlock->explodeOnDeath == true)
|
|
{
|
|
SceneObject *so = mSegments[mNumSegments-1].hitObj;
|
|
if( so )
|
|
{
|
|
Point3F normal = mSegments[mNumSegments-1].endNormal;
|
|
onCollision( mSegments[mNumSegments-1].end, normal, so );
|
|
}
|
|
explode(mSegments[mNumSegments - 1].end, mSegments[mNumSegments - 1].endNormal, false);
|
|
}
|
|
else
|
|
{
|
|
// End of the line without explosion. Just delete the object. If we don't, the
|
|
// current unpackUpdate logic can break the client, since it interprets hidden
|
|
// on initial update to mean: explosion.
|
|
mHidden = true;
|
|
deleteObject();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Otherwise update our position
|
|
Point3F oldPos;
|
|
getTransform().getColumn(3, &oldPos);
|
|
Point3F newPos = deriveExactPosition(mCurrTick);
|
|
|
|
F32 hitDelta;
|
|
Point3F hitPos, hitNormal;
|
|
SceneObject* hitObject;
|
|
if (updatePos(oldPos, newPos, &hitDelta, &hitPos, &hitNormal, hitObject) == false) {
|
|
// Hit something dynamic in this pass
|
|
|
|
MatrixF xform(true);
|
|
xform.setColumn(3, hitPos);
|
|
setTransform(xform);
|
|
|
|
onCollision(hitPos, hitNormal, hitObject);
|
|
|
|
if (hitObject->getTypeMask() & csmDecalMask)
|
|
mEndedWithDecal = true;
|
|
explode(hitPos, hitNormal, true);
|
|
} else {
|
|
// No hit, continue on...
|
|
MatrixF xform(true);
|
|
xform.setColumn(3, newPos);
|
|
setTransform(xform);
|
|
}
|
|
} else {
|
|
if( mCurrTick >= mSplashTick )
|
|
{
|
|
createSplash();
|
|
}
|
|
|
|
if (mHidden == true)
|
|
return;
|
|
else if (((mCurrTick - 1) * TickMs) >= mSegments[mNumSegments - 1].msEnd) {
|
|
if (mSegments[mNumSegments - 1].cutShort == true && (mSegments[mNumSegments - 1].endTypeMask & csmDecalMask))
|
|
mEndedWithDecal = true;
|
|
|
|
if (mSegments[mNumSegments - 1].cutShort == true || mDataBlock->explodeOnDeath == true) {
|
|
explode(mSegments[mNumSegments - 1].end, mSegments[mNumSegments - 1].endNormal, false);
|
|
} else {
|
|
mHidden = true;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Point3F currPos;
|
|
Point3F nextPos;
|
|
|
|
if (mWarpTicksRemaining) {
|
|
currPos = mWarpStart * (mWarpTicksRemaining) +
|
|
mWarpEnd * (smProjectileWarpTicks - mWarpTicksRemaining);
|
|
nextPos = mWarpStart * (mWarpTicksRemaining - 1) +
|
|
mWarpEnd * (smProjectileWarpTicks - mWarpTicksRemaining + 1);
|
|
|
|
currPos /= smProjectileWarpTicks;
|
|
nextPos /= smProjectileWarpTicks;
|
|
|
|
mWarpTicksRemaining--;
|
|
}
|
|
else {
|
|
// Normal update...
|
|
currPos = deriveExactPosition(mCurrTick - 1);
|
|
nextPos = deriveExactPosition(mCurrTick);
|
|
}
|
|
|
|
Point3F partVel = deriveExactVelocity(mCurrTick);
|
|
if (partVel.isZero())
|
|
partVel = mSegments[mNumSegments - 1].segmentVel;
|
|
|
|
emitParticles(currPos, nextPos, partVel, TickMs);
|
|
|
|
// Check for collision
|
|
F32 hitDelta;
|
|
Point3F hitPos, hitNormal;
|
|
SceneObject* hitObject;
|
|
if (mDataBlock->doDynamicClientHits == true &&
|
|
updatePos(currPos, nextPos, &hitDelta, &hitPos, &hitNormal, hitObject) == false) {
|
|
if (hitObject->getTypeMask() & csmDecalMask)
|
|
mEndedWithDecal = true;
|
|
|
|
// Hit something dynamic in this pass
|
|
explode(hitPos, hitNormal, true);
|
|
|
|
mCurrBackDelta = currPos - hitPos;
|
|
mCurrDeltaBase = hitPos;
|
|
mSegments[0].msEnd = (U32)(mCurrTick * TickMs - (hitDelta * TickMs));
|
|
mSegments[1].msEnd = (U32)(mCurrTick * TickMs - (hitDelta * TickMs));
|
|
}
|
|
else {
|
|
// No hit, continue on...
|
|
mCurrBackDelta = currPos - nextPos;
|
|
mCurrDeltaBase = nextPos;
|
|
}
|
|
MatrixF temp(true);
|
|
temp.setColumn(3, mCurrDeltaBase);
|
|
setTransform(temp);
|
|
|
|
// If we're past our activate time, create our activate sequence
|
|
if (mDataBlock->activateDelayMS != -1 && mProjectileShape != NULL &&
|
|
mCurrTick * TickMs >= mDataBlock->activateDelayMS &&
|
|
!mActivateThread) {
|
|
mActivateThread = mProjectileShape->addThread();
|
|
mProjectileShape->setTimeScale(mActivateThread, 1);
|
|
mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LinearProjectile::interpolateTick(F32 delta)
|
|
{
|
|
Parent::interpolateTick(delta);
|
|
|
|
// Client object. Must check to see if there are any warp ticks left...
|
|
if (mHidden == true)
|
|
return;
|
|
|
|
Point3F newPos = mCurrDeltaBase + (mCurrBackDelta * delta);
|
|
MatrixF xform(true);
|
|
|
|
Point3F dir = deriveExactVelocity(getMax(S32(mCurrTick) - 1, 0));
|
|
if( dir.isZero() )
|
|
{
|
|
dir.set( 0.0, 0.0, 1.0 ); // odd but prevents crash
|
|
}
|
|
else
|
|
{
|
|
dir.normalize();
|
|
}
|
|
|
|
xform = MathUtils::createOrientFromDir( dir );
|
|
xform.setPosition( newPos );
|
|
setRenderTransform( xform );
|
|
|
|
|
|
S32 currMs = (S32)(mCurrTick * TickMs - (delta * TickMs));
|
|
if (currMs > mDataBlock->fizzleTimeMS) {
|
|
U32 fizzle = currMs - mDataBlock->fizzleTimeMS;
|
|
mFadeValue = 1.0 - (F32(fizzle) /
|
|
F32(mDataBlock->lifetimeMS - mDataBlock->fizzleTimeMS));
|
|
} else {
|
|
mFadeValue = 1.0;
|
|
}
|
|
|
|
updateSound(newPos, deriveExactVelocity(mCurrTick), true);
|
|
}
|
|
|
|
void LinearProjectile::advanceTime(F32 dt)
|
|
{
|
|
Parent::advanceTime(dt);
|
|
|
|
if (mHidden == true || dt == 0.0)
|
|
return;
|
|
|
|
if (mActivateThread &&
|
|
mProjectileShape->getDuration(mActivateThread) > mProjectileShape->getTime(mActivateThread) + dt) {
|
|
mProjectileShape->advanceTime(dt, mActivateThread);
|
|
} else {
|
|
if (mMaintainThread) {
|
|
mProjectileShape->advanceTime(dt, mMaintainThread);
|
|
} else if (mActivateThread && mDataBlock->maintainSeq != -1) {
|
|
mMaintainThread = mProjectileShape->addThread();
|
|
mProjectileShape->setTimeScale(mMaintainThread, 1);
|
|
mProjectileShape->setSequence(mMaintainThread, mDataBlock->maintainSeq, 0);
|
|
mProjectileShape->advanceTime(dt, mMaintainThread);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
U32 LinearProjectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
|
|
{
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) {
|
|
// Initial update
|
|
|
|
// Write: initialPosition
|
|
// initialVector (compressed to 10b standard)
|
|
// currServerTime
|
|
// if (sourceId)
|
|
// sourceId
|
|
// sourceSlot
|
|
if (stream->writeFlag(mHidden) == false) {
|
|
con->writeCompressed(stream, mInitialPosition);
|
|
stream->writeNormalVector(mInitialDirection, InitialDirectionBits);
|
|
stream->writeRangedU32(mCurrTick, 0, MaxLivingTicks);
|
|
|
|
// Potentially have to write this to the client, let's make sure it has a
|
|
// ghost on the other side...
|
|
S32 ghostIndex = -1;
|
|
if (bool(mSourceObject))
|
|
ghostIndex = con->getGhostIndex(mSourceObject);
|
|
|
|
if (stream->writeFlag(ghostIndex != -1))
|
|
{
|
|
stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount - 1);
|
|
stream->writeRangedU32(U32(mSourceObjectSlot),
|
|
0, ShapeBase::MaxMountedImages - 1);
|
|
|
|
if (stream->writeFlag(mExcessVel != 0))
|
|
{
|
|
stream->writeRangedU32(mExcessVel, 0, 255);
|
|
stream->writeNormalVector(mExcessDir, ExcessVelDirBits);
|
|
}
|
|
}
|
|
|
|
if (bool(mVehicleObject)) {
|
|
// Potentially have to write this to the client, let's make sure it has a
|
|
// ghost on the other side...
|
|
S32 ghostIndex = con->getGhostIndex(mVehicleObject);
|
|
if (stream->writeFlag(ghostIndex != -1)) {
|
|
stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount - 1);
|
|
}
|
|
} else {
|
|
stream->writeFlag(false);
|
|
}
|
|
} else {
|
|
// We've already exploded. Write out our explosion position and normal...
|
|
con->writeCompressed(stream, mExplosionPosition);
|
|
stream->writeNormalVector(mExplosionNormal, InitialDirectionBits);
|
|
stream->writeFlag(mEndedWithDecal);
|
|
}
|
|
} else {
|
|
// Explosion update
|
|
AssertFatal((mask & ExplosionMask) != 0,
|
|
"LinearProjectile::packUpdate: only two types of packets, initial and explosion, this is neither?");
|
|
con->writeCompressed(stream, mExplosionPosition);
|
|
stream->writeNormalVector(mExplosionNormal, InitialDirectionBits);
|
|
stream->writeFlag(mEndedWithDecal);
|
|
}
|
|
|
|
return retMask;
|
|
}
|
|
|
|
void LinearProjectile::unpackUpdate(NetConnection* con, BitStream* stream)
|
|
{
|
|
Parent::unpackUpdate(con, stream);
|
|
|
|
if (stream->readFlag()) {
|
|
// Initial update
|
|
if (stream->readFlag() == false) {
|
|
con->readCompressed(stream, &mInitialPosition);
|
|
stream->readNormalVector(&mInitialDirection, InitialDirectionBits);
|
|
mCurrTick = stream->readRangedU32(0, MaxLivingTicks);
|
|
|
|
if (stream->readFlag()) {
|
|
mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount - 1);
|
|
mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1);
|
|
|
|
NetObject* pObject = con->resolveGhost(mSourceObjectId);
|
|
if (pObject != NULL)
|
|
mSourceObject = dynamic_cast<ShapeBase*>(pObject);
|
|
|
|
if (bool(mSourceObject) == false)
|
|
Con::errorf(ConsoleLogEntry::General, "LinearProjectile::unpackUpdate: could not resolve source ghost properly on initial update");
|
|
|
|
if (stream->readFlag()) {
|
|
mExcessVel = stream->readRangedU32(0, 255);
|
|
stream->readNormalVector(&mExcessDir, ExcessVelDirBits);
|
|
} else {
|
|
mExcessDir.set(0, 0, 1);
|
|
mExcessVel = 0;
|
|
}
|
|
mExcessDirDumb = mExcessDir;
|
|
} else {
|
|
mSourceObjectId = -1;
|
|
mSourceObjectSlot = -1;
|
|
mSourceObject = NULL;
|
|
|
|
mExcessDir.set(0, 0, 1);
|
|
mExcessDirDumb = mExcessDir;
|
|
mExcessVel = 0;
|
|
}
|
|
|
|
if (stream->readFlag()) {
|
|
mVehicleObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount - 1);
|
|
NetObject* pObject = con->resolveGhost(mVehicleObjectId);
|
|
if (pObject != NULL)
|
|
mVehicleObject = dynamic_cast<ShapeBase*>(pObject);
|
|
if (bool(mVehicleObject) == false)
|
|
Con::errorf(ConsoleLogEntry::General, "LinearProjectile::unpackUpdate: could not resolve source ghost properly on initial update");
|
|
} else {
|
|
mVehicleObjectId = 0;
|
|
mVehicleObject = NULL;
|
|
}
|
|
} else {
|
|
mHidden = true;
|
|
con->readCompressed(stream, &mExplosionPosition);
|
|
stream->readNormalVector(&mExplosionNormal, InitialDirectionBits);
|
|
mInitialPosition = mExplosionPosition + mExplosionNormal;
|
|
mInitialDirection = - mExplosionNormal;
|
|
|
|
mEndedWithDecal = stream->readFlag();
|
|
}
|
|
} else {
|
|
// Explosion notification
|
|
con->readCompressed(stream, &mExplosionPosition);
|
|
stream->readNormalVector(&mExplosionNormal, InitialDirectionBits);
|
|
mEndedWithDecal = stream->readFlag();
|
|
explode(mExplosionPosition, mExplosionNormal, false);
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
Point3F LinearProjectile::deriveExactPosition(const U32 tick) const
|
|
{
|
|
U32 currMs = tick * TickMs;
|
|
|
|
// The hard way...
|
|
if (currMs == 0) {
|
|
return mSegments[0].start;
|
|
} else if (currMs <= mSegments[0].msEnd) {
|
|
return mSegments[0].start + (mSegments[0].segmentVel * (F32(currMs) / 1000.0f));
|
|
} else if (mNumSegments > 1 && currMs <= mSegments[1].msEnd) {
|
|
AssertFatal(currMs > mSegments[1].msStart, "Error, should have been greater here");
|
|
return mSegments[1].start + (mSegments[1].segmentVel * (F32(currMs - mSegments[1].msStart) / 1000.0f));
|
|
} else {
|
|
if (mNumSegments == 1) {
|
|
return mSegments[0].end;
|
|
} else {
|
|
return mSegments[1].end;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
Point3F LinearProjectile::deriveExactVelocity(const U32 tick) const
|
|
{
|
|
U32 currMs = tick * TickMs;
|
|
|
|
if (currMs < mSegments[0].msEnd)
|
|
return mSegments[0].segmentVel;
|
|
else if (mNumSegments > 1 && currMs < mSegments[1].msEnd)
|
|
return mSegments[1].segmentVel;
|
|
else {
|
|
return Point3F(0, 0, 0);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void LinearProjectile::calcSplash()
|
|
{
|
|
Point3F vel = mInitialDirection;
|
|
|
|
if( mWetStart )
|
|
{
|
|
vel *= mDataBlock->wetVelocity;
|
|
}
|
|
else
|
|
{
|
|
vel *= mDataBlock->dryVelocity;
|
|
}
|
|
|
|
// If the hidden flag is set at this point then the server has indicated that the
|
|
// collision point is so close that the projectile has already hit and exploded.
|
|
// It also means the mExcess variables haven't come down to the client and are invalid
|
|
if( !mHidden )
|
|
{
|
|
vel += mExcessDirDumb * F32(mExcessVel);
|
|
}
|
|
|
|
Point3F start = mInitialPosition;
|
|
Point3F end = mInitialPosition + vel * (F32(mDataBlock->lifetimeMS) / 1000.0);
|
|
|
|
bool startUnder = pointInWater( start );
|
|
bool endUnder = pointInWater( end );
|
|
|
|
mSplashTick = U32(-1);
|
|
|
|
if( startUnder && endUnder )
|
|
{
|
|
return;
|
|
}
|
|
|
|
RayInfo rInfo;
|
|
|
|
if( startUnder )
|
|
{
|
|
if( gClientContainer.castRay(end, start, WaterObjectType, &rInfo) )
|
|
{
|
|
mSplashPos = rInfo.point;
|
|
mSplashTick = U32(mDataBlock->lifetimeMS * (1.0 - rInfo.t) / TickMs);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( gClientContainer.castRay(start, end, WaterObjectType, &rInfo) )
|
|
{
|
|
mSplashPos = rInfo.point;
|
|
mSplashTick = U32(mDataBlock->lifetimeMS * rInfo.t / TickMs);
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void LinearProjectile::renderObject(SceneState* state, SceneRenderImage* sri)
|
|
{
|
|
if( !mHitWater || !mPlayedSplash )
|
|
{
|
|
Parent::renderObject( state, sri );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void LinearProjectile::createSplash()
|
|
{
|
|
if( mDataBlock->splash && !mPlayedSplash )
|
|
{
|
|
MatrixF trans = getTransform();
|
|
trans.setPosition( mSplashPos );
|
|
Splash *splash = new Splash;
|
|
splash->onNewDataBlock( mDataBlock->splash );
|
|
splash->setTransform( trans );
|
|
splash->setInitialState( trans.getPosition(), Point3F( 0.0, 0.0, 1.0 ) );
|
|
if (!splash->registerObject())
|
|
delete splash;
|
|
|
|
mPlayedSplash = true;
|
|
}
|
|
}
|