Jump to content

[Bug + Fix] Aetherial Crown effect


thebigMuh

Recommended Posts

Greetings!

 

This bug has been bothering me for ages, so I finally got annoyed enough to investigate and fix it:

 

I'll refer to the ability the crown is holding as "crown ability" and the ability the player should have even when the crown is not equipped as "player ability".

 

* You can easily get the Aetherial Crown into a state where it will only correctly apply the crown ability every second time you equip it.

* You can equally easy get three (or more) doomstone abilities to be active at the same time - one from the crown, two actually on the player.

* Also, if you store the same ability in the crown as you have as a player ability, then equipping and unequipping the crown will erase the player ability, leaving you without a doomstone ability while the crown is unequipped.

 

The annoying (and bothersome to fix) were the first two bugs, the last one was a drive-by fix.

 

How to reproduce:

 

* To watch what the crown is doing enter 'prid xx00cff1' in the console to pick the crown reference (0200cff1 usually). Use 'sv' along the way to print the variables on it.

    * currentCrownAbility is what the crown thinks it has stored

    * currentDoomstoneAbility is the ability the crown thinks is on the player

* For easy testing, travel to the guardian stones, since you can quickly switch between three different abilities there.

    * Thief is ability number 11

    * Mage is ability number 6

    * Warrior is ability number 13

* To get into the bugged behavior, you have to get the currentCrownAbility to be 0 even tough there actually is an ability stored in the crown.

* For starters, unequip the crown, pick Mage, equip the crown, then pick Warrior. This should set the crown ability to 6, and the player ability to 13, no matter which state your game was in before. If not, then continue flipping through the stones until you get to this state. You should have Warrior as player ability and Mage as crown ability at this point, ready to screw everything up.

* Unequip, then reequip the crown, checking 'sv' after each equip. currentDoomstoneAbility will flip between 13 and 0 every time you equip the crown.

* When it's 0, you can do various things:

    * Pick Thief and you will have all three guardian stones active at the same time. currentCrownAbility will be 0 (should be 13), and the Mage will be left over (should have been removed).

    * Pick either Warrior or Mage, and nothing will change from an ability standpoint, but the currentCrownAbility will be 0 (should still be 6), and only every second time you equip the crown will actually give you the Mage ability.

    * Play around some more if you want. If you play your cards right, you should be able to get all doomstone abilities at the same time by playing this game with all doomstones in reverse order from 13 up to 1, adding one leftover ability after another (Warrior->Tower->Thief->Steed->Shadow->Serpent->Ritual->Mage->Lover->Lord->Lady->Atronach->Apprentice).

 

In any case, the logic of the crown script (DLC1LD_AetherialCrownScript.psc) is pretty screwed up.

 

Here's my proposed fix. It makes it quite a bit larger, partly to correctly track both the player ability and the crown ability, and partly so this script can "heal" a game that already has the crown in a broken state. I heavily commented it, which together with a diff of the old script should hopefully explain everything. My comments have a space between the ';' and the text.

Scriptname DLC1LD_AetherialCrownScript extends ReferenceAlias
{Script for the DLC1LD Aetherial Crown reward item}

;DLC1LD Postquest quest, since we need to keep the crown on an alias.
Quest property DLC1LD_Postquest Auto

;Perk that traps the Doomstone activation.
Perk property DLC1LD_AetherialCrownPerk Auto

;Standard doomstone spells & perks.							;Int value used for tracking purposes.
Spell property pDoomApprenticeAbility Auto				;1
Spell property pdoomApprenticeNegativeAbility Auto	
Spell property pDoomAtronachAbility Auto				;2
Spell property pDoomLadyAbility Auto					;3
Spell property pDoomLordAbility Auto					;4
Spell property pDoomLoverAbility Auto					;5
Spell property pDoomMageAbility Auto					;6
Spell property pDoomRitualAbility Auto					;7
Perk property pDoomRitualPerk Auto
Spell property pDoomSerpentAbility Auto					;8
Spell property pDoomShadowAbility Auto					;9
Spell property pDoomSteedAbility Auto					;10
Spell property pDoomThiefAbility Auto					;11
Spell property pDoomTowerAbility Auto					;12
Spell property pDoomWarriorAbility Auto					;13

