Skip to content

Commit ea8217b

Browse files
Pyrhameclaude
andcommitted
v1.5.1: remote activate + stolen items + bug fixes
New features: - Scanner remote activate (G key / LB+A on gamepad): activate the selected scanner object as if pressing E next to it. Picks up items, opens containers, uses doors (teleports through), triggers activators. Capped at 2000 units. Autowalk gamepad combo moved from LB+A to LB+X to make room. MCM migration v6 -> v7 auto-applies the remap on save load - Stolen item announcement in inventory/container/barter/gift menus. Helper IsItemStolen() iterates ExtraDataList for ExtraOwnership pointing to non-player owner (more reliable than IsOwnedBy) Bug fixes: - Map markers no longer disappear after autowalk. The Papyrus cleanup was deleting any XMarker (baseForm 0x10), including vanilla city/fort markers. Now only deletes markers explicitly created via PlaceAtMe (tracked via TempMarkerRef variable) - Locations category in scanner: stable across navigation, distances computed from cached marker position for far-away unloaded markers - Inventory: items sharing a name prefix are now reliably announced. Selection-change detection uses GFx item pointer in addition to text, so adjacent items with same display name (e.g. soul gems) are detected Under the hood: - ScannerActivateCurrent invalidates picked items via post-pickup check (800ms after dispatch); invalidation preserves scanner cursor by capturing neighbor formID before clearing the picked item - Gamepad bindings infrastructure extended with new RemoteActivate slot (configurable in MCM Gamepad page) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b9ddf35 commit ea8217b

16 files changed

Lines changed: 1204 additions & 621 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
### New features
66
- Scanner: containers and corpses the player has already opened are now announced with a `looted` flag, independently of whether they still contain items. Useful to avoid re-visiting a container or corpse you already checked. The flag persists across saves (stored in the SKSE cosave). When the game engine respawns a reference (e.g. dungeon chests refill after ~30 days), the `looted` flag is automatically cleared for that reference, so the scanner correctly announces the re-filled chest as unlooted. A safety fallback also clears any entry older than 30 in-game days in case the engine reset event was missed
77
- Scanner: item sub-filters (Weapons, Armor, Potions, Food, Ingredients, Scrolls, Books, Soul Gems, Miscellaneous) are now available in the `All` category too, in addition to the `Items` category. The `All` category without sub-filter still lists everything as before; when a sub-filter is active, only the matching items are shown. Easier to search for a specific item type without having to switch to the Items category first
8+
- **Scanner: remote activate (G key / LB+A on gamepad)** — press G (keyboard) or LB+A (gamepad) outside any menu to activate the object currently selected in the scanner, exactly as if you had pressed E next to it. Picks up items, opens containers (chests, corpses), uses doors (teleports you through), and triggers activators (levers, pedestals, switches). Capped at 2000 units to prevent activating something across half a cell by accident. Saves a lot of time when navigating dungeons or scavenging dropped loot. **Note for gamepad users**: autowalk has moved from LB+A to **LB+X** to make room for the new combo. Both buttons are remappable in the MCM under the Gamepad page
9+
- **Stolen item announcement** — items marked as stolen (red text in vanilla, red color in SkyUI) are now announced as such in the inventory, container, barter and gift menus. The word `stolen` is spoken right after the item name (e.g. "Iron sword, stolen, value 10, weight 9"), so you immediately know if you are about to equip, drop or sell something that could get you fined or arrested
810

911
### Changes
1012
- Active effects shortcut moved from Ctrl+H to Shift+H — Ctrl+H was conflicting with the vanilla stand/crouch shortcut some players remap to Ctrl. Press Shift+H in game to hear your active effects (poisons, diseases, buffs, etc.)
1113

