Jump to content

How to get default weapon speed? (base object)


Recommended Posts

Hey there,

i'm working on a mod, which modifies the speed of a weapon for every npc depending on their stats.

Currently i'm facing 2 questions with it:


1. For what is CloneForm used exactly? Currenlty i'm assigning the current Weapon to a ref and just modify its speed later like this:

set CurWeapon to Me.GetEquippedObject 16

....

set CurSpeed to GetWeaponSpeed CurWeapon

....

Me.SetWeaponSpeed NewSpeed CurWeapon

But i heard it's better to use CloneForm for modifying an existing weapon. Is that right and why? I also heard there are memory leaks related to CloneForm (discussed here: https://forums.nexusmods.com/index.php?/topic/300599-cloneform-questions/).

 

2. My second problem is the most important one. Since the script is attached to an object it runs very often and checks for a weapon switch or new stats to calculate the speed from. Main problem is that GetWeaponSpeed CurWeapon also throws the already modified speed. I would love to use / get the default speed for that weapon and calculate from that point.

 

I literally didn't find anything to get default values from any weapon in OBSE or Oblivion Script Language so far. And after searching for 2 days, i only have you guys left here... I'M STUCK!

 

It would also be possible to create a totally fresh and new weapon of that type and add / equip it to the actor, but i have no idea how to do that, i can only copy existing ones -.-

Edited by Kongolan
Link to comment
Share on other sites

The reply by QQuix in the thread you linked explains it pretty well. That is, there are both base objects and object references. Base objects are the blueprints, sort of "abstract ideas" of the object, you cannot see any base objects in the gameworld. Object references are actual, "physical" instances of those base objects that you see the gameworld. For example an iron longsword: there exists one single blueprint of the sword, and each actual sword in the gameworld (for example three swords on a shelf) are instances of that one single blueprint.

 

The problem is that when modifying some values for an object reference, the modifications are performed on the base object and are therefore reflected by all other object references that share the same base object. One example is the SetName function: using the SetName function to change the name of, for example, an NPC reference, results in every single NPC that shares the same base object also getting that new name. This is not an issue for unique NPCs where there is only one instance of each base object, but for example a bandit's name cannot be changed without also changing the name of all other bandits that share the same base object.

 

It says this in the OBSE Command Documentation about SetName function:

 

For most forms the name is part of the base form and changing the name will change it for all instances of the type

 

 

This is why functions that modify values on the base object/form are problematic. Changing a value on the base object results in that value being shared by all instances of that base object. Which is not what you want, obviously, being that you want each individual weapon on each individual NPC to have different values for speed. Assuming speed is a sort of shared value that is modified on the base object.

 

CloneForm clones a base form. It creates a copy of a base form. The copy is separate from the original base form, and can be edited without affecting the original base form. However that does not change anything said above about values on the base form that are shared by all instances of it. It is a workaround. For example:

  • changing the name of one specific bandit bowman reference to "Gottfried" causes the change to take place on the base form, affecting all instances of that bandit bowman - this means that every other bandit bowman the player encounters (that shares the same base form) is also named "Gottfried"
  • because the name is modified on the base form, we need to disconnect the bandit instance from the "common" base form for bandit bowmen to avoid modifying all other bandit bowmen in the process of name change - but each object reference still needs a base form, so it is not possible to simply disconnect the base form from a reference
  • to work around that, a new unique base form would need to be created for our to-be-renamed bandit bowman reference, after which the name of the new unique base form could be changed without affecting any other bandit bowmen
  • CloneForm clones a base form, returning the new base form - however the new base form is saved in the savegame (where else?) - this is a potential source of savegame bloat
  • using CloneForm, we can create a copy of the bandit bowman base form and change the name of the base form copy to "Gottfried"
  • now we may place one "Gottfried" (one instance of that renamed base form copy) in front of the player without affecting any other bandit bowmen that use the original base form ("Gottfried" now uses a copy of the original base form) - however this is a new object, so it was not really a case of "renaming" anything existing, but sort of