;Standard doomstone removed messages.
Message property pDoomApprenticeRemovedMSG Auto
Message property pDoomAtronachRemovedMSG Auto
Message property pDoomLadyRemovedMSG Auto
Message property pDoomLordRemovedMSG Auto
Message property pDoomLoverRemovedMSG Auto
Message property pDoomMageRemovedMSG Auto
Message property pDoomRitualRemovedMSG Auto
Message property pDoomSerpentRemovedMSG Auto
Message property pDoomShadowRemovedMSG Auto
Message property pDoomSteedRemovedMSG Auto
Message property pDoomThiefRemovedMSG Auto
Message property pDoomTowerRemovedMSG Auto
Message property pDoomWarriorRemovedMSG Auto

;Rested abilities, which have to be removed when the Lover Stone bonus is added.
Spell property pRested Auto
Spell property pWellRested Auto
Spell property pMarriageRested Auto

;The current ability stored in the crown.
int currentCrownAbility

;The current doomstone on the player.
int currentDoomstoneAbility

;Internal variables for the effect stored in the crown.
Spell currentSpell1
Spell currentSpell2
Perk currentPerk
Message currentRemoveMessage

;Timestamp of when the player last interacted with a doomstone.
float interactionTimestamp = 0.0


;When the player equips the crown, turn on the activation-trap perk and begin tracking.
Event OnEquipped(Actor akActor)
	if (akActor == Game.GetPlayer())
		;Debug.Trace("CROWN: Aetherial Crown equipped. Current Ability = " + currentCrownAbility)
		akActor.AddPerk(DLC1LD_AetherialCrownPerk)
		
		; Update the ability in the crown. This might have been screwed up because of an old
		; buggy crown script.
		currentCrownAbility = IdentifyCurrentCrownAbility()

		; Find out what the current doomstone effect on the player is, ignoring the effect stored
		; in the crown (if any).
		currentDoomstoneAbility = IdentifyCurrentDoomstoneEffectOnEquip()
		
		if (currentCrownAbility != currentDoomstoneAbility)
			;Reinstate the last-recorded doomstone ability.
			ApplyCrownEffect(True)
		EndIf
		;Debug.Trace("CROWN: Aetherial Crown equip done. Current Doomstone recorded as: " + currentDoomstoneAbility)
	EndIf
EndEvent


;When the player removes the crown, turn off the activation-trap perk.
Event OnUnequipped(Actor akActor)
	;Debug.Trace("CROWN: Aetherial Crown unequipped. Current Ability = " + currentCrownAbility)
	akActor.RemovePerk(DLC1LD_AetherialCrownPerk)

	; Again, update the ability in the crown. This might have been screwed up because of an old
	; buggy crown script.
	currentCrownAbility = IdentifyCurrentCrownAbility()
		
	; Only remove the crown's ability from the player if it's different from the one on the player.
	If (currentCrownAbility != currentDoomstoneAbility)
		ApplyCrownEffect(False)
	EndIf
EndEvent


;When the perk catches a Doomstone activation, it calls this function so we can record it.
;We have to wait briefly for the player to exit the Doomstone menu so we know whether he took the new ability.
Function DoomstoneActivated()
	;Debug.Trace("CROWN: Doomstone activation received.")
	;Debug.Trace("CROWN: Current Doomstone recorded as: " + currentDoomstoneAbility)
	;Debug.Trace("CROWN: Registering for update.")
	interactionTimestamp = Utility.GetCurrentRealTime()
	UnregisterForUpdate()
	RegisterForSingleUpdate(0.5)
EndFunction


;Check to see if the player changed doomstone abilities. If so, change the ability stored in the crown.
Event OnUpdate()
	RunUpdate()
EndEvent

