Jump to content

Odd A.I. Bug


rms827

Recommended Posts

I managed to duplicate the improper attacks by invincible sentries bug that a few players have complained about relating to my Dawnguard Sentries mod. The mod is supposed to be set up so that player character vampires and their followers are ignored. Occasionally somebody reports that they ge attacked anyway, and that the Sentries cannot be killed despite switching their Essential / Respawn Only toggle (in the MCM menu).

 

For me, this bug hit after completing the Bloodstone Chalice mission and starting the Prophecy quest. It's consistent too. I played through from a few different saves and literally spent all last night (about 6 hours) stripping mods away from my load order trying to find the cause.

 

LONG story short and saving the detailed description of all the mod combinations I disabled and re-enabled, it *seems* like the problem is either in Skyrim / Dawnguard itself OR there is some odd bug in Dawnguard Sentries.

 

At this point, I cut back the Sentries to a really simple AI system. They're basically town guards with Dawnguard gear that can level higher than normal city guards. No Dawnguard related factions at all. No added faction relations at all since the town guards pretty much ignore anything but crimes and dragon or vampire attacks already. Beyond that, they just walk in circles on a path of idle markers. The only scripts I use are the Disable during Civil War sieges (so they don't interfere) and the MCM Menu one. Point being I don't know what I could do to simplify and conflict proof my mod any further.

 

Oddly enough, I figured out how to fix the bug (temporarily?), regardless of the mods I was using; do a clean save with the mod. Exit the game, disable the mod, start it back up, save and exit again, and then re-enable the mod and the Sentries go back to ignoring the vampire player & their followers.

 

Since the problem occurred and the solution worked rather I was using 63 mods or literally just the Unofficial Patches, i'm not sure the mods had anything to do with it.

 

Here's the only "non-vanilla" script I have for the mod:

 

 

 

Scriptname DawnSentryMCMStates extends SKI_ConfigBase
{Creates Dawnguard Sentries MCM Menu}

 

bool ImmortalCity = False
bool RedShirtBeGone = False
bool ImmortalOutside = False
bool ExteriorBeGone = False
bool PatrolImmortal = False
bool PatrolBeGone = False
bool RoriksteadImmortal = False
bool RoriksteadBeGone = False
bool CastleVampsBeGone = False

 

Function theInit()
ModName = "Dawnguard Sentries"
Pages = new String[1]
Pages[0] = "Configuration"
EndFunction

 

Event OnConfigInit()
theInit()
EndEvent

 

Event OnConfigOpen()
theInit()
EndEvent

 

Event OnVersionUpdate(int a_version)
theInit()
EndEvent

 

Event OnPageReset(string page)
If (page == "")
LoadCustomContent("Dawnguard Sentries/DawnguardShield.dds")
Return
Endif
UnloadCustomContent()

If (page=="Configuration")
SetCursorFillMode(TOP_TO_BOTTOM)
AddToggleOptionST ("MakeEssential", "Make Sentries Essential", ImmortalCity)
AddEmptyOption()
AddToggleOptionST ("DisableRedShirts", "Disable Dawnguard Recruits", RedShirtBeGone)
AddEmptyOption()
AddToggleOptionST ("ExteriorEssential", "Make Exterior Sentries Essential", ImmortalOutside)
AddToggleOptionST ("ExteriorDisable", "Disable Exterior Sentries", ExteriorBeGone)
AddEmptyOption()
AddToggleOptionST ("PatrolEssential", "Make Sentry Road Patrol Essential", PatrolImmortal)
AddToggleOptionST ("PatrolDisable", "Disable Sentry Road Patrol", PatrolBeGone)
AddEmptyOption()
AddToggleOptionST("RoriksteadEssential", "Set Rorikstead Sentries as Essential", RoriksteadImmortal)
AddToggleOptionST("RoriksteadDisabled", "Disable Sentries in Rorikstead", RoriksteadBeGone)
AddEmptyOption()
AddToggleOptionST("VampireSentries", "Disable Vampires Outside Volkihar", CastleVampsBeGone)
EndIf
EndEvent

 

State MakeEssential
Event OnHighlightST()
SetInfoText("Sets Non-Recruit Sentries in Towns & Cities as Essential")
EndEvent

 

Event OnSelectST()
ImmortalCity = !ImmortalCity
SetToggleOptionValueST(ImmortalCity)
If ImmortalCity == True
DawnSentryEssentialToggleQuest.start()
ElseIf ImmortalCity == False
DawnSentryEssentialToggleQuest.stop()
EndIf
EndEvent

 

Event OnDefaultST()
ImmortalCity = False
SetToggleOptionValueST(False)
DawnSentryEssentialToggleQuest.stop()
EndEvent
EndState

 

 

