diff --git a/src/app/games/massive-darkness2/boss-fight/boss-activation/boss-activation.component.html b/src/app/games/massive-darkness2/boss-fight/boss-activation/boss-activation.component.html index 94ec154..828b33e 100644 --- a/src/app/games/massive-darkness2/boss-fight/boss-activation/boss-activation.component.html +++ b/src/app/games/massive-darkness2/boss-fight/boss-activation/boss-activation.component.html @@ -3,7 +3,7 @@
- +
@@ -17,11 +17,11 @@
- +
- +
diff --git a/src/app/games/massive-darkness2/boss-fight/boss-activation/boss-activation.component.ts b/src/app/games/massive-darkness2/boss-fight/boss-activation/boss-activation.component.ts index dc8e73c..fcebd93 100644 --- a/src/app/games/massive-darkness2/boss-fight/boss-activation/boss-activation.component.ts +++ b/src/app/games/massive-darkness2/boss-fight/boss-activation/boss-activation.component.ts @@ -6,10 +6,11 @@ import { takeUntil } from 'rxjs/operators'; import { MD2Service } from '../../../../services/MD2/md2.service'; import { MsgBoxService } from '../../../../services/msg-box.service'; import { StateService } from '../../../../services/state.service'; -import { MobDlgType, MD2Icon, MD2HeroInfo, RoundPhase } from '../../massive-darkness2.model'; +import { MobDlgType, MD2Icon, MD2HeroInfo, RoundPhase, MobInfo } from '../../massive-darkness2.model'; import { MobSkill, IBossFight } from '../../massive-darkness2.model.boss'; import { MD2ComponentBase } from '../../MD2Base'; import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.component'; +import { MD2MobInfo, MD2MobSkill } from '../../massive-darkness2.db.model'; @Component({ selector: 'ngx-boss-activation', @@ -17,8 +18,8 @@ import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.com styleUrls: ['./boss-activation.component.scss'] }) export class BossActivationComponent implements OnInit { - boss: IBossFight; - bossAction: MobSkill; + boss: MobInfo; + bossAction: MD2MobSkill; currentAction: number; allActions: number; MobDlgType = MobDlgType; diff --git a/src/app/games/massive-darkness2/boss-fight/boss-fight.component.html b/src/app/games/massive-darkness2/boss-fight/boss-fight.component.html index ffc8ead..50cc62a 100644 --- a/src/app/games/massive-darkness2/boss-fight/boss-fight.component.html +++ b/src/app/games/massive-darkness2/boss-fight/boss-fight.component.html @@ -8,17 +8,17 @@
- +
- {{boss.info.unitRemainHp}}/{{boss.info.hp}} + {{boss.unitRemainHp}}/{{boss.hp}}
+ [style.width.%]="(boss.unitRemainHp / boss.hp) * 100">
@@ -27,26 +27,26 @@
- - + - +
-
-
+
+
- + + --> -
diff --git a/src/app/games/massive-darkness2/boss-fight/boss-fight.component.scss b/src/app/games/massive-darkness2/boss-fight/boss-fight.component.scss index b87ca22..82da670 100644 --- a/src/app/games/massive-darkness2/boss-fight/boss-fight.component.scss +++ b/src/app/games/massive-darkness2/boss-fight/boss-fight.component.scss @@ -6,6 +6,11 @@ nb-card { max-height: 67vh; object-fit: contain; } +::ng-deep .bossSpecialRules { + .MD2Icon { + font-size: 30px; + } +} // HP and Mana Bars Overlay .hero-stats-overlay { position: absolute; diff --git a/src/app/games/massive-darkness2/boss-fight/boss-fight.component.ts b/src/app/games/massive-darkness2/boss-fight/boss-fight.component.ts index c92ebf9..ada88be 100644 --- a/src/app/games/massive-darkness2/boss-fight/boss-fight.component.ts +++ b/src/app/games/massive-darkness2/boss-fight/boss-fight.component.ts @@ -39,7 +39,7 @@ export class BossFightComponent extends MD2ComponentBase { super.ngOnInit(); this.md2Service.heroAttackingSubject.pipe(takeUntil(this.destroy$)).subscribe(result => { if (this.md2Service.info.isBossFight) { - this.attack(this.boss.info); + this.attack(this.boss); } }); } @@ -49,7 +49,7 @@ export class BossFightComponent extends MD2ComponentBase { this.destroy$.complete(); } activate() { - this.boss.activating(); + this.md2Service.activateBoss(); } WIN() { this.msgBoxService.show('Win', { text: 'You Win the Boss Fight', icon: ADIcon.INFO }); @@ -65,8 +65,8 @@ export class BossFightComponent extends MD2ComponentBase { if (mobResult) { let attackDamage = mobResult.uiWounds; if (attackDamage) { - this.boss.info.unitRemainHp -= attackDamage; - if (this.boss.info.unitRemainHp <= 0) { + this.boss.unitRemainHp -= attackDamage; + if (this.boss.unitRemainHp <= 0) { this.WIN(); } this.cdRef.detectChanges(); diff --git a/src/app/games/massive-darkness2/hero-dashboard/hero-dashboard.component.html b/src/app/games/massive-darkness2/hero-dashboard/hero-dashboard.component.html index d7ada9a..2cdd979 100644 --- a/src/app/games/massive-darkness2/hero-dashboard/hero-dashboard.component.html +++ b/src/app/games/massive-darkness2/hero-dashboard/hero-dashboard.component.html @@ -270,16 +270,24 @@ *ngIf="hero.uiActivating">
-
+
+ (blur)="heroUpdateDebounceTimer.resetTimer()"> - +
+
+ + +
+
+
diff --git a/src/app/games/massive-darkness2/massive-darkness2.component.html b/src/app/games/massive-darkness2/massive-darkness2.component.html index 30d9816..c885982 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.component.html +++ b/src/app/games/massive-darkness2/massive-darkness2.component.html @@ -73,6 +73,14 @@ {{hero.frozenToken}} + + + {{hero.extraToken}} + + + {{hero.extraToken2}} + + Actions: {{hero.remainActions}} diff --git a/src/app/games/massive-darkness2/massive-darkness2.component.ts b/src/app/games/massive-darkness2/massive-darkness2.component.ts index 49e47e8..f779c9c 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.component.ts +++ b/src/app/games/massive-darkness2/massive-darkness2.component.ts @@ -185,7 +185,7 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit { public get round(): string { if (this.md2Service.info.isBossFight) { - return `Boss Fight ${NumberUtils.Ordinal(this.md2Service.info.boss.rounds)} Round`; + return `Boss Fight ${NumberUtils.Ordinal(this.md2Service.info.bossRound)} Round`; } else { return NumberUtils.Ordinal(this.md2Service.info.round) + ' Round'; } diff --git a/src/app/games/massive-darkness2/massive-darkness2.db.model.ts b/src/app/games/massive-darkness2/massive-darkness2.db.model.ts index 8613a4c..aff50cb 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.db.model.ts +++ b/src/app/games/massive-darkness2/massive-darkness2.db.model.ts @@ -8,14 +8,17 @@ export enum MobSkillTarget { HighestHp = 70, HighestMp = 80, LowestLevel = 90, - MostCorruption = 200, - LeastCorruption = 201 + MostExtraToken = 200, + LeastExtraToken, + MostExtraToken2, + LeastExtraToken2 } export enum GameBundle { CoreGame, HeavenFallen, - Zombiecide + Zombiecide, + ZombiecideWhiteDeath } export interface MD2MobInfo { id: string; diff --git a/src/app/games/massive-darkness2/massive-darkness2.logic.ts b/src/app/games/massive-darkness2/massive-darkness2.logic.ts index d99f229..4c80711 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.logic.ts +++ b/src/app/games/massive-darkness2/massive-darkness2.logic.ts @@ -6,49 +6,60 @@ import { StringUtils } from "../../utilities/string-utils"; import { GamePlayer } from "../games.model"; import { MD2HeroInfo, AttackTarget, HeroClass } from "./massive-darkness2.model"; import { MobSkill } from "./massive-darkness2.model.boss"; +import { MobSkillTarget } from "./massive-darkness2.db.model"; export class MD2Logic { - public static getTargetHeroByFilter(heros: MD2HeroInfo[], targetType: AttackTarget) { + public static getTargetHeroByFilter(heros: MD2HeroInfo[], targetType: MobSkillTarget) { return this.getTargetHerosByFilter(heros, targetType, true)[0]; } - public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: AttackTarget, onlyOne: boolean = false) { + public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: MobSkillTarget, onlyOne: boolean = false) { let beenAttackedHero = [] as MD2HeroInfo[]; switch (targetType) { - case AttackTarget.LeastHp: + case MobSkillTarget.LeastHp: let lowestHp = Math.min(...heros.map(h => h.hp)); beenAttackedHero = heros.filter(h => h.hp == lowestHp); //this.otherAttackTarget = 'attacking the other Lowest HP hero.'; break; - case AttackTarget.LeastMp: + case MobSkillTarget.LeastMp: let lowestMp = Math.min(...heros.map(h => h.mp)); beenAttackedHero = heros.filter(h => h.hp == lowestMp); //this.otherAttackTarget = 'attacking the other Lowest HP hero.'; break; - case AttackTarget.HighestHp: + case MobSkillTarget.HighestHp: let highestHp = Math.max(...heros.map(h => h.hp)); beenAttackedHero = heros.filter(h => h.hp == highestHp); //this.otherAttackTarget = 'attacking the other Highest HP hero.'; break; - case AttackTarget.HighestMp: + case MobSkillTarget.HighestMp: let highestMp = Math.max(...heros.map(h => h.mp)); beenAttackedHero = heros.filter(h => h.mp == highestMp); //this.otherAttackTarget = 'attacking the other Highest Mp hero.'; break; - case AttackTarget.LowestLevel: + case MobSkillTarget.LowestLevel: let lowestLevel = Math.max(...heros.map(h => h.level)); beenAttackedHero = heros.filter(h => h.level == lowestLevel); //this.otherAttackTarget = 'attacking the other Lowest Level hero.'; break; - case AttackTarget.LeastCorruption: + case MobSkillTarget.LeastExtraToken: - let leastCor = Math.min(...heros.map(h => h.corruptionToken)); - beenAttackedHero = heros.filter(h => h.corruptionToken == leastCor); + let leastExtraToken = Math.min(...heros.map(h => h.extraToken)); + beenAttackedHero = heros.filter(h => h.extraToken == leastExtraToken); break; - case AttackTarget.MostCorruption: - let mostCor = Math.max(...heros.map(h => h.corruptionToken)); - beenAttackedHero = heros.filter(h => h.corruptionToken == mostCor); + case MobSkillTarget.MostExtraToken: + let mostExtraToken = Math.max(...heros.map(h => h.extraToken)); + beenAttackedHero = heros.filter(h => h.extraToken == mostExtraToken); break; - case AttackTarget.Random: + + case MobSkillTarget.LeastExtraToken2: + + let leastExtraToken2 = Math.min(...heros.map(h => h.extraToken2)); + beenAttackedHero = heros.filter(h => h.extraToken2 == leastExtraToken2); + break; + case MobSkillTarget.MostExtraToken2: + let mostExtraToken2 = Math.max(...heros.map(h => h.extraToken2)); + beenAttackedHero = heros.filter(h => h.extraToken2 == mostExtraToken2); + break; + case MobSkillTarget.Random: default: beenAttackedHero = [heros[Math.round(Math.random() * (heros.length - 1))]]; //this.otherAttackTarget = 'Just act like normal.'; diff --git a/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts b/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts index 85d463f..0524017 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts +++ b/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts @@ -6,7 +6,7 @@ import { StringUtils } from "../../utilities/string-utils" import { BossActivationComponent } from "./boss-fight/boss-activation/boss-activation.component" 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" +import { MD2DiceSet, MD2MobSkill, MobSkillTarget } from "./massive-darkness2.db.model" export enum MobSkillType { @@ -156,14 +156,14 @@ export class BossMicheal extends BossFight { bossAction(): Observable { - let actionResult = new RollingBlackDice().roll(this.actionBlackDice); + let actionResult = new RollingBlackDice().roll(2); let actionHtml = ''; let beenAttackedHero = [] as MD2HeroInfo[]; let bossAction: MobSkill; switch (actionResult.claws) { case 0: //Justice From Above - beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.MostCorruption, true); + beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.MostExtraToken, true); bossAction = new MobSkill( { name: 'Justice From Above', @@ -173,7 +173,7 @@ export class BossMicheal extends BossFight { break; case 1: //Lance Dash - beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true); + beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastExtraToken, true); bossAction = new MobSkill({ name: 'Lance Dash', description: @@ -193,12 +193,13 @@ export class BossMicheal extends BossFight { default: break; } - return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose; + return null; + //return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose; } prepareForBossFight(): void { this.md2Service.heros.forEach(hero => { - hero.uiShowCorruptionToken = true; + hero.uiShowExtraToken = true; }); this.md2Service.msgBoxService.show('Prepare Boss Fight', { text: `
Place ${this.md2Service.heros.length * 2} ${this.corruptionTokenHtml} on the Corruption Stone Zones (Shadow @@ -282,7 +283,7 @@ export class BossReaper extends BossFight { switch (actionResult.claws) { case 0: //Justice From Above - beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastMp, true); + beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastMp, true); bossAction = new MobSkill( { name: 'Soul Drain', @@ -292,7 +293,7 @@ export class BossReaper extends BossFight { break; case 1: //Lance Dash - beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true); + beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastExtraToken, true); bossAction = new MobSkill({ name: 'Time Ticking', description: @@ -313,7 +314,8 @@ export class BossReaper extends BossFight { default: break; } - return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose; + return null; + //return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose; } prepareForBossFight(): void { this.md2Service.msgBoxService.show('Prepare Boss Fight', { diff --git a/src/app/games/massive-darkness2/massive-darkness2.model.ts b/src/app/games/massive-darkness2/massive-darkness2.model.ts index 601231d..32e8776 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.model.ts +++ b/src/app/games/massive-darkness2/massive-darkness2.model.ts @@ -5,7 +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"; +import { BossFightProfile, 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 { @@ -333,12 +333,15 @@ export class MobInfo implements IDrawingItem { fireToken: number = 0; frozenToken: number = 0; corruptionToken: number = 0; + uiExtraTokenCount: number = 0; + uiExtraTokenCount2: number = 0; uiWounds: number; uiFireTokens: number; uiFrozenTokens: number; uiCorruptionTokens: number; uiAttackedBy: string; extraRule: string; + bossFightProfile?: BossFightProfile; get identifyName(): string { return `${this.name}_${this.level}`; } @@ -409,7 +412,8 @@ export class MD2HeroInfo { level: number = 1; fireToken: number = 0; frozenToken: number = 0; - corruptionToken: number = 0; + extraToken: number = 0; + extraToken2: number = 0; playerInfo: GamePlayer; imgUrl: string; skillHtml: string; @@ -417,7 +421,12 @@ export class MD2HeroInfo { remainActions: number = 3; rage: number = 0; uiActivating = false; - uiShowCorruptionToken = false; + uiExtraTokenHtml: string = ''; + uiExtraTokenHtml2: string = ''; + uiExtraTokenName: string = ''; + uiExtraTokenName2: string = ''; + uiShowExtraToken = false; + uiShowExtraToken2 = false; uiBossFight = false; uiShowAttackBtn = false; diff --git a/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts b/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts index f682081..11d44a6 100644 --- a/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts +++ b/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts @@ -145,46 +145,6 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewIn }).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; - if (view && view.state) { - try { - // Parse the HTML content - const dom = document.createElement('div'); - dom.innerHTML = html; - - // Use ProseMirror's DOMParser to parse HTML - // Access it from the view's state - const pmState = view.state; - - // Use the editor's exec method or manual dispatch - if ((this.editor as any).exec) { - // Try using exec with 'insertHTML' command if available - try { - (this.editor as any).exec('insertHTML', html); - } catch (e) { - throw new Error('insertHTML not supported'); - } - } else { - throw new Error('exec method not available'); - } - } catch (e) { - console.error('Error inserting HTML:', e); - // Fallback: append to the end - const currentValue = this.editor.value || ''; - const newValue = currentValue + ' ' + html; - this.value = newValue; - this.onChange(newValue); - } - } else { - // Fallback: append to the end - const currentValue = this.editor.value || ''; - const newValue = currentValue + ' ' + html; - this.value = newValue; - this.onChange(newValue); - } } }); } @@ -445,7 +405,7 @@ export const rbjTagNodeSpec: NodeSpec = { "span", { class: classValue, - // "rbj-tag-id": node.attrs["rbj-tag-id"], + "md2-icon": md2IconText, // "tag-marker": node.attrs["tag-marker"], // "tag-value": node.attrs["tag-value"], // "tag-preview": node.attrs["tag-preview"], diff --git a/src/app/games/massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component.ts b/src/app/games/massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component.ts index 1f79bea..aee5727 100644 --- a/src/app/games/massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component.ts +++ b/src/app/games/massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component.ts @@ -37,7 +37,7 @@ import { MD2Service } from '../../../services/MD2/md2.service'; gap: 10px; max-height: 400px; overflow-y: auto; - width:400px; + //width:400px; } .icon-item { cursor: pointer; diff --git a/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.html b/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.html index 38963c5..218c6df 100644 --- a/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.html +++ b/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.html @@ -2,7 +2,28 @@ -
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
@@ -33,39 +54,7 @@
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
- - -
-
-
-
- - -
-
-
@@ -81,8 +70,8 @@ + [filterable]="true" [pageable]="true" [height]="400" (remove)="removePhaseBuffHandler($event)" + (dataStateChange)="phaseBuffsState = $event; loadPhaseBuffs()"> @@ -116,7 +105,8 @@ -
+
- @@ -125,7 +115,8 @@ - + @@ -141,4 +132,4 @@ - + \ No newline at end of file diff --git a/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.scss b/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.scss index f67848d..88c7e37 100644 --- a/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.scss +++ b/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.scss @@ -1 +1,10 @@ // Boss Fight Editor styles +.tokenIconDiv { + margin-left: 5px; + font-size: 30px; + img { + width: 30px; + height: 30px; + object-fit: contain; + } +} diff --git a/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.ts b/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.ts index e596fe1..9b5c26b 100644 --- a/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.ts +++ b/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component.ts @@ -8,6 +8,7 @@ import { MobSkillType } from '../../massive-darkness2.model.boss'; import { MD2BossFightProfileService, MD2PhaseBuffService } from '../../service/massive-darkness2.service'; import { MsgBoxService } from '../../../../services/msg-box.service'; import { MD2PhaseBuffEditorComponent } from '../md2-phase-buff-editor/md2-phase-buff-editor.component'; +import { MD2IconPickerDlgComponent } from '../../md2-html-editor/md2-icon-picker-dlg.component'; @Component({ selector: 'ngx-md2-boss-fight-editor', @@ -192,7 +193,20 @@ export class MD2BossFightEditorComponent extends DialogContentBase implements On }); } } - + showInsertMD2Icon(attributeName: string) { + this.dialogService.open({ + title: 'Select MD2 Icon', + content: MD2IconPickerDlgComponent, + width: '800px', + height: 600 + }).result.subscribe((html: string) => { + if (html && typeof html === 'string') { + this.model[attributeName] = html; + } else { + this.model[attributeName] = ''; + } + }); + } public get isValid(): boolean { if (!this.model) { return false; diff --git a/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component.html b/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component.html index 7ad9c05..8c551f5 100644 --- a/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component.html +++ b/src/app/games/massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component.html @@ -11,7 +11,8 @@ + (dataStateChange)="gridState = $event; processGridData()" (edit)="editHandler($event)" + (remove)="removeHandler($event)" (add)="addHandler()"> diff --git a/src/app/services/MD2/md2-init.service.ts b/src/app/services/MD2/md2-init.service.ts index 0da00fa..9653c19 100644 --- a/src/app/services/MD2/md2-init.service.ts +++ b/src/app/services/MD2/md2-init.service.ts @@ -31,6 +31,7 @@ export class MD2InitService { this.mobInfoService.getAll().pipe(first()).subscribe(result => { this.md2Service.mobInfos = result.filter(m => m.type == MobType.Mob); this.md2Service.roamingMobInfos = result.filter(m => m.type == MobType.RoamingMonster); + this.md2Service.bossInfos = result.filter(m => m.type == MobType.Boss); for (let i = 0; i < result.length; i++) { const mobInfo = result[i]; for (let j = 0; j < mobInfo.mobLevelInfos.length; j++) { diff --git a/src/app/services/MD2/md2.service.ts b/src/app/services/MD2/md2.service.ts index b49b556..2d840ef 100644 --- a/src/app/services/MD2/md2.service.ts +++ b/src/app/services/MD2/md2.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@angular/core'; import { AttackInfo, AttackTarget, CoreGameDarknessPhaseRule, DrawingBag, DrawingItem, HeroClass, IDarknessPhaseRule, MD2EnemyPhaseSpecialInfo, MD2EnemyPhaseSpecialRule, MD2HeroInfo, MD2Icon, MD2Rules, MobInfo, MobType, RoundPhase, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model'; import { first, map, reduce } from "rxjs/operators"; import { NbDialogService, NbThemeService } from '@nebular/theme'; -import { Subject } from 'rxjs'; -import { BossMicheal, BossReaper, IBossFight } from '../../games/massive-darkness2/massive-darkness2.model.boss'; +import { Observable, Subject } from 'rxjs'; +import { BossFight, MobSkillType } from '../../games/massive-darkness2/massive-darkness2.model.boss'; import { ADIcon, MessageBoxConfig } from '../../ui/alert-dlg/alert-dlg.model'; import { NumberUtils } from '../../utilities/number-utils'; import { StringUtils } from '../../utilities/string-utils'; @@ -16,8 +16,10 @@ import { MD2Logic } from '../../games/massive-darkness2/massive-darkness2.logic' import { MD2InitService } from './md2-init.service'; import { ArrayUtils } from '../../utilities/array-utils'; import { DropDownOption } from '../../entity/dropDownOption'; -import { GameBundle, MD2DiceSet, MD2MobInfo } from '../../games/massive-darkness2/massive-darkness2.db.model'; +import { GameBundle, MD2DiceSet, MD2MobInfo, MD2MobSkill, MobSkillTarget } from '../../games/massive-darkness2/massive-darkness2.db.model'; import { environment } from '../../../environments/environment'; +import { RollingBlackDice } from '../../games/massive-darkness2/massive-darkness2.model.dice'; +import { BossActivationComponent } from '../../games/massive-darkness2/boss-fight/boss-activation/boss-activation.component'; const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }; @@ -32,6 +34,7 @@ export class MD2Service { public specialRule: MD2EnemyPhaseSpecialInfo; public info: MD2GameInfo; public playerHero: MD2HeroInfo; + public bossInfos: MD2MobInfo[] = []; public mobInfos: MD2MobInfo[] = []; public roamingMobInfos: MD2MobInfo[] = []; public mobDeck: DrawingBag; @@ -134,8 +137,8 @@ export class MD2Service { this.msgBoxService.show(`${NumberUtils.Ordinal(this.info.round)} Hero Phase`, { icon: ADIcon.INFO }); } } else { - this.info.boss.darknessPhase(); - this.msgBoxService.show(`Boss Fight - ${NumberUtils.Ordinal(this.info.boss.rounds)} Hero Phase`, { icon: ADIcon.INFO }); + this.bossDarknessPhase(); + this.msgBoxService.show(`Boss Fight - ${NumberUtils.Ordinal(this.info.bossRound)} Hero Phase`, { icon: ADIcon.INFO }); } //this.runNextPhase(); @@ -221,15 +224,15 @@ export class MD2Service { mobInfo.skills = mobInfo.skills.sort((a, b) => b.seq - a.seq); switch (dbMobInfo.type) { case MobType.Mob: - 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.leaderImgUrl = !!mobInfo.leaderImgUrl ? MD2_IMG_URL(mobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`); + mobInfo.minionImgUrl = !!mobInfo.minionImgUrl ? MD2_IMG_URL(mobInfo.minionImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`); break; case MobType.RoamingMonster: - mobInfo.leaderImgUrl = mobInfo.leaderImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/RoamingMonsters/${mobInfo.name}/Stand.png`); + mobInfo.leaderImgUrl = !!mobInfo.leaderImgUrl ? MD2_IMG_URL(mobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/RoamingMonsters/${mobInfo.name}/Stand.png`); mobInfo.minionImgUrl = null; break; case MobType.Boss: - mobInfo.leaderImgUrl = mobInfo.leaderImgUrl || MD2_IMG_URL(`/Boss/${mobInfo.name}-Stand.png`); + mobInfo.leaderImgUrl = !!mobInfo.leaderImgUrl ? MD2_IMG_URL(mobInfo.leaderImgUrl) : MD2_IMG_URL(`/Boss/${mobInfo.name}-Stand.png`); mobInfo.minionImgUrl = null; break; default: @@ -297,34 +300,58 @@ export class MD2Service { } public enterBossFight() { + this.msgBoxService.showInputbox('Boss Fight', 'Choose the boss', { inputType: 'dropdown', - dropDownOptions: [new DropDownOption('The Reaper', 'The Reaper'), new DropDownOption('Michael - The Corrupted Archangel', 'Michael - The Corrupted Archangel')] + dropDownOptions: this.bossInfos.map(b => new DropDownOption(b.id, b.name)) }).pipe(first()).subscribe(result => { if (result) { this.info.mobs = []; this.info.roamingMonsters = []; + let bossInfo = this.bossInfos.find(b => b.id == result); + this.info.boss = new MobInfo(bossInfo); - if (result == 'The Reaper') { - this.info.boss = new BossReaper(this); - } else { - this.info.boss = new BossMicheal(this); + this.info.boss.leaderImgUrl = !!this.info.boss.leaderImgUrl ? this.imgUrl(this.info.boss.leaderImgUrl) : this.imgUrl(`/Boss/${this.info.boss.name}-Stand.png`); + + let bossLevelInfo = bossInfo.mobLevelInfos.sort((a, b) => b.level - a.level) + .find(l => l.level <= Math.max(...this.info.heros.map(h => h.level))); + if (bossLevelInfo) { + this.info.boss.hp = bossLevelInfo.hpPerHero * this.info.heros.length; + this.info.boss.unitRemainHp = this.info.boss.hp; + this.info.boss.actions = bossLevelInfo.actions; + this.info.boss.attackInfos = [this.getAttackInfo(bossLevelInfo.attackInfo)]; + let altAttackInfo = bossLevelInfo.alterAttackInfo; + if (altAttackInfo + && (altAttackInfo.black > 0 || altAttackInfo.blue > 0 || altAttackInfo.green > 0 || altAttackInfo.orange > 0 || altAttackInfo.red > 0 || altAttackInfo.yellow > 0) + ) { + this.info.boss.attackInfos.push(this.getAttackInfo(altAttackInfo)); + } + this.info.boss.defenseInfo = bossLevelInfo.defenceInfo; } this.info.roamingMonsters = []; this.info.mobs = []; this.info.isBossFight = true; - this.info.isBossFight = true; - this.info.boss.info.hp = this.info.boss.info.hpPerHero * this.info.heros.length; - this.info.boss.info.unitRemainHp = this.info.boss.info.hp; this.refreshUI$.next(); - this.info.boss.prepareForBossFight(); + this.prepareForBossFight(); this.levelUpPhase(false); + + //Reset all heroes + let extraTokenName = this.info.boss.bossFightProfile.extraTokenName; + let extraTokenHtml = this.info.boss.bossFightProfile.extraTokenHtml; + let extraTokenName2 = this.info.boss.bossFightProfile.extraTokenName2; + let extraTokenHtml2 = this.info.boss.bossFightProfile.extraTokenHtml2; this.heros.forEach(hero => { hero.hp = hero.hpMaximum; hero.mp = hero.mpMaximum; hero.remainActions = 3; hero.uiActivating = false; hero.uiBossFight = true; + hero.uiExtraTokenHtml = extraTokenHtml; + hero.uiExtraTokenName = extraTokenName; + hero.uiShowExtraToken = !!extraTokenName; + hero.uiShowExtraToken2 = !!extraTokenName2; + hero.uiExtraTokenHtml2 = extraTokenHtml2; + hero.uiExtraTokenName2 = extraTokenName2; }); this.broadcastGameInfo(); } @@ -332,9 +359,6 @@ export class MD2Service { }); //this.sendMsgboxMsg } - public activateBoss() { - this.info.boss.activating(); - } public fileList(folderPath: string) { return this.fileService.FileList('Images/MD2/' + folderPath); } @@ -439,7 +463,7 @@ export class MD2Service { }); } - public getTargetHerosByFilter(targetType: AttackTarget, onlyOne: boolean = false) { + public getTargetHerosByFilter(targetType: MobSkillTarget, onlyOne: boolean = false) { return MD2Logic.getTargetHerosByFilter(this.info.heros, targetType, onlyOne); } @@ -505,39 +529,39 @@ export class MD2Service { return ``; } else { if (!cssClass) { - cssClass = 'g-height-25 mr-1'; + cssClass = 'MD2IconImg g-height-25 mr-1'; } //image based icons switch (icon) { case MD2Icon.HP_Color: - return this.imgHtml('HeartIcon.png', cssClass); + return this.imgHtml('HeartIcon.png', cssClass, 'HP'); case MD2Icon.Mana_Color: - return this.imgHtml('ManaIcon.png', cssClass); + return this.imgHtml('ManaIcon.png', cssClass, 'Mana'); case MD2Icon.CorruptToken: - return this.imgHtml('Tokens/CorruptToken.png', cssClass); + return this.imgHtml('Tokens/CorruptToken.png', cssClass, 'Corruption Token'); case MD2Icon.TimeToken: - return this.imgHtml('Tokens/TimeToken.png', cssClass); + return this.imgHtml('Tokens/TimeToken.png', cssClass, 'Time Token'); case MD2Icon.FireToken: - return this.imgHtml('Tokens/FireToken.png', cssClass); + return this.imgHtml('Tokens/FireToken.png', cssClass, 'Fire Token'); case MD2Icon.FrozenToken: - return this.imgHtml('Tokens/FrozenToken.png', cssClass); + return this.imgHtml('Tokens/FrozenToken.png', cssClass, 'Frozen Token'); case MD2Icon.TreasureToken: - return this.imgHtml('TreasureToken/Cover.png', cssClass); + return this.imgHtml('TreasureToken/Cover.png', cssClass, 'Treasure Token'); case MD2Icon.TreasureToken_Common: - return this.imgHtml('TreasureToken/Common.png', cssClass); + return this.imgHtml('TreasureToken/Common.png', cssClass, 'Common Treasure Token'); case MD2Icon.TreasureToken_Rare: - return this.imgHtml('TreasureToken/Rare.png', cssClass); + return this.imgHtml('TreasureToken/Rare.png', cssClass, 'Rare Treasure Token'); case MD2Icon.TreasureToken_Epic: - return this.imgHtml('TreasureToken/Epic.png', cssClass); + return this.imgHtml('TreasureToken/Epic.png', cssClass, 'Epic Treasure Token'); case MD2Icon.TreasureToken_Legendary: - return this.imgHtml('TreasureToken/Legendary.png', cssClass); + return this.imgHtml('TreasureToken/Legendary.png', cssClass, 'Legendary Treasure Token'); } } } - public imgHtml(imgPath: string, cssClass = 'g-height-25 mr-1') { - return ``; + public imgHtml(imgPath: string, cssClass = 'g-height-25 mr-1', imgTitle = '') { + return ``; } public imgUrl(imgPath: string) { @@ -619,14 +643,7 @@ export class MD2Service { } public broadcastGameInfo() { let parameters = {}; - if (this.info.boss) { - this.info.boss.md2Service = undefined; - } parameters['gameInfo'] = JSON.stringify(this.info); - - if (this.info.boss) { - this.info.boss.md2Service = this; - } this.broadcastMessage('GameRoom', 'update', parameters); } broadcastFetchGameInfo() { @@ -650,8 +667,191 @@ export class MD2Service { } + + // #endregion Public Methods (27) + //#region Boss Fight + private bossActivatedTimes: number = 0; + activateBoss(): boolean { + this.bossActivatedTimes = this.info.boss.actions; + this.runBossAction(); + return true; + } + + runBossAction() { + this.bossAction().pipe(first()).subscribe(result => { + this.bossActivatedTimes--; + if (this.bossActivatedTimes) { + this.runBossAction(); + } else { + if (false == this.heros.some(h => h.remainActions > 0)) { + this.darknessPhase(); + } + } + }); + } + + bossAction(): Observable { + let actionBlackDice = 2; + let actionResult = new RollingBlackDice().roll(actionBlackDice); + let actionHtml = ''; + let beenAttackedHero = [] as MD2HeroInfo[]; + let bossAction: MD2MobSkill; + bossAction = this.info.boss.skills.find(s => s.type == MobSkillType.ActiveSkill && s.skillRoll == actionResult.claws); + + return this.dlgService.open(BossActivationComponent, { context: { boss: this.info.boss, bossAction: bossAction, currentAction: this.bossActivatedTimes, allActions: this.info.boss.actions } }).onClose; + + } + prepareForBossFight(): void { + this.heros.forEach(hero => { + hero.uiShowExtraToken = !!this.info.boss.bossFightProfile.extraTokenName; + hero.uiShowExtraToken2 = !!this.info.boss.bossFightProfile.extraTokenName2; + }); + //init boss first round buff + this.bossDarknessPhase(); + let prerequisiteHtml = this.info.boss.bossFightProfile.prerequisite; + //Detect prerequisiteHtml is empty or not,like

+ if (prerequisiteHtml.trim() == '

') { + prerequisiteHtml = ''; + } + if (prerequisiteHtml) { + prerequisiteHtml = this.skillParse(prerequisiteHtml, MobSkillTarget.Random); + this.msgBoxService.show('Prepare Boss Fight', { + text: prerequisiteHtml + }); + } + + } + bossDarknessPhase(): void { + this.info.bossRound++; + let roundBuff = this.info.boss.bossFightProfile.phaseBuffs.find(p => p.phase == this.info.bossRound); + if (roundBuff) { + this.info.boss.actions += roundBuff.extraAction || 0; + for (let i = 0; i < this.info.boss.attackInfos.length; i++) { + this.info.boss.attackInfos[i].black += roundBuff.extraAttackDice.black || 0; + this.info.boss.attackInfos[i].yellow += roundBuff.extraAttackDice.yellow || 0; + this.info.boss.attackInfos[i].orange += roundBuff.extraAttackDice.orange || 0; + this.info.boss.attackInfos[i].red += roundBuff.extraAttackDice.red || 0; + } + this.info.boss.defenseInfo.black += roundBuff.extraDefenceDice.black || 0; + this.info.boss.defenseInfo.yellow += roundBuff.extraDefenceDice.yellow || 0; + this.info.boss.defenseInfo.orange += roundBuff.extraDefenceDice.orange || 0; + this.info.boss.defenseInfo.red += roundBuff.extraDefenceDice.red || 0; + this.info.boss.hp += roundBuff.extraHp || 0; + this.info.boss.unitRemainHp += roundBuff.extraHp || 0; + this.info.boss.uiExtraTokenCount += roundBuff.extraTokenCount || 0; + this.info.boss.uiExtraTokenCount2 += roundBuff.extraTokenCount2 || 0; + } + + } + //#endregion Boss Fight + + public skillParse(skillHtmlContent: string, targetRule: MobSkillTarget, onlyOneTarget: boolean = true) { + let targetHeros = MD2Logic.getTargetHerosByFilter(this.info.heros, targetRule, onlyOneTarget); + //if the skillHtmlContent contains ${Expression} replace it with the expression result + //Example: ${1+1} will be replaced with 2 + //bossExtraTokenCount will be replaced with this.info.boss.uiExtraTokenCount + //bossExtraTokenCount2 will be replaced with this.info.boss.uiExtraTokenCount2 + //targetHero.level will be replaced with the hero level + //targetHero.hp will be replaced with the hero hp + //targetHero.mp will be replaced with the hero mp + //targetHero.ap will be replaced with the hero ap + //targetHero.fireToken will be replaced with the hero fire token + //targetHero.frozenToken will be replaced with the hero frozen token + //targetHero.uiExtraTokenCount will be replaced with the hero extra token count + //targetHero.uiExtraTokenCount2 will be replaced with the hero extra token count2 + + //For example: + //${targetHero.level+bossExtraTokenCount} will be replaced with the number + //then calculate the result and replace the ${Expression} with the result + //Example: ${1+1} will be replaced with 2 + //${3-1} + //${2*3} + //${2/3} + //${2%3} + + + // No target heroes found, still process boss tokens + let result = skillHtmlContent; + + result = result.replace(/\bheroes.length\b/g, String(this.info?.heros?.length || 0)); + + if (result.includes('boss.') && this.info?.boss) { + // Replace boss tokens even if no heroes + result = result.replace(/\bboss.ExtraTokenCount\b/g, String(this.info?.boss?.uiExtraTokenCount || 0)); + result = result.replace(/\bboss.ExtraTokenCount2\b/g, String(this.info?.boss?.uiExtraTokenCount2 || 0)); + } + // Evaluate remaining expressions (if any numbers remain) + + + + if (result.includes('targetHero') && targetHeros && targetHeros.length > 0) { + + // Use the first hero as targetHero for replacements + const targetHero = targetHeros[0]; + let result = skillHtmlContent; + + // Replace targetHero properties + result = result.replace(/\btargetHero\.name\b/g, String(targetHero?.heroFullName || 0)); + result = result.replace(/\btargetHero\.level\b/g, String(targetHero?.level || 0)); + result = result.replace(/\btargetHero\.hp\b/g, String(targetHero?.hp || 0)); + result = result.replace(/\btargetHero\.mp\b/g, String(targetHero?.mp || 0)); + result = result.replace(/\btargetHero\.ap\b/g, String(targetHero?.ap || 0)); + result = result.replace(/\btargetHero\.fireToken\b/g, String(targetHero?.fireToken || 0)); + result = result.replace(/\btargetHero\.frozenToken\b/g, String(targetHero?.frozenToken || 0)); + // Map uiExtraTokenCount to extraToken and uiExtraTokenCount2 to extraToken2 + result = result.replace(/\btargetHero\.uiExtraTokenCount\b/g, String(targetHero?.extraToken || 0)); + result = result.replace(/\btargetHero\.uiExtraTokenCount2\b/g, String(targetHero?.extraToken2 || 0)); + + } + + // Evaluate expressions in ${...} format + result = this.evaluateExpressions(result); + + return result; + } + + private evaluateExpressions(content: string): string { + // Match ${expression} pattern + const expressionPattern = /\$\{([^}]+)\}/g; + + return content.replace(expressionPattern, (match, expression) => { + try { + const sanitizedExpression = expression.trim(); + + // After placeholder replacement, expression should only contain numbers, operators, and spaces + // Check if expression still contains letters (variables that weren't replaced) + if (/[a-zA-Z_]/.test(sanitizedExpression)) { + // Variables weren't replaced, return original match + return match; + } + + // Validate expression contains only safe mathematical characters + // Allow: numbers, decimal points, operators (+, -, *, /, %), parentheses, and spaces + if (!/^[0-9+\-*/().%\s]+$/.test(sanitizedExpression)) { + // Contains unsafe characters, return original + return match; + } + + // Evaluate the expression safely using Function constructor + // This is safer than eval() as it runs in strict mode + const result = Function('"use strict"; return (' + sanitizedExpression + ')')(); + + // Check if result is a valid number + if (typeof result === 'number' && !isNaN(result) && isFinite(result)) { + return String(result); + } + + // Invalid result, return original match + return match; + } catch (error) { + // If evaluation fails for any reason, return the original match + return match; + } + }); + } + } export class MD2GameInfo { @@ -666,8 +866,8 @@ export class MD2GameInfo { this.mobs = this.mobs.map(m => new MobInfo(m)); this.roamingMonsters = this.roamingMonsters.map(m => new MobInfo(m)); - if (this.boss && this.boss.info) { - this.boss.info = new MobInfo(this.boss.info); + if (this.boss) { + this.boss = new MobInfo(this.boss); } this.heros = this.heros.map(h => new MD2HeroInfo(h)); } @@ -679,7 +879,8 @@ export class MD2GameInfo { public heros: MD2HeroInfo[] = []; public disconnectedHeroes: MD2HeroInfo[] = []; public round = 1; + public bossRound = 0; public roundPhase: RoundPhase = RoundPhase.HeroPhase; public showAttackBtn: boolean = false; - public boss: IBossFight; + public boss: MobInfo; } \ No newline at end of file diff --git a/src/assets/styles/site.scss b/src/assets/styles/site.scss index cdcef4c..4ff44f2 100644 --- a/src/assets/styles/site.scss +++ b/src/assets/styles/site.scss @@ -30,3 +30,7 @@ a, input { touch-action: manipulation; } + +k-editor-content p { + line-height: 1rem; +}