Difference between revisions of "Talk:GetDialogueTarget - Actor"

From the CreationKit Wiki
Jump to navigation Jump to search
imported>Zartar
imported>Haravikk
 
(7 intermediate revisions by 3 users not shown)
Line 45: Line 45:
EndFunction
EndFunction


; Print a message if the player is in dialogue with Bob.
Event SomeEvent()
if (GetPlayerDialogueTarget() == Bob)
        ; Print a message if the player is in dialogue with Bob.
  Debug.Trace("The player is in dialogue with Bob!")
        if (GetPlayerDialogueTarget() == Bob)
endIf
                Debug.Trace("The player is in dialogue with Bob!")
        endIf
EndEvent
</source>
</source>


== Notes ==
== Notes ==
This usually finds the actor the player is in dialogue with but more testing is required. It works very well for me, so far... I tend to modify this by passing in the player's reference and lowering iLoopCount but the version I posted is safe and convenient.  
This usually finds the actor the player is in dialogue with but more testing is required. It works very well for me, so far... I tend to modify this by passing in the player's reference and lowering iLoopCount but the version I posted is safe and convenient.  
EDIT: jbezorg's alternate method is flawless in interior cells, I would recommend it but it is worth noting that the method may fail in exterior cells if the player is speaking to an NPC that is in another cell (this could happen if the player is standing very close to a cell border). For exterior cells it may be safer to use my method but increase both the loop count and the radius of the find random actor function. ~[[Zartar]]
== Alternative Method ==
Same usage, slower, but in theory the examples above could miss the Dialogue target by testing the same NPC several times. Unlikely with a 200 unit radius but it also cannot test outside the 200 unit radius and I've had arresting city guards enter dialogue with the player at a much greater distance. ~[[jbezorg]]
<source lang="papyrus">
;Requires SKSE.
Actor Function GetPlayerDialogueTarget() global
Actor kPlayerRef = Game.GetPlayer()
Actor kTargetRef = None
Actor kNthRef    = None
Cell kCell      = kPlayerRef.GetParentCell()
Int iType        = 43 ; kNPC = 43
Int iIndex      = kCell.GetNumRefs( iType )
While iIndex && !kTargetRef
iIndex -= 1
kNthRef = kCell.GetNthRef( iIndex, iType ) as Actor
If kNthRef != kPlayerRef && kNthRef.IsInDialogueWithPlayer()
kTargetRef = kNthRef
EndIf
EndWhile
Return kTargetRef
EndFunction
</source>
== Comprehensive Alternative Method ==
This is a potential method that needs testing. I haven't been able to test it, but its goal is to fix the single-cell restriction which limits the above alternative method. With this, it will test the player's cell. If that doesn't work, it will drop an xMarker, move it out to 'Radius' away from the player, and then orbit that marker around the player checking what cells it ends up in, and then it tests those cells as well until it finds that answer or it goes full circle. It looks slow, but whenever the xMarker is in a cell that's already been tested, it skips that, speeding it up considerably. iStep is the amount of degrees the marker moves. ~[[Xander9009]]
<source lang="papyrus">
;Requires SKSE.
Static Property XMarker Auto
Actor Function GetPlayerDialogueTarget(float fRadius = 500.0, int iStep = 5) global
Actor kPlayerRef = Game.GetPlayer()
Actor kTargetRef = None
Actor kNthRef    = None
Cell kCell      = kPlayerRef.GetParentCell()
Int iType        = 43 ; kNPC = 43
Int iIndex      = kCell.GetNumRefs( iType )
Int Degrees      = 360
 
While iIndex && !kTargetRef
iIndex -= 1
kNthRef = kCell.GetNthRef( iIndex, iType ) as Actor
If kNthRef != kPlayerRef && kNthRef.IsInDialogueWithPlayer()
kTargetRef = kNthRef
EndIf
EndWhile
 
If !kTargetRef
ObjectReference kXMarker = kPlayerRef.PlaceAtMe(XMarker)
Cell[] kCellA = New Cell[10]
kCellA[0] = kCell
kXMarker.MoveTo(kPlayerRef, fRadius)
iIndex = kCellA.Length
While Degrees > 0 && !kTargetRef
While iIndex
iIndex -= 1
If kCellA[iIndex] == kXMarker.GetParentCell()
iIndex = -1
EndIf
EndWhile
If iIndex != -1
iIndex = 0
int iEmptyIndex
While iIndex < kCellA.Length && !iEmptyIndex
If !kCellA[iIndex]
iEmptyIndex = iIndex
EndIf
iIndex += 1
EndWhile
kCellA[iEmptyIndex] = kXMarker.GetParentCell()
iIndex = kCell.GetNumRefs( iType )
While iIndex && !kTargetRef
iIndex -= 1
kNthRef = kCell.GetNthRef( iIndex, iType ) as Actor
If kNthRef != kPlayerRef && kNthRef.IsInDialogueWithPlayer()
kTargetRef = kNthRef
EndIf
EndWhile
EndIf
Degrees -= iStep
Float PosX = Math.Cos(Degrees)*fRadius
Float PosY = Math.Sin(Degrees)*fRadius
kXMarker.MoveTo(kPlayerRef, PosX, PosY)
EndWhile
kXMarker.Delete()
EndIf
Return kTargetRef
EndFunction
</source>