If that makes any sense, then that is great. :tongue: CloneForm is a sort of workaround. And while player-created potions, enchanted items and others apparently also are sort of "cloned forms" (new base forms created in-game and saved in the savegame), it might not necessarily always be a good idea to generate potentially thousands of new base forms via scripts. If they really are sall aved in the savegame, then at some point it might start to show in the size of the savegame. Or not. I have not tested it, but then again, I have always avoided using the CloneForm function in the first place. Maybe someone at some point will create a function for deleting cloned base forms, that would be great. But for now, if each one of the cloned forms sticks in a player's savegame forever, then that might be a potential source of savegame bloat at some point, I think.

 

So to change the stats (that are changed on the base form, not on the reference) of one weapon, without affecting all other weapons like it, you would need a new base form for that specific single weapon. CloneForm can create for you a copy of the original base form, a copy that you can edit freely without affecting the original base form (and all weapon references that use it) and then add to the NPC in question and equip it to it (also maybe remove the original weapon from the NPC or otherwise there will be both the original and the cloned one in there). To change stats of each individual weapon on each individual NPC, you might potentially need one new base form for each single weapon with unique stats - or simply each individual weapon out there in the gameworld - depending on how you do it. With some sort of smart tracking system and a database of sorts (using OBSE arrays and stringmaps), you might be able to create a sort of "pool" of cloned forms to pull from, while also substituting any old, irrelevant clones with original game items to free up the "pool" for new clones (to reduce the amount of cloning, by reusing existing cloned base forms from the pool of clones base forms, while actively keeping the size of the pool to a minimum - this might help you keep the amount of cloned forms below one hundred even, maybe under fifty even, or thirty or something, depending on the number of NPCs that need unique stats for their weapons simultaneously).

 

I think the reason you cannot get the original speed (from the original base form) might have something to do with the speed change affecting the original base form, overwriting the original speed. Exiting, then restarting the game should revert the speed of the base object back to default, I think. Maybe just reloading a savegame would also do it. It might be worth testing it. You could save the original value before modifying it, too, to keep it safe somewhere in a variable of some description.

 

As for creating a new weapon and equipping it on the actor - that is what CloneForm is, really: fetch a base form (for example from the currently equipped weapon), create a copy of that base form with CloneForm (a brand new weapon base form), add one instance of that new weapon base form (returned by CloneForm) to the NPC and then equip it. But it has a potential issue - namely the creation of new base forms - which, on the other hand, is all it does. Hehe. :tongue:

 

If you are really going to use CloneForm for what you are describing, you could use IsClonedForm to avoid cloning already cloned forms, if CloneForm can clone cloned forms. Also OBSE arrays and stringmaps for a building a pool of clones, together with a tracking system of some description, to reduce the need for creating new clones by reusing existing ones.

 

Hopefully that helps a bit. If someone spots any oddities, feel free to correct. :blush: Or if someone has ideas on how to avoid CloneForm that would also be great. I cannot think fo anything at the moment. Maybe QQuix or someone else will read this at some point and have a better answer for you.

Link to comment
Share on other sites

Thank you for this super long post. I really wasn't aware that altering the base object means that all references get this reflected change. That explains a lot, why i saw CloneForm used so much. Obviously i was hoping to avoid CloneForm or at least to avoid creating multiple copies of weapons for every character and even for every "set" of stats, but it seems like i can't avoid using a database. This will be some hard work, but for sure doable. Since i'm already experienced in various other programming languages the usage of arrays and sorting mechanismn isn't a problem. My main problem is the leak of understanding Oblivions functions...

 

To change stats of each individual weapon on each individual NPC, you might potentially need one new base form for each single weapon with unique stats - or simply each individual weapon out there in the gameworld - depending on how you do it. With some sort of smart tracking system and a database of sorts (using OBSE arrays and stringmaps), you might be able to create a sort of "pool" of cloned forms to pull from, while also substituting any old, irrelevant clones with original game items to free up the "pool" for new clones.

 

