|
| 1 | +// The Knight's third episode, at Ashminster in the Ember Wastes, and the |
| 2 | +// anchoress's two quests, against the real server. |
| 3 | +process.env.PORT = '0'; |
| 4 | +process.env.DATA_DIR = require('fs').mkdtempSync(require('os').tmpdir() + '/tc-ashstory-test-'); |
| 5 | +let pass = 0, fail = 0; |
| 6 | +function check(name, cond, extra) { if (cond) { pass++; console.log('PASS -', name); } else { fail++; console.log('FAIL -', name, extra != null ? `(${JSON.stringify(extra)})` : ''); } } |
| 7 | +function makeMockSocket(label) { |
| 8 | + return { label, OPEN: 1, readyState: 1, sent: [], _handlers: {}, |
| 9 | + on(event, cb) { this._handlers[event] = cb; }, send(data) { this.sent.push(JSON.parse(data)); }, |
| 10 | + emit(event, ...args) { if (this._handlers[event]) this._handlers[event](...args); }, |
| 11 | + lastOfType(type) { for (let i = this.sent.length - 1; i >= 0; i--) if (this.sent[i].type === type) return this.sent[i]; return null; } }; |
| 12 | +} |
| 13 | +require('../server.js'); |
| 14 | +const { STORYLINES, QUEST_CATALOG } = require('../data/quests'); |
| 15 | +const { TITLES } = require('../data/titles'); |
| 16 | +const { EMBER_TOWN: T, ASH_SPOTS, ANCHORESS } = require('../data/emberTown'); |
| 17 | + |
| 18 | +const K = STORYLINES[3].chapters; |
| 19 | +check('the Knight has twelve chapters; the other classes keep their nine', K.length === 12 && [0, 1, 2, 4, 5].every((c) => STORYLINES[c].chapters.length === 9)); |
| 20 | +check('the third episode: find Ashminster, speak at the squint, lay fifteen to rest', K[9].objective.type === 'visit_room' && K[9].objective.room === 'ashminster' && K[10].objective.type === 'talk_npc' && K[10].objective.npcId === 'npc_anchoress' && K[11].objective.type === 'kill_mob' && K[11].objective.target === 15); |
| 21 | +check('each new chapter has its telling before and after, its level, and its pay', K.slice(9).every((c) => c.intro.length > 150 && c.outro.length > 150 && c.requiresLevel === 17 && c.xpReward > 0 && c.goldReward > 0 && c.title)); |
| 22 | +check('it keeps to the story the first two episodes told: the Order, the witches it had hunted, Hazel, and the page cut out', /leaf was cut out/.test(K[9].intro) && /five women it had been hunting/.test(K[10].outro) && /Hazel cut the one over the door/.test(K[10].outro) && /we were not ashamed of you/.test(K[10].outro) && /daisy wheel/.test(K[11].outro)); |
| 23 | +check('the anchoress says the same thing to everyone at her squint', /stopped, here, at this wall/.test(ANCHORESS.says[2]) && /five women the Order had been hunting/.test(ANCHORESS.says[2])); |
| 24 | +const f = QUEST_CATALOG.isolt_field, w = QUEST_CATALOG.isolt_watch; |
| 25 | +check('her two quests: one to do again, one long one done once that follows it and gives a title that exists', f && w && f.npcId === 'npc_anchoress' && !f.oneTime && f.type === 'kill_mob' && f.target === 10 && w.oneTime === true && w.requiresQuestDone === 'isolt_field' && w.target === 25 && w.grantsTitle === 'frith_keeper' && TITLES.frith_keeper && /Ashminster/.test(TITLES.frith_keeper.from)); |
| 26 | + |
| 27 | +setTimeout(() => { |
| 28 | + const h = global.__testHooks; |
| 29 | + const wss = global.__wssInstances[0]; |
| 30 | + const connHandler = (wss._handlers && wss._handlers.connection) || wss.listeners('connection')[0]; |
| 31 | + function join(tag, charId) { |
| 32 | + const s = makeMockSocket(tag); connHandler(s); |
| 33 | + s.emit('message', JSON.stringify({ type: 'join', name: tag, charId })); |
| 34 | + const p = h.players.get(s.lastOfType('init').id); |
| 35 | + p.accountKey = tag.toLowerCase(); s._isGuest = false; |
| 36 | + const prog = h.getProgress(p); prog.level = 18; if (!prog.story) prog.story = {}; |
| 37 | + return { s, p }; |
| 38 | + } |
| 39 | + const setChapter = (p, idx) => { h.getProgress(p).story[p.charId] = { chapter: idx, progress: 0, active: true }; }; |
| 40 | + const chapterOf = (p) => h.getProgress(p).story[p.charId].chapter; |
| 41 | + |
| 42 | + const A = join('Oathkeeper', 3); |
| 43 | + setChapter(A.p, 9); |
| 44 | + Object.assign(A.p, { room: 'ember_wastes', isDead: false, x: 10000, y: 18600 }); |
| 45 | + h.ashminster.tick([A.p]); |
| 46 | + check('standing at the arrival portal is not finding Ashminster', chapterOf(A.p) === 9); |
| 47 | + Object.assign(A.p, { x: T.x, y: T.y + 1900 }); h.ashminster.tick([A.p]); |
| 48 | + check('walking into the town completes "The Mother-House"', chapterOf(A.p) === 10, chapterOf(A.p)); |
| 49 | + check('the next chapter waits to be taken up, as every chapter does', h.getProgress(A.p).story[3].active === false); |
| 50 | + setChapter(A.p, 10); // she takes it up in her Journal |
| 51 | + Object.assign(A.p, { x: ASH_SPOTS.anchoress.x + 20, y: ASH_SPOTS.anchoress.y + 30 }); |
| 52 | + A.s.emit('message', JSON.stringify({ type: 'ash_use', id: 'anchoress' })); |
| 53 | + check('speaking at the squint completes "The Ninth Anchoress"', chapterOf(A.p) === 11, chapterOf(A.p)); |
| 54 | + setChapter(A.p, 11); |
| 55 | + for (let i = 0; i < 14; i++) h.storyEvent(A.p, 'kill_mob', { pool: 'ember_mob', mobType: 'ash_wraith' }); |
| 56 | + check('fourteen is not fifteen', chapterOf(A.p) === 11 && h.getProgress(A.p).story[3].progress === 14); |
| 57 | + A.s.sent.length = 0; |
| 58 | + h.storyEvent(A.p, 'kill_mob', { pool: 'ember_mob', mobType: 'ash_wraith' }); |
| 59 | + const done = A.s.lastOfType('story_chapter_complete'); |
| 60 | + check('the fifteenth completes the episode, and with it the Knight\'s whole story', chapterOf(A.p) === 12 && done && done.storyComplete === true, done && done.storyComplete); |
| 61 | + |
| 62 | + // a Witch who walks the same road has her own story untouched by it, and is still given the anchoress's work |
| 63 | + const B = join('Hedgewitch', 0); |
| 64 | + setChapter(B.p, 3); |
| 65 | + Object.assign(B.p, { room: 'ember_wastes', isDead: false, x: T.x, y: T.y + 1900 }); h.ashminster.tick([B.p]); |
| 66 | + Object.assign(B.p, { x: ASH_SPOTS.anchoress.x + 20, y: ASH_SPOTS.anchoress.y + 30 }); |
| 67 | + for (let i = 0; i < ANCHORESS.says.length + 2; i++) B.s.emit('message', JSON.stringify({ type: 'ash_use', id: 'anchoress' })); |
| 68 | + check('another class is not moved on in her own story by any of it', chapterOf(B.p) === 3); |
| 69 | + check('and after the anchoress has said her piece she is offered work', B.s.lastOfType('ash_quest') && B.s.lastOfType('ash_quest').npcId === 'npc_anchoress'); |
| 70 | + B.s.sent.length = 0; B.s.emit('message', JSON.stringify({ type: 'quest_talk', npcId: 'npc_anchoress' })); |
| 71 | + const offer = B.s.sent.find((m) => /quest/.test(m.type)); |
| 72 | + check('the quest she is offered is "Lay the Field"', offer && JSON.stringify(offer).includes('Lay the Field'), offer && offer.type); |
| 73 | + console.log(`\n${pass} passed, ${fail} failed`); |
| 74 | + process.exit(fail ? 1 : 0); |
| 75 | +}, 400); |
0 commit comments