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"
}
]
}
},
"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 { 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 { MD2MobLevelEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-level-editor/md2-mob-level-editor.component';
@NgModule({
@ -82,7 +83,8 @@ import { MD2MobSkillEditorComponent } from './massive-darkness2/md2-mob-info-mai
MD2MobInfoMaintenanceComponent,
MD2MobInfoEditorComponent,
MD2MobInfoDetailComponent,
MD2MobSkillEditorComponent
MD2MobSkillEditorComponent,
MD2MobLevelEditorComponent
],
imports: [
CommonModule,

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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';
@Component({
@ -10,7 +10,8 @@ import { MD2StateService } from '../../../services/MD2/md2-state.service';
export class MD2IconComponent implements OnInit {
@Input() iconClass: string = 'mr-1';
isImageIcon: boolean = false;
imgUrl: string;
iconHtml: string;
private _icon: string | MD2Icon;
@ -27,7 +28,7 @@ export class MD2IconComponent implements OnInit {
v = MD2Icon[key as keyof typeof MD2Icon];
}
}
this.iconHtml = this.md2StateService.iconHtml(v as MD2Icon);
this.initIcon(v as MD2Icon);
}
if (this.isMD2Icon(v)) {
this.iconName = MD2Icon[v].toLowerCase();
@ -48,20 +49,46 @@ export class MD2IconComponent implements OnInit {
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 {
switch (this.size) {
case 'sm':
return 'g-font-size-18'
return this.isImageIcon ? 'g-width-25 img-fluid' : 'g-font-size-18'
break;
case 'med':
return 'g-font-size-30'
return this.isImageIcon ? 'g-width-35 img-fluid' : 'g-font-size-30'
break;
case 'lg':
return 'g-font-size-50'
return this.isImageIcon ? 'g-width-50 img-fluid' : 'g-font-size-50'
break;
default:
return 'g-font-size-' + this.size;
return this.isImageIcon ? 'g-width-20 img-fluid' : 'g-font-size-' + this.size;
break;
}
}

View File

@ -32,19 +32,12 @@
<kendo-grid #levelsGrid [data]="mobLevelInfos" [loading]="isLoading" [pageSize]="levelsState.take"
[skip]="levelsState.skip" [sortable]="true" [filterable]="true" [pageable]="true" [height]="300"
kendoGridTemplateEditing (edit)="editLevelHandler($event)" (cancel)="cancelLevelHandler($event)"
(save)="saveLevelHandler($event)" (remove)="removeLevelHandler($event)"
(dataStateChange)="levelsState = $event">
(remove)="removeLevelHandler($event)" (dataStateChange)="levelsState = $event">
<kendo-grid-column field="level" title="Level" [width]="50">
<ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.level }}
</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 field="fixedHp" title="HP" [width]="80">
@ -62,11 +55,6 @@
<ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.hpPerHero }}
</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 field="actions" title="Actions" [width]="100">
@ -84,67 +72,37 @@
<ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.rewardTokens }}
</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 field="fixedRareTreasure" title="Rare Treasure" [width]="80">
<ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.fixedRareTreasure }}
</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 field="fixedEpicTreasure" title="Epic Treasure" [width]="80">
<ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.fixedEpicTreasure }}
</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 field="fixedLegendTreasure" title="Legend Treasure" [width]="60">
<ng-template kendoGridCellTemplate let-dataItem>
{{ dataItem.fixedLegendTreasure }}
</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>
<!-- result.defenceInfo.blue
result.defenceInfo.green -->
<kendo-grid-column field="defenceInfo.blue" title="Blue Dice" [width]="60">
<ng-template kendoGridCellTemplate let-dataItem>
{{ 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>
{{ dataItem.defenceInfo?.blue }}
</ng-template>
</kendo-grid-column>
<kendo-grid-command-column title="Actions" [width]="133">
<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 kendoButton (click)="selectLevelInfo(dataItem)" [look]="'flat'">
View Skills
</button> -->
<button kendoGridSaveCommand>Save</button>
<button kendoGridCancelCommand>Cancel</button>
</ng-template>
</kendo-grid-command-column>
</kendo-grid>

View File

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

View File

@ -9,6 +9,7 @@ import { MobSkillType } from '../../massive-darkness2.model.boss';
import { MD2MobLevelInfoService, MD2MobSkillService } from '../../service/massive-darkness2.service';
import { MsgBoxService } from '../../../../services/msg-box.service';
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({
selector: 'ngx-md2-mob-info-detail',
@ -90,28 +91,25 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
public selectLevelInfo(levelInfo: MD2MobLevelInfo): void {
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) {
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.loadSkills();
}
public editLevelHandler({ sender, rowIndex, dataItem }: any): void {
// The template-driven editing directive handles edit mode automatically
// 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 editLevelHandler(dataItem: MD2MobLevelInfo): void {
if (!this.mobInfo) return;
public cancelLevelHandler({ sender, rowIndex }: any): void {
// Cancel editing - template-driven editing handles this automatically
sender.closeRow(rowIndex);
// Create a copy of the level info for editing
const levelCopy: MD2MobLevelInfo = JSON.parse(JSON.stringify(dataItem));
this.openLevelEditor(levelCopy, false);
}
public addLevelHandler(): void {
if (!this.mobInfo || !this.levelsGrid) return;
if (!this.mobInfo) return;
// Get the last level info (highest level) if any exists
const lastLevel = this.mobLevelInfos.length > 0
@ -121,16 +119,8 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
// Calculate the next level number
const nextLevel = lastLevel ? lastLevel.level + 2 : 1;
let rewardTokens = 0;
let actions = 1;
switch (nextLevel) {
case 1:
case 3:
rewardTokens = 1;
break;
case 5:
rewardTokens = 2;
break;
}
let newLevel: MD2MobLevelInfo;
@ -144,15 +134,15 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
fixedRareTreasure: lastLevel.fixedRareTreasure ?? 0,
fixedEpicTreasure: lastLevel.fixedEpicTreasure ?? 0,
fixedLegendTreasure: lastLevel.fixedLegendTreasure ?? 0,
fixedHp: lastLevel.fixedHp ?? 1,
fixedHp: lastLevel.fixedHp ?? 0,
hpPerHero: lastLevel.hpPerHero ?? 0,
actions: lastLevel.actions ?? 1,
actions: lastLevel.actions ?? actions,
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
? { ...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 {
// Default values if no levels exist
@ -164,63 +154,101 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
fixedRareTreasure: 0,
fixedEpicTreasure: 0,
fixedLegendTreasure: 0,
fixedHp: 1,
fixedHp: 0,
hpPerHero: 0,
actions: 1,
attackInfo: { 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 }
actions: actions,
attackInfo: { type: MobSkillType.Attack, 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
this.levelsGrid.addRow(newLevel);
switch (this.mobInfo.type) {
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 {
if (isNew) {
dataItem.id = this.generateLevelId();
dataItem.mobInfoId = this.mobInfo?.id;
}
private openLevelEditor(level: MD2MobLevelInfo, isNew: boolean): void {
if (!this.mobInfo) return;
// 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;
this.mobLevelInfos.push(result);
} else {
// For existing items, update in the array
const index = this.mobLevelInfos.findIndex(l => l.id === result.id);
if (index !== -1) {
this.mobLevelInfos[index] = result;
// Update selected level if it's the one being edited
if (this.selectedLevelInfo?.id === result.id) {
this.selectedLevelInfo = result;
this.loadSkills();
}
}
}
// 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);
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 (!this.mobInfo.mobLevelInfos) {
this.mobInfo.mobLevelInfos = [];
}
result.attackInfo.black = 0;
this.mobLevelInfos.push(result);
} else {
const index = this.mobLevelInfos.findIndex(l => l.id === result.id);
if (index !== -1) {
this.mobLevelInfos[index] = result;
// Update selected level if it's the one being edited
if (this.selectedLevelInfo?.id === result.id) {
this.selectedLevelInfo = result;
this.loadSkills();
}
}
}
}
public removeLevelHandler({ dataItem }: { dataItem: MD2MobLevelInfo }): void {

View File

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

View File

@ -1,6 +1,6 @@
import { Component, OnInit, ViewChild } from '@angular/core';
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 { MD2MobInfo, GameBundle } from '../massive-darkness2.db.model';
import { MobType } from '../massive-darkness2.model';
@ -19,6 +19,7 @@ export class MD2MobInfoMaintenanceComponent implements OnInit {
@ViewChild('grid') grid: GridComponent;
public gridData: GridDataResult = { data: [], total: 0 };
private allData: MD2MobInfo[] = [];
public gridState: State = {
skip: 0,
take: 10,
@ -51,14 +52,30 @@ export class MD2MobInfoMaintenanceComponent implements OnInit {
public loadData(): void {
this.isLoading = true;
this.mobInfoService.getAll().pipe(first()).subscribe(result => {
this.gridData = {
data: result.sort((a, b) => a.name.localeCompare(b.name)),
total: result.length
};
this.allData = result;
this.processGridData();
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 {
const editorData = {} as MD2MobInfo;
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;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-moz-font-feature-settings: "liga", "kern";
text-rendering: optimizelegibility;
background-color: #fff;
overflow-x: hidden;

View File

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