Jump to content

[LE] Need help advancing quest stage upon item pick up


Recommended Posts

7 hours ago, xkkmEl said:

Many ways of doing it.

Here's one:

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)

	If akNewContainer == PlayerREF && playerREF.getItemCount( ItempieceX) &&  playerREF.getItemCount( ItempieceY) &&  playerREF.getItemCount( ItempieceW) &&  playerREF.getItemCount( ItempieceZ)

		MyQuest.SetStage(10)

	EndIf

EndEvent

 

 

ok so i tried this, kind of. for me i ended up writing it like this:

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)

If akNewContainer == Game.GetPlayer().GetItemCount(Alias_PieceX.GetRef()) && Game.GetPlayer().GetItemCount(Alias_PieceY.GetRef()) && Game.GetPlayer().GetItemCount(Alias_PieceW.GetRef()) && Game.GetPlayer().GetItemCount(Alias_PieceZ.GetRef()) >= 1
  SetStage(10)
EndIf

EndEvent

 

but now i get 2 errors, 1. "No Variable alternative at input 'event' " and 2. "missing EOF at 'EndFunction' " (the function is basically a fragment of the quest im creating).

 

what am i doing wrong ?

Link to comment
Share on other sites

I would say, about everything.   You call SetStage(10) directly, does it mean you are trying to put this into your Quest script?   A quest does not receive and OnContainerChanged.  I am pretty sure I was writing a response to this thread, but my response somehow disappeared.

I would say the easiest option would be to do this through script attached to player alias.    You make player an alias in your quest, and attach a script to that alias.   You also create a formlist, and add the 4 pieces to be collected into this formlist.    The assumption here is that each quest piece is a unique form, with only one reference of it existing in the world. 
 

ScriptName Weaponx_PlayerAliasScript extends ReferenceAlias

FormList Property Weaponx_PartsList Auto ; this is filled with the formlist in the properties
int PartsCount = 0

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
  if PartsCount < 4
    if WeaponX_PartsList.HasForm(akBaseItem)
      PartsCount += 1
      if PartsCount == 4
        GetOwningQuest().SetStage(10)
      endif
    endif
  endif
EndEvent

This is a bit simplistic as technically player can remove and add the same piece several times...    unless you flag them as quest items I guess, but still: here is an improved variant:
 

ScriptName Weaponx_PlayerAliasScript extends ReferenceAlias

FormList Property Weaponx_PartsList Auto ; this is filled with the formlist in the properties

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
  if GetOwningQuest().GetStage() < 10
    if WeaponX_PartsList.HasForm(akBaseItem)
      int i = 0
      bool success = true
      while i < WeaponX_PartsList.GetSize()
        if GetRef().GetItemCount(WeaponX_PartsList.GetAt(i)) == 0
        success = false
        i += 1
      endwhile
      if success
        GetOwningQuest().SetStage(10)
      endif
    endif
  endif
EndEvent

 

Link to comment
Share on other sites

If you want to use OnContainerChanged, you need to do it in a script attached to the alias for the quest items themselves.   So in your quest, you add aliases for the weapon piece references placed in the world, and attach the same exact script to each.

ScriptName WeaponX_PartAlias extends ReferenceAlias

Actor Property PlayerRef Auto ; populate with player reference
Bool Property isObtained Auto ; populate with false

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
  if PlayerRef == akNewContainer as Actor
    isObtained = true
    (GetOwningQuest() as WeaponX_QuestScript).CheckPartsList()
  endif
EndEvent

And then, assuming you have a script named 'WeaponX_QuestScript' attached to your quest, in that script you add a function:

ReferenceAlias Property Part1Alias Auto
ReferenceAlias Property Part2Alias Auto
ReferenceAlias Property Part3Alias Auto
ReferenceAlias Property Part4Alias Auto

Function CheckPartsList()
  if ((Part1Alias as WeaponX_PartAlias).isObtained && (Part2Alias as WeaponX_PartAlias).isObtained && \
      (Part3Alias as WeaponX_PartAlias).isObtained && (Part4Alias as WeaponX_PartAlias).isObtained)
    SetStage(10)
  endif
EndFunction


 

Link to comment
Share on other sites

On 1/25/2025 at 6:57 PM, scorrp10 said:

