1 #include "projectilemanager.hpp" 2 3 #include <iomanip> 4 5 #include <memory> 6 #include <optional> 7 #include <osg/PositionAttitudeTransform> 8 9 #include <components/debug/debuglog.hpp> 10 11 #include <components/esm/esmwriter.hpp> 12 #include <components/esm/projectilestate.hpp> 13 14 #include <components/misc/constants.hpp> 15 #include <components/misc/convert.hpp> 16 17 #include <components/resource/resourcesystem.hpp> 18 #include <components/resource/scenemanager.hpp> 19 20 #include <components/sceneutil/controller.hpp> 21 #include <components/sceneutil/visitor.hpp> 22 #include <components/sceneutil/lightmanager.hpp> 23 24 #include "../mwworld/manualref.hpp" 25 #include "../mwworld/class.hpp" 26 #include "../mwworld/esmstore.hpp" 27 #include "../mwworld/inventorystore.hpp" 28 29 #include "../mwbase/soundmanager.hpp" 30 #include "../mwbase/world.hpp" 31 #include "../mwbase/environment.hpp" 32 33 #include "../mwmechanics/combat.hpp" 34 #include "../mwmechanics/creaturestats.hpp" 35 #include "../mwmechanics/spellcasting.hpp" 36 #include "../mwmechanics/actorutil.hpp" 37 #include "../mwmechanics/aipackage.hpp" 38 #include "../mwmechanics/weapontype.hpp" 39 40 #include "../mwrender/animation.hpp" 41 #include "../mwrender/vismask.hpp" 42 #include "../mwrender/renderingmanager.hpp" 43 #include "../mwrender/util.hpp" 44 45 #include "../mwsound/sound.hpp" 46 47 #include "../mwphysics/physicssystem.hpp" 48 #include "../mwphysics/projectile.hpp" 49 50 namespace 51 { getMagicBoltData(std::vector<std::string> & projectileIDs,std::set<std::string> & sounds,float & speed,std::string & texture,std::string & sourceName,const std::string & id)52 ESM::EffectList getMagicBoltData(std::vector<std::string>& projectileIDs, std::set<std::string>& sounds, float& speed, std::string& texture, std::string& sourceName, const std::string& id) 53 { 54 const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); 55 const ESM::EffectList* effects; 56 if (const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(id)) // check if it's a spell 57 { 58 sourceName = spell->mName; 59 effects = &spell->mEffects; 60 } 61 else // check if it's an enchanted item 62 { 63 MWWorld::ManualRef ref(esmStore, id); 64 MWWorld::Ptr ptr = ref.getPtr(); 65 const ESM::Enchantment* ench = esmStore.get<ESM::Enchantment>().find(ptr.getClass().getEnchantment(ptr)); 66 sourceName = ptr.getClass().getName(ptr); 67 effects = &ench->mEffects; 68 } 69 70 int count = 0; 71 speed = 0.0f; 72 ESM::EffectList projectileEffects; 73 for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects->mList.begin()); 74 iter!=effects->mList.end(); ++iter) 75 { 76 const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( 77 iter->mEffectID); 78 79 // Speed of multi-effect projectiles should be the average of the constituent effects, 80 // based on observation of the original engine. 81 speed += magicEffect->mData.mSpeed; 82 count++; 83 84 if (iter->mRange != ESM::RT_Target) 85 continue; 86 87 if (magicEffect->mBolt.empty()) 88 projectileIDs.emplace_back("VFX_DefaultBolt"); 89 else 90 projectileIDs.push_back(magicEffect->mBolt); 91 92 static const std::string schools[] = { 93 "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" 94 }; 95 if (!magicEffect->mBoltSound.empty()) 96 sounds.emplace(magicEffect->mBoltSound); 97 else 98 sounds.emplace(schools[magicEffect->mData.mSchool] + " bolt"); 99 projectileEffects.mList.push_back(*iter); 100 } 101 102 if (count != 0) 103 speed /= count; 104 105 // the particle texture is only used if there is only one projectile 106 if (projectileEffects.mList.size() == 1) 107 { 108 const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( 109 effects->mList.begin()->mEffectID); 110 texture = magicEffect->mParticle; 111 } 112 113 if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects 114 { 115 const std::string ID = "VFX_Multiple" + std::to_string(effects->mList.size()); 116 std::vector<std::string>::iterator it; 117 it = projectileIDs.begin(); 118 it = projectileIDs.insert(it, ID); 119 } 120 return projectileEffects; 121 } 122 getMagicBoltLightDiffuseColor(const ESM::EffectList & effects)123 osg::Vec4 getMagicBoltLightDiffuseColor(const ESM::EffectList& effects) 124 { 125 // Calculate combined light diffuse color from magical effects 126 osg::Vec4 lightDiffuseColor; 127 float lightDiffuseRed = 0.0f; 128 float lightDiffuseGreen = 0.0f; 129 float lightDiffuseBlue = 0.0f; 130 for (std::vector<ESM::ENAMstruct>::const_iterator iter(effects.mList.begin()); 131 iter != effects.mList.end(); ++iter) 132 { 133 const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find( 134 iter->mEffectID); 135 lightDiffuseRed += (static_cast<float>(magicEffect->mData.mRed) / 255.f); 136 lightDiffuseGreen += (static_cast<float>(magicEffect->mData.mGreen) / 255.f); 137 lightDiffuseBlue += (static_cast<float>(magicEffect->mData.mBlue) / 255.f); 138 } 139 int numberOfEffects = effects.mList.size(); 140 lightDiffuseColor = osg::Vec4(lightDiffuseRed / numberOfEffects 141 , lightDiffuseGreen / numberOfEffects 142 , lightDiffuseBlue / numberOfEffects 143 , 1.0f); 144 145 return lightDiffuseColor; 146 } 147 } 148 149 namespace MWWorld 150 { 151 ProjectileManager(osg::Group * parent,Resource::ResourceSystem * resourceSystem,MWRender::RenderingManager * rendering,MWPhysics::PhysicsSystem * physics)152 ProjectileManager::ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem, 153 MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics) 154 : mParent(parent) 155 , mResourceSystem(resourceSystem) 156 , mRendering(rendering) 157 , mPhysics(physics) 158 , mCleanupTimer(0.0f) 159 { 160 161 } 162 163 /// Rotates an osg::PositionAttitudeTransform over time. 164 class RotateCallback : public osg::NodeCallback 165 { 166 public: RotateCallback(const osg::Vec3f & axis=osg::Vec3f (0,-1,0),float rotateSpeed=osg::PI * 2)167 RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2) 168 : mAxis(axis) 169 , mRotateSpeed(rotateSpeed) 170 { 171 } 172 operator ()(osg::Node * node,osg::NodeVisitor * nv)173 void operator()(osg::Node* node, osg::NodeVisitor* nv) override 174 { 175 osg::PositionAttitudeTransform* transform = static_cast<osg::PositionAttitudeTransform*>(node); 176 177 double time = nv->getFrameStamp()->getSimulationTime(); 178 179 osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis); 180 transform->setAttitude(orient); 181 182 traverse(node, nv); 183 } 184 185 private: 186 osg::Vec3f mAxis; 187 float mRotateSpeed; 188 }; 189 190 createModel(State & state,const std::string & model,const osg::Vec3f & pos,const osg::Quat & orient,bool rotate,bool createLight,osg::Vec4 lightDiffuseColor,std::string texture)191 void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, 192 bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) 193 { 194 state.mNode = new osg::PositionAttitudeTransform; 195 state.mNode->setNodeMask(MWRender::Mask_Effect); 196 state.mNode->setPosition(pos); 197 state.mNode->setAttitude(orient); 198 199 osg::Group* attachTo = state.mNode; 200 201 if (rotate) 202 { 203 osg::ref_ptr<osg::PositionAttitudeTransform> rotateNode (new osg::PositionAttitudeTransform); 204 rotateNode->addUpdateCallback(new RotateCallback()); 205 state.mNode->addChild(rotateNode); 206 attachTo = rotateNode; 207 } 208 209 osg::ref_ptr<osg::Node> projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo); 210 211 if (state.mIdMagic.size() > 1) 212 for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) 213 { 214 std::ostringstream nodeName; 215 nodeName << "Dummy" << std::setw(2) << std::setfill('0') << iter; 216 const ESM::Weapon* weapon = MWBase::Environment::get().getWorld()->getStore().get<ESM::Weapon>().find (state.mIdMagic.at(iter)); 217 SceneUtil::FindByNameVisitor findVisitor(nodeName.str()); 218 attachTo->accept(findVisitor); 219 if (findVisitor.mFoundNode) 220 mResourceSystem->getSceneManager()->getInstance("meshes\\" + weapon->mModel, findVisitor.mFoundNode); 221 } 222 223 if (createLight) 224 { 225 osg::ref_ptr<osg::Light> projectileLight(new osg::Light); 226 projectileLight->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); 227 projectileLight->setDiffuse(lightDiffuseColor); 228 projectileLight->setSpecular(osg::Vec4(0.0f, 0.0f, 0.0f, 0.0f)); 229 projectileLight->setConstantAttenuation(0.f); 230 projectileLight->setLinearAttenuation(0.1f); 231 projectileLight->setQuadraticAttenuation(0.f); 232 projectileLight->setPosition(osg::Vec4(pos, 1.0)); 233 234 SceneUtil::LightSource* projectileLightSource = new SceneUtil::LightSource; 235 projectileLightSource->setNodeMask(MWRender::Mask_Lighting); 236 projectileLightSource->setRadius(66.f); 237 238 state.mNode->addChild(projectileLightSource); 239 projectileLightSource->setLight(projectileLight); 240 } 241 242 SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; 243 state.mNode->accept(disableFreezeOnCullVisitor); 244 245 state.mNode->addCullCallback(new SceneUtil::LightListCallback); 246 247 mParent->addChild(state.mNode); 248 249 state.mEffectAnimationTime.reset(new MWRender::EffectAnimationTime); 250 251 SceneUtil::AssignControllerSourcesVisitor assignVisitor (state.mEffectAnimationTime); 252 state.mNode->accept(assignVisitor); 253 254 MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); 255 } 256 update(State & state,float duration)257 void ProjectileManager::update(State& state, float duration) 258 { 259 state.mEffectAnimationTime->addTime(duration); 260 } 261 launchMagicBolt(const std::string & spellId,const Ptr & caster,const osg::Vec3f & fallbackDirection)262 void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection) 263 { 264 osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); 265 if (caster.getClass().isActor()) 266 { 267 // Note: we ignore the collision box offset, this is required to make some flying creatures work as intended. 268 pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight; 269 } 270 271 if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible 272 return; 273 274 osg::Quat orient; 275 if (caster.getClass().isActor()) 276 orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) 277 * osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); 278 else 279 orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection)); 280 281 MagicBoltState state; 282 state.mSpellId = spellId; 283 state.mCasterHandle = caster; 284 if (caster.getClass().isActor()) 285 state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); 286 else 287 state.mActorId = -1; 288 289 std::string texture; 290 291 state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); 292 293 // Non-projectile should have been removed by getMagicBoltData 294 if (state.mEffects.mList.empty()) 295 return; 296 297 if (!caster.getClass().isActor() && fallbackDirection.length2() <= 0) 298 { 299 Log(Debug::Warning) << "Unable to launch magic bolt (direction to target is empty)"; 300 return; 301 } 302 303 MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); 304 MWWorld::Ptr ptr = ref.getPtr(); 305 306 osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); 307 308 auto model = ptr.getClass().getModel(ptr); 309 createModel(state, model, pos, orient, true, true, lightDiffuseColor, texture); 310 311 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); 312 for (const std::string &soundid : state.mSoundIds) 313 { 314 MWBase::Sound *sound = sndMgr->playSound3D(pos, soundid, 1.0f, 1.0f, 315 MWSound::Type::Sfx, MWSound::PlayMode::Loop); 316 if (sound) 317 state.mSounds.push_back(sound); 318 } 319 320 // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape 321 if (state.mIdMagic.size() > 1) 322 model = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get<ESM::Weapon>().find(state.mIdMagic.at(1))->mModel; 323 state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); 324 state.mToDelete = false; 325 mMagicBolts.push_back(state); 326 } 327 launchProjectile(Ptr actor,ConstPtr projectile,const osg::Vec3f & pos,const osg::Quat & orient,Ptr bow,float speed,float attackStrength)328 void ProjectileManager::launchProjectile(Ptr actor, ConstPtr projectile, const osg::Vec3f &pos, const osg::Quat &orient, Ptr bow, float speed, float attackStrength) 329 { 330 ProjectileState state; 331 state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); 332 state.mBowId = bow.getCellRef().getRefId(); 333 state.mVelocity = orient * osg::Vec3f(0,1,0) * speed; 334 state.mIdArrow = projectile.getCellRef().getRefId(); 335 state.mCasterHandle = actor; 336 state.mAttackStrength = attackStrength; 337 int type = projectile.get<ESM::Weapon>()->mBase->mData.mType; 338 state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown; 339 340 MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); 341 MWWorld::Ptr ptr = ref.getPtr(); 342 343 const auto model = ptr.getClass().getModel(ptr); 344 createModel(state, model, pos, orient, false, false, osg::Vec4(0,0,0,0)); 345 if (!ptr.getClass().getEnchantment(ptr).empty()) 346 SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); 347 348 state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false); 349 state.mToDelete = false; 350 mProjectiles.push_back(state); 351 } 352 updateCasters()353 void ProjectileManager::updateCasters() 354 { 355 for (auto& state : mProjectiles) 356 mPhysics->setCaster(state.mProjectileId, state.getCaster()); 357 358 for (auto& state : mMagicBolts) 359 { 360 // casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified back. 361 // TODO: should object-type caster be restored from savegame? 362 if (state.mActorId == -1) 363 continue; 364 365 auto caster = state.getCaster(); 366 if (caster.isEmpty()) 367 { 368 Log(Debug::Error) << "Couldn't find caster with ID " << state.mActorId; 369 cleanupMagicBolt(state); 370 continue; 371 } 372 mPhysics->setCaster(state.mProjectileId, caster); 373 } 374 } 375 update(float dt)376 void ProjectileManager::update(float dt) 377 { 378 periodicCleanup(dt); 379 moveProjectiles(dt); 380 moveMagicBolts(dt); 381 } 382 periodicCleanup(float dt)383 void ProjectileManager::periodicCleanup(float dt) 384 { 385 mCleanupTimer -= dt; 386 if (mCleanupTimer <= 0.0f) 387 { 388 mCleanupTimer = 2.0f; 389 390 auto isCleanable = [](const ProjectileManager::State& state) -> bool 391 { 392 const float farawayThreshold = 72000.0f; 393 osg::Vec3 playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); 394 return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold; 395 }; 396 397 for (auto& projectileState : mProjectiles) 398 { 399 if (isCleanable(projectileState)) 400 cleanupProjectile(projectileState); 401 } 402 403 for (auto& magicBoltState : mMagicBolts) 404 { 405 if (isCleanable(magicBoltState)) 406 cleanupMagicBolt(magicBoltState); 407 } 408 } 409 } 410 moveMagicBolts(float duration)411 void ProjectileManager::moveMagicBolts(float duration) 412 { 413 for (auto& magicBoltState : mMagicBolts) 414 { 415 if (magicBoltState.mToDelete) 416 continue; 417 418 auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); 419 if (!projectile->isActive()) 420 continue; 421 // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. 422 MWWorld::Ptr caster = magicBoltState.getCaster(); 423 if (!caster.isEmpty() && caster.getClass().isActor()) 424 { 425 if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) 426 { 427 cleanupMagicBolt(magicBoltState); 428 continue; 429 } 430 } 431 432 osg::Quat orient = magicBoltState.mNode->getAttitude(); 433 static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>() 434 .find("fTargetSpellMaxSpeed")->mValue.getFloat(); 435 float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; 436 osg::Vec3f direction = orient * osg::Vec3f(0,1,0); 437 direction.normalize(); 438 osg::Vec3f newPos = projectile->getPosition() + direction * duration * speed; 439 440 update(magicBoltState, duration); 441 442 // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. 443 std::vector<MWWorld::Ptr> targetActors; 444 if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) 445 caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); 446 projectile->setValidTargets(targetActors); 447 448 mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); 449 } 450 } 451 moveProjectiles(float duration)452 void ProjectileManager::moveProjectiles(float duration) 453 { 454 for (auto& projectileState : mProjectiles) 455 { 456 if (projectileState.mToDelete) 457 continue; 458 459 auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); 460 if (!projectile->isActive()) 461 continue; 462 // gravity constant - must be way lower than the gravity affecting actors, since we're not 463 // simulating aerodynamics at all 464 projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; 465 466 osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration; 467 468 // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. 469 if (!projectileState.mThrown) 470 { 471 osg::Quat orient; 472 orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity); 473 projectileState.mNode->setAttitude(orient); 474 } 475 476 update(projectileState, duration); 477 478 MWWorld::Ptr caster = projectileState.getCaster(); 479 480 // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. 481 std::vector<MWWorld::Ptr> targetActors; 482 if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) 483 caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); 484 projectile->setValidTargets(targetActors); 485 486 mPhysics->updateProjectile(projectileState.mProjectileId, newPos); 487 } 488 } 489 processHits()490 void ProjectileManager::processHits() 491 { 492 for (auto& projectileState : mProjectiles) 493 { 494 if (projectileState.mToDelete) 495 continue; 496 497 auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); 498 499 const auto pos = projectile->getPosition(); 500 projectileState.mNode->setPosition(pos); 501 502 if (projectile->isActive()) 503 continue; 504 505 const auto target = projectile->getTarget(); 506 auto caster = projectileState.getCaster(); 507 assert(target != caster); 508 509 if (caster.isEmpty()) 510 caster = target; 511 512 // Try to get a Ptr to the bow that was used. It might no longer exist. 513 MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); 514 MWWorld::Ptr bow = projectileRef.getPtr(); 515 if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) 516 { 517 MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); 518 MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); 519 if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) 520 bow = *invIt; 521 } 522 if (projectile->getHitWater()) 523 mRendering->emitWaterRipple(pos); 524 525 MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); 526 cleanupProjectile(projectileState); 527 } 528 for (auto& magicBoltState : mMagicBolts) 529 { 530 if (magicBoltState.mToDelete) 531 continue; 532 533 auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); 534 535 const auto pos = projectile->getPosition(); 536 magicBoltState.mNode->setPosition(pos); 537 for (const auto& sound : magicBoltState.mSounds) 538 sound->setPosition(pos); 539 540 if (projectile->isActive()) 541 continue; 542 543 const auto target = projectile->getTarget(); 544 const auto caster = magicBoltState.getCaster(); 545 assert(target != caster); 546 547 MWMechanics::CastSpell cast(caster, target); 548 cast.mHitPosition = pos; 549 cast.mId = magicBoltState.mSpellId; 550 cast.mSourceName = magicBoltState.mSourceName; 551 cast.mStack = false; 552 cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); 553 554 MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); 555 cleanupMagicBolt(magicBoltState); 556 } 557 mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), 558 mProjectiles.end()); 559 mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }), 560 mMagicBolts.end()); 561 } 562 cleanupProjectile(ProjectileManager::ProjectileState & state)563 void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) 564 { 565 mParent->removeChild(state.mNode); 566 mPhysics->removeProjectile(state.mProjectileId); 567 state.mToDelete = true; 568 } 569 cleanupMagicBolt(ProjectileManager::MagicBoltState & state)570 void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) 571 { 572 mParent->removeChild(state.mNode); 573 mPhysics->removeProjectile(state.mProjectileId); 574 state.mToDelete = true; 575 for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) 576 { 577 MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); 578 } 579 } 580 clear()581 void ProjectileManager::clear() 582 { 583 for (auto& mProjectile : mProjectiles) 584 cleanupProjectile(mProjectile); 585 mProjectiles.clear(); 586 587 for (auto& mMagicBolt : mMagicBolts) 588 cleanupMagicBolt(mMagicBolt); 589 mMagicBolts.clear(); 590 } 591 write(ESM::ESMWriter & writer,Loading::Listener & progress) const592 void ProjectileManager::write(ESM::ESMWriter &writer, Loading::Listener &progress) const 593 { 594 for (std::vector<ProjectileState>::const_iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) 595 { 596 writer.startRecord(ESM::REC_PROJ); 597 598 ESM::ProjectileState state; 599 state.mId = it->mIdArrow; 600 state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); 601 state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); 602 state.mActorId = it->mActorId; 603 604 state.mBowId = it->mBowId; 605 state.mVelocity = it->mVelocity; 606 state.mAttackStrength = it->mAttackStrength; 607 608 state.save(writer); 609 610 writer.endRecord(ESM::REC_PROJ); 611 } 612 613 for (std::vector<MagicBoltState>::const_iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) 614 { 615 writer.startRecord(ESM::REC_MPRJ); 616 617 ESM::MagicBoltState state; 618 state.mId = it->mIdMagic.at(0); 619 state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); 620 state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); 621 state.mActorId = it->mActorId; 622 623 state.mSpellId = it->mSpellId; 624 state.mSpeed = it->mSpeed; 625 626 state.save(writer); 627 628 writer.endRecord(ESM::REC_MPRJ); 629 } 630 } 631 readRecord(ESM::ESMReader & reader,uint32_t type)632 bool ProjectileManager::readRecord(ESM::ESMReader &reader, uint32_t type) 633 { 634 if (type == ESM::REC_PROJ) 635 { 636 ESM::ProjectileState esm; 637 esm.load(reader); 638 639 ProjectileState state; 640 state.mActorId = esm.mActorId; 641 state.mBowId = esm.mBowId; 642 state.mVelocity = esm.mVelocity; 643 state.mIdArrow = esm.mId; 644 state.mAttackStrength = esm.mAttackStrength; 645 state.mToDelete = false; 646 647 std::string model; 648 try 649 { 650 MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); 651 MWWorld::Ptr ptr = ref.getPtr(); 652 model = ptr.getClass().getModel(ptr); 653 int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType; 654 state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; 655 656 state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false); 657 } 658 catch(...) 659 { 660 return true; 661 } 662 663 createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false, false, osg::Vec4(0,0,0,0)); 664 665 mProjectiles.push_back(state); 666 return true; 667 } 668 if (type == ESM::REC_MPRJ) 669 { 670 ESM::MagicBoltState esm; 671 esm.load(reader); 672 673 MagicBoltState state; 674 state.mIdMagic.push_back(esm.mId); 675 state.mSpellId = esm.mSpellId; 676 state.mActorId = esm.mActorId; 677 state.mToDelete = false; 678 std::string texture; 679 680 try 681 { 682 state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); 683 } 684 catch(...) 685 { 686 Log(Debug::Warning) << "Warning: Failed to recreate magic projectile from saved data (id \"" << state.mSpellId << "\" no longer exists?)"; 687 return true; 688 } 689 690 state.mSpeed = esm.mSpeed; // speed is derived from non-projectile effects as well as 691 // projectile effects, so we can't calculate it from the save 692 // file's effect list, which is already trimmed of non-projectile 693 // effects. We need to use the stored value. 694 695 std::string model; 696 try 697 { 698 MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); 699 MWWorld::Ptr ptr = ref.getPtr(); 700 model = ptr.getClass().getModel(ptr); 701 } 702 catch(...) 703 { 704 return true; 705 } 706 707 osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); 708 createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); 709 state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true); 710 711 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); 712 for (const std::string &soundid : state.mSoundIds) 713 { 714 MWBase::Sound *sound = sndMgr->playSound3D(esm.mPosition, soundid, 1.0f, 1.0f, 715 MWSound::Type::Sfx, MWSound::PlayMode::Loop); 716 if (sound) 717 state.mSounds.push_back(sound); 718 } 719 720 mMagicBolts.push_back(state); 721 return true; 722 } 723 724 return false; 725 } 726 countSavedGameRecords() const727 int ProjectileManager::countSavedGameRecords() const 728 { 729 return mMagicBolts.size() + mProjectiles.size(); 730 } 731 getCaster()732 MWWorld::Ptr ProjectileManager::State::getCaster() 733 { 734 if (!mCasterHandle.isEmpty()) 735 return mCasterHandle; 736 737 return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); 738 } 739 740 } 741