void SoundManager::say(const MWWorld::Ptr &ptr, const std::string& filename) { if(!mOutput->isInitialized()) return; try { float basevol = volumeFromType(Play_TypeVoice); std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getRefData().getPosition(); const osg::Vec3f objpos(pos.asVec3()); MWBase::World* world = MWBase::Environment::get().getWorld(); static const float fAudioMinDistanceMult = world->getStore().get<ESM::GameSetting>().find("fAudioMinDistanceMult")->getFloat(); static const float fAudioMaxDistanceMult = world->getStore().get<ESM::GameSetting>().find("fAudioMaxDistanceMult")->getFloat(); static const float fAudioVoiceDefaultMinDistance = world->getStore().get<ESM::GameSetting>().find("fAudioVoiceDefaultMinDistance")->getFloat(); static const float fAudioVoiceDefaultMaxDistance = world->getStore().get<ESM::GameSetting>().find("fAudioVoiceDefaultMaxDistance")->getFloat(); float minDistance = fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult; float maxDistance = fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult; minDistance = std::max(minDistance, 1.f); maxDistance = std::max(minDistance, maxDistance); MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, minDistance, maxDistance, Play_Normal|Play_TypeVoice, 0, true); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) { std::cout <<"Sound Error: "<<e.what()<< std::endl; } }
// Convert a soundId to file name, and modify the volume // according to the sounds local volume setting, minRange and // maxRange. std::string SoundManager::lookup(const std::string &soundId, float &volume, float &min, float &max) { MWBase::World* world = MWBase::Environment::get().getWorld(); const ESM::Sound *snd = world->getStore().get<ESM::Sound>().find(soundId); volume *= static_cast<float>(pow(10.0, (snd->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); if(snd->mData.mMinRange == 0 && snd->mData.mMaxRange == 0) { static const float fAudioDefaultMinDistance = world->getStore().get<ESM::GameSetting>().find("fAudioDefaultMinDistance")->getFloat(); static const float fAudioDefaultMaxDistance = world->getStore().get<ESM::GameSetting>().find("fAudioDefaultMaxDistance")->getFloat(); min = fAudioDefaultMinDistance; max = fAudioDefaultMaxDistance; } else { min = snd->mData.mMinRange; max = snd->mData.mMaxRange; } static const float fAudioMinDistanceMult = world->getStore().get<ESM::GameSetting>().find("fAudioMinDistanceMult")->getFloat(); static const float fAudioMaxDistanceMult = world->getStore().get<ESM::GameSetting>().find("fAudioMaxDistanceMult")->getFloat(); min *= fAudioMinDistanceMult; max *= fAudioMaxDistanceMult; min = std::max(min, 1.0f); max = std::max(min, max); return "Sound/"+snd->mSound; }
void LevelupDialog::open() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats (player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); mSpentAttributes.clear(); resetCoins(); setAttributeValues(); const ESM::NPC *playerData = player.get<ESM::NPC>()->mBase; // set class image const ESM::Class *cls = world->getStore().get<ESM::Class>().find(playerData->mClass); // Vanilla uses thief.dds for custom classes. A player with a custom class // doesn't have mId set, see mwworld/esmstore.hpp where it is initialised as // "$dynamic0". This check should resolve bug #1260. // Choosing Stealth specialization and Speed/Agility as attributes. if(world->getStore().get<ESM::Class>().isDynamic(cls->mId)) { MWWorld::SharedIterator<ESM::Class> it = world->getStore().get<ESM::Class>().begin(); for(; it != world->getStore().get<ESM::Class>().end(); it++) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) break; } mClassImage->setImageTexture ("textures\\levelup\\" + it->mId + ".dds"); } else mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast<std::string>(level)); std::string levelupdescription; if(level>20) levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default"); else levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+boost::lexical_cast<std::string>(level)); mLevelDescription->setCaption (levelupdescription); for (int i=0; i<8; ++i) { MyGUI::TextBox* text = mAttributeMultipliers[i]; int mult = pcStats.getLevelupAttributeMultiplier (i); text->setCaption(mult <= 1 ? "" : "x" + boost::lexical_cast<std::string>(mult)); } center(); }
std::string InterpreterContext::getPCNextRank() const { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); std::string factionId = mReference.getClass().getNpcStats (mReference).getFactionRanks().begin()->first; std::map<std::string, int> ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map<std::string, int>::const_iterator it = ranks.find(factionId); int rank = -1; if (it != ranks.end()) rank = it->second; ++rank; // Next rank // if we are already at max rank, there is no next rank if (rank > 9) rank = 9; const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId); if(rank < 0 || rank > 9) return ""; return faction->mRanks[rank]; }
std::string InterpreterContext::getPCRank() const { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); std::string factionId = mReference.getClass().getNpcStats (mReference).getFactionRanks().begin()->first; std::map<std::string, int> ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map<std::string, int>::const_iterator it = ranks.find(factionId); int rank = -1; if (it != ranks.end()) rank = it->second; // If you are not in the faction, PcRank returns the first rank, for whatever reason. // This is used by the dialogue when joining the Thieves Guild in Balmora. if (rank == -1) rank = 0; const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId); if(rank < 0 || rank > 9) // there are only 10 ranks return ""; return faction->mRanks[rank]; }
void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) { MWBase::World* world = MWBase::Environment::get().getWorld(); const ESM::Pathgrid *pathgrid = world->getStore().get<ESM::Pathgrid>().search(*store->getCell()); if (!pathgrid) return; osg::Vec3f cellPathGridPos(0, 0, 0); MWMechanics::CoordinateConverter(store->getCell()).toWorld(cellPathGridPos); osg::ref_ptr<osg::PositionAttitudeTransform> cellPathGrid = new osg::PositionAttitudeTransform; cellPathGrid->setPosition(cellPathGridPos); osg::ref_ptr<osg::Geometry> geometry = SceneUtil::createPathgridGeometry(*pathgrid); cellPathGrid->addChild(geometry); mPathGridRoot->addChild(cellPathGrid); if (store->getCell()->isExterior()) { mExteriorPathgridNodes[std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())] = cellPathGrid; } else { assert(mInteriorPathgridNode == NULL); mInteriorPathgridNode = cellPathGrid; } }
std::string InterpreterContext::getPCRank() const { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); std::string factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); if (factionId.empty()) throw std::runtime_error("getPCRank(): NPC is not in a faction"); const std::map<std::string, int>& ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map<std::string, int>::const_iterator it = ranks.find(Misc::StringUtils::lowerCase(factionId)); int rank = -1; if (it != ranks.end()) rank = it->second; // If you are not in the faction, PcRank returns the first rank, for whatever reason. // This is used by the dialogue when joining the Thieves Guild in Balmora. if (rank == -1) rank = 0; const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId); if(rank < 0 || rank > 9) // there are only 10 ranks return ""; return faction->mRanks[rank]; }
std::string InterpreterContext::getPCNextRank() const { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); std::string factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); if (factionId.empty()) throw std::runtime_error("getPCNextRank(): NPC is not in a faction"); const std::map<std::string, int>& ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map<std::string, int>::const_iterator it = ranks.find(Misc::StringUtils::lowerCase(factionId)); int rank = -1; if (it != ranks.end()) rank = it->second; ++rank; // Next rank // if we are already at max rank, there is no next rank if (rank > 9) rank = 9; const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get<ESM::Faction>().find(factionId); if(rank < 0 || rank > 9) return ""; return faction->mRanks[rank]; }
void SoundManager::updateRegionSound(float duration) { static float sTimeToNextEnvSound = 0.0f; static int total = 0; static std::string regionName = ""; static float sTimePassed = 0.0; MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Ptr player = world->getPlayerPtr(); const ESM::Cell *cell = player.getCell()->getCell(); sTimePassed += duration; if(!cell->isExterior() || sTimePassed < sTimeToNextEnvSound) return; float a = Misc::Rng::rollClosedProbability(); // NOTE: We should use the "Minimum Time Between Environmental Sounds" and // "Maximum Time Between Environmental Sounds" fallback settings here. sTimeToNextEnvSound = 5.0f*a + 15.0f*(1.0f-a); sTimePassed = 0; if(regionName != cell->mRegion) { regionName = cell->mRegion; total = 0; } const ESM::Region *regn = world->getStore().get<ESM::Region>().search(regionName); if(regn == NULL) return; std::vector<ESM::Region::SoundRef>::const_iterator soundIter; if(total == 0) { soundIter = regn->mSoundList.begin(); while(soundIter != regn->mSoundList.end()) { total += (int)soundIter->mChance; ++soundIter; } if(total == 0) return; } int r = Misc::Rng::rollDice(total); int pos = 0; soundIter = regn->mSoundList.begin(); while(soundIter != regn->mSoundList.end()) { if(r - pos < soundIter->mChance) { playSound(soundIter->mSound.toString(), 1.0f, 1.0f); break; } pos += soundIter->mChance; ++soundIter; } }
void RecordHelper::updateNpcRecord(const ESM::NPC& npc) { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::ESMStore *esmStore = const_cast<MWWorld::ESMStore *>(&world->getStore()); MWWorld::Store<ESM::NPC> *npcStore = const_cast<MWWorld::Store<ESM::NPC> *> (&esmStore->get<ESM::NPC>()); npcStore->insert(npc); }
void RecordHelper::updateCreatureRecord(const ESM::Creature& creature) { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::ESMStore *esmStore = const_cast<MWWorld::ESMStore *>(&world->getStore()); MWWorld::Store<ESM::Creature> *creatureStore = const_cast<MWWorld::Store<ESM::Creature> *> (&esmStore->get<ESM::Creature>()); creatureStore->insert(creature); }
void WaitDialog::startWaiting(int hoursToWait) { if(Settings::Manager::getBool("autosave","Saves")) //autosaves when enabled MWBase::Environment::get().getStateManager()->quickSave("Autosave"); MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2f); mFadeTimeRemaining = 0.4f; setVisible(false); mHours = hoursToWait; // FIXME: move this somewhere else? mInterruptAt = -1; MWWorld::Ptr player = world->getPlayerPtr(); if (mSleeping && player.getCell()->isExterior()) { std::string regionstr = player.getCell()->getCell()->mRegion; if (!regionstr.empty()) { const ESM::Region *region = world->getStore().get<ESM::Region>().find (regionstr); if (!region->mSleepList.empty()) { // figure out if player will be woken while sleeping int x = Misc::Rng::rollDice(hoursToWait); float fSleepRandMod = world->getStore().get<ESM::GameSetting>().find("fSleepRandMod")->getFloat(); if (x < fSleepRandMod * hoursToWait) { float fSleepRestMod = world->getStore().get<ESM::GameSetting>().find("fSleepRestMod")->getFloat(); int interruptAtHoursRemaining = int(fSleepRestMod * hoursToWait); if (interruptAtHoursRemaining != 0) { mInterruptAt = hoursToWait - interruptAtHoursRemaining; mInterruptCreatureList = region->mSleepList; } } } } } mProgressBar.setProgress (0, hoursToWait); }
std::string InterpreterContext::getNPCRank() const { std::map<std::string, int> ranks = mReference.getClass().getNpcStats (mReference).getFactionRanks(); std::map<std::string, int>::const_iterator it = ranks.begin(); MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get<ESM::Faction>().find(it->first); return faction->mRanks[it->second]; }
void WaitDialog::startWaiting(int hoursToWait) { if(Settings::Manager::getBool("autosave","Saves") && mSleeping) //autosaves when enabled and sleeping MWBase::Environment::get().getStateManager()->quickSave("Autosave"); MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2); setVisible(false); mProgressBar.setVisible (true); mWaiting = true; mCurHour = 0; mHours = hoursToWait; // FIXME: move this somewhere else? mInterruptAt = -1; MWWorld::Ptr player = world->getPlayerPtr(); if (mSleeping && player.getCell()->isExterior()) { std::string regionstr = player.getCell()->getCell()->mRegion; if (!regionstr.empty()) { const ESM::Region *region = world->getStore().get<ESM::Region>().find (regionstr); if (!region->mSleepList.empty()) { float fSleepRandMod = world->getStore().get<ESM::GameSetting>().find("fSleepRandMod")->getFloat(); int x = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * hoursToWait; // [0, hoursRested] float y = fSleepRandMod * hoursToWait; if (x > y) { float fSleepRestMod = world->getStore().get<ESM::GameSetting>().find("fSleepRestMod")->getFloat(); mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait); mInterruptCreatureList = region->mSleepList; } } } } mRemainingTime = 0.05; mProgressBar.setProgress (0, mHours); }
std::string InterpreterContext::getNPCRank() const { const MWWorld::Ptr& ptr = getReferenceImp(); std::string faction = ptr.getClass().getPrimaryFaction(ptr); if (faction.empty()) throw std::runtime_error("getNPCRank(): NPC is not in a faction"); int rank = ptr.getClass().getPrimaryFactionRank(ptr); if (rank < 0 || rank > 9) throw std::runtime_error("getNPCRank(): invalid rank"); MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *fact = store.get<ESM::Faction>().find(faction); return fact->mRanks[rank]; }
Player::Player (MWRender::Player *renderer, const ESM::NPC *player, const MWBase::World& world) : mCellStore (0), mRenderer (renderer), mClass (0), mAutoMove (false), mForwardBackward (0) { mPlayer.base = player; mPlayer.ref.refID = "player"; mName = player->name; mMale = !(player->flags & ESM::NPC::Female); mRace = player->race; float* playerPos = mPlayer.mData.getPosition().pos; playerPos[0] = playerPos[1] = playerPos[2] = 0; mPlayer.mData.setBaseNode(renderer->getNode()); /// \todo Do not make a copy of classes defined in esm/p records. mClass = new ESM::Class (*world.getStore().classes.find (player->cls)); }
void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) { MWBase::World* world = MWBase::Environment::get().getWorld(); const ESM::Pathgrid *pathgrid = world->getStore().get<ESM::Pathgrid>().search(*store->getCell()); if (!pathgrid) return; osg::Vec3f cellPathGridPos(0, 0, 0); if (store->getCell()->isExterior()) { cellPathGridPos.x() = static_cast<float>(store->getCell()->mData.mX * ESM::Land::REAL_SIZE); cellPathGridPos.y() = static_cast<float>(store->getCell()->mData.mY * ESM::Land::REAL_SIZE); } osg::ref_ptr<osg::PositionAttitudeTransform> cellPathGrid = new osg::PositionAttitudeTransform; cellPathGrid->setPosition(cellPathGridPos); osg::ref_ptr<osg::Geode> lineGeode = new osg::Geode; osg::ref_ptr<osg::Geometry> lines = createPathgridLines(pathgrid); lineGeode->addDrawable(lines); osg::ref_ptr<osg::Geode> pointGeode = new osg::Geode; osg::ref_ptr<osg::Geometry> points = createPathgridPoints(pathgrid); pointGeode->addDrawable(points); cellPathGrid->addChild(lineGeode); cellPathGrid->addChild(pointGeode); mPathGridRoot->addChild(cellPathGrid); if (store->getCell()->isExterior()) { mExteriorPathgridNodes[std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())] = cellPathGrid; } else { assert(mInteriorPathgridNode == NULL); mInteriorPathgridNode = cellPathGrid; } }
float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue) { MWMechanics::CreatureStats &stats = attacker.getClass().getCreatureStats(attacker); const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>(); float defenseTerm = 0; MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim); if (victimStats.getFatigue().getCurrent() >= 0) { // Maybe we should keep an aware state for actors updated every so often instead of testing every time bool unaware = (!victimStats.getAiSequence().isInCombat()) && (attacker == getPlayer()) && (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim)); if (!(victimStats.getKnockedDown() || victimStats.isParalyzed() || unaware )) { defenseTerm = victimStats.getEvasion(); } defenseTerm += std::min(100.f, gmst.find("fCombatInvisoMult")->getFloat() * victimStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude()); defenseTerm += std::min(100.f, gmst.find("fCombatInvisoMult")->getFloat() * victimStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude()); } float attackTerm = skillValue + (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); attackTerm *= stats.getFatigueTerm(); attackTerm += mageffects.get(ESM::MagicEffect::FortifyAttack).getMagnitude() - mageffects.get(ESM::MagicEffect::Blind).getMagnitude(); return round(attackTerm - defenseTerm); }
void projectileHit(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, MWWorld::Ptr weapon, const MWWorld::Ptr &projectile, const osg::Vec3f& hitPosition, float attackStrength) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>(); if(victim.isEmpty() || !victim.getClass().isActor() || victim.getClass().getCreatureStats(victim).isDead()) // Can't hit non-actors or dead actors { reduceWeaponCondition(0.f, false, weapon, attacker); return; } if(attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weapskill = ESM::Skill::Marksman; if(!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); return; } const unsigned char* attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop; float damage = attack[0] + ((attack[1]-attack[0])*attackStrength); // Bow/crossbow damage // Arrow/bolt damage // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon attack = projectile.get<ESM::Weapon>()->mBase->mData.mChop; damage += attack[0] + ((attack[1]-attack[0])*attackStrength); adjustWeaponDamage(damage, weapon, attacker); reduceWeaponCondition(damage, true, weapon, attacker); if(attacker == getPlayer()) attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); if (victim.getClass().getCreatureStats(victim).getKnockedDown()) damage *= gmst.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" effect of the weapon bool appliedEnchantment = applyEnchantment(attacker, victim, weapon, hitPosition); if (weapon != projectile) appliedEnchantment = applyEnchantment(attacker, victim, projectile, hitPosition); if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory if (victim != getPlayer() && !appliedEnchantment) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } victim.getClass().onHit(victim, damage, true, projectile, attacker, true); }
bool AiCombat::reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, AiCombatStorage& storage, MWWorld::Ptr target) { MWMechanics::Movement& movement = storage.mMovement; if (isTargetMagicallyHidden(target)) { storage.stopAttack(); return false; // TODO: run away instead of doing nothing } const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); if(!currentCell || cellChange) { currentCell = actor.getCell(); } const MWWorld::Class& actorClass = actor.getClass(); actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); float& actionCooldown = storage.mActionCooldown; if (actionCooldown > 0) return false; float rangeAttack = 0; float rangeFollow = 0; boost::shared_ptr<Action>& currentAction = storage.mCurrentAction; if (characterController.readyToPrepareAttack()) { currentAction = prepareNextAction(actor, target); actionCooldown = currentAction->getActionCooldown(); } if (currentAction.get()) currentAction->getCombatRange(rangeAttack, rangeFollow); // FIXME: consider moving this stuff to ActionWeapon::getCombatRange const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype = WeapType_None; float weapRange = 1.0f; // Get weapon characteristics MWBase::World* world = MWBase::Environment::get().getWorld(); if (actorClass.hasInventoryStore(actor)) { //Get weapon range MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); if (weaptype == WeapType_HandToHand) { static float fHandToHandReach = world->getStore().get<ESM::GameSetting>().find("fHandToHandReach")->getFloat(); weapRange = fHandToHandReach; } else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None) { // All other WeapTypes are actually weapons, so get<ESM::Weapon> is safe. weapon = weaponSlot->get<ESM::Weapon>()->mBase; weapRange = weapon->mData.mReach; } weapRange *= 100.0f; } else //is creature { weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; weapRange = 150.0f; //TODO: use true attack range (the same problem in Creature::hit) } bool distantCombat = false; if (weaptype != WeapType_Spell) { // TODO: move to ActionWeapon if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) { rangeAttack = 1000; rangeFollow = 0; // not needed in ranged combat distantCombat = true; } else { rangeAttack = weapRange; rangeFollow = 300; } } else { distantCombat = (rangeAttack > 500); } bool& readyToAttack = storage.mReadyToAttack; // start new attack storage.startAttackIfReady(actor, characterController, weapon, distantCombat); /* * Some notes on meanings of variables: * * rangeAttack: * * - Distance where attack using the actor's weapon is possible: * longer for ranged weapons (obviously?) vs. melee weapons * - Determined by weapon's reach parameter; hardcoded value * for ranged weapon and for creatures * - Once within this distance mFollowTarget is triggered * * rangeFollow: * * - Applies to melee weapons or hand to hand only (or creatures without * weapons) * - Distance a little further away than the actor's weapon reach * i.e. rangeFollow > rangeAttack for melee weapons * - Hardcoded value (0 for ranged weapons) * - Once the target gets beyond this distance mFollowTarget is cleared * and a path to the target needs to be found * * mFollowTarget: * * - Once triggered, the actor follows the target with LOS shortcut * (the shortcut really only applies to cells where pathgrids are * available, since the default path without pathgrids is direct to * target even if LOS is not achieved) */ ESM::Position pos = actor.getRefData().getPosition(); osg::Vec3f vActorPos(pos.asVec3()); osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); osg::Vec3f& lastActorPos = storage.mLastActorPos; bool& followTarget = storage.mFollowTarget; bool isStuck = false; float speed = 0.0f; if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * REACTION_INTERVAL / 2) isStuck = true; lastActorPos = vActorPos; // check if actor can move along z-axis bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. if (distToTarget >= rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? storage.stopAttack(); return false; } // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack bool inLOS = distantCombat ? world->getLOS(actor, target) : true; // (within attack dist) || (not quite attack dist while following) if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) { mPathFinder.clearPath(); //Melee and Close-up combat // getXAngleToDir determines vertical angle to target: // if actor can move along z-axis it will control movement dir // if can't - it will control correct aiming. // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate if (distantCombat) { osg::Vec3f& lastTargetPos = storage.mLastTargetPos; vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, REACTION_INTERVAL, weaptype, storage.mStrength); lastTargetPos = vTargetPos; movement.mRotation[0] = getXAngleToDir(vAimDir); movement.mRotation[2] = getZAngleToDir(vAimDir); } else { movement.mRotation[0] = getXAngleToDir(vAimDir); movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } // (not quite attack dist while following) if (followTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target storage.stopCombatMove(); movement.mPosition[1] = 1; } else // (within attack dist) { storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); readyToAttack = true; //only once got in melee combat, actor is allowed to use close-up shortcutting followTarget = true; } } else // remote pathfinding { bool preferShortcut = false; if (!distantCombat) inLOS = world->getLOS(actor, target); // check if shortcut is available bool& forceNoShortcut = storage.mForceNoShortcut; ESM::Position& shortcutFailPos = storage.mShortcutFailPos; if(inLOS && (!isStuck || readyToAttack) && (!forceNoShortcut || (shortcutFailPos.asVec3() - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { if(speed == 0.0f) speed = actorClass.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed float maxAvoidDist = REACTION_INTERVAL * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vAimDir.x(), vAimDir.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); } // don't use pathgrid when actor can move in 3 dimensions if (canMoveByZ) { preferShortcut = true; movement.mRotation[0] = getXAngleToDir(vAimDir); } if(preferShortcut) { movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); forceNoShortcut = false; shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0; mPathFinder.clearPath(); } else // if shortcut failed stick to path grid { if(!isStuck && shortcutFailPos.pos[0] == 0.0f && shortcutFailPos.pos[1] == 0.0f && shortcutFailPos.pos[2] == 0.0f) { forceNoShortcut = true; shortcutFailPos = pos; } followTarget = false; buildNewPath(actor, target); // should always return a path (even if it's just go straight on target.) assert(mPathFinder.isPathConstructed()); } if (readyToAttack) { // to stop possible sideway moving after target moved out of attack range storage.stopCombatMove(); readyToAttack = false; } movement.mPosition[1] = 1; } return false; }
void projectileHit(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, MWWorld::Ptr weapon, const MWWorld::Ptr &projectile, const Ogre::Vector3& hitPosition) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>(); MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); const MWWorld::Class &othercls = victim.getClass(); if(!othercls.isActor()) // Can't hit non-actors return; MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); if(otherstats.isDead()) // Can't hit dead actors return; if(attacker.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weapskill = ESM::Skill::Marksman; if(!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); float skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); if((::rand()/(RAND_MAX+1.0)) > getHitChance(attacker, victim, skillValue)/100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); return; } float damage = 0.0f; float fDamageStrengthBase = gmst.find("fDamageStrengthBase")->getFloat(); float fDamageStrengthMult = gmst.find("fDamageStrengthMult")->getFloat(); const unsigned char* attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop; damage = attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); // Bow/crossbow damage if (weapon != projectile) { // Arrow/bolt damage attack = projectile.get<ESM::Weapon>()->mBase->mData.mChop; damage += attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); } damage *= fDamageStrengthBase + (attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1); if(attacker.getRefData().getHandle() == "player") attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); if(!detected) { damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } if (victim.getClass().getCreatureStats(victim).getKnockedDown()) damage *= gmst.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" effect of the weapon applyEnchantment(attacker, victim, weapon, hitPosition); if (weapon != projectile) applyEnchantment(attacker, victim, projectile, hitPosition); if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); victim.getClass().onHit(victim, damage, true, projectile, attacker, true); }
void LevelupDialog::open() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); const ESM::NPC *playerData = player.get<ESM::NPC>()->mBase; // set class image const ESM::Class *cls = world->getStore().get<ESM::Class>().find(playerData->mClass); if(world->getStore().get<ESM::Class>().isDynamic(cls->mId)) { // Vanilla uses thief.dds for custom classes. // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. MWWorld::SharedIterator<ESM::Class> it = world->getStore().get<ESM::Class>().begin(); for(; it != world->getStore().get<ESM::Class>().end(); it++) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) break; } if (it == world->getStore().get<ESM::Class>().end()) it = world->getStore().get<ESM::Class>().begin(); if (it != world->getStore().get<ESM::Class>().end()) cls = &*it; } mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast<std::string>(level)); std::string levelupdescription; if(level > 20) levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default"); else levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+boost::lexical_cast<std::string>(level)); mLevelDescription->setCaption (levelupdescription); unsigned int availableAttributes = 0; for (int i = 0; i < 8; ++i) { MyGUI::TextBox* text = mAttributeMultipliers[i]; if (pcStats.getAttribute(i).getBase() < 100) { mAttributes[i]->setEnabled(true); availableAttributes++; int mult = pcStats.getLevelupAttributeMultiplier (i); text->setCaption(mult <= 1 ? "" : "x" + boost::lexical_cast<std::string>(mult)); } else { mAttributes[i]->setEnabled(false); text->setCaption(""); } } mCoinCount = std::min(sMaxCoins, availableAttributes); for (unsigned int i = 0; i < sMaxCoins; i++) { if (i < mCoinCount) mCoins[i]->attachToWidget(mCoinBox); else mCoins[i]->detachFromWidget(); } mSpentAttributes.clear(); resetCoins(); setAttributeValues(); center(); }
bool AiTravel::execute (const MWWorld::Ptr& actor,float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); Movement &movement = actor.getClass().getMovementSettings(actor); const ESM::Cell *cell = actor.getCell()->mCell; MWWorld::Ptr player = world->getPlayer().getPlayer(); if(cell->mData.mX != player.getCell()->mCell->mData.mX) { int sideX = sgn(cell->mData.mX - player.getCell()->mCell->mData.mX); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { movement.mPosition[1] = 0; return false; } } if(cell->mData.mY != player.getCell()->mCell->mData.mY) { int sideY = sgn(cell->mData.mY - player.getCell()->mCell->mData.mY); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) { movement.mPosition[1] = 0; return false; } } const ESM::Pathgrid *pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell); bool cellChange = cell->mData.mX != cellX || cell->mData.mY != cellY; if(!mPathFinder.isPathConstructed() || cellChange) { cellX = cell->mData.mX; cellY = cell->mData.mY; float xCell = 0; float yCell = 0; if(cell->isExterior()) { xCell = cell->mData.mX * ESM::Land::REAL_SIZE; yCell = cell->mData.mY * ESM::Land::REAL_SIZE; } ESM::Pathgrid::Point dest; dest.mX = mX; dest.mY = mY; dest.mZ = mZ; ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); } if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { movement.mPosition[1] = 0; return true; } float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); // TODO: use movement settings instead of rotating directly world->rotateObject(actor, 0, 0, zAngle, false); movement.mPosition[1] = 1; return false; }
/* * Current AiCombat movement states (as of 0.29.0), ignoring the details of the * attack states such as CombatMove, Strike and ReadyToAttack: * * +----(within strike range)----->attack--(beyond strike range)-->follow * | | ^ | | * | | | | | * pursue<---(beyond follow range)-----+ +----(within strike range)---+ | * ^ | * | | * +-------------------------(beyond follow range)--------------------+ * * * Below diagram is high level only, the code detail is a little different * (but including those detail will just complicate the diagram w/o adding much) * * +----------(same)-------------->attack---------(same)---------->follow * | |^^ ||| * | ||| ||| * | +--(same)-----------------+|+----------(same)------------+|| * | | | || * | | | (in range) || * | <---+ (too far) | || * pursue<-------------------------[door open]<-----+ || * ^^^ | || * ||| | || * ||+----------evade-----+ | || * || | [closed door] | || * |+----> maybe stuck, check --------------> back up, check door || * | ^ | ^ | ^ || * | | | | | | || * | | +---+ +---+ || * | +-------------------------------------------------------+| * | | * +---------------------------(same)---------------------------------+ * * FIXME: * * The new scheme is way too complicated, should really be implemented as a * proper state machine. * * TODO: * * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. */ bool AiCombat::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { // get or create temporary storage AiCombatStorage& storage = state.get<AiCombatStorage>(); //General description if(actor.getClass().getCreatureStats(actor).isDead()) return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); if (target.isEmpty()) return false; if(!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered // with the MechanicsManager || target.getClass().getCreatureStats(target).isDead()) return true; const MWWorld::Class& actorClass = actor.getClass(); MWBase::World* world = MWBase::Environment::get().getWorld(); //Update every frame bool& combatMove = storage.mCombatMove; float& timerCombatMove = storage.mTimerCombatMove; MWMechanics::Movement& movement = storage.mMovement; if(combatMove) { timerCombatMove -= duration; if( timerCombatMove <= 0) { timerCombatMove = 0; movement.mPosition[1] = movement.mPosition[0] = 0; combatMove = false; } } actorClass.getMovementSettings(actor) = movement; actorClass.getMovementSettings(actor).mRotation[0] = 0; actorClass.getMovementSettings(actor).mRotation[2] = 0; if(movement.mRotation[2] != 0) { if(zTurn(actor, Ogre::Degree(movement.mRotation[2]))) movement.mRotation[2] = 0; } if(movement.mRotation[0] != 0) { if(smoothTurn(actor, Ogre::Degree(movement.mRotation[0]), 0)) movement.mRotation[0] = 0; } //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f float attacksPeriod = 1.0f; ESM::Weapon::AttackType attackType; bool& attack = storage.mAttack; bool& readyToAttack = storage.mReadyToAttack; float& timerAttack = storage.mTimerAttack; bool& minMaxAttackDurationInitialised = storage.mMinMaxAttackDurationInitialised; float (&minMaxAttackDuration)[3][2] = storage.mMinMaxAttackDuration; if(readyToAttack) { if (!minMaxAttackDurationInitialised) { // TODO: this must be updated when a different weapon is equipped getMinMaxAttackDuration(actor, minMaxAttackDuration); minMaxAttackDurationInitialised = true; } if (timerAttack < 0) attack = false; timerAttack -= duration; } else { timerAttack = -attacksPeriod; attack = false; } actorClass.getCreatureStats(actor).setAttackingOrSpell(attack); float& actionCooldown = storage.mActionCooldown; actionCooldown -= duration; float& timerReact = storage.mTimerReact; float tReaction = 0.25f; if(timerReact < tReaction) { timerReact += duration; return false; } //Update with period = tReaction // Stop attacking if target is not seen if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 || target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 75) { movement.mPosition[1] = movement.mPosition[0] = 0; return false; // TODO: run away instead of doing nothing } timerReact = 0; const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); if(!currentCell || cellChange) { currentCell = actor.getCell(); } MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(actor); if (!anim) // shouldn't happen return false; actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); if (actionCooldown > 0) return false; float rangeAttack = 0; float rangeFollow = 0; boost::shared_ptr<Action>& currentAction = storage.mCurrentAction; if (anim->upperBodyReady()) { currentAction = prepareNextAction(actor, target); actionCooldown = currentAction->getActionCooldown(); } if (currentAction.get()) currentAction->getCombatRange(rangeAttack, rangeFollow); // FIXME: consider moving this stuff to ActionWeapon::getCombatRange const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype = WeapType_None; float weapRange = 1.0f; // Get weapon characteristics if (actorClass.hasInventoryStore(actor)) { // TODO: Check equipped weapon and equip a different one if we can't attack with it // (e.g. no ammunition, or wrong type of ammunition equipped, etc. autoEquip is not very smart in this regard)) //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); if (weaptype == WeapType_HandToHand) { static float fHandToHandReach = world->getStore().get<ESM::GameSetting>().find("fHandToHandReach")->getFloat(); weapRange = fHandToHandReach; } else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None) { // All other WeapTypes are actually weapons, so get<ESM::Weapon> is safe. weapon = weaponSlot->get<ESM::Weapon>()->mBase; weapRange = weapon->mData.mReach; } weapRange *= 100.0f; } else //is creature { weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; weapRange = 150.0f; //TODO: use true attack range (the same problem in Creature::hit) } bool distantCombat = false; if (weaptype != WeapType_Spell) { // TODO: move to ActionWeapon if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) { rangeAttack = 1000; rangeFollow = 0; // not needed in ranged combat distantCombat = true; } else { rangeAttack = weapRange; rangeFollow = 300; } } else { distantCombat = (rangeAttack > 500); weapRange = 150.f; } float& strength = storage.mStrength; // start new attack if(readyToAttack) { if(timerAttack <= -attacksPeriod) { attack = true; // attack starts just now if (!distantCombat) attackType = chooseBestAttack(weapon, movement); else attackType = ESM::Weapon::AT_Chop; // cause it's =0 strength = OEngine::Misc::Rng::rollClosedProbability(); // Note: may be 0 for some animations timerAttack = minMaxAttackDuration[attackType][0] + (minMaxAttackDuration[attackType][1] - minMaxAttackDuration[attackType][0]) * strength; //say a provoking combat phrase if (actor.getClass().isNpc()) { const MWWorld::ESMStore &store = world->getStore(); int chance = store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->getInt(); if (OEngine::Misc::Rng::roll0to99() < chance) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } } } } /* * Some notes on meanings of variables: * * rangeAttack: * * - Distance where attack using the actor's weapon is possible: * longer for ranged weapons (obviously?) vs. melee weapons * - Determined by weapon's reach parameter; hardcoded value * for ranged weapon and for creatures * - Once within this distance mFollowTarget is triggered * * rangeFollow: * * - Applies to melee weapons or hand to hand only (or creatures without * weapons) * - Distance a little further away than the actor's weapon reach * i.e. rangeFollow > rangeAttack for melee weapons * - Hardcoded value (0 for ranged weapons) * - Once the target gets beyond this distance mFollowTarget is cleared * and a path to the target needs to be found * * mFollowTarget: * * - Once triggered, the actor follows the target with LOS shortcut * (the shortcut really only applies to cells where pathgrids are * available, since the default path without pathgrids is direct to * target even if LOS is not achieved) */ ESM::Position pos = actor.getRefData().getPosition(); Ogre::Vector3 vActorPos(pos.pos); Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; float distToTarget = vDirToTarget.length(); Ogre::Vector3& lastActorPos = storage.mLastActorPos; bool& followTarget = storage.mFollowTarget; bool isStuck = false; float speed = 0.0f; if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2) isStuck = true; lastActorPos = vActorPos; // check if actor can move along z-axis bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack bool inLOS = distantCombat ? world->getLOS(actor, target) : true; // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. if (distToTarget >= rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? movement.mPosition[0] = 0; movement.mPosition[1] = 0; movement.mPosition[2] = 0; readyToAttack = false; actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return false; } // (within attack dist) || (not quite attack dist while following) if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) { //Melee and Close-up combat // getXAngleToDir determines vertical angle to target: // if actor can move along z-axis it will control movement dir // if can't - it will control correct aiming. // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate if (distantCombat) { Ogre::Vector3& lastTargetPos = storage.mLastTargetPos; Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, strength); lastTargetPos = vTargetPos; movement.mRotation[0] = getXAngleToDir(vAimDir); movement.mRotation[2] = getZAngleToDir(vAimDir); } else { movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); movement.mRotation[2] = getZAngleToDir(vDirToTarget); } // (not quite attack dist while following) if (followTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target movement.mPosition[1] = 1; } else // (within attack dist) { if(movement.mPosition[0] || movement.mPosition[1]) { timerCombatMove = 0.1f + 0.1f * OEngine::Misc::Rng::rollClosedProbability(); combatMove = true; } // only NPCs are smart enough to use dodge movements else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) { //apply sideway movement (kind of dodging) with some probability if (OEngine::Misc::Rng::rollClosedProbability() < 0.25) { movement.mPosition[0] = OEngine::Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; timerCombatMove = 0.05f + 0.15f * OEngine::Misc::Rng::rollClosedProbability(); combatMove = true; } } if(distantCombat && distToTarget < rangeAttack/4) { movement.mPosition[1] = -1; } readyToAttack = true; //only once got in melee combat, actor is allowed to use close-up shortcutting followTarget = true; } } else // remote pathfinding { bool preferShortcut = false; if (!distantCombat) inLOS = world->getLOS(actor, target); // check if shortcut is available bool& forceNoShortcut = storage.mForceNoShortcut; ESM::Position& shortcutFailPos = storage.mShortcutFailPos; if(inLOS && (!isStuck || readyToAttack) && (!forceNoShortcut || (Ogre::Vector3(shortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { if(speed == 0.0f) speed = actorClass.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability preferShortcut = checkWayIsClear(vActorPos, vTargetPos, Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); } // don't use pathgrid when actor can move in 3 dimensions if(canMoveByZ) preferShortcut = true; if(preferShortcut) { if (canMoveByZ) movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); movement.mRotation[2] = getZAngleToDir(vDirToTarget); forceNoShortcut = false; shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0; mPathFinder.clearPath(); } else // if shortcut failed stick to path grid { if(!isStuck && shortcutFailPos.pos[0] == 0.0f && shortcutFailPos.pos[1] == 0.0f && shortcutFailPos.pos[2] == 0.0f) { forceNoShortcut = true; shortcutFailPos = pos; } followTarget = false; buildNewPath(actor, target); //may fail to build a path, check before use //delete visited path node mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1]); // This works on the borders between the path grid and areas with no waypoints. if(inLOS && mPathFinder.getPath().size() > 1) { // get point just before target std::list<ESM::Pathgrid::Point>::const_iterator pntIter = --mPathFinder.getPath().end(); --pntIter; Ogre::Vector3 vBeforeTarget(PathFinder::MakeOgreVector3(*pntIter)); // if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target if(distToTarget <= (vTargetPos - vBeforeTarget).length()) { movement.mRotation[2] = getZAngleToDir(vDirToTarget); preferShortcut = true; } } // if there is no new path, then go straight on target if(!preferShortcut) { if(!mPathFinder.getPath().empty()) movement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); else movement.mRotation[2] = getZAngleToDir(vDirToTarget); } } movement.mPosition[1] = 1; if (readyToAttack) { // to stop possible sideway moving after target moved out of attack range combatMove = true; timerCombatMove = 0; } readyToAttack = false; } if(!isStuck && distToTarget > rangeAttack && !distantCombat) { //special run attack; it shouldn't affect melee combat tactics if(actorClass.getMovementSettings(actor).mPosition[1] == 1) { /* check if actor can overcome the distance = distToTarget - attackerWeapRange less than in time of swinging with weapon (t_swing), then start attacking */ float speed1 = actorClass.getSpeed(actor); float speed2 = target.getClass().getSpeed(target); if(target.getClass().getMovementSettings(target).mPosition[0] == 0 && target.getClass().getMovementSettings(target).mPosition[1] == 0) speed2 = 0; float s1 = distToTarget - weapRange; float t = s1/speed1; float s2 = speed2 * t; float t_swing = minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * OEngine::Misc::Rng::rollClosedProbability(); if (t + s2/speed1 <= t_swing) { readyToAttack = true; if(timerAttack <= -attacksPeriod) { timerAttack = t_swing; attack = true; } } } } // NOTE: This section gets updated every tReaction, which is currently hard // coded at 250ms or 1/4 second // // TODO: Add a parameter to vary DURATION_SAME_SPOT? if((distToTarget > rangeAttack || followTarget) && mObstacleCheck.check(actor, tReaction)) // check if evasive action needed { // probably walking into another NPC TODO: untested in combat situation // TODO: diagonal should have same animation as walk forward // but doesn't seem to do that? actor.getClass().getMovementSettings(actor).mPosition[0] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; // change the angle a bit, too if(mPathFinder.isPathConstructed()) zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); if(followTarget) followTarget = false; // FIXME: can fool actors to stay behind doors, etc. // Related to Bug#1102 and to some degree #1155 as well } return false; }
std::string InterpreterContext::getPCRace() const { MWBase::World *world = MWBase::Environment::get().getWorld(); std::string race = world->getPlayerPtr().get<ESM::NPC>()->mBase->mRace; return world->getStore().get<ESM::Race>().find(race)->mName; }
bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { if (actor.getClass().isNpc()) actor.getClass().getNpcStats(actor).setDrawState(DrawState_Nothing); MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { // End package if duration is complete or mid-night hits: MWWorld::TimeStamp currentTime = world->getTimeStamp(); if(currentTime.getHour() >= mStartTime.getHour() + mDuration) { if(!mRepeat) { stopWalking(actor); return true; } else mStartTime = currentTime; } else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()) { if(!mRepeat) { stopWalking(actor); return true; } else mStartTime = currentTime; } } ESM::Position pos = actor.getRefData().getPosition(); if(!mStoredAvailableNodes) { mStoredAvailableNodes = true; mPathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->mCell); mCellX = actor.getCell()->mCell->mData.mX; mCellY = actor.getCell()->mCell->mData.mY; if(!mPathgrid) mDistance = 0; else if(mPathgrid->mPoints.empty()) mDistance = 0; if(mDistance) { mXCell = 0; mYCell = 0; if(actor.getCell()->mCell->isExterior()) { mXCell = mCellX * ESM::Land::REAL_SIZE; mYCell = mCellY * ESM::Land::REAL_SIZE; } Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos); npcPos[0] = npcPos[0] - mXCell; npcPos[1] = npcPos[1] - mYCell; for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++) { Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY, mPathgrid->mPoints[counter].mZ); if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) mAllowedNodes.push_back(mPathgrid->mPoints[counter]); } if(!mAllowedNodes.empty()) { Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ); float closestNode = npcPos.squaredDistance(firstNodePos); unsigned int index = 0; for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) { Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, mAllowedNodes[counterThree].mZ); float tempDist = npcPos.squaredDistance(nodePos); if(tempDist < closestNode) index = counterThree; } mCurrentNode = mAllowedNodes[index]; mAllowedNodes.erase(mAllowedNodes.begin() + index); } } } if(mAllowedNodes.empty()) mDistance = 0; // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. if(mDistance && (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY)) mDistance = 0; if(mChooseAction) { mPlayedIdle = 0; unsigned short idleRoll = 0; for(unsigned int counter = 0; counter < mIdle.size(); counter++) { unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter]; unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { mPlayedIdle = counter+2; idleRoll = randSelect; } } if(!mPlayedIdle && mDistance) { mChooseAction = false; mMoveNow = true; } else { // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: MWWorld::TimeStamp currentTime = world->getTimeStamp(); mStartTime = currentTime; playIdle(actor, mPlayedIdle); mChooseAction = false; mIdleNow = true; } } if(mIdleNow) { if(!checkIdle(actor, mPlayedIdle)) { mPlayedIdle = 0; mIdleNow = false; mChooseAction = true; } } if(mMoveNow && mDistance) { if(!mPathFinder.isPathConstructed()) { assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ); ESM::Pathgrid::Point dest; dest.mX = destNodePos[0] + mXCell; dest.mY = destNodePos[1] + mYCell; dest.mZ = destNodePos[2]; ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; mPathFinder.buildPath(start, dest, mPathgrid, mXCell, mYCell, false); if(mPathFinder.isPathConstructed()) { // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; mAllowedNodes.erase(mAllowedNodes.begin() + randNode); mAllowedNodes.push_back(mCurrentNode); mCurrentNode = temp; mMoveNow = false; mWalking = true; } // Choose a different node and delete this one from possible nodes because it is uncreachable: else mAllowedNodes.erase(mAllowedNodes.begin() + randNode); } } if(mWalking) { float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); // TODO: use movement settings instead of rotating directly world->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { stopWalking(actor); mMoveNow = false; mWalking = false; mChooseAction = true; } } return false; }
std::string InterpreterContext::getPCClass() const { MWBase::World *world = MWBase::Environment::get().getWorld(); std::string class_ = world->getPlayerPtr().get<ESM::NPC>()->mBase->mClass; return world->getStore().get<ESM::Class>().find(class_)->mName; }
void projectileHit(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, MWWorld::Ptr weapon, const MWWorld::Ptr &projectile, const Ogre::Vector3& hitPosition) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>(); MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); if(victim.isEmpty() || !victim.getClass().isActor() || victim.getClass().getCreatureStats(victim).isDead()) // Can't hit non-actors or dead actors { reduceWeaponCondition(0.f, false, weapon, attacker); return; } if(attacker.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weapskill = ESM::Skill::Marksman; if(!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); float skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); if((::rand()/(RAND_MAX+1.0)) > getHitChance(attacker, victim, skillValue)/100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); return; } float damage = 0.0f; float fDamageStrengthBase = gmst.find("fDamageStrengthBase")->getFloat(); float fDamageStrengthMult = gmst.find("fDamageStrengthMult")->getFloat(); const unsigned char* attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop; damage = attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); // Bow/crossbow damage if (weapon != projectile) { // Arrow/bolt damage attack = projectile.get<ESM::Weapon>()->mBase->mData.mChop; damage += attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); } damage *= fDamageStrengthBase + (attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1); adjustWeaponDamage(damage, weapon); reduceWeaponCondition(damage, true, weapon, attacker); if(attacker.getRefData().getHandle() == "player") attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); if (victim.getClass().getCreatureStats(victim).getKnockedDown()) damage *= gmst.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" effect of the weapon applyEnchantment(attacker, victim, weapon, hitPosition); if (weapon != projectile) applyEnchantment(attacker, victim, projectile, hitPosition); if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); // Arrows shot at enemies have a chance to turn up in their inventory if (victim != MWBase::Environment::get().getWorld()->getPlayerPtr()) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } victim.getClass().onHit(victim, damage, true, projectile, attacker, true); }
bool RecordHelper::doesCreatureExist(const std::string& refId) { MWBase::World *world = MWBase::Environment::get().getWorld(); return world->getStore().get<ESM::Creature>().search(refId); }
void LevelupDialog::open() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats(player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); const ESM::NPC *playerData = player.get<ESM::NPC>()->mBase; // set class image const ESM::Class *cls = world->getStore().get<ESM::Class>().find(playerData->mClass); if(world->getStore().get<ESM::Class>().isDynamic(cls->mId)) { // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. MWWorld::SharedIterator<ESM::Class> it = world->getStore().get<ESM::Class>().begin(); for(; it != world->getStore().get<ESM::Class>().end(); ++it) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) break; } if (it == world->getStore().get<ESM::Class>().end()) it = world->getStore().get<ESM::Class>().begin(); if (it != world->getStore().get<ESM::Class>().end()) cls = &*it; } setClassImage(mClassImage, cls->mId); int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); std::string levelupdescription; if(level > 20) levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default"); else levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level)); mLevelDescription->setCaption (levelupdescription); unsigned int availableAttributes = 0; for (int i = 0; i < 8; ++i) { MyGUI::TextBox* text = mAttributeMultipliers[i]; if (pcStats.getAttribute(i).getBase() < 100) { mAttributes[i]->setEnabled(true); mAttributeValues[i]->setEnabled(true); availableAttributes++; int mult = pcStats.getLevelupAttributeMultiplier (i); mult = std::min(mult, 100-pcStats.getAttribute(i).getBase()); text->setCaption(mult <= 1 ? "" : "x" + MyGUI::utility::toString(mult)); } else { mAttributes[i]->setEnabled(false); mAttributeValues[i]->setEnabled(false); text->setCaption(""); } } mCoinCount = std::min(sMaxCoins, availableAttributes); mSpentAttributes.clear(); resetCoins(); setAttributeValues(); center(); // Play LevelUp Music MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Triumph.mp3"); }