1 #include "npc.hpp" 2 3 #include <memory> 4 5 #include <components/misc/constants.hpp> 6 #include <components/misc/rng.hpp> 7 8 #include <components/debug/debuglog.hpp> 9 #include <components/esm/loadmgef.hpp> 10 #include <components/esm/loadnpc.hpp> 11 #include <components/esm/npcstate.hpp> 12 #include <components/settings/settings.hpp> 13 14 #include "../mwbase/environment.hpp" 15 #include "../mwbase/world.hpp" 16 #include "../mwbase/mechanicsmanager.hpp" 17 #include "../mwbase/windowmanager.hpp" 18 #include "../mwbase/dialoguemanager.hpp" 19 #include "../mwbase/soundmanager.hpp" 20 21 #include "../mwmechanics/creaturestats.hpp" 22 #include "../mwmechanics/npcstats.hpp" 23 #include "../mwmechanics/movement.hpp" 24 #include "../mwmechanics/spellcasting.hpp" 25 #include "../mwmechanics/disease.hpp" 26 #include "../mwmechanics/combat.hpp" 27 #include "../mwmechanics/autocalcspell.hpp" 28 #include "../mwmechanics/difficultyscaling.hpp" 29 #include "../mwmechanics/weapontype.hpp" 30 #include "../mwmechanics/actorutil.hpp" 31 32 #include "../mwworld/ptr.hpp" 33 #include "../mwworld/actiontalk.hpp" 34 #include "../mwworld/actionopen.hpp" 35 #include "../mwworld/failedaction.hpp" 36 #include "../mwworld/inventorystore.hpp" 37 #include "../mwworld/customdata.hpp" 38 #include "../mwworld/cellstore.hpp" 39 #include "../mwworld/localscripts.hpp" 40 41 #include "../mwrender/objects.hpp" 42 #include "../mwrender/renderinginterface.hpp" 43 #include "../mwrender/npcanimation.hpp" 44 45 #include "../mwgui/tooltips.hpp" 46 47 namespace 48 { 49 is_even(double d)50 int is_even(double d) { 51 double int_part; 52 modf(d / 2.0, &int_part); 53 return 2.0 * int_part == d; 54 } 55 round_ieee_754(double d)56 int round_ieee_754(double d) { 57 double i = floor(d); 58 d -= i; 59 if(d < 0.5) 60 return static_cast<int>(i); 61 if(d > 0.5) 62 return static_cast<int>(i) + 1; 63 if(is_even(i)) 64 return static_cast<int>(i); 65 return static_cast<int>(i) + 1; 66 } 67 autoCalculateAttributes(const ESM::NPC * npc,MWMechanics::CreatureStats & creatureStats)68 void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) 69 { 70 // race bonus 71 const ESM::Race *race = 72 MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace); 73 74 bool male = (npc->mFlags & ESM::NPC::Female) == 0; 75 76 int level = creatureStats.getLevel(); 77 for (int i=0; i<ESM::Attribute::Length; ++i) 78 { 79 const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i]; 80 creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); 81 } 82 83 // class bonus 84 const ESM::Class *class_ = 85 MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass); 86 87 for (int i=0; i<2; ++i) 88 { 89 int attribute = class_->mData.mAttribute[i]; 90 if (attribute>=0 && attribute<8) 91 { 92 creatureStats.setAttribute(attribute, 93 creatureStats.getAttribute(attribute).getBase() + 10); 94 } 95 } 96 97 // skill bonus 98 for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute) 99 { 100 float modifierSum = 0; 101 102 for (int j=0; j<ESM::Skill::Length; ++j) 103 { 104 const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(j); 105 106 if (skill->mData.mAttribute != attribute) 107 continue; 108 109 // is this a minor or major skill? 110 float add=0.2f; 111 for (int k=0; k<5; ++k) 112 { 113 if (class_->mData.mSkills[k][0] == j) 114 add=0.5; 115 } 116 for (int k=0; k<5; ++k) 117 { 118 if (class_->mData.mSkills[k][1] == j) 119 add=1.0; 120 } 121 modifierSum += add; 122 } 123 creatureStats.setAttribute(attribute, std::min( 124 round_ieee_754(creatureStats.getAttribute(attribute).getBase() 125 + (level-1) * modifierSum), 100) ); 126 } 127 128 // initial health 129 float strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); 130 float endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); 131 132 int multiplier = 3; 133 134 if (class_->mData.mSpecialization == ESM::Class::Combat) 135 multiplier += 2; 136 else if (class_->mData.mSpecialization == ESM::Class::Stealth) 137 multiplier += 1; 138 139 if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance 140 || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) 141 multiplier += 1; 142 143 creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); 144 } 145 146 /** 147 * @brief autoCalculateSkills 148 * 149 * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): 150 * 151 * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) 152 * 153 * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. 154 * 155 * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, 156 * zero for other Skills. 157 * 158 * and by adding class, race, specialization bonus. 159 */ autoCalculateSkills(const ESM::NPC * npc,MWMechanics::NpcStats & npcStats,const MWWorld::Ptr & ptr,bool spellsInitialised)160 void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised) 161 { 162 const ESM::Class *class_ = 163 MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass); 164 165 unsigned int level = npcStats.getLevel(); 166 167 const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace); 168 169 170 for (int i = 0; i < 2; ++i) 171 { 172 int bonus = (i==0) ? 10 : 25; 173 174 for (int i2 = 0; i2 < 5; ++i2) 175 { 176 int index = class_->mData.mSkills[i2][i]; 177 if (index >= 0 && index < ESM::Skill::Length) 178 { 179 npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); 180 } 181 } 182 } 183 184 for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) 185 { 186 float majorMultiplier = 0.1f; 187 float specMultiplier = 0.0f; 188 189 int raceBonus = 0; 190 int specBonus = 0; 191 192 for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) 193 { 194 if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) 195 { 196 raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; 197 break; 198 } 199 } 200 201 for (int k = 0; k < 5; ++k) 202 { 203 // is this a minor or major skill? 204 if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) 205 { 206 majorMultiplier = 1.0f; 207 break; 208 } 209 } 210 211 // is this skill in the same Specialization as the class? 212 const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skillIndex); 213 if (skill->mData.mSpecialization == class_->mData.mSpecialization) 214 { 215 specMultiplier = 0.5f; 216 specBonus = 5; 217 } 218 219 npcStats.getSkill(skillIndex).setBase( 220 std::min( 221 round_ieee_754( 222 npcStats.getSkill(skillIndex).getBase() 223 + 5 224 + raceBonus 225 + specBonus 226 +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 227 } 228 229 int skills[ESM::Skill::Length]; 230 for (int i=0; i<ESM::Skill::Length; ++i) 231 skills[i] = npcStats.getSkill(i).getBase(); 232 233 int attributes[ESM::Attribute::Length]; 234 for (int i=0; i<ESM::Attribute::Length; ++i) 235 attributes[i] = npcStats.getAttribute(i).getBase(); 236 237 if (!spellsInitialised) 238 { 239 std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); 240 npcStats.getSpells().addAllToInstance(spells); 241 } 242 } 243 } 244 245 namespace MWClass 246 { 247 248 class NpcCustomData : public MWWorld::TypedCustomData<NpcCustomData> 249 { 250 public: 251 MWMechanics::NpcStats mNpcStats; 252 MWMechanics::Movement mMovement; 253 MWWorld::InventoryStore mInventoryStore; 254 asNpcCustomData()255 NpcCustomData& asNpcCustomData() override 256 { 257 return *this; 258 } asNpcCustomData() const259 const NpcCustomData& asNpcCustomData() const override 260 { 261 return *this; 262 } 263 }; 264 getGmst()265 const Npc::GMST& Npc::getGmst() 266 { 267 static GMST gmst; 268 static bool inited = false; 269 if(!inited) 270 { 271 const MWBase::World *world = MWBase::Environment::get().getWorld(); 272 const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>(); 273 274 gmst.fMinWalkSpeed = store.find("fMinWalkSpeed"); 275 gmst.fMaxWalkSpeed = store.find("fMaxWalkSpeed"); 276 gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); 277 gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); 278 gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); 279 gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); 280 gmst.fMinFlySpeed = store.find("fMinFlySpeed"); 281 gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); 282 gmst.fSwimRunBase = store.find("fSwimRunBase"); 283 gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); 284 gmst.fJumpEncumbranceBase = store.find("fJumpEncumbranceBase"); 285 gmst.fJumpEncumbranceMultiplier = store.find("fJumpEncumbranceMultiplier"); 286 gmst.fJumpAcrobaticsBase = store.find("fJumpAcrobaticsBase"); 287 gmst.fJumpAcroMultiplier = store.find("fJumpAcroMultiplier"); 288 gmst.fJumpRunMultiplier = store.find("fJumpRunMultiplier"); 289 gmst.fWereWolfRunMult = store.find("fWereWolfRunMult"); 290 gmst.fKnockDownMult = store.find("fKnockDownMult"); 291 gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); 292 gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); 293 gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult"); 294 295 inited = true; 296 } 297 return gmst; 298 } 299 ensureCustomData(const MWWorld::Ptr & ptr) const300 void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const 301 { 302 if (!ptr.getRefData().getCustomData()) 303 { 304 std::unique_ptr<NpcCustomData> data(new NpcCustomData); 305 306 MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 307 308 bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId); 309 310 // creature stats 311 int gold=0; 312 if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) 313 { 314 gold = ref->mBase->mNpdt.mGold; 315 316 for (unsigned int i=0; i< ESM::Skill::Length; ++i) 317 data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt.mSkills[i]); 318 319 data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength); 320 data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence); 321 data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt.mWillpower); 322 data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt.mAgility); 323 data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt.mSpeed); 324 data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt.mEndurance); 325 data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt.mPersonality); 326 data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt.mLuck); 327 328 data->mNpcStats.setHealth (ref->mBase->mNpdt.mHealth); 329 data->mNpcStats.setMagicka (ref->mBase->mNpdt.mMana); 330 data->mNpcStats.setFatigue (ref->mBase->mNpdt.mFatigue); 331 332 data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel); 333 data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition); 334 data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); 335 336 data->mNpcStats.setNeedRecalcDynamicStats(false); 337 } 338 else 339 { 340 gold = ref->mBase->mNpdt.mGold; 341 342 for (int i=0; i<3; ++i) 343 data->mNpcStats.setDynamic (i, 10); 344 345 data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel); 346 data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition); 347 data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); 348 349 autoCalculateAttributes(ref->mBase, data->mNpcStats); 350 autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised); 351 352 data->mNpcStats.setNeedRecalcDynamicStats(true); 353 } 354 355 // Persistent actors with 0 health do not play death animation 356 if (data->mNpcStats.isDead()) 357 data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr)); 358 359 // race powers 360 const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace); 361 data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList); 362 363 if (!ref->mBase->mFaction.empty()) 364 { 365 static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>() 366 .find("iAutoRepFacMod")->mValue.getInteger(); 367 static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>() 368 .find("iAutoRepLevMod")->mValue.getInteger(); 369 int rank = ref->mBase->getFactionRank(); 370 371 data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1)); 372 } 373 374 data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage); 375 376 data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); 377 data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); 378 data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); 379 data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); 380 381 // spells 382 if (!spellsInitialised) 383 data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); 384 385 // inventory 386 // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items 387 data->mInventoryStore.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); 388 389 data->mNpcStats.setGoldPool(gold); 390 391 // store 392 ptr.getRefData().setCustomData(std::move(data)); 393 394 getInventoryStore(ptr).autoEquip(ptr); 395 } 396 } 397 insertObjectRendering(const MWWorld::Ptr & ptr,const std::string & model,MWRender::RenderingInterface & renderingInterface) const398 void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const 399 { 400 renderingInterface.getObjects().insertNPC(ptr); 401 } 402 isPersistent(const MWWorld::ConstPtr & actor) const403 bool Npc::isPersistent(const MWWorld::ConstPtr &actor) const 404 { 405 const MWWorld::LiveCellRef<ESM::NPC>* ref = actor.get<ESM::NPC>(); 406 return ref->mBase->mPersistent; 407 } 408 getModel(const MWWorld::ConstPtr & ptr) const409 std::string Npc::getModel(const MWWorld::ConstPtr &ptr) const 410 { 411 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 412 413 std::string model = Settings::Manager::getString("baseanim", "Models"); 414 const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace); 415 if(race->mData.mFlags & ESM::Race::Beast) 416 model = Settings::Manager::getString("baseanimkna", "Models"); 417 418 return model; 419 } 420 getModelsToPreload(const MWWorld::Ptr & ptr,std::vector<std::string> & models) const421 void Npc::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector<std::string> &models) const 422 { 423 const MWWorld::LiveCellRef<ESM::NPC> *npc = ptr.get<ESM::NPC>(); 424 const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(npc->mBase->mRace); 425 if(race && race->mData.mFlags & ESM::Race::Beast) 426 models.emplace_back(Settings::Manager::getString("baseanimkna", "Models")); 427 428 // keep these always loaded just in case 429 models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models")); 430 models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models")); 431 models.emplace_back(Settings::Manager::getString("xbaseanim", "Models")); 432 433 if (!npc->mBase->mModel.empty()) 434 models.push_back("meshes/"+npc->mBase->mModel); 435 436 if (!npc->mBase->mHead.empty()) 437 { 438 const ESM::BodyPart* head = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(npc->mBase->mHead); 439 if (head) 440 models.push_back("meshes/"+head->mModel); 441 } 442 if (!npc->mBase->mHair.empty()) 443 { 444 const ESM::BodyPart* hair = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(npc->mBase->mHair); 445 if (hair) 446 models.push_back("meshes/"+hair->mModel); 447 } 448 449 bool female = (npc->mBase->mFlags & ESM::NPC::Female); 450 451 // FIXME: use const version of InventoryStore functions once they are available 452 // preload equipped items 453 const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); 454 for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) 455 { 456 MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); 457 if (equipped != invStore.end()) 458 { 459 std::vector<ESM::PartReference> parts; 460 if(equipped->getTypeName() == typeid(ESM::Clothing).name()) 461 { 462 const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase; 463 parts = clothes->mParts.mParts; 464 } 465 else if(equipped->getTypeName() == typeid(ESM::Armor).name()) 466 { 467 const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase; 468 parts = armor->mParts.mParts; 469 } 470 else 471 { 472 std::string model = equipped->getClass().getModel(*equipped); 473 if (!model.empty()) 474 models.push_back(model); 475 } 476 477 for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it) 478 { 479 std::string partname = female ? it->mFemale : it->mMale; 480 if (partname.empty()) 481 partname = female ? it->mMale : it->mFemale; 482 const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname); 483 if (part && !part->mModel.empty()) 484 models.push_back("meshes/"+part->mModel); 485 } 486 } 487 } 488 489 // preload body parts 490 if (race) 491 { 492 const std::vector<const ESM::BodyPart*>& parts = MWRender::NpcAnimation::getBodyParts(Misc::StringUtils::lowerCase(race->mId), female, false, false); 493 for (std::vector<const ESM::BodyPart*>::const_iterator it = parts.begin(); it != parts.end(); ++it) 494 { 495 const ESM::BodyPart* part = *it; 496 if (part && !part->mModel.empty()) 497 models.push_back("meshes/"+part->mModel); 498 } 499 } 500 501 } 502 getName(const MWWorld::ConstPtr & ptr) const503 std::string Npc::getName (const MWWorld::ConstPtr& ptr) const 504 { 505 if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) 506 { 507 const MWBase::World *world = MWBase::Environment::get().getWorld(); 508 const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>(); 509 510 return store.find("sWerewolfPopup")->mValue.getString(); 511 } 512 513 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 514 const std::string& name = ref->mBase->mName; 515 516 return !name.empty() ? name : ref->mBase->mId; 517 } 518 getCreatureStats(const MWWorld::Ptr & ptr) const519 MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const 520 { 521 ensureCustomData (ptr); 522 523 return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; 524 } 525 getNpcStats(const MWWorld::Ptr & ptr) const526 MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const 527 { 528 ensureCustomData (ptr); 529 530 return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; 531 } 532 533 hit(const MWWorld::Ptr & ptr,float attackStrength,int type) const534 void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const 535 { 536 MWBase::World *world = MWBase::Environment::get().getWorld(); 537 538 const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>(); 539 540 // Get the weapon used (if hand-to-hand, weapon = inv.end()) 541 MWWorld::InventoryStore &inv = getInventoryStore(ptr); 542 MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); 543 MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr()); 544 if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) 545 weapon = MWWorld::Ptr(); 546 547 MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); 548 549 const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat(); 550 float dist = fCombatDistance * (!weapon.isEmpty() ? 551 weapon.get<ESM::Weapon>()->mBase->mData.mReach : 552 store.find("fHandToHandReach")->mValue.getFloat()); 553 554 // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. 555 std::vector<MWWorld::Ptr> targetActors; 556 if (ptr != MWMechanics::getPlayer()) 557 getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); 558 559 // TODO: Use second to work out the hit angle 560 std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors); 561 MWWorld::Ptr victim = result.first; 562 osg::Vec3f hitPosition (result.second); 563 if(victim.isEmpty()) // Didn't hit anything 564 return; 565 566 const MWWorld::Class &othercls = victim.getClass(); 567 if(!othercls.isActor()) // Can't hit non-actors 568 return; 569 MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim); 570 if(otherstats.isDead()) // Can't hit dead actors 571 return; 572 573 if(ptr == MWMechanics::getPlayer()) 574 MWBase::Environment::get().getWindowManager()->setEnemy(victim); 575 576 int weapskill = ESM::Skill::HandToHand; 577 if(!weapon.isEmpty()) 578 weapskill = weapon.getClass().getEquipmentSkill(weapon); 579 580 float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill)); 581 582 if (Misc::Rng::roll0to99() >= hitchance) 583 { 584 othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false); 585 MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); 586 return; 587 } 588 589 bool healthdmg; 590 float damage = 0.0f; 591 if(!weapon.isEmpty()) 592 { 593 const unsigned char *attack = nullptr; 594 if(type == ESM::Weapon::AT_Chop) 595 attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop; 596 else if(type == ESM::Weapon::AT_Slash) 597 attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash; 598 else if(type == ESM::Weapon::AT_Thrust) 599 attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust; 600 if(attack) 601 { 602 damage = attack[0] + ((attack[1]-attack[0])*attackStrength); 603 } 604 MWMechanics::adjustWeaponDamage(damage, weapon, ptr); 605 MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); 606 MWMechanics::applyWerewolfDamageMult(victim, weapon, damage); 607 MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); 608 healthdmg = true; 609 } 610 else 611 { 612 MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); 613 } 614 if(ptr == MWMechanics::getPlayer()) 615 { 616 skillUsageSucceeded(ptr, weapskill, 0); 617 618 const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence(); 619 620 bool unaware = !seq.isInCombat() 621 && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); 622 if(unaware) 623 { 624 damage *= store.find("fCombatCriticalStrikeMult")->mValue.getFloat(); 625 MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); 626 MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); 627 } 628 } 629 630 if (othercls.getCreatureStats(victim).getKnockedDown()) 631 damage *= store.find("fCombatKODamageMult")->mValue.getFloat(); 632 633 // Apply "On hit" enchanted weapons 634 MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); 635 636 MWMechanics::applyElementalShields(ptr, victim); 637 638 if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) 639 damage = 0; 640 641 if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState()) 642 damage = 0; 643 644 MWMechanics::diseaseContact(victim, ptr); 645 646 othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); 647 } 648 onHit(const MWWorld::Ptr & ptr,float damage,bool ishealth,const MWWorld::Ptr & object,const MWWorld::Ptr & attacker,const osg::Vec3f & hitPosition,bool successful) const649 void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const 650 { 651 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); 652 MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 653 bool wasDead = stats.isDead(); 654 655 // Note OnPcHitMe is not set for friendly hits. 656 bool setOnPcHitMe = true; 657 658 // NOTE: 'object' and/or 'attacker' may be empty. 659 if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) 660 { 661 stats.setAttacked(true); 662 setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); 663 } 664 665 // Attacker and target store each other as hitattemptactor if they have no one stored yet 666 if (!attacker.isEmpty() && attacker.getClass().isActor()) 667 { 668 MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); 669 // First handle the attacked actor 670 if ((stats.getHitAttemptActorId() == -1) 671 && (statsAttacker.getAiSequence().isInCombat(ptr) 672 || attacker == MWMechanics::getPlayer())) 673 stats.setHitAttemptActorId(statsAttacker.getActorId()); 674 675 // Next handle the attacking actor 676 if ((statsAttacker.getHitAttemptActorId() == -1) 677 && (statsAttacker.getAiSequence().isInCombat(ptr) 678 || attacker == MWMechanics::getPlayer())) 679 statsAttacker.setHitAttemptActorId(stats.getActorId()); 680 } 681 682 if (!object.isEmpty()) 683 stats.setLastHitAttemptObject(object.getCellRef().getRefId()); 684 685 if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) 686 { 687 const std::string &script = getScript(ptr); 688 /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ 689 if(!script.empty()) 690 ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); 691 } 692 693 if (!successful) 694 { 695 // Missed 696 if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) 697 sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); 698 return; 699 } 700 701 if (!object.isEmpty()) 702 stats.setLastHitObject(object.getCellRef().getRefId()); 703 704 705 if (damage > 0.0f && !object.isEmpty()) 706 MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); 707 708 if (damage < 0.001f) 709 damage = 0; 710 711 bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); 712 713 if (godmode) 714 damage = 0; 715 716 if (damage > 0.0f && !attacker.isEmpty()) 717 { 718 // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying 719 // something, alert the character controller, scripts, etc. 720 721 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); 722 const GMST& gmst = getGmst(); 723 724 int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->mValue.getInteger(); 725 if (Misc::Rng::roll0to99() < chance) 726 MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); 727 728 // Check for knockdown 729 float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->mValue.getFloat(); 730 float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() 731 * gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f + gmst.iKnockDownOddsBase->mValue.getInteger(); 732 if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) 733 stats.setKnockedDown(true); 734 else 735 stats.setHitRecovery(true); // Is this supposed to always occur? 736 737 if (damage > 0 && ishealth) 738 { 739 // Hit percentages: 740 // cuirass = 30% 741 // shield, helmet, greaves, boots, pauldrons = 10% each 742 // guantlets = 5% each 743 static const int hitslots[20] = { 744 MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, 745 MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, 746 MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, 747 MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft, 748 MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Helmet, 749 MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Greaves, 750 MWWorld::InventoryStore::Slot_Boots, MWWorld::InventoryStore::Slot_Boots, 751 MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_LeftPauldron, 752 MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron, 753 MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet 754 }; 755 int hitslot = hitslots[Misc::Rng::rollDice(20)]; 756 757 float unmitigatedDamage = damage; 758 float x = damage / (damage + getArmorRating(ptr)); 759 damage *= std::max(gmst.fCombatArmorMinMult->mValue.getFloat(), x); 760 int damageDiff = static_cast<int>(unmitigatedDamage - damage); 761 damage = std::max(1.f, damage); 762 damageDiff = std::max(1, damageDiff); 763 764 MWWorld::InventoryStore &inv = getInventoryStore(ptr); 765 MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); 766 MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); 767 bool hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name(); 768 // If there's no item in the carried left slot or if it is not a shield redistribute the hit. 769 if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft) 770 { 771 if (Misc::Rng::rollDice(2) == 0) 772 hitslot = MWWorld::InventoryStore::Slot_Cuirass; 773 else 774 hitslot = MWWorld::InventoryStore::Slot_LeftPauldron; 775 armorslot = inv.getSlot(hitslot); 776 if (armorslot != inv.end()) 777 { 778 armor = *armorslot; 779 hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name(); 780 } 781 } 782 if (hasArmor) 783 { 784 if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()) // Unarmed creature attacks don't affect armor condition 785 { 786 int armorhealth = armor.getClass().getItemHealth(armor); 787 armorhealth -= std::min(damageDiff, armorhealth); 788 armor.getCellRef().setCharge(armorhealth); 789 790 // Armor broken? unequip it 791 if (armorhealth == 0) 792 armor = *inv.unequipItem(armor, ptr); 793 } 794 795 if (ptr == MWMechanics::getPlayer()) 796 skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); 797 798 switch(armor.getClass().getEquipmentSkill(armor)) 799 { 800 case ESM::Skill::LightArmor: 801 sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); 802 break; 803 case ESM::Skill::MediumArmor: 804 sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); 805 break; 806 case ESM::Skill::HeavyArmor: 807 sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); 808 break; 809 } 810 } 811 else if(ptr == MWMechanics::getPlayer()) 812 skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); 813 } 814 } 815 816 if (ishealth) 817 { 818 if (!attacker.isEmpty() && !godmode) 819 damage = scaleDamage(damage, attacker, ptr); 820 821 if (damage > 0.0f) 822 { 823 sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); 824 if (ptr == MWMechanics::getPlayer()) 825 MWBase::Environment::get().getWindowManager()->activateHitOverlay(); 826 if (!attacker.isEmpty()) 827 MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition); 828 } 829 MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth()); 830 health.setCurrent(health.getCurrent() - damage); 831 stats.setHealth(health); 832 } 833 else 834 { 835 MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue()); 836 fatigue.setCurrent(fatigue.getCurrent() - damage, true); 837 stats.setFatigue(fatigue); 838 } 839 840 if (!wasDead && getCreatureStats(ptr).isDead()) 841 { 842 // NPC was killed 843 if (!attacker.isEmpty() && attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()) 844 { 845 attacker.getClass().getNpcStats(attacker).addWerewolfKill(); 846 } 847 848 MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker); 849 } 850 } 851 activate(const MWWorld::Ptr & ptr,const MWWorld::Ptr & actor) const852 std::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr, 853 const MWWorld::Ptr& actor) const 854 { 855 // player got activated by another NPC 856 if(ptr == MWMechanics::getPlayer()) 857 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(actor)); 858 859 // Werewolfs can't activate NPCs 860 if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) 861 { 862 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); 863 const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC"); 864 865 std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); 866 if(sound) action->setSound(sound->mId); 867 868 return action; 869 } 870 871 const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 872 873 if(stats.isDead()) 874 { 875 bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game"); 876 877 // by default user can loot friendly actors during death animation 878 if (canLoot && !stats.getAiSequence().isInCombat()) 879 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); 880 881 // otherwise wait until death animation 882 if(stats.isDeathAnimationFinished()) 883 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); 884 } 885 else if (!stats.getAiSequence().isInCombat()) 886 { 887 if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) 888 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing 889 890 // Can't talk to werewolves 891 if (!getNpcStats(ptr).isWerewolf()) 892 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr)); 893 } 894 else // In combat 895 { 896 const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); 897 if (stealingInCombat && stats.getKnockedDown()) 898 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing 899 } 900 901 // Tribunal and some mod companions oddly enough must use open action as fallback 902 if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) 903 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); 904 905 return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction("")); 906 } 907 getContainerStore(const MWWorld::Ptr & ptr) const908 MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) 909 const 910 { 911 ensureCustomData (ptr); 912 913 return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; 914 } 915 getInventoryStore(const MWWorld::Ptr & ptr) const916 MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr) 917 const 918 { 919 ensureCustomData (ptr); 920 921 return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; 922 } 923 getScript(const MWWorld::ConstPtr & ptr) const924 std::string Npc::getScript (const MWWorld::ConstPtr& ptr) const 925 { 926 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 927 928 return ref->mBase->mScript; 929 } 930 getMaxSpeed(const MWWorld::Ptr & ptr) const931 float Npc::getMaxSpeed(const MWWorld::Ptr& ptr) const 932 { 933 // TODO: This function is called several times per frame for each NPC. 934 // It would be better to calculate it only once per frame for each NPC and save the result in CreatureStats. 935 const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 936 bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); 937 if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead()) 938 return 0.f; 939 940 const MWBase::World *world = MWBase::Environment::get().getWorld(); 941 const GMST& gmst = getGmst(); 942 943 const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData()); 944 const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); 945 946 const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); 947 948 bool swimming = world->isSwimming(ptr); 949 bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); 950 bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); 951 bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr); 952 running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); 953 954 float moveSpeed; 955 if(getEncumbrance(ptr) > getCapacity(ptr)) 956 moveSpeed = 0.0f; 957 else if(mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && 958 world->isLevitationEnabled()) 959 { 960 float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + 961 mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); 962 flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); 963 flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; 964 flySpeed = std::max(0.0f, flySpeed); 965 moveSpeed = flySpeed; 966 } 967 else if (swimming) 968 moveSpeed = getSwimSpeed(ptr); 969 else if (running && !sneaking) 970 moveSpeed = getRunSpeed(ptr); 971 else 972 moveSpeed = getWalkSpeed(ptr); 973 974 if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) 975 moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); 976 977 return moveSpeed; 978 } 979 getJump(const MWWorld::Ptr & ptr) const980 float Npc::getJump(const MWWorld::Ptr &ptr) const 981 { 982 if(getEncumbrance(ptr) > getCapacity(ptr)) 983 return 0.f; 984 985 const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 986 bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); 987 if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead()) 988 return 0.f; 989 990 const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData()); 991 const GMST& gmst = getGmst(); 992 const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); 993 const float encumbranceTerm = gmst.fJumpEncumbranceBase->mValue.getFloat() + 994 gmst.fJumpEncumbranceMultiplier->mValue.getFloat() * 995 (1.0f - Npc::getNormalizedEncumbrance(ptr)); 996 997 float a = getSkill(ptr, ESM::Skill::Acrobatics); 998 float b = 0.0f; 999 if(a > 50.0f) 1000 { 1001 b = a - 50.0f; 1002 a = 50.0f; 1003 } 1004 1005 float x = gmst.fJumpAcrobaticsBase->mValue.getFloat() + 1006 std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->mValue.getFloat()); 1007 x += 3.0f * b * gmst.fJumpAcroMultiplier->mValue.getFloat(); 1008 x += mageffects.get(ESM::MagicEffect::Jump).getMagnitude() * 64; 1009 x *= encumbranceTerm; 1010 1011 if(stats.getStance(MWMechanics::CreatureStats::Stance_Run)) 1012 x *= gmst.fJumpRunMultiplier->mValue.getFloat(); 1013 x *= npcdata->mNpcStats.getFatigueTerm(); 1014 x -= -Constants::GravityConst * Constants::UnitsPerMeter; 1015 x /= 3.0f; 1016 1017 return x; 1018 } 1019 getMovementSettings(const MWWorld::Ptr & ptr) const1020 MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const 1021 { 1022 ensureCustomData (ptr); 1023 1024 return ptr.getRefData().getCustomData()->asNpcCustomData().mMovement; 1025 } 1026 isEssential(const MWWorld::ConstPtr & ptr) const1027 bool Npc::isEssential (const MWWorld::ConstPtr& ptr) const 1028 { 1029 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1030 1031 return (ref->mBase->mFlags & ESM::NPC::Essential) != 0; 1032 } 1033 registerSelf()1034 void Npc::registerSelf() 1035 { 1036 std::shared_ptr<Class> instance (new Npc); 1037 registerClass (typeid (ESM::NPC).name(), instance); 1038 } 1039 hasToolTip(const MWWorld::ConstPtr & ptr) const1040 bool Npc::hasToolTip(const MWWorld::ConstPtr& ptr) const 1041 { 1042 if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode()) 1043 return true; 1044 1045 const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); 1046 1047 if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) 1048 return true; 1049 1050 if (!customData.mNpcStats.getAiSequence().isInCombat()) 1051 return true; 1052 1053 const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); 1054 if (stealingInCombat && customData.mNpcStats.getKnockedDown()) 1055 return true; 1056 1057 return false; 1058 } 1059 getToolTipInfo(const MWWorld::ConstPtr & ptr,int count) const1060 MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const 1061 { 1062 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1063 1064 bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp(); 1065 MWGui::ToolTipInfo info; 1066 1067 info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); 1068 if(fullHelp && !ref->mBase->mName.empty() && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) 1069 { 1070 info.caption += " ("; 1071 info.caption += MyGUI::TextIterator::toTagsString(ref->mBase->mName); 1072 info.caption += ")"; 1073 } 1074 1075 if(fullHelp) 1076 info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); 1077 1078 return info; 1079 } 1080 getCapacity(const MWWorld::Ptr & ptr) const1081 float Npc::getCapacity (const MWWorld::Ptr& ptr) const 1082 { 1083 const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); 1084 static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fEncumbranceStrMult")->mValue.getFloat(); 1085 return stats.getAttribute(ESM::Attribute::Strength).getModified()*fEncumbranceStrMult; 1086 } 1087 getEncumbrance(const MWWorld::Ptr & ptr) const1088 float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const 1089 { 1090 // According to UESP, inventory weight is ignored in werewolf form. Does that include 1091 // feather and burden effects? 1092 return getNpcStats(ptr).isWerewolf() ? 0.0f : Actor::getEncumbrance(ptr); 1093 } 1094 apply(const MWWorld::Ptr & ptr,const std::string & id,const MWWorld::Ptr & actor) const1095 bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, 1096 const MWWorld::Ptr& actor) const 1097 { 1098 MWMechanics::CastSpell cast(ptr, ptr); 1099 return cast.cast(id); 1100 } 1101 skillUsageSucceeded(const MWWorld::Ptr & ptr,int skill,int usageType,float extraFactor) const1102 void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const 1103 { 1104 MWMechanics::NpcStats& stats = getNpcStats (ptr); 1105 1106 if (stats.isWerewolf()) 1107 return; 1108 1109 MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1110 1111 const ESM::Class *class_ = 1112 MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find ( 1113 ref->mBase->mClass 1114 ); 1115 1116 stats.useSkill (skill, *class_, usageType, extraFactor); 1117 } 1118 getArmorRating(const MWWorld::Ptr & ptr) const1119 float Npc::getArmorRating (const MWWorld::Ptr& ptr) const 1120 { 1121 const MWBase::World *world = MWBase::Environment::get().getWorld(); 1122 const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>(); 1123 1124 MWMechanics::NpcStats &stats = getNpcStats(ptr); 1125 const MWWorld::InventoryStore &invStore = getInventoryStore(ptr); 1126 1127 float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); 1128 float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); 1129 float unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored); 1130 1131 float ratings[MWWorld::InventoryStore::Slots]; 1132 for(int i = 0;i < MWWorld::InventoryStore::Slots;i++) 1133 { 1134 MWWorld::ConstContainerStoreIterator it = invStore.getSlot(i); 1135 if (it == invStore.end() || it->getTypeName() != typeid(ESM::Armor).name()) 1136 { 1137 // unarmored 1138 ratings[i] = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); 1139 } 1140 else 1141 { 1142 ratings[i] = it->getClass().getEffectiveArmorRating(*it, ptr); 1143 1144 // Take in account armor condition 1145 const bool hasHealth = it->getClass().hasItemHealth(*it); 1146 if (hasHealth) 1147 { 1148 ratings[i] *= it->getClass().getItemNormalizedHealth(*it); 1149 } 1150 } 1151 } 1152 1153 float shield = stats.getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); 1154 1155 return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3f 1156 + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet] 1157 + ratings[MWWorld::InventoryStore::Slot_Greaves] + ratings[MWWorld::InventoryStore::Slot_Boots] 1158 + ratings[MWWorld::InventoryStore::Slot_LeftPauldron] + ratings[MWWorld::InventoryStore::Slot_RightPauldron] 1159 ) * 0.1f 1160 + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + ratings[MWWorld::InventoryStore::Slot_RightGauntlet]) 1161 * 0.05f 1162 + shield; 1163 } 1164 adjustScale(const MWWorld::ConstPtr & ptr,osg::Vec3f & scale,bool rendering) const1165 void Npc::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f&scale, bool rendering) const 1166 { 1167 if (!rendering) 1168 return; // collision meshes are not scaled based on race height 1169 // having the same collision extents for all races makes the environments easier to test 1170 1171 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1172 1173 const ESM::Race* race = 1174 MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace); 1175 1176 // Race weight should not affect 1st-person meshes, otherwise it will change hand proportions and can break aiming. 1177 if (ptr == MWMechanics::getPlayer() && ptr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson()) 1178 { 1179 if (ref->mBase->isMale()) 1180 scale *= race->mData.mHeight.mMale; 1181 else 1182 scale *= race->mData.mHeight.mFemale; 1183 1184 return; 1185 } 1186 1187 if (ref->mBase->isMale()) 1188 { 1189 scale.x() *= race->mData.mWeight.mMale; 1190 scale.y() *= race->mData.mWeight.mMale; 1191 scale.z() *= race->mData.mHeight.mMale; 1192 } 1193 else 1194 { 1195 scale.x() *= race->mData.mWeight.mFemale; 1196 scale.y() *= race->mData.mWeight.mFemale; 1197 scale.z() *= race->mData.mHeight.mFemale; 1198 } 1199 } 1200 getServices(const MWWorld::ConstPtr & actor) const1201 int Npc::getServices(const MWWorld::ConstPtr &actor) const 1202 { 1203 return actor.get<ESM::NPC>()->mBase->mAiData.mServices; 1204 } 1205 1206 getSoundIdFromSndGen(const MWWorld::Ptr & ptr,const std::string & name) const1207 std::string Npc::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const 1208 { 1209 if(name == "left" || name == "right") 1210 { 1211 MWBase::World *world = MWBase::Environment::get().getWorld(); 1212 if(world->isFlying(ptr)) 1213 return std::string(); 1214 osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); 1215 if(world->isSwimming(ptr)) 1216 return (name == "left") ? "Swim Left" : "Swim Right"; 1217 if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) 1218 return (name == "left") ? "FootWaterLeft" : "FootWaterRight"; 1219 if(world->isOnGround(ptr)) 1220 { 1221 if (getNpcStats(ptr).isWerewolf() 1222 && getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) 1223 { 1224 int weaponType = ESM::Weapon::None; 1225 MWMechanics::getActiveWeapon(ptr, &weaponType); 1226 if (weaponType == ESM::Weapon::None) 1227 return std::string(); 1228 } 1229 1230 const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr); 1231 MWWorld::ConstContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); 1232 if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name()) 1233 return (name == "left") ? "FootBareLeft" : "FootBareRight"; 1234 1235 switch(boots->getClass().getEquipmentSkill(*boots)) 1236 { 1237 case ESM::Skill::LightArmor: 1238 return (name == "left") ? "FootLightLeft" : "FootLightRight"; 1239 case ESM::Skill::MediumArmor: 1240 return (name == "left") ? "FootMedLeft" : "FootMedRight"; 1241 case ESM::Skill::HeavyArmor: 1242 return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight"; 1243 } 1244 } 1245 return std::string(); 1246 } 1247 1248 // Morrowind ignores land soundgen for NPCs 1249 if(name == "land") 1250 return std::string(); 1251 if(name == "swimleft") 1252 return "Swim Left"; 1253 if(name == "swimright") 1254 return "Swim Right"; 1255 // TODO: I have no idea what these are supposed to do for NPCs since they use 1256 // voiced dialog for various conditions like health loss and combat taunts. Maybe 1257 // only for biped creatures? 1258 1259 if(name == "moan") 1260 return std::string(); 1261 if(name == "roar") 1262 return std::string(); 1263 if(name == "scream") 1264 return std::string(); 1265 1266 throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); 1267 } 1268 copyToCellImpl(const MWWorld::ConstPtr & ptr,MWWorld::CellStore & cell) const1269 MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const 1270 { 1271 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1272 1273 return MWWorld::Ptr(cell.insert(ref), &cell); 1274 } 1275 getSkill(const MWWorld::Ptr & ptr,int skill) const1276 float Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const 1277 { 1278 return getNpcStats(ptr).getSkill(skill).getModified(); 1279 } 1280 getBloodTexture(const MWWorld::ConstPtr & ptr) const1281 int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const 1282 { 1283 return ptr.get<ESM::NPC>()->mBase->mBloodType; 1284 } 1285 readAdditionalState(const MWWorld::Ptr & ptr,const ESM::ObjectState & state) const1286 void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) 1287 const 1288 { 1289 if (!state.mHasCustomState) 1290 return; 1291 1292 if (state.mVersion > 0) 1293 { 1294 if (!ptr.getRefData().getCustomData()) 1295 { 1296 // Create a CustomData, but don't fill it from ESM records (not needed) 1297 ptr.getRefData().setCustomData(std::make_unique<NpcCustomData>()); 1298 } 1299 } 1300 else 1301 ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. 1302 1303 NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); 1304 const ESM::NpcState& npcState = state.asNpcState(); 1305 customData.mInventoryStore.readState (npcState.mInventory); 1306 customData.mNpcStats.readState (npcState.mNpcStats); 1307 bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get<ESM::NPC>()->mBase->mId); 1308 if(spellsInitialised) 1309 customData.mNpcStats.getSpells().clear(); 1310 customData.mNpcStats.readState (npcState.mCreatureStats); 1311 } 1312 writeAdditionalState(const MWWorld::ConstPtr & ptr,ESM::ObjectState & state) const1313 void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) 1314 const 1315 { 1316 if (!ptr.getRefData().getCustomData()) 1317 { 1318 state.mHasCustomState = false; 1319 return; 1320 } 1321 1322 if (ptr.getRefData().getCount() <= 0) 1323 { 1324 state.mHasCustomState = false; 1325 return; 1326 } 1327 1328 const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); 1329 ESM::NpcState& npcState = state.asNpcState(); 1330 customData.mInventoryStore.writeState (npcState.mInventory); 1331 customData.mNpcStats.writeState (npcState.mNpcStats); 1332 customData.mNpcStats.writeState (npcState.mCreatureStats); 1333 } 1334 getBaseGold(const MWWorld::ConstPtr & ptr) const1335 int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const 1336 { 1337 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1338 return ref->mBase->mNpdt.mGold; 1339 } 1340 isClass(const MWWorld::ConstPtr & ptr,const std::string & className) const1341 bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const 1342 { 1343 return Misc::StringUtils::ciEqual(ptr.get<ESM::NPC>()->mBase->mClass, className); 1344 } 1345 canSwim(const MWWorld::ConstPtr & ptr) const1346 bool Npc::canSwim(const MWWorld::ConstPtr &ptr) const 1347 { 1348 return true; 1349 } 1350 canWalk(const MWWorld::ConstPtr & ptr) const1351 bool Npc::canWalk(const MWWorld::ConstPtr &ptr) const 1352 { 1353 return true; 1354 } 1355 respawn(const MWWorld::Ptr & ptr) const1356 void Npc::respawn(const MWWorld::Ptr &ptr) const 1357 { 1358 const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); 1359 if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) 1360 return; 1361 1362 if (!creatureStats.isDeathAnimationFinished()) 1363 return; 1364 1365 const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); 1366 static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); 1367 static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); 1368 1369 float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); 1370 1371 if (ptr.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Respawn 1372 && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) 1373 { 1374 if (ptr.getCellRef().hasContentFile()) 1375 { 1376 if (ptr.getRefData().getCount() == 0) 1377 { 1378 ptr.getRefData().setCount(1); 1379 const std::string& script = getScript(ptr); 1380 if (!script.empty()) 1381 MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); 1382 } 1383 1384 MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); 1385 ptr.getRefData().setCustomData(nullptr); 1386 1387 // Reset to original position 1388 MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], 1389 ptr.getCellRef().getPosition().pos[1], 1390 ptr.getCellRef().getPosition().pos[2]); 1391 } 1392 } 1393 } 1394 getBaseFightRating(const MWWorld::ConstPtr & ptr) const1395 int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const 1396 { 1397 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1398 return ref->mBase->mAiData.mFight; 1399 } 1400 isBipedal(const MWWorld::ConstPtr & ptr) const1401 bool Npc::isBipedal(const MWWorld::ConstPtr &ptr) const 1402 { 1403 return true; 1404 } 1405 getPrimaryFaction(const MWWorld::ConstPtr & ptr) const1406 std::string Npc::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const 1407 { 1408 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1409 return ref->mBase->mFaction; 1410 } 1411 getPrimaryFactionRank(const MWWorld::ConstPtr & ptr) const1412 int Npc::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const 1413 { 1414 std::string factionID = ptr.getClass().getPrimaryFaction(ptr); 1415 if(factionID.empty()) 1416 return -1; 1417 1418 // Search in the NPC data first 1419 if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) 1420 { 1421 int rank = data->asNpcCustomData().mNpcStats.getFactionRank(factionID); 1422 if (rank >= 0) 1423 return rank; 1424 } 1425 1426 // Use base NPC record as a fallback 1427 const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); 1428 return ref->mBase->getFactionRank(); 1429 } 1430 setBaseAISetting(const std::string & id,MWMechanics::CreatureStats::AiSetting setting,int value) const1431 void Npc::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const 1432 { 1433 MWMechanics::setBaseAISetting<ESM::NPC>(id, setting, value); 1434 } 1435 modifyBaseInventory(const std::string & actorId,const std::string & itemId,int amount) const1436 void Npc::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const 1437 { 1438 MWMechanics::modifyBaseInventory<ESM::NPC>(actorId, itemId, amount); 1439 } 1440 getWalkSpeed(const MWWorld::Ptr & ptr) const1441 float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const 1442 { 1443 const GMST& gmst = getGmst(); 1444 const NpcCustomData* npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData()); 1445 const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); 1446 const bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); 1447 1448 float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() 1449 + 0.01f * npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() 1450 * (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); 1451 walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat()*normalizedEncumbrance; 1452 walkSpeed = std::max(0.0f, walkSpeed); 1453 if(sneaking) 1454 walkSpeed *= gmst.fSneakSpeedMultiplier->mValue.getFloat(); 1455 1456 return walkSpeed; 1457 } 1458 getRunSpeed(const MWWorld::Ptr & ptr) const1459 float Npc::getRunSpeed(const MWWorld::Ptr& ptr) const 1460 { 1461 const GMST& gmst = getGmst(); 1462 return getWalkSpeed(ptr) 1463 * (0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fAthleticsRunBonus->mValue.getFloat() 1464 + gmst.fBaseRunMultiplier->mValue.getFloat()); 1465 } 1466 getSwimSpeed(const MWWorld::Ptr & ptr) const1467 float Npc::getSwimSpeed(const MWWorld::Ptr& ptr) const 1468 { 1469 const GMST& gmst = getGmst(); 1470 const MWBase::World* world = MWBase::Environment::get().getWorld(); 1471 const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 1472 const NpcCustomData* npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData()); 1473 const MWMechanics::MagicEffects& mageffects = npcdata->mNpcStats.getMagicEffects(); 1474 const bool swimming = world->isSwimming(ptr); 1475 const bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr); 1476 const bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run) 1477 && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); 1478 1479 float swimSpeed; 1480 1481 if (running) 1482 swimSpeed = getRunSpeed(ptr); 1483 else 1484 swimSpeed = getWalkSpeed(ptr); 1485 1486 swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); 1487 swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() 1488 + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat(); 1489 1490 return swimSpeed; 1491 } 1492 } 1493