Allow Lua to mutate the ESM store on load

An implementation of !3467 (comment 2623426917) except without the onContentFile handler proposed there because it would be a real pain to implement.

Related to #3214, #7207, #7816, #8716, #8791 (closed). Closes #8987 (closed).

Opening this early to get some feedback before I actually bother to write any documentation. Documentation: https://assumeru-openmw.readthedocs.io/en/loadlua/reference/lua-scripting/openmw_content.html

I wanted to dehardcode the default globals alongside the default statics, but given that those are still used by various hardcoded mechanics, doing so didn't seem super worthwhile yet. It should be done after all the relevant code has migrated to data-mw. Statics are nice and simple too, though.

This MR adds a new, severely limited Lua context (LOAD) which runs an engine handler onContentFilesLoaded after the engine has finished loading every content file, but before ESMStore::setUp runs. This context features a new package (openmw.content) that is roughly analogous to openmw.world. It should be thought of as a binding to the ESM store that allows its contents to be mutated prior to any saves being loaded. It should also be noted that allowing modification at this juncture is significantly less scary than doing the same thing during game play because nothing has pointers/references/string_views to this data yet. (Not that we shouldn't have the ability to do some of this at runtime, but still.)

In the context of our built in scripts, this new handler allows us to move currently hardcoded Morrowind things into data-mw Lua scripts. Specifically the generateDefaultX functions in worldimp.cpp and the Store::setUp methods in store.cpp for Skills, Attributes, and Magic Effects. Down the line it could also be used to dehardcode bodypart slots and things like armor/clothing/weapon types.

contentbindings.cpp currently contains bindings for global variables and statics. The former are somewhat unique in that they're really just id+number pairs yielding a highly specialized binding. Statics, on the other hand, are a normal, if tiny record type. Such types should have their backing store bound using the templated addRecordStoreBindings function, with the second argument being the createRecordDraft function we already have for certain types (currently they're anonymous functions, but that's easily rectified.) The ESM record types themselves are bound using MutableStore because (unlike in other contexts) users can delete/mutate their data so we can't just bind references. To a user, the bound record type should be essentially identical to the types they'd normally get from openmw.world except mutable (with the exception of ID.) Keeping these mutable bindings and the existing immutable bindings close together (in code and behaviour) seems desirable, though I've opted not to put the ESM::Static bindings in types/static.cpp for now.

Note that this should be the only Lua context in which we get to create new StringRefIds. The validateId function exists to do this correctly (by not letting people create IDs starting with Generated: and the like.) In theory we could also allow FormIds in this context, but it seems like bit of a hassle to do correctly.

I've made (read only) player storage (openmw.storage) accessible in the LOAD context. This matches the MENU context (where both player and global storage are available, but the latter only works after getting into the game.) The purpose of storage at this stage is to allow modders to implement toggles in the Scripts menu that affect how content files are loaded (after restarting the game) very specifically: this would (after further additions) allow the Tamriel Data MWSE option to turn (armor) hats into clothing to be implemented in OpenMW.

Edited by Alexei Kotov

Merge request reports

Loading