Compare commits

...

240 commits

Author SHA1 Message Date
Alexei Kotov
55107e0913 Merge branch 'idle_select_to_group_name' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Use string_view for sIdleSelectToGroupName

See merge request OpenMW/openmw!4646
2025-04-28 03:02:17 +03:00
Alexei Kotov
011dfb305c Merge branch 'navmeshtool_lib' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Do not build navmeshtool translation units twice

See merge request OpenMW/openmw!4647
2025-04-25 21:40:57 +03:00
elsid
fc4cc3255d
Do not build navmeshtool translation units twice 2025-04-24 23:03:07 +02:00
elsid
f487a6332b
Use string_view for sIdleSelectToGroupName 2025-04-24 23:01:30 +02:00
psi29a
80d6f020ed Merge branch 'apt_get_retry' into 'master'
Some checks failed
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Retry apt-get update and add-apt-repository

See merge request OpenMW/openmw!4639
2025-04-24 09:07:20 +00:00
psi29a
c7c95c5a85 Merge branch 'fix-bug-8462' into 'master'
Set SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH.

Closes #8225 and #8462

See merge request OpenMW/openmw!4641
2025-04-24 09:06:27 +00:00
Sam Kaufman
1948ab21f7 Set SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH.
Some checks failed
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
This fixes bugs #8225 and #8462.
2025-04-23 20:33:56 -07:00
Alexei Kotov
ca3a286cc4 Merge branch 'master' into 'master'
Some checks are pending
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Build and test / Ubuntu (push) Waiting to run
FIX: Clarify that `ignore` field of raycast options types are a list and not a single object

See merge request OpenMW/openmw!4613
2025-04-24 02:09:47 +03:00
Evil Eye
ab5070328d Merge branch 'powershell-multiline-exit' into 'master'
Detect failures in multiline PowerShell commands

See merge request OpenMW/openmw!4645
2025-04-23 18:54:47 +00:00
Evil Eye
23522ed314 Merge branch 'missing_enum' into 'master'
Add missing TargetPolygonNotFound enum value to lua bindings

See merge request OpenMW/openmw!4643
2025-04-23 18:54:46 +00:00
Evil Eye
b4d5013679 Merge branch 'lua_state' into 'master'
Use unique_ptr to handle lua state lifetime

See merge request OpenMW/openmw!4636
2025-04-23 18:54:40 +00:00
Dave Corley
5ef2cf23b3 CLEANUP: Loop param, also, is a bool
Some checks failed
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
2025-04-23 11:20:46 -07:00
elsid
8ee0c9e7be
Retry apt-get update and add-apt-repository
Some checks failed
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
2025-04-22 23:05:27 +02:00
AnyOldName3
19725473d7 Detect failures in multiline PowerShell commands
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
GitLab inserts a check for failure after each command in our `script`.
This is documented here https://docs.gitlab.com/runner/shells/#powershell

However, it doesn't detect failures if we run commands back to back.

This adds the checks GitLab would have added for us if we were able to make it do that.
2025-04-22 20:58:43 +01:00
psi29a
aed135a7c0 Merge branch 'bump-cache-key' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Increment cache keys missed in !4450

Closes #8463

See merge request OpenMW/openmw!4644
2025-04-22 07:05:45 +00:00
AnyOldName3
928bbed09b Increment cache keys missed in !4450
It changed the filenames for deps, so we've got two copies of the deps in the cache, and now we're running out of disk space.
2025-04-21 22:52:24 +01:00
elsid
626d7b2282
Add missing TargetPolygonNotFound enum value to lua bindings 2025-04-21 16:36:19 +02:00
Evil Eye
e20d52d23c Merge branch 'namespace_typo' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix typo in namespace name

See merge request OpenMW/openmw!4635
2025-04-21 08:41:27 +00:00
Evil Eye
05c624bc34 Merge branch 'rm_using_namespace_fallback' into 'master'
Remove redundant using namespace Fallback

See merge request OpenMW/openmw!4638
2025-04-21 08:39:48 +00:00
Evil Eye
5a42db3256 Merge branch 'static_assert' into 'master'
Use static_assert for compile time check

See merge request OpenMW/openmw!4637
2025-04-21 08:35:17 +00:00
elsid
6f89d38b78
Replace includes by forward declaration 2025-04-20 02:28:34 +02:00
elsid
042c4b2b9d
Use static_assert for compile time check 2025-04-18 14:38:23 +02:00
elsid
f80283422f
Use unique_ptr to handle lua state lifetime 2025-04-18 14:01:07 +02:00
elsid
972995d124
Fix typo in namespace name 2025-04-18 12:27:48 +02:00
elsid
0d5e9ef85f
Remove redundant using namespace Fallback
C++ has ADL to find overloads. using namespace does nothing in this
case.
2025-04-18 12:27:37 +02:00
psi29a
87d77a6882 Merge branch 'rippleshaders' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix crash if ripple pipeline shaders are unavailable

