Compare commits
19 Commits
fd32ae5dcc
...
b41c01e6f7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b41c01e6f7 | ||
|
|
9ad991a70e | ||
|
|
6806eeff8a | ||
|
|
3c3c880a3c | ||
|
|
542f24c12d | ||
|
|
23e6da2808 | ||
|
|
61604355c1 | ||
|
|
b4d52283aa | ||
|
|
b24753afe7 | ||
|
|
89cb09adb6 | ||
|
|
716e25f0ba | ||
|
|
d20f2a37c4 | ||
|
|
701c36112c | ||
|
|
e5933104cc | ||
|
|
46ec236ed5 | ||
|
|
b8b35645ac | ||
|
|
ed3c116d13 | ||
|
|
719108fd6a | ||
|
|
f88cd21b33 |
18
.hintrc
Normal file
18
.hintrc
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": [
|
||||
"development"
|
||||
],
|
||||
"hints": {
|
||||
"axe/text-alternatives": [
|
||||
"default",
|
||||
{
|
||||
"image-alt": "off"
|
||||
}
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults",
|
||||
"not ie 11",
|
||||
"not ie <= 11"
|
||||
]
|
||||
}
|
||||
5819
package-lock.json
generated
5819
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,8 +13,8 @@
|
||||
"ng": "ng",
|
||||
"conventional-changelog": "conventional-changelog",
|
||||
"start": "ng serve --host=127.0.0.1",
|
||||
"build": "ng build --output-path \\\\ArkNAS\\docker\\nginx-proxy\\data\\ChurchAngular --configuration production",
|
||||
"build:prod": "npm run build -- --configuration production --aot",
|
||||
"build": "ng build",
|
||||
"build:prod": "ng build --output-path \\\\ArkNAS\\docker\\nginx-proxy\\data\\ChurchAngular --configuration production --aot",
|
||||
"test": "ng test",
|
||||
"test:coverage": "rimraf coverage && npm run test -- --code-coverage",
|
||||
"lint": "ng lint",
|
||||
@ -47,7 +47,10 @@
|
||||
"@nebular/theme": "13.0.0",
|
||||
"@progress/kendo-angular-buttons": "^20.1.1",
|
||||
"@progress/kendo-angular-dialog": "^20.1.1",
|
||||
"@progress/kendo-angular-dropdowns": "^20.1.1",
|
||||
"@progress/kendo-angular-editor": "^20.1.1",
|
||||
"@progress/kendo-angular-grid": "^20.1.1",
|
||||
"@progress/kendo-angular-inputs": "^20.1.1",
|
||||
"@progress/kendo-angular-toolbar": "^20.1.1",
|
||||
"@progress/kendo-licensing": "^1.7.1",
|
||||
"@progress/kendo-svg-icons": "^4.5.0",
|
||||
@ -89,6 +92,7 @@
|
||||
"@angular/cli": "^17.3.3",
|
||||
"@angular/compiler-cli": "^17.3.3",
|
||||
"@angular/language-service": "17.3.3",
|
||||
"@angular/localize": "^17.3.3",
|
||||
"@compodoc/compodoc": "1.0.1",
|
||||
"@fortawesome/fontawesome-free": "^5.2.0",
|
||||
"@schematics/angular": "^14.1.3",
|
||||
|
||||
@ -3,14 +3,12 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { NbDialogService } from '@nebular/theme';
|
||||
import { Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { inherits } from 'util';
|
||||
import { CellGroupRoutineEvents } from '../../entity/CellGroupRoutineEvents';
|
||||
import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service';
|
||||
import { MsgBoxService } from '../../services/msg-box.service';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { FancySettings } from '../../ui/fancy-table/fancy-settings.model';
|
||||
import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component';
|
||||
import { ObjectUtils } from '../../utilities/object-utils';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-cell-group-routine-events',
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { basename } from "path";
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { ScreenBase } from "../ScreenBase";
|
||||
import { ICrudService } from "../services/crudServices/crud.service";
|
||||
import { PastoralDomainService } from "../services/crudServices/pastoral-domain.service";
|
||||
import { StateService } from "../services/state.service";
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@ -5,6 +5,8 @@ import { AvalonComponent } from './avalon/avalon.component';
|
||||
import { GamesComponent } from './games.component';
|
||||
import { HeroDashboardComponent } from './massive-darkness2/hero-dashboard/hero-dashboard.component';
|
||||
import { MassiveDarkness2Component } from './massive-darkness2/massive-darkness2.component';
|
||||
import { MD2MobInfoMaintenanceComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component';
|
||||
import { MD2HeroProfileMaintenanceComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-maintenance.component';
|
||||
export class GameRoomMenuConfig {
|
||||
public static HostMenu: NbMenuItem[] = [
|
||||
|
||||
@ -47,6 +49,8 @@ const routes: Routes = [
|
||||
{ path: 'avalonHost', component: AvalonComponent },
|
||||
{ path: 'MD2', component: MassiveDarkness2Component },
|
||||
{ path: 'MD2_Hero/:roomId', component: HeroDashboardComponent },
|
||||
{ path: 'MD2MobInfo', component: MD2MobInfoMaintenanceComponent },
|
||||
{ path: 'MD2HeroProfile', component: MD2HeroProfileMaintenanceComponent },
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@ -3,4 +3,6 @@
|
||||
<router-outlet></router-outlet>
|
||||
</ngx-plain-layout>
|
||||
|
||||
<div kendoDialogContainer></div>
|
||||
|
||||
<!-- ngx-plain-layout ngx-one-column-layout-->
|
||||
@ -39,6 +39,19 @@ import { MD2IconPickerDlgComponent } from './massive-darkness2/md2-html-editor/m
|
||||
import { EditorModule as KendoEditorModule } from '@progress/kendo-angular-editor';
|
||||
import { ToolBarModule } from '@progress/kendo-angular-toolbar';
|
||||
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
||||
import { GridModule } from '@progress/kendo-angular-grid';
|
||||
import { DialogModule } from '@progress/kendo-angular-dialog';
|
||||
import { InputsModule } from '@progress/kendo-angular-inputs';
|
||||
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
|
||||
import { LayoutModule } from '@progress/kendo-angular-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MD2MobInfoMaintenanceComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.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 { 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';
|
||||
import { MD2HeroProfileMaintenanceComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-maintenance.component';
|
||||
import { MD2HeroProfileEditorComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-editor/md2-hero-profile-editor.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@ -68,12 +81,20 @@ import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
||||
MobCombatInfoComponent,
|
||||
MobStandInfoComponent,
|
||||
MD2HtmlEditorComponent,
|
||||
MD2IconPickerDlgComponent
|
||||
MD2IconPickerDlgComponent,
|
||||
MD2MobInfoMaintenanceComponent,
|
||||
MD2MobInfoEditorComponent,
|
||||
MD2MobInfoDetailComponent,
|
||||
MD2MobSkillEditorComponent,
|
||||
MD2MobLevelEditorComponent,
|
||||
MD2HeroProfileMaintenanceComponent,
|
||||
MD2HeroProfileEditorComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
GamesRoutingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
AdminRoutingModule,
|
||||
ThemeModule,
|
||||
NbMenuModule,
|
||||
@ -103,7 +124,12 @@ import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
||||
EditorModule,
|
||||
KendoEditorModule,
|
||||
ToolBarModule,
|
||||
ButtonsModule
|
||||
ButtonsModule,
|
||||
GridModule,
|
||||
DialogModule,
|
||||
InputsModule,
|
||||
DropDownsModule,
|
||||
LayoutModule
|
||||
]
|
||||
})
|
||||
export class GamesModule { }
|
||||
|
||||
@ -1,222 +0,0 @@
|
||||
import { environment } from "../../../../../environments/environment";
|
||||
import { DefenseInfo, IMobFactory, MD2Icon, MobInfo } from "../../massive-darkness2.model";
|
||||
import { MobSkill, MobSkillType } from "../../massive-darkness2.model.boss";
|
||||
|
||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }
|
||||
const CORE_GAME_MOB_LEVEL = [
|
||||
{ name: 'Gargoyles', level: 1, hp: 2, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Gargoyles', level: 3, hp: 3, rewardTokens: 1, defBlue: 2 },
|
||||
{ name: 'Gargoyles', level: 5, hp: 6, rewardTokens: 2, defBlue: 3 },
|
||||
|
||||
{ name: 'Demons', level: 1, hp: 3, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Demons', level: 3, hp: 4, rewardTokens: 1, defBlue: 2 },
|
||||
{ name: 'Demons', level: 5, hp: 6, rewardTokens: 2, defBlue: 4 },
|
||||
|
||||
{ name: 'Undead', level: 1, hp: 4, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Undead', level: 3, hp: 5, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Undead', level: 5, hp: 8, rewardTokens: 2, defBlue: 1 },
|
||||
|
||||
{ name: 'Fire Entities', level: 1, hp: 3, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Fire Entities', level: 3, hp: 4, rewardTokens: 1, defBlue: 2 },
|
||||
{ name: 'Fire Entities', level: 5, hp: 7, rewardTokens: 2, defBlue: 3 },
|
||||
|
||||
{ name: 'Fallen Angels', level: 1, hp: 2, rewardTokens: 1, defBlue: 2 },
|
||||
{ name: 'Fallen Angels', level: 3, hp: 3, rewardTokens: 1, defBlue: 3 },
|
||||
{ name: 'Fallen Angels', level: 5, hp: 5, rewardTokens: 2, defBlue: 5 },
|
||||
|
||||
{ name: 'Infernal Imps', level: 1, hp: 3, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Infernal Imps', level: 3, hp: 4, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Infernal Imps', level: 5, hp: 5, rewardTokens: 2, defBlue: 3 },
|
||||
|
||||
{ name: 'Skeletons', level: 1, hp: 2, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Skeletons', level: 3, hp: 3, rewardTokens: 1, defBlue: 2 },
|
||||
{ name: 'Skeletons', level: 5, hp: 5, rewardTokens: 2, defBlue: 4 },
|
||||
|
||||
{ name: 'Satyrs', level: 1, hp: 3, rewardTokens: 1, defBlue: 1 },
|
||||
{ name: 'Satyrs', level: 3, hp: 4, rewardTokens: 1, defBlue: 2 },
|
||||
{ name: 'Satyrs', level: 5, hp: 6, rewardTokens: 2, defBlue: 4 }]
|
||||
|
||||
export abstract class MobFactory implements IMobFactory {
|
||||
abstract mobName: string;
|
||||
abstract generate(level: number): MobInfo
|
||||
protected mob: MobInfo;
|
||||
protected loadLevelInfo(mobName: string, level: number) {
|
||||
let levelInfo = CORE_GAME_MOB_LEVEL.find(m => m.name == mobName && level >= m.level);
|
||||
|
||||
this.mob = new MobInfo({
|
||||
name: mobName, hp: levelInfo.hp, level: level, rewardTokens: levelInfo.rewardTokens,
|
||||
defenseInfo: new DefenseInfo(levelInfo.defBlue)
|
||||
});
|
||||
this.mob.leaderImgUrl = MD2_IMG_URL(`/CoreGame/Mobs/${this.mob.name}/Leader.png`);
|
||||
this.mob.minionImgUrl = MD2_IMG_URL(`/CoreGame/Mobs/${this.mob.name}/Minion.png`);
|
||||
|
||||
}
|
||||
iconHtml(icon: MD2Icon, cssClass = 'g-font-size-24') {
|
||||
if (icon == MD2Icon.Fire) {
|
||||
cssClass += ' g-color-google-plus ';
|
||||
}
|
||||
if (icon == MD2Icon.Frost || icon == MD2Icon.Mana) {
|
||||
cssClass += ' g-color-aqua ';
|
||||
}
|
||||
|
||||
if (icon < MD2Icon.RedDice) {
|
||||
return `<span class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
|
||||
|
||||
}
|
||||
else {
|
||||
return `<span class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
export class MobDemonsFactory extends MobFactory {
|
||||
mobName: string = 'Demons';
|
||||
generate(level: number): MobInfo {
|
||||
this.loadLevelInfo('Demons', level);
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
{
|
||||
description: `Attacking or defending Hero discards 1 ${this.iconHtml(MD2Icon.Mana)}`
|
||||
}
|
||||
)
|
||||
return this.mob;
|
||||
}
|
||||
|
||||
}
|
||||
export class MobFallenAngelFactory extends MobFactory {
|
||||
mobName: string = 'Fallen Angels';
|
||||
generate(level: number): MobInfo {
|
||||
this.loadLevelInfo('Fallen Angels', level);
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
{
|
||||
description: `Defender -${level == 1 ? 1 : 2} ${this.iconHtml(MD2Icon.Defense)}`,
|
||||
type: MobSkillType.Attack
|
||||
}
|
||||
)
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
|
||||
export class MobFireEntitiesFactory extends MobFactory {
|
||||
mobName: string = 'Fire Entities';
|
||||
generate(level: number): MobInfo {
|
||||
this.loadLevelInfo('Fire Entities', level);
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
{
|
||||
description: `Add 1 ${this.iconHtml(MD2Icon.Fire)} to the attacking or defending Hero.`,
|
||||
type: MobSkillType.Combat
|
||||
}
|
||||
)
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
|
||||
export class MobGargoylesFactory extends MobFactory {
|
||||
mobName: string = 'Gargoyles';
|
||||
generate(level: number): MobInfo {
|
||||
this.loadLevelInfo('Gargoyles', level);
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
{
|
||||
description: `+ ${level < 5 ? 1 : 2} ${this.iconHtml(MD2Icon.Defense)}`,
|
||||
type: MobSkillType.Defense
|
||||
}
|
||||
)
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
export class MobInfernalImpsFactory extends MobFactory {
|
||||
mobName: string = 'Infernal Imps';
|
||||
generate(level: number): MobInfo {
|
||||
this.loadLevelInfo('Infernal Imps', level);
|
||||
let damage = 1;
|
||||
switch (level) {
|
||||
case 1:
|
||||
case 2:
|
||||
damage = 1;
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
damage = 2;
|
||||
break;
|
||||
case 5:
|
||||
damage = 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
damage = 1;
|
||||
break;
|
||||
}
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
{
|
||||
description: `Kill 1 Imp, then deal ${damage} Wound to each Hero in the attacker's Zone(once per roll).`,
|
||||
type: MobSkillType.Defense
|
||||
}
|
||||
)
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
export class MobSatyrsFactory extends MobFactory {
|
||||
mobName: string = 'Satyrs';
|
||||
generate(level: number): MobInfo {
|
||||
this.loadLevelInfo('Satyrs', level);
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
{
|
||||
description: `+ ${level < 3 ? 1 : 2} ${this.iconHtml(MD2Icon.Attack)}`,
|
||||
type: MobSkillType.Attack
|
||||
}
|
||||
)
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
|
||||
export class MobSkeletonsFactory extends MobFactory {
|
||||
mobName: string = 'Skeletons';
|
||||
generate(level: number): MobInfo {
|
||||
this.loadLevelInfo('Skeletons', level);
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
{
|
||||
description: `Add 1 minion to this Mob(if possible) unless the Hero discards ${level < 5 ? 1 : 2} ${this.iconHtml(MD2Icon.Mana)}.`,
|
||||
type: MobSkillType.Defense,
|
||||
skillRoll: 2
|
||||
}
|
||||
)
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
|
||||
export class MobUndeadFactory extends MobFactory {
|
||||
mobName: string = 'Undead';
|
||||
generate(level: number): MobInfo {
|
||||
this.loadLevelInfo('Undead', level);
|
||||
|
||||
let skillDesc = '';
|
||||
if (level < 3) {
|
||||
skillDesc = `+1 ${this.iconHtml(MD2Icon.YellowDice)}`;
|
||||
} else if (level < 5) {
|
||||
skillDesc = `+2 ${this.iconHtml(MD2Icon.YellowDice)}`;
|
||||
|
||||
} else {
|
||||
skillDesc = `+1 ${this.iconHtml(MD2Icon.YellowDice)} 1 ${this.iconHtml(MD2Icon.OrangeDice)}`;
|
||||
|
||||
}
|
||||
skillDesc += ' and this Mob takes 2 wounds';
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
{
|
||||
description: skillDesc,
|
||||
type: MobSkillType.Attack
|
||||
}
|
||||
)
|
||||
this.mob.drawingWeight = 1;
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const CoreGameMobFactories = [
|
||||
new MobDemonsFactory(),
|
||||
new MobFallenAngelFactory(),
|
||||
new MobFireEntitiesFactory(),
|
||||
new MobGargoylesFactory(),
|
||||
new MobInfernalImpsFactory(),
|
||||
new MobSatyrsFactory(),
|
||||
new MobSkeletonsFactory(),
|
||||
new MobUndeadFactory(),
|
||||
];
|
||||
@ -3,8 +3,9 @@ import { first, map } from "rxjs/operators";
|
||||
import { environment } from "../../../../../environments/environment";
|
||||
import { ADButtons, ADIcon } from "../../../../ui/alert-dlg/alert-dlg.model";
|
||||
import { MD2Logic } from "../../massive-darkness2.logic";
|
||||
import { AttackInfo, AttackTarget, DefenseInfo, IMobFactory, MD2Icon, MobInfo, MobType, TreasureItem, TreasureType } from "../../massive-darkness2.model";
|
||||
import { MobSkill, MobSkillType } from "../../massive-darkness2.model.boss";
|
||||
import { AttackInfo, AttackTarget, IMobFactory, MD2Icon, MobInfo, MobType, TreasureItem, TreasureType } from "../../massive-darkness2.model";
|
||||
import { MobSkillType } from "../../massive-darkness2.model.boss";
|
||||
import { MD2DiceSet, MD2MobSkill } from "../../massive-darkness2.db.model";
|
||||
|
||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }
|
||||
const CORE_GAME_MOB_LEVEL = [
|
||||
@ -13,68 +14,68 @@ const CORE_GAME_MOB_LEVEL = [
|
||||
name: 'Andra', level: 1, hp: 5,
|
||||
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 1, 0, 0, 1), new AttackInfo(MD2Icon.Range, 1, 0, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(2, 1),
|
||||
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'Andra', level: 3, hp: 7,
|
||||
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 1, 1, 0, 1), new AttackInfo(MD2Icon.Range, 1, 1, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(3, 1),
|
||||
defenseInfo: { blue: 3, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'Andra', level: 5, hp: 5,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 1, 2, 0, 1), new AttackInfo(MD2Icon.Range, 1, 2, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(5, 1),
|
||||
defenseInfo: { blue: 5, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
|
||||
new MobInfo({
|
||||
name: 'Ytheria, Undead Queen', level: 1, hp: 4,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 1), new AttackInfo(MD2Icon.Range, 2, 0, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(1, 1),
|
||||
defenseInfo: { blue: 1, black: 1 } as MD2DiceSet,
|
||||
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'Ytheria, Undead Queen', level: 3, hp: 6,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1), new AttackInfo(MD2Icon.Range, 1, 1, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(2, 1),
|
||||
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
|
||||
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'Ytheria, Undead Queen', level: 5, hp: 8,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 2, 1), new AttackInfo(MD2Icon.Range, 2, 1, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(4, 1),
|
||||
defenseInfo: { blue: 4, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
|
||||
new MobInfo({
|
||||
name: 'Lyidan, Incubus Lord', level: 1, hp: 7,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1, 0, 2)],
|
||||
defenseInfo: new DefenseInfo(2, 1),
|
||||
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'Lyidan, Incubus Lord', level: 3, hp: 10,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 2, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(2, 1),
|
||||
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'Lyidan, Incubus Lord', level: 5, hp: 12,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(4, 1),
|
||||
defenseInfo: { blue: 4, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
|
||||
new MobInfo({
|
||||
name: 'The Ghoul', level: 1, hp: 5,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1, 0, 1)],
|
||||
defenseInfo: new DefenseInfo(2, 1),
|
||||
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'The Ghoul', level: 3, hp: 8,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 2, 0, 2)],
|
||||
defenseInfo: new DefenseInfo(3, 1),
|
||||
defenseInfo: { blue: 3, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'The Ghoul', level: 5, hp: 10,
|
||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 3, 0, 3)],
|
||||
defenseInfo: new DefenseInfo(4, 1),
|
||||
defenseInfo: { blue: 4, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
|
||||
|
||||
@ -83,21 +84,21 @@ const CORE_GAME_MOB_LEVEL = [
|
||||
attackInfos: [
|
||||
new AttackInfo(MD2Icon.Magic, 0, 1, 0, 2),
|
||||
],
|
||||
defenseInfo: new DefenseInfo(2, 1),
|
||||
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'Balrog', level: 3, hp: 8,
|
||||
attackInfos: [
|
||||
new AttackInfo(MD2Icon.Magic, 0, 2, 0, 2),
|
||||
],
|
||||
defenseInfo: new DefenseInfo(3, 1),
|
||||
defenseInfo: { blue: 3, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
new MobInfo({
|
||||
name: 'Balrog', level: 5, hp: 10,
|
||||
attackInfos: [
|
||||
new AttackInfo(MD2Icon.Magic, 0, 3, 0, 2),
|
||||
],
|
||||
defenseInfo: new DefenseInfo(4, 1),
|
||||
defenseInfo: { blue: 4, black: 1 } as MD2DiceSet,
|
||||
}),
|
||||
]
|
||||
|
||||
@ -194,12 +195,13 @@ export class RMUndeadQueenFactory extends CoreGameRMFactory {
|
||||
});;
|
||||
}
|
||||
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
this.mob.skills = [
|
||||
{
|
||||
description: `Add 1 Minion to each Mob in the Dungeon, if possible.`,
|
||||
type: MobSkillType.Attack
|
||||
}
|
||||
)
|
||||
type: MobSkillType.Attack,
|
||||
|
||||
skillRoll: 1
|
||||
} as MD2MobSkill];
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
@ -249,12 +251,12 @@ export class RMAndraFactory extends CoreGameRMFactory {
|
||||
});
|
||||
}
|
||||
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
this.mob.skills = [
|
||||
{
|
||||
description: `Deal ${damage} wound to another Hero with the lowest HP in LoS`,
|
||||
type: MobSkillType.Combat
|
||||
}
|
||||
)
|
||||
type: MobSkillType.Combat,
|
||||
skillRoll: 1
|
||||
} as MD2MobSkill];
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
@ -293,12 +295,12 @@ export class RMTheGhoulFactory extends CoreGameRMFactory {
|
||||
});
|
||||
}
|
||||
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
this.mob.skills = [
|
||||
{
|
||||
description: `Move the closest <b>Mob with minion</b> 1 Zone toward The Ghoul.`,
|
||||
type: MobSkillType.Combat
|
||||
}
|
||||
)
|
||||
type: MobSkillType.Combat,
|
||||
skillRoll: 1
|
||||
} as MD2MobSkill];
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
@ -340,12 +342,12 @@ export class RMLyidanIncubusLordFactory extends CoreGameRMFactory {
|
||||
});
|
||||
}
|
||||
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
this.mob.skills = [
|
||||
{
|
||||
description: `After combat, resolve all ${this.iconHtml(MD2Icon.Fire)} on the defending Hero(once per combat).`,
|
||||
type: MobSkillType.Attack
|
||||
}
|
||||
)
|
||||
type: MobSkillType.Attack,
|
||||
skillRoll: 1
|
||||
} as MD2MobSkill];
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
@ -376,12 +378,12 @@ export class RMBalrogFactory extends CoreGameRMFactory {
|
||||
}
|
||||
}
|
||||
|
||||
this.mob.combatSkill = new MobSkill(
|
||||
this.mob.skills = [
|
||||
{
|
||||
description: `The Hero takes 1 ${this.iconHtml(MD2Icon.Fire)}`,
|
||||
type: MobSkillType.Combat
|
||||
}
|
||||
)
|
||||
type: MobSkillType.Combat,
|
||||
skillRoll: 1
|
||||
} as MD2MobSkill];
|
||||
return this.mob;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,35 +1,186 @@
|
||||
<nb-card *ngIf="!hero">
|
||||
<nb-card-body>
|
||||
<button nbButton hero status="primary" fullWidth (click)="initHero()">Choose Hero</button>
|
||||
<!-- Hero Selection Screen - Initial -->
|
||||
<nb-card *ngIf="!hero && !isSelectingHero" class="hero-selection-card">
|
||||
<nb-card-body class="hero-selection-body">
|
||||
<div class="hero-selection-content">
|
||||
<h2 class="hero-selection-title">Choose Your Hero</h2>
|
||||
<p class="hero-selection-subtitle">Begin your epic adventure</p>
|
||||
<button nbButton hero status="primary" size="large" class="hero-selection-btn" (click)="initHero()">
|
||||
<nb-icon icon="star-outline" class="mr-2"></nb-icon>
|
||||
Select Hero
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
||||
<!-- Hero Selection Panel -->
|
||||
<div *ngIf="!hero && isSelectingHero && currentSelectingHero" class="hero-selection-panel">
|
||||
<div class="row no-gutters hero-selection-row">
|
||||
<div class="col-12 hero-selection-left">
|
||||
<div class="hero-selection-card-wrapper">
|
||||
<div class="hero-selection-header">
|
||||
<div class="hero-selection-title-bar">
|
||||
<h3 class="hero-selection-hero-name">{{currentSelectingHero.name}}</h3>
|
||||
<span class="hero-selection-class">{{HeroClass[selectedHeroClass]}}</span>
|
||||
<span class="hero-selection-counter">({{currentHeroIndex + 1}} / {{heros.length}})</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-selection-content-area">
|
||||
<div class="row no-gutters h-100">
|
||||
<div class="col-6 hero-select-image-col">
|
||||
<div class="hero-select-image-wrapper"
|
||||
[style.background-image]="'url(' + imgUrl('/Mobs/BG.png') + ')'">
|
||||
<img src="{{imgUrl('Heros/'+className+'.png')}}" class="hero-select-image"
|
||||
alt="{{currentSelectingHero.name}}">
|
||||
<!-- HP and Mana Bars -->
|
||||
<div class="hero-select-stats-overlay">
|
||||
<div class="stat-bar-overlay hp-bar-overlay">
|
||||
<div class="stat-bar-label-overlay">
|
||||
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
|
||||
<span class="stat-value-overlay">{{currentSelectingHero.hpMaximum}}</span>
|
||||
</div>
|
||||
<div class="stat-progress-bar-overlay">
|
||||
<div class="stat-progress-fill-overlay hp-fill-overlay full-stat"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-bar-overlay mp-bar-overlay">
|
||||
<div class="stat-bar-label-overlay">
|
||||
<md2-icon [icon]="MD2Icon.Mana_Color" size="sm"></md2-icon>
|
||||
<span class="stat-value-overlay">{{currentSelectingHero.mpMaximum}}</span>
|
||||
</div>
|
||||
<div class="stat-progress-bar-overlay">
|
||||
<div class="stat-progress-fill-overlay mp-fill-overlay full-stat"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 hero-select-skills-col">
|
||||
<div class="hero-select-skills">
|
||||
<div class="skills-title">Abilities</div>
|
||||
<div class="skill-content" [innerHTML]="currentSelectingHero.skillHtml"></div>
|
||||
<div class="skills-title shadow-skills-title">Shadow Abilities</div>
|
||||
<div class="skill-content shadow-skill-content"
|
||||
[innerHTML]="currentSelectingHero.shadowSkillHtml"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-selection-actions">
|
||||
<button nbButton hero status="basic" class="nav-hero-btn" (click)="previousHero()">
|
||||
<nb-icon icon="chevron-back-outline" class="mr-1"></nb-icon>
|
||||
Previous
|
||||
</button>
|
||||
<button nbButton hero status="primary" class="select-hero-btn" (click)="selectCurrentHero()">
|
||||
<nb-icon icon="checkmark-circle-outline" class="mr-2"></nb-icon>
|
||||
It's Me!
|
||||
</button>
|
||||
<button nbButton hero status="basic" class="nav-hero-btn" (click)="nextHero()">
|
||||
Next
|
||||
<nb-icon icon="chevron-forward-outline" class="ml-1"></nb-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="hero">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-12 col-sm-7">
|
||||
<div class="tp-wrapper mb-2">
|
||||
<div class="tp-box g-height-300 g-height-350--sm g-height-500--md" [@flipState]="flip">
|
||||
<div class="tp-box" [@flipState]="flip">
|
||||
<div class="tp-box__side tp-box__front ">
|
||||
|
||||
<img class="MD2HeroCard " src="{{hero.imgUrl}}" (click)="toggleFlip()">
|
||||
|
||||
<div class="d-none d-sm-block">
|
||||
<div *ngIf="hero.uiActivating&&hero.remainActions>0">
|
||||
<button nbButton hero class="mr-2" status="info" (click)="moveAction()"
|
||||
*ngIf="!showMoveAction">Move</button>
|
||||
<button nbButton hero class="mr-2" status="info" (click)="moveActionEnd()"
|
||||
*ngIf="showMoveAction">Move
|
||||
End</button>
|
||||
<button nbButton hero class="mr-2" status="danger" (click)="action('attackAction')"
|
||||
*ngIf="!showMoveAction&&allowAttack">Attack!</button>
|
||||
<button nbButton hero class="mr-2" status="info" (click)="action('tradeAction')"
|
||||
*ngIf="!showMoveAction">Trade</button>
|
||||
<button nbButton hero status="success" (click)="action('recoveryAction')"
|
||||
*ngIf="!showMoveAction">Recovery</button>
|
||||
<div class="hero-card-content">
|
||||
<div class="row no-gutters h-100">
|
||||
<div class="col-6 hero-image-col">
|
||||
<div class="hero-image-wrapper"
|
||||
[style.background-image]="'url(' + imgUrl('/Mobs/BG.png') + ')'">
|
||||
<img src="{{imgUrl('Heros/'+className+'.png')}}" class="hero-image"
|
||||
(click)="toggleFlip()" alt="{{hero.name}}">
|
||||
<!-- HP and Mana Bars -->
|
||||
<div class="hero-stats-overlay">
|
||||
<div class="stat-bar-overlay hp-bar-overlay">
|
||||
<div class="stat-bar-label-overlay">
|
||||
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
|
||||
<span
|
||||
class="stat-value-overlay">{{hero.hp}}/{{hero.hpMaximum}}</span>
|
||||
</div>
|
||||
<div class="stat-progress-bar-overlay">
|
||||
<div class="stat-progress-fill-overlay hp-fill-overlay"
|
||||
[style.width.%]="(hero.hp / hero.hpMaximum) * 100"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-bar-overlay mp-bar-overlay">
|
||||
<div class="stat-bar-label-overlay">
|
||||
<md2-icon [icon]="MD2Icon.Mana_Color" size="sm"></md2-icon>
|
||||
<span
|
||||
class="stat-value-overlay">{{hero.mp}}/{{hero.mpMaximum}}</span>
|
||||
</div>
|
||||
<div class="stat-progress-bar-overlay">
|
||||
<div class="stat-progress-fill-overlay mp-fill-overlay"
|
||||
[style.width.%]="(hero.mp / hero.mpMaximum) * 100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 hero-skills-col">
|
||||
<div class="hero-skills">
|
||||
<div class="skills-title">Abilities</div>
|
||||
<div class="skill-content" [innerHTML]="hero.skillHtml"></div>
|
||||
<div class="skills-title shadow-skills-title">Shadow Abilities</div>
|
||||
<div class="skill-content shadow-skill-content"
|
||||
[innerHTML]="hero.shadowSkillHtml"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <img class="MD2HeroCard " src="{{imgUrl('Heros/'+className+'.jpg')}}" (click)="toggleFlip()"> -->
|
||||
|
||||
|
||||
<button nbButton hero fullWidth status="info" *ngIf="allowStartAction"
|
||||
(click)="startActivation()">Start Activation</button>
|
||||
<!-- Action Buttons (Desktop/Landscape) -->
|
||||
<div class="hero-actions d-none d-sm-block">
|
||||
<div class="action-buttons-group" *ngIf="hero.uiActivating && hero.remainActions > 0">
|
||||
<button nbButton hero class="action-btn" status="info" (click)="moveAction()"
|
||||
*ngIf="!showMoveAction">
|
||||
<nb-icon icon="arrow-forward-outline" class="mr-1"></nb-icon>
|
||||
Move
|
||||
</button>
|
||||
<button nbButton hero class="action-btn" status="info" (click)="moveActionEnd()"
|
||||
*ngIf="showMoveAction">
|
||||
<nb-icon icon="checkmark-outline" class="mr-1"></nb-icon>
|
||||
End Move
|
||||
</button>
|
||||
<button nbButton hero class="action-btn" status="danger"
|
||||
(click)="action('attackAction')" *ngIf="!showMoveAction && allowAttack">
|
||||
<nb-icon icon="flash-outline" class="mr-1"></nb-icon>
|
||||
Attack!
|
||||
</button>
|
||||
<button nbButton hero class="action-btn" status="info" (click)="action('tradeAction')"
|
||||
*ngIf="!showMoveAction">
|
||||
<nb-icon icon="swap-outline" class="mr-1"></nb-icon>
|
||||
Trade
|
||||
</button>
|
||||
<button nbButton hero class="action-btn" status="success"
|
||||
(click)="action('recoveryAction')" *ngIf="!showMoveAction">
|
||||
<nb-icon icon="heart-outline" class="mr-1"></nb-icon>
|
||||
Recovery
|
||||
</button>
|
||||
</div>
|
||||
<button nbButton hero class="action-btn special-action-btn" status="info" fullWidth
|
||||
(click)="openDoor()" *ngIf="showMoveAction">
|
||||
<nb-icon icon="grid-outline" class="mr-1"></nb-icon>
|
||||
Open Door
|
||||
</button>
|
||||
<button nbButton hero fullWidth status="info" class="start-activation-btn"
|
||||
*ngIf="allowStartAction" (click)="startActivation()">
|
||||
<nb-icon icon="play-circle-outline" class="mr-2"></nb-icon>
|
||||
Start Activation
|
||||
</button>
|
||||
<button nbButton hero fullWidth status="warning" class="end-activation-btn"
|
||||
*ngIf="hero.uiActivating" (click)="endActivation()">
|
||||
<nb-icon icon="stop-circle-outline" class="mr-2"></nb-icon>
|
||||
End Activation
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tp-box__side tp-box__back">
|
||||
@ -134,31 +285,6 @@
|
||||
|
||||
</div>
|
||||
<div *ngIf="md2Service.info.isBossFight"></div>
|
||||
<div class="d-sm-none">
|
||||
<div *ngIf="hero.uiActivating&&hero.remainActions>0">
|
||||
<button nbButton hero class="mr-2" status="info" (click)="moveAction()"
|
||||
*ngIf="!showMoveAction">Move</button>
|
||||
<button nbButton hero class="mr-2" status="info" (click)="moveActionEnd()"
|
||||
*ngIf="showMoveAction">Move End</button>
|
||||
<button nbButton hero class="mr-2" status="danger"
|
||||
(click)="action('attackAction')">Attack!</button>
|
||||
<button nbButton hero class="mr-2" status="info" (click)="action('tradeAction')"
|
||||
*ngIf="!showMoveAction">Trade</button>
|
||||
<button nbButton hero status="success" (click)="action('recoveryAction')"
|
||||
*ngIf="!showMoveAction">Recovery</button>
|
||||
</div>
|
||||
|
||||
|
||||
<button nbButton hero fullWidth status="info" *ngIf="allowStartAction"
|
||||
(click)="startActivation()">Start Activation</button>
|
||||
</div>
|
||||
|
||||
|
||||
<button nbButton hero status="info" class="mt-2" (click)="openDoor()" *ngIf="showMoveAction">Open
|
||||
Door</button>
|
||||
<button nbButton hero fullWidth status="warning" class="mt-3" *ngIf="hero.uiActivating"
|
||||
(click)="endActivation()">End
|
||||
Activation</button>
|
||||
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
||||
@ -1,3 +1,367 @@
|
||||
// Hero Selection Screen
|
||||
.hero-selection-card {
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-selection-body {
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.9) 0%, rgba(118, 75, 162, 0.9) 100%);
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-content {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero-selection-title {
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.75rem;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-subtitle {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-btn {
|
||||
padding: 0.75rem 2rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.6rem 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Hero Selection Panel
|
||||
.hero-selection-panel {
|
||||
height: 85vh;
|
||||
max-height: 85vh;
|
||||
padding: 0.5rem;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media (orientation: landscape) {
|
||||
height: 85vh;
|
||||
max-height: 85vh;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) {
|
||||
height: 85vh;
|
||||
max-height: 85vh;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-height: 667px) {
|
||||
height: 85vh;
|
||||
max-height: 85vh;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
height: 85vh;
|
||||
max-height: 85vh;
|
||||
padding: 0.15rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-row {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hero-selection-left {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-card-wrapper {
|
||||
height: 100%;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-header {
|
||||
padding: 0.75rem 1rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
||||
flex-shrink: 0;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-title-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-hero-name {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-class {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.9;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 500;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-counter {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.8;
|
||||
margin-left: auto;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-content-area {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.4rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-select-image-col,
|
||||
.hero-select-skills-col {
|
||||
padding: 0.4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-select-image-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #f8f9fa;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-select-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero-select-stats-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0.5rem;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
|
||||
border-radius: 0 0 8px 8px;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-select-skills {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
overflow-y: auto;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
min-height: 0;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.4rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-selection-actions {
|
||||
padding: 0.75rem;
|
||||
border-top: 2px solid #e9ecef;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.5rem;
|
||||
gap: 0.35rem;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-hero-btn {
|
||||
flex: 1;
|
||||
min-height: 40px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.3s;
|
||||
padding: 0.5rem;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
min-height: 32px;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
min-height: 38px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.select-hero-btn {
|
||||
flex: 2;
|
||||
min-height: 40px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
transition: all 0.3s;
|
||||
padding: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.5);
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
min-height: 32px;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: 767px) {
|
||||
min-height: 38px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
.MD2Hp {
|
||||
font-size: xx-large;
|
||||
position: fixed;
|
||||
@ -36,42 +400,410 @@
|
||||
.tp-wrapper {
|
||||
-webkit-perspective: 800px;
|
||||
perspective: 800px;
|
||||
height: 40vh; // Default for portrait
|
||||
|
||||
@media (orientation: landscape) {
|
||||
height: 85vh;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-height: 667px) {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
.tp-box {
|
||||
position: relative;
|
||||
//width: 200px;
|
||||
//height: 100px;
|
||||
//margin: 3rem auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
-webkit-transform: transform 1s;
|
||||
-ms-transform: transform 1s;
|
||||
transform: transform 1s;
|
||||
}
|
||||
|
||||
.tp-box__side {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Hero Card Content Section
|
||||
.hero-card-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0.5rem;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-image-col {
|
||||
padding: 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.15rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-image-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #f8f9fa;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
// HP and Mana Bars Overlay
|
||||
.hero-stats-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0.5rem;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
|
||||
border-radius: 0 0 8px 8px;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-bar-overlay {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-bar-label-overlay {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
gap: 0.5rem;
|
||||
|
||||
md2-icon {
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.stat-value-overlay {
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
margin-bottom: 0.15rem;
|
||||
gap: 0.3rem;
|
||||
|
||||
md2-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.stat-value-overlay {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-progress-bar-overlay {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-progress-fill-overlay {
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
transition: width 0.5s ease-out;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
&.full-stat {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.hp-fill-overlay {
|
||||
background: linear-gradient(90deg, #ff6b6b, #ee5a6f);
|
||||
box-shadow: 0 0 8px rgba(238, 90, 111, 0.6);
|
||||
}
|
||||
|
||||
.mp-fill-overlay {
|
||||
background: linear-gradient(90deg, #4ecdc4, #44a08d);
|
||||
box-shadow: 0 0 8px rgba(68, 160, 141, 0.6);
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-skills-col {
|
||||
padding: 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.15rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-skills {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
overflow-y: auto;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.skills-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.25rem;
|
||||
border-bottom: 2px solid #667eea;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
font-size: 0.75rem;
|
||||
margin-bottom: 0.3rem;
|
||||
padding-bottom: 0.15rem;
|
||||
}
|
||||
}
|
||||
|
||||
.shadow-skills-title {
|
||||
margin-top: 1rem;
|
||||
color: #764ba2;
|
||||
border-bottom-color: #764ba2;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.skill-content {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
.shadow-skill-content {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
// Action Buttons Section
|
||||
.hero-actions {
|
||||
padding: 0.75rem;
|
||||
border-top: 2px solid #e9ecef;
|
||||
margin-top: auto;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.5rem;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
min-height: 40px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
min-width: 70px;
|
||||
min-height: 36px;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.special-action-btn {
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
min-height: 40px;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
margin-bottom: 0.35rem;
|
||||
min-height: 32px;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.start-activation-btn {
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem;
|
||||
min-height: 44px;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.5rem;
|
||||
min-height: 36px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.end-activation-btn {
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem;
|
||||
min-height: 44px;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 8px rgba(255, 193, 7, 0.3);
|
||||
margin-top: 0.5rem;
|
||||
background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%);
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4);
|
||||
}
|
||||
|
||||
@media (max-height: 450px) and (orientation: landscape) {
|
||||
padding: 0.5rem;
|
||||
min-height: 36px;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tp-box__front {
|
||||
-webkit-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.tp-box__back {
|
||||
-webkit-transform: rotateY(-180deg);
|
||||
|
||||
@ -11,8 +11,9 @@ import { ADButtonColor, ADButtons } from '../../../ui/alert-dlg/alert-dlg.model'
|
||||
import { ArrayUtils } from '../../../utilities/array-utils';
|
||||
import { StringUtils } from '../../../utilities/string-utils';
|
||||
import { DebounceTimer } from '../../../utilities/timer-utils';
|
||||
import { HeroClass, MD2HeroInfo, MD2Icon } from '../massive-darkness2.model';
|
||||
import { HeroClass, MD2HeroInfo, MD2HeroProfile, MD2Icon } from '../massive-darkness2.model';
|
||||
import { MD2Base } from '../MD2Base';
|
||||
import { MD2HeroProfileService } from '../service/massive-darkness2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-hero-dashboard',
|
||||
@ -33,6 +34,7 @@ import { MD2Base } from '../MD2Base';
|
||||
]
|
||||
})
|
||||
export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
MD2Icon = MD2Icon;
|
||||
heroAction(hero: MD2HeroInfo, action: string) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
@ -55,24 +57,23 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
new DropDownOption(HeroClass.Druid, 'Druid'),
|
||||
];
|
||||
heros = [] as MD2HeroInfo[];
|
||||
wizards: MD2HeroInfo[] = [
|
||||
new MD2HeroInfo({ name: 'Ajax', mpMaximum: 6, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }),
|
||||
new MD2HeroInfo({ name: 'Baldric', mpMaximum: 5, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }),
|
||||
new MD2HeroInfo({ name: 'Ego', mpMaximum: 5, hpMaximum: 6, skillHtml: '', shadowSkillHtml: '' }),
|
||||
new MD2HeroInfo({ name: 'Elias', mpMaximum: 6, hpMaximum: 5, skillHtml: '', shadowSkillHtml: '' }),
|
||||
new MD2HeroInfo({ name: 'Megan', mpMaximum: 5, hpMaximum: 5, skillHtml: '', shadowSkillHtml: '' }),
|
||||
new MD2HeroInfo({ name: 'Moira', mpMaximum: 6, hpMaximum: 5, skillHtml: '', shadowSkillHtml: '' }),
|
||||
new MD2HeroInfo({ name: 'Myriam', mpMaximum: 7, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }),
|
||||
new MD2HeroInfo({ name: 'Valdis', mpMaximum: 6, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' })
|
||||
]
|
||||
heroProfiles: MD2HeroProfile[] = [];
|
||||
currentHeroIndex: number = 0;
|
||||
isSelectingHero: boolean = false;
|
||||
selectedHeroClass: HeroClass;
|
||||
|
||||
public get hero() {
|
||||
return this.md2Service.playerHero;
|
||||
}
|
||||
|
||||
public get currentSelectingHero(): MD2HeroInfo {
|
||||
return this.heros[this.currentHeroIndex];
|
||||
}
|
||||
|
||||
constructor(
|
||||
private gameRoomService: GameRoomService,
|
||||
public md2Service: MD2Service,
|
||||
private heroProfileService: MD2HeroProfileService,
|
||||
protected stateService: StateService,
|
||||
protected route: ActivatedRoute,
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
@ -100,6 +101,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
initHero() {
|
||||
this.gameRoomService.gameRoomId = this.roomId;
|
||||
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRSessionId)) {
|
||||
|
||||
this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' })
|
||||
.pipe(first()).subscribe(heroClass => {
|
||||
if (heroClass != null) {
|
||||
@ -124,43 +126,56 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||
initClassHeroList(heroClass: HeroClass) {
|
||||
this.heros = [];
|
||||
this.className = HeroClass[heroClass];
|
||||
this.fileList(`Heros/${this.className}`).pipe(first()).subscribe(fileNames => {
|
||||
for (let i = 0; i < fileNames.length; i++) {
|
||||
const heroNames = fileNames[i].split('.')[0].split('-');
|
||||
this.selectedHeroClass = heroClass;
|
||||
this.heroProfileService.getAll().pipe(first()).subscribe(result => {
|
||||
|
||||
this.heros.push(new MD2HeroInfo({
|
||||
name: heroNames[0].replace('/', ''),
|
||||
mpMaximum: Number.parseInt(heroNames[1]),
|
||||
hpMaximum: Number.parseInt(heroNames[2]),
|
||||
imgUrl: this.imgUrl(`Heros/${this.className}/${fileNames[i]}`),
|
||||
this.heroProfiles = result.filter(h => h.heroClass == heroClass);
|
||||
for (let i = 0; i < this.heroProfiles.length; i++) {
|
||||
const heroProfile = this.heroProfiles[i];
|
||||
const heroInfo = new MD2HeroInfo({
|
||||
name: heroProfile.title,
|
||||
mpMaximum: heroProfile.mana,
|
||||
hpMaximum: heroProfile.hp,
|
||||
hp: heroProfile.hp,
|
||||
mp: heroProfile.mana,
|
||||
skillHtml: heroProfile.skillHtml,
|
||||
shadowSkillHtml: heroProfile.shadowSkillHtml,
|
||||
class: heroClass
|
||||
}))
|
||||
});
|
||||
heroInfo.imgUrl = this.imgUrl('Heros/' + HeroClass[heroClass] + '.jpg');
|
||||
this.heros.push(heroInfo);
|
||||
}
|
||||
this.heros = ArrayUtils.Shuffle(this.heros);//.sort((a, b) => StringUtils.compareSemVer(a.name, b.name));
|
||||
this.showHeroList(heroClass, 0);
|
||||
this.currentHeroIndex = 0;
|
||||
this.isSelectingHero = true;
|
||||
this.detectChanges();
|
||||
});
|
||||
|
||||
}
|
||||
showHeroList(heroClass: HeroClass, index: number) {
|
||||
let className = HeroClass[heroClass];
|
||||
let heroInfo = this.heros[index];
|
||||
this.msgBoxService.show(`${className}(${index + 1}/${this.heros.length})`, {
|
||||
text: `<img src='${heroInfo.imgUrl}' class="g-width-50vw-md g-width-80vw">`,
|
||||
buttons: ADButtons.YesNo,
|
||||
cardWidthClass: '',
|
||||
confirmButtonText: 'It\'s Me!',
|
||||
cancelButtonText: 'Next',
|
||||
cancelButtonColor: ADButtonColor.INFO
|
||||
}).pipe(first()).subscribe(result => {
|
||||
if (result) {
|
||||
this.md2Service.playerJoin(heroInfo);
|
||||
|
||||
selectCurrentHero() {
|
||||
if (this.currentSelectingHero) {
|
||||
this.md2Service.playerJoin(this.currentSelectingHero);
|
||||
this.isSelectingHero = false;
|
||||
this.detectChanges();
|
||||
this.gameRoomService.joinGameRoom(this.roomId);
|
||||
} else {
|
||||
index++;
|
||||
if (index == this.heros.length) index = 0;
|
||||
this.showHeroList(heroClass, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextHero() {
|
||||
this.currentHeroIndex++;
|
||||
if (this.currentHeroIndex >= this.heros.length) {
|
||||
this.currentHeroIndex = 0;
|
||||
}
|
||||
this.detectChanges();
|
||||
}
|
||||
|
||||
previousHero() {
|
||||
this.currentHeroIndex--;
|
||||
if (this.currentHeroIndex < 0) {
|
||||
this.currentHeroIndex = this.heros.length - 1;
|
||||
}
|
||||
this.detectChanges();
|
||||
}
|
||||
|
||||
broadcastHeroInfo() {
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
</nb-accordion>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<md2-html-editor></md2-html-editor>
|
||||
</div>
|
||||
-->
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ import { NumberUtils } from '../../utilities/number-utils';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MassiveDarkness2Component extends MD2Base implements OnInit {
|
||||
HeroClass: HeroClass
|
||||
HeroClass = HeroClass;
|
||||
constructor(
|
||||
private fileService: FileService,
|
||||
private initService: MD2InitService,
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
import { MobType } from "./massive-darkness2.model";
|
||||
import { MobSkillType } from "./massive-darkness2.model.boss";
|
||||
|
||||
export enum MobSkillTarget {
|
||||
Random = 40,
|
||||
LeastHp = 50,
|
||||
LeastMp = 60,
|
||||
HighestHp = 70,
|
||||
HighestMp = 80,
|
||||
LowestLevel = 90,
|
||||
MostCorruption = 200,
|
||||
LeastCorruption = 201
|
||||
}
|
||||
|
||||
export enum GameBundle {
|
||||
CoreGame,
|
||||
HeavenFallen,
|
||||
Zombiecide
|
||||
}
|
||||
export interface MD2MobInfo {
|
||||
id: string;
|
||||
type: MobType;
|
||||
from: GameBundle;
|
||||
name: string;
|
||||
leaderImgUrl: string;
|
||||
minionImgUrl: string;
|
||||
mobLevelInfos: MD2MobLevelInfo[];
|
||||
skills: MD2MobSkill[];
|
||||
}
|
||||
|
||||
export interface MD2MobLevelInfo {
|
||||
id: string;
|
||||
level: number;
|
||||
mobInfoId: string;
|
||||
rewardTokens: number;
|
||||
fixedRareTreasure: number;
|
||||
fixedEpicTreasure: number;
|
||||
fixedLegendTreasure: number;
|
||||
fixedHp: number;
|
||||
hpPerHero: number;
|
||||
actions: number;
|
||||
attackInfo: MD2DiceSet;
|
||||
alterAttackInfo?: MD2DiceSet;
|
||||
defenceInfo: MD2DiceSet;
|
||||
}
|
||||
|
||||
export interface MD2MobSkill {
|
||||
id: string;
|
||||
seq: number;
|
||||
level: number;
|
||||
mobInfoId: string;
|
||||
type: MobSkillType;
|
||||
skillTarget: MobSkillTarget | null;
|
||||
clawRoll: number;
|
||||
skillRoll: number;
|
||||
name: string;
|
||||
skillCondition: string;
|
||||
description: string;
|
||||
uiDisplay?: boolean;
|
||||
}
|
||||
|
||||
export interface MD2DiceSet {
|
||||
type: MobSkillType;
|
||||
yellow: number | null;
|
||||
orange: number | null;
|
||||
red: number | null;
|
||||
blue: number | null;
|
||||
green: number | null;
|
||||
black: number | null;
|
||||
}
|
||||
@ -1,17 +1,24 @@
|
||||
import { stringify } from "querystring"
|
||||
|
||||
import { Observable, Subject, Subscription } from "rxjs"
|
||||
import { first } from "rxjs/operators"
|
||||
import { MD2Service } from "../../services/MD2/md2.service"
|
||||
import { StringUtils } from "../../utilities/string-utils"
|
||||
import { BossActivationComponent } from "./boss-fight/boss-activation/boss-activation.component"
|
||||
import { TreasureType, AttackInfo, DefenseInfo, AttackType, MD2Icon, MD2HeroInfo, AttackTarget, MobInfo, MobType } from "./massive-darkness2.model"
|
||||
import { TreasureType, AttackInfo, AttackType, MD2Icon, MD2HeroInfo, AttackTarget, MobInfo, MobType } from "./massive-darkness2.model"
|
||||
import { RollingBlackDice } from "./massive-darkness2.model.dice"
|
||||
import { MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model"
|
||||
|
||||
|
||||
export enum MobSkillType {
|
||||
Attack,
|
||||
Defense,
|
||||
Combat
|
||||
Combat,
|
||||
Passive,
|
||||
ConditionalSkill,
|
||||
ActiveSkill,
|
||||
MeleeAttack = 15,
|
||||
RangeAttack,
|
||||
MagicAttack,
|
||||
}
|
||||
export class MobSkill {
|
||||
constructor(config: Partial<MobSkill> = {}) {
|
||||
@ -110,14 +117,18 @@ export class BossMicheal extends BossFight {
|
||||
type: MobType.Boss,
|
||||
hpPerHero: 15,
|
||||
level: 10,
|
||||
combatSkill: new MobSkill(
|
||||
{
|
||||
name: `Combat 1 ${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)}`,
|
||||
description: `Deal 1 Wound for each ${this.corruptionTokenHtml} on the attacking or defending Hero. Discard the tokens afterwards(once per combat).`
|
||||
}),
|
||||
imageUrl: md2Service.stateService.imgUrl('/Boss/Michael.png')
|
||||
});
|
||||
this.info.defenseInfo = new DefenseInfo(5, 1);
|
||||
if (!this.info.skills) {
|
||||
this.info.skills = [];
|
||||
}
|
||||
this.info.skills.push({
|
||||
name: `Combat 1 ${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)}`,
|
||||
description: `Deal 1 Wound for each ${this.corruptionTokenHtml} on the attacking or defending Hero. Discard the tokens afterwards(once per combat).`,
|
||||
type: MobSkillType.Combat,
|
||||
skillRoll: 1
|
||||
} as MD2MobSkill);
|
||||
this.info.defenseInfo = { blue: 5, black: 1 } as MD2DiceSet;
|
||||
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)];
|
||||
this.actions = 1;
|
||||
this.actionBlackDice = 2;
|
||||
@ -233,15 +244,17 @@ export class BossReaper extends BossFight {
|
||||
type: MobType.Boss,
|
||||
hpPerHero: 25,
|
||||
level: 10,
|
||||
combatSkill: new MobSkill(
|
||||
{
|
||||
description: `If the Hero has no ${this.md2Service.stateService.iconHtml(MD2Icon.Mana)}, they take 1 ${this.md2Service.stateService.iconHtml(MD2Icon.Frost)}`,
|
||||
type: MobSkillType.Attack,
|
||||
|
||||
}),
|
||||
imageUrl: md2Service.stateService.imgUrl('/Boss/The Reaper-Stand.png')
|
||||
});
|
||||
this.info.defenseInfo = new DefenseInfo(4, 3);
|
||||
if (!this.info.skills) {
|
||||
this.info.skills = [];
|
||||
}
|
||||
this.info.skills.push({
|
||||
description: `If the Hero has no ${this.md2Service.stateService.iconHtml(MD2Icon.Mana_Color)}, they take 1 ${this.md2Service.stateService.iconHtml(MD2Icon.FrozenToken)}`,
|
||||
type: MobSkillType.Attack,
|
||||
skillRoll: 1
|
||||
} as MD2MobSkill);
|
||||
this.info.defenseInfo = { blue: 4, black: 3 } as MD2DiceSet;
|
||||
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 1, 2, 0, 3)];
|
||||
this.actions = 1;
|
||||
this.actionBlackDice = 2;
|
||||
|
||||
@ -5,6 +5,7 @@ import { ObjectUtils } from "../../utilities/object-utils";
|
||||
import { GamePlayer } from "../games.model";
|
||||
import { MD2Clone } from "./factorys/md2-clone";
|
||||
import { MobSkill } from "./massive-darkness2.model.boss";
|
||||
import { MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model";
|
||||
|
||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/${(id ? `${encodeURI(id)}` : '')}` }
|
||||
export enum MobDlgType {
|
||||
@ -22,6 +23,7 @@ export enum RoundPhase {
|
||||
BossActivation
|
||||
}
|
||||
export enum TreasureType {
|
||||
Cover,
|
||||
Common,
|
||||
Rare,
|
||||
Epic,
|
||||
@ -71,9 +73,23 @@ 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,
|
||||
HP_Color,
|
||||
Mana_Color,
|
||||
CorruptToken,
|
||||
TimeToken,
|
||||
FireToken,
|
||||
FrozenToken
|
||||
|
||||
}
|
||||
export enum AttackTarget {
|
||||
Random = 40,
|
||||
@ -110,14 +126,7 @@ export class AttackInfo {
|
||||
yellow: number
|
||||
black: number
|
||||
}
|
||||
export class DefenseInfo {
|
||||
constructor(blue: number, black: number = 0) {
|
||||
this.blue = blue
|
||||
this.black = black
|
||||
}
|
||||
blue: number
|
||||
black: number
|
||||
}
|
||||
|
||||
export class MD2LevelUpReward {
|
||||
constructor(config: Partial<MD2LevelUpReward>) {
|
||||
Object.assign(this, config);
|
||||
@ -311,10 +320,11 @@ export class MobInfo implements IDrawingItem {
|
||||
fixedCarriedTreasure: TreasureItem[];
|
||||
unitRemainHp: number;
|
||||
attackInfos: AttackInfo[];
|
||||
defenseInfo: DefenseInfo;
|
||||
defenseInfo: MD2DiceSet;
|
||||
|
||||
skills: MD2MobSkill[];
|
||||
actions: number = 0;
|
||||
activateDescription: string;
|
||||
combatSkill: MobSkill
|
||||
|
||||
fireToken: number = 0;
|
||||
frozenToken: number = 0;
|
||||
@ -370,7 +380,15 @@ export class MobInfo implements IDrawingItem {
|
||||
public actionSubject: Subject<string>;
|
||||
public activateFunction?: (mob: MobInfo, msgBoxService: MsgBoxService, heros: MD2HeroInfo[]) => void;
|
||||
}
|
||||
|
||||
export interface MD2HeroProfile {
|
||||
id: string;
|
||||
heroClass: HeroClass;
|
||||
title: string;
|
||||
hp: number;
|
||||
mana: number;
|
||||
skillHtml: string;
|
||||
shadowSkillHtml: string;
|
||||
}
|
||||
export class MD2HeroInfo {
|
||||
constructor(
|
||||
config: Partial<MD2HeroInfo> = {}
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
<div class="k-dialog-content">
|
||||
<form #form="ngForm">
|
||||
<div class="row form-group">
|
||||
<div class="col-md-4">
|
||||
|
||||
<label class="k-label">Hero Class *</label>
|
||||
<kendo-dropdownlist [(ngModel)]="selectedHeroClass" name="heroClass" [data]="heroClasses"
|
||||
[valueField]="'value'" [textField]="'text'"
|
||||
[defaultItem]="{ value: null, text: 'Select hero class...' }">
|
||||
</kendo-dropdownlist>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
|
||||
<label class="k-label">Title</label>
|
||||
<input kendoTextBox [(ngModel)]="model.title" name="title" class="k-input"
|
||||
placeholder="Enter hero profile title" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
|
||||
<label class="k-label">Mana</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.mana" name="mana" [format]="'n0'" [min]="0" [max]="100"
|
||||
class="k-input" placeholder="Enter mana value">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
|
||||
<label class="k-label">HP</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.hp" name="hP" [format]="'n0'" [min]="0" [max]="100"
|
||||
class="k-input" placeholder="Enter HP value">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label class="k-label">Skill HTML</label>
|
||||
|
||||
<md2-html-editor [(ngModel)]="model.skillHtml" name="skillHtml" class="htmlEditor"></md2-html-editor>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="k-label">Shadow Skill HTML</label>
|
||||
<md2-html-editor [(ngModel)]="model.shadowSkillHtml" name="shadowSkillHtml"
|
||||
class="htmlEditor"></md2-html-editor>
|
||||
</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>
|
||||
@ -0,0 +1,25 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.k-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.k-form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.k-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.k-form-error {
|
||||
color: #f31700;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2HeroProfile, HeroClass } from '../../massive-darkness2.model';
|
||||
import { MD2HeroProfileService } from '../../service/massive-darkness2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-hero-profile-editor',
|
||||
templateUrl: './md2-hero-profile-editor.component.html',
|
||||
styleUrls: ['./md2-hero-profile-editor.component.scss']
|
||||
})
|
||||
export class MD2HeroProfileEditorComponent extends DialogContentBase implements OnInit {
|
||||
@Input() public data: MD2HeroProfile;
|
||||
@Input() public isAdding: boolean = false;
|
||||
@ViewChild('form') form: NgForm;
|
||||
|
||||
public model: MD2HeroProfile;
|
||||
public processing: boolean = false;
|
||||
public heroClasses: Array<{ value: HeroClass; text: string }> = [];
|
||||
public selectedHeroClass: { value: HeroClass; text: string } | null = null;
|
||||
|
||||
constructor(
|
||||
public dialog: DialogRef,
|
||||
private heroProfileService: MD2HeroProfileService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
super(dialog);
|
||||
this.initializeEnums();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initializeModel();
|
||||
}
|
||||
|
||||
public initializeModel(): void {
|
||||
const classValue = this.data?.heroClass !== undefined && this.data?.heroClass !== null ? this.data.heroClass : HeroClass.Berserker;
|
||||
|
||||
this.model = {
|
||||
id: this.data?.id || '',
|
||||
heroClass: classValue,
|
||||
title: this.data?.title || '',
|
||||
hp: this.data?.hp || 0,
|
||||
mana: this.data?.mana || 0,
|
||||
skillHtml: this.data?.skillHtml || '',
|
||||
shadowSkillHtml: this.data?.shadowSkillHtml || ''
|
||||
};
|
||||
|
||||
// Set selected object for dropdown
|
||||
this.selectedHeroClass = this.heroClasses.find(c => c.value === classValue) || this.heroClasses[0] || null;
|
||||
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
private initializeEnums(): void {
|
||||
// Initialize HeroClass options
|
||||
Object.keys(HeroClass).filter(key => isNaN(Number(key))).forEach(key => {
|
||||
this.heroClasses.push({
|
||||
value: HeroClass[key] as HeroClass,
|
||||
text: key
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.dialog.close();
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
if (this.model.title && !this.processing) {
|
||||
this.processing = true;
|
||||
|
||||
// Extract enum value from selected object
|
||||
const heroProfile: MD2HeroProfile = {
|
||||
...this.model,
|
||||
heroClass: this.selectedHeroClass?.value ?? HeroClass.Berserker
|
||||
};
|
||||
|
||||
this.heroProfileService.createOrUpdate(heroProfile).pipe(first()).subscribe(result => {
|
||||
this.processing = false;
|
||||
this.dialog.close(result);
|
||||
}, error => {
|
||||
this.processing = false;
|
||||
console.error('Error saving hero profile:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
if (!this.model) {
|
||||
return false;
|
||||
}
|
||||
const titleValid = true;// this.model.title && this.model.title.trim().length > 0;
|
||||
const classValid = this.selectedHeroClass !== null && this.selectedHeroClass !== undefined;
|
||||
return titleValid && classValid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
<nb-card>
|
||||
<nb-card-header>
|
||||
<h4>MD2 Hero Profile Maintenance</h4>
|
||||
<button class="float-right" kendoButton (click)="addHandler()" [primary]="true">
|
||||
<span class="k-icon k-i-plus"></span> Add New
|
||||
</button>
|
||||
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<kendo-grid #grid [data]="gridData" [loading]="isLoading" [pageSize]="gridState.take" [skip]="gridState.skip"
|
||||
[group]="gridState.group" [filter]="gridState.filter" [sort]="gridState.sort" [sortable]="true"
|
||||
[filterable]="true" [pageable]="true" [selectable]="true" [groupable]="true"
|
||||
(dataStateChange)="gridState = $event; processGridData()" (edit)="editHandler($event)"
|
||||
(remove)="removeHandler($event)" (add)="addHandler()">
|
||||
|
||||
<kendo-grid-toolbar>
|
||||
<button kendoGridAddCommand>Add new</button>
|
||||
</kendo-grid-toolbar>
|
||||
|
||||
<kendo-grid-column field="title" title="Title" [width]="150">
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="heroClass" title="Hero Class" [width]="60">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ getHeroClassName(dataItem.heroClass) }}
|
||||
</ng-template>
|
||||
<ng-template kendoGridGroupHeaderTemplate let-value="value">
|
||||
{{ getHeroClassName(value) }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="hP" title="HP" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
<md2-icon [icon]="MD2Icon.Mana_Color" size="sm"></md2-icon> {{ dataItem.mana }}
|
||||
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon> {{ dataItem.hp }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="skill" title="Skill" [width]="500">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
<div [innerHTML]="dataItem.skillHtml"></div>
|
||||
<div>
|
||||
<md2-icon [icon]="MD2Icon.Shadow" size="sm"></md2-icon> <b>Shadow:</b>
|
||||
<div [innerHTML]="dataItem.shadowSkillHtml"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-command-column title="Actions" [width]="140">
|
||||
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||
<button kendoGridEditCommand [primary]="true">Edit</button>
|
||||
<button kendoGridRemoveCommand>Remove</button>
|
||||
</ng-template>
|
||||
</kendo-grid-command-column>
|
||||
</kendo-grid>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
@ -0,0 +1,11 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
kendo-grid {
|
||||
height: 77vh;
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||
import { State, process } from '@progress/kendo-data-query';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2HeroProfile, HeroClass, MD2Icon } from '../massive-darkness2.model';
|
||||
import { MD2HeroProfileService } from '../service/massive-darkness2.service';
|
||||
import { MD2HeroProfileEditorComponent } from './md2-hero-profile-editor/md2-hero-profile-editor.component';
|
||||
import { DialogService } from '@progress/kendo-angular-dialog';
|
||||
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-hero-profile-maintenance',
|
||||
templateUrl: './md2-hero-profile-maintenance.component.html',
|
||||
styleUrls: ['./md2-hero-profile-maintenance.component.scss']
|
||||
})
|
||||
export class MD2HeroProfileMaintenanceComponent implements OnInit {
|
||||
@ViewChild('grid') grid: GridComponent;
|
||||
MD2Icon = MD2Icon;
|
||||
public gridData: GridDataResult = { data: [], total: 0 };
|
||||
private allData: MD2HeroProfile[] = [];
|
||||
private lastSelectedHeroClass: HeroClass;
|
||||
public gridState: State = {
|
||||
skip: 0,
|
||||
take: 10,
|
||||
sort: [{
|
||||
field: 'title',
|
||||
dir: 'asc'
|
||||
}],
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: []
|
||||
},
|
||||
group: [{
|
||||
field: 'heroClass',
|
||||
dir: 'asc'
|
||||
}]
|
||||
};
|
||||
public isLoading: boolean = false;
|
||||
|
||||
constructor(
|
||||
private heroProfileService: MD2HeroProfileService,
|
||||
private dialogService: DialogService,
|
||||
private msgBoxService: MsgBoxService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
public loadData(): void {
|
||||
this.isLoading = true;
|
||||
this.heroProfileService.getAll().pipe(first()).subscribe(result => {
|
||||
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 = { heroClass: this.lastSelectedHeroClass || HeroClass.Berserker } as MD2HeroProfile;
|
||||
const dialogRef = this.dialogService.open({
|
||||
title: 'Add New Hero Profile',
|
||||
content: MD2HeroProfileEditorComponent,
|
||||
width: '90vw',
|
||||
height: 600
|
||||
});
|
||||
|
||||
const editor = dialogRef.content.instance;
|
||||
editor.isAdding = true;
|
||||
editor.data = editorData;
|
||||
|
||||
// Force model re-initialization after data is set
|
||||
setTimeout(() => {
|
||||
editor.initializeModel();
|
||||
}, 0);
|
||||
|
||||
dialogRef.result.subscribe((result: MD2HeroProfile) => {
|
||||
if (result) {
|
||||
this.lastSelectedHeroClass = result.heroClass;
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public editHandler({ dataItem }: { dataItem: MD2HeroProfile }): void {
|
||||
const dialogRef = this.dialogService.open({
|
||||
title: 'Edit Hero Profile',
|
||||
content: MD2HeroProfileEditorComponent,
|
||||
width: '90vw',
|
||||
height: 600
|
||||
});
|
||||
|
||||
const editor = dialogRef.content.instance;
|
||||
editor.isAdding = false;
|
||||
editor.data = JSON.parse(JSON.stringify(dataItem));
|
||||
|
||||
// Force model re-initialization after data is set
|
||||
setTimeout(() => {
|
||||
editor.initializeModel();
|
||||
}, 0);
|
||||
|
||||
dialogRef.result.subscribe(result => {
|
||||
if (result) {
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public removeHandler({ dataItem }: { dataItem: MD2HeroProfile }): void {
|
||||
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||
if (answer === true) {
|
||||
this.isLoading = true;
|
||||
this.heroProfileService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||
this.loadData();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getHeroClassName(heroClass: HeroClass): string {
|
||||
return HeroClass[heroClass] || '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,40 @@
|
||||
<kendo-editor [value]="value" (valueChange)="onChange($event)" (blur)="onTouched()" [disabled]="disabled" [schema]="messageTemplateSchema"
|
||||
[iframeCss]="{ content: customCssClass }">
|
||||
<kendo-editor [value]="value" (valueChange)="onChange($event)" (blur)="onTouched()" [disabled]="disabled"
|
||||
[schema]="messageTemplateSchema" [iframe]="false" class="h-100">
|
||||
<kendo-toolbar>
|
||||
<!-- Standard editing tools -->
|
||||
<kendo-toolbar-button kendoEditorBold></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorItalic></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorUnderline></kendo-toolbar-button>
|
||||
<kendo-toolbar-separator></kendo-toolbar-separator>
|
||||
<kendo-toolbar-button kendoEditorUnorderedList></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorOrderedList></kendo-toolbar-button>
|
||||
<kendo-toolbar-separator></kendo-toolbar-separator>
|
||||
<kendo-toolbar-button kendoEditorUndo></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorRedo></kendo-toolbar-button>
|
||||
<kendo-toolbar-separator></kendo-toolbar-separator>
|
||||
<!-- Custom MD2 Icon button -->
|
||||
<kendo-toolbar-button (click)="showInsertMD2Icon()">
|
||||
Insert Icon
|
||||
</kendo-toolbar-button>
|
||||
<kendo-toolbar-button text="Insert Icon" (click)="showInsertMD2Icon()"></kendo-toolbar-button>
|
||||
<!-- Standard editing tools -->
|
||||
<kendo-toolbar-buttongroup>
|
||||
<kendo-toolbar-button kendoEditorBoldButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorItalicButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorUnderlineButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorStrikethroughButton></kendo-toolbar-button>
|
||||
</kendo-toolbar-buttongroup>
|
||||
<kendo-toolbar-buttongroup>
|
||||
<kendo-toolbar-button kendoEditorAlignLeftButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorAlignCenterButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorAlignRightButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorAlignJustifyButton></kendo-toolbar-button>
|
||||
</kendo-toolbar-buttongroup>
|
||||
<kendo-toolbar-dropdownlist kendoEditorFormat></kendo-toolbar-dropdownlist>
|
||||
<kendo-toolbar-dropdownlist kendoEditorFontSize #fontSizeDropdown
|
||||
[data]="fontSizeData"></kendo-toolbar-dropdownlist>
|
||||
<kendo-toolbar-dropdownlist kendoEditorFontFamily></kendo-toolbar-dropdownlist>
|
||||
<kendo-toolbar-colorpicker kendoEditorForeColor></kendo-toolbar-colorpicker>
|
||||
<kendo-toolbar-colorpicker kendoEditorBackColor view="gradient"></kendo-toolbar-colorpicker>
|
||||
<kendo-toolbar-buttongroup>
|
||||
<kendo-toolbar-button kendoEditorInsertUnorderedListButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorInsertOrderedListButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorIndentButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorOutdentButton></kendo-toolbar-button>
|
||||
</kendo-toolbar-buttongroup>
|
||||
<kendo-toolbar-buttongroup>
|
||||
<kendo-toolbar-button kendoEditorUndoButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorRedoButton></kendo-toolbar-button>
|
||||
</kendo-toolbar-buttongroup>
|
||||
<kendo-toolbar-button kendoEditorInsertFileButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorInsertImageButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorViewSourceButton></kendo-toolbar-button>
|
||||
<kendo-toolbar-button kendoEditorCleanFormattingButton></kendo-toolbar-button>
|
||||
</kendo-toolbar>
|
||||
</kendo-editor>
|
||||
@ -0,0 +1,3 @@
|
||||
:host ::ng-deep .k-editor-content .MD2Icon {
|
||||
font-size: 30px;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, ElementRef, EventEmitter, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, ViewChild } from '@angular/core';
|
||||
import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
|
||||
import { EditorComponent } from '@progress/kendo-angular-editor';
|
||||
import { Component, ElementRef, EventEmitter, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, ViewChild, AfterViewInit, forwardRef, ChangeDetectorRef } from '@angular/core';
|
||||
import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { EditorComponent, NodeSpec, schema, Schema, FontSizeItem } from '@progress/kendo-angular-editor';
|
||||
|
||||
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||
import { DropDownOption } from '../../../entity/dropDownOption';
|
||||
@ -9,14 +9,23 @@ import { first } from 'rxjs/operators';
|
||||
import { MD2StateService } from '../../../services/MD2/md2-state.service';
|
||||
import { MD2IconPickerDlgComponent } from './md2-icon-picker-dlg.component';
|
||||
import { NbDialogService } from '@nebular/theme';
|
||||
|
||||
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
|
||||
import { DialogService } from '@progress/kendo-angular-dialog';
|
||||
@Component({
|
||||
selector: 'md2-html-editor',
|
||||
templateUrl: './md2-html-editor.component.html',
|
||||
styleUrl: './md2-html-editor.component.scss'
|
||||
styleUrl: './md2-html-editor.component.scss',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => MD2HtmlEditorComponent),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewInit {
|
||||
@ViewChild(EditorComponent) editor: EditorComponent;
|
||||
@ViewChild('fontSizeDropdown') fontSizeDropdown: any;
|
||||
|
||||
value: string = '';
|
||||
disabled: boolean = false;
|
||||
@ -24,6 +33,26 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
messageTemplateSchema = this.createCustomSchema();
|
||||
customCssClass = '.MD2Icon{font-family: "Massive Darkness 2", sans-serif !important; font-size: 40px; margin-left:5px} body{font-size: 30px; }';
|
||||
|
||||
// Default font size: 30px
|
||||
fontSizeData: FontSizeItem[] = [
|
||||
{ size: 30, text: '30px' },
|
||||
{ size: 8, text: '8px' },
|
||||
{ size: 10, text: '10px' },
|
||||
{ size: 12, text: '12px' },
|
||||
{ size: 14, text: '14px' },
|
||||
{ size: 16, text: '16px' },
|
||||
{ size: 18, text: '18px' },
|
||||
{ size: 20, text: '20px' },
|
||||
{ size: 24, text: '24px' },
|
||||
{ size: 30, text: '30px' },
|
||||
{ size: 36, text: '36px' },
|
||||
{ size: 48, text: '48px' },
|
||||
{ size: 60, text: '60px' },
|
||||
{ size: 72, text: '72px' }
|
||||
];
|
||||
|
||||
defaultFontSize: FontSizeItem = { size: 30, text: '30px' };
|
||||
|
||||
// ControlValueAccessor interface
|
||||
private onChangeFn = (value: string) => { };
|
||||
private onTouchedFn = () => { };
|
||||
@ -31,14 +60,58 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
constructor(
|
||||
private msgBoxService: MsgBoxService,
|
||||
private md2StateService: MD2StateService,
|
||||
private dialogService: NbDialogService,
|
||||
private dialogService: DialogService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
elementRef: ElementRef, ngZone: NgZone, @Inject(PLATFORM_ID) platformId: Object) {
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
// Set default font size after view initialization
|
||||
// The fontSizeDropdown is the EditorFontSizeComponent instance
|
||||
setTimeout(() => {
|
||||
if (this.fontSizeDropdown) {
|
||||
// Access the fontSizeDropDownList component which has the defaultItem property
|
||||
if (this.fontSizeDropdown.fontSizeDropDownList) {
|
||||
this.fontSizeDropdown.fontSizeDropDownList.defaultItem = this.defaultFontSize;
|
||||
}
|
||||
// Also try setting it directly on the component if it has the property
|
||||
if (this.fontSizeDropdown.defaultItem !== undefined) {
|
||||
this.fontSizeDropdown.defaultItem = this.defaultFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the editor value is set if writeValue was called before view init
|
||||
if (this.editor && this.value && this.editor.value !== this.value) {
|
||||
this.editor.value = this.value;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// ControlValueAccessor implementation
|
||||
writeValue(value: string): void {
|
||||
this.value = value || '';
|
||||
writeValue(value: string | null | undefined): void {
|
||||
const newValue = value || '';
|
||||
|
||||
// Only update if the value actually changed to avoid unnecessary updates
|
||||
if (this.value !== newValue) {
|
||||
this.value = newValue;
|
||||
|
||||
// Angular's [value] binding will handle updating the editor
|
||||
// But if editor is already initialized and value is out of sync, ensure sync
|
||||
if (this.editor && this.editor.value !== this.value) {
|
||||
// Use setTimeout to avoid ExpressionChangedAfterItHasBeenCheckedError
|
||||
setTimeout(() => {
|
||||
if (this.editor && this.editor.value !== this.value) {
|
||||
this.editor.value = this.value;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Trigger change detection to ensure the binding updates
|
||||
if (!this.cdr['destroyed']) {
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: string) => void): void {
|
||||
@ -54,8 +127,11 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
}
|
||||
|
||||
onChange(value: string): void {
|
||||
this.value = value;
|
||||
this.onChangeFn(value);
|
||||
// Only update if value actually changed to prevent infinite loops
|
||||
if (this.value !== value) {
|
||||
this.value = value || '';
|
||||
this.onChangeFn(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
onTouched(): void {
|
||||
@ -63,11 +139,15 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
}
|
||||
|
||||
showInsertMD2Icon() {
|
||||
this.dialogService.open(MD2IconPickerDlgComponent, {
|
||||
closeOnBackdropClick: true,
|
||||
closeOnEsc: true
|
||||
}).onClose.pipe(first()).subscribe((html: string) => {
|
||||
this.dialogService.open({
|
||||
title: 'Insert MD2 Icon',
|
||||
content: MD2IconPickerDlgComponent,
|
||||
width: '800px',
|
||||
height: 600
|
||||
}).result.subscribe((html: string) => {
|
||||
if (html && this.editor) {
|
||||
this.insertAfterSelection(html, true);
|
||||
return;
|
||||
// Insert the HTML content at the current cursor position
|
||||
// Use ProseMirror's dispatch method to insert content at cursor
|
||||
const view = this.editor.view;
|
||||
@ -144,7 +224,7 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
if (contentNode) {
|
||||
const transaction = view.state.tr.insert(0, contentNode);
|
||||
view.dispatch(transaction);
|
||||
this.onValueChanged(this.editor.value);
|
||||
this.onChange(this.editor.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +243,7 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
if (contentNode) {
|
||||
const transaction = view.state.tr.insert(endPos, contentNode);
|
||||
view.dispatch(transaction);
|
||||
this.onValueChanged(this.editor.value);
|
||||
this.onChange(this.editor.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +262,7 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
if (contentNode) {
|
||||
const transaction = view.state.tr.insert(from, contentNode);
|
||||
view.dispatch(transaction);
|
||||
this.onValueChanged(this.editor.value);
|
||||
this.onChange(this.editor.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,7 +281,7 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
if (contentNode) {
|
||||
const transaction = view.state.tr.insert(to, contentNode);
|
||||
view.dispatch(transaction);
|
||||
this.onValueChanged(this.editor.value);
|
||||
this.onChange(this.editor.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,7 +308,7 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
const transaction = view.state.tr.replaceWith(from, to, contentNode);
|
||||
view.dispatch(transaction);
|
||||
}
|
||||
this.onValueChanged(this.editor.value);
|
||||
this.onChange(this.editor.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -286,7 +366,7 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
if (contentNode) {
|
||||
const transaction = view.state.tr.insert(safePos, contentNode);
|
||||
view.dispatch(transaction);
|
||||
this.onValueChanged(this.editor.value);
|
||||
this.onChange(this.editor.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,21 +389,21 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
if (contentNode) {
|
||||
const transaction = view.state.tr.replaceWith(safeFrom, safeTo, contentNode);
|
||||
view.dispatch(transaction);
|
||||
this.onValueChanged(this.editor.value);
|
||||
this.onChange(this.editor.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
showVariablePicker() {
|
||||
this.easyEditorService.openTableMultiPicker(this.variables, this.variableTableSettings, "Please select a variable").pipe(first()).subscribe(result => {
|
||||
if (result) {
|
||||
result.forEach(c => {
|
||||
this.insertAfterSelection(`<${RbjTagNode} class="rbj-tag" ${RbjTagIdAttribute}="${c.name}" ${RbjTagValueAttribute}="${c.name}">${c.name}</${RbjTagNode}>`, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// showVariablePicker() {
|
||||
// this.easyEditorService.openTableMultiPicker(this.variables, this.variableTableSettings, "Please select a variable").pipe(first()).subscribe(result => {
|
||||
// if (result) {
|
||||
// result.forEach(c => {
|
||||
// this.insertAfterSelection(`<${RbjTagNode} class="rbj-tag" ${RbjTagIdAttribute}="${c.name}" ${RbjTagValueAttribute}="${c.name}">${c.name}</${RbjTagNode}>`, true);
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
// Private methods
|
||||
@ -342,9 +422,10 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor {
|
||||
export const rbjTagNodeSpec: NodeSpec = {
|
||||
// Define the node attributes for the tag
|
||||
attrs: {
|
||||
"rbj-tag-id": { default: "" },
|
||||
"tag-value": { default: "" },
|
||||
"tag-preview": { default: "" }
|
||||
"md2-icon": { default: "" },
|
||||
"class": { default: "" },
|
||||
// "tag-value": { default: "" },
|
||||
// "tag-preview": { default: "" }
|
||||
},
|
||||
// Specify that this node should be treated as an inline element
|
||||
inline: true,
|
||||
@ -355,23 +436,27 @@ export const rbjTagNodeSpec: NodeSpec = {
|
||||
|
||||
// Define how the node should be rendered in the DOM
|
||||
toDOM: (node) => {
|
||||
let tagPreview = node.attrs["tag-preview"];
|
||||
let displayValue = tagPreview == 'true' ? node.attrs["tag-value"] : node.attrs["rbj-tag-id"];
|
||||
let md2IconText = node.attrs["md2-icon"] as string;
|
||||
let classValue = node.attrs["class"] as string;
|
||||
if (classValue.includes('dice')) {
|
||||
md2IconText = '';
|
||||
}
|
||||
// let displayValue = tagPreview == 'true' ? node.attrs["tag-value"] : node.attrs["rbj-tag-id"];
|
||||
|
||||
return [
|
||||
"span",
|
||||
{
|
||||
class: "rbj-tag",
|
||||
"rbj-tag-id": node.attrs["rbj-tag-id"],
|
||||
"tag-marker": node.attrs["tag-marker"],
|
||||
"tag-value": node.attrs["tag-value"],
|
||||
"tag-preview": node.attrs["tag-preview"],
|
||||
//contenteditable: "false",
|
||||
//spellcheck: "false",
|
||||
class: classValue,
|
||||
// "rbj-tag-id": node.attrs["rbj-tag-id"],
|
||||
// "tag-marker": node.attrs["tag-marker"],
|
||||
// "tag-value": node.attrs["tag-value"],
|
||||
// "tag-preview": node.attrs["tag-preview"],
|
||||
contenteditable: "false",
|
||||
//spellcheck: "false", style="font-size: 36px;"
|
||||
style: "display: inline;"
|
||||
},
|
||||
//node.attrs["tag-marker"] + node.attrs["tag-value"] // Display the tag content directly
|
||||
displayValue
|
||||
md2IconText
|
||||
];
|
||||
},
|
||||
|
||||
@ -379,32 +464,20 @@ export const rbjTagNodeSpec: NodeSpec = {
|
||||
parseDOM: [
|
||||
{
|
||||
// Look for span elements with class rbj-tag (higher priority)
|
||||
tag: "span[rbj-tag-id]",
|
||||
tag: "span[md2-icon]",
|
||||
priority: 51, // Higher priority to catch before other parsers
|
||||
// Extract attributes from the DOM element
|
||||
getAttrs: (dom) => {
|
||||
const element = dom as HTMLElement;
|
||||
// Must have rbj-tag-id attribute to be valid
|
||||
if (!element.hasAttribute("rbj-tag-id")) {
|
||||
if (!element.hasAttribute("md2-icon")) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
"rbj-tag-id": element.getAttribute("rbj-tag-id") || "",
|
||||
"tag-preview": element.getAttribute("tag-preview") || "false",
|
||||
"tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id")
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
// Look for div elements with rbj-tag-id attribute (for backward compatibility)
|
||||
tag: "div[rbj-tag-id]",
|
||||
// Extract attributes from the DOM element
|
||||
getAttrs: (dom) => {
|
||||
const element = dom as HTMLElement;
|
||||
return {
|
||||
"rbj-tag-id": element.getAttribute("rbj-tag-id") || "",
|
||||
"tag-preview": element.getAttribute("tag-preview") || "false",
|
||||
"tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id")
|
||||
"md2-icon": element.getAttribute("md2-icon") || "",
|
||||
"class": element.className || "",
|
||||
// "tag-preview": element.getAttribute("tag-preview") || "false",
|
||||
// "tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,36 +2,42 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { NbDialogRef } from '@nebular/theme';
|
||||
import { MD2Icon } from '../massive-darkness2.model';
|
||||
import { MD2StateService } from '../../../services/MD2/md2-state.service';
|
||||
import { DialogRef } from '@progress/kendo-angular-dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'md2-icon-picker-dlg',
|
||||
template: `
|
||||
<nb-card style="max-width: 800px;">
|
||||
template: ` <div class="md2-icon-grid">
|
||||
<div
|
||||
*ngFor="let iconData of iconList"
|
||||
(click)="selectIcon(iconData)"
|
||||
class="icon-item" title="{{iconData.name}}"
|
||||
[innerHTML]="iconData.html">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <nb-card>
|
||||
<nb-card-header>
|
||||
<h5>Insert MD2 Icon</h5>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="icon-grid">
|
||||
<div
|
||||
*ngFor="let iconData of iconList"
|
||||
(click)="selectIcon(iconData)"
|
||||
class="icon-item"
|
||||
[innerHTML]="iconData.html">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nb-card-body>
|
||||
<nb-card-footer>
|
||||
<button nbButton status="primary" (click)="cancel()">Cancel</button>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
</nb-card> -->
|
||||
`,
|
||||
styles: [`
|
||||
.icon-grid {
|
||||
nb-card{
|
||||
max-width: 800px;
|
||||
z-index: 1050;
|
||||
}
|
||||
.md2-icon-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
|
||||
gap: 10px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
width:400px;
|
||||
}
|
||||
.icon-item {
|
||||
cursor: pointer;
|
||||
@ -42,11 +48,12 @@ import { MD2StateService } from '../../../services/MD2/md2-state.service';
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
font-size: 30px;
|
||||
}
|
||||
.icon-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
border-color: #007bff;
|
||||
transform: scale(1.1);
|
||||
//transform: scale(1.1);
|
||||
}
|
||||
`]
|
||||
})
|
||||
@ -55,7 +62,7 @@ export class MD2IconPickerDlgComponent implements OnInit {
|
||||
iconList: Array<{ icon: MD2Icon, name: string, html: string }> = [];
|
||||
|
||||
constructor(
|
||||
private dlgRef: NbDialogRef<MD2IconPickerDlgComponent>,
|
||||
private dlgRef: DialogRef,
|
||||
private md2StateService: MD2StateService
|
||||
) { }
|
||||
|
||||
|
||||
@ -1 +1,5 @@
|
||||
<span class="MD2Icon {{iconName}} {{iconClass}} {{sizeClass}}"></span>
|
||||
@if(isImageIcon) {
|
||||
<span [innerHTML]="imgUrl" class="{{sizeClass}} mx-2"></span>
|
||||
} @else {
|
||||
<span [innerHtml]="iconHtml" class="{{sizeClass}} mx-2"></span>
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
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({
|
||||
selector: 'md2-icon',
|
||||
@ -9,14 +10,25 @@ import { MD2Icon } from '../massive-darkness2.model';
|
||||
export class MD2IconComponent implements OnInit {
|
||||
|
||||
@Input() iconClass: string = 'mr-1';
|
||||
|
||||
|
||||
isImageIcon: boolean = false;
|
||||
imgUrl: string;
|
||||
iconHtml: string;
|
||||
private _icon: string | MD2Icon;
|
||||
|
||||
@Input() public set icon(v: string | MD2Icon) {
|
||||
if (v !== undefined) {
|
||||
if (this._icon != v) {
|
||||
this._icon = v;
|
||||
|
||||
//if it's string, convert it to MD2Icon
|
||||
if (typeof v === 'string') {
|
||||
const key = Object.keys(MD2Icon).find(
|
||||
k => k.toLowerCase() === v.toString().toLowerCase()
|
||||
);
|
||||
if (key) {
|
||||
v = MD2Icon[key as keyof typeof MD2Icon];
|
||||
}
|
||||
}
|
||||
this.initIcon(v as MD2Icon);
|
||||
}
|
||||
if (this.isMD2Icon(v)) {
|
||||
this.iconName = MD2Icon[v].toLowerCase();
|
||||
@ -25,30 +37,43 @@ export class MD2IconComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isMD2Icon(icon: MD2Icon | string): icon is MD2Icon {
|
||||
return Number.isInteger(icon);
|
||||
}
|
||||
@Input() size: string = 'sm';
|
||||
iconName: string;
|
||||
constructor() { }
|
||||
constructor(private md2StateService: MD2StateService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
private initIcon(icon: MD2Icon): void {
|
||||
if (icon < MD2Icon.TreasureToken) {
|
||||
this.isImageIcon = false;
|
||||
this.iconHtml = this.md2StateService.iconHtml(icon);
|
||||
} else {
|
||||
this.isImageIcon = true;
|
||||
this.imgUrl = this.md2StateService.iconHtml(icon);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,187 @@
|
||||
<div class="k-dialog-content detail-container">
|
||||
<div class="mob-info-section">
|
||||
<h3>{{ mobInfo?.name }}</h3>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<label>Type:</label>
|
||||
<span>{{ getMobTypeName(mobInfo?.type) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Game Bundle:</label>
|
||||
<span>{{ getGameBundleName(mobInfo?.from) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Leader Image:</label>
|
||||
<span>{{ mobInfo?.leaderImgUrl || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>Minion Image:</label>
|
||||
<span>{{ mobInfo?.minionImgUrl || 'N/A' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<kendo-tabstrip tabPosition="top">
|
||||
<kendo-tabstrip-tab title="Levels" [selected]="true">
|
||||
<ng-template kendoTabContent>
|
||||
<div class="levels-toolbar">
|
||||
<button kendoButton (click)="addLevelHandler()" [primary]="true">
|
||||
<span class="k-icon k-i-plus"></span> Add Level
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<kendo-grid #levelsGrid [data]="mobLevelInfos" [loading]="isLoading" [pageSize]="levelsState.take"
|
||||
[skip]="levelsState.skip" [sortable]="true" [filterable]="true" [pageable]="true" [height]="300"
|
||||
(remove)="removeLevelHandler($event)" (dataStateChange)="levelsState = $event">
|
||||
|
||||
<kendo-grid-column field="level" title="Level" [width]="50">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.level }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<!-- <kendo-grid-column field="fixedHp" title="HP" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.fixedHp }}
|
||||
</ng-template>
|
||||
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||
<kendo-numerictextbox [(ngModel)]="dataItem.fixedHp" [name]="'fixedHp_' + rowIndex" [min]="0" [decimals]="0"
|
||||
[format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</ng-template>
|
||||
</kendo-grid-column> -->
|
||||
|
||||
<kendo-grid-column field="hpPerHero" title="HP/Hero" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.hpPerHero }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<!-- <kendo-grid-column field="actions" title="Actions" [width]="100">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.actions }}
|
||||
</ng-template>
|
||||
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||
<kendo-numerictextbox [(ngModel)]="dataItem.actions" [name]="'actions_' + rowIndex" [min]="0" [decimals]="0"
|
||||
[format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</ng-template>
|
||||
</kendo-grid-column> -->
|
||||
|
||||
<kendo-grid-column field="rewardTokens" title="Reward Tokens" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.rewardTokens }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="fixedRareTreasure" title="Rare Treasure" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.fixedRareTreasure }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="fixedEpicTreasure" title="Epic Treasure" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.fixedEpicTreasure }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="fixedLegendTreasure" title="Legend Treasure" [width]="60">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.fixedLegendTreasure }}
|
||||
</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>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-command-column title="Actions" [width]="133">
|
||||
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||
<button kendoButton [primary]="true" (click)="editLevelHandler(dataItem)">Edit</button>
|
||||
<button kendoGridRemoveCommand>Remove</button>
|
||||
</ng-template>
|
||||
</kendo-grid-command-column>
|
||||
</kendo-grid>
|
||||
</ng-template>
|
||||
</kendo-tabstrip-tab>
|
||||
<kendo-tabstrip-tab title="Skills">
|
||||
<ng-template kendoTabContent>
|
||||
<div class="skills-toolbar">
|
||||
<button kendoButton (click)="addSkillHandler()" [primary]="true">
|
||||
<span class="k-icon k-i-plus"></span> Add Skill
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<kendo-grid #skillsGrid [data]="skillsData" [loading]="isLoading" [pageSize]="skillsState.take"
|
||||
[skip]="skillsState.skip" [sortable]="true" [filterable]="true" [pageable]="true" [height]="400"
|
||||
(remove)="removeSkillHandler($event)" (dataStateChange)="skillsState = $event">
|
||||
|
||||
|
||||
<kendo-grid-column field="seq" title="Seq" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.seq }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
<kendo-grid-column field="level" title="Level" [width]="80">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.level }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
<kendo-grid-column field="name" title="Name" [width]="150">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.name }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="type" title="Type" [width]="120">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ getSkillTypeName(dataItem.type) }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="skillTarget" title="Target" [width]="150">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ getSkillTargetName(dataItem.skillTarget) }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="skillRoll" title="Skill Roll" [width]="100">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.skillRoll }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="clawRoll" title="Claw Roll" [width]="100">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ dataItem.clawRoll }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
|
||||
<kendo-grid-column field="description" title="Description" [width]="300">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
<div *ngIf="dataItem.type == MobSkillType.ConditionalSkill" [innerHTML]="dataItem.skillCondition"></div>
|
||||
<div [innerHTML]="dataItem.description"></div>
|
||||
</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 kendoButton [primary]="true" (click)="editSkillHandler(dataItem)">Edit</button>
|
||||
<button kendoGridRemoveCommand>Remove</button>
|
||||
</ng-template>
|
||||
</kendo-grid-command-column>
|
||||
</kendo-grid>
|
||||
</ng-template>
|
||||
</kendo-tabstrip-tab>
|
||||
</kendo-tabstrip>
|
||||
|
||||
</div>
|
||||
|
||||
<kendo-dialog-actions>
|
||||
<button kendoButton [primary]="true" (click)="close()">Close</button>
|
||||
</kendo-dialog-actions>
|
||||
@ -0,0 +1,56 @@
|
||||
.k-dialog-content {
|
||||
padding: 20px;
|
||||
max-height: 80vh;
|
||||
//overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.mob-info-section {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding-bottom: 15px;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grd;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
label {
|
||||
font-weight: 500;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.levels-section {
|
||||
h4 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.levels-toolbar {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.skills-section {
|
||||
h4 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skills-toolbar {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@ -0,0 +1,431 @@
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { DialogRef, DialogContentBase, DialogService } from '@progress/kendo-angular-dialog';
|
||||
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||
import { State } from '@progress/kendo-data-query';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2MobInfo, MD2MobLevelInfo, MD2MobSkill, MobSkillTarget, GameBundle } from '../../massive-darkness2.db.model';
|
||||
import { MobType } from '../../massive-darkness2.model';
|
||||
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',
|
||||
templateUrl: './md2-mob-info-detail.component.html',
|
||||
styleUrls: ['./md2-mob-info-detail.component.scss']
|
||||
})
|
||||
export class MD2MobInfoDetailComponent extends DialogContentBase implements OnInit {
|
||||
MobSkillType = MobSkillType;
|
||||
@Input() public mobInfo: MD2MobInfo;
|
||||
@ViewChild('levelsGrid') levelsGrid: GridComponent;
|
||||
@ViewChild('skillsGrid') skillsGrid: GridComponent;
|
||||
|
||||
public mobLevelInfos: MD2MobLevelInfo[] = [];
|
||||
public levelsData: GridDataResult = { data: [], total: 0 };
|
||||
public levelsState: State = {
|
||||
skip: 0,
|
||||
take: 10,
|
||||
sort: [],
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: []
|
||||
}
|
||||
};
|
||||
public selectedLevelInfo: MD2MobLevelInfo | null = null;
|
||||
public skillsData: GridDataResult = { data: [], total: 0 };
|
||||
public skillsState: State = {
|
||||
skip: 0,
|
||||
take: 10,
|
||||
sort: [],
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: []
|
||||
}
|
||||
};
|
||||
public isLoading: boolean = false;
|
||||
|
||||
public skillTypes: Array<{ value: MobSkillType; text: string }> = [];
|
||||
public skillTargets: Array<{ value: MobSkillTarget | null; text: string }> = [];
|
||||
|
||||
constructor(
|
||||
public dialog: DialogRef,
|
||||
private dialogService: DialogService,
|
||||
private mobLevelInfoService: MD2MobLevelInfoService,
|
||||
private mobSkillService: MD2MobSkillService,
|
||||
private msgBoxService: MsgBoxService
|
||||
) {
|
||||
super(dialog);
|
||||
this.initializeEnums();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.mobInfo) {
|
||||
this.mobLevelInfos = this.mobInfo.mobLevelInfos || [];
|
||||
if (this.mobLevelInfos.length > 0) {
|
||||
this.selectLevelInfo(this.mobLevelInfos[0]);
|
||||
}
|
||||
this.loadSkills();
|
||||
}
|
||||
}
|
||||
|
||||
private initializeEnums(): void {
|
||||
// Initialize MobSkillType options
|
||||
Object.keys(MobSkillType).filter(key => isNaN(Number(key))).forEach(key => {
|
||||
this.skillTypes.push({
|
||||
value: MobSkillType[key] as MobSkillType,
|
||||
text: key
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize MobSkillTarget options
|
||||
this.skillTargets.push({ value: null, text: 'None' });
|
||||
Object.keys(MobSkillTarget).filter(key => isNaN(Number(key))).forEach(key => {
|
||||
this.skillTargets.push({
|
||||
value: MobSkillTarget[key] as MobSkillTarget,
|
||||
text: key
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public selectLevelInfo(levelInfo: MD2MobLevelInfo): void {
|
||||
if (levelInfo.defenceInfo == null) {
|
||||
levelInfo.defenceInfo = { type: MobSkillType.Defense, blue: 0, green: 0, yellow: 0, orange: 0, red: 0, black: 0 };
|
||||
}
|
||||
if (levelInfo.attackInfo == null) {
|
||||
levelInfo.attackInfo = { type: MobSkillType.Attack, blue: 0, green: 0, yellow: 0, orange: 0, red: 0, black: 0 };
|
||||
}
|
||||
this.selectedLevelInfo = levelInfo;
|
||||
this.loadSkills();
|
||||
}
|
||||
|
||||
public editLevelHandler(dataItem: MD2MobLevelInfo): void {
|
||||
if (!this.mobInfo) return;
|
||||
|
||||
// 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) return;
|
||||
|
||||
// Get the last level info (highest level) if any exists
|
||||
const lastLevel = this.mobLevelInfos.length > 0
|
||||
? this.mobLevelInfos.reduce((prev, current) => (prev.level > current.level) ? prev : current)
|
||||
: null;
|
||||
|
||||
// Calculate the next level number
|
||||
const nextLevel = lastLevel ? lastLevel.level + 2 : 1;
|
||||
let rewardTokens = 0;
|
||||
let actions = 1;
|
||||
|
||||
|
||||
let newLevel: MD2MobLevelInfo;
|
||||
|
||||
if (lastLevel) {
|
||||
// Copy the last level's info as initial values
|
||||
newLevel = {
|
||||
id: this.generateLevelId(),
|
||||
level: nextLevel,
|
||||
mobInfoId: this.mobInfo.id,
|
||||
rewardTokens: rewardTokens,
|
||||
fixedRareTreasure: lastLevel.fixedRareTreasure ?? 0,
|
||||
fixedEpicTreasure: lastLevel.fixedEpicTreasure ?? 0,
|
||||
fixedLegendTreasure: lastLevel.fixedLegendTreasure ?? 0,
|
||||
fixedHp: lastLevel.fixedHp ?? 0,
|
||||
hpPerHero: lastLevel.hpPerHero ?? 0,
|
||||
actions: lastLevel.actions ?? actions,
|
||||
attackInfo: lastLevel.attackInfo
|
||||
? { ...lastLevel.attackInfo }
|
||||
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||
defenceInfo: lastLevel.defenceInfo
|
||||
? { ...lastLevel.defenceInfo }
|
||||
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
|
||||
};
|
||||
} else {
|
||||
// Default values if no levels exist
|
||||
newLevel = {
|
||||
id: this.generateLevelId(),
|
||||
level: nextLevel,
|
||||
mobInfoId: this.mobInfo.id,
|
||||
rewardTokens: rewardTokens,
|
||||
fixedRareTreasure: 0,
|
||||
fixedEpicTreasure: 0,
|
||||
fixedLegendTreasure: 0,
|
||||
fixedHp: 0,
|
||||
hpPerHero: 0,
|
||||
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 }
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 (!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 {
|
||||
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||
if (answer === true) {
|
||||
this.isLoading = true;
|
||||
this.mobLevelInfoService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||
const index = this.mobLevelInfos.findIndex(l => l.id === dataItem.id);
|
||||
if (index !== -1) {
|
||||
this.mobLevelInfos.splice(index, 1);
|
||||
// Clear selection if deleted level was selected
|
||||
if (this.selectedLevelInfo?.id === dataItem.id) {
|
||||
this.selectedLevelInfo = null;
|
||||
this.loadSkills();
|
||||
}
|
||||
}
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public loadSkills(): void {
|
||||
if (this.mobInfo && this.mobInfo.skills) {
|
||||
// Filter skills by selected level if a level is selected
|
||||
let filteredSkills = this.mobInfo.skills.sort((a, b) => a.seq - b.seq);
|
||||
// if (this.selectedLevelInfo) {
|
||||
// filteredSkills = this.mobInfo.skills.filter(s => s.level === this.selectedLevelInfo.level);
|
||||
// }
|
||||
this.skillsData = {
|
||||
data: filteredSkills,
|
||||
total: filteredSkills.length
|
||||
};
|
||||
} else {
|
||||
this.skillsData = { data: [], total: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
public addSkillHandler(): void {
|
||||
if (!this.mobInfo) return;
|
||||
|
||||
// Get the last level info (highest level) if any exists
|
||||
const lastLevel = this.mobInfo.skills.length > 0
|
||||
? this.mobInfo.skills.reduce((prev, current) => (prev.seq > current.seq) ? prev : current)
|
||||
: null;
|
||||
let seq = 0;
|
||||
let level = 1;
|
||||
let type = MobSkillType.Combat;
|
||||
|
||||
if (lastLevel) {
|
||||
seq = lastLevel.seq + 1;
|
||||
level = lastLevel.level + 2;
|
||||
type = lastLevel.type;
|
||||
}
|
||||
const newSkill: MD2MobSkill = {
|
||||
id: this.generateId(),
|
||||
seq: seq,
|
||||
level: level,
|
||||
mobInfoId: this.mobInfo.id,
|
||||
type: type,
|
||||
skillTarget: MobSkillTarget.Random,
|
||||
clawRoll: 0,
|
||||
skillRoll: 1,
|
||||
name: 'Basic Skill',
|
||||
skillCondition: '',
|
||||
description: lastLevel?.description || ''
|
||||
};
|
||||
|
||||
this.openSkillEditor(newSkill, true);
|
||||
}
|
||||
|
||||
public editSkillHandler(dataItem: MD2MobSkill): void {
|
||||
if (!this.mobInfo) return;
|
||||
|
||||
// Create a copy of the skill for editing
|
||||
const skillCopy: MD2MobSkill = JSON.parse(JSON.stringify(dataItem));
|
||||
this.openSkillEditor(skillCopy, false);
|
||||
}
|
||||
|
||||
private openSkillEditor(skill: MD2MobSkill, isNew: boolean): void {
|
||||
if (!this.mobInfo) return;
|
||||
|
||||
const dialogRef = this.dialogService.open({
|
||||
title: isNew ? 'Add New Skill' : 'Edit Skill',
|
||||
content: MD2MobSkillEditorComponent,
|
||||
width: '80vw',
|
||||
height: 700
|
||||
});
|
||||
|
||||
const editor = dialogRef.content.instance;
|
||||
editor.isAdding = isNew;
|
||||
editor.data = skill;
|
||||
editor.mobInfoId = this.mobInfo.id;
|
||||
editor.selectedLevel = this.selectedLevelInfo?.level || 1;
|
||||
|
||||
// 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.handleSkillSaved(result as MD2MobSkill, isNew);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleSkillSaved(result: MD2MobSkill, isNew: boolean): void {
|
||||
if (!this.mobInfo) return;
|
||||
|
||||
if (isNew) {
|
||||
if (!this.mobInfo.skills) {
|
||||
this.mobInfo.skills = [];
|
||||
}
|
||||
this.mobInfo.skills.push(result);
|
||||
} else {
|
||||
const index = this.mobInfo.skills?.findIndex(s => s.id === result.id);
|
||||
if (index !== undefined && index !== -1 && this.mobInfo.skills) {
|
||||
this.mobInfo.skills[index] = result;
|
||||
}
|
||||
}
|
||||
this.loadSkills();
|
||||
}
|
||||
|
||||
public saveSkillHandler({ dataItem, isNew }: any): void {
|
||||
// This method is no longer used but kept for backward compatibility
|
||||
// Skills are now edited via dialog
|
||||
}
|
||||
|
||||
public removeSkillHandler({ dataItem }: { dataItem: MD2MobSkill }): void {
|
||||
if (!this.mobInfo) return;
|
||||
|
||||
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||
if (answer === true) {
|
||||
this.isLoading = true;
|
||||
this.mobSkillService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||
if (this.mobInfo.skills) {
|
||||
const index = this.mobInfo.skills.findIndex(s => s.id === dataItem.id);
|
||||
if (index !== -1) {
|
||||
this.mobInfo.skills.splice(index, 1);
|
||||
}
|
||||
this.loadSkills();
|
||||
}
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private generateId(): string {
|
||||
return 'skill_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
private generateLevelId(): string {
|
||||
return 'level_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
public getSkillTypeName(type: MobSkillType): string {
|
||||
return MobSkillType[type] || '';
|
||||
}
|
||||
|
||||
public getSkillTargetName(target: MobSkillTarget | null): string {
|
||||
if (target === null) return 'None';
|
||||
return MobSkillTarget[target] || '';
|
||||
}
|
||||
|
||||
public getMobTypeName(type: MobType): string {
|
||||
return MobType[type] || '';
|
||||
}
|
||||
|
||||
public getGameBundleName(bundle: GameBundle): string {
|
||||
return GameBundle[bundle] || '';
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.dialog.close(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
<div class="k-dialog-content">
|
||||
<form #form="ngForm" class="k-form">
|
||||
<div class="k-form-field">
|
||||
<label class="k-label">Name *</label>
|
||||
<input kendoTextBox [(ngModel)]="model.name" name="name" required class="k-input"
|
||||
placeholder="Enter mob name" />
|
||||
<span class="k-form-error" *ngIf="form.controls['name']?.invalid && form.controls['name']?.touched">
|
||||
Name is required
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="k-form-field">
|
||||
<label class="k-label">Type *</label>
|
||||
<kendo-dropdownlist [(ngModel)]="selectedMobType" name="type" [data]="mobTypes" [valueField]="'value'"
|
||||
[textField]="'text'" [defaultItem]="{ value: null, text: 'Select type...' }">
|
||||
</kendo-dropdownlist>
|
||||
</div>
|
||||
|
||||
<div class="k-form-field">
|
||||
<label class="k-label">Game Bundle *</label>
|
||||
<kendo-dropdownlist [(ngModel)]="selectedGameBundle" name="from" [data]="gameBundles" [valueField]="'value'"
|
||||
[textField]="'text'" [defaultItem]="{ value: null, text: 'Select bundle...' }">
|
||||
</kendo-dropdownlist>
|
||||
</div>
|
||||
|
||||
<div class="k-form-field">
|
||||
<label class="k-label">Leader Image URL</label>
|
||||
<input kendoTextBox [(ngModel)]="model.leaderImgUrl" name="leaderImgUrl" class="k-input"
|
||||
placeholder="Enter leader image URL" />
|
||||
</div>
|
||||
|
||||
<div class="k-form-field">
|
||||
<label class="k-label">Minion Image URL</label>
|
||||
<input kendoTextBox [(ngModel)]="model.minionImgUrl" name="minionImgUrl" class="k-input"
|
||||
placeholder="Enter minion image URL" />
|
||||
</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>
|
||||
@ -0,0 +1,29 @@
|
||||
.k-dialog-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.k-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.k-form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.k-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.k-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.k-form-error {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2MobInfo, GameBundle } from '../../massive-darkness2.db.model';
|
||||
import { MobType } from '../../massive-darkness2.model';
|
||||
import { MD2MobInfoService } from '../../service/massive-darkness2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-mob-info-editor',
|
||||
templateUrl: './md2-mob-info-editor.component.html',
|
||||
styleUrls: ['./md2-mob-info-editor.component.scss']
|
||||
})
|
||||
export class MD2MobInfoEditorComponent extends DialogContentBase implements OnInit {
|
||||
@Input() public data: MD2MobInfo;
|
||||
@Input() public isAdding: boolean = false;
|
||||
@ViewChild('form') form: NgForm;
|
||||
|
||||
public model: MD2MobInfo;
|
||||
public processing: boolean = false;
|
||||
public mobTypes: Array<{ value: MobType; text: string }> = [];
|
||||
public gameBundles: Array<{ value: GameBundle; text: string }> = [];
|
||||
public selectedMobType: { value: MobType; text: string } | null = null;
|
||||
public selectedGameBundle: { value: GameBundle; text: string } | null = null;
|
||||
|
||||
constructor(
|
||||
public dialog: DialogRef,
|
||||
private mobInfoService: MD2MobInfoService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
super(dialog);
|
||||
this.initializeEnums();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initializeModel();
|
||||
}
|
||||
|
||||
public initializeModel(): void {
|
||||
const typeValue = this.data?.type !== undefined && this.data?.type !== null ? this.data.type : MobType.Mob;
|
||||
const fromValue = this.data?.from !== undefined && this.data?.from !== null ? this.data.from : GameBundle.CoreGame;
|
||||
|
||||
this.model = {
|
||||
id: this.data?.id || '',
|
||||
name: this.data?.name || '',
|
||||
type: typeValue,
|
||||
from: fromValue,
|
||||
leaderImgUrl: this.data?.leaderImgUrl || '',
|
||||
minionImgUrl: this.data?.minionImgUrl || '',
|
||||
mobLevelInfos: this.data?.mobLevelInfos || [],
|
||||
skills: this.data?.skills || []
|
||||
};
|
||||
|
||||
// Set selected objects for dropdowns
|
||||
this.selectedMobType = this.mobTypes.find(t => t.value === typeValue) || this.mobTypes[0] || null;
|
||||
this.selectedGameBundle = this.gameBundles.find(b => b.value === fromValue) || this.gameBundles[0] || null;
|
||||
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
|
||||
private initializeEnums(): void {
|
||||
// Initialize MobType options
|
||||
Object.keys(MobType).filter(key => isNaN(Number(key))).forEach(key => {
|
||||
this.mobTypes.push({
|
||||
value: MobType[key] as MobType,
|
||||
text: key
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize GameBundle options
|
||||
Object.keys(GameBundle).filter(key => isNaN(Number(key))).forEach(key => {
|
||||
this.gameBundles.push({
|
||||
value: GameBundle[key] as GameBundle,
|
||||
text: key
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.dialog.close();
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
if (this.model.name && !this.processing) {
|
||||
this.processing = true;
|
||||
|
||||
// Extract enum values from selected objects
|
||||
const mobInfo: MD2MobInfo = {
|
||||
...this.model,
|
||||
type: this.selectedMobType?.value ?? MobType.Mob,
|
||||
from: this.selectedGameBundle?.value ?? GameBundle.CoreGame,
|
||||
mobLevelInfos: this.data?.mobLevelInfos || [],
|
||||
skills: this.data?.skills || []
|
||||
};
|
||||
|
||||
this.mobInfoService.createOrUpdate(mobInfo).pipe(first()).subscribe(result => {
|
||||
this.processing = false;
|
||||
this.dialog.close(result);
|
||||
}, error => {
|
||||
this.processing = false;
|
||||
console.error('Error saving mob info:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
if (!this.model) {
|
||||
return false;
|
||||
}
|
||||
const nameValid = this.model.name && this.model.name.trim().length > 0;
|
||||
const typeValid = this.selectedMobType !== null && this.selectedMobType !== undefined;
|
||||
const fromValid = this.selectedGameBundle !== null && this.selectedGameBundle !== undefined;
|
||||
return nameValid && typeValid && fromValid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
<nb-card>
|
||||
<nb-card-header>
|
||||
<h4>MD2 Mob Info Maintenance</h4>
|
||||
<div class="float-right">
|
||||
<button kendoButton (click)="addHandler()" [primary]="true">
|
||||
<span class="k-icon k-i-plus"></span> Add New
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<kendo-grid #grid [data]="gridData" [loading]="isLoading" [pageSize]="gridState.take" [skip]="gridState.skip"
|
||||
[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>
|
||||
</kendo-grid-toolbar>
|
||||
|
||||
<kendo-grid-column field="name" title="Name" [width]="200">
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="type" title="Type" [width]="150">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ getMobTypeName(dataItem.type) }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="from" title="Game Bundle" [width]="150">
|
||||
<ng-template kendoGridCellTemplate let-dataItem>
|
||||
{{ getGameBundleName(dataItem.from) }}
|
||||
</ng-template>
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="leaderImgUrl" title="Leader Image" [width]="200">
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-column field="minionImgUrl" title="Minion Image" [width]="200">
|
||||
</kendo-grid-column>
|
||||
|
||||
<kendo-grid-command-column title="Actions" [width]="250">
|
||||
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||
<button kendoGridEditCommand [primary]="true">Edit</button>
|
||||
<button kendoGridRemoveCommand>Remove</button>
|
||||
<button kendoButton (click)="viewDetailHandler({ dataItem })" [look]="'flat'">
|
||||
View Details
|
||||
</button>
|
||||
</ng-template>
|
||||
</kendo-grid-command-column>
|
||||
</kendo-grid>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
@ -0,0 +1,12 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
kendo-grid {
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
@ -0,0 +1,162 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||
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';
|
||||
import { MD2MobInfoService } from '../service/massive-darkness2.service';
|
||||
import { MD2MobInfoDetailComponent } from './md2-mob-info-detail/md2-mob-info-detail.component';
|
||||
import { MD2MobInfoEditorComponent } from './md2-mob-info-editor/md2-mob-info-editor.component';
|
||||
import { DialogService } from '@progress/kendo-angular-dialog';
|
||||
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-mob-info-maintenance',
|
||||
templateUrl: './md2-mob-info-maintenance.component.html',
|
||||
styleUrls: ['./md2-mob-info-maintenance.component.scss']
|
||||
})
|
||||
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,
|
||||
sort: [{
|
||||
field: 'name',
|
||||
dir: 'asc'
|
||||
}],
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: []
|
||||
},
|
||||
group: [{
|
||||
field: 'type',
|
||||
dir: 'asc'
|
||||
}]
|
||||
};
|
||||
public isLoading: boolean = false;
|
||||
|
||||
constructor(
|
||||
private mobInfoService: MD2MobInfoService,
|
||||
private dialogService: DialogService,
|
||||
private msgBoxService: MsgBoxService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
public loadData(): void {
|
||||
this.isLoading = true;
|
||||
this.mobInfoService.getAll().pipe(first()).subscribe(result => {
|
||||
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({
|
||||
title: 'Add New Mob Info',
|
||||
content: MD2MobInfoEditorComponent,
|
||||
width: '90vw',
|
||||
height: 600
|
||||
});
|
||||
|
||||
const editor = dialogRef.content.instance;
|
||||
editor.isAdding = true;
|
||||
editor.data = editorData;
|
||||
|
||||
// Force model re-initialization after data is set
|
||||
setTimeout(() => {
|
||||
editor.initializeModel();
|
||||
}, 0);
|
||||
|
||||
dialogRef.result.subscribe(result => {
|
||||
if (result) {
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public editHandler({ dataItem }: { dataItem: MD2MobInfo }): void {
|
||||
const dialogRef = this.dialogService.open({
|
||||
title: 'Edit Mob Info',
|
||||
content: MD2MobInfoEditorComponent,
|
||||
width: '90vw',
|
||||
height: 600
|
||||
});
|
||||
|
||||
const editor = dialogRef.content.instance;
|
||||
editor.isAdding = false;
|
||||
editor.data = JSON.parse(JSON.stringify(dataItem));
|
||||
|
||||
dialogRef.result.subscribe(result => {
|
||||
if (result) {
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public removeHandler({ dataItem }: { dataItem: MD2MobInfo }): void {
|
||||
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||
if (answer === true) {
|
||||
this.isLoading = true;
|
||||
this.mobInfoService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||
this.loadData();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public viewDetailHandler({ dataItem }: { dataItem: MD2MobInfo }): void {
|
||||
this.mobInfoService.getById(dataItem.id).pipe(first()).subscribe(mobInfo => {
|
||||
const dialogRef = this.dialogService.open({
|
||||
title: `Mob Info: ${mobInfo.name}`,
|
||||
content: MD2MobInfoDetailComponent,
|
||||
width: '90vw',
|
||||
height: 800
|
||||
});
|
||||
|
||||
const detail = dialogRef.content.instance;
|
||||
detail.mobInfo = mobInfo;
|
||||
|
||||
dialogRef.result.subscribe(result => {
|
||||
if (result) {
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getMobTypeName(type: MobType): string {
|
||||
return MobType[type] || '';
|
||||
}
|
||||
|
||||
public getGameBundleName(bundle: GameBundle): string {
|
||||
return GameBundle[bundle] || '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 !== '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
<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">Sequence</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.seq" name="seq" [min]="0" [decimals]="0" [format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
<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-6">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Name</label>
|
||||
<input kendoTextBox [(ngModel)]="model.name" name="name" class="k-input"
|
||||
placeholder="Enter skill name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Type *</label>
|
||||
<kendo-dropdownlist [(ngModel)]="selectedSkillType" name="type" [data]="skillTypes"
|
||||
[valueField]="'value'" [textField]="'text'"
|
||||
[defaultItem]="{ value: null, text: 'Select type...' }">
|
||||
</kendo-dropdownlist>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Target</label>
|
||||
<kendo-dropdownlist [(ngModel)]="selectedSkillTarget" name="skillTarget" [data]="skillTargets"
|
||||
[valueField]="'value'" [textField]="'text'">
|
||||
</kendo-dropdownlist>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="k-label"><md2-icon icon="EnemySkill"></md2-icon>Skill Roll</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.skillRoll" name="skillRoll" [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="EnemyClaw"></md2-icon>Claw Roll</label>
|
||||
<kendo-numerictextbox [(ngModel)]="model.clawRoll" name="clawRoll" [min]="0" [decimals]="0"
|
||||
[format]="'n0'">
|
||||
</kendo-numerictextbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12" *ngIf="selectedSkillType.value == MobSkillType.ConditionalSkill">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Skill Condition</label>
|
||||
<md2-html-editor [(ngModel)]="model.skillCondition" name="skillCondition"
|
||||
class="htmlEditor"></md2-html-editor>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="k-label">Description</label>
|
||||
<md2-html-editor [(ngModel)]="model.description" name="description"
|
||||
class="htmlEditor"></md2-html-editor>
|
||||
</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>
|
||||
@ -0,0 +1,8 @@
|
||||
.k-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
md2-html-editor {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { MD2MobSkill, MobSkillTarget } from '../../massive-darkness2.db.model';
|
||||
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||
import { MD2MobSkillService } from '../../service/massive-darkness2.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-md2-mob-skill-editor',
|
||||
templateUrl: './md2-mob-skill-editor.component.html',
|
||||
styleUrls: ['./md2-mob-skill-editor.component.scss']
|
||||
})
|
||||
export class MD2MobSkillEditorComponent extends DialogContentBase implements OnInit {
|
||||
MobSkillType = MobSkillType;
|
||||
@Input() public data: MD2MobSkill;
|
||||
@Input() public mobInfoId: string;
|
||||
@Input() public selectedLevel: number = 1;
|
||||
@Input() public isAdding: boolean = false;
|
||||
@ViewChild('form') form: NgForm;
|
||||
|
||||
public model: MD2MobSkill;
|
||||
public processing: boolean = false;
|
||||
public skillTypes: Array<{ value: MobSkillType; text: string }> = [];
|
||||
public skillTargets: Array<{ value: MobSkillTarget | null; text: string }> = [];
|
||||
public selectedSkillType: { value: MobSkillType; text: string } | null = null;
|
||||
public selectedSkillTarget: { value: MobSkillTarget | null; text: string } | null = null;
|
||||
|
||||
constructor(
|
||||
public dialog: DialogRef,
|
||||
private mobSkillService: MD2MobSkillService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
super(dialog);
|
||||
this.initializeEnums();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initializeModel();
|
||||
}
|
||||
|
||||
public initializeModel(): void {
|
||||
const typeValue = this.data?.type !== undefined && this.data?.type !== null ? this.data.type : MobSkillType.Combat;
|
||||
const targetValue = this.data?.skillTarget !== undefined ? this.data.skillTarget : null;
|
||||
|
||||
this.model = {
|
||||
id: this.data?.id || '',
|
||||
seq: this.data?.seq ?? 0,
|
||||
level: this.data?.level ?? this.selectedLevel,
|
||||
mobInfoId: this.mobInfoId || this.data?.mobInfoId || '',
|
||||
type: typeValue,
|
||||
skillTarget: targetValue,
|
||||
clawRoll: this.data?.clawRoll ?? 0,
|
||||
skillRoll: this.data?.skillRoll ?? 1,
|
||||
name: this.data?.name || '',
|
||||
skillCondition: this.data?.skillCondition || '',
|
||||
description: this.data?.description || ''
|
||||
};
|
||||
|
||||
// Set selected objects for dropdowns
|
||||
this.selectedSkillType = this.skillTypes.find(t => t.value === typeValue) || this.skillTypes[0] || null;
|
||||
this.selectedSkillTarget = this.skillTargets.find(t => t.value === targetValue) || this.skillTargets[0] || null;
|
||||
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
private initializeEnums(): void {
|
||||
// Initialize MobSkillType options
|
||||
Object.keys(MobSkillType).filter(key => isNaN(Number(key))).forEach(key => {
|
||||
this.skillTypes.push({
|
||||
value: MobSkillType[key] as MobSkillType,
|
||||
text: key
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize MobSkillTarget options
|
||||
this.skillTargets.push({ value: null, text: 'None' });
|
||||
Object.keys(MobSkillTarget).filter(key => isNaN(Number(key))).forEach(key => {
|
||||
this.skillTargets.push({
|
||||
value: MobSkillTarget[key] as MobSkillTarget,
|
||||
text: key
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.dialog.close();
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
if (!this.processing) {
|
||||
this.processing = true;
|
||||
|
||||
// Extract enum values from selected objects
|
||||
const mobSkill: MD2MobSkill = {
|
||||
...this.model,
|
||||
type: this.selectedSkillType?.value ?? MobSkillType.Combat,
|
||||
skillTarget: this.selectedSkillTarget?.value ?? null,
|
||||
mobInfoId: this.mobInfoId || this.model.mobInfoId,
|
||||
level: this.model.level || this.selectedLevel
|
||||
};
|
||||
|
||||
this.mobSkillService.createOrUpdate(mobSkill).pipe(first()).subscribe(result => {
|
||||
this.processing = false;
|
||||
this.dialog.close(result);
|
||||
}, error => {
|
||||
this.processing = false;
|
||||
console.error('Error saving mob skill:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
if (!this.model) {
|
||||
return false;
|
||||
}
|
||||
const typeValid = this.selectedSkillType !== null && this.selectedSkillType !== undefined;
|
||||
return typeValid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,10 +15,18 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group' *ngIf="showSkill">
|
||||
<label for='' class='MD2text g-font-size-22 label mb-2'>
|
||||
<div class='form-group'>
|
||||
<!-- <label for='' class='MD2text g-font-size-22 label mb-2'>
|
||||
<md2-icon icon="blackDice" size="lg"></md2-icon> {{skillTriggerHtml}} <md2-icon icon="enemySkill" size="md">
|
||||
</md2-icon>
|
||||
</label> -->
|
||||
<div *ngFor="let skill of mob.skills" class=" g-brd-bottom--dashed g-brd-gray-light-v2">
|
||||
<div *ngIf="skill.uiDisplay">
|
||||
<label for='' class='MD2text g-font-size-22 label mb-2'>
|
||||
{{MobSkillType[skill.type]}} {{skill.skillRoll}} <md2-icon icon="enemySkill" size="md"></md2-icon>
|
||||
</label>
|
||||
<div class='g-font-size-20 skillDesc MD2text' [innerHtml]="mob.combatSkill.description"></div>
|
||||
<div class='g-font-size-20 skillDesc MD2text' [innerHtml]="skill.description"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -23,6 +23,9 @@
|
||||
font-size: 45px;
|
||||
}
|
||||
}
|
||||
.skillDesc .MD2Icon {
|
||||
font-size: 45px;
|
||||
|
||||
//override the style of the skillDesc class and sub elements
|
||||
|
||||
:host ::ng-deep .skillDesc .MD2Icon {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import { MobSkillType } from '../../../massive-darkness2.model.boss';
|
||||
styleUrls: ['./mob-combat-info.component.scss']
|
||||
})
|
||||
export class MobCombatInfoComponent implements OnInit {
|
||||
|
||||
MobSkillType = MobSkillType;
|
||||
MD2Icon = MD2Icon;
|
||||
private _mob: MobInfo;
|
||||
public get mob(): MobInfo {
|
||||
@ -28,23 +28,23 @@ export class MobCombatInfoComponent implements OnInit {
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.mob.combatSkill) {
|
||||
if (this.mob.skills && this.mob.skills.length > 0) {
|
||||
this.mob.skills.forEach(element => {
|
||||
switch (this.mode) {
|
||||
case MobDlgType.Activating:
|
||||
this.showSkill = [MobSkillType.Combat, MobSkillType.Attack].includes(this.mob.combatSkill.type);
|
||||
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Attack].includes(element.type);
|
||||
break;
|
||||
case MobDlgType.BeenAttacked:
|
||||
this.showSkill = [MobSkillType.Combat, MobSkillType.Defense].includes(this.mob.combatSkill.type);
|
||||
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Defense].includes(element.type);
|
||||
break;
|
||||
case MobDlgType.PreView:
|
||||
this.showSkill = true;
|
||||
element.uiDisplay = true;
|
||||
break;
|
||||
case MobDlgType.Spawn:
|
||||
default:
|
||||
this.showSkill = false;
|
||||
element.uiDisplay = false;
|
||||
break;
|
||||
}
|
||||
this.skillTriggerHtml = `${MobSkillType[this.mob.combatSkill.type]} ${this.mob.combatSkill.skillRoll} `
|
||||
});
|
||||
}
|
||||
|
||||
this.showBlackDice = this.mob.type == MobType.Mob && (this.mode == MobDlgType.Activating || this.mode == MobDlgType.BeenAttacked) && this.mob.minionAmount > 0;;
|
||||
|
||||
@ -5,12 +5,12 @@
|
||||
<md2-icon icon="defense" size="lg"></md2-icon>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div *ngIf="mob.defenseInfo.blue" class="g-height-45">
|
||||
<div *ngIf="mob.defenseInfo?.blue" class="g-height-45">
|
||||
<span class="MD2Icon Blue dice g-font-size-50">
|
||||
<span class="MD2text diceAmount">x{{mob.defenseInfo.blue}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="mob.defenseInfo.black" class="g-height-45 mt-1">
|
||||
<div *ngIf="mob.defenseInfo?.black" class="g-height-45 mt-1">
|
||||
<span class="MD2Icon Black dice g-font-size-50">
|
||||
<span class="MD2text diceAmount">x{{mob.defenseInfo.black}}</span>
|
||||
</span>
|
||||
|
||||
@ -38,6 +38,9 @@ export class MobDefInfoComponent implements OnInit {
|
||||
this.display = false;
|
||||
break;
|
||||
}
|
||||
if (!this.mob.defenseInfo || this.mob.defenseInfo.blue == 0 && this.mob.defenseInfo.black == 0) {
|
||||
this.display = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { NbDialogService } from '@nebular/theme';
|
||||
import { stringify } from 'querystring';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { DropDownOption } from '../../../entity/dropDownOption';
|
||||
import { FileService } from '../../../services/file.service';
|
||||
@ -9,11 +8,9 @@ import { MD2MobService } from '../../../services/MD2/md2-mob.service';
|
||||
import { MD2Service } from '../../../services/MD2/md2.service';
|
||||
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
import { ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
|
||||
import { ArrayUtils } from '../../../utilities/array-utils';
|
||||
import { ADButtons, ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
|
||||
import { NumberUtils } from '../../../utilities/number-utils';
|
||||
import { StringUtils } from '../../../utilities/string-utils';
|
||||
import { CoreGameMobFactories } from '../factorys/mobs/CoreGame';
|
||||
import { CoreGameRMFactories } from '../factorys/roamingMonsters/CoreGame';
|
||||
import { DrawingBag, DrawingItem, MD2Icon, MobDlgType, MobInfo, TreasureType } from '../massive-darkness2.model';
|
||||
import { MD2Base, MD2ComponentBase } from '../MD2Base';
|
||||
@ -92,10 +89,19 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
|
||||
}
|
||||
|
||||
spawnSpecificMob() {
|
||||
let mobOptions = this.isRoamingMonster ? CoreGameRMFactories.map(f => new DropDownOption(f.mobName, f.mobName)) : CoreGameMobFactories.map(f => new DropDownOption(f.mobName, f.mobName));
|
||||
this.msgBoxService.showInputbox('Spawn', '', { inputType: 'dropdown', dropDownOptions: mobOptions }).pipe(first()).subscribe(mobName => {
|
||||
if (mobName) {
|
||||
let mobOptions = this.isRoamingMonster ? CoreGameRMFactories.map(f => new DropDownOption(f.mobName, f.mobName)) : this.md2Service.allMobInfos.map(f => new DropDownOption(f.name, f.name));
|
||||
this.msgBoxService.showInputbox('Spawn', '',
|
||||
{
|
||||
inputType: 'dropdown', dropDownOptions: mobOptions,
|
||||
buttons: ADButtons.YesNoCancel,
|
||||
confirmButtonText: 'Spawn',
|
||||
cancelButtonText: 'Random'
|
||||
}).pipe(first()).subscribe(mobName => {
|
||||
|
||||
|
||||
if (mobName || mobName === false) {
|
||||
|
||||
if (!mobName) { mobName = null; }
|
||||
let result = this.md2Service.spawnMob(this.isRoamingMonster, mobName);
|
||||
let titleText = result.exitingMob == null ? `${result.mob.description} Shows Up` : `${result.mob.description} Activate One Action Now!`;
|
||||
let actType = result.exitingMob == null ? MobDlgType.Spawn : MobDlgType.Activating;
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CrudService } from '../../../services/crudServices/crud.service';
|
||||
import { MD2MobInfo, MD2MobLevelInfo, MD2MobSkill } from '../massive-darkness2.db.model';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MD2HeroProfile } from '../massive-darkness2.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MD2MobInfoService extends CrudService<MD2MobInfo> {
|
||||
|
||||
constructor(http: HttpClient) {
|
||||
super(http, (action: string = null) => { return `MD2MobInfo${(action ? `/${action}` : '')}` });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MD2MobLevelInfoService extends CrudService<MD2MobLevelInfo> {
|
||||
|
||||
constructor(http: HttpClient) {
|
||||
super(http, (action: string = null) => { return `MD2MobLevelInfo${(action ? `/${action}` : '')}` });
|
||||
}
|
||||
|
||||
}
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MD2MobSkillService extends CrudService<MD2MobSkill> {
|
||||
|
||||
constructor(http: HttpClient) {
|
||||
super(http, (action: string = null) => { return `MD2MobSkill${(action ? `/${action}` : '')}` });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MD2HeroProfileService extends CrudService<MD2HeroProfile> {
|
||||
|
||||
constructor(http: HttpClient) {
|
||||
super(http, (action: string = null) => { return `MD2HeroProfile${(action ? `/${action}` : '')}` });
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreGameMobFactories } from '../../games/massive-darkness2/factorys/mobs/CoreGame';
|
||||
import { CoreGameRMFactories } from '../../games/massive-darkness2/factorys/roamingMonsters/CoreGame';
|
||||
import { DrawingBag, MobInfo, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { MD2StateService } from './md2-state.service';
|
||||
import { MD2MobInfoService } from '../../games/massive-darkness2/service/massive-darkness2.service';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -10,7 +11,8 @@ import { MD2StateService } from './md2-state.service';
|
||||
export class MD2InitService {
|
||||
|
||||
constructor(
|
||||
private stateService: MD2StateService
|
||||
private stateService: MD2StateService,
|
||||
private mobInfoService: MD2MobInfoService,
|
||||
) { }
|
||||
|
||||
public initMobDecks() {
|
||||
@ -31,11 +33,26 @@ export class MD2InitService {
|
||||
|
||||
}
|
||||
private initCoreGameMobs() {
|
||||
CoreGameMobFactories.forEach(factory => {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
this.stateService.mobDeck.AddItem(new MobInfo({ name: factory.mobName, level: i, drawingWeight: 1 }));
|
||||
i++;
|
||||
// CoreGameMobFactories.forEach(factory => {
|
||||
// for (let i = 1; i <= 5; i++) {
|
||||
// this.stateService.mobDeck.AddItem(new MobInfo({ name: factory.mobName, level: i, drawingWeight: 1 }));
|
||||
// i++;
|
||||
// }
|
||||
// });
|
||||
this.mobInfoService.getAll().pipe(first()).subscribe(result => {
|
||||
this.stateService.mobInfos = result;
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const mobInfo = result[i];
|
||||
for (let j = 0; j < mobInfo.mobLevelInfos.length; j++) {
|
||||
const levelInfo = mobInfo.mobLevelInfos[j];
|
||||
|
||||
this.stateService.mobDeck.AddItem(new MobInfo({ name: mobInfo.name, level: levelInfo.level, drawingWeight: 1 }));
|
||||
}
|
||||
}
|
||||
|
||||
// result.forEach(mobInfo => {
|
||||
// this.stateService.mobDeck.AddItem(new MobInfo({ name: mobInfo.name, level: mobInfo.level, drawingWeight: 1 }));
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { DrawingBag, MD2HeroInfo, MD2Icon, MobInfo, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { FileService } from '../file.service';
|
||||
import { MD2GameInfo } from './md2.service';
|
||||
import { MD2MobInfo } from '../../games/massive-darkness2/massive-darkness2.db.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -13,6 +14,7 @@ export class MD2StateService {
|
||||
|
||||
public info: MD2GameInfo;
|
||||
public playerHero: MD2HeroInfo;
|
||||
public mobInfos: MD2MobInfo[] = [];
|
||||
public mobDeck: DrawingBag<MobInfo>;
|
||||
public roamingMobDeck: DrawingBag<MobInfo>;
|
||||
public treasureBag: DrawingBag<TreasureItem> = new DrawingBag<TreasureItem>();
|
||||
@ -31,9 +33,39 @@ export class MD2StateService {
|
||||
cssClass += ' g-color-aqua ';
|
||||
}
|
||||
if (icon < MD2Icon.RedDice) {
|
||||
return `<span class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
|
||||
return `<span md2-icon='${String.fromCharCode(65 + icon)}' class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
|
||||
} else if (icon < MD2Icon.TreasureToken) {
|
||||
return `<span md2-icon='${String.fromCharCode(65 + icon)}' class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
|
||||
} else {
|
||||
return `<span class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
|
||||
if (!cssClass) {
|
||||
cssClass = 'g-height-25 mr-1';
|
||||
}
|
||||
//image based icons
|
||||
switch (icon) {
|
||||
|
||||
case MD2Icon.HP_Color:
|
||||
return this.imgHtml('HeartIcon.png', cssClass);
|
||||
case MD2Icon.Mana_Color:
|
||||
return this.imgHtml('ManaIcon.png', cssClass);
|
||||
case MD2Icon.CorruptToken:
|
||||
return this.imgHtml('Tokens/CorruptToken.png', cssClass);
|
||||
case MD2Icon.TimeToken:
|
||||
return this.imgHtml('Tokens/TimeToken.png', cssClass);
|
||||
case MD2Icon.FireToken:
|
||||
return this.imgHtml('Tokens/FireToken.png', cssClass);
|
||||
case MD2Icon.FrozenToken:
|
||||
return this.imgHtml('Tokens/FrozenToken.png', cssClass);
|
||||
case MD2Icon.TreasureToken:
|
||||
return this.imgHtml('TreasureToken/Cover.png', cssClass);
|
||||
case MD2Icon.TreasureToken_Common:
|
||||
return this.imgHtml('TreasureToken/Common.png', cssClass);
|
||||
case MD2Icon.TreasureToken_Rare:
|
||||
return this.imgHtml('TreasureToken/Rare.png', cssClass);
|
||||
case MD2Icon.TreasureToken_Epic:
|
||||
return this.imgHtml('TreasureToken/Epic.png', cssClass);
|
||||
case MD2Icon.TreasureToken_Legendary:
|
||||
return this.imgHtml('TreasureToken/Legendary.png', cssClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AttackInfo, AttackTarget, CoreGameDarknessPhaseRule, DefenseInfo, DrawingBag, DrawingItem, HeroClass, IDarknessPhaseRule, MD2HeroInfo, MD2Icon, MD2Rules, MobInfo, MobType, RoundPhase, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { AttackInfo, AttackTarget, CoreGameDarknessPhaseRule, DrawingBag, DrawingItem, HeroClass, IDarknessPhaseRule, MD2HeroInfo, MD2Icon, MD2Rules, MobInfo, MobType, RoundPhase, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
|
||||
import { first, map, reduce } from "rxjs/operators";
|
||||
import { NbDialogService } from '@nebular/theme';
|
||||
import { Subject } from 'rxjs';
|
||||
@ -14,14 +14,16 @@ import { MsgBoxService } from '../msg-box.service';
|
||||
import { SignalRService, SignalRSession, SignalRMessage } from '../signal-r.service';
|
||||
import { MD2StateService } from './md2-state.service';
|
||||
import { MD2BroadcastService } from './md2-broadcast.service';
|
||||
import { CoreGameMobFactories } from '../../games/massive-darkness2/factorys/mobs/CoreGame';
|
||||
import { MD2Logic } from '../../games/massive-darkness2/massive-darkness2.logic';
|
||||
import { CoreGameRMFactories } from '../../games/massive-darkness2/factorys/roamingMonsters/CoreGame';
|
||||
import { MD2InitService } from './md2-init.service';
|
||||
import { ArrayUtils } from '../../utilities/array-utils';
|
||||
import { DropDownOption } from '../../entity/dropDownOption';
|
||||
import { GameBundle, MD2MobInfo } from '../../games/massive-darkness2/massive-darkness2.db.model';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
|
||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` };
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@ -49,6 +51,9 @@ export class MD2Service {
|
||||
public get mobs() {
|
||||
return this.stateService.info.mobs;
|
||||
}
|
||||
public get allMobInfos() {
|
||||
return this.stateService.mobInfos;
|
||||
}
|
||||
|
||||
|
||||
public get info(): MD2GameInfo {
|
||||
@ -176,7 +181,7 @@ export class MD2Service {
|
||||
exitingMob.imageUrl = newSpawnMob.imageUrl;
|
||||
exitingMob.attackInfos = newSpawnMob.attackInfos;
|
||||
exitingMob.defenseInfo = newSpawnMob.defenseInfo;
|
||||
exitingMob.combatSkill = newSpawnMob.combatSkill;
|
||||
exitingMob.skills = newSpawnMob.skills;
|
||||
} else {
|
||||
if (isRoamingMonster) {
|
||||
newSpawnMob = CoreGameRMFactories.find(f => f.mobName == newSpawnMob.name).generate(level);
|
||||
@ -185,19 +190,57 @@ export class MD2Service {
|
||||
newSpawnMob.mobAmount = 0;
|
||||
this.roamingMonsters.push(newSpawnMob);
|
||||
} else {
|
||||
newSpawnMob = CoreGameMobFactories.find(f => f.mobName == newSpawnMob.name).generate(level);
|
||||
newSpawnMob.mobAmount = this.playerAmount + 1;
|
||||
newSpawnMob = this.generateMob(newSpawnMob.name, level);
|
||||
this.mobs.push(newSpawnMob);
|
||||
}
|
||||
|
||||
newSpawnMob.carriedTreasure = this.treasureBag.DrawAndRemove(newSpawnMob.rewardTokens);
|
||||
|
||||
this.refreshTreasureBagSubject.next(this.treasureBag);
|
||||
}
|
||||
newSpawnMob.imageUrl = this.mobImage(newSpawnMob.name, newSpawnMob.level, isRoamingMonster);
|
||||
|
||||
return { exitingMob, mob: newSpawnMob };
|
||||
}
|
||||
private generateMob(mobName: string, level: number) {
|
||||
let dbMobInfo = this.allMobInfos.find(m => m.name == mobName);
|
||||
let levelInfo = dbMobInfo.mobLevelInfos.find(l => l.level <= level);
|
||||
let skills = dbMobInfo.skills.filter(s => s.level <= level).sort((a, b) => b.level - a.level);
|
||||
//Distinct by SkillType and Skill Name, take the one with the highest level
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let mobInfo = new MobInfo({
|
||||
name: dbMobInfo.name,
|
||||
hp: levelInfo.hpPerHero,
|
||||
hpPerHero: levelInfo.hpPerHero,
|
||||
level: level,
|
||||
rewardTokens: levelInfo.rewardTokens,
|
||||
defenseInfo: levelInfo.defenceInfo,
|
||||
skills: []
|
||||
});
|
||||
|
||||
for (let i = 0; i < skills.length; i++) {
|
||||
const skill = skills[i];
|
||||
if (!mobInfo.skills.find(s => s.type == skill.type && s.name == skill.name)) {
|
||||
mobInfo.skills.push(skill);
|
||||
}
|
||||
}
|
||||
mobInfo.skills = mobInfo.skills.sort((a, b) => b.seq - a.seq);
|
||||
|
||||
mobInfo.leaderImgUrl = mobInfo.leaderImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`);
|
||||
mobInfo.minionImgUrl = mobInfo.minionImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`);
|
||||
|
||||
mobInfo.fixedCarriedTreasure = [];
|
||||
levelInfo.fixedRareTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Rare, levelInfo.fixedRareTreasure));
|
||||
levelInfo.fixedEpicTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Epic, levelInfo.fixedEpicTreasure));
|
||||
levelInfo.fixedLegendTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Legendary, levelInfo.fixedLegendTreasure));
|
||||
//Maybe add more mobs later
|
||||
mobInfo.mobAmount = this.playerAmount + 1;
|
||||
return mobInfo;
|
||||
}
|
||||
public enemyPhase() {
|
||||
//this.msgBoxService
|
||||
this.enemyPhaseMobs = this.roamingMonsters.concat(this.mobs);
|
||||
|
||||
@ -178,165 +178,165 @@ export class CombinedKeyCrudService<T> implements ICrudService<T> {
|
||||
}
|
||||
|
||||
}
|
||||
// Type definitions
|
||||
type TextResponse = { message: string };
|
||||
/**
|
||||
* Base CRUD service that targets the provided controller path.
|
||||
*
|
||||
* It mirrors the endpoints of CrudBaseApiController<T>:
|
||||
* GET /api/{controller}
|
||||
* GET /api/{controller}/{id}
|
||||
* POST /api/{controller} -> string
|
||||
* POST /api/{controller}/batch -> string[]
|
||||
* PUT /api/{controller}
|
||||
* PUT /api/{controller}/batch -> number
|
||||
* DELETE /api/{controller}/{id}
|
||||
* DELETE /api/{controller}/batch -> text summary
|
||||
* GET /api/{controller}/{id}/exists -> boolean
|
||||
* GET /api/{controller}/count -> number
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CrudBaseApiService<T extends object> {
|
||||
/**
|
||||
* Example: baseUrl = 'https://your-api', controller = 'Customer' →
|
||||
* endpoint = 'https://your-api/api/Customer'
|
||||
*/
|
||||
protected readonly endpoint: string;
|
||||
// // Type definitions
|
||||
// type TextResponse = { message: string };
|
||||
// /**
|
||||
// * Base CRUD service that targets the provided controller path.
|
||||
// *
|
||||
// * It mirrors the endpoints of CrudBaseApiController<T>:
|
||||
// * GET /api/{controller}
|
||||
// * GET /api/{controller}/{id}
|
||||
// * POST /api/{controller} -> string
|
||||
// * POST /api/{controller}/batch -> string[]
|
||||
// * PUT /api/{controller}
|
||||
// * PUT /api/{controller}/batch -> number
|
||||
// * DELETE /api/{controller}/{id}
|
||||
// * DELETE /api/{controller}/batch -> text summary
|
||||
// * GET /api/{controller}/{id}/exists -> boolean
|
||||
// * GET /api/{controller}/count -> number
|
||||
// */
|
||||
// @Injectable({ providedIn: 'root' })
|
||||
// export class CrudBaseApiService<T extends object> {
|
||||
// /**
|
||||
// * Example: baseUrl = 'https://your-api', controller = 'Customer' →
|
||||
// * endpoint = 'https://your-api/api/Customer'
|
||||
// */
|
||||
// protected readonly endpoint: string;
|
||||
|
||||
|
||||
/**
|
||||
* @param http Angular HttpClient
|
||||
* @param baseUrl API root without trailing slash (e.g., environment.apiBaseUrl)
|
||||
* @param controllerName Controller name (e.g., 'Customer', 'Orders')
|
||||
*/
|
||||
constructor(
|
||||
protected http: HttpClient,
|
||||
protected apiConfig: ApiConfigService,
|
||||
@Inject(String) private controllerName: string
|
||||
) {
|
||||
this.endpoint = apiConfig.getApiUrl(this.controllerName);
|
||||
}
|
||||
// /**
|
||||
// * @param http Angular HttpClient
|
||||
// * @param baseUrl API root without trailing slash (e.g., environment.apiBaseUrl)
|
||||
// * @param controllerName Controller name (e.g., 'Customer', 'Orders')
|
||||
// */
|
||||
// constructor(
|
||||
// protected http: HttpClient,
|
||||
// protected apiConfig: ApiConfigService,
|
||||
// @Inject(String) private controllerName: string
|
||||
// ) {
|
||||
// this.endpoint = apiConfig.getApiUrl(this.controllerName);
|
||||
// }
|
||||
|
||||
|
||||
/** Optional default headers (JSON). Override in subclasses if needed. */
|
||||
protected get jsonHeaders(): HttpHeaders {
|
||||
return new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
}
|
||||
// /** Optional default headers (JSON). Override in subclasses if needed. */
|
||||
// protected get jsonHeaders(): HttpHeaders {
|
||||
// return new HttpHeaders({ 'Content-Type': 'application/json' });
|
||||
// }
|
||||
|
||||
|
||||
/** Shared error handler that surfaces useful messages. */
|
||||
protected handleError(error: HttpErrorResponse): Observable<never> {
|
||||
let msg = 'Unknown error';
|
||||
if (error.error instanceof Blob) {
|
||||
// In case backend returns text/plain; charset=utf-8 as Blob
|
||||
return throwError(() => new Error('Server returned an error blob'));
|
||||
}
|
||||
if (typeof error.error === 'string') msg = error.error;
|
||||
else if (error.error?.message) msg = error.error.message;
|
||||
else if (error.message) msg = error.message;
|
||||
return throwError(() => new Error(msg));
|
||||
}
|
||||
/** Prepare the response for the given entity. Override in subclasses if needed. */
|
||||
protected prepareResponse(response: T): T {
|
||||
// Do nothing by default
|
||||
return response;
|
||||
}
|
||||
// /** Shared error handler that surfaces useful messages. */
|
||||
// protected handleError(error: HttpErrorResponse): Observable<never> {
|
||||
// let msg = 'Unknown error';
|
||||
// if (error.error instanceof Blob) {
|
||||
// // In case backend returns text/plain; charset=utf-8 as Blob
|
||||
// return throwError(() => new Error('Server returned an error blob'));
|
||||
// }
|
||||
// if (typeof error.error === 'string') msg = error.error;
|
||||
// else if (error.error?.message) msg = error.error.message;
|
||||
// else if (error.message) msg = error.message;
|
||||
// return throwError(() => new Error(msg));
|
||||
// }
|
||||
// /** Prepare the response for the given entity. Override in subclasses if needed. */
|
||||
// protected prepareResponse(response: T): T {
|
||||
// // Do nothing by default
|
||||
// return response;
|
||||
// }
|
||||
|
||||
/** GET /api/{controller} */
|
||||
getAll(): Observable<T[]> {
|
||||
return this.http
|
||||
.get<T[]>(this.endpoint)
|
||||
.pipe(
|
||||
map(response => {
|
||||
// /** GET /api/{controller} */
|
||||
// getAll(): Observable<T[]> {
|
||||
// return this.http
|
||||
// .get<T[]>(this.endpoint)
|
||||
// .pipe(
|
||||
// map(response => {
|
||||
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const element = response[i];
|
||||
response[i] = this.prepareResponse(element);
|
||||
}
|
||||
return response;
|
||||
}),
|
||||
catchError(err => this.handleError(err)));
|
||||
}
|
||||
// for (let i = 0; i < response.length; i++) {
|
||||
// const element = response[i];
|
||||
// response[i] = this.prepareResponse(element);
|
||||
// }
|
||||
// return response;
|
||||
// }),
|
||||
// catchError(err => this.handleError(err)));
|
||||
// }
|
||||
|
||||
|
||||
/** GET /api/{controller}/{id} */
|
||||
getById(id: string): Observable<T> {
|
||||
return this.http
|
||||
.get<T>(`${this.endpoint}/${id}`)
|
||||
.pipe(
|
||||
map(response => this.prepareResponse(response)),
|
||||
catchError(err => this.handleError(err)));
|
||||
}
|
||||
// /** GET /api/{controller}/{id} */
|
||||
// getById(id: string): Observable<T> {
|
||||
// return this.http
|
||||
// .get<T>(`${this.endpoint}/${id}`)
|
||||
// .pipe(
|
||||
// map(response => this.prepareResponse(response)),
|
||||
// catchError(err => this.handleError(err)));
|
||||
// }
|
||||
|
||||
|
||||
|
||||
/** POST /api/{controller} -> string */
|
||||
create(entity: T): Observable<string> {
|
||||
return this.http
|
||||
.post<string>(this.endpoint, entity, { headers: this.jsonHeaders })
|
||||
.pipe(catchError(err => this.handleError(err)));
|
||||
}
|
||||
// /** POST /api/{controller} -> string */
|
||||
// create(entity: T): Observable<string> {
|
||||
// return this.http
|
||||
// .post<string>(this.endpoint, entity, { headers: this.jsonHeaders })
|
||||
// .pipe(catchError(err => this.handleError(err)));
|
||||
// }
|
||||
|
||||
|
||||
/** POST /api/{controller}/batch -> string[] */
|
||||
createRange(entities: T[]): Observable<string[]> {
|
||||
return this.http
|
||||
.post<string[]>(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders })
|
||||
.pipe(catchError(err => this.handleError(err)));
|
||||
}
|
||||
// /** POST /api/{controller}/batch -> string[] */
|
||||
// createRange(entities: T[]): Observable<string[]> {
|
||||
// return this.http
|
||||
// .post<string[]>(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders })
|
||||
// .pipe(catchError(err => this.handleError(err)));
|
||||
// }
|
||||
|
||||
|
||||
/** PUT /api/{controller} */
|
||||
update(entity: T): Observable<void> {
|
||||
return this.http
|
||||
.put<void>(this.endpoint, entity, { headers: this.jsonHeaders })
|
||||
.pipe(catchError(err => this.handleError(err)));
|
||||
}
|
||||
// /** PUT /api/{controller} */
|
||||
// update(entity: T): Observable<void> {
|
||||
// return this.http
|
||||
// .put<void>(this.endpoint, entity, { headers: this.jsonHeaders })
|
||||
// .pipe(catchError(err => this.handleError(err)));
|
||||
// }
|
||||
|
||||
|
||||
/** PUT /api/{controller}/batch -> number (updated count) */
|
||||
updateRange(entities: T[]): Observable<number> {
|
||||
return this.http
|
||||
.put<number>(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders })
|
||||
.pipe(catchError(err => this.handleError(err)));
|
||||
}
|
||||
// /** PUT /api/{controller}/batch -> number (updated count) */
|
||||
// updateRange(entities: T[]): Observable<number> {
|
||||
// return this.http
|
||||
// .put<number>(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders })
|
||||
// .pipe(catchError(err => this.handleError(err)));
|
||||
// }
|
||||
|
||||
|
||||
/** DELETE /api/{controller}/{id} */
|
||||
delete(id: string): Observable<void> {
|
||||
return this.http
|
||||
.delete<void>(`${this.endpoint}/${id}`)
|
||||
.pipe(catchError(err => this.handleError(err)));
|
||||
}
|
||||
// /** DELETE /api/{controller}/{id} */
|
||||
// delete(id: string): Observable<void> {
|
||||
// return this.http
|
||||
// .delete<void>(`${this.endpoint}/${id}`)
|
||||
// .pipe(catchError(err => this.handleError(err)));
|
||||
// }
|
||||
|
||||
|
||||
/** DELETE /api/{controller}/batch -> text summary */
|
||||
deleteRange(ids: string[]): Observable<TextResponse> {
|
||||
// API returns a plain text message; map it into a TextResponse for convenience
|
||||
return this.http
|
||||
.delete(`${this.endpoint}/batch`, {
|
||||
body: ids,
|
||||
headers: this.jsonHeaders
|
||||
})
|
||||
.pipe(
|
||||
map((response: any) => ({ message: response || 'Batch delete completed' })),
|
||||
catchError(err => this.handleError(err))
|
||||
);
|
||||
}
|
||||
// /** DELETE /api/{controller}/batch -> text summary */
|
||||
// deleteRange(ids: string[]): Observable<TextResponse> {
|
||||
// // API returns a plain text message; map it into a TextResponse for convenience
|
||||
// return this.http
|
||||
// .delete(`${this.endpoint}/batch`, {
|
||||
// body: ids,
|
||||
// headers: this.jsonHeaders
|
||||
// })
|
||||
// .pipe(
|
||||
// map((response: any) => ({ message: response || 'Batch delete completed' })),
|
||||
// catchError(err => this.handleError(err))
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
/** GET /api/{controller}/{id}/exists -> boolean */
|
||||
exists(id: string): Observable<boolean> {
|
||||
return this.http
|
||||
.get<boolean>(`${this.endpoint}/${id}/exists`)
|
||||
.pipe(catchError(err => this.handleError(err)));
|
||||
}
|
||||
// /** GET /api/{controller}/{id}/exists -> boolean */
|
||||
// exists(id: string): Observable<boolean> {
|
||||
// return this.http
|
||||
// .get<boolean>(`${this.endpoint}/${id}/exists`)
|
||||
// .pipe(catchError(err => this.handleError(err)));
|
||||
// }
|
||||
|
||||
|
||||
/** GET /api/{controller}/count -> number */
|
||||
count(): Observable<number> {
|
||||
return this.http
|
||||
.get<number>(`${this.endpoint}/count`)
|
||||
.pipe(catchError(err => this.handleError(err)));
|
||||
}
|
||||
}
|
||||
// /** GET /api/{controller}/count -> number */
|
||||
// count(): Observable<number> {
|
||||
// return this.http
|
||||
// .get<number>(`${this.endpoint}/count`)
|
||||
// .pipe(catchError(err => this.handleError(err)));
|
||||
// }
|
||||
// }
|
||||
|
||||
@ -57,5 +57,16 @@ export class ArrayUtils {
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public static orderByThenBy<T>(
|
||||
array: T[],
|
||||
...criteria: ((a: T, b: T) => number)[]
|
||||
): T[] {
|
||||
return array.sort((a, b) => {
|
||||
for (const compare of criteria) {
|
||||
const result = compare(a, b);
|
||||
if (result !== 0) return result;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
//import { FtTagType } from "../components/fancy-table/fancy-row-column.model";
|
||||
//import { AddressInfo } from "../models/contactInfo.model";
|
||||
|
||||
import { stringify } from "querystring";
|
||||
import { AddressInfo } from "../entity/PastoralDomain";
|
||||
|
||||
export class StringUtils {
|
||||
|
||||
@ -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;
|
||||
@ -18589,62 +18588,6 @@ li:last-child .u-dot-line-v2::after, li:last-child
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
/* HZ
|
||||
------------------------------------*/
|
||||
.nav-item > a,
|
||||
.nav-item > .nav-link,
|
||||
[class*="u-tab-link"]:not([class*="-icon"]) {
|
||||
transition-property: color, background-color, border-color;
|
||||
transition-duration: .2s;
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
|
||||
[role="tablist"]:not([data-tabs-mobile-type="slide-up-down"]):not([data-tabs-mobile-type="accordion"]):not([data-scroll]) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[role="tablist"]:not([data-tabs-mobile-type="slide-up-down"]):not([data-tabs-mobile-type="accordion"]):not([data-scroll]) .nav-item {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
[role="tablist"]:not([data-tabs-mobile-type="slide-up-down"]):not([data-tabs-mobile-type="accordion"]):not([data-scroll]) .js-tabs-mobile {
|
||||
position: relative;
|
||||
display: none;
|
||||
}
|
||||
|
||||
[role="tablist"]:not([data-tabs-mobile-type="slide-up-down"]):not([data-tabs-mobile-type="accordion"]):not([data-scroll]) .js-tabs-mobile-control {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
[role="tablist"]:not([data-tabs-mobile-type="slide-up-down"]):not([data-tabs-mobile-type="accordion"]):not([data-scroll]) .js-tabs-mobile-control::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 5px 3.5px 0 3.5px;
|
||||
border-style: solid;
|
||||
border-color: #777 transparent transparent transparent;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
[role="tablist"]:not([data-tabs-mobile-type="slide-up-down"]):not([data-tabs-mobile-type="accordion"]):not([data-scroll]) .js-tabs-mobile .nav-inner {
|
||||
position: absolute;
|
||||
top: calc(100% + 1px);
|
||||
right: 0;
|
||||
display: none;
|
||||
background-color: #fff;
|
||||
border: 1px solid;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
[role="tablist"]:not([data-tabs-mobile-type="slide-up-down"]):not([data-tabs-mobile-type="accordion"]):not([data-scroll]) .js-tabs-mobile .nav-inner .nav-item {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-scroll]:not([data-tabs-mobile-type="slide-up-down"]):not([data-tabs-mobile-type="accordion"]) {
|
||||
width: 100%;
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
@use "../../../app/@theme/styles/themes";
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 1rem !important;
|
||||
}
|
||||
// ::-webkit-scrollbar {
|
||||
// width: 1rem !important;
|
||||
// }
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: nb-theme(color-primary-300) !important;
|
||||
}
|
||||
// ::-webkit-scrollbar-thumb {
|
||||
// background-color: nb-theme(color-primary-300) !important;
|
||||
// }
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: nb-theme(color-primary-200) !important;
|
||||
}
|
||||
// ::-webkit-scrollbar-track {
|
||||
// background: nb-theme(color-primary-200) !important;
|
||||
// }
|
||||
|
||||
.label {
|
||||
color: #736f6f;
|
||||
|
||||
@ -39,8 +39,15 @@
|
||||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
.shadow-skill {
|
||||
::before {
|
||||
font-family: "Massive Darkness 2", sans-serif !important;
|
||||
content: "D";
|
||||
}
|
||||
}
|
||||
.MD2Icon {
|
||||
font-family: "Massive Darkness 2", sans-serif !important;
|
||||
//font-size: 20px;
|
||||
//font-size: 50px;
|
||||
|
||||
&.attack::before {
|
||||
@ -127,6 +134,12 @@
|
||||
}
|
||||
color: #ffc107 !important;
|
||||
}
|
||||
&.diceGreen {
|
||||
&::before {
|
||||
content: "U";
|
||||
}
|
||||
color: #3df33d !important;
|
||||
}
|
||||
&.diceOrange {
|
||||
&::before {
|
||||
content: "U";
|
||||
@ -162,6 +175,9 @@
|
||||
&.Red {
|
||||
color: crimson !important;
|
||||
}
|
||||
&.Green {
|
||||
color: #3df33d !important;
|
||||
}
|
||||
&::before {
|
||||
content: "U";
|
||||
}
|
||||
|
||||
@ -20,3 +20,7 @@ nb-card {
|
||||
max-width: 96vw;
|
||||
max-height: 96vh;
|
||||
}
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ const urls = [
|
||||
'https://api.golife.love'
|
||||
];
|
||||
const LINE_CLIENT_ID = '1657422139';
|
||||
const dockerDebug = urls[2];
|
||||
const dockerDebug = urls[0];
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: dockerDebug,
|
||||
|
||||
@ -31,6 +31,9 @@
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
|
||||
|
||||
<link rel="stylesheet" href="./assets/home/css/styles.op-architecture.css">
|
||||
<!-- <link href="https://unpkg.com/@progress/kendo-theme-fluent@12.2.0/dist/fluent-main.css" rel="stylesheet" /> -->
|
||||
|
||||
|
||||
<!-- <link rel="stylesheet" href="./assets/home/css/">
|
||||
<link rel="stylesheet" href="./assets/home/css/">
|
||||
<link rel="stylesheet" href="./assets/home/css/"> -->
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/// <reference types="@angular/localize" />
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright Akveo. All Rights Reserved.
|
||||
|
||||
@ -40,7 +40,7 @@ import 'zone.js'; // Included with Angular CLI.
|
||||
*/
|
||||
import 'core-js/es7/array';
|
||||
import 'core-js/es7/object';
|
||||
|
||||
import '@angular/localize/init';
|
||||
if (typeof SVGElement.prototype.contains === 'undefined') {
|
||||
SVGElement.prototype.contains = HTMLDivElement.prototype.contains;
|
||||
}
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"baseUrl": "./"
|
||||
"baseUrl": "./",
|
||||
"types": [
|
||||
"@angular/localize"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"main.ts",
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
"baseUrl": "./",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
"node",
|
||||
"@angular/localize"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user