This commit is contained in:
Chris Chen 2025-11-05 08:04:55 -08:00
parent 701c36112c
commit d20f2a37c4
17 changed files with 487 additions and 139 deletions

View File

@ -9,5 +9,10 @@
"image-alt": "off" "image-alt": "off"
} }
] ]
} },
"browserslist": [
"defaults",
"not ie 11",
"not ie <= 11"
]
} }

View File

@ -49,6 +49,7 @@ import { MD2MobInfoMaintenanceComponent } from './massive-darkness2/md2-mob-info
import { MD2MobInfoEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-editor/md2-mob-info-editor.component'; import { MD2MobInfoEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-editor/md2-mob-info-editor.component';
import { MD2MobInfoDetailComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-detail/md2-mob-info-detail.component'; import { MD2MobInfoDetailComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-detail/md2-mob-info-detail.component';
import { MD2MobSkillEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-skill-editor/md2-mob-skill-editor.component'; import { MD2MobSkillEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-skill-editor/md2-mob-skill-editor.component';
import { MD2MobLevelEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-level-editor/md2-mob-level-editor.component';
@NgModule({ @NgModule({
@ -82,7 +83,8 @@ import { MD2MobSkillEditorComponent } from './massive-darkness2/md2-mob-info-mai
MD2MobInfoMaintenanceComponent, MD2MobInfoMaintenanceComponent,
MD2MobInfoEditorComponent, MD2MobInfoEditorComponent,
MD2MobInfoDetailComponent, MD2MobInfoDetailComponent,
MD2MobSkillEditorComponent MD2MobSkillEditorComponent,
MD2MobLevelEditorComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@ -40,6 +40,7 @@ export interface MD2MobLevelInfo {
hpPerHero: number; hpPerHero: number;
actions: number; actions: number;
attackInfo: MD2DiceSet; attackInfo: MD2DiceSet;
alterAttackInfo?: MD2DiceSet;
defenceInfo: MD2DiceSet; defenceInfo: MD2DiceSet;
} }
@ -59,6 +60,7 @@ export interface MD2MobSkill {
} }
export interface MD2DiceSet { export interface MD2DiceSet {
type: MobSkillType;
yellow: number | null; yellow: number | null;
orange: number | null; orange: number | null;
red: number | null; red: number | null;

View File

@ -15,7 +15,10 @@ export enum MobSkillType {
Combat, Combat,
Passive, Passive,
ConditionalSkill, ConditionalSkill,
ActiveSkill ActiveSkill,
MeleeAttack = 15,
RangeAttack,
MagicAttack,
} }
export class MobSkill { export class MobSkill {
constructor(config: Partial<MobSkill> = {}) { constructor(config: Partial<MobSkill> = {}) {

View File

@ -23,6 +23,7 @@ export enum RoundPhase {
BossActivation BossActivation
} }
export enum TreasureType { export enum TreasureType {
Cover,
Common, Common,
Rare, Rare,
Epic, Epic,
@ -72,9 +73,17 @@ export enum MD2Icon {
Rage, Rage,
RedDice, RedDice,
BlueDice, BlueDice,
GreenDice,
YellowDice, YellowDice,
OrangeDice, OrangeDice,
BlackDice BlackDice,
//Below are image based icons
TreasureToken = 300,
TreasureToken_Common,
TreasureToken_Rare,
TreasureToken_Epic,
TreasureToken_Legendary,
} }
export enum AttackTarget { export enum AttackTarget {
Random = 40, Random = 40,

View File

@ -1 +1,5 @@
@if(isImageIcon) {
<img [src]="imgUrl" class="{{sizeClass}} mx-2">
} @else {
<span [innerHtml]="iconHtml" class="{{sizeClass}} mx-2"></span> <span [innerHtml]="iconHtml" class="{{sizeClass}} mx-2"></span>
}

View File

@ -1,5 +1,5 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { MD2Icon } from '../massive-darkness2.model'; import { MD2Icon, TreasureType } from '../massive-darkness2.model';
import { MD2StateService } from '../../../services/MD2/md2-state.service'; import { MD2StateService } from '../../../services/MD2/md2-state.service';
@Component({ @Component({
@ -10,7 +10,8 @@ import { MD2StateService } from '../../../services/MD2/md2-state.service';
export class MD2IconComponent implements OnInit { export class MD2IconComponent implements OnInit {
@Input() iconClass: string = 'mr-1'; @Input() iconClass: string = 'mr-1';
isImageIcon: boolean = false;
imgUrl: string;
iconHtml: string; iconHtml: string;
private _icon: string | MD2Icon; private _icon: string | MD2Icon;
@ -27,7 +28,7 @@ export class MD2IconComponent implements OnInit {
v = MD2Icon[key as keyof typeof MD2Icon]; v = MD2Icon[key as keyof typeof MD2Icon];
} }
} }
this.iconHtml = this.md2StateService.iconHtml(v as MD2Icon); this.initIcon(v as MD2Icon);
} }
if (this.isMD2Icon(v)) { if (this.isMD2Icon(v)) {
this.iconName = MD2Icon[v].toLowerCase(); this.iconName = MD2Icon[v].toLowerCase();
@ -48,20 +49,46 @@ export class MD2IconComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
} }
private initIcon(icon: MD2Icon): void {
if (icon < MD2Icon.TreasureToken) {
this.isImageIcon = false;
this.iconHtml = this.md2StateService.iconHtml(icon);
} else {
this.isImageIcon = true;
switch (icon) {
case MD2Icon.TreasureToken:
this.imgUrl = this.md2StateService.treasureImage(TreasureType.Cover);
break;
case MD2Icon.TreasureToken_Common:
this.imgUrl = this.md2StateService.treasureImageHtml(TreasureType.Common);
break;
case MD2Icon.TreasureToken_Rare:
this.imgUrl = this.md2StateService.treasureImage(TreasureType.Rare);
break;
case MD2Icon.TreasureToken_Epic:
this.imgUrl = this.md2StateService.treasureImage(TreasureType.Epic);
break;
case MD2Icon.TreasureToken_Legendary:
this.imgUrl = this.md2StateService.treasureImage(TreasureType.Legendary);
break;
}
}
}
public get sizeClass(): string { public get sizeClass(): string {
switch (this.size) { switch (this.size) {
case 'sm': case 'sm':
return 'g-font-size-18' return this.isImageIcon ? 'g-width-25 img-fluid' : 'g-font-size-18'
break; break;
case 'med': case 'med':
return 'g-font-size-30' return this.isImageIcon ? 'g-width-35 img-fluid' : 'g-font-size-30'
break; break;
case 'lg': case 'lg':
return 'g-font-size-50' return this.isImageIcon ? 'g-width-50 img-fluid' : 'g-font-size-50'
break; break;
default: default:
return 'g-font-size-' + this.size; return this.isImageIcon ? 'g-width-20 img-fluid' : 'g-font-size-' + this.size;
break; break;
} }
} }

View File

@ -32,19 +32,12 @@
<kendo-grid #levelsGrid [data]="mobLevelInfos" [loading]="isLoading" [pageSize]="levelsState.take" <kendo-grid #levelsGrid [data]="mobLevelInfos" [loading]="isLoading" [pageSize]="levelsState.take"
[skip]="levelsState.skip" [sortable]="true" [filterable]="true" [pageable]="true" [height]="300" [skip]="levelsState.skip" [sortable]="true" [filterable]="true" [pageable]="true" [height]="300"
kendoGridTemplateEditing (edit)="editLevelHandler($event)" (cancel)="cancelLevelHandler($event)" (remove)="removeLevelHandler($event)" (dataStateChange)="levelsState = $event">
(save)="saveLevelHandler($event)" (remove)="removeLevelHandler($event)"
(dataStateChange)="levelsState = $event">
<kendo-grid-column field="level" title="Level" [width]="50"> <kendo-grid-column field="level" title="Level" [width]="50">
<ng-template kendoGridCellTemplate let-dataItem> <ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.level }} {{ dataItem.level }}
</ng-template> </ng-template>
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
<kendo-numerictextbox [(ngModel)]="dataItem.level" [name]="'level_' + rowIndex" [min]="1" [decimals]="0"
[format]="'n0'">
</kendo-numerictextbox>
</ng-template>
</kendo-grid-column> </kendo-grid-column>
<!-- <kendo-grid-column field="fixedHp" title="HP" [width]="80"> <!-- <kendo-grid-column field="fixedHp" title="HP" [width]="80">
@ -62,11 +55,6 @@
<ng-template kendoGridCellTemplate let-dataItem> <ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.hpPerHero }} {{ dataItem.hpPerHero }}
</ng-template> </ng-template>
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
<kendo-numerictextbox [(ngModel)]="dataItem.hpPerHero" [name]="'hpPerHero_' + rowIndex" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</ng-template>
</kendo-grid-column> </kendo-grid-column>
<!-- <kendo-grid-column field="actions" title="Actions" [width]="100"> <!-- <kendo-grid-column field="actions" title="Actions" [width]="100">
@ -84,67 +72,37 @@
<ng-template kendoGridCellTemplate let-dataItem> <ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.rewardTokens }} {{ dataItem.rewardTokens }}
</ng-template> </ng-template>
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
<kendo-numerictextbox [(ngModel)]="dataItem.rewardTokens" [name]="'rewardTokens_' + rowIndex" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</ng-template>
</kendo-grid-column> </kendo-grid-column>
<kendo-grid-column field="fixedRareTreasure" title="Rare Treasure" [width]="80"> <kendo-grid-column field="fixedRareTreasure" title="Rare Treasure" [width]="80">
<ng-template kendoGridCellTemplate let-dataItem> <ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.fixedRareTreasure }} {{ dataItem.fixedRareTreasure }}
</ng-template> </ng-template>
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
<kendo-numerictextbox [(ngModel)]="dataItem.fixedRareTreasure" [name]="'fixedRareTreasure_' + rowIndex"
[min]="0" [decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</ng-template>
</kendo-grid-column> </kendo-grid-column>
<kendo-grid-column field="fixedEpicTreasure" title="Epic Treasure" [width]="80"> <kendo-grid-column field="fixedEpicTreasure" title="Epic Treasure" [width]="80">
<ng-template kendoGridCellTemplate let-dataItem> <ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.fixedEpicTreasure }} {{ dataItem.fixedEpicTreasure }}
</ng-template> </ng-template>
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
<kendo-numerictextbox [(ngModel)]="dataItem.fixedEpicTreasure" [name]="'fixedEpicTreasure_' + rowIndex"
[min]="0" [decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</ng-template>
</kendo-grid-column> </kendo-grid-column>
<kendo-grid-column field="fixedLegendTreasure" title="Legend Treasure" [width]="60"> <kendo-grid-column field="fixedLegendTreasure" title="Legend Treasure" [width]="60">
<ng-template kendoGridCellTemplate let-dataItem> <ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.fixedLegendTreasure }} {{ dataItem.fixedLegendTreasure }}
</ng-template> </ng-template>
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
<kendo-numerictextbox [(ngModel)]="dataItem.fixedLegendTreasure"
[name]="'fixedLegendTreasure_' + rowIndex" [min]="0" [decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</ng-template>
</kendo-grid-column> </kendo-grid-column>
<!-- result.defenceInfo.blue <!-- result.defenceInfo.blue
result.defenceInfo.green --> result.defenceInfo.green -->
<kendo-grid-column field="defenceInfo.blue" title="Blue Dice" [width]="60"> <kendo-grid-column field="defenceInfo.blue" title="Blue Dice" [width]="60">
<ng-template kendoGridCellTemplate let-dataItem> <ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.defenceInfo.blue }} {{ dataItem.defenceInfo?.blue }}
</ng-template>
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
<kendo-numerictextbox [(ngModel)]="dataItem.defenceInfo.blue" [name]="'defenceInfo.blue_' + rowIndex"
[min]="0" [decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</ng-template> </ng-template>
</kendo-grid-column> </kendo-grid-column>
<kendo-grid-command-column title="Actions" [width]="133"> <kendo-grid-command-column title="Actions" [width]="133">
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex"> <ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex">
<button kendoGridEditCommand [primary]="true">Edit</button> <button kendoButton [primary]="true" (click)="editLevelHandler(dataItem)">Edit</button>
<button kendoGridRemoveCommand>Remove</button> <button kendoGridRemoveCommand>Remove</button>
<!-- <button kendoButton (click)="selectLevelInfo(dataItem)" [look]="'flat'">
View Skills
</button> -->
<button kendoGridSaveCommand>Save</button>
<button kendoGridCancelCommand>Cancel</button>
</ng-template> </ng-template>
</kendo-grid-command-column> </kendo-grid-command-column>
</kendo-grid> </kendo-grid>

View File

@ -20,7 +20,7 @@
} }
.info-grid { .info-grid {
display: grid; display: grd;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 10px; gap: 10px;
} }

View File

@ -9,6 +9,7 @@ import { MobSkillType } from '../../massive-darkness2.model.boss';
import { MD2MobLevelInfoService, MD2MobSkillService } from '../../service/massive-darkness2.service'; import { MD2MobLevelInfoService, MD2MobSkillService } from '../../service/massive-darkness2.service';
import { MsgBoxService } from '../../../../services/msg-box.service'; import { MsgBoxService } from '../../../../services/msg-box.service';
import { MD2MobSkillEditorComponent } from '../md2-mob-skill-editor/md2-mob-skill-editor.component'; import { MD2MobSkillEditorComponent } from '../md2-mob-skill-editor/md2-mob-skill-editor.component';
import { MD2MobLevelEditorComponent } from '../md2-mob-level-editor/md2-mob-level-editor.component';
@Component({ @Component({
selector: 'ngx-md2-mob-info-detail', selector: 'ngx-md2-mob-info-detail',
@ -90,28 +91,25 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
public selectLevelInfo(levelInfo: MD2MobLevelInfo): void { public selectLevelInfo(levelInfo: MD2MobLevelInfo): void {
if (levelInfo.defenceInfo == null) { if (levelInfo.defenceInfo == null) {
levelInfo.defenceInfo = { blue: 0, green: 0, yellow: 0, orange: 0, red: 0, black: 0 }; levelInfo.defenceInfo = { type: MobSkillType.Defense, blue: 0, green: 0, yellow: 0, orange: 0, red: 0, black: 0 };
} }
if (levelInfo.attackInfo == null) { if (levelInfo.attackInfo == null) {
levelInfo.attackInfo = { blue: 0, green: 0, yellow: 0, orange: 0, red: 0, black: 0 }; levelInfo.attackInfo = { type: MobSkillType.Attack, blue: 0, green: 0, yellow: 0, orange: 0, red: 0, black: 0 };
} }
this.selectedLevelInfo = levelInfo; this.selectedLevelInfo = levelInfo;
this.loadSkills(); this.loadSkills();
} }
public editLevelHandler({ sender, rowIndex, dataItem }: any): void { public editLevelHandler(dataItem: MD2MobLevelInfo): void {
// The template-driven editing directive handles edit mode automatically if (!this.mobInfo) return;
// This handler is here to ensure the grid enters edit mode
// The grid should already be in edit mode from the directive, but we ensure it
}
public cancelLevelHandler({ sender, rowIndex }: any): void { // Create a copy of the level info for editing
// Cancel editing - template-driven editing handles this automatically const levelCopy: MD2MobLevelInfo = JSON.parse(JSON.stringify(dataItem));
sender.closeRow(rowIndex); this.openLevelEditor(levelCopy, false);
} }
public addLevelHandler(): void { public addLevelHandler(): void {
if (!this.mobInfo || !this.levelsGrid) return; if (!this.mobInfo) return;
// Get the last level info (highest level) if any exists // Get the last level info (highest level) if any exists
const lastLevel = this.mobLevelInfos.length > 0 const lastLevel = this.mobLevelInfos.length > 0
@ -121,16 +119,8 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
// Calculate the next level number // Calculate the next level number
const nextLevel = lastLevel ? lastLevel.level + 2 : 1; const nextLevel = lastLevel ? lastLevel.level + 2 : 1;
let rewardTokens = 0; let rewardTokens = 0;
let actions = 1;
switch (nextLevel) {
case 1:
case 3:
rewardTokens = 1;
break;
case 5:
rewardTokens = 2;
break;
}
let newLevel: MD2MobLevelInfo; let newLevel: MD2MobLevelInfo;
@ -144,15 +134,15 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
fixedRareTreasure: lastLevel.fixedRareTreasure ?? 0, fixedRareTreasure: lastLevel.fixedRareTreasure ?? 0,
fixedEpicTreasure: lastLevel.fixedEpicTreasure ?? 0, fixedEpicTreasure: lastLevel.fixedEpicTreasure ?? 0,
fixedLegendTreasure: lastLevel.fixedLegendTreasure ?? 0, fixedLegendTreasure: lastLevel.fixedLegendTreasure ?? 0,
fixedHp: lastLevel.fixedHp ?? 1, fixedHp: lastLevel.fixedHp ?? 0,
hpPerHero: lastLevel.hpPerHero ?? 0, hpPerHero: lastLevel.hpPerHero ?? 0,
actions: lastLevel.actions ?? 1, actions: lastLevel.actions ?? actions,
attackInfo: lastLevel.attackInfo attackInfo: lastLevel.attackInfo
? { ...lastLevel.attackInfo } ? { ...lastLevel.attackInfo }
: { yellow: null, orange: null, red: null, blue: null, green: null, black: null }, : { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
defenceInfo: lastLevel.defenceInfo defenceInfo: lastLevel.defenceInfo
? { ...lastLevel.defenceInfo } ? { ...lastLevel.defenceInfo }
: { yellow: null, orange: null, red: null, blue: null, green: null, black: null } : { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
}; };
} else { } else {
// Default values if no levels exist // Default values if no levels exist
@ -164,42 +154,91 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
fixedRareTreasure: 0, fixedRareTreasure: 0,
fixedEpicTreasure: 0, fixedEpicTreasure: 0,
fixedLegendTreasure: 0, fixedLegendTreasure: 0,
fixedHp: 1, fixedHp: 0,
hpPerHero: 0, hpPerHero: 0,
actions: 1, actions: actions,
attackInfo: { yellow: null, orange: null, red: null, blue: null, green: null, black: null }, attackInfo: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
defenceInfo: { yellow: null, orange: null, red: null, blue: null, green: null, black: null } defenceInfo: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
}; };
} }
// Use grid's addRow method to properly add a new row in edit mode switch (this.mobInfo.type) {
this.levelsGrid.addRow(newLevel); case MobType.Mob:
newLevel.actions = 2;
switch (nextLevel) {
case 1:
case 3:
newLevel.rewardTokens = 1;
break;
case 5:
newLevel.rewardTokens = 2;
break;
}
break;
case MobType.Boss:
newLevel.actions = 1;
break;
case MobType.RoamingMonster:
newLevel.actions = 1;
switch (nextLevel) {
case 1:
newLevel.rewardTokens = 2;
newLevel.fixedRareTreasure = 1;
break;
case 3:
newLevel.rewardTokens = 2;
newLevel.fixedEpicTreasure = 1;
break;
case 5:
newLevel.rewardTokens = 0;
newLevel.fixedEpicTreasure = 3;
break;
}
break;
}
this.openLevelEditor(newLevel, true);
} }
public saveLevelHandler({ dataItem, isNew }: any): void { private openLevelEditor(level: MD2MobLevelInfo, isNew: boolean): void {
if (!this.mobInfo) return;
const dialogRef = this.dialogService.open({
title: isNew ? 'Add New Level' : 'Edit Level',
content: MD2MobLevelEditorComponent,
width: '80vw',
height: 700
});
const editor = dialogRef.content.instance as MD2MobLevelEditorComponent;
editor.isAdding = isNew;
editor.data = level;
editor.mobInfoId = this.mobInfo.id;
editor.mobType = this.mobInfo.type;
// Force model re-initialization after data is set
setTimeout(() => {
editor.initializeModel();
}, 0);
dialogRef.result.subscribe(result => {
if (result && typeof result === 'object' && 'id' in result) {
this.handleLevelSaved(result as MD2MobLevelInfo, isNew);
}
});
}
private handleLevelSaved(result: MD2MobLevelInfo, isNew: boolean): void {
if (!this.mobInfo) return;
if (isNew) { if (isNew) {
dataItem.id = this.generateLevelId(); if (!this.mobInfo.mobLevelInfos) {
dataItem.mobInfoId = this.mobInfo?.id; this.mobInfo.mobLevelInfos = [];
} }
// Ensure required objects exist
if (!dataItem.attackInfo) {
dataItem.attackInfo = { yellow: null, orange: null, red: null, blue: null, green: null, black: null };
}
if (!dataItem.defenceInfo) {
dataItem.defenceInfo = { yellow: null, orange: null, red: null, blue: null, green: null, black: null };
}
this.isLoading = true;
this.mobLevelInfoService.createOrUpdate(dataItem).pipe(first()).subscribe(result => {
this.isLoading = false;
if (isNew) {
// For new items, add to the array
result.attackInfo.black = 0; result.attackInfo.black = 0;
this.mobLevelInfos.push(result); this.mobLevelInfos.push(result);
} else { } else {
// For existing items, update in the array
const index = this.mobLevelInfos.findIndex(l => l.id === result.id); const index = this.mobLevelInfos.findIndex(l => l.id === result.id);
if (index !== -1) { if (index !== -1) {
this.mobLevelInfos[index] = result; this.mobLevelInfos[index] = result;
@ -210,17 +249,6 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
} }
} }
} }
// Close the edit row after saving
if (this.levelsGrid) {
const rowIndex = this.mobLevelInfos.findIndex(l => l.id === result.id);
if (rowIndex !== -1) {
this.levelsGrid.closeRow(rowIndex);
}
}
}, error => {
this.isLoading = false;
console.error('Error saving level info:', error);
});
} }
public removeLevelHandler({ dataItem }: { dataItem: MD2MobLevelInfo }): void { public removeLevelHandler({ dataItem }: { dataItem: MD2MobLevelInfo }): void {

View File

@ -9,8 +9,9 @@
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
<kendo-grid #grid [data]="gridData" [loading]="isLoading" [pageSize]="gridState.take" [skip]="gridState.skip" <kendo-grid #grid [data]="gridData" [loading]="isLoading" [pageSize]="gridState.take" [skip]="gridState.skip"
[group]="gridState.group" [sortable]="true" [filterable]="true" [pageable]="true" [selectable]="true" [group]="gridState.group" [filter]="gridState.filter" [sort]="gridState.sort" [sortable]="true"
[groupable]="true" (dataStateChange)="gridState = $event; loadData()"> [filterable]="true" [pageable]="true" [selectable]="true" [groupable]="true"
(dataStateChange)="gridState = $event; processGridData()">
<kendo-grid-toolbar> <kendo-grid-toolbar>
<button kendoGridAddCommand>Add new</button> <button kendoGridAddCommand>Add new</button>

View File

@ -1,6 +1,6 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid'; import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
import { State } from '@progress/kendo-data-query'; import { State, process } from '@progress/kendo-data-query';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { MD2MobInfo, GameBundle } from '../massive-darkness2.db.model'; import { MD2MobInfo, GameBundle } from '../massive-darkness2.db.model';
import { MobType } from '../massive-darkness2.model'; import { MobType } from '../massive-darkness2.model';
@ -19,6 +19,7 @@ export class MD2MobInfoMaintenanceComponent implements OnInit {
@ViewChild('grid') grid: GridComponent; @ViewChild('grid') grid: GridComponent;
public gridData: GridDataResult = { data: [], total: 0 }; public gridData: GridDataResult = { data: [], total: 0 };
private allData: MD2MobInfo[] = [];
public gridState: State = { public gridState: State = {
skip: 0, skip: 0,
take: 10, take: 10,
@ -51,14 +52,30 @@ export class MD2MobInfoMaintenanceComponent implements OnInit {
public loadData(): void { public loadData(): void {
this.isLoading = true; this.isLoading = true;
this.mobInfoService.getAll().pipe(first()).subscribe(result => { this.mobInfoService.getAll().pipe(first()).subscribe(result => {
this.gridData = { this.allData = result;
data: result.sort((a, b) => a.name.localeCompare(b.name)), this.processGridData();
total: result.length
};
this.isLoading = false; this.isLoading = false;
}); });
} }
public processGridData(): void {
// Normalize filter state to handle null/undefined/empty filters
let normalizedFilter: { logic: 'and' | 'or'; filters: any[] } = { logic: 'and', filters: [] };
if (this.gridState.filter) {
const filters = this.gridState.filter.filters || [];
if (filters.length > 0) {
normalizedFilter = this.gridState.filter;
}
}
const normalizedState: State = {
...this.gridState,
filter: normalizedFilter
};
this.gridData = process(this.allData, normalizedState);
}
public addHandler(): void { public addHandler(): void {
const editorData = {} as MD2MobInfo; const editorData = {} as MD2MobInfo;
const dialogRef = this.dialogService.open({ const dialogRef = this.dialogService.open({

View File

@ -0,0 +1,168 @@
<div class="k-dialog-content">
<form #form="ngForm" class="k-form">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label class="k-label">Level *</label>
<kendo-numerictextbox [(ngModel)]="model.level" name="level" [min]="1" [decimals]="0"
[format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.HP"></md2-icon>
{{mobType==MobType.Mob?'HP/Unit':'HP/Hero'}}
</label>
<kendo-numerictextbox [(ngModel)]="model.hpPerHero" name="hpPerHero" [min]="0" [decimals]="0"
[format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.TreasureToken"></md2-icon>Reward Tokens</label>
<kendo-numerictextbox [(ngModel)]="model.rewardTokens" name="rewardTokens" [min]="0" [decimals]="0"
[format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">Fixed HP</label>
<kendo-numerictextbox [(ngModel)]="model.fixedHp" name="fixedHp" [min]="0" [decimals]="0"
[format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.TreasureToken_Rare"></md2-icon>Rare Treasure</label>
<kendo-numerictextbox [(ngModel)]="model.fixedRareTreasure" name="fixedRareTreasure" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.TreasureToken_Epic"></md2-icon>Epic Treasure</label>
<kendo-numerictextbox [(ngModel)]="model.fixedEpicTreasure" name="fixedEpicTreasure" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.TreasureToken_Legendary"></md2-icon>Legend Treasure</label>
<kendo-numerictextbox [(ngModel)]="model.fixedLegendTreasure" name="fixedLegendTreasure" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">Actions</label>
<kendo-numerictextbox [(ngModel)]="model.actions" name="actions" [min]="0" [decimals]="0"
[format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h5>Defense Info</h5>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.BlueDice"></md2-icon>Blue Dice</label>
<kendo-numerictextbox [(ngModel)]="model.defenceInfo.blue" name="defenceInfo.blue" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.GreenDice"></md2-icon>Green Dice</label>
<kendo-numerictextbox [(ngModel)]="model.defenceInfo.green" name="defenceInfo.green" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.BlackDice"></md2-icon>Black Dice</label>
<kendo-numerictextbox [(ngModel)]="model.defenceInfo.black" name="defenceInfo.black" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
</div>
<div class="row" *ngIf="mobType!=MobType.Mob">
<div class="col-md-12">
<h5>Attack Info</h5>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.YellowDice"></md2-icon>Yellow Dice</label>
<kendo-numerictextbox [(ngModel)]="model.attackInfo.yellow" name="attackInfo.yellow" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.OrangeDice"></md2-icon>Orange Dice</label>
<kendo-numerictextbox [(ngModel)]="model.attackInfo.orange" name="attackInfo.orange" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.RedDice"></md2-icon>Red Dice</label>
<kendo-numerictextbox [(ngModel)]="model.attackInfo.red" name="attackInfo.red" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.BlackDice"></md2-icon>Black Dice</label>
<kendo-numerictextbox [(ngModel)]="model.attackInfo.black" name="attackInfo.black" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
</div>
</form>
</div>
<kendo-dialog-actions>
<button kendoButton (click)="close()">Cancel</button>
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
{{ processing ? 'Saving...' : 'Save' }}
</button>
</kendo-dialog-actions>

View File

@ -0,0 +1,22 @@
.k-dialog-content {
padding: 20px;
}
.k-form {
.form-group {
margin-bottom: 15px;
}
.k-label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
h5 {
margin-top: 20px;
margin-bottom: 15px;
font-weight: 600;
}
}

View File

@ -0,0 +1,94 @@
import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core';
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
import { first } from 'rxjs/operators';
import { MD2MobLevelInfo } from '../../massive-darkness2.db.model';
import { MobSkillType } from '../../massive-darkness2.model.boss';
import { MD2MobLevelInfoService } from '../../service/massive-darkness2.service';
import { MD2Icon, MobType } from '../../massive-darkness2.model';
@Component({
selector: 'ngx-md2-mob-level-editor',
templateUrl: './md2-mob-level-editor.component.html',
styleUrls: ['./md2-mob-level-editor.component.scss']
})
export class MD2MobLevelEditorComponent extends DialogContentBase implements OnInit {
MobType = MobType;
MD2Icon = MD2Icon;
MobSkillType = MobSkillType;
@Input() public data: MD2MobLevelInfo;
@Input() public mobType: MobType;
@Input() public mobInfoId: string;
@Input() public isAdding: boolean = false;
public model: MD2MobLevelInfo;
public processing: boolean = false;
constructor(
public dialog: DialogRef,
private mobLevelInfoService: MD2MobLevelInfoService,
private cdr: ChangeDetectorRef
) {
super(dialog);
}
ngOnInit(): void {
this.initializeModel();
}
public initializeModel(): void {
this.model = {
id: this.data?.id || '',
level: this.data?.level ?? 1,
mobInfoId: this.mobInfoId || this.data?.mobInfoId || '',
rewardTokens: this.data?.rewardTokens ?? 0,
fixedRareTreasure: this.data?.fixedRareTreasure ?? 0,
fixedEpicTreasure: this.data?.fixedEpicTreasure ?? 0,
fixedLegendTreasure: this.data?.fixedLegendTreasure ?? 0,
fixedHp: this.data?.fixedHp ?? 1,
hpPerHero: this.data?.hpPerHero ?? 0,
actions: this.data?.actions ?? 1,
attackInfo: this.data?.attackInfo
? { ...this.data.attackInfo }
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
defenceInfo: this.data?.defenceInfo
? { ...this.data.defenceInfo }
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
};
this.cdr.detectChanges();
}
public close(): void {
this.dialog.close();
}
public save(): void {
if (!this.processing) {
this.processing = true;
// Ensure required objects exist
if (!this.model.attackInfo) {
this.model.attackInfo = { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
}
if (!this.model.defenceInfo) {
this.model.defenceInfo = { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
}
this.mobLevelInfoService.createOrUpdate(this.model).pipe(first()).subscribe(result => {
this.processing = false;
this.dialog.close(result);
}, error => {
this.processing = false;
console.error('Error saving mob level info:', error);
});
}
}
public get isValid(): boolean {
if (!this.model) {
return false;
}
return this.model.level > 0 && this.model.mobInfoId !== '';
}
}

View File

@ -15,7 +15,6 @@ body {
color: #7d7d8f; color: #7d7d8f;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-moz-font-feature-settings: "liga", "kern";
text-rendering: optimizelegibility; text-rendering: optimizelegibility;
background-color: #fff; background-color: #fff;
overflow-x: hidden; overflow-x: hidden;

View File

@ -128,6 +128,12 @@
} }
color: #ffc107 !important; color: #ffc107 !important;
} }
&.diceGreen {
&::before {
content: "U";
}
color: #3df33d !important;
}
&.diceOrange { &.diceOrange {
&::before { &::before {
content: "U"; content: "U";
@ -163,6 +169,9 @@
&.Red { &.Red {
color: crimson !important; color: crimson !important;
} }
&.Green {
color: #3df33d !important;
}
&::before { &::before {
content: "U"; content: "U";
} }