Function RunUpdate()
	;Debug.Trace("CROWN: OnUpdate: " + currentDoomstoneAbility + " - " + IdentifyCurrentDoomstoneEffect())
	;Make sure the currentCrownAbility is correct
	currentCrownAbility = IdentifyCurrentCrownAbility()
	;Did the player change doomstone abilities?
	Int identifiedEffect = IdentifyNewDoomstoneEffect(currentCrownAbility, currentDoomstoneAbility)
	;Debug.Trace("CROWN: We have: " + identifiedEffect + ", " + currentDoomstoneAbility + ", " + currentCrownAbility)
	if (identifiedEffect > 0)
		if (identifiedEffect != currentDoomstoneAbility)
			;Debug.Trace("CROWN: Now updating.")
			ApplyCrownEffect(False)							;Remove the old ability stored in the crown.
			
			; Check for a bugged old crown. This means that currentDoomstoneAbility == 0 even though
			; there might be two (or more) doomstone abilities on the player right now (the old one 
			; and the new one). In that case, find the next one.
			If (currentDoomstoneAbility == 0)
				Int secondEffect = IdentifyNewDoomstoneEffect(identifiedEffect, 0)
				If (secondEffect != 0)
					; There is a second effect. Move the first one into the crown, and make the second
					; one the new doomstone ability. Might be the wrong way around, but is the best we
					; can do at this point.
					currentDoomstoneAbility = identifiedEffect
					identifiedEffect = secondEffect
				EndIf
			EndIf
			
			SelectNewCrownEffect(currentDoomstoneAbility)	;Determine which ability should now be stored by the crown.
			ApplyCrownEffect(True)							;Apply the new ability.
			currentDoomstoneAbility = identifiedEffect		;Record the current doomstone for reference in the next loop.
		ElseIf (Utility.GetCurrentRealTime() - interactionTimestamp < 60)
			;Debug.Trace("CROWN: Re-Registering for Update, v1.")
			UnregisterForUpdate()
			RegisterForSingleUpdate(0.5)
		Else
			;Debug.Trace("CROWN: Timer expired, v1.")
		EndIf
	ElseIf (Utility.GetCurrentRealTime() - interactionTimestamp < 60)
		;Debug.Trace("CROWN: Re-Registering for Update, v2.")
		UnregisterForUpdate()
		RegisterForSingleUpdate(0.5)
	Else
		;Debug.Trace("CROWN: Timer expired, v2.")
	EndIf
EndFunction


;Add or remove the ability stored by the crown.
Function ApplyCrownEffect(bool shouldAdd)
	if (shouldAdd)
		;Debug.Trace("CROWN: Adding Crown ability." + currentCrownAbility + " " + currentSpell1)
		if (currentSpell1 != None)		
			Game.GetPlayer().AddSpell(currentSpell1)
			if (currentSpell2 != None)
				Game.GetPlayer().AddSpell(currentSpell2)
			EndIf
			if (currentPerk != None)
				Game.GetPlayer().AddPerk(currentPerk)
			EndIf
		EndIf		
	Else
		;Debug.Trace("CROWN: Removing Crown ability for " + currentCrownAbility + " " + currentSpell1)
		if (currentSpell1 != None)
			Game.GetPlayer().RemoveSpell(currentSpell1)
			if (currentSpell2 != None)
				Game.GetPlayer().RemoveSpell(currentSpell2)
			EndIf
			if (currentPerk != None)
				Game.GetPlayer().RemovePerk(currentPerk)
			EndIf
			currentRemoveMessage.Show()
		EndIf
	EndIf
EndFunction

