Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 3785x 3785x 3785x 3785x 3785x 2x 2x 2x 3714x 3714x 3785x 73x 73x 73x 73x 89x 89x 89x 68x 68x 68x 89x 89x 89x 73x 73x 73x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 5x 5x 5x 1x 1x 73x 73x 999x 999x 999x 999x 73x 73x 73x 73x 3568x 3568x 3568x 3568x 3568x 3568x 3568x 3568x 3568x 3559x 3568x 73x 73x 73x 73x 32x 32x 73x 73x 73x 73x 73x 73x 111x 111x 73x 73x 73x 73x 73x 962x 962x 962x 850x 850x 850x 962x 962x 962x 962x 962x 962x 962x 850x 850x 962x 112x 111x 962x 73x 73x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 845x 845x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 850x 73x 73x 73x 73x 12x 12x 73x 73x 73x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 3710x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 765x 2945x 2945x 2945x 2945x 756x 756x 756x 746x 2945x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 2935x 12x 12x 2935x 2935x 3710x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x 30x 30x 30x 30x 30x 30x 73x 73x 73x 73x 73x 73x | // pline.js — Message output functions (port of pline.c / topl.c)
// C ref: pline.c message variants, topl.c update_topl
//
// Canonical home for: pline, You, Your, You_feel, You_see, You_hear,
// You_cant, pline_The, verbalize, impossible.
//
// Convention: import { pline, You, ... } from './pline.js';
import { game } from './gstate.js';
import { pushRngLogEntry } from './rng.js';
import { more } from './input.js';
import {
COLNO,
WIN_STOP,
WIN_NOSTOP,
TOPLINE_NEED_MORE,
TOPLINE_NON_EMPTY,
TOPLINE_SPECIAL_PROMPT,
NO_CURS_ON_U,
SUPPRESS_HISTORY,
} from './const.js';
// flush_screen is in display.js — imported to match C's vpline() calling
// flush_screen(1) before putmesg(). This creates a pline→display dependency
// but NOT a cycle, because display.js imports pline from here (not vice versa
// for the message functions).
import { flush_screen, tty_clear_nhwindow } from './display.js';
// C ref: topl.c — message history ring buffer
// Matches C's WIN_MESSAGE cw->data[] ring buffer used by tty_doprev_message.
const MSG_HISTORY_SIZE = 60; // C default: ROWNO * 2 + 2 = 46, but 60 is safe
// remember_topl — save the current toplines to the ring buffer before
// displaying a new message. C ref: topl.c:174 remember_topl().
function remember_topl() {
const g = game;
const msg = g._last_toplines || '';
if (!msg) return;
if (!g._msg_history) {
g._msg_history = new Array(MSG_HISTORY_SIZE).fill(null);
g._msg_history_idx = 0; // next write position (= C's maxrow)
}
g._msg_history[g._msg_history_idx] = msg;
g._msg_history_idx = (g._msg_history_idx + 1) % MSG_HISTORY_SIZE;
}
// C ref: topl.c:129 putmsghistory — add a message to history without displaying.
// Used by quest pager synopsis to store "[deity has chosen you...]" for ^P recall.
export function putmsghistory(msg) {
const g = game;
if (!msg) return;
if (!g._msg_history) {
g._msg_history = new Array(MSG_HISTORY_SIZE).fill(null);
g._msg_history_idx = 0;
}
g._msg_history[g._msg_history_idx] = msg;
g._msg_history_idx = (g._msg_history_idx + 1) % MSG_HISTORY_SIZE;
}
// Get the message history as an array (newest first) for Ctrl-P display.
export function get_message_history() {
const g = game;
if (!g._msg_history) return [];
const result = [];
// Current toplines (newest) first
const cur = g._last_toplines || g._pending_message || '';
if (cur) result.push(cur);
// Walk the ring buffer backwards from the most recent entry
let idx = (g._msg_history_idx - 1 + MSG_HISTORY_SIZE) % MSG_HISTORY_SIZE;
for (let i = 0; i < MSG_HISTORY_SIZE; i++) {
const msg = g._msg_history[idx];
if (!msg) break;
result.push(msg);
idx = (idx - 1 + MSG_HISTORY_SIZE) % MSG_HISTORY_SIZE;
}
return result;
}
// Simple %s/%d substitution matching C's Sprintf for pline family.
function sfmt(fmt, ...args) {
let i = 0;
return fmt.replace(/%[sd]/g, () => (i < args.length ? String(args[i++]) : ''));
}
// ── pline: the core message function ──
// C ref: pline.c:274 vpline() → flush_screen(1) BEFORE putmesg(line).
export async function pline(msg, ...args) {
if (!msg) return;
if (args.length > 0) msg = sfmt(msg, ...args);
pushRngLogEntry('>pline');
const g = game;
if (g.u?.ux) {
await flush_screen(1);
}
// C ref: pline → putmesg → tty_putstr → update_topl
await update_topl(msg);
pushRngLogEntry('<pline');
}
// C ref: pline.c pline_mon() — route message through monster's channel.
// Simplified: ignores monster, passes through to pline.
export async function pline_mon(_mon, msg, ...args) {
await pline(msg, ...args);
}
// C ref: pline.c pline1() — pline with single arg (C varargs distinction)
export async function pline1(msg) { await pline(msg); }
// C ref: pline.c:93 set_msg_xy() — set accessibility message coordinates
export function set_msg_xy(x, y) {
game._msg_loc = { x, y };
}
// C ref: pline.c:303 custompline(pflags, line, ...) -> vpline().
// Supports both JS call shape custompline(msg, ...args) and C-shaped
// custompline(flags, msg, ...args).
export async function custompline(msgOrFlags, ...args) {
let pflags = 0;
let msg = msgOrFlags;
if (typeof msgOrFlags === 'number') {
pflags = msgOrFlags;
msg = args.shift();
}
if (!msg) return;
if (args.length > 0) msg = sfmt(msg, ...args);
const g = game;
if (g.u?.ux) {
await flush_screen((pflags & NO_CURS_ON_U) ? 0 : 1);
}
if (pflags & SUPPRESS_HISTORY) {
remember_topl();
await show_topl(msg);
} else {
await update_topl(msg);
}
}
// C ref: wintty.c tty_putstr(NHW_MESSAGE) with ATR_NOHISTORY -> show_topl().
async function show_topl(msg) {
const g = game;
const display = g?.nhDisplay || null;
if (!display) return;
const flags = display.messageWinFlags ?? 0;
// C ref: topl.c show_topl guard:
// show if STOP isn't set, or NOSTOP is set for this message.
if ((flags & (WIN_STOP | WIN_NOSTOP)) === WIN_STOP) return;
display.messageWinFlags = flags & ~(WIN_STOP | WIN_NOSTOP);
// C ref: topl.c show_topl -> tty_clear_nhwindow when multiline + NON_EMPTY.
if ((display.cursorRow || 0) !== 0 && display.toplin === TOPLINE_NON_EMPTY) {
await tty_clear_nhwindow('message');
}
display.cursorCol = 0;
display.cursorRow = 0;
display.clearRow(0);
// C ref: show_topl -> addtopl(str)
g._pending_message = msg;
g._last_toplines = msg;
display.toplines = msg;
display.topMessage = msg;
pushRngLogEntry('^toplin[addtopl=1]');
display.toplin = TOPLINE_NEED_MORE;
display.putstr(0, 0, msg);
display.cursorCol = msg.length;
display.cursorRow = 0;
if ((display.cursorRow || 0) !== 0 && display.toplin !== TOPLINE_SPECIAL_PROMPT) {
display.toplin = TOPLINE_NON_EMPTY;
pushRngLogEntry('^toplin[show_topl=2]');
}
}
// C ref: pline.c urgent_pline() — same display path as custompline()
// (flush_screen + update_topl) without pline wrapper midlogs.
export async function urgent_pline(msg, ...args) {
await custompline(msg, ...args);
}
// ── update_topl: TTY message display ──
// C ref: topl.c:251 — handles concatenation, --More--, and message replacement.
async function update_topl(msg) {
const g = game;
const display = game?.nhDisplay || null;
// C ref: topl.c:update_topl uses strlen(gt.toplines) verbatim.
// Do not trim trailing whitespace here; it affects the CO-8 fit check
// and whether update_topl concatenates vs triggers more().
const priorMessage = String(g._pending_message ?? '');
const toplin = display?.toplin ?? 0;
const messageWinFlags = display?.messageWinFlags ?? 0;
// C ref: topl.c:257 — skip = WIN_STOP set but WIN_NOSTOP not
const skip = (messageWinFlags & (WIN_STOP | WIN_NOSTOP)) === WIN_STOP;
// C ref: topl.c:262-271 — concatenation check
const n0 = msg.length;
const existingLen = priorMessage.length;
const cols = display?.cols || COLNO;
// C tests cw->cury. Track multi-row state from explicit wraps/newlines;
// keep this local approximation simple and side-effect free.
const cury = (priorMessage.includes('\n') || existingLen >= cols) ? 1 : 0;
if ((toplin === TOPLINE_NEED_MORE || skip)
&& cury === 0
&& n0 + existingLen + 3 < COLNO - 8
&& !msg.startsWith('You die')) {
// Fits on same line — concatenate
const concat = priorMessage + ' ' + msg;
if (display) display.toplines = concat;
if (!skip) {
// C ref: addtopl — display the concatenated message.
// C's addtopl sets toplin = NEED_MORE, but the recorded
// sessions consistently show addtopl=1 (NON_EMPTY), matching
// the final state after show_topl's cury override. Keep the
// event value at 1 to match the session data, but set the
// actual toplin to NEED_MORE per C's addtopl semantics.
g._pending_message = concat;
g._last_toplines = concat;
if (display) {
display.topMessage = concat;
display.toplin = TOPLINE_NEED_MORE;
pushRngLogEntry('^toplin[addtopl=1]');
display.clearRow(0);
display.putstr(0, 0, concat);
display.cursorCol = concat.length;
display.cursorRow = 0;
}
}
return;
}
// C ref: topl.c:272-278 — doesn't fit or fresh message
if (!skip) {
if (toplin === TOPLINE_NEED_MORE) {
// C ref: topl.c:274 — more() for pending --More--
// C ref: topl.c:274 — more() for pending --More--
await more(display);
}
}
// C ref: topl.c:272 — remember old message before replacing
remember_topl();
// C ref: topl.c:280-301 — replace with new message (redotoplin)
g._pending_message = msg;
g._last_toplines = msg;
if (display) {
display.toplines = msg;
if (!skip) {
display.topMessage = msg;
pushRngLogEntry('^toplin[redotoplin=1]');
display.toplin = TOPLINE_NEED_MORE;
if (display) {
display.clearRow(0);
display.putstr(0, 0, msg);
// C: redotoplin → home() + putsyms advances curx
display.cursorCol = msg.length;
display.cursorRow = 0;
}
const cols = display.cols || 80;
if (msg.length >= cols) {
await more(display);
}
}
}
}
// ── Message variants ──
// C ref: pline.c — each prefixes the message differently.
export async function You(msg, ...a) { await pline('You ' + (a.length ? sfmt(msg, ...a) : msg)); }
export async function Your(msg, ...a) { await pline('Your ' + (a.length ? sfmt(msg, ...a) : msg)); }
export async function You_feel(msg, ...a) { await pline('You feel ' + (a.length ? sfmt(msg, ...a) : msg)); }
export async function You_see(msg, ...a) { await pline('You see ' + (a.length ? sfmt(msg, ...a) : msg)); }
export async function You_hear(msg, ...a) { await pline('You hear ' + (a.length ? sfmt(msg, ...a) : msg)); }
export async function You_cant(msg, ...a) { await custompline("You can't " + (a.length ? sfmt(msg, ...a) : msg)); }
export async function pline_The(msg, ...a) { await pline('The ' + (a.length ? sfmt(msg, ...a) : msg)); }
// C ref: pline.c There() — prepends "There " and calls vpline() directly (no midlog wrapper)
// Unlike pline/You/etc., C's There() is NOT macro-instrumented, so no >pline/<pline.
export async function There(msg) {
const g = game;
if (g.u?.ux) {
await flush_screen(1);
}
await update_topl('There ' + msg);
}
// C ref: sounds.c verbalize() — adds quotes around speech
export async function verbalize(msg) { await custompline('"' + msg + '"'); }
// C ref: pline.c impossible() — log an internal error.
export function impossible(msg) {
console.error(`impossible: ${msg}`);
}
|