void CweaponIGC::FireWeapon(Time now) { assert(m_mountID >= 0); assert(m_ship); bool fFiredShot = false; if (m_fActive && (m_nextFire < now)) { Time lastUpdate = m_ship->GetLastUpdate(); //Never fire retroactively. if (m_nextFire < lastUpdate) m_nextFire = lastUpdate; bool fSelected = fIsWeaponSelected(); float energy = m_ship->GetEnergy(); //the energy the ship would have at "now" if (energy >= m_typeData->energyPerShot) { //We'd be able to fire before now and would have enough energy at now to fire, so ... float rechargeRate = m_ship->GetHullType()->GetRechargeRate(); float energyDeficit = (m_nextFire - now) * //this is how much we are in the hole because we rechargeRate; //are shooting sooner than "now" (must be < 0) short ammo = m_ship->GetAmmo(); if ((ammo > 0) || (m_typeData->cAmmoPerShot == 0)) { // we are firing at least once this frame fFiredShot = true; m_pMission->GetIgcSite()->PlayFFEffect(effectFire, m_pshipGunner ? m_pshipGunner : m_ship); //Get the stuff that won't change between shots IclusterIGC* cluster = m_ship->GetCluster(); assert(cluster); const Orientation& shipOrientation = m_ship->GetOrientation(); //Orientation orientationBfr; const Orientation* pMyOrientation; if (m_pshipGunner) { /* orientationBfr = m_pshipGunner->GetOrientation() * (*m_porientation * shipOrientation); pMyOrientation = &orientationBfr; */ pMyOrientation = &(m_pshipGunner->GetOrientation()); } else { pMyOrientation = &shipOrientation; } //This is how much energy deficit recovers between shots float dtimeBurst = GetDtBurst(); float recharge = rechargeRate * dtimeBurst; const Vector& myVelocity = m_ship->GetVelocity(); Vector myPosition = m_ship->GetPosition() + *m_pposition * shipOrientation; //*pMyOrientation; /* m_ship->GetThingSite()->AddMuzzleFlare( m_emissionPt, min(dtimeBurst * 0.5f, 0.05f) ); */ DataProjectileIGC dataProjectile; dataProjectile.projectileTypeID = m_projectileType->GetObjectID(); float lifespan = GetLifespan(); float speed = m_projectileType->GetSpeed(); if (m_typeData->cAmmoPerShot) speed *= m_ship->GetSide()->GetGlobalAttributeSet().GetAttribute(c_gaSpeedAmmo); bool absoluteF = m_projectileType->GetAbsoluteF(); const Vector* ppositionTarget; const Vector* pvelocityTarget; ImodelIGC* ptarget = NULL; if (m_projectileType->GetBlastPower() != 0.0f) { if (m_pshipGunner) ptarget = m_pshipGunner->GetCommandTarget(c_cmdCurrent); else ptarget = m_ship->GetCommandTarget(c_cmdCurrent); if (ptarget) { if ((ptarget->GetCluster() == cluster) && m_ship->CanSee(ptarget)) { ppositionTarget = &(ptarget->GetPosition()); pvelocityTarget = &(ptarget->GetVelocity()); } else ptarget = NULL; } } int nShots = fSelected ? 10 : 0; //Only allow a single shot if the weapon is no longer selected do { if (energy + energyDeficit < m_typeData->energyPerShot) { //We don't have enough energy to fire at our prefered time ... so wait until we do. m_nextFire += (m_typeData->energyPerShot - (energy + energyDeficit)) / rechargeRate; } //Permute the "forward" direction slightly by a random amount dataProjectile.forward = pMyOrientation->GetForward(); if (m_typeData->dispersion != 0.0f) { float r = random(0.0f, m_typeData->dispersion); float a = random(0.0f, 2.0f * pi); dataProjectile.forward += (r * cos(a)) * pMyOrientation->GetRight(); dataProjectile.forward += (r * sin(a)) * pMyOrientation->GetUp(); dataProjectile.forward.SetNormalize(); } dataProjectile.velocity = speed * dataProjectile.forward; if (!absoluteF) dataProjectile.velocity += myVelocity; Vector position = myPosition + myVelocity * (m_nextFire - lastUpdate); dataProjectile.lifespan = lifespan; if (ptarget) { Vector dV = *pvelocityTarget - dataProjectile.velocity; float dV2 = dV.LengthSquared(); if (dV2 != 0.0f) { Vector dP = position - *ppositionTarget; //reverse so time has right sense dataProjectile.lifespan = (dV * dP) / dV2; if (dataProjectile.lifespan > lifespan) dataProjectile.lifespan = lifespan; else if (dataProjectile.lifespan < 0.1f) dataProjectile.lifespan = 0.1f; //Don't let it explode in our face } } IprojectileIGC* p = (IprojectileIGC*)(m_pMission->CreateObject(m_nextFire, OT_projectile, &dataProjectile, sizeof(dataProjectile))); if (p) { if (m_pshipGunner) p->SetGunner(m_pshipGunner); else p->SetLauncher(m_ship); p->SetPosition(position); p->SetCluster(cluster); p->Release(); } energyDeficit += rechargeRate * dtimeBurst; energy -= m_typeData->energyPerShot; ammo -= m_typeData->cAmmoPerShot; m_nextFire += dtimeBurst; } while ((nShots-- > 0) && (m_nextFire < now) && (energy + energyDeficit >= m_typeData->energyPerShot) && (ammo > 0)); m_ship->SetAmmo(ammo > 0 ? ammo : 0); if ((ammo == 0) && (m_typeData->cAmmoPerShot != 0)) fSelected = false; m_ship->SetEnergy(energy); } } if (!fSelected) Deactivate(); } // if we fired a shot, keep track of it (note - stored localy because the // call to deactivate (above) clears the member variable). m_fFiringShot = fFiredShot; // if we are still firing and have the energy and ammo for the next shot, // assume we are firing a burst. if ((m_ship->GetEnergy() >= m_typeData->energyPerShot) && (m_ship->GetAmmo() >= m_typeData->cAmmoPerShot) && (m_fFiringBurst || (m_fActive && m_fFiringShot)) // a burst starts with a shot ) { m_fFiringBurst = true; } else { m_fFiringBurst = false; } }
void PartSysParser::ParseEmitter(const TabFileRecord& record) { auto systemName = record[COL_PARTSYS_NAME].AsString(); auto& system = mSpecs[tolower(systemName)]; // Create it on demand if (!system) { system = std::make_shared<PartSysSpec>(systemName); } // Add the emitter auto emitter = system->CreateEmitter(record[COL_EMITTER_NAME].AsString()); ParseOptionalFloat(record, COL_DELAY, "Delay", [&] (float value) { emitter->SetDelay(value / 30.0f); }); ParseLifespan(record, emitter); ParseParticleLifespan(record, emitter); ParseParticleRate(record, emitter); ParseOptionalEnum<PartSysEmitterSpace>(record, COL_EMITTER_SPACE, "emitter space", EmitterSpaceMapping, [&](auto space) { emitter->SetSpace(space); }); ParseEmitterNodeName(record, emitter); ParseOptionalEnum<PartSysCoordSys>(record, COL_EMITTER_COORD_SYS, "emitter coord sys", CoordSysMapping, [&](auto coordSys) { emitter->SetCoordSys(coordSys); }); ParseOptionalEnum<PartSysCoordSys>(record, COL_EMITTER_OFFSET_COORD_SYS, "emitter offset coord sys", CoordSysMapping, [&](auto coordSys) { emitter->SetOffsetCoordSys(coordSys); }); ParseOptionalEnum<PartSysParticleType>(record, COL_PARTICLE_TYPE, "particle type", ParticleTypeMapping, [&](PartSysParticleType type) { emitter->SetParticleType(type); }); ParseOptionalEnum<PartSysBlendMode>(record, COL_BLEND_MODE, "blend mode", BlendModeMapping, [&](auto mode) { emitter->SetBlendMode(mode); }); ParseMaterial(record, emitter); ParseOptionalEnum<PartSysCoordSys>(record, COL_PARTICLE_POS_COORD_SYS, "particle pos coord sys", CoordSysMapping, [&](auto coordSys) { emitter->SetParticlePosCoordSys(coordSys); }); ParseOptionalEnum<PartSysCoordSys>(record, COL_PARTICLE_VELOCITY_COORD_SYS, "particle velocity coord sys", CoordSysMapping, [&](auto coordSys) { emitter->SetParticleVelocityCoordSys(coordSys); }); ParseOptionalEnum<PartSysParticleSpace>(record, COL_PARTICLE_SPACE, "particle space", ParticleSpaceMapping, [&](auto space) { emitter->SetParticleSpace(space); }); ParseMesh(record, emitter); // Parse the bounding box ParseOptionalFloat(record, COL_BB_LEFT, "bb left", [&](float val) { emitter->SetBoxLeft(val); }); ParseOptionalFloat(record, COL_BB_TOP, "bb top", [&](float val) { emitter->SetBoxTop(val); }); ParseOptionalFloat(record, COL_BB_RIGHT, "bb right", [&](float val) { emitter->SetBoxRight(val); }); ParseOptionalFloat(record, COL_BB_BOTTOM, "bb bottom", [&](float val) { emitter->SetBoxBottom(val); }); for (int paramId = 0; paramId <= part_attractorBlend; paramId++) { int colIdx = 22 + paramId; auto col = record[colIdx]; if (col) { bool success; std::unique_ptr<PartSysParam> param(ParserParams::Parse((PartSysParamId) paramId, col, emitter->GetLifespan(), emitter->GetParticleLifespan(), success)); if (success) { emitter->SetParam((PartSysParamId)paramId, param); } else { logger->warn("Unable to parse particle system param {} for particle system {} and emitter {} with value {}", paramId, systemName, emitter->GetName(), col.AsString()); } } } }