All files / js were.js

34.82% Statements 70/201
55.55% Branches 5/9
12.5% Functions 1/8
34.82% Lines 70/201

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 20273x 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 73x 73x 73x 73x 73x 73x 26108x 4x 4x 4x 4x 4x                               1118x       26108x 73x 73x 73x                     73x 73x 73x                       73x 73x 73x                                                           73x 73x 73x                                                                               73x 73x 73x                     73x 73x 73x                       73x 73x 73x        
// were.js — Lycanthropy (port of were.c)
// C ref: were.c — were-creature shape changes, summoning, player lycanthropy
 
import { game } from './gstate.js';
import { rn1, rn2, rnd } from './rng.js';
import { pline, You_hear, You_feel } from './pline.js';
import { Deaf, Hallucination, Stunned, Unchanging, Polymorph_control, Unaware, Protection_from_shape_changers } from './macros.js';
import { newsym, canseemon } from './display.js';
import { makemon } from './makemon.js';
import {
    mons,
    PM_WEREWOLF, PM_HUMAN_WEREWOLF, PM_WEREJACKAL, PM_HUMAN_WEREJACKAL,
    PM_WERERAT, PM_HUMAN_WERERAT, PM_WOLF, PM_WARG, PM_WINTER_WOLF,
    PM_WINTER_WOLF_CUB, PM_JACKAL, PM_FOX, PM_COYOTE,
    PM_SEWER_RAT, PM_GIANT_RAT, PM_RABID_RAT,
} from './monsters.js';
import { FULL_MOON, NON_PM, NO_MM_FLAGS } from './const.js';
import { Monnam, healmon, monflee, wake_nearto } from './mon.js';
import { tamedog } from './dog.js';
import { night } from './calendar.js';
 
import { pmname } from './do_name.js';
import { monsndx, set_mon_data, is_were, is_human } from './mondata.js';
import { rehumanize, set_uasmon, polymon } from './polyself.js';
import { onscary } from './monmove.js';
import { mon_break_armor } from './worn.js';
import { Soundeffect } from './sounds.js';
import { helpless, monster_nearby } from './hack.js';
import { paranoid_query } from './input.js';
import { Mgender, monnear } from './hacklib.js';
 
// STUBS: functions not yet ported (NO RNG consumption)
// Protection_from_shape_changers imported from macros.js
// helpless: imported from hack.js
import { possibly_unwield } from './weapon.js';
// paranoid_query: imported from input.js
const ParanoidWerechange = 0;
const LOW_PM = 0;
 
// C ref: were.c:9 were_change() — monthly/random were-creature transformation
export async function were_change(mon) {
    if (!is_were(mon.data)) return;
 
    if (is_human(mon.data)) {
        if (!Protection_from_shape_changers()
            && !rn2(night() ? (game.flags.moonphase === FULL_MOON ? 3 : 30)
                            : (game.flags.moonphase === FULL_MOON ? 10 : 50))) {
            await new_were(mon);
            game.were_changes = (game.were_changes ?? 0) + 1;
            if (!Deaf() && !canseemon(mon)) {
                let howler = null;
                switch (monsndx(mon.data)) {
                case PM_WEREWOLF: howler = 'wolf'; break;
                case PM_WEREJACKAL: howler = 'jackal'; break;
                default: break;
                }
                if (howler) {
                    await You_hear(`a ${howler} howling at the moon.`);
                    wake_nearto(mon.mx, mon.my, 4 * 4);
                }
            }
        }
    } else if (!rn2(30) || Protection_from_shape_changers()) {
        await new_were(mon);
        game.were_changes = (game.were_changes ?? 0) + 1;
    }
}
 
// C ref: were.c:48 counter_were() — get the other form
export function counter_were(pm) {
    switch (pm) {
    case PM_WEREWOLF: return PM_HUMAN_WEREWOLF;
    case PM_HUMAN_WEREWOLF: return PM_WEREWOLF;
    case PM_WEREJACKAL: return PM_HUMAN_WEREJACKAL;
    case PM_HUMAN_WEREJACKAL: return PM_WEREJACKAL;
    case PM_WERERAT: return PM_HUMAN_WERERAT;
    case PM_HUMAN_WERERAT: return PM_WERERAT;
    default: return NON_PM;
    }
}
 
