Skip to content

Commit

Permalink
closes #43
Browse files Browse the repository at this point in the history
  • Loading branch information
seiyria committed Aug 17, 2024
1 parent 86f2d7e commit 6dbc76f
Show file tree
Hide file tree
Showing 15 changed files with 468 additions and 79 deletions.
11 changes: 9 additions & 2 deletions src/app/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,20 @@ export const typePropSets: Record<string, string[]> = {

export const typePropDefaults: Record<
string,
Record<string, number | string | boolean | Partial<StatBlock>>
Record<
string,
| number
| string
| boolean
| Partial<StatBlock>
| Array<{ id: string; text: string }>
>
> = {
Arrow: { shots: 1000, tier: 1, damageClass: 'physical' },
Bottle: { ounces: 1 },
Food: { ounces: 1 },
Gem: {},
Book: { bookPages: 1, bookItemFilter: '', bookFindablePages: '' },
Book: { bookPages: [], bookItemFilter: '', bookFindablePages: '' },
Trap: { trapUses: 1 },
Twig: { type: 'Staff' },
};
Expand Down
8 changes: 5 additions & 3 deletions src/app/helpers/export/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,11 @@ export function formatNPCs(npcs: INPCDefinition[]): INPCDefinition[] {
delete npc.items.equipment[slot];
});

npc.triggers.leash.messages = npc.triggers.leash.messages.filter(Boolean);
npc.triggers.spawn.messages = npc.triggers.spawn.messages.filter(Boolean);
npc.triggers.leash.messages = npc.triggers.leash.messages.filter(Boolean);
['leash', 'spawn', 'combat'].forEach((triggerType) => {
if (!npc.triggers?.[triggerType]?.messages) return;
npc.triggers[triggerType].messages =
npc.triggers[triggerType].messages.filter(Boolean);
});

return fillInNPCProperties(npc as INPCDefinition);
});
Expand Down
2 changes: 1 addition & 1 deletion src/app/helpers/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const defaultNPC: () => INPCDefinition = () => ({
leash: {
messages: [''],
sfx: {
name: '',
name: undefined as unknown as string,
maxChance: 0,
},
},
Expand Down
207 changes: 191 additions & 16 deletions src/app/helpers/schemas/_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,175 @@
import { difference, get, isNumber, isUndefined } from 'lodash';
import { HasIdentification, Schema } from '../../../interfaces';
import { difference, get, isNumber, isString, isUndefined } from 'lodash';
import {
Allegiance,
HasIdentification,
ItemSlot,
QuestRewardType,
Schema,
SchemaValidator,
SchemaValidatorMessage,
Skill,
Stat,
} from '../../../interfaces';

export function isInteger(num: any): boolean {
return isNumber(num) && Math.floor(num) === num;
export function isArrayOf(validator: SchemaValidator): SchemaValidator {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return (val: any) => val.every(validator);
}

export function isArrayOfAtLeastLength(length: number): SchemaValidator {
return (val: any) => val.filter(Boolean).length >= length;
}

export function isArrayOfAtMostLength(length: number): SchemaValidator {
return (val: any) => val.filter(Boolean).length <= length;
}

export function isObjectWith(keys: string[]): SchemaValidator {
return (val: any) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
if (Object.keys(val).length !== keys.length) return false;
return keys.every((k) => !isUndefined(val[k]));
};
}

export function isObjectWithFailure(keys: string[]): SchemaValidatorMessage {
return (val: any) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
if (Object.keys(val).length !== keys.length)
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return `keys: ${keys.join(', ')} vs ${Object.keys(val).join(
', '
)} is of different length`;
return `keys: ${keys
.filter((k) => isUndefined(val[k]))
.join(', ')} must not be undefined`;
};
}

export function isObjectWithSome(keys: string[]): SchemaValidator {
return (val: any) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return Object.keys(val).every((k) => keys.includes(k));
};
}

export function isObjectWithSomeFailure(
keys: string[]
): SchemaValidatorMessage {
return (val: any) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return `extraneous keys: ${Object.keys(val)
.map((k) => (keys.includes(k) ? '' : k))
.filter(Boolean)
.join(', ')}`;
};
}

export function isPartialObjectOf<T>(possibleVals: T[]): SchemaValidator {
return (val: any) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
Object.keys(val).every((k) => possibleVals.includes(k as T));
}

export function isPartialObjectOfFailure<T>(
possibleVals: T[]
): SchemaValidatorMessage {
return (val: any) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
`extraneous keys: ${Object.keys(val)
.map((k) => (possibleVals.includes(k as T) ? '' : k))
.filter(Boolean)
.join(', ')}`;
}

export const isPartialStatObject = isPartialObjectOf<Stat>(Object.values(Stat));
export const isPartialStatObjectFailure = isPartialObjectOfFailure<Stat>(
Object.values(Stat)
);

export const isPartialSkillObject = isPartialObjectOf<Skill>(
Object.values(Skill)
);
export const isPartialSkillObjectFailure = isPartialObjectOfFailure<Skill>(
Object.values(Skill)
);

export const isPartialEquipmentObject = isPartialObjectOf<ItemSlot>(
Object.values(ItemSlot)
);
export const isPartialEquipmentObjectFailure =
isPartialObjectOfFailure<ItemSlot>(Object.values(ItemSlot));

export const isPartialReputationObject = isPartialObjectOf<Allegiance>(
Object.values(Allegiance)
);
export const isPartialReputationObjectFailure =
isPartialObjectOfFailure<Allegiance>(Object.values(Allegiance));

export function isItemSlot(val: any): boolean {
return Object.values(ItemSlot).includes(val as ItemSlot);
}

