run-dmcp 0.3.0 → 0.4.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/README.md +19 -5
- package/dist/bin/run-dmcp.js +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +7 -1
- package/dist/rpg/index.d.ts +0 -16
- package/dist/rpg/index.js +4 -22
- package/dist/rpg/server.d.ts +16 -0
- package/dist/rpg/server.js +22 -0
- package/dist/schemas/index.d.ts +62 -62
- package/dist/server.d.ts +1 -0
- package/dist/server.js +20 -0
- package/dist/utils/output-schemas.d.ts +58 -58
- package/package.json +9 -1
- package/dist/register/abilities.d.ts +0 -2
- package/dist/register/abilities.js +0 -165
- package/dist/register/combat.d.ts +0 -2
- package/dist/register/combat.js +0 -207
- package/dist/register/mcp-prompts.d.ts +0 -2
- package/dist/register/mcp-prompts.js +0 -684
- package/dist/register/quests.d.ts +0 -2
- package/dist/register/quests.js +0 -118
- package/dist/register/status.d.ts +0 -2
- package/dist/register/status.js +0 -130
- package/dist/register/tables.d.ts +0 -2
- package/dist/register/tables.js +0 -146
- package/dist/tools/ability.d.ts +0 -48
- package/dist/tools/ability.js +0 -238
- package/dist/tools/combat.d.ts +0 -13
- package/dist/tools/combat.js +0 -195
- package/dist/tools/dice.d.ts +0 -23
- package/dist/tools/dice.js +0 -111
- package/dist/tools/quest.d.ts +0 -34
- package/dist/tools/quest.js +0 -164
- package/dist/tools/status.d.ts +0 -36
- package/dist/tools/status.js +0 -218
- package/dist/tools/tables.d.ts +0 -33
- package/dist/tools/tables.js +0 -209
package/dist/tools/quest.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from "uuid";
|
|
2
|
-
import { getDatabase } from "../db/connection.js";
|
|
3
|
-
import { safeJsonParse } from "../utils/json.js";
|
|
4
|
-
import { gameEvents } from "../events/emitter.js";
|
|
5
|
-
import { validateGameExists } from "./game.js";
|
|
6
|
-
export function createQuest(params) {
|
|
7
|
-
// Validate game exists to prevent orphaned records
|
|
8
|
-
validateGameExists(params.gameId);
|
|
9
|
-
const db = getDatabase();
|
|
10
|
-
const id = uuidv4();
|
|
11
|
-
const objectives = params.objectives.map((obj) => ({
|
|
12
|
-
id: uuidv4(),
|
|
13
|
-
description: obj.description,
|
|
14
|
-
completed: obj.completed || false,
|
|
15
|
-
optional: obj.optional,
|
|
16
|
-
}));
|
|
17
|
-
const stmt = db.prepare(`
|
|
18
|
-
INSERT INTO quests (id, game_id, name, description, objectives, status, rewards)
|
|
19
|
-
VALUES (?, ?, ?, ?, ?, 'active', ?)
|
|
20
|
-
`);
|
|
21
|
-
stmt.run(id, params.gameId, params.name, params.description, JSON.stringify(objectives), params.rewards || null);
|
|
22
|
-
return {
|
|
23
|
-
id,
|
|
24
|
-
gameId: params.gameId,
|
|
25
|
-
name: params.name,
|
|
26
|
-
description: params.description,
|
|
27
|
-
objectives,
|
|
28
|
-
status: "active",
|
|
29
|
-
rewards: params.rewards,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
export function getQuest(id) {
|
|
33
|
-
const db = getDatabase();
|
|
34
|
-
const stmt = db.prepare(`SELECT * FROM quests WHERE id = ?`);
|
|
35
|
-
const row = stmt.get(id);
|
|
36
|
-
if (!row)
|
|
37
|
-
return null;
|
|
38
|
-
return {
|
|
39
|
-
id: row.id,
|
|
40
|
-
gameId: row.game_id,
|
|
41
|
-
name: row.name,
|
|
42
|
-
description: row.description,
|
|
43
|
-
objectives: safeJsonParse(row.objectives, []),
|
|
44
|
-
status: row.status,
|
|
45
|
-
rewards: row.rewards,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
export function updateQuest(id, updates) {
|
|
49
|
-
const db = getDatabase();
|
|
50
|
-
const current = getQuest(id);
|
|
51
|
-
if (!current)
|
|
52
|
-
return null;
|
|
53
|
-
const newName = updates.name ?? current.name;
|
|
54
|
-
const newDescription = updates.description ?? current.description;
|
|
55
|
-
const newStatus = updates.status ?? current.status;
|
|
56
|
-
const newRewards = updates.rewards ?? current.rewards;
|
|
57
|
-
const stmt = db.prepare(`
|
|
58
|
-
UPDATE quests SET name = ?, description = ?, status = ?, rewards = ? WHERE id = ?
|
|
59
|
-
`);
|
|
60
|
-
stmt.run(newName, newDescription, newStatus, newRewards || null, id);
|
|
61
|
-
const updated = {
|
|
62
|
-
...current,
|
|
63
|
-
name: newName,
|
|
64
|
-
description: newDescription,
|
|
65
|
-
status: newStatus,
|
|
66
|
-
rewards: newRewards,
|
|
67
|
-
};
|
|
68
|
-
// Emit realtime event
|
|
69
|
-
gameEvents.emit({
|
|
70
|
-
type: "quest:updated",
|
|
71
|
-
gameId: current.gameId,
|
|
72
|
-
entityId: id,
|
|
73
|
-
entityType: "quest",
|
|
74
|
-
timestamp: new Date().toISOString(),
|
|
75
|
-
data: { name: newName, status: newStatus },
|
|
76
|
-
});
|
|
77
|
-
return updated;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Modify quest objectives - add new objectives and/or complete existing ones in a single call.
|
|
81
|
-
*/
|
|
82
|
-
export function modifyObjectives(questId, params) {
|
|
83
|
-
const db = getDatabase();
|
|
84
|
-
const quest = getQuest(questId);
|
|
85
|
-
if (!quest)
|
|
86
|
-
return null;
|
|
87
|
-
const objectives = [...quest.objectives];
|
|
88
|
-
const added = [];
|
|
89
|
-
const completed = [];
|
|
90
|
-
// Complete objectives first
|
|
91
|
-
if (params.complete) {
|
|
92
|
-
for (const objectiveId of params.complete) {
|
|
93
|
-
const idx = objectives.findIndex((o) => o.id === objectiveId);
|
|
94
|
-
if (idx !== -1 && !objectives[idx].completed) {
|
|
95
|
-
objectives[idx] = { ...objectives[idx], completed: true };
|
|
96
|
-
completed.push(objectiveId);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Add new objectives
|
|
101
|
-
if (params.add) {
|
|
102
|
-
for (const obj of params.add) {
|
|
103
|
-
const newObjective = {
|
|
104
|
-
id: uuidv4(),
|
|
105
|
-
description: obj.description,
|
|
106
|
-
completed: false,
|
|
107
|
-
optional: obj.optional,
|
|
108
|
-
};
|
|
109
|
-
objectives.push(newObjective);
|
|
110
|
-
added.push(newObjective);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// Check if all required objectives are complete
|
|
114
|
-
const requiredComplete = objectives
|
|
115
|
-
.filter((o) => !o.optional)
|
|
116
|
-
.every((o) => o.completed);
|
|
117
|
-
const shouldComplete = requiredComplete && quest.status === "active";
|
|
118
|
-
const newStatus = shouldComplete ? "completed" : quest.status;
|
|
119
|
-
// Update objectives and status atomically in a single statement
|
|
120
|
-
const stmt = db.prepare(`UPDATE quests SET objectives = ?, status = ? WHERE id = ?`);
|
|
121
|
-
stmt.run(JSON.stringify(objectives), newStatus, questId);
|
|
122
|
-
// Emit event if quest was completed
|
|
123
|
-
if (shouldComplete) {
|
|
124
|
-
gameEvents.emit({
|
|
125
|
-
type: "quest:updated",
|
|
126
|
-
gameId: quest.gameId,
|
|
127
|
-
entityId: questId,
|
|
128
|
-
entityType: "quest",
|
|
129
|
-
timestamp: new Date().toISOString(),
|
|
130
|
-
data: { name: quest.name, status: "completed" },
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
const finalQuest = { ...quest, objectives, status: newStatus };
|
|
134
|
-
return { quest: finalQuest, added, completed };
|
|
135
|
-
}
|
|
136
|
-
export function deleteQuest(id) {
|
|
137
|
-
const db = getDatabase();
|
|
138
|
-
const stmt = db.prepare(`DELETE FROM quests WHERE id = ?`);
|
|
139
|
-
const result = stmt.run(id);
|
|
140
|
-
return result.changes > 0;
|
|
141
|
-
}
|
|
142
|
-
export function listQuests(gameId, filter) {
|
|
143
|
-
const db = getDatabase();
|
|
144
|
-
let query = `SELECT * FROM quests WHERE game_id = ?`;
|
|
145
|
-
const params = [gameId];
|
|
146
|
-
if (filter?.status) {
|
|
147
|
-
query += ` AND status = ?`;
|
|
148
|
-
params.push(filter.status);
|
|
149
|
-
}
|
|
150
|
-
const stmt = db.prepare(query);
|
|
151
|
-
const rows = stmt.all(...params);
|
|
152
|
-
return rows.map((row) => ({
|
|
153
|
-
id: row.id,
|
|
154
|
-
gameId: row.game_id,
|
|
155
|
-
name: row.name,
|
|
156
|
-
description: row.description,
|
|
157
|
-
objectives: safeJsonParse(row.objectives, []),
|
|
158
|
-
status: row.status,
|
|
159
|
-
rewards: row.rewards,
|
|
160
|
-
}));
|
|
161
|
-
}
|
|
162
|
-
export function getActiveQuests(gameId) {
|
|
163
|
-
return listQuests(gameId, { status: "active" });
|
|
164
|
-
}
|
package/dist/tools/status.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { StatusEffect } from "../types/index.js";
|
|
2
|
-
export declare function applyStatusEffect(params: {
|
|
3
|
-
gameId: string;
|
|
4
|
-
targetId: string;
|
|
5
|
-
name: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
effectType?: "buff" | "debuff" | "neutral";
|
|
8
|
-
duration?: number;
|
|
9
|
-
stacks?: number;
|
|
10
|
-
maxStacks?: number;
|
|
11
|
-
effects?: Record<string, number>;
|
|
12
|
-
sourceId?: string;
|
|
13
|
-
sourceType?: string;
|
|
14
|
-
expiresAt?: string;
|
|
15
|
-
}): StatusEffect;
|
|
16
|
-
export declare function getStatusEffect(id: string): StatusEffect | null;
|
|
17
|
-
export declare function removeStatusEffect(id: string): boolean;
|
|
18
|
-
export declare function listStatusEffects(targetId: string, filter?: {
|
|
19
|
-
effectType?: "buff" | "debuff" | "neutral";
|
|
20
|
-
}): StatusEffect[];
|
|
21
|
-
export interface TickResult {
|
|
22
|
-
expired: StatusEffect[];
|
|
23
|
-
remaining: StatusEffect[];
|
|
24
|
-
}
|
|
25
|
-
export declare function tickDurations(gameId: string, amount?: number): TickResult;
|
|
26
|
-
export declare function modifyStacks(id: string, delta: number): StatusEffect | null;
|
|
27
|
-
export declare function clearEffects(targetId: string, filter?: {
|
|
28
|
-
effectType?: "buff" | "debuff" | "neutral";
|
|
29
|
-
name?: string;
|
|
30
|
-
}): number;
|
|
31
|
-
export interface EffectiveModifiers {
|
|
32
|
-
targetId: string;
|
|
33
|
-
modifiers: Record<string, number>;
|
|
34
|
-
effects: StatusEffect[];
|
|
35
|
-
}
|
|
36
|
-
export declare function getEffectiveModifiers(targetId: string): EffectiveModifiers;
|
package/dist/tools/status.js
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from "uuid";
|
|
2
|
-
import { getDatabase } from "../db/connection.js";
|
|
3
|
-
import { safeJsonParse } from "../utils/json.js";
|
|
4
|
-
import { validateGameExists } from "./game.js";
|
|
5
|
-
export function applyStatusEffect(params) {
|
|
6
|
-
// Validate game exists to prevent orphaned records
|
|
7
|
-
validateGameExists(params.gameId);
|
|
8
|
-
const db = getDatabase();
|
|
9
|
-
const now = new Date().toISOString();
|
|
10
|
-
// Check if effect already exists on target
|
|
11
|
-
const existing = db.prepare(`
|
|
12
|
-
SELECT * FROM status_effects WHERE game_id = ? AND target_id = ? AND name = ?
|
|
13
|
-
`).get(params.gameId, params.targetId, params.name);
|
|
14
|
-
if (existing) {
|
|
15
|
-
// Stack the effect - use existing maxStacks (can't change stack limit after creation)
|
|
16
|
-
const currentStacks = existing.stacks;
|
|
17
|
-
const existingMaxStacks = existing.max_stacks;
|
|
18
|
-
const newStacks = existingMaxStacks !== null
|
|
19
|
-
? Math.min(currentStacks + (params.stacks ?? 1), existingMaxStacks)
|
|
20
|
-
: currentStacks + (params.stacks ?? 1);
|
|
21
|
-
// Refresh duration if provided
|
|
22
|
-
const newDuration = params.duration ?? existing.duration;
|
|
23
|
-
db.prepare(`
|
|
24
|
-
UPDATE status_effects SET stacks = ?, duration = ? WHERE id = ?
|
|
25
|
-
`).run(newStacks, newDuration, existing.id);
|
|
26
|
-
const updated = getStatusEffect(existing.id);
|
|
27
|
-
if (!updated) {
|
|
28
|
-
throw new Error(`Failed to retrieve updated status effect '${existing.id}'`);
|
|
29
|
-
}
|
|
30
|
-
return updated;
|
|
31
|
-
}
|
|
32
|
-
// Create new effect
|
|
33
|
-
const id = uuidv4();
|
|
34
|
-
db.prepare(`
|
|
35
|
-
INSERT INTO status_effects (id, game_id, target_id, name, description, effect_type, duration, stacks, max_stacks, effects, source_id, source_type, expires_at, created_at)
|
|
36
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
37
|
-
`).run(id, params.gameId, params.targetId, params.name, params.description || "", params.effectType || null, params.duration ?? null, params.stacks ?? 1, params.maxStacks ?? null, JSON.stringify(params.effects || {}), params.sourceId || null, params.sourceType || null, params.expiresAt || null, now);
|
|
38
|
-
return {
|
|
39
|
-
id,
|
|
40
|
-
gameId: params.gameId,
|
|
41
|
-
targetId: params.targetId,
|
|
42
|
-
name: params.name,
|
|
43
|
-
description: params.description || "",
|
|
44
|
-
effectType: params.effectType || null,
|
|
45
|
-
duration: params.duration ?? null,
|
|
46
|
-
stacks: params.stacks ?? 1,
|
|
47
|
-
maxStacks: params.maxStacks ?? null,
|
|
48
|
-
effects: params.effects || {},
|
|
49
|
-
sourceId: params.sourceId || null,
|
|
50
|
-
sourceType: params.sourceType || null,
|
|
51
|
-
expiresAt: params.expiresAt || null,
|
|
52
|
-
createdAt: now,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
export function getStatusEffect(id) {
|
|
56
|
-
const db = getDatabase();
|
|
57
|
-
const row = db.prepare(`SELECT * FROM status_effects WHERE id = ?`).get(id);
|
|
58
|
-
if (!row)
|
|
59
|
-
return null;
|
|
60
|
-
return {
|
|
61
|
-
id: row.id,
|
|
62
|
-
gameId: row.game_id,
|
|
63
|
-
targetId: row.target_id,
|
|
64
|
-
name: row.name,
|
|
65
|
-
description: row.description,
|
|
66
|
-
effectType: row.effect_type,
|
|
67
|
-
duration: row.duration,
|
|
68
|
-
stacks: row.stacks,
|
|
69
|
-
maxStacks: row.max_stacks,
|
|
70
|
-
effects: safeJsonParse(row.effects || "{}", {}),
|
|
71
|
-
sourceId: row.source_id,
|
|
72
|
-
sourceType: row.source_type,
|
|
73
|
-
expiresAt: row.expires_at,
|
|
74
|
-
createdAt: row.created_at,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
export function removeStatusEffect(id) {
|
|
78
|
-
const db = getDatabase();
|
|
79
|
-
const result = db.prepare(`DELETE FROM status_effects WHERE id = ?`).run(id);
|
|
80
|
-
return result.changes > 0;
|
|
81
|
-
}
|
|
82
|
-
export function listStatusEffects(targetId, filter) {
|
|
83
|
-
const db = getDatabase();
|
|
84
|
-
let query = `SELECT * FROM status_effects WHERE target_id = ?`;
|
|
85
|
-
const params = [targetId];
|
|
86
|
-
if (filter?.effectType) {
|
|
87
|
-
query += ` AND effect_type = ?`;
|
|
88
|
-
params.push(filter.effectType);
|
|
89
|
-
}
|
|
90
|
-
query += ` ORDER BY created_at`;
|
|
91
|
-
const rows = db.prepare(query).all(...params);
|
|
92
|
-
return rows.map(row => ({
|
|
93
|
-
id: row.id,
|
|
94
|
-
gameId: row.game_id,
|
|
95
|
-
targetId: row.target_id,
|
|
96
|
-
name: row.name,
|
|
97
|
-
description: row.description,
|
|
98
|
-
effectType: row.effect_type,
|
|
99
|
-
duration: row.duration,
|
|
100
|
-
stacks: row.stacks,
|
|
101
|
-
maxStacks: row.max_stacks,
|
|
102
|
-
effects: safeJsonParse(row.effects || "{}", {}),
|
|
103
|
-
sourceId: row.source_id,
|
|
104
|
-
sourceType: row.source_type,
|
|
105
|
-
expiresAt: row.expires_at,
|
|
106
|
-
createdAt: row.created_at,
|
|
107
|
-
}));
|
|
108
|
-
}
|
|
109
|
-
// NOTE on expiry consequences: status effects do NOT support the declared
|
|
110
|
-
// on-expiry consequence mechanism that scheduled_events (time.ts) and
|
|
111
|
-
// timers (timers.ts) support. tickDurations() only decrements duration and
|
|
112
|
-
// deletes the row at zero, exactly as before. Status effects were left out
|
|
113
|
-
// deliberately -- unifying all three expiry systems behind one consequence
|
|
114
|
-
// mechanism is a larger refactor than this feature warrants; see the
|
|
115
|
-
// scheduled_events/timers implementations for the supported pattern if this
|
|
116
|
-
// gets extended later.
|
|
117
|
-
export function tickDurations(gameId, amount = 1) {
|
|
118
|
-
const db = getDatabase();
|
|
119
|
-
// Get all effects with duration
|
|
120
|
-
const effects = db.prepare(`
|
|
121
|
-
SELECT * FROM status_effects WHERE game_id = ? AND duration IS NOT NULL
|
|
122
|
-
`).all(gameId);
|
|
123
|
-
const expired = [];
|
|
124
|
-
const remaining = [];
|
|
125
|
-
for (const row of effects) {
|
|
126
|
-
const currentDuration = row.duration;
|
|
127
|
-
const newDuration = currentDuration - amount;
|
|
128
|
-
if (newDuration <= 0) {
|
|
129
|
-
// Effect expired
|
|
130
|
-
db.prepare(`DELETE FROM status_effects WHERE id = ?`).run(row.id);
|
|
131
|
-
expired.push({
|
|
132
|
-
id: row.id,
|
|
133
|
-
gameId: row.game_id,
|
|
134
|
-
targetId: row.target_id,
|
|
135
|
-
name: row.name,
|
|
136
|
-
description: row.description,
|
|
137
|
-
effectType: row.effect_type,
|
|
138
|
-
duration: 0,
|
|
139
|
-
stacks: row.stacks,
|
|
140
|
-
maxStacks: row.max_stacks,
|
|
141
|
-
effects: safeJsonParse(row.effects || "{}", {}),
|
|
142
|
-
sourceId: row.source_id,
|
|
143
|
-
sourceType: row.source_type,
|
|
144
|
-
expiresAt: row.expires_at,
|
|
145
|
-
createdAt: row.created_at,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// Update duration
|
|
150
|
-
db.prepare(`UPDATE status_effects SET duration = ? WHERE id = ?`).run(newDuration, row.id);
|
|
151
|
-
remaining.push({
|
|
152
|
-
id: row.id,
|
|
153
|
-
gameId: row.game_id,
|
|
154
|
-
targetId: row.target_id,
|
|
155
|
-
name: row.name,
|
|
156
|
-
description: row.description,
|
|
157
|
-
effectType: row.effect_type,
|
|
158
|
-
duration: newDuration,
|
|
159
|
-
stacks: row.stacks,
|
|
160
|
-
maxStacks: row.max_stacks,
|
|
161
|
-
effects: safeJsonParse(row.effects || "{}", {}),
|
|
162
|
-
sourceId: row.source_id,
|
|
163
|
-
sourceType: row.source_type,
|
|
164
|
-
expiresAt: row.expires_at,
|
|
165
|
-
createdAt: row.created_at,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return { expired, remaining };
|
|
170
|
-
}
|
|
171
|
-
export function modifyStacks(id, delta) {
|
|
172
|
-
const db = getDatabase();
|
|
173
|
-
const effect = getStatusEffect(id);
|
|
174
|
-
if (!effect)
|
|
175
|
-
return null;
|
|
176
|
-
const newStacks = effect.stacks + delta;
|
|
177
|
-
if (newStacks <= 0) {
|
|
178
|
-
// Remove effect
|
|
179
|
-
db.prepare(`DELETE FROM status_effects WHERE id = ?`).run(id);
|
|
180
|
-
return { ...effect, stacks: 0 };
|
|
181
|
-
}
|
|
182
|
-
const finalStacks = effect.maxStacks !== null
|
|
183
|
-
? Math.min(newStacks, effect.maxStacks)
|
|
184
|
-
: newStacks;
|
|
185
|
-
db.prepare(`UPDATE status_effects SET stacks = ? WHERE id = ?`).run(finalStacks, id);
|
|
186
|
-
return { ...effect, stacks: finalStacks };
|
|
187
|
-
}
|
|
188
|
-
export function clearEffects(targetId, filter) {
|
|
189
|
-
const db = getDatabase();
|
|
190
|
-
let query = `DELETE FROM status_effects WHERE target_id = ?`;
|
|
191
|
-
const params = [targetId];
|
|
192
|
-
if (filter?.effectType) {
|
|
193
|
-
query += ` AND effect_type = ?`;
|
|
194
|
-
params.push(filter.effectType);
|
|
195
|
-
}
|
|
196
|
-
if (filter?.name) {
|
|
197
|
-
query += ` AND name = ?`;
|
|
198
|
-
params.push(filter.name);
|
|
199
|
-
}
|
|
200
|
-
const result = db.prepare(query).run(...params);
|
|
201
|
-
return result.changes;
|
|
202
|
-
}
|
|
203
|
-
export function getEffectiveModifiers(targetId) {
|
|
204
|
-
const effects = listStatusEffects(targetId);
|
|
205
|
-
const modifiers = {};
|
|
206
|
-
for (const effect of effects) {
|
|
207
|
-
for (const [key, value] of Object.entries(effect.effects)) {
|
|
208
|
-
// Multiply by stacks
|
|
209
|
-
const totalValue = value * effect.stacks;
|
|
210
|
-
modifiers[key] = (modifiers[key] || 0) + totalValue;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return {
|
|
214
|
-
targetId,
|
|
215
|
-
modifiers,
|
|
216
|
-
effects,
|
|
217
|
-
};
|
|
218
|
-
}
|
package/dist/tools/tables.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { RandomTable, TableEntry, TableRollResult } from "../types/index.js";
|
|
2
|
-
export declare function createTable(params: {
|
|
3
|
-
gameId: string;
|
|
4
|
-
name: string;
|
|
5
|
-
description?: string;
|
|
6
|
-
category?: string;
|
|
7
|
-
entries?: TableEntry[];
|
|
8
|
-
rollExpression?: string;
|
|
9
|
-
}): RandomTable;
|
|
10
|
-
export declare function getTable(id: string): RandomTable | null;
|
|
11
|
-
export declare function updateTable(id: string, updates: {
|
|
12
|
-
name?: string;
|
|
13
|
-
description?: string;
|
|
14
|
-
category?: string | null;
|
|
15
|
-
entries?: TableEntry[];
|
|
16
|
-
rollExpression?: string;
|
|
17
|
-
}): RandomTable | null;
|
|
18
|
-
export declare function deleteTable(id: string): boolean;
|
|
19
|
-
export declare function listTables(gameId: string, category?: string): RandomTable[];
|
|
20
|
-
export declare function rollTable(tableId: string, modifier?: number): TableRollResult | null;
|
|
21
|
-
/**
|
|
22
|
-
* Modify table entries - add and/or remove entries in a single call.
|
|
23
|
-
*/
|
|
24
|
-
export declare function modifyTableEntries(tableId: string, params: {
|
|
25
|
-
add?: TableEntry[];
|
|
26
|
-
remove?: number[];
|
|
27
|
-
}): {
|
|
28
|
-
table: RandomTable;
|
|
29
|
-
added: number;
|
|
30
|
-
removed: number;
|
|
31
|
-
invalidIndices?: number[];
|
|
32
|
-
} | null;
|
|
33
|
-
export declare function createSimpleTable(gameId: string, name: string, results: string[], category?: string): RandomTable;
|
package/dist/tools/tables.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from "uuid";
|
|
2
|
-
import { getDatabase } from "../db/connection.js";
|
|
3
|
-
import { safeJsonParse } from "../utils/json.js";
|
|
4
|
-
import { validateGameExists } from "./game.js";
|
|
5
|
-
import { roll } from "./dice.js";
|
|
6
|
-
export function createTable(params) {
|
|
7
|
-
// Validate game exists to prevent orphaned records
|
|
8
|
-
validateGameExists(params.gameId);
|
|
9
|
-
const db = getDatabase();
|
|
10
|
-
const id = uuidv4();
|
|
11
|
-
const now = new Date().toISOString();
|
|
12
|
-
const entries = params.entries || [];
|
|
13
|
-
const rollExpression = params.rollExpression || "1d100";
|
|
14
|
-
db.prepare(`
|
|
15
|
-
INSERT INTO random_tables (id, game_id, name, description, category, entries, roll_expression, created_at)
|
|
16
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
17
|
-
`).run(id, params.gameId, params.name, params.description || "", params.category || null, JSON.stringify(entries), rollExpression, now);
|
|
18
|
-
return {
|
|
19
|
-
id,
|
|
20
|
-
gameId: params.gameId,
|
|
21
|
-
name: params.name,
|
|
22
|
-
description: params.description || "",
|
|
23
|
-
category: params.category || null,
|
|
24
|
-
entries,
|
|
25
|
-
rollExpression,
|
|
26
|
-
createdAt: now,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
export function getTable(id) {
|
|
30
|
-
const db = getDatabase();
|
|
31
|
-
const row = db.prepare(`SELECT * FROM random_tables WHERE id = ?`).get(id);
|
|
32
|
-
if (!row)
|
|
33
|
-
return null;
|
|
34
|
-
return {
|
|
35
|
-
id: row.id,
|
|
36
|
-
gameId: row.game_id,
|
|
37
|
-
name: row.name,
|
|
38
|
-
description: row.description || "",
|
|
39
|
-
category: row.category,
|
|
40
|
-
entries: safeJsonParse(row.entries, []),
|
|
41
|
-
rollExpression: row.roll_expression,
|
|
42
|
-
createdAt: row.created_at,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
export function updateTable(id, updates) {
|
|
46
|
-
const db = getDatabase();
|
|
47
|
-
const current = getTable(id);
|
|
48
|
-
if (!current)
|
|
49
|
-
return null;
|
|
50
|
-
const newName = updates.name ?? current.name;
|
|
51
|
-
const newDescription = updates.description ?? current.description;
|
|
52
|
-
const newCategory = updates.category !== undefined ? updates.category : current.category;
|
|
53
|
-
const newEntries = updates.entries ?? current.entries;
|
|
54
|
-
const newRollExpression = updates.rollExpression ?? current.rollExpression;
|
|
55
|
-
db.prepare(`
|
|
56
|
-
UPDATE random_tables
|
|
57
|
-
SET name = ?, description = ?, category = ?, entries = ?, roll_expression = ?
|
|
58
|
-
WHERE id = ?
|
|
59
|
-
`).run(newName, newDescription, newCategory, JSON.stringify(newEntries), newRollExpression, id);
|
|
60
|
-
return {
|
|
61
|
-
...current,
|
|
62
|
-
name: newName,
|
|
63
|
-
description: newDescription,
|
|
64
|
-
category: newCategory,
|
|
65
|
-
entries: newEntries,
|
|
66
|
-
rollExpression: newRollExpression,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
export function deleteTable(id) {
|
|
70
|
-
const db = getDatabase();
|
|
71
|
-
const result = db.prepare(`DELETE FROM random_tables WHERE id = ?`).run(id);
|
|
72
|
-
return result.changes > 0;
|
|
73
|
-
}
|
|
74
|
-
export function listTables(gameId, category) {
|
|
75
|
-
const db = getDatabase();
|
|
76
|
-
let query = `SELECT * FROM random_tables WHERE game_id = ?`;
|
|
77
|
-
const params = [gameId];
|
|
78
|
-
if (category) {
|
|
79
|
-
query += ` AND category = ?`;
|
|
80
|
-
params.push(category);
|
|
81
|
-
}
|
|
82
|
-
query += ` ORDER BY name`;
|
|
83
|
-
const rows = db.prepare(query).all(...params);
|
|
84
|
-
return rows.map(row => ({
|
|
85
|
-
id: row.id,
|
|
86
|
-
gameId: row.game_id,
|
|
87
|
-
name: row.name,
|
|
88
|
-
description: row.description || "",
|
|
89
|
-
category: row.category,
|
|
90
|
-
entries: safeJsonParse(row.entries, []),
|
|
91
|
-
rollExpression: row.roll_expression,
|
|
92
|
-
createdAt: row.created_at,
|
|
93
|
-
}));
|
|
94
|
-
}
|
|
95
|
-
export function rollTable(tableId, modifier = 0) {
|
|
96
|
-
const table = getTable(tableId);
|
|
97
|
-
if (!table || table.entries.length === 0)
|
|
98
|
-
return null;
|
|
99
|
-
const diceRoll = roll(table.rollExpression);
|
|
100
|
-
const rollTotal = diceRoll.total + modifier;
|
|
101
|
-
// Find matching entry
|
|
102
|
-
let matchedEntry = null;
|
|
103
|
-
for (const entry of table.entries) {
|
|
104
|
-
if (rollTotal >= entry.minRoll && rollTotal <= entry.maxRoll) {
|
|
105
|
-
matchedEntry = entry;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// If no range match, try weighted random
|
|
110
|
-
if (!matchedEntry) {
|
|
111
|
-
const weightedEntries = table.entries.filter(e => e.weight !== undefined && e.weight > 0);
|
|
112
|
-
if (weightedEntries.length > 0) {
|
|
113
|
-
const totalWeight = weightedEntries.reduce((sum, e) => sum + (e.weight || 0), 0);
|
|
114
|
-
// Use cumulative comparison to avoid floating-point precision issues
|
|
115
|
-
const target = Math.random() * totalWeight;
|
|
116
|
-
let cumulative = 0;
|
|
117
|
-
for (const entry of weightedEntries) {
|
|
118
|
-
cumulative += entry.weight || 0;
|
|
119
|
-
if (target < cumulative) {
|
|
120
|
-
matchedEntry = entry;
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
// Handle edge case where target equals totalWeight due to floating-point
|
|
125
|
-
if (!matchedEntry && weightedEntries.length > 0) {
|
|
126
|
-
matchedEntry = weightedEntries[weightedEntries.length - 1];
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
// If still no match, this indicates a table configuration error
|
|
131
|
-
if (!matchedEntry) {
|
|
132
|
-
throw new Error(`Table '${table.name}' (${tableId}) has no entry matching roll ${rollTotal} and no weighted entries. Check table configuration.`);
|
|
133
|
-
}
|
|
134
|
-
const result = {
|
|
135
|
-
table,
|
|
136
|
-
roll: diceRoll,
|
|
137
|
-
entry: matchedEntry,
|
|
138
|
-
result: matchedEntry.result,
|
|
139
|
-
};
|
|
140
|
-
// Handle subtable
|
|
141
|
-
if (matchedEntry.subtable) {
|
|
142
|
-
const subtableResult = rollTable(matchedEntry.subtable, 0);
|
|
143
|
-
if (subtableResult) {
|
|
144
|
-
result.subtableResults = [subtableResult];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return result;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Modify table entries - add and/or remove entries in a single call.
|
|
151
|
-
*/
|
|
152
|
-
export function modifyTableEntries(tableId, params) {
|
|
153
|
-
const table = getTable(tableId);
|
|
154
|
-
if (!table)
|
|
155
|
-
return null;
|
|
156
|
-
let entries = [...table.entries];
|
|
157
|
-
let removedCount = 0;
|
|
158
|
-
const invalidIndices = [];
|
|
159
|
-
// Remove entries first (process in reverse order to maintain indices)
|
|
160
|
-
if (params.remove && params.remove.length > 0) {
|
|
161
|
-
const uniqueIndices = [...new Set(params.remove)];
|
|
162
|
-
// Track invalid indices
|
|
163
|
-
for (const idx of uniqueIndices) {
|
|
164
|
-
if (idx < 0 || idx >= entries.length) {
|
|
165
|
-
invalidIndices.push(idx);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const indicesToRemove = uniqueIndices
|
|
169
|
-
.filter(i => i >= 0 && i < entries.length)
|
|
170
|
-
.sort((a, b) => b - a); // Sort descending
|
|
171
|
-
for (const idx of indicesToRemove) {
|
|
172
|
-
entries.splice(idx, 1);
|
|
173
|
-
removedCount++;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// Add new entries
|
|
177
|
-
const addedCount = params.add?.length || 0;
|
|
178
|
-
if (params.add) {
|
|
179
|
-
entries = [...entries, ...params.add];
|
|
180
|
-
}
|
|
181
|
-
const updated = updateTable(tableId, { entries });
|
|
182
|
-
if (!updated)
|
|
183
|
-
return null;
|
|
184
|
-
const result = {
|
|
185
|
-
table: updated,
|
|
186
|
-
added: addedCount,
|
|
187
|
-
removed: removedCount,
|
|
188
|
-
};
|
|
189
|
-
if (invalidIndices.length > 0) {
|
|
190
|
-
result.invalidIndices = invalidIndices;
|
|
191
|
-
}
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
194
|
-
// Helper to create a simple d6 table quickly
|
|
195
|
-
export function createSimpleTable(gameId, name, results, category) {
|
|
196
|
-
const dieSize = results.length;
|
|
197
|
-
const entries = results.map((result, i) => ({
|
|
198
|
-
minRoll: i + 1,
|
|
199
|
-
maxRoll: i + 1,
|
|
200
|
-
result,
|
|
201
|
-
}));
|
|
202
|
-
return createTable({
|
|
203
|
-
gameId,
|
|
204
|
-
name,
|
|
205
|
-
category,
|
|
206
|
-
entries,
|
|
207
|
-
rollExpression: `1d${dieSize}`,
|
|
208
|
-
});
|
|
209
|
-
}
|