See merge request OpenMW/openmw!4622
2025-04-17 09:26:12 +00:00
Alexei Kotov
c1c8769742 Merge branch 'clang_tidy_identifier_naming' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Enable identifier naming clang-tidy check (#8424)

See merge request OpenMW/openmw!4631
2025-04-14 13:39:09 +03:00
Alexei Kotov
271ab2e109 Merge branch 'dont-ask-the-disk-for-things-we-already-know' into 'master'
Avoid IO in resolveParentFileIndices

See merge request OpenMW/openmw!4627
2025-04-14 13:11:57 +03:00
Alexei Kotov
583620e607 Merge branch 'patch-1' into 'master'
Crimes.lua wrong type for victim aware

See merge request OpenMW/openmw!4623
2025-04-14 09:44:45 +03:00
psi29a
6dd2cac3ec Merge branch 'better-freeze-catcher' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Improve Windows crash/freeze catcher UX

See merge request OpenMW/openmw!4630
2025-04-13 20:57:12 +00:00
Alexei Kotov
6aed2d8284 Bump Crimes interface version 2025-04-13 14:05:07 +03:00
elsid
84f471ce5c
Enable identifier naming clang-tidy check 2025-04-13 09:59:51 +02:00
AnyOldName3
deb070389f Improve Windows crash/freeze catcher UX
* Change crash log to crash dump in messages.
* Make the freeze catcher popup disappear more quickly when OpenMW thaws - we got a few freeze dumps from after a thaw.
* Improve freeze catcher message - hopefully fewer users think it's a false positive they're expected to put up with and we get future reports sooner.
2025-04-12 18:38:55 +01:00
Alexei Kotov
f7f148a6ca Merge branch 'fix-debug-config' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix Windows Debug build

See merge request OpenMW/openmw!4629
2025-04-12 17:35:03 +03:00
AnyOldName3
a5a6f33578 Manual reformatting that wasn't done automatically on my machine 2025-04-11 17:41:40 +01:00
AnyOldName3
d74a0edb82 Format 2025-04-11 17:37:55 +01:00
AnyOldName3
396cd1c727 Fix Windows Debug build
This was a regression from https://gitlab.com/OpenMW/openmw/-/merge_requests/4596

Also move more things into the anonymous namespace because there's not really a reason not to and I had to rearrange things anyway.
2025-04-11 17:33:19 +01:00
AnyOldName3
31fcc5e126 Add test for new ReadersCache functions 2025-04-11 17:30:56 +01:00
Dave Corley
22172f3b0e FIX: useAmbientLighting arg of addVfx options is a bool 2025-04-10 11:48:57 -07:00
Dave Corley
b68935e917 FIX: Model param of addVfx should not be a header 2025-04-10 11:44:34 -07:00
Dave Corley
e512a8e74f FIX: Add a name to options table in castRenderingRay 2025-04-10 11:42:24 -07:00
AnyOldName3
48572e4c96 Even more elses 2025-04-10 18:32:52 +01:00
Dave Corley
1d1ae1c906 CLEANUP: But it can also be a single object 2025-04-10 10:15:31 -07:00
Dave Corley
586467540b FIX: Clarify that ignore field of raycast options types are a list and not a single object 2025-04-10 10:15:31 -07:00
AnyOldName3
37dc1a6a76 Remove redundant elses 2025-04-10 16:51:23 +01:00
AnyOldName3
15162a734d Avoid IO in resolveParentFileIndices
In the olden days, we passed it a vector of open ESMReader instances, as they knew the filenames and sizes, so were a convenient source of this knowledge.

When the ReadersCache was introduced as a pool of readers to limit the maximum number of simultaneously open file handles (to avoid going over the OS' limit) it was a poor substitute.
* We iterate over all the earlier readers in order in a double loop, which is the worst case scenario for an LRU pool as once we're past the size limit, we're guaranteed maximum thrashing - the least recently used item is the most likely to be used next, so the worst to evict.
* We didn't want to read any ESM files, just know whether they'd been read and what their sizes were, so didn't want to open a file handle, which the ReadersCache forced us to do.

Obviously, opening lots of file handles isn't fast, and as this was an operation done for each content file which iterated over the file's masters and within that loop iterated over every loaded file, that's O(n^3) complexity in the worst case, and for things like delta plugin merged plugins, they hit the worst case in long load orders.

This resolves the freeze reported as https://gitlab.com/OpenMW/openmw/-/issues/8425, but there may be other freezes on launch.
2025-04-10 16:16:19 +01:00
psi29a
ea8369eff0 Merge branch 'dont-redraw-the-whole-gui-every-time-we-change-the-tiniest-thing' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Be more careful when we tell Qt that data has changed

Closes #8405

See merge request OpenMW/openmw!4621
2025-04-09 11:19:32 +00:00
AnyOldName3
096759435a Add progress bars where the launcher can be limited by IO
I tested this with a USB3 external hard drive.

These two places were the only ones where we're IO-bound and block the main thread, so they're the only ones that need progress bars.

If trying to replicate this test, then it's important to unplug the hard drive between each repeat.
Apparently Windows is excellent at disk caching these days as it takes a minute and a half to start the launcher with Total Overhaul on this drive when it's just been plugged in, but less time than the first launch after a reboot on an NVME drive once the cache has been warmed up.
2025-04-09 01:36:52 +01:00
AnyOldName3
894ea4ba62 Don't precompute load order errors after every change
It's much slower than doing it on demand as it only takes a microsecond, but for a really big load order, there are hundreds of thousands of intermediate calls before everything's set up and we can draw the GUI.
2025-04-08 01:19:24 +01:00
AnyOldName3
d6b61f1f54 Sprinkle some const&
QStringView required more fighting as loads of call sites take a const&
2025-04-08 00:34:45 +01:00
AnyOldName3
e779f115ef Exclude directories from containsDataFiles
Also include capo's microoptimisation even though it doesn't make things any faster.
2025-04-07 16:11:27 +01:00
psi29a
428044abe2 Merge branch 'moveoutalready' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Open the data directory file picker at the last opened location

Closes #8426

See merge request OpenMW/openmw!4606
2025-04-07 11:04:26 +00:00
psi29a
bd1c2a11d7 Merge branch 'herbalfish' into 'master'
Fix some graphic herbalism issues

See merge request OpenMW/openmw!4624
2025-04-07 11:03:55 +00:00
psi29a
0c4c202998 Merge branch 'ifavideofallsintheforest' into 'master'
Pause menu video playback when OpenMW is minimized

Closes #8441

See merge request OpenMW/openmw!4625
2025-04-07 11:03:37 +00:00
Alexei Kotov
8419116cae Fix crash if ripple pipeline shaders are unavailable 2025-04-07 03:31:32 +03:00
psi29a
8a0f513094 Merge branch 'fix_clang_tidy' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Fix clang tidy checks

See merge request OpenMW/openmw!4619
2025-04-06 17:54:20 +00:00
Evil Eye
1667b11564 Pause menu video playback when OpenMW is minimized 2025-04-06 19:42:04 +02:00
psi29a
b29d89bd6a Merge branch 'enchantment404' into 'master'
Allow referenced enchantments to be missing on equipped items

See merge request OpenMW/openmw!4617
2025-04-06 16:06:10 +00:00
psi29a
211a5e5bda Merge branch 'lightminimumradius' into 'master'
Give point lights a minimum radius of 16

See merge request OpenMW/openmw!4601
2025-04-06 16:03:35 +00:00
psi29a
2ed14de41f Merge branch 'objectrootless' into 'master'
Account for creatures not having a model in more places

Closes #8439

See merge request OpenMW/openmw!4618
2025-04-06 16:01:50 +00:00
Evil Eye
d826962eaa Don't assume unresolved containers contain no visible items 2025-04-06 11:02:31 +02:00
Evil Eye
962ef91e25 Allow skinned plants to be harvested 2025-04-06 11:01:26 +02:00
Chronolegionnaire
ed62f9b12b Lua api demands a boolean for victim aware but crimes.lua looks for a number. Which makes scripts that call the crime interface unable to provide a value other than nil for victim aware. 2025-04-06 06:27:40 +00:00
AnyOldName3
973282e471 Optimise ContentSelectorModel::ContentModel::item
This saves about 5% of remaining launcher startup time

Not using fileProperty avoids loads of QVariant conversions.
2025-04-06 02:46:31 +01:00
AnyOldName3
7bad2864d9 Reuse QIcon
This saves more than 15% of launcher startup time on my machine (after the prior improvements - it's way less without those)
2025-04-06 02:40:42 +01:00
AnyOldName3
1237746549 Be more careful when we tell Qt that data has changed
Unchecking files only changes whether they're checked, and doesn't completely rearrange the table and change the number of elements it has, so we only need to change the check state, not the whole layout.

It's way faster to just query all the data once after setting a content list than it is to query the data for all files between the old and new location of a file when we change any file's location in the load order.
2025-04-06 01:31:05 +01:00
elsid
7254bb74a4
Enable modernize-avoid-bind clang-tidy check 2025-04-05 12:55:18 +02:00
elsid
3af2091b28
Use prefix with dot for clang-analyzer-optin. checks 2025-04-05 12:55:18 +02:00
elsid
621a0a15a3
Disable clang-analyzer-cplusplus.NewDelete clang-tidy check 2025-04-05 12:55:18 +02:00
elsid
c34b0f90d7
Avoid clang-tidy checks duplication 2025-04-05 12:55:17 +02:00
elsid
e098770ba2
Use custom clang-tidy config for extern/ 2025-04-05 12:55:17 +02:00
elsid
7c45a564a1
Fix clang-analyzer-deadcode.DeadStores 2025-04-05 12:55:17 +02:00
elsid
da388c93eb
Remove boost-* clang-tidy checks
There are only:
* https://clang.llvm.org/extra/clang-tidy/checks/boost/use-ranges.html
* https://clang.llvm.org/extra/clang-tidy/checks/boost/use-to-string.html

None of them makes sense in this project.
2025-04-05 12:55:17 +02:00
elsid
d609bd1ab1
Fix clang-tidy header filter 2025-04-05 12:55:17 +02:00
Evil Eye
15f4368fe6 Account for creatures not having a model in more places 2025-04-05 10:19:50 +02:00
Alexei Kotov
3901084cc2 Merge branch 'cmake_minimum_required_macos' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Remove cmake_minimum_required for osx install script

See merge request OpenMW/openmw!4615
2025-04-05 08:54:09 +03:00
Alexei Kotov
065a388632 Allow enchantments to be missing on equipped items 2025-04-05 01:10:41 +03:00
elsid
9a6807f862
Remove cmake_minimum_required for osx install script
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
2025-04-03 23:01:36 +02:00
Alexei Kotov
3523ba564a Merge branch 'non-deprecated-known-folder-api' into 'master'
Some checks failed
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Use non-deprecated known folder API

See merge request OpenMW/openmw!4603
2025-04-02 19:20:21 +03:00
Alexei Kotov
1629ea32f7 Merge branch 'rm_unused' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Remove declaration without definition

See merge request OpenMW/openmw!4607
2025-04-01 23:35:49 +03:00
Evil Eye
86426aa87b Open the data directory file picker at the last opened location 2025-03-31 17:11:09 +02:00
elsid
a61ce111a5
Remove declaration without definition 2025-03-31 00:28:06 +02:00
Alexei Kotov
11c2fd9e3d Merge branch 'master' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
FIX: Remove outdated instructions for ubuntu installation

See merge request OpenMW/openmw!4605
2025-03-29 02:47:16 +03:00
Dave Corley
b6be7cdd56 CLEANUP: Use apt instead of apt-get 2025-03-28 22:27:30 +00:00
Dave Corley
caef91d261 FIX: Remove outdated instructions for ubuntu installation 2025-03-27 14:55:05 -07:00
psi29a
72aefbf191 Merge branch 'lua_save_load_test' into 'master'
Some checks failed
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Add Lua integration tests for loading and saving

See merge request OpenMW/openmw!4604
2025-03-27 11:05:12 +00:00
psi29a
747771ac5e Merge branch 'fontexport' into 'master'
Restore --export-fonts option functionality

See merge request OpenMW/openmw!4561
2025-03-27 11:04:35 +00:00
psi29a
5f413e7b4d Merge branch 'navigator_debug_mesh' into 'master'
Make navigator debug meshes generation safer

See merge request OpenMW/openmw!4602
2025-03-27 11:04:18 +00:00
elsid
2ebdc43bbe
Add test for load while teleporting
To reproduce #8311.

Load game while landracer is scheduled to teleport from different cell.
2025-03-25 23:41:04 +01:00
elsid
536325e0ba
Add test for saving and loading the game 2025-03-25 23:40:58 +01:00
elsid
cbcd4f6acd
Move matchers to different module 2025-03-25 23:24:24 +01:00
Evil Eye
9570b29a0a Merge branch 'lua_test_menu' into 'master'
Some checks failed
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Run Lua integration tests starting with menu script

See merge request OpenMW/openmw!4556
2025-03-25 16:54:41 +00:00
AnyOldName3
166852254f Use non-deprecated known folder API
SHGetFolderPathW was deprecated in Windows Vista nearly two decades ago. ShGetKnownFolderPath is the replacement.

Also log if there was an error. Someone seemed to be getting an error on Discord, despite other apps being able to get the path just fine with these functions.

Also don't pass the flags to create the folders if they don't exist. We probably don't have the right permissions and if they don't exist, then there are bigger problems. Maybe this will fix the issue the user was having.

Also add a comment about global config on Windows being fundamentally wrong.
2025-03-25 01:32:44 +00:00
Alexei Kotov
f8be5fdd2a Give point lights a minimum radius of 16 2025-03-24 22:14:08 +03:00
elsid
ada48d9021
Reduce a chance to have a deadlock in the AsyncNavMeshUpdater
* Do not fail tile generation if debug mesh writing fails.
* Mark some functions as noexcept to better crash than have a deadlock.
* Unlock tile and remove job if there on exception while processing it.
2025-03-23 23:33:40 +01:00
elsid
7112217adc
Use temporary directory for tests output 2025-03-23 23:33:40 +01:00
elsid
87a2f776b7
Add version to the recast log prefix 2025-03-23 23:33:39 +01:00
psi29a
d13f108779 Merge branch 'nbsp' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Non-breaking space-related fixes (#8378)

Closes #8378

See merge request OpenMW/openmw!4592
2025-03-23 16:27:07 +00:00
Evil Eye
57fb334a6e Merge branch 'recast_log_level' into 'master'
Support max log level for Recast via env variable

See merge request OpenMW/openmw!4596
2025-03-23 14:50:11 +00:00
Alexei Kotov
88c673de51 Merge branch 'luadoc' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Fix minor documentation errors

See merge request OpenMW/openmw!4595
2025-03-22 14:22:51 +03:00
elsid
51258662b5
Support max log level for Recast via env variable
Do not write to log if log message level is greater than one speficied
in the OPENMW_RECAST_MAX_LOG_LEVEL env variable. Use Error by default.
2025-03-21 15:34:47 +01:00
Evil Eye
e5e21eef20 Fix minor documentation errors 2025-03-20 20:08:31 +01:00
psi29a
057c85b710 Merge branch 'esmtool_qust_subrecords' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Skip SLSD, SCVR, SCRV subrecords in QUST record

See merge request OpenMW/openmw!4589
2025-03-18 13:18:15 +00:00
psi29a
241a24564a Merge branch 'doubleexit' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Don't assume there is a GUI mode in exitCurrentGuiMode (#8380)

Closes #8380

See merge request OpenMW/openmw!4590
2025-03-18 13:16:23 +00:00
Alexei Kotov
cd3980eca4 Make figure space non-breaking 2025-03-18 10:29:29 +03:00
Alexei Kotov
c2744a1846 Change substitute character in Mystic Cards from question mark to underscore 2025-03-18 00:50:03 +03:00
Alexei Kotov
8d0dcb774f Add no-break space to MysticCards 2025-03-18 00:49:51 +03:00
Alexei Kotov
b5a2a4e52d Render no-break space in books, don't consider narrow NBSP breaking 2025-03-17 22:03:38 +03:00
Alexei Kotov
e4ae0c9a95 Don't assume there is a GUI mode in exitCurrentGuiMode (#8380) 2025-03-16 09:06:01 +03:00
elsid
e5f6b77c29
Skip SLSD, SCVR, SCRV subrecords in QUST record
Present in:

Fallout 3 GOTY English/Data/Anchorage.esm
Fallout 3 GOTY English/Data/BrokenSteel.esm
Fallout 3 GOTY English/Data/PointLookout.esm
Fallout 3 GOTY English/Data/ThePitt.esm
Fallout 3 GOTY English/Data/Zeta.esm
2025-03-15 13:11:26 +01:00
Evil Eye
2b5d076ff8 Merge branch 'followmeifyouwanttohit' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Don't use attack strength as "hit ready" flag

See merge request OpenMW/openmw!4583
2025-03-15 12:02:35 +00:00
psi29a
2ca1850ea9 Merge branch 'clang19' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Clang 19 build fix (!4549 without sol update)

See merge request OpenMW/openmw!4585
2025-03-14 14:47:20 +00:00
psi29a
9a35e3f64d Merge branch 'ripplinglogs' into 'master'
Only log ripples pipeline once

See merge request OpenMW/openmw!4584
2025-03-14 13:17:14 +00:00
Alexei Kotov
fdba5d4cb7 Merge branch 'padgame' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Distinguish between I.Controls and I.GamepadControls

See merge request OpenMW/openmw!4582
2025-03-12 08:59:39 +03:00
Alexei Kotov
ced142da92 Lift upstream sol::optional::emplace Clang 19 build fix 2025-03-12 00:05:20 +03:00
elsid
e5ad1cd214 Do not use no longer supported std::char_traits
/usr/bin/../include/c++/v1/string_view:300:42: error: implicit instantiation of undefined template 'std::char_traits<signed char>'
  300 |   static_assert(is_same<_CharT, typename traits_type::char_type>::value,
      |                                          ^
/home/elsid/dev/openmw/components/to_utf8/to_utf8.cpp:55:41: note: in instantiation of template class 'std::basic_string_view<signed char>' requested here
   55 |     std::basic_string_view<signed char> getTranslationArray(FromType sourceEncoding)
      |                                         ^
/usr/bin/../include/c++/v1/__fwd/string.h:23:29: note: template is declared here
   23 | struct _LIBCPP_TEMPLATE_VIS char_traits;
      |                             ^

std::char_traits support for non char types was removed from libc++19:
https://reviews.llvm.org/D157058.
2025-03-11 20:32:36 +03:00
Alexei Kotov
9f85e51934 Only log ripples pipeline once 2025-03-11 17:37:18 +03:00
Alexei Kotov
569ed4559f Merge branch 'fix_msvc_warnings' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix msvc warnings

See merge request OpenMW/openmw!4579
2025-03-11 00:55:32 +03:00
Alexei Kotov
5354a5f786 Don't use attack strength as "hit ready" flag
This unbreaks follow animations' strength dependence
2025-03-11 00:44:35 +03:00
Evil Eye
c691917172 Distinguish between I.Controls and I.GamepadControls 2025-03-10 16:37:13 +01:00
psi29a
73bb17009e Merge branch 'getsoundplaying' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Don't require a reference for GetSoundPlaying (#8389)

Closes #8389

See merge request OpenMW/openmw!4576
2025-03-10 08:13:23 +00:00
elsid
51d73e37df
Fix msvc warnings
components\lua\configuration.cpp(133): warning C4267: 'argument': conversion from 'size_t' to 'int', possible loss of data
components\esm3\effectlist.cpp(35): warning C4267: '=': conversion from 'size_t' to 'uint32_t', possible loss of data
components_tests\misc\testmathutil.cpp(54): warning C4305: 'argument': truncation from 'const double' to 'osg::Vec3f::value_type'
components_tests\misc\testmathutil.cpp(62): warning C4305: 'argument': truncation from 'const double' to 'osg::Vec3f::value_type'
components_tests\misc\testmathutil.cpp(131): warning C4305: 'argument': truncation from 'const double' to 'osg::Vec3f::value_type'
components_tests\misc\testmathutil.cpp(135): warning C4305: 'argument': truncation from 'const double' to 'osg::Vec3f::value_type'
components_tests\misc\testmathutil.cpp(135): warning C4305: 'argument': truncation from 'const double' to 'osg::Vec3f::value_type'
components_tests\misc\testmathutil.cpp(139): warning C4305: 'argument': truncation from 'const double' to 'osg::Vec3f::value_type'
2025-03-09 17:55:17 +01:00
Alexei Kotov
a49a900a7b Merge branch 'fix_lua_teleport' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Merge deleted refs when unloading a cell (#8311)

Closes #8311

See merge request OpenMW/openmw!4575
2025-03-09 15:51:01 +03:00
Alexei Kotov
01ea2ad08c Merge branch 'fix_delete_game_ub' into 'master'
Some checks are pending
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Avoid accessing removed character on deleting last save (#8387)

Closes #8387

See merge request OpenMW/openmw!4574
2025-03-09 10:42:11 +03:00
Alexei Kotov
8cb1838c4a Don't require a reference for GetSoundPlaying (#8389) 2025-03-09 00:52:00 +03:00
elsid
2892e19c43
Run integration tests with verbose output 2025-03-08 13:14:23 +01:00
elsid
0e19b1dd75
Run Lua integration tests starting with menu script
This allows writing tests for menu scripts.

Keep global script as entry point to morrowind tests.

Fix menu.newGame and menu.loadGame to hide main menu.
2025-03-08 13:14:20 +01:00
elsid
f800f63ee5
Merge deleted refs when unloading a cell
To unload objects scheduled to be teleported.
2025-03-08 12:48:28 +01:00
elsid
5776eea1b0
Avoid accessing removed character on deleting last save 2025-03-08 00:34:27 +01:00
Alexei Kotov
5f92d520ee Merge branch 'mainmembers' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix more potential use-after-free issues

See merge request OpenMW/openmw!4566
2025-03-07 00:25:35 +03:00
Evil Eye
9bf6a15ff5 Force move the captured string 2025-03-06 17:32:56 +01:00
psi29a
c5a1ca7c3e Merge branch 'changelog' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Add addressed Korean font issue (#8378) to the changelog

Closes #8378

See merge request OpenMW/openmw!4573
2025-03-06 12:22:58 +00:00
elsid
7a9c2d5e88
Split local and global event handlers 2025-03-05 22:18:46 +01:00
elsid
981ca957c1
Register global tests to run them 2025-03-05 22:18:46 +01:00
elsid
8b62f02523
Use world.players to initialize player in global tests 2025-03-05 22:18:46 +01:00
elsid
c298210844
Make integration_tests.py output more verbose
* Make it look more like googletest.
* Print total and failed number of tests.
* Print failed tests names.
* Print duration of each test and total.
* Hide all logs by default.
2025-03-05 22:18:46 +01:00
elsid
f80c7b2355
Expect openmw.cfg to exist 2025-03-05 22:18:47 +01:00
Alexei Kotov
124ada8d14 Add addressed Korean font issue (#8378) to the changelog 2025-03-06 00:18:10 +03:00
psi29a
95312139d5 Merge branch 'context_matters' into 'master'
Some checks are pending
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Fix in-game actions not clearing because of input bindings only initializing in menu context

See merge request OpenMW/openmw!4570
2025-03-05 21:18:02 +00:00
psi29a
8bbb46b52c Merge branch 'frankfontaine' into 'master'
Properly implement bitmap font kerning (#8378)

See merge request OpenMW/openmw!4565
2025-03-05 21:16:19 +00:00
psi29a
888415059c Merge branch 'fontainofdreams' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Allow bitmap font texture reading to end prematurely (#8378)

See merge request OpenMW/openmw!4564
2025-03-05 13:11:10 +00:00
psi29a
1e0bdcc270 Merge branch 'elefont' into 'master'
Further revise bitmap glyph replacements (#7531)

See merge request OpenMW/openmw!4563
2025-03-05 13:09:53 +00:00
Alexei Kotov
97717e6fce Merge branch 'fix_lua_vfs_crash' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Fix crash on LuaManager::clear triggered by vfs (#8370)

Closes #8370

See merge request OpenMW/openmw!4559
2025-03-05 04:51:47 +03:00
uramer
990096ff9b Fix in-game actions not clearing because of input bindings only initializing in menu context 2025-03-04 21:08:26 +01:00
Alexei Kotov
82307d4e6b Merge branch 'lua_player_attack_test' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Direct player attack lower by target's half height

See merge request OpenMW/openmw!4562
2025-03-04 22:49:32 +03:00
Evil Eye
b0e9df0139 Ensure class members are tied to the main Lua state 2025-03-03 19:37:07 +01:00
Evil Eye
f0cee09b7c Extend lifetime of strings placed on the action queue 2025-03-03 19:36:54 +01:00
Alexei Kotov
6e9d15f91d Merge branch 'keep-menu-actions-after-load' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
#8365 Keep MENU-registered input actions between games

See merge request OpenMW/openmw!4554
2025-03-03 18:10:12 +00:00
Evil Eye
62d1cdcdac Merge branch 'lua_hide_main_menu' into 'master'
Hide main menu on new and loading game from menu scripts

See merge request OpenMW/openmw!4560
2025-03-03 15:39:05 +00:00
Alexei Kotov
fd358396fc Properly implement bitmap font kerning 2025-03-03 08:42:39 +03:00
Alexei Kotov
24468fd965 Allow bitmap font texture to end prematurely 2025-03-03 08:29:00 +03:00
Alexei Kotov
c8fe596fc4 Add some remaining missing bitmap substitutions 2025-03-03 08:02:34 +03:00
Alexei Kotov
c50d8195bb Remove custom substitutions for glyphs that may exist in the font 2025-03-03 08:02:16 +03:00
elsid
7670afcba1
Direct player attack lower by target's half height
To make sure it always hits the target.
2025-03-03 00:18:42 +01:00
Alexei Kotov
dc3264a3a5 Restore --export-fonts option functionality 2025-03-02 22:33:06 +03:00
elsid
1bb3198b71
Fix crash on LuaManager::clear triggered by vfs
See https://gitlab.com/OpenMW/openmw/-/issues/8370#note_2370896069.

=================================================================
==8699==ERROR: AddressSanitizer: heap-use-after-free on address 0x50800060d4b0 at pc 0x7254de50893e bp 0x7fffa97f9700 sp 0x7fffa97f96f0
READ of size 8 at 0x50800060d4b0 thread T0
    #0 0x7254de50893d  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x6293d) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #1 0x7254de50ccad in lua_rawgeti (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x66cad) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #2 0x7254de5d4cab in luaL_unref (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x12ecab) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #3 0x5f96378dd1e9 in sol::stateless_reference::deref(lua_State*) const /home/elsid/dev/openmw/extern/sol3/sol/reference.hpp:440
    #4 0x5f96378dd1e9 in sol::basic_reference<false>::deref() const /home/elsid/dev/openmw/extern/sol3/sol/reference.hpp:545
    #5 0x5f96378dd1e9 in sol::basic_reference<false>::~basic_reference() /home/elsid/dev/openmw/extern/sol3/sol/reference.hpp:635
    #6 0x5f96378dd1e9 in sol::basic_object_base<sol::basic_reference<false> >::~basic_object_base() /home/elsid/dev/openmw/extern/sol3/sol/object_base.hpp:33
    #7 0x5f96378dd1e9 in sol::basic_object<sol::basic_reference<false> >::~basic_object() /home/elsid/dev/openmw/extern/sol3/sol/object.hpp:35
    #8 0x5f96378dd1e9 in ~<lambda> /home/elsid/dev/openmw/apps/openmw/mwlua/vfsbindings.cpp:195
    #9 0x5f96378dd1e9 in ~functor_function /home/elsid/dev/openmw/extern/sol3/sol/function_types_stateful.hpp:32
    #10 0x5f96378dd1e9 in destroy_at<sol::function_detail::functor_function<MWLua::initVFSPackage(const Context&)::<lambda(sol::this_state, sol::object)>::<lambda()>, false, true> > /usr/include/c++/14.2.1/bits/stl_construct.h:88
    #11 0x5f96378dd1e9 in destroy<sol::function_detail::functor_function<MWLua::initVFSPackage(const Context&)::<lambda(sol::this_state, sol::object)>::<lambda()>, false, true> > /usr/include/c++/14.2.1/bits/alloc_traits.h:599
    #12 0x5f96378dd1e9 in user_alloc_destroy<sol::function_detail::functor_function<MWLua::initVFSPackage(const Context&)::<lambda(sol::this_state, sol::object)>::<lambda()>, false, true> > /home/elsid/dev/openmw/extern/sol3/sol/stack_core.hpp:460
    #13 0x5f963a31e305 in int sol::detail::trampoline<int (*&)(lua_State*)>(lua_State*, int (*&)(lua_State*)) /home/elsid/dev/openmw/extern/sol3/sol/trampoline.hpp:158
    #14 0x5f963a31e89c in sol::detail::c_trampoline(lua_State*, int (*)(lua_State*)) /home/elsid/dev/openmw/extern/sol3/sol/trampoline.hpp:183
    #15 0x7254de4dc13a  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x3613a) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #16 0x7254de4deac4  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x38ac4) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #17 0x7254de4df1a2  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x391a2) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #18 0x7254de4e1cf2  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x3bcf2) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #19 0x7254de4e2a37  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x3ca37) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #20 0x7254de50f4a4 in lua_gc (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x694a4) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #21 0x5f96371f615c in MWLua::LuaManager::clear() /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp:348
    #22 0x5f96371f91ea in MWLua::LuaManager::noGame() /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp:397
    #23 0x5f963a1c7170 in MWState::StateManager::cleanup(bool) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:71
    #24 0x5f963a1cabfe in MWState::StateManager::newGame(bool) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:169
    #25 0x5f963a1c7aa4 in MWState::StateManager::update(float) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:761
    #26 0x5f963a230bab in OMW::Engine::frame(unsigned int, float) /home/elsid/dev/openmw/apps/openmw/engine.cpp:238
    #27 0x5f963a2442f3 in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:1032
    #28 0x5f963633b3a7 in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:228
    #29 0x5f963b375b45 in Debug::wrapApplication(int (*)(int, char**), int, char**, std::basic_string_view<char, std::char_traits<char> >) /home/elsid/dev/openmw/components/debug/debugging.cpp:457
    #30 0x5f9636331695 in main /home/elsid/dev/openmw/apps/openmw/main.cpp:240
    #31 0x7254db435487  (/usr/lib/libc.so.6+0x27487) (BuildId: 0b707b217b15b106c25fe51df3724b25848310c0)
    #32 0x7254db43554b in __libc_start_main (/usr/lib/libc.so.6+0x2754b) (BuildId: 0b707b217b15b106c25fe51df3724b25848310c0)
    #33 0x5f9636331464 in _start (/home/elsid/dev/openmw/build/gcc/asan/openmw+0x10db464) (BuildId: ac74a52ca60e8913bef6eb6b3b23d6de648cf3c9)

0x50800060d4b0 is located 16 bytes inside of 96-byte region [0x50800060d4a0,0x50800060d500)
freed by thread T0 here:
    #0 0x7254e2afc102 in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x5f963a2f84e7 in LuaUtil::LuaState::trackingAllocator(void*, void*, unsigned long, unsigned long) /home/elsid/dev/openmw/components/lua/luastate.cpp:107
    #2 0x7254de4f7779  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x51779) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #3 0x7254de4de7f3  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x387f3) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #4 0x7254de4e1a9a  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x3ba9a) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #5 0x7254de4e2a37  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x3ca37) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #6 0x7254de50f4a4 in lua_gc (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x694a4) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #7 0x5f96371f615c in MWLua::LuaManager::clear() /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp:348
    #8 0x5f96371f91ea in MWLua::LuaManager::noGame() /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp:397
    #9 0x5f963a1c7170 in MWState::StateManager::cleanup(bool) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:71
    #10 0x5f963a1cabfe in MWState::StateManager::newGame(bool) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:169
    #11 0x5f963a1c7aa4 in MWState::StateManager::update(float) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:761
    #12 0x5f963a230bab in OMW::Engine::frame(unsigned int, float) /home/elsid/dev/openmw/apps/openmw/engine.cpp:238
    #13 0x5f963a2442f3 in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:1032
    #14 0x5f963633b3a7 in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:228
    #15 0x5f963b375b45 in Debug::wrapApplication(int (*)(int, char**), int, char**, std::basic_string_view<char, std::char_traits<char> >) /home/elsid/dev/openmw/components/debug/debugging.cpp:457
    #16 0x5f9636331695 in main /home/elsid/dev/openmw/apps/openmw/main.cpp:240
    #17 0x7254db435487  (/usr/lib/libc.so.6+0x27487) (BuildId: 0b707b217b15b106c25fe51df3724b25848310c0)
    #18 0x7254db43554b in __libc_start_main (/usr/lib/libc.so.6+0x2754b) (BuildId: 0b707b217b15b106c25fe51df3724b25848310c0)
    #19 0x5f9636331464 in _start (/home/elsid/dev/openmw/build/gcc/asan/openmw+0x10db464) (BuildId: ac74a52ca60e8913bef6eb6b3b23d6de648cf3c9)

previously allocated by thread T20 here:
    #0 0x7254e2afc3c2 in realloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:85
    #1 0x5f963a2f7080 in LuaUtil::LuaState::trackingAllocator(void*, void*, unsigned long, unsigned long) /home/elsid/dev/openmw/components/lua/luastate.cpp:110
    #2 0x7254de4e2fc8  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x3cfc8) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #3 0x7254de4f7476  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x51476) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #4 0x7254de50c456 in lua_newthread (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x66456) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #5 0x7254de5d53e5  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x12f3e5) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
    #6 0x7254de4dc0c5  (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x360c5) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)

Thread T20 created by T0 here:
    #0 0x7254e2af44cb in pthread_create /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:245
    #1 0x7254db6e2071 in __gthread_create /usr/src/debug/gcc/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:676
    #2 0x7254db6e2071 in std:🧵:_M_start_thread(std::unique_ptr<std:🧵:_State, std::default_delete<std:🧵:_State> >, void (*)()) /usr/src/debug/gcc/gcc/libstdc++-v3/src/c++11/thread.cc:172
    #3 0x5f96380fa2eb in thread<MWLua::Worker::Worker(MWLua::LuaManager&)::<lambda()> > /usr/include/c++/14.2.1/bits/std_thread.h:173
    #4 0x5f96380fa2eb in MWLua::Worker::Worker(MWLua::LuaManager&) /home/elsid/dev/openmw/apps/openmw/mwlua/worker.cpp:18
    #5 0x5f963a23faf4 in std::__detail::_MakeUniq<MWLua::Worker>::__single_object std::make_unique<MWLua::Worker, MWLua::LuaManager&>(MWLua::LuaManager&) /usr/include/c++/14.2.1/bits/unique_ptr.h:1077
    #6 0x5f963a23faf4 in OMW::Engine::prepareEngine() /home/elsid/dev/openmw/apps/openmw/engine.cpp:920
    #7 0x5f963a2413ae in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:952
    #8 0x5f963633b3a7 in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:228
    #9 0x5f963b375b45 in Debug::wrapApplication(int (*)(int, char**), int, char**, std::basic_string_view<char, std::char_traits<char> >) /home/elsid/dev/openmw/components/debug/debugging.cpp:457
    #10 0x5f9636331695 in main /home/elsid/dev/openmw/apps/openmw/main.cpp:240
    #11 0x7254db435487  (/usr/lib/libc.so.6+0x27487) (BuildId: 0b707b217b15b106c25fe51df3724b25848310c0)
    #12 0x7254db43554b in __libc_start_main (/usr/lib/libc.so.6+0x2754b) (BuildId: 0b707b217b15b106c25fe51df3724b25848310c0)
    #13 0x5f9636331464 in _start (/home/elsid/dev/openmw/build/gcc/asan/openmw+0x10db464) (BuildId: ac74a52ca60e8913bef6eb6b3b23d6de648cf3c9)

SUMMARY: AddressSanitizer: heap-use-after-free (/home/elsid/dev/LuaJIT/build/gcc/asan/install/lib/libluajit-5.1.so.2+0x6293d) (BuildId: 1249151684379d19b11900f406fea9704a6375cb)
Shadow bytes around the buggy address:
  0x50800060d200: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x50800060d280: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x50800060d300: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x50800060d380: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x50800060d400: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
=>0x50800060d480: fa fa fa fa fd fd[fd]fd fd fd fd fd fd fd fd fd
  0x50800060d500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50800060d580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50800060d600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50800060d680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50800060d700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==8699==ABORTING
2025-03-01 15:54:24 +01:00
elsid
d400c0959c
Hide main menu on new and loading game from menu scripts 2025-02-28 18:11:22 +01:00
uramer
3a98b945a8 Keep MENU-registered input actions between games 2025-02-26 16:56:41 +01:00
Alexei Kotov
a6676fd6f3 Merge branch 'actionshaveeffects' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Turn ActorActiveEffects:remove into a delayed action

Closes #8317, #8350, and #8366

See merge request OpenMW/openmw!4553
2025-02-24 22:49:52 +00:00
Alexei Kotov
73bb281f34 Merge branch 'toscrollornottoscroll' into 'master'
Don't create a scrollbar that cannot be scrolled

Closes #8364

See merge request OpenMW/openmw!4552
2025-02-24 21:35:19 +00:00
Evil Eye
f891a7c3b3 Turn ActorActiveEffects:remove into a delayed action 2025-02-24 17:07:32 +01:00
psi29a
614ca25d3b Merge branch 'changelog' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Move #6097 from 0.49.0 to 0.48.0 changelog

See merge request OpenMW/openmw!4544
2025-02-24 08:34:56 +00:00
psi29a
74018162c7 Merge branch 'deordmitri-master-patch-83688' into 'master'
Update install-game-files.rst

See merge request OpenMW/openmw!4269
2025-02-24 08:33:13 +00:00
Evil Eye
3e3dfac4e0 Don't create a scrollbar that cannot be scrolled 2025-02-23 10:48:13 +01:00
Alexei Kotov
dad22cb672 Apply jvoisin's suggestion to install-game-files.rst 2025-02-22 22:37:58 +00:00
Evil Eye
07cc2a72bb Merge branch 'automove' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Don't disable automove when the player can't move (#8358)

Closes #8358

See merge request OpenMW/openmw!4547
2025-02-22 11:18:39 +00:00
Alexei Kotov
835ad09657 Move #6097 from 0.49.0 to 0.48.0 changelog 2025-02-21 11:39:54 +03:00
Alexei Kotov
cd53cbbea2 Don't disable automove when the player can't move (#8358) 2025-02-21 01:38:08 +03:00
psi29a
5b788baa35 Merge branch 'screening' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Editor: Fall back to the closest screen when necessary (#8354)

Closes #8354

See merge request OpenMW/openmw!4542
2025-02-19 11:27:33 +00:00
Alexei Kotov
04689334c5 Editor: Use the first/primary screen as last resort 2025-02-18 22:28:54 +03:00
Alexei Kotov
d71e4ec9f0 Editor: Fall back to the closest screen when necessary (#8354) 2025-02-18 13:31:13 +03:00
psi29a
6ede5635b3 Merge branch 'edgelord' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix UB when pathgrid geometry is generated and all pathgrid edges are invalid

See merge request OpenMW/openmw!4541
2025-02-17 15:01:15 +00:00
Alexei Kotov
602a429a68 Fix UB when pathgrid geometry is generated and all pathgrid edges are invalid 2025-02-16 17:46:52 +03:00
psi29a
3b05ec0ab1 Merge branch 'countteleportula' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Include Ptrs with a count of 0 in cell unloading

Closes #8311

See merge request OpenMW/openmw!4536
2025-02-14 18:25:14 +00:00
Evil Eye
ad8f6e5eb6 Include Ptrs with a count of 0 in cell unloading 2025-02-12 22:07:30 +01:00
psi29a
63e3b8f41b Merge branch 'levelledcreatures' into 'master'
Some checks failed
Build and test / MacOS (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Avoid reference to temporary in levelled creatures bindings (#8347)

Closes #8347

See merge request OpenMW/openmw!4535
2025-02-11 20:34:20 +00:00
psi29a
19793c21b4 Merge branch 'menucrash' into 'master'
Clear queued scripts when clearing the Lua manager

Closes #8346

See merge request OpenMW/openmw!4537
2025-02-11 20:33:11 +00:00
Evil Eye
86d56a0b1a Clear queued scripts when clearing the Lua manager 2025-02-10 20:04:24 +01:00
psi29a
782c274d86 Merge branch 'shutupsdl' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Silence SDL3 window/display events coming from SDL2-compat

See merge request OpenMW/openmw!4531
2025-02-10 16:58:28 +00:00
psi29a
d2610973dd Merge branch 'shutupssg' into 'master'
Add a dummy serializer for billboards

See merge request OpenMW/openmw!4532
2025-02-10 16:58:21 +00:00
psi29a
3405dbab6d Merge branch 'nifboolvectors' into 'master'
Optimize NIF boolean list reading

See merge request OpenMW/openmw!4534
2025-02-10 16:57:53 +00:00
Alexei Kotov
5626d925e3 Avoid reference to temporary in levelled creatures bindings (#8347) 2025-02-10 13:07:31 +03:00
Alexei Kotov
c1960635d2 Optimize NIF boolean vector reading 2025-02-07 04:55:06 +03:00
Alexei Kotov
eaf9488ba0 Silence SDL3 window/display events coming from SDL2-compat 2025-02-04 20:30:19 +03:00
Alexei Kotov
cfa1ad0b33 Add a dummy serializer for billboards 2025-02-04 20:22:14 +03:00
AnyOldName3
1aa4ef029c Merge branch 'lessborkedprecipocclusion' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix invisible rain when occlusion is enabled and sky blending isn't (#7273)

Closes #7273

See merge request OpenMW/openmw!4520
2025-02-01 15:19:07 +00:00
Alexei Kotov
fe571c1a4d Fix invisible rain when occlusion is enabled and sky blending isn't (#7273) 2025-01-31 18:13:29 +03:00
Alexei Kotov
a17bffda26 Merge branch 'blencludes' into 'master'
Some checks are pending
Build and test / Windows (2022) (push) Blocked by required conditions
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Remove superfluous includes from animblendrules.cpp

See merge request OpenMW/openmw!4523
2025-01-31 14:46:12 +00:00
Alexei Kotov
c04cd2dfa4 Merge branch '49_multiview' into 'master'
.49 stereo fixes

See merge request OpenMW/openmw!4527
2025-01-31 14:35:33 +00:00
Alexei Kotov
81287034fc Merge branch 'pin-awscli' into 'master'
Pin awscli to 2.22.35

See merge request OpenMW/openmw!4524
2025-01-31 14:30:03 +00:00
AnyOldName3
a3531fe954 Direct downgrade failed, try uninstalling first
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
2025-01-31 00:55:17 +00:00
AnyOldName3
e583e64380 Downgrade preinstalled awscli to a version that works 2025-01-31 00:00:29 +00:00
Alexei Kotov
4c95e91a8d Replace awscli with s3cmd for macOS
Homebrew doesn't let us downgrade, we have to use an alternative client (for now)
2025-01-31 01:02:49 +03:00
Mads Buvik Sandvei
4428c1db7d Unused includes 2025-01-30 20:53:56 +01:00
Mads Buvik Sandvei
5b1aafb77a Formatting mistakes 2025-01-29 23:01:34 +01:00
Mads Buvik Sandvei
517aa81938 Change sky blending fix to remove changing the "sky" texture slot when disabling sky blending. 2025-01-29 22:35:19 +01:00
Mads Buvik Sandvei
b2c0d20d56 explicitly include lib/core/fragment.h.glsl 2025-01-28 20:05:08 +01:00
Mads Buvik Sandvei
efe72ea2d5 Clang format 2025-01-26 20:10:58 +01:00
Mads Buvik Sandvei
152dfacab2 multiview_resolve did not need to be version 330 2025-01-26 19:20:46 +01:00
Mads Buvik Sandvei
dec9ce4a5f sky dummy texture not actually needed when using sky texture slot. 2025-01-26 17:41:43 +01:00
Mads Buvik Sandvei
a1df9afc9a Formatting changes 2025-01-26 17:05:42 +01:00
Mads Buvik Sandvei
ea51c55d00 Restore valid per view shadow settings. 2025-01-26 16:31:52 +01:00
Mads Buvik Sandvei
a2f5e1c075 Fix multiview use in techniques 2025-01-26 16:26:36 +01:00
Mads Buvik Sandvei
6071de9d1d Set a dummy texture for sky blending, when multiview is enabled. 2025-01-26 15:20:25 +01:00
Mads Buvik Sandvei
2762be9f85 opaque depth texture must account for multiview 2025-01-26 14:57:34 +01:00
AnyOldName3
9ae12baee1 Pin it on Windows, too
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
2025-01-21 20:44:08 +00:00
AnyOldName3
57ffc11fba Try installing specific version of awscli on MacOS 2025-01-21 18:47:54 +00:00
AnyOldName3
2d0f45ea41 Log awscli version
2.23.0 had breaking changes, so we need to know if we're using it, and be able to diagnose anything else caused by breaking changes in the future now they're a possibility.
2025-01-21 17:16:07 +00:00
AnyOldName3
e90c4187ff Pin awscli to 2.22.35
Because of https://github.com/aws/aws-cli/issues/9214, 2.23.0 and later won't work with our non-Amazon-hosted S3 buckets.
2025-01-20 22:50:38 +00:00
Evil Eye
af9a9a6d64 Remove superfluous includes from animblendrules.cpp 2025-01-20 16:49:22 +01:00
psi29a
c16064e6f3 Merge branch 'nooneactuallyknowshowmorrowindcombatworks' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Check if the victim is within weapon reach upon hit (#8280)

See merge request OpenMW/openmw!4518
2025-01-18 10:40:52 +00:00
psi29a
e515e99937 Merge branch 'loadingesm4' into 'master'
Some checks are pending
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Track ESM4 file loading progress

See merge request OpenMW/openmw!4516
2025-01-17 11:59:20 +00:00
Alexei Kotov
2a62dd728f Check if the victim is within weapon reach upon hit (#8280) 2025-01-16 13:08:06 +03:00
Alexei Kotov
9fc62be2c6 Track ESM4 file loading progress 2025-01-14 20:27:02 +03:00
Alexei Kotov
fe047d98b1 Merge branch 'install-more-things' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Install tools left out on Windows

Closes #8297

See merge request OpenMW/openmw!4510
2025-01-14 15:17:51 +00:00
psi29a
af3640217e Merge branch 'smoothlandslikesmoothlittlebabies' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Editor: Prevent crash on smoothing undefined cell borders (#8299)

Closes #8299

See merge request OpenMW/openmw!4512
2025-01-14 11:18:28 +00:00
Alexei Kotov
df681a0da0 Merge branch 'muh-html-formatting' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
FIX: tooltips lose some of the relevant information if not stored as a QString

See merge request OpenMW/openmw!4513
2025-01-13 16:54:13 +00:00
jvoisin
7d47b225ab Merge branch '8300_create_arrows_bolts' into 'master'
Lua: Allow creating arrows and bolt records (#8300)

See merge request OpenMW/openmw!4514
2025-01-13 14:31:36 +00:00
psi29a
6ea3d11a8b Merge branch 'leave-openmw.cfg-alone' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Don't unnecessarily overwrite openmw.cfg

Closes #8286

See merge request OpenMW/openmw!4508
2025-01-13 08:25:43 +00:00
AnyOldName3
085c3b03a9 c h a n g e l o g 2025-01-12 18:21:29 +00:00
AnyOldName3
806635b96c Don't unnecessarily overwrite openmw.cfg
We don't need to risk reformatting the user's potentially-handwritten file if it parses to the same thing as we're about to write.
2025-01-12 18:21:06 +00:00
Alexei Kotov
d3fe31803f Editor: Prevent crash on smoothing undefined cell borders (#8299) 2025-01-12 21:05:48 +03:00
psi29a
ec43849020 Merge branch 'sensiblechain' into 'master'
Some checks are pending
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Handle weird post-processing chains gracefully (#8295)

Closes #8295

See merge request OpenMW/openmw!4509
2025-01-12 17:58:41 +00:00
psi29a
73161e6b64 Merge branch 'commas-arent-special' into 'master'
Don't give commas special meaning when matching comments to openmw.cfg values

Closes #8287

See merge request OpenMW/openmw!4507
2025-01-12 17:57:54 +00:00
Dave Corley
bc3c3bbc9c FIX: tooltips lose some of the relevant information if not stored as a QString 2025-01-12 10:07:40 -07:00
Mehdi Yousfi-Monod
ffedd62ea1 Lua: Allow creating arrows and bolt records (#8300) 2025-01-12 18:00:19 +01:00
AnyOldName3
a4bc99db7a Install tools left out on Windows 2025-01-11 19:36:24 +00:00
AnyOldName3
e908f28cb7 c h a n g e l o g 2025-01-11 17:27:12 +00:00
Alexei Kotov
383876a516 Handle weird post-processing chains gracefully (#8295) 2025-01-11 17:03:59 +03:00
psi29a
763e88817a Merge branch 'defaultdevice' into 'master'
Some checks failed
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Ubuntu (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix default audio device switch for PulseAudio backend (#7731)

Closes #7731

See merge request OpenMW/openmw!4505
2025-01-10 14:29:17 +00:00
AnyOldName3
e1208b64e7 Update comments 2025-01-09 17:16:06 +00:00
Alexei Kotov
dd44b2668c Be prepared if someone feels like breaking getDeviceName 2025-01-09 18:34:29 +03:00
AnyOldName3
29af981345 Don't give commas special meaning when matching comments to openmw.cfg values
Previously, comments would be associated with the openmw.cfg line that followed them, but only up to the first comma.
This meant that if you had fallback=thing,otherthing and fallback=thing,thirdthing, comments above the thirdthing line would be moved above the otherthing line, even though both lines would be kept when the file was written out.

This seemed to be an attempt at a feature when cc9cii first implemented the comment preservation system, but it only seems to cause confusion.
2025-01-09 15:21:14 +00:00
psi29a
4c9206485b Merge branch 'write_debug_recast_mesh' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Write debug recast mesh before generating navmesh (#8281)

See merge request OpenMW/openmw!4504
2025-01-08 10:14:18 +00:00
Alexei Kotov
d4f4b3c304 Fix default audio device switch for PulseAudio backend (#7731) 2025-01-08 07:34:15 +03:00
elsid
6464d99134
Throw system error on open file failure 2025-01-06 23:38:32 +01:00
elsid
be1ce81be7
Write debug recast mesh before generating navmesh 2025-01-06 23:38:29 +01:00
psi29a
710aa9ad4f Merge branch 'internationalonoff' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Fix localized checkbox toggling behavior

See merge request OpenMW/openmw!4501
2025-01-06 08:58:56 +00:00
Alexei Kotov
4eb5b24af5 Fix localized checkbox toggling behavior 2025-01-06 09:43:44 +03:00
Bob Tuttle
3ef2084f80 Update install-game-files.rst 2024-07-22 20:19:21 +00:00
192 changed files with 2325 additions and 1376 deletions

View file

@ -1,18 +1,15 @@
Checks: > Checks: >
-*, -*,
boost-*,
portability-*, portability-*,
clang-analyzer-*, clang-analyzer-*,
-clang-analyzer-optin*, -clang-analyzer-optin.*,
-clang-analyzer-cplusplus.NewDeleteLeaks, -clang-analyzer-cplusplus.NewDeleteLeaks,
-clang-analyzer-cplusplus.NewDelete,
-clang-analyzer-core.CallAndMessage, -clang-analyzer-core.CallAndMessage,
-modernize-avoid-bind modernize-avoid-bind,
WarningsAsErrors: > readability-identifier-naming
-*, WarningsAsErrors: '*'
boost-*, HeaderFilterRegex: '(apps|components)/'
portability-*, CheckOptions:
clang-analyzer-*, - key: readability-identifier-naming.ConceptCase
-clang-analyzer-optin*, value: CamelCase
-clang-analyzer-cplusplus.NewDeleteLeaks,
-clang-analyzer-core.CallAndMessage
HeaderFilterRegex: '^(apps|components)'

View file

@ -202,7 +202,11 @@ jobs:
AWS_DEFAULT_REGION: eu-west-3 AWS_DEFAULT_REGION: eu-west-3
if: ${{ env.AWS_ACCESS_KEY_ID != '' && env.AWS_SECRET_ACCESS_KEY != '' && inputs.package }} if: ${{ env.AWS_ACCESS_KEY_ID != '' && env.AWS_SECRET_ACCESS_KEY != '' && inputs.package }}
working-directory: ${{ github.workspace }}/SymStore working-directory: ${{ github.workspace }}/SymStore
run: aws --endpoint-url https://rgw.ctrl-c.liu.se s3 sync --size-only --exclude * --include *.ex_ --include *.dl_ --include *.pd_ . s3://openmw-sym run: |
choco uninstall awscli -y
choco install awscli -y --version=2.22.35
aws --version
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 sync --size-only --exclude * --include *.ex_ --include *.dl_ --include *.pd_ . s3://openmw-sym
- name: Add install directory to PATH - name: Add install directory to PATH
shell: bash shell: bash

View file

@ -512,9 +512,16 @@ Ubuntu_GCC_integration_tests_asan:
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}.dmg"; done - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}.dmg"; done
- | - |
if [[ -n "${AWS_ACCESS_KEY_ID}" ]]; then if [[ -n "${AWS_ACCESS_KEY_ID}" ]]; then
echo "[default]" > ~/.s3cfg
echo "access_key = ${AWS_ACCESS_KEY_ID}" >> ~/.s3cfg
echo "secret_key = ${AWS_SECRET_ACCESS_KEY}" >> ~/.s3cfg
echo "host_base = rgw.ctrl-c.liu.se" >> ~/.s3cfg
echo "host_bucket = %(bucket)s.rgw.ctrl-c.liu.se" >> ~/.s3cfg
echo "use_https = True" >> ~/.s3cfg
artifactDirectory="${CI_PROJECT_NAMESPACE//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_REF_NAME//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_SHORT_SHA//[\"<>|$'\t'\/\\?*]/_}-${CI_JOB_ID//[\"<>|$'\t'\/\\?*]/_}/" artifactDirectory="${CI_PROJECT_NAMESPACE//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_REF_NAME//[\"<>|$'\t'\/\\?*]/_}/${CI_COMMIT_SHORT_SHA//[\"<>|$'\t'\/\\?*]/_}-${CI_JOB_ID//[\"<>|$'\t'\/\\?*]/_}/"
for dmg in *.dmg; do for dmg in *.dmg; do
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "${dmg}" s3://openmw-artifacts/${artifactDirectory} s3cmd put "${dmg}" s3://openmw-artifacts/${artifactDirectory}
done done
fi fi
- ccache -s - ccache -s
@ -540,7 +547,7 @@ macOS14_Xcode15_arm64:
script: script:
- apt-get update - apt-get update
- apt-get install -y curl gcab unzip - apt-get install -y curl gcab unzip
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscli-exe-linux-x86_64.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 - unzip -d awscli-exe-linux-x86_64 awscli-exe-linux-x86_64.zip
- pushd awscli-exe-linux-x86_64 - pushd awscli-exe-linux-x86_64
- ./aws/install - ./aws/install
@ -587,7 +594,7 @@ macOS14_Xcode15_arm64:
- choco install vswhere -y - choco install vswhere -y
- choco install ninja -y - choco install ninja -y
- choco install python -y - choco install python -y
- choco install awscli -y - choco install awscli -y --version=2.22.35
- refreshenv - refreshenv
- | - |
function Make-SafeFileName { function Make-SafeFileName {
@ -621,15 +628,20 @@ macOS14_Xcode15_arm64:
- 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}`nCI_JOB_ID ${CI_JOB_ID}`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}"))/" - $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 - Get-ChildItem -Recurse *.ilk | Remove-Item
- aws --version
- | - |
if (Get-ChildItem -Recurse *.pdb) { 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_${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) { 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_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory}
if(!$?) { Exit $LASTEXITCODE }
} }
Push-Location .. Push-Location ..
..\CI\Store-Symbols.ps1 -SkipCompress ..\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_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
if(!$?) { Exit $LASTEXITCODE }
Pop-Location Pop-Location
Get-ChildItem -Recurse *.pdb | Remove-Item Get-ChildItem -Recurse *.pdb | Remove-Item
} }
@ -637,13 +649,20 @@ macOS14_Xcode15_arm64:
- | - |
if (Test-Path env:AWS_ACCESS_KEY_ID) { 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_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory}
if(!$?) { Exit $LASTEXITCODE }
}
- |
if ($executables) {
foreach ($exe in $executables.Split(',')) {
& .\$exe
if(!$?) { Exit $LASTEXITCODE }
}
} }
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script: after_script:
- Get-Volume - Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache: cache:
key: ninja-2022-v11 key: ninja-2022-v12
paths: paths:
- ccache - ccache
- deps - deps
@ -738,7 +757,7 @@ macOS14_Xcode15_arm64:
- choco install 7zip -y - choco install 7zip -y
- choco install vswhere -y - choco install vswhere -y
- choco install python -y - choco install python -y
- choco install awscli -y - choco install awscli -y --version=2.22.35
- refreshenv - refreshenv
- | - |
function Make-SafeFileName { function Make-SafeFileName {
@ -767,15 +786,20 @@ macOS14_Xcode15_arm64:
- 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}`nCI_JOB_ID ${CI_JOB_ID}`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}"))/" - $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 - Get-ChildItem -Recurse *.ilk | Remove-Item
- aws --version
- | - |
if (Get-ChildItem -Recurse *.pdb) { 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_${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) { 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_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory}
if(!$?) { Exit $LASTEXITCODE }
} }
Push-Location .. Push-Location ..
..\CI\Store-Symbols.ps1 -SkipCompress ..\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_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
if(!$?) { Exit $LASTEXITCODE }
Pop-Location Pop-Location
Get-ChildItem -Recurse *.pdb | Remove-Item Get-ChildItem -Recurse *.pdb | Remove-Item
} }
@ -783,13 +807,20 @@ macOS14_Xcode15_arm64:
- | - |
if (Test-Path env:AWS_ACCESS_KEY_ID) { 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_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory}
if(!$?) { Exit $LASTEXITCODE }
}
- |
if ($executables) {
foreach ($exe in $executables.Split(',')) {
& .\$exe
if(!$?) { Exit $LASTEXITCODE }
}
} }
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script: after_script:
- Get-Volume - Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache: cache:
key: msbuild-2022-v11 key: msbuild-2022-v12
paths: paths:
- deps - deps
- MSVC2022_64/deps/Qt - MSVC2022_64/deps/Qt

View file

@ -35,7 +35,6 @@
Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load
Bug #6025: Subrecords cannot overlap records Bug #6025: Subrecords cannot overlap records
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex
Bug #6097: Level Progress Tooltip Sometimes Not Updated
Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item
Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly
Bug #6190: Unintuitive sun specularity time of day dependence Bug #6190: Unintuitive sun specularity time of day dependence
@ -226,6 +225,13 @@
Bug #8231: AGOP doesn't like NiCollisionSwitch Bug #8231: AGOP doesn't like NiCollisionSwitch
Bug #8237: Non-bipedal creatures should *not* use spellcast equip/unequip animations Bug #8237: Non-bipedal creatures should *not* use spellcast equip/unequip animations
Bug #8252: Plugin dependencies are not required to be loaded Bug #8252: Plugin dependencies are not required to be loaded
Bug #8295: Post-processing chain is case-sensitive
Bug #8299: Crash while smoothing landscape
Bug #8364: Crash when clicking scrollbar without handle (divide by zero)
Bug #8378: Korean bitmap fonts are unusable
Bug #8439: Creatures without models can crash the game
Bug #8441: Freeze when using video main menu replacers
Bug #8462: Crashes when resizing the window on macOS
Feature #1415: Infinite fall failsafe Feature #1415: Infinite fall failsafe
Feature #2566: Handle NAM9 records for manual cell references Feature #2566: Handle NAM9 records for manual cell references
Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking
@ -310,6 +316,8 @@
Feature #8109: Expose commitCrime to Lua API Feature #8109: Expose commitCrime to Lua API
Feature #8130: Launcher: Add the ability to open a selected data directory in the file browser Feature #8130: Launcher: Add the ability to open a selected data directory in the file browser
Feature #8145: Starter spell flag is not exposed Feature #8145: Starter spell flag is not exposed
Feature #8286: Launcher: Preserve semantically identical openmw.cfg
Feature #8287: Launcher: Special handling for comma in openmw.cfg entries is unintuitive and should be removed
Task #5859: User openmw-cs.cfg has comment talking about settings.cfg Task #5859: User openmw-cs.cfg has comment talking about settings.cfg
Task #5896: Do not use deprecated MyGUI properties Task #5896: Do not use deprecated MyGUI properties
Task #6085: Replace boost::filesystem with std::filesystem Task #6085: Replace boost::filesystem with std::filesystem
@ -388,6 +396,7 @@
Bug #6066: Addtopic "return" does not work from within script. No errors thrown Bug #6066: Addtopic "return" does not work from within script. No errors thrown
Bug #6067: ESP loader fails for certain subrecord orders Bug #6067: ESP loader fails for certain subrecord orders
Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends
Bug #6097: Level Progress Tooltip Sometimes Not Updated
Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime
Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
Bug #6109: Crash when playing a custom made menu_background file Bug #6109: Crash when playing a custom made menu_background file

View file

@ -7,7 +7,7 @@ export HOMEBREW_AUTOREMOVE=1
brew tap --repair brew tap --repair
brew update --quiet brew update --quiet
brew install curl xquartz gd fontconfig freetype harfbuzz brotli brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd
command -v ccache >/dev/null 2>&1 || brew install ccache command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake command -v cmake >/dev/null 2>&1 || brew install cmake

View file

@ -38,7 +38,7 @@ fi
if [[ $CI_CLANG_TIDY ]]; then if [[ $CI_CLANG_TIDY ]]; then
CMAKE_CONF_OPTS+=( CMAKE_CONF_OPTS+=(
-DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*" -DCMAKE_CXX_CLANG_TIDY=clang-tidy
-DBUILD_COMPONENTS_TESTS=ON -DBUILD_COMPONENTS_TESTS=ON
-DBUILD_OPENMW_TESTS=ON -DBUILD_OPENMW_TESTS=ON
-DBUILD_OPENCS_TESTS=ON -DBUILD_OPENCS_TESTS=ON

View file

@ -95,7 +95,7 @@ declare -rA GROUPED_DEPS=(
[libasan6]="libasan6" [libasan6]="libasan6"
[android]="binutils build-essential cmake ccache curl unzip git pkg-config" [android]="binutils build-essential cmake ccache curl unzip git pkg-config"
[openmw-clang-format]=" [openmw-clang-format]="
clang-format-14 clang-format-14
git-core git-core
@ -126,10 +126,24 @@ export APT_CACHE_DIR="${PWD}/apt-cache"
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
set -x set -x
mkdir -pv "$APT_CACHE_DIR" mkdir -pv "$APT_CACHE_DIR"
apt-get update -yqq
while true; do
apt-get update -yqq && break
done
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/dev/null apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/dev/null
add-apt-repository -y ppa:openmw/openmw
add-apt-repository -y ppa:openmw/openmw-daily while true; do
add-apt-repository -y ppa:openmw/staging add-apt-repository -y ppa:openmw/openmw && break
done
while true; do
add-apt-repository -y ppa:openmw/openmw-daily && break
done
while true; do
add-apt-repository -y ppa:openmw/staging && break
done
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null
apt list --installed apt list --installed

View file

@ -9,7 +9,7 @@ git checkout FETCH_HEAD
cd .. cd ..
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \ xvfb-run --auto-servernum --server-args='-screen 0 640x480x24x60' \
scripts/integration_tests.py --omw build/install/bin/openmw --workdir integration_tests_output example-suite/ scripts/integration_tests.py --verbose --omw build/install/bin/openmw --workdir integration_tests_output example-suite/
ls integration_tests_output/*.osg_stats.log | while read v; do ls integration_tests_output/*.osg_stats.log | while read v; do
scripts/osg_stats.py --stats '.*' --regexp_match < "${v}" scripts/osg_stats.py --stats '.*' --regexp_match < "${v}"

View file

@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 69) set(OPENMW_LUA_API_REVISION 72)
set(OPENMW_POSTPROCESSING_API_REVISION 2) set(OPENMW_POSTPROCESSING_API_REVISION 2)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")
@ -860,7 +860,6 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
set(BU_CHMOD_BUNDLE_ITEMS ON) set(BU_CHMOD_BUNDLE_ITEMS ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH})
include(BundleUtilities) include(BundleUtilities)
cmake_minimum_required(VERSION 3.1)
" COMPONENT Runtime) " COMPONENT Runtime)
set(ABSOLUTE_PLUGINS "") set(ABSOLUTE_PLUGINS "")

View file

@ -179,7 +179,7 @@ namespace
generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random); generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random);
std::size_t n = 0; std::size_t n = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
const auto& key = keys[n++ % keys.size()]; const auto& key = keys[n++ % keys.size()];
auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh);

View file

@ -104,7 +104,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random); std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random);
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serialize()); benchmark::DoNotOptimize(refIds[i].serialize());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -118,7 +118,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serialize(); }); = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serialize(); });
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserialize(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserialize(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())
@ -131,7 +131,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random); std::vector<ESM::RefId> refIds = generateStringRefIds(state.range(0), random);
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serializeText()); benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -145,7 +145,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); }); = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())
@ -158,7 +158,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateGeneratedRefIds(random); std::vector<ESM::RefId> refIds = generateGeneratedRefIds(random);
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serializeText()); benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -172,7 +172,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); = generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())
@ -185,7 +185,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateIndexRefIds(random); std::vector<ESM::RefId> refIds = generateIndexRefIds(random);
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serializeText()); benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -199,7 +199,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); = generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())
@ -212,7 +212,7 @@ namespace
std::minstd_rand random; std::minstd_rand random;
std::vector<ESM::RefId> refIds = generateESM3ExteriorCellRefIds(random); std::vector<ESM::RefId> refIds = generateESM3ExteriorCellRefIds(random);
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(refIds[i].serializeText()); benchmark::DoNotOptimize(refIds[i].serializeText());
if (++i >= refIds.size()) if (++i >= refIds.size())
@ -226,7 +226,7 @@ namespace
std::vector<std::string> serializedRefIds std::vector<std::string> serializedRefIds
= generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); = generateSerializedESM3ExteriorCellRefIds(random, [](ESM::RefId v) { return v.serializeText(); });
std::size_t i = 0; std::size_t i = 0;
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i]));
if (++i >= serializedRefIds.size()) if (++i >= serializedRefIds.size())

View file

@ -9,7 +9,7 @@ namespace
{ {
void settingsManager(benchmark::State& state) void settingsManager(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::Manager::getFloat("sky blending start", "Fog")); benchmark::DoNotOptimize(Settings::Manager::getFloat("sky blending start", "Fog"));
} }
@ -17,7 +17,7 @@ namespace
void settingsManager2(benchmark::State& state) void settingsManager2(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera")); benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera"));
benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing")); benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing"));
@ -26,7 +26,7 @@ namespace
void settingsManager3(benchmark::State& state) void settingsManager3(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera")); benchmark::DoNotOptimize(Settings::Manager::getFloat("near clip", "Camera"));
benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing")); benchmark::DoNotOptimize(Settings::Manager::getBool("transparent postpass", "Post Processing"));
@ -36,7 +36,7 @@ namespace
void localStatic(benchmark::State& state) void localStatic(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
static float v = Settings::Manager::getFloat("sky blending start", "Fog"); static float v = Settings::Manager::getFloat("sky blending start", "Fog");
benchmark::DoNotOptimize(v); benchmark::DoNotOptimize(v);
@ -45,7 +45,7 @@ namespace
void localStatic2(benchmark::State& state) void localStatic2(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
static float v1 = Settings::Manager::getFloat("near clip", "Camera"); static float v1 = Settings::Manager::getFloat("near clip", "Camera");
static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
@ -56,7 +56,7 @@ namespace
void localStatic3(benchmark::State& state) void localStatic3(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
static float v1 = Settings::Manager::getFloat("near clip", "Camera"); static float v1 = Settings::Manager::getFloat("near clip", "Camera");
static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing");
@ -69,7 +69,7 @@ namespace
void settingsStorage(benchmark::State& state) void settingsStorage(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
float v = Settings::fog().mSkyBlendingStart.get(); float v = Settings::fog().mSkyBlendingStart.get();
benchmark::DoNotOptimize(v); benchmark::DoNotOptimize(v);
@ -78,7 +78,7 @@ namespace
void settingsStorage2(benchmark::State& state) void settingsStorage2(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
bool v1 = Settings::postProcessing().mTransparentPostpass.get(); bool v1 = Settings::postProcessing().mTransparentPostpass.get();
float v2 = Settings::camera().mNearClip.get(); float v2 = Settings::camera().mNearClip.get();
@ -89,7 +89,7 @@ namespace
void settingsStorage3(benchmark::State& state) void settingsStorage3(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
bool v1 = Settings::postProcessing().mTransparentPostpass.get(); bool v1 = Settings::postProcessing().mTransparentPostpass.get();
float v2 = Settings::camera().mNearClip.get(); float v2 = Settings::camera().mNearClip.get();
@ -102,7 +102,7 @@ namespace
void settingsStorageGet(benchmark::State& state) void settingsStorageGet(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::get<float>("Fog", "sky blending start")); benchmark::DoNotOptimize(Settings::get<float>("Fog", "sky blending start"));
} }
@ -110,7 +110,7 @@ namespace
void settingsStorageGet2(benchmark::State& state) void settingsStorageGet2(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass")); benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass"));
benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip")); benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip"));
@ -119,7 +119,7 @@ namespace
void settingsStorageGet3(benchmark::State& state) void settingsStorageGet3(benchmark::State& state)
{ {
for (auto _ : state) for ([[maybe_unused]] auto _ : state)
{ {
benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass")); benchmark::DoNotOptimize(Settings::get<bool>("Post Processing", "transparent postpass"));
benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip")); benchmark::DoNotOptimize(Settings::get<float>("Camera", "near clip"));

View file

@ -18,6 +18,10 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(bsatool gcov) target_link_libraries(bsatool gcov)
endif() endif()
if (WIN32)
install(TARGETS bsatool RUNTIME DESTINATION ".")
endif()
if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(bsatool PRIVATE target_precompile_headers(bsatool PRIVATE
<filesystem> <filesystem>

View file

@ -53,8 +53,6 @@ namespace
bpo::options_description makeOptionsDescription() bpo::options_description makeOptionsDescription()
{ {
using Fallback::FallbackMap;
bpo::options_description result; bpo::options_description result;
auto addOption = result.add_options(); auto addOption = result.add_options();
addOption("help", "print help message"); addOption("help", "print help message");
@ -87,7 +85,8 @@ namespace
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
"\n\twin1252 - Western European (Latin) alphabet, used by default"); "\n\twin1252 - Western European (Latin) alphabet, used by default");
addOption("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")->multitoken()->composing(), addOption("fallback",
bpo::value<Fallback::FallbackMap>()->default_value(Fallback::FallbackMap(), "")->multitoken()->composing(),
"fallback values"); "fallback values");
Files::ConfigurationManager::addCommonOptions(result); Files::ConfigurationManager::addCommonOptions(result);

View file

@ -5,7 +5,9 @@
#include <components/detournavigator/makenavmesh.hpp> #include <components/detournavigator/makenavmesh.hpp>
#include <components/detournavigator/navmeshdbutils.hpp> #include <components/detournavigator/navmeshdbutils.hpp>
#include <components/detournavigator/serialization.hpp> #include <components/detournavigator/serialization.hpp>
#include <components/files/conversion.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include <components/testing/util.hpp>
#include <BulletCollision/CollisionShapes/btBoxShape.h> #include <BulletCollision/CollisionShapes/btBoxShape.h>
@ -372,6 +374,106 @@ namespace
} }
} }
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_write_debug_recast_mesh)
{
mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager);
mSettings.mEnableWriteRecastMeshToFile = true;
const std::filesystem::path dir = TestingOpenMW::outputDirPath("DetourNavigatorAsyncNavMeshUpdaterTest");
mSettings.mRecastMeshPathPrefix = Files::pathToUnicodeString(dir) + "/";
Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } };
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_TRUE(std::filesystem::exists(dir / "0.0.recastmesh.obj"));
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_write_debug_recast_mesh_with_revision)
{
mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager);
mSettings.mEnableWriteRecastMeshToFile = true;
mSettings.mEnableRecastMeshFileNameRevision = true;
const std::filesystem::path dir = TestingOpenMW::outputDirPath("DetourNavigatorAsyncNavMeshUpdaterTest");
mSettings.mRecastMeshPathPrefix = Files::pathToUnicodeString(dir) + "/";
Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } };
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_TRUE(std::filesystem::exists(dir / "0.0.recastmesh.1.2.obj"));
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, writing_recast_mesh_to_absent_file_should_not_fail_tile_generation)
{
mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager);
mSettings.mEnableWriteRecastMeshToFile = true;
const std::filesystem::path dir = TestingOpenMW::outputDir() / "absent";
mSettings.mRecastMeshPathPrefix = Files::pathToUnicodeString(dir) + "/";
Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } };
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u);
EXPECT_FALSE(std::filesystem::exists(dir));
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_write_debug_navmesh)
{
mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager);
mSettings.mEnableWriteNavMeshToFile = true;
const std::filesystem::path dir = TestingOpenMW::outputDirPath("DetourNavigatorAsyncNavMeshUpdaterTest");
mSettings.mNavMeshPathPrefix = Files::pathToUnicodeString(dir) + "/";
Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } };
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_TRUE(std::filesystem::exists(dir / "all_tiles_navmesh.bin"));
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_write_debug_navmesh_with_revision)
{
mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager);
mSettings.mEnableWriteNavMeshToFile = true;
mSettings.mEnableNavMeshFileNameRevision = true;
const std::filesystem::path dir = TestingOpenMW::outputDirPath("DetourNavigatorAsyncNavMeshUpdaterTest");
mSettings.mNavMeshPathPrefix = Files::pathToUnicodeString(dir) + "/";
Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } };
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_TRUE(std::filesystem::exists(dir / "all_tiles_navmesh.1.1.bin"));
}
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, writing_navmesh_to_absent_file_should_not_fail_tile_generation)
{
mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager);
mSettings.mEnableWriteNavMeshToFile = true;
const std::filesystem::path dir = TestingOpenMW::outputDir() / "absent";
mSettings.mNavMeshPathPrefix = Files::pathToUnicodeString(dir) + "/";
Log(Debug::Verbose) << mSettings.mRecastMeshPathPrefix;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(1, mSettings);
const std::map<TilePosition, ChangeType> changedTiles{ { TilePosition{ 0, 0 }, ChangeType::add } };
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u);
EXPECT_FALSE(std::filesystem::exists(dir));
}
struct DetourNavigatorSpatialJobQueueTest : Test struct DetourNavigatorSpatialJobQueueTest : Test
{ {
const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) }; const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) };

View file

@ -88,4 +88,31 @@ namespace
EXPECT_EQ(reader->getFileOffset(), sInitialOffset); EXPECT_EQ(reader->getFileOffset(), sInitialOffset);
} }
} }
TEST_F(ESM3ReadersCacheWithContentFile, CachedSizeAndName)
{
ESM::ReadersCache readers(2);
{
readers.get(0)->openRaw(std::make_unique<std::istringstream>("123"), "closed0.omwaddon");
readers.get(1)->openRaw(std::make_unique<std::istringstream>("12345"), "closed1.omwaddon");
readers.get(2)->openRaw(std::make_unique<std::istringstream>("1234567"), "free.omwaddon");
}
auto busy = readers.get(3);
busy->openRaw(std::make_unique<std::istringstream>("123456789"), "busy.omwaddon");
EXPECT_EQ(readers.getFileSize(0), 3);
EXPECT_EQ(readers.getName(0), "closed0.omwaddon");
EXPECT_EQ(readers.getFileSize(1), 5);
EXPECT_EQ(readers.getName(1), "closed1.omwaddon");
EXPECT_EQ(readers.getFileSize(2), 7);
EXPECT_EQ(readers.getName(2), "free.omwaddon");
EXPECT_EQ(readers.getFileSize(3), 9);
EXPECT_EQ(readers.getName(3), "busy.omwaddon");
// not-yet-seen indices give zero for their size
EXPECT_EQ(readers.getFileSize(4), 0);
}
} }

View file

@ -30,7 +30,7 @@ namespace
TEST(FilesGetHash, shouldClearErrors) TEST(FilesGetHash, shouldClearErrors)
{ {
const auto fileName = temporaryFilePath("fileName"); const auto fileName = outputFilePath("fileName");
std::string content; std::string content;
std::fill_n(std::back_inserter(content), 1, 'a'); std::fill_n(std::back_inserter(content), 1, 'a');
std::istringstream stream(content); std::istringstream stream(content);
@ -41,7 +41,7 @@ namespace
TEST_P(FilesGetHash, shouldReturnHashForStringStream) TEST_P(FilesGetHash, shouldReturnHashForStringStream)
{ {
const auto fileName = temporaryFilePath("fileName"); const auto fileName = outputFilePath("fileName");
std::string content; std::string content;
std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); std::fill_n(std::back_inserter(content), GetParam().mSize, 'a');
std::istringstream stream(content); std::istringstream stream(content);

View file

@ -38,10 +38,10 @@ namespace
sol::state lua; sol::state lua;
LuaUtil::InputAction::Registry registry; LuaUtil::InputAction::Registry registry;
LuaUtil::InputAction::Info a({ "a", LuaUtil::InputAction::Type::Boolean, "test", "a_name", "a_description", LuaUtil::InputAction::Info a({ "a", LuaUtil::InputAction::Type::Boolean, "test", "a_name", "a_description",
sol::make_object(lua, false) }); sol::make_object(lua, false), false });
registry.insert(a); registry.insert(a);
LuaUtil::InputAction::Info b({ "b", LuaUtil::InputAction::Type::Boolean, "test", "b_name", "b_description", LuaUtil::InputAction::Info b({ "b", LuaUtil::InputAction::Type::Boolean, "test", "b_name", "b_description",
sol::make_object(lua, false) }); sol::make_object(lua, false), false });
registry.insert(b); registry.insert(b);
LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) }); LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) });
LuaUtil::Callback bindBToA( LuaUtil::Callback bindBToA(

View file

@ -2,6 +2,7 @@
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/settings/parser.hpp> #include <components/settings/parser.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/testing/util.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -24,5 +25,9 @@ int main(int argc, char** argv)
Settings::StaticValues::init(); Settings::StaticValues::init();
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
const int result = RUN_ALL_TESTS();
if (result == 0)
std::filesystem::remove_all(TestingOpenMW::outputDir());
return result;
} }

View file

@ -51,7 +51,7 @@ namespace Misc
const std::pair<osg::Quat, osg::Vec3f> eulerAnglesXZQuat[] = { const std::pair<osg::Quat, osg::Vec3f> eulerAnglesXZQuat[] = {
{ {
osg::Quat(1, 0, 0, 0), osg::Quat(1, 0, 0, 0),
osg::Vec3f(0, 0, osg::PI), osg::Vec3f(0, 0, osg::PIf),
}, },
{ {
osg::Quat(0, 1, 0, 0), osg::Quat(0, 1, 0, 0),
@ -59,7 +59,7 @@ namespace Misc
}, },
{ {
osg::Quat(0, 0, 1, 0), osg::Quat(0, 0, 1, 0),
osg::Vec3f(0, 0, osg::PI), osg::Vec3f(0, 0, osg::PIf),
}, },
{ {
osg::Quat(0, 0, 0, 1), osg::Quat(0, 0, 0, 1),
@ -128,15 +128,15 @@ namespace Misc
const std::pair<osg::Quat, osg::Vec3f> eulerAnglesZYXQuat[] = { const std::pair<osg::Quat, osg::Vec3f> eulerAnglesZYXQuat[] = {
{ {
osg::Quat(1, 0, 0, 0), osg::Quat(1, 0, 0, 0),
osg::Vec3f(osg::PI, 0, 0), osg::Vec3f(osg::PIf, 0, 0),
}, },
{ {
osg::Quat(0, 1, 0, 0), osg::Quat(0, 1, 0, 0),
osg::Vec3f(osg::PI, 0, osg::PI), osg::Vec3f(osg::PIf, 0, osg::PIf),
}, },
{ {
osg::Quat(0, 0, 1, 0), osg::Quat(0, 0, 1, 0),
osg::Vec3f(0, 0, osg::PI), osg::Vec3f(0, 0, osg::PIf),
}, },
{ {
osg::Quat(0, 0, 0, 1), osg::Quat(0, 0, 0, 1),

View file

@ -16,7 +16,7 @@ namespace
ShaderManager mManager; ShaderManager mManager;
ShaderManager::DefineMap mDefines; ShaderManager::DefineMap mDefines;
ShaderManagerTest() { mManager.setShaderPath("tests_output"); } ShaderManagerTest() { mManager.setShaderPath(TestingOpenMW::outputDir()); }
template <class F> template <class F>
void withShaderFile(const std::string& content, F&& f) void withShaderFile(const std::string& content, F&& f)

View file

@ -25,6 +25,10 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(esmtool gcov) target_link_libraries(esmtool gcov)
endif() endif()
if (WIN32)
install(TARGETS esmtool RUNTIME DESTINATION ".")
endif()
if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(esmtool PRIVATE target_precompile_headers(esmtool PRIVATE
<fstream> <fstream>

View file

@ -8,6 +8,7 @@
#include <QList> #include <QList>
#include <QMessageBox> #include <QMessageBox>
#include <QPair> #include <QPair>
#include <QProgressDialog>
#include <QPushButton> #include <QPushButton>
#include <algorithm> #include <algorithm>
@ -351,9 +352,17 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
if (!resourcesVfs.isEmpty()) if (!resourcesVfs.isEmpty())
directories.insert(0, { resourcesVfs }); directories.insert(0, { resourcesVfs });
QIcon containsDataIcon(":/images/openmw-plugin.png");
QProgressDialog progressBar("Adding data directories", {}, 0, directories.count(), this);
progressBar.setWindowModality(Qt::WindowModal);
progressBar.setValue(0);
std::unordered_set<QString> visitedDirectories; std::unordered_set<QString> visitedDirectories;
for (const Config::SettingValue& currentDir : directories) for (const Config::SettingValue& currentDir : directories)
{ {
progressBar.setValue(progressBar.value() + 1);
if (!visitedDirectories.insert(currentDir.value).second) if (!visitedDirectories.insert(currentDir.value).second)
continue; continue;
@ -402,7 +411,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
// Add a "data file" icon if the directory contains a content file // Add a "data file" icon if the directory contains a content file
if (mSelector->containsDataFiles(currentDir.value)) if (mSelector->containsDataFiles(currentDir.value))
{ {
item->setIcon(QIcon(":/images/openmw-plugin.png")); item->setIcon(containsDataIcon);
tooltip << tr("Contains content file(s)"); tooltip << tr("Contains content file(s)");
} }
@ -765,7 +774,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
return; return;
QString rootPath = QFileDialog::getExistingDirectory( QString rootPath = QFileDialog::getExistingDirectory(
this, tr("Select Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly); this, tr("Select Directory"), {}, QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly);
if (rootPath.isEmpty()) if (rootPath.isEmpty())
return; return;

View file

@ -1,20 +1,16 @@
set(NAVMESHTOOL set(NAVMESHTOOL_LIB
worldspacedata.cpp worldspacedata.cpp
navmesh.cpp navmesh.cpp
main.cpp
) )
source_group(apps\\navmeshtool FILES ${NAVMESHTOOL})
add_library(openmw-navmeshtool-lib STATIC source_group(apps\\navmeshtool FILES ${NAVMESHTOOL_LIB} main.cpp)
${NAVMESHTOOL}
) add_library(openmw-navmeshtool-lib STATIC ${NAVMESHTOOL_LIB})
if (ANDROID) if (ANDROID)
add_library(openmw-navmeshtool SHARED add_library(openmw-navmeshtool SHARED main.cpp)
main.cpp
)
else() else()
openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL}) openmw_add_executable(openmw-navmeshtool main.cpp)
endif() endif()
target_link_libraries(openmw-navmeshtool openmw-navmeshtool-lib) target_link_libraries(openmw-navmeshtool openmw-navmeshtool-lib)

View file

@ -62,8 +62,6 @@ namespace NavMeshTool
bpo::options_description makeOptionsDescription() bpo::options_description makeOptionsDescription()
{ {
using Fallback::FallbackMap;
bpo::options_description result; bpo::options_description result;
auto addOption = result.add_options(); auto addOption = result.add_options();
addOption("help", "print help message"); addOption("help", "print help message");
@ -225,7 +223,8 @@ namespace NavMeshTool
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::RecastGlobalAllocator::init();
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); DetourNavigator::Settings navigatorSettings
= DetourNavigator::makeSettingsFromSettingsManager(Debug::getRecastMaxLogLevel());
navigatorSettings.mRecast.mSwimHeightScale navigatorSettings.mRecast.mSwimHeightScale
= EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat(); = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat();

View file

@ -17,6 +17,10 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(niftest gcov) target_link_libraries(niftest gcov)
endif() endif()
if (WIN32)
install(TARGETS niftest RUNTIME DESTINATION ".")
endif()
if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC)
target_precompile_headers(niftest PRIVATE <filesystem>) target_precompile_headers(niftest PRIVATE <filesystem>)
endif() endif()

View file

@ -38,8 +38,6 @@
#include "view/doc/viewmanager.hpp" #include "view/doc/viewmanager.hpp"
using namespace Fallback;
CS::Editor::Editor(int argc, char** argv) CS::Editor::Editor(int argc, char** argv)
: mConfigVariables(readConfiguration()) : mConfigVariables(readConfiguration())
, mSettingsState(mCfgMgr) , mSettingsState(mCfgMgr)
@ -124,7 +122,10 @@ boost::program_options::variables_map CS::Editor::readConfiguration()
->default_value(std::vector<std::string>(), "fallback-archive") ->default_value(std::vector<std::string>(), "fallback-archive")
->multitoken()); ->multitoken());
addOption("fallback", addOption("fallback",
boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "")->multitoken()->composing(), boost::program_options::value<Fallback::FallbackMap>()
->default_value(Fallback::FallbackMap(), "")
->multitoken()
->composing(),
"fallback values"); "fallback values");
Files::ConfigurationManager::addCommonOptions(desc); Files::ConfigurationManager::addCommonOptions(desc);
@ -141,7 +142,7 @@ std::pair<Files::PathContainer, std::vector<std::string>> CS::Editor::readConfig
{ {
boost::program_options::variables_map& variables = mConfigVariables; boost::program_options::variables_map& variables = mConfigVariables;
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap); Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
mEncodingName = variables["encoding"].as<std::string>(); mEncodingName = variables["encoding"].as<std::string>();
mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName));

View file

@ -1164,23 +1164,30 @@ void CSVDoc::View::onRequestFocus(const std::string& id)
QScreen* CSVDoc::View::getWidgetScreen(const QPoint& position) QScreen* CSVDoc::View::getWidgetScreen(const QPoint& position)
{ {
QScreen* screen = QApplication::screenAt(position); QScreen* screen = QApplication::screenAt(position);
if (screen == nullptr) if (screen)
return screen;
const QList<QScreen*> screens = QApplication::screens();
if (screens.isEmpty())
throw std::runtime_error("No screens available");
int closestDistance = std::numeric_limits<int>::max();
for (QScreen* candidate : screens)
{ {
QPoint clampedPosition = position; const QRect geometry = candidate->geometry();
const int dx = position.x() - std::clamp(position.x(), geometry.left(), geometry.right());
const int dy = position.y() - std::clamp(position.y(), geometry.top(), geometry.bottom());
const int distance = dx * dx + dy * dy;
// If we failed to find the screen, if (distance < closestDistance)
// clamp negative positions and try again {
if (clampedPosition.x() <= 0) closestDistance = distance;
clampedPosition.setX(0); screen = candidate;
if (clampedPosition.y() <= 0) }
clampedPosition.setY(0);
screen = QApplication::screenAt(clampedPosition);
} }
if (screen == nullptr) if (screen == nullptr)
throw std::runtime_error( screen = screens.first();
Misc::StringUtils::format("Can not detect the screen for position [%d, %d]", position.x(), position.y()));
return screen; return screen;
} }

View file

@ -60,24 +60,6 @@
#include "pagedworldspacewidget.hpp" #include "pagedworldspacewidget.hpp"
#include "worldspacewidget.hpp" #include "worldspacewidget.hpp"
namespace
{
constexpr std::string_view sInstanceModeTooltip = R"(
Instance editing
<ul><li>Use {scene-select-primary} and {scene-select-secondary} to select and unselect instances</li>
<li>Use {scene-edit-primary} to manipulate instances</li>
<li>Use {scene-select-tertiary} to select a reference object and then {scene-edit-secondary} to snap
selection relative to the reference object</li>
<li>Use {scene-submode-move}, {scene-submode-rotate}, {scene-submode-scale} to change to move, rotate, and
scale modes respectively</li>
<li>Use {scene-axis-x}, {scene-axis-y}, and {scene-axis-z} to lock changes to X, Y, and Z axes
respectively</li>
<li>Use {scene-delete} to delete currently selected objects</li>
<li>Use {scene-duplicate} to duplicate instances</li>
<li>Use {scene-instance-drop} to drop instances</li></ul>
)";
}
int CSVRender::InstanceMode::getSubModeFromId(const std::string& id) const int CSVRender::InstanceMode::getSubModeFromId(const std::string& id) const
{ {
return id == "move" ? 0 : (id == "rotate" ? 1 : 2); return id == "move" ? 0 : (id == "rotate" ? 1 : 2);
@ -312,10 +294,28 @@ void CSVRender::InstanceMode::setDragAxis(const char axis)
mDragAxis = newDragAxis; mDragAxis = newDragAxis;
} }
QString CSVRender::InstanceMode::getTooltip()
{
return QString(
"Instance editing"
"<ul><li>Use {scene-select-primary} and {scene-select-secondary} to select and unselect instances</li>"
"<li>Use {scene-edit-primary} to manipulate instances</li>"
"<li>Use {scene-select-tertiary} to select a reference object and then {scene-edit-secondary} to snap "
"selection relative to the reference object</li>"
"<li>Use {scene-submode-move}, {scene-submode-rotate}, {scene-submode-scale} to change to move, "
"rotate, and "
"scale modes respectively</li>"
"<li>Use {scene-axis-x}, {scene-axis-y}, and {scene-axis-z} to lock changes to X, Y, and Z axes "
"respectively</li>"
"<li>Use {scene-delete} to delete currently selected objects</li>"
"<li>Use {scene-duplicate} to duplicate instances</li>"
"<li>Use {scene-instance-drop} to drop instances</li></ul>");
}
CSVRender::InstanceMode::InstanceMode( CSVRender::InstanceMode::InstanceMode(
WorldspaceWidget* worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget* parent) WorldspaceWidget* worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget* parent)
: EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-instance"), : EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-instance"),
Mask_Reference | Mask_Terrain, sInstanceModeTooltip.data(), parent) Mask_Reference | Mask_Terrain, getTooltip(), parent)
, mSubMode(nullptr) , mSubMode(nullptr)
, mSubModeId("move") , mSubModeId("move")
, mSelectionMode(nullptr) , mSelectionMode(nullptr)

View file

@ -53,6 +53,7 @@ namespace CSVRender
std::vector<osg::Vec3> mObjectsAtDragStart; std::vector<osg::Vec3> mObjectsAtDragStart;
CSMWorld::IdTable* mSelectionGroups; CSMWorld::IdTable* mSelectionGroups;
QString getTooltip();
int getSubModeFromId(const std::string& id) const; int getSubModeFromId(const std::string& id) const;
osg::Vec3 quatToEuler(const osg::Quat& quat) const; osg::Vec3 quatToEuler(const osg::Quat& quat) const;

View file

@ -761,18 +761,17 @@ void CSVRender::TerrainShapeMode::smoothHeight(
// this = this Cell // this = this Cell
// left = x - 1, up = y - 1, right = x + 1, down = y + 1 // left = x - 1, up = y - 1, right = x + 1, down = y + 1
// Altered = transient edit (in current edited) // Altered = transient edit (in current edited)
float thisAlteredHeight = 0.0f;
if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) != nullptr)
thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY);
float thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; float thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX];
float leftHeight = 0.0f; float* thisAlteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY);
float leftAlteredHeight = 0.0f; float thisAlteredHeight = thisAlteredHeightPtr != nullptr ? *thisAlteredHeightPtr : 0.f;
float upAlteredHeight = 0.0f; float leftHeight = thisHeight;
float rightHeight = 0.0f; float leftAlteredHeight = thisAlteredHeight;
float rightAlteredHeight = 0.0f; float rightHeight = thisHeight;
float downHeight = 0.0f; float rightAlteredHeight = thisAlteredHeight;
float downAlteredHeight = 0.0f; float downHeight = thisHeight;
float upHeight = 0.0f; float downAlteredHeight = thisAlteredHeight;
float upHeight = thisHeight;
float upAlteredHeight = thisAlteredHeight;
if (allowLandShapeEditing(cellId)) if (allowLandShapeEditing(cellId))
{ {
@ -780,70 +779,80 @@ void CSVRender::TerrainShapeMode::smoothHeight(
if (inCellX == 0) if (inCellX == 0)
{ {
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY());
const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer if (isLandLoaded(cellId))
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) {
.value<CSMWorld::LandHeightsColumn::DataType>(); const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer
leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; = landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2)) .value<CSMWorld::LandHeightsColumn::DataType>();
leftAlteredHeight leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)];
= *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); float* alteredHeightPtr
= paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY);
leftAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
}
} }
if (inCellY == 0) if (inCellY == 0)
{ {
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1);
const CSMWorld::LandHeightsColumn::DataType landUpShapePointer if (isLandLoaded(cellId))
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) {
.value<CSMWorld::LandHeightsColumn::DataType>(); const CSMWorld::LandHeightsColumn::DataType landUpShapePointer
upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; = landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) .value<CSMWorld::LandHeightsColumn::DataType>();
upAlteredHeight upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX];
= *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); float* alteredHeightPtr
= paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2);
upAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
}
} }
if (inCellX > 0) if (inCellX > 0)
{ {
leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1];
leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY);
leftAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
} }
if (inCellY > 0) if (inCellY > 0)
{ {
upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX];
upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1);
upAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
} }
if (inCellX == ESM::Land::LAND_SIZE - 1) if (inCellX == ESM::Land::LAND_SIZE - 1)
{ {
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY());
const CSMWorld::LandHeightsColumn::DataType landRightShapePointer if (isLandLoaded(cellId))
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>();
rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1];
if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY))
{ {
rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); const CSMWorld::LandHeightsColumn::DataType landRightShapePointer
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>();
rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1];
float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY);
rightAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
} }
} }
if (inCellY == ESM::Land::LAND_SIZE - 1) if (inCellY == ESM::Land::LAND_SIZE - 1)
{ {
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1);
const CSMWorld::LandHeightsColumn::DataType landDownShapePointer if (isLandLoaded(cellId))
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>();
downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX];
if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1))
{ {
downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); const CSMWorld::LandHeightsColumn::DataType landDownShapePointer
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>();
downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX];
float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1);
downAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
} }
} }
if (inCellX < ESM::Land::LAND_SIZE - 1) if (inCellX < ESM::Land::LAND_SIZE - 1)
{ {
rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1];
if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY);
rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); rightAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
} }
if (inCellY < ESM::Land::LAND_SIZE - 1) if (inCellY < ESM::Land::LAND_SIZE - 1)
{ {
downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX];
if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) float* alteredHeightPtr = paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1);
downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); downAlteredHeight = alteredHeightPtr != nullptr ? *alteredHeightPtr : 0.f;
} }
float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight

View file

@ -1,4 +1,5 @@
#include <components/debug/debugging.hpp> #include <components/debug/debugging.hpp>
#include <components/testing/util.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -7,5 +8,9 @@ int main(int argc, char* argv[])
Log::sMinDebugLevel = Debug::getDebugLevel(); Log::sMinDebugLevel = Debug::getDebugLevel();
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
const int result = RUN_ALL_TESTS();
if (result == 0)
std::filesystem::remove_all(TestingOpenMW::outputDir());
return result;
} }

View file

@ -373,11 +373,15 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mScriptConsoleMode(false) , mScriptConsoleMode(false)
, mActivationDistanceOverride(-1) , mActivationDistanceOverride(-1)
, mGrab(true) , mGrab(true)
, mExportFonts(false)
, mRandomSeed(0) , mRandomSeed(0)
, mNewGame(false) , mNewGame(false)
, mCfgMgr(configurationManager) , mCfgMgr(configurationManager)
, mGlMaxTextureImageUnits(0) , mGlMaxTextureImageUnits(0)
{ {
#if SDL_VERSION_ATLEAST(2, 24, 0)
SDL_SetHint(SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, "1");
#endif
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads
Uint32 flags Uint32 flags
@ -807,7 +811,7 @@ void OMW::Engine::prepareEngine()
rootNode->addChild(guiRoot); rootNode->addChild(guiRoot);
mWindowManager = std::make_unique<MWGui::WindowManager>(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWindowManager = std::make_unique<MWGui::WindowManager>(mWindow, mViewer, guiRoot, mResourceSystem.get(),
mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts,
Version::getOpenmwVersionDescription(), shadersSupported, mCfgMgr); Version::getOpenmwVersionDescription(), shadersSupported, mCfgMgr);
mEnvironment.setWindowManager(*mWindowManager); mEnvironment.setWindowManager(*mWindowManager);
@ -847,7 +851,7 @@ void OMW::Engine::prepareEngine()
} }
listener->loadingOff(); listener->loadingOff();
mWorld->init(mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue); mWorld->init(mMaxRecastLogLevel, mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue);
mEnvironment.setWorldScene(mWorld->getWorldScene()); mEnvironment.setWorldScene(mWorld->getWorldScene());
mWorld->setupPlayer(); mWorld->setupPlayer();
mWorld->setRandomSeed(mRandomSeed); mWorld->setRandomSeed(mRandomSeed);
@ -1109,6 +1113,11 @@ void OMW::Engine::setWarningsMode(int mode)
mWarningsMode = mode; mWarningsMode = mode;
} }
void OMW::Engine::enableFontExport(bool exportFonts)
{
mExportFonts = exportFonts;
}
void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame) void OMW::Engine::setSaveGameFile(const std::filesystem::path& savegame)
{ {
mSaveGameFile = savegame; mSaveGameFile = savegame;

View file

@ -4,6 +4,7 @@
#include <filesystem> #include <filesystem>
#include <components/compiler/extensions.hpp> #include <components/compiler/extensions.hpp>
#include <components/debug/debuglog.hpp>
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
#include <components/files/collections.hpp> #include <components/files/collections.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -171,7 +172,9 @@ namespace OMW
// Grab mouse? // Grab mouse?
bool mGrab; bool mGrab;
bool mExportFonts;
unsigned int mRandomSeed; unsigned int mRandomSeed;
Debug::Level mMaxRecastLogLevel = Debug::Error;
Compiler::Extensions mExtensions; Compiler::Extensions mExtensions;
std::unique_ptr<Compiler::Context> mScriptContext; std::unique_ptr<Compiler::Context> mScriptContext;
@ -180,6 +183,9 @@ namespace OMW
Translation::Storage mTranslationDataStorage; Translation::Storage mTranslationDataStorage;
bool mNewGame; bool mNewGame;
Files::ConfigurationManager& mCfgMgr;
int mGlMaxTextureImageUnits;
// not implemented // not implemented
Engine(const Engine&); Engine(const Engine&);
Engine& operator=(const Engine&); Engine& operator=(const Engine&);
@ -251,14 +257,14 @@ namespace OMW
void setWarningsMode(int mode); void setWarningsMode(int mode);
void enableFontExport(bool exportFonts);
/// Set the save game file to load after initialising the engine. /// Set the save game file to load after initialising the engine.
void setSaveGameFile(const std::filesystem::path& savegame); void setSaveGameFile(const std::filesystem::path& savegame);
void setRandomSeed(unsigned int seed); void setRandomSeed(unsigned int seed);
private: void setRecastMaxLogLevel(Debug::Level value) { mMaxRecastLogLevel = value; }
Files::ConfigurationManager& mCfgMgr;
int mGlMaxTextureImageUnits;
}; };
} }

View file

@ -28,8 +28,6 @@ extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x
#include <unistd.h> #include <unistd.h>
#endif #endif
using namespace Fallback;
/** /**
* \brief Parses application command line and calls \ref Cfg::ConfigurationManager * \brief Parses application command line and calls \ref Cfg::ConfigurationManager
* to parse configuration files. * to parse configuration files.
@ -152,9 +150,10 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati
engine.setSaveGameFile(variables["load-savegame"].as<Files::MaybeQuotedPath>().u8string()); engine.setSaveGameFile(variables["load-savegame"].as<Files::MaybeQuotedPath>().u8string());
// other settings // other settings
Fallback::Map::init(variables["fallback"].as<FallbackMap>().mMap); Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
engine.setSoundUsage(!variables["no-sound"].as<bool>()); engine.setSoundUsage(!variables["no-sound"].as<bool>());
engine.setActivationDistanceOverride(variables["activate-dist"].as<int>()); engine.setActivationDistanceOverride(variables["activate-dist"].as<int>());
engine.enableFontExport(variables["export-fonts"].as<bool>());
engine.setRandomSeed(variables["random-seed"].as<unsigned int>()); engine.setRandomSeed(variables["random-seed"].as<unsigned int>());
return true; return true;
@ -220,6 +219,8 @@ int runApplication(int argc, char* argv[])
Files::ConfigurationManager cfgMgr; Files::ConfigurationManager cfgMgr;
std::unique_ptr<OMW::Engine> engine = std::make_unique<OMW::Engine>(cfgMgr); std::unique_ptr<OMW::Engine> engine = std::make_unique<OMW::Engine>(cfgMgr);
engine->setRecastMaxLogLevel(Debug::getRecastMaxLogLevel());
if (parseOptions(argc, argv, *engine, cfgMgr)) if (parseOptions(argc, argv, *engine, cfgMgr))
{ {
if (!Misc::checkRequiredOSGPluginsArePresent()) if (!Misc::checkRequiredOSGPluginsArePresent())

View file

@ -200,7 +200,7 @@ namespace MWBase
///< Skip the animation for the given MW-reference for one frame. Calls to this function for ///< Skip the animation for the given MW-reference for one frame. Calls to this function for
/// references that are currently not in the scene should be ignored. /// references that are currently not in the scene should be ignored.
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) = 0;
virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0; virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0;

View file

@ -363,7 +363,7 @@ namespace MWBase
void windowVisibilityChange(bool visible) override = 0; void windowVisibilityChange(bool visible) override = 0;
void windowResized(int x, int y) override = 0; void windowResized(int x, int y) override = 0;
void windowClosed() override = 0; void windowClosed() override = 0;
virtual bool isWindowVisible() = 0; virtual bool isWindowVisible() const = 0;
virtual void watchActor(const MWWorld::Ptr& ptr) = 0; virtual void watchActor(const MWWorld::Ptr& ptr) = 0;
virtual MWWorld::Ptr getWatchedActor() const = 0; virtual MWWorld::Ptr getWatchedActor() const = 0;

View file

@ -237,7 +237,12 @@ namespace MWClass
bool Container::hasToolTip(const MWWorld::ConstPtr& ptr) const bool Container::hasToolTip(const MWWorld::ConstPtr& ptr) const
{ {
if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData())
return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems(); {
if (!canBeHarvested(ptr))
return true;
const MWWorld::ContainerStore& store = data->asContainerCustomData().mStore;
return !store.isResolved() || store.hasVisibleItems();
}
return true; return true;
} }

View file

@ -236,11 +236,8 @@ namespace MWClass
} }
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
float dist = store.find("fCombatDistance")->mValue.getFloat();
if (!weapon.isEmpty())
dist *= weapon.get<ESM::Weapon>()->mBase->mData.mReach;
const float dist = MWMechanics::getMeleeWeaponReach(ptr, weapon);
const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist); const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist);
if (result.first.isEmpty()) // Didn't hit anything if (result.first.isEmpty()) // Didn't hit anything
return true; return true;
@ -281,6 +278,9 @@ namespace MWClass
if (otherstats.isDead()) // Can't hit dead actors if (otherstats.isDead()) // Can't hit dead actors
return; return;
if (!MWMechanics::isInMeleeReach(ptr, victim, MWMechanics::getMeleeWeaponReach(ptr, weapon)))
return;
if (!success) if (!success)
{ {
victim.getClass().onHit( victim.getClass().onHit(

View file

@ -572,12 +572,8 @@ namespace MWClass
weapon = *weaponslot; weapon = *weaponslot;
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat();
float dist = fCombatDistance
* (!weapon.isEmpty() ? weapon.get<ESM::Weapon>()->mBase->mData.mReach
: store.find("fHandToHandReach")->mValue.getFloat());
const float dist = MWMechanics::getMeleeWeaponReach(ptr, weapon);
const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist); const std::pair<MWWorld::Ptr, osg::Vec3f> result = MWMechanics::getHitContact(ptr, dist);
if (result.first.isEmpty()) // Didn't hit anything if (result.first.isEmpty()) // Didn't hit anything
return true; return true;
@ -615,6 +611,9 @@ namespace MWClass
if (otherstats.isDead()) // Can't hit dead actors if (otherstats.isDead()) // Can't hit dead actors
return; return;
if (!MWMechanics::isInMeleeReach(ptr, victim, MWMechanics::getMeleeWeaponReach(ptr, weapon)))
return;
if (ptr == MWMechanics::getPlayer()) if (ptr == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->setEnemy(victim); MWBase::Environment::get().getWindowManager()->setEnemy(victim);

View file

@ -1343,12 +1343,13 @@ namespace MWGui
return codePoint == '\r'; return codePoint == '\r';
} }
// Normal no-break space (0x00A0) is ignored here
// because Morrowind compatibility requires us to render its glyph
static bool ucsSpace(int codePoint) static bool ucsSpace(int codePoint)
{ {
switch (codePoint) switch (codePoint)
{ {
case 0x0020: // SPACE case 0x0020: // SPACE
case 0x00A0: // NO-BREAK SPACE
case 0x1680: // OGHAM SPACE MARK case 0x1680: // OGHAM SPACE MARK
case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x180E: // MONGOLIAN VOWEL SEPARATOR
case 0x2000: // EN QUAD case 0x2000: // EN QUAD
@ -1373,12 +1374,14 @@ namespace MWGui
} }
} }
// No-break spaces (0x00A0, 0x202F, 0xFEFF - normal, narrow, zero width)
// are ignored here for obvious reasons
// Figure space (0x2007) is not a breaking space either
static bool ucsBreakingSpace(int codePoint) static bool ucsBreakingSpace(int codePoint)
{ {
switch (codePoint) switch (codePoint)
{ {
case 0x0020: // SPACE case 0x0020: // SPACE
// case 0x00A0: // NO-BREAK SPACE
case 0x1680: // OGHAM SPACE MARK case 0x1680: // OGHAM SPACE MARK
case 0x180E: // MONGOLIAN VOWEL SEPARATOR case 0x180E: // MONGOLIAN VOWEL SEPARATOR
case 0x2000: // EN QUAD case 0x2000: // EN QUAD
@ -1388,15 +1391,12 @@ namespace MWGui
case 0x2004: // THREE-PER-EM SPACE case 0x2004: // THREE-PER-EM SPACE
case 0x2005: // FOUR-PER-EM SPACE case 0x2005: // FOUR-PER-EM SPACE
case 0x2006: // SIX-PER-EM SPACE case 0x2006: // SIX-PER-EM SPACE
case 0x2007: // FIGURE SPACE
case 0x2008: // PUNCTUATION SPACE case 0x2008: // PUNCTUATION SPACE
case 0x2009: // THIN SPACE case 0x2009: // THIN SPACE
case 0x200A: // HAIR SPACE case 0x200A: // HAIR SPACE
case 0x200B: // ZERO WIDTH SPACE case 0x200B: // ZERO WIDTH SPACE
case 0x202F: // NARROW NO-BREAK SPACE
case 0x205F: // MEDIUM MATHEMATICAL SPACE case 0x205F: // MEDIUM MATHEMATICAL SPACE
case 0x3000: // IDEOGRAPHIC SPACE case 0x3000: // IDEOGRAPHIC SPACE
// case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
return true; return true;
default: default:
return false; return false;

View file

@ -666,7 +666,8 @@ namespace MWGui
else if (scrollbar) else if (scrollbar)
{ {
mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second)); mHistory->setSize(MyGUI::IntSize(mHistory->getWidth(), book->getSize().second));
size_t range = book->getSize().second - viewHeight; // Scroll range should be >= 2 to enable scrolling and prevent a crash
size_t range = std::max(book->getSize().second - viewHeight, size_t(2));
mScrollBar->setScrollRange(range); mScrollBar->setScrollRange(range);
mScrollBar->setScrollPosition(range - 1); mScrollBar->setScrollPosition(range - 1);
mScrollBar->setTrackSize( mScrollBar->setTrackSize(

View file

@ -29,11 +29,26 @@ namespace MWGui
{ {
Misc::FrameRateLimiter frameRateLimiter Misc::FrameRateLimiter frameRateLimiter
= Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit());
const MWBase::WindowManager& windowManager = *MWBase::Environment::get().getWindowManager();
bool paused = false;
while (mRunning) while (mRunning)
{ {
// If finished playing, start again if (windowManager.isWindowVisible())
if (!mVideo->update()) {
mVideo->playVideo("video\\menu_background.bik"); if (paused)
{
mVideo->resume();
paused = false;
}
// If finished playing, start again
if (!mVideo->update())
mVideo->playVideo("video\\menu_background.bik");
}
else if (!paused)
{
paused = true;
mVideo->pause();
}
frameRateLimiter.limit(); frameRateLimiter.limit();
} }
} }

View file

@ -18,6 +18,7 @@
#include <SDL_video.h> #include <SDL_video.h>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/l10n/manager.hpp>
#include <components/lua_ui/scriptsettings.hpp> #include <components/lua_ui/scriptsettings.hpp>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <components/misc/display.hpp> #include <components/misc/display.hpp>
@ -669,12 +670,12 @@ namespace MWGui
void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender)
{ {
std::string_view on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); const std::string on = MWBase::Environment::get().getL10nManager()->getMessage("Interface", "On");
const std::string off = MWBase::Environment::get().getL10nManager()->getMessage("Interface", "Off");
bool newState; bool newState;
if (_sender->castType<MyGUI::Button>()->getCaption() == on) if (_sender->castType<MyGUI::Button>()->getCaption() == on)
{ {
_sender->castType<MyGUI::Button>()->setCaption( _sender->castType<MyGUI::Button>()->setCaption(MyGUI::UString(off));
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "Off")));
newState = false; newState = false;
} }
else else

View file

@ -146,7 +146,7 @@ namespace MWGui
WindowManager::WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, WindowManager::WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot,
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::filesystem::path& logpath, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::filesystem::path& logpath,
bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding,
const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr) bool exportFonts, const std::string& versionDescription, bool useShaders, Files::ConfigurationManager& cfgMgr)
: mOldUpdateMask(0) : mOldUpdateMask(0)
, mOldCullMask(0) , mOldCullMask(0)
, mStore(nullptr) , mStore(nullptr)
@ -215,7 +215,8 @@ namespace MWGui
MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag);
// Load fonts // Load fonts
mFontLoader = std::make_unique<Gui::FontLoader>(encoding, resourceSystem->getVFS(), mScalingFactor); mFontLoader
= std::make_unique<Gui::FontLoader>(encoding, resourceSystem->getVFS(), mScalingFactor, exportFonts);
// Register own widgets with MyGUI // Register own widgets with MyGUI
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget");
@ -729,6 +730,9 @@ namespace MWGui
return; return;
} }
if (mGuiModes.empty())
return;
GuiModeState& state = mGuiModeStates[mGuiModes.back()]; GuiModeState& state = mGuiModeStates[mGuiModes.back()];
for (const auto& window : state.mWindows) for (const auto& window : state.mWindows)
{ {
@ -1214,7 +1218,7 @@ namespace MWGui
// TODO: check if any windows are now off-screen and move them back if so // TODO: check if any windows are now off-screen and move them back if so
} }
bool WindowManager::isWindowVisible() bool WindowManager::isWindowVisible() const
{ {
return mWindowVisible; return mWindowVisible;
} }

View file

@ -128,7 +128,7 @@ namespace MWGui
WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot, WindowManager(SDL_Window* window, osgViewer::Viewer* viewer, osg::Group* guiRoot,
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
const std::filesystem::path& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, const std::filesystem::path& logpath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage,
ToUTF8::FromType encoding, const std::string& versionDescription, bool useShaders, ToUTF8::FromType encoding, bool exportFonts, const std::string& versionDescription, bool useShaders,
Files::ConfigurationManager& cfgMgr); Files::ConfigurationManager& cfgMgr);
virtual ~WindowManager(); virtual ~WindowManager();
@ -290,7 +290,7 @@ namespace MWGui
void windowVisibilityChange(bool visible) override; void windowVisibilityChange(bool visible) override;
void windowResized(int x, int y) override; void windowResized(int x, int y) override;
void windowClosed() override; void windowClosed() override;
bool isWindowVisible() override; bool isWindowVisible() const override;
void watchActor(const MWWorld::Ptr& ptr) override; void watchActor(const MWWorld::Ptr& ptr) override;
MWWorld::Ptr getWatchedActor() const override; MWWorld::Ptr getWatchedActor() const override;

View file

@ -148,7 +148,7 @@ namespace MWLua
}; };
} }
sol::table readOnly = LuaUtil::makeReadOnly(api); sol::table readOnlyApi = LuaUtil::makeReadOnly(api);
return context.setTypePackage(readOnly, "openmw_core"); return context.setTypePackage(readOnlyApi, "openmw_core");
} }
} }

View file

@ -38,94 +38,116 @@ namespace MWLua
sol::table initInputPackage(const Context& context) sol::table initInputPackage(const Context& context)
{ {
sol::object cached = context.getTypePackage("openmw_input");
if (cached != sol::nil)
return cached;
sol::state_view lua = context.sol(); sol::state_view lua = context.sol();
{
if (lua["openmw_input"] != sol::nil)
return lua["openmw_input"];
}
sol::usertype<SDL_Keysym> keyEvent = lua.new_usertype<SDL_Keysym>("KeyEvent"); context.cachePackage("openmw_input_keyevent", [&lua]() {
keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { sol::usertype<SDL_Keysym> keyEvent = lua.new_usertype<SDL_Keysym>("KeyEvent");
if (e.sym > 0 && e.sym <= 255) keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) {
return std::string(1, static_cast<char>(e.sym)); if (e.sym > 0 && e.sym <= 255)
else return std::string(1, static_cast<char>(e.sym));
return std::string(); else
return std::string();
});
keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; });
keyEvent["withShift"]
= sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; });
keyEvent["withCtrl"]
= sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; });
keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; });
keyEvent["withSuper"]
= sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; });
return sol::table(lua, sol::create);
}); });
keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; });
keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; });
keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; });
keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; });
keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; });
auto touchpadEvent = lua.new_usertype<SDLUtil::TouchEvent>("TouchpadEvent"); context.cachePackage("openmw_input_touchpadevent", [&lua]() {
touchpadEvent["device"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; }); auto touchpadEvent = lua.new_usertype<SDLUtil::TouchEvent>("TouchpadEvent");
touchpadEvent["finger"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; }); touchpadEvent["device"]
touchpadEvent["position"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> osg::Vec2f { = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; });
return { e.mX, e.mY }; touchpadEvent["finger"]
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; });
touchpadEvent["position"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> osg::Vec2f {
return { e.mX, e.mY };
});
touchpadEvent["pressure"]
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; });
return sol::table(lua, sol::create);
}); });
touchpadEvent["pressure"]
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; });
auto inputActions = lua.new_usertype<LuaUtil::InputAction::Registry>("InputActions"); context.cachePackage("openmw_input_inputactions", [&lua]() {
inputActions[sol::meta_function::index] auto inputActions = lua.new_usertype<LuaUtil::InputAction::Registry>("InputActions");
= [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; }; inputActions[sol::meta_function::index]
{ = [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; };
auto pairs = [](LuaUtil::InputAction::Registry& registry) { {
auto next auto pairs = [](LuaUtil::InputAction::Registry& registry) {
= [](LuaUtil::InputAction::Registry& registry, auto next = [](LuaUtil::InputAction::Registry& registry, std::string_view key)
std::string_view key) -> sol::optional<std::tuple<std::string, LuaUtil::InputAction::Info>> { -> sol::optional<std::tuple<std::string, LuaUtil::InputAction::Info>> {
std::optional<std::string> nextKey(registry.nextKey(key)); std::optional<std::string> nextKey(registry.nextKey(key));
if (!nextKey.has_value()) if (!nextKey.has_value())
return sol::nullopt; return sol::nullopt;
else else
return std::make_tuple(*nextKey, registry[*nextKey].value()); return std::make_tuple(*nextKey, registry[*nextKey].value());
};
return std::make_tuple(next, registry, registry.firstKey());
}; };
return std::make_tuple(next, registry, registry.firstKey()); inputActions[sol::meta_function::pairs] = pairs;
}; }
inputActions[sol::meta_function::pairs] = pairs; return sol::table(lua, sol::create);
} });
auto actionInfo = lua.new_usertype<LuaUtil::InputAction::Info>("ActionInfo"); context.cachePackage("openmw_input_actioninfo", [&lua]() {
actionInfo["key"] = sol::readonly_property( auto actionInfo = lua.new_usertype<LuaUtil::InputAction::Info>("ActionInfo");
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; }); actionInfo["key"] = sol::readonly_property(
actionInfo["name"] = sol::readonly_property( [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; });
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; }); actionInfo["name"] = sol::readonly_property(
actionInfo["description"] = sol::readonly_property( [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; });
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; }); actionInfo["description"] = sol::readonly_property(
actionInfo["l10n"] = sol::readonly_property( [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; });
[](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; }); actionInfo["l10n"] = sol::readonly_property(
actionInfo["type"] = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; }); [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; });
actionInfo["defaultValue"] actionInfo["type"]
= sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; }); = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; });
actionInfo["defaultValue"]
= sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; });
return sol::table(lua, sol::create);
});
auto inputTriggers = lua.new_usertype<LuaUtil::InputTrigger::Registry>("InputTriggers"); context.cachePackage("openmw_input_inputtriggers", [&lua]() {
inputTriggers[sol::meta_function::index] auto inputTriggers = lua.new_usertype<LuaUtil::InputTrigger::Registry>("InputTriggers");
= [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; }; inputTriggers[sol::meta_function::index]
{ = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; };
auto pairs = [](LuaUtil::InputTrigger::Registry& registry) { {
auto next auto pairs = [](LuaUtil::InputTrigger::Registry& registry) {
= [](LuaUtil::InputTrigger::Registry& registry, auto next = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key)
std::string_view key) -> sol::optional<std::tuple<std::string, LuaUtil::InputTrigger::Info>> { -> sol::optional<std::tuple<std::string, LuaUtil::InputTrigger::Info>> {
std::optional<std::string> nextKey(registry.nextKey(key)); std::optional<std::string> nextKey(registry.nextKey(key));
if (!nextKey.has_value()) if (!nextKey.has_value())
return sol::nullopt; return sol::nullopt;
else else
return std::make_tuple(*nextKey, registry[*nextKey].value()); return std::make_tuple(*nextKey, registry[*nextKey].value());
};
return std::make_tuple(next, registry, registry.firstKey());
}; };
return std::make_tuple(next, registry, registry.firstKey()); inputTriggers[sol::meta_function::pairs] = pairs;
}; }
inputTriggers[sol::meta_function::pairs] = pairs; return sol::table(lua, sol::create);
} });
auto triggerInfo = lua.new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo"); context.cachePackage("openmw_input_triggerinfo", [&lua]() {
triggerInfo["key"] = sol::readonly_property( auto triggerInfo = lua.new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo");
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; }); triggerInfo["key"] = sol::readonly_property(
triggerInfo["name"] = sol::readonly_property( [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; });
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; }); triggerInfo["name"] = sol::readonly_property(
triggerInfo["description"] = sol::readonly_property( [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; });
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; }); triggerInfo["description"] = sol::readonly_property(
triggerInfo["l10n"] = sol::readonly_property( [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; });
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; }); triggerInfo["l10n"] = sol::readonly_property(
[](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; });
return sol::table(lua, sol::create);
});
MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
sol::table api(lua, sol::create); sol::table api(lua, sol::create);
@ -139,16 +161,18 @@ namespace MWLua
})); }));
api["actions"] = std::ref(context.mLuaManager->inputActions()); api["actions"] = std::ref(context.mLuaManager->inputActions());
api["registerAction"] = [manager = context.mLuaManager](sol::table options) { api["registerAction"]
LuaUtil::InputAction::Info parsedOptions; = [manager = context.mLuaManager, persistent = context.mType == Context::Menu](sol::table options) {
parsedOptions.mKey = options["key"].get<std::string_view>(); LuaUtil::InputAction::Info parsedOptions;
parsedOptions.mType = options["type"].get<LuaUtil::InputAction::Type>(); parsedOptions.mKey = options["key"].get<std::string_view>();
parsedOptions.mL10n = options["l10n"].get<std::string_view>(); parsedOptions.mType = options["type"].get<LuaUtil::InputAction::Type>();
parsedOptions.mName = options["name"].get<std::string_view>(); parsedOptions.mL10n = options["l10n"].get<std::string_view>();
parsedOptions.mDescription = options["description"].get<std::string_view>(); parsedOptions.mName = options["name"].get<std::string_view>();
parsedOptions.mDefaultValue = options["defaultValue"].get<sol::main_object>(); parsedOptions.mDescription = options["description"].get<std::string_view>();
manager->inputActions().insert(std::move(parsedOptions)); parsedOptions.mDefaultValue = options["defaultValue"].get<sol::main_object>();
}; parsedOptions.mPersistent = persistent;
manager->inputActions().insert(std::move(parsedOptions));
};
api["bindAction"] = [manager = context.mLuaManager]( api["bindAction"] = [manager = context.mLuaManager](
std::string_view key, const sol::table& callback, sol::table dependencies) { std::string_view key, const sol::table& callback, sol::table dependencies) {
std::vector<std::string_view> parsedDependencies; std::vector<std::string_view> parsedDependencies;
@ -178,14 +202,16 @@ namespace MWLua
}; };
api["triggers"] = std::ref(context.mLuaManager->inputTriggers()); api["triggers"] = std::ref(context.mLuaManager->inputTriggers());
api["registerTrigger"] = [manager = context.mLuaManager](sol::table options) { api["registerTrigger"]
LuaUtil::InputTrigger::Info parsedOptions; = [manager = context.mLuaManager, persistent = context.mType == Context::Menu](sol::table options) {
parsedOptions.mKey = options["key"].get<std::string_view>(); LuaUtil::InputTrigger::Info parsedOptions;
parsedOptions.mL10n = options["l10n"].get<std::string_view>(); parsedOptions.mKey = options["key"].get<std::string_view>();
parsedOptions.mName = options["name"].get<std::string_view>(); parsedOptions.mL10n = options["l10n"].get<std::string_view>();
parsedOptions.mDescription = options["description"].get<std::string_view>(); parsedOptions.mName = options["name"].get<std::string_view>();
manager->inputTriggers().insert(std::move(parsedOptions)); parsedOptions.mDescription = options["description"].get<std::string_view>();
}; parsedOptions.mPersistent = persistent;
manager->inputTriggers().insert(std::move(parsedOptions));
};
api["registerTriggerHandler"] api["registerTriggerHandler"]
= [manager = context.mLuaManager](std::string_view key, const sol::table& callback) { = [manager = context.mLuaManager](std::string_view key, const sol::table& callback) {
manager->inputTriggers().registerHandler(key, LuaUtil::Callback::fromLua(callback)); manager->inputTriggers().registerHandler(key, LuaUtil::Callback::fromLua(callback));
@ -445,8 +471,8 @@ namespace MWLua
{ "Tab", SDL_SCANCODE_TAB }, { "Tab", SDL_SCANCODE_TAB },
})); }));
lua["openmw_input"] = LuaUtil::makeReadOnly(api); sol::table readOnlyApi = LuaUtil::makeReadOnly(api);
return lua["openmw_input"]; return context.setTypePackage(readOnlyApi, "openmw_input");
} }
} }

View file

@ -71,7 +71,8 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value; obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }]
= sol::main_object(value);
} }
else else
throw std::runtime_error("Only global or self scripts can set the value"); throw std::runtime_error("Only global or self scripts can set the value");

View file

@ -54,7 +54,7 @@ namespace MWLua
{ {
} }
MWBase::LuaManager::ActorControls mControls; MWBase::LuaManager::ActorControls mControls;
std::map<CachedStat, sol::object> mStatsCache; std::map<CachedStat, sol::main_object> mStatsCache;
bool mIsActive; bool mIsActive;
}; };

View file

@ -343,6 +343,7 @@ namespace MWLua
mPlayerStorage.clearTemporaryAndRemoveCallbacks(); mPlayerStorage.clearTemporaryAndRemoveCallbacks();
mInputActions.clear(); mInputActions.clear();
mInputTriggers.clear(); mInputTriggers.clear();
mQueuedAutoStartedScripts.clear();
for (int i = 0; i < 5; ++i) for (int i = 0; i < 5; ++i)
lua_gc(mLua.unsafeState(), LUA_GCCOLLECT, 0); lua_gc(mLua.unsafeState(), LUA_GCCOLLECT, 0);
} }
@ -653,8 +654,8 @@ namespace MWLua
MWBase::Environment::get().getL10nManager()->dropCache(); MWBase::Environment::get().getL10nManager()->dropCache();
mUiResourceManager.clear(); mUiResourceManager.clear();
mLua.dropScriptCache(); mLua.dropScriptCache();
mInputActions.clear(); mInputActions.clear(true);
mInputTriggers.clear(); mInputTriggers.clear(true);
initConfiguration(); initConfiguration();
ESM::LuaScripts globalData; ESM::LuaScripts globalData;

View file

@ -1061,7 +1061,7 @@ namespace MWLua
}; };
// types.Actor.activeEffects(o):removeEffect(id, ?arg) // types.Actor.activeEffects(o):removeEffect(id, ?arg)
activeEffectsT["remove"] = [getEffectKey](const ActorActiveEffects& effects, std::string_view idStr, activeEffectsT["remove"] = [getEffectKey, context](const ActorActiveEffects& effects, std::string_view idStr,
sol::optional<std::string_view> argStr) { sol::optional<std::string_view> argStr) {
if (!effects.isActor()) if (!effects.isActor())
return; return;
@ -1071,12 +1071,14 @@ namespace MWLua
MWMechanics::EffectKey key = getEffectKey(idStr, argStr); MWMechanics::EffectKey key = getEffectKey(idStr, argStr);
// Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a context.mLuaManager->addAction([key, effects]() {
// spell), we still need to use the active spells store to purge this effect from active spells. // Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a
const auto& ptr = effects.mActor.ptr(); // spell), we still need to use the active spells store to purge this effect from active spells.
const auto& ptr = effects.mActor.ptr();
auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells(); auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells();
activeSpells.purgeEffect(ptr, key.mId, key.mArg); activeSpells.purgeEffect(ptr, key.mId, key.mArg);
});
}; };
// types.Actor.activeEffects(o):set(value, id, ?arg) // types.Actor.activeEffects(o):set(value, id, ?arg)

View file

@ -213,6 +213,7 @@ namespace MWLua
{ "NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound }, { "NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound },
{ "StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound }, { "StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound },
{ "EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound }, { "EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound },
{ "TargetPolygonNotFound", DetourNavigator::Status::TargetPolygonNotFound },
{ "MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed }, { "MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed },
{ "FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed }, { "FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed },
{ "InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed }, { "InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed },

View file

@ -123,7 +123,8 @@ namespace MWLua
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] = value; obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }]
= sol::main_object(value);
} }
}; };
@ -159,7 +160,7 @@ namespace MWLua
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }] obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }]
= value; = sol::main_object(value);
} }
}; };
@ -183,7 +184,8 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] = value; obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }]
= sol::main_object(value);
} }
sol::object getProgress(const Context& context) const sol::object getProgress(const Context& context) const
@ -204,7 +206,8 @@ namespace MWLua
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] = value; obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }]
= sol::main_object(value);
} }
SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const
@ -258,7 +261,7 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = value; 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) static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
@ -318,7 +321,7 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mId, prop }] = value; 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) static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
@ -402,7 +405,7 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = value; 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) static void setValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
@ -465,7 +468,8 @@ namespace MWLua
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast<int>(mIndex), prop }] = value; 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) static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)

View file

@ -43,7 +43,7 @@ namespace MWLua
[](const ESM::CreatureLevList& rec) -> std::string { return rec.mId.serializeText(); }); [](const ESM::CreatureLevList& rec) -> std::string { return rec.mId.serializeText(); });
record["chanceNone"] = sol::readonly_property( record["chanceNone"] = sol::readonly_property(
[](const ESM::CreatureLevList& rec) -> float { return std::clamp(rec.mChanceNone / 100.f, 0.f, 1.f); }); [](const ESM::CreatureLevList& rec) -> float { return std::clamp(rec.mChanceNone / 100.f, 0.f, 1.f); });
record["creatures"] = sol::readonly_property([&](const ESM::CreatureLevList& rec) -> sol::table { record["creatures"] = sol::readonly_property([state](const ESM::CreatureLevList& rec) -> sol::table {
sol::table res(state, sol::create); sol::table res(state, sol::create);
for (size_t i = 0; i < rec.mList.size(); ++i) for (size_t i = 0; i < rec.mList.size(); ++i)
res[LuaUtil::toLuaIndex(i)] = rec.mList[i]; res[LuaUtil::toLuaIndex(i)] = rec.mList[i];

View file

@ -63,7 +63,7 @@ namespace
if (rec["type"] != sol::nil) if (rec["type"] != sol::nil)
{ {
int weaponType = rec["type"].get<int>(); int weaponType = rec["type"].get<int>();
if (weaponType >= 0 && weaponType <= ESM::Weapon::MarksmanThrown) if (weaponType >= 0 && weaponType <= ESM::Weapon::Last)
weapon.mData.mType = weaponType; weapon.mData.mType = weaponType;
else else
throw std::runtime_error("Invalid Weapon Type provided: " + std::to_string(weaponType)); throw std::runtime_error("Invalid Weapon Type provided: " + std::to_string(weaponType));

View file

@ -168,7 +168,8 @@ namespace MWLua
if (index == LuaUi::Layer::count()) if (index == LuaUi::Layer::count())
throw std::logic_error(std::string("Layer not found")); throw std::logic_error(std::string("Layer not found"));
index++; index++;
context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); context.mLuaManager->addAction(
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
}; };
layersTable["insertBefore"] = [context]( layersTable["insertBefore"] = [context](
std::string_view beforename, std::string_view name, const sol::object& opt) { std::string_view beforename, std::string_view name, const sol::object& opt) {
@ -177,7 +178,8 @@ namespace MWLua
size_t index = LuaUi::Layer::indexOf(beforename); size_t index = LuaUi::Layer::indexOf(beforename);
if (index == LuaUi::Layer::count()) if (index == LuaUi::Layer::count())
throw std::logic_error(std::string("Layer not found")); throw std::logic_error(std::string("Layer not found"));
context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); context.mLuaManager->addAction(
[=, name = std::string(name)]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer");
}; };
sol::table layers = LuaUtil::makeReadOnly(layersTable); sol::table layers = LuaUtil::makeReadOnly(layersTable);
sol::table layersMeta = layers[sol::metatable_key]; sol::table layersMeta = layers[sol::metatable_key];
@ -285,8 +287,9 @@ namespace MWLua
return res; return res;
}; };
api["_setWindowDisabled"] api["_setWindowDisabled"]
= [windowManager, luaManager = context.mLuaManager](std::string_view window, bool disabled) { = [windowManager, luaManager = context.mLuaManager](std::string window, bool disabled) {
luaManager->addAction([=]() { windowManager->setDisabledByLua(window, disabled); }); luaManager->addAction(
[=, window = std::move(window)]() { windowManager->setDisabledByLua(window, disabled); });
}; };
// TODO // TODO
@ -308,7 +311,7 @@ namespace MWLua
return res.str(); return res.str();
}; };
element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; },
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); [](LuaUi::Element& element, const sol::main_table& layout) { element.mLayout = layout; });
element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) { element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
if (element->mState != LuaUi::Element::Created) if (element->mState != LuaUi::Element::Created)
return; return;

View file

@ -68,7 +68,7 @@ namespace MWLua
Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'."; Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'.";
} }
sol::object readFile(sol::this_state lua, FileHandle& file) sol::object readFile(lua_State* lua, FileHandle& file)
{ {
std::ostringstream os; std::ostringstream os;
if (file.mFilePtr && file.mFilePtr->peek() != EOF) if (file.mFilePtr && file.mFilePtr->peek() != EOF)
@ -79,7 +79,7 @@ namespace MWLua
return sol::make_object<std::string>(lua, std::move(result)); return sol::make_object<std::string>(lua, std::move(result));
} }
sol::object readLineFromFile(sol::this_state lua, FileHandle& file) sol::object readLineFromFile(lua_State* lua, FileHandle& file)
{ {
std::string result; std::string result;
if (file.mFilePtr && std::getline(*file.mFilePtr, result)) if (file.mFilePtr && std::getline(*file.mFilePtr, result))
@ -91,7 +91,7 @@ namespace MWLua
return sol::nil; return sol::nil;
} }
sol::object readNumberFromFile(sol::this_state lua, Files::IStreamPtr& file) sol::object readNumberFromFile(lua_State* lua, Files::IStreamPtr& file)
{ {
double number = 0; double number = 0;
if (file && *file >> number) if (file && *file >> number)
@ -100,7 +100,7 @@ namespace MWLua
return sol::nil; return sol::nil;
} }
sol::object readCharactersFromFile(sol::this_state lua, FileHandle& file, size_t count) sol::object readCharactersFromFile(lua_State* lua, FileHandle& file, size_t count)
{ {
if (count <= 0 && file.mFilePtr->peek() != EOF) if (count <= 0 && file.mFilePtr->peek() != EOF)
return sol::make_object<std::string>(lua, std::string()); return sol::make_object<std::string>(lua, std::string());
@ -189,7 +189,7 @@ namespace MWLua
return seek(lua, self, std::ios_base::cur, off); return seek(lua, self, std::ios_base::cur, off);
}); });
handle["lines"] = [](sol::this_state lua, sol::object self) { handle["lines"] = [](sol::this_main_state lua, sol::main_object self) {
if (!self.is<FileHandle*>()) if (!self.is<FileHandle*>())
throw std::runtime_error("self should be a file handle"); throw std::runtime_error("self should be a file handle");
return sol::as_function([lua, self]() -> sol::object { return sol::as_function([lua, self]() -> sol::object {
@ -199,7 +199,7 @@ namespace MWLua
}); });
}; };
api["lines"] = [vfs](sol::this_state lua, std::string_view fileName) { api["lines"] = [vfs](sol::this_main_state lua, std::string_view fileName) {
auto normalizedName = VFS::Path::normalizeFilename(fileName); auto normalizedName = VFS::Path::normalizeFilename(fileName);
return sol::as_function( return sol::as_function(
[lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable { [lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable {

View file

@ -289,8 +289,9 @@ namespace MWMechanics
const ESM::RefId& enchantmentId = slot->getClass().getEnchantment(*slot); const ESM::RefId& enchantmentId = slot->getClass().getEnchantment(*slot);
if (enchantmentId.empty()) if (enchantmentId.empty())
continue; continue;
const ESM::Enchantment* enchantment = world->getStore().get<ESM::Enchantment>().find(enchantmentId); const ESM::Enchantment* enchantment
if (enchantment->mData.mType != ESM::Enchantment::ConstantEffect) = world->getStore().get<ESM::Enchantment>().search(enchantmentId);
if (enchantment == nullptr || enchantment->mData.mType != ESM::Enchantment::ConstantEffect)
continue; continue;
if (std::find_if(mSpells.begin(), mSpells.end(), if (std::find_if(mSpells.begin(), mSpells.end(),
[&](const ActiveSpellParams& params) { [&](const ActiveSpellParams& params) {

View file

@ -2028,7 +2028,7 @@ namespace MWMechanics
iter->second->getCharacterController().skipAnim(); iter->second->getCharacterController().skipAnim();
} }
bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) const
{ {
const auto iter = mIndex.find(ptr.mRef); const auto iter = mIndex.find(ptr.mRef);
if (iter != mIndex.end()) if (iter != mIndex.end())

View file

@ -119,7 +119,7 @@ namespace MWMechanics
std::string_view startKey, std::string_view stopKey, bool forceLoop); std::string_view startKey, std::string_view stopKey, bool forceLoop);
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable);
void skipAnimation(const MWWorld::Ptr& ptr) const; void skipAnimation(const MWWorld::Ptr& ptr) const;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) const;
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const; bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const;
void persistAnimationStates() const; void persistAnimationStates() const;
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted); void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted);

View file

@ -40,15 +40,15 @@ namespace MWMechanics
static const std::size_t MAX_IDLE_SIZE = 8; static const std::size_t MAX_IDLE_SIZE = 8;
const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
std::string("idle2"), "idle2",
std::string("idle3"), "idle3",
std::string("idle4"), "idle4",
std::string("idle5"), "idle5",
std::string("idle6"), "idle6",
std::string("idle7"), "idle7",
std::string("idle8"), "idle8",
std::string("idle9"), "idle9",
}; };
namespace namespace
@ -680,7 +680,7 @@ namespace MWMechanics
{ {
if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle))
{ {
const std::string& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; const std::string_view groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle];
return MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1); return MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1);
} }
else else
@ -695,7 +695,7 @@ namespace MWMechanics
{ {
if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle))
{ {
const std::string& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; const std::string_view groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle];
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, groupName); return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, groupName);
} }
else else

View file

@ -3,6 +3,7 @@
#include "typedaipackage.hpp" #include "typedaipackage.hpp"
#include <string_view>
#include <vector> #include <vector>
#include "aitemporarybase.hpp" #include "aitemporarybase.hpp"
@ -181,9 +182,7 @@ namespace MWMechanics
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage); const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
/// lookup table for converting idleSelect value to groupName /// lookup table for converting idleSelect value to groupName
static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; static const std::string_view sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1];
static int OffsetToPreventOvercrowding();
}; };
} }

View file

@ -1104,10 +1104,10 @@ namespace MWMechanics
attackType = ESM::Weapon::AT_Thrust; attackType = ESM::Weapon::AT_Thrust;
// We want to avoid hit keys that come out of nowhere (e.g. in the follow animation) // We want to avoid hit keys that come out of nowhere (e.g. in the follow animation)
// and processing multiple hit keys for a single attack // and processing multiple hit keys for a single attack
if (mAttackStrength != -1.f) if (mReadyToHit)
{ {
charClass.hit(mPtr, mAttackStrength, attackType, mAttackVictim, mAttackHitPos, mAttackSuccess); charClass.hit(mPtr, mAttackStrength, attackType, mAttackVictim, mAttackHitPos, mAttackSuccess);
mAttackStrength = -1.f; mReadyToHit = false;
} }
} }
else if (isRandomAttackAnimation(groupname) && action == "start") else if (isRandomAttackAnimation(groupname) && action == "start")
@ -1153,10 +1153,10 @@ namespace MWMechanics
else if (action == "shoot release") else if (action == "shoot release")
{ {
// See notes for melee release above // See notes for melee release above
if (mAttackStrength != -1.f) if (mReadyToHit)
{ {
mAnimation->releaseArrow(mAttackStrength); mAnimation->releaseArrow(mAttackStrength);
mAttackStrength = -1.f; mReadyToHit = false;
} }
} }
else if (action == "shoot follow attach") else if (action == "shoot follow attach")
@ -1246,7 +1246,7 @@ namespace MWMechanics
void CharacterController::prepareHit() void CharacterController::prepareHit()
{ {
if (mAttackStrength != -1.f) if (mReadyToHit)
return; return;
auto& prng = MWBase::Environment::get().getWorld()->getPrng(); auto& prng = MWBase::Environment::get().getWorld()->getPrng();
@ -1261,6 +1261,8 @@ namespace MWMechanics
mAttackStrength = 0.f; mAttackStrength = 0.f;
playSwishSound(); playSwishSound();
} }
mReadyToHit = true;
} }
bool CharacterController::updateWeaponState() bool CharacterController::updateWeaponState()
@ -1520,6 +1522,7 @@ namespace MWMechanics
&& (mHitState == CharState_None || mHitState == CharState_Block)) && (mHitState == CharState_None || mHitState == CharState_Block))
{ {
mAttackStrength = -1.f; mAttackStrength = -1.f;
mReadyToHit = false;
// Randomize attacks for non-bipedal creatures // Randomize attacks for non-bipedal creatures
if (!cls.isBipedal(mPtr) if (!cls.isBipedal(mPtr)
@ -1806,8 +1809,7 @@ namespace MWMechanics
stop = strength + ' ' + stop; stop = strength + ' ' + stop;
} }
// Reset attack strength to make extra sure hits that come out of nowhere aren't processed mReadyToHit = false;
mAttackStrength = -1.f;
if (animPlaying) if (animPlaying)
mAnimation->disable(mCurrentWeapon); mAnimation->disable(mCurrentWeapon);

View file

@ -172,6 +172,7 @@ namespace MWMechanics
std::string mCurrentWeapon; std::string mCurrentWeapon;
float mAttackStrength{ -1.f }; float mAttackStrength{ -1.f };
bool mReadyToHit{ false };
MWWorld::Ptr mAttackVictim; MWWorld::Ptr mAttackVictim;
osg::Vec3f mAttackHitPos; osg::Vec3f mAttackHitPos;
bool mAttackSuccess{ false }; bool mAttackSuccess{ false };

View file

@ -578,6 +578,24 @@ namespace MWMechanics
return dist; return dist;
} }
float getMeleeWeaponReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat();
if (!weapon.isEmpty())
return fCombatDistance * weapon.get<ESM::Weapon>()->mBase->mData.mReach;
if (actor.getClass().isNpc())
return fCombatDistance * store.find("fHandToHandReach")->mValue.getFloat();
return fCombatDistance;
}
bool isInMeleeReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const float reach)
{
const float heightDiff = actor.getRefData().getPosition().pos[2] - target.getRefData().getPosition().pos[2];
return std::abs(heightDiff) < reach && getDistanceToBounds(actor, target) < reach;
}
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach) std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach)
{ {
// Lasciate ogne speranza, voi ch'entrate // Lasciate ogne speranza, voi ch'entrate
@ -614,11 +632,13 @@ namespace MWMechanics
{ {
if (actor == target || target.getClass().getCreatureStats(target).isDead()) if (actor == target || target.getClass().getCreatureStats(target).isDead())
continue; continue;
const float dist = getDistanceToBounds(actor, target); const float dist = getDistanceToBounds(actor, target);
const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); if (dist >= minDist || !isInMeleeReach(actor, target, reach))
if (dist >= reach || dist >= minDist || std::abs(targetPos.z() - actorPos.z()) >= reach)
continue; continue;
const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
// Horizontal angle checks. // Horizontal angle checks.
osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() }; osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() };
actorToTargetXY.normalize(); actorToTargetXY.normalize();

View file

@ -64,6 +64,10 @@ namespace MWMechanics
// Cursed distance calculation used for combat proximity and hit checks in Morrowind // Cursed distance calculation used for combat proximity and hit checks in Morrowind
float getDistanceToBounds(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); float getDistanceToBounds(const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
float getMeleeWeaponReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon);
bool isInMeleeReach(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const float reach);
// Similarly cursed hit target selection // Similarly cursed hit target selection
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach); std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::Ptr& actor, float reach);

View file

@ -778,7 +778,8 @@ namespace MWMechanics
else else
mObjects.skipAnimation(ptr); mObjects.skipAnimation(ptr);
} }
bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName)
bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName)
{ {
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())
return mActors.checkAnimationPlaying(ptr, groupName); return mActors.checkAnimationPlaying(ptr, groupName);

View file

@ -146,7 +146,7 @@ namespace MWMechanics
std::string_view startKey, std::string_view stopKey, bool forceLoop) override; std::string_view startKey, std::string_view stopKey, bool forceLoop) override;
void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override; void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override;
void skipAnimation(const MWWorld::Ptr& ptr) override; void skipAnimation(const MWWorld::Ptr& ptr) override;
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, std::string_view groupName) override;
bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override; bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override;
void persistAnimationStates() override; void persistAnimationStates() override;
void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) override; void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) override;

View file

@ -161,7 +161,7 @@ namespace MWRender
bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
{ {
if (Settings::game().mShieldSheathing) if (Settings::game().mShieldSheathing && mObjectRoot)
{ {
const MWWorld::Class& cls = mPtr.getClass(); const MWWorld::Class& cls = mPtr.getClass();
MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr); MWMechanics::CreatureStats& stats = cls.getCreatureStats(mPtr);

View file

@ -1695,7 +1695,7 @@ namespace MWRender
mGlowUpdater->setColor(color); mGlowUpdater->setColor(color);
mGlowUpdater->setDuration(glowDuration); mGlowUpdater->setDuration(glowDuration);
} }
else else if (mObjectRoot)
mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration); mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration);
} }
} }
@ -1869,7 +1869,7 @@ namespace MWRender
void Animation::setAlpha(float alpha) void Animation::setAlpha(float alpha)
{ {
if (alpha == mAlpha) if (alpha == mAlpha || !mObjectRoot)
return; return;
mAlpha = alpha; mAlpha = alpha;

View file

@ -260,7 +260,6 @@ namespace MWRender
// turn off sky blending // turn off sky blending
stateset->addUniform(new osg::Uniform("far", 10000000.0f)); stateset->addUniform(new osg::Uniform("far", 10000000.0f));
stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f));
stateset->addUniform(new osg::Uniform("sky", 0));
stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 })); stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 }));
stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); stateset->addUniform(new osg::Uniform("emissiveMult", 1.f));

View file

@ -259,7 +259,8 @@ namespace MWRender
void CreatureWeaponAnimation::addControllers() void CreatureWeaponAnimation::addControllers()
{ {
Animation::addControllers(); Animation::addControllers();
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); if (mObjectRoot)
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get());
} }
osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration)

View file

@ -736,7 +736,6 @@ namespace MWRender
// turn of sky blending // turn of sky blending
stateset->addUniform(new osg::Uniform("far", 10000000.0f)); stateset->addUniform(new osg::Uniform("far", 10000000.0f));
stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f));
stateset->addUniform(new osg::Uniform("sky", 0));
stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 })); stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{ 1, 1 }));
osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel; osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel;

View file

@ -762,14 +762,18 @@ namespace MWRender
if (Misc::StringUtils::ciEqual(technique->getName(), name)) if (Misc::StringUtils::ciEqual(technique->getName(), name))
return technique; return technique;
std::string realName = name;
auto fileIter = mTechniqueFileMap.find(name);
if (fileIter != mTechniqueFileMap.end())
realName = fileIter->first;
auto technique = std::make_shared<fx::Technique>(*mVFS, *mRendering.getResourceSystem()->getImageManager(), auto technique = std::make_shared<fx::Technique>(*mVFS, *mRendering.getResourceSystem()->getImageManager(),
name, renderWidth(), renderHeight(), mUBO, mNormalsSupported); std::move(realName), renderWidth(), renderHeight(), mUBO, mNormalsSupported);
technique->compile(); technique->compile();
if (technique->getStatus() != fx::Technique::Status::File_Not_exists) if (technique->getStatus() != fx::Technique::Status::File_Not_exists)
technique->setLastModificationTime( technique->setLastModificationTime(std::filesystem::last_write_time(fileIter->second));
std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]));
if (loadNextFrame) if (loadNextFrame)
{ {

View file

@ -18,6 +18,7 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/fx/stateupdater.hpp> #include <components/fx/stateupdater.hpp>
#include <components/fx/technique.hpp> #include <components/fx/technique.hpp>
#include <components/misc/strings/algorithm.hpp>
#include "pingpongcanvas.hpp" #include "pingpongcanvas.hpp"
#include "transparentpass.hpp" #include "transparentpass.hpp"
@ -229,7 +230,8 @@ namespace MWRender
TechniqueList mQueuedTemplates; TechniqueList mQueuedTemplates;
TechniqueList mInternalTechniques; TechniqueList mInternalTechniques;
std::unordered_map<std::string, std::filesystem::path> mTechniqueFileMap; std::unordered_map<std::string, std::filesystem::path, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual>
mTechniqueFileMap;
RenderingManager& mRendering; RenderingManager& mRendering;
osgViewer::Viewer* mViewer; osgViewer::Viewer* mViewer;

View file

@ -49,11 +49,6 @@ namespace MWRender
&& exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute; && exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute;
#endif #endif
if (mUseCompute)
Log(Debug::Info) << "Initialized compute shader pipeline for water ripples";
else
Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples";
for (size_t i = 0; i < mState.size(); ++i) for (size_t i = 0; i < mState.size(); ++i)
{ {
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet; osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
@ -91,6 +86,18 @@ namespace MWRender
else else
setupFragmentPipeline(); setupFragmentPipeline();
if (mProgramBlobber != nullptr)
{
static bool pipelineLogged = [&] {
if (mUseCompute)
Log(Debug::Info) << "Initialized compute shader pipeline for water ripples";
else
Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples";
return true;
}();
(void)pipelineLogged;
}
setCullCallback(new osg::NodeCallback); setCullCallback(new osg::NodeCallback);
setUpdateCallback(new osg::NodeCallback); setUpdateCallback(new osg::NodeCallback);
} }
@ -102,21 +109,36 @@ namespace MWRender
Shader::ShaderManager::DefineMap defineMap = { { "rippleMapSize", std::to_string(sRTTSize) + ".0" } }; Shader::ShaderManager::DefineMap defineMap = { { "rippleMapSize", std::to_string(sRTTSize) + ".0" } };
osg::ref_ptr<osg::Shader> vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); osg::ref_ptr<osg::Shader> vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> blobber
= shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT);
osg::ref_ptr<osg::Shader> simulate
= shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT);
if (vertex == nullptr || blobber == nullptr || simulate == nullptr)
{
Log(Debug::Error) << "Failed to load shaders required for fragment shader ripple pipeline";
return;
}
mProgramBlobber = shaderManager.getProgram( mProgramBlobber = shaderManager.getProgram(vertex, std::move(blobber));
vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT)); mProgramSimulation = shaderManager.getProgram(std::move(vertex), std::move(simulate));
mProgramSimulation = shaderManager.getProgram(
std::move(vertex), shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT));
} }
void RipplesSurface::setupComputePipeline() void RipplesSurface::setupComputePipeline()
{ {
auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager();
mProgramBlobber = shaderManager.getProgram( osg::ref_ptr<osg::Shader> blobber
nullptr, shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE)); = shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE);
mProgramSimulation = shaderManager.getProgram( osg::ref_ptr<osg::Shader> simulate
nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE)); = shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE);
if (blobber == nullptr || simulate == nullptr)
{
Log(Debug::Error) << "Failed to load shaders required for compute shader ripple pipeline";
return;
}
mProgramBlobber = shaderManager.getProgram(nullptr, std::move(blobber));
mProgramSimulation = shaderManager.getProgram(nullptr, std::move(simulate));
} }
void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state) void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state)
@ -184,6 +206,9 @@ namespace MWRender
void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const
{ {
if (mProgramBlobber == nullptr || mProgramSimulation == nullptr)
return;
osg::State& state = *renderInfo.getState(); osg::State& state = *renderInfo.getState();
const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2;
const State& frameState = mState[currentFrame]; const State& frameState = mState[currentFrame];

View file

@ -262,15 +262,15 @@ namespace MWRender
, mPrecipitationAlpha(0.f) , mPrecipitationAlpha(0.f)
, mDirtyParticlesEffect(false) , mDirtyParticlesEffect(false)
{ {
osg::ref_ptr<CameraRelativeTransform> skyroot = new CameraRelativeTransform; mSkyRootNode = new CameraRelativeTransform;
skyroot->setName("Sky Root"); mSkyRootNode->setName("Sky Root");
// Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline // Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline
if (!mSceneManager->getForceShaders()) if (!mSceneManager->getForceShaders())
skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), mSkyRootNode->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(),
osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON);
mSceneManager->setUpNormalsRTForStateSet(skyroot->getOrCreateStateSet(), false); mSceneManager->setUpNormalsRTForStateSet(mSkyRootNode->getOrCreateStateSet(), false);
SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet()); SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*mSkyRootNode->getOrCreateStateSet());
parentNode->addChild(skyroot); parentNode->addChild(mSkyRootNode);
mEarlyRenderBinRoot = new osg::Group; mEarlyRenderBinRoot = new osg::Group;
// render before the world is rendered // render before the world is rendered
@ -281,19 +281,18 @@ namespace MWRender
if (enableSkyRTT) if (enableSkyRTT)
{ {
mSkyRTT = new SkyRTT(Settings::fog().mSkyRttResolution, mEarlyRenderBinRoot); mSkyRTT = new SkyRTT(Settings::fog().mSkyRttResolution, mEarlyRenderBinRoot);
skyroot->addChild(mSkyRTT); mSkyRootNode->addChild(mSkyRTT);
mRootNode = new osg::Group;
skyroot->addChild(mRootNode);
} }
else
mRootNode = skyroot;
mRootNode->setNodeMask(Mask_Sky); mSkyNode = new osg::Group;
mRootNode->addChild(mEarlyRenderBinRoot); mSkyNode->setNodeMask(Mask_Sky);
mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); mSkyNode->addChild(mEarlyRenderBinRoot);
mSkyRootNode->addChild(mSkyNode);
mUnderwaterSwitch = new UnderwaterSwitchCallback(mSkyRootNode);
mPrecipitationOcclusion = Settings::shaders().mWeatherParticleOcclusion; mPrecipitationOcclusion = Settings::shaders().mWeatherParticleOcclusion;
mPrecipitationOccluder = std::make_unique<PrecipitationOccluder>(skyroot, parentNode, rootNode, camera); mPrecipitationOccluder = std::make_unique<PrecipitationOccluder>(mSkyRootNode, parentNode, rootNode, camera);
} }
void SkyManager::create() void SkyManager::create()
@ -464,7 +463,7 @@ namespace MWRender
mRainParticleSystem->setUserValue("particleOcclusion", true); mRainParticleSystem->setUserValue("particleOcclusion", true);
mSceneManager->recreateShaders(mRainNode); mSceneManager->recreateShaders(mRainNode);
mRootNode->addChild(mRainNode); mSkyNode->addChild(mRainNode);
if (mPrecipitationOcclusion) if (mPrecipitationOcclusion)
mPrecipitationOccluder->enable(); mPrecipitationOccluder->enable();
} }
@ -474,7 +473,7 @@ namespace MWRender
if (!mRainNode) if (!mRainNode)
return; return;
mRootNode->removeChild(mRainNode); mSkyNode->removeChild(mRainNode);
mRainNode = nullptr; mRainNode = nullptr;
mPlacer = nullptr; mPlacer = nullptr;
mCounter = nullptr; mCounter = nullptr;
@ -485,10 +484,10 @@ namespace MWRender
SkyManager::~SkyManager() SkyManager::~SkyManager()
{ {
if (mRootNode) if (mSkyRootNode)
{ {
mRootNode->getParent(0)->removeChild(mRootNode); mSkyRootNode->getParent(0)->removeChild(mSkyRootNode);
mRootNode = nullptr; mSkyRootNode = nullptr;
} }
} }
@ -595,7 +594,7 @@ namespace MWRender
const osg::Node::NodeMask mask = enabled ? Mask_Sky : 0u; const osg::Node::NodeMask mask = enabled ? Mask_Sky : 0u;
mEarlyRenderBinRoot->setNodeMask(mask); mEarlyRenderBinRoot->setNodeMask(mask);
mRootNode->setNodeMask(mask); mSkyNode->setNodeMask(mask);
if (!enabled && mParticleNode && mParticleEffect) if (!enabled && mParticleNode && mParticleEffect)
{ {
@ -691,7 +690,7 @@ namespace MWRender
{ {
if (mParticleNode) if (mParticleNode)
{ {
mRootNode->removeChild(mParticleNode); mSkyNode->removeChild(mParticleNode);
mParticleNode = nullptr; mParticleNode = nullptr;
} }
if (mRainEffect.empty()) if (mRainEffect.empty())
@ -706,7 +705,7 @@ namespace MWRender
mParticleNode = new osg::PositionAttitudeTransform; mParticleNode = new osg::PositionAttitudeTransform;
mParticleNode->addCullCallback(mUnderwaterSwitch); mParticleNode->addCullCallback(mUnderwaterSwitch);
mParticleNode->setNodeMask(Mask_WeatherParticles); mParticleNode->setNodeMask(Mask_WeatherParticles);
mRootNode->addChild(mParticleNode); mSkyNode->addChild(mParticleNode);
} }
mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode);

View file

@ -118,7 +118,8 @@ namespace MWRender
osg::Camera* mCamera; osg::Camera* mCamera;
osg::ref_ptr<osg::Group> mRootNode; osg::ref_ptr<CameraRelativeTransform> mSkyRootNode;
osg::ref_ptr<osg::Group> mSkyNode;
osg::ref_ptr<osg::Group> mEarlyRenderBinRoot; osg::ref_ptr<osg::Group> mEarlyRenderBinRoot;
osg::ref_ptr<osg::PositionAttitudeTransform> mParticleNode; osg::ref_ptr<osg::PositionAttitudeTransform> mParticleNode;

View file

@ -162,11 +162,17 @@ namespace MWScript
public: public:
void execute(Interpreter::Runtime& runtime) override void execute(Interpreter::Runtime& runtime) override
{ {
MWWorld::Ptr ptr = R()(runtime); MWWorld::Ptr ptr = R()(runtime, false);
int index = runtime[0].mInteger; int index = runtime[0].mInteger;
runtime.pop(); runtime.pop();
if (ptr.isEmpty())
{
runtime.push(0);
return;
}
bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying( bool ret = MWBase::Environment::get().getSoundManager()->getSoundPlaying(
ptr, ESM::RefId::stringRefId(runtime.getStringLiteral(index))); ptr, ESM::RefId::stringRefId(runtime.getStringLiteral(index)));

View file

@ -417,19 +417,15 @@ namespace MWSound
{ {
{ {
const std::lock_guard<std::mutex> openLock(mOutput.mReopenMutex); const std::lock_guard<std::mutex> openLock(mOutput.mReopenMutex);
auto defaultName = getDeviceName(nullptr); std::basic_string_view<ALCchar> defaultName = getDeviceName(nullptr);
if (mCurrentName != defaultName) if (mCurrentName != defaultName)
{ {
Log(Debug::Info) << "Default audio device changed"; mCurrentName = defaultName;
ALCboolean reopened Log(Debug::Info) << "Default audio device changed to \"" << mCurrentName << "\"";
= alcReopenDeviceSOFT(mOutput.mDevice, nullptr, mOutput.mContextAttributes.data()); ALCboolean reopened = alcReopenDeviceSOFT(
mOutput.mDevice, mCurrentName.data(), mOutput.mContextAttributes.data());
if (reopened == AL_FALSE) if (reopened == AL_FALSE)
{
mCurrentName = defaultName;
Log(Debug::Warning) << "Failed to switch to new audio device"; Log(Debug::Warning) << "Failed to switch to new audio device";
}
else
mCurrentName = getDeviceName(mOutput.mDevice);
} }
} }
mCondVar.wait_for(lock, std::chrono::seconds(2)); mCondVar.wait_for(lock, std::chrono::seconds(2));

View file

@ -38,7 +38,7 @@ MWState::Character* MWState::CharacterManager::getCurrentCharacter()
return mCurrent; return mCurrent;
} }
void MWState::CharacterManager::deleteSlot(const MWState::Character* character, const MWState::Slot* slot) void MWState::CharacterManager::deleteSlot(const MWState::Slot* slot, const MWState::Character*& character)
{ {
std::list<Character>::iterator it = findCharacter(character); std::list<Character>::iterator it = findCharacter(character);
@ -51,6 +51,7 @@ void MWState::CharacterManager::deleteSlot(const MWState::Character* character,
if (character == mCurrent) if (character == mCurrent)
mCurrent = nullptr; mCurrent = nullptr;
mCharacters.erase(it); mCharacters.erase(it);
character = nullptr;
} }
} }

View file

@ -33,7 +33,7 @@ namespace MWState
Character* getCurrentCharacter(); Character* getCurrentCharacter();
///< @note May return null ///< @note May return null
void deleteSlot(const MWState::Character* character, const MWState::Slot* slot); void deleteSlot(const MWState::Slot* slot, const Character*& character);
Character* createCharacter(const std::string& name); Character* createCharacter(const std::string& name);
///< Create new character within saved game management ///< Create new character within saved game management

View file

@ -706,10 +706,10 @@ void MWState::StateManager::quickLoad()
void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot) void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot)
{ {
const std::filesystem::path savePath = slot->mPath; const std::filesystem::path savePath = slot->mPath;
mCharacterManager.deleteSlot(character, slot); mCharacterManager.deleteSlot(slot, character);
if (mLastSavegame == savePath) if (mLastSavegame == savePath)
{ {
if (character->begin() != character->end()) if (character != nullptr)
mLastSavegame = character->begin()->mPath; mLastSavegame = character->begin()->mPath;
else else
mLastSavegame.clear(); mLastSavegame.clear();
@ -757,12 +757,14 @@ void MWState::StateManager::update(float duration)
if (mNewGameRequest) if (mNewGameRequest)
{ {
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu);
newGame(); newGame();
mNewGameRequest = false; mNewGameRequest = false;
} }
if (mLoadRequest) if (mLoadRequest)
{ {
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_MainMenu);
loadGame(*mLoadRequest); loadGame(*mLoadRequest);
mLoadRequest = std::nullopt; mLoadRequest = std::nullopt;
} }

View file

@ -337,13 +337,13 @@ namespace
// helper function for forEachInternal // helper function for forEachInternal
template <class Visitor, class List> template <class Visitor, class List>
bool forEachImp(Visitor& visitor, List& list, MWWorld::CellStore* cellStore) bool forEachImp(Visitor& visitor, List& list, MWWorld::CellStore& cellStore, bool includeDeleted)
{ {
for (typename List::List::iterator iter(list.mList.begin()); iter != list.mList.end(); ++iter) for (auto& v : list.mList)
{ {
if (!MWWorld::CellStore::isAccessible(iter->mData, iter->mRef)) if (!includeDeleted && !MWWorld::CellStore::isAccessible(v.mData, v.mRef))
continue; continue;
if (!visitor(MWWorld::Ptr(&*iter, cellStore))) if (!visitor(MWWorld::Ptr(&v, &cellStore)))
return false; return false;
} }
return true; return true;
@ -399,12 +399,12 @@ namespace MWWorld
// listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved
// objects are accounted for. // objects are accounted for.
template <class Visitor> template <class Visitor>
static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore) static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore, bool includeDeleted)
{ {
bool returnValue = true; bool returnValue = true;
Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&visitor, &returnValue, &cellStore](auto& store) { Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&](auto& store) {
returnValue = returnValue && forEachImp(visitor, store, &cellStore); returnValue = returnValue && forEachImp(visitor, store, cellStore, includeDeleted);
}); });
return returnValue; return returnValue;
@ -583,11 +583,11 @@ namespace MWWorld
mMergedRefsNeedsUpdate = true; mMergedRefsNeedsUpdate = true;
} }
void CellStore::updateMergedRefs() const void CellStore::updateMergedRefs(bool includeDeleted) const
{ {
mMergedRefs.clear(); mMergedRefs.clear();
MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell); MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell);
CellStoreImp::forEachInternal(visitor, const_cast<CellStore&>(*this)); CellStoreImp::forEachInternal(visitor, const_cast<CellStore&>(*this), includeDeleted);
visitor.merge(); visitor.merge();
mMergedRefsNeedsUpdate = false; mMergedRefsNeedsUpdate = false;
} }

View file

@ -213,13 +213,13 @@ namespace MWWorld
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed? /// \return Iteration completed?
template <class Visitor> template <class Visitor>
bool forEach(Visitor&& visitor) bool forEach(Visitor&& visitor, bool includeDeleted = false)
{ {
if (mState != State_Loaded) if (mState != State_Loaded)
return false; return false;
if (mMergedRefsNeedsUpdate) if (mMergedRefsNeedsUpdate)
updateMergedRefs(); updateMergedRefs(includeDeleted);
if (mMergedRefs.empty()) if (mMergedRefs.empty())
return true; return true;
@ -227,7 +227,7 @@ namespace MWWorld
for (LiveCellRefBase* mergedRef : mMergedRefs) for (LiveCellRefBase* mergedRef : mMergedRefs)
{ {
if (!isAccessible(mergedRef->mData, mergedRef->mRef)) if (!includeDeleted && !isAccessible(mergedRef->mData, mergedRef->mRef))
continue; continue;
if (!visitor(MWWorld::Ptr(mergedRef, this))) if (!visitor(MWWorld::Ptr(mergedRef, this)))
@ -242,17 +242,17 @@ namespace MWWorld
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed? /// \return Iteration completed?
template <class Visitor> template <class Visitor>
bool forEachConst(Visitor&& visitor) const bool forEachConst(Visitor&& visitor, bool includeDeleted = false) const
{ {
if (mState != State_Loaded) if (mState != State_Loaded)
return false; return false;
if (mMergedRefsNeedsUpdate) if (mMergedRefsNeedsUpdate)
updateMergedRefs(); updateMergedRefs(includeDeleted);
for (const LiveCellRefBase* mergedRef : mMergedRefs) for (const LiveCellRefBase* mergedRef : mMergedRefs)
{ {
if (!isAccessible(mergedRef->mData, mergedRef->mRef)) if (!includeDeleted && !isAccessible(mergedRef->mData, mergedRef->mRef))
continue; continue;
if (!visitor(MWWorld::ConstPtr(mergedRef, this))) if (!visitor(MWWorld::ConstPtr(mergedRef, this)))
@ -267,28 +267,25 @@ namespace MWWorld
/// unintended behaviour. \attention This function also lists deleted (count 0) objects! /// unintended behaviour. \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed? /// \return Iteration completed?
template <class T, class Visitor> template <class T, class Visitor>
bool forEachType(Visitor&& visitor) bool forEachType(Visitor&& visitor, bool includeDeleted = false)
{ {
if (mState != State_Loaded) if (mState != State_Loaded)
return false; return false;
if (mMergedRefsNeedsUpdate) if (mMergedRefsNeedsUpdate)
updateMergedRefs(); updateMergedRefs(includeDeleted);
if (mMergedRefs.empty()) if (mMergedRefs.empty())
return true; return true;
mHasState = true; mHasState = true;
CellRefList<T>& list = get<T>(); for (LiveCellRefBase& base : get<T>().mList)
for (typename CellRefList<T>::List::iterator it(list.mList.begin()); it != list.mList.end(); ++it)
{ {
LiveCellRefBase* base = &*it; if (mMovedToAnotherCell.contains(&base))
if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end())
continue; continue;
if (!isAccessible(base->mData, base->mRef)) if (!includeDeleted && !isAccessible(base.mData, base.mRef))
continue; continue;
if (!visitor(MWWorld::Ptr(base, this))) if (!visitor(MWWorld::Ptr(&base, this)))
return false; return false;
} }
@ -406,7 +403,7 @@ namespace MWWorld
/// Repopulate mMergedRefs. /// Repopulate mMergedRefs.
void requestMergedRefsUpdate(); void requestMergedRefsUpdate();
void updateMergedRefs() const; void updateMergedRefs(bool includeDeleted = false) const;
// (item, max charge) // (item, max charge)
typedef std::vector<std::pair<LiveCellRefBase*, float>> TRechargingItems; typedef std::vector<std::pair<LiveCellRefBase*, float>> TRechargingItems;

View file

@ -406,8 +406,7 @@ namespace MWWorld
template <class T> template <class T>
ContainerStoreIteratorBase(const ContainerStoreIteratorBase<T>& other) ContainerStoreIteratorBase(const ContainerStoreIteratorBase<T>& other)
{ {
char CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR[IsConvertible<T, PtrType, void>::value ? 1 : -1]; static_assert(IsConvertible<T, PtrType, void>::value);
((void)CANNOT_CONVERT_CONST_ITERATOR_TO_ITERATOR);
copy(other); copy(other);
} }

View file

@ -70,7 +70,7 @@ namespace MWWorld
mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr); mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr);
reader.setModIndex(index); reader.setModIndex(index);
reader.updateModIndices(mNameToIndex); reader.updateModIndices(mNameToIndex);
mStore.loadESM4(reader); mStore.loadESM4(reader, listener);
break; break;
} }
} }

View file

@ -468,9 +468,16 @@ namespace MWWorld
} }
} }
void ESMStore::loadESM4(ESM4::Reader& reader) void ESMStore::loadESM4(ESM4::Reader& reader, Loading::Listener* listener)
{ {
auto visitorRec = [this](ESM4::Reader& reader) { return ESMStoreImp::readRecord(reader, *this); }; if (listener != nullptr)
listener->setProgressRange(::EsmLoader::fileProgress);
auto visitorRec = [this, listener](ESM4::Reader& reader) {
bool result = ESMStoreImp::readRecord(reader, *this);
if (listener != nullptr)
listener->setProgress(::EsmLoader::fileProgress * reader.getFileOffset() / reader.getFileSize());
return result;
};
ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {}); ESM4::ReaderUtils::readAll(reader, visitorRec, [](ESM4::Reader&) {});
} }

View file

@ -219,7 +219,7 @@ namespace MWWorld
void validateDynamic(); void validateDynamic();
void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); void load(ESM::ESMReader& esm, Loading::Listener* listener, ESM::Dialogue*& dialogue);
void loadESM4(ESM4::Reader& esm); void loadESM4(ESM4::Reader& esm, Loading::Listener* listener);
template <class T> template <class T>
const Store<T>& get() const const Store<T>& get() const

View file

@ -367,7 +367,7 @@ namespace MWWorld
ListAndResetObjectsVisitor visitor; ListAndResetObjectsVisitor visitor;
cell->forEach(visitor); cell->forEach(visitor, true); // Include objects being teleported by Lua
for (const auto& ptr : visitor.mObjects) for (const auto& ptr : visitor.mObjects)
{ {
if (const auto object = mPhysics->getObject(ptr)) if (const auto object = mPhysics->getObject(ptr))

View file

@ -290,14 +290,14 @@ namespace MWWorld
mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat(); mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
} }
void World::init(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, SceneUtil::WorkQueue* workQueue, void World::init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
SceneUtil::UnrefQueue& unrefQueue) SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue)
{ {
mPhysics = std::make_unique<MWPhysics::PhysicsSystem>(mResourceSystem, rootNode); mPhysics = std::make_unique<MWPhysics::PhysicsSystem>(mResourceSystem, rootNode);
if (Settings::navigator().mEnable) if (Settings::navigator().mEnable)
{ {
auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(maxRecastLogLevel);
navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale;
mNavigator = DetourNavigator::makeNavigator(navigatorSettings, mUserDataPath); mNavigator = DetourNavigator::makeNavigator(navigatorSettings, mUserDataPath);
} }

View file

@ -4,6 +4,7 @@
#include <osg/Timer> #include <osg/Timer>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <components/debug/debuglog.hpp>
#include <components/esm3/readerscache.hpp> #include <components/esm3/readerscache.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -201,8 +202,8 @@ namespace MWWorld
Loading::Listener* listener); Loading::Listener* listener);
// Must be called after `loadData`. // Must be called after `loadData`.
void init(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, SceneUtil::WorkQueue* workQueue, void init(Debug::Level maxRecastLogLevel, osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
SceneUtil::UnrefQueue& unrefQueue); SceneUtil::WorkQueue* workQueue, SceneUtil::UnrefQueue& unrefQueue);
virtual ~World(); virtual ~World();

View file

@ -2,6 +2,7 @@
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/settings/parser.hpp> #include <components/settings/parser.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include <components/testing/util.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -24,5 +25,9 @@ int main(int argc, char* argv[])
Settings::StaticValues::init(); Settings::StaticValues::init();
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
const int result = RUN_ALL_TESTS();
if (result == 0)
std::filesystem::remove_all(TestingOpenMW::outputDir());
return result;
} }

View file

@ -59,7 +59,7 @@ list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}
add_component_dir (lua add_component_dir (lua
luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8
shapes/box inputactions yamlloader scripttracker shapes/box inputactions yamlloader scripttracker luastateptr
) )
add_component_dir (l10n add_component_dir (l10n

View file

@ -1,6 +1,7 @@
#include "gamesettings.hpp" #include "gamesettings.hpp"
#include <QDir> #include <QDir>
#include <QProgressDialog>
#include <QRegularExpression> #include <QRegularExpression>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
@ -37,8 +38,13 @@ void Config::GameSettings::validatePaths()
mDataDirs.clear(); mDataDirs.clear();
QProgressDialog progressBar("Validating paths", {}, 0, paths.count() + 1);
progressBar.setWindowModality(Qt::WindowModal);
progressBar.setValue(0);
for (const auto& dataDir : paths) for (const auto& dataDir : paths)
{ {
progressBar.setValue(progressBar.value() + 1);
if (QDir(dataDir.value).exists()) if (QDir(dataDir.value).exists())
{ {
SettingValue copy = dataDir; SettingValue copy = dataDir;
@ -50,6 +56,8 @@ void Config::GameSettings::validatePaths()
// Do the same for data-local // Do the same for data-local
const QString& local = mSettings.value(QString("data-local")).value; const QString& local = mSettings.value(QString("data-local")).value;
progressBar.setValue(progressBar.value() + 1);
if (!local.isEmpty() && QDir(local).exists()) if (!local.isEmpty() && QDir(local).exists())
{ {
mDataLocal = QDir(local).canonicalPath(); mDataLocal = QDir(local).canonicalPath();
@ -274,9 +282,8 @@ bool Config::GameSettings::isOrderedLine(const QString& line)
// - Always ignore a line beginning with '#' or empty lines; added above a config // - Always ignore a line beginning with '#' or empty lines; added above a config
// entry. // entry.
// //
// - If a line in file exists with matching key and first part of value (before ',', // - If a line in file exists with matching key and value, then replace the line with that of mUserSettings.
// '\n', etc) also matches, then replace the line with that of mUserSettings. // - else if only the key matches, remove comment
// - else remove line
// //
// - If there is no corresponding line in file, add at the end // - If there is no corresponding line in file, add at the end
// //
@ -302,6 +309,25 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
if (fileCopy.empty()) if (fileCopy.empty())
return writeFile(stream); return writeFile(stream);
QMultiMap<QString, SettingValue> existingSettings;
QString context = QFileInfo(file).absoluteDir().path();
if (readFile(stream, existingSettings, context))
{
// don't use QMultiMap operator== as mUserSettings may have blank context fields
// don't use one std::equal with custom predicate as (until Qt 6.4) there was no key-value iterator
if (std::equal(existingSettings.keyBegin(), existingSettings.keyEnd(), mUserSettings.keyBegin(),
mUserSettings.keyEnd())
&& std::equal(existingSettings.cbegin(), existingSettings.cend(), mUserSettings.cbegin(),
[](const SettingValue& l, const SettingValue& r) {
return l.originalRepresentation == r.originalRepresentation;
}))
{
// The existing file already contains what we need, don't risk scrambling comments and formatting
return true;
}
}
stream.seek(0);
// start // start
// | // |
// | +----------------------------------------------------------+ // | +----------------------------------------------------------+
@ -325,7 +351,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
// +----------------------------------------------------------+ // +----------------------------------------------------------+
// //
// //
QRegularExpression settingRegex("^([^=]+)\\s*=\\s*([^,]+)(.*)$"); QRegularExpression settingRegex("^([^=]+)\\s*=\\s*(.+?)\\s*$");
std::vector<QString> comments; std::vector<QString> comments;
auto commentStart = fileCopy.end(); auto commentStart = fileCopy.end();
std::map<QString, std::vector<QString>> commentsMap; std::map<QString, std::vector<QString>> commentsMap;
@ -395,8 +421,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
// look for a key in the line // look for a key in the line
if (!match.hasMatch() || settingRegex.captureCount() < 2) if (!match.hasMatch() || settingRegex.captureCount() < 2)
{ {
// no key or first part of value found in line, replace with a null string which // no key or no value found in line, replace with a null string which will be removed later
// will be removed later
*iter = QString(); *iter = QString();
comments.clear(); comments.clear();
commentStart = fileCopy.end(); commentStart = fileCopy.end();

View file

@ -10,6 +10,7 @@
#include <QDataStream> #include <QDataStream>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QDirIterator>
#include <QFont> #include <QFont>
#include <QIODevice> #include <QIODevice>
@ -78,14 +79,10 @@ ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(int row)
} }
const ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(const QString& name) const const ContentSelectorModel::EsmFile* ContentSelectorModel::ContentModel::item(const QString& name) const
{ {
EsmFile::FileProperty fp = EsmFile::FileProperty_FileName; bool path = name.contains('/');
if (name.contains('/'))
fp = EsmFile::FileProperty_FilePath;
for (const EsmFile* file : mFiles) for (const EsmFile* file : mFiles)
{ {
if (name.compare(file->fileProperty(fp).toString(), Qt::CaseInsensitive) == 0) if (name.compare(path ? file->filePath() : file->fileName(), Qt::CaseInsensitive) == 0)
return file; return file;
} }
return nullptr; return nullptr;
@ -310,7 +307,6 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
{ {
setCheckState(file->filePath(), success); setCheckState(file->filePath(), success);
emit dataChanged(index, index); emit dataChanged(index, index);
checkForLoadOrderErrors();
} }
else else
return success; return success;
@ -425,7 +421,6 @@ bool ContentSelectorModel::ContentModel::dropMimeData(
dataChanged(index(minRow, 0), index(maxRow, 0)); dataChanged(index(minRow, 0), index(maxRow, 0));
// at this point we know that drag and drop has finished. // at this point we know that drag and drop has finished.
checkForLoadOrderErrors();
return true; return true;
} }
@ -552,15 +547,13 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
bool ContentSelectorModel::ContentModel::containsDataFiles(const QString& path) bool ContentSelectorModel::ContentModel::containsDataFiles(const QString& path)
{ {
QDir dir(path);
QStringList filters; QStringList filters;
filters << "*.esp" filters << "*.esp"
<< "*.esm" << "*.esm"
<< "*.omwgame" << "*.omwgame"
<< "*.omwaddon"; << "*.omwaddon";
dir.setNameFilters(filters); QDirIterator it(path, filters, QDir::Files | QDir::NoDotAndDotDot);
return it.hasNext();
return dir.entryList().count() != 0;
} }
void ContentSelectorModel::ContentModel::clearFiles() void ContentSelectorModel::ContentModel::clearFiles()
@ -707,12 +700,13 @@ void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fi
bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const
{ {
return mPluginsWithLoadOrderError.contains(file->filePath()); int index = indexFromItem(file).row();
auto errors = checkForLoadOrderErrors(file, index);
return !errors.empty();
} }
void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileList) void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileList)
{ {
mPluginsWithLoadOrderError.clear();
int previousPosition = -1; int previousPosition = -1;
for (const QString& filepath : fileList) for (const QString& filepath : fileList)
{ {
@ -725,7 +719,6 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
if (filePosition < previousPosition) if (filePosition < previousPosition)
{ {
mFiles.move(filePosition, previousPosition); mFiles.move(filePosition, previousPosition);
emit dataChanged(index(filePosition, 0, QModelIndex()), index(previousPosition, 0, QModelIndex()));
} }
else else
{ {
@ -733,24 +726,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
} }
} }
} }
checkForLoadOrderErrors(); emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
}
void ContentSelectorModel::ContentModel::checkForLoadOrderErrors()
{
for (int row = 0; row < mFiles.count(); ++row)
{
EsmFile* file = mFiles.at(row);
bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0;
if (isRowInError)
{
mPluginsWithLoadOrderError.insert(file->filePath());
}
else
{
mPluginsWithLoadOrderError.remove(file->filePath());
}
}
} }
QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::checkForLoadOrderErrors( QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::checkForLoadOrderErrors(
@ -791,11 +767,12 @@ QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::
QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const
{ {
if (isLoadOrderError(file)) int index = indexFromItem(file).row();
auto errors = checkForLoadOrderErrors(file, index);
if (!errors.empty())
{ {
QString text("<b>"); QString text("<b>");
int index = indexFromItem(item(file->filePath())).row(); for (const LoadOrderError& error : errors)
for (const LoadOrderError& error : checkForLoadOrderErrors(file, index))
{ {
assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None); assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None);
@ -900,7 +877,6 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke
void ContentSelectorModel::ContentModel::uncheckAll() void ContentSelectorModel::ContentModel::uncheckAll()
{ {
emit layoutAboutToBeChanged();
mCheckedFiles.clear(); mCheckedFiles.clear();
emit layoutChanged(); emit dataChanged(index(0, 0), index(rowCount(), columnCount()), { Qt::CheckStateRole, Qt::UserRole + 1 });
} }

View file

@ -69,9 +69,6 @@ namespace ContentSelectorModel
void refreshModel(); void refreshModel();
/// Checks all plug-ins for load order errors and updates mPluginsWithLoadOrderError with plug-ins with issues
void checkForLoadOrderErrors();
private: private:
void addFile(EsmFile* file); void addFile(EsmFile* file);
@ -89,7 +86,6 @@ namespace ContentSelectorModel
QStringList mNonUserContent; QStringList mNonUserContent;
std::set<const EsmFile*> mCheckedFiles; std::set<const EsmFile*> mCheckedFiles;
QHash<QString, bool> mNewFiles; QHash<QString, bool> mNewFiles;
QSet<QString> mPluginsWithLoadOrderError;
QString mEncoding; QString mEncoding;
QIcon mWarningIcon; QIcon mWarningIcon;
QIcon mErrorIcon; QIcon mErrorIcon;

View file

@ -48,18 +48,18 @@ namespace ContentSelectorModel
void addGameFile(const QString& name) { mGameFiles.append(name); } void addGameFile(const QString& name) { mGameFiles.append(name); }
QVariant fileProperty(const FileProperty prop) const; QVariant fileProperty(const FileProperty prop) const;
QString fileName() const { return mFileName; } const QString& fileName() const { return mFileName; }
QString author() const { return mAuthor; } const QString& author() const { return mAuthor; }
QDateTime modified() const { return mModified; } QDateTime modified() const { return mModified; }
QString formatVersion() const { return mVersion; } const QString& formatVersion() const { return mVersion; }
QString filePath() const { return mPath; } const QString& filePath() const { return mPath; }
bool builtIn() const { return mBuiltIn; } bool builtIn() const { return mBuiltIn; }
bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; } bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; }
bool isMissing() const { return mPath.isEmpty(); } bool isMissing() const { return mPath.isEmpty(); }
/// @note Contains file names, not paths. /// @note Contains file names, not paths.
const QStringList& gameFiles() const { return mGameFiles; } const QStringList& gameFiles() const { return mGameFiles; }
QString description() const { return mDescription; } const QString& description() const { return mDescription; }
QString toolTip() const QString toolTip() const
{ {
if (isMissing()) if (isMissing())

View file

@ -211,7 +211,6 @@ void ContentSelectorView::ContentSelector::addFiles(const QString& path, bool ne
ui->gameFileView->setCurrentIndex(0); ui->gameFileView->setCurrentIndex(0);
mContentModel->uncheckAll(); mContentModel->uncheckAll();
mContentModel->checkForLoadOrderErrors();
} }
void ContentSelectorView::ContentSelector::sortFiles() void ContentSelectorView::ContentSelector::sortFiles()
@ -254,7 +253,6 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i
oldIndex = index; oldIndex = index;
setGameFileSelected(index, true); setGameFileSelected(index, true);
mContentModel->checkForLoadOrderErrors();
} }
emit signalCurrentGamefileIndexChanged(index); emit signalCurrentGamefileIndexChanged(index);

Some files were not shown because too many files have changed in this diff Show more