PJMail Posted March 25, 2024 Share Posted March 25, 2024 Obviously in-game objects that hang around - like references (REFR), actor, armors, weapons, magic effects, quest ID's etc are in your saves, but are 'ephemeral' ID's (resolved at game start) also stored? E.g. COBJ, DIAL,... What about other things that don't hang around - like projectiles, image space modifiers, movement types etc? Looking in Resaver isn't helping - unless the only important things are in the "changeForms" section - in which case only these record contents are important: ACHR,BOOK,CELL,CLAS,FLST,INFO,LCTN,LVLN,MGEF,NOTE,NPC_,PCON,PGRE,QUST,REFR,RELA,SCEN,SMQN (and obviously the base object of any objectreference - as that needs to be resolved correctly on game start!) Thanks in advance. Link to comment Share on other sites More sharing options...
SKKmods Posted March 25, 2024 Share Posted March 25, 2024 I dont *KNOW* but have always inferred that base forms are NOT saved in a .FOS save, because when a mod is removed with thoise forms instantiated in the game the error log shows: Cannot open store for class THE_BASE_FORM_I_REMOVED, missing file? You could explore with a test; create mod with a new each base form type with a script that spawns one of each principle form into the world or uses a form (e.g. an actor that uses new level lists, perk, mgef, combat styles), save and remove the mod. See what errors you get. Link to comment Share on other sites More sharing options...
LarannKiar Posted March 25, 2024 Share Posted March 25, 2024 (edited) I don't know to be honest, I haven't examined the save loader and manager very much (I avoid writing data directly to saves). The code works something like this: in theory, any Form can be flagged as "changed". This is done by the EXE through a few virtual functions of the Form class. The game writes the changes into a buffer, keeps them in the memory then upon saving, flushes the changes to the save file. Form Lists for example is known to changeable, this ability is even exposed to Papyrus (e.g. AddForm()). Script added forms are stored in another "list" of each FormList that contains such forms. On the other hand, certain Form data isn't loaded into the memory as actual Form data. For example, loose mods (the MiscItems) linked to every craftable ObjectMod are loaded into a separate hash map and are not treated like an "ObjectMod data" despite in mod and master files it seems like as if they were and in both FO4Edit and the Creation Kit loose mods are modifiable in the ObjectMod record. So plugin records and record structures don't really matter. What determines whether a particular Form like a Scene has any variable that can be changed during a game session is that whether the EXE has functions to interpret the changes. Which is unknown as the game is closed source. So one can only try to map the "actually changeable" Form types through test observations. As for Projectiles, Projectile base form is unlikely to be changed but as I said, I can't know for sure.. Projectile references so the actual objects that the player and NPCs shoot for example are short lived IsCreated Object References spawned by the code with a specific, additional Projectile data structure. So they're a special type of Object References (just like Explosion instances by the way). (Bit off topic, I decoded the ActorCause struct of Object References (made Set/GetActorCause) recently that might be of interests, tested on a few thrown grenades but I don't know if they actually be used to fix the related known engine bugs, e.g. random hostality by "friendly" grenade exploading after CombatState = 0). Image space modifiers are stored in saves but they're kind of a "global data" so what image space modifiers were in effect at the time the save was created is not an "Image Space Modifier data" like their Depth of Field values. The code creates "instances" of them similarly to Input Enable Layers then, if there's an instance in the save the game will auto apply them after loading it. Movement Types, I don't think so but active animations performed by NPCs are stored in saves in general... but it's hard to tell exactly how as animation handling looks a bit overcomplicated (though ironcally I see more anim glitches in Starfield than in Fallout..). As for "changeFlags", unlike form flags like the "Non-Playable" or the "Temporary reference" flags (that one can only figure out by examining the integer bits), the EXE contains a handy function that can even prettyprint the changeFlag results. Signiture would look like [ GetFormChangeFlagAsString(int FormChangeFlag, byte FormType, byte ReturnFriendlyName = 1) ]. I added the ability to see the changeFlags in Starfield's GetReferenceInfo but not to Garden of Eden SE.. (maybe later). The result differ depending on what FormType is used. (They are enumerated). ChangeFlag 30 for a Cell form would mean it has "CellDetachTime" (used for Cell reset). Here's a list of the changeFlag values (not decoded or verified by me, I only have the function). By the way, I can post the disassembled function or the formType list if you're interested. Those Forms are likely changeable. FormCreated = 0, FormFlags = 0, ReferenceMove = 1, ReferenceHavokMove = 2, ReferenceCellChange = 3, ReferenceScale = 4, ReferenceInventory = 5, ReferenceExtraOwnership = 6, ReferenceBaseObject = 7, ReferenceExtraLinkRef = 8, ReferenceExtraWorkshop = 9, ReferenceExtraGameOnly = 31, ReferenceExtraCreatedOnly = 30, ReferenceExtraEncounterZone = 29, ReferenceAnimation = 28, ReferenceLeveledInventory = 27, ReferenceExtraActivatingChildren = 26, ReferencePromoted = 25, ActorLifestate = 10, ActorExtraPackageData = 11, ActorExtraMerchantContainer = 12, ActorPermanentModifiers = 23, ActorOverrideModifiers = 22, ActorDamageModifiers = 21, ActorTempModifiers = 20, ActorDispositionModifiers = 19, ActorExtraLeveledActor = 18, ActorExtraDismemberedLimbs = 17, ObjectExtraItemData = 10, ObjectExtraAmmo = 11, ObjectExtraLock = 12, ObjectForceMove = 13, ObjectOpenState = 23, ObjectOpenDefaultState = 22, ObjectEmpty = 21, DoorExtraTeleport = 17, QuestFlags = 1, QuestScriptDelay = 2, QuestStages = 31, QuestScript = 30, QuestObjectives = 29, QuestRunData = 28, QuestInstances = 27, QuestAlreadyRun = 26, TopicSaidPlayer = 30, TopicSaidOnce = 31, RelationshipData = 1, LocationSpecRefs = 28, LocationNewRefs = 29, LocationKeywordData = 30, LocationCleared = 31, NoteRead = 31, CellSeenData = 31, CellDetachTime = 30, CellExteriorChar = 29, CellExteriorShort = 28, CellFlags = 1, CellFullname = 2, CellOwnership = 3, FactionFlags = 1, FactionReactions = 2, FactionVendorData = 30, FactionCrimeCounts = 31, PackageNeverRun = 31, PackageWaiting = 30, BaseObjectValue = 1, BaseObjectFullName = 2, BookTeaches = 5, BookRead = 6, TalkingActivatorSpeaker = 23, ActorBaseData = 1, ActorBaseAttributes = 2, ActorBaseAIData = 3, ActorBaseSpellList = 4, ActorBaseFullName = 5, ActorBaseFactions = 6, NPCSkills = 9, NPCClass = 10, NPCFace = 11, NPCDefaultOutfit = 12, NPCSleepOutfit = 13, NPCBodyScales = 14, NPCRace = 25, NPCGender = 24, CreatureSkills = 9, ClassTagSkills = 1, EncounterZoneFlags = 1, EncounterZoneGameData = 31, QuestNodeRunTime = 31, SceneActive = 31, FormListAddedForm = 31, LeveledListAddedObject = 31, IngredientUse = 31, INRMergeTarget = 31 GetFormChangeFlagAsString(int FormChangeFlag, byte FormType, byte ReturnFriendlyName = 1) (...) if (FormType == 0x41) { ; Actor if (ChangeFormFlags < 0x80001) { if (ChangeFormFlags == 0x80000) { ; 1 << 27 ---> 0x80000 pcVar1 = "CHANGE_ACTOR_DISPOSITION_MODIFIERS"; if (ReturnFriendlyName != False) { pcVar1 = "Disp Modifiers"; } return pcVar1; } if (ChangeFormFlags == 0x400) { pcVar1 = "CHANGE_ACTOR_LIFESTATE"; if (ReturnFriendlyName != False) { pcVar1 = "Life State"; } return pcVar1; } (...) Edited March 25, 2024 by LarannKiar Link to comment Share on other sites More sharing options...
worm82075 Posted March 25, 2024 Share Posted March 25, 2024 5 hours ago, LarannKiar said: (Bit off topic, I decoded the ActorCause struct of Object References (made Set/GetActorCause) recently that might be of interests, tested on a few thrown grenades but I don't know if they actually be used to fix the related known engine bugs, e.g. random hostality by "friendly" grenade exploading after CombatState = 0). Reveal hidden contents FormCreated = 0, FormFlags = 0, ReferenceMove = 1, ReferenceHavokMove = 2, ReferenceCellChange = 3, ReferenceScale = 4, ReferenceInventory = 5, ReferenceExtraOwnership = 6, ReferenceBaseObject = 7, ReferenceExtraLinkRef = 8, ReferenceExtraWorkshop = 9, ReferenceExtraGameOnly = 31, ReferenceExtraCreatedOnly = 30, ReferenceExtraEncounterZone = 29, ReferenceAnimation = 28, ReferenceLeveledInventory = 27, ReferenceExtraActivatingChildren = 26, ReferencePromoted = 25, ActorLifestate = 10, ActorExtraPackageData = 11, ActorExtraMerchantContainer = 12, ActorPermanentModifiers = 23, ActorOverrideModifiers = 22, ActorDamageModifiers = 21, ActorTempModifiers = 20, ActorDispositionModifiers = 19, ActorExtraLeveledActor = 18, ActorExtraDismemberedLimbs = 17, ObjectExtraItemData = 10, ObjectExtraAmmo = 11, ObjectExtraLock = 12, ObjectForceMove = 13, ObjectOpenState = 23, ObjectOpenDefaultState = 22, ObjectEmpty = 21, DoorExtraTeleport = 17, QuestFlags = 1, QuestScriptDelay = 2, QuestStages = 31, QuestScript = 30, QuestObjectives = 29, QuestRunData = 28, QuestInstances = 27, QuestAlreadyRun = 26, TopicSaidPlayer = 30, TopicSaidOnce = 31, RelationshipData = 1, LocationSpecRefs = 28, LocationNewRefs = 29, LocationKeywordData = 30, LocationCleared = 31, NoteRead = 31, CellSeenData = 31, CellDetachTime = 30, CellExteriorChar = 29, CellExteriorShort = 28, CellFlags = 1, CellFullname = 2, CellOwnership = 3, FactionFlags = 1, FactionReactions = 2, FactionVendorData = 30, FactionCrimeCounts = 31, PackageNeverRun = 31, PackageWaiting = 30, BaseObjectValue = 1, BaseObjectFullName = 2, BookTeaches = 5, BookRead = 6, TalkingActivatorSpeaker = 23, ActorBaseData = 1, ActorBaseAttributes = 2, ActorBaseAIData = 3, ActorBaseSpellList = 4, ActorBaseFullName = 5, ActorBaseFactions = 6, NPCSkills = 9, NPCClass = 10, NPCFace = 11, NPCDefaultOutfit = 12, NPCSleepOutfit = 13, NPCBodyScales = 14, NPCRace = 25, NPCGender = 24, CreatureSkills = 9, ClassTagSkills = 1, EncounterZoneFlags = 1, EncounterZoneGameData = 31, QuestNodeRunTime = 31, SceneActive = 31, FormListAddedForm = 31, LeveledListAddedObject = 31, IngredientUse = 31, INRMergeTarget = 31 GetFormChangeFlagAsString(int FormChangeFlag, byte FormType, byte ReturnFriendlyName = 1) (...) if (FormType == 0x41) { ; Actor if (ChangeFormFlags < 0x80001) { if (ChangeFormFlags == 0x80000) { ; 1 << 27 ---> 0x80000 pcVar1 = "CHANGE_ACTOR_DISPOSITION_MODIFIERS"; if (ReturnFriendlyName != False) { pcVar1 = "Disp Modifiers"; } return pcVar1; } if (ChangeFormFlags == 0x400) { pcVar1 = "CHANGE_ACTOR_LIFESTATE"; if (ReturnFriendlyName != False) { pcVar1 = "Life State"; } return pcVar1; } (...) I have observed this same bug triggered by other events. Most often while in workshop mode and setting ownership on an object(ie placing a new object at just the right time after return to combatstate=0). i may be wrong but it seems to me that that game engine is changing AI behavior on the fly using keyword swapping. It probably can't do individual swaps so it has to delete all and repopulate which leaves a split second window where the NPC doesn't have any of the player factions keyed when the aggro event fires. Link to comment Share on other sites More sharing options...
SKKmods Posted March 25, 2024 Share Posted March 25, 2024 That also happens when healing friendly NPC actors like settlers from bleedout caused by actual hostile/enemies ~5% of the time they then turn hostile and aggro the player UNLESS this is called before bleedout recovery ends: (ThisREF as Actor).StopCombat() ;avoid hostility when bleedout ends (ThisREF as Actor).StopCombatAlarm() ;avoid hostility when bleedout ends NPC starts bleedout from combat; is hostile to player = false NPC recovers from bleedout without clearing combat state; is hostile to player = true Link to comment Share on other sites More sharing options...
LarannKiar Posted March 26, 2024 Share Posted March 26, 2024 (edited) Interesting.. these issues seem to be out of the scope of ActorCause then. The bug I was referring to also affects companions, when they throw a grenade in a settlement for example which, if exploads shortly after the combat is over, may make everyone start combat with the player (and the companion who even "hated that"). I personally haven't tried the Simple Offence Suppression mod but I wonder if it fixes any of these already. It overwrites the vanilla faction combat reaction function. (I don't patch vanilla code in Garden of Eden SE so regardless of whether this bug is in the combat AI or caused by broken ActorCause data it won't be fixed "natively" by it anyway, only through Papyrus if that's possible at all). Edited March 26, 2024 by LarannKiar Link to comment Share on other sites More sharing options...
PJMail Posted March 26, 2024 Author Share Posted March 26, 2024 As always, a huge amount of useful information - Thanks! The ChangeForm information does not directly answer my question but does help in me deciding which form types to experiment with (particularly that useful change flag list). Background to my question is I want to find which Form types can have their FormID changed without impact on a Save (because the save does not know about it). Due to a 'simplification' in the way Bethesda implemented ESL it is possible to change a formID outside the ESL range to a valid ESL one without impact on an existing save. However, 16777215 objects does not map into 4095 so inevitably there are some clashes when 2 forms need to be transformed to the same ESL formID. This is where I would like to improve my script to 'sacrifice' (renumber) any FormID that doesn't have to be 'save safe'. (and yes, I know this doesn't help if the mod is using GetFormFromFile in a script - but this will be a 'helper' script for those who really MUST eslify what they can). Link to comment Share on other sites More sharing options...
LarannKiar Posted March 27, 2024 Share Posted March 27, 2024 Ah, I see. I don't have any ideas how to help with this project unfortunately. Even if a form doesn't get changed during the game, any form can be referenced by Papyrus scripts after a save is loaded so they could end up in a save game anytime. For example if a save was created during a Papyrus loop the form was used in as variable, either the form or more likely its bound script object handle would be flushed into the save.. but I don't know, I haven't experimented with these. Link to comment Share on other sites More sharing options...
PJMail Posted March 27, 2024 Author Share Posted March 27, 2024 Changing a mod mid-game will always be risky, and with scripts especially so. I am aiming this project at converting non-scripted mods (or at least ones without persistent scripts) and just seeing how far I can get... Link to comment Share on other sites More sharing options...
DlinnyLag Posted March 28, 2024 Share Posted March 28, 2024 On 3/27/2024 at 4:23 AM, PJMail said: can have their FormID changed without impact on a Save Don't forget about F4SE co-save files %) Link to comment Share on other sites More sharing options...
Recommended Posts