[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Apple Games Disassembly Project - Ultima 1
Holy mother of God... Thank you!!!
I'll get another JACE cheat up for this one as soon as I get the rest of the mockingboard kinks out.
-B
On Thursday, May 10, 2012 2:48:54 PM UTC-5, Michael wrote:
> Apple Games Disassembly Project - Ultima 1
>
> Curious how Ultima 1 rendering works?
> Curious why the original Ultima 1 runs like crap?
> (That's a technical term for 'slow'. =)
> Curious about learning how to optimize a 6502 map renderer?
>
> Since I couldn't find any *good* technical notes
> about the original Ultima 1 for the Apple ...
>
> ... for your viewing pleasure:
>
> Michael's Ultima 1 Annotated Technical Notes
>
>
> Table of Contents:
> 0. Required Materials
> 1. View outdoor tiles
> 2. Map Viewer
> 3. Fast Map Drawing
> 4. Annotated original DrawMap
> 5. Monster Shapes
> 6. Bootstrapping
> 7. Applesoft Variables
> 8. Bugs
> 9. Misc.
>
>
> 0. Required Materials
> = = = = = = = = = =
> (x) Ultima 1 (Original 1981 DOS3.3 version) disks/images
> Notes:
> I mounted drive 1 with: ultima_1.dsk.gz
> I mounted drive 2 with: ultima_2.dsk.gz
> from Asimov: /games/rpg/ultima/ultima_I/
> After unzipping if they don't have the '.dsk'
> extension rename them so they do.
> (x) Apple ][ (real or emulator)
>
> You can tell which version you have by the border
> No map border = original 1981
> Blue map border = re-released 1986 remake
>
>
> 1. View outdoor tiles
> = = = = = = = = = = =
>
> 1. Boot Ultima 1
> 2. At the ] prompt press CTRL-RESET twice
> (AppleWin: Ctrl-F2)
> 3. Type
> (or copy and use Shift-Insert to paste into AppleWin)
> BLOAD ULTSHAPES,A$800
> BLOAD DRAW 64.OBJ,A$A00
> CALL-151
> 6:9 5 80
> 1000:00 10 20 30 40 50 60 70
> 1040:80 90 A0 B0 C0 D0 E0 F0
> F3E2G
> C052
> A00G
>
> To get back to Applesoft
> C053
> <Ctr-C> RETURN
> TEXT
>
> Tiles: (in hex)
> 00 Water
> 10 Land
> 20 Woods
> 30 Mountains
> 40 Castle (top-down)
> 50 Sign
> 60 Town/City
> 70 Dungeon
> 80 Fighter (and Avatar)
> 90 Horse
> A0 Cart
> B0 Raft
> C0 Pirate Ship
> D0 Hover Car
> E0 Space Shuttle
> F0 Time Machine
>
> To draw the map from Applesoft:
> CALL 2560
>
>
> 2. More Fun - Map Viewer
> = = = = = = = = = = = =
>
> First, load the necessary data and code...
> BLOAD ULTSHAPES,D1,A$800
> BLOAD DRAW 64.OBJ,D1,A$A00
> BLOAD BTERRA0,D2,A$1000
>
> Then enter this program to view the world =)
>
> CALL-151
> - - - - - 8< - - - - -
> C00:20 E2 F3 A9 81 85 08 85
> C08:EB D0 20 AD 8B 0A C9 0A
> C10:D0 04 A9 0C D0 06 C9 0C
> C18:D0 05 A9 0A 8D 8B 0A A5
> C20:EB 49 01 85 EB AA 9D D2
> C28:BF D0 06 A9 20 85 06 85
> C30:07 20 00 0A A5 06 20 DA
> C38:FD A9 A0 20 ED FD A5 07
> C40:20 DA FD A9 A0 20 ED FD
> C48:AE C5 0C E8 8A 20 ED FD
> C50:20 8E FD AD 00 C0 10 FB
> C58:8D 10 C0 C9 8D F0 47 C9
> C60:8B F0 43 C9 AF F0 3B C9
> C68:8A F0 37 C9 88 F0 2E C9
> C70:95 F0 26 C9 A0 F0 B2 C9
> C78:9B F0 8E C9 D1 F0 2C C9
> C80:B1 90 D0 C9 B5 B0 CC AA
> C88:CA 8E C5 0C A2 00 BD B8
> C90:0C F0 0C 20 BD 9E E8 D0
> C98:F5 E6 06 E6 06 C6 06 18
> CA0:90 8F E6 07 E6 07 C6 07
> CA8:18 90 F5 A0 03 A9 00 C6
> CB0:B8 91 B8 88 10 FB 60 EA
> CB8:84 C2 CC CF C1 C4 A0 C2
> CC0:D4 C5 D2 D2 C1 BE AC C1
> CC8:A4 B1 B0 B0 B0 8D 00
> - - - - - 8< - - - - -
> C00G
>
> Map Viewer Keys
> RETURN Move Up (or Up arrow)
> / Move Down (or Down arrow)
> <- Move Left
> -> Move Right
> Space Teleport to center of map
> 1 Load map 0 (Lands of Lord British)
> 2 Load map 1 (Lands of the Feudal Lords)
> 3 Load map 2 (Lands of the Dark Unknown)
> 4 Load map 3 (Lands of Danger and Despair)
> ESC Toggle fullscreen
> Q Exit
>
> Note: Prints X Y W, where W is the map/world last loaded
>
> For map details:
> http://shrines.rpgclassics.com/pc/ultima1/overworld.shtml
>
> Annoted Ultima 1 (original) MapViewer 1e
>
> C00:20 E2 F3 JSR HGR
> C03:A9 81 LDA #81 ; sprite #80 = Fighter (vehicle =
> none), #1 = MixedMode
> C05:85 08 STA $8 ; player vehicle
> C07:85 EB STA $EB ; FullScreenFlag
> C09:D0 20 BNE .warp
> C0B:AD 8B 0A TFS LDA $0A8B ; have custom DrawMap Patch
> v4?
> C0E:C9 0A CMP #A
> C10:D0 04 BNE .mix
> C12:A9 0C LDA #C
> C14:D0 06 BNE .doFS
> C16:C9 0C .mix CMP #C
> C18:D0 05 BNE .tfs
> C1A:A9 0A LDA #A
> C1C:8D 8B 0A .doFS STA $0A8B
> C1F:A5 EB .tfs LDA $EB ; ToggleFullScreen
> C21:49 01 EOR #1
> C23:85 EB .fs STA $EB ; FullScreen
> C25:AA TAX
> C26:9D D2 BF STA $BFD2,X ; $BFD2+$80 = $C052 or $C053
> C29:D0 06 BNE .draw ; don't reset player position
> C2B:A9 20 .warp LDA #20 ; reset player to center of map
> C2D:85 06 STA PlayerX
> C2F:85 07 STA PlayerY
> C31:20 00 0A .draw JSR DrawMap
> C34:A5 06 LDA $06
> C36:20 DA FD JSR PRBYTE ; $FDDA
> C39:A9 A0 LDA ' '
> C3B:20 ED FD JSR COUT ; $FDED
> C3D:A5 07 LDA $07
> C40:20 DA FD JSR PRBTE ; $FDDA
> C43:A9 A0 LDA ' '
> C45:20 ED FD JSR COUT ; $FDED
> C48:AE C5 0C LDX LOAD+#D ; last loaded map, bterra#
> C4B:E8 INX
> C4C:8A TXA
> C4D:20 ED FD JSR COUT ; $FDED
> C50:20 8E FD JSR CROUT ; $FD8E -> $FDED
> C53:AD 00 C0 .key LDA Key
> C56:10 FB BPL .key
> C58:8D 10 C0 STA KeyStrobe
> C5B:C9 8D CMP kRETURN ; #$8D Return
> C5D:F0 47 BEQ .up
> C5F:C9 8B CMP kUP ; #$8B Ctrl-K ^
> C61:F0 43 BEQ .up : |
> C63:C9 AF CMP kSLASH ; #$AF /
> C65:F0 3B BEQ .dn ; |
> C67:C9 8A CMP kDOWN ; #$8A Ctrl-J v
> C69:F0 37 BEQ .dn
> C6B:C9 88 CMP kLEFT ; #$88 Ctrl-H <--
> C6D:F0 2E BEQ .lEFT
> C6F:C9 95 CMP kRIGHT ; #$95 Ctrl-U -->
> C71:F0 26 BEQ .rIGHT
> C73:C9 A0 CMP kSPACE ; #$A0 ' '
> C75:F0 B2 BEQ .pos
> C77:C9 9B CMP kESC ; #$9B ESC
> C79:F0 8E BEQ TFS ; ToggleFullScreen
> C7B:C9 D1 CMP 'Q' ; #$D1 'Q'
> C7D:F0 2C BEQ .done
> C7F:C9 B1 CMP '1' ; < '1'
> C81:90 D0 BCC .key
> C83:C9 B5 CMP '5' ; >= '5'
> C85:B0 CC BCS .key
> C87:AA TAX
> C88:CA DEX
> C89:8E C5 0C STX LOAD+#D ; bterra#
> C8C:A2 00 LDX #0
> C8E:BD B8 0C .load LDA $0CC0,X
> C91:F0 0C BEQ .2draw ; to lazy to calculate .draw offset
> C93:20 BD 9E JSR $9EBD ; DOS3.3 -> $9EBD CSW
> C96:E8 INX
> C97:D0 F5 BNE load
> C99:E6 06 .right INC PlayerX
> C9B:E6 06 INC PlayerX
> C9D:C6 06 .left DEC PlayerX
> C9F:18 .2draw CLC
> CA0:90 8F BCC .draw
> CA2:E6 07 .dn INC PlayerY
> CA4:E6 07 INC PlayerY
> CA6:C6 07 .up DEC PlayerY
> CA8:18 CLC
> CA9:90 F5 BCC .2draw+1 ; to lazy to calculate .draw offset
> CAB:A0 03 .done LDY #3 ; clear rest of input line (may have
> had a BLOAD)
> CAD:A9 00 LDA #0 ; Applesoft: D7EB: LDY #2 LDA
> ($B8),Y --> if line #0 then END
> CAF:C6 B8 DEC $B8 ; Monitor: get 'CR' ($8D) from
> 'C00G'
> CB1:91 B8 .clear STA ($B8),Y ; w/o adjust, would get 'A' from
> BLOAD...
> CB3:88 DEY ; we can't store '$8D' as Applesoft
> would assume 'PLOT' token
> CB4:10 FB BPl .clear ; $0CB3
> CB6:60
> CB7:EA NOP ; pad out to $CB8
> CB8:84 LOAD DB $84 ; Ctrl-D for DOS3.3
> CB9:C2 CC CF C1 C4 ASC "BLOAD BTERRA>,A$1000" ; $BE '>' + 1 --> '?'
> $BF
> A0 C2 D4 C5 D2
> D2 C1 BE AC C1
> A4 B1 B0 B0 B0
> CCD:8D 00 DB $8D, $8D, $00
>
> AppleWin Symbols
> DB LOAD CB8:CCE
>
> SYM MapView = C00
> SYM .mix = C16
> SYM .doFS = C1C
> SYM .tfs = C1F
> SYM .fs = C2D
> SYM .warp = C2B
> SYM .draw = C31
> SYM .key = C53
> SYM .load = C8E
> SYM .right = C99
> SYM .left = C9D
> SYM .2draw = C9F
> SYM .down = CA2
> SYM .up = CA6
> SYM .done = CAB
> SYM .clear = CB1
>
>
> 3. Fast Map Drawing
> = = = = = = = = = =
>
> You can definitely tell Richard Garriot was
> still cutting his teeth learning 6502 assembly.
>
> Ultima 1 outdoor drawing is SLOW because:
>
> - it *always* draw an extra 2 bottom rows of the outside map
> even though those are always covered by
> the 4 line command history and status window!
> - it draws the 14x16 sprites in 2 column passes;
> redundant HGR Y calculations at $A6E
> - The GetMapPtr at $A31 uses a SLOW Y*64 calculation
> - it doesn't use a HGR Y table
> - The map tiles are organized by column, not scanline
> This means the blitter is horribly inefficient.
> (The enhanced version of Ultima 1 uses a scanline blitter! =)
> - It always draws the map tile under the avatar and THEN
> draws the vehicle instead of ONLY drawing the vehicle
> for the player tile. This causes flicker when moving.
>
> Here is a faster and smaller version of DrawMap.
> - About tTwice as fast as original
> - Fixes the vehicle flicker
> - Smaller!
> - Annotated source is provided
> - Only draws 10 visible tile rows
> - Uses a mini HGR Y lookup table
>
> CALL-151
> - - - - - 8< - - - - -
> A00:38 A5 07 E9 05 85 D7 38
> A08:A5 06 E9 09 85 EF A2 00
> A10:86 09 A5 EF 85 D6 A0 00
> A18:BD 90 0A 85 FE BD 9C 0A
> A20:85 FF A2 00 86 FC A5 D6
> A28:C5 06 D0 0C A5 D7 C5 07
> A30:D0 04 A5 08 D0 1E A5 D6
> A38:C9 40 B0 1B A5 D7 C9 40
> A40:B0 15 6A 66 FC 6A 66 FC
> A48:09 10 85 FD A5 FC 65 D6
> A50:85 FC A1 FC 29 F0 AA A5
> A58:FE 85 FA A5 FF 85 FB BD
> A60:00 09 91 FA C8 BD 00 08
> A68:91 FA 88 E8 18 A5 FB 69
> A70:04 C9 40 90 E8 A5 FA 49
> A78:80 C5 FE D0 DC E6 D6 C8
> A80:C8 C0 28 D0 9D E6 D7 A6
> A88:09 E8 E0 0A D0 82 60 EA
> 0A90:00 00 00 00 28 28 28 28 50 50 50 50
> 0A9C:20 21 22 23 20 21 22 23 20 21 22 23
> - - - - - 8< - - - - -
> A00G
>
> If you Want to patch a copy of your "Master" disk
> UNLOCK DRAW 64.oBJ
> BLOAD DRAW 64.OBJ,A$A00
> Enter the above in...
> BSAVE DRAW 64.OBJ,A$A00,L$200
> LOCK DRAW 64.oBJ
>
> Notes:
> For some reason DRAW 64.OBJ also includes
> an Applesoft DRAW shape table at $B00.
> Don't ask me why since the game also
> loads them via:
> BLOAD OUT.SHAPES,A$B00
>
> Annotated Optimized DrawMap ver 4c:
> 0A00:38 DrawMap SEC
> 0A01:A5 07 LDA $7 ; Player.Y
> 0A03:E9 05 SBC #5 ; World to MapWindow.Top
> 0A05:85 D7 STA TileY ; CenterY (10 Tiles Tall)
> 0A07:38 SEC
> 0A08:A5 06 LDA $6 ; Player.X
> 0A0A:E9 09 SBC #9 ; World to MapWindow.Left
> 0A0C:85 EF STA TileLeft ; CenterX (20 Tiles Across)
> 0A0E:A2 00 LDX #0 ;
> 0A10:86 09 drawRow STX Row ; Row is zero based
> 0A12:A5 EF LDA TileLeft
> 0A14:85 D6 STA TileX
> 0A16:A0 00 LDY #0 ; NextDrawColumn
> 0A18:BD 90 0A LDA HGRYLO,X
> 0A1B:85 FE STA $FE
> 0A1D:BD 9C 0A LDA HGRYHI,X
> 0A20:85 FF STA $FF
> 0A22:A2 00 drawCols LDX #0 ; default Water Tile (00)
> 0A24:86 FC STX MAP.PTR ; Init MapTilePtrL
> 0A26:A5 D6 LDA TileX
> 0A28:C5 06 CMP PlayerX ; CurTileX == PlayerX ?
> 0A2A:D0 0C BNE clipX2
> 0A2C:A5 D7 LDA TileY ; CurTileY == PlayerY ?
> 0A2E:C5 07 CMP PlayerY
> 0A30:D0 04 BNE clipX
> 0A32:A5 08 doVehicle LDA Vehicle ; ($08) is always > #$80
> 0A34:D0 1E BNE maskTile ; could also use A6 08 LDX
> Vehicle 30 21 BMI drawTile for speed
> 0A36:A5 D6 clipX LDA TileX
> 0A38:C9 40 clipX2 CMP #40 ; Win.X > 64 ?
> 0A3A:B0 1B BCS drawTile
> 0A3C:A5 D7 LDA TileX ; 0 00cdefgh 00000000
> 0A3E:C9 40 CMP #40 ; Win.Y > 64 ?
> 0A40:B0 15 BCS drawTile ; get a free Clear Carry :)
> ; CLC carry Acc/$FD $FC
> 0A42:6A ROR ; h 000cdefg 00000000
> 0A43:66 FC ROR $FC ; 0 000cdefg h0000000
> 0A45:6A ROR ; g 0000cdef h0000000
> 0A46:66 FC ROR $FC ; 0 0000cdef gh000000
> 0A48:09 10 OR #10 ; $FD.$FC max = $0FFF
> 0A4A:85 FD STA $FD ; Map64x64 at $1000..$1FFF
> 0A4C:A5 FC LDA $FC
> 0A4E:65 D6 ADC $D6
> 0A50:85 FC STA $FC
> 0A52:A1 FC LDA ($FC,X) ; CurTile
> 0A54:29 F0 maskTile AND #F0
> 0A56:AA TAX
> 0A57:A5 FE drawTile LDA $FE
> 0A59:85 FA drawHalf STA $FA
> 0A5B:A5 FF LDA $FF
> 0A5D:85 FB draw8 STA $FB
> 0A5F:BD 00 09 LDA $0900,X
> 0A62:91 FA STA ($FA),Y ; draw even col
> 0A64:C8 INY
> 0A65:BD 00 08 LDA $0800,X
> 0A68:91 FA STA ($FA),Y ; draw odd col
> 0A6A:88 DEY
> 0A6B:E8 INX
> 0A6C:18 CLC
> 0A6D:A5 FB LDA $FB
> 0A6F:69 04 ADC #4 ; next screen row
> 0A71:C9 40 CMP #40 ; done 8 rows?
> 0A73:90 E8 BCC draw8
> 0A75:A5 FA LDA $FA
> 0A77:49 80 EOR #80 ; optimization: ADC #80
> 0A79:C5 FE CMP $FE
> 0A7B:D0 DC BNE drawHalf ; done all 16 scanlines?
> 0A7D:E6 D6 INC TileX
> 0A7F:C8 INY
> 0A80:C8 INY
> 0A81:C0 28 CPY #$28 ; drawn all tiles acrosss?
> 0A83:D0 9D BNE drawCols
> 0A85:E6 D7 INC TileY
> 0A87:A6 09 LDX Row ;
> 0A89:E8 INX
> 0A8A:E0 0A CPX #A ; done 10 tile rows?
> 0A8C:D0 82 BNE drawRow ; ... must ... reach ...
> 0A8E:60
> 0A8F:EA NOP ; pad to $A90
> 0A90:00 00 00 00 HGRYLO DB 00,00,00,00 ; dest address of every
> 16 scanlines
> 0A94:28 28 28 28 DB 28,28,28,28
> 0A98:50 50 50 50 DB 50,50,50,50
> 0A9C:20 21 22 23 HGRYHI DB 20,21,22,23
> 0AA0:20 21 22 23 DB 20,21,22,23
> 0AA4:20 21 22 23 DB 20,21,22,23
>
> Applewin symbol commands
> DB PlayerX 6
> DB PlayerY 7
> DB Vehicle 8 // player vehicle sprite #
> DB Row 9
> DB TileX D6 // = (PlayerX - 9)
> DB TileY D7 // = (PlayerY - 5)
> DB TileLeft EF
> DB HGR.DST FA
> DB MAP.PTR FC
> DB HGR.BASE FE
> DB HGRYLO A90:A9B
> DB HGRYHI A9C:AA7
> DB TilesOdd 800
> DB TilesEvn 900
> DB Map 1000
>
> SYM DrawMap = A00
> SYM drawRow = A10
> SYM drawCols = A22
> SYM drawPlayer = A32
> SYM clipX = A36
> SYM clipX2 = A38
> SYM maskTile = A54
> SYM drawTile = A57
> SYM drawHalf = A59
> SYM draw8 = A5D
>
> In order to get any more speed the tiles
> at $800 .. $9FF would need to be swizzled.
>
> Ideally, there would also be 4 map scrollers that scroll
> the HGR page "in-place"
> - scroll left
> - scroll right
> - scroll down
> - scroll up
> And the corresponding 4 draw partial map functions:
> - draw right edge
> - draw left edge
> - draw top edge
> - draw bottom edge
>
> This is left as an exercise for the reader. =)
>
>
> 4. Annotated original DrawMap
> = = = = = = = = = = = = = = =
>
> If you want a "good" example of how NOT to
> write a 2D map renderer here is my
> annotated Ultima 1 for the original Draw Map code.
>
> File: DRAW 64.OBJ
>
> 0A00:38 DrawMap SEC
> 0A01:A5 07 LDA $7 ; Player.Y
> 0A03:E9 05 SBC #5 ; World to MapWindow.Top
> 0A05:85 D7 STA $D7 ; CurTile.Y
> 0A07:A9 00 LDA #00
> 0A09:85 FE STA $FE ; HGR.BASE (dest tile ptr)
> 0A0B:A9 20 drawTriad LDA #20 ; every 64 rows dest Y start
> address $20xx
> 0A0D:85 FF STA $FF ;
> 0A0F:A0 00 drawRow LDY #0 ; left column
> 0A11:38 SEC
> 0A12:A5 06 LDA $06 ; Player.X
> 0A14:E9 09 SBC #9 ; World to MapWindow.Left
> 0A16:85 D6 STA $D6 ; CurTile.X
> 0A18:A9 09 drawCol LDA #9 ; start with even column of source
> TileAddr.Hi
> 0A1A:AD 68 0A STA TileAdrH; Self Modifying Code: GET
> TileScanLine
> 0A1D:A9 00 LDA #0 ; CurTile = default to Water Tile
> (0)
> 0A1F:48 PHA ; save curtile
> 0A20:A5 D6 LDA $D6 ; Win.X > 64 ?
> 0A22:C9 40 CMP #40
> 0A24:B0 28 BCS ClipWin ; $0A4E
> 0A26:A5 D7 LDA $D7 ; Win.Y > 64 ?
> 0A28:c9 40 CMP #40
> 0A2A:B0 22 BCS ClipWin ; $0A4E
> 0A2C:68 PLA ; toss CurTile
> 0A2D:85 FD STA $FD ; Init MapTilePtrH
> 0A2F:A5 D7 LDA $D7 ; MapTileAddr(y,x) = $1000 + y *
> 64 + X
> 0A31:0A ASL ; inefficient y*64
> 0A32:0A ASL
> 0A33:0A ASL
> 0A34:26 FD ROL $FD
> 0A36:0A ASL
> 0A37:26 FD ROL $FD
> 0A39:0A ASL
> 0A3A:26 FD ROL $FD
> 0A3C:0A ASL
> 0A3D:26 FD ROL $FD
> 0A3F:65 D6 ADC $D5 ; +x
> 0A41:84 FC STA $FC
> 0A43:A5 FD LDA $FD
> 0A45:69 10 ADC #10 ; #0..#0FFF -> #1000..#1FFF
> 0A47:85 FD STA $FD
> 0A49:A2 00 LDX #0
> 0A4B:A1 FC LDA ($FC,X) ; get tile from map
> 0A4D:48 PHA
> 0A4E:68 clipWin PLA ; get CurTile
> 0A4F:09 0F ORA #0F ; Tile.Height = 16
> 0A51:AA TAX
> 0A52:86 09 STX $09 ; not needed -- save CurTile??
> 0A54:A9 80 doTile LDA #80 ; start HGR address Low bottom 8
> scanlines
> 0A56:A6 09 LDX $09
> 0A58:48 doHalf PHA ; save HGR.DST Low
> 0A59:18 CLC
> 0A5A:65 FE ADC $FE ; add HGR.BASE.LOW
> 0A5C:85 FA STA
> 0A5E:A9 1C LDA #1C ; start at dest bottom half = 8
> scanline
> 0A60:48 draw8 PHA
> 0A61:18 CLC
> 0A62:65 FF ADC $FF
> 0A64:85 FB STA $FB
> 0A66:BD 00 07 LDA $0700,X ; source tile byte
> 0A69:CA DEX
> 0A6A:91 FA STA ($FA),Y ; dest HGR 7-pixels
> 0A6C:38 SEC
> 0A6D:68 PLA
> 0A6E:E9 04 SBC #4 ; move to previous HGR scanline
> 0A70:10 EE BPL draw8 ; $0A60
> 0A72:38 SEC
> 0A74:E9 80 SBC #80 ; adjust dest scanline ptr for top
> 8
> 0A76:10 E0 BPL doHalf ; $0A58
> 0A78:C8 INY ; move to next HGR column
> 0A79:CE 68 0A DEC TileAdrH; move to source tile prev column
> data
> 0A7C:AD 68 0A LDA TileAdrH; $A68
> 0A7F:C9 08 CMP #08 ; done even source col of tile?
> 0A81:10 D1 BPL doTile ; $0A54
> 0A83:E6 D6 INC $D6 ; TileX
> 0A85:C0 28 CPY #28 ; Drawn all 40 columns?
> 0A87:D0 8F BNE drawCol ; $0A18
> 0A89:E6 D7 INC $D7 ; TileY
> 0A8B:E6 FF INC $FF ; HGR.BASE.HI = constant delta $1
> every 16 scanlines
> 0A8D:A5 FF LDA $FF
> 0A8F:C9 24 CMP #24 ; drawn 64 scanlines?
> 0A91:F0 03 BEQ done64 ; $0A96
> 0A93:4C 0F 0A JMP drawRow ; $0A0F
> 0A96:18 done64 CLC
> 0A97:A5 FE LDA $FE ; HGR.BASE.LO
> 0A99:69 28 ADC #28
> 0A9B:85 FE STA $FE
> 0A9D:C9 78 CMP #78 ; done all 192 rows?
> 0A9F:F0 03 BEQ vehicle ; $0AA4
> 0AA1:4C 0B 0A JMP drawTriad ; $0A0B
>
> 0AA4:A9 28 vehicle LDA #28 ; y=#50 + x=#12 = $2128 + $12 =
> $213A
> 0AA6:85 FE STA $FE
> 0AA8:A9 21 LDA #21
> 0AAA:85 FF STA $FF
> 0AAC:A0 12 LDY #12 ; start $12 columns over (center
> of screen)
> 0AAE:A5 08 LDA $8 ; PlayerVehicle
> 0AB0:09 0F ORA #0F ; Tile.Height = 16
> 0AB2:85 09 STA $09 ; CurTile
> 0AB4:A9 09 LDA #09 ; start with even column of source
> TileAddr.Hi
> 0AB6:8D CD 0A STA vehTile+2
> 0AB9:A9 80 vehTile LDA #80 ; start HGR address Low bottom 8
> scanlines
> 0ABB:A6 09 LDX $09
> 0ABD:48 vehHalf PHA
> 0ABE:18 CLC
> 0ABF:65 FE ADC $FE
> 0AC1:85 FA STA $FA
> 0AC3:A9 1C LDA #1C ; start at dest bottom 8 scanline
> 0AC5:48 vehRow PHA
> 0AC6:18 CLC
> 0AC7:65 FF ADC $FF
> 0AC9:85 FB STA $FB
> 0ACB:BD 00 07 LDA $0700,X
> 0ACE:CA DEX
> 0ACF:91 FA STA ($FA),Y
> 0AD1:38 SEC
> 0AD2:68 PLA
> 0AD3:E9 04 SBC #4
> 0AD5:10 EE BPL vehRow ; $0AC5
> 0AD7:38 SEC
> 0AD8:68 PLA
> 0AD9:E9 80 SBC #80
> 0ADB:10 E0 BPL vehHalf ; $0ABD
> 0ADD:C8 INY ; move to next HGR column
> 0ADE:CE CD 0A DEC vehTile+2
> 0AE1:AD CD 0A LDA vehTile+2
> 0AE4:C9 08 CMP #8 ; done even source col of tile?
> 0AE6:10 D1 BPL vehTile ; $0AB9
> 0AE8:60 RTS
>
> AppleWin Symbols
> DB PlayerX 6
> DB PlayerY 7
> DB Vehicle 8 // player vehicle sprite #
> DB CurTile 9
> DB TileX D6 // = (PlayerX - 9)
> DB TileY D7 // = (PlayerY - 5)
> DB HGR.DST FA
> DB MAP.PTR FC // Tile from world to fetch
> DB HGR.BASE FE
> DB SpritesODD 800
> DB SpritesEVEN 900
> DB Map 1000 // 64x64 $1000..$1FFF
>
> SYM DrawMap = A00
> SYM drawTriad = A0B
> SYM drawRow = A0F
> SYM drawCol = A18
> SYM clipWin = A4E
> SYM doTile = A54
> SYM doHalf = A58
> SYM draw8 = A60
> SYM TileAdrH = A68
> SYM done64 = A96
> SYM vehicle = AA4
> SYM vehTile = AB9
> SYM vehHalf = ABD
> SYM vehRow = AC5
> SYM VehAdrH = ACD
>
> And
> reg Y = 0 .. $28 (column to draw)
>
> Example Water Tile:
> Row Hi-Res Data Source
> [00] $2000: 80 80 ($900 $800)
> [01] $2400: 80 80 ($901 $801)
> [02] $2800: D0 A0 ($902 $802)
> [03] $2C00: 85 8A ($903 $803)
> [04] $3000: 80 80 ($904 $804)
> [05] $3400: 80 80 ($905 $805)
> [06] $3800: 85 82 ($906 $806)
> [07] $3C00: D0 A8 ($907 $807)
> [08] $2080: 80 80 ($908 $808)
> [09] $2480: 80 80 ($909 $809)
> [0A] $2880: 90 A8 ($90A $80A)
> [0B] $2C80: C5 82 ($90B $80B)
> [0C] $3080: 80 80 ($90C $80C)
> [0D] $3480: 80 80 ($90D $80D)
> [0E] $3880: C1 82 ($90E $80E)
> [0F] $3C80: 94 A8 ($90F $80F)
>
>
> 5. Monster Sprites
> = = = = = = = = =
>
> NPC Sprites
> HGR
> BLOAD OUT.SHAPES,D1,A$B00
> SCALE=1:ROT=0
> POKE 232,0:POKE 233,11
> NEW
> HCOLOR=3:DRAW 1 at 0, 0:DRAW 2 AT 14, 0:DRAW 3 AT 28, 0
> HCOLOR=3:FOR Y=16 TO 47:HPLOT 0,Y TO 41,Y:NEXT
> HCOLOr=0:DRAW 4 AT 0,16:DRAW 5 AT 14,16:DRAW 6 AT 28,16
> HCOLOr=0:DRAW 4 AT 0,32:DRAW 5 AT 14,32:DRAW 6 AT 28,32
> HCOLOR=3:DRAW 1 at 0,16:DRAW 2 AT 14,16:DRAW 3 AT 28,16
>
> Partial Annotated Ultima 1 (original) Draw NPC Data
> B00:06 00 // 6 Sprites
> B02:0E 00 // #1 @ B0E OR mask Sea Serpent - drawn over water
> B04:35 00 // #2 @ B35 OR mask Mage - drawn over land
> B06:6D 00 // #3 @ B64 OR mask Ent - drawn over forest
> B08:82 00 // #4 @ B82 AND mask Sea Serpent - drawn over water
> B0A:CF 00 // #5 @ BCF AND mask Mage - drawn over land
> B0C:2C 01 // #6 @ C2C AND mask Ent - drawn over forest
>
> Tiles:
> $00 Water
> $10 Land
> $20 Forest
>
> AppleWin
> DW NPC.SPRITES B00:B0D
>
> Sprites used by:
> FILE: OUT MOVE
> LINE 1211 T1 = maptile(x,y)/16
> T1 = 0, 1, 2
> LINE 1213 draw map: if T1 > 2 T1 = 1
> LINE 1214
> HCOLOR=0:DRAW T1+4 AT (9+DX)*14,(5+DY)*16
> HCOLOR=3:DRAW T1+1 AT (9+DX)*14, 16*(DY+5)
> Note: The player/vehicle/avatar is always at ScreenTile 9,5.
>
>
> 6. Bootstrapping
> = = = = = = = =
>
> If you are using the cracked disk image
> You will need to patch the Applesoft
> program so you can view the BASIC source.
> LOAD INTRO
> 806:0D
> 89B:0D
> LIST
>
> RUN ULTIMA
> RUN INIT DISPLAY,D1
> LOMEM:30730
> IN = -1
> BLOAD CUR.PIC
> BLOAD B.CUR,A$B00
> IN = -2
> BLOAD CUR.PIC
> BLOAD B.CUR,$B00
> IN <> -3
> BLOAD ULTSHAPES,A$800,D1
> BLOAD DRAW 64.OBJ
> BLOAD TWN.CAS.SHAPES,A3700
> RUN OUT MOVE,D1
>
>
>
> 7. Applesoft Variables
> = = = = = = = = = = =
>
> Note: Applesoft only uses the first 2 characters
> of a variable name. FA and FACE all
> refer to the same variable.
>
> FILE: OUT MOVE
>
> 10000 Load World Map
> Variable: FACE = which world
> PRINT D$;"BLOAD BTERRA";FA:
> BLOAD BTERRA0,A$1000
> BLOAD BTERRA1,A$1000
> BLOAD BTERRA2,A$1000
> BLOAD BTERRA3,A$1000
>
> Line 2100 of Gets the tile# with
> INT( PEEK (4096 + TY*64 + TX) /16) );
>
>
> Line 2150 updates the world map with the vehicle
> 2150 RED(3) = T-8:
> POKE (4096+TY*64+TX),
> (PEEK(4096+TY*64+TX) - INT(PEEK(4096+TY*64+TX)/16)*16) * 16:
> GOSUB 1299: RETURN
> Not a bug, but an perfect example why Applesoft is slow...
> .. because there is no built-in MOD function.
> Context: Exit vehicle (Line 2100 = Board Vehicle/craft)
> What this code does: multiply map tile * 16
> i.e. If MapTile(x,y) = $AB
> Then MapTile(x,y) = $B0
> i.e.
> A = 33:PRINT (A-INT(A/16)*16)*16
> FOR H=0 TO 15:FOR L=0 TO 15:B=H*16+L:PRINT B;" -> ";(B-INT(B/
> 16)*16)*16:NEXT:NEXT
>
> For speed the code should of only set $08
>
>
> If you board a space machine this Applesoft program is run:
> 2135 RUN SPA MOVE
> Enter town
> 500 RUN TWN MOVE
> Enter castle
> 520 RUN CAS MOVE
> Enter Dungeon
> 530 RUN DNG MOVE
> Draw map
> 91 ... GOSUB 1297 ...
> 1213 CALL 2560
> 1299 FOOD -= (7 - RED(3)) / 14
> TIME += (7 - RED(3)) / 7
> POKE 6,XX: POKE 7,YY: POKE 8,(RED(3)+8)*16: CALL 2560
>
> Main Loop
> 600 Print Player Stat Summary, Life, Food, Experience,Gold
> 800 "COMMAND:", check for keypress
> 840 "PASS" -- Time-out after 2 seconds
> 1000 main loop
> Check if Life<0 Food<0
> ZZ = 141 $8D return = up arrow
> ZZ = 175 $AF / = down arrow
> ZZ = 149 $95 -> = right arrow
> ZZ = 136 $88 <- = left arrow
> Call 1200, jump 1040
> 1030 Handle normal key input - jump table
> 1040 call 6000, jump 1000
> Commands:
> 2000 A = Attack
> 2100 B = Board craft
> 2200 C = Cast (readied spell)
> 2210 0="POTENTITIS-LAUDIS", 33% for which random effect
> i) banish all monsters!
> ii) If almost dead (life < 10), set life = 10
> iii) If almost starving (food < 10), set food = 10
> 2290 1, 2=Dungeon Only
> 2220 3="DELICIO-ERU-U", magic-missile; dmg=C(5) +2 *
> (7<weapon<12)
> 2290 4, 5, 6, 7, 8, 9 Dungeon
> 2230 10="INTERFICIO-NUNC", attack monster
> 2300 D = Drop = n/a
> 2400 E = Enter town/castle
> 2500 F = Fire vehicle weapns. 20% chance to miss.
> Vehicle: 4 = Cannons
> Vehicle: 5 = Lasers
> 2600 G = Get = n/a
> 2700 H = n/a
> 2800 I = Inform and search
> 2900 J = n/a
> 3000 K = n/a
> 3100 L = Lag Time (0-9)
> 3200 M = n/a
> 3300 N = n/a
> 3400 O = Open
> 3500 P = n/a
> 3600 Q = "Quit & Save Game"
> BSAVE BEVERY,A$7800,L$1E01
> BSAVE BTERRA,A$1000,L$1000
> 3700 R = Ready Weapon, Armor, Spell
> 3800 S = Steal -- n/a outdoors
> 3900 T = Talk -- n/a outdoors
> 4000 U = Unlock
> 4100 V = n/a
> 4200 W = n/a
> 4300 X = Exit vehicle
> 4400 Y = n/a
> 4500 Z = Inventory
> 6000 Combat - attacked by mob
> 6400 Dead state
>
> Variables (some initialized in Line 280)
> FACE - which land/world to enter next
> Called on movement
> 1215: IF TX + DX > 75 THEN GOSUB 980, 900, 990: JUMP 1297
> 1216: IF TX + DY < -12 THEN ...
> 1217: IF TY + DY > 75 THEN ...
> 1218: IF TY + DY < -10 THEN ...
> 0 900: FACE++: 902: TX = -10
> 1 920: FACE--: 922: TX = 73
> 2 940: FACE-2: 942: TY = -10
> 3 960: FACE+2: 962: TY = 73
> World saved at 985
>
> RED(0) = Spell Readied
> RED(1) = Weapon Readied
> RED(2) = Armor Readied
> RED(3) = Vehicle Boarded
> G1 = # of Red Gem
> G2 = # of Green Gems
> G3 = # of Blue Gems
> G4 = # of White Gems
> NA$ = Player name
> SPE() = # of each spell
>
> C(0) = Player Life/Health, Init = 100, Line #720
> C(1) = Strength
> C(2) = Agility
> C(3) =
> C(4) =
> C(5) = Wisdom
> C(6) = Intelligence
> C(7) = Player Gold, Init = 100, File: INIT DISPLAY, Line #720
> C(8) = Race, INIT DISPLAY, Line
> 1 c(6) + 5
> 2 c(2) + 5
> 3 c(1) + 5
> 4 c(5) + 10, c(1) - 5
> C(9) = Type, INIT DISPLAY Line 630
> 1 c(1) + 10, c(2) + 10
> 2 c(5) + 10
> 3 c(6) + 10
> 4 c(2) + 10
>
> Race Attribute Bonus
> Human +5 Intelligence
> Elf +5 Agility
> Dwarf +5 Strength
> Bobbit +10 Wisdom; -5 Strength
>
> Class/Profession
> Attribute Bonus
> Fighter +10 Strength; +10 Agility
> Cleric +10 Wisdom
> Wizard +10 Intelligence
> Thief +10 Agility
>
> FOOD = Player Food
> EPNT = Player Experience
> AN = Monster Count!
> AM = Monster Type
> MN$()
> 6 = Ness Creature
> 7 = Giant Squid
> 10 = Hood
> 12 = Hidden Archer
> 19 = Evil Ranger
> 20 = Wandering Warlock
> 30 = Gelationous Cube
> 33 = Lizard Man
> 32 = Mimic
> 39 = Wraith
> 41 = Invisible Seeker
> TDT
> 44 = The Souther Sign Post
> FO = ?
> TX = PlayerX
> TY = PlayerY
>
> Player disk name, File: INIT DISPLAY, Line 760, has typo?? PRINT NANE
> $
> OPEN INFO
> WRITE INFO
> PRINT NANE$;" DISK "; CHR$(48):
> CLOSE INFO
> Player disk name read, Line 10000
> OPEN INFO
> READ INFO
> INPUT Q$
> CLOSE INFO
>
> TIME = 0
> WRLD = 0
> FACE = 0
> EPNTS = 1
> INOUT = 0
> LEV = 0
> TX = 40
> TY = 40
> PX = 0
> PY = 0
> DX = 0
> DY = 0
> FOOD = 100
> PW(1) = 2
> ARM(1) = 1
> RED(1) = 1
> RED(2) = 1
>
> 8. BUGS
> = = = =
>
> Death: When you die you will be put to a random map
> location. Sometimes you will be stuck in the middle
> of the ocean unable to move. (Guess swimming was
> not invented yet.)
> FILE:OUT MOVE
> LINE:6400
> 6400 PRINT NA$;", YOU ARE DEAD.":
> PRINT "ATTEMPTING RESURRECTION!":
> FOR X=1 TO 15: PW(X)=0: NEXT:
> IN=0: RE(1)=0: RE(3)=0: C(7)=0: FO=20:
> TX = INT(RND(1)*64):
> TY = INT(RND(1)*64):
> C(0) = 99: AM = 0
> 6402 FOR X=1 TO 2000:NEXT:GOSUB 600:GOSUB 1297:GOTO 1000
>
> Add this line:
> 6401 TX = INT(RND(1)*48)+8: TY = INT(RND(1)*48)+8:
> T = INT(PEEK(4096+TY*64+TX)/16):IF T=0 OR T=3 THEN 6401
>
>
> 9. Misc.
> = = = =
> Since the original came out in 1981 does this make
> these notes the oldest fan-made patches for a 31 year
> old game? =)