========== The brief ===========
I'm taking the plunge and working on an experimental version of SDR with a new .dll plugin that *should* improve performance (hopefully significantly). For those of you that build OBSE plug-ins, are modders that might want to take advantage of some of the new stuff, or just fans of SDR, this is a big deal. (assuming I can pull this off)
========== The plan ============
The goal is to move the entire detection formula out of the scripts and into the sdr_obse.dll plug-in.
To do this, I have to create a number of customized functions that provide an alternative method to functions called by the Add Actor Values OBSE plug-in.
Instead of relying on AAV, I've come up with a system using a three tiered nested array (3TNA). I have successfully tested this out using scripts, but the performance of AAV is better. Hopefully, when I move the 3TNA system out of the scripts and into the sdr_obse.dll, the performance improvement will be enough to justify the rest of the changes.
========== Details on the 3TNA Array System ============
The top tier is a string map array of all the mods that have actors with stored values, using the mod name as a key for each key:value pair, with the value being another String Map array of the actors (middle tier).
Oblivion.esm: (array of actors)
OOO.esm: (Array of actors)
etc.
When SDR starts it will check against the list of mods that were loaded against the list of mods stored as keys in the array. If any of the keys/mods are no longer loaded, those records will be deleted from the array. This will make sure that SDR cleans up after itself and reduces bloat.
NOTE: the mod name keys are not actually populated into the array until a data type value is added to an actor ref. So if a mod doesn't have any actors associated with it, they will never appear in the array.
The middle tier is an array of all the actors (connected to the mod of the parent key) that have been assigned one or more values.
I've created a custom .dll function ("sdrGetParentModName") which can be used to quickly retrieve the parent mod name of any reference (or optionally an object belonging to a reference). This function is called to find/set/retrieve the array stored with the ModName as key.
I've also created a custom .dll function ("sdrGetBaseRefID") which strips the modIndex from the hexadecimal value of the actor ref's form ID and uses that as the key for each key:value pair in the actor list array. This allows you to change the load order of mods without losing that specific actor's data or creating duplicate entries for the same reference.
Here is a print out of a debugging log that shows the results of my tests. The base RefID is in parentheses after the actor name:
SDR: Test'yr (000014)
SDR: Ra'Jhan (01eb85)
SDR: Marrie (006238)
SDR: Imperial Watch (01c34d)
SDR: Gin-Wulm (03e92e)
SDR: Sevarius Atius (01fc5c)
SDR: Amillia of Daggerfall (0021a2)
SDR: Bassanio (007d28)
SDR: Rochelle Bantien (022bb3)
SDR: Manfred Kastring (178b2e)
SDR: Mandil (01c4c2)
SDR: Irene Metrick (01c4be)
SDR: Simplicia the Slow (01c462)
The bottom tier is a string map array of all the actor data types and their values that belong to the BaseRefID/key of the parent actor. For example:
["avDistToPlayer":1500]
["avStealth":48]
etc.
So overall, the array tier would be something like this:
MainDataArrayMap[ ["Oblivion.esm":[actor array map]] ["000014":[data array map]] ["avSkClothMult":1] ["avSkLightMult":1.2] ["avSkHeavyMult":.8] etc. ["01c34d":[data array map]] ["avSkClothMult":1.2] ["avSkLightMult":1] ["avSkHeavyMult":2] etc. etc. ["Better Cities.esp":[actor array map]] ["0021a2":[data array map]] ["avSkClothMult":.8] ["avSkLightMult":1] ["avSkHeavyMult":1.2] etc. ["178b2e":[data array map]] ["avSkClothMult":1] ["avSkLightMult":.8] ["avSkHeavyMult":1.4] etc. etc. etc.]
===================== Default Data =http://forums.bethsoft.com/topic/1508153-sdr-obse-plugin-experiment-wip/==================
Part of the system will include a method for very quickly setting default values for an actor, rather than having to set each one with a function call.
As an example, with SDR, I intend to set the defaults immediately when a token is first added to an actor. It will then be immediately updated with the correct values as the token initializes.
Here's a list of all the data types SDR currently uses and what their default values will be:
Spoiler
Let vDefaultData := ar_Construct StringMap
Let vDefaultData["avDistToPlayer"] := 5000 ; Distance of actor to the player
Let vDefaultData["avDetectionFormula"] := 0 ; Detection Formula to use
Let vDefaultData["avStealth"] := -100 ; SDR's Stealth Perception AV
Let vDefaultData["avBaseAlphaValue"] := 1 ; Base Alpha Value (transparency) 0 to 1, 1 equals solid
Let vDefaultData["avTokenValid"] := -1 ; 1 = valid token, meaning it has been initialized
Let vDefaultData["avHelmetArmorRating"] := 0 ; when wearing Helmet, Armor Rating is applied
Let vDefaultData["avHelmetPenaltyFOV"] := 0 ; the field of view penalty when wearing a helmet / hood
Let vDefaultData["avVisionQuality"] := 2 ; a.k.a. "VQ". Scale of 0 to 4, 0 = blind. 2 is average.
Let vDefaultData["avVisionFOVp"] := 180 ; Peripheral field of view range
Let vDefaultData["avVisionFOVb"] := 120 ; Binocular field of view range
Let vDefaultData["avDeafness"] := 0 ; How deaf the actor is
Let vDefaultData["avHearingQuality"] := 2 ; How well the actor can hear. Scale of 0 to 4. 2 is average.
Let vDefaultData["avHearingType"] := 0 ; how the ears handle directionality, 0 = fixed, 1 = semi-flexible, 2 = omni-directional
Let vDefaultData["avHearingCurrent"] := 2 ; The current hearing quality of the actor. (the refular avHearingQuality will probably be the default, but may be modified by spell effects.)
Let vDefaultData["avMuffleMult"] := 1 ; modifies overall sound made by the actor
Let vDefaultData["avCreatureType"] := 0 ; determines if normal (0), undead (1), or daedra (2)
Let vDefaultData["avFollowerType"] := 0 ; ?
Let vDefaultData["avVampirismStatus"] := 0 ; determines how far along in vampirism an actor is. 0 = not a vampire
Let vDefaultData["avBaseWeight"] := 100 ; this is the base weight of the actor, loosely based on their height/width/volume
Let vDefaultData["avEncumbrance"] := 0 ; how much weight the actor is carrying
Let vDefaultData["avTotalMass"] := 100 ; combines base weight with encumbrance
Let vDefaultData["avHealthFatigue"] := 0 ; the modifier to detection based on health / fatigue
Let vDefaultData["avIsUnconsious"] := 0 ; 1 = unconscious
Let vDefaultData["avLightLevel"] := 100 ; the light level hitting the actor. Scale of 0 to 100. 100 equals fully lit
Let vDefaultData["avChameleon"] := 0 ; SDR's adjusted Chameleon value.
Let vDefaultData["avLandingBump"] := 0 ; the bump for being detected when landing after a fall/jump
Let vDefaultData["avTerrainType"] := 0 ; the type of terrain the actor is currently treading on
Let vDefaultData["avMoveRateMult"] := 1 ; the modifier to detection based on how fast the actor is travelling
Let vDefaultData["avMoveType"] := 0 ; how the actor is moving (0 = walking)
Let vDefaultData["avSkClothMult"] := 1 ; the multiplier to cloth gear noise based on skill of actor
Let vDefaultData["avSkLightMult"] := 1 ; the multiplier to light armor gear noise based on skill of actor
Let vDefaultData["avSkHeavyMult"] := 1 ; the multiplier to heavy armor gear noise based on skill of actor
Let vDefaultData["avGearPenalty"] := 0 ; the total penalty based on the gear being worn
Let vDefaultData["avAudioPenalty"] := 0 ; the total audio penalty based on all factors
Let vDefaultData["avTempBlindValue"] := 0 ; the blindness level of the actor due to magical effects
Let vDefaultData["avTempBlindDurationMax"] := 0 ; the maximum duration of the temporary blindness effects
Let vDefaultData["avTempBlindSecondsPassed"] := 0 ; how many seconds have passed since the temp blindness effects were applied
Let vDefaultData["avTempDeafValue"] := 0 ; the deafness level of the actor due to magical effects
Let vDefaultData["avTempDeafDurationMax"] := 0 ; the maximum duration of the temporary deafness effects
Let vDefaultData["avTempDeafSecondsPassed"] := 0 ; how many seconds have passed since the temp deafness effects were applied
Let vDefaultData["avxDetectorAdj"] := 0 ; adjustments to the detector's chance to detect, tweaked by external mods
Let vDefaultData["avxTargetAdj"] := 0 ; adjustments to the target's chance to detect, tweaked by external mods
Let vDefaultData["avSkClothMult"] := 0 ; adjustments applied based on factors from the Real Sleep mod
Let vDefaultData["avSkLightMult"] := 0 ; adjustments applied based on factors from the Basic Primary Needs mod
Let vDefaultData["avSkHeavyMult"] := 0 ; adjustments applied based on factors from the Bare Necessities mod
What is really awesome about using the array system is that I'm not stuck with some of the limitations of AAV. AAV can only store integers. By using an array, I can store integers, fractions, strings and other arrays. This will provide an insane amount of flexibility that I never had before.
===================== Icing on the CAKE for MODDERS ====================
I am designing this system so that not only can SDR take advantage of it, but so can other modders that use SDR's plug-in.
I'm creating functions for Set/Get/Mod the data values. They will look something like this:
ActorRef.sdrGetDataTypeStr DataArray DataType
ActorRef.sdrSetDataTypeStr DataArray DataType String
ActorRef.sdrGetDataTypeNum DataArray DataType
ActorRef.sdrSetDataTypeNum DataArray DataType Number
ActorRef.sdrModDataTypeNum DataArray DataType ModAmount
AcrorRef.sdrSetDefaultValues DataArray DefaultArray*
* (array of actor values, e.g. DataType:Value)
shorthands will be sdrGDTS, sdrSDTS, sdrGDTN, sdrSDTN, sdrMDTN, sdrSDV
In order to take advantage of this system, you only need some basic tools:
- construct a string map array for the top tier
- construct a default array of actor values
- create some scripts that validate previous mod lists with the current mod list and removes mod records from the master array.
- create a script that finds and removes an actor from the array when they are killed off.
All four of these things I will have to do for SDR, so I will provide example code as part of a modder's resource package.
You can then use the system to store/retreive/modify actor values of your own design in any number of ways with (hopefully) minimal performance and bloat issues. In theory, you could probably have different "master arrays" for different purposes.
In theory, you could store yet another array as a value at the third tier, creating a fourth tier, so perhaps I will need another pair of functions:
ActorRef.sdrSetDataTypeArr DataArray DataType Array
ActorRef.sdrGetDataTypeArr DataArray DataType
I personally don't think I would use them. But who can say? They might be handy to have, and it will be easier to build them all at the same time.
====================== How it would work in a script =====================================
*** setting / modding a value ***
Assume there is an array_var variable called MainDataArrayMap in the quest sdrQ.
Each time a "set" or "mode" function is called, you will actually be replacing the entire array.
It is important to know if you setting/modding the value to be a number or string:
Let sdrQ.MainDataArrayMap := PlayerRef.sdrSetDataTypeNum sdrQ.MainDataArrayMap "avTerrainType" 1 ; sets the Terrain Type
Let sdrQ.MainDataArrayMap := PlayerRef.sdrModDataTypeNum sdrQ.MainDataArrayMap "avTempBlindValue" -10 ; reduces the current temporary blindness value by 10
Let sdrQ.MainDataArrayMap := PlayerRef.sdrSetDataTypeString sdrQ.MainDataArrayMap "avCreatureType" "Daedra" ; sets the creature type to be "Daedra", particularly helpful if they are not flagged as a Daedra but should be treated like one.
*** retrieving a value ***
In this case, you have to know if the result is going to be a number or string to know which function to call.
short vTempAV
string_var vTempString
Let vTempAV := PlayerRef.sdrGetDataTypeNum sdrQ.MainDataArrayMap "avTempDeafValue" ; returns the temporaty deafness value
Let vTempString := PlayerRef.sdrGetDataTypeStr sdrQ.MainDataArrayMap "avHearingType" ; returns the current hearing type stored ("fixed", "semi-flexible", "omni-directional", etc.)
(side note: I probably won't be using strings very much, since number processing is much faster, but there's no harm in making the option available.)
================ Other uses ========================
This system is not limited to just actors. Because you can retrieve the base form ID of objects, you could potentially create dataType/value pairs of other information for other purposes. For example, let's say you wanted to track an alternate weight / length of a sword or the material the sword was made from without modifying the original data set with the object. You could potentially do all of that with a different stringmap. So you could have a "MainActorDataMap" for storing custom actor values and a "MainObjectDataMap" for storing custom object values, or even a "MainSpellDataMap" for storing custom magic spell values.
================ Conclusion =========================
I haven't dealt with a lot of arrays, so I'm not exactly sure if there are any gotchas I haven't thought of yet. But I think as long as I (and you modders) follow a "Leave No Trace" policy, the overall save game bloat should not be too significant.
I am very much looking forward to hearing people's thoughts and suggestions about what I am planning on doing.