diff --git a/Atari/textproc.asm b/Atari/textproc.asm index ab9fea8..dbf074d 100644 --- a/Atari/textproc.asm +++ b/Atari/textproc.asm @@ -2157,5 +2157,83 @@ zeroth_talk ldy #0 rts .endp + +;------------------------------------------------- +.proc _calc_packed_display +; Find Nth packed string inside a [len][packed-bytes] stream. +; +; in: +; TextNumberOff = index (0..) +; LineAddress4x4 = base address of the packed stream (points to first len byte) +; out: +; LineAddress4x4 = address of selected record (points to its len byte) +; trashes: A, X, Y, temp, temp2 +; +; Record size in bytes = 1 + ceil(len*5/8) +; where `len` is the 1-byte character count (max 63). +;------------------------------------------------- +@idx = temp+1 + lda TextNumberOff + sta @idx + beq done + +next_record + ldy #0 + lda (LineAddress4x4),y + sta temp ; len (low byte) + + ; advance past len byte + inw LineAddress4x4 + + ; temp2 = len*5 + 7 + lda temp + sta temp2 + lda #0 + sta temp2+1 + + ; temp2 = len*4 + asl temp2 + rol temp2+1 + asl temp2 + rol temp2+1 + ; temp2 = len*5 + clc + lda temp2 + adc temp + sta temp2 + lda temp2+1 + adc #0 + sta temp2+1 + ; +7 for ceil + clc + lda temp2 + adc #7 + sta temp2 + bcc @+ + inc temp2+1 +@ + ; >>3 (divide by 8) + lsr temp2+1 + ror temp2 + lsr temp2+1 + ror temp2 + lsr temp2+1 + ror temp2 + + ; LineAddress4x4 += temp2 + clc + lda LineAddress4x4 + adc temp2 + sta LineAddress4x4 + lda LineAddress4x4+1 + adc temp2+1 + sta LineAddress4x4+1 + + dec @idx + bne next_record + +done + rts +.endp .endif \ No newline at end of file diff --git a/C64/textproc.asm b/C64/textproc.asm index 7621f96..2942b04 100644 --- a/C64/textproc.asm +++ b/C64/textproc.asm @@ -376,6 +376,84 @@ DisplayAngle zeroth_talk rts .endp + +;------------------------------------------------- +.proc _calc_packed_display +; Find Nth packed string inside a [len][packed-bytes] stream. +; +; in: +; TextNumberOff = index (0..) +; LineAddress4x4 = base address of the packed stream (points to first len byte) +; out: +; LineAddress4x4 = address of selected record (points to its len byte) +; trashes: A, X, Y, temp, temp2 +; +; Record size in bytes = 1 + ceil(len*5/8) +; where `len` is the 1-byte character count (max 63). +;------------------------------------------------- +@idx = temp+1 + lda TextNumberOff + sta @idx + beq done + +next_record + ldy #0 + lda (LineAddress4x4),y + sta temp ; len (low byte) + + ; advance past len byte + inw LineAddress4x4 + + ; temp2 = len*5 + 7 + lda temp + sta temp2 + lda #0 + sta temp2+1 + + ; temp2 = len*4 + asl temp2 + rol temp2+1 + asl temp2 + rol temp2+1 + ; temp2 = len*5 + clc + lda temp2 + adc temp + sta temp2 + lda temp2+1 + adc #0 + sta temp2+1 + ; +7 for ceil + clc + lda temp2 + adc #7 + sta temp2 + bcc @+ + inc temp2+1 +@ + ; >>3 (divide by 8) + lsr temp2+1 + ror temp2 + lsr temp2+1 + ror temp2 + lsr temp2+1 + ror temp2 + + ; LineAddress4x4 += temp2 + clc + lda LineAddress4x4 + adc temp2 + sta LineAddress4x4 + lda LineAddress4x4+1 + adc temp2+1 + sta LineAddress4x4+1 + + dec @idx + bne next_record + +done + rts +.endp ;------------------------------------------------- diff --git a/artwork/pack_talk5.py b/artwork/pack_talk5.py new file mode 100644 index 0000000..ef2c115 --- /dev/null +++ b/artwork/pack_talk5.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +"""Pack Scorch talk texts into 5-bit stream. + +Reads artwork/talk.asm and generates artwork/talk_packed.asm. + +Design goals: +- Keep the original artwork/talk.asm as editable source of strings. +- Generate a MADS-friendly .asm include with: + - .proc talk (namespace-compatible) + - talk5_alphabet (32 chars) + - talk5_data: records of [len][packed bytes...] + - constants (NumberOfOffensiveTexts, etc.) copied verbatim + - hoverFull/hoverEmpty blocks copied verbatim (uncompressed) + +Bit packing: +- 5-bit codes are packed LSB-first. +- For each string record: + - 1 byte length (0..63) + - packed bytes little-endian (first char in bits 0..4) + +The decoder in 6502 should read 5-bit codes from the low bits. +""" + +from __future__ import annotations + +import argparse +import re +from pathlib import Path +from typing import Iterable, List, Tuple + + +# 32-symbol alphabet. +# Note: We intentionally omit 'X' to make room for punctuation. +# Order must match the decoder table. +ALPHABET = " ABCDEFGHIJKLMNOPQRSTUVWYZ'!,-.?" # length must be 32 + + +_DTA_STR_RE = re.compile(r"^\s*dta\s+d\"(.*?)\"\^\s*(?:;.*)?$") + + +def _iter_lines(path: Path) -> List[str]: + return path.read_text(encoding="utf-8", errors="replace").splitlines() + + +def _find_section(lines: List[str], start_pat: re.Pattern[str], end_pat: re.Pattern[str]) -> Tuple[int, int]: + start_idx = None + for i, line in enumerate(lines): + if start_pat.search(line): + start_idx = i + break + if start_idx is None: + raise ValueError(f"Start pattern not found: {start_pat.pattern}") + + for j in range(start_idx + 1, len(lines)): + if end_pat.search(lines[j]): + return start_idx, j + raise ValueError(f"End pattern not found: {end_pat.pattern}") + + +def extract_talk_strings(lines: List[str]) -> List[str]: + # Only pack strings inside `.proc talk` up to the `LEND` marker. + proc_start, _ = _find_section(lines, re.compile(r"^\s*\.proc\s+talk\b"), re.compile(r"^\s*\.endp\b")) + + lend_idx = None + for i in range(proc_start, len(lines)): + if re.match(r"^\s*LEND\b", lines[i]): + lend_idx = i + break + if lend_idx is None: + raise ValueError("LEND marker not found inside .proc talk") + + strings: List[str] = [] + for line in lines[proc_start:lend_idx]: + m = _DTA_STR_RE.match(line) + if m: + strings.append(m.group(1)) + + if not strings: + raise ValueError("No talk strings found to pack") + + return strings + + +def extract_constants_block(lines: List[str]) -> List[str]: + # Copy constant definitions from after LEND up to `.endp` (inclusive of constants, exclusive of .endp). + proc_start, proc_end = _find_section(lines, re.compile(r"^\s*\.proc\s+talk\b"), re.compile(r"^\s*\.endp\b")) + + lend_idx = None + for i in range(proc_start, proc_end + 1): + if re.match(r"^\s*LEND\b", lines[i]): + lend_idx = i + break + if lend_idx is None: + raise ValueError("LEND marker not found inside .proc talk") + + # Keep from LEND line through the line before `.endp`. + return lines[lend_idx:proc_end] + + +def extract_tail_after_talk_proc(lines: List[str]) -> List[str]: + # Copy everything after `.endp` for talk proc. This includes hoverFull/hoverEmpty. + _, proc_end = _find_section(lines, re.compile(r"^\s*\.proc\s+talk\b"), re.compile(r"^\s*\.endp\b")) + return lines[proc_end + 1 :] + + +def validate_alphabet() -> None: + if len(ALPHABET) != 32: + raise ValueError(f"ALPHABET must be 32 chars, got {len(ALPHABET)}") + if len(set(ALPHABET)) != len(ALPHABET): + raise ValueError("ALPHABET has duplicate characters") + + +def pack_string_5bit(s: str, mapping: dict[str, int]) -> bytes: + if len(s) > 63: + raise ValueError(f"String too long ({len(s)}): {s!r}") + + out = bytearray() + out.append(len(s) & 0xFF) + + bitbuf = 0 + bitcount = 0 + + for ch in s: + try: + code = mapping[ch] + except KeyError as e: + raise ValueError(f"Character {ch!r} not in alphabet") from e + + bitbuf |= (code & 0x1F) << bitcount + bitcount += 5 + + while bitcount >= 8: + out.append(bitbuf & 0xFF) + bitbuf >>= 8 + bitcount -= 8 + + if bitcount: + out.append(bitbuf & 0xFF) + + return bytes(out) + + +def format_dta_bytes(data: bytes, indent: str = " ", per_line: int = 16) -> List[str]: + lines: List[str] = [] + for i in range(0, len(data), per_line): + chunk = data[i : i + per_line] + nums = ",".join(f"${b:02x}" for b in chunk) + lines.append(f"{indent}dta b({nums})") + return lines + + +def generate_output( + source_path: Path, + strings: List[str], + constants_block: List[str], + tail_lines: List[str], +) -> str: + mapping = {ch: i for i, ch in enumerate(ALPHABET)} + + packed_records: List[bytes] = [pack_string_5bit(s, mapping) for s in strings] + + out_lines: List[str] = [] + out_lines.append("; AUTO-GENERATED FILE - DO NOT EDIT") + out_lines.append(f"; Generated by {source_path.name} -> pack_talk5.py") + out_lines.append("; Source: artwork/talk.asm") + out_lines.append("") + + out_lines.append(".proc talk") + out_lines.append("; 5-bit packed talk strings (len + packed bytes)") + out_lines.append(f"talk5_alphabet dta d\"{ALPHABET}\"") + out_lines.append("talk5_data") + + for rec in packed_records: + out_lines.extend(format_dta_bytes(rec)) + + out_lines.append(";") + out_lines.append("; Constants copied from source") + out_lines.extend(constants_block) + out_lines.append(".endp") + + if tail_lines: + out_lines.append("") + out_lines.append("; Tail copied from source (uncompressed)") + out_lines.extend(tail_lines) + + out_lines.append("") + return "\n".join(out_lines) + + +def main(argv: List[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Pack artwork/talk.asm into a 5-bit blob for MADS.") + parser.add_argument( + "--src", + default="artwork/talk.asm", + help="Path to source talk.asm (default: artwork/talk.asm)", + ) + parser.add_argument( + "--out", + default="artwork/talk_packed.asm", + help="Path to output .asm include (default: artwork/talk_packed.asm)", + ) + + args = parser.parse_args(argv) + + validate_alphabet() + + src_path = Path(args.src) + out_path = Path(args.out) + + lines = _iter_lines(src_path) + strings = extract_talk_strings(lines) + constants_block = extract_constants_block(lines) + tail_lines = extract_tail_after_talk_proc(lines) + + content = generate_output(src_path, strings, constants_block, tail_lines) + + out_path.write_text(content, encoding="utf-8") + print(f"Wrote {out_path} ({len(content.encode('utf-8'))} bytes text)") + print(f"Packed {len(strings)} strings") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/artwork/talk.asm b/artwork/talk.asm index e620fb6..28989d1 100644 --- a/artwork/talk.asm +++ b/artwork/talk.asm @@ -101,23 +101,23 @@ dta d"I LET YOU HIT ME!"^ dta d"SUCKER SHOT!"^ dta d"I DIDN'T WANT TO LIVE ANYWAY."^ - dta d"--"^ + dta d".-SOB-."^ dta d"WAS THAT AS CLOSE AS I THINK IT WAS?"^ dta d"JOIN THE ARMY, SEE THE WORLD THEY SAID."^ dta d"IT WASN'T JUST A JOB, IT WAS AN ADVENTURE!"^ dta d"I DIDN'T LIKE VIOLENCE ANYWAY!"^ dta d"I THOUGHT YOU LIKED ME?"^ - dta d"CTO XYEB"^ + dta d"RUSH B!"^ dta d"I THINK THIS GUY'S A LITTLE CRAZY."^ dta d"SOMEHOW I DON'T FEEL LIKE KILLING ANYMORE."^ dta d"HEY! KILLIN' AIN'T COOL."^ dta d"GEE... THANKS."^ dta d"I'VE FALLEN AND I CAN'T GET UP!"^ - dta d"911?"^ + dta d"NINE ONE ONE?"^ dta d"OH NO! HERE I BLOW AGAIN!"^ dta d"I'LL BE BACK..."^ dta d"I'VE GOT LAWYERS!"^ - dta d"CALL 1-900-SUE-TANK."^ + dta d"CALL MY LAWYERS!"^ dta d"YOU BIG DUMMY!"^ ;(sanford and son) LEND NumberOfOffensiveTexts=55 diff --git a/artwork/talk_packed.asm b/artwork/talk_packed.asm new file mode 100644 index 0000000..1810ab3 --- /dev/null +++ b/artwork/talk_packed.asm @@ -0,0 +1,174 @@ +; AUTO-GENERATED FILE - DO NOT EDIT +; Generated by talk.asm -> pack_talk5.py +; Source: artwork/talk.asm + +.proc talk +; 5-bit packed talk strings (len + packed bytes) +talk5_alphabet dta d" ABCDEFGHIJKLMNOPQRSTUVWYZ'!,-.?" +talk5_data + dta b($04,$24,$95,$0d) + dta b($10,$a6,$4a,$40,$4a,$a5,$73,$20,$16,$1c,$d9) + dta b($11,$f8,$55,$2d,$0b,$20,$25,$10,$d0,$4a,$a0,$1e) + dta b($0f,$24,$15,$30,$5e,$6b,$a9,$00,$98,$ce,$06) + dta b($08,$36,$0d,$fa,$24,$de) + dta b($11,$24,$15,$0e,$02,$4b,$c5,$81,$79,$93,$2b,$1b) + dta b($08,$e1,$ca,$ba,$f6,$de) + dta b($09,$03,$af,$00,$04,$0b,$14) + dta b($0a,$34,$ac,$02,$28,$4a,$73,$03) + dta b($0e,$25,$50,$d0,$30,$98,$e8,$49,$3a,$37) + dta b($0d,$f8,$55,$2d,$0b,$a0,$2f,$4c,$ba,$01) + dta b($07,$22,$b8,$1c,$d2,$06) + dta b($0a,$0f,$16,$07,$6e,$22,$65,$03) + dta b($09,$28,$00,$14,$00,$0a,$1e) + dta b($07,$03,$05,$79,$ca,$06) + dta b($07,$81,$d2,$30,$d6,$06) + dta b($0f,$24,$15,$0e,$68,$70,$ab,$cf,$51,$db,$06) + dta b($0d,$c9,$01,$fc,$aa,$04,$26,$8c,$b2,$01) + dta b($0c,$34,$ac,$02,$02,$40,$69,$95,$0d) + dta b($0c,$2d,$ac,$02,$1a,$06,$24,$60,$0f) + dta b($0d,$cb,$bd,$b1,$38,$58,$ee,$8d,$e5,$01) + dta b($23,$46,$be,$06,$50,$61,$4c,$4f,$80,$4a,$90,$14,$24,$30,$69,$10) + dta b($20,$50,$40,$51,$29,$de,$7b) + dta b($18,$e4,$01,$fc,$2a,$30,$a5,$30,$c0,$ea,$58,$98,$03,$1a,$dc,$fa) + dta b($12,$f8,$55,$2d,$0b,$78,$95,$d2,$00,$50,$91,$c5,$03) + dta b($11,$37,$50,$3a,$03,$68,$81,$d2,$00,$f0,$ab,$1f) + dta b($16,$46,$96,$92,$0b,$07,$4f,$82,$a4,$19,$03,$13,$bd,$47,$37) + dta b($21,$b7,$80,$f1,$5a,$01,$c9,$01,$58,$c2,$28,$a0,$83,$89,$de,$a3) + dta b($80,$3e,$b0,$12,$63,$1b) + dta b($2a,$09,$b0,$67,$0b,$a0,$a8,$80,$d9,$0a,$63,$e0,$19,$e0,$02,$0c) + dta b($ac,$81,$e4,$00,$45,$05,$b4,$27,$5d,$72,$c7,$03) + dta b($12,$13,$bd,$0b,$e6,$6b,$05,$c8,$32,$61,$19,$d4,$03) + dta b($1e,$aa,$4e,$0a,$2e,$7a,$80,$3c,$80,$5f,$05,$14,$25,$b7,$00,$7e) + dta b($15,$04,$59,$3e) + dta b($0f,$ec,$bd,$05,$5e,$a5,$40,$14,$f6,$ee,$06) + dta b($10,$ec,$bd,$05,$9e,$2d,$12,$50,$54,$64,$f1) + dta b($1f,$a7,$96,$39,$c1,$45,$81,$ea,$09,$c6,$6b,$c9,$1d,$60,$9e,$04) + dta b($24,$39,$57,$e4,$07) + dta b($10,$ad,$48,$89,$c1,$40,$32,$4d,$da,$c2,$f4) + dta b($14,$a8,$c8,$02,$ce,$2b,$13,$b8,$47,$51,$72,$c7,$7b,$0f) + dta b($23,$e4,$39,$4d,$c1,$7d,$52,$62,$0e,$12,$05,$69,$3a,$4d,$41,$00) + dta b($2c,$d9,$02,$e4,$ab,$8e,$78) + dta b($16,$82,$bd,$47,$38,$80,$21,$39,$0e,$6c,$7a,$ac,$b8,$51,$36) + dta b($11,$34,$ac,$02,$28,$4a,$93,$83,$99,$e6,$c4,$1b) + dta b($14,$09,$4c,$14,$18,$03,$86,$05,$4a,$8b,$03,$f8,$d5,$0d) + dta b($1d,$09,$4c,$14,$18,$03,$b3,$85,$89,$00,$7e,$55,$82,$7a,$18,$06) + dta b($34,$b8,$b5,$01) + dta b($1f,$09,$dc,$e7,$48,$91,$e0,$a2,$40,$01,$45,$69,$02,$51,$29,$7d) + dta b($0e,$90,$57,$e6,$07) + dta b($1b,$e4,$39,$4d,$01,$0d,$ab,$00,$8a,$d2,$04,$b0,$c8,$f9,$5c,$60) + dta b($0c,$7b) + dta b($18,$f7,$55,$46,$00,$45,$69,$82,$16,$56,$01,$f8,$55,$d0,$02,$f9) + dta b($24,$09,$d0,$c7,$08,$c0,$af,$02,$fa,$00,$2b,$c1,$16,$d0,$30,$98) + dta b($69,$d2,$22,$41,$60,$cf,$95,$0d) + dta b($1b,$09,$8c,$57,$19,$01,$13,$06,$59,$00,$7e,$95,$03,$51,$29,$b8) + dta b($08,$7f) + dta b($1d,$0d,$03,$f1,$9a,$00,$69,$02,$91,$ce,$29,$12,$50,$14,$1c,$c0) + dta b($af,$ca,$e9,$01) + dta b($16,$e4,$39,$4d,$81,$79,$f2,$14,$0a,$82,$78,$95,$82,$56,$36) + dta b($15,$28,$4c,$1a,$00,$0b,$c0,$a6,$49,$03,$07,$22,$08,$bc,$01) + dta b($1d,$14,$a5,$09,$d2,$04,$f8,$55,$09,$84,$0c,$c9,$81,$e7,$c0,$1c) + dta b($4f,$0e,$e4,$01) + dta b($24,$14,$a5,$09,$e6,$90,$a5,$38,$10,$92,$d3,$14,$88,$74,$40,$71) + dta b($af,$1e,$04,$cc,$93,$a0,$4e,$0f) + dta b($26,$09,$4c,$14,$18,$03,$2f,$31,$d0,$30,$a0,$55,$ca,$42,$c1,$4d) + dta b($14,$01,$fc,$aa,$04,$82,$bd,$47,$3c) + dta b($1a,$49,$37,$70,$9e,$73,$01,$08,$59,$c2,$02,$f8,$55,$09,$4c,$18) + dta b($65,$03) + dta b($18,$2d,$b4,$00,$66,$48,$04,$2c,$f7,$c6,$02,$f8,$55,$f0,$2a,$dd) + dta b($16,$09,$a0,$07,$0b,$c0,$af,$82,$e2,$d4,$c3,$00,$86,$e4,$36) + dta b($24,$e8,$5d,$4d,$00,$7e,$15,$b0,$b4,$0a,$78,$ae,$80,$30,$e4,$9b) + dta b($13,$e0,$57,$25,$60,$09,$ce,$0f) + dta b($24,$30,$48,$9a,$dc,$01,$69,$82,$59,$07,$02,$f3,$96,$42,$c1,$7c) + dta b($52,$be,$eb,$bd,$07,$ee,$d1,$0d) + dta b($04,$f5,$a0,$0d) + dta b($06,$21,$c8,$83,$36) + dta b($09,$21,$84,$73,$10,$42,$1b) + dta b($0c,$49,$37,$d0,$0a,$a3,$c9,$9d,$0d) + dta b($05,$ef,$19,$ef,$01) + dta b($03,$0f,$6d) + dta b($05,$a5,$94,$b5,$01) + dta b($06,$21,$8c,$81,$36) + dta b($1c,$09,$a0,$40,$0b,$48,$14,$5c,$54,$1c,$a0,$28,$50,$80,$02,$84) + dta b($c5,$4d,$0f) + dta b($20,$cf,$15,$80,$12,$05,$23,$38,$20,$6b,$72,$00,$bf,$2a,$c1,$45) + dta b($8f,$15,$40,$02,$f6) + dta b($06,$0f,$01,$f7,$36) + dta b($07,$ee,$51,$d0,$ca,$06) + dta b($05,$af,$0e,$e4,$01) + dta b($11,$0f,$01,$f7,$38,$70,$8f,$82,$70,$42,$72,$1e) + dta b($1b,$c1,$3d,$8a,$8a,$04,$cf,$15,$20,$12,$2d,$13,$50,$54,$00,$a9) + dta b($93,$7a) + dta b($08,$e7,$3d,$22,$70,$f1) + dta b($08,$a8,$30,$08,$5a,$d9) + dta b($16,$26,$c8,$72,$0b,$63,$1c,$0c,$59,$0b,$03,$f7,$49,$46,$3c) + dta b($13,$b2,$b4,$d2,$44,$91,$80,$a2,$02,$02,$0b,$ed,$6d) + dta b($07,$0f,$81,$16,$dc,$06) + dta b($07,$e4,$bd,$7a,$d0,$06) + dta b($12,$ae,$5c,$40,$02,$e6,$c0,$95,$0b,$c4,$6b,$c2,$03) + dta b($20,$14,$a5,$09,$d2,$04,$14,$15,$50,$1c,$e1,$a0,$61,$f0,$1c,$c3) + dta b($c0,$c8,$54,$1c,$f1) + dta b($0b,$b6,$48,$0c,$4c,$75,$0e,$7b) + dta b($12,$14,$15,$60,$02,$05,$2c,$10,$0c,$66,$70,$c7,$03) + dta b($16,$17,$61,$40,$5e,$99,$00,$05,$08,$8b,$03,$f4,$81,$56,$3e) + dta b($0f,$49,$37,$70,$5e,$72,$07,$90,$77,$9d,$07) + dta b($17,$49,$db,$02,$ce,$a3,$20,$00,$11,$08,$30,$a5,$b0,$e4,$8e,$07) + dta b($08,$43,$06,$f8,$58,$f0) + dta b($04,$f0,$dd,$0d) + dta b($04,$22,$99,$0d) + dta b($04,$22,$b4,$0d) + dta b($05,$f9,$b9,$b5,$01) + dta b($1f,$09,$4c,$f4,$2a,$23,$da,$16,$c0,$d2,$a4,$c5,$15,$02,$e8,$03) + dta b($0d,$83,$f6,$9a,$07) + dta b($2a,$09,$dc,$c0,$16,$a0,$48,$be,$7a,$10,$a0,$a8,$00,$1b,$18,$2b) + dta b($18,$3c,$03,$28,$2a,$60,$a2,$40,$de,$f5,$de,$03) + dta b($14,$17,$05,$0a,$6e,$98,$80,$a2,$40,$81,$7b,$69,$96,$0f) + dta b($24,$2d,$b4,$00,$66,$48,$04,$50,$54,$64,$d1,$04,$88,$02,$48,$c0) + dta b($13,$b0,$b4,$0a,$a0,$28,$4d,$0f) + dta b($1e,$89,$ea,$09,$54,$9d,$14,$3c,$57,$c0,$33,$80,$a2,$37,$0b,$20) + dta b($01,$4f,$ef,$3d) + dta b($17,$09,$cc,$52,$40,$00,$42,$a6,$83,$28,$60,$e9,$20,$ea,$bd,$07) + dta b($13,$ed,$b5,$86,$3f,$48,$13,$50,$14,$28,$c0,$af,$7e) + dta b($11,$09,$b0,$42,$01,$7e,$15,$a0,$44,$41,$2b,$1b) + dta b($0c,$b3,$8e,$55,$24,$98,$e8,$d1,$0d) + dta b($1d,$09,$90,$44,$9c,$a6,$e0,$06,$47,$01,$7d,$80,$25,$5b,$40,$70) + dta b($f8,$06,$ec,$01) + dta b($07,$be,$cf,$27,$ba,$07) + dta b($24,$37,$4c,$40,$51,$a0,$20,$4c,$30,$d8,$9b,$05,$84,$09,$12,$a0) + dta b($28,$b9,$05,$12,$05,$37,$cc,$0f) + dta b($27,$ea,$25,$07,$28,$2a,$20,$c8,$86,$39,$98,$a5,$00,$8a,$0a,$b8) + dta b($4f,$32,$02,$28,$2a,$18,$cc,$90,$88,$07) + dta b($2a,$89,$82,$1b,$a6,$d3,$14,$a8,$3a,$29,$08,$40,$3d,$c1,$41,$a2) + dta b($e0,$86,$09,$82,$03,$81,$d8,$e2,$68,$95,$65,$03) + dta b($1e,$09,$90,$44,$9c,$a6,$80,$a5,$55,$80,$4d,$8f,$15,$37,$0a,$08) + dta b($0e,$df,$80,$37) + dta b($17,$09,$50,$f4,$ea,$41,$14,$e0,$57,$01,$4b,$ab,$10,$d0,$ca,$07) + dta b($07,$b2,$4e,$04,$c4,$06) + dta b($22,$09,$50,$94,$dc,$02,$14,$a5,$09,$4e,$c5,$7a,$82,$00,$58,$a2) + dta b($94,$15,$30,$64,$c8,$d8,$03) + dta b($2a,$f3,$b5,$82,$de,$05,$09,$90,$e7,$34,$05,$a6,$14,$06,$58,$5a) + dta b($05,$ac,$c4,$58,$72,$07,$04,$87,$db,$93,$c5,$03) + dta b($18,$a8,$e0,$0d,$56,$62,$2c,$39,$0d,$42,$72,$9a,$82,$f1,$1e,$f3) + dta b($0e,$a7,$14,$ef,$3d,$a0,$28,$b8,$35,$3d) + dta b($1f,$49,$db,$02,$4c,$60,$ac,$38,$10,$1c,$01,$09,$8c,$e0,$34,$05) + dta b($a7,$50,$50,$e1,$06) + dta b($0d,$2e,$b9,$02,$9e,$2b,$e0,$b9,$f2,$01) + dta b($19,$0f,$01,$f7,$36,$40,$45,$16,$90,$80,$60,$ef,$82,$70,$42,$72) + dta b($1b) + dta b($0f,$49,$33,$06,$44,$01,$22,$8c,$e5,$bd,$07) + dta b($11,$49,$db,$02,$ce,$a3,$80,$85,$8b,$8b,$9c,$1b) + dta b($10,$23,$30,$06,$1a,$06,$2c,$5c,$5c,$e4,$dc) + dta b($0e,$f8,$55,$20,$d2,$01,$a4,$b6,$86,$37) +; +; Constants copied from source +LEND +NumberOfOffensiveTexts=55 +NumberOfDeffensiveTexts=62 +NumberOfPropagandaTexts=21 +VeryFunnyText=79 +.endp + +; Tail copied from source (uncompressed) +hoverFull dta d"MY HOVERCRAFT IS FULL OF EELS!"^ +hoverFullEnd +hoverEmpty dta d"RUNNING OUT OF EELS"^ +hoverEmptyEnd diff --git a/grafproc.asm b/grafproc.asm index 3a48a61..6cdfeb7 100644 --- a/grafproc.asm +++ b/grafproc.asm @@ -1422,22 +1422,35 @@ notZero sta plot4x4color tya tax ; save Y - mwa #talk LineAddress4x4 - jsr _calc_inverse_display - - ; now find length of the text -@ iny - lda (LineAddress4x4),y - bpl @- - iny - sty fx + mwa #talk.talk5_data LineAddress4x4 + jsr _calc_packed_display + + ; record starts with length byte + ldy #0 + lda (LineAddress4x4),y + sta fx + inw LineAddress4x4 ; point to packed payload txa ; load Y tay + jmp Display4x4AboveTankPacked +.endp + +;-------------------------------------------------------- +.proc Display4x4AboveTankPacked ; + ; Displays packed texts using PutChar4x4 above tank and mountains. + ;parameters are: + ;Y - number of tank above which text is displayed + ;fx - length of text + ;LineAddress4x4 - address of packed payload + + lda xtankstableL,y + sta temp + lda xtankstableH,y + sta temp+1 + jsr Calculate4x4TextPosition + jmp TypeLine4x4Packed.noLengthNoColor - ;jsr Display4x4AboveTank - ;rts - ; POZOR !!! .endp ;-------------------------------------------------------- @@ -1627,6 +1640,115 @@ EndOfTypeLine4x4 rts .endp +;------------------------------- +.proc TypeLine4x4Packed ; +;------------------------------- + ;this routine prints packed line of length `fx` + ;packed payload address in LineAddress4x4 + ;starting from LineXdraw, LineYdraw + + lda #14 ; default length of 4x4 texts + sta fx + +variableLength + lda #$ff ; $ff - visible characters, $00 - clearing + +staplot4x4color + sta plot4x4color +noLengthNoColor + + ; init packed bitstream + ; Reuse ZP vars: + ; - LineAddress4x4 is the packed byte pointer (advanced during decoding) + ; - Multiplier/Multiplier_ are used as bit buffer/state + lda #0 + sta Multiplier + sta Multiplier+1 + sta Multiplier_ + + ldy #0 + sty LineCharNr + +TypeLine4x4PackedLoop + ldy LineCharNr + + jsr _packed_get5bits + tay + lda talk.talk5_alphabet,y + sta CharCode4x4 + mwa LineXdraw dx + mva LineYdraw dy + mva #0 dy+1 ; dy is 2 bytes value + jsr PutChar4x4 ;type empty pixels as well! + adw LineXdraw #4 + inc:lda LineCharNr + cmp fx + bne TypeLine4x4PackedLoop + + rts +.endp + +;-------------------------------- +; Packed 5-bit bitstream decoder +; +; Uses a 16-bit shift register where the lowest bits are the next bits to read. +; Bytes are appended little-endian (LSB-first) at bit position PackedBitCount4x4. +;-------------------------------- +.proc _packed_get5bits + ; ensure at least 5 bits available + lda Multiplier_ + cmp #5 + bcs have_bits + + ; read next byte and append at current bit count + ldy #0 + lda (LineAddress4x4),y + sta temp2 ; new byte + lda #0 + sta temp2+1 + inw LineAddress4x4 + + ldx Multiplier_ + beq append_ready +append_shift + asl temp2 + rol temp2+1 + dex + bne append_shift +append_ready + lda Multiplier + ora temp2 + sta Multiplier + lda Multiplier+1 + ora temp2+1 + sta Multiplier+1 + + lda Multiplier_ + clc + adc #8 + sta Multiplier_ + +have_bits + lda Multiplier + and #$1f + sta temp2 + + ; shift register right by 5 + ldx #5 +@ lsr Multiplier+1 + ror Multiplier + dex + bne @- + + lda Multiplier_ + sec + sbc #5 + sta Multiplier_ + + lda temp2 + rts +.endp + ;-------------------------------- .proc AreYouSure diff --git a/scorch.asm b/scorch.asm index 03a418a..add9950 100644 --- a/scorch.asm +++ b/scorch.asm @@ -535,7 +535,7 @@ noingame ;---------------------------------------------- icl 'ai.asm' ;---------------------------------------------- - icl 'artwork/talk.asm' + icl 'artwork/talk_packed.asm' ;---------------------------------------------- TankFont ins 'artwork/tanksv4.fnt',+0,384 ; 48 characters only diff --git a/scorch5bit.xex b/scorch5bit.xex new file mode 100644 index 0000000..d824067 Binary files /dev/null and b/scorch5bit.xex differ diff --git a/scorchC64.asm b/scorchC64.asm index b42f537..65e599d 100644 --- a/scorchC64.asm +++ b/scorchC64.asm @@ -315,7 +315,7 @@ noShellDelay ;---------------------------------------------- icl 'ai.asm' ;---------------------------------------------- - icl 'artwork/talk.asm' + icl 'artwork/talk_packed.asm' ;---------------------------------------------- TankFont ins 'artwork/tanksv4.fnt',+0,384 ; 48 characters only diff --git a/weapons.asm b/weapons.asm index ecfe047..80ee12f 100644 --- a/weapons.asm +++ b/weapons.asm @@ -399,19 +399,15 @@ nexttext bcs @- sta TextNumberOff - ; all text start from `talk` and end with an inverse. - ; we go through the `talk`, count number of inverses. - ; if equal to TextNumberOff, it is our text, printit lda #$ff sta plot4x4color - mwa #talk LineAddress4x4 - jsr _calc_inverse_display - ; now find length of the text -@ iny + mwa #talk.talk5_data LineAddress4x4 + jsr _calc_packed_display + ; record starts with length byte + ldy #0 lda (LineAddress4x4),y - bpl @- - iny - sty fx + sta fx + inw LineAddress4x4 ; point to packed payload mwa tempXROLLER temp ; X coordinate of hitpoint ; calculate position of message jsr Calculate4x4TextPosition @@ -436,7 +432,7 @@ nexttext dec LineXdraw+1 DisplayMessage ; display propaganda message - jsr TypeLine4x4.noLengthNoColor + jsr TypeLine4x4Packed.noLengthNoColor ldy #7 jsr PauseYFrames