14+
### Bug fixes
15+
- **Map markers no longer disappear after autowalk** — a critical bug introduced in v1.5.1 caused vanilla map markers (cities, forts, dungeons, etc.) to be permanently deleted when stopping an autowalk if you had previously placed a custom map marker via P. The autowalk cleanup was identifying any XMarker (baseForm 0x10) as one of our temporary markers and deleting it, but vanilla map markers share the same baseForm. The cleanup now only deletes markers we explicitly created via PlaceAtMe. If you lost markers from a previous autowalk session, they need to be rediscovered (visit the location once)
16+
- **Locations category in the scanner is now stable** — the `Locations` category had several bugs that made it unreliable: some discovered locations were missing from the list, others would briefly appear then disappear when navigating between objects, and the announced distance could be incorrect for distant markers. The list is now consistent across category switches and navigation, and distances are recalculated from the cached marker position so far-away locations (cities, forts) keep an accurate distance even when their cell is not loaded
17+
- **Inventory: items sharing a name prefix are now reliably read** — when navigating between two adjacent items whose displayed names started identically (e.g. "Soul Gem (empty)" right next to "Soul Gem (filled)", or two enchanted variants of the same base weapon), the second item was sometimes skipped silently because the change-detection only compared the announcement text. The scanner now also tracks the underlying GFx item pointer, so any selection change is detected and announced, even when the visible names look the same
18+
1219
### Under the hood
1320
- Scanner distance calculation rewritten for stability — announced distances could jump wildly (e.g. 3000 units then 500 without the player moving) for items dropped on the ground, because the engine returned the physics center of mass which bounces frame by frame. The scanner now reads the visible mesh position, which is stable. Quest distances are now computed the same way as the rest of the scanner (3D euclidean) instead of switching between 2D and 3D depending on context. Items no longer show a "ghost" distance once they leave your loaded area — they disappear from the list until they come back in range
1421
- `Lock nearest enemy` (X key) and the quest-target fallback now announce `above` or `below` when the target is on a different floor, consistent with the rest of the scanner

autowalk/SkyrimTTS_AutoWalk.pex

698 Bytes
Binary file not shown.

autowalk/SkyrimTTS_AutoWalk.psc

Lines changed: 87 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ EndEvent
1616

