-
Notifications
You must be signed in to change notification settings - Fork 0
Modifiers Subsytem
Modifiers are scripts bound to the device that perceive various events around it and generate responses.
For example, a modifier can add gold to the inventory when the player removes the device. Or a modifier can activate other devices when the player kills an enemy.
Modifiers have certain properties and restrictions that they follow:
- Each device can have no more than one modifier of each type.
- Modifiers can be set in the armor script in esp, and/or added by the patcher when the device first appears in the inventory.
- Modifier parameters are set during its generation and are not changed afterwards.
- Modifier parameters are stored individually for each device.
- Modifiers are processed only on equipped devices.
The modifier subsystem consists of several parts:
- Modifier Manager - coordinates the whole system, collects and passes events to modifiers.
- Modifiers Storages - self-registering containers with modifiers.
- Modifiers themselves - scripts on quest aliases that define how the modifiers work.
- Configuration - each device has properties for storing configurations of applied modifiers.
- Patcher - Patcher can also be used to assign modifiers on devices, so it can be referred to this subsystem, although it is not a mandatory part of it.
- Modifiers Patcher Presets - parallel scripts on modifier aliases that define the settings by which parameters (DataStr and DataForms) are created when a modifier is added by the Patcher to a device.
- There are also separate quests, scripts and methods that are scattered throughout the framework and help to collect events for the manager.
Scriptname UD_ModifierManager_Script extends QuestScriptName UD_ModifierStorage extends QuestTo make Modifiers Storage all you need to do is create a quest, bind the UD_ModifierStorage script to it, and add modifiers aliases. See UD_ModifierSlots_Quest Quest for the reference.
Scriptname UD_Modifier extends ReferenceAliasScriptname UD_CustomDevice_RenderScript extends ObjectReference
; <...>
Alias[] Property UD_ModifiersRef auto
String[] Property UD_ModifiersDataStr auto
Form[] Property UD_ModifiersDataForm1 auto
Form[] Property UD_ModifiersDataForm2 auto
Form[] Property UD_ModifiersDataForm3 auto
Form[] Property UD_ModifiersDataForm4 auto
Form[] Property UD_ModifiersDataForm5 autoAlmost all events go through a manager that calls the appropriate modifier methods on each device of registered actors.
Function GameLoaded()
Function TimeUpdateSecond(Float afTime)
Function TimeUpdateHour(Float afMult)
Function Orgasm()
Function DeviceLocked()
Function DeviceUnlocked()
Function MinigameStarted(UD_CustomDevice_RenderScript akMinigameDevice)
Function MinigameEnded(UD_CustomDevice_RenderScript akMinigameDevice)
Function WeaponHit(Weapon akWeapon, Float afDamage)
Function SpellHit(Form akSpell, Float afDamage)
Function SpellCast(Spell akSpell)
Function ConditionLoss(Int aiCondition)
Function StatEvent(String asStatName, Int aiStatValue)
Function Sleep(Float afDuration, Bool abInterrupted)
Function ActorAction(Int aiActorAction, Form akSource)
Function KillMonitor(ObjectReference akVictim, Int aiCrimeStatus)
Function ItemAdded(Form akItemForm, Int aiItemCount, ObjectReference akSourceContainer, Bool abIsStolen)
Function ItemRemoved(Form akItemForm, Int aiItemCount, ObjectReference akSourceContainer, Bool abIsStolen)Custom modifier or modifier in general - a type of modifier that is created as a standalone script with a complete logic of reaction to external events. It, like any modifier, can have flexible customization, but, in general, acts according to the same template. On the other hand, this pattern of actions can be very complex, as the script can take into account all possible events, and the outcome is limited only by the developer's imagination.
See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#custom-modifiers
Combo modifiers (CMs) are a subclass of modifiers that consist of a trigger script (hereafter just Trigger) and an outcome script (hereafter just Outcome) that are selected from predefined lists and can be used in any combination. CM forwards incoming events to the Trigger, and when it responds (returns true), the Outcome procedure is executed.
Base class for the CM is implemented in the file:
ScriptName UD_Modifier_Combo extends UD_ModifierLike any other modifier CM uses the configuration stored in the device properties, but with certain caveats:
- The arguments in
DataStrare shared between the Trigger and the Outcome. Indexes 0..6 are used in the Trigger, and all subsequent indexes are used in the Outcome. It is potentially possible to pass information between the Trigger and the Outcome using these arguments, but this is not used anywhere right now. -
DataForm1- stores the trigger script. -
DataForm2- stores the outcome script. -
DataForm3- passed to the Trigger. -
DataForm4- passed to the Outcome. -
DataForm5- passed to the Outcome.
As an example, consider the following configuration of property values on a device.
UD_ModifiersRef[0] = <UD_Modifier_Combo> ; alias with generic combo modifier script on it
UD_ModifiersDataStr[0] = "DU,50.0,,,,,,1,3,0" ; 0..6 arguments for the Trigger, all of the following arguments for the Outcome
UD_ModifiersDataForm1[0] = <UD_ModTrigger_DeviceEvent> ; MiscForm with attached trigger script
UD_ModifiersDataForm2[0] = <UD_ModOutcome_AddItem> ; MiscForm with attached outcome script
UD_ModifiersDataForm3[0] = None ; is passed to the Trigger
UD_ModifiersDataForm4[0] = <Lockpick> ; is passed to the Outcome
UD_ModifiersDataForm5[0] = None ; is passed to the OutcomeThe UD_ModTrigger_DeviceEvent trigger reacts to manipulation of its own device and the first 7 arguments from the DataStr (DU,50.0,,,,,) along with DataForm3 are passed to it. This causes the trigger to activate on the DeviceUnlock event with a 50% probability. The procedure from the outcome script will then be called with the remaining arguments from DataStr (1,3,0) and DataForm4, DataForm5. The given values for the Outcome will have the following effect: 1-3 lockpicks will be added to the player's inventory.
The example above showed one way in which CM can be used. When a template combo-modifier is added to the device, and then filled with the desired trigger, outcome and configuration for them. There are several such templates made for this use case, which lie in the UD_ModifierSlotsCMB_Quest storage. CMN1 .. CMN5 for the modifiers with the negative effects and CMP1 .. CMP5 for the modifiers with the positive effects.
This approach allows you to quickly create modifiers of any degree of insanity, but it also has its limitations:
- It is impossible to predict and prevent conflicts between modifiers.
- You have to settle for a generic name and description.
- They can't be used with the Patcher (at least not in any reasonable way).
- Add one of the generic combo modifiers to the device (
CMN1..5orCMP1..5from questUD_ModifierSlotsCMB_Quest) - Complete the configuration similar to the example above, namely:
- DataFrom1 - Trigger.
- DataForm2 - Outcome.
- Other properties fill according to the description of the trigger and outcome.
The trigger accepts incoming event information from the modification manager and returns either true or false. It can also write some information into DataStr parameters to implement “memory-aware” algorithms. It essentially repeats the work of a regular modifier, but with limited capabilities and with one type of reaction: true/false.
Base class for the Trigger is implemented in the file:
Scriptname UD_ModTrigger extends MiscObjectEvery Trigger should be placed on unique MiscObject in esp.
See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#triggers
The Outcome of a combo modifier is essentially a simple procedure that performs one action according to the passed configuration.
Base class for the Outcome is implemented in the file:
Scriptname UD_ModOutcome extends MiscObjectEvery Outcome should be placed on unique MiscObject in esp.
See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#outcomes
Another way to use CM is to create presets: separate aliases with pre-defined Trigger and Outcome. These presets do not require custom scripts, but allow you to use it as a regular modifier: set names and descriptions and assign with the Patcher.
ScriptName UD_Modifier_ComboPreset extends UD_Modifier_Combo
UD_ModTrigger Property ModTrigger Auto
UD_ModOutcome Property ModOutcome Auto
<...>See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#combo-modifiers-presets
- Add new quest alias on Modifier Storage Quest.
- Attach
UD_Modifier_ComboPresetquest to the created alias. - Fill properties
ModTriggerandModOutcomewith appropriate values. - Use the created modifier in devices with configuration according to the trigger and outcome descriptions.
;/ Variable: UD_ModsMinCap
If the number of modifiers is less than the specified value, the patcher will try to add more
/;
Int Property UD_ModsMinCap = 2 Auto Hidden
;/ Variable: UD_ModsSoftCap
Soft cap for the number of modifiers added by the patcher (the actual number of mods may slightly exceed this value)
/;
Int Property UD_ModsSoftCap = 4 Auto Hidden
;/ Variable: UD_ModsHardCap
Hard cap for the number of modifiers added by the patcher (when this value is reached, the patcher stops adding mods)
/;
Int Property UD_ModsHardCap = 99 Auto Hidden
;/ Variable: UD_ModGlobalProbabilityMult
Multplier that affects probability to add each modifier
/;
Float Property UD_ModGlobalProbabilityMult = 1.0 Auto Hidden
;/ Variable: UD_ModGlobalSeverityShift
Addition to the severity of each modifier (mathematical expectation of a random variable)
/;
Float Property UD_ModGlobalSeverityShift = 0.0 Auto Hidden
;/ Variable: UD_ModGlobalSeverityDispMult
The value by which the severity dispersion of each modifier is multiplied
/;
Float Property UD_ModGlobalSeverityDispMult = 1.0 Auto HiddenTo assing modifiers to the device, the following operations are performed:
- Quickly check all modifiers for compatibility. In the basic implementation, the check is rather superficial to speed up the execution. In this step, an upper bound for the number of compatible modifiers is calculated and an array of them is formed. See script
UD_Modifierand functionPatchModifierFastCheckfor reference. - The array of compatible modifiers is passed to a more detailed analysis. This loop performs a full compatibility check, calculates the probability of addition, and adds the modifier itself. See script
UD_Modifierand functionPatchModifierCheckAndAddfor reference. In the basic implementation of this function, the probabilities are adjusted to meet the softcap of the number of modifiers (seeUD_ModsSoftCap). Provided that this is allowed by the modifier settings (seeUD_Patcher_ModPreset::IsNormalizedProbability). - If there are not enough modifiers on the device (less than
UD_ModsMinCap) then step 2 is performed again.
Bool Function PatchModifierFastCheckOverride(UD_CustomDevice_RenderScript akDevice)
Float Function PatchModifierCheckAndAddOverride(UD_CustomDevice_RenderScript akDevice)In the basic implementation, when Patcher adds a modifier to a device, the most suitable preset is searched and a configuration is generated according to it. The preset is implemented as a script, which is bound to the same quest alias as the modifier script. In total, up to three presets can be bound to an alias. Each preset is a separate trivial script inherited from the base script UD_Patcher_ModPreset, which allows you to quickly find and distinguish them from each other in the modifier functions.
Scriptname UD_Patcher_ModPreset1 extends UD_Patcher_ModPreset
Scriptname UD_Patcher_ModPreset2 extends UD_Patcher_ModPreset
Scriptname UD_Patcher_ModPreset3 extends UD_Patcher_ModPresetThere are no settings left in the Modifier itself for the Patcher. Therefore, if there are no Preset scripts added to the Modifier alias, it will not be processed by the Patcher.
See UD_Patcher_ModPreset and function Int Function CheckDevice(UD_CustomDevice_RenderScript akDevice) for the reference.
Several filters are used to discard incompatible presets and select the best of the remaining:
- Checking that the preset is for the player's device, the NPC's device, or both
- Checking for the forbidden types of devices defined by keywords.
- Checking for the conflicted tags from modifiers already assigned to the patched device.
- Checking for the conflicted tags from modifiers on all worn devices.
- Checking for the favorable types of devices defined by keywords. If any of the favored device types are specified, the preset becomes exclusive for that device type. And this preset becomes more favored than other presets if there are any.
- Checking for required tags on existed modifiers. Even if the necessary tags are not found, the Patcher will make another attempt to add this modifier in the last pass.
All these settings are stored in public properties that could be configured in plugin.
;/ Variable: ApplicableToNPC
Indicates that this modifier can be applied to devices on NPCs
/;
Bool Property ApplicableToNPC = True Auto
;/ Variable: ApplicableToPlayer
Indicates that this modifier can be applied to devices on the Player
/;
Bool Property ApplicableToPlayer = True Auto
;/ Variable: PreferredDevices
This preset is exclusive to devices with any of the specified keywords
/;
Keyword[] Property PreferredDevices Auto
;/ Variable: ForbiddenDevices
This preset is not compatible with devices with any of the specified keywords
/;
Keyword[] Property ForbiddenDevices Auto
;/ Variable: ConflictedDeviceModTags
Modifier tags on the device that conflict with this preset
/;
String[] Property ConflictedDeviceModTags Auto
;/ Variable: ConflictedGlobalModTags
Modifier tags on all worn devices that conflict with this preset
/;
String[] Property ConflictedGlobalModTags Auto
;/ Variable: RequiredDeviceModTags
Modifier tags on the device that needed by this preset
/;
String[] Property RequiredDeviceModTags AutoAfter compatibility checks each modifier has an element of chance that it will be added or not.
;/ Variable: BaseProbability
Base probability of applying this modifier
/;
Float Property BaseProbability = 100.0 Auto
;/ Variable: IsNormalizedProbability
Indicates that the probability is normalized to the allowed number of modifiers (softcap)
/;
Bool Property IsNormalizedProbability = True AutoIsNormalizedProbability property is responsible for the fact that as the total number of modifiers in the library increases, the probability of adding each of them (with this flag) will decrease, so that the total number of modifiers on the device remains close to the softcap.
After the modifier has passed all the checks with a certain preset, it is needed to generate the parameters with which it will be present on the device. For this purpose, the preset has several properties.
;/ Variable: DataStr_Easy
Easiest DataStr configuration when adding a modifier with the Patcher
/;
String Property DataStr_Easy Auto
;/ Variable: DataStr_Ground
Medium DataStr configuration when adding a modifier with the Patcher
(Silly name to be displayed in the correct order)
/;
String Property DataStr_Ground Auto
;/ Variable: DataStr_Hard
Hardest DataStr configuration when adding a modifier with the Patcher
/;
String Property DataStr_Hard Auto
;/ Variable: DataStr_Types
Types of the parameters in configurations
/;
String Property DataStr_Types Auto
;/ Variable: Form1_Variants
List of possible values for the DataFormN when adding a modifier with the Patcher.
The easiest options come first.
/;
FormList Property Form1_Variants Auto
FormList Property Form2_Variants Auto
FormList Property Form3_Variants Auto
FormList Property Form4_Variants Auto
FormList Property Form5_Variants AutoThe DataStr_Easy, DataStr_Ground and DataStr_Hard properties are used to create the DataStr parameter of the modifier instance on the device. The properties specify reference values that correspond to the easiest configuration, medium and maximum difficult configuration.
For each parameter in DataStr values are generated in the range given by those reference configurations above using normally distributed numbers on the interval (-1;1).
A normal distribution is also used to select the parameters of the Form type. It is assumed that FormN_Variants contains possible variants ordered by increasing diffculty.
The parameters of the normal distribution are set in the properties below
;/ Variable: BaseSeverity
Average modifier severity (mathematical expectation of the random variable on which the configuration is generated)
/;
Float Property BaseSeverity = 0.0 Auto
;/ Variable: SeverityDispersion
Severity dispersion
/;
Float Property SeverityDispersion = 0.20 AutoThe graph below shows the probability densities for random distributions with different Severity and Dispersion parameters. In our context the easiest configuration is on the left and the hardest - on the right.

The user has access to the preset settings on the MCM page. He can change the applicability, probability and parameters with which the modifier configuration is generated.
TODO: Use a tagging system instead of checking by name.
TODO: Check some tags on every equipped device.
A list of space-separated tags that reflect the nature of the modifier. Used to resolve possible conflicts during automatic generation. Each tag is a short abbreviation. Some tags may be accompanied by a "+" or "-" sign, which reflect the positive or negative effect of the modifier.
For example:
-
GOLD+- the modifier gives gold to the Player. Adding modifiers withGOLD+andGOLD-tags may cause a conflict. -
EVL- the modifier causes the device to evolve. Modifiers that rely on device removal events may not work properly. -
UNQ- the modifier has a unique (quest) effect. Adding other modifiers with this tag can lead to unpredictable results.