Author Topic: Recreating complete QFG1 EGA source code  (Read 56303 times)

0 Members and 1 Guest are viewing this topic.

Offline Collector

Re: Recreating complete QFG1 EGA source code
« Reply #30 on: June 15, 2016, 01:17:21 PM »
The one version that I am missing. I guess this is one thing that any repository would have to take into account is different game versions, not that we should expect a decompilation of every version, but for any collaborative effort everyone would need to be working with the same version.
KQII Remake Pic

Offline troflip

Re: Recreating complete QFG1 EGA source code
« Reply #31 on: June 15, 2016, 04:52:19 PM »
I agree. I was trying to figure out how this code could be left in without producing an error (and without introducing new syntax). An asm block might be the way.

I've decided to go with special markup that lets you call a missing export without causing a compile error:

Code: [Select]
(__proc_341_0 "hello")  ; calls export 0 in script 341

Easier to implement than having the decompiler stick an asm block in there. I actually already do something similar with missing kernels (i.e. when I can't find a kernel name to map it to), so in some decompiles you might see:

Code: [Select]
(kernel_121 "blahblah") ; calls kernel 121
Check out my website: http://icefallgames.com
Groundhog Day Competition

Offline OmerMor

Re: Recreating complete QFG1 EGA source code
« Reply #32 on: June 15, 2016, 07:00:40 PM »
Sound great!

Offline OmerMor

Re: Recreating complete QFG1 EGA source code
« Reply #33 on: June 24, 2016, 10:01:09 AM »
Hey Charles,
I found 2 scripts from QfG1. You might want to use them for reverse engineering the game's scripts.

CHARSAVE.SC
Code: [Select]
;charsave.sc   Hero's Quest...save character stats.

(script# CHARSAVE)

(public
   CharSave    0
)

;; Bits in svMiscEquip
(define  SWORD_BIT   1)
(define  CHAIN_BIT   2)
(define  PICK_BIT    4)
(define  TOOL_BIT    8)
(define  MIRROR_BIT  16)
(define  BABA_BIT    32)         
(define  SCORE_BIT   64)

(define  EXTRA_DATA  18)   ; Data items other than stats and name
(define  CHECK_DATA  10)   ; Data items that are in check sums

(local
;; local data for saving hero stats for next game
                         ;;;;;;;;;;;;;;;;;;start;;;;;;;;;;;;;;;;;;
   statsKey = $53        ;;;;;;;;order dependent variables;;;;;;;;
   svCharType            ;;;;;;;;order dependent variables;;;;;;;;
   svHighGold            ;;;;;;;;order dependent variables;;;;;;;;
   svLowGold             ;;;;;;;;order dependent variables;;;;;;;;
   svScore               ;;;;;;;;order dependent variables;;;;;;;;
   svMiscEquip           ;;;;;;;;order dependent variables;;;;;;;;
   [codedStats NUMSTATS] ;;;;;;;;order dependent variables;;;;;;;;
   svDaggers             ;;;;;;;;order dependent variables;;;;;;;;
   svHealing             ;;;;;;;;order dependent variables;;;;;;;;
   svMana                ;;;;;;;;order dependent variables;;;;;;;;
   svStamina             ;;;;;;;;order dependent variables;;;;;;;;
   svGhostOil            ;;;;;;;;order dependent variables;;;;;;;;
   bogus0   = $79        ;;;;;;;;order dependent variables;;;;;;;;
   bogus1   = $86        ;;;;;;;;order dependent variables;;;;;;;;
   checkSum1             ;;;;;;;;order dependent variables;;;;;;;;
   checkSum2             ;;;;;;;;order dependent variables;;;;;;;;
   bogus2   = $43        ;;;;;;;;order dependent variables;;;;;;;;
   bogus3   = $88        ;;;;;;;;order dependent variables;;;;;;;;
   bogus4   = $ad        ;;;;;;;;order dependent variables;;;;;;;;
   bogus5   = $f0        ;;;;;;;;order dependent variables;;;;;;;;
   checkSumKey = $ce     ;;;;;;;;order dependent variables;;;;;;;;
                         ;;;;;;;;;;;;;;;;;;;end;;;;;;;;;;;;;;;;;;;
   check1
   check2
   [YNSTR 5]
   [heroFileName 16]
   [bigStr 400]
   hasSaved              ;TRUE if hero saved
   [str 40]
)

(enum          ;states of saveHero Script
   askSave
   getInfoFileName
   getInfoFileName2
   openFile
   writeHeroInfo
   writeComplete
   tryAgain
   badAnswer
   saveDone
)

;(procedure
;  makeChar
;  makeZero
;  restoreHero
;  convWord
;  convByte
;)
;
;(procedure (makeZero &tmp whichSkill)
;  (HighPrint "make Zero")
;  (for  ((= whichSkill 0))
;        (< whichSkill NUMSTATS)
;        ((++ whichSkill))
;     (= [egoStats whichSkill] 0)
;  )
;  (= [invNum iGold] (= [invNum iSilver] (= score 0)))
;  (ego use: iSword)     
;  (ego use: iChainMail) 
;  (ego use: iLockPick)   
;  (ego use: iThiefKit)   
;  (ego use: iMagicMirror)
;  (Bclr fBabaFrog)     
;  (= [invNum iDagger] 0)
;  (= [invNum iHealingPotion] 0)
;  (= [invNum iManaPotion] 0)
;  (= [invNum iStaminaPotion] 0)
;  (= [invNum iGhostOil] 0)
;  (= score 0)
;  (= heroType 0)
;  (StrCpy @userName {xxxxxxxxxxy})
;  (for  ((= whichSkill 0))
;        (< whichSkill (+ NUMSTATS EXTRA_DATA))
;        ((++ whichSkill))
;     (= [statsKey (+ whichSkill 1)] 0)
;  )
;  (HighPrint "char Zeroed")
;  (return)
;)
;
;(procedure (makeChar &tmp whichSkill)
;  (HighPrint "make Char")
;  (for  ((= whichSkill 0))
;        (< whichSkill NUMSTATS)
;        ((++ whichSkill))
;     (= [egoStats whichSkill] whichSkill)
;  )
;  (= [invNum iDagger] 1)
;  (= [invNum iHealingPotion] 2)
;  (= [invNum iManaPotion] 3)
;  (= [invNum iStaminaPotion] 4)
;  (= [invNum iGhostOil] 5)
;  (= score 432)
;  (HighPrint "Char made")
;  (return)
;)
;
;(procedure (restoreHero &tmp whichSkill)
;  (HighPrint "restore Hero")
;  (if (not (heroinfo open: fRead))
;     (HighPrint "Failure in opening file")
;     (return)
;  )
;  (heroinfo readString: @userName 52)
;  (heroinfo readString: @bigStr 90)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  (for  ((= whichSkill 0))
;        (< whichSkill (+ NUMSTATS EXTRA_DATA))
;        ((++ whichSkill))
;
;     (= [statsKey (+ whichSkill 1)] (convWord [bigStr whichSkill]))
;
;  )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  (for  ((= whichSkill (+ NUMSTATS EXTRA_DATA)))
;        (< 0 whichSkill)
;        ((-- whichSkill))
;
;     (^= [statsKey whichSkill] (& [statsKey (- whichSkill 1)] 127))
;
;  )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  (= check1 checkSumKey)
;  (for  ((= whichSkill 0))
;        (< whichSkill (+ NUMSTATS CHECK_DATA))
;        ((+= whichSkill 2))
;     (= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
;     (+= check1 [statsKey (+ whichSkill 1)])
;  )
;  (= check2 0)
;  (for  ((= whichSkill 1))
;        (< whichSkill (+ NUMSTATS CHECK_DATA))
;        ((+= whichSkill 2))
;     (= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
;     (+= check2 [statsKey (+ whichSkill 1)])
;  )
;  (&= check1 127)
;  (&= check2 127)
;  (if (or (!= check1 checkSum1) (!= check2 checkSum2))
;     (HighPrint "CHECKSUM ERROR")
;  )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;  (for  ((= whichSkill 0))
;        (< whichSkill NUMSTATS)
;        ((++ whichSkill))
;     (= [egoStats whichSkill] [codedStats whichSkill])
;  )
;  (= [invNum iDagger] svDaggers) 
;  (= [invNum iHealingPotion] svHealing) 
;  (= [invNum iManaPotion] svMana)     
;  (= [invNum iStaminaPotion] svStamina) 
;  (= [invNum iGhostOil] svGhostOil)
;  (= [invNum iGold] (+ (* svHighGold 100) svLowGold))
;  (= score (+ svScore 128))
;  (if (& svMiscEquip SCORE_BIT) (+= score 256))
;  (if (& svMiscEquip SWORD_BIT) (= [invNum iSword] 1))
;  (if (& svMiscEquip CHAIN_BIT) (= [invNum iChainMail] 1))
;  (if (& svMiscEquip PICK_BIT)  (= [invNum iLockPick] 1)) 
;  (if (& svMiscEquip TOOL_BIT)  (= [invNum iThiefKit] 1)) 
;  (if (& svMiscEquip MIRROR_BIT) (= [invNum iMagicMirror] 1))
;  (HighPrint "Hero restored")
;  (return)
;)
;
;(procedure (convWord ascii)
;  (return (+ (convByte (>> ascii 8)) (* (convByte (& ascii 255)) 16)))
;)
;
;(procedure (convByte ascii)
;  (cond
;     ((== ascii 32)
;        (return 0)
;     )
;     ((<= 48 ascii 57)
;        (return (- ascii 48))
;     )
;     (else
;        (return (- ascii 87))
;     )
;  )
;)

(instance CharSave of Room
   (properties
      picture  pBlueSkyForCarpet
      horizon  0
      style    IRISOUT
   )
   
   (method (dispose)
      (StatusLine code: dftStatusCode)
      (super dispose:)
   )

   (method (init)
      (StatusLine
         code: endStatus,
         enable:)
      (super init: &rest)
      (cSound stop:)

      ; don't let'm control anything!
      (User canControl: FALSE, canInput: FALSE)
;;    (User canControl: FALSE, canInput: TRUE)

      (self setScript: saveHero)
   )

;  (method (handleEvent event)
;     (if (Said 'quit')
;        (= quit TRUE)
;     )
;     (if (Said 'make/zero')
;        (makeZero)
;     )
;     (if (Said 'make/hero')
;        (makeChar)
;     )
;     (if (and (Said 'restore/hero') hasSaved)
;        (restoreHero)
;     )
;  )
)


(instance heroinfo of File
   (properties
      name  {hq1_hero.sav})
)

(instance saveHero of Script
   (method (changeState newState &tmp whichSkill oldGold)
      (switch (= state newState)
         (askSave
;           (makeChar)
            (Format @heroFileName "a:hq1\_hero.sav")
            (if (>= score 500)
               (HighPrint "CONGRATULATIONS!!__You have successfully
                     completed \"Hero's Quest I:__So You Want To Be
                     A Hero\" with the maximum possible score, 500 of 500!!_
                     We welcome you to the ranks of the few, the proud,
                     the True Heroes!")
            else
               (HighPrint (Format @bigStr "Congratulations!__You have
                     successfully completed \"Hero's Quest I:__So You
                     Want To Be A Hero.\"__Your final score was %d of
                     500 possible Puzzle Points." score))
            )

            (HighPrint "If you have not already done so,
                  we encourage you to play \"Hero's Quest I\" again
                  with the other two character types; many of the
                  puzzles are different, or have alternate solutions.")

            (HighPrint "In the meantime, you are already a winner!__Please
                  insert a writeable disk in your floppy drive to
                  save your winning Hero for use in\n
                  \"Hero's Quest II:__Trial By Fire.\"")
            (self  cue:)
         )

         (getInfoFileName
            (= cycles 2)
         )

         (getInfoFileName2
            (if (GetInput @heroFileName 30
                     {Disk file in which to save your Hero.})
               (heroinfo name: @heroFileName)
               (= cycles 2)
            else
               (self changeState: tryAgain)
            )
         )

         (openFile
            (if (heroinfo open: fTrunc)
               (heroinfo close:)
               (= seconds 2)
            else
               (HighPrint (Format @bigStr "Could not create file -- %s."
                     (heroinfo name?)))
               (self changeState: tryAgain)
            )
         )               

         (writeHeroInfo
            (if (not (heroinfo open: fAppend))
               (self  changeState: tryAgain)
               (return)
            )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            (for  ((= whichSkill 0))
                  (< whichSkill NUMSTATS)
                  ((++ whichSkill))
               (= [codedStats whichSkill] [egoStats whichSkill])
            )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            (= oldGold (+ [invNum iGold] (/ [invNum iSilver] 10)))
            (= svCharType heroType)
            (= svHighGold ( /  oldGold 100))
            (= svLowGold  (mod oldGold 100))
            (= svScore score)
            (= svMiscEquip 0)
            (if (ego has: iSword)         (|= svMiscEquip SWORD_BIT))
            (if (ego has: iChainMail)     (|= svMiscEquip CHAIN_BIT))
            (if (ego has: iLockPick)      (|= svMiscEquip PICK_BIT))
            (if (ego has: iThiefKit)      (|= svMiscEquip TOOL_BIT))
            (if (ego has: iMagicMirror)   (|= svMiscEquip MIRROR_BIT))
            (if (Btst fBabaFrog)          (|= svMiscEquip BABA_BIT))
            (if (< 255 score)             (|= svMiscEquip SCORE_BIT))
            (= svDaggers  [invNum iDagger])
;           (= svHealing  [invNum iHealingPotion])
;           (= svMana     [invNum iManaPotion])
;           (= svStamina  [invNum iStaminaPotion])
            (= svGhostOil [invNum iGhostOil])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            (= checkSum1 checkSumKey)
            (for  ((= whichSkill 0))
                  (< whichSkill (+ NUMSTATS CHECK_DATA))
                  ((+= whichSkill 2))
               (= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
               (+= checkSum1 [statsKey (+ whichSkill 1)])
            )
            (= checkSum2 0)
            (for  ((= whichSkill 1))
                  (< whichSkill (+ NUMSTATS CHECK_DATA))
                  ((+= whichSkill 2))
               (= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
               (+= checkSum2 [statsKey (+ whichSkill 1)])
            )
            (&= checkSum1 127)
            (&= checkSum2 127)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            (for  ((= whichSkill 0))
                  (< whichSkill (+ NUMSTATS EXTRA_DATA))
                  ((++ whichSkill))
               (= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
               (^= [statsKey (+ whichSkill 1)] [statsKey whichSkill])
            )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            (heroinfo writeString: @userName)
            (heroinfo writeString: {\n})
            (for  ((= whichSkill 1))
                  ( < whichSkill (+ NUMSTATS EXTRA_DATA 1))
                  ((++ whichSkill))
               (Format @bigStr "%2x" [statsKey whichSkill])
               (heroinfo writeString: @bigStr)
            )
            (heroinfo writeString: {\n})
            (heroinfo close:)
            (= seconds 2)
         )

         (writeComplete
            (HighPrint "The save character file has been created.__Save
                  this disk for use with \"Hero's Quest II:__Trial By Fire\"
                  from Sierra On-Line.")
            (= hasSaved TRUE)
            (= cycles 2)
         )

         (tryAgain
            (Format @YNSTR "n")
            (if (GetInput @YNSTR 2
                     {If you wish to try saving your character again,
                        type \"y\", then ENTER.__Otherwise type \"n\",
                        then ENTER.}
                  )
               (if (StrCmp @YNSTR {y})
                  (self changeState: saveDone)
               else
                  (= bogus0 $79)
                  (= bogus1 $86)
                  (= bogus2 $43)
                  (= bogus3 $88)
                  (= bogus4 $ad)
                  (= bogus5 $f0)
                  (self changeState: getInfoFileName)
               )
            else
               (= cycles 2)
            )
         )

         (badAnswer
            (HighPrint "Please answer \"y\" or \"n\".")
            (self  changeState: tryAgain)
         )

         (saveDone
            (HighPrint "Thank you for playing \"Hero's Quest I:__So You
                  Want To Be A Hero,\" and congratulations again on
                  winning.__We'll see you again soon in\n
                  \"Hero's Quest II:__Trial By Fire.\"")
            (= quit TRUE)
         )
      )
   )
)

(instance endStatus of Code
   (method (doit strg)
      (Format strg "___Wow!__You're Really A Hero!__[score %d of 500]" score)
   )
)


MAZEBUG.SC
Code: [Select]
; MazeBug class to handle behavior of bugs in wizard's game (Mages' Mazes)

(script# MAZEBUG)

(include wizgame.sh)

(procedure
   setDeltaX
   setDeltaY
)


(class MazeBug kindof Actor
   (properties
      xStep    2
      yStep    2
      startX   0           ; Where to come back after dying
      startY   0
      deadTime 0           ; How long before we resurrect
      otherBug 0           ; I.D. of opponent
      form     smallBug    ; Which type (size) of creature
      smarts   5           ; How far bug thinks/looks ahead
      path     0           ; Which path we're following
   )

   (methods
      highPri        ; Switch to higher priority
      lowPri         ; Switch to lower priority
      changeForm     ; Trigger changes to next size/shape
      fixState       ; Set illegalBits and such based on priority/size
      die            ; Fall into the chasm and start game over
      nearRock       ; Are we close to a rock?
      nearBridge     ; Are we close to a bridge?
      nearLadder     ; Are we close to a ladder?
   )

   (method (init)
      (super  init: &rest)
      (self
         setPri: LowPri,
         fixState:
      )
   )

   (method (highPri)
      (self
         setPri: HighPri,
         fixState:
      )
   )

   (method (lowPri)
      (self
         setPri: LowPri,
         fixState:
      )
   )

   (method (changeForm  &tmp newLoop)
      (= newLoop (+ loop 1))
      (if (> (++ form) largeBug)
         ( = form smallBug)
         (-= newLoop 3)
      )

      (self
         setLoop: newLoop,
         fixState:
      )
      (if (and (!= form mediumBug) (== (self onControl: origin) cGREEN))
         (self  die:)            ; Changing form while on a ladder
      )
   )

   (method (fixState)            ; Account for current creature size
      (= illegalBits cWHITE)
      (if (<= priority LowPri)
         ( |= illegalBits cYELLOW)        ; On lower path
      else
         ( |= illegalBits cLMAGENTA)      ; On upper path
      )

      (switch form
         (smallBug
            ( |= illegalBits cGREEN)      ; Too small for ladders
            ( = moveSpeed (= cycleSpeed 0))
         )
         (mediumBug
            ; Too big to enter tunnels
;;          ( |= illegalBits (| cMAGENTA cLMAGENTA))
            ( |= illegalBits cLMAGENTA)
            ( = moveSpeed (= cycleSpeed 1))
         )
         (largeBug
            ; Too big to enter tunnels or climb ladders (clumsy)
;;          ( |= illegalBits ( | cMAGENTA cLMAGENTA cGREEN))
            ( |= illegalBits ( | cLMAGENTA cGREEN))
            ( = moveSpeed (= cycleSpeed 2))
         )
      )
   )

   (method (doit  &tmp whichControl)
      (cond
         (deadTime
            (if (not (-- deadTime))    ; Time to be resurrected
               (self
                  posn: startX startY,
                  setLoop: (- loop (- form smallBug)),
                  form: smallBug,
                  lowPri:,
                  fixState:
               )
            )
         )
         (else
            (super  doit:)
         )
      )
   )

   (method (canBeHere  &tmp canWe ctrls)
      (if (= canWe (super canBeHere:))
         (= ctrls (self onControl: 0))
         (if (or
               (and (& ctrls cLRED)  (self nearRock: 25))
               (and (& ctrls cLCYAN) (not (self nearBridge: 25)))
               (and (& ctrls cGREEN) (not (self nearLadder: 25)))
            )
            (= canWe FALSE)
         )
      )
      (return canWe)
   )

   (method (die)
      (= deadTime 20)            ; Disappear for 20 cycles
   )

   (method (nearRock dist)       ; Are we close to a rock?
      (return FALSE)             ; Implemented in instance
   )

   (method (nearBridge dist)     ; Are we close to a bridge?
      (return TRUE)              ; Implemented in instance
   )

   (method (nearLadder dist)     ; Are we close to a ladder?
      (return TRUE)              ; Implemented in instance
   )
)


(class MazeMove kindof Motion       ; Motion class for bugs in maze
   (properties
      client      0     ; Actor we are controlling
      caller      0     ; Object to cue when complete/blocked
      x           0     ; Eventual destination
      y           0
      b-moveCnt   0     ; Iterations of doit to skip
      curDir      mazeSW   ; Which way we're going (compass direction)
      prevDir     mazeSW   ; Which way we last went (compass direction)
      ditherTime  0     ; How long we should stand and dither
      completed   0     ; Set to TRUE when move is complete
      altMove     0     ; Kludge to make diagonal moves close to correct
      prevControl 0     ; Which control color we were on last doit cycle
   )

   (methods
      calcDir           ; Calculate compass direction from angle
      doMove            ; Do next step of move
      tryStep           ; Try to take a step
      chooseRoute       ; Find best path for next move(s)
      checkRoute        ; See if specified route works
      setHeading        ; Set client's heading based on compass direction
   )

   ;; Initialize mover -- do *not* do a (super init:)
   (method (init actor xTo yTo toCall &tmp cy theAngle)
      (if (>= argc 1)            (= client actor)
         (if (>= argc 2)         (= x xTo)
            (if (>= argc 3)      (= y yTo)
               (if (>= argc 4)   (= caller toCall)
               )
            )
         )
      )
     
      ;Set actor's heading.
      (client heading: (= theAngle (GetAngle (client x?) (client y?) x y)))
      (= prevDir (= curDir (self calcDir: theAngle)))
     
      (= b-moveCnt 0)
      (if (= cy (client cycler?))      ; Keep cycler in synch with mover
         (cy cycleCnt: 0)
      )
   )
   

   (method (calcDir theHeading)
      (return
         (cond
            (HEADING_N  mazeN)
            (HEADING_NE mazeNE)
            (HEADING_E  mazeE)
            (HEADING_SE mazeSE)
            (HEADING_S  mazeS)
            (HEADING_SW mazeSW)
            (HEADING_W  mazeW)
            (HEADING_NW mazeNW)
         )
      )
   )


   ;; Do next step -- do *not* do a (super doit:)
   (method (doit  &tmp clForm thisControl)
      (if ditherTime
         (-- ditherTime)
         (return)
      )

      (if (> (++ b-moveCnt) (client moveSpeed?))
         (= b-moveCnt 0)

         (= clForm (client form?))
         (= thisControl (client  onControl: origin))
         (if (and (!= thisControl prevControl) (< (client priority?) LowPri))
            (client  setPri: LowPri)      ; (Maybe) just left a tunnel
         )

         (switch thisControl
            (cGREY
               (client  lowPri:)
            )

            (cLBLUE
               (client  highPri:)
            )

            (cGREEN        ; On a ladder -- get off quickly
               (if (not (client  nearLadder: 25))
                  (client  die:)       ; Somebody yanked away our ladder
               )
            )

            (cLCYAN        ; On a bridge, or where one should be
               (if (not (client  nearBridge: 25))
                  (client  die:)       ; Fall into the chasm
               )
            )

            (cBROWN        ; At an intersection -- choose best path
               (self  chooseRoute: FALSE)
            )

            (cLMAGENTA
               (if (== (client priority?) LowPri)
                  (client setPri: (- LowPri 2))
               )
            )

            (cMAGENTA
               (if (== (client priority?) LowPri)
                  (client setPri: (- LowPri 2))
               )
            )
         )

         (= prevControl thisControl)
         (self  doMove:)         ; Make next move (if we can)
      )
     
      ;If our client is where he should be, we terminate.
      (if (and (<= (Abs (- x (client x?))) 2) (<= (Abs (- y (client y?))) 2))
         (self moveDone:)
         (return)
      )
   )


   ; Actually do next step of motion
   (method (doMove &tmp oldX oldY DX DY newDir newAngle mSpeed)
      (if (== (Random 1 10) 7)
         (self setHeading: (= newDir (Random 0 7)))   ; Monkey wrench
      )

      (= oldX (client x?))
      (= oldY (client y?))

      (if (self tryStep: oldX oldY curDir)
         (= prevDir curDir)      ; Don't care about previous bump anymore
      else
         (self  chooseRoute: TRUE)     ; Blocked -- get a new route
         (= mSpeed (client moveSpeed?))
         (= ditherTime (+ 1 mSpeed mSpeed))        ; Dither for a bit
         (client
            signal: (| (client signal?) blocked),
            forceUpd:
         )
         (return FALSE)
      )

      (client forceUpd:)

      (return TRUE)
   )


   (method (tryStep oldX oldY theDir  &tmp DX DY)
      (= DX (client xStep?))
      (= DY (client yStep?))

      (if (== (++ altMove) 2)
         (= altMove 0)
         (if (& curDir 1)        ; Moving at a diagonal
            (<<= DX 1)
            (<<= DY 1)
         )
      )

      (= DX (setDeltaX theDir DX))
      (= DY (setDeltaY theDir DY))

      (client
         x: (+ oldX DX),
         y: (+ oldY DY)
      )

      (BaseSetter client)
      (if (client canBeHere:)
         (return TRUE)
      else
         (if (== (++ altMove) 2)
            (= altMove 0)
         )

         (client
            x: oldX,
            y: oldY
         )

         (BaseSetter client)
         (client
            signal: (| (client signal?) blocked),
            forceUpd:
         )
         (return FALSE)
      )
   )


   (method (chooseRoute wasBlocked
         &tmp theAngle newDir incr aDir sm best cur bestDir other forms dist)

      (= newDir curDir)
      (if (== (Random 1 3) 2)
         (= theAngle (GetAngle (client x?) (client y?) x y))
         (= newDir (self  calcDir: theAngle))
      )

      (= other (client otherBug?))
      (= dist  (client distanceTo: other))
      (if (< dist ChaseRange)
         (= forms (- (client form?) (other form?)))
         (cond
            ((== forms -1)    ; Other bug wants to eat us -- run away!
               (= theAngle
                  (GetAngle (other x?) (other y?) (client x?) (client y?)))
               (= newDir (self  calcDir: theAngle))
            )

            ((and (< dist 12)
                  (== forms 1)
                  (== (client priority?) (other priority?))
               )
               ; Yum -- we caught dinner!
               (other die:)
            )

            ; Oh, what a cute/tasty little bug -- let's chase it!
            ; (if merely "cute", only a 1 in 4 of chasing it)
            ((or (== forms 1) (and (== forms 0) (not (Random 0 3))))
               (= theAngle
                  (GetAngle (client x?) (client y?) (other x?) (other y?)))
               (= newDir (self  calcDir: theAngle))
            )
         )
      )

      ;; Check routes, preferring one selected above.  If none of them
      ;;    work, stick with the pre-chosen one.
;;    (= sm (client smarts?))
      (= bestDir newDir)
      (for ((= incr (= best 0))) (<= incr 4) ((++ incr))
         (= cur (self  checkRoute: (= aDir (mod (+ newDir incr) 8))))
         (if (== aDir (mod (+ curDir 4) 8))
            (-= cur 4)        ; Try not to reverse directions
         )
         (if (> cur best)
            (= best cur)
            (= bestDir aDir)
         )

         (= cur (self  checkRoute: (= aDir (mod (- (+ newDir 8) incr) 8))))
         (if (== aDir (mod (+ curDir 4) 8))
            (-= cur 4)        ; Try not to reverse directions
         )
         (if (> cur best)
            (= best cur)
            (= bestDir aDir)
         )
      )

      (= newDir bestDir)
      (while (and wasBlocked (== curDir newDir))
         (= newDir (Random 0 7))
      )

      (self  setHeading: newDir)
      (client  forceUpd:)
      (return curDir)
   )


   ;; See if endpoint of potential route is legal place for creature to be
   ;;    (look-ahead distance determined by creature's smarts).
   (method (checkRoute theDir  &tmp oldX oldY curX curY sm index)
      (= curX (= oldX (client x?)))
      (= curY (= oldY (client y?)))
      (= sm (/ (client smarts?) 2))
;;    (= sm (- 11 (client smarts?)))      ; Shorter looks are smarter (??)

      (for ((= index 0)) (< index sm) ((++ index))
         (if (not (self tryStep: curX curY theDir))
            (break)
         )
      )

      (client
         x: oldX,
         y: oldY
      )

      (BaseSetter client)
      (return index)
   )


   (method (setHeading newHeading)
      (= prevDir curDir)
      (= curDir  newHeading)
      (client heading:
         (switch newHeading
            (mazeN   0)
            (mazeNE  45)
            (mazeE   90)
            (mazeSE  135)
            (mazeS   180)
            (mazeSW  225)
            (mazeW   270)
            (mazeNW  315)
         )
      )
   )
)


(procedure (setDeltaX theDir DX)
   (switch theDir
      (mazeN   (= DX 0))
      (mazeS   (= DX 0))
      (mazeSW  (= DX (- DX)))
      (mazeW   (= DX (- DX)))
      (mazeNW  (= DX (- DX)))
   )
   (return DX)
)

(procedure (setDeltaY theDir DY)
   (switch theDir
      (mazeN   (= DY (- DY)))
      (mazeNE  (= DY (- DY)))
      (mazeE   (= DY 0))
      (mazeW   (= DY 0))
      (mazeNW  (= DY (- DY)))
   )
   (return DY)
)



Offline lskovlun

Re: Recreating complete QFG1 EGA source code
« Reply #34 on: June 24, 2016, 02:48:20 PM »
I found 2 scripts from QfG1. You might want to use them for reverse engineering the game's scripts.
I knew about the first one... the second one is interesting. It abusesdoes some creative things with the animation system.

Offline Charles

Re: Recreating complete QFG1 EGA source code
« Reply #35 on: June 27, 2016, 10:21:06 PM »
Oooh, it's like Christmas. Those are both really helpful samples. The contents of both I'd already deciphered, especially the CHARSAVE.SC file, but learning the original names of some constants and procedures is extremely nice. I'm updating my sources to use those names now (except for Btst and Bclr... I prefer my names of getEventFlag and clearEventFlag).

Just to give a bit of status update, I had originally decompiled based on the v3.0.7 (I think?) SCICompanion, and had made a tonne of good progress renaming files, procedures and variables, but somewhere long the way, I made a mistake and nothing would compile. I tried backtracking, but had no joy. And then Phil updated the decompiled, so I figured I might as well just start over with decompiling based on the sco and game.ini files I'd already created. That's where I am now. Everything decompiled from scratch with my current variable names. My next step is to replace the constants for things like character class, or walk/run/sneak mode, or TimeOfDay (I.e. Dawn, Midday, sunset, Midnight, etc.) and so on and so forth. Now I'm compiling after each set of constants is replaced, and pushing to my local git server. I've tried allowing outside access, but there's a block somewhere along the chain (router, server, git, ??), and I haven't had a moment to sit down and troubleshoot.

A couple of things I've learned: I'm very impressed with the code for racking up puzzle points. There's a clever array that's significantly long, and they've made wrappers for it so you can toggle each bit in the array individually. At least a couple hundred... Very efficient, since each true/false flag only takes a single bit. So there's a bunch in there for game state stuff (like have you climbed up Henry's cliff side, or destroyed the nest outside the healer. Just everything like that. They also use these flags for puzzle points. So there's a specific procedure you call with the flag, the amount of points, and what character class it applies to (if it's only a specific class). I see a very easy potential to create a kid that lists exactly which points you missed at the end of the game, ala QFG5. Also I learned that if you're a Thief, and you get robbed in the alley, you lose 10 points. So that's fun.

Something else I found... Casting flame dart improves your throwing skill. I believe this is a bug, due to copy/pasta. The throwing file and the flame dart file are both very similar. I want to double check that one though, I didn't spend a lot of time reviewing it the 1st time through.

Offline OmerMor

Re: Recreating complete QFG1 EGA source code
« Reply #36 on: June 28, 2016, 02:46:55 AM »
Thanks for the update - very interesting!

Btst and Bclr are used in many SCI games, and it's not limited to event flags, so you if you don't like the original names, you might want to use bitTest and bitClear etc. Names like getEventFlag is too specific.
EDIT: So I noticed that these functions are actually tied to a specific global flags array. Here's the version from QfG2:
Code: [Select]
(procedure (Btst flagEnum)
   (& [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
;; (DisposeScript FLAGS)
)

(procedure (Bset flagEnum  &tmp oldState)
   (= oldState (Btst flagEnum))
   (|= [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
   oldState
;; (DisposeScript FLAGS)
)

(procedure (Bclr flagEnum  &tmp oldState)
   (= oldState (Btst flagEnum))
   (&= [gameFlags (/ flagEnum 16)] (~ (>> $8000 (mod flagEnum 16))))
   oldState
;; (DisposeScript FLAGS)
)

The puzzle points management is nice and similar to other QfG games. It's a very good idea to add a patch for displaying the missing points! I say go for it.  :)

About the flame dart issue: you might find the discussion in the following ScummVM bug interesting:
https://bugs.scummvm.org/ticket/7133
It's about the problematic spell skill learning in QfG3 and even includes correspondence with Corey Cole.

« Last Edit: July 06, 2018, 09:09:17 AM by OmerMor »

Offline troflip

Re: Recreating complete QFG1 EGA source code
« Reply #37 on: June 28, 2016, 12:54:38 PM »
« Last Edit: June 28, 2016, 04:11:43 PM by troflip »
Check out my website: http://icefallgames.com
Groundhog Day Competition

Offline Charles

Re: Recreating complete QFG1 EGA source code
« Reply #38 on: June 29, 2016, 12:05:12 AM »
All excellent points on keeping it as Btst, Bset and Bclr. Works for me.

I hadn't read anything on the template games documentation yet either, but I see those would be a good resource to use to learn some common conventions, too.

Edited to add, that's an interesting discussion about QFG3. I'd love to tackle some custom patches for that game, some time down the road. Classic problem: too much to do, to little time to do it.
« Last Edit: June 29, 2016, 12:08:14 AM by Charles »

Offline OmerMor

Re: Recreating complete QFG1 EGA source code
« Reply #39 on: June 29, 2016, 07:38:15 AM »
I hadn't read anything on the template games documentation yet either, but I see those would be a good resource to use to learn some common conventions, too.

Yes, you should.
Or even better, go over Sierra's system scripts (the original "template" game): https://github.com/OmerMor/SCI16/tree/master/SYSTEM.
This is their version for SCI16 from 1993 (1.001.099), but it should be pretty similar to the system scripts for QfG1EGA.

I also have earlier versions from SCI0. I'll upload them sometime as well.

Offline Charles

Re: Recreating complete QFG1 EGA source code
« Reply #40 on: July 04, 2018, 01:50:14 PM »
Okay, so after a bunch of restarts then dropping it for almost 2 years, I picked this project back up.

I'm using the latest SCICompanion (built myself from the github source), with Hero's Quest v1.000. My thought being that when this is fully commented, then I can then re-implement the differences between released versions, ending with the QFG1EGA available on gog and steam.

I've made some decisions that may ruffle some feathers. Whenever possible, I'm ditching the defines, variable naming, and file naming conventions originally created by Brian for SCIStudio in favour of those originally used by Sierra.  The SCI16 source code and documentation provided by OmerMor was a huge help in this regard.  Even though it was for a later version of SCI than's used in HQ1, I went with the same assumption OmerMor alluded to earlier... that (for the most part) Sierra wouldn't have completely ditched working code without a good reason... they were a business afterall, and time costs money.  So, I believe I've been able to accurately deduce most of the original global variable names (that weren't game specific).

As of now, I have completely commented/renamed the system scripts, as well as Main.sc. I have replaced all Control references (i.e. onControl and illegalBits) with names, instead of magic numbers.  I've renamed all classes to their original names (i.e. Act is Actor, and Rm is Room, etc.). I've named almost all the HQ-specific global variables, and procedures.

The source code compiles without error, and the game runs.  I haven't done a complete play-through, but I have wandered around and tried a few things. I know there is at least one game error related to either the decompile or the recompile (can't tell which), and possibly more [EDIT: the error is outside Baba Yaga's hut.  I cannot enter the hut.]. I've already corrected one error (which I can share in a later post).  I haven't gone bug hunting yet (other than one game crashing bug in the Waterfall room).  I've been focused on variable/procedure/class renaming, while keeping the game compilable and at least runable. 

This recent stretch has probably been a couple months of finding 10 minutes or an hour here, or an evening there.  I'm a bit burned out on it for the moment, which is a shame, because I've only just gotten to the game proper now.  Anyway, I wanted to share my progress.
« Last Edit: July 04, 2018, 01:53:08 PM by Charles »

Offline Kawa

Re: Recreating complete QFG1 EGA source code
« Reply #41 on: July 04, 2018, 03:02:46 PM »
Consider me impressed.

Offline Charles

Re: Recreating complete QFG1 EGA source code
« Reply #42 on: July 05, 2018, 09:29:25 AM »
Scratch that error with Baba Yaga's hut... that exists in the original game.  There are no known errors in the compilation. But emphasis on known, because I haven't done a full playthrough.

The error I did fix, btw, was in script 82 (Waterfalls.sc).

Here's the code as decompiled (which crashed as soon as it ran, during the room's init):
Code: [Select]
(procedure (localproc_054e &tmp temp0)
(= temp0 0)
(while (< temp0 8)
([local6 temp0]
setLoop: 0
ignoreActors:
x: [local38 temp0]
y: [local46 (= [local14 temp0] (Clone aFallScript))]
init:
setPri: 1
cycleSpeed: 3
setScript: [local14 (= [local6 temp0] (Clone waterFalling))] 0 temp0
)
(++ temp0)
)
)

And here's the correction I made that worked:
Code: [Select]
(procedure (SetWaterfallHighDetail &tmp i)
;CI: NOTE: SCICompanion incorrectly decompiled this script, as follows:
;
;y: [yHighDetail (= [waterfallScripts i] (Clone aFallScript))]
;setScript: [waterfallScripts (= [waterfallProps i] (Clone waterFalling))] 0 i
;
;instead of calling the Cloning and assignment first. This caused this room to
;crash immedaitely upon the room's init.

(= i 0)
(while (< i 8)
(= [waterfallProps i] (Clone waterFalling))
(= [waterfallScripts i] (Clone aFallScript))

([waterfallProps i]
setLoop: 0
ignoreActors:
x: [xHighDetail i]
y: [yHighDetail i]
init:
setPri: 1
cycleSpeed: 3
setScript: [waterfallScripts i] NULL i
)
(++ i)
)
)

Please ignore the change in variable/procedure names.  I'm not sure exactly what the decompiler is trying to do there.

Offline Kawa

Re: Recreating complete QFG1 EGA source code
« Reply #43 on: July 05, 2018, 10:33:35 AM »
Please ignore the change in variable/procedure names.  I'm not sure exactly what the decompiler is trying to do there.
Its best? I mean, those names aren't stored in the final script code, unlike selectors and class names and such.

Offline Charles

Re: Recreating complete QFG1 EGA source code
« Reply #44 on: July 05, 2018, 10:41:54 AM »
Oops, I didn’t mean to phrase that as a single thought. I know the variable names aren’t stored anywhere in the compiled code, and the decompiler does an amazing job substituting reasonable variable names whenever possible.

What I meant was, I’m not sure what the decompiler is trying to do in the first code snippet.  It looks like it’s trying to define a variable in one of the properties of that variable itself?


SMF 2.0.19 | SMF © 2021, Simple Machines
Simple Audio Video Embedder

Page created in 0.074 seconds with 23 queries.