State DisableRedShirts
Event OnHighlightST()
SetInfoText("Disables the Dawnguard Recruits in Dayspring Canyon")
EndEvent

 

Event OnSelectST()
RedShirtBeGone = !RedShirtBeGone
SetToggleOptionValueST(RedShirtBeGone)
If RedShirtBeGone == True
DawnSentryRedShirtDisableXMarker.disable()
ElseIf RedShirtBeGone == False
DawnSentryRedShirtDisableXMarker.enable()
EndIf
EndEvent

 

Event OnDefaultST()
RedShirtBeGone = False
SetToggleOptionValueST(False)
DawnSentryRedShirtDisableXMarker.enable()
EndEvent
EndState

 

 

State ExteriorEssential
Event OnHighlightST()
SetInfoText("Sets Sentries Patrolling Outside Riften & Solitude as Essential")
EndEvent

Event OnSelectST()
ImmortalOutside = !ImmortalOutside
SetToggleOptionValueST(ImmortalOutside)
If ImmortalOutside == True
DawnSentryEssentialExteriorSentries.Start()
ElseIf ImmortalOutside == False
DawnSentryEssentialExteriorSentries.Stop()
EndIf
EndEvent

 

Event OnDefaultST()
ImmortalOutside = False
SetToggleOptionValueST(False)
DawnSentryEssentialExteriorSentries.Stop()
EndEvent
EndState

 

 

State ExteriorDisable
Event OnHighlightST()
SetInfoText("Disable the Sentries Patrolling Outside Riften & Solitude")
EndEvent

 

Event OnSelectST()
ExteriorBeGone = !ExteriorBeGone
SetToggleOptionValueST(ExteriorBeGone)
If ExteriorBeGone == True
DawnSentrySolitudeDocksDisableToggleMarker.disable()
DawnSentryRiftenExteriorDisableXMarker.disable()
ElseIf ExteriorBeGone == False
DawnSentrySolitudeDocksDisableToggleMarker.enable()
DawnSentryRiftenExteriorDisableXMarker.enable()
EndIf
EndEvent

 

Event OnDefaultST()
ExteriorBeGone = False
SetToggleOptionValueST(False)
DawnSentrySolitudeDocksDisableToggleMarker.enable()
DawnSentryRiftenExteriorDisableXMarker.enable()
EndEvent
EndState

 

 

State PatrolEssential
Event OnHighlightST()
SetInfoText("Sets Sentries Patrolling Between Riften and Dayspring Canyon as Essential")
EndEvent

 

Event OnSelectST()
PatrolImmortal = !PatrolImmortal
SetToggleOptionValueST(PatrolImmortal)
If PatrolImmortal == True
DawnSentryEssentialPatrolQuest.Start()
ElseIf PatrolImmortal == False
DawnSentryEssentialPatrolQuest.Stop()
EndIf
EndEvent

 

Event OnDefaultST()
PatrolImmortal = False
SetToggleOptionValueST(False)
DawnSentryEssentialPatrolQuest.Stop()
EndEvent
EndState

 

 

State PatrolDisable
Event OnHighlightST()
SetInfoText("Disables Sentries Patrolling Between Riften and Dayspring Canyon")
EndEvent

 

Event OnSelectST()
PatrolBeGone = !PatrolBeGone
SetToggleOptionValueST(PatrolBeGone)
If PatrolBeGone == True
DawnSentryPatrolUnitDisableXMarker.disable()
ElseIf PatrolBeGone == False
DawnSentryPatrolUnitDisableXMarker.enable()
EndIf
EndEvent

 

Event OnDefaultST()
PatrolBeGone = False
SetToggleOptionValueST(False)
DawnSentryPatrolUnitDisableXMarker.enable()
EndEvent
EndState

 

 

State RoriksteadEssential
Event OnHighlightST()
SetInfoText("Sets Sentries Patrolling Rorikstead as Essential")
EndEvent

 

Event OnSelectST()
RoriksteadImmortal = !RoriksteadImmortal
SetToggleOptionValueST(RoriksteadImmortal)
If RoriksteadImmortal == True
DawnSentryRoriksteadToggleQuest.Start()
ElseIf RoriksteadImmortal == False
DawnSentryRoriksteadToggleQuest.Stop()
EndIf
EndEvent

 

Event OnDefaultST()
RoriksteadImmortal = False
SetToggleOptionValueST(False)
DawnSentryRoriksteadToggleQuest.Stop()
EndEvent
EndState

 

 

State RoriksteadDisabled
Event OnHighlightST()
SetInfoText("Disables Sentries Patrolling Rorikstead Village")
EndEvent

 

Event OnSelectST()
RoriksteadBeGone = !RoriksteadBeGone
SetToggleOptionValueST(RoriksteadBeGone)
If RoriksteadBeGone == True
DawnSentryRoriksteadDisableXMarker.disable()
ElseIf RoriksteadBeGone == False
DawnSentryRoriksteadDisableXMarker.enable()
EndIf
EndEvent

 

