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 | 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 71x 71x 142x 71x 73x 73x 71x 71x 71x 71x 71x 71x 73x 73x 73x 73x 73x 73x 73x 73x 73x 71x 71x 71x 71x 73x 73x 73x 73x 73x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 71x 5x 5x 5x 5x 71x 71x 71x 4x 4x 4x 4x 71x 71x 71x 4x 4x 4x 4x 71x 73x 73x 73x | // role.js — Role/race/gender/alignment initialization (port of role.c)
// role_init() consumes 0-2 RNG calls for quest NPC genders.
import { game } from './gstate.js';
import { rn2 } from './rng.js';
import { NON_PM, P_CLERIC_SPELL } from './const.js';
import { SPE_LIGHT } from './objects.js';
import { mons, M2_MALE, M2_FEMALE, M2_NEUTER, M2_PEACEFUL,
M2_NASTY, M2_STALK, M2_HOSTILE,
M3_CLOSE, M3_WANTSARTI, M3_WAITFORU,
MS_LEADER, MS_NEMESIS, PM_CLERIC } from './monsters.js';
import { is_male, is_female, is_neuter } from './mondata.js';
// Alignment values from generated data
const aligns = alignsData;
// Roles and races data — generated by scripts/generators/gen_roles.py
import { roles, races, aligns as alignsData } from './roles.js';
// C ref: role.c randrole_filtered() — pick random valid role
function randrole_filtered() {
// TODO: implement properly
return 0;
}
// C ref: role.c randrace() — pick random valid race for role
function randrace(_role) {
return 0; // human
}
// C ref: role.c randalign() — pick random valid alignment for role/race
function randalign(_role, _race) {
return 0; // neutral
}
// Validation stubs
function validrole(r) { return r >= 0 && r < 13; }
function validrace(_r, race) { return race >= 0; }
function validgend(_r, _race, _g) { return true; }
function validalign(_r, _race, _a) { return _a >= 0; }
// C ref: role.c plnamesuffix()
function plnamesuffix() {
// TODO: parse "-role-race-&c" suffixes from plname
// C ref: role.c:2243 — rigid_role_checks() is called from plnamesuffix
rigid_role_checks();
}
// C ref: role.c:1211 pick_align()
// RNG: rn2(aligns_ok) to select from valid alignments
function pick_align(rolenum, racenum, gendnum) {
let aligns_ok = 0;
for (let i = 0; i < 3; i++) {
if (ok_align(rolenum, racenum, gendnum, i))
aligns_ok++;
}
if (aligns_ok === 0) return -1; // ROLE_NONE
let pick = rn2(aligns_ok);
for (let i = 0; i < 3; i++) {
if (ok_align(rolenum, racenum, gendnum, i)) {
if (pick === 0) return i;
pick--;
}
}
return -1;
}
// C ref: role.c:1135 ok_align()
function ok_align(rolenum, racenum, _gendnum, alignnum) {
const ROLE_ALIGNS = 3;
if (alignnum < 0 || alignnum >= ROLE_ALIGNS) return false;
const role = roles[rolenum];
const race = races[racenum];
if (!role || !race) return true;
// C: AM_CHAOTIC=0x01, AM_NEUTRAL=0x02, AM_LAWFUL=0x04
const alignBits = [0x01, 0x02, 0x04]; // chaotic, neutral, lawful
const bit = alignBits[alignnum];
if (!bit) return false;
return !!(role.allow & bit) && !!(race.allow & bit);
}
// C ref: role.c:1234 rigid_role_checks()
// If role/race/align are constrained to single choice, fix them.
// RNG: pick_align calls rn2(aligns_ok) when initalign is unset (-1)
function rigid_role_checks() {
const g = game;
if (g.flags.initalign < 0) {
const tmp = pick_align(g.flags.initrole, g.flags.initrace, g.flags.initgend);
if (tmp >= 0) g.flags.initalign = tmp;
}
}
// C ref: role.c:1980 role_init()
// Sets up urole, urace, quest leader/nemesis gender, pantheon.
// RNG calls: 0-2 rn2(100) for quest NPC genders (only if gender-neutral).
export async function role_init() {
const g = game;
plnamesuffix();
if (!validrole(g.flags.initrole)) {
g.flags.initrole = randrole_filtered();
}
if (!validrace(g.flags.initrole, g.flags.initrace))
g.flags.initrace = randrace(g.flags.initrole);
if (g.flags.pantheon === -1) {
if (!validgend(g.flags.initrole, g.flags.initrace, g.flags.female))
g.flags.female = !g.flags.female;
}
if (!validgend(g.flags.initrole, g.flags.initrace, g.flags.initgend))
g.flags.initgend = g.flags.female ? 1 : 0;
if (!validalign(g.flags.initrole, g.flags.initrace, g.flags.initalign))
g.flags.initalign = randalign(g.flags.initrole, g.flags.initrace);
const alignmnt = aligns[g.flags.initalign]?.value ?? 0;
// Copy role/race data
if (roles[g.flags.initrole]) {
g.urole = { ...roles[g.flags.initrole] };
}
if (races[g.flags.initrace]) {
g.urace = { ...races[g.flags.initrace] };
}
// Fix up quest leader gender — RNG CALL if gender-neutral
if (g.urole && g.urole.ldrnum !== undefined && g.urole.ldrnum !== NON_PM) {
const pm = mons[g.urole.ldrnum];
if (pm) {
pm.msound = MS_LEADER;
pm.mflags2 |= M2_PEACEFUL;
pm.mflags3 |= M3_CLOSE;
pm.maligntyp = alignmnt * 3;
g.quest_status.ldrgend =
is_neuter(pm) ? 2 : is_female(pm) ? 1 : is_male(pm)
? 0 : (rn2(100) < 50 ? 1 : 0);
}
}
// Fix up quest guardian
if (g.urole && g.urole.guardnum !== undefined && g.urole.guardnum !== NON_PM) {
const pm = mons[g.urole.guardnum];
if (pm) {
pm.mflags2 |= M2_PEACEFUL;
pm.maligntyp = alignmnt * 3;
}
}
// Fix up quest nemesis — RNG CALL if gender-neutral
if (g.urole && g.urole.neminum !== undefined && g.urole.neminum !== NON_PM) {
const pm = mons[g.urole.neminum];
if (pm) {
pm.msound = MS_NEMESIS;
pm.mflags2 &= ~M2_PEACEFUL;
pm.mflags2 |= (M2_NASTY | M2_STALK | M2_HOSTILE);
pm.mflags3 &= ~M3_CLOSE;
pm.mflags3 |= M3_WANTSARTI | M3_WAITFORU;
g.quest_status.nemgend =
is_neuter(pm) ? 2 : is_female(pm) ? 1 : is_male(pm)
? 0 : (rn2(100) < 50 ? 1 : 0);
}
}
// Fix up pantheon — C ref: role.c:2064-2069
if (g.flags.pantheon === -1) {
let trycnt = 0;
g.flags.pantheon = g.flags.initrole;
// If current role has no gods, pick random roles until finding one
while (g.flags.pantheon >= 0 && !roles[g.flags.pantheon]?.lgod && ++trycnt < 100) {
// C: randrole(FALSE) = rn2(SIZE(roles)-1) = rn2(13)
// C's roles array has 14 entries (13 roles + sentinel), so SIZE-1=13
g.flags.pantheon = rn2(13);
}
if (g.flags.pantheon >= 0 && !roles[g.flags.pantheon]?.lgod) {
// Fallback: find first role with gods
for (let i = 0; i < roles.length - 1; i++) {
if (roles[i]?.lgod) { g.flags.pantheon = i; break; }
}
}
}
if (g.urole && !g.urole.lgod && roles[g.flags.pantheon]) {
g.urole.lgod = roles[g.flags.pantheon].lgod;
g.urole.ngod = roles[g.flags.pantheon].ngod;
g.urole.cgod = roles[g.flags.pantheon].cgod;
}
// C ref: role.c:2087-2088 — Cleric role changes light spell to clerical school
if (g.urole?.mnum === PM_CLERIC && game.objects[SPE_LIGHT]) {
game.objects[SPE_LIGHT].oc_subtyp = P_CLERIC_SPELL;
// oc_skill is a separate JS property (C #defines it as oc_subtyp)
game.objects[SPE_LIGHT].oc_skill = P_CLERIC_SPELL;
}
}
// C: uchangealign(alignment, reason) — change hero alignment
export function uchangealign(alignment, reason) {
// TODO: port from u_init.c
}
|