This commit is contained in:
Dave Schmenk 2017-11-11 06:52:19 -08:00
commit e90a95f944
10 changed files with 294 additions and 258 deletions

View File

@ -1,14 +1,25 @@
Converting MIDI files to internal music secquencer files:
cvtmid.py, the MIDI file converter, uses the mido package: https://mido.readthedocs.io/en/latest/
cvtmid.py, the MIDI file converter, uses Python and the mido package: https://mido.readthedocs.io/en/latest/
Simply put a MIDI file in the same directory as ctvmid.py and type:
./cvtmid.py midifile.mid > midifile.seq
Linux and OSX/macOS already have Python installed. Windows will need to install Python 7.2.xx from:
https://www.python.org/downloads/windows/
To install mido, use pip. If you are on OSX/macOS, you first need to install pip with:
```
sudo easy_install pip
```
Then, install mido with:
```
sudo pip install mido
```
To convert a MIDI file, simply put a MIDI file in the same directory as ctvmid.py and type:
```
./cvtmid.py midifile.mid > midifile.seq
```
The midifile.seq output file is an ACME assembly file that can be included in another assembly file, a PLASMA file, or assembled into a binary file which can be loaded later. Simply type:
acme --setpc 0x1000 -o seqfile.bin midifile.seq
```
acme --setpc 0x1000 -o seqfile.bin midifile.seq
```
The starting address is irrelevant, but ACME requires one to assemble properly.

Binary file not shown.

View File

@ -4,11 +4,31 @@ import sys
#from mido import MidiFile
import mido
mid = mido.MidiFile(sys.argv[1])
optarg = 1
timescale = 16.0 # Scale time to 16th of a second
extperchan = 9 # Default to standard MIDI channel 10 for extra percussion
if len(sys.argv) == 1:
print 'Usage:', sys.argv[0], '[-p extra_percussion_channel] [-t timescale] MIDI_file'
sys.exit(0)
# Parse optional arguments
while optarg < (len(sys.argv) - 1):
if sys.argv[optarg] == '-t': # Override tempo percentage
timescale = float(sys.argv[optarg + 1]) * 16.0
optarg += 2
if sys.argv[optarg] == '-p': # Add extra percussion channel
extperchan = int(sys.argv[optarg + 1]) - 1
optarg += 2
mid = mido.MidiFile(sys.argv[optarg])
timeshift = timescale
totaltime = 0
eventtime = 0.0
for msg in mid:
eventtime += msg.time * timeshift
#print '; time = ', msg.time
if msg.type == 'note_on' or msg.type == 'note_off':
deltatime = int(msg.time * 16 + 0.5)
#if eventtime > 0.0 and eventtime < 0.5:
# eventtime = 0.5
deltatime = int(round(eventtime))
octave = int(msg.note / 12 - 1)
onote = int(msg.note % 12)
lrchan = int(msg.channel & 1)
@ -20,15 +40,29 @@ for msg in mid:
if octave < 0:
octave = 0
totaltime += deltatime
if msg.channel == 9:
if msg.channel == 9 or msg.channel == extperchan:
#
# Percussion
#
if vol > 0 and deltatime > 0:
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}'.format(deltatime, msg.note >> 3, 2)
if vol > 0:
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Percussion {3:d} Chan {4:d} Dur {5:d}'.format(deltatime, msg.note ^ 0x40, (lrchan << 7) | vol, msg.note, msg.channel + 1, vol)
if extperchan == 9: # Play percussion on both channels if no extended percussion
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Percussion {3:d} Chan {4:d} Dur {5:d}'.format(0, msg.note ^ 0x40, vol, msg.note, msg.channel + 1, vol)
eventtime = 0.0
else:
#
# Note
#
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}'.format(deltatime, 0x80 | (octave << 4) | onote, (lrchan << 7) | vol)
print '\t!BYTE\t$00, $00, $00'
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Note {3:d} Chan {4:d} Vol {5:d}'.format(deltatime, 0x80 | (octave << 4) | onote, (lrchan << 7) | vol, msg.note, msg.channel + 1, vol)
eventtime = 0.0
elif msg.type == 'set_tempo':
pass
#timeshift = msg.tempo / 500000.0 * timescale
#print '; timescale = ', timescale
elif msg.type == 'time_signature':
pass
elif msg.type == 'control_chage':
pass
elif msg.type == 'program_change':
pass
print '\t!BYTE\t${0:02X}, $00, $00'.format(int(eventtime + 0.5))

View File

@ -104,6 +104,7 @@ class A2PackPartitions
def nWarnings = 0
def warningBuf = new StringBuilder()
def worldFileDir
def binaryStubsOnly = false
final int CACHE_VERSION = 1 // increment to force full rebuild
def cache = ["version": CACHE_VERSION]
@ -1005,6 +1006,7 @@ class A2PackPartitions
//println "Packing 3D map #$num named '$name': num=$num."
withContext("map '$name'") {
addResourceDep("map", name, "map3D", name)
addResourceDep("map", name, "tileSet", "tileSet_special") // global tiles for clock, compass, etc.
def rows = parseMap(mapEl, tileEls)
def (scriptModule, locationsWithTriggers) = packScripts(mapEl, name, rows[0].size(), rows.size())
def buf = ByteBuffer.allocate(50000)
@ -1216,9 +1218,10 @@ class A2PackPartitions
//println "esdName='$esdName'"
assert esdName != null : "failed to look up esdIndex $esdIndex"
def offset = cache["globalExports"][esdName]
//println "offset=$offset"
//println "external fixup: esdIndex=$esdIndex esdName='$esdName' target=$target offset=$offset"
assert offset != null : "failed to find global export for symbol '$esdName'"
target += offset
// External fixups can only refer to gamelib, which is always set at $1000 in memory.
target += offset + 0x1000
}
else if (invDefs.containsKey(target)) {
target = invDefs[target]
@ -1239,12 +1242,14 @@ class A2PackPartitions
codeBuf[addr] = (byte)(target & 0xFF)
codeBuf[addr+1] = (byte)((target >> 8) & 0xFF)
// And record the fixup
assert addr >= 0 && addr <= 0x3FFF : "code module too big"
newFixup.add((byte)((addr>>8) & 0x3F) |
(inByteCode ? 0x40 : 0) |
((fixupType == 0x91) ? 0x80 : 0))
newFixup.add((byte)(addr & 0xFF))
// And record the fixup (no need to record ext fixups - they're absolute starting at $1000)
if (fixupType != 0x91) {
assert addr >= 0 && addr <= 0x3FFF : "code module too big"
newFixup.add((byte)((addr>>8) & 0x3F) |
(inByteCode ? 0x40 : 0) |
((fixupType == 0x91) ? 0x80 : 0))
newFixup.add((byte)(addr & 0xFF))
}
}
newFixup.add((byte)0xFF)
@ -1386,9 +1391,15 @@ class A2PackPartitions
// If already on disk 1, don't duplicate.
if (part1Chunks.containsKey(key)) {
if (DEBUG_TRACE_RESOURCES)
println "${(" " * rtraceLevel)} in part 1 already."
return 0
if (key[0] == "tileSet" || key[0] == "texture") {
if (DEBUG_TRACE_RESOURCES)
println "${(" " * rtraceLevel)} must dupe ${key[0]}s."
}
else {
if (DEBUG_TRACE_RESOURCES)
println "${(" " * rtraceLevel)} in part 1 already."
return 0
}
}
// Okay, we need to add it.
@ -1586,7 +1597,7 @@ class A2PackPartitions
month == 10 ? 'n' :
'd'
def hourCode = (char) (97 + hour) // 'a'=0, 'b'=1, etc.
def engineCode = String.format("%d%c%02d%c", yearCode, monthCode, day, hourCode)
def engineCode = String.format("%d%s%02d%c", yearCode, monthCode, day, hourCode)
def offset = Math.max(-99, Math.min(99, (int) ((scenarioStamp - engineStamp) / (1000 * 60 * 60))))
return String.format("%s%s%d", engineCode, offset < 0 ? "-" : ".", Math.abs(offset))
@ -1798,6 +1809,8 @@ class A2PackPartitions
def srcFile = new File(partial).getCanonicalFile()
if (!srcFile.exists())
srcFile = new File(srcFile.getName()) // try current directory
if (!srcFile.exists())
srcFile = new File(worldFileDir, srcFile.getName()) // try dir containing world.xml
if (srcFile.exists()) {
if (dstFile.exists()) {
if (srcFile.lastModified() == dstFile.lastModified())
@ -2138,7 +2151,7 @@ class A2PackPartitions
if (type == "Code" && k.num > lastSysModule)
type = "Script"
def name = k.name.replaceAll(/\s*-\s*[23][dD].*/, "")
def dataKey = [type:type, name:name]
def dataKey = [type:type, name:name, num:k.num]
if (!data.containsKey(dataKey))
data[dataKey] = v
else {
@ -2167,8 +2180,8 @@ class A2PackPartitions
cSub = 0
ucSub = 0
}
reportWriter.println String.format(" %-20s: %6.1fK memory, %6.1fK disk",
k.name, v.uclen/1024.0, v.clen/1024.0)
reportWriter.println String.format(" %-20s: %6.1fK memory, %6.1fK disk (id %d)",
k.name, v.uclen/1024.0, v.clen/1024.0, k.num)
cSub += v.clen
cTot += v.clen
ucSub += v.uclen
@ -3479,6 +3492,7 @@ end
// Create PLASMA headers
inst1 = new A2PackPartitions()
inst1.buildDir = buildDir
inst1.worldFileDir = xmlFile.getAbsoluteFile().getParentFile()
inst1.reportWriter = reportWriter
inst1.dataGen(xmlFile, dataIn)
@ -3491,6 +3505,7 @@ end
inst2.nWarnings = inst1.nWarnings
inst2.resourceDeps = resourceDeps // inject partial deps
inst2.buildDir = buildDir
inst2.worldFileDir = xmlFile.getAbsoluteFile().getParentFile()
inst2.reportWriter = reportWriter
inst2.pack(xmlFile, dataIn)
@ -3883,11 +3898,8 @@ end
if (!text || text == "") // interpret lack of text as a single empty string
chunks = [""]
chunks.eachWithIndex { chunk, idx ->
outIndented((idx == chunks.size()-1 && blk.@type == 'text_println') ? \
'scriptDisplayStrNL(' : 'scriptDisplayStr(')
out << escapeString(chunk) << ")\n"
// Workaround for strings filling up the frame stack
outIndented("tossStrings()\n")
String str = (idx == chunks.size()-1 && blk.@type == 'text_println') ? chunk+"\\n" : chunk
outIndented("scriptDisplayStr(" + escapeString(str) + ")\n")
}
}
else {

View File

@ -14,7 +14,7 @@
;
; See detailed description in mem.i
* = $4000 ; PLASMA loader loads us initially at $2000
* = $4000 ; PLASMA loader loads us initially at $4000
; Use hi-bit ASCII for Apple II
!convtab "../include/hiBitAscii.ct"
@ -62,6 +62,8 @@ rwts_mark = $18 ; to reset seek ptr, zero out 4 bytes here
proRWTS = $D000
; Memory buffers
plasmaFrames = $E00 ; 2 pages
gameLoop = $1000 ; just after plasma frames
unusedBuf = $4000 ; used to be for ProDOS file buf and a copy space, but no longer
headerBuf = $4C00 ; len $1400
@ -208,7 +210,7 @@ init: !zone
; 1: aux $0000 -> 2, active + locked
; 2: aux $0800 -> 3, inactive ; TEMPORARY: until we figure out prob w aux screen holes
; 3: aux $BFFD -> 0, active + locked
; 4: main $0xxx -> 5, inactive (xxx = end of mem mgr low mem portion)
; 4: main $1000 -> 5, inactive
; 5: main $2000 -> 6, active + locked
; 6: main $6000 -> 7, inactive
; 7: main $BFFD -> 8, active + locked
@ -245,9 +247,7 @@ init: !zone
dey
sty tSegAdrHi+3
sty tSegAdrHi+7
lda #<lastLoMem
sta tSegAdrLo+4
lda #>lastLoMem
lda #$10
sta tSegAdrHi+4
lda #$20
sta tSegAdrHi+5
@ -275,51 +275,38 @@ init: !zone
lda #$20
sta framePtr+1 ; because sanity check verifies it's not $BE or $BF
}
ldx #0
ldy #2 ; 2 pages
lda #REQUEST_MEMORY
jsr main_dispatch
stx framePtr
stx outerFramePtr
stx .frameSet+1
stx frameChk+1
sty .frameSet+2
sty frameChk+2
lda #<(plasmaFrames+$200)
sta framePtr
sta outerFramePtr
lda #>(plasmaFrames+$200)
sta framePtr+1
sta outerFramePtr+1
lda #$AA ; store sentinel byte at bottom of frame
.frameSet:
sta $1111 ; self-modified above
iny ; twice for 2 pages: initial pointer at top of new space
iny
sty framePtr+1
sty outerFramePtr+1
dey
dey
lda #LOCK_MEMORY ; lock it in place forever
jsr main_dispatch
sta plasmaFrames ; self-modified above
; Load PLASMA module #1 from partition #1
ldx #1
lda #START_LOAD
jsr main_dispatch
ldx #<gameLoop
ldy #>gameLoop
lda #SET_MEM_TARGET
jsr main_dispatch
ldx #RES_TYPE_MODULE
ldy #1
lda #QUEUE_LOAD
jsr main_dispatch
stx .gomod+1
stx glibBase ; save addr for extern fixups
sty .gomod+2
sty glibBase+1
lda #LOCK_MEMORY ; lock it in forever
jsr main_dispatch
lda #FINISH_LOAD ; and load it
jsr main_dispatch
ldx #$10 ; set initial PLASMA eval stack index
.gomod: jmp $1111 ; jump to module to start the game
.gomod: jmp gameLoop ; jump to module to start the game
;------------------------------------------------------------------------------
; Vectors and debug support code - these go in low memory at $800
loMemBegin: !pseudopc $800 {
jmp j_main_dispatch
jmp j_aux_dispatch
jmp __main_dispatch
jmp __aux_dispatch
jmp __asmPlasmNoRet
jmp __asmPlasmRet
@ -343,17 +330,14 @@ loMemBegin: !pseudopc $800 {
_fixedRTS:
rts ; fixed place to find RTS, for setting V flag
j_main_dispatch:
__aux_dispatch:
sec
!byte $24 ; skip over next byte
__main_dispatch:
clc
bit setLcRW+lcBank1 ; switch in mem mgr
bit setLcRW+lcBank1
jsr main_dispatch
bit setLcRW+lcBank2 ; back to PLASMA
rts
j_aux_dispatch:
bit setLcRW+lcBank1 ; switch in mem mgr
bit setLcRW+lcBank1
jsr aux_dispatch
jsr dispatch
bit setLcRW+lcBank2 ; back to PLASMA
rts
@ -489,8 +473,7 @@ __asmPlasmNoRet:
pha ; and save that
cmp #$11 ; again, X must be in range 0..$10
bcs .badx
frameChk:
lda $1111 ; self-modified by init code
lda plasmaFrames
cmp #$AA ; check for sentinel value
bne .badfrm
bit setLcRW+lcBank2
@ -1119,6 +1102,19 @@ heapTop !word 0
gcHash_top !byte 0
lastLoMem = *
; Be careful not to grow past the size of the LC bank
!ifdef PASS2a {
!if DEBUG {
!warn "mmgr lomem spare: ", plasmaFrames - lastLoMem
!if lastLoMem >= plasmaFrames {
!error "mmgr lomem part grew too large."
}
} ; DEBUG
} else { ;PASS2a
!set PASS2a=1
}
} ; end of !pseodupc $800
loMemEnd = *
@ -1286,13 +1282,12 @@ scanForAvail: !zone
;------------------------------------------------------------------------------
main_dispatch: !zone
+ pha
lda #0
beq .go
aux_dispatch:
clc
dispatch:
pha
lda #1
.go sta isAuxCmd
lda #0
rol ; transfer carry bit
sta isAuxCmd ; to isAuxCmd
pla
!if SANITY_CHECK { jsr saneStart : jsr + : jmp saneEnd }
+ cmp #REQUEST_MEMORY
@ -1855,11 +1850,13 @@ mem_queueLoad: !zone
lda #QUEUE_LOAD
ldx #RES_TYPE_BYTECODE
ldy resNum
jsr aux_dispatch ; load the aux mem part (the bytecode)
sec
jsr dispatch ; load the aux mem part (the bytecode)
lda #QUEUE_LOAD
ldx #RES_TYPE_FIXUP ; queue loading of the fixup resource
ldy resNum
jsr aux_dispatch
sec
jsr dispatch
.modRet ldx #11 ; all done; return address of the main memory block.
ldy #22
rts
@ -2584,7 +2581,7 @@ doAllFixups: !zone
}
.mainBase !word 0
.auxBase !word 0
glibBase !word $1111
glibBase !word gameLoop
;------------------------------------------------------------------------------
; More heap management routines (that don't need to actually access LC bank 2)
@ -2910,15 +2907,15 @@ tSegAdrHi !fill MAX_SEGS
tableEnd = *
; Be careful not to grow past the size of the LC bank
!ifdef PASS2 {
!ifdef PASS2b {
!if DEBUG {
!warn "mmgr spare: ", lx47Decomp - tableEnd
!warn "mmgr himem spare: ", lx47Decomp - tableEnd
!if tableEnd >= lx47Decomp {
!error "Memory manager grew too large."
!error "mmgr himem part grew too large."
}
} ; DEBUG
} else { ;PASS2
!set PASS2=1
} else { ;PASS2b
!set PASS2b=1
}
} ; end of !pseudopc $D000

View File

@ -53,6 +53,46 @@ def freePreloaded()#0
mmgr(FINISH_LOAD, 0)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def rollDiceWithLuck(encoded, luck)#1
byte nDice, dieSize, droll
word result
nDice = (encoded >> 12) & $F // must mask off replicated hi-bits
dieSize = (encoded >> 8) & $F
result = encoded & $F
while nDice
droll = (rand16() % dieSize) + 1
if luck > 0
if (rand16() % 100) < (luck * 2)
droll = max(droll, (rand16() % dieSize) + 1)
fin
elsif luck < 0
if (rand16() % 100) < (luck * -2)
droll = min(droll, (rand16() % dieSize) + 1)
fin
fin
result = result + droll
nDice--
loop
return result
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def rollPercentileWithLuck(luck)#1
byte result
result = rand16() % 100
if luck > 0
if (rand16() % 100) < (luck * 2)
result = max(result, rand16() % 100)
fin
elsif luck < 0
if (rand16() % 100) < (luck * -2)
result = min(result, rand16() % 100)
fin
fin
return result
end
///////////////////////////////////////////////////////////////////////////////////////////////////
def combatPause()#0
byte key

View File

@ -101,13 +101,10 @@ import gamelib
predef rightJustifyNum(num, rightX)#0
predef rightJustifyStr(str, rightX)#0
predef rollDice(encoded)#1
predef rollDiceWithLuck(encoded, luck)#1
predef rollPercentileWithLuck(luck)#1
predef roomInPack(p_player)#1
predef scanForNamedObj(p_obj, name)#1
predef scriptCombat(mapCode)#1
predef scriptDisplayStr(str)#0
predef scriptDisplayStrNL(str)#0
predef scriptEvent(event, param)#0
predef scriptSetAvatar(avatarTileNum)#0
predef scriptSwapTile(fromX, fromY, toX, toY)#0
@ -137,7 +134,6 @@ import gamelib
predef sum(p, sel, func)#1
predef takeItemFromParty(itemName)#0
predef textHome()#0
predef tossStrings()#0
predef trueFunc()#1
predef unbenchPlayer()#0

View File

@ -178,21 +178,39 @@ ysav1 = $35
seed = $4E
magic = $2227 ; there are 2048 magic values that work; this one caught my eye. - MH
; Yes, needs to be adjusted 3 places.
expandVec = $800
; NOTE ABOUT ABSOLUTE CODE ADDRESSING (e.g. STA .var, JMP .label, etc.)
; We cannot use it: this code, including variable space, can be loaded *anywhere*.
; So don't JMP to labels, declare any variables as !byte or !word here, etc.
; We cannot use it: this code will be preceded by stubs for the PLASMA routines, hence
; absolute addressing must be done carefully, adding ABS_OFFSET below.
;
; So don't JMP to labels, declare any variables as !byte or !word here, etc. without
; accounting for that.
ABS_OFFSET = (_DEFCNT*5) - 13 ; 13 for plasma's module header (stripped by packer)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Temporary hack: after scriptDisplayStr is called, generated code calls this to clear the PLASMA
// string pool. That way, many long strings can be used in a single function.
export asm tossStrings()#0
// After scriptDisplayStr is called, we clear the PLASMA string pool. That way, many long strings
// can be used in a single function.
export asm scriptDisplayStr(str)#0
lda .callScriptDisplay + ABS_OFFSET + 2 ; first time init?
beq +
.callScriptDisplay
jsr 0 ; self-modified below
lda framePtr
sta outerFramePtr
lda framePtr+1
sta outerFramePtr+1
rts
+ ; first-time init
lda evalStkL,x
sta .callScriptDisplay + ABS_OFFSET + 1
lda evalStkH,x
sta .callScriptDisplay + ABS_OFFSET + 2
inx
rts
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -328,56 +346,42 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
asm readAuxByte(ptr)#1
+asmPlasmRet 1
; Create the following subroutine, used to copy one char from aux to main:
;0010- 8D 03 C0 STA $C003
;0013- AD XX XX LDA $XXXX
;0016- 8D 02 C0 STA $C002
;0019- 60 RTS
sta $14
sty $15
lda #$8D
sta $10
sta $16
ldx #2
stx $17
inx
stx $11
lda #$C0
sta $12
sta $18
lda #$AD
sta $13
lda #$60
sta $19
; And call the routine
ldy #0
sta pTmp
sty pTmp+1
ldy #12
- lda .rdauxb-1+ABS_OFFSET, y
sta $10-1,y
dey
bne -
jmp $10
.rdauxb
sei ; prevent interrupts while in aux mem
sta setAuxRd
lda (pTmp),y
sta clrAuxRd
cli
rts
end
///////////////////////////////////////////////////////////////////////////////////////////////////
asm splitExpander(expandVec)#1 // param: expandVec; returns: remaining lo-aux size
+asmPlasmRet 1
; assumes readAuxByte has just been called, which puts a little routine at $10.
; Adjust that routine to call expander instead of reading a byte.
sta $1B
sty $1C
lda #$6C
sta $1A
lda #$20
sta $13
lda #$60
sta 2
jsr 2
asm splitExpander()#1 // param: expandVec; returns: remaining lo-aux size
+asmPlasmRet 0
ldy #14
- lda .splitexp+ABS_OFFSET,y
sta $10,y
dey
bpl -
jmp $10
.splitexp
!pseudopc $10 {
sei ; prevent interrupts while in aux mem
jsr $10
sta setAuxRd
jsr +
sta clrAuxRd
cli
rts
+ jmp (expandVec)
}
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -512,39 +516,11 @@ asm blit(isAux, srcData, dstScreenPtr, nLines, lineSize)#0
lda evalStkL+4,x
lsr ; to carry bit
bcc +
; If reading from aux, create the following subroutine:
; 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
ldy #15 ; put aux copy routine in zero page
- lda .cpaux + ABS_OFFSET,y
sta $10,y
dey
bpl -
+ pla ; get line count
tax
--
@ -571,6 +547,16 @@ asm blit(isAux, srcData, dstScreenPtr, nLines, lineSize)#0
dex
bne -- ; Loop until we've done all rows.
rts
.cpaux
sei ; avoid interrupts while reading aux
sta setAuxRd
- lda (tmp),y
sta (pTmp),y
dey
bpl -
sta clrAuxRd
cli
rts
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -748,26 +734,24 @@ end
// Send a command to the memory manager
export asm mmgr(cmd, wordParam)#1
+asmPlasmRet 2
jsr .setmmgr+ABS_OFFSET
jsr mainLoader ; ret value in X=lo/Y=hi
txa ; to A=lo/Y=hi for asmPlasm
rts
.setmmgr
lda evalStkL+1,x ; command code
pha
ldy evalStkH,x ; address (or other param)... hi byte in Y
lda evalStkL,x
tax ; ...lo byte in X
pla
jsr mainLoader ; ret value in X=lo/Y=hi
txa ; to A=lo/Y=hi for asmPlasm
rts
end
// Aux version of memory manager command
export asm auxMmgr(cmd, wordParam)#1
+asmPlasmRet 2
lda evalStkL+1,x ; command code
pha
ldy evalStkH,x ; address (or other param)
lda evalStkL,x
tax
pla
jsr .setmmgr+ABS_OFFSET
jsr auxLoader ; ret value in X=lo/Y=hi
txa ; to A=lo/Y=hi for asmPlasm
rts
@ -1337,46 +1321,16 @@ export def encodeDice(nDice, dieSize, add)#1 // ndice=0..15, dieSize=0..15, add
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def rollDiceWithLuck(encoded, luck)#1
byte i, nDice, dieSize, add, droll, result
export def rollDice(encoded)#1
byte nDice, dieSize
word result
nDice = (encoded >> 12) & $F // must mask off replicated hi-bits
dieSize = (encoded >> 8) & $F
add = encoded & $F
result = add
for i = 1 to nDice
droll = (rand16() % dieSize) + 1
if luck > 0
if (rand16() % 100) < (luck * 2)
droll = max(droll, (rand16() % dieSize) + 1)
fin
elsif luck < 0
if (rand16() % 100) < (luck * -2)
droll = min(droll, (rand16() % dieSize) + 1)
fin
fin
result = result + droll
next
return result
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def rollDice(encoded)#1
return rollDiceWithLuck(encoded, 0)
end
///////////////////////////////////////////////////////////////////////////////////////////////////
export def rollPercentileWithLuck(luck)#1
byte result
result = rand16() % 100
if luck > 0
if (rand16() % 100) < (luck * 2)
result = max(result, rand16() % 100)
fin
elsif luck < 0
if (rand16() % 100) < (luck * -2)
result = min(result, rand16() % 100)
fin
fin
result = encoded & $F // initial add
while nDice
result = result + (rand16() % dieSize) + 1
nDice--
loop
return result
end
@ -1562,15 +1516,15 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Clear map window, then set a text area within it.
export def useMapWindow()#0
byte bottom1, bottom2
if frameLoaded == 3 // don't check mapIs3D, since we might be in an engine
setWindow(24, 154, 14, 140) // Top, Bottom, Left, Right
clearWindow()
setWindow(24, 150, 14, 140) // Top, Bottom, Left, Right
bottom1 = 154; bottom2 = 150
else
setWindow(24, 169, 14, 140) // Top, Bottom, Left, Right
clearWindow()
setWindow(24, 168, 14, 140) // Top, Bottom, Left, Right
bottom1 = 169; bottom2 = 168
fin
setWindow(24, bottom1, 14, 140) // Top, Bottom, Left, Right
clearWindow()
setWindow(24, bottom2, 14, 140) // Top, Bottom, Left, Right
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -2157,9 +2111,7 @@ export def showMapName(mapName)#0
if newNameHash <> mapNameHash
setWindow1()
clearWindow()
displayChar('Y'-$40) // center mode
displayStr(mapName)
displayChar('N'-$40) // normal mode
rawDisplayf1("^Y%s^N", mapName)
if mapIs3D and texturesLoaded; copyWindow(); fin
mapNameHash = newNameHash
fin
@ -2186,9 +2138,7 @@ export def setScriptInfo(mapName, trigTbl, wdt, hgt)#0
showMapName(mapName)
// Get ready for new encounter zones
if allowZoneInit
global=>p_encounterZones = NULL
fin
if allowZoneInit; global=>p_encounterZones = NULL; fin
// Back to the main text window.
setWindow2()
@ -2197,7 +2147,7 @@ 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.
export def scriptDisplayStr(str)#0
export def _scriptDisplayStr(str)#0
if pIntimate
pIntimate=>intimate_displayStr(str)
else
@ -2206,12 +2156,6 @@ export def scriptDisplayStr(str)#0
displayStr(str)
textDrawn = TRUE
fin
// No: tossString() // Doesn't work here, because we need to toss strings in the *parent's* frame
end
export def scriptDisplayStrNL(str)#0
scriptDisplayStr(str)
displayStr("\n")
end
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -2244,13 +2188,14 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Show the current animation frame
def showAnimFrame()#0
byte top
if curPortrait
// Blit portrait to the appropriate area on the screen
top = 32 // start at 4th text line in 2D
if frameLoaded == 3 // 3D-mode frame? Note: don't check mapIs3D, because we might be in an engine
blit(1, curPortrait + 2, getScreenLine(24)+2, 128, 18) // start at 3rd text line
else
blit(1, curPortrait + 2, getScreenLine(32)+2, 128, 18) // start at 4th text line
top = 24 // start at 4th text line in 3D
fin
blit(1, curPortrait + 2, getScreenLine(top)+2, 128, 18)
needRender = FALSE // suppress display of map for this frame
elsif curFullscreenImg
blit(1, curFullscreenImg + 2, getScreenLine(0), 192, 40) // the +2 is to skip anim hdr offset
@ -2749,8 +2694,7 @@ def loadTitle()#0
mmgr(FREE_MEMORY, pFont)
// Split the expander (relocating most of it to aux LC ram)
readAuxByte($1A) // sets up aux routine
expanderSize = splitExpander(expandVec)
expanderSize = splitExpander()
// Lock in the part of the expander that remains in low aux mem.
auxMmgr(FREE_MEMORY, expandVec)
@ -2779,7 +2723,7 @@ export def initHeap(loadedSize)#0
heapLocked = TRUE
fin
if loadedSize <> 0
if loadedSize
mmgr(SET_MEM_TARGET, HEAP_BOTTOM + loadedSize)
fin
mmgr(HEAP_SET, HEAP_BOTTOM)
@ -2788,7 +2732,7 @@ export def initHeap(loadedSize)#0
mmgr(HEAP_ADD_TYPE, typeTbls[i])
i = i+1
loop
if loadedSize <> 0
if loadedSize
global = HEAP_BOTTOM
else
global = mmgr(HEAP_ALLOC, TYPE_GLOBAL)
@ -3169,6 +3113,7 @@ end
///////////////////////////////////////////////////////////////////////////////////////////////////
// Main code.
//
scriptDisplayStr(@_scriptDisplayStr) // 1-time init
loadTitle()
startGame(TRUE) // ask whether new or load
kbdLoop()

View File

@ -89,7 +89,7 @@ word[5] arpeggioDuration = DUR16TH, DUR16TH, DUR16TH/2, DUR16TH/3, DUR16TH/4
// These are utility sequences/routines needed to test the music sequencer code.
//
asm toneTrack
include "ultima3.seq"
include "test.seq"
end
asm putc(ch)#0
LDA ESTKL,X
@ -477,17 +477,18 @@ def mbSequence(yield, func)#0
//
period = seqEvent->perchanvol
if period
psgWrite(mbVIA1, MIXER, $1C) // NG on C, Tone on B, A
psgWrite(mbVIA1, CENVAMP, $10)
psgWrite(mbVIA1, NGFREQ, note)
psgWrite(mbVIA1, ENVPERIOD+1, period)
psgWrite(mbVIA1, ENVSHAPE, $00) // Single decay
if mbVIA2
if (period & $80)
psgWrite(mbVIA1, MIXER, $1C) // NG on C, Tone on B, A
psgWrite(mbVIA1, CENVAMP, $10)
psgWrite(mbVIA1, ENVSHAPE, (note >> 4) & $04)
psgWrite(mbVIA1, NGFREQ, (note >> 1) & $1F)
psgWrite(mbVIA1, ENVPERIOD+1, period & $7F)
elsif mbVIA2
psgWrite(mbVIA2, MIXER, $1C) // NG on C, Tone on B, A
psgWrite(mbVIA2, CENVAMP, $10)
psgWrite(mbVIA2, NGFREQ, note)
psgWrite(mbVIA2, ENVSHAPE, (note >> 4) & $04)
psgWrite(mbVIA2, NGFREQ, (note >> 1) & $1F)
psgWrite(mbVIA2, ENVPERIOD+1, period)
psgWrite(mbVIA2, ENVSHAPE, $00) // Single decay
fin
else
if seqRepeat
@ -662,7 +663,7 @@ def spkrSequence(yield, func)#0
// Percussion event
//
if seqEvent->perchanvol
spkrPWM($D000, 0, 64) // Play some random sample as percussion
//spkrPWM($D000, 0, 64) // Play some random sample as percussion
else
if seqRepeat
musicPlay(seqTrack, TRUE)