This commit is contained in:
Chris Chen 2024-04-06 07:26:12 -07:00
parent d486fe9594
commit dfc1f269a0
18 changed files with 368 additions and 94 deletions

View File

@ -81,11 +81,11 @@ export abstract class MD2Base {
} }
abstract refreshUI(); abstract refreshUI();
handleSignalRCallback(message: SignalRMessage): void { handleSignalRCallback(message: SignalRMessage): void {
if (message.from.isGroup) { // if (message.from.isGroup) {
if (!this.isHeroDashboard) return; // if (!this.isHeroDashboard) return;
} else { // } else {
if (this.isHeroDashboard && this.md2Service.playerHero.playerInfo.signalRClientId == message.from.sessionId) return; // if (this.isHeroDashboard && this.md2Service.playerHero.playerInfo.signalRClientId == message.from.sessionId) return;
} // }
switch (message.actionType) { switch (message.actionType) {
case 'hero': case 'hero':
@ -143,7 +143,7 @@ export abstract class MD2Base {
case 'GameRoom': case 'GameRoom':
switch (message.actionName) { switch (message.actionName) {
case 'Leaving': case 'Leaving':
this.md2Service.heros.splice(this.md2Service.heros.findIndex(h => h.playerInfo.signalRClientId == message.from.sessionId)); this.md2Service.heros.splice(this.md2Service.heros.findIndex(h => h.playerInfo.tabId == message.from.sessionId));
this.detectChanges(); this.detectChanges();
break; break;
case 'update': case 'update':
@ -158,6 +158,12 @@ export abstract class MD2Base {
this.detectChanges(); this.detectChanges();
} }
break; break;
case 'sendJoinInfo':
if (this.isHeroDashboard && this.md2Service.playerHero) {
this.md2Service.playerHero.playerInfo.signalRClientId = message.parameters['signalrconnid'];
this.md2Service.broadcastService.broadcastMyHeroInfo();
}
break;
default: default:
break; break;
} }
@ -206,7 +212,7 @@ export abstract class MD2Base {
break; break;
case 'tradeAction': case 'tradeAction':
this.md2Service.msgBoxService.show('Trade and Equip', { this.md2Service.msgBoxService.show('Trade and Equip', {
text: `every one in the <b>same zone with <b>${this.md2Service.heroFullName(this.md2Service.currentActivateHero)}</b> may freely trade and text: `every one in the <b>same zone with ${this.md2Service.heroFullName(this.md2Service.currentActivateHero)}</b> may freely trade and
equip items!`, equip items!`,
icon: ADIcon.INFO icon: ADIcon.INFO
}); });

View File

@ -76,6 +76,29 @@ const CORE_GAME_MOB_LEVEL = [
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 3, 0, 3)], attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 3, 0, 3)],
defenseInfo: new DefenseInfo(4, 1), defenseInfo: new DefenseInfo(4, 1),
}), }),
new MobInfo({
name: 'Balrog', level: 1, hp: 5,
attackInfos: [
new AttackInfo(MD2Icon.Magic, 0, 1, 0, 2),
],
defenseInfo: new DefenseInfo(2, 1),
}),
new MobInfo({
name: 'Balrog', level: 3, hp: 8,
attackInfos: [
new AttackInfo(MD2Icon.Magic, 0, 2, 0, 2),
],
defenseInfo: new DefenseInfo(3, 1),
}),
new MobInfo({
name: 'Balrog', level: 5, hp: 10,
attackInfos: [
new AttackInfo(MD2Icon.Magic, 0, 3, 0, 2),
],
defenseInfo: new DefenseInfo(4, 1),
}),
] ]
export abstract class CoreGameRMFactory implements IMobFactory { export abstract class CoreGameRMFactory implements IMobFactory {
@ -200,9 +223,6 @@ export class RMAndraFactory extends CoreGameRMFactory {
}).pipe(first()).subscribe(result => { }).pipe(first()).subscribe(result => {
if (result) { if (result) {
mob.actions = 0; mob.actions = 0;
mob.actionSubject.next(
`Undead Queen attacks each Hero in LoS(resolve each attack separately).`
);
msgBoxService.show('Is Any Hero in the LoS of Andra?', { msgBoxService.show('Is Any Hero in the LoS of Andra?', {
icon: ADIcon.QUESTION, icon: ADIcon.QUESTION,
@ -300,7 +320,7 @@ export class RMLyidanIncubusLordFactory extends CoreGameRMFactory {
`The Incubus Lord got 3 Wounds, Move it to the closest Shadow Zone.` `The Incubus Lord got 3 Wounds, Move it to the closest Shadow Zone.`
); );
} else { } else {
msgBoxService.show('Is there a Herp up to 3 Zones away(regardless of LoS) from The Incubus Lord?', { msgBoxService.show('Is there a Hero up to 3 Zones away(regardless of LoS) from The Incubus Lord?', {
icon: ADIcon.QUESTION, icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => { }).pipe(first()).subscribe(result => {
@ -329,9 +349,46 @@ export class RMLyidanIncubusLordFactory extends CoreGameRMFactory {
return this.mob; return this.mob;
} }
} }
export class RMBalrogFactory extends CoreGameRMFactory {
mobName: string = 'Balrog';
generate(level: number): MobInfo {
this.loadLevelInfo('Balrog', level);
this.mob.extraRule = `When Balrog is in the Dungeon, ${this.iconHtml(MD2Icon.Fire)} on Heros can't be removed, Heros still suffer its effects when activating.`
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
mob.actions = 0;
let noFireHeros = heros.filter(h => h.fireToken == 0);
if (noFireHeros.length == 0) {
mob.actions = 3;
mob.actionSubject.next(
`The Balrog gains 3 Actions`
);
} else {
let fireTokens = Math.round(Math.random() * 2) + 1;
mob.actionSubject.next(
`Balrog ,moves 2 Zones toward to ${MD2Logic.getTargetHerosHtml(noFireHeros)} and Each Hero in ${this.iconHtml(MD2Icon.Magic)} range takes ${fireTokens} ${this.iconHtml(MD2Icon.Fire)}`
);
}
}
this.mob.combatSkill = new MobSkill(
{
description: `The Hero takes 1 ${this.iconHtml(MD2Icon.Fire)}`,
type: MobSkillType.Combat
}
)
return this.mob;
}
}
export const CoreGameRMFactories = [ export const CoreGameRMFactories = [
new RMUndeadQueenFactory(), new RMUndeadQueenFactory(),
new RMAndraFactory(), new RMAndraFactory(),
new RMTheGhoulFactory(), new RMTheGhoulFactory(),
new RMLyidanIncubusLordFactory(), new RMLyidanIncubusLordFactory(),
new RMBalrogFactory()
]; ];

View File

@ -7,18 +7,45 @@
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-12 col-sm-7"> <div class="col-12 col-sm-7">
<div class="tp-wrapper mb-2"> <div class="tp-wrapper mb-2">
<div class="tp-box g-height-300 g-height-350--sm g-height-500--md" (click)="toggleFlip()" <div class="tp-box g-height-300 g-height-350--sm g-height-500--md" [@flipState]="flip">
[@flipState]="flip">
<div class="tp-box__side tp-box__front "> <div class="tp-box__side tp-box__front ">
<img class="MD2HeroCard " src="{{hero.imgUrl}}"> <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>
<button nbButton hero fullWidth status="info" *ngIf="allowStartAction"
(click)="startActivation()">Start Activation</button>
</div>
</div> </div>
<div class="tp-box__side tp-box__back"> <div class="tp-box__side tp-box__back">
<div class="row no-gutters">
<div class="col-6">
<img class="MD2HeroCard " src="{{imgUrl('Heros/Guide/'+className+'.jpg')}}"
(click)="toggleFlip()">
</div>
<div class="col-6">
<img class="MD2HeroCard " src="{{imgUrl('Sets/Shadowbane/'+className+'.png')}}"
(click)="toggleFlip()">
</div>
</div>
<img class="MD2HeroCard " src="{{imgUrl('Heros/Guide/'+className+'.jpg')}}">
<img class="MD2HeroCard " src="{{imgUrl('Sets/Shadowbane/'+className+'.png')}}">
</div> </div>
</div> </div>
@ -33,6 +60,7 @@
<img class="MD2HeroCard" src="{{hero.imgUrl}}"> <img class="MD2HeroCard" src="{{hero.imgUrl}}">
<img class="MD2HeroCard HpMpBar" src="{{imgUrl('/Heros/Template/Border.png')}}"> <img class="MD2HeroCard HpMpBar" src="{{imgUrl('/Heros/Template/Border.png')}}">
</div> --> </div> -->
</div> </div>
<div class="col-12 col-sm-5"> <div class="col-12 col-sm-5">
@ -41,72 +69,90 @@
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-6"> <div class="col-6">
<!-- <adj-number-input name="heroHP" [(ngModel)]="hero.hp"
[maximum]="hero.hpMaximum" minimum="0"
title="{{iconHtml(MD2Icon.HP,'g-color-google-plus mr-1 g-font-size-18')}}HP" showMaximum
(blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()">
</adj-number-input> -->
<adj-number-input name="heroHP" [(ngModel)]="hero.hp" [maximum]="hero.hpMaximum" minimum="0" <adj-number-input name="heroHP" [(ngModel)]="hero.hp" [maximum]="hero.hpMaximum" minimum="0"
title="{{imgHtml('HpIcon.png','g-height-25 mr-1')}}HP" showMaximum title="{{imgHtml('HpIcon.png','g-height-25')}}" showMaximum
(blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()"> (blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()">
</adj-number-input> </adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroLevel" [(ngModel)]="hero.level" minimum="1" maximum="5"
title="Level" (blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroMana" [(ngModel)]="hero.mp" [maximum]="hero.mpMaximum" <adj-number-input name="heroMana" [(ngModel)]="hero.mp" [maximum]="hero.mpMaximum"
minimum="0" title="{{imgHtml('HeroIcon.png','g-height-25 mr-1')}}Mana" showMaximum minimum="0" title="{{imgHtml('HeroIcon.png','g-height-25')}}" showMaximum
(blur)="heroUpdateDebounceTimer.resetTimer()"> (blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input> </adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroExp" [(ngModel)]="hero.exp" minimum="0" title="Exp"
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroFire" [(ngModel)]="hero.fireToken" minimum="0" <adj-number-input name="heroFire" [(ngModel)]="hero.fireToken" minimum="0"
title="{{iconHtml(MD2Icon.Fire,'g-color-google-plus mr-1 g-font-size-18')}}Fire Token" title="{{iconHtml(MD2Icon.Fire,'g-color-google-plus mr-1 g-font-size-18')}}Fire Token"
(blur)="heroUpdateDebounceTimer.resetTimer()"> (blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroFire" [(ngModel)]="hero.frozenToken" minimum="0" </div>
<div class="col-6">
<adj-number-input name="heroFrozen" [(ngModel)]="hero.frozenToken" minimum="0"
title="{{iconHtml(MD2Icon.Frost,'g-color-aqua mr-1 g-font-size-18')}}Frozen Token" title="{{iconHtml(MD2Icon.Frost,'g-color-aqua mr-1 g-font-size-18')}}Frozen Token"
(blur)="heroUpdateDebounceTimer.resetTimer()"> (blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input> </adj-number-input>
</div> </div>
<div class="col-6"> <div class="col-6">
<adj-number-input name="remainActions" [(ngModel)]="hero.remainActions" minimum="0" <adj-number-input name="remainActions" [(ngModel)]="hero.remainActions" minimum="0"
title="Remain Actions" (blur)="heroUpdateDebounceTimer.resetTimer()" hideIncreaseBtn title="Remain Actions" (blur)="heroUpdateDebounceTimer.resetTimer()" hideIncreaseBtn
*ngIf="hero.uiActivating"> *ngIf="hero.uiActivating">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroLevel" [(ngModel)]="hero.level" minimum="1" maximum="5" </div>
title="Level" (blur)="heroUpdateDebounceTimer.resetTimer()"> <div class="col-6">
</adj-number-input>
<adj-number-input name="heroExp" [(ngModel)]="hero.exp" minimum="0" title="Exp"
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
<adj-number-input name="heroRage" [(ngModel)]="hero.rage" minimum="0" maximum="7" <adj-number-input name="heroRage" [(ngModel)]="hero.rage" minimum="0" maximum="7"
title="{{iconHtml(MD2Icon.Rage,'g-color-google-plus mr-1 g-font-size-18')}}Rage" title="{{iconHtml(MD2Icon.Rage,'g-color-google-plus mr-1 g-font-size-18')}}Rage"
(blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.class==HeroClass.Berserker"> (blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.class==HeroClass.Berserker">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroCorruption" [(ngModel)]="hero.corruptionToken" minimum="0" <adj-number-input name="heroCorruption" [(ngModel)]="hero.corruptionToken" minimum="0"
title="{{imgHtml('Tokens/CorruptToken.png','g-height-18')}} Corruption" title="{{imgHtml('Tokens/CorruptToken.png','g-height-18')}} Corruption"
(blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.uiShowCorruptionToken"> (blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.uiShowCorruptionToken">
</adj-number-input> </adj-number-input>
</div> </div>
</div> </div>
<div *ngIf="md2Service.info.isBossFight"></div> <div *ngIf="md2Service.info.isBossFight"></div>
<div class="d-sm-none">
<div *ngIf="hero.uiActivating&&hero.remainActions>0"> <div *ngIf="hero.uiActivating&&hero.remainActions>0">
<button nbButton hero class="mr-2" status="info" (click)="moveAction()" <button nbButton hero class="mr-2" status="info" (click)="moveAction()"
*ngIf="!showMoveAction">Move</button> *ngIf="!showMoveAction">Move</button>
<button nbButton hero class="mr-2" status="info" (click)="moveActionEnd()" <button nbButton hero class="mr-2" status="info" (click)="moveActionEnd()"
*ngIf="showMoveAction">Move End</button> *ngIf="showMoveAction">Move End</button>
<button nbButton hero class="mr-2" status="danger" (click)="action('attackAction')" <button nbButton hero class="mr-2" status="danger"
*ngIf="!showMoveAction&&allowAttack">Attack!</button> (click)="action('attackAction')">Attack!</button>
<button nbButton hero class="mr-2" status="info" (click)="action('tradeAction')" <button nbButton hero class="mr-2" status="info" (click)="action('tradeAction')"
*ngIf="!showMoveAction">Trade</button> *ngIf="!showMoveAction">Trade</button>
<button nbButton hero status="success" (click)="action('recoveryAction')" <button nbButton hero status="success" (click)="action('recoveryAction')"
*ngIf="!showMoveAction">Recovery</button> *ngIf="!showMoveAction">Recovery</button>
</div> </div>
<button nbButton hero fullWidth status="info" *ngIf="allowStartAction" <button nbButton hero fullWidth status="info" *ngIf="allowStartAction"
(click)="startActivation()">Start Activation</button> (click)="startActivation()">Start Activation</button>
</div>
<button nbButton hero status="info" class="mt-2" (click)="openDoor()" *ngIf="showMoveAction">Open <button nbButton hero status="info" class="mt-2" (click)="openDoor()" *ngIf="showMoveAction">Open
Door</button> Door</button>

View File

@ -11,7 +11,7 @@ import { ADButtonColor, ADButtons } from '../../../ui/alert-dlg/alert-dlg.model'
import { ArrayUtils } from '../../../utilities/array-utils'; import { ArrayUtils } from '../../../utilities/array-utils';
import { StringUtils } from '../../../utilities/string-utils'; import { StringUtils } from '../../../utilities/string-utils';
import { DebounceTimer } from '../../../utilities/timer-utils'; import { DebounceTimer } from '../../../utilities/timer-utils';
import { HeroClass, MD2HeroInfo } from '../massive-darkness2.model'; import { HeroClass, MD2HeroInfo, MD2Icon } from '../massive-darkness2.model';
import { MD2Base } from '../MD2Base'; import { MD2Base } from '../MD2Base';
@Component({ @Component({
@ -65,6 +65,11 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
new MD2HeroInfo({ name: 'Myriam', mpMaximum: 7, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }), new MD2HeroInfo({ name: 'Myriam', mpMaximum: 7, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }),
new MD2HeroInfo({ name: 'Valdis', mpMaximum: 6, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }) new MD2HeroInfo({ name: 'Valdis', mpMaximum: 6, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' })
] ]
public get hero() {
return this.md2Service.playerHero;
}
constructor( constructor(
private gameRoomService: GameRoomService, private gameRoomService: GameRoomService,
public md2Service: MD2Service, public md2Service: MD2Service,
@ -78,7 +83,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
} }
public get allowAttack(): boolean { public get allowAttack(): boolean {
return this.md2Service.playerHero.uiBossFight || (!!this.md2Service.mobs && this.md2Service.mobs.length > 0) || (!!this.md2Service.roamingMonsters && this.md2Service.roamingMonsters.length > 0); return this.hero.uiBossFight || (!!this.md2Service.mobs && this.md2Service.mobs.length > 0) || (!!this.md2Service.roamingMonsters && this.md2Service.roamingMonsters.length > 0);
} }
ngOnInit(): void { ngOnInit(): void {
@ -94,7 +99,6 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
} }
initHero() { initHero() {
this.gameRoomService.gameRoomId = this.roomId; this.gameRoomService.gameRoomId = this.roomId;
this.gameRoomService.joinGameRoom(this.roomId);
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRSessionId)) { 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' }) this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' })
.pipe(first()).subscribe(heroClass => { .pipe(first()).subscribe(heroClass => {
@ -150,6 +154,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
if (result) { if (result) {
this.md2Service.playerJoin(heroInfo); this.md2Service.playerJoin(heroInfo);
this.detectChanges(); this.detectChanges();
this.gameRoomService.joinGameRoom(this.roomId);
} else { } else {
index++; index++;
if (index == this.heros.length) index = 0; if (index == this.heros.length) index = 0;
@ -210,13 +215,27 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
get allowStartAction() { get allowStartAction() {
return !this.md2Service.heros.some(h => h.uiActivating) && !this.hero.uiActivating && this.hero.remainActions > 0; return !this.md2Service.heros.some(h => h.uiActivating) && !this.hero.uiActivating && this.hero.remainActions > 0;
} }
public get hero() {
return this.md2Service.playerHero;
}
startActivation() { startActivation() {
this.hero.uiActivating = true; this.hero.uiActivating = true;
//this.hero.remainActions = 3;
if (this.hero.fireToken > 0) {
this.msgBoxService.show(`You Are On ${this.iconHtml(MD2Icon.Fire)}!`, {
text: `Roll ${this.iconHtml(MD2Icon.YellowDice)} ${this.hero.fireToken} times.`
});
}
if (this.hero.frozenToken > 0) {
let loseActions = Math.min(this.hero.frozenToken, this.hero.remainActions);
this.hero.remainActions -= loseActions;
this.hero.frozenToken -= loseActions;
this.msgBoxService.show(`It's So Cold ${this.iconHtml(MD2Icon.Frost)}!`, {
text: `Lose ${loseActions} actions.`
});
}
if (this.hero.remainActions == 0) {
this.hero.uiActivating = false;
}
this.broadcastHeroInfo(); this.broadcastHeroInfo();
} }
endActivation() { endActivation() {
if (this.hero.remainActions > 0) { if (this.hero.remainActions > 0) {

View File

@ -15,12 +15,15 @@
<div class="col-12 col-md-5"> <div class="col-12 col-md-5">
<nb-card> <nb-card>
<nb-card-header> <nb-card-header>
<img src="{{imgUrl('HeroIcon.png')}}" width="40px"> Game Info <img src="{{imgUrl('HeroIcon.png')}}" width="40px">
<span class="ml-2 g-font-size-17 MD2text" [innerHtml]="round"></span>
<button nbButton hero status="info" size="small" (click)="showQrCode()" <button nbButton hero status="info" size="small" (click)="showQrCode()"
class="float-right">Invite</button> class="float-right">Invite</button>
<button nbButton hero status="info" size="small" [disabled]="anyHeroRemainAction" <button nbButton hero status="info" size="small" [disabled]="anyHeroRemainAction"
(click)="md2Service.runNextPhase()" class="float-right mr-2">Next Phase</button> (click)="md2Service.runNextPhase()" class="float-right mr-2">Next Phase</button>
<button nbButton hero status="info" size="small" (click)="broadcastHeros()"
class="float-right mr-2">Broadcast</button>
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
@ -41,8 +44,7 @@
</div> </div>
<div class="row" *ngIf="md2Service.heros.length>0"> <div class="row" *ngIf="md2Service.heros.length>0">
<div class="col-12 g-font-size-17" [innerHtml]="roundPhase"></div> <!-- <div class="col-12 g-font-size-17" [innerHtml]="roundPhase"></div> -->
<!-- <div class="col-6"> <!-- <div class="col-6">
<label for='playerAmount' class='label'>Hero Amount ({{md2Service.playerAmount}})</label> <label for='playerAmount' class='label'>Hero Amount ({{md2Service.playerAmount}})</label>
</div> </div>
@ -57,14 +59,19 @@
<span class="badge badge-primary mr-1">HP: {{hero.hp}}/{{hero.hpMaximum}}</span> <span class="badge badge-primary mr-1">HP: {{hero.hp}}/{{hero.hpMaximum}}</span>
<span class="badge badge-primary mr-1">Mana: {{hero.mp}}/{{hero.mpMaximum}}</span> <span class="badge badge-primary mr-1">Mana: {{hero.mp}}/{{hero.mpMaximum}}</span>
<span class="badge badge-success mr-1">Exp: {{hero.exp}}</span> <span class="badge badge-success mr-1">Exp: {{hero.exp}}</span>
<span class="badge badge-danger mr-1" *ngIf="hero.fireToken">Fire:{{hero.fireToken}}</span> <span class="badge badge-danger mr-1" *ngIf="hero.fireToken">
<span class="badge badge-info mr-1" *ngIf="hero.frozenToken">Frozen:{{hero.frozenToken}}</span> <md2-icon icon="fire" size="sm"></md2-icon> {{hero.fireToken}}
<span class="badge badge-success mr-1" *ngIf="hero.remainActions>0">Remain </span>
Actions: {{hero.remainActions}}</span> <span class="badge badge-info mr-1" *ngIf="hero.frozenToken">
<md2-icon icon="frozen" size="sm"></md2-icon>{{hero.frozenToken}}
</span>
<span class="badge badge-success mr-1" *ngIf="hero.remainActions>0">Actions:
{{hero.remainActions}}</span>
<span class="badge badge-light mr-1" *ngIf=" !hero.uiActivating">Inactive</span> <span class="badge badge-light mr-1" *ngIf=" !hero.uiActivating">Inactive</span>
<span class="badge badge-primary mr-1" *ngIf="hero.uiActivating">Activating</span> <span class="badge badge-primary mr-1" *ngIf="hero.uiActivating">Activating</span>
<!-- <span class="badge badge-success mr-1">{{hero.playerInfo.signalRClientId}}</span> --> <!-- <span class="badge badge-success mr-1">{{hero.playerInfo.signalRClientId}}</span> -->
<!-- <button nbButton hero status="primary" size="tiny" class="ml-2"
(click)="removeHero(hero)">Remove</button> -->
</div> </div>
</div> </div>
@ -77,10 +84,38 @@
<div class="col-12 col-md-3"> <div class="col-12 col-md-3">
<div class="row"> <div class="row">
<div class="form-group col-12"> <!-- <div class="form-group col-4">
<button nbButton hero fullWidth status="primary" (click)="enterBossFight()">Enter Boss Fight</button> <button nbButton hero fullWidth status="primary" (click)="enterBossFight()">Enter Boss Fight</button>
</div> </div>
<div class="form-group col-4">
<button nbButton hero fullWidth status="primary" (click)="enterBossFight()">Enter Boss Fight</button>
</div>
<div class="form-group col-4">
openGreatTreasureChest
<button nbButton hero fullWidth status="success" (click)="accessHealFountain()">Access Heal Fountain</button>
</div> -->
<ng-container *ngIf="md2Service.currentActivateHero">
<div class="form-group col-12">
<button nbButton hero fullWidth status="info" (click)="md2Service.openTreasureChest()">Open
Treasure Chest</button>
</div>
<div class="form-group col-12">
<button nbButton hero fullWidth status="primary" (click)="md2Service.openGreatTreasureChest()">Open
Great
Treasure Chest</button>
</div>
<div class="form-group col-12">
<button nbButton hero fullWidth status="success" (click)="accessHealFountain()">Access Heal
Fountain</button>
</div>
</ng-container>
<div class="form-group col-12">
<button nbButton hero fullWidth status="danger" (click)="enterBossFight()">Enter Boss Fight</button>
</div>
</div> </div>

View File

@ -16,6 +16,7 @@ import { StringUtils } from '../../utilities/string-utils';
import { SpawnMobDlgComponent } from './mobs/spawn-mob-dlg/spawn-mob-dlg.component'; import { SpawnMobDlgComponent } from './mobs/spawn-mob-dlg/spawn-mob-dlg.component';
import { BossMicheal } from './massive-darkness2.model.boss'; import { BossMicheal } from './massive-darkness2.model.boss';
import { MD2InitService } from '../../services/MD2/md2-init.service'; import { MD2InitService } from '../../services/MD2/md2-init.service';
import { NumberUtils } from '../../utilities/number-utils';
@Component({ @Component({
selector: 'ngx-massive-darkness2', selector: 'ngx-massive-darkness2',
@ -137,4 +138,26 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
this.md2Service.enterBossFight(); this.md2Service.enterBossFight();
} }
accessHealFountain() {
this.md2Service.drawingHealFountain();
}
broadcastHeros() {
this.md2Service.heros.forEach(hero => {
hero.uiShowAttackBtn = this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0 || this.md2Service.info.isBossFight;
});
this.md2Service.broadcastService.broadcastAllHeroInfoToAll();
}
removeHero(hero) {
this.md2Service.info.heros.splice(this.md2Service.info.heros.indexOf(hero));
}
public get round(): string {
if (this.md2Service.info.isBossFight) {
return `Boss Fight ${NumberUtils.Ordinal(this.md2Service.info.boss.rounds)} Round`;
} else {
return NumberUtils.Ordinal(this.md2Service.info.round) + ' Round';
}
}
} }

View File

@ -7,7 +7,9 @@ import { GamePlayer } from "../games.model";
import { MD2HeroInfo, AttackTarget, HeroClass } from "./massive-darkness2.model"; import { MD2HeroInfo, AttackTarget, HeroClass } from "./massive-darkness2.model";
import { MobSkill } from "./massive-darkness2.model.boss"; import { MobSkill } from "./massive-darkness2.model.boss";
export class MD2Logic { export class MD2Logic {
public static getTargetHeroByFilter(heros: MD2HeroInfo[], targetType: AttackTarget) {
return this.getTargetHerosByFilter(heros, targetType, true)[0];
}
public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: AttackTarget, onlyOne: boolean = false) { public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: AttackTarget, onlyOne: boolean = false) {
let beenAttackedHero = [] as MD2HeroInfo[]; let beenAttackedHero = [] as MD2HeroInfo[];
switch (targetType) { switch (targetType) {

View File

@ -324,7 +324,7 @@ export class MobInfo implements IDrawingItem {
uiFrozenTokens: number; uiFrozenTokens: number;
uiCorruptionTokens: number; uiCorruptionTokens: number;
uiAttackedBy: string; uiAttackedBy: string;
extraRule: string;
get identifyName(): string { get identifyName(): string {
return `${this.name}_${this.level}`; return `${this.name}_${this.level}`;
} }
@ -398,6 +398,7 @@ export class MD2HeroInfo {
uiActivating = false; uiActivating = false;
uiShowCorruptionToken = false; uiShowCorruptionToken = false;
uiBossFight = false; uiBossFight = false;
uiShowAttackBtn = false;
public get heroFullName(): string { public get heroFullName(): string {
return `${this.playerInfo.name} (${HeroClass[this.class]} - ${this.name})` return `${this.playerInfo.name} (${HeroClass[this.class]} - ${this.name})`
@ -488,7 +489,7 @@ export class CoreGameDarknessPhaseRule implements IDarknessPhaseRule {
switch (this.round) { switch (this.round) {
case 3: case 3:
case 9: case 9:
this.spawnMob.next(); this.spawnRoamingMonster.next();
return false; return false;
break; break;
case 4: case 4:
@ -496,7 +497,7 @@ export class CoreGameDarknessPhaseRule implements IDarknessPhaseRule {
break; break;
case 5: case 5:
case 7: case 7:
this.spawnRoamingMonster.next(); this.spawnMob.next();
return false; return false;
break; break;
case 6: case 6:

View File

@ -20,9 +20,9 @@
<span class="MD2text diceAmount">x{{info.red}}</span> <span class="MD2text diceAmount">x{{info.red}}</span>
</span> </span>
</div> </div>
<div *ngIf="mob.defenseInfo.black" class="g-height-45 mt-1"> <div *ngIf="info.black" class="g-height-45 mt-1">
<span class="MD2Icon Black dice g-font-size-50"> <span class="MD2Icon Black dice g-font-size-50">
<span class="MD2text diceAmount">x{{mob.defenseInfo.black}}</span> <span class="MD2text diceAmount">x{{info.black}}</span>
</span> </span>
</div> </div>
</div> </div>

View File

@ -51,3 +51,7 @@
</md2-mob-attack-info> </md2-mob-attack-info>
<md2-mob-def-info [mob]="mob" [mode]="mode"></md2-mob-def-info> <md2-mob-def-info [mob]="mob" [mode]="mode"></md2-mob-def-info>
<md2-mob-combat-info [mob]="mob" [mode]="mode"></md2-mob-combat-info> <md2-mob-combat-info [mob]="mob" [mode]="mode"></md2-mob-combat-info>
<div *ngIf="mob.extraRule">
<div class="alert alert-warning" role="alert" [innerHtml]="mob.extraRule">
</div>
</div>

View File

@ -3,10 +3,10 @@
<img src="{{imgUrl('Mobs/MobToken.png')}}" width="40px"> {{(isRoamingMonster?'Roaming Monsters':'Mobs')}} <img src="{{imgUrl('Mobs/MobToken.png')}}" width="40px"> {{(isRoamingMonster?'Roaming Monsters':'Mobs')}}
<!-- <button nbButton hero status="warning" size="small" (click)="initMobDecks()" class="float-right">Reset <!-- <button nbButton hero status="warning" size="small" (click)="initMobDecks()" class="float-right">Reset
Mobs</button> --> Mobs</button> -->
<button nbButton hero status="danger" size="small" (click)="spawnMob()" class="float-right" <button nbButton hero status="danger" size="small" (click)="spawnSpecificMob()" class="float-right"
*ngIf="isRoamingMonster">Spawn Roaming *ngIf="isRoamingMonster">Spawn Roaming
Monster</button> Monster</button>
<button nbButton hero status="warning" size="small" (click)="spawnMob()" class="float-right mr-2" <button nbButton hero status="warning" size="small" (click)="spawnSpecificMob()" class="float-right mr-2"
*ngIf="!isRoamingMonster">Spawn *ngIf="!isRoamingMonster">Spawn
Mob</button> Mob</button>
<!-- <button nbButton hero status="warning" size="tiny" (click)="resetTreasureBag()" <!-- <button nbButton hero status="warning" size="tiny" (click)="resetTreasureBag()"

View File

@ -3,14 +3,18 @@ import { ActivatedRoute } from '@angular/router';
import { NbDialogService } from '@nebular/theme'; import { NbDialogService } from '@nebular/theme';
import { stringify } from 'querystring'; import { stringify } from 'querystring';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { DropDownOption } from '../../../entity/dropDownOption';
import { FileService } from '../../../services/file.service'; import { FileService } from '../../../services/file.service';
import { MD2MobService } from '../../../services/MD2/md2-mob.service'; import { MD2MobService } from '../../../services/MD2/md2-mob.service';
import { MD2Service } from '../../../services/MD2/md2.service'; import { MD2Service } from '../../../services/MD2/md2.service';
import { MsgBoxService } from '../../../services/msg-box.service'; import { MsgBoxService } from '../../../services/msg-box.service';
import { StateService } from '../../../services/state.service'; import { StateService } from '../../../services/state.service';
import { ADIcon } from '../../../ui/alert-dlg/alert-dlg.model'; import { ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
import { ArrayUtils } from '../../../utilities/array-utils';
import { NumberUtils } from '../../../utilities/number-utils'; import { NumberUtils } from '../../../utilities/number-utils';
import { StringUtils } from '../../../utilities/string-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 { DrawingBag, DrawingItem, MD2Icon, MobDlgType, MobInfo, TreasureType } from '../massive-darkness2.model';
import { MD2Base, MD2ComponentBase } from '../MD2Base'; import { MD2Base, MD2ComponentBase } from '../MD2Base';
import { SpawnMobDlgComponent } from './spawn-mob-dlg/spawn-mob-dlg.component'; import { SpawnMobDlgComponent } from './spawn-mob-dlg/spawn-mob-dlg.component';
@ -86,6 +90,28 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
}); });
this.cdRef.detectChanges(); this.cdRef.detectChanges();
} }
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 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;
let mob = result.exitingMob == null ? result.mob : result.exitingMob;
this.dlgService.open(SpawnMobDlgComponent, { context: { title: titleText, mode: actType, mob: mob } })
.onClose.pipe(first()).subscribe(result => {
this.afterSpawn();
});
this.cdRef.detectChanges();
}
});
}
afterSpawn() { afterSpawn() {
this.cdRef.detectChanges(); this.cdRef.detectChanges();
this.md2Service.broadcastService.broadcastMobsInfo(); this.md2Service.broadcastService.broadcastMobsInfo();

View File

@ -27,8 +27,9 @@ export class MD2BroadcastService {
} }
public broadcastAllHeroInfoToAll() { public broadcastAllHeroInfoToAll() {
this.stateService.info.heros.forEach(element => { this.stateService.info.heros.forEach(element => {
this.broadcastHeroInfoToOwner(element); this.broadcastHeroInfoToAll(element);
}); });
this.broadcastMobsInfo();
} }
public broadcastHeroAction(action: string, extraValue: any = null) { public broadcastHeroAction(action: string, extraValue: any = null) {

View File

@ -121,19 +121,19 @@ export class MD2MobService {
this.attackingAllExp = 0; this.attackingAllExp = 0;
this.attackingAttackerExp = 0; this.attackingAttackerExp = 0;
this.md2Service.broadcastService.broadcastAllHeroInfoToAll(); this.md2Service.broadcastService.broadcastAllHeroInfoToAll();
this.md2Service.refreshUI$.next();
}); });
} else if (this.attackingAttackerExp > 0 && attacker) { } else if (this.attackingAttackerExp > 0 && attacker) {
this.msgBoxService.show(`<b>${attackerTitle}</b> Gains ${this.attackingAttackerExp} Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => { this.msgBoxService.show(`<b>${attackerTitle}</b> Gains ${this.attackingAttackerExp} Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
attacker.exp += this.attackingAttackerExp; attacker.exp += this.attackingAttackerExp;
this.attackingAttackerExp = 0; this.attackingAttackerExp = 0;
this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker); this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker);
this.md2Service.refreshUI$.next();
}); });
} }
this.md2Service.refreshUI$.next();
} }
} }

View File

@ -22,6 +22,14 @@ export class MD2StateService {
) { } ) { }
public iconHtml(icon: MD2Icon, cssClass = '') { public iconHtml(icon: MD2Icon, cssClass = '') {
if (icon == MD2Icon.Fire) {
cssClass += ' g-color-google-plus ';
}
if (icon == MD2Icon.Frost || icon == MD2Icon.Mana) {
cssClass += ' g-color-aqua ';
}
if (icon < MD2Icon.RedDice) { if (icon < MD2Icon.RedDice) {
return `<span class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>` return `<span class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
} else { } else {
@ -39,4 +47,7 @@ export class MD2StateService {
public treasureImage(type: TreasureType) { public treasureImage(type: TreasureType) {
return this.imgUrl(`TreasureToken/${TreasureType[type]}.png`); return this.imgUrl(`TreasureToken/${TreasureType[type]}.png`);
} }
public treasureImageHtml(type: TreasureType) {
return this.imgHtml(`TreasureToken/${TreasureType[type]}.png`, 'g-height-40 mr-1');
}
} }

View File

@ -124,9 +124,6 @@ export class MD2Service {
public darknessPhase() { public darknessPhase() {
this.heros.forEach(hero => { this.heros.forEach(hero => {
hero.remainActions = 3; hero.remainActions = 3;
let remainFrozenToken = Math.max(0, hero.frozenToken - hero.remainActions);
hero.remainActions = Math.max(0, hero.remainActions - hero.frozenToken);
hero.frozenToken = remainFrozenToken;
this.broadcastService.broadcastHeroInfoToOwner(hero); this.broadcastService.broadcastHeroInfoToOwner(hero);
}); });
this.stateService.info.roundPhase = RoundPhase.HeroPhase; this.stateService.info.roundPhase = RoundPhase.HeroPhase;
@ -143,7 +140,7 @@ export class MD2Service {
//this.runNextPhase(); //this.runNextPhase();
} }
public spawnMob(isRoamingMonster: boolean) { public spawnMob(isRoamingMonster: boolean, mobName: string = null) {
let mobDeck = null as DrawingBag<MobInfo>; let mobDeck = null as DrawingBag<MobInfo>;
let level = 1; let level = 1;
if (this.highestPlayerLevel < 3) { if (this.highestPlayerLevel < 3) {
@ -163,8 +160,13 @@ export class MD2Service {
mobDeck.RestoreRemoveItems(); mobDeck.RestoreRemoveItems();
} }
let newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level)[0]); let newSpawnMob = null as MobInfo;
if (mobName) {
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && m.name == mobName)[0]);
} else {
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level)[0]);
}
let exitingMob = isRoamingMonster ? this.roamingMonsters.find(m => m.name == newSpawnMob.name) : this.mobs.find(m => m.name == newSpawnMob.name); let exitingMob = isRoamingMonster ? this.roamingMonsters.find(m => m.name == newSpawnMob.name) : this.mobs.find(m => m.name == newSpawnMob.name);
@ -272,6 +274,7 @@ export class MD2Service {
levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp); levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp);
} }
} }
this.refreshUI$.next();
if (runNextPhase) { if (runNextPhase) {
this.runNextPhase(); this.runNextPhase();
} }
@ -299,16 +302,16 @@ export class MD2Service {
hero.mp = hero.mpMaximum; hero.mp = hero.mpMaximum;
hero.exp = 0; hero.exp = 0;
this.stateService.playerHero = hero; this.stateService.playerHero = hero;
let message = { // let message = {
receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession, // receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId }, // from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
actionType: 'hero', // actionType: 'hero',
actionName: 'join', // actionName: 'join',
} as SignalRMessage; // } as SignalRMessage;
message.parameters = { hero: JSON.stringify(hero) }; // message.parameters = { hero: JSON.stringify(hero) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => { // this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
}); // });
} }
@ -318,9 +321,6 @@ export class MD2Service {
case RoundPhase.HeroPhase: case RoundPhase.HeroPhase:
this.heros.forEach(hero => { this.heros.forEach(hero => {
hero.remainActions = 3; hero.remainActions = 3;
let remainFrozenToken = Math.max(0, hero.frozenToken - hero.remainActions);
hero.remainActions = Math.max(0, hero.remainActions - hero.frozenToken);
hero.frozenToken = remainFrozenToken;
this.broadcastService.broadcastHeroInfoToOwner(hero); this.broadcastService.broadcastHeroInfoToOwner(hero);
}); });
break; break;
@ -359,6 +359,49 @@ export class MD2Service {
public getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) { public getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) {
return MD2Logic.getTargetHerosHtml(beenAttackedHero); return MD2Logic.getTargetHerosHtml(beenAttackedHero);
} }
public drawingHealFountain() {
let result = new DrawingBag<DrawingItem>([new DrawingItem('Heal Fountain', '3 HP', '', 2), new DrawingItem('Heal Fountain', '4 HP', '', 1), new DrawingItem('Heal Fountain', 'Full HP', '', 1)])
.Draw(1)[0];
switch (result.description) {
case '3 HP':
this.currentActivateHero.hp = Math.min(this.currentActivateHero.hp + 3, this.currentActivateHero.hpMaximum);
break;
case '4 HP':
this.currentActivateHero.hp = Math.min(this.currentActivateHero.hp + 4, this.currentActivateHero.hpMaximum);
break;
case 'Full HP':
this.currentActivateHero.hp = this.currentActivateHero.hpMaximum;
break;
default:
break;
}
this.broadcastService.broadcastHeroInfoToAll(this.currentActivateHero);
this.broadcastService.broadcastHeroInfoToOwner(this.currentActivateHero);
this.msgBoxService.show(
this.stateService.imgHtml('/Fountain/Cover.png', '')
, { text: `${this.currentActivateHero.heroFullName} Recovered ${result.description}.` });
}
public openTreasureChest() {
let result = new DrawingBag<DrawingItem>([
new DrawingItem('Common', `${this.stateService.treasureImageHtml(TreasureType.Common)} x 2`, '', 10),
new DrawingItem('Open Treasure Chest', `${this.stateService.treasureImageHtml(TreasureType.Rare)} x 2`, '', 5),
])
.Draw(1)[0];
this.msgBoxService.show(
this.stateService.imgHtml('/TreasureChest/SmallTresureChest.jpg', ''), { text: `${this.currentActivateHero.heroFullName} gets ${result.description}.` });
}
public openGreatTreasureChest() {
let result = new DrawingBag<DrawingItem>([
new DrawingItem('Common', `${this.stateService.treasureImageHtml(TreasureType.Rare)} x 3`, '', 3),
new DrawingItem('Open Treasure Chest', `${this.stateService.treasureImageHtml(TreasureType.Epic)} x 3`, '', 1),
]).Draw(1)[0];
this.msgBoxService.show(
this.stateService.imgHtml('/TreasureChest/BigTresureChest.png', ''), { text: `${this.currentActivateHero.heroFullName} gets ${result.description}.` });
}
// #endregion Public Methods (27) // #endregion Public Methods (27)
} }

View File

@ -1,7 +1,7 @@
<form (ngSubmit)="confirm()" #inputForm="ngForm"> <form (ngSubmit)="confirm()" #inputForm="ngForm">
<nb-card class="alertCard {{config.cardWidthClass}} g-px-0 g-px-20--md"> <nb-card class="alertCard {{config.cardWidthClass}} g-px-2 g-px-20--md">
<nb-card-body class="g-px-0 g-px-20--md"> <nb-card-body class="g-px-2 g-px-20--md">
<div class="icon {{config.icon}}" *ngIf="config.icon" [ngSwitch]="config.icon"> <div class="icon {{config.icon}}" *ngIf="config.icon" [ngSwitch]="config.icon">
<div class="icon-content" *ngSwitchCase="'question'"> <div class="icon-content" *ngSwitchCase="'question'">