Fix reconnect issue.

This commit is contained in:
Chris Chen 2025-11-15 15:10:48 -08:00
parent 3a12b6a4ab
commit 3325c63631
14 changed files with 124 additions and 75 deletions

View File

@ -83,6 +83,7 @@ export abstract class MD2Base {
} }
abstract refreshUI(); abstract refreshUI();
handleSignalRCallback(message: SignalRMessage): void { handleSignalRCallback(message: SignalRMessage): void {
console.log('handleSignalRCallback', message);
if (message.from) { if (message.from) {
if (message.from.isGroup) { if (message.from.isGroup) {
if (!this.isHeroDashboard) return; if (!this.isHeroDashboard) return;
@ -146,6 +147,8 @@ export abstract class MD2Base {
break; break;
case 'update': case 'update':
if (this.isHeroDashboard) { if (this.isHeroDashboard) {
//Before update game info check the current Hero level
let playerHeroLevel = this.md2Service.playerHero?.level;
this.md2Service.info = new MD2GameInfo(JSON.parse(message.parameters['gameInfo']) as MD2GameInfo); this.md2Service.info = new MD2GameInfo(JSON.parse(message.parameters['gameInfo']) as MD2GameInfo);
let playerHero = this.md2Service.heros.find(h => h.playerInfo.tabId == this.stateService.loginUserService.sessionTabId); let playerHero = this.md2Service.heros.find(h => h.playerInfo.tabId == this.stateService.loginUserService.sessionTabId);
if (playerHero) { if (playerHero) {
@ -154,6 +157,13 @@ export abstract class MD2Base {
playerHero.playerInfo.isDisconnected = false; playerHero.playerInfo.isDisconnected = false;
this.md2Service.playerHero = playerHero; this.md2Service.playerHero = playerHero;
this.md2Service.broadcastMyHeroInfo(); this.md2Service.broadcastMyHeroInfo();
//When fetch game info, if the hero level is changed, show the level up message
if (playerHeroLevel && playerHeroLevel != playerHero.level) {
//do i--
for (let i = playerHero.level; i > playerHeroLevel; i--) {
this.md2Service.msgBoxService.show(`Level Up Lv.${i}`, { text: 'Please do a skill level up!', icon: ADIcon.INFO });
}
}
} }
this.detectChanges(); this.detectChanges();
} }
@ -172,9 +182,11 @@ export abstract class MD2Base {
} }
break; break;
case 'sendJoinInfo': case 'sendJoinInfo':
//When hero join the game, or reconnect to the game will receive this message
if (this.isHeroDashboard && this.md2Service.playerHero) { if (this.isHeroDashboard && this.md2Service.playerHero) {
this.md2Service.playerHero.playerInfo.signalRClientId = message.parameters['signalrconnid']; this.md2Service.playerHero.playerInfo.signalRClientId = message.parameters['signalrconnid'];
this.md2Service.broadcastMyHeroInfo(); //Send fetch game info to the hero
this.md2Service.broadcastFetchGameInfo();
} }
break; break;
default: default:

View File

@ -2,6 +2,7 @@ import { Component, OnInit, Input } from '@angular/core';
import { NbDialogRef } from '@nebular/theme'; import { NbDialogRef } from '@nebular/theme';
import { GameBundle } from '../massive-darkness2.db.model'; import { GameBundle } from '../massive-darkness2.db.model';
import { MD2Service } from '../../../services/MD2/md2.service'; import { MD2Service } from '../../../services/MD2/md2.service';
import { StringUtils } from '../../../utilities/string-utils';
export interface GameInitConfig { export interface GameInitConfig {
enabledBundles: GameBundle[]; enabledBundles: GameBundle[];
@ -23,12 +24,7 @@ export class GameInitDlgComponent implements OnInit {
enableMobSpecialRule: boolean = false; enableMobSpecialRule: boolean = false;
enableHeroBetrayal: boolean = false; enableHeroBetrayal: boolean = false;
bundleOptions = [ bundleOptions = [];
{ value: GameBundle.CoreGame, label: 'Core Game' },
{ value: GameBundle.HeavenFallen, label: 'Heaven Fallen' },
{ value: GameBundle.Zombiecide, label: 'Zombiecide' },
{ value: GameBundle.ZombiecideWhiteDeath, label: 'Zombiecide White Death' }
];
constructor( constructor(
private dlgRef: NbDialogRef<GameInitDlgComponent>, private dlgRef: NbDialogRef<GameInitDlgComponent>,
@ -36,6 +32,10 @@ export class GameInitDlgComponent implements OnInit {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
//For each GameBundle, create a new option
this.bundleOptions = Object.values(GameBundle).filter(b => !isNaN(Number(b))).map(b => ({
value: b as GameBundle, label: StringUtils.camelToTitle(GameBundle[b as GameBundle] as string)
}));
// Initialize from context if provided // Initialize from context if provided
if (this.initialConfig) { if (this.initialConfig) {
this.enabledBundles = [...this.initialConfig.enabledBundles]; this.enabledBundles = [...this.initialConfig.enabledBundles];

View File

@ -15,6 +15,7 @@ import { HeroClass, MD2HeroInfo, MD2HeroProfile, MD2Icon } from '../massive-dark
import { MD2Base } from '../MD2Base'; import { MD2Base } from '../MD2Base';
import { MD2HeroProfileService } from '../service/massive-darkness2.service'; import { MD2HeroProfileService } from '../service/massive-darkness2.service';
import { SignalRService } from '../../../services/signal-r.service'; import { SignalRService } from '../../../services/signal-r.service';
import { NbToastrService } from '@nebular/theme';
@Component({ @Component({
selector: 'ngx-hero-dashboard', selector: 'ngx-hero-dashboard',
@ -46,7 +47,9 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
} }
heroUpdateDebounceTimer = new DebounceTimer(1000, () => { this.broadcastHeroInfo(); }) heroUpdateDebounceTimer = new DebounceTimer(1000, () => {
this.broadcastHeroInfo();
})
classOptions: DropDownOption[] = [ classOptions: DropDownOption[] = [
new DropDownOption(HeroClass.Berserker, 'Berserker'), new DropDownOption(HeroClass.Berserker, 'Berserker'),
@ -92,7 +95,8 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef, protected cdRef: ChangeDetectorRef,
private msgBoxService: MsgBoxService, private msgBoxService: MsgBoxService,
private signalRService: SignalRService private signalRService: SignalRService,
private toastrService: NbToastrService
) { ) {
super(md2Service, stateService, route, cdRef); super(md2Service, stateService, route, cdRef);
this.isHeroDashboard = true; this.isHeroDashboard = true;
@ -109,9 +113,10 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
this.gameRoomService.joinGameRoom(this.roomId); this.gameRoomService.joinGameRoom(this.roomId);
//this.fetchGameInfo(); //this.fetchGameInfo();
this.signalRService.signalRMessageConnSubject.subscribe(state => { this.signalRService.signalRMessageConnSubject.subscribe(state => {
if (state.status == 'connected') { //fetchGameInfo is called in MD2Base.handleSignalRCallback sendJoinInfo message
this.fetchGameInfo(); // if (state.status == 'connected') {
} // this.fetchGameInfo();
// }
}); });
} }
@ -284,7 +289,6 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
this.hero.uiActivating = false; this.hero.uiActivating = false;
} }
this.broadcastHeroInfo(); this.broadcastHeroInfo();
} }
endActivation() { endActivation() {
if (this.hero.remainActions > 0) { if (this.hero.remainActions > 0) {

View File

@ -57,7 +57,9 @@
({{md2Service.highestPlayerLevel}})</label> ({{md2Service.highestPlayerLevel}})</label>
</div> --> </div> -->
<div class="col-12" *ngFor="let hero of md2Service.heros"> <div class="col-12" *ngFor="let hero of md2Service.heros">
<label class='label mr-1'>{{hero.playerInfo.name}} ({{heroClassName(hero)}} - <label class='label mr-1'
(click)="adjustHeroValue(hero,'remainActions')">{{hero.playerInfo.name}}
({{heroClassName(hero)}} -
{{hero.name}})</label> {{hero.name}})</label>
<span class="badge badge-primary mr-1" <span class="badge badge-primary mr-1"
(click)="adjustHeroValue(hero,'level')">Lv.:{{hero.level}}</span> (click)="adjustHeroValue(hero,'level')">Lv.:{{hero.level}}</span>

View File

@ -112,7 +112,7 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
inputType: 'number', inputType: 'number',
inputValue: hero[value].toString() inputValue: hero[value].toString()
}).pipe(first()).subscribe(result => { }).pipe(first()).subscribe(result => {
if (result) { if (![false, null, undefined].includes(result)) {
hero[value] = Number.parseInt(result); hero[value] = Number.parseInt(result);
this.md2Service.broadcastHeroInfoToAll(hero, true); this.md2Service.broadcastHeroInfoToAll(hero, true);
this.detectChanges(); this.detectChanges();

View File

@ -8,6 +8,7 @@ export enum MobSkillTarget {
HighestHp = 70, HighestHp = 70,
HighestMp = 80, HighestMp = 80,
LowestLevel = 90, LowestLevel = 90,
HighestLevel = 100,
MostExtraToken = 200, MostExtraToken = 200,
LeastExtraToken, LeastExtraToken,
MostExtraToken2, MostExtraToken2,
@ -17,8 +18,9 @@ export enum MobSkillTarget {
export enum GameBundle { export enum GameBundle {
CoreGame, CoreGame,
HeavenFallen, HeavenFallen,
Zombiecide, Zombicide,
ZombiecideWhiteDeath ZombicideWhiteDeath,
DarkBringerPack
} }
export interface MD2MobInfo { export interface MD2MobInfo {
id: string; id: string;
@ -48,7 +50,10 @@ export interface MD2MobLevelInfo {
defenceInfo: MD2DiceSet; defenceInfo: MD2DiceSet;
} }
export interface MD2MobSkill { export class MD2MobSkill {
constructor(config: Partial<MD2MobSkill> = {}) {
Object.assign(this, config);
}
id: string; id: string;
seq: number; seq: number;
level: number; level: number;

View File

@ -21,7 +21,7 @@ export class MD2Logic {
break; break;
case MobSkillTarget.LeastMp: case MobSkillTarget.LeastMp:
let lowestMp = Math.min(...heros.map(h => h.mp)); let lowestMp = Math.min(...heros.map(h => h.mp));
beenAttackedHero = heros.filter(h => h.hp == lowestMp); beenAttackedHero = heros.filter(h => h.mp == lowestMp);
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.'; //this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
break; break;
@ -36,10 +36,15 @@ export class MD2Logic {
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.'; //this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
break; break;
case MobSkillTarget.LowestLevel: case MobSkillTarget.LowestLevel:
let lowestLevel = Math.max(...heros.map(h => h.level)); let lowestLevel = Math.min(...heros.map(h => h.level));
beenAttackedHero = heros.filter(h => h.level == lowestLevel); beenAttackedHero = heros.filter(h => h.level == lowestLevel);
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.'; //this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
break; break;
case MobSkillTarget.HighestLevel:
let highestLevel = Math.max(...heros.map(h => h.level));
beenAttackedHero = heros.filter(h => h.level == highestLevel);
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
break;
case MobSkillTarget.LeastExtraToken: case MobSkillTarget.LeastExtraToken:
let leastExtraToken = Math.min(...heros.map(h => h.extraToken)); let leastExtraToken = Math.min(...heros.map(h => h.extraToken));

View File

@ -5,7 +5,7 @@ import { ObjectUtils } from "../../utilities/object-utils";
import { GamePlayer } from "../games.model"; import { GamePlayer } from "../games.model";
import { MD2Clone } from "./factorys/md2-clone"; import { MD2Clone } from "./factorys/md2-clone";
import { MobSkill } from "./massive-darkness2.model.boss"; import { MobSkill } from "./massive-darkness2.model.boss";
import { BossFightProfile, MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model"; import { BossFightProfile, GameBundle, MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model";
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/${(id ? `${encodeURI(id)}` : '')}` } const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/${(id ? `${encodeURI(id)}` : '')}` }
export enum MobDlgType { export enum MobDlgType {
@ -307,6 +307,8 @@ export class MobInfo implements IDrawingItem {
this.drawingWeight = 1; this.drawingWeight = 1;
this.unitRemainHp = config.hp this.unitRemainHp = config.hp
} }
id: string;
from: GameBundle;
type: MobType = MobType.Mob; type: MobType = MobType.Mob;
imageUrl: string imageUrl: string
standUrl: string standUrl: string
@ -429,7 +431,7 @@ export class MD2HeroInfo {
uiShowExtraToken2 = false; uiShowExtraToken2 = false;
uiBossFight = false; uiBossFight = false;
uiShowAttackBtn = false; uiShowAttackBtn = false;
uiBetrayal = 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})`
} }

View File

@ -18,10 +18,11 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
MD2Icon = MD2Icon; MD2Icon = MD2Icon;
public gridData: GridDataResult = { data: [], total: 0 }; public gridData: GridDataResult = { data: [], total: 0 };
private allData: MD2HeroProfile[] = []; private allData: MD2HeroProfile[] = [];
private lastSelectedHero: MD2HeroProfile;
private lastSelectedHeroClass: HeroClass; private lastSelectedHeroClass: HeroClass;
public gridState: State = { public gridState: State = {
skip: 0, skip: 0,
take: 10, take: 1000,
sort: [{ sort: [{
field: 'title', field: 'title',
dir: 'asc' dir: 'asc'
@ -77,6 +78,11 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
public addHandler(): void { public addHandler(): void {
const editorData = { heroClass: this.lastSelectedHeroClass || HeroClass.Berserker } as MD2HeroProfile; const editorData = { heroClass: this.lastSelectedHeroClass || HeroClass.Berserker } as MD2HeroProfile;
if (this.lastSelectedHero) {
editorData.heroClass = this.lastSelectedHero.heroClass;
editorData.skillHtml = this.lastSelectedHero.skillHtml;
editorData.shadowSkillHtml = this.lastSelectedHero.shadowSkillHtml;
}
const dialogRef = this.dialogService.open({ const dialogRef = this.dialogService.open({
title: 'Add New Hero Profile', title: 'Add New Hero Profile',
content: MD2HeroProfileEditorComponent, content: MD2HeroProfileEditorComponent,
@ -95,6 +101,7 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
dialogRef.result.subscribe((result: MD2HeroProfile) => { dialogRef.result.subscribe((result: MD2HeroProfile) => {
if (result) { if (result) {
this.lastSelectedHero = result;
this.lastSelectedHeroClass = result.heroClass; this.lastSelectedHeroClass = result.heroClass;
this.loadData(); this.loadData();
} }
@ -108,7 +115,7 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
width: '90vw', width: '90vw',
height: 600 height: 600
}); });
this.lastSelectedHero = dataItem;
const editor = dialogRef.content.instance; const editor = dialogRef.content.instance;
editor.isAdding = false; editor.isAdding = false;
editor.data = JSON.parse(JSON.stringify(dataItem)); editor.data = JSON.parse(JSON.stringify(dataItem));

View File

@ -14,6 +14,7 @@ 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';
import { GameBundle } from '../massive-darkness2.db.model';
@Component({ @Component({
selector: 'md2-mobs', selector: 'md2-mobs',
@ -89,21 +90,23 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
spawnSpecificMob() { spawnSpecificMob() {
let mobOptions = this.isRoamingMonster ? let mobOptions = this.isRoamingMonster ?
this.md2Service.allRoamingMonsterInfos.map(f => new DropDownOption(f.name, f.name)) : this.md2Service.allRoamingMonsterInfos.map(f => new DropDownOption(f.id, `${StringUtils.camelToTitle(GameBundle[f.from])} - ${f.name}`)) :
this.md2Service.allMobInfos.map(f => new DropDownOption(f.name, f.name)); this.md2Service.allMobInfos.map(f => new DropDownOption(f.id, `${StringUtils.camelToTitle(GameBundle[f.from])} - ${f.name}`));
mobOptions = mobOptions.sort((a, b) => a.value1.localeCompare(b.value1));
this.msgBoxService.showInputbox('Spawn', '', this.msgBoxService.showInputbox('Spawn', '',
{ {
inputType: 'dropdown', dropDownOptions: mobOptions, inputType: 'dropdown', dropDownOptions: mobOptions,
buttons: ADButtons.YesNoCancel, buttons: ADButtons.YesNoCancel,
confirmButtonText: 'Spawn', confirmButtonText: 'Spawn',
cancelButtonText: 'Random' cancelButtonText: 'Random'
}).pipe(first()).subscribe(mobName => { }).pipe(first()).subscribe(mobId => {
if (mobName || mobName === false) { if (mobId || mobId === false) {
if (!mobName) { mobName = null; } if (!mobId) { mobId = null; }
let result = this.md2Service.spawnMob(this.isRoamingMonster, mobName); let result = this.md2Service.spawnMob(this.isRoamingMonster, mobId);
let titleText = result.exitingMob == null ? `${result.mob.description} Shows Up` : `${result.mob.description} Activate One Action Now!`; let titleText = result.exitingMob == null ? `${result.mob.description} Shows Up` : `${result.mob.description} Activate One Action Now!`;
let actType = result.exitingMob == null ? MobDlgType.Spawn : MobDlgType.Activating; let actType = result.exitingMob == null ? MobDlgType.Spawn : MobDlgType.Activating;
let mob = result.exitingMob == null ? result.mob : result.exitingMob; let mob = result.exitingMob == null ? result.mob : result.exitingMob;

View File

@ -12,7 +12,8 @@ import { MD2ComponentBase } from '../../MD2Base';
import { ADButtons, ADIcon } from '../../../../ui/alert-dlg/alert-dlg.model'; import { ADButtons, ADIcon } from '../../../../ui/alert-dlg/alert-dlg.model';
import { MobSkillType } from '../../massive-darkness2.model.boss'; import { MobSkillType } from '../../massive-darkness2.model.boss';
import { Observable, from, of } from 'rxjs'; import { Observable, from, of } from 'rxjs';
import { MD2MobSkill } from '../../massive-darkness2.db.model'; import { MD2MobSkill, MobSkillTarget } from '../../massive-darkness2.db.model';
import { MD2Logic } from '../../massive-darkness2.logic';
type SkillResolutionResult = { result: boolean; skill: MD2MobSkill | null }; type SkillResolutionResult = { result: boolean; skill: MD2MobSkill | null };
@ -31,7 +32,7 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
MD2Icon = MD2Icon; MD2Icon = MD2Icon;
mob: MobInfo; mob: MobInfo;
actionInfoHtml: string; actionInfoHtml: string;
beenAttackedHero = [] as MD2HeroInfo[]; beenAttackedHero = null as MD2HeroInfo;
attackTarget: string; attackTarget: string;
otherAttackTarget: string; otherAttackTarget: string;
constructor( constructor(
@ -157,50 +158,45 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
if (this.mode == MobDlgType.Spawn) { if (this.mode == MobDlgType.Spawn) {
htmlText = `${this.mob.description} Shows Up`; htmlText = `${this.mob.description} Shows Up`;
} else if (this.mode == MobDlgType.Activating) { } else if (this.mode == MobDlgType.Activating) {
let targetType = null as AttackTarget; let targetType = null as MobSkillTarget;
let randomAttack = Math.random() * AttackTarget.LowestLevel; let randomAttack = Math.random() * MobSkillTarget.HighestLevel;
const values = Object.values(AttackTarget); const values = Object.values(MobSkillTarget);
const stringKeys = Object const stringKeys = Object
.keys(AttackTarget) .keys(MobSkillTarget)
.filter((v) => isNaN(Number(v))) .filter((v) => isNaN(Number(v)))
for (let i = 0; i < stringKeys.length; i++) { for (let i = 0; i < stringKeys.length; i++) {
const element = AttackTarget[stringKeys[i]]; const element = MobSkillTarget[stringKeys[i]];
if (element >= randomAttack) { if (element >= randomAttack) {
targetType = element; targetType = element;
break; break;
} }
} }
this.beenAttackedHero = MD2Logic.getTargetHeroByFilter(this.md2Service.heros, targetType);
switch (targetType) { switch (targetType) {
case AttackTarget.LeastHp: case MobSkillTarget.LeastHp:
let lowestHp = Math.min(...this.md2Service.heros.map(h => h.hp)); this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == lowestHp);
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
break; break;
case AttackTarget.HighestHp: case MobSkillTarget.HighestHp:
let highestHp = Math.max(...this.md2Service.heros.map(h => h.hp)); this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == highestHp);
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
break; break;
case AttackTarget.HighestMp: case MobSkillTarget.HighestMp:
let highestMp = Math.max(...this.md2Service.heros.map(h => h.mp)); this.otherAttackTarget = 'attacking the other <b>Highest Mana</b> hero.';
this.beenAttackedHero = this.md2Service.heros.filter(h => h.mp == highestMp);
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
break; break;
case AttackTarget.LowestLevel: case MobSkillTarget.LowestLevel:
let lowestLevel = Math.max(...this.md2Service.heros.map(h => h.level)); this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
this.beenAttackedHero = this.md2Service.heros.filter(h => h.level == lowestLevel);
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
break; break;
case AttackTarget.Random: case MobSkillTarget.HighestLevel:
this.otherAttackTarget = 'attacking the other <b>Highest Level</b> hero.';
break;
case MobSkillTarget.Random:
default: default:
this.beenAttackedHero = [this.md2Service.heros[Math.round(Math.random() * (this.md2Service.heros.length - 1))]]; this.otherAttackTarget = 'Just act like normal.';
//this.otherAttackTarget = 'Just act like normal.';
break; break;
} }
let attackedHeros = StringUtils.makeCommaSeparatedString(this.beenAttackedHero.map(h => this.md2Service.heroFullName(h)), true, true); //let attackedHeros = StringUtils.makeCommaSeparatedString(this.beenAttackedHero.map(h => this.md2Service.heroFullName(h)), true, true);
this.attackTarget = `Attacking <b>${attackedHeros}</b> first if possible.`; this.attackTarget = `Attacking <b>${this.beenAttackedHero.heroFullName}</b> first if possible, otherwise ${this.otherAttackTarget}`;
htmlText = `${this.title}`; htmlText = `${this.title}`;
} else { } else {
htmlText = `${this.mob.description}`; htmlText = `${this.mob.description}`;

View File

@ -38,10 +38,10 @@ export class MD2InitService {
const levelInfo = mobInfo.mobLevelInfos[j]; const levelInfo = mobInfo.mobLevelInfos[j];
switch (mobInfo.type) { switch (mobInfo.type) {
case MobType.Mob: case MobType.Mob:
this.md2Service.mobDeck.AddItem(new MobInfo({ name: mobInfo.name, level: levelInfo.level, drawingWeight: 1 })); this.md2Service.mobDeck.AddItem(new MobInfo({ id: mobInfo.id, name: mobInfo.name, from: mobInfo.from, level: levelInfo.level, drawingWeight: 1 }));
break; break;
case MobType.RoamingMonster: case MobType.RoamingMonster:
this.md2Service.roamingMobDeck.AddItem(new MobInfo({ name: mobInfo.name, level: levelInfo.level, drawingWeight: 1 })); this.md2Service.roamingMobDeck.AddItem(new MobInfo({ id: mobInfo.id, name: mobInfo.name, from: mobInfo.from, level: levelInfo.level, drawingWeight: 1 }));
break; break;
} }
} }

View File

@ -20,6 +20,7 @@ import { GameBundle, MD2DiceSet, MD2MobInfo, MD2MobSkill, MobSkillTarget } from
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { RollingBlackDice } from '../../games/massive-darkness2/massive-darkness2.model.dice'; import { RollingBlackDice } from '../../games/massive-darkness2/massive-darkness2.model.dice';
import { BossActivationComponent } from '../../games/massive-darkness2/boss-fight/boss-activation/boss-activation.component'; import { BossActivationComponent } from '../../games/massive-darkness2/boss-fight/boss-activation/boss-activation.component';
import { NbToastrService } from '@nebular/theme';
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }; const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` };
@ -79,7 +80,8 @@ export class MD2Service {
public loginUserService: LoginUserService, public loginUserService: LoginUserService,
public signalRService: SignalRService, public signalRService: SignalRService,
public dlgService: NbDialogService, public dlgService: NbDialogService,
public themeService: NbThemeService public themeService: NbThemeService,
public toastrService: NbToastrService
) { ) {
this.darknessPhaseRule = new CoreGameDarknessPhaseRule(); this.darknessPhaseRule = new CoreGameDarknessPhaseRule();
this.info = new MD2GameInfo(); this.info = new MD2GameInfo();
@ -133,6 +135,18 @@ export class MD2Service {
this.info.roundPhase = RoundPhase.HeroPhase; this.info.roundPhase = RoundPhase.HeroPhase;
if (!this.info.isBossFight) { if (!this.info.isBossFight) {
this.info.round++; this.info.round++;
if (this.info.round == 5 && this.info.enableHeroBetrayal) {
let betrayalHero = this.getTargetHerosByFilter(MobSkillTarget.LowestLevel, true)[0];
this.sendMsgboxMsg(betrayalHero.playerInfo.signalRClientId,
{
title: 'Dark Lords Invitation', text: 'Child of light… why pretend?<br>' +
'I have seen the hunger within you, the one your allies fear you will one day unleash.<br>' +
'Serve me openly, and the world shall kneel before you.<br>' +
'One act of betrayal—one moment of truth—and you will become the legend the others only pretend to be.', icon: ADIcon.WARNING
});
betrayalHero.uiBetrayal = true;
this.broadcastGameInfo();
}
if (this.darknessPhaseRule.runDarknessPhase()) { if (this.darknessPhaseRule.runDarknessPhase()) {
this.msgBoxService.show(`${NumberUtils.Ordinal(this.info.round)} Hero Phase`, { icon: ADIcon.INFO }); this.msgBoxService.show(`${NumberUtils.Ordinal(this.info.round)} Hero Phase`, { icon: ADIcon.INFO });
} }
@ -144,7 +158,7 @@ export class MD2Service {
//this.runNextPhase(); //this.runNextPhase();
} }
public spawnMob(isRoamingMonster: boolean, mobName: string = null) { public spawnMob(isRoamingMonster: boolean, mobId: string = null) {
let mobDeck = null as DrawingBag<MobInfo>; let mobDeck = null as DrawingBag<MobInfo>;
let level = 1; let level = 1;
if (this.highestPlayerLevel < 3) { if (this.highestPlayerLevel < 3) {
@ -159,17 +173,17 @@ export class MD2Service {
mobDeck = this.mobDeck; mobDeck = this.mobDeck;
} }
if (mobDeck.drawingItems.filter(m => (m as MobInfo).level == level) if (mobDeck.drawingItems.filter(m => (m as MobInfo).level == level && this.info.enabledBundles.includes((m as MobInfo).from))
.map(d => d.drawingWeight).reduce((a, b) => a + b, 0) == 0) { .map(d => d.drawingWeight).reduce((a, b) => a + b, 0) == 0) {
mobDeck.RestoreRemoveItems(); mobDeck.RestoreRemoveItems();
} }
let newSpawnMob = null as MobInfo; let newSpawnMob = null as MobInfo;
if (mobName) { if (mobId) {
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && m.name == mobName)[0]); newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && m.id == mobId)[0]);
} else { } else {
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level)[0]); newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && this.info.enabledBundles.includes(m.from))[0]);
} }
let exitingMob = isRoamingMonster ? this.roamingMonsters.find(m => m.name == newSpawnMob.name) : this.mobs.find(m => m.name == newSpawnMob.name); let exitingMob = isRoamingMonster ? this.roamingMonsters.find(m => m.name == newSpawnMob.name) : this.mobs.find(m => m.name == newSpawnMob.name);
@ -224,15 +238,15 @@ export class MD2Service {
mobInfo.skills = mobInfo.skills.sort((a, b) => b.seq - a.seq); mobInfo.skills = mobInfo.skills.sort((a, b) => b.seq - a.seq);
switch (dbMobInfo.type) { switch (dbMobInfo.type) {
case MobType.Mob: case MobType.Mob:
mobInfo.leaderImgUrl = !!mobInfo.leaderImgUrl ? MD2_IMG_URL(mobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`); mobInfo.leaderImgUrl = !!dbMobInfo.leaderImgUrl ? this.imgUrl(dbMobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`);
mobInfo.minionImgUrl = !!mobInfo.minionImgUrl ? MD2_IMG_URL(mobInfo.minionImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`); mobInfo.minionImgUrl = !!dbMobInfo.minionImgUrl ? this.imgUrl(dbMobInfo.minionImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`);
break; break;
case MobType.RoamingMonster: case MobType.RoamingMonster:
mobInfo.leaderImgUrl = !!mobInfo.leaderImgUrl ? MD2_IMG_URL(mobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/RoamingMonsters/${mobInfo.name}/Stand.png`); mobInfo.leaderImgUrl = !!dbMobInfo.leaderImgUrl ? this.imgUrl(dbMobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/RoamingMonsters/${mobInfo.name}/Stand.png`);
mobInfo.minionImgUrl = null; mobInfo.minionImgUrl = null;
break; break;
case MobType.Boss: case MobType.Boss:
mobInfo.leaderImgUrl = !!mobInfo.leaderImgUrl ? MD2_IMG_URL(mobInfo.leaderImgUrl) : MD2_IMG_URL(`/Boss/${mobInfo.name}-Stand.png`); mobInfo.leaderImgUrl = !!dbMobInfo.leaderImgUrl ? this.imgUrl(dbMobInfo.leaderImgUrl) : this.imgUrl(`/Boss/${mobInfo.name}-Stand.png`);
mobInfo.minionImgUrl = null; mobInfo.minionImgUrl = null;
break; break;
default: default:
@ -632,7 +646,6 @@ export class MD2Service {
actionName: actionName, actionName: actionName,
parameters: parameters parameters: parameters
} as SignalRMessage; } as SignalRMessage;
if (sessionId == null) { if (sessionId == null) {
message.receiver = { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient message.receiver = { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient
} else { } else {
@ -698,7 +711,9 @@ export class MD2Service {
let actionHtml = ''; let actionHtml = '';
let beenAttackedHero = [] as MD2HeroInfo[]; let beenAttackedHero = [] as MD2HeroInfo[];
let bossAction: MD2MobSkill; let bossAction: MD2MobSkill;
bossAction = this.info.boss.skills.find(s => s.type == MobSkillType.ActiveSkill && s.skillRoll == actionResult.claws); bossAction = new MD2MobSkill(this.info.boss.skills.find(s => s.type == MobSkillType.ActiveSkill && s.skillRoll == actionResult.claws));
bossAction.description = this.skillParse(bossAction.description, bossAction.skillTarget);
return this.dlgService.open(BossActivationComponent, { context: { boss: this.info.boss, bossAction: bossAction, currentAction: this.bossActivatedTimes, allActions: this.info.boss.actions } }).onClose; return this.dlgService.open(BossActivationComponent, { context: { boss: this.info.boss, bossAction: bossAction, currentAction: this.bossActivatedTimes, allActions: this.info.boss.actions } }).onClose;
@ -790,7 +805,6 @@ export class MD2Service {
// Use the first hero as targetHero for replacements // Use the first hero as targetHero for replacements
const targetHero = targetHeros[0]; const targetHero = targetHeros[0];
let result = skillHtmlContent;
// Replace targetHero properties // Replace targetHero properties
result = result.replace(/\btargetHero\.name\b/g, String(targetHero?.heroFullName || 0)); result = result.replace(/\btargetHero\.name\b/g, String(targetHero?.heroFullName || 0));
@ -824,7 +838,7 @@ export class MD2Service {
// Check if expression still contains letters (variables that weren't replaced) // Check if expression still contains letters (variables that weren't replaced)
if (/[a-zA-Z_]/.test(sanitizedExpression)) { if (/[a-zA-Z_]/.test(sanitizedExpression)) {
// Variables weren't replaced, return original match // Variables weren't replaced, return original match
return match; return match.replace('${', '').replace('}', '');
} }
// Validate expression contains only safe mathematical characters // Validate expression contains only safe mathematical characters

View File

@ -26,7 +26,6 @@ p {
} }
button, button,
a,
input { input {
touch-action: manipulation; touch-action: manipulation;
} }