1717
; === Internal state ===
1818
ObjectReference CurrentTarget
19+
ObjectReference TempMarkerRef ; XMarker cree par PlaceAtMe (mode coords), a supprimer au stop
1920
float fStopDistance = 100.0
2021
bool IsWalking = false
2122
bool MountedMode = false ; true si le walk en cours utilise le mode "mounted"
@@ -55,20 +56,25 @@ Function OnWalkToTarget(int aiFormID, float afStopDistance, float afX = 0.0, flo
5556

5657
fStopDistance = afStopDistance
5758

58-
; Create a temp XMarker at the target position
59+
; Create a temp XMarker at the target position. Tracker dans TempMarkerRef
60+
; pour pouvoir le supprimer au stop sans toucher aux markers de carte vanilla
61+
; (qui sont AUSSI des XMarker baseForm 0x10 mais ne doivent JAMAIS etre supprimes).
5962
ObjectReference tempMarker = PlayerRef.PlaceAtMe(Game.GetForm(0x10), 1, true, true)
6063
tempMarker.SetPosition(afX, afY, afZ)
64+
TempMarkerRef = tempMarker
6165
StartWalkToRef(tempMarker, afStopDistance)
6266
endIf
6367
EndFunction
6468

6569
; === MOUNTED MODE — Called from C++ when player is on a horse ===
66-
; OPTION B : on redirige l'alias Traveler vers le CHEVAL au lieu du joueur.
67-
; Notre Travel package est attaché au Traveler alias (via ALPC dans l'ESP), donc
68-
; en changeant l'alias on transfère l'exécution du package au cheval. Le cheval est
69-
; un Actor avec son propre AI complet, il peut exécuter un Travel package nativement.
70-
; Pas besoin de Game.SetPlayerAIDriven : c'est le cheval qui agit, pas le joueur.
70+
; Reproduit le comportement v1.4 qui marchait : meme dispatch qu'a pied
71+
; (SetPlayerAIDriven sur le joueur) avec en plus un EvaluatePackage sur
72+
; le cheval pour reveiller son AI. Le moteur Skyrim gere le couple
73+
; joueur+cheval : l'IA joueur AI-driven sur un cheval pilote le couple.
74+
; Le 6e parametre aiMountFormID permet de recuperer le cheval pour
75+
; appeler EvaluatePackage dessus (pas d'API Papyrus pour PlayerRef.GetMount).
7176
Function OnWalkToTargetMounted(int aiFormID, float afStopDistance, float afX, float afY, float afZ, int aiMountFormID)
77+
Debug.Trace("SkyrimTTS:AutoWalkMounted - OnWalkToTargetMounted called: aiFormID=" + aiFormID + " stopDist=" + afStopDistance + " coords=(" + afX + "," + afY + "," + afZ + ") mountID=" + aiMountFormID)
7278
; Récupérer le cheval
7379
Form mountForm = Game.GetForm(aiMountFormID)
7480
if mountForm == None
@@ -112,11 +118,21 @@ Function OnWalkToTargetMounted(int aiFormID, float afStopDistance, float afX, fl
112118

113119
ObjectReference tempMarker = PlayerRef.PlaceAtMe(Game.GetForm(0x10), 1, true, true)
114120
tempMarker.SetPosition(afX, afY, afZ)
121+
TempMarkerRef = tempMarker
115122
StartWalkToRefMounted(tempMarker, afStopDistance, mountActor)
116123
endIf
117124
EndFunction
118125

119126
; === MOUNTED MODE start helper ===
127+
; Reproduit le comportement v1.4 qui marchait : on dispatche le Travel package
128+
; sur le joueur via SetPlayerAIDriven(true) comme en mode a pied. Le moteur
129+
; Skyrim gere le couple joueur+cheval : l'IA joueur AI-driven sur un cheval
130+
; pilote indirectement le cheval.
131+
;
132+
; On ajoute juste un EvaluatePackage sur le cheval pour le reveiller,
133+
; et on flagge MountedMode pour que le stop fasse le bon cleanup.
134+
;
135+
; Pas de Traveler.ForceRefTo(mount) (option A jamais validee empiriquement).
120136
Function StartWalkToRefMounted(ObjectReference target, float stopDist, Actor mountActor)
121137
if IsWalking
122138
StopWalkingInternal(false)
@@ -143,24 +159,26 @@ Function StartWalkToRefMounted(ObjectReference target, float stopDist, Actor mou
143159
mountActor.StopCombat()
144160
mountActor.StopCombatAlarm()
145161

146-
; OPTION B — Étape clé : rediriger l'alias Traveler vers le CHEVAL.
147-
; Notre Travel package est attaché à l'alias Traveler (via ALPC dans l'ESP).
148-
; En changeant la ref de l'alias, le package va s'exécuter sur le cheval
149-
; au lieu du joueur. Le cheval est un Actor avec son propre AI, il peut
150-
; exécuter le package nativement sans avoir besoin d'AIDriven.
151-
Traveler.ForceRefTo(mountActor)
152-
Debug.Trace("SkyrimTTS:AutoWalkMounted - Traveler alias redirected to mount: " + Traveler.GetReference())
153-
154-
; Remplir DstMarker comme d'habitude (la cible ne change pas)
162+
; Remplir DstMarker avec la cible -> le Travel package lit cet alias
155163
DstMarker.ForceRefTo(target)
156164
Debug.Trace("SkyrimTTS:AutoWalkMounted - DstMarker set to " + DstMarker.GetReference())
157165

158-
; Réveiller l'AI du cheval pour qu'il prenne en compte son nouveau package
159-
mountActor.EvaluatePackage()
160-
Debug.Trace("SkyrimTTS:AutoWalkMounted - mount.EvaluatePackage called")
166+
; Reset propre de l'AI joueur avant de la reactiver en AIDriven.
167+
; Equivalent Papyrus du SetAIDriven(false)+EvaluatePackage que le C++ v1.4
168+
; faisait avant le dispatch et qu'on a supprime cote C++ pour eviter le
169+
; crash BSShaderAccumulator. Ici en Papyrus c'est safe (le moteur gere le
170+
; timing). Sans ce reset, AIDriven semble silencieusement ignore quand le
171+
; joueur est sur un cheval (le contrôle reste au joueur, pas a l'IA).
172+
Game.SetPlayerAIDriven(false)
173+
PlayerRef.EvaluatePackage()
174+
Debug.Trace("SkyrimTTS:AutoWalkMounted - pre-reset AIDriven=false done")
161175

162-
; PAS de Game.SetPlayerAIDriven : on n'a pas besoin de driver le joueur,
163-
; c'est le cheval qui est maintenant l'acteur du package Travel.
176+
; Take away player control, let AI run the Travel package.
177+
; Sur un cheval, AIDriven sur le joueur fait que l'IA pilote le couple
178+
; joueur+cheval (comportement v1.4 confirme).
179+
Game.SetPlayerAIDriven(true)
180+
PlayerRef.EvaluatePackage()
181+
Debug.Trace("SkyrimTTS:AutoWalkMounted - AI driven, EvaluatePackage called")
164182

165183
IsWalking = true
166184
RegisterForSingleUpdate(CheckInterval)
@@ -220,41 +238,29 @@ Function StopWalkingInternal(bool abNotify)
220238
; Clear DstMarker so the Travel package deactivates
221239
DstMarker.Clear()
222240

241+
; Cleanup identique pour mounted et a pied : on libere SetPlayerAIDriven et on
242+
; reevalue le package du joueur. Le cheval revient a son AI normale au prochain
243+
; tick automatique du moteur (pas besoin d'EvaluatePackage explicite : on n'a
244+
; plus la ref du cheval ici, et de toute facon c'est l'IA du joueur qui pilotait).
245+
Game.SetPlayerAIDriven(false)
246+
PlayerRef.EvaluatePackage()
223247
if MountedMode
224-
; OPTION B : restaurer l'alias Traveler vers le PlayerRef pour que les
225-
; futurs autowalks à pied fonctionnent correctement.
226-
; On capture aussi la ref actuelle (le cheval) pour pouvoir EvaluatePackage
227-
; dessus afin qu'il revienne à son comportement normal.
228-
Actor mountActor = Traveler.GetReference() as Actor
229-
Traveler.ForceRefTo(PlayerRef)
230-
Debug.Trace("SkyrimTTS:AutoWalkMounted - Traveler restored to PlayerRef")
231-
232-
if mountActor != None
233-
mountActor.EvaluatePackage()
234-
Debug.Trace("SkyrimTTS:AutoWalkMounted - mount.EvaluatePackage called (cleanup)")
235-
endIf
236-
237-
; Pas besoin de SetPlayerAIDriven(false) puisqu'on ne l'a jamais activé en mounted.
238-
PlayerRef.EvaluatePackage()
239248
Debug.Trace("SkyrimTTS:AutoWalkMounted - Stopped")
240249
else
241-
; Mode à pied : restaurer le contrôle joueur.
242-
; Note : on ne touche PAS à SpeedMult — on ne l'a jamais boosté, donc rien à restaurer.
243-
Game.SetPlayerAIDriven(false)
244-
PlayerRef.EvaluatePackage()
245250
Debug.Trace("SkyrimTTS:AutoWalk - Stopped")
246251
endIf
247252

248-
; Supprimer le temp marker si c'est un XMarker créé par PlaceAtMe (mode coordonnées).
249-
; Sans ça, les markers s'accumulent dans la save à chaque autowalk et finissent
250-
; par surcharger le scene graph → crash progressif.
251-
if CurrentTarget != None && CurrentTarget != PlayerRef
252-
Form baseForm = CurrentTarget.GetBaseObject()
253-
if baseForm != None && baseForm.GetFormID() == 0x10
254-
CurrentTarget.Disable()
255-
CurrentTarget.Delete()
256-
Debug.Trace("SkyrimTTS:AutoWalk - Deleted temp XMarker")
257-
endIf
253+
; Supprimer UNIQUEMENT le temp marker qu'on a cree nous-memes via PlaceAtMe (mode coords).
254+
; ATTENTION : on ne peut PAS se contenter de tester baseForm.GetFormID() == 0x10
255+
; sur CurrentTarget car les markers de carte vanilla (Fort Dragon, villes, donjons)
256+
; sont AUSSI des XMarker de baseForm 0x10 — les supprimer les ferait disparaitre
257+
; definitivement de la carte. On utilise TempMarkerRef qu'on a explicitement
258+
; rempli a la creation du tempMarker, donc on est sur de ne supprimer que ca.
259+
if TempMarkerRef != None
260+
TempMarkerRef.Disable()
261+
TempMarkerRef.Delete()
262+
Debug.Trace("SkyrimTTS:AutoWalk - Deleted temp XMarker")
263+
TempMarkerRef = None
258264
endIf
259265

260266
CurrentTarget = None
@@ -327,6 +333,10 @@ Function OnLoadGameReset()
327333
IsWalking = false
328334
MountedMode = false
329335
CurrentTarget = None
336+
; Nettoyer un eventuel tempMarker orphelin de la save (mais NE PAS le delete :
337+
; la save pourrait avoir une ref obsolete pointant vers un marker de carte vanilla
338+
; suite a un ancien bug. On reset juste le pointeur sans toucher a la ref.)
339+
TempMarkerRef = None
330340
currentLoopInstance = 0
331341
Debug.Trace("SkyrimTTS:AutoWalk - OnLoadGameReset: full state cleanup")
332342
EndFunction
@@ -408,3 +418,31 @@ Function OnStopLoopSound()
408418
currentLoopInstance = 0
409419
endIf
410420
EndFunction
421+
422+
; === REMOTE ACTIVATE — Called from C++ when player presses G in scanner ===
423+
; Activates the target reference as if the player pressed E next to it.
424+
; Works on items (pickup), containers (open), doors (teleport), activators
425+
; (lever/button/etc.). The C++ side enforces a max distance check before dispatch.
426+
Function OnRemoteActivate(int aiTargetFormID)
427+
Debug.Trace("SkyrimTTS:RemoteActivate - Called with FormID: " + aiTargetFormID)
428+
if aiTargetFormID == 0
429+
Debug.Trace("SkyrimTTS:RemoteActivate - FormID is 0, abort")
430+
return
431+
endIf
432+
Form targetForm = Game.GetForm(aiTargetFormID)
433+
if targetForm == None
434+
Debug.Trace("SkyrimTTS:RemoteActivate - GetForm returned None for FormID: " + aiTargetFormID)
435+
return
436+
endIf
437+
ObjectReference targetRef = targetForm as ObjectReference
438+
if targetRef == None
439+
Debug.Trace("SkyrimTTS:RemoteActivate - Form is not an ObjectReference: " + aiTargetFormID)
440+
return
441+
endIf
442+
if targetRef.IsDeleted()
443+
Debug.Trace("SkyrimTTS:RemoteActivate - Target is deleted: " + aiTargetFormID)
444+
return
445+
endIf
446+
Debug.Trace("SkyrimTTS:RemoteActivate - Activating ref: " + aiTargetFormID)
447+
targetRef.Activate(PlayerRef)
448+
EndFunction

autowalk/SkyrimTTS_MCM.pex

577 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)