Compare commits

...

189 Commits

Author SHA1 Message Date
psi29a 4f919d9239 Merge branch 'fix_element_destroy' into 'master'
Fix bug in LuaUi::Element::destroy() that sometimes leads to an infinite loop on UI cleanup

See merge request OpenMW/openmw!3033

(cherry picked from commit 364bc91f5b)

c6eed2a6 Fix bug in LuaUi::Element::destroy() that sometimes leads to an infinite loop on UI cleanup
1 year ago
psi29a 0e141dc06e Merge branch 'fix_navmeshtool_avoid_shape_48' into 'openmw-48'
Use different object id for avoid shape (0.48)

See merge request OpenMW/openmw!3019
1 year ago
elsid 227b4d3e5e
Use different object id for avoid shape
Otherwise addObject will ignore it as a duplicate and resulting recastmesh will
not match generated by the engine causing navmeshdb cache miss.
1 year ago
psi29a 53f3a5b49b Merge branch 'fix_local_map_update_48' into 'openmw-48'
Update cell local map on different neighbour cells (0.48) (#7140)

See merge request OpenMW/openmw!3015
1 year ago
elsid de99c74ef9
Fix integration tests job deps 1 year ago
elsid ab0a460a6f
Update cell local map on different neighbour cells 1 year ago
psi29a 9dbf2e708b Merge branch 'lua_casting_48' into 'openmw-48'
Fix crash on sol::object type mismatch in invalid Lua script (!2975 for 0.48)

See merge request OpenMW/openmw!2978
1 year ago
Petr Mikheev d18588dce7 Use LuaUtil::cast for casting sol::object to prevent crashing on type mismatch in Lua scripts. 1 year ago
Petr Mikheev 51cbfa84fa Add function LuaUtil::cast 1 year ago
psi29a 2d25ffeb89 Merge branch 'openmw-48-patch-50cc' into 'openmw-48'
fix broken macos builds

See merge request OpenMW/openmw!2869
1 year ago
psi29a 37df58cc79 fix macos issue 1 year ago
psi29a ed528c5d70 Update file luascripts.hpp 1 year ago
psi29a 64ca3e71bb Merge branch 'fix_launcher_paths_48' into 'openmw-48'
Save original paths in launcher (#7246) for 0.48

See merge request OpenMW/openmw!2849
1 year ago
elsid 80387049c1
Save original paths
If directory path is a symlink it should be showed and written to config files
as is. Between launcher runs the resulting canonical path may be different so
the resolved path becomes outdated.
1 year ago
elsid 9f00b99b22
Use set to track visited directories instead of removing duplicates 1 year ago
psi29a da5376ff3a Merge branch 'cstemprefs48' into 'openmw-48'
!2844 for 0.48

See merge request OpenMW/openmw!2846
1 year ago
Evil Eye 47d75c70ea Don't capture temporaries by reference 1 year ago
psi29a 3f19dc62e5 Merge branch '1372_for_048' into 'openmw-48'
MR 1372 for 0.48. Unbreak boats mods and scripted transformations

See merge request OpenMW/openmw!2841
1 year ago
fredzio 76c6848121 In 0.46, SetPos was setting position of actors before physics simulation, and from this position movement was simulated. This changed with async physics merging, and at the same time problems started, mostly with abot's scenic travel.
Skipping the simulation, switching off collisions, and other approaches were not correct as they either broke some mods, or some core mechanics of the engine such as teleportation or waterwalking. As it turns out, the way to go is to simply do _nothing_ (modulo some gymnastics to account for the 1 frame difference in case of async).

Scripted movement and the unstucking logic tends to collide. Early out of unstuck in case the actor doesn't attempt to move. This means there is no AI package for NPC, which are the case for some boats and striders, or the player is content with their position.
1 year ago
psi29a f81e78e4c1 Merge branch 'update-appdata-48' into 'openmw-48'
Update AppData summary to be more concise (for 0.48.0)

See merge request OpenMW/openmw!2828
1 year ago
Alexander Olofsson d4fe16415a Update AppData summary to be more concise
This brings the AppData in line with the Debian package title
1 year ago
psi29a fb3a80bebd Merge branch 'openmw-48-stereo-fixes' into 'openmw-48'
Openmw 48 stereo fixes

See merge request OpenMW/openmw!2815
1 year ago
Alexei Kotov e093474ba2 Merge branch '2805-for-48' into 'openmw-48'
2805 for OpenMW 0.48

See merge request OpenMW/openmw!2817
1 year ago
Mads Buvik Sandvei e5582799a2 Empty-Commit to trigger pipeline 1 year ago
AnyOldName3 681026ba1c Ensure shader requirements are pushed at least once for subgraph
Shaders, if deemed necessary, get attached to the node mentioned by the
top of the requirements stack. Previously an empty stack was incorrectly
assumed to mean no shaders were required, but we found out that was
wrong. We need to put shaders *somewhere*, and the root of the subgraph
we're modifying should be the best place.
1 year ago
AnyOldName3 c223b214bd Revert "Attach shaders to geometry that lacks a stateset if necessary (bug #7251)"
This reverts commit d33be39fb6.
1 year ago
Mads Buvik Sandvei 7d057600ea SkyStereoStatesetUpdater was incorrectly always using reverseZ matrices. 1 year ago
Mads Buvik Sandvei 00e02bb326 Change assosiative order of stereo-related matrix multiplications to reduce FP errors. 1 year ago
Mads Buvik Sandvei 93a72a6368 update OSG multiview build to one that includes missing dll osgdb_dae.dll 1 year ago
psi29a 5c2f4f462e Merge branch 'fix_physics_deadlock_48' into 'openmw-48'
Do not use std::shared_mutex to wait for job for async physics (0.48)

See merge request OpenMW/openmw!2799
1 year ago
elsid 2c0e64510c
Do not use std::shared_mutex to wait for job for async physics
std::shared_mutex in combination with std::condition_variable_any may
lead to a situation when notify_all does not wake up all waiting threads
on Windows. Use separate std::mutex and std::condition_variable to
notify about new job. Encapsulate all workers synchronization logic into
a separate type.
1 year ago
psi29a bb3ef08ac5 Merge branch 'maybe-fix-freeze-catcher' into 'master'
Fix Windows freeze catcher

See merge request OpenMW/openmw!2795

(cherry picked from commit afa6643c6e)

5b3e9e15 Maybe fix Windows freeze catcher
1 year ago
psi29a 0fb7d93219 Merge branch 'limit_max_bullet_supported_threads' into 'master'
Limit max bullet supported threads by BT_MAX_THREAD_COUNT - 1

See merge request OpenMW/openmw!2797

(cherry picked from commit 31ae1cd339)

949b9191 Limit max bullet supported threads by BT_MAX_THREAD_COUNT - 1
1 year ago
psi29a a6989dcb12 Merge branch 'shadervisitor48' into 'openmw-48'
Fixes to shader rendering state handling (0.48.0)

See merge request OpenMW/openmw!2783
1 year ago
psi29a 5dfd2709b7 Merge branch 'fix_clamp_physics_threads' into 'master'
Fix clamping physics threads

See merge request OpenMW/openmw!2792

(cherry picked from commit fbb00027d8)

3d3cccb8 Fix clamping physics threads
1 year ago
Alexei Dobrohotov ed44095cdc Use Rig/MorphGeometry state for its child geometry 1 year ago
Alexei Dobrohotov d33be39fb6 Attach shaders to geometry that lacks a stateset if necessary (bug #7251) 1 year ago
psi29a a7116aacbc Merge branch 'helios-stop-blinking' into 'openmw-48'
!2768 for 0.48.0

See merge request OpenMW/openmw!2793
1 year ago
Cody Glassman 7fb4acaaf2 fix sunglare flash, make sure all bound render targets have color masks set 1 year ago
psi29a 7a52b9d1d8 Merge branch 'kantoniak/lua-docs-fixes' into 'master'
Apply minor fixes to Lua documentation

See merge request OpenMW/openmw!2785

(cherry picked from commit 56c8c25a0e)

ccdd381f Minor fixes to Lua documentation
1 year ago
psi29a 66bd283d4b Merge branch 'fix_7210_48' into 'openmw-48'
!2737 for 0.48 (fixes #7210)

See merge request OpenMW/openmw!2780
1 year ago
Petr Mikheev 47df1ca1f8 !2737 for 0.48 (fixes #7210) 1 year ago
psi29a 56d9758af2 Merge branch 'silicon_fix' into 'master'
Fix executable for silicon builds

See merge request OpenMW/openmw!2767

(cherry picked from commit 3979d540b1)

f729a280 Fix executable for silicon builds
1 year ago
psi29a 2f6a809d18 Merge branch 'fix_lua_48' into 'openmw-48'
Merge !2661, !2687, !2733, !2770, !2774 to openmw-48 (fixes #7128)

See merge request OpenMW/openmw!2778
1 year ago
Petr Mikheev ae23daf82d [Lua] Fix memory leak in sandboxed "require" (!2774 for 0.48). 1 year ago
uramer e476cb24e7 Add a note in the docs (!2770 for 0.48) 1 year ago
uramer ed5e0ff4f3 Set Element layout to nil on destroy (!2770 for 0.48) 1 year ago
Petr Mikheev 95219e6fa2 Fix bug in lua_ui/content.lua: getmetatable(ui.content{}) shouldn't return a global mutable table (!2733 for 0.48) 1 year ago
Petr Mikheev 1540c9679f Don't expose LuaUtil::Callback to lua (!2733 for 0.48) 1 year ago
Petr Mikheev 628017a817 Move asyncpackage from apps/openmw/mwlua to components/lua (!2733 for 0.48) 1 year ago
Bret Curtis e206081d86 make sure lua works with macos packages (!2687 for 0.48) 1 year ago
uramer 4b2ef32b86 Move implementation of UI Content to Lua (!2661 for 0.48) 1 year ago
psi29a 15236faf03 Merge branch 'openal48' into 'openmw-48'
!2748 for 0.48

See merge request OpenMW/openmw!2772
1 year ago
Evil Eye c2d6c29028 Bump OpenAL to 1.23.0 on Windows 1 year ago
psi29a d95a03a8ba Merge branch 'fix_7223' into 'master'
Fix #7223

Closes #7223

See merge request OpenMW/openmw!2736

(cherry picked from commit a43b6fba4b)

0c3237ad Fix #7223
1 year ago
psi29a c9eb08dbc3 Merge branch 'took_an_arrow_to_the_disposition_cap48' into 'openmw-48'
!2734 for 0.48

See merge request OpenMW/openmw!2735
1 year ago
Evil Eye 8e73d49602 Cap temporary disposition gain and compute permanent changes accordingly 1 year ago
psi29a 6622e9df5b Merge branch 'fix_physics_locking_48' into 'openmw-48'
Use shared locks in physics system when using multithreaded bullet (#7218) (0.48)

See merge request OpenMW/openmw!2727
1 year ago
elsid 09199ea006
Use shared locks in physics system when using multithreaded bullet 1 year ago
psi29a e0a25e02f0 Merge branch 'postprocesshud48' into 'openmw-48'
Improve post-process HUD search field usability (0.48.0)

See merge request OpenMW/openmw!2718
1 year ago
Alexei Kotov 3ab719f5a6 Improve post-process HUD search field usability (0.48.0) 1 year ago
psi29a 1dc71a133e Merge branch 'ingredientbindings48' into 'openmw-48'
Fix Lua ingredient bindings (0.48.0)

See merge request OpenMW/openmw!2707
1 year ago
Alexei Kotov 15df41f459 Fix Lua ingredient bindings 1 year ago
psi29a 3dd8fd2ef9 Merge branch 'handle_bad_navmeshtool_message_magic_48' into 'openmw-48'
Stop updating navmeshtool progress on first bad message (0.48)

See merge request OpenMW/openmw!2660
1 year ago
psi29a 7211779889 Merge branch 'fix_gpu_osg_stats' into 'master'
Delay OSG stats reporting for 3 frames

See merge request OpenMW/openmw!2677

(cherry picked from commit 9c92a8ab57)

96ea1903 Delay OSG stats reporting for 3 frames
1 year ago
psi29a 41b5ddf010 Merge branch 'lost_archives48' into 'openmw-48'
Restore 0936d716d96cd45b3efc2d1a3697614f7f96952d

See merge request OpenMW/openmw!2673
1 year ago
Evil Eye b24ea17301 Restore 0936d716d96cd45b3efc2d1a3697614f7f96952d 1 year ago
psi29a 4c5d03c5e5 Merge branch 'changelogforanilayer' into 'openmw-48'
Changelog for osga-animation layering

See merge request OpenMW/openmw!2666
1 year ago
unelsson 8d5edacffd Changelog for osga-animation layering 1 year ago
psi29a f3f65c87d6 Merge branch 'docs_fix' into 'openmw-48'
Remove accidental copy-paste in global.doclua

See merge request OpenMW/openmw!2653
1 year ago
elsid f23866be90
Stop updating navmeshtool progress on first bad message 1 year ago
uramer a3a5a227ad Remove accidental copy-paste in global.doclua 1 year ago
psi29a 37152b4917 Merge branch 'no_teleporting_before_you_eat_your_veggies48' into 'openmw-48'
!2631 for 0.48

See merge request OpenMW/openmw!2633
1 year ago
Evil Eye c1b9a91877 Delay teleportation till the menu is closed 1 year ago
psi29a e30d5a5a35 Merge branch 'safely_on_the_other_side_of_the_door_48' into 'openmw-48'
!2619 for 0.48

See merge request OpenMW/openmw!2622
1 year ago
Evil Eye bb36155a54 End combat when the target is outside the active grid 1 year ago
psi29a e6b6f2ced6 Merge branch 'cuphead48' into 'openmw-48'
Regression fixes for animation-less knockout [0.48]

See merge request OpenMW/openmw!2590
1 year ago
Alexei Kotov d327e92d7f Don't cancel animation-less knockout 1 year ago
psi29a eebaf2b61e Merge branch 'fix_hour_modulo_48' into 'openmw-48'
Fix hour modulo expression (#7121) for 0.48

See merge request OpenMW/openmw!2560
1 year ago
Alexei Kotov 98997d893f Merge branch 'bug-fix-7116-openmw48' into 'openmw-48'
Connect zoom in/out only when the option is allowed

See merge request OpenMW/openmw!2559
1 year ago
elsid 9c34ef8720
Fix hour modulo expression
Round result of std::fmod(hours, 24) to the nearest float below 24 on double to
float conversion when it is not. Add special type and conversion function along
with tests to be used in all places where such conversion happens.

To avoid producing hours equal to 24 due to double to float precision loss.
1 year ago
Cédric Mocquillon 30e2ea2951 Connect zoom in/out only when the option is allowed 1 year ago
psi29a 01be3d16b2 Merge branch 'classless48' into 'openmw-48'
!2552 for 0.48

See merge request OpenMW/openmw!2554
1 year ago
Evil Eye 7d97ca6cd5 Don't call getClass on an empty Ptr 1 year ago
psi29a 5b56fe8d0d Merge branch 'drippy-48' into 'openmw-48'
!2537 for 0.48

See merge request OpenMW/openmw!2545
1 year ago
Evil Eye 08a6d9e2cf Ignore non-3D agents in the navigator 1 year ago
psi29a 678898b8f6 Merge branch 'keep_calm_and_get_hit_48' into 'openmw-48'
!2522 for 0.48

See merge request OpenMW/openmw!2538
1 year ago
psi29a b40ac6dce5 Merge branch 'give-up-on-thinking-up-a-way-to-determine-this-automatically-per-object-openmw-48' into 'openmw-48'
Add a setting to control coverage adjustment (!2536 cherry-pick)

See merge request OpenMW/openmw!2539
1 year ago
AnyOldName3 33e39a0360 Add a setting to control coverage adjustment
With it on, which was always the case before this setting was added,
vanilla content and poorly-made mods will look acceptable, but well-made
mods will have alpha-tested meshes appear to grow and potentially gain a
weird outline as they get further away.

With it off, which replicates the 0.46 behaviour, well-made mods will
look really good, but vanilla content and poorly-made mods will have
alpha-tested meshes shrink as they get further away.

It's been bugging me that this was forced on since 0.47 released, and
I'd hoped to figure out a solution for automatic detection at some point
before 0.48 branched off, but I didn't, so now this is what we're
getting to have Tamriel Rebuilt look right.
1 year ago
Evil Eye 5188d2a7f9 Update reference 1 year ago
Evil Eye 92761f0527 Add option to restore non-MCP Calm spell behaviour 1 year ago
psi29a fff497c205 Merge branch 'lua_gc_48' into 'openmw-48'
[0.48] Run Lua GC in every frame

See merge request OpenMW/openmw!2531
1 year ago
Petr Mikheev 734f09abe6 Run Lua GC in every frame 1 year ago
psi29a 9bbc4a54c1 Merge branch 'dial_early_48' into 'openmw-48'
!2524 for 0.48

See merge request OpenMW/openmw!2525
1 year ago
Evil Eye 3b8b5aee39 Add an early out to dialogue loading to match Morrowind.exe behaviour 1 year ago
Andrei Kortunov 3e70fc2577 Add missing translations 2 years ago
Andrei Kortunov babd9ee24b Improve postprocess HUD layout 2 years ago
psi29a e1e7f3e135 Merge branch 'fix_7056' into 'master'
Add missing content=builtin.omwscripts if openmw is started via openmw-cs

Closes #7056

See merge request OpenMW/openmw!2496

(cherry picked from commit c2b495a187)

6025943f Add missing content=builtin.omwscripts if openmw is started via openmw-cs (fixes #7056)
2 years ago
psi29a be89953368 Merge branch 'fix_settings_reloadlua' into 'master'
Clear storage of setting groups on reloadlua

See merge request OpenMW/openmw!2497

(cherry picked from commit 523fabe1e8)

17891600 Clear storage of setting groups on reloadlua
2 years ago
psi29a 30b1b2f911 Merge branch 'fix_7005' into 'master'
Fix Lua settings UI not working after a save (#7005)

See merge request OpenMW/openmw!2495

(cherry picked from commit 084396f29e)

589d7e82 Fix Lua settings UI not working after a save (#7005)
2 years ago
psi29a 2580cbfe67 Merge branch 'time_stamp_48' into 'openmw-48'
Support negative days in TimeStamp for 0.48 (#6981)

See merge request OpenMW/openmw!2493
2 years ago
elsid 4f360e6374
Support negative days in TimeStamp
As vanilla engine does.
2 years ago
psi29a a5b1ce77ea Merge branch 'flying_position48' into 'openmw-48'
Only force adjust the player and NPCs teleported out of the active grid

See merge request OpenMW/openmw!2489
2 years ago
Evil Eye df5eac88b8 Only force adjust the player and NPCs teleported out of the active grid 2 years ago
psi29a e0c2131cd7 Merge branch 'bound48' into 'openmw-48'
Allow bound effects to be recast if they're attached to a recastable effect 0.48

See merge request OpenMW/openmw!2487
2 years ago
Evil Eye 2881a30e9e Allow bound effects to be recast if they're attached to a recastable effect 2 years ago
psi29a ddc90bc778 Merge branch 'async_crashes_48' into 'openmw-48'
Pull fixes for async Lua into 0.48

See merge request OpenMW/openmw!2482
2 years ago
Petr Mikheev 710ad11dc8 Fix #7039: freeze after throwing an error in a queued Lua callback 2 years ago
uramer 5cf96a808e Lua coroutine crash tests 2 years ago
uramer 4f25796029 Execute async callbacks on the main Lua stack 2 years ago
psi29a dc9ba9dc80 Merge branch 'grayscale_dialogue_48' into 'openmw-48'
Dialogue issues 0.48

See merge request OpenMW/openmw!2480
2 years ago
Evil Eye d6fcf54438 Mark constant methods const 2 years ago
Evil Eye 53c03b65cf Prevent potentially returning garbage flags 2 years ago
Evil Eye 7e8da3dc9c Don't use potentially invalid cache entry 2 years ago
Evil Eye 1e16900d97 Merge branch 'water_walking_evasion_48' into 'openmw-48'
Adjust initial distance when destination is changed for obstacle check (#6860) (0.48)

See merge request OpenMW/openmw!2442
2 years ago
psi29a fedd9191d5 Merge branch 'fix_reset_animation_48' into 'openmw-48'
Fix resetting player's animation on game loading (#7030) (0.48)

See merge request OpenMW/openmw!2449
2 years ago
elsid 70fe539ee4
Fix resetting player's animation on game loading
When game is loaded player's animation is replaced by a new object. Old object
is destructed without explicit removeFromScene call.
2 years ago
psi29a 37ee7170cb Merge branch 'lua_stats_used_memory_48' into 'openmw-48'
Report used memory by Lua interpreter (0.48)

See merge request OpenMW/openmw!2445
2 years ago
elsid a74b842e16
Report used memory by Lua interpreter 2 years ago
elsid ff44c96118
Adjust initial distance when destination is changed for obstacle check
Changed destination may create a situation when the distance actor moved between
2 update calls is less than initial distance because destination has been changed.
This forces actor to take evasive action when there is no actual obstacle.
2 years ago
AnyOldName3 b9585b8a80 Merge branch 'upgrade_sdl_windows' into 'master'
bump sdl from 2.0.22 to 2.0.24 for windows

Closes #6924

See merge request OpenMW/openmw!2425

(cherry picked from commit 483f370b01)

4c5c449b bump sdl from 2.0.22 to 2.0.24 for windows
36b4e692 Update CI/before_script.msvc.sh
ef92281b Update CI/before_script.msvc.sh
3c173646 Update CI/before_script.msvc.sh
2 years ago
Alexei Kotov 022f245150 Merge branch 'count48' into 'openmw-48'
Improve item count handling (0.48)

See merge request OpenMW/openmw!2436
2 years ago
psi29a 73f69ea37f Merge branch 'appendIndex48' into 'openmw-48'
Fix topic infos creation

See merge request OpenMW/openmw!2435
2 years ago
Andrei Kortunov 3f72432c9f Make count input box larger to fit larger text 2 years ago
Andrei Kortunov f3aebf22dd Improve item count handling 2 years ago
Andrei Kortunov d00fc845c0 Fix topic infos creation 2 years ago
psi29a 7b3adff1c5 Merge branch 'stereo_fix' into 'master'
Fix stereo crash in the editor

Closes #7019

See merge request OpenMW/openmw!2431

(cherry picked from commit e9cfc2381f)

890be1b5 Fix crash in the editor
2 years ago
psi29a 779ec6e55c Merge branch 'recall_how_to_recall' into 'master'
Prevent recursive calls to ActiveSpells::update

Closes #7022

See merge request OpenMW/openmw!2426

(cherry picked from commit d38c072030)

d3253cb6 Prevent recursive calls to ActiveSpells::update
2 years ago
psi29a 960934afee Merge branch 'force-lua-5.1' into 'openmw-48'
Enforce Lua 5.1 when LuaJIT is not being used (OpenMW 0.48)

See merge request OpenMW/openmw!2410
2 years ago
AnyOldName3 87eaa38cf7 Merge branch 're-sign-mac-applications' into 'master'
Re-sign Mac Applications before creating install package

See merge request OpenMW/openmw!2418

(cherry picked from commit 1dd392d33d)

52501b7b Re-sign Mac Applications before creating install package
e4f04390 Merge commit 'cd8b20439ec707574826679a8f851546c78e294e' into re-sign-mac-applications
71f6f950 Make Mac Plugins osgPlugins symlink relative
9f7e1324 Made CMake 3.19 requirement specific to macOS app packaging.
2 years ago
psi29a 1bf3d6b01a Merge branch 'levelincrease' into 'master'
Update both instances of level detail text

See merge request OpenMW/openmw!2282

(cherry picked from commit 1d270e1683)

a4427235 Update both instances of level detail text
2 years ago
psi29a 4d16af29ba Merge branch 'fix_shader_prefix' into 'master'
Use nv_default shader prefix for unhandled types

See merge request OpenMW/openmw!2419

(cherry picked from commit 92680ab9cf)

63b51ead Use nv_default shader prefix for unhandled types
2 years ago
psi29a 847e2bbeaf Merge branch 'fix_reserved_nodes' into 'master'
Add missing non-prefixed reserved nodes

See merge request OpenMW/openmw!2414

(cherry picked from commit cd8b20439e)

ac01fd5e Add missing non-prefixed reserved nodes
2 years ago
Andrew Dunn fe59b9a92a Enforce Lua 5.1 when LuaJIT is not being used
Later minor version bumps of Lua somehow break a lot of our code, this
will fix things like 3rd person mode, crosshair etc for the Apple
Silicon build
2 years ago
psi29a 9bed210e4e Merge branch 'fix_reserved_names_init' into 'master'
Initialize reserved names once to avoid race condition (#7008)

Closes #7008

See merge request OpenMW/openmw!2409

(cherry picked from commit 16fd01a765)

8068d015 Initialize reserved names once to avoid race condition
2 years ago
psi29a b710cf872a Merge branch 'revert_filesystem' into 'openmw-48'
Revert std::filesystem for 0.48

See merge request OpenMW/openmw!2405
2 years ago
Andrei Kortunov d313431e43 Revert std::filesystem usages 2 years ago
elsid d9a6350c5e Limit AiWander destination by wander distance
From initial actor position.

findRandomPointAroundCircle may return a position outside given range. Use
raycast to choose a different reachable point within a radius but double check
and discard if it's still outside.

Use wander radius instead of wander distance for findRandomPointAroundCircle to
have better chance for a position to be inside wander distance.
2 years ago
psi29a 85f343e87a Merge branch 'fix_lua_color' into 'openmw-48'
Make r, g, b, a read-only properties, rather than getters

See merge request OpenMW/openmw!2380
2 years ago
uramer 19d01f26d6 Make r, g, b, a read-only properties, rather than getters 2 years ago
psi29a 64d925eace Merge branch 'font_cleanup' into 'master'
Revert changes in the progress bars layout

See merge request OpenMW/openmw!2366

(cherry picked from commit 5815faecda)

cbe923ea Revert changes in the progress bars layout
2 years ago
jvoisin 6863b2586c Merge branch 'bloom-shader-localisation' into 'master'
Bloom shader (MR2313) french localisation

See merge request OpenMW/openmw!2347

(cherry picked from commit 2ee36c2d8f)

297952bb Add lines to be translated
b8d32986 Bloom shader translation, first pass
d911e992 Missing acute accents
d58f94e5 Other typos
2 years ago
psi29a 30b05203b8 Merge branch 'bloom_options_fix' into 'master'
Fix bloomlinear options

See merge request OpenMW/openmw!2356

(cherry picked from commit 20186fd2c2)

7344a176 Fix bloomlinear options
2 years ago
psi29a d335989f11 Merge branch 'fonts-48' into 'openmw-48'
Backport fonts fixes to 0.48

See merge request OpenMW/openmw!2352
2 years ago
Andrei Kortunov d38454a125 Backport fonts fixes to 0.48 2 years ago
psi29a c3980bebf8 Merge branch 'load_menu' into 'master'
Enlarge character selection widget

See merge request OpenMW/openmw!2350

(cherry picked from commit a0029cb512)

8bac073f Enlarge character selection menu to fit long character or class names
2 years ago
psi29a ba58a322e4 Merge branch 'no_shiny_rocks' into 'master'
Only reflect spells that have a caster

Closes #6969

See merge request OpenMW/openmw!2349

(cherry picked from commit ca90b53c30)

7729ef2e Only reflect spells that have a caster
2 years ago
AnyOldName3 4bb88a4a75 Merge branch 'light_fix' into 'master'
Fix incorrect scene lighting #6952

See merge request OpenMW/openmw!2335

(cherry picked from commit 1d177e2d71)

1f2d9f22 don't recycle statesets for light cullcallback for now
2 years ago
psi29a af14e4d7cd Merge branch 'fix_recast_mesh_collision_shapes_48' into 'openmw-48'
Do not use collision shapes with visual only collision to generate navmesh (for 0.48)

See merge request OpenMW/openmw!2337
2 years ago
psi29a cc0df8f43c Merge branch 'remove_forgotten_code' into 'master'
Remove forgotten commented-out debugging code

See merge request OpenMW/openmw!2319

(cherry picked from commit d9ea6e36fa)

59a0d9c0 Update files/data/shaders/bloomlinear.omwfx
09a39c87 Remove more unused code
2 years ago
elsid 01b3938935
Do not use collision shapes with visual only collision to generate navmesh
These collision shapes are not used for actors movement physics simulation.
2 years ago
psi29a 649ca6b8e6 Merge branch 'scroll_indices' into 'master'
Properly transform item ID to enchantment ID

Closes #6959

See merge request OpenMW/openmw!2328

(cherry picked from commit f1e95ad615)

2b9d475e Fix #6959
2 years ago
psi29a ada64daf87 Merge branch 'cherry-pick-0dacbaf31a5e0703c49eead6d6a977a28ec299f3' into 'master'
ActionManager::toggleMainMenu() should close PostProcessor HUD

See merge request OpenMW/openmw!2305

(cherry picked from commit bf0865d03d)

642d0ad6 ActionManager::toggleMainMenu() should close PostProcessor HUD.
2 years ago
psi29a 03e24b1e70 Merge branch 'apple-silicon-arm-build' into 'master'
Get build working on Apple Silicon

See merge request OpenMW/openmw!2286

(cherry picked from commit 36fbef1048)

1d7d3d57 Get build working on Apple Silicon
808a2e58 Merge commit '5ee4ce1232b0f334f29dd702f811c58dccf5c00d' into apple-silicon-arm-build
a61237f2 Cleaned up macOS FFmpeg framework linking
2 years ago
psi29a 2f1b4eb699 Merge branch 'cherry-pick-31f0ef45d2bb53e0969962f23eb29350465993dd' into 'master'
Show a message if player attempts to access postprocessor hud when postprocessing is disabled.

See merge request OpenMW/openmw!2306

(cherry picked from commit 0889635dc7)

5863790c Show a message if player attempts to access postprocessor hud when postprocessing is disabled
2 years ago
psi29a 77beaf5bc6 Merge branch 'font_loading' into 'master'
Cleanup fonts loading

See merge request OpenMW/openmw!2309

(cherry picked from commit f36e13444e)

9e1ab590 Cleanup fonts loading
2 years ago
psi29a 2795097907 Merge branch 'consistently_hostile' into 'master'
Clear the magic queue when unloading actors

Closes #6954

See merge request OpenMW/openmw!2317

(cherry picked from commit 7c899364af)

afcbb3cb Clear the magic queue when unloading actors
443420ea CI compare cells
2 years ago
AnyOldName3 d262ca2d30 Merge branch 'cherry-pick-abb14df943304a954f734b6289fb48f4951ae242' into 'master'
[Multiview][Postprocessor] omw_GetWorldPosFromUV() should use omw_GetDepth()

See merge request OpenMW/openmw!2320

(cherry picked from commit 23e765954d)

70e1efdd omw_GetWorldPosFromUV() should use omw_GetDepth()
2 years ago
psi29a 071b66999d Merge branch 'bloom_shader' into 'master'
Add wareya's linear bloom shader

See merge request OpenMW/openmw!2313

(cherry picked from commit 537c6e96ab)

534f0377 Add wareya's linear bloom shader
84c72b1c Add dots at the end, and add the .ru translation
2 years ago
psi29a 19df671bba Merge branch 'effect_indices' into 'master'
Preserve effect indices when applying AoE and targeted spells

Closes #6957

See merge request OpenMW/openmw!2315

(cherry picked from commit 5ed9764f3b)

a5476082 Preserve effect indices when applying AoE and targeted spells
2 years ago
psi29a b2d0b3da93 Merge branch 'unique_summons' into 'master'
Mark summon effects as applied if they have spawned a creature

Closes #6955

See merge request OpenMW/openmw!2316

(cherry picked from commit 8ec8e733a1)

8b02c17a Mark summon effects as applied if they have spawned a creature
2 years ago
psi29a b21c9cdf31 Merge branch 'SHADER_HOT_RELOAD' into 'master'
Shaders: Hot reload, togglable by lua debug command

See merge request OpenMW/openmw!2238

(cherry picked from commit 4078f19c74)

8d194a16 Shaders: rudimentary hot reloader on shaders
4e7c1c5b Added break when the operation failed
6b38d622 Added lua bindings to toggle hot reload (disabled by default) or to trigger a reload
31d41252 forgot memory include
f78fa989 fixed include, cleaned comments and indentation
fc8838c7 Renamed lua binding, and use action to avoid concurrency issue
aa51d6de Missing chrono include ?
68d06989 Fixed cyclical included check
b6d7293a Removed weird lines that I thought were necessary to please the compiler
9a475b0c fixed blank lines and missing breaks
cdd95f78 replaced empty function body by default
a1c8dc9d C++17 compat ?
7b78bf4b Fix files with different defines weren't added to the hot reload manager
cc9d4364 includes now work when the same shader has different defines
15751c57 Lua debug api doc
3ab0a991 Hot reload done only once every 200 ms, no point in beeing faster
df69fc76 Post processing shaders now use the same lua commands, no more launcher option...
c71f3508 changed overview.rst of post processing
603b30e1 Added some variable names to make it clearer what their function was
baadc06e Merge branch 'master' into 'SHADER_HOT_RELOAD'
decfbc53 Fix threading issues
b14cc673 adds missing decleration
16a4b571 adds missing include
166717d6 Makes sure threads are only stopped once ,and that they will be re-started
25c1f0ca Renamed variable to fix case issue
2 years ago
psi29a c4dd9d40fe Merge branch 'dont-look-back-in-anger-or-look-backwards-at-all-really' into 'master'
Validate near and far clip distances

See merge request OpenMW/openmw!2311

(cherry picked from commit 510eac3fb4)

17c10537 Validate near and far clip distances
2 years ago
psi29a d88b34131a Merge branch 'boost180' into 'master'
Bump boost to 1.80 for windows

Closes #6942

See merge request OpenMW/openmw!2304

(cherry picked from commit 9f26d6023b)

448853fb Update CI/before_script.msvc.sh
2 years ago
psi29a 5e76c124a7 Merge branch 'font_fixes' into 'master'
Font fixes

See merge request OpenMW/openmw!2297

(cherry picked from commit 7bb1856b74)

c3d3f314 Allow to change font settings in the launcher
2cd2b42e Improve handling of larger font size
2cae8bea Improve layout of Interface tab
3117a993 Fix documentation
2 years ago
psi29a 33d85635fd Merge branch 'terraindocs' into 'master'
Clarify object paging merge factor docs (#5924)

Closes #5924

See merge request OpenMW/openmw!2295

(cherry picked from commit 0d776d7009)

15f4b551 Clarify object paging merge factor docs
2 years ago
psi29a 18de21cf13 Update CHANGELOG.md 2 years ago
psi29a bbfc573b15 Merge branch 'windows_save' into 'master'
[Postprocessing] Mitigate clashes with live reload and external saves on windows

See merge request OpenMW/openmw!2237

(cherry picked from commit 92f3b4ba82)

0f9a7594 wait a brief moment before reading a file marked as modified
2 years ago
psi29a 341161ba03 Merge branch 'fontconfig' into 'master'
Allow users to decide if they need to import bitmap fonts

See merge request OpenMW/openmw!2270

(cherry picked from commit 1e4dd46688)

43f552f4 Allow users to decide if they need to import bitmap fonts
2 years ago
psi29a f45bb18614 Merge branch 'swedish-i10n-0-48' into 'master'
Updated Swedish strings for built-in shaders

See merge request OpenMW/openmw!2284

(cherry picked from commit 2a892a2525)

b3b16284 Update BuiltInShaders/sv.yaml
2 years ago
psi29a efa4b52e9a Comment out flatpack build for now. 2 years ago
psi29a 3fdb2201b4 Merge branch 'cherry-pick-856fcb77' into 'openmw-48'
Merge branch 'FlatpakCI' into 'master' flatpack RC1

See merge request OpenMW/openmw!2285
2 years ago
psi29a fda1127b5c Merge branch 'FlatpakCI' into 'master'
Flatpak CI Build

See merge request OpenMW/openmw!2066

(cherry picked from commit 856fcb7742)

274ad078 add flatpak ci
2 years ago
psi29a d444b1b350 Merge branch 'coverity' into 'master'
Do not copy data when it is not needed

See merge request OpenMW/openmw!2277

(cherry picked from commit f42ab6a3e7)

e3ad30a5 Do not copy data when it is not needed
2 years ago
psi29a cc27baec62 Merge branch 'update-windows-deps' into 'openmw-48'
Update OSGoS dependency package

See merge request OpenMW/openmw!2278
2 years ago
Alexander Olofsson 149d0b2f29
Increment CI cache key for Windows builds 2 years ago
Alexander Olofsson 619bb73d44
Add lost backslash 2 years ago
Alexander Olofsson 944e748971
Use the correct debug-suffixes for dependencies 2 years ago
Alexander Olofsson 14663b8314
Update OSGoS dependency package 2 years ago
psi29a df3ebf6b87 Merge branch 'cs-tables-doc-fix' into 'master'
OpenMW-CS tables doc polish

See merge request OpenMW/openmw!2269
2 years ago
psi29a 15ed99be5b Merge branch 'sky-doc-small-fixes' into 'master'
Better wording and formatting of the Sky Structure doc

See merge request OpenMW/openmw!2265
2 years ago
psi29a 0fb2c96a69 Merge branch 'stagger' into 'openmw-48'
Stagger fixes, 0.48.0 edition

See merge request OpenMW/openmw!2259
2 years ago
Alexei Kotov 2f5f2bdbad Stagger fixes, 0.48.0 edition 2 years ago
psi29a f61cc5c60c Merge branch 'changelog2' into 'master'
Additional fixes for 0.48 changelog

See merge request OpenMW/openmw!2257
2 years ago
psi29a bc79de4934 Merge branch 'sheerheartattackhasnoweaknesses' into 'master'
Animation regression fixes

See merge request OpenMW/openmw!2255
2 years ago
psi29a cec1429c0f Merge branch 'cleanup' into 'master'
#5534 remove OSG 3.4 support and require at least 3.6.5 support

Closes #3180 and #5534

See merge request OpenMW/openmw!2228
2 years ago
psi29a 105be76d81 Merge branch 'localization-fr-1' into 'openmw-48'
French i10n translations

See merge request OpenMW/openmw!2250
2 years ago
Arnaud Dochain 5f69af582e French i10n translations 2 years ago
psi29a 0339e32c0c Merge branch 'ubb_fix' into 'master'
Remove excessive allocations in lightmanager

See merge request OpenMW/openmw!2251
2 years ago

@ -440,7 +440,7 @@ macOS12_Xcode13:
after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: ninja-v3
key: ninja-v4
paths:
- ccache
- deps
@ -541,7 +541,7 @@ macOS12_Xcode13:
after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: msbuild-v3
key: msbuild-v4
paths:
- ccache
- deps
@ -651,3 +651,23 @@ Ubuntu_AndroidNDK_arm64-v8a:
- pip3 install --user requests click discord_webhook
script:
- scripts/find_missing_merge_requests.py --project_id=$CI_PROJECT_ID --ignored_mrs_path=$CI_PROJECT_DIR/.resubmitted_merge_requests.txt
.flatpak:
image: 'docker.io/bilelmoussaoui/flatpak-github-actions'
stage: build
script:
- flatpak install -y flathub org.kde.Platform/x86_64/5.15-21.08
- flatpak install -y flathub org.kde.Sdk/x86_64/5.15-21.08
- flatpak-builder --ccache --force-clean --repo=repo build CI/org.openmw.OpenMW.devel.yaml
- flatpak build-bundle ./repo openmw.flatpak org.openmw.OpenMW.devel
cache:
key: flatpak
paths:
- ".flatpak-builder"
artifacts:
untracked: false
paths:
- "openmw.flatpak"
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
# Flatpak Builds compile all dependencies aswell so need more time. Build results of libraries are cached
timeout: 4h

@ -13,12 +13,14 @@
Bug #3855: AI sometimes spams defensive spells
Bug #3867: All followers attack player when one follower enters combat with player
Bug #3905: Great House Dagoth issues
Bug #4175: Objects "vibrate" when extremely far from (0,0)
Bug #4203: Resurrecting an actor doesn't close the loot GUI
Bug #4227: Spellcasting restrictions are checked before spellcasting animations are played
Bug #4310: Spell description is centered
Bug #4374: Player rotation reset when nearing area that hasn't been loaded yet
Bug #4376: Moved actors don't respawn in their original cells
Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node
Bug #4526: Crash when additional maps are applied over a model with out of bounds UV
Bug #4602: Robert's Bodies: crash inside createInstance()
Bug #4700: OpenMW-CS: Incorrect command implementation
Bug #4744: Invisible particles aren't always processed
@ -141,6 +143,7 @@
Bug #6794: Light sources are attached to mesh bounds centers instead of mesh origins when AttachLight NiNode is missing
Bug #6799: Game crashes if an NPC has no Class attached
Bug #6849: ImageButton texture is not scaled properly
Bug #6860: Sinnammu randomly strafes while running on water
Bug #6869: Hits queue stagger during swing animation
Bug #6890: SDL_PeepEvents errors are not handled
Bug #6895: Removing a negative number of items from a script, makes the script terminate with an error
@ -150,6 +153,11 @@
Bug #6909: Using enchanted items has no animation
Bug #6910: Torches should not be extinguished when not being held
Bug #6913: Constant effect enchanted items don't break invisibility
Bug #6923: Dispose of corpse prevents respawning after load
Bug #6937: Divided by Nix Hounds quest is broken
Bug #7008: Race condition on initializing a vector of reserved node names
Bug #7121: Crash on TimeStamp construction with invalid hour value
Bug #7251: Force shaders setting still renders some drawables with FFP
Feature #890: OpenMW-CS: Column filtering
Feature #1465: "Reset" argument for AI functions
Feature #2491: Ability to make OpenMW "portable"
@ -157,8 +165,10 @@
Feature #2766: Warn user if their version of Morrowind is not the latest.
Feature #2780: A way to see current OpenMW version in the console
Feature #2858: Add a tab to the launcher for handling datafolders
Feature #3245: Grid and angle snapping for the OpenMW-CS
Feature #3180: Support uncompressed colour-mapped TGA files
Feature #3245: OpenMW-CS: Instance editing grid
Feature #3616: Allow Zoom levels on the World Map
Feature #3668: Support palettized DDS files
Feature #4067: Post Processing
Feature #4297: Implement APPLIED_ONCE flag for magic effects
Feature #4414: Handle duration of EXTRA SPELL magic effect
@ -169,7 +179,7 @@
Feature #5454: Clear active spells from actor when he disappears from scene
Feature #5489: MCP: Telekinesis fix for activators
Feature #5701: Convert osgAnimation::RigGeometry to double-buffered custom version
Feature #5737: Handle instance move from one cell to another
Feature #5737: OpenMW-CS: Handle instance move from one cell to another
Feature #5928: Allow Glow in the Dahrk to be disabled
Feature #5996: Support Lua scripts in OpenMW
Feature #6017: Separate persistent and temporary cell references when saving
@ -183,7 +193,7 @@
Feature #6248: Embedded error marker mesh
Feature #6249: Alpha testing support for Collada
Feature #6251: OpenMW-CS: Set instance movement based on camera zoom
Feature #6288: Preserve the "blocked" record flag for referenceable objects.
Feature #6288: OpenMW-CS: Preserve "blocked" record flags when saving
Feature #6360: More realistic raindrop ripples
Feature #6380: Treat commas as whitespace in scripts
Feature #6419: Don't grey out topics if they can produce another topic reference
@ -200,9 +210,11 @@
Feature #6700: Support windowed fullscreen
Feature #6706: Save the size of the Options window
Feature #6721: OpenMW-CS: Add option to open records in new window
Feature #6823: Support for animation layering for osgAnimation formats
Feature #6867: Add a way to localize hardcoded strings in GUI
Feature #6888: Add switch for armor degradation fix
Feature #6925: Allow to use a mouse wheel to rotate a head in the race selection menu
Task #5534: Remove support for OSG 3.4
Task #6161: Refactor Sky to use shaders and be GLES/GL3 friendly
Task #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
Task #6435: Add support for MSVC 2022
@ -342,7 +354,6 @@
Bug #6047: Mouse bindings can be triggered during save loading
Bug #6136: Game freezes when NPCs try to open doors that are about to be closed
Bug #6294: Game crashes with empty pathgrid
Bug #6923: Dispose of corpse prevents respawning after load
Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu

@ -1,6 +1,7 @@
#!/bin/sh -ex
export HOMEBREW_NO_EMOJI=1
brew tap --repair
brew update --quiet
# workaround python issue on travis

@ -379,9 +379,9 @@ case $VS_VERSION in
QT_MSVC_YEAR="2019"
BULLET_MSVC_YEAR="2015"
BOOST_VER="1.79.0"
BOOST_VER_URL="1_79_0"
BOOST_VER_SDK="107900"
BOOST_VER="1.80.0"
BOOST_VER_URL="1_80_0"
BOOST_VER_SDK="108000"
;;
16|16.0|2019 )
@ -397,9 +397,9 @@ case $VS_VERSION in
QT_MSVC_YEAR="2019"
BULLET_MSVC_YEAR="2015"
BOOST_VER="1.79.0"
BOOST_VER_URL="1_79_0"
BOOST_VER_SDK="107900"
BOOST_VER="1.80.0"
BOOST_VER_URL="1_80_0"
BOOST_VER_SDK="108000"
;;
15|15.0|2017 )
@ -556,11 +556,11 @@ fi
ICU_VER="70_1"
OSG_ARCHIVE_NAME="OSGoS 3.6.5"
OSG_ARCHIVE="OSGoS-3.6.5-b02abe2-msvc${OSG_MSVC_YEAR}-win${BITS}"
OSG_ARCHIVE="OSGoS-3.6.5-dd803bc-msvc${OSG_MSVC_YEAR}-win${BITS}"
OSG_ARCHIVE_REPO_URL="https://gitlab.com/OpenMW/openmw-deps/-/raw/main"
if ! [ -z $OSG_MULTIVIEW_BUILD ]; then
OSG_ARCHIVE_NAME="OSG-3.6-multiview"
OSG_ARCHIVE="OSG-3.6-multiview-ee297dce0-msvc${OSG_MSVC_YEAR}-win${BITS}"
OSG_ARCHIVE="OSG-3.6-multiview-d2ee5aa8-msvc${OSG_MSVC_YEAR}-win${BITS}"
OSG_ARCHIVE_REPO_URL="https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr-ovr_multiview"
fi
@ -612,9 +612,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# OpenAL
download "OpenAL-Soft 1.20.1" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.20.1.zip" \
"OpenAL-Soft-1.20.1.zip"
download "OpenAL-Soft 1.23.0" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.23.0.zip" \
"OpenAL-Soft-1.23.0.zip"
# OSGoS
download "${OSG_ARCHIVE_NAME}" \
@ -628,9 +628,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# SDL2
download "SDL 2.0.22" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.22.zip" \
"SDL2-2.0.22.zip"
download "SDL 2.24.0" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-devel-2.24.0-VC.zip" \
"SDL2-devel-2.24.0-VC.zip"
# LZ4
download "LZ4 1.9.2" \
@ -803,19 +803,19 @@ printf "MyGUI 3.4.1... "
cd $DEPS
echo
# OpenAL
printf "OpenAL-Soft 1.20.1... "
printf "OpenAL-Soft 1.23.0... "
{
if [ -d openal-soft-1.20.1-bin ]; then
if [ -d openal-soft-1.23.0-bin ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf openal-soft-1.20.1-bin
eval 7z x -y OpenAL-Soft-1.20.1.zip $STRIP
rm -rf openal-soft-1.23.0-bin
eval 7z x -y OpenAL-Soft-1.23.0.zip $STRIP
fi
OPENAL_SDK="$(real_pwd)/openal-soft-1.20.1-bin"
OPENAL_SDK="$(real_pwd)/openal-soft-1.23.0-bin"
add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \
-DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib"
for config in ${CONFIGURATIONS[@]}; do
add_runtime_dlls $config "$(pwd)/openal-soft-1.20.1-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
add_runtime_dlls $config "$(pwd)/openal-soft-1.23.0-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
done
echo Done.
}
@ -830,7 +830,7 @@ printf "${OSG_ARCHIVE_NAME}... "
grep "OPENSCENEGRAPH_MINOR_VERSION 6" OSG/include/osg/Version > /dev/null && \
grep "OPENSCENEGRAPH_PATCH_VERSION 5" OSG/include/osg/Version > /dev/null
then
printf "Exists. "
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf OSG
eval 7z x -y "${DEPS}/${OSG_ARCHIVE}.7z" $STRIP
@ -842,17 +842,27 @@ printf "${OSG_ARCHIVE_NAME}... "
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [ $CONFIGURATION == "Debug" ]; then
SUFFIX="d"
SUFFIX_UPCASE="D"
else
SUFFIX=""
SUFFIX_UPCASE=""
fi
if ! [ -z $OSG_MULTIVIEW_BUILD ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{ot21-OpenThreads,zlib,libpng16}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg162-osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
else
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,icuuc58,libpng16,zlib}${SUFFIX}.dll \
"$(pwd)/OSG/bin/libxml2"${SUFFIX_UPCASE}.dll \
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/icudt58.dll"
if [ $CONFIGURATION == "Debug" ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{boost_filesystem-vc141-mt-gd-1_63,boost_system-vc141-mt-gd-1_63,collada-dom2.4-dp-vc141-mt-d}.dll
else
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{boost_filesystem-vc141-mt-1_63,boost_system-vc141-mt-1_63,collada-dom2.4-dp-vc141-mt}.dll
fi
fi
if ! [ -z $OSG_MULTIVIEW_BUILD ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{ot21-OpenThreads,zlib,libpng16}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg162-osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
else
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
fi
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dae,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll
done
echo Done.
@ -935,17 +945,17 @@ printf "Qt 5.15.2... "
cd $DEPS
echo
# SDL2
printf "SDL 2.0.22... "
printf "SDL 2.24.0... "
{
if [ -d SDL2-2.0.22 ]; then
if [ -d SDL2-2.24.0 ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf SDL2-2.0.22
eval 7z x -y SDL2-2.0.22.zip $STRIP
rm -rf SDL2-2.24.0
eval 7z x -y SDL2-devel-2.24.0-VC.zip $STRIP
fi
export SDL2DIR="$(real_pwd)/SDL2-2.0.22"
export SDL2DIR="$(real_pwd)/SDL2-2.24.0"
for config in ${CONFIGURATIONS[@]}; do
add_runtime_dlls $config "$(pwd)/SDL2-2.0.22/lib/x${ARCHSUFFIX}/SDL2.dll"
add_runtime_dlls $config "$(pwd)/SDL2-2.24.0/lib/x${ARCHSUFFIX}/SDL2.dll"
done
echo Done.
}

@ -46,6 +46,7 @@ declare -rA GROUPED_DEPS=(
[openmw-integration-tests]="
ca-certificates
gdb
git
git-lfs
libavcodec58
@ -55,7 +56,7 @@ declare -rA GROUPED_DEPS=(
libboost-iostreams1.74.0
libboost-program-options1.74.0
libboost-system1.74.0
libbullet3.06
libbullet3.24
libcollada-dom2.5-dp0
libicu70
libjpeg8

@ -0,0 +1,200 @@
---
app-id: org.openmw.OpenMW.devel
runtime: org.kde.Platform
runtime-version: '5.15-21.08'
sdk: org.kde.Sdk
command: openmw-launcher
rename-appdata-file: openmw.appdata.xml
finish-args:
- "--share=ipc"
- "--socket=x11"
- "--device=all"
- "--filesystem=host"
- "--socket=pulseaudio"
build-options:
cflags: "-O2 -g"
cxxflags: "-O2 -g"
cleanup:
- "/include"
- "/lib/pkgconfig"
- "/lib/cmake"
- "/share/pkgconfig"
- "/share/aclocal"
- "/share/doc"
- "/man"
- "/share/man"
- "/share/gtk-doc"
- "/share/vala"
- "*.la"
- "*.a"
modules:
- name: boost
buildsystem: simple
build-commands:
- ./bootstrap.sh --prefix=/app --with-libraries=filesystem,iostreams,program_options,system
- ./b2 headers
- ./b2 install
sources:
- type: archive
url: https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz
sha256: aeb26f80e80945e82ee93e5939baebdca47b9dee80a07d3144be1e1a6a66dd6a
- name: collada-dom
buildsystem: cmake-ninja
config-opts:
- "-DOPT_COLLADA14=1"
- "-DOPT_COLLADA15=0"
sources:
- type: archive
url: https://github.com/rdiankov/collada-dom/archive/c1e20b7d6ff806237030fe82f126cb86d661f063.zip
sha256: 6c51cd068c7d6760b587391884942caaac8a515d138535041e42d00d3e5c9152
- name: ffmpeg
config-opts:
- "--disable-static"
- "--enable-shared"
- "--disable-programs"
- "--disable-doc"
- "--disable-avdevice"
- "--disable-avfilter"
- "--disable-postproc"
- "--disable-encoders"
- "--disable-muxers"
- "--disable-protocols"
- "--disable-indevs"
- "--disable-devices"
- "--disable-filters"
sources:
- type: archive
url: http://ffmpeg.org/releases/ffmpeg-4.3.2.tar.xz
sha256: 46e4e64f1dd0233cbc0934b9f1c0da676008cad34725113fb7f802cfa84ccddb
cleanup:
- "/share/ffmpeg"
- name: openscenegraph
buildsystem: cmake-ninja
config-opts:
- "-DBUILD_OSG_PLUGINS_BY_DEFAULT=0"
- "-DBUILD_OSG_PLUGIN_OSG=1"
- "-DBUILD_OSG_PLUGIN_DDS=1"
- "-DBUILD_OSG_PLUGIN_DAE=1"
- "-DBUILD_OSG_PLUGIN_TGA=1"
- "-DBUILD_OSG_PLUGIN_BMP=1"
- "-DBUILD_OSG_PLUGIN_JPEG=1"
- "-DBUILD_OSG_PLUGIN_PNG=1"
- "-DBUILD_OSG_DEPRECATED_SERIALIZERS=0"
- "-DBUILD_OSG_APPLICATIONS=0"
- "-DCMAKE_BUILD_TYPE=Release"
build-options:
env:
COLLADA_DIR: /app/include/collada-dom2.5
sources:
- type: archive
url: https://github.com/openmw/osg/archive/76e061739610bc9a3420a59e7c9395e742ce2f97.zip
sha256: fa1100362eae260192819d65d90b29ec0b88fdf80e30cee677730b7a0d68637e
- name: bullet
# The cmake + ninja buildsystem doesn't install the required binaries correctly
buildsystem: cmake
config-opts:
- "-DBUILD_BULLET2_DEMOS=0"
- "-DBUILD_BULLET3=0"
- "-DBUILD_CPU_DEMOS=0"
- "-DBUILD_EXTRAS=0"
- "-DBUILD_OPENGL3_DEMOS=0"
- "-DBUILD_UNIT_TESTS=0"
- "-DCMAKE_BUILD_TYPE=Release"
- "-DUSE_GLUT=0"
- "-DUSE_GRAPHICAL_BENCHMARK=0"
- "-DUSE_DOUBLE_PRECISION=on"
- "-DBULLET2_MULTITHREADING=on"
sources:
- type: archive
url: https://github.com/bulletphysics/bullet3/archive/93be7e644024e92df13b454a4a0b0fcd02b21b10.zip
sha256: 82968fbf20a92c51bc71ac9ee8f6381ecf3420c7cbb881ffb7bb633fa13b27f9
- name: mygui
buildsystem: cmake-ninja
config-opts:
- "-DCMAKE_BUILD_TYPE=Release"
- "-DMYGUI_RENDERSYSTEM=1"
- "-DMYGUI_BUILD_DEMOS=0"
- "-DMYGUI_BUILD_TOOLS=0"
- "-DMYGUI_BUILD_PLUGINS=0"
sources:
- type: archive
url: https://github.com/MyGUI/mygui/archive/refs/tags/MyGUI3.4.1.tar.gz
sha256: bdf730bdeb4ad89e6b8223967db01aa5274d2b93adc2c0d6aa4842faeed4de1a
- name: libunshield
buildsystem: cmake-ninja
config-opts:
- "-DCMAKE_BUILD_TYPE=Release"
sources:
- type: archive
url: https://github.com/twogood/unshield/archive/1.4.3.tar.gz
sha256: aa8c978dc0eb1158d266eaddcd1852d6d71620ddfc82807fe4bf2e19022b7bab
- name: lz4
buildsystem: simple
build-commands:
- "make lib"
- "PREFIX=/app make install"
sources:
- type: archive
url: https://github.com/lz4/lz4/archive/refs/tags/v1.9.3.tar.gz
sha256: 030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1
- name: recastnavigation
buildsystem: cmake-ninja
config-opts:
- "-DCMAKE_BUILD_TYPE=Release"
- "-DRECASTNAVIGATION_DEMO=no"
- "-DRECASTNAVIGATION_TESTS=no"
- "-DRECASTNAVIGATION_EXAMPLES=no"
sources:
- type: archive
url: https://github.com/recastnavigation/recastnavigation/archive/c5cbd53024c8a9d8d097a4371215e3342d2fdc87.zip
sha256: 53dacfd7bead4d3b0c9a04a648caed3e7c3900e0aba765c15dee26b50f6103c6
- name: yaml-cpp
buildsystem: cmake-ninja
sources:
- type: archive
url: https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip
sha256: 4d5e664a7fb2d7445fc548cc8c0e1aa7b1a496540eb382d137e2cc263e6d3ef5
- name: LuaJIT
buildsystem: simple
build-commands:
- make install PREFIX=/app
sources:
- type: archive
url: https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.0.5.zip
sha256: 2adbe397a5b6b8ab22fa8396507ce852a2495db50e50734b3daa1ffcadd9eeb4
- name: openmw
builddir: true
buildsystem: cmake-ninja
config-opts:
- "-DBUILD_BSATOOL=no"
- "-DBUILD_ESMTOOL=no"
- "-DCMAKE_BUILD_TYPE=Release"
- "-DICONDIR=/app/share/icons"
- "-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=yes"
sources:
- type: dir
path: ..
- type: shell
commands:
- "sed -i 's:/wiki:/old-wiki:' ./files/openmw.appdata.xml"
- "sed -i 's:>org.openmw.launcher.desktop<:>org.openmw.OpenMW.devel.desktop<:' ./files/openmw.appdata.xml"
- "sed -i 's:Icon=openmw:Icon=org.openmw.OpenMW.devel.png:' ./files/org.openmw.launcher.desktop"
- "sed -i 's:Icon=openmw-cs:Icon=org.openmw.OpenMW.OpenCS.devel.png:' ./files/org.openmw.cs.desktop"
post-install:
- "mv /app/share/applications/org.openmw.launcher.desktop /app/share/applications/org.openmw.OpenMW.devel.desktop"
- "mv /app/share/applications/org.openmw.cs.desktop /app/share/applications/org.openmw.OpenMW.OpenCS.devel.desktop"
- "mv /app/share/icons/openmw.png /app/share/icons/org.openmw.OpenMW.devel.png"
- "mv /app/share/icons/openmw-cs.png /app/share/icons/org.openmw.OpenMW.OpenCS.devel.png"

@ -407,10 +407,7 @@ if(NOT HAVE_STDINT_H)
endif()
if(OPENMW_USE_SYSTEM_OSG)
find_package(OpenSceneGraph 3.4.0 REQUIRED ${USED_OSG_COMPONENTS})
if (${OPENSCENEGRAPH_VERSION} VERSION_GREATER 3.6.2 AND ${OPENSCENEGRAPH_VERSION} VERSION_LESS 3.6.5)
message(FATAL_ERROR "OpenSceneGraph version ${OPENSCENEGRAPH_VERSION} has critical regressions which cause crashes. Please upgrade to 3.6.5 or later. We strongly recommend using the tip of the official 'OpenSceneGraph-3.6' branch or the tip of '3.6' OpenMW/osg (OSGoS).")
endif()
find_package(OpenSceneGraph 3.6.5 REQUIRED ${USED_OSG_COMPONENTS}) # Bump to 3.6.6 when released
if(OSG_STATIC)
find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS})
@ -462,7 +459,7 @@ if(USE_LUAJIT)
set(LUA_INCLUDE_DIR ${LuaJit_INCLUDE_DIR})
set(LUA_LIBRARIES ${LuaJit_LIBRARIES})
else(USE_LUAJIT)
find_package(Lua REQUIRED)
find_package(Lua 5.1 EXACT REQUIRED)
add_compile_definitions(NO_LUAJIT)
endif(USE_LUAJIT)
@ -783,18 +780,14 @@ if (WIN32)
endif()
if (BUILD_OPENMW AND APPLE)
if (USE_LUAJIT)
# Without these flags LuaJit crashes on startup on OSX
set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000")
endif(USE_LUAJIT)
target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1)
target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1)
endif()
# Apple bundling
if (OPENMW_OSX_DEPLOYMENT AND APPLE)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4)
message(FATAL_ERROR "macOS packaging is broken in early CMake 3.13 releases, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use at least 3.13.4 or an older version like 3.12.4")
if (CMAKE_VERSION VERSION_LESS 3.19)
message(FATAL_ERROR "macOS packaging requires CMake 3.19 or higher to sign macOS app bundles.")
endif ()
get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE)
@ -890,6 +883,9 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
fixup_bundle(\"${INSTALLED_OPENMW_APP}\" \"${PLUGINS}\" \"\")
fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\")
" COMPONENT Runtime)
set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_SOURCE_DIR}/cmake/SignMacApplications.cmake)
include(CPack)
elseif(NOT APPLE)
get_generator_is_multi_config(multi_config)

@ -14,8 +14,8 @@ OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see [files/data/fonts/DejaVuFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DejaVuFontLicense.txt) for more information)
* OMWAyembedt.ttf: SIL Open Font License (see [files/data/fonts/OMWAyembedtFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/OMWAyembedtFontLicense.txt) for more information)
* Pelagiad.ttf: SIL Open Font License (see [files/data/fonts/PelagiadFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/PelagiadFontLicense.txt) for more information)
* DemonicLetters.ttf: SIL Open Font License (see [files/data/fonts/DemonicLettersFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/DemonicLettersFontLicense.txt) for more information)
* MysticCards.ttf: SIL Open Font License (see [files/data/fonts/MysticCardsFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/data/fonts/MysticCardsFontLicense.txt) for more information)
Current Status
--------------

@ -10,6 +10,7 @@ openmw_add_executable(bsatool
target_link_libraries(bsatool
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
)

@ -1,10 +1,10 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <vector>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <components/bsa/compressedbsafile.hpp>
#include <components/misc/stringops.hpp>
@ -13,6 +13,7 @@
// Create local aliases for brevity
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
struct Arguments
{
@ -200,26 +201,26 @@ int extract(std::unique_ptr<File>& bsa, Arguments& info)
}
// Get the target path (the path the file will be extracted to)
std::filesystem::path relPath (extractPath);
std::filesystem::path outdir (info.outdir);
bfs::path relPath (extractPath);
bfs::path outdir (info.outdir);
std::filesystem::path target;
bfs::path target;
if (info.fullpath)
target = outdir / relPath;
else
target = outdir / relPath.filename();
// Create the directory hierarchy
std::filesystem::create_directories(target.parent_path());
bfs::create_directories(target.parent_path());
std::filesystem::file_status s = std::filesystem::status(target.parent_path());
if (!std::filesystem::is_directory(s))
bfs::file_status s = bfs::status(target.parent_path());
if (!bfs::is_directory(s))
{
std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl;
return 3;
}
std::ofstream out(target, std::ios::binary);
bfs::ofstream out(target, std::ios::binary);
// Write the file to disk
std::cout << "Extracting " << info.extractfile << " to " << target << std::endl;
@ -239,14 +240,14 @@ int extractAll(std::unique_ptr<File>& bsa, Arguments& info)
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
// Get the target path (the path the file will be extracted to)
std::filesystem::path target (info.outdir);
bfs::path target (info.outdir);
target /= extractPath;
// Create the directory hierarchy
std::filesystem::create_directories(target.parent_path());
bfs::create_directories(target.parent_path());
std::filesystem::file_status s = std::filesystem::status(target.parent_path());
if (!std::filesystem::is_directory(s))
bfs::file_status s = bfs::status(target.parent_path());
if (!bfs::is_directory(s))
{
std::cout << "ERROR: " << target.parent_path() << " is not a directory." << std::endl;
return 3;
@ -254,7 +255,7 @@ int extractAll(std::unique_ptr<File>& bsa, Arguments& info)
// Get a stream for the file to extract
Files::IStreamPtr data = bsa->getFile(&file);
std::ofstream out(target, std::ios::binary);
bfs::ofstream out(target, std::ios::binary);
// Write the file to disk
std::cout << "Extracting " << target << std::endl;
@ -268,7 +269,7 @@ int extractAll(std::unique_ptr<File>& bsa, Arguments& info)
template<typename File>
int add(std::unique_ptr<File>& bsa, Arguments& info)
{
std::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in);
boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in);
bsa->addFile(info.addfile, stream);
return 0;

@ -4,12 +4,12 @@
#include <list>
#include <unordered_set>
#include <map>
#include <fstream>
#include <cmath>
#include <memory>
#include <optional>
#include <iomanip>
#include <boost/filesystem/fstream.hpp>
#include <boost/program_options.hpp>
#include <components/esm3/esmreader.hpp>
@ -308,7 +308,7 @@ void printRawTes3(std::string_view path)
}
}
int loadTes3(const Arguments& info, std::unique_ptr<std::ifstream>&& stream, ESMData* data)
int loadTes3(const Arguments& info, std::unique_ptr<boost::filesystem::ifstream>&& stream, ESMData* data)
{
std::cout << "Loading TES3 file: " << info.filename << '\n';
@ -499,7 +499,7 @@ int clone(const Arguments& info)
esm.setVersion(ESM::VER_13);
esm.setRecordCount (recordCount);
std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary);
boost::filesystem::fstream save(info.outname, boost::filesystem::fstream::out | boost::filesystem::fstream::binary);
esm.save(save);
int saved = 0;

@ -2,7 +2,6 @@
#include "arguments.hpp"
#include "labels.hpp"
#include <fstream>
#include <iostream>
#include <type_traits>
@ -286,7 +285,7 @@ namespace EsmTool
}
}
int loadTes4(const Arguments& info, std::unique_ptr<std::ifstream>&& stream)
int loadTes4(const Arguments& info, std::unique_ptr<boost::filesystem::ifstream>&& stream)
{
std::cout << "Loading TES4 file: " << info.filename << '\n';

@ -1,15 +1,16 @@
#ifndef OPENMW_ESMTOOL_TES4_H
#define OPENMW_ESMTOOL_TES4_H
#include <fstream>
#include <iosfwd>
#include <memory>
#include <boost/filesystem/fstream.hpp>
namespace EsmTool
{
struct Arguments;
int loadTes4(const Arguments& info, std::unique_ptr<std::ifstream>&& stream);
int loadTes4(const Arguments& info, std::unique_ptr<boost::filesystem::ifstream>&& stream);
}
#endif

@ -35,6 +35,7 @@ openmw_add_executable(openmw-essimporter
target_link_libraries(openmw-essimporter
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
components
)

@ -1,8 +1,8 @@
#include "importer.hpp"
#include <iomanip>
#include <filesystem>
#include <fstream>
#include <boost/filesystem/fstream.hpp>
#include <osgDB/ReadFile>
#include <osg/ImageUtils>
@ -345,7 +345,7 @@ namespace ESSImport
writer.setFormat (ESM::SavedGame::sCurrentFormat);
std::ofstream stream(std::filesystem::path(mOutFile), std::ios::out | std::ios::binary);
boost::filesystem::ofstream stream(boost::filesystem::path(mOutFile), std::ios::out | std::ios::binary);
// all unused
writer.setVersion(0);
writer.setType(0);

@ -1,13 +1,14 @@
#include <iostream>
#include <filesystem>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <components/files/configurationmanager.hpp>
#include "importer.hpp"
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
int main(int argc, char** argv)
@ -56,7 +57,7 @@ int main(int argc, char** argv)
else
{
const std::string& ext = ".omwsave";
if (std::filesystem::exists(std::filesystem::path(outputFile))
if (bfs::exists(bfs::path(outputFile))
&& (outputFile.size() < ext.size() || outputFile.substr(outputFile.size()-ext.size()) != ext))
{
throw std::runtime_error("Output file already exists and does not end in .omwsave. Did you mean to use --compare?");

@ -95,6 +95,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
loadSettingBool(classicCalmSpellsCheckBox, "classic calm spells behavior", "Game");
loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game");
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
@ -127,6 +128,7 @@ bool Launcher::AdvancedPage::loadSettings()
if (Settings::Manager::getInt("antialiasing", "Video") == 0) {
antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked);
}
loadSettingBool(adjustCoverageForAlphaTestCheckBox, "adjust coverage for alpha test", "Shaders");
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
@ -152,7 +154,6 @@ bool Launcher::AdvancedPage::loadSettings()
connect(postprocessEnabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPostProcessToggled(bool)));
loadSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing");
loadSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing");
loadSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing");
postprocessHDRTimeComboBox->setValue(Settings::Manager::getDouble("auto exposure speed", "Post Processing"));
@ -205,6 +206,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map");
loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "GUI"));
fontSizeSpinBox->setValue(Settings::Manager::getInt("font size", "GUI"));
}
// Bug fixes
@ -257,6 +259,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
saveSettingBool(classicCalmSpellsCheckBox, "classic calm spells behavior", "Game");
saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game");
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
@ -281,6 +284,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(radialFogCheckBox, "radial fog", "Fog");
saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders");
saveSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders");
saveSettingBool(adjustCoverageForAlphaTestCheckBox, "adjust coverage for alpha test", "Shaders");
saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
@ -309,7 +313,6 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
saveSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing");
saveSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing");
saveSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing");
double hdrExposureTime = postprocessHDRTimeComboBox->value();
if (hdrExposureTime != Settings::Manager::getDouble("auto exposure speed", "Post Processing"))
@ -365,9 +368,14 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map");
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
float uiScalingFactor = scalingSpinBox->value();
if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI"))
Settings::Manager::setFloat("scaling factor", "GUI", uiScalingFactor);
int fontSize = fontSizeSpinBox->value();
if (fontSize != Settings::Manager::getInt("font size", "GUI"))
Settings::Manager::setInt("font size", "GUI", fontSize);
}
// Bug fixes
@ -466,7 +474,6 @@ void Launcher::AdvancedPage::slotAnimSourcesToggled(bool checked)
void Launcher::AdvancedPage::slotPostProcessToggled(bool checked)
{
postprocessLiveReloadCheckBox->setEnabled(checked);
postprocessTransparentPostpassCheckBox->setEnabled(checked);
postprocessHDRTimeComboBox->setEnabled(checked);
postprocessHDRTimeLabel->setEnabled(checked);

@ -9,8 +9,10 @@
#include <thread>
#include <mutex>
#include <algorithm>
#include <unordered_set>
#include <apps/launcher/utils/cellnameloader.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/contentselector/model/esmfile.hpp>
@ -22,6 +24,7 @@
#include <components/settings/settings.hpp>
#include <components/bsa/compressedbsafile.hpp>
#include <components/navmeshtool/protocol.hpp>
#include <components/debug/debuglog.hpp>
#include <components/vfs/bsaarchive.hpp>
#include "utils/textinputdialog.hpp"
@ -233,21 +236,22 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
if (!globalDataDir.isEmpty())
directories.insert(0, globalDataDir);
// normalize user supplied directories: resolve symlink, convert to native separator, make absolute
for (auto& currentDir : directories)
currentDir = QDir(QDir::cleanPath(currentDir)).canonicalPath();
// add directories, archives and content files
directories.removeDuplicates();
for (const auto& currentDir : directories)
std::unordered_set<QString> visitedDirectories;
for (const QString& currentDir : directories)
{
// normalize user supplied directories: resolve symlink, convert to native separator, make absolute
const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath();
if (!visitedDirectories.insert(canonicalDirPath).second)
continue;
// add new achives files presents in current directory
addArchivesFromDir(currentDir);
QString tooltip;
// add content files presents in current directory
mSelector->addFiles(currentDir, mNewDataDirs.contains(currentDir));
mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath));
// add current directory to list
ui.directoryListWidget->addItem(currentDir);
@ -255,7 +259,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
auto* item = ui.directoryListWidget->item(row);
// Display new content with green background
if (mNewDataDirs.contains(currentDir))
if (mNewDataDirs.contains(canonicalDirPath))
{
tooltip += "Will be added to the current profile\n";
item->setBackground(Qt::green);
@ -787,6 +791,7 @@ void Launcher::DataFilesPage::startNavMeshTool()
ui.navMeshLogPlainTextEdit->clear();
ui.navMeshProgressBar->setValue(0);
ui.navMeshProgressBar->setMaximum(1);
ui.navMeshProgressBar->resetFormat();
mNavMeshToolProgress = NavMeshToolProgress {};
@ -813,6 +818,8 @@ void Launcher::DataFilesPage::readNavMeshToolStderr()
void Launcher::DataFilesPage::updateNavMeshProgress(int minDataSize)
{
if (!mNavMeshToolProgress.mEnabled)
return;
QProcess& process = *mNavMeshToolInvoker->getProcess();
mNavMeshToolProgress.mMessagesData.append(process.readAllStandardError());
if (mNavMeshToolProgress.mMessagesData.size() < minDataSize)
@ -826,14 +833,23 @@ void Launcher::DataFilesPage::updateNavMeshProgress(int minDataSize)
ui.navMeshProgressBar->maximum(),
ui.navMeshProgressBar->value(),
};
while (true)
try
{
NavMeshTool::Message message;
const std::byte* const nextPosition = NavMeshTool::deserialize(position, end, message);
if (nextPosition == position)
break;
position = nextPosition;
handle = std::visit(handle, NavMeshTool::decode(message));
while (true)
{
NavMeshTool::Message message;
const std::byte* const nextPosition = NavMeshTool::deserialize(position, end, message);
if (nextPosition == position)
break;
position = nextPosition;
handle = std::visit(handle, NavMeshTool::decode(message));
}
}
catch (const std::exception& e)
{
Log(Debug::Error) << "Failed to deserialize navmeshtool message: " << e.what();
mNavMeshToolProgress.mEnabled = false;
ui.navMeshProgressBar->setFormat("Failed to update progress: " + QString(e.what()));
}
if (position != begin)
mNavMeshToolProgress.mMessagesData = mNavMeshToolProgress.mMessagesData.mid(position - begin);
@ -861,7 +877,10 @@ void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitSt
updateNavMeshProgress(0);
ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAllStandardOutput()));
if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit)
{
ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum());
ui.navMeshProgressBar->resetFormat();
}
ui.cancelNavMeshButton->setEnabled(false);
ui.navMeshProgressBar->setEnabled(false);
}

@ -83,6 +83,7 @@ namespace Launcher
private:
struct NavMeshToolProgress
{
bool mEnabled = true;
QByteArray mLogData;
QByteArray mMessagesData;
std::map<std::uint64_t, std::string> mWorldspaces;

@ -129,6 +129,9 @@ void Launcher::SettingsPage::on_importerButton_clicked()
if (addonsCheckBox->isChecked())
arguments.append(QString("--game-files"));
if (fontsCheckBox->isChecked())
arguments.append(QString("--fonts"));
arguments.append(QString("--encoding"));
arguments.append(mGameSettings.value(QString("encoding"), QString("win1252")));
arguments.append(QString("--ini"));

@ -66,6 +66,7 @@ int wmain(int argc, wchar_t *wargv[]) {
("cfg,c", bpo::value<std::string>(), "openmw.cfg file")
("output,o", bpo::value<std::string>()->default_value(""), "openmw.cfg file")
("game-files,g", "import esm and esp files")
("fonts,f", "import bitmap fonts")
("no-archives,A", "disable bsa archives import")
("encoding,e", bpo::value<std::string>()-> default_value("win1252"),
"Character encoding used in OpenMW game messages:\n"
@ -117,6 +118,13 @@ int wmain(int argc, wchar_t *wargv[]) {
MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile);
MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile);
if (!vm.count("fonts"))
{
ini.erase("Fonts:Font 0");
ini.erase("Fonts:Font 1");
ini.erase("Fonts:Font 2");
}
importer.merge(cfg, ini);
importer.mergeFallback(cfg, ini);

@ -26,6 +26,7 @@
#include <osg/Vec3f>
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <cstddef>

@ -21,6 +21,7 @@
#include <components/debug/debugging.hpp>
#include <components/navmeshtool/protocol.hpp>
#include <components/esm3/readerscache.hpp>
#include <components/detournavigator/debug.hpp>
#include <LinearMath/btVector3.h>
@ -35,6 +36,8 @@
#include <tuple>
#include <utility>
#include <vector>
#include <charconv>
#include <sstream>
namespace NavMeshTool
{
@ -224,6 +227,31 @@ namespace NavMeshTool
const std::vector<std::byte> data = serialize(value);
getRawStderr().write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
}
std::string toHex(std::string_view value)
{
std::string buffer(value.size() * 2, '0');
char* out = buffer.data();
for (const char v : value)
{
const std::ptrdiff_t space = static_cast<std::ptrdiff_t>(static_cast<std::uint8_t>(v) <= 0xf);
const auto [ptr, ec] = std::to_chars(out + space, out + space + 2, static_cast<std::uint8_t>(v), 16);
if (ec != std::errc())
throw std::system_error(std::make_error_code(ec));
out += 2;
}
return buffer;
}
std::string makeAddObjectErrorMessage(ObjectId objectId, DetourNavigator::AreaType areaType, const CollisionShape& shape)
{
std::ostringstream stream;
stream << "Failed to add object to recast mesh objectId=" << objectId.value()
<< " areaType=" << areaType
<< " fileName=" << shape.getInstance()->getSource()->mFileName
<< " fileHash=" << toHex(shape.getInstance()->getSource()->mFileHash);
return stream.str();
}
}
WorldspaceNavMeshInput::WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings)
@ -304,6 +332,9 @@ namespace NavMeshTool
forEachObject(cell, esmData, vfs, bulletShapeManager, readers,
[&] (BulletObject object)
{
if (object.getShapeInstance()->mVisualCollisionType != Resource::VisualCollisionType::None)
return;
const btTransform& transform = object.getCollisionObject().getWorldTransform();
const btAABB aabb = BulletHelpers::getAabb(*object.getCollisionObject().getCollisionShape(), transform);
mergeOrAssign(aabb, navMeshInput.mAabb, navMeshInput.mAabbInitialized);
@ -313,14 +344,17 @@ namespace NavMeshTool
const ObjectId objectId(++objectsCounter);
const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform,
DetourNavigator::AreaType_ground, [] (const auto&) {});
if (!navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform,
DetourNavigator::AreaType_ground, [] (const auto&) {}))
throw std::logic_error(makeAddObjectErrorMessage(objectId, DetourNavigator::AreaType_ground, shape));
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
{
const ObjectId avoidObjectId(++objectsCounter);
const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform,
DetourNavigator::AreaType_null, [] (const auto&) {});
if (!navMeshInput.mTileCachedRecastMeshManager.addObject(avoidObjectId, avoidShape, transform,
DetourNavigator::AreaType_null, [] (const auto&) {}))
throw std::logic_error(makeAddObjectErrorMessage(avoidObjectId, DetourNavigator::AreaType_null, avoidShape));
}
data.mObjects.emplace_back(std::move(object));

@ -9,6 +9,7 @@ openmw_add_executable(niftest
)
target_link_libraries(niftest
${Boost_FILESYSTEM_LIBRARY}
components
)

@ -1,7 +1,6 @@
///Program to test .nif files both on the FileSystem and in BSA archives.
#include <iostream>
#include <filesystem>
#include <components/misc/stringops.hpp>
#include <components/nif/niffile.hpp>
@ -11,9 +10,11 @@
#include <components/vfs/filesystemarchive.hpp>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
// Create local aliases for brevity
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
///See if the file has the named extension
bool hasExtension(std::string filename, std::string extensionToFind)
@ -136,7 +137,7 @@ int main(int argc, char **argv)
// std::cout << "Reading BSA File: " << name << std::endl;
readVFS(std::make_unique<VFS::BsaArchive>(name));
}
else if(std::filesystem::is_directory(std::filesystem::path(name)))
else if(bfs::is_directory(bfs::path(name)))
{
// std::cout << "Reading All Files in: " << name << std::endl;
readVFS(std::make_unique<VFS::FileSystemArchive>(name), name);

@ -84,6 +84,7 @@ void CSMDoc::Runner::start (bool delayed)
QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str());
arguments << "--replace=content";
arguments << "--content=builtin.omwscripts";
for (std::vector<std::string>::const_iterator iter (mContentFiles.begin());
iter!=mContentFiles.end(); ++iter)

@ -1,7 +1,6 @@
#ifndef CSM_DOC_SAVINGSTATE_H
#define CSM_DOC_SAVINGSTATE_H
#include <fstream>
#include <map>
#include <deque>

@ -61,6 +61,11 @@ namespace CSMWorld
/// Works like getAppendIndex unless an overloaded method uses the record pointer
/// to get additional info about the record that results in an alternative index.
int getAppendIndex(const std::string& id, UniversalId::Type type) const override
{
return getInsertIndex(id, type);
}
bool reorderRows (int baseIndex, const std::vector<int>& newOrder) override;
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
/// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).

@ -12,7 +12,6 @@
#include <osgViewer/ViewerEventHandlers>
#include <osg/LightModel>
#include <osg/Material>
#include <osg/Version>
#include <components/debug/debuglog.hpp>
#include <components/resource/scenemanager.hpp>
@ -97,14 +96,6 @@ RenderWidget::~RenderWidget()
try
{
CompositeViewer::get().removeView(mView);
#if OSG_VERSION_LESS_THAN(3,6,5)
// before OSG 3.6.4, the default font was a static object, and if it wasn't attached to the scene when a graphics context was destroyed, it's program wouldn't be released.
// 3.6.4 moved it into the object cache, which meant it usually got released, but not here.
// 3.6.5 improved cleanup with osgViewer::CompositeViewer::removeView so it more reliably released associated state for objects in the object cache.
osg::ref_ptr<osg::GraphicsContext> graphicsContext = mView->getCamera()->getGraphicsContext();
osgText::Font::getDefaultFont()->releaseGLObjects(graphicsContext->getState());
#endif
}
catch(const std::exception& e)
{
@ -146,9 +137,7 @@ CompositeViewer::CompositeViewer()
// https://gitlab.com/OpenMW/openmw/-/issues/5481
setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
#if OSG_VERSION_GREATER_OR_EQUAL(3,5,5)
setUseConfigureAffinity(false);
#endif
// disable the default setting of viewer.done() by pressing Escape.
setKeyEventSetsDone(0);

@ -53,7 +53,7 @@ QMenu & TableHeaderMouseEventHandler::createContextMenu()
action->setChecked(!table.isColumnHidden(i));
menu->addAction(action);
connect(action, &QAction::triggered, [this, &action, &i]() {
connect(action, &QAction::triggered, [this, action, i]() {
table.setColumnHidden(i, !action->isChecked());
action->setChecked(!action->isChecked());
action->toggle();

@ -60,7 +60,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp object worldview userdataserializer eventqueue
luabindings localscripts playerscripts objectbindings cellbindings asyncbindings
luabindings localscripts playerscripts objectbindings cellbindings
camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings
types/types types/door types/actor types/container types/weapon types/npc types/creature types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair
)
@ -221,9 +221,12 @@ if(APPLE)
target_link_libraries(openmw ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK})
if (FFmpeg_FOUND)
find_library(COREVIDEO_FRAMEWORK CoreVideo)
find_library(VDA_FRAMEWORK VideoDecodeAcceleration)
target_link_libraries(openmw z ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK})
target_link_libraries(openmw z)
target_link_options(openmw PRIVATE "LINKER:SHELL:-framework CoreVideo"
"LINKER:SHELL:-framework CoreMedia"
"LINKER:SHELL:-framework VideoToolbox"
"LINKER:SHELL:-framework AudioToolbox"
"LINKER:SHELL:-framework VideoDecodeAcceleration")
endif()
endif(APPLE)

@ -3,12 +3,9 @@
#include <iomanip>
#include <chrono>
#include <thread>
#include <filesystem>
#include <boost/filesystem/fstream.hpp>
#include <osg/Version>
#include <osgViewer/ViewerEventHandlers>
#include <osgDB/WriteFile>
@ -443,7 +440,9 @@ bool OMW::Engine::frame(float frametime)
stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems());
stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads());
mEnvironment.reportStats(frameNumber, *stats);
mMechanicsManager->reportStats(frameNumber, *stats);
mWorld->reportStats(frameNumber, *stats);
mLuaManager->reportStats(frameNumber, *stats);
}
}
catch (const std::exception& e)
@ -714,7 +713,7 @@ void OMW::Engine::createWindow()
void OMW::Engine::setWindowIcon()
{
std::ifstream windowIconStream;
boost::filesystem::ifstream windowIconStream;
std::string windowIcon = (mResDir / "openmw.png").string();
windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary);
if (windowIconStream.fail())
@ -791,13 +790,13 @@ void OMW::Engine::prepareEngine()
// showing a loading screen and keeping the window responsive while doing so
std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v3.xml").string();
bool keybinderUserExists = std::filesystem::exists(keybinderUser);
bool keybinderUserExists = boost::filesystem::exists(keybinderUser);
if(!keybinderUserExists)
{
std::string input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml").string();
if(std::filesystem::exists(input2)) {
std::filesystem::copy_file(input2, keybinderUser);
keybinderUserExists = std::filesystem::exists(keybinderUser);
if(boost::filesystem::exists(input2)) {
boost::filesystem::copy_file(input2, keybinderUser);
keybinderUserExists = boost::filesystem::exists(keybinderUser);
Log(Debug::Info) << "Loading keybindings file: " << keybinderUser;
}
}
@ -809,13 +808,13 @@ void OMW::Engine::prepareEngine()
const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt";
std::string userGameControllerdb;
if (std::filesystem::exists(userdefault))
if (boost::filesystem::exists(userdefault))
userGameControllerdb = userdefault;
std::string gameControllerdb;
if (std::filesystem::exists(localdefault))
if (boost::filesystem::exists(localdefault))
gameControllerdb = localdefault;
else if (std::filesystem::exists(globaldefault))
else if (boost::filesystem::exists(globaldefault))
gameControllerdb = globaldefault;
//else if it doesn't exist, pass in an empty string
@ -1032,16 +1031,14 @@ void OMW::Engine::go()
mViewer = new osgViewer::Viewer;
mViewer->setReleaseContextAtEndOfFrameHint(false);
#if OSG_VERSION_GREATER_OR_EQUAL(3,5,5)
// Do not try to outsmart the OS thread scheduler (see bug #4785).
mViewer->setUseConfigureAffinity(false);
#endif
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
prepareEngine();
std::ofstream stats;
boost::filesystem::ofstream stats;
if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE"))
{
stats.open(path, std::ios_base::out);
@ -1128,14 +1125,16 @@ void OMW::Engine::go()
if (stats)
{
constexpr unsigned statsReportDelay = 3;
const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber();
if (frameNumber >= 2)
if (frameNumber >= statsReportDelay)
{
mViewer->getViewerStats()->report(stats, frameNumber - 2);
const unsigned reportFrameNumber = frameNumber - statsReportDelay;
mViewer->getViewerStats()->report(stats, reportFrameNumber);
osgViewer::Viewer::Cameras cameras;
mViewer->getCameras(cameras);
for (auto camera : cameras)
camera->getStats()->report(stats, frameNumber - 2);
camera->getStats()->report(stats, reportFrameNumber);
}
}

@ -21,8 +21,6 @@
extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
#endif
#include <filesystem>
#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix))
#include <unistd.h>
#endif
@ -214,8 +212,8 @@ int runApplication(int argc, char *argv[])
Platform::init();
#ifdef __APPLE__
std::filesystem::path binary_path = std::filesystem::absolute(std::filesystem::path(argv[0]));
std::filesystem::current_path(binary_path.parent_path());
boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0]));
boost::filesystem::current_path(binary_path.parent_path());
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif

