diff --git a/Platform/Apple/tools/ConvertMidi/README.md b/Platform/Apple/tools/ConvertMidi/README.md index 022ae4fc..5aca321f 100644 --- a/Platform/Apple/tools/ConvertMidi/README.md +++ b/Platform/Apple/tools/ConvertMidi/README.md @@ -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. diff --git a/Platform/Apple/tools/ConvertMidi/Ultima3.mid b/Platform/Apple/tools/ConvertMidi/Ultima3.mid new file mode 100644 index 00000000..d8030201 Binary files /dev/null and b/Platform/Apple/tools/ConvertMidi/Ultima3.mid differ diff --git a/Platform/Apple/tools/ConvertMidi/cvtmid.py b/Platform/Apple/tools/ConvertMidi/cvtmid.py index 19b5ad28..61b9b491 100644 --- a/Platform/Apple/tools/ConvertMidi/cvtmid.py +++ b/Platform/Apple/tools/ConvertMidi/cvtmid.py @@ -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)) diff --git a/Platform/Apple/tools/PLASMA/src/plasm.jar b/Platform/Apple/tools/PLASMA/src/plasm.jar index a1d46870..7546e7e3 100644 Binary files a/Platform/Apple/tools/PLASMA/src/plasm.jar and b/Platform/Apple/tools/PLASMA/src/plasm.jar differ diff --git a/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy b/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy index f378e2c4..8b353405 100644 --- a/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy +++ b/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy @@ -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 { diff --git a/Platform/Apple/virtual/src/core/mem.s b/Platform/Apple/virtual/src/core/mem.s index fd79b660..9f59adff 100644 --- a/Platform/Apple/virtual/src/core/mem.s +++ b/Platform/Apple/virtual/src/core/mem.s @@ -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 + 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 + 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 diff --git a/Platform/Apple/virtual/src/plasma/combat.pla b/Platform/Apple/virtual/src/plasma/combat.pla index 764d96a7..3ab13b1c 100644 --- a/Platform/Apple/virtual/src/plasma/combat.pla +++ b/Platform/Apple/virtual/src/plasma/combat.pla @@ -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 diff --git a/Platform/Apple/virtual/src/plasma/gamelib.plh b/Platform/Apple/virtual/src/plasma/gamelib.plh index 29a780c9..04c99b9c 100644 --- a/Platform/Apple/virtual/src/plasma/gamelib.plh +++ b/Platform/Apple/virtual/src/plasma/gamelib.plh @@ -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 diff --git a/Platform/Apple/virtual/src/plasma/gameloop.pla b/Platform/Apple/virtual/src/plasma/gameloop.pla index 63c0e7c9..b1d44a19 100644 --- a/Platform/Apple/virtual/src/plasma/gameloop.pla +++ b/Platform/Apple/virtual/src/plasma/gameloop.pla @@ -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() diff --git a/Platform/Apple/virtual/src/plasma/music.pla b/Platform/Apple/virtual/src/plasma/music.pla index 202c74ef..7c397074 100644 --- a/Platform/Apple/virtual/src/plasma/music.pla +++ b/Platform/Apple/virtual/src/plasma/music.pla @@ -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)