Moved attributes and adjustments to typed schemas

This commit is contained in:
ThaumRystra
2025-01-10 09:35:30 +02:00
parent fcf6a84b01
commit 0f32afd25a
10 changed files with 84 additions and 177 deletions

View File

@@ -1,7 +1,7 @@
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema';
import ChildSchema, { TreeDoc } from '/imports/api/parenting/ChildSchema';
import ChildSchema from '/imports/api/parenting/ChildSchema';
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema';
import propertySchemasIndex from '/imports/api/properties/computedPropertySchemasIndex';
import { storedIconsSchema } from '/imports/api/icons/Icons';
@@ -130,17 +130,6 @@ const DenormalisedOnlyCreaturePropertySchema = new TypedSimpleSchema({
const CreaturePropertySchema = PreComputeCreaturePropertySchema.extend(DenormalisedOnlyCreaturePropertySchema);
type CreaturePropertyByType<T extends keyof typeof propertySchemasIndex> =
InferType<typeof propertySchemasIndex[T]>
& InferType<typeof CreaturePropertySchema>
& InferType<typeof ColorSchema>
& InferType<typeof ChildSchema>
& InferType<typeof SoftRemovableSchema>
type ConvertToUnion<T> = T[keyof T];
type CreatureProperty = ConvertToUnion<{ [key in keyof typeof propertySchemasIndex]: CreaturePropertyByType<key> }>;
type ActionProperty = CreaturePropertyByType<'action'>;
const CreatureProperties = new Mongo.Collection<CreatureProperty>('creatureProperties');
let key: keyof typeof propertySchemasIndex;
@@ -164,6 +153,16 @@ for (key in propertySchemasIndex) {
}
}
export type CreaturePropertyByType<T extends keyof typeof propertySchemasIndex> =
InferType<typeof propertySchemasIndex[T]>
& InferType<typeof CreaturePropertySchema>
& InferType<typeof ColorSchema>
& InferType<typeof ChildSchema>
& InferType<typeof SoftRemovableSchema>
type ConvertToUnion<T> = T[keyof T];
export type CreatureProperty = ConvertToUnion<{ [key in keyof typeof propertySchemasIndex]: CreaturePropertyByType<key> }>;
export default CreatureProperties;
export {
DenormalisedOnlyCreaturePropertySchema,

View File

@@ -1,5 +1,6 @@
import SimpleSchema from 'simpl-schema';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
export interface Reference {
collection: string,
@@ -15,7 +16,7 @@ export interface TreeDoc {
removed?: true,
}
const RefSchema = new SimpleSchema({
const RefSchema = new TypedSimpleSchema({
id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
@@ -26,7 +27,7 @@ const RefSchema = new SimpleSchema({
},
});
const ChildSchema = new SimpleSchema({
const ChildSchema = new TypedSimpleSchema({
root: {
type: Object,
},
@@ -47,7 +48,7 @@ const ChildSchema = new SimpleSchema({
optional: true,
},
/**
* The tree structure goes as follows where the numbering follows a counterclockwise depth first
* The tree structure goes as follows where the numbering follows a counter-clockwise depth first
* path around the tree. The canonical structure comes from the root and parentId references,
* while the left and right numbering is used to optimize ancestor queries.
*

View File

@@ -1,6 +1,7 @@
import SimpleSchema from 'simpl-schema';
import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
let SoftRemovableSchema = new SimpleSchema({
let SoftRemovableSchema = new TypedSimpleSchema({
'removed': {
type: Boolean,
optional: true,

View File

@@ -3,64 +3,7 @@ import createPropertySchema from '/imports/api/properties/subSchemas/createPrope
import { storedIconsSchema } from '/imports/api/icons/Icons';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX';
import { CreatureProperty } from '/imports/api/creature/creatureProperties/CreatureProperties';
import { InlineCalculation } from '/imports/api/properties/subSchemas/inlineCalculationField';
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
import Property from '/imports/api/properties/Properties.type';
import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
export type CreatureAction = Action & CreatureProperty & {
overridden?: boolean
insufficientResources?: boolean
}
/*
* Actions are things a character can do
*/
export interface Action extends ActionBase {
type: 'action'
}
/**
* Base property type for both spells and actions
*/
export interface ActionBase extends Property {
name?: string
summary?: InlineCalculation
description?: InlineCalculation
actionType: 'action' | 'bonus' | 'attack' | 'reaction' | 'free' | 'long' | 'event'
variableName?: string
target: 'self' | 'singleTarget' | 'multipleTargets'
attackRoll?: CalculatedField
uses?: CalculatedField
usesUsed?: number
reset?: string
silent?: boolean
usesLeft?: number
// Resources
resources: {
itemsConsumed: {
_id: string
tag?: string
itemName?: string
quantity?: CalculatedField
itemId?: string
available?: number
}[]
attributesConsumed: {
_id: string
variableName?: string
quantity?: CalculatedField
available?: number
statName?: string
}[]
conditions?: {
_id: string,
condition?: CalculatedField
conditionNote?: string,
}[]
}
}
import { Expand, InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
/*
* Actions are things a character can do
@@ -72,11 +15,11 @@ const ActionSchema = createPropertySchema({
max: STORAGE_LIMITS.name,
},
summary: {
type: 'inlineCalculationFieldToCompute',
type: 'inlineCalculationFieldToCompute' as const,
optional: true,
},
description: {
type: 'inlineCalculationFieldToCompute',
type: 'inlineCalculationFieldToCompute' as const,
optional: true,
},
// What time-resource is used to take the action in combat
@@ -102,16 +45,16 @@ const ActionSchema = createPropertySchema({
'self',
'singleTarget',
'multipleTargets',
],
] as const,
},
// Some actions have an attack roll
attackRoll: {
type: 'fieldToCompute',
type: 'fieldToCompute' as const,
optional: true,
},
// Calculation of how many times this action can be used
uses: {
type: 'fieldToCompute',
type: 'fieldToCompute' as const,
optional: true,
},
// Integer of how many times it has already been used
@@ -152,7 +95,7 @@ const ActionSchema = createPropertySchema({
optional: true,
},
'resources.itemsConsumed.$.quantity': {
type: 'fieldToCompute',
type: 'fieldToCompute' as const,
optional: true,
},
'resources.itemsConsumed.$.itemId': {
@@ -181,7 +124,7 @@ const ActionSchema = createPropertySchema({
max: STORAGE_LIMITS.variableName,
},
'resources.attributesConsumed.$.quantity': {
type: 'fieldToCompute',
type: 'fieldToCompute' as const,
optional: true,
},
'resources.conditions': {
@@ -200,7 +143,7 @@ const ActionSchema = createPropertySchema({
}
},
'resources.conditions.$.condition': {
type: 'fieldToCompute',
type: 'fieldToCompute' as const,
optional: true,
},
'resources.conditions.$.conditionNote': {
@@ -217,11 +160,11 @@ const ActionSchema = createPropertySchema({
const ComputedOnlyActionSchema = createPropertySchema({
summary: {
type: 'computedOnlyInlineCalculationField',
type: 'computedOnlyInlineCalculationField' as const,
optional: true,
},
description: {
type: 'computedOnlyInlineCalculationField',
type: 'computedOnlyInlineCalculationField' as const,
optional: true,
},
// True if the uses left is zero, or any item or attribute consumed is
@@ -232,12 +175,12 @@ const ComputedOnlyActionSchema = createPropertySchema({
removeBeforeCompute: true,
},
attackRoll: {
type: 'computedOnlyField',
type: 'computedOnlyField' as const,
optional: true,
},
uses: {
parseLevel: 'reduce',
type: 'computedOnlyField',
type: 'computedOnlyField' as const,
optional: true,
},
// Uses - usesUsed
@@ -270,7 +213,7 @@ const ComputedOnlyActionSchema = createPropertySchema({
removeBeforeCompute: true,
},
'resources.itemsConsumed.$.quantity': {
type: 'computedOnlyField',
type: 'computedOnlyField' as const,
optional: true,
},
'resources.itemsConsumed.$.itemName': {
@@ -299,7 +242,7 @@ const ComputedOnlyActionSchema = createPropertySchema({
type: Object,
},
'resources.attributesConsumed.$.quantity': {
type: 'computedOnlyField',
type: 'computedOnlyField' as const,
optional: true,
},
'resources.attributesConsumed.$.available': {
@@ -319,4 +262,8 @@ const ComputedActionSchema = new TypedSimpleSchema({})
.extend(ActionSchema)
.extend(ComputedOnlyActionSchema);
export type Action = InferType<typeof ActionSchema>;
export type ComputedOnlyAction = InferType<typeof ComputedOnlyActionSchema>;
export type ComputedAction = Expand<InferType<typeof ActionSchema> & InferType<typeof ComputedOnlyActionSchema>>;
export { ActionSchema, ComputedOnlyActionSchema, ComputedActionSchema };

View File

@@ -1,12 +1,12 @@
import SimpleSchema from 'simpl-schema';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema';
import { Expand, InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
const AdjustmentSchema = createPropertySchema({
// The roll that determines how much to change the attribute
// This can be simplified, but should only compute when activated
amount: {
type: 'fieldToCompute',
type: 'fieldToCompute' as const,
parseLevel: 'compile',
optional: true,
defaultValue: 1,
@@ -40,14 +40,18 @@ const AdjustmentSchema = createPropertySchema({
const ComputedOnlyAdjustmentSchema = createPropertySchema({
amount: {
type: 'computedOnlyField',
type: 'computedOnlyField' as const,
parseLevel: 'compile',
optional: true,
},
});
const ComputedAdjustmentSchema = new SimpleSchema()
const ComputedAdjustmentSchema = new TypedSimpleSchema({})
.extend(AdjustmentSchema)
.extend(ComputedOnlyAdjustmentSchema);
export type Adjustment = InferType<typeof AdjustmentSchema>;
export type ComputedOnlyAdjustment = InferType<typeof ComputedOnlyAdjustmentSchema>;
export type ComputedAdjustment = Expand<InferType<typeof AdjustmentSchema> & InferType<typeof ComputedOnlyAdjustmentSchema>>;
export { AdjustmentSchema, ComputedAdjustmentSchema, ComputedOnlyAdjustmentSchema };

View File

@@ -2,52 +2,7 @@ import SimpleSchema from 'simpl-schema';
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema';
import { CreatureProperty } from '/imports/api/creature/creatureProperties/CreatureProperties';
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
import { InlineCalculation } from '/imports/api/properties/subSchemas/inlineCalculationField';
import { ConstantValueType } from '/imports/parser/parseTree/constant';
import Property from '/imports/api/properties/Properties.type';
export type CreatureAttribute = Attribute & CreatureProperty & {
total?: ConstantValueType;
value?: ConstantValueType;
modifier?: number;
proficiency?: 0 | 0.49 | 0.5 | 1 | 2;
advantage?: -1 | 0 | 1;
constitutionMod?: number;
hide?: true;
overridden?: true;
effectIds?: string[];
proficiencyIds?: string[];
definitions?: { _id: string, type: string, row?: number }[];
}
export interface Attribute extends Property {
type: 'attribute';
name?: string;
variableName?: string;
attributeType: 'ability' | 'stat' | 'modifier' | 'hitDice' | 'healthBar' | 'resource' |
'spellSlot' | 'utility';
hitDiceSize?: 'd1' | 'd2' | 'd4' | 'd6' | 'd8' | 'd10' | 'd12' | 'd20' | 'd100';
spellSlotLevel?: CalculatedField;
healthBarColorMid?: string;
healthBarColorLow?: string;
healthBarNoDamage?: true;
healthBarNoHealing?: true;
healthBarNoDamageOverflow?: true;
healthBarNoHealingOverflow?: true;
healthBarDamageOrder?: number;
healthBarHealingOrder?: number;
baseValue?: CalculatedField;
description?: InlineCalculation;
damage?: number;
decimal?: true;
ignoreLowerLimit?: true;
ignoreUpperLimit?: true;
hideWhenTotalZero?: true;
hideWhenValueZero?: true;
reset?: string;
}
import { Expand, InferType } from '/imports/api/utility/TypedSimpleSchema';
/*
* Attributes are numbered stats of a character
@@ -90,7 +45,7 @@ const AttributeSchema = createPropertySchema({
},
// For type spellSlot, the level needs to be stored separately
spellSlotLevel: {
type: 'fieldToCompute',
type: 'fieldToCompute' as const,
optional: true,
},
// For type healthBar midColor, and lowColor can be set separately from the
@@ -134,12 +89,12 @@ const AttributeSchema = createPropertySchema({
},
// The starting value, before effects
baseValue: {
type: 'fieldToCompute',
type: 'fieldToCompute' as const,
optional: true,
},
// Description of what the attribute is used for
description: {
type: 'inlineCalculationFieldToCompute',
type: 'inlineCalculationFieldToCompute' as const,
optional: true,
},
// The damage done to the attribute, should always compute as positive
@@ -182,15 +137,15 @@ const AttributeSchema = createPropertySchema({
const ComputedOnlyAttributeSchema = createPropertySchema({
description: {
type: 'computedOnlyInlineCalculationField',
type: 'computedOnlyInlineCalculationField' as const,
optional: true,
},
baseValue: {
type: 'computedOnlyField',
type: 'computedOnlyField' as const,
optional: true,
},
spellSlotLevel: {
type: 'computedOnlyField',
type: 'computedOnlyField' as const,
optional: true,
},
// The computed value of the attribute
@@ -346,4 +301,8 @@ const ComputedAttributeSchema = new SimpleSchema({})
.extend(ComputedOnlyAttributeSchema)
.extend(AttributeSchema);
export type Attribute = InferType<typeof AttributeSchema>;
export type ComputedOnlyAttribute = InferType<typeof ComputedOnlyAttributeSchema>;
export type ComputedAttribute = Expand<InferType<typeof AttributeSchema> & InferType<typeof ComputedOnlyAttributeSchema>>;
export { AttributeSchema, ComputedOnlyAttributeSchema, ComputedAttributeSchema };

View File

@@ -4,8 +4,11 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import { ConstantValueType } from '/imports/parser/parseTree/constant';
export interface CalculatedField {
export type FieldToCalculate = {
calculation?: string;
}
export type CalculatedOnlyField = {
value?: ConstantValueType;
valueNode: ParseNode;
effectIds?: string[];
@@ -17,6 +20,8 @@ export interface CalculatedField {
errors?: any[];
}
export type CalculatedField = FieldToCalculate & CalculatedOnlyField;
// Get schemas that apply fields directly so they can be gracefully extended
// because {type: Schema} fields can't be extended
function fieldToCompute(field) {

View File

@@ -11,7 +11,7 @@ import { Definition, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleS
// Search through the schema for keys whose type is 'fieldToCompute' etc.
// replace the type with Object and attach extend the schema with
// the required fields to make the computation work
export default function createPropertySchema(definition: Definition) {
export default function createPropertySchema<T extends Definition>(definition: T): TypedSimpleSchema<T> {
const computationFields = {
inlineCalculationFieldToCompute: [],
computedOnlyInlineCalculationField: [],

View File

@@ -3,7 +3,11 @@ import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import { CalculatedField } from './computedField';
export interface InlineCalculation {
export type InlineCalculationFieldToCompute = {
text?: string,
}
export type ComputedOnlyInlineCalculationField = {
text?: string,
hash?: number,
value?: string,
@@ -45,6 +49,7 @@ function computedOnlyInlineCalculationField(field) {
type: String,
optional: true,
max: STORAGE_LIMITS.inlineCalculationField,
// @ts-expect-error removeBeforeCompute is an extension of SimpleSchema
removeBeforeCompute: true,
},
[`${field}.inlineCalculations`]: {

View File

@@ -1,4 +1,10 @@
import SimpleSchema, { SimpleSchemaDefinition } from 'simpl-schema';
import type {
FieldToCalculate, CalculatedOnlyField
} from '/imports/api/properties/subSchemas/computedField';
import type {
InlineCalculationFieldToCompute, ComputedOnlyInlineCalculationField
} from '/imports/api/properties/subSchemas/inlineCalculationField';
// It DOES NOT support a constructor with multiple schemas.
export type Definition = Exclude<SimpleSchemaDefinition, any[]>;
@@ -27,7 +33,7 @@ type NotImplementedMarker = { readonly NotImplementedMarker: unique symbol };
type ArrayMarker = { readonly ArrayMarker: unique symbol };
type ObjectMarker = { readonly ObjectMarker: unique symbol };
export type InferType<T> = ExpandRecursively<MakeUndefinedOptional<InferTypeInner<T>>>;
export type InferType<T> = Expand<MakeUndefinedOptional<InferTypeInner<T>>>;
// Infer TypeScript type from SimpleSchema type.
type InferTypeInner<T> =
@@ -36,8 +42,10 @@ type InferTypeInner<T> =
// eslint-disable-next-line @typescript-eslint/ban-types
T extends typeof Function ? Function :
T extends typeof Number ? number :
T extends typeof SimpleSchema.Integer ? number :
T extends typeof Object ? ObjectMarker :
T extends typeof String ? string :
T extends typeof Date ? Date :
T extends RegExp ? string :
T extends TypedSimpleSchema<infer U> ? InferSchema<U> :
NotImplementedMarker;
@@ -50,8 +58,11 @@ export type InferField<Def extends Definition, Key extends keyof Def> =
? Array<InferField<Def, `${Key}.$`>>
: ObjectMarker extends InferTypeInner<Typ>
? { [L in keyof Def as L extends `${Key}.${infer SubKey}` ? SubKey extends `${string}.${string}` ? never : SubKey : never]: InferField<Def, L> }
: Def[Key] extends { allowedValues: infer Allowed extends string[] }
? InferOptional<Def, Key, InferEnum<Allowed>>
: Def[Key] extends { allowedValues: infer Allowed extends string[] } ? InferOptional<Def, Key, InferEnum<Allowed>>
: Def[Key] extends { type: 'fieldToCompute' } ? FieldToCalculate
: Def[Key] extends { type: 'computedOnlyField' } ? CalculatedOnlyField
: Def[Key] extends { type: 'inlineCalculationFieldToCompute' } ? InlineCalculationFieldToCompute
: Def[Key] extends { type: 'computedOnlyInlineCalculationField' } ? ComputedOnlyInlineCalculationField
: InferOptional<Def, Key, InferTypeInner<Typ>>
: NotImplementedMarker
: NotImplementedMarker
@@ -71,34 +82,9 @@ export type InferSchema<Def extends Definition> = InferField<
& { [Key in keyof Def as Key extends string ? `.${Key}` : never]: Def[Key] }, ''
>;
const testSchema = new TypedSimpleSchema({
name: {
type: String,
optional: true,
},
age: {
type: Number,
},
children: {
type: Array,
optional: true,
defaultValue: [],
},
'children.$': {
type: String,
},
type: {
type: String,
allowedValues: ['cat', 'dog'] as const,
optional: true,
}
});
// expands object types recursively
type ExpandRecursively<T> = T extends object
export type ExpandRecursively<T> = T extends object
? T extends infer O ? { [K in keyof O]: ExpandRecursively<O[K]> } : never
: T;
type testType = InferType<typeof testSchema>;
type subType = ExpandRecursively<testType>
export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;