@ -54,14 +54,14 @@ namespace MWBase
virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) = 0;
virtual bool inJournal (const std::string& topicId, const std::string& infoId) = 0;
virtual bool inJournal (const std::string& topicId, const std::string& infoId) const = 0;
virtual void addTopic(std::string_view topic) = 0;
virtual void addChoice(std::string_view text,int choice) = 0;
virtual const std::vector<std::pair<std::string, int> >& getChoices() = 0;
virtual const std::vector<std::pair<std::string, int> >& getChoices() const = 0;
virtual bool isGoodbye() = 0;
virtual bool isGoodbye() const = 0;
virtual void goodbye() = 0;
@ -90,7 +90,7 @@ namespace MWBase
};
virtual std::list<std::string> getAvailableTopics() = 0;
virtual int getTopicFlag(const std::string&) = 0;
virtual int getTopicFlag(const std::string&) const = 0;
virtual bool checkServiceRefused (ResponseCallback* callback, ServiceType service = ServiceType::Any) = 0;

@ -4,17 +4,6 @@
#include <components/resource/resourcesystem.hpp>
#include "world.hpp"
#include "scriptmanager.hpp"
#include "dialoguemanager.hpp"
#include "journal.hpp"
#include "soundmanager.hpp"
#include "mechanicsmanager.hpp"
#include "inputmanager.hpp"
#include "windowmanager.hpp"
#include "statemanager.hpp"
#include "luamanager.hpp"
MWBase::Environment *MWBase::Environment::sThis = nullptr;
MWBase::Environment::Environment()
@ -27,9 +16,3 @@ MWBase::Environment::~Environment()
{
sThis = nullptr;
}
void MWBase::Environment::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
mMechanicsManager->reportStats(frameNumber, stats);
mWorld->reportStats(frameNumber, stats);
}