On which base should i sort the arrays? It seems like every weapon type (longsword, claymore or such) will need a own unique cloned form, right? So would it be smart to sort a key map with the name of the weapon and the array of all cloned forms for this weapon type?
Alternatively there are differences between all stats and their NPC, so it will maybe be more useful to just use 1 cloned form per NPC representing his active weapon. But i think i can't change what the weapon is, when i just use CloneForm right? Also how can i store Actor references, by which indicator (are there IDs? Same with WeaponTypes)?
So i will still need one form for every weapon. The reuse of the weapons can indeed be fetched and checked for old or irrelevant clones and then applied to the npc (most of them use the same weapons anyways). But i'm not exactly sure on which criteria i should check this relevance, best indicator would be if the player is in the loaded cell, but i literally can't imagine how to do it.
Also i attach a token with a object script to the npcs, which means it stores the reference for the old and current weapon. What will happen when i alter a cloned form stored in the array, which has a reference in another unloaded npc? Will the changes affect this one and if so will this function call true?
set OldWeapon to CurWeapon
set CurWeapon to Me.GetEquippedObject 16

PrintToConsole "%n" OldWeapon
PrintToConsole "%n" CurWeapon

if (CurWeapon != OldWeapon)
  PrintToConsole "Updating Entered."
  set bDoUpdate to 1
endif

Most important: "substituting any old, irrelevant clones with original game items to free up the "pool""

How can i do this? I still don't get how i can create default references or items and even how to fill them into the arrays. Is it possible to use the name of a item as objectID or how should i get the default of the item stored in CurWeapon?

Me.AddItem CurWeapon 1 ; obviously gives me just the same reference again and not the default one

Sorry for that many questions, most likely the last question is by far the most important.

 

Edit:

Kind of found GetFormIDString, but not sure if it's the correct one, since it will give me another formID for the cloned weapon, right?

Edited by Kongolan
Link to comment
Share on other sites

No problem, I do not mind writing when something is interesting. Now we just need someone to double-check my writings in case there is something fishy in them, I might have forgotten something or otherwise write something that is relatively far from reality. It has happened, but thankfully someone has always been there to correct me. :blush:

 

The GetFormIDString might return only the full eight character string? All form IDs are built of two parts: mod load order index and the actual ID of the item within a mod. For example one form from a mod might have, depending on load order, any of the following values (an example): 010001A2, 040001A2, A30001A2, B70001A2, 160001A2, D60001A2 where the two first ones change depenging on load order (the load order index in what seems to be hexadecimal, from Oblivion.esm at 00 to last possible slot in load order FE with FF being dynamically created references that do not come from any plugin in load order, like spawned bandits or dropped inventory items). So a system working only on form IDs might or might not end up breaking at some point if a user changes load order and the first two characters no longer correspond to the actual current load order (and therefore the current form ID) of a base form. But if cloned base forms always start with "FF" then maybe it might not matter. There is a reason why GetFormFromMod takes two parameters: the plugin name and the form ID within the plugin (six characters, it is possible to supply all eight, but the first two are then ignored according to documentation). Like:

let SomeRef := GetFormFromMod "SomeMod.esp" "0001A2"

It avoids issues with changes in load order, when the plugin name is used for determining the load order index. But it also possible to fetch the plugin name within a script, so you could combine both the plugin name and the form ID, unless you are tracking cloned forms, in which case there probably will not be a plugin name, and the form ID might begin with FF.

 

The substitution of old items from an inventory could maybe work... hmm... now that is a good question! :tongue: Depends on when an item needs to be reverted back to the original one. When an NPC with the item dies? When the item ends up in the player's inventory? When the NPC is alive, but too far from the player to be of any significance? Something else? In Skyrim, there exists a function to query if the 3D presentation of an object reference is loaded, but I have not found that in Oblivion, so building the actual substitution system might be interesting.

 

I am currently on my laptop so I cannot do any actual testing for now, but OBSE added the array_var variable type that can be "Array", "Map" or "StringMap". There are examples and explanations in the OBSE Command Documentation. For example by prepending the underscore to the function name (also in the OBSE docs), working with arrays becomes a lot more pleasant. An untested OBSE user-defined function that should compile and work (I hope):

scriptname SomeTestFunction
 
array_var TempMap
array_var IterMap
 
