//----------------------------------------------------------------------------- // 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 ); }