@ -5,11 +5,6 @@
#include <memory>
namespace osg
{
class Stats;
}
namespace Resource
{
class ResourceSystem;
@ -118,8 +113,6 @@ namespace MWBase
assert(sThis != nullptr);
return *sThis;
}
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
};
}

@ -129,6 +129,8 @@ namespace MWBase
virtual bool isConsoleMode() const = 0;
virtual bool isPostProcessorHudVisible() const = 0;
virtual void toggleVisible (MWGui::GuiWindow wnd) = 0;
virtual void forceHide(MWGui::GuiWindow wnd) = 0;

@ -305,7 +305,7 @@ namespace MWBase
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) = 0;
///< @return an updated Ptr
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec) = 0;
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr& ptr, const osg::Vec3f& vec, bool moveToActive) = 0;
///< @return an updated Ptr
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale, bool force = false) = 0;

@ -257,7 +257,7 @@ namespace MWDialogue
}
}
bool DialogueManager::inJournal (const std::string& topicId, const std::string& infoId)
bool DialogueManager::inJournal(const std::string& topicId, const std::string& infoId) const
{
const MWDialogue::Topic *topicHistory = nullptr;
MWBase::Journal *journal = MWBase::Environment::get().getJournal();
@ -290,9 +290,7 @@ namespace MWDialogue
const ESM::Dialogue& dialogue = *dialogues.find (topic);
const ESM::DialInfo* info =
mChoice == -1 && mActorKnownTopics.count(topic) ?
mActorKnownTopics[topic].mInfo : filter.search(dialogue, true);
const ESM::DialInfo* info = filter.search(dialogue, true);
if (info)
{
@ -424,9 +422,12 @@ namespace MWDialogue
return keywordList;
}
int DialogueManager::getTopicFlag(const std::string& topicId)
int DialogueManager::getTopicFlag(const std::string& topicId) const
{
return mActorKnownTopics[topicId].mFlags;
auto known = mActorKnownTopics.find(topicId);
if (known != mActorKnownTopics.end())
return known->second.mFlags;
return 0;
}
void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback)
@ -523,12 +524,12 @@ namespace MWDialogue
mChoices.emplace_back(text, choice);
}
const std::vector<std::pair<std::string, int> >& DialogueManager::getChoices()
const std::vector<std::pair<std::string, int>>& DialogueManager::getChoices() const
{
return mChoices;
}
bool DialogueManager::isGoodbye()
bool DialogueManager::isGoodbye() const
{
return mGoodbye;
}