;Given the int value of a new effect (see list at top, or the identifying function below), set up the new effect stored in the crown.
Function SelectNewCrownEffect(int newEffect)
	currentCrownAbility = newEffect

	If (currentCrownAbility == 0)
		; If we get in here, then the old currentDoomstoneAbility was 0, either because
		; of an old bugged crown or because the player didn't have a previous doomstone
		; ability. Clean out the currently stored ability just to be sure.
		currentSpell1 = None
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = None
	ElseIf (currentCrownAbility == 1)
		currentSpell1 = pDoomApprenticeAbility
		currentSpell2 = pdoomApprenticeNegativeAbility
		currentPerk = None
		currentRemoveMessage = pDoomApprenticeRemovedMSG
	ElseIf (currentCrownAbility == 2)
		currentSpell1 = pDoomAtronachAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomAtronachRemovedMSG
	ElseIf (currentCrownAbility == 3)
		currentSpell1 = pDoomLadyAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomLadyRemovedMSG
	ElseIf (currentCrownAbility == 4)
		currentSpell1 = pDoomLordAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomLordRemovedMSG
	ElseIf (currentCrownAbility == 5)
		;Special case - Remove Rested bonuses for Lover.
		Game.GetPlayer().RemoveSpell(pRested)
		Game.GetPlayer().RemoveSpell(pWellRested)
		Game.GetPlayer().RemoveSpell(pMarriageRested)
		currentSpell1 = pDoomLoverAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomLoverRemovedMSG
	ElseIf (currentCrownAbility == 6)
		currentSpell1 = pDoomMageAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomMageRemovedMSG
	ElseIf (currentCrownAbility == 7)
		currentSpell1 = pDoomRitualAbility
		currentSpell2 = None
		currentPerk = pDoomRitualPerk
		currentRemoveMessage = pDoomRitualRemovedMSG
	ElseIf (currentCrownAbility == 8)
		currentSpell1 = pDoomSerpentAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomSerpentRemovedMSG
	ElseIf (currentCrownAbility == 9)
		currentSpell1 = pDoomShadowAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomShadowRemovedMSG
	ElseIf (currentCrownAbility == 10)
		currentSpell1 = pDoomSteedAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomSteedRemovedMSG
	ElseIf (currentCrownAbility == 11)
		currentSpell1 = pDoomThiefAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomThiefRemovedMSG
	ElseIf (currentCrownAbility == 12)
		currentSpell1 = pDoomTowerAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomTowerRemovedMSG
	ElseIf (currentCrownAbility == 13)
		currentSpell1 = pDoomWarriorAbility
		currentSpell2 = None
		currentPerk = None
		currentRemoveMessage = pDoomWarriorRemovedMSG
	EndIf
	;Debug.Trace("CROWN: New crown ability selected for " + currentCrownAbility)
EndFunction


;Determine which doomstone ability the player has by testing the abilities on them.
; The activator script on the doomstones only removes the first ability it finds,
; which can be either the ability in the crown or the ability actually on the player.
; Therefore we have to ignore both the old crown ability as well as the old doomstone
; ability when looking for the new ability.
; If the old crown effect was screwed up (== 0) then this will find either the previous
; doomstone effect or the new one. Doesn't really matter, we just move one of them 
; into the crown, and the other will be the new doomstone effect on the player.
int Function IdentifyNewDoomstoneEffect(Int firstReject, Int secondReject)
	;Debug.Trace("CROWN: Now Identifying: " + currentCrownAbility)
	if (Game.GetPlayer().HasSpell(pDoomApprenticeAbility) && firstReject != 1 && secondReject != 1)
		return 1
	ElseIf (Game.GetPlayer().HasSpell(pDoomAtronachAbility) && firstReject != 2 && secondReject != 2)
		return 2
	ElseIf (Game.GetPlayer().HasSpell(pDoomLadyAbility) && firstReject != 3 && secondReject != 3)
		return 3
	ElseIf (Game.GetPlayer().HasSpell(pDoomLordAbility) && firstReject != 4 && secondReject != 4)
		return 4
	ElseIf (Game.GetPlayer().HasSpell(pDoomLoverAbility) && firstReject != 5 && secondReject != 5)
		return 5
	ElseIf (Game.GetPlayer().HasSpell(pDoomMageAbility) && firstReject != 6 && secondReject != 6)
		return 6
	ElseIf (Game.GetPlayer().HasSpell(pDoomRitualAbility) && firstReject != 7 && secondReject != 7)
		return 7
	ElseIf (Game.GetPlayer().HasSpell(pDoomSerpentAbility) && firstReject != 8 && secondReject != 8)
		return 8
	ElseIf (Game.GetPlayer().HasSpell(pDoomShadowAbility) && firstReject != 9 && secondReject != 9)
		return 9
	ElseIf (Game.GetPlayer().HasSpell(pDoomSteedAbility) && firstReject != 10 && secondReject != 10)
		return 10
	ElseIf (Game.GetPlayer().HasSpell(pDoomThiefAbility) && firstReject != 11 && secondReject != 11)
		return 11
	ElseIf (Game.GetPlayer().HasSpell(pDoomTowerAbility) && firstReject != 12 && secondReject != 12)
		return 12
	ElseIf (Game.GetPlayer().HasSpell(pDoomWarriorAbility) && firstReject != 13 && secondReject != 13)
		return 13
	Else
		return 0
	EndIf
