1
0
Fork 0
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:
Alexei Kotov 2026-01-01 18:43:16 +03:00
commit a207b45101
3 changed files with 554 additions and 420 deletions

View file

@ -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:

View file

@ -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

View file

@ -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