@ -83,16 +83,16 @@ namespace MWDialogue
bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) override;
std::list<std::string> getAvailableTopics() override;
int getTopicFlag(const std::string& topicId) override;
int getTopicFlag(const std::string& topicId) const override;
bool inJournal (const std::string& topicId, const std::string& infoId) override;
bool inJournal (const std::string& topicId, const std::string& infoId) const override;
void addTopic(std::string_view topic) override;
void addChoice(std::string_view text,int choice) override;
const std::vector<std::pair<std::string, int> >& getChoices() override;
const std::vector<std::pair<std::string, int> >& getChoices() const override;
bool isGoodbye() override;
bool isGoodbye() const override;
void goodbye() override;

@ -184,8 +184,8 @@ namespace MWGui
return;
Widgets::MWSpellPtr spellWidget;
const int lineHeight = 18;
MyGUI::IntCoord coord(0, 0, mSpellArea->getWidth(), 18);
const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
MyGUI::IntCoord coord(0, 0, mSpellArea->getWidth(), lineHeight);
const MWWorld::ESMStore &store =
MWBase::Environment::get().getWorld()->getStore();

@ -4,8 +4,8 @@
#include <MyGUI_InputManager.h>
#include <MyGUI_LayerManager.h>
#include <filesystem>
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <components/compiler/exception.hpp>
#include <components/compiler/extensions0.hpp>
@ -220,7 +220,8 @@ namespace MWGui
void Console::executeFile (const std::string& path)
{
std::ifstream stream ((std::filesystem::path(path)));
namespace bfs = boost::filesystem;
bfs::ifstream stream ((bfs::path(path)));
if (!stream.is_open())
printError ("failed to open file: " + path);

@ -40,7 +40,7 @@ namespace MWGui
mSlider->setScrollRange(maxCount);
mItemText->setCaption(item);
int width = std::max(mItemText->getTextSize().width + 128, 320);
int width = std::max(mItemText->getTextSize().width + 160, 320);
setCoord(viewSize.width/2 - width/2,
viewSize.height/2 - mMainWidget->getHeight()/2,
width,

@ -92,14 +92,14 @@ namespace MWGui
// - Shader editor
MyGUI::TabItem* itemLV = mTabControl->addItem("Log Viewer");
itemLV->setCaptionWithReplacing("#{DebugMenu:LogViewer}");
itemLV->setCaptionWithReplacing(" #{DebugMenu:LogViewer} ");
mLogView = itemLV->createWidgetReal<MyGUI::EditBox>
("LogEdit", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Stretch);
mLogView->setEditReadOnly(true);
#ifndef BT_NO_PROFILE
MyGUI::TabItem* item = mTabControl->addItem("Physics Profiler");
item->setCaptionWithReplacing("#{DebugMenu:PhysicsProfiler}");
item->setCaptionWithReplacing(" #{DebugMenu:PhysicsProfiler} ");
mBulletProfilerEdit = item->createWidgetReal<MyGUI::EditBox>
("LogEdit", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Stretch);
#else

@ -7,6 +7,7 @@
#include <MyGUI_Button.h>
#include <components/debug/debuglog.hpp>
#include <components/widgets/box.hpp>
#include <components/widgets/list.hpp>
#include <components/translation/translation.hpp>
@ -25,6 +26,7 @@
#include "bookpage.hpp"
#include "textcolours.hpp"
#include "tooltips.hpp"
#include "journalbooks.hpp" // to_utf8_span
@ -59,6 +61,8 @@ namespace MWGui
PersuasionDialog::PersuasionDialog(ResponseCallback* callback)
: WindowModal("openmw_persuasion_dialog.layout")
, mCallback(callback)
, mInitialGoldLabelWidth(0)
, mInitialMainWidgetWidth(0)
{
getWidget(mCancelButton, "CancelButton");
getWidget(mAdmireButton, "AdmireButton");
@ -68,6 +72,26 @@ namespace MWGui
getWidget(mBribe100Button, "Bribe100Button");
getWidget(mBribe1000Button, "Bribe1000Button");
getWidget(mGoldLabel, "GoldLabel");
getWidget(mActionsBox, "ActionsBox");
int totalHeight = 3;
adjustAction(mAdmireButton, totalHeight);
adjustAction(mIntimidateButton, totalHeight);
adjustAction(mTauntButton, totalHeight);
adjustAction(mBribe10Button, totalHeight);
adjustAction(mBribe100Button, totalHeight);
adjustAction(mBribe1000Button, totalHeight);
totalHeight += 3;
int diff = totalHeight - mActionsBox->getSize().height;
if (diff > 0)
{
auto mainWidgetSize = mMainWidget->getSize();
mMainWidget->setSize(mainWidgetSize.width, mainWidgetSize.height + diff);
}
mInitialGoldLabelWidth = mActionsBox->getSize().width - mCancelButton->getSize().width - 8;
mInitialMainWidgetWidth = mMainWidget->getSize().width;
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onCancel);
mAdmireButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
@ -78,6 +102,14 @@ namespace MWGui
mBribe1000Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
}
void PersuasionDialog::adjustAction(MyGUI::Widget* action, int& totalHeight)
{
const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
auto currentCoords = action->getCoord();
action->setCoord(currentCoords.left, totalHeight, currentCoords.width, lineHeight);
totalHeight += lineHeight;
}
void PersuasionDialog::onCancel(MyGUI::Widget *sender)
{
setVisible(false);
@ -114,6 +146,13 @@ namespace MWGui
mBribe1000Button->setEnabled (playerGold >= 1000);
mGoldLabel->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold));
int diff = mGoldLabel->getRequestedSize().width - mInitialGoldLabelWidth;
if (diff > 0)
mMainWidget->setSize(mInitialMainWidgetWidth + diff, mMainWidget->getSize().height);
else
mMainWidget->setSize(mInitialMainWidgetWidth, mMainWidget->getSize().height);
WindowModal::onOpen();
}