Event OnDefaultST()
RoriksteadBeGone = False
SetToggleOptionValueST(False)
DawnSentryRoriksteadDisableXMarker.enable()
EndEvent
EndState

 

 

State VampireSentries
Event OnHighlightST()
SetInfoText("Disables Vampire Guards Patrolling Outside Castle Volkihar")
EndEvent

 

Event OnSelectST()
CastleVampsBeGone = !CastleVampsBeGone
SetToggleOptionValueST(CastleVampsBeGone)
If CastleVampsBeGone == True
DawnSentryVampCastleGuardDisableXMarker.disable()
ElseIf CastleVampsBeGone == False
DawnSentryVampCastleGuardDisableXMarker.enable()
EndIf
EndEvent

 

Event OnDefaultST()
CastleVampsBeGone = False
SetToggleOptionValueST(False)
DawnSentryVampCastleGuardDisableXMarker.enable()
EndEvent
EndState

 

 

Quest Property DawnSentryEssentialToggleQuest Auto
Quest Property DawnSentryEssentialExteriorSentries Auto
Quest Property DawnSentryEssentialPatrolQuest Auto
Quest Property DawnSentryRoriksteadToggleQuest Auto
ObjectReference Property DawnSentryPatrolUnitDisableXMarker Auto
ObjectReference Property DawnSentryRiftenExteriorDisableXMarker Auto
ObjectReference Property DawnSentryRedShirtDisableXMarker Auto
ObjectReference Property DawnSentrySolitudeDocksDisableToggleMarker Auto
ObjectReference Property DawnSentryRoriksteadDisableXMarker Auto
ObjectReference Property DawnSentryVampCastleGuardDisableXMarker Auto

 

 

 

Anybody see anything I goofed? The MCM script seems to work fine and this is the only line I saw in the logs that pertained to my script:

 

[06/13/2015 - 01:30:40AM] Error: Unable to call GetFormID - no native object bound to the script object, or object is of incorrect type
stack:
[None].DawnSentryMCMStates.GetFormID() - "<native>" Line ?
[sKI_ConfigManagerInstance (0E000802)].SKI_ConfigManager.Cleanup() - "SKI_ConfigManager.psc" Line ?
[sKI_ConfigManagerInstance (0E000802)].SKI_ConfigManager.OnGameReload() - "SKI_ConfigManager.psc" Line 82
[alias PlayerRef on quest SKI_ConfigManagerInstance (0E000802)].SKI_PlayerLoadGameAlias.OnPlayerLoadGame() - "SKI_PlayerLoadGameAlias.psc" Line 6

 

With no specific line referenced, I'm not sure what native object to look for that it's not seeing connected to a script.

 

 

ANYWAY... If anybody has any bright ideas what may be causing this, feel free to chime in.

Link to comment
Share on other sites

I'm no help with your problem.

But some advice I'd offer on your calling Start() on quests from an open MCM menu or any menu, I would advise against it.

 

A quest wont start while in a menu.

Start() quest Is latent and will not return until the quest is actually started (and any start-up stage fragments run).

So you end up hanging the MCM event until the menu is exited.

So for example in your MCM script you have:

     Event OnSelectST()
          ImmortalCity = !ImmortalCity
          SetToggleOptionValueST(ImmortalCity)
          If ImmortalCity == True
               DawnSentryEssentialToggleQuest.start() ;<--- At this point the quest won't start until the menu is closed. So now you have hung this event, as the event won't continue on until the quest start returns, which it never will return while the menu is open.
          ElseIf ImmortalCity == False
               DawnSentryEssentialToggleQuest.stop()
          EndIf 
     EndEvent

There are ways to work around it, but it does add a little more code.

 

I know this doesn't resolve your initial problem, but eliminating things that can break your procedures does help over all.

Link to comment
Share on other sites

I'm no help with your problem.

But some advice I'd offer on your calling Start() on quests from an open MCM menu or any menu, I would advise against it.

 

A quest wont start while in a menu.

Start() quest Is latent and will not return until the quest is actually started (and any start-up stage fragments run).

So you end up hanging the MCM event until the menu is exited.

So for example in your MCM script you have:

     Event OnSelectST()
          ImmortalCity = !ImmortalCity
          SetToggleOptionValueST(ImmortalCity)
          If ImmortalCity == True
               DawnSentryEssentialToggleQuest.start() ;<--- At this point the quest won't start until the menu is closed. So now you have hung this event, as the event won't continue on until the quest start returns, which it never will return while the menu is open.
          ElseIf ImmortalCity == False
               DawnSentryEssentialToggleQuest.stop()
          EndIf 
     EndEvent

