mirror of
https://github.com/OpenMW/openmw.git
synced 2026-01-06 00:13:16 +00:00
Merge branch 'splitting-headache' into 'master'
Split the Windows build into two, like it was when the time limit was an hour before mid 2022 See merge request OpenMW/openmw!5070
This commit is contained in:
commit
a207b45101
3 changed files with 554 additions and 420 deletions
150
.gitlab-ci.yml
150
.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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<int>(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<int>(value));
|
||||
else if (prop == "skillIncreasesForAttribute")
|
||||
stats.setSkillIncreasesForAttribute(
|
||||
*std::get<ESM::RefId>(index).getIf<ESM::StringRefId>(), LuaUtil::cast<int>(value));
|
||||
else if (prop == "skillIncreasesForSpecialization")
|
||||
stats.setSkillIncreasesForSpecialization(
|
||||
static_cast<ESM::Class::Specialization>(std::get<int>(index)), LuaUtil::cast<int>(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<ESM::Class::Specialization>(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<LevelStat> 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 <class G>
|
||||
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<DynamicStat> create(ObjectVariant object, Index i)
|
||||
{
|
||||
if (!object.ptr().getClass().isActor())
|
||||
return {};
|
||||
int index = std::get<int>(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<int>(i);
|
||||
auto& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
auto stat = stats.getDynamic(index);
|
||||
float floatValue = LuaUtil::cast<float>(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<int>(value));
|
||||
}
|
||||
|
||||
public:
|
||||
template <class G>
|
||||
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<float>(get(context, "base", &MWMechanics::AttributeValue::getBase));
|
||||
auto damage = LuaUtil::cast<float>(get(context, "damage", &MWMechanics::AttributeValue::getDamage));
|
||||
auto modifier = LuaUtil::cast<float>(get(context, "modifier", &MWMechanics::AttributeValue::getModifier));
|
||||
return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified
|
||||
}
|
||||
|
||||
static std::optional<AttributeStat> create(ObjectVariant object, Index i)
|
||||
{
|
||||
if (!object.ptr().getClass().isActor())
|
||||
return {};
|
||||
ESM::RefId id = std::get<ESM::RefId>(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<ESM::RefId>(i);
|
||||
auto& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
auto stat = stats.getAttribute(id);
|
||||
float floatValue = LuaUtil::cast<float>(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<ESM::Class>().find(ptr.get<ESM::NPC>()->mBase->mClass);
|
||||
return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(id, *cl);
|
||||
}
|
||||
|
||||
public:
|
||||
template <class G>
|
||||
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<float>(get(context, "base", &MWMechanics::SkillValue::getBase));
|
||||
auto damage = LuaUtil::cast<float>(get(context, "damage", &MWMechanics::SkillValue::getDamage));
|
||||
auto modifier = LuaUtil::cast<float>(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<SkillStat> create(ObjectVariant object, Index index)
|
||||
{
|
||||
if (!object.ptr().getClass().isNpc())
|
||||
return {};
|
||||
ESM::RefId id = std::get<ESM::RefId>(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<ESM::RefId>(index);
|
||||
auto& stats = ptr.getClass().getNpcStats(ptr);
|
||||
auto stat = stats.getSkill(id);
|
||||
float floatValue = LuaUtil::cast<float>(value);
|
||||
if (prop == "base")
|
||||
stat.setBase(floatValue);
|
||||
else if (prop == "damage")
|
||||
if (prop == "progress")
|
||||
stats.setLevelProgress(LuaUtil::cast<int>(value));
|
||||
else if (prop == "skillIncreasesForAttribute")
|
||||
stats.setSkillIncreasesForAttribute(
|
||||
*std::get<ESM::RefId>(index).getIf<ESM::StringRefId>(), LuaUtil::cast<int>(value));
|
||||
else if (prop == "skillIncreasesForSpecialization")
|
||||
stats.setSkillIncreasesForSpecialization(
|
||||
static_cast<ESM::Class::Specialization>(std::get<int>(index)), LuaUtil::cast<int>(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 <class G>
|
||||
sol::object get(const Context& context, std::string_view prop, G getter) const
|
||||
{
|
||||
return getValue(context, mObject, &AIStat::setValue, static_cast<int>(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<int>(get(context, "base", &MWMechanics::Stat<int>::getBase));
|
||||
auto modifier = LuaUtil::cast<int>(get(context, "modifier", &MWMechanics::Stat<int>::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<AIStat> 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<ESM::Class::Specialization>(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<int>(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<MWMechanics::AiSetting>(std::get<int>(i));
|
||||
auto& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
auto stat = stats.getAiSetting(index);
|
||||
int intValue = LuaUtil::cast<int>(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<LevelStat> 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 <class G>
|
||||
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<DynamicStat> create(ObjectVariant object, Index i)
|
||||
{
|
||||
if (!object.ptr().getClass().isActor())
|
||||
return {};
|
||||
int index = std::get<int>(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<int>(i);
|
||||
auto& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
auto stat = stats.getDynamic(index);
|
||||
float floatValue = LuaUtil::cast<float>(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 <class G>
|
||||
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<float>(get(context, "base", &MWMechanics::AttributeValue::getBase));
|
||||
auto damage = LuaUtil::cast<float>(get(context, "damage", &MWMechanics::AttributeValue::getDamage));
|
||||
auto modifier
|
||||
= LuaUtil::cast<float>(get(context, "modifier", &MWMechanics::AttributeValue::getModifier));
|
||||
return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified
|
||||
}
|
||||
|
||||
static std::optional<AttributeStat> create(ObjectVariant object, Index i)
|
||||
{
|
||||
if (!object.ptr().getClass().isActor())
|
||||
return {};
|
||||
ESM::RefId id = std::get<ESM::RefId>(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<ESM::RefId>(i);
|
||||
auto& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
auto stat = stats.getAttribute(id);
|
||||
float floatValue = LuaUtil::cast<float>(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<ESM::Class>().find(ptr.get<ESM::NPC>()->mBase->mClass);
|
||||
return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(id, *cl);
|
||||
}
|
||||
|
||||
public:
|
||||
template <class G>
|
||||
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<float>(get(context, "base", &MWMechanics::SkillValue::getBase));
|
||||
auto damage = LuaUtil::cast<float>(get(context, "damage", &MWMechanics::SkillValue::getDamage));
|
||||
auto modifier = LuaUtil::cast<float>(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<SkillStat> create(ObjectVariant object, Index index)
|
||||
{
|
||||
if (!object.ptr().getClass().isNpc())
|
||||
return {};
|
||||
ESM::RefId id = std::get<ESM::RefId>(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<ESM::RefId>(index);
|
||||
auto& stats = ptr.getClass().getNpcStats(ptr);
|
||||
auto stat = stats.getSkill(id);
|
||||
float floatValue = LuaUtil::cast<float>(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 <class G>
|
||||
sol::object get(const Context& context, std::string_view prop, G getter) const
|
||||
{
|
||||
return getValue(context, mObject, &AIStat::setValue, static_cast<int>(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<int>(get(context, "base", &MWMechanics::Stat<int>::getBase));
|
||||
auto modifier = LuaUtil::cast<int>(get(context, "modifier", &MWMechanics::Stat<int>::getModifier));
|
||||
return std::max(0, base + modifier);
|
||||
}
|
||||
|
||||
static std::optional<AIStat> 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<int>(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<MWMechanics::AiSetting>(std::get<int>(i));
|
||||
auto& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
auto stat = stats.getAiSetting(index);
|
||||
int intValue = LuaUtil::cast<int>(value);
|
||||
if (prop == "base")
|
||||
stat.setBase(intValue);
|
||||
else if (prop == "modifier")
|
||||
stat.setModifier(intValue);
|
||||
stats.setAiSetting(index, stat);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace sol
|
||||
|
|
|
|||
Loading…
Reference in a new issue