+
+
+
- Treasure Bag
-
-
-
+
Game Info
+
+
+
-
-
-
X {{treasure.unitAmount}}
-
+
+
+
+
0">
+
+
+
+
+
+ Lv.:{{hero.level}}
+ HP: {{hero.hp}}/{{hero.hpMaximum}}
+ Mana: {{hero.mp}}/{{hero.mpMaximum}}
+ Exp: {{hero.exp}}
+ Fire:{{hero.fireToken}}
+ Frozen:{{hero.frozenToken}}
+ Inactive
+ 0">Remain
+ Actions: {{hero.remainActions}}
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/games/massive-darkness2/massive-darkness2.component.ts b/src/app/games/massive-darkness2/massive-darkness2.component.ts
index f5d5c1c..8398231 100644
--- a/src/app/games/massive-darkness2/massive-darkness2.component.ts
+++ b/src/app/games/massive-darkness2/massive-darkness2.component.ts
@@ -4,6 +4,17 @@ import { FileService } from '../../services/file.service';
import { MsgBoxService } from '../../services/msg-box.service';
import { ArrayUtils } from '../../utilities/array-utils';
import { ObjectUtils } from '../../utilities/object-utils';
+import { first, map, take, takeUntil } from 'rxjs/operators';
+import { TreasureType, DrawingBag, DrawingItem, HeroClass, MD2HeroInfo, RoundPhase, MobInfo, MobDlgType } from './massive-darkness2.model';
+import { MD2Service } from '../../services/md2.service';
+import { GameRoomService } from '../../services/game-room.service';
+import { MD2Base } from './MD2Base';
+import { StateService } from '../../services/state.service';
+import { ActivatedRoute } from '@angular/router';
+import { QRCodeService } from '../../services/qrcode.service';
+import { StringUtils } from '../../utilities/string-utils';
+import { SpawnMobDlgComponent } from './mobs/spawn-mob-dlg/spawn-mob-dlg.component';
+import { BossMicheal } from './massive-darkness2.model.boss';
@Component({
selector: 'ngx-massive-darkness2',
@@ -11,133 +22,113 @@ import { ObjectUtils } from '../../utilities/object-utils';
styleUrls: ['./massive-darkness2.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class MassiveDarkness2Component implements OnInit {
- TreasureType = TreasureType;
- treasureBag: DrawingBag = new DrawingBag();
-
+export class MassiveDarkness2Component extends MD2Base implements OnInit {
+ HeroClass: HeroClass
constructor(
private fileService: FileService,
- private cdRef: ChangeDetectorRef,
- private msgBoxService: MsgBoxService
- ) { }
+ private msgBoxService: MsgBoxService,
+ private qrCodeService: QRCodeService,
+ public gameRoomService: GameRoomService,
+ public md2Service: MD2Service,
+ protected stateService: StateService,
+ protected route: ActivatedRoute,
+ protected cdRef: ChangeDetectorRef,
+ ) {
+ super(md2Service, stateService, route, cdRef);
+ }
ngOnInit(): void {
- this.resetTreasureBag();
- this.detectChanges();
- }
- detectChanges() {
+ super.ngOnInit();
+ this.md2Service.enemyPhaseSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
+ this.showEnemyPhaseAction(0);
+ });
- if (!this.cdRef['destroyed']) {
- this.cdRef.detectChanges();
+ }
+ override signalRInitialized() {
+
+ }
+ showQrCode() {
+ if (this.md2Service.initialized == false) {
+ this.gameRoomService.createGameRoom('MD2');
+ this.md2Service.initialized = true;
}
+ let initUrl = `${window.location.origin}/games/MD2_Hero/${this.gameRoomService.gameRoomId}`;
+ this.msgBoxService.show("Scan To Join", { text: `
Link` });
}
- resetTreasureBag() {
- this.treasureBag.ClearAllItems();
- this.addTreasure(TreasureType.Common, 15);
- }
- addTreasure(type: TreasureType, amount: number = 1) {
- let item = new DrawingItem(`${TreasureType[type]} Treasure`, `It's a ${TreasureType[type]} Treasure!`, this.fileService.ImageUrl(`TreasureToken/${TreasureType[type]}.png`), amount);
-
- this.treasureBag.AddItem(item);
- this.detectChanges();
- }
- treasureImage(type: TreasureType) {
- return this.fileService.ImageUrl(`TreasureToken/${TreasureType[type]}.png`);
- }
- drawTreasure() {
+ refreshUI() {
+ console.log('Dashboard RefreshUI');
}
-}
-export enum TreasureType {
- Common,
- Rare,
- Epic,
- Legendary
-}
-export class DrawingBag {
- constructor() {
- this.drawingItems = [];
- this.removedItems = [];
+ heroClassName(heroInfo: MD2HeroInfo) {
+ return HeroClass[heroInfo.class];
}
- drawingItems: DrawingItem[]
- removedItems: DrawingItem[]
- public Draw(amount: number): DrawingItem[] {
- let drawItems: DrawingItem[] = [];
- for (let i = 0; i < amount; i++) {
+ showEnemyPhaseAction(index: number) {
+ let mob = new MobInfo(this.md2Service.enemyPhaseMobs[index]);
+ let enemyInfo = `

`;
+ let extraRule = '';
- drawItems.push(this.DrawAndRemove());
- }
- this.RestoreRemoveItems();
- return drawItems;
- }
- public DrawAndRemove(): DrawingItem {
- if (this.drawingItems.length > 0) {
- let drawItem = null as DrawingItem;
- let drawIndex = Math.random() * this.drawingItems.reduce((sum, current) => sum + current.unitAmount, 0);
+ // switch (Math.random() * 3) {
- let drawCalc = 0;
- for (let i = 0; i < this.drawingItems.length; i++) {
- const item = this.drawingItems[i];
- drawCalc += item.unitAmount;
- if (drawCalc >= drawIndex) {
- drawItem = ObjectUtils.CloneValue(item);
- drawItem.unitAmount = 1;
- item.unitAmount -= 1;
- break;
+ // case 1:
+
+ // break;
+ // case 2:
+
+ // break;
+ // case 3:
+
+ // break;
+
+ // case 0:
+ // default:
+ // break;
+ // }
+
+ 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 => {
+ index++;
+ if (index < this.md2Service.enemyPhaseMobs.length) {
+ this.showEnemyPhaseAction(index);
+ } else {
+ this.md2Service.runNextPhase();
}
- }
- //ObjectUtils.CloneValue
- this.RemoveItem(drawItem);
- return drawItem;
- }
- return null;
+ });
+ // return this.msgBoxService.show(`Enemy Phase(${(this.enemyPhaseMobs.indexOf(mob) + 1)}/${this.enemyPhaseMobs.length})`, {
+ // text: enemyInfo,
+ // confirmButtonText: 'Next', buttons: ADButtons.OK
+ // }).pipe(first()).subscribe(result => {
+ // if ((this.enemyPhaseMobs.indexOf(mob) + 1) < this.enemyPhaseMobs.length) {
+ // this.showEnemyPhaseAction(this.enemyPhaseMobs[this.enemyPhaseMobs.indexOf(mob) + 1]);
+ // }
+ // });
}
-
- public RestoreRemoveItems() {
- for (let i = 0; i < this.removedItems.length; i++) {
- const removedItem = this.removedItems[i];
- this.AddItem(removedItem);
- }
-
- }
- public AddItem(item: DrawingItem) {
- let existingItem = this.drawingItems.find(i => i.name == item.name);
- if (existingItem) {
- existingItem.unitAmount += item.unitAmount;
- } else {
- this.drawingItems.push(item);
+ public get roundPhase(): string {
+ switch (this.md2Service.info.roundPhase) {
+ case RoundPhase.HeroPhase:
+ return StringUtils.getHtmlBadge("Hero Action Phase", "primary")
+ case RoundPhase.EnemyPhase:
+ return StringUtils.getHtmlBadge("Enemy Action Phase", "danger")
+ case RoundPhase.LevelUpPhase:
+ return StringUtils.getHtmlBadge("Level Up Phase", "success")
+ case RoundPhase.DarknessPhase:
+ return StringUtils.getHtmlBadge("Darkness Phase", "dark")
+ default: break;
}
}
- public RemoveItem(item: DrawingItem) {
- let existingItem = this.removedItems.find(i => i.name == item.name);
- if (existingItem) {
- existingItem.unitAmount += item.unitAmount;
- } else {
- this.removedItems.push(item);
- }
+ public get anyHeroRemainAction(): boolean {
+ return this.md2Service.heros.some(h => h.remainActions > 0);
}
- public ClearAllItems() {
- this.drawingItems = [];
- this.removedItems = [];
+ heroAction(hero: MD2HeroInfo, action: string) {
+
}
-}
-export class DrawingItem {
- constructor(
- name: string,
- description: string,
- imageUrl: string,
- unitAmount: number = 1
- ) {
- this.imageUrl = imageUrl
- this.name = name
- this.description = description
- this.unitAmount = unitAmount
+ enterBossFight() {
+ this.msgBoxService.showInputbox('Boss Fight', 'Choose the boss').pipe(first()).subscribe(result => {
+ this.md2Service.info.isBossFight = true;
+ this.md2Service.info.boss = new BossMicheal(this.md2Service);
+ this.detectChanges();
+ });
}
- imageUrl: string
- name: string
- description: string
- unitAmount: number
}
\ No newline at end of file
diff --git a/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts b/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts
new file mode 100644
index 0000000..5319131
--- /dev/null
+++ b/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts
@@ -0,0 +1,135 @@
+import { Subject } from "rxjs"
+import { first } from "rxjs/operators"
+import { MD2Service } from "../../services/md2.service"
+import { StringUtils } from "../../utilities/string-utils"
+import { BossActivationComponent } from "./boss-fight/boss-activation/boss-activation.component"
+import { TreasureType, AttackInfo, DefenseInfo, AttackType, MD2Icon, MD2HeroInfo, AttackTarget, MobInfo } from "./massive-darkness2.model"
+import { RollingBlackDice } from "./massive-darkness2.model.dice"
+
+
+export interface IBossFight {
+ name: string
+ addTreasureToken: Subject
+ spawnMob: Subject
+ spawnRoamingMonster: Subject
+ rounds: number
+ actions: number
+ hpPerHero: number
+ info: MobInfo
+ actionBlackDice: number
+ imgUrl: string
+ standUrl: string
+ combatInfo: MobSkill
+ activating(): boolean
+ prepareForBossFight(): void
+ nextRound(): void
+}
+export class BossMicheal implements IBossFight {
+
+ constructor(private md2Service: MD2Service) {
+ 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
+ addTreasureToken: Subject
+ spawnMob: Subject
+ spawnRoamingMonster: Subject
+ rounds: number
+ actions: number
+ hpPerHero: number
+ info: MobInfo
+ actionBlackDice: number
+ imgUrl: string
+ standUrl: string
+ combatInfo: MobSkill
+ corruptionTokenHtml: string
+ activating(): boolean {
+
+ 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.MostCorruption, true);
+ bossAction = new MobSkill('Justice From Above',
+ `Place Michael in the Zone at ${this.md2Service.getTargetHerosHtml(beenAttackedHero)} and attack Him/Her.`, beenAttackedHero);
+
+ break;
+ case 1:
+ //Lance Dash
+ beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true);
+ bossAction = new MobSkill('Lance Dash',
+ `Move Michael and Place 1 ${this.corruptionTokenHtml} in the Zone at ${this.md2Service.getTargetHerosHtml(beenAttackedHero)} and attack Him/Her.`, beenAttackedHero);
+ break;
+ case 2:
+ //Dark Blessing
+ bossAction = new MobSkill('Dark Blessing',
+ `Place Michael ion the central Zone and add 1 ${this.corruptionTokenHtml} to the Corruption Stone Zone with the least amount of ${this.corruptionTokenHtml}.
` +
+ `Deal ${this.darkBlessingCorruptionAmt} Wounds per ${this.corruptionTokenHtml} to all Heros in each Tiles distributed as they wish.`, beenAttackedHero);
+ break;
+
+ default:
+ break;
+ }
+ this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction } }).onClose
+ .pipe(first()).subscribe(result => {
+
+ });
+ return true;
+ }
+ prepareForBossFight(): void {
+
+ }
+ darkBlessingCorruptionAmt: number = 1;
+ nextRound(): void {
+ this.rounds++;
+ switch (this.rounds) {
+ case 3:
+ case 5:
+ this.darkBlessingCorruptionAmt++;
+ break;
+ case 2:
+ case 4:
+ this.info.defenseInfos[0].black += 1;
+ this.info.attackInfos[0].black += 1;
+ break;
+ // case 4:
+ // this.defInfo.black += 2;
+ // this.atkInfos[0].black += 2;
+ // break;
+
+ default:
+ break;
+ }
+ }
+}
+
+export class MobSkill {
+ constructor(skillName: string, skillDescription: string, targetHeros: MD2HeroInfo[] = []) {
+ this.skillName = skillName
+ this.skillDescription = skillDescription
+ this.targetHeros = targetHeros
+ }
+ skillName: string
+ skillDescription: string
+ targetHeros: MD2HeroInfo[]
+}
\ No newline at end of file
diff --git a/src/app/games/massive-darkness2/massive-darkness2.model.dice.ts b/src/app/games/massive-darkness2/massive-darkness2.model.dice.ts
new file mode 100644
index 0000000..99e7453
--- /dev/null
+++ b/src/app/games/massive-darkness2/massive-darkness2.model.dice.ts
@@ -0,0 +1,24 @@
+export class RollingBlackDice {
+ roll(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 };
+ }
+}
diff --git a/src/app/games/massive-darkness2/massive-darkness2.model.ts b/src/app/games/massive-darkness2/massive-darkness2.model.ts
new file mode 100644
index 0000000..8c1ed70
--- /dev/null
+++ b/src/app/games/massive-darkness2/massive-darkness2.model.ts
@@ -0,0 +1,447 @@
+import { Subject } from "rxjs";
+import { ObjectUtils } from "../../utilities/object-utils";
+import { GamePlayer } from "../games.model";
+import { MobSkill } from "./massive-darkness2.model.boss";
+
+export enum MobDlgType {
+ Spawn,
+ Activating,
+ BeenAttacked,
+ PreView
+}
+export enum RoundPhase {
+ HeroPhase,
+ EnemyPhase,
+ LevelUpPhase,
+ DarknessPhase,
+ BossActivation
+}
+export enum TreasureType {
+ Common,
+ Rare,
+ Epic,
+ Legendary
+}
+export enum HeroClass {
+ Berserker,
+ Wizard,
+ Rogue,
+ Ranger,
+ Shaman,
+ Paladin,
+}
+export enum MD2Icon {
+ Attack,
+ Defense,
+ Mana,
+ Shadow,
+ EnemySkill,
+ EnemyClaw,
+ Reroll,
+ Fire,
+ Frost,
+ OneHand,
+ TwoHand,
+ Helmet,
+ Armor,
+ Ring,
+ Foot,
+ Melee,
+ Range,
+ Magic,
+ HP,
+ MP,
+ Dice,
+ Arrow,
+ ArrowBullseye,
+ ArrowOverload,
+ SoulToken,
+ Rage,
+ RedDice,
+ BlueDice,
+ YellowDice,
+ OrangeDice
+}
+export enum AttackTarget {
+ Random = 40,
+ LowestHp = 50,
+ HighestHp = 60,
+ HighestMp = 70,
+ LowestLevel = 80,
+ MostCorruption = 200,
+ LeastCorruption = 201
+}
+export enum AttackType {
+ Melee = 15,
+ Range = 16,
+ Magic = 17
+}
+export class AttackInfo {
+ constructor(
+ type: MD2Icon,
+ yellow: number = 0,
+ orange: number = 0,
+ red: number = 0,
+ black: number = 0
+ ) {
+ this.type = type
+ this.orange = orange
+ this.red = red
+ this.yellow = yellow
+ this.black = black
+ }
+ type: MD2Icon
+ orange: number
+ red: number
+ yellow: number
+ black: number
+ attackSkill: MobSkill
+}
+export class DefenseInfo {
+ constructor(blue: number, black: number = 0) {
+ this.blue = blue
+ this.black = black
+ }
+ blue: number
+ black: number
+ defenseSkill: MobSkill
+}
+export class MD2LevelUpReward {
+ constructor(config: Partial) {
+ Object.assign(this, config);
+
+ }
+ level: number = 1;
+ needExp: number = 0;
+ currentExp = 0
+ extraHp = 0
+ extraMp = 0
+ extraRareToken = 0
+ extraEpicToken = 0
+}
+export class DrawingBag {
+ constructor(drawingItems: IDrawingItem[] = []) {
+ this.drawingItems = drawingItems;
+ this.removedItems = [];
+ }
+ drawingItems: IDrawingItem[]
+ removedItems: IDrawingItem[]
+ public bagIsEmpty() {
+ return this.drawingItems.reduce((sum, current) => sum + current.drawingWeight, 0) == 0;
+ }
+
+ public Draw(amount: number): T[] {
+ let drawItems: T[] = this.DrawAndRemove(amount);
+ this.RestoreRemoveItems();
+ return drawItems;
+ }
+ public DrawAndRemove(amount: number = 1, predicate: (value: T) => boolean = undefined): T[] {
+ let drawItems: T[] = [];
+ for (let i = 0; i < amount; i++) {
+ if (!this.bagIsEmpty()) {
+ let drawItem = null as T;
+ let drawingPool = [] as T[];
+ if (predicate) {
+ drawingPool = this.drawingItems.filter(predicate) as T[];
+ } else {
+ drawingPool = this.drawingItems as T[];
+ }
+ let drawIndex = Math.random() * drawingPool.reduce((sum, current) => sum + current.drawingWeight, 0);
+
+ let drawCalc = 0;
+ for (let i = 0; i < drawingPool.length; i++) {
+ const item = drawingPool[i];
+ drawCalc += item.drawingWeight;
+ if (drawCalc >= drawIndex) {
+ drawItem = ObjectUtils.CloneValue(item);
+ drawItem.drawingWeight = 1;
+ break;
+ }
+ }
+ //ObjectUtils.CloneValue
+ this.RemoveItem(drawItem);
+ drawItems.push(drawItem);
+ } else {
+ break;
+ }
+ }
+
+ return drawItems;
+ }
+
+ public RestoreRemoveItems() {
+ for (let i = 0; i < this.removedItems.length; i++) {
+ const removedItem = this.removedItems[i];
+ this.AddItem(removedItem);
+ }
+ this.removedItems = [];
+ }
+ public AddItem(item: IDrawingItem) {
+ let existingItem = this.drawingItems.find(i => i.name == item.name);
+ if (existingItem) {
+ existingItem.drawingWeight += item.drawingWeight;
+ } else {
+ this.drawingItems.push(item);
+ }
+ }
+
+ public RemoveItem(item: IDrawingItem) {
+ 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 (removedItem) {
+ removedItem.drawingWeight += item.drawingWeight;
+ } else {
+ this.removedItems.push(item);
+ }
+ }
+ }
+
+ }
+ public ClearAllItems() {
+
+ this.drawingItems = [];
+ this.removedItems = [];
+ }
+}
+
+export interface IDrawingItem {
+ imageUrl: string
+ name: string
+ description: string
+ drawingWeight: number
+}
+export class DrawingItem implements IDrawingItem {
+ constructor(
+ name: string,
+ description: string,
+ imageUrl: string,
+ drawingWeight: number = 1
+ ) {
+ this.imageUrl = imageUrl
+ this.name = name
+ this.description = description
+ this.drawingWeight = drawingWeight
+ }
+ imageUrl: string
+ name: string
+ description: string
+ drawingWeight: number
+}
+
+export class MobInfo implements IDrawingItem {
+ constructor(
+ config: Partial = {}
+ ) {
+ Object.assign(this, config);
+ this.description = config.name;
+ this.drawingWeight = 1;
+ this.unitRemainHp = config.hp
+ }
+ imageUrl: string
+ standUrl: string
+ name: string
+ description: string
+ drawingWeight: number
+ level: number;
+ rewardTokens: number;
+ hp: number;
+ mobAmount: number;
+ carriedTreasure: DrawingItem[];
+ fixedCarriedTreasure: DrawingItem[];
+ unitRemainHp: number;
+ isRoamingMonster: boolean = false;
+ attackInfos: AttackInfo[];
+ defenseInfos: DefenseInfo;
+ fireToken: number = 0;
+ frozenToken: number = 0;
+ corruptionToken: number = 0;
+ uiWounds: number;
+ uiFireTokens: number;
+ uiFrozenTokens: number;
+ uiCorruptionTokens: number;
+ uiAttackedBy: string;
+
+ public get carriedTreasureHtml(): string {
+ if (!this.carriedTreasure) return '';
+ return this.carriedTreasure.map(i => `
`)
+ .concat(this.fixedCarriedTreasure?.map(i => `
`)).join();
+ }
+
+ public get totalHp(): number {
+ return this.isRoamingMonster ? this.unitRemainHp : (this.mobAmount - 1) * this.hp + this.unitRemainHp;
+ }
+
+ public get minionAmount(): number {
+ return (this.mobAmount - 1);
+ }
+ public get leaderExp(): number {
+ return this.isRoamingMonster ? 4 : 2;
+ }
+
+ public get mobInfoHtml(): string {
+ let html = `
`
+ + `
Target Unit HP:${this.unitRemainHp}`;
+
+ if (this.isRoamingMonster) {
+ html += `