diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea831feee4..95b2a517a3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -625,7 +625,7 @@ macOS15_Xcode16_arm64: - ./aws/install - popd - aws --version - - unzip -d sym_store *sym_store.zip + - unzip -n -d sym_store '*sym_store.zip' - shopt -s globstar - | for file in sym_store/**/*.exe; do @@ -650,6 +650,60 @@ macOS15_Xcode16_arm64: aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude '*' --include '*.ex_' --include '*.dl_' --include '*.pd_' sym_store s3://openmw-sym fi +.Merge_Artifacts_Base: + extends: .Ubuntu_Image + stage: build + variables: + GIT_STRATEGY: none + script: + - apt-get update + - apt-get install -y curl dos2unix unzip zip + - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.22.35.zip" -o awscli-exe-linux-x86_64.zip + - unzip -d awscli-exe-linux-x86_64 awscli-exe-linux-x86_64.zip + - pushd awscli-exe-linux-x86_64 + - ./aws/install + - popd + - aws --version + - mkdir -p incoming_artifacts + - mv *.zip incoming_artifacts/ + - declare -A destinations + - | + for merge_list in *_to-be-merged.txt; do + while IFS=: read -r partial destination; do + destinations["$destination"]="" + echo "Unzipping '$partial' to '$destination'" + unzip -n -d "$destination" "incoming_artifacts/$partial" + done < <(dos2unix < $merge_list) + done + - | + for destination in ${!destinations[@]}; do + pushd "$destination" + for ci_id in CI-ID_*.txt; do + cat "$ci_id" >> CI-ID.txt + rm "$ci_id" + done + echo "Creating $destination.zip" + zip -r "../$destination.zip" . + popd + done + - | + if [[ -v AWS_ACCESS_KEY_ID ]]; then + artifactDirectory="$CI_PROJECT_NAMESPACE/$CI_COMMIT_REF_NAME/$CI_COMMIT_SHORT_SHA-$CI_JOB_ID/" + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp *.zip s3://openmw-artifacts/$artifactDirectory + fi + artifacts: + when: always + paths: + - "*.zip" + +.variables-for-split-jobs: &target-group-one + targets: "openmw openmw-tests" + group_name: "group-one" + +.variables-for-split-jobs: &target-group-two + targets: "bsatool components-tests esmtool niftest openmw-cs openmw-cs-tests openmw_detournavigator_navmeshtilescache_benchmark openmw_esm_refid_benchmark openmw_settings_access_benchmark openmw-bulletobjecttool openmw-essimporter openmw-iniimporter openmw-launcher openmw-navmeshtool openmw-wizard" + group_name: "group-two" + .Windows_Ninja_Base: tags: - saas-windows-medium-amd64 @@ -657,6 +711,7 @@ macOS15_Xcode16_arm64: - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: - Get-Volume + - Stop-Service docker - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 - choco source disable -n=chocolatey @@ -691,38 +746,40 @@ macOS15_Xcode16_arm64: - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - New-Item -Type File -Force -Path MSVC2022_64_Ninja\.cmake\api\v1\query\codemodel-v2 - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -N -b -t -C $multiview -E - - Get-Volume - cd MSVC2022_64_Ninja + - Get-Volume - .\ActivateMSVC.ps1 - - cmake --build . --config $config --target $targets + - cmake --build . --config $config --target ($targets.Split(' ')) - ccache --show-stats -v - cd $config - - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt + - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`r`nCI_JOB_ID ${CI_JOB_ID}`r`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" - Get-ChildItem -Recurse *.ilk | Remove-Item - aws --version - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt if(!$?) { Exit $LASTEXITCODE } if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} if(!$?) { Exit $LASTEXITCODE } } Push-Location .. ..\CI\Store-Symbols.ps1 -SkipCompress if(!$?) { Exit $LASTEXITCODE } - 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt if(!$?) { Exit $LASTEXITCODE } Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' + - Rename-Item CI-ID.txt CI-ID_${group_name}.txt + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - | if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} if(!$?) { Exit $LASTEXITCODE } } + - echo "$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}.zip")):$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}"))" | Out-File -Encoding UTF8 "..\..\${group_name}_to-be-merged.txt" - | if ($executables) { foreach ($exe in $executables.Split(',')) { @@ -746,6 +803,7 @@ macOS15_Xcode16_arm64: - "*.log" - MSVC2022_64_Ninja/*.log - MSVC2022_64_Ninja/**/*.log + - "*_to-be-merged.txt" variables: targets: all # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. @@ -800,11 +858,34 @@ macOS15_Xcode16_arm64: # Gitlab can't successfully execute following binaries due to unknown reason # executables: "components-tests.exe,openmw-tests.exe,openmw-cs-tests.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" +.Windows_Ninja_RelWithDebInfo_GroupOne: + extends: + - .Windows_Ninja_RelWithDebInfo + variables: + <<: *target-group-one + +.Windows_Ninja_RelWithDebInfo_GroupTwo: + extends: + - .Windows_Ninja_RelWithDebInfo + variables: + <<: *target-group-two + .Windows_Compress_And_Upload_Symbols_Ninja_RelWithDebInfo: extends: - .Compress_And_Upload_Symbols_Base needs: - - job: "Windows_Ninja_RelWithDebInfo" + - job: "Windows_Ninja_RelWithDebInfo_GroupOne" + artifacts: true + - job: "Windows_Ninja_RelWithDebInfo_GroupTwo" + artifacts: true + +.Windows_Merge_Artifacts_Ninja_RelWithDebInfo: + extends: + - .Merge_Artifacts_Base + needs: + - job: "Windows_Ninja_RelWithDebInfo_GroupOne" + artifacts: true + - job: "Windows_Ninja_RelWithDebInfo_GroupTwo" artifacts: true .Windows_Ninja_CacheInit: @@ -823,6 +904,7 @@ macOS15_Xcode16_arm64: - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: - Get-Volume + - Stop-Service docker - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 - choco source disable -n=chocolatey @@ -854,34 +936,36 @@ macOS15_Xcode16_arm64: - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -b -t -C $multiview -E - cd MSVC2022_64 - Get-Volume - - cmake --build . --config $config --target $targets + - cmake --build . --config $config --target ($targets.Split(' ')) - cd $config - - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt + - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`r`nCI_JOB_ID ${CI_JOB_ID}`r`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" - Get-ChildItem -Recurse *.ilk | Remove-Item - aws --version - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt if(!$?) { Exit $LASTEXITCODE } if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} if(!$?) { Exit $LASTEXITCODE } } Push-Location .. ..\CI\Store-Symbols.ps1 -SkipCompress if(!$?) { Exit $LASTEXITCODE } - 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt if(!$?) { Exit $LASTEXITCODE } Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' + - Rename-Item CI-ID.txt CI-ID_${group_name}.txt + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - | if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} if(!$?) { Exit $LASTEXITCODE } } + - echo "$(Make-SafeFileName("OpenMW_MSVC2022_64_${group_name}_${config}_${CI_COMMIT_REF_NAME}.zip")):$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}"))" | Out-File -Encoding UTF8 "..\..\${group_name}_to-be-merged.txt" - | if ($executables) { foreach ($exe in $executables.Split(',')) { @@ -904,6 +988,7 @@ macOS15_Xcode16_arm64: - "*.log" - MSVC2022_64/*.log - MSVC2022_64/**/*.log + - "*_to-be-merged.txt" variables: targets: ALL_BUILD # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. @@ -936,7 +1021,7 @@ macOS15_Xcode16_arm64: - job: "Windows_MSBuild_Debug" artifacts: true -Windows_MSBuild_RelWithDebInfo: +.Windows_MSBuild_RelWithDebInfo: extends: - .Windows_MSBuild_Base variables: @@ -948,11 +1033,38 @@ Windows_MSBuild_RelWithDebInfo: # run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "schedule" +Windows_MSBuild_RelWithDebInfo_GroupOne: + extends: + - .Windows_MSBuild_RelWithDebInfo + variables: + <<: *target-group-one + +Windows_MSBuild_RelWithDebInfo_GroupTwo: + extends: + - .Windows_MSBuild_RelWithDebInfo + variables: + <<: *target-group-two + Windows_Compress_And_Upload_Symbols_MSBuild_RelWithDebInfo: extends: - .Compress_And_Upload_Symbols_Base needs: - - job: "Windows_MSBuild_RelWithDebInfo" + - job: "Windows_MSBuild_RelWithDebInfo_GroupOne" + artifacts: true + - job: "Windows_MSBuild_RelWithDebInfo_GroupTwo" + artifacts: true + # temporarily enabled while we're linking the above on the downloads page + rules: + # run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it + - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "schedule" + +Windows_Merge_Artifacts_MSBuild_RelWithDebInfo: + extends: + - .Merge_Artifacts_Base + needs: + - job: "Windows_MSBuild_RelWithDebInfo_GroupOne" + artifacts: true + - job: "Windows_MSBuild_RelWithDebInfo_GroupTwo" artifacts: true # temporarily enabled while we're linking the above on the downloads page rules: diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 8570d9df60..22956bf00f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -123,6 +123,19 @@ add_library(openmw-lib STATIC ${OPENMW_SOURCES} ) +if (MSVC) + # these TUs take a lot of memory for codegen + # in CI, they often go over the 8GB memory limit if another cl.exe is running at the same time + # this option (make sure you're not looking at the docs for the identically-named linker option) stops four codegen threads being spun off + # it hurts build time ~33% to enable this unnecessarily, though, so target it to the hungriest boys + set_source_files_properties( + options.cpp + mwlua/stats.cpp + PROPERTIES + COMPILE_OPTIONS /cgthreads1 + ) +endif() + if(BUILD_OPENMW) if (ANDROID) add_library(openmw SHARED diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index e4ee31d411..93cd8c294e 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -62,428 +62,437 @@ namespace namespace MWLua { - static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj) + namespace { - if (!obj.mStatsCache.empty()) - return; // was already added before - manager->addAction( - [obj = Object(obj)] { - LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); - if (scripts) - scripts->applyStatsCache(); - }, - "StatUpdateAction"); - } - - static void setCreatureValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) - { - auto& stats = ptr.getClass().getCreatureStats(ptr); - if (prop == "current") - stats.setLevel(LuaUtil::cast(value)); - } - - static void setNpcValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) - { - auto& stats = ptr.getClass().getNpcStats(ptr); - if (prop == "progress") - stats.setLevelProgress(LuaUtil::cast(value)); - else if (prop == "skillIncreasesForAttribute") - stats.setSkillIncreasesForAttribute( - *std::get(index).getIf(), LuaUtil::cast(value)); - else if (prop == "skillIncreasesForSpecialization") - stats.setSkillIncreasesForSpecialization( - static_cast(std::get(index)), LuaUtil::cast(value)); - } - - class SkillIncreasesForAttributeStats - { - ObjectVariant mObject; - - public: - SkillIncreasesForAttributeStats(ObjectVariant object) - : mObject(std::move(object)) + static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj) { + if (!obj.mStatsCache.empty()) + return; // was already added before + manager->addAction( + [obj = Object(obj)] { + LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + if (scripts) + scripts->applyStatsCache(); + }, + "StatUpdateAction"); } - sol::object get(const Context& context, ESM::StringRefId attributeId) const + static void setCreatureValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { - if (!mObject.ptr().getClass().isNpc()) - return sol::nil; - - return getValue(context, mObject, &setNpcValue, attributeId, "skillIncreasesForAttribute", - [attributeId](const MWWorld::Ptr& ptr) { - return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForAttribute(attributeId); - }); - } - - void set(const Context& context, ESM::StringRefId attributeId, const sol::object& value) const - { - const auto& ptr = mObject.ptr(); - if (!ptr.getClass().isNpc()) - return; - - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] - = sol::main_object(value); - } - }; - - class SkillIncreasesForSpecializationStats - { - ObjectVariant mObject; - - public: - SkillIncreasesForSpecializationStats(ObjectVariant object) - : mObject(std::move(object)) - { - } - - sol::object get(const Context& context, int specialization) const - { - if (!mObject.ptr().getClass().isNpc()) - return sol::nil; - - return getValue(context, mObject, &setNpcValue, specialization, "skillIncreasesForSpecialization", - [specialization](const MWWorld::Ptr& ptr) { - return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization( - static_cast(specialization)); - }); - } - - void set(const Context& context, int specialization, const sol::object& value) const - { - const auto& ptr = mObject.ptr(); - if (!ptr.getClass().isNpc()) - return; - - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }] - = sol::main_object(value); - } - }; - - class LevelStat - { - ObjectVariant mObject; - - LevelStat(ObjectVariant object) - : mObject(std::move(object)) - { - } - - public: - sol::object getCurrent(const Context& context) const - { - return getValue(context, mObject, &setCreatureValue, std::monostate{}, "current", - [](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); }); - } - - void setCurrent(const Context& context, const sol::object& value) const - { - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] - = sol::main_object(value); - } - - sol::object getProgress(const Context& context) const - { - if (!mObject.ptr().getClass().isNpc()) - return sol::nil; - - return getValue(context, mObject, &setNpcValue, std::monostate{}, "progress", - [](const MWWorld::Ptr& ptr) { return ptr.getClass().getNpcStats(ptr).getLevelProgress(); }); - } - - void setProgress(const Context& context, const sol::object& value) const - { - const auto& ptr = mObject.ptr(); - if (!ptr.getClass().isNpc()) - return; - - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] - = sol::main_object(value); - } - - SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const - { - return SkillIncreasesForAttributeStats{ mObject }; - } - - SkillIncreasesForSpecializationStats getSkillIncreasesForSpecializationStats() const - { - return SkillIncreasesForSpecializationStats{ mObject }; - } - - static std::optional create(ObjectVariant object, Index) - { - if (!object.ptr().getClass().isActor()) - return {}; - return LevelStat{ std::move(object) }; - } - }; - - class DynamicStat - { - ObjectVariant mObject; - int mIndex; - - DynamicStat(ObjectVariant object, int index) - : mObject(std::move(object)) - , mIndex(index) - { - } - - public: - template - sol::object get(const Context& context, std::string_view prop, G getter) const - { - return getValue( - context, mObject, &DynamicStat::setValue, mIndex, prop, [this, getter](const MWWorld::Ptr& ptr) { - return (ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).*getter)(); - }); - } - - static std::optional create(ObjectVariant object, Index i) - { - if (!object.ptr().getClass().isActor()) - return {}; - int index = std::get(i); - return DynamicStat{ std::move(object), index }; - } - - void cache(const Context& context, std::string_view prop, const sol::object& value) const - { - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = sol::main_object(value); - } - - static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) - { - int index = std::get(i); auto& stats = ptr.getClass().getCreatureStats(ptr); - auto stat = stats.getDynamic(index); - float floatValue = LuaUtil::cast(value); - if (prop == "base") - stat.setBase(floatValue); - else if (prop == "current") - stat.setCurrent(floatValue, true, true); - else if (prop == "modifier") - stat.setModifier(floatValue); - stats.setDynamic(index, stat); - } - }; - - class AttributeStat - { - ObjectVariant mObject; - ESM::RefId mId; - - AttributeStat(ObjectVariant object, ESM::RefId id) - : mObject(std::move(object)) - , mId(id) - { + if (prop == "current") + stats.setLevel(LuaUtil::cast(value)); } - public: - template - sol::object get(const Context& context, std::string_view prop, G getter) const + static void setNpcValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) { - return getValue( - context, mObject, &AttributeStat::setValue, mId, prop, [this, getter](const MWWorld::Ptr& ptr) { - return (ptr.getClass().getCreatureStats(ptr).getAttribute(mId).*getter)(); - }); - } - - float getModified(const Context& context) const - { - auto base = LuaUtil::cast(get(context, "base", &MWMechanics::AttributeValue::getBase)); - auto damage = LuaUtil::cast(get(context, "damage", &MWMechanics::AttributeValue::getDamage)); - auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::AttributeValue::getModifier)); - return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified - } - - static std::optional create(ObjectVariant object, Index i) - { - if (!object.ptr().getClass().isActor()) - return {}; - ESM::RefId id = std::get(i); - return AttributeStat{ std::move(object), id }; - } - - void cache(const Context& context, std::string_view prop, const sol::object& value) const - { - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mId, prop }] = sol::main_object(value); - } - - static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) - { - ESM::RefId id = std::get(i); - auto& stats = ptr.getClass().getCreatureStats(ptr); - auto stat = stats.getAttribute(id); - float floatValue = LuaUtil::cast(value); - if (prop == "base") - stat.setBase(floatValue); - else if (prop == "damage") - { - stat.restore(stat.getDamage()); - stat.damage(floatValue); - } - else if (prop == "modifier") - stat.setModifier(floatValue); - stats.setAttribute(id, stat); - } - }; - - class SkillStat - { - ObjectVariant mObject; - ESM::RefId mId; - - SkillStat(ObjectVariant object, ESM::RefId id) - : mObject(std::move(object)) - , mId(id) - { - } - - static float getProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat) - { - float progress = stat.getProgress(); - if (progress != 0.f) - progress /= getMaxProgress(ptr, id, stat); - return progress; - } - - static float getMaxProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat) - { - const auto& store = *MWBase::Environment::get().getESMStore(); - const auto cl = store.get().find(ptr.get()->mBase->mClass); - return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(id, *cl); - } - - public: - template - sol::object get(const Context& context, std::string_view prop, G getter) const - { - return getValue(context, mObject, &SkillStat::setValue, mId, prop, [this, getter](const MWWorld::Ptr& ptr) { - return (ptr.getClass().getNpcStats(ptr).getSkill(mId).*getter)(); - }); - } - - float getModified(const Context& context) const - { - auto base = LuaUtil::cast(get(context, "base", &MWMechanics::SkillValue::getBase)); - auto damage = LuaUtil::cast(get(context, "damage", &MWMechanics::SkillValue::getDamage)); - auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::SkillValue::getModifier)); - return std::max(0.f, base - damage + modifier); // Should match SkillValue::getModified - } - - sol::object getProgress(const Context& context) const - { - return getValue(context, mObject, &SkillStat::setValue, mId, "progress", [this](const MWWorld::Ptr& ptr) { - return getProgress(ptr, mId, ptr.getClass().getNpcStats(ptr).getSkill(mId)); - }); - } - - static std::optional create(ObjectVariant object, Index index) - { - if (!object.ptr().getClass().isNpc()) - return {}; - ESM::RefId id = std::get(index); - return SkillStat{ std::move(object), id }; - } - - void cache(const Context& context, std::string_view prop, const sol::object& value) const - { - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = sol::main_object(value); - } - - static void setValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) - { - ESM::RefId id = std::get(index); auto& stats = ptr.getClass().getNpcStats(ptr); - auto stat = stats.getSkill(id); - float floatValue = LuaUtil::cast(value); - if (prop == "base") - stat.setBase(floatValue); - else if (prop == "damage") + if (prop == "progress") + stats.setLevelProgress(LuaUtil::cast(value)); + else if (prop == "skillIncreasesForAttribute") + stats.setSkillIncreasesForAttribute( + *std::get(index).getIf(), LuaUtil::cast(value)); + else if (prop == "skillIncreasesForSpecialization") + stats.setSkillIncreasesForSpecialization( + static_cast(std::get(index)), LuaUtil::cast(value)); + } + + class SkillIncreasesForAttributeStats + { + ObjectVariant mObject; + + public: + SkillIncreasesForAttributeStats(ObjectVariant object) + : mObject(std::move(object)) { - stat.restore(stat.getDamage()); - stat.damage(floatValue); } - else if (prop == "modifier") - stat.setModifier(floatValue); - else if (prop == "progress") - stat.setProgress(floatValue * getMaxProgress(ptr, id, stat)); - stats.setSkill(id, stat); - } - }; - class AIStat - { - ObjectVariant mObject; - MWMechanics::AiSetting mIndex; + sol::object get(const Context& context, ESM::StringRefId attributeId) const + { + if (!mObject.ptr().getClass().isNpc()) + return sol::nil; - AIStat(ObjectVariant object, MWMechanics::AiSetting index) - : mObject(std::move(object)) - , mIndex(index) + return getValue(context, mObject, &setNpcValue, attributeId, "skillIncreasesForAttribute", + [attributeId](const MWWorld::Ptr& ptr) { + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForAttribute(attributeId); + }); + } + + void set(const Context& context, ESM::StringRefId attributeId, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] + = sol::main_object(value); + } + }; + + class SkillIncreasesForSpecializationStats { - } + ObjectVariant mObject; - public: - template - sol::object get(const Context& context, std::string_view prop, G getter) const - { - return getValue(context, mObject, &AIStat::setValue, static_cast(mIndex), prop, - [this, getter](const MWWorld::Ptr& ptr) { - return (ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).*getter)(); - }); - } + public: + SkillIncreasesForSpecializationStats(ObjectVariant object) + : mObject(std::move(object)) + { + } - int getModified(const Context& context) const - { - auto base = LuaUtil::cast(get(context, "base", &MWMechanics::Stat::getBase)); - auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::Stat::getModifier)); - return std::max(0, base + modifier); - } + sol::object get(const Context& context, int specialization) const + { + if (!mObject.ptr().getClass().isNpc()) + return sol::nil; - static std::optional create(ObjectVariant object, MWMechanics::AiSetting index) - { - if (!object.ptr().getClass().isActor()) - return {}; - return AIStat{ std::move(object), index }; - } + return getValue(context, mObject, &setNpcValue, specialization, "skillIncreasesForSpecialization", + [specialization](const MWWorld::Ptr& ptr) { + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization( + static_cast(specialization)); + }); + } - void cache(const Context& context, std::string_view prop, const sol::object& value) const - { - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast(mIndex), prop }] - = sol::main_object(value); - } + void set(const Context& context, int specialization, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; - static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ + &setNpcValue, specialization, "skillIncreasesForSpecialization" }] + = sol::main_object(value); + } + }; + + class LevelStat { - auto index = static_cast(std::get(i)); - auto& stats = ptr.getClass().getCreatureStats(ptr); - auto stat = stats.getAiSetting(index); - int intValue = LuaUtil::cast(value); - if (prop == "base") - stat.setBase(intValue); - else if (prop == "modifier") - stat.setModifier(intValue); - stats.setAiSetting(index, stat); - } - }; + ObjectVariant mObject; + + LevelStat(ObjectVariant object) + : mObject(std::move(object)) + { + } + + public: + sol::object getCurrent(const Context& context) const + { + return getValue(context, mObject, &setCreatureValue, std::monostate{}, "current", + [](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); }); + } + + void setCurrent(const Context& context, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] + = sol::main_object(value); + } + + sol::object getProgress(const Context& context) const + { + if (!mObject.ptr().getClass().isNpc()) + return sol::nil; + + return getValue(context, mObject, &setNpcValue, std::monostate{}, "progress", + [](const MWWorld::Ptr& ptr) { return ptr.getClass().getNpcStats(ptr).getLevelProgress(); }); + } + + void setProgress(const Context& context, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] + = sol::main_object(value); + } + + SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const + { + return SkillIncreasesForAttributeStats{ mObject }; + } + + SkillIncreasesForSpecializationStats getSkillIncreasesForSpecializationStats() const + { + return SkillIncreasesForSpecializationStats{ mObject }; + } + + static std::optional create(ObjectVariant object, Index) + { + if (!object.ptr().getClass().isActor()) + return {}; + return LevelStat{ std::move(object) }; + } + }; + + class DynamicStat + { + ObjectVariant mObject; + int mIndex; + + DynamicStat(ObjectVariant object, int index) + : mObject(std::move(object)) + , mIndex(index) + { + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue( + context, mObject, &DynamicStat::setValue, mIndex, prop, [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).*getter)(); + }); + } + + static std::optional create(ObjectVariant object, Index i) + { + if (!object.ptr().getClass().isActor()) + return {}; + int index = std::get(i); + return DynamicStat{ std::move(object), index }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] + = sol::main_object(value); + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + int index = std::get(i); + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getDynamic(index); + float floatValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(floatValue); + else if (prop == "current") + stat.setCurrent(floatValue, true, true); + else if (prop == "modifier") + stat.setModifier(floatValue); + stats.setDynamic(index, stat); + } + }; + + class AttributeStat + { + ObjectVariant mObject; + ESM::RefId mId; + + AttributeStat(ObjectVariant object, ESM::RefId id) + : mObject(std::move(object)) + , mId(id) + { + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue( + context, mObject, &AttributeStat::setValue, mId, prop, [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getCreatureStats(ptr).getAttribute(mId).*getter)(); + }); + } + + float getModified(const Context& context) const + { + auto base = LuaUtil::cast(get(context, "base", &MWMechanics::AttributeValue::getBase)); + auto damage = LuaUtil::cast(get(context, "damage", &MWMechanics::AttributeValue::getDamage)); + auto modifier + = LuaUtil::cast(get(context, "modifier", &MWMechanics::AttributeValue::getModifier)); + return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified + } + + static std::optional create(ObjectVariant object, Index i) + { + if (!object.ptr().getClass().isActor()) + return {}; + ESM::RefId id = std::get(i); + return AttributeStat{ std::move(object), id }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mId, prop }] + = sol::main_object(value); + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + ESM::RefId id = std::get(i); + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getAttribute(id); + float floatValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(floatValue); + else if (prop == "damage") + { + stat.restore(stat.getDamage()); + stat.damage(floatValue); + } + else if (prop == "modifier") + stat.setModifier(floatValue); + stats.setAttribute(id, stat); + } + }; + + class SkillStat + { + ObjectVariant mObject; + ESM::RefId mId; + + SkillStat(ObjectVariant object, ESM::RefId id) + : mObject(std::move(object)) + , mId(id) + { + } + + static float getProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat) + { + float progress = stat.getProgress(); + if (progress != 0.f) + progress /= getMaxProgress(ptr, id, stat); + return progress; + } + + static float getMaxProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat) + { + const auto& store = *MWBase::Environment::get().getESMStore(); + const auto cl = store.get().find(ptr.get()->mBase->mClass); + return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(id, *cl); + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue( + context, mObject, &SkillStat::setValue, mId, prop, [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getNpcStats(ptr).getSkill(mId).*getter)(); + }); + } + + float getModified(const Context& context) const + { + auto base = LuaUtil::cast(get(context, "base", &MWMechanics::SkillValue::getBase)); + auto damage = LuaUtil::cast(get(context, "damage", &MWMechanics::SkillValue::getDamage)); + auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::SkillValue::getModifier)); + return std::max(0.f, base - damage + modifier); // Should match SkillValue::getModified + } + + sol::object getProgress(const Context& context) const + { + return getValue( + context, mObject, &SkillStat::setValue, mId, "progress", [this](const MWWorld::Ptr& ptr) { + return getProgress(ptr, mId, ptr.getClass().getNpcStats(ptr).getSkill(mId)); + }); + } + + static std::optional create(ObjectVariant object, Index index) + { + if (!object.ptr().getClass().isNpc()) + return {}; + ESM::RefId id = std::get(index); + return SkillStat{ std::move(object), id }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = sol::main_object(value); + } + + static void setValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + ESM::RefId id = std::get(index); + auto& stats = ptr.getClass().getNpcStats(ptr); + auto stat = stats.getSkill(id); + float floatValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(floatValue); + else if (prop == "damage") + { + stat.restore(stat.getDamage()); + stat.damage(floatValue); + } + else if (prop == "modifier") + stat.setModifier(floatValue); + else if (prop == "progress") + stat.setProgress(floatValue * getMaxProgress(ptr, id, stat)); + stats.setSkill(id, stat); + } + }; + + class AIStat + { + ObjectVariant mObject; + MWMechanics::AiSetting mIndex; + + AIStat(ObjectVariant object, MWMechanics::AiSetting index) + : mObject(std::move(object)) + , mIndex(index) + { + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue(context, mObject, &AIStat::setValue, static_cast(mIndex), prop, + [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).*getter)(); + }); + } + + int getModified(const Context& context) const + { + auto base = LuaUtil::cast(get(context, "base", &MWMechanics::Stat::getBase)); + auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::Stat::getModifier)); + return std::max(0, base + modifier); + } + + static std::optional create(ObjectVariant object, MWMechanics::AiSetting index) + { + if (!object.ptr().getClass().isActor()) + return {}; + return AIStat{ std::move(object), index }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast(mIndex), prop }] + = sol::main_object(value); + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto index = static_cast(std::get(i)); + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getAiSetting(index); + int intValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(intValue); + else if (prop == "modifier") + stat.setModifier(intValue); + stats.setAiSetting(index, stat); + } + }; + } } namespace sol