1926 lines
65 KiB
C++
Executable File
1926 lines
65 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "core/bitStream.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "console/consoleInternal.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "game/fx/particleEngine.h"
|
|
#include "audio/audioDataBlock.h"
|
|
#include "game/shapeBase.h"
|
|
#include "game/projectile.h"
|
|
#include "game/gameConnection.h"
|
|
#include "math/mathIO.h"
|
|
#include "game/debris.h"
|
|
#include "math/mathUtils.h"
|
|
#include "sim/netObject.h"
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
ShapeBaseImageData* InvalidImagePtr = (ShapeBaseImageData*) 1;
|
|
|
|
static EnumTable::Enums enumLoadedStates[] =
|
|
{
|
|
{ ShapeBaseImageData::StateData::IgnoreLoaded, "Ignore" },
|
|
{ ShapeBaseImageData::StateData::Loaded, "Loaded" },
|
|
{ ShapeBaseImageData::StateData::NotLoaded, "Empty" },
|
|
};
|
|
static EnumTable EnumLoadedState(3, &enumLoadedStates[0]);
|
|
|
|
static EnumTable::Enums enumSpinStates[] =
|
|
{
|
|
{ ShapeBaseImageData::StateData::IgnoreSpin,"Ignore" },
|
|
{ ShapeBaseImageData::StateData::NoSpin, "Stop" },
|
|
{ ShapeBaseImageData::StateData::SpinUp, "SpinUp" },
|
|
{ ShapeBaseImageData::StateData::SpinDown, "SpinDown" },
|
|
{ ShapeBaseImageData::StateData::FullSpin, "FullSpeed" },
|
|
};
|
|
static EnumTable EnumSpinState(5, &enumSpinStates[0]);
|
|
|
|
static EnumTable::Enums enumRecoilStates[] =
|
|
{
|
|
{ ShapeBaseImageData::StateData::NoRecoil, "NoRecoil" },
|
|
{ ShapeBaseImageData::StateData::LightRecoil, "LightRecoil" },
|
|
{ ShapeBaseImageData::StateData::MediumRecoil, "MediumRecoil" },
|
|
{ ShapeBaseImageData::StateData::HeavyRecoil, "HeavyRecoil" },
|
|
};
|
|
static EnumTable EnumRecoilState(4, &enumRecoilStates[0]);
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(ShapeBaseImageData);
|
|
|
|
ShapeBaseImageData::StateData::StateData()
|
|
{
|
|
name = 0;
|
|
transition.loaded[0] = transition.loaded[1] = -1;
|
|
transition.ammo[0] = transition.ammo[1] = -1;
|
|
transition.target[0] = transition.target[1] = -1;
|
|
transition.trigger[0] = transition.trigger[1] = -1;
|
|
transition.wet[0] = transition.wet[1] = -1;
|
|
transition.timeout = -1;
|
|
waitForTimeout = true;
|
|
timeoutValue = 0;
|
|
fire = false;
|
|
energyDrain = 0;
|
|
allowImageChange = true;
|
|
loaded = IgnoreLoaded;
|
|
spin = IgnoreSpin;
|
|
recoil = NoRecoil;
|
|
flashSequence = false;
|
|
sequence = -1;
|
|
sequenceVis = -1;
|
|
sound = 0;
|
|
emitter = NULL;
|
|
script = 0;
|
|
ignoreLoadedForReady = false;
|
|
}
|
|
|
|
static ShapeBaseImageData::StateData gDefaultStateData;
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
ShapeBaseImageData::ShapeBaseImageData()
|
|
{
|
|
emap = false;
|
|
|
|
mountPoint = 0;
|
|
mountOffset.identity();
|
|
eyeOffset.identity();
|
|
correctMuzzleVector = true;
|
|
firstPerson = true;
|
|
useEyeOffset = false;
|
|
mass = 0;
|
|
|
|
usesEnergy = false;
|
|
minEnergy = 2;
|
|
accuFire = false;
|
|
|
|
projectile = NULL;
|
|
|
|
cloakable = true;
|
|
|
|
lightType = ShapeBaseImageData::NoLight;
|
|
lightColor.set(1.f,1.f,1.f,1.f);
|
|
lightTime = 1000;
|
|
lightRadius = 10.f;
|
|
|
|
mountTransform.identity();
|
|
shapeName = "";
|
|
fireState = -1;
|
|
computeCRC = false;
|
|
|
|
//
|
|
for (int i = 0; i < MaxStates; i++) {
|
|
stateName[i] = 0;
|
|
stateTransitionLoaded[i] = 0;
|
|
stateTransitionNotLoaded[i] = 0;
|
|
stateTransitionAmmo[i] = 0;
|
|
stateTransitionNoAmmo[i] = 0;
|
|
stateTransitionTarget[i] = 0;
|
|
stateTransitionNoTarget[i] = 0;
|
|
stateTransitionWet[i] = 0;
|
|
stateTransitionNotWet[i] = 0;
|
|
stateTransitionTriggerUp[i] = 0;
|
|
stateTransitionTriggerDown[i] = 0;
|
|
stateTransitionTimeout[i] = 0;
|
|
stateWaitForTimeout[i] = true;
|
|
stateTimeoutValue[i] = 0;
|
|
stateFire[i] = false;
|
|
stateEjectShell[i] = false;
|
|
stateEnergyDrain[i] = 0;
|
|
stateAllowImageChange[i] = true;
|
|
stateScaleAnimation[i] = true;
|
|
stateDirection[i] = true;
|
|
stateLoaded[i] = StateData::IgnoreLoaded;
|
|
stateSpin[i] = StateData::IgnoreSpin;
|
|
stateRecoil[i] = StateData::NoRecoil;
|
|
stateSequence[i] = 0;
|
|
stateSequenceRandomFlash[i] = false;
|
|
stateSound[i] = 0;
|
|
stateScript[i] = 0;
|
|
stateEmitter[i] = 0;
|
|
stateEmitterTime[i] = 0;
|
|
stateEmitterNode[i] = 0;
|
|
stateIgnoreLoadedForReady[i] = false;
|
|
}
|
|
statesLoaded = false;
|
|
|
|
casing = NULL;
|
|
casingID = 0;
|
|
shellExitDir.set( 1.0, 0.0, 1.0 );
|
|
shellExitDir.normalize();
|
|
shellExitVariance = 20.0;
|
|
shellVelocity = 1.0;
|
|
|
|
}
|
|
|
|
ShapeBaseImageData::~ShapeBaseImageData()
|
|
{
|
|
|
|
}
|
|
|
|
bool ShapeBaseImageData::onAdd()
|
|
{
|
|
if (!Parent::onAdd())
|
|
return false;
|
|
|
|
// Copy state data from the scripting arrays into the
|
|
// state structure array. If we have state data already,
|
|
// we are on the client and need to leave it alone.
|
|
for (U32 i = 0; i < MaxStates; i++) {
|
|
StateData& s = state[i];
|
|
if (statesLoaded == false) {
|
|
s.name = stateName[i];
|
|
s.transition.loaded[0] = lookupState(stateTransitionNotLoaded[i]);
|
|
s.transition.loaded[1] = lookupState(stateTransitionLoaded[i]);
|
|
s.transition.ammo[0] = lookupState(stateTransitionNoAmmo[i]);
|
|
s.transition.ammo[1] = lookupState(stateTransitionAmmo[i]);
|
|
s.transition.target[0] = lookupState(stateTransitionNoTarget[i]);
|
|
s.transition.target[1] = lookupState(stateTransitionTarget[i]);
|
|
s.transition.wet[0] = lookupState(stateTransitionNotWet[i]);
|
|
s.transition.wet[1] = lookupState(stateTransitionWet[i]);
|
|
s.transition.trigger[0] = lookupState(stateTransitionTriggerUp[i]);
|
|
s.transition.trigger[1] = lookupState(stateTransitionTriggerDown[i]);
|
|
s.transition.timeout = lookupState(stateTransitionTimeout[i]);
|
|
s.waitForTimeout = stateWaitForTimeout[i];
|
|
s.timeoutValue = stateTimeoutValue[i];
|
|
s.fire = stateFire[i];
|
|
s.ejectShell = stateEjectShell[i];
|
|
s.energyDrain = stateEnergyDrain[i];
|
|
s.allowImageChange = stateAllowImageChange[i];
|
|
s.scaleAnimation = stateScaleAnimation[i];
|
|
s.direction = stateDirection[i];
|
|
s.loaded = stateLoaded[i];
|
|
s.spin = stateSpin[i];
|
|
s.recoil = stateRecoil[i];
|
|
s.sequence = -1; // Sequence is resolved in load
|
|
s.sequenceVis = -1; // Vis Sequence is resolved in load
|
|
s.sound = stateSound[i];
|
|
s.script = stateScript[i];
|
|
s.emitter = stateEmitter[i];
|
|
s.emitterTime = stateEmitterTime[i];
|
|
s.emitterNode = -1; // Sequnce is resolved in load
|
|
}
|
|
|
|
// The first state marked as "fire" is the state entered on the
|
|
// client when it recieves a fire event.
|
|
if (s.fire && fireState == -1)
|
|
fireState = i;
|
|
}
|
|
|
|
// Always preload images, this is needed to avoid problems with
|
|
// resolving sequences before transmission to a client.
|
|
return true;
|
|
}
|
|
|
|
bool ShapeBaseImageData::preload(bool server, char errorBuffer[256])
|
|
{
|
|
if (!Parent::preload(server, errorBuffer))
|
|
return false;
|
|
|
|
// Resolve objects transmitted from server
|
|
if (!server) {
|
|
if (projectile)
|
|
if (Sim::findObject(SimObjectId(projectile), projectile) == false)
|
|
Con::errorf(ConsoleLogEntry::General, "Error, unable to load projectile for shapebaseimagedata");
|
|
|
|
for (U32 i = 0; i < MaxStates; i++) {
|
|
if (state[i].emitter)
|
|
if (!Sim::findObject(SimObjectId(state[i].emitter), state[i].emitter))
|
|
Con::errorf(ConsoleLogEntry::General, "Error, unable to load emitter for image datablock");
|
|
if (state[i].sound)
|
|
if (!Sim::findObject(SimObjectId(state[i].sound), state[i].sound))
|
|
Con::errorf(ConsoleLogEntry::General, "Error, unable to load sound profile for image datablock");
|
|
}
|
|
}
|
|
|
|
// Use the first person eye offset if it's set.
|
|
useEyeOffset = !eyeOffset.isIdentity();
|
|
|
|
if (shapeName && shapeName[0]) {
|
|
// Resolve shapename
|
|
shape = ResourceManager->load(shapeName, computeCRC);
|
|
if (!bool(shape)) {
|
|
dSprintf(errorBuffer, 256, "Unable to load shape: %s", shapeName);
|
|
return false;
|
|
}
|
|
if(computeCRC)
|
|
{
|
|
Con::printf("Validation required for shape: %s", shapeName);
|
|
if(server)
|
|
mCRC = shape.getCRC();
|
|
else if(mCRC != shape.getCRC())
|
|
{
|
|
dSprintf(errorBuffer, 256, "Shape \"%s\" does not match version on server.",shapeName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Resolve nodes & build mount transform
|
|
ejectNode = shape->findNode("ejectPoint");
|
|
muzzleNode = shape->findNode("muzzlePoint");
|
|
retractNode = shape->findNode("retractionPoint");
|
|
mountTransform = mountOffset;
|
|
S32 node = shape->findNode("mountPoint");
|
|
if (node != -1) {
|
|
MatrixF total(1);
|
|
do {
|
|
MatrixF nmat;
|
|
QuatF q;
|
|
TSTransform::setMatrix(shape->defaultRotations[node].getQuatF(&q),shape->defaultTranslations[node],&nmat);
|
|
total.mul(nmat);
|
|
node = shape->nodes[node].parentIndex;
|
|
}
|
|
while(node != -1);
|
|
total.inverse();
|
|
mountTransform.mul(total);
|
|
}
|
|
|
|
// Resolve state sequence names & emitter nodes
|
|
isAnimated = false;
|
|
hasFlash = false;
|
|
for (U32 i = 0; i < MaxStates; i++) {
|
|
StateData& s = state[i];
|
|
if (stateSequence[i] && stateSequence[i][0])
|
|
s.sequence = shape->findSequence(stateSequence[i]);
|
|
if (s.sequence != -1)
|
|
{
|
|
isAnimated = true;
|
|
}
|
|
|
|
if (stateSequence[i] && stateSequence[i][0] && stateSequenceRandomFlash[i]) {
|
|
char bufferVis[128];
|
|
dStrncpy(bufferVis, stateSequence[i], 100);
|
|
dStrcat(bufferVis, "_vis");
|
|
s.sequenceVis = shape->findSequence(bufferVis);
|
|
}
|
|
if (s.sequenceVis != -1)
|
|
{
|
|
s.flashSequence = true;
|
|
hasFlash = true;
|
|
}
|
|
s.ignoreLoadedForReady = stateIgnoreLoadedForReady[i];
|
|
|
|
if (stateEmitterNode[i] && stateEmitterNode[i][0])
|
|
s.emitterNode = shape->findNode(stateEmitterNode[i]);
|
|
if (s.emitterNode == -1)
|
|
s.emitterNode = muzzleNode;
|
|
}
|
|
ambientSequence = shape->findSequence("ambient");
|
|
spinSequence = shape->findSequence("spin");
|
|
}
|
|
else {
|
|
dSprintf(errorBuffer, sizeof(errorBuffer), "Bad Datablock from server");
|
|
return false;
|
|
}
|
|
|
|
if( !casing && casingID != 0 )
|
|
{
|
|
if( !Sim::findObject( SimObjectId( casingID ), casing ) )
|
|
{
|
|
Con::errorf( ConsoleLogEntry::General, "ShapeBaseImageData::preload: Invalid packet, bad datablockId(casing): 0x%x", casingID );
|
|
}
|
|
}
|
|
|
|
|
|
TSShapeInstance* pDummy = new TSShapeInstance(shape, !server);
|
|
delete pDummy;
|
|
return true;
|
|
}
|
|
|
|
S32 ShapeBaseImageData::lookupState(const char* name)
|
|
{
|
|
if (!name || !name[0])
|
|
return -1;
|
|
for (U32 i = 0; i < MaxStates; i++)
|
|
if (stateName[i] && !dStricmp(name,stateName[i]))
|
|
return i;
|
|
Con::errorf(ConsoleLogEntry::General,"ShapeBaseImageData:: Could not resolve state \"%s\" for image \"%s\"",name,getName());
|
|
return 0;
|
|
}
|
|
|
|
static EnumTable::Enums imageLightEnum[] =
|
|
{
|
|
{ ShapeBaseImageData::NoLight, "NoLight" },
|
|
{ ShapeBaseImageData::ConstantLight, "ConstantLight" },
|
|
{ ShapeBaseImageData::PulsingLight, "PulsingLight" },
|
|
{ ShapeBaseImageData::WeaponFireLight, "WeaponFireLight" }
|
|
};
|
|
|
|
static EnumTable gImageLightTypeTable(ShapeBaseImageData::NumLightTypes, &imageLightEnum[0]);
|
|
|
|
void ShapeBaseImageData::initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
addField("emap", TypeBool, Offset(emap, ShapeBaseImageData));
|
|
addField("shapeFile", TypeFilename, Offset(shapeName, ShapeBaseImageData));
|
|
|
|
addField("projectile", TypeProjectileDataPtr, Offset(projectile, ShapeBaseImageData));
|
|
|
|
addField("cloakable", TypeBool, Offset(cloakable, ShapeBaseImageData));
|
|
|
|
addField("mountPoint", TypeS32, Offset(mountPoint,ShapeBaseImageData));
|
|
addField("offset", TypeMatrixPosition, Offset(mountOffset,ShapeBaseImageData));
|
|
addField("rotation", TypeMatrixRotation, Offset(mountOffset,ShapeBaseImageData));
|
|
addField("eyeOffset", TypeMatrixPosition, Offset(eyeOffset,ShapeBaseImageData));
|
|
addField("eyeRotation", TypeMatrixRotation, Offset(eyeOffset,ShapeBaseImageData));
|
|
addField("correctMuzzleVector", TypeBool, Offset(correctMuzzleVector, ShapeBaseImageData));
|
|
addField("firstPerson", TypeBool, Offset(firstPerson, ShapeBaseImageData));
|
|
addField("mass", TypeF32, Offset(mass, ShapeBaseImageData));
|
|
|
|
addField("usesEnergy", TypeBool, Offset(usesEnergy,ShapeBaseImageData));
|
|
addField("minEnergy", TypeF32, Offset(minEnergy,ShapeBaseImageData));
|
|
addField("accuFire", TypeBool, Offset(accuFire, ShapeBaseImageData));
|
|
|
|
addField("lightType", TypeEnum, Offset(lightType, ShapeBaseImageData), 1, &gImageLightTypeTable);
|
|
addField("lightColor", TypeColorF, Offset(lightColor, ShapeBaseImageData));
|
|
addField("lightTime", TypeS32, Offset(lightTime, ShapeBaseImageData));
|
|
addField("lightRadius", TypeF32, Offset(lightRadius, ShapeBaseImageData));
|
|
|
|
addField("casing", TypeDebrisDataPtr, Offset(casing, ShapeBaseImageData));
|
|
addField("shellExitDir", TypePoint3F, Offset(shellExitDir, ShapeBaseImageData));
|
|
addField("shellExitVariance", TypeF32, Offset(shellExitVariance, ShapeBaseImageData));
|
|
addField("shellVelocity", TypeF32, Offset(shellVelocity, ShapeBaseImageData));
|
|
|
|
// State arrays
|
|
addField("stateName", TypeCaseString, Offset(stateName, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnLoaded", TypeString, Offset(stateTransitionLoaded, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnNotLoaded", TypeString, Offset(stateTransitionNotLoaded, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnAmmo", TypeString, Offset(stateTransitionAmmo, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnNoAmmo", TypeString, Offset(stateTransitionNoAmmo, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnTarget", TypeString, Offset(stateTransitionTarget, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnNoTarget", TypeString, Offset(stateTransitionNoTarget, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnWet", TypeString, Offset(stateTransitionWet, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnNotWet", TypeString, Offset(stateTransitionNotWet, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnTriggerUp", TypeString, Offset(stateTransitionTriggerUp, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnTriggerDown", TypeString, Offset(stateTransitionTriggerDown, ShapeBaseImageData), MaxStates);
|
|
addField("stateTransitionOnTimeout", TypeString, Offset(stateTransitionTimeout, ShapeBaseImageData), MaxStates);
|
|
addField("stateTimeoutValue", TypeF32, Offset(stateTimeoutValue, ShapeBaseImageData), MaxStates);
|
|
addField("stateWaitForTimeout", TypeBool, Offset(stateWaitForTimeout, ShapeBaseImageData), MaxStates);
|
|
addField("stateFire", TypeBool, Offset(stateFire, ShapeBaseImageData), MaxStates);
|
|
addField("stateEjectShell", TypeBool, Offset(stateEjectShell, ShapeBaseImageData), MaxStates);
|
|
addField("stateEnergyDrain", TypeF32, Offset(stateEnergyDrain, ShapeBaseImageData), MaxStates);
|
|
addField("stateAllowImageChange", TypeBool, Offset(stateAllowImageChange, ShapeBaseImageData), MaxStates);
|
|
addField("stateDirection", TypeBool, Offset(stateDirection, ShapeBaseImageData), MaxStates);
|
|
addField("stateLoadedFlag", TypeEnum, Offset(stateLoaded, ShapeBaseImageData), MaxStates, &EnumLoadedState);
|
|
addField("stateSpinThread", TypeEnum, Offset(stateSpin, ShapeBaseImageData), MaxStates, &EnumSpinState);
|
|
addField("stateRecoil", TypeEnum, Offset(stateRecoil, ShapeBaseImageData), MaxStates, &EnumRecoilState);
|
|
addField("stateSequence", TypeString, Offset(stateSequence, ShapeBaseImageData), MaxStates);
|
|
addField("stateSequenceRandomFlash", TypeBool, Offset(stateSequenceRandomFlash, ShapeBaseImageData), MaxStates);
|
|
addField("stateSound", TypeAudioProfilePtr, Offset(stateSound, ShapeBaseImageData), MaxStates);
|
|
addField("stateScript", TypeCaseString, Offset(stateScript, ShapeBaseImageData), MaxStates);
|
|
addField("stateEmitter", TypeParticleEmitterDataPtr, Offset(stateEmitter, ShapeBaseImageData), MaxStates);
|
|
addField("stateEmitterTime", TypeF32, Offset(stateEmitterTime, ShapeBaseImageData), MaxStates);
|
|
addField("stateEmitterNode", TypeString, Offset(stateEmitterNode, ShapeBaseImageData), MaxStates);
|
|
addField("stateIgnoreLoadedForReady", TypeBool, Offset(stateIgnoreLoadedForReady, ShapeBaseImageData), MaxStates);
|
|
addField("computeCRC", TypeBool, Offset(computeCRC, ShapeBaseImageData));
|
|
}
|
|
|
|
void ShapeBaseImageData::packData(BitStream* stream)
|
|
{
|
|
Parent::packData(stream);
|
|
|
|
if(stream->writeFlag(computeCRC))
|
|
stream->write(mCRC);
|
|
|
|
stream->writeFlag(emap);
|
|
|
|
stream->writeString(shapeName);
|
|
stream->write(mountPoint);
|
|
if (!stream->writeFlag(mountOffset.isIdentity()))
|
|
stream->writeAffineTransform(mountOffset);
|
|
if (!stream->writeFlag(eyeOffset.isIdentity()))
|
|
stream->writeAffineTransform(eyeOffset);
|
|
|
|
stream->writeFlag(correctMuzzleVector);
|
|
stream->writeFlag(firstPerson);
|
|
stream->write(mass);
|
|
stream->writeFlag(usesEnergy);
|
|
stream->write(minEnergy);
|
|
stream->writeFlag(hasFlash);
|
|
// Client doesn't need accuFire
|
|
|
|
// Write the projectile datablock
|
|
if (stream->writeFlag(projectile))
|
|
stream->writeRangedU32(packed? SimObjectId(projectile):
|
|
projectile->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
|
|
|
stream->writeFlag(cloakable);
|
|
stream->writeRangedU32(lightType, 0, NumLightTypes-1);
|
|
if(lightType != NoLight)
|
|
{
|
|
stream->write(lightRadius);
|
|
stream->write(lightTime);
|
|
stream->writeFloat(lightColor.red, 7);
|
|
stream->writeFloat(lightColor.green, 7);
|
|
stream->writeFloat(lightColor.blue, 7);
|
|
stream->writeFloat(lightColor.alpha, 7);
|
|
}
|
|
|
|
mathWrite( *stream, shellExitDir );
|
|
stream->write(shellExitVariance);
|
|
stream->write(shellVelocity);
|
|
|
|
if( stream->writeFlag( casing ) )
|
|
{
|
|
stream->writeRangedU32(packed? SimObjectId(casing):
|
|
casing->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
|
}
|
|
|
|
for (U32 i = 0; i < MaxStates; i++)
|
|
if (stream->writeFlag(state[i].name && state[i].name[0])) {
|
|
StateData& s = state[i];
|
|
// States info not needed on the client:
|
|
// s.allowImageChange
|
|
// s.scriptNames
|
|
// Transitions are inc. one to account for -1 values
|
|
stream->writeString(state[i].name);
|
|
|
|
stream->writeInt(s.transition.loaded[0]+1,NumStateBits);
|
|
stream->writeInt(s.transition.loaded[1]+1,NumStateBits);
|
|
stream->writeInt(s.transition.ammo[0]+1,NumStateBits);
|
|
stream->writeInt(s.transition.ammo[1]+1,NumStateBits);
|
|
stream->writeInt(s.transition.target[0]+1,NumStateBits);
|
|
stream->writeInt(s.transition.target[1]+1,NumStateBits);
|
|
stream->writeInt(s.transition.wet[0]+1,NumStateBits);
|
|
stream->writeInt(s.transition.wet[1]+1,NumStateBits);
|
|
stream->writeInt(s.transition.trigger[0]+1,NumStateBits);
|
|
stream->writeInt(s.transition.trigger[1]+1,NumStateBits);
|
|
stream->writeInt(s.transition.timeout+1,NumStateBits);
|
|
|
|
if(stream->writeFlag(s.timeoutValue != gDefaultStateData.timeoutValue))
|
|
stream->write(s.timeoutValue);
|
|
|
|
stream->writeFlag(s.waitForTimeout);
|
|
stream->writeFlag(s.fire);
|
|
stream->writeFlag(s.ejectShell);
|
|
stream->writeFlag(s.scaleAnimation);
|
|
stream->writeFlag(s.direction);
|
|
if(stream->writeFlag(s.energyDrain != gDefaultStateData.energyDrain))
|
|
stream->write(s.energyDrain);
|
|
|
|
stream->writeInt(s.loaded,StateData::NumLoadedBits);
|
|
stream->writeInt(s.spin,StateData::NumSpinBits);
|
|
stream->writeInt(s.recoil,StateData::NumRecoilBits);
|
|
if(stream->writeFlag(s.sequence != gDefaultStateData.sequence))
|
|
stream->writeSignedInt(s.sequence, 16);
|
|
|
|
if(stream->writeFlag(s.sequenceVis != gDefaultStateData.sequenceVis))
|
|
stream->writeSignedInt(s.sequenceVis,16);
|
|
stream->writeFlag(s.flashSequence);
|
|
stream->writeFlag(s.ignoreLoadedForReady);
|
|
|
|
if (stream->writeFlag(s.emitter)) {
|
|
stream->writeRangedU32(packed? SimObjectId(s.emitter):
|
|
s.emitter->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
|
stream->write(s.emitterTime);
|
|
stream->write(s.emitterNode);
|
|
}
|
|
|
|
if (stream->writeFlag(s.sound))
|
|
stream->writeRangedU32(packed? SimObjectId(s.sound):
|
|
s.sound->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
|
}
|
|
}
|
|
|
|
void ShapeBaseImageData::unpackData(BitStream* stream)
|
|
{
|
|
Parent::unpackData(stream);
|
|
computeCRC = stream->readFlag();
|
|
if(computeCRC)
|
|
stream->read(&mCRC);
|
|
|
|
emap = stream->readFlag();
|
|
|
|
shapeName = stream->readSTString();
|
|
stream->read(&mountPoint);
|
|
if (stream->readFlag())
|
|
mountOffset.identity();
|
|
else
|
|
stream->readAffineTransform(&mountOffset);
|
|
if (stream->readFlag())
|
|
eyeOffset.identity();
|
|
else
|
|
stream->readAffineTransform(&eyeOffset);
|
|
|
|
correctMuzzleVector = stream->readFlag();
|
|
firstPerson = stream->readFlag();
|
|
stream->read(&mass);
|
|
usesEnergy = stream->readFlag();
|
|
stream->read(&minEnergy);
|
|
hasFlash = stream->readFlag();
|
|
|
|
projectile = (stream->readFlag() ?
|
|
(ProjectileData*)stream->readRangedU32(DataBlockObjectIdFirst,
|
|
DataBlockObjectIdLast) : 0);
|
|
|
|
cloakable = stream->readFlag();
|
|
lightType = stream->readRangedU32(0, NumLightTypes-1);
|
|
if(lightType != NoLight)
|
|
{
|
|
stream->read(&lightRadius);
|
|
stream->read(&lightTime);
|
|
lightColor.red = stream->readFloat(7);
|
|
lightColor.green = stream->readFloat(7);
|
|
lightColor.blue = stream->readFloat(7);
|
|
lightColor.alpha = stream->readFloat(7);
|
|
}
|
|
|
|
mathRead( *stream, &shellExitDir );
|
|
stream->read(&shellExitVariance);
|
|
stream->read(&shellVelocity);
|
|
|
|
if(stream->readFlag())
|
|
{
|
|
casingID = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
|
|
}
|
|
|
|
for (U32 i = 0; i < MaxStates; i++) {
|
|
if (stream->readFlag()) {
|
|
StateData& s = state[i];
|
|
// States info not needed on the client:
|
|
// s.allowImageChange
|
|
// s.scriptNames
|
|
// Transitions are dec. one to restore -1 values
|
|
s.name = stream->readSTString();
|
|
|
|
s.transition.loaded[0] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.loaded[1] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.ammo[0] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.ammo[1] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.target[0] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.target[1] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.wet[0] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.wet[1] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.trigger[0] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.trigger[1] = stream->readInt(NumStateBits) - 1;
|
|
s.transition.timeout = stream->readInt(NumStateBits) - 1;
|
|
if(stream->readFlag())
|
|
stream->read(&s.timeoutValue);
|
|
else
|
|
s.timeoutValue = gDefaultStateData.timeoutValue;
|
|
|
|
s.waitForTimeout = stream->readFlag();
|
|
s.fire = stream->readFlag();
|
|
s.ejectShell = stream->readFlag();
|
|
s.scaleAnimation = stream->readFlag();
|
|
s.direction = stream->readFlag();
|
|
if(stream->readFlag())
|
|
stream->read(&s.energyDrain);
|
|
else
|
|
s.energyDrain = gDefaultStateData.energyDrain;
|
|
|
|
s.loaded = (StateData::LoadedState)stream->readInt(StateData::NumLoadedBits);
|
|
s.spin = (StateData::SpinState)stream->readInt(StateData::NumSpinBits);
|
|
s.recoil = (StateData::RecoilState)stream->readInt(StateData::NumRecoilBits);
|
|
if(stream->readFlag())
|
|
s.sequence = stream->readSignedInt(16);
|
|
else
|
|
s.sequence = gDefaultStateData.sequence;
|
|
|
|
if(stream->readFlag())
|
|
s.sequenceVis = stream->readSignedInt(16);
|
|
else
|
|
s.sequenceVis = gDefaultStateData.sequenceVis;
|
|
|
|
s.flashSequence = stream->readFlag();
|
|
s.ignoreLoadedForReady = stream->readFlag();
|
|
|
|
if (stream->readFlag()) {
|
|
s.emitter = (ParticleEmitterData*) stream->readRangedU32(DataBlockObjectIdFirst,
|
|
DataBlockObjectIdLast);
|
|
stream->read(&s.emitterTime);
|
|
stream->read(&s.emitterNode);
|
|
}
|
|
else
|
|
s.emitter = 0;
|
|
s.sound = stream->readFlag()? (AudioProfile*) stream->readRangedU32(DataBlockObjectIdFirst,
|
|
DataBlockObjectIdLast): 0;
|
|
}
|
|
}
|
|
statesLoaded = true;
|
|
}
|
|
|
|
void ShapeBase::MountedImage::registerImageLights(LightManager * lightManager, bool lightingScene, const Point3F &objectPosition, U32 startTime)
|
|
{
|
|
if(lightingScene)
|
|
return;
|
|
|
|
F32 intensity;
|
|
|
|
F32 delta = Sim::getCurrentTime() - startTime;
|
|
|
|
switch(dataBlock->lightType)
|
|
{
|
|
case ShapeBaseImageData::ConstantLight:
|
|
intensity = 1.f;
|
|
break;
|
|
|
|
case ShapeBaseImageData::PulsingLight:
|
|
{
|
|
intensity = 0.5f + 0.5f * mSin(M_PI * delta / F32(dataBlock->lightTime));
|
|
intensity = 0.15f + intensity * 0.85f;
|
|
break;
|
|
}
|
|
|
|
case ShapeBaseImageData::WeaponFireLight:
|
|
{
|
|
if (delta > dataBlock->lightTime)
|
|
return;
|
|
intensity = 1.0 - F32(delta) / F32(dataBlock->lightTime);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
intensity = 1.0f;
|
|
return;
|
|
}
|
|
|
|
mLight.mColor = dataBlock->lightColor * intensity;
|
|
mLight.mColor.clamp();
|
|
mLight.mType = LightInfo::Point;
|
|
mLight.mRadius = dataBlock->lightRadius;
|
|
|
|
//get the light source position
|
|
Point3F mountOffset;
|
|
dataBlock->mountTransform.getColumn(3, &mountOffset);
|
|
mLight.mPos = objectPosition + mountOffset;
|
|
|
|
lightManager->sgRegisterGlobalLight(&mLight);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
ShapeBase::MountedImage::MountedImage()
|
|
{
|
|
shapeInstance = 0;
|
|
state = 0;
|
|
dataBlock = 0;
|
|
nextImage = InvalidImagePtr;
|
|
animSound = 0;
|
|
delayTime = 0;
|
|
ammo = false;
|
|
target = false;
|
|
triggerDown = false;
|
|
loaded = false;
|
|
fireCount = 0;
|
|
wet = false;
|
|
}
|
|
|
|
ShapeBase::MountedImage::~MountedImage()
|
|
{
|
|
delete shapeInstance;
|
|
|
|
// stop sound
|
|
if(animLoopingSound && (animSound != NULL_AUDIOHANDLE))
|
|
alxStop(animSound);
|
|
|
|
for (S32 i = 0; i < MaxImageEmitters; i++)
|
|
if (bool(emitter[i].emitter))
|
|
emitter[i].emitter->deleteWhenEmpty();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Any item with an item image is selectable
|
|
|
|
bool ShapeBase::mountImage(ShapeBaseImageData* imageData,U32 imageSlot,bool loaded,StringHandle &skinNameHandle)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock) {
|
|
if ((image.dataBlock == imageData) && (image.skinNameHandle == skinNameHandle)) {
|
|
// Image already loaded
|
|
image.nextImage = InvalidImagePtr;
|
|
return true;
|
|
}
|
|
}
|
|
//
|
|
setImage(imageSlot,imageData,skinNameHandle,loaded);
|
|
|
|
//see if the image has a light source
|
|
if (imageData->lightType != ShapeBaseImageData::NoLight)
|
|
Sim::getLightSet()->addObject(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ShapeBase::unmountImage(U32 imageSlot)
|
|
{
|
|
bool returnValue = false;
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock)
|
|
{
|
|
StringHandle temp;
|
|
setImage(imageSlot,0, temp);
|
|
returnValue = true;
|
|
}
|
|
|
|
//see if we're still part of the light group
|
|
bool found = false;
|
|
for (S32 i = 0; i < MaxMountedImages; i++)
|
|
{
|
|
ShapeBaseImageData* imageData = getMountedImage(i);
|
|
if (imageData != NULL && imageData->lightType != ShapeBaseImageData::NoLight)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
Sim::getLightSet()->removeObject(this);
|
|
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
ShapeBaseImageData* ShapeBase::getMountedImage(U32 imageSlot)
|
|
{
|
|
return mMountedImageList[imageSlot].dataBlock;
|
|
}
|
|
|
|
|
|
ShapeBase::MountedImage* ShapeBase::getImageStruct(U32 imageSlot)
|
|
{
|
|
return &mMountedImageList[imageSlot];
|
|
}
|
|
|
|
|
|
ShapeBaseImageData* ShapeBase::getPendingImage(U32 imageSlot)
|
|
{
|
|
ShapeBaseImageData* data = mMountedImageList[imageSlot].nextImage;
|
|
return (data == InvalidImagePtr)? 0: data;
|
|
}
|
|
|
|
bool ShapeBase::isImageFiring(U32 imageSlot)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
return image.dataBlock && image.state->fire;
|
|
}
|
|
|
|
bool ShapeBase::isImageReady(U32 imageSlot,U32 ns,U32 depth)
|
|
{
|
|
// Will pressing the trigger lead to a fire state?
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (depth++ > 5 || !image.dataBlock)
|
|
return false;
|
|
ShapeBaseImageData::StateData& stateData = (ns == -1) ?
|
|
*image.state : image.dataBlock->state[ns];
|
|
if (stateData.fire)
|
|
return true;
|
|
|
|
// Try the transitions...
|
|
if (stateData.ignoreLoadedForReady == true) {
|
|
if ((ns = stateData.transition.loaded[true]) != -1)
|
|
if (isImageReady(imageSlot,ns,depth))
|
|
return true;
|
|
} else {
|
|
if ((ns = stateData.transition.loaded[image.loaded]) != -1)
|
|
if (isImageReady(imageSlot,ns,depth))
|
|
return true;
|
|
}
|
|
if ((ns = stateData.transition.ammo[image.ammo]) != -1)
|
|
if (isImageReady(imageSlot,ns,depth))
|
|
return true;
|
|
if ((ns = stateData.transition.target[image.target]) != -1)
|
|
if (isImageReady(imageSlot,ns,depth))
|
|
return true;
|
|
if ((ns = stateData.transition.wet[image.wet]) != -1)
|
|
if (isImageReady(imageSlot,ns,depth))
|
|
return true;
|
|
if ((ns = stateData.transition.trigger[1]) != -1)
|
|
if (isImageReady(imageSlot,ns,depth))
|
|
return true;
|
|
if ((ns = stateData.transition.timeout) != -1)
|
|
if (isImageReady(imageSlot,ns,depth))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::isImageMounted(ShapeBaseImageData* imageData)
|
|
{
|
|
for (U32 i = 0; i < MaxMountedImages; i++)
|
|
if (imageData == mMountedImageList[i].dataBlock)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
S32 ShapeBase::getMountSlot(ShapeBaseImageData* imageData)
|
|
{
|
|
for (U32 i = 0; i < MaxMountedImages; i++)
|
|
if (imageData == mMountedImageList[i].dataBlock)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
StringHandle ShapeBase::getImageSkinTag(U32 imageSlot)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
return image.dataBlock? image.skinNameHandle : StringHandle();
|
|
}
|
|
|
|
const char* ShapeBase::getImageState(U32 imageSlot)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
return image.dataBlock? image.state->name: 0;
|
|
}
|
|
|
|
void ShapeBase::setImageAmmoState(U32 imageSlot,bool ammo)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock && !image.dataBlock->usesEnergy && image.ammo != ammo) {
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
image.ammo = ammo;
|
|
}
|
|
}
|
|
|
|
bool ShapeBase::getImageAmmoState(U32 imageSlot)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (!image.dataBlock)
|
|
return false;
|
|
return image.ammo;
|
|
}
|
|
|
|
void ShapeBase::setImageWetState(U32 imageSlot,bool wet)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock && image.wet != wet) {
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
image.wet = wet;
|
|
}
|
|
}
|
|
|
|
bool ShapeBase::getImageWetState(U32 imageSlot)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (!image.dataBlock)
|
|
return false;
|
|
return image.wet;
|
|
}
|
|
|
|
void ShapeBase::setImageLoadedState(U32 imageSlot,bool loaded)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock && image.loaded != loaded) {
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
image.loaded = loaded;
|
|
}
|
|
}
|
|
|
|
bool ShapeBase::getImageLoadedState(U32 imageSlot)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (!image.dataBlock)
|
|
return false;
|
|
return image.loaded;
|
|
}
|
|
|
|
void ShapeBase::getMuzzleVector(U32 imageSlot,VectorF* vec)
|
|
{
|
|
MatrixF mat;
|
|
getMuzzleTransform(imageSlot,&mat);
|
|
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock->correctMuzzleVector)
|
|
if (GameConnection * gc = getControllingClient())
|
|
if (gc->isFirstPerson() && !gc->isAIControlled())
|
|
if (getCorrectedAim(mat, vec))
|
|
return;
|
|
|
|
mat.getColumn(1,vec);
|
|
}
|
|
|
|
void ShapeBase::getMuzzlePoint(U32 imageSlot,Point3F* pos)
|
|
{
|
|
MatrixF mat;
|
|
getMuzzleTransform(imageSlot,&mat);
|
|
mat.getColumn(3,pos);
|
|
}
|
|
|
|
|
|
void ShapeBase::getRenderMuzzleVector(U32 imageSlot,VectorF* vec)
|
|
{
|
|
MatrixF mat;
|
|
getRenderMuzzleTransform(imageSlot,&mat);
|
|
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock->correctMuzzleVector)
|
|
if (GameConnection * gc = getControllingClient())
|
|
if (gc->isFirstPerson() && !gc->isAIControlled())
|
|
if (getCorrectedAim(mat, vec))
|
|
return;
|
|
|
|
mat.getColumn(1,vec);
|
|
}
|
|
|
|
void ShapeBase::getRenderMuzzlePoint(U32 imageSlot,Point3F* pos)
|
|
{
|
|
MatrixF mat;
|
|
getRenderMuzzleTransform(imageSlot,&mat);
|
|
mat.getColumn(3,pos);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::scriptCallback(U32 imageSlot,const char* function)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
char buff1[32];
|
|
dSprintf(buff1,sizeof(buff1),"%d",imageSlot);
|
|
Con::executef(image.dataBlock, 3, function,scriptThis(),buff1);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::getMountTransform(U32 mountPoint,MatrixF* mat)
|
|
{
|
|
// Returns mount point to world space transform
|
|
if (mountPoint < ShapeBaseData::NumMountPoints) {
|
|
S32 ni = mDataBlock->mountPointNode[mountPoint];
|
|
if (ni != -1) {
|
|
MatrixF mountTransform = mShapeInstance->mNodeTransforms[ni];
|
|
const Point3F& scale = getScale();
|
|
|
|
// The position of the mount point needs to be scaled.
|
|
Point3F position = mountTransform.getPosition();
|
|
position.convolve( scale );
|
|
mountTransform.setPosition( position );
|
|
|
|
// Also we would like the object to be scaled to the model.
|
|
mat->mul(mObjToWorld, mountTransform);
|
|
return;
|
|
}
|
|
}
|
|
*mat = mObjToWorld;
|
|
}
|
|
|
|
void ShapeBase::getImageTransform(U32 imageSlot,MatrixF* mat)
|
|
{
|
|
// Image transform in world space
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock) {
|
|
ShapeBaseImageData& data = *image.dataBlock;
|
|
|
|
MatrixF nmat;
|
|
if (data.useEyeOffset && isFirstPerson()) {
|
|
getEyeTransform(&nmat);
|
|
mat->mul(nmat,data.eyeOffset);
|
|
}
|
|
else {
|
|
getMountTransform(image.dataBlock->mountPoint,&nmat);
|
|
mat->mul(nmat,data.mountTransform);
|
|
}
|
|
}
|
|
else
|
|
*mat = mObjToWorld;
|
|
}
|
|
|
|
void ShapeBase::getImageTransform(U32 imageSlot,S32 node,MatrixF* mat)
|
|
{
|
|
// Muzzle transform in world space
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock) {
|
|
if (node != -1) {
|
|
MatrixF imat;
|
|
getImageTransform(imageSlot,&imat);
|
|
mat->mul(imat,image.shapeInstance->mNodeTransforms[node]);
|
|
}
|
|
else
|
|
getImageTransform(imageSlot,mat);
|
|
}
|
|
else
|
|
*mat = mObjToWorld;
|
|
}
|
|
|
|
void ShapeBase::getImageTransform(U32 imageSlot,StringTableEntry nodeName,MatrixF* mat)
|
|
{
|
|
getImageTransform( imageSlot, getNodeIndex( imageSlot, nodeName ), mat );
|
|
}
|
|
|
|
void ShapeBase::getMuzzleTransform(U32 imageSlot,MatrixF* mat)
|
|
{
|
|
// Muzzle transform in world space
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock)
|
|
getImageTransform(imageSlot,image.dataBlock->muzzleNode,mat);
|
|
else
|
|
*mat = mObjToWorld;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::getRenderMountTransform(U32 mountPoint,MatrixF* mat)
|
|
{
|
|
// Returns mount point to world space transform
|
|
if (mountPoint < ShapeBaseData::NumMountPoints) {
|
|
S32 ni = mDataBlock->mountPointNode[mountPoint];
|
|
if (ni != -1) {
|
|
MatrixF mountTransform = mShapeInstance->mNodeTransforms[ni];
|
|
const Point3F& scale = getScale();
|
|
|
|
// The position of the mount point needs to be scaled.
|
|
Point3F position = mountTransform.getPosition();
|
|
position.convolve( scale );
|
|
mountTransform.setPosition( position );
|
|
|
|
// Also we would like the object to be scaled to the model.
|
|
mountTransform.scale( scale );
|
|
mat->mul(getRenderTransform(), mountTransform);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Return origin by default.
|
|
*mat = getRenderTransform();
|
|
}
|
|
|
|
|
|
void ShapeBase::getRenderImageTransform(U32 imageSlot,MatrixF* mat)
|
|
{
|
|
// Image transform in world space
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock) {
|
|
ShapeBaseImageData& data = *image.dataBlock;
|
|
|
|
MatrixF nmat;
|
|
if (data.useEyeOffset && isFirstPerson()) {
|
|
getRenderEyeTransform(&nmat);
|
|
mat->mul(nmat,data.eyeOffset);
|
|
}
|
|
else {
|
|
getRenderMountTransform(data.mountPoint,&nmat);
|
|
mat->mul(nmat,data.mountTransform);
|
|
}
|
|
}
|
|
else
|
|
*mat = getRenderTransform();
|
|
}
|
|
|
|
void ShapeBase::getRenderImageTransform(U32 imageSlot,S32 node,MatrixF* mat)
|
|
{
|
|
// Muzzle transform in world space
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock) {
|
|
if (node != -1) {
|
|
MatrixF imat;
|
|
getRenderImageTransform(imageSlot,&imat);
|
|
mat->mul(imat,image.shapeInstance->mNodeTransforms[node]);
|
|
}
|
|
else
|
|
getRenderImageTransform(imageSlot,mat);
|
|
}
|
|
else
|
|
*mat = getRenderTransform();
|
|
}
|
|
|
|
void ShapeBase::getRenderImageTransform(U32 imageSlot,StringTableEntry nodeName,MatrixF* mat)
|
|
{
|
|
getRenderImageTransform( imageSlot, getNodeIndex( imageSlot, nodeName ), mat );
|
|
}
|
|
|
|
void ShapeBase::getRenderMuzzleTransform(U32 imageSlot,MatrixF* mat)
|
|
{
|
|
// Muzzle transform in world space
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock)
|
|
getRenderImageTransform(imageSlot,image.dataBlock->muzzleNode,mat);
|
|
else
|
|
*mat = getRenderTransform();
|
|
}
|
|
|
|
|
|
void ShapeBase::getRetractionTransform(U32 imageSlot,MatrixF* mat)
|
|
{
|
|
// Muzzle transform in world space
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock) {
|
|
if (image.dataBlock->retractNode != -1)
|
|
getImageTransform(imageSlot,image.dataBlock->retractNode,mat);
|
|
else
|
|
getImageTransform(imageSlot,image.dataBlock->muzzleNode,mat);
|
|
} else {
|
|
*mat = getTransform();
|
|
}
|
|
}
|
|
|
|
|
|
void ShapeBase::getRenderRetractionTransform(U32 imageSlot,MatrixF* mat)
|
|
{
|
|
// Muzzle transform in world space
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock) {
|
|
if (image.dataBlock->retractNode != -1)
|
|
getRenderImageTransform(imageSlot,image.dataBlock->retractNode,mat);
|
|
else
|
|
getRenderImageTransform(imageSlot,image.dataBlock->muzzleNode,mat);
|
|
} else {
|
|
*mat = getRenderTransform();
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
S32 ShapeBase::getNodeIndex(U32 imageSlot,StringTableEntry nodeName)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (image.dataBlock)
|
|
return image.dataBlock->shape->findNode(nodeName);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
// Modify muzzle if needed to aim at whatever is straight in front of eye. Let the
|
|
// caller know if we actually modified the result.
|
|
bool ShapeBase::getCorrectedAim(const MatrixF& muzzleMat, VectorF* result)
|
|
{
|
|
const F32 pullInD = 6.0;
|
|
const F32 maxAdjD = 500;
|
|
|
|
VectorF aheadVec(0, maxAdjD, 0);
|
|
|
|
MatrixF eyeMat;
|
|
Point3F eyePos;
|
|
getEyeTransform(&eyeMat);
|
|
eyeMat.getColumn(3, &eyePos);
|
|
eyeMat.mulV(aheadVec);
|
|
Point3F aheadPoint = (eyePos + aheadVec);
|
|
|
|
// Should we check if muzzle point is really close to eye? Does that happen?
|
|
Point3F muzzlePos;
|
|
muzzleMat.getColumn(3, &muzzlePos);
|
|
|
|
Point3F collidePoint;
|
|
VectorF collideVector;
|
|
disableCollision();
|
|
RayInfo rinfo;
|
|
if (getContainer()->castRay(eyePos, aheadPoint, 0xFFFFFFFF, &rinfo))
|
|
collideVector = ((collidePoint = rinfo.point) - eyePos);
|
|
else
|
|
collideVector = ((collidePoint = aheadPoint) - eyePos);
|
|
enableCollision();
|
|
|
|
// For close collision we want to NOT aim at ground since we're bending
|
|
// the ray here as it is. But we don't want to pop, so adjust continuously.
|
|
F32 lenSq = collideVector.lenSquared();
|
|
if (lenSq < (pullInD * pullInD) && lenSq > 0.04)
|
|
{
|
|
F32 len = mSqrt(lenSq);
|
|
F32 mid = pullInD; // (pullInD + len) / 2.0;
|
|
// This gives us point beyond to focus on-
|
|
collideVector *= (mid / len);
|
|
collidePoint = (eyePos + collideVector);
|
|
}
|
|
|
|
VectorF muzzleToCollide = (collidePoint - muzzlePos);
|
|
lenSq = muzzleToCollide.lenSquared();
|
|
if (lenSq > 0.04)
|
|
{
|
|
muzzleToCollide *= (1 / mSqrt(lenSq));
|
|
* result = muzzleToCollide;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::updateMass()
|
|
{
|
|
if (mDataBlock) {
|
|
F32 imass = 0;
|
|
for (U32 i = 0; i < MaxMountedImages; i++) {
|
|
MountedImage& image = mMountedImageList[i];
|
|
if (image.dataBlock)
|
|
imass += image.dataBlock->mass;
|
|
}
|
|
//
|
|
mMass = mDataBlock->mass + imass;
|
|
mOneOverMass = 1 / mMass;
|
|
}
|
|
}
|
|
|
|
void ShapeBase::onImageRecoil(U32,ShapeBaseImageData::StateData::RecoilState)
|
|
{
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::setImage(U32 imageSlot, ShapeBaseImageData* imageData, StringHandle& skinNameHandle, bool loaded, bool ammo, bool triggerDown, bool target)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
|
|
// If we already have this datablock...
|
|
if (image.dataBlock == imageData) {
|
|
// Mark that there is not a datablock change pending.
|
|
image.nextImage = InvalidImagePtr;
|
|
// Change the skin handle if necessary.
|
|
if (image.skinNameHandle != skinNameHandle) {
|
|
if (!isGhost()) {
|
|
// Serverside, note the skin handle and tell the client.
|
|
image.skinNameHandle = skinNameHandle;
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
}
|
|
else {
|
|
// Clientside, do the reskin.
|
|
image.skinNameHandle = skinNameHandle;
|
|
if (image.shapeInstance) {
|
|
image.shapeInstance->reSkin(skinNameHandle);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check to see if we need to delay image changes until state change.
|
|
if (!isGhost()) {
|
|
if (imageData && image.dataBlock && !image.state->allowImageChange) {
|
|
image.nextImage = imageData;
|
|
image.nextSkinNameHandle = skinNameHandle;
|
|
image.nextLoaded = loaded;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Mark that updates are happenin'.
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
|
|
// Notify script unmount since we're swapping datablocks.
|
|
if (image.dataBlock && !isGhost()) {
|
|
scriptCallback(imageSlot,"onUnmount");
|
|
}
|
|
|
|
// Stop anything currently going on with the image.
|
|
resetImageSlot(imageSlot);
|
|
|
|
// If we're just unselecting the current shape without swapping
|
|
// in a new one, then bail.
|
|
if (!imageData) {
|
|
return;
|
|
}
|
|
|
|
// Otherwise, init the new shape.
|
|
image.dataBlock = imageData;
|
|
image.state = &image.dataBlock->state[0];
|
|
image.skinNameHandle = skinNameHandle;
|
|
image.shapeInstance = new TSShapeInstance(image.dataBlock->shape, isClientObject());
|
|
if (isClientObject()) {
|
|
if (image.shapeInstance) {
|
|
image.shapeInstance->cloneMaterialList();
|
|
image.shapeInstance->reSkin(skinNameHandle);
|
|
}
|
|
}
|
|
image.loaded = loaded;
|
|
image.ammo = ammo;
|
|
image.triggerDown = triggerDown;
|
|
image.target = target;
|
|
|
|
// The server needs the shape loaded for muzzle mount nodes
|
|
// but it doesn't need to run any of the animations.
|
|
image.ambientThread = 0;
|
|
image.animThread = 0;
|
|
image.flashThread = 0;
|
|
image.spinThread = 0;
|
|
if (isGhost()) {
|
|
if (image.dataBlock->isAnimated) {
|
|
image.animThread = image.shapeInstance->addThread();
|
|
image.shapeInstance->setTimeScale(image.animThread,0);
|
|
}
|
|
if (image.dataBlock->hasFlash) {
|
|
image.flashThread = image.shapeInstance->addThread();
|
|
image.shapeInstance->setTimeScale(image.flashThread,0);
|
|
}
|
|
if (image.dataBlock->ambientSequence != -1) {
|
|
image.ambientThread = image.shapeInstance->addThread();
|
|
image.shapeInstance->setTimeScale(image.ambientThread,1);
|
|
image.shapeInstance->setSequence(image.ambientThread,
|
|
image.dataBlock->ambientSequence,0);
|
|
}
|
|
if (image.dataBlock->spinSequence != -1) {
|
|
image.spinThread = image.shapeInstance->addThread();
|
|
image.shapeInstance->setTimeScale(image.spinThread,1);
|
|
image.shapeInstance->setSequence(image.spinThread,
|
|
image.dataBlock->spinSequence,0);
|
|
}
|
|
}
|
|
|
|
// Set the image to its starting state.
|
|
setImageState(imageSlot, 0, true);
|
|
|
|
// Update the mass for the mount object.
|
|
updateMass();
|
|
|
|
// Notify script mount.
|
|
if (!isGhost()) {
|
|
scriptCallback(imageSlot,"onMount");
|
|
}
|
|
|
|
// Done.
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::resetImageSlot(U32 imageSlot)
|
|
{
|
|
// Clear out current image
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
delete image.shapeInstance;
|
|
image.shapeInstance = 0;
|
|
if (image.animSound) {
|
|
alxStop(image.animSound);
|
|
image.animSound = 0;
|
|
}
|
|
for (S32 i = 0; i < MaxImageEmitters; i++) {
|
|
MountedImage::ImageEmitter& em = image.emitter[i];
|
|
if (bool(em.emitter)) {
|
|
em.emitter->deleteWhenEmpty();
|
|
em.emitter = 0;
|
|
}
|
|
}
|
|
image.dataBlock = 0;
|
|
image.nextImage = InvalidImagePtr;
|
|
image.skinNameHandle = StringHandle();
|
|
image.nextSkinNameHandle = StringHandle();
|
|
image.state = 0;
|
|
image.delayTime = 0;
|
|
image.ammo = false;
|
|
image.triggerDown = false;
|
|
image.loaded = false;
|
|
image.lightStart = 0;
|
|
// image.light.fLight.fType = TSLight::LightInvalid;
|
|
updateMass();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool ShapeBase::getImageTriggerState(U32 imageSlot)
|
|
{
|
|
if (isGhost() || !mMountedImageList[imageSlot].dataBlock)
|
|
return false;
|
|
return mMountedImageList[imageSlot].triggerDown;
|
|
}
|
|
|
|
void ShapeBase::setImageTriggerState(U32 imageSlot,bool trigger)
|
|
{
|
|
if (isGhost() || !mMountedImageList[imageSlot].dataBlock)
|
|
return;
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
|
|
if (trigger) {
|
|
if (!image.triggerDown && image.dataBlock) {
|
|
image.triggerDown = true;
|
|
if (!isGhost()) {
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
updateImageState(imageSlot,0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
if (image.triggerDown) {
|
|
image.triggerDown = false;
|
|
if (!isGhost()) {
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
updateImageState(imageSlot,0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
U32 ShapeBase::getImageFireState(U32 imageSlot)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
// If there is no fire state, then try state 0
|
|
if (image.dataBlock && image.dataBlock->fireState != -1)
|
|
return image.dataBlock->fireState;
|
|
return 0;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
|
|
{
|
|
if (!mMountedImageList[imageSlot].dataBlock)
|
|
return;
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
|
|
|
|
// The client never enters the initial fire state on its own, but it
|
|
// will continue to set that state...
|
|
if (isGhost() && !force && newState == image.dataBlock->fireState) {
|
|
if (image.state != &image.dataBlock->state[newState])
|
|
return;
|
|
}
|
|
|
|
// Eject shell casing on every state change
|
|
ShapeBaseImageData::StateData& nextStateData = image.dataBlock->state[newState];
|
|
if (isGhost() && nextStateData.ejectShell) {
|
|
ejectShellCasing( imageSlot );
|
|
}
|
|
|
|
|
|
// Server must animate the shape if it is a firestate...
|
|
if (newState == image.dataBlock->fireState && isServerObject())
|
|
mShapeInstance->animate();
|
|
|
|
// If going back into the same state, just reset the timer
|
|
// and invoke the script callback
|
|
if (!force && image.state == &image.dataBlock->state[newState]) {
|
|
image.delayTime = image.state->timeoutValue;
|
|
if (image.state->script && !isGhost())
|
|
scriptCallback(imageSlot,image.state->script);
|
|
|
|
// If this is a flash sequence, we need to select a new position for the
|
|
// animation if we're returning to that state...
|
|
if (image.animThread && image.state->sequence != -1 && image.state->flashSequence) {
|
|
F32 randomPos = Platform::getRandom();
|
|
image.shapeInstance->setPos(image.animThread, randomPos);
|
|
image.shapeInstance->setTimeScale(image.animThread, 0);
|
|
if (image.flashThread)
|
|
image.shapeInstance->setPos(image.flashThread, 0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
F32 lastDelay = image.delayTime;
|
|
ShapeBaseImageData::StateData& lastState = *image.state;
|
|
image.state = &image.dataBlock->state[newState];
|
|
|
|
//
|
|
// Do state cleanup first...
|
|
//
|
|
ShapeBaseImageData& imageData = *image.dataBlock;
|
|
ShapeBaseImageData::StateData& stateData = *image.state;
|
|
|
|
// Stop any looping sounds or animations use in the last state.
|
|
if (image.animSound && image.animLoopingSound) {
|
|
alxStop(image.animSound);
|
|
image.animSound = 0;
|
|
}
|
|
|
|
// Mount pending images
|
|
if (image.nextImage != InvalidImagePtr && stateData.allowImageChange) {
|
|
setImage(imageSlot,image.nextImage,image.nextSkinNameHandle,image.nextLoaded);
|
|
return;
|
|
}
|
|
|
|
// Reset cyclic sequences back to the first frame to turn it off
|
|
// (the first key frame should be it's off state).
|
|
if (image.animThread && image.dataBlock->shape->sequences[image.shapeInstance->getSequence(image.animThread)].isCyclic()) {
|
|
image.shapeInstance->setPos(image.animThread,0);
|
|
image.shapeInstance->setTimeScale(image.animThread,0);
|
|
}
|
|
if (image.flashThread) {
|
|
image.shapeInstance->setPos(image.flashThread,0);
|
|
image.shapeInstance->setTimeScale(image.flashThread,0);
|
|
}
|
|
|
|
// Check for immediate transitions
|
|
S32 ns;
|
|
if ((ns = stateData.transition.loaded[image.loaded]) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
//if (!imageData.usesEnergy)
|
|
if ((ns = stateData.transition.ammo[image.ammo]) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
if ((ns = stateData.transition.target[image.target]) != -1) {
|
|
setImageState(imageSlot, ns);
|
|
return;
|
|
}
|
|
if ((ns = stateData.transition.wet[image.wet]) != -1) {
|
|
setImageState(imageSlot, ns);
|
|
return;
|
|
}
|
|
if ((ns = stateData.transition.trigger[image.triggerDown]) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Initialize the new state...
|
|
//
|
|
image.delayTime = stateData.timeoutValue;
|
|
if (stateData.loaded != ShapeBaseImageData::StateData::IgnoreLoaded)
|
|
image.loaded = stateData.loaded == ShapeBaseImageData::StateData::Loaded;
|
|
if (!isGhost() && newState == imageData.fireState) {
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
image.fireCount = (image.fireCount + 1) & 0x7;
|
|
}
|
|
|
|
// Apply recoil
|
|
if (stateData.recoil != ShapeBaseImageData::StateData::NoRecoil)
|
|
onImageRecoil(imageSlot,stateData.recoil);
|
|
|
|
// Play sound
|
|
if (stateData.sound && isGhost()) {
|
|
Point3F vel = getVelocity();
|
|
image.animSound = alxPlay(stateData.sound, &getRenderTransform(), &vel);
|
|
ALint value = 0;
|
|
alxGetSourcei(image.animSound, AL_LOOPING, &value);
|
|
image.animLoopingSound = (value == AL_TRUE);
|
|
}
|
|
|
|
// Play animation
|
|
if (image.animThread && stateData.sequence != -1) {
|
|
image.shapeInstance->setSequence(image.animThread,stateData.sequence,
|
|
stateData.direction ? 0 : 1);
|
|
if (stateData.flashSequence == false) {
|
|
F32 timeScale = (stateData.scaleAnimation && stateData.timeoutValue) ?
|
|
image.shapeInstance->getDuration(image.animThread) / stateData.timeoutValue :
|
|
1;
|
|
image.shapeInstance->setTimeScale(image.animThread, stateData.direction ? timeScale :
|
|
-timeScale);
|
|
} else {
|
|
F32 randomPos = Platform::getRandom();
|
|
image.shapeInstance->setPos(image.animThread, randomPos);
|
|
image.shapeInstance->setTimeScale(image.animThread, 0);
|
|
|
|
image.shapeInstance->setSequence(image.flashThread, stateData.sequenceVis, 0);
|
|
image.shapeInstance->setPos(image.flashThread, 0);
|
|
F32 timeScale = (stateData.scaleAnimation && stateData.timeoutValue) ?
|
|
image.shapeInstance->getDuration(image.animThread) / stateData.timeoutValue :
|
|
1;
|
|
image.shapeInstance->setTimeScale(image.flashThread, timeScale);
|
|
}
|
|
}
|
|
|
|
// Start particle emitter on the client
|
|
if (isGhost() && stateData.emitter)
|
|
startImageEmitter(image,stateData);
|
|
|
|
// Start spin thread
|
|
if (image.spinThread) {
|
|
switch (stateData.spin) {
|
|
case ShapeBaseImageData::StateData::IgnoreSpin:
|
|
image.shapeInstance->setTimeScale(image.spinThread, image.shapeInstance->getTimeScale(image.spinThread));
|
|
break;
|
|
case ShapeBaseImageData::StateData::NoSpin:
|
|
image.shapeInstance->setTimeScale(image.spinThread,0);
|
|
break;
|
|
case ShapeBaseImageData::StateData::SpinUp:
|
|
if (lastState.spin == ShapeBaseImageData::StateData::SpinDown)
|
|
image.delayTime *= 1.0f - (lastDelay / stateData.timeoutValue);
|
|
break;
|
|
case ShapeBaseImageData::StateData::SpinDown:
|
|
if (lastState.spin == ShapeBaseImageData::StateData::SpinUp)
|
|
image.delayTime *= 1.0f - (lastDelay / stateData.timeoutValue);
|
|
break;
|
|
case ShapeBaseImageData::StateData::FullSpin:
|
|
image.shapeInstance->setTimeScale(image.spinThread,1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Script callback on server
|
|
if (stateData.script && stateData.script[0] && !isGhost())
|
|
scriptCallback(imageSlot,stateData.script);
|
|
|
|
// If there is a zero timeout, and a timeout transition, then
|
|
// go ahead and transition imediately.
|
|
if (!image.delayTime)
|
|
{
|
|
if ((ns = stateData.transition.timeout) != -1)
|
|
{
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::updateImageState(U32 imageSlot,F32 dt)
|
|
{
|
|
if (!mMountedImageList[imageSlot].dataBlock)
|
|
return;
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
ShapeBaseImageData& imageData = *image.dataBlock;
|
|
ShapeBaseImageData::StateData& stateData = *image.state;
|
|
image.delayTime -= dt;
|
|
|
|
// Energy management
|
|
if (imageData.usesEnergy) {
|
|
F32 newEnergy = getEnergyLevel() - stateData.energyDrain * dt;
|
|
if (newEnergy < 0)
|
|
newEnergy = 0;
|
|
setEnergyLevel(newEnergy);
|
|
|
|
if (!isGhost()) {
|
|
bool ammo = newEnergy > imageData.minEnergy;
|
|
if (ammo != image.ammo) {
|
|
setMaskBits(ImageMaskN << imageSlot);
|
|
image.ammo = ammo;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for transitions. On some states we must wait for the
|
|
// full timeout value before moving on.
|
|
S32 ns;
|
|
if (image.delayTime <= 0 || !stateData.waitForTimeout) {
|
|
if ((ns = stateData.transition.loaded[image.loaded]) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
if ((ns = stateData.transition.ammo[image.ammo]) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
if ((ns = stateData.transition.target[image.target]) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
if ((ns = stateData.transition.wet[image.wet]) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
if ((ns = stateData.transition.trigger[image.triggerDown]) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
if (image.delayTime <= 0 &&
|
|
(ns = stateData.transition.timeout) != -1) {
|
|
setImageState(imageSlot,ns);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Update the spinning thread timeScale
|
|
if (image.spinThread) {
|
|
float timeScale;
|
|
|
|
switch (stateData.spin) {
|
|
case ShapeBaseImageData::StateData::IgnoreSpin:
|
|
case ShapeBaseImageData::StateData::NoSpin:
|
|
case ShapeBaseImageData::StateData::FullSpin: {
|
|
timeScale = 0;
|
|
image.shapeInstance->setTimeScale(image.spinThread, image.shapeInstance->getTimeScale(image.spinThread));
|
|
break;
|
|
}
|
|
|
|
case ShapeBaseImageData::StateData::SpinUp: {
|
|
timeScale = 1.0f - image.delayTime / stateData.timeoutValue;
|
|
image.shapeInstance->setTimeScale(image.spinThread,timeScale);
|
|
break;
|
|
}
|
|
|
|
case ShapeBaseImageData::StateData::SpinDown: {
|
|
timeScale = image.delayTime / stateData.timeoutValue;
|
|
image.shapeInstance->setTimeScale(image.spinThread,timeScale);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::updateImageAnimation(U32 imageSlot, F32 dt)
|
|
{
|
|
if (!mMountedImageList[imageSlot].dataBlock)
|
|
return;
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
|
|
// Advance animation threads
|
|
if (image.ambientThread)
|
|
image.shapeInstance->advanceTime(dt,image.ambientThread);
|
|
if (image.animThread)
|
|
image.shapeInstance->advanceTime(dt,image.animThread);
|
|
if (image.spinThread)
|
|
image.shapeInstance->advanceTime(dt,image.spinThread);
|
|
if (image.flashThread)
|
|
image.shapeInstance->advanceTime(dt,image.flashThread);
|
|
|
|
// Update any playing sound.
|
|
if (image.animSound)
|
|
alxSourceMatrixF(image.animSound, &getRenderTransform());
|
|
|
|
// Particle emission
|
|
for (S32 i = 0; i < MaxImageEmitters; i++) {
|
|
MountedImage::ImageEmitter& em = image.emitter[i];
|
|
if (bool(em.emitter)) {
|
|
if (em.time > 0) {
|
|
em.time -= dt;
|
|
|
|
MatrixF mat;
|
|
getRenderImageTransform(imageSlot,em.node,&mat);
|
|
Point3F pos,axis;
|
|
mat.getColumn(3,&pos);
|
|
mat.getColumn(1,&axis);
|
|
em.emitter->emitParticles(pos,true,axis,getVelocity(),(U32) (dt * 1000));
|
|
}
|
|
else {
|
|
em.emitter->deleteWhenEmpty();
|
|
em.emitter = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::startImageEmitter(MountedImage& image,ShapeBaseImageData::StateData& state)
|
|
{
|
|
MountedImage::ImageEmitter* bem = 0;
|
|
MountedImage::ImageEmitter* em = image.emitter;
|
|
MountedImage::ImageEmitter* ee = &image.emitter[MaxImageEmitters];
|
|
|
|
// If we are already emitting the same particles from the same
|
|
// node, then simply extend the time. Otherwise, find an empty
|
|
// emitter slot, or grab the one with the least amount of time left.
|
|
for (; em != ee; em++) {
|
|
if (bool(em->emitter)) {
|
|
if (state.emitter == em->emitter->getDataBlock() && state.emitterNode == em->node) {
|
|
if (state.emitterTime > em->time)
|
|
em->time = state.emitterTime;
|
|
return;
|
|
}
|
|
if (!bem || (bool(bem->emitter) && bem->time > em->time))
|
|
bem = em;
|
|
}
|
|
else
|
|
bem = em;
|
|
}
|
|
|
|
bem->time = state.emitterTime;
|
|
bem->node = state.emitterNode;
|
|
bem->emitter = new ParticleEmitter;
|
|
bem->emitter->onNewDataBlock(state.emitter);
|
|
if( !bem->emitter->registerObject() )
|
|
delete bem->emitter;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
Light* ShapeBase::getImageLight(U32 imageSlot)
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
if (!image.dataBlock)
|
|
return 0;
|
|
|
|
ShapeBaseImageData& imageData = *image.dataBlock;
|
|
if (imageData.lightType == ShapeBaseImageData::NoLight)
|
|
return 0;
|
|
|
|
F32 intensity;
|
|
F32 delta = Sim::getCurrentTime() - image.lightStart;
|
|
switch (imageData.lightType) {
|
|
case ShapeBaseImageData::ConstantLight:
|
|
intensity = 1.0;
|
|
break;
|
|
case ShapeBaseImageData::WeaponFireLight: {
|
|
if (delta > imageData.lightTime)
|
|
return 0;
|
|
intensity = 1.0 - delta / imageData.lightTime;
|
|
break;
|
|
}
|
|
case ShapeBaseImageData::PulsingLight: {
|
|
intensity = 0.5 + 0.5 * mSin(M_PI * delta / imageData.lightTime);
|
|
intensity = 0.15 + intensity * 0.85;
|
|
break;
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
// If there is no muzzle node on the shape getMuzzleTransform
|
|
// returns the image origin. So should work fine for either
|
|
// fire or pulsing lights.
|
|
MatrixF mat;
|
|
getMuzzleTransform(imageSlot,&mat);
|
|
return 0;
|
|
}
|
|
|
|
void ShapeBase::registerLights(LightManager * lightManager, bool lightingScene)
|
|
{
|
|
MatrixF posMat;
|
|
|
|
//one of the mounted images must have a light source...
|
|
for (S32 i = 0; i < MaxMountedImages; i++)
|
|
{
|
|
ShapeBaseImageData* imageData = mMountedImageList[i].dataBlock;
|
|
|
|
if (imageData != NULL && imageData->lightType != ShapeBaseImageData::NoLight)
|
|
{
|
|
getRenderMountTransform(imageData->mountPoint,&posMat);
|
|
mMountedImageList[i].registerImageLights(lightManager, lightingScene, posMat.getPosition(), mLightTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::ejectShellCasing( U32 imageSlot )
|
|
{
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
ShapeBaseImageData* imageData = image.dataBlock;
|
|
|
|
if (!imageData->casing)
|
|
return;
|
|
|
|
MatrixF ejectTrans;
|
|
getImageTransform( imageSlot, imageData->ejectNode, &ejectTrans );
|
|
|
|
Point3F ejectDir = imageData->shellExitDir;
|
|
ejectDir.normalize();
|
|
|
|
F32 ejectSpread = mDegToRad( imageData->shellExitVariance );
|
|
MatrixF ejectOrient = MathUtils::createOrientFromDir( ejectDir );
|
|
|
|
Point3F randomDir;
|
|
randomDir.x = mSin( gRandGen.randF( -ejectSpread, ejectSpread ) );
|
|
randomDir.y = 1.0;
|
|
randomDir.z = mSin( gRandGen.randF( -ejectSpread, ejectSpread ) );
|
|
randomDir.normalizeSafe();
|
|
|
|
ejectOrient.mulV( randomDir );
|
|
|
|
MatrixF imageTrans = getTransform();
|
|
imageTrans.mulV( randomDir );
|
|
|
|
Point3F shellVel = randomDir * imageData->shellVelocity;
|
|
Point3F shellPos = ejectTrans.getPosition();
|
|
|
|
|
|
Debris *casing = new Debris;
|
|
casing->onNewDataBlock( imageData->casing );
|
|
casing->setTransform( imageTrans );
|
|
|
|
if (!casing->registerObject())
|
|
delete casing;
|
|
|
|
casing->init( shellPos, shellVel );
|
|
}
|