Skip to content

Modifiers Subsytem

iiw2012 edited this page Mar 7, 2025 · 25 revisions

Description

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 stored individually for each device.
  • Modifier parameters for the device are set during its creation/generation and are not changed afterwards.
  • Modifiers are processed only on equipped devices.

Implementation

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 in storages. The scripts describe the logic of modifier operation.
  • Configuration - each device has properties for storing configurations of applied modifiers.
  • Patcher - since Patcher is used to assign modifiers on devices, 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 Modifier Manager.

Modifier Manager

Modifier Manager is implemented as UD_ModifierManager_Quest quest with UD_ModifierManager_Script script on it.

Modifiers Storages

To make Modifiers Storage all you need to do is create a quest, bind the UD_ModifierStorage script to it, and create aliases for the each modifier. See UD_ModifierSlots_Quest quest for the reference.

Base modifier script

All modifier scripts are inherited from the base script UD_Modifier.

Configuration data

Each device (in the script UD_CustomDevice_RenderScript) has a special property to store references to modifiers and their configurations.

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    auto

This configuration is stored in parallel arrays. A cut of values at one index refers to one modifier.

Event processing

Almost all events go through a Modifier Manager that calls the appropriate modifier methods on each device of registered actors.

Modifier processor list

The modifier script (see UD_Modifier) has event handlers that can be used to receive information about the outside world and generate an appropriate response.

