diff --git a/CMakeLists.txt b/CMakeLists.txt index 78bc58903..c2526d5e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,7 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/.git) else(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) message(STATUS "Shallow Git clone detected, not attempting to retrieve version info") endif(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) -endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) +endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) # Macros include(OpenMWMacros) @@ -441,8 +441,10 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF(NOT WIN32 AND NOT APPLE) if(WIN32) - FILE(GLOB dll_files "${OpenMW_BINARY_DIR}/Release/*.dll") - INSTALL(FILES ${dll_files} DESTINATION ".") + FILE(GLOB dll_files_debug "${OpenMW_BINARY_DIR}/Debug/*.dll") + FILE(GLOB dll_files_release "${OpenMW_BINARY_DIR}/Release/*.dll") + INSTALL(FILES ${dll_files_debug} DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES ${dll_files_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") @@ -452,36 +454,23 @@ if(WIN32) "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg" "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" - "${OpenMW_BINARY_DIR}/Release/openmw.exe" DESTINATION ".") - IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-launcher.exe" DESTINATION ".") - ENDIF(BUILD_LAUNCHER) - IF(BUILD_MWINIIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-iniimporter.exe" DESTINATION ".") - ENDIF(BUILD_MWINIIMPORTER) - IF(BUILD_ESSIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-essimporter.exe" DESTINATION ".") - ENDIF(BUILD_ESSIMPORTER) - IF(BUILD_OPENCS) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-cs.exe" DESTINATION ".") - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".") - ENDIF(BUILD_OPENCS) - IF(BUILD_WIZARD) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-wizard.exe" DESTINATION ".") - ENDIF(BUILD_WIZARD) if(BUILD_MYGUI_PLUGIN) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION ".") + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Debug/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) ENDIF(BUILD_MYGUI_PLUGIN) IF(DESIRED_QT_VERSION MATCHES 5) - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION ".") + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/platforms" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) ENDIF() INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") - FILE(GLOB plugin_dir "${OpenMW_BINARY_DIR}/Release/osgPlugins-*") - INSTALL(DIRECTORY ${plugin_dir} DESTINATION ".") + FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*") + FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*") + INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug) + INSTALL(DIRECTORY ${plugin_dir_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) SET(CPACK_GENERATOR "NSIS") SET(CPACK_PACKAGE_NAME "OpenMW") @@ -857,3 +846,4 @@ if (DOXYGEN_FOUND) WORKING_DIRECTORY ${OpenMW_BINARY_DIR} COMMENT "Generating documentation for the github-pages at ${DOXYGEN_PAGES_OUTPUT_DIR}" VERBATIM) endif () + diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index 84e31dad9..93f53d0e8 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -42,3 +42,7 @@ if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-essimporter gcov) endif() + +if (WIN32) + INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") +endif(WIN32) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 207f6a84b..8cbe18d51 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -87,6 +87,10 @@ add_executable(openmw-launcher ${UI_HDRS} ) +if (WIN32) + INSTALL(TARGETS openmw-launcher RUNTIME DESTINATION ".") +endif (WIN32) + target_link_libraries(openmw-launcher ${SDL2_LIBRARY_ONLY} components diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 4024c0b42..4bd661685 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -22,7 +22,8 @@ target_link_libraries(openmw-iniimporter if (WIN32) target_link_libraries(openmw-iniimporter ${Boost_LOCALE_LIBRARY}) -endif() + INSTALL(TARGETS openmw-iniimporter RUNTIME DESTINATION ".") +endif(WIN32) if (MINGW) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 1f572c3f8..acf3cf021 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -223,8 +223,17 @@ endif() if (WIN32) target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) + INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".") endif() +if (MSVC) + # Debug version needs increased number of sections beyond 2^16 + if (CMAKE_CL_64) + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") + endif (CMAKE_CL_64) +endif (MSVC) + if(APPLE) INSTALL(TARGETS openmw-cs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d73bb7630..530d02eef 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -205,3 +205,8 @@ if (MSVC) endif (CMAKE_CL_64) add_definitions("-D_USE_MATH_DEFINES") endif (MSVC) + +if (WIN32) + INSTALL(TARGETS openmw RUNTIME DESTINATION ".") +endif (WIN32) + diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index e95b04416..2795012ff 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -563,6 +563,12 @@ namespace MWBase virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; virtual bool isPlayerInJail() const = 0; + + /// Return terrain height at \a worldPos position. + virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0; + + /// Return physical or rendering half extents of the given actor. + virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const = 0; }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 2e1dfbac5..80b343d4f 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -18,6 +18,7 @@ #include "character.hpp" #include "aicombataction.hpp" #include "combat.hpp" +#include "coordinateconverter.hpp" namespace { @@ -50,6 +51,19 @@ namespace MWMechanics bool mForceNoShortcut; ESM::Position mShortcutFailPos; MWMechanics::Movement mMovement; + + enum FleeState + { + FleeState_None, + FleeState_Idle, + FleeState_RunBlindly, + FleeState_RunToDestination + }; + FleeState mFleeState; + bool mFleeLOS; + float mFleeUpdateLOSTimer; + float mFleeBlindRunTimer; + ESM::Pathgrid::Point mFleeDest; AiCombatStorage(): mAttackCooldown(0), @@ -66,7 +80,11 @@ namespace MWMechanics mStrength(), mForceNoShortcut(false), mShortcutFailPos(), - mMovement() + mMovement(), + mFleeState(FleeState_None), + mFleeLOS(false), + mFleeUpdateLOSTimer(0.0f), + mFleeBlindRunTimer(0.0f) {} void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); @@ -76,6 +94,10 @@ namespace MWMechanics const ESM::Weapon* weapon, bool distantCombat); void updateAttack(CharacterController& characterController); void stopAttack(); + + void startFleeing(); + void stopFleeing(); + bool isFleeing(); }; AiCombat::AiCombat(const MWWorld::Ptr& actor) : @@ -157,16 +179,23 @@ namespace MWMechanics || target.getClass().getCreatureStats(target).isDead()) return true; - if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range + if (!storage.isFleeing()) { - //Update every frame - bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); - if (is_target_reached) storage.mReadyToAttack = true; - } + if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range + { + //Update every frame + bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); + if (is_target_reached) storage.mReadyToAttack = true; + } - storage.updateCombatMove(duration); - if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); - storage.updateAttack(characterController); + storage.updateCombatMove(duration); + if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); + storage.updateAttack(characterController); + } + else + { + updateFleeing(actor, target, duration, storage); + } storage.mActionCooldown -= duration; float& timerReact = storage.mTimerReact; @@ -185,12 +214,6 @@ namespace MWMechanics void AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) { - if (isTargetMagicallyHidden(target)) - { - storage.stopAttack(); - return; // TODO: run away instead of doing nothing - } - const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); if(!currentCell || cellChange) @@ -198,30 +221,61 @@ namespace MWMechanics currentCell = actor.getCell(); } + bool forceFlee = false; + if (!canFight(actor, target)) + { + storage.stopAttack(); + characterController.setAttackingOrSpell(false); + storage.mActionCooldown = 0.f; + forceFlee = true; + } + const MWWorld::Class& actorClass = actor.getClass(); actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); float& actionCooldown = storage.mActionCooldown; - if (actionCooldown > 0) - return; - - float &rangeAttack = storage.mAttackRange; boost::shared_ptr& currentAction = storage.mCurrentAction; - if (characterController.readyToPrepareAttack()) + + if (!forceFlee) { - currentAction = prepareNextAction(actor, target); + if (actionCooldown > 0) + return; + + if (characterController.readyToPrepareAttack()) + { + currentAction = prepareNextAction(actor, target); + actionCooldown = currentAction->getActionCooldown(); + } + } + else + { + currentAction.reset(new ActionFlee()); actionCooldown = currentAction->getActionCooldown(); } - const ESM::Weapon *weapon = NULL; - bool isRangedCombat = false; - if (currentAction.get()) + if (!currentAction) + return; + + if (storage.isFleeing() != currentAction->isFleeing()) { - rangeAttack = currentAction->getCombatRange(isRangedCombat); - // Get weapon characteristics - weapon = currentAction->getWeapon(); + if (currentAction->isFleeing()) + { + storage.startFleeing(); + MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); + return; + } + else + storage.stopFleeing(); } + bool isRangedCombat = false; + float &rangeAttack = storage.mAttackRange; + + rangeAttack = currentAction->getCombatRange(isRangedCombat); + + // Get weapon characteristics + const ESM::Weapon* weapon = currentAction->getWeapon(); + ESM::Position pos = actor.getRefData().getPosition(); osg::Vec3f vActorPos(pos.asVec3()); osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); @@ -229,19 +283,7 @@ namespace MWMechanics osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); - if (!currentAction) - return; - storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack); - - // 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; - } if (storage.mReadyToAttack) { @@ -267,6 +309,106 @@ namespace MWMechanics } } + void MWMechanics::AiCombat::updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) + { + static const float LOS_UPDATE_DURATION = 0.5f; + static const float BLIND_RUN_DURATION = 1.0f; + + if (storage.mFleeUpdateLOSTimer <= 0.f) + { + storage.mFleeLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target); + storage.mFleeUpdateLOSTimer = LOS_UPDATE_DURATION; + } + else + storage.mFleeUpdateLOSTimer -= duration; + + AiCombatStorage::FleeState& state = storage.mFleeState; + switch (state) + { + case AiCombatStorage::FleeState_None: + return; + + case AiCombatStorage::FleeState_Idle: + { + float triggerDist = getMaxAttackDistance(target); + + if (storage.mFleeLOS && + (triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist)) + { + const ESM::Pathgrid* pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*storage.mCell->getCell()); + + bool runFallback = true; + + if (pathgrid && !actor.getClass().isPureWaterCreature(actor)) + { + ESM::Pathgrid::PointList points; + CoordinateConverter coords(storage.mCell->getCell()); + + osg::Vec3f localPos = actor.getRefData().getPosition().asVec3(); + coords.toLocal(localPos); + + int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos); + for (int i = 0; i < static_cast(pathgrid->mPoints.size()); i++) + { + if (i != closestPointIndex && storage.mCell->isPointConnected(closestPointIndex, i)) + { + points.push_back(pathgrid->mPoints[static_cast(i)]); + } + } + + if (!points.empty()) + { + ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size())]; + coords.toWorld(dest); + + state = AiCombatStorage::FleeState_RunToDestination; + storage.mFleeDest = ESM::Pathgrid::Point(dest.mX, dest.mY, dest.mZ); + + runFallback = false; + } + } + + if (runFallback) + { + state = AiCombatStorage::FleeState_RunBlindly; + storage.mFleeBlindRunTimer = 0.0f; + } + } + } + break; + + case AiCombatStorage::FleeState_RunBlindly: + { + // timer to prevent twitchy movement that can be observed in vanilla MW + if (storage.mFleeBlindRunTimer < BLIND_RUN_DURATION) + { + storage.mFleeBlindRunTimer += duration; + + storage.mMovement.mRotation[2] = osg::PI + getZAngleToDir(target.getRefData().getPosition().asVec3()-actor.getRefData().getPosition().asVec3()); + storage.mMovement.mPosition[1] = 1; + updateActorsMovement(actor, duration, storage); + } + else + state = AiCombatStorage::FleeState_Idle; + } + break; + + case AiCombatStorage::FleeState_RunToDestination: + { + static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fFleeDistance")->getFloat(); + + float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); + if ((dist > fFleeDistance && !storage.mFleeLOS) + || pathTo(actor, storage.mFleeDest, duration)) + { + state = AiCombatStorage::FleeState_Idle; + } + } + break; + }; + } + void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage) { // apply combat movement @@ -446,6 +588,29 @@ namespace MWMechanics mReadyToAttack = false; mAttack = false; } + + void AiCombatStorage::startFleeing() + { + stopFleeing(); + mFleeState = FleeState_Idle; + } + + void AiCombatStorage::stopFleeing() + { + mMovement.mPosition[0] = 0; + mMovement.mPosition[1] = 0; + mMovement.mPosition[2] = 0; + mFleeState = FleeState_None; + mFleeDest = ESM::Pathgrid::Point(0, 0, 0); + mFleeLOS = false; + mFleeUpdateLOSTimer = 0.0f; + mFleeUpdateLOSTimer = 0.0f; + } + + bool AiCombatStorage::isFleeing() + { + return mFleeState != FleeState_None; + } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 4be2ac9da..3f2bde776 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -61,6 +61,8 @@ namespace MWMechanics void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); + void updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); + /// Transfer desired movement (from AiCombatStorage) to Actor void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 094df1db3..437aae277 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -5,14 +5,17 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" +#include "../mwworld/cellstore.hpp" #include "npcstats.hpp" #include "spellcasting.hpp" +#include "combat.hpp" namespace { @@ -517,6 +520,7 @@ namespace MWMechanics Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); float bestActionRating = 0.f; + float antiFleeRating = 0.f; // Default to hand-to-hand combat boost::shared_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) @@ -536,6 +540,7 @@ namespace MWMechanics { bestActionRating = rating; bestAction.reset(new ActionPotion(*it)); + antiFleeRating = std::numeric_limits::max(); } } @@ -546,6 +551,7 @@ namespace MWMechanics { bestActionRating = rating; bestAction.reset(new ActionEnchantedItem(it)); + antiFleeRating = std::numeric_limits::max(); } } @@ -593,6 +599,7 @@ namespace MWMechanics bestActionRating = rating; bestAction.reset(new ActionWeapon(*it, ammo)); + antiFleeRating = vanillaRateWeaponAndAmmo(*it, ammo, actor, enemy); } } } @@ -606,13 +613,308 @@ namespace MWMechanics { bestActionRating = rating; bestAction.reset(new ActionSpell(spell->mId)); + antiFleeRating = vanillaRateSpell(spell, actor, enemy); } } + if (makeFleeDecision(actor, enemy, antiFleeRating)) + bestAction.reset(new ActionFlee()); + if (bestAction.get()) bestAction->prepare(actor); return bestAction; } + + float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool minusZDist) + { + osg::Vec3f actor1Pos = actor1.getRefData().getPosition().asVec3(); + osg::Vec3f actor2Pos = actor2.getRefData().getPosition().asVec3(); + + float dist = (actor1Pos - actor2Pos).length(); + + if (minusZDist) + dist -= std::abs(actor1Pos.z() - actor2Pos.z()); + + return (dist + - MWBase::Environment::get().getWorld()->getHalfExtents(actor1).y() + - MWBase::Environment::get().getWorld()->getHalfExtents(actor2).y()); + } + + float getMaxAttackDistance(const MWWorld::Ptr& actor) + { + const CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + + std::string selectedSpellId = stats.getSpells().getSelectedSpell(); + MWWorld::Ptr selectedEnchItem; + + MWWorld::Ptr activeWeapon, activeAmmo; + if (actor.getClass().hasInventoryStore(actor)) + { + MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); + + MWWorld::ContainerStoreIterator item = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (item != invStore.end() && item.getType() == MWWorld::ContainerStore::Type_Weapon) + activeWeapon = *item; + + item = invStore.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (item != invStore.end() && item.getType() == MWWorld::ContainerStore::Type_Weapon) + activeAmmo = *item; + + if (invStore.getSelectedEnchantItem() != invStore.end()) + selectedEnchItem = *invStore.getSelectedEnchantItem(); + } + + float dist = 1.0f; + if (activeWeapon.isEmpty() && !selectedSpellId.empty() && !selectedEnchItem.isEmpty()) + { + static const float fHandToHandReach = gmst.find("fHandToHandReach")->getFloat(); + dist = fHandToHandReach; + } + else if (stats.getDrawState() == MWMechanics::DrawState_Spell) + { + dist = 1.0f; + if (!selectedSpellId.empty()) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(selectedSpellId); + for (std::vector::const_iterator effectIt = + spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + { + if (effectIt->mArea == ESM::RT_Target) + { + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + dist = effect->mData.mSpeed; + break; + } + } + } + else if (!selectedEnchItem.isEmpty()) + { + std::string enchId = selectedEnchItem.getClass().getEnchantment(selectedEnchItem); + if (!enchId.empty()) + { + const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().find(enchId); + for (std::vector::const_iterator effectIt = + ench->mEffects.mList.begin(); effectIt != ench->mEffects.mList.end(); ++effectIt) + { + if (effectIt->mArea == ESM::RT_Target) + { + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + dist = effect->mData.mSpeed; + break; + } + } + } + } + + static const float fTargetSpellMaxSpeed = gmst.find("fTargetSpellMaxSpeed")->getFloat(); + dist *= std::max(1000.0f, fTargetSpellMaxSpeed); + } + else if (!activeWeapon.isEmpty()) + { + const ESM::Weapon* esmWeap = activeWeapon.get()->mBase; + if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow) + { + static const float fTargetSpellMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); + dist = fTargetSpellMaxSpeed; + if (!activeAmmo.isEmpty()) + { + const ESM::Weapon* esmAmmo = activeAmmo.get()->mBase; + dist *= esmAmmo->mData.mSpeed; + } + } + else if (esmWeap->mData.mReach > 1) + { + dist = esmWeap->mData.mReach; + } + } + + dist = (dist > 0.f) ? dist : 1.0f; + + static const float fCombatDistance = gmst.find("fCombatDistance")->getFloat(); + static const float fCombatDistanceWerewolfMod = gmst.find("fCombatDistanceWerewolfMod")->getFloat(); + + float combatDistance = fCombatDistance; + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + combatDistance *= (fCombatDistanceWerewolfMod + 1.0f); + + if (dist < combatDistance) + dist *= combatDistance; + + return dist; + } + + bool canFight(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) + { + ESM::Position actorPos = actor.getRefData().getPosition(); + ESM::Position enemyPos = enemy.getRefData().getPosition(); + + const CreatureStats& enemyStats = enemy.getClass().getCreatureStats(enemy); + if (enemyStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 + || enemyStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 0) + { + if (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(enemy, actor)) + return false; + } + + if (actor.getClass().isPureWaterCreature(actor)) + { + if (!MWBase::Environment::get().getWorld()->isWading(enemy)) + return false; + } + + float atDist = getMaxAttackDistance(actor); + if (atDist > getDistanceMinusHalfExtents(actor, enemy) + && atDist > std::abs(actorPos.pos[2] - enemyPos.pos[2])) + { + if (MWBase::Environment::get().getWorld()->getLOS(actor, enemy)) + return true; + } + + if (actor.getClass().isPureFlyingCreature(actor) || actor.getClass().isPureLandCreature(actor)) + { + if (MWBase::Environment::get().getWorld()->isSwimming(enemy)) + return false; + } + + if (actor.getClass().isBipedal(actor) || !actor.getClass().canFly(actor)) + { + if (enemy.getClass().getCreatureStats(enemy).getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0) + { + float attackDistance = getMaxAttackDistance(actor); + if ((attackDistance + actorPos.pos[2]) < enemyPos.pos[2]) + { + if (enemy.getCell()->isExterior()) + { + if (attackDistance < (enemyPos.pos[2] - MWBase::Environment::get().getWorld()->getTerrainHeightAt(enemyPos.asVec3()))) + return false; + } + } + } + } + + if (!actor.getClass().canWalk(actor) && !actor.getClass().isBipedal(actor)) + return true; + + if (actor.getClass().getCreatureStats(actor).getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0) + return true; + + if (MWBase::Environment::get().getWorld()->isSwimming(actor)) + return true; + + if (getDistanceMinusHalfExtents(actor, enemy, true) <= 0.0f) + return false; + + return true; + } + + float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) + { + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + + static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->getFloat(); + static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->getFloat(); + + float mult = fAIMagicSpellMult; + + for (std::vector::const_iterator effectIt = + spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + { + if (effectIt->mArea == ESM::RT_Target) + { + if (!MWBase::Environment::get().getWorld()->isSwimming(enemy)) + mult = fAIRangeMagicSpellMult; + else + mult = 0.0f; + break; + } + } + + return MWMechanics::getSpellSuccessChance(spell, actor) * mult; + } + + float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) + { + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + + static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->getFloat(); + static const float fAIMeleeArmorMult = gmst.find("fAIMeleeArmorMult")->getFloat(); + static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->getFloat(); + + if (weapon.isEmpty()) + return 0.f; + + float skillMult = actor.getClass().getSkill(actor, weapon.getClass().getEquipmentSkill(weapon)) * 0.01f; + float chopMult = fAIMeleeWeaponMult; + float bonusDamage = 0.f; + + const ESM::Weapon* esmWeap = weapon.get()->mBase; + + if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow) + { + if (!ammo.isEmpty() && !MWBase::Environment::get().getWorld()->isSwimming(enemy)) + { + bonusDamage = ammo.get()->mBase->mData.mChop[1]; + chopMult = fAIRangeMeleeWeaponMult; + } + else + chopMult = 0.f; + } + + float chopRating = (esmWeap->mData.mChop[1] + bonusDamage) * skillMult * chopMult; + float slashRating = esmWeap->mData.mSlash[1] * skillMult * fAIMeleeWeaponMult; + float thrustRating = esmWeap->mData.mThrust[1] * skillMult * fAIMeleeWeaponMult; + + return actor.getClass().getArmorRating(actor) * fAIMeleeArmorMult + + std::max(std::max(chopRating, slashRating), thrustRating); + } + + float vanillaRateFlee(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) + { + const CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + + int flee = stats.getAiSetting(CreatureStats::AI_Flee).getModified(); + if (flee >= 100) + return flee; + + static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->getFloat(); + static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->getFloat(); + + float healthPercentage = (stats.getHealth().getModified() == 0.0f) + ? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified(); + float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult; + + static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->getInt(); + + if (enemy.getClass().isNpc() && enemy.getClass().getNpcStats(enemy).isWerewolf() && stats.getLevel() < iWereWolfLevelToAttack) + { + static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->getInt(); + rating = iWereWolfFleeMod; + } + + if (rating != 0.0f) + rating += getFightDistanceBias(actor, enemy); + + return rating; + } + + bool makeFleeDecision(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, float antiFleeRating) + { + float fleeRating = vanillaRateFlee(actor, enemy); + if (fleeRating < 100.0f) + fleeRating = 0.0f; + + if (fleeRating > antiFleeRating) + return true; + + // Run away after summoning a creature if we have nothing to use but fists. + if (antiFleeRating == 0.0f && !actor.getClass().getCreatureStats(actor).getSummonedCreatureMap().empty()) + return true; + + return false; + } + } diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index b36413587..0f1f7dd5b 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -20,6 +20,18 @@ namespace MWMechanics virtual float getActionCooldown() { return 0.f; } virtual const ESM::Weapon* getWeapon() const { return NULL; }; virtual bool isAttackingOrSpell() const { return true; } + virtual bool isFleeing() const { return false; } + }; + + class ActionFlee : public Action + { + public: + ActionFlee() {} + virtual void prepare(const MWWorld::Ptr& actor) {} + virtual float getCombatRange (bool& isRanged) const { return 0.0f; } + virtual float getActionCooldown() { return 3.0f; } + virtual bool isAttackingOrSpell() const { return false; } + virtual bool isFleeing() const { return true; } }; class ActionSpell : public Action @@ -89,6 +101,15 @@ namespace MWMechanics float rateEffects (const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); boost::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + + float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool minusZDist=false); + float getMaxAttackDistance(const MWWorld::Ptr& actor); + bool canFight(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + + float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + float vanillaRateFlee(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + bool makeFleeDecision(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, float antiFleeRating); } #endif diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index bcfd1bd2f..543221aaf 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -452,4 +452,19 @@ namespace MWMechanics return true; } + + float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2) + { + osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); + osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3()); + + float d = (pos1 - pos2).length(); + + static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( + "iFightDistanceBase")->getInt(); + static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( + "fFightDistanceMultiplier")->getFloat(); + + return (iFightDistanceBase - fFightDistanceMultiplier * d); + } } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 7d0b3b78f..3f733763a 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -42,6 +42,7 @@ void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, /// e.g. If attacker is a fish, is victim in water? Or, if attacker can't swim, is victim on land? bool isEnvironmentCompatible(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); +float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2); } #endif diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 49b18d1ff..815b45158 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -25,25 +25,11 @@ #include "autocalcspell.hpp" #include "npcstats.hpp" #include "actorutil.hpp" +#include "combat.hpp" namespace { - float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2) - { - osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); - osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3()); - - float d = (pos1 - pos2).length(); - - static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( - "iFightDistanceBase")->getInt(); - static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( - "fFightDistanceMultiplier")->getFloat(); - - return (iFightDistanceBase - fFightDistanceMultiplier * d); - } - float getFightDispositionBias(float disposition) { static const float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get().find( diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index b12f4510b..fba5f17b2 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1329,7 +1329,7 @@ void SkyManager::createRain() mRainParticleSystem = new osgParticle::ParticleSystem; mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); - mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,-1)); + mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1)); osg::ref_ptr stateset (mRainParticleSystem->getOrCreateStateSet()); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index d04252a2c..96a56fe39 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -393,7 +393,26 @@ namespace MWWorld bool Class::isPureWaterCreature(const MWWorld::Ptr& ptr) const { - return canSwim(ptr) && !canWalk(ptr); + return canSwim(ptr) + && !isBipedal(ptr) + && !canFly(ptr) + && !canWalk(ptr); + } + + bool Class::isPureFlyingCreature(const Ptr& ptr) const + { + return canFly(ptr) + && !isBipedal(ptr) + && !canSwim(ptr) + && !canWalk(ptr); + } + + bool Class::isPureLandCreature(const Ptr& ptr) const + { + return canWalk(ptr) + && !isBipedal(ptr) + && !canFly(ptr) + && !canSwim(ptr); } bool Class::isMobile(const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 8d3f9b891..06deedc53 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -312,6 +312,8 @@ namespace MWWorld virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; virtual bool canWalk(const MWWorld::ConstPtr& ptr) const; bool isPureWaterCreature(const MWWorld::Ptr& ptr) const; + bool isPureFlyingCreature(const MWWorld::Ptr& ptr) const; + bool isPureLandCreature(const MWWorld::Ptr& ptr) const; bool isMobile(const MWWorld::Ptr& ptr) const; virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4ab8323ea..5d1945ed1 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -434,11 +434,16 @@ namespace MWWorld gmst["sBribeFail"] = ESM::Variant("Bribe Fail"); gmst["fNPCHealthBarTime"] = ESM::Variant(5.f); gmst["fNPCHealthBarFade"] = ESM::Variant(1.f); + gmst["fFleeDistance"] = ESM::Variant(3000.f); // Werewolf (BM) gmst["fWereWolfRunMult"] = ESM::Variant(1.f); gmst["fWereWolfSilverWeaponDamageMult"] = ESM::Variant(1.f); gmst["iWerewolfFightMod"] = ESM::Variant(1); + gmst["iWereWolfFleeMod"] = ESM::Variant(100); + gmst["iWereWolfLevelToAttack"] = ESM::Variant(20); + gmst["iWereWolfBounty"] = ESM::Variant(10000); + gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f); std::map globals; // vanilla Morrowind does not define dayspassed. @@ -1319,7 +1324,7 @@ namespace MWWorld float terrainHeight = -std::numeric_limits::max(); if (ptr.getCell()->isExterior()) - terrainHeight = mRendering->getTerrainHeightAt(pos.asVec3()); + terrainHeight = getTerrainHeightAt(pos.asVec3()); if (pos.pos[2] < terrainHeight) pos.pos[2] = terrainHeight; @@ -3188,6 +3193,19 @@ namespace MWWorld return MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail); } + float World::getTerrainHeightAt(const osg::Vec3f& worldPos) const + { + return mRendering->getTerrainHeightAt(worldPos); + } + + osg::Vec3f World::getHalfExtents(const ConstPtr& actor, bool rendering) const + { + if (rendering) + return mPhysics->getRenderingHalfExtents(actor); + else + return mPhysics->getHalfExtents(actor); + } + void World::spawnRandomCreature(const std::string &creatureList) { const ESM::CreatureLevList* list = getStore().get().find(creatureList); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 2ffca0d9b..32434e773 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -664,6 +664,12 @@ namespace MWWorld virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target); virtual bool isPlayerInJail() const; + + /// Return terrain height at \a worldPos position. + virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const; + + /// Return physical or rendering half extents of the given actor. + virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering=false) const; }; } diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index dd2e1a748..d2b9ab0f6 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -140,3 +140,6 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux") target_link_libraries(openmw-wizard dl Xt) endif() +if (WIN32) + INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION ".") +endif(WIN32) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b67b5de03..43bd68341 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -235,7 +235,7 @@ target_link_libraries(components ${OSGFX_LIBRARIES} ${OSGANIMATION_LIBRARIES} ${Bullet_LIBRARIES} - ${SDL2_LIBRARY} + ${SDL2_LIBRARIES} # For MyGUI platform ${GL_LIB} ${MyGUI_LIBRARIES}