H3lp Yours3lf
Version: 0.5
Sometimes, you just have to h3lp yours3lf.
H3lp Yours3lf is a collection of scripting modules built for openmw. It contains multiple individual interfaces which authors can use to improve performance and ergonomics in their OpenMW-Lua scripts. Additionally, H3lp Yours3lf includes some helper functions for more exotic behaviors, such as detecting the context in which a given script is running.
- Download the mod from Here
- Extract the zip to a location of your choosing.
- Paste the location you extracted it to into the form below, hit
Generate Config, and paste the result into openmw.cfg.
Where would you like to install this mod?
# H3lp Yours3lf Data Directories
data=C:/Games/OpenMW-Mods/H3lp Yours3lf/.
# H3lp Yours3lf Content Files
content=H3lp Yours3lf.esp
Modules
S3lf
s3lf is a replacement for OpenMW's built-in self module. It contains all the same contents but saves some footguns in the API and makes certain calls more precise on your behalf, alongside being easier to introspect. This mod should be installed purely as a dependency of others, as it adds nothing on its own except an interface which other scripts may make use of. For scripters, read below to learn about how and why to make use of the s3lf interface.
Using the S3lf Module
It's a fairly common convention in Lua to use the keyword self in a table when it... needs to reference itself in some way. OpenMW-Lua subtly teaches you to use its own module openmw.self instead, which can break attempts to use the self keyword normally, induce subtle bugs, or just be plain weird. Additionally, the API overall is often considered too spread out or confusing to be easily used, with things like health being accessed like self.type.stats.dynamic.health(self). The s3lf module will save you these painful indexes with hidden implementation footguns, and allow you to use the self keyword as you normally would. Compare the normal version to s3lf.health.current.
Any api function which takes self.object as its first argument now implicitly passes the SelfObject as the first argument. If the SelfObject is the only argument, then you do not even need to bother with the function call. The userdata values returned by the API are cached where possible and all flattened into the s3lf object. To best get a grip on it, just try using it in the Lua console!
Some specific notes on how fields are changed:
self.typeis no longer accessible, as all fields inside of the.typefield are flattened intos3lf- Attributes, skills, level, and dynamic stats are all directly accessible via
s3lf - All fields of
self.type.stats.aiare available unders3lf - All fields from the original
GameObjecttype are available unders3lf - All fields of the associated record are available under
s3lf. Due to a name collision, the.idfield ofs3lfwill always refer toGameObject.idand not the record's id. uses3lf.recordIdto find the object's record name instead of the instance id. - All fields of the animation module are available under
s3lf - A new
.recordfield is added to replace the.recordfunction, which returnsself.type.records[self.recordId] - An additional function,
ConsoleLogis added which will display a given message in the~console from any context.
s3lf is exported as an interface and immediately usable: local s3lf = require('openmw.interfaces').s3lf
To use it in the lua console, make sure the script is installed and enabled, then use luap or luas and try the following:
s3lf = I.s3lf
s3lf.record.hair
s3lf.health.base, s3lf.strength.base, s3lf.acrobatics.base, s3lf.speed.modified
s3lf objects may also construct other s3lf objects from normal GameObjects to gain the same benefits:
local weapon = s3lf.getEquipment(s3lf.EQUIPMENT_SLOT.CarriedRight)
local weaponType = s3lf.From(weapon).record.type
s3lf also tracks combat targets on players, using the built-in openmw event, OMWMusicCombatTargetsChanged. All combat targets are stored in the table s3lf.combatTargets, and additionally the function s3lf.isInCombat() may be used to quickly determine whether a fight is currently happening. When a combat target is added or removed, either the event S3CombatTargetAdded or S3CombatTargetRemoved is sent to the player, with the actor whom is added or removed being the only argument provided to downstream eventHandlers.
Additionally, s3lf objects provide a couple more convenience features to ease debugging and type checking respectively. A new function, `objectType`, is added to all objects which can be represented as a `s3lf`. For example:
s3lf = I.s3lf
s3lf.objectType()
npc
All object types are represented as simple (lowercase) strings that you'd intuitively expect them to be, eg npc, miscellaneous, weapon, and so on.
Every s3lf object also includes a display() method, which will show a neatly-formatted output of all fields and functions currently used by this s3lf object:
luap
I.s3lf.display()
S3GameGameSelf {
Fields: { },
Methods: { From, display, objectType },
UserData: { gameObject = openmw.self[object@0x1 (NPC, "player")], health = userdata: 0x702f9c0449e0, magicka = userdata: 0x702f81068130, record = ESM3_NPC["player"], shortblade = userdata: 0x702f62f9b350, speed = userdata: 0x702f62f40580 }
}
s3lf objects may call the display method anywhere they see fit, but the result will only be visible if a player is nearby (as it is printed to the console directly, using the nearby module to locate nearby players.)
If your mod uses the s3lf interface and it is not available, it is recommended you link back to the mod page in your error outputs so the user can get ahold of it themselves.
ProtectedTable
A construct designed to make it easy to bind a game system, to a global OpenMW settings group. This makes it easy, for example, to make a setting group which scales actor health and reference it in all scripts with concise notation. For example:
---@class CameraManager:ProtectedTable
---@field NoThirdPerson boolean
---@field PitchLocked boolean
---@field CursorCamPitch number configured pitch lock
local CameraManager = I.S3ProtectedTable.new {
logPrefix = ModInfo.logPrefix,
inputGroupName = 'SettingsGlobal' .. ModInfo.name .. 'MoveTurnGroup',
}
---@param dt number deltaTime
---@param Managers ManagementStore
function CameraManager:onFrameEnd(dt, Managers)
CameraManager:updateDelta()
if self.NoThirdPerson and camera.getMode() == camera.MODE.FirstPerson then
camera.setMode(camera.MODE.ThirdPerson)
end
end
ProtectedTables are usable in all contexts, except for MENU. Additionally, you may use I.S3ProtectedTable.help through the in-game (lua) console at any time for a detailed description of its usage. They will always be up-to-date with the settings group they're built on, as they contain a built-in subscribe function which will sync the values. If you wish, it is possible to override this subscribe function by using the subscribeHandler parameter of the constructor. subscribeHandler functions must match the ShadowTableSubscriptionHandler type definition.
ProtectedTables additionally come with a __tostring method that will show the manager name and all functions and methods associated with it. When building a script around a ProtectedTable, you may add as many new functions as you wish, but you may not add non-function fields. Additionally, to prevent invalidating the setting values the table tracks, the script will actually throw an error if you attempt to write directly to the table. To counteract this, all ProtectedTables include a state table which you may write to, and overwrite completely. The reason for this design is to allow flexible access to settings and to override each available function in the ProtectedTable on a case-by-case basis. If you create a protectedTable and use it as an interface, downstream modders may override the individual functions at their leisure without having to override your entire Interface.
Finally, due to their nature, please keep in mind that ProtectedTables only work with global setting groups, not player ones. This is to ensure that all possible gameObjects have access to the various values in each group.
ScriptContext
Provides an enum describing script contexts and a function to return the current one. Used by the LogMessage Function. Handy for when you would like for a given script or function to be usable regardless of what script context it is being ran in. ScriptContext is not available through an interface and must be required directly, since interfaces are naturally scopes anyway, and this is a somewhat niche usage.
Example:
local ScriptContext = require 'scripts.s3.scriptContext'
local currentContext = ScriptContext.get()
if currentContext == ScriptContext.Types.Player then
print("I'm a player script!")
elseif currentContext == ScriptContext.Types.Global then
print("I'm a global script!")
endLogMessage
Emits a message to the ~ console from any context. Takes one argument. Re-exported through the s3lf module under the name ConsoleLog.
Example:
-- S3lf interface
Lua[Player] I.s3lf.ConsoleLog(('Hai from %s!'):format(I.s3lf.recordId))
Hai from player!
Lua[Player] exit()
Lua mode OFF
> luas
Lua mode ON, use exit() to return, help() for more info
Context: Local[object0x4001134 (NPC, "SW_HungoxSteward")]
Lua[sw_hungoxsteward] I.s3lf.ConsoleLog(('Hai from %s!'):format(I.s3lf.recordId))-- standalone
local LogMessage = require 'scripts.s3.logmessage'
LogMessage(('Hai From %s'):format(I.s3lf.recordId))