✨feature: Initial code setup
This commit is contained in:
203
src/schemas/content.ts
Normal file
203
src/schemas/content.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
contentReferenceSchema,
|
||||
diceKindSchema,
|
||||
ruleEffectSchema,
|
||||
} from "./rules";
|
||||
|
||||
export const tableEntrySchema = z.object({
|
||||
key: z.string().min(1),
|
||||
min: z.number().int().optional(),
|
||||
max: z.number().int().optional(),
|
||||
exact: z.number().int().optional(),
|
||||
d66: z.number().int().optional(),
|
||||
label: z.string().min(1),
|
||||
text: z.string().optional(),
|
||||
effects: z.array(ruleEffectSchema).optional(),
|
||||
references: z.array(contentReferenceSchema).optional(),
|
||||
});
|
||||
|
||||
export const tableDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
code: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
category: z.enum(["generic", "random-list", "loot", "level", "optional", "town", "room"]),
|
||||
level: z.number().int().positive().optional(),
|
||||
page: z.number().int().positive(),
|
||||
diceKind: diceKindSchema,
|
||||
usesModifiedRangesRule: z.boolean().optional(),
|
||||
entries: z.array(tableEntrySchema),
|
||||
notes: z.array(z.string()).optional(),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const weaponDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
category: z.enum(["melee", "ranged"]),
|
||||
handedness: z.enum(["one-handed", "two-handed"]),
|
||||
baseDamage: z.number().int(),
|
||||
allowedManoeuvreIds: z.array(z.string()),
|
||||
tags: z.array(z.string()),
|
||||
startingOption: z.boolean(),
|
||||
});
|
||||
|
||||
export const manoeuvreDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
weaponCategories: z.array(z.enum(["melee", "ranged"])),
|
||||
shiftCost: z.number().int().optional(),
|
||||
disciplineModifier: z.number().int().optional(),
|
||||
precisionModifier: z.number().int().optional(),
|
||||
damageModifier: z.number().int().optional(),
|
||||
exactStrikeBonus: z.boolean().optional(),
|
||||
interruptRule: z.string().optional(),
|
||||
effectText: z.string().optional(),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const armourDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
armourValue: z.number().int(),
|
||||
penalties: z
|
||||
.object({
|
||||
shift: z.number().int().optional(),
|
||||
discipline: z.number().int().optional(),
|
||||
precision: z.number().int().optional(),
|
||||
})
|
||||
.optional(),
|
||||
deflectionRule: z.string().optional(),
|
||||
startingOption: z.boolean(),
|
||||
valueGp: z.number().int().optional(),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const itemDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
itemType: z.enum([
|
||||
"gear",
|
||||
"treasure",
|
||||
"quest",
|
||||
"herb",
|
||||
"rune",
|
||||
"misc",
|
||||
"ration",
|
||||
"light-source",
|
||||
]),
|
||||
stackable: z.boolean(),
|
||||
consumable: z.boolean(),
|
||||
valueGp: z.number().int().optional(),
|
||||
weight: z.number().optional(),
|
||||
rulesText: z.string().optional(),
|
||||
effects: z.array(ruleEffectSchema).optional(),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const potionDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
tableSource: z.string().min(1),
|
||||
useTiming: z.enum(["combat", "exploration", "town", "any"]),
|
||||
effects: z.array(ruleEffectSchema),
|
||||
valueGp: z.number().int().optional(),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const scrollDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
tableSource: z.string().min(1),
|
||||
castCheck: z
|
||||
.object({
|
||||
diceKind: z.enum(["d6", "2d6"]),
|
||||
successMin: z.number().int().optional(),
|
||||
successMax: z.number().int().optional(),
|
||||
})
|
||||
.optional(),
|
||||
onSuccess: z.array(ruleEffectSchema),
|
||||
onFailureTableCode: z.string().optional(),
|
||||
valueGp: z.number().int().optional(),
|
||||
startingOption: z.boolean(),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const creatureDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
level: z.number().int().positive(),
|
||||
category: z.string().min(1),
|
||||
hp: z.number().int().positive(),
|
||||
attackProfile: z.object({
|
||||
discipline: z.number().int(),
|
||||
precision: z.number().int(),
|
||||
damage: z.number().int(),
|
||||
numberAppearing: z.string().optional(),
|
||||
}),
|
||||
defenceProfile: z
|
||||
.object({
|
||||
armour: z.number().int().optional(),
|
||||
specialRules: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
xpReward: z.number().int().optional(),
|
||||
lootTableCodes: z.array(z.string()).optional(),
|
||||
interruptRules: z.array(z.string()).optional(),
|
||||
traits: z.array(z.string()).optional(),
|
||||
sourcePage: z.number().int().positive(),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const exitTemplateSchema = z.object({
|
||||
direction: z.enum(["north", "east", "south", "west"]).optional(),
|
||||
exitType: z.enum(["open", "door", "locked", "secret", "shaft", "stairs"]),
|
||||
destinationLevel: z.number().int().optional(),
|
||||
});
|
||||
|
||||
export const roomTemplateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
level: z.number().int().positive(),
|
||||
roomClass: z.enum(["normal", "small", "large", "special", "start", "stairs"]),
|
||||
tableCode: z.string().min(1),
|
||||
tableEntryKey: z.string().min(1),
|
||||
title: z.string().min(1),
|
||||
text: z.string().optional(),
|
||||
dimensions: z
|
||||
.object({
|
||||
width: z.number().int().positive(),
|
||||
height: z.number().int().positive(),
|
||||
})
|
||||
.optional(),
|
||||
exits: z.array(exitTemplateSchema).optional(),
|
||||
encounterRefs: z.array(contentReferenceSchema).optional(),
|
||||
objectRefs: z.array(contentReferenceSchema).optional(),
|
||||
tags: z.array(z.string()),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const townServiceDefinitionSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
serviceType: z.enum(["market", "temple", "tavern", "healer", "smith", "quest"]),
|
||||
tableCodes: z.array(z.string()).optional(),
|
||||
costRules: z.array(z.string()).optional(),
|
||||
effects: z.array(ruleEffectSchema).optional(),
|
||||
mvp: z.boolean(),
|
||||
});
|
||||
|
||||
export const contentPackSchema = z.object({
|
||||
version: z.string().min(1),
|
||||
sourceBooks: z.array(z.string()),
|
||||
tables: z.array(tableDefinitionSchema),
|
||||
weapons: z.array(weaponDefinitionSchema),
|
||||
manoeuvres: z.array(manoeuvreDefinitionSchema),
|
||||
armour: z.array(armourDefinitionSchema),
|
||||
items: z.array(itemDefinitionSchema),
|
||||
potions: z.array(potionDefinitionSchema),
|
||||
scrolls: z.array(scrollDefinitionSchema),
|
||||
creatures: z.array(creatureDefinitionSchema),
|
||||
roomTemplates: z.array(roomTemplateSchema),
|
||||
townServices: z.array(townServiceDefinitionSchema),
|
||||
});
|
||||
71
src/schemas/rules.ts
Normal file
71
src/schemas/rules.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const diceKindSchema = z.enum(["d3", "d6", "2d6", "d66"]);
|
||||
export const contentReferenceTypeSchema = z.enum([
|
||||
"table",
|
||||
"weapon",
|
||||
"manoeuvre",
|
||||
"armour",
|
||||
"item",
|
||||
"potion",
|
||||
"scroll",
|
||||
"creature",
|
||||
"room",
|
||||
"service",
|
||||
]);
|
||||
|
||||
export const contentReferenceSchema = z.object({
|
||||
type: contentReferenceTypeSchema,
|
||||
id: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ruleEffectSchema = z.object({
|
||||
type: z.enum([
|
||||
"gain-xp",
|
||||
"gain-gold",
|
||||
"heal",
|
||||
"take-damage",
|
||||
"modify-shift",
|
||||
"modify-discipline",
|
||||
"modify-precision",
|
||||
"apply-status",
|
||||
"remove-status",
|
||||
"add-item",
|
||||
"remove-item",
|
||||
"start-combat",
|
||||
"reveal-exit",
|
||||
"move-level",
|
||||
"log-only",
|
||||
]),
|
||||
amount: z.number().optional(),
|
||||
statusId: z.string().optional(),
|
||||
target: z.enum(["self", "enemy", "room", "campaign"]).optional(),
|
||||
referenceId: z.string().optional(),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
export const rollResultSchema = z.object({
|
||||
diceKind: diceKindSchema,
|
||||
rolls: z.array(z.number().int()),
|
||||
primary: z.number().int().optional(),
|
||||
secondary: z.number().int().optional(),
|
||||
total: z.number().int().optional(),
|
||||
modifier: z.number().int().optional(),
|
||||
modifiedTotal: z.number().int().optional(),
|
||||
clamped: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const logEntrySchema = z.object({
|
||||
id: z.string().min(1),
|
||||
at: z.string().min(1),
|
||||
type: z.enum(["system", "roll", "combat", "loot", "room", "town", "progression"]),
|
||||
text: z.string().min(1),
|
||||
relatedIds: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const actionResolutionSchema = z.object({
|
||||
success: z.boolean(),
|
||||
effects: z.array(ruleEffectSchema),
|
||||
logEntries: z.array(logEntrySchema),
|
||||
warnings: z.array(z.string()).optional(),
|
||||
});
|
||||
217
src/schemas/state.ts
Normal file
217
src/schemas/state.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { logEntrySchema, rollResultSchema, ruleEffectSchema } from "./rules";
|
||||
|
||||
export const statusInstanceSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
source: z.string().optional(),
|
||||
duration: z.enum(["round", "combat", "room", "run", "permanent"]).optional(),
|
||||
value: z.number().optional(),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
export const inventoryEntrySchema = z.object({
|
||||
definitionId: z.string().min(1),
|
||||
quantity: z.number().int().nonnegative(),
|
||||
identified: z.boolean().optional(),
|
||||
charges: z.number().int().optional(),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
export const inventoryStateSchema = z.object({
|
||||
carried: z.array(inventoryEntrySchema),
|
||||
equipped: z.array(inventoryEntrySchema),
|
||||
stored: z.array(inventoryEntrySchema),
|
||||
currency: z.object({
|
||||
gold: z.number().int().nonnegative(),
|
||||
}),
|
||||
rationCount: z.number().int().nonnegative(),
|
||||
lightSources: z.array(inventoryEntrySchema),
|
||||
});
|
||||
|
||||
export const adventurerStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
level: z.number().int().positive(),
|
||||
xp: z.number().int().nonnegative(),
|
||||
hp: z.object({
|
||||
current: z.number().int(),
|
||||
max: z.number().int().positive(),
|
||||
base: z.number().int().positive(),
|
||||
}),
|
||||
stats: z.object({
|
||||
shift: z.number().int(),
|
||||
discipline: z.number().int(),
|
||||
precision: z.number().int(),
|
||||
}),
|
||||
weaponId: z.string().min(1),
|
||||
manoeuvreIds: z.array(z.string()),
|
||||
armourId: z.string().optional(),
|
||||
favour: z.record(z.string(), z.number().int()),
|
||||
statuses: z.array(statusInstanceSchema),
|
||||
inventory: inventoryStateSchema,
|
||||
progressionFlags: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const townStateSchema = z.object({
|
||||
visits: z.number().int().nonnegative(),
|
||||
knownServices: z.array(z.string()),
|
||||
stash: z.array(inventoryEntrySchema),
|
||||
pendingSales: z.array(inventoryEntrySchema),
|
||||
serviceFlags: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const questStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
title: z.string().min(1),
|
||||
status: z.enum(["available", "active", "completed", "failed"]),
|
||||
progressFlags: z.array(z.string()),
|
||||
rewardText: z.string().optional(),
|
||||
});
|
||||
|
||||
export const runSummarySchema = z.object({
|
||||
runId: z.string().min(1),
|
||||
startedAt: z.string().min(1),
|
||||
endedAt: z.string().optional(),
|
||||
deepestLevel: z.number().int().positive(),
|
||||
roomsVisited: z.number().int().nonnegative(),
|
||||
creaturesDefeated: z.array(z.string()),
|
||||
xpGained: z.number().int().nonnegative(),
|
||||
treasureValue: z.number().int().nonnegative(),
|
||||
outcome: z.enum(["escaped", "defeated", "saved-in-progress"]),
|
||||
});
|
||||
|
||||
export const campaignStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
createdAt: z.string().min(1),
|
||||
updatedAt: z.string().min(1),
|
||||
rulesVersion: z.string().min(1),
|
||||
contentVersion: z.string().min(1),
|
||||
adventurer: adventurerStateSchema,
|
||||
unlockedLevels: z.array(z.number().int().positive()),
|
||||
completedLevels: z.array(z.number().int().positive()),
|
||||
townState: townStateSchema,
|
||||
questState: z.array(questStateSchema),
|
||||
campaignFlags: z.array(z.string()),
|
||||
runHistory: z.array(runSummarySchema),
|
||||
});
|
||||
|
||||
export const roomExitStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
direction: z.enum(["north", "east", "south", "west"]),
|
||||
exitType: z.enum(["open", "door", "locked", "secret", "shaft", "stairs"]),
|
||||
discovered: z.boolean(),
|
||||
traversable: z.boolean(),
|
||||
leadsToRoomId: z.string().optional(),
|
||||
destinationLevel: z.number().int().optional(),
|
||||
});
|
||||
|
||||
export const encounterStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
sourceTableCode: z.string().optional(),
|
||||
creatureIds: z.array(z.string()),
|
||||
resolved: z.boolean(),
|
||||
surprise: z.boolean().optional(),
|
||||
rewardPending: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const roomObjectStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
objectType: z.enum(["container", "altar", "corpse", "hazard", "feature", "quest"]),
|
||||
sourceTableCode: z.string().optional(),
|
||||
interacted: z.boolean(),
|
||||
hidden: z.boolean().optional(),
|
||||
effects: z.array(ruleEffectSchema).optional(),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
export const roomStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
level: z.number().int().positive(),
|
||||
templateId: z.string().optional(),
|
||||
position: z.object({
|
||||
x: z.number().int(),
|
||||
y: z.number().int(),
|
||||
}),
|
||||
dimensions: z.object({
|
||||
width: z.number().int().positive(),
|
||||
height: z.number().int().positive(),
|
||||
}),
|
||||
roomClass: z.enum(["normal", "small", "large", "special", "start", "stairs"]),
|
||||
exits: z.array(roomExitStateSchema),
|
||||
discovery: z.object({
|
||||
generated: z.boolean(),
|
||||
entered: z.boolean(),
|
||||
cleared: z.boolean(),
|
||||
searched: z.boolean(),
|
||||
}),
|
||||
encounter: encounterStateSchema.optional(),
|
||||
objects: z.array(roomObjectStateSchema),
|
||||
notes: z.array(z.string()),
|
||||
flags: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const dungeonLevelStateSchema = z.object({
|
||||
level: z.number().int().positive(),
|
||||
themeName: z.string().optional(),
|
||||
rooms: z.record(z.string(), roomStateSchema),
|
||||
discoveredRoomOrder: z.array(z.string()),
|
||||
stairsUpRoomId: z.string().optional(),
|
||||
stairsDownRoomId: z.string().optional(),
|
||||
secretDoorUsed: z.boolean().optional(),
|
||||
exhaustedExitSearch: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const dungeonStateSchema = z.object({
|
||||
levels: z.record(z.string(), dungeonLevelStateSchema),
|
||||
revealedPercentByLevel: z.record(z.string(), z.number()),
|
||||
globalFlags: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const combatantStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
sourceDefinitionId: z.string().optional(),
|
||||
hpCurrent: z.number().int(),
|
||||
hpMax: z.number().int().positive(),
|
||||
shift: z.number().int(),
|
||||
discipline: z.number().int(),
|
||||
precision: z.number().int(),
|
||||
armourValue: z.number().int().optional(),
|
||||
statuses: z.array(statusInstanceSchema),
|
||||
traits: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const interruptStateSchema = z.object({
|
||||
source: z.enum(["player", "enemy"]),
|
||||
trigger: z.string().min(1),
|
||||
effectText: z.string().min(1),
|
||||
resolved: z.boolean(),
|
||||
});
|
||||
|
||||
export const combatStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
round: z.number().int().positive(),
|
||||
actingSide: z.enum(["player", "enemy"]),
|
||||
fatigueDie: z.number().int().optional(),
|
||||
player: combatantStateSchema,
|
||||
enemies: z.array(combatantStateSchema),
|
||||
selectedManoeuvreId: z.string().optional(),
|
||||
lastRoll: rollResultSchema.optional(),
|
||||
pendingInterrupt: interruptStateSchema.optional(),
|
||||
combatLog: z.array(logEntrySchema),
|
||||
});
|
||||
|
||||
export const runStateSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
campaignId: z.string().min(1),
|
||||
status: z.enum(["active", "paused", "completed", "failed"]),
|
||||
startedAt: z.string().min(1),
|
||||
currentLevel: z.number().int().positive(),
|
||||
currentRoomId: z.string().optional(),
|
||||
dungeon: dungeonStateSchema,
|
||||
adventurerSnapshot: adventurerStateSchema,
|
||||
activeCombat: combatStateSchema.optional(),
|
||||
log: z.array(logEntrySchema),
|
||||
pendingEffects: z.array(ruleEffectSchema),
|
||||
});
|
||||
Reference in New Issue
Block a user