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();
handleSignalRCallback(message: SignalRMessage): void {
console.log('handleSignalRCallback', message);
if (message.from) {
if (message.from.isGroup) {
if (!this.isHeroDashboard) return;
@ -146,6 +147,8 @@ export abstract class MD2Base {
break;
case 'update':
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);
let playerHero = this.md2Service.heros.find(h => h.playerInfo.tabId == this.stateService.loginUserService.sessionTabId);
if (playerHero) {
@ -154,6 +157,13 @@ export abstract class MD2Base {
playerHero.playerInfo.isDisconnected = false;
this.md2Service.playerHero = playerHero;
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();
}
@ -172,9 +182,11 @@ export abstract class MD2Base {
}
break;
case 'sendJoinInfo':
//When hero join the game, or reconnect to the game will receive this message
if (this.isHeroDashboard && this.md2Service.playerHero) {
this.md2Service.playerHero.playerInfo.signalRClientId = message.parameters['signalrconnid'];
this.md2Service.broadcastMyHeroInfo();
//Send fetch game info to the hero
this.md2Service.broadcastFetchGameInfo();
}
break;
default:

View File

@ -2,6 +2,7 @@ import { Component, OnInit, Input } from '@angular/core';
import { NbDialogRef } from '@nebular/theme';
import { GameBundle } from '../massive-darkness2.db.model';
import { MD2Service } from '../../../services/MD2/md2.service';
import { StringUtils } from '../../../utilities/string-utils';
export interface GameInitConfig {
enabledBundles: GameBundle[];
@ -23,12 +24,7 @@ export class GameInitDlgComponent implements OnInit {
enableMobSpecialRule: boolean = false;
enableHeroBetrayal: boolean = false;
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' }
];
bundleOptions = [];
constructor(
private dlgRef: NbDialogRef<GameInitDlgComponent>,
@ -36,6 +32,10 @@ export class GameInitDlgComponent implements OnInit {
) { }
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
if (this.initialConfig) {
this.enabledBundles = [...this.initialConfig.enabledBundles];

View File

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

View File

@ -57,7 +57,9 @@
({{md2Service.highestPlayerLevel}})</label>
</div> -->
<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>
<span class="badge badge-primary mr-1"
(click)="adjustHeroValue(hero,'level')">Lv.:{{hero.level}}</span>

View File

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

View File

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

View File

@ -21,7 +21,7 @@ export class MD2Logic {
break;
case MobSkillTarget.LeastMp:
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.';
break;
@ -36,10 +36,15 @@ export class MD2Logic {
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
break;
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);
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
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:
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 { MD2Clone } from "./factorys/md2-clone";
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)}` : '')}` }
export enum MobDlgType {
@ -307,6 +307,8 @@ export class MobInfo implements IDrawingItem {
this.drawingWeight = 1;
this.unitRemainHp = config.hp
}
id: string;
from: GameBundle;
type: MobType = MobType.Mob;
imageUrl: string
standUrl: string
@ -429,7 +431,7 @@ export class MD2HeroInfo {
uiShowExtraToken2 = false;
uiBossFight = false;
uiShowAttackBtn = false;
uiBetrayal = false;
public get heroFullName(): string {
return `${this.playerInfo.name} (${HeroClass[this.class]} - ${this.name})`
}

View File

@ -18,10 +18,11 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
MD2Icon = MD2Icon;
public gridData: GridDataResult = { data: [], total: 0 };
private allData: MD2HeroProfile[] = [];
private lastSelectedHero: MD2HeroProfile;
private lastSelectedHeroClass: HeroClass;
public gridState: State = {
skip: 0,
take: 10,
take: 1000,
sort: [{
field: 'title',
dir: 'asc'
@ -77,6 +78,11 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
public addHandler(): void {
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({
title: 'Add New Hero Profile',
content: MD2HeroProfileEditorComponent,
@ -95,6 +101,7 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
dialogRef.result.subscribe((result: MD2HeroProfile) => {
if (result) {
this.lastSelectedHero = result;
this.lastSelectedHeroClass = result.heroClass;
this.loadData();
}
@ -108,7 +115,7 @@ export class MD2HeroProfileMaintenanceComponent implements OnInit {
width: '90vw',
height: 600
});
this.lastSelectedHero = dataItem;
const editor = dialogRef.content.instance;
editor.isAdding = false;
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 { MD2Base, MD2ComponentBase } from '../MD2Base';
import { SpawnMobDlgComponent } from './spawn-mob-dlg/spawn-mob-dlg.component';
import { GameBundle } from '../massive-darkness2.db.model';
@Component({
selector: 'md2-mobs',
@ -89,21 +90,23 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
spawnSpecificMob() {
let mobOptions = this.isRoamingMonster ?
this.md2Service.allRoamingMonsterInfos.map(f => new DropDownOption(f.name, f.name)) :
this.md2Service.allMobInfos.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.id, `${StringUtils.camelToTitle(GameBundle[f.from])} - ${f.name}`));
mobOptions = mobOptions.sort((a, b) => a.value1.localeCompare(b.value1));
this.msgBoxService.showInputbox('Spawn', '',
{
inputType: 'dropdown', dropDownOptions: mobOptions,
buttons: ADButtons.YesNoCancel,
confirmButtonText: 'Spawn',
cancelButtonText: 'Random'
}).pipe(first()).subscribe(mobName => {
}).pipe(first()).subscribe(mobId => {
if (mobName || mobName === false) {
if (mobId || mobId === false) {
if (!mobName) { mobName = null; }
let result = this.md2Service.spawnMob(this.isRoamingMonster, mobName);
if (!mobId) { mobId = null; }
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 actType = result.exitingMob == null ? MobDlgType.Spawn : MobDlgType.Activating;
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 { MobSkillType } from '../../massive-darkness2.model.boss';
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 };
@ -31,7 +32,7 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
MD2Icon = MD2Icon;
mob: MobInfo;
actionInfoHtml: string;
beenAttackedHero = [] as MD2HeroInfo[];
beenAttackedHero = null as MD2HeroInfo;
attackTarget: string;
otherAttackTarget: string;
constructor(
@ -157,50 +158,45 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
if (this.mode == MobDlgType.Spawn) {
htmlText = `${this.mob.description} Shows Up`;
} else if (this.mode == MobDlgType.Activating) {
let targetType = null as AttackTarget;
let randomAttack = Math.random() * AttackTarget.LowestLevel;
const values = Object.values(AttackTarget);
let targetType = null as MobSkillTarget;
let randomAttack = Math.random() * MobSkillTarget.HighestLevel;
const values = Object.values(MobSkillTarget);
const stringKeys = Object
.keys(AttackTarget)
.keys(MobSkillTarget)
.filter((v) => isNaN(Number(v)))
for (let i = 0; i < stringKeys.length; i++) {
const element = AttackTarget[stringKeys[i]];
const element = MobSkillTarget[stringKeys[i]];
if (element >= randomAttack) {
targetType = element;
break;
}
}
this.beenAttackedHero = MD2Logic.getTargetHeroByFilter(this.md2Service.heros, targetType);
switch (targetType) {
case AttackTarget.LeastHp:
let lowestHp = Math.min(...this.md2Service.heros.map(h => h.hp));
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == lowestHp);
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
case MobSkillTarget.LeastHp:
this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
break;
case AttackTarget.HighestHp:
let highestHp = Math.max(...this.md2Service.heros.map(h => h.hp));
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == highestHp);
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
case MobSkillTarget.HighestHp:
this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
break;
case AttackTarget.HighestMp:
let highestMp = Math.max(...this.md2Service.heros.map(h => h.mp));
this.beenAttackedHero = this.md2Service.heros.filter(h => h.mp == highestMp);
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
case MobSkillTarget.HighestMp:
this.otherAttackTarget = 'attacking the other <b>Highest Mana</b> hero.';
break;
case AttackTarget.LowestLevel:
let lowestLevel = Math.max(...this.md2Service.heros.map(h => h.level));
this.beenAttackedHero = this.md2Service.heros.filter(h => h.level == lowestLevel);
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
case MobSkillTarget.LowestLevel:
this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
break;
case AttackTarget.Random:
case MobSkillTarget.HighestLevel:
this.otherAttackTarget = 'attacking the other <b>Highest Level</b> hero.';
break;
case MobSkillTarget.Random:
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;
}
let attackedHeros = StringUtils.makeCommaSeparatedString(this.beenAttackedHero.map(h => this.md2Service.heroFullName(h)), true, true);
this.attackTarget = `Attacking <b>${attackedHeros}</b> first if possible.`;
//let attackedHeros = StringUtils.makeCommaSeparatedString(this.beenAttackedHero.map(h => this.md2Service.heroFullName(h)), true, true);
this.attackTarget = `Attacking <b>${this.beenAttackedHero.heroFullName}</b> first if possible, otherwise ${this.otherAttackTarget}`;
htmlText = `${this.title}`;
} else {
htmlText = `${this.mob.description}`;

View File

@ -38,10 +38,10 @@ export class MD2InitService {
const levelInfo = mobInfo.mobLevelInfos[j];
switch (mobInfo.type) {
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;
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;
}
}

View File

@ -20,6 +20,7 @@ import { GameBundle, MD2DiceSet, MD2MobInfo, MD2MobSkill, MobSkillTarget } from
import { environment } from '../../../environments/environment';
import { RollingBlackDice } from '../../games/massive-darkness2/massive-darkness2.model.dice';
import { BossActivationComponent } from '../../games/massive-darkness2/boss-fight/boss-activation/boss-activation.component';
import { NbToastrService } from '@nebular/theme';
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 signalRService: SignalRService,
public dlgService: NbDialogService,
public themeService: NbThemeService
public themeService: NbThemeService,
public toastrService: NbToastrService
) {
this.darknessPhaseRule = new CoreGameDarknessPhaseRule();
this.info = new MD2GameInfo();
@ -133,6 +135,18 @@ export class MD2Service {
this.info.roundPhase = RoundPhase.HeroPhase;
if (!this.info.isBossFight) {
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()) {
this.msgBoxService.show(`${NumberUtils.Ordinal(this.info.round)} Hero Phase`, { icon: ADIcon.INFO });
}
@ -144,7 +158,7 @@ export class MD2Service {
//this.runNextPhase();
}
public spawnMob(isRoamingMonster: boolean, mobName: string = null) {
public spawnMob(isRoamingMonster: boolean, mobId: string = null) {
let mobDeck = null as DrawingBag<MobInfo>;
let level = 1;
if (this.highestPlayerLevel < 3) {
@ -159,17 +173,17 @@ export class MD2Service {
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) {
mobDeck.RestoreRemoveItems();
}
let newSpawnMob = null as MobInfo;
if (mobName) {
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && m.name == mobName)[0]);
if (mobId) {
newSpawnMob = new MobInfo(mobDeck.DrawAndRemove(1, m => m.level == level && m.id == mobId)[0]);
} 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);
@ -224,15 +238,15 @@ export class MD2Service {
mobInfo.skills = mobInfo.skills.sort((a, b) => b.seq - a.seq);
switch (dbMobInfo.type) {
case MobType.Mob:
mobInfo.leaderImgUrl = !!mobInfo.leaderImgUrl ? MD2_IMG_URL(mobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`);
mobInfo.minionImgUrl = !!mobInfo.minionImgUrl ? MD2_IMG_URL(mobInfo.minionImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`);
mobInfo.leaderImgUrl = !!dbMobInfo.leaderImgUrl ? this.imgUrl(dbMobInfo.leaderImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`);
mobInfo.minionImgUrl = !!dbMobInfo.minionImgUrl ? this.imgUrl(dbMobInfo.minionImgUrl) : MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`);
break;
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;
break;
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;
break;
default:
@ -632,7 +646,6 @@ export class MD2Service {
actionName: actionName,
parameters: parameters
} as SignalRMessage;
if (sessionId == null) {
message.receiver = { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient
} else {
@ -698,7 +711,9 @@ export class MD2Service {
let actionHtml = '';
let beenAttackedHero = [] as MD2HeroInfo[];
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;
@ -790,7 +805,6 @@ export class MD2Service {
// Use the first hero as targetHero for replacements
const targetHero = targetHeros[0];
let result = skillHtmlContent;
// Replace targetHero properties
result = result.replace(/\btargetHero\.name\b/g, String(targetHero?.heroFullName || 0));
@ -824,7 +838,7 @@ export class MD2Service {
// Check if expression still contains letters (variables that weren't replaced)
if (/[a-zA-Z_]/.test(sanitizedExpression)) {
// Variables weren't replaced, return original match
return match;
return match.replace('${', '').replace('}', '');
}
// Validate expression contains only safe mathematical characters

View File

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