diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts
index e02097f..768747f 100644
--- a/src/app/home/home.component.ts
+++ b/src/app/home/home.component.ts
@@ -13,6 +13,7 @@ import { formatMod } from '../helpers/exporter';
import { DebugService } from '../services/debug.service';
import { ElectronService } from '../services/electron.service';
import { ModService } from '../services/mod.service';
+import { PinpointService } from '../services/pinpoint.service';
@Component({
selector: 'app-home',
@@ -21,6 +22,7 @@ import { ModService } from '../services/mod.service';
})
export class HomeComponent {
private localStorage = inject(LocalStorageService);
+ public pinpointService = inject(PinpointService);
public debugService = inject(DebugService);
public electronService = inject(ElectronService);
public modService = inject(ModService);
diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts
index 8d957f6..149ef70 100644
--- a/src/app/home/home.module.ts
+++ b/src/app/home/home.module.ts
@@ -10,6 +10,7 @@ import { AgGridModule } from 'ag-grid-angular';
import { ColorPickerModule } from 'ngx-color-picker';
import { NgxFloatUiModule } from 'ngx-float-ui';
+import { PinpointComponent } from '../pinpoint/pinpoint.component';
import { SharedModule } from '../shared/shared.module';
import { CoresEditorComponent } from '../tabs/cores/cores-editor/cores-editor.component';
import { CoresComponent } from '../tabs/cores/cores.component';
@@ -60,6 +61,7 @@ import { HomeComponent } from './home.component';
TraitTreesEditorComponent,
StemsComponent,
StemsEditorComponent,
+ PinpointComponent,
],
imports: [
CommonModule,
@@ -80,6 +82,7 @@ import { HomeComponent } from './home.component';
TraitTreesEditorComponent,
StemsComponent,
StemsEditorComponent,
+ PinpointComponent,
],
})
export class HomeModule {}
diff --git a/src/app/pinpoint/pinpoint.component.html b/src/app/pinpoint/pinpoint.component.html
new file mode 100644
index 0000000..8cdc56d
--- /dev/null
+++ b/src/app/pinpoint/pinpoint.component.html
@@ -0,0 +1,215 @@
+
+
+ @for(tab of tabOrder; track tab.name; let i = $index) {
+
+ {{ tab.name }}
+
+ }
+
+
+ Done
+
+
+
+
+
+@switch (pinpointService.activePinpointTab()) {
+@case (0) {
+
+
+
+ @for(entry of pinpointService.mapInformation(); track $index) {
+
+
+ @if(entry.npcName) {
+
+
+
+ } @else {
+
+ {{ entry.originName }}
+
+ }
+
+
+ ➡️
+
+
+ @if(entry.itemName) {
+
+
+
+ }
+
+ @if(entry.chance !== -1 && entry.maxChance !== -1) {
+
+ ({{ entry.chance }}/{{ entry.maxChance }})
+
+ }
+
+
+ }
+
+}
+
+@case (1) {
+
+
+
+
+ @let itemInfo = pinpointService.itemInformation();
+
+ @if(itemInfo.length === 0) {
+
+ }
+
+
+ @for(entry of itemInfo; track $index) {
+
+
+
+ @if(entry.npcName) {
+
+
+
+ }
+
+ @if(entry.containingItemName) {
+
+
+
+ }
+
+ @if(entry.recipeName) {
+
+ RECIPE: {{ entry.recipeName }}
+
+ }
+
+ @if(entry.questName) {
+
+ QUEST: {{ entry.questName }}
+
+ }
+
+ @if(entry.droptableName) {
+
+ DROPS: {{ entry.droptableName }}
+
+ }
+
+ @if(entry.npcScriptName) {
+
+ SCRIPT: {{ entry.npcScriptName }}
+
+ }
+
+
+ {{ entry.extraDescription }}
+
+
+
+ }
+
+
+}
+
+@case (2) {
+
+
+
+ @let npcInfo = pinpointService.npcInformation();
+
+
+
+
+
+}
+}
\ No newline at end of file
diff --git a/src/app/pinpoint/pinpoint.component.scss b/src/app/pinpoint/pinpoint.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pinpoint/pinpoint.component.ts b/src/app/pinpoint/pinpoint.component.ts
new file mode 100644
index 0000000..b19d280
--- /dev/null
+++ b/src/app/pinpoint/pinpoint.component.ts
@@ -0,0 +1,26 @@
+import { Component, inject } from '@angular/core';
+import { PinpointService } from '../services/pinpoint.service';
+
+@Component({
+ selector: 'app-pinpoint',
+ templateUrl: './pinpoint.component.html',
+ styleUrl: './pinpoint.component.scss',
+})
+export class PinpointComponent {
+ public pinpointService = inject(PinpointService);
+
+ public tabOrder = [
+ {
+ name: 'Map',
+ },
+ {
+ name: 'Item',
+ },
+ {
+ name: 'NPC',
+ },
+ {
+ name: 'Spawner',
+ },
+ ];
+}
diff --git a/src/app/services/pinpoint.service.ts b/src/app/services/pinpoint.service.ts
new file mode 100644
index 0000000..b7fca7b
--- /dev/null
+++ b/src/app/services/pinpoint.service.ts
@@ -0,0 +1,468 @@
+import { computed, inject, Injectable, signal } from '@angular/core';
+import { get, sortBy, uniq, uniqBy } from 'lodash';
+import { INPCDefinition, Rollable } from '../../interfaces';
+import { ModService } from './mod.service';
+
+interface ItemDropDescriptor {
+ npcName?: string;
+ itemName?: string;
+
+ originName: string;
+
+ chance: number;
+ maxChance: number;
+}
+
+interface ItemUseDescriptor {
+ npcName?: string;
+ recipeName?: string;
+ questName?: string;
+ containingItemName?: string;
+ npcScriptName?: string;
+ droptableName?: string;
+
+ extraDescription?: string;
+}
+
+interface NPCUseDescriptor {
+ spawnerName?: string;
+ questName?: string;
+
+ extraDescription?: string;
+}
+
+@Injectable({
+ providedIn: 'root',
+})
+export class PinpointService {
+ private modService = inject(ModService);
+
+ public activePinpointTab = signal
(0);
+ public isPinpointing = signal(false);
+
+ public pinpointMap = signal(undefined);
+ public pinpointItem = signal(undefined);
+ public pinpointNPC = signal(undefined);
+
+ public mapInformation = computed(() => {
+ const map = this.pinpointMap();
+ if (!map) return [];
+
+ return [
+ ...this.getItemsFromMapSpawnerNPCs(map),
+ ...this.getItemsFromMapDroptables(map),
+ ];
+ });
+
+ public itemInformation = computed(() => {
+ const item = this.pinpointItem();
+ if (!item) return [];
+
+ return this.getItemUses(item);
+ });
+
+ public npcInformation = computed(() => {
+ const npc = this.pinpointNPC();
+ if (!npc) return { uses: [], drops: [] };
+
+ return {
+ uses: this.getNPCUses(npc),
+ drops: this.getNPCDrops(npc),
+ };
+ });
+
+ public togglePinpointing() {
+ const newSetting = !this.isPinpointing();
+ this.isPinpointing.set(newSetting);
+
+ this.pinpointMap.set(undefined);
+ this.pinpointItem.set(undefined);
+ this.pinpointNPC.set(undefined);
+ }
+
+ public searchMap(map: string) {
+ this.pinpointMap.set(map);
+ this.isPinpointing.set(true);
+ this.activePinpointTab.set(0);
+ }
+
+ public searchItem(item: string | undefined) {
+ this.pinpointItem.set(item);
+ this.isPinpointing.set(true);
+ this.activePinpointTab.set(1);
+ }
+
+ public searchNPC(npc: string | undefined) {
+ this.pinpointNPC.set(npc);
+ this.isPinpointing.set(true);
+ this.activePinpointTab.set(2);
+ }
+
+ private getDroppedItemsFromNPC(ref: INPCDefinition): ItemDropDescriptor[] {
+ const drops =
+ ref.drops?.map((d) => ({
+ npcName: ref.npcId,
+ chance: d.chance,
+ maxChance: d.maxChance ?? -1,
+ itemName: d.result,
+ originName: `NPC: ${ref.npcId}`,
+ })) ?? [];
+
+ const dropPool =
+ ref.dropPool?.items?.map((d) => ({
+ npcName: ref.npcId,
+ chance: d.chance,
+ maxChance: d.maxChance ?? -1,
+ itemName: d.result,
+ originName: `NPC: ${ref.npcId}`,
+ })) ?? [];
+
+ const copyDrops =
+ ref.copyDrops
+ ?.map((c) => {
+ const potentials = get(ref.items, c.result) as Rollable[];
+ return (
+ potentials?.map((p) => ({
+ npcName: ref.npcId,
+ chance: -1,
+ maxChance: -1,
+ itemName: p.result,
+ originName: `NPC: ${ref.npcId}`,
+ })) ?? []
+ );
+ })
+ .flat(Infinity) ?? [];
+
+ const tans = ref.tansFor
+ ? {
+ npcName: ref.npcId,
+ chance: 100,
+ maxChance: 100,
+ itemName: ref.tansFor,
+ originName: `NPC: ${ref.npcId}`,
+ }
+ : undefined;
+
+ return [...drops, ...dropPool, ...copyDrops, tans]
+ .filter(Boolean)
+ .flat()
+ .filter((entry) => entry?.itemName !== 'none') as ItemDropDescriptor[];
+ }
+
+ private getItemUsesFromNPC(
+ ref: INPCDefinition,
+ item: string
+ ): ItemUseDescriptor[] {
+ const drops: ItemUseDescriptor[] =
+ ref.drops
+ ?.filter((d) => d.result === item)
+ .map(() => ({
+ npcName: ref.npcId,
+ extraDescription: 'DROP',
+ })) ?? [];
+
+ const dropPool: ItemUseDescriptor[] =
+ ref.dropPool?.items
+ ?.filter((d) => d.result === item)
+ .map(() => ({
+ npcName: ref.npcId,
+ extraDescription: 'DROPPOOL',
+ })) ?? [];
+
+ const copyDrops: ItemUseDescriptor[] =
+ ref.copyDrops
+ ?.map((c) => {
+ const potentials = get(ref.items, c.result) as Rollable[];
+ return potentials
+ ?.filter((f) => f.result === item)
+ .map(() => ({
+ npcName: ref.npcId,
+ extraDescription: 'COPYDROP',
+ }));
+ })
+ .flat(2) ?? [];
+
+ const equipment: ItemUseDescriptor[] = Object.values(
+ ref.items?.equipment ?? {}
+ )
+ .flat()
+ .filter((r) => r.result === item)
+ .map(() => ({
+ npcName: ref.npcId,
+ extraDescription: 'EQUIPMENT',
+ }));
+
+ const sack: ItemUseDescriptor[] = (ref.items?.sack ?? [])
+ .filter((r) => r.result === item)
+ .map(() => ({
+ npcName: ref.npcId,
+ extraDescription: 'SACK',
+ }));
+
+ const tans: ItemUseDescriptor | undefined =
+ ref.tansFor === item
+ ? {
+ npcName: ref.npcId,
+ extraDescription: `TANS`,
+ }
+ : undefined;
+
+ return (
+ [
+ ...drops,
+ ...dropPool,
+ ...copyDrops,
+ ...equipment,
+ ...sack,
+ tans,
+ ] as ItemUseDescriptor[]
+ )
+ .filter(Boolean)
+ .flat();
+ }
+
+ private getItemsFromMapSpawnerNPCs(map: string): ItemDropDescriptor[] {
+ const mod = this.modService.mod();
+
+ const mapData = mod.maps.find((m) => m.name === map)?.map;
+ if (!mapData) return [];
+
+ const allSpawners = uniq(
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
+ mapData.layers[10].objects.map((o: any) => o.properties.tag as string)
+ );
+ if (allSpawners.length === 0) return [];
+
+ const allLairs = mapData.layers[10].objects
+ .map((o: any) => o.properties.lairName as string)
+ .filter(Boolean);
+
+ const allSpawnerRefs = allSpawners
+ .map((s) => mod.spawners.find((m) => m.tag === s))
+ .filter((s) => (s?.npcIds.length ?? 0) > 0);
+
+ if (allLairs.length === 0 && allSpawnerRefs.length === 0) return [];
+
+ const allNPCNames = uniq([
+ ...allLairs,
+ ...allSpawnerRefs
+ .map((s) => s?.npcIds.map((m) => m.result))
+ .flat(Infinity),
+ ]);
+
+ const allNPCRefs = allNPCNames.map((m) =>
+ mod.npcs.find((npc) => npc.npcId === m)
+ );
+ if (allNPCRefs.length === 0) return [];
+
+ const allDrops: ItemDropDescriptor[] = sortBy(allNPCRefs, 'npcId')
+ .map((ref) => {
+ if (!ref) return [];
+ return this.getDroppedItemsFromNPC(ref);
+ })
+ .flat(Infinity) as ItemDropDescriptor[];
+
+ return allDrops;
+ }
+
+ private getItemsFromMapDroptables(map: string): ItemDropDescriptor[] {
+ const mod = this.modService.mod();
+
+ const mapData = mod.maps.find((m) => m.name === map)?.map;
+ if (!mapData) return [];
+
+ const region = mapData.properties.region;
+
+ const dropTables = mod.drops.filter(
+ (m) => m.mapName === map || m.regionName === region || m.isGlobal
+ );
+
+ return dropTables
+ .map((d) => {
+ let originName = 'DT (global)';
+ if (d.mapName) originName = `DT (${map})`;
+ if (d.regionName) originName = `DT (${region})`;
+
+ return d.drops.map((m) => ({
+ originName: m.requireHoliday
+ ? `[${m.requireHoliday}] ${originName}`
+ : originName,
+ itemName: m.result,
+ chance: m.chance,
+ maxChance: m.maxChance ?? -1,
+ }));
+ })
+ .flat();
+ }
+
+ private getItemUses(item: string): ItemUseDescriptor[] {
+ const mod = this.modService.mod();
+
+ // gather usages
+ const containingItems =
+ mod.items.filter((f) =>
+ f.containedItems?.find((c) => c.result === item)
+ ) ?? [];
+
+ const recipeUses =
+ mod.recipes.filter(
+ (r) =>
+ r.item === item ||
+ r.ingredients?.some((i) => i === item) ||
+ r.ozIngredients?.some((i) => i.display === item)
+ ) ?? [];
+
+ const questUses = mod.quests.filter((q) => q.requirements.item === item);
+
+ const npcScriptUses = mod.dialogs.filter((sc) =>
+ Object.values(sc.items?.equipment ?? {}).some((s) => s === item)
+ );
+
+ const droptableUses = mod.drops.filter((f) =>
+ f.drops.some((d) => d.result === item)
+ );
+
+ // format usages
+ const containingItemDescs: ItemUseDescriptor[] = containingItems.map(
+ (c) => ({
+ containingItemName: c.name,
+ extraDescription: 'CONTAINS',
+ })
+ );
+
+ const recipeDescs: ItemUseDescriptor[] = recipeUses.map((r) => ({
+ recipeName: r.name,
+ extraDescription: r.item === item ? 'RESULT' : 'INGREDIENT',
+ }));
+
+ const questDescs: ItemUseDescriptor[] = questUses.map((q) => ({
+ questName: q.name,
+ extraDescription: 'REQUIRED',
+ }));
+
+ const npcDescs: ItemUseDescriptor[] = uniqBy(
+ sortBy(mod.npcs, 'npcId')
+ .map((n) => this.getItemUsesFromNPC(n, item))
+ .filter((f) => f.length > 0)
+ .flat(),
+ 'npcName'
+ );
+
+ const npcScriptDescs: ItemUseDescriptor[] = npcScriptUses.map((sc) => ({
+ npcScriptName: sc.tag,
+ extraDescription: 'EQUIPMENT',
+ }));
+
+ const droptableDescs: ItemUseDescriptor[] = droptableUses.map((d) => ({
+ droptableName: d.mapName ?? d.regionName ?? 'Global',
+ extraDescription: 'DROPTABLE',
+ }));
+
+ return [
+ ...containingItemDescs,
+ ...recipeDescs,
+ ...questDescs,
+ ...npcDescs,
+ ...npcScriptDescs,
+ ...droptableDescs,
+ ] as ItemUseDescriptor[];
+ }
+
+ private getNPCUses(npc: string): NPCUseDescriptor[] {
+ const mod = this.modService.mod();
+
+ const lairMaps: NPCUseDescriptor[] = mod.maps
+ .map((m) => {
+ const lairSpawners = m.map.layers[10].objects.filter(
+ (o: any) => o.properties.lairName === npc
+ );
+
+ return lairSpawners.map((spawner: any) => ({
+ spawnerName: 'Lair',
+ extraDescription: `MAP ${m.name} (${spawner.x / 64}, ${
+ spawner.y / 64 - 1
+ })`,
+ })) as NPCUseDescriptor[];
+ })
+ .flat();
+
+ const relatedSpawners = mod.spawners
+ .filter((s) => s.npcIds.some((n) => n.result === npc))
+ .map((m) => m.tag);
+
+ const relatedSpawnerUses = mod.maps
+ .map((m) => {
+ const foundSpawners = m.map.layers[10].objects.filter((o: any) =>
+ relatedSpawners.includes(o.properties.tag as string)
+ );
+
+ return foundSpawners.map((spawner: any) => ({
+ spawnerName: spawner.properties.tag,
+ extraDescription: `MAP ${m.name} (${spawner.x / 64}, ${
+ spawner.y / 64 - 1
+ })`,
+ })) as NPCUseDescriptor[];
+ })
+ .flat();
+
+ const relatedQuests = mod.quests
+ .filter((q) => q.requirements?.npcIds?.includes(npc))
+ .map((q) => ({
+ questName: q.name,
+ }));
+
+ return [...lairMaps, ...relatedSpawnerUses, ...relatedQuests].filter(
+ Boolean
+ );
+ }
+
+ private getNPCDrops(npc: string): Rollable[] {
+ const mod = this.modService.mod();
+
+ const npcData = mod.npcs.find((n) => n.npcId === npc);
+ if (!npcData) return [];
+
+ const allPossibleSpawners = mod.spawners
+ .filter((s) => s.npcIds.some((n) => n.result === npc))
+ .map((s) => s.tag);
+
+ const allMaps = mod.maps.filter((m) =>
+ m.map.layers[10].objects.some(
+ (o: any) =>
+ o.properties.lairName === npc ||
+ allPossibleSpawners.includes(o.properties.tag as string)
+ )
+ );
+
+ const allMapNames = allMaps.map((m) => m.name);
+ const allMapRegions = allMaps.map((m) => m.map.properties.region as string);
+
+ const droptables = mod.drops.filter(
+ (d) =>
+ d.isGlobal ||
+ (d.mapName && allMapNames.includes(d.mapName)) ||
+ (d.regionName && allMapRegions.includes(d.regionName))
+ );
+
+ return [
+ ...(npcData.drops ?? []),
+ ...(npcData.dropPool?.items ?? []),
+ ...(
+ npcData.copyDrops?.map((c) => {
+ return get(npcData.items, c.result) as Rollable[];
+ }) ?? []
+ ).flat(),
+ ...(npcData.tansFor
+ ? [
+ {
+ result: npcData.tansFor,
+ chance: -1,
+ },
+ ]
+ : []),
+ ...droptables.map((d) => d.drops).flat(),
+ ]
+ .filter(Boolean)
+ .filter((i) => i.result !== 'none');
+ }
+}
diff --git a/src/app/shared/components/cell-buttons/cell-buttons.component.html b/src/app/shared/components/cell-buttons/cell-buttons.component.html
index 9306396..2c96563 100644
--- a/src/app/shared/components/cell-buttons/cell-buttons.component.html
+++ b/src/app/shared/components/cell-buttons/cell-buttons.component.html
@@ -1,4 +1,10 @@
+ @if(params.showPinpointButton) {
+
+
+
+ }
+
@if(params.showCopyButton) {
diff --git a/src/app/shared/components/input-item/input-item.component.ts b/src/app/shared/components/input-item/input-item.component.ts
index 41765e6..567177e 100644
--- a/src/app/shared/components/input-item/input-item.component.ts
+++ b/src/app/shared/components/input-item/input-item.component.ts
@@ -21,7 +21,7 @@ type ItemModel = { category: string; data: IItemDefinition; value: string };
export class InputItemComponent implements OnInit {
private modService = inject(ModService);
- public item = model();
+ public item = model();
public label = input('Item');
public defaultValue = input();
public allowNone = input(false);
@@ -51,7 +51,7 @@ export class InputItemComponent implements OnInit {
const defaultItem = this.defaultValue();
if (defaultItem) {
const foundItem = this.values().find((i) => i.value === defaultItem);
- this.item.set(foundItem as unknown as IItemDefinition);
+ this.item.set(foundItem);
}
}
diff --git a/src/app/shared/components/input-npc/input-npc.component.ts b/src/app/shared/components/input-npc/input-npc.component.ts
index 40b5b1f..7d09869 100644
--- a/src/app/shared/components/input-npc/input-npc.component.ts
+++ b/src/app/shared/components/input-npc/input-npc.component.ts
@@ -21,7 +21,7 @@ type NPCModel = { category: string; data: INPCDefinition; value: string };
export class InputNpcComponent implements OnInit {
private modService = inject(ModService);
- public npc = model.required();
+ public npc = model();
public label = input('NPC');
public defaultValue = input();
public change = output();
diff --git a/src/app/shared/components/input-spawner/input-spawner.component.html b/src/app/shared/components/input-spawner/input-spawner.component.html
new file mode 100644
index 0000000..3d7df2c
--- /dev/null
+++ b/src/app/shared/components/input-spawner/input-spawner.component.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ {{ item.data.tag }}
+
+
+
+
+
+
+
+ {{ item.data.tag }}
+
+
+
+
+
+
{{ label() }}
+
\ No newline at end of file
diff --git a/src/app/shared/components/input-spawner/input-spawner.component.scss b/src/app/shared/components/input-spawner/input-spawner.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/components/input-spawner/input-spawner.component.ts b/src/app/shared/components/input-spawner/input-spawner.component.ts
new file mode 100644
index 0000000..617e253
--- /dev/null
+++ b/src/app/shared/components/input-spawner/input-spawner.component.ts
@@ -0,0 +1,61 @@
+import {
+ Component,
+ computed,
+ inject,
+ input,
+ model,
+ OnInit,
+ output,
+} from '@angular/core';
+import { sortBy } from 'lodash';
+import { ISpawnerData } from '../../../../interfaces';
+import { ModService } from '../../../services/mod.service';
+
+type ItemModel = { category: string; data: ISpawnerData; value: string };
+
+@Component({
+ selector: 'app-input-spawner',
+ templateUrl: './input-spawner.component.html',
+ styleUrl: './input-spawner.component.scss',
+})
+export class InputSpawnerComponent implements OnInit {
+ private modService = inject(ModService);
+
+ public spawner = model();
+ public label = input('Spawner');
+ public defaultValue = input();
+ public change = output();
+
+ public values = computed(() => {
+ const mod = this.modService.mod();
+
+ return [
+ ...sortBy(
+ mod.spawners.map((i) => ({
+ category: 'My Mod Spawners',
+ data: i,
+ value: i.tag,
+ })),
+ 'value'
+ ),
+ ]
+ .flat()
+ .filter(Boolean) as ItemModel[];
+ });
+
+ ngOnInit() {
+ const defaultItem = this.defaultValue();
+ if (defaultItem) {
+ const foundItem = this.values().find((i) => i.value === defaultItem);
+ this.spawner.set(foundItem as unknown as ISpawnerData);
+ }
+ }
+
+ public itemCompare(itemA: ItemModel, itemB: ItemModel): 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/shared.module.ts b/src/app/shared/shared.module.ts
index 981ebb8..38b001f 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -60,6 +60,7 @@ import { InputMacrotypeComponent } from './components/input-macrotype/input-macr
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';
@NgModule({
declarations: [
@@ -112,6 +113,7 @@ import { InputGameeventComponent } from './components/input-gameevent/input-game
InputBufftypeComponent,
EditStatobjectComponent,
InputGameeventComponent,
+ InputSpawnerComponent,
],
imports: [
CommonModule,
@@ -175,6 +177,7 @@ import { InputGameeventComponent } from './components/input-gameevent/input-game
InputBufftypeComponent,
EditStatobjectComponent,
InputGameeventComponent,
+ InputSpawnerComponent,
],
})
export class SharedModule {}
diff --git a/src/app/tabs/items/items.component.ts b/src/app/tabs/items/items.component.ts
index 029633c..12d505d 100644
--- a/src/app/tabs/items/items.component.ts
+++ b/src/app/tabs/items/items.component.ts
@@ -1,8 +1,9 @@
-import { Component, computed } from '@angular/core';
+import { Component, computed, inject } from '@angular/core';
import { ColDef } from 'ag-grid-community';
import { IItemDefinition, IModKit } from '../../../interfaces';
import { defaultItem, id } from '../../helpers';
+import { PinpointService } from '../../services/pinpoint.service';
import { CellButtonsComponent } from '../../shared/components/cell-buttons/cell-buttons.component';
import { CellSpriteComponent } from '../../shared/components/cell-sprite/cell-sprite.component';
import { EditorBaseTableComponent } from '../../shared/components/editor-base-table/editor-base-table.component';
@@ -16,6 +17,8 @@ type EditingType = IItemDefinition;
styleUrl: './items.component.scss',
})
export class ItemsComponent extends EditorBaseTableComponent {
+ private pinpointService = inject(PinpointService);
+
protected dataKey: keyof Omit = 'items';
public defaultData = defaultItem;
@@ -71,7 +74,7 @@ export class ItemsComponent extends EditorBaseTableComponent {
},
{
field: '',
- width: 200,
+ width: 250,
sortable: false,
suppressMovable: true,
headerComponent: HeaderButtonsComponent,
@@ -82,6 +85,10 @@ export class ItemsComponent extends EditorBaseTableComponent {
cellRenderer: CellButtonsComponent,
cellClass: 'no-adjust',
cellRendererParams: {
+ showPinpointButton: true,
+ pinpointCallback: (item: EditingType) => {
+ this.pinpointService.searchItem(item.name);
+ },
showCopyButton: true,
copyCallback: (item: EditingType) => {
const newItem = structuredClone(item);
diff --git a/src/app/tabs/maps/maps.component.ts b/src/app/tabs/maps/maps.component.ts
index fd73e51..f3d8516 100644
--- a/src/app/tabs/maps/maps.component.ts
+++ b/src/app/tabs/maps/maps.component.ts
@@ -11,6 +11,7 @@ import { IEditorMap } from '../../../interfaces/map';
import { id } from '../../helpers';
import { ElectronService } from '../../services/electron.service';
import { NotifyService } from '../../services/notify.service';
+import { PinpointService } from '../../services/pinpoint.service';
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';
@@ -25,6 +26,7 @@ type EditingType = IEditorMap;
export class MapsComponent extends EditorBaseTableComponent {
private notifyService = inject(NotifyService);
private electronService = inject(ElectronService);
+ private pinpointService = inject(PinpointService);
public newSwal = viewChild('newSwal');
public renameSwal = viewChild('renameSwal');
@@ -83,7 +85,7 @@ export class MapsComponent extends EditorBaseTableComponent {
},
{
field: '',
- width: 250,
+ width: 300,
sortable: false,
suppressMovable: true,
headerComponent: HeaderButtonsComponent,
@@ -96,6 +98,10 @@ export class MapsComponent extends EditorBaseTableComponent {
cellRenderer: CellButtonsComponent,
cellClass: 'no-adjust',
cellRendererParams: {
+ showPinpointButton: true,
+ pinpointCallback: (item: EditingType) => {
+ this.pinpointService.searchMap(item.name);
+ },
showCopyButton: true,
copyCallback: (item: EditingType) => this.copyMap(item.name),
showRenameButton: true,
diff --git a/src/app/tabs/npcs/npcs.component.ts b/src/app/tabs/npcs/npcs.component.ts
index a9f611f..51032a9 100644
--- a/src/app/tabs/npcs/npcs.component.ts
+++ b/src/app/tabs/npcs/npcs.component.ts
@@ -4,6 +4,7 @@ import { ColDef } from 'ag-grid-community';
import { IModKit, INPCDefinition } from '../../../interfaces';
import { defaultNPC, id } from '../../helpers';
import { ElectronService } from '../../services/electron.service';
+import { PinpointService } from '../../services/pinpoint.service';
import { CellButtonsComponent } from '../../shared/components/cell-buttons/cell-buttons.component';
import { CellSpriteComponent } from '../../shared/components/cell-sprite/cell-sprite.component';
import { EditorBaseTableComponent } from '../../shared/components/editor-base-table/editor-base-table.component';
@@ -18,6 +19,7 @@ type EditingType = INPCDefinition;
})
export class NpcsComponent extends EditorBaseTableComponent {
private electronService = inject(ElectronService);
+ private pinpointService = inject(PinpointService);
protected dataKey: keyof Omit = 'npcs';
@@ -78,7 +80,7 @@ export class NpcsComponent extends EditorBaseTableComponent {
},
{
field: '',
- width: 200,
+ width: 250,
sortable: false,
suppressMovable: true,
headerComponent: HeaderButtonsComponent,
@@ -89,6 +91,10 @@ export class NpcsComponent extends EditorBaseTableComponent {
cellRenderer: CellButtonsComponent,
cellClass: 'no-adjust',
cellRendererParams: {
+ showPinpointButton: true,
+ pinpointCallback: (item: EditingType) => {
+ this.pinpointService.searchNPC(item.npcId);
+ },
showCopyButton: true,
copyCallback: (item: EditingType) => {
const newItem = structuredClone(item);
diff --git a/src/app/tabs/spawners/spawners.component.ts b/src/app/tabs/spawners/spawners.component.ts
index a053eaa..df538d9 100644
--- a/src/app/tabs/spawners/spawners.component.ts
+++ b/src/app/tabs/spawners/spawners.component.ts
@@ -4,6 +4,7 @@ import { ColDef } from 'ag-grid-community';
import { IModKit, ISpawnerData } from '../../../interfaces';
import { defaultSpawner, id } from '../../helpers';
import { ElectronService } from '../../services/electron.service';
+import { PinpointService } from '../../services/pinpoint.service';
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';
@@ -17,6 +18,7 @@ type EditingType = ISpawnerData;
})
export class SpawnersComponent extends EditorBaseTableComponent {
private electronService = inject(ElectronService);
+ private pinpointService = inject(PinpointService);
protected dataKey: keyof Omit = 'spawners';
@@ -66,7 +68,7 @@ export class SpawnersComponent extends EditorBaseTableComponent {
},
{
field: '',
- width: 200,
+ width: 250,
sortable: false,
suppressMovable: true,
headerComponent: HeaderButtonsComponent,