RedWood Elf and RandomNoob, thank you very much for your help!
Below you can see the scripts for an initial version that's working more or less. I'd be glad if you could have a look & comment. And I have still some quest related questions.
The first script is attached to a magic effect; it looks for non depleted ore veins in the player's vicinity. (The search radius expands with (concentration) spell time, starting fairly low, so in effect one emulates looping through ever more distant candidates.)
Quest relevant are the last 3 lines (Quest: Start/SetActive, Alias:ForceRefTo)
Spoiler Scriptname EK_FindOreVein extends ActiveMagicEffect FormList Property EK_VanillaOreVeinList AutoQuest Property EK_MiningQuest AutoReferenceAlias Property EK_OreVeinAlias AutoGlobalVariable Property EK_GlobMiningDistanceMin AutoGlobalVariable Property EK_GlobMiningDistanceRPS Autofloat TUpdate = 0.2float StepSizefloat ActDistEvent OnEffectStart(actor akTarget, actor akCaster) float MiningDistanceMin = EK_GlobMiningDistanceMin.GetValue() as float float MiningDistanceRPS = EK_GlobMiningDistanceRPS.GetValue() as float StepSize = MiningDistanceRPS * TUpdate ActDist = MiningDistanceMin RegisterForSingleUpdate(TUpdate)EndEventEvent OnUpdate() Actor Player = Game.GetPlayer() ; step forward distance ActDist = ActDist + StepSize ; find random list member (may be empty, already!) within distance ObjectReference OreVeinRef = \ Game.FindRandomReferenceOfAnyTypeInListFromRef(EK_VanillaOreVeinList, \ Player, ActDist) EK_VanillaOreVeinList.Revert() ; no vein at all -> try again if ( OreVeinRef == none ) RegisterForSingleUpdate(TUpdate) return endif if ( (OreVeinRef as MineOreScript).ResourceCountCurrent >= 0 ) ; on non-full vein try again RegisterForSingleUpdate(TUpdate) return endif; ; vein is full (ResourceCountCurrent < 0) EK_MiningQuest.Start() EK_MiningQuest.SetActive(True) EK_OreVeinAlias.ForceRefTo(OreVeinRef)endEvent
The second script is attached to the objective. I tried to fit all functionality into one script, not using stages (other than startup and shutdown) or papyrus fragments attached to them (reason: I like to have all relevant coding in one place, not in several GUI-windows like the fragments).
Spoiler Scriptname EK_MarkOreVein extends QuestReferenceAlias Property EK_OreVeinAlias AutoGlobalVariable Property EK_GlobMiningTimeOut AutoEvent OnUpdate() Debug.Notification("Root - OnUpdate") GoToState("Idle")EndEventState Idle Event OnUpdate() RegisterForSingleUpdate(2.0) Debug.Notification("Idle - OnUpdate") if IsRunning() GoToState("Waiting") endIf EndEventEndStateState Waiting ; wait for something to fill EK_OreVeinAlias Event OnUpdate() Debug.Notification("Waiting - OnUpdate") if EK_OreVeinAlias.GetRef() ; display objective SetObjectiveDisplayed(10, True, True) Debug.Notification("ObjectiveDisplayed") ; got to state timeout GoToState("Timeout") float TimeOut = EK_GlobMiningTimeOut.GetValue() as float RegisterForSingleUpdate(TimeOut) else ; landing here after the completion of the first objective Debug.Notification("no ObjectiveDisplayed") SetObjectiveDisplayed(10, False) RegisterForSingleUpdate(2.0) endif EndEventEndStateState Timeout Event OnUpdate() Debug.Notification("TimeOut - OnUpdate") ; clear alias EK_OreVeinAlias.Clear() ; deactivate quest, clear current entry from log EK_OreVeinAlias.Clear() SetActive(False) CompleteAllObjectives() SetObjectiveDisplayed(10, False) ; prepare for next call GoToState("Idle") RegisterForSingleUpdate(2.0) EndEventEndState
Quest flow and some harmless questions first:
- The quest seems to be called after game load, I get notifications 'Root - OnUpdate' (empty state, I guess), then in a loop 'Idle - OnUpdate', even though I did not register for update, explicitely. Is this done automatically?
- When the magic effect starts the quest for real I get again 'Root - OnUpdate', then once 'Idle - OnUpdate', propagated to 'Waiting - OnUpdate'. This is what I intended.
- The objective is set, journal entry and map marker appear, and after a duration EK_GlobMiningTimeOut the script goes to 'TimeOut - OnUpdate', thus removing marker and journal entry, just as intended.
I can stop the quest in the TimeOut state but I did not succeed in re-starting it (tried reset in the quest script, SetStage anywhere or StartQuest in the magic effect script does nothing, either), even though I set 'allow repeated stages'.
@RedWoodElf: What did you mean with 'make the quest repeatable'?
Because of this the scripted solution does not stop the quest but puts it into state 'Waiting'. This way the magic effect script can assign a new value to the OreVeinAlias. The StartQuest on a quest still running doesn't seem to hurt....
So this seems to work after a fashion but I'd like a cleaner solution that, if possible,
- prevents the quest from looping in state "Idle" before even started explicitely
- and does a clean stop and re-start instead of having it looping in state 'Waiting'.
Still I'd like to have all the functionality in one script only (I did not find OnStage blocks in the Wiki but read somewhere that the papyrus fragments associated with a stage use this keyword?).
@RedWoodElf: I did not understand your words
(after the second "Don't edit anything between these comments" comment). Are those comments from an auto-generated papyrus fragment?
Thanks again!
Edit: Hmmm, something else is fishy. I wanted to test this with a second character B using a savegame made before I added this quest (let's call it SAVE_B_1 for later reference).
Here the quest script gives no debug notification at all. Maybe the savegame of the character A I used for development now contains some remains from discarded attempts, like the OnInit blocks I had once but then decided they did no good?
Another Edit: This is tricky. I made a new save of the character B (SAVE_B_2) and added an OnInit Block, so the script quoted above now starts with
Spoiler ; run once when the script is initialized and on reset or quest startEvent OnInit() Debug.Notification("Empty - OnInit") RegisterForUpdate(2) GotoState("Idle")EndEventEvent OnUpdate() Debug.Notification("Empty - OnUpdate") RegisterForUpdate(2) GotoState("Idle")EndEvent
This had
no effect at all when I restarted Skyrim with the savegame SAVE_B_2 just made, the quest script still wouldn't give any messages!
But when I loaded the earlier save SAVE_B_1, the quest and spell worked as with character A.
So it seems that the answer to my question why the quest had been looping in state 'Idle' without having been explicitely initialized is: the initialization had been carried over from the savegame and still works even if the quest in the current form would not have been able to start it at all.
Maybe all this inconsistencies could be addressed once I learn to stop and restart the quest cleanly?