Project

General

Profile

Bug #4055

Local scripts don't inherit variables from their base record.

Added by Greatness 7 3 months ago. Updated about 2 months ago.

Status:
Confirmed
Priority:
Normal
Assignee:
-
Category:
Scripting
Target version:
Start date:
09/01/2017
% Done:

0%

Reproducibility:
Always
Operating system:
Other
Severity:
Normal

Description

A useful feature of the vanilla engine is that newly spawned objects with local scripts will inherit the values of variables defined in their script's base record. This is not currently working in OpenMW and is causing some mods to seriously malfunction.

Easy way to see the bug via console commands:

set chargendagger.done to -123
placeatpc "chargen dagger" 1 0 0
# click the dagger that was spawned
showvars
# vanilla: done == -123
# openMW: done == 0

History

#1 Updated by scrawl . 3 months ago

I had no idea that base record stores values. Why would this be a useful feature though? Conditionally changing the initial variables of scripts sounds like an accident waiting to happen. Why not do it in the script itself, isn't that more readable and predictable?

Do we have a list of mods depending on this?

#2 Updated by Greatness 7 3 months ago

scrawl . wrote:

I had no idea that base record stores values. Why would this be a useful feature though? Conditionally changing the initial variables of scripts sounds like an accident waiting to happen. Why not do it in the script itself, isn't that more readable and predictable?

Do we have a list of mods depending on this?

Sorry, no list on hand, I only recently became aware it wasn't supported in OpenMW. It's quite an old trick and used in many mods. The "script.var" version less so, but modifying base records has a long history.

Usually the reason a modder would utilize this feature is that doing something equivalent in a local script is either impossible or very inefficient.

For example, say after a certain event you want all cliff racers to avoid attacking the player. Using this method it's very simple and efficient, you just do SetFight 0 on the base record and you're done. Elegant implementation, just one line, and no local scripts needed at all. For something as common as a cliffracer those would've added significant bloat to the save file and even been detrimental to FPS with the common swarms.

Another use I've seen is to allow unique scripted items with important variables to be transfered to other NPCs or containers. The script could simple copy its locals straight into its own base script, and then when RemoveItem->AddItem is called, the item is safely transferred to its new owner with the variables all intact, not a single global was needed.

It's really just a nice feature to have, I'm kinda surprised it hadn't been brought up before.

#3 Updated by Marc Zinnschlag 3 months ago

Is this data actually stored in the base record? Looks to me more like the value is stored in the local variable of a global script and then when creating an instance that uses this script as a local script the variables of this instance are set to the one of the global script.

#4 Updated by Greatness 7 3 months ago

Marc Zinnschlag wrote:

Is this data actually stored in the base record? Looks to me more like the value is stored in the local variable of a global script and then when creating an instance that uses this script as a local script the variables of this instance are set to the one of the global script.

A global script is the base record. Any time a global script is executed it will store the current variables in the the base SCPT record, aka the one that loaded last -> the one from your save file.

This is different than local scripts, which store their variables alongside the instance in what ever record is containing them (cell/inventory/etc).

And yes, importantly, this is not restricted to scripts. It works the same for all the various attributes that a base record can have. You can for instance do ["cliff racer"->SetFlee 100], then [PlaceAtPC "cliff racer" 1 0 0] multiple times, and they would all show [GetFlee == 100]. An important caveat though, if an instance of the base record is already currently loaded, the game will prefer to modify that instance rather than the base record.

#5 Updated by Miroslav Remák 3 months ago

Regarding SetFight behavior, there is another issue for that: https://bugs.openmw.org/issues/2798

Do you happen to know if there are other instructions that override base records besides:
  • Set/Mod Fight/Flee/Alarm/Hello
  • AddToLevItem, AddToLevCreature (and remove variants)

#6 Updated by Greatness 7 3 months ago

Miroslav Remák wrote:

Regarding SetFight behavior, there is another issue for that: https://bugs.openmw.org/issues/2798

Do you happen to know if there are other instructions that override base records besides:
  • Set/Mod Fight/Flee/Alarm/Hello
  • AddToLevItem, AddToLevCreature (and remove variants)

Ah, it may be best to merge this report with those others then - they're all the same issue.

As for other instructions: Those in the NPDT subrecord, such as skills and stats, don't allow this method. I assume because that subrecord may not exist in the base record depending on the flags assigned (Autocalc). Ideally OpenMW could allow editing these as well though.

As for those that do work that you hadn't already mentioned: The big one is inventory contents, which has many uses as you can pack items with local scripts into the base record, allowing events to trigger on spawn without needing to overwrite/conflict with attached scripts from other mods. SetHealth works as well (from scripts, but not from the console), used sometimes to allow dynamic quests which weaken the final boss/minions before you get to them. SetLevel and SetRepution also work, with the latter being an interesting case as IIRC it is actually defined by autocalc too.

#7 Updated by Marc Zinnschlag 3 months ago

  • Status changed from New to Confirmed
  • Target version set to openmw-1.0
  • Operating system changed from Windows to Other

A global script is the base record. Any time a global script is executed it will store the current variables in the the base SCPT record, aka the one that loaded last -> the one from your save file.

That is not how OpenMW works. In OpenMW records are generally immutable. And a global script is not the same as the script record.

It is unfortunate that we did not know about this behaviour (much) earlier. I don't think there is a simple general solution to the wider issue, though the script part can easily be dealt with. So best not to merge the issues.

#8 Updated by Marc Zinnschlag 3 months ago

You can for instance do ["cliff racer"->SetFlee 100], then [PlaceAtPC "cliff racer" 1 0 0] multiple times, and they would all show [GetFlee == 100]. An important caveat though, if an instance of the base record is already currently loaded, the game will prefer to modify that instance rather than the base record.

Uh, big problem. OpenMW's idea of not loaded is very different from Morrowind's. OpenMW generally does not make a difference between loaded and not loaded references (except for prioritising). If there is a single cliff racer in the entire world "cliff racer"->SetFlee 100 would always find and modify this cliff racer.
Besides, making scripts working differently depending on if something is loaded or not is just asking for trouble, so we definitely will not replicate this "feature".

#9 Updated by scrawl . 2 months ago

So there can be a global script and a local script of the same ID at the same time? Confusing.

the script part can easily be dealt with.

Should we, though? Inheriting values from an unrelated instance sounds like the kind of behavior that no one would expect and would trip you up.

Can we get some examples of mods that are 'seriously malfunctioning' as a result of this so we can better judge whether or not to implement?

#10 Updated by Randy Davin about 2 months ago

I think the vanilla implementation is outdated and cause limitations, if we are to make more flexible scripts in the future. Modern games have extra functions to handle this type of issue. We can introduce new functions in the future to deal with this.

Instead of using "cliff racer"->setflee 100 to affect all cliff racers

We can use:
GetUnitsOfTypeIdAll("cliff racer")->setflee 100

I know its a bad example, but something like that.

Also available in: Atom PDF