There are ways to work around it, but it does add a little more code.

 

I know this doesn't resolve your initial problem, but eliminating things that can break your procedures does help over all.

 

The quests you see in the MCM Menu are (more or less) empty quests, They swap out the Sentries with quest alias versions that are set as essential instead of respawn only.

 

My little bit of studying indicated that would be the cleanest way to do that, particularly in terms of allowing for a clean uninstall of the mod if somebody didn't like it. Those quest alias options were intended to be "set it and forget it" also, as opposed to something a user might switch on the fly regularly. Like users always do what we expect them to do, lol.

 

Explanations aside, I'm open to ideas here. The quests getting stuck in some sort of permanent hang may be at least contributing to the issue. So, is there a better way to cleanly toggle over 80 NPCs between respawn and essential?

Link to comment
Share on other sites

You can still stop and start the quest from the MCM, but you want to only call start() after you exit the menu.

 

Gotcha. That makes sense.

 

Since I've been up all night grinding out the Open Cities compatibllity changes for the mod, my brain is in shutdown mode now. How would I change the script wording to delay the quest start till exit?

Link to comment
Share on other sites

Well there is many ways you can do it.

 

This is one way I tried and it appears to work ok, but there are other ways that may better suite your needs depending on what you've got setup.

 

I add this to each quest script that MCM will be stopping and starting from a toggle.

If the quest doesn't have a script then I add a script to the quest with just this in the script.

(this one for example is the DawnSentryEssentialToggleQuest)

ScriptName DawnSentryEssentialToggleQuestScript Extends Quest

Bool bStart

 ;The function isn't latent and returns straight away, so your MCM script will keep doing it's thing.
Function HandleQuest(Bool bState) 
	bStart = bState
	RegisterForSingleUpdate(0.5)
EndFunction

Event OnUpdate()
	If bStart
		;No use trying to start the quest while in Menu Mode.
		If Utility.IsInMenuMode() 
			RegisterForSingleUpdate(1.0)
		Else
			;If the quest is not running then start it.
			If !Self.IsRunning()
				Self.Start()
			EndIf
		EndIf
	Else
		;Stopping a quest is not latent (or at least i don't think so).
		;But we only need to stop it if it's running.
		If Self.IsRunning()
			Self.Stop()
		EndIf
	EndIf
EndEvent

Then in your MCM script call the HandleQuest() function in the quest script.

So in your case you have:

Event OnSelectST()
          ImmortalCity = !ImmortalCity
          SetToggleOptionValueST(ImmortalCity)
          If ImmortalCity == True
               DawnSentryEssentialToggleQuest.start() 
          ElseIf ImmortalCity == False
               DawnSentryEssentialToggleQuest.stop()
          EndIf 
     EndEvent
I change it to:

Event OnSelectST()
          ImmortalCity = !ImmortalCity
          SetToggleOptionValueST(ImmortalCity)
          (DawnSentryEssentialToggleQuest As DawnSentryEssentialToggleQuestScrit).HandleQuest(ImmortalCity)
     EndEvent
This way the user can toggle the option all they like and it doesn't hang the MCM event.

You'd do the same for the other quest Stop/Start entries you got.

Link to comment
Share on other sites

It helped me as well.

I improved my own mod by using an altered version of the above.

Which has made it so my toggle quest start/stop is almost bulletproof regardless of what a user does.

 

Prior I was using OnConfigClose() in my MCM script to toggle my quest.

So when OnConfigOpen() was fired I would store the Quest state eg:

Bool QuestState = False  ; My toggle control uses this
Bool QuestStateFlag ;I use this to see if the toggle has changed when exiting.

Event OnConfigOpen()
    QuestStateFlag = QuestState
EndEvent

State myQuestToggle
    Event OnSelectST()
        QuestState = !QuestState
        SetToggleOptionValueST(QuestState)
    EndEvent
EndState

Event OnConfigClose()
    If QuestStateFlag != QuestState
        If QuestState
            If !myQuest.IsRunning()   
               myQuest.Start()
            EndIf
        Else
            If myQuest.IsRunning()   
               myQuest.Stop()
            EndIf
        EndIf
    EndIf
EndEvent

The problem I could see happening with the above is if for some reason the quest hung on start then the MCM OnConfigClose() event would hang, which in turn could affect other MCM menus.

 

OnConfigClose() fires when the user exits my menu, but they may not exit all menus (eg: still in the game menu).

So my hung OnConfigClose() could cause MCM to go screwy until the user exits all menus back to their game.

 

So I changed it so the quest start is fired outside of MCM :smile:

eg:

Event OnConfigClose()
    If QuestStateFlag != QuestState
        (myQuest As myQuestScript).HandleQuest(QuestState)
    EndIf
EndEvent
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...