Compare commits

..

5 Commits

Author SHA1 Message Date
Chris Chen
f30c41afba Optmize 2025-11-13 15:44:39 -08:00
Chris Chen
2ef9968920 WIP 2025-11-12 18:22:33 -08:00
Chris Chen
d8db9f650b WIP 2025-11-06 21:47:04 -08:00
Chris Chen
349510db56 WIP 2025-11-06 16:58:36 -08:00
Chris Chen
b44834343a WIP 2025-11-06 07:10:28 -08:00
46 changed files with 993 additions and 618 deletions

View File

@ -20,7 +20,7 @@ export interface LoginTokenViewModel {
avatarImage: string; avatarImage: string;
role: Role; role: Role;
cellGroup: PastoralDomain; cellGroup: PastoralDomain;
signalRSessionId; signalRConnectionId;
sessionTabId: string; sessionTabId: string;
} }

View File

@ -5,6 +5,7 @@ export interface IGamePlayer {
isPlayer: boolean; isPlayer: boolean;
signalRClientId: string; signalRClientId: string;
tabId: string; tabId: string;
isDisconnected: boolean;
} }
export class GamePlayer implements IGamePlayer { export class GamePlayer implements IGamePlayer {
@ -14,4 +15,5 @@ export class GamePlayer implements IGamePlayer {
isPlayer: boolean; isPlayer: boolean;
signalRClientId: string; signalRClientId: string;
tabId: string; tabId: string;
isDisconnected: boolean;
} }

View File

@ -7,6 +7,8 @@ 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";
import { MD2HeroInfo, MD2Icon, MobInfo, RoundPhase } from "./massive-darkness2.model"; import { MD2HeroInfo, MD2Icon, MobInfo, RoundPhase } from "./massive-darkness2.model";
import { LoginUserService } from "../../services/login-user.service";
import { GamePlayer } from "../games.model";
@Injectable() @Injectable()
export abstract class MD2Base { export abstract class MD2Base {
@ -58,14 +60,14 @@ export abstract class MD2Base {
} }
imgUrl(imgPath: string) { imgUrl(imgPath: string) {
return this.md2Service.stateService.imgUrl(imgPath); return this.md2Service.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.stateService.iconHtml(icon, cssClass); return this.md2Service.iconHtml(icon, cssClass);
} }
imgHtml(imgFile: string, cssClass = '') { imgHtml(imgFile: string, cssClass = '') {
@ -81,12 +83,17 @@ export abstract class MD2Base {
} }
abstract refreshUI(); abstract refreshUI();
handleSignalRCallback(message: SignalRMessage): void { handleSignalRCallback(message: SignalRMessage): void {
// if (message.from.isGroup) { if (message.from) {
// if (!this.isHeroDashboard) return; if (message.from.isGroup) {
// } else { if (!this.isHeroDashboard) return;
// if (this.isHeroDashboard && this.md2Service.playerHero.playerInfo.signalRClientId == message.from.sessionId) return; } else {
// } if (this.isHeroDashboard && this.md2Service.playerHero?.playerInfo?.signalRClientId == message.from.connectionId) return;
}
}
if (!this.isHeroDashboard) {
}
switch (message.actionType) { switch (message.actionType) {
case 'hero': case 'hero':
let heroInfo = new MD2HeroInfo(JSON.parse(message.parameters['hero'])); let heroInfo = new MD2HeroInfo(JSON.parse(message.parameters['hero']));
@ -95,43 +102,12 @@ export abstract class MD2Base {
this.md2Service.heros.push(heroInfo); this.md2Service.heros.push(heroInfo);
break; break;
case 'update': case 'update':
let exitingHero = this.md2Service.heros.find(h => h.playerInfo.signalRClientId == heroInfo.playerInfo.signalRClientId); this.updateHeroInfo(heroInfo);
if (exitingHero) {
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 {
this.md2Service.heros.push(heroInfo);
}
if (!this.isHeroDashboard) {
if (this.gameInfo.roundPhase == RoundPhase.HeroPhase) {
if (!this.md2Service.heros.some(h => h.remainActions > 0) && !this.md2Service.heros.some(h => h.uiActivating)) {
if (!this.md2Service.info.isBossFight) {
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();
}
}
}
}
}
//Object.assign(heroInfo, exitingHero); //Object.assign(heroInfo, exitingHero);
break; break;
case 'updateMyHero': case 'updateMyHero':
if (this.isHeroDashboard) { if (this.isHeroDashboard) {
this.md2Service.stateService.playerHero = heroInfo; this.md2Service.playerHero = heroInfo;
} }
break; break;
@ -140,15 +116,45 @@ export abstract class MD2Base {
} }
this.detectChanges(); this.detectChanges();
break; break;
case 'heroes':
switch (message.actionName) {
case 'updateAll':
if (this.isHeroDashboard) {
let allHeroes = (JSON.parse(message.parameters['heros']) as MD2HeroInfo[]).map(h => new MD2HeroInfo(h));
//Remove heroes that are not in the list
this.md2Service.info.heros = this.md2Service.heros.filter(h => allHeroes.some(h2 => h2.playerInfo.tabId == h.playerInfo.tabId));
allHeroes.forEach(heroInfo => {
this.updateHeroInfo(heroInfo);
});
this.detectChanges();
}
break;
}
break;
case 'GameRoom': case 'GameRoom':
switch (message.actionName) { switch (message.actionName) {
case 'Leaving': case 'Leaving':
this.md2Service.heros.splice(this.md2Service.heros.findIndex(h => h.playerInfo.tabId == message.from.sessionId)); let leavingPlayerInfo = message.value as GamePlayer;
let leavingHero = this.md2Service.heros.find(h => h.playerInfo.tabId == leavingPlayerInfo.tabId);
if (leavingHero) {
leavingHero.playerInfo.isDisconnected = true;
}
//var disconnectHero = this.md2Service.heros.splice(this.md2Service.heros.findIndex(h => h.playerInfo.signalRClientId == leavingPlayerInfo.signalRClientId));
//this.md2Service.info.disconnectedHeroes.push(...disconnectHero);
this.detectChanges(); this.detectChanges();
break; break;
case 'update': case 'update':
if (this.isHeroDashboard) { if (this.isHeroDashboard) {
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);
if (playerHero) {
playerHero.playerInfo = this.md2Service.gameRoomService.currentPlayer();
playerHero.playerInfo.isDisconnected = false;
this.md2Service.playerHero = playerHero;
this.md2Service.broadcastMyHeroInfo();
}
this.detectChanges(); this.detectChanges();
} }
break; break;
@ -158,10 +164,17 @@ export abstract class MD2Base {
this.detectChanges(); this.detectChanges();
} }
break; break;
case 'getGameInfo':
if (!this.isHeroDashboard) {
this.md2Service.broadcastGameInfo();
}
break;
case 'sendJoinInfo': case 'sendJoinInfo':
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.broadcastService.broadcastMyHeroInfo(); this.md2Service.broadcastMyHeroInfo();
} }
break; break;
default: default:
@ -229,6 +242,41 @@ export abstract class MD2Base {
break; break;
} }
} }
updateHeroInfo(heroInfo: MD2HeroInfo) {
let exitingHero = this.md2Service.heros.find(h => h.playerInfo.tabId == heroInfo.playerInfo.tabId);
if (exitingHero) {
//For boss fight, if the hero finished activating, activate the boss
let activateBoss = exitingHero.uiActivating && !heroInfo.uiActivating;
this.md2Service.heros[this.md2Service.heros.indexOf(exitingHero)] = heroInfo;
//My hero update
if (this.isHeroDashboard && this.md2Service.loginUserService.sessionTabId == heroInfo.playerInfo.tabId) {
this.md2Service.playerHero = heroInfo;
}
if (!this.isHeroDashboard && this.md2Service.info.isBossFight && activateBoss) {
this.md2Service.activateBoss();
}
} else {
this.md2Service.heros.push(heroInfo);
}
if (!this.isHeroDashboard) {
if (this.gameInfo.roundPhase == RoundPhase.HeroPhase) {
if (!this.md2Service.heros.some(h => h.remainActions > 0) && !this.md2Service.heros.some(h => h.uiActivating)) {
if (!this.md2Service.info.isBossFight) {
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();
}
}
}
}
}
}
abstract heroAction(hero: MD2HeroInfo, action: string); abstract heroAction(hero: MD2HeroInfo, action: string);
} }
@ -260,14 +308,14 @@ export abstract class MD2ComponentBase {
this.destroy$.complete(); this.destroy$.complete();
} }
imgUrl(imgPath: string) { imgUrl(imgPath: string) {
return this.md2Service.stateService.imgUrl(imgPath); return this.md2Service.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.stateService.iconHtml(icon, cssClass); return this.md2Service.iconHtml(icon, cssClass);
} }
detectChanges() { detectChanges() {
if (!this.cdRef['destroyed']) { if (!this.cdRef['destroyed']) {

View File

@ -11,7 +11,7 @@
<div class="col-md-7"> <div class="col-md-7">
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<adj-number-input name="mob{{boss.info.name}}" [(ngModel)]="boss.info.unitRemainHp" minimum="0" <adj-number-input name="mob{{boss.info.name}}" [(ngModel)]="boss.info.hp" minimum="0"
class="mb-3" title="Boss HP" (hitMinimum)="WIN()"> class="mb-3" title="Boss HP" (hitMinimum)="WIN()">
</adj-number-input> </adj-number-input>
<md2-mob-attack-info [mob]="boss.info"> <md2-mob-attack-info [mob]="boss.info">

View File

@ -52,7 +52,11 @@ export class BossFightComponent extends MD2ComponentBase {
this.boss.activating(); this.boss.activating();
} }
WIN() { WIN() {
this.msgBoxService.show('Win', { text: 'You Win the Boss Fight', icon: ADIcon.INFO });
this.md2Service.info.isBossFight = false;
this.md2Service.info.boss = undefined;
this.md2Service.heros.forEach(h => h.uiBossFight = false);
this.md2Service.broadcastGameInfo();
} }
attack(mob: MobInfo) { attack(mob: MobInfo) {
@ -62,6 +66,9 @@ export class BossFightComponent extends MD2ComponentBase {
let attackDamage = mobResult.uiWounds; let attackDamage = mobResult.uiWounds;
if (attackDamage) { if (attackDamage) {
this.boss.info.hp -= attackDamage; this.boss.info.hp -= attackDamage;
if (this.boss.info.hp <= 0) {
this.WIN();
}
this.cdRef.detectChanges(); this.cdRef.detectChanges();
} }
} }

View File

@ -8,99 +8,6 @@ import { MobSkillType } from "../../massive-darkness2.model.boss";
import { MD2DiceSet, MD2MobSkill } from "../../massive-darkness2.db.model"; import { MD2DiceSet, MD2MobSkill } from "../../massive-darkness2.db.model";
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)}` : '')}` }
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: { blue: 2, black: 1 } as MD2DiceSet,
}),
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: { blue: 3, black: 1 } as MD2DiceSet,
}),
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: { blue: 5, black: 1 } as MD2DiceSet,
}),
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: { blue: 1, black: 1 } as MD2DiceSet,
}),
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: { blue: 2, black: 1 } as MD2DiceSet,
}),
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: { blue: 4, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'Lyidan, Incubus Lord', level: 1, hp: 7,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1, 0, 2)],
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'Lyidan, Incubus Lord', level: 3, hp: 10,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 2, 0, 1)],
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'Lyidan, Incubus Lord', level: 5, hp: 12,
attackInfos: [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)],
defenseInfo: { blue: 4, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'The Ghoul', level: 1, hp: 5,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1, 0, 1)],
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'The Ghoul', level: 3, hp: 8,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 2, 0, 2)],
defenseInfo: { blue: 3, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'The Ghoul', level: 5, hp: 10,
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 3, 0, 3)],
defenseInfo: { blue: 4, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'Balrog', level: 1, hp: 5,
attackInfos: [
new AttackInfo(MD2Icon.Magic, 0, 1, 0, 2),
],
defenseInfo: { blue: 2, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'Balrog', level: 3, hp: 8,
attackInfos: [
new AttackInfo(MD2Icon.Magic, 0, 2, 0, 2),
],
defenseInfo: { blue: 3, black: 1 } as MD2DiceSet,
}),
new MobInfo({
name: 'Balrog', level: 5, hp: 10,
attackInfos: [
new AttackInfo(MD2Icon.Magic, 0, 3, 0, 2),
],
defenseInfo: { blue: 4, black: 1 } as MD2DiceSet,
}),
]
export abstract class CoreGameRMFactory implements IMobFactory { export abstract class CoreGameRMFactory implements IMobFactory {
abstract mobName: string; abstract mobName: string;
@ -192,7 +99,7 @@ export class RMUndeadQueenFactory extends CoreGameRMFactory {
} }
}); });
} }
});; });
} }
this.mob.skills = [ this.mob.skills = [

View File

@ -126,9 +126,10 @@
</div> </div>
<div class="col-6 hero-skills-col"> <div class="col-6 hero-skills-col">
<div class="hero-skills"> <div class="hero-skills">
<div class="skills-title">Abilities</div> <div class="skills-title" (click)="showSkills('abilities')">Abilities</div>
<div class="skill-content" [innerHTML]="hero.skillHtml"></div> <div class="skill-content" [innerHTML]="hero.skillHtml"></div>
<div class="skills-title shadow-skills-title">Shadow Abilities</div> <div class="skills-title shadow-skills-title" (click)="showSkills('shadow')">
Shadow Abilities</div>
<div class="skill-content shadow-skill-content" <div class="skill-content shadow-skill-content"
[innerHTML]="hero.shadowSkillHtml"></div> [innerHTML]="hero.shadowSkillHtml"></div>
</div> </div>
@ -138,7 +139,7 @@
<!-- <img class="MD2HeroCard " src="{{imgUrl('Heros/'+className+'.jpg')}}" (click)="toggleFlip()"> --> <!-- <img class="MD2HeroCard " src="{{imgUrl('Heros/'+className+'.jpg')}}" (click)="toggleFlip()"> -->
<!-- Action Buttons (Desktop/Landscape) --> <!-- Action Buttons (Desktop/Landscape) -->
<div class="hero-actions d-none d-sm-block"> <div class="hero-actions d-block">
<div class="action-buttons-group" *ngIf="hero.uiActivating && hero.remainActions > 0"> <div class="action-buttons-group" *ngIf="hero.uiActivating && hero.remainActions > 0">
<button nbButton hero class="action-btn" status="info" (click)="moveAction()" <button nbButton hero class="action-btn" status="info" (click)="moveAction()"
*ngIf="!showMoveAction"> *ngIf="!showMoveAction">

View File

@ -810,3 +810,6 @@
-ms-transform: rotateY(-180deg); -ms-transform: rotateY(-180deg);
transform: rotateY(-180deg); transform: rotateY(-180deg);
} }
::ng-deep .skill-content .MD2Icon {
font-size: 30px;
}

