clibuddy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/adventure/adventureUI.d.ts +24 -0
- package/dist/adventure/adventureUI.js +290 -0
- package/dist/adventure/adventures.d.ts +4 -0
- package/dist/adventure/adventures.js +206 -0
- package/dist/adventure/biomes.d.ts +30 -0
- package/dist/adventure/biomes.js +80 -0
- package/dist/adventure/combat/combat.d.ts +14 -0
- package/dist/adventure/combat/combat.js +638 -0
- package/dist/adventure/combat/combatUI.d.ts +5 -0
- package/dist/adventure/combat/combatUI.js +116 -0
- package/dist/adventure/combat/conditions.d.ts +20 -0
- package/dist/adventure/combat/conditions.js +111 -0
- package/dist/adventure/combat/enemies.d.ts +4 -0
- package/dist/adventure/combat/enemies.js +430 -0
- package/dist/adventure/combat/gear.d.ts +3 -0
- package/dist/adventure/combat/gear.js +199 -0
- package/dist/adventure/combat/skills.d.ts +6 -0
- package/dist/adventure/combat/skills.js +197 -0
- package/dist/adventure/combat.d.ts +31 -0
- package/dist/adventure/combat.js +732 -0
- package/dist/adventure/combatUI.d.ts +5 -0
- package/dist/adventure/combatUI.js +116 -0
- package/dist/adventure/endless.d.ts +18 -0
- package/dist/adventure/endless.js +154 -0
- package/dist/adventure/enemies.d.ts +4 -0
- package/dist/adventure/enemies.js +320 -0
- package/dist/adventure/engine.d.ts +20 -0
- package/dist/adventure/engine.js +137 -0
- package/dist/adventure/gear.d.ts +3 -0
- package/dist/adventure/gear.js +149 -0
- package/dist/adventure/generation/biomes.d.ts +30 -0
- package/dist/adventure/generation/biomes.js +102 -0
- package/dist/adventure/generation/endless.d.ts +18 -0
- package/dist/adventure/generation/endless.js +154 -0
- package/dist/adventure/generation/generator.d.ts +9 -0
- package/dist/adventure/generation/generator.js +245 -0
- package/dist/adventure/generation/templates.d.ts +25 -0
- package/dist/adventure/generation/templates.js +228 -0
- package/dist/adventure/generator.d.ts +9 -0
- package/dist/adventure/generator.js +245 -0
- package/dist/adventure/skills.d.ts +6 -0
- package/dist/adventure/skills.js +197 -0
- package/dist/adventure/templates.d.ts +25 -0
- package/dist/adventure/templates.js +228 -0
- package/dist/adventure/types.d.ts +236 -0
- package/dist/adventure/types.js +97 -0
- package/dist/app/state.d.ts +49 -0
- package/dist/app/state.js +51 -0
- package/dist/buddy/activities.d.ts +16 -0
- package/dist/buddy/activities.js +90 -0
- package/dist/buddy/decay.d.ts +3 -0
- package/dist/buddy/decay.js +45 -0
- package/dist/buddy/leveling.d.ts +11 -0
- package/dist/buddy/leveling.js +25 -0
- package/dist/buddy/roll.d.ts +4 -0
- package/dist/buddy/roll.js +61 -0
- package/dist/buddy/species.d.ts +4 -0
- package/dist/buddy/species.js +592 -0
- package/dist/buddy/titles.d.ts +17 -0
- package/dist/buddy/titles.js +89 -0
- package/dist/buddy/types.d.ts +92 -0
- package/dist/buddy/types.js +21 -0
- package/dist/commands/actions.d.ts +2 -0
- package/dist/commands/actions.js +141 -0
- package/dist/commands/admin.d.ts +2 -0
- package/dist/commands/admin.js +202 -0
- package/dist/commands/registry.d.ts +25 -0
- package/dist/commands/registry.js +31 -0
- package/dist/commands/social.d.ts +2 -0
- package/dist/commands/social.js +92 -0
- package/dist/dialogue/engine.d.ts +7 -0
- package/dist/dialogue/engine.js +68 -0
- package/dist/dialogue/lines.d.ts +26 -0
- package/dist/dialogue/lines.js +294 -0
- package/dist/events/engine.d.ts +20 -0
- package/dist/events/engine.js +51 -0
- package/dist/events/events.d.ts +13 -0
- package/dist/events/events.js +149 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1665 -0
- package/dist/inventory/finding.d.ts +11 -0
- package/dist/inventory/finding.js +99 -0
- package/dist/inventory/items.d.ts +31 -0
- package/dist/inventory/items.js +63 -0
- package/dist/minigames/copycat.d.ts +2 -0
- package/dist/minigames/copycat.js +153 -0
- package/dist/minigames/fetch.d.ts +2 -0
- package/dist/minigames/fetch.js +179 -0
- package/dist/minigames/moodmatch.d.ts +2 -0
- package/dist/minigames/moodmatch.js +144 -0
- package/dist/minigames/quickpaws.d.ts +2 -0
- package/dist/minigames/quickpaws.js +142 -0
- package/dist/minigames/registry.d.ts +5 -0
- package/dist/minigames/registry.js +16 -0
- package/dist/minigames/rpsplus.d.ts +2 -0
- package/dist/minigames/rpsplus.js +168 -0
- package/dist/minigames/treasurehunt.d.ts +2 -0
- package/dist/minigames/treasurehunt.js +146 -0
- package/dist/minigames/types.d.ts +30 -0
- package/dist/minigames/types.js +69 -0
- package/dist/rendering/commandPalette.d.ts +16 -0
- package/dist/rendering/commandPalette.js +77 -0
- package/dist/rendering/display.d.ts +9 -0
- package/dist/rendering/display.js +231 -0
- package/dist/rendering/inventoryUI.d.ts +14 -0
- package/dist/rendering/inventoryUI.js +99 -0
- package/dist/rendering/items.d.ts +7 -0
- package/dist/rendering/items.js +34 -0
- package/dist/rendering/listUtils.d.ts +3 -0
- package/dist/rendering/listUtils.js +24 -0
- package/dist/rendering/minigameUI.d.ts +11 -0
- package/dist/rendering/minigameUI.js +37 -0
- package/dist/rendering/overlayUI.d.ts +24 -0
- package/dist/rendering/overlayUI.js +184 -0
- package/dist/rendering/scene.d.ts +8 -0
- package/dist/rendering/scene.js +87 -0
- package/dist/rendering/screen.d.ts +43 -0
- package/dist/rendering/screen.js +97 -0
- package/dist/sound/sound.d.ts +11 -0
- package/dist/sound/sound.js +55 -0
- package/dist/state/save.d.ts +5 -0
- package/dist/state/save.js +100 -0
- package/dist/state/settings.d.ts +7 -0
- package/dist/state/settings.js +81 -0
- package/dist/story/mainStory.d.ts +4 -0
- package/dist/story/mainStory.js +3111 -0
- package/dist/story/npcs.d.ts +17 -0
- package/dist/story/npcs.js +137 -0
- package/dist/story/progress.d.ts +26 -0
- package/dist/story/progress.js +168 -0
- package/dist/story/seasonal.d.ts +6 -0
- package/dist/story/seasonal.js +96 -0
- package/dist/story/shop.d.ts +7 -0
- package/dist/story/shop.js +26 -0
- package/dist/story/sideQuests.d.ts +4 -0
- package/dist/story/sideQuests.js +151 -0
- package/dist/story/types.d.ts +61 -0
- package/dist/story/types.js +36 -0
- package/dist/updates.d.ts +23 -0
- package/dist/updates.js +142 -0
- package/package.json +53 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface NPC {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
title: string;
|
|
5
|
+
art: string[];
|
|
6
|
+
/** Dialogue pools keyed by story flag milestones (checked in order, first match wins) */
|
|
7
|
+
dialogueByFlag: {
|
|
8
|
+
flag: string;
|
|
9
|
+
lines: string[];
|
|
10
|
+
}[];
|
|
11
|
+
defaultDialogue: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare const NPCS: Record<string, NPC>;
|
|
14
|
+
/** Get NPC dialogue based on current story flags */
|
|
15
|
+
export declare function getNPCDialogue(npcId: string, storyFlags: string[]): string;
|
|
16
|
+
export declare function getNPC(id: string): NPC | undefined;
|
|
17
|
+
//# sourceMappingURL=npcs.d.ts.map
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NPCS = void 0;
|
|
4
|
+
exports.getNPCDialogue = getNPCDialogue;
|
|
5
|
+
exports.getNPC = getNPC;
|
|
6
|
+
exports.NPCS = {
|
|
7
|
+
pip: {
|
|
8
|
+
id: "pip",
|
|
9
|
+
name: "Pip",
|
|
10
|
+
title: "Merchant Mouse",
|
|
11
|
+
art: [" (\\./) ", " (o.o) ", " (\")(\")"],
|
|
12
|
+
dialogueByFlag: [
|
|
13
|
+
{ flag: "ch8_complete", lines: [
|
|
14
|
+
"We did it! The world is safe! ...Can I interest you in some celebratory snacks?",
|
|
15
|
+
"I never thought a mouse like me would help save the world!",
|
|
16
|
+
"Business is booming now that the roads are safe again!",
|
|
17
|
+
] },
|
|
18
|
+
{ flag: "ch4_complete", lines: [
|
|
19
|
+
"That swamp was TERRIFYING. But I'd do it again. ...From a distance.",
|
|
20
|
+
"I've been collecting some rare goods from grateful travelers!",
|
|
21
|
+
"The Stones... someone really broke them on purpose? That's scary.",
|
|
22
|
+
] },
|
|
23
|
+
{ flag: "ch1_complete", lines: [
|
|
24
|
+
"Oh, you're back! I've set up a little shop — take a look!",
|
|
25
|
+
"The forest feels calmer now. Thank you for fixing the Stone!",
|
|
26
|
+
"I found some interesting things on the road. Want to trade?",
|
|
27
|
+
"P-please be careful out there. I worry, you know?",
|
|
28
|
+
] },
|
|
29
|
+
],
|
|
30
|
+
defaultDialogue: [
|
|
31
|
+
"P-please don't eat me! I'm just a merchant!",
|
|
32
|
+
"I sell things! Good things! Useful things!",
|
|
33
|
+
"The forest has been acting strange lately...",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
shelly: {
|
|
37
|
+
id: "shelly",
|
|
38
|
+
name: "Elder Shelly",
|
|
39
|
+
title: "Wise Tortoise",
|
|
40
|
+
art: [" .---. ", " / o o \\", " \\_____/", " /| |\\"],
|
|
41
|
+
dialogueByFlag: [
|
|
42
|
+
{ flag: "ch8_complete", lines: [
|
|
43
|
+
"The Harmony is restored. I've waited centuries for this day.",
|
|
44
|
+
"You remind me of the first guardians. Perhaps even braver.",
|
|
45
|
+
"Rest now, little one. You've earned it.",
|
|
46
|
+
] },
|
|
47
|
+
{ flag: "ch5_complete", lines: [
|
|
48
|
+
"No more riddles. This is war. The Shadow Beast is real.",
|
|
49
|
+
"The Stranger... I should have seen it. I should have known.",
|
|
50
|
+
"The ruins hold the truth. Everything we need is written in stone.",
|
|
51
|
+
] },
|
|
52
|
+
{ flag: "ch2_complete", lines: [
|
|
53
|
+
"The caves sing again! You have a gift, young one.",
|
|
54
|
+
"There are six Stones in total. Each in a different realm.",
|
|
55
|
+
"I sense you have questions. Patience — the answers will come.",
|
|
56
|
+
"Would you like to hear an old story? I have many.",
|
|
57
|
+
] },
|
|
58
|
+
],
|
|
59
|
+
defaultDialogue: [
|
|
60
|
+
"Hmm? A visitor? It's been... decades.",
|
|
61
|
+
"All things return to the source, in time.",
|
|
62
|
+
"These crystals are weeping. Can you hear them?",
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
blaze: {
|
|
66
|
+
id: "blaze",
|
|
67
|
+
name: "Blaze",
|
|
68
|
+
title: "Rival Phoenix",
|
|
69
|
+
art: [" )\\*/( ", " (^><^) ", " \\=/\\=/"],
|
|
70
|
+
dialogueByFlag: [
|
|
71
|
+
{ flag: "ch8_complete", lines: [
|
|
72
|
+
"We... actually did it. Don't let it go to your head.",
|
|
73
|
+
"I guess having friends isn't the WORST thing.",
|
|
74
|
+
"Next time there's a world-ending crisis, call me first. I was bored.",
|
|
75
|
+
] },
|
|
76
|
+
{ flag: "ch6_complete", lines: [
|
|
77
|
+
"Alright, I'll admit it. You're not terrible at this.",
|
|
78
|
+
"The Shadow Beast is real. I've felt its presence. We fight together.",
|
|
79
|
+
"Let's finish this. Together. ...Don't make it weird.",
|
|
80
|
+
] },
|
|
81
|
+
{ flag: "ch3_complete", lines: [
|
|
82
|
+
"You beat me. ONCE. Don't get used to it.",
|
|
83
|
+
"Maybe you're not totally useless. MAYBE.",
|
|
84
|
+
"I'm still hunting the Stones. Stay out of my way. ...Unless you need help.",
|
|
85
|
+
] },
|
|
86
|
+
],
|
|
87
|
+
defaultDialogue: [
|
|
88
|
+
"You? Save the world? Please.",
|
|
89
|
+
"The Stones belong to the strong. That's ME.",
|
|
90
|
+
"Out of my way, small fry.",
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
stranger: {
|
|
94
|
+
id: "stranger",
|
|
95
|
+
name: "???",
|
|
96
|
+
title: "The Stranger",
|
|
97
|
+
art: [" /?\\ ", " {. .} ", " \\?/"],
|
|
98
|
+
dialogueByFlag: [
|
|
99
|
+
{ flag: "ch8_complete", lines: [
|
|
100
|
+
"Thank you... for remembering who I was.",
|
|
101
|
+
"The Harmony Stones shine again. I can finally rest.",
|
|
102
|
+
"Watch over this world. It's more fragile than it looks.",
|
|
103
|
+
] },
|
|
104
|
+
{ flag: "ch7_complete", lines: [
|
|
105
|
+
"You freed me. I didn't think it was possible.",
|
|
106
|
+
"I was the first guardian. The Beast consumed me from within.",
|
|
107
|
+
"I tried to warn you... I tried to protect you by pushing you away.",
|
|
108
|
+
] },
|
|
109
|
+
{ flag: "ch5_complete", lines: [
|
|
110
|
+
"Turn back. You don't understand what you're awakening.",
|
|
111
|
+
"The Stones were a seal. Breaking them was necessary... and terrible.",
|
|
112
|
+
"...you remind me of someone I used to be.",
|
|
113
|
+
] },
|
|
114
|
+
],
|
|
115
|
+
defaultDialogue: [
|
|
116
|
+
"...",
|
|
117
|
+
"Turn back.",
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
/** Get NPC dialogue based on current story flags */
|
|
122
|
+
function getNPCDialogue(npcId, storyFlags) {
|
|
123
|
+
const npc = exports.NPCS[npcId];
|
|
124
|
+
if (!npc)
|
|
125
|
+
return "...";
|
|
126
|
+
// Check flags in order — first match wins (most progressed flag first)
|
|
127
|
+
for (const pool of npc.dialogueByFlag) {
|
|
128
|
+
if (storyFlags.includes(pool.flag)) {
|
|
129
|
+
return pool.lines[Math.floor(Math.random() * pool.lines.length)];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return npc.defaultDialogue[Math.floor(Math.random() * npc.defaultDialogue.length)];
|
|
133
|
+
}
|
|
134
|
+
function getNPC(id) {
|
|
135
|
+
return exports.NPCS[id];
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=npcs.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BuddyState } from "../buddy/types";
|
|
2
|
+
import { QuestChapter, QuestChain, QuestProgress, SavedAdventureState } from "./types";
|
|
3
|
+
import { AdventureState } from "../adventure/types";
|
|
4
|
+
/** Complete a chapter: add to completed list, set story flag, award rewards */
|
|
5
|
+
export declare function completeChapter(state: BuddyState, chapter: QuestChapter): BuddyState;
|
|
6
|
+
/** Set active quest */
|
|
7
|
+
export declare function setActiveQuest(state: BuddyState, chainId: string, chapterId: string): BuddyState;
|
|
8
|
+
/** Add a lore fragment */
|
|
9
|
+
export declare function addLoreFragment(state: BuddyState, fragmentId: string): BuddyState;
|
|
10
|
+
/** Add gold */
|
|
11
|
+
export declare function addGold(state: BuddyState, amount: number): BuddyState;
|
|
12
|
+
/** Spend gold (returns null if insufficient) */
|
|
13
|
+
export declare function spendGold(state: BuddyState, amount: number): BuddyState | null;
|
|
14
|
+
/** Get the next available chapter in a chain */
|
|
15
|
+
export declare function getNextChapter(chain: QuestChain, progress: QuestProgress): QuestChapter | null;
|
|
16
|
+
/** Get chain completion percentage */
|
|
17
|
+
export declare function getChainProgress(chain: QuestChain, progress: QuestProgress): number;
|
|
18
|
+
/** Save mid-adventure progress for quest resumption */
|
|
19
|
+
export declare function saveQuestAdventure(state: BuddyState, adventure: AdventureState): BuddyState;
|
|
20
|
+
/** Clear saved adventure state */
|
|
21
|
+
export declare function clearSavedAdventure(state: BuddyState): BuddyState;
|
|
22
|
+
/** Check if there's a saved adventure to resume */
|
|
23
|
+
export declare function hasSavedAdventure(state: BuddyState): boolean;
|
|
24
|
+
/** Restore AdventureState from saved data */
|
|
25
|
+
export declare function restoreAdventureState(saved: SavedAdventureState): AdventureState;
|
|
26
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.completeChapter = completeChapter;
|
|
4
|
+
exports.setActiveQuest = setActiveQuest;
|
|
5
|
+
exports.addLoreFragment = addLoreFragment;
|
|
6
|
+
exports.addGold = addGold;
|
|
7
|
+
exports.spendGold = spendGold;
|
|
8
|
+
exports.getNextChapter = getNextChapter;
|
|
9
|
+
exports.getChainProgress = getChainProgress;
|
|
10
|
+
exports.saveQuestAdventure = saveQuestAdventure;
|
|
11
|
+
exports.clearSavedAdventure = clearSavedAdventure;
|
|
12
|
+
exports.hasSavedAdventure = hasSavedAdventure;
|
|
13
|
+
exports.restoreAdventureState = restoreAdventureState;
|
|
14
|
+
/** Complete a chapter: add to completed list, set story flag, award rewards */
|
|
15
|
+
function completeChapter(state, chapter) {
|
|
16
|
+
const progress = { ...state.questProgress };
|
|
17
|
+
// Add to completed
|
|
18
|
+
if (!progress.completedChapters.includes(chapter.id)) {
|
|
19
|
+
progress.completedChapters = [...progress.completedChapters, chapter.id];
|
|
20
|
+
}
|
|
21
|
+
// Set story flag
|
|
22
|
+
if (chapter.storyFlag && !progress.storyFlags.includes(chapter.storyFlag)) {
|
|
23
|
+
progress.storyFlags = [...progress.storyFlags, chapter.storyFlag];
|
|
24
|
+
}
|
|
25
|
+
// Clear active quest if this was it
|
|
26
|
+
if (progress.activeChapter === chapter.id) {
|
|
27
|
+
progress.activeChapter = undefined;
|
|
28
|
+
progress.activeChain = undefined;
|
|
29
|
+
}
|
|
30
|
+
// Award gold
|
|
31
|
+
progress.gold += chapter.rewards.gold;
|
|
32
|
+
// Award XP
|
|
33
|
+
let xp = state.xp + chapter.rewards.xp;
|
|
34
|
+
// Award items
|
|
35
|
+
let inventory = state.inventory;
|
|
36
|
+
if (chapter.rewards.items) {
|
|
37
|
+
for (const itemId of chapter.rewards.items) {
|
|
38
|
+
const existing = inventory.find((s) => s.itemId === itemId);
|
|
39
|
+
if (!existing) {
|
|
40
|
+
inventory = [...inventory, { itemId, count: 1 }];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Award title
|
|
45
|
+
let titles = state.titles;
|
|
46
|
+
if (chapter.rewards.titleId && !titles.includes(chapter.rewards.titleId)) {
|
|
47
|
+
titles = [...titles, chapter.rewards.titleId];
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
...state,
|
|
51
|
+
questProgress: progress,
|
|
52
|
+
xp,
|
|
53
|
+
inventory,
|
|
54
|
+
titles,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Set active quest */
|
|
58
|
+
function setActiveQuest(state, chainId, chapterId) {
|
|
59
|
+
return {
|
|
60
|
+
...state,
|
|
61
|
+
questProgress: {
|
|
62
|
+
...state.questProgress,
|
|
63
|
+
activeChain: chainId,
|
|
64
|
+
activeChapter: chapterId,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Add a lore fragment */
|
|
69
|
+
function addLoreFragment(state, fragmentId) {
|
|
70
|
+
if (state.questProgress.loreFragments.includes(fragmentId))
|
|
71
|
+
return state;
|
|
72
|
+
return {
|
|
73
|
+
...state,
|
|
74
|
+
questProgress: {
|
|
75
|
+
...state.questProgress,
|
|
76
|
+
loreFragments: [...state.questProgress.loreFragments, fragmentId],
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Add gold */
|
|
81
|
+
function addGold(state, amount) {
|
|
82
|
+
return {
|
|
83
|
+
...state,
|
|
84
|
+
questProgress: {
|
|
85
|
+
...state.questProgress,
|
|
86
|
+
gold: state.questProgress.gold + amount,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Spend gold (returns null if insufficient) */
|
|
91
|
+
function spendGold(state, amount) {
|
|
92
|
+
if (state.questProgress.gold < amount)
|
|
93
|
+
return null;
|
|
94
|
+
return {
|
|
95
|
+
...state,
|
|
96
|
+
questProgress: {
|
|
97
|
+
...state.questProgress,
|
|
98
|
+
gold: state.questProgress.gold - amount,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Get the next available chapter in a chain */
|
|
103
|
+
function getNextChapter(chain, progress) {
|
|
104
|
+
for (const chapter of chain.chapters) {
|
|
105
|
+
if (!progress.completedChapters.includes(chapter.id)) {
|
|
106
|
+
// Check prerequisites
|
|
107
|
+
const allPrereqsMet = chapter.prerequisites.every((p) => progress.completedChapters.includes(p));
|
|
108
|
+
if (allPrereqsMet)
|
|
109
|
+
return chapter;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/** Get chain completion percentage */
|
|
115
|
+
function getChainProgress(chain, progress) {
|
|
116
|
+
const completed = chain.chapters.filter((c) => progress.completedChapters.includes(c.id)).length;
|
|
117
|
+
return Math.round((completed / chain.chapters.length) * 100);
|
|
118
|
+
}
|
|
119
|
+
/** Save mid-adventure progress for quest resumption */
|
|
120
|
+
function saveQuestAdventure(state, adventure) {
|
|
121
|
+
const saved = {
|
|
122
|
+
adventureId: adventure.adventureId,
|
|
123
|
+
currentRoomId: adventure.currentRoomId,
|
|
124
|
+
roomsVisited: adventure.roomsVisited,
|
|
125
|
+
morale: adventure.morale,
|
|
126
|
+
lootCollected: adventure.lootCollected,
|
|
127
|
+
xpEarned: adventure.xpEarned,
|
|
128
|
+
battlesWon: adventure.battlesWon,
|
|
129
|
+
battlesFled: adventure.battlesFled,
|
|
130
|
+
damageTaken: adventure.damageTaken,
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
...state,
|
|
134
|
+
questProgress: { ...state.questProgress, savedAdventure: saved },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Clear saved adventure state */
|
|
138
|
+
function clearSavedAdventure(state) {
|
|
139
|
+
return {
|
|
140
|
+
...state,
|
|
141
|
+
questProgress: { ...state.questProgress, savedAdventure: undefined },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** Check if there's a saved adventure to resume */
|
|
145
|
+
function hasSavedAdventure(state) {
|
|
146
|
+
return !!state.questProgress.savedAdventure;
|
|
147
|
+
}
|
|
148
|
+
/** Restore AdventureState from saved data */
|
|
149
|
+
function restoreAdventureState(saved) {
|
|
150
|
+
return {
|
|
151
|
+
active: true,
|
|
152
|
+
adventureId: saved.adventureId,
|
|
153
|
+
currentRoomId: saved.currentRoomId,
|
|
154
|
+
roomsVisited: saved.roomsVisited,
|
|
155
|
+
phase: "room",
|
|
156
|
+
morale: saved.morale,
|
|
157
|
+
textPage: 0,
|
|
158
|
+
textFullyShown: false,
|
|
159
|
+
lootCollected: saved.lootCollected,
|
|
160
|
+
xpEarned: saved.xpEarned,
|
|
161
|
+
battlesWon: saved.battlesWon,
|
|
162
|
+
battlesLost: 0,
|
|
163
|
+
battlesFled: saved.battlesFled,
|
|
164
|
+
damageTaken: saved.damageTaken,
|
|
165
|
+
buddyReaction: "Right where we left off!",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { QuestChain } from "./types";
|
|
2
|
+
/** Winter Frost Festival — sample seasonal quest */
|
|
3
|
+
export declare const WINTER_FROST: QuestChain;
|
|
4
|
+
/** Get seasonal quests active right now */
|
|
5
|
+
export declare function getSeasonalQuests(): QuestChain[];
|
|
6
|
+
//# sourceMappingURL=seasonal.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WINTER_FROST = void 0;
|
|
4
|
+
exports.getSeasonalQuests = getSeasonalQuests;
|
|
5
|
+
const types_1 = require("../adventure/types");
|
|
6
|
+
function makeAdventure(def) {
|
|
7
|
+
return { ...def, roomMap: (0, types_1.buildRoomMap)(def.rooms) };
|
|
8
|
+
}
|
|
9
|
+
/** Winter Frost Festival — sample seasonal quest */
|
|
10
|
+
exports.WINTER_FROST = {
|
|
11
|
+
id: "winter_frost",
|
|
12
|
+
name: "Winter Frost Festival",
|
|
13
|
+
description: "A magical winter celebration! Ice enemies, snow, and holiday cheer.",
|
|
14
|
+
type: "seasonal",
|
|
15
|
+
chapters: [
|
|
16
|
+
{
|
|
17
|
+
id: "winter_1", name: "Frozen Festivities",
|
|
18
|
+
description: "The Frost Festival has arrived! But something is freezing the decorations...",
|
|
19
|
+
prerequisites: [], levelRequired: 3,
|
|
20
|
+
storyFlag: "winter_1_complete",
|
|
21
|
+
rewards: { xp: 60, gold: 40, items: ["golden_apple"] },
|
|
22
|
+
acts: [{ id: "act1", name: "Festival", type: "dungeon", buildAdventure: () => makeAdventure({
|
|
23
|
+
id: "winter_1", name: "Frozen Festivities",
|
|
24
|
+
description: "The Frost Festival is under threat!", difficulty: "medium",
|
|
25
|
+
levelRequired: 3, energyCost: 15, hungerCost: 10, completionXp: 60,
|
|
26
|
+
startRoomId: "w1_start",
|
|
27
|
+
rooms: [
|
|
28
|
+
{ id: "w1_start", type: "narrative", title: "The Festival Grounds",
|
|
29
|
+
text: [
|
|
30
|
+
"Snowflakes drift gently over a festive clearing.",
|
|
31
|
+
"Lanterns, garlands, and a massive ice sculpture of a buddy!",
|
|
32
|
+
"But the decorations are frozen solid. The hot cocoa is ICE.",
|
|
33
|
+
"Something is making this winter TOO cold.",
|
|
34
|
+
],
|
|
35
|
+
buddyLine: "Brrr! This isn't normal cold... something magical is happening!",
|
|
36
|
+
choices: [
|
|
37
|
+
{ label: "Follow the frost trail", nextRoomId: "w1_combat" },
|
|
38
|
+
{ label: "Ask the festival-goers", nextRoomId: "w1_event" },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{ id: "w1_event", type: "event", title: "Festival Stall",
|
|
42
|
+
text: ["A shivering frog runs a hot cocoa stand.", "\"F-f-free cocoa if you stop this c-c-cold!\""],
|
|
43
|
+
buddyLine: "Deal!",
|
|
44
|
+
nextRoomId: "w1_combat",
|
|
45
|
+
eventChoices: [
|
|
46
|
+
{ label: "Take the cocoa", outcome: "The cocoa warms you from the inside! Delicious!", effect: { hp: 15, happiness: 10 } },
|
|
47
|
+
{ label: "Save it for later", outcome: "\"Suit yourself!\" the frog says, teeth chattering." },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{ id: "w1_combat", type: "combat", title: "Ice Path",
|
|
51
|
+
text: ["The frost trail leads to a clearing. An ice-coated snail blocks the way!"],
|
|
52
|
+
buddyLine: "Even the snails are frozen!", enemyId: "snail", nextRoomId: "w1_boss",
|
|
53
|
+
},
|
|
54
|
+
{ id: "w1_boss", type: "boss", title: "The Frost Crystal",
|
|
55
|
+
text: [
|
|
56
|
+
"A massive frost crystal pulses at the center of the clearing.",
|
|
57
|
+
"It's not alive — but the corrupted golem guarding it IS.",
|
|
58
|
+
"Defeat the guardian to shatter the frost crystal and save the festival!",
|
|
59
|
+
],
|
|
60
|
+
buddyLine: "For hot cocoa! I mean — for the FESTIVAL!",
|
|
61
|
+
enemyId: "golem",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
}) }],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
const SEASONAL_WINDOWS = [
|
|
69
|
+
{ chain: exports.WINTER_FROST, startMonth: 12, startDay: 15, endMonth: 1, endDay: 5 },
|
|
70
|
+
// Future: { chain: HALLOWEEN, startMonth: 10, startDay: 20, endMonth: 11, endDay: 5 },
|
|
71
|
+
];
|
|
72
|
+
function isDateInWindow(now, window) {
|
|
73
|
+
const month = now.getMonth() + 1; // 0-indexed → 1-indexed
|
|
74
|
+
const day = now.getDate();
|
|
75
|
+
// Handle windows that cross year boundary (Dec → Jan)
|
|
76
|
+
if (window.startMonth > window.endMonth) {
|
|
77
|
+
return (month > window.startMonth || (month === window.startMonth && day >= window.startDay)) ||
|
|
78
|
+
(month < window.endMonth || (month === window.endMonth && day <= window.endDay));
|
|
79
|
+
}
|
|
80
|
+
// Normal window within same year
|
|
81
|
+
if (month > window.startMonth && month < window.endMonth)
|
|
82
|
+
return true;
|
|
83
|
+
if (month === window.startMonth && day >= window.startDay)
|
|
84
|
+
return true;
|
|
85
|
+
if (month === window.endMonth && day <= window.endDay)
|
|
86
|
+
return true;
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
/** Get seasonal quests active right now */
|
|
90
|
+
function getSeasonalQuests() {
|
|
91
|
+
const now = new Date();
|
|
92
|
+
return SEASONAL_WINDOWS
|
|
93
|
+
.filter((w) => isDateInWindow(now, w))
|
|
94
|
+
.map((w) => w.chain);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=seasonal.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SHOP_ITEMS = void 0;
|
|
4
|
+
/** Pip's shop inventory — available after Ch1 */
|
|
5
|
+
exports.SHOP_ITEMS = [
|
|
6
|
+
// Basic consumables
|
|
7
|
+
{ itemId: "berry", price: 10 },
|
|
8
|
+
{ itemId: "fish", price: 25 },
|
|
9
|
+
{ itemId: "cake", price: 50 },
|
|
10
|
+
{ itemId: "herb", price: 10 },
|
|
11
|
+
{ itemId: "potion", price: 30 },
|
|
12
|
+
{ itemId: "elixir", price: 60 },
|
|
13
|
+
{ itemId: "stick", price: 10 },
|
|
14
|
+
{ itemId: "ball", price: 25 },
|
|
15
|
+
// Premium consumables
|
|
16
|
+
{ itemId: "golden_apple", price: 80 },
|
|
17
|
+
{ itemId: "dragon_steak", price: 90 },
|
|
18
|
+
{ itemId: "phoenix_tears", price: 100 },
|
|
19
|
+
// Basic gear
|
|
20
|
+
{ itemId: "wooden_stick", price: 30 },
|
|
21
|
+
{ itemId: "leaf_cloak", price: 30 },
|
|
22
|
+
{ itemId: "leather_cap", price: 25 },
|
|
23
|
+
{ itemId: "sandals", price: 20 },
|
|
24
|
+
{ itemId: "lucky_charm", price: 40 },
|
|
25
|
+
];
|
|
26
|
+
//# sourceMappingURL=shop.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSideQuestChains = getSideQuestChains;
|
|
4
|
+
const types_1 = require("../adventure/types");
|
|
5
|
+
function makeAdventure(def) {
|
|
6
|
+
return { ...def, roomMap: (0, types_1.buildRoomMap)(def.rooms) };
|
|
7
|
+
}
|
|
8
|
+
// ─── Pip's Lost Shipment (unlocks after Ch1) ─────────────────
|
|
9
|
+
const PIPS_SHIPMENT = {
|
|
10
|
+
id: "pips_shipment",
|
|
11
|
+
name: "Pip's Lost Shipment",
|
|
12
|
+
description: "Help Pip recover his stolen merchant goods from forest bandits.",
|
|
13
|
+
type: "side",
|
|
14
|
+
chapters: [
|
|
15
|
+
{
|
|
16
|
+
id: "pip_s1", name: "The Missing Cart",
|
|
17
|
+
description: "Pip's cart was stolen by mischievous squirrels. Track them down!",
|
|
18
|
+
prerequisites: ["ch1_whispers"], levelRequired: 2,
|
|
19
|
+
storyFlag: "pip_s1_complete",
|
|
20
|
+
rewards: { xp: 30, gold: 20, items: ["berry", "fish"] },
|
|
21
|
+
acts: [{ id: "act1", name: "Quest", type: "dungeon", buildAdventure: () => makeAdventure({
|
|
22
|
+
id: "pip_s1", name: "The Missing Cart", description: "Find Pip's stolen goods.",
|
|
23
|
+
difficulty: "easy", levelRequired: 2, energyCost: 10, hungerCost: 8, completionXp: 30,
|
|
24
|
+
startRoomId: "ps1_start",
|
|
25
|
+
rooms: [
|
|
26
|
+
{ id: "ps1_start", type: "narrative", title: "Pip's Plea",
|
|
27
|
+
text: ["Pip wrings his tiny paws. \"They took EVERYTHING! My berries, my fish, my favorite cheese!\"", "Squirrel tracks lead deeper into the woods."],
|
|
28
|
+
buddyLine: "Don't worry, Pip. We'll get your stuff back.",
|
|
29
|
+
choices: [{ label: "Follow the tracks", nextRoomId: "ps1_combat" }],
|
|
30
|
+
},
|
|
31
|
+
{ id: "ps1_combat", type: "combat", title: "Squirrel Bandits",
|
|
32
|
+
text: ["Three squirrels chatter angrily around Pip's overturned cart!"],
|
|
33
|
+
buddyLine: "Hey! Give that back!", enemyId: "squirrel", nextRoomId: "ps1_end",
|
|
34
|
+
},
|
|
35
|
+
{ id: "ps1_end", type: "treasure", title: "Recovered Goods",
|
|
36
|
+
text: ["Pip's cart is battered but intact. Most of the goods are still here!"],
|
|
37
|
+
buddyLine: "Pip is going to be SO happy!", lootPool: ["berry", "fish", "herb", "cake"],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
}) }],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "pip_s2", name: "Bandit Boss",
|
|
44
|
+
description: "The squirrel bandits have a leader. Deal with it for good.",
|
|
45
|
+
prerequisites: ["pip_s1"], levelRequired: 3,
|
|
46
|
+
storyFlag: "pip_s2_complete",
|
|
47
|
+
rewards: { xp: 50, gold: 35 },
|
|
48
|
+
acts: [{ id: "act1", name: "Quest", type: "dungeon", buildAdventure: () => makeAdventure({
|
|
49
|
+
id: "pip_s2", name: "Bandit Boss", description: "Confront the bandit leader.",
|
|
50
|
+
difficulty: "medium", levelRequired: 3, energyCost: 15, hungerCost: 10, completionXp: 50,
|
|
51
|
+
startRoomId: "ps2_start",
|
|
52
|
+
rooms: [
|
|
53
|
+
{ id: "ps2_start", type: "narrative", title: "Deeper Trail",
|
|
54
|
+
text: ["The bandit squirrels aren't random — they have a hideout.", "Pip: \"They keep coming back! There must be a leader!\""],
|
|
55
|
+
buddyLine: "Let's end this once and for all.",
|
|
56
|
+
choices: [{ label: "Raid the hideout", nextRoomId: "ps2_combat" }],
|
|
57
|
+
},
|
|
58
|
+
{ id: "ps2_combat", type: "combat", title: "The Bandit Den",
|
|
59
|
+
text: ["A particularly large mushroom stands guard at the den entrance."],
|
|
60
|
+
buddyLine: "That's one tough bouncer!", enemyId: "mushroom", nextRoomId: "ps2_boss",
|
|
61
|
+
},
|
|
62
|
+
{ id: "ps2_boss", type: "combat", title: "Bandit King",
|
|
63
|
+
text: ["The bandit leader — a storm wolf with a crown of stolen jewelry!"],
|
|
64
|
+
buddyLine: "All that stolen loot... give it back!", enemyId: "stormwolf",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
}) }],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
// ─── Shelly's Memories (unlocks after Ch2) ───────────────────
|
|
72
|
+
const SHELLYS_MEMORIES = {
|
|
73
|
+
id: "shellys_memories",
|
|
74
|
+
name: "Shelly's Memories",
|
|
75
|
+
description: "Help Elder Shelly recover lost lore fragments from the deep caves.",
|
|
76
|
+
type: "side",
|
|
77
|
+
chapters: [
|
|
78
|
+
{
|
|
79
|
+
id: "shelly_s1", name: "Lost Tablets",
|
|
80
|
+
description: "Ancient stone tablets are scattered in the caves. Find them for Shelly.",
|
|
81
|
+
prerequisites: ["ch2_echoes"], levelRequired: 4,
|
|
82
|
+
storyFlag: "shelly_s1_complete",
|
|
83
|
+
rewards: { xp: 40, gold: 30 },
|
|
84
|
+
acts: [{ id: "act1", name: "Quest", type: "dungeon", buildAdventure: () => makeAdventure({
|
|
85
|
+
id: "shelly_s1", name: "Lost Tablets", description: "Recover ancient lore.",
|
|
86
|
+
difficulty: "medium", levelRequired: 4, energyCost: 15, hungerCost: 10, completionXp: 40,
|
|
87
|
+
startRoomId: "ss1_start",
|
|
88
|
+
rooms: [
|
|
89
|
+
{ id: "ss1_start", type: "narrative", title: "Shelly's Request",
|
|
90
|
+
text: ["\"There are tablets deeper than even I can reach,\" Shelly says.", "\"They contain the history of the Stones' creation. Please find them.\""],
|
|
91
|
+
buddyLine: "For knowledge! And for Shelly!",
|
|
92
|
+
choices: [{ label: "Descend into the depths", nextRoomId: "ss1_combat" }],
|
|
93
|
+
},
|
|
94
|
+
{ id: "ss1_combat", type: "combat", title: "Crystal Guardians",
|
|
95
|
+
text: ["Automated crystal guardians still protect the old archives."],
|
|
96
|
+
buddyLine: "They're ancient security systems!", enemyId: "golem", nextRoomId: "ss1_find",
|
|
97
|
+
},
|
|
98
|
+
{ id: "ss1_find", type: "treasure", title: "The Lost Archive",
|
|
99
|
+
text: ["Stone tablets covered in ancient writing. Shelly will be thrilled!"],
|
|
100
|
+
buddyLine: "These must be thousands of years old...",
|
|
101
|
+
lootPool: ["crystal_crown", "potion", "mana_crystal"],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
}) }],
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
// ─── Blaze's Challenge (unlocks after Ch3) ───────────────────
|
|
109
|
+
const BLAZES_CHALLENGE = {
|
|
110
|
+
id: "blazes_challenge",
|
|
111
|
+
name: "Blaze's Challenge",
|
|
112
|
+
description: "Blaze keeps showing up for rematches. Prove yourself!",
|
|
113
|
+
type: "side",
|
|
114
|
+
chapters: [
|
|
115
|
+
{
|
|
116
|
+
id: "blaze_s1", name: "Round Two",
|
|
117
|
+
description: "Blaze wants a rematch. No Harmony Stone on the line — just pride.",
|
|
118
|
+
prerequisites: ["ch3_storm"], levelRequired: 6,
|
|
119
|
+
storyFlag: "blaze_s1_complete",
|
|
120
|
+
rewards: { xp: 60, gold: 45, items: ["flame_scepter"] },
|
|
121
|
+
acts: [{ id: "act1", name: "Quest", type: "dungeon", buildAdventure: () => makeAdventure({
|
|
122
|
+
id: "blaze_s1", name: "Round Two", description: "Rematch with Blaze.",
|
|
123
|
+
difficulty: "hard", levelRequired: 6, energyCost: 20, hungerCost: 15, completionXp: 60,
|
|
124
|
+
startRoomId: "bs1_start",
|
|
125
|
+
rooms: [
|
|
126
|
+
{ id: "bs1_start", type: "narrative", title: "The Challenge",
|
|
127
|
+
text: ["A letter arrives, written in scorch marks:", "\"Same place. Sunrise. No excuses. — Blaze\""],
|
|
128
|
+
buddyLine: "This guy just doesn't quit, does he?",
|
|
129
|
+
choices: [{ label: "Accept the challenge", nextRoomId: "bs1_boss" }],
|
|
130
|
+
},
|
|
131
|
+
{ id: "bs1_boss", type: "boss", title: "Blaze: Round Two",
|
|
132
|
+
text: ["Blaze lands with a dramatic burst of flame.", "\"You got lucky last time. Let's see if it was a fluke.\""],
|
|
133
|
+
buddyLine: "Bring it on, firebird!", enemyId: "infernodrake",
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
}) }],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
/** Get all available side quest chains based on story progress */
|
|
141
|
+
function getSideQuestChains(completedChapters) {
|
|
142
|
+
const chains = [];
|
|
143
|
+
if (completedChapters.includes("ch1_whispers"))
|
|
144
|
+
chains.push(PIPS_SHIPMENT);
|
|
145
|
+
if (completedChapters.includes("ch2_echoes"))
|
|
146
|
+
chains.push(SHELLYS_MEMORIES);
|
|
147
|
+
if (completedChapters.includes("ch3_storm"))
|
|
148
|
+
chains.push(BLAZES_CHALLENGE);
|
|
149
|
+
return chains;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=sideQuests.js.map
|