diff --git a/src/app/helpers/event.ts b/src/app/helpers/event.ts
new file mode 100644
index 0000000..44f9e74
--- /dev/null
+++ b/src/app/helpers/event.ts
@@ -0,0 +1,31 @@
+import {
+ DynamicEventRarity,
+ DynamicEventSuccessTypeType,
+ IDynamicEventMeta,
+} from '../../interfaces';
+import { id } from './id';
+
+export const defaultEvent: () => IDynamicEventMeta = () => ({
+ _id: id(),
+ cooldown: 0,
+ description: '',
+ duration: 0,
+ endMessage: '',
+ name: '',
+ rarity: DynamicEventRarity.Common,
+ startMessage: '',
+ conflicts: [],
+ extraData: {},
+ map: undefined as unknown as string,
+ npc: undefined as unknown as string,
+
+ requiresPreviousEvent: false,
+ spawnEventOnFailure: undefined as unknown as string,
+ spawnEventOnSuccess: undefined as unknown as string,
+
+ successMetrics: {
+ count: 0,
+ killNPCs: [],
+ type: undefined as unknown as DynamicEventSuccessTypeType,
+ },
+});
diff --git a/src/app/helpers/exporter.ts b/src/app/helpers/exporter.ts
index 073d298..ef9d75d 100644
--- a/src/app/helpers/exporter.ts
+++ b/src/app/helpers/exporter.ts
@@ -22,6 +22,7 @@ export function formatMod(modData: IModKit): IModKit {
stems: modData.stems,
traitTrees: modData.traitTrees,
achievements: modData.achievements,
+ events: modData.events,
};
return exported;
diff --git a/src/app/helpers/index.ts b/src/app/helpers/index.ts
index 4c973ee..d586ec8 100644
--- a/src/app/helpers/index.ts
+++ b/src/app/helpers/index.ts
@@ -3,6 +3,7 @@ export * from './constants';
export * from './core';
export * from './dialog';
export * from './droptable';
+export * from './event';
export * from './exporter';
export * from './id';
export * from './importer';
diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html
index acd45f4..1de1882 100644
--- a/src/app/home/home.component.html
+++ b/src/app/home/home.component.html
@@ -219,6 +219,10 @@
}
+ @case (12) {
+
+ }
+
}
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts
index 07b436d..8610c97 100644
--- a/src/app/home/home.component.ts
+++ b/src/app/home/home.component.ts
@@ -108,6 +108,10 @@ export class HomeComponent implements OnInit {
name: 'Achievements',
count: computed(() => this.modService.mod().achievements.length),
},
+ {
+ name: 'Events',
+ count: computed(() => this.modService.mod().events.length),
+ },
];
constructor() {}
diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts
index bde62cc..37350fd 100644
--- a/src/app/home/home.module.ts
+++ b/src/app/home/home.module.ts
@@ -15,6 +15,8 @@ import { DependenciesComponent } from '../dependencies/dependencies.component';
import { PinpointComponent } from '../pinpoint/pinpoint.component';
import { QueryComponent } from '../query/query.component';
import { SharedModule } from '../shared/shared.module';
+import { EventsEditorComponent } from '../tabs/achievements copy/events-editor/events-editor.component';
+import { EventsComponent } from '../tabs/achievements copy/events.component';
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';
@@ -72,6 +74,8 @@ import { HomeComponent } from './home.component';
AnalysisComponent,
QueryComponent,
DependenciesComponent,
+ EventsComponent,
+ EventsEditorComponent,
],
imports: [
CommonModule,
diff --git a/src/app/services/mod.service.ts b/src/app/services/mod.service.ts
index d538ef4..706167b 100644
--- a/src/app/services/mod.service.ts
+++ b/src/app/services/mod.service.ts
@@ -48,6 +48,7 @@ export function defaultModKit(): IModKit {
stems: [],
traitTrees: [],
achievements: [],
+ events: [],
};
}
diff --git a/src/app/shared/components/input-event/input-event.component.html b/src/app/shared/components/input-event/input-event.component.html
new file mode 100644
index 0000000..dec8c58
--- /dev/null
+++ b/src/app/shared/components/input-event/input-event.component.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+ {{ item.value }}
+
+
+
{{ item.desc }}
+
+
+
+
+
{{ label() }}
+
\ No newline at end of file
diff --git a/src/app/shared/components/input-gameevent/input-gameevent.component.scss b/src/app/shared/components/input-event/input-event.component.scss
similarity index 100%
rename from src/app/shared/components/input-gameevent/input-gameevent.component.scss
rename to src/app/shared/components/input-event/input-event.component.scss
diff --git a/src/app/shared/components/input-event/input-event.component.ts b/src/app/shared/components/input-event/input-event.component.ts
new file mode 100644
index 0000000..11b55c5
--- /dev/null
+++ b/src/app/shared/components/input-event/input-event.component.ts
@@ -0,0 +1,72 @@
+import {
+ Component,
+ computed,
+ inject,
+ input,
+ model,
+ OnInit,
+ output,
+} from '@angular/core';
+import { sortBy } from 'lodash';
+import { IDynamicEventMeta } from '../../../../interfaces';
+import { ModService } from '../../../services/mod.service';
+
+@Component({
+ selector: 'app-input-event',
+ templateUrl: './input-event.component.html',
+ styleUrl: './input-event.component.scss',
+})
+export class InputEventComponent implements OnInit {
+ private modService = inject(ModService);
+
+ public event = model();
+ public defaultValue = input();
+ public label = input('Event');
+ public change = output();
+
+ public values = computed(() => {
+ const mod = this.modService.mod();
+ const activeDependencies = this.modService.activeDependencies();
+
+ const myModEvents = mod.events.map((i) => ({
+ category: `${mod.meta.name} (Current)`,
+ data: i,
+ value: i.name,
+ desc: i.description,
+ index: 0,
+ }));
+
+ const depEvents = activeDependencies
+ .map((dep, idx) =>
+ dep.events.map((i) => ({
+ category: dep.meta.name,
+ data: i,
+ value: i.name,
+ desc: i.description,
+ index: idx + 1,
+ }))
+ )
+ .flat();
+
+ return [...sortBy([...myModEvents, ...depEvents], ['index', 'value'])];
+ });
+
+ ngOnInit() {
+ const defaultItem = this.defaultValue();
+ if (defaultItem) {
+ const foundItem = this.values().find((i) => i.value === defaultItem);
+ this.event.set(foundItem as unknown as IDynamicEventMeta);
+ }
+ }
+
+ public itemCompare(
+ itemA: { value: string; desc: string },
+ itemB: { value: string; desc: string }
+ ): boolean {
+ return itemA.value === itemB.value;
+ }
+
+ public search(term: string, item: { value: string }) {
+ return item.value.toLowerCase().includes(term.toLowerCase());
+ }
+}
diff --git a/src/app/shared/components/input-eventrarity/input-eventrarity.component.html b/src/app/shared/components/input-eventrarity/input-eventrarity.component.html
new file mode 100644
index 0000000..56344cc
--- /dev/null
+++ b/src/app/shared/components/input-eventrarity/input-eventrarity.component.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/src/app/shared/components/input-eventrarity/input-eventrarity.component.scss b/src/app/shared/components/input-eventrarity/input-eventrarity.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/components/input-eventrarity/input-eventrarity.component.ts b/src/app/shared/components/input-eventrarity/input-eventrarity.component.ts
new file mode 100644
index 0000000..f5898e2
--- /dev/null
+++ b/src/app/shared/components/input-eventrarity/input-eventrarity.component.ts
@@ -0,0 +1,17 @@
+import { Component, model, output } from '@angular/core';
+import {
+ DynamicEventRarity,
+ DynamicEventRarityType,
+} from '../../../../interfaces';
+
+@Component({
+ selector: 'app-input-eventrarity',
+ templateUrl: './input-eventrarity.component.html',
+ styleUrl: './input-eventrarity.component.scss',
+})
+export class InputEventRarityComponent {
+ public rarity = model.required();
+ public change = output();
+
+ public values = [...Object.values(DynamicEventRarity).sort()];
+}
diff --git a/src/app/shared/components/input-eventsuccesstype/input-eventsuccesstype.component.html b/src/app/shared/components/input-eventsuccesstype/input-eventsuccesstype.component.html
new file mode 100644
index 0000000..f3ea423
--- /dev/null
+++ b/src/app/shared/components/input-eventsuccesstype/input-eventsuccesstype.component.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/src/app/shared/components/input-eventsuccesstype/input-eventsuccesstype.component.scss b/src/app/shared/components/input-eventsuccesstype/input-eventsuccesstype.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/components/input-eventsuccesstype/input-eventsuccesstype.component.ts b/src/app/shared/components/input-eventsuccesstype/input-eventsuccesstype.component.ts
new file mode 100644
index 0000000..a668f83
--- /dev/null
+++ b/src/app/shared/components/input-eventsuccesstype/input-eventsuccesstype.component.ts
@@ -0,0 +1,15 @@
+import { Component, input, model, output } from '@angular/core';
+import { DynamicEventSuccessType } from '../../../../interfaces';
+
+@Component({
+ selector: 'app-input-eventsuccesstype',
+ templateUrl: './input-eventsuccesstype.component.html',
+ styleUrl: './input-eventsuccesstype.component.scss',
+})
+export class InputEventSuccessTypeComponent {
+ public successType = model.required();
+ public label = input('Success Type');
+ public change = output();
+
+ public values = [...Object.values(DynamicEventSuccessType)];
+}
diff --git a/src/app/shared/components/input-gameevent/input-gameevent.component.html b/src/app/shared/components/input-gameevent/input-gameevent.component.html
deleted file mode 100644
index fd05ac8..0000000
--- a/src/app/shared/components/input-gameevent/input-gameevent.component.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
- {{ item.value }}
-
-
-
-
-
-
- {{ item.value }}
-
-
-
{{ item.desc }}
-
-
-
-
-
{{ label() }}
-
\ No newline at end of file
diff --git a/src/app/shared/components/input-gameevent/input-gameevent.component.ts b/src/app/shared/components/input-gameevent/input-gameevent.component.ts
deleted file mode 100644
index 29db140..0000000
--- a/src/app/shared/components/input-gameevent/input-gameevent.component.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import {
- Component,
- computed,
- inject,
- input,
- model,
- output,
-} from '@angular/core';
-import { ModService } from '../../../services/mod.service';
-
-@Component({
- selector: 'app-input-gameevent',
- templateUrl: './input-gameevent.component.html',
- styleUrl: './input-gameevent.component.scss',
-})
-export class InputGameeventComponent {
- private modService = inject(ModService);
-
- public gameEvent = model.required();
- public label = input('In-Game Event');
- public change = output();
-
- public values = computed(() => {
- const eventObj = this.modService
- .mod()
- .cores.find((c) => c.name === 'events')?.json as Record;
- if (!eventObj) return [];
-
- return Object.keys(eventObj ?? {})
- .sort()
- .map((t) => ({
- value: t,
- desc: eventObj[t].description ?? 'No description',
- }));
- });
-
- public search(term: string, item: { value: string }) {
- return item.value.toLowerCase().includes(term.toLowerCase());
- }
-}
diff --git a/src/app/shared/components/input-map/input-map.component.html b/src/app/shared/components/input-map/input-map.component.html
index e704c6d..6de9c41 100644
--- a/src/app/shared/components/input-map/input-map.component.html
+++ b/src/app/shared/components/input-map/input-map.component.html
@@ -2,5 +2,5 @@
- Map
+ {{ label() }}
\ No newline at end of file
diff --git a/src/app/shared/components/input-map/input-map.component.ts b/src/app/shared/components/input-map/input-map.component.ts
index c723e38..0de38ce 100644
--- a/src/app/shared/components/input-map/input-map.component.ts
+++ b/src/app/shared/components/input-map/input-map.component.ts
@@ -20,6 +20,7 @@ export class InputMapComponent implements OnInit {
public map = model.required();
public defaultValue = input();
+ public label = input('Map');
public change = output();
public values = computed(() =>
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 0052a9c..fb93e8c 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -32,8 +32,10 @@ import { InputChallengeratingComponent } from './components/input-challengeratin
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 { InputEventComponent } from './components/input-event/input-event.component';
+import { InputEventRarityComponent } from './components/input-eventrarity/input-eventrarity.component';
+import { InputEventSuccessTypeComponent } from './components/input-eventsuccesstype/input-eventsuccesstype.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';
@@ -115,11 +117,13 @@ import { WebviewDirective } from './directives/';
InputMacrotypeComponent,
InputBufftypeComponent,
EditStatobjectComponent,
- InputGameeventComponent,
InputSpawnerComponent,
InputAnalysisReportComponent,
InputStemComponent,
InputAchievementTypeComponent,
+ InputEventRarityComponent,
+ InputEventComponent,
+ InputEventSuccessTypeComponent,
],
imports: [
CommonModule,
@@ -182,11 +186,13 @@ import { WebviewDirective } from './directives/';
InputMacrotypeComponent,
InputBufftypeComponent,
EditStatobjectComponent,
- InputGameeventComponent,
InputSpawnerComponent,
InputAnalysisReportComponent,
InputStemComponent,
InputAchievementTypeComponent,
+ InputEventRarityComponent,
+ InputEventComponent,
+ InputEventSuccessTypeComponent,
],
})
export class SharedModule {}
diff --git a/src/app/tabs/achievements copy/events-editor/events-editor.component.html b/src/app/tabs/achievements copy/events-editor/events-editor.component.html
new file mode 100644
index 0000000..9d2e8a1
--- /dev/null
+++ b/src/app/tabs/achievements copy/events-editor/events-editor.component.html
@@ -0,0 +1,204 @@
+@let editingData = editing();
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ editingData | json }}
+
\ No newline at end of file
diff --git a/src/app/tabs/achievements copy/events-editor/events-editor.component.scss b/src/app/tabs/achievements copy/events-editor/events-editor.component.scss
new file mode 100644
index 0000000..bbffa1d
--- /dev/null
+++ b/src/app/tabs/achievements copy/events-editor/events-editor.component.scss
@@ -0,0 +1,7 @@
+.editor-container {
+ height: 60vh;
+}
+
+.form-error {
+ min-height: 48px;
+}
diff --git a/src/app/tabs/achievements copy/events-editor/events-editor.component.ts b/src/app/tabs/achievements copy/events-editor/events-editor.component.ts
new file mode 100644
index 0000000..d17e21b
--- /dev/null
+++ b/src/app/tabs/achievements copy/events-editor/events-editor.component.ts
@@ -0,0 +1,106 @@
+import { Component, computed, OnInit, signal } from '@angular/core';
+
+import { IDynamicEventMeta, INPCDefinition } from '../../../../interfaces';
+import { EditorBaseComponent } from '../../../shared/components/editor-base/editor-base.component';
+
+@Component({
+ selector: 'app-events-editor',
+ templateUrl: './events-editor.component.html',
+ styleUrl: './events-editor.component.scss',
+})
+export class EventsEditorComponent
+ extends EditorBaseComponent
+ implements OnInit
+{
+ public currentConflictEvent = signal(undefined);
+ public currentNPC = signal(undefined);
+
+ public canSave = computed(() => {
+ const data = this.editing();
+ return (
+ data.name &&
+ data.description &&
+ data.rarity &&
+ data.startMessage &&
+ data.endMessage &&
+ this.satisfiesUnique()
+ );
+ });
+
+ public satisfiesUnique = computed(() => {
+ const data = this.editing();
+ return !this.modService.doesExistDuplicate(
+ 'events',
+ 'name',
+ data.name,
+ data._id
+ );
+ });
+
+ public isCurrentEventAConflict = computed(() => {
+ const evt = this.currentConflictEvent();
+ if (!evt) return true;
+
+ if (evt === this.editing().name) return true;
+
+ return this.editing().conflicts?.includes(evt);
+ });
+
+ public conflictsInOrder = computed(() => this.editing().conflicts.sort());
+ public npcKillsInOrder = computed(() =>
+ this.editing().successMetrics.killNPCs.sort()
+ );
+
+ ngOnInit(): void {
+ super.ngOnInit();
+ }
+
+ doSave() {
+ const core = this.editing();
+
+ this.editing.set(core);
+
+ super.doSave();
+ }
+
+ public addConflictEvent(event: string | undefined) {
+ if (!event) return;
+
+ const core = this.editing();
+
+ core.conflicts.push(event);
+
+ this.editing.set(structuredClone(core));
+ }
+
+ public removeConflict(event: string) {
+ const core = this.editing();
+
+ core.conflicts = core.conflicts.filter((f) => f !== event);
+
+ this.editing.set(structuredClone(core));
+ }
+
+ public addNPC(npc: INPCDefinition | undefined) {
+ if (!npc) return;
+
+ const event = this.editing();
+
+ event.successMetrics.killNPCs.push(npc.npcId);
+
+ this.editing.set(structuredClone(event));
+ }
+
+ public removeNPC(index: number) {
+ const event = this.editing();
+
+ event.successMetrics.killNPCs.splice(index, 1);
+
+ this.editing.set(structuredClone(event));
+ }
+
+ public hasNPC(npc: INPCDefinition | undefined) {
+ if (!npc) return false;
+ return this.editing().successMetrics.killNPCs.some((n) => n === npc.npcId);
+ }
+}
diff --git a/src/app/tabs/achievements copy/events.component.html b/src/app/tabs/achievements copy/events.component.html
new file mode 100644
index 0000000..deff495
--- /dev/null
+++ b/src/app/tabs/achievements copy/events.component.html
@@ -0,0 +1,10 @@
+@let editing = isEditing();
+
+
+
+@if(editing) {
+
+}
\ No newline at end of file
diff --git a/src/app/tabs/achievements copy/events.component.scss b/src/app/tabs/achievements copy/events.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/tabs/achievements copy/events.component.ts b/src/app/tabs/achievements copy/events.component.ts
new file mode 100644
index 0000000..cf736e8
--- /dev/null
+++ b/src/app/tabs/achievements copy/events.component.ts
@@ -0,0 +1,94 @@
+import { Component, computed } from '@angular/core';
+import { ColDef } from 'ag-grid-community';
+
+import { IDynamicEventMeta, IModKit } from '../../../interfaces';
+import { defaultEvent, id } from '../../helpers';
+import { CellButtonsComponent } from '../../shared/components/cell-buttons/cell-buttons.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 = IDynamicEventMeta;
+
+@Component({
+ selector: 'app-events',
+ templateUrl: './events.component.html',
+ styleUrl: './events.component.scss',
+})
+export class EventsComponent extends EditorBaseTableComponent {
+ protected dataKey: keyof Omit = 'events';
+
+ public defaultData = defaultEvent;
+
+ public tableItems = computed(() => this.modService.mod().events);
+ public tableColumns: ColDef[] = [
+ {
+ field: 'name',
+ flex: 2,
+ cellDataType: 'text',
+ filter: 'agTextColumnFilter',
+ sort: 'asc',
+ },
+ {
+ field: 'description',
+ headerName: 'Description',
+ flex: 5,
+ cellDataType: 'text',
+ filter: 'agTextColumnFilter',
+ cellClass: 'leading-4 whitespace-break-spaces',
+ sortable: false,
+ },
+ {
+ field: 'map',
+ headerName: 'Active Map',
+ flex: 1,
+ cellDataType: 'text',
+ filter: 'agTextColumnFilter',
+ },
+ {
+ field: 'rarity',
+ flex: 1,
+ cellDataType: 'text',
+ filter: 'agTextColumnFilter',
+ },
+ {
+ field: 'duration',
+ headerName: 'Duration',
+ flex: 1,
+ cellDataType: 'number',
+ filter: 'agNumberColumnFilter',
+ },
+ {
+ field: 'cooldown',
+ headerName: 'Cooldown',
+ flex: 1,
+ cellDataType: 'number',
+ filter: 'agNumberColumnFilter',
+ },
+ {
+ 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/achievements/achievements-editor/achievements-editor.component.ts b/src/app/tabs/achievements/achievements-editor/achievements-editor.component.ts
index 71ebaa6..11d2654 100644
--- a/src/app/tabs/achievements/achievements-editor/achievements-editor.component.ts
+++ b/src/app/tabs/achievements/achievements-editor/achievements-editor.component.ts
@@ -17,7 +17,7 @@ export class AchievementsEditorComponent
public canSave = computed(() => {
const data = this.editing();
- return data.name && data.desc && data.ap > 0;
+ return data.name && data.desc && data.ap > 0 && this.satisfiesUnique();
});
public satisfiesUnique = computed(() => {
diff --git a/src/app/tabs/spawners/spawners-editor/spawners-editor.component.html b/src/app/tabs/spawners/spawners-editor/spawners-editor.component.html
index 876c2b7..f74c225 100644
--- a/src/app/tabs/spawners/spawners-editor/spawners-editor.component.html
+++ b/src/app/tabs/spawners/spawners-editor/spawners-editor.component.html
@@ -86,9 +86,11 @@