View File

@ -14,6 +14,7 @@ import { DebounceTimer } from '../../../utilities/timer-utils';
import { HeroClass, MD2HeroInfo, MD2HeroProfile, MD2Icon } from '../massive-darkness2.model'; import { HeroClass, MD2HeroInfo, MD2HeroProfile, MD2Icon } from '../massive-darkness2.model';
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';
@Component({ @Component({
selector: 'ngx-hero-dashboard', selector: 'ngx-hero-dashboard',
@ -55,6 +56,10 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
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'), new DropDownOption(HeroClass.Druid, 'Druid'),
new DropDownOption(HeroClass.Necromancer, 'Necromancer'),
new DropDownOption(HeroClass.Monk, 'Monk'),
new DropDownOption(HeroClass.Thinker, 'Thinker'),
new DropDownOption(HeroClass.Bard, 'Bard'),
]; ];
heros = [] as MD2HeroInfo[]; heros = [] as MD2HeroInfo[];
heroProfiles: MD2HeroProfile[] = []; heroProfiles: MD2HeroProfile[] = [];
@ -66,6 +71,15 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
return this.md2Service.playerHero; return this.md2Service.playerHero;
} }
public get className() {
if (this.md2Service.playerHero) {
return HeroClass[this.md2Service.playerHero.class];
}
if (this.selectedHeroClass) {
return HeroClass[this.selectedHeroClass];
}
return '';
}
public get currentSelectingHero(): MD2HeroInfo { public get currentSelectingHero(): MD2HeroInfo {
return this.heros[this.currentHeroIndex]; return this.heros[this.currentHeroIndex];
} }
@ -78,6 +92,7 @@ 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
) { ) {
super(md2Service, stateService, route, cdRef); super(md2Service, stateService, route, cdRef);
this.isHeroDashboard = true; this.isHeroDashboard = true;
@ -89,6 +104,15 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit(); super.ngOnInit();
this.gameRoomService.gameRoomId = this.roomId;
this.gameRoomService.joinGameRoom(this.roomId);
//this.fetchGameInfo();
this.signalRService.signalRMessageConnSubject.subscribe(state => {
if (state.status == 'connected') {
this.fetchGameInfo();
}
});
} }
override signalRInitialized() { override signalRInitialized() {
@ -99,8 +123,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
// } // }
} }
initHero() { initHero() {
this.gameRoomService.gameRoomId = this.roomId; if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRConnectionId)) {
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRSessionId)) {
this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' }) this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' })
.pipe(first()).subscribe(heroClass => { .pipe(first()).subscribe(heroClass => {
@ -122,10 +145,11 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
}); });
} }
} }
className: string;
initClassHeroList(heroClass: HeroClass) { initClassHeroList(heroClass: HeroClass) {
this.heros = []; this.heros = [];
this.className = HeroClass[heroClass];
this.selectedHeroClass = heroClass; this.selectedHeroClass = heroClass;
this.heroProfileService.getAll().pipe(first()).subscribe(result => { this.heroProfileService.getAll().pipe(first()).subscribe(result => {
@ -139,7 +163,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
hp: heroProfile.hp, hp: heroProfile.hp,
mp: heroProfile.mana, mp: heroProfile.mana,
skillHtml: heroProfile.skillHtml, skillHtml: heroProfile.skillHtml,
shadowSkillHtml: heroProfile.shadowSkillHtml, shadowSkillHtml: heroProfile.shadowSkillHtml.replace("<p>", '<p>' + this.iconHtml(MD2Icon.Shadow) + ' : '),
class: heroClass class: heroClass
}); });
heroInfo.imgUrl = this.imgUrl('Heros/' + HeroClass[heroClass] + '.jpg'); heroInfo.imgUrl = this.imgUrl('Heros/' + HeroClass[heroClass] + '.jpg');
@ -156,12 +180,18 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
selectCurrentHero() { selectCurrentHero() {
if (this.currentSelectingHero) { if (this.currentSelectingHero) {
this.md2Service.playerJoin(this.currentSelectingHero); this.md2Service.playerJoin(this.currentSelectingHero);
this.md2Service.broadcastMyHeroInfo();
this.isSelectingHero = false; this.isSelectingHero = false;
this.detectChanges(); this.detectChanges();
this.gameRoomService.joinGameRoom(this.roomId);
} }
} }
showSkills(type: string) {
if (type == 'abilities') {
this.msgBoxService.show('Abilities', { text: this.currentSelectingHero.skillHtml });
} else {
this.msgBoxService.show('Shadow Abilities', { text: this.currentSelectingHero.shadowSkillHtml });
}
}
nextHero() { nextHero() {
this.currentHeroIndex++; this.currentHeroIndex++;
if (this.currentHeroIndex >= this.heros.length) { if (this.currentHeroIndex >= this.heros.length) {
@ -178,8 +208,12 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
this.detectChanges(); this.detectChanges();
} }
fetchGameInfo() {
this.md2Service.broadcastFetchGameInfo();
}
broadcastHeroInfo() { broadcastHeroInfo() {
this.md2Service.broadcastService.broadcastMyHeroInfo(); this.md2Service.broadcastMyHeroInfo();
this.heroUpdateDebounceTimer.clearOut(); this.heroUpdateDebounceTimer.clearOut();
} }
increaseRage() { increaseRage() {
@ -188,8 +222,8 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
} }
} }
openDoor() { openDoor() {
this.md2Service.broadcastService.broadcastHeroAction('openDoor'); this.md2Service.broadcastHeroAction('openDoor');
this.showMoveAction = false; //this.showMoveAction = false;
this.detectChanges(); this.detectChanges();
} }
moveAction() { moveAction() {
@ -213,7 +247,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
default: default:
break; break;
} }
this.md2Service.broadcastService.broadcastHeroAction(action); this.md2Service.broadcastHeroAction(action);
this.reduceAction(); this.reduceAction();
} }
reduceAction() { reduceAction() {

View File

@ -59,23 +59,32 @@
<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'>{{hero.playerInfo.name}} ({{heroClassName(hero)}} -
{{hero.name}})</label> {{hero.name}})</label>
<span class="badge badge-primary mr-1">Lv.:{{hero.level}}</span> <span class="badge badge-primary mr-1"
<span class="badge badge-primary mr-1">HP: {{hero.hp}}/{{hero.hpMaximum}}</span> (click)="adjustHeroValue(hero,'level')">Lv.:{{hero.level}}</span>
<span class="badge badge-primary mr-1">Mana: {{hero.mp}}/{{hero.mpMaximum}}</span> <span class="badge badge-primary mr-1" (click)="adjustHeroValue(hero,'hp')">HP:
<span class="badge badge-success mr-1">Exp: {{hero.exp}}</span> {{hero.hp}}/{{hero.hpMaximum}}</span>
<span class="badge badge-danger mr-1" *ngIf="hero.fireToken"> <span class="badge badge-primary mr-1" (click)="adjustHeroValue(hero,'mp')">Mana:
<md2-icon icon="fire" size="sm"></md2-icon> {{hero.fireToken}} {{hero.mp}}/{{hero.mpMaximum}}</span>
<span class="badge badge-success mr-1" (click)="adjustHeroValue(hero,'exp')">Exp:
{{hero.exp}}</span>
<span class="badge mr-1" *ngIf="hero.fireToken">
<md2-icon [icon]="MD2Icon.FireToken" size="sm"></md2-icon> {{hero.fireToken}}
</span> </span>
<span class="badge badge-info mr-1" *ngIf="hero.frozenToken"> <span class="badge mr-1" *ngIf="hero.frozenToken">
<md2-icon icon="frozen" size="sm"></md2-icon>{{hero.frozenToken}} <md2-icon [icon]="MD2Icon.FrozenToken" size="sm"></md2-icon>{{hero.frozenToken}}
</span> </span>
<span class="badge badge-success mr-1" *ngIf="hero.remainActions>0">Actions: <span class="badge badge-success mr-1" *ngIf="hero.remainActions>0"
(click)="adjustHeroValue(hero,'remainActions')">Actions:
{{hero.remainActions}}</span> {{hero.remainActions}}</span>
<span class="badge badge-light mr-1" *ngIf=" !hero.uiActivating">Inactive</span> <span class="badge badge-light mr-1" *ngIf=" !hero.uiActivating"
(click)="activatingHero(hero)">Inactive</span>
<span class="badge badge-primary mr-1" *ngIf="hero.uiActivating">Activating</span> <span class="badge badge-primary mr-1" *ngIf="hero.uiActivating">Activating</span>
<span class="badge badge-warning mr-1"
*ngIf="hero.playerInfo.isDisconnected">Disconnected</span>
<!-- <span class="badge badge-success mr-1">{{hero.playerInfo.signalRClientId}}</span> --> <!-- <span class="badge badge-success mr-1">{{hero.playerInfo.signalRClientId}}</span> -->
<!-- <button nbButton hero status="primary" size="tiny" class="ml-2"
(click)="removeHero(hero)">Remove</button> --> <span class="badge badge-danger mr-1" (click)="removeHero(hero)">X
</span>
</div> </div>
</div> </div>

View File

@ -0,0 +1,3 @@
.badge {
cursor: pointer;
}

View File