@ -12,6 +12,7 @@
namespace Gui
{
class AutoSizedTextBox;
class MWList;
}
@ -31,6 +32,9 @@ namespace MWGui
private:
std::unique_ptr<ResponseCallback> mCallback;
int mInitialGoldLabelWidth;
int mInitialMainWidgetWidth;
MyGUI::Button* mCancelButton;
MyGUI::Button* mAdmireButton;
MyGUI::Button* mIntimidateButton;
@ -38,7 +42,10 @@ namespace MWGui
MyGUI::Button* mBribe10Button;
MyGUI::Button* mBribe100Button;
MyGUI::Button* mBribe1000Button;
MyGUI::TextBox* mGoldLabel;
MyGUI::Widget* mActionsBox;
Gui::AutoSizedTextBox* mGoldLabel;
void adjustAction(MyGUI::Widget* action, int& totalHeight);
void onCancel (MyGUI::Widget* sender);
void onPersuade (MyGUI::Widget* sender);

@ -20,9 +20,28 @@ namespace
{
std::string getCountString(int count)
{
static const int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight();
if (count == 1)
return "";
// With small text size we can use up to 4 characters, while with large ones - only up to 3.
if (fontHeight > 16)
{
if (count > 999999999)
return MyGUI::utility::toString(count/1000000000) + "b";
else if (count > 99999999)
return ">9m";
else if (count > 999999)
return MyGUI::utility::toString(count/1000000) + "m";
else if (count > 99999)
return ">9k";
else if (count > 999)
return MyGUI::utility::toString(count/1000) + "k";
else
return MyGUI::utility::toString(count);
}
if (count > 999999999)
return MyGUI::utility::toString(count/1000000000) + "b";
else if (count > 999999)

@ -1,12 +1,10 @@
#include "loadingscreen.hpp"
#include <array>
#include <condition_variable>
#include <osgViewer/Viewer>
#include <osg/Texture2D>
#include <osg/Version>
#include <MyGUI_ScrollBar.h>
#include <MyGUI_Gui.h>
@ -305,12 +303,8 @@ namespace MWGui
mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture);
}
#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback);
mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback);
#else
mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback);
#endif
mCopyFramebufferToTextureCallback->reset();
mSplashImage->setBackgroundImage("");

@ -766,6 +766,7 @@ namespace MWGui
, mEventBoxLocal(nullptr)
, mGlobalMapRender(std::make_unique<MWRender::GlobalMap>(localMapRender->getRoot(), workQueue))
, mEditNoteDialog()
, mAllowZooming(Settings::Manager::getBool("allow zooming", "Map"))
{
static bool registered = false;
if (!registered)
@ -804,8 +805,7 @@ namespace MWGui
getWidget(mEventBoxGlobal, "EventBoxGlobal");
mEventBoxGlobal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
mEventBoxGlobal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
const bool allowZooming = Settings::Manager::getBool("allow zooming", "Map");
if(allowZooming)
if (mAllowZooming)
mEventBoxGlobal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
mEventBoxGlobal->setDepth(Global_ExploreOverlayLayer);
@ -813,7 +813,7 @@ namespace MWGui
mEventBoxLocal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked);
if (allowZooming)
if (mAllowZooming)
mEventBoxLocal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
LocalMapBase::init(mLocalMap, mPlayerArrowLocal, getLocalViewingDistance());
@ -1067,7 +1067,8 @@ namespace MWGui
markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}")));
markerWidget->setDepth(Global_MarkerLayer);
markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
markerWidget->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
if (mAllowZooming)
markerWidget->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
return markerWidget;
@ -1351,14 +1352,16 @@ namespace MWGui
marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
marker->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onCustomMarkerDoubleClicked);
marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
if (mAllowZooming)
marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
}
void MapWindow::doorMarkerCreated(MyGUI::Widget *marker)
{
marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag);
marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart);
marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
if (mAllowZooming)
marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
}
void MapWindow::asyncPrepareSaveMap()

@ -325,6 +325,7 @@ namespace MWGui
EditNoteDialog mEditNoteDialog;
ESM::CustomMarker mEditingMarker;
bool mAllowZooming;
void onPinToggled() override;
void onTitleDoubleClicked() override;

@ -9,6 +9,8 @@
#include <components/fx/widgets.hpp>
#include <components/fx/technique.hpp>
#include <components/misc/utf8stream.hpp>
#include <components/widgets/box.hpp>
#include "../mwrender/postprocessor.hpp"
@ -30,7 +32,6 @@ namespace MWGui
PostProcessorHud::PostProcessorHud()
: WindowBase("openmw_postprocessor_hud.layout")
{
getWidget(mTabConfiguration, "TabConfiguration");
getWidget(mActiveList, "ActiveList");
getWidget(mInactiveList, "InactiveList");
getWidget(mConfigLayout, "ConfigLayout");
@ -397,8 +398,14 @@ namespace MWGui
if (!technique)
continue;
if (!technique->getHidden() && !processor->isTechniqueEnabled(technique) && name.find(mFilter->getCaption()) != std::string::npos)
mInactiveList->addItem(name, technique);
if (!technique->getHidden() && !processor->isTechniqueEnabled(technique))
{
std::string lowerName = Utf8Stream::lowerCaseUtf8(name);
std::string lowerCaption = mFilter->getCaption();
lowerCaption = Utf8Stream::lowerCaseUtf8(lowerCaption);
if (lowerName.find(lowerCaption) != std::string::npos)
mInactiveList->addItem(name, technique);
}
}
for (auto technique : processor->getTechniques())
@ -409,6 +416,10 @@ namespace MWGui
auto tryFocus = [this](ListWrapper* widget, const std::string& hint)
{
MyGUI::Widget* oldFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget();
if (oldFocus == mFilter)
return;
size_t index = widget->findItemIndexWith(hint);
if (index != MyGUI::ITEM_NONE)

@ -81,8 +81,6 @@ namespace MWGui
void layout();
MyGUI::TabItem* mTabConfiguration;
ListWrapper* mActiveList;
ListWrapper* mInactiveList;

@ -400,7 +400,7 @@ namespace MWGui
return;
Widgets::MWSkillPtr skillWidget;
const int lineHeight = 18;
const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
MyGUI::IntCoord coord1(0, 0, mSkillList->getWidth(), 18);
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -436,8 +436,8 @@ namespace MWGui
if (mCurrentRaceId.empty())
return;
const int lineHeight = 18;
MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), 18);
const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), lineHeight);
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Race *race = store.get<ESM::Race>().find(mCurrentRaceId);

@ -47,7 +47,6 @@ namespace MWGui
getWidget(mDeleteButton, "DeleteButton");
getWidget(mSaveList, "SaveList");
getWidget(mSaveNameEdit, "SaveNameEdit");
getWidget(mSpacer, "Spacer");
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked);
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked);
mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked);
@ -219,7 +218,6 @@ namespace MWGui
mSaveNameEdit->setVisible(!load);
mCharacterSelection->setUserString("Hidden", load ? "false" : "true");
mCharacterSelection->setVisible(load);
mSpacer->setUserString("Hidden", load ? "false" : "true");
mDeleteButton->setUserString("Hidden", load ? "false" : "true");
mDeleteButton->setVisible(load);

@ -63,7 +63,6 @@ namespace MWGui
MyGUI::Button* mDeleteButton;
MyGUI::ListBox* mSaveList;
MyGUI::EditBox* mSaveNameEdit;
MyGUI::Widget* mSpacer;
const MWState::Character* mCurrentCharacter;
const MWState::Slot* mCurrentSlot;

@ -814,7 +814,7 @@ namespace MWGui
void SettingsWindow::layoutControlsBox()
{
const int h = 18;
const int h = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
const int w = mControlsBox->getWidth() - 28;
const int noWidgetsInRow = 2;
const int totalH = mControlsBox->getChildCount() / noWidgetsInRow * h;

@ -5,6 +5,9 @@
#include <MyGUI_ImageBox.h>
#include <MyGUI_Gui.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include <components/widgets/sharedstatebutton.hpp>
#include <components/widgets/box.hpp>
@ -84,7 +87,7 @@ namespace MWGui
int curType = -1;
const int spellHeight = 18;
const int spellHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
mLines.clear();

@ -331,6 +331,19 @@ namespace MWGui
MWWorld::Ptr player = MWMechanics::getPlayer();
const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player);
std::string detailText;
std::stringstream detail;
for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute)
{
float mult = PCstats.getLevelupAttributeMultiplier(attribute);
mult = std::min(mult, 100 - PCstats.getAttribute(attribute).getBase());
if (mult > 1)
detail << (detail.str().empty() ? "" : "\n") << "#{"
<< MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[attribute])
<< "} x" << MyGUI::utility::toString(mult);
}
detailText = MyGUI::LanguageManager::getInstance().replaceTags(detail.str());
// level progress
MyGUI::Widget* levelWidget;
for (int i=0; i<2; ++i)
@ -342,18 +355,8 @@ namespace MWGui
levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max));
levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/"
+ MyGUI::utility::toString(max));
levelWidget->setUserString("Caption_LevelDetailText", detailText);
}
std::stringstream detail;
for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute)
{
float mult = PCstats.getLevelupAttributeMultiplier(attribute);
mult = std::min(mult, 100 - PCstats.getAttribute(attribute).getBase());
if (mult > 1)
detail << (detail.str().empty() ? "" : "\n") << "#{"
<< MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[attribute])
<< "} x" << MyGUI::utility::toString(mult);
}
levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str()));
setFactions(PCstats.getFactionRanks());
setExpelled(PCstats.getExpelled ());

@ -98,6 +98,8 @@ namespace MWGui
const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
for (int i=0; i<3; ++i)
{
int price = static_cast<int>(pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->mValue.getInteger());
@ -105,7 +107,7 @@ namespace MWGui
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true);
MyGUI::Button* button = mTrainingOptions->createWidget<MyGUI::Button>(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip
MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default);
MyGUI::IntCoord(5, 5+i*lineHeight, mTrainingOptions->getWidth()-10, lineHeight), MyGUI::Align::Default);
button->setUserData(skills[i].first);
button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected);

@ -3,7 +3,6 @@
#include <algorithm>
#include <cassert>
#include <chrono>
#include <filesystem>
#include <thread>
#include <osgViewer/Viewer>
@ -196,7 +195,7 @@ namespace MWGui
mScalingFactor = std::clamp(Settings::Manager::getFloat("scaling factor", "GUI"), 0.5f, 8.f);
mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(),
resourceSystem->getVFS(), mScalingFactor, "mygui",
(std::filesystem::path(logpath) / "MyGUI.log").generic_string());
(boost::filesystem::path(logpath) / "MyGUI.log").generic_string());
mGui = new MyGUI::Gui;
mGui->initialise("");
@ -207,7 +206,6 @@ namespace MWGui
// Load fonts
mFontLoader = std::make_unique<Gui::FontLoader>(encoding, resourceSystem->getVFS(), mScalingFactor);
mFontLoader->loadBitmapFonts();
//Register own widgets with MyGUI
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget");
@ -235,7 +233,6 @@ namespace MWGui
MyGUI::FactoryManager::getInstance().registerFactory<ResourceImageSetPointerFix>("Resource", "ResourceImageSetPointer");
MyGUI::FactoryManager::getInstance().registerFactory<AutoSizedResourceSkin>("Resource", "AutoSizedResourceSkin");
MyGUI::ResourceManager::getInstance().load("core.xml");
mFontLoader->loadTrueTypeFonts();
bool keyboardNav = Settings::Manager::getBool("keyboard navigation", "GUI");
mKeyboardNavigation = std::make_unique<KeyboardNavigation>();
@ -1174,9 +1171,6 @@ namespace MWGui
for (WindowBase* window : mWindows)
window->onResChange(x, y);
// We should reload TrueType fonts to fit new resolution
mFontLoader->loadTrueTypeFonts();
// TODO: check if any windows are now off-screen and move them back if so
}
@ -1519,6 +1513,11 @@ namespace MWGui
return mConsole && mConsole->isVisible();
}
bool WindowManager::isPostProcessorHudVisible() const
{
return mPostProcessorHud->isVisible();
}
MWGui::GuiMode WindowManager::getMode() const
{
if (mGuiModes.empty())
@ -2078,7 +2077,10 @@ namespace MWGui
void WindowManager::togglePostProcessorHud()
{
if (!MWBase::Environment::get().getWorld()->getPostProcessor()->isEnabled())
{
messageBox("Postprocessor is not enabled.");
return;
}
bool visible = mPostProcessorHud->isVisible();

@ -169,6 +169,8 @@ namespace MWGui
bool isConsoleMode() const override;
bool isPostProcessorHudVisible() const override;
void toggleVisible(GuiWindow wnd) override;
void forceHide(MWGui::GuiWindow wnd) override;

@ -324,6 +324,12 @@ namespace MWInput
return;
}
if (MWBase::Environment::get().getWindowManager()->isPostProcessorHudVisible())
{
MWBase::Environment::get().getWindowManager()->togglePostProcessorHud();
return;
}
if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) //No open GUIs, open up the MainMenu
{
MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);