== See Also ==
== See Also ==
Line 60: Line 157:
*[[Bethesda Tutorial Dialogue]]
*[[Bethesda Tutorial Dialogue]]
*[[Bethesda Tutorial Advanced Dialogue]]
*[[Bethesda Tutorial Advanced Dialogue]]
== Additional Optimisations ==
I've found myself needing to grab the player's dialogue target recently, and I've found that the Game.[[GetCurrentCrosshairRef]]() function can usually get the dialogue target as long it triggers quickly enough. For example:
<source lang="papyrus">
Event OnInit()
    RegisterForMenu("Dialogue Menu")
EndEvent
Event OnMenuOpen(String asMenu)
    If asMenu == "Dialogue Menu" ; don't need this if this is the only menu we care about
        Actor akActor = Game.GetCurrentCrosshairRef() as Actor
        If !akActor.IsInDialogueWithPlayer()
            ; Perform a search with Game.FindRandomActorFromRef or search by Cell (see examples above)
        ; Else ; Got it one!
        EndIf
        If akActor != None ; Got it one, or search ended with a result
            ; Do something with akActor
        EndIf
    EndIf
EndEvent
</source>
It can be a bit hit and miss if the dialogue is already open, but it's generally pretty reliable, so the code won't usually need any of the extra checks.
Man I wish Bethesda had just added a function for this, or at the very least given us more useful search functions (ones that didn't include the search target in the result would have been nice, or one to grab several matches at once, e.g- the nearest 10 NPCs). Oh well! -- [[User:Haravikk|Haravikk]] ([[User talk:Haravikk|talk]]) 2020-02-27T17:37:41 (EST)

Latest revision as of 17:37, 27 February 2020

Obtains the actor the player is currently in dialogue with.

Syntax[edit source]

Actor Function GetPlayerDialogueTarget() non-native

Source:

Actor Function GetPlayerDialogueTarget()
	Actor kPlayerDialogueTarget
        Actor kPlayerRef = Game.GetPlayer()
	Int iLoopCount = 10
	While iLoopCount > 0
		iLoopCount -= 1
		kPlayerDialogueTarget = Game.FindRandomActorFromRef(kPlayerRef , 200.0)
		If kPlayerDialogueTarget != kPlayerRef && kPlayerDialogueTarget.IsInDialogueWithPlayer() 
			Return kPlayerDialogueTarget
		EndIf
	EndWhile
        Return None
EndFunction

Parameters[edit source]

None.

Return Value[edit source]

The actor the player is currently in dialogue with (if any).

Examples[edit source]

;This is a custom function so you have to include it in your script.
Actor Function GetPlayerDialogueTarget()
	Actor kPlayerDialogueTarget
        Actor kPlayerRef = Game.GetPlayer()
	Int iLoopCount = 10
	While iLoopCount > 0
		iLoopCount -= 1
		kPlayerDialogueTarget = Game.FindRandomActorFromRef(kPlayerRef , 200.0)
		If kPlayerDialogueTarget != kPlayerRef && kPlayerDialogueTarget.IsInDialogueWithPlayer() 
			Return kPlayerDialogueTarget
		EndIf
	EndWhile
        Return None
EndFunction

Event SomeEvent()
        ; Print a message if the player is in dialogue with Bob.
        if (GetPlayerDialogueTarget() == Bob)
                Debug.Trace("The player is in dialogue with Bob!")
        endIf
EndEvent

Notes[edit source]

This usually finds the actor the player is in dialogue with but more testing is required. It works very well for me, so far... I tend to modify this by passing in the player's reference and lowering iLoopCount but the version I posted is safe and convenient.

EDIT: jbezorg's alternate method is flawless in interior cells, I would recommend it but it is worth noting that the method may fail in exterior cells if the player is speaking to an NPC that is in another cell (this could happen if the player is standing very close to a cell border). For exterior cells it may be safer to use my method but increase both the loop count and the radius of the find random actor function. ~Zartar

Alternative Method[edit source]

Same usage, slower, but in theory the examples above could miss the Dialogue target by testing the same NPC several times. Unlikely with a 200 unit radius but it also cannot test outside the 200 unit radius and I've had arresting city guards enter dialogue with the player at a much greater distance. ~jbezorg

;Requires SKSE.
Actor Function GetPlayerDialogueTarget() global
	Actor kPlayerRef = Game.GetPlayer()
	Actor kTargetRef = None
	Actor kNthRef    = None

	Cell kCell       = kPlayerRef.GetParentCell()
	Int iType        = 43 ; kNPC = 43
	Int iIndex       = kCell.GetNumRefs( iType ) 
	
	While iIndex && !kTargetRef
		iIndex -= 1
		kNthRef = kCell.GetNthRef( iIndex, iType ) as Actor
		If kNthRef != kPlayerRef && kNthRef.IsInDialogueWithPlayer()
			kTargetRef = kNthRef
		EndIf
	EndWhile
	
	Return kTargetRef
EndFunction

Comprehensive Alternative Method[edit source]

This is a potential method that needs testing. I haven't been able to test it, but its goal is to fix the single-cell restriction which limits the above alternative method. With this, it will test the player's cell. If that doesn't work, it will drop an xMarker, move it out to 'Radius' away from the player, and then orbit that marker around the player checking what cells it ends up in, and then it tests those cells as well until it finds that answer or it goes full circle. It looks slow, but whenever the xMarker is in a cell that's already been tested, it skips that, speeding it up considerably. iStep is the amount of degrees the marker moves. ~Xander9009

;Requires SKSE.
Static Property XMarker Auto

Actor Function GetPlayerDialogueTarget(float fRadius = 500.0, int iStep = 5) global
	Actor kPlayerRef = Game.GetPlayer()
	Actor kTargetRef = None
	Actor kNthRef    = None

	Cell kCell       = kPlayerRef.GetParentCell()
	Int iType        = 43 ; kNPC = 43
	Int iIndex       = kCell.GetNumRefs( iType ) 
	Int Degrees      = 360
  
	While iIndex && !kTargetRef
		iIndex -= 1
		kNthRef = kCell.GetNthRef( iIndex, iType ) as Actor
		If kNthRef != kPlayerRef && kNthRef.IsInDialogueWithPlayer()
			kTargetRef = kNthRef
		EndIf
	EndWhile
  
	If !kTargetRef
		ObjectReference kXMarker = kPlayerRef.PlaceAtMe(XMarker)
		Cell[] kCellA = New Cell[10]
		kCellA[0] = kCell
		kXMarker.MoveTo(kPlayerRef, fRadius)
		iIndex = kCellA.Length
		While Degrees > 0 && !kTargetRef
			While iIndex
				iIndex -= 1
				If kCellA[iIndex] == kXMarker.GetParentCell()
					iIndex = -1
				EndIf
			EndWhile
			If iIndex != -1
				iIndex = 0
				int iEmptyIndex
				While iIndex < kCellA.Length && !iEmptyIndex
					If !kCellA[iIndex]
						iEmptyIndex = iIndex
					EndIf
					iIndex += 1
				EndWhile
				kCellA[iEmptyIndex] = kXMarker.GetParentCell()
				iIndex = kCell.GetNumRefs( iType )
				While iIndex && !kTargetRef
					iIndex -= 1
					kNthRef = kCell.GetNthRef( iIndex, iType ) as Actor
					If kNthRef != kPlayerRef && kNthRef.IsInDialogueWithPlayer()
						kTargetRef = kNthRef
					EndIf
				EndWhile
			EndIf
			Degrees -= iStep
			Float PosX = Math.Cos(Degrees)*fRadius
			Float PosY = Math.Sin(Degrees)*fRadius
			kXMarker.MoveTo(kPlayerRef, PosX, PosY)
		EndWhile
		kXMarker.Delete()
	EndIf
	Return kTargetRef
EndFunction

See Also[edit source]

Additional Optimisations[edit source]

I've found myself needing to grab the player's dialogue target recently, and I've found that the Game.GetCurrentCrosshairRef() function can usually get the dialogue target as long it triggers quickly enough. For example:

Event OnInit()
    RegisterForMenu("Dialogue Menu")
EndEvent

Event OnMenuOpen(String asMenu)
    If asMenu == "Dialogue Menu" ; don't need this if this is the only menu we care about
        Actor akActor = Game.GetCurrentCrosshairRef() as Actor
        If !akActor.IsInDialogueWithPlayer()
            ; Perform a search with Game.FindRandomActorFromRef or search by Cell (see examples above)
        ; Else ; Got it one!
        EndIf
        If akActor != None ; Got it one, or search ended with a result
            ; Do something with akActor
        EndIf
    EndIf
EndEvent

It can be a bit hit and miss if the dialogue is already open, but it's generally pretty reliable, so the code won't usually need any of the extra checks.

Man I wish Bethesda had just added a function for this, or at the very least given us more useful search functions (ones that didn't include the search target in the result would have been nice, or one to grab several matches at once, e.g- the nearest 10 NPCs). Oh well! -- Haravikk (talk) 2020-02-27T17:37:41 (EST)