If you want to use OnContainerChanged, you need to do it in a script attached to the alias for the quest items themselves.   So in your quest, you add aliases for the weapon piece references placed in the world, and attach the same exact script to each.

ScriptName WeaponX_PartAlias extends ReferenceAlias

Actor Property PlayerRef Auto ; populate with player reference
Bool Property isObtained Auto ; populate with false

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
  if PlayerRef == akNewContainer as Actor
    isObtained = true
    (GetOwningQuest() as WeaponX_QuestScript).CheckPartsList()
  endif
EndEvent

And then, assuming you have a script named 'WeaponX_QuestScript' attached to your quest, in that script you add a function:

ReferenceAlias Property Part1Alias Auto
ReferenceAlias Property Part2Alias Auto
ReferenceAlias Property Part3Alias Auto
ReferenceAlias Property Part4Alias Auto

Function CheckPartsList()
  if ((Part1Alias as WeaponX_PartAlias).isObtained && (Part2Alias as WeaponX_PartAlias).isObtained && \
      (Part3Alias as WeaponX_PartAlias).isObtained && (Part4Alias as WeaponX_PartAlias).isObtained)
    SetStage(10)
  endif
EndFunction


 

i think this is the one for me. thank you for giving 2 full responses to my question, this one in particular is definitely the one for me, because i have already created an alias for each weapon piece and was using "Game.GetPlayer().GetItemCount(Alias_WeaponPieceX.GetRef()) >= 1" for each of the pieces earlier in the quest script.

 

now with your help i know how to put the Aliases to good use again for this stage of the Quest. i've also already created a secondary script to aid with the main one, as another stage of the quest needed it so i can just add the second part to the secondary script.

 

one last question though just to be 100% sure, do i need to add the first part to my main Quest script, ie: QF_MyQuest_XXXXXXXX and the second part to the secondary script that lets say i named: MyQuestScript and then go back to the main quest script and add the function (kind of like Mehrunes Razor quest script) ? or can i just drop both in the main script ?

 

and if the latter is the case, which part should go inside the Stage 10 fragment of the script ? i need to know this because i'm editing the script in Notepad++ not inside creationkit itself.

 

thank you for the thorough answer, i know many others that have the same issue and are looking for someone with more technical knowledge to help them out regarding this, and your answer might've cracked the code for many people.

Edited by ThalmorJusticiar7th
Link to comment
Share on other sites

No, the first part is the script you attach to the aliases for each weapon piece, NOT put in the quest.   It is the weapon pieces for which the container changes, therefor it is they (and their aliases) that receive the event.   

As to scripts attached to the quest:   The QF_MyQuest_XXXXXXXX script is the one auto-generated by CK when you add fragments to the quest stages.    I am of opinion that it is best reserved for fragments only, and you should not edit it directly.

For all the other stuff, you add one more script to the quest, and it is a good idea to flag it as conditional, so you can put conditional properties into it.    All the additional code needed for the quest can go there. 

 

Link to comment
Share on other sites

36 minutes ago, scorrp10 said:

No, the first part is the script you attach to the aliases for each weapon piece, NOT put in the quest.   It is the weapon pieces for which the container changes, therefor it is they (and their aliases) that receive the event.   

As to scripts attached to the quest:   The QF_MyQuest_XXXXXXXX script is the one auto-generated by CK when you add fragments to the quest stages.    I am of opinion that it is best reserved for fragments only, and you should not edit it directly.

For all the other stuff, you add one more script to the quest, and it is a good idea to flag it as conditional, so you can put conditional properties into it.    All the additional code needed for the quest can go there. 

 

ah got it. so basically it can go in the main script but you're saying its best i make a new one which i've already done and made several of them so it wouldn't overcrowd the main quest script.

 

basically you were trying to explain how the event and the function go, but both parts can be written in a single script like the most common scripts,

 

basically:

Referrence A property X auto

Referrence B property Y auto



Event X

If

EndIf

Endevent



Function X

If

EndIf

EndFunction

 

 

all in one script, right ?

 

Edit: i've actually checked, and the creationkit created a reference quest for all the weapon aliases because i've already created an script for a prior stage of the quest to check if each piece is added into my inventory and upon receiving them, mark a quest objective complete, (copied the concept from mehrunes razor quest). i can just add the first part of your script there and add the references and the function to my secondary script for the Quest.

 

thank you for explaining this. i'll implement it and then try this in game and see how it goes.

