Compare commits

..

2 Commits

Author SHA1 Message Date
Chris Chen
d486fe9594 Update boss fight 2024-03-29 08:04:07 -07:00
Chris Chen
6a031ca478 WIP 2024-03-22 11:06:42 -07:00
63 changed files with 2745 additions and 1118 deletions

View File

@ -70,6 +70,10 @@ const socialLinks = [
NbChatModule.forRoot({ NbChatModule.forRoot({
messageGoogleMapKey: 'AIzaSyA_wNuCzia92MAmdLRemailRGvCF7wCZPY', messageGoogleMapKey: 'AIzaSyA_wNuCzia92MAmdLRemailRGvCF7wCZPY',
}), }),
NbDialogModule.forRoot({
closeOnBackdropClick: false,
closeOnEsc: false
}),
NgxMaskModule.forRoot(maskConfig), NgxMaskModule.forRoot(maskConfig),
NbDateFnsDateModule.forRoot({ format: 'MM/dd/yyyy' }), NbDateFnsDateModule.forRoot({ format: 'MM/dd/yyyy' }),
CoreModule.forRoot(), CoreModule.forRoot(),

View File

@ -32,6 +32,7 @@ import { BossActivationComponent } from './massive-darkness2/boss-fight/boss-act
import { MobAttackInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-attack-info/mob-attack-info.component'; import { MobAttackInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-attack-info/mob-attack-info.component';
import { MobDefInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-def-info/mob-def-info.component'; import { MobDefInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-def-info/mob-def-info.component';
import { MobCombatInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-combat-info/mob-combat-info.component'; import { MobCombatInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-combat-info/mob-combat-info.component';
import { MobStandInfoComponent } from './massive-darkness2/mobs/mob-stand-info/mob-stand-info.component';
@NgModule({ @NgModule({
@ -58,7 +59,8 @@ import { MobCombatInfoComponent } from './massive-darkness2/mobs/mob-detail-info
BossActivationComponent, BossActivationComponent,
MobAttackInfoComponent, MobAttackInfoComponent,
MobDefInfoComponent, MobDefInfoComponent,
MobCombatInfoComponent MobCombatInfoComponent,
MobStandInfoComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@ -2,7 +2,7 @@ import { ChangeDetectorRef, Injectable } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import { first, takeUntil } from "rxjs/operators"; import { first, takeUntil } from "rxjs/operators";
import { MD2GameInfo, MD2Service } from "../../services/md2.service"; import { MD2GameInfo, MD2Service } from "../../services/MD2/md2.service";
import { SignalRMessage } from "../../services/signal-r.service"; import { SignalRMessage } from "../../services/signal-r.service";
import { StateService } from "../../services/state.service"; import { StateService } from "../../services/state.service";
import { ADIcon, MessageBoxConfig } from "../../ui/alert-dlg/alert-dlg.model"; import { ADIcon, MessageBoxConfig } from "../../ui/alert-dlg/alert-dlg.model";
@ -37,6 +37,9 @@ export abstract class MD2Base {
} }
});
this.md2Service.refreshUI$.pipe(takeUntil(this.destroy$)).subscribe(result => {
this.cdRef.detectChanges();
}); });
this.stateService.loginUserService.signalRInitialized.pipe(first()).subscribe(result => { this.stateService.loginUserService.signalRInitialized.pipe(first()).subscribe(result => {
console.log('signalRInitialized'); console.log('signalRInitialized');
@ -55,14 +58,14 @@ export abstract class MD2Base {
} }
imgUrl(imgPath: string) { imgUrl(imgPath: string) {
return this.md2Service.imgUrl(imgPath); return this.md2Service.stateService.imgUrl(imgPath);
} }
fileList(folderPath: string) { fileList(folderPath: string) {
return this.md2Service.fileList(folderPath); return this.md2Service.fileList(folderPath);
} }
iconHtml(icon: MD2Icon, cssClass = '') { iconHtml(icon: MD2Icon, cssClass = '') {
return this.md2Service.iconHtml(icon, cssClass); return this.md2Service.stateService.iconHtml(icon, cssClass);
} }
imgHtml(imgFile: string, cssClass = '') { imgHtml(imgFile: string, cssClass = '') {
@ -78,9 +81,15 @@ export abstract class MD2Base {
} }
abstract refreshUI(); abstract refreshUI();
handleSignalRCallback(message: SignalRMessage): void { handleSignalRCallback(message: SignalRMessage): void {
if (message.from.isGroup) {
if (!this.isHeroDashboard) return;
} else {
if (this.isHeroDashboard && this.md2Service.playerHero.playerInfo.signalRClientId == message.from.sessionId) return;
}
switch (message.actionType) { switch (message.actionType) {
case 'hero': case 'hero':
let heroInfo = JSON.parse(message.parameters['hero']) as MD2HeroInfo; let heroInfo = new MD2HeroInfo(JSON.parse(message.parameters['hero']));
switch (message.actionName) { switch (message.actionName) {
case 'join': case 'join':
this.md2Service.heros.push(heroInfo); this.md2Service.heros.push(heroInfo);
@ -88,26 +97,43 @@ export abstract class MD2Base {
case 'update': case 'update':
let exitingHero = this.md2Service.heros.find(h => h.playerInfo.signalRClientId == heroInfo.playerInfo.signalRClientId); let exitingHero = this.md2Service.heros.find(h => h.playerInfo.signalRClientId == heroInfo.playerInfo.signalRClientId);
if (exitingHero) { if (exitingHero) {
Object.keys(heroInfo).forEach(key => exitingHero[key] = heroInfo[key]); let activateBoss = exitingHero.uiActivating && !heroInfo.uiActivating;
this.md2Service.heros[this.md2Service.heros.indexOf(exitingHero)] = heroInfo;
if (this.isHeroDashboard && this.md2Service.stateService.playerHero.playerInfo.tabId == heroInfo.playerInfo.tabId) {
this.md2Service.stateService.playerHero = heroInfo;
}
if (!this.isHeroDashboard && this.md2Service.info.isBossFight && activateBoss) {
this.md2Service.activateBoss();
}
} else { } else {
this.md2Service.heros.push(heroInfo); this.md2Service.heros.push(heroInfo);
} }
if (!this.isHeroDashboard) { if (!this.isHeroDashboard) {
if (this.gameInfo.roundPhase == RoundPhase.HeroPhase) { if (this.gameInfo.roundPhase == RoundPhase.HeroPhase) {
if (!this.md2Service.heros.some(h => h.remainActions > 0)) { if (!this.md2Service.heros.some(h => h.remainActions > 0) && !this.md2Service.heros.some(h => h.uiActivating)) {
if (this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0) { if (!this.md2Service.info.isBossFight) {
this.md2Service.msgBoxService.show('Enemy Phase', { icon: ADIcon.WARNING }).pipe(first()).subscribe(result => { if (this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0) {
this.md2Service.msgBoxService.show('Enemy Phase', { icon: ADIcon.WARNING }).pipe(first()).subscribe(result => {
this.md2Service.runNextPhase();
});
} else {
this.md2Service.runNextPhase(); this.md2Service.runNextPhase();
}); }
} else {
this.md2Service.runNextPhase();
} }
} }
} }
} }
//Object.assign(heroInfo, exitingHero); //Object.assign(heroInfo, exitingHero);
break;
case 'updateMyHero':
if (this.isHeroDashboard) {
this.md2Service.stateService.playerHero = heroInfo;
}
break; break;
default: default:
break; break;
@ -126,6 +152,12 @@ export abstract class MD2Base {
this.detectChanges(); this.detectChanges();
} }
break; break;
case 'phase':
if (this.isHeroDashboard) {
this.md2Service.info.roundPhase = JSON.parse(message.parameters['phase']);
this.detectChanges();
}
break;
default: default:
break; break;
} }
@ -160,17 +192,21 @@ export abstract class MD2Base {
break; break;
case 'heroAction': case 'heroAction':
if (!this.isHeroDashboard) { if (!this.isHeroDashboard) {
this.gameInfo.currentActivateHero = this.md2Service.heros.find(h => h.playerInfo.tabId == message.parameters['tabId']); //this.md2Service.currentActivateHero = this.md2Service.heros.find(h => h.playerInfo.tabId == message.parameters['tabId']);
switch (message.actionName) { switch (message.actionName) {
case 'attackAction': case 'attackAction':
this.gameInfo.showAttackBtn = true; if (this.gameInfo.isBossFight) {
this.md2Service.heroAttackingSubject.next(this.md2Service.currentActivateHero);
} else {
this.gameInfo.showAttackBtn = true;
}
break; break;
case 'openDoor': case 'openDoor':
//Door component listen for it //Door component listen for it
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</b> with ${this.md2Service.heroFullName(this.gameInfo.currentActivateHero)} may freely trade and text: `every one in the <b>same zone with <b>${this.md2Service.heroFullName(this.md2Service.currentActivateHero)}</b> may freely trade and
equip items!`, equip items!`,
icon: ADIcon.INFO icon: ADIcon.INFO
}); });
@ -179,7 +215,7 @@ export abstract class MD2Base {
//this.md2Service.roundPhase = Number.parseInt(message.parameters['phase']); //this.md2Service.roundPhase = Number.parseInt(message.parameters['phase']);
break; break;
} }
this.heroAction(this.gameInfo.currentActivateHero, message.actionName); this.heroAction(this.md2Service.currentActivateHero, message.actionName);
this.detectChanges(); this.detectChanges();
} }
break; break;
@ -218,13 +254,24 @@ export abstract class MD2ComponentBase {
this.destroy$.complete(); this.destroy$.complete();
} }
imgUrl(imgPath: string) { imgUrl(imgPath: string) {
return this.md2Service.imgUrl(imgPath); return this.md2Service.stateService.imgUrl(imgPath);
} }
fileList(folderPath: string) { fileList(folderPath: string) {
return this.md2Service.fileList(folderPath); return this.md2Service.fileList(folderPath);
} }
iconHtml(icon: MD2Icon, cssClass = '') { iconHtml(icon: MD2Icon, cssClass = '') {
return this.md2Service.iconHtml(icon, cssClass); return this.md2Service.stateService.iconHtml(icon, cssClass);
}
detectChanges() {
if (!this.cdRef['destroyed']) {
this.cdRef.detectChanges();
this.refreshUI();
this.md2Service.refreshUI$.next();
}
}
refreshUI() {
} }
} }

View File

@ -1,21 +1,31 @@
<nb-card> <nb-card>
<nb-card-body> <nb-card-body class="g-overflow-hidden">
<div class="row form-group" style=" <div class="row form-group">
height: 53vh; <div class="col-md-5 g-height-700px">
overflow: auto; <md2-mob-stand-info [mob]="boss.info" [mode]="mode"></md2-mob-stand-info>
">
<div class="col-md-5">
<img src="{{boss.standUrl}}" class="img-fluid">
</div> </div>
<div class="col-md-7"> <div class="col-md-7">
<label class="MD2text g-font-size-40 mt-5" [innerHtml]="bossAction.skillName"> <label class="MD2text g-font-size-40 mt-4" [innerHtml]="bossAction.name">
</label> </label>
<label class="g-font-size-20 mt-3" [innerHtml]="bossAction.skillDescription"> <label class="mt-2 g-font-size-20 my-3 MD2IconContainer-lg" [innerHtml]="bossAction.description">
</label> </label>
<hr>
<div class="row">
<div class="col-md-4">
<md2-mob-attack-info [mob]="boss.info"></md2-mob-attack-info>
</div>
<div class="col-md-8 MD2IconContainer-lg">
<md2-mob-combat-info [mob]="boss.info"></md2-mob-combat-info>
</div>
</div>
</div> </div>
</div> </div>
</nb-card-body> </nb-card-body>

View File

@ -1,10 +1,12 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { NbDialogRef } from '@nebular/theme'; import { NbDialogRef } from '@nebular/theme';
import { MD2Service } from '../../../../services/md2.service'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
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 { MobDlgType, MD2Icon, MD2HeroInfo } from '../../massive-darkness2.model'; import { MobDlgType, MD2Icon, MD2HeroInfo, RoundPhase } from '../../massive-darkness2.model';
import { MobSkill, IBossFight } from '../../massive-darkness2.model.boss'; import { MobSkill, IBossFight } from '../../massive-darkness2.model.boss';
import { MD2ComponentBase } from '../../MD2Base'; import { MD2ComponentBase } from '../../MD2Base';
import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.component'; import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.component';
@ -14,12 +16,13 @@ import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.com
templateUrl: './boss-activation.component.html', templateUrl: './boss-activation.component.html',
styleUrls: ['./boss-activation.component.scss'] styleUrls: ['./boss-activation.component.scss']
}) })
export class BossActivationComponent extends MD2ComponentBase implements OnInit { export class BossActivationComponent implements OnInit {
boss: IBossFight; boss: IBossFight;
bossAction: MobSkill; bossAction: MobSkill;
currentAction: number;
allActions: number;
MobDlgType = MobDlgType; MobDlgType = MobDlgType;
mode: MobDlgType; mode: MobDlgType = MobDlgType.Activating;
title: string; title: string;
titleHtml: string; titleHtml: string;
@ -31,16 +34,29 @@ export class BossActivationComponent extends MD2ComponentBase implements OnInit
otherAttackTarget: string; otherAttackTarget: string;
constructor( constructor(
private dlgRef: NbDialogRef<SpawnMobDlgComponent>, private dlgRef: NbDialogRef<SpawnMobDlgComponent>,
private msgBoxService: MsgBoxService,
public md2Service: MD2Service, public md2Service: MD2Service,
protected stateService: StateService, protected stateService: StateService,
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef, protected cdRef: ChangeDetectorRef,
) { ) {
super(md2Service, stateService, route, cdRef); this.md2Service.refreshUI$.pipe(takeUntil(this.destroy$)).subscribe(result => {
if (!this.cdRef['destroyed']) {
this.cdRef.detectChanges();
}
});
}
private destroy$: Subject<void> = new Subject<void>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
ngOnInit(): void {
} }
close() { close() {
this.boss.standUrl //this.boss.standUrl
this.md2Service.info.roundPhase = RoundPhase.HeroPhase;
this.dlgRef.close(); this.dlgRef.close();
} }

View File

@ -1,5 +1,5 @@
<nb-card> <nb-card>
<nb-card-header> <nb-card-header class="MD2text g-font-size-28">
{{boss.name}} {{boss.name}}
<button nbButton hero status="primary" (click)="activate()">Action</button> <button nbButton hero status="primary" (click)="activate()">Action</button>
</nb-card-header> </nb-card-header>
@ -9,18 +9,29 @@
<img src="{{boss.standUrl}}" class="w-100 g-max-height-80vh"> <img src="{{boss.standUrl}}" class="w-100 g-max-height-80vh">
</div> </div>
<div class="col-md-7"> <div class="col-md-7">
<md2-mob-detail-info [mob]="boss.info"> <div class="row">
</md2-mob-detail-info> <div class="col-md">
<adj-number-input name="mob{{boss.info.name}}" [(ngModel)]="boss.info.unitRemainHp" minimum="0"
class="mb-3" title="Boss HP" (hitMinimum)="WIN()">
</adj-number-input>
<md2-mob-attack-info [mob]="boss.info">
</md2-mob-attack-info>
<md2-mob-def-info [mob]="boss.info"></md2-mob-def-info>
</div>
<div class="col-md-9 h6" *ngIf="boss.extraRules">
<div [innerHtml]="boss.extraRules"></div>
</div>
<adj-number-input name="mob{{boss.info.name}}" [(ngModel)]="boss.info.unitRemainHp" minimum="0" </div>
title="Boss HP" (hitMinimum)="WIN()">
</adj-number-input>
<button nbButton hero status="danger" size="small" (click)="attack(boss.info)">Attack It</button>
<label class="MD2Text mt-3" [innerHtml]="boss.combatInfo.skillName"> <md2-mob-combat-info [mob]="boss.info"></md2-mob-combat-info>
</label> <!--
<label class="MD2Text" [innerHtml]="boss.combatInfo.skillDescription"> <button nbButton hero status="danger" size="small" (click)="attack(boss.info)">Attack It</button> -->
<!-- <label class="MD2Text mt-3" [innerHtml]="boss.info.combatSkill.skillName">
</label> </label>
<label class="MD2Text" [innerHtml]="boss.info.combatInfo.skillDescription">
</label> -->
</div> </div>
</div> </div>

View File

@ -2,8 +2,8 @@ import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { NbDialogService } from '@nebular/theme'; import { NbDialogService } from '@nebular/theme';
import { Subject, Observable } from 'rxjs'; import { Subject, Observable } from 'rxjs';
import { first } from 'rxjs/operators'; import { first, takeUntil } from 'rxjs/operators';
import { MD2Service } from '../../../services/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';
@ -37,7 +37,16 @@ export class BossFightComponent extends MD2ComponentBase {
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit(); super.ngOnInit();
this.md2Service.heroAttackingSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
if (this.md2Service.info.isBossFight) {
this.attack(this.boss.info);
}
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
} }
activate() { activate() {
this.boss.activating(); this.boss.activating();

View File

@ -5,7 +5,7 @@ import { ADButtons } from '../../../ui/alert-dlg/alert-dlg.model';
import { DrawingBag, DrawingItem } from '../massive-darkness2.model'; import { DrawingBag, DrawingItem } from '../massive-darkness2.model';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { MD2Service } from '../../../services/md2.service'; import { MD2Service } from '../../../services/MD2/md2.service';
import { StateService } from '../../../services/state.service'; import { StateService } from '../../../services/state.service';
import { MD2Base, MD2ComponentBase } from '../MD2Base'; import { MD2Base, MD2ComponentBase } from '../MD2Base';
import { SignalRMessage } from '../../../services/signal-r.service'; import { SignalRMessage } from '../../../services/signal-r.service';
@ -58,7 +58,7 @@ export class DoorEventsComponent extends MD2ComponentBase implements OnInit {
this.msgBoxService.show('', { text: `<img src="${door.imageUrl}" class="g-height-70vh g-max-width-80vw">`, buttons: ADButtons.YesNo, confirmButtonText: 'Keep It', cancelButtonText: 'Discard' }) this.msgBoxService.show('', { text: `<img src="${door.imageUrl}" class="g-height-70vh g-max-width-80vw">`, buttons: ADButtons.YesNo, confirmButtonText: 'Keep It', cancelButtonText: 'Discard' })
.pipe(first()).subscribe(result => { .pipe(first()).subscribe(result => {
if (result) { if (result) {
door.name = this.md2Service.heroFullName(this.md2Service.info.currentActivateHero); door.name = this.md2Service.heroFullName(this.md2Service.currentActivateHero);
this.drawDoorEvents.push(door); this.drawDoorEvents.push(door);
} }
this.cdRef.detectChanges(); this.cdRef.detectChanges();

View File

@ -0,0 +1,20 @@
import { ObjectUtils } from "../../../utilities/object-utils";
import { IDrawingItem, MobInfo, TreasureItem } from "../massive-darkness2.model";
export class MD2Clone {
public static CloneDrawingItem(obj: IDrawingItem) {
let type = obj.constructor.name;
let cloneObj = null;
switch (type) {
case "TreasureItem":
return new TreasureItem(obj['type'], 1);
break;
case "MobInfo":
return new MobInfo(obj);
break;
default: break;
}
return ObjectUtils.CloneValue(obj);
}
}

View File

@ -0,0 +1,222 @@
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(),
];

View File

@ -0,0 +1,337 @@
import { Subject } from "rxjs";
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";
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }
const CORE_GAME_MOB_LEVEL = [
new MobInfo({
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),
}),
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),
}),
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),
}),
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),
}),
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),
}),
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),
}),
new MobInfo({
name: 'Lyidan, Incubus Lord', level: 1, hp: 7,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1, 0, 2)],
defenseInfo: new DefenseInfo(2, 1),
}),
new MobInfo({
name: 'Lyidan, Incubus Lord', level: 3, hp: 10,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 2, 0, 1)],
defenseInfo: new DefenseInfo(2, 1),
}),
new MobInfo({
name: 'Lyidan, Incubus Lord', level: 5, hp: 12,
attackInfos: [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)],
defenseInfo: new DefenseInfo(4, 1),
}),
new MobInfo({
name: 'The Ghoul', level: 1, hp: 5,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1, 0, 1)],
defenseInfo: new DefenseInfo(2, 1),
}),
new MobInfo({
name: 'The Ghoul', level: 3, hp: 8,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 2, 0, 2)],
defenseInfo: new DefenseInfo(3, 1),
}),
new MobInfo({
name: 'The Ghoul', level: 5, hp: 10,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 3, 0, 3)],
defenseInfo: new DefenseInfo(4, 1),
}),
]
export abstract class CoreGameRMFactory 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({
type: MobType.RoamingMonster,
name: mobName, hpPerHero: levelInfo.hp, level: level, rewardTokens: levelInfo.rewardTokens,
attackInfos: levelInfo.attackInfos,
defenseInfo: levelInfo.defenseInfo,
actionSubject: new Subject<string>()
});
this.mob.leaderImgUrl = MD2_IMG_URL(`/CoreGame/RoamingMonsters/${this.mob.name}/Stand.png`);
//this.mob.minionImgUrl = MD2_IMG_URL(`CoreGame/Mobs/${encodeURI(this.mob.name)}/Minion.png`);
if (level < 3) {
this.mob.rewardTokens = 2;
this.mob.fixedCarriedTreasure = [new TreasureItem(TreasureType.Rare)];
} else if (level < 5) {
this.mob.rewardTokens = 2;
this.mob.fixedCarriedTreasure = [new TreasureItem(TreasureType.Epic)];
} else {
this.mob.rewardTokens = 0;
this.mob.fixedCarriedTreasure = [new TreasureItem(TreasureType.Epic, 3)];
}
}
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) {
return `<span class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
}
else {
return `<span class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
}
}
}
export class RMUndeadQueenFactory extends CoreGameRMFactory {
mobName: string = 'Ytheria, Undead Queen';
generate(level: number): MobInfo {
this.loadLevelInfo('Ytheria, Undead Queen', level);
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
msgBoxService.show('Is There more than 1 Hero in LoS of Undead Queen?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.actions = 0;
mob.actionSubject.next(
`Undead Queen attacks each Hero in LoS(resolve each attack separately).`
);
} else {
msgBoxService.show('Is There any Hero in LoS of Undead Queen?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
if (level < 3) {
actionResult = `Undead Queen +1 ${this.iconHtml(MD2Icon.YellowDice)} when attack`;
} else if (level < 5) {
actionResult = `Undead Queen +1 ${this.iconHtml(MD2Icon.YellowDice)} 1 ${this.iconHtml(MD2Icon.OrangeDice)} when attack`;
} else {
actionResult = `Undead Queen +2 ${this.iconHtml(MD2Icon.OrangeDice)} when attack`;
}
mob.actions = 0;
mob.actionSubject.next(
actionResult
);
} else {
mob.actions = 2;
mob.actionSubject.next(
`Undead Queen Gains 2 Actions`
);
}
});
}
});;
}
this.mob.combatSkill = new MobSkill(
{
description: `Add 1 Minion to each Mob in the Dungeon, if possible.`,
type: MobSkillType.Attack
}
)
return this.mob;
}
}
export class RMAndraFactory extends CoreGameRMFactory {
mobName: string = 'Andra';
generate(level: number): MobInfo {
this.loadLevelInfo('Andra', level);
let damage = 2;
if (level < 3) {
damage = 1;
}
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
mob.actions = 0;
msgBoxService.show('Is Andra in the Dungeon?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
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?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.actionSubject.next(
`Andra attack the Hero with the lowest HP in LoS.<br>then Put Andra to out side of Dungeon.(Hero is not reachable but not dead)`
);
} else {
mob.actions = 0;
mob.actionSubject.next(
`Put Andra to out side of Dungeon.(Hero is not reachable but not dead)`
);
}
});
} else {
let beenAttackHero = MD2Logic.getTargetHerosByFilter(heros, AttackTarget.LeastHp, true)[0];
mob.actionSubject.next(
`Place Andra in the same zone of ${MD2Logic.heroFullName(beenAttackHero)} and attack that Hero.`
);
}
});
}
this.mob.combatSkill = new MobSkill(
{
description: `Deal ${damage} wound to another Hero with the lowest HP in LoS`,
type: MobSkillType.Combat
}
)
return this.mob;
}
}
export class RMTheGhoulFactory extends CoreGameRMFactory {
mobName: string = 'The Ghoul';
generate(level: number): MobInfo {
this.loadLevelInfo('The Ghoul', level);
let health = 2;
if (level < 3) {
health = 5;
} else if (level < 5) {
health = 8;
} else {
health = 10;
}
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
mob.actions = 0;
msgBoxService.show('Is there any <b>Mob with minion</b> in The Ghoul zone?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.unitRemainHp += health;
mob.actionSubject.next(
`Kill 1 minion in The Ghoul zone(player choose), The Ghoul heals ${health}.`
);
} else {
mob.actionSubject.next(
`The Ghoul moves 3 Zones toward the closest Hero and attack him/her, if possible.`
);
}
});
}
this.mob.combatSkill = new MobSkill(
{
description: `Move the closest <b>Mob with minion</b> 1 Zone toward The Ghoul.`,
type: MobSkillType.Combat
}
)
return this.mob;
}
}
export class RMLyidanIncubusLordFactory extends CoreGameRMFactory {
mobName: string = 'Lyidan, Incubus Lord';
generate(level: number): MobInfo {
this.loadLevelInfo('Lyidan, Incubus Lord', level);
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
mob.actions = 0;
msgBoxService.show('Is Incubus Lord in a Light Zone?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.unitRemainHp -= 3;
mob.actionSubject.next(
`The Incubus Lord got 3 Wounds, Move it to the closest Shadow Zone.`
);
} else {
msgBoxService.show('Is there a Herp up to 3 Zones away(regardless of LoS) from The Incubus Lord?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.actionSubject.next(
`Place The Incubus Lord in the zone of furthest Hero up to 3 Zones away.<br>` +
`Add 1 ${this.iconHtml(MD2Icon.Fire)} to that Hero and attack him/her.`
);
} else {
mob.actions = 2;
mob.actionSubject.next(
`The Incubus Lord 2 Actions`
);
}
});
}
});
}
this.mob.combatSkill = new MobSkill(
{
description: `After combat, resolve all ${this.iconHtml(MD2Icon.Fire)} on the defending Hero(once per combat).`,
type: MobSkillType.Attack
}
)
return this.mob;
}
}
export const CoreGameRMFactories = [
new RMUndeadQueenFactory(),
new RMAndraFactory(),
new RMTheGhoulFactory(),
new RMLyidanIncubusLordFactory(),
];

View File

@ -1,9 +1,9 @@
<nb-card *ngIf="!md2Service.playerHero"> <nb-card *ngIf="!hero">
<nb-card-body> <nb-card-body>
<button nbButton hero status="primary" fullWidth (click)="initHero()">Choose Hero</button> <button nbButton hero status="primary" fullWidth (click)="initHero()">Choose Hero</button>
</nb-card-body> </nb-card-body>
</nb-card> </nb-card>
<div *ngIf="md2Service.playerHero"> <div *ngIf="hero">
<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">
@ -11,24 +11,26 @@
[@flipState]="flip"> [@flipState]="flip">
<div class="tp-box__side tp-box__front "> <div class="tp-box__side tp-box__front ">
<img class="MD2HeroCard " src="{{md2Service.playerHero.imgUrl}}"> <img class="MD2HeroCard " src="{{hero.imgUrl}}">
</div> </div>
<div class="tp-box__side tp-box__back"> <div class="tp-box__side tp-box__back">
<img class="MD2HeroCard " src="{{imgUrl('Heros/Guide/'+className+'.jpg')}}"> <img class="MD2HeroCard " src="{{imgUrl('Heros/Guide/'+className+'.jpg')}}">
<img class="MD2HeroCard " src="{{imgUrl('Sets/Shadowbane/'+className+'.png')}}">
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="g-max-height-80vh mb-2"> <!-- <div class="g-max-height-80vh mb-2">
<img class="MD2HeroCard" src="{{md2Service.playerHero.imgUrl}}"> <img class="MD2HeroCard" src="{{hero.imgUrl}}">
<div class="MD2HeroCard"> <div class="MD2HeroCard">
<span class="MD2text MD2Name">{{md2Service.playerHero.name}}</span> <span class="MD2text MD2Name">{{hero.name}}</span>
<span class="MD2text MD2Hp">{{md2Service.playerHero.hpMaximum}}</span> <span class="MD2text MD2Hp">{{hero.hpMaximum}}</span>
<span class="MD2text MD2Mp">{{md2Service.playerHero.mpMaximum}}</span> <span class="MD2text MD2Mp">{{hero.mpMaximum}}</span>
</div> </div>
<img class="MD2HeroCard" src="{{md2Service.playerHero.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>
@ -39,63 +41,57 @@
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-6"> <div class="col-6">
<!-- <adj-number-input name="heroHP" [(ngModel)]="md2Service.playerHero.hp" <!-- <adj-number-input name="heroHP" [(ngModel)]="hero.hp"
[maximum]="md2Service.playerHero.hpMaximum" minimum="0" [maximum]="hero.hpMaximum" minimum="0"
title="{{iconHtml(MD2Icon.HP,'g-color-google-plus mr-1 g-font-size-18')}}HP" showMaximum title="{{iconHtml(MD2Icon.HP,'g-color-google-plus mr-1 g-font-size-18')}}HP" showMaximum
(blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()"> (blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()">
</adj-number-input> --> </adj-number-input> -->
<adj-number-input name="heroHP" [(ngModel)]="md2Service.playerHero.hp" <adj-number-input name="heroHP" [(ngModel)]="hero.hp" [maximum]="hero.hpMaximum" minimum="0"
[maximum]="md2Service.playerHero.hpMaximum" minimum="0"
title="{{imgHtml('HpIcon.png','g-height-25 mr-1')}}HP" showMaximum title="{{imgHtml('HpIcon.png','g-height-25 mr-1')}}HP" showMaximum
(blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()"> (blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroMana" [(ngModel)]="md2Service.playerHero.mp" <adj-number-input name="heroMana" [(ngModel)]="hero.mp" [maximum]="hero.mpMaximum"
[maximum]="md2Service.playerHero.mpMaximum" minimum="0" minimum="0" title="{{imgHtml('HeroIcon.png','g-height-25 mr-1')}}Mana" showMaximum
title="{{imgHtml('HeroIcon.png','g-height-25 mr-1')}}Mana" showMaximum
(blur)="heroUpdateDebounceTimer.resetTimer()"> (blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroFire" [(ngModel)]="md2Service.playerHero.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)]="md2Service.playerHero.frozenToken" <adj-number-input name="heroFire" [(ngModel)]="hero.frozenToken" minimum="0"
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)]="md2Service.playerHero.remainActions" <adj-number-input name="remainActions" [(ngModel)]="hero.remainActions" minimum="0"
minimum="0" title="Remain Actions" (blur)="heroUpdateDebounceTimer.resetTimer()" title="Remain Actions" (blur)="heroUpdateDebounceTimer.resetTimer()" hideIncreaseBtn
hideIncreaseBtn> *ngIf="hero.uiActivating">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroLevel" [(ngModel)]="md2Service.playerHero.level" minimum="1" <adj-number-input name="heroLevel" [(ngModel)]="hero.level" minimum="1" maximum="5"
maximum="5" title="Level" (blur)="heroUpdateDebounceTimer.resetTimer()"> title="Level" (blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroExp" [(ngModel)]="md2Service.playerHero.exp" minimum="0" <adj-number-input name="heroExp" [(ngModel)]="hero.exp" minimum="0" title="Exp"
title="Exp" (blur)="heroUpdateDebounceTimer.resetTimer()"> (blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroRage" [(ngModel)]="md2Service.playerHero.rage" minimum="0" <adj-number-input name="heroRage" [(ngModel)]="hero.rage" minimum="0" maximum="7"
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()" (blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.class==HeroClass.Berserker">
*ngIf="md2Service.playerHero.class==HeroClass.Berserker">
</adj-number-input> </adj-number-input>
<adj-number-input name="heroCorruption" [(ngModel)]="md2Service.playerHero.corruptionToken" <adj-number-input name="heroCorruption" [(ngModel)]="hero.corruptionToken" minimum="0"
minimum="0" title="{{imgHtml('Tokens/CorruptToken.png','g-height-18')}} Corruption" title="{{imgHtml('Tokens/CorruptToken.png','g-height-18')}} Corruption"
(blur)="heroUpdateDebounceTimer.resetTimer()" (blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.uiShowCorruptionToken">
*ngIf="md2Service.playerHero.corruptionToken>0">
</adj-number-input> </adj-number-input>
</div> </div>
</div> </div>
<div *ngIf="md2Service.info.isBossFight"></div> <div *ngIf="md2Service.info.isBossFight"></div>
<div *ngIf="md2Service.playerHero.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()"
@ -109,8 +105,14 @@
</div> </div>
<button nbButton hero fullWidth status="info" *ngIf="allowStartAction"
(click)="startActivation()">Start Activation</button>
<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>
<button nbButton hero fullWidth status="warning" class="mt-3" *ngIf="hero.uiActivating"
(click)="endActivation()">End
Activation</button>
</nb-card-body> </nb-card-body>
</nb-card> </nb-card>
@ -121,7 +123,7 @@
</div> </div>
</div> </div>
<!-- <nb-flip-card *ngIf="md2Service.playerHero"> <!-- <nb-flip-card *ngIf="hero">
<nb-card-front> <nb-card-front>
<nb-card> <nb-card>

View File

@ -4,10 +4,11 @@ import { ActivatedRoute } from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { DropDownOption } from '../../../entity/dropDownOption'; import { DropDownOption } from '../../../entity/dropDownOption';
import { GameRoomService } from '../../../services/game-room.service'; import { GameRoomService } from '../../../services/game-room.service';
import { MD2Service } from '../../../services/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 { ADButtonColor, ADButtons } from '../../../ui/alert-dlg/alert-dlg.model'; import { ADButtonColor, ADButtons } from '../../../ui/alert-dlg/alert-dlg.model';
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 } from '../massive-darkness2.model';
@ -51,6 +52,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
new DropDownOption(HeroClass.Rogue, 'Rogue'), new DropDownOption(HeroClass.Rogue, 'Rogue'),
new DropDownOption(HeroClass.Wizard, 'Wizard'), new DropDownOption(HeroClass.Wizard, 'Wizard'),
new DropDownOption(HeroClass.Shaman, 'Shaman'), new DropDownOption(HeroClass.Shaman, 'Shaman'),
new DropDownOption(HeroClass.Druid, 'Druid'),
]; ];
heros = [] as MD2HeroInfo[]; heros = [] as MD2HeroInfo[];
wizards: MD2HeroInfo[] = [ wizards: MD2HeroInfo[] = [
@ -76,7 +78,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
} }
public get allowAttack(): boolean { public get allowAttack(): boolean {
return (!!this.md2Service.mobs && this.md2Service.mobs.length > 0) || (!!this.md2Service.roamingMonsters && this.md2Service.roamingMonsters.length > 0); return this.md2Service.playerHero.uiBossFight || (!!this.md2Service.mobs && this.md2Service.mobs.length > 0) || (!!this.md2Service.roamingMonsters && this.md2Service.roamingMonsters.length > 0);
} }
ngOnInit(): void { ngOnInit(): void {
@ -130,7 +132,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
class: heroClass class: heroClass
})) }))
} }
this.heros = this.heros.sort((a, b) => StringUtils.compareSemVer(a.name, b.name)); this.heros = ArrayUtils.Shuffle(this.heros);//.sort((a, b) => StringUtils.compareSemVer(a.name, b.name));
this.showHeroList(heroClass, 0); this.showHeroList(heroClass, 0);
}); });
} }
@ -157,16 +159,16 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
} }
broadcastHeroInfo() { broadcastHeroInfo() {
this.md2Service.broadcastMyHeroInfo(); this.md2Service.broadcastService.broadcastMyHeroInfo();
this.heroUpdateDebounceTimer.clearOut(); this.heroUpdateDebounceTimer.clearOut();
} }
increaseRage() { increaseRage() {
if (this.md2Service.playerHero.rage < 7) { if (this.hero.rage < 7) {
this.md2Service.playerHero.rage++; this.hero.rage++;
} }
} }
openDoor() { openDoor() {
this.md2Service.broadcastHeroAction('openDoor'); this.md2Service.broadcastService.broadcastHeroAction('openDoor');
this.showMoveAction = false; this.showMoveAction = false;
this.detectChanges(); this.detectChanges();
} }
@ -183,17 +185,19 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
this.showMoveAction = false; this.showMoveAction = false;
switch (action) { switch (action) {
case 'recoveryAction': case 'recoveryAction':
this.msgBoxService.show('Recovery', { text: 'takes the Recover action may gain up to 2 Health or Mana in any combination (either 2 Health, 2 Mana, or 1 of each).' }) this.msgBoxService.show('Recovery', { text: 'takes the Recover action may gain up to 2 Health or Mana in any combination (either 2 Health, 2 Mana, or 1 of each).' });
break;
case 'attackAction':
this.msgBoxService.show('Attacking', { text: 'Please process attacking action in Dashboard.' });
break; break;
default: default:
break; break;
} }
this.md2Service.broadcastHeroAction(action); this.md2Service.broadcastService.broadcastHeroAction(action);
this.reduceAction(); this.reduceAction();
} }
reduceAction() { reduceAction() {
this.md2Service.playerHero.remainActions -= 1; this.hero.remainActions -= 1;
this.detectChanges(); this.detectChanges();
this.broadcastHeroInfo(); this.broadcastHeroInfo();
} }
@ -202,4 +206,30 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
toggleFlip() { toggleFlip() {
this.flip = (this.flip == 'inactive') ? 'active' : 'inactive'; this.flip = (this.flip == 'inactive') ? 'active' : 'inactive';
} }
get allowStartAction() {
return !this.md2Service.heros.some(h => h.uiActivating) && !this.hero.uiActivating && this.hero.remainActions > 0;
}
public get hero() {
return this.md2Service.playerHero;
}
startActivation() {
this.hero.uiActivating = true;
this.broadcastHeroInfo();
}
endActivation() {
if (this.hero.remainActions > 0) {
this.msgBoxService.show('Are you sure?', { text: `End Activation will lose ${this.hero.remainActions} remaining actions.`, buttons: ADButtons.YesNo }).pipe(first()).subscribe(result => {
if (result) {
this.hero.remainActions = 0;
this.endActivation();
}
});
} else {
this.hero.uiActivating = false;
this.broadcastHeroInfo();
this.detectChanges();
}
}
} }

View File

@ -24,7 +24,7 @@
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
<div class="row" *ngIf="md2Service.heros.length==0"> <div class="row" *ngIf="md2Service.heros.length==0&& false">
<div class="col-6"> <div class="col-6">
<adj-number-input name="heroLevel" [(ngModel)]="md2Service.playerAmount" [maximum]="6" <adj-number-input name="heroLevel" [(ngModel)]="md2Service.playerAmount" [maximum]="6"
@ -59,9 +59,10 @@
<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">Fire:{{hero.fireToken}}</span>
<span class="badge badge-info mr-1" *ngIf="hero.frozenToken">Frozen:{{hero.frozenToken}}</span> <span class="badge badge-info mr-1" *ngIf="hero.frozenToken">Frozen:{{hero.frozenToken}}</span>
<span class="badge badge-light mr-1" *ngIf="hero.remainActions==0">Inactive</span>
<span class="badge badge-success mr-1" *ngIf="hero.remainActions>0">Remain <span class="badge badge-success mr-1" *ngIf="hero.remainActions>0">Remain
Actions: {{hero.remainActions}}</span> Actions: {{hero.remainActions}}</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-success mr-1">{{hero.playerInfo.signalRClientId}}</span> --> <!-- <span class="badge badge-success mr-1">{{hero.playerInfo.signalRClientId}}</span> -->
</div> </div>

View File

@ -6,7 +6,7 @@ import { ArrayUtils } from '../../utilities/array-utils';
import { ObjectUtils } from '../../utilities/object-utils'; import { ObjectUtils } from '../../utilities/object-utils';
import { first, map, take, takeUntil } from 'rxjs/operators'; import { first, map, take, takeUntil } from 'rxjs/operators';
import { TreasureType, DrawingBag, DrawingItem, HeroClass, MD2HeroInfo, RoundPhase, MobInfo, MobDlgType } from './massive-darkness2.model'; import { TreasureType, DrawingBag, DrawingItem, HeroClass, MD2HeroInfo, RoundPhase, MobInfo, MobDlgType } from './massive-darkness2.model';
import { MD2Service } from '../../services/md2.service'; import { MD2Service } from '../../services/MD2/md2.service';
import { GameRoomService } from '../../services/game-room.service'; import { GameRoomService } from '../../services/game-room.service';
import { MD2Base } from './MD2Base'; import { MD2Base } from './MD2Base';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
@ -15,6 +15,7 @@ import { QRCodeService } from '../../services/qrcode.service';
import { StringUtils } from '../../utilities/string-utils'; 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';
@Component({ @Component({
selector: 'ngx-massive-darkness2', selector: 'ngx-massive-darkness2',
@ -26,6 +27,7 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
HeroClass: HeroClass HeroClass: HeroClass
constructor( constructor(
private fileService: FileService, private fileService: FileService,
private initService: MD2InitService,
private msgBoxService: MsgBoxService, private msgBoxService: MsgBoxService,
private qrCodeService: QRCodeService, private qrCodeService: QRCodeService,
public gameRoomService: GameRoomService, public gameRoomService: GameRoomService,
@ -42,7 +44,8 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
this.md2Service.enemyPhaseSubject.pipe(takeUntil(this.destroy$)).subscribe(result => { this.md2Service.enemyPhaseSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
this.showEnemyPhaseAction(0); this.showEnemyPhaseAction(0);
}); });
this.initService.initMobDecks();
this.initService.initTreasureBag();
} }
override signalRInitialized() { override signalRInitialized() {
@ -64,7 +67,7 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
} }
showEnemyPhaseAction(index: number) { showEnemyPhaseAction(index: number) {
let mob = new MobInfo(this.md2Service.enemyPhaseMobs[index]); let mob = this.md2Service.enemyPhaseMobs[index];
let enemyInfo = `<img src="${mob.imageUrl}" class='g-height-70vh'><br>`; let enemyInfo = `<img src="${mob.imageUrl}" class='g-height-70vh'><br>`;
let extraRule = ''; let extraRule = '';
@ -85,7 +88,13 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
// break; // break;
// } // }
this.msgBoxService.dlgService.open(SpawnMobDlgComponent, { context: { title: `Enemy Phase(${(index + 1)}/${this.md2Service.enemyPhaseMobs.length})`, mode: MobDlgType.Activating, mob: mob } }) this.msgBoxService.dlgService.open(SpawnMobDlgComponent, {
context: {
title: `Enemy Phase(${(index + 1)}/${this.md2Service.enemyPhaseMobs.length})`,
mode: MobDlgType.Activating,
mob: mob
}
})
.onClose.pipe(first()).subscribe(result => { .onClose.pipe(first()).subscribe(result => {
index++; index++;
if (index < this.md2Service.enemyPhaseMobs.length) { if (index < this.md2Service.enemyPhaseMobs.length) {
@ -125,10 +134,7 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
} }
enterBossFight() { enterBossFight() {
this.msgBoxService.showInputbox('Boss Fight', 'Choose the boss').pipe(first()).subscribe(result => { this.md2Service.enterBossFight();
this.md2Service.info.isBossFight = true;
this.md2Service.info.boss = new BossMicheal(this.md2Service);
this.detectChanges();
});
} }
} }

View File

@ -0,0 +1,69 @@
import { Observable, Subject } from "rxjs";
import { environment } from "../../../environments/environment";
import { MsgBoxService } from "../../services/msg-box.service";
import { ObjectUtils } from "../../utilities/object-utils";
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";
export class MD2Logic {
public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: AttackTarget, onlyOne: boolean = false) {
let beenAttackedHero = [] as MD2HeroInfo[];
switch (targetType) {
case AttackTarget.LeastHp:
let lowestHp = Math.min(...heros.map(h => h.hp));
beenAttackedHero = heros.filter(h => h.hp == lowestHp);
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
break;
case AttackTarget.LeastMp:
let lowestMp = Math.min(...heros.map(h => h.mp));
beenAttackedHero = heros.filter(h => h.hp == lowestMp);
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
break;
case AttackTarget.HighestHp:
let highestHp = Math.max(...heros.map(h => h.hp));
beenAttackedHero = heros.filter(h => h.hp == highestHp);
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
break;
case AttackTarget.HighestMp:
let highestMp = Math.max(...heros.map(h => h.mp));
beenAttackedHero = heros.filter(h => h.mp == highestMp);
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
break;
case AttackTarget.LowestLevel:
let lowestLevel = Math.max(...heros.map(h => h.level));
beenAttackedHero = heros.filter(h => h.level == lowestLevel);
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
break;
case AttackTarget.LeastCorruption:
let leastCor = Math.min(...heros.map(h => h.corruptionToken));
beenAttackedHero = heros.filter(h => h.corruptionToken == leastCor);
break;
case AttackTarget.MostCorruption:
let mostCor = Math.max(...heros.map(h => h.corruptionToken));
beenAttackedHero = heros.filter(h => h.corruptionToken == mostCor);
break;
case AttackTarget.Random:
default:
beenAttackedHero = [heros[Math.round(Math.random() * (heros.length - 1))]];
//this.otherAttackTarget = 'Just act like normal.';
break;
}
if (onlyOne && beenAttackedHero.length > 1) {
beenAttackedHero = [beenAttackedHero[Math.round(Math.random() * (beenAttackedHero.length - 1))]];
}
return beenAttackedHero;
}
public static getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) {
return `<b>${StringUtils.makeCommaSeparatedString(beenAttackedHero.map(h => this.heroFullName(h)), false, true)}</b>`;
}
public static heroFullName(hero: MD2HeroInfo) {
if (!hero) return '';
return `${hero.playerInfo.name} (${HeroClass[hero.class]} - ${hero.name})`
}
}

View File

@ -1,12 +1,37 @@
import { Subject } from "rxjs" import { stringify } from "querystring"
import { Observable, Subject, Subscription } from "rxjs"
import { first } from "rxjs/operators" import { first } from "rxjs/operators"
import { MD2Service } from "../../services/md2.service" import { MD2Service } from "../../services/MD2/md2.service"
import { StringUtils } from "../../utilities/string-utils" import { StringUtils } from "../../utilities/string-utils"
import { BossActivationComponent } from "./boss-fight/boss-activation/boss-activation.component" import { BossActivationComponent } from "./boss-fight/boss-activation/boss-activation.component"
import { TreasureType, AttackInfo, DefenseInfo, AttackType, MD2Icon, MD2HeroInfo, AttackTarget, MobInfo } from "./massive-darkness2.model" import { TreasureType, AttackInfo, DefenseInfo, AttackType, MD2Icon, MD2HeroInfo, AttackTarget, MobInfo, MobType } from "./massive-darkness2.model"
import { RollingBlackDice } from "./massive-darkness2.model.dice" import { RollingBlackDice } from "./massive-darkness2.model.dice"
export enum MobSkillType {
Attack,
Defense,
Combat
}
export class MobSkill {
constructor(config: Partial<MobSkill> = {}) {
let defaultConfig = {
type: MobSkillType.Combat,
skillRoll: 1
} as Partial<MobSkill>;
Object.assign(defaultConfig, config);
Object.assign(this, defaultConfig);
// if (StringUtils.isNullOrWhitespace(this.name)) {
// this.name=`${MobSkillType[this.type]} ${this.skillRoll} ${}`
// }
}
type: MobSkillType;
clawRoll: number;
skillRoll: number;
name: string
description: string
targetHeros: MD2HeroInfo[]
}
export interface IBossFight { export interface IBossFight {
name: string name: string
addTreasureToken: Subject<TreasureType> addTreasureToken: Subject<TreasureType>
@ -14,53 +39,109 @@ export interface IBossFight {
spawnRoamingMonster: Subject<void> spawnRoamingMonster: Subject<void>
rounds: number rounds: number
actions: number actions: number
hpPerHero: number activatedTimes: number
info: MobInfo info: MobInfo
actionBlackDice: number actionBlackDice: number
imgUrl: string imgUrl: string
standUrl: string standUrl: string
combatInfo: MobSkill extraRules: string
activating(): boolean activating(): boolean
prepareForBossFight(): void prepareForBossFight(): void
nextRound(): void darknessPhase(): void
} }
export class BossMicheal implements IBossFight {
constructor(private md2Service: MD2Service) { export abstract class BossFight implements IBossFight {
this.name = 'Michael - The Corrupted Archangel';
this.imgUrl = md2Service.imgUrl('/Boss/Michael - The Corrupted Archangel.jpg');
this.standUrl = md2Service.imgUrl('/Boss/Michael.png');
this.hpPerHero = 15;
this.info = new MobInfo({
isRoamingMonster: true,
hp: this.md2Service.heros.length * this.hpPerHero,
level: 10
});
this.info.defenseInfos = new DefenseInfo(5, 1);
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)];
this.actions = 1;
this.rounds = 0;
this.actionBlackDice = 2;
this.corruptionTokenHtml = this.md2Service.imgHtml('Tokens/CorruptToken.png');
this.combatInfo = new MobSkill(`Combat 1${this.md2Service.iconHtml(MD2Icon.EnemySkill)}`,
`Deal 1 Wound for each ${this.corruptionTokenHtml} on the attacking or defending Hero. Discard the tokens afterwards(once per combat).`);;
}
name: string name: string
addTreasureToken: Subject<TreasureType> addTreasureToken: Subject<TreasureType>
spawnMob: Subject<void> spawnMob: Subject<void>
spawnRoamingMonster: Subject<void> spawnRoamingMonster: Subject<void>
rounds: number rounds: number
actions: number actions: number
hpPerHero: number activatedTimes: number
info: MobInfo info: MobInfo
actionBlackDice: number actionBlackDice: number
imgUrl: string imgUrl: string
standUrl: string standUrl: string
combatInfo: MobSkill extraRules: string
corruptionTokenHtml: string protected subscription: Subscription
constructor(protected md2Service: MD2Service) {
this.rounds = 1;
}
activating(): boolean { activating(): boolean {
this.activatedTimes = this.actions;
this.runAction();
return true;
}
runAction() {
this.bossAction().pipe(first()).subscribe(result => {
this.activatedTimes--;
if (this.activatedTimes) {
this.runAction();
} else {
if (false == this.md2Service.heros.some(h => h.remainActions > 0)) {
this.md2Service.darknessPhase();
}
}
});
}
abstract bossAction(): Observable<boolean>;
protected actionEnd
prepareForBossFight(): void {
throw new Error("Method not implemented.")
}
darknessPhase(): void {
throw new Error("Method not implemented.")
}
}
export class BossMicheal extends BossFight {
constructor(protected md2Service: MD2Service) {
super(md2Service);
this.corruptionTokenHtml = this.md2Service.stateService.imgHtml('Tokens/CorruptToken.png');
this.name = 'Michael - The Corrupted Archangel';
this.imgUrl = md2Service.stateService.imgUrl('/Boss/Michael - The Corrupted Archangel.jpg');
this.standUrl = md2Service.stateService.imgUrl('/Boss/Michael.png');
this.info = new MobInfo({
description: this.name,
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);
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)];
this.actions = 1;
this.actionBlackDice = 2;
this.extraRules = `Archangel Michael cant be the target of any attack, skill, ability or take Wounds until there are no Corruption tokens in the whole Tile.<br><br>` +
`Any Hero on a Zone with a ${this.corruptionTokenHtml} may spend 1 action to remove it. Each time a Hero removes a ${this.corruptionTokenHtml} from a Zone they must roll 1 ${this.md2Service.stateService.iconHtml(MD2Icon.BlackDice)}.` +
`If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemyClaw)} the Hero takes 1 Wound.<br>If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)} place 1 ${this.corruptionTokenHtml} on their Dashboard.<br>` +
`If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemyClaw)}/${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)} the Hero takes 1 Wound and places 1 ${this.corruptionTokenHtml} on their Dashboard.`
}
activatedTimes: number
acted: number
name: string
addTreasureToken: Subject<TreasureType>
spawnMob: Subject<void>
spawnRoamingMonster: Subject<void>
rounds: number
actions: number
info: MobInfo
actionBlackDice: number
imgUrl: string
standUrl: string
corruptionTokenHtml: string
bossAction(): Observable<boolean> {
let actionResult = new RollingBlackDice().roll(this.actionBlackDice); let actionResult = new RollingBlackDice().roll(this.actionBlackDice);
let actionHtml = ''; let actionHtml = '';
@ -70,37 +151,50 @@ export class BossMicheal implements IBossFight {
case 0: case 0:
//Justice From Above //Justice From Above
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.MostCorruption, true); beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.MostCorruption, true);
bossAction = new MobSkill('Justice From Above', bossAction = new MobSkill(
`Place Michael in the Zone at ${this.md2Service.getTargetHerosHtml(beenAttackedHero)} and attack Him/Her.`, beenAttackedHero); {
name: 'Justice From Above',
description: `Place Michael in the Zone at ${this.md2Service.getTargetHerosHtml(beenAttackedHero)} and attack Him/Her.`
});
break; break;
case 1: case 1:
//Lance Dash //Lance Dash
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true); beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true);
bossAction = new MobSkill('Lance Dash', bossAction = new MobSkill({
`Move Michael and Place 1 ${this.corruptionTokenHtml} in the Zone at ${this.md2Service.getTargetHerosHtml(beenAttackedHero)} and attack Him/Her.`, beenAttackedHero); name: 'Lance Dash',
description:
`Move Michael and Place 1 ${this.corruptionTokenHtml} in the Zone at ${this.md2Service.getTargetHerosHtml(beenAttackedHero)} and attack Him/Her.`
});
break; break;
case 2: case 2:
//Dark Blessing //Dark Blessing
bossAction = new MobSkill('Dark Blessing', bossAction = new MobSkill({
`Place Michael ion the central Zone and add 1 ${this.corruptionTokenHtml} to the Corruption Stone Zone with the least amount of ${this.corruptionTokenHtml}.<br>` + name: 'Dark Blessing',
`Deal <b>${this.darkBlessingCorruptionAmt}</b> Wounds per ${this.corruptionTokenHtml} to all Heros in each Tiles <b>distributed as they wish</b>.`, beenAttackedHero); description:
`Place Michael on the central Zone and add 1 ${this.corruptionTokenHtml} to the Corruption Stone Zone with the least amount of ${this.corruptionTokenHtml}.<br>` +
`Deal <b>${this.darkBlessingCorruptionAmt}</b> Wounds per ${this.corruptionTokenHtml} to all Heros in each Tiles <b>distributed as they wish</b>.`
});
break; break;
default: default:
break; break;
} }
this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction } }).onClose return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
.pipe(first()).subscribe(result => {
});
return true;
} }
prepareForBossFight(): void { prepareForBossFight(): void {
this.md2Service.heros.forEach(hero => {
hero.uiShowCorruptionToken = true;
});
this.md2Service.msgBoxService.show('Prepare Boss Fight', {
text: `<h6>Place ${this.md2Service.heros.length * 2} ${this.corruptionTokenHtml} on the Corruption Stone Zones (Shadow
Zones).<br>Players choose the Zones, but must distribute
the tokens as equally as possible among them.</h6>`
});
} }
darkBlessingCorruptionAmt: number = 1; darkBlessingCorruptionAmt: number = 1;
nextRound(): void { darknessPhase(): void {
this.rounds++; this.rounds++;
switch (this.rounds) { switch (this.rounds) {
case 3: case 3:
@ -109,7 +203,7 @@ export class BossMicheal implements IBossFight {
break; break;
case 2: case 2:
case 4: case 4:
this.info.defenseInfos[0].black += 1; this.info.defenseInfo.black += 1;
this.info.attackInfos[0].black += 1; this.info.attackInfos[0].black += 1;
break; break;
// case 4: // case 4:
@ -123,13 +217,101 @@ export class BossMicheal implements IBossFight {
} }
} }
export class MobSkill {
constructor(skillName: string, skillDescription: string, targetHeros: MD2HeroInfo[] = []) { export class BossReaper extends BossFight {
this.skillName = skillName
this.skillDescription = skillDescription constructor(protected md2Service: MD2Service) {
this.targetHeros = targetHeros super(md2Service);
this.timeTokenHtml = this.md2Service.stateService.imgHtml('Tokens/TimeToken.png');
this.name = 'The Reaper';
this.imgUrl = md2Service.stateService.imgUrl('/Boss/The Reaper.jpg');
this.standUrl = md2Service.stateService.imgUrl('/Boss/The Reaper-Stand.png');
this.info = new MobInfo({
description: this.name,
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);
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 1, 2, 0, 3)];
this.actions = 1;
this.actionBlackDice = 2;
this.extraRules = `A Hero standing in the Hourglass Zone may spend 1 action to add 1 ${this.timeTokenHtml} in each Hourglass Zone.`;
}
name: string
addTreasureToken: Subject<TreasureType>
spawnMob: Subject<void>
spawnRoamingMonster: Subject<void>
rounds: number
actions: number
info: MobInfo
actionBlackDice: number
imgUrl: string
standUrl: string
timeTokenHtml: string
bossAction() {
let actionResult = new RollingBlackDice().roll(this.actionBlackDice);
let actionHtml = '';
let beenAttackedHero = [] as MD2HeroInfo[];
let bossAction: MobSkill;
switch (actionResult.claws) {
case 0:
//Justice From Above
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastMp, true);
bossAction = new MobSkill(
{
name: 'Soul Drain',
description: `Place The Reaper in the Zone at ${this.md2Service.getTargetHerosHtml(beenAttackedHero)} and attack Him/Her.`
});
break;
case 1:
//Lance Dash
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true);
bossAction = new MobSkill({
name: 'Time Ticking',
description:
`Place The Reaper in the <b>Hourglass Zone</b> withe the least ${this.timeTokenHtml} and remove 1 ${this.timeTokenHtml} from <b>The OtherHourglass Zone</b>.`
});
break;
case 2:
//Dark Blessing
bossAction = new MobSkill({
name: 'Death Is Coming',
description:
`Place The Reaper in the central Zone.<br>` +
`Roll 1 ${this.md2Service.stateService.iconHtml(MD2Icon.YellowDice)}. Remove ${this.timeTokenHtml} equal to ${this.md2Service.stateService.iconHtml(MD2Icon.Melee)} rolled from both <b>Hourglass Zone</b>.<br>` +
`Each Hero discards ${this.md2Service.stateService.iconHtml(MD2Icon.MP)} equal to ${this.md2Service.stateService.iconHtml(MD2Icon.Melee)} rolled.`
});
break;
default:
break;
}
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', {
text: `<h6>Place 2 ${this.timeTokenHtml} in each Hourglass Zone.</h6>`
})
}
darkBlessingCorruptionAmt: number = 1;
darknessPhase(): void {
this.rounds++;
if (this.rounds > 4) {
this.actions = 3;
} else if (this.rounds > 1) {
this.actions = 2;
}
} }
skillName: string
skillDescription: string
targetHeros: MD2HeroInfo[]
} }

View File

@ -1,13 +1,18 @@
import { Subject } from "rxjs"; import { Observable, Subject } from "rxjs";
import { environment } from "../../../environments/environment";
import { MsgBoxService } from "../../services/msg-box.service";
import { ObjectUtils } from "../../utilities/object-utils"; import { ObjectUtils } from "../../utilities/object-utils";
import { GamePlayer } from "../games.model"; import { GamePlayer } from "../games.model";
import { MD2Clone } from "./factorys/md2-clone";
import { MobSkill } from "./massive-darkness2.model.boss"; import { MobSkill } from "./massive-darkness2.model.boss";
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/${(id ? `${encodeURI(id)}` : '')}` }
export enum MobDlgType { export enum MobDlgType {
Spawn, Spawn,
Activating, Activating,
BeenAttacked, BeenAttacked,
PreView PreView,
Dashboard
} }
export enum RoundPhase { export enum RoundPhase {
HeroPhase, HeroPhase,
@ -29,7 +34,14 @@ export enum HeroClass {
Ranger, Ranger,
Shaman, Shaman,
Paladin, Paladin,
Druid,
} }
export enum MobType {
Mob,
RoamingMonster,
Boss
}
export enum MD2Icon { export enum MD2Icon {
Attack, Attack,
Defense, Defense,
@ -60,14 +72,16 @@ export enum MD2Icon {
RedDice, RedDice,
BlueDice, BlueDice,
YellowDice, YellowDice,
OrangeDice OrangeDice,
BlackDice
} }
export enum AttackTarget { export enum AttackTarget {
Random = 40, Random = 40,
LowestHp = 50, LeastHp = 50,
HighestHp = 60, LeastMp = 60,
HighestMp = 70, HighestHp = 70,
LowestLevel = 80, HighestMp = 80,
LowestLevel = 90,
MostCorruption = 200, MostCorruption = 200,
LeastCorruption = 201 LeastCorruption = 201
} }
@ -95,7 +109,6 @@ export class AttackInfo {
red: number red: number
yellow: number yellow: number
black: number black: number
attackSkill: MobSkill
} }
export class DefenseInfo { export class DefenseInfo {
constructor(blue: number, black: number = 0) { constructor(blue: number, black: number = 0) {
@ -104,7 +117,6 @@ export class DefenseInfo {
} }
blue: number blue: number
black: number black: number
defenseSkill: MobSkill
} }
export class MD2LevelUpReward { export class MD2LevelUpReward {
constructor(config: Partial<MD2LevelUpReward>) { constructor(config: Partial<MD2LevelUpReward>) {
@ -126,8 +138,12 @@ export class DrawingBag<T extends IDrawingItem> {
} }
drawingItems: IDrawingItem[] drawingItems: IDrawingItem[]
removedItems: IDrawingItem[] removedItems: IDrawingItem[]
public bagIsEmpty() { public bagIsEmpty(predicate: (value: T) => boolean = undefined) {
return this.drawingItems.reduce((sum, current) => sum + current.drawingWeight, 0) == 0; if (predicate) {
return this.drawingItems.filter(predicate).reduce((sum, current) => sum + current.drawingWeight, 0) == 0;
} else {
return this.drawingItems.reduce((sum, current) => sum + current.drawingWeight, 0) == 0;
}
} }
public Draw(amount: number): T[] { public Draw(amount: number): T[] {
@ -138,7 +154,7 @@ export class DrawingBag<T extends IDrawingItem> {
public DrawAndRemove(amount: number = 1, predicate: (value: T) => boolean = undefined): T[] { public DrawAndRemove(amount: number = 1, predicate: (value: T) => boolean = undefined): T[] {
let drawItems: T[] = []; let drawItems: T[] = [];
for (let i = 0; i < amount; i++) { for (let i = 0; i < amount; i++) {
if (!this.bagIsEmpty()) { if (!this.bagIsEmpty(predicate)) {
let drawItem = null as T; let drawItem = null as T;
let drawingPool = [] as T[]; let drawingPool = [] as T[];
if (predicate) { if (predicate) {
@ -153,7 +169,7 @@ export class DrawingBag<T extends IDrawingItem> {
const item = drawingPool[i]; const item = drawingPool[i];
drawCalc += item.drawingWeight; drawCalc += item.drawingWeight;
if (drawCalc >= drawIndex) { if (drawCalc >= drawIndex) {
drawItem = ObjectUtils.CloneValue(item); drawItem = MD2Clone.CloneDrawingItem(item);
drawItem.drawingWeight = 1; drawItem.drawingWeight = 1;
break; break;
} }
@ -177,7 +193,7 @@ export class DrawingBag<T extends IDrawingItem> {
this.removedItems = []; this.removedItems = [];
} }
public AddItem(item: IDrawingItem) { public AddItem(item: IDrawingItem) {
let existingItem = this.drawingItems.find(i => i.name == item.name); let existingItem = this.drawingItems.find(i => i.identifyName == item.identifyName);
if (existingItem) { if (existingItem) {
existingItem.drawingWeight += item.drawingWeight; existingItem.drawingWeight += item.drawingWeight;
} else { } else {
@ -187,17 +203,34 @@ export class DrawingBag<T extends IDrawingItem> {
public RemoveItem(item: IDrawingItem) { public RemoveItem(item: IDrawingItem) {
if (item) { if (item) {
let existingItem = this.drawingItems.find(i => i.name == item.name);
if (existingItem) {
existingItem.drawingWeight -= item.drawingWeight;
let removedItem = this.removedItems.find(i => i.name == item.name); if (item.identifyName) {
if (removedItem) { let existingItem = this.drawingItems.find(i => i.identifyName == item.identifyName);
removedItem.drawingWeight += item.drawingWeight; if (existingItem) {
} else { existingItem.drawingWeight -= item.drawingWeight;
this.removedItems.push(item);
let removedItem = this.removedItems.find(i => i.identifyName == item.identifyName);
if (removedItem) {
removedItem.drawingWeight += item.drawingWeight;
} else {
this.removedItems.push(item);
}
} }
} else {
let existingItem = this.drawingItems.find(i => i.name == item.name);
if (existingItem) {
existingItem.drawingWeight -= item.drawingWeight;
let removedItem = this.removedItems.find(i => i.name == item.name);
if (removedItem) {
removedItem.drawingWeight += item.drawingWeight;
} else {
this.removedItems.push(item);
}
}
} }
} }
} }
@ -207,10 +240,14 @@ export class DrawingBag<T extends IDrawingItem> {
this.removedItems = []; this.removedItems = [];
} }
} }
export interface IMobFactory {
mobName: string;
generate(level: number): MobInfo;
}
export interface IDrawingItem { export interface IDrawingItem {
imageUrl: string imageUrl: string
name: string name: string
get identifyName(): string
description: string description: string
drawingWeight: number drawingWeight: number
} }
@ -226,12 +263,28 @@ export class DrawingItem implements IDrawingItem {
this.description = description this.description = description
this.drawingWeight = drawingWeight this.drawingWeight = drawingWeight
} }
get identifyName(): string {
return this.name;
}
imageUrl: string imageUrl: string
name: string name: string
description: string description: string
drawingWeight: number drawingWeight: number
} }
export class TreasureItem extends DrawingItem {
constructor(type: TreasureType, itemAmount: number = 1) {
super(`${TreasureType[type]} Treasure`,
`It's a ${TreasureType[type]} Treasure!`,
MD2_IMG_URL(`TreasureToken/${TreasureType[type]}.png`), itemAmount);
this.type = type;
this.itemAmount = itemAmount;
}
type: TreasureType;
itemAmount: number;
get identifyName(): string {
return this.name;
}
}
export class MobInfo implements IDrawingItem { export class MobInfo implements IDrawingItem {
constructor( constructor(
config: Partial<MobInfo> = {} config: Partial<MobInfo> = {}
@ -241,21 +294,28 @@ export class MobInfo implements IDrawingItem {
this.drawingWeight = 1; this.drawingWeight = 1;
this.unitRemainHp = config.hp this.unitRemainHp = config.hp
} }
type: MobType = MobType.Mob;
imageUrl: string imageUrl: string
standUrl: string standUrl: string
leaderImgUrl: string
minionImgUrl: string
name: string name: string
description: string description: string
drawingWeight: number drawingWeight: number
level: number; level: number;
rewardTokens: number; rewardTokens: number;
hp: number; hp: number;
hpPerHero: number;
mobAmount: number; mobAmount: number;
carriedTreasure: DrawingItem[]; carriedTreasure: TreasureItem[];
fixedCarriedTreasure: DrawingItem[]; fixedCarriedTreasure: TreasureItem[];
unitRemainHp: number; unitRemainHp: number;
isRoamingMonster: boolean = false;
attackInfos: AttackInfo[]; attackInfos: AttackInfo[];
defenseInfos: DefenseInfo; defenseInfo: DefenseInfo;
actions: number = 0;
activateDescription: string;
combatSkill: MobSkill
fireToken: number = 0; fireToken: number = 0;
frozenToken: number = 0; frozenToken: number = 0;
corruptionToken: number = 0; corruptionToken: number = 0;
@ -265,47 +325,50 @@ export class MobInfo implements IDrawingItem {
uiCorruptionTokens: number; uiCorruptionTokens: number;
uiAttackedBy: string; uiAttackedBy: string;
get identifyName(): string {
return `${this.name}_${this.level}`;
}
public get carriedTreasureHtml(): string { public get carriedTreasureHtml(): string {
if (!this.carriedTreasure) return ''; if (!this.carriedTreasure) return '';
return this.carriedTreasure.map(i => `<img src="${i.imageUrl}" class='mr-1' width="40px">`) return this.carriedTreasure.map(i => `<img src="${i.imageUrl}" class='mr-1' width="40px">`)
.concat(this.fixedCarriedTreasure?.map(i => `<img src="${i.imageUrl}" class='mr-1' width="40px">`)).join(); .concat(this.fixedCarriedTreasure?.map(i => `<img src="${i.imageUrl}" class='mr-1' width="40px">`.repeat(i.drawingWeight))).join();
} }
public get totalHp(): number { public get totalHp(): number {
return this.isRoamingMonster ? this.unitRemainHp : (this.mobAmount - 1) * this.hp + this.unitRemainHp; switch (this.type) {
case MobType.Mob:
return (this.mobAmount - 1) * this.hp + this.unitRemainHp;
case MobType.RoamingMonster:
return this.unitRemainHp;
case MobType.Boss:
default:
return this.unitRemainHp;
}
} }
public get minionAmount(): number { public get minionAmount(): number {
return (this.mobAmount - 1); switch (this.type) {
case MobType.Mob:
return (this.mobAmount - 1);
case MobType.RoamingMonster:
case MobType.Boss:
default:
return 0;
}
} }
public get leaderExp(): number { public get leaderExp(): number {
return this.isRoamingMonster ? 4 : 2; switch (this.type) {
} case MobType.Mob:
return 2;
public get mobInfoHtml(): string { case MobType.RoamingMonster:
let html = `<img src="${this.imageUrl}" class="g-height-50vh">` return 4;
+ `<br>Target Unit HP:${this.unitRemainHp}`; case MobType.Boss:
default:
if (this.isRoamingMonster) { return 0;
html += `<br><label class="label">Alive Units:${this.mobAmount}`;
} else {
html += `<br>Total HP:${this.totalHp}`;
} }
return html;
}
public getCssClass(): string {
let levelString = '';
if (this.level < 3) {
levelString = '-lv1-2';
} else if (this.level < 5) {
levelString = '-lv3-4';
} else {
levelString = '-lv5';
}
return `${this.name.replace(' ', '')}${levelString}`;
} }
public actionSubject: Subject<string>;
public activateFunction?: (mob: MobInfo, msgBoxService: MsgBoxService, heros: MD2HeroInfo[]) => void;
} }
export class MD2HeroInfo { export class MD2HeroInfo {
@ -332,10 +395,12 @@ export class MD2HeroInfo {
shadowSkillHtml: string; shadowSkillHtml: string;
remainActions: number = 3; remainActions: number = 3;
rage: number = 0; rage: number = 0;
uiActivating = false;
uiShowCorruptionToken = false;
uiBossFight = 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})`
} }
} }
@ -344,7 +409,7 @@ export class MD2Rules {
public static CoreGameLevelBoard = [ public static CoreGameLevelBoard = [
new MD2LevelUpReward({ level: 2, needExp: 5, extraHp: 1, extraMp: 0, extraRareToken: 1 }), new MD2LevelUpReward({ level: 2, needExp: 5, extraHp: 1, extraMp: 0, extraRareToken: 1 }),
new MD2LevelUpReward({ level: 3, needExp: 10, extraHp: 1, extraMp: 1, extraEpicToken: 1 }), new MD2LevelUpReward({ level: 3, needExp: 10, extraHp: 1, extraMp: 1, extraEpicToken: 1 }),
new MD2LevelUpReward({ level: 4, needExp: 12, extraHp: 2, extraMp: 2, extraEpicToken: 1 }), new MD2LevelUpReward({ level: 4, needExp: 12, extraHp: 2, extraMp: 1, extraEpicToken: 1 }),
new MD2LevelUpReward({ level: 5, needExp: 18, extraHp: 2, extraMp: 2, extraEpicToken: 1 }), new MD2LevelUpReward({ level: 5, needExp: 18, extraHp: 2, extraMp: 2, extraEpicToken: 1 }),
]; ];
public static checkCoreGameLevelup(currentLevel: number, currentExp: number): MD2LevelUpReward { public static checkCoreGameLevelup(currentLevel: number, currentExp: number): MD2LevelUpReward {
@ -381,6 +446,9 @@ export class MD2EnemyPhaseSpecialRule implements IDrawingItem {
this.title = title this.title = title
this.description = description this.description = description
} }
get identifyName(): string {
return this.name;
}
imageUrl: string; imageUrl: string;
name: string; name: string;
drawingWeight: number; drawingWeight: number;

View File

@ -3,7 +3,7 @@ import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors, NG_
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { DropDownOption } from '../../../entity/dropDownOption'; import { DropDownOption } from '../../../entity/dropDownOption';
import { MD2Service } from '../../../services/md2.service'; import { MD2Service } from '../../../services/MD2/md2.service';
import { ArrayUtils } from '../../../utilities/array-utils'; import { ArrayUtils } from '../../../utilities/array-utils';
import { HeroClass, MD2HeroInfo } from '../massive-darkness2.model'; import { HeroClass, MD2HeroInfo } from '../massive-darkness2.model';

View File

@ -1 +1 @@
<span class="MD2Icon {{icon}} {{iconClass}}"></span> <span class="MD2Icon {{iconName}} {{iconClass}} {{sizeClass}}"></span>

View File

@ -8,14 +8,49 @@ import { MD2Icon } from '../massive-darkness2.model';
}) })
export class MD2IconComponent implements OnInit { export class MD2IconComponent implements OnInit {
@Input() iconClass: string = ''; @Input() iconClass: string = 'mr-1';
@Input("icon") icon: MD2Icon;
private _icon: string | MD2Icon;
@Input() public set icon(v: string | MD2Icon) {
if (this._icon != v) {
this._icon = v;
}
if (this.isMD2Icon(v)) {
this.iconName = MD2Icon[v].toLowerCase();
} else {
this.iconName = v;
}
}
isMD2Icon(icon: MD2Icon | string): icon is MD2Icon {
return Number.isInteger(icon);
}
@Input() size: string = 'sm';
iconName: string;
constructor() { } constructor() { }
ngOnInit(): void { ngOnInit(): void {
} }
public get sizeClass(): string {
switch (this.size) {
case 'sm':
return 'g-font-size-18'
break;
case 'med':
return 'g-font-size-30'
break;
case 'lg':
return 'g-font-size-50'
break;
default:
return 'g-font-size-' + this.size;
break;
}
}
} }

View File

@ -1,23 +1,30 @@
<label class='label'>Weapon Info</label> <!-- <label class='label'>Weapon Info</label> -->
<div class="g-brd-3 g-brd-bottom--dashed g-brd-gray-light-v2 mb-3 mt-2 row" *ngFor="let info of mob.attackInfos"> <ng-container *ngIf="display">
<div class="col-md-4"> <div class="g-brd-3 g-brd-bottom--dashed g-brd-gray-light-v2 mb-3 mt-4 row" *ngFor="let info of mob.attackInfos">
<span class=" g-font-size-50" [innerHtml]="iconHtml(info.type)"></span> <div class="col-md-4">
</div> <md2-icon [icon]="info.type" size="lg"></md2-icon>
<div class="col-md-8">
<div *ngIf="info.yellow" class="g-height-45">
<span class="MD2Icon Yellow dice g-font-size-50">
<span class="MD2text diceAmount">x{{info.yellow}}</span>
</span>
</div> </div>
<div *ngIf="info.orange" class="g-height-45 mt-1"> <div class="col-md-8">
<span class="MD2Icon Orange dice g-font-size-50"> <div *ngIf="info.yellow" class="g-height-45">
<span class="MD2text diceAmount">x{{info.orange}}</span> <span class="MD2Icon Yellow dice g-font-size-50">
</span> <span class="MD2text diceAmount">x{{info.yellow}}</span>
</div> </span>
<div *ngIf="info.red" class="g-height-45 mt-1"> </div>
<span class="MD2Icon Red dice g-font-size-50"> <div *ngIf="info.orange" class="g-height-45 mt-1">
<span class="MD2text diceAmount">x{{info.red}}</span> <span class="MD2Icon Orange dice g-font-size-50">
</span> <span class="MD2text diceAmount">x{{info.orange}}</span>
</span>
</div>
<div *ngIf="info.red" class="g-height-45 mt-1">
<span class="MD2Icon Red dice g-font-size-50">
<span class="MD2text diceAmount">x{{info.red}}</span>
</span>
</div>
<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>
</div>
</div> </div>
</div> </div>
</div> </ng-container>

View File

@ -0,0 +1,19 @@
.diceAmount {
color: white;
z-index: 2;
position: absolute;
left: 25px;
font-size: 30px;
}
.dice {
&::before {
position: absolute;
}
}
.blackDiceAmount {
color: white;
z-index: 2;
position: absolute;
left: 26px;
font-size: 30px;
}

View File

@ -1,8 +1,8 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { MD2Icon, MobInfo } from '../../../massive-darkness2.model'; import { MD2Icon, MobDlgType, MobInfo, MobType } from '../../../massive-darkness2.model';
@Component({ @Component({
selector: 'ngx-mob-attack-info', selector: 'md2-mob-attack-info',
templateUrl: './mob-attack-info.component.html', templateUrl: './mob-attack-info.component.html',
styleUrls: ['./mob-attack-info.component.scss'] styleUrls: ['./mob-attack-info.component.scss']
}) })
@ -20,10 +20,25 @@ export class MobAttackInfoComponent implements OnInit {
} }
} }
@Input() mode: MobDlgType = MobDlgType.PreView;
display: boolean = false;
constructor() { } constructor() { }
ngOnInit(): void { ngOnInit(): void {
switch (this.mode) {
case MobDlgType.Spawn:
this.display = this.mob.type != MobType.Mob;
break;
case MobDlgType.Activating:
case MobDlgType.PreView:
case MobDlgType.Dashboard:
this.display = true;
break;
default:
this.display = false;
break;
}
} }
} }

View File

@ -1 +1,24 @@
<p>mob-combat-info works!</p> <!-- <div class='form-group row' *ngIf="showSkill">
<label class='label col-sm-3 form-control-label MD2text g-font-size-30'>
{{mob.combatSkill.skillRoll}} <md2-icon icon="enemySkill" size="md"></md2-icon>
</label>
<div class='col-sm' [innerHtml]="mob.combatSkill.description"></div>
</div> -->
<div *ngIf="showBlackDice" class="row">
<!-- <md2-icon></md2-icon> -->
<div class="col-md-4">
<md2-icon icon="enemySkill" size="lg"></md2-icon>
</div>
<div class="col-md-8">
<span class="MD2Icon Black dice g-font-size-50">
<span class="MD2text blackDiceAmount">x{{mob.minionAmount}}</span>
</span>
</div>
</div>
<div class='form-group' *ngIf="showSkill">
<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 class='g-font-size-20 skillDesc MD2text' [innerHtml]="mob.combatSkill.description"></div>
</div>

View File

@ -0,0 +1,28 @@
.diceAmount {
color: white;
z-index: 2;
position: absolute;
left: 25px;
font-size: 30px;
}
.dice {
&::before {
position: absolute;
}
}
.blackDiceAmount {
color: white;
z-index: 2;
position: absolute;
left: 26px;
font-size: 30px;
}
.skillDesc {
padding-left: 8px;
.MD2Icon {
font-size: 45px;
}
}
.skillDesc .MD2Icon {
font-size: 45px;
}

View File

@ -1,15 +1,53 @@
import { Component, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { MD2Icon, MobDlgType, MobInfo, MobType } from '../../../massive-darkness2.model';
import { MobSkillType } from '../../../massive-darkness2.model.boss';
@Component({ @Component({
selector: 'ngx-mob-combat-info', selector: 'md2-mob-combat-info',
templateUrl: './mob-combat-info.component.html', templateUrl: './mob-combat-info.component.html',
styleUrls: ['./mob-combat-info.component.scss'] styleUrls: ['./mob-combat-info.component.scss']
}) })
export class MobCombatInfoComponent implements OnInit { export class MobCombatInfoComponent implements OnInit {
MD2Icon = MD2Icon;
private _mob: MobInfo;
public get mob(): MobInfo {
return this._mob;
}
@Input() public set mob(v: MobInfo) {
if (this._mob != v) {
this._mob = v;
}
}
@Input() mode: MobDlgType = MobDlgType.PreView;
showSkill: boolean = false;
showBlackDice: boolean
skillTriggerHtml: string = '';
constructor() { } constructor() { }
ngOnInit(): void { ngOnInit(): void {
if (this.mob.combatSkill) {
switch (this.mode) {
case MobDlgType.Activating:
this.showSkill = [MobSkillType.Combat, MobSkillType.Attack].includes(this.mob.combatSkill.type);
break;
case MobDlgType.BeenAttacked:
this.showSkill = [MobSkillType.Combat, MobSkillType.Defense].includes(this.mob.combatSkill.type);
break;
case MobDlgType.PreView:
this.showSkill = true;
break;
case MobDlgType.Spawn:
default:
this.showSkill = 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;;
} }
} }

View File

@ -1 +1,20 @@
<p>mob-def-info works!</p> <!-- <label class='label'>Defense Info</label> -->
<ng-container *ngIf="display">
<div class="g-brd-3 g-brd-bottom--dashed g-brd-gray-light-v2 mb-3 mt-2 row">
<div class="col-md-4">
<md2-icon icon="defense" size="lg"></md2-icon>
</div>
<div class="col-md-8">
<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">
<span class="MD2Icon Black dice g-font-size-50">
<span class="MD2text diceAmount">x{{mob.defenseInfo.black}}</span>
</span>
</div>
</div>
</div>
</ng-container>

View File

@ -0,0 +1,19 @@
.diceAmount {
color: white;
z-index: 2;
position: absolute;
left: 25px;
font-size: 30px;
}
.dice {
&::before {
position: absolute;
}
}
.blackDiceAmount {
color: white;
z-index: 2;
position: absolute;
left: 26px;
font-size: 30px;
}

View File

@ -1,15 +1,43 @@
import { Component, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { MD2Icon, MobInfo, MobDlgType } from '../../../massive-darkness2.model';
import { MobSkillType } from '../../../massive-darkness2.model.boss';
@Component({ @Component({
selector: 'ngx-mob-def-info', selector: 'md2-mob-def-info',
templateUrl: './mob-def-info.component.html', templateUrl: './mob-def-info.component.html',
styleUrls: ['./mob-def-info.component.scss'] styleUrls: ['./mob-def-info.component.scss']
}) })
export class MobDefInfoComponent implements OnInit { export class MobDefInfoComponent implements OnInit {
constructor() { } MD2Icon = MD2Icon;
private _mob: MobInfo;
public get mob(): MobInfo {
return this._mob;
}
@Input() public set mob(v: MobInfo) {
if (this._mob != v) {
this._mob = v;
}
}
@Input() mode: MobDlgType = MobDlgType.PreView;
display: boolean = false;
constructor() { }
ngOnInit(): void { ngOnInit(): void {
switch (this.mode) {
case MobDlgType.BeenAttacked:
case MobDlgType.PreView:
case MobDlgType.Spawn:
case MobDlgType.Dashboard:
this.display = true;
break;
default:
this.display = false;
break;
}
} }
} }

View File

@ -1,48 +1,53 @@
<label class='label'>Level <b class="MD2text g-font-size-18">{{mob.level}}</b></label><br> <div class="row no-gutters">
<div class="col-md-6">
<ng-container *ngIf="mob.mobAmount"> <label class='label g-text-nowrap'>Level <b class="MD2text g-font-size-18">{{mob.level}}</b></label><br>
<label class='label'>Alive Units <b class="MD2text g-font-size-18">{{mob.mobAmount}}</b></label><br>
<label class='label g-text-nowrap'>Total HP <b class="MD2text g-font-size-18">{{mob.totalHp}}</b></label>
</div>
<div class="pl-2 col-md-6" *ngIf="mob.mobAmount">
<ng-container>
<label class='label g-text-nowrap'>Alive Units <b
class="MD2text g-font-size-18">{{mob.mobAmount}}</b></label><br>
</ng-container>
</div>
</div>
<div class="row">
<div class="col-md-6" *ngIf="showTokenAdjustment||mob.fireToken>0">
<adj-number-input name="mobFire" [(ngModel)]="mob.fireToken" minimum="0"
title="{{iconHtml(MD2Icon.Fire,'g-color-google-plus mr-1 g-font-size-18')}}Fire Token">
</adj-number-input>
</div>
<div class="col-md-6" *ngIf="showTokenAdjustment||mob.frozenToken>0">
<adj-number-input name="mobFrost" [(ngModel)]="mob.frozenToken" minimum="0"
title="{{iconHtml(MD2Icon.Frost,'g-color-aqua mr-1 g-font-size-18')}}Frozen Token">
</adj-number-input>
</div>
</div>
<ng-container *ngIf="showAdjustment">
<adj-number-input name="mob{{mob.name}}" [ngModel]="mob.unitRemainHp" [maximum]="mob.hp" minimum="1"
title="Target Unit HP" (hitChange)="adjustMobHp($event)" (hitMinimum)="adjustMobHp(-1)"
(hitMaximum)="adjustMobHp(1)">
</adj-number-input>
</ng-container> </ng-container>
<label class='label'>Total HP <b class="MD2text g-font-size-18">{{mob.totalHp}}</b></label><br>
<ng-container *ngIf="mob.carriedTreasureHtml"> <ng-container *ngIf="mob.carriedTreasureHtml">
<label class='label'>Carried Treasure</label><br> <label class='label'>Carried Treasure</label><br>
<div [innerHtml]="mob.carriedTreasureHtml"></div> <div [innerHtml]="mob.carriedTreasureHtml"></div>
</ng-container> </ng-container>
<div class="g-height-20 mb-1"></div>
<ng-container *ngIf="mob.attackInfos&&mob.attackInfos.length>0&& !hideWeaponInfo"> <md2-mob-attack-info [mob]="mob" [mode]="mode">
</md2-mob-attack-info>
</ng-container> <md2-mob-def-info [mob]="mob" [mode]="mode"></md2-mob-def-info>
<md2-mob-combat-info [mob]="mob" [mode]="mode"></md2-mob-combat-info>
<ng-container *ngIf="mob.defenseInfos&& !hideWeaponInfo">
<label class='label'>Defense Info</label>
<div class="g-brd-3 g-brd-bottom--dashed g-brd-gray-light-v2 mb-3 mt-2 row">
<div class="col-md-4">
<span class="g-font-size-50" [innerHtml]="iconHtml(MD2Icon.Defense)"></span>
</div>
<div class="col-md-8">
<div *ngIf="mob.defenseInfos.blue" class="g-height-45">
<span class="MD2Icon Blue dice g-font-size-50">
<span class="MD2text diceAmount">x{{mob.defenseInfos.blue}}</span>
</span>
</div>
<div *ngIf="mob.defenseInfos.black" class="g-height-45 mt-1">
<span class="MD2Icon Black dice g-font-size-50">
<span class="MD2text diceAmount">x{{mob.defenseInfos.black}}</span>
</span>
</div>
</div>
</div>
</ng-container>
<div *ngIf="showBlackDice" class="row">
<!-- <md2-icon></md2-icon> -->
<div class="col-md-4">
<span class=" g-font-size-50" [innerHtml]="iconHtml(MD2Icon.EnemySkill)"></span>
</div>
<div class="col-md-8">
<span class="MD2Icon Black dice g-font-size-50">
<span class="MD2text blackDiceAmount">x{{mob.minionAmount}}</span>
</span>
</div>
</div>

View File

@ -1,6 +1,7 @@
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { MD2Service } from '../../../../services/md2.service'; import { MD2MobService } from '../../../../services/MD2/md2-mob.service';
import { MD2Service } from '../../../../services/MD2/md2.service';
import { StateService } from '../../../../services/state.service'; import { StateService } from '../../../../services/state.service';
import { MD2Icon, MobDlgType, MobInfo } from '../../massive-darkness2.model'; import { MD2Icon, MobDlgType, MobInfo } from '../../massive-darkness2.model';
import { MD2ComponentBase } from '../../MD2Base'; import { MD2ComponentBase } from '../../MD2Base';
@ -39,11 +40,9 @@ export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
this.showAttackingInfo = typeof value !== "undefined" && value !== false; this.showAttackingInfo = typeof value !== "undefined" && value !== false;
} }
public get showBlackDice(): boolean {
return (this.mode == MobDlgType.Activating || this.mode == MobDlgType.BeenAttacked) && this.mob.minionAmount > 0;
}
constructor( constructor(
private mobService: MD2MobService,
public md2Service: MD2Service, public md2Service: MD2Service,
protected stateService: StateService, protected stateService: StateService,
protected route: ActivatedRoute, protected route: ActivatedRoute,
@ -64,4 +63,21 @@ export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
} }
} }
public get showAdjustment(): boolean {
return [MobDlgType.Activating, MobDlgType.BeenAttacked, MobDlgType.Dashboard].includes(this.mode);
}
public get showTokenAdjustment(): boolean {
return [MobDlgType.Activating, MobDlgType.BeenAttacked].includes(this.mode);
}
adjustMobHp(adjustAmount: number) {
if (adjustAmount < 0) {
this.mobService.attackMob(this.mob, 1);
} else {
this.mobService.healMob(this.mob, 1);
}
this.md2Service.refreshUI$.next();
}
} }

View File

@ -0,0 +1,7 @@
<img class="g-width-95x img-thumbnail mobBg" src="{{imgUrl('/Mobs/BG.png')}}" />
<img class="mobImg roamingMonster" src="{{getMobImageUrl(mob)}}" (click)="showMobImage(mob)" *ngIf="!isMob" />
<div *ngIf="isMob">
<img class="mobImg mobLeader" src="{{mob.leaderImgUrl}}" (click)="showMobImage(mob)" />
<img class="mobImg mobMinion" src="{{mob.minionImgUrl}}" (click)="showMobImage(mob)" />
</div>

View File

@ -0,0 +1,23 @@
.mobImg {
position: absolute;
z-index: 2;
&.roamingMonster {
width: 95%;
max-height: 80%;
}
&.mobLeader {
z-index: 3;
width: 70%;
max-height: 80%;
top: 40px;
}
&.mobMinion {
width: 60%;
max-height: 80%;
right: 0;
}
}
.mobBg {
position: absolute;
z-index: 1;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MobStandInfoComponent } from './mob-stand-info.component';
describe('MobStandInfoComponent', () => {
let component: MobStandInfoComponent;
let fixture: ComponentFixture<MobStandInfoComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MobStandInfoComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MobStandInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,69 @@
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { first } from 'rxjs/operators';
import { MD2Service } from '../../../../services/MD2/md2.service';
import { StateService } from '../../../../services/state.service';
import { StringUtils } from '../../../../utilities/string-utils';
import { MD2Icon, MobInfo, MobDlgType, MobType } from '../../massive-darkness2.model';
import { MD2ComponentBase } from '../../MD2Base';
import { SpawnMobDlgComponent } from '../spawn-mob-dlg/spawn-mob-dlg.component';
@Component({
selector: 'md2-mob-stand-info',
templateUrl: './mob-stand-info.component.html',
styleUrls: ['./mob-stand-info.component.scss']
})
export class MobStandInfoComponent extends MD2ComponentBase implements OnInit {
MD2Icon = MD2Icon;
private _mob: MobInfo;
public get mob(): MobInfo {
return this._mob;
}
@Input() public set mob(v: MobInfo) {
if (this._mob != v) {
this._mob = v;
}
}
@Input() mode: MobDlgType = MobDlgType.PreView;
public get hideWeaponInfo(): boolean {
return this.mode == MobDlgType.Spawn;
}
constructor(
public md2Service: MD2Service,
protected stateService: StateService,
protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef,
) {
super(md2Service, stateService, route, cdRef);
}
ngOnInit(): void {
}
public getMobImageUrl(mob: MobInfo): string {
if (StringUtils.isNullOrWhitespace(mob.leaderImgUrl)) {
return mob.imageUrl;
} else {
return mob.leaderImgUrl;
}
}
showMobImage(mob: MobInfo) {
this.md2Service.dlgService.open(SpawnMobDlgComponent, { context: { title: `${mob.description}`, mode: MobDlgType.PreView, mob: mob } })
.onClose.pipe(first()).subscribe(result => {
});
}
public get isMob(): boolean {
return this.mob.type == MobType.Mob;
}
}

View File

@ -28,21 +28,16 @@
<div class="col col-sm-6 col-md-6 col-lg-4 mb-4" *ngFor="let mob of this.mobs"> <div class="col col-sm-6 col-md-6 col-lg-4 mb-4" *ngFor="let mob of this.mobs">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-12 col-md-8"> <div class="col-12 col-md-8">
<md2-mob-stand-info [mob]="mob"></md2-mob-stand-info>
<img class="mobImg g-width-95x" src="{{mob.imageUrl}}" (click)="showMobImage(mob)" />
</div> </div>
<div class=" col-12 col-md-4"> <div class=" col-12 col-md-4">
<md2-mob-detail-info name="mobDetail{{mob.name}}" [mob]="mob" [mode]="MobDlgType.PreView"> <md2-mob-detail-info name="mobDetail{{mob.name}}" [mob]="mob" [mode]="MobDlgType.Dashboard">
</md2-mob-detail-info> </md2-mob-detail-info>
<adj-number-input name="mob{{mob.name}}" [(ngModel)]="mob.unitRemainHp"
[maximum]="isRoamingMonster?null: mob.hp" minimum="1" title="Target Unit HP"
(hitMinimum)="killOneUnit(mob)" (hitMaximum)="addOneUnit(mob)">
</adj-number-input>
<button nbButton hero status="danger" size="small" (click)="attackMob(mob)" <button nbButton hero status="danger" size="small" (click)="attackMob(mob)"
*ngIf="md2Service.showAttackBtn">Attack It</button> *ngIf="md2Service.info.showAttackBtn">Attack It</button>
<!-- <div class='form-group'> <!-- <div class='form-group'>
<label for='playerAmount' class='label'>Current Unit HP</label> <label for='playerAmount' class='label'>Current Unit HP</label>
<div> <div>

View File

@ -18,83 +18,6 @@ $mob-card-height: 556px;
/* right: auto; */ /* right: auto; */
z-index: 99; z-index: 99;
} }
.mobImg {
object-fit: cover;
&.Gargoyles-lv1-2 {
object-position: 0 0;
}
&.Demons-lv1-2 {
object-position: calc(#{$mob-card-width} * -1) 0;
}
&.Undead-lv1-2 {
object-position: calc(#{$mob-card-width} * -2) 0;
}
&.FireEntities-lv1-2 {
object-position: calc(#{$mob-card-width} * -3) 0;
}
&.FallenAngels-lv1-2 {
object-position: calc(#{$mob-card-width} * -4) 0;
}
&.InfernalImps-lv1-2 {
object-position: calc(#{$mob-card-width} * -5) 0;
}
&.Skeletons-lv1-2 {
object-position: calc(#{$mob-card-width} * -6) 0;
}
&.Satyrs-lv1-2 {
object-position: calc(#{$mob-card-width} * -7) 0;
}
&.Gargoyles-lv3-4 {
object-position: 0 calc(#{$mob-card-height} * -1);
}
&.FireEntities-lv3-4 {
object-position: calc(#{$mob-card-width} * -1) calc(#{$mob-card-height} * -1);
}
&.Skeletons-lv3-4 {
object-position: calc(#{$mob-card-width} * -2) calc(#{$mob-card-height} * -1);
}
&.InfernalImps-lv3-4 {
object-position: calc(#{$mob-card-width} * -3) calc(#{$mob-card-height} * -1);
}
&.Undead-lv3-4 {
object-position: calc(#{$mob-card-width} * -4) calc(#{$mob-card-height} * -1);
}
&.Demons-lv3-4 {
object-position: calc(#{$mob-card-width} * -5) calc(#{$mob-card-height} * -1);
}
&.Satyrs-lv3-4 {
object-position: calc(#{$mob-card-width} * -6) calc(#{$mob-card-height} * -1);
}
&.FallenAngels-lv3-4 {
object-position: calc(#{$mob-card-width} * -7) calc(#{$mob-card-height} * -1);
}
&.Satyrs-lv5 {
object-position: 0 calc(#{$mob-card-height} * -2);
}
&.FallenAngels-lv5 {
object-position: calc(#{$mob-card-width} * -1) calc(#{$mob-card-height} * -2);
}
&.Gargoyles-lv5 {
object-position: calc(#{$mob-card-width} * -2) calc(#{$mob-card-height} * -2);
}
&.FireEntities-lv5 {
object-position: calc(#{$mob-card-width} * -3) calc(#{$mob-card-height} * -2);
}
&.InfernalImps-lv5 {
object-position: calc(#{$mob-card-width} * -4) calc(#{$mob-card-height} * -2);
}
&.Undead-lv5 {
object-position: calc(#{$mob-card-width} * -5) calc(#{$mob-card-height} * -2);
}
&.Skeletons-lv5 {
object-position: calc(#{$mob-card-width} * -6) calc(#{$mob-card-height} * -2);
}
&.Demons-lv5 {
object-position: calc(#{$mob-card-width} * -7) calc(#{$mob-card-height} * -2);
}
}
} }
#clip { #clip {
position: absolute; position: absolute;

View File

@ -1,13 +1,16 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { NbDialogService } from '@nebular/theme'; import { NbDialogService } from '@nebular/theme';
import { stringify } from 'querystring';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { FileService } from '../../../services/file.service'; import { FileService } from '../../../services/file.service';
import { MD2Service } from '../../../services/md2.service'; import { MD2MobService } from '../../../services/MD2/md2-mob.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 { NumberUtils } from '../../../utilities/number-utils'; import { NumberUtils } from '../../../utilities/number-utils';
import { StringUtils } from '../../../utilities/string-utils';
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';
@ -34,6 +37,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
private fileService: FileService, private fileService: FileService,
private msgBoxService: MsgBoxService, private msgBoxService: MsgBoxService,
private dlgService: NbDialogService, private dlgService: NbDialogService,
private mobService: MD2MobService,
public md2Service: MD2Service, public md2Service: MD2Service,
protected stateService: StateService, protected stateService: StateService,
protected route: ActivatedRoute, protected route: ActivatedRoute,
@ -44,6 +48,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.initMobDecks(); this.initMobDecks();
super.ngOnInit();
} }
@ -61,7 +66,6 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
initMobDecks() { initMobDecks() {
this.md2Service.initMobDecks();
this.mobs = []; this.mobs = [];
this.cdRef.detectChanges(); this.cdRef.detectChanges();
let spawn$ = this.isRoamingMonster ? this.md2Service.darknessPhaseRule.spawnRoamingMonster : this.md2Service.darknessPhaseRule.spawnMob; let spawn$ = this.isRoamingMonster ? this.md2Service.darknessPhaseRule.spawnRoamingMonster : this.md2Service.darknessPhaseRule.spawnMob;
@ -71,25 +75,20 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
}); });
} }
spawnMob() { spawnMob() {
let result = this.md2Service.drawMob(this.isRoamingMonster); let result = this.md2Service.spawnMob(this.isRoamingMonster);
if (result.exitingMob) { let titleText = result.exitingMob == null ? `${result.mob.description} Shows Up` : `${result.mob.description} Activate One Action Now!`;
this.dlgService.open(SpawnMobDlgComponent, { context: { title: `${result.mob.description} Activate One Action Now!`, mode: MobDlgType.Activating, mob: result.mob } }) let actType = result.exitingMob == null ? MobDlgType.Spawn : MobDlgType.Activating;
.onClose.pipe(first()).subscribe(result => { let mob = result.exitingMob == null ? result.mob : result.exitingMob;
this.afterSpawn()
}); this.dlgService.open(SpawnMobDlgComponent, { context: { title: titleText, mode: actType, mob: mob } })
} else { .onClose.pipe(first()).subscribe(result => {
this.md2Service.refreshTreasureBagSubject.next(this.md2Service.treasureBag); this.afterSpawn();
//this.msgBoxService.show(`${newSpawnMob.description} Shows Up`, { text: actionText }); });
this.dlgService.open(SpawnMobDlgComponent, { context: { title: `${result.mob.description} Shows Up`, mode: MobDlgType.Spawn, mob: result.mob } })
.onClose.pipe(first()).subscribe(result => {
this.afterSpawn();
});
}
this.cdRef.detectChanges(); this.cdRef.detectChanges();
} }
afterSpawn() { afterSpawn() {
this.cdRef.detectChanges(); this.cdRef.detectChanges();
this.md2Service.broadcastMobsInfo(); this.md2Service.broadcastService.broadcastMobsInfo();
if (this.showRoundMessage) { if (this.showRoundMessage) {
this.msgBoxService.show(`${NumberUtils.Ordinal(this.md2Service.info.round)} Hero Phase`, { icon: ADIcon.INFO }); this.msgBoxService.show(`${NumberUtils.Ordinal(this.md2Service.info.round)} Hero Phase`, { icon: ADIcon.INFO });
} }
@ -109,14 +108,21 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
this.cdRef.detectChanges(); this.cdRef.detectChanges();
} }
killOneUnit(mob: MobInfo) { killOneUnit(mob: MobInfo) {
let attacker = this.md2Service.currentActivateHero;
if (mob.mobAmount > 1) { if (mob.mobAmount > 1) {
mob.mobAmount--; mob.mobAmount--;
mob.unitRemainHp = mob.hp; mob.unitRemainHp = mob.hp;
if (this.attacking) { if (this.attacking) {
this.attackingAttackerExp += 1; this.attackingAttackerExp += 1;
} else { } else {
this.msgBoxService.show('The Attacker Gain 1 Exp', { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
}); if (attacker) {
attacker.exp += 1;
this.msgBoxService.show(`${attacker.heroFullName} Gain 1 Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker);
});
}
} }
} else { } else {
@ -128,7 +134,11 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
this.attackingAttackerExp += 1; this.attackingAttackerExp += 1;
this.attackingAttackerReward = `<br> and gains ${mob.carriedTreasureHtml}`; this.attackingAttackerReward = `<br> and gains ${mob.carriedTreasureHtml}`;
} else { } else {
this.msgBoxService.show(`All Hero Gain ${mob.leaderExp} Exp`, { text: `The Attacker Gain 1 Extra Exp.<br> and gains ${mob.carriedTreasureHtml}`, icon: ADIcon.INFO }).pipe(first()).subscribe(result => { let messageText = '';
if (attacker) {
messageText = `<b>${attacker.heroFullName}</b> Gain 1 Extra Exp.<br> and gains ${mob.carriedTreasureHtml}`;
}
this.msgBoxService.show(`All Hero Gain ${mob.leaderExp} Exp`, { text: messageText, icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
}); });
} }
@ -154,49 +164,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
.onClose.pipe(first()).subscribe(mobResult => { .onClose.pipe(first()).subscribe(mobResult => {
if (mobResult) { if (mobResult) {
let attackDamage = mobResult.uiWounds; let attackDamage = mobResult.uiWounds;
if (attackDamage) { this.mobService.attackMob(mobResult, attackDamage);
do {
if (attackDamage >= mob.unitRemainHp) {
attackDamage -= mob.unitRemainHp;
this.killOneUnit(mob);
} else {
let originAttackDamage = attackDamage;
attackDamage -= mob.unitRemainHp;
mob.unitRemainHp -= originAttackDamage;
}
} while (attackDamage > 0 && mob.mobAmount > 0);
this.attacking = false;
let attacker = this.md2Service.info.currentActivateHero;
let attackerTitle = 'The Attacker';
if (attacker) {
attackerTitle = this.md2Service.heroFullName(attacker);
}
if (this.attackingAllExp > 0) {
this.msgBoxService.show(`All Hero Gains ${this.attackingAllExp} Exp`,
{ text: `${attackerTitle} Gains ${this.attackingAttackerExp} Extra Exp.<br> and gains ${mob.carriedTreasureHtml}`, icon: ADIcon.INFO })
.pipe(first()).subscribe(result => {
for (let i = 0; i < this.md2Service.heros.length; i++) {
const hero = this.md2Service.heros[i];
hero.exp += this.attackingAllExp;
}
attacker.exp += this.attackingAttackerExp;
this.md2Service.broadcastAllHeroInfoToAll();
});
} else if (this.attackingAttackerExp > 0) {
this.msgBoxService.show(`${attackerTitle} Gains ${this.attackingAttackerExp} Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
attacker.exp += this.attackingAttackerExp;
this.md2Service.broadcastAllHeroInfoToAll();
});
}
this.cdRef.detectChanges();
}
this.cdRef.detectChanges();
this.md2Service.broadcastMobsInfo();
} }
}); });
@ -209,12 +177,21 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
} }
public getMobImageUrl(mob: MobInfo): string {
if (StringUtils.isNullOrWhitespace(mob.leaderImgUrl)) {
return mob.imageUrl;
} else {
return mob.leaderImgUrl;
}
}
showMobImage(mob: MobInfo) { showMobImage(mob: MobInfo) {
this.dlgService.open(SpawnMobDlgComponent, { context: { title: `${mob.description}`, mode: MobDlgType.PreView, mob: mob } }) this.dlgService.open(SpawnMobDlgComponent, { context: { title: `${mob.description}`, mode: MobDlgType.PreView, mob: mob } })
.onClose.pipe(first()).subscribe(result => { .onClose.pipe(first()).subscribe(result => {
}); });
//this.msgBoxService.show('', { text: mob.mobInfoHtml.replace('g-height-50vh', 'g-height-70vh') });
} }
weaponHtml(mob: MobInfo) { weaponHtml(mob: MobInfo) {
let html = '<br>'; let html = '<br>';

View File

@ -1,18 +1,26 @@
<nb-card status="{{headerStatus}}" class="mobCard" size="giant"> <nb-card status="{{headerStatus}}" class="mobCard" size="giant">
<nb-card-header> <nb-card-header>
<img src="{{imgUrl('Mobs/MobToken.png')}}" width="40px"> {{(mob.isRoamingMonster?'Roaming Monster':'Mob')}} <img src="{{imgUrl('Mobs/MobToken.png')}}" width="40px"> {{cardTitle}}
<span [innerHtml]="titleHtml"></span> <span [innerHtml]="titleHtml"></span>
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col-md-7"> <div class="col-md-7 g-height-90vh">
<img src="{{mob.imageUrl}}" class="g-width-90x"> <!-- <img src="{{mob.imageUrl}}" class="g-width-90x"> -->
<md2-mob-stand-info [mob]="mob" [mode]="mode"></md2-mob-stand-info>
</div> </div>
<div class="col-md-5"> <div class="col-md-5">
<md2-mob-detail-info [mob]="mob" [mode]="mode"> <md2-mob-detail-info [mob]="mob" [mode]="mode">
</md2-mob-detail-info> </md2-mob-detail-info>
<ng-container *ngIf="mode==MobDlgType.Spawn&&!mob.isRoamingMonster"> <div *ngIf="actionInfoHtml">
<div class="alert alert-warning" role="alert" [innerHtml]="actionInfoHtml">
</div>
</div>
<ng-container *ngIf="mode==MobDlgType.Spawn&&mob.type==MobType.Mob">
<div class="row form-group mt-2"> <div class="row form-group mt-2">
<div class="col-md-2"> <div class="col-md-2">
<span class="MD2Icon melee g-font-size-40"></span> <span class="MD2Icon melee g-font-size-40"></span>
@ -71,7 +79,7 @@
<label for='damages' class='label'>Cause How Many Damages?</label> <label for='damages' class='label'>Cause How Many Damages?</label>
<input type='number' nbInput fullWidth id='damages' name='damages' [(ngModel)]='mob.uiWounds'> <input type='number' nbInput fullWidth id='damages' name='damages' [(ngModel)]='mob.uiWounds'>
</div> </div>
<div class='form-group'> <!-- <div class='form-group'>
<label for='damages' class='label'>Cause How Many <span <label for='damages' class='label'>Cause How Many <span
[innerHtml]="iconHtml(MD2Icon.Fire,'g-color-google-plus mr-1 g-font-size-18')"></span>?</label> [innerHtml]="iconHtml(MD2Icon.Fire,'g-color-google-plus mr-1 g-font-size-18')"></span>?</label>
<input type='number' nbInput fullWidth id='uiFireTokens' name='uiFireTokens' <input type='number' nbInput fullWidth id='uiFireTokens' name='uiFireTokens'
@ -82,7 +90,7 @@
[innerHtml]="iconHtml(MD2Icon.Frost,'g-color-aqua mr-1 g-font-size-18')"></span>?</label> [innerHtml]="iconHtml(MD2Icon.Frost,'g-color-aqua mr-1 g-font-size-18')"></span>?</label>
<input type='number' nbInput fullWidth id='uiFrozenTokens' name='uiFrozenTokens' <input type='number' nbInput fullWidth id='uiFrozenTokens' name='uiFrozenTokens'
[(ngModel)]='mob.uiFrozenTokens'> [(ngModel)]='mob.uiFrozenTokens'>
</div> </div> -->
</ng-container> </ng-container>
</div> </div>

View File

@ -1,12 +1,13 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { NbDialogRef } from '@nebular/theme'; import { NbDialogRef } from '@nebular/theme';
import { first } from 'rxjs/operators';
import { FileService } from '../../../../services/file.service'; import { FileService } from '../../../../services/file.service';
import { MD2Service } from '../../../../services/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 { StringUtils } from '../../../../utilities/string-utils'; import { StringUtils } from '../../../../utilities/string-utils';
import { AttackInfo, AttackTarget, AttackType, MD2HeroInfo, MD2Icon, MobDlgType, MobInfo } from '../../massive-darkness2.model'; import { AttackInfo, AttackTarget, AttackType, MD2HeroInfo, MD2Icon, MobDlgType, MobInfo, MobType } from '../../massive-darkness2.model';
import { MD2ComponentBase } from '../../MD2Base'; import { MD2ComponentBase } from '../../MD2Base';
@Component({ @Component({
@ -16,13 +17,14 @@ import { MD2ComponentBase } from '../../MD2Base';
}) })
export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit { export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
MobDlgType = MobDlgType; MobDlgType = MobDlgType;
MobType = MobType;
mode: MobDlgType; mode: MobDlgType;
cardTitle: string;
title: string; title: string;
titleHtml: string; titleHtml: string;
MD2Icon = MD2Icon; MD2Icon = MD2Icon;
mob: MobInfo; mob: MobInfo;
actionInfoHtml: string;
beenAttackedHero = [] as MD2HeroInfo[]; beenAttackedHero = [] as MD2HeroInfo[];
attackTarget: string; attackTarget: string;
otherAttackTarget: string; otherAttackTarget: string;
@ -38,28 +40,15 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
//this.mob = new MobInfo(this.mob); //this.mob = new MobInfo(this.mob);
if (this.mode == MobDlgType.Spawn) { if (this.mode == MobDlgType.Spawn && this.mob.type == MobType.Mob) {
this.mob.attackInfos = [ this.mob.attackInfos = [new AttackInfo(MD2Icon.Melee), new AttackInfo(MD2Icon.Range), new AttackInfo(MD2Icon.Magic)];
{ } else if (this.mode == MobDlgType.Activating) {
type: MD2Icon.Melee, if (this.mob.actionSubject) {
red: 0, this.mob.actionSubject.pipe(first()).subscribe(result => {
yellow: 0, this.actionInfoHtml = result;
orange: 0 });
} as AttackInfo, this.mob.activateFunction(this.mob, this.md2Service.msgBoxService, this.md2Service.info.heros);
{ }
type: MD2Icon.Range,
red: 0,
yellow: 0,
orange: 0
} as AttackInfo,
{
type: MD2Icon.Magic,
red: 0,
yellow: 0,
orange: 0
} as AttackInfo
]
} }
this.mob.uiWounds = 0; this.mob.uiWounds = 0;
this.mob.uiFrozenTokens = 0; this.mob.uiFrozenTokens = 0;
@ -71,28 +60,29 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
this.mob.attackInfos = this.mob.attackInfos.filter(a => a.yellow > 0 || a.orange > 0 || a.red > 0); this.mob.attackInfos = this.mob.attackInfos.filter(a => a.yellow > 0 || a.orange > 0 || a.red > 0);
} }
// switch (this.mode) { switch (this.mode) {
// case MobDlgType.Spawn: case MobDlgType.Spawn:
// return 'warning'; this.mob.attackInfos = this.mob.attackInfos.filter(a => a.yellow > 0 || a.orange > 0 || a.red > 0);
// break; break;
// case MobDlgType.Activating: case MobDlgType.Activating:
// return 'danger'; break;
// break; case MobDlgType.BeenAttacked:
// case MobDlgType.BeenAttacked: break;
// return 'info'; case MobDlgType.PreView:
// break; default:
// case MobDlgType.PreView: break;
// default: }
// return '';
// break;
// }
this.md2Service.info.showAttackBtn = false; this.md2Service.info.showAttackBtn = false;
this.md2Service.refreshUI$.next();
this.dlgRef.close(this.mob); this.dlgRef.close(this.mob);
} }
cancel() { cancel() {
this.dlgRef.close(); this.dlgRef.close();
} }
initTitleHtml() { initTitleHtml() {
this.cardTitle = MobType[this.mob.type];
let htmlText = ''; let htmlText = '';
if (this.mode == MobDlgType.Spawn) { if (this.mode == MobDlgType.Spawn) {
htmlText = `${this.mob.description} Shows Up`; htmlText = `${this.mob.description} Shows Up`;
@ -113,7 +103,7 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
} }
} }
switch (targetType) { switch (targetType) {
case AttackTarget.LowestHp: case AttackTarget.LeastHp:
let lowestHp = Math.min(...this.md2Service.heros.map(h => h.hp)); let lowestHp = Math.min(...this.md2Service.heros.map(h => h.hp));
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == lowestHp); this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == lowestHp);
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.'; //this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';

View File

@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs-compat'; import { Subject } from 'rxjs-compat';
import { first, takeUntil } from 'rxjs/operators'; import { first, takeUntil } from 'rxjs/operators';
import { FileService } from '../../../services/file.service'; import { FileService } from '../../../services/file.service';
import { MD2Service } from '../../../services/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 { ADButtons, ADIcon } from '../../../ui/alert-dlg/alert-dlg.model'; import { ADButtons, ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
@ -47,7 +47,7 @@ export class TreasureBagComponent extends MD2ComponentBase implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.md2Service.initTreasureBag(); //this.md2Service.initTreasureBag();
this.detectChanges(); this.detectChanges();
} }
detectChanges() { detectChanges() {
@ -59,7 +59,7 @@ export class TreasureBagComponent extends MD2ComponentBase implements OnInit {
resetTreasureBag(showConfirmMsg = true) { resetTreasureBag(showConfirmMsg = true) {
this.msgBoxService.show("Restore Treasure Bag", { text: 'Are you sure?', icon: ADIcon.QUESTION, buttons: ADButtons.YesNo }).pipe(first()).subscribe(result => { this.msgBoxService.show("Restore Treasure Bag", { text: 'Are you sure?', icon: ADIcon.QUESTION, buttons: ADButtons.YesNo }).pipe(first()).subscribe(result => {
if (result) { if (result) {
this.md2Service.initTreasureBag(); //this.md2Service.initTreasureBag();
} }
}); });

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { MD2BroadcastService } from './md2-broadcast.service';
describe('MD2BroadcastService', () => {
let service: MD2BroadcastService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(MD2BroadcastService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,109 @@
import { Injectable } from '@angular/core';
import { NbDialogService } from '@nebular/theme';
import { first } from 'rxjs/operators';
import { MD2HeroInfo } from '../../games/massive-darkness2/massive-darkness2.model';
import { FileService } from '../file.service';
import { GameRoomService } from '../game-room.service';
import { LoginUserService } from '../login-user.service';
import { SignalRService, SignalRSession, SignalRMessage } from '../signal-r.service';
import { MD2StateService } from './md2-state.service';
@Injectable({
providedIn: 'root'
})
export class MD2BroadcastService {
constructor(
public fileService: FileService,
private gameRoomService: GameRoomService,
private loginUserService: LoginUserService,
public stateService: MD2StateService,
public signalRService: SignalRService,
public dlgService: NbDialogService
) { }
public get playerHero(): MD2HeroInfo {
return this.stateService.playerHero;
}
public broadcastAllHeroInfoToAll() {
this.stateService.info.heros.forEach(element => {
this.broadcastHeroInfoToOwner(element);
});
}
public broadcastHeroAction(action: string, extraValue: any = null) {
let parameters = {};
parameters['tabId'] = this.loginUserService.sessionTabId;
parameters['extraValue'] = JSON.stringify(extraValue);
this.broadcastMessage('heroAction', action, parameters);
}
public broadcastHeroInfoToAll(hero: MD2HeroInfo) {
let message = {
receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
from: { isGroup: false, sessionId: hero.playerInfo.signalRClientId },
actionType: 'hero',
actionName: 'update',
} as SignalRMessage;
message.parameters = { hero: JSON.stringify(hero) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public broadcastHeroInfoToOwner(hero: MD2HeroInfo) {
let message = {
receiver: { isGroup: false, sessionId: hero.playerInfo.signalRClientId } as SignalRSession,
from: { isGroup: true, sessionId: this.gameRoomService.gameRoomId },
actionType: 'hero',
actionName: 'updateMyHero',
} as SignalRMessage;
message.parameters = { hero: JSON.stringify(hero) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
/**
* `sessionId` = null means broadcast to all
*/
public broadcastMessage(actionType: string,
actionName: string,
parameters: { [key: string]: string; } = {},
sessionId: string = null) {
let message = {
receiver: { isGroup: !sessionId, sessionId: sessionId } as SignalRSession,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
actionType: actionType,
actionName: actionName,
parameters: parameters
} as SignalRMessage;
if (sessionId == null) {
message.receiver = { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession
} else {
message.receiver = { isGroup: false, sessionId: this.gameRoomService.gameRoomId } as SignalRSession
}
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public broadcastGameInfo() {
let parameters = {};
parameters['gameInfo'] = JSON.stringify(this.stateService.info);
this.broadcastMessage('GameRoom', 'update', parameters);
}
public broadcastRoundPhase() {
let parameters = {};
parameters['phase'] = JSON.stringify(this.stateService.info.roundPhase);
this.broadcastMessage('GameRoom', 'phase', parameters);
}
public broadcastMobsInfo() {
let parameters = {};
parameters['roamingMonsters'] = JSON.stringify(this.stateService.info.roamingMonsters);
parameters['mobs'] = JSON.stringify(this.stateService.info.mobs);
this.broadcastMessage('mobs', 'update', parameters);
}
public broadcastMyHeroInfo() {
this.broadcastHeroInfoToAll(this.playerHero);
}
}

View File

@ -0,0 +1,51 @@
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';
@Injectable({
providedIn: 'root'
})
export class MD2InitService {
constructor(
private stateService: MD2StateService
) { }
public initMobDecks() {
this.stateService.mobDeck = new DrawingBag();
this.stateService.roamingMobDeck = new DrawingBag();
this.initCoreGameMobs();
this.initCoreGameRoamingMonsters();
}
private initCoreGameRoamingMonsters() {
CoreGameRMFactories.forEach(factory => {
for (let i = 1; i <= 5; i++) {
this.stateService.roamingMobDeck.AddItem(new MobInfo({ name: factory.mobName, level: i, drawingWeight: 1 }));
i++;
}
});
}
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++;
}
});
}
public initTreasureBag() {
this.stateService.treasureBag.ClearAllItems();
this.addTreasure(TreasureType.Common, 15);
this.addTreasure(TreasureType.Rare, 5);
}
public addTreasure(type: TreasureType, amount: number = 1) {
this.stateService.treasureBag.AddItem(new TreasureItem(type, amount));
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { MD2MobService } from './md2-mob.service';
describe('MD2MobService', () => {
let service: MD2MobService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(MD2MobService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,140 @@
import { Injectable } from '@angular/core';
import { first } from 'rxjs/operators';
import { MobInfo, MobType } from '../../games/massive-darkness2/massive-darkness2.model';
import { ADIcon } from '../../ui/alert-dlg/alert-dlg.model';
import { MsgBoxService } from '../msg-box.service';
import { MD2StateService } from './md2-state.service';
import { MD2Service } from './md2.service';
@Injectable({
providedIn: 'root'
})
export class MD2MobService {
attackingAllExp: number = 0;
attackingAttackerExp: number = 0;
attackingAttackerReward: string = '';
constructor(
private md2Service: MD2Service,
private stateService: MD2StateService,
private msgBoxService: MsgBoxService
) { }
public get roamingMonsters() {
return this.stateService.info.roamingMonsters;
}
public get mobs() {
return this.stateService.info.mobs;
}
addOneUnit(mob: MobInfo) {
if (mob.type == MobType.Mob) {
mob.mobAmount++;
mob.unitRemainHp = 1;
this.md2Service.refreshUI$.next();
}
}
private killOneUnit(mob: MobInfo) {
let attacker = this.md2Service.currentActivateHero;
if (mob.mobAmount > 1) {
mob.mobAmount--;
mob.unitRemainHp = mob.hp;
this.attackingAttackerExp += 1;
} else {
if (mob.type == MobType.Mob) {
this.mobs.splice(this.mobs.indexOf(mob), 1);
} else {
this.roamingMonsters.splice(this.roamingMonsters.indexOf(mob), 1);
}
mob.mobAmount = 0;
this.attackingAllExp += mob.leaderExp;
this.attackingAttackerExp += 1;
this.attackingAttackerReward = `<br> and gains ${mob.carriedTreasureHtml}`;
this.md2Service.mobBeenKilledSubject.next(mob);
}
this.md2Service.refreshUI$.next();
}
healMob(mob: MobInfo, healAmount: number) {
switch (mob.type) {
case MobType.Mob:
mob.unitRemainHp += healAmount;
if (mob.unitRemainHp > mob.hp) {
mob.unitRemainHp -= mob.hp;
mob.mobAmount += 1;
}
break;
case MobType.RoamingMonster:
case MobType.Boss:
mob.unitRemainHp += healAmount;
if (mob.unitRemainHp > mob.hp) {
mob.unitRemainHp = mob.hp;
}
break;
default: break;
}
this.md2Service.refreshUI$.next();
}
attackMob(mob: MobInfo, attackDamage: number) {
this.attackingAttackerReward = '';
if (attackDamage) {
do {
if (attackDamage >= mob.unitRemainHp) {
attackDamage -= mob.unitRemainHp;
this.killOneUnit(mob);
} else {
let originAttackDamage = attackDamage;
attackDamage -= mob.unitRemainHp;
mob.unitRemainHp -= originAttackDamage;
}
} while (attackDamage > 0 && mob.mobAmount > 0);
let attacker = this.md2Service.currentActivateHero;
let attackerTitle = '';
if (attacker) {
attackerTitle = attacker.heroFullName;
}
if (this.attackingAllExp > 0) {
let attackerRewardText = '';
if (attacker) {
attackerRewardText = `<b>${attackerTitle}</b> Gains ${this.attackingAttackerExp} Extra Exp.<br> and gains ${mob.carriedTreasureHtml}`;
}
this.msgBoxService.show(`All Hero Gains ${this.attackingAllExp} Exp`,
{ text: attackerRewardText, icon: ADIcon.INFO })
.pipe(first()).subscribe(result => {
for (let i = 0; i < this.md2Service.heros.length; i++) {
const hero = this.md2Service.heros[i];
hero.exp += this.attackingAllExp;
}
if (attacker) {
attacker.exp += this.attackingAttackerExp;
}
this.attackingAllExp = 0;
this.attackingAttackerExp = 0;
this.md2Service.broadcastService.broadcastAllHeroInfoToAll();
});
} else if (this.attackingAttackerExp > 0 && attacker) {
this.msgBoxService.show(`<b>${attackerTitle}</b> Gains ${this.attackingAttackerExp} Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
attacker.exp += this.attackingAttackerExp;
this.attackingAttackerExp = 0;
this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker);
});
}
this.md2Service.refreshUI$.next();
}
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { MD2StateService } from './md2-state.service';
describe('MD2StateService', () => {
let service: MD2StateService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(MD2StateService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,42 @@
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';
@Injectable({
providedIn: 'root'
})
export class MD2StateService {
private _highestPlayerLevel: number = 1;
private _playerAmount: number = 2;
public info: MD2GameInfo;
public playerHero: MD2HeroInfo;
public mobDeck: DrawingBag<MobInfo>;
public roamingMobDeck: DrawingBag<MobInfo>;
public treasureBag: DrawingBag<TreasureItem> = new DrawingBag<TreasureItem>();
constructor(
public fileService: FileService,
) { }
public iconHtml(icon: MD2Icon, cssClass = '') {
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>`;
}
}
public imgHtml(imgPath: string, cssClass = 'g-height-25 mr-1') {
return `<img src='${this.imgUrl(imgPath)}' class='${cssClass}'>`;
}
public imgUrl(imgPath: string) {
return this.fileService.ImageUrl('MD2/' + imgPath);
}
public treasureImage(type: TreasureType) {
return this.imgUrl(`TreasureToken/${TreasureType[type]}.png`);
}
}

View File

@ -0,0 +1,393 @@
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 { first, map, reduce } from "rxjs/operators";
import { NbDialogService } from '@nebular/theme';
import { Subject } from 'rxjs';
import { BossMicheal, BossReaper, IBossFight } 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';
import { FileService } from '../file.service';
import { GameRoomService } from '../game-room.service';
import { LoginUserService } from '../login-user.service';
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';
@Injectable({
providedIn: 'root'
})
export class MD2Service {
// #region Properties (24)
public darknessPhaseRule: IDarknessPhaseRule;
public enemyPhaseMobs: MobInfo[];
public enemyPhaseSubject = new Subject<MobInfo>();
public initialized = false;
public mobBeenKilledSubject = new Subject<MobInfo>();
public refreshUI$: Subject<void> = new Subject<void>();
public refreshTreasureBagSubject = new Subject<DrawingBag<TreasureItem>>();
public heroAttackingSubject = new Subject<MD2HeroInfo>();
public get heros() {
return this.stateService.info.heros;
}
public get roamingMonsters() {
return this.stateService.info.roamingMonsters;
}
public get mobs() {
return this.stateService.info.mobs;
}
public get info(): MD2GameInfo {
return this.stateService.info;
}
public set info(v: MD2GameInfo) {
this.stateService.info = v;
}
// #endregion Properties (24)
// #region Constructors (1)
constructor(
public fileService: FileService,
public msgBoxService: MsgBoxService,
private gameRoomService: GameRoomService,
private loginUserService: LoginUserService,
public stateService: MD2StateService,
public signalRService: SignalRService,
public dlgService: NbDialogService,
public broadcastService: MD2BroadcastService
) {
this.darknessPhaseRule = new CoreGameDarknessPhaseRule();
this.stateService.info = new MD2GameInfo();
this.darknessPhaseRule.addTreasureToken.subscribe(treasureType => {
this.addTreasure(treasureType, 1);
});
}
// #endregion Constructors (1)
// #region Public Getters And Setters (5)
public get highestPlayerLevel(): number {
if (this.heros.length > 0) {
return Math.max(...this.heros.map(h => h.level));
} else {
return 1;
}
}
public get playerAmount(): number {
if (this.heros.length > 0) {
return this.heros.length;
} else {
return 1;
}
}
public get playerHero(): MD2HeroInfo {
return this.stateService.playerHero;
}
public get currentActivateHero(): MD2HeroInfo {
return this.stateService.info.heros.find(h => h.uiActivating);
}
// #endregion Public Getters And Setters (5)
// #region Public Methods (27)
public get treasureBag() {
return this.stateService.treasureBag
}
public addTreasure(type: TreasureType, amount: number = 1) {
this.treasureBag.AddItem(new TreasureItem(type, amount));
this.refreshTreasureBagSubject.next(this.treasureBag);
}
public darknessPhase() {
this.heros.forEach(hero => {
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.stateService.info.roundPhase = RoundPhase.HeroPhase;
if (!this.stateService.info.isBossFight) {
this.stateService.info.round++;
if (this.darknessPhaseRule.runDarknessPhase()) {
this.msgBoxService.show(`${NumberUtils.Ordinal(this.stateService.info.round)} Hero Phase`, { icon: ADIcon.INFO });
}
} else {
this.stateService.info.boss.darknessPhase();
this.msgBoxService.show(`Boss Fight - ${NumberUtils.Ordinal(this.stateService.info.boss.rounds)} Hero Phase`, { icon: ADIcon.INFO });
}
//this.runNextPhase();
}
public spawnMob(isRoamingMonster: boolean) {
let mobDeck = null as DrawingBag<MobInfo>;
let level = 1;
if (this.highestPlayerLevel < 3) {
} else if (this.highestPlayerLevel < 5) {
level = 3;
} else {
level = 5;
}
if (isRoamingMonster) {
mobDeck = this.stateService.roamingMobDeck;
} else {
mobDeck = this.stateService.mobDeck;
}
if (mobDeck.drawingItems.filter(m => (m as MobInfo).level == level)
.map(d => d.drawingWeight).reduce((a, b) => a + b, 0) == 0) {
mobDeck.RestoreRemoveItems();
}
let 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);
if (exitingMob) {
exitingMob.level = newSpawnMob.level;
exitingMob.hp = newSpawnMob.hp;
exitingMob.imageUrl = newSpawnMob.imageUrl;
exitingMob.attackInfos = newSpawnMob.attackInfos;
exitingMob.defenseInfo = newSpawnMob.defenseInfo;
exitingMob.combatSkill = newSpawnMob.combatSkill;
} else {
if (isRoamingMonster) {
newSpawnMob = CoreGameRMFactories.find(f => f.mobName == newSpawnMob.name).generate(level);
newSpawnMob.hp = newSpawnMob.hpPerHero * this.playerAmount;
newSpawnMob.unitRemainHp = newSpawnMob.hp;
newSpawnMob.mobAmount = 0;
this.roamingMonsters.push(newSpawnMob);
} else {
newSpawnMob = CoreGameMobFactories.find(f => f.mobName == newSpawnMob.name).generate(level);
newSpawnMob.mobAmount = this.playerAmount + 1;
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 };
}
public enemyPhase() {
//this.msgBoxService
this.enemyPhaseMobs = this.roamingMonsters.concat(this.mobs);
if (this.enemyPhaseMobs.length > 0) {
this.enemyPhaseMobs = ArrayUtils.Shuffle(this.enemyPhaseMobs);
//this.showEnemyPhaseAction();
this.enemyPhaseSubject.next(this.enemyPhaseMobs[0]);
} else {
this.runNextPhase();
}
}
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')]
}).pipe(first()).subscribe(result => {
if (result) {
this.info.mobs = [];
this.info.roamingMonsters = [];
if (result == 'The Reaper') {
this.info.boss = new BossReaper(this);
} else {
this.info.boss = new BossMicheal(this);
}
this.stateService.info.roamingMonsters = [];
this.stateService.info.mobs = [];
this.stateService.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.levelUpPhase(false);
this.heros.forEach(hero => {
hero.hp = hero.hpMaximum;
hero.mp = hero.mpMaximum;
hero.remainActions = 3;
hero.uiActivating = false;
hero.uiBossFight = true;
});
this.broadcastService.broadcastAllHeroInfoToAll();
}
});
//this.sendMsgboxMsg
}
public activateBoss() {
this.stateService.info.boss.activating();
}
public fileList(folderPath: string) {
return this.fileService.FileList('Images/MD2/' + folderPath);
}
public heroFullName(hero: MD2HeroInfo) {
return MD2Logic.heroFullName(hero);
}
public levelUpPhase(runNextPhase: boolean = true) {
for (let i = 0; i < this.heros.length; i++) {
const hero = this.heros[i];
let levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp);
while (levelUpInfo != null) {
hero.level = levelUpInfo.level;
hero.hp += levelUpInfo.extraHp;
hero.hpMaximum += levelUpInfo.extraHp;
hero.mp += levelUpInfo.extraMp;
hero.mpMaximum += levelUpInfo.extraMp;
hero.exp = levelUpInfo.currentExp;
this.broadcastService.broadcastHeroInfoToOwner(hero);
this.sendMsgboxMsg(hero.playerInfo.signalRClientId, { title: 'Level Up', text: 'Please do a skill level up!', icon: ADIcon.INFO });
levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp);
}
}
if (runNextPhase) {
this.runNextPhase();
}
}
public mobImage(name: string, level: number, isRoamingMonsters: boolean) {
if (level < 3) {
level = 1;
} else if (level < 5) {
level = 2;
} else {
level = 3;
}
if (isRoamingMonsters) {
return this.stateService.imgUrl(`Mobs/CoreGame/RoamingMonsters/${name}/${level}.png`);
} else {
return this.stateService.imgUrl(`Mobs/CoreGame/Mobs/${name}/${level}.png`);
}
}
public playerJoin(hero: MD2HeroInfo) {
hero.playerInfo = this.gameRoomService.currentPlayer();
hero.level = 1;
hero.hp = hero.hpMaximum;
hero.mp = hero.mpMaximum;
hero.exp = 0;
this.stateService.playerHero = hero;
let message = {
receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
actionType: 'hero',
actionName: 'join',
} as SignalRMessage;
message.parameters = { hero: JSON.stringify(hero) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public runNextPhase() {
this.stateService.info.roundPhase++;
switch (this.stateService.info.roundPhase) {
case RoundPhase.HeroPhase:
this.heros.forEach(hero => {
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);
});
break;
case RoundPhase.EnemyPhase:
this.enemyPhase();
break;
case RoundPhase.LevelUpPhase:
this.levelUpPhase();
break;
case RoundPhase.DarknessPhase:
this.darknessPhase();
break;
default: break;
}
let parameters = {};
parameters['phase'] = this.stateService.info.roundPhase;
this.broadcastService.broadcastMessage('roundPhase', '', parameters);
}
public sendMsgboxMsg(playerSessionId: string, msg: Partial<MessageBoxConfig>) {
let message = {
receiver: { isGroup: false, sessionId: playerSessionId } as SignalRSession,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
actionType: 'message',
actionName: 'popup',
} as SignalRMessage;
message.parameters = { msg: JSON.stringify(msg) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public getTargetHerosByFilter(targetType: AttackTarget, onlyOne: boolean = false) {
return MD2Logic.getTargetHerosByFilter(this.stateService.info.heros, targetType, onlyOne);
}
public getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) {
return MD2Logic.getTargetHerosHtml(beenAttackedHero);
}
// #endregion Public Methods (27)
}
export class MD2GameInfo {
/**
*
*/
constructor(
config: Partial<MD2GameInfo> = undefined
) {
if (config) {
Object.assign(this, config);
this.mobs = this.mobs.map(m => new MobInfo(m));
this.roamingMonsters = this.roamingMonsters.map(m => new MobInfo(m));
if (this.boss.info) {
this.boss.info = new MobInfo(this.boss.info);
}
this.heros = this.heros.map(h => new MD2HeroInfo(h));
}
}
public isBossFight: boolean = false;
public mobs: MobInfo[] = [];
public roamingMonsters: MobInfo[] = [];
public heros: MD2HeroInfo[] = [];
public round = 1;
public roundPhase: RoundPhase = RoundPhase.HeroPhase;
public showAttackBtn: boolean = false;
public boss: IBossFight;
}

View File

@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
import { MD2StateService } from "./md2-state.service";
@Injectable({
providedIn: 'root'
})
export class MD2SpawnMobService {
/**
*
*/
constructor(
public stateService: MD2StateService,) {
}
}

View File

@ -1,589 +0,0 @@
import { Injectable } from '@angular/core';
import { AttackInfo, AttackTarget, CoreGameDarknessPhaseRule, DefenseInfo, DrawingBag, DrawingItem, HeroClass, IDarknessPhaseRule, MD2HeroInfo, MD2Icon, MD2Rules, MobInfo, RoundPhase, TreasureType } from '../games/massive-darkness2/massive-darkness2.model';
import { first, map, reduce } from "rxjs/operators";
import { Subject } from 'rxjs';
import { FileService } from './file.service';
import { StringUtils } from '../utilities/string-utils';
import { MsgBoxService } from './msg-box.service';
import { ADButtons, ADIcon, MessageBoxConfig } from '../ui/alert-dlg/alert-dlg.model';
import { GameRoomService } from './game-room.service';
import { SignalRMessage, SignalRService, SignalRSession } from './signal-r.service';
import { LoginUserService } from './login-user.service';
import { NumberUtils } from '../utilities/number-utils';
import { SpawnMobDlgComponent } from '../games/massive-darkness2/mobs/spawn-mob-dlg/spawn-mob-dlg.component';
import { stringify } from 'querystring';
import { NbDialogService } from '@nebular/theme';
import { IBossFight } from '../games/massive-darkness2/massive-darkness2.model.boss';
@Injectable({
providedIn: 'root'
})
export class MD2Service {
// #region Properties (24)
private _highestPlayerLevel: number = 1;
private _playerAmount: number = 2;
public info: MD2GameInfo;
public darknessPhaseRule: IDarknessPhaseRule;
public enemyPhaseMobs: MobInfo[];
public enemyPhaseSubject = new Subject<MobInfo>();
public initialized = false;
public mobBeenKilledSubject = new Subject<MobInfo>();
public mobDeck: DrawingBag<MobInfo>;
public roamingMobDeck: DrawingBag<MobInfo>;
public treasureBag: DrawingBag<DrawingItem> = new DrawingBag<DrawingItem>();
public refreshUI$: Subject<void> = new Subject<void>();
public refreshTreasureBagSubject = new Subject<DrawingBag<DrawingItem>>();
public get heros() {
return this.info.heros;
}
public get roamingMonsters() {
return this.info.roamingMonsters;
}
public get mobs() {
return this.info.mobs;
}
// #endregion Properties (24)
// #region Constructors (1)
constructor(
public fileService: FileService,
public msgBoxService: MsgBoxService,
private gameRoomService: GameRoomService,
private loginUserService: LoginUserService,
public signalRService: SignalRService,
public dlgService: NbDialogService
) {
this.darknessPhaseRule = new CoreGameDarknessPhaseRule();
this.info = new MD2GameInfo();
this.darknessPhaseRule.addTreasureToken.subscribe(treasureType => {
this.addTreasure(treasureType, 1);
});
}
// #endregion Constructors (1)
private initCoreGameRoamingMonsters() {
let mobs = [];
this.roamingMobDeck.AddItem(new MobInfo({ name: 'Andra', hp: 5, level: 1, rewardTokens: 2, isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Rare)] }));
this.roamingMobDeck.AddItem(new MobInfo({ name: 'Andra', hp: 7, level: 3, rewardTokens: 2, isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Epic)] }));
this.roamingMobDeck.AddItem(new MobInfo({ name: 'Andra', hp: 5, level: 5, rewardTokens: 0, isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Epic), this.getTreasureDrawingItem(TreasureType.Epic), this.getTreasureDrawingItem(TreasureType.Epic)] }));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'Ytheria, Undead Queen', hp: 4, level: 1, rewardTokens: 2,
attackInfos: [new AttackInfo(MD2Icon.Melee, 1), new AttackInfo(MD2Icon.Rage, 2, 0, 0, 1)],
defenseInfos: new DefenseInfo(1, 1),
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Rare)]
}));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'Ytheria, Undead Queen', hp: 6, level: 3, rewardTokens: 2,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1), new AttackInfo(MD2Icon.Rage, 1, 1, 0, 1)],
defenseInfos: new DefenseInfo(2, 1),
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Epic)]
}));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'Ytheria, Undead Queen', hp: 8, level: 5, rewardTokens: 0,
attackInfos: [new AttackInfo(MD2Icon.Melee, 2, 1), new AttackInfo(MD2Icon.Rage, 2, 1, 1, 1)],
defenseInfos: new DefenseInfo(4, 1),
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Epic), this.getTreasureDrawingItem(TreasureType.Epic), this.getTreasureDrawingItem(TreasureType.Epic)]
}));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'Lyidan, Incubus Lord', hp: 7, level: 1, rewardTokens: 2,
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Rare)]
}));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'Lyidan, Incubus Lord', hp: 10, level: 3, rewardTokens: 2,
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Epic)]
}));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'Lyidan, Incubus Lord', hp: 7, level: 5, rewardTokens: 0,
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Epic), this.getTreasureDrawingItem(TreasureType.Epic), this.getTreasureDrawingItem(TreasureType.Epic)]
}));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'The Ghoul', hp: 5, level: 1, rewardTokens: 2,
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Rare)]
}));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'The Ghoul', hp: 8, level: 3, rewardTokens: 2,
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Epic)]
}));
this.roamingMobDeck.AddItem(new MobInfo({
name: 'The Ghoul', hp: 5, level: 5, rewardTokens: 0,
isRoamingMonster: true, fixedCarriedTreasure: [this.getTreasureDrawingItem(TreasureType.Epic), this.getTreasureDrawingItem(TreasureType.Epic), this.getTreasureDrawingItem(TreasureType.Epic)]
}));
}
// #region Public Getters And Setters (5)
public get highestPlayerLevel(): number {
if (this.heros.length > 0) {
return Math.max(...this.heros.map(h => h.level));
} else {
return this._highestPlayerLevel;
}
}
public set highestPlayerLevel(v: number) {
this._highestPlayerLevel = v;
}
public get playerAmount(): number {
if (this.heros.length > 0) {
return this.heros.length;
} else {
return this._playerAmount;
}
}
public set playerAmount(v: number) {
this._playerAmount = v;
}
public get playerHero(): MD2HeroInfo {
return this.heros.find(h => h.playerInfo.signalRClientId == this.loginUserService.userAccess.signalRSessionId);
}
// #endregion Public Getters And Setters (5)
// #region Public Methods (27)
public addTreasure(type: TreasureType, amount: number = 1) {
let item = this.getTreasureDrawingItem(type, amount);
this.treasureBag.AddItem(item);
this.refreshTreasureBagSubject.next(this.treasureBag);
}
public broadcastAllHeroInfoToAll() {
this.heros.forEach(element => {
this.broadcastHeroInfoToAll(element);
});
}
public broadcastHeroAction(action: string, extraValue: any = null) {
let parameters = {};
parameters['tabId'] = this.loginUserService.sessionTabId;
parameters['extraValue'] = JSON.stringify(extraValue);
this.broadcastMessage('heroAction', action, parameters);
}
public broadcastHeroInfoToAll(hero: MD2HeroInfo) {
let message = {
receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
from: { isGroup: false, sessionId: hero.playerInfo.signalRClientId },
actionType: 'hero',
actionName: 'update',
} as SignalRMessage;
message.parameters = { hero: JSON.stringify(hero) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public broadcastHeroInfoToOwner(hero: MD2HeroInfo) {
let message = {
receiver: { isGroup: false, sessionId: hero.playerInfo.signalRClientId } as SignalRSession,
from: { isGroup: true, sessionId: this.gameRoomService.gameRoomId },
actionType: 'hero',
actionName: 'update',
} as SignalRMessage;
message.parameters = { hero: JSON.stringify(hero) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
/**
* `sessionId` = null means broadcast to all
*/
public broadcastMessage(actionType: string,
actionName: string,
parameters: { [key: string]: string; } = {},
sessionId: string = null) {
let message = {
receiver: { isGroup: !sessionId, sessionId: sessionId } as SignalRSession,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
actionType: actionType,
actionName: actionName,
parameters: parameters
} as SignalRMessage;
if (sessionId == null) {
message.receiver = { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession
} else {
message.receiver = { isGroup: false, sessionId: this.gameRoomService.gameRoomId } as SignalRSession
}
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public broadcastGameInfo() {
let parameters = {};
parameters['gameInfo'] = JSON.stringify(this.info);
this.broadcastMessage('GameRoom', 'update', parameters);
}
public broadcastMobsInfo() {
let parameters = {};
parameters['roamingMonsters'] = JSON.stringify(this.roamingMonsters);
parameters['mobs'] = JSON.stringify(this.mobs);
this.broadcastMessage('mobs', 'update', parameters);
}
public broadcastMyHeroInfo() {
this.broadcastHeroInfoToAll(this.playerHero);
}
public darknessPhase() {
this.heros.forEach(hero => {
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.broadcastHeroInfoToOwner(hero);
});
this.info.roundPhase = RoundPhase.HeroPhase;
this.info.round++;
if (this.darknessPhaseRule.runDarknessPhase()) {
this.msgBoxService.show(`${NumberUtils.Ordinal(this.info.round)} Hero Phase`, { icon: ADIcon.INFO });
}
//this.runNextPhase();
}
public drawMob(isRoamingMonster: boolean) {
let mobDeck = null as DrawingBag<MobInfo>;
let level = 1;
if (this.highestPlayerLevel < 3) {
} else if (this.highestPlayerLevel < 5) {
level = 3;
} else {
level = 5;
}
if (isRoamingMonster) {
mobDeck = this.roamingMobDeck;
} else {
mobDeck = this.mobDeck;
}
if (mobDeck.drawingItems.filter(m => (m as MobInfo).level == level)
.map(d => d.drawingWeight).reduce((a, b) => a + b, 0) == 0) {
mobDeck.RestoreRemoveItems();
}
let newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level)[0]);
if (isRoamingMonster) {
newSpawnMob.unitRemainHp = newSpawnMob.hp * this.playerAmount;
newSpawnMob.mobAmount = 1;
} else {
newSpawnMob.mobAmount = this.playerAmount + 1;
}
newSpawnMob.imageUrl = this.mobImage(newSpawnMob.name, newSpawnMob.level, isRoamingMonster);
let exitingMob = this.mobs.find(m => m.name == newSpawnMob.name);
if (exitingMob) {
exitingMob.level = newSpawnMob.level;
exitingMob.hp = newSpawnMob.hp;
exitingMob.imageUrl = newSpawnMob.imageUrl;
} else {
this.mobs.push(newSpawnMob);
newSpawnMob.carriedTreasure = this.treasureBag.DrawAndRemove(newSpawnMob.rewardTokens);
}
return { exitingMob, mob: newSpawnMob };
}
public enemyPhase() {
//this.msgBoxService
let monsterBag = new DrawingBag<MobInfo>();
monsterBag.drawingItems = this.roamingMonsters.concat(this.mobs);
for (let i = 0; i < monsterBag.drawingItems.length; i++) {
const element = monsterBag.drawingItems[i];
element.drawingWeight = 1;
}
if (monsterBag.drawingItems.length > 0) {
this.enemyPhaseMobs = monsterBag.DrawAndRemove(monsterBag.drawingItems.length);
//this.showEnemyPhaseAction();
this.enemyPhaseSubject.next(this.enemyPhaseMobs[0]);
} else {
this.runNextPhase();
}
}
public enterBossFight() {
//this.sendMsgboxMsg
}
public fileList(folderPath: string) {
return this.fileService.FileList('Images/MD2/' + folderPath);
}
public getTreasureDrawingItem(type: TreasureType, amount: number = 1) {
return new DrawingItem(`${TreasureType[type]} Treasure`, `It's a ${TreasureType[type]} Treasure!`, this.treasureImage(type), amount);
}
public heroFullName(hero: MD2HeroInfo) {
if (!hero) return '';
return `${hero.playerInfo.name} (${HeroClass[hero.class]} - ${hero.name})`
}
public iconHtml(icon: MD2Icon, cssClass = '') {
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>`;
}
}
public imgHtml(imgPath: string, cssClass = 'g-height-25 mr-1') {
return `<img src='${this.imgUrl(imgPath)}' class='${cssClass}'>`;
}
public imgUrl(imgPath: string) {
return this.fileService.ImageUrl('MD2/' + imgPath);
}
public initMobDecks() {
this.mobDeck = new DrawingBag();
this.roamingMobDeck = new DrawingBag();
this.mobDeck.AddItem(new MobInfo({ name: 'Gargoyles', hp: 2, level: 1, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Demons', hp: 3, level: 1, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Undead', hp: 4, level: 1, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Fire Entities', hp: 3, level: 1, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Fallen Angels', hp: 2, level: 1, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Infernal Imps', hp: 3, level: 1, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Skeletons', hp: 2, level: 1, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Satyrs', hp: 3, level: 1, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Gargoyles', hp: 3, level: 3, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Demons', hp: 4, level: 3, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Undead', hp: 5, level: 3, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Fire Entities', hp: 4, level: 3, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Fallen Angels', hp: 3, level: 3, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Infernal Imps', hp: 4, level: 3, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Skeletons', hp: 3, level: 3, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Satyrs', hp: 4, level: 3, rewardTokens: 1 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Gargoyles', hp: 6, level: 5, rewardTokens: 2 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Demons', hp: 6, level: 5, rewardTokens: 2 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Undead', hp: 8, level: 5, rewardTokens: 2 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Fire Entities', hp: 7, level: 5, rewardTokens: 2 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Fallen Angels', hp: 5, level: 5, rewardTokens: 2 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Infernal Imps', hp: 5, level: 5, rewardTokens: 2 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Skeletons', hp: 5, level: 5, rewardTokens: 2 }));
this.mobDeck.AddItem(new MobInfo({ name: 'Satyrs', hp: 6, level: 5, rewardTokens: 2 }));
this.initCoreGameRoamingMonsters();
}
public initTreasureBag() {
this.treasureBag.ClearAllItems();
this.addTreasure(TreasureType.Common, 15);
this.addTreasure(TreasureType.Rare, 5);
}
public levelUpPhase() {
for (let i = 0; i < this.heros.length; i++) {
const hero = this.heros[i];
let levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp);
while (levelUpInfo != null) {
hero.level = levelUpInfo.level;
hero.hp += levelUpInfo.extraHp;
hero.hpMaximum += levelUpInfo.extraHp;
hero.mp += levelUpInfo.extraMp;
hero.mpMaximum += levelUpInfo.extraMp;
hero.exp = levelUpInfo.currentExp;
this.broadcastHeroInfoToOwner(hero);
this.sendMsgboxMsg(hero.playerInfo.signalRClientId, { title: 'Level Up', text: 'Please do a skill level up!', icon: ADIcon.INFO });
levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp);
}
}
this.runNextPhase();
}
public mobImage(name: string, level: number, isRoamingMonsters: boolean) {
name = StringUtils.replaceAll(name, ' ', '').replace(',', '');
if (level < 3) {
level = 1;
} else if (level < 5) {
level = 2;
} else {
level = 3;
}
if (isRoamingMonsters) {
return this.imgUrl(`Mobs/CoreGame/RoamingMonsters/${name}/${level}.png`);
} else {
return this.imgUrl(`Mobs/CoreGame/Mobs/${name}/${level}.png`);
}
}
public playerJoin(hero: MD2HeroInfo) {
hero.playerInfo = this.gameRoomService.currentPlayer();
hero.level = 1;
hero.hp = hero.hpMaximum;
hero.mp = hero.mpMaximum;
hero.exp = 0;
let message = {
receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
actionType: 'hero',
actionName: 'join',
} as SignalRMessage;
message.parameters = { hero: JSON.stringify(hero) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public rollBlackDice(times: number) {
let wounds = 0;
let claws = 0;
//miss 33%
//1 claw 33%
//1 wound 17%
//1 claw, 1 wound 17%
for (let i = 0; i < times; i++) {
let result = Math.random() * 100;
if (result <= 33) {
} else if (result <= 67) {
claws += 1;
} else if (result <= 83) {
wounds += 1;
} else {
claws += 1;
wounds += 1;
}
}
return { claws, wounds };
}
public runNextPhase() {
this.info.roundPhase++;
switch (this.info.roundPhase) {
case RoundPhase.HeroPhase:
this.heros.forEach(hero => {
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.broadcastHeroInfoToOwner(hero);
});
break;
case RoundPhase.EnemyPhase:
this.enemyPhase();
break;
case RoundPhase.LevelUpPhase:
this.levelUpPhase();
break;
case RoundPhase.DarknessPhase:
this.darknessPhase();
break;
default: break;
}
let parameters = {};
parameters['phase'] = this.info.roundPhase;
this.broadcastMessage('roundPhase', '', parameters);
}
public sendMsgboxMsg(playerSessionId: string, msg: Partial<MessageBoxConfig>) {
let message = {
receiver: { isGroup: false, sessionId: playerSessionId } as SignalRSession,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
actionType: 'message',
actionName: 'popup',
} as SignalRMessage;
message.parameters = { msg: JSON.stringify(msg) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public treasureImage(type: TreasureType) {
return this.imgUrl(`TreasureToken/${TreasureType[type]}.png`);
}
public getTargetHerosByFilter(targetType: AttackTarget, onlyOne: boolean = false) {
let beenAttackedHero = [] as MD2HeroInfo[];
switch (targetType) {
case AttackTarget.LowestHp:
let lowestHp = Math.min(...this.heros.map(h => h.hp));
beenAttackedHero = this.heros.filter(h => h.hp == lowestHp);
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
break;
case AttackTarget.HighestHp:
let highestHp = Math.max(...this.heros.map(h => h.hp));
beenAttackedHero = this.heros.filter(h => h.hp == highestHp);
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
break;
case AttackTarget.HighestMp:
let highestMp = Math.max(...this.heros.map(h => h.mp));
beenAttackedHero = this.heros.filter(h => h.mp == highestMp);
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
break;
case AttackTarget.LowestLevel:
let lowestLevel = Math.max(...this.heros.map(h => h.level));
beenAttackedHero = this.heros.filter(h => h.level == lowestLevel);
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
break;
case AttackTarget.LeastCorruption:
let leastCor = Math.min(...this.heros.map(h => h.corruptionToken));
beenAttackedHero = this.heros.filter(h => h.corruptionToken == leastCor);
break;
case AttackTarget.MostCorruption:
let mostCor = Math.max(...this.heros.map(h => h.corruptionToken));
beenAttackedHero = this.heros.filter(h => h.corruptionToken == mostCor);
break;
case AttackTarget.Random:
default:
beenAttackedHero = [this.heros[Math.round(Math.random() * (this.heros.length - 1))]];
//this.otherAttackTarget = 'Just act like normal.';
break;
}
if (onlyOne && beenAttackedHero.length > 1) {
beenAttackedHero = [beenAttackedHero[Math.round(Math.random() * (beenAttackedHero.length - 1))]];
}
return beenAttackedHero;
}
public getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) {
return `<b>${StringUtils.makeCommaSeparatedString(beenAttackedHero.map(h => this.heroFullName(h)), false, true)}</b>`;
}
// #endregion Public Methods (27)
}
export class MD2GameInfo {
/**
*
*/
constructor(
config: Partial<MD2GameInfo> = undefined
) {
if (config) {
Object.assign(this, config);
this.mobs = this.mobs.map(m => new MobInfo(m));
this.roamingMonsters = this.roamingMonsters.map(m => new MobInfo(m));
if (this.boss.info) {
this.boss.info = new MobInfo(this.boss.info);
}
this.currentActivateHero = new MD2HeroInfo(this.currentActivateHero);
this.heros = this.heros.map(h => new MD2HeroInfo(h));
}
}
public currentActivateHero: MD2HeroInfo;
public isBossFight: boolean = false;
public mobs: MobInfo[] = [];
public roamingMonsters: MobInfo[] = [];
public heros: MD2HeroInfo[] = [];
public round = 1;
public roundPhase: RoundPhase = RoundPhase.HeroPhase;
public showAttackBtn: boolean = false;
public boss: IBossFight;
}

View File

@ -1,4 +1,4 @@
<div class='form-group'> <div class='form-group adjustContainer'>
<label for='playerAmount' class='label' *ngIf="title" [innerHtml]="titleHtml"></label> <label for='playerAmount' class='label' *ngIf="title" [innerHtml]="titleHtml"></label>
<div> <div>
<button nbButton outline type="button" status="primary" size="{{size}}" (click)="adjustNumber(false)"> <button nbButton outline type="button" status="primary" size="{{size}}" (click)="adjustNumber(false)">

View File

@ -0,0 +1,3 @@
.adjustContainer {
white-space: nowrap;
}

View File

@ -57,17 +57,21 @@ export class AdjacentNumberInputComponent implements ControlValueAccessor, OnIni
@Output() hitMaximum = new EventEmitter<number>(); @Output() hitMaximum = new EventEmitter<number>();
@Output() hitIncreasing = new EventEmitter<number>(); @Output() hitIncreasing = new EventEmitter<number>();
@Output() hitDecreasing = new EventEmitter<number>(); @Output() hitDecreasing = new EventEmitter<number>();
@Output() hitChange = new EventEmitter<number>();
onChange = (value: number) => { onChange = (value: number) => {
}; };
onTouched = () => { }; onTouched = () => { };
constructor( constructor(
private cdRef: ChangeDetectorRef private cdRef: ChangeDetectorRef
) { } ) { }
writeValue(obj: number): void { writeValue(obj: number): void {
this.currentNumber = obj; if (this.currentNumber != obj) {
this.onChange(obj);
this.currentNumber = obj;
this.onChange(obj);
this.cdRef.detectChanges();
}
} }
registerOnChange(fn: (value: number) => void): void { registerOnChange(fn: (value: number) => void): void {
@ -99,6 +103,8 @@ export class AdjacentNumberInputComponent implements ControlValueAccessor, OnIni
if (null == this.maximum || this.currentNumber < this.maximum) { if (null == this.maximum || this.currentNumber < this.maximum) {
this.currentNumber++; this.currentNumber++;
this.hitIncreasing.next(1); this.hitIncreasing.next(1);
this.hitChange.next(1);
} else { } else {
this.hitMaximum.next(this.currentNumber); this.hitMaximum.next(this.currentNumber);
} }
@ -106,6 +112,7 @@ export class AdjacentNumberInputComponent implements ControlValueAccessor, OnIni
if (null == this.minimum || this.currentNumber > this.minimum) { if (null == this.minimum || this.currentNumber > this.minimum) {
this.currentNumber--; this.currentNumber--;
this.hitDecreasing.next(1); this.hitDecreasing.next(1);
this.hitChange.next(-1);
} else { } else {
this.hitMinimum.next(this.currentNumber); this.hitMinimum.next(this.currentNumber);
} }

View File

@ -40,4 +40,22 @@ export class ArrayUtils {
} }
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]); arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
}; };
public static Shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex > 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
} }

View File

@ -1,26 +1,26 @@
// Typography // Typography
// @import "globals/typo-font-sizes"; @import "globals/typo-font-sizes";
// @import "globals/typo-font-weights"; @import "globals/typo-font-weights";
// @import "globals/typo-text-transforms"; @import "globals/typo-text-transforms";
// @import "globals/typo-text-decorations"; @import "globals/typo-text-decorations";
// @import "globals/typo-letter-spacings"; @import "globals/typo-letter-spacings";
// @import "globals/typo-line-heights"; @import "globals/typo-line-heights";
// @import "globals/typo-font-styles"; @import "globals/typo-font-styles";
// @import "globals/typo-list-styles"; @import "globals/typo-list-styles";
// @import "globals/typo-text-styles"; @import "globals/typo-text-styles";
// Border styles // Border styles
// @import "globals/borders"; @import "globals/borders";
// @import "globals/border-none"; @import "globals/border-none";
// @import "globals/border-styles"; @import "globals/border-styles";
// @import "globals/border-radiuses"; @import "globals/border-radiuses";
// @import "globals/border-dashed"; @import "globals/border-dashed";
// @import "globals/border-dotted"; @import "globals/border-dotted";
// @import "globals/border-widths"; @import "globals/border-widths";
//Common //Common
@import "globals/heights"; @import "globals/heights";
@import "globals/widths"; @import "globals/widths";
// @import "globals/z-index"; @import "globals/z-index";
// @import "globals/cursor"; @import "globals/cursor";

View File

@ -2,6 +2,30 @@
Heights Heights
------------------------------------*/ ------------------------------------*/
$i: 5;
@while $i < 100 {
.g-height-#{$i}vh {
height: #{$i}vh;
}
$i: $i + 5;
}
$i: 10;
@while $i < 200 {
.g-height-#{$i}px {
height: #{$i}px;
}
$i: $i + 2;
}
$i: 200;
@while $i < 800 {
.g-height-#{$i}px {
height: #{$i}px;
}
$i: $i + 20;
}
@each $breakpoint in map-keys($grid-breakpoints) { @each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) { @include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints); $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
@ -37,6 +61,39 @@
} }
$i: $i + 5; $i: $i + 5;
} }
$i: 5;
@while $i < 100 {
.g-height-#{$i}vh {
height: #{$i}vh;
}
.g-max-height-#{$i}vh#{$infix} {
max-height: #{$i}vh !important;
}
.g-min-height-#{$i}vh#{$infix} {
min-height: #{$i}vh !important;
}
$i: $i + 5;
}
$i: 10;
@while $i < 200 {
.g-height-#{$i}px#{$infix} {
height: #{$i}px;
}
$i: $i + 2;
}
$i: 200;
@while $i < 800 {
.g-height-#{$i}px#{$infix} {
height: #{$i}px;
}
$i: $i + 20;
}
/* Auto Height */ /* Auto Height */
.g-height-auto#{$infix} { .g-height-auto#{$infix} {
height: auto; height: auto;

View File

@ -4,3 +4,7 @@
.g-text-break-word { .g-text-break-word {
word-wrap: break-word; word-wrap: break-word;
} }
.g-text-nowrap {
white-space: nowrap;
}

View File

@ -15,14 +15,38 @@
.MD2text { .MD2text {
font-family: "DwarvenAxeBBW00-Regular", sans-serif !important; font-family: "DwarvenAxeBBW00-Regular", sans-serif !important;
} }
.MD2IconContainer-sm {
.MD2Icon {
font-size: 18px;
line-height: 18px;
}
}
.MD2IconContainer-md {
.MD2Icon {
font-size: 30px;
line-height: 30px;
}
}
.MD2IconContainer-lg {
.MD2Icon {
font-size: 40px;
line-height: 40px;
}
}
.MD2IconContainer-xl {
.MD2Icon {
font-size: 50px;
line-height: 50px;
}
}
.MD2Icon { .MD2Icon {
font-family: "Massive Darkness 2", sans-serif !important; font-family: "Massive Darkness 2", sans-serif !important;
//font-size: 50px; //font-size: 50px;
&.atk::before { &.attack::before {
content: "A"; content: "A";
} }
&.def::before { &.defense::before {
content: "B"; content: "B";
} }
&.mana::before { &.mana::before {
@ -34,7 +58,7 @@
&.enemySkill::before { &.enemySkill::before {
content: "E"; content: "E";
} }
&.enemyAtk::before { &.enemyAttack::before {
content: "F"; content: "F";
} }
&.reroll::before { &.reroll::before {
@ -107,13 +131,13 @@
&::before { &::before {
content: "U"; content: "U";
} }
color: orangered !important; color: #ffab6a !important;
} }
&.diceBlack { &.diceBlack {
&::before { &::before {
content: "U"; content: "U";
} }
color: black !important; color: #5b5d64 !important;
} }
&.diceRed { &.diceRed {
&::before { &::before {
@ -124,16 +148,16 @@
&.dice { &.dice {
font-family: "Massive Darkness 2", sans-serif !important; font-family: "Massive Darkness 2", sans-serif !important;
&.Blue { &.Blue {
color: aqua !important; color: #58a1ff !important;
} }
&.Yellow { &.Yellow {
color: #ffc107 !important; color: #ffc107 !important;
} }
&.Orange { &.Orange {
color: orangered !important; color: #ffab6a !important;
} }
&.Black { &.Black {
color: black !important; color: #5b5d64 !important;
} }
&.Red { &.Red {
color: crimson !important; color: crimson !important;

View File

@ -17,6 +17,6 @@
@import "form-controls"; @import "form-controls";
@import "md2"; @import "md2";
nb-card { nb-card {
max-width: 90vw; max-width: 96vw;
max-height: 96vh; max-height: 96vh;
} }

View File

@ -13,7 +13,7 @@ const urls = [
'https://api.happiness.tours' 'https://api.happiness.tours'
]; ];
const LINE_CLIENT_ID = '1657422139'; const LINE_CLIENT_ID = '1657422139';
const dockerDebug = urls[0]; const dockerDebug = urls[2];
export const environment = { export const environment = {
production: false, production: false,
apiUrl: dockerDebug, apiUrl: dockerDebug,