export function isTraitObject(val: any): boolean {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return Object.keys(val).every((k) => isInteger(val[k]));
}

export function isStat(val: any): boolean {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return Object.values(Stat).includes(val);
}

export function isAllegiance(val: any): boolean {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return Object.values(Allegiance).includes(val);
}

export function isRepMod(val: any): boolean {
return isInteger(val.delta) && isAllegiance(val.allegiance);
}

export function isRandomStatObject(val: any): boolean {
const allStats = Object.values(Stat);

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return Object.keys(val).every(
(stat) =>
allStats.includes(stat as Stat) &&
isObjectWith(['min', 'max'])(val[stat]) &&
isInteger(val[stat].min) &&
isInteger(val[stat].max)
);
}

export function isRandomTraitObject(val: any): boolean {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return (
isObjectWith(['name', 'level'])(val) &&
isArrayOf(isString)(val.name) &&
isRandomNumber(val.level)
);
}

export function isNPCEffect(val: any): boolean {
return (
val.endsAt === -1 &&
isString(val.name) &&
isObjectWithSome(['potency', 'damageType', 'enrageTimer'])(val.extra)
);
}

export function isQuestReward(val: any): boolean {
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
Object.values(QuestRewardType).includes(val.type) &&
isInteger(val.value) &&
(val.statName ? isStat(val.statName) || isAllegiance(val.statName) : true)
);
}

export function isOzIngredient(val: any): boolean {
return isString(val.filter) && isString(val.display) && isInteger(val.ounces);
}

export function isCosmetic(cos: any): boolean {
Expand All @@ -17,21 +184,19 @@ export function isSuccor(suc: any): boolean {
return suc.map && isInteger(suc.x) && isInteger(suc.y);
}

export function isOptionalRollable(rol: any): boolean {
if (!rol || rol.length === 0) return true;

return !!(rol[0].chance && rol[0].result);
}

export function isRollable(rol: any): boolean {
return !!(rol.length > 0 && rol[0].chance && rol[0].result);
return !!(rol.chance && rol.result);
}

export function isTrait(trait: any): boolean {
return !!(trait.name && isNumber(trait.level));
}

export function isIntegerBetween(min: any, max: any): (val: any) => boolean {
export function isInteger(num: any): boolean {
return isNumber(num) && Math.floor(num) === num;
}

export function isIntegerBetween(min: any, max: any): SchemaValidator {
return (num) => num >= min && num <= max && isInteger(num);
}

Expand All @@ -43,16 +208,24 @@ export function isEncrust(enc: any): boolean {
return !!(enc.stats || enc.equipEffect || enc.strikeEffect);
}

export function isBookPage(val: any): boolean {
return val.id === '' && isString(val.text);
}

export function isDropPool(pool: any): boolean {
return (
isInteger(pool.choose.min) &&
isInteger(pool.choose.max) &&
isRollable(pool.items)
isArrayOf(isRollable)(pool.items)
);
}

export function isRandomNumber(num: any) {
return isInteger(num.min) && isInteger(num.max);
return (
isObjectWith(['min', 'max'])(num) &&
isInteger(num.min) &&
isInteger(num.max)
);
}

export function validateSchema<T extends HasIdentification>(
Expand All @@ -62,7 +235,7 @@ export function validateSchema<T extends HasIdentification>(
): string[] {
const errors: string[] = [];

schema.forEach(([prop, required, validator]) => {
schema.forEach(([prop, required, validator, message]) => {
const value = get(obj, prop);

if (isUndefined(value)) {
Expand All @@ -75,7 +248,9 @@ export function validateSchema<T extends HasIdentification>(

if (!validator(value))
errors.push(
`Property '${prop}' does not pass validation for '${label}'.`
`Property '${prop}' does not pass validation for '${label}' (${
message?.(value) ?? 'no additional information provided'
}).`
);
});

Expand Down
29 changes: 21 additions & 8 deletions src/app/helpers/schemas/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { isArray, isNumber, isObject, isString } from 'lodash';
import { isNumber, isObject, isString } from 'lodash';
import { Schema } from '../../../interfaces';
import { isRandomNumber } from './_helpers';
import {
isArrayOf,
isObjectWith,
isPartialEquipmentObject,
isPartialEquipmentObjectFailure,
isPartialStatObject,
isPartialStatObjectFailure,
isRandomNumber,
} from './_helpers';

export const dialogSchema: Schema = [
['tag', true, isString],
Expand All @@ -14,10 +22,15 @@ export const dialogSchema: Schema = [
['hp', false, isRandomNumber],
['mp', false, isRandomNumber],

['otherStats', false, isObject],
['usableSkills', false, isArray],
['items', false, isObject],
['items.equipment', false, isObject],
['dialog', false, isObject],
['behaviors', false, isArray],
['otherStats', false, isPartialStatObject, isPartialStatObjectFailure],
['usableSkills', false, isArrayOf(isString)],
['items', false, isObjectWith(['equipment'])],
[
'items.equipment',
false,
isPartialEquipmentObject,
isPartialEquipmentObjectFailure,
],
['dialog', false, isObjectWith(['keyword'])],
['behaviors', false, isArrayOf(isObject)],
];
5 changes: 3 additions & 2 deletions src/app/helpers/schemas/droptable.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { isArray, isBoolean, isString } from 'lodash';
import { isBoolean, isString } from 'lodash';
import { Schema } from '../../../interfaces';
import { isArrayOf, isRollable } from './_helpers';

export const droptableSchema: Schema = [
['mapName', false, isString],
['regionName', false, isString],
['isGlobal', false, isBoolean],
['drops', true, isArray],
['drops', true, isArrayOf(isRollable)],
];
Loading

0 comments on commit 6dbc76f

Please sign in to comment.