EndFunction

; Identifies the doomstone effect on the player, and tries to ignore the effect
; stored in the crown. It assumes that the crown is currently NOT equipped, but
; because the player can rapidly equip and unequip items, the old effect might
; still linger. If that should be the case, the bestFit will be returned.
Int Function IdentifyCurrentDoomstoneEffectOnEquip()
	Int bestFit = -1
	
	If (Game.GetPlayer().HasSpell(pDoomApprenticeAbility))
		If (currentCrownAbility == 1)
			bestFit = 1
		Else
			return 1
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomAtronachAbility))
		If (currentCrownAbility == 2)
			bestFit = 2
		Else
			return 2
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomLadyAbility))
		If (currentCrownAbility == 3)
			bestFit = 3
		Else
			return 3
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomLordAbility))
		If (currentCrownAbility == 4)
			bestFit = 4
		Else
			return 4
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomLoverAbility))
		If (currentCrownAbility == 5)
			bestFit = 5
		Else
			return 5
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomMageAbility))
		If (currentCrownAbility == 6)
			bestFit = 6
		Else
			return 6
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomRitualAbility))
		If (currentCrownAbility == 7)
			bestFit = 7
		Else
			return 7
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomSerpentAbility))
		If (currentCrownAbility == 8)
			bestFit = 8
		Else
			return 8
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomShadowAbility))
		If (currentCrownAbility == 9)
			bestFit = 9
		Else
			return 9
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomSteedAbility))
		If (currentCrownAbility == 10)
			bestFit = 10
		Else
			return 10
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomThiefAbility))
		If (currentCrownAbility == 11)
			bestFit = 11
		Else
			return 11
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomTowerAbility))
		If (currentCrownAbility == 12)
			bestFit = 12
		Else
			return 12
		EndIf
	EndIf
	
	If (Game.GetPlayer().HasSpell(pDoomWarriorAbility))
		If (currentCrownAbility == 13)
			bestFit = 13
		Else
			return 13
		EndIf
	EndIf
	
	return bestFit
EndFunction

; Identifies the doomstone ability currently stored in the crown based
; on the spell in currentSpell1. Shouldn't ordinarily be needed since
; currentCrownAbility already tracks this, but there's always potential
; for something to get screwed up, and this should fix it.
Int Function IdentifyCurrentCrownAbility()
	if (currentSpell1 == pDoomApprenticeAbility)
		return 1
	ElseIf (currentSpell1 == pDoomAtronachAbility)
		return 2
	ElseIf (currentSpell1 == pDoomLadyAbility)
		return 3
	ElseIf (currentSpell1 == pDoomLordAbility)
		return 4
	ElseIf (currentSpell1 == pDoomLoverAbility)
		return 5
	ElseIf (currentSpell1 == pDoomMageAbility)
		return 6
	ElseIf (currentSpell1 == pDoomRitualAbility)
		return 7
	ElseIf (currentSpell1 == pDoomSerpentAbility)
		return 8
	ElseIf (currentSpell1 == pDoomShadowAbility)
		return 9
	ElseIf (currentSpell1 == pDoomSteedAbility)
		return 10
	ElseIf (currentSpell1 == pDoomThiefAbility)
		return 11
	ElseIf (currentSpell1 == pDoomTowerAbility)
		return 12
	ElseIf (currentSpell1 == pDoomWarriorAbility)
		return 13
	EndIf
	
	return 0
EndFunction

Alternatively/additionally you could of course also use a one time script to clean up the player's doomstone abilities, and then use a somewhat less resilient but shorter script for the crown.

 

Ciao, Muh!

Edited by thebigMuh
Link to comment
Share on other sites

  • 2 weeks later...

I would like to try your proposed changes but have no idea how to generate a pex file. would you send me a compiled script with the above?

Link to comment
Share on other sites

  • 10 months later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...