Skip to content

Commit

Permalink
add dialog validators
Browse files Browse the repository at this point in the history
  • Loading branch information
seiyria committed Sep 27, 2024
1 parent dca8128 commit 56e37f0
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 7 deletions.
14 changes: 9 additions & 5 deletions src/app/helpers/data/extract-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ export function getAllNodesFromDialog(dialogEntry: any): any[] {
return nodes.flat(Infinity);
}

export function extractAllItemsFromDialog(
dialog: INPCScript,
validClasses: BaseClassType[]
): string[] {
const allDialogWords = Object.keys(dialog.dialog ?? {})
export function getAllDialogActions(dialog: INPCScript): any[] {
return Object.keys(dialog.dialog ?? {})
.flatMap((k) =>
Object.keys(dialog.dialog[k] ?? {}).map(
(d) => dialog.dialog[k][d].actions
Expand All @@ -32,6 +29,13 @@ export function extractAllItemsFromDialog(
.flat()
.map((a: any) => getAllNodesFromDialog(a))
.flat(Infinity);
}

export function extractAllItemsFromDialog(
dialog: INPCScript,
validClasses: BaseClassType[]
): string[] {
const allDialogWords = getAllDialogActions(dialog);

const allDialogItemNames = allDialogWords
.filter((i) => i.item)
Expand Down
39 changes: 38 additions & 1 deletion src/app/helpers/schemas/_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
difference,
get,
isArray,
isBoolean,
isNull,
isNumber,
Expand All @@ -10,8 +11,10 @@ import {
import {
Allegiance,
BaseClass,
Currency,
DamageClass,
HasIdentification,
Holiday,
ItemClass,
ItemSlot,
ITraitTreeRowTrait,
Expand Down Expand Up @@ -142,6 +145,14 @@ export function isItemSlot(val: any): boolean {
return itemSlots.includes(val as ItemSlot);
}

export function isDialogItemSlot(val: any): boolean {
const dialogSlots = [...itemSlots, 'sack', 'leftHand', 'rightHand'];
return (
dialogSlots.includes(val as ItemSlot) ||
(isArray(val) && val.every((i) => dialogSlots.includes(i 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]));
Expand All @@ -162,10 +173,36 @@ export function isAllegiance(val: any): boolean {
return Object.values(Allegiance).includes(val);
}

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

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

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

export function isDialogChatOption(val: any): boolean {
return isString(val.text) && isString(val.action);
}

export function isDialogItem(val: any): boolean {
return isString(val.name) && val.amount ? isNumber(val.amount) : true;
}

export function isAny(): boolean {
return true;
}

export function is(val: any): SchemaValidator {
return (match: any) => val === match;
}

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

Expand Down Expand Up @@ -313,7 +350,7 @@ export function validateSchema<T extends HasIdentification>(

if (!validator(value) && !isNull(value) && !isUndefined(value))
errors.push(
`Property '${prop}' does not pass validation for '${label}' (${
`Property '${prop}' (value: ${value}) does not pass validation for '${label}' (${
message?.(value) ?? 'no additional information provided'
}).`
);
Expand Down
176 changes: 176 additions & 0 deletions src/app/helpers/schemas/dialog-behaviors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { isArray, isBoolean, isNumber, isObject, isString } from 'lodash';
import { DialogActionType, Schema } from '../../../interfaces';
import {
is,
isAny,
isArrayOf,
isCurrency,
isDialogChatOption,
isDialogItem,
isDialogItemSlot,
isHoliday,
} from './_helpers';

export const dialogBehaviorSchemas: Record<DialogActionType, Schema> = {
[DialogActionType.Chat]: [
['type', true, is('chat')],
['displayNPCSprite', false, isNumber],
['displayNPCName', false, isString],
['displayNPCUUID', false, isString],
['displayItemName', false, isString],
['displayNPCSprite', false, isNumber],
['displayTitle', false, isString],
['maxDistance', false, isNumber],
['message', true, isString],
['width', false, isString],
['extraClasses', false, isArrayOf(isString)],
['options', false, isArrayOf(isDialogChatOption)],
],
[DialogActionType.CheckItem]: [
['type', true, is('checkItem')],
['fromHands', false, isBoolean],
['fromSack', false, isBoolean],
['checkProperty', false, isString],
['checkValue', false, isAny],
['slot', true, isDialogItemSlot],
['item', true, isDialogItem],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.CheckNoItem]: [
['type', true, is('checkNoItem')],
['fromHands', false, isBoolean],
['slot', true, isArrayOf(isDialogItemSlot)],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.TakeItem]: [
['type', true, is('takeItem')],
['slot', true, isDialogItemSlot],
['fromHands', false, isBoolean],
['item', true, isDialogItem],
],
[DialogActionType.GiveItem]: [
['type', true, is('giveItem')],
['slot', true, isDialogItemSlot],
['item', true, isDialogItem],
],
[DialogActionType.MergeAndGiveItem]: [
['type', true, is('mergeAndGiveItem')],
['slot', true, isDialogItemSlot],
['item', true, isDialogItem],
],
[DialogActionType.ModifyItem]: [
['type', true, is('modifyItem')],
['slot', true, isArrayOf(isDialogItemSlot)],
['item', false, isDialogItem],
['mods', true, isObject],
],
[DialogActionType.CheckItemCanUpgrade]: [
['type', true, is('checkItemCanUpgrade')],
['slot', true, isDialogItemSlot],
['upgrade', false, isString],
['fromHands', false, isBoolean],
['item', false, isDialogItem],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.AddUpgradeItem]: [
['type', true, is('addItemUpgrade')],
['slot', true, isDialogItemSlot],
['upgrade', false, isString],
],
[DialogActionType.GiveEffect]: [
['type', true, is('giveEffect')],
['effect', true, isString],
['duration', true, isNumber],
],
[DialogActionType.GiveCurrency]: [
['type', true, is('giveCurrency')],
['currency', true, isCurrency],
['amount', true, isNumber],
],
[DialogActionType.CheckQuest]: [
['type', true, is('checkQuest')],
['quest', true, isString],
['maxDistance', false, isNumber],
['questCompleteActions', false, isArray],
],
[DialogActionType.UpdateQuest]: [
['type', true, is('updateQuest')],
['quest', true, isString],
['maxDistance', false, isNumber],
['arrayItem', false, isString],
],
[DialogActionType.CheckHoliday]: [
['type', true, is('checkHoliday')],
['holiday', true, isHoliday],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.CheckDailyQuest]: [
['type', true, is('checkDailyQuest')],
['quests', true, isArrayOf(isString)],
['npc', true, isString],
['maxDistance', false, isNumber],
],
[DialogActionType.GiveQuest]: [
['type', true, is('giveQuest')],
['quest', true, isString],
['maxDistance', false, isNumber],
],
[DialogActionType.HasQuest]: [
['type', true, is('hasQuest')],
['quest', true, isString],
['maxDistance', false, isNumber],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.GiveDailyQuest]: [
['type', true, is('giveDailyQuest')],
['quests', true, isArrayOf(isString)],
['maxDistance', false, isNumber],
],
[DialogActionType.CheckLevel]: [
['type', true, is('checkLevel')],
['level', true, isNumber],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.CheckAlignment]: [
['type', true, is('checkAlignment')],
['alignment', true, isString],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.SetAlignment]: [
['type', true, is('setAlignment')],
['alignment', true, isString],
],
[DialogActionType.CheckNPCsAndDropItems]: [
['type', true, is('checkNearbyNPCsAndDropItems')],
['npcs', true, isArrayOf(isString)],
['item', true, isString],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.CheckAnyHostilesNearby]: [
['type', true, is('checkAnyHostilesNearby')],
['range', true, isNumber],
['checkFailActions', false, isArray],
['checkPassActions', true, isArray],
],
[DialogActionType.DropItems]: [
['type', true, is('dropItems')],
['item', true, isString],
['amount', true, isNumber],
],
[DialogActionType.KillSelfSilently]: [
['type', true, is('killSelfSilently')],
['leaveMessage', false, isString],
],
[DialogActionType.GrantAchievement]: [
['type', true, is('grantAchievement')],
['achievementName', true, isString],
],
};
1 change: 1 addition & 0 deletions src/app/helpers/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './achievement';
export * from './dialog';
export * from './dialog-behaviors';
export * from './droptable';
export * from './item';
export * from './npc';
Expand Down
2 changes: 2 additions & 0 deletions src/app/helpers/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
nonexistentNPCs,
nonexistentRecipes,
validateAchievements,
validateDialogActions,
validateDialogs,
validateDialogsItems,
validateDroptables,
Expand Down Expand Up @@ -56,6 +57,7 @@ export function validationMessagesForMod(
...checkMapSpawners(mod),
...checkMapItems(mod),
validateDialogs(mod),
validateDialogActions(mod),
validateDialogsItems(mod, classes),
validateTraitTrees(mod),
validateTraitTreeData(mod, classes),
Expand Down
30 changes: 29 additions & 1 deletion src/app/helpers/validators/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
BaseClassType,
DialogActionType,
IModKit,
INPCScript,
ValidationMessage,
Expand All @@ -8,8 +9,9 @@ import {
import {
extractAllItemsFromBehavior,
extractAllItemsFromDialog,
getAllDialogActions,
} from '../data';
import { dialogSchema } from '../schemas';
import { dialogBehaviorSchemas, dialogSchema } from '../schemas';
import { validateSchema } from '../schemas/_helpers';

export function checkMapNPCDialogs(mod: IModKit): ValidationMessageGroup {
Expand Down Expand Up @@ -62,6 +64,32 @@ export function validateDialogs(mod: IModKit): ValidationMessageGroup {
return itemValidations;
}

export function validateDialogActions(mod: IModKit): ValidationMessageGroup {
const itemValidations: ValidationMessageGroup = {
header: 'Invalid NPC Script Actions',
messages: [],
};

mod.dialogs.forEach((item) => {
const allActions = getAllDialogActions(item);

allActions.forEach((action) => {
const failures = validateSchema<any>(
`${item.tag}->${action.type}`,
action,
dialogBehaviorSchemas[action.type as DialogActionType]
);
const validationFailures: ValidationMessage[] = failures.map((f) => ({
type: 'error',
message: f,
}));
itemValidations.messages.push(...validationFailures);
});
});

return itemValidations;
}

export function validateDialogsItems(
mod: IModKit,
validClasses: BaseClassType[]
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum DialogActionType {
CheckAnyHostilesNearby = 'checkAnyHostilesNearby',
DropItems = 'dropItems',
KillSelfSilently = 'killSelfSilently',
GrantAchievement = 'grantAchievement',
}

// dialog items, used for check/take/give
Expand Down

0 comments on commit 56e37f0

Please sign in to comment.