Edited by ThalmorJusticiar7th
Link to comment
Share on other sites

No.   Papyrus is Object-Oriented.   A given script attaches to (extends) a particular object, be it a quest, an actor, or whatever.    So the handler for an event specifically goes into the script that attaches to an alias.   As in, you got to quest aliases tab of the quest, double-click an alias for a piece, and there is a place there to add script(s).

About the decision as to where to add the script:   

You can attach a script to the base item, and if this script extends ObjectReference, it will automatically apply to every instance of said item placed in the world.    Typical example: a book that starts a quest (I.e. Legend or Red Eagle).   Quite a few of those are scattered around Skyrim, and quest is started by any copy.   

You can attach a script to a specific item reference placed in the world.   For example a secret door that opens when you sit on a specific chair.    You might have a dozen instances of the same base chair item in the house, but  only one of them will open the door since the script is attached to that chair reference only.  If an item is unique - that is, only one reference exists in the world - it does not really matter is you attach the script to the base item or the reference.

When a script is attached to an alias, it will only work when the owning quest is running and the alias is filled.   For example, the  'Lost to the Ages' quest (the Aetherium Forge).   You might find and pick up the crest pieces before you start the quest and nothing happens, but when you are doing the quest, collecting the piece will cause Katria to appear and comment on your progress.   If the script has to do with advancement of a quest, it is generally considered the cleanest to attach the script to an alias.  Heck, consider the radiant  'numbers' quest by Thieve's Guild where you need to mess with a business ledger somewhere.   There are many such ledgers in the world, but the quest advancing script runs when you mess with the one ledger that fills the quest alias.

PartsList from my first response is a FormList containing all four weapon piece forms.    It is just a convenience thing.  With this method, code is pretty much the same whether your quest calls for finding four or forty items.

Link to comment
Share on other sites

12 minutes ago, scorrp10 said:

No.   Papyrus is Object-Oriented.   A given script attaches to (extends) a particular object, be it a quest, an actor, or whatever.    So the handler for an event specifically goes into the script that attaches to an alias.   As in, you got to quest aliases tab of the quest, double-click an alias for a piece, and there is a place there to add script(s).

About the decision as to where to add the script:   

You can attach a script to the base item, and if this script extends ObjectReference, it will automatically apply to every instance of said item placed in the world.    Typical example: a book that starts a quest (I.e. Legend or Red Eagle).   Quite a few of those are scattered around Skyrim, and quest is started by any copy.   

You can attach a script to a specific item reference placed in the world.   For example a secret door that opens when you sit on a specific chair.    You might have a dozen instances of the same base chair item in the house, but  only one of them will open the door since the script is attached to that chair reference only.  If an item is unique - that is, only one reference exists in the world - it does not really matter is you attach the script to the base item or the reference.

When a script is attached to an alias, it will only work when the owning quest is running and the alias is filled.   For example, the  'Lost to the Ages' quest (the Aetherium Forge).   You might find and pick up the crest pieces before you start the quest and nothing happens, but when you are doing the quest, collecting the piece will cause Katria to appear and comment on your progress.   If the script has to do with advancement of a quest, it is generally considered the cleanest to attach the script to an alias.  Heck, consider the radiant  'numbers' quest by Thieve's Guild where you need to mess with a business ledger somewhere.   There are many such ledgers in the world, but the quest advancing script runs when you mess with the one ledger that fills the quest alias.

PartsList from my first response is a FormList containing all four weapon piece forms.    It is just a convenience thing.  With this method, code is pretty much the same whether your quest calls for finding four or forty items.

right. thanks for explaining this. i already knew that Aliases are used to ensure only this specific instance of the item is modified through the quest script instead of the base item, and i need to add the first part of the script inside the Alias's Script box. your original explanation here:

Quote

If you want to use OnContainerChanged, you need to do it in a script attached to the alias for the quest items themselves.   So in your quest, you add aliases for the weapon piece references placed in the world, and attach the same exact script to each.

threw me off, hence why i asked whether you're saying if i should add it to the FormID or the Alias. of course its safest to put it on Alias and actually the reason why we make the Aliases in the first place. but it's even better to get the confirmation now that you've explained it.

 

as for Formlists i know what they're for. already created one. your original explanation was very thorough, so you didn't need to follow up, but thanks for explaining the basics, i'm sure many will get better familiarized with these concepts when they see your explanation here and it'll be a refresher for me as well.

 

if you see the "Edit:" part of my previous post you'll see that i actually found out that i've already created an oncontainerchanged script for the Aliases through writing the code for another part of the Quest, so i'll just be adding your script inside that one and autofill the properties.

 

thank you for all the aid and added follow ups. i'll try this all this afternoon and i imagine there would be no issues, but if there are, i'll be reporting back 😉

Link to comment
Share on other sites

  • 2 weeks later...
On 1/29/2025 at 9:54 AM, scorrp10 said:

No.   Papyrus is Object-Oriented.   A given script attaches to (extends) a particular object, be it a quest, an actor, or whatever.    So the handler for an event specifically goes into the script that attaches to an alias.   As in, you got to quest aliases tab of the quest, double-click an alias for a piece, and there is a place there to add script(s).

About the decision as to where to add the script:   

You can attach a script to the base item, and if this script extends ObjectReference, it will automatically apply to every instance of said item placed in the world.    Typical example: a book that starts a quest (I.e. Legend or Red Eagle).   Quite a few of those are scattered around Skyrim, and quest is started by any copy.   

You can attach a script to a specific item reference placed in the world.   For example a secret door that opens when you sit on a specific chair.    You might have a dozen instances of the same base chair item in the house, but  only one of them will open the door since the script is attached to that chair reference only.  If an item is unique - that is, only one reference exists in the world - it does not really matter is you attach the script to the base item or the reference.

When a script is attached to an alias, it will only work when the owning quest is running and the alias is filled.   For example, the  'Lost to the Ages' quest (the Aetherium Forge).   You might find and pick up the crest pieces before you start the quest and nothing happens, but when you are doing the quest, collecting the piece will cause Katria to appear and comment on your progress.   If the script has to do with advancement of a quest, it is generally considered the cleanest to attach the script to an alias.  Heck, consider the radiant  'numbers' quest by Thieve's Guild where you need to mess with a business ledger somewhere.   There are many such ledgers in the world, but the quest advancing script runs when you mess with the one ledger that fills the quest alias.

PartsList from my first response is a FormList containing all four weapon piece forms.    It is just a convenience thing.  With this method, code is pretty much the same whether your quest calls for finding four or forty items.

hi there. i just wanted to say that with your help i was able to completely script out this part of the quest and get it to work in game. thank you for your help on this one.

 

there's only one last stage (well 3 separate ones really) to my quest. i'll describe it as shortly as i can and wait to see if you can help me out with this part as well:

 

in this stage, i have created a fake soul gem (misc item) that is supposed to get filled once when i kill a giant, then a dragonpriest and then a dragon. everytime i kill one of them, i want the game to queue the absorb soul effect (like the soul absorb enchantment on weapons), and then remove the current version of the misc item from my inventory and add the new version of it.

 

i was thinking something like adding an onkill event and function, but i do not know what properties i need to add, or for the soul absorb effect playing, how should i even make that happen, when i am not actually using a soul gem ?

 

any help will be appreciated. thanks

 

Link to comment
Share on other sites

Ok, thoughts:

You want a script flagged 'Conditional' attached to your quest, (lets call it SpecialSoulGemQuestScript) and define 3 boolean conditional properties: CapturedDragon(Giant/DragonPricest)Soul, 
Create a 'SpecialSoulTrapKeyword' keyword.
Create a SpecialSoulTrap spell that is a duplicate of 'SoulTrap'   More on this later.

Detection of combat:    Make sure your quest priority is higher than 50.  Take a look at 'CreatureDialogueGiant' quest.  In 'Combat' tab it has a (Hit) item.    Duplicate it in YOUR quest's Combat section, but you probably want to add a condition that subject's combat target is player, and a GetVMQuestVariable condition that checks 'CapturedGiantSoul' is false (0), and a  condition 'HasMagicEffectKeyword'  for 'SpeialSoulTrapKeyword' is 0.
Create 'Hit' category TopicInfos for  Dragon and Dragon priest as well (see their respective CreatureDialogue quests)
To all 3 topicinfos  add a script that has SpecialSoulTrap(Spell)  and PlayerRef(Actor) properties, and goes like:    SpecialSoulTrap.Cast(PlayerRef, akSpeaker)

What the above accomplishes: when a member of giant, dragon or dragonpriest  race is hit in combat, and their target is player, and a respective soul for your quest is not yet collected, and they don't have this effe3ct already, it will have SpecialSoulTrap spell cast on them by player.

Now, more about this spell - you likely want to make it a longer range (100ft) and check 'Disallow Absorb/Reflect' and 'Ignore Resistance' boxes.  Maybe change perk to ConjurationNovice00.

You will see that this spell has a single effect item: Soul Trap, and its effect is 'SoulTrapFFActor', 1 minute duration.  Go ahead and create a duplicate of this MagicEffect, call it 'SpecialSoulTrapFFActor'.  Change the SpecialSoulTrap spell to use this effect instead.

Now, the effect itself:   You likely want to remove all of its conditions, change Minimum skill level to 0, and change base cost and skill usage mult to 0 as well.  Uncheck Hostile, Recover, FX Persist boxes, Check 'NoHit Effect'.   In Keywords, add  SpecialSoulTrapKeyword.   In Target Conditions, remove all conditions.  In Visual Effects and Sounds sections, You want everything set to 'None'.

This effect has two scripts attached:  magicsoultrapfxscript and SayOnHitByMagicEffectScript.    The second one, you can just remove.   But the first one you need to study.    
You basically want to create your own version of this script, with same properties, filled the same,  and attach it instead of the magicsoultrapfxscript.

The OnEffectStart  event can remain the same, or you can add some extra change to make sure that Target is a valid one, and you still need their soul.    (Add your quest as a property to this script)

SpecialSoulGemQuestScript Property MyQuest  Auto 

In OnEffectFinish event,  instead of  "if Caster.TrapSoul(victim) == true" you can do:   "if MyQuest.SpecialTrapSoul(victim, caster) == True"

And in your Quest script, you add a Bool Function SpecialSoulTrap(Actor victim, Actor Caster) which will have something like:

ScriptName SpecialSoulGemQuestScript extends Quest Conditional

Bool Property CapturedGiantSoul Auto Conditional         ;these 3 set to false in properties
Bool Property CapturedDragonSoul Auto Conditional
Bool Property CapturedDragonPriestSoul Auto Conditional
Actor Property PlayerRef Auto 
MiscObject Property EmptySoulGem Auto
MiscObject Property PartialSoulGem Auto
MiscObject Property FilledSoulGem Auto
Race Property GiantRace Auto
Race Property DragonRace Auto
Race Property DragonPriestRace Auto

Bool Function SpecialTrapSoul(Actor Victim, Actor Caster)
  If Caster == PlayerRef && (PlayerRef.GetItemCount(EmptySoulGem) > 0 || PlayerRef.GetItemCount(PartialSoulGem) > 0)
    Bool AddSoul = false
    If Victim.GetRace() == GiantRace
      If !CapturedGiantSoul
        CapturedGiantSoul = true
        AddSoul = true
        SetObjectiveCompleted(30, true)  ; Assume your quest's objective 30 is 'collect giant soul', and was set upon starting this quest stage
      EndIf
    Elseif Victim.GetRace() == DragonRace
      If !CapturedDragonSoul
        CapturedDragonSoul = true
        AddSoul = true
        SetObjectiveCompleted(31, true)  
      EndIf
	Elseif Victim.GetRace() == DragonPriestRace
      If !CapturedDragonPriestSoul
        CapturedDragonPriestSoul = true
        AddSoul = true
        SetObjectiveCompleted(32, true)  
      EndIf
	EndIf
    If AddSoul
      If !CapturedGiantSoul || !CapturedDragonSoul || !CapturedDragonPriestSoul
        If PlayerRef.GetItemCount(EmptySoulGem) > 0
          PlayerRef.RemoveItem(EmptySoulGem, 1, true)
          PlayerRef.AddItem(PartialSoulGem, 1)
        EndIf
      ElseIf CapturedGiantSoul && CapturedDragonSoul && CapturedDragonPriestSoul
        PlayerRef.RemoveItem(PartialSoulGem, 1, true)
        PlayerRef.AddItem(FilledSoulGem, 1)
        ; Here probably want to add some code to advance the quest.
      EndIf
      Return True
    EndIf
  EndIf
  Return False
EndFunction


 

Link to comment
Share on other sites

×
×
  • Create New...