@ -5,7 +5,7 @@ import { MsgBoxService } from '../../services/msg-box.service';
import { ArrayUtils } from '../../utilities/array-utils'; 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, MD2Icon } from './massive-darkness2.model';
import { MD2Service } from '../../services/MD2/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';
@ -25,6 +25,7 @@ import { NumberUtils } from '../../utilities/number-utils';
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class MassiveDarkness2Component extends MD2Base implements OnInit { export class MassiveDarkness2Component extends MD2Base implements OnInit {
MD2Icon = MD2Icon;
HeroClass = HeroClass; HeroClass = HeroClass;
constructor( constructor(
private fileService: FileService, private fileService: FileService,
@ -50,6 +51,31 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
} }
override signalRInitialized() { override signalRInitialized() {
}
adjustHeroValue(hero: MD2HeroInfo, value: string) {
this.msgBoxService.showInputbox(`Adjust ${value} for ${hero.playerInfo.name}`, `Enter the new value for ${value}`, {
inputType: 'number',
inputValue: hero[value].toString()
}).pipe(first()).subscribe(result => {
if (result) {
hero[value] = Number.parseInt(result);
this.md2Service.broadcastHeroInfoToAll(hero, true);
this.detectChanges();
}
});
}
activatingHero(hero: MD2HeroInfo) {
if (hero.remainActions > 0) {
this.msgBoxService.show('Activating', { text: `Are you sure you want to activate ${hero.playerInfo.name}?` }).pipe(first()).subscribe(result => {
if (result) {
this.md2Service.heros.forEach(h => h.uiActivating = false);
hero.uiActivating = true;
this.md2Service.broadcastAllHeroInfoToAll();
this.detectChanges();
}
});
}
} }
showQrCode() { showQrCode() {
if (this.md2Service.initialized == false) { if (this.md2Service.initialized == false) {
@ -145,10 +171,15 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
this.md2Service.heros.forEach(hero => { this.md2Service.heros.forEach(hero => {
hero.uiShowAttackBtn = this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0 || this.md2Service.info.isBossFight; hero.uiShowAttackBtn = this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0 || this.md2Service.info.isBossFight;
}); });
this.md2Service.broadcastService.broadcastAllHeroInfoToAll(); this.md2Service.broadcastAllHeroInfoToAll();
} }
removeHero(hero) { removeHero(hero) {
this.md2Service.info.heros.splice(this.md2Service.info.heros.indexOf(hero)); this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(result => {
if (result) {
this.md2Service.info.heros.splice(this.md2Service.info.heros.indexOf(hero));
this.cdRef.detectChanges();
}
});
} }

View File

@ -15,6 +15,7 @@ export enum MobSkillType {
Combat, Combat,
Passive, Passive,
ConditionalSkill, ConditionalSkill,
OtherWiseSkill,
ActiveSkill, ActiveSkill,
MeleeAttack = 15, MeleeAttack = 15,
RangeAttack, RangeAttack,
@ -52,6 +53,7 @@ export interface IBossFight {
imgUrl: string imgUrl: string
standUrl: string standUrl: string
extraRules: string extraRules: string
md2Service: MD2Service
activating(): boolean activating(): boolean
prepareForBossFight(): void prepareForBossFight(): void
darknessPhase(): void darknessPhase(): void
@ -72,7 +74,7 @@ export abstract class BossFight implements IBossFight {
extraRules: string extraRules: string
protected subscription: Subscription protected subscription: Subscription
constructor(protected md2Service: MD2Service) { constructor(public md2Service: MD2Service) {
this.rounds = 1; this.rounds = 1;
} }
activating(): boolean { activating(): boolean {
@ -104,26 +106,26 @@ export abstract class BossFight implements IBossFight {
} }
export class BossMicheal extends BossFight { export class BossMicheal extends BossFight {
constructor(protected md2Service: MD2Service) { constructor(public md2Service: MD2Service) {
super(md2Service); super(md2Service);
this.corruptionTokenHtml = this.md2Service.stateService.imgHtml('Tokens/CorruptToken.png'); this.corruptionTokenHtml = this.md2Service.imgHtml('Tokens/CorruptToken.png');
this.name = 'Michael - The Corrupted Archangel'; this.name = 'Michael - The Corrupted Archangel';
this.imgUrl = md2Service.stateService.imgUrl('/Boss/Michael - The Corrupted Archangel.jpg'); this.imgUrl = md2Service.imgUrl('/Boss/Michael - The Corrupted Archangel.jpg');
this.standUrl = md2Service.stateService.imgUrl('/Boss/Michael.png'); this.standUrl = md2Service.imgUrl('/Boss/Michael.png');
this.info = new MobInfo({ this.info = new MobInfo({
description: this.name, description: this.name,
type: MobType.Boss, type: MobType.Boss,
hpPerHero: 15, hpPerHero: 15,
level: 10, level: 10,
imageUrl: md2Service.stateService.imgUrl('/Boss/Michael.png') imageUrl: md2Service.imgUrl('/Boss/Michael.png')
}); });
if (!this.info.skills) { if (!this.info.skills) {
this.info.skills = []; this.info.skills = [];
} }
this.info.skills.push({ this.info.skills.push({
name: `Combat 1 ${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)}`, name: `Combat 1 ${this.md2Service.iconHtml(MD2Icon.EnemySkill)}`,
description: `Deal 1 Wound for each ${this.corruptionTokenHtml} on the attacking or defending Hero. Discard the tokens afterwards(once per combat).`, description: `Deal 1 Wound for each ${this.corruptionTokenHtml} on the attacking or defending Hero. Discard the tokens afterwards(once per combat).`,
type: MobSkillType.Combat, type: MobSkillType.Combat,
skillRoll: 1 skillRoll: 1
@ -134,9 +136,9 @@ export class BossMicheal extends BossFight {
this.actionBlackDice = 2; 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>` + 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)}.` + `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.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.iconHtml(MD2Icon.EnemyClaw)} the Hero takes 1 Wound.<br>If ${this.md2Service.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.` `If ${this.md2Service.iconHtml(MD2Icon.EnemyClaw)}/${this.md2Service.iconHtml(MD2Icon.EnemySkill)} the Hero takes 1 Wound and places 1 ${this.corruptionTokenHtml} on their Dashboard.`
} }
activatedTimes: number activatedTimes: number
acted: number acted: number
@ -231,26 +233,26 @@ export class BossMicheal extends BossFight {
export class BossReaper extends BossFight { export class BossReaper extends BossFight {
constructor(protected md2Service: MD2Service) { constructor(public md2Service: MD2Service) {
super(md2Service); super(md2Service);
this.timeTokenHtml = this.md2Service.stateService.imgHtml('Tokens/TimeToken.png'); this.timeTokenHtml = this.md2Service.imgHtml('Tokens/TimeToken.png');
this.name = 'The Reaper'; this.name = 'The Reaper';
this.imgUrl = md2Service.stateService.imgUrl('/Boss/The Reaper.jpg'); this.imgUrl = md2Service.imgUrl('/Boss/The Reaper.jpg');
this.standUrl = md2Service.stateService.imgUrl('/Boss/The Reaper-Stand.png'); this.standUrl = md2Service.imgUrl('/Boss/The Reaper-Stand.png');
this.info = new MobInfo({ this.info = new MobInfo({
description: this.name, description: this.name,
type: MobType.Boss, type: MobType.Boss,
hpPerHero: 25, hpPerHero: 25,
level: 10, level: 10,
imageUrl: md2Service.stateService.imgUrl('/Boss/The Reaper-Stand.png') imageUrl: md2Service.imgUrl('/Boss/The Reaper-Stand.png')
}); });
if (!this.info.skills) { if (!this.info.skills) {
this.info.skills = []; this.info.skills = [];
} }
this.info.skills.push({ this.info.skills.push({
description: `If the Hero has no ${this.md2Service.stateService.iconHtml(MD2Icon.Mana_Color)}, they take 1 ${this.md2Service.stateService.iconHtml(MD2Icon.FrozenToken)}`, description: `If the Hero has no ${this.md2Service.iconHtml(MD2Icon.Mana_Color)}, they take 1 ${this.md2Service.iconHtml(MD2Icon.FrozenToken)}`,
type: MobSkillType.Attack, type: MobSkillType.Attack,
skillRoll: 1 skillRoll: 1
} as MD2MobSkill); } as MD2MobSkill);
@ -303,8 +305,8 @@ export class BossReaper extends BossFight {
name: 'Death Is Coming', name: 'Death Is Coming',
description: description:
`Place The Reaper in the central Zone.<br>` + `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>` + `Roll 1 ${this.md2Service.iconHtml(MD2Icon.YellowDice)}. Remove ${this.timeTokenHtml} equal to ${this.md2Service.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.` `Each Hero discards ${this.md2Service.iconHtml(MD2Icon.MP)} equal to ${this.md2Service.iconHtml(MD2Icon.Melee)} rolled.`
}); });
break; break;

View File

@ -37,6 +37,10 @@ export enum HeroClass {
Shaman, Shaman,
Paladin, Paladin,
Druid, Druid,
Necromancer,
Monk,
Thinker,
Bard
} }
export enum MobType { export enum MobType {
Mob, Mob,
@ -377,7 +381,6 @@ export class MobInfo implements IDrawingItem {
return 0; return 0;
} }
} }
public actionSubject: Subject<string>;
public activateFunction?: (mob: MobInfo, msgBoxService: MsgBoxService, heros: MD2HeroInfo[]) => void; public activateFunction?: (mob: MobInfo, msgBoxService: MsgBoxService, heros: MD2HeroInfo[]) => void;
} }
export interface MD2HeroProfile { export interface MD2HeroProfile {

View File

@ -6,7 +6,6 @@ import { MsgBoxService } from '../../../services/msg-box.service';
import { DropDownOption } from '../../../entity/dropDownOption'; import { DropDownOption } from '../../../entity/dropDownOption';
import { MD2Icon } from '../massive-darkness2.model'; import { MD2Icon } from '../massive-darkness2.model';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { MD2StateService } from '../../../services/MD2/md2-state.service';
import { MD2IconPickerDlgComponent } from './md2-icon-picker-dlg.component'; import { MD2IconPickerDlgComponent } from './md2-icon-picker-dlg.component';
import { NbDialogService } from '@nebular/theme'; import { NbDialogService } from '@nebular/theme';
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model'; import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
@ -59,7 +58,6 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewIn
constructor( constructor(
private msgBoxService: MsgBoxService, private msgBoxService: MsgBoxService,
private md2StateService: MD2StateService,
private dialogService: DialogService, private dialogService: DialogService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
elementRef: ElementRef, ngZone: NgZone, @Inject(PLATFORM_ID) platformId: Object) { elementRef: ElementRef, ngZone: NgZone, @Inject(PLATFORM_ID) platformId: Object) {

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { NbDialogRef } from '@nebular/theme'; import { NbDialogRef } from '@nebular/theme';
import { MD2Icon } from '../massive-darkness2.model'; import { MD2Icon } from '../massive-darkness2.model';
import { MD2StateService } from '../../../services/MD2/md2-state.service';
import { DialogRef } from '@progress/kendo-angular-dialog'; import { DialogRef } from '@progress/kendo-angular-dialog';
import { MD2Service } from '../../../services/MD2/md2.service';
@Component({ @Component({
selector: 'md2-icon-picker-dlg', selector: 'md2-icon-picker-dlg',
@ -63,7 +63,7 @@ export class MD2IconPickerDlgComponent implements OnInit {
constructor( constructor(
private dlgRef: DialogRef, private dlgRef: DialogRef,
private md2StateService: MD2StateService private md2Service: MD2Service
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
@ -72,7 +72,7 @@ export class MD2IconPickerDlgComponent implements OnInit {
this.iconList.push({ this.iconList.push({
icon: icon, icon: icon,
name: MD2Icon[icon], name: MD2Icon[icon],
html: this.md2StateService.iconHtml(icon) html: this.md2Service.iconHtml(icon)
}); });
} }
} }

View File

@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { MD2Icon, TreasureType } from '../massive-darkness2.model'; import { MD2Icon, TreasureType } from '../massive-darkness2.model';
import { MD2StateService } from '../../../services/MD2/md2-state.service'; import { MD2Service } from '../../../services/MD2/md2.service';
@Component({ @Component({
selector: 'md2-icon', selector: 'md2-icon',
@ -44,7 +44,7 @@ export class MD2IconComponent implements OnInit {
} }
@Input() size: string = 'sm'; @Input() size: string = 'sm';
iconName: string; iconName: string;
constructor(private md2StateService: MD2StateService) { } constructor(private md2Service: MD2Service) { }
ngOnInit(): void { ngOnInit(): void {
} }
@ -52,10 +52,10 @@ export class MD2IconComponent implements OnInit {
private initIcon(icon: MD2Icon): void { private initIcon(icon: MD2Icon): void {
if (icon < MD2Icon.TreasureToken) { if (icon < MD2Icon.TreasureToken) {
this.isImageIcon = false; this.isImageIcon = false;
this.iconHtml = this.md2StateService.iconHtml(icon); this.iconHtml = this.md2Service.iconHtml(icon);
} else { } else {
this.isImageIcon = true; this.isImageIcon = true;
this.imgUrl = this.md2StateService.iconHtml(icon); this.imgUrl = this.md2Service.iconHtml(icon);
} }
} }

View File

@ -57,7 +57,6 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
private msgBoxService: MsgBoxService private msgBoxService: MsgBoxService
) { ) {
super(dialog); super(dialog);
this.initializeEnums();
} }
ngOnInit(): void { ngOnInit(): void {
@ -68,6 +67,7 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
} }
this.loadSkills(); this.loadSkills();
} }
this.initializeEnums();
} }
private initializeEnums(): void { private initializeEnums(): void {
@ -140,6 +140,9 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
attackInfo: lastLevel.attackInfo attackInfo: lastLevel.attackInfo
? { ...lastLevel.attackInfo } ? { ...lastLevel.attackInfo }
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null }, : { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
alterAttackInfo: lastLevel.alterAttackInfo
? { ...lastLevel.alterAttackInfo }
: null,
defenceInfo: lastLevel.defenceInfo defenceInfo: lastLevel.defenceInfo
? { ...lastLevel.defenceInfo } ? { ...lastLevel.defenceInfo }
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null } : { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
@ -158,6 +161,7 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
hpPerHero: 0, hpPerHero: 0,
actions: actions, actions: actions,
attackInfo: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null }, attackInfo: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
alterAttackInfo: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
defenceInfo: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null } defenceInfo: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
}; };
} }
@ -186,14 +190,20 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
case 1: case 1:
newLevel.rewardTokens = 2; newLevel.rewardTokens = 2;
newLevel.fixedRareTreasure = 1; newLevel.fixedRareTreasure = 1;
newLevel.fixedEpicTreasure = 0;
newLevel.fixedLegendTreasure = 0;
break; break;
case 3: case 3:
newLevel.rewardTokens = 2; newLevel.rewardTokens = 2;
newLevel.fixedRareTreasure = 0;
newLevel.fixedEpicTreasure = 1; newLevel.fixedEpicTreasure = 1;
newLevel.fixedLegendTreasure = 0;
break; break;
case 5: case 5:
newLevel.rewardTokens = 0; newLevel.rewardTokens = 0;
newLevel.fixedRareTreasure = 0;
newLevel.fixedEpicTreasure = 3; newLevel.fixedEpicTreasure = 3;
newLevel.fixedLegendTreasure = 0;
break; break;
} }
break; break;
@ -309,11 +319,11 @@ export class MD2MobInfoDetailComponent extends DialogContentBase implements OnIn
level: level, level: level,
mobInfoId: this.mobInfo.id, mobInfoId: this.mobInfo.id,
type: type, type: type,
skillTarget: MobSkillTarget.Random, skillTarget: lastLevel?.skillTarget || MobSkillTarget.Random,
clawRoll: 0, clawRoll: lastLevel?.clawRoll || 0,
skillRoll: 1, skillRoll: lastLevel?.skillRoll || 1,
name: 'Basic Skill', name: lastLevel?.name || 'Combat Skill',
skillCondition: '', skillCondition: lastLevel?.skillCondition || '',
description: lastLevel?.description || '' description: lastLevel?.description || ''
}; };

View File

@ -20,9 +20,14 @@
<kendo-grid-column field="name" title="Name" [width]="200"> <kendo-grid-column field="name" title="Name" [width]="200">
</kendo-grid-column> </kendo-grid-column>
<kendo-grid-column field="type" title="Type" [width]="150"> <kendo-grid-column field="type" title="Type" [width]="1">
<ng-template kendoGridCellTemplate let-dataItem> <ng-template kendoGridCellTemplate let-dataItem>
{{ getMobTypeName(dataItem.type) }} <ng-template kendoGridCellTemplate let-dataItem>
{{ getMobTypeName(dataItem.type) }}
</ng-template>
<ng-template kendoGridGroupHeaderTemplate let-value="value">
{{ getMobTypeName(value) }}
</ng-template>
</ng-template> </ng-template>
</kendo-grid-column> </kendo-grid-column>

View File

@ -119,6 +119,22 @@
<div class="col-md-12"> <div class="col-md-12">
<h5>Attack Info</h5> <h5>Attack Info</h5>
</div> </div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.Attack"></md2-icon>Attack Type</label>
<!-- Dropdown list for attack type , the text is html string-->
<kendo-dropdownlist [(ngModel)]="selectedAttackType" name="attackInfo.type" [data]="attackTypes"
[textField]="'text'" [valueField]="'value'">
<ng-template kendoDropDownListItemTemplate let-dataItem>
<span [innerHTML]="dataItem.text"></span>
</ng-template>
<ng-template kendoDropDownListValueTemplate let-dataItem>
<span [innerHTML]="dataItem?.text || ''"></span>
</ng-template>
</kendo-dropdownlist>
</div>
</div>
<div class="col-md-2"> <div class="col-md-2">
<div class="form-group"> <div class="form-group">
<label class="k-label"> <label class="k-label">
@ -157,6 +173,63 @@
</div> </div>
</div> </div>
<div class="row" *ngIf="mobType!=MobType.Mob">
<div class="col-md-12">
<h5>Alter Attack Info</h5>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.Attack"></md2-icon>Alter Attack Type</label>
<!-- Dropdown list for attack type , the text is html string-->
<kendo-dropdownlist [(ngModel)]="selectedAlterAttackType" name="alterAttackInfo.type"
[data]="attackTypes" [textField]="'text'" [valueField]="'value'">
<ng-template kendoDropDownListItemTemplate let-dataItem>
<span [innerHTML]="dataItem.text"></span>
</ng-template>
<ng-template kendoDropDownListValueTemplate let-dataItem>
<span [innerHTML]="dataItem?.text || ''"></span>
</ng-template>
</kendo-dropdownlist>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.YellowDice"></md2-icon>Yellow Dice</label>
<kendo-numerictextbox [(ngModel)]="model.alterAttackInfo.yellow" name="alterAttackInfo.yellow"
[min]="0" [decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.OrangeDice"></md2-icon>Orange Dice</label>
<kendo-numerictextbox [(ngModel)]="model.alterAttackInfo.orange" name="alterAttackInfo.orange"
[min]="0" [decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.RedDice"></md2-icon>Red Dice</label>
<kendo-numerictextbox [(ngModel)]="model.alterAttackInfo.red" name="alterAttackInfo.red" [min]="0"
[decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label class="k-label">
<md2-icon [icon]="MD2Icon.BlackDice"></md2-icon>Black Dice</label>
<kendo-numerictextbox [(ngModel)]="model.alterAttackInfo.black" name="alterAttackInfo.black"
[min]="0" [decimals]="0" [format]="'n0'">
</kendo-numerictextbox>
</div>
</div>
</div>
</form> </form>
</div> </div>

View File

@ -5,6 +5,7 @@ import { MD2MobLevelInfo } from '../../massive-darkness2.db.model';
import { MobSkillType } from '../../massive-darkness2.model.boss'; import { MobSkillType } from '../../massive-darkness2.model.boss';
import { MD2MobLevelInfoService } from '../../service/massive-darkness2.service'; import { MD2MobLevelInfoService } from '../../service/massive-darkness2.service';
import { MD2Icon, MobType } from '../../massive-darkness2.model'; import { MD2Icon, MobType } from '../../massive-darkness2.model';
import { MD2Service } from '../../../../services/MD2/md2.service';
@Component({ @Component({
selector: 'ngx-md2-mob-level-editor', selector: 'ngx-md2-mob-level-editor',
@ -20,21 +21,35 @@ export class MD2MobLevelEditorComponent extends DialogContentBase implements OnI
@Input() public mobInfoId: string; @Input() public mobInfoId: string;
@Input() public isAdding: boolean = false; @Input() public isAdding: boolean = false;
public selectedAttackType: { value: MobSkillType; text: string } | null = null;
public model: MD2MobLevelInfo; public model: MD2MobLevelInfo;
public processing: boolean = false; public processing: boolean = false;
public attackTypes: Array<{ value: MobSkillType; text: string }> = [];
public selectedAlterAttackType: { value: MobSkillType; text: string } | null = null;
constructor( constructor(
public dialog: DialogRef, public dialog: DialogRef,
private mobLevelInfoService: MD2MobLevelInfoService, private mobLevelInfoService: MD2MobLevelInfoService,
private cdr: ChangeDetectorRef private cdr: ChangeDetectorRef,
private md2Service: MD2Service
) { ) {
super(dialog); super(dialog);
} }
ngOnInit(): void { ngOnInit(): void {
this.initializeModel(); this.initializeModel();
this.initializeEnums();
} }
public initializeEnums(): void {
this.attackTypes = [
{ value: MobSkillType.Attack, text: 'None' },
{ value: MobSkillType.MeleeAttack, text: this.md2Service.iconHtml(MD2Icon.Melee) + ' Melee Attack' },
{ value: MobSkillType.RangeAttack, text: this.md2Service.iconHtml(MD2Icon.Range) + ' Range Attack' },
{ value: MobSkillType.MagicAttack, text: this.md2Service.iconHtml(MD2Icon.Magic) + ' Magic Attack' },
];
this.selectedAttackType = this.attackTypes.find(t => t.value === this.model.attackInfo.type) || this.attackTypes[0] || null;
this.selectedAlterAttackType = this.attackTypes.find(t => t.value === this.model.alterAttackInfo?.type) || this.attackTypes[0] || null;
}
public initializeModel(): void { public initializeModel(): void {
this.model = { this.model = {
id: this.data?.id || '', id: this.data?.id || '',
@ -50,6 +65,9 @@ export class MD2MobLevelEditorComponent extends DialogContentBase implements OnI
attackInfo: this.data?.attackInfo attackInfo: this.data?.attackInfo
? { ...this.data.attackInfo } ? { ...this.data.attackInfo }
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null }, : { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
alterAttackInfo: this.data?.alterAttackInfo
? { ...this.data.alterAttackInfo }
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
defenceInfo: this.data?.defenceInfo defenceInfo: this.data?.defenceInfo
? { ...this.data.defenceInfo } ? { ...this.data.defenceInfo }
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null } : { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
@ -73,7 +91,10 @@ export class MD2MobLevelEditorComponent extends DialogContentBase implements OnI
if (!this.model.defenceInfo) { if (!this.model.defenceInfo) {
this.model.defenceInfo = { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }; this.model.defenceInfo = { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
} }
this.model.attackInfo.type = this.selectedAttackType?.value ?? MobSkillType.Attack;
if (this.model.alterAttackInfo) {
this.model.alterAttackInfo.type = this.selectedAlterAttackType?.value ?? MobSkillType.Attack;
}
this.mobLevelInfoService.createOrUpdate(this.model).pipe(first()).subscribe(result => { this.mobLevelInfoService.createOrUpdate(this.model).pipe(first()).subscribe(result => {
this.processing = false; this.processing = false;
this.dialog.close(result); this.dialog.close(result);

View File

@ -20,13 +20,12 @@
<md2-icon icon="blackDice" size="lg"></md2-icon> {{skillTriggerHtml}} <md2-icon icon="enemySkill" size="md"> <md2-icon icon="blackDice" size="lg"></md2-icon> {{skillTriggerHtml}} <md2-icon icon="enemySkill" size="md">
</md2-icon> </md2-icon>
</label> --> </label> -->
<div *ngFor="let skill of mob.skills" class=" g-brd-bottom--dashed g-brd-gray-light-v2"> <ng-container *ngFor="let skill of mob.skills">
<div *ngIf="skill.uiDisplay"> <div *ngIf="skill.uiDisplay" class=" g-brd-bottom--dashed g-brd-gray-light-v2">
<label for='' class='MD2text g-font-size-22 label mb-2'> <label for='' class='MD2text g-font-size-22 label mb-2'>
{{MobSkillType[skill.type]}} {{skill.skillRoll}} <md2-icon icon="enemySkill" size="md"></md2-icon> {{MobSkillType[skill.type]}} {{skill.skillRoll}} <md2-icon icon="enemySkill" size="md"></md2-icon>
</label> </label>
<div class='g-font-size-20 skillDesc MD2text' [innerHtml]="skill.description"></div> <div class='g-font-size-20 skillDesc MD2text' [innerHtml]="skill.description"></div>
</div> </div>
</ng-container>
</div>
</div> </div>

View File

@ -22,12 +22,13 @@ export class MobCombatInfoComponent implements OnInit {
} }
} }
@Input() mode: MobDlgType = MobDlgType.PreView; @Input() mode: MobDlgType = MobDlgType.PreView;
showSkill: boolean = false; showAllSkill: boolean = false;
showBlackDice: boolean showBlackDice: boolean
skillTriggerHtml: string = ''; skillTriggerHtml: string = '';
constructor() { } constructor() { }
ngOnInit(): void { ngOnInit(): void {
this.showAllSkill = [MobDlgType.PreView, MobDlgType.Dashboard].includes(this.mode);
if (this.mob.skills && this.mob.skills.length > 0) { if (this.mob.skills && this.mob.skills.length > 0) {
this.mob.skills.forEach(element => { this.mob.skills.forEach(element => {
switch (this.mode) { switch (this.mode) {
@ -38,7 +39,8 @@ export class MobCombatInfoComponent implements OnInit {
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Defense].includes(element.type); element.uiDisplay = [MobSkillType.Combat, MobSkillType.Defense].includes(element.type);
break; break;
case MobDlgType.PreView: case MobDlgType.PreView:
element.uiDisplay = true; case MobDlgType.Dashboard:
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Attack, MobSkillType.Defense].includes(element.type);
break; break;
case MobDlgType.Spawn: case MobDlgType.Spawn:
element.uiDisplay = false; element.uiDisplay = false;

View File

@ -8,8 +8,8 @@
<div class="pl-2 col-md-6" *ngIf="mob.mobAmount"> <div class="pl-2 col-md-6" *ngIf="mob.mobAmount">
<ng-container> <ng-container>
<label class='label g-text-nowrap'>Alive Units <b <label class='label g-text-nowrap'>Minions <b
class="MD2text g-font-size-18">{{mob.mobAmount}}</b></label><br> class="MD2text g-font-size-18">{{mob.mobAmount-1}}</b></label><br>
</ng-container> </ng-container>
</div> </div>
@ -34,11 +34,21 @@
<ng-container *ngIf="showAdjustment"> <ng-container *ngIf="showAdjustment">
<div class="row">
<div class="col-md-6">
<adj-number-input name="mob{{mob.name}}" [ngModel]="mob.unitRemainHp" [maximum]="mob.hp" minimum="1" <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)" title="Target Unit HP" (hitChange)="adjustMobHp($event)" (hitMinimum)="adjustMobHp(-1)"
(hitMaximum)="adjustMobHp(1)"> (hitMaximum)="adjustMobHp(1)">
</adj-number-input> </adj-number-input>
</div>
<div class="col-md-6" *ngIf="mode==MobDlgType.Activating">
<adj-number-input name="mob{{mob.name}}Actions" [(ngModel)]="mob.actions" title="Remaining Actions"
hideIncreaseBtn>
</adj-number-input>
</div>
</div>
</ng-container> </ng-container>
@ -50,8 +60,8 @@
<md2-mob-attack-info [mob]="mob" [mode]="mode"> <md2-mob-attack-info [mob]="mob" [mode]="mode">
</md2-mob-attack-info> </md2-mob-attack-info>
<md2-mob-def-info [mob]="mob" [mode]="mode"></md2-mob-def-info> <md2-mob-def-info [mob]="mob" [mode]="mode"></md2-mob-def-info>
<md2-mob-combat-info [mob]="mob" [mode]="mode"></md2-mob-combat-info> <md2-mob-combat-info *ngIf="!hideCombatInfo" [mob]="mob" [mode]="mode"></md2-mob-combat-info>
<div *ngIf="mob.extraRule"> <div *ngIf="!hideCombatInfo && mob.extraRule">
<div class="alert alert-warning" role="alert" [innerHtml]="mob.extraRule"> <div class="alert alert-warning" role="alert" [innerHtml]="mob.extraRule">
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@ import { MD2ComponentBase } from '../../MD2Base';
styleUrls: ['./mob-detail-info.component.scss'] styleUrls: ['./mob-detail-info.component.scss']
}) })
export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit { export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
MobDlgType = MobDlgType;
MD2Icon = MD2Icon; MD2Icon = MD2Icon;
private _mob: MobInfo; private _mob: MobInfo;
public get mob(): MobInfo { public get mob(): MobInfo {
@ -33,6 +33,10 @@ export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
return this.mode == MobDlgType.Spawn; return this.mode == MobDlgType.Spawn;
} }
public get hideCombatInfo(): boolean {
return this.mode == MobDlgType.Dashboard;
}
showAttackingInfo: boolean = false; showAttackingInfo: boolean = false;
@Input("showAttackingInfo") @Input("showAttackingInfo")

View File

@ -1,7 +1,8 @@
<img class="g-width-95x img-thumbnail mobBg" src="{{imgUrl('/Mobs/BG.png')}}" /> <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" /> <img class="mobImg roamingMonster" src="{{getMobImageUrl(mob)}}" (click)="showMobImage(mob)" *ngIf="!isMob" />
<div *ngIf="isMob"> <div *ngIf="isMob">
<img class="mobImg mobLeader" src="{{mob.leaderImgUrl}}" (click)="showMobImage(mob)" /> <img class="mobImg mobLeader" src="{{mob.leaderImgUrl}}" (click)="showMobImage(mob)"
<img class="mobImg mobMinion" src="{{mob.minionImgUrl}}" (click)="showMobImage(mob)" /> [ngClass]="{'noMinions': mob.mobAmount==1}" />
<img class="mobImg mobMinion" src="{{mob.minionImgUrl}}" (click)="showMobImage(mob)" *ngIf="mob.mobAmount>1" />
</div> </div>

View File

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

View File

@ -29,6 +29,12 @@
<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> <md2-mob-stand-info [mob]="mob"></md2-mob-stand-info>
<md2-mob-combat-info [mob]="mob" [mode]="MobDlgType.Dashboard"></md2-mob-combat-info>
<div *ngIf="mob.extraRule">
<div class="alert alert-warning" role="alert" [innerHtml]="mob.extraRule">
</div>
</div>
</div> </div>
<div class=" col-12 col-md-4"> <div class=" col-12 col-md-4">

View File

@ -11,7 +11,6 @@ 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';
import { NumberUtils } from '../../../utilities/number-utils'; import { NumberUtils } from '../../../utilities/number-utils';
import { StringUtils } from '../../../utilities/string-utils'; import { StringUtils } from '../../../utilities/string-utils';
import { CoreGameRMFactories } from '../factorys/roamingMonsters/CoreGame';
import { DrawingBag, DrawingItem, MD2Icon, MobDlgType, MobInfo, TreasureType } from '../massive-darkness2.model'; import { DrawingBag, DrawingItem, MD2Icon, MobDlgType, MobInfo, TreasureType } from '../massive-darkness2.model';
import { MD2Base, MD2ComponentBase } from '../MD2Base'; import { MD2Base, MD2ComponentBase } from '../MD2Base';
import { SpawnMobDlgComponent } from './spawn-mob-dlg/spawn-mob-dlg.component'; import { SpawnMobDlgComponent } from './spawn-mob-dlg/spawn-mob-dlg.component';
@ -89,7 +88,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
} }
spawnSpecificMob() { spawnSpecificMob() {
let mobOptions = this.isRoamingMonster ? CoreGameRMFactories.map(f => new DropDownOption(f.mobName, f.mobName)) : this.md2Service.allMobInfos.map(f => new DropDownOption(f.name, f.name)); 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.msgBoxService.showInputbox('Spawn', '', this.msgBoxService.showInputbox('Spawn', '',
{ {
inputType: 'dropdown', dropDownOptions: mobOptions, inputType: 'dropdown', dropDownOptions: mobOptions,
@ -120,7 +119,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
afterSpawn() { afterSpawn() {
this.cdRef.detectChanges(); this.cdRef.detectChanges();
this.md2Service.broadcastService.broadcastMobsInfo(); this.md2Service.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 });
} }
@ -151,7 +150,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
if (attacker) { if (attacker) {
attacker.exp += 1; attacker.exp += 1;
this.msgBoxService.show(`${attacker.heroFullName} Gain 1 Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => { this.msgBoxService.show(`${attacker.heroFullName} Gain 1 Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker); this.md2Service.broadcastHeroInfoToOwner(attacker);
}); });
} }

View File

@ -5,20 +5,33 @@
</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 g-height-90vh"> <div class="col-md-7">
<!-- <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> <md2-mob-stand-info [mob]="mob" [mode]="mode"></md2-mob-stand-info>
<div *ngIf="activeSkill">
<!-- <div class="alert alert-warning" role="alert">
<h4>{{activeSkill.name}}</h4>
<div [innerHtml]="activeSkill.description"></div>
</div> -->
<details class="skill-card" open>
<summary>
<h3 class="skill-name">{{ activeSkill.name }}</h3>
<!-- <span class="rarity" [ngClass]="rarity.toLowerCase()">{{ rarity }}</span> -->
</summary>
<div class="skill-body" [innerHtml]="activeSkill.description">
<!-- <p>{{ description }}</p>
<ul>
<li *ngFor="let s of stats"><strong>{{ s.label }}:</strong> {{ s.value }}</li>
</ul> -->
</div>
</details>
</div>
</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>
<div *ngIf="actionInfoHtml">
<div class="alert alert-warning" role="alert" [innerHtml]="actionInfoHtml">
</div>
</div>
<ng-container *ngIf="mode==MobDlgType.Spawn&&mob.type==MobType.Mob"> <ng-container *ngIf="mode==MobDlgType.Spawn&&mob.type==MobType.Mob">
<div class="row form-group mt-2"> <div class="row form-group mt-2">

View File

@ -2,3 +2,122 @@
width: 885px; width: 885px;
height: 95vh !important; height: 95vh !important;
} }
.skill-card {
border-radius: 16px;
overflow: hidden;
position: relative;
border: 1px solid #252a34;
background: radial-gradient(120% 180% at 50% -40%, rgba(217, 143, 43, 0.08), transparent 60%),
linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)), #14161b;
transition:
transform 0.12s ease,
box-shadow 0.25s ease,
filter 0.2s ease;
margin-top: -130px;
z-index: 1;
width: 96%;
}
.skill-card::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
border-radius: 16px;
padding: 1px;
background: linear-gradient(
180deg,
rgba(246, 230, 185, 0.6),
rgba(176, 136, 46, 0.35) 55%,
rgba(246, 230, 185, 0.6)
);
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
.skill-card[open] {
box-shadow:
0 0 0 2px rgba(217, 143, 43, 0.25),
0 12px 28px rgba(0, 0, 0, 0.6);
}
summary {
list-style: none;
cursor: pointer;
position: relative;
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 0.75rem;
padding: 6px 1rem;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0));
}
summary::-webkit-details-marker {
display: none;
}
summary:hover {
filter: saturate(1.08);
box-shadow:
0 0 0 2px rgba(155, 28, 28, 0.25),
0 0 18px rgba(217, 143, 43, 0.18) inset;
}
summary:focus-visible {
outline: 2px solid #d98f2b;
outline-offset: 2px;
box-shadow: 0 0 18px rgba(217, 143, 43, 0.35);
}
.skill-name {
margin: 0;
font-size: 2rem;
letter-spacing: 0.04em;
background: linear-gradient(180deg, #f6e6b9, #b0882e 50%, #f6e6b9);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-family: "DwarvenAxeBBW00-Regular", sans-serif !important;
}
.rarity {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.12em;
padding: 0.25rem 0.5rem;
border-radius: 999px;
border: 1px solid rgba(240, 217, 154, 0.45);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02));
color: #e8e5de;
}
.rarity.legendary {
border-color: rgba(217, 143, 43, 0.6);
box-shadow: inset 0 0 10px rgba(217, 143, 43, 0.25);
}
.rarity.epic {
border-color: rgba(155, 28, 28, 0.6);
box-shadow: inset 0 0 10px rgba(155, 28, 28, 0.3);
}
.rarity.rare {
border-color: rgba(148, 164, 58, 0.6);
box-shadow: inset 0 0 10px rgba(148, 164, 58, 0.28);
}
::ng-deep {
.skill-body {
padding: 0 1rem 1rem;
color: #cfcab7;
}
.skill-body p {
margin: 0.6rem 0 0.4rem;
color: #ddd6bf;
}
.skill-body ul {
margin: 0.25rem 0 0;
padding-left: 1.1rem;
}
.skill-body li {
margin: 0.25rem 0;
color: #a7a196;
}
.skill-body strong {
color: #efe7cf;
}
}

View File

@ -1,7 +1,7 @@
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 { concatMap, find, defaultIfEmpty, first, map, switchMap } from 'rxjs/operators';
import { FileService } from '../../../../services/file.service'; import { FileService } from '../../../../services/file.service';
import { MD2Service } from '../../../../services/MD2/md2.service'; import { MD2Service } from '../../../../services/MD2/md2.service';
import { MsgBoxService } from '../../../../services/msg-box.service'; import { MsgBoxService } from '../../../../services/msg-box.service';
@ -9,6 +9,12 @@ 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, MobType } 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';
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';
type SkillResolutionResult = { result: boolean; skill: MD2MobSkill | null };
@Component({ @Component({
selector: 'ngx-spawn-mob-dlg', selector: 'ngx-spawn-mob-dlg',
@ -38,16 +44,34 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
) { ) {
super(md2Service, stateService, route, cdRef); super(md2Service, stateService, route, cdRef);
} }
activeSkill: MD2MobSkill = null;;
ngOnInit(): void { ngOnInit(): void {
//this.mob = new MobInfo(this.mob); //this.mob = new MobInfo(this.mob);
if (this.mode == MobDlgType.Spawn && this.mob.type == MobType.Mob) { if (this.mode == MobDlgType.Spawn && this.mob.type == MobType.Mob) {
this.mob.attackInfos = [new AttackInfo(MD2Icon.Melee), new AttackInfo(MD2Icon.Range), new AttackInfo(MD2Icon.Magic)]; this.mob.attackInfos = [new AttackInfo(MD2Icon.Melee), new AttackInfo(MD2Icon.Range), new AttackInfo(MD2Icon.Magic)];
} else if (this.mode == MobDlgType.Activating) { } else if (this.mode == MobDlgType.Activating) {
if (this.mob.actionSubject) { if (this.mob.skills) {
this.mob.actionSubject.pipe(first()).subscribe(result => {
this.actionInfoHtml = result; //this.mobActivate(this.mob, this.md2Service.info.heros);
//this.mob.activateFunction(this.mob, this.md2Service.msgBoxService, this.md2Service.info.heros);
this.mobActivate(this.mob, this.md2Service.info.heros).pipe(first()).subscribe(result => {
if (result && result.skill) {
this.activeSkill = result.skill;
//this.actionInfoHtml = `<h4>${result.skill.name}</h4><div>${result.skill.description}</div>`;
}
if (this.mob.type == MobType.Mob) {
this.mob.actions = 2;
} else {
if (result && result.skill) {
this.mob.actions = 1;
} else {
this.mob.actions = 2;
this.activeSkill = { name: 'Normal Action', description: `${this.mob.name} Gains 2 Actions.` } as MD2MobSkill;
//this.actionInfoHtml = `${this.mob.name} Gains 2 Actions.`;
}
}
}); });
this.mob.activateFunction(this.mob, this.md2Service.msgBoxService, this.md2Service.info.heros);
} }
} }
this.mob.uiWounds = 0; this.mob.uiWounds = 0;
@ -55,6 +79,52 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
this.mob.uiFrozenTokens = 0; this.mob.uiFrozenTokens = 0;
this.initTitleHtml(); this.initTitleHtml();
} }
mobActivate(mob: MobInfo, heros: MD2HeroInfo[]): Observable<SkillResolutionResult> {
switch (mob.type) {
case MobType.Mob:
return of({ result: false, skill: null } as SkillResolutionResult);
case MobType.RoamingMonster:
if (!mob.skills || mob.skills.length === 0) {
return of({ result: false, skill: null } as SkillResolutionResult);
}
const orderedSkills = mob.skills
.filter(s => [MobSkillType.ConditionalSkill, MobSkillType.OtherWiseSkill].includes(s.type))
.sort((a, b) => (a.seq ?? Number.MAX_SAFE_INTEGER) - (b.seq ?? Number.MAX_SAFE_INTEGER));
if (orderedSkills.length === 0) {
return of({ result: false, skill: null } as SkillResolutionResult);
}
return from(orderedSkills).pipe(
concatMap(skill => this.resolveSkillPrompt(skill)),
find(resolution => resolution.result === true),
defaultIfEmpty({ result: false, skill: null } as SkillResolutionResult)
);
case MobType.Boss:
default:
return of({ result: false, skill: null } as SkillResolutionResult);
}
}
private resolveSkillPrompt(skill: MD2MobSkill): Observable<SkillResolutionResult> {
if (skill.type === MobSkillType.OtherWiseSkill) {
return of({ result: true, skill } as SkillResolutionResult);
}
const title = 'Resolve Skill?';
const prompt = skill.skillCondition;
return this.msgBoxService.show(title, {
text: prompt,
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(
map(answer => ({ result: !!answer, skill } as SkillResolutionResult))
);
}
submit() { submit() {
if (this.mode == MobDlgType.Spawn) { if (this.mode == MobDlgType.Spawn) {

View File

@ -1,16 +0,0 @@
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

@ -1,110 +0,0 @@
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.broadcastHeroInfoToAll(element);
});
this.broadcastMobsInfo();
}
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

@ -1,9 +1,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreGameRMFactories } from '../../games/massive-darkness2/factorys/roamingMonsters/CoreGame'; import { DrawingBag, MobInfo, MobType, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
import { DrawingBag, MobInfo, TreasureItem, TreasureType } from '../../games/massive-darkness2/massive-darkness2.model';
import { MD2StateService } from './md2-state.service';
import { MD2MobInfoService } from '../../games/massive-darkness2/service/massive-darkness2.service'; import { MD2MobInfoService } from '../../games/massive-darkness2/service/massive-darkness2.service';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { MD2Service } from './md2.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -11,28 +10,18 @@ import { first } from 'rxjs/operators';
export class MD2InitService { export class MD2InitService {
constructor( constructor(
private stateService: MD2StateService, private md2Service: MD2Service,
private mobInfoService: MD2MobInfoService, private mobInfoService: MD2MobInfoService,
) { } ) { }
public initMobDecks() { public initMobDecks() {
this.stateService.mobDeck = new DrawingBag(); this.md2Service.mobDeck = new DrawingBag();
this.stateService.roamingMobDeck = new DrawingBag(); this.md2Service.roamingMobDeck = new DrawingBag();
this.initCoreGameMobs(); this.initCoreGameMobAndRoamingMonsters();
this.initCoreGameRoamingMonsters();
} }
private initCoreGameRoamingMonsters() { private initCoreGameMobAndRoamingMonsters() {
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 => { // CoreGameMobFactories.forEach(factory => {
// for (let i = 1; i <= 5; i++) { // for (let i = 1; i <= 5; i++) {
// this.stateService.mobDeck.AddItem(new MobInfo({ name: factory.mobName, level: i, drawingWeight: 1 })); // this.stateService.mobDeck.AddItem(new MobInfo({ name: factory.mobName, level: i, drawingWeight: 1 }));
@ -40,13 +29,20 @@ export class MD2InitService {
// } // }
// }); // });
this.mobInfoService.getAll().pipe(first()).subscribe(result => { this.mobInfoService.getAll().pipe(first()).subscribe(result => {
this.stateService.mobInfos = result; this.md2Service.mobInfos = result.filter(m => m.type == MobType.Mob);
this.md2Service.roamingMobInfos = result.filter(m => m.type == MobType.RoamingMonster);
for (let i = 0; i < result.length; i++) { for (let i = 0; i < result.length; i++) {
const mobInfo = result[i]; const mobInfo = result[i];
for (let j = 0; j < mobInfo.mobLevelInfos.length; j++) { for (let j = 0; j < mobInfo.mobLevelInfos.length; j++) {
const levelInfo = mobInfo.mobLevelInfos[j]; const levelInfo = mobInfo.mobLevelInfos[j];
switch (mobInfo.type) {
this.stateService.mobDeck.AddItem(new MobInfo({ name: mobInfo.name, level: levelInfo.level, drawingWeight: 1 })); case MobType.Mob:
this.md2Service.mobDeck.AddItem(new MobInfo({ name: mobInfo.name, level: levelInfo.level, drawingWeight: 1 }));
break;
case MobType.RoamingMonster:
this.md2Service.roamingMobDeck.AddItem(new MobInfo({ name: mobInfo.name, level: levelInfo.level, drawingWeight: 1 }));
break;
}
} }
} }
@ -57,12 +53,12 @@ export class MD2InitService {
} }
public initTreasureBag() { public initTreasureBag() {
this.stateService.treasureBag.ClearAllItems(); this.md2Service.treasureBag.ClearAllItems();
this.addTreasure(TreasureType.Common, 15); this.addTreasure(TreasureType.Common, 15);
this.addTreasure(TreasureType.Rare, 5); this.addTreasure(TreasureType.Rare, 5);
} }
public addTreasure(type: TreasureType, amount: number = 1) { public addTreasure(type: TreasureType, amount: number = 1) {
this.stateService.treasureBag.AddItem(new TreasureItem(type, amount)); this.md2Service.treasureBag.AddItem(new TreasureItem(type, amount));
} }
} }

View File

@ -3,7 +3,6 @@ import { first } from 'rxjs/operators';
import { MobInfo, MobType } from '../../games/massive-darkness2/massive-darkness2.model'; import { MobInfo, MobType } from '../../games/massive-darkness2/massive-darkness2.model';
import { ADIcon } from '../../ui/alert-dlg/alert-dlg.model'; import { ADIcon } from '../../ui/alert-dlg/alert-dlg.model';
import { MsgBoxService } from '../msg-box.service'; import { MsgBoxService } from '../msg-box.service';
import { MD2StateService } from './md2-state.service';
import { MD2Service } from './md2.service'; import { MD2Service } from './md2.service';
@Injectable({ @Injectable({
@ -16,14 +15,13 @@ export class MD2MobService {
attackingAttackerReward: string = ''; attackingAttackerReward: string = '';
constructor( constructor(
private md2Service: MD2Service, private md2Service: MD2Service,
private stateService: MD2StateService,
private msgBoxService: MsgBoxService private msgBoxService: MsgBoxService
) { } ) { }
public get roamingMonsters() { public get roamingMonsters() {
return this.stateService.info.roamingMonsters; return this.md2Service.info.roamingMonsters;
} }
public get mobs() { public get mobs() {
return this.stateService.info.mobs; return this.md2Service.info.mobs;
} }
addOneUnit(mob: MobInfo) { addOneUnit(mob: MobInfo) {
@ -120,14 +118,14 @@ export class MD2MobService {
} }
this.attackingAllExp = 0; this.attackingAllExp = 0;
this.attackingAttackerExp = 0; this.attackingAttackerExp = 0;
this.md2Service.broadcastService.broadcastAllHeroInfoToAll(); this.md2Service.broadcastAllHeroInfoToAll();
this.md2Service.refreshUI$.next(); this.md2Service.refreshUI$.next();
}); });
} else if (this.attackingAttackerExp > 0 && attacker) { } else if (this.attackingAttackerExp > 0 && attacker) {
this.msgBoxService.show(`<b>${attackerTitle}</b> Gains ${this.attackingAttackerExp} Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => { this.msgBoxService.show(`<b>${attackerTitle}</b> Gains ${this.attackingAttackerExp} Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
attacker.exp += this.attackingAttackerExp; attacker.exp += this.attackingAttackerExp;
this.attackingAttackerExp = 0; this.attackingAttackerExp = 0;
this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker); this.md2Service.broadcastHeroInfoToOwner(attacker);
this.md2Service.refreshUI$.next(); this.md2Service.refreshUI$.next();
}); });

View File

@ -1,16 +0,0 @@
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

@ -1,85 +0,0 @@
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';
import { MD2MobInfo } from '../../games/massive-darkness2/massive-darkness2.db.model';
@Injectable({
providedIn: 'root'
})
export class MD2StateService {
private _highestPlayerLevel: number = 1;
private _playerAmount: number = 2;
public info: MD2GameInfo;
public playerHero: MD2HeroInfo;
public mobInfos: MD2MobInfo[] = [];
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.Fire) {
cssClass += ' g-color-google-plus ';
}
if (icon == MD2Icon.Frost || icon == MD2Icon.Mana) {
cssClass += ' g-color-aqua ';
}
if (icon < MD2Icon.RedDice) {
return `<span md2-icon='${String.fromCharCode(65 + icon)}' class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
} else if (icon < MD2Icon.TreasureToken) {
return `<span md2-icon='${String.fromCharCode(65 + icon)}' class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
} else {
if (!cssClass) {
cssClass = 'g-height-25 mr-1';
}
//image based icons
switch (icon) {
case MD2Icon.HP_Color:
return this.imgHtml('HeartIcon.png', cssClass);
case MD2Icon.Mana_Color:
return this.imgHtml('ManaIcon.png', cssClass);
case MD2Icon.CorruptToken:
return this.imgHtml('Tokens/CorruptToken.png', cssClass);
case MD2Icon.TimeToken:
return this.imgHtml('Tokens/TimeToken.png', cssClass);
case MD2Icon.FireToken:
return this.imgHtml('Tokens/FireToken.png', cssClass);
case MD2Icon.FrozenToken:
return this.imgHtml('Tokens/FrozenToken.png', cssClass);
case MD2Icon.TreasureToken:
return this.imgHtml('TreasureToken/Cover.png', cssClass);
case MD2Icon.TreasureToken_Common:
return this.imgHtml('TreasureToken/Common.png', cssClass);
case MD2Icon.TreasureToken_Rare:
return this.imgHtml('TreasureToken/Rare.png', cssClass);
case MD2Icon.TreasureToken_Epic:
return this.imgHtml('TreasureToken/Epic.png', cssClass);
case MD2Icon.TreasureToken_Legendary:
return this.imgHtml('TreasureToken/Legendary.png', cssClass);
}
}
}
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`);
}
public treasureImageHtml(type: TreasureType) {
return this.imgHtml(`TreasureToken/${TreasureType[type]}.png`, 'g-height-40 mr-1');
}
}

View File

@ -11,15 +11,12 @@ import { FileService } from '../file.service';
import { GameRoomService } from '../game-room.service'; import { GameRoomService } from '../game-room.service';
import { LoginUserService } from '../login-user.service'; import { LoginUserService } from '../login-user.service';
import { MsgBoxService } from '../msg-box.service'; import { MsgBoxService } from '../msg-box.service';
import { SignalRService, SignalRSession, SignalRMessage } from '../signal-r.service'; import { SignalRService, SignalRClient, SignalRMessage } from '../signal-r.service';
import { MD2StateService } from './md2-state.service';
import { MD2BroadcastService } from './md2-broadcast.service';
import { MD2Logic } from '../../games/massive-darkness2/massive-darkness2.logic'; 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 { MD2InitService } from './md2-init.service';
import { ArrayUtils } from '../../utilities/array-utils'; import { ArrayUtils } from '../../utilities/array-utils';
import { DropDownOption } from '../../entity/dropDownOption'; import { DropDownOption } from '../../entity/dropDownOption';
import { GameBundle, MD2MobInfo } from '../../games/massive-darkness2/massive-darkness2.db.model'; import { GameBundle, MD2DiceSet, MD2MobInfo } from '../../games/massive-darkness2/massive-darkness2.db.model';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
@ -30,6 +27,17 @@ const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files
export class MD2Service { export class MD2Service {
// #region Properties (24) // #region Properties (24)
private _highestPlayerLevel: number = 1;
private _playerAmount: number = 2;
public info: MD2GameInfo;
public playerHero: MD2HeroInfo;
public mobInfos: MD2MobInfo[] = [];
public roamingMobInfos: MD2MobInfo[] = [];
public mobDeck: DrawingBag<MobInfo>;
public roamingMobDeck: DrawingBag<MobInfo>;
public treasureBag: DrawingBag<TreasureItem> = new DrawingBag<TreasureItem>();
public darknessPhaseRule: IDarknessPhaseRule; public darknessPhaseRule: IDarknessPhaseRule;
public enemyPhaseMobs: MobInfo[]; public enemyPhaseMobs: MobInfo[];
public enemyPhaseSubject = new Subject<MobInfo>(); public enemyPhaseSubject = new Subject<MobInfo>();
@ -43,42 +51,34 @@ export class MD2Service {
public get heros() { public get heros() {
return this.stateService.info.heros; return this.info.heros;
} }
public get roamingMonsters() { public get roamingMonsters() {
return this.stateService.info.roamingMonsters; return this.info.roamingMonsters;
} }
public get mobs() { public get mobs() {
return this.stateService.info.mobs; return this.info.mobs;
}
public get allRoamingMonsterInfos() {
return this.roamingMobInfos;
} }
public get allMobInfos() { public get allMobInfos() {
return this.stateService.mobInfos; return this.mobInfos;
}
public get info(): MD2GameInfo {
return this.stateService.info;
}
public set info(v: MD2GameInfo) {
this.stateService.info = v;
} }
// #endregion Properties (24) // #endregion Properties (24)
// #region Constructors (1) // #region Constructors (1)
constructor( constructor(
public fileService: FileService, public fileService: FileService,
public msgBoxService: MsgBoxService, public msgBoxService: MsgBoxService,
private gameRoomService: GameRoomService, public gameRoomService: GameRoomService,
private loginUserService: LoginUserService, public loginUserService: LoginUserService,
public stateService: MD2StateService,
public signalRService: SignalRService, public signalRService: SignalRService,
public dlgService: NbDialogService, public dlgService: NbDialogService
public broadcastService: MD2BroadcastService
) { ) {
this.darknessPhaseRule = new CoreGameDarknessPhaseRule(); this.darknessPhaseRule = new CoreGameDarknessPhaseRule();
this.stateService.info = new MD2GameInfo(); this.info = new MD2GameInfo();
this.darknessPhaseRule.addTreasureToken.subscribe(treasureType => { this.darknessPhaseRule.addTreasureToken.subscribe(treasureType => {
this.addTreasure(treasureType, 1); this.addTreasure(treasureType, 1);
}); });
@ -103,11 +103,8 @@ export class MD2Service {
} }
} }
public get playerHero(): MD2HeroInfo {
return this.stateService.playerHero;
}
public get currentActivateHero(): MD2HeroInfo { public get currentActivateHero(): MD2HeroInfo {
return this.stateService.info.heros.find(h => h.uiActivating); return this.info.heros.find(h => h.uiActivating);
} }
@ -117,9 +114,6 @@ export class MD2Service {
// #region Public Methods (27) // #region Public Methods (27)
public get treasureBag() {
return this.stateService.treasureBag
}
public addTreasure(type: TreasureType, amount: number = 1) { public addTreasure(type: TreasureType, amount: number = 1) {
this.treasureBag.AddItem(new TreasureItem(type, amount)); this.treasureBag.AddItem(new TreasureItem(type, amount));
@ -129,17 +123,17 @@ export class MD2Service {
public darknessPhase() { public darknessPhase() {
this.heros.forEach(hero => { this.heros.forEach(hero => {
hero.remainActions = 3; hero.remainActions = 3;
this.broadcastService.broadcastHeroInfoToOwner(hero); this.broadcastHeroInfoToOwner(hero);
}); });
this.stateService.info.roundPhase = RoundPhase.HeroPhase; this.info.roundPhase = RoundPhase.HeroPhase;
if (!this.stateService.info.isBossFight) { if (!this.info.isBossFight) {
this.stateService.info.round++; this.info.round++;
if (this.darknessPhaseRule.runDarknessPhase()) { if (this.darknessPhaseRule.runDarknessPhase()) {
this.msgBoxService.show(`${NumberUtils.Ordinal(this.stateService.info.round)} Hero Phase`, { icon: ADIcon.INFO }); this.msgBoxService.show(`${NumberUtils.Ordinal(this.info.round)} Hero Phase`, { icon: ADIcon.INFO });
} }
} else { } else {
this.stateService.info.boss.darknessPhase(); this.info.boss.darknessPhase();
this.msgBoxService.show(`Boss Fight - ${NumberUtils.Ordinal(this.stateService.info.boss.rounds)} Hero Phase`, { icon: ADIcon.INFO }); this.msgBoxService.show(`Boss Fight - ${NumberUtils.Ordinal(this.info.boss.rounds)} Hero Phase`, { icon: ADIcon.INFO });
} }
//this.runNextPhase(); //this.runNextPhase();
@ -155,9 +149,9 @@ export class MD2Service {
level = 5; level = 5;
} }
if (isRoamingMonster) { if (isRoamingMonster) {
mobDeck = this.stateService.roamingMobDeck; mobDeck = this.roamingMobDeck;
} else { } else {
mobDeck = this.stateService.mobDeck; mobDeck = this.mobDeck;
} }
if (mobDeck.drawingItems.filter(m => (m as MobInfo).level == level) if (mobDeck.drawingItems.filter(m => (m as MobInfo).level == level)
@ -184,13 +178,10 @@ export class MD2Service {
exitingMob.skills = newSpawnMob.skills; exitingMob.skills = newSpawnMob.skills;
} else { } else {
if (isRoamingMonster) { if (isRoamingMonster) {
newSpawnMob = CoreGameRMFactories.find(f => f.mobName == newSpawnMob.name).generate(level); newSpawnMob = this.generateMob(isRoamingMonster, newSpawnMob.name, level);
newSpawnMob.hp = newSpawnMob.hpPerHero * this.playerAmount;
newSpawnMob.unitRemainHp = newSpawnMob.hp;
newSpawnMob.mobAmount = 0;
this.roamingMonsters.push(newSpawnMob); this.roamingMonsters.push(newSpawnMob);
} else { } else {
newSpawnMob = this.generateMob(newSpawnMob.name, level); newSpawnMob = this.generateMob(isRoamingMonster, newSpawnMob.name, level);
this.mobs.push(newSpawnMob); this.mobs.push(newSpawnMob);
} }
@ -202,16 +193,12 @@ export class MD2Service {
return { exitingMob, mob: newSpawnMob }; return { exitingMob, mob: newSpawnMob };
} }
private generateMob(mobName: string, level: number) { private generateMob(isRoamingMonster: boolean, mobName: string, level: number) {
let dbMobInfo = this.allMobInfos.find(m => m.name == mobName); let dbMobInfo = isRoamingMonster ? this.allRoamingMonsterInfos.find(m => m.name == mobName) : this.allMobInfos.find(m => m.name == mobName);
let levelInfo = dbMobInfo.mobLevelInfos.find(l => l.level <= level); let levelInfo = dbMobInfo.mobLevelInfos.find(l => l.level <= level);
let skills = dbMobInfo.skills.filter(s => s.level <= level).sort((a, b) => b.level - a.level); let skills = dbMobInfo.skills.filter(s => s.level <= level).sort((a, b) => b.seq - a.seq);
//Distinct by SkillType and Skill Name, take the one with the highest level //Distinct by SkillType and Skill Name, take the one with the highest level
let mobInfo = new MobInfo({ let mobInfo = new MobInfo({
name: dbMobInfo.name, name: dbMobInfo.name,
hp: levelInfo.hpPerHero, hp: levelInfo.hpPerHero,
@ -219,6 +206,7 @@ export class MD2Service {
level: level, level: level,
rewardTokens: levelInfo.rewardTokens, rewardTokens: levelInfo.rewardTokens,
defenseInfo: levelInfo.defenceInfo, defenseInfo: levelInfo.defenceInfo,
type: dbMobInfo.type,
skills: [] skills: []
}); });
@ -229,18 +217,52 @@ 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) {
mobInfo.leaderImgUrl = mobInfo.leaderImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`); case MobType.Mob:
mobInfo.minionImgUrl = mobInfo.minionImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`); mobInfo.leaderImgUrl = mobInfo.leaderImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Leader.png`);
mobInfo.minionImgUrl = mobInfo.minionImgUrl || MD2_IMG_URL(`/${GameBundle[dbMobInfo.from]}/Mobs/${mobInfo.name}/Minion.png`);
break;
case MobType.RoamingMonster:
mobInfo.leaderImgUrl = mobInfo.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(`/Boss/${mobInfo.name}-Stand.png`);
mobInfo.minionImgUrl = null;
break;
default:
break;
}
mobInfo.fixedCarriedTreasure = []; mobInfo.fixedCarriedTreasure = [];
levelInfo.fixedRareTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Rare, levelInfo.fixedRareTreasure)); levelInfo.fixedRareTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Rare, levelInfo.fixedRareTreasure));
levelInfo.fixedEpicTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Epic, levelInfo.fixedEpicTreasure)); levelInfo.fixedEpicTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Epic, levelInfo.fixedEpicTreasure));
levelInfo.fixedLegendTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Legendary, levelInfo.fixedLegendTreasure)); levelInfo.fixedLegendTreasure > 0 && mobInfo.fixedCarriedTreasure.push(new TreasureItem(TreasureType.Legendary, levelInfo.fixedLegendTreasure));
//Maybe add more mobs later
mobInfo.mobAmount = this.playerAmount + 1;
switch (dbMobInfo.type) {
case MobType.Mob:
//Maybe add more mobs later
mobInfo.mobAmount = this.playerAmount + 1;
break;
case MobType.RoamingMonster:
case MobType.Boss:
mobInfo.hp = levelInfo.hpPerHero * this.playerAmount;
mobInfo.unitRemainHp = mobInfo.hp;
mobInfo.mobAmount = 0;
mobInfo.attackInfos = [this.getAttackInfo(levelInfo.attackInfo)];
let altAttackInfo = levelInfo.alterAttackInfo;
if (altAttackInfo
&& (altAttackInfo.black > 0 || altAttackInfo.blue > 0 || altAttackInfo.green > 0 || altAttackInfo.orange > 0 || altAttackInfo.red > 0 || altAttackInfo.yellow > 0)
) {
mobInfo.attackInfos.push(this.getAttackInfo(altAttackInfo));
}
break;
}
return mobInfo; return mobInfo;
} }
private getAttackInfo(attackInfo: MD2DiceSet): AttackInfo {
return new AttackInfo(attackInfo.type as unknown as MD2Icon, attackInfo.yellow, attackInfo.orange, attackInfo.red, attackInfo.black);
}
public enemyPhase() { public enemyPhase() {
//this.msgBoxService //this.msgBoxService
this.enemyPhaseMobs = this.roamingMonsters.concat(this.mobs); this.enemyPhaseMobs = this.roamingMonsters.concat(this.mobs);
@ -268,9 +290,9 @@ export class MD2Service {
} else { } else {
this.info.boss = new BossMicheal(this); this.info.boss = new BossMicheal(this);
} }
this.stateService.info.roamingMonsters = []; this.info.roamingMonsters = [];
this.stateService.info.mobs = []; this.info.mobs = [];
this.stateService.info.isBossFight = true; this.info.isBossFight = true;
this.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.hp = this.info.boss.info.hpPerHero * this.info.heros.length;
this.info.boss.info.unitRemainHp = this.info.boss.info.hp; this.info.boss.info.unitRemainHp = this.info.boss.info.hp;
@ -284,14 +306,14 @@ export class MD2Service {
hero.uiActivating = false; hero.uiActivating = false;
hero.uiBossFight = true; hero.uiBossFight = true;
}); });
this.broadcastService.broadcastAllHeroInfoToAll(); this.broadcastGameInfo();
} }
}); });
//this.sendMsgboxMsg //this.sendMsgboxMsg
} }
public activateBoss() { public activateBoss() {
this.stateService.info.boss.activating(); this.info.boss.activating();
} }
public fileList(folderPath: string) { public fileList(folderPath: string) {
return this.fileService.FileList('Images/MD2/' + folderPath); return this.fileService.FileList('Images/MD2/' + folderPath);
@ -312,8 +334,8 @@ export class MD2Service {
hero.mp += levelUpInfo.extraMp; hero.mp += levelUpInfo.extraMp;
hero.mpMaximum += levelUpInfo.extraMp; hero.mpMaximum += levelUpInfo.extraMp;
hero.exp = levelUpInfo.currentExp; hero.exp = levelUpInfo.currentExp;
this.broadcastService.broadcastHeroInfoToOwner(hero); this.broadcastHeroInfoToOwner(hero);
this.sendMsgboxMsg(hero.playerInfo.signalRClientId, { title: 'Level Up', text: 'Please do a skill level up!', icon: ADIcon.INFO }); this.sendMsgboxMsg(hero.playerInfo.signalRClientId, { title: `Level Up Lv.${hero.level}`, text: 'Please do a skill level up!', icon: ADIcon.INFO });
levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp); levelUpInfo = MD2Rules.checkCoreGameLevelup(hero.level, hero.exp);
} }
} }
@ -332,9 +354,9 @@ export class MD2Service {
level = 3; level = 3;
} }
if (isRoamingMonsters) { if (isRoamingMonsters) {
return this.stateService.imgUrl(`Mobs/CoreGame/RoamingMonsters/${name}/${level}.png`); return this.imgUrl(`Mobs/CoreGame/RoamingMonsters/${name}/${level}.png`);
} else { } else {
return this.stateService.imgUrl(`Mobs/CoreGame/Mobs/${name}/${level}.png`); return this.imgUrl(`Mobs/CoreGame/Mobs/${name}/${level}.png`);
} }
} }
@ -344,7 +366,7 @@ export class MD2Service {
hero.hp = hero.hpMaximum; hero.hp = hero.hpMaximum;
hero.mp = hero.mpMaximum; hero.mp = hero.mpMaximum;
hero.exp = 0; hero.exp = 0;
this.stateService.playerHero = hero; this.playerHero = hero;
// let message = { // let message = {
// receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession, // receiver: { isGroup: true, sessionId: this.gameRoomService.gameRoomId } as SignalRSession,
// from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId }, // from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId },
@ -359,12 +381,12 @@ export class MD2Service {
public runNextPhase() { public runNextPhase() {
this.stateService.info.roundPhase++; this.info.roundPhase++;
switch (this.stateService.info.roundPhase) { switch (this.info.roundPhase) {
case RoundPhase.HeroPhase: case RoundPhase.HeroPhase:
this.heros.forEach(hero => { this.heros.forEach(hero => {
hero.remainActions = 3; hero.remainActions = 3;
this.broadcastService.broadcastHeroInfoToOwner(hero); this.broadcastHeroInfoToOwner(hero);
}); });
break; break;
case RoundPhase.EnemyPhase: case RoundPhase.EnemyPhase:
@ -379,14 +401,14 @@ export class MD2Service {
default: break; default: break;
} }
let parameters = {}; let parameters = {};
parameters['phase'] = this.stateService.info.roundPhase; parameters['phase'] = this.info.roundPhase;
this.broadcastService.broadcastMessage('roundPhase', '', parameters); this.broadcastMessage('roundPhase', '', parameters);
} }
public sendMsgboxMsg(playerSessionId: string, msg: Partial<MessageBoxConfig>) { public sendMsgboxMsg(playerSessionId: string, msg: Partial<MessageBoxConfig>) {
let message = { let message = {
receiver: { isGroup: false, sessionId: playerSessionId } as SignalRSession, receiver: { isGroup: false, connectionId: playerSessionId } as SignalRClient,
from: { isGroup: false, sessionId: this.loginUserService.userAccess.signalRSessionId }, from: { isGroup: false, connectionId: this.loginUserService.userAccess.signalRConnectionId },
actionType: 'message', actionType: 'message',
actionName: 'popup', actionName: 'popup',
} as SignalRMessage; } as SignalRMessage;
@ -396,7 +418,7 @@ export class MD2Service {
} }
public getTargetHerosByFilter(targetType: AttackTarget, onlyOne: boolean = false) { public getTargetHerosByFilter(targetType: AttackTarget, onlyOne: boolean = false) {
return MD2Logic.getTargetHerosByFilter(this.stateService.info.heros, targetType, onlyOne); return MD2Logic.getTargetHerosByFilter(this.info.heros, targetType, onlyOne);
} }
public getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) { public getTargetHerosHtml(beenAttackedHero: MD2HeroInfo[]) {
@ -419,32 +441,193 @@ export class MD2Service {
default: default:
break; break;
} }
this.broadcastService.broadcastHeroInfoToAll(this.currentActivateHero); this.broadcastHeroInfoToAll(this.currentActivateHero, true);
this.broadcastService.broadcastHeroInfoToOwner(this.currentActivateHero); //this.broadcastHeroInfoToOwner(this.currentActivateHero);
this.msgBoxService.show( this.msgBoxService.show(
this.stateService.imgHtml('/Fountain/Cover.png', '') this.imgHtml('/Fountain/Cover.png', '')
, { text: `${this.currentActivateHero.heroFullName} Recovered ${result.description}.` }); , { text: `${this.currentActivateHero.heroFullName} Recovered ${result.description}.` });
} }
public openTreasureChest() { public openTreasureChest() {
let result = new DrawingBag<DrawingItem>([ let result = new DrawingBag<DrawingItem>([
new DrawingItem('Common', `${this.stateService.treasureImageHtml(TreasureType.Common)} x 2`, '', 10), new DrawingItem('Common', `${this.treasureImageHtml(TreasureType.Common)} x 2`, '', 10),
new DrawingItem('Open Treasure Chest', `${this.stateService.treasureImageHtml(TreasureType.Rare)} x 2`, '', 5), new DrawingItem('Open Treasure Chest', `${this.treasureImageHtml(TreasureType.Rare)} x 2`, '', 5),
]) ])
.Draw(1)[0]; .Draw(1)[0];
this.msgBoxService.show( this.msgBoxService.show(
this.stateService.imgHtml('/TreasureChest/SmallTresureChest.jpg', ''), { text: `${this.currentActivateHero.heroFullName} gets ${result.description}.` }); this.imgHtml('/TreasureChest/SmallTresureChest.jpg', ''), { text: `${this.currentActivateHero.heroFullName} gets ${result.description}.` });
} }
public openGreatTreasureChest() { public openGreatTreasureChest() {
let result = new DrawingBag<DrawingItem>([ let result = new DrawingBag<DrawingItem>([
new DrawingItem('Common', `${this.stateService.treasureImageHtml(TreasureType.Rare)} x 3`, '', 3), new DrawingItem('Common', `${this.treasureImageHtml(TreasureType.Rare)} x 3`, '', 3),
new DrawingItem('Open Treasure Chest', `${this.stateService.treasureImageHtml(TreasureType.Epic)} x 3`, '', 1), new DrawingItem('Open Treasure Chest', `${this.treasureImageHtml(TreasureType.Epic)} x 3`, '', 1),
]).Draw(1)[0]; ]).Draw(1)[0];
this.msgBoxService.show( this.msgBoxService.show(
this.stateService.imgHtml('/TreasureChest/BigTresureChest.png', ''), { text: `${this.currentActivateHero.heroFullName} gets ${result.description}.` }); this.imgHtml('/TreasureChest/BigTresureChest.png', ''), { text: `${this.currentActivateHero.heroFullName} gets ${result.description}.` });
} }
public iconHtml(icon: MD2Icon, cssClass = '') {
if (icon == MD2Icon.Fire) {
cssClass += ' g-color-google-plus ';
}
if (icon == MD2Icon.Frost || icon == MD2Icon.Mana) {
cssClass += ' g-color-aqua ';
}
if (icon < MD2Icon.RedDice) {
return `<span md2-icon='${String.fromCharCode(65 + icon)}' class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
} else if (icon < MD2Icon.TreasureToken) {
return `<span md2-icon='${String.fromCharCode(65 + icon)}' class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
} else {
if (!cssClass) {
cssClass = 'g-height-25 mr-1';
}
//image based icons
switch (icon) {
case MD2Icon.HP_Color:
return this.imgHtml('HeartIcon.png', cssClass);
case MD2Icon.Mana_Color:
return this.imgHtml('ManaIcon.png', cssClass);
case MD2Icon.CorruptToken:
return this.imgHtml('Tokens/CorruptToken.png', cssClass);
case MD2Icon.TimeToken:
return this.imgHtml('Tokens/TimeToken.png', cssClass);
case MD2Icon.FireToken:
return this.imgHtml('Tokens/FireToken.png', cssClass);
case MD2Icon.FrozenToken:
return this.imgHtml('Tokens/FrozenToken.png', cssClass);
case MD2Icon.TreasureToken:
return this.imgHtml('TreasureToken/Cover.png', cssClass);
case MD2Icon.TreasureToken_Common:
return this.imgHtml('TreasureToken/Common.png', cssClass);
case MD2Icon.TreasureToken_Rare:
return this.imgHtml('TreasureToken/Rare.png', cssClass);
case MD2Icon.TreasureToken_Epic:
return this.imgHtml('TreasureToken/Epic.png', cssClass);
case MD2Icon.TreasureToken_Legendary:
return this.imgHtml('TreasureToken/Legendary.png', cssClass);
}
}
}
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`);
}
public treasureImageHtml(type: TreasureType) {
return this.imgHtml(`TreasureToken/${TreasureType[type]}.png`, 'g-height-40 mr-1');
}
public broadcastAllHeroInfoToAll() {
let message = {
receiver: { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient,
from: { isGroup: false, connectionId: this.loginUserService.userAccess.signalRConnectionId },
actionType: 'heroes',
actionName: 'updateAll',
} as SignalRMessage;
message.parameters = { heros: JSON.stringify(this.info.heros) };
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
this.broadcastMobsInfo();
}
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, fromDashboard: boolean = false) {
let message = {
receiver: { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient,
from: { isGroup: fromDashboard, connectionId: fromDashboard ? this.gameRoomService.gameRoomId : 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, connectionId: hero.playerInfo.signalRClientId } as SignalRClient,
from: { isGroup: true, connectionId: 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, connectionId: sessionId } as SignalRClient,
from: { isGroup: false, connectionId: this.loginUserService.userAccess.signalRConnectionId },
actionType: actionType,
actionName: actionName,
parameters: parameters
} as SignalRMessage;
if (sessionId == null) {
message.receiver = { isGroup: true, connectionId: this.gameRoomService.gameRoomId } as SignalRClient
} else {
message.receiver = { isGroup: false, connectionId: this.gameRoomService.gameRoomId } as SignalRClient
}
this.gameRoomService.sendMessage(message).pipe(first()).subscribe(result => {
});
}
public broadcastGameInfo() {
let parameters = {};
if (this.info.boss) {
this.info.boss.md2Service = undefined;
}
parameters['gameInfo'] = JSON.stringify(this.info);
if (this.info.boss) {
this.info.boss.md2Service = this;
}
this.broadcastMessage('GameRoom', 'update', parameters);
}
broadcastFetchGameInfo() {
this.broadcastMessage('GameRoom', 'getGameInfo', {});
}
public broadcastRoundPhase() {
let parameters = {};
parameters['phase'] = JSON.stringify(this.info.roundPhase);
this.broadcastMessage('GameRoom', 'phase', parameters);
}
public broadcastMobsInfo() {
let parameters = {};
parameters['roamingMonsters'] = JSON.stringify(this.info.roamingMonsters);
parameters['mobs'] = JSON.stringify(this.info.mobs);
this.broadcastMessage('mobs', 'update', parameters);
}
public broadcastMyHeroInfo() {
this.broadcastHeroInfoToAll(this.playerHero);
}
// #endregion Public Methods (27) // #endregion Public Methods (27)
} }
@ -461,7 +644,7 @@ export class MD2GameInfo {
this.mobs = this.mobs.map(m => new MobInfo(m)); this.mobs = this.mobs.map(m => new MobInfo(m));
this.roamingMonsters = this.roamingMonsters.map(m => new MobInfo(m)); this.roamingMonsters = this.roamingMonsters.map(m => new MobInfo(m));
if (this.boss.info) { if (this.boss && this.boss.info) {
this.boss.info = new MobInfo(this.boss.info); this.boss.info = new MobInfo(this.boss.info);
} }
this.heros = this.heros.map(h => new MD2HeroInfo(h)); this.heros = this.heros.map(h => new MD2HeroInfo(h));
@ -472,6 +655,7 @@ export class MD2GameInfo {
public mobs: MobInfo[] = []; public mobs: MobInfo[] = [];
public roamingMonsters: MobInfo[] = []; public roamingMonsters: MobInfo[] = [];
public heros: MD2HeroInfo[] = []; public heros: MD2HeroInfo[] = [];
public disconnectedHeroes: MD2HeroInfo[] = [];
public round = 1; public round = 1;
public roundPhase: RoundPhase = RoundPhase.HeroPhase; public roundPhase: RoundPhase = RoundPhase.HeroPhase;
public showAttackBtn: boolean = false; public showAttackBtn: boolean = false;

View File

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

View File

@ -23,18 +23,19 @@ export class GameRoomService {
return { return {
name: this.loginUserService.userAccess.firstName, name: this.loginUserService.userAccess.firstName,
id: this.loginUserService.userAccess.memberId, id: this.loginUserService.userAccess.memberId,
signalRClientId: this.loginUserService.userAccess.signalRSessionId, signalRClientId: this.loginUserService.userAccess.signalRConnectionId,
isPlayer: true, isPlayer: true,
gameRoomId: this.gameRoomId, gameRoomId: this.gameRoomId,
tabId: this.loginUserService.sessionTabId tabId: this.loginUserService.sessionTabId
} as IGamePlayer; } as IGamePlayer;
} }
createGameRoom(name: string) { createGameRoom(name: string) {
let gameRoom = this.currentPlayer(); //Using current player to create game room
gameRoom.gameRoomId = gameRoom.id; let currentPlayer = this.currentPlayer();
this.gameRoomId = gameRoom.id; currentPlayer.gameRoomId = currentPlayer.id;
this.gameRoomId = currentPlayer.id;
this.http.post<boolean>(GAME_ROOM_URL, this.http.post<boolean>(GAME_ROOM_URL,
JSON.stringify(gameRoom), { JSON.stringify(currentPlayer), {
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -14,14 +14,14 @@ export class LoginUserService {
} }
setSignalRConnectionId(id: string) { setSignalRConnectionId(id: string) {
this.userAccess.signalRSessionId = id; this.userAccess.signalRConnectionId = id;
} }
public get sessionTabId(): string { public get sessionTabId(): string {
return sessionStorage.getItem('tabId'); return localStorage.getItem('tabId');
} }
public set sessionTabId(v: string) { public set sessionTabId(v: string) {
sessionStorage.setItem('tabId', v); localStorage.setItem('tabId', v);
} }
} }

View File

@ -15,7 +15,7 @@ const SIGNAL_R_URL = (id: string = null) => { return `${environment.apiUrl}/${id
}) })
export class SignalRService { export class SignalRService {
ReceivedSignalRMessageSubject = new Subject<SignalRMessage>(); ReceivedSignalRMessageSubject = new Subject<SignalRMessage>();
signalRMessageConnSubject = new Subject<any>(); signalRMessageConnSubject = new Subject<SignalRConnectionState>();
private hubConnection: signalR.HubConnection private hubConnection: signalR.HubConnection
constructor( constructor(
private loginUserService: LoginUserService, private loginUserService: LoginUserService,
@ -42,7 +42,10 @@ export class SignalRService {
} }
public startSignalRConnection(gameRoomId: string = '') { public startSignalRConnection(gameRoomId: string = '') {
this.loginUserService.sessionTabId = UuidUtils.generate(); this.signalRMessageConnSubject.next({ status: 'connecting' });
if (!this.loginUserService.sessionTabId) {
this.loginUserService.sessionTabId = UuidUtils.generate();
}
const tree = this.router.createUrlTree([], { const tree = this.router.createUrlTree([], {
queryParams: { queryParams: {
@ -63,13 +66,21 @@ export class SignalRService {
.withUrl(`${SIGNAL_R_URL('GameRoomHub')}?${params.toString()}`) .withUrl(`${SIGNAL_R_URL('GameRoomHub')}?${params.toString()}`)
.withAutomaticReconnect() .withAutomaticReconnect()
.build(); .build();
this.registerHubConnectionLifecycleHandlers();
let me = this let me = this
this.hubConnection this.hubConnection
.start() .start()
.then(() => { .then(() => {
me.setSignalRConnectionId(this.hubConnection.connectionId); me.setSignalRConnectionId(this.hubConnection.connectionId);
this.signalRMessageConnSubject.next({
status: 'connected',
connectionId: this.hubConnection.connectionId
});
})
.catch(err => {
console.log('Error while starting connection: ' + err);
this.signalRMessageConnSubject.next({ status: 'error', error: err });
}) })
.catch(err => console.log('Error while starting connection: ' + err))
this.hubConnection.on("ReceivedMessage", (jsonString) => { this.hubConnection.on("ReceivedMessage", (jsonString) => {
this.BroadcastAPIMessageReceive(JSON.parse(jsonString)); this.BroadcastAPIMessageReceive(JSON.parse(jsonString));
}); });
@ -77,12 +88,37 @@ export class SignalRService {
} }
public setSignalRConnectionId(connectionId: string) { public setSignalRConnectionId(connectionId: string) {
this.loginUserService.userAccess.signalRSessionId = connectionId; this.loginUserService.userAccess.signalRConnectionId = connectionId;
this.loginUserService.signalRInitialized.next(connectionId); this.loginUserService.signalRInitialized.next(connectionId);
console.log('GameRoomHub start connection: ' + this.hubConnection.connectionId) console.log('GameRoomHub start connection: ' + this.hubConnection.connectionId)
} }
private registerHubConnectionLifecycleHandlers() {
this.hubConnection.onreconnecting(error => {
console.warn('SignalR reconnecting...', error);
this.signalRMessageConnSubject.next({ status: 'reconnecting', error });
});
this.hubConnection.onreconnected(connectionId => {
console.log('SignalR reconnected with new connectionId', connectionId);
this.setSignalRConnectionId(connectionId);
this.signalRMessageConnSubject.next({ status: 'reconnected', connectionId });
});
this.hubConnection.onclose(error => {
console.warn('SignalR connection closed', error);
this.clearSignalRConnectionId();
this.signalRMessageConnSubject.next({ status: 'closed', error });
});
}
private clearSignalRConnectionId() {
if (this.loginUserService?.userAccess) {
this.loginUserService.userAccess.signalRConnectionId = null;
}
}
BroadcastAPIMessageReceive(msg: SignalRMessage) { BroadcastAPIMessageReceive(msg: SignalRMessage) {
@ -100,16 +136,23 @@ export class SignalRService {
} }
export interface SignalRMessage { export interface SignalRMessage {
from: SignalRSession; from: SignalRClient;
receiver: SignalRSession; receiver: SignalRClient;
actionType: string; actionType: string;
actionName: string; actionName: string;
parameters: { [key: string]: string; }; parameters: { [key: string]: string; };
value: any;
jsonValue: string; jsonValue: string;
} }
export interface SignalRSession { export interface SignalRClient {
sessionId: string; connectionId: string;
name: string; name: string;
isGroup: boolean; isGroup: boolean;
} }
export interface SignalRConnectionState {
status: 'connecting' | 'connected' | 'reconnecting' | 'reconnected' | 'closed' | 'error';
connectionId?: string;
error?: any;
}

View File

@ -24,3 +24,9 @@ p {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
} }
button,
a,
input {
touch-action: manipulation;
}

View File

@ -13,7 +13,7 @@ const urls = [
'https://api.golife.love' 'https://api.golife.love'
]; ];
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,

View File

@ -6,8 +6,9 @@
<title>真幸福的幸福小組</title> <title>真幸福的幸福小組</title>
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="favicon.png"> <link rel="icon" type="image/png" href="favicon.png">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<script defer <script defer