begin _Function {}
 
    ; ar_Map creates a StringMap with the key-value pairs supplied
    let TempMap := ar_Map "PlayerHere"::( PlayerRef ), "PlayerGoldAmount"::( PlayerRef.GetItemCount F )
 
    ; dump to in-game console for inspection
    ar_Dump TempMap

    ; remove one key-value pair
    ar_Erase TempMap "PlayerHere"
    ar_Dump TempMap
 
    ; ar_List creates an Array, a simple list, of the things supplied
    let TempMap["ArrayHere"] := ar_List "Wololololo", "green bananas", "sometest"
 
    ; it is possible to assign, for example, a StringMap within a StringMap
    let TempMap["SubMap"] := ar_Construct "StringMap"  ; <-- simple construction of empty StringMap
    let TempMap["SubMap"]["A"] := GetHighActors  ; <-- returns an array of current high AI processing level actors
    let TempMap["SubMap"]["B"] := PlayerRef.GetEquippedObject 16
 
    PrintToConsole "the map itself"
    ar_Dump TempMap
 
    foreach IterMap <- ( ar_Keys TempMap )
        PrintToConsole "Key = " + $( IterMap["value"] )
        ar_Dump TempMap[IterMap["value"]]  ; should work, if I remember correctly
    loop
 
    ; clean up the TempMap, IterMap should already be ar_Null according to documentation
    ; as it is only ever used in the foreach loop as an iterator
    let TempMap := ar_Null
 
end

Cleaning up is always the tricky part. It says that OBSE automatically deletes arrays that are not referenced, so one would assume it also cleans up all arrays within the unreferenced arrays. But string_vars are (apparently, in the current OBSE version, from what I have heard) not automatically cleaned up, and I have no idea if strings within arrays are cleaned up automatically. I hope they are, otherwise I will need to add some cleaning to my mods and working with StringMaps would become less fun.

 

GetEquippedObject already returns the base form. Based on the wiki page for CloneForm, you might be able to use it like this:

ref OriginalBaseForm
ref ClonedBaseForm
 
...
 
let OriginalBaseForm := PlayerRef.GetEquippedObject 16
 
PrintToConsole "original base: %n (%i)" OriginalBaseForm OriginalBaseForm
 
if ( OriginalBaseForm )
    let ClonedBaseForm := CloneForm OriginalBaseForm
endif

PrintToConsole "cloned base: %n (%i)" ClonedBaseForm ClonedBaseForm
 
if ( ClonedBaseForm )
    SetName "Glamdring" ClonedBaseForm
    PlayerRef.AddItem ClonedBaseForm 1
    PlayerRef.EquipItem ClonedBaseForm
endif
 
PrintToConsole "cloned base: %n (%i)" ClonedBaseForm ClonedBaseForm

And with the cloned and normal base forms you could build a tracking system. Maybe also NPC data and OBSE event handlers (for tracking NPC deaths and/or other events, if you do not want to keep polling everything every frame with a token or somesuch). But it would end up being an extremely complex and potentially error-prone system.

 

Hopefully that answers some of your questions. Maybe someone else can provide more info, correct any mistakes I might have made, or maybe even have a better idea on how to adjust weapon speeds! Or provide some opinions of whether anything actually makes sense.

 

I have never thought about weapon speeds for NPCs myself when playing, I have always been too busy running away, throwing spells at the enemies. Hehe. :D

Link to comment
Share on other sites

Hi - just thinking out loud here..

 

I wonder if this can be achieved without CloneForm, by applying an enchantment to the weapon?

I can't see from the OBSE documentation, so I'm not sure if

<ref>.SetEnchantment <whatEnchantment>

affects that specific reference, or the base object, which would leave you back at square one...

 

But if it only applies to that specific reference, you might be able to achieve the sort of powerup you're looking for with an enchantment.

If that does work, you could set a huge charge, with only a one-point usage per hit, so it wouldn't run out on the NPC anytime soon.

You could even maintain your database of who has what, and periodically charge the weapons up.

 

Just a thought, and probably not a good one!

 

@Contrathetix

Nice detailed explanation on the CloneForm thing - thanks, it clarified some things for me.

Incidentally, I'm having a major CloneForm problem myself in a mod I'm working on, and I'll be posting a thread here in hope of a solution.. but that's a whole other thing.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...