@ -1,73 +0,0 @@
#include "luabindings.hpp"
#include "luamanagerimp.hpp"
namespace sol
{
template <>
struct is_automagical<MWLua::AsyncPackageId> : std::false_type {};
template <>
struct is_automagical<LuaUtil::Callback> : std::false_type {};
}
namespace MWLua
{
struct TimerCallback
{
AsyncPackageId mAsyncId;
std::string mName;
};
sol::function getAsyncPackageInitializer(const Context& context)
{
using TimerType = LuaUtil::ScriptsContainer::TimerType;
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback)
{
asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback));
return TimerCallback{asyncId, std::string(name)};
};
api["newSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
const TimerCallback& callback, sol::object callbackArg)
{
callback.mAsyncId.mContainer->setupSerializableTimer(
TimerType::SIMULATION_TIME, world->getSimulationTime() + delay,
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
};
api["newGameTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
const TimerCallback& callback, sol::object callbackArg)
{
callback.mAsyncId.mContainer->setupSerializableTimer(
TimerType::GAME_TIME, world->getGameTime() + delay,
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
};
api["newUnsavableSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
{
asyncId.mContainer->setupUnsavableTimer(
TimerType::SIMULATION_TIME, world->getSimulationTime() + delay, asyncId.mScriptId, std::move(callback));
};
api["newUnsavableGameTimer"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
{
asyncId.mContainer->setupUnsavableTimer(
TimerType::GAME_TIME, world->getGameTime() + delay, asyncId.mScriptId, std::move(callback));
};
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) -> LuaUtil::Callback
{
return LuaUtil::Callback{std::move(fn), asyncId.mHiddenData};
};
sol::usertype<LuaUtil::Callback> callbackType = context.mLua->sol().new_usertype<LuaUtil::Callback>("Callback");
callbackType[sol::meta_function::call] =
[](const LuaUtil::Callback& callback, sol::variadic_args va) { return callback.call(sol::as_args(va)); };
auto initializer = [](sol::table hiddenData)
{
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey];
return AsyncPackageId{id.mContainer, id.mIndex, hiddenData};
};
return sol::make_object(context.mLua->sol(), initializer);
}
}

@ -5,6 +5,11 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "../mwrender/postprocessor.hpp"
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/shader/shadermanager.hpp>
#include <components/lua/luastate.hpp>
@ -46,6 +51,27 @@ namespace MWLua
});
};
api["triggerShaderReload"] = [context]()
{
context.mLuaManager->addAction([]
{
auto world = MWBase::Environment::get().getWorld();
world->getRenderingManager()->getResourceSystem()->getSceneManager()->getShaderManager().triggerShaderReload();
world->getPostProcessor()->triggerShaderReload();
});
};
api["setShaderHotReloadEnabled"] = [context](bool value)
{
context.mLuaManager->addAction([value]
{
auto world = MWBase::Environment::get().getWorld();
world->getRenderingManager()->getResourceSystem()->getSceneManager()->getShaderManager().setHotReloadEnabled(value);
world->getPostProcessor()->mEnableLiveReload = value;
});
};
return LuaUtil::makeReadOnly(api);
}
}

@ -38,15 +38,6 @@ namespace MWLua
void initCellBindingsForLocalScripts(const Context&);
void initCellBindingsForGlobalScripts(const Context&);
// Implemented in asyncbindings.cpp
struct AsyncPackageId
{
LuaUtil::ScriptsContainer* mContainer;
int mScriptId;
sol::table mHiddenData;
};
sol::function getAsyncPackageInitializer(const Context&);
// Implemented in camerabindings.cpp
sol::table initCameraPackage(const Context&);

@ -1,6 +1,8 @@
#include "luamanagerimp.hpp"
#include <filesystem>
#include <osg/Stats>
#include "sol/state_view.hpp"
#include <components/debug/debuglog.hpp>
@ -10,6 +12,7 @@
#include <components/settings/settings.hpp>
#include <components/lua/asyncpackage.hpp>
#include <components/lua/utilpackage.hpp>
#include <components/lua_ui/util.hpp>
@ -84,7 +87,10 @@ namespace MWLua
LocalScripts::initializeSelfPackage(localContext);
LuaUtil::LuaStorage::initLuaBindings(mLua.sol());
mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context));
mLua.addCommonPackage("openmw.async",
LuaUtil::getAsyncPackageInitializer(
mLua.sol(), [this] { return mWorldView.getSimulationTime(); },
[this] { return mWorldView.getGameTime(); }));
mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol()));
mLua.addCommonPackage("openmw.core", initCorePackage(context));
mLua.addCommonPackage("openmw.types", initTypesPackage(context));
@ -111,24 +117,28 @@ namespace MWLua
void LuaManager::loadPermanentStorage(const std::string& userConfigPath)
{
auto globalPath = std::filesystem::path(userConfigPath) / "global_storage.bin";
auto playerPath = std::filesystem::path(userConfigPath) / "player_storage.bin";
if (std::filesystem::exists(globalPath))
mGlobalStorage.load(globalPath.string());
if (std::filesystem::exists(playerPath))
mPlayerStorage.load(playerPath.string());
auto globalPath = boost::filesystem::path(userConfigPath) / "global_storage.bin";
auto playerPath = boost::filesystem::path(userConfigPath) / "player_storage.bin";
if (boost::filesystem::exists(globalPath))
mGlobalStorage.load(globalPath);
if (boost::filesystem::exists(playerPath))
mPlayerStorage.load(playerPath);
}
void LuaManager::savePermanentStorage(const std::string& userConfigPath)
{
std::filesystem::path confDir(userConfigPath);
mGlobalStorage.save((confDir / "global_storage.bin").string());
mPlayerStorage.save((confDir / "player_storage.bin").string());
boost::filesystem::path confDir(userConfigPath);
mGlobalStorage.save((confDir / "global_storage.bin"));
mPlayerStorage.save((confDir / "player_storage.bin"));
}
void LuaManager::update()
{
static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua");
static const int gcStepCount = Settings::Manager::getInt("gc steps per frame", "Lua");
if (gcStepCount > 0)
lua_gc(mLua.sol(), LUA_GCSTEP, gcStepCount);
if (mPlayer.isEmpty())
return; // The game is not started yet.
@ -178,7 +188,7 @@ namespace MWLua
// Run queued callbacks
for (CallbackWithData& c : mQueuedCallbacks)
c.mCallback.call(c.mArg);
c.mCallback.tryCall(c.mArg);
mQueuedCallbacks.clear();
// Engine handlers in local scripts
@ -600,4 +610,9 @@ namespace MWLua
mActionQueue.push_back(std::make_unique<FunctionAction>(&mLua, std::move(action), name));
}
void LuaManager::reportStats(unsigned int frameNumber, osg::Stats& stats)
{
const sol::state_view state(mLua.sol());
stats.setAttribute(frameNumber, "Lua UsedMemory", state.memory_used());
}
}

@ -108,7 +108,7 @@ namespace MWLua
void handleConsoleCommand(const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) override;
// Used to call Lua callbacks from C++
void queueCallback(LuaUtil::Callback callback, sol::object arg)
void queueCallback(LuaUtil::Callback callback, sol::main_object arg)
{
mQueuedCallbacks.push_back({std::move(callback), std::move(arg)});
}
@ -119,13 +119,16 @@ namespace MWLua
template <class Arg>
std::function<void(Arg)> wrapLuaCallback(const LuaUtil::Callback& c)
{
return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); };
return
[this, c](Arg arg) { this->queueCallback(c, sol::main_object(this->mLua.sol(), sol::in_place, arg)); };
}
LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; }
bool isProcessingInputEvents() const { return mProcessingInputEvents; }
void reportStats(unsigned int frameNumber, osg::Stats& stats);
private:
void initConfiguration();
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
@ -171,7 +174,7 @@ namespace MWLua
struct CallbackWithData
{
LuaUtil::Callback mCallback;
sol::object mArg;
sol::main_object mArg;
};
std::vector<CallbackWithData> mQueuedCallbacks;

@ -109,14 +109,12 @@ namespace MWLua
MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false);
return res;
};
api["asyncCastRenderingRay"] =
[manager=context.mLuaManager](const LuaUtil::Callback& callback, const osg::Vec3f& from, const osg::Vec3f& to)
{
manager->addAction([manager, callback, from, to]
{
api["asyncCastRenderingRay"] = [context](
const sol::table& callback, const osg::Vec3f& from, const osg::Vec3f& to) {
context.mLuaManager->addAction([context, callback = LuaUtil::Callback::fromLua(callback), from, to] {
MWPhysics::RayCastingResult res;
MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false);
manager->queueCallback(callback, sol::make_object(callback.mFunc.lua_state(), res));
context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res));
});
};

@ -139,7 +139,7 @@ namespace MWLua
{
auto& stats = ptr.getClass().getCreatureStats(ptr);
if(prop == "current")
stats.setLevel(value.as<int>());
stats.setLevel(LuaUtil::cast<int>(value));
}
};
@ -179,7 +179,7 @@ namespace MWLua
{
auto& stats = ptr.getClass().getCreatureStats(ptr);
auto stat = stats.getDynamic(index);
float floatValue = value.as<float>();
float floatValue = LuaUtil::cast<float>(value);
if(prop == "base")
stat.setBase(floatValue);
else if(prop == "current")
@ -209,9 +209,9 @@ namespace MWLua
float getModified(const Context& context) const
{
auto base = get(context, "base", &MWMechanics::AttributeValue::getBase).as<float>();
auto damage = get(context, "damage", &MWMechanics::AttributeValue::getDamage).as<float>();
auto modifier = get(context, "modifier", &MWMechanics::AttributeValue::getModifier).as<float>();
auto base = LuaUtil::cast<float>(get(context, "base", &MWMechanics::AttributeValue::getBase));
auto damage = LuaUtil::cast<float>(get(context, "damage", &MWMechanics::AttributeValue::getDamage));
auto modifier = LuaUtil::cast<float>(get(context, "modifier", &MWMechanics::AttributeValue::getModifier));
return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified
}
@ -234,7 +234,7 @@ namespace MWLua
{
auto& stats = ptr.getClass().getCreatureStats(ptr);
auto stat = stats.getAttribute(index);
float floatValue = value.as<float>();
float floatValue = LuaUtil::cast<float>(value);
if(prop == "base")
stat.setBase(floatValue);
else if(prop == "damage")
@ -281,9 +281,9 @@ namespace MWLua
float getModified(const Context& context) const
{
auto base = get(context, "base", &MWMechanics::SkillValue::getBase).as<float>();
auto damage = get(context, "damage", &MWMechanics::SkillValue::getDamage).as<float>();
auto modifier = get(context, "modifier", &MWMechanics::SkillValue::getModifier).as<float>();
auto base = LuaUtil::cast<float>(get(context, "base", &MWMechanics::SkillValue::getBase));
auto damage = LuaUtil::cast<float>(get(context, "damage", &MWMechanics::SkillValue::getDamage));
auto modifier = LuaUtil::cast<float>(get(context, "modifier", &MWMechanics::SkillValue::getModifier));
return std::max(0.f, base - damage + modifier); // Should match SkillValue::getModified
}
@ -315,7 +315,7 @@ namespace MWLua
{
auto& stats = ptr.getClass().getNpcStats(ptr);
auto stat = stats.getSkill(index);
float floatValue = value.as<float>();
float floatValue = LuaUtil::cast<float>(value);
if(prop == "base")
stat.setBase(floatValue);
else if(prop == "damage")

@ -236,11 +236,11 @@ namespace MWLua
SetEquipmentAction::Equipment eqp;
for (auto& [key, value] : equipment)
{
int slot = key.as<int>();
int slot = LuaUtil::cast<int>(key);
if (value.is<Object>())
eqp[slot] = value.as<Object>().id();
eqp[slot] = LuaUtil::cast<Object>(value).id();
else
eqp[slot] = value.as<std::string>();
eqp[slot] = LuaUtil::cast<std::string>(value);
}
context.mLuaManager->addAction(std::make_unique<SetEquipmentAction>(context.mLua, obj.id(), std::move(eqp)));
};

@ -1,6 +1,8 @@
#include "types.hpp"
#include <components/esm3/loadingr.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp>
#include <apps/openmw/mwworld/esmstore.hpp>
@ -16,17 +18,23 @@ namespace MWLua
{
void addIngredientBindings(sol::table ingredient, const Context& context)
{
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
const MWWorld::Store<ESM::Ingredient>* store = &MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>();
ingredient["record"] = sol::overload(
[](const Object& obj)-> const ESM::Ingredient* { return obj.ptr().get<ESM::Ingredient>()->mBase; },
[store](const std::string& recordID)-> const ESM::Ingredient* {return store->find(recordID); });
sol::usertype<ESM::Ingredient> record = context.mLua->sol().new_usertype<ESM::Ingredient>(("ESM3_Ingredient"));
record[sol::meta_function::to_string] = [](const ESM::Potion& rec) {return "ESM3_Ingredient[" + rec.mId + "]"; };
record[sol::meta_function::to_string] = [](const ESM::Ingredient& rec) {return "ESM3_Ingredient[" + rec.mId + "]"; };
record["id"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {return rec.mId; });
record["name"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {return rec.mName; });
record["model"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {return rec.mModel; });
record["model"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string {
return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs);
});
record["mwscript"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {return rec.mScript; });
record["icon"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {return rec.mIcon; });
record["icon"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});
record["weight"] = sol::readonly_property([](const ESM::Ingredient& rec) -> float {return rec.mData.mWeight; });
record["value"] = sol::readonly_property([](const ESM::Ingredient& rec) -> int{return rec.mData.mValue; });
}

@ -89,69 +89,6 @@ namespace MWLua
sol::table initUserInterfacePackage(const Context& context)
{
auto uiContent = context.mLua->sol().new_usertype<LuaUi::Content>("UiContent");
uiContent[sol::meta_function::length] = [](const LuaUi::Content& content)
{
return content.size();
};
uiContent[sol::meta_function::index] = sol::overload(
[](const LuaUi::Content& content, size_t index)
{
return content.at(fromLuaIndex(index));
},
[](const LuaUi::Content& content, std::string_view name)
{
return content.at(name);
});
uiContent[sol::meta_function::new_index] = sol::overload(
[](LuaUi::Content& content, size_t index, const sol::table& table)
{
content.assign(fromLuaIndex(index), table);
},
[](LuaUi::Content& content, size_t index, sol::nil_t nil)
{
content.remove(fromLuaIndex(index));
},
[](LuaUi::Content& content, std::string_view name, const sol::table& table)
{
content.assign(name, table);
},
[](LuaUi::Content& content, std::string_view name, sol::nil_t nil)
{
content.remove(name);
});
uiContent["insert"] = [](LuaUi::Content& content, size_t index, const sol::table& table)
{
content.insert(fromLuaIndex(index), table);
};
uiContent["add"] = [](LuaUi::Content& content, const sol::table& table)
{
content.insert(content.size(), table);
};
uiContent["indexOf"] = [](LuaUi::Content& content, const sol::table& table) -> sol::optional<size_t>
{
size_t index = content.indexOf(table);
if (index < content.size())
return toLuaIndex(index);
else
return sol::nullopt;
};
{
auto pairs = [](LuaUi::Content& content)
{
auto next = [](LuaUi::Content& content, size_t i) -> sol::optional<std::tuple<size_t, sol::table>>
{
if (i < content.size())
return std::make_tuple(i + 1, content.at(i));
else
return sol::nullopt;
};
return std::make_tuple(next, content, 0);
};
uiContent[sol::meta_function::ipairs] = pairs;
uiContent[sol::meta_function::pairs] = pairs;
}
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
element["layout"] = sol::property(
[](LuaUi::Element& element)
@ -210,12 +147,8 @@ namespace MWLua
luaManager->addAction([wm, obj=obj.as<LObject>()]{ wm->setConsoleSelectedObject(obj.ptr()); });
}
};
api["content"] = [](const sol::table& table)
{
return LuaUi::Content(table);
};
api["create"] = [context](const sol::table& layout)
{
api["content"] = LuaUi::loadContentConstructor(context.mLua);
api["create"] = [context](const sol::table& layout) {
auto element = LuaUi::Element::make(layout);
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::CREATE, element, context.mLua));
return element;

@ -148,7 +148,9 @@ namespace MWMechanics
void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration)
{
const auto& creatureStats = ptr.getClass().getCreatureStats(ptr);
if (mIterating)
return;
auto& creatureStats = ptr.getClass().getCreatureStats(ptr);
assert(&creatureStats.getActiveSpells() == this);
IterationGuard guard{*this};
// Erase no longer active spells and effects
@ -306,6 +308,15 @@ namespace MWMechanics
}
++spellIt;
}
static const bool keepCalm = Settings::Manager::getBool("classic calm spells behavior", "Game");
if (keepCalm)
{
ESM::MagicEffect::Effects effect
= ptr.getClass().isNpc() ? ESM::MagicEffect::CalmHumanoid : ESM::MagicEffect::CalmCreature;
if (creatureStats.getMagicEffects().get(effect).getMagnitude() > 0.f)
creatureStats.getAiSequence().stopCombat();
}
}
void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
@ -497,4 +508,13 @@ namespace MWMechanics
for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue)
mQueue.emplace_back(ActiveSpellParams{spell});
}
void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr)
{
purge([] (const auto& spell)
{
return spell.getType() == ESM::ActiveSpells::Type_Consumable || spell.getType() == ESM::ActiveSpells::Type_Temporary;
}, ptr);
mQueue.clear();
}
}

@ -145,6 +145,8 @@ namespace MWMechanics
///< case insensitive
void skipWorsenings(double hours);
void unloadActor(const MWWorld::Ptr& ptr);
};
}

@ -220,10 +220,7 @@ void soulTrap(const MWWorld::Ptr& creature)
void removeTemporaryEffects(const MWWorld::Ptr& ptr)
{
ptr.getClass().getCreatureStats(ptr).getActiveSpells().purge([] (const auto& spell)
{
return spell.getType() == ESM::ActiveSpells::Type_Consumable || spell.getType() == ESM::ActiveSpells::Type_Temporary;
}, ptr);
ptr.getClass().getCreatureStats(ptr).getActiveSpells().unloadActor(ptr);
}
}
@ -601,7 +598,8 @@ namespace MWMechanics
}
std::set<MWWorld::Ptr> playerAllies;
getActorsSidingWith(MWMechanics::getPlayer(), playerAllies, cachedAllies);
MWWorld::Ptr player = MWMechanics::getPlayer();
getActorsSidingWith(player, playerAllies, cachedAllies);
bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end();
@ -624,7 +622,8 @@ namespace MWMechanics
mechanicsManager->startCombat(actor1, actor2);
// Also have actor1's allies start combat
for (const MWWorld::Ptr& ally1 : allies1)
mechanicsManager->startCombat(ally1, actor2);
if (ally1 != player)
mechanicsManager->startCombat(ally1, actor2);
return;
}
}
@ -724,7 +723,10 @@ namespace MWMechanics
{
bool actorKilled = false;
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.getCasterActorId());
MWWorld::Ptr caster
= MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.getCasterActorId());
if (caster.isEmpty())
continue;
for (const auto& effect : spell.getEffects())
{
static const std::array<int, 7> damageEffects{

@ -112,7 +112,7 @@ namespace MWMechanics
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
if (target.isEmpty())
return false;
return true;
if(!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered
// with the MechanicsManager

@ -360,14 +360,27 @@ namespace MWMechanics
if (!isWaterCreature && !isFlyingCreature)
{
// findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance
if (const auto destination = DetourNavigator::findRandomPointAroundCircle(*navigator, agentBounds,
mInitialActorPosition, wanderDistance, navigatorFlags, []() {
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
return Misc::Rng::rollProbability(prng);
}))
mDestination = *destination;
else
mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius);
const auto getRandom = []()
{
return Misc::Rng::rollProbability(MWBase::Environment::get().getWorld()->getPrng());
};
auto destination = DetourNavigator::findRandomPointAroundCircle(*navigator, agentBounds,
mInitialActorPosition, wanderRadius, navigatorFlags, getRandom);
if (destination.has_value())
{
osg::Vec3f direction = *destination - mInitialActorPosition;
if (direction.length() > wanderDistance)
{
direction.normalize();
const osg::Vec3f adjustedDestination = mInitialActorPosition + direction * wanderRadius;
destination = DetourNavigator::raycast(*navigator, agentBounds, currentPosition,
adjustedDestination, navigatorFlags);
if (destination.has_value() && (*destination - mInitialActorPosition).length() > wanderDistance)
continue;
}
}
mDestination = destination.has_value() ? *destination
: getRandomPointAround(mInitialActorPosition, wanderRadius);
}
else
mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius);

@ -333,6 +333,9 @@ void CharacterController::refreshHitRecoilAnims()
{
if (!mAnimation->isPlaying(mCurrentHit))
{
if (isKnockedOut() && mCurrentHit.empty() && knockout)
return;
mHitState = CharState_None;
mCurrentHit.clear();
stats.setKnockedDown(false);
@ -384,18 +387,6 @@ void CharacterController::refreshHitRecoilAnims()
mCurrentHit = chooseRandomGroup(hitStateToAnimGroup(CharState_Hit));
}
if (!mAnimation->hasAnimation(mCurrentHit))
{
// The hit animation is missing. Reset the current hit state and immediately cancel all states as if the animation were instantaneous.
mHitState = CharState_None;
mCurrentHit.clear();
stats.setKnockedDown(false);
stats.setHitRecovery(false);
stats.setBlock(false);
resetCurrentIdleState();
return;
}
// Cancel upper body animations
if (isKnockedOut() || isKnockedDown())
{
@ -413,6 +404,12 @@ void CharacterController::refreshHitRecoilAnims()
}
}
if (!mAnimation->hasAnimation(mCurrentHit))
{
mCurrentHit.clear();
return;
}
mAnimation->play(mCurrentHit, priority, MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul);
}
@ -681,7 +678,7 @@ void CharacterController::refreshIdleAnims(CharacterState idle, bool force)
// FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
// the idle animation should be displayed
if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped)
|| mMovementState != CharState_None || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr))
|| mMovementState != CharState_None || !mCurrentHit.empty()) && !mPtr.getClass().isBipedal(mPtr))
{
resetCurrentIdleState();
return;
@ -1482,7 +1479,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
}
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
MWRender::Animation::BlendMask_All, false,
1, startKey, stopKey,
0.0f, 0);
mUpperBodyState = UpperCharState_CastingSpell;
@ -1509,7 +1506,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
}
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0, 0);
mUpperBodyState = UpperCharState_FollowStartToFollowStop;
@ -1570,8 +1567,9 @@ bool CharacterController::updateWeaponState(CharacterState idle)
mUpperBodyState = UpperCharState_StartToMinAttack;
if (isRandomAttackAnimation(mCurrentWeapon))
{
world->breakInvisibility(mPtr);
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
playSwishSound(0.0f);
playSwishSound(mAttackStrength);
}
}
}
@ -1674,7 +1672,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
}
}
if(!animPlaying)
if (!animPlaying || complete >= 1.f)
{
if(mUpperBodyState == UpperCharState_EquipingWeap ||
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
@ -1688,12 +1686,21 @@ bool CharacterController::updateWeaponState(CharacterState idle)
if (mUpperBodyState != UpperCharState_EquipingWeap && isRecovery())
mAnimation->disable(mCurrentHit);
if (animPlaying)
mAnimation->disable(mCurrentWeapon);
mUpperBodyState = UpperCharState_WeapEquiped;
}
else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
{
if (animPlaying)
mAnimation->disable(mCurrentWeapon);
mUpperBodyState = UpperCharState_Nothing;
}
}
else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon))
if (complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon))
{
std::string start, stop;
switch(mUpperBodyState)
@ -1772,19 +1779,14 @@ bool CharacterController::updateWeaponState(CharacterState idle)
mask = MWRender::Animation::BlendMask_UpperBody;
mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, priorityWeapon,
mask, true,
weapSpeed, start, stop, 0.0f, 0);
else
mAnimation->play(mCurrentWeapon, priorityWeapon,
mask, false,
weapSpeed, start, stop, 0.0f, 0);
mAnimation->play(mCurrentWeapon, priorityWeapon, mask, false, weapSpeed, start, stop, 0.0f, 0);
}
}
else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon))
{
clearStateAnimation(mCurrentWeapon);
if (isRecovery())
mAnimation->disable(mCurrentHit);
mUpperBodyState = UpperCharState_WeapEquiped;
}

