lawless-legends/Platform/Apple/virtual/src/plasma/gameloop.pla

2249 lines
57 KiB
Plaintext
Raw Normal View History

2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1
// (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
// Unless required by applicable law or agreed to in writing, software distributed under
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
// ANY KIND, either express or implied. See the License for the specific language
// governing permissions and limitations under the License.
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
2014-08-21 15:17:50 +00:00
// Handy constants.
2014-06-25 15:47:28 +00:00
const FALSE = 0
2015-11-05 15:18:17 +00:00
const TRUE = 1
const NULL = 0
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Fixed memory locations
const seed = $4E // Incremented continuously by keyboard read routine
2015-04-05 16:52:38 +00:00
const displayEngine = $6000 // main mem (raycaster and tile engine at same location)
const expandVec = $2000 // aux mem (only for raycaster)
const fontEngine = $E000 // main mem
const heapStart = $F000 // main mem
const heapSize = $800
2014-07-06 21:40:35 +00:00
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Resource numbers
2014-07-06 21:40:35 +00:00
const RES_NUM_RAYCASTER = 1
const RES_NUM_EXPAND_VEC = 2
const RES_NUM_FONT_ENGINE = 3
const RES_NUM_TILE_ENGINE = 4
2014-07-06 21:40:35 +00:00
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Memory manager definitions
2014-07-06 21:40:35 +00:00
2014-08-21 15:17:50 +00:00
// Resource types
2014-07-06 21:40:35 +00:00
const RES_TYPE_CODE = 1
const RES_TYPE_2D_MAP = 2
const RES_TYPE_3D_MAP = 3
const RES_TYPE_TILE = 4
const RES_TYPE_TEXTURE = 5
const RES_TYPE_SCREEN = 6
const RES_TYPE_FONT = 7
const RES_TYPE_MODULE = 8
const RES_TYPE_BYTECODE = 9
const RES_TYPE_FIXUP = 10
const RES_TYPE_PORTRAIT = 11
2014-07-06 21:40:35 +00:00
2014-08-21 15:17:50 +00:00
// Command codes
2014-07-06 21:40:35 +00:00
const RESET_MEMORY = $10
const REQUEST_MEMORY = $11
const LOCK_MEMORY = $12
const UNLOCK_MEMORY = $13
const SET_MEM_TARGET = $14
const START_LOAD = $15
const QUEUE_LOAD = $16
const FINISH_LOAD = $17
const FREE_MEMORY = $18
const CALC_FREE = $19
const DEBUG_MEM = $1A
2015-08-21 14:59:03 +00:00
const CHECK_MEM = $1B
2014-07-06 21:40:35 +00:00
const CHAIN_LOADER = $1E
const FATAL_ERROR = $1F
2015-08-21 14:59:03 +00:00
const HEAP_SET = $20
const HEAP_ADD_TYPE = $21
const HEAP_ALLOC = $22
const HEAP_INTERN = $23
const HEAP_COLLECT = $24
///////////////////////////////////////////////////////////////////////////////////////////////////
// Other constants
2014-07-09 02:11:03 +00:00
const callbacks = $300
2015-11-05 17:09:45 +00:00
const CHAR_WND_LIFE_X = 91
const CHAR_WND_GUN_X = 114
include "playtype.plh"
//include "heaptest.plh"
word global // the global heap object, from which all live objects must be reachable
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Predefined functions, for circular calls or out-of-order calls
2015-04-09 17:31:36 +00:00
predef setWindow2, initCmds
2015-01-18 16:59:56 +00:00
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Global variables
2015-04-20 15:32:02 +00:00
byte mapNum
byte mapIs3D
2015-08-21 14:59:03 +00:00
word mapNameHash = 0
word totalMapWidth
word totalMapHeight
2015-08-21 14:59:03 +00:00
word skyNum = 9
word groundNum = 10
byte portraitNum = 1
word triggerOriginX, triggerOriginY
2015-02-27 13:33:11 +00:00
word triggerTbl
2015-08-21 14:59:03 +00:00
word cmdTbl[64]
2015-05-07 16:12:52 +00:00
byte frameLoaded = 0
2015-03-03 17:03:22 +00:00
byte textDrawn = FALSE
byte needRender = FALSE
// Queue setMap / teleport, since otherwise script might be replaced while executing
byte q_mapIs3D
byte q_mapNum = 0
word q_x
word q_y
byte q_dir
2015-08-21 14:59:03 +00:00
2015-11-05 17:09:45 +00:00
// For decimal conversion and display tabbing
byte decimalBuf[7]
byte tabBuf[5]
byte isPlural
2015-11-05 17:09:45 +00:00
// Combat variables that don't need to be saved in game state
byte nPlayersFighting
byte nEnemiesFighting
byte isFleeing
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Definitions used by assembly code
asm __defs
2015-02-27 13:33:11 +00:00
; Use hi-bit ASCII for Apple II
!convtab "../../include/hiBitAscii.ct"
2015-02-27 13:33:11 +00:00
; Headers
2014-07-06 21:40:35 +00:00
!source "../../include/global.i"
!source "../../include/plasma.i"
!source "../../include/mem.i"
!source "../../include/fontEngine.i"
2015-02-27 13:33:11 +00:00
; Optional debug printing support
DEBUG = 0
2015-02-27 13:33:11 +00:00
; General use
tmp = $2
pTmp = $4
ysav = $34
2015-11-11 16:55:16 +00:00
ysav1 = $35
2015-02-27 13:33:11 +00:00
2015-08-21 15:57:15 +00:00
; 16-bit random number seed - incremented by ROM kbd routine
seed = $4E
magic = $2227 ; there are 2048 magic values that work; this one caught my eye.
2015-04-20 15:32:02 +00:00
; NOTE ABOUT ABSOLUTE ADDRESSES
; You cannot use them: this code, including variable space, can be loaded *anywhere*.
; So don't declare any variables as !byte or !word here.
end
2015-04-09 17:31:36 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// API to call rendering engine (same API for raycaster and tile engine)
2015-04-14 14:19:42 +00:00
asm initDisplay // params: mapNum, pMapData, x, y, dir
+asmPlasm 5
2015-04-09 17:31:36 +00:00
jmp $6000
end
asm flipToPage1 // no params
+asmPlasm 0
2015-04-09 17:31:36 +00:00
jmp $6003
end
asm getPos // params: @x, @y
+asmPlasm 2
2015-04-09 17:31:36 +00:00
jmp $6006
end
2015-04-14 14:19:42 +00:00
asm setPos // params: x, y
+asmPlasm 2
2015-04-09 17:31:36 +00:00
jmp $6009
end
asm getDir // no params; returns: dir (0-15)
+asmPlasm 0
2015-04-09 17:31:36 +00:00
jmp $600C
end
asm setDir // params: dir (0-15)
+asmPlasm 1
2015-04-09 17:31:36 +00:00
jmp $600F
end
asm advance // no params; return: 0 if same pos, 1 if new pos, 2 if new pos and scripted
+asmPlasm 0
2015-04-09 17:31:36 +00:00
jmp $6012
end
asm setColor // params: slot (0=sky/1=ground), color (0-17)
+asmPlasm 2
2015-04-09 17:31:36 +00:00
jmp $6015
end
2015-05-07 17:17:38 +00:00
asm render // no params
+asmPlasm 0
2015-05-07 17:17:38 +00:00
jmp $6018
end
2015-11-11 16:55:16 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
asm pushAuxStr // params: none; ret: $200 (inbuf)
stx tmp ; save PLASMA's X reg eval stack index
tsx
lda $103,x ; get PLASMA's Y-reg value from its place in the stack
tay
; Create the following subroutine, used to copy chars from aux to main:
;0010- 8D 03 C0 STA $C003
;0013- B1 F4 LDA ($F4),Y
;0015- 8D 02 C0 STA $C002
;0018- 60 RTS
lda #$8D
sta $10
sta $15
ldx #2
stx $16
inx
stx $11
lda #$C0
sta $12
sta $17
lda #$B1
sta $13
lda #$F4
sta $14
lda #$60
sta $18
; Get string length and save it
iny ; advance to next code byte
bne +
inc $F5 ; next pg if necessary
tsx
inc $105,x ; also PLASMA's copy of the page pointer
+ jsr $10
sta $200
ldx #0
; Copy entire string
.lup cpx $200
beq ++
iny ; advance to next code byte
bne +
inc $F5 ; next pg if necessary
stx tmp+1
tsx
inc $105,x ; also PLASMA's copy of the page pointer
ldx tmp+1
+ jsr $10 ; now fetch the byte from aux mem
inx
sta $200,x ; and stuff it in the string / input buffer
bne .lup ; always taken
++ tsx
tya
sta $103,x ; modify PLASMA's Y reg so it picks up code execution just after the string
ldx tmp ; retrieve PLASMA's eval stack ptr
dex ; make room for ret value
lda #0
sta evalStkL,x ; return value lo (<$200)
lda #2
sta evalStkH,x ; and hi (>$200)
rts
end
2015-04-09 17:31:36 +00:00
2015-11-11 16:55:16 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// String building for display with the font engine. Includes plurality processing to handily
// handle things like "Dirt bag(s)" and "his/their"
2015-11-11 16:55:16 +00:00
asm buildString
+asmPlasm 1
sta cswl
sty cswh
lda #0
sta inbuf
rts
end
asm addToString
sty ysav1
inc inbuf
ldy inbuf
sta inbuf,y
ldy ysav1
rts
end
asm finishString
!zone {
+asmPlasm 1
sta tmp ; save isPlural flag
jsr setvid ; put the cout vector back to default
bit monrts ; V flag for prev-is-punctuation
ldy #1 ; dest offset in Y
ldx #1 ; source offset in X
cpx inbuf
beq + ; only process if string has at least 1 char
bcs .done
+ sty tmp+1 ; offset of last punctuation
.fetch lda inbuf,x
cmp #"("
bne .notpar
bvs .notpar ; skip paren processing right punctuation
lda tmp ; check isPlural flag
bne .plurpr
- lda inbuf,x ; it's singular, so skip everything in parens
cmp #")"
beq .next
inx
cpx inbuf
bne -
beq .done ; handle missing trailing paren
.plurpr inx ; it's plural, so copy everything within the parens
lda inbuf,x ; copy characters
cpx inbuf ; handle missing trailing paren
beq .store
cmp #")" ; go until we reach ending paren
beq .next
sta inbuf,y
iny
bne .plurpr ; always taken
.notpar cmp #"/"
bne .notsl
bvs .notsl ; skip slash processing right after punctuation
lda tmp ; check isPlural flag
bne .plursl
- inx ; loop that skips plural form
cpx inbuf
beq +
bcs .done ; handle end of string
+ lda inbuf,x
cmp #"A" ; eat letters (and stop when we hit punctuation)
bcs -
bcc .store ; copy the ending punctuation and continue normal processing
.plursl ldy tmp+1 ; erase singular form by backing up to prev punc
iny ; plus 1 to retain prev punc
bne .next ; resume regular copying of the plural form
.notsl cmp #"A" ; if <= ASCII "A", consider it punctuation
bcc +
clv ; clear last-is-punc flag
bvc .store ; always taken
+ bit monrts ; set prev-is-punc flag
sty tmp+1 ; save dest offset of last punctuation
.store sta inbuf,y ; save to dest
iny
.next inx
cpx inbuf ; compare src offset to length
bcc .fetch ; loop while less than
beq .fetch ; or equal
.done dey
sty inbuf ; save new length
2015-11-11 16:55:16 +00:00
lda #<inbuf
ldy #>inbuf
rts
}
2015-11-11 16:55:16 +00:00
end
///////////////////////////////////////////////////////////////////////////////////////////////////
asm blitPortrait // params: srcData, dstScreenPtr
+asmPlasm 2
; Save the dest pointer
sta pTmp
sty pTmp+1
; Save the source pointer
lda evalStkL+1,x
sta tmp
lda evalStkH+1,x
sta tmp+1
; Create the following subroutine, used to copy pixels from aux to main:
; 0010- 8D 03 C0 STA $C003
; 0013- B1 02 LDA ($02),Y
; 0015- 91 04 STA ($04),Y
; 0017- 88 DEY
; 0018- 10 F9 BPL $0013
; 001A- 8D 02 C0 STA $C002
; 001D- 60 RTS
lda #$8D
sta $10
sta $1A
ldx #2
stx $14
stx $1B
inx
stx $11
lda #$C0
sta $12
sta $1C
lda #$B1
sta $13
lda #$91
sta $15
inx
stx $16
lda #$88
sta $17
lda #$10
sta $18
lda #$F9
sta $19
lda #$60
sta $1D
; Outer copy loop
ldx #128 ; line count
.loop ldy #17 ; byte count minus 1
jsr $10 ; copy pixel bytes
; Advance to next row of data
lda tmp
clc
adc #18
sta tmp
bcc +
inc tmp+1
+
; Fun code to advance to the next hi-res line
ldy pTmp+1
iny
iny
iny
iny
cpy #$40
bcc .ok2
tya
sbc #$20 ; carry already set
tay
lda pTmp
eor #$80
bmi .ok
iny
cpy #$24
bcc .ok
ldy #$20
adc #$27 ; carry was set, so actually adding $28
.ok sta pTmp
.ok2 sty pTmp+1
; Loop until we've done all rows.
dex
bne .loop
rts
end
2015-04-20 15:32:02 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Simply retrieve the X register. Used to double-check that we're not leaking PLASMA eval
// stack entries.
asm getXReg
+asmPlasm 0
2015-04-20 15:32:02 +00:00
txa
ldy #0
2015-04-20 15:32:02 +00:00
rts
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Calculate 16-bit hash of a string
asm hashString
+asmPlasm 1
sta pTmp
sty pTmp+1
ldy #0
sty tmp+1
lda (pTmp),y
tax
iny
- clc
adc (pTmp),y
bcc +
inc tmp+1
+ asl
rol tmp+1
bcc +
ora #1
+ asl
rol tmp+1
bcc +
ora #1
+ asl
rol tmp+1
bcc +
ora #1
+ iny
dex
bne -
ldy tmp+1
rts
end
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Print a string
asm puts
+asmPlasm 1
2014-07-06 21:40:35 +00:00
sta pTmp
sty pTmp+1
2014-07-06 21:40:35 +00:00
ldy #0
lda (pTmp),y
tax
iny
- lda (pTmp),y
ora #$80
jsr cout
iny
dex
bne -
rts
2014-07-06 14:40:57 +00:00
end
2015-09-05 18:01:45 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Get a character from the keyboard
asm rdkey
+asmPlasm 0
jmp rdkey
end
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Print part of a string, until we hit the end or a '%' code. Return how far we got, or -1 for end.
asm partialPrintf
2015-04-20 15:32:02 +00:00
!zone {
+asmPlasm 2
2015-04-20 15:32:02 +00:00
lda evalStkL+1,x ; get string pointer
sta pTmp
lda evalStkH+1,x
sta pTmp+1
ldy #0
lda (pTmp),y ; get length byte
sec
sbc evalStkL,x ; minus offset
sta tmp ; to count of characters left to print
2015-04-20 15:32:02 +00:00
bcc .eos ; avoid overrunning
beq .eos
lda evalStkL,x ; get desired offset into string
tay
iny ; increment past length byte
- lda (pTmp),y
ora #$80
cmp #'%' ; stop if we hit % code
beq +
jsr cout
iny
dec tmp ; otherwise go until end of string
bne -
2015-04-20 15:32:02 +00:00
.eos ldy #$FF ; if we hit end of string, return -1
tya
rts
+ dey ; adjust back for length byte
tya ; that's the lo byte of return
ldy #0 ; hi byte of return is zero
rts
2015-04-20 15:32:02 +00:00
}
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Print a 16-bit hex value
2014-07-06 14:40:57 +00:00
asm printHex
+asmPlasm 1
tax
tya
jmp prntax
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Print a single character
asm printChar
+asmPlasm 1
2015-04-20 15:32:02 +00:00
ora #$80
jmp cout
2014-06-25 15:47:28 +00:00
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Print a carriage return
asm crout
+asmPlasm 0
jmp crout
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Ring the bell
asm beep
+asmPlasm 0
jmp bell
end
2015-05-03 16:25:57 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Read a string from the keyboard, turn it into a PLASMA string and return a pointer to the string.
2015-05-07 16:12:52 +00:00
asm readStr
2015-05-03 16:25:57 +00:00
+asmPlasm 0
jsr getln1
txa
pha
beq +
- lda inbuf-1,x
and #$7F
sta inbuf,x
dex
bne -
+ pla
sta inbuf,x
lda #<inbuf
ldy #>inbuf
rts
end
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Send a command to the memory manager
// Params: cmd, wordParam
asm mmgr
+asmPlasm 2
lda evalStkL+1,x ; command code
2014-07-06 21:40:35 +00:00
pha
ldy evalStkH,x ; address (or other param)... hi byte in Y
2014-07-06 21:40:35 +00:00
lda evalStkL,x
tax ; ...lo byte in X
2014-07-06 21:40:35 +00:00
pla
2015-04-20 15:32:02 +00:00
jsr mainLoader ; ret value in X=lo/Y=hi
txa ; to A=lo/Y=hi for asmPlasm
2015-04-20 15:32:02 +00:00
rts
end
// Aux version of memory manager command
asm auxMmgr
+asmPlasm 2
lda evalStkL+1,x ; command code
pha
ldy evalStkH,x ; address (or other param)
lda evalStkL,x
tax
pla
jsr auxLoader ; ret value in X=lo/Y=hi
txa ; to A=lo/Y=hi for asmPlasm
2015-04-20 15:32:02 +00:00
rts
2014-07-06 21:40:35 +00:00
end
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Jump straight to the system monitor
// Params: None
2014-07-06 21:40:35 +00:00
asm goMon
jmp $FF69
end
2015-03-03 17:03:22 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
2015-04-05 16:52:38 +00:00
// Execute a monitor breakpoint
2015-03-03 17:03:22 +00:00
// Params: None
asm brk
2015-04-20 15:32:02 +00:00
bit setText
bit page1
2015-03-03 17:03:22 +00:00
brk
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set up the font engine
// Params: pFont
asm setFont
+asmPlasm 1
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp SetFont
end
///////////////////////////////////////////////////////////////////////////////////////////////////
2015-11-30 16:02:32 +00:00
// Use the font engine to clear the current text window.
// Parameters: top, bottom, left, right
asm setWindow
+asmPlasm 4
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp SetWindow
end
2015-12-05 23:01:44 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Save the cursor position (1 save slot)
asm saveCursor
+asmPlasm 0
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp SaveCursor
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Restore the cursor position (1 save slot)
asm restoreCursor
+asmPlasm 0
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp RestCursor
end
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Use the font engine to clear the current text window
// Params: None
2014-07-09 02:11:03 +00:00
asm clearWindow
+asmPlasm 0
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp ClearWindow
2014-07-09 02:11:03 +00:00
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Use the font engine to copy the current text window to hi-res page 2
// Params: None
asm copyWindow
+asmPlasm 0
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp CopyWindow
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Display a character using the font engine.
// Params: ch
asm displayChar
+asmPlasm 1
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp DisplayChar
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Display a string using the font engine.
2014-08-21 15:17:50 +00:00
// Params: pStr
asm displayStr
+asmPlasm 1
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp DisplayStr
end
2015-11-05 17:09:45 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Calculate string width using the font engine.
// Params: pStr
asm calcWidth
+asmPlasm 1
bit setLcRW+lcBank2
bit setLcRW+lcBank2
jmp CalcWidth
end
2015-07-11 15:58:00 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
2015-08-21 15:57:15 +00:00
// Display a string using the font engine but not its parser.
2015-07-11 15:58:00 +00:00
// Params: pStr
asm rawDisplayStr
+asmPlasm 1
bit setLcRW+lcBank2
bit setLcRW+lcBank2
sta pTmp
sty pTmp+1
ldy #0
lda (pTmp),y
sta tmp
- cpy tmp
bcc +
rts
+ iny
lda (pTmp),y
ora #$80
cmp #"^"
bne +
iny
lda (pTmp),y
and #$1F
ora #$80
+ sty tmp+1
jsr DisplayChar
ldy tmp+1
bne -
end
2015-08-21 15:57:15 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Random number generator
// Adapted from http://codebase64.org/doku.php?id=base:small_fast_16-bit_prng
asm rand16
+asmPlasm 0
lda seed
beq .lowZero ; $0000 and $8000 are special values to test for
; Do a normal shift
asl seed
lda seed+1
rol
bcc .noEor
.doEor:
2015-11-30 16:49:46 +00:00
; high byte is in A
2015-08-21 15:57:15 +00:00
eor #>magic
sta seed+1
tay ; for asmPlasm, return hi byte in Y, lo byte in A
lda seed
eor #<magic
sta seed
rts
.lowZero:
lda seed+1
beq .doEor ; High byte is also zero, so apply the EOR
; For speed, you could store 'magic' into 'seed' directly
; instead of running the EORs
; wasn't zero, check for $8000
asl
beq .noEor ; if $00 is left after the shift, then it was $80
bcs .doEor ; else, do the EOR based on the carry bit as usual
.noEor:
sta seed+1
tay ; for asmPlasm, return hi byte in Y, lo byte in A
lda seed
rts
end
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// General methods
2014-07-06 23:45:27 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Fatal error: print message and stop the system.
def fatal(msg)
mmgr(FATAL_ERROR, msg)
end
2015-12-05 23:01:44 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Return the max of two unsigned 16-bit numbers
def max(a, b)
if a < b
return b
else
return a
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Return the min of two unsigned 16-bit numbers
def min(a, b)
if a < b
return a
else
return b
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
2015-11-05 17:09:45 +00:00
// Convert signed decimal to string in decimalBuf
def convertDec(n)
2015-05-03 16:25:57 +00:00
word n0
2015-11-05 17:09:45 +00:00
word p
p = @decimalBuf + 1
if n < 0; ^p = '-'; p=p+1; n = -n; fin
2015-05-03 16:25:57 +00:00
n0 = n
2015-11-05 17:09:45 +00:00
if n0 > 9999; ^p = '0' + n/10000; p=p+1; n = n%10000; fin
if n0 > 999; ^p = '0' + n/1000; p=p+1; n = n%1000; fin
if n0 > 99; ^p = '0' + n/100; p=p+1; n = n%100; fin
if n0 > 9; ^p = '0' + n/10; p=p+1; n = n%10; fin
^p = '0' + n; p=p+1
decimalBuf[0] = p - @decimalBuf - 1 // record final length of string
return @decimalBuf
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Print a formatted string a'la C printf, with up to four parameters.
def printf4(str, arg1, arg2, arg3, arg4)
word pos
word curArg
word p
pos = 0
curArg = @arg1
while TRUE
pos = partialPrintf(str, pos)
if pos < 0
break
fin
p = str + pos + 2
when ^p
is 'c'
printChar(*curArg); break
is 'd'
2015-11-05 17:09:45 +00:00
puts(convertDec(*curArg)); break
is 's'
puts(*curArg); break
is 'x'
printHex(*curArg); break
is '%'
printChar('%'); break
otherwise
printHex(^p); fatal("Unknown % code")
wend
curArg = curArg + 2
pos = pos + 2
loop
end
def printf1(str, arg1); printf4(str, arg1, 0, 0, 0); end
def printf2(str, arg1, arg2); printf4(str, arg1, arg2, 0, 0); end
def printf3(str, arg1, arg2, arg3); printf4(str, arg1, arg2, arg3, 0); end
2015-11-11 16:55:16 +00:00
// Like printf, but displays text using font engine
def displayf4(str, arg1, arg2, arg3, arg4)
buildString(@addToString)
printf4(str, arg1, arg2, arg3, arg4)
displayStr(finishString(isPlural))
2015-11-11 16:55:16 +00:00
end
def displayf1(str, arg1); displayf4(str, arg1, 0, 0, 0); end
def displayf2(str, arg1, arg2); displayf4(str, arg1, arg2, 0, 0); end
def displayf3(str, arg1, arg2, arg3); displayf4(str, arg1, arg2, arg3, 0); end
2015-05-03 16:25:57 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
def parseDec(str)
word n
word pend
word p
byte neg
neg = FALSE
n = 0
p = str + 1
pend = p + ^str
while p < pend
if p == (str+1) and ^p == '-'
neg = TRUE
elsif ^p >= '0' and ^p <= '9'
n = (n*10) + (^p - '0')
else
break
fin
p = p+1
loop
if neg; return -n; fin
return n
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def parseDecWithDefault(str, default)
if ^str == 0
return default
fin
return parseDec(str)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Get a keystroke and convert it to upper case
def getUpperKey()
byte key
word oldSeed
oldSeed = *seed // for now, make the seed stay consistent, for randomized heap testing
2015-11-05 15:18:17 +00:00
key = rdkey() & $7F
*seed = oldSeed
if key >= $60
key = key - $20
fin
return key
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set the sky color (relevant to 3D display only)
def setSky(num)
skyNum = num
setColor(0, skyNum)
needRender = TRUE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Switch to the next sky color (3D only)
def nextSky()
setSky((skyNum + 1) % 18)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set the ground color (relevant to 3D display only)
def setGround(num)
groundNum = num
setColor(1, groundNum)
needRender = TRUE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Switch to the next ground color (3D only)
def nextGround()
setGround((groundNum + 1) % 18)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Establish a keystroke -> command association in the command table
def initCmd(key, func)
if key >= $60
key = key - $20
fin
cmdTbl[key-$20] = func
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Load the Frame Image, and lock it.
def loadFrameImg()
byte img
2015-05-07 16:12:52 +00:00
img = mapIs3D+2
if frameLoaded <> img
if frameLoaded
mmgr(UNLOCK_MEMORY,$2000)
mmgr(FREE_MEMORY, $2000)
2015-05-07 16:12:52 +00:00
fin
mmgr(SET_MEM_TARGET, $2000)
mmgr(QUEUE_LOAD, img<<8 | RES_TYPE_SCREEN)
mmgr(LOCK_MEMORY, $2000)
2015-05-07 16:12:52 +00:00
frameLoaded = img
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Window for the map name bar
def setWindow1()
2015-11-30 16:02:32 +00:00
setWindow(8, 18, 35, 119) // Top, Bottom, Left, Right
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Window for the large upper right bar
def setWindow2()
2015-11-30 16:02:32 +00:00
displayChar('N'-$40) // Set normal mode - clear all special modes (like underline, etc.)
setWindow(24, 132, 154, 269) // Top, Bottom, Left, Right
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Window for the mid-size lower right bar
def setWindow3()
2015-11-30 16:49:46 +00:00
setWindow(144, 180, 154, 269) // Top, Bottom, Left, Right
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Window for the map area (used for clearing it)
def setMapWindow()
if mapIs3D
2015-11-30 16:02:32 +00:00
setWindow(24, 153, 14, 140) // Top, Bottom, Left, Right
else
2015-11-30 16:02:32 +00:00
setWindow(24, 169, 14, 140) // Top, Bottom, Left, Right
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
2015-11-05 17:09:45 +00:00
def rightJustifyStr(str, rightX)
word x
x = rightX - calcWidth(str)
tabBuf[0] = 4 // length
tabBuf[1] = 20 // Ctrl-T
tabBuf[2] = (x / 100) + '0'
tabBuf[3] = ((x / 10) % 10) + '0'
tabBuf[4] = (x % 10) + '0'
rawDisplayStr(@tabBuf)
rawDisplayStr(str)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def rightJustifyNum(num, rightX)
rightJustifyStr(convertDec(num), rightX)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayPlayerData(player)
word pi
// Display the player's name and health
rawDisplayStr(player=>s_name)
rightJustifyNum(player=>w_health, CHAR_WND_LIFE_X)
// Locate the first weapon, and display it's clip
pi = player=>p_items
while pi and pi->t_type <> TYPE_WEAPON; pi = pi=>p_nextObj; loop
if pi
rightJustifyNum(pi->b_clipCurrent, CHAR_WND_GUN_X)
else
rightJustifyStr("---", CHAR_WND_GUN_X)
fin
// All done.
rawDisplayStr("\n")
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Display the party data on the screen
def showParty()
word p
2015-12-05 23:01:44 +00:00
saveCursor()
setWindow3()
2015-12-05 23:01:44 +00:00
clearWindow()
2015-11-05 17:09:45 +00:00
// Display header
rawDisplayStr("^LName") // begin underline mode
rightJustifyStr("Life", CHAR_WND_LIFE_X)
rightJustifyStr("Gun", CHAR_WND_GUN_X)
rawDisplayStr("^L\n") // end underline mode
// Display each character
p = global=>p_players
while p
displayPlayerData(p)
p = p=>p_nextObj
loop
// Finish up
if mapIs3D; copyWindow(); fin
setWindow2()
2015-12-05 23:01:44 +00:00
restoreCursor()
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Load code and data, set up everything to display a 3D map
2015-04-05 16:52:38 +00:00
def initMap(x, y, dir)
2015-08-21 14:59:03 +00:00
word pFont
word pMap
// Set up the command table
2015-04-09 17:31:36 +00:00
initCmds()
2014-08-21 15:17:50 +00:00
// Reset memory (our module will stay since memory manager locked it upon load)
mmgr(RESET_MEMORY, 0)
2014-07-06 23:45:27 +00:00
2015-04-05 16:52:38 +00:00
// Load the font engine and its font
mmgr(SET_MEM_TARGET, fontEngine)
mmgr(QUEUE_LOAD, RES_NUM_FONT_ENGINE<<8 | RES_TYPE_CODE)
2014-07-06 23:45:27 +00:00
mmgr(SET_MEM_TARGET, $9000)
pFont = mmgr(QUEUE_LOAD, 1<<8 | RES_TYPE_FONT)
2014-08-21 15:17:50 +00:00
2015-04-05 16:52:38 +00:00
// Queue loading of the raycaster or tile engine and the map data
mmgr(SET_MEM_TARGET, displayEngine)
2015-04-05 16:52:38 +00:00
if mapIs3D
mmgr(QUEUE_LOAD, RES_NUM_RAYCASTER<<8 | RES_TYPE_CODE)
pMap = mmgr(QUEUE_LOAD, mapNum<<8 | RES_TYPE_3D_MAP)
auxMmgr(SET_MEM_TARGET, expandVec)
auxMmgr(QUEUE_LOAD, RES_NUM_EXPAND_VEC<<8 | RES_TYPE_CODE)
2015-04-05 16:52:38 +00:00
else
mmgr(QUEUE_LOAD, RES_NUM_TILE_ENGINE<<8 | RES_TYPE_CODE)
pMap = mmgr(QUEUE_LOAD, mapNum<<8 | RES_TYPE_2D_MAP)
2015-04-05 16:52:38 +00:00
fin
2014-07-06 23:45:27 +00:00
2014-08-21 15:17:50 +00:00
// Load everything that we just queued
mmgr(FINISH_LOAD, 1) // 1 = keep open
2014-07-06 23:45:27 +00:00
2014-08-21 15:17:50 +00:00
// Load the frame image (and lock it there)
loadFrameImg()
mmgr(FINISH_LOAD, 1) // 1 = keep open
// Tell the font engine where to find its font
setFont(pFont)
2014-07-06 23:45:27 +00:00
2015-04-09 17:31:36 +00:00
// Start up the display engine with map data and starting position. This will also load and
2015-04-20 15:32:02 +00:00
// init the script module, if any, which will end up calling us back at the setScriptInfo
2015-02-27 13:33:11 +00:00
triggerTbl = NULL
2015-05-07 16:12:52 +00:00
setWindow2()
2015-04-14 14:19:42 +00:00
initDisplay(mapNum, pMap, x, y, dir)
needRender = FALSE
2015-11-05 17:09:45 +00:00
// Display the party characters
showParty()
end
2015-02-27 13:33:11 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
2015-04-09 17:31:36 +00:00
// Check for script(s) attached to the current location, and call them if there are any.
def checkScripts()
word x
word y
2015-02-27 13:33:11 +00:00
word p
word pNext
word script
2015-03-03 17:03:22 +00:00
2015-04-09 17:31:36 +00:00
if !triggerTbl; return; fin
setWindow2()
getPos(@x, @y)
x = x - triggerOriginX
y = y - triggerOriginY
2015-02-27 13:33:11 +00:00
p = triggerTbl
while TRUE
if ^p == $FF
return
fin
2015-03-03 17:03:22 +00:00
pNext = p + p->1
2015-02-27 13:33:11 +00:00
if ^p == y
2015-03-03 17:03:22 +00:00
p = p + 2
2015-02-27 13:33:11 +00:00
while p < pNext
2015-03-03 17:03:22 +00:00
if x == ^p
script = p=>1
script()
2015-02-27 13:33:11 +00:00
fin
p = p + 3
loop
fin
p = pNext
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Advance one step forward (3D maps only)
def moveForward()
2015-04-09 17:31:36 +00:00
byte val
val = advance()
// If not blocked, render at the new position.
if val > 0
2015-06-01 02:03:38 +00:00
if !mapIs3D
render()
if textDrawn and mapIs3D; copyWindow(); fin
2015-06-01 02:03:38 +00:00
needRender = FALSE
else
needRender = TRUE
fin
fin
// If we're on a new map tile, clear text from script(s) on the old tile.
if val >= 2 and textDrawn
2015-04-09 17:31:36 +00:00
clearWindow()
if textDrawn and mapIs3D; copyWindow(); fin
2015-04-09 17:31:36 +00:00
textDrawn = FALSE
fin
// If there are script(s) on the new tile, run them.
if val == 3
2015-04-09 17:31:36 +00:00
checkScripts()
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
2015-04-09 17:31:36 +00:00
// Adjust player's direction plus or minus n increments
def adjustDir(n)
2015-04-09 17:31:36 +00:00
setDir((getDir() + n) & 15)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Move backward one step (3D mode)
def moveBackward()
adjustDir(8)
moveForward()
adjustDir(8)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Turn left (3D mode)
def rotateLeft()
adjustDir(-1)
needRender = TRUE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Rotate to the right (3D mode)
def rotateRight()
adjustDir(1)
needRender = TRUE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Sidestep to the right (3D mode)
def strafeRight()
adjustDir(4)
moveForward()
adjustDir(-4)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Sidestep to the left (3D mode)
def strafeLeft()
adjustDir(-4)
moveForward()
adjustDir(4)
end
2015-01-18 16:59:56 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
def moveNorth()
word x, y
getPos(@x, @y)
if y > 4
setDir(0)
moveForward()
else
beep()
fin
2015-01-18 16:59:56 +00:00
end
def moveEast()
word x, y
getPos(@x, @y)
if x < totalMapWidth-5
setDir(4)
moveForward()
else
beep()
fin
2015-01-18 16:59:56 +00:00
end
def moveSouth()
word x, y
getPos(@x, @y)
if y < totalMapHeight-5
setDir(8)
moveForward()
else
beep()
fin
2015-01-18 16:59:56 +00:00
end
def moveWest()
word x, y
getPos(@x, @y)
if x > 4
setDir(12)
moveForward()
else
beep()
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Switch to a new map (2D or 3D) and establish position on it
def setMap(is3D, num, x, y, dir)
if is3D == mapIs3D and num == mapNum
setPos(x, y)
setDir(dir)
needRender = TRUE
else
flipToPage1()
setWindow1(); clearWindow()
displayChar('Y'-$40) // center mode
displayStr("Traveling...")
mapNameHash = 0 // because we replaced the title text
displayChar('N'-$40) // normal mode
setMapWindow(); clearWindow()
setWindow2(); clearWindow()
mapIs3D = is3D
mapNum = num
initMap(x, y, dir)
fin
// Don't check scripts, because we often land on an "Exit to wilderness?" script
//NO:checkScripts()
end
2015-05-03 16:25:57 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
def kbdTeleport()
byte d3, num
2015-05-03 16:25:57 +00:00
word x, y
2015-05-07 16:12:52 +00:00
byte dir
2015-05-16 15:48:29 +00:00
flipToPage1()
2015-05-03 16:25:57 +00:00
^$c053
2015-05-06 16:45:59 +00:00
if ^$25 < 23; ^$25 = 23; fin
getPos(@x, @y)
printf3("\nCurrent: X=%d Y=%d Facing=%d\n", x, y, getDir())
2015-05-07 16:12:52 +00:00
printf1("3D [%d]: ", mapIs3D)
d3 = parseDecWithDefault(readStr(), mapIs3D)
if d3 > 1; d3 = 1; fin
printf1("Map [%d]: ", mapNum)
num = parseDecWithDefault(readStr(), mapNum)
printf1("X [%d] : ", x)
x = parseDecWithDefault(readStr(), x)
printf1("Y [%d] : ", y)
y = parseDecWithDefault(readStr(), y)
printf1("Facing [%d]: ", dir)
dir = parseDecWithDefault(readStr(), dir)
2015-05-07 16:12:52 +00:00
2015-05-06 16:45:59 +00:00
^$c052
2015-05-07 16:12:52 +00:00
setMap(d3, num, x, y, dir)
2015-05-07 16:12:52 +00:00
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def showPos()
word x, y
byte dir
flipToPage1()
^$c053
if ^$25 < 23; ^$25 = 23; fin
getPos(@x, @y)
printf2("\nX=%d Y=%d ", x, y)
if mapIs3D
printf3("Facing=%d Sky=%d Ground=%d", getDir(), skyNum, groundNum)
fin
puts("\nHit any key.\n")
getUpperKey()
^$c052
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def queue_setMap(is3D, num, x, y, dir)
q_mapIs3D = is3D
q_mapNum = num
q_x = x
q_y = y
q_dir = dir
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def queue_teleport(x, y, dir)
queue_setMap(mapIs3D, mapNum, x, y, dir)
2015-05-03 16:25:57 +00:00
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Get a key and dispatch it to a command. Then do it again, forever.
def kbdLoop()
word key, func
2015-04-20 15:32:02 +00:00
byte xreg, tmp
xreg = getXReg()
while TRUE
2015-04-20 15:32:02 +00:00
tmp = getXReg()
if tmp <> xreg
printf2("Error: x reg changed from %x to %x\n", xreg, tmp)
brk()
fin
key = getUpperKey()
key = key - $20
if key >= 0 and key < $40
func = cmdTbl[key]
2015-04-09 17:31:36 +00:00
if func; func(); fin
fin
if q_mapNum
setMap(q_mapIs3D, q_mapNum, q_x, q_y, q_dir)
q_mapNum = 0
fin
if needRender
render()
if textDrawn and mapIs3D; copyWindow(); fin
needRender = FALSE
fin
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set initial info for the scripts on this map: the name of the map, its trigger table, and the
// maximum extent (width, height). This is called by the init function for the scripts.
def setScriptInfo(mapName, trigTbl, wdt, hgt)
word newNameHash
2015-04-20 15:32:02 +00:00
// Grab the trigger table origins (used so the table can be more compact)
triggerOriginX = trigTbl=>0
triggerOriginY = trigTbl=>2
2015-04-20 15:32:02 +00:00
// Record the trigger table pointer
triggerTbl = trigTbl + 4
2015-04-20 15:32:02 +00:00
// Record the maximum width and height
totalMapWidth = wdt
totalMapHeight = hgt
2015-04-20 15:32:02 +00:00
// Display map name
newNameHash = hashString(mapName)
if newNameHash <> mapNameHash
setWindow1()
clearWindow()
displayChar('Y'-$40) // center mode
displayStr(mapName)
displayChar('N'-$40) // normal mode
if mapIs3D; copyWindow(); fin
mapNameHash = newNameHash
fin
2015-04-20 15:32:02 +00:00
// Back to the main text window.
2015-04-20 15:32:02 +00:00
setWindow2()
2015-02-27 13:33:11 +00:00
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Called by scripts to display a string. We set the flag noting that something has been
// displayed, then use an assembly routine to do the work.
def scriptDisplayStr(str)
2015-03-03 17:03:22 +00:00
textDrawn = TRUE
flipToPage1()
2015-02-27 13:33:11 +00:00
displayStr(str)
end
def scriptDisplayStrNL(str)
scriptDisplayStr(str)
displayStr("\n")
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Get a key, and don't return until it's Y or N (or lower-case of those). Returns 1 for Y.
def getYN()
byte key
while TRUE
key = getUpperKey()
if key == 'Y'
return 1
elsif key == 'N'
clearWindow()
if textDrawn and mapIs3D; clearWindow(); fin
2015-03-03 17:03:22 +00:00
textDrawn = FALSE
return 0
fin
beep()
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Display a portrait drawing (typically called from scripts)
def setPortrait(portraitNum)
word srcData
byte cx, cy
flipToPage1()
// We're going to switch windows. Save the cursor pos in the text window.
2015-12-05 23:01:44 +00:00
saveCursor()
// Now clear out the map area
setMapWindow()
clearWindow()
// Restore the cursor position
setWindow2()
2015-12-05 23:01:44 +00:00
restoreCursor()
// Load the portrait image and display it
srcData = auxMmgr(QUEUE_LOAD, portraitNum<<8 | RES_TYPE_PORTRAIT)
mmgr(FINISH_LOAD, 0) // 0 = close
if mapIs3D
blitPortrait(srcData, $2182) // start at 3rd text line
else
blitPortrait(srcData, $2202) // start at 4th text line
fin
needRender = FALSE // suppress display of map for this frame
auxMmgr(FREE_MEMORY, srcData)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Clear the displayed portrait drawing
def clrPortrait()
render()
if textDrawn and mapIs3D; copyWindow(); fin
needRender = FALSE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Test out portrait drawing
def testPortrait()
setPortrait(portraitNum)
2015-11-11 16:55:16 +00:00
printf1("portraitNum=%d\n", portraitNum)
portraitNum = portraitNum + 1
end
2015-11-11 16:55:16 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
def countList(p)
2015-12-05 23:01:44 +00:00
byte n
2015-11-11 16:55:16 +00:00
n = 0
while p
n = n+1
p = p=>p_nextObj
loop
return n
end
2015-12-05 19:13:35 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
def countListFiltered(p, offset, filterFunc)
2015-12-05 23:01:44 +00:00
byte n
2015-12-05 19:13:35 +00:00
n = 0
while p
if filterFunc(p)
n = n+1
fin
p = *(p + offset)
loop
return n
end
2015-12-05 23:01:44 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
def randomFromListFiltered(p, offset, filterFunc)
byte n
n = rand16() % countListFiltered(p, offset, filterFunc)
while p
if filterFunc(p)
if n == 0; return p; fin
n = n+1
fin
p = *(p + offset)
loop
return NULL
end
2015-11-11 16:55:16 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Call like this: addToList(player + items, itemToAdd)
def addToList(addTo, p)
p=>p_nextObj = *addTo
*addTo = p
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Call like this: removeFromList(player + items, itemToRemove)
def removeFromList(pList, toRemove)
word p
p = *pList
while p and p <> toRemove
pList = p + p_nextObj
p = *pList
loop
if p
*pList = p=>p_nextObj
p=>p_nextObj = NULL
else
fatal("InvalUnlink")
fin
end
include "playtype.pla"
//include "heaptest.pla"
///////////////////////////////////////////////////////////////////////////////////////////////////
2015-12-05 19:13:35 +00:00
def canFight(p)
return p=>w_health > 0
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def chooseEnemy(maxDist)
word p
byte n
// TODO: Consider distance of enemies
n = rand16() % nEnemiesFighting
p = global=>p_combatFirst
while TRUE
2015-12-05 19:13:35 +00:00
if p->t_type == TYPE_ENEMY and canFight(p)
if n == 0; return p; fin
n = n - 1
fin
p = p=>p_combatNext
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def playerMelee(pPlayer, pWeapon)
word pEnemy, dmg
pEnemy = chooseEnemy(5) // max distance 5 feet for melee
if !pEnemy; return FALSE; fin
if pWeapon
dmg = rollDice(pWeapon=>r_meleeDmg)
else
dmg = (rand16() % 6) + 1 // default to 1d6 for fists
fin
// TODO: Add extra melee damage for skills, strength, etc.
// TODO: consider enemy dodge
pEnemy=>w_health = pEnemy=>w_health - dmg
isPlural = FALSE
2015-11-30 16:49:46 +00:00
buildString(@addToString)
printf3("\n%s pummels %s for %d damage.", pPlayer=>s_name, pEnemy=>s_name, dmg)
2015-11-30 16:49:46 +00:00
if pEnemy=>w_health <= 0
printf1(" %s is killed!", pEnemy=>s_name)
nEnemiesFighting = nEnemiesFighting - 1
fin
2015-11-30 16:49:46 +00:00
puts("\n")
displayStr(finishString(0))
return TRUE
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def playerShoot(pPlayer, pWeapon)
2015-12-06 20:02:18 +00:00
word pEnemy, pSkill, dmg
word chance
byte roll
pEnemy = chooseEnemy(pWeapon->b_weaponRange)
if !pEnemy; return FALSE; fin
2015-12-06 20:02:18 +00:00
// Figure out chance to hit. First come agility and aim.
chance = (pPlayer->b_agility * 4) + pPlayer->b_aiming
2015-11-30 16:49:46 +00:00
2015-12-06 20:02:18 +00:00
// Add in skill modifier, if any
pSkill = pPlayer=>p_skills
while pSkill
if pSkill=>b_modKind == pWeapon=>b_itemKind
chance = chance + pSkill=>b_modValue
fin
pSkill = pSkill=>p_nextObj
loop
2015-12-06 20:02:18 +00:00
// Nobody can have 100% chance to hit. Let's cap it at 90%.
chance = min(90, chance)
roll = rand16() % 100
if roll >= chance
// Miss!
displayf2("\n%s shoots at %s but misses.\n", pPlayer=>s_name, pEnemy=>s_name)
else
// TODO: consider multi-shot weapons
dmg = rollDice(pWeapon=>r_projectileDmg)
// TODO: Add extra melee damage for skills, strength, etc.
// TODO: consider enemy dodge
pEnemy=>w_health = pEnemy=>w_health - dmg
buildString(@addToString)
printf3("\n%s shoots %s for %d damage.", pPlayer=>s_name, pEnemy=>s_name, dmg)
if pEnemy=>w_health <= 0
printf1(" %s is killed!", pEnemy=>s_name)
nEnemiesFighting = nEnemiesFighting - 1
fin
puts("\n")
displayStr(finishString(0))
return TRUE
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def playerDodge(pPlayer)
displayStr("\nTODO: player dodge.\n")
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def chooseWeapon(player)
displayStr("\nTODO: choose weapon\n")
end
2015-12-05 19:13:35 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
def combatPause()
word n
2015-12-06 20:02:18 +00:00
for n = 1 to 3000
2015-12-05 19:13:35 +00:00
next
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayOpponents()
word p
byte count, first
buildString(@addToString)
puts("\nYou face ")
2015-12-05 19:13:35 +00:00
first = TRUE
p = global=>p_enemyGroups
while p
if !first
if p=>p_nextObj
puts(", ")
else
puts(" and ")
fin
fin
first = FALSE
count = countListFiltered(p=>p_enemies, p_nextObj, @canFight)
isPlural = (count <> 1)
printf3("%d %s at %d'", count, p=>p_enemies=>s_name, p->b_enemyGroupRange)
p = p=>p_nextObj
loop
puts(".\n")
displayStr(finishString(isPlural))
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def displayOption(key, str)
buildString(@addToString)
printf2(" (%c)^T033%s\n", key, str)
rawDisplayStr(finishString(0))
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def playerCombatChoose(pl)
word p, pWeapon
2015-12-05 19:13:35 +00:00
byte nWeapons, key
byte canShoot, canReload, canChange
2015-12-05 19:13:35 +00:00
// Tell what the player currently faces
displayOpponents()
combatPause()
// Count weapons, and take the first as the current one.
nWeapons = 0
pWeapon = NULL
p = pl=>p_items
while p
if p->t_type == TYPE_WEAPON
if !pWeapon; pWeapon = p; fin
nWeapons = nWeapons + 1
fin
p = p=>p_nextObj
loop
// Let them know their options
2015-12-05 19:13:35 +00:00
displayStr("\n")
when rand16() % 5
is 0
displayStr("Think fast!"); break
is 1
displayStr("Do you:"); break
is 2
displayStr("Suck it up:"); break
is 3
displayStr("Options:"); break
otherwise
displayStr("Decisions, decisions..."); break
wend
displayStr("\n")
2015-12-05 19:13:35 +00:00
displayOption('M', "Melee")
if pWeapon
if pWeapon->b_clipCurrent
canShoot = TRUE
2015-12-05 19:13:35 +00:00
displayOption('S', "Shoot")
fin
if pWeapon->b_clipCurrent < pWeapon->b_clipSize
// TODO: Need to check for enough ammo, and use it up.
canReload = TRUE
2015-12-05 19:13:35 +00:00
displayOption('R', "Reload")
fin
if nWeapons >= 2
canChange = TRUE
2015-12-05 19:13:35 +00:00
displayOption('C', "Change weapon")
fin
fin
2015-12-05 19:13:35 +00:00
displayOption('D', "Dodge")
displayOption('F', "Flee")
while TRUE
pl->b_combatChoice = getUpperKey()
when pl->b_combatChoice
is 'M'
return
is 'F'
isFleeing = TRUE
return
is 'D'
return
is 'S'
if canShoot; return; fin
break
is 'R'
if canReload; return; fin
break
is 'C'
if canChange; return; fin
break
wend
beep()
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def playerCombatTurn(pl)
word pWeapon
// Get weapon
pWeapon = pl=>p_items
while pWeapon
if pWeapon->t_type == TYPE_WEAPON; break; fin
pWeapon = pWeapon=>p_nextObj
loop
// Execute the player's choice
when pl->b_combatChoice
is 'M'
playerMelee(pl, pWeapon)
break
is 'F'
break
is 'D'
playerDodge(pl)
break
is 'S'
playerShoot(pl, pWeapon)
break
is 'R'
pWeapon->b_clipCurrent = pWeapon->b_clipSize
displayf1("%s has reloaded.\n", pl=>s_name)
break
is 'C'
isPlural = FALSE
displayf2("%s changed to using %s.\n", pl=>s_name, pWeapon=>s_name)
break
wend
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def enemyCombatTurn(pe)
2015-12-05 23:01:44 +00:00
word pl
byte roll, dam
// Choose a target
pl = randomFromListFiltered(global=>p_players, p_nextObj, @canFight)
if !pl; return; fin
buildString(@addToString)
printf3("\n%s %s %s ", pe=>s_name, pe=>s_attackText, pl=>s_name)
// Roll to hit
roll = rand16() % 100
if roll <= pe->b_chanceToHit
dam = rollDice(pe=>r_enemyDmg)
printf1("and hits for %d damage!", dam)
pl=>w_health = max(0, pl=>w_health - dam)
if pl=>w_health == 0
printf1(" %s is killed!", pl=>s_name)
fin
showParty()
else
puts("and misses.")
fin
puts("\n")
displayStr(finishString(0))
printf2("chance=%d roll=%d\n", pe->b_chanceToHit, roll)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def combatInsert(toAdd)
word p, pPrev
// Find the proper position based on combat order number (keep largest first in the list)
pPrev = global + p_combatFirst
while TRUE
p = *pPrev
if !p or p->b_combatOrder < toAdd->b_combatOrder
break
fin
pPrev = p + p_combatNext
loop
// Add it right there.
toAdd=>p_combatNext = *pPrev
*pPrev = toAdd
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def determineCombatOrder()
word p, p2
nPlayersFighting = 0
nEnemiesFighting = 0
// Do each player character, with random chance based on their agility
global=>p_combatFirst = NULL
p = global=>p_players
while p
2015-12-05 19:13:35 +00:00
if canFight(p)
p->b_combatOrder = rand16() % (p->b_agility * 10)
combatInsert(p)
nPlayersFighting = nPlayersFighting + 1
fin
p = p=>p_nextObj
loop
// Then all the enemies, with random chance based on their chance to hit.
p = global=>p_enemyGroups
while p
p2 = p=>p_enemies
while p2
2015-12-05 19:13:35 +00:00
if canFight(p2)
p2->b_combatOrder = rand16() % p2->b_chanceToHit
combatInsert(p2)
nEnemiesFighting = nEnemiesFighting + 1
fin
p2 = p2=>p_nextObj
loop
p = p=>p_nextObj
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def startCombat()
word p, p2, n, s
2015-11-11 16:55:16 +00:00
2015-11-30 16:49:46 +00:00
// Setup
isFleeing = FALSE
2015-11-11 16:55:16 +00:00
// Create the enemy group(s).
global=>p_enemyGroups = NULL
addToList(global + p_enemyGroups, new_EnemyGroup_Dirt_Bags())
// Display portrait of first group
setPortrait(global=>p_enemyGroups=>p_enemies->ba_images[0])
2015-11-11 16:55:16 +00:00
// We're going to do all our text drawing in window 2. Also, might as well
// set everything up so that the map gets redrawn when combat finishes.
2015-11-11 16:55:16 +00:00
setWindow2()
clearWindow()
textDrawn = TRUE
needRender = TRUE
// Say who we're fighting
when rand16() % 5
is 0
s = "Uh oh, it's gunna' be one of THOSE days.\n"; break
is 1
s = "Smells like trouble!\n"; break
is 2
s = "The hairs on the back of your neck reach for the sky!\n"; break
is 3
s = "You've got a bad feeling about this...\n"; break
otherwise
s = "Momma said there'd be days like this!\n"; break
wend
displayStr(s)
2015-11-11 16:55:16 +00:00
p = global=>p_enemyGroups
while p
n = countList(p=>p_enemies)
isPlural = (n <> 1)
when rand16() % 5
is 0
s = "From out of nowhere comes/come %d %s to have their way with you!\n"; break
is 1
s = "%d nasty %s comes/come to stake their claim on you!\n"; break
is 2
s = "It's gone from bad to worse, %d %s is/are looking for trouble!\n"; break
is 3
s = "%d %s is/are sniffing you out, shoulda' bathed last week!\n"; break
otherwise
s = "You mutter a curse under your breath as you see %d %s approach you with malice!\n"; break
wend
displayf2(s, n, p=>p_enemies=>s_name)
2015-11-11 16:55:16 +00:00
p = p=>p_nextObj
loop
2015-12-05 19:13:35 +00:00
rawDisplayStr("\nDo you:\n")
displayOption('F', "Fight")
displayOption('R', "Run")
2015-11-19 16:36:35 +00:00
while TRUE
n = getUpperKey()
if n == 'F'
clearWindow()
displayStr("Fight!\n")
return TRUE
2015-11-19 16:36:35 +00:00
elsif n == 'R'
clearWindow()
displayStr("Coward.")
return FALSE
2015-11-19 16:36:35 +00:00
fin
beep()
loop
2015-11-11 16:55:16 +00:00
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def doCombat()
word p
// Show portrait and threat details, find out if player wants to fight (vs. run)
if !startCombat(); return; fin
// Do each round of combat until player wins, loses, or flees
while TRUE
p = global=>p_players
while p
if canFight(p)
playerCombatChoose(p)
fin
p = p=>p_nextObj
loop
determineCombatOrder()
p = global=>p_combatFirst
while p
if !nPlayersFighting
displayStr("\nYou lost.")
2015-12-05 19:13:35 +00:00
//Lose: You bought the farm, with your life! Thank's for playing! Relead last save ? Y/N
//Lose: Didn't see that coming...to see a fine player like you slaughtered like a common rodent...Well, let's reload and try that again, ok? Y/N
return
elsif !nEnemiesFighting
displayStr("\nYou won!")
2015-12-05 19:13:35 +00:00
//WIN: You survive the ordeal and rifle through the carnage to find X gold. (if player gets random item) Upon further searching you find a
//Win: That was a close call! You see something shimmering on the ground and find X gold.
//Win: Looks like you live to fight another day anyway! You get X experience and discover X gold.
//I forgot to add experience to the above ones
// WIN: You survive the ordeal and rifle through the carnage to find X gold and receive x experience. (if player gets random item) Upon further searching you find a
//Win: That was a close call! You see something shimmering on the ground and find X gold and x expereince.
return
elsif isFleeing
displayStr("\nYou have fled.")
return
fin
if canFight(p)
when p->t_type
is TYPE_PLAYER
playerCombatTurn(p); break
is TYPE_ENEMY
enemyCombatTurn(p); break
otherwise
brk()
wend
combatPause()
fin
p = p=>p_combatNext
loop
loop
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set up the command table for 3D mode
2015-04-09 17:31:36 +00:00
def initCmds()
2015-04-05 16:52:38 +00:00
// Clear the command table
byte i
for i = 0 to 63
cmdTbl[i] = 0
next
2015-05-03 16:25:57 +00:00
// Commands common to both 2D and 3D
initCmd('T', @kbdTeleport)
initCmd('P', @showPos)
initCmd('/', @testPortrait)
initCmd('!', @doCombat)
2015-05-03 16:25:57 +00:00
// Commands handled differently in 3D vs 2D
2015-04-05 16:52:38 +00:00
if mapIs3D
initCmd('W', @moveForward)
initCmd('A', @rotateLeft)
initCmd('D', @rotateRight)
initCmd('S', @moveBackward)
initCmd('X', @moveBackward)
initCmd('Z', @strafeLeft)
initCmd('C', @strafeRight)
initCmd('I', @moveForward)
initCmd('J', @rotateLeft)
initCmd('L', @rotateRight)
initCmd('K', @moveBackward)
initCmd(',', @moveBackward)
initCmd('M', @strafeLeft)
initCmd('.', @strafeRight)
initCmd('Y', @nextSky)
initCmd('G', @nextGround)
else
initCmd('W', @moveNorth)
initCmd('D', @moveEast)
initCmd('S', @moveSouth)
initCmd('X', @moveSouth)
initCmd('A', @moveWest)
2015-05-03 16:25:57 +00:00
initCmd('I', @moveNorth)
initCmd('J', @moveWest)
initCmd('L', @moveEast)
initCmd('K', @moveSouth)
initCmd(',', @moveSouth)
2015-04-09 17:31:36 +00:00
fin
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Load and display the title screen.
def loadTitle()
2015-03-03 17:03:22 +00:00
puts("Loading Lawless Legends.\n")
// Load the title screen
mmgr(UNLOCK_MEMORY, $2000)
mmgr(FREE_MEMORY, $2000)
mmgr(SET_MEM_TARGET, $2000)
mmgr(QUEUE_LOAD, 1<<8 | RES_TYPE_SCREEN) // title screen is fixed at #1
mmgr(LOCK_MEMORY, $2000)
mmgr(FINISH_LOAD, 1) // 1 = keep open
2015-05-07 16:12:52 +00:00
frameLoaded = 1
2015-08-21 15:03:08 +00:00
^$C050 // graphics
^$C057 // hi-res
^$C054 // page 1
^$C052 // full screen
// Hack for real (not emulated) IIc: sometimes displays only lo-bit graphics
// unless we do this. *HUGE* thanks to Brendan Robert for the fix!
^$C07E=0 // disable double-hi-res
^$C05F // disable double-hi-res
2015-08-06 15:43:41 +00:00
// Wait for a key.
getUpperKey()
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set vectors so that scripts in PLASMA can call back to do things with this engine.
def setCallbacks()
// $300
callbacks.0 = $4c
2015-04-09 17:31:36 +00:00
callbacks:1 = @setScriptInfo
// $303
callbacks.3 = $4c
callbacks:4 = @pushAuxStr
// $306
callbacks.6 = $4c
callbacks:7 = @scriptDisplayStr
// $309
callbacks.9 = $4c
callbacks:10 = @scriptDisplayStrNL
// $30C
callbacks.12 = $4c
callbacks:13 = @getYN
// $30F
callbacks.15 = $4c
callbacks:16 = @queue_setMap
2015-05-07 16:12:52 +00:00
// $312
callbacks.18 = $4c
callbacks:19 = @setSky
// $315
callbacks.21 = $4c
callbacks:22 = @setGround
// $318
callbacks.24 = $4c
callbacks:25 = @queue_teleport
// $31B
callbacks.27 = $4c
callbacks:28 = @setPortrait
2015-06-01 02:03:38 +00:00
// $31E
callbacks.30 = $4c
callbacks:31 = @clrPortrait
2015-06-03 14:56:19 +00:00
// $321
callbacks.33 = $4c
callbacks:34 = @moveBackward
// $324
callbacks.36 = $4c
callbacks:37 = @getUpperKey
// $327
callbacks.39 = $4c
callbacks:40 = @clearWindow
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set up the small-object heap
def initHeap()
mmgr(SET_MEM_TARGET, heapStart)
mmgr(REQUEST_MEMORY, heapSize)
mmgr(LOCK_MEMORY, heapStart)
mmgr(HEAP_SET, heapStart)
addTypes()
end
2014-08-21 15:17:50 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////
// Main code.
//
initHeap()
addToList(global + p_players, new_Player_Hue_Hauser())
loadTitle()
setCallbacks()
2015-11-05 15:18:17 +00:00
// Start map/loc per Seth. Need to have this in a script in the futurecheckScripts()
mapIs3D = 0
mapNum = 1
initMap(6, 123, 12)
kbdLoop()
2014-06-25 15:47:28 +00:00
done