From b7e73dfc99940d0d93de50ce52d340fe04a060e0 Mon Sep 17 00:00:00 2001 From: "Kyle J. Kemp" Date: Sun, 22 Sep 2024 16:19:09 -0500 Subject: [PATCH] add achievement editor, fix other bugs --- src/app/helpers/achievement.ts | 45 ++++ src/app/helpers/exporter.ts | 1 + src/app/helpers/index.ts | 1 + src/app/helpers/schemas/_helpers.ts | 10 + src/app/helpers/schemas/achievement.ts | 38 ++++ src/app/helpers/schemas/index.ts | 1 + src/app/helpers/validate.ts | 2 + src/app/helpers/validators/achievement.ts | 30 +++ src/app/helpers/validators/index.ts | 1 + src/app/home/home.component.html | 4 + src/app/home/home.component.ts | 4 + src/app/home/home.module.ts | 10 +- src/app/services/electron.service.ts | 1 + src/app/services/mod.service.ts | 43 ++-- .../cell-icon/cell-icon.component.html | 7 +- .../cell-icon/cell-icon.component.ts | 28 ++- .../editor-base/editor-base.component.ts | 4 +- .../input-achievementtype.component.html | 6 + .../input-achievementtype.component.scss | 0 .../input-achievementtype.component.ts | 20 ++ src/app/shared/shared.module.ts | 19 +- .../achievements-editor.component.html | 206 ++++++++++++++++++ .../achievements-editor.component.scss | 7 + .../achievements-editor.component.ts | 51 +++++ .../achievements/achievements.component.html | 10 + .../achievements/achievements.component.scss | 0 .../achievements/achievements.component.ts | 103 +++++++++ .../dialogs-editor.component.ts | 7 + src/interfaces/achievement.ts | 42 ++++ src/interfaces/index.ts | 1 + src/interfaces/modkit.ts | 2 + 31 files changed, 666 insertions(+), 38 deletions(-) create mode 100644 src/app/helpers/achievement.ts create mode 100644 src/app/helpers/schemas/achievement.ts create mode 100644 src/app/helpers/validators/achievement.ts create mode 100644 src/app/shared/components/input-achievementtype/input-achievementtype.component.html create mode 100644 src/app/shared/components/input-achievementtype/input-achievementtype.component.scss create mode 100644 src/app/shared/components/input-achievementtype/input-achievementtype.component.ts create mode 100644 src/app/tabs/achievements/achievements-editor/achievements-editor.component.html create mode 100644 src/app/tabs/achievements/achievements-editor/achievements-editor.component.scss create mode 100644 src/app/tabs/achievements/achievements-editor/achievements-editor.component.ts create mode 100644 src/app/tabs/achievements/achievements.component.html create mode 100644 src/app/tabs/achievements/achievements.component.scss create mode 100644 src/app/tabs/achievements/achievements.component.ts create mode 100644 src/interfaces/achievement.ts diff --git a/src/app/helpers/achievement.ts b/src/app/helpers/achievement.ts new file mode 100644 index 0000000..f233716 --- /dev/null +++ b/src/app/helpers/achievement.ts @@ -0,0 +1,45 @@ +import { + BaseClassType, + IAchievement, + IAchievementRequirements, + SkillType, + TradeskillType, +} from '../../interfaces'; +import { id } from './id'; + +export const defaultAchievementRequirements: () => IAchievementRequirements = + () => ({ + bindItem: { + item: '', + }, + kill: { + npc: '', + }, + level: { + baseClass: undefined as unknown as BaseClassType, + level: 0, + }, + skill: { + level: 0, + skill: undefined as unknown as SkillType, + }, + tradeskill: { + level: 0, + tradeskill: undefined as unknown as TradeskillType, + }, + }); + +export const defaultAchievement: () => IAchievement = () => ({ + _id: id(), + ap: 0, + desc: '', + icon: 'uncertainty', + iconColor: '', + iconBgColor: '', + iconBorderColor: '', + activationType: 'other', + hidden: false, + name: '', + requirements: defaultAchievementRequirements(), + shareWithParty: false, +}); diff --git a/src/app/helpers/exporter.ts b/src/app/helpers/exporter.ts index e421bf8..073d298 100644 --- a/src/app/helpers/exporter.ts +++ b/src/app/helpers/exporter.ts @@ -21,6 +21,7 @@ export function formatMod(modData: IModKit): IModKit { cores: modData.cores, stems: modData.stems, traitTrees: modData.traitTrees, + achievements: modData.achievements, }; return exported; diff --git a/src/app/helpers/index.ts b/src/app/helpers/index.ts index 25b9500..4c973ee 100644 --- a/src/app/helpers/index.ts +++ b/src/app/helpers/index.ts @@ -1,3 +1,4 @@ +export * from './achievement'; export * from './constants'; export * from './core'; export * from './dialog'; diff --git a/src/app/helpers/schemas/_helpers.ts b/src/app/helpers/schemas/_helpers.ts index e55180c..1335124 100644 --- a/src/app/helpers/schemas/_helpers.ts +++ b/src/app/helpers/schemas/_helpers.ts @@ -9,6 +9,7 @@ import { } from 'lodash'; import { Allegiance, + BaseClass, DamageClass, HasIdentification, ItemClass, @@ -20,6 +21,7 @@ import { SchemaValidatorMessage, Skill, Stat, + Tradeskill, } from '../../../interfaces'; const itemSlots = [ @@ -132,6 +134,10 @@ export function isSkill(val: any): boolean { return Object.values(Skill).includes(val as Skill); } +export function isTradeskill(val: any): boolean { + return Object.values(Tradeskill).includes(val as Tradeskill); +} + export function isItemSlot(val: any): boolean { return itemSlots.includes(val as ItemSlot); } @@ -239,6 +245,10 @@ export function isRequirement(req: any): boolean { return !!(req.level || req.baseClass || req.alignment || req.quest); } +export function isBaseClass(req: any): boolean { + return Object.values(BaseClass).includes(req as BaseClass); +} + export function isSuccor(suc: any): boolean { return suc.map && isInteger(suc.x) && isInteger(suc.y); } diff --git a/src/app/helpers/schemas/achievement.ts b/src/app/helpers/schemas/achievement.ts new file mode 100644 index 0000000..2cb9099 --- /dev/null +++ b/src/app/helpers/schemas/achievement.ts @@ -0,0 +1,38 @@ +import { isBoolean, isNumber, isObject, isString } from 'lodash'; +import { Schema } from '../../../interfaces'; +import { isBaseClass, isSkill, isTradeskill } from './_helpers'; + +export const achievementSchema: Schema = [ + ['ap', true, isNumber], + ['desc', true, isString], + ['name', true, isString], + ['hidden', false, isBoolean], + ['shareWithParty', false, isBoolean], + + ['activationType', true, isString], + + ['icon', true, isString], + ['iconColor', true, isString], + ['iconBgColor', true, isString], + ['iconBorderColor', true, isString], + + ['requirements', true, isObject], + + ['requirements.bindItem', true, isObject], + ['requirements.bindItem.item', false, isString], + + ['requirements.kill', true, isObject], + ['requirements.kill.npc', false, isString], + + ['requirements.level', true, isObject], + ['requirements.level.baseClass', false, isBaseClass], + ['requirements.level.level', false, isNumber], + + ['requirements.skill', true, isObject], + ['requirements.skill.skill', false, isSkill], + ['requirements.skill.level', false, isNumber], + + ['requirements.tradeskill', true, isObject], + ['requirements.tradeskill.skill', false, isTradeskill], + ['requirements.tradeskill.level', false, isNumber], +]; diff --git a/src/app/helpers/schemas/index.ts b/src/app/helpers/schemas/index.ts index bffab91..455f482 100644 --- a/src/app/helpers/schemas/index.ts +++ b/src/app/helpers/schemas/index.ts @@ -1,3 +1,4 @@ +export * from './achievement'; export * from './dialog'; export * from './droptable'; export * from './item'; diff --git a/src/app/helpers/validate.ts b/src/app/helpers/validate.ts index 548d802..3b44b53 100644 --- a/src/app/helpers/validate.ts +++ b/src/app/helpers/validate.ts @@ -22,6 +22,7 @@ import { nonexistentItems, nonexistentNPCs, nonexistentRecipes, + validateAchievements, validateDialogs, validateDialogsItems, validateDroptables, @@ -67,6 +68,7 @@ export function validationMessagesForMod( validateSTEMs(mod), validateSTEMProperties(mod), validateRNGDungeons(mod, classes), + validateAchievements(mod), nonexistentItems(mod), nonexistentNPCs(mod), nonexistentRecipes(mod), diff --git a/src/app/helpers/validators/achievement.ts b/src/app/helpers/validators/achievement.ts new file mode 100644 index 0000000..d27b63c --- /dev/null +++ b/src/app/helpers/validators/achievement.ts @@ -0,0 +1,30 @@ +import { + IAchievement, + IModKit, + ValidationMessage, + ValidationMessageGroup, +} from '../../../interfaces'; +import { achievementSchema } from '../schemas'; +import { validateSchema } from '../schemas/_helpers'; + +export function validateAchievements(mod: IModKit): ValidationMessageGroup { + const itemValidations: ValidationMessageGroup = { + header: 'Invalid Achievements', + messages: [], + }; + + mod.achievements.forEach((item) => { + const failures = validateSchema( + item.name, + item, + achievementSchema + ); + const validationFailures: ValidationMessage[] = failures.map((f) => ({ + type: 'error', + message: f, + })); + itemValidations.messages.push(...validationFailures); + }); + + return itemValidations; +} diff --git a/src/app/helpers/validators/index.ts b/src/app/helpers/validators/index.ts index 9c72d96..aa7f489 100644 --- a/src/app/helpers/validators/index.ts +++ b/src/app/helpers/validators/index.ts @@ -1,3 +1,4 @@ +export * from './achievement'; export * from './autogenerated'; export * from './dialog'; export * from './droptable'; diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 467e3e0..a4bbb27 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -190,6 +190,10 @@ } + @case (11) { + + } + } diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index dfad242..6e24ed5 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -102,6 +102,10 @@ export class HomeComponent implements OnInit { name: 'Trait Trees', count: computed(() => this.modService.mod().traitTrees.length), }, + { + name: 'Achievements', + count: computed(() => this.modService.mod().achievements.length), + }, ]; constructor() {} diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts index 312464b..fe4b490 100644 --- a/src/app/home/home.module.ts +++ b/src/app/home/home.module.ts @@ -12,7 +12,10 @@ import { NgxFloatUiModule } from 'ngx-float-ui'; import { AnalysisComponent } from '../analysis/analysis.component'; import { PinpointComponent } from '../pinpoint/pinpoint.component'; +import { QueryComponent } from '../query/query.component'; import { SharedModule } from '../shared/shared.module'; +import { AchievementsEditorComponent } from '../tabs/achievements/achievements-editor/achievements-editor.component'; +import { AchievementsComponent } from '../tabs/achievements/achievements.component'; import { CoresEditorComponent } from '../tabs/cores/cores-editor/cores-editor.component'; import { CoresComponent } from '../tabs/cores/cores.component'; import { DialogsEditorComponent } from '../tabs/dialogs/dialogs-editor/dialogs-editor.component'; @@ -36,7 +39,6 @@ import { TraitTreesEditorComponent } from '../tabs/trait-trees/trait-trees-edito import { TraitTreesComponent } from '../tabs/trait-trees/trait-trees.component'; import { ValidationComponent } from '../validation/validation.component'; import { HomeComponent } from './home.component'; -import { QueryComponent } from '../query/query.component'; @NgModule({ declarations: [ @@ -61,6 +63,8 @@ import { QueryComponent } from '../query/query.component'; CoresEditorComponent, TraitTreesComponent, TraitTreesEditorComponent, + AchievementsComponent, + AchievementsEditorComponent, StemsComponent, StemsEditorComponent, PinpointComponent, @@ -78,8 +82,6 @@ import { QueryComponent } from '../query/query.component'; CodeEditorModule, ColorPickerModule, ], - exports: [ - QueryComponent - ], + exports: [QueryComponent], }) export class HomeModule {} diff --git a/src/app/services/electron.service.ts b/src/app/services/electron.service.ts index 7a55fc3..01b6826 100644 --- a/src/app/services/electron.service.ts +++ b/src/app/services/electron.service.ts @@ -145,6 +145,7 @@ export class ElectronService { // the mod has no backup, which means it was a clean export. it might need some reformatting to get it back in window.api.receive('loadmod', (mod: IModKit) => { const importedMod = importMod(mod); + this.modService.migrateMod(importedMod); this.modService.updateMod(importedMod); tryEnsureMaps(); diff --git a/src/app/services/mod.service.ts b/src/app/services/mod.service.ts index 0346fb5..2e97063 100644 --- a/src/app/services/mod.service.ts +++ b/src/app/services/mod.service.ts @@ -45,6 +45,7 @@ export function defaultModKit(): IModKit { cores: [], stems: [], traitTrees: [], + achievements: [], }; } @@ -97,7 +98,7 @@ export class ModService { } // mod functions - private migrateMod(mod: IModKit) { + public migrateMod(mod: IModKit) { const check = defaultModKit(); Object.keys(check).forEach((checkKeyString) => { const checkKey = checkKeyString as keyof IModKit; @@ -153,7 +154,7 @@ export class ModService { const mod = this.mod(); const arr = mod[key] as unknown as T[]; - console.log(`[ENTRY:NEW]`, data); + console.info(`[ENTRY:NEW]`, data); arr.push(data); this.updateMod(mod); @@ -167,7 +168,7 @@ export class ModService { const mod = this.mod(); const arr = mod[key] as unknown as T[]; - console.log(`[ENTRY:EDIT]`, oldData, newData); + console.info(`[ENTRY:EDIT]`, oldData, newData); const foundItemIdx = arr.findIndex((i) => i._id === oldData._id); if (foundItemIdx === -1) return; @@ -184,7 +185,7 @@ export class ModService { const mod = this.mod(); const arr = mod[key] as unknown as T[]; - console.log(`[ENTRY:DELETE]`, data); + console.info(`[ENTRY:DELETE]`, data); (mod[key] as unknown as T[]) = arr.filter((i) => i._id !== data._id); @@ -268,7 +269,7 @@ export class ModService { mod.drops.forEach((droptable) => { if (droptable.mapName !== oldName) return; - console.log( + console.info( `[Propagate Map] Updated droptable "${droptable.mapName}" Map: ${oldName} -> ${newName}` ); droptable.mapName = newName; @@ -297,7 +298,7 @@ export class ModService { spawner.npcIds.forEach((rollable) => { if (rollable.result !== oldName) return; - console.log( + console.info( `[Propagate NPC] Updated spawner "${spawner.tag}" NPC: ${oldName} -> ${newName}` ); rollable.result = newName; @@ -308,7 +309,7 @@ export class ModService { quest.requirements.npcIds?.forEach((id, i) => { if (id !== oldName) return; - console.log( + console.info( `[Propagate NPC] Updated quest "${quest.name}" NPC: ${oldName} -> ${newName}` ); quest.requirements.npcIds[i] = newName; @@ -327,7 +328,7 @@ export class ModService { mod.items.forEach((item) => { if (item.requirements?.quest !== oldName) return; - console.log( + console.info( `[Propagate Quest] Updated requirements.quest for "${item.name}" item: ${oldName} -> ${newName}` ); item.requirements.quest = newName; @@ -349,7 +350,7 @@ export class ModService { contained.forEach((rollable) => { if (rollable.result !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated containedItems for "${item.name}" item: ${oldName} -> ${newName}` ); rollable.result = newName; @@ -360,7 +361,7 @@ export class ModService { droptable.drops.forEach((drop) => { if (drop.result !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated droptable for "${ droptable.mapName || droptable.regionName || @@ -375,7 +376,7 @@ export class ModService { mod.quests.forEach((quest) => { if (quest.requirements.item !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated quest "${quest.name}" item: ${oldName} -> ${newName}` ); @@ -387,7 +388,7 @@ export class ModService { const itemSlot = slot as ItemSlotType; if (npcScript.items.equipment[itemSlot] !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated NPC Script "${npcScript.name}" item#${itemSlot}: ${oldName} -> ${newName}` ); npcScript.items.equipment[itemSlot] = newName; @@ -398,7 +399,7 @@ export class ModService { npc.items?.sack?.forEach((item) => { if (item.result !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated NPC "${npc.npcId}" item in sack: ${oldName} -> ${newName}` ); @@ -411,7 +412,7 @@ export class ModService { npc.items?.equipment?.[itemSlot].forEach((rollable) => { if (rollable.result !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated NPC "${npc.npcId}" item#${itemSlot}: ${oldName} -> ${newName}` ); @@ -422,7 +423,7 @@ export class ModService { npc.drops?.forEach((rollable) => { if (rollable.result !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated NPC "${npc.npcId}" drop: ${oldName} -> ${newName}` ); @@ -432,7 +433,7 @@ export class ModService { npc.dropPool?.items?.forEach((rollable) => { if (rollable.result !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated NPC "${npc.npcId}" dropPool: ${oldName} -> ${newName}` ); @@ -440,7 +441,7 @@ export class ModService { }); if (npc.tansFor === oldName) { - console.log( + console.info( `[Propagate Item] Updated NPC "${npc.npcId}" tans for item: ${oldName} -> ${newName}` ); @@ -450,7 +451,7 @@ export class ModService { mod.recipes.forEach((recipe) => { if (recipe.item === oldName) { - console.log( + console.info( `[Propagate Item] Updated recipe "${recipe.name}" result item: ${oldName} -> ${newName}` ); @@ -458,7 +459,7 @@ export class ModService { } if (recipe.transferOwnerFrom === oldName) { - console.log( + console.info( `[Propagate Item] Updated recipe "${recipe.name}" transfer owner item: ${oldName} -> ${newName}` ); @@ -469,7 +470,7 @@ export class ModService { ingredients?.forEach((ing, i) => { if (ing !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated recipe "${recipe.name}" ingredient#${i}: ${oldName} -> ${newName}` ); @@ -478,7 +479,7 @@ export class ModService { recipe.ozIngredients?.forEach((ing) => { if (ing.display !== oldName) return; - console.log( + console.info( `[Propagate Item] Updated recipe "${recipe.name}" ozIngredient: ${oldName} -> ${newName}` ); diff --git a/src/app/shared/components/cell-icon/cell-icon.component.html b/src/app/shared/components/cell-icon/cell-icon.component.html index 95a4d0c..76bce76 100644 --- a/src/app/shared/components/cell-icon/cell-icon.component.html +++ b/src/app/shared/components/cell-icon/cell-icon.component.html @@ -1,2 +1,5 @@ - \ No newline at end of file +@let icon = iconData(); + + \ No newline at end of file diff --git a/src/app/shared/components/cell-icon/cell-icon.component.ts b/src/app/shared/components/cell-icon/cell-icon.component.ts index 6fb2d75..d4f411c 100644 --- a/src/app/shared/components/cell-icon/cell-icon.component.ts +++ b/src/app/shared/components/cell-icon/cell-icon.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, computed } from '@angular/core'; import { ICellRendererAngularComp } from 'ag-grid-angular'; import { ICellRendererParams } from 'ag-grid-community'; @@ -8,6 +8,32 @@ import { ICellRendererParams } from 'ag-grid-community'; styleUrl: './cell-icon.component.scss', }) export class CellIconComponent implements ICellRendererAngularComp { + public iconData = computed(() => { + switch (this.params.type) { + case 'stemIcon': { + return { + icon: this.params.data.all.icon, + color: this.params.data.all.color, + bgColor: this.params.data.all.bgColor, + borderColor: this.params.data.trait?.borderColor, + }; + } + + case 'achievementIcon': { + return { + icon: this.params.data.icon, + color: this.params.data.iconColor, + bgColor: this.params.data.iconBgColor, + borderColor: this.params.data.iconBorderColor, + }; + } + } + + return { + icon: 'uncertainty', + }; + }); + public params!: any; agInit(params: ICellRendererParams) { diff --git a/src/app/shared/components/editor-base/editor-base.component.ts b/src/app/shared/components/editor-base/editor-base.component.ts index 0ced99d..e8b37cd 100644 --- a/src/app/shared/components/editor-base/editor-base.component.ts +++ b/src/app/shared/components/editor-base/editor-base.component.ts @@ -62,7 +62,7 @@ export class EditorBaseComponent editing._id = id(); } - console.log(`[EDIT BEGIN]`, editing); + console.info(`[EDIT BEGIN]`, editing); this.editing.set(editing); } @@ -79,7 +79,7 @@ export class EditorBaseComponent } doSave() { - console.log('[SAVE DATA]', this.editing()); + console.info('[SAVE DATA]', this.editing()); this.save.emit(this.editing()); } } diff --git a/src/app/shared/components/input-achievementtype/input-achievementtype.component.html b/src/app/shared/components/input-achievementtype/input-achievementtype.component.html new file mode 100644 index 0000000..9b9049e --- /dev/null +++ b/src/app/shared/components/input-achievementtype/input-achievementtype.component.html @@ -0,0 +1,6 @@ +
+ + + Achievement Type +
\ No newline at end of file diff --git a/src/app/shared/components/input-achievementtype/input-achievementtype.component.scss b/src/app/shared/components/input-achievementtype/input-achievementtype.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/input-achievementtype/input-achievementtype.component.ts b/src/app/shared/components/input-achievementtype/input-achievementtype.component.ts new file mode 100644 index 0000000..c6ec350 --- /dev/null +++ b/src/app/shared/components/input-achievementtype/input-achievementtype.component.ts @@ -0,0 +1,20 @@ +import { Component, computed, model, output } from '@angular/core'; + +@Component({ + selector: 'app-input-achievementtype', + templateUrl: './input-achievementtype.component.html', + styleUrl: './input-achievementtype.component.scss', +}) +export class InputAchievementTypeComponent { + public achievementType = model.required(); + public change = output(); + + public values = computed(() => [ + 'other', + 'bindItem', + 'kill', + 'level', + 'skill', + 'tradeskill', + ]); +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 09be7c5..0052a9c 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -12,22 +12,28 @@ import { ColorPickerModule } from 'ngx-color-picker'; import { NgxFloatUiModule } from 'ngx-float-ui'; import { CellButtonsComponent } from './components/cell-buttons/cell-buttons.component'; +import { CellIconComponent } from './components/cell-icon/cell-icon.component'; import { CellSpriteComponent } from './components/cell-sprite/cell-sprite.component'; import { DebugViewComponent } from './components/debug-view/debug-view.component'; import { EditBaseeffectComponent } from './components/edit-baseeffect/edit-baseeffect.component'; +import { EditStatobjectComponent } from './components/edit-statobject/edit-statobject.component'; import { EditorBaseTableComponent } from './components/editor-base-table/editor-base-table.component'; import { EditorBaseComponent } from './components/editor-base/editor-base.component'; import { EditorViewTableComponent } from './components/editor-view-table/editor-view-table.component'; import { HeaderButtonsComponent } from './components/header-buttons/header-buttons.component'; import { IconComponent } from './components/icon/icon.component'; +import { InputAchievementTypeComponent } from './components/input-achievementtype/input-achievementtype.component'; import { InputAlignmentComponent } from './components/input-alignment/input-alignment.component'; import { InputAllegianceComponent } from './components/input-allegiance/input-allegiance.component'; +import { InputAnalysisReportComponent } from './components/input-analysis-report/input-analysis-report.component'; +import { InputBufftypeComponent } from './components/input-bufftype/input-bufftype.component'; import { InputCategoryComponent } from './components/input-category/input-category.component'; import { InputChallengeratingComponent } from './components/input-challengerating/input-challengerating.component'; import { InputClassComponent } from './components/input-class/input-class.component'; import { InputDamageclassComponent } from './components/input-damageclass/input-damageclass.component'; import { InputEffectComponent } from './components/input-effect/input-effect.component'; import { InputFloatingLabelComponent } from './components/input-floating-label/input-floating-label.component'; +import { InputGameeventComponent } from './components/input-gameevent/input-gameevent.component'; import { InputHolidayComponent } from './components/input-holiday/input-holiday.component'; import { InputHostilityComponent } from './components/input-hostility/input-hostility.component'; import { InputIconComponent } from './components/input-icon/input-icon.component'; @@ -35,6 +41,7 @@ import { InputItemComponent } from './components/input-item/input-item.component import { InputItemclassComponent } from './components/input-itemclass/input-itemclass.component'; import { InputItemslotEncrustComponent } from './components/input-itemslot-encrust/input-itemslot-encrust.component'; import { InputItemslotComponent } from './components/input-itemslot/input-itemslot.component'; +import { InputMacrotypeComponent } from './components/input-macrotype/input-macrotype.component'; import { InputMapComponent } from './components/input-map/input-map.component'; import { InputMapnpcComponent } from './components/input-mapnpc/input-mapnpc.component'; import { InputNpcComponent } from './components/input-npc/input-npc.component'; @@ -45,9 +52,11 @@ import { InputRecipeComponent } from './components/input-recipe/input-recipe.com import { InputRegionComponent } from './components/input-region/input-region.component'; import { InputSfxComponent } from './components/input-sfx/input-sfx.component'; import { InputSkillComponent } from './components/input-skill/input-skill.component'; +import { InputSpawnerComponent } from './components/input-spawner/input-spawner.component'; import { InputSpellComponent } from './components/input-spell/input-spell.component'; import { InputSpriteComponent } from './components/input-sprite/input-sprite.component'; import { InputStatComponent } from './components/input-stat/input-stat.component'; +import { InputStemComponent } from './components/input-stem/input-stem.component'; import { InputTradeskillComponent } from './components/input-tradeskill/input-tradeskill.component'; import { InputTraitComponent } from './components/input-trait/input-trait.component'; import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; @@ -55,14 +64,6 @@ import { SpriteWithInlineNameComponent } from './components/sprite-with-inline-n import { SpriteComponent } from './components/sprite/sprite.component'; import { TestViewComponent } from './components/test-view/test-view.component'; import { WebviewDirective } from './directives/'; -import { CellIconComponent } from './components/cell-icon/cell-icon.component'; -import { InputMacrotypeComponent } from './components/input-macrotype/input-macrotype.component'; -import { InputBufftypeComponent } from './components/input-bufftype/input-bufftype.component'; -import { EditStatobjectComponent } from './components/edit-statobject/edit-statobject.component'; -import { InputGameeventComponent } from './components/input-gameevent/input-gameevent.component'; -import { InputSpawnerComponent } from './components/input-spawner/input-spawner.component'; -import { InputAnalysisReportComponent } from './components/input-analysis-report/input-analysis-report.component'; -import { InputStemComponent } from './components/input-stem/input-stem.component'; @NgModule({ declarations: [ @@ -118,6 +119,7 @@ import { InputStemComponent } from './components/input-stem/input-stem.component InputSpawnerComponent, InputAnalysisReportComponent, InputStemComponent, + InputAchievementTypeComponent, ], imports: [ CommonModule, @@ -184,6 +186,7 @@ import { InputStemComponent } from './components/input-stem/input-stem.component InputSpawnerComponent, InputAnalysisReportComponent, InputStemComponent, + InputAchievementTypeComponent, ], }) export class SharedModule {} diff --git a/src/app/tabs/achievements/achievements-editor/achievements-editor.component.html b/src/app/tabs/achievements/achievements-editor/achievements-editor.component.html new file mode 100644 index 0000000..6980d8a --- /dev/null +++ b/src/app/tabs/achievements/achievements-editor/achievements-editor.component.html @@ -0,0 +1,206 @@ +@let editingData = editing(); + +
+ +
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ Icon Color + +
+
+ +
+
+ +
+
+
+ +
+
+
+ Icon Background Color + +
+
+ +
+
+ +
+
+
+ +
+
+
+ Icon Border Color + +
+
+ +
+
+ +
+
+
+
+ +
+
+ Name + +
+ + +
+ Description + +
+ +
+
+
+ Achievement Points + +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+ +
+ +
+ + @switch(editingData.activationType) { + @case ('level') { +
+
+
+ +
+
+ +
+
+ Level + +
+
+
+ } + + @case ('skill') { +
+
+
+ +
+
+ +
+
+ Level + +
+
+
+ } + + @case ('tradeskill') { +
+
+
+ +
+
+ +
+
+ Level + +
+
+
+ } + + @case ('kill') { +
+ +
+ } + + @case ('bindItem') { +
+ +
+ } + } + +
+
+ + + {{ editingData | json }} + \ No newline at end of file diff --git a/src/app/tabs/achievements/achievements-editor/achievements-editor.component.scss b/src/app/tabs/achievements/achievements-editor/achievements-editor.component.scss new file mode 100644 index 0000000..bbffa1d --- /dev/null +++ b/src/app/tabs/achievements/achievements-editor/achievements-editor.component.scss @@ -0,0 +1,7 @@ +.editor-container { + height: 60vh; +} + +.form-error { + min-height: 48px; +} diff --git a/src/app/tabs/achievements/achievements-editor/achievements-editor.component.ts b/src/app/tabs/achievements/achievements-editor/achievements-editor.component.ts new file mode 100644 index 0000000..71ebaa6 --- /dev/null +++ b/src/app/tabs/achievements/achievements-editor/achievements-editor.component.ts @@ -0,0 +1,51 @@ +import { Component, computed, OnInit, signal } from '@angular/core'; + +import { IAchievement } from '../../../../interfaces'; +import { defaultAchievementRequirements } from '../../../helpers'; +import { EditorBaseComponent } from '../../../shared/components/editor-base/editor-base.component'; + +@Component({ + selector: 'app-achievements-editor', + templateUrl: './achievements-editor.component.html', + styleUrl: './achievements-editor.component.scss', +}) +export class AchievementsEditorComponent + extends EditorBaseComponent + implements OnInit +{ + public currentItem = signal(undefined); + + public canSave = computed(() => { + const data = this.editing(); + return data.name && data.desc && data.ap > 0; + }); + + public satisfiesUnique = computed(() => { + const data = this.editing(); + return !this.modService.doesExistDuplicate( + 'achievements', + 'name', + data.name, + data._id + ); + }); + + ngOnInit(): void { + super.ngOnInit(); + } + + doSave() { + const core = this.editing(); + + this.editing.set(core); + + super.doSave(); + } + + resetAchievementType() { + this.editing.update((editing) => { + editing.requirements = defaultAchievementRequirements(); + return structuredClone(editing); + }); + } +} diff --git a/src/app/tabs/achievements/achievements.component.html b/src/app/tabs/achievements/achievements.component.html new file mode 100644 index 0000000..80c1377 --- /dev/null +++ b/src/app/tabs/achievements/achievements.component.html @@ -0,0 +1,10 @@ +@let editing = isEditing(); + + + +@if(editing) { + +} \ No newline at end of file diff --git a/src/app/tabs/achievements/achievements.component.scss b/src/app/tabs/achievements/achievements.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/tabs/achievements/achievements.component.ts b/src/app/tabs/achievements/achievements.component.ts new file mode 100644 index 0000000..5ccd4a1 --- /dev/null +++ b/src/app/tabs/achievements/achievements.component.ts @@ -0,0 +1,103 @@ +import { Component, computed } from '@angular/core'; +import { ColDef } from 'ag-grid-community'; + +import { IAchievement, IModKit } from '../../../interfaces'; +import { defaultAchievement, id } from '../../helpers'; +import { CellButtonsComponent } from '../../shared/components/cell-buttons/cell-buttons.component'; +import { CellIconComponent } from '../../shared/components/cell-icon/cell-icon.component'; +import { EditorBaseTableComponent } from '../../shared/components/editor-base-table/editor-base-table.component'; +import { HeaderButtonsComponent } from '../../shared/components/header-buttons/header-buttons.component'; + +type EditingType = IAchievement; + +@Component({ + selector: 'app-achievements', + templateUrl: './achievements.component.html', + styleUrl: './achievements.component.scss', +}) +export class AchievementsComponent extends EditorBaseTableComponent { + protected dataKey: keyof Omit = 'achievements'; + + public defaultData = defaultAchievement; + + public tableItems = computed(() => this.modService.mod().achievements); + public tableColumns: ColDef[] = [ + { + field: 'icon', + headerName: '', + resizable: false, + sortable: false, + width: 100, + cellRenderer: CellIconComponent, + cellRendererParams: { type: 'achievementIcon' }, + }, + { + field: 'name', + flex: 2, + cellDataType: 'text', + filter: 'agTextColumnFilter', + sort: 'asc', + }, + { + field: 'desc', + headerName: 'Description', + flex: 5, + cellDataType: 'text', + filter: 'agTextColumnFilter', + cellClass: 'leading-4 whitespace-break-spaces', + sortable: false, + }, + { + field: 'ap', + headerName: 'AP', + flex: 1, + cellDataType: 'number', + filter: 'agNumberColumnFilter', + }, + { + field: 'activationType', + headerName: 'Type', + flex: 1, + cellDataType: 'text', + filter: 'agTextColumnFilter', + }, + { + field: 'shareWithParty', + headerName: 'Party?', + flex: 1, + cellDataType: 'boolean', + }, + { + field: 'hidden', + headerName: 'Hidden?', + flex: 1, + cellDataType: 'boolean', + }, + { + field: '', + width: 200, + sortable: false, + suppressMovable: true, + headerComponent: HeaderButtonsComponent, + headerComponentParams: { + showNewButton: true, + newCallback: () => this.createNew(), + }, + cellRenderer: CellButtonsComponent, + cellClass: 'no-adjust', + cellRendererParams: { + showCopyButton: true, + copyCallback: (item: EditingType) => { + const newItem = structuredClone(item); + newItem.name = `${newItem.name} (copy)`; + newItem._id = id(); + this.saveNewData(newItem); + }, + showEditButton: true, + editCallback: (item: EditingType) => this.editExisting(item), + showDeleteButton: true, + deleteCallback: (item: EditingType) => this.deleteData(item), + }, + }, + ]; +} diff --git a/src/app/tabs/dialogs/dialogs-editor/dialogs-editor.component.ts b/src/app/tabs/dialogs/dialogs-editor/dialogs-editor.component.ts index c25d64e..c0dea7b 100644 --- a/src/app/tabs/dialogs/dialogs-editor/dialogs-editor.component.ts +++ b/src/app/tabs/dialogs/dialogs-editor/dialogs-editor.component.ts @@ -93,7 +93,14 @@ export class DialogsEditorComponent this.dialogModel.value = this.dialogText(); } + npc.items ??= { + equipment: {} as any, + }; + + npc.items.equipment ??= {} as any; + npc.baseEffects ??= []; + npc.baseEffects.forEach((eff) => (eff.extra ??= { potency: 0 })); npc.hp ??= { min: 1, max: 1 }; npc.mp ??= { min: 1, max: 1 }; diff --git a/src/interfaces/achievement.ts b/src/interfaces/achievement.ts new file mode 100644 index 0000000..5376328 --- /dev/null +++ b/src/interfaces/achievement.ts @@ -0,0 +1,42 @@ +import { BaseClassType, SkillType, TradeskillType } from './building-blocks'; +import { HasIdentification } from './identified'; + +export interface IAchievementRequirements { + level: { + baseClass: BaseClassType; + level: number; + }; + + skill: { + skill: SkillType; + level: number; + }; + + tradeskill: { + tradeskill: TradeskillType; + level: number; + }; + + kill: { + npc: string; + }; + + bindItem: { + item: string; + }; +} + +export interface IAchievement extends HasIdentification { + name: string; + icon: string; + iconColor: string; + iconBgColor: string; + iconBorderColor: string; + activationType: keyof IAchievementRequirements | 'other'; + desc: string; + ap: number; + shareWithParty: boolean; + hidden: boolean; + + requirements: IAchievementRequirements; +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 8193083..0d7979d 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,3 +1,4 @@ +export * from './achievement'; export * from './analysis'; export * from './behavior'; export * from './building-blocks'; diff --git a/src/interfaces/modkit.ts b/src/interfaces/modkit.ts index 49d4c9c..7d4dd7e 100644 --- a/src/interfaces/modkit.ts +++ b/src/interfaces/modkit.ts @@ -1,3 +1,4 @@ +import { IAchievement } from './achievement'; import { ICoreContent } from './core'; import { IDroptable } from './droptable'; import { IItemDefinition } from './item'; @@ -34,4 +35,5 @@ export interface IModKit { cores: ICoreContent[]; stems: ISTEM[]; traitTrees: ITraitTree[]; + achievements: IAchievement[]; }