@ -631,7 +631,7 @@ namespace MWMechanics
float playerRating1, playerRating2, playerRating3;
getPersuasionRatings(playerStats, playerRating1, playerRating2, playerRating3, true);
int currentDisposition = getDerivedDisposition(npc);
const int currentDisposition = getDerivedDisposition(npc);
float d = 1 - 0.02f * abs(currentDisposition - 50);
float target1 = d * (playerRating1 - npcRating1 + 50);
@ -644,10 +644,10 @@ namespace MWMechanics
float target3 = d * (playerRating3 - npcRating3 + 50) + bribeMod;
float iPerMinChance = floor(gmst.find("iPerMinChance")->mValue.getFloat());
float iPerMinChange = floor(gmst.find("iPerMinChange")->mValue.getFloat());
float fPerDieRollMult = gmst.find("fPerDieRollMult")->mValue.getFloat();
float fPerTempMult = gmst.find("fPerTempMult")->mValue.getFloat();
const float iPerMinChance = gmst.find("iPerMinChance")->mValue.getFloat();
const float iPerMinChange = gmst.find("iPerMinChange")->mValue.getFloat();
const float fPerDieRollMult = gmst.find("fPerDieRollMult")->mValue.getFloat();
const float fPerTempMult = gmst.find("fPerTempMult")->mValue.getFloat();
float x = 0;
float y = 0;
@ -740,22 +740,23 @@ namespace MWMechanics
x = success ? std::max(iPerMinChange, c) : c;
}
tempChange = type == PT_Intimidate ? int(x) : int(x * fPerTempMult);
int cappedDispositionChange = tempChange;
if (currentDisposition + tempChange > 100)
cappedDispositionChange = 100 - currentDisposition;
if (currentDisposition + tempChange < 0)
if (type == PT_Intimidate)
{
cappedDispositionChange = -currentDisposition;
tempChange = cappedDispositionChange;
tempChange = int(x);
if (currentDisposition + tempChange > 100)
tempChange = 100 - currentDisposition;
else if (currentDisposition + tempChange < 0)
tempChange = -currentDisposition;
permChange = success ? -int(tempChange / fPerTempMult) : int(y);
}
permChange = floor(cappedDispositionChange / fPerTempMult);
if (type == PT_Intimidate)
else
{
permChange = success ? -int(cappedDispositionChange/ fPerTempMult) : int(y);
tempChange = int(x * fPerTempMult);
if (currentDisposition + tempChange > 100)
tempChange = 100 - currentDisposition;
else if (currentDisposition + tempChange < 0)
tempChange = -currentDisposition;
permChange = int(tempChange / fPerTempMult);
}
}

@ -137,11 +137,18 @@ namespace MWMechanics
mStateDuration = 0;
mPrev = position;
mInitialDistance = (destination - position).length();
mDestination = destination;
return;
}
if (mWalkState != WalkState::Evade)
{
if (mDestination != destination)
{
mInitialDistance = (destination - mPrev).length();
mDestination = destination;
}
const float distSameSpot = DIST_SAME_SPOT * actor.getClass().getCurrentSpeed(actor) * duration;
const float prevDistance = (destination - mPrev).length();
const float currentDistance = (destination - position).length();

@ -45,6 +45,7 @@ namespace MWMechanics
private:
osg::Vec3f mPrev;
osg::Vec3f mDestination;
// directions to try moving in when get stuck
static const float evadeDirections[NUM_EVADE_DIRECTIONS][2];

@ -69,13 +69,29 @@ namespace MWMechanics
// If none of the effects need to apply, we can early-out
bool found = false;
bool containsRecastable = false;
std::vector<const ESM::MagicEffect*> magicEffects;
magicEffects.reserve(effects.mList.size());
const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>();
for (const ESM::ENAMstruct& effect : effects.mList)
{
if (effect.mRange == range)
{
found = true;
break;
const ESM::MagicEffect* magicEffect = store.find(effect.mEffectID);
// caster needs to be an actor for linked effects (e.g. Absorb)
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
&& (mCaster.isEmpty() || !mCaster.getClass().isActor()))
{
magicEffects.push_back(nullptr);
continue;
}
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable))
containsRecastable = true;
magicEffects.push_back(magicEffect);
}
else
magicEffects.push_back(nullptr);
}
if (!found)
return;
@ -106,49 +122,39 @@ namespace MWMechanics
if (targetIsActor)
targetSpells = &target.getClass().getCreatureStats(target).getActiveSpells();
bool canCastAnEffect = false; // For bound equipment.If this remains false
// throughout the iteration of this spell's
// effects, we display a "can't re-cast" message
int currentEffectIndex = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
// Re-casting a bound equipment effect has no effect if the spell is still active
if (!containsRecastable && targetSpells && targetSpells->isSpellActive(mId))
{
if (effectIt->mRange != range)
continue;
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectIt->mEffectID);
if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}");
return;
}
// Re-casting a bound equipment effect has no effect if the spell is still active
if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells && targetSpells->isSpellActive(mId))
{
if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}");
for (size_t currentEffectIndex = 0; !target.isEmpty() && currentEffectIndex < effects.mList.size();
++currentEffectIndex)
{
const ESM::ENAMstruct& enam = effects.mList[currentEffectIndex];
if (enam.mRange != range)
continue;
}
canCastAnEffect = true;
// caster needs to be an actor for linked effects (e.g. Absorb)
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
&& (caster.isEmpty() || !caster.getClass().isActor()))
const ESM::MagicEffect* magicEffect = magicEffects[currentEffectIndex];
if (!magicEffect)
continue;
ActiveSpells::ActiveEffect effect;
effect.mEffectId = effectIt->mEffectID;
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
effect.mEffectId = enam.mEffectID;
effect.mArg = MWMechanics::EffectKey(enam).mArg;
effect.mMagnitude = 0.f;
effect.mMinMagnitude = effectIt->mMagnMin;
effect.mMaxMagnitude = effectIt->mMagnMax;
effect.mMinMagnitude = enam.mMagnMin;
effect.mMaxMagnitude = enam.mMagnMax;
effect.mTimeLeft = 0.f;
effect.mEffectIndex = currentEffectIndex;
effect.mEffectIndex = static_cast<int>(currentEffectIndex);
effect.mFlags = ESM::ActiveEffect::Flag_None;
if(mManualSpell)
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
effect.mDuration = hasDuration ? static_cast<float>(enam.mDuration) : 1.f;
bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce;
if (!appliedOnce)
@ -159,8 +165,9 @@ namespace MWMechanics
// add to list of active effects, to apply in next frame
params.getEffects().emplace_back(effect);
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth)
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful
|| enam.mEffectID == ESM::MagicEffect::RestoreHealth;
if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth)
{
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
MWBase::Environment::get().getWindowManager()->setEnemy(target);

@ -308,7 +308,7 @@ namespace
if(target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment && spellParams.getType() != ESM::ActiveSpells::Type_Permanent)
{
bool canReflect = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) &&
!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) && magnitudes.get(ESM::MagicEffect::Reflect).getMagnitude() > 0.f;
!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) && magnitudes.get(ESM::MagicEffect::Reflect).getMagnitude() > 0.f && !caster.isEmpty();
bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f;
if(canReflect || canAbsorb)
{
@ -918,6 +918,13 @@ MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorl
}
else
{
// Morrowind.exe doesn't apply magic effects while the menu is open, we do because we like to see stats
// updated instantly. We don't want to teleport instantly though
if (!dt
&& (effect.mEffectId == ESM::MagicEffect::Recall
|| effect.mEffectId == ESM::MagicEffect::DivineIntervention
|| effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention))
return MagicApplicationResult::APPLIED;
auto& stats = target.getClass().getCreatureStats(target);
auto& magnitudes = stats.getMagicEffects();
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))

@ -176,8 +176,6 @@ void Actor::updateCollisionObjectPosition()
trans.setOrigin(Misc::Convert::toBullet(newPosition));
trans.setRotation(Misc::Convert::toBullet(mRotation));
mCollisionObject->setWorldTransform(trans);
mWorldPositionChanged = false;
}
osg::Vec3f Actor::getCollisionObjectPosition() const
@ -189,14 +187,13 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
bool Actor::setPosition(const osg::Vec3f& position)
{
std::scoped_lock lock(mPositionMutex);
const bool worldPositionChanged = mPositionOffset.length2() != 0;
applyOffsetChange();
bool hasChanged = (mPosition.operator!=(position) && !mSkipSimulation) || mWorldPositionChanged;
if (!mSkipSimulation)
{
mPreviousPosition = mPosition;
mPosition = position;
}
return hasChanged;
if (worldPositionChanged || mSkipSimulation)
return true;
mPreviousPosition = mPosition;
mPosition = position;
return mPreviousPosition != mPosition;
}
void Actor::adjustPosition(const osg::Vec3f& offset)
@ -205,15 +202,16 @@ void Actor::adjustPosition(const osg::Vec3f& offset)
mPositionOffset += offset;
}
void Actor::applyOffsetChange()
osg::Vec3f Actor::applyOffsetChange()
{
if (mPositionOffset.length() == 0)
return;
mPosition += mPositionOffset;
mPreviousPosition += mPositionOffset;
mSimulationPosition += mPositionOffset;
mPositionOffset = osg::Vec3f();
mWorldPositionChanged = true;
if (mPositionOffset.length2() != 0)
{
mPosition += mPositionOffset;
mPreviousPosition += mPositionOffset;
mSimulationPosition += mPositionOffset;
mPositionOffset = osg::Vec3f();
}
return mPosition;
}
void Actor::setRotation(osg::Quat quat)

@ -19,7 +19,7 @@ class btConvexShape;
namespace Resource
{
class BulletShape;
struct BulletShape;
}
namespace MWPhysics
@ -95,7 +95,7 @@ namespace MWPhysics
void adjustPosition(const osg::Vec3f& offset);
// apply position offset. Can't be called during simulation
void applyOffsetChange();
osg::Vec3f applyOffsetChange();
/**
* Returns the half extents of the collision body (scaled according to rendering scale)
@ -189,7 +189,6 @@ namespace MWPhysics
osg::Vec3f mScale;
osg::Vec3f mPositionOffset;
bool mWorldPositionChanged;
bool mSkipSimulation;
mutable std::mutex mPositionMutex;

@ -452,6 +452,9 @@ namespace MWPhysics
if(actor.mSkipCollisionDetection) // noclipping/tcl
return;
if (actor.mMovement.length2() == 0) // no AI nor player attempted to move, current position is assumed correct
return;
auto tempPosition = actor.mPosition;
if(actor.mStuckFrames >= 10)

@ -3,9 +3,11 @@
#include <optional>
#include <shared_mutex>
#include <mutex>
#include <stdexcept>
#include <BulletCollision/BroadphaseCollision/btDbvtBroadphase.h>
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
#include <LinearMath/btThreads.h>
#include <osg/Stats>
@ -36,11 +38,11 @@
namespace
{
template <class Mutex>
std::optional<std::unique_lock<Mutex>> makeExclusiveLock(Mutex& mutex, unsigned threadCount)
std::optional<std::unique_lock<Mutex>> makeExclusiveLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy)
{
if (threadCount > 0)
return std::unique_lock(mutex);
return {};
if (lockingPolicy == MWPhysics::LockingPolicy::NoLocks)
return {};
return std::unique_lock(mutex);
}
/// @brief A scoped lock that is either exclusive or inexistent depending on configuration
@ -48,22 +50,21 @@ namespace
class MaybeExclusiveLock
{
public:
/// @param mutex a mutex
/// @param threadCount decide wether the excluse lock will be taken
explicit MaybeExclusiveLock(Mutex& mutex, unsigned threadCount)
: mImpl(makeExclusiveLock(mutex, threadCount))
{}
explicit MaybeExclusiveLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy)
: mImpl(makeExclusiveLock(mutex, lockingPolicy))
{
}
private:
std::optional<std::unique_lock<Mutex>> mImpl;
};
template <class Mutex>
std::optional<std::shared_lock<Mutex>> makeSharedLock(Mutex& mutex, unsigned threadCount)
std::optional<std::shared_lock<Mutex>> makeSharedLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy)
{
if (threadCount > 0)
return std::shared_lock(mutex);
return {};
if (lockingPolicy == MWPhysics::LockingPolicy::NoLocks)
return {};
return std::shared_lock(mutex);
}
/// @brief A scoped lock that is either shared or inexistent depending on configuration
@ -71,24 +72,31 @@ namespace
class MaybeSharedLock
{
public:
/// @param mutex a shared mutex
/// @param threadCount decide wether the shared lock will be taken
explicit MaybeSharedLock(Mutex& mutex, unsigned threadCount)
: mImpl(makeSharedLock(mutex, threadCount))
{}
explicit MaybeSharedLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy)
: mImpl(makeSharedLock(mutex, lockingPolicy))
{
}
private:
std::optional<std::shared_lock<Mutex>> mImpl;
};
template <class Mutex>
std::variant<std::monostate, std::unique_lock<Mutex>, std::shared_lock<Mutex>> makeLock(Mutex& mutex, unsigned threadCount)
std::variant<std::monostate, std::unique_lock<Mutex>, std::shared_lock<Mutex>> makeLock(
Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy)
{
if (threadCount > 1)
return std::shared_lock(mutex);
if (threadCount == 1)
return std::unique_lock(mutex);
return std::monostate {};
switch (lockingPolicy)
{
case MWPhysics::LockingPolicy::NoLocks:
return std::monostate{};
case MWPhysics::LockingPolicy::ExclusiveLocksOnly:
return std::unique_lock(mutex);
case MWPhysics::LockingPolicy::AllowSharedLocks:
return std::shared_lock(mutex);
};
throw std::runtime_error("Unsupported LockingPolicy: "
+ std::to_string(static_cast<std::underlying_type_t<MWPhysics::LockingPolicy>>(lockingPolicy)));
}
/// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration
@ -96,10 +104,10 @@ namespace
class MaybeLock
{
public:
/// @param mutex a shared mutex
/// @param threadCount decide wether the lock will be shared, exclusive or inexistent
explicit MaybeLock(Mutex& mutex, unsigned threadCount)
: mImpl(makeLock(mutex, threadCount)) {}
explicit MaybeLock(Mutex& mutex, MWPhysics::LockingPolicy lockingPolicy)
: mImpl(makeLock(mutex, lockingPolicy))
{
}
private:
std::variant<std::monostate, std::unique_lock<Mutex>, std::shared_lock<Mutex>> mImpl;
@ -132,7 +140,7 @@ namespace
{
const Impl& mImpl;
std::shared_mutex& mCollisionWorldMutex;
const unsigned mNumThreads;
const MWPhysics::LockingPolicy mLockingPolicy;
template <class Ptr, class FrameData>
void operator()(MWPhysics::SimulationImpl<Ptr, FrameData>& sim) const
@ -144,7 +152,7 @@ namespace
// Locked shared_ptr has to be destructed after releasing mCollisionWorldMutex to avoid
// possible deadlock. Ptr destructor also acquires mCollisionWorldMutex.
const std::pair arg(std::move(ptr), frameData);
const Lock<std::shared_mutex> lock(mCollisionWorldMutex, mNumThreads);
const Lock<std::shared_mutex> lock(mCollisionWorldMutex, mLockingPolicy);
mImpl(arg);
}
};
@ -159,15 +167,14 @@ namespace
return;
auto& [actor, frameDataRef] = *locked;
auto& frameData = frameDataRef.get();
actor->applyOffsetChange();
frameData.mPosition = actor->getPosition();
frameData.mPosition = actor->applyOffsetChange();
if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld))
{
const auto offset = osg::Vec3f(0, 0, frameData.mWaterlevel - frameData.mPosition.z());
MWBase::Environment::get().getWorld()->moveObjectBy(actor->getPtr(), offset);
actor->applyOffsetChange();
frameData.mPosition = actor->getPosition();
MWBase::Environment::get().getWorld()->moveObjectBy(actor->getPtr(), offset, false);
frameData.mPosition = actor->applyOffsetChange();
}
actor->updateCollisionObjectPosition();
frameData.mOldHeight = frameData.mPosition.z();
const auto rotation = actor->getPtr().getRefData().getPosition().asRotationVec3();
frameData.mRotation = osg::Vec2f(rotation.x(), rotation.z());
@ -284,42 +291,115 @@ namespace
}
};
}
}
namespace Config
namespace MWPhysics
{
namespace
{
/// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading and user requested more than 1 background threads
unsigned computeNumThreads()
unsigned getMaxBulletSupportedThreads()
{
int wantedThread = Settings::Manager::getInt("async num threads", "Physics");
auto broad = std::make_unique<btDbvtBroadphase>();
auto maxSupportedThreads = broad->m_rayTestStacks.size();
auto threadSafeBullet = (maxSupportedThreads > 1);
if (!threadSafeBullet && wantedThread > 1)
assert(BT_MAX_THREAD_COUNT > 0);
return std::min<unsigned>(broad->m_rayTestStacks.size(), BT_MAX_THREAD_COUNT - 1);
}
LockingPolicy detectLockingPolicy()
{
if (Settings::Manager::getInt("async num threads", "Physics") < 1)
return LockingPolicy::NoLocks;
if (getMaxBulletSupportedThreads() > 1)
return LockingPolicy::AllowSharedLocks;
Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used";
return LockingPolicy::ExclusiveLocksOnly;
}
unsigned getNumThreads(LockingPolicy lockingPolicy)
{
switch (lockingPolicy)
{
Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used";
return 1;
case LockingPolicy::NoLocks:
return 0;
case LockingPolicy::ExclusiveLocksOnly:
return 1;
case LockingPolicy::AllowSharedLocks:
return std::clamp<unsigned>(
Settings::Manager::getInt("async num threads", "Physics"), 0, getMaxBulletSupportedThreads());
}
return static_cast<unsigned>(std::max(0, wantedThread));
throw std::runtime_error("Unsupported LockingPolicy: "
+ std::to_string(static_cast<std::underlying_type_t<LockingPolicy>>(lockingPolicy)));
}
}
}
namespace MWPhysics
{
class PhysicsTaskScheduler::WorkersSync
{
public:
void waitForWorkers()
{
std::unique_lock lock(mWorkersDoneMutex);
if (mFrameCounter != mWorkersFrameCounter)
mWorkersDone.wait(lock);
}
void wakeUpWorkers()
{
const std::lock_guard lock(mHasJobMutex);
++mFrameCounter;
mHasJob.notify_all();
}
void stopWorkers()
{
const std::lock_guard lock(mHasJobMutex);
mShouldStop = true;
mHasJob.notify_all();
}
void workIsDone()
{
const std::lock_guard lock(mWorkersDoneMutex);
++mWorkersFrameCounter;
mWorkersDone.notify_all();
}
template <class F>
void runWorker(F&& f) noexcept
{
std::size_t lastFrame = 0;
std::unique_lock lock(mHasJobMutex);
while (!mShouldStop)
{
mHasJob.wait(lock, [&] { return mShouldStop || mFrameCounter != lastFrame; });
lastFrame = mFrameCounter;
lock.unlock();
f();
lock.lock();
}
}
private:
std::size_t mWorkersFrameCounter = 0;
std::condition_variable mWorkersDone;
std::mutex mWorkersDoneMutex;
std::condition_variable mHasJob;
bool mShouldStop = false;
std::size_t mFrameCounter = 0;
std::mutex mHasJobMutex;
};
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer)
: mDefaultPhysicsDt(physicsDt)
, mPhysicsDt(physicsDt)
, mTimeAccum(0.f)
, mCollisionWorld(collisionWorld)
, mDebugDrawer(debugDrawer)
, mNumThreads(Config::computeNumThreads())
, mLockingPolicy(detectLockingPolicy())
, mNumThreads(getNumThreads(mLockingPolicy))
, mNumJobs(0)
, mRemainingSteps(0)
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
, mFrameCounter(0)
, mAdvanceSimulation(false)
, mQuit(false)
, mNextJob(0)
, mNextLOS(0)
, mFrameNumber(0)
@ -332,9 +412,11 @@ namespace MWPhysics
, mTimeBegin(0)
, mTimeEnd(0)
, mFrameStart(0)
, mWorkersSync(mNumThreads >= 1 ? std::make_unique<WorkersSync>() : nullptr)
{
if (mNumThreads >= 1)
{
Log(Debug::Info) << "Using " << mNumThreads << " async physics threads";
for (unsigned i = 0; i < mNumThreads; ++i)
mThreads.emplace_back([&] { worker(); } );
}
@ -354,12 +436,12 @@ namespace MWPhysics
{
waitForWorkers();
{
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
mQuit = true;
MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy);
mNumJobs = 0;
mRemainingSteps = 0;
mHasJob.notify_all();
}
if (mWorkersSync != nullptr)
mWorkersSync->stopWorkers();
for (auto& thread : mThreads)
thread.join();
}
@ -413,11 +495,18 @@ namespace MWPhysics
void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector<Simulation>&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
waitForWorkers();
prepareWork(timeAccum, std::move(simulations), frameStart, frameNumber, stats);
if (mWorkersSync != nullptr)
mWorkersSync->wakeUpWorkers();
}
void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector<Simulation>&& simulations,
osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
// This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run.
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy);
double timeStart = mTimer->tick();
@ -446,7 +535,6 @@ namespace MWPhysics
mPhysicsDt = newDelta;
mSimulations = std::move(simulations);
mAdvanceSimulation = (mRemainingSteps != 0);
++mFrameCounter;
mNumJobs = mSimulations.size();
mNextLOS.store(0, std::memory_order_relaxed);
mNextJob.store(0, std::memory_order_release);
@ -467,7 +555,6 @@ namespace MWPhysics
}
mAsyncStartTime = mTimer->tick();
mHasJob.notify_all();
if (mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
}
@ -475,7 +562,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{
waitForWorkers();
MaybeExclusiveLock lock(mSimulationMutex, mNumThreads);
MaybeExclusiveLock lock(mSimulationMutex, mLockingPolicy);
mBudget.reset(mDefaultPhysicsDt);
mAsyncBudget.reset(0.0f);
mSimulations.clear();
@ -488,25 +575,25 @@ namespace MWPhysics
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
{
MaybeLock lock(mCollisionWorldMutex, mNumThreads);
MaybeLock lock(mCollisionWorldMutex, mLockingPolicy);
mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback);
}
void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const
{
MaybeLock lock(mCollisionWorldMutex, mNumThreads);
MaybeLock lock(mCollisionWorldMutex, mLockingPolicy);
mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback);
}
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{
MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads);
MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy);
ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback);
}
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
{
MaybeLock lock(mCollisionWorldMutex, mNumThreads);
MaybeLock lock(mCollisionWorldMutex, mLockingPolicy);
// target the collision object's world origin, this should be the center of the collision object
btTransform rayTo;
rayTo.setIdentity();
@ -523,33 +610,33 @@ namespace MWPhysics
void PhysicsTaskScheduler::aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback)
{
MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads);
MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy);
mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback);
}
void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max)
{
MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads);
MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy);
obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max);
}
void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask)
{
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy);
collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask;
}
void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask)
{
mCollisionObjects.insert(collisionObject);
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy);
mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask);
}
void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject)
{
mCollisionObjects.erase(collisionObject);
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy);
mCollisionWorld->removeCollisionObject(collisionObject);
}
@ -561,14 +648,14 @@ namespace MWPhysics
}
else
{
MaybeExclusiveLock lock(mUpdateAabbMutex, mNumThreads);
MaybeExclusiveLock lock(mUpdateAabbMutex, mLockingPolicy);
mUpdateAabb.insert(ptr);
}
}
bool PhysicsTaskScheduler::getLineOfSight(const std::shared_ptr<Actor>& actor1, const std::shared_ptr<Actor>& actor2)
{
MaybeExclusiveLock lock(mLOSCacheMutex, mNumThreads);
MaybeExclusiveLock lock(mLOSCacheMutex, mLockingPolicy);
auto req = LOSRequest(actor1, actor2);
auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req);
@ -584,7 +671,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::refreshLOSCache()
{
MaybeSharedLock lock(mLOSCacheMutex, mNumThreads);
MaybeSharedLock lock(mLOSCacheMutex, mLockingPolicy);
int job = 0;
int numLOS = mLOSCache.size();
while ((job = mNextLOS.fetch_add(1, std::memory_order_relaxed)) < numLOS)
@ -603,7 +690,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::updateAabbs()
{
MaybeExclusiveLock lock(mUpdateAabbMutex, mNumThreads);
MaybeExclusiveLock lock(mUpdateAabbMutex, mLockingPolicy);
std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(),
[this](const std::weak_ptr<PtrHolder>& ptr)
{
@ -616,7 +703,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::updatePtrAabb(const std::shared_ptr<PtrHolder>& ptr)
{
MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads);
MaybeExclusiveLock lock(mCollisionWorldMutex, mLockingPolicy);
if (const auto actor = std::dynamic_pointer_cast<Actor>(ptr))
{
actor->updateCollisionObjectPosition();
@ -636,24 +723,16 @@ namespace MWPhysics
void PhysicsTaskScheduler::worker()
{
std::size_t lastFrame = 0;
std::shared_lock lock(mSimulationMutex);
while (!mQuit)
{
if (lastFrame == mFrameCounter)
{
mHasJob.wait(lock, [&] { return mQuit || lastFrame != mFrameCounter; });
lastFrame = mFrameCounter;
}
mWorkersSync->runWorker([this] {
std::shared_lock lock(mSimulationMutex);
doSimulation();
}
});
}
void PhysicsTaskScheduler::updateActorsPositions()
{
const Visitors::UpdatePosition impl{mCollisionWorld};
const Visitors::WithLockedPtr<Visitors::UpdatePosition, MaybeExclusiveLock> vis{impl, mCollisionWorldMutex, mNumThreads};
const Visitors::WithLockedPtr<Visitors::UpdatePosition, MaybeExclusiveLock> vis{impl, mCollisionWorldMutex, mLockingPolicy};
for (Simulation& sim : mSimulations)
std::visit(vis, sim);
}
@ -667,7 +746,7 @@ namespace MWPhysics
resultCallback.m_collisionFilterGroup = CollisionType_AnyPhysical;
resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door;
MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads);
MaybeLock lockColWorld(mCollisionWorldMutex, mLockingPolicy);
mCollisionWorld->rayTest(pos1, pos2, resultCallback);
return !resultCallback.hasHit();
@ -680,7 +759,7 @@ namespace MWPhysics
mPreStepBarrier->wait([this] { afterPreStep(); });
int job = 0;
const Visitors::Move impl{mPhysicsDt, mCollisionWorld, *mWorldFrameData};
const Visitors::WithLockedPtr<Visitors::Move, MaybeLock> vis{impl, mCollisionWorldMutex, mNumThreads};
const Visitors::WithLockedPtr<Visitors::Move, MaybeLock> vis{impl, mCollisionWorldMutex, mLockingPolicy};
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
std::visit(vis, mSimulations[job]);
@ -708,7 +787,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::debugDraw()
{
MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads);
MaybeSharedLock lock(mCollisionWorldMutex, mLockingPolicy);
mDebugDrawer->step();
}
@ -734,7 +813,7 @@ namespace MWPhysics
if (!mRemainingSteps)
return;
const Visitors::PreStep impl{mCollisionWorld};
const Visitors::WithLockedPtr<Visitors::PreStep, MaybeExclusiveLock> vis{impl, mCollisionWorldMutex, mNumThreads};
const Visitors::WithLockedPtr<Visitors::PreStep, MaybeExclusiveLock> vis{impl, mCollisionWorldMutex, mLockingPolicy};
for (auto& sim : mSimulations)
std::visit(vis, sim);
}
@ -752,17 +831,15 @@ namespace MWPhysics
void PhysicsTaskScheduler::afterPostSim()
{
{
MaybeExclusiveLock lock(mLOSCacheMutex, mNumThreads);
MaybeExclusiveLock lock(mLOSCacheMutex, mLockingPolicy);
mLOSCache.erase(
std::remove_if(mLOSCache.begin(), mLOSCache.end(),
[](const LOSRequest& req) { return req.mStale; }),
mLOSCache.end());
}
mTimeEnd = mTimer->tick();
std::unique_lock lock(mWorkersDoneMutex);
++mWorkersFrameCounter;
mWorkersDone.notify_all();
if (mWorkersSync != nullptr)
mWorkersSync->workIsDone();
}
void PhysicsTaskScheduler::syncWithMainThread()
@ -780,10 +857,7 @@ namespace MWPhysics
// https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks
void PhysicsTaskScheduler::waitForWorkers()
{
if (mNumThreads == 0)
return;
std::unique_lock lock(mWorkersDoneMutex);
if (mFrameCounter != mWorkersFrameCounter)
mWorkersDone.wait(lock);
if (mWorkersSync != nullptr)
mWorkersSync->waitForWorkers();
}
}

@ -29,6 +29,13 @@ namespace MWRender
namespace MWPhysics
{
enum class LockingPolicy
{
NoLocks,
ExclusiveLocksOnly,
AllowSharedLocks,
};
class PhysicsTaskScheduler
{
public:
@ -61,6 +68,8 @@ namespace MWPhysics
void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler()
private:
class WorkersSync;
void doSimulation();
void worker();
void updateActorsPositions();
@ -75,6 +84,8 @@ namespace MWPhysics
void afterPostSim();
void syncWithMainThread();
void waitForWorkers();
void prepareWork(float& timeAccum, std::vector<Simulation>&& simulations, osg::Timer_t frameStart,
unsigned int frameNumber, osg::Stats& stats);
std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<Simulation> mSimulations;
@ -92,26 +103,20 @@ namespace MWPhysics
std::unique_ptr<Misc::Barrier> mPostStepBarrier;
std::unique_ptr<Misc::Barrier> mPostSimBarrier;
LockingPolicy mLockingPolicy;
unsigned mNumThreads;
int mNumJobs;
int mRemainingSteps;
int mLOSCacheExpiry;
std::size_t mFrameCounter;
bool mAdvanceSimulation;
bool mQuit;
std::atomic<int> mNextJob;
std::atomic<int> mNextLOS;
std::vector<std::thread> mThreads;
std::size_t mWorkersFrameCounter = 0;
std::condition_variable mWorkersDone;
std::mutex mWorkersDoneMutex;
mutable std::shared_mutex mSimulationMutex;
mutable std::shared_mutex mCollisionWorldMutex;
mutable std::shared_mutex mLOSCacheMutex;
mutable std::mutex mUpdateAabbMutex;
std::condition_variable_any mHasJob;
unsigned int mFrameNumber;
const osg::Timer* mTimer;
@ -124,6 +129,8 @@ namespace MWPhysics
osg::Timer_t mTimeBegin;
osg::Timer_t mTimeEnd;
osg::Timer_t mFrameStart;
std::unique_ptr<WorkersSync> mWorkersSync;
};
}

@ -489,14 +489,16 @@ namespace MWPhysics
assert(!getObject(ptr));
// Override collision type based on shape content.
switch (shapeInstance->mCollisionType)
switch (shapeInstance->mVisualCollisionType)
{
case Resource::BulletShape::CollisionType::Camera:
collisionType = CollisionType_CameraOnly;
case Resource::VisualCollisionType::None:
break;
case Resource::BulletShape::CollisionType::None:
case Resource::VisualCollisionType::Default:
collisionType = CollisionType_VisualOnly;
break;
case Resource::VisualCollisionType::Camera:
collisionType = CollisionType_CameraOnly;
break;
}
auto obj = std::make_shared<Object>(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get());

@ -20,7 +20,7 @@ namespace osg
namespace Resource
{
class BulletShape;
struct BulletShape;
}
namespace MWPhysics

@ -58,7 +58,10 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group>
removeEffects();
}
ActorAnimation::~ActorAnimation() = default;
ActorAnimation::~ActorAnimation()
{
removeFromSceneImpl();
}
PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor)
{
@ -572,10 +575,15 @@ void ActorAnimation::removeHiddenItemLight(const MWWorld::ConstPtr& item)
}
void ActorAnimation::removeFromScene()
{
removeFromSceneImpl();
Animation::removeFromScene();
}
void ActorAnimation::removeFromSceneImpl()
{
for (const auto& [k, v] : mItemLights)
mInsert->removeChild(v);
Animation::removeFromScene();
}
}

@ -65,6 +65,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight);
void removeHiddenItemLight(const MWWorld::ConstPtr& item);
void resetControllers(osg::Node* node);
void removeFromSceneImpl();
typedef std::map<MWWorld::ConstPtr, osg::ref_ptr<SceneUtil::LightSource> > ItemLightMap;
ItemLightMap mItemLights;

@ -543,7 +543,10 @@ namespace MWRender
mLightListCallback = new SceneUtil::LightListCallback;
}
Animation::~Animation() = default;
Animation::~Animation()
{
removeFromSceneImpl();
}
void Animation::setActive(int active)
{
@ -1783,6 +1786,11 @@ namespace MWRender
}
void Animation::removeFromScene()
{
removeFromSceneImpl();
}
void Animation::removeFromSceneImpl()
{
if (mGlowLight != nullptr)
mInsert->removeChild(mGlowLight);

@ -344,6 +344,8 @@ protected:
*/
virtual void addControllers();
void removeFromSceneImpl();
public:
Animation(const MWWorld::Ptr &ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem);

