As I detailed in the
"What are we working on?" thread earlier, I've modded QFG1EGA to implement a "Retry" feature when the Hero dies.
I was absolutely floored by the positive encouragement I've received in the community, so I wanted to share how I implemented it, so others could also implement in their games (or mod existing Sierra games).
My goal with "Retry" was to remove a potential point of frustration for new (and old) players being punished for exploring the game and trying things. However, I didn't want to eliminate the deaths entirely, because they are a fundamental part of the Sierra experience, and are part of each Sierra game's unique charm.
My particular solution was to replace game-ending deaths with an additional achievement system, which not only keeps track of which deaths the user encountered but also displays them (like QFG1EGA's inventory screen).
I chose a "minimally invasive" approach, whereby I only reverted the specific events that caused the death (and any items directly branched off from that death, if the death wasn't instant), leaving the rest of the users experiences intact.
I had to modify/add five things to make this happen:
- game.sh (modified)
- main.sc (modified)
- menu.sc (modified)
- DeathSheet.sc (added)
- any script that calls the EgoDead function
To keep track of each unique death, I created an Enum. Because this can grow fairly large (QFG1EGA had 79 Unique Deaths), I decided to create a new file and reference it in game.sh, rather than putting everything into game.sh itself.
;These are death flags. They're used to track which ways the user has died.
;They will be shown at the end of the game.
; unique deaths
(define DIE_RESTART -2) ;show Restore, Restart, Quit; (same as not specifying anything at all) instead of Retry, Restore, Quit)
(define DIE_RETRY -1) ;show a Retry, Restore, Quit
(define DIE_NOFLAG -1) ; a retry is shown, but nothing is flagged.
;explicitly define the starting number, ending number and death count, which will be used in the DeathSheet script.
(define DIE_START 450)
(define DIE_COUNT 79)
(define DIE_END 528)
;there are roughly 100 deaths, so we'll reserve flags 450-550 for them (with room for 50 more in expansion up to flag 600)
(enum 450
DIE_NOSTAMINA
DIE_PICKNOSE
DIE_ARRESTED
DIE_NIGHTGAUNT
)
Next up is modifying the EgoDeath function in main.sc. Keeping track of the different deaths is done with the eventFlags variable, and Btst, Bclr, Bset. I don't know if every Sierra game includes them or not, so if they're not present, you'll need to add them. the eventFlags variable must be an array. Each byte of the array can hold 16 bits (or 16 individual flags). QFG1EGA allocated 50 bytes (800 flags), and didn't use nearly all of them, so I was able to just use them without allocating any more space.
(procedure (Bset bit)
(= [eventFlags (/ bit 16)]
(|
[eventFlags (/ bit 16)]
(>> $8000 (mod bit 16))
)
)
)
(procedure (Bclr bit)
(= [eventFlags (/ bit 16)]
(&
[eventFlags (/ bit 16)]
(~ (>> $8000 (mod bit 16)))
)
)
)
(procedure (Btst bit)
(return
(&
[eventFlags (/ bit 16)]
(>> $8000 (mod bit 16))
)
)
)
Then the EgoDeath function. In QFG1EGA it's the first global function in main.sc, so I assume that's constant across all Sierra games.
(enum 1
RESTORE
RESTART
QUIT
RETRY
)
(procedure (EgoDead what how &tmp printRet)
;stops all sounds, plays the death music, and gives the player a choice:
; Restore, Restart, Quit or
; Retry, Restore, Quit
;
; 'what' decides what we're doing: Restart or Retry.
; if 'what' == DIE_RETRY (-1), it's a Retry death;
; if 'what' == DIE_RESTART (-2), it's a Restart death (explicitly defined).
; any other values are assumed to be the original EgoDead function, for a Restart death (implicitly defined),
; and should be fully passed to the Print procedure.
;
; 'how' is this specific death's flag, to be set in the eventFlags variable, via the Bset function
;
; (CI: NOTE: This procedure is a recreation, based on Eric Oaklands's SCI01 template)
; https://github.com/EricOakford/SCI01-Template/blob/master/src/Main.sc
; it has been modified to more closely match the original asm, which is commented out but intact above,
; then has been further modified to support a Retry mode.
(HandsOff)
(Wait 100)
(theGame setCursor: normalCursor TRUE)
(sounds eachElementDo: #stop)
(if (!= deathMusic NULL)
(music number: deathMusic priority: 15 init: play:)
)
;if we're specifying a way the player died, then we'll set the flag for that death method.
(if (and (== what DIE_RETRY) (!= how DIE_NOFLAG))
(++ deathCount)
(= prevDeathNum how) ;we're going to flag out *every* time the hero dies.
(if (not (Btst how))
(Bset how)
(++ deathCountUnique)
)
)
(repeat
(= printRet
(cond
;1st priority: the new Retry dialog
((== what DIE_RETRY)
(Print &rest
#width 250
#button { Retry_} RETRY
#button {Restore} RESTORE
#button { Quit_} QUIT
)
)
;2nd priority: an explicit DIE_RESTART flag with no second parameter
((== what DIE_RESTART)
(Print how &rest
#width 250
#button {Restore} RESTORE
#button { Restart_} RESTART
#button { Quit_} QUIT
)
)
;finally, any original script EgoDeath scripts
((>= what 0)
(Print what how &rest
#width 250
#button {Restore} RESTORE
#button { Restart_} RESTART
#button { Quit_} QUIT
)
)
)
)
(switch printRet
(RESTORE ;restore
(theGame restore:)
)
(RESTART ;restart
(theGame restart:)
)
(QUIT ;quit
(= quit TRUE)
(break)
)
(RETRY ;retry
(if (!= deathMusic NULL)
(music stop:)
)
(HandsOn)
(break)
)
)
)
)
If you wanted, you could stop here and compile your game without changing anything else. All existing calls to EgoDead will continue to function as they always have, bringing up the RESTORE/RESTART/QUIT dialog.
I'll describe the DeathSheet and actually implementing the new EgoDead function in game script in the next post.
EDIT: I forgot, I also added a couple of global variables to main.sc. There were several unused variables in QFG1EGA, so I was able to just rename one of them, without adding any more.
deathCount ;added by CI: the total number of times Ego has died (shown on the Death List)
deathCountUnique ;added by CI: the number of unique ways Ego has died (shown on the Death List)
prevDeathNum ;added by CI: Used to highlight the most recent death in the Death List.