Skip to content

Clear invalid spell selections when converting Hellfire saves to Diablo#8507

Open
morfidon wants to merge 9 commits intodiasurgical:masterfrom
morfidon:fix/3466-converting-hellfire-save-to-diablo-spell
Open

Clear invalid spell selections when converting Hellfire saves to Diablo#8507
morfidon wants to merge 9 commits intodiasurgical:masterfrom
morfidon:fix/3466-converting-hellfire-save-to-diablo-spell

Conversation

@morfidon
Copy link

Closes #3466

Summary

When a Hellfire save is converted to Diablo while a Hellfire-only spell is selected, that selection can survive in an invalid state.

In DevilutionX this can leave the player with a selected spell that has no icon but may still remain usable. In vanilla Diablo, loading such a save can crash.

This change clears invalid spell selections during load/conversion and keeps the player's spell state consistent afterwards.

What changed

  • call ValidatePlayer() before recalculating inventory-derived spell state during load
  • use CalcPlrInv() during loading so spell sources from inventory, scrolls, and charges are rebuilt before validating selections
  • update spell selection validation so SpellType::Spell is no longer treated as automatically valid
  • sanitize invalid selected spells and invalid hotkeys after loading
  • normalize derived runtime spell state after sanitization so stale queued/executed spell data does not survive
  • make LoadHotkeys() explicitly handle both legacy hotkey data and missing or malformed hotkey blobs safely

Why this approach

This issue is triggered by Hellfire -> Diablo conversion, but the real problem is broader than a single Hellfire spell id surviving the rewrite.

After conversion, the selected spell and hotkeys may no longer be valid for the rebuilt player state. Because of that, this patch does not special-case only Hellfire spell ids. Instead, it validates spell selections against the player's actual available spell sources after loading:

  • learned spells
  • skills
  • scrolls
  • charges

This follows the intent discussed in #3466:

  • validate the player before recalculating spell-related state
  • rebuild spell availability from inventory
  • clear invalid readied spells
  • do the same for LoadHotkeys()

The extra runtime-state normalization is intentional. Clearing only _pRSpell / _pRSplType would still leave stale queuedSpell, executedSpell, or spellFrom state behind, which could keep the player in a partially invalid state after sanitization.

Updated load/conversion flow

Now, during Hellfire -> Diablo conversion:

  1. the player is loaded
  2. ValidatePlayer() removes invalid Hellfire-only learned spell state
  3. CalcPlrInv() rebuilds spell availability from inventory, including scrolls and charges
  4. hotkeys and the selected spell are loaded
  5. any selection that is no longer valid for the rebuilt player state is cleared
  6. runtime spell state is synchronized from the sanitized selection

This ensures that invalid Hellfire spell selections do not survive conversion as selected, queued, or executed spell state.

Tests

Added tests to cover:

  • spell selection validation by source
  • rejecting invalid spell/type combinations
  • clearing invalid spell selections when reading from save
  • sanitizing and normalizing state when the hotkeys file is missing
  • sanitizing invalid selections from legacy hotkey data
  • preserving valid legacy scroll selections
  • persisting sanitized selections after Hellfire -> Diablo rewrite

Expose ValidatePlayer directly instead of routing load-time validation through a one-off wrapper.
Add a persisted-state regression test that rewrites a Hellfire-origin save under Diablo and verifies invalid spell selections stay cleared after saving and reloading hotkeys.
Move hotkey loading out of InitPlayer so it runs only after spell sources are rebuilt. Also make LoadHotkeys sanitize the selected spell and hotkeys itself, including when the sidecar hotkeys file is missing, and resync queuedSpell in the player load paths.
Add a slot-aware LoadHotkeys overload and use it in player load paths so hero data and hotkeys are always read from the same save number.

This removes the saveNum/gSaveNumber mismatch risk in pfile_read_player_from_save while keeping the existing wrapper for current-player call sites.
Introduce SyncPlayerSpellStateFromSelections and call it in hotkey load paths so queued/executed spell metadata and spellFrom are reset consistently after sanitization.

This centralizes post-load spell-state normalization, removes duplicated per-call-site resync code, and adds writehero regression tests for missing hotkeys data and legacy 4-slot hotkeys format handling.
Add a regression test that loads a valid scroll selection from legacy hotkeys data and verifies the first cast path still works with normalized spellFrom state by consuming the scroll.

Also replace legacy hotkeys test helper casts from int8_t to int32_t for clearer and safer intent when serializing SpellID values.
Recognize legacy hotkeys blobs by their exact payload size so the loader does not misinterpret them as the newer header-based format. Update the legacy scroll regression test to validate selection preservation against engine-backed scroll availability without depending on UI redraw side effects.
Treat only the exact legacy hotkeys blob size as the old format and validate the new-format header and payload before reading them. Keep the legacy scroll regression test aligned with actual scroll availability so the first cast path is verified after load.
- Rename LoadHotkeysLegacyFormatPreservesValidScrollAndFirstCastConsumesIt to LoadHotkeysLegacyFormatPreservesValidScrollSelection to better reflect actual test behavior
- Remove pfile_read_player_from_save_preserves_valid_spell_selections test as it's covered by other tests and is nice-to-have rather than critical
- Keep core logic tests: IsPlayerSpellSelectionValidChecksSpellSources, IsPlayerSpellSelectionValidRejectsInvalidSelections
- Keep important regression/integration tests: pfile_read_player_from_save_clears_invalid_spell_selections, LoadHotkeysLegacyFormatSanitizesInvalidSelections, DiabloRewritePersistsSanitizedSpellSelectionsFromHellfireSave
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Converting hellfire save to diablo while having a hellfire spell selected

1 participant