// C ref: were.c:69 were_beastie() — convert similar monsters
export function were_beastie(pm) {
    switch (pm) {
    case PM_WERERAT: case PM_SEWER_RAT: case PM_GIANT_RAT: case PM_RABID_RAT:
        return PM_WERERAT;
    case PM_WEREJACKAL: case PM_JACKAL: case PM_FOX: case PM_COYOTE:
        return PM_WEREJACKAL;
    case PM_WEREWOLF: case PM_WOLF: case PM_WARG:
    case PM_WINTER_WOLF: case PM_WINTER_WOLF_CUB:
        return PM_WEREWOLF;
    default: return NON_PM;
    }
}
 
// C ref: were.c:96 new_were() — transform a were-creature
export async function new_were(mon) {
    if (Protection_from_shape_changers() && is_human(mon.data)) return;

    const pm = counter_were(monsndx(mon.data));
    if (pm < LOW_PM) return;

    if (canseemon(mon) && !Hallucination()) {
        const formName = is_human(mons[pm]) ? 'human'
            : (pmname(mons[pm], Mgender(mon)).replace(/^were/, '') || 'beast');
        await pline(`${Monnam(mon)} changes into a ${formName}.`);
    }

    set_mon_data(mon, mons[pm]);
    if (helpless(mon)) {
        mon.msleeping = 0;
        mon.mfrozen = 0;
        mon.mcanmove = 1;
    }
    // Regenerate 1/4 of lost HP
    healmon(mon, Math.trunc((mon.mhpmax - mon.mhp) / 4), 0);
    newsym(mon.mx, mon.my);
    await mon_break_armor(mon, false);
    possibly_unwield(mon, false);

    if (game.context?.mon_moving && !mon.mpeaceful
        && onscary(mon.mux, mon.muy, mon)
        && monnear(mon, mon.mux, mon.muy)) {
        await monflee(mon, rn1(9, 2), true, true);
    }
}
 
// C ref: were.c:141 were_summon() — summon a horde
export async function were_summon(ptr, yours, genbuf) {
    const pm = monsndx(ptr);
    let total = 0;
    let visible = 0;

    if (Protection_from_shape_changers() && !yours) return { total: 0, visible: 0 };

    const count = rnd(5);
    for (let i = count; i > 0; i--) {
        let typ;
        switch (pm) {
        case PM_WERERAT: case PM_HUMAN_WERERAT: {
            const r1 = rn2(3); const r2 = rn2(3);
            typ = r1 ? PM_SEWER_RAT : r2 ? PM_GIANT_RAT : PM_RABID_RAT;
            if (genbuf) genbuf.value = 'rat';
            break;
        }
        case PM_WEREJACKAL: case PM_HUMAN_WEREJACKAL: {
            const r1 = rn2(7); const r2 = rn2(3);
            typ = r1 ? PM_JACKAL : r2 ? PM_COYOTE : PM_FOX;
            if (genbuf) genbuf.value = 'jackal';
            break;
        }
        case PM_WEREWOLF: case PM_HUMAN_WEREWOLF: {
            const r1 = rn2(5); const r2 = rn2(2);
            typ = r1 ? PM_WOLF : r2 ? PM_WARG : PM_WINTER_WOLF;
            if (genbuf) genbuf.value = 'wolf';
            break;
        }
        default: continue;
        }
        const mtmp = await makemon(mons[typ], game.u.ux, game.u.uy, NO_MM_FLAGS);
        if (mtmp) {
            total++;
            if (canseemon(mtmp)) visible++;
        }
        if (yours && mtmp) await tamedog(mtmp, null, false);
    }
    return { total, visible };
}
 
// C ref: were.c:191 you_were() — player transforms into beast
export async function you_were() {
    if (Unchanging() || game.u.umonnum === game.u.ulycn) return;
    if (Polymorph_control() && !(Stunned() || Unaware())) {
        // Controlled transformation — would need yn prompt
        return;
    } else if (monster_nearby()) {
        return;
    }
    game.were_changes = (game.were_changes ?? 0) + 1;
    await polymon(game.u.ulycn);
}
 
// C ref: were.c:213 you_unwere() — player reverts from beast
export async function you_unwere(purify) {
    if (purify) {
        await You_feel('purified.');
        set_ulycn(NON_PM);
    }
    if (!Unchanging() && is_were(game.youmonst?.data)
        && !monster_nearby()) {
        await rehumanize();
    } else if (is_were(game.youmonst?.data) && !game.u.mtimedone) {
        game.u.mtimedone = rn1(200, 200);
    }
}
 
// C ref: were.c:231 set_ulycn() — set/clear lycanthropy
export function set_ulycn(which) {
    game.u.ulycn = which;
    set_uasmon();
}