• Welcome to Schneider / Amstrad CPC Forum.
Welcome to Schneider / Amstrad CPC Forum. Please login.

16. April 2026, 09:06:21

Login with username, password and session length

Shoutbox

TFM

2024-04-08, 20:42:44
Happy Sonnenfinsternis!  :)

TFM

2024-01-15, 17:06:57
Momentan billige Farbbänder auf Ebay für PCW

Devilmarkus

2023-07-09, 10:37:40
Zweiter 👋😂🤣

TFM

2023-06-13, 14:21:49
Sommerloch!

Recent

Members
Stats
  • Total Posts: 12,832
  • Total Topics: 1,528
  • Online today: 444
  • Online ever: 1,724
  • (16. January 2020, 00:18:45)
Users Online
Users: 1
Guests: 233
Total: 234

233 Guests, 1 User
GarfieldTheFirst

BASIC Textadventure bequem online erzeugen

Started by Devilmarkus, 10. February 2026, 14:12:35

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Devilmarkus

Ich hab mal JavaScript und HTML programmiert, um einen "CPC Adventure Generator" zu basteln, damit kann man relativ einfach komplexe Textadventure-Spiele erzeugen. Der BASIC Code kann direkt ausgegeben werden.
2 Beispiele sind bereits vorhanden.

Der Parser kann wahlweise auch auf Deutsch, Französisch oder Spanisch eingestellt werden, Default-Sprache ist Englisch.
Verwendet im Spiel allerdings keine Umlaute! ;)

Viel Spaß beim Erstellen! Ich bin auf Ergebnisse gespannt!


https://cpcwiki.de/adventure/

Anleitung findet ihr unter "Hilfe" 🕹🕹🕹
https://cpcwiki.de
Dein Deutsches CPCWiki!

TFM

Kann man da Anstelle von BASIC auch Maschinen-Sprache nehmen?
Wenn Du Lust hast das einzubauen, dann helfe ich gerne :)
TFM of FutureSoft
http://www.futureos.de --> Das Betriebssystem FutureOS (Update: 24.12.2025)
http://futureos.cpc-live.com/files/LambdaSpeak_RSX_by_TFM.zip --> RSX ROM für LambdaSpeak (Update: 29.01.2025)

Devilmarkus

Wozu? ;) Du kannst ja den Code dann in MC umschreiben :P
Da der CPC ja von Haus aus BASIC kann, ist das nicht notwendig, es soll ja die User dazu animieren, auch BASIC zu verstehen...
https://cpcwiki.de
Dein Deutsches CPCWiki!

Devilmarkus

Aber tu dir keinen Zwang an, du musst nur diesen Teil umschreiben, dass er MC Code (Assembler) erzeugt:

// ========================================
// BASIC CODE GENERATOR v5.4
// With Multilingual Command Support
// ========================================

// ========== WORD WRAP HELPER ==========
function wrapText(text, forPrint) {
    if (forPrint === undefined) forPrint = false;
    var maxLen = 40;
    if (!text || text.length <= maxLen) return text || "";
   
    var separator = forPrint
        ? "\";CHR$(13);CHR$(10);\""
        : "\"+CHR$(13)+CHR$(10)+\"";
   
    var result = "";
    var remaining = text;
    var first = true;
   
    while (remaining.length > 0) {
        if (remaining.length <= maxLen) {
            if (!first) {
                result += separator;
            }
            result += remaining;
            break;
        }
       
        var breakPos = remaining.lastIndexOf(' ', maxLen);
       
        if (breakPos <= 0) {
            breakPos = maxLen;
        }
       
        if (!first) {
            result += separator;
        }
        result += remaining.substring(0, breakPos);
       
        remaining = remaining.substring(breakPos).trimStart();
        first = false;
    }
   
    return result;
}

// ========== SAFE STRING HELPER ==========
function safeStr(str, maxLen) {
    if (!str) return "";
    if (maxLen) return String(str).substring(0, maxLen);
    return String(str);
}