Function GameLoaded(UD_CustomDevice_RenderScript akDevice, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function TimeUpdateSeconds(UD_CustomDevice_RenderScript akDevice, Float afGameHoursSinceLastCall, Float afRealSecondsSinceLastCall, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function TimeUpdateHour(UD_CustomDevice_RenderScript akDevice, Float afHoursSinceLastCall, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function Orgasm(UD_CustomDevice_RenderScript akDevice, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function DeviceLocked(UD_CustomDevice_RenderScript akDevice, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function DeviceUnlocked(UD_CustomDevice_RenderScript akDevice, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Bool Function MinigameAllowed(UD_CustomDevice_RenderScript akModDevice, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
    return true
EndFunction

Function MinigameStarted(UD_CustomDevice_RenderScript akModDevice, UD_CustomDevice_RenderScript akMinigameDevice, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function MinigameEnded(UD_CustomDevice_RenderScript akModDevice, UD_CustomDevice_RenderScript akMinigameDevice,String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function WeaponHit(UD_CustomDevice_RenderScript akDevice, Weapon akWeapon, Float afDamage, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function SpellHit(UD_CustomDevice_RenderScript akDevice, Form akSpell, Float afDamage, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function SpellCast(UD_CustomDevice_RenderScript akDevice, Spell akSpell, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function ConditionLoss(UD_CustomDevice_RenderScript akDevice, Int aiCondition, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function StatEvent(UD_CustomDevice_RenderScript akDevice, String asStatName, Int aiStatValue, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function Sleep(UD_CustomDevice_RenderScript akDevice, Float afDuration, Bool abInterrupted, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function ActorAction(UD_CustomDevice_RenderScript akDevice, Int aiActorAction, Int aiEquipSlot, Form akSource, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function KillMonitor(UD_CustomDevice_RenderScript akDevice, ObjectReference akVictim, Int aiCrimeStatus, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function ItemAdded(UD_CustomDevice_RenderScript akDevice, Form akItemForm, Int aiItemCount, ObjectReference akSourceContainer, Bool abIsStolen, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function ItemRemoved(UD_CustomDevice_RenderScript akDevice, Form akItemForm, Int aiItemCount, ObjectReference akDestContainer, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Function SkillIncreased(UD_CustomDevice_RenderScript akDevice, String asSkill, Int aiValue, String aiDataStr, Form akForm1, Form akForm2, Form akForm3, Form akForm4, Form akForm5)
EndFunction

Since modifiers are external scripts for the device, each time their function is called, you have to pass configuration stored on the device.

Custom modifiers

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. Like any modifier it can have flexible customization, but acts in general 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.

List of (custom) modifiers

See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#custom-modifiers

Combo 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 abstract class for the CM is implemented in the file:

ScriptName UD_Modifier_Combo extends UD_Modifier

Like any other modifier CM uses the configuration stored in the device properties, but with certain caveats:

  • The parameters in DataStr are 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 parameters, but this is not used anywhere right now.
  • DataForm1 - passed to the Trigger.
  • DataForm2 - passed to the Outcome.
  • DataForm3 - passed to the Outcome.

Two implementations of the combined modifier are available based on this script:

  1. Preset (UD_Modifier_ComboPreset) - a modifier with pre-determined Triggers and Outcome. It must be bound to an alias in the modifier storage.
  2. Generic (UD_Modifier_ComboGeneric) - allows to construct modifiers directly on the device without adding new aliases. Template aliases defined in UD_ModifierSlotsGeneric_Quest are used for this purpose.

Preset Combo Modifiers

Modifiers based on UD_Modifier_ComboPreset with pre-determined Triggers and Outcome.

Implementation

ScriptName UD_Modifier_ComboPreset extends UD_Modifier_Combo

UD_ModTrigger Property ModTrigger Auto
UD_ModOutcome Property ModOutcome Auto
<...>

List of combo modifiers presets

See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#combo-modifiers-presets

Usage

  1. Add new quest alias on Modifier Storage Quest.
  2. Attach UD_Modifier_ComboPreset quest to the created alias.
  3. Fill properties ModTrigger and ModOutcome with appropriate values.
  4. Use the created modifier in devices with configuration according to the trigger and outcome descriptions.

Generic Combo Modifiers

Modifiers based on UD_Modifier_ComboGeneric with Triggers and Outcome that defined on device in DataForm4 and DataForm5 form arguments. Template aliases defined in UD_ModifierSlotsGeneric_Quest are used for UD_ModifiersRef[] items.

Below is an example of such a modifier assigned to a device:

UD_ModifiersRef[0]        = <UD_Modifier_ComboGeneric>      ; alias with generic combo modifier script on it 
UD_ModifiersDataStr[0]    = "DU,50.0,,,,,,1,3,0"            ; 0..6 parameters for the Trigger, all of the following parameters for the Outcome
UD_ModifiersDataForm1[0]  = None                            ; is passed to the Trigger
UD_ModifiersDataForm2[0]  = <Lockpick>                      ; is passed to the Outcome
UD_ModifiersDataForm3[0]  = None                            ; is passed to the Outcome
UD_ModifiersDataForm4[0]  = <UD_ModTrigger_DeviceEvent>     ; MiscForm with attached trigger script
UD_ModifiersDataForm5[0]  = <UD_ModOutcome_AddItem>         ; MiscForm with attached outcome script

The UD_ModTrigger_DeviceEvent trigger reacts to manipulation of its own device and the first 7 parameters from the DataStr (DU,50.0,,,,,) along with DataForm1 are passed to it. This causes the trigger to activate on the DeviceUnlock event with a 50% probability. The outcome script will then be called with the remaining arguments from DataStr (1,3,0) and DataForm2, DataForm3. And 1-3 lockpicks will be added to the player's inventory.

Usage

  1. Add one of the generic combo modifiers to the device (CMN1..5 or CMP1..5 alias from quest UD_ModifierSlotsGeneric_Quest)
  2. Complete the configuration similar to the example above, namely:
    1. DataFrom4 - Trigger.
    2. DataForm5 - Outcome.
    3. Other properties fill according to the nature of the Trigger and Outcome.

Triggers

The trigger accepts incoming event information from the Modifier 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.

Implementation

Base class for the Trigger is implemented in the file:

Scriptname UD_ModTrigger extends MiscObject

Every Trigger should be placed on unique MiscObject in esp.

List of Triggers

See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#trigger-scripts

Outcomes

The Outcome of a combo modifier is essentially a simple procedure that performs action(s) according to the passed configuration (see function Outcome). Outcome is normally only called once per event.

Function Outcome(UD_Modifier_Combo akModifier, UD_CustomDevice_RenderScript akDevice, String aiDataStr, Form akForm2, Form akForm3)
EndFunction

Bool Function MinigameAllowed(UD_Modifier_Combo akModifier, UD_CustomDevice_RenderScript akDevice, String aiDataStr, Form akForm2, Form akForm3)
    Return True
EndFunction

Implementation

Base class for the Outcome is implemented in the file:

Scriptname UD_ModOutcome extends MiscObject

Every Outcome should be placed on unique MiscObject in esp.

List of Outcomes

See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#outcome-scripts

Patcher and modifiers

Patcher MCM Configuration

;/  Variable: UD_ModsMin
    Minimum number of mods added by the Patcher
/;
Int         Property UD_ModsMin             =    1    Auto Hidden

;/  Variable: UD_ModsMax
    Maximum number of mods added by the Patcher
/;
Int         Property UD_ModsMax             =    4    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 Hidden

;/  Variable: UD_ModAddToTest
    This modifier (specified by alias name) will be added to the device to be processed by the patcher.
    After attempting to add, the value will be reset.
/;
String      Property UD_ModAddToTest                = ""    Auto Hidden

Presets for the Patcher

Patcher (UD_Patcher) uses presets (UD_Patcher_ModPreset) in its work with modifiers. If no presets are defined for a modifier, it will not be used by the Patcher.

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. Up to three presets can be bound to an alias in total. 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 functions.

Scriptname UD_Patcher_ModPreset1 extends UD_Patcher_ModPreset
Scriptname UD_Patcher_ModPreset2 extends UD_Patcher_ModPreset
Scriptname UD_Patcher_ModPreset3 extends UD_Patcher_ModPreset

Methods to Override

Int Function CheckDeviceCompatibility(UD_CustomDevice_RenderScript akDevice, Bool abCheckWearer = True)
Float Function GetProbability(UD_CustomDevice_RenderScript akDevice, Float afNormMult, Float afGlobalProbabilityMult)

Tags

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 with GOLD+ and GOLD- 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. See https://github.com/iiw2012/UnforgivingDevices/wiki/Modifiers-List#used-tags

Checking and selecting the most suitable preset

Several filters are used to discard incompatible presets and select the best of the remaining:

  1. Checking all modifiers that compatible with the device (keywords and tags restriction from existed modifiers).
Bool Function CheckModifierCompatibility(String[] aasForbiddenModTags)
  1. Finding the most suitable preset for each modifier
UD_Patcher_ModPreset Function GetCompatiblePatcherPreset(UD_CustomDevice_RenderScript akDevice, Bool abCheckWearer = True)
  1. Checking if the preset is compatible with tags (from existing modifiers) on the device or wearer
Int Function CheckTagsCompatibility(String[] aasDeviceModsTags, String[] aasWearerModsTags)

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              Auto
  1. We get the probability of addition for each remaining preset. Summarize all probabilities and randomly select from the resulting array.
;/  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
/;
Bool        Property IsNormalizedProbability    = True  Auto

Tip

For example, to make a modifier that will always appear on suitable devices, you need to set IsNormalizedProbability to False and BaseProbability to 100.0%.

Generating modifier parameters

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                     Auto

The 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.

Normally Distributed Values

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  Auto

The 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.

image

MCM Configuration

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.