@ -210,7 +210,10 @@ namespace MWRender
void Camera::setMode(Mode newMode, bool force)
{
if (mMode == newMode)
{
mQueuedMode = std::nullopt;
return;
}
Mode oldMode = mMode;
if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && mAnimation && !mAnimation->upperBodyReady())
{

@ -177,28 +177,28 @@ void LocalMap::setupRenderToTexture(int segment_x, int segment_y, float left, fl
void LocalMap::requestMap(const MWWorld::CellStore* cell)
{
if (cell->isExterior())
if (!cell->isExterior())
{
int cellX = cell->getCell()->getGridX();
int cellY = cell->getCell()->getGridY();
MapSegment& segment = mExteriorSegments[std::make_pair(cellX, cellY)];
if (!segment.needUpdate)
return;
else
{
requestExteriorMap(cell);
segment.needUpdate = false;
}
}
else
requestInteriorMap(cell);
return;
}
int cellX = cell->getCell()->getGridX();
int cellY = cell->getCell()->getGridY();
MapSegment& segment = mExteriorSegments[std::make_pair(cellX, cellY)];
const std::uint8_t neighbourFlags = getExteriorNeighbourFlags(cellX, cellY);
if ((segment.mLastRenderNeighbourFlags & neighbourFlags) == neighbourFlags)
return;
requestExteriorMap(cell, segment);
segment.mLastRenderNeighbourFlags = neighbourFlags;
}
void LocalMap::addCell(MWWorld::CellStore *cell)
{
if (cell->isExterior())
mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())].needUpdate = true;
mExteriorSegments.emplace(
std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY()), MapSegment{});
}
void LocalMap::removeExteriorCell(int x, int y)
@ -210,7 +210,9 @@ void LocalMap::removeCell(MWWorld::CellStore *cell)
{
saveFogOfWar(cell);
if (!cell->isExterior())
if (cell->isExterior())
mExteriorSegments.erase({ cell->getCell()->getGridX(), cell->getCell()->getGridY() });
else
mInteriorSegments.clear();
}
@ -249,7 +251,7 @@ void LocalMap::cleanupCameras()
}
}
void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell)
void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell, MapSegment& segment)
{
mInterior = false;
@ -260,18 +262,16 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell)
float zmin = bound.center().z() - bound.radius();
float zmax = bound.center().z() + bound.radius();
setupRenderToTexture(cell->getCell()->getGridX(), cell->getCell()->getGridY(),
x * mMapWorldSize + mMapWorldSize / 2.f, y * mMapWorldSize + mMapWorldSize / 2.f,
setupRenderToTexture(x, y, x * mMapWorldSize + mMapWorldSize / 2.f, y * mMapWorldSize + mMapWorldSize / 2.f,
osg::Vec3d(0, 1, 0), zmin, zmax);
MapSegment& segment = mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())];
if (!segment.mFogOfWarImage)
{
if (cell->getFog())
segment.loadFogOfWar(cell->getFog()->mFogTextures.back());
else
segment.initFogOfWar();
}
if (segment.mFogOfWarImage != nullptr)
return;
if (cell->getFog())
segment.loadFogOfWar(cell->getFog()->mFogTextures.back());
else
segment.initFogOfWar();
}
void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell)
@ -561,9 +561,23 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient
}
}
LocalMap::MapSegment::MapSegment()
: mHasFogState(false)
std::uint8_t LocalMap::getExteriorNeighbourFlags(int cellX, int cellY) const
{
constexpr std::tuple<NeighbourCellFlag, int, int> flags[] = {
{ NeighbourCellTopLeft, -1, -1 },
{ NeighbourCellTopCenter, 0, -1 },
{ NeighbourCellTopRight, 1, -1 },
{ NeighbourCellMiddleLeft, -1, 0 },
{ NeighbourCellMiddleRight, 1, 0 },
{ NeighbourCellBottomLeft, -1, 1 },
{ NeighbourCellBottomCenter, 0, 1 },
{ NeighbourCellBottomRight, 1, 1 },
};
std::uint8_t result = 0;
for (const auto& [flag, dx, dy] : flags)
if (mExteriorSegments.contains({cellX + dx, cellY + dy}))
result |= flag;
return result;
}
void LocalMap::MapSegment::createFogOfWarTexture()

@ -1,6 +1,7 @@
#ifndef GAME_RENDER_LOCALMAP_H
#define GAME_RENDER_LOCALMAP_H
#include <cstdint>
#include <set>
#include <vector>
#include <map>
@ -105,23 +106,30 @@ namespace MWRender
typedef std::set<std::pair<int, int> > Grid;
Grid mCurrentGrid;
struct MapSegment
enum NeighbourCellFlag : std::uint8_t
{
MapSegment();
~MapSegment() = default;
NeighbourCellTopLeft = 1,
NeighbourCellTopCenter = 1 << 1,
NeighbourCellTopRight = 1 << 2,
NeighbourCellMiddleLeft = 1 << 3,
NeighbourCellMiddleRight = 1 << 4,
NeighbourCellBottomLeft = 1 << 5,
NeighbourCellBottomCenter = 1 << 6,
NeighbourCellBottomRight = 1 << 7,
};
struct MapSegment
{
void initFogOfWar();
void loadFogOfWar(const ESM::FogTexture& fog);
void saveFogOfWar(ESM::FogTexture& fog) const;
void createFogOfWarTexture();
std::uint8_t mLastRenderNeighbourFlags = 0;
bool mHasFogState = false;
osg::ref_ptr<osg::Texture2D> mMapTexture;
osg::ref_ptr<osg::Texture2D> mFogOfWarTexture;
osg::ref_ptr<osg::Image> mFogOfWarImage;
bool needUpdate = true;
bool mHasFogState;
};
typedef std::map<std::pair<int, int>, MapSegment> SegmentMap;
@ -141,13 +149,15 @@ namespace MWRender
float mAngle;
const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle);
void requestExteriorMap(const MWWorld::CellStore* cell);
void requestExteriorMap(const MWWorld::CellStore* cell, MapSegment& segment);
void requestInteriorMap(const MWWorld::CellStore* cell);
void setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax);
bool mInterior;
osg::BoundingBox mBounds;
std::uint8_t getExteriorNeighbourFlags(int cellX, int cellY) const;
};
}

@ -47,7 +47,7 @@ namespace MWRender
auto& sm = Stereo::Manager::instance();
auto view = sm.getEye(cv);
int index = view == Stereo::Eye::Right ? 1 : 0;
auto projectionMatrix = sm.computeEyeViewOffset(index) * sm.computeEyeProjection(index, true);
auto projectionMatrix = sm.computeEyeProjection(index, true);
postProcessor->getStateUpdater()->setProjectionMatrix(projectionMatrix);
}

@ -1,6 +1,8 @@
#include "postprocessor.hpp"
#include <algorithm>
#include <chrono>
#include <thread>
#include <SDL_opengl_glext.h>
#include <osg/Texture1D>
@ -9,6 +11,8 @@
#include <osg/Texture3D>
#include <osg/Texture2DArray>
#include <boost/filesystem/operations.hpp>
#include <components/settings/settings.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/color.hpp>
@ -102,6 +106,7 @@ namespace MWRender
{
PostProcessor::PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs)
: osg::Group()
, mEnableLiveReload(false)
, mRootNode(rootNode)
, mSamples(Settings::Manager::getInt("antialiasing", "Video"))
, mDirty(false)
@ -109,6 +114,7 @@ namespace MWRender
, mRendering(rendering)
, mViewer(viewer)
, mVFS(vfs)
, mTriggerShaderReload(false)
, mReload(false)
, mEnabled(false)
, mUsePostProcessing(false)
@ -212,11 +218,11 @@ namespace MWRender
{
for (const auto& name : mVFS->getRecursiveDirectoryIterator(fx::Technique::sSubdir))
{
std::filesystem::path path = name;
boost::filesystem::path path = name;
std::string fileExt = Misc::StringUtils::lowerCase(path.extension().string());
if (!path.parent_path().has_parent_path() && fileExt == fx::Technique::sExt)
{
auto absolutePath = std::filesystem::path(mVFS->getAbsoluteFileName(name));
auto absolutePath = boost::filesystem::path(mVFS->getAbsoluteFileName(name));
mTechniqueFileMap[absolutePath.stem().string()] = absolutePath;
}
}
@ -361,21 +367,26 @@ namespace MWRender
void PostProcessor::updateLiveReload()
{
static const bool liveReload = Settings::Manager::getBool("live reload", "Post Processing");
if (!liveReload)
if (!mEnableLiveReload && !mTriggerShaderReload)
return;
mTriggerShaderReload = false;//Done only once
for (auto& technique : mTechniques)
{
if (technique->getStatus() == fx::Technique::Status::File_Not_exists)
continue;
const auto lastWriteTime = std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]);
const auto lastWriteTime = boost::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]);
const bool isDirty = technique->setLastModificationTime(lastWriteTime);
if (!isDirty)
continue;
// TODO: Temporary workaround to avoid conflicts with external programs saving the file, especially problematic on Windows.
// If we move to a file watcher using native APIs this should be removed.
std::this_thread::sleep_for(std::chrono::milliseconds(5));
if (technique->compile())
Log(Debug::Info) << "Reloaded technique : " << mTechniqueFileMap[technique->getName()].string();
@ -779,7 +790,7 @@ namespace MWRender
technique->compile();
if (technique->getStatus() != fx::Technique::Status::File_Not_exists)
technique->setLastModificationTime(std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]));
technique->setLastModificationTime(boost::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]));
if (loadNextFrame)
{
@ -853,5 +864,10 @@ namespace MWRender
return Stereo::Manager::instance().eyeResolution().y();
return mHeight;
}
void PostProcessor::triggerShaderReload()
{
mTriggerShaderReload = true;
}
}

@ -6,8 +6,6 @@
#include <string>
#include <unordered_map>
#include <filesystem>
#include <osg/Texture2D>
#include <osg/Group>
#include <osg/FrameBufferObject>
@ -180,9 +178,14 @@ namespace MWRender
int renderWidth() const;
int renderHeight() const;
void triggerShaderReload();
bool mEnableLiveReload;
void loadChain();
void saveChain();
private:
void populateTechniqueFiles();
@ -215,7 +218,7 @@ namespace MWRender
TechniqueList mTemplates;
TechniqueList mQueuedTemplates;
std::unordered_map<std::string, std::filesystem::path> mTechniqueFileMap;
std::unordered_map<std::string, boost::filesystem::path> mTechniqueFileMap;
int mSamples;
@ -226,6 +229,7 @@ namespace MWRender
osgViewer::Viewer* mViewer;
const VFS::Manager* mVFS;
bool mTriggerShaderReload;
bool mReload;
bool mEnabled;
bool mUsePostProcessing;

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

Loading…
Cancel
Save