mirror of
https://github.com/OpenMW/openmw.git
synced 2026-01-05 01:43:11 +00:00
Merge branch 'master' of https://gitlab.com/OpenMW/openmw
This commit is contained in:
commit
eba063ac23
24 changed files with 200 additions and 89 deletions
|
|
@ -414,7 +414,7 @@ Ubuntu_Clang_Tidy_openmw:
|
|||
needs:
|
||||
- Ubuntu_Clang_Tidy_components
|
||||
variables:
|
||||
BUILD_TARGETS: openmw
|
||||
BUILD_TARGETS: openmw openmw-tests
|
||||
timeout: 3h
|
||||
|
||||
Ubuntu_Clang_Tidy_openmw-cs:
|
||||
|
|
@ -430,7 +430,7 @@ Ubuntu_Clang_Tidy_other:
|
|||
needs:
|
||||
- Ubuntu_Clang_Tidy_components
|
||||
variables:
|
||||
BUILD_TARGETS: bsatool esmtool openmw-launcher openmw-iniimporter openmw-essimporter openmw-wizard niftest components-tests openmw-tests openmw-cs-tests openmw-navmeshtool openmw-bulletobjecttool
|
||||
BUILD_TARGETS: components-tests bsatool esmtool openmw-launcher openmw-iniimporter openmw-essimporter openmw-wizard niftest openmw-navmeshtool openmw-bulletobjecttool
|
||||
timeout: 3h
|
||||
|
||||
.Ubuntu_Clang_tests:
|
||||
|
|
@ -546,6 +546,9 @@ macOS14_Xcode15_amd64:
|
|||
CCACHE_SIZE: 3G
|
||||
DMG_IDENTIFIER: amd64
|
||||
MACOS_AMD64: true
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
HOMEBREW_NO_EMOJI: true
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: true
|
||||
|
||||
macOS14_Xcode15_arm64:
|
||||
extends: .MacOS
|
||||
|
|
@ -557,6 +560,9 @@ macOS14_Xcode15_arm64:
|
|||
variables:
|
||||
DMG_IDENTIFIER: arm64
|
||||
CCACHE_SIZE: 3G
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
HOMEBREW_NO_EMOJI: true
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: true
|
||||
|
||||
.Compress_And_Upload_Symbols_Base:
|
||||
extends: .Ubuntu_Image
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ If you feel your name is missing from this list, please add it to `AUTHORS.md`.
|
|||
Programmers
|
||||
-----------
|
||||
|
||||
Bret Curtis (psi29a) - Project leader 2019-present
|
||||
Alexey Dobrokhotov (Capo) - Project leader 2025-present
|
||||
Bret Curtis (psi29a) - Project leader 2019-2025
|
||||
Marc Zinnschlag (Zini) - Project leader 2010-2018
|
||||
Nicolay Korslund - Project leader 2008-2010
|
||||
scrawl - Top contributor
|
||||
|
|
@ -49,7 +50,6 @@ Programmers
|
|||
Berulacks
|
||||
Bo Svensson
|
||||
Britt Mathis (galdor557)
|
||||
Capostrophic
|
||||
Carl Maxwell
|
||||
cc9cii
|
||||
Cédric Mocquillon
|
||||
|
|
|
|||
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -6,16 +6,21 @@
|
|||
Bug #6039: Next Spell keybind fails while selected enchanted item has multiple copies
|
||||
Bug #6573: Editor: Selection behaves incorrectly on high-DPI displays
|
||||
Bug #6792: Birth sign info box has no line breaks
|
||||
Bug #7371: Equipping item from inventory does not play a Down sound when equipping fails
|
||||
Bug #7622: Player's marksman weapons don't work on close actors underwater
|
||||
Bug #7649: The sound and vfx of resisted enchanted items' magic still play
|
||||
Bug #7740: Magic items in the HUD aren't composited correctly
|
||||
Bug #7799: Picking up ingredients while object paging active grid is on may cause a hiccup
|
||||
Bug #7871: Kwama Queen doesn't start combat with player
|
||||
Bug #8245: The console command ShowVars does not list global mwscripts
|
||||
Bug #8265: Topics are linked incorrectly
|
||||
Bug #8303: On target spells cast by non-actors should fire underwater
|
||||
Bug #8318: Missing global variables are not handled gracefully in dialogue conditions
|
||||
Bug #8333: Quest status subrecords should not actually cause parsing to skip remaining data
|
||||
Bug #8340: Multi-effect enchantments are too expensive
|
||||
Bug #8341: Repeat shader visitor passes discard parallax
|
||||
Bug #8349: Travel to non-existent cell causes persistent black screen
|
||||
Bug #8359: Some quick keys menu related issues
|
||||
Bug #8371: Silence affects powers
|
||||
Bug #8375: Moon phase cycle doesn't match Morrowind
|
||||
Bug #8383: Casting bound helm or boots on beast races doesn't cleanup properly
|
||||
|
|
@ -38,12 +43,19 @@
|
|||
Bug #8593: Render targets do not generate mipmaps
|
||||
Bug #8598: Post processing shaders don't interact with the vfs correctly
|
||||
Bug #8599: Non-ASCII paths in BSA files don't work
|
||||
Bug #8609: The crosshair is too large
|
||||
Bug #8610: Terrain normal maps using NormalGL format instead of NormalDX
|
||||
Bug #8612: Using aiactivate on an ingredient when graphical herbalism is enabled triggers non-stop pickup sounds
|
||||
Bug #8615: Rest/wait time progress speed is different from vanilla
|
||||
Feature #2522: Support quick item transfer
|
||||
Feature #3769: Allow GetSpellEffects on enchantments
|
||||
Feature #8112: Expose landscape record data to Lua
|
||||
Feature #8113: Support extended selection in autodetected subdirectory dialog
|
||||
Feature #8139: Editor: Redesign the selection markers
|
||||
Feature #8285: Expose list of active shaders in postprocessing API
|
||||
Feature #8313: Show the character name in the savegame details
|
||||
Feature #8320: Add access mwscript source text to lua api
|
||||
Feature #8334: Lua: AddTopic equivalent
|
||||
Feature #8355: Lua: Window visibility checking in interfaces.UI
|
||||
Feature #8580: Sort characters in the save loading menu
|
||||
Feature #8597: Lua: Add more built-in event handlers
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
#!/bin/sh -ex
|
||||
|
||||
export HOMEBREW_NO_EMOJI=1
|
||||
export HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
export HOMEBREW_AUTOREMOVE=1
|
||||
|
||||
if [[ "${MACOS_AMD64}" ]]; then
|
||||
./CI/macos/before_install.amd64.sh
|
||||
else
|
||||
|
|
|
|||
|
|
@ -3,14 +3,7 @@
|
|||
brew tap --repair
|
||||
brew update --quiet
|
||||
|
||||
brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd
|
||||
|
||||
command -v ccache >/dev/null 2>&1 || brew install ccache
|
||||
command -v cmake >/dev/null 2>&1 || brew install cmake
|
||||
command -v qmake >/dev/null 2>&1 || brew install qt@6
|
||||
|
||||
# Install deps
|
||||
brew install openal-soft icu4c yaml-cpp sqlite
|
||||
brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd ccache cmake qt@6 openal-soft icu4c yaml-cpp sqlite
|
||||
|
||||
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240818-arm64.tar.xz -o ~/openmw-deps.tar.xz
|
||||
tar xf ~/openmw-deps.tar.xz -C /tmp > /dev/null
|
||||
|
|
|
|||
|
|
@ -466,7 +466,7 @@ find_package(Boost 1.70.0 CONFIG REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONA
|
|||
if(OPENMW_USE_SYSTEM_MYGUI)
|
||||
find_package(MyGUI 3.4.3 REQUIRED)
|
||||
endif()
|
||||
find_package(SDL2 2.0.10 REQUIRED)
|
||||
find_package(SDL2 2.0.20 REQUIRED)
|
||||
find_package(OpenAL REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
|
|
|
|||
|
|
@ -433,6 +433,8 @@ OMW::Engine::~Engine()
|
|||
}
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
Log(Debug::Info) << "Quitting peacefully.";
|
||||
}
|
||||
|
||||
// Set data dir
|
||||
|
|
@ -1069,8 +1071,6 @@ void OMW::Engine::go()
|
|||
Settings::Manager::saveUser(mCfgMgr.getUserConfigPath() / "settings.cfg");
|
||||
Settings::ShaderManager::get().save();
|
||||
mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath());
|
||||
|
||||
Log(Debug::Info) << "Quitting peacefully.";
|
||||
}
|
||||
|
||||
void OMW::Engine::setCompileAll(bool all)
|
||||
|
|
|
|||
|
|
@ -192,12 +192,13 @@ namespace MWClass
|
|||
{
|
||||
if (!isTrapped)
|
||||
{
|
||||
if (canBeHarvested(ptr))
|
||||
{
|
||||
return std::make_unique<MWWorld::ActionHarvest>(ptr);
|
||||
}
|
||||
if (!canBeHarvested(ptr))
|
||||
return std::make_unique<MWWorld::ActionOpen>(ptr);
|
||||
|
||||
return std::make_unique<MWWorld::ActionOpen>(ptr);
|
||||
if (hasToolTip(ptr))
|
||||
return std::make_unique<MWWorld::ActionHarvest>(ptr);
|
||||
|
||||
return std::make_unique<MWWorld::FailedAction>(std::string_view{}, ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ namespace MWGui
|
|||
mAssignDialog->setVisible(false);
|
||||
}
|
||||
|
||||
void QuickKeysMenu::onAssignItem(MWWorld::Ptr item)
|
||||
void QuickKeysMenu::assignItem(MWWorld::Ptr item)
|
||||
{
|
||||
assert(mSelected);
|
||||
|
||||
|
|
@ -258,7 +258,11 @@ namespace MWGui
|
|||
|
||||
if (mItemSelectionDialog)
|
||||
mItemSelectionDialog->setVisible(false);
|
||||
}
|
||||
|
||||
void QuickKeysMenu::onAssignItem(MWWorld::Ptr item)
|
||||
{
|
||||
assignItem(item);
|
||||
MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item));
|
||||
}
|
||||
|
||||
|
|
@ -649,7 +653,7 @@ namespace MWGui
|
|||
else
|
||||
{
|
||||
if (quickKey.mType == ESM::QuickKeys::Type::Item)
|
||||
onAssignItem(item);
|
||||
assignItem(item);
|
||||
else // if (quickKey.mType == ESM::QuickKeys::Type::MagicItem)
|
||||
onAssignMagicItem(item);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ namespace MWGui
|
|||
// Check if quick key is still valid
|
||||
inline void validate(int index);
|
||||
void unassign(keyData* key);
|
||||
void assignItem(MWWorld::Ptr item);
|
||||
|
||||
bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
|
||||
size_t mControllerFocus;
|
||||
|
|
|
|||
|
|
@ -430,7 +430,6 @@ namespace MWInput
|
|||
void ControllerManager::enableGyroSensor()
|
||||
{
|
||||
mGyroAvailable = false;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 14)
|
||||
SDL_GameController* cntrl = mBindingsManager->getControllerOrNull();
|
||||
if (!cntrl)
|
||||
return;
|
||||
|
|
@ -442,7 +441,6 @@ namespace MWInput
|
|||
return;
|
||||
}
|
||||
mGyroAvailable = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ControllerManager::isGyroAvailable() const
|
||||
|
|
@ -453,7 +451,6 @@ namespace MWInput
|
|||
std::array<float, 3> ControllerManager::getGyroValues() const
|
||||
{
|
||||
float gyro[3] = { 0.f };
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 14)
|
||||
SDL_GameController* cntrl = mBindingsManager->getControllerOrNull();
|
||||
if (cntrl && mGyroAvailable)
|
||||
{
|
||||
|
|
@ -461,7 +458,6 @@ namespace MWInput
|
|||
if (result < 0)
|
||||
Log(Debug::Error) << "Failed to get game controller sensor data: " << SDL_GetError();
|
||||
}
|
||||
#endif
|
||||
return std::array<float, 3>({ gyro[0], gyro[1], gyro[2] });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -235,13 +235,16 @@ namespace MWLua
|
|||
&mOnSkillLevelUp });
|
||||
}
|
||||
|
||||
void LocalScripts::setActive(bool active)
|
||||
void LocalScripts::setActive(bool active, bool callHandlers)
|
||||
{
|
||||
mData.mIsActive = active;
|
||||
if (active)
|
||||
callEngineHandlers(mOnActiveHandlers);
|
||||
else
|
||||
callEngineHandlers(mOnInactiveHandlers);
|
||||
if (callHandlers)
|
||||
{
|
||||
if (active)
|
||||
callEngineHandlers(mOnActiveHandlers);
|
||||
else
|
||||
callEngineHandlers(mOnInactiveHandlers);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalScripts::applyStatsCache()
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ namespace MWLua
|
|||
MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; }
|
||||
const MWWorld::Ptr& getPtrOrEmpty() const { return mData.ptrOrEmpty(); }
|
||||
|
||||
void setActive(bool active);
|
||||
void setActive(bool active, bool callHandlers = true);
|
||||
bool isActive() const override { return mData.mIsActive; }
|
||||
void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); }
|
||||
void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); }
|
||||
|
|
|
|||
|
|
@ -540,7 +540,10 @@ namespace MWLua
|
|||
localScripts = createLocalScripts(ptr);
|
||||
localScripts->addAutoStartedScripts();
|
||||
if (ptr.isInCell() && MWBase::Environment::get().getWorldScene()->isCellActive(*ptr.getCell()))
|
||||
{
|
||||
localScripts->setActive(true, false);
|
||||
mActiveLocalScripts.insert(localScripts);
|
||||
}
|
||||
}
|
||||
localScripts->addCustomScript(scriptId, initData);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1062,9 +1062,12 @@ namespace MWMechanics
|
|||
{
|
||||
if (heldIter != inventoryStore.end() && heldIter->getType() == ESM::Light::sRecordId)
|
||||
{
|
||||
// At day, unequip lights and auto equip shields or other suitable items
|
||||
// (Note: autoEquip will ignore lights)
|
||||
inventoryStore.autoEquip();
|
||||
// At day, unequip lights and auto equip shields
|
||||
auto shield = inventoryStore.getPreferredShield();
|
||||
if (shield != inventoryStore.end())
|
||||
inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield);
|
||||
else
|
||||
inventoryStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,39 @@ namespace MWScript
|
|||
}
|
||||
};
|
||||
|
||||
class OpFillJournal : public Interpreter::Opcode0
|
||||
{
|
||||
public:
|
||||
void execute(Interpreter::Runtime& runtime) override
|
||||
{
|
||||
const MWWorld::Store<ESM::Dialogue>& dialogues
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Dialogue>();
|
||||
MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
MWBase::Journal* journal = MWBase::Environment::get().getJournal();
|
||||
MWBase::DialogueManager* dialogueManager = MWBase::Environment::get().getDialogueManager();
|
||||
|
||||
for (const auto& dialogue : dialogues)
|
||||
{
|
||||
if (dialogue.mType == ESM::Dialogue::Type::Journal)
|
||||
{
|
||||
for (const auto& journalInfo : dialogue.mInfoOrder.getOrderedInfo())
|
||||
{
|
||||
if (journalInfo.mQuestStatus != ESM::DialInfo::QS_Name)
|
||||
journal->addEntry(dialogue.mId, journalInfo.mData.mJournalIndex, playerPtr);
|
||||
}
|
||||
}
|
||||
else if (dialogue.mType == ESM::Dialogue::Type::Topic)
|
||||
{
|
||||
for (const auto& topicInfo : dialogue.mInfoOrder.getOrderedInfo())
|
||||
{
|
||||
journal->addTopic(dialogue.mId, topicInfo.mId, playerPtr);
|
||||
}
|
||||
dialogueManager->addTopic(dialogue.mId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class OpAddTopic : public Interpreter::Opcode0
|
||||
{
|
||||
public:
|
||||
|
|
@ -288,6 +321,7 @@ namespace MWScript
|
|||
interpreter.installSegment5<OpJournal<ExplicitRef>>(Compiler::Dialogue::opcodeJournalExplicit);
|
||||
interpreter.installSegment5<OpSetJournalIndex>(Compiler::Dialogue::opcodeSetJournalIndex);
|
||||
interpreter.installSegment5<OpGetJournalIndex>(Compiler::Dialogue::opcodeGetJournalIndex);
|
||||
interpreter.installSegment5<OpFillJournal>(Compiler::Dialogue::opcodeFillJournal);
|
||||
interpreter.installSegment5<OpAddTopic>(Compiler::Dialogue::opcodeAddTopic);
|
||||
interpreter.installSegment3<OpChoice>(Compiler::Dialogue::opcodeChoice);
|
||||
interpreter.installSegment5<OpForceGreeting<ImplicitRef>>(Compiler::Dialogue::opcodeForceGreeting);
|
||||
|
|
|
|||
|
|
@ -485,5 +485,6 @@ op 0x2000322: GetPCVisionBonus
|
|||
op 0x2000323: SetPCVisionBonus
|
||||
op 0x2000324: ModPCVisionBonus
|
||||
op 0x2000325: TestModels, T3D
|
||||
op 0x2000326: FillJournal
|
||||
|
||||
opcodes 0x2000326-0x3ffffff unused
|
||||
opcodes 0x2000327-0x3ffffff unused
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <sstream>
|
||||
|
||||
#include <components/compiler/locals.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm/records.hpp>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
|
@ -300,26 +301,43 @@ namespace MWScript
|
|||
|
||||
std::string_view InterpreterContext::getNPCFaction() const
|
||||
{
|
||||
const ESM::NPC* npc = getReferenceImp().get<ESM::NPC>()->mBase;
|
||||
const ESM::Faction* faction = MWBase::Environment::get().getESMStore()->get<ESM::Faction>().find(npc->mFaction);
|
||||
const MWWorld::Ptr& ptr = getReferenceImp();
|
||||
const ESM::RefId& factionId = ptr.getClass().getPrimaryFaction(ptr);
|
||||
if (factionId.empty())
|
||||
{
|
||||
Log(Debug::Warning) << "getNPCFaction(): NPC " << ptr.getCellRef().getRefId() << " has no primary faction";
|
||||
return "%";
|
||||
}
|
||||
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::ESMStore& store = world->getStore();
|
||||
const ESM::Faction* faction = store.get<ESM::Faction>().find(factionId);
|
||||
return faction->mName;
|
||||
}
|
||||
|
||||
std::string_view InterpreterContext::getNPCRank() const
|
||||
{
|
||||
const MWWorld::Ptr& ptr = getReferenceImp();
|
||||
const ESM::RefId& 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");
|
||||
const MWWorld::Class& ptrClass = ptr.getClass();
|
||||
const ESM::RefId& factionId = ptrClass.getPrimaryFaction(ptr);
|
||||
if (factionId.empty())
|
||||
{
|
||||
Log(Debug::Warning) << "getNPCRank(): NPC " << ptr.getCellRef().getRefId() << " has no primary faction";
|
||||
return "%";
|
||||
}
|
||||
|
||||
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];
|
||||
const ESM::Faction* faction = store.get<ESM::Faction>().find(factionId);
|
||||
|
||||
int rank = ptrClass.getPrimaryFactionRank(ptr);
|
||||
if (rank < 0 || rank > 9)
|
||||
{
|
||||
Log(Debug::Warning) << "getNPCRank(): NPC " << ptr.getCellRef().getRefId() << " has invalid rank " << rank
|
||||
<< " in faction " << factionId;
|
||||
return "%";
|
||||
}
|
||||
return faction->mRanks[rank];
|
||||
}
|
||||
|
||||
std::string_view InterpreterContext::getPCName() const
|
||||
|
|
@ -344,13 +362,16 @@ namespace MWScript
|
|||
|
||||
std::string_view InterpreterContext::getPCRank() const
|
||||
{
|
||||
const MWWorld::Ptr& ptr = getReferenceImp();
|
||||
const ESM::RefId& factionId = ptr.getClass().getPrimaryFaction(ptr);
|
||||
if (factionId.empty())
|
||||
{
|
||||
Log(Debug::Warning) << "getPCRank(): NPC " << ptr.getCellRef().getRefId() << " has no primary faction";
|
||||
return "%";
|
||||
}
|
||||
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
MWWorld::Ptr player = world->getPlayerPtr();
|
||||
|
||||
const ESM::RefId& factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp());
|
||||
if (factionId.empty())
|
||||
throw std::runtime_error("getPCRank(): NPC is not in a faction");
|
||||
|
||||
const auto& ranks = player.getClass().getNpcStats(player).getFactionRanks();
|
||||
auto it = ranks.find(factionId);
|
||||
int rank = -1;
|
||||
|
|
@ -373,13 +394,16 @@ namespace MWScript
|
|||
|
||||
std::string_view InterpreterContext::getPCNextRank() const
|
||||
{
|
||||
const MWWorld::Ptr& ptr = getReferenceImp();
|
||||
const ESM::RefId& factionId = ptr.getClass().getPrimaryFaction(ptr);
|
||||
if (factionId.empty())
|
||||
{
|
||||
Log(Debug::Warning) << "getPCNextRank(): NPC " << ptr.getCellRef().getRefId() << " has no primary faction";
|
||||
return "%";
|
||||
}
|
||||
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
MWWorld::Ptr player = world->getPlayerPtr();
|
||||
|
||||
const ESM::RefId& factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp());
|
||||
if (factionId.empty())
|
||||
throw std::runtime_error("getPCNextRank(): NPC is not in a faction");
|
||||
|
||||
const auto& ranks = player.getClass().getNpcStats(player).getFactionRanks();
|
||||
auto it = ranks.find(factionId);
|
||||
int rank = -1;
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ namespace Compiler
|
|||
extensions.registerInstruction("journal", "cl", opcodeJournal, opcodeJournalExplicit);
|
||||
extensions.registerInstruction("setjournalindex", "cl", opcodeSetJournalIndex);
|
||||
extensions.registerFunction("getjournalindex", 'l', "c", opcodeGetJournalIndex);
|
||||
extensions.registerInstruction("filljournal", "", opcodeFillJournal);
|
||||
extensions.registerInstruction("addtopic", "S", opcodeAddTopic);
|
||||
extensions.registerInstruction(
|
||||
"choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice);
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ namespace Compiler
|
|||
const int opcodeJournalExplicit = 0x200030b;
|
||||
const int opcodeSetJournalIndex = 0x2000134;
|
||||
const int opcodeGetJournalIndex = 0x2000135;
|
||||
const int opcodeFillJournal = 0x2000326;
|
||||
const int opcodeAddTopic = 0x200013a;
|
||||
const int opcodeChoice = 0x2000a;
|
||||
const int opcodeForceGreeting = 0x200014f;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#define OPENMW_COMPONENTS_NIF_NIFKEY_HPP
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "exception.hpp"
|
||||
#include "niffile.hpp"
|
||||
|
|
@ -17,7 +18,7 @@ namespace Nif
|
|||
InterpolationType_Unknown = 0,
|
||||
InterpolationType_Linear = 1,
|
||||
InterpolationType_Quadratic = 2,
|
||||
InterpolationType_TBC = 3,
|
||||
InterpolationType_TCB = 3,
|
||||
InterpolationType_XYZ = 4,
|
||||
InterpolationType_Constant = 5
|
||||
};
|
||||
|
|
@ -28,18 +29,19 @@ namespace Nif
|
|||
T mValue;
|
||||
T mInTan; // Only for Quadratic interpolation, and never for QuaternionKeyList
|
||||
T mOutTan; // Only for Quadratic interpolation, and never for QuaternionKeyList
|
||||
|
||||
// FIXME: Implement TBC interpolation
|
||||
/*
|
||||
float mTension; // Only for TBC interpolation
|
||||
float mBias; // Only for TBC interpolation
|
||||
float mContinuity; // Only for TBC interpolation
|
||||
*/
|
||||
};
|
||||
using FloatKey = KeyT<float>;
|
||||
using Vector3Key = KeyT<osg::Vec3f>;
|
||||
using Vector4Key = KeyT<osg::Vec4f>;
|
||||
using QuaternionKey = KeyT<osg::Quat>;
|
||||
|
||||
template <typename T>
|
||||
struct TCBKey
|
||||
{
|
||||
float mTime;
|
||||
T mValue{};
|
||||
T mInTan{};
|
||||
T mOutTan{};
|
||||
float mTension;
|
||||
float mContinuity;
|
||||
float mBias;
|
||||
};
|
||||
|
||||
template <typename T, T (NIFStream::*getValue)()>
|
||||
struct KeyMapT
|
||||
|
|
@ -101,15 +103,20 @@ namespace Nif
|
|||
mKeys[time] = key;
|
||||
}
|
||||
}
|
||||
else if (mInterpolationType == InterpolationType_TBC)
|
||||
else if (mInterpolationType == InterpolationType_TCB)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
std::vector<TCBKey<T>> tcbKeys(count);
|
||||
for (TCBKey<T>& tcbKey : tcbKeys)
|
||||
{
|
||||
float time;
|
||||
nif->read(time);
|
||||
readTBC(*nif, key);
|
||||
mKeys[time] = key;
|
||||
nif->read(tcbKey.mTime);
|
||||
tcbKey.mValue = ((*nif).*getValue)();
|
||||
nif->read(tcbKey.mTension);
|
||||
nif->read(tcbKey.mContinuity);
|
||||
nif->read(tcbKey.mBias);
|
||||
}
|
||||
generateTCBTangents(tcbKeys);
|
||||
for (TCBKey<T>& key : tcbKeys)
|
||||
mKeys[key.mTime] = KeyType{ std::move(key.mValue), std::move(key.mInTan), std::move(key.mOutTan) };
|
||||
}
|
||||
else if (mInterpolationType == InterpolationType_XYZ)
|
||||
{
|
||||
|
|
@ -140,14 +147,43 @@ namespace Nif
|
|||
|
||||
static void readQuadratic(NIFStream& nif, KeyT<osg::Quat>& key) { readValue(nif, key); }
|
||||
|
||||
static void readTBC(NIFStream& nif, KeyT<T>& key)
|
||||
template <typename U>
|
||||
static void generateTCBTangents(std::vector<TCBKey<U>>& keys)
|
||||
{
|
||||
readValue(nif, key);
|
||||
/*key.mTension = */ nif.get<float>();
|
||||
/*key.mBias = */ nif.get<float>();
|
||||
/*key.mContinuity = */ nif.get<float>();
|
||||
if (keys.size() <= 1)
|
||||
return;
|
||||
|
||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
||||
{
|
||||
TCBKey<U>& curr = keys[i];
|
||||
const TCBKey<U>* prev = (i == 0) ? nullptr : &keys[i - 1];
|
||||
const TCBKey<U>* next = (i == keys.size() - 1) ? nullptr : &keys[i + 1];
|
||||
const float prevLen = prev != nullptr && next != nullptr ? curr.mTime - prev->mTime : 1.f;
|
||||
const float nextLen = prev != nullptr && next != nullptr ? next->mTime - curr.mTime : 1.f;
|
||||
if (prevLen + nextLen == 0.f)
|
||||
continue;
|
||||
const float x = (1.f - curr.mTension) * (1.f - curr.mContinuity) * (1.f + curr.mBias);
|
||||
const float y = (1.f - curr.mTension) * (1.f + curr.mContinuity) * (1.f - curr.mBias);
|
||||
const float z = (1.f - curr.mTension) * (1.f + curr.mContinuity) * (1.f + curr.mBias);
|
||||
const float w = (1.f - curr.mTension) * (1.f - curr.mContinuity) * (1.f - curr.mBias);
|
||||
const U prevDelta = prev != nullptr ? curr.mValue - prev->mValue : next->mValue - curr.mValue;
|
||||
const U nextDelta = next != nullptr ? next->mValue - curr.mValue : curr.mValue - prev->mValue;
|
||||
curr.mInTan = (prevDelta * x + nextDelta * y) * prevLen / (prevLen + nextLen);
|
||||
curr.mOutTan = (prevDelta * z + nextDelta * w) * nextLen / (prevLen + nextLen);
|
||||
}
|
||||
}
|
||||
|
||||
static void generateTCBTangents(std::vector<TCBKey<bool>>& keys)
|
||||
{
|
||||
// TODO: is this even legal?
|
||||
}
|
||||
|
||||
static void generateTCBTangents(std::vector<TCBKey<osg::Quat>>& keys)
|
||||
{
|
||||
// TODO: implement TCB interpolation for quaternions
|
||||
}
|
||||
};
|
||||
|
||||
using FloatKeyMap = KeyMapT<float, &NIFStream::get<float>>;
|
||||
using Vector3KeyMap = KeyMapT<osg::Vec3f, &NIFStream::get<osg::Vec3f>>;
|
||||
using Vector4KeyMap = KeyMapT<osg::Vec4f, &NIFStream::get<osg::Vec4f>>;
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ namespace NifOsg
|
|||
case Nif::InterpolationType_Constant:
|
||||
return fraction > 0.5f ? b.mValue : a.mValue;
|
||||
case Nif::InterpolationType_Quadratic:
|
||||
case Nif::InterpolationType_TCB:
|
||||
{
|
||||
// Using a cubic Hermite spline.
|
||||
// b1(t) = 2t^3 - 3t^2 + 1
|
||||
|
|
@ -147,7 +148,6 @@ namespace NifOsg
|
|||
const float b4 = t3 - t2;
|
||||
return a.mValue * b1 + b.mValue * b2 + a.mOutTan * b3 + b.mInTan * b4;
|
||||
}
|
||||
// TODO: Implement TBC interpolation
|
||||
default:
|
||||
return a.mValue + ((b.mValue - a.mValue) * fraction);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ namespace SDLUtil
|
|||
float mY;
|
||||
float mPressure;
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 14)
|
||||
explicit TouchEvent(const SDL_ControllerTouchpadEvent& arg)
|
||||
: mDevice(arg.touchpad)
|
||||
, mFinger(arg.finger)
|
||||
|
|
@ -37,7 +36,6 @@ namespace SDLUtil
|
|||
, mPressure(arg.pressure)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
///////////////
|
||||
|
|
|
|||
|
|
@ -173,7 +173,6 @@ namespace SDLUtil
|
|||
if (mConListener)
|
||||
mConListener->axisMoved(1, evt.caxis);
|
||||
break;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 14)
|
||||
case SDL_CONTROLLERSENSORUPDATE:
|
||||
// controller sensor data is received on demand
|
||||
break;
|
||||
|
|
@ -186,7 +185,6 @@ namespace SDLUtil
|
|||
case SDL_CONTROLLERTOUCHPADUP:
|
||||
mConListener->touchpadReleased(1, TouchEvent(evt.ctouchpad));
|
||||
break;
|
||||
#endif
|
||||
case SDL_WINDOWEVENT:
|
||||
handleWindowEvent(evt);
|
||||
break;
|
||||
|
|
|
|||
Loading…
Reference in a new issue