// ========== BASIC CODE GENERATOR ==========
function genCode() {
    var rc = Object.keys(S.rooms).length;
    var ic = Object.keys(S.items).length;
    var nc = Object.keys(S.npcs).length;
   
    if (rc === 0) {
        console.log("genCode: No rooms found");
        return null;
    }

    var maxR = Math.max.apply(null, Object.values(S.rooms).map(function(r) { return r.num; })) + 5;
    var dimI = Math.max(ic + 5, 10);
    var dimN = Math.max(nc + 5, 5);

    var rooms = Object.entries(S.rooms).sort(function(a, b) { return a[1].num - b[1].num; });
    var items = Object.entries(S.items);
    var npcs = Object.entries(S.npcs);
    var deadly = rooms.filter(function(r) { return r[1].deadly; });
    var winItems = S.game.winItems ? S.game.winItems.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s; }) : [];
    var m = S.msg || {};

    // ========== COMMAND & KEYWORD SETUP ==========
    // Default commands (English fallback)
    var defaultCommands = {
        north: ["N", "NORTH"],
        south: ["S", "SOUTH"],
        east: ["E", "EAST"],
        west: ["W", "WEST"],
        up: ["U", "UP"],
        down: ["D", "DOWN", "DN"],
        look: ["L", "LOOK"],
        inventory: ["I", "INV", "INVENTORY"],
        take: ["TAKE", "GET"],
        drop: ["DROP"],
        use: ["USE"],
        examine: ["EXAMINE", "X"],
        open: ["OPEN"],
        close: ["CLOSE"],
        talk: ["TALK"],
        give: ["GIVE"],
        put: ["PUT"],
        score: ["SCORE"],
        help: ["HELP", "?"],
        save: ["SAVE"],
        load: ["LOAD"],
        quit: ["QUIT", "Q"]
    };
   
    var defaultKeywords = {
        to: "TO",
        from: "FROM",
        in: "IN",
        at: "AT"
    };
   
    var defaultDirections = {
        north: "N",
        south: "S",
        east: "E",
        west: "W",
        up: "UP",
        down: "DN"
    };

    var cmd = (m.commands && typeof m.commands === 'object') ? m.commands : defaultCommands;
    var kw = (m.keywords && typeof m.keywords === 'object') ? m.keywords : defaultKeywords;
    var dirs = (m.directions && typeof m.directions === 'object') ? m.directions : defaultDirections;
   
    // Helper functions
    function getKeyword(key) {
        var val = kw[key] || defaultKeywords[key] || key;
        return String(val).toUpperCase();
    }
   
    function getCmdVariants(action) {
        var variants = cmd[action];
        if (!variants || !Array.isArray(variants) || variants.length === 0) {
            // Fallback to default
            variants = defaultCommands[action];
            if (!variants || !Array.isArray(variants) || variants.length === 0) {
                return [action.toUpperCase()];
            }
        }
        return variants.map(function(v) { return String(v).toUpperCase(); });
    }
   
    function getPrimaryCmd(action) {
        return getCmdVariants(action)[0];
    }
   
    function getDirAbbr(dir) {
        var name = dirs[dir] || defaultDirections[dir] || dir;
        if (typeof name === 'string') {
            return name.length <= 2 ? name.toUpperCase() : name.substring(0, 2).toUpperCase();
        }
        return dir.substring(0, 1).toUpperCase();
    }
   
    // Keywords
    var KW_TO = getKeyword('to');
    var KW_FROM = getKeyword('from');
    var KW_IN = getKeyword('in');
    var KW_AT = getKeyword('at');
   
    // Primary commands
    var CMD_USE = getPrimaryCmd('use');
    var CMD_TAKE = getPrimaryCmd('take');
    var CMD_DROP = getPrimaryCmd('drop');
    var CMD_EXAMINE = getPrimaryCmd('examine');
    var CMD_OPEN = getPrimaryCmd('open');
    var CMD_CLOSE = getPrimaryCmd('close');
    var CMD_TALK = getPrimaryCmd('talk');
    var CMD_GIVE = getPrimaryCmd('give');
    var CMD_PUT = getPrimaryCmd('put');
    var CMD_LOOK = getPrimaryCmd('look');

    // Safe message access
    function msg(key, fallback, maxLen) {
        var val = m[key] || fallback || "";
        if (maxLen) return String(val).substring(0, maxLen);
        return String(val);
    }

    var L = [];
   
    // ========== HEADER ==========
    L.push("10 REM *** " + safeStr(S.game.title, 28).toUpperCase() + " ***");
    L.push("20 REM *** BY " + safeStr(S.game.author, 24).toUpperCase() + " ***");
    L.push("30 MODE 1:INK 0,0:INK 1,24:BORDER 0:PEN 1:PAPER 0");
    L.push("40 DIM R$(" + maxR + "),D$(" + maxR + "),RL(" + maxR + ",6),RD$(" + maxR + ",6),UL(" + maxR + ",6)");
    L.push("50 DIM IT$(" + dimI + "),ID$(" + dimI + "),IL(" + dimI + "),IC(" + dimI + "),IO(" + dimI + "),IP(" + dimI + "),IS(" + dimI + ")");
    L.push("60 DIM NP$(" + dimN + "),ND$(" + dimN + "),NT$(" + dimN + "),NW$(" + dimN + "),NR$(" + dimN + "),NS(" + dimN + "),NL(" + dimN + "),NG(" + dimN + ")");
    L.push("70 DIM TD(" + Math.max(deadly.length + 1, 3) + "),TM$(" + Math.max(deadly.length + 1, 3) + "),TS$(" + Math.max(deadly.length + 1, 3) + ")");
    L.push("80 DIM INV$(20)");
    L.push("90 CR=" + S.game.startRoom + ":SC=0:IC=0:ML=" + S.game.invLimit + ":NM=" + ic + ":NN=" + nc + ":RF=1");
    L.push("95 GOSUB 1500:GOSUB 3000");

    // ========== MAIN LOOP ==========
    L.push("100 IF RF=1 THEN GOSUB 200:RF=0");
    L.push("110 GOSUB 300");
    L.push("120 GOSUB 400");
    L.push("130 GOTO 100");

    // ========== SHOW ROOM (200-299) ==========
    L.push("200 CLS:PRINT:PRINT R$(CR):PRINT STRING$(40,42)");
    L.push("210 PRINT D$(CR):PRINT");
    L.push("220 FOR J=1 TO NM:IF IL(J)=CR AND IP(J)=0 THEN PRINT \"" + msg('youSee', 'You see: ', 40) + "\";IT$(J)");
    L.push("230 NEXT J");
    L.push("240 FOR J=1 TO NN:IF NL(J)=CR THEN PRINT \"" + msg('youSee', 'You see: ', 24) + "\";NP$(J)");
    L.push("250 NEXT J");
    L.push("260 PRINT:PRINT \"" + msg('exits', 'Exits: ', 10) + "\";");
    L.push("261 EX$=\"\"");
    L.push("262 IF RL(CR,1)>0 THEN EX$=EX$+\"" + getDirAbbr('north') + " \"");
    L.push("263 IF RL(CR,2)>0 THEN EX$=EX$+\"" + getDirAbbr('east') + " \"");
    L.push("264 IF RL(CR,3)>0 THEN EX$=EX$+\"" + getDirAbbr('south') + " \"");
    L.push("265 IF RL(CR,4)>0 THEN EX$=EX$+\"" + getDirAbbr('west') + " \"");
    L.push("266 IF RL(CR,5)>0 THEN EX$=EX$+\"" + getDirAbbr('up') + " \"");
    L.push("267 IF RL(CR,6)>0 THEN EX$=EX$+\"" + getDirAbbr('down') + " \"");
    L.push("268 IF EX$=\"\" THEN EX$=\"" + msg('none', 'none', 6) + "\"");
    L.push("270 PRINT EX$:PRINT \"" + msg('score', 'Score: ', 8) + "\";SC");
    L.push("280 RETURN");

    // ========== INPUT (300-350) ==========
    L.push("300 PRINT:PRINT \"> \";:LINE INPUT C$:C$=UPPER$(C$):RETURN");

    // ========== PARSE (400-499) - DYNAMIC GENERATION ==========
    var pln = 400;
   
    // Movement directions
    var directions = [
        { action: 'north', mv: 1 },
        { action: 'south', mv: 3 },
        { action: 'east', mv: 2 },
        { action: 'west', mv: 4 },
        { action: 'up', mv: 5 },
        { action: 'down', mv: 6 }
    ];
   
    directions.forEach(function(d) {
        var variants = getCmdVariants(d.action);
        var checks = variants.map(function(v) { return "C$=\"" + v + "\""; }).join(" OR ");
        L.push(pln + " IF " + checks + " THEN MV=" + d.mv + ":GOSUB 500:RETURN");
        pln += 2;
    });

    // USE - all variants
    getCmdVariants('use').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" THEN TK$=MID$(C$," + (v.length + 2) + "):GOSUB 550:RETURN");
        pln += 2;
    });

    // TAKE FROM - all TAKE variants with FROM keyword
    getCmdVariants('take').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" AND INSTR(C$,\" " + KW_FROM + " \")>0 THEN GOSUB 1600:RETURN");
        pln += 2;
    });

    // TAKE - all variants
    getCmdVariants('take').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" THEN TK$=MID$(C$," + (v.length + 2) + "):GOSUB 600:RETURN");
        pln += 2;
    });

    // DROP - all variants
    getCmdVariants('drop').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" THEN TK$=MID$(C$," + (v.length + 2) + "):GOSUB 650:RETURN");
        pln += 2;
    });

    // EXAMINE - all variants
    getCmdVariants('examine').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" THEN TK$=MID$(C$," + (v.length + 2) + "):GOSUB 700:RETURN");
        pln += 2;
    });

    // LOOK AT - special handling
    var lookVariants = getCmdVariants('look');
    lookVariants.forEach(function(v) {
        var lookAt = v + " " + KW_AT;
        L.push(pln + " IF LEFT$(C$," + lookAt.length + ")=\"" + lookAt + "\" THEN TK$=MID$(C$," + (lookAt.length + 2) + "):GOSUB 700:RETURN");
        pln += 2;
    });

    // TALK TO - all variants
    getCmdVariants('talk').forEach(function(v) {
        var talkTo = v + " " + KW_TO;
        L.push(pln + " IF LEFT$(C$," + talkTo.length + ")=\"" + talkTo + "\" THEN TK$=MID$(C$," + (talkTo.length + 2) + "):GOSUB 750:RETURN");
        pln += 2;
    });

    // GIVE - all variants
    getCmdVariants('give').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" THEN GOSUB 800:RETURN");
        pln += 2;
    });

    // OPEN - all variants
    getCmdVariants('open').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" THEN TK$=MID$(C$," + (v.length + 2) + "):GOSUB 850:RETURN");
        pln += 2;
    });

    // CLOSE - all variants
    getCmdVariants('close').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" THEN TK$=MID$(C$," + (v.length + 2) + "):GOSUB 870:RETURN");
        pln += 2;
    });

    // LOOK IN - special handling
    lookVariants.forEach(function(v) {
        var lookIn = v + " " + KW_IN;
        L.push(pln + " IF LEFT$(C$," + lookIn.length + ")=\"" + lookIn + "\" THEN TK$=MID$(C$," + (lookIn.length + 2) + "):GOSUB 890:RETURN");
        pln += 2;
    });

    // PUT - all variants
    getCmdVariants('put').forEach(function(v) {
        L.push(pln + " IF LEFT$(C$," + v.length + ")=\"" + v + "\" THEN GOSUB 920:RETURN");
        pln += 2;
    });

    // INVENTORY - exact matches
    var invVariants = getCmdVariants('inventory');
    var invChecks = invVariants.map(function(v) { return "C$=\"" + v + "\""; }).join(" OR ");
    L.push(pln + " IF " + invChecks + " THEN GOSUB 980:RETURN");
    pln += 2;

    // LOOK - exact matches
    var lookChecks = lookVariants.map(function(v) { return "C$=\"" + v + "\""; }).join(" OR ");
    L.push(pln + " IF " + lookChecks + " THEN RF=1:RETURN");
    pln += 2;

    // HELP
    var helpVariants = getCmdVariants('help');
    var helpChecks = helpVariants.map(function(v) { return "C$=\"" + v + "\""; }).join(" OR ");
    L.push(pln + " IF " + helpChecks + " THEN GOSUB 1050:RETURN");
    pln += 2;

    // SAVE
    var saveVariants = getCmdVariants('save');
    var saveChecks = saveVariants.map(function(v) { return "C$=\"" + v + "\""; }).join(" OR ");
    L.push(pln + " IF " + saveChecks + " THEN GOSUB 1100:RETURN");
    pln += 2;

    // LOAD
    var loadVariants = getCmdVariants('load');
    var loadChecks = loadVariants.map(function(v) { return "C$=\"" + v + "\""; }).join(" OR ");
    L.push(pln + " IF " + loadChecks + " THEN GOSUB 1150:RETURN");
    pln += 2;

    // SCORE
    var scoreVariants = getCmdVariants('score');
    var scoreChecks = scoreVariants.map(function(v) { return "C$=\"" + v + "\""; }).join(" OR ");
    L.push(pln + " IF " + scoreChecks + " THEN PRINT:PRINT \"" + msg('score', 'Score: ', 8) + "\";SC:GOSUB 1090:RETURN");
    pln += 2;

    // QUIT
    var quitVariants = getCmdVariants('quit');
    var quitChecks = quitVariants.map(function(v) { return "C$=\"" + v + "\""; }).join(" OR ");
    L.push(pln + " IF " + quitChecks + " THEN PRINT:PRINT \"" + msg('bye', 'Goodbye!', 24) + "\":END");
    pln += 2;

    // Unknown command
    L.push(pln + " PRINT \"" + msg('unknown', 'Huh?', 24) + "\":RETURN");

    // ========== MOVE (500-545) ==========
    L.push("500 NR=RL(CR,MV):IF NR=0 THEN PRINT \"" + msg('cantGo', "Can't go there.", 18) + "\":RETURN");
    L.push("510 RQ$=RD$(CR,MV):IF RQ$=\"OPEN\" OR UL(CR,MV)=1 THEN 530");
    L.push("520 PRINT \"" + msg('blocked', 'Blocked.', 12) + " " + msg('need', 'Need: ', 8) + "\";RQ$:RETURN");
    L.push("530 CR=NR:RF=1:GOSUB 1200:GOSUB 1300:RETURN");

    // ========== USE (550-599) ==========
    L.push("550 IF TK$=\"\" THEN PRINT \"" + msg('useWhat', 'Use what?', 15) + "\":RETURN");
    L.push("560 FD=0:FOR J=1 TO IC:IF INSTR(INV$(J),TK$)>0 THEN FD=J");
    L.push("565 NEXT J:IF FD=0 THEN PRINT \"" + msg('dontHave', "Don't have it.", 18) + "\":RETURN");
    L.push("570 FOR MV=1 TO 6:IF RD$(CR,MV)=INV$(FD) AND UL(CR,MV)=0 THEN UL(CR,MV)=1:PRINT \"" + msg('unlocked', 'Unlocked!', 15) + "\":RETURN");
    L.push("580 NEXT MV:PRINT \"" + msg('nothing', 'Nothing happens.', 15) + "\":RETURN");

    // ========== TAKE (600-645) ==========
    L.push("600 IF TK$=\"\" THEN PRINT \"" + msg('what', 'What?', 8) + "\":RETURN");
    L.push("605 FOR J=1 TO NM:IF IC(J)=1 THEN 630");
    L.push("610 IF IL(J)<>CR THEN 630");
    L.push("615 IF IP(J)<>0 THEN 630");
    L.push("620 IF INSTR(IT$(J),TK$)>0 THEN 640");
    L.push("630 NEXT J:PRINT \"" + msg('notHere', 'Not here.', 12) + "\":RETURN");
    L.push("640 IF IC>=ML THEN PRINT \"" + msg('full', 'Full!', 10) + "\":RETURN");
    L.push("642 IC=IC+1:INV$(IC)=IT$(J):IL(J)=0:SC=SC+IS(J):IS(J)=0");
    L.push("645 PRINT \"" + msg('taken', 'Taken.', 10) + "\":RETURN");

    // ========== DROP (650-699) ==========
    L.push("650 IF TK$=\"\" THEN PRINT \"" + msg('what', 'What?', 8) + "\":RETURN");
    L.push("660 FOR J=1 TO IC:IF INSTR(INV$(J),TK$)>0 THEN 680");
    L.push("670 NEXT J:PRINT \"" + msg('dontHave', "Don't have it.", 18) + "\":RETURN");
    L.push("680 FOR K=1 TO NM:IF IT$(K)=INV$(J) THEN IL(K)=CR:IP(K)=0");
    L.push("685 NEXT K");
    L.push("690 FOR K=J TO IC-1:INV$(K)=INV$(K+1):NEXT K:IC=IC-1");
    L.push("695 PRINT \"" + msg('dropped', 'Dropped.', 10) + "\":RETURN");

    // ========== EXAMINE (700-745) ==========
    L.push("700 IF TK$=\"\" THEN PRINT \"" + msg('what', 'What?', 8) + "\":RETURN");
    L.push("710 FOR J=1 TO NM:IF INSTR(IT$(J),TK$)>0 THEN IF IL(J)=CR OR IL(J)=0 THEN 730");
    L.push("720 NEXT J");
    L.push("722 FOR J=1 TO NN:IF INSTR(NP$(J),TK$)>0 AND NL(J)=CR THEN PRINT:PRINT ND$(J):GOSUB 1090:RETURN");
    L.push("725 NEXT J:PRINT \"" + msg('notHere', 'Not here.', 12) + "\":RETURN");
    L.push("730 IF ID$(J)=\"\" THEN PRINT \"" + msg('examine', 'You see nothing special.', 30) + "\":RETURN");
    L.push("735 PRINT:PRINT ID$(J):GOSUB 1090:RETURN");

    // ========== TALK (750-795) ==========
    L.push("750 IF TK$=\"\" THEN PRINT \"" + msg('talkTo', 'Talk to whom?', 15) + "\":RETURN");
    L.push("760 FOR J=1 TO NN:IF INSTR(NP$(J),TK$)>0 AND NL(J)=CR THEN 780");
    L.push("770 NEXT J:PRINT \"" + msg('noOne', 'No one here.', 15) + "\":RETURN");
    L.push("780 IF NT$(J)=\"\" THEN PRINT \"...\":RETURN");
    L.push("785 PRINT:PRINT CHR$(34);NT$(J);CHR$(34):GOSUB 1090:RETURN");

    // ========== GIVE (800-845) ==========
    var giveLen = CMD_GIVE.length;
    var toLen = KW_TO.length;
    L.push("800 G1$=\"\":G2$=\"\":P=INSTR(C$,\" " + KW_TO + " \")");
    L.push("805 IF P=0 THEN PRINT \"" + CMD_GIVE + " X " + KW_TO + " Y\":RETURN");
    L.push("810 G1$=MID$(C$," + (giveLen + 2) + ",P-" + (giveLen + 2) + "):G2$=MID$(C$,P+" + (toLen + 2) + ")");
    L.push("815 IF G1$=\"\" THEN PRINT \"" + msg('give', 'Give what?', 12) + "\":RETURN");
    L.push("820 IF G2$=\"\" THEN PRINT \"" + msg('toWhom', 'To whom?', 12) + "\":RETURN");
    L.push("825 FI=0:FOR J=1 TO IC:IF INSTR(INV$(J),G1$)>0 THEN FI=J");
    L.push("830 NEXT J:IF FI=0 THEN PRINT \"" + msg('dontHave', "Don't have it.", 18) + "\":RETURN");
    L.push("832 FKN=0:FOR J=1 TO NN:IF INSTR(NP$(J),G2$)>0 AND NL(J)=CR THEN FKN=J");
    L.push("835 NEXT J:IF FKN=0 THEN PRINT \"" + msg('noOne', 'No one here.', 15) + "\":RETURN");
    L.push("837 IF NG(FKN)=1 THEN PRINT \"" + msg('nothing', 'Nothing happens.', 15) + "\":RETURN");
    L.push("840 IF NW$(FKN)<>INV$(FI) THEN PRINT \"" + msg('noWant', "They don't want that.", 20) + "\":RETURN");
    L.push("842 FOR K=FI TO IC-1:INV$(K)=INV$(K+1):NEXT K:IC=IC-1");
    L.push("843 SC=SC+NS(FKN):NG(FKN)=1:PRINT \"" + msg('thanks', 'Thanks!', 10) + "\"");
    L.push("845 IF NR$(FKN)<>\"\" THEN IC=IC+1:INV$(IC)=NR$(FKN):PRINT \"Got: \";NR$(FKN)");
    L.push("847 RETURN");

    // ========== OPEN (850-869) ==========
    L.push("850 IF TK$=\"\" THEN PRINT \"" + msg('what', 'What?', 8) + "\":RETURN");
    L.push("855 FOR J=1 TO NM:IF INSTR(IT$(J),TK$)>0 AND IC(J)=1 THEN 857");
    L.push("856 NEXT J:PRINT \"" + msg('notContainer', "Can't open that.", 18) + "\":RETURN");
    L.push("857 IF IL(J)<>CR AND IL(J)<>0 THEN PRINT \"" + msg('notHere', 'Not here.', 12) + "\":RETURN");
    L.push("860 IO(J)=1:PRINT \"" + msg('open', 'Opened.', 12) + "\":RETURN");

    // ========== CLOSE (870-889) ==========
    L.push("870 IF TK$=\"\" THEN PRINT \"" + msg('what', 'What?', 8) + "\":RETURN");
    L.push("875 FOR J=1 TO NM:IF INSTR(IT$(J),TK$)>0 AND IC(J)=1 AND IO(J)=1 THEN 880");
    L.push("877 NEXT J:PRINT \"" + msg('notContainer', "Can't open that.", 18) + "\":RETURN");
    L.push("880 IO(J)=0:PRINT \"" + msg('close', 'Closed.', 12) + "\":RETURN");

    // ========== LOOK IN (890-919) ==========
    L.push("890 IF TK$=\"\" THEN PRINT \"" + msg('what', 'What?', 8) + "\":RETURN");
    L.push("895 FC=0:FOR J=1 TO NM:IF INSTR(IT$(J),TK$)>0 AND IC(J)=1 THEN FC=J");
    L.push("900 NEXT J:IF FC=0 THEN PRINT \"" + msg('notContainer', "Can't open that.", 18) + "\":RETURN");
    L.push("905 IF IO(FC)=0 THEN PRINT \"" + msg('blocked', 'Blocked.', 12) + "\":RETURN");
    L.push("910 PRINT:PRINT \"" + msg('lookIn', 'Inside: ', 10) + "\"");
    L.push("912 FO=0:FOR J=1 TO NM:IF IP(J)=FC THEN PRINT \"  \";IT$(J):FO=1");
    L.push("914 NEXT J:IF FO=0 THEN PRINT \"  " + msg('emptyC', 'Empty.', 8) + "\"");
    L.push("916 GOSUB 1090:RETURN");

    // ========== PUT IN (920-979) ==========
    var putLen = CMD_PUT.length;
    var inLen = KW_IN.length;
    L.push("920 P=INSTR(C$,\" " + KW_IN + " \"):IF P=0 THEN PRINT \"" + CMD_PUT + " X " + KW_IN + " Y\":RETURN");
    L.push("925 G1$=MID$(C$," + (putLen + 2) + ",P-" + (putLen + 2) + "):G2$=MID$(C$,P+" + (inLen + 2) + ")");
    L.push("930 IF G1$=\"\" OR G2$=\"\" THEN PRINT \"" + msg('what', 'What?', 8) + "\":RETURN");
    L.push("935 FI=0:FOR J=1 TO IC:IF INSTR(INV$(J),G1$)>0 THEN FI=J");
    L.push("940 NEXT J:IF FI=0 THEN PRINT \"" + msg('dontHave', "Don't have it.", 18) + "\":RETURN");
    L.push("945 FC=0:FOR J=1 TO NM:IF INSTR(IT$(J),G2$)>0 AND IC(J)=1 THEN FC=J");
    L.push("950 NEXT J:IF FC=0 THEN PRINT \"" + msg('notContainer', "Can't open that.", 18) + "\":RETURN");
    L.push("952 REM Check if container is accessible");
    L.push("953 IF IL(FC)<>CR AND IL(FC)<>0 THEN PRINT \"" + msg('notHere', 'Not here.', 12) + "\":RETURN");
    L.push("955 IF IO(FC)=0 THEN PRINT \"" + msg('blocked', 'Blocked.', 12) + "\":RETURN");
    L.push("960 FOR K=1 TO NM:IF IT$(K)=INV$(FI) THEN IP(K)=FC:IL(K)=IL(FC)");
    L.push("965 NEXT K");
    L.push("970 FOR K=FI TO IC-1:INV$(K)=INV$(K+1):NEXT K:IC=IC-1");
    L.push("975 PRINT \"" + msg('putIn', 'Done.', 10) + "\":RETURN");

    // ========== INVENTORY (980-1049) ==========
    L.push("980 PRINT:PRINT \"" + msg('inventory', 'Inventory:', 12) + "\"");
    L.push("985 IF IC=0 THEN PRINT \"  " + msg('empty', 'empty', 8) + "\":GOTO 990");
    L.push("987 FOR J=1 TO IC:PRINT \"  \";INV$(J):NEXT J");
    L.push("990 GOSUB 1090:RETURN");

    // ========== HELP (1050-1089) ==========
    var moveCmds = directions.map(function(d) { return getPrimaryCmd(d.action); }).join(",");
    L.push("1050 PRINT:PRINT \"COMMANDS:\"");
    L.push("1052 PRINT \"" + moveCmds.substring(0, 38) + "\"");
    L.push("1054 PRINT \"" + CMD_TAKE + " x, " + CMD_DROP + " x\"");
    L.push("1056 PRINT \"" + CMD_EXAMINE + " x, " + CMD_USE + " x\"");
    L.push("1058 PRINT \"" + CMD_TALK + " " + KW_TO + " x\"");
    L.push("1060 PRINT \"" + CMD_GIVE + " x " + KW_TO + " y\"");
    L.push("1062 PRINT \"" + CMD_OPEN + " x, " + CMD_CLOSE + " x\"");
    L.push("1064 PRINT \"" + CMD_LOOK + " " + KW_IN + " x\"");
    L.push("1066 PRINT \"" + CMD_PUT + " x " + KW_IN + " y\"");
    L.push("1068 PRINT \"" + CMD_TAKE + " x " + KW_FROM + " y\"");
    L.push("1070 PRINT \"" + invVariants[0] + ", " + lookVariants[0] + ", " + scoreVariants[0] + "\"");
    L.push("1072 PRINT \"" + saveVariants[0] + ", " + loadVariants[0] + ", " + quitVariants[0] + "\"");
    L.push("1080 GOSUB 1090:RETURN");

    // ========== WAIT KEY (1090-1099) ==========
    L.push("1090 PRINT:PRINT \"" + msg('pressKey', '[key]', 10) + "\";:CALL &BB18:PRINT:RETURN");

    // ========== SAVE (1100-1149) ==========
    L.push("1100 ON ERROR GOTO 1140");
    L.push("1105 OPENOUT \"SAVEGAME\"");
    L.push("1110 PRINT #9,CR;SC;IC");
    L.push("1115 FOR J=1 TO NM:PRINT #9,IL(J);IP(J);IO(J):NEXT J");
    L.push("1120 FOR J=1 TO NN:PRINT #9,NG(J):NEXT J");
    L.push("1125 FOR J=1 TO 6:FOR K=1 TO " + maxR + ":PRINT #9,UL(K,J):NEXT K:NEXT J");
    L.push("1130 FOR J=1 TO IC:PRINT #9,INV$(J):NEXT J");
    L.push("1135 CLOSEOUT:PRINT \"" + msg('saved', 'Game saved.', 15) + "\":RETURN");
    L.push("1140 CLOSEOUT:PRINT \"" + msg('cantSave', "Can't save.", 15) + "\":RETURN");

    // ========== LOAD (1150-1199) ==========
    L.push("1150 ON ERROR GOTO 1190");
    L.push("1155 OPENIN \"SAVEGAME\"");
    L.push("1160 INPUT #9,CR,SC,IC");
    L.push("1165 FOR J=1 TO NM:INPUT #9,IL(J),IP(J),IO(J):NEXT J");
    L.push("1170 FOR J=1 TO NN:INPUT #9,NG(J):NEXT J");
    L.push("1175 FOR J=1 TO 6:FOR K=1 TO " + maxR + ":INPUT #9,UL(K,J):NEXT K:NEXT J");
    L.push("1180 FOR J=1 TO IC:LINE INPUT #9,INV$(J):NEXT J");
    L.push("1185 CLOSEIN:RF=1:PRINT \"" + msg('loaded', 'Game loaded.', 15) + "\":RETURN");
    L.push("1190 CLOSEIN:PRINT \"" + msg('cantLoad', "Can't load.", 15) + "\":RETURN");

    // ========== CHECK DEATH (1200-1299) ==========
    L.push("1200 REM CHECK DEATH");
    if (deadly.length > 0) {
        L.push("1205 FOR X=1 TO " + deadly.length);
        L.push("1210 IF TD(X)<>CR THEN 1250");
        L.push("1220 IF TS$(X)=\"\" THEN 1240");
        L.push("1230 FOR Y=1 TO IC:IF INV$(Y)=TS$(X) THEN 1250");
        L.push("1235 NEXT Y");
        L.push("1240 CLS:PRINT:PRINT TM$(X):PRINT:PRINT \"" + msg('gameOver', 'GAME OVER', 12) + "\":END");
        L.push("1250 NEXT X");
    }
    L.push("1260 RETURN");

    // ========== CHECK WIN (1300-1399) ==========
    L.push("1300 REM CHECK WIN");
    if (S.game.winRoom > 0) {
        L.push("1305 IF CR<>" + S.game.winRoom + " THEN RETURN");
        if (winItems.length > 0) {
            L.push("1310 WC=0");
            L.push("1315 FOR Y=1 TO IC");
            winItems.forEach(function(item, idx) {
                L.push((1316 + idx) + " IF INV$(Y)=\"" + item + "\" THEN WC=WC+1");
            });
            L.push("1330 NEXT Y");
            L.push("1335 IF WC<" + winItems.length + " THEN RETURN");
        }
        if (S.game.winScore > 0) {
            L.push("1340 IF SC<" + S.game.winScore + " THEN RETURN");
        }
        L.push("1350 CLS:PRINT:PRINT \"" + wrapText(safeStr(S.game.winMsg, 200).replace(/"/g, "'"), true) + "\"");
        L.push("1360 PRINT:PRINT \"" + msg('youWin', 'YOU WIN!', 15) + "\"");
        L.push("1370 PRINT:PRINT \"" + msg('score', 'Score: ', 8) + "\";SC:END");
    } else {
        L.push("1350 RETURN");
    }

    // ========== TITLE (1500-1599) ==========
    L.push("1500 CLS:PRINT:PRINT");
    L.push("1510 PRINT TAB(2);\"" + safeStr(S.game.title, 34) + "\"");
    L.push("1520 PRINT:PRINT TAB(2);\"" + msg('by', 'by ', 4) + safeStr(S.game.author, 28) + "\"");
    L.push("1530 PRINT:PRINT:PRINT TAB(2);\"" + msg('pressKey', '[key]', 15) + "\"");
    L.push("1540 CALL &BB18:CLS:RETURN");

    // ========== TAKE FROM (1600-1649) ==========
    var takeLen = CMD_TAKE.length;
    var fromLen = KW_FROM.length;
    L.push("1600 P=INSTR(C$,\" " + KW_FROM + " \"):IF P=0 THEN RETURN");
    L.push("1605 G1$=MID$(C$," + (takeLen + 2) + ",P-" + (takeLen + 2) + "):G2$=MID$(C$,P+" + (fromLen + 2) + ")");
    L.push("1610 IF G1$=\"\" OR G2$=\"\" THEN PRINT \"" + msg('what', 'What?', 8) + "\":RETURN");
    L.push("1615 FC=0:FOR J=1 TO NM:IF INSTR(IT$(J),G2$)>0 AND IC(J)=1 THEN FC=J");
    L.push("1620 NEXT J:IF FC=0 THEN PRINT \"" + msg('notContainer', "Can't open that.", 18) + "\":RETURN");
    L.push("1625 IF IL(FC)<>CR AND IL(FC)<>0 THEN PRINT \"" + msg('notHere', 'Not here.', 12) + "\":RETURN");
    L.push("1630 IF IO(FC)=0 THEN PRINT \"" + msg('blocked', 'Blocked.', 12) + "\":RETURN");
    L.push("1635 FI=0:FOR J=1 TO NM:IF INSTR(IT$(J),G1$)>0 AND IP(J)=FC THEN FI=J");
    L.push("1640 NEXT J:IF FI=0 THEN PRINT \"" + msg('notHere', 'Not here.', 12) + "\":RETURN");
    L.push("1642 IF IC>=ML THEN PRINT \"" + msg('full', 'Full!', 10) + "\":RETURN");
    L.push("1645 IC=IC+1:INV$(IC)=IT$(FI):IP(FI)=0:IL(FI)=0");
    L.push("1647 PRINT \"" + msg('takeFrom', 'Taken.', 40) + "\":RETURN");

    // ========== DATA (3000+) ==========
    var ln = 3000;
    L.push(ln + " REM *** DATA ***");
    ln += 10;

    // ROOM NAMES & DESCRIPTIONS
    rooms.forEach(function(entry) {
        var r = entry[1];
        L.push(ln + " R$(" + r.num + ")=\"" + wrapText(safeStr(r.name).replace(/"/g, "'"), false) + "\"");
        ln += 10;
        L.push(ln + " D$(" + r.num + ")=\"" + wrapText(safeStr(r.desc).replace(/"/g, "'"), false) + "\"");
        ln += 10;
    });

    // ROOM CONNECTIONS
    rooms.forEach(function(entry) {
        var id = entry[0];
        var r = entry[1];
        var c = S.conns[id];
        var g = function(d) {
            return c[d].t ? (S.rooms[c[d].t] ? S.rooms[c[d].t].num : 0) : 0;
        };
        L.push(ln + " RL(" + r.num + ",1)=" + g('north') + ":RL(" + r.num + ",2)=" + g('east') + ":RL(" + r.num + ",3)=" + g('south') + ":RL(" + r.num + ",4)=" + g('west') + ":RL(" + r.num + ",5)=" + g('up') + ":RL(" + r.num + ",6)=" + g('down'));
        ln += 10;
    });

    // ROOM REQUIREMENTS
    rooms.forEach(function(entry) {
        var id = entry[0];
        var r = entry[1];
        var c = S.conns[id];
        L.push(ln + " RD$(" + r.num + ",1)=\"" + (c.north.r || 'OPEN') + "\":RD$(" + r.num + ",2)=\"" + (c.east.r || 'OPEN') + "\":RD$(" + r.num + ",3)=\"" + (c.south.r || 'OPEN') + "\":RD$(" + r.num + ",4)=\"" + (c.west.r || 'OPEN') + "\":RD$(" + r.num + ",5)=\"" + (c.up.r || 'OPEN') + "\":RD$(" + r.num + ",6)=\"" + (c.down.r || 'OPEN') + "\"");
        ln += 10;
    });

    // DEATH ROOMS
    deadly.forEach(function(entry, idx) {
        var r = entry[1];
        var deathMsg = wrapText(safeStr(r.deathMsg || 'Dead!').replace(/"/g, "'"), false);
        L.push(ln + " TD(" + (idx + 1) + ")=" + r.num + ":TM$(" + (idx + 1) + ")=\"" + deathMsg + "\":TS$(" + (idx + 1) + ")=\"" + safeStr(r.survItem) + "\"");
        ln += 10;
    });

    // ITEMS
    items.forEach(function(entry, idx) {
        var iid = entry[0];
        var i = entry[1];
        var loc = S.rooms[i.loc] ? S.rooms[i.loc].num : 0;
        var j = idx + 1;
        var parentIdx = 0;
        if (i.parent) {
            var pIdx = items.findIndex(function(e) { return e[0] === i.parent; });
            if (pIdx >= 0) parentIdx = pIdx + 1;
        }
        var itemDesc = wrapText(safeStr(i.desc).replace(/"/g, "'"), false);
        L.push(ln + " IT$(" + j + ")=\"" + safeStr(i.name, 25) + "\":ID$(" + j + ")=\"" + itemDesc + "\"");
        ln += 10;
        L.push(ln + " IL(" + j + ")=" + loc + ":IC(" + j + ")=" + (i.container ? 1 : 0) + ":IS(" + j + ")=" + (i.points || 0) + ":IP(" + j + ")=" + parentIdx);
        ln += 10;
    });

    // NPCs
    npcs.forEach(function(entry, idx) {
        var n = entry[1];
        var loc = S.rooms[n.loc] ? S.rooms[n.loc].num : 0;
        var j = idx + 1;
        var npcDesc = wrapText(safeStr(n.desc).replace(/"/g, "'"), false);
        var npcTalk = wrapText(safeStr(n.talk).replace(/"/g, "'"), false);
        L.push(ln + " NP$(" + j + ")=\"" + safeStr(n.name, 25) + "\":ND$(" + j + ")=\"" + npcDesc + "\"");
        ln += 10;
        L.push(ln + " NT$(" + j + ")=\"" + npcTalk + "\"");
        ln += 10;
        L.push(ln + " NW$(" + j + ")=\"" + safeStr(n.wants) + "\":NR$(" + j + ")=\"" + safeStr(n.gives) + "\":NS(" + j + ")=" + (n.points || 0) + ":NL(" + j + ")=" + loc);
        ln += 10;
    });

    L.push(ln + " RETURN");
   
    return L.join("\n");
}

// ========== EXPORT ==========
if (typeof window !== 'undefined') {
    window.genCode = genCode;
    window.wrapText = wrapText;
}
https://cpcwiki.de
Dein Deutsches CPCWiki!

TFM

TFM of FutureSoft
http://www.futureos.de --> Das Betriebssystem FutureOS (Update: 24.12.2025)
http://futureos.cpc-live.com/files/LambdaSpeak_RSX_by_TFM.zip --> RSX ROM für LambdaSpeak (Update: 29.01.2025)

Devilmarkus

Hier mal 2 Adventures, die ich damit erzeugt habe, viel Spaß damit ;)
https://cpcwiki.de
Dein Deutsches CPCWiki!

Devilmarkus

Erzeugt jetzt auch Assemblercode:

; ========================================
; THE FORGOTTEN MAYA PRINCESS
; By Devil-System
; Z80 Assembly for Amstrad CPC
; ========================================

; === AMSTRAD CPC FIRMWARE CALLS ===
TXT_OUTPUT      EQU &BB5A
KM_WAIT_CHAR    EQU &BB06
SCR_SET_MODE    EQU &BC0E
TXT_CLEAR       EQU &BB6C
TXT_SET_PEN     EQU &BB90
TXT_SET_PAPER   EQU &BB96
SCR_SET_INK     EQU &BC32
SCR_SET_BORDER  EQU &BC38
TXT_CUR_ON      EQU &BB81
TXT_CUR_OFF     EQU &BB84
CAS_IN_OPEN     EQU &BC77
CAS_IN_DIRECT   EQU &BC83
CAS_IN_CLOSE    EQU &BC7A
CAS_OUT_OPEN    EQU &BC8C
CAS_OUT_DIRECT  EQU &BC98
CAS_OUT_CLOSE   EQU &BC8F

; === GAME CONSTANTS ===
NUM_ROOMS       EQU 25
NUM_ITEMS       EQU 32
NUM_NPCS        EQU 8
NUM_DEADLY      EQU 5
MAX_INV         EQU 20
INV_LIMIT       EQU 8
START_ROOM      EQU 1
WIN_ROOM        EQU 25
WIN_SCORE       EQU 0
WIN_ITEMS_COUNT EQU 1
INPUT_MAX       EQU 78

    ORG &4000

; ========================================
; PROGRAM START
; ========================================
start:
    LD (original_sp),SP  ; Save stack for clean exit
    ; Set Mode 1 (40 columns)
    LD A,1
    CALL SCR_SET_MODE

    ; Set colors: Ink 0 = black, Ink 1 = yellow
    LD A,0
    LD B,0
    LD C,0
    CALL SCR_SET_INK
    LD A,1
    LD B,24
    LD C,24
    CALL SCR_SET_INK
    LD B,0
    LD C,0
    CALL SCR_SET_BORDER

    ; Initialize game state
    LD A,START_ROOM
    LD (current_room),A
    XOR A
    LD (score),A
    LD (score+1),A
    LD (inv_count),A
    LD A,1
    LD (refresh_flag),A

    ; Initialize items and NPCs
    CALL init_data

    ; Show title screen
    CALL show_title

; === MAIN GAME LOOP ===
main_loop:
    LD A,(refresh_flag)
    OR A
    JR Z,main_no_refresh
    CALL show_room
    XOR A
    LD (refresh_flag),A
main_no_refresh:
    CALL get_input
    CALL parse_command
    JR main_loop

; ========== SHOW TITLE SCREEN ==========
show_title:
    CALL TXT_CLEAR
    CALL print_nl
    CALL print_nl
    LD HL,str_title
    CALL print_str
    CALL print_nl
    CALL print_nl
    LD HL,str_by
    CALL print_str
    LD HL,str_author
    CALL print_str
    CALL print_nl
    CALL print_nl
    CALL print_nl
    CALL wait_key
    CALL TXT_CLEAR
    RET

; ========================================
; SHOW CURRENT ROOM
; ========================================
show_room:
    CALL TXT_CLEAR
    CALL print_nl

    ; Get and print room name
    LD A,(current_room)
    CALL get_room_name_ptr
    CALL print_str
    CALL print_nl

    ; Print separator
    LD B,40
show_room_sep:
    LD A,'*'
    CALL TXT_OUTPUT
    DJNZ show_room_sep
    CALL print_nl

    ; Get and print room description
    LD A,(current_room)
    CALL get_room_desc_ptr
    CALL print_str
    CALL print_nl
    CALL print_nl

    ; Show items in room
    CALL show_items_here

    ; Show NPCs in room
    CALL show_npcs_here

    ; Show exits
    CALL show_exits

    ; Show score
    LD HL,msg_score
    CALL print_str
    LD HL,(score)
    CALL print_num
    CALL print_nl
    RET

; === SHOW ITEMS IN CURRENT ROOM ===
show_items_here:
    LD A,NUM_ITEMS
    OR A
    RET Z
    LD B,A
    LD C,1                ; item index
sih_loop:
    PUSH BC
    ; Get item location
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A               ; D = item location
    ; Check if in current room
    LD A,(current_room)
    CP D
    JR NZ,sih_next
    ; Check if item is in container
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR NZ,sih_next       ; in container, skip
    ; Print 'You see: '
    LD HL,msg_yousee
    CALL print_str
    ; Print item name
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    CALL print_str
    CALL print_nl
sih_next:
    POP BC
    INC C
    DJNZ sih_loop
    RET

; === SHOW NPCS IN CURRENT ROOM ===
show_npcs_here:
    LD A,NUM_NPCS
    OR A
    RET Z
    LD B,A
    LD C,1
snh_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,npc_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JR NZ,snh_next
    LD HL,msg_yousee
    CALL print_str
    POP BC
    PUSH BC
    LD A,C
    CALL get_npc_name_ptr
    CALL print_str
    CALL print_nl
snh_next:
    POP BC
    INC C
    DJNZ snh_loop
    RET

; === SHOW AVAILABLE EXITS ===
show_exits:
    LD HL,msg_exits
    CALL print_str
    LD A,(current_room)
    LD (temp_room),A     ; save room
    LD E,0               ; exits found flag

    ; Check North (direction 1)
    LD A,(temp_room)
    LD B,1
    CALL get_exit
    OR A
    JP Z,se_east
    LD HL,str_dir_n
    CALL print_str
    LD E,1
se_east:
    LD A,(temp_room)
    LD B,2
    CALL get_exit
    OR A
    JP Z,se_south
    LD HL,str_dir_e
    CALL print_str
    LD E,1
se_south:
    LD A,(temp_room)
    LD B,3
    CALL get_exit
    OR A
    JP Z,se_west
    LD HL,str_dir_s
    CALL print_str
    LD E,1
se_west:
    LD A,(temp_room)
    LD B,4
    CALL get_exit
    OR A
    JP Z,se_up
    LD HL,str_dir_w
    CALL print_str
    LD E,1
se_up:
    LD A,(temp_room)
    LD B,5
    CALL get_exit
    OR A
    JP Z,se_down
    LD HL,str_dir_u
    CALL print_str
    LD E,1
se_down:
    LD A,(temp_room)
    LD B,6
    CALL get_exit
    OR A
    JP Z,se_done
    LD HL,str_dir_d
    CALL print_str
    LD E,1
se_done:
    LD A,E
    OR A
    JP NZ,se_has_exits
    LD HL,msg_none
    CALL print_str
se_has_exits:
    CALL print_nl
    RET

; ========================================
; GET PLAYER INPUT
; ========================================
get_input:
    CALL print_nl
    LD A,'>'
    CALL TXT_OUTPUT
    LD A,' '
    CALL TXT_OUTPUT
    CALL TXT_CUR_ON

    LD HL,input_buffer
    LD B,0               ; char count
gi_loop:
    CALL KM_WAIT_CHAR
    CP 13                ; Enter
    JR Z,gi_done
    CP 127               ; Delete
    JR Z,gi_backspace
    CP 8                 ; Backspace
    JR Z,gi_backspace
    CP 32
    JR C,gi_loop         ; ignore control chars

    ; Check buffer space
    LD C,A
    LD A,B
    CP INPUT_MAX
    JR NC,gi_loop
    LD A,C

    ; Convert to uppercase
    CP 'a'
    JR C,gi_store
    CP 'z'+1
    JR NC,gi_store
    SUB 32
gi_store:
    LD (HL),A
    INC HL
    INC B
    CALL TXT_OUTPUT
    JR gi_loop

gi_backspace:
    LD A,B
    OR A
    JR Z,gi_loop
    DEC HL
    DEC B
    LD A,8
    CALL TXT_OUTPUT
    LD A,' '
    CALL TXT_OUTPUT
    LD A,8
    CALL TXT_OUTPUT
    JR gi_loop

gi_done:
    LD (HL),0            ; null terminate
    LD A,B
    LD (input_len),A
    CALL TXT_CUR_OFF
    CALL print_nl
    RET

; ========================================
; PARSE AND EXECUTE COMMAND
; ========================================
parse_command:
    LD A,(input_len)
    OR A
    RET Z                ; empty input

    ; === MOVEMENT COMMANDS ===
    LD HL,input_buffer
    LD DE,cmd_north
    CALL str_equal
    JR NZ,pc_north_no
    LD A,1
    JP do_move
pc_north_no:
    LD HL,input_buffer
    LD DE,cmd_north1
    CALL str_equal
    JR NZ,pc_north1_no
    LD A,1
    JP do_move
pc_north1_no:
    LD HL,input_buffer
    LD DE,cmd_north2
    CALL str_equal
    JR NZ,pc_north2_no
    LD A,1
    JP do_move
pc_north2_no:
    LD HL,input_buffer
    LD DE,cmd_south
    CALL str_equal
    JR NZ,pc_south_no
    LD A,3
    JP do_move
pc_south_no:
    LD HL,input_buffer
    LD DE,cmd_south1
    CALL str_equal
    JR NZ,pc_south1_no
    LD A,3
    JP do_move
pc_south1_no:
    LD HL,input_buffer
    LD DE,cmd_south2
    CALL str_equal
    JR NZ,pc_south2_no
    LD A,3
    JP do_move
pc_south2_no:
    LD HL,input_buffer
    LD DE,cmd_east
    CALL str_equal
    JR NZ,pc_east_no
    LD A,2
    JP do_move
pc_east_no:
    LD HL,input_buffer
    LD DE,cmd_east1
    CALL str_equal
    JR NZ,pc_east1_no
    LD A,2
    JP do_move
pc_east1_no:
    LD HL,input_buffer
    LD DE,cmd_east2
    CALL str_equal
    JR NZ,pc_east2_no
    LD A,2
    JP do_move
pc_east2_no:
    LD HL,input_buffer
    LD DE,cmd_west
    CALL str_equal
    JR NZ,pc_west_no
    LD A,4
    JP do_move
pc_west_no:
    LD HL,input_buffer
    LD DE,cmd_west1
    CALL str_equal
    JR NZ,pc_west1_no
    LD A,4
    JP do_move
pc_west1_no:
    LD HL,input_buffer
    LD DE,cmd_west2
    CALL str_equal
    JR NZ,pc_west2_no
    LD A,4
    JP do_move
pc_west2_no:
    LD HL,input_buffer
    LD DE,cmd_up
    CALL str_equal
    JR NZ,pc_up_no
    LD A,5
    JP do_move
pc_up_no:
    LD HL,input_buffer
    LD DE,cmd_up1
    CALL str_equal
    JR NZ,pc_up1_no
    LD A,5
    JP do_move
pc_up1_no:
    LD HL,input_buffer
    LD DE,cmd_up2
    CALL str_equal
    JR NZ,pc_up2_no
    LD A,5
    JP do_move
pc_up2_no:
    LD HL,input_buffer
    LD DE,cmd_up3
    CALL str_equal
    JR NZ,pc_up3_no
    LD A,5
    JP do_move
pc_up3_no:
    LD HL,input_buffer
    LD DE,cmd_down
    CALL str_equal
    JR NZ,pc_down_no
    LD A,6
    JP do_move
pc_down_no:
    LD HL,input_buffer
    LD DE,cmd_down1
    CALL str_equal
    JR NZ,pc_down1_no
    LD A,6
    JP do_move
pc_down1_no:
    LD HL,input_buffer
    LD DE,cmd_down2
    CALL str_equal
    JR NZ,pc_down2_no
    LD A,6
    JP do_move
pc_down2_no:
    LD HL,input_buffer
    LD DE,cmd_down3
    CALL str_equal
    JR NZ,pc_down3_no
    LD A,6
    JP do_move
pc_down3_no:
    LD HL,input_buffer
    LD DE,cmd_down4
    CALL str_equal
    JR NZ,pc_down4_no
    LD A,6
    JP do_move
pc_down4_no:

    ; === LOOK IN COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_lookin
    LD B,4
    CALL str_prefix
    JP NZ,pc_lookin_no
    LD A,5
    JP do_look_in
pc_lookin_no:
    LD HL,input_buffer
    LD DE,cmd_lookin1
    LD B,7
    CALL str_prefix
    JP NZ,pc_lookin1_no
    LD A,8
    JP do_look_in
pc_lookin1_no:
    LD HL,input_buffer
    LD DE,cmd_lookin2
    LD B,15
    CALL str_prefix
    JP NZ,pc_lookin2_no
    LD A,16
    JP do_look_in
pc_lookin2_no:

    ; === LOOK COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_look
    CALL str_equal
    JR NZ,pc_look_no
    LD A,1
    LD (refresh_flag),A
    RET
pc_look_no:
    LD HL,input_buffer
    LD DE,cmd_look1
    CALL str_equal
    JR NZ,pc_look1_no
    LD A,1
    LD (refresh_flag),A
    RET
pc_look1_no:
    LD HL,input_buffer
    LD DE,cmd_look2
    CALL str_equal
    JR NZ,pc_look2_no
    LD A,1
    LD (refresh_flag),A
    RET
pc_look2_no:

    ; === INVENTORY COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_inv
    CALL str_equal
    JR NZ,pc_inv_no
    JP do_inventory
pc_inv_no:
    LD HL,input_buffer
    LD DE,cmd_inv1
    CALL str_equal
    JR NZ,pc_inv1_no
    JP do_inventory
pc_inv1_no:
    LD HL,input_buffer
    LD DE,cmd_inv2
    CALL str_equal
    JR NZ,pc_inv2_no
    JP do_inventory
pc_inv2_no:

    ; === TAKE FROM COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_take
    LD B,4
    CALL str_prefix
    JP NZ,pc_takefrom_no
    ; Check if FROM keyword present
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,pc_takefrom_no
    JP do_take_from
pc_takefrom_no:
    LD HL,input_buffer
    LD DE,cmd_take1
    LD B,3
    CALL str_prefix
    JP NZ,pc_takefrom1_no
    ; Check if FROM keyword present
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,pc_takefrom1_no
    JP do_take_from
pc_takefrom1_no:
    LD HL,input_buffer
    LD DE,cmd_take2
    LD B,4
    CALL str_prefix
    JP NZ,pc_takefrom2_no
    ; Check if FROM keyword present
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,pc_takefrom2_no
    JP do_take_from
pc_takefrom2_no:
    LD HL,input_buffer
    LD DE,cmd_take3
    LD B,7
    CALL str_prefix
    JP NZ,pc_takefrom3_no
    ; Check if FROM keyword present
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,pc_takefrom3_no
    JP do_take_from
pc_takefrom3_no:

    ; === TAKE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_take
    LD B,4
    CALL str_prefix
    JR NZ,pc_take_no
    LD A,5
    JP do_take
pc_take_no:
    LD HL,input_buffer
    LD DE,cmd_take1
    LD B,3
    CALL str_prefix
    JR NZ,pc_take1_no
    LD A,4
    JP do_take
pc_take1_no:
    LD HL,input_buffer
    LD DE,cmd_take2
    LD B,4
    CALL str_prefix
    JR NZ,pc_take2_no
    LD A,5
    JP do_take
pc_take2_no:
    LD HL,input_buffer
    LD DE,cmd_take3
    LD B,7
    CALL str_prefix
    JR NZ,pc_take3_no
    LD A,8
    JP do_take
pc_take3_no:

    ; === DROP COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_drop
    LD B,4
    CALL str_prefix
    JR NZ,pc_drop_no
    LD A,5
    JP do_drop
pc_drop_no:
    LD HL,input_buffer
    LD DE,cmd_drop1
    LD B,8
    CALL str_prefix
    JR NZ,pc_drop1_no
    LD A,9
    JP do_drop
pc_drop1_no:
    LD HL,input_buffer
    LD DE,cmd_drop2
    LD B,5
    CALL str_prefix
    JR NZ,pc_drop2_no
    LD A,6
    JP do_drop
pc_drop2_no:

    ; === EXAMINE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_exam
    LD B,7
    CALL str_prefix
    JR NZ,pc_exam_no
    LD A,8
    JP do_examine
pc_exam_no:
    LD HL,input_buffer
    LD DE,cmd_exam1
    LD B,1
    CALL str_prefix
    JR NZ,pc_exam1_no
    LD A,2
    JP do_examine
pc_exam1_no:
    LD HL,input_buffer
    LD DE,cmd_exam2
    LD B,7
    CALL str_prefix
    JR NZ,pc_exam2_no
    LD A,8
    JP do_examine
pc_exam2_no:
    LD HL,input_buffer
    LD DE,cmd_exam3
    LD B,5
    CALL str_prefix
    JR NZ,pc_exam3_no
    LD A,6
    JP do_examine
pc_exam3_no:
    LD HL,input_buffer
    LD DE,cmd_exam4
    LD B,7
    CALL str_prefix
    JR NZ,pc_exam4_no
    LD A,8
    JP do_examine
pc_exam4_no:

    ; === USE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_use
    LD B,3
    CALL str_prefix
    JR NZ,pc_use_no
    LD A,4
    JP do_use
pc_use_no:
    LD HL,input_buffer
    LD DE,cmd_use1
    LD B,5
    CALL str_prefix
    JR NZ,pc_use1_no
    LD A,6
    JP do_use
pc_use1_no:

    ; === OPEN COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_open
    LD B,4
    CALL str_prefix
    JR NZ,pc_open_no
    LD A,5
    JP do_open
pc_open_no:

    ; === CLOSE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_close
    LD B,5
    CALL str_prefix
    JR NZ,pc_close_no
    LD A,6
    JP do_close
pc_close_no:
    LD HL,input_buffer
    LD DE,cmd_close1
    LD B,4
    CALL str_prefix
    JR NZ,pc_close1_no
    LD A,5
    JP do_close
pc_close1_no:

    ; === TALK COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_talk
    LD B,7
    CALL str_prefix
    JR NZ,pc_talk_no
    LD A,8
    JP do_talk
pc_talk_no:
    LD HL,input_buffer
    LD DE,cmd_talk1
    LD B,8
    CALL str_prefix
    JR NZ,pc_talk1_no
    LD A,9
    JP do_talk
pc_talk1_no:
    LD HL,input_buffer
    LD DE,cmd_talk2
    LD B,6
    CALL str_prefix
    JR NZ,pc_talk2_no
    LD A,7
    JP do_talk
pc_talk2_no:
    LD HL,input_buffer
    LD DE,cmd_talk3
    LD B,6
    CALL str_prefix
    JR NZ,pc_talk3_no
    LD A,7
    JP do_talk
pc_talk3_no:

    ; === GIVE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_give
    LD B,4
    CALL str_prefix
    JR NZ,pc_give_no
    JP do_give
pc_give_no:
    LD HL,input_buffer
    LD DE,cmd_give1
    LD B,5
    CALL str_prefix
    JR NZ,pc_give1_no
    JP do_give
pc_give1_no:
    LD HL,input_buffer
    LD DE,cmd_give2
    LD B,4
    CALL str_prefix
    JR NZ,pc_give2_no
    JP do_give
pc_give2_no:

    ; === HELP COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_help
    CALL str_equal
    JR NZ,pc_help_no
    JP do_help
pc_help_no:
    LD HL,input_buffer
    LD DE,cmd_help1
    CALL str_equal
    JR NZ,pc_help1_no
    JP do_help
pc_help1_no:
    LD HL,input_buffer
    LD DE,cmd_help2
    CALL str_equal
    JR NZ,pc_help2_no
    JP do_help
pc_help2_no:
    LD HL,input_buffer
    LD DE,cmd_help3
    CALL str_equal
    JR NZ,pc_help3_no
    JP do_help
pc_help3_no:

    ; === SCORE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_score
    CALL str_equal
    JR NZ,pc_score_no
    JP do_show_score
pc_score_no:
    LD HL,input_buffer
    LD DE,cmd_score1
    CALL str_equal
    JR NZ,pc_score1_no
    JP do_show_score
pc_score1_no:

    ; === SAVE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_save
    CALL str_equal
    JP NZ,pc_save_no
    JP do_save
pc_save_no:
    LD HL,input_buffer
    LD DE,cmd_save1
    CALL str_equal
    JP NZ,pc_save1_no
    JP do_save
pc_save1_no:

    ; === LOAD COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_load
    CALL str_equal
    JP NZ,pc_load_no
    JP do_load
pc_load_no:
    LD HL,input_buffer
    LD DE,cmd_load1
    CALL str_equal
    JP NZ,pc_load1_no
    JP do_load
pc_load1_no:
    LD HL,input_buffer
    LD DE,cmd_load2
    CALL str_equal
    JP NZ,pc_load2_no
    JP do_load
pc_load2_no:

    ; === QUIT COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_quit
    CALL str_equal
    JR NZ,pc_quit_no
    JP do_quit
pc_quit_no:
    LD HL,input_buffer
    LD DE,cmd_quit1
    CALL str_equal
    JR NZ,pc_quit1_no
    JP do_quit
pc_quit1_no:
    LD HL,input_buffer
    LD DE,cmd_quit2
    CALL str_equal
    JR NZ,pc_quit2_no
    JP do_quit
pc_quit2_no:
    LD HL,input_buffer
    LD DE,cmd_quit3
    CALL str_equal
    JR NZ,pc_quit3_no
    JP do_quit
pc_quit3_no:

    ; Unknown command
    LD HL,msg_unknown
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: MOVE
; A = direction (1-6) on entry
; ========================================
do_move:
    LD (temp_dir),A      ; save direction
    LD B,A               ; B = direction for get_exit
    LD A,(current_room)  ; A = room
    CALL get_exit        ; A = target room (0=none)
    OR A
    JP NZ,dm_can_go
    LD HL,msg_cantgo
    CALL print_str
    CALL print_nl
    RET
dm_can_go:
    LD (temp_target),A   ; save target room
    ; Check if exit has requirement
    LD A,(current_room)
    LD HL,temp_dir
    LD B,(HL)            ; B = direction
    CALL get_exit_requirement
    ; HL = requirement string pointer
    LD A,(HL)
    OR A                 ; empty = no requirement
    JP Z,dm_unlocked     ; no requirement, can pass
    ; Has requirement - check if unlocked
    LD A,(current_room)
    LD HL,temp_dir
    LD B,(HL)
    CALL check_exit_unlocked
    OR A
    JP NZ,dm_unlocked
    ; Blocked!
    LD HL,msg_blocked
    CALL print_str
    CALL print_nl
    RET
dm_unlocked:
    LD A,(temp_target)
    LD (current_room),A
    LD A,1
    LD (refresh_flag),A
    CALL check_death
    CALL check_win
    RET

; ========================================
; ACTION: TAKE
; A = offset in input buffer to object name
; ========================================
do_take:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dt_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dt_have_name:
    LD A,NUM_ITEMS
    OR A
    JP NZ,dt_search
    LD HL,msg_nothere
    CALL print_str
    CALL print_nl
    RET
dt_search:
    LD B,A
    LD C,1
dt_loop:
    PUSH BC
    ; Check item location
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,dt_next
    ; Check not in container
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP NZ,dt_next
    ; Check not a fixed container
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    CP 2
    JP Z,dt_next
    ; Check name matches
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dt_next
    ; Found it! Check inventory space
    LD A,(inv_count)
    CP INV_LIMIT
    JR C,dt_can_take
    POP BC
    LD HL,msg_full
    CALL print_str
    CALL print_nl
    RET
dt_can_take:
    POP BC
    LD A,C
    CALL add_to_inventory
    ; Set item location to 0
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD (HL),0
    ; Add points
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_points
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR Z,dt_no_points
    LD HL,(score)
    LD B,0
    LD C,A
    ADD HL,BC
    LD (score),HL
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_points
    CALL add_a_to_hl
    LD (HL),0
dt_no_points:
    POP BC
    LD HL,msg_taken
    CALL print_str
    CALL print_nl
    RET
dt_next:
    POP BC
    INC C
    DEC B
    JP NZ,dt_loop
    LD HL,msg_nothere
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: DROP
; ========================================
do_drop:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dd_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dd_have_name:
    LD A,(inv_count)
    OR A
    JP NZ,dd_search
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET
dd_search:
    LD B,A
    LD C,1
dd_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dd_next
    PUSH AF
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    POP AF
    JP NZ,dd_next
    ; Found - set location to current room
    LD D,A
    LD A,D
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(current_room)
    LD (HL),A
    POP BC
    LD A,C
    CALL remove_from_inventory
    LD HL,msg_dropped
    CALL print_str
    CALL print_nl
    RET
dd_next:
    POP BC
    INC C
    DEC B
    JP NZ,dd_loop
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: EXAMINE
; ========================================
do_examine:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dx_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dx_have_name:
    LD A,NUM_ITEMS
    OR A
    JP Z,dx_check_npcs
    LD B,A
    LD C,1
dx_item_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP Z,dx_item_accessible
    LD A,D
    OR A
    JP NZ,dx_item_next
dx_item_accessible:
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dx_item_next
    POP BC
    LD A,C
    CALL get_item_desc_ptr
    LD A,(HL)
    OR A
    JR NZ,dx_has_desc
    LD HL,msg_nothing_special
dx_has_desc:
    CALL print_nl
    CALL print_str
    CALL print_nl
    CALL wait_key
    RET
dx_item_next:
    POP BC
    INC C
    DEC B
    JP NZ,dx_item_loop
dx_check_npcs:
    LD A,NUM_NPCS
    OR A
    JP Z,dx_not_found
    LD B,A
    LD C,1
dx_npc_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,npc_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,dx_npc_next
    POP BC
    PUSH BC
    LD A,C
    CALL get_npc_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dx_npc_next
    POP BC
    LD A,C
    CALL get_npc_desc_ptr
    CALL print_nl
    CALL print_str
    CALL print_nl
    CALL wait_key
    RET
dx_npc_next:
    POP BC
    INC C
    DEC B
    JP NZ,dx_npc_loop
dx_not_found:
    LD HL,msg_nothere
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: USE (unlock exits)
; ========================================
do_use:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,du_have_name
    LD HL,msg_usewhat
    CALL print_str
    CALL print_nl
    RET
du_have_name:
    LD A,(inv_count)
    OR A
    JP NZ,du_search
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET
du_search:
    LD B,A
    LD C,1
du_inv_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,du_inv_next
    PUSH AF
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    POP AF
    JP NZ,du_inv_next
    ; Found item in inventory - try to unlock exits
    LD (found_item),A
    POP BC
    JP du_try_unlock
du_inv_next:
    POP BC
    INC C
    DEC B
    JP NZ,du_inv_loop
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET

du_try_unlock:
    ; Check all 6 directions
    LD B,6
    LD C,1
du_dir_loop:
    PUSH BC
    ; Get required item for this exit
    LD A,(current_room)
    LD B,C
    CALL get_exit_requirement
    LD A,(HL)
    OR A
    JP Z,du_dir_next
    ; Compare with our item name
    PUSH HL
    LD A,(found_item)
    CALL get_item_name_ptr
    POP DE
    CALL str_equal
    JP NZ,du_dir_next
    ; Check if already unlocked
    POP BC
    PUSH BC
    LD A,(current_room)
    LD B,C
    CALL check_exit_unlocked
    OR A
    JP NZ,du_dir_next
    ; Unlock it!
    POP BC
    LD A,(current_room)
    LD B,C
    CALL set_exit_unlocked
    LD HL,msg_unlocked
    CALL print_str
    CALL print_nl
    RET
du_dir_next:
    POP BC
    INC C
    DEC B
    JP NZ,du_dir_loop
    LD HL,msg_nothing
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: OPEN CONTAINER
; ========================================
do_open:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dop_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dop_have_name:
    LD A,NUM_ITEMS
    OR A
    JP Z,dop_not_container
    LD B,A
    LD C,1
dop_loop:
    PUSH BC
    ; Is it a container?
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dop_next
    ; Is it here or carried?
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP Z,dop_accessible
    LD A,D
    OR A
    JP NZ,dop_next
dop_accessible:
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dop_next
    ; Open it
    POP BC
    LD A,C
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD (HL),1
    LD HL,msg_opened
    CALL print_str
    CALL print_nl
    RET
dop_next:
    POP BC
    INC C
    DEC B
    JP NZ,dop_loop
dop_not_container:
    LD HL,msg_notcontainer
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: CLOSE CONTAINER
; ========================================
do_close:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dc_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dc_have_name:
    LD A,NUM_ITEMS
    OR A
    JP Z,dc_not_container
    LD B,A
    LD C,1
dc_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dc_next
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dc_next
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dc_next
    POP BC
    LD A,C
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD (HL),0
    LD HL,msg_closed
    CALL print_str
    CALL print_nl
    RET
dc_next:
    POP BC
    INC C
    DEC B
    JP NZ,dc_loop
dc_not_container:
    LD HL,msg_notcontainer
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: LOOK IN CONTAINER
; A = offset to container name
; ========================================
do_look_in:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dli_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dli_have_name:
    LD A,NUM_ITEMS
    OR A
    JP Z,dli_not_found
    LD B,A
    LD C,1
dli_loop:
    PUSH BC
    ; Is it a container?
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dli_next
    ; Is it here or carried?
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP Z,dli_accessible
    LD A,D
    OR A
    JP NZ,dli_next
dli_accessible:
    ; Check name matches
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dli_next
    ; Found container - check if open
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP NZ,dli_show_contents
    ; Closed
    POP BC
    LD HL,msg_closed
    CALL print_str
    CALL print_nl
    RET
dli_show_contents:
    POP BC
    LD A,C
    LD (temp_container),A
    CALL print_nl
    LD HL,msg_inside
    CALL print_str
    CALL print_nl
    ; Search for items with parent = container
    LD A,NUM_ITEMS
    OR A
    JP Z,dli_empty
    LD B,A
    LD C,1
    LD E,0
dli_content_loop:
    PUSH BC
    PUSH DE
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(temp_container)
    CP D
    JP NZ,dli_content_next
    ; Found item in container
    LD HL,msg_indent
    CALL print_str
    POP DE
    POP BC
    PUSH BC
    PUSH DE
    LD A,C
    CALL get_item_name_ptr
    CALL print_str
    CALL print_nl
    POP DE
    LD E,1
    PUSH DE
dli_content_next:
    POP DE
    POP BC
    INC C
    DEC B
    JP NZ,dli_content_loop
    LD A,E
    OR A
    JP NZ,dli_done
dli_empty:
    LD HL,msg_empty_c
    CALL print_str
    CALL print_nl
dli_done:
    CALL wait_key
    RET
dli_next:
    POP BC
    INC C
    DEC B
    JP NZ,dli_loop
dli_not_found:
    LD HL,msg_notcontainer
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: TAKE ITEM FROM CONTAINER
; ========================================
do_take_from:
    ; Parse 'TAKE item FROM container'
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,dtf_syntax_err
    LD (temp_ptr2),HL    ; position of ' FROM '
    ; Get item name (after TAKE, before FROM)
    LD HL,input_buffer
    LD B,5               ; skip 'TAKE '
dtf_skip:
    INC HL
    DJNZ dtf_skip
    LD (temp_ptr),HL     ; item name start
    ; Get container name (after ' FROM ')
    LD HL,(temp_ptr2)
    LD B,6               ; skip ' FROM '
dtf_skip2:
    INC HL
    DJNZ dtf_skip2
    LD A,(HL)
    OR A
    JP Z,dtf_syntax_err
    LD (temp_ptr3),HL    ; container name start

    ; Find container in room or inventory
    LD A,NUM_ITEMS
    OR A
    JP Z,dtf_no_container
    LD B,A
    LD C,1
dtf_cont_loop:
    PUSH BC
    ; Is it a container?
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dtf_cont_next
    ; Is it here or carried?
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP Z,dtf_cont_accessible
    LD A,D
    OR A                 ; 0 = carried
    JP NZ,dtf_cont_next
dtf_cont_accessible:
    ; Check container name matches
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr3)
    CALL str_contains
    JP NZ,dtf_cont_next
    ; Found container!
    POP BC
    LD A,C
    LD (temp_container),A
    JP dtf_check_open
dtf_cont_next:
    POP BC
    INC C
    DEC B
    JP NZ,dtf_cont_loop
dtf_no_container:
    LD HL,msg_notcontainer
    CALL print_str
    CALL print_nl
    RET

dtf_check_open:
    ; Check if container is open
    LD A,(temp_container)
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP NZ,dtf_find_item
    LD HL,msg_closed
    CALL print_str
    CALL print_nl
    RET

dtf_find_item:
    ; Find item in container
    LD A,NUM_ITEMS
    LD B,A
    LD C,1
dtf_item_loop:
    PUSH BC
    ; Check if item is in this container
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(temp_container)
    CP D
    JP NZ,dtf_item_next
    ; Check item name matches
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_partial_match
    JP NZ,dtf_item_next
    ; Found item! Check inventory space
    LD A,(inv_count)
    CP INV_LIMIT
    JP C,dtf_can_take
    POP BC
    LD HL,msg_full
    CALL print_str
    CALL print_nl
    RET
dtf_can_take:
    POP BC
    ; Add to inventory
    LD A,C
    CALL add_to_inventory
    ; Remove from container (set parent to 0)
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD (HL),0
    ; Set location to 0 (carried)
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD (HL),0
    ; Add points
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_points
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dtf_no_points
    LD HL,(score)
    LD B,0
    LD C,A
    ADD HL,BC
    LD (score),HL
    ; Clear points
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_points
    CALL add_a_to_hl
    LD (HL),0
dtf_no_points:
    POP BC
    LD HL,msg_taken
    CALL print_str
    CALL print_nl
    RET
dtf_item_next:
    POP BC
    INC C
    DEC B
    JP NZ,dtf_item_loop
    LD HL,msg_nothere
    CALL print_str
    CALL print_nl
    RET
dtf_syntax_err:
    LD HL,msg_takefrom_syntax
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: TALK TO NPC
; ========================================
do_talk:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dtk_have_name
    LD HL,msg_talkto
    CALL print_str
    CALL print_nl
    RET
dtk_have_name:
    LD A,NUM_NPCS
    OR A
    JP Z,dtk_noone
    LD B,A
    LD C,1
dtk_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,npc_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,dtk_next
    POP BC
    PUSH BC
    LD A,C
    CALL get_npc_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dtk_next
    POP BC
    LD A,C
    CALL get_npc_talk_ptr
    LD A,(HL)
    OR A
    JR NZ,dtk_has_dialog
    LD HL,msg_silence
    CALL print_str
    CALL print_nl
    RET
dtk_has_dialog:
    CALL print_nl
    LD A,34              ; quote char
    CALL TXT_OUTPUT
    CALL print_str
    LD A,34
    CALL TXT_OUTPUT
    CALL print_nl
    CALL wait_key
    RET
dtk_next:
    POP BC
    INC C
    DEC B
    JP NZ,dtk_loop
dtk_noone:
    LD HL,msg_noone
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: GIVE ITEM TO NPC
; ========================================
do_give:
    ; Parse 'GIVE item TO npc'
    LD HL,input_buffer
    LD DE,str_kw_to
    CALL str_find
    JP NC,dg_syntax_err
    LD (temp_ptr2),HL
    ; Get item name start (after GIVE )
    LD HL,input_buffer
    LD B,5               ; skip 'GIVE '
dg_skip:
    INC HL
    DJNZ dg_skip
    LD (temp_ptr),HL
    ; Get NPC name (after ' TO ')
    LD HL,(temp_ptr2)
    LD B,4               ; skip ' TO '
dg_skip2:
    INC HL
    DJNZ dg_skip2
    LD A,(HL)
    OR A
    JP Z,dg_syntax_err
    LD (temp_ptr3),HL

    ; Find item in inventory
    LD A,(inv_count)
    OR A
    JP Z,dg_no_item
    LD B,A
    LD C,1
dg_item_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dg_item_next
    PUSH AF
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_partial_match
    POP AF
    JP NZ,dg_item_next
    LD (found_item),A
    POP BC
    LD A,C
    LD (found_inv_slot),A
    JP dg_find_npc
dg_item_next:
    POP BC
    INC C
    DEC B
    JP NZ,dg_item_loop
dg_no_item:
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET

dg_find_npc:
    LD A,NUM_NPCS
    OR A
    JP Z,dg_no_npc
    LD B,A
    LD C,1
dg_npc_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,npc_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,dg_npc_next
    POP BC
    PUSH BC
    LD A,C
    CALL get_npc_name_ptr
    EX DE,HL
    LD HL,(temp_ptr3)
    CALL str_contains
    JP NZ,dg_npc_next
    POP BC
    LD A,C
    LD (found_npc),A
    JP dg_do_give
dg_npc_next:
    POP BC
    INC C
    DEC B
    JP NZ,dg_npc_loop
dg_no_npc:
    LD HL,msg_noone
    CALL print_str
    CALL print_nl
    RET

dg_do_give:
    ; Check if NPC already got gift
    LD A,(found_npc)
    DEC A
    LD HL,npc_got_gift
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP NZ,dg_nothing
    ; Check if NPC wants this item
    LD A,(found_npc)
    CALL get_npc_wants_ptr
    LD A,(HL)
    OR A
    JP Z,dg_no_want
    EX DE,HL
    LD A,(found_item)
    CALL get_item_name_ptr
    CALL str_equal
    JP NZ,dg_no_want
    ; NPC accepts!
    LD A,(found_inv_slot)
    CALL remove_from_inventory
    ; Mark NPC
    LD A,(found_npc)
    DEC A
    LD HL,npc_got_gift
    CALL add_a_to_hl
    LD (HL),1
    ; Add points
    LD A,(found_npc)
    DEC A
    LD HL,npc_points
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR Z,dg_no_points
    LD HL,(score)
    LD B,0
    LD C,A
    ADD HL,BC
    LD (score),HL
dg_no_points:
    LD HL,msg_thanks
    CALL print_str
    CALL print_nl
    ; Check reward
    LD A,(found_npc)
    CALL get_npc_gives_ptr
    LD A,(HL)
    OR A
    RET Z
    LD HL,msg_got
    CALL print_str
    LD A,(found_npc)
    CALL get_npc_gives_ptr
    CALL print_str
    CALL print_nl
    RET
dg_nothing:
    LD HL,msg_nothing
    CALL print_str
    CALL print_nl
    RET
dg_no_want:
    LD HL,msg_nowant
    CALL print_str
    CALL print_nl
    RET
dg_syntax_err:
    LD HL,msg_give_syntax
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: SHOW INVENTORY
; ========================================
do_inventory:
    CALL print_nl
    LD HL,msg_inventory
    CALL print_str
    CALL print_nl
    LD A,(inv_count)
    OR A
    JR NZ,di_show_items
    LD HL,msg_empty
    CALL print_str
    CALL print_nl
    CALL wait_key
    RET
di_show_items:
    LD B,A
    LD C,1
di_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR Z,di_next
    LD HL,msg_indent
    CALL print_str
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    CALL get_item_name_ptr
    CALL print_str
    CALL print_nl
di_next:
    POP BC
    INC C
    DJNZ di_loop
    CALL wait_key
    RET

; ========================================
; ACTION: SHOW HELP
; ========================================
do_help:
    CALL print_nl
    LD HL,str_help_text
    CALL print_str
    CALL wait_key
    RET

; ========================================
; ACTION: SHOW SCORE
; ========================================
do_show_score:
    CALL print_nl
    LD HL,msg_score
    CALL print_str
    LD HL,(score)
    CALL print_num
    CALL print_nl
    CALL wait_key
    RET

; ========================================
; ACTION: QUIT GAME
; ========================================
do_quit:
    CALL print_nl
    LD HL,msg_bye
    CALL print_str
    CALL print_nl
    CALL wait_key
    ; Clean return to BASIC
    LD SP,(original_sp)  ; Restore original stack
    RET                  ; Return to BASIC

; ========================================
; ACTION: SAVE GAME
; ========================================
do_save:
    CALL print_nl
    LD HL,msg_filename
    CALL print_str
    ; Get filename from user
    CALL get_filename
    LD A,(filename_len)
    OR A
    JP Z,do_save_cancel
    ; Open file for writing
    LD B,A               ; B = filename length
    LD HL,filename_buf
    LD DE,cas_buffer     ; 2KB buffer for cassette
    CALL CAS_OUT_OPEN
    JP NC,do_save_error
    ; Save game data
    LD HL,save_data_start
    LD DE,save_data_end-save_data_start
    LD BC,0              ; execution address (not used)
    LD A,2               ; file type = binary
    CALL CAS_OUT_DIRECT
    JP NC,do_save_error_close
    ; Close file
    CALL CAS_OUT_CLOSE
    LD HL,msg_saved
    CALL print_str
    CALL print_nl
    RET
do_save_error_close:
    CALL CAS_OUT_CLOSE
do_save_error:
    LD HL,msg_save_error
    CALL print_str
    CALL print_nl
    RET
do_save_cancel:
    LD HL,msg_cancelled
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: LOAD GAME
; ========================================
do_load:
    CALL print_nl
    LD HL,msg_filename
    CALL print_str
    ; Get filename from user
    CALL get_filename
    LD A,(filename_len)
    OR A
    JP Z,do_load_cancel
    ; Open file for reading
    LD B,A               ; B = filename length
    LD HL,filename_buf
    LD DE,cas_buffer     ; 2KB buffer for cassette
    CALL CAS_IN_OPEN
    JP NC,do_load_error
    ; Load game data
    LD HL,save_data_start
    CALL CAS_IN_DIRECT
    JP NC,do_load_error_close
    ; Close file
    CALL CAS_IN_CLOSE
    ; Set refresh flag
    LD A,1
    LD (refresh_flag),A
    LD HL,msg_loaded
    CALL print_str
    CALL print_nl
    RET
do_load_error_close:
    CALL CAS_IN_CLOSE
do_load_error:
    LD HL,msg_load_error
    CALL print_str
    CALL print_nl
    RET
do_load_cancel:
    LD HL,msg_cancelled
    CALL print_str
    CALL print_nl
    RET

; ========================================
; GET FILENAME (max 8 chars)
; ========================================
get_filename:
    CALL TXT_CUR_ON
    LD HL,filename_buf
    LD B,0               ; char count
gfn_loop:
    CALL KM_WAIT_CHAR
    CP 13                ; Enter
    JR Z,gfn_done
    CP 27                ; Escape
    JR Z,gfn_escape
    CP 127               ; Delete
    JR Z,gfn_backspace
    CP 8                 ; Backspace
    JR Z,gfn_backspace
    CP 32                ; Must be >= space
    JR C,gfn_loop
    CP '.'               ; No dots allowed
    JR Z,gfn_loop
    ; Check max length (8 chars)
    LD C,A
    LD A,B
    CP 8
    JR NC,gfn_loop       ; Already 8 chars
    LD A,C
    ; Convert to uppercase
    CP 'a'
    JR C,gfn_store
    CP 'z'+1
    JR NC,gfn_store
    SUB 32
gfn_store:
    LD (HL),A
    INC HL
    INC B
    CALL TXT_OUTPUT
    JR gfn_loop
gfn_backspace:
    LD A,B
    OR A
    JR Z,gfn_loop
    DEC HL
    DEC B
    LD A,8
    CALL TXT_OUTPUT
    LD A,' '
    CALL TXT_OUTPUT
    LD A,8
    CALL TXT_OUTPUT
    JR gfn_loop
gfn_escape:
    LD B,0               ; Return empty = cancelled
gfn_done:
    LD A,B
    LD (filename_len),A
    CALL TXT_CUR_OFF
    CALL print_nl
    RET

; ========================================
; CHECK IF ROOM IS DEADLY
; ========================================
check_death:
    LD A,NUM_DEADLY
    OR A
    RET Z
    LD B,A
    LD C,0
cd_loop:
    PUSH BC
    LD A,C
    LD HL,deadly_rooms
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,cd_next
    ; In deadly room - check survival item
    POP BC
    PUSH BC
    LD A,C
    CALL get_survival_item_ptr
    LD A,(HL)
    OR A
    JP Z,cd_die
    CALL check_have_item
    JP C,cd_next
cd_die:
    CALL TXT_CLEAR
    CALL print_nl
    POP BC
    LD A,C
    CALL get_death_msg_ptr
    CALL print_str
    CALL print_nl
    CALL print_nl
    LD HL,msg_gameover
    CALL print_str
    CALL print_nl
    CALL wait_key
    LD SP,(original_sp)
    RET
cd_next:
    POP BC
    INC C
    DEC B
    JP NZ,cd_loop
    RET

; ========================================
; CHECK WIN CONDITION
; ========================================
check_win:
    LD A,WIN_ROOM
    OR A
    RET Z
    LD B,A
    LD A,(current_room)
    CP B
    RET NZ
    ; Check score
    LD A,WIN_SCORE
    OR A
    JR Z,cw_check_items
    LD HL,(score)
    LD B,0
    LD C,A
    OR A
    SBC HL,BC
    RET C
cw_check_items:
    LD A,WIN_ITEMS_COUNT
    OR A
    JR Z,cw_win
cw_win:
    CALL TXT_CLEAR
    CALL print_nl
    LD HL,str_winmsg
    CALL print_str
    CALL print_nl
    CALL print_nl
    LD HL,msg_youwin
    CALL print_str
    CALL print_nl
    LD HL,msg_score
    CALL print_str
    LD HL,(score)
    CALL print_num
    CALL print_nl
    CALL wait_key
    LD SP,(original_sp)
    RET

; ========================================
; UTILITY FUNCTIONS
; ========================================

; Print null-terminated string at HL
print_str:
    LD A,(HL)
    OR A
    RET Z
    CALL TXT_OUTPUT
    INC HL
    JR print_str

; Print CR/LF
print_nl:
    LD A,13
    CALL TXT_OUTPUT
    LD A,10
    CALL TXT_OUTPUT
    RET

; Print 16-bit number in HL
print_num:
    LD DE,10000
    CALL pn_digit
    LD DE,1000
    CALL pn_digit
    LD DE,100
    CALL pn_digit
    LD DE,10
    CALL pn_digit
    LD A,L
    ADD A,'0'
    CALL TXT_OUTPUT
    RET
pn_digit:
    LD A,'0'-1
pn_loop:
    INC A
    OR A
    SBC HL,DE
    JR NC,pn_loop
    ADD HL,DE
    CALL TXT_OUTPUT
    RET

; Wait for keypress
wait_key:
    LD HL,msg_presskey
    CALL print_str
    CALL KM_WAIT_CHAR
    CALL print_nl
    RET

; Add A to HL
add_a_to_hl:
    ADD A,L
    LD L,A
    RET NC
    INC H
    RET

; Compare strings HL and DE, Z if equal
str_equal:
    PUSH HL
    PUSH DE
seq_loop:
    LD A,(DE)
    CP (HL)
    JR NZ,seq_ne
    OR A
    JR Z,seq_eq
    INC HL
    INC DE
    JR seq_loop
seq_eq:
    POP DE
    POP HL
    XOR A
    RET
seq_ne:
    POP DE
    POP HL
    OR 1
    RET

; Check if HL starts with DE (B chars), Z if match
str_prefix:
    PUSH HL
    PUSH DE
sp_loop:
    LD A,B
    OR A
    JR Z,sp_match
    LD A,(DE)
    CP (HL)
    JR NZ,sp_no
    INC HL
    INC DE
    DEC B
    JR sp_loop
sp_match:
    POP DE
    POP HL
    XOR A
    RET
sp_no:
    POP DE
    POP HL
    OR 1
    RET

; Check if DE contains HL substring, Z if found
str_contains:
    PUSH HL
    PUSH DE
sc_outer:
    LD A,(DE)
    OR A
    JR Z,sc_no
    PUSH HL
    PUSH DE
sc_inner:
    LD A,(HL)
    OR A
    JR Z,sc_yes_pop
    LD A,(DE)
    OR A
    JR Z,sc_no_pop
    CP (HL)
    JR NZ,sc_no_pop
    INC HL
    INC DE
    JR sc_inner
sc_yes_pop:
    POP DE
    POP HL
    POP DE
    POP HL
    XOR A
    RET
sc_no_pop:
    POP DE
    POP HL
    INC DE
    JR sc_outer
sc_no:
    POP DE
    POP HL
    OR 1
    RET

; Partial match - HL until space/TO in DE, Z if found
str_partial_match:
    PUSH HL
    PUSH DE
spm_outer:
    LD A,(DE)
    OR A
    JR Z,spm_no
    PUSH HL
    PUSH DE
spm_inner:
    LD A,(HL)
    OR A
    JR Z,spm_yes_pop
    CP ' '
    JR Z,spm_yes_pop
    LD A,(DE)
    OR A
    JR Z,spm_no_pop
    CP (HL)
    JR NZ,spm_no_pop
    INC HL
    INC DE
    JR spm_inner
spm_yes_pop:
    POP DE
    POP HL
    POP DE
    POP HL
    XOR A
    RET
spm_no_pop:
    POP DE
    POP HL
    INC DE
    JR spm_outer
spm_no:
    POP DE
    POP HL
    OR 1
    RET

; Find DE substring in HL, C flag if found, HL = match pos
str_find:
    LD (sf_de_save),DE    ; Save search string pointer
sf_loop:
    LD A,(HL)
    OR A
    JR Z,sf_no            ; End of string, not found
    PUSH HL               ; Save current position
    LD DE,(sf_de_save)    ; Get search string
sf_cmp:
    LD A,(DE)
    OR A
    JR Z,sf_yes           ; End of search string = found!
    CP (HL)
    JR NZ,sf_cmp_no       ; Mismatch
    INC HL
    INC DE
    JR sf_cmp
sf_yes:
    POP HL                ; HL = position where match started
    SCF                   ; Set carry = found
    RET
sf_cmp_no:
    POP HL                ; Restore position
    INC HL                ; Try next position
    JR sf_loop
sf_no:
    OR A                  ; Clear carry = not found
    RET
sf_de_save:
    DEFW 0                ; Storage for DE

; ========================================
; DATA ACCESS FUNCTIONS
; ========================================

; Get room name pointer for room A
get_room_name_ptr:
    DEC A
    SLA A
    LD HL,room_name_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get room desc pointer for room A
get_room_desc_ptr:
    DEC A
    SLA A
    LD HL,room_desc_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get exit: A=room, B=dir(1-6), returns A=target
get_exit:
    PUSH BC
    DEC A
    LD C,A
    LD A,B
    DEC A
    LD B,A
    ; offset = room * 6 + dir
    LD A,C
    LD H,0
    LD L,A
    ADD HL,HL
    ADD HL,HL
    LD D,0
    LD E,A
    ADD HL,DE
    ADD HL,DE
    LD A,B
    CALL add_a_to_hl
    LD DE,room_exits
    ADD HL,DE
    LD A,(HL)
    POP BC
    RET

; Check exit unlocked: A=room, B=dir, A=1 if open
check_exit_unlocked:
    PUSH BC
    DEC A
    LD C,A
    LD A,B
    DEC A
    LD B,A
    LD A,C
    LD H,0
    LD L,A
    ADD HL,HL
    ADD HL,HL
    LD D,0
    LD E,A
    ADD HL,DE
    ADD HL,DE
    LD A,B
    CALL add_a_to_hl
    LD DE,room_unlocked
    ADD HL,DE
    LD A,(HL)
    POP BC
    RET

; Set exit unlocked: A=room, B=dir
set_exit_unlocked:
    PUSH BC
    DEC A
    LD C,A
    LD A,B
    DEC A
    LD B,A
    LD A,C
    LD H,0
    LD L,A
    ADD HL,HL
    ADD HL,HL
    LD D,0
    LD E,A
    ADD HL,DE
    ADD HL,DE
    LD A,B
    CALL add_a_to_hl
    LD DE,room_unlocked
    ADD HL,DE
    LD (HL),1
    POP BC
    RET

; Get exit requirement: A=room, B=dir, HL=ptr
get_exit_requirement:
    PUSH BC
    DEC A
    LD C,A
    LD A,B
    DEC A
    LD B,A
    LD A,C
    LD H,0
    LD L,A
    ADD HL,HL
    ADD HL,HL
    LD D,0
    LD E,A
    ADD HL,DE
    ADD HL,DE
    LD A,B
    CALL add_a_to_hl
    ADD HL,HL
    LD DE,room_req_ptrs
    ADD HL,DE
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    POP BC
    RET

; Get item name: A=index(1-based), HL=ptr
get_item_name_ptr:
    DEC A
    SLA A
    LD HL,item_name_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get item desc: A=index, HL=ptr
get_item_desc_ptr:
    DEC A
    SLA A
    LD HL,item_desc_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC name: A=index(1-based), HL=ptr
get_npc_name_ptr:
    DEC A
    SLA A
    LD HL,npc_name_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC desc: A=index, HL=ptr
get_npc_desc_ptr:
    DEC A
    SLA A
    LD HL,npc_desc_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC talk: A=index, HL=ptr
get_npc_talk_ptr:
    DEC A
    SLA A
    LD HL,npc_talk_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC wants: A=index, HL=ptr
get_npc_wants_ptr:
    DEC A
    SLA A
    LD HL,npc_wants_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC gives: A=index, HL=ptr
get_npc_gives_ptr:
    DEC A
    SLA A
    LD HL,npc_gives_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get death msg: A=index, HL=ptr
get_death_msg_ptr:
    SLA A
    LD HL,death_msg_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get survival item: A=index, HL=ptr
get_survival_item_ptr:
    SLA A
    LD HL,survival_item_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; ========================================
; INVENTORY MANAGEMENT
; ========================================

; Add item A to inventory
add_to_inventory:
    PUSH AF
    LD A,(inv_count)
    LD HL,inventory
    CALL add_a_to_hl
    POP AF
    LD (HL),A
    LD A,(inv_count)
    INC A
    LD (inv_count),A
    RET

; Remove item at slot A
remove_from_inventory:
    LD (rfi_slot),A
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD D,H
    LD E,L
    INC DE
    LD A,(inv_count)
    LD B,A
    LD A,(rfi_slot)
    SUB B
    NEG
    JR Z,rfi_done
    LD B,A
rfi_shift:
    LD A,(DE)
    LD (HL),A
    INC HL
    INC DE
    DJNZ rfi_shift
rfi_done:
    LD A,(inv_count)
    DEC A
    LD (inv_count),A
    RET
rfi_slot:
    DEFB 0

; Check if have item, HL=name, C flag if yes
check_have_item:
    LD (chi_name),HL
    LD A,(inv_count)
    OR A
    JR Z,chi_no
    LD B,A
    LD C,1
chi_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR Z,chi_next
    CALL get_item_name_ptr
    LD DE,(chi_name)
    CALL str_contains
    JR NZ,chi_next
    POP BC
    SCF
    RET
chi_next:
    POP BC
    INC C
    DJNZ chi_loop
chi_no:
    OR A
    RET
chi_name:
    DEFW 0

; ========================================
; INITIALIZE GAME DATA
; ========================================
init_data:
    ; Copy initial item locations
    LD HL,item_locations_init
    LD DE,item_locations
    LD BC,NUM_ITEMS
    LD A,B
    OR C
    JR Z,id_skip_items
    LDIR
id_skip_items:
    ; Copy initial item parents
    LD HL,item_parents_init
    LD DE,item_parents
    LD BC,NUM_ITEMS
    LD A,B
    OR C
    JR Z,id_skip_parents
    LDIR
id_skip_parents:
    ; Copy initial NPC locations
    LD HL,npc_locations_init
    LD DE,npc_locations
    LD BC,NUM_NPCS
    LD A,B
    OR C
    JR Z,id_skip_npcs
    LDIR
id_skip_npcs:
    ; Init unlocked flags to 0 (locked by default)
    ; Exits without requirements are always open (checked in do_move)
    LD HL,room_unlocked
    LD BC,NUM_ROOMS*6
id_unlock_loop:
    XOR A
    LD (HL),A
    INC HL
    DEC BC
    LD A,B
    OR C
    JR NZ,id_unlock_loop
    ; Clear item open flags
    LD HL,item_is_open
    LD BC,NUM_ITEMS
    LD A,B
    OR C
    JR Z,id_skip_open
id_open_loop:
    XOR A
    LD (HL),A
    INC HL
    DEC BC
    LD A,B
    OR C
    JR NZ,id_open_loop
id_skip_open:
    ; Clear NPC gift flags
    LD HL,npc_got_gift
    LD BC,NUM_NPCS
    LD A,B
    OR C
    JR Z,id_skip_gift
id_gift_loop:
    XOR A
    LD (HL),A
    INC HL
    DEC BC
    LD A,B
    OR C
    JR NZ,id_gift_loop
id_skip_gift:
    ; Clear inventory
    LD HL,inventory
    LD B,MAX_INV
    XOR A
id_inv_loop:
    LD (HL),A
    INC HL
    DJNZ id_inv_loop
    RET

; ========================================
; GAME DATA
; ========================================

str_title:
    DEFB "THE FORGOTTEN MAYA PRINCESS",0
str_author:
    DEFB "Devil-System",0
str_by:
    DEFB "by ",0

str_winmsg:
    DEFB "Princess Ixchel embraces you! Together",13,10
    DEFB "you escape into the sunset!",0

str_help_text:
    DEFB "COMMANDS:",13,10
    DEFB "N,S,E,W,U,D - Move",13,10
    DEFB "TAKE, DROP, USE",13,10
    DEFB "EXAMINE, OPEN, CLOSE",13,10
    DEFB "TALK TO, GIVE X TO Y",13,10
    DEFB "I, L, SCORE, QUIT",0

str_dir_n:
    DEFB "N ",0
str_dir_e:
    DEFB "E ",0
str_dir_s:
    DEFB "S ",0
str_dir_w:
    DEFB "W ",0
str_dir_u:
    DEFB "U ",0
str_dir_d:
    DEFB "D ",0

str_kw_to:
    DEFB " TO ",0

str_kw_from:
    DEFB " FROM ",0
; === MESSAGES ===
msg_filename:
    DEFB "SAVEFILE (no ext.):",0
msg_saved:
    DEFB "Game saved.",0
msg_loaded:
    DEFB "Game loaded.",0
msg_save_error:
    DEFB "Can't save.",0
msg_load_error:
    DEFB "Can't load.",0
msg_cancelled:
    DEFB "Cancelled.",0
msg_takefrom_syntax:
    DEFB "TAKE X FROM Y",0
msg_yousee:
    DEFB "You see: ",0
msg_inside:
    DEFB "Inside: ",0
msg_empty_c:
    DEFB "  Empty.",0
msg_exits:
    DEFB "Exits: ",0
msg_none:
    DEFB "none",0
msg_score:
    DEFB "Score: ",0
msg_presskey:
    DEFB "[key]",0
msg_unknown:
    DEFB "Huh?",0
msg_cantgo:
    DEFB "Can't go there.",0
msg_blocked:
    DEFB "Blocked.",0
msg_what:
    DEFB "What?",0
msg_nothere:
    DEFB "Not here.",0
msg_donthave:
    DEFB "Don't have it.",0
msg_full:
    DEFB "Full!",0
msg_taken:
    DEFB "Taken.",0
msg_dropped:
    DEFB "Dropped.",0
msg_usewhat:
    DEFB "Use what?",0
msg_unlocked:
    DEFB "Unlocked!",0
msg_nothing:
    DEFB "Nothing happens.",0
msg_nothing_special:
    DEFB "You see nothing special.",0
msg_opened:
    DEFB "Opened.",0
msg_closed:
    DEFB "Closed.",0
msg_notcontainer:
    DEFB "Can't open that.",0
msg_talkto:
    DEFB "Talk to whom?",0
msg_noone:
    DEFB "No one here.",0
msg_silence:
    DEFB "...",0
msg_thanks:
    DEFB "Thanks!",0
msg_nowant:
    DEFB "They don't want that.",0
msg_got:
    DEFB "Got: ",0
msg_give_syntax:
    DEFB "GIVE X TO Y",0
msg_inventory:
    DEFB "Inventory:",0
msg_empty:
    DEFB "  empty",0
msg_indent:
    DEFB "  ",0
msg_gameover:
    DEFB "GAME OVER",0
msg_youwin:
    DEFB "YOU WIN!",0
msg_bye:
    DEFB "Goodbye!",0
str_empty:
    DEFB 0

; === COMMAND STRINGS ===
cmd_north:
    DEFB "N",0
cmd_north1:
    DEFB "NORTH",0
cmd_north2:
    DEFB "GO NORTH",0
cmd_south:
    DEFB "S",0
cmd_south1:
    DEFB "SOUTH",0
cmd_south2:
    DEFB "GO SOUTH",0
cmd_east:
    DEFB "E",0
cmd_east1:
    DEFB "EAST",0
cmd_east2:
    DEFB "GO EAST",0
cmd_west:
    DEFB "W",0
cmd_west1:
    DEFB "WEST",0
cmd_west2:
    DEFB "GO WEST",0
cmd_up:
    DEFB "U",0
cmd_up1:
    DEFB "UP",0
cmd_up2:
    DEFB "GO UP",0
cmd_up3:
    DEFB "CLIMB",0
cmd_down:
    DEFB "D",0
cmd_down1:
    DEFB "DOWN",0
cmd_down2:
    DEFB "DN",0
cmd_down3:
    DEFB "GO DOWN",0
cmd_down4:
    DEFB "DESCEND",0
cmd_lookin:
    DEFB "L IN",0
cmd_lookin1:
    DEFB "LOOK IN",0
cmd_lookin2:
    DEFB "EXAMINE ROOM IN",0
cmd_look:
    DEFB "L",0
cmd_look1:
    DEFB "LOOK",0
cmd_look2:
    DEFB "EXAMINE ROOM",0
cmd_inv:
    DEFB "I",0
cmd_inv1:
    DEFB "INV",0
cmd_inv2:
    DEFB "INVENTORY",0
cmd_take:
    DEFB "TAKE",0
cmd_take1:
    DEFB "GET",0
cmd_take2:
    DEFB "GRAB",0
cmd_take3:
    DEFB "PICK UP",0
cmd_drop:
    DEFB "DROP",0
cmd_drop1:
    DEFB "PUT DOWN",0
cmd_drop2:
    DEFB "LEAVE",0
cmd_exam:
    DEFB "EXAMINE",0
cmd_exam1:
    DEFB "X",0
cmd_exam2:
    DEFB "LOOK AT",0
cmd_exam3:
    DEFB "CHECK",0
cmd_exam4:
    DEFB "INSPECT",0
cmd_use:
    DEFB "USE",0
cmd_use1:
    DEFB "APPLY",0
cmd_open:
    DEFB "OPEN",0
cmd_close:
    DEFB "CLOSE",0
cmd_close1:
    DEFB "SHUT",0
cmd_talk:
    DEFB "TALK TO",0
cmd_talk1:
    DEFB "SPEAK TO",0
cmd_talk2:
    DEFB "ASK TO",0
cmd_talk3:
    DEFB "SAY TO",0
cmd_give:
    DEFB "GIVE",0
cmd_give1:
    DEFB "OFFER",0
cmd_give2:
    DEFB "HAND",0
cmd_help:
    DEFB "HELP",0
cmd_help1:
    DEFB "?",0
cmd_help2:
    DEFB "COMMANDS",0
cmd_help3:
    DEFB "HINT",0
cmd_score:
    DEFB "SCORE",0
cmd_score1:
    DEFB "POINTS",0
cmd_save:
    DEFB "SAVE",0
cmd_save1:
    DEFB "SAVE GAME",0
cmd_load:
    DEFB "LOAD",0
cmd_load1:
    DEFB "RESTORE",0
cmd_load2:
    DEFB "LOAD GAME",0
cmd_quit:
    DEFB "QUIT",0
cmd_quit1:
    DEFB "EXIT",0
cmd_quit2:
    DEFB "Q",0
cmd_quit3:
    DEFB "BYE",0

; === ROOM DATA ===
room_name_ptrs:
    DEFW room_1_name
    DEFW room_2_name
    DEFW room_3_name
    DEFW room_4_name
    DEFW room_5_name
    DEFW room_6_name
    DEFW room_7_name
    DEFW room_8_name
    DEFW room_9_name
    DEFW room_10_name
    DEFW room_11_name
    DEFW room_12_name
    DEFW room_13_name
    DEFW room_14_name
    DEFW room_15_name
    DEFW room_16_name
    DEFW room_17_name
    DEFW room_18_name
    DEFW room_19_name
    DEFW room_20_name
    DEFW room_21_name
    DEFW room_22_name
    DEFW room_23_name
    DEFW room_24_name
    DEFW room_25_name

room_desc_ptrs:
    DEFW room_1_desc
    DEFW room_2_desc
    DEFW room_3_desc
    DEFW room_4_desc
    DEFW room_5_desc
    DEFW room_6_desc
    DEFW room_7_desc
    DEFW room_8_desc
    DEFW room_9_desc
    DEFW room_10_desc
    DEFW room_11_desc
    DEFW room_12_desc
    DEFW room_13_desc
    DEFW room_14_desc
    DEFW room_15_desc
    DEFW room_16_desc
    DEFW room_17_desc
    DEFW room_18_desc
    DEFW room_19_desc
    DEFW room_20_desc
    DEFW room_21_desc
    DEFW room_22_desc
    DEFW room_23_desc
    DEFW room_24_desc
    DEFW room_25_desc

room_1_name:
    DEFB "Base Camp",0
room_1_desc:
    DEFB "Your expedition camp at jungle's edge.",13,10
    DEFB "A worn tent and smoking fire. Your",13,10
    DEFB "guide fled, leaving notes about a",13,10
    DEFB "cursed princess of legendary beauty.",0
room_2_name:
    DEFB "Jungle Trail",0
room_2_desc:
    DEFB "A narrow path through dense",13,10
    DEFB "vegetation. Howler monkeys scream",13,10
    DEFB "above. The humidity makes your clothes",13,10
    DEFB "cling to your body.",0
room_3_name:
    DEFB "Riverside",0
room_3_desc:
    DEFB "A swift river blocks your path.",13,10
    DEFB "Crocodiles bask on muddy banks,",13,10
    DEFB "watching hungrily. You need a way to",13,10
    DEFB "cross safely.",0
room_4_name:
    DEFB "Abandoned Camp",0
room_4_desc:
    DEFB "A destroyed campsite. Torn tents and",13,10
    DEFB "scattered gear. A skeleton clutches a",13,10
    DEFB "journal. Something terrible happened",13,10
    DEFB "here.",0
room_5_name:
    DEFB "Jungle Clearing",0
room_5_desc:
    DEFB "Sunlight breaks through the canopy.",13,10
    DEFB "Exotic birds sing. Stone markers hint",13,10
    DEFB "at ancient civilization ahead.",0
room_6_name:
    DEFB "Shaman's Hut",0
room_6_desc:
    DEFB "A hut draped with colorful fabrics.",13,10
    DEFB "Incense curls through the air. Strange",13,10
    DEFB "masks hang on walls. The smell is",13,10
    DEFB "intoxicating.",0
room_7_name:
    DEFB "Jaguar's Den",0
room_7_desc:
    DEFB "A dark cave reeking of musk. Yellow",13,10
    DEFB "eyes gleam from shadows. Low growling",13,10
    DEFB "echoes. The beast is hungry.",0
room_8_name:
    DEFB "Overgrown Path",0
room_8_desc:
    DEFB "Thick vines block your way north.",13,10
    DEFB "Carvings on trees depict a beautiful",13,10
    DEFB "woman, her curves shown in flowing",13,10
    DEFB "robes.",0
room_9_name:
    DEFB "Quicksand Marsh",0
room_9_desc:
    DEFB "Treacherous boggy ground. Bubbles rise",13,10
    DEFB "from hidden pools. One wrong step",13,10
    DEFB "means slow death.",0
room_10_name:
    DEFB "Sacred Cenote",0
room_10_desc:
    DEFB "A deep pool of crystal blue water.",13,10
    DEFB "Ancient Maya threw offerings here.",13,10
    DEFB "Gold glitters in the depths.",0
room_11_name:
    DEFB "Temple Steps",0
room_11_desc:
    DEFB "Massive stone steps lead to a pyramid.",13,10
    DEFB "Carved reliefs show a princess - full",13,10
    DEFB "lips, flowing hair, alluring curves.",0
room_12_name:
    DEFB "Serpent Pit",0
room_12_desc:
    DEFB "A chamber filled with writhing snakes.",13,10
    DEFB "Their hissing echoes. Hundreds of",13,10
    DEFB "forked tongues taste the air.",0
room_13_name:
    DEFB "Outer Treasury",0
room_13_desc:
    DEFB "Gold and jade scattered everywhere.",13,10
    DEFB "Silks and perfume bottles hint at a",13,10
    DEFB "lady's presence. A faint scent",13,10
    DEFB "lingers.",0
room_14_name:
    DEFB "Temple Entrance",0
room_14_desc:
    DEFB "A vast hall with towering serpent",13,10
    DEFB "columns. Torches flicker. Murals",13,10
    DEFB "depict ceremonies honoring a beautiful",13,10
    DEFB "maiden.",0
room_15_name:
    DEFB "Priest's Quarters",0
room_15_desc:
    DEFB "Dusty scrolls and ritual objects. A",13,10
    DEFB "skeleton in robes sits at a desk,",13,10
    DEFB "quill in hand. His message unfinished.",0
room_16_name:
    DEFB "Inner Treasury",0
room_16_desc:
    DEFB "The princess's treasure room. Jewels",13,10
    DEFB "sparkle. Delicate silk garments hint",13,10
    DEFB "at her refined tastes.",0
room_17_name:
    DEFB "Main Temple Hall",0
room_17_desc:
    DEFB "An enormous chamber. Moonlight streams",13,10
    DEFB "through openings. Murals show the",13,10
    DEFB "princess dancing sensually under",13,10
    DEFB "stars.",0
room_18_name:
    DEFB "Bathing Pool",0
room_18_desc:
    DEFB "A crystal pool surrounded by orchids.",13,10
    DEFB "Warm steam rises. Rose petals float.",13,10
    DEFB "The princess bathed here.",0
room_19_name:
    DEFB "Cursed Altar",0
room_19_desc:
    DEFB "A black obsidian altar with glowing",13,10
    DEFB "symbols. Dark energy pulses. The",13,10
    DEFB "priest cast his curse here.",0
room_20_name:
    DEFB "Garden of Desire",0
room_20_desc:
    DEFB "A hidden garden with moonflowers. Soft",13,10
    DEFB "music plays from nowhere. Aphrodisiac",13,10
    DEFB "scents fill the air.",0
room_21_name:
    DEFB "Spirit Sanctuary",0
room_21_desc:
    DEFB "Ethereal mist swirls here. Candles",13,10
    DEFB "float in midair. The boundary between",13,10
    DEFB "worlds is thin.",0
room_22_name:
    DEFB "Royal Wardrobe",0
room_22_desc:
    DEFB "Sheer silk gowns fill wardrobes. Lace",13,10
    DEFB "garments lie draped over chairs. A",13,10
    DEFB "vanity reflects your flushed face.",0
room_23_name:
    DEFB "Princess's Chamber",0
room_23_desc:
    DEFB "Silk curtains frame a golden bed.",13,10
    DEFB "PRINCESS IXCHEL lies there, caramel",13,10
    DEFB "skin glowing, curves beneath thin",13,10
    DEFB "white silk.",0
room_24_name:
    DEFB "Hidden Passage",0
room_24_desc:
    DEFB "A secret tunnel behind the bed. Cool",13,10
    DEFB "air flows from ahead. This is the",13,10
    DEFB "escape route.",0
room_25_name:
    DEFB "Temple Exit",0
room_25_desc:
    DEFB "Warm sunlight streams through ancient",13,10
    DEFB "doors. The jungle awaits. Freedom for",13,10
    DEFB "those who carry true love.",0

room_exits:
    DEFB 2,0,0,0,0,0 ; Room 1
    DEFB 5,4,1,3,0,0 ; Room 2
    DEFB 6,2,0,0,0,0 ; Room 3
    DEFB 7,0,0,2,0,0 ; Room 4
    DEFB 8,7,2,6,0,0 ; Room 5
    DEFB 9,5,3,0,0,0 ; Room 6
    DEFB 10,0,4,5,0,0 ; Room 7
    DEFB 11,10,5,9,0,0 ; Room 8
    DEFB 12,8,6,0,0,0 ; Room 9
    DEFB 13,0,7,8,0,0 ; Room 10
    DEFB 14,13,8,12,0,0 ; Room 11
    DEFB 15,11,9,0,0,0 ; Room 12
    DEFB 16,0,10,11,0,0 ; Room 13
    DEFB 17,16,11,15,0,0 ; Room 14
    DEFB 18,14,12,0,0,0 ; Room 15
    DEFB 19,0,13,14,0,0 ; Room 16
    DEFB 20,19,14,18,0,0 ; Room 17
    DEFB 21,17,15,0,0,0 ; Room 18
    DEFB 22,0,16,17,0,0 ; Room 19
    DEFB 23,22,17,21,0,0 ; Room 20
    DEFB 24,20,18,0,0,0 ; Room 21
    DEFB 0,0,19,20,0,0 ; Room 22
    DEFB 0,0,20,24,0,0 ; Room 23
    DEFB 25,23,21,0,0,0 ; Room 24
    DEFB 0,0,24,0,0,0 ; Room 25

room_req_ptrs:
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_8_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_11_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_13_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_17_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_20_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_24_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty

room_8_req_north:
    DEFB "MACHETE",0
room_11_req_north:
    DEFB "TEMPLE KEY",0
room_13_req_north:
    DEFB "SILVER KEY",0
room_17_req_north:
    DEFB "SPIRIT BLESSING",0
room_20_req_north:
    DEFB "GOLDEN KEY",0
room_24_req_north:
    DEFB "JADE HEART",0

; === ITEM DATA ===
item_name_ptrs:
    DEFW item_1_name
    DEFW item_2_name
    DEFW item_3_name
    DEFW item_4_name
    DEFW item_5_name
    DEFW item_6_name
    DEFW item_7_name
    DEFW item_8_name
    DEFW item_9_name
    DEFW item_10_name
    DEFW item_11_name
    DEFW item_12_name
    DEFW item_13_name
    DEFW item_14_name
    DEFW item_15_name
    DEFW item_16_name
    DEFW item_17_name
    DEFW item_18_name
    DEFW item_19_name
    DEFW item_20_name
    DEFW item_21_name
    DEFW item_22_name
    DEFW item_23_name
    DEFW item_24_name
    DEFW item_25_name
    DEFW item_26_name
    DEFW item_27_name
    DEFW item_28_name
    DEFW item_29_name
    DEFW item_30_name
    DEFW item_31_name
    DEFW item_32_name

item_desc_ptrs:
    DEFW item_1_desc
    DEFW item_2_desc
    DEFW item_3_desc
    DEFW item_4_desc
    DEFW item_5_desc
    DEFW item_6_desc
    DEFW item_7_desc
    DEFW item_8_desc
    DEFW item_9_desc
    DEFW item_10_desc
    DEFW item_11_desc
    DEFW item_12_desc
    DEFW item_13_desc
    DEFW item_14_desc
    DEFW item_15_desc
    DEFW item_16_desc
    DEFW item_17_desc
    DEFW item_18_desc
    DEFW item_19_desc
    DEFW item_20_desc
    DEFW item_21_desc
    DEFW item_22_desc
    DEFW item_23_desc
    DEFW item_24_desc
    DEFW item_25_desc
    DEFW item_26_desc
    DEFW item_27_desc
    DEFW item_28_desc
    DEFW item_29_desc
    DEFW item_30_desc
    DEFW item_31_desc
    DEFW item_32_desc

item_locations_init:
    DEFB 1
    DEFB 1
    DEFB 1
    DEFB 2
    DEFB 4
    DEFB 4
    DEFB 4
    DEFB 4
    DEFB 6
    DEFB 6
    DEFB 6
    DEFB 5
    DEFB 10
    DEFB 10
    DEFB 10
    DEFB 11
    DEFB 13
    DEFB 13
    DEFB 13
    DEFB 15
    DEFB 15
    DEFB 16
    DEFB 16
    DEFB 16
    DEFB 18
    DEFB 18
    DEFB 20
    DEFB 20
    DEFB 21
    DEFB 22
    DEFB 22
    DEFB 22

item_is_container:
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0

item_points:
    DEFB 0
    DEFB 5
    DEFB 10
    DEFB 5
    DEFB 15
    DEFB 0
    DEFB 10
    DEFB 10
    DEFB 20
    DEFB 15
    DEFB 10
    DEFB 15
    DEFB 25
    DEFB 15
    DEFB 20
    DEFB 5
    DEFB 0
    DEFB 20
    DEFB 15
    DEFB 20
    DEFB 15
    DEFB 0
    DEFB 30
    DEFB 25
    DEFB 10
    DEFB 15
    DEFB 15
    DEFB 25
    DEFB 35
    DEFB 20
    DEFB 15
    DEFB 20

item_parents_init:
    DEFB 0
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 6
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 17
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 22
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0

item_1_name:
    DEFB "EXPEDITION BAG",0
item_1_desc:
    DEFB "A weathered leather bag left behind.",0
item_2_name:
    DEFB "COMPASS",0
item_2_desc:
    DEFB "A brass compass. Spins wildly near the",13,10
    DEFB "temple.",0
item_3_name:
    DEFB "ROPE",0
item_3_desc:
    DEFB "A long coil of strong rope.",0
item_4_name:
    DEFB "CANTEEN",0
item_4_desc:
    DEFB "A metal canteen filled with water.",0
item_5_name:
    DEFB "JOURNAL",0
item_5_desc:
    DEFB "Describes the princess: 'Skin like",13,10
    DEFB "honey, lips like petals, curves that",13,10
    DEFB "drive men mad.'",0
item_6_name:
    DEFB "WOODEN CHEST",0
item_6_desc:
    DEFB "A rotting chest half-buried in mud.",0
item_7_name:
    DEFB "MACHETE",0
item_7_desc:
    DEFB "A sharp jungle blade for cutting",13,10
    DEFB "vines.",0
item_8_name:
    DEFB "WOODEN PLANK",0
item_8_desc:
    DEFB "A sturdy plank to cross soft ground.",0
item_9_name:
    DEFB "SNAKE CHARM",0
item_9_desc:
    DEFB "A wooden flute that mesmerizes",13,10
    DEFB "serpents.",0
item_10_name:
    DEFB "LOVE POTION",0
item_10_desc:
    DEFB "Pink liquid in a heart bottle.",13,10
    DEFB "'Awakens passion.'",0
item_11_name:
    DEFB "RAW MEAT",0
item_11_desc:
    DEFB "Fresh meat wrapped in leaves.",13,10
    DEFB "Predators love it.",0
item_12_name:
    DEFB "EXOTIC ORCHID",0
item_12_desc:
    DEFB "A purple flower with intoxicating",13,10
    DEFB "fragrance.",0
item_13_name:
    DEFB "GOLD COINS",0
item_13_desc:
    DEFB "Ancient Maya gold coins. Worth a",13,10
    DEFB "fortune.",0
item_14_name:
    DEFB "DIVING MASK",0
item_14_desc:
    DEFB "Primitive diving mask of glass and",13,10
    DEFB "leather.",0
item_15_name:
    DEFB "TEMPLE KEY",0
item_15_desc:
    DEFB "Heavy bronze key with serpent",13,10
    DEFB "engravings.",0
item_16_name:
    DEFB "TORCH",0
item_16_desc:
    DEFB "A burning torch for dark passages.",0
item_17_name:
    DEFB "JEWELED BOX",0
item_17_desc:
    DEFB "An ornate box with rubies and",13,10
    DEFB "emeralds.",0
item_18_name:
    DEFB "SILVER KEY",0
item_18_desc:
    DEFB "A delicate silver key shaped like a",13,10
    DEFB "flower.",0
item_19_name:
    DEFB "SILK SCARF",0
item_19_desc:
    DEFB "Rose-colored. Still carries her sweet",13,10
    DEFB "perfume.",0
item_20_name:
    DEFB "ANCIENT SCROLL",0
item_20_desc:
    DEFB "'Only true love's kiss can wake the",13,10
    DEFB "sleeping beauty.'",0
item_21_name:
    DEFB "RITUAL DAGGER",0
item_21_desc:
    DEFB "An obsidian blade for ancient",13,10
    DEFB "ceremonies.",0
item_22_name:
    DEFB "TREASURE CHEST",0
item_22_desc:
    DEFB "A golden chest overflowing with",13,10
    DEFB "jewels.",0
item_23_name:
    DEFB "JADE AMULET",0
item_23_desc:
    DEFB "Protective amulet pulsing with warm",13,10
    DEFB "energy.",0
item_24_name:
    DEFB "GOLDEN KEY",0
item_24_desc:
    DEFB "A heart-shaped key. Unlocks something",13,10
    DEFB "precious.",0
item_25_name:
    DEFB "ROSE PETALS",0
item_25_desc:
    DEFB "Soft petals floating on the pool.",13,10
    DEFB "Divine scent.",0
item_26_name:
    DEFB "BATH OIL",0
item_26_desc:
    DEFB "Crystal vial of seductive scented oil.",0
item_27_name:
    DEFB "PASSION FRUIT",0
item_27_desc:
    DEFB "Ripe exotic fruit. Its juice awakens",13,10
    DEFB "desire.",0
item_28_name:
    DEFB "MOONFLOWER",0
item_28_desc:
    DEFB "Rare white flower of legendary beauty.",0
item_29_name:
    DEFB "JADE NECKLACE",0
item_29_desc:
    DEFB "Exquisite necklace with heart-shaped",13,10
    DEFB "pendant.",0
item_30_name:
    DEFB "SILK GOWN",0
item_30_desc:
    DEFB "Sheer white gown leaving little to",13,10
    DEFB "imagination.",0
item_31_name:
    DEFB "PERFUME",0
item_31_desc:
    DEFB "Crystal bottle of seductive tropical",13,10
    DEFB "essence.",0
item_32_name:
    DEFB "LACE GARMENT",0
item_32_desc:
    DEFB "Delicate intimate garment of finest",13,10
    DEFB "lace.",0

; === NPC DATA ===
npc_name_ptrs:
    DEFW npc_1_name
    DEFW npc_2_name
    DEFW npc_3_name
    DEFW npc_4_name
    DEFW npc_5_name
    DEFW npc_6_name
    DEFW npc_7_name
    DEFW npc_8_name

npc_desc_ptrs:
    DEFW npc_1_desc
    DEFW npc_2_desc
    DEFW npc_3_desc
    DEFW npc_4_desc
    DEFW npc_5_desc
    DEFW npc_6_desc
    DEFW npc_7_desc
    DEFW npc_8_desc

npc_talk_ptrs:
    DEFW npc_1_talk
    DEFW npc_2_talk
    DEFW npc_3_talk
    DEFW npc_4_talk
    DEFW npc_5_talk
    DEFW npc_6_talk
    DEFW npc_7_talk
    DEFW npc_8_talk

npc_wants_ptrs:
    DEFW npc_1_wants
    DEFW npc_2_wants
    DEFW npc_3_wants
    DEFW npc_4_wants
    DEFW npc_5_wants
    DEFW npc_6_wants
    DEFW npc_7_wants
    DEFW npc_8_wants

npc_gives_ptrs:
    DEFW npc_1_gives
    DEFW npc_2_gives
    DEFW npc_3_gives
    DEFW npc_4_gives
    DEFW npc_5_gives
    DEFW npc_6_gives
    DEFW npc_7_gives
    DEFW npc_8_gives

npc_locations_init:
    DEFB 6
    DEFB 5
    DEFB 17
    DEFB 18
    DEFB 21
    DEFB 22
    DEFB 23
    DEFB 19

npc_points:
    DEFB 20
    DEFB 25
    DEFB 30
    DEFB 25
    DEFB 50
    DEFB 20
    DEFB 60
    DEFB 30

npc_1_name:
    DEFB "OLD SHAMAN",0
npc_1_desc:
    DEFB "A wise old man with knowing eyes and",13,10
    DEFB "colorful feathers.",0
npc_1_talk:
    DEFB "You seek the princess! Take the",13,10
    DEFB "charm for serpents. Feed the jaguar.",13,10
    DEFB "Women love flowers!",0
npc_1_wants:
    DEFB "CANTEEN",0
npc_1_gives:
    DEFB "BLESSING POWDER",0
npc_2_name:
    DEFB "JUNGLE GUIDE",0
npc_2_desc:
    DEFB "Your former guide hiding in bushes. He",13,10
    DEFB "looks ashamed.",0
npc_2_talk:
    DEFB "Forgive me! Take this temple key",13,10
    DEFB "from the cenote. Beware quicksand to",13,10
    DEFB "the west!",0
npc_2_wants:
    DEFB "GOLD COINS",0
npc_2_gives:
    DEFB "TEMPLE MAP",0
npc_3_name:
    DEFB "TEMPLE GUARDIAN",0
npc_3_desc:
    DEFB "Muscular spirit warrior with ancient",13,10
    DEFB "tattoos.",0
npc_3_talk:
    DEFB "Prove worthy! Bring the priest's",13,10
    DEFB "scroll to pass to the sacred garden!",0
npc_3_wants:
    DEFB "ANCIENT SCROLL",0
npc_3_gives:
    DEFB "SPIRIT BLESSING",0
npc_4_name:
    DEFB "BATHING NYMPH",0
npc_4_desc:
    DEFB "Water spirit with glistening wet skin",13,10
    DEFB "and playful eyes.",0
npc_4_talk:
    DEFB "The princess bathed here. Bring",13,10
    DEFB "orchid and I'll share altar secrets.",0
npc_4_wants:
    DEFB "EXOTIC ORCHID",0
npc_4_gives:
    DEFB "WATER BLESSING",0
npc_5_name:
    DEFB "QUEEN IXMUCANE",0
npc_5_desc:
    DEFB "Ghost of princess's mother. Still",13,10
    DEFB "beautiful in ethereal silks.",0
npc_5_talk:
    DEFB "My daughter dreams of rescue! Bring",13,10
    DEFB "her necklace for the Jade Heart.",0
npc_5_wants:
    DEFB "JADE NECKLACE",0
npc_5_gives:
    DEFB "JADE HEART",0
npc_6_name:
    DEFB "HANDMAIDEN",0
npc_6_desc:
    DEFB "Pretty ghost servant in transparent",13,10
    DEFB "robes. She blushes.",0
npc_6_talk:
    DEFB "My lady awaits true love! She adores",13,10
    DEFB "moonflowers and sweet perfume!",0
npc_6_wants:
    DEFB "PERFUME",0
npc_6_gives:
    DEFB "CHAMBER KEY",0
npc_7_name:
    DEFB "PRINCESS IXCHEL",0
npc_7_desc:
    DEFB "Legendary beauty on silken bed.",13,10
    DEFB "Caramel skin, dark hair, curves in",13,10
    DEFB "thin white silk.",0
npc_7_talk:
    DEFB "You came! Kiss me and free me! But",13,10
    DEFB "first... flowers for your princess?",0
npc_7_wants:
    DEFB "MOONFLOWER",0
npc_7_gives:
    DEFB "PRINCESS KISS",0
npc_8_name:
    DEFB "CURSED PRIEST",0
npc_8_desc:
    DEFB "Skeleton of the jealous priest. Dark",13,10
    DEFB "energy swirls around.",0
npc_8_talk:
    DEFB "She was meant for ME! The curse",13,10
    DEFB "stays... unless you have the jade",13,10
    DEFB "amulet!",0
npc_8_wants:
    DEFB "RITUAL DAGGER",0
npc_8_gives:
    DEFB "CURSE FRAGMENT",0

; === DEADLY ROOMS ===
deadly_rooms:
    DEFB 3
    DEFB 7
    DEFB 9
    DEFB 12
    DEFB 19

death_msg_ptrs:
    DEFW death_msg_0
    DEFW death_msg_1
    DEFW death_msg_2
    DEFW death_msg_3
    DEFW death_msg_4

survival_item_ptrs:
    DEFW survival_0
    DEFW survival_1
    DEFW survival_2
    DEFW survival_3
    DEFW survival_4

death_msg_0:
    DEFB "You try to swim! The crocodiles are",13,10
    DEFB "faster!",0
survival_0:
    DEFB "ROPE",0
death_msg_1:
    DEFB "The jaguar pounces! Its claws tear",13,10
    DEFB "through you!",0
survival_1:
    DEFB "RAW MEAT",0
death_msg_2:
    DEFB "You sink into quicksand! Darkness",13,10
    DEFB "takes you!",0
survival_2:
    DEFB "WOODEN PLANK",0
death_msg_3:
    DEFB "The serpents strike! Venom burns",13,10
    DEFB "through you!",0
survival_3:
    DEFB "SNAKE CHARM",0
death_msg_4:
    DEFB "Dark magic engulfs you! Your soul is",13,10
    DEFB "torn away!",0
survival_4:
    DEFB "JADE AMULET",0

; ========================================
; VARIABLES (RAM)
; ========================================

; === SAVE DATA BLOCK START ===
; All game state that needs to be saved
save_data_start:
current_room:
    DEFS 1
score:
    DEFS 2
inv_count:
    DEFS 1
inventory:
    DEFS MAX_INV
item_locations:
    DEFS NUM_ITEMS
item_is_open:
    DEFS NUM_ITEMS
item_parents:
    DEFS NUM_ITEMS
npc_locations:
    DEFS NUM_NPCS
npc_got_gift:
    DEFS NUM_NPCS
room_unlocked:
    DEFS NUM_ROOMS*6
save_data_end:
; === SAVE DATA BLOCK END ===

; === TEMPORARY VARIABLES (not saved) ===
refresh_flag:
    DEFS 1
temp_dir:
    DEFS 1
temp_room:
    DEFS 1
temp_target:
    DEFS 1
temp_container:
    DEFS 1
temp_ptr:
    DEFS 2
temp_ptr2:
    DEFS 2
temp_ptr3:
    DEFS 2
found_item:
    DEFS 1
found_inv_slot:
    DEFS 1
found_npc:
    DEFS 1
input_len:
    DEFS 1
input_buffer:
    DEFS INPUT_MAX+1

; === FILENAME INPUT ===
filename_len:
    DEFS 1
filename_buf:
    DEFS 16              ; Max 8 chars + some padding

; === CASSETTE BUFFER (2KB needed by firmware) ===
cas_buffer:
    DEFS 2048
; === ORIGINAL STACK POINTER ===
original_sp:
    DEFS 2

; === END ===

Ist noch gründlich zu testen!
https://cpcwiki.de
Dein Deutsches CPCWiki!

TFM

Quote from: Devilmarkus on 10. February 2026, 21:35:50Erzeugt jetzt auch Assemblercode:

; ========================================
; THE FORGOTTEN MAYA PRINCESS
; By Devil-System
; Z80 Assembly for Amstrad CPC
; ========================================

; === AMSTRAD CPC FIRMWARE CALLS ===
TXT_OUTPUT      EQU &BB5A
KM_WAIT_CHAR    EQU &BB06
SCR_SET_MODE    EQU &BC0E
TXT_CLEAR       EQU &BB6C
TXT_SET_PEN     EQU &BB90
TXT_SET_PAPER   EQU &BB96
SCR_SET_INK     EQU &BC32
SCR_SET_BORDER  EQU &BC38
TXT_CUR_ON      EQU &BB81
TXT_CUR_OFF     EQU &BB84
CAS_IN_OPEN     EQU &BC77
CAS_IN_DIRECT   EQU &BC83
CAS_IN_CLOSE    EQU &BC7A
CAS_OUT_OPEN    EQU &BC8C
CAS_OUT_DIRECT  EQU &BC98
CAS_OUT_CLOSE   EQU &BC8F

; === GAME CONSTANTS ===
NUM_ROOMS       EQU 25
NUM_ITEMS       EQU 32
NUM_NPCS        EQU 8
NUM_DEADLY      EQU 5
MAX_INV         EQU 20
INV_LIMIT       EQU 8
START_ROOM      EQU 1
WIN_ROOM        EQU 25
WIN_SCORE       EQU 0
WIN_ITEMS_COUNT EQU 1
INPUT_MAX       EQU 78

    ORG &4000

; ========================================
; PROGRAM START
; ========================================
start:
    LD (original_sp),SP  ; Save stack for clean exit
    ; Set Mode 1 (40 columns)
    LD A,1
    CALL SCR_SET_MODE

    ; Set colors: Ink 0 = black, Ink 1 = yellow
    LD A,0
    LD B,0
    LD C,0
    CALL SCR_SET_INK
    LD A,1
    LD B,24
    LD C,24
    CALL SCR_SET_INK
    LD B,0
    LD C,0
    CALL SCR_SET_BORDER

    ; Initialize game state
    LD A,START_ROOM
    LD (current_room),A
    XOR A
    LD (score),A
    LD (score+1),A
    LD (inv_count),A
    LD A,1
    LD (refresh_flag),A

    ; Initialize items and NPCs
    CALL init_data

    ; Show title screen
    CALL show_title

; === MAIN GAME LOOP ===
main_loop:
    LD A,(refresh_flag)
    OR A
    JR Z,main_no_refresh
    CALL show_room
    XOR A
    LD (refresh_flag),A
main_no_refresh:
    CALL get_input
    CALL parse_command
    JR main_loop

; ========== SHOW TITLE SCREEN ==========
show_title:
    CALL TXT_CLEAR
    CALL print_nl
    CALL print_nl
    LD HL,str_title
    CALL print_str
    CALL print_nl
    CALL print_nl
    LD HL,str_by
    CALL print_str
    LD HL,str_author
    CALL print_str
    CALL print_nl
    CALL print_nl
    CALL print_nl
    CALL wait_key
    CALL TXT_CLEAR
    RET

; ========================================
; SHOW CURRENT ROOM
; ========================================
show_room:
    CALL TXT_CLEAR
    CALL print_nl

    ; Get and print room name
    LD A,(current_room)
    CALL get_room_name_ptr
    CALL print_str
    CALL print_nl

    ; Print separator
    LD B,40
show_room_sep:
    LD A,'*'
    CALL TXT_OUTPUT
    DJNZ show_room_sep
    CALL print_nl

    ; Get and print room description
    LD A,(current_room)
    CALL get_room_desc_ptr
    CALL print_str
    CALL print_nl
    CALL print_nl

    ; Show items in room
    CALL show_items_here

    ; Show NPCs in room
    CALL show_npcs_here

    ; Show exits
    CALL show_exits

    ; Show score
    LD HL,msg_score
    CALL print_str
    LD HL,(score)
    CALL print_num
    CALL print_nl
    RET

; === SHOW ITEMS IN CURRENT ROOM ===
show_items_here:
    LD A,NUM_ITEMS
    OR A
    RET Z
    LD B,A
    LD C,1                ; item index
sih_loop:
    PUSH BC
    ; Get item location
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A               ; D = item location
    ; Check if in current room
    LD A,(current_room)
    CP D
    JR NZ,sih_next
    ; Check if item is in container
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR NZ,sih_next       ; in container, skip
    ; Print 'You see: '
    LD HL,msg_yousee
    CALL print_str
    ; Print item name
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    CALL print_str
    CALL print_nl
sih_next:
    POP BC
    INC C
    DJNZ sih_loop
    RET

; === SHOW NPCS IN CURRENT ROOM ===
show_npcs_here:
    LD A,NUM_NPCS
    OR A
    RET Z
    LD B,A
    LD C,1
snh_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,npc_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JR NZ,snh_next
    LD HL,msg_yousee
    CALL print_str
    POP BC
    PUSH BC
    LD A,C
    CALL get_npc_name_ptr
    CALL print_str
    CALL print_nl
snh_next:
    POP BC
    INC C
    DJNZ snh_loop
    RET

; === SHOW AVAILABLE EXITS ===
show_exits:
    LD HL,msg_exits
    CALL print_str
    LD A,(current_room)
    LD (temp_room),A     ; save room
    LD E,0               ; exits found flag

    ; Check North (direction 1)
    LD A,(temp_room)
    LD B,1
    CALL get_exit
    OR A
    JP Z,se_east
    LD HL,str_dir_n
    CALL print_str
    LD E,1
se_east:
    LD A,(temp_room)
    LD B,2
    CALL get_exit
    OR A
    JP Z,se_south
    LD HL,str_dir_e
    CALL print_str
    LD E,1
se_south:
    LD A,(temp_room)
    LD B,3
    CALL get_exit
    OR A
    JP Z,se_west
    LD HL,str_dir_s
    CALL print_str
    LD E,1
se_west:
    LD A,(temp_room)
    LD B,4
    CALL get_exit
    OR A
    JP Z,se_up
    LD HL,str_dir_w
    CALL print_str
    LD E,1
se_up:
    LD A,(temp_room)
    LD B,5
    CALL get_exit
    OR A
    JP Z,se_down
    LD HL,str_dir_u
    CALL print_str
    LD E,1
se_down:
    LD A,(temp_room)
    LD B,6
    CALL get_exit
    OR A
    JP Z,se_done
    LD HL,str_dir_d
    CALL print_str
    LD E,1
se_done:
    LD A,E
    OR A
    JP NZ,se_has_exits
    LD HL,msg_none
    CALL print_str
se_has_exits:
    CALL print_nl
    RET

; ========================================
; GET PLAYER INPUT
; ========================================
get_input:
    CALL print_nl
    LD A,'>'
    CALL TXT_OUTPUT
    LD A,' '
    CALL TXT_OUTPUT
    CALL TXT_CUR_ON

    LD HL,input_buffer
    LD B,0               ; char count
gi_loop:
    CALL KM_WAIT_CHAR
    CP 13                ; Enter
    JR Z,gi_done
    CP 127               ; Delete
    JR Z,gi_backspace
    CP 8                 ; Backspace
    JR Z,gi_backspace
    CP 32
    JR C,gi_loop         ; ignore control chars

    ; Check buffer space
    LD C,A
    LD A,B
    CP INPUT_MAX
    JR NC,gi_loop
    LD A,C

    ; Convert to uppercase
    CP 'a'
    JR C,gi_store
    CP 'z'+1
    JR NC,gi_store
    SUB 32
gi_store:
    LD (HL),A
    INC HL
    INC B
    CALL TXT_OUTPUT
    JR gi_loop

gi_backspace:
    LD A,B
    OR A
    JR Z,gi_loop
    DEC HL
    DEC B
    LD A,8
    CALL TXT_OUTPUT
    LD A,' '
    CALL TXT_OUTPUT
    LD A,8
    CALL TXT_OUTPUT
    JR gi_loop

gi_done:
    LD (HL),0            ; null terminate
    LD A,B
    LD (input_len),A
    CALL TXT_CUR_OFF
    CALL print_nl
    RET

; ========================================
; PARSE AND EXECUTE COMMAND
; ========================================
parse_command:
    LD A,(input_len)
    OR A
    RET Z                ; empty input

    ; === MOVEMENT COMMANDS ===
    LD HL,input_buffer
    LD DE,cmd_north
    CALL str_equal
    JR NZ,pc_north_no
    LD A,1
    JP do_move
pc_north_no:
    LD HL,input_buffer
    LD DE,cmd_north1
    CALL str_equal
    JR NZ,pc_north1_no
    LD A,1
    JP do_move
pc_north1_no:
    LD HL,input_buffer
    LD DE,cmd_north2
    CALL str_equal
    JR NZ,pc_north2_no
    LD A,1
    JP do_move
pc_north2_no:
    LD HL,input_buffer
    LD DE,cmd_south
    CALL str_equal
    JR NZ,pc_south_no
    LD A,3
    JP do_move
pc_south_no:
    LD HL,input_buffer
    LD DE,cmd_south1
    CALL str_equal
    JR NZ,pc_south1_no
    LD A,3
    JP do_move
pc_south1_no:
    LD HL,input_buffer
    LD DE,cmd_south2
    CALL str_equal
    JR NZ,pc_south2_no
    LD A,3
    JP do_move
pc_south2_no:
    LD HL,input_buffer
    LD DE,cmd_east
    CALL str_equal
    JR NZ,pc_east_no
    LD A,2
    JP do_move
pc_east_no:
    LD HL,input_buffer
    LD DE,cmd_east1
    CALL str_equal
    JR NZ,pc_east1_no
    LD A,2
    JP do_move
pc_east1_no:
    LD HL,input_buffer
    LD DE,cmd_east2
    CALL str_equal
    JR NZ,pc_east2_no
    LD A,2
    JP do_move
pc_east2_no:
    LD HL,input_buffer
    LD DE,cmd_west
    CALL str_equal
    JR NZ,pc_west_no
    LD A,4
    JP do_move
pc_west_no:
    LD HL,input_buffer
    LD DE,cmd_west1
    CALL str_equal
    JR NZ,pc_west1_no
    LD A,4
    JP do_move
pc_west1_no:
    LD HL,input_buffer
    LD DE,cmd_west2
    CALL str_equal
    JR NZ,pc_west2_no
    LD A,4
    JP do_move
pc_west2_no:
    LD HL,input_buffer
    LD DE,cmd_up
    CALL str_equal
    JR NZ,pc_up_no
    LD A,5
    JP do_move
pc_up_no:
    LD HL,input_buffer
    LD DE,cmd_up1
    CALL str_equal
    JR NZ,pc_up1_no
    LD A,5
    JP do_move
pc_up1_no:
    LD HL,input_buffer
    LD DE,cmd_up2
    CALL str_equal
    JR NZ,pc_up2_no
    LD A,5
    JP do_move
pc_up2_no:
    LD HL,input_buffer
    LD DE,cmd_up3
    CALL str_equal
    JR NZ,pc_up3_no
    LD A,5
    JP do_move
pc_up3_no:
    LD HL,input_buffer
    LD DE,cmd_down
    CALL str_equal
    JR NZ,pc_down_no
    LD A,6
    JP do_move
pc_down_no:
    LD HL,input_buffer
    LD DE,cmd_down1
    CALL str_equal
    JR NZ,pc_down1_no
    LD A,6
    JP do_move
pc_down1_no:
    LD HL,input_buffer
    LD DE,cmd_down2
    CALL str_equal
    JR NZ,pc_down2_no
    LD A,6
    JP do_move
pc_down2_no:
    LD HL,input_buffer
    LD DE,cmd_down3
    CALL str_equal
    JR NZ,pc_down3_no
    LD A,6
    JP do_move
pc_down3_no:
    LD HL,input_buffer
    LD DE,cmd_down4
    CALL str_equal
    JR NZ,pc_down4_no
    LD A,6
    JP do_move
pc_down4_no:

    ; === LOOK IN COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_lookin
    LD B,4
    CALL str_prefix
    JP NZ,pc_lookin_no
    LD A,5
    JP do_look_in
pc_lookin_no:
    LD HL,input_buffer
    LD DE,cmd_lookin1
    LD B,7
    CALL str_prefix
    JP NZ,pc_lookin1_no
    LD A,8
    JP do_look_in
pc_lookin1_no:
    LD HL,input_buffer
    LD DE,cmd_lookin2
    LD B,15
    CALL str_prefix
    JP NZ,pc_lookin2_no
    LD A,16
    JP do_look_in
pc_lookin2_no:

    ; === LOOK COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_look
    CALL str_equal
    JR NZ,pc_look_no
    LD A,1
    LD (refresh_flag),A
    RET
pc_look_no:
    LD HL,input_buffer
    LD DE,cmd_look1
    CALL str_equal
    JR NZ,pc_look1_no
    LD A,1
    LD (refresh_flag),A
    RET
pc_look1_no:
    LD HL,input_buffer
    LD DE,cmd_look2
    CALL str_equal
    JR NZ,pc_look2_no
    LD A,1
    LD (refresh_flag),A
    RET
pc_look2_no:

    ; === INVENTORY COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_inv
    CALL str_equal
    JR NZ,pc_inv_no
    JP do_inventory
pc_inv_no:
    LD HL,input_buffer
    LD DE,cmd_inv1
    CALL str_equal
    JR NZ,pc_inv1_no
    JP do_inventory
pc_inv1_no:
    LD HL,input_buffer
    LD DE,cmd_inv2
    CALL str_equal
    JR NZ,pc_inv2_no
    JP do_inventory
pc_inv2_no:

    ; === TAKE FROM COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_take
    LD B,4
    CALL str_prefix
    JP NZ,pc_takefrom_no
    ; Check if FROM keyword present
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,pc_takefrom_no
    JP do_take_from
pc_takefrom_no:
    LD HL,input_buffer
    LD DE,cmd_take1
    LD B,3
    CALL str_prefix
    JP NZ,pc_takefrom1_no
    ; Check if FROM keyword present
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,pc_takefrom1_no
    JP do_take_from
pc_takefrom1_no:
    LD HL,input_buffer
    LD DE,cmd_take2
    LD B,4
    CALL str_prefix
    JP NZ,pc_takefrom2_no
    ; Check if FROM keyword present
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,pc_takefrom2_no
    JP do_take_from
pc_takefrom2_no:
    LD HL,input_buffer
    LD DE,cmd_take3
    LD B,7
    CALL str_prefix
    JP NZ,pc_takefrom3_no
    ; Check if FROM keyword present
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,pc_takefrom3_no
    JP do_take_from
pc_takefrom3_no:

    ; === TAKE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_take
    LD B,4
    CALL str_prefix
    JR NZ,pc_take_no
    LD A,5
    JP do_take
pc_take_no:
    LD HL,input_buffer
    LD DE,cmd_take1
    LD B,3
    CALL str_prefix
    JR NZ,pc_take1_no
    LD A,4
    JP do_take
pc_take1_no:
    LD HL,input_buffer
    LD DE,cmd_take2
    LD B,4
    CALL str_prefix
    JR NZ,pc_take2_no
    LD A,5
    JP do_take
pc_take2_no:
    LD HL,input_buffer
    LD DE,cmd_take3
    LD B,7
    CALL str_prefix
    JR NZ,pc_take3_no
    LD A,8
    JP do_take
pc_take3_no:

    ; === DROP COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_drop
    LD B,4
    CALL str_prefix
    JR NZ,pc_drop_no
    LD A,5
    JP do_drop
pc_drop_no:
    LD HL,input_buffer
    LD DE,cmd_drop1
    LD B,8
    CALL str_prefix
    JR NZ,pc_drop1_no
    LD A,9
    JP do_drop
pc_drop1_no:
    LD HL,input_buffer
    LD DE,cmd_drop2
    LD B,5
    CALL str_prefix
    JR NZ,pc_drop2_no
    LD A,6
    JP do_drop
pc_drop2_no:

    ; === EXAMINE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_exam
    LD B,7
    CALL str_prefix
    JR NZ,pc_exam_no
    LD A,8
    JP do_examine
pc_exam_no:
    LD HL,input_buffer
    LD DE,cmd_exam1
    LD B,1
    CALL str_prefix
    JR NZ,pc_exam1_no
    LD A,2
    JP do_examine
pc_exam1_no:
    LD HL,input_buffer
    LD DE,cmd_exam2
    LD B,7
    CALL str_prefix
    JR NZ,pc_exam2_no
    LD A,8
    JP do_examine
pc_exam2_no:
    LD HL,input_buffer
    LD DE,cmd_exam3
    LD B,5
    CALL str_prefix
    JR NZ,pc_exam3_no
    LD A,6
    JP do_examine
pc_exam3_no:
    LD HL,input_buffer
    LD DE,cmd_exam4
    LD B,7
    CALL str_prefix
    JR NZ,pc_exam4_no
    LD A,8
    JP do_examine
pc_exam4_no:

    ; === USE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_use
    LD B,3
    CALL str_prefix
    JR NZ,pc_use_no
    LD A,4
    JP do_use
pc_use_no:
    LD HL,input_buffer
    LD DE,cmd_use1
    LD B,5
    CALL str_prefix
    JR NZ,pc_use1_no
    LD A,6
    JP do_use
pc_use1_no:

    ; === OPEN COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_open
    LD B,4
    CALL str_prefix
    JR NZ,pc_open_no
    LD A,5
    JP do_open
pc_open_no:

    ; === CLOSE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_close
    LD B,5
    CALL str_prefix
    JR NZ,pc_close_no
    LD A,6
    JP do_close
pc_close_no:
    LD HL,input_buffer
    LD DE,cmd_close1
    LD B,4
    CALL str_prefix
    JR NZ,pc_close1_no
    LD A,5
    JP do_close
pc_close1_no:

    ; === TALK COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_talk
    LD B,7
    CALL str_prefix
    JR NZ,pc_talk_no
    LD A,8
    JP do_talk
pc_talk_no:
    LD HL,input_buffer
    LD DE,cmd_talk1
    LD B,8
    CALL str_prefix
    JR NZ,pc_talk1_no
    LD A,9
    JP do_talk
pc_talk1_no:
    LD HL,input_buffer
    LD DE,cmd_talk2
    LD B,6
    CALL str_prefix
    JR NZ,pc_talk2_no
    LD A,7
    JP do_talk
pc_talk2_no:
    LD HL,input_buffer
    LD DE,cmd_talk3
    LD B,6
    CALL str_prefix
    JR NZ,pc_talk3_no
    LD A,7
    JP do_talk
pc_talk3_no:

    ; === GIVE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_give
    LD B,4
    CALL str_prefix
    JR NZ,pc_give_no
    JP do_give
pc_give_no:
    LD HL,input_buffer
    LD DE,cmd_give1
    LD B,5
    CALL str_prefix
    JR NZ,pc_give1_no
    JP do_give
pc_give1_no:
    LD HL,input_buffer
    LD DE,cmd_give2
    LD B,4
    CALL str_prefix
    JR NZ,pc_give2_no
    JP do_give
pc_give2_no:

    ; === HELP COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_help
    CALL str_equal
    JR NZ,pc_help_no
    JP do_help
pc_help_no:
    LD HL,input_buffer
    LD DE,cmd_help1
    CALL str_equal
    JR NZ,pc_help1_no
    JP do_help
pc_help1_no:
    LD HL,input_buffer
    LD DE,cmd_help2
    CALL str_equal
    JR NZ,pc_help2_no
    JP do_help
pc_help2_no:
    LD HL,input_buffer
    LD DE,cmd_help3
    CALL str_equal
    JR NZ,pc_help3_no
    JP do_help
pc_help3_no:

    ; === SCORE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_score
    CALL str_equal
    JR NZ,pc_score_no
    JP do_show_score
pc_score_no:
    LD HL,input_buffer
    LD DE,cmd_score1
    CALL str_equal
    JR NZ,pc_score1_no
    JP do_show_score
pc_score1_no:

    ; === SAVE COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_save
    CALL str_equal
    JP NZ,pc_save_no
    JP do_save
pc_save_no:
    LD HL,input_buffer
    LD DE,cmd_save1
    CALL str_equal
    JP NZ,pc_save1_no
    JP do_save
pc_save1_no:

    ; === LOAD COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_load
    CALL str_equal
    JP NZ,pc_load_no
    JP do_load
pc_load_no:
    LD HL,input_buffer
    LD DE,cmd_load1
    CALL str_equal
    JP NZ,pc_load1_no
    JP do_load
pc_load1_no:
    LD HL,input_buffer
    LD DE,cmd_load2
    CALL str_equal
    JP NZ,pc_load2_no
    JP do_load
pc_load2_no:

    ; === QUIT COMMAND ===
    LD HL,input_buffer
    LD DE,cmd_quit
    CALL str_equal
    JR NZ,pc_quit_no
    JP do_quit
pc_quit_no:
    LD HL,input_buffer
    LD DE,cmd_quit1
    CALL str_equal
    JR NZ,pc_quit1_no
    JP do_quit
pc_quit1_no:
    LD HL,input_buffer
    LD DE,cmd_quit2
    CALL str_equal
    JR NZ,pc_quit2_no
    JP do_quit
pc_quit2_no:
    LD HL,input_buffer
    LD DE,cmd_quit3
    CALL str_equal
    JR NZ,pc_quit3_no
    JP do_quit
pc_quit3_no:

    ; Unknown command
    LD HL,msg_unknown
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: MOVE
; A = direction (1-6) on entry
; ========================================
do_move:
    LD (temp_dir),A      ; save direction
    LD B,A               ; B = direction for get_exit
    LD A,(current_room)  ; A = room
    CALL get_exit        ; A = target room (0=none)
    OR A
    JP NZ,dm_can_go
    LD HL,msg_cantgo
    CALL print_str
    CALL print_nl
    RET
dm_can_go:
    LD (temp_target),A   ; save target room
    ; Check if exit has requirement
    LD A,(current_room)
    LD HL,temp_dir
    LD B,(HL)            ; B = direction
    CALL get_exit_requirement
    ; HL = requirement string pointer
    LD A,(HL)
    OR A                 ; empty = no requirement
    JP Z,dm_unlocked     ; no requirement, can pass
    ; Has requirement - check if unlocked
    LD A,(current_room)
    LD HL,temp_dir
    LD B,(HL)
    CALL check_exit_unlocked
    OR A
    JP NZ,dm_unlocked
    ; Blocked!
    LD HL,msg_blocked
    CALL print_str
    CALL print_nl
    RET
dm_unlocked:
    LD A,(temp_target)
    LD (current_room),A
    LD A,1
    LD (refresh_flag),A
    CALL check_death
    CALL check_win
    RET

; ========================================
; ACTION: TAKE
; A = offset in input buffer to object name
; ========================================
do_take:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dt_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dt_have_name:
    LD A,NUM_ITEMS
    OR A
    JP NZ,dt_search
    LD HL,msg_nothere
    CALL print_str
    CALL print_nl
    RET
dt_search:
    LD B,A
    LD C,1
dt_loop:
    PUSH BC
    ; Check item location
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,dt_next
    ; Check not in container
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP NZ,dt_next
    ; Check not a fixed container
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    CP 2
    JP Z,dt_next
    ; Check name matches
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dt_next
    ; Found it! Check inventory space
    LD A,(inv_count)
    CP INV_LIMIT
    JR C,dt_can_take
    POP BC
    LD HL,msg_full
    CALL print_str
    CALL print_nl
    RET
dt_can_take:
    POP BC
    LD A,C
    CALL add_to_inventory
    ; Set item location to 0
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD (HL),0
    ; Add points
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_points
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR Z,dt_no_points
    LD HL,(score)
    LD B,0
    LD C,A
    ADD HL,BC
    LD (score),HL
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_points
    CALL add_a_to_hl
    LD (HL),0
dt_no_points:
    POP BC
    LD HL,msg_taken
    CALL print_str
    CALL print_nl
    RET
dt_next:
    POP BC
    INC C
    DEC B
    JP NZ,dt_loop
    LD HL,msg_nothere
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: DROP
; ========================================
do_drop:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dd_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dd_have_name:
    LD A,(inv_count)
    OR A
    JP NZ,dd_search
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET
dd_search:
    LD B,A
    LD C,1
dd_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dd_next
    PUSH AF
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    POP AF
    JP NZ,dd_next
    ; Found - set location to current room
    LD D,A
    LD A,D
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(current_room)
    LD (HL),A
    POP BC
    LD A,C
    CALL remove_from_inventory
    LD HL,msg_dropped
    CALL print_str
    CALL print_nl
    RET
dd_next:
    POP BC
    INC C
    DEC B
    JP NZ,dd_loop
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: EXAMINE
; ========================================
do_examine:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dx_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dx_have_name:
    LD A,NUM_ITEMS
    OR A
    JP Z,dx_check_npcs
    LD B,A
    LD C,1
dx_item_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP Z,dx_item_accessible
    LD A,D
    OR A
    JP NZ,dx_item_next
dx_item_accessible:
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dx_item_next
    POP BC
    LD A,C
    CALL get_item_desc_ptr
    LD A,(HL)
    OR A
    JR NZ,dx_has_desc
    LD HL,msg_nothing_special
dx_has_desc:
    CALL print_nl
    CALL print_str
    CALL print_nl
    CALL wait_key
    RET
dx_item_next:
    POP BC
    INC C
    DEC B
    JP NZ,dx_item_loop
dx_check_npcs:
    LD A,NUM_NPCS
    OR A
    JP Z,dx_not_found
    LD B,A
    LD C,1
dx_npc_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,npc_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,dx_npc_next
    POP BC
    PUSH BC
    LD A,C
    CALL get_npc_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dx_npc_next
    POP BC
    LD A,C
    CALL get_npc_desc_ptr
    CALL print_nl
    CALL print_str
    CALL print_nl
    CALL wait_key
    RET
dx_npc_next:
    POP BC
    INC C
    DEC B
    JP NZ,dx_npc_loop
dx_not_found:
    LD HL,msg_nothere
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: USE (unlock exits)
; ========================================
do_use:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,du_have_name
    LD HL,msg_usewhat
    CALL print_str
    CALL print_nl
    RET
du_have_name:
    LD A,(inv_count)
    OR A
    JP NZ,du_search
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET
du_search:
    LD B,A
    LD C,1
du_inv_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,du_inv_next
    PUSH AF
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    POP AF
    JP NZ,du_inv_next
    ; Found item in inventory - try to unlock exits
    LD (found_item),A
    POP BC
    JP du_try_unlock
du_inv_next:
    POP BC
    INC C
    DEC B
    JP NZ,du_inv_loop
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET

du_try_unlock:
    ; Check all 6 directions
    LD B,6
    LD C,1
du_dir_loop:
    PUSH BC
    ; Get required item for this exit
    LD A,(current_room)
    LD B,C
    CALL get_exit_requirement
    LD A,(HL)
    OR A
    JP Z,du_dir_next
    ; Compare with our item name
    PUSH HL
    LD A,(found_item)
    CALL get_item_name_ptr
    POP DE
    CALL str_equal
    JP NZ,du_dir_next
    ; Check if already unlocked
    POP BC
    PUSH BC
    LD A,(current_room)
    LD B,C
    CALL check_exit_unlocked
    OR A
    JP NZ,du_dir_next
    ; Unlock it!
    POP BC
    LD A,(current_room)
    LD B,C
    CALL set_exit_unlocked
    LD HL,msg_unlocked
    CALL print_str
    CALL print_nl
    RET
du_dir_next:
    POP BC
    INC C
    DEC B
    JP NZ,du_dir_loop
    LD HL,msg_nothing
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: OPEN CONTAINER
; ========================================
do_open:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dop_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dop_have_name:
    LD A,NUM_ITEMS
    OR A
    JP Z,dop_not_container
    LD B,A
    LD C,1
dop_loop:
    PUSH BC
    ; Is it a container?
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dop_next
    ; Is it here or carried?
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP Z,dop_accessible
    LD A,D
    OR A
    JP NZ,dop_next
dop_accessible:
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dop_next
    ; Open it
    POP BC
    LD A,C
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD (HL),1
    LD HL,msg_opened
    CALL print_str
    CALL print_nl
    RET
dop_next:
    POP BC
    INC C
    DEC B
    JP NZ,dop_loop
dop_not_container:
    LD HL,msg_notcontainer
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: CLOSE CONTAINER
; ========================================
do_close:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dc_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dc_have_name:
    LD A,NUM_ITEMS
    OR A
    JP Z,dc_not_container
    LD B,A
    LD C,1
dc_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dc_next
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dc_next
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dc_next
    POP BC
    LD A,C
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD (HL),0
    LD HL,msg_closed
    CALL print_str
    CALL print_nl
    RET
dc_next:
    POP BC
    INC C
    DEC B
    JP NZ,dc_loop
dc_not_container:
    LD HL,msg_notcontainer
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: LOOK IN CONTAINER
; A = offset to container name
; ========================================
do_look_in:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dli_have_name
    LD HL,msg_what
    CALL print_str
    CALL print_nl
    RET
dli_have_name:
    LD A,NUM_ITEMS
    OR A
    JP Z,dli_not_found
    LD B,A
    LD C,1
dli_loop:
    PUSH BC
    ; Is it a container?
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dli_next
    ; Is it here or carried?
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP Z,dli_accessible
    LD A,D
    OR A
    JP NZ,dli_next
dli_accessible:
    ; Check name matches
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dli_next
    ; Found container - check if open
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP NZ,dli_show_contents
    ; Closed
    POP BC
    LD HL,msg_closed
    CALL print_str
    CALL print_nl
    RET
dli_show_contents:
    POP BC
    LD A,C
    LD (temp_container),A
    CALL print_nl
    LD HL,msg_inside
    CALL print_str
    CALL print_nl
    ; Search for items with parent = container
    LD A,NUM_ITEMS
    OR A
    JP Z,dli_empty
    LD B,A
    LD C,1
    LD E,0
dli_content_loop:
    PUSH BC
    PUSH DE
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(temp_container)
    CP D
    JP NZ,dli_content_next
    ; Found item in container
    LD HL,msg_indent
    CALL print_str
    POP DE
    POP BC
    PUSH BC
    PUSH DE
    LD A,C
    CALL get_item_name_ptr
    CALL print_str
    CALL print_nl
    POP DE
    LD E,1
    PUSH DE
dli_content_next:
    POP DE
    POP BC
    INC C
    DEC B
    JP NZ,dli_content_loop
    LD A,E
    OR A
    JP NZ,dli_done
dli_empty:
    LD HL,msg_empty_c
    CALL print_str
    CALL print_nl
dli_done:
    CALL wait_key
    RET
dli_next:
    POP BC
    INC C
    DEC B
    JP NZ,dli_loop
dli_not_found:
    LD HL,msg_notcontainer
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: TAKE ITEM FROM CONTAINER
; ========================================
do_take_from:
    ; Parse 'TAKE item FROM container'
    LD HL,input_buffer
    LD DE,str_kw_from
    CALL str_find
    JP NC,dtf_syntax_err
    LD (temp_ptr2),HL    ; position of ' FROM '
    ; Get item name (after TAKE, before FROM)
    LD HL,input_buffer
    LD B,5               ; skip 'TAKE '
dtf_skip:
    INC HL
    DJNZ dtf_skip
    LD (temp_ptr),HL     ; item name start
    ; Get container name (after ' FROM ')
    LD HL,(temp_ptr2)
    LD B,6               ; skip ' FROM '
dtf_skip2:
    INC HL
    DJNZ dtf_skip2
    LD A,(HL)
    OR A
    JP Z,dtf_syntax_err
    LD (temp_ptr3),HL    ; container name start

    ; Find container in room or inventory
    LD A,NUM_ITEMS
    OR A
    JP Z,dtf_no_container
    LD B,A
    LD C,1
dtf_cont_loop:
    PUSH BC
    ; Is it a container?
    LD A,C
    DEC A
    LD HL,item_is_container
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dtf_cont_next
    ; Is it here or carried?
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP Z,dtf_cont_accessible
    LD A,D
    OR A                 ; 0 = carried
    JP NZ,dtf_cont_next
dtf_cont_accessible:
    ; Check container name matches
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr3)
    CALL str_contains
    JP NZ,dtf_cont_next
    ; Found container!
    POP BC
    LD A,C
    LD (temp_container),A
    JP dtf_check_open
dtf_cont_next:
    POP BC
    INC C
    DEC B
    JP NZ,dtf_cont_loop
dtf_no_container:
    LD HL,msg_notcontainer
    CALL print_str
    CALL print_nl
    RET

dtf_check_open:
    ; Check if container is open
    LD A,(temp_container)
    DEC A
    LD HL,item_is_open
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP NZ,dtf_find_item
    LD HL,msg_closed
    CALL print_str
    CALL print_nl
    RET

dtf_find_item:
    ; Find item in container
    LD A,NUM_ITEMS
    LD B,A
    LD C,1
dtf_item_loop:
    PUSH BC
    ; Check if item is in this container
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(temp_container)
    CP D
    JP NZ,dtf_item_next
    ; Check item name matches
    POP BC
    PUSH BC
    LD A,C
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_partial_match
    JP NZ,dtf_item_next
    ; Found item! Check inventory space
    LD A,(inv_count)
    CP INV_LIMIT
    JP C,dtf_can_take
    POP BC
    LD HL,msg_full
    CALL print_str
    CALL print_nl
    RET
dtf_can_take:
    POP BC
    ; Add to inventory
    LD A,C
    CALL add_to_inventory
    ; Remove from container (set parent to 0)
    LD A,C
    DEC A
    LD HL,item_parents
    CALL add_a_to_hl
    LD (HL),0
    ; Set location to 0 (carried)
    LD A,C
    DEC A
    LD HL,item_locations
    CALL add_a_to_hl
    LD (HL),0
    ; Add points
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_points
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dtf_no_points
    LD HL,(score)
    LD B,0
    LD C,A
    ADD HL,BC
    LD (score),HL
    ; Clear points
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,item_points
    CALL add_a_to_hl
    LD (HL),0
dtf_no_points:
    POP BC
    LD HL,msg_taken
    CALL print_str
    CALL print_nl
    RET
dtf_item_next:
    POP BC
    INC C
    DEC B
    JP NZ,dtf_item_loop
    LD HL,msg_nothere
    CALL print_str
    CALL print_nl
    RET
dtf_syntax_err:
    LD HL,msg_takefrom_syntax
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: TALK TO NPC
; ========================================
do_talk:
    LD HL,input_buffer
    CALL add_a_to_hl
    LD (temp_ptr),HL
    LD A,(HL)
    OR A
    JP NZ,dtk_have_name
    LD HL,msg_talkto
    CALL print_str
    CALL print_nl
    RET
dtk_have_name:
    LD A,NUM_NPCS
    OR A
    JP Z,dtk_noone
    LD B,A
    LD C,1
dtk_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,npc_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,dtk_next
    POP BC
    PUSH BC
    LD A,C
    CALL get_npc_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_contains
    JP NZ,dtk_next
    POP BC
    LD A,C
    CALL get_npc_talk_ptr
    LD A,(HL)
    OR A
    JR NZ,dtk_has_dialog
    LD HL,msg_silence
    CALL print_str
    CALL print_nl
    RET
dtk_has_dialog:
    CALL print_nl
    LD A,34              ; quote char
    CALL TXT_OUTPUT
    CALL print_str
    LD A,34
    CALL TXT_OUTPUT
    CALL print_nl
    CALL wait_key
    RET
dtk_next:
    POP BC
    INC C
    DEC B
    JP NZ,dtk_loop
dtk_noone:
    LD HL,msg_noone
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: GIVE ITEM TO NPC
; ========================================
do_give:
    ; Parse 'GIVE item TO npc'
    LD HL,input_buffer
    LD DE,str_kw_to
    CALL str_find
    JP NC,dg_syntax_err
    LD (temp_ptr2),HL
    ; Get item name start (after GIVE )
    LD HL,input_buffer
    LD B,5               ; skip 'GIVE '
dg_skip:
    INC HL
    DJNZ dg_skip
    LD (temp_ptr),HL
    ; Get NPC name (after ' TO ')
    LD HL,(temp_ptr2)
    LD B,4               ; skip ' TO '
dg_skip2:
    INC HL
    DJNZ dg_skip2
    LD A,(HL)
    OR A
    JP Z,dg_syntax_err
    LD (temp_ptr3),HL

    ; Find item in inventory
    LD A,(inv_count)
    OR A
    JP Z,dg_no_item
    LD B,A
    LD C,1
dg_item_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP Z,dg_item_next
    PUSH AF
    CALL get_item_name_ptr
    EX DE,HL
    LD HL,(temp_ptr)
    CALL str_partial_match
    POP AF
    JP NZ,dg_item_next
    LD (found_item),A
    POP BC
    LD A,C
    LD (found_inv_slot),A
    JP dg_find_npc
dg_item_next:
    POP BC
    INC C
    DEC B
    JP NZ,dg_item_loop
dg_no_item:
    LD HL,msg_donthave
    CALL print_str
    CALL print_nl
    RET

dg_find_npc:
    LD A,NUM_NPCS
    OR A
    JP Z,dg_no_npc
    LD B,A
    LD C,1
dg_npc_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,npc_locations
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,dg_npc_next
    POP BC
    PUSH BC
    LD A,C
    CALL get_npc_name_ptr
    EX DE,HL
    LD HL,(temp_ptr3)
    CALL str_contains
    JP NZ,dg_npc_next
    POP BC
    LD A,C
    LD (found_npc),A
    JP dg_do_give
dg_npc_next:
    POP BC
    INC C
    DEC B
    JP NZ,dg_npc_loop
dg_no_npc:
    LD HL,msg_noone
    CALL print_str
    CALL print_nl
    RET

dg_do_give:
    ; Check if NPC already got gift
    LD A,(found_npc)
    DEC A
    LD HL,npc_got_gift
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JP NZ,dg_nothing
    ; Check if NPC wants this item
    LD A,(found_npc)
    CALL get_npc_wants_ptr
    LD A,(HL)
    OR A
    JP Z,dg_no_want
    EX DE,HL
    LD A,(found_item)
    CALL get_item_name_ptr
    CALL str_equal
    JP NZ,dg_no_want
    ; NPC accepts!
    LD A,(found_inv_slot)
    CALL remove_from_inventory
    ; Mark NPC
    LD A,(found_npc)
    DEC A
    LD HL,npc_got_gift
    CALL add_a_to_hl
    LD (HL),1
    ; Add points
    LD A,(found_npc)
    DEC A
    LD HL,npc_points
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR Z,dg_no_points
    LD HL,(score)
    LD B,0
    LD C,A
    ADD HL,BC
    LD (score),HL
dg_no_points:
    LD HL,msg_thanks
    CALL print_str
    CALL print_nl
    ; Check reward
    LD A,(found_npc)
    CALL get_npc_gives_ptr
    LD A,(HL)
    OR A
    RET Z
    LD HL,msg_got
    CALL print_str
    LD A,(found_npc)
    CALL get_npc_gives_ptr
    CALL print_str
    CALL print_nl
    RET
dg_nothing:
    LD HL,msg_nothing
    CALL print_str
    CALL print_nl
    RET
dg_no_want:
    LD HL,msg_nowant
    CALL print_str
    CALL print_nl
    RET
dg_syntax_err:
    LD HL,msg_give_syntax
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: SHOW INVENTORY
; ========================================
do_inventory:
    CALL print_nl
    LD HL,msg_inventory
    CALL print_str
    CALL print_nl
    LD A,(inv_count)
    OR A
    JR NZ,di_show_items
    LD HL,msg_empty
    CALL print_str
    CALL print_nl
    CALL wait_key
    RET
di_show_items:
    LD B,A
    LD C,1
di_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR Z,di_next
    LD HL,msg_indent
    CALL print_str
    POP BC
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    CALL get_item_name_ptr
    CALL print_str
    CALL print_nl
di_next:
    POP BC
    INC C
    DJNZ di_loop
    CALL wait_key
    RET

; ========================================
; ACTION: SHOW HELP
; ========================================
do_help:
    CALL print_nl
    LD HL,str_help_text
    CALL print_str
    CALL wait_key
    RET

; ========================================
; ACTION: SHOW SCORE
; ========================================
do_show_score:
    CALL print_nl
    LD HL,msg_score
    CALL print_str
    LD HL,(score)
    CALL print_num
    CALL print_nl
    CALL wait_key
    RET

; ========================================
; ACTION: QUIT GAME
; ========================================
do_quit:
    CALL print_nl
    LD HL,msg_bye
    CALL print_str
    CALL print_nl
    CALL wait_key
    ; Clean return to BASIC
    LD SP,(original_sp)  ; Restore original stack
    RET                  ; Return to BASIC

; ========================================
; ACTION: SAVE GAME
; ========================================
do_save:
    CALL print_nl
    LD HL,msg_filename
    CALL print_str
    ; Get filename from user
    CALL get_filename
    LD A,(filename_len)
    OR A
    JP Z,do_save_cancel
    ; Open file for writing
    LD B,A               ; B = filename length
    LD HL,filename_buf
    LD DE,cas_buffer     ; 2KB buffer for cassette
    CALL CAS_OUT_OPEN
    JP NC,do_save_error
    ; Save game data
    LD HL,save_data_start
    LD DE,save_data_end-save_data_start
    LD BC,0              ; execution address (not used)
    LD A,2               ; file type = binary
    CALL CAS_OUT_DIRECT
    JP NC,do_save_error_close
    ; Close file
    CALL CAS_OUT_CLOSE
    LD HL,msg_saved
    CALL print_str
    CALL print_nl
    RET
do_save_error_close:
    CALL CAS_OUT_CLOSE
do_save_error:
    LD HL,msg_save_error
    CALL print_str
    CALL print_nl
    RET
do_save_cancel:
    LD HL,msg_cancelled
    CALL print_str
    CALL print_nl
    RET

; ========================================
; ACTION: LOAD GAME
; ========================================
do_load:
    CALL print_nl
    LD HL,msg_filename
    CALL print_str
    ; Get filename from user
    CALL get_filename
    LD A,(filename_len)
    OR A
    JP Z,do_load_cancel
    ; Open file for reading
    LD B,A               ; B = filename length
    LD HL,filename_buf
    LD DE,cas_buffer     ; 2KB buffer for cassette
    CALL CAS_IN_OPEN
    JP NC,do_load_error
    ; Load game data
    LD HL,save_data_start
    CALL CAS_IN_DIRECT
    JP NC,do_load_error_close
    ; Close file
    CALL CAS_IN_CLOSE
    ; Set refresh flag
    LD A,1
    LD (refresh_flag),A
    LD HL,msg_loaded
    CALL print_str
    CALL print_nl
    RET
do_load_error_close:
    CALL CAS_IN_CLOSE
do_load_error:
    LD HL,msg_load_error
    CALL print_str
    CALL print_nl
    RET
do_load_cancel:
    LD HL,msg_cancelled
    CALL print_str
    CALL print_nl
    RET

; ========================================
; GET FILENAME (max 8 chars)
; ========================================
get_filename:
    CALL TXT_CUR_ON
    LD HL,filename_buf
    LD B,0               ; char count
gfn_loop:
    CALL KM_WAIT_CHAR
    CP 13                ; Enter
    JR Z,gfn_done
    CP 27                ; Escape
    JR Z,gfn_escape
    CP 127               ; Delete
    JR Z,gfn_backspace
    CP 8                 ; Backspace
    JR Z,gfn_backspace
    CP 32                ; Must be >= space
    JR C,gfn_loop
    CP '.'               ; No dots allowed
    JR Z,gfn_loop
    ; Check max length (8 chars)
    LD C,A
    LD A,B
    CP 8
    JR NC,gfn_loop       ; Already 8 chars
    LD A,C
    ; Convert to uppercase
    CP 'a'
    JR C,gfn_store
    CP 'z'+1
    JR NC,gfn_store
    SUB 32
gfn_store:
    LD (HL),A
    INC HL
    INC B
    CALL TXT_OUTPUT
    JR gfn_loop
gfn_backspace:
    LD A,B
    OR A
    JR Z,gfn_loop
    DEC HL
    DEC B
    LD A,8
    CALL TXT_OUTPUT
    LD A,' '
    CALL TXT_OUTPUT
    LD A,8
    CALL TXT_OUTPUT
    JR gfn_loop
gfn_escape:
    LD B,0               ; Return empty = cancelled
gfn_done:
    LD A,B
    LD (filename_len),A
    CALL TXT_CUR_OFF
    CALL print_nl
    RET

; ========================================
; CHECK IF ROOM IS DEADLY
; ========================================
check_death:
    LD A,NUM_DEADLY
    OR A
    RET Z
    LD B,A
    LD C,0
cd_loop:
    PUSH BC
    LD A,C
    LD HL,deadly_rooms
    CALL add_a_to_hl
    LD A,(HL)
    LD D,A
    LD A,(current_room)
    CP D
    JP NZ,cd_next
    ; In deadly room - check survival item
    POP BC
    PUSH BC
    LD A,C
    CALL get_survival_item_ptr
    LD A,(HL)
    OR A
    JP Z,cd_die
    CALL check_have_item
    JP C,cd_next
cd_die:
    CALL TXT_CLEAR
    CALL print_nl
    POP BC
    LD A,C
    CALL get_death_msg_ptr
    CALL print_str
    CALL print_nl
    CALL print_nl
    LD HL,msg_gameover
    CALL print_str
    CALL print_nl
    CALL wait_key
    LD SP,(original_sp)
    RET
cd_next:
    POP BC
    INC C
    DEC B
    JP NZ,cd_loop
    RET

; ========================================
; CHECK WIN CONDITION
; ========================================
check_win:
    LD A,WIN_ROOM
    OR A
    RET Z
    LD B,A
    LD A,(current_room)
    CP B
    RET NZ
    ; Check score
    LD A,WIN_SCORE
    OR A
    JR Z,cw_check_items
    LD HL,(score)
    LD B,0
    LD C,A
    OR A
    SBC HL,BC
    RET C
cw_check_items:
    LD A,WIN_ITEMS_COUNT
    OR A
    JR Z,cw_win
cw_win:
    CALL TXT_CLEAR
    CALL print_nl
    LD HL,str_winmsg
    CALL print_str
    CALL print_nl
    CALL print_nl
    LD HL,msg_youwin
    CALL print_str
    CALL print_nl
    LD HL,msg_score
    CALL print_str
    LD HL,(score)
    CALL print_num
    CALL print_nl
    CALL wait_key
    LD SP,(original_sp)
    RET

; ========================================
; UTILITY FUNCTIONS
; ========================================

; Print null-terminated string at HL
print_str:
    LD A,(HL)
    OR A
    RET Z
    CALL TXT_OUTPUT
    INC HL
    JR print_str

; Print CR/LF
print_nl:
    LD A,13
    CALL TXT_OUTPUT
    LD A,10
    CALL TXT_OUTPUT
    RET

; Print 16-bit number in HL
print_num:
    LD DE,10000
    CALL pn_digit
    LD DE,1000
    CALL pn_digit
    LD DE,100
    CALL pn_digit
    LD DE,10
    CALL pn_digit
    LD A,L
    ADD A,'0'
    CALL TXT_OUTPUT
    RET
pn_digit:
    LD A,'0'-1
pn_loop:
    INC A
    OR A
    SBC HL,DE
    JR NC,pn_loop
    ADD HL,DE
    CALL TXT_OUTPUT
    RET

; Wait for keypress
wait_key:
    LD HL,msg_presskey
    CALL print_str
    CALL KM_WAIT_CHAR
    CALL print_nl
    RET

; Add A to HL
add_a_to_hl:
    ADD A,L
    LD L,A
    RET NC
    INC H
    RET

; Compare strings HL and DE, Z if equal
str_equal:
    PUSH HL
    PUSH DE
seq_loop:
    LD A,(DE)
    CP (HL)
    JR NZ,seq_ne
    OR A
    JR Z,seq_eq
    INC HL
    INC DE
    JR seq_loop
seq_eq:
    POP DE
    POP HL
    XOR A
    RET
seq_ne:
    POP DE
    POP HL
    OR 1
    RET

; Check if HL starts with DE (B chars), Z if match
str_prefix:
    PUSH HL
    PUSH DE
sp_loop:
    LD A,B
    OR A
    JR Z,sp_match
    LD A,(DE)
    CP (HL)
    JR NZ,sp_no
    INC HL
    INC DE
    DEC B
    JR sp_loop
sp_match:
    POP DE
    POP HL
    XOR A
    RET
sp_no:
    POP DE
    POP HL
    OR 1
    RET

; Check if DE contains HL substring, Z if found
str_contains:
    PUSH HL
    PUSH DE
sc_outer:
    LD A,(DE)
    OR A
    JR Z,sc_no
    PUSH HL
    PUSH DE
sc_inner:
    LD A,(HL)
    OR A
    JR Z,sc_yes_pop
    LD A,(DE)
    OR A
    JR Z,sc_no_pop
    CP (HL)
    JR NZ,sc_no_pop
    INC HL
    INC DE
    JR sc_inner
sc_yes_pop:
    POP DE
    POP HL
    POP DE
    POP HL
    XOR A
    RET
sc_no_pop:
    POP DE
    POP HL
    INC DE
    JR sc_outer
sc_no:
    POP DE
    POP HL
    OR 1
    RET

; Partial match - HL until space/TO in DE, Z if found
str_partial_match:
    PUSH HL
    PUSH DE
spm_outer:
    LD A,(DE)
    OR A
    JR Z,spm_no
    PUSH HL
    PUSH DE
spm_inner:
    LD A,(HL)
    OR A
    JR Z,spm_yes_pop
    CP ' '
    JR Z,spm_yes_pop
    LD A,(DE)
    OR A
    JR Z,spm_no_pop
    CP (HL)
    JR NZ,spm_no_pop
    INC HL
    INC DE
    JR spm_inner
spm_yes_pop:
    POP DE
    POP HL
    POP DE
    POP HL
    XOR A
    RET
spm_no_pop:
    POP DE
    POP HL
    INC DE
    JR spm_outer
spm_no:
    POP DE
    POP HL
    OR 1
    RET

; Find DE substring in HL, C flag if found, HL = match pos
str_find:
    LD (sf_de_save),DE    ; Save search string pointer
sf_loop:
    LD A,(HL)
    OR A
    JR Z,sf_no            ; End of string, not found
    PUSH HL               ; Save current position
    LD DE,(sf_de_save)    ; Get search string
sf_cmp:
    LD A,(DE)
    OR A
    JR Z,sf_yes           ; End of search string = found!
    CP (HL)
    JR NZ,sf_cmp_no       ; Mismatch
    INC HL
    INC DE
    JR sf_cmp
sf_yes:
    POP HL                ; HL = position where match started
    SCF                   ; Set carry = found
    RET
sf_cmp_no:
    POP HL                ; Restore position
    INC HL                ; Try next position
    JR sf_loop
sf_no:
    OR A                  ; Clear carry = not found
    RET
sf_de_save:
    DEFW 0                ; Storage for DE

; ========================================
; DATA ACCESS FUNCTIONS
; ========================================

; Get room name pointer for room A
get_room_name_ptr:
    DEC A
    SLA A
    LD HL,room_name_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get room desc pointer for room A
get_room_desc_ptr:
    DEC A
    SLA A
    LD HL,room_desc_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get exit: A=room, B=dir(1-6), returns A=target
get_exit:
    PUSH BC
    DEC A
    LD C,A
    LD A,B
    DEC A
    LD B,A
    ; offset = room * 6 + dir
    LD A,C
    LD H,0
    LD L,A
    ADD HL,HL
    ADD HL,HL
    LD D,0
    LD E,A
    ADD HL,DE
    ADD HL,DE
    LD A,B
    CALL add_a_to_hl
    LD DE,room_exits
    ADD HL,DE
    LD A,(HL)
    POP BC
    RET

; Check exit unlocked: A=room, B=dir, A=1 if open
check_exit_unlocked:
    PUSH BC
    DEC A
    LD C,A
    LD A,B
    DEC A
    LD B,A
    LD A,C
    LD H,0
    LD L,A
    ADD HL,HL
    ADD HL,HL
    LD D,0
    LD E,A
    ADD HL,DE
    ADD HL,DE
    LD A,B
    CALL add_a_to_hl
    LD DE,room_unlocked
    ADD HL,DE
    LD A,(HL)
    POP BC
    RET

; Set exit unlocked: A=room, B=dir
set_exit_unlocked:
    PUSH BC
    DEC A
    LD C,A
    LD A,B
    DEC A
    LD B,A
    LD A,C
    LD H,0
    LD L,A
    ADD HL,HL
    ADD HL,HL
    LD D,0
    LD E,A
    ADD HL,DE
    ADD HL,DE
    LD A,B
    CALL add_a_to_hl
    LD DE,room_unlocked
    ADD HL,DE
    LD (HL),1
    POP BC
    RET

; Get exit requirement: A=room, B=dir, HL=ptr
get_exit_requirement:
    PUSH BC
    DEC A
    LD C,A
    LD A,B
    DEC A
    LD B,A
    LD A,C
    LD H,0
    LD L,A
    ADD HL,HL
    ADD HL,HL
    LD D,0
    LD E,A
    ADD HL,DE
    ADD HL,DE
    LD A,B
    CALL add_a_to_hl
    ADD HL,HL
    LD DE,room_req_ptrs
    ADD HL,DE
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    POP BC
    RET

; Get item name: A=index(1-based), HL=ptr
get_item_name_ptr:
    DEC A
    SLA A
    LD HL,item_name_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get item desc: A=index, HL=ptr
get_item_desc_ptr:
    DEC A
    SLA A
    LD HL,item_desc_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC name: A=index(1-based), HL=ptr
get_npc_name_ptr:
    DEC A
    SLA A
    LD HL,npc_name_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC desc: A=index, HL=ptr
get_npc_desc_ptr:
    DEC A
    SLA A
    LD HL,npc_desc_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC talk: A=index, HL=ptr
get_npc_talk_ptr:
    DEC A
    SLA A
    LD HL,npc_talk_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC wants: A=index, HL=ptr
get_npc_wants_ptr:
    DEC A
    SLA A
    LD HL,npc_wants_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get NPC gives: A=index, HL=ptr
get_npc_gives_ptr:
    DEC A
    SLA A
    LD HL,npc_gives_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get death msg: A=index, HL=ptr
get_death_msg_ptr:
    SLA A
    LD HL,death_msg_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; Get survival item: A=index, HL=ptr
get_survival_item_ptr:
    SLA A
    LD HL,survival_item_ptrs
    CALL add_a_to_hl
    LD A,(HL)
    INC HL
    LD H,(HL)
    LD L,A
    RET

; ========================================
; INVENTORY MANAGEMENT
; ========================================

; Add item A to inventory
add_to_inventory:
    PUSH AF
    LD A,(inv_count)
    LD HL,inventory
    CALL add_a_to_hl
    POP AF
    LD (HL),A
    LD A,(inv_count)
    INC A
    LD (inv_count),A
    RET

; Remove item at slot A
remove_from_inventory:
    LD (rfi_slot),A
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD D,H
    LD E,L
    INC DE
    LD A,(inv_count)
    LD B,A
    LD A,(rfi_slot)
    SUB B
    NEG
    JR Z,rfi_done
    LD B,A
rfi_shift:
    LD A,(DE)
    LD (HL),A
    INC HL
    INC DE
    DJNZ rfi_shift
rfi_done:
    LD A,(inv_count)
    DEC A
    LD (inv_count),A
    RET
rfi_slot:
    DEFB 0

; Check if have item, HL=name, C flag if yes
check_have_item:
    LD (chi_name),HL
    LD A,(inv_count)
    OR A
    JR Z,chi_no
    LD B,A
    LD C,1
chi_loop:
    PUSH BC
    LD A,C
    DEC A
    LD HL,inventory
    CALL add_a_to_hl
    LD A,(HL)
    OR A
    JR Z,chi_next
    CALL get_item_name_ptr
    LD DE,(chi_name)
    CALL str_contains
    JR NZ,chi_next
    POP BC
    SCF
    RET
chi_next:
    POP BC
    INC C
    DJNZ chi_loop
chi_no:
    OR A
    RET
chi_name:
    DEFW 0

; ========================================
; INITIALIZE GAME DATA
; ========================================
init_data:
    ; Copy initial item locations
    LD HL,item_locations_init
    LD DE,item_locations
    LD BC,NUM_ITEMS
    LD A,B
    OR C
    JR Z,id_skip_items
    LDIR
id_skip_items:
    ; Copy initial item parents
    LD HL,item_parents_init
    LD DE,item_parents
    LD BC,NUM_ITEMS
    LD A,B
    OR C
    JR Z,id_skip_parents
    LDIR
id_skip_parents:
    ; Copy initial NPC locations
    LD HL,npc_locations_init
    LD DE,npc_locations
    LD BC,NUM_NPCS
    LD A,B
    OR C
    JR Z,id_skip_npcs
    LDIR
id_skip_npcs:
    ; Init unlocked flags to 0 (locked by default)
    ; Exits without requirements are always open (checked in do_move)
    LD HL,room_unlocked
    LD BC,NUM_ROOMS*6
id_unlock_loop:
    XOR A
    LD (HL),A
    INC HL
    DEC BC
    LD A,B
    OR C
    JR NZ,id_unlock_loop
    ; Clear item open flags
    LD HL,item_is_open
    LD BC,NUM_ITEMS
    LD A,B
    OR C
    JR Z,id_skip_open
id_open_loop:
    XOR A
    LD (HL),A
    INC HL
    DEC BC
    LD A,B
    OR C
    JR NZ,id_open_loop
id_skip_open:
    ; Clear NPC gift flags
    LD HL,npc_got_gift
    LD BC,NUM_NPCS
    LD A,B
    OR C
    JR Z,id_skip_gift
id_gift_loop:
    XOR A
    LD (HL),A
    INC HL
    DEC BC
    LD A,B
    OR C
    JR NZ,id_gift_loop
id_skip_gift:
    ; Clear inventory
    LD HL,inventory
    LD B,MAX_INV
    XOR A
id_inv_loop:
    LD (HL),A
    INC HL
    DJNZ id_inv_loop
    RET

; ========================================
; GAME DATA
; ========================================

str_title:
    DEFB "THE FORGOTTEN MAYA PRINCESS",0
str_author:
    DEFB "Devil-System",0
str_by:
    DEFB "by ",0

str_winmsg:
    DEFB "Princess Ixchel embraces you! Together",13,10
    DEFB "you escape into the sunset!",0

str_help_text:
    DEFB "COMMANDS:",13,10
    DEFB "N,S,E,W,U,D - Move",13,10
    DEFB "TAKE, DROP, USE",13,10
    DEFB "EXAMINE, OPEN, CLOSE",13,10
    DEFB "TALK TO, GIVE X TO Y",13,10
    DEFB "I, L, SCORE, QUIT",0

str_dir_n:
    DEFB "N ",0
str_dir_e:
    DEFB "E ",0
str_dir_s:
    DEFB "S ",0
str_dir_w:
    DEFB "W ",0
str_dir_u:
    DEFB "U ",0
str_dir_d:
    DEFB "D ",0

str_kw_to:
    DEFB " TO ",0

str_kw_from:
    DEFB " FROM ",0
; === MESSAGES ===
msg_filename:
    DEFB "SAVEFILE (no ext.):",0
msg_saved:
    DEFB "Game saved.",0
msg_loaded:
    DEFB "Game loaded.",0
msg_save_error:
    DEFB "Can't save.",0
msg_load_error:
    DEFB "Can't load.",0
msg_cancelled:
    DEFB "Cancelled.",0
msg_takefrom_syntax:
    DEFB "TAKE X FROM Y",0
msg_yousee:
    DEFB "You see: ",0
msg_inside:
    DEFB "Inside: ",0
msg_empty_c:
    DEFB "  Empty.",0
msg_exits:
    DEFB "Exits: ",0
msg_none:
    DEFB "none",0
msg_score:
    DEFB "Score: ",0
msg_presskey:
    DEFB "[key]",0
msg_unknown:
    DEFB "Huh?",0
msg_cantgo:
    DEFB "Can't go there.",0
msg_blocked:
    DEFB "Blocked.",0
msg_what:
    DEFB "What?",0
msg_nothere:
    DEFB "Not here.",0
msg_donthave:
    DEFB "Don't have it.",0
msg_full:
    DEFB "Full!",0
msg_taken:
    DEFB "Taken.",0
msg_dropped:
    DEFB "Dropped.",0
msg_usewhat:
    DEFB "Use what?",0
msg_unlocked:
    DEFB "Unlocked!",0
msg_nothing:
    DEFB "Nothing happens.",0
msg_nothing_special:
    DEFB "You see nothing special.",0
msg_opened:
    DEFB "Opened.",0
msg_closed:
    DEFB "Closed.",0
msg_notcontainer:
    DEFB "Can't open that.",0
msg_talkto:
    DEFB "Talk to whom?",0
msg_noone:
    DEFB "No one here.",0
msg_silence:
    DEFB "...",0
msg_thanks:
    DEFB "Thanks!",0
msg_nowant:
    DEFB "They don't want that.",0
msg_got:
    DEFB "Got: ",0
msg_give_syntax:
    DEFB "GIVE X TO Y",0
msg_inventory:
    DEFB "Inventory:",0
msg_empty:
    DEFB "  empty",0
msg_indent:
    DEFB "  ",0
msg_gameover:
    DEFB "GAME OVER",0
msg_youwin:
    DEFB "YOU WIN!",0
msg_bye:
    DEFB "Goodbye!",0
str_empty:
    DEFB 0

; === COMMAND STRINGS ===
cmd_north:
    DEFB "N",0
cmd_north1:
    DEFB "NORTH",0
cmd_north2:
    DEFB "GO NORTH",0
cmd_south:
    DEFB "S",0
cmd_south1:
    DEFB "SOUTH",0
cmd_south2:
    DEFB "GO SOUTH",0
cmd_east:
    DEFB "E",0
cmd_east1:
    DEFB "EAST",0
cmd_east2:
    DEFB "GO EAST",0
cmd_west:
    DEFB "W",0
cmd_west1:
    DEFB "WEST",0
cmd_west2:
    DEFB "GO WEST",0
cmd_up:
    DEFB "U",0
cmd_up1:
    DEFB "UP",0
cmd_up2:
    DEFB "GO UP",0
cmd_up3:
    DEFB "CLIMB",0
cmd_down:
    DEFB "D",0
cmd_down1:
    DEFB "DOWN",0
cmd_down2:
    DEFB "DN",0
cmd_down3:
    DEFB "GO DOWN",0
cmd_down4:
    DEFB "DESCEND",0
cmd_lookin:
    DEFB "L IN",0
cmd_lookin1:
    DEFB "LOOK IN",0
cmd_lookin2:
    DEFB "EXAMINE ROOM IN",0
cmd_look:
    DEFB "L",0
cmd_look1:
    DEFB "LOOK",0
cmd_look2:
    DEFB "EXAMINE ROOM",0
cmd_inv:
    DEFB "I",0
cmd_inv1:
    DEFB "INV",0
cmd_inv2:
    DEFB "INVENTORY",0
cmd_take:
    DEFB "TAKE",0
cmd_take1:
    DEFB "GET",0
cmd_take2:
    DEFB "GRAB",0
cmd_take3:
    DEFB "PICK UP",0
cmd_drop:
    DEFB "DROP",0
cmd_drop1:
    DEFB "PUT DOWN",0
cmd_drop2:
    DEFB "LEAVE",0
cmd_exam:
    DEFB "EXAMINE",0
cmd_exam1:
    DEFB "X",0
cmd_exam2:
    DEFB "LOOK AT",0
cmd_exam3:
    DEFB "CHECK",0
cmd_exam4:
    DEFB "INSPECT",0
cmd_use:
    DEFB "USE",0
cmd_use1:
    DEFB "APPLY",0
cmd_open:
    DEFB "OPEN",0
cmd_close:
    DEFB "CLOSE",0
cmd_close1:
    DEFB "SHUT",0
cmd_talk:
    DEFB "TALK TO",0
cmd_talk1:
    DEFB "SPEAK TO",0
cmd_talk2:
    DEFB "ASK TO",0
cmd_talk3:
    DEFB "SAY TO",0
cmd_give:
    DEFB "GIVE",0
cmd_give1:
    DEFB "OFFER",0
cmd_give2:
    DEFB "HAND",0
cmd_help:
    DEFB "HELP",0
cmd_help1:
    DEFB "?",0
cmd_help2:
    DEFB "COMMANDS",0
cmd_help3:
    DEFB "HINT",0
cmd_score:
    DEFB "SCORE",0
cmd_score1:
    DEFB "POINTS",0
cmd_save:
    DEFB "SAVE",0
cmd_save1:
    DEFB "SAVE GAME",0
cmd_load:
    DEFB "LOAD",0
cmd_load1:
    DEFB "RESTORE",0
cmd_load2:
    DEFB "LOAD GAME",0
cmd_quit:
    DEFB "QUIT",0
cmd_quit1:
    DEFB "EXIT",0
cmd_quit2:
    DEFB "Q",0
cmd_quit3:
    DEFB "BYE",0

; === ROOM DATA ===
room_name_ptrs:
    DEFW room_1_name
    DEFW room_2_name
    DEFW room_3_name
    DEFW room_4_name
    DEFW room_5_name
    DEFW room_6_name
    DEFW room_7_name
    DEFW room_8_name
    DEFW room_9_name
    DEFW room_10_name
    DEFW room_11_name
    DEFW room_12_name
    DEFW room_13_name
    DEFW room_14_name
    DEFW room_15_name
    DEFW room_16_name
    DEFW room_17_name
    DEFW room_18_name
    DEFW room_19_name
    DEFW room_20_name
    DEFW room_21_name
    DEFW room_22_name
    DEFW room_23_name
    DEFW room_24_name
    DEFW room_25_name

room_desc_ptrs:
    DEFW room_1_desc
    DEFW room_2_desc
    DEFW room_3_desc
    DEFW room_4_desc
    DEFW room_5_desc
    DEFW room_6_desc
    DEFW room_7_desc
    DEFW room_8_desc
    DEFW room_9_desc
    DEFW room_10_desc
    DEFW room_11_desc
    DEFW room_12_desc
    DEFW room_13_desc
    DEFW room_14_desc
    DEFW room_15_desc
    DEFW room_16_desc
    DEFW room_17_desc
    DEFW room_18_desc
    DEFW room_19_desc
    DEFW room_20_desc
    DEFW room_21_desc
    DEFW room_22_desc
    DEFW room_23_desc
    DEFW room_24_desc
    DEFW room_25_desc

room_1_name:
    DEFB "Base Camp",0
room_1_desc:
    DEFB "Your expedition camp at jungle's edge.",13,10
    DEFB "A worn tent and smoking fire. Your",13,10
    DEFB "guide fled, leaving notes about a",13,10
    DEFB "cursed princess of legendary beauty.",0
room_2_name:
    DEFB "Jungle Trail",0
room_2_desc:
    DEFB "A narrow path through dense",13,10
    DEFB "vegetation. Howler monkeys scream",13,10
    DEFB "above. The humidity makes your clothes",13,10
    DEFB "cling to your body.",0
room_3_name:
    DEFB "Riverside",0
room_3_desc:
    DEFB "A swift river blocks your path.",13,10
    DEFB "Crocodiles bask on muddy banks,",13,10
    DEFB "watching hungrily. You need a way to",13,10
    DEFB "cross safely.",0
room_4_name:
    DEFB "Abandoned Camp",0
room_4_desc:
    DEFB "A destroyed campsite. Torn tents and",13,10
    DEFB "scattered gear. A skeleton clutches a",13,10
    DEFB "journal. Something terrible happened",13,10
    DEFB "here.",0
room_5_name:
    DEFB "Jungle Clearing",0
room_5_desc:
    DEFB "Sunlight breaks through the canopy.",13,10
    DEFB "Exotic birds sing. Stone markers hint",13,10
    DEFB "at ancient civilization ahead.",0
room_6_name:
    DEFB "Shaman's Hut",0
room_6_desc:
    DEFB "A hut draped with colorful fabrics.",13,10
    DEFB "Incense curls through the air. Strange",13,10
    DEFB "masks hang on walls. The smell is",13,10
    DEFB "intoxicating.",0
room_7_name:
    DEFB "Jaguar's Den",0
room_7_desc:
    DEFB "A dark cave reeking of musk. Yellow",13,10
    DEFB "eyes gleam from shadows. Low growling",13,10
    DEFB "echoes. The beast is hungry.",0
room_8_name:
    DEFB "Overgrown Path",0
room_8_desc:
    DEFB "Thick vines block your way north.",13,10
    DEFB "Carvings on trees depict a beautiful",13,10
    DEFB "woman, her curves shown in flowing",13,10
    DEFB "robes.",0
room_9_name:
    DEFB "Quicksand Marsh",0
room_9_desc:
    DEFB "Treacherous boggy ground. Bubbles rise",13,10
    DEFB "from hidden pools. One wrong step",13,10
    DEFB "means slow death.",0
room_10_name:
    DEFB "Sacred Cenote",0
room_10_desc:
    DEFB "A deep pool of crystal blue water.",13,10
    DEFB "Ancient Maya threw offerings here.",13,10
    DEFB "Gold glitters in the depths.",0
room_11_name:
    DEFB "Temple Steps",0
room_11_desc:
    DEFB "Massive stone steps lead to a pyramid.",13,10
    DEFB "Carved reliefs show a princess - full",13,10
    DEFB "lips, flowing hair, alluring curves.",0
room_12_name:
    DEFB "Serpent Pit",0
room_12_desc:
    DEFB "A chamber filled with writhing snakes.",13,10
    DEFB "Their hissing echoes. Hundreds of",13,10
    DEFB "forked tongues taste the air.",0
room_13_name:
    DEFB "Outer Treasury",0
room_13_desc:
    DEFB "Gold and jade scattered everywhere.",13,10
    DEFB "Silks and perfume bottles hint at a",13,10
    DEFB "lady's presence. A faint scent",13,10
    DEFB "lingers.",0
room_14_name:
    DEFB "Temple Entrance",0
room_14_desc:
    DEFB "A vast hall with towering serpent",13,10
    DEFB "columns. Torches flicker. Murals",13,10
    DEFB "depict ceremonies honoring a beautiful",13,10
    DEFB "maiden.",0
room_15_name:
    DEFB "Priest's Quarters",0
room_15_desc:
    DEFB "Dusty scrolls and ritual objects. A",13,10
    DEFB "skeleton in robes sits at a desk,",13,10
    DEFB "quill in hand. His message unfinished.",0
room_16_name:
    DEFB "Inner Treasury",0
room_16_desc:
    DEFB "The princess's treasure room. Jewels",13,10
    DEFB "sparkle. Delicate silk garments hint",13,10
    DEFB "at her refined tastes.",0
room_17_name:
    DEFB "Main Temple Hall",0
room_17_desc:
    DEFB "An enormous chamber. Moonlight streams",13,10
    DEFB "through openings. Murals show the",13,10
    DEFB "princess dancing sensually under",13,10
    DEFB "stars.",0
room_18_name:
    DEFB "Bathing Pool",0
room_18_desc:
    DEFB "A crystal pool surrounded by orchids.",13,10
    DEFB "Warm steam rises. Rose petals float.",13,10
    DEFB "The princess bathed here.",0
room_19_name:
    DEFB "Cursed Altar",0
room_19_desc:
    DEFB "A black obsidian altar with glowing",13,10
    DEFB "symbols. Dark energy pulses. The",13,10
    DEFB "priest cast his curse here.",0
room_20_name:
    DEFB "Garden of Desire",0
room_20_desc:
    DEFB "A hidden garden with moonflowers. Soft",13,10
    DEFB "music plays from nowhere. Aphrodisiac",13,10
    DEFB "scents fill the air.",0
room_21_name:
    DEFB "Spirit Sanctuary",0
room_21_desc:
    DEFB "Ethereal mist swirls here. Candles",13,10
    DEFB "float in midair. The boundary between",13,10
    DEFB "worlds is thin.",0
room_22_name:
    DEFB "Royal Wardrobe",0
room_22_desc:
    DEFB "Sheer silk gowns fill wardrobes. Lace",13,10
    DEFB "garments lie draped over chairs. A",13,10
    DEFB "vanity reflects your flushed face.",0
room_23_name:
    DEFB "Princess's Chamber",0
room_23_desc:
    DEFB "Silk curtains frame a golden bed.",13,10
    DEFB "PRINCESS IXCHEL lies there, caramel",13,10
    DEFB "skin glowing, curves beneath thin",13,10
    DEFB "white silk.",0
room_24_name:
    DEFB "Hidden Passage",0
room_24_desc:
    DEFB "A secret tunnel behind the bed. Cool",13,10
    DEFB "air flows from ahead. This is the",13,10
    DEFB "escape route.",0
room_25_name:
    DEFB "Temple Exit",0
room_25_desc:
    DEFB "Warm sunlight streams through ancient",13,10
    DEFB "doors. The jungle awaits. Freedom for",13,10
    DEFB "those who carry true love.",0

room_exits:
    DEFB 2,0,0,0,0,0 ; Room 1
    DEFB 5,4,1,3,0,0 ; Room 2
    DEFB 6,2,0,0,0,0 ; Room 3
    DEFB 7,0,0,2,0,0 ; Room 4
    DEFB 8,7,2,6,0,0 ; Room 5
    DEFB 9,5,3,0,0,0 ; Room 6
    DEFB 10,0,4,5,0,0 ; Room 7
    DEFB 11,10,5,9,0,0 ; Room 8
    DEFB 12,8,6,0,0,0 ; Room 9
    DEFB 13,0,7,8,0,0 ; Room 10
    DEFB 14,13,8,12,0,0 ; Room 11
    DEFB 15,11,9,0,0,0 ; Room 12
    DEFB 16,0,10,11,0,0 ; Room 13
    DEFB 17,16,11,15,0,0 ; Room 14
    DEFB 18,14,12,0,0,0 ; Room 15
    DEFB 19,0,13,14,0,0 ; Room 16
    DEFB 20,19,14,18,0,0 ; Room 17
    DEFB 21,17,15,0,0,0 ; Room 18
    DEFB 22,0,16,17,0,0 ; Room 19
    DEFB 23,22,17,21,0,0 ; Room 20
    DEFB 24,20,18,0,0,0 ; Room 21
    DEFB 0,0,19,20,0,0 ; Room 22
    DEFB 0,0,20,24,0,0 ; Room 23
    DEFB 25,23,21,0,0,0 ; Room 24
    DEFB 0,0,24,0,0,0 ; Room 25

room_req_ptrs:
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_8_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_11_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_13_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_17_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_20_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW room_24_req_north
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty
    DEFW str_empty

room_8_req_north:
    DEFB "MACHETE",0
room_11_req_north:
    DEFB "TEMPLE KEY",0
room_13_req_north:
    DEFB "SILVER KEY",0
room_17_req_north:
    DEFB "SPIRIT BLESSING",0
room_20_req_north:
    DEFB "GOLDEN KEY",0
room_24_req_north:
    DEFB "JADE HEART",0

; === ITEM DATA ===
item_name_ptrs:
    DEFW item_1_name
    DEFW item_2_name
    DEFW item_3_name
    DEFW item_4_name
    DEFW item_5_name
    DEFW item_6_name
    DEFW item_7_name
    DEFW item_8_name
    DEFW item_9_name
    DEFW item_10_name
    DEFW item_11_name
    DEFW item_12_name
    DEFW item_13_name
    DEFW item_14_name
    DEFW item_15_name
    DEFW item_16_name
    DEFW item_17_name
    DEFW item_18_name
    DEFW item_19_name
    DEFW item_20_name
    DEFW item_21_name
    DEFW item_22_name
    DEFW item_23_name
    DEFW item_24_name
    DEFW item_25_name
    DEFW item_26_name
    DEFW item_27_name
    DEFW item_28_name
    DEFW item_29_name
    DEFW item_30_name
    DEFW item_31_name
    DEFW item_32_name

item_desc_ptrs:
    DEFW item_1_desc
    DEFW item_2_desc
    DEFW item_3_desc
    DEFW item_4_desc
    DEFW item_5_desc
    DEFW item_6_desc
    DEFW item_7_desc
    DEFW item_8_desc
    DEFW item_9_desc
    DEFW item_10_desc
    DEFW item_11_desc
    DEFW item_12_desc
    DEFW item_13_desc
    DEFW item_14_desc
    DEFW item_15_desc
    DEFW item_16_desc
    DEFW item_17_desc
    DEFW item_18_desc
    DEFW item_19_desc
    DEFW item_20_desc
    DEFW item_21_desc
    DEFW item_22_desc
    DEFW item_23_desc
    DEFW item_24_desc
    DEFW item_25_desc
    DEFW item_26_desc
    DEFW item_27_desc
    DEFW item_28_desc
    DEFW item_29_desc
    DEFW item_30_desc
    DEFW item_31_desc
    DEFW item_32_desc

item_locations_init:
    DEFB 1
    DEFB 1
    DEFB 1
    DEFB 2
    DEFB 4
    DEFB 4
    DEFB 4
    DEFB 4
    DEFB 6
    DEFB 6
    DEFB 6
    DEFB 5
    DEFB 10
    DEFB 10
    DEFB 10
    DEFB 11
    DEFB 13
    DEFB 13
    DEFB 13
    DEFB 15
    DEFB 15
    DEFB 16
    DEFB 16
    DEFB 16
    DEFB 18
    DEFB 18
    DEFB 20
    DEFB 20
    DEFB 21
    DEFB 22
    DEFB 22
    DEFB 22

item_is_container:
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0

item_points:
    DEFB 0
    DEFB 5
    DEFB 10
    DEFB 5
    DEFB 15
    DEFB 0
    DEFB 10
    DEFB 10
    DEFB 20
    DEFB 15
    DEFB 10
    DEFB 15
    DEFB 25
    DEFB 15
    DEFB 20
    DEFB 5
    DEFB 0
    DEFB 20
    DEFB 15
    DEFB 20
    DEFB 15
    DEFB 0
    DEFB 30
    DEFB 25
    DEFB 10
    DEFB 15
    DEFB 15
    DEFB 25
    DEFB 35
    DEFB 20
    DEFB 15
    DEFB 20

item_parents_init:
    DEFB 0
    DEFB 1
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 6
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 17
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 22
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0
    DEFB 0

item_1_name:
    DEFB "EXPEDITION BAG",0
item_1_desc:
    DEFB "A weathered leather bag left behind.",0
item_2_name:
    DEFB "COMPASS",0
item_2_desc:
    DEFB "A brass compass. Spins wildly near the",13,10
    DEFB "temple.",0
item_3_name:
    DEFB "ROPE",0
item_3_desc:
    DEFB "A long coil of strong rope.",0
item_4_name:
    DEFB "CANTEEN",0
item_4_desc:
    DEFB "A metal canteen filled with water.",0
item_5_name:
    DEFB "JOURNAL",0
item_5_desc:
    DEFB "Describes the princess: 'Skin like",13,10
    DEFB "honey, lips like petals, curves that",13,10
    DEFB "drive men mad.'",0
item_6_name:
    DEFB "WOODEN CHEST",0
item_6_desc:
    DEFB "A rotting chest half-buried in mud.",0
item_7_name:
    DEFB "MACHETE",0
item_7_desc:
    DEFB "A sharp jungle blade for cutting",13,10
    DEFB "vines.",0
item_8_name:
    DEFB "WOODEN PLANK",0
item_8_desc:
    DEFB "A sturdy plank to cross soft ground.",0
item_9_name:
    DEFB "SNAKE CHARM",0
item_9_desc:
    DEFB "A wooden flute that mesmerizes",13,10
    DEFB "serpents.",0
item_10_name:
    DEFB "LOVE POTION",0
item_10_desc:
    DEFB "Pink liquid in a heart bottle.",13,10
    DEFB "'Awakens passion.'",0
item_11_name:
    DEFB "RAW MEAT",0
item_11_desc:
    DEFB "Fresh meat wrapped in leaves.",13,10
    DEFB "Predators love it.",0
item_12_name:
    DEFB "EXOTIC ORCHID",0
item_12_desc:
    DEFB "A purple flower with intoxicating",13,10
    DEFB "fragrance.",0
item_13_name:
    DEFB "GOLD COINS",0
item_13_desc:
    DEFB "Ancient Maya gold coins. Worth a",13,10
    DEFB "fortune.",0
item_14_name:
    DEFB "DIVING MASK",0
item_14_desc:
    DEFB "Primitive diving mask of glass and",13,10
    DEFB "leather.",0
item_15_name:
    DEFB "TEMPLE KEY",0
item_15_desc:
    DEFB "Heavy bronze key with serpent",13,10
    DEFB "engravings.",0
item_16_name:
    DEFB "TORCH",0
item_16_desc:
    DEFB "A burning torch for dark passages.",0
item_17_name:
    DEFB "JEWELED BOX",0
item_17_desc:
    DEFB "An ornate box with rubies and",13,10
    DEFB "emeralds.",0
item_18_name:
    DEFB "SILVER KEY",0
item_18_desc:
    DEFB "A delicate silver key shaped like a",13,10
    DEFB "flower.",0
item_19_name:
    DEFB "SILK SCARF",0
item_19_desc:
    DEFB "Rose-colored. Still carries her sweet",13,10
    DEFB "perfume.",0
item_20_name:
    DEFB "ANCIENT SCROLL",0
item_20_desc:
    DEFB "'Only true love's kiss can wake the",13,10
    DEFB "sleeping beauty.'",0
item_21_name:
    DEFB "RITUAL DAGGER",0
item_21_desc:
    DEFB "An obsidian blade for ancient",13,10
    DEFB "ceremonies.",0
item_22_name:
    DEFB "TREASURE CHEST",0
item_22_desc:
    DEFB "A golden chest overflowing with",13,10
    DEFB "jewels.",0
item_23_name:
    DEFB "JADE AMULET",0
item_23_desc:
    DEFB "Protective amulet pulsing with warm",13,10
    DEFB "energy.",0
item_24_name:
    DEFB "GOLDEN KEY",0
item_24_desc:
    DEFB "A heart-shaped key. Unlocks something",13,10
    DEFB "precious.",0
item_25_name:
    DEFB "ROSE PETALS",0
item_25_desc:
    DEFB "Soft petals floating on the pool.",13,10
    DEFB "Divine scent.",0
item_26_name:
    DEFB "BATH OIL",0
item_26_desc:
    DEFB "Crystal vial of seductive scented oil.",0
item_27_name:
    DEFB "PASSION FRUIT",0
item_27_desc:
    DEFB "Ripe exotic fruit. Its juice awakens",13,10
    DEFB "desire.",0
item_28_name:
    DEFB "MOONFLOWER",0
item_28_desc:
    DEFB "Rare white flower of legendary beauty.",0
item_29_name:
    DEFB "JADE NECKLACE",0
item_29_desc:
    DEFB "Exquisite necklace with heart-shaped",13,10
    DEFB "pendant.",0
item_30_name:
    DEFB "SILK GOWN",0
item_30_desc:
    DEFB "Sheer white gown leaving little to",13,10
    DEFB "imagination.",0
item_31_name:
    DEFB "PERFUME",0
item_31_desc:
    DEFB "Crystal bottle of seductive tropical",13,10
    DEFB "essence.",0
item_32_name:
    DEFB "LACE GARMENT",0
item_32_desc:
    DEFB "Delicate intimate garment of finest",13,10
    DEFB "lace.",0

; === NPC DATA ===
npc_name_ptrs:
    DEFW npc_1_name
    DEFW npc_2_name
    DEFW npc_3_name
    DEFW npc_4_name
    DEFW npc_5_name
    DEFW npc_6_name
    DEFW npc_7_name
    DEFW npc_8_name

npc_desc_ptrs:
    DEFW npc_1_desc
    DEFW npc_2_desc
    DEFW npc_3_desc
    DEFW npc_4_desc
    DEFW npc_5_desc
    DEFW npc_6_desc
    DEFW npc_7_desc
    DEFW npc_8_desc

npc_talk_ptrs:
    DEFW npc_1_talk
    DEFW npc_2_talk
    DEFW npc_3_talk
    DEFW npc_4_talk
    DEFW npc_5_talk
    DEFW npc_6_talk
    DEFW npc_7_talk
    DEFW npc_8_talk

npc_wants_ptrs:
    DEFW npc_1_wants
    DEFW npc_2_wants
    DEFW npc_3_wants
    DEFW npc_4_wants
    DEFW npc_5_wants
    DEFW npc_6_wants
    DEFW npc_7_wants
    DEFW npc_8_wants

npc_gives_ptrs:
    DEFW npc_1_gives
    DEFW npc_2_gives
    DEFW npc_3_gives
    DEFW npc_4_gives
    DEFW npc_5_gives
    DEFW npc_6_gives
    DEFW npc_7_gives
    DEFW npc_8_gives

npc_locations_init:
    DEFB 6
    DEFB 5
    DEFB 17
    DEFB 18
    DEFB 21
    DEFB 22
    DEFB 23
    DEFB 19

npc_points:
    DEFB 20
    DEFB 25
    DEFB 30
    DEFB 25
    DEFB 50
    DEFB 20
    DEFB 60
    DEFB 30

npc_1_name:
    DEFB "OLD SHAMAN",0
npc_1_desc:
    DEFB "A wise old man with knowing eyes and",13,10
    DEFB "colorful feathers.",0
npc_1_talk:
    DEFB "You seek the princess! Take the",13,10
    DEFB "charm for serpents. Feed the jaguar.",13,10
    DEFB "Women love flowers!",0
npc_1_wants:
    DEFB "CANTEEN",0
npc_1_gives:
    DEFB "BLESSING POWDER",0
npc_2_name:
    DEFB "JUNGLE GUIDE",0
npc_2_desc:
    DEFB "Your former guide hiding in bushes. He",13,10
    DEFB "looks ashamed.",0
npc_2_talk:
    DEFB "Forgive me! Take this temple key",13,10
    DEFB "from the cenote. Beware quicksand to",13,10
    DEFB "the west!",0
npc_2_wants:
    DEFB "GOLD COINS",0
npc_2_gives:
    DEFB "TEMPLE MAP",0
npc_3_name:
    DEFB "TEMPLE GUARDIAN",0
npc_3_desc:
    DEFB "Muscular spirit warrior with ancient",13,10
    DEFB "tattoos.",0
npc_3_talk:
    DEFB "Prove worthy! Bring the priest's",13,10
    DEFB "scroll to pass to the sacred garden!",0
npc_3_wants:
    DEFB "ANCIENT SCROLL",0
npc_3_gives:
    DEFB "SPIRIT BLESSING",0
npc_4_name:
    DEFB "BATHING NYMPH",0
npc_4_desc:
    DEFB "Water spirit with glistening wet skin",13,10
    DEFB "and playful eyes.",0
npc_4_talk:
    DEFB "The princess bathed here. Bring",13,10
    DEFB "orchid and I'll share altar secrets.",0
npc_4_wants:
    DEFB "EXOTIC ORCHID",0
npc_4_gives:
    DEFB "WATER BLESSING",0
npc_5_name:
    DEFB "QUEEN IXMUCANE",0
npc_5_desc:
    DEFB "Ghost of princess's mother. Still",13,10
    DEFB "beautiful in ethereal silks.",0
npc_5_talk:
    DEFB "My daughter dreams of rescue! Bring",13,10
    DEFB "her necklace for the Jade Heart.",0
npc_5_wants:
    DEFB "JADE NECKLACE",0
npc_5_gives:
    DEFB "JADE HEART",0
npc_6_name:
    DEFB "HANDMAIDEN",0
npc_6_desc:
    DEFB "Pretty ghost servant in transparent",13,10
    DEFB "robes. She blushes.",0
npc_6_talk:
    DEFB "My lady awaits true love! She adores",13,10
    DEFB "moonflowers and sweet perfume!",0
npc_6_wants:
    DEFB "PERFUME",0
npc_6_gives:
    DEFB "CHAMBER KEY",0
npc_7_name:
    DEFB "PRINCESS IXCHEL",0
npc_7_desc:
    DEFB "Legendary beauty on silken bed.",13,10
    DEFB "Caramel skin, dark hair, curves in",13,10
    DEFB "thin white silk.",0
npc_7_talk:
    DEFB "You came! Kiss me and free me! But",13,10
    DEFB "first... flowers for your princess?",0
npc_7_wants:
    DEFB "MOONFLOWER",0
npc_7_gives:
    DEFB "PRINCESS KISS",0
npc_8_name:
    DEFB "CURSED PRIEST",0
npc_8_desc:
    DEFB "Skeleton of the jealous priest. Dark",13,10
    DEFB "energy swirls around.",0
npc_8_talk:
    DEFB "She was meant for ME! The curse",13,10
    DEFB "stays... unless you have the jade",13,10
    DEFB "amulet!",0
npc_8_wants:
    DEFB "RITUAL DAGGER",0
npc_8_gives:
    DEFB "CURSE FRAGMENT",0

; === DEADLY ROOMS ===
deadly_rooms:
    DEFB 3
    DEFB 7
    DEFB 9
    DEFB 12
    DEFB 19

death_msg_ptrs:
    DEFW death_msg_0
    DEFW death_msg_1
    DEFW death_msg_2
    DEFW death_msg_3
    DEFW death_msg_4

survival_item_ptrs:
    DEFW survival_0
    DEFW survival_1
    DEFW survival_2
    DEFW survival_3
    DEFW survival_4

death_msg_0:
    DEFB "You try to swim! The crocodiles are",13,10
    DEFB "faster!",0
survival_0:
    DEFB "ROPE",0
death_msg_1:
    DEFB "The jaguar pounces! Its claws tear",13,10
    DEFB "through you!",0
survival_1:
    DEFB "RAW MEAT",0
death_msg_2:
    DEFB "You sink into quicksand! Darkness",13,10
    DEFB "takes you!",0
survival_2:
    DEFB "WOODEN PLANK",0
death_msg_3:
    DEFB "The serpents strike! Venom burns",13,10
    DEFB "through you!",0
survival_3:
    DEFB "SNAKE CHARM",0
death_msg_4:
    DEFB "Dark magic engulfs you! Your soul is",13,10
    DEFB "torn away!",0
survival_4:
    DEFB "JADE AMULET",0

; ========================================
; VARIABLES (RAM)
; ========================================

; === SAVE DATA BLOCK START ===
; All game state that needs to be saved
save_data_start:
current_room:
    DEFS 1
score:
    DEFS 2
inv_count:
    DEFS 1
inventory:
    DEFS MAX_INV
item_locations:
    DEFS NUM_ITEMS
item_is_open:
    DEFS NUM_ITEMS
item_parents:
    DEFS NUM_ITEMS
npc_locations:
    DEFS NUM_NPCS
npc_got_gift:
    DEFS NUM_NPCS
room_unlocked:
    DEFS NUM_ROOMS*6
save_data_end:
; === SAVE DATA BLOCK END ===

; === TEMPORARY VARIABLES (not saved) ===
refresh_flag:
    DEFS 1
temp_dir:
    DEFS 1
temp_room:
    DEFS 1
temp_target:
    DEFS 1
temp_container:
    DEFS 1
temp_ptr:
    DEFS 2
temp_ptr2:
    DEFS 2
temp_ptr3:
    DEFS 2
found_item:
    DEFS 1
found_inv_slot:
    DEFS 1
found_npc:
    DEFS 1
input_len:
    DEFS 1
input_buffer:
    DEFS INPUT_MAX+1

; === FILENAME INPUT ===
filename_len:
    DEFS 1
filename_buf:
    DEFS 16              ; Max 8 chars + some padding

; === CASSETTE BUFFER (2KB needed by firmware) ===
cas_buffer:
    DEFS 2048
; === ORIGINAL STACK POINTER ===
original_sp:
    DEFS 2

; === END ===

Ist noch gründlich zu testen!

WoW! Sauber! Das ist ein schöner erster Schritt, und super schnell hinbekommen!!! Respekt und Kudos!

Sage mir bitte bescheid, sobald das stabil läuft, dann könnte man ja eine Version für FutureOS machen, denn dann läuft es wirklich schnell.

Eventuell kann man dann auch Erweiterungs-RAM nutzen. Aber ein Schritt nach dem Anderen :)  :)  :)
TFM of FutureSoft
http://www.futureos.de --> Das Betriebssystem FutureOS (Update: 24.12.2025)
http://futureos.cpc-live.com/files/LambdaSpeak_RSX_by_TFM.zip --> RSX ROM für LambdaSpeak (Update: 29.01.2025)

Devilmarkus

Der Assemblerteil hatte noch Fehler, das ist nun behoben, etwaige Savegames passen allerdings nicht mehr...
https://cpcwiki.de
Dein Deutsches CPCWiki!