Jump to content
ℹ️ Intermittent Download History issues ×

foamyesque

Members
  • Posts

    835
  • Joined

  • Last visited

Posts posted by foamyesque

  1. You can have as many loops as you like; the issue is that you put the loop in the OnInit block. As long as an OnInit block is running on an object, nothing else except other OnInit blocks can run on that object. It's to prevent races. Since your loop takes (a minimum of) 300 seconds it blocks everything else from running until that time is up. This behaviour is why it's generally recommended to keep OnInit blocks as small and fast as reasonably practical.

     

    OnUpdate events are shared amongst all scripts attached to the same object, which is why both quest scripts trigger it.

  2. Some (though by no means all, or even most) exterior cells have editor names, and you can use COC to get to them. These typically include all cells in a city's worldspace (e.g., CityNameOrigin), and in small towns, the cell of the town itself. There's also usually named cells at or immediately surrounding named locations of various sorts, e.g. stables, entrances to dungeons, immediate city exteriors, and so on.

  3.  

    Don't need to explicitly declare the empty activation in the Disabled state. Undeclared events fall back to their defaults, which is an empty block anyway.

     

    However, my suggestion would be to go to the Disabled state immediately on activation, regardless of whether or not the player has the gold, etc. Do all those checks there, and then either display the menu or go back to the Active state. This gives the smallest possible window for malformed input and should functionally guarantee that all the code finishes running, in either path, before a new trigger can start it again.

     

    For clarity I'd probably rename them from Active -> Waiting and Disabled -> Active.

    Noted. Thanks! I changed it up, I wasn't aware changing the state didn't end the current function abruptly.

     

     

    Scriptname AAAxWCxINVxPurchaseScript extends ObjectReference  
    {Used to buy a property to generate income.}
    
    ;----------------
    ;Local properties
    ;----------------
    
    bool property IsInn = false auto
    {Increases price by 50%}
    
    GlobalVariable Property gCnt auto
    {Objective counter}
    
    GlobalVariable Property gCntTotal auto
    {Objective counter cap for completion}
    
    GlobalVariable Property gBasePrice auto
    {Starting price tier}
    
    int property ModObjID auto
    {ID of objective to count}
    
    AAAxWCxINVxAliasControl property AliasQS auto
    {Alias to control}
    
    ;--------------------
    ;Universal properties
    ;--------------------
    
    Actor property PlayerREF auto
    
    float fCost
    
    GlobalVariable Property gTotalPaid auto
    
    MiscObject Property mCoin auto
    
    Message property mMenuP auto
    Message property mMenuFailP auto
    
    MusicType property muBuy auto
    
    Quest property qIncByInt auto
    
    AAAxWCxINVxIncomeScript property IncQS auto
    
    ;------
    ;States
    ;------
    
    Auto State Standby ;Debug--allows the player to activate the trigger
    	Event OnActivate(ObjectReference akActionRef) ;Checks to see if the player has the money and changes state
    		GotoState("")
    		fCost = gBasePrice.GetValue()
    		if IsInn == true ;Calculates the cost of the property
    			fCost *= 1.5
    		endif
    		
    		if PlayerREF.GetGoldAmount() >= fCost
    			BuyMenu()
    		else
    			mMenuFailP.Show(fCost)
    			GotoState("Standby")
    		endif
    	EndEvent
    endState
    
    ;---------
    ;Functions
    ;---------
    
    Event OnActivate(ObjectReference akActionRef) ;Debug--stops the player from activating trigger
    	;Do nothing
    EndEvent
    
    Function BuyMenu(int aiButton = 0) ;Takes the player's money/adds 1 to the objective/runs an income update from a remote script
    	aiButton = mMenuP.Show(fCost)
    	if aiButton == 0 ;Yes
    		PlayerREF.RemoveItem(mCoin, fCost as int)
    		gTotalPaid.Mod(fCost)
    		qIncByInt.ModObjectiveGlobal(1, gCnt, ModObjID, gCntTotal.GetValue(), true, true)
    		IncQS.UpdStatVal()
    		if qIncByInt.IsRunning() == False ;Starts the income quest if its disabled
    			qIncByInt.Start()
    		endif
    		Debug.Notification("Property purchased!")
    		muBuy.Add() ;Plays sound effect
    		DeleteRef(self.GetLinkedRef(), self.countLinkedRefChain())
    	else ;No
    		GotoState("Standby")
    	endif
    endFunction
    
    Function DeleteRef(ObjectReference LRef = none, int iLCnt = 0) ;Fades/Deletes any custom sign objects and controls the alias from a remote script
    	self.Disable() ;Disables initially for AliasCtrl
    	AliasQS.AliasCtrl()
    	while iLCnt > 1 ;Loops till all linked references are erased from the chain
    		LRef.Disable(true)
    		LRef.Delete()
    		LRef = LRef.GetLinkedRef()
    		iLCnt -= 1
    	endWhile
    	Self.Delete()
    EndFunction 

     

     

     

    Which brings me to my next consistently recurring train of thought. What will force a script to drop what it's doing, besides "return"?

     

    Would this work? Or will it only read the current running function before before having issues?

     

     

    Scriptname NewScript extends ObjectReference
    
    Auto State Waiting
    	Event OnActivate(ObjectReference akActionRef)
    		GoToState("")
    		MyFunc()
    	endEvent
    	
    	Function MyFunc()
    		MyFunc2()
    	endFunction
    endState
    
    Event OnActivate(ObjectReference akActionRef)
    	;Do nothing or something else
    endEvent
    
    Function MyFunc()
    	;Do nothing or something else
    endFunction
    
    Function MyFunc2()
    	;Do stuff
    	GoToState("Waiting")
    endFunction 

     

     

     

    I have a feeling this will fail to run "MyFunc()" inside the "Waiting" state, and will run the one inside the empty state instead. That would makes since to me, but let's make sure I am not going insane here.

     

     

    What will happen there is, assuming it starts in the Waiting state and something triggers the OnActivate Event:

     

     

    The OnActivate event declared in the Waiting state starts processing. The first thing it does is put the script into the empty state, so any further function calls or event triggers will use the versions declared in the empty state. Then it proceeds to call MyFunct(), which will use the empty state version, and therefore not call MyFunct2(), so the script doesn't get put back into the Waiting state.

  4.  

    They are, so there's something in your setup that's clearing them.

    What could clear a quest alias aside from stopping/reset the quest, which isn't happening, or calling clear() directly on the alias?

     

     

    There shouldn't be anything AFAIK; a ForceRef to 'none' shouldn't do anything. There's a note in the wiki's talk page for ForceRef of someone having the same issue back in 2016, but I've used ForcedRef both script and console across saves without issue.

     

    Without more details on your scripting / quest setup it's hard to figure the issue. One possibility is that Quests that are Start Game Enabled will start twice, so depending on how exactly you've set up your force-fill it could be that they're filling on game start and then being wiped when the quest starts for the second time (e.g., OnInit events fire twice in this scenario).

  5. Don't need to explicitly declare the empty activation in the Disabled state. Undeclared events fall back to their defaults, which is an empty block anyway.

     

    However, my suggestion would be to go to the Disabled state immediately on activation, regardless of whether or not the player has the gold, etc. Do all those checks there, and then either display the menu or go back to the Active state. This gives the smallest possible window for malformed input and should functionally guarantee that all the code finishes running, in either path, before a new trigger can start it again.

     

    For clarity I'd probably rename them from Active -> Waiting and Disabled -> Active.

  6. I see, thank you for the suggestion it is greatly appreciated - I have only really been using the creation kit for about a week and a bit so I'm still unsure how a lot of it works. Do you know any resources I can use to learn how to set up a quest script and then link it to the Topic Info?

     

    Thanks again :smile:

     

    I'd generally recomment hitting up the CK wiki. But if you're creating fragment scripts with working properties and such you already have pretty much everything you need -- a working quest, you know how to navigate the CK, add & fill properties, etc.

     

    On the main Quest screen, at the top, there's a tab list. You should be familiar with it b/c you've been doing dialogue. At the far right there's one called Scripts, which is a list of all the scripts that're attached to that Quest (there's similar windows for anything that can have scripts -- MagicEffects, Aliases, various kinds of ObjectReference, etc), The buttons beside should be familiar and reasonably self-explanatory. Hit "Add", and create a new script (named as you wish) that extends Quest.

    This will open up the CK Papyrus editor & compiler with the scriptname line already generated. Write all your processing code -- GetOutfit, SetOutfit, etc -- here, in functions. Set properties per normal.

     

    Then change your TopicInfo fragments to say this:

     

    (GetOwningQuest() as ScriptName).SetBeggar()

     

    and

     

    (GetOwningQuest() as ScriptName).ClearBeggar()

     

    Using whatever function names/script name you choose. What this does is have the topicinfo fragments tell the *quest script* to run those functions (just as when you say Actor.SetOutfit(), you're calling a function that runs on the actor; only in this case, you're accessing code you yourself wrote, not the basic stuff that shipped with the game). Because Quests are script objects that are always loaded they are extraordinarily useful for persistent data storage and state tracking, and in this case you need one anyway for the dialogue :)

     

     

    (According to the Wiki TopicInfo script variables get cleared for memory reasons, so presumably that's what's been causing your issues, BTW)

  7. The properties are all filled except the CitizenOutfit01 as it is a script driven property - I fill that with the originalOutfit() function, All of the debug notifications fire - I placed them intentionally so I would know the code is working. With regard to the CitizenOutfit01 being filled, The makeBeggar() and originalOutfit() functions should always run first as it is impossible for the NPC to have the keyword AAInfluenceIsBeggar without the makeBeggar() function (I have added the keyword to the quest alias and that is only way to fill it)

     

    So the way it will work is, I approach an NPC and there will be an option "You are stripped of all your earthly possessions and are cast out of the city!" (I'm making a sort of social class system that the player can influence with dialogue)

    Then the NPC fills the quest alias WhiterunBeggar01 and follows the AI Packages etc and is given a new outfit.

     

    Then, there is another dialogue option that is conditional to the NPC having the keyword AAInfluenceIsBeggar which then says "You may re-enter the city, your banishment has been lifted" And then this triggers the releaseBeggar() function (which works perfectly) and then sets the outfit to CitizenOutfit01 (which should've been filled when the NPC was made a beggar) but for some reason it doesn't change and now I have an NPC wandering around the city wearing rags - doesn't look the best when you use it on the Jarl and now you have a Jarl sitting on his throne in ragged robes hahahah

     

    The only thing I can think of is that because CitizenOutfit is a script driven property and not a property that is filled with an In game object then perhaps it is only filled on that specific instance of the script running

     

    So maybe because CitizenOutfit01 was filled on a different TopicInfo to the TopicInfo it is actually used it won't work?

     

    I ran a little test where I set the CitizenOutfit01 IMMEDIATELY after setting the BeggarOutfit when making the NPC a Beggar and that worked so I know that CitizenOutfit01 is filling properly, It just isn't carrying over to the TopicInfo that releases the Beggar.

     

    So basically I just need a solution to how I can get the original outfit to carry over to the next TopicInfo. Everything else works fine.

     

    Personally I think you should try to avoid putting significant code in fragments like this. I'd put all the outfit & alias manipulation into a Quest script with functions for the TopicInfo fragments to call. That will make troubleshooting (and data passing) significantly simpler.

  8. Are you certain your code paths are being followed? You've got debug alerts in there. Do the expected ones fire? Is there anything in the CitizenOutfit property when you go to call the SetOutfity function directly after the releaseBeggar() function?

     

     

    Have you filled the properties with the appropriate data?

    In this case the relevant properties are script-driven. It's a TopicInfo, which means IIRC akSpeaker is an inherent autofill from the dialogue and citizenOutfit should be being pulled from the originalOutfit() function's GetOutfit() call.

  9.  

    The replaced one Event to Function:

    scriptName defaultTeleportAbility extends activeMagicEffect
    {Attempt at a generic script for NPCs to teleport to XXX markers when threatened by melee}
    
    import game
    import utility
    
    ;VFX
    Activator property SummonFX Auto
    
    ;Summon parameters
    activator property teleportBase auto
    {Base object to search for as a teleport point}
    float property fSearchRadius = 1600.0 auto
    {How far near me to search for teleport points? DEFAULT = 768u}
    int property iMinToTeleport = 2 auto
    {Min # of hits I'll take before teleporting.  DEFAULT = 2}
    int property iMaxToTeleport = 4 auto
    {Max # of hits I'll take before teleporting.  DEFAULT = 4}
    effectShader property fadeOutFX auto
    effectShader property fadeInFX auto
    visualEffect property TrailFX auto
    ;{Let's try some trailing FX to show where I'm going to pop out}
    
    ;Internal variables.
    bool combatStarted
    int hitCount
    ObjectReference teleportGoal 
    ObjectReference player
    actor caster
    objectReference casterRef
    bool inBleedout
    objectReference opponent
    
    Event OnEffectStart(Actor akTarget, Actor akCaster)
    	player = game.getPlayer()
    	caster = akCaster
    	casterRef = (caster as ObjectReference)
    endEVENT
    
    ;Every 2 hits, the Caller teleports.
    Event OnHit(ObjectReference akAggressor, Form weap, Projectile proj, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
    	opponent = akAggressor
    	int hitTolerance = RandomInt(iMinToTeleport, iMaxToTeleport)
    	hitCount = hitCount + 1
    	if (hitCount > hitTolerance && !caster.IsDead())
    		hitCount = 0
    		if inBleedout == FALSE && caster.isDead() == FALSE
    			Teleport()
    		endif
    	EndIf
    	
    	; additional check - seen instances where NPC with this ability would not die!
    	if Caster.getActorValue("Health") < 0
    		caster.kill()
    ; 		debug.trace("teleport NPC killed by self (HP<0 safety net): "+caster)
    	endif
    	
    EndEvent
    
    Function Teleport()
    		
    	;Where's the Caller going?
    	int i = 0					; incrementer for while
    	int maxSearch = 10		; limit number of while recursions
    	while i < maxSearch
    		teleportGoal = FindRandomReferenceOfTypeFromRef(teleportBase, casterRef, fSearchRadius)
    		if teleportGoal.getDistance(opponent) < 512 || (player as actor).hasLoS(teleportGoal) == FALSE
    ; 			;debug.trace(caster.getActorBase()+" found but doesn't prefer "+teleportGoal)
    			; this teleport marker isn't the best, but hold onto it as a backup option
    			; therefore don't kick out of the loop just yet
    		elseif teleportGoal.getDistance(opponent) > 512 || (player as actor).hasLoS(teleportGoal) == TRUE
    			; if we found a teleport marker that works, just use it!
    ; 			;debug.trace(caster.getActorBase()+" found a good goal and kicking out of while loop "+teleportGoal)
    			i = maxSearch
    		endif
    		i += 1
    ; 		;debug.trace("While Recursion #"+i+" on "+self)
    	endWhile
    			
    
    	;Perform the swap.
    	;casterRef.Disable()
    	fadeOutFX.play(casterRef)
    	caster.setGhost(TRUE)
    	objectReference previousLoc = casterRef.PlaceAtMe(SummonFX)
    	Utility.Wait(0.5)
    	
    	TrailFX.Play(previousLoc, 0.5, teleportGoal)
    	
    	casterRef.MoveTo(teleportGoal)
    	casterRef.PlaceAtMe(SummonFX)
    	Utility.Wait(0.5)
    	
    	;casterRef.Enable()
    	fadeOutFX.stop(casterRef)
    	caster.setGhost(FALSE)
    	caster.evaluatePackage()
    	caster.startCombat(opponent as actor)
    EndEvent
    
    EVENT onEnterBleedout()
    	inBleedout = TRUE
    	; For now never reset this boolean.  Considering it part of the flow that bleeding out turns the ability off
    endEVENT
    

     

    I misspoke slightly: You also need to change the "EndEvent" to "EndFunction" to match the declaration :v

  10. You can absolutely custom define new events and there's no difference whatsoever script-wise between the two. All events are functions, and you can convert a function to an event just by changing its declaration. Nothing will care. The distinction is purely a meta one for the programmer, to highlight things that are triggered by the game doing something.

  11. SetOutfit and GetOutfit are ActorBase functions. This means that they need to be ran with the NPCs ActorBase.

     

    Example:

    (akSpeaker as Actor).GetActorBase().GetOutFit()

     

    But running GetOutFit after having used SetOutFit will return the new outfit and not the original outfit. This is because SetOutFit replaces the default outfit. If you need to know the original outfit, then it will need to be stored to a variable prior to changing the outfit with SetOutfit.

    There is also a SetOutfit() Actor function, though it lacks a corresponding one for GetOutfit(), which is why the SetOutfit side of things worked. How that interacts with the GetOutfit() ActorBase function, I have not tested.

  12. No. Casting is only required if you're going from a general type to a more specific one (for example, ObjectReference -> Actor). Actor already extends ObjectReference, so it can be supplied without casting to anything that expects an ObjectReference, because all actors *are* ObjectReferences.

     

    Which also makes me wonder why you're bothering to store it twice.

  13. Quest *fragments* aren't something I like to deal with either, but *quests themselves* are an extraordinarily powerful and flexible resource. An awful lot of things are best accomplished via scripting either on a quest or a referencealias. This is because, amongst other things, the same object can be in multiple aliases simultaneously, which means multiple mods can modify the same resource with them. The alias-fill tool is the best way of finding something and exposing it for scripting. Aliases are handy for designing even non-generic quests because you can set up the logic and relations of each thing in the quest without needing to care about *what specifically* is in them, so that if for example you decide to swap your questgiver from Jarl Balgruuf to Kodlak Whitemane, or whatever, all you need to do is swap the alias instead of every single reference to them throughout your code/dialogue.

  14. I mean, if you were using aliases, you wouldn't necessarily *need* to have custom scripts on the items themselves, which is useful if e.g. you don't want those scripts firing when the quest is no longer running. Personally pretty much every single thing you list off I'd put in as an alias just so that it's very clear to me, as I work through things, what I am working with and what their role is going to be. This is particularly true if I'm dealing with NPCs, since you may want to do things like override their AI packages, protect them, change their inventory, give them temporary scripts, etc.

  15. Thanks, gotcha. I've been trying to research this online, but I can't seem to find anything: is it possible for a Papyrus script to check if a dialogue line is currently valid and/or on cooldown? Let's say I've conditioned a dialogue line to only be said every 12 in-game hours. If the countdown timer hasn't already been met, how do I make this killmove detect script not trigger the dialogue?

     

    One possibility is putting the exact same conditions on a magic effect, casting it on them, and then having a pingback from the ActiveMagicEffect if it worked. Bit clunky, though.

  16. The distinction between Event and Function is for the programmer. It's to distinguish stuff that's generated by some triggering 'event' from stuff that isn't (which is a bit of a squishy line, in practice). Most events are tied to the game engine throwing them, but custom events can be created that are thrown by a script, for whatever reason. So it has some applicability if you're doing X extends Y stuff (even for yourself) or if you're providing a framework for others. SkyUI has some good examples of this, with a lot of custom UI events for managing MCM work.

     

    As far as the actual papyrus scripting engine is concerned though? They perform identically and can be treated identically in all respects.

  17. I have found Tes5edit to be the simplest way to remove unwanted edits. Open Tes5 and select only your mod. You will get a tree on the left showing everything added, changed or removed by your mod. Expand the tree until you find the object you deleted and delete it in Tes5. That doesn't remove it from the game it removes any changes such as deleting it from your mod. Exit tes5 and that will save your change to your mod. You might want to make a backup of your mod before starting this process. Just in case.

    It's been a lifesaving proccess for me on more than one occasion!

  18. @ foamyesque
    I don't quite get what you are trying to say or you haven't understand correctly the issue that i'm facing.

     

    As I understand it, your goal is a three-stage boss fight, where after the boss hits certain hitpoint thresholds, they move to the next stage. You were previously worried about the boss being oneshot, so I suggested you make them essential until they're in the final stage, but that has had the side effect of making their health bar refill to full, correct?

     

    My suggestion here is to change how you do the stage transitions. For example, currently you have them shift on, say, 1,000 HP remaining and 500 HP remaining. But what if instead you made going into bleedout the transition point, and just changed the maximum HP down a bit so that each full HP bar is effectively that stage's HP? That way, they drop into bleedout, you disguise that with whatever transition effects you want and increment the fight stage, and their health then restores to full to continue the fight.

×
×
  • Create New...