mirror of
https://github.com/badvision/lawless-legends.git
synced 2025-02-21 12:29:05 +00:00
Merge branch 'master' of https://github.com/badvision/lawless-legends
This commit is contained in:
commit
e90a95f944
@ -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.
|
||||
|
BIN
Platform/Apple/tools/ConvertMidi/Ultima3.mid
Normal file
BIN
Platform/Apple/tools/ConvertMidi/Ultima3.mid
Normal file
Binary file not shown.
@ -